From 67d91547956627a78ec07186940d861c724dcffa Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 4 Oct 2025 10:15:38 +0300 Subject: [PATCH] chore(react/type_widgets): reintroduce relation note dragging --- .../type_widgets/relation_map/RelationMap.tsx | 50 ++++++++++++++++++- .../widgets/type_widgets/relation_map/api.ts | 18 +++++++ .../widgets/type_widgets_old/relation_map.ts | 35 ------------- 3 files changed, 66 insertions(+), 37 deletions(-) diff --git a/apps/client/src/widgets/type_widgets/relation_map/RelationMap.tsx b/apps/client/src/widgets/type_widgets/relation_map/RelationMap.tsx index ed62dce7d..deb2b6caa 100644 --- a/apps/client/src/widgets/type_widgets/relation_map/RelationMap.tsx +++ b/apps/client/src/widgets/type_widgets/relation_map/RelationMap.tsx @@ -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 (
-
+
; + mapApiRef: RefObject; +}): Pick, "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, jsPlumbApiRef: RefObject }) { const connectionCallback = useCallback(async (info: OnConnectionBindInfo, originalEvent: Event) => { const connection = info.connection; diff --git a/apps/client/src/widgets/type_widgets/relation_map/api.ts b/apps/client/src/widgets/type_widgets/relation_map/api.ts index cf9c2c280..f3e258db6 100644 --- a/apps/client/src/widgets/type_widgets/relation_map/api.ts +++ b/apps/client/src/widgets/type_widgets/relation_map/api.ts @@ -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); diff --git a/apps/client/src/widgets/type_widgets_old/relation_map.ts b/apps/client/src/widgets/type_widgets_old/relation_map.ts index c414cdcac..109e7657b 100644 --- a/apps/client/src/widgets/type_widgets_old/relation_map.ts +++ b/apps/client/src/widgets/type_widgets_old/relation_map.ts @@ -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(); - } - }