From 6559e6c25b364f176a55932d7566536a36a503eb Mon Sep 17 00:00:00 2001 From: azivner Date: Sun, 21 Oct 2018 10:26:14 +0200 Subject: [PATCH] add children to relation map --- src/public/javascripts/services/messaging.js | 4 +- .../services/note_detail_relation_map.js | 356 ++++++++++-------- src/public/stylesheets/relation-map.css | 2 +- src/routes/api/notes.js | 28 +- src/routes/routes.js | 1 + 5 files changed, 229 insertions(+), 162 deletions(-) diff --git a/src/public/javascripts/services/messaging.js b/src/public/javascripts/services/messaging.js index db8140c24..62ee07bf7 100644 --- a/src/public/javascripts/services/messaging.js +++ b/src/public/javascripts/services/messaging.js @@ -41,7 +41,7 @@ function handleMessage(event) { lastPingTs = new Date().getTime(); if (message.data.length > 0) { - console.log(utils.now(), "Sync data: ", message.data); + console.debug(utils.now(), "Sync data: ", message.data); lastSyncId = message.data[message.data.length - 1].id; } @@ -67,7 +67,7 @@ function connectWebSocket() { // use wss for secure messaging const ws = new WebSocket(protocol + "://" + location.host); - ws.onopen = event => console.log(utils.now(), "Connected to server with WebSocket"); + ws.onopen = event => console.debug(utils.now(), "Connected to server with WebSocket"); ws.onmessage = handleMessage; ws.onclose = function(){ // Try to reconnect in 5 seconds diff --git a/src/public/javascripts/services/note_detail_relation_map.js b/src/public/javascripts/services/note_detail_relation_map.js index dfb2d7e80..984ee6610 100644 --- a/src/public/javascripts/services/note_detail_relation_map.js +++ b/src/public/javascripts/services/note_detail_relation_map.js @@ -4,14 +4,34 @@ import libraryLoader from "./library_loader.js"; const $noteDetailRelationMap = $("#note-detail-relation-map"); const $relationMapCanvas = $("#relation-map-canvas"); +const $addChildNotesButton = $("#relation-map-add-child-notes"); + +let mapData; +let instance; +let initDone = false; async function show() { $noteDetailRelationMap.show(); await libraryLoader.requireLibrary(libraryLoader.RELATION_MAP); + const currentNote = noteDetailService.getCurrentNote(); + mapData = { + notes: [], + relations: [] + }; + + if (currentNote.content) { + try { + mapData = JSON.parse(currentNote.content); + } + catch (e) { + console.log("Could not parse content: ", e); + } + } + jsPlumb.ready(function () { - const instance = jsPlumb.getInstance({ + instance = jsPlumb.getInstance({ Endpoint: ["Dot", {radius: 2}], Connector: "StateMachine", HoverPaintStyle: {stroke: "#1e8151", strokeWidth: 2 }, @@ -29,156 +49,30 @@ async function show() { instance.registerConnectionType("basic", { anchor:"Continuous", connector:"StateMachine" }); - window.jsp = instance; - - let data; - let initDone = false; - - instance.bind("connection", function (info) { - const connection = info.connection; - let type; - - if (initDone) { - type = prompt("Specify new connection label:"); - - data.relations.push({ - connectionId: connection.id, - source: connection.sourceId, - target: connection.targetId, - type: type - }); - - saveData(); - } - else { - type = "none"; - } - - connection.getOverlay("label").setLabel(type); - }); + // 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, "auto", "auto"); }); - function saveData() { - localStorage.setItem('triliumData', JSON.stringify(data)); - } - - function initNode(el) { - instance.draggable(el, { - handle: ".handle", - start:function(params) { - }, - drag:function(params) { - - }, - stop:function(params) { - const note = data.notes.find(note => note.id === params.el.id); - - if (!note) { - console.error(`Note ${params.el.id} not found!`); - return; - } - - [note.x, note.y] = params.finalPos; - - saveData(); - } - }); - - instance.makeSource(el, { - filter: ".endpoint", - anchor: "Continuous", - connectorStyle: { stroke: "#5c96bc", strokeWidth: 2, outlineStroke: "transparent", outlineWidth: 4 }, - connectionType:"basic", - extract:{ - "action":"the-action" - } - }); - - instance.makeTarget(el, { - dropOptions: { hoverClass: "dragHover" }, - anchor: "Continuous", - allowLoopback: true - }); - - // this is not part of the core demo functionality; it is a means for the Toolkit edition's wrapped - // version of this demo to find out about new nodes being added. - // - instance.fire("jsPlumbDemoNodeAdded", el); - - $(el).resizable({ - resize: function(event, ui) { -// instance.repaint(ui.helper.prop("id")); - - instance.repaintEverything(); - }, - stop: function(event, ui) { - const note = data.notes.find(note => note.id === ui.helper.prop("id")); - - note.width = ui.helper.width(); - note.height = ui.helper.height(); - - saveData(); - }, - handles: "all" - }); - } - - function newNode(id, title, x, y, width, height) { - const $noteBox = $("
") - .addClass("note-box") - .prop("id", id) - .append($("
").addClass("handle")) - .append($("").addClass("title").text(title)) - .append($("
").addClass("endpoint")) - .css("left", x + "px") - .css("top", y + "px") - .css("width", width) - .css("height", height); - - instance.getContainer().appendChild($noteBox[0]); - - initNode($noteBox[0]); - } - - const notes = [ - { id: "eliI", title: "Queen Elizabeth", x: 100, y: 100 }, - { id: "kinggeorge", title: "King George VI.", x: 300, y: 100 }, - { id: "phillip", title: "Prince Phillip" }, - { id: "eliII", title: "Queen Elizabeth II.", x: 300, y: 300 }, - { id: "charles", title: "Prince Charles" }, - { id: "diana", title: "Princess Diana" }, - { id: "harry", title: "Prince Harry" }, - { id: "william", title: "Prince William "} - ]; - - const relations = [ - { source: "eliI", target: "kinggeorge", type: "isSpouse" }, - { source: "eliI", target: "eliII", type: "hasChild" }, - { source: "kinggeorge", target: "eliII", type: "hasChild" }, - { source: "eliII", target: "charles", type: "hasChild" }, - { source: "phillip", target: "charles", type: "hasChild" }, - { source: "phillip", target: "eliII", type: "isSpouse" }, - { source: "charles", target: "diana", type: "isSpouse" }, - { source: "charles", target: "william", type: "hasChild" }, - { source: "charles", target: "harry", type: "hasChild" }, - { source: "diana", target: "william", type: "hasChild" }, - { source: "diana", target: "harry", type: "hasChild" } - ]; - - data = localStorage.getItem('triliumData'); - - if (data) { - data = JSON.parse(data); - } - else { - data = { notes, relations }; - - saveData(); - } - $relationMapCanvas.contextmenu({ delegate: ".note-box", menu: [ @@ -196,8 +90,8 @@ async function show() { instance.remove(noteId); - data.notes = data.notes.filter(note => note.id !== noteId); - data.relations = data.relations.filter(relation => relation.source !== noteId && relation.target !== noteId); + mapData.notes = mapData.notes.filter(note => note.id !== noteId); + mapData.relations = mapData.relations.filter(relation => relation.source !== noteId && relation.target !== noteId); saveData(); } @@ -208,7 +102,7 @@ async function show() { return; } - const note = data.notes.find(note => note.id === noteId); + const note = mapData.notes.find(note => note.id === noteId); note.title = title; $noteBox.find(".title").text(note.title); @@ -237,7 +131,7 @@ async function show() { instance.deleteConnection(connection); - data.relations = data.relations.filter(relation => relation.connectionId !== connection.id); + mapData.relations = mapData.relations.filter(relation => relation.connectionId !== connection.id); saveData(); } else if (ui.cmd === 'edit-name') { @@ -245,8 +139,8 @@ async function show() { connection.getOverlay("label").setLabel(relationName); - const relation = data.relations.find(relation => relation.connectionId === connection.id); - relation.type = relationName; + const relation = mapData.relations.find(relation => relation.connectionId === connection.id); + relation.name = relationName; saveData(); } @@ -260,11 +154,11 @@ async function show() { }); instance.batch(function () { - const maxY = notes.filter(note => !!note.y).map(note => note.y).reduce((a, b) => Math.max(a, b)); + 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 data.notes) { + for (const note of mapData.notes) { if (note.x && note.y) { newNode(note.id, note.title, note.x, note.y, note.width + "px", note.height + "px"); } @@ -284,12 +178,12 @@ async function show() { } } - for (const relation of data.relations) { - const connection = instance.connect({ id: relation.id, source: relation.source, target: relation.target, type:"basic" }); + for (const relation of mapData.relations) { + const connection = instance.connect({ id: relation.id, source: relation.source, target: relation.target, type: "basic" }); relation.connectionId = connection.id; - connection.getOverlay("label").setLabel(relation.type); + connection.getOverlay("label").setLabel(relation.name); connection.canvas.setAttribute("data-connection-id", connection.id); } @@ -305,9 +199,155 @@ async function show() { }); } +function saveData() { + const currentNote = noteDetailService.getCurrentNote(); + + noteDetailService.saveNote(); +} + +function initNode(el) { + instance.draggable(el, { + handle: ".handle", + start:function(params) { + }, + drag:function(params) { + + }, + stop:function(params) { + const note = mapData.notes.find(note => note.id === params.el.id); + + if (!note) { + console.error(`Note ${params.el.id} not found!`); + return; + } + + [note.x, note.y] = params.finalPos; + + saveData(); + } + }); + + instance.makeSource(el, { + filter: ".endpoint", + anchor: "Continuous", + connectorStyle: { + stroke: "#5c96bc", + strokeWidth: 2, + outlineStroke: "transparent", + outlineWidth: 4 + }, + connectionType: "basic", + extract:{ + "action": "the-action" + } + }); + + instance.makeTarget(el, { + dropOptions: { hoverClass: "dragHover" }, + anchor: "Continuous", + allowLoopback: true + }); + + // this is not part of the core demo functionality; it is a means for the Toolkit edition's wrapped + // version of this demo to find out about new nodes being added. + // + instance.fire("jsPlumbDemoNodeAdded", el); + + $(el).resizable({ + resize: function(event, ui) { +// instance.repaint(ui.helper.prop("id")); + + instance.repaintEverything(); + }, + stop: function(event, ui) { + const note = mapData.notes.find(note => note.id === ui.helper.prop("id")); + + note.width = ui.helper.width(); + note.height = ui.helper.height(); + + saveData(); + }, + handles: "all" + }); +} + +function newNode(id, title, x, y, width, height) { + const $noteBox = $("
") + .addClass("note-box") + .prop("id", id) + .append($("
").addClass("handle")) + .append($("").addClass("title").text(title)) + .append($("
").addClass("endpoint")) + .css("left", x + "px") + .css("top", y + "px") + .css("width", width) + .css("height", height); + + instance.getContainer().appendChild($noteBox[0]); + + initNode($noteBox[0]); +} + +$addChildNotesButton.click(async () => { + const children = await server.get("notes/" + noteDetailService.getCurrentNoteId() + "/children"); + + 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 child of children) { + const note = { + id: child.noteId, + title: child.title, + width: "auto", + height: "auto" + }; + + mapData.notes.push(note); + + newNode(note.id, note.title, curX, curY, note.width, note.height); + + if (curX > 1000) { + curX = 100; + curY += 200; + } + else { + curX += 200; + } + } + + for (const child of children) { + for (const relation of child.relations) { + const connection = instance.connect({ + id: relation.attributeId, + source: child.noteId, + target: relation.targetNoteId, + type: "basic" + }); + + if (!connection) { + continue; + } + + mapData.relations.push({ + source: child.noteId, + target: relation.targetNoteId, + name: relation.name + }); + + relation.connectionId = connection.id; + + connection.getOverlay("label").setLabel(relation.name); + connection.canvas.setAttribute("data-connection-id", connection.id); + } + } + + saveData(); +}); + export default { show, - getContent: () => "", + getContent: () => JSON.stringify(mapData), focus: () => null, onNoteChange: () => null } \ No newline at end of file diff --git a/src/public/stylesheets/relation-map.css b/src/public/stylesheets/relation-map.css index c7050bf0a..7598723fb 100644 --- a/src/public/stylesheets/relation-map.css +++ b/src/public/stylesheets/relation-map.css @@ -1,5 +1,5 @@ #relation-map-canvas { - height: 100vh; + height: 500px; } .note-box { diff --git a/src/routes/api/notes.js b/src/routes/api/notes.js index ccb5f75b6..12dd8e919 100644 --- a/src/routes/api/notes.js +++ b/src/routes/api/notes.js @@ -20,6 +20,31 @@ async function getNote(req) { return note; } +async function getChildren(req) { + const parentNoteId = req.params.parentNoteId; + const parentNote = await repository.getNote(parentNoteId); + + if (!parentNote) { + return [404, `Note ${parentNoteId} has not been found.`]; + } + + const ret = []; + + for (const childNote of await parentNote.getChildNotes()) { + ret.push({ + noteId: childNote.noteId, + title: childNote.title, + relations: (await childNote.getRelations()).map(relation => { return { + attributeId: relation.attributeId, + name: relation.name, + targetNoteId: relation.value + }; }) + }); + } + + return ret; +} + async function createNote(req) { const parentNoteId = req.params.parentNoteId; const newNote = req.body; @@ -73,5 +98,6 @@ module.exports = { createNote, sortNotes, protectSubtree, - setNoteTypeMime + setNoteTypeMime, + getChildren }; \ No newline at end of file diff --git a/src/routes/routes.js b/src/routes/routes.js index 65448d869..0c887ac75 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -116,6 +116,7 @@ function register(app) { apiRoute(GET, '/api/notes/:noteId', notesApiRoute.getNote); apiRoute(PUT, '/api/notes/:noteId', notesApiRoute.updateNote); apiRoute(POST, '/api/notes/:parentNoteId/children', notesApiRoute.createNote); + apiRoute(GET, '/api/notes/:parentNoteId/children', notesApiRoute.getChildren); apiRoute(PUT, '/api/notes/:noteId/sort', notesApiRoute.sortNotes); apiRoute(PUT, '/api/notes/:noteId/protect/:isProtected', notesApiRoute.protectSubtree); apiRoute(PUT, /\/api\/notes\/(.*)\/type\/(.*)\/mime\/(.*)/, notesApiRoute.setNoteTypeMime);