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 (