chore(react/type_widget): export as SVG/PNG

This commit is contained in:
Elian Doran
2025-09-20 21:29:20 +03:00
parent 469683f30f
commit d95ed4a5d2
2 changed files with 31 additions and 36 deletions

View File

@@ -5,7 +5,9 @@ import { RawHtmlBlock } from "../../react/RawHtml";
import server from "../../../services/server"; import server from "../../../services/server";
import svgPanZoom, { zoomIn } from "svg-pan-zoom"; import svgPanZoom, { zoomIn } from "svg-pan-zoom";
import { RefObject } from "preact"; import { RefObject } from "preact";
import { useElementSize } from "../../react/hooks"; import { useElementSize, useTriliumEvent } from "../../react/hooks";
import utils from "../../../services/utils";
import toast from "../../../services/toast";
interface SvgSplitEditorProps extends Omit<SplitEditorProps, "previewContent"> { interface SvgSplitEditorProps extends Omit<SplitEditorProps, "previewContent"> {
/** /**
@@ -22,7 +24,18 @@ interface SvgSplitEditorProps extends Omit<SplitEditorProps, "previewContent"> {
renderSvg(content: string): string | Promise<string>; renderSvg(content: string): string | Promise<string>;
} }
export default function SvgSplitEditor({ note, attachmentName, renderSvg, ...props }: SvgSplitEditorProps) { /**
* A specialization of `SplitTypeWidget` meant for note types that have a SVG preview.
*
* This adds the following functionality:
*
* - Automatic handling of the preview when content or the note changes via {@link renderSvg}.
* - Built-in pan and zoom functionality with automatic re-centering.
* - Automatically displays errors to the user if {@link renderSvg} failed.
* - Automatically saves the SVG attachment.
*
*/
export default function SvgSplitEditor({ ntxId, note, attachmentName, renderSvg, ...props }: SvgSplitEditorProps) {
const [ svg, setSvg ] = useState<string>(); const [ svg, setSvg ] = useState<string>();
const [ error, setError ] = useState<string | null | undefined>(); const [ error, setError ] = useState<string | null | undefined>();
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
@@ -54,13 +67,28 @@ export default function SvgSplitEditor({ note, attachmentName, renderSvg, ...pro
server.post(`notes/${note.noteId}/attachments?matchBy=title`, payload); server.post(`notes/${note.noteId}/attachments?matchBy=title`, payload);
} }
// Import/export
useTriliumEvent("exportSvg", ({ ntxId: eventNtxId }) => {
if (eventNtxId !== ntxId || !svg) return;
utils.downloadSvg(note.title, svg);
});
useTriliumEvent("exportPng", async ({ ntxId: eventNtxId }) => {
if (eventNtxId !== ntxId || !svg) return;
try {
await utils.downloadSvgAsPng(note.title, svg);
} catch (e) {
console.warn(e);
toast.showError(t("svg.export_to_png"));
}
});
// Pan & zoom. // Pan & zoom.
const zoomRef = useResizer(containerRef, note.noteId, svg); const zoomRef = useResizer(containerRef, note.noteId, svg);
return ( return (
<SplitEditor <SplitEditor
className="svg-editor" className="svg-editor"
note={note} note={note} ntxId={ntxId}
error={error} error={error}
onContentChanged={onContentChanged} onContentChanged={onContentChanged}
dataSaved={onSave} dataSaved={onSave}

View File

@@ -7,17 +7,6 @@ import utils from "../../services/utils.js";
import OnClickButtonWidget from "../buttons/onclick_button.js"; import OnClickButtonWidget from "../buttons/onclick_button.js";
import AbstractSplitTypeWidget from "./abstract_split_type_widget.js"; import AbstractSplitTypeWidget from "./abstract_split_type_widget.js";
/**
* A specialization of `SplitTypeWidget` meant for note types that have a SVG preview.
*
* This adds the following functionality:
*
* - Automatic handling of the preview when content or the note changes via {@link renderSvg}.
* - Built-in pan and zoom functionality with automatic re-centering.
* - Automatically displays errors to the user if {@link renderSvg} failed.
* - Automatically saves the SVG attachment.
*
*/
export default abstract class AbstractSvgSplitTypeWidget extends AbstractSplitTypeWidget { export default abstract class AbstractSvgSplitTypeWidget extends AbstractSplitTypeWidget {
private $renderContainer!: JQuery<HTMLElement>; private $renderContainer!: JQuery<HTMLElement>;
@@ -47,27 +36,5 @@ export default abstract class AbstractSvgSplitTypeWidget extends AbstractSplitTy
} }
}); });
} }
async exportSvgEvent({ ntxId }: EventData<"exportSvg">) {
if (!this.isNoteContext(ntxId) || this.note?.type !== "mermaid" || !this.svg) {
return;
}
utils.downloadSvg(this.note.title, this.svg);
}
async exportPngEvent({ ntxId }: EventData<"exportPng">) {
console.log("Export to PNG", this.noteContext?.noteId, ntxId, this.svg);
if (!this.isNoteContext(ntxId) || this.note?.type !== "mermaid" || !this.svg) {
console.log("Return");
return;
}
try {
await utils.downloadSvgAsPng(this.note.title, this.svg);
} catch (e) {
console.warn(e);
toast.showError(t("svg.export_to_png"));
}
}
} }