chore(type_widgets): start porting relation map

This commit is contained in:
Elian Doran
2025-09-29 18:50:40 +03:00
parent 286d7c8228
commit 04b678ef4c
3 changed files with 108 additions and 68 deletions

View File

@@ -35,7 +35,8 @@ const TYPE_MAPPINGS: Record<ExtendedNoteType, () => Promise<{ default: TypeWidge
"readOnlyText": () => import("./type_widgets/text/ReadOnlyText"),
"editableText": () => import("./type_widgets/text/EditableText"),
"render": () => import("./type_widgets/Render"),
"canvas": () => import("./type_widgets/Canvas")
"canvas": () => import("./type_widgets/Canvas"),
"relationMap": () => import("./type_widgets/RelationMap")
// TODO: finalize the record.
};

View File

@@ -0,0 +1,106 @@
import { useEffect, useRef, useState } from "preact/hooks";
import { TypeWidgetProps } from "./type_widget";
import { Defaults, jsPlumb, OverlaySpec } from "jsplumb";
import { useNoteBlob } from "../react/hooks";
import FNote from "../../entities/fnote";
interface MapData {
notes: {
noteId: string;
x: number;
y: number;
}[];
transform: {
x: number,
y: number,
scale: number
}
}
const uniDirectionalOverlays: OverlaySpec[] = [
[
"Arrow",
{
location: 1,
id: "arrow",
length: 14,
foldback: 0.8
}
],
["Label", { label: "", id: "label", cssClass: "connection-label" }]
];
export default function RelationMap({ note }: TypeWidgetProps) {
const data = useData(note);
console.log("Got data", data);
return (
<div className="note-detail-relation-map note-detail-printable">
<div className="relation-map-wrapper">
<JsPlumb
className="relation-map-container"
props={{
Endpoint: ["Dot", { radius: 2 }],
Connector: "StateMachine",
ConnectionOverlays: uniDirectionalOverlays,
HoverPaintStyle: { stroke: "#777", strokeWidth: 1 },
}}
/>
</div>
</div>
)
}
function useData(note: FNote) {
const blob = useNoteBlob(note);
let content: MapData | null = null;
if (blob?.content) {
try {
content = JSON.parse(blob.content);
} catch (e) {
console.log("Could not parse content: ", e);
}
}
if (!content) {
content = {
notes: [],
// it is important to have this exact value here so that initial transform is the same as this
// which will guarantee note won't be saved on first conversion to the relation map note type
// this keeps the principle that note type change doesn't destroy note content unless user
// does some actual change
transform: {
x: 0,
y: 0,
scale: 1
}
};
}
return content;
}
function JsPlumb({ className, props }: {
className?: string;
props: Omit<Defaults, "container">;
}) {
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!containerRef.current) return;
const jsPlumbInstance = jsPlumb.getInstance({
Container: containerRef.current,
...props
});
return () => jsPlumbInstance.cleanupListeners();
}, []);
return (
<div ref={containerRef} className={className}>
</div>
)
}

View File

@@ -30,19 +30,6 @@ declare module "jsplumb" {
}
}
const uniDirectionalOverlays: OverlaySpec[] = [
[
"Arrow",
{
location: 1,
id: "arrow",
length: 14,
foldback: 0.8
}
],
["Label", { label: "", id: "label", cssClass: "connection-label" }]
];
const biDirectionalOverlays = [
[
"Arrow",
@@ -102,13 +89,6 @@ const linkOverlays = [
]
];
const TPL = /*html*/`
<div class="note-detail-relation-map note-detail-printable">
<div class="relation-map-wrapper">
<div class="relation-map-container"></div>
</div>
</div>`;
let containerCounter = 1;
interface Clipboard {
@@ -116,19 +96,6 @@ interface Clipboard {
title: string;
}
interface MapData {
notes: {
noteId: string;
x: number;
y: number;
}[];
transform: {
x: number,
y: number,
scale: number
}
}
export type RelationType = "uniDirectional" | "biDirectional" | "inverse";
interface Relation {
@@ -280,31 +247,6 @@ export default class RelationMapTypeWidget extends TypeWidget {
}
}
async loadMapData() {
this.mapData = {
notes: [],
// it is important to have this exact value here so that initial transform is the same as this
// which will guarantee note won't be saved on first conversion to the relation map note type
// this keeps the principle that note type change doesn't destroy note content unless user
// does some actual change
transform: {
x: 0,
y: 0,
scale: 1
}
};
const blob = await this.note?.getBlob();
if (blob?.content) {
try {
this.mapData = JSON.parse(blob.content);
} catch (e) {
console.log("Could not parse content: ", e);
}
}
}
noteIdToId(noteId: string) {
return `rel-map-note-${noteId}`;
}
@@ -476,15 +418,6 @@ export default class RelationMapTypeWidget extends TypeWidget {
return;
}
const jsPlumb = (await import("jsplumb")).default.jsPlumb;
this.jsPlumbInstance = jsPlumb.getInstance({
Endpoint: ["Dot", { radius: 2 }],
Connector: "StateMachine",
ConnectionOverlays: uniDirectionalOverlays,
HoverPaintStyle: { stroke: "#777", strokeWidth: 1 },
Container: this.$relationMapContainer.attr("id")
});
if (!this.jsPlumbInstance) {
return;
}