mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
removal of link map service
This commit is contained in:
parent
d4a955a3f5
commit
bdff1c1246
@ -3,203 +3,5 @@ import server from "./server.js";
|
|||||||
import froca from "./froca.js";
|
import froca from "./froca.js";
|
||||||
|
|
||||||
export default class LinkMap {
|
export default class LinkMap {
|
||||||
constructor(note, $linkMapContainer, options = {}) {
|
|
||||||
this.note = note;
|
|
||||||
this.options = Object.assign({
|
|
||||||
maxDepth: 10,
|
|
||||||
maxNotes: 100,
|
|
||||||
zoom: 1.0
|
|
||||||
}, options);
|
|
||||||
|
|
||||||
this.$linkMapContainer = $linkMapContainer;
|
|
||||||
|
|
||||||
this.zoomLevel = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
setZoomLevel(level) {
|
|
||||||
this.zoomLevel = level;
|
|
||||||
}
|
|
||||||
|
|
||||||
async render() {
|
|
||||||
await libraryLoader.requireLibrary(libraryLoader.FORCE_GRAPH);
|
|
||||||
|
|
||||||
this.graph = ForceGraph()(this.$linkMapContainer[0])
|
|
||||||
.width(this.options.width)
|
|
||||||
.height(this.options.height)
|
|
||||||
.onZoom(zoom => this.setZoomLevel(zoom.k))
|
|
||||||
.nodeRelSize(7)
|
|
||||||
.nodeCanvasObject((node, ctx) => this.paintNode(node, this.stringToColor(node.type), ctx))
|
|
||||||
.nodePointerAreaPaint((node, ctx) => this.paintNode(node, this.stringToColor(node.type), ctx))
|
|
||||||
.nodeLabel(node => node.name)
|
|
||||||
.maxZoom(5)
|
|
||||||
.nodePointerAreaPaint((node, color, ctx) => {
|
|
||||||
ctx.fillStyle = color;
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.arc(node.x, node.y, 5, 0, 2 * Math.PI, false);
|
|
||||||
ctx.fill();
|
|
||||||
})
|
|
||||||
.linkLabel(l => `${l.source.name} - <strong>${l.name}</strong> - ${l.target.name}`)
|
|
||||||
.linkCanvasObject((link, ctx) => this.paintLink(link, ctx))
|
|
||||||
.linkCanvasObjectMode(() => "after")
|
|
||||||
.linkDirectionalArrowLength(4)
|
|
||||||
.linkDirectionalArrowRelPos(1)
|
|
||||||
.linkWidth(2)
|
|
||||||
.linkColor("#ddd")
|
|
||||||
.d3VelocityDecay(0.2)
|
|
||||||
.onNodeClick(node => this.nodeClicked(node));
|
|
||||||
|
|
||||||
this.graph.d3Force('link').distance(50);
|
|
||||||
|
|
||||||
this.graph.d3Force('center').strength(0.9);
|
|
||||||
|
|
||||||
this.graph.d3Force('charge').strength(-30);
|
|
||||||
this.graph.d3Force('charge').distanceMax(400);
|
|
||||||
|
|
||||||
this.renderData(await this.loadNotesAndRelations());
|
|
||||||
}
|
|
||||||
|
|
||||||
renderData(data, zoomToFit = true, zoomPadding = 10) {
|
|
||||||
this.graph.graphData(data);
|
|
||||||
|
|
||||||
if (zoomToFit) {
|
|
||||||
setTimeout(() => this.graph.zoomToFit(400, zoomPadding), 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setHeight(height) {
|
|
||||||
this.graph.height(height);
|
|
||||||
}
|
|
||||||
|
|
||||||
centerOnNode(node) {
|
|
||||||
this.nodeClicked(node);
|
|
||||||
|
|
||||||
this.graph.centerAt(node.x, node.y, 1000);
|
|
||||||
this.graph.zoom(6, 2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
async nodeClicked(node) {
|
|
||||||
if (!node.expanded) {
|
|
||||||
const neighborGraph = await fetchNeighborGraph(node.id);
|
|
||||||
|
|
||||||
addToTasGraph(neighborGraph);
|
|
||||||
|
|
||||||
renderData(getTasGraph(), false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadNotesAndRelations(options = {}) {
|
|
||||||
this.options = Object.assign(this.options, options);
|
|
||||||
|
|
||||||
const links = await server.post(`notes/${this.note.noteId}/link-map`, {
|
|
||||||
maxNotes: this.options.maxNotes,
|
|
||||||
maxDepth: this.options.maxDepth
|
|
||||||
});
|
|
||||||
|
|
||||||
const noteIds = new Set(links.map(l => l.noteId).concat(links.map(l => l.targetNoteId)));
|
|
||||||
|
|
||||||
if (noteIds.size === 0) {
|
|
||||||
noteIds.add(this.note.noteId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// preload all notes
|
|
||||||
const notes = await froca.getNotes(Array.from(noteIds), true);
|
|
||||||
|
|
||||||
return {
|
|
||||||
nodes: notes.map(note => ({
|
|
||||||
id: note.noteId,
|
|
||||||
name: note.title,
|
|
||||||
type: note.type
|
|
||||||
})),
|
|
||||||
links: links.map(link => ({
|
|
||||||
id: link.noteId + "-" + link.name + "-" + link.targetNoteId,
|
|
||||||
source: link.noteId,
|
|
||||||
target: link.targetNoteId,
|
|
||||||
name: link.name
|
|
||||||
}))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup() {
|
|
||||||
}
|
|
||||||
|
|
||||||
paintLink(link, ctx) {
|
|
||||||
if (this.zoomLevel < 3) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.font = '3px MontserratLight';
|
|
||||||
ctx.textAlign = 'center';
|
|
||||||
ctx.textBaseline = 'middle';
|
|
||||||
ctx.fillStyle = "grey";
|
|
||||||
|
|
||||||
const {source, target} = link;
|
|
||||||
|
|
||||||
const x = (source.x + target.x) / 2;
|
|
||||||
const y = (source.y + target.y) / 2;
|
|
||||||
|
|
||||||
ctx.save();
|
|
||||||
ctx.translate(x, y);
|
|
||||||
|
|
||||||
const deltaY = source.y - target.y;
|
|
||||||
const deltaX = source.x - target.x;
|
|
||||||
|
|
||||||
let angle = Math.atan2(deltaY, deltaX);
|
|
||||||
let moveY = 2;
|
|
||||||
|
|
||||||
if (angle < -Math.PI / 2 || angle > Math.PI / 2) {
|
|
||||||
angle += Math.PI;
|
|
||||||
moveY = -2;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.rotate(angle);
|
|
||||||
ctx.fillText(link.name, 0, moveY);
|
|
||||||
ctx.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
paintNode(node, color, ctx) {
|
|
||||||
const {x, y} = node;
|
|
||||||
|
|
||||||
ctx.fillStyle = color;
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.arc(x, y, 4, 0, 2 * Math.PI, false);
|
|
||||||
ctx.fill();
|
|
||||||
|
|
||||||
if (this.zoomLevel < 2) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!node.expanded) {
|
|
||||||
ctx.fillStyle = color;
|
|
||||||
ctx.font = 10 + 'px MontserratLight';
|
|
||||||
ctx.textAlign = 'center';
|
|
||||||
ctx.textBaseline = 'middle';
|
|
||||||
ctx.fillText("+", x, y + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.fillStyle = "#555";
|
|
||||||
ctx.font = 5 + 'px MontserratLight';
|
|
||||||
ctx.textAlign = 'center';
|
|
||||||
ctx.textBaseline = 'middle';
|
|
||||||
|
|
||||||
let title = node.name;
|
|
||||||
|
|
||||||
if (title.length > 15) {
|
|
||||||
title = title.substr(0, 15) + "...";
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.fillText(title, x, y + 7);
|
|
||||||
}
|
|
||||||
|
|
||||||
stringToColor(str) {
|
|
||||||
let hash = 0;
|
|
||||||
for (let i = 0; i < str.length; i++) {
|
|
||||||
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
|
||||||
}
|
|
||||||
let colour = '#';
|
|
||||||
for (let i = 0; i < 3; i++) {
|
|
||||||
const value = (hash >> (i * 8)) & 0xFF;
|
|
||||||
colour += ('00' + value.toString(16)).substr(-2);
|
|
||||||
}
|
|
||||||
return colour;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||||
import froca from "../../services/froca.js";
|
import froca from "../../services/froca.js";
|
||||||
|
import libraryLoader from "../../services/library_loader.js";
|
||||||
|
import server from "../../services/server.js";
|
||||||
|
|
||||||
const TPL = `
|
const TPL = `
|
||||||
<div class="link-map-widget">
|
<div class="link-map-widget">
|
||||||
@ -42,6 +44,7 @@ export default class LinkMapWidget extends NoteContextAwareWidget {
|
|||||||
|
|
||||||
doRender() {
|
doRender() {
|
||||||
this.$widget = $(TPL);
|
this.$widget = $(TPL);
|
||||||
|
this.$container = this.$widget.find(".link-map-container");
|
||||||
|
|
||||||
this.$openFullButton = this.$widget.find('.open-full-button');
|
this.$openFullButton = this.$widget.find('.open-full-button');
|
||||||
this.$openFullButton.on('click', () => {
|
this.$openFullButton.on('click', () => {
|
||||||
@ -51,7 +54,7 @@ export default class LinkMapWidget extends NoteContextAwareWidget {
|
|||||||
|
|
||||||
this.$widget.find('.link-map-container').css("height", maxHeight);
|
this.$widget.find('.link-map-container').css("height", maxHeight);
|
||||||
|
|
||||||
this.linkMapService.setHeight(maxHeight);
|
this.graph.height(maxHeight);
|
||||||
|
|
||||||
this.$openFullButton.hide();
|
this.$openFullButton.hide();
|
||||||
this.$collapseButton.show();
|
this.$collapseButton.show();
|
||||||
@ -61,37 +64,193 @@ export default class LinkMapWidget extends NoteContextAwareWidget {
|
|||||||
this.$collapseButton.on('click', () => {
|
this.$collapseButton.on('click', () => {
|
||||||
this.$widget.find('.link-map-container,.force-graph-container,canvas').css("height", 300);
|
this.$widget.find('.link-map-container,.force-graph-container,canvas').css("height", 300);
|
||||||
|
|
||||||
this.linkMapService.setHeight(300);
|
this.graph.height(300);
|
||||||
|
|
||||||
this.$openFullButton.show();
|
this.$openFullButton.show();
|
||||||
this.$collapseButton.hide();
|
this.$collapseButton.hide();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
this.overflowing();
|
this.overflowing();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setZoomLevel(level) {
|
||||||
|
this.zoomLevel = level;
|
||||||
|
}
|
||||||
|
|
||||||
async refreshWithNote(note) {
|
async refreshWithNote(note) {
|
||||||
this.$widget.find(".link-map-container").empty();
|
this.$container.empty();
|
||||||
|
|
||||||
const $linkMapContainer = this.$widget.find('.link-map-container');
|
await libraryLoader.requireLibrary(libraryLoader.FORCE_GRAPH);
|
||||||
|
|
||||||
const LinkMapServiceClass = (await import('../../services/link_map.js')).default;
|
this.graph = ForceGraph()(this.$container[0])
|
||||||
|
.width(this.$container.width())
|
||||||
|
.height(this.$container.height())
|
||||||
|
.onZoom(zoom => this.setZoomLevel(zoom.k))
|
||||||
|
.nodeRelSize(7)
|
||||||
|
.nodeCanvasObject((node, ctx) => this.paintNode(node, this.stringToColor(node.type), ctx))
|
||||||
|
.nodePointerAreaPaint((node, ctx) => this.paintNode(node, this.stringToColor(node.type), ctx))
|
||||||
|
.nodeLabel(node => node.name)
|
||||||
|
.maxZoom(5)
|
||||||
|
.nodePointerAreaPaint((node, color, ctx) => {
|
||||||
|
ctx.fillStyle = color;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(node.x, node.y, 5, 0, 2 * Math.PI, false);
|
||||||
|
ctx.fill();
|
||||||
|
})
|
||||||
|
.linkLabel(l => `${l.source.name} - <strong>${l.name}</strong> - ${l.target.name}`)
|
||||||
|
.linkCanvasObject((link, ctx) => this.paintLink(link, ctx))
|
||||||
|
.linkCanvasObjectMode(() => "after")
|
||||||
|
.linkDirectionalArrowLength(4)
|
||||||
|
.linkDirectionalArrowRelPos(1)
|
||||||
|
.linkWidth(2)
|
||||||
|
.linkColor("#ddd")
|
||||||
|
.d3VelocityDecay(0.2)
|
||||||
|
.onNodeClick(node => this.nodeClicked(node));
|
||||||
|
|
||||||
this.linkMapService = new LinkMapServiceClass(note, $linkMapContainer, {
|
this.graph.d3Force('link').distance(50);
|
||||||
maxDepth: 3,
|
|
||||||
zoom: 0.6,
|
this.graph.d3Force('center').strength(0.9);
|
||||||
width: $linkMapContainer.width(),
|
|
||||||
height: $linkMapContainer.height()
|
this.graph.d3Force('charge').strength(-30);
|
||||||
|
this.graph.d3Force('charge').distanceMax(400);
|
||||||
|
|
||||||
|
this.renderData(await this.loadNotesAndRelations());
|
||||||
|
}
|
||||||
|
|
||||||
|
renderData(data, zoomToFit = true, zoomPadding = 10) {
|
||||||
|
this.graph.graphData(data);
|
||||||
|
|
||||||
|
if (zoomToFit) {
|
||||||
|
setTimeout(() => this.graph.zoomToFit(400, zoomPadding), 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
centerOnNode(node) {
|
||||||
|
this.nodeClicked(node);
|
||||||
|
|
||||||
|
this.graph.centerAt(node.x, node.y, 1000);
|
||||||
|
this.graph.zoom(6, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
async nodeClicked(node) {
|
||||||
|
if (!node.expanded) {
|
||||||
|
const neighborGraph = await fetchNeighborGraph(node.id);
|
||||||
|
|
||||||
|
addToTasGraph(neighborGraph);
|
||||||
|
|
||||||
|
renderData(getTasGraph(), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadNotesAndRelations(options = {}) {
|
||||||
|
const links = await server.post(`notes/${this.note.noteId}/link-map`, {
|
||||||
|
maxNotes: 30,
|
||||||
|
maxDepth: 5
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.linkMapService.render();
|
const noteIds = new Set(links.map(l => l.noteId).concat(links.map(l => l.targetNoteId)));
|
||||||
|
|
||||||
|
if (noteIds.size === 0) {
|
||||||
|
noteIds.add(this.note.noteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup() {
|
// preload all notes
|
||||||
if (this.linkMapService) {
|
const notes = await froca.getNotes(Array.from(noteIds), true);
|
||||||
this.linkMapService.cleanup();
|
|
||||||
|
return {
|
||||||
|
nodes: notes.map(note => ({
|
||||||
|
id: note.noteId,
|
||||||
|
name: note.title,
|
||||||
|
type: note.type
|
||||||
|
})),
|
||||||
|
links: links.map(link => ({
|
||||||
|
id: link.noteId + "-" + link.name + "-" + link.targetNoteId,
|
||||||
|
source: link.noteId,
|
||||||
|
target: link.targetNoteId,
|
||||||
|
name: link.name
|
||||||
|
}))
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
paintLink(link, ctx) {
|
||||||
|
if (this.zoomLevel < 3) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.font = '3px MontserratLight';
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.textBaseline = 'middle';
|
||||||
|
ctx.fillStyle = "grey";
|
||||||
|
|
||||||
|
const {source, target} = link;
|
||||||
|
|
||||||
|
const x = (source.x + target.x) / 2;
|
||||||
|
const y = (source.y + target.y) / 2;
|
||||||
|
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(x, y);
|
||||||
|
|
||||||
|
const deltaY = source.y - target.y;
|
||||||
|
const deltaX = source.x - target.x;
|
||||||
|
|
||||||
|
let angle = Math.atan2(deltaY, deltaX);
|
||||||
|
let moveY = 2;
|
||||||
|
|
||||||
|
if (angle < -Math.PI / 2 || angle > Math.PI / 2) {
|
||||||
|
angle += Math.PI;
|
||||||
|
moveY = -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.rotate(angle);
|
||||||
|
ctx.fillText(link.name, 0, moveY);
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
paintNode(node, color, ctx) {
|
||||||
|
const {x, y} = node;
|
||||||
|
|
||||||
|
ctx.fillStyle = color;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(x, y, 4, 0, 2 * Math.PI, false);
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
if (this.zoomLevel < 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!node.expanded) {
|
||||||
|
ctx.fillStyle = color;
|
||||||
|
ctx.font = 10 + 'px MontserratLight';
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.textBaseline = 'middle';
|
||||||
|
ctx.fillText("+", x, y + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.fillStyle = "#555";
|
||||||
|
ctx.font = 5 + 'px MontserratLight';
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.textBaseline = 'middle';
|
||||||
|
|
||||||
|
let title = node.name;
|
||||||
|
|
||||||
|
if (title.length > 15) {
|
||||||
|
title = title.substr(0, 15) + "...";
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.fillText(title, x, y + 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
stringToColor(str) {
|
||||||
|
let hash = 0;
|
||||||
|
for (let i = 0; i < str.length; i++) {
|
||||||
|
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
||||||
|
}
|
||||||
|
let colour = '#';
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
const value = (hash >> (i * 8)) & 0xFF;
|
||||||
|
colour += ('00' + value.toString(16)).substr(-2);
|
||||||
|
}
|
||||||
|
return colour;
|
||||||
}
|
}
|
||||||
|
|
||||||
entitiesReloadedEvent({loadResults}) {
|
entitiesReloadedEvent({loadResults}) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user