1
0
Fork 0

move export to web worker

This commit is contained in:
Enoch Riese 2022-08-21 10:26:35 +01:00
parent df7011329b
commit 7cf7e0c90a
13 changed files with 248 additions and 133 deletions

View file

@ -55,5 +55,8 @@
}, },
"browserslist": [ "browserslist": [
"last 2 versions" "last 2 versions"
] ],
"engines": {
"node": "14.x"
}
} }

View file

@ -1,6 +1,6 @@
import Page from 'site/components/wrappers/page.js' import Page from 'site/components/wrappers/page.js'
import useApp from 'site/hooks/useApp.js' import useApp from 'site/hooks/useApp.js'
import WorkbenchWrapper from 'shared/components/wrappers/workbench.js' import WorkbenchWrapper from 'shared/components/wrappers/workbench'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import Layout from 'site/components/layouts/lab' import Layout from 'site/components/layouts/lab'

View file

@ -0,0 +1,56 @@
import yaml from 'js-yaml'
import axios from 'axios'
import PdfExporter from './pdfExporter'
addEventListener('message', async(e) => {
const {format, gist, svg} = e.data
// handle the data exports
if (format === 'json') return exportJson(gist)
if (format === 'yaml') return exportYaml(gist)
if (format === 'github gist') return exportGithubGist(gist)
if (format === 'svg') return exportSvg(gist, svg)
new PdfExporter(e.data).export(postSuccess)
})
const postSuccess = (blob) => {
postMessage({success: true, blob})
close()
}
const exportJson = gist => {
const blob = new Blob([JSON.stringify(gist, null, 2)], {
type: 'application/json;charset=utf-8'
})
postSuccess(blob)
}
const exportYaml = gist => {
const blob = new Blob([yaml.dump(gist)], {
type: 'application/x-yaml;charset=utf-8'
})
postSuccess(blob)
}
const exportSvg = (gist, svg) => {
const blob = new Blob([svg], {
type: 'image/svg+xml;charset=utf-8'
})
postSuccess(blob)
}
const exportGithubGist = (data) => {
axios.post('https://backend.freesewing.org/github/gist', {
design: data.design,
data: yaml.dump(data)
})
.then(res => {
postMessage({
success: true,
link: 'https://gist.github.com/' + res.data.id
})
})
.catch(err => console.log(err))
}

View file

