From 0e329e195d1baa70d32b60b90893886af442605f Mon Sep 17 00:00:00 2001 From: Enoch Riese Date: Sun, 4 Jun 2023 09:59:47 -0500 Subject: [PATCH 1/6] print view settings menu --- sites/shared/components/workbench/index.mjs | 61 ++++++----- .../workbench/layout/print/index.mjs | 78 ------------- .../workbench/menus/design-options/index.mjs | 2 +- .../workbench/menus/shared/inputs.mjs | 2 +- .../workbench/views/print/config.mjs | 52 +++++++++ .../workbench/views/print/index.mjs | 103 ++++++++++++++++++ .../components/workbench/views/print/menu.mjs | 42 +++++++ .../workbench/views/print/settings.mjs | 48 ++++++++ 8 files changed, 283 insertions(+), 105 deletions(-) delete mode 100644 sites/shared/components/workbench/layout/print/index.mjs create mode 100644 sites/shared/components/workbench/views/print/config.mjs create mode 100644 sites/shared/components/workbench/views/print/index.mjs create mode 100644 sites/shared/components/workbench/views/print/menu.mjs create mode 100644 sites/shared/components/workbench/views/print/settings.mjs diff --git a/sites/shared/components/workbench/index.mjs b/sites/shared/components/workbench/index.mjs index 8b96ae07ede..5a1ef70cb10 100644 --- a/sites/shared/components/workbench/index.mjs +++ b/sites/shared/components/workbench/index.mjs @@ -14,16 +14,22 @@ import { ModalSpinner } from 'shared/components/modal/spinner.mjs' // Views import { DraftView, ns as draftNs } from 'shared/components/workbench/views/draft/index.mjs' import { SaveView, ns as saveNs } from 'shared/components/workbench/views/save/index.mjs' +import { PrintView, ns as printNs } from 'shared/components/workbench/views/print/index.mjs' -export const ns = ['account', 'workbench', ...draftNs, ...saveNs] +export const ns = ['account', 'workbench', ...draftNs, ...saveNs, ...printNs] const defaultUi = { renderer: 'react', } +const views = { + draft: DraftView, + print: PrintView, +} + const draftViews = ['draft', 'test'] -export const Workbench = ({ design, Design, baseSettings, DynamicDocs, from, set }) => { +export const Workbench = ({ design, Design, baseSettings, DynamicDocs, from }) => { // Hooks const { t, i18n } = useTranslation(ns) const { language } = i18n @@ -72,34 +78,39 @@ export const Workbench = ({ design, Design, baseSettings, DynamicDocs, from, set } let viewContent = null - // Draft view - if (view === 'draft') { - // Generate the pattern here so we can pass it down to both the view and the options menu - const pattern = - settings.measurements && draftViews.includes(view) ? new Design(settings) : false + switch (view) { + // Save view + case 'save': + viewContent = + break + default: { + // Generate the pattern here so we can pass it down to both the view and the options menu + const pattern = settings.measurements !== undefined && new Design(settings) - // Return early if the pattern is not initialized yet - if (typeof pattern.getConfig !== 'function') return null + // Return early if the pattern is not initialized yet + if (typeof pattern.getConfig !== 'function') return null - const patternConfig = pattern.getConfig() - if (ui.renderer === 'svg') { - // Add theme to svg renderer - pattern.use(pluginI18n, { t }) - pattern.use(pluginTheme, { skipGrid: ['pages'] }) + const patternConfig = pattern.getConfig() + if (ui.renderer === 'svg') { + // Add theme to svg renderer + pattern.use(pluginI18n, { t }) + pattern.use(pluginTheme, { skipGrid: ['pages'] }) + } + + if (draftViews.includes(view)) { + // Draft the pattern or die trying + try { + pattern.draft() + } catch (error) { + console.log(error) + setError({JSON.stringify(error)}) + } + } + const View = views[view] + viewContent = } - // Draft the pattern or die trying - try { - pattern.draft() - } catch (error) { - console.log(error) - setError({JSON.stringify(error)}) - } - viewContent = } - // Save view - else if (view === 'save') viewContent = - return ( <> diff --git a/sites/shared/components/workbench/layout/print/index.mjs b/sites/shared/components/workbench/layout/print/index.mjs deleted file mode 100644 index ca99b1831cd..00000000000 --- a/sites/shared/components/workbench/layout/print/index.mjs +++ /dev/null @@ -1,78 +0,0 @@ -import { useEffect, useState } from 'react' -import { useTranslation } from 'next-i18next' -import { PrintLayoutSettings } from './settings.mjs' -import { Draft } from '../draft/index.mjs' -import { pagesPlugin } from '../plugin-layout-part.mjs' -import { - handleExport, - defaultPdfSettings, -} from 'shared/components/workbench/exporting/export-handler.mjs' -import { Popout } from 'shared/components/popout.mjs' - -export const PrintLayout = (props) => { - // disable xray - useEffect(() => { - if (props.gist?._state?.xray?.enabled) props.updateGist(['_state', 'xray', 'enabled'], false) - }) - - const { t } = useTranslation(['workbench', 'plugin']) - const [error, setError] = useState(false) - - const draft = props.draft - - // add the pages plugin to the draft - const layoutSettings = { - ...defaultPdfSettings, - ...props.gist?._state?.layout?.forPrinting?.page, - } - draft.use(pagesPlugin(layoutSettings)) - - let patternProps - try { - // draft the pattern - draft.draft() - patternProps = draft.getRenderProps() - } catch (err) { - console.log(err, props.gist) - } - const bgProps = { fill: 'none' } - - const exportIt = () => { - setError(false) - handleExport( - 'pdf', - props.gist, - props.design, - t, - props.app, - () => setError(false), - () => setError(true) - ) - } - - let name = props.design.designConfig.data.name - name = name.replace('@freesewing/', '') - return ( -
-

{t('layoutThing', { thing: name }) + ': ' + t('forPrinting')}

