mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-04 13:39:01 +01:00 
			
		
		
		
	unified note map with ribbon map
This commit is contained in:
		
							parent
							
								
									0f693dae5e
								
							
						
					
					
						commit
						a766374bf4
					
				@ -29,16 +29,25 @@ const TPL = `<div class="note-map-widget" style="position: relative;">
 | 
				
			|||||||
      <button type="button" class="btn btn-secondary" title="Tree map" data-type="tree"><span class="bx bx-sitemap"></span></button>
 | 
					      <button type="button" class="btn btn-secondary" title="Tree map" data-type="tree"><span class="bx bx-sitemap"></span></button>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="style-resolver"></div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <div class="note-map-container"></div>
 | 
					    <div class="note-map-container"></div>
 | 
				
			||||||
</div>`;
 | 
					</div>`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class NoteMapWidget extends NoteContextAwareWidget {
 | 
					export default class NoteMapWidget extends NoteContextAwareWidget {
 | 
				
			||||||
 | 
					    constructor(widgetMode) {
 | 
				
			||||||
 | 
					        super();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.widgetMode = widgetMode; // 'type' or 'ribbon'
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    doRender() {
 | 
					    doRender() {
 | 
				
			||||||
        this.$widget = $(TPL);
 | 
					        this.$widget = $(TPL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.$container = this.$widget.find(".note-map-container");
 | 
					        this.$container = this.$widget.find(".note-map-container");
 | 
				
			||||||
 | 
					        this.$styleResolver = this.$widget.find('.style-resolver');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        window.addEventListener('resize', () => this.setFullHeight(), false);
 | 
					        window.addEventListener('resize', () => this.setHeight(), false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.$widget.find(".map-type-switcher button").on("click",  async e => {
 | 
					        this.$widget.find(".map-type-switcher button").on("click",  async e => {
 | 
				
			||||||
            const type = $(e.target).closest("button").attr("data-type");
 | 
					            const type = $(e.target).closest("button").attr("data-type");
 | 
				
			||||||
@ -49,31 +58,30 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
 | 
				
			|||||||
        super.doRender();
 | 
					        super.doRender();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    setFullHeight() {
 | 
					    setHeight() {
 | 
				
			||||||
        if (!this.graph) { // no graph has been even rendered
 | 
					        if (!this.graph) { // no graph has been even rendered
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const {top} = this.$widget[0].getBoundingClientRect();
 | 
					        const $parent = this.$widget.parent();
 | 
				
			||||||
 | 
					 | 
				
			||||||
        const height = $(window).height() - top;
 | 
					 | 
				
			||||||
        const width = this.$widget.width();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.$widget.find('.note-map-container')
 | 
					 | 
				
			||||||
            .css("height", height)
 | 
					 | 
				
			||||||
            .css("width", this.$widget.width());
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.graph
 | 
					        this.graph
 | 
				
			||||||
            .height(height)
 | 
					            .height($parent.height())
 | 
				
			||||||
            .width(width);
 | 
					            .width($parent.width());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async refreshWithNote() {
 | 
					    async refreshWithNote() {
 | 
				
			||||||
        this.$widget.show();
 | 
					        this.$widget.show();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.css = {
 | 
				
			||||||
 | 
					            fontFamily: this.$container.css("font-family"),
 | 
				
			||||||
 | 
					            textColor: this.rgb2hex(this.$container.css("color")),
 | 
				
			||||||
 | 
					            mutedTextColor: this.rgb2hex(this.$styleResolver.css("color"))
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.mapType = this.note.getLabelValue("mapType") === "tree" ? "tree" : "link";
 | 
					        this.mapType = this.note.getLabelValue("mapType") === "tree" ? "tree" : "link";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.setFullHeight();
 | 
					        this.setHeight();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await libraryLoader.requireLibrary(libraryLoader.FORCE_GRAPH);
 | 
					        await libraryLoader.requireLibrary(libraryLoader.FORCE_GRAPH);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -98,7 +106,7 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
 | 
				
			|||||||
            .linkDirectionalArrowRelPos(1)
 | 
					            .linkDirectionalArrowRelPos(1)
 | 
				
			||||||
            .linkWidth(1)
 | 
					            .linkWidth(1)
 | 
				
			||||||
            .linkColor(() => this.css.mutedTextColor)
 | 
					            .linkColor(() => this.css.mutedTextColor)
 | 
				
			||||||
            .onNodeClick(node => this.nodeClicked(node));
 | 
					            .onNodeClick(node => appContext.tabManager.getActiveContext().setNote(node.id));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (this.mapType === 'link') {
 | 
					        if (this.mapType === 'link') {
 | 
				
			||||||
            this.graph
 | 
					            this.graph
 | 
				
			||||||
@ -112,20 +120,29 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
 | 
				
			|||||||
        this.graph.d3Force('charge').strength(-30);
 | 
					        this.graph.d3Force('charge').strength(-30);
 | 
				
			||||||
        this.graph.d3Force('charge').distanceMax(1000);
 | 
					        this.graph.d3Force('charge').distanceMax(1000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let mapRootNoteId = this.note.getLabelValue("mapRootNoteId");
 | 
					        let mapRootNoteId = this.getMapRootNoteId();
 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (mapRootNoteId === 'hoisted') {
 | 
					 | 
				
			||||||
            mapRootNoteId = hoistedNoteService.getHoistedNoteId();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        else if (!mapRootNoteId) {
 | 
					 | 
				
			||||||
            mapRootNoteId = appContext.tabManager.getActiveContext().parentNoteId;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const data = await this.loadNotesAndRelations(mapRootNoteId);
 | 
					        const data = await this.loadNotesAndRelations(mapRootNoteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.renderData(data);
 | 
					        this.renderData(data);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    getMapRootNoteId() {
 | 
				
			||||||
 | 
					        if (this.widgetMode === 'ribbon') {
 | 
				
			||||||
 | 
					            return this.noteId;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let mapRootNoteId = this.note.getLabelValue("mapRootNoteId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (mapRootNoteId === 'hoisted') {
 | 
				
			||||||
 | 
					            mapRootNoteId = hoistedNoteService.getHoistedNoteId();
 | 
				
			||||||
 | 
					        } else if (!mapRootNoteId) {
 | 
				
			||||||
 | 
					            mapRootNoteId = appContext.tabManager.getActiveContext().parentNoteId;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return mapRootNoteId;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    stringToColor(str) {
 | 
					    stringToColor(str) {
 | 
				
			||||||
        let hash = 0;
 | 
					        let hash = 0;
 | 
				
			||||||
        for (let i = 0; i < str.length; i++) {
 | 
					        for (let i = 0; i < str.length; i++) {
 | 
				
			||||||
@ -167,14 +184,6 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
 | 
				
			|||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!node.expanded) {
 | 
					 | 
				
			||||||
            ctx.fillStyle =  this.css.textColor;
 | 
					 | 
				
			||||||
            ctx.font = 10 + 'px ' + this.css.fontFamily;
 | 
					 | 
				
			||||||
            ctx.textAlign = 'center';
 | 
					 | 
				
			||||||
            ctx.textBaseline = 'middle';
 | 
					 | 
				
			||||||
            ctx.fillText("+", x, y + 0.5);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        ctx.fillStyle = this.css.textColor;
 | 
					        ctx.fillStyle = this.css.textColor;
 | 
				
			||||||
        ctx.font = size + 'px ' + this.css.fontFamily;
 | 
					        ctx.font = size + 'px ' + this.css.fontFamily;
 | 
				
			||||||
        ctx.textAlign = 'center';
 | 
					        ctx.textAlign = 'center';
 | 
				
			||||||
@ -265,13 +274,14 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return {
 | 
					        this.nodes = resp.notes.map(([noteId, title, type]) => ({
 | 
				
			||||||
            nodes: resp.notes.map(([noteId, title, type]) => ({
 | 
					 | 
				
			||||||
            id: noteId,
 | 
					            id: noteId,
 | 
				
			||||||
            name: title,
 | 
					            name: title,
 | 
				
			||||||
            type: type,
 | 
					            type: type,
 | 
				
			||||||
                expanded: true
 | 
					        }));
 | 
				
			||||||
            })),
 | 
					
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            nodes: this.nodes,
 | 
				
			||||||
            links: Object.values(linksGroupedBySourceTarget).map(link => ({
 | 
					            links: Object.values(linksGroupedBySourceTarget).map(link => ({
 | 
				
			||||||
                id: link.id,
 | 
					                id: link.id,
 | 
				
			||||||
                source: link.sourceNoteId,
 | 
					                source: link.sourceNoteId,
 | 
				
			||||||
@ -295,11 +305,20 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    renderData(data, zoomToFit = true, zoomPadding = 10) {
 | 
					    renderData(data) {
 | 
				
			||||||
        this.graph.graphData(data);
 | 
					        this.graph.graphData(data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (zoomToFit && data.nodes.length > 1) {
 | 
					        if (this.widgetMode === 'ribbon') {
 | 
				
			||||||
            setTimeout(() => this.graph.zoomToFit(400, zoomPadding), 1000);
 | 
					            setTimeout(() => {
 | 
				
			||||||
 | 
					                const node = this.nodes.find(node => node.id === this.noteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                this.graph.centerAt(node.x, node.y, 500);
 | 
				
			||||||
 | 
					            }, 1000);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else if (this.widgetMode === 'type') {
 | 
				
			||||||
 | 
					            if (data.nodes.length > 1) {
 | 
				
			||||||
 | 
					                setTimeout(() => this.graph.zoomToFit(400, 10), 1000);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,8 +1,5 @@
 | 
				
			|||||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
 | 
					import NoteContextAwareWidget from "../note_context_aware_widget.js";
 | 
				
			||||||
import froca from "../../services/froca.js";
 | 
					import NoteMapWidget from "../note_map.js";
 | 
				
			||||||
import libraryLoader from "../../services/library_loader.js";
 | 
					 | 
				
			||||||
import server from "../../services/server.js";
 | 
					 | 
				
			||||||
import appContext from "../../services/app_context.js";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const TPL = `
 | 
					const TPL = `
 | 
				
			||||||
<div class="note-map-ribbon-widget">
 | 
					<div class="note-map-ribbon-widget">
 | 
				
			||||||
@ -33,11 +30,16 @@ const TPL = `
 | 
				
			|||||||
    <button class="bx bx-arrow-to-top icon-action collapse-button" style="display: none;" title="Collapse to normal size"></button>
 | 
					    <button class="bx bx-arrow-to-top icon-action collapse-button" style="display: none;" title="Collapse to normal size"></button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <div class="note-map-container"></div>
 | 
					    <div class="note-map-container"></div>
 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    <div class="style-resolver"></div>
 | 
					 | 
				
			||||||
