2022-10-23 19:52:32 +02:00
|
|
|
import Worker from 'web-worker'
|
2022-08-24 12:07:08 -05:00
|
|
|
import fileSaver from 'file-saver'
|
2022-08-29 08:29:55 +02:00
|
|
|
import { themePlugin } from '@freesewing/plugin-theme'
|
2023-03-09 08:59:03 -06:00
|
|
|
import { pluginI18n } from '@freesewing/plugin-i18n'
|
2025-04-01 16:15:20 +02:00
|
|
|
import { tilerPlugin } from './plugin-tiler.mjs'
|
2025-05-30 11:29:55 +02:00
|
|
|
import { capitalize, escapeSvgText, get } from '@freesewing/utils'
|
2024-02-13 08:09:57 -08:00
|
|
|
import mustache from 'mustache'
|
|
|
|
import he from 'he'
|
2024-02-14 07:18:33 -08:00
|
|
|
import yaml from 'js-yaml'
|
2022-08-24 12:07:08 -05:00
|
|
|
|
|
|
|
export const exportTypes = {
|
2023-06-06 15:19:11 -05:00
|
|
|
exportForPrinting: ['a4', 'a3', 'a2', 'a1', 'a0', 'letter', 'legal', 'tabloid'],
|
2022-08-24 12:07:08 -05:00
|
|
|
exportForEditing: ['svg', 'pdf'],
|
2023-11-03 19:41:21 +01:00
|
|
|
exportAsData: ['json', 'yaml'],
|
2022-08-24 12:07:08 -05:00
|
|
|
}
|
|
|
|
|
2025-04-01 16:15:20 +02:00
|
|
|
export const defaultPrintSettings = (units) => ({
|
|
|
|
size: units === 'imperial' ? 'letter' : 'a4',
|
|
|
|
orientation: 'portrait',
|
|
|
|
margin: units === 'imperial' ? 12.7 : 10,
|
|
|
|
coverPage: true,
|
|
|
|
cutlist: true,
|
|
|
|
})
|
|
|
|
|
2023-03-19 20:39:31 -05:00
|
|
|
/**
|
|
|
|
* Instantiate a pattern that uses plugins theme, i18n, and cutlist
|
2023-06-05 16:07:16 -05:00
|
|
|
* @param {Design} Design the design to construct the pattern from
|
|
|
|
* @param {Object} settings the settings
|
|
|
|
* @param {Object} overwrite settings to overwrite settings settings with
|
2023-03-19 20:39:31 -05:00
|
|
|
* @param {string} format the export format this pattern will be prepared for
|
|
|
|
* @param {function} t the i18n function
|
|
|
|
* @return {Pattern} a pattern
|
|
|
|
*/
|
2023-06-05 16:07:16 -05:00
|
|
|
const themedPattern = (Design, settings, overwrite, format, t) => {
|
|
|
|
const pattern = new Design({ ...settings, ...overwrite })
|
2023-03-15 12:48:46 -05:00
|
|
|
|
|
|
|
// add the theme and translation to the pattern
|
|
|
|
pattern.use(themePlugin, { stripped: format !== 'svg', skipGrid: ['pages'] })
|
2025-04-13 08:59:27 +00:00
|
|
|
pattern.use(pluginI18n, t)
|
2023-03-15 12:48:46 -05:00
|
|
|
|
|
|
|
return pattern
|
|
|
|
}
|
2023-03-19 20:39:31 -05:00
|
|
|
|
2022-08-24 12:07:08 -05:00
|
|
|
/**
|
2023-06-05 16:07:16 -05:00
|
|
|
* Handle exporting the draft or settings
|
2022-08-24 12:07:08 -05:00
|
|
|
* format: format to export to
|
2023-06-05 16:07:16 -05:00
|
|
|
* settings: the settings
|
|
|
|
* Design: the pattern constructor for the design to be exported
|
2022-08-24 12:07:08 -05:00
|
|
|
* t: a translation function to attach to the draft
|
|
|
|
* onComplete: business to perform after a successful export
|
|
|
|
* onError: business to perform on error
|
|
|
|
* */
|
2023-06-05 16:07:16 -05:00
|
|
|
export const handleExport = async ({
|
|
|
|
format,
|
|
|
|
settings,
|
|
|
|
Design,
|
|
|
|
design,
|
2025-04-01 16:15:20 +02:00
|
|
|
ui,
|
2023-06-05 16:07:16 -05:00
|
|
|
t,
|
|
|
|
startLoading,
|
|
|
|
stopLoading,
|
2024-02-18 08:49:25 -08:00
|
|
|
stopLoadingFail,
|
2023-06-05 16:07:16 -05:00
|
|
|
onComplete,
|
|
|
|
onError,
|
|
|
|
}) => {
|
2022-08-24 12:07:08 -05:00
|
|
|
// start the loading indicator
|
2023-06-05 16:07:16 -05:00
|
|
|
if (typeof startLoading === 'function') startLoading()
|
2022-08-24 12:07:08 -05:00
|
|
|
|
|
|
|
// get a worker going
|
2022-11-14 16:53:31 -06:00
|
|
|
const worker = new Worker(new URL('./export-worker.js', import.meta.url), { type: 'module' })
|
2022-08-24 12:07:08 -05:00
|
|
|
|
2023-11-07 19:24:43 +01:00
|
|
|
/*
|
|
|
|
* Guard against settings being false, which happens for
|
|
|
|
* fully default designs that do not require measurements
|
|
|
|
*/
|
|
|
|
if (settings === false) settings = {}
|
|
|
|
|
2022-08-24 12:07:08 -05:00
|
|
|
// listen for the worker's message back
|
2022-10-23 19:52:32 +02:00
|
|
|
worker.addEventListener('message', (e) => {
|
2022-08-24 12:07:08 -05:00
|
|
|
// on success
|
|
|
|
if (e.data.success) {
|
|
|
|
// save it out
|
|
|
|
if (e.data.blob) {
|
|
|
|
const fileType = exportTypes.exportForPrinting.indexOf(format) === -1 ? format : 'pdf'
|
2023-06-05 16:07:16 -05:00
|
|
|
fileSaver.saveAs(e.data.blob, `freesewing-${design || 'pattern'}.${fileType}`)
|
2022-08-24 12:07:08 -05:00
|
|
|
}
|
|
|
|
// do additional business
|
|
|
|
onComplete && onComplete(e)
|
2024-02-18 08:49:25 -08:00
|
|
|
// stop the loader
|
|
|
|
if (typeof stopLoading === 'function') stopLoading()
|
2022-08-24 12:07:08 -05:00
|
|
|
}
|
|
|
|
// on error
|
|
|
|
else {
|
|
|
|
console.log(e.data.error)
|
|
|
|
onError && onError(e)
|
2024-02-18 08:49:25 -08:00
|
|
|
// stop the loader
|
|
|
|
if (typeof stopLoadingFail === 'function') stopLoadingFail()
|
2022-08-24 12:07:08 -05:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
// pdf settings
|
2023-06-05 16:07:16 -05:00
|
|
|
const pageSettings = {
|
|
|
|
...defaultPrintSettings(settings.units),
|
2025-04-01 16:15:20 +02:00
|
|
|
...get(ui, 'layout', {}),
|
2022-08-24 12:07:08 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// arguments to pass to the worker
|
2023-06-05 16:07:16 -05:00
|
|
|
const workerArgs = { format, settings, pageSettings }
|
2022-08-24 12:07:08 -05:00
|
|
|
|
|
|
|
// 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) {
|
2023-06-05 16:07:16 -05:00
|
|
|
settings.embed = false
|
2022-08-24 12:07:08 -05:00
|
|
|
// make a pattern instance for export rendering
|
2023-06-05 16:07:16 -05:00
|
|
|
const layout = settings.layout || ui.layouts?.print || true
|
|
|
|
let pattern = themedPattern(Design, settings, { layout }, format, t)
|
2022-08-24 12:07:08 -05:00
|
|
|
|
2023-06-05 16:07:16 -05:00
|
|
|
// a specified size should override the settings one
|
2023-08-06 18:24:14 +00:00
|
|
|
pageSettings.size = format
|
2022-08-24 12:07:08 -05:00
|
|
|
|
|
|
|
try {
|
|
|
|
// add pages to pdf exports
|
2023-08-06 18:24:14 +00:00
|
|
|
if (!exportTypes.exportForEditing.includes(format)) {
|
2022-10-23 19:52:32 +02:00
|
|
|
pattern.use(
|
2025-04-01 16:15:20 +02:00
|
|
|
tilerPlugin({
|
2023-06-05 16:07:16 -05:00
|
|
|
...pageSettings,
|
2022-10-23 19:52:32 +02:00
|
|
|
printStyle: true,
|
|
|
|
renderBlanks: false,
|
|
|
|
setPatternSize: true,
|
|
|
|
})
|
|
|
|
)
|
2024-02-18 06:56:23 -08:00
|
|
|
}
|
2022-08-24 12:07:08 -05:00
|
|
|
|
2024-02-18 06:56:23 -08:00
|
|
|
// add the strings that are used on the cover page
|
|
|
|
workerArgs.strings = {
|
|
|
|
design: capitalize(design),
|
2025-04-01 16:15:20 +02:00
|
|
|
tagline: 'Come for the sewing pattern. Stay for the community.',
|
2024-02-18 06:56:23 -08:00
|
|
|
url: window.location.href,
|
2025-04-01 16:15:20 +02:00
|
|
|
cuttingLayout: 'Cutting layout',
|
2022-08-24 12:07:08 -05:00
|
|
|
}
|
|
|
|
|
2024-02-14 07:18:33 -08:00
|
|
|
// Initialize the pattern stores
|
|
|
|
pattern.getConfig()
|
|
|
|
|
|
|
|
// Save the measurement set name to pattern stores
|
|
|
|
if (settings?.metadata?.setName) {
|
2025-05-01 16:26:09 +02:00
|
|
|
pattern.store.set('data.setName', escapeSvgText(settings.metadata.setName))
|
|
|
|
for (const store of pattern.setStores)
|
|
|
|
store.set('data.setName', escapeSvgText(settings.metadata.setName))
|
2024-02-14 07:18:33 -08:00
|
|
|
}
|
|
|
|
|
2022-08-24 12:07:08 -05:00
|
|
|
// draft and render the pattern
|
2022-10-23 19:52:32 +02:00
|
|
|
pattern.draft()
|
2022-11-14 14:02:11 -06:00
|
|
|
workerArgs.svg = pattern.render()
|
2022-08-24 12:07:08 -05:00
|
|
|
|
2024-02-14 07:18:33 -08:00
|
|
|
// Get coversheet info: setName, settings YAML, version, notes, warnings
|
2024-02-13 08:09:57 -08:00
|
|
|
const store = pattern.setStores[pattern.activeSet]
|
2024-02-14 07:18:33 -08:00
|
|
|
workerArgs.strings.setName = settings?.metadata?.setName
|
|
|
|
? settings.metadata.setName
|
|
|
|
: 'ephemeral'
|
2025-05-18 13:33:30 +00:00
|
|
|
const settingsWithoutLayout = structuredClone(settings)
|
|
|
|
delete settingsWithoutLayout.layout
|
|
|
|
workerArgs.strings.yaml = yaml.dump(settingsWithoutLayout)
|
2024-02-14 07:18:33 -08:00
|
|
|
workerArgs.strings.version = store?.data?.version ? store.data.version : ''
|
2024-02-13 08:09:57 -08:00
|
|
|
const notes = store?.plugins?.['plugin-annotations']?.flags?.note
|
|
|
|
? store?.plugins?.['plugin-annotations']?.flags?.note
|
|
|
|
: []
|
|
|
|
const warns = store?.plugins?.['plugin-annotations']?.flags?.warn
|
|
|
|
? store?.plugins?.['plugin-annotations']?.flags?.warn
|
|
|
|
: []
|
|
|
|
workerArgs.strings.notes = flagsToString(notes, mustache, t)
|
|
|
|
workerArgs.strings.warns = flagsToString(warns, mustache, t)
|
|
|
|
|
2023-08-06 18:24:14 +00:00
|
|
|
if (format === 'pdf') pageSettings.size = [pattern.width, pattern.height]
|
|
|
|
|
2022-08-24 12:07:08 -05:00
|
|
|
// add the svg and pages data to the worker args
|
2022-11-14 14:02:11 -06:00
|
|
|
workerArgs.pages = pattern.setStores[pattern.activeSet].get('pages')
|
2022-10-23 19:52:32 +02:00
|
|
|
} catch (err) {
|
2022-08-24 12:07:08 -05:00
|
|
|
console.log(err)
|
|
|
|
onError && onError(err)
|
|
|
|
}
|
|
|
|
}
|
2023-06-06 15:19:11 -05:00
|
|
|
|
|
|
|
// post a message to the worker with all needed data
|
|
|
|
worker.postMessage(workerArgs)
|
2022-08-24 12:07:08 -05:00
|
|
|
}
|
2024-02-13 08:09:57 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Convert pattern flags to a formatted string for printing
|
|
|
|
*/
|
|
|
|
const flagsToString = (flags, mustache, t) => {
|
|
|
|
let first = true
|
|
|
|
let string = ''
|
|
|
|
for (const flag of Object.values(flags)) {
|
2025-05-18 13:33:30 +00:00
|
|
|
let title = flag.replace ? mustache.render(t(flag.title), flag.replace) : t(flag.title)
|
2024-02-13 08:09:57 -08:00
|
|
|
title = he.decode(title)
|
2025-05-18 13:33:30 +00:00
|
|
|
let desc = flag.replace ? mustache.render(t(flag.desc), flag.replace) : t(flag.desc)
|
2024-02-13 08:09:57 -08:00
|
|
|
desc = desc.replaceAll('\n\n', '\n')
|
|
|
|
desc = desc.replaceAll('\n', ' ')
|
|
|
|
desc = he.decode(desc)
|
|
|
|
if (!first) string += '\n'
|
|
|
|
first = false
|
|
|
|
string += '- ' + title + ': ' + desc
|
|
|
|
}
|
|
|
|
return string
|
|
|
|
}
|