-
- - {error && ( - - {t('error')}: - {t('somethingWentWrong')} - - )} -
- -
- ) -} diff --git a/sites/shared/components/workbench/menus/design-options/index.mjs b/sites/shared/components/workbench/menus/design-options/index.mjs index de16921ce1d..b96add8db41 100644 --- a/sites/shared/components/workbench/menus/design-options/index.mjs +++ b/sites/shared/components/workbench/menus/design-options/index.mjs @@ -91,7 +91,7 @@ export const DesignOptions = ({ language, ns: menuNs, passProps: { settings }, - updateFunc: (name, value) => update.settings(['options', name], value), + updateFunc: (name, value) => update.settings(['options', ...name], value), }} /> ) diff --git a/sites/shared/components/workbench/menus/shared/inputs.mjs b/sites/shared/components/workbench/menus/shared/inputs.mjs index 57dccf18835..e80fd4edcc4 100644 --- a/sites/shared/components/workbench/menus/shared/inputs.mjs +++ b/sites/shared/components/workbench/menus/shared/inputs.mjs @@ -45,7 +45,7 @@ const useSharedHandlers = ({ dflt, updateFunc, name }) => { (newCurrent) => { if (newCurrent === dflt) newCurrent = undefined - updateFunc(name, newCurrent) + updateFunc([name], newCurrent) }, [dflt, updateFunc, name] ) diff --git a/sites/shared/components/workbench/views/print/config.mjs b/sites/shared/components/workbench/views/print/config.mjs new file mode 100644 index 00000000000..20a93648e52 --- /dev/null +++ b/sites/shared/components/workbench/views/print/config.mjs @@ -0,0 +1,52 @@ +import { measurementAsMm } from 'shared/utils.mjs' + +export const defaultPrintSettings = (units, inMm = true) => { + const margin = units === 'imperial' ? 0.5 : 1 + return { + size: 'a4', + orientation: 'portrait', + margin: inMm ? measurementAsMm(margin, units) : margin, + coverPage: true, + cutlist: true, + } +} +const sizes = ['a4', 'a3', 'a2', 'a1', 'a0', 'letter', 'tabloid'] +export const loadPrintConfig = (units) => { + const defaults = defaultPrintSettings(units, false) + const config = { + size: { + control: 2, + list: sizes, + dflt: defaults.size, + choiceTitles: {}, + valueTitles: {}, + }, + orientation: { + control: 2, + list: ['portrait', 'landscape'], + dflt: defaults.orientation, + }, + margin: { + control: 2, + min: units === 'imperial' ? 0.25 : 0.5, + max: 2.5, + step: units === 'imperial' ? 0.125 : 0.1, + dflt: defaults.margin, + }, + coverPage: { + control: 3, + dflt: defaults.coverPage, + }, + cutlist: { + control: 3, + dflt: defaults.cutlist, + }, + } + + sizes.forEach((s) => { + config.size.choiceTitles[s] = s + config.size.valueTitles[s] = s + }) + + return config +} diff --git a/sites/shared/components/workbench/views/print/index.mjs b/sites/shared/components/workbench/views/print/index.mjs new file mode 100644 index 00000000000..49c959fee9c --- /dev/null +++ b/sites/shared/components/workbench/views/print/index.mjs @@ -0,0 +1,103 @@ +import { useEffect, useState } from 'react' +import { useTranslation } from 'next-i18next' +// import { PrintLayoutSettings } from './settings.mjs' +// import { Draft } from '../draft/index.mjs' +import { pagesPlugin } from 'shared/components/workbench/layout/plugin-layout-part.mjs' +// import { +// handleExport, +// defaultPdfSettings, +// } from 'shared/components/workbench/exporting/export-handler.mjs' +// import { Popout } from 'shared/components/popout.mjs' +import get from 'lodash.get' +import { PanZoomPattern as ShowPattern } from 'shared/components/workbench/pan-zoom-pattern.mjs' +import { PrintMenu, ns as menuNs } from './menu.mjs' + +const viewNs = ['print'] +export const ns = [...viewNs, ...menuNs] + +const defaultPdfSettings = { + size: 'a4', + orientation: 'portrait', + margin: 10, + coverPage: true, + cutlist: true, +} + +export const PrintView = ({ + design, + pattern, + patternConfig, + setView, + settings, + ui, + update, + language, + account, + DynamicDocs, +}) => { + // disable xray + // useEffect(() => { + // if (props.gist?._state?.xray?.enabled) props.updateGist(['_state', 'xray', 'enabled'], false) + // }) + + const { t } = useTranslation(viewNs) + const [error, setError] = useState(false) + + // add the pages plugin to the draft + const layoutSettings = { + ...defaultPdfSettings, + ...get(ui, ['layout', 'print', 'page']), + } + + pattern.use(pagesPlugin(layoutSettings)) + + let patternProps + try { + // draft the pattern + pattern.draft() + patternProps = pattern.getRenderProps() + } catch (err) { + console.log(err) + } + const bgProps = { fill: 'none' } + + const exportIt = () => { + // setError(false) + // handleExport( + // 'pdf', + // props.gist, + // props.design, + // t, + // props.app, + // () => setError(false), + // () => setError(true) + // ) + } + + let name = design + return ( +
+

{t('layoutThing', { thing: name }) + ': ' + t('forPrinting')}

