diff --git a/src/public/javascripts/services/note_detail_relation_map.js b/src/public/javascripts/services/note_detail_relation_map.js index c26ef892a..86611e0ec 100644 --- a/src/public/javascripts/services/note_detail_relation_map.js +++ b/src/public/javascripts/services/note_detail_relation_map.js @@ -10,11 +10,34 @@ let mapData; let instance; let initDone = false; -async function show() { - $noteDetailRelationMap.show(); +const uniDirectionalOverlays = [ + [ "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(); mapData = { notes: [], @@ -24,246 +47,242 @@ async function show() { if (currentNote.content) { try { mapData = JSON.parse(currentNote.content); - } - catch (e) { + } catch (e) { console.log("Could not parse content: ", e); } } +} - jsPlumb.ready(async function () { - const uniDirectionalOverlays = [ - [ "Arrow", { - location: 1, - id: "arrow", - length: 14, - foldback: 0.8 - } ], - [ "Label", { label: "", id: "label", cssClass: "aLabel" }] - ]; +async function show() { + $noteDetailRelationMap.show(); - 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 - } ] - ]; + await libraryLoader.requireLibrary(libraryLoader.RELATION_MAP); - instance = jsPlumb.getInstance({ - Endpoint: ["Dot", {radius: 2}], - Connector: "StateMachine", - HoverPaintStyle: {stroke: "#1e8151", strokeWidth: 2 }, - Container: "relation-map-canvas" - }); + loadMapData(); + 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) { - // 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); - // }); + 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))); - jsPlumb.on($relationMapCanvas[0], "dblclick", function(e) { - newNode(jsPlumbUtil.uuid(),"new", e.offsetX, e.offsetY); - }); + if (match) { + match.type = 'biDirectional'; + } else { + relation.type = 'uniDirectional'; + relations.push(relation); + } + } - $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: function(event, ui) { - const $noteBox = ui.target.closest(".note-box"); - const noteId = $noteBox.prop("id"); + mapData.notes = mapData.notes.filter(note => note.id in data.noteTitles); - if (ui.cmd === "remove") { - if (!confirm("Are you sure you want to remove the note?")) { - return; - } + instance.batch(function () { + 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; - instance.remove(noteId); + for (const note of mapData.notes) { + const title = data.noteTitles[note.id]; - mapData.notes = mapData.notes.filter(note => note.id !== noteId); - mapData.relations = mapData.relations.filter(relation => relation.source !== noteId && relation.target !== noteId); + if (note.x && note.y) { + 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); - - instance.batch(function () { - 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) { + if (relation.name === 'isChildOf') { + continue; } - for (const relation of relations) { - if (relation.name === 'isChildOf') { - continue; - } + const connection = instance.connect({ + id: `${relation.sourceNoteId}${relation.targetNoteId}`, + 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); - } - - 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); + connection.getOverlay("label").setLabel(relation.name); + connection.canvas.setAttribute("data-connection-id", connection.id); } - $relationMapCanvas[0].addEventListener('zoom', function(e) { - mapData.transform = pz.getTransform(); - saveData(); - }); - - $relationMapCanvas[0].addEventListener('panend', function(e) { - mapData.transform = pz.getTransform(); - saveData(); - }, true); + initDone = 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() { noteDetailService.noteChanged(); }