From 054b7565e684e03cb2acaac2b47cf0efacadf88d Mon Sep 17 00:00:00 2001 From: Joost De Cock Date: Sat, 25 Apr 2020 13:29:02 +0200 Subject: [PATCH] feat(components): Added pan and zoom to Workbench. Closes #368 --- packages/components/package.json | 1 + packages/components/rollup.config.js | 37 ++-- packages/components/src/Draft/Svg/index.js | 5 +- packages/components/src/Draft/index.js | 2 + .../src/Workbench/DraftPattern/index.js | 184 +++++++++++++----- .../components/src/Workbench/Json/index.js | 20 ++ .../src/Workbench/SamplePattern/index.js | 47 ++--- .../components/src/Workbench/Zoombox/index.js | 158 +++++++++++++++ packages/components/src/Workbench/index.js | 34 +++- 9 files changed, 397 insertions(+), 91 deletions(-) create mode 100644 packages/components/src/Workbench/Json/index.js create mode 100644 packages/components/src/Workbench/Zoombox/index.js diff --git a/packages/components/package.json b/packages/components/package.json index e574a9d02d4..e68017c2cc2 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -44,6 +44,7 @@ "@material-ui/core": "^4.0.1", "@material-ui/icons": "^4.0.1", "@material-ui/lab": "^v4.0.0-alpha.14", + "react-svg-pan-zoom": "^3.8.0", "prismjs": "1.16.0", "file-saver": "^2.0.2" }, diff --git a/packages/components/rollup.config.js b/packages/components/rollup.config.js index 2f14560f2b6..428b0fdcf59 100644 --- a/packages/components/rollup.config.js +++ b/packages/components/rollup.config.js @@ -1,17 +1,17 @@ -import babel from "rollup-plugin-babel"; -import resolve from "rollup-plugin-node-resolve"; -import json from "rollup-plugin-json"; -import minify from "rollup-plugin-babel-minify"; -import peerDepsExternal from "rollup-plugin-peer-deps-external"; -import { name, version, description, author, license } from "./package.json"; -import components from "./src/index.js"; +import babel from 'rollup-plugin-babel' +import resolve from 'rollup-plugin-node-resolve' +import json from 'rollup-plugin-json' +import minify from 'rollup-plugin-babel-minify' +import peerDepsExternal from 'rollup-plugin-peer-deps-external' +import { name, version, description, author, license } from './package.json' +import components from './src/index.js' const createConfig = (component, module) => { return { - input: `./src/${component + "/"}index.js`, + input: `./src/${component + '/'}index.js`, output: { - file: `./${component}/index` + (module ? ".mjs" : ".js"), - format: module ? "es" : "cjs", + file: `./${component}/index` + (module ? '.mjs' : '.js'), + format: module ? 'es' : 'cjs', sourcemap: true }, plugins: [ @@ -19,8 +19,8 @@ const createConfig = (component, module) => { resolve({ modulesOnly: true }), json(), babel({ - exclude: "node_modules/**", - plugins: ["@babel/plugin-proposal-object-rest-spread"] + exclude: 'node_modules/**', + plugins: ['@babel/plugin-proposal-object-rest-spread'] }), minify({ comments: false, @@ -28,13 +28,16 @@ const createConfig = (component, module) => { banner: `/**\n * ${name}/${component} | v${version}\n * ${description}\n * (c) ${new Date().getFullYear()} ${author}\n * @license ${license}\n */` }) ] - }; -}; + } +} -const config = []; +const config = [] +// When developing, you can use this to only rebuild the components you're working on +let dev = false +let only = ['Workbench'] for (let component of components) { - config.push(createConfig(component, false)); + if (!dev || only.indexOf(component) !== -1) config.push(createConfig(component, false)) // Webpack doesn't handle .mjs very well //config.push(createConfig(component, true)); } -export default config; +export default config diff --git a/packages/components/src/Draft/Svg/index.js b/packages/components/src/Draft/Svg/index.js index 84742ae601b..af9be6ae531 100644 --- a/packages/components/src/Draft/Svg/index.js +++ b/packages/components/src/Draft/Svg/index.js @@ -7,7 +7,7 @@ const Svg = (props) => { 'xmlns:svg': 'http://www.w3.org/2000/svg', xmlnsXlink: 'http://www.w3.org/1999/xlink', xmlLang: props.language, - viewBox: `0 0 ${props.width} ${props.height}`, + viewBox: props.viewBox || `0 0 ${props.width} ${props.height}`, className: props.className, style: props.style } @@ -33,7 +33,8 @@ Svg.defaultProps = { design: false, language: 'en', className: 'freesewing draft', - style: {} + style: {}, + viewBox: false } export default Svg diff --git a/packages/components/src/Draft/index.js b/packages/components/src/Draft/index.js index d95f8f5cfb4..7b30b7341ca 100644 --- a/packages/components/src/Draft/index.js +++ b/packages/components/src/Draft/index.js @@ -13,6 +13,8 @@ const Draft = (props) => ( id={props.settings.idPrefix + 'svg'} design={props.design} style={props.style} + viewBox={props.viewBox} + className={props.className || 'freesewing draft'} > { +const DraftPattern = (props) => { const [design, setDesign] = useState(true) const [focus, setFocus] = useState(null) + const [viewBox, setViewBox] = useState(false) + const [hideAside, setHideAside] = useState(false) const raiseEvent = (type, data) => { if (type === 'clearFocusAll') { @@ -31,7 +43,7 @@ const DraftPattern = props => { setFocus(f) } - const svgToFile = svg => { + const svgToFile = (svg) => { const blob = new Blob([svg], { type: 'image/svg+xml;charset=utf-8' }) @@ -54,6 +66,27 @@ const DraftPattern = props => { const styles = { paragraph: { padding: '0 1rem' + }, + aside: { + maxWidth: '350px', + background: 'transparent', + border: 0, + fontSize: '90%', + boxShadow: '0 0 1px #cccc', + display: hideAside ? 'none' : 'block' + }, + icon: { + margin: '0 0.25rem' + }, + unhide: { + position: 'absolute', + top: '76px', + right: 0, + background: props.theme === 'dark' ? '#f8f9fa' : '#212529', + borderTopLeftRadius: '50%', + borderBottomLeftRadius: '50%', + width: '26px', + height: '30px' } } let pattern = new props.Pattern(props.gist.settings) @@ -73,57 +106,116 @@ const DraftPattern = props => { Prism.languages.javascript, 'javascript' ) + let iconProps = { + size: 'small', + style: styles.icon, + color: 'inherit' + } + const color = (check) => (check ? '#40c057' : '#fa5252') return (
-

