diff --git a/libraries/springy.js b/libraries/springy.js index 0bf5ba44c..a1e8d65f4 100644 --- a/libraries/springy.js +++ b/libraries/springy.js @@ -506,7 +506,7 @@ Springy.requestAnimationFrame(function step() { t.tick(0.03); - if (render !== undefined) { + if (!t._stop && render !== undefined) { render(); } diff --git a/src/public/javascripts/dialogs/link_map.js b/src/public/javascripts/dialogs/link_map.js index fada7f76b..a3e5f57b2 100644 --- a/src/public/javascripts/dialogs/link_map.js +++ b/src/public/javascripts/dialogs/link_map.js @@ -16,14 +16,22 @@ const linkOverlays = [ } ] ]; +const LINK_TYPES = [ "hyper", "image", "relation", "relation-map" ]; + const $dialog = $("#link-map-dialog"); +const $maxNotesInput = $("#link-map-max-notes"); let jsPlumbInstance = null; let pzInstance = null; +let renderer = null; async function showDialog() { glob.activeDialog = $dialog; + // set default settings + $maxNotesInput.val(50); + LINK_TYPES.forEach(lt => $("#link-map-" + lt).attr("checked", "checked")); + await libraryLoader.requireLibrary(libraryLoader.LINK_MAP); jsPlumb.ready(() => { @@ -38,12 +46,26 @@ async function showDialog() { } async function loadNotesAndRelations() { + cleanup(); + + const linkTypes = LINK_TYPES.filter(lt => $(`#link-map-${lt}:checked`).length > 0); + const maxNotes = parseInt($maxNotesInput.val()); + + console.log(linkTypes); + const activeNoteId = noteDetailService.getActiveNoteId(); - const links = await server.get(`notes/${activeNoteId}/link-map`); + const links = await server.post(`notes/${activeNoteId}/link-map`, { + linkTypes, + maxNotes + }); const noteIds = new Set(links.map(l => l.noteId).concat(links.map(l => l.targetNoteId))); + if (noteIds.size === 0) { + noteIds.add(activeNoteId); + } + // preload all notes const notes = await treeCache.getNotes(Array.from(noteIds)); @@ -94,7 +116,7 @@ async function loadNotesAndRelations() { return $noteBox; } - const renderer = new Springy.Renderer( + renderer = new Springy.Renderer( layout, () => {}, (edge, p1, p2) => { @@ -150,6 +172,10 @@ function initPanZoom() { } function cleanup() { + if (renderer) { + renderer.stop(); + } + // delete all endpoints and connections // this is done at this point (after async operations) to reduce flicker to the minimum jsPlumbInstance.deleteEveryEndpoint(); @@ -183,6 +209,10 @@ function noteIdToId(noteId) { return "link-map-note-" + noteId; } +$(".link-map-settings").change(loadNotesAndRelations); + +$maxNotesInput.on("input", loadNotesAndRelations); + export default { showDialog }; \ No newline at end of file diff --git a/src/public/stylesheets/desktop.css b/src/public/stylesheets/desktop.css index e6b1ee608..30beb328d 100644 --- a/src/public/stylesheets/desktop.css +++ b/src/public/stylesheets/desktop.css @@ -335,10 +335,4 @@ li.dropdown-submenu:hover > ul.dropdown-menu { .note-tab-row.note-tab-row-is-sorting .note-tab:not(.note-tab-is-dragging), .note-tab-row:not(.note-tab-row-is-sorting) .note-tab.note-tab-was-just-dragged { transition: transform 120ms ease-in-out; -} - -#link-map-container { - position: relative; - height: 700px; - outline: none; /* remove dotted outline on click */ } \ No newline at end of file diff --git a/src/public/stylesheets/link_map.css b/src/public/stylesheets/link_map.css index 291c534aa..684166845 100644 --- a/src/public/stylesheets/link_map.css +++ b/src/public/stylesheets/link_map.css @@ -1,5 +1,7 @@ -.link-map-active-note { - background-color: var(--more-accented-background-color) !important; +#link-map-container { + position: relative; + height: calc(95vh - 130px); + outline: none; /* remove dotted outline on click */ } #link-map-container .note-box { @@ -33,4 +35,8 @@ #link-map-container .floating-button { position: absolute !important; z-index: 100; +} + +.link-map-active-note { + background-color: var(--more-accented-background-color) !important; } \ No newline at end of file diff --git a/src/routes/api/link_map.js b/src/routes/api/link_map.js index e34bae139..b1b853238 100644 --- a/src/routes/api/link_map.js +++ b/src/routes/api/link_map.js @@ -2,8 +2,8 @@ const sql = require('../../services/sql'); -async function getLinks(noteIds) { - return await sql.getManyRows(` +async function getLinks(noteIds, linkTypes) { + return (await sql.getManyRows(` SELECT noteId, targetNoteId, type FROM links WHERE (noteId IN (???) OR targetNoteId IN (???)) @@ -14,17 +14,18 @@ async function getLinks(noteIds) { WHERE (noteId IN (???) OR value IN (???)) AND type = 'relation' AND isDeleted = 0 - `, Array.from(noteIds)); + `, Array.from(noteIds))).filter(l => linkTypes.includes(l.type)); } async function getLinkMap(req) { const {noteId} = req.params; + const {linkTypes, maxNotes} = req.body; let noteIds = new Set([noteId]); let links = []; while (true) { - const newLinks = await getLinks(noteIds); + const newLinks = await getLinks(noteIds, linkTypes); const newNoteIds = new Set(newLinks.map(l => l.noteId).concat(newLinks.map(l => l.targetNoteId))); if (newNoteIds.size === noteIds.size) { @@ -32,7 +33,7 @@ async function getLinkMap(req) { break; } - if (newNoteIds.length > 50) { + if (newNoteIds.size > maxNotes) { // to many notes to display break; } diff --git a/src/routes/routes.js b/src/routes/routes.js index c48a32684..9b8655dae 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -155,7 +155,7 @@ function register(app) { apiRoute(GET, '/api/attributes/names', attributesRoute.getAttributeNames); apiRoute(GET, '/api/attributes/values/:attributeName', attributesRoute.getValuesForAttribute); - apiRoute(GET, '/api/notes/:noteId/link-map', linkMapRoute.getLinkMap); + apiRoute(POST, '/api/notes/:noteId/link-map', linkMapRoute.getLinkMap); apiRoute(GET, '/api/date-notes/date/:date', dateNotesRoute.getDateNote); apiRoute(GET, '/api/date-notes/month/:month', dateNotesRoute.getMonthNote); diff --git a/src/views/dialogs/link_map.ejs b/src/views/dialogs/link_map.ejs index 9ebafc79f..79fe92ef7 100644 --- a/src/views/dialogs/link_map.ejs +++ b/src/views/dialogs/link_map.ejs @@ -1,8 +1,40 @@