@ -1,13 +1,11 @@
import { useState } from 'react' import { useState } from 'react'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import fileSaver from 'file-saver'
import yaml from 'js-yaml'
import axios from 'axios'
import Popout from 'shared/components/popout' import Popout from 'shared/components/popout'
import WebLink from 'shared/components/web-link' import WebLink from 'shared/components/web-link'
import Worker from 'web-worker';
import fileSaver from 'file-saver'
import theme from '@freesewing/plugin-theme' import theme from '@freesewing/plugin-theme'
import {pagesPlugin} from '../layout/print/plugin' import {pagesPlugin} from '../layout/print/plugin'
import PdfExporter from './pdfExporter'
export const exports = { export const exports = {
exportForPrinting: ['a4', 'a3', 'a2', 'a1', 'a0', 'letter', 'tabloid'], exportForPrinting: ['a4', 'a3', 'a2', 'a1', 'a0', 'letter', 'tabloid'],
@ -22,91 +20,73 @@ export const defaultPdfSettings = {
coverPage: true coverPage: true
} }
export const handleExport = (format, gist, design, t, app, setLink, setFormat) => { export const handleExport = async(format, gist, design, t, app, onComplete) => {
app.startLoading();
// handle state setting if there's supposed to be any const worker = new Worker(new URL('./export_worker.js', import.meta.url), {type: module});
setLink && setLink(false)
setFormat && setFormat(format)
// handle the data exports worker.addEventListener('message', e => {
if (exports.exportAsData.indexOf(format) !== -1) { if (e.data.blob) {
if (format === 'json') exportJson(gist) const fileType = exports.exportForPrinting.indexOf(format) === -1 ? format : 'pdf'
else if (format === 'yaml') exportYaml(gist) fileSaver.saveAs(e.data.blob, `freesewing-${gist.design || 'gist'}.${fileType}`)
else if (format === 'github gist') exportGithubGist(gist, app, setLink)
return
}
gist.embed=false
let svg = ''
// make a pattern instance for export rendering
const layout = gist.layouts?.printingLayout || gist.layout || true
let pattern = new design({...gist, layout})
// add the theme and translation to the pattern
pattern.use(theme, {stripped: format !== 'svg'})
pattern.use({
hooks: {
insertText: (locale, text, {t}) => t(text)
} }
},{t}) app.stopLoading()
onComplete && onComplete(e)
})
let svg = ''
// pdf settings // pdf settings
const settings = { const settings = {
...defaultPdfSettings, ...defaultPdfSettings,
...(gist._state.layout?.forPrinting?.page || {}) ...(gist._state.layout?.forPrinting?.page || {})
} }
const workerArgs = {format, gist, settings}
// a specified size should override the gist one if (exports.exportAsData.indexOf(format) === -1) {
if (format !== 'pdf') { // gist.embed=false
settings.size = format // make a pattern instance for export rendering
} const layout = gist.layouts?.printingLayout || gist.layout || true
let pattern = new design({...gist, layout})
try {
// add pages to pdf exports // add the theme and translation to the pattern
if (format !== 'svg') { pattern.use(theme, {stripped: format !== 'svg'})
pattern.use(pagesPlugin(settings, true)) pattern.use({
hooks: {
insertText: (locale, text, {t}) => t(text)
}
},{t})
// a specified size should override the gist one
if (format !== 'pdf') {
settings.size = format
}
try {
// add pages to pdf exports
if (format !== 'svg') {
pattern.use(pagesPlugin({
...settings,
printStyle: true,
renderBlanks: false,
setPatternSize: true
}))
}
pattern.draft();
svg = pattern.render()
workerArgs.svg = svg
if (pattern.parts.pages) {
workerArgs.pages = pattern.parts.pages.pages
}
} catch(err) {
console.log(err)
app.stopLoading();
} }
pattern.draft();
svg = pattern.render()
} catch(err) {
console.log(err)
} }
if (format === 'svg') return exportSvg(gist, svg) worker.postMessage(workerArgs)
return new PdfExporter(gist.design, pattern, svg, settings).export();
}
const exportJson = gist => {
const blob = new Blob([JSON.stringify(gist, null, 2)], {
type: 'application/json;charset=utf-8'
})
fileSaver.saveAs(blob, `freesewing-${gist.design || 'gist'}.json`)
}
const exportYaml = gist => {
const blob = new Blob([yaml.dump(gist)], {
type: 'application/x-yaml;charset=utf-8'
})
fileSaver.saveAs(blob, `freesewing-${gist.design || 'gist'}.yaml`)
}
const exportSvg = (gist, svg) => {
const blob = new Blob([svg], {
type: 'image/svg+xml;charset=utf-8'
})
fileSaver.saveAs(blob, `freesewing-${gist.design || 'pattern'}.svg`)
}
const exportGithubGist = (data, app, setLink) => {
app.setLoading(true)
axios.post('https://backend.freesewing.org/github/gist', {
design: data.design,
data: yaml.dump(data)
})
.then(res => setLink('https://gist.github.com/' + res.data.id))
.catch(err => console.log(err))
.finally(() => app.stopLoading())
} }
const ExportDraft = ({ gist, design, app }) => { const ExportDraft = ({ gist, design, app }) => {
@ -115,6 +95,13 @@ const ExportDraft = ({ gist, design, app }) => {
const [format, setFormat] = useState(false) const [format, setFormat] = useState(false)
const { t } = useTranslation(['app']) const { t } = useTranslation(['app'])
const doExport = (format) => {
setLink(false)
setFormat(format)
handleExport(format, gist, design, t, app, (e) => {
if (e.data.link) {setLink(e.data.link)}
})
}
return ( return (
<div className="max-w-screen-xl m-auto"> <div className="max-w-screen-xl m-auto">
@ -135,7 +122,7 @@ const ExportDraft = ({ gist, design, app }) => {
{exports[type].map(format => ( {exports[type].map(format => (
<button key={format} <button key={format}
className="btn btn-primary" className="btn btn-primary"
onClick={() => handleExport(format, gist, design, t, app, setLink, setFormat)} onClick={() => doExport(format)}
> >
{type === 'exportForPrinting' ? `${format} pdf` : format } {type === 'exportForPrinting' ? `${format} pdf` : format }
</button> </button>

View file

@ -13,7 +13,8 @@ const pxToPoints = (72/96);
// multiply a mm value by this to get a pixel value // multiply a mm value by this to get a pixel value
const mmToPx = 3.77953 const mmToPx = 3.77953
// multiply a mm value by this to get a points value // multiply a mm value by this to get a points value
const mmToPoints = mmToPx * pxToPoints // const mmToPoints = mmToPx * pxToPoints
const mmToPoints = 2.834645669291339
/** /**
* Freesewing's first explicit class? * Freesewing's first explicit class?
@ -30,6 +31,7 @@ export default class Exporter {
pattern pattern
/** the pdfKit instance that is writing the document */ /** the pdfKit instance that is writing the document */
pdf pdf
buffers
/** the usable width (excluding margin) of the pdf page, in points */ /** the usable width (excluding margin) of the pdf page, in points */
pageWidth pageWidth
@ -51,36 +53,40 @@ export default class Exporter {
pagesWithContent = {} pagesWithContent = {}
constructor(designName, pattern, svg, settings) { constructor({svg, settings, pages}) {
// default just in case
this.designName = designName || 'freesewing'
this.settings = settings this.settings = settings
this.pattern = pattern this.pagesWithContent = pages.withContent;
this.svg = svg
this.createPdf() this.createPdf()
this.margin = this.settings.margin * mmToPoints // margin is in mm because it comes from us, so we convert it to points this.margin = this.settings.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 // this is in points because it comes from pdfKit 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// 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
// console.log(pages.width * pages.cols, pages.height * pages.rows, this.pageWidth, this.pageHeight)
// console.log(svg)
// we pass the svg as a string, so we need to make it a DOM element so we can manipulate it // we pass the svg as a string, so we need to make it a DOM element so we can manipulate it
const divElem = document.createElement('div'); // const divElem = document.createElement('div');
divElem.innerHTML = svg; // divElem.innerHTML = svg;
this.svg = divElem.firstElementChild; // this.svg = divElem.firstElementChild;
// get the pages data // get the pages data
const pages = this.pattern.parts.pages.pages
this.columns = pages.cols this.columns = pages.cols
this.rows = pages.rows this.rows = pages.rows
// then set the svg's width and height in points to include all pages in full (the original svg will have been set to show only as much page as is occupied) // then set the svg's width and height in points to include all pages in full (the original svg will have been set to show only as much page as is occupied)
this.svgWidth = this.columns * this.pageWidth this.svgWidth = this.columns * pages.width * mmToPoints
this.svgHeight = this.rows * this.pageHeight this.svgHeight = this.rows * pages.height * mmToPoints
this.svg.setAttribute('height', this.svgWidth + 'pt')
this.svg.setAttribute('width', this.svgHeight + 'pt') console.log(this.columns, this.rows, pages.width, pages.height, this.svgWidth, this.svgHeight)
console.log(svg)
// this.svg.replace(/width="\d+mm"/, `width="${this.svgWidth}pt"`)
// this.svg.replace(/height="\d+mm"/, `height="${this.svgHeight}pt"`)
// this.svg.setAttribute('height', this.svgWidth + 'pt')
// this.svg.setAttribute('width', this.svgHeight + 'pt')
// set the viewbox to include all pages in full as well, this time in mm // set the viewbox to include all pages in full as well, this time in mm
this.svg.setAttribute('viewBox', `0 0 ${this.columns * this.pageWidthInMm} ${this.rows * this.pageHeightInMm}`) // this.svg.setAttribute('viewBox', `0 0 ${this.columns * this.pageWidthInMm} ${this.rows * this.pageHeightInMm}`)
} }
/** pdf page usable (excluding margin) width, in mm */ /** pdf page usable (excluding margin) width, in mm */
@ -99,18 +105,9 @@ export default class Exporter {
// PdfKit wants to flush the buffer on each new page. // PdfKit wants to flush the buffer on each new page.
// We don't want to try to save the document until it's complete, so we have to manage the buffers ourselves // We don't want to try to save the document until it's complete, so we have to manage the buffers ourselves
const buffers = []; this.buffers = [];
// add new data to our buffer storage // add new data to our buffer storage
this.pdf.on('data', buffers.push.bind(buffers)); this.pdf.on('data', this.buffers.push.bind(this.buffers));
// when the end event fires, then we save the whole thing
this.pdf.on('end', () => {
// convert buffers to a blob
const blob = new Blob(buffers, {
type: 'application/pdf'
})
// save the blob
fileSaver.saveAs(blob, `freesewing-${this.designName}.pdf`)
});
} }
/** /**
@ -197,8 +194,16 @@ export default class Exporter {
} }
/** export to pdf */ /** export to pdf */
async export() { async export(onComplete) {
this.scanPages() // when the end event fires, then we save the whole thing
this.pdf.on('end', () => {
// convert buffers to a blob
const blob = new Blob(this.buffers, {
type: 'application/pdf'
})
onComplete(blob)
});
await this.generateCoverPage() await this.generateCoverPage()
await this.generatePages(); await this.generatePages();
this.save() this.save()
@ -218,7 +223,7 @@ export default class Exporter {
let coverWidth = this.pageWidth - coverMargin * 2 let coverWidth = this.pageWidth - coverMargin * 2
// add the entire pdf to the page, so that it fills the available space as best it can // add the entire pdf to the page, so that it fills the available space as best it can
await SVGtoPDF(this.pdf, this.svg.outerHTML, coverMargin, coverMargin, { await SVGtoPDF(this.pdf, this.svg, coverMargin, coverMargin, {
width: coverWidth, width: coverWidth,
height: coverHeight, height: coverHeight,
assumePt: true, assumePt: true,
@ -234,11 +239,12 @@ export default class Exporter {
assumePt: true, assumePt: true,
width: this.svgWidth, width: this.svgWidth,
height: this.svgHeight, height: this.svgHeight,
preserveAspectRatio: 'xMinYMin slice' preserveAspectRatio: 'xMinYMin slice',
useCSS: true
} }
// everything is offset by half a margin so that it's centered on the page // everything is offset by half a margin so that it's centered on the page
const startMargin = 0.5 * this.margin const startMargin = this.margin
for (var h = 0; h < this.rows; h++) { for (var h = 0; h < this.rows; h++) {
for (var w = 0; w < this.columns; w++) { for (var w = 0; w < this.columns; w++) {
// skip empty pages // skip empty pages
@ -255,7 +261,7 @@ export default class Exporter {
} }
// add the pdf to the page, offset by the page distances // add the pdf to the page, offset by the page distances
await SVGtoPDF(this.pdf, this.svg.outerHTML, x, y, options) await SVGtoPDF(this.pdf, this.svg, x, y, options)
} }
} }
} }

View file

@ -7,6 +7,8 @@ import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch"
const Draft = props => { const Draft = props => {
const { draft, patternProps, gist, updateGist, app, bgProps={}, fitLayoutPart = false, layoutType="printingLayout"} = props const { draft, patternProps, gist, updateGist, app, bgProps={}, fitLayoutPart = false, layoutType="printingLayout"} = props
const svgRef = useRef(null);
if (!patternProps) return null
// keep a fresh copy of the layout because we might manipulate it without saving to the gist // keep a fresh copy of the layout because we might manipulate it without saving to the gist
let layout = draft.settings.layout === true ? { let layout = draft.settings.layout === true ? {
...patternProps.autoLayout, ...patternProps.autoLayout,
@ -14,9 +16,7 @@ const Draft = props => {
height: patternProps.height height: patternProps.height
} : {...draft.settings.layout} } : {...draft.settings.layout}
const svgRef = useRef(null);
if (!patternProps) return null
// Helper method to update part layout and re-calculate width * height // Helper method to update part layout and re-calculate width * height
const updateLayout = (name, config, history=true) => { const updateLayout = (name, config, history=true) => {

View file

@ -35,7 +35,7 @@ const PrintLayout = props => {
const bgProps = { fill: "url(#page)" } const bgProps = { fill: "url(#page)" }
const exportIt = () => { const exportIt = () => {
handleExport('pdf', props.gist, props.design, t) handleExport('pdf', props.gist, props.design, t, props.app)
} }
return ( return (

View file

@ -16,15 +16,55 @@ const indexLetter = (i) => String.fromCharCode('A'.charCodeAt(0) + i - 1)
/** /**
* A plugin to add printer pages * A plugin to add printer pages
* */ * */
export const pagesPlugin = ({size='a4', orientation='portrait', margin=10}, printStyle = false /** should the pages be rendered for printing or for screen viewing? */ ) => { export const pagesPlugin = ({
size='a4',
orientation='portrait',
margin=10,
printStyle = false, /** should the pages be rendered for printing or for screen viewing? */
setPatternSize = true
}) => {
const ls = orientation === 'landscape' const ls = orientation === 'landscape'
let sheetHeight = sizes[size][ls ? 1 : 0] let sheetHeight = sizes[size][ls ? 0 : 1]
let sheetWidth = sizes[size][ls ? 0 : 1] let sheetWidth = sizes[size][ls ? 1 : 0]
sheetWidth -= margin sheetWidth -= margin * 2
sheetHeight -= margin sheetHeight -= margin * 2
return basePlugin({sheetWidth, sheetHeight, orientation, printStyle}) return basePlugin({sheetWidth, sheetHeight, orientation, printStyle, setPatternSize})
} }
const doScanForBlanks = (parts, layout, x, y, w, h) => {
let hasContent = false
for (var p in parts) {
let part = parts[p]
// skip the pages part and any that aren't rendered
if (part === this || part.render === false || part.isEmpty()) continue
// get the position of the part
let partLayout = layout.parts[p]
let partMinX = (partLayout.tl?.x || (partLayout.move.x + part.topLeft.x))
let partMinY = (partLayout.tl?.y || (partLayout.move.y + part.topLeft.y))
let partMaxX = (partLayout.br?.x || (partMinX + part.width))
let partMaxY = (partLayout.br?.y || (partMinY + part.height))
// check if the part overlaps the page extents
if (
// if the left of the part is further left than the right end of the page
partMinX < x + w &&
// and the top of the part is above the bottom of the page
partMinY < y + h &&
// and the right of the part is further right than the left of the page
partMaxX > x &&
// and the bottom of the part is below the top to the page
partMaxY > y
) {
// the part has content inside the page
hasContent = true;
// so we stop looking
break;
}
}
return hasContent
}
/** /**
* The base plugin for adding a layout helper part like pages or fabric * The base plugin for adding a layout helper part like pages or fabric
* sheetWidth: the width of the helper part * sheetWidth: the width of the helper part
@ -39,7 +79,10 @@ const basePlugin = ({
boundary=false, boundary=false,
partName="pages", partName="pages",
responsiveColumns=true, responsiveColumns=true,
printStyle=false printStyle=false,
scanForBlanks=true,
renderBlanks=true,
setPatternSize=false
}) => ({ }) => ({
name, name,
version, version,
@ -62,9 +105,13 @@ const basePlugin = ({
responsiveColumns && (width += pattern.settings.layout.topLeft.x) responsiveColumns && (width += pattern.settings.layout.topLeft.x)
} }
macro('addPages', { size: [sheetWidth, sheetHeight], height, width }) macro('addPages', { size: [sheetHeight,sheetWidth, ], height, width })
if (boundary) pattern.parts[partName].boundary(); if (boundary) pattern.parts[partName].boundary();
if (setPatternSize) {
pattern.width = sheetWidth * pattern.parts[partName].pages.cols
pattern.height = sheetHeight * pattern.parts[partName].pages.rows
}
} }
}, },
macros: { macros: {
@ -74,13 +121,22 @@ const basePlugin = ({
const cols = Math.ceil(so.width / w) const cols = Math.ceil(so.width / w)
const rows = Math.ceil(so.height / h) const rows = Math.ceil(so.height / h)
const { points, Point, paths, Path, macro } = this.shorthand() const { points, Point, paths, Path, macro } = this.shorthand()
let x = 0
let y = 0
let count = 0 let count = 0
let withContent = {}
// get the layout from the pattern
const layout = typeof this.context.settings.layout === 'object' ? this.context.settings.layout : this.context.autoLayout;
for (let row=0;row<rows;row++) { for (let row=0;row<rows;row++) {
x=0 let y = row * h
withContent[row] = {}
for (let col=0;col<cols;col++) { for (let col=0;col<cols;col++) {
count++ let x = col * w
let hasContent = true
if (scanForBlanks && layout) {
hasContent = doScanForBlanks(this.context.parts, layout, x, y, w, h)
withContent[row][col] = hasContent
if (!renderBlanks && !hasContent) continue
}
if (hasContent) count++
const pageName = `_pages__row${row}-col${col}` const pageName = `_pages__row${row}-col${col}`
points[`${pageName}-tl`] = new Point(x,y) points[`${pageName}-tl`] = new Point(x,y)
points[`${pageName}-tr`] = new Point(x+w,y) points[`${pageName}-tr`] = new Point(x+w,y)
@ -114,12 +170,10 @@ const basePlugin = ({
macro('addRuler', {xAxis: true, pageName}) macro('addRuler', {xAxis: true, pageName})
macro('addRuler', {xAxis: false, pageName}) macro('addRuler', {xAxis: false, pageName})
} }
x += w
} }
y += h
} }
// Store page count in part // Store page count in part
this.pages = { cols, rows, count: cols*rows } this.pages = { cols, rows, count, withContent, width: w, height: h}
}, },
/** add a ruler to the top left corner of the page */ /** add a ruler to the top left corner of the page */

View file

@ -49,7 +49,7 @@ const PrintLayoutSettings = props => {
<input <input
type="range" type="range"
max={50} max={50}
min={5} min={0}
step={1} step={1}
onChange={setMargin} onChange={setMargin}
value={margin} value={margin}

View file

@ -11,7 +11,7 @@ import Modal from 'shared/components/modal'
import Measurements from 'shared/components/workbench/measurements/index.js' import Measurements from 'shared/components/workbench/measurements/index.js'
import LabDraft from 'shared/components/workbench/draft/index.js' import LabDraft from 'shared/components/workbench/draft/index.js'
import LabSample from 'shared/components/workbench/sample.js' import LabSample from 'shared/components/workbench/sample.js'
import ExportDraft from 'shared/components/workbench/exporting/index.js' import ExportDraft from 'shared/components/workbench/exporting/index'
import GistAsJson from 'shared/components/workbench/json.js' import GistAsJson from 'shared/components/workbench/json.js'
import GistAsYaml from 'shared/components/workbench/yaml.js' import GistAsYaml from 'shared/components/workbench/yaml.js'
import DraftEvents from 'shared/components/workbench/events.js' import DraftEvents from 'shared/components/workbench/events.js'

View file

@ -39,7 +39,8 @@
"remark-smartypants": "^2.0.0", "remark-smartypants": "^2.0.0",
"svg-to-pdfkit": "^0.1.8", "svg-to-pdfkit": "^0.1.8",
"to-vfile": "^7.2.2", "to-vfile": "^7.2.2",
"unist-util-visit": "^4.1.0" "unist-util-visit": "^4.1.0",
"web-worker": "^1.2.0"
}, },
"devDependencies": { "devDependencies": {
"autoprefixer": "^10.4.0", "autoprefixer": "^10.4.0",
@ -47,5 +48,8 @@
"postcss": "^8.4.4", "postcss": "^8.4.4",
"tailwindcss": "^3.0.1", "tailwindcss": "^3.0.1",
"tailwindcss-open-variant": "^1.0.0" "tailwindcss-open-variant": "^1.0.0"
},
"engines": {
"node": "14.x"
} }
} }

View file

@ -33,7 +33,7 @@ const loadFromUnpkg = (design, version) => {
const pageTemplate = design => `${header} const pageTemplate = design => `${header}
import design from 'designs/${design}/src/index.js' import design from 'designs/${design}/src/index.js'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations' import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import PageTemplate from 'site/page-templates/workbench.js' import PageTemplate from 'site/page-templates/workbench'
const Page = (props) => <PageTemplate {...props} design={design} version="next"/> const Page = (props) => <PageTemplate {...props} design={design} version="next"/>
export default Page export default Page

View file

@ -24378,6 +24378,11 @@ web-streams-polyfill@^3.0.3:
resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6" resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6"
integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q== integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==
web-worker@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/web-worker/-/web-worker-1.2.0.tgz#5d85a04a7fbc1e7db58f66595d7a3ac7c9c180da"
integrity sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==
webidl-conversions@^3.0.0: webidl-conversions@^3.0.0:
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"