From 1a65a16e569c8a6dde97683a4a9e5b2cb8e82d5c Mon Sep 17 00:00:00 2001 From: Enoch Riese Date: Tue, 9 Aug 2022 16:16:06 -0500 Subject: [PATCH] separate print layout from draft layout. add better buttons --- packages/core/src/utils.js | 44 ++++--- sites/shared/components/icons/clear.js | 4 +- sites/shared/components/icons/flip.js | 15 +++ sites/shared/components/icons/rotate.js | 7 ++ sites/shared/components/icons/sheet.js | 6 + .../components/workbench/draft/part/index.js | 13 +- .../workbench/layout/draft/buttons.js | 66 +++++++++++ .../workbench/layout/draft/index.js | 47 ++++---- .../components/workbench/layout/draft/part.js | 111 ++++++++++-------- .../workbench/layout/print/index.js | 14 ++- .../workbench/layout/print/settings.js | 2 +- .../shared/components/workbench/menu/index.js | 2 +- sites/shared/components/wrappers/workbench.js | 6 +- sites/shared/hooks/useGist.js | 5 +- sites/shared/styles/globals.css | 8 -- sites/shared/styles/svg-freesewing-draft.css | 8 +- 16 files changed, 246 insertions(+), 112 deletions(-) create mode 100644 sites/shared/components/icons/flip.js create mode 100644 sites/shared/components/icons/rotate.js create mode 100644 sites/shared/components/icons/sheet.js create mode 100644 sites/shared/components/workbench/layout/draft/buttons.js diff --git a/packages/core/src/utils.js b/packages/core/src/utils.js index edc9ea06ffb..35595ae3f5b 100644 --- a/packages/core/src/utils.js +++ b/packages/core/src/utils.js @@ -362,25 +362,41 @@ export function pctBasedOn(measurement) { /** Generates the transform attributes needed for a given part */ export const generatePartTransform = (x, y, rotate, flipX, flipY, part) => { - const center = { - x: part.topLeft.x + (part.bottomRight.x - part.topLeft.x)/2, - y: part.topLeft.y + (part.bottomRight.y - part.topLeft.y)/2, + + const transforms = [] + let xTotal = x || 0; + let yTotal = y || 0; + let scaleX = 1 + let scaleY = 1 + + if (flipX) { + scaleX = -1 + xTotal += part.topLeft.x * 2 + part.width + } + if (flipY) { + scaleY = -1 + yTotal += part.topLeft.y * 2 + part.height } - const transforms = [`translate(${x},${y})`] - if (flipX) transforms.push( - 'scale(-1, 1)', - ) - if (flipY) transforms.push( - 'scale(1, -1)', - ) - if (rotate) transforms.push( - `rotate(${rotate})` + if (scaleX + scaleY < 2) { + transforms.push(`scale(${scaleX} ${scaleY})`) + } + + if (rotate) { + const center = { + x: part.topLeft.x + part.width/2, + y: part.topLeft.y + part.height/2, + } + + transforms.push(`rotate(${rotate} ${center.x} ${center.y})`) + } + + if (xTotal > 0 || yTotal > 0) transforms.unshift( + `translate(${xTotal} ${yTotal})` ) return { transform: transforms.join(' '), - 'transform-origin': `${center.x} ${center.y}` + // 'transform-origin': `${center.x} ${center.y}` } } - diff --git a/sites/shared/components/icons/clear.js b/sites/shared/components/icons/clear.js index cb2c653c78f..09f81422f19 100644 --- a/sites/shared/components/icons/clear.js +++ b/sites/shared/components/icons/clear.js @@ -1,6 +1,8 @@ +export const ClearIconInner = () => + const ClearIcon = ({ className='h-6 w-6' }) => ( - + ) diff --git a/sites/shared/components/icons/flip.js b/sites/shared/components/icons/flip.js new file mode 100644 index 00000000000..385e0f9c5d0 --- /dev/null +++ b/sites/shared/components/icons/flip.js @@ -0,0 +1,15 @@ +const Triangle = ({transform='translate(0,0)', fill="currentColor"}) => + +const Line = () => + +export const FlipIconInner = ({x=0, y=0, rotate=0, ...style}) => + + + + + +export default function({ className="h-6 w-6" }) { + return + + +} diff --git a/sites/shared/components/icons/rotate.js b/sites/shared/components/icons/rotate.js new file mode 100644 index 00000000000..53a7720e777 --- /dev/null +++ b/sites/shared/components/icons/rotate.js @@ -0,0 +1,7 @@ +export const RotateIconInner = ({flipX=false}) => + +export default function () { + return + + +} diff --git a/sites/shared/components/icons/sheet.js b/sites/shared/components/icons/sheet.js new file mode 100644 index 00000000000..ae29d54a7fa --- /dev/null +++ b/sites/shared/components/icons/sheet.js @@ -0,0 +1,6 @@ +/* Sourced from heroicons.com - Thanks guys! */ + +export default function () { + return + +} diff --git a/sites/shared/components/workbench/draft/part/index.js b/sites/shared/components/workbench/draft/part/index.js index 517d14ae35e..82b35b13ed1 100644 --- a/sites/shared/components/workbench/draft/part/index.js +++ b/sites/shared/components/workbench/draft/part/index.js @@ -1,4 +1,4 @@ -import React from 'react' +import {forwardRef} from 'react' import Path from '../path' import Point from '../point' import Snippet from '../snippet' @@ -85,7 +85,7 @@ const XrayPart = props => { ) } -export const PartInner = props => { +export const PartInner = forwardRef((props, ref) => { const { partName, part, gist } = props const grid = gist.paperless ? ( @@ -99,7 +99,7 @@ export const PartInner = props => { /> ) : null - return (<> + return ( {grid} { gist._state?.xray?.enabled && @@ -133,14 +133,15 @@ export const PartInner = props => { {...props} /> ))} - ) -} + ) +}) const Part = props => { const { partName, part} = props + return ( - {PartInner(props)} + ) } diff --git a/sites/shared/components/workbench/layout/draft/buttons.js b/sites/shared/components/workbench/layout/draft/buttons.js new file mode 100644 index 00000000000..a9e8fd7c9bb --- /dev/null +++ b/sites/shared/components/workbench/layout/draft/buttons.js @@ -0,0 +1,66 @@ +import {FlipIconInner} from 'shared/components/icons/flip' +import {RotateIconInner} from 'shared/components/icons/rotate' +import {ClearIconInner} from 'shared/components/icons/clear' +import { useTranslation } from 'next-i18next' + +const rectSize = 24 + +const Button = ({onClickCb, transform, Icon, children}) => { + const _onClick = (event) => { + event.stopPropagation(); + onClickCb(event); + } + + return + + + {children} + + } + +/** buttons for manipulating the part */ +const Buttons = ({ transform, flip, rotate, setRotate, resetPart, rotate90}) => { + const {t} = useTranslation('workbench') + return ( + + {rotate + ? + : + } + + + + + + + ) +} + +export default Buttons diff --git a/sites/shared/components/workbench/layout/draft/index.js b/sites/shared/components/workbench/layout/draft/index.js index aa555e8a3b5..8cbe6efe4e6 100644 --- a/sites/shared/components/workbench/layout/draft/index.js +++ b/sites/shared/components/workbench/layout/draft/index.js @@ -2,27 +2,15 @@ import { useEffect, useRef} from 'react' import Svg from '../../draft/svg' import Defs from '../../draft/defs' import Part from './part' +import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch" const Draft = props => { - if (!props.gistReady) {return null} - const { patternProps, gist, updateGist ,app, bgProps={} } = props - const { layout=false } = gist + const { patternProps, gist, updateGist, app, bgProps={}, fitLayoutPart = false, layoutType="printLayout"} = props + let layout = {...props.layout} const svgRef = useRef(null); - useEffect(() => { - if (!layout) { - // On the initial draft, core does the layout, so we set the layout to the auto-layout - // After this, core won't handle layout anymore. It's up to the user from this point onwards - updateGist(['layout'], { - ...patternProps.autoLayout, - width: patternProps.width, - height: patternProps.height - }, false) - } - }, [layout]) - - if (!patternProps || !layout) return null + if (!patternProps) return null // Helper method to update part layout and re-calculate width * height const updateLayout = (name, config, history=true) => { @@ -34,7 +22,9 @@ const Draft = props => { let topLeft = {x: 0, y: 0} let bottomRight = {x: 0, y: 0} for (const [pname, part] of Object.entries(patternProps.parts)) { + if (pname == props.layoutPart && !fitLayoutPart) continue let partLayout = newLayout.parts[pname]; + // Pages part does not have its topLeft and bottomRight set by core since it's added post-draft if (partLayout?.tl) { // set the pattern extremes @@ -49,7 +39,13 @@ const Draft = props => { newLayout.height = bottomRight.y - topLeft.y newLayout.bottomRight = bottomRight newLayout.topLeft = topLeft - updateGist(['layout'], newLayout, history) + + if (history) { + updateGist(['layouts', layoutType], newLayout, history) + } else { + // we don't put it in the gist if it shouldn't contribute to history because we need some the data calculated here for rendering purposes on the initial layout, but we don't want to actually save a layout until the user manipulates it + layout = newLayout + } } @@ -60,19 +56,25 @@ const Draft = props => { return (
+ + - + {[ - partList.filter(name => name === 'pages'), - partList.filter(name => name !== 'pages'), + partList.filter(name => name === props.layoutPart), + partList.filter(name => name !== props.layoutPart), ].map(list => list.map(name => ( { app, gist, updateLayout, + isLayoutPart: name === props.layoutPart }}/> )))} + +
) } diff --git a/sites/shared/components/workbench/layout/draft/part.js b/sites/shared/components/workbench/layout/draft/part.js index 8c473dcfe47..4cbd76ad0a8 100644 --- a/sites/shared/components/workbench/layout/draft/part.js +++ b/sites/shared/components/workbench/layout/draft/part.js @@ -49,33 +49,8 @@ import { getProps, angle } from '../../draft/utils' import { drag } from 'd3-drag' import { select } from 'd3-selection' import { useRef, useState, useEffect} from 'react' +import Buttons from './buttons' -/** buttons for manipulating the part */ -const Buttons = ({ transform, flip, rotate, setRotate, resetPart }) => { - const letter = 'F' - const style = { style: {fill: 'white', fontSize: 18, fontWeight: 'bold', textAnchor: 'middle'} } - - return ( - - {rotate - ? - : - } - - - {letter} - - flip('y')}> - - {letter} - - flip('x')}> - - {letter} - - - ) -} const Part = props => { const { layout, part, partName} = props @@ -88,20 +63,23 @@ const Part = props => { // Use a ref for direct DOM manipulation const partRef = useRef(null) const centerRef = useRef(null) + const innerRef = useRef(null) // State variable to switch between moving or rotating the part const [rotate, setRotate] = useState(false) // update the layout on mount useEffect(() => { - if (partRef.current) updateLayout(false) - }, [partRef]) + if (partRef.current && !props.isLayoutPart) { + updateLayout(false) + } + }, [partRef, partLayout]) // Initialize drag handler useEffect(() => { - if (!partRef.current) {return} + if (props.isLayoutPart) return handleDrag(select(partRef.current)) - }, [rotate, layout]) + }, [rotate, partRef, partLayout]) // These are kept as vars because re-rendering on drag would kill performance // Managing the difference between re-render and direct DOM updates makes this @@ -121,6 +99,17 @@ const Part = props => { /** get the delta rotation from the start of the drag event to now */ const getRotation = (event) => angle(center, event.subject) - angle(center, { x:event.x, y: event.y }); + const setTransforms = () => { + // get the transform attributes + const transforms = generatePartTransform(translateX, translateY, rotation, flipX, flipY, part); + + const me = select(partRef.current); + for (var t in transforms) { + me.attr(t, transforms[t]) + } + } + + let didDrag = false; const handleDrag = drag() // subject allows us to save data from the start of the event to use throughout event handing .subject(function(event) { @@ -131,8 +120,13 @@ const Part = props => { {x: translateX, y: translateY} }) .on('drag', function(event) { + if (!event.dx && !event.dy) return + if (rotate) { let newRotation = getRotation(event); + if (event.sourceEvent.shiftKey) { + newRotation = Math.ceil(newRotation/15) * 15 + } // reverse the rotation direction one time per flip. if we're flipped both directions, rotation will be positive again if (flipX) newRotation *= -1 if (flipY) newRotation *= -1 @@ -144,46 +138,47 @@ const Part = props => { translateY = event.y } - // get the transform attributes - const transforms = generatePartTransform(translateX, translateY, rotation, flipX, flipY, part); - - const me = select(this); - for (var t in transforms) { - me.attr(t, transforms[t]) - } + didDrag = true; + setTransforms() }) .on('end', function(event) { // save to gist - updateLayout() + if (didDrag) updateLayout() + + didDrag = false }) - const resetPart = () => { + const resetPart = (event) => { rotation = 0 flipX = 0 flipY = 0 updateLayout() } const toggleDragRotate = () => { - if (!partRef.current) {return} + if (!partRef.current || props.isLayoutPart) {return} updateLayout() setRotate(!rotate) } + const updateLayout = (history=true) => { - const partRect = partRef.current.getBoundingClientRect(); - const matrix = partRef.current.ownerSVGElement.getScreenCTM().inverse(); + if (!partRef.current || props.isLayoutPart) return + + setTransforms() + const partRect = innerRef.current.getBoundingClientRect(); + const matrix = innerRef.current.ownerSVGElement.getScreenCTM().inverse(); const domToSvg = (point) => DOMPointReadOnly.fromPoint(point).matrixTransform(matrix) // include the new top left and bottom right to ease calculating the pattern width and height const tl = domToSvg({x: partRect.left, y: partRect.top}); - const br = domToSvg({x: partRect.right, y: partRect.bottom}); + const br = domToSvg({x: partRect.right, y: props.isLayoutPart ? 0 : partRect.bottom}); props.updateLayout(partName, { move: { x: translateX, y: translateY, }, - rotate: rotation, + rotate: rotation % 360, flipX, flipY, tl, @@ -198,30 +193,44 @@ const Part = props => { updateLayout() } + const rotate90 = (direction = 1) => { + if (flipX) direction *= -1 + if (flipY) direction *= -1 + + rotation += 90 * direction + + updateLayout() + } + + if (Object.keys(part.snippets).length === 0 && Object.keys(part.paths).length === 0) return null; + return ( - {PartInner(props)} - {partName !== 'pages' && <> + + {!props.isLayoutPart && <> } diff --git a/sites/shared/components/workbench/layout/print/index.js b/sites/shared/components/workbench/layout/print/index.js index fe54ede1963..069404604fa 100644 --- a/sites/shared/components/workbench/layout/print/index.js +++ b/sites/shared/components/workbench/layout/print/index.js @@ -1,11 +1,10 @@ -import { useEffect } from 'react' +import { useEffect, useRef } from 'react' import { useTranslation } from 'next-i18next' import Settings from './settings' import Draft from '../draft/index' import pluginBuilder from './plugin' const PrintLayout = props => { - useEffect(() => { if (props.gist?._state?.xray?.enabled) props.updateGist( ['_state', 'xray', 'enabled'], @@ -15,14 +14,21 @@ const PrintLayout = props => { const { t } = useTranslation(['workbench']) - const draft = new props.design(props.gist).use(pluginBuilder( + const draft = props.draft + draft.use(pluginBuilder( props.gist?._state?.layout?.forPrinting?.page?.size, props.gist?._state?.layout?.forPrinting?.page?.orientation, )) let patternProps + let layout try { draft.draft() patternProps = draft.getRenderProps() + layout = draft.settings.layout === true ? { + ...patternProps.autoLayout, + width: patternProps.width, + height: patternProps.height + } : draft.settings.layout } catch(err) { console.log(err, props.gist) } @@ -47,6 +53,8 @@ const PrintLayout = props => { patternProps={patternProps} bgProps={bgProps} gistReady={props.gistReady} + layoutPart="pages" + layout={layout} /> ) diff --git a/sites/shared/components/workbench/layout/print/settings.js b/sites/shared/components/workbench/layout/print/settings.js index 8034e068010..b58c03f9324 100644 --- a/sites/shared/components/workbench/layout/print/settings.js +++ b/sites/shared/components/workbench/layout/print/settings.js @@ -26,7 +26,7 @@ const PrintLayoutSettings = props => {