mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-11-22 20:35:20 +00:00
dev: apply review suggestions and make auto-preview toggleable
This commit is contained in:
parent
fcaf7a7e9a
commit
25b8a77ae8
9 changed files with 61 additions and 63 deletions
|
|
@ -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": {
|
"vcs": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"clientKind": "git",
|
"clientKind": "git",
|
||||||
|
|
|
||||||
|
|
@ -282,7 +282,8 @@ export const messageHandlers: Record<string, MessageHandler> = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} 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<string, MessageHandler> = {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log(`Generating preview for format=${format}, extraArgs=`, extraArgs);
|
|
||||||
try {
|
try {
|
||||||
const ops = exportOps(extraArgs);
|
const ops = exportOps(extraArgs);
|
||||||
const formatProvider = provideFormats(extraArgs);
|
const formatProvider = provideFormats(extraArgs);
|
||||||
|
|
||||||
// await vscode.window.showInformationMessage(`Active `)
|
|
||||||
|
|
||||||
// Get the active document
|
// Get the active document
|
||||||
const uri = ops.resolveInputPath();
|
const uri = ops.resolveInputPath();
|
||||||
if (!uri) {
|
if (!uri) {
|
||||||
// sendError("No active document found");
|
// Just ignore if no active document
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -332,7 +330,6 @@ export const messageHandlers: Record<string, MessageHandler> = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Generating preview for format=${format}, extraArgs=${extraArgs}, uri=${uri}`);
|
|
||||||
// For visual formats, generate PNG/SVG previews
|
// For visual formats, generate PNG/SVG previews
|
||||||
if (format === "pdf" || format === "png" || format === "svg") {
|
if (format === "pdf" || format === "png" || format === "svg") {
|
||||||
// Extract base64 data from the response
|
// Extract base64 data from the response
|
||||||
|
|
@ -362,7 +359,6 @@ export const messageHandlers: Record<string, MessageHandler> = {
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = error instanceof Error ? error.message : String(error);
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
console.log("Preview generation failed:", error);
|
|
||||||
sendError(`Preview generation failed: ${message}`);
|
sendError(`Preview generation failed: ${message}`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -12,34 +12,6 @@ export const ActionButtons = ({ onExport }: ActionButtonsProps) => {
|
||||||
|
|
||||||
h3("Export Actions"),
|
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
|
// Action Buttons
|
||||||
div(
|
div(
|
||||||
{ class: "action-buttons flex items-center gap-md" },
|
{ class: "action-buttons flex items-center gap-md" },
|
||||||
|
|
|
||||||
|
|
@ -23,20 +23,8 @@ export const Header =
|
||||||
{ class: "flex flex-col sm:flex-row sm:justify-between sm:items-center gap-sm" },
|
{ class: "flex flex-col sm:flex-row sm:justify-between sm:items-center gap-sm" },
|
||||||
div(
|
div(
|
||||||
{ class: "flex flex-col gap-xs" },
|
{ class: "flex flex-col gap-xs" },
|
||||||
h1(
|
h1({ class: "text-xl font-semibold text-base-content" }, title),
|
||||||
{
|
p({ class: "text-desc font-sm" }, description),
|
||||||
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,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
actions ? div({ class: "flex gap-xs" }, actions) : null,
|
actions ? div({ class: "flex gap-xs" }, actions) : null,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ const OptionField = (schema: OptionSchema, valueState: State<Scalar>) => {
|
||||||
|
|
||||||
const renderInput = (
|
const renderInput = (
|
||||||
schema: OptionSchema,
|
schema: OptionSchema,
|
||||||
valueState: State<Scalar>,
|
valueState: State<Scalar | undefined>,
|
||||||
validationError: State<string | undefined>,
|
validationError: State<string | undefined>,
|
||||||
) => {
|
) => {
|
||||||
const { type, key, options: selectOptions, min, max } = schema;
|
const { type, key, options: selectOptions, min, max } = schema;
|
||||||
|
|
@ -102,7 +102,7 @@ const renderInput = (
|
||||||
const target = e.target as HTMLInputElement;
|
const target = e.target as HTMLInputElement;
|
||||||
// Call custom validation function if provided
|
// Call custom validation function if provided
|
||||||
validationError.val = schema.validate?.(target.value);
|
validationError.val = schema.validate?.(target.value);
|
||||||
valueState.val = parseFloat(target.value);
|
valueState.val = target.value === "" ? undefined : parseFloat(target.value);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,18 @@
|
||||||
import van, { type State } from "vanjs-core";
|
import van, { type State } from "vanjs-core";
|
||||||
import type { ExportFormat, PreviewData, PreviewPage } from "../types";
|
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 {
|
interface PreviewGridProps {
|
||||||
format: State<ExportFormat>;
|
format: State<ExportFormat>;
|
||||||
previewData: State<PreviewData>;
|
previewData: State<PreviewData>;
|
||||||
previewGenerating: State<boolean>;
|
previewGenerating: State<boolean>;
|
||||||
|
autoPreview: State<boolean>;
|
||||||
onPreview: () => void;
|
onPreview: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PreviewGrid = (props: PreviewGridProps) => {
|
export const PreviewGrid = (props: PreviewGridProps) => {
|
||||||
const { format, previewData, previewGenerating, onPreview } = props;
|
const { format, previewData, previewGenerating, autoPreview, onPreview } = props;
|
||||||
|
|
||||||
const thumbnailZoom = van.state<number>(100); // Percentage for thumbnail sizing
|
const thumbnailZoom = van.state<number>(100); // Percentage for thumbnail sizing
|
||||||
|
|
||||||
|
|
@ -19,14 +20,13 @@ export const PreviewGrid = (props: PreviewGridProps) => {
|
||||||
// Preview Header
|
// Preview Header
|
||||||
div(
|
div(
|
||||||
{ class: "flex justify-between items-center mb-md" },
|
{ 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" },
|
||||||
div(
|
|
||||||
{ class: "flex items-center gap-sm", style: "min-height: 2rem;" },
|
h3({ class: "text-lg font-semibold" }, () => `Preview (${format.val.label})`),
|
||||||
// Only show zoom controls for image content (thumbnails)
|
|
||||||
!previewGenerating.val && previewData.val.pages && previewData.val.pages.length > 0
|
// Generate Preview Button
|
||||||
? ZoomControls(thumbnailZoom)
|
() =>
|
||||||
: null,
|
|
||||||
button(
|
button(
|
||||||
{
|
{
|
||||||
class: "btn btn-secondary",
|
class: "btn btn-secondary",
|
||||||
|
|
@ -35,6 +35,31 @@ export const PreviewGrid = (props: PreviewGridProps) => {
|
||||||
},
|
},
|
||||||
previewGenerating.val ? "Generating..." : "Generate Preview",
|
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,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ export function useExporter() {
|
||||||
let previewVersion = 0;
|
let previewVersion = 0;
|
||||||
const previewGenerating = van.state(false);
|
const previewGenerating = van.state(false);
|
||||||
const previewData = van.state<PreviewData>({});
|
const previewData = van.state<PreviewData>({});
|
||||||
|
const autoPreview = van.state(true);
|
||||||
|
|
||||||
const buildOptions = () => {
|
const buildOptions = () => {
|
||||||
const extraOpts = Object.fromEntries(
|
const extraOpts = Object.fromEntries(
|
||||||
|
|
@ -52,6 +53,8 @@ export function useExporter() {
|
||||||
|
|
||||||
// Regenerate preview automatically when format or options change
|
// Regenerate preview automatically when format or options change
|
||||||
van.derive(() => {
|
van.derive(() => {
|
||||||
|
if (!autoPreview.val) return;
|
||||||
|
|
||||||
if (format.oldVal !== format.val) {
|
if (format.oldVal !== format.val) {
|
||||||
// Clear previous preview data when format changes
|
// Clear previous preview data when format changes
|
||||||
previewData.val = {};
|
previewData.val = {};
|
||||||
|
|
@ -71,6 +74,10 @@ export function useExporter() {
|
||||||
};
|
};
|
||||||
window?.addEventListener("message", handleMessage);
|
window?.addEventListener("message", handleMessage);
|
||||||
|
|
||||||
|
const cleanup = () => {
|
||||||
|
window?.removeEventListener("message", handleMessage);
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
inputPath,
|
inputPath,
|
||||||
outputPath,
|
outputPath,
|
||||||
|
|
@ -78,8 +85,10 @@ export function useExporter() {
|
||||||
optionStates,
|
optionStates,
|
||||||
previewGenerating,
|
previewGenerating,
|
||||||
previewData,
|
previewData,
|
||||||
|
autoPreview,
|
||||||
exportDocument,
|
exportDocument,
|
||||||
generatePreview,
|
generatePreview,
|
||||||
|
cleanup,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,9 +45,11 @@ export const EXPORT_FORMATS: ExportFormat[] = [
|
||||||
if (value.trim() === "") {
|
if (value.trim() === "") {
|
||||||
return; // Allow empty input
|
return; // Allow empty input
|
||||||
}
|
}
|
||||||
|
if (!/^\d+$/.test(value)) {
|
||||||
|
return "Creation timestamp must be a valid non-negative integer UNIX timestamp";
|
||||||
|
}
|
||||||
const num = Number(value);
|
const num = Number(value);
|
||||||
if (Number.isNaN(num) || !Number.isInteger(num) || num < 0) {
|
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";
|
return "Creation timestamp must be a valid non-negative integer UNIX timestamp";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,15 @@ const ExportTool = () => {
|
||||||
optionStates,
|
optionStates,
|
||||||
previewGenerating,
|
previewGenerating,
|
||||||
previewData,
|
previewData,
|
||||||
|
autoPreview,
|
||||||
exportDocument,
|
exportDocument,
|
||||||
generatePreview,
|
generatePreview,
|
||||||
} = useExporter();
|
} = 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(
|
return div(
|
||||||
{ class: "export-tool-container flex flex-col gap-lg text-base-content" },
|
{ class: "export-tool-container flex flex-col gap-lg text-base-content" },
|
||||||
|
|
||||||
|
|
@ -49,6 +54,7 @@ const ExportTool = () => {
|
||||||
format: format,
|
format: format,
|
||||||
previewData,
|
previewData,
|
||||||
previewGenerating,
|
previewGenerating,
|
||||||
|
autoPreview,
|
||||||
onPreview: generatePreview,
|
onPreview: generatePreview,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue