chore(react/type_widgets): reintroduce relation note dragging

This commit is contained in:
Elian Doran 2025-10-04 10:15:38 +03:00
parent 1eca9f6541
commit 67d9154795
No known key found for this signature in database
3 changed files with 66 additions and 37 deletions

View File

@ -1,4 +1,4 @@
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks";
import { TypeWidgetProps } from "../type_widget";
import { jsPlumbInstance, OnConnectionBindInfo } from "jsplumb";
import { useEditorSpacedUpdate, useTriliumEvent, useTriliumEvents } from "../../react/hooks";
@ -19,6 +19,7 @@ import { NoteBox } from "./NoteBox";
import utils from "../../../services/utils";
import attribute_autocomplete from "../../../services/attribute_autocomplete";
import { buildRelationContextMenuHandler } from "./context_menu";
import { HTMLProps } from "preact/compat";
interface Clipboard {
noteId: string;
@ -90,6 +91,7 @@ export default function RelationMap({ note, ntxId }: TypeWidgetProps) {
ntxId,
mapApiRef
});
const dragProps = useNoteDragging({ containerRef, mapApiRef });
const connectionCallback = useRelationCreation({ mapApiRef, jsPlumbApiRef: pbApiRef });
@ -115,7 +117,11 @@ export default function RelationMap({ note, ntxId }: TypeWidgetProps) {
return (
<div className="note-detail-relation-map note-detail-printable">
<div className="relation-map-wrapper" onClick={clickCallback}>
<div
className="relation-map-wrapper"
onClick={clickCallback}
{...dragProps}
>
<JsPlumb
apiRef={pbApiRef}
containerRef={containerRef}
@ -305,6 +311,46 @@ function useNoteCreation({ ntxId, note, containerRef, mapApiRef }: {
return onClickHandler;
}
function useNoteDragging({ containerRef, mapApiRef }: {
containerRef: RefObject<HTMLDivElement>;
mapApiRef: RefObject<RelationMapApi>;
}): Pick<HTMLProps<HTMLDivElement>, "onDrop" | "onDragOver"> {
const dragProps = useMemo(() => ({
onDrop(ev: DragEvent) {
const container = containerRef.current;
if (!container) return;
const dragData = ev.dataTransfer?.getData("text");
if (!dragData) return;
const notes = JSON.parse(dragData);
let { x, y } = getMousePosition(ev, container, getZoom(container));
const entries: (MapDataNoteEntry & { title: string })[] = [];
for (const note of notes) {
entries.push({
...note,
x, y
});
if (x > 1000) {
y += 100;
x = 0;
} else {
x += 200;
}
}
mapApiRef.current?.addMultipleNotes(entries);
},
onDragOver(ev) {
ev.preventDefault();
}
}), [ containerRef, mapApiRef ]);
return dragProps;
}
function useRelationCreation({ mapApiRef, jsPlumbApiRef }: { mapApiRef: RefObject<RelationMapApi>, jsPlumbApiRef: RefObject<jsPlumbInstance> }) {
const connectionCallback = useCallback(async (info: OnConnectionBindInfo, originalEvent: Event) => {
const connection = info.connection;

View File

@ -4,6 +4,7 @@ import { t } from "../../../services/i18n";
import server from "../../../services/server";
import utils from "../../../services/utils";
import { RelationMapRelation } from "@triliumnext/commons";
import toast from "../../../services/toast";
export interface MapDataNoteEntry {
noteId: string;
@ -103,6 +104,23 @@ export default class RelationMapApi {
this.onDataChange(false);
}
addMultipleNotes(entries: (MapDataNoteEntry & { title: string })[]) {
if (!entries.length) return;
for (const entry of entries) {
const exists = this.data.notes.some((n) => n.noteId === entry.noteId);
if (exists) {
toast.showError(t("relation_map.note_already_in_diagram", { title: entry.title }));
continue;
}
this.data.notes.push(entry);
}
this.onDataChange(true);
}
async connect(name: string, sourceNoteId: string, targetNoteId: string) {
name = utils.filterAttributeName(name);
const relationExists = this.relations?.some((rel) => rel.targetNoteId === targetNoteId && rel.sourceNoteId === sourceNoteId && rel.name === name);

View File

@ -70,7 +70,6 @@ export default class RelationMapTypeWidget extends TypeWidget {
this.clipboard = null;
this.$widget.on("drop", (ev) => this.dropNoteOntoRelationMapHandler(ev));
this.$widget.on("dragover", (ev) => ev.preventDefault());
this.initialized = new Promise(async (res) => {
// Weird typecast is needed probably due to bad typings in the module itself.
@ -122,38 +121,4 @@ export default class RelationMapTypeWidget extends TypeWidget {
this.spacedUpdate.scheduleUpdate();
}
async dropNoteOntoRelationMapHandler(ev: JQuery.DropEvent) {
ev.preventDefault();
const dragData = ev.originalEvent?.dataTransfer?.getData("text");
if (!dragData) {
return;
}
const notes = JSON.parse(dragData);
let { x, y } = this.getMousePosition(ev);
for (const note of notes) {
const exists = this.mapData?.notes.some((n) => n.noteId === note.noteId);
if (exists) {
toastService.showError(t("relation_map.note_already_in_diagram", { title: note.title }));
continue;
}
this.mapData?.notes.push({ noteId: note.noteId, x, y });
if (x > 1000) {
y += 100;
x = 0;
} else {
x += 200;
}
}
this.saveData();
this.loadNotesAndRelations();
}
}