From 25b8a77ae8ca01db2ff2751c375adf55eeb26939 Mon Sep 17 00:00:00 2001 From: QuadnucYard Date: Sat, 15 Nov 2025 21:24:20 +0800 Subject: [PATCH] dev: apply review suggestions and make auto-preview toggleable --- biome.json | 2 +- .../src/features/tool/message-handler.ts | 10 ++--- .../exporter/components/action-buttons.ts | 28 ------------ .../features/exporter/components/header.ts | 16 +------ .../exporter/components/options-panel.ts | 4 +- .../exporter/components/preview-grid.ts | 45 ++++++++++++++----- .../src/features/exporter/exporter.ts | 9 ++++ .../src/features/exporter/formats.ts | 4 +- .../src/features/exporter/index.ts | 6 +++ 9 files changed, 61 insertions(+), 63 deletions(-) diff --git a/biome.json b/biome.json index 5c4239ac..5c23d798 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.3.1/schema.json", + "$schema": "https://biomejs.dev/schemas/2.3.5/schema.json", "vcs": { "enabled": false, "clientKind": "git", diff --git a/editors/vscode/src/features/tool/message-handler.ts b/editors/vscode/src/features/tool/message-handler.ts index 6389cf6a..031d0364 100644 --- a/editors/vscode/src/features/tool/message-handler.ts +++ b/editors/vscode/src/features/tool/message-handler.ts @@ -282,7 +282,8 @@ export const messageHandlers: Record = { } } } catch (error) { - await vscode.window.showErrorMessage(`Export failed: ${error}`); + const message = error instanceof Error ? error.message : String(error); + await vscode.window.showErrorMessage(`Export failed: ${message}`); } }, @@ -298,17 +299,14 @@ export const messageHandlers: Record = { }); }; - console.log(`Generating preview for format=${format}, extraArgs=`, extraArgs); try { const ops = exportOps(extraArgs); const formatProvider = provideFormats(extraArgs); - // await vscode.window.showInformationMessage(`Active `) - // Get the active document const uri = ops.resolveInputPath(); if (!uri) { - // sendError("No active document found"); + // Just ignore if no active document return; } @@ -332,7 +330,6 @@ export const messageHandlers: Record = { return; } - console.log(`Generating preview for format=${format}, extraArgs=${extraArgs}, uri=${uri}`); // For visual formats, generate PNG/SVG previews if (format === "pdf" || format === "png" || format === "svg") { // Extract base64 data from the response @@ -362,7 +359,6 @@ export const messageHandlers: Record = { } } catch (error) { const message = error instanceof Error ? error.message : String(error); - console.log("Preview generation failed:", error); sendError(`Preview generation failed: ${message}`); } }, diff --git a/tools/editor-tools/src/features/exporter/components/action-buttons.ts b/tools/editor-tools/src/features/exporter/components/action-buttons.ts index 13ed61d5..23a8faec 100644 --- a/tools/editor-tools/src/features/exporter/components/action-buttons.ts +++ b/tools/editor-tools/src/features/exporter/components/action-buttons.ts @@ -12,34 +12,6 @@ export const ActionButtons = ({ onExport }: ActionButtonsProps) => { h3("Export Actions"), - // Task Label Input - /* div( - { class: "card flex flex-col gap-xs" }, - label( - { - class: "text-sm font-medium", - for: "task-label", - style: "margin-bottom: 0.5rem; display: block;", - }, - "Custom Task Label (Optional)", - ), - input({ - class: "input", - type: "text", - id: "task-label", - placeholder: `Export to ${exportConfig.val.format.label}`, - value: customTaskLabel, - oninput: (e: Event) => { - const target = e.target as HTMLInputElement; - customTaskLabel.val = target.value; - }, - }), - div( - { class: "text-xs text-desc" }, - "This will be used as the task name in tasks.json. Leave empty for default naming.", - ), - ), */ - // Action Buttons div( { class: "action-buttons flex items-center gap-md" }, diff --git a/tools/editor-tools/src/features/exporter/components/header.ts b/tools/editor-tools/src/features/exporter/components/header.ts index 9858d4cf..90e4203c 100644 --- a/tools/editor-tools/src/features/exporter/components/header.ts +++ b/tools/editor-tools/src/features/exporter/components/header.ts @@ -23,20 +23,8 @@ export const Header = { class: "flex flex-col sm:flex-row sm:justify-between sm:items-center gap-sm" }, div( { class: "flex flex-col gap-xs" }, - h1( - { - class: "text-xl font-semibold text-base-content", - style: "margin: 0; font-size: 1.25rem; font-weight: 600;", - }, - title, - ), - p( - { - class: "text-desc", - style: "margin: 0; font-size: 0.875rem;", - }, - description, - ), + h1({ class: "text-xl font-semibold text-base-content" }, title), + p({ class: "text-desc font-sm" }, description), ), actions ? div({ class: "flex gap-xs" }, actions) : null, ), diff --git a/tools/editor-tools/src/features/exporter/components/options-panel.ts b/tools/editor-tools/src/features/exporter/components/options-panel.ts index d784eaae..6566dfd5 100644 --- a/tools/editor-tools/src/features/exporter/components/options-panel.ts +++ b/tools/editor-tools/src/features/exporter/components/options-panel.ts @@ -67,7 +67,7 @@ const OptionField = (schema: OptionSchema, valueState: State) => { const renderInput = ( schema: OptionSchema, - valueState: State, + valueState: State, validationError: State, ) => { const { type, key, options: selectOptions, min, max } = schema; @@ -102,7 +102,7 @@ const renderInput = ( const target = e.target as HTMLInputElement; // Call custom validation function if provided validationError.val = schema.validate?.(target.value); - valueState.val = parseFloat(target.value); + valueState.val = target.value === "" ? undefined : parseFloat(target.value); }, }); diff --git a/tools/editor-tools/src/features/exporter/components/preview-grid.ts b/tools/editor-tools/src/features/exporter/components/preview-grid.ts index 7ba873d0..a812da7a 100644 --- a/tools/editor-tools/src/features/exporter/components/preview-grid.ts +++ b/tools/editor-tools/src/features/exporter/components/preview-grid.ts @@ -1,17 +1,18 @@ import van, { type State } from "vanjs-core"; import type { ExportFormat, PreviewData, PreviewPage } from "../types"; -const { div, h3, img, span, button } = van.tags; +const { div, h3, img, span, button, label, input } = van.tags; interface PreviewGridProps { format: State; previewData: State; previewGenerating: State; + autoPreview: State; onPreview: () => void; } export const PreviewGrid = (props: PreviewGridProps) => { - const { format, previewData, previewGenerating, onPreview } = props; + const { format, previewData, previewGenerating, autoPreview, onPreview } = props; const thumbnailZoom = van.state(100); // Percentage for thumbnail sizing @@ -19,14 +20,13 @@ export const PreviewGrid = (props: PreviewGridProps) => { // Preview Header div( { class: "flex justify-between items-center mb-md" }, - h3({ class: "text-lg font-semibold" }, () => `Preview (${format.val.label})`), - () => - div( - { class: "flex items-center gap-sm", style: "min-height: 2rem;" }, - // Only show zoom controls for image content (thumbnails) - !previewGenerating.val && previewData.val.pages && previewData.val.pages.length > 0 - ? ZoomControls(thumbnailZoom) - : null, + div( + { class: "flex items-center gap-sm" }, + + h3({ class: "text-lg font-semibold" }, () => `Preview (${format.val.label})`), + + // Generate Preview Button + () => button( { class: "btn btn-secondary", @@ -35,6 +35,31 @@ export const PreviewGrid = (props: PreviewGridProps) => { }, previewGenerating.val ? "Generating..." : "Generate Preview", ), + + // Auto-preview toggle + label( + { class: "flex items-center gap-xs cursor-pointer", style: "user-select: none;" }, + input({ + type: "checkbox", + class: "toggle", + checked: autoPreview, + onchange: (e: Event) => { + const target = e.target as HTMLInputElement; + autoPreview.val = target.checked; + }, + }), + span({ class: "text-sm" }, "Auto"), + ), + ), + + () => + div( + { class: "flex items-center gap-sm", style: "min-height: 2rem;" }, + + // Only show zoom controls for image content (thumbnails) + !previewGenerating.val && previewData.val.pages && previewData.val.pages.length > 0 + ? ZoomControls(thumbnailZoom) + : null, ), ), diff --git a/tools/editor-tools/src/features/exporter/exporter.ts b/tools/editor-tools/src/features/exporter/exporter.ts index ce46eebe..d409fa25 100644 --- a/tools/editor-tools/src/features/exporter/exporter.ts +++ b/tools/editor-tools/src/features/exporter/exporter.ts @@ -14,6 +14,7 @@ export function useExporter() { let previewVersion = 0; const previewGenerating = van.state(false); const previewData = van.state({}); + const autoPreview = van.state(true); const buildOptions = () => { const extraOpts = Object.fromEntries( @@ -52,6 +53,8 @@ export function useExporter() { // Regenerate preview automatically when format or options change van.derive(() => { + if (!autoPreview.val) return; + if (format.oldVal !== format.val) { // Clear previous preview data when format changes previewData.val = {}; @@ -71,6 +74,10 @@ export function useExporter() { }; window?.addEventListener("message", handleMessage); + const cleanup = () => { + window?.removeEventListener("message", handleMessage); + }; + return { inputPath, outputPath, @@ -78,8 +85,10 @@ export function useExporter() { optionStates, previewGenerating, previewData, + autoPreview, exportDocument, generatePreview, + cleanup, }; } diff --git a/tools/editor-tools/src/features/exporter/formats.ts b/tools/editor-tools/src/features/exporter/formats.ts index e35fcca6..54747127 100644 --- a/tools/editor-tools/src/features/exporter/formats.ts +++ b/tools/editor-tools/src/features/exporter/formats.ts @@ -45,9 +45,11 @@ export const EXPORT_FORMATS: ExportFormat[] = [ if (value.trim() === "") { return; // Allow empty input } + if (!/^\d+$/.test(value)) { + return "Creation timestamp must be a valid non-negative integer UNIX timestamp"; + } const num = Number(value); if (Number.isNaN(num) || !Number.isInteger(num) || num < 0) { - // fixme: it still accepts floating point numbers like "1e5" return "Creation timestamp must be a valid non-negative integer UNIX timestamp"; } }, diff --git a/tools/editor-tools/src/features/exporter/index.ts b/tools/editor-tools/src/features/exporter/index.ts index ed2c1d84..32a55d6c 100644 --- a/tools/editor-tools/src/features/exporter/index.ts +++ b/tools/editor-tools/src/features/exporter/index.ts @@ -23,10 +23,15 @@ const ExportTool = () => { optionStates, previewGenerating, previewData, + autoPreview, exportDocument, generatePreview, } = useExporter(); + // Note: cleanup() should be called when the component is unmounted + // In the current single-page architecture, this might not be needed, + // but it's available for future use if the tool becomes part of a larger app + return div( { class: "export-tool-container flex flex-col gap-lg text-base-content" }, @@ -49,6 +54,7 @@ const ExportTool = () => { format: format, previewData, previewGenerating, + autoPreview, onPreview: generatePreview, }),