</div>`;
 | 
					</div>`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class NoteMapRibbonWidget extends NoteContextAwareWidget {
 | 
					export default class NoteMapRibbonWidget extends NoteContextAwareWidget {
 | 
				
			||||||
 | 
					    constructor() {
 | 
				
			||||||
 | 
					        super();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.noteMapWidget = new NoteMapWidget('ribbon');
 | 
				
			||||||
 | 
					        this.child(this.noteMapWidget);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    get name() {
 | 
					    get name() {
 | 
				
			||||||
        return "noteMap";
 | 
					        return "noteMap";
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -62,6 +64,7 @@ export default class NoteMapRibbonWidget extends NoteContextAwareWidget {
 | 
				
			|||||||
        this.$widget = $(TPL);
 | 
					        this.$widget = $(TPL);
 | 
				
			||||||
        this.contentSized();
 | 
					        this.contentSized();
 | 
				
			||||||
        this.$container = this.$widget.find(".note-map-container");
 | 
					        this.$container = this.$widget.find(".note-map-container");
 | 
				
			||||||
 | 
					        this.$container.append(this.noteMapWidget.render());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.openState = 'small';
 | 
					        this.openState = 'small';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -73,6 +76,8 @@ export default class NoteMapRibbonWidget extends NoteContextAwareWidget {
 | 
				
			|||||||
            this.$collapseButton.show();
 | 
					            this.$collapseButton.show();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            this.openState = 'full';
 | 
					            this.openState = 'full';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.noteMapWidget.setHeight();
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.$collapseButton = this.$widget.find('.collapse-button');
 | 
					        this.$collapseButton = this.$widget.find('.collapse-button');
 | 
				
			||||||
@ -83,11 +88,10 @@ export default class NoteMapRibbonWidget extends NoteContextAwareWidget {
 | 
				
			|||||||
            this.$collapseButton.hide();
 | 
					            this.$collapseButton.hide();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            this.openState = 'small';
 | 
					            this.openState = 'small';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.noteMapWidget.setHeight();
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.$styleResolver = this.$widget.find('.style-resolver');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        window.addEventListener('resize', () => {
 | 
					        window.addEventListener('resize', () => {
 | 
				
			||||||
            if (!this.graph) { // no graph has been even rendered
 | 
					            if (!this.graph) { // no graph has been even rendered
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
@ -107,10 +111,6 @@ export default class NoteMapRibbonWidget extends NoteContextAwareWidget {
 | 
				
			|||||||
        const width = this.$widget.width();
 | 
					        const width = this.$widget.width();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.$widget.find('.note-map-container')
 | 
					        this.$widget.find('.note-map-container')
 | 
				
			||||||
            .css("height", SMALL_SIZE_HEIGHT)
 | 
					 | 
				
			||||||
            .css("width", width);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.graph
 | 
					 | 
				
			||||||
            .height(SMALL_SIZE_HEIGHT)
 | 
					            .height(SMALL_SIZE_HEIGHT)
 | 
				
			||||||
            .width(width);
 | 
					            .width(width);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -122,237 +122,7 @@ export default class NoteMapRibbonWidget extends NoteContextAwareWidget {
 | 
				
			|||||||
        const width = this.$widget.width();
 | 
					        const width = this.$widget.width();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.$widget.find('.note-map-container')
 | 
					        this.$widget.find('.note-map-container')
 | 
				
			||||||
            .css("height", height)
 | 
					 | 
				
			||||||
            .css("width", this.$widget.width());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.graph
 | 
					 | 
				
			||||||
            .height(height)
 | 
					            .height(height)
 | 
				
			||||||
            .width(width);
 | 
					            .width(width);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    setZoomLevel(level) {
 | 
					 | 
				
			||||||
        this.zoomLevel = level;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async refreshWithNote(note) {
 | 
					 | 
				
			||||||
        this.linkIdToLinkMap = {};
 | 
					 | 
				
			||||||
        this.noteIdToLinkCountMap = {};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.$container.empty();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.css = {
 | 
					 | 
				
			||||||
            fontFamily: this.$container.css("font-family"),
 | 
					 | 
				
			||||||
            textColor: this.rgb2hex(this.$container.css("color")),
 | 
					 | 
				
			||||||
            mutedTextColor: this.rgb2hex(this.$styleResolver.css("color"))
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await libraryLoader.requireLibrary(libraryLoader.FORCE_GRAPH);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        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(7)
 | 
					 | 
				
			||||||
            .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(() => this.css.mutedTextColor)
 | 
					 | 
				
			||||||
            .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(this.noteId,2));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    renderData(data, zoomToFit = true, zoomPadding = 10) {
 | 
					 | 
				
			||||||
        this.graph.graphData(data);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (zoomToFit && data.nodes.length > 1) {
 | 
					 | 
				
			||||||
            setTimeout(() => this.graph.zoomToFit(400, zoomPadding), 1000);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async nodeClicked(node) {
 | 
					 | 
				
			||||||
        if (!node.expanded) {
 | 
					 | 
				
			||||||
            this.renderData(
 | 
					 | 
				
			||||||
                await this.loadNotesAndRelations(node.id,1),
 | 
					 | 
				
			||||||
                false
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        else {
 | 
					 | 
				
			||||||
            await appContext.tabManager.getActiveContext().setNote(node.id);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async loadNotesAndRelations(noteId, maxDepth) {
 | 
					 | 
				
			||||||
        const resp = await server.post(`notes/${noteId}/link-map`, {
 | 
					 | 
				
			||||||
            maxNotes: 1000,
 | 
					 | 
				
			||||||
            maxDepth
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.noteIdToLinkCountMap = {...this.noteIdToLinkCountMap, ...resp.noteIdToLinkCountMap};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (const link of resp.links) {
 | 
					 | 
				
			||||||
            this.linkIdToLinkMap[link.id] = link;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // preload all notes
 | 
					 | 
				
			||||||
        const notes = await froca.getNotes(Object.keys(this.noteIdToLinkCountMap), true);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const noteIdToLinkIdMap = {};
 | 
					 | 
				
			||||||
        noteIdToLinkIdMap[this.noteId] = new Set(); // for case there are no relations
 | 
					 | 
				
			||||||
        const linksGroupedBySourceTarget = {};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (const link of Object.values(this.linkIdToLinkMap)) {
 | 
					 | 
				
			||||||
            noteIdToLinkIdMap[link.sourceNoteId] = noteIdToLinkIdMap[link.sourceNoteId] || new Set();
 | 
					 | 
				
			||||||
            noteIdToLinkIdMap[link.sourceNoteId].add(link.id);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            noteIdToLinkIdMap[link.targetNoteId] = noteIdToLinkIdMap[link.targetNoteId] || new Set();
 | 
					 | 
				
			||||||
            noteIdToLinkIdMap[link.targetNoteId].add(link.id);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            const key = `${link.sourceNoteId}-${link.targetNoteId}`;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (key in linksGroupedBySourceTarget) {
 | 
					 | 
				
			||||||
                if (!linksGroupedBySourceTarget[key].names.includes(link.name)) {
 | 
					 | 
				
			||||||
                    linksGroupedBySourceTarget[key].names.push(link.name);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            else {
 | 
					 | 
				
			||||||
                linksGroupedBySourceTarget[key] = {
 | 
					 | 
				
			||||||
                    id: key,
 | 
					 | 
				
			||||||
                    sourceNoteId: link.sourceNoteId,
 | 
					 | 
				
			||||||
                    targetNoteId: link.targetNoteId,
 | 
					 | 
				
			||||||
                    names: [link.name]
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return {
 | 
					 | 
				
			||||||
            nodes: notes.map(note => ({
 | 
					 | 
				
			||||||
                id: note.noteId,
 | 
					 | 
				
			||||||
                name: note.title,
 | 
					 | 
				
			||||||
                type: note.type,
 | 
					 | 
				
			||||||
                expanded: this.noteIdToLinkCountMap[note.noteId] === noteIdToLinkIdMap[note.noteId].size
 | 
					 | 
				
			||||||
            })),
 | 
					 | 
				
			||||||
            links: Object.values(linksGroupedBySourceTarget).map(link => ({
 | 
					 | 
				
			||||||
                id: link.id,
 | 
					 | 
				
			||||||
                source: link.sourceNoteId,
 | 
					 | 
				
			||||||
                target: link.targetNoteId,
 | 
					 | 
				
			||||||
                name: link.names.join(", ")
 | 
					 | 
				
			||||||
            }))
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    paintLink(link, ctx) {
 | 
					 | 
				
			||||||
        if (this.zoomLevel < 5) {
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        ctx.font = '3px ' + this.css.fontFamily;
 | 
					 | 
				
			||||||
        ctx.textAlign = 'center';
 | 
					 | 
				
			||||||
        ctx.textBaseline = 'middle';
 | 
					 | 
				
			||||||
        ctx.fillStyle = this.css.mutedTextColor;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        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 = node.id === this.noteId ? 'red' : color;
 | 
					 | 
				
			||||||
        ctx.beginPath();
 | 
					 | 
				
			||||||
        ctx.arc(x, y, node.id === this.noteId ? 8 : 4, 0, 2 * Math.PI, false);
 | 
					 | 
				
			||||||
        ctx.fill();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (this.zoomLevel < 2) {
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (!node.expanded) {
 | 
					 | 
				
			||||||
            ctx.fillStyle =  this.css.textColor;
 | 
					 | 
				
			||||||
            ctx.font = 10 + 'px ' + this.css.fontFamily;
 | 
					 | 
				
			||||||
            ctx.textAlign = 'center';
 | 
					 | 
				
			||||||
            ctx.textBaseline = 'middle';
 | 
					 | 
				
			||||||
            ctx.fillText("+", x, y + 0.5);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        ctx.fillStyle = this.css.textColor;
 | 
					 | 
				
			||||||
        ctx.font = 5 + 'px ' + this.css.fontFamily;
 | 
					 | 
				
			||||||
        ctx.textAlign = 'center';
 | 
					 | 
				
			||||||
        ctx.textBaseline = 'middle';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let title = node.name;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (title.length > 15) {
 | 
					 | 
				
			||||||
            title = title.substr(0, 15) + "...";
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        ctx.fillText(title, x, y + (node.id === this.noteId ? 11 : 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;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    rgb2hex(rgb) {
 | 
					 | 
				
			||||||
        return `#${rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/)
 | 
					 | 
				
			||||||
            .slice(1)
 | 
					 | 
				
			||||||
            .map(n => parseInt(n, 10).toString(16).padStart(2, '0'))
 | 
					 | 
				
			||||||
            .join('')}`
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    entitiesReloadedEvent({loadResults}) {
 | 
					 | 
				
			||||||
        if (loadResults.getAttributes().find(attr => attr.type === 'relation' && (attr.noteId === this.noteId || attr.value === this.noteId))) {
 | 
					 | 
				
			||||||
            this.refresh();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -9,7 +9,7 @@ export default class NoteMapTypeWidget extends TypeWidget {
 | 
				
			|||||||
    constructor() {
 | 
					    constructor() {
 | 
				
			||||||
        super();
 | 
					        super();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.noteMapWidget = new NoteMapWidget();
 | 
					        this.noteMapWidget = new NoteMapWidget('type');
 | 
				
			||||||
        this.child(this.noteMapWidget);
 | 
					        this.child(this.noteMapWidget);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -21,8 +21,6 @@ export default class NoteMapTypeWidget extends TypeWidget {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async doRefresh(note) {
 | 
					    async doRefresh(note) {
 | 
				
			||||||
        console.log("isEnabled", this.noteMapWidget.isEnabled());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await this.noteMapWidget.refresh();
 | 
					        await this.noteMapWidget.refresh();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user