mirror of
https://github.com/zadam/trilium.git
synced 2025-12-23 16:49:58 +01:00
chore(react/type_widget): restore pan/zoom
This commit is contained in:
@@ -84,5 +84,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* #region SVG */
|
/* #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 */
|
/* #endregion */
|
||||||
@@ -10,6 +10,7 @@ import { ComponentChildren } from "preact";
|
|||||||
import ActionButton, { ActionButtonProps } from "../../react/ActionButton";
|
import ActionButton, { ActionButtonProps } from "../../react/ActionButton";
|
||||||
|
|
||||||
export interface SplitEditorProps extends EditableCodeProps {
|
export interface SplitEditorProps extends EditableCodeProps {
|
||||||
|
className?: string;
|
||||||
error?: string | null;
|
error?: string | null;
|
||||||
splitOptions?: Split.Options;
|
splitOptions?: Split.Options;
|
||||||
previewContent: ComponentChildren;
|
previewContent: ComponentChildren;
|
||||||
@@ -25,7 +26,7 @@ export interface SplitEditorProps extends EditableCodeProps {
|
|||||||
* - Can display errors to the user via {@link setError}.
|
* - 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.
|
* - 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 splitEditorOrientation = useSplitOrientation();
|
||||||
const [ readOnly ] = useNoteLabelBoolean(note, "readOnly");
|
const [ readOnly ] = useNoteLabelBoolean(note, "readOnly");
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
@@ -72,7 +73,7 @@ export default function SplitEditor({ note, error, splitOptions, previewContent,
|
|||||||
}, [ readOnly, splitEditorOrientation ]);
|
}, [ readOnly, splitEditorOrientation ]);
|
||||||
|
|
||||||
return (
|
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"
|
{splitEditorOrientation === "horizontal"
|
||||||
? <>{editor}{preview}</>
|
? <>{editor}{preview}</>
|
||||||
: <>{preview}{editor}</>}
|
: <>{preview}{editor}</>}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { useState } from "preact/hooks";
|
import { useEffect, useRef, useState } from "preact/hooks";
|
||||||
import { t } from "../../../services/i18n";
|
import { t } from "../../../services/i18n";
|
||||||
import SplitEditor, { PreviewButton, SplitEditorProps } from "./SplitEditor";
|
import SplitEditor, { PreviewButton, SplitEditorProps } from "./SplitEditor";
|
||||||
import { RawHtmlBlock } from "../../react/RawHtml";
|
import { RawHtmlBlock } from "../../react/RawHtml";
|
||||||
import server from "../../../services/server";
|
import server from "../../../services/server";
|
||||||
|
import svgPanZoom from "svg-pan-zoom";
|
||||||
|
|
||||||
interface SvgSplitEditorProps extends Omit<SplitEditorProps, "previewContent"> {
|
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) {
|
export default function SvgSplitEditor({ 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);
|
||||||
|
|
||||||
// Render the SVG.
|
// Render the SVG.
|
||||||
async function onContentChanged(content: string) {
|
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);
|
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 (
|
return (
|
||||||
<SplitEditor
|
<SplitEditor
|
||||||
|
className="svg-editor"
|
||||||
note={note}
|
note={note}
|
||||||
error={error}
|
error={error}
|
||||||
onContentChanged={onContentChanged}
|
onContentChanged={onContentChanged}
|
||||||
dataSaved={onSave}
|
dataSaved={onSave}
|
||||||
previewContent={(
|
previewContent={(
|
||||||
<RawHtmlBlock className="render-container" html={svg} />
|
<RawHtmlBlock
|
||||||
|
className="render-container"
|
||||||
|
containerRef={containerRef}
|
||||||
|
html={svg}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
previewButtons={
|
previewButtons={
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -65,11 +65,6 @@ export default abstract class AbstractSplitTypeWidget extends TypeWidget {
|
|||||||
super.doRender();
|
super.doRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup(): void {
|
|
||||||
this.#destroyResizer();
|
|
||||||
this.editorTypeWidget.cleanup();
|
|
||||||
}
|
|
||||||
|
|
||||||
async doRefresh(note: FNote) {
|
async doRefresh(note: FNote) {
|
||||||
this.#adjustLayoutOrientation();
|
this.#adjustLayoutOrientation();
|
||||||
|
|
||||||
|
|||||||
@@ -106,51 +106,6 @@ export default abstract class AbstractSvgSplitTypeWidget extends AbstractSplitTy
|
|||||||
|
|
||||||
abstract get attachmentName(): string;
|
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 {
|
buildSplitExtraOptions(): Split.Options {
|
||||||
return {
|
return {
|
||||||
onDrag: () => this.zoomHandler?.()
|
onDrag: () => this.zoomHandler?.()
|
||||||
|
|||||||
Reference in New Issue
Block a user