diff --git a/src/app/document/io/create.ts b/src/app/document/io/create.ts index 24e79a88d..76ae51621 100644 --- a/src/app/document/io/create.ts +++ b/src/app/document/io/create.ts @@ -56,7 +56,7 @@ export function createDocumentIOActions( setDocumentSource: sourceActions.setDocumentSource, fitCurrentPageToViewport }) - const { openDOMFile } = createDOMOpenActions({ + const { openDOMFile, importDOMText } = createDOMOpenActions({ editor, state, setDocumentSource: sourceActions.setDocumentSource, @@ -73,6 +73,7 @@ export function createDocumentIOActions( disposeDocumentIO: sourceActions.disposeDocumentIO, openFigFile, openDOMFile, + importDOMText, saveFigFile: sourceActions.saveFigFile, saveFigFileAs: sourceActions.saveFigFileAs } diff --git a/src/app/document/io/dom.ts b/src/app/document/io/dom.ts index e2821a473..f8d3d9ffe 100644 --- a/src/app/document/io/dom.ts +++ b/src/app/document/io/dom.ts @@ -28,6 +28,11 @@ type DOMImportOptions = { path?: string } +type DOMTextImportOptions = { + cssText?: string + documentName?: string +} + function documentNameFor(file: File): string { return file.name.replace(/\.(html?|xhtml)$/i, '') } @@ -38,19 +43,41 @@ export function createDOMOpenActions({ setDocumentSource, fitCurrentPageToViewport }: OpenDOMFileOptions) { + async function applyDOMText(html: string, options: DOMTextImportOptions) { + await yieldToUI() + const pageName = options.documentName ?? 'DOM Import' + const graph = await browserHTMLToSceneGraph(html, { cssText: options.cssText, pageName }) + await yieldToUI() + await applyImportedDocument(editor, graph) + state.documentName = pageName + await fitCurrentPageToViewport() + editor.requestRender() + return pageName + } + + async function importDOMText(html: string, options: DOMTextImportOptions = {}) { + try { + state.loading = true + const pageName = await applyDOMText(html, options) + setDocumentSource(`${pageName}.html`, 'html') + toast.info('Imported DOM/CSS document') + } catch (e) { + console.error('Failed to import DOM/CSS:', e) + toast.error(`Failed to import DOM/CSS: ${e instanceof Error ? e.message : String(e)}`) + } finally { + state.loading = false + } + } + async function openDOMFile(file: File, options: DOMImportOptions = {}) { try { state.loading = true - await yieldToUI() const html = await file.text() - const pageName = documentNameFor(file) - const graph = await browserHTMLToSceneGraph(html, { cssText: options.cssText, pageName }) - await yieldToUI() - await applyImportedDocument(editor, graph) - state.documentName = pageName + await applyDOMText(html, { + cssText: options.cssText, + documentName: documentNameFor(file) + }) setDocumentSource(file.name, 'html', options.handle, options.path) - await fitCurrentPageToViewport() - editor.requestRender() } catch (e) { console.error('Failed to open DOM/CSS file:', e) toast.error(`Failed to open DOM/CSS file: ${e instanceof Error ? e.message : String(e)}`) @@ -59,5 +86,5 @@ export function createDOMOpenActions({ } } - return { openDOMFile } + return { openDOMFile, importDOMText } } diff --git a/src/app/editor/session/modules.ts b/src/app/editor/session/modules.ts index 8bda7db2e..634bcb2aa 100644 --- a/src/app/editor/session/modules.ts +++ b/src/app/editor/session/modules.ts @@ -70,6 +70,7 @@ export function createEditorStoreModules( ...vectorEdit, openFigFile: documentIO.openFigFile, openDOMFile: documentIO.openDOMFile, + importDOMText: documentIO.importDOMText, setViewportSize: documentIO.setViewportSize, fitCurrentPageToViewport: documentIO.fitCurrentPageToViewport, saveFigFile: documentIO.saveFigFile, diff --git a/src/components/CodePanel.vue b/src/components/CodePanel.vue index 88c00b6c7..eab1d5531 100644 --- a/src/components/CodePanel.vue +++ b/src/components/CodePanel.vue @@ -18,6 +18,10 @@ const store = useEditorStore() const { copy, copied } = useClipboard({ copiedDuring: 2000 }) const { dialogs } = useI18n() const jsxFormat = ref('openpencil') +const showImporter = ref(false) +const importHTML = ref('') +const importCSS = ref('') +const importing = ref(false) function toggleFormat() { jsxFormat.value = jsxFormat.value === 'openpencil' ? 'tailwind' : 'openpencil' @@ -38,6 +42,32 @@ const highlightedLines = computed(() => { const { copy: copyRef, copied: copiedRef } = useClipboard({ copiedDuring: 2000 }) +const canImport = computed(() => importHTML.value.trim().length > 0) + +function toggleImporter() { + showImporter.value = !showImporter.value +} + +async function pasteImportHTML() { + try { + importHTML.value = await navigator.clipboard.readText() + } catch (e) { + console.error('Failed to read clipboard:', e) + } +} + +async function importCode() { + if (!canImport.value || importing.value) return + try { + importing.value = true + await store.importDOMText(importHTML.value, { + cssText: importCSS.value.trim() || undefined + }) + } finally { + importing.value = false + } +} + function copyCode() { copy(jsxCode.value) } @@ -48,16 +78,9 @@ function copyReference() {