fix(io): parse fig files off the main thread
This commit is contained in:
parent
317b3469df
commit
cf416e6ceb
|
|
@ -1,8 +1,9 @@
|
|||
import { IS_BROWSER } from '../../../constants'
|
||||
import { importNodeChanges } from '../../../kiwi/fig-import'
|
||||
import { parseFigBuffer } from '../../../kiwi/fig-parse-core'
|
||||
import { deserializeSceneGraph } from '../../../kiwi/graph-transfer'
|
||||
|
||||
import type { FigParseResult } from '../../../kiwi/fig-parse-core'
|
||||
import type { SerializedSceneGraph } from '../../../kiwi/graph-transfer'
|
||||
import type { SceneGraph } from '../../../scene-graph'
|
||||
|
||||
function parseFigFileSync(buffer: ArrayBuffer): SceneGraph {
|
||||
|
|
@ -12,23 +13,24 @@ function parseFigFileSync(buffer: ArrayBuffer): SceneGraph {
|
|||
return graph
|
||||
}
|
||||
|
||||
interface WorkerParseResult {
|
||||
graph?: SerializedSceneGraph
|
||||
error?: string
|
||||
}
|
||||
|
||||
function parseViaWorker(buffer: ArrayBuffer): Promise<SceneGraph> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const worker = new Worker(new URL('../../../kiwi/fig-parse-worker.ts', import.meta.url), {
|
||||
type: 'module'
|
||||
})
|
||||
|
||||
worker.onmessage = (e: MessageEvent<FigParseResult & { error?: string }>) => {
|
||||
worker.onmessage = (e: MessageEvent<WorkerParseResult>) => {
|
||||
worker.terminate()
|
||||
if (e.data.error) {
|
||||
reject(new Error(e.data.error))
|
||||
if (e.data.error || !e.data.graph) {
|
||||
reject(new Error(e.data.error ?? 'Worker failed to parse .fig file'))
|
||||
return
|
||||
}
|
||||
const { nodeChanges, blobs, images: imageEntries, figKiwiVersion } = e.data
|
||||
const images = new Map<string, Uint8Array>(imageEntries)
|
||||
const graph = importNodeChanges(nodeChanges, blobs, images)
|
||||
graph.figKiwiVersion = figKiwiVersion
|
||||
resolve(graph)
|
||||
resolve(deserializeSceneGraph(e.data.graph))
|
||||
}
|
||||
|
||||
worker.onerror = (err) => {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
import { importNodeChanges } from './fig-import'
|
||||
import { parseFigBuffer } from './fig-parse-core'
|
||||
|
||||
export type { FigParseResult } from './fig-parse-core'
|
||||
import { serializeSceneGraph } from './graph-transfer'
|
||||
|
||||
self.onmessage = (e: MessageEvent<ArrayBuffer>) => {
|
||||
try {
|
||||
self.postMessage(parseFigBuffer(e.data))
|
||||
const { nodeChanges, blobs, images, figKiwiVersion } = parseFigBuffer(e.data)
|
||||
const graph = importNodeChanges(nodeChanges, blobs, new Map(images))
|
||||
graph.figKiwiVersion = figKiwiVersion
|
||||
self.postMessage({ graph: serializeSceneGraph(graph) })
|
||||
} catch (err) {
|
||||
self.postMessage({ error: err instanceof Error ? err.message : String(err) })
|
||||
}
|
||||
|
|
|
|||
43
packages/core/src/kiwi/graph-transfer.ts
Normal file
43
packages/core/src/kiwi/graph-transfer.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { SceneGraph } from '../scene-graph'
|
||||
|
||||
import type { SceneNode, Variable, VariableCollection, DocumentColorSpace } from '../scene-graph'
|
||||
|
||||
export interface SerializedSceneGraph {
|
||||
rootId: string
|
||||
nodes: Array<[string, SceneNode]>
|
||||
images: Array<[string, Uint8Array]>
|
||||
variables: Array<[string, Variable]>
|
||||
variableCollections: Array<[string, VariableCollection]>
|
||||
activeMode: Array<[string, string]>
|
||||
instanceIndex: Array<[string, string[]]>
|
||||
figKiwiVersion: number | null
|
||||
documentColorSpace: DocumentColorSpace
|
||||
}
|
||||
|
||||
export function serializeSceneGraph(graph: SceneGraph): SerializedSceneGraph {
|
||||
return {
|
||||
rootId: graph.rootId,
|
||||
nodes: [...graph.nodes],
|
||||
images: [...graph.images],
|
||||
variables: [...graph.variables],
|
||||
variableCollections: [...graph.variableCollections],
|
||||
activeMode: [...graph.activeMode],
|
||||
instanceIndex: [...graph.instanceIndex].map(([id, nodeIds]) => [id, [...nodeIds]]),
|
||||
figKiwiVersion: graph.figKiwiVersion,
|
||||
documentColorSpace: graph.documentColorSpace
|
||||
}
|
||||
}
|
||||
|
||||
export function deserializeSceneGraph(data: SerializedSceneGraph): SceneGraph {
|
||||
const graph = new SceneGraph()
|
||||
graph.rootId = data.rootId
|
||||
graph.nodes = new Map(data.nodes)
|
||||
graph.images = new Map(data.images)
|
||||
graph.variables = new Map(data.variables)
|
||||
graph.variableCollections = new Map(data.variableCollections)
|
||||
graph.activeMode = new Map(data.activeMode)
|
||||
graph.instanceIndex = new Map(data.instanceIndex.map(([id, nodeIds]) => [id, new Set(nodeIds)]))
|
||||
graph.figKiwiVersion = data.figKiwiVersion
|
||||
graph.documentColorSpace = data.documentColorSpace
|
||||
return graph
|
||||
}
|
||||
|
|
@ -82,6 +82,10 @@ export function closeTab(tabId: string) {
|
|||
closingTab.store.dispose()
|
||||
}
|
||||
|
||||
function yieldToUI(): Promise<void> {
|
||||
return new Promise((resolve) => requestAnimationFrame(() => resolve()))
|
||||
}
|
||||
|
||||
export async function openFileInNewTab(
|
||||
file: File,
|
||||
handle?: FileSystemFileHandle,
|
||||
|
|
@ -90,31 +94,29 @@ export async function openFileInNewTab(
|
|||
const current = activeTab.value
|
||||
const isUntouched =
|
||||
current?.store.state.documentName === 'Untitled' && !current.store.undo.canUndo
|
||||
const bytes = new Uint8Array(await file.arrayBuffer())
|
||||
const { graph: imported, sourceFormat } = await io.readDocument({
|
||||
name: file.name,
|
||||
mimeType: file.type || undefined,
|
||||
data: bytes
|
||||
})
|
||||
const store = isUntouched ? current.store : createTab().store
|
||||
const documentName = file.name.replace(/\.[^.]+$/i, '')
|
||||
|
||||
if (isUntouched) {
|
||||
current.store.replaceGraph(imported)
|
||||
current.store.undo.clear()
|
||||
current.store.state.documentName = documentName
|
||||
current.store.setDocumentSource(file.name, sourceFormat, handle, path)
|
||||
current.store.state.selectedIds = new Set()
|
||||
const pageId = current.store.graph.getPages()[0]?.id ?? current.store.graph.rootId
|
||||
await current.store.switchPage(pageId)
|
||||
} else {
|
||||
const store = createEditorStore(imported)
|
||||
createTab(store)
|
||||
store.state.documentName = documentName
|
||||
store.state.loading = true
|
||||
await yieldToUI()
|
||||
|
||||
try {
|
||||
const bytes = new Uint8Array(await file.arrayBuffer())
|
||||
const { graph: imported, sourceFormat } = await io.readDocument({
|
||||
name: file.name,
|
||||
mimeType: file.type || undefined,
|
||||
data: bytes
|
||||
})
|
||||
|
||||
store.replaceGraph(imported)
|
||||
store.undo.clear()
|
||||
store.state.documentName = documentName
|
||||
store.setDocumentSource(file.name, sourceFormat, handle, path)
|
||||
store.state.selectedIds = new Set()
|
||||
const pageId = store.graph.getPages()[0]?.id ?? store.graph.rootId
|
||||
await store.switchPage(pageId)
|
||||
} finally {
|
||||
store.state.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue