chore(react/type_widget): restore pan/zoom

This commit is contained in:
Elian Doran 2025-09-20 21:07:51 +03:00
parent 8e9f5fb486
commit 145ff1a2a5
No known key found for this signature in database
5 changed files with 48 additions and 55 deletions

View File

@ -84,5 +84,10 @@
}
/* #region SVG */
.note-detail-split.svg-editor .render-container,
.note-detail-split.svg-editor .render-container svg {
width: 100%;
height: 100%;
max-width: 100%;
}
/* #endregion */

View File

@ -10,6 +10,7 @@ import { ComponentChildren } from "preact";
import ActionButton, { ActionButtonProps } from "../../react/ActionButton";
export interface SplitEditorProps extends EditableCodeProps {
className?: string;
error?: string | null;
splitOptions?: Split.Options;
previewContent: ComponentChildren;
@ -25,7 +26,7 @@ export interface SplitEditorProps extends EditableCodeProps {
* - Can display errors to the user via {@link setError}.
* - Horizontal or vertical orientation for the editor/preview split, adjustable via the switch split orientation button floating button.
*/
export default function SplitEditor({ note, error, splitOptions, previewContent, previewButtons, ...editorProps }: SplitEditorProps) {
export default function SplitEditor({ note, error, splitOptions, previewContent, previewButtons, className, ...editorProps }: SplitEditorProps) {
const splitEditorOrientation = useSplitOrientation();
const [ readOnly ] = useNoteLabelBoolean(note, "readOnly");
const containerRef = useRef<HTMLDivElement>(null);
@ -72,7 +73,7 @@ export default function SplitEditor({ note, error, splitOptions, previewContent,
}, [ readOnly, splitEditorOrientation ]);
return (
<div ref={containerRef} className={`note-detail-split note-detail-printable ${"split-" + splitEditorOrientation} ${readOnly ? "split-read-only" : ""}`}>
<div ref={containerRef} className={`note-detail-split note-detail-printable ${"split-" + splitEditorOrientation} ${readOnly ? "split-read-only" : ""} ${className ?? ""}`}>
{splitEditorOrientation === "horizontal"
? <>{editor}{preview}</>
: <>{preview}{editor}</>}

View File

@ -1,8 +1,9 @@
import { useState } from "preact/hooks";
import { useEffect, useRef, useState } from "preact/hooks";
import { t } from "../../../services/i18n";
import SplitEditor, { PreviewButton, SplitEditorProps } from "./SplitEditor";
import { RawHtmlBlock } from "../../react/RawHtml";
import server from "../../../services/server";
import svgPanZoom from "svg-pan-zoom";
interface SvgSplitEditorProps extends Omit<SplitEditorProps, "previewContent"> {
/**
@ -22,6 +23,7 @@ interface SvgSplitEditorProps extends Omit<SplitEditorProps, "previewContent"> {
export default function SvgSplitEditor({ note, attachmentName, renderSvg, ...props }: SvgSplitEditorProps) {
const [ svg, setSvg ] = useState<string>();
const [ error, setError ] = useState<string | null | undefined>();
const containerRef = useRef<HTMLDivElement>(null);
// Render the SVG.
async function onContentChanged(content: string) {
@ -50,14 +52,49 @@ export default function SvgSplitEditor({ note, attachmentName, renderSvg, ...pro
server.post(`notes/${note.noteId}/attachments?matchBy=title`, payload);
}
// Pan & zoom.
const lastPanZoom = useRef<{ pan: SvgPanZoom.Point, zoom: number }>();
const lastNoteId = useRef<string>();
useEffect(() => {
const shouldPreservePanZoom = (lastNoteId.current === note.noteId);
const svgEl = containerRef.current?.querySelector("svg");
if (!svgEl) return;
const zoomInstance = svgPanZoom(svgEl, {
zoomEnabled: true,
controlIconsEnabled: false
});
// Restore the previous pan/zoom if the user updates same note.
if (shouldPreservePanZoom && lastPanZoom.current) {
zoomInstance.zoom(lastPanZoom.current.zoom);
zoomInstance.pan(lastPanZoom.current.pan);
} else {
zoomInstance.resize().center().fit();
}
lastNoteId.current = note.noteId;
return () => {
lastPanZoom.current = {
pan: zoomInstance.getPan(),
zoom: zoomInstance.getZoom()
}
zoomInstance.destroy();
};
}, [ svg ]);
return (
<SplitEditor
className="svg-editor"
note={note}
error={error}
onContentChanged={onContentChanged}
dataSaved={onSave}
previewContent={(
<RawHtmlBlock className="render-container" html={svg} />
<RawHtmlBlock
className="render-container"
containerRef={containerRef}
html={svg}
/>
)}
previewButtons={
<>

View File

@ -65,11 +65,6 @@ export default abstract class AbstractSplitTypeWidget extends TypeWidget {
super.doRender();
}
cleanup(): void {
this.#destroyResizer();
this.editorTypeWidget.cleanup();
}
async doRefresh(note: FNote) {
this.#adjustLayoutOrientation();

View File

@ -106,51 +106,6 @@ export default abstract class AbstractSvgSplitTypeWidget extends AbstractSplitTy
abstract get attachmentName(): string;
/**
* @param preservePanZoom `true` to keep the pan/zoom settings of the previous image, or `false` to re-center it.
*/
async #setupPanZoom(preservePanZoom: boolean) {
// Clean up
let pan: SvgPanZoom.Point | null = null;
let zoom: number | null = null;
if (preservePanZoom && this.zoomInstance) {
// Store pan and zoom for same note, when the user is editing the note.
pan = this.zoomInstance.getPan();
zoom = this.zoomInstance.getZoom();
this.#cleanUpZoom();
}
const $svgEl = this.$renderContainer.find("svg");
// Fit the image to bounds
$svgEl.attr("width", "100%")
.attr("height", "100%")
.css("max-width", "100%");
if (!$svgEl.length) {
return;
}
const svgPanZoom = (await import("svg-pan-zoom")).default;
const zoomInstance = svgPanZoom($svgEl[0], {
zoomEnabled: true,
controlIconsEnabled: false
});
if (preservePanZoom && pan && zoom) {
// Restore the pan and zoom.
zoomInstance.zoom(zoom);
zoomInstance.pan(pan);
} else {
// New instance, reposition properly.
zoomInstance.resize();
zoomInstance.center();
zoomInstance.fit();
}
this.zoomInstance = zoomInstance;
}
buildSplitExtraOptions(): Split.Options {
return {
onDrag: () => this.zoomHandler?.()