mirror of
https://github.com/zadam/trilium.git
synced 2025-12-29 02:34:25 +01:00
chore(type_widgets): introduce panzoom
This commit is contained in:
parent
9d4127ba6d
commit
d8b9d14712
@ -1,13 +1,14 @@
|
||||
import { useEffect, useRef, useState } from "preact/hooks";
|
||||
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
|
||||
import { TypeWidgetProps } from "./type_widget";
|
||||
import { Defaults, jsPlumb, OverlaySpec } from "jsplumb";
|
||||
import { useNoteBlob } from "../react/hooks";
|
||||
import { Defaults, jsPlumb, jsPlumbInstance, OverlaySpec } from "jsplumb";
|
||||
import { useEditorSpacedUpdate, useNoteBlob } from "../react/hooks";
|
||||
import FNote from "../../entities/fnote";
|
||||
import { ComponentChildren } from "preact";
|
||||
import { ComponentChildren, RefObject } from "preact";
|
||||
import froca from "../../services/froca";
|
||||
import NoteLink from "../react/NoteLink";
|
||||
import "./RelationMap.css";
|
||||
import { t } from "../../services/i18n";
|
||||
import panzoom, { PanZoomOptions } from "panzoom";
|
||||
|
||||
interface MapData {
|
||||
notes: {
|
||||
@ -37,11 +38,38 @@ const uniDirectionalOverlays: OverlaySpec[] = [
|
||||
|
||||
export default function RelationMap({ note }: TypeWidgetProps) {
|
||||
const data = useData(note);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const apiRef = useRef<jsPlumbInstance>(null);
|
||||
|
||||
const onTransform = useCallback(() => {
|
||||
if (!containerRef.current || !apiRef.current) return;
|
||||
const zoom = getZoom(containerRef.current);
|
||||
apiRef.current.setZoom(zoom);
|
||||
}, [ ]);
|
||||
|
||||
usePanZoom({
|
||||
containerRef,
|
||||
options: {
|
||||
maxZoom: 2,
|
||||
minZoom: 0.3,
|
||||
smoothScroll: false,
|
||||
//@ts-expect-error Upstream incorrectly mentions no arguments.
|
||||
filterKey: function (e: KeyboardEvent) {
|
||||
// if ALT is pressed, then panzoom should bubble the event up
|
||||
// this is to preserve ALT-LEFT, ALT-RIGHT navigation working
|
||||
return e.altKey;
|
||||
}
|
||||
},
|
||||
transformData: data.transform,
|
||||
onTransform
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="note-detail-relation-map note-detail-printable">
|
||||
<div className="relation-map-wrapper">
|
||||
<JsPlumb
|
||||
apiRef={apiRef}
|
||||
containerRef={containerRef}
|
||||
className="relation-map-container"
|
||||
props={{
|
||||
Endpoint: ["Dot", { radius: 2 }],
|
||||
@ -89,23 +117,60 @@ function useData(note: FNote) {
|
||||
return content;
|
||||
}
|
||||
|
||||
function JsPlumb({ className, props, children }: {
|
||||
function usePanZoom({ containerRef, options, transformData, onTransform }: {
|
||||
containerRef: RefObject<HTMLElement>;
|
||||
options: PanZoomOptions;
|
||||
transformData: MapData["transform"] | undefined;
|
||||
onTransform: () => void
|
||||
}) {
|
||||
const apiRef = useRef<PanZoom>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!containerRef.current) return;
|
||||
const pzInstance = panzoom(containerRef.current, options);
|
||||
apiRef.current = pzInstance;
|
||||
|
||||
if (transformData) {
|
||||
pzInstance.zoomTo(0, 0, transformData.scale);
|
||||
pzInstance.moveTo(transformData.x, transformData.y);
|
||||
} else {
|
||||
// set to initial coordinates
|
||||
pzInstance.moveTo(0, 0);
|
||||
}
|
||||
|
||||
if (onTransform) {
|
||||
apiRef.current!.on("transform", onTransform);
|
||||
}
|
||||
|
||||
return () => pzInstance.dispose();
|
||||
}, [ containerRef, onTransform ]);
|
||||
}
|
||||
|
||||
function JsPlumb({ className, props, children, containerRef: externalContainerRef, apiRef }: {
|
||||
className?: string;
|
||||
props: Omit<Defaults, "container">;
|
||||
children: ComponentChildren;
|
||||
containerRef?: RefObject<HTMLElement>;
|
||||
apiRef?: RefObject<jsPlumbInstance>;
|
||||
}) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!containerRef.current) return;
|
||||
if (externalContainerRef) {
|
||||
externalContainerRef.current = containerRef.current;
|
||||
}
|
||||
|
||||
const jsPlumbInstance = jsPlumb.getInstance({
|
||||
Container: containerRef.current,
|
||||
...props
|
||||
});
|
||||
if (apiRef) {
|
||||
apiRef.current = jsPlumbInstance;
|
||||
}
|
||||
|
||||
return () => jsPlumbInstance.cleanupListeners();
|
||||
}, []);
|
||||
}, [ apiRef ]);
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className={className}>
|
||||
@ -142,3 +207,19 @@ function noteIdToId(noteId: string) {
|
||||
function idToNoteId(id: string) {
|
||||
return id.substr(13);
|
||||
}
|
||||
|
||||
function getZoom(container: HTMLDivElement) {
|
||||
const transform = window.getComputedStyle(container).transform;
|
||||
if (transform === "none") {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const matrixRegex = /matrix\((-?\d*\.?\d+),\s*0,\s*0,\s*-?\d*\.?\d+,\s*-?\d*\.?\d+,\s*-?\d*\.?\d+\)/;
|
||||
const matches = transform.match(matrixRegex);
|
||||
|
||||
if (!matches) {
|
||||
throw new Error(t("relation_map.cannot_match_transform", { transform }));
|
||||
}
|
||||
|
||||
return parseFloat(matches[1]);
|
||||
}
|
||||
|
||||
@ -339,43 +339,11 @@ export default class RelationMapTypeWidget extends TypeWidget {
|
||||
}
|
||||
|
||||
async initPanZoom() {
|
||||
if (this.pzInstance) {
|
||||
return;
|
||||
}
|
||||
|
||||
const panzoom = (await import("panzoom")).default;
|
||||
this.pzInstance = panzoom(this.$relationMapContainer[0], {
|
||||
maxZoom: 2,
|
||||
minZoom: 0.3,
|
||||
smoothScroll: false,
|
||||
|
||||
//@ts-expect-error Upstream incorrectly mentions no arguments.
|
||||
filterKey: function (e: KeyboardEvent) {
|
||||
// if ALT is pressed, then panzoom should bubble the event up
|
||||
// this is to preserve ALT-LEFT, ALT-RIGHT navigation working
|
||||
return e.altKey;
|
||||
}
|
||||
});
|
||||
|
||||
if (!this.pzInstance) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.pzInstance.on("transform", () => {
|
||||
// gets triggered on any transform change
|
||||
this.jsPlumbInstance?.setZoom(this.getZoom());
|
||||
|
||||
this.saveCurrentTransform();
|
||||
});
|
||||
|
||||
if (this.mapData?.transform) {
|
||||
this.pzInstance.zoomTo(0, 0, this.mapData.transform.scale);
|
||||
|
||||
this.pzInstance.moveTo(this.mapData.transform.x, this.mapData.transform.y);
|
||||
} else {
|
||||
// set to initial coordinates
|
||||
this.pzInstance.moveTo(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
saveCurrentTransform() {
|
||||
@ -564,24 +532,6 @@ export default class RelationMapTypeWidget extends TypeWidget {
|
||||
});
|
||||
}
|
||||
|
||||
getZoom() {
|
||||
const matrixRegex = /matrix\((-?\d*\.?\d+),\s*0,\s*0,\s*-?\d*\.?\d+,\s*-?\d*\.?\d+,\s*-?\d*\.?\d+\)/;
|
||||
|
||||
const transform = this.$relationMapContainer.css("transform");
|
||||
|
||||
if (transform === "none") {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const matches = transform.match(matrixRegex);
|
||||
|
||||
if (!matches) {
|
||||
throw new Error(t("relation_map.cannot_match_transform", { transform }));
|
||||
}
|
||||
|
||||
return parseFloat(matches[1]);
|
||||
}
|
||||
|
||||
async dropNoteOntoRelationMapHandler(ev: JQuery.DropEvent) {
|
||||
ev.preventDefault();
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user