From 581abf3b93d3da491de62bd81f3f1bb25f77f988 Mon Sep 17 00:00:00 2001 From: Danila Poyarkov Date: Sun, 29 Mar 2026 19:16:45 +0300 Subject: [PATCH] Fix export panel target handling --- packages/vue/src/controls/useExport.ts | 100 ++++++++++++++------ src/components/DesignPanel.vue | 1 + src/components/properties/ExportSection.vue | 79 ++++++++++++---- 3 files changed, 135 insertions(+), 45 deletions(-) diff --git a/packages/vue/src/controls/useExport.ts b/packages/vue/src/controls/useExport.ts index bf7fefb39..909db436c 100644 --- a/packages/vue/src/controls/useExport.ts +++ b/packages/vue/src/controls/useExport.ts @@ -1,14 +1,12 @@ -import { ref } from 'vue' +import { computed, ref } from 'vue' import { BUILTIN_IO_FORMATS, IORegistry } from '@open-pencil/core' import { useEditor } from '@open-pencil/vue/context/editorContext' import { useSceneComputed } from '@open-pencil/vue/internal/useSceneComputed' export type ExportFormatId = 'png' | 'jpg' | 'webp' | 'svg' | 'fig' +export type ExportPanelTarget = 'selection' | 'page' -/** - * Single export preset row managed by {@link useExport}. - */ interface ExportSetting { scale: number format: ExportFormatId @@ -18,60 +16,104 @@ const SCALES = [0.5, 0.75, 1, 1.5, 2, 3, 4] as const const FORMATS: ExportFormatId[] = ['png', 'jpg', 'webp', 'svg', 'fig'] const io = new IORegistry(BUILTIN_IO_FORMATS) -/** - * Returns selection-aware export settings for export panel UIs. - * - * This composable manages export presets such as scale and format while also - * exposing the current export target label derived from the selection. - */ +function createDefaultSetting(): ExportSetting { + return { scale: 1, format: 'png' } +} + export function useExport() { const editor = useEditor() - const settings = ref([{ scale: 1, format: 'png' }]) + const selectionSettings = ref([createDefaultSetting()]) + const pageSettings = ref([createDefaultSetting()]) const selectedIds = useSceneComputed(() => [...editor.state.selectedIds]) const formatSupportsScale = (format: ExportFormatId) => io.getFormat(format)?.exportOptions?.scale ?? false - const nodeName = useSceneComputed(() => { + const selectedNodeName = useSceneComputed(() => { const ids = editor.state.selectedIds if (ids.size === 1) { const id = [...ids][0] return editor.graph.getNode(id)?.name ?? 'Export' } - return `${ids.size} layers` + if (ids.size > 1) return `${ids.size} layers` + return null }) - function addSetting() { - const last = settings.value[settings.value.length - 1] + const currentPageName = useSceneComputed(() => { + const page = editor.graph.getNode(editor.state.currentPageId) + return page?.name ?? 'Page' + }) + + const hasSelection = computed(() => selectedIds.value.length > 0) + const activeTarget = computed(() => + hasSelection.value ? 'selection' : 'page' + ) + const activeName = computed(() => + activeTarget.value === 'selection' ? (selectedNodeName.value ?? 'Export') : currentPageName.value + ) + const activeSettings = computed(() => + activeTarget.value === 'selection' ? selectionSettings.value : pageSettings.value + ) + + function addSelectionSetting() { + const last = selectionSettings.value[selectionSettings.value.length - 1] const nextScale = SCALES.find((s) => s > (last?.scale ?? 1)) ?? 2 - settings.value.push({ scale: nextScale, format: last?.format ?? 'png' }) + selectionSettings.value.push({ scale: nextScale, format: last?.format ?? 'png' }) } - function removeSetting(index: number) { - settings.value.splice(index, 1) + function addPageSetting() { + const last = pageSettings.value[pageSettings.value.length - 1] + const nextScale = SCALES.find((s) => s > (last?.scale ?? 1)) ?? 2 + pageSettings.value.push({ scale: nextScale, format: last?.format ?? 'png' }) } - function updateScale(index: number, scale: number) { - settings.value[index] = { ...settings.value[index], scale } + function removeSelectionSetting(index: number) { + selectionSettings.value.splice(index, 1) } - function updateFormat(index: number, format: ExportFormatId) { - settings.value[index] = { ...settings.value[index], format } + function removePageSetting(index: number) { + pageSettings.value.splice(index, 1) + } + + function updateSelectionScale(index: number, scale: number) { + selectionSettings.value[index] = { ...selectionSettings.value[index], scale } + } + + function updatePageScale(index: number, scale: number) { + pageSettings.value[index] = { ...pageSettings.value[index], scale } + } + + function updateSelectionFormat(index: number, format: ExportFormatId) { + selectionSettings.value[index] = { ...selectionSettings.value[index], format } + } + + function updatePageFormat(index: number, format: ExportFormatId) { + pageSettings.value[index] = { ...pageSettings.value[index], format } } return { editor, - settings, selectedIds, - nodeName, scales: SCALES, formats: FORMATS, - addSetting, - removeSetting, - updateScale, - updateFormat, - formatSupportsScale + formatSupportsScale, + hasSelection, + activeTarget, + activeName, + activeSettings, + selectedNodeName, + currentPageName, + selectionSettings, + pageSettings, + addSelectionSetting, + addPageSetting, + removeSelectionSetting, + removePageSetting, + updateSelectionScale, + updatePageScale, + updateSelectionFormat, + updatePageFormat } } diff --git a/src/components/DesignPanel.vue b/src/components/DesignPanel.vue index d2d26862f..7d7aa7575 100644 --- a/src/components/DesignPanel.vue +++ b/src/components/DesignPanel.vue @@ -105,6 +105,7 @@ const { panels } = useI18n() > + diff --git a/src/components/properties/ExportSection.vue b/src/components/properties/ExportSection.vue index f3d4f46fb..98ad1e885 100644 --- a/src/components/properties/ExportSection.vue +++ b/src/components/properties/ExportSection.vue @@ -12,12 +12,17 @@ import type { ExportFormatId } from '@open-pencil/vue/controls/useExport' const editorStore = useEditorStore() const { panels } = useI18n() const { - settings, - nodeName, - addSetting, - removeSetting, - updateScale, - updateFormat, + activeTarget, + activeName, + activeSettings, + addSelectionSetting, + addPageSetting, + removeSelectionSetting, + removePageSetting, + updateSelectionScale, + updatePageScale, + updateSelectionFormat, + updatePageFormat, formatSupportsScale } = useExport() @@ -36,10 +41,41 @@ const exporting = ref(false) const PREVIEW_WIDTH = 480 -async function doExport(exportSettings: Array<{ scale: number; format: ExportFormatId }>) { +function addSetting() { + if (activeTarget.value === 'selection') addSelectionSetting() + else addPageSetting() +} + +function removeSetting(index: number) { + if (activeTarget.value === 'selection') removeSelectionSetting(index) + else removePageSetting(index) +} + +function updateScale(index: number, scale: number) { + if (activeTarget.value === 'selection') updateSelectionScale(index, scale) + else updatePageScale(index, scale) +} + +function updateFormat(index: number, format: ExportFormatId) { + if (activeTarget.value === 'selection') updateSelectionFormat(index, format) + else updatePageFormat(index, format) +} + +async function doExport() { exporting.value = true try { - for (const s of exportSettings) await editorStore.exportSelection(s.scale, s.format) + if (activeTarget.value === 'selection') { + for (const s of activeSettings.value) await editorStore.exportSelection(s.scale, s.format) + return + } + + for (const s of activeSettings.value) { + await editorStore.exportTarget( + { scope: 'page', pageId: editorStore.state.currentPageId }, + s.format, + { scale: s.scale } + ) + } } finally { exporting.value = false } @@ -47,12 +83,18 @@ async function doExport(exportSettings: Array<{ scale: number; format: ExportFor async function updatePreview() { if (!showPreview.value) return - const ids = [...editorStore.state.selectedIds] + + const ids = + activeTarget.value === 'selection' + ? [...editorStore.state.selectedIds] + : editorStore.graph.getChildren(editorStore.state.currentPageId).map((n) => n.id) + if (ids.length === 0) { if (previewUrl.value) URL.revokeObjectURL(previewUrl.value) previewUrl.value = null return } + let maxW = 0 for (const id of ids) { const node = editorStore.getNode(id) @@ -68,7 +110,12 @@ async function updatePreview() { } const previewKey = computed( - () => `${editorStore.state.sceneVersion}:${[...editorStore.state.selectedIds].sort().join(',')}` + () => + `${activeTarget.value}:${editorStore.state.sceneVersion}:${editorStore.state.currentPageId}:${[ + ...editorStore.state.selectedIds + ] + .sort() + .join(',')}` ) watch(() => showPreview.value, updatePreview, { flush: 'post' }) @@ -87,8 +134,8 @@ onScopeDispose(() => {
{