diff --git a/spx-gui/src/components/project/runner/v1/ProjectRunnerV1.vue b/spx-gui/src/components/project/runner/v1/ProjectRunnerV1.vue index d565a46a..d5544173 100644 --- a/spx-gui/src/components/project/runner/v1/ProjectRunnerV1.vue +++ b/spx-gui/src/components/project/runner/v1/ProjectRunnerV1.vue @@ -13,7 +13,7 @@ import { registerPlayer } from '@/utils/player-registry' import { until } from '@/utils/utils' import { useFileUrl } from '@/utils/file' import { toPng } from '@/utils/img' -import { toNativeFile } from '@/models/common/file' +import { File, toNativeFile } from '@/models/common/file' import { Project } from '@/models/project' import { UIImg, UILoading } from '@/components/ui' import IframeDisplay, { preload } from './IframeDisplay.vue' @@ -47,23 +47,43 @@ onUnmounted(() => { registered.onStopped() }) +const zipEntryCache = new WeakMap>() + +function getZipEntry(file: File) { + if (zipEntryCache.has(file)) return zipEntryCache.get(file)! + const zipEntry = toNativeFile(file).then((nf) => { + // For svg files, we convert them to png before sending to spx (v1): + // 1. Compatibility: Many SVG features are not supported in spx v1 + // 2. Improve performance: SVG rendering is slow in spx v1 + if (nf.type === 'image/svg+xml') { + const startConvertAt = Date.now() + return toPng(nf).then(png => { + console.log(`Convert ${nf.name} to PNG in ${Date.now() - startConvertAt}ms`) + return png + }) + } + return nf + }) + zipEntryCache.set(file, zipEntry) + return zipEntry +} + async function getProjectZipData() { const zip = new JSZip() + let lastAt = Date.now() const [, files] = await props.project.export() + + console.log(`Export project in ${Date.now() - lastAt}ms`) + lastAt = Date.now() + Object.entries(files).forEach(([path, file]) => { if (file == null) return - zip.file( - path, - toNativeFile(file).then((nf) => { - // For svg files, we convert them to png before sending to spx (v1): - // 1. Compatibility: Many SVG features are not supported in spx v1 - // 2. Improve performance: SVG rendering is slow in spx v1 - if (nf.type === 'image/svg+xml') return toPng(nf) - return nf - }) - ) + zip.file(path, getZipEntry(file)) + }) + return zip.generateAsync({ type: 'arraybuffer' }).then(r => { + console.log(`Generate zip in ${Date.now() - lastAt}ms`) + return r }) - return zip.generateAsync({ type: 'arraybuffer' }) } defineExpose({ diff --git a/spx-gui/src/utils/img.ts b/spx-gui/src/utils/img.ts index 2728c250..5743bd65 100644 --- a/spx-gui/src/utils/img.ts +++ b/spx-gui/src/utils/img.ts @@ -11,24 +11,16 @@ export function convertImg( return new Promise((resolve, reject) => { const img = new Image() img.onload = async () => { - const canvas = document.createElement('canvas') + let size = { width: img.naturalWidth, height: img.naturalHeight } if (input.type === 'image/svg+xml') { const svgText = await input.text() - const { width, height } = await getSVGSize(svgText) - canvas.width = width - canvas.height = height - } else { - canvas.width = img.naturalWidth - canvas.height = img.naturalHeight + size = await getSVGSize(svgText) } - canvas.getContext('2d')?.drawImage(img, 0, 0, canvas.width, canvas.height) - canvas.toBlob((newBlob) => { - if (newBlob == null) { - reject(new Error('toBlob failed')) - return - } - resolve(newBlob) - }, type) + const canvas = new OffscreenCanvas(size.width, size.height) + const ctx = canvas.getContext('2d')! + ctx.drawImage(img, 0, 0, size.width, size.height) + const newBlob = await canvas.convertToBlob({ type }) + resolve(newBlob) } img.onerror = (e) => reject(new Error(`load image failed: ${e.toString()}`)) const url = URL.createObjectURL(input) diff --git a/tools/spxls/client.ts b/tools/spxls/client.ts index 5e335b7c..861b9c27 100644 --- a/tools/spxls/client.ts +++ b/tools/spxls/client.ts @@ -1,5 +1,8 @@ import { type Files, type NotificationMessage, type RequestMessage, type ResponseMessage, type ResponseError as ResponseErrorObj, type Spxls } from '.' +// eslint-disable-next-line no-console +const debug = process.env.NODE_ENV === 'developments' ? console.debug : () => {} + /** * Client wrapper for the spxls. */ @@ -75,9 +78,8 @@ export class Spxlc { */ request(method: string, params?: any): Promise { const id = this.nextRequestId++ - /* eslint-disable no-console */ // TODO: remove debug logs - console.debug(`[${id}][${method}] params:`, params) + debug(`[${id}][${method}] params:`, params) const sendAt = Date.now() return new Promise((resolve, reject) => { const message: RequestMessage = { @@ -95,14 +97,14 @@ export class Spxlc { }).then( result => { const time = Date.now() - sendAt - console.debug(`[${id}][${method}] took ${time}ms`) - console.debug(`[${id}][${method}] result:`, result) + debug(`[${id}][${method}] took ${time}ms`) + debug(`[${id}][${method}] result:`, result) return result }, err => { const time = Date.now() - sendAt - console.debug(`[${id}][${method}] took ${time}ms`) - console.debug(`[${id}][${method}] error:`, err) + debug(`[${id}][${method}] took ${time}ms`) + debug(`[${id}][${method}] error:`, err) throw err } )