trilium/src/public/javascripts/dialogs/link_map.js

188 lines
4.8 KiB
JavaScript
Raw Normal View History

2019-06-02 23:12:18 +08:00
import server from '../services/server.js';
import noteDetailService from "../services/note_detail.js";
2019-06-04 04:55:59 +08:00
import libraryLoader from "../services/library_loader.js";
2019-06-05 04:57:10 +08:00
import treeCache from "../services/tree_cache.js";
2019-06-10 03:48:30 +08:00
import linkService from "../services/link.js";
2019-06-04 04:55:59 +08:00
const $linkMapContainer = $("#link-map-container");
const linkOverlays = [
[ "Arrow", {
location: 1,
id: "arrow",
2019-06-10 17:22:52 +08:00
length: 10,
width: 10,
foldback: 0.7
2019-06-04 04:55:59 +08:00
} ]
];
2019-06-02 23:12:18 +08:00
2019-06-02 21:35:57 +08:00
const $dialog = $("#link-map-dialog");
2019-06-04 04:55:59 +08:00
let jsPlumbInstance = null;
2019-06-09 17:50:11 +08:00
let pzInstance = null;
2019-06-04 04:55:59 +08:00
2019-06-02 21:35:57 +08:00
async function showDialog() {
glob.activeDialog = $dialog;
2019-06-09 17:50:11 +08:00
await libraryLoader.requireLibrary(libraryLoader.LINK_MAP);
2019-06-02 23:12:18 +08:00
2019-06-04 04:55:59 +08:00
jsPlumb.ready(() => {
initJsPlumbInstance();
2019-06-02 23:12:18 +08:00
2019-06-09 17:50:11 +08:00
initPanZoom();
2019-06-04 04:55:59 +08:00
loadNotesAndRelations();
});
2019-06-02 23:12:18 +08:00
2019-06-02 21:35:57 +08:00
$dialog.modal();
}
2019-06-04 04:55:59 +08:00
async function loadNotesAndRelations() {
2019-06-10 03:48:30 +08:00
const activeNoteId = noteDetailService.getActiveNoteId();
2019-06-04 04:55:59 +08:00
2019-06-10 03:48:30 +08:00
const links = await server.get(`notes/${activeNoteId}/link-map`);
2019-06-04 04:55:59 +08:00
const noteIds = new Set(links.map(l => l.noteId).concat(links.map(l => l.targetNoteId)));
2019-06-05 04:57:10 +08:00
// preload all notes
const notes = await treeCache.getNotes(Array.from(noteIds));
2019-06-04 04:55:59 +08:00
const graph = new Springy.Graph();
graph.addNodes(...noteIds);
2019-06-05 04:57:10 +08:00
graph.addEdges(...links.map(l => [l.noteId, l.targetNoteId]));
2019-06-04 04:55:59 +08:00
const layout = new Springy.Layout.ForceDirected(
graph,
400.0, // Spring stiffness
400.0, // Node repulsion
0.5 // Damping
);
2019-06-10 01:18:14 +08:00
function getNoteBox(noteId) {
const noteBoxId = noteIdToId(noteId);
const $existingNoteBox = $("#" + noteBoxId);
if ($existingNoteBox.length > 0) {
return $existingNoteBox;
}
const note = notes.find(n => n.noteId === noteId);
const $noteBox = $("<div>")
.addClass("note-box")
2019-06-10 03:48:30 +08:00
.prop("id", noteBoxId);
linkService.createNoteLink(noteId, note.title).then($link => {
$noteBox.append($("<span>").addClass("title").append($link));
});
if (activeNoteId === noteId) {
$noteBox.addClass("link-map-active-note");
}
2019-06-10 01:18:14 +08:00
jsPlumbInstance.getContainer().appendChild($noteBox[0]);
2019-06-10 03:48:30 +08:00
jsPlumbInstance.draggable($noteBox[0], {
start: params => {
renderer.stop();
},
drag: params => {},
stop: params => {}
});
2019-06-10 01:18:14 +08:00
return $noteBox;
}
2019-06-05 04:57:10 +08:00
const renderer = new Springy.Renderer(
layout,
2019-06-10 03:48:30 +08:00
() => {},
2019-06-10 01:18:14 +08:00
(edge, p1, p2) => {
const connectionId = edge.source.id + '-' + edge.target.id;
2019-06-05 04:57:10 +08:00
2019-06-10 01:18:14 +08:00
if ($("#" + connectionId).length > 0) {
return;
}
2019-06-05 04:57:10 +08:00
2019-06-10 01:18:14 +08:00
getNoteBox(edge.source.id);
getNoteBox(edge.target.id);
2019-06-05 04:57:10 +08:00
2019-06-10 01:18:14 +08:00
const connection = jsPlumbInstance.connect({
source: noteIdToId(edge.source.id),
target: noteIdToId(edge.target.id),
2019-06-10 03:48:30 +08:00
type: 'link'
2019-06-05 04:57:10 +08:00
});
2019-06-10 01:18:14 +08:00
connection.canvas.id = connectionId;
},
(node, p) => {
const $noteBox = getNoteBox(node.id);
$noteBox
.css("left", (300 + p.x * 100) + "px")
.css("top", (300 + p.y * 100) + "px");
},
() => {},
() => {},
() => {
jsPlumbInstance.repaintEverything();
2019-06-05 04:57:10 +08:00
}
);
2019-06-04 04:55:59 +08:00
2019-06-05 04:57:10 +08:00
renderer.start();
2019-06-04 04:55:59 +08:00
}
2019-06-09 17:50:11 +08:00
function initPanZoom() {
if (pzInstance) {
return;
}
pzInstance = panzoom($linkMapContainer[0], {
maxZoom: 2,
minZoom: 0.3,
smoothScroll: false,
filterKey: function (e, dx, dy, dz) {
// if ALT is pressed then panzoom should bubble the event up
// this is to preserve ALT-LEFT, ALT-RIGHT navigation working
return e.altKey;
}
});
}
2019-06-10 01:18:14 +08:00
function cleanup() {
// delete all endpoints and connections
// this is done at this point (after async operations) to reduce flicker to the minimum
jsPlumbInstance.deleteEveryEndpoint();
// without this we still end up with note boxes remaining in the canvas
$linkMapContainer.empty();
2019-06-10 17:22:52 +08:00
// reset zoom/pan
pzInstance.zoomTo(0, 0, 1);
pzInstance.moveTo(0, 0);
2019-06-10 01:18:14 +08:00
}
2019-06-04 04:55:59 +08:00
function initJsPlumbInstance() {
if (jsPlumbInstance) {
2019-06-10 01:18:14 +08:00
cleanup();
2019-06-04 04:55:59 +08:00
return;
}
2019-06-05 04:57:10 +08:00
jsPlumbInstance = jsPlumb.getInstance({
2019-06-10 17:22:52 +08:00
ConnectionOverlays: linkOverlays,
PaintStyle: { stroke: "var(--muted-text-color)", strokeWidth: 1 },
HoverPaintStyle: { stroke: "var(--main-text-color)", strokeWidth: 1 },
2019-06-05 04:57:10 +08:00
Container: $linkMapContainer.attr("id")
2019-06-04 04:55:59 +08:00
});
2019-06-10 03:48:30 +08:00
jsPlumbInstance.registerConnectionType("link", { anchor: "Continuous", connector: "Straight", overlays: linkOverlays });
2019-06-05 04:57:10 +08:00
}
2019-06-04 04:55:59 +08:00
2019-06-05 04:57:10 +08:00
function noteIdToId(noteId) {
return "link-map-note-" + noteId;
2019-06-04 04:55:59 +08:00
}
2019-06-02 21:35:57 +08:00
export default {
showDialog
};