+
+
+ +
+
+ +
+
+
+ ) +} diff --git a/sites/shared/components/workbench/views/print/menu.mjs b/sites/shared/components/workbench/views/print/menu.mjs new file mode 100644 index 00000000000..49872ba7e06 --- /dev/null +++ b/sites/shared/components/workbench/views/print/menu.mjs @@ -0,0 +1,42 @@ +import { + DesignOptions, + ns as designMenuNs, +} from 'shared/components/workbench/menus/design-options/index.mjs' +import { + CoreSettings, + ns as coreMenuNs, +} from 'shared/components/workbench/menus/core-settings/index.mjs' +import { PrintSettings, ns as printMenuNs } from './settings.mjs' +export const ns = [...coreMenuNs, ...designMenuNs, ...printMenuNs] + +export const PrintMenu = ({ + design, + patternConfig, + settings, + ui, + update, + language, + account, + DynamicDocs, + inspector = false, +}) => { + const control = account.control + const menuProps = { + design, + patternConfig, + settings, + update, + language, + account, + DynamicDocs, + control, + } + + return ( + + ) +} diff --git a/sites/shared/components/workbench/views/print/settings.mjs b/sites/shared/components/workbench/views/print/settings.mjs new file mode 100644 index 00000000000..c5e6b60f76d --- /dev/null +++ b/sites/shared/components/workbench/views/print/settings.mjs @@ -0,0 +1,48 @@ +import { PrintIcon } from 'shared/components/icons.mjs' +import { WorkbenchMenu } from 'shared/components/workbench/menus/shared/index.mjs' +import { ListInput, BoolInput, MmInput } from 'shared/components/workbench/menus/shared/inputs.mjs' +import { ListValue, BoolValue, MmValue } from 'shared/components/workbench/menus/shared/values.mjs' +import { loadPrintConfig } from './config.mjs' +import get from 'lodash.get' + +const inputs = { + size: ListInput, + orientation: ListInput, + margin: MmInput, + coverPage: BoolInput, + cutlist: BoolInput, +} + +const values = { + size: ListValue, + orientation: ListValue, + margin: MmValue, + coverPage: BoolValue, + cutlist: BoolValue, +} + +export const ns = ['print'] + +export const settingsPath = ['layout', 'print', 'page'] +export const PrintSettings = ({ update, settings, ui, account }) => { + const config = loadPrintConfig(settings.units) + const passProps = { units: settings.units } + const updateFunc = (name, newVal) => update.ui([...settingsPath, name], newVal) + + return ( + + ) +} From b879e230186549215ee0de7578659afcbdfad933 Mon Sep 17 00:00:00 2001 From: Enoch Riese Date: Sun, 4 Jun 2023 23:28:43 -0500 Subject: [PATCH 2/6] movable pattern for print layout --- .../react-components/src/pattern/group.mjs | 8 +- .../react-components/src/pattern/index.mjs | 104 +++---- .../react-components/src/pattern/utils.mjs | 12 +- sites/shared/components/workbench/index.mjs | 5 +- .../components/workbench/pan-zoom-pattern.mjs | 2 +- .../workbench/pattern/movable/index.mjs | 113 ++++++++ .../workbench/pattern/movable/stack.mjs | 264 ++++++++++++++++++ .../pattern/movable/transform-buttons.mjs | 123 ++++++++ .../workbench/views/print/index.mjs | 33 +-- .../layout => plugins}/plugin-layout-part.mjs | 6 +- 10 files changed, 591 insertions(+), 79 deletions(-) create mode 100644 sites/shared/components/workbench/pattern/movable/index.mjs create mode 100644 sites/shared/components/workbench/pattern/movable/stack.mjs create mode 100644 sites/shared/components/workbench/pattern/movable/transform-buttons.mjs rename sites/shared/{components/workbench/layout => plugins}/plugin-layout-part.mjs (98%) diff --git a/packages/react-components/src/pattern/group.mjs b/packages/react-components/src/pattern/group.mjs index 92d04df3de2..51690f21ecb 100644 --- a/packages/react-components/src/pattern/group.mjs +++ b/packages/react-components/src/pattern/group.mjs @@ -1,3 +1,7 @@ -import React from 'react' +import { forwardRef } from 'react' -export const Group = (props) => {props.children} +export const Group = forwardRef((props, ref) => ( + + {props.children} + +)) diff --git a/packages/react-components/src/pattern/index.mjs b/packages/react-components/src/pattern/index.mjs index ce185800aa1..2c211f3f366 100644 --- a/packages/react-components/src/pattern/index.mjs +++ b/packages/react-components/src/pattern/index.mjs @@ -1,4 +1,4 @@ -import React from 'react' +import { forwardRef } from 'react' // Components that can be swizzled import { Svg as DefaultSvg } from './svg.mjs' import { Defs as DefaultDefs } from './defs.mjs' @@ -10,6 +10,7 @@ import { Snippet as DefaultSnippet } from './snippet.mjs' import { Path as DefaultPath } from './path.mjs' import { Grid as DefaultGrid } from './grid.mjs' import { Text as DefaultText, TextOnPath as DefaultTextOnPath } from './text.mjs' +import { Circle as DefaultCircle } from './circle.mjs' /* * Allow people to swizzle these components @@ -26,55 +27,60 @@ const defaultComponents = { Grid: DefaultGrid, Text: DefaultText, TextOnPath: DefaultTextOnPath, + Circle: DefaultCircle, } -export const Pattern = ({ - renderProps = false, - t = (string) => string, - components = {}, - children = false, - className = 'freesewing pattern', - ref = false, -}) => { - if (!renderProps) return null +export const Pattern = forwardRef( + ( + { + renderProps = false, + t = (string) => string, + components = {}, + children = false, + className = 'freesewing pattern', + }, + ref + ) => { + if (!renderProps) return null - // Merge default and swizzled components - components = { - ...defaultComponents, - ...components, + // Merge default and swizzled components + components = { + ...defaultComponents, + ...components, + } + + const { Svg, Defs, Stack, Group } = components + + const optionalProps = {} + if (className) optionalProps.className = className + + return ( + + + + + {children + ? children + : Object.keys(renderProps.stacks).map((stackName) => ( + + ))} + + + ) } - - const { Svg, Defs, Stack, Group } = components - - const optionalProps = {} - if (ref) optionalProps.ref = ref - if (className) optionalProps.className = className - - return ( - - - - - {children - ? children - : Object.keys(renderProps.stacks).map((stackName) => ( - - ))} - - - ) -} +) diff --git a/packages/react-components/src/pattern/utils.mjs b/packages/react-components/src/pattern/utils.mjs index faf9fed0605..4c7a88e6ac3 100644 --- a/packages/react-components/src/pattern/utils.mjs +++ b/packages/react-components/src/pattern/utils.mjs @@ -1,5 +1,3 @@ -import React from 'react' - export const getProps = (obj) => { /** I can't believe it but there seems to be no method on NPM todo this */ const cssKey = (key) => { @@ -38,6 +36,16 @@ export const getProps = (obj) => { return props } +export const dx = (pointA, pointB) => pointB.x - pointA.x +export const dy = (pointA, pointB) => pointB.y - pointA.y +export const rad2deg = (radians) => radians * 57.29577951308232 +export const angle = (pointA, pointB) => { + let rad = Math.atan2(-1 * dy(pointA, pointB), dx(pointA, pointB)) + while (rad < 0) rad += 2 * Math.PI + + return rad2deg(rad) +} + export const withinPartBounds = (point, part) => point.x >= part.topLeft.x && point.x <= part.bottomRight.x && diff --git a/sites/shared/components/workbench/index.mjs b/sites/shared/components/workbench/index.mjs index 5a1ef70cb10..03b4fc03c32 100644 --- a/sites/shared/components/workbench/index.mjs +++ b/sites/shared/components/workbench/index.mjs @@ -84,8 +84,9 @@ export const Workbench = ({ design, Design, baseSettings, DynamicDocs, from }) = viewContent = break default: { + const layout = ui.layouts?.[view] || settings.layout || true // Generate the pattern here so we can pass it down to both the view and the options menu - const pattern = settings.measurements !== undefined && new Design(settings) + const pattern = settings.measurements !== undefined && new Design({ layout, ...settings }) // Return early if the pattern is not initialized yet if (typeof pattern.getConfig !== 'function') return null @@ -107,7 +108,7 @@ export const Workbench = ({ design, Design, baseSettings, DynamicDocs, from }) = } } const View = views[view] - viewContent = + viewContent = } } diff --git a/sites/shared/components/workbench/pan-zoom-pattern.mjs b/sites/shared/components/workbench/pan-zoom-pattern.mjs index 0fcf71ea339..6e149cc5f27 100644 --- a/sites/shared/components/workbench/pan-zoom-pattern.mjs +++ b/sites/shared/components/workbench/pan-zoom-pattern.mjs @@ -58,7 +58,7 @@ export const PanZoomPattern = forwardRef((props, ref) => { >
- +
diff --git a/sites/shared/components/workbench/pattern/movable/index.mjs b/sites/shared/components/workbench/pattern/movable/index.mjs new file mode 100644 index 00000000000..058c14342f5 --- /dev/null +++ b/sites/shared/components/workbench/pattern/movable/index.mjs @@ -0,0 +1,113 @@ +import { useRef } from 'react' +import { Stack } from './stack.mjs' +// import { SvgWrapper } from '../../pattern/svg.mjs' +// import { PartInner } from '../../pattern/part.mjs' +import { PanZoomPattern } from 'shared/components/workbench/pan-zoom-pattern.mjs' +import { MovableStack } from './stack.mjs' +import get from 'lodash.get' + +export const MovablePattern = ({ + design, + pattern, + renderProps, + patternConfig, + settings, + ui, + update, + bgProps = {}, + fitImmovable = false, + immovable = [], + layoutPath, +}) => { + const svgRef = useRef(null) + if (!renderProps) return null + + // keep a fresh copy of the layout because we might manipulate it without saving to the gist + let layout = + renderProps.settings[0].layout === true + ? { + ...renderProps.autoLayout, + width: renderProps.width, + height: renderProps.height, + } + : renderProps.settings[0].layout + + // Helper method to update part layout and re-calculate width * height + const updateLayout = (name, config, history = true) => { + // Start creating new layout + const newLayout = { ...layout } + newLayout.stacks[name] = config + + // Pattern topLeft and bottomRight + let topLeft = { x: 0, y: 0 } + let bottomRight = { x: 0, y: 0 } + for (const pname in renderProps.stacks) { + if (immovable.includes(pname) && !fitImmovable) continue + let partLayout = newLayout.stacks[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 + topLeft.x = Math.min(topLeft.x, partLayout.tl.x) + topLeft.y = Math.min(topLeft.y, partLayout.tl.y) + bottomRight.x = Math.max(bottomRight.x, partLayout.br.x) + bottomRight.y = Math.max(bottomRight.y, partLayout.br.y) + } + } + + newLayout.width = bottomRight.x - topLeft.x + newLayout.height = bottomRight.y - topLeft.y + newLayout.bottomRight = bottomRight + newLayout.topLeft = topLeft + + if (history) { + update.ui(layoutPath, newLayout) + } else { + // we don't put it in the gist if it shouldn't contribute to history because we need some of 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. This is what allows the layout to respond appropriately to settings changes. Once the user has starting playing with the layout, all bets are off + layout = newLayout + } + } + + const viewBox = layout.topLeft + ? `${layout.topLeft.x} ${layout.topLeft.y} ${layout.width} ${layout.height}` + : false + + const sortedStacks = {} + Object.keys(renderProps.stacks) + .sort((a, b) => { + const hasA = immovable.includes(a) + const hasB = immovable.includes(b) + if (hasA && !hasB) return -1 + if (!hasA && hasB) return 1 + return 0 + }) + .forEach((s) => (sortedStacks[s] = renderProps.stacks[s])) + + const sortedRenderProps = { ...renderProps, stacks: sortedStacks } + + const Stack = ({ stackName, stack, settings, components, t }) => ( + + ) + + return ( + + ) +} diff --git a/sites/shared/components/workbench/pattern/movable/stack.mjs b/sites/shared/components/workbench/pattern/movable/stack.mjs new file mode 100644 index 00000000000..3da5d3f856f --- /dev/null +++ b/sites/shared/components/workbench/pattern/movable/stack.mjs @@ -0,0 +1,264 @@ +/* + * This React component is a long way from perfect, but it's a start for + * handling custom layouts. + * + * There are a few reasons that (at least in my opinion) implementing this is non-trivial: + * + * 1) React re-render vs DOM updates + * + * For performance reasons, we can't re-render with React when the user drags a + * pattern part (or rotates it). It would kill performance. + * So, we don't re-render with React upon dragging/rotating, but instead manipulate + * the DOM directly. + * + * So far so good, but of course we don't want a pattern that's only correctly laid + * out in the DOM. We want to update the pattern gist so that the new layout is stored. + * For this, we re-render with React on the end of the drag (or rotate). + * + * Handling this balance between DOM updates and React re-renders is a first contributing + * factor to why this component is non-trivial + * + * 2) SVG vs DOM coordinates + * + * When we drag or rotate with the mouse, all the events are giving us coordinates of + * where the mouse is in the DOM. + * + * The layout uses coordinates from the embedded SVG which are completely different. + * + * We run `getScreenCTM().inverse()` on the svg element to pass to `matrixTransform` on a `DOMPointReadOnly` for dom to svg space conversions. + * + * 3) Part-level transforms + * + * All parts use their center as the transform-origin to simplify transforms, especially flipping and rotating. + * + * 4) Bounding box + * + * We use `getBoundingClientRect` rather than `getBBox` because it provides more data and factors in the transforms. + * We then use our `domToSvg` function to move the points back into the SVG space. + * + * + * Known issues + * - currently none + * + * I've sort of left it at this because I'm starting to wonder if we should perhaps re-think + * how custom layouts are supported in the core. And I would like to discuss this with the core team. + */ +import { useRef, useState, useEffect } from 'react' +import { generateStackTransform, getTransformedBounds } from '@freesewing/core' +import { Stack } from 'pkgs/react-components/src/pattern/stack.mjs' +import { getProps, angle } from 'pkgs/react-components/src/pattern/utils.mjs' +import { drag } from 'd3-drag' +import { select } from 'd3-selection' +import { Buttons } from './transform-buttons.mjs' +import get from 'lodash.get' + +export const MovableStack = ({ + stackName, + stack, + settings, + components, + t, + movable = true, + layout, + updateLayout, + showButtons, +}) => { + const stackExists = !movable || typeof layout?.move?.x !== 'undefined' + + // Use a ref for direct DOM manipulation + const stackRef = 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(() => { + // only update if there's a rendered part and it's not the pages or fabric part + if (stackRef.current && movable) { + updateStacklayout(false) + } + }, [stackRef, layout]) + + // Initialize drag handler + useEffect(() => { + // don't drag the pages + if (!movable || !stackExists) return + handleDrag(select(stackRef.current)) + }, [rotate, stackRef, layout]) + + // // Don't just assume this makes sense + if (!stackExists) return null + + if (!movable) return + + // 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 + // whole thing a bit tricky to wrap your head around + let translateX = layout.move.x + let translateY = layout.move.y + let stackRotation = layout.rotate || 0 + let rotation = stackRotation + let flipX = !!layout.flipX + let flipY = !!layout.flipY + + const center = { + x: stack.topLeft.x + (stack.bottomRight.x - stack.topLeft.x) / 2, + y: stack.topLeft.y + (stack.bottomRight.y - stack.topLeft.y) / 2, + } + + /** 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 = generateStackTransform(translateX, translateY, rotation, flipX, flipY, stack) + + const me = select(stackRef.current) + me.attr('transform', transforms.join(' ')) + + return transforms + } + + 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) { + return rotate + ? // if we're rotating, the subject is the mouse position + { x: event.x, y: event.y } + : // if we're moving, the subject is the part's x,y coordinates + { x: translateX, y: translateY } + }) + .on('drag', function (event) { + if (!event.dx && !event.dy) return + + if (rotate) { + let newRotation = getRotation(event) + // shift key to snap the rotation + 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 + + rotation = stackRotation + newRotation + } else { + translateX = event.x + translateY = event.y + } + + // a drag happened, so we should update the layout when we're done + didDrag = true + setTransforms() + }) + .on('end', function () { + // save to gist if anything actually changed + if (didDrag) updateStacklayout() + + didDrag = false + }) + + /** reset the part's transforms */ + const resetPart = () => { + rotation = 0 + flipX = 0 + flipY = 0 + updateStacklayout() + } + + /** toggle between dragging and rotating */ + const toggleDragRotate = () => { + // only respond if the part should be able to drag/rotate + if (!stackRef.current || !movable) { + return + } + + setRotate(!rotate) + } + + /** update the layout either locally or in the gist */ + const updateStacklayout = (history = true) => { + /** don't mess with what we don't lay out */ + if (!stackRef.current || !movable) return + + // set the transforms on the stack in order to calculate from the latest position + const transforms = setTransforms() + + // apply the transforms to the bounding box to get the new extents of the stack + const { tl, br } = getTransformedBounds(stack, transforms) + + // update it on the draft component + updateLayout( + stackName, + { + move: { + x: translateX, + y: translateY, + }, + rotate: rotation % 360, + flipX, + flipY, + tl, + br, + }, + history + ) + } + + /** Method to flip (mirror) the part along the X or Y axis */ + const flip = (axis) => { + if (axis === 'x') flipX = !flipX + else flipY = !flipY + updateStacklayout() + } + + /** method to rotate 90 degrees */ + const rotate90 = (direction = 1) => { + if (flipX) direction *= -1 + if (flipY) direction *= -1 + + rotation += 90 * direction + + updateStacklayout() + } + + const { Group, Part } = components + return ( + + + {[...stack.parts].map((part, key) => ( + + ))} + + {movable && ( + <> + + {showButtons ? ( + + ) : null} + + )} + + ) +} diff --git a/sites/shared/components/workbench/pattern/movable/transform-buttons.mjs b/sites/shared/components/workbench/pattern/movable/transform-buttons.mjs new file mode 100644 index 00000000000..84562e672a3 --- /dev/null +++ b/sites/shared/components/workbench/pattern/movable/transform-buttons.mjs @@ -0,0 +1,123 @@ +import { useTranslation } from 'next-i18next' +import { ClearIcon } from 'shared/components/icons.mjs' +import get from 'lodash.get' + +const Triangle = ({ transform = 'translate(0,0)', fill = 'currentColor' }) => ( + +) + +const Line = () => ( + +) + +const FlipIconInner = ({ x = 0, y = 0, rotate = 0, ...style }) => ( + + + + + + +) + +const RotateIconInner = ({ flipX = false }) => ( + +) + +const rectSize = 24 + +const Button = ({ onClickCb, transform, Icon, children }) => { + const _onClick = (event) => { + event.stopPropagation() + onClickCb(event) + } + + return ( + + + + {children} + + + ) +} + +export const ShowButtonsToggle = ({ gist, layoutSetType, updateGist }) => { + const { t } = useTranslation('workbench') + const path = ['_state', 'layout', layoutSetType, 'showButtons'] + const showButtons = get(gist, path, true) + const setShowButtons = () => updateGist(path, !showButtons) + + return ( + + ) +} + +/** buttons for manipulating the part */ +export const Buttons = ({ transform, flip, rotate, resetPart, rotate90 }) => { + const { t } = useTranslation('workbench') + return ( + + {rotate ? ( + + ) : ( + + )} + + + + + + + ) +} diff --git a/sites/shared/components/workbench/views/print/index.mjs b/sites/shared/components/workbench/views/print/index.mjs index 49c959fee9c..bd1de68a83c 100644 --- a/sites/shared/components/workbench/views/print/index.mjs +++ b/sites/shared/components/workbench/views/print/index.mjs @@ -2,26 +2,19 @@ import { useEffect, useState } from 'react' import { useTranslation } from 'next-i18next' // import { PrintLayoutSettings } from './settings.mjs' // import { Draft } from '../draft/index.mjs' -import { pagesPlugin } from 'shared/components/workbench/layout/plugin-layout-part.mjs' +import { pagesPlugin } from 'shared/plugins/plugin-layout-part.mjs' // import { // handleExport, // defaultPdfSettings, // } from 'shared/components/workbench/exporting/export-handler.mjs' // import { Popout } from 'shared/components/popout.mjs' import get from 'lodash.get' -import { PanZoomPattern as ShowPattern } from 'shared/components/workbench/pan-zoom-pattern.mjs' +import { MovablePattern } from 'shared/components/workbench/pattern/movable/index.mjs' import { PrintMenu, ns as menuNs } from './menu.mjs' +import { defaultPrintSettings } from './config.mjs' const viewNs = ['print'] -export const ns = [...viewNs, ...menuNs] - -const defaultPdfSettings = { - size: 'a4', - orientation: 'portrait', - margin: 10, - coverPage: true, - cutlist: true, -} +export const ns = [...viewNs] export const PrintView = ({ design, @@ -35,27 +28,23 @@ export const PrintView = ({ account, DynamicDocs, }) => { - // disable xray - // useEffect(() => { - // if (props.gist?._state?.xray?.enabled) props.updateGist(['_state', 'xray', 'enabled'], false) - // }) - const { t } = useTranslation(viewNs) const [error, setError] = useState(false) + const defaultSettings = defaultPrintSettings(settings.units) // add the pages plugin to the draft const layoutSettings = { - ...defaultPdfSettings, - ...get(ui, ['layout', 'print', 'page']), + ...defaultSettings, + ...get(ui, ['print', 'page']), } pattern.use(pagesPlugin(layoutSettings)) - let patternProps + let renderProps try { // draft the pattern pattern.draft() - patternProps = pattern.getRenderProps() + renderProps = pattern.getRenderProps() } catch (err) { console.log(err) } @@ -80,7 +69,9 @@ export const PrintView = ({