- -

- -

gist

-
-
-        
+ + {hideAside && ( +
+ setHideAside(false)} + title="Show sidebar" + {...iconProps} + style={{ margin: 0 }} + > + + + + +
+ )}
-
- ); -}; + ) +} SamplePattern.propTypes = { gist: PropTypes.object.isRequired, @@ -46,12 +49,12 @@ SamplePattern.propTypes = { config: PropTypes.object.isRequired, raiseEvent: PropTypes.func.isRequired, Pattern: PropTypes.func.isRequired, - units: PropTypes.oneOf(["metric", "imperial"]) -}; + units: PropTypes.oneOf(['metric', 'imperial']) +} SamplePattern.defaultProps = { - units: "metric", + units: 'metric', pointInfo: null -}; +} -export default SamplePattern; +export default SamplePattern diff --git a/packages/components/src/Workbench/Zoombox/index.js b/packages/components/src/Workbench/Zoombox/index.js new file mode 100644 index 00000000000..de029706eea --- /dev/null +++ b/packages/components/src/Workbench/Zoombox/index.js @@ -0,0 +1,158 @@ +import React, { useState, useRef, useEffect } from 'react' +import Draft from '../../Draft' +import IconButton from '@material-ui/core/IconButton' +import ZoomIcon from '@material-ui/icons/Cancel' + +const Zoombox = (props) => { + const [from, setFrom] = useState(false) + const [to, setTo] = useState(false) + const [dragging, setDragging] = useState(false) + const [factor, setFactor] = useState(1) + const [box, setBox] = useState(false) + const [panning, setPanning] = useState(false) + const [falseAlarm, setFalseAlarm] = useState(false) + const [panFrom, setPanFrom] = useState(false) + const ref = useRef(null) + + useEffect(() => { + let box = ref.current.getBoundingClientRect() + setBox(box) + setFactor(props.patternProps.width / box.width) + }, []) + + const resetZoom = (evt) => { + evt.stopPropagation() + evt.preventDefault() + setFrom(false) + setTo(false) + setDragging(false) + props.setViewBox(false) + } + const startPan = (evt) => { + if (!dragging && !panning) { + evt.stopPropagation() + evt.preventDefault() + setPanning(true) + setPanFrom([evt.clientX, evt.clientY]) + } + } + const endPan = (evt) => { + if (!dragging && panning) { + evt.stopPropagation() + evt.preventDefault() + setPanning(false) + setPanFrom(false) + updateViewBox(evt) + //props.setViewBox(`${from[0] * factor} ${from[1] * factor} ${to[0] * factor} ${to[1] * factor}`) + } + } + const handlePan = (evt) => { + if (!dragging && panning) { + evt.stopPropagation() + evt.preventDefault() + let x, y + if (from[0] + (evt.clientX - panFrom[0]) <= -5) { + // Bump into left + } else if (from[1] + (evt.clientY - panFrom[1]) <= -5) { + // Bump into top + } else if (to[0] + (evt.clientX - panFrom[0]) >= box.width - 11) { + // Bump into right + } else if (to[1] + (evt.clientY - panFrom[1]) >= box.height - 11) { + // Bump into bottom + } else { + setPanFrom([evt.clientX, evt.clientY]) + setFrom([from[0] + (evt.clientX - panFrom[0]), from[1] + (evt.clientY - panFrom[1])]) + setTo([to[0] + (evt.clientX - panFrom[0]), to[1] + (evt.clientY - panFrom[1])]) + } + } + } + const handleMouseDown = (evt) => { + evt.stopPropagation() + evt.preventDefault() + setFrom([evt.clientX - box.x, evt.clientY - box.y]) + setTo([evt.clientX - box.x, evt.clientY - box.y]) + setDragging(1) + setPanning(false) + } + const handleMouseUp = (evt) => { + if (dragging == 2) { + updateViewBox(evt) + if (falseAlarm) setFalseAlarm(false) + } else setFalseAlarm(true) + setDragging(false) + setPanning(false) + evt.stopPropagation() + evt.preventDefault() + } + const handleMouseMove = (evt) => { + if (dragging) { + evt.stopPropagation() + evt.preventDefault() + if (dragging === 1) setDragging(2) + if (falseAlarm) setFalseAlarm(false) + setTo([evt.clientX - box.x, evt.clientY - box.y]) + } + } + const handleMouseOver = (evt) => { + evt.stopPropagation() + evt.preventDefault() + setFactor(props.patternProps.width / box.width) + } + const updateViewBox = (evt) => { + props.setViewBox( + from[0] * factor + + ' ' + + from[1] * factor + + ' ' + + (evt.clientX - box.x - from[0]) * factor + + ' ' + + (evt.clientY - box.y - from[1]) * factor + ) + } + + return ( + <> +
+ +
+ {box && from && to && dragging !== 1 && !falseAlarm && ( +
+ {!dragging && ( + + + + )} +
+ )} +
+
{false && JSON.stringify({ from, to, panFrom }, null, 2)}
+ + ) +} + +export default Zoombox diff --git a/packages/components/src/Workbench/index.js b/packages/components/src/Workbench/index.js index b1e3b9e7bbc..0be15d03de0 100644 --- a/packages/components/src/Workbench/index.js +++ b/packages/components/src/Workbench/index.js @@ -11,12 +11,13 @@ import LanguageIcon from '@material-ui/icons/Translate' import DarkModeIcon from '@material-ui/icons/Brightness3' import LanguageChooser from './LanguageChooser' import DraftPattern from './DraftPattern' +import Json from './Json' import SamplePattern from './SamplePattern' import Welcome from './Welcome' import Footer from '../Footer' import Measurements from './Measurements' -const Workbench = props => { +const Workbench = (props) => { const [display, setDisplay] = useState(null) const [pattern, setPattern] = useState(false) const [theme, setTheme] = useState('light') @@ -39,12 +40,12 @@ const Workbench = props => { }, [props.language]) const getDisplay = () => storage.get(props.config.name + '-display') - const saveDisplay = d => { + const saveDisplay = (d) => { setDisplay(d) storage.set(props.config.name + '-display', d) } const getMeasurements = () => storage.get(props.config.name + '-measurements') - const saveMeasurements = data => { + const saveMeasurements = (data) => { storage.set(props.config.name + '-measurements', data) props.updateGist(data, 'settings', 'measurements') } @@ -54,7 +55,7 @@ const Workbench = props => { setMeasurements(updatedMeasurements) saveMeasurements(updatedMeasurements) } - const preloadMeasurements = model => { + const preloadMeasurements = (model) => { let updatedMeasurements = { ...measurements, ...model @@ -97,6 +98,12 @@ const Workbench = props => { onClick: () => saveDisplay('measurements'), text: 'app.measurements', active: display === 'measurements' ? true : false + }, + json: { + type: 'button', + onClick: () => saveDisplay('json'), + text: 'JSON', + active: display === 'json' ? true : false } }, right: { @@ -148,6 +155,7 @@ const Workbench = props => { units={props.units} svgExport={svgExport} setSvgExport={setSvgExport} + theme={theme} /> ) break @@ -177,6 +185,24 @@ const Workbench = props => { /> ) break + case 'json': + main = + break + case 'inspect': + main = ( + + ) + break default: main = }