mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 10:26:08 +01:00 
			
		
		
		
	basic refactoring of relation map code
This commit is contained in:
		| @@ -10,11 +10,34 @@ let mapData; | |||||||
| let instance; | let instance; | ||||||
| let initDone = false; | let initDone = false; | ||||||
|  |  | ||||||
| async function show() { | const uniDirectionalOverlays = [ | ||||||
|     $noteDetailRelationMap.show(); |     [ "Arrow", { | ||||||
|  |         location: 1, | ||||||
|  |         id: "arrow", | ||||||
|  |         length: 14, | ||||||
|  |         foldback: 0.8 | ||||||
|  |     } ], | ||||||
|  |     [ "Label", { label: "", id: "label", cssClass: "aLabel" }] | ||||||
|  | ]; | ||||||
|  |  | ||||||
|     await libraryLoader.requireLibrary(libraryLoader.RELATION_MAP); | const biDirectionalOverlays = [ | ||||||
|  |     [ "Arrow", { | ||||||
|  |         location: 1, | ||||||
|  |         id: "arrow", | ||||||
|  |         length: 14, | ||||||
|  |         foldback: 0.8 | ||||||
|  |     } ], | ||||||
|  |     [ "Label", { label: "", id: "label", cssClass: "aLabel" }], | ||||||
|  |     [ "Arrow", { | ||||||
|  |         location: 0, | ||||||
|  |         id: "arrow2", | ||||||
|  |         length: 14, | ||||||
|  |         direction: -1, | ||||||
|  |         foldback: 0.8 | ||||||
|  |     } ] | ||||||
|  | ]; | ||||||
|  |  | ||||||
|  | function loadMapData() { | ||||||
|     const currentNote = noteDetailService.getCurrentNote(); |     const currentNote = noteDetailService.getCurrentNote(); | ||||||
|     mapData = { |     mapData = { | ||||||
|         notes: [], |         notes: [], | ||||||
| @@ -24,246 +47,242 @@ async function show() { | |||||||
|     if (currentNote.content) { |     if (currentNote.content) { | ||||||
|         try { |         try { | ||||||
|             mapData = JSON.parse(currentNote.content); |             mapData = JSON.parse(currentNote.content); | ||||||
|         } |         } catch (e) { | ||||||
|         catch (e) { |  | ||||||
|             console.log("Could not parse content: ", e); |             console.log("Could not parse content: ", e); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|     jsPlumb.ready(async function () { | async function show() { | ||||||
|         const uniDirectionalOverlays = [ |     $noteDetailRelationMap.show(); | ||||||
|             [ "Arrow", { |  | ||||||
|                 location: 1, |  | ||||||
|                 id: "arrow", |  | ||||||
|                 length: 14, |  | ||||||
|                 foldback: 0.8 |  | ||||||
|             } ], |  | ||||||
|             [ "Label", { label: "", id: "label", cssClass: "aLabel" }] |  | ||||||
|         ]; |  | ||||||
|  |  | ||||||
|         const biDirectionalOverlays = [ |     await libraryLoader.requireLibrary(libraryLoader.RELATION_MAP); | ||||||
|             [ "Arrow", { |  | ||||||
|                 location: 1, |  | ||||||
|                 id: "arrow", |  | ||||||
|                 length: 14, |  | ||||||
|                 foldback: 0.8 |  | ||||||
|             } ], |  | ||||||
|             [ "Label", { label: "", id: "label", cssClass: "aLabel" }], |  | ||||||
|             [ "Arrow", { |  | ||||||
|                 location: 0, |  | ||||||
|                 id: "arrow2", |  | ||||||
|                 length: 14, |  | ||||||
|                 direction: -1, |  | ||||||
|                 foldback: 0.8 |  | ||||||
|             } ] |  | ||||||
|         ]; |  | ||||||
|  |  | ||||||
|         instance = jsPlumb.getInstance({ |     loadMapData(); | ||||||
|             Endpoint: ["Dot", {radius: 2}], |  | ||||||
|             Connector: "StateMachine", |  | ||||||
|             HoverPaintStyle: {stroke: "#1e8151", strokeWidth: 2 }, |  | ||||||
|             Container: "relation-map-canvas" |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|  |     jsPlumb.ready(initJsPlumb); | ||||||
|  | } | ||||||
|  |  | ||||||
|         instance.registerConnectionType("uniDirectional", { anchor:"Continuous", connector:"StateMachine", overlays: uniDirectionalOverlays }); | async function loadNotesAndRelations() { | ||||||
|  |     const noteIds = mapData.notes.map(note => note.id); | ||||||
|  |     const data = await server.post("notes/relation-map", {noteIds}); | ||||||
|  |  | ||||||
|         instance.registerConnectionType("biDirectional", { anchor:"Continuous", connector:"StateMachine", overlays: biDirectionalOverlays }); |     const relations = []; | ||||||
|  |  | ||||||
|         // instance.bind("connection", function (info) { |     for (const relation of data.relations) { | ||||||
|         //     const connection = info.connection; |         const match = relations.find(rel => | ||||||
|         //     let name = "none"; |             rel.name === relation.name | ||||||
|         // |             && ((rel.sourceNoteId === relation.sourceNoteId && rel.targetNoteId === relation.targetNoteId) | ||||||
|         //     if (initDone) { |             || (rel.sourceNoteId === relation.targetNoteId && rel.targetNoteId === relation.sourceNoteId))); | ||||||
|         //         name = prompt("Specify new connection label:"); |  | ||||||
|         // |  | ||||||
|         //         mapData.relations.push({ |  | ||||||
|         //             connectionId: connection.id, |  | ||||||
|         //             source: connection.sourceId, |  | ||||||
|         //             target: connection.targetId, |  | ||||||
|         //             name: name |  | ||||||
|         //         }); |  | ||||||
|         // |  | ||||||
|         //         saveData(); |  | ||||||
|         //     } |  | ||||||
|         // |  | ||||||
|         //     connection.getOverlay("label").setLabel(name); |  | ||||||
|         // }); |  | ||||||
|  |  | ||||||
|         jsPlumb.on($relationMapCanvas[0], "dblclick", function(e) { |         if (match) { | ||||||
|             newNode(jsPlumbUtil.uuid(),"new", e.offsetX, e.offsetY); |             match.type = 'biDirectional'; | ||||||
|         }); |         } else { | ||||||
|  |             relation.type = 'uniDirectional'; | ||||||
|  |             relations.push(relation); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|         $relationMapCanvas.contextmenu({ |     mapData.notes = mapData.notes.filter(note => note.id in data.noteTitles); | ||||||
|             delegate: ".note-box", |  | ||||||
|             menu: [ |  | ||||||
|                 {title: "Remove note", cmd: "remove", uiIcon: "ui-icon-trash"}, |  | ||||||
|                 {title: "Edit title", cmd: "edit-title", uiIcon: "ui-icon-pencil"}, |  | ||||||
|             ], |  | ||||||
|             select: function(event, ui) { |  | ||||||
|                 const $noteBox = ui.target.closest(".note-box"); |  | ||||||
|                 const noteId = $noteBox.prop("id"); |  | ||||||
|  |  | ||||||
|                 if (ui.cmd === "remove") { |     instance.batch(function () { | ||||||
|                     if (!confirm("Are you sure you want to remove the note?")) { |         const maxY = mapData.notes.filter(note => !!note.y).map(note => note.y).reduce((a, b) => Math.max(a, b), 0); | ||||||
|                         return; |         let curX = 100; | ||||||
|                     } |         let curY = maxY + 200; | ||||||
|  |  | ||||||
|                     instance.remove(noteId); |         for (const note of mapData.notes) { | ||||||
|  |             const title = data.noteTitles[note.id]; | ||||||
|  |  | ||||||
|                     mapData.notes = mapData.notes.filter(note => note.id !== noteId); |             if (note.x && note.y) { | ||||||
|                     mapData.relations = mapData.relations.filter(relation => relation.source !== noteId && relation.target !== noteId); |                 newNode(note.id, title, note.x, note.y); | ||||||
|  |             } else { | ||||||
|  |                 newNode(note.id, title, curX, curY); | ||||||
|  |  | ||||||
|                     saveData(); |                 if (curX > 1000) { | ||||||
|  |                     curX = 100; | ||||||
|  |                     curY += 200; | ||||||
|  |                 } else { | ||||||
|  |                     curX += 200; | ||||||
|                 } |                 } | ||||||
|                 else if (ui.cmd === "edit-title") { |  | ||||||
|                     const title = prompt("Enter new note title:"); |  | ||||||
|  |  | ||||||
|                     if (!title) { |  | ||||||
|                         return; |  | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                     const note = mapData.notes.find(note => note.id === noteId); |  | ||||||
|                     note.title = title; |  | ||||||
|  |  | ||||||
|                     $noteBox.find(".title").text(note.title); |  | ||||||
|  |  | ||||||
|                     saveData(); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         $.widget("moogle.contextmenuRelation", $.moogle.contextmenu, {}); |  | ||||||
|  |  | ||||||
|         $relationMapCanvas.contextmenuRelation({ |  | ||||||
|             delegate: ".aLabel,.jtk-connector", |  | ||||||
|             autoTrigger: false, // it doesn't open automatically, needs to be triggered explicitly by .open() call |  | ||||||
|             menu: [ |  | ||||||
|                 {title: "Remove relation", cmd: "remove", uiIcon: "ui-icon-trash"}, |  | ||||||
|                 {title: "Edit relation name", cmd: "edit-name", uiIcon: "ui-icon-pencil"}, |  | ||||||
|             ], |  | ||||||
|             select: function(event, ui) { |  | ||||||
|                 const {connection} = ui.extraData; |  | ||||||
|  |  | ||||||
|                 if (ui.cmd === 'remove') { |  | ||||||
|                     if (!confirm("Are you sure you want to remove the relation?")) { |  | ||||||
|                         return; |  | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                     instance.deleteConnection(connection); |  | ||||||
|  |  | ||||||
|                     mapData.relations = mapData.relations.filter(relation => relation.connectionId !== connection.id); |  | ||||||
|                     saveData(); |  | ||||||
|                 } |  | ||||||
|                 else if (ui.cmd === 'edit-name') { |  | ||||||
|                     const relationName = prompt("Specify new relation name:"); |  | ||||||
|  |  | ||||||
|                     connection.getOverlay("label").setLabel(relationName); |  | ||||||
|  |  | ||||||
|                     const relation = mapData.relations.find(relation => relation.connectionId === connection.id); |  | ||||||
|                     relation.name = relationName; |  | ||||||
|  |  | ||||||
|                     saveData(); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         instance.bind("contextmenu", function (c, e) { |  | ||||||
|             e.preventDefault(); |  | ||||||
|  |  | ||||||
|             $relationMapCanvas.contextmenuRelation("open", e, { connection: c }); |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         const noteIds = mapData.notes.map(note => note.id); |  | ||||||
|         const data = await server.post("notes/relation-map", { noteIds }); |  | ||||||
|  |  | ||||||
|         const relations = []; |  | ||||||
|  |  | ||||||
|         for (const relation of data.relations) { |  | ||||||
|             const match = relations.find(rel => |  | ||||||
|                 rel.name === relation.name |  | ||||||
|                 && ((rel.sourceNoteId === relation.sourceNoteId && rel.targetNoteId === relation.targetNoteId) |  | ||||||
|                     || (rel.sourceNoteId === relation.targetNoteId && rel.targetNoteId === relation.sourceNoteId))); |  | ||||||
|  |  | ||||||
|             if (match) { |  | ||||||
|                 match.type = 'biDirectional'; |  | ||||||
|             } |  | ||||||
|             else { |  | ||||||
|                 relation.type = 'uniDirectional'; |  | ||||||
|                 relations.push(relation); |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         mapData.notes = mapData.notes.filter(note => note.id in data.noteTitles); |         for (const relation of relations) { | ||||||
|  |             if (relation.name === 'isChildOf') { | ||||||
|         instance.batch(function () { |                 continue; | ||||||
|             const maxY = mapData.notes.filter(note => !!note.y).map(note => note.y).reduce((a, b) => Math.max(a, b), 0); |  | ||||||
|             let curX = 100; |  | ||||||
|             let curY = maxY + 200; |  | ||||||
|  |  | ||||||
|             for (const note of mapData.notes) { |  | ||||||
|                 const title = data.noteTitles[note.id]; |  | ||||||
|  |  | ||||||
|                 if (note.x && note.y) { |  | ||||||
|                     newNode(note.id, title, note.x, note.y); |  | ||||||
|                 } |  | ||||||
|                 else { |  | ||||||
|                     newNode(note.id, title, curX, curY); |  | ||||||
|  |  | ||||||
|                     if (curX > 1000) { |  | ||||||
|                         curX = 100; |  | ||||||
|                         curY += 200; |  | ||||||
|                     } |  | ||||||
|                     else { |  | ||||||
|                         curX += 200; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             for (const relation of relations) { |             const connection = instance.connect({ | ||||||
|                 if (relation.name === 'isChildOf') { |                 id: `${relation.sourceNoteId}${relation.targetNoteId}`, | ||||||
|                     continue; |                 source: relation.sourceNoteId, | ||||||
|                 } |                 target: relation.targetNoteId, | ||||||
|  |                 type: relation.type | ||||||
|  |             }); | ||||||
|  |  | ||||||
|                 const connection = instance.connect({ id: `${relation.sourceNoteId}${relation.targetNoteId}`, source: relation.sourceNoteId, target: relation.targetNoteId, type: relation.type }); |             relation.connectionId = connection.id; | ||||||
|  |  | ||||||
|                 relation.connectionId = connection.id; |             connection.getOverlay("label").setLabel(relation.name); | ||||||
|  |             connection.canvas.setAttribute("data-connection-id", connection.id); | ||||||
|                 connection.getOverlay("label").setLabel(relation.name); |  | ||||||
|                 connection.canvas.setAttribute("data-connection-id", connection.id); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             initDone = true; |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         // so that canvas is not panned when clicking/dragging note box |  | ||||||
|         $relationMapCanvas.on('mousedown touchstart', '.note-box, .aLabel', e => e.stopPropagation()); |  | ||||||
|  |  | ||||||
|         jsPlumb.fire("jsPlumbDemoLoaded", instance); |  | ||||||
|  |  | ||||||
|         const pz = panzoom($relationMapCanvas[0], { |  | ||||||
|             maxZoom: 2, |  | ||||||
|             minZoom: 0.1, |  | ||||||
|             smoothScroll: false |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         if (mapData.transform) { |  | ||||||
|             pz.moveTo(mapData.transform.x, mapData.transform.y); |  | ||||||
|             pz.zoomTo(0, 0, mapData.transform.scale); |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         $relationMapCanvas[0].addEventListener('zoom', function(e) { |         initDone = true; | ||||||
|             mapData.transform = pz.getTransform(); |  | ||||||
|             saveData(); |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         $relationMapCanvas[0].addEventListener('panend', function(e) { |  | ||||||
|             mapData.transform = pz.getTransform(); |  | ||||||
|             saveData(); |  | ||||||
|         }, true); |  | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function initPanZoom() { | ||||||
|  |     const pz = panzoom($relationMapCanvas[0], { | ||||||
|  |         maxZoom: 2, | ||||||
|  |         minZoom: 0.1, | ||||||
|  |         smoothScroll: false | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     if (mapData.transform) { | ||||||
|  |         pz.moveTo(mapData.transform.x, mapData.transform.y); | ||||||
|  |         pz.zoomTo(0, 0, mapData.transform.scale); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $relationMapCanvas[0].addEventListener('zoom', function (e) { | ||||||
|  |         mapData.transform = pz.getTransform(); | ||||||
|  |         saveData(); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     $relationMapCanvas[0].addEventListener('panend', function (e) { | ||||||
|  |         mapData.transform = pz.getTransform(); | ||||||
|  |         saveData(); | ||||||
|  |     }, true); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function initJsPlumb () { | ||||||
|  |     instance = jsPlumb.getInstance({ | ||||||
|  |         Endpoint: ["Dot", {radius: 2}], | ||||||
|  |         Connector: "StateMachine", | ||||||
|  |         HoverPaintStyle: {stroke: "#1e8151", strokeWidth: 2 }, | ||||||
|  |         Container: "relation-map-canvas" | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     instance.registerConnectionType("uniDirectional", { anchor:"Continuous", connector:"StateMachine", overlays: uniDirectionalOverlays }); | ||||||
|  |  | ||||||
|  |     instance.registerConnectionType("biDirectional", { anchor:"Continuous", connector:"StateMachine", overlays: biDirectionalOverlays }); | ||||||
|  |  | ||||||
|  |     // instance.bind("connection", function (info) { | ||||||
|  |     //     const connection = info.connection; | ||||||
|  |     //     let name = "none"; | ||||||
|  |     // | ||||||
|  |     //     if (initDone) { | ||||||
|  |     //         name = prompt("Specify new connection label:"); | ||||||
|  |     // | ||||||
|  |     //         mapData.relations.push({ | ||||||
|  |     //             connectionId: connection.id, | ||||||
|  |     //             source: connection.sourceId, | ||||||
|  |     //             target: connection.targetId, | ||||||
|  |     //             name: name | ||||||
|  |     //         }); | ||||||
|  |     // | ||||||
|  |     //         saveData(); | ||||||
|  |     //     } | ||||||
|  |     // | ||||||
|  |     //     connection.getOverlay("label").setLabel(name); | ||||||
|  |     // }); | ||||||
|  |  | ||||||
|  |     jsPlumb.on($relationMapCanvas[0], "dblclick", function(e) { | ||||||
|  |         newNode(jsPlumbUtil.uuid(),"new", e.offsetX, e.offsetY); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     $relationMapCanvas.contextmenu({ | ||||||
|  |         delegate: ".note-box", | ||||||
|  |         menu: [ | ||||||
|  |             {title: "Remove note", cmd: "remove", uiIcon: "ui-icon-trash"}, | ||||||
|  |             {title: "Edit title", cmd: "edit-title", uiIcon: "ui-icon-pencil"}, | ||||||
|  |         ], | ||||||
|  |         select: noteContextMenuHandler | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     $.widget("moogle.contextmenuRelation", $.moogle.contextmenu, {}); | ||||||
|  |  | ||||||
|  |     $relationMapCanvas.contextmenuRelation({ | ||||||
|  |         delegate: ".aLabel,.jtk-connector", | ||||||
|  |         autoTrigger: false, // it doesn't open automatically, needs to be triggered explicitly by .open() call | ||||||
|  |         menu: [ | ||||||
|  |             {title: "Remove relation", cmd: "remove", uiIcon: "ui-icon-trash"}, | ||||||
|  |             {title: "Edit relation name", cmd: "edit-name", uiIcon: "ui-icon-pencil"}, | ||||||
|  |         ], | ||||||
|  |         select: relationContextMenuHandler | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     instance.bind("contextmenu", function (c, e) { | ||||||
|  |         e.preventDefault(); | ||||||
|  |  | ||||||
|  |         $relationMapCanvas.contextmenuRelation("open", e, { connection: c }); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     await loadNotesAndRelations(); | ||||||
|  |  | ||||||
|  |     // so that canvas is not panned when clicking/dragging note box | ||||||
|  |     $relationMapCanvas.on('mousedown touchstart', '.note-box, .aLabel', e => e.stopPropagation()); | ||||||
|  |  | ||||||
|  |     jsPlumb.fire("jsPlumbDemoLoaded", instance); | ||||||
|  |  | ||||||
|  |     initPanZoom(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function relationContextMenuHandler(event, ui) { | ||||||
|  |     const {connection} = ui.extraData; | ||||||
|  |  | ||||||
|  |     if (ui.cmd === 'remove') { | ||||||
|  |         if (!confirm("Are you sure you want to remove the relation?")) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         instance.deleteConnection(connection); | ||||||
|  |  | ||||||
|  |         mapData.relations = mapData.relations.filter(relation => relation.connectionId !== connection.id); | ||||||
|  |         saveData(); | ||||||
|  |     } | ||||||
|  |     else if (ui.cmd === 'edit-name') { | ||||||
|  |         const relationName = prompt("Specify new relation name:"); | ||||||
|  |  | ||||||
|  |         connection.getOverlay("label").setLabel(relationName); | ||||||
|  |  | ||||||
|  |         const relation = mapData.relations.find(relation => relation.connectionId === connection.id); | ||||||
|  |         relation.name = relationName; | ||||||
|  |  | ||||||
|  |         saveData(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function noteContextMenuHandler(event, ui) { | ||||||
|  |     const $noteBox = ui.target.closest(".note-box"); | ||||||
|  |     const noteId = $noteBox.prop("id"); | ||||||
|  |  | ||||||
|  |     if (ui.cmd === "remove") { | ||||||
|  |         if (!confirm("Are you sure you want to remove the note?")) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         instance.remove(noteId); | ||||||
|  |  | ||||||
|  |         mapData.notes = mapData.notes.filter(note => note.id !== noteId); | ||||||
|  |         mapData.relations = mapData.relations.filter(relation => relation.source !== noteId && relation.target !== noteId); | ||||||
|  |  | ||||||
|  |         saveData(); | ||||||
|  |     } | ||||||
|  |     else if (ui.cmd === "edit-title") { | ||||||
|  |         const title = prompt("Enter new note title:"); | ||||||
|  |  | ||||||
|  |         if (!title) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         const note = mapData.notes.find(note => note.id === noteId); | ||||||
|  |         note.title = title; | ||||||
|  |  | ||||||
|  |         $noteBox.find(".title").text(note.title); | ||||||
|  |  | ||||||
|  |         saveData(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| function saveData() { | function saveData() { | ||||||
|     noteDetailService.noteChanged(); |     noteDetailService.noteChanged(); | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user