{t('layoutThing', { thing: name }) + ': ' + t('forPrinting')}

- +
Date: Mon, 5 Jun 2023 16:07:16 -0500 Subject: [PATCH 3/6] working reset and export buttons for print layout --- .../workbench/exporting/export-handler.mjs | 94 ++++++++++--------- .../workbench/exporting/export-worker.js | 25 +++-- .../workbench/exporting/pdf-maker.mjs | 16 ++-- sites/shared/components/workbench/index.mjs | 1 + .../components/workbench/layout/cut/index.mjs | 21 ++--- .../layout/cut/plugin-cut-layout.mjs | 2 +- .../workbench/menus/shared/index.mjs | 2 +- .../workbench/pattern/movable/index.mjs | 4 +- .../workbench/views/print/actions.mjs | 59 ++++++++++++ .../workbench/views/print/config.mjs | 12 ++- .../workbench/views/print/index.mjs | 85 +++++++++++------ .../components/workbench/views/print/menu.mjs | 7 +- .../workbench/views/print/print.en.yaml | 41 ++++++++ .../workbench/views/print/settings.mjs | 7 +- sites/shared/plugins/plugin-layout-part.mjs | 1 + 15 files changed, 266 insertions(+), 111 deletions(-) create mode 100644 sites/shared/components/workbench/views/print/actions.mjs create mode 100644 sites/shared/components/workbench/views/print/print.en.yaml diff --git a/sites/shared/components/workbench/exporting/export-handler.mjs b/sites/shared/components/workbench/exporting/export-handler.mjs index 5b5b9dcf057..4dd1258d915 100644 --- a/sites/shared/components/workbench/exporting/export-handler.mjs +++ b/sites/shared/components/workbench/exporting/export-handler.mjs @@ -2,39 +2,36 @@ import Worker from 'web-worker' import fileSaver from 'file-saver' import { themePlugin } from '@freesewing/plugin-theme' import { pluginI18n } from '@freesewing/plugin-i18n' -import { pagesPlugin, fabricPlugin } from '../layout/plugin-layout-part.mjs' +import { pagesPlugin, fabricPlugin } from 'shared/plugins/plugin-layout-part.mjs' import { pluginAnnotations } from '@freesewing/plugin-annotations' import { cutLayoutPlugin } from '../layout/cut/plugin-cut-layout.mjs' import { fabricSettingsOrDefault } from '../layout/cut/index.mjs' import { useFabricLength } from '../layout/cut/settings.mjs' import { capitalize, formatMm } from 'shared/utils.mjs' +import { + defaultPrintSettings, + printSettingsPath, +} from 'shared/components/workbench/views/print/config.mjs' import get from 'lodash.get' +export const ns = ['plugin', 'common'] export const exportTypes = { exportForPrinting: ['a4', 'a3', 'a2', 'a1', 'a0', 'letter', 'tabloid'], exportForEditing: ['svg', 'pdf'], exportAsData: ['json', 'yaml', 'github gist'], } -export const defaultPdfSettings = { - size: 'a4', - orientation: 'portrait', - margin: 10, - coverPage: true, - cutlist: true, -} - /** * Instantiate a pattern that uses plugins theme, i18n, and cutlist - * @param {Design} design the design to construct the pattern from - * @param {Object} gist the gist - * @param {Object} overwrite settings to overwrite gist settings with + * @param {Design} Design the design to construct the pattern from + * @param {Object} settings the settings + * @param {Object} overwrite settings to overwrite settings settings with * @param {string} format the export format this pattern will be prepared for * @param {function} t the i18n function * @return {Pattern} a pattern */ -const themedPattern = (design, gist, overwrite, format, t) => { - const pattern = new design({ ...gist, ...overwrite }) +const themedPattern = (Design, settings, overwrite, format, t) => { + const pattern = new Design({ ...settings, ...overwrite }) // add the theme and translation to the pattern pattern.use(themePlugin, { stripped: format !== 'svg', skipGrid: ['pages'] }) @@ -48,28 +45,28 @@ const themedPattern = (design, gist, overwrite, format, t) => { * Generate svgs of all cutting layouts for the pattern * @param {Pattern} pattern the pattern to generate cutting layouts for * @param {Design} design the design constructor for the pattern - * @param {Object} gist the gist + * @param {Object} settings the settings * @param {string} format the export format this pattern will be prepared for * @param {function} t the i18n function * @return {Object} a dictionary of svgs and related translation strings, keyed by fabric */ -const generateCutLayouts = (pattern, design, gist, format, t) => { +const generateCutLayouts = (pattern, Design, settings, format, t, ui) => { // get the fabrics from the already drafted base pattern const fabrics = pattern.setStores[pattern.activeSet].cutlist.getCutFabrics( pattern.settings[0] ) || ['fabric'] if (!fabrics.length) return - const isImperial = gist.units === 'imperial' + const isImperial = settings.units === 'imperial' const cutLayouts = {} // each fabric fabrics.forEach((f) => { // get the settings and layout for that fabric - const fabricSettings = fabricSettingsOrDefault(gist, f) - const fabricLayout = get(gist, ['layouts', 'cuttingLayout', f], true) + const fabricSettings = fabricSettingsOrDefault(settings.units, ui, f) + const fabricLayout = get(ui, ['layouts', 'cut', f], true) // make a new pattern - const fabricPattern = themedPattern(design, gist, { layout: fabricLayout }, format, t) + const fabricPattern = themedPattern(Design, settings, { layout: fabricLayout }, format, t) // add cut layout plugin and fabric plugin .use(cutLayoutPlugin(f, fabricSettings.grainDirection)) .use(fabricPlugin({ ...fabricSettings, printStyle: true, setPatternSize: 'width' })) @@ -82,7 +79,7 @@ const generateCutLayouts = (pattern, design, gist, format, t) => { svg, title: t('plugin:' + f), dimensions: t('plugin:fabricSize', { - width: formatMm(fabricSettings.sheetWidth, gist.units, 'notags'), + width: formatMm(fabricSettings.sheetWidth, settings.units, 'notags'), length: useFabricLength(isImperial, fabricPattern.height, 'notags'), interpolation: { escapeValue: false }, }), @@ -92,18 +89,28 @@ const generateCutLayouts = (pattern, design, gist, format, t) => { return cutLayouts } /** - * Handle exporting the draft or gist + * Handle exporting the draft or settings * format: format to export to - * gist: the gist - * design: the pattern constructor for the design to be exported + * settings: the settings + * Design: the pattern constructor for the design to be exported * t: a translation function to attach to the draft - * app: an app instance * onComplete: business to perform after a successful export * onError: business to perform on error * */ -export const handleExport = async (format, gist, design, t, app, onComplete, onError) => { +export const handleExport = async ({ + format, + settings, + Design, + design, + t, + startLoading, + stopLoading, + onComplete, + onError, + ui, +}) => { // start the loading indicator - app.startLoading() + if (typeof startLoading === 'function') startLoading() // get a worker going const worker = new Worker(new URL('./export-worker.js', import.meta.url), { type: 'module' }) @@ -115,7 +122,7 @@ export const handleExport = async (format, gist, design, t, app, onComplete, onE // save it out if (e.data.blob) { const fileType = exportTypes.exportForPrinting.indexOf(format) === -1 ? format : 'pdf' - fileSaver.saveAs(e.data.blob, `freesewing-${gist.design || 'gist'}.${fileType}`) + fileSaver.saveAs(e.data.blob, `freesewing-${design || 'pattern'}.${fileType}`) } // do additional business onComplete && onComplete(e) @@ -127,37 +134,38 @@ export const handleExport = async (format, gist, design, t, app, onComplete, onE } // stop the loader - app.stopLoading() + if (typeof stopLoading === 'function') stopLoading() }) // pdf settings - const settings = { - ...defaultPdfSettings, - ...(gist._state.layout?.forPrinting?.page || {}), + const pageSettings = { + ...defaultPrintSettings(settings.units), + ...get(ui, printSettingsPath, {}), } // arguments to pass to the worker - const workerArgs = { format, gist, settings } + const workerArgs = { format, settings, pageSettings } // data passed to the worker must be JSON serializable, so we can't pass functions or prototypes // that means if it's not a data export there's more work to do before we can hand off to the worker if (exportTypes.exportAsData.indexOf(format) === -1) { - gist.embed = false + settings.embed = false // make a pattern instance for export rendering - const layout = gist.layouts?.printingLayout || gist.layout || true - let pattern = themedPattern(design, gist, { layout }, format, t) + const layout = settings.layout || ui.layouts?.print || true + let pattern = themedPattern(Design, settings, { layout }, format, t) - // a specified size should override the gist one + // a specified size should override the settings one if (format !== 'pdf') { - settings.size = format + pageSettings.size = format } try { + throw new Error('bad bad bad') // add pages to pdf exports if (format !== 'svg') { pattern.use( pagesPlugin({ - ...settings, + ...pageSettings, printStyle: true, renderBlanks: false, setPatternSize: true, @@ -166,7 +174,7 @@ export const handleExport = async (format, gist, design, t, app, onComplete, onE // add the strings that are used on the cover page workerArgs.strings = { - design: capitalize(pattern.designConfig.data.name.replace('@freesewing/', '')), + design: capitalize(design), tagline: t('common:sloganCome') + '. ' + t('common:sloganStay'), url: window.location.href, cuttingLayout: t('plugin:cuttingLayout'), @@ -181,15 +189,15 @@ export const handleExport = async (format, gist, design, t, app, onComplete, onE workerArgs.pages = pattern.setStores[pattern.activeSet].get('pages') // add cutting layouts if requested - if (format !== 'svg' && settings.cutlist) { - workerArgs.cutLayouts = generateCutLayouts(pattern, design, gist, format, t) + if (format !== 'svg' && pageSettings.cutlist) { + workerArgs.cutLayouts = generateCutLayouts(pattern, Design, settings, format, t, ui) } // post a message to the worker with all needed data worker.postMessage(workerArgs) } catch (err) { console.log(err) - app.stopLoading() + if (typeof stopLoading === 'function') stopLoading() onError && onError(err) } } diff --git a/sites/shared/components/workbench/exporting/export-worker.js b/sites/shared/components/workbench/exporting/export-worker.js index 4d83a5e90bd..c2ac2040a27 100644 --- a/sites/shared/components/workbench/exporting/export-worker.js +++ b/sites/shared/components/workbench/exporting/export-worker.js @@ -7,16 +7,21 @@ import { PdfMaker } from './pdf-maker' /** when the worker receives data from the page, do the appropriate export */ addEventListener('message', async (e) => { - const { format, gist, svg } = e.data + const { format, settings, svg } = e.data // handle export by type try { - if (format === 'json') return exportJson(gist) - if (format === 'yaml') return exportYaml(gist) - if (format === 'github gist') return exportGithubGist(gist) - - if (format === 'svg') return exportSvg(svg) - - await exportPdf(e.data) + switch (format) { + case 'json': + return exportJson(settings) + case 'yaml': + return exportYaml(settings) + case 'github gist': + return exportGithubGist(settings) + case 'svg': + return exportSvg(svg) + default: + return await exportPdf(e.data) + } } catch (e) { postMessage({ success: false, error: e }) close() @@ -37,9 +42,9 @@ const exportBlob = (blobContent, type) => { postSuccess(blob) } -const exportJson = (gist) => exportBlob(JSON.stringify(gist, null, 2), 'application/json') +const exportJson = (settings) => exportBlob(JSON.stringify(settings, null, 2), 'application/json') -const exportYaml = (gist) => exportBlob(yaml.dump(gist), 'application/x-yaml') +const exportYaml = (settings) => exportBlob(yaml.dump(settings), 'application/x-yaml') const exportSvg = (svg) => exportBlob(svg, 'image/svg+xml') diff --git a/sites/shared/components/workbench/exporting/pdf-maker.mjs b/sites/shared/components/workbench/exporting/pdf-maker.mjs index d7e63fca39d..08979c6b715 100644 --- a/sites/shared/components/workbench/exporting/pdf-maker.mjs +++ b/sites/shared/components/workbench/exporting/pdf-maker.mjs @@ -22,7 +22,7 @@ export class PdfMaker { /** the svg as text to embed in the pdf */ svg /** the document configuration */ - settings + pageSettings /** the pdfKit instance that is writing the document */ pdf /** the export buffer to hold pdfKit output */ @@ -51,8 +51,8 @@ export class PdfMaker { pageCount = 0 lineLevel = 50 - constructor({ svg, settings, pages, strings, cutLayouts }) { - this.settings = settings + constructor({ svg, pageSettings, pages, strings, cutLayouts }) { + this.pageSettings = pageSettings this.pagesWithContent = pages.withContent this.svg = svg this.strings = strings @@ -60,7 +60,7 @@ export class PdfMaker { this.initPdf() - this.margin = this.settings.margin * mmToPoints // margin is in mm because it comes from us, so we convert it to points + this.margin = this.pageSettings.margin * mmToPoints // margin is in mm because it comes from us, so we convert it to points this.pageHeight = this.pdf.page.height - this.margin * 2 // this is in points because it comes from pdfKit this.pageWidth = this.pdf.page.width - this.margin * 2 // this is in points because it comes from pdfKit @@ -77,8 +77,8 @@ export class PdfMaker { initPdf() { // instantiate with the correct size and orientation this.pdf = new PDFDocument({ - size: this.settings.size.toUpperCase(), - layout: this.settings.orientation, + size: this.pageSettings.size.toUpperCase(), + layout: this.pageSettings.orientation, }) // PdfKit wants to flush the buffer on each new page. @@ -117,7 +117,7 @@ export class PdfMaker { /** generate the cover page for the pdf */ async generateCoverPage() { // don't make one if it's not requested - if (!this.settings.coverPage) { + if (!this.pageSettings.coverPage) { return } @@ -185,7 +185,7 @@ export class PdfMaker { /** generate all cutting layout pages */ async generateCutLayoutPages() { - if (!this.settings.cutlist || !this.cutLayouts) return + if (!this.pageSettings.cutlist || !this.cutLayouts) return for (const fabric in this.cutLayouts) { this.nextPage() diff --git a/sites/shared/components/workbench/index.mjs b/sites/shared/components/workbench/index.mjs index 03b4fc03c32..bef27f45400 100644 --- a/sites/shared/components/workbench/index.mjs +++ b/sites/shared/components/workbench/index.mjs @@ -75,6 +75,7 @@ export const Workbench = ({ design, Design, baseSettings, DynamicDocs, from }) = ui, language, DynamicDocs, + Design, } let viewContent = null diff --git a/sites/shared/components/workbench/layout/cut/index.mjs b/sites/shared/components/workbench/layout/cut/index.mjs index 4a3445d2656..26ad576c549 100644 --- a/sites/shared/components/workbench/layout/cut/index.mjs +++ b/sites/shared/components/workbench/layout/cut/index.mjs @@ -1,20 +1,19 @@ import { useTranslation } from 'next-i18next' import { CutLayoutSettings } from './settings.mjs' -import { Draft } from '../draft/index.mjs' -import { fabricPlugin } from '../plugin-layout-part.mjs' +// import { Draft } from '../draft/index.mjs' +import { fabricPlugin } from 'shared/plugins/plugin-layout-part.mjs' import { cutLayoutPlugin } from './plugin-cut-layout.mjs' import { pluginAnnotations } from '@freesewing/plugin-annotations' import { measurementAsMm } from 'shared/utils.mjs' import { useEffect } from 'react' import get from 'lodash.get' -export const fabricSettingsOrDefault = (gist, fabric) => { - const isImperial = gist.units === 'imperial' - const sheetHeight = measurementAsMm(isImperial ? 36 : 100, gist.units) - const gistSettings = get(gist, ['_state', 'layout', 'forCutting', 'fabric', fabric]) - const sheetWidth = gistSettings?.sheetWidth || measurementAsMm(isImperial ? 54 : 120, gist.units) - const grainDirection = - gistSettings?.grainDirection === undefined ? 90 : gistSettings.grainDirection +export const fabricSettingsOrDefault = (units, ui, fabric) => { + const isImperial = units === 'imperial' + const sheetHeight = measurementAsMm(isImperial ? 36 : 100, units) + const uiSettings = get(ui, ['cut', 'fabric', fabric], {}) + const sheetWidth = uiSettings.sheetWidth || measurementAsMm(isImperial ? 54 : 120, units) + const grainDirection = uiSettings.grainDirection === undefined ? 90 : uiSettings.grainDirection return { activeFabric: fabric, sheetWidth, grainDirection, sheetHeight } } @@ -109,7 +108,7 @@ export const CutLayout = (props) => { ))}
) : null} - { layoutPart="fabric" layoutType={['cuttingLayout', fabricSettings.activeFabric]} layoutSetType="forCutting" - /> + />*/}
) : null diff --git a/sites/shared/components/workbench/layout/cut/plugin-cut-layout.mjs b/sites/shared/components/workbench/layout/cut/plugin-cut-layout.mjs index f03f015ce22..9e027bc20d4 100644 --- a/sites/shared/components/workbench/layout/cut/plugin-cut-layout.mjs +++ b/sites/shared/components/workbench/layout/cut/plugin-cut-layout.mjs @@ -1,4 +1,4 @@ -import { addToOnly } from '../plugin-layout-part.mjs' +import { addToOnly } from 'shared/plugins/plugin-layout-part.mjs' import { pluginMirror } from '@freesewing/plugin-mirror' const prefix = 'mirroredOnFold' diff --git a/sites/shared/components/workbench/menus/shared/index.mjs b/sites/shared/components/workbench/menus/shared/index.mjs index a2bb2fe6fa7..74ec217ec4e 100644 --- a/sites/shared/components/workbench/menus/shared/index.mjs +++ b/sites/shared/components/workbench/menus/shared/index.mjs @@ -51,7 +51,7 @@ export const useDocsLoader = (DynamicDocs, getDocsPath, language) => { export const WorkbenchMenu = ({ updateFunc, ns, - Icon, + Icon = () => null, name, config, control, diff --git a/sites/shared/components/workbench/pattern/movable/index.mjs b/sites/shared/components/workbench/pattern/movable/index.mjs index 058c14342f5..da9f2db4b06 100644 --- a/sites/shared/components/workbench/pattern/movable/index.mjs +++ b/sites/shared/components/workbench/pattern/movable/index.mjs @@ -12,7 +12,7 @@ export const MovablePattern = ({ renderProps, patternConfig, settings, - ui, + showButtons = true, update, bgProps = {}, fitImmovable = false, @@ -96,7 +96,7 @@ export const MovablePattern = ({ movable: !immovable.includes(stackName), layout: layout.stacks[stackName], updateLayout, - showButtons: get(ui, 'showMovableButtons', true), + showButtons, }} /> ) diff --git a/sites/shared/components/workbench/views/print/actions.mjs b/sites/shared/components/workbench/views/print/actions.mjs new file mode 100644 index 00000000000..bc4933f0e13 --- /dev/null +++ b/sites/shared/components/workbench/views/print/actions.mjs @@ -0,0 +1,59 @@ +import { WorkbenchMenu } from 'shared/components/workbench/menus/shared/index.mjs' +import { MenuItem } from 'shared/components/workbench/menus/shared/menu-item.mjs' +import { BoolInput } from 'shared/components/workbench/menus/shared/inputs.mjs' +import { ListValue } from 'shared/components/workbench/menus/shared/values.mjs' +import { useTranslation } from 'next-i18next' +import { ClearIcon, ExportIcon } from 'shared/components/icons.mjs' +const handlers = { + showMovableButtons: (update) => (_path, newVal) => update.ui('showMovableButtons', newVal), +} + +export const ns = ['workbench', 'print'] + +const config = { + showMovableButtons: { + dflt: true, + }, +} + +const PrintActionItem = ({ name, config, settings, ui, updateFunc, passProps, ...rest }) => { + switch (name) { + case 'showMovableButtons': + return + } +} + +export const PrintActions = ({ update, settings, ui, account, exportIt }) => { + // get translation for the menu + const { t } = useTranslation(ns) + + const hideButtons = (evt) => { + update.ui('hideMovableButtons', !evt.target.checked) + } + + const resetLayout = () => update.ui(['layouts', 'print']) + + return ( +
+
+ + + +
+
+ ) +} diff --git a/sites/shared/components/workbench/views/print/config.mjs b/sites/shared/components/workbench/views/print/config.mjs index 20a93648e52..83077cd6501 100644 --- a/sites/shared/components/workbench/views/print/config.mjs +++ b/sites/shared/components/workbench/views/print/config.mjs @@ -1,5 +1,7 @@ import { measurementAsMm } from 'shared/utils.mjs' +export const printSettingsPath = ['print', 'pages'] + export const defaultPrintSettings = (units, inMm = true) => { const margin = units === 'imperial' ? 0.5 : 1 return { @@ -10,7 +12,7 @@ export const defaultPrintSettings = (units, inMm = true) => { cutlist: true, } } -const sizes = ['a4', 'a3', 'a2', 'a1', 'a0', 'letter', 'tabloid'] +const sizes = ['a4', 'a3', 'a2', 'a1', 'a0', 'letter', 'legal', 'tabloid'] export const loadPrintConfig = (units) => { const defaults = defaultPrintSettings(units, false) const config = { @@ -24,6 +26,14 @@ export const loadPrintConfig = (units) => { orientation: { control: 2, list: ['portrait', 'landscape'], + choiceTitles: { + portrait: 'portrait', + landscape: 'landscape', + }, + valueTitles: { + portrait: 'portrait', + landscape: 'landscape', + }, dflt: defaults.orientation, }, margin: { diff --git a/sites/shared/components/workbench/views/print/index.mjs b/sites/shared/components/workbench/views/print/index.mjs index bd1de68a83c..bf348fdca94 100644 --- a/sites/shared/components/workbench/views/print/index.mjs +++ b/sites/shared/components/workbench/views/print/index.mjs @@ -1,20 +1,20 @@ -import { useEffect, useState } from 'react' +import { useEffect, useState, useContext } from 'react' import { useTranslation } from 'next-i18next' -// import { PrintLayoutSettings } from './settings.mjs' -// import { Draft } from '../draft/index.mjs' import { pagesPlugin } from 'shared/plugins/plugin-layout-part.mjs' -// import { -// handleExport, -// defaultPdfSettings, -// } from 'shared/components/workbench/exporting/export-handler.mjs' -// import { Popout } from 'shared/components/popout.mjs' +import { + handleExport, + ns as exportNs, +} from 'shared/components/workbench/exporting/export-handler.mjs' import get from 'lodash.get' import { MovablePattern } from 'shared/components/workbench/pattern/movable/index.mjs' import { PrintMenu, ns as menuNs } from './menu.mjs' -import { defaultPrintSettings } from './config.mjs' +import { defaultPrintSettings, printSettingsPath } from './config.mjs' +import { PrintIcon, RightIcon, ClearIcon, ExportIcon } from 'shared/components/icons.mjs' +import { LoadingContext } from 'shared/context/loading-context.mjs' +import { useToast } from 'shared/hooks/use-toast.mjs' -const viewNs = ['print'] -export const ns = [...viewNs] +const viewNs = ['print', ...exportNs] +export const ns = [...viewNs, ...menuNs] export const PrintView = ({ design, @@ -27,18 +27,20 @@ export const PrintView = ({ language, account, DynamicDocs, + Design, }) => { - const { t } = useTranslation(viewNs) - const [error, setError] = useState(false) + const { t } = useTranslation(ns) + const loading = useContext(LoadingContext) + const toast = useToast() const defaultSettings = defaultPrintSettings(settings.units) // add the pages plugin to the draft - const layoutSettings = { + const pageSettings = { ...defaultSettings, - ...get(ui, ['print', 'page']), + ...get(ui, printSettingsPath, {}), } - pattern.use(pagesPlugin(layoutSettings)) + pattern.use(pagesPlugin(pageSettings)) let renderProps try { @@ -51,26 +53,52 @@ export const PrintView = ({ const bgProps = { fill: 'none' } const exportIt = () => { - // setError(false) - // handleExport( - // 'pdf', - // props.gist, - // props.design, - // t, - // props.app, - // () => setError(false), - // () => setError(true) - // ) + handleExport({ + format: 'pdf', + settings, + design, + t, + Design, + ui, + startLoading: loading.startLoading, + stopLoading: loading.stopLoading, + onComplete: () => {}, + onError: (err) => toast.error(err.message), + }) } let name = design + const pages = pattern.setStores[0].get('pages', {}) + const { cols, rows, count } = pages return (
-

{t('layoutThing', { thing: name }) + ': ' + t('forPrinting')}

+
+

+ {t('layoutThing', { thing: name }) + ' ' + t('forPrinting')} +

+
+ + {count} + | + + {cols} + | +
+ +
+ {rows} +
+
@@ -85,6 +113,7 @@ export const PrintView = ({ language, account, DynamicDocs, + exportIt, }} />
diff --git a/sites/shared/components/workbench/views/print/menu.mjs b/sites/shared/components/workbench/views/print/menu.mjs index 49872ba7e06..6668f45e83f 100644 --- a/sites/shared/components/workbench/views/print/menu.mjs +++ b/sites/shared/components/workbench/views/print/menu.mjs @@ -7,10 +7,13 @@ import { ns as coreMenuNs, } from 'shared/components/workbench/menus/core-settings/index.mjs' import { PrintSettings, ns as printMenuNs } from './settings.mjs' +import { PrintActions } from './actions.mjs' + export const ns = [...coreMenuNs, ...designMenuNs, ...printMenuNs] export const PrintMenu = ({ design, + pattern, patternConfig, settings, ui, @@ -18,7 +21,7 @@ export const PrintMenu = ({ language, account, DynamicDocs, - inspector = false, + exportIt, }) => { const control = account.control const menuProps = { @@ -31,9 +34,9 @@ export const PrintMenu = ({ DynamicDocs, control, } - return (