| 
									
										
										
										
											2021-05-28 23:19:11 +02:00
										 |  |  | import NoteContextAwareWidget from "../note_context_aware_widget.js"; | 
					
						
							|  |  |  | import froca from "../../services/froca.js"; | 
					
						
							| 
									
										
										
										
											2021-05-31 21:31:07 +02:00
										 |  |  | import libraryLoader from "../../services/library_loader.js"; | 
					
						
							|  |  |  | import server from "../../services/server.js"; | 
					
						
							| 
									
										
										
										
											2021-06-01 22:03:38 +02:00
										 |  |  | import appContext from "../../services/app_context.js"; | 
					
						
							| 
									
										
										
										
											2021-05-28 23:19:11 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | const TPL = `
 | 
					
						
							|  |  |  | <div class="link-map-widget"> | 
					
						
							| 
									
										
										
										
											2021-05-28 23:52:42 +02:00
										 |  |  |     <style> | 
					
						
							|  |  |  |         .link-map-widget { | 
					
						
							|  |  |  |             position: relative; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-05-29 22:52:32 +02:00
										 |  |  |          | 
					
						
							|  |  |  |         .link-map-container { | 
					
						
							| 
									
										
										
										
											2021-05-30 23:21:34 +02:00
										 |  |  |             height: 300px; | 
					
						
							| 
									
										
										
										
											2021-05-29 22:52:32 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-05-28 23:52:42 +02:00
										 |  |  |      | 
					
						
							| 
									
										
										
										
											2021-05-31 21:20:30 +02:00
										 |  |  |         .open-full-button, .collapse-button { | 
					
						
							| 
									
										
										
										
											2021-05-28 23:52:42 +02:00
										 |  |  |             position: absolute; | 
					
						
							| 
									
										
										
										
											2021-05-31 21:20:30 +02:00
										 |  |  |             right: 5px; | 
					
						
							|  |  |  |             bottom: 5px; | 
					
						
							|  |  |  |             font-size: 180%; | 
					
						
							| 
									
										
										
										
											2021-05-28 23:52:42 +02:00
										 |  |  |             z-index: 1000; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-06-02 21:23:40 +02:00
										 |  |  |          | 
					
						
							|  |  |  |         .style-resolver { | 
					
						
							|  |  |  |             color: var(--muted-text-color); | 
					
						
							|  |  |  |             display: none; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-05-28 23:52:42 +02:00
										 |  |  |     </style> | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-31 21:20:30 +02:00
										 |  |  |     <button class="bx bx-arrow-to-bottom icon-action open-full-button" title="Open full"></button> | 
					
						
							|  |  |  |     <button class="bx bx-arrow-to-top icon-action collapse-button" style="display: none;" title="Collapse to normal size"></button> | 
					
						
							| 
									
										
										
										
											2021-05-28 23:52:42 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-30 23:21:34 +02:00
										 |  |  |     <div class="link-map-container"></div> | 
					
						
							| 
									
										
										
										
											2021-06-02 21:23:40 +02:00
										 |  |  |      | 
					
						
							|  |  |  |     <div class="style-resolver"></div> | 
					
						
							| 
									
										
										
										
											2021-05-28 23:19:11 +02:00
										 |  |  | </div>`; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export default class LinkMapWidget extends NoteContextAwareWidget { | 
					
						
							| 
									
										
										
										
											2021-06-27 12:53:05 +02:00
										 |  |  |     get name() { | 
					
						
							|  |  |  |         return "linkMap"; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-28 23:19:11 +02:00
										 |  |  |     isEnabled() { | 
					
						
							|  |  |  |         return this.note; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     getTitle() { | 
					
						
							|  |  |  |         return { | 
					
						
							|  |  |  |             show: this.isEnabled(), | 
					
						
							|  |  |  |             title: 'Link Map', | 
					
						
							|  |  |  |             icon: 'bx bx-network-chart' | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     doRender() { | 
					
						
							|  |  |  |         this.$widget = $(TPL); | 
					
						
							| 
									
										
										
										
											2021-06-13 22:55:31 +02:00
										 |  |  |         this.contentSized(); | 
					
						
							| 
									
										
										
										
											2021-05-31 21:31:07 +02:00
										 |  |  |         this.$container = this.$widget.find(".link-map-container"); | 
					
						
							| 
									
										
										
										
											2021-05-31 21:20:30 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-31 23:38:47 +02:00
										 |  |  |         this.openState = 'small'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-31 21:20:30 +02:00
										 |  |  |         this.$openFullButton = this.$widget.find('.open-full-button'); | 
					
						
							|  |  |  |         this.$openFullButton.on('click', () => { | 
					
						
							| 
									
										
										
										
											2021-05-31 23:38:47 +02:00
										 |  |  |             this.setFullHeight(); | 
					
						
							| 
									
										
										
										
											2021-05-31 21:20:30 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             this.$openFullButton.hide(); | 
					
						
							|  |  |  |             this.$collapseButton.show(); | 
					
						
							| 
									
										
										
										
											2021-05-31 23:38:47 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             this.openState = 'full'; | 
					
						
							| 
									
										
										
										
											2021-05-31 21:20:30 +02:00
										 |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this.$collapseButton = this.$widget.find('.collapse-button'); | 
					
						
							|  |  |  |         this.$collapseButton.on('click', () => { | 
					
						
							| 
									
										
										
										
											2021-05-31 23:38:47 +02:00
										 |  |  |             this.setSmallSize(); | 
					
						
							| 
									
										
										
										
											2021-05-31 21:20:30 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             this.$openFullButton.show(); | 
					
						
							|  |  |  |             this.$collapseButton.hide(); | 
					
						
							| 
									
										
										
										
											2021-05-31 23:38:47 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             this.openState = 'small'; | 
					
						
							| 
									
										
										
										
											2021-05-31 21:20:30 +02:00
										 |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-02 21:23:40 +02:00
										 |  |  |         this.$styleResolver = this.$widget.find('.style-resolver'); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-31 23:38:47 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         window.addEventListener('resize', () => { | 
					
						
							|  |  |  |             if (!this.graph) { // no graph has been even rendered
 | 
					
						
							|  |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (this.openState === 'full') { | 
					
						
							|  |  |  |                 this.setFullHeight(); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             else if (this.openState === 'small') { | 
					
						
							|  |  |  |                 this.setSmallSize(); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }, false); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     setSmallSize() { | 
					
						
							|  |  |  |         const SMALL_SIZE_HEIGHT = 300; | 
					
						
							|  |  |  |         const width = this.$widget.width(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this.$widget.find('.link-map-container') | 
					
						
							|  |  |  |             .css("height", SMALL_SIZE_HEIGHT) | 
					
						
							|  |  |  |             .css("width", width); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this.graph | 
					
						
							|  |  |  |             .height(SMALL_SIZE_HEIGHT) | 
					
						
							|  |  |  |             .width(width); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     setFullHeight() { | 
					
						
							|  |  |  |         const {top} = this.$widget[0].getBoundingClientRect(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const height = $(window).height() - top; | 
					
						
							|  |  |  |         const width = this.$widget.width(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this.$widget.find('.link-map-container') | 
					
						
							|  |  |  |             .css("height", height) | 
					
						
							|  |  |  |             .css("width", this.$widget.width()); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this.graph | 
					
						
							|  |  |  |             .height(height) | 
					
						
							|  |  |  |             .width(width); | 
					
						
							| 
									
										
										
										
											2021-05-28 23:19:11 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-31 21:31:07 +02:00
										 |  |  |     setZoomLevel(level) { | 
					
						
							|  |  |  |         this.zoomLevel = level; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-28 23:19:11 +02:00
										 |  |  |     async refreshWithNote(note) { | 
					
						
							| 
									
										
										
										
											2021-06-01 22:03:38 +02:00
										 |  |  |         this.linkIdToLinkMap = {}; | 
					
						
							|  |  |  |         this.noteIdToLinkCountMap = {}; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-31 21:31:07 +02:00
										 |  |  |         this.$container.empty(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-02 21:23:40 +02:00
										 |  |  |         this.css = { | 
					
						
							|  |  |  |             fontFamily: this.$container.css("font-family"), | 
					
						
							|  |  |  |             textColor: this.rgb2hex(this.$container.css("color")), | 
					
						
							|  |  |  |             mutedTextColor: this.rgb2hex(this.$styleResolver.css("color")) | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-31 21:31:07 +02:00
										 |  |  |         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) | 
					
						
							| 
									
										
										
										
											2021-05-31 23:38:47 +02:00
										 |  |  |             .maxZoom(7) | 
					
						
							| 
									
										
										
										
											2021-05-31 21:31:07 +02:00
										 |  |  |             .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) | 
					
						
							| 
									
										
										
										
											2021-06-02 21:23:40 +02:00
										 |  |  |             .linkColor(() => this.css.mutedTextColor) | 
					
						
							| 
									
										
										
										
											2021-05-31 21:31:07 +02:00
										 |  |  |             .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); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-02 21:39:18 +02:00
										 |  |  |         this.renderData(await this.loadNotesAndRelations(this.noteId,2)); | 
					
						
							| 
									
										
										
										
											2021-05-31 21:31:07 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     renderData(data, zoomToFit = true, zoomPadding = 10) { | 
					
						
							|  |  |  |         this.graph.graphData(data); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-13 22:55:31 +02:00
										 |  |  |         if (zoomToFit && data.nodes.length > 1) { | 
					
						
							| 
									
										
										
										
											2021-05-31 21:31:07 +02:00
										 |  |  |             setTimeout(() => this.graph.zoomToFit(400, zoomPadding), 1000); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     async nodeClicked(node) { | 
					
						
							|  |  |  |         if (!node.expanded) { | 
					
						
							| 
									
										
										
										
											2021-06-01 22:03:38 +02:00
										 |  |  |             this.renderData( | 
					
						
							|  |  |  |                 await this.loadNotesAndRelations(node.id,1), | 
					
						
							|  |  |  |                 false | 
					
						
							|  |  |  |             ); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         else { | 
					
						
							|  |  |  |             await appContext.tabManager.getActiveContext().setNote(node.id); | 
					
						
							| 
									
										
										
										
											2021-05-31 21:31:07 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-05-28 23:19:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-01 22:03:38 +02:00
										 |  |  |     async loadNotesAndRelations(noteId, maxDepth) { | 
					
						
							|  |  |  |         const resp = await server.post(`notes/${noteId}/link-map`, { | 
					
						
							|  |  |  |             maxNotes: 1000, | 
					
						
							|  |  |  |             maxDepth | 
					
						
							| 
									
										
										
										
											2021-05-28 23:19:11 +02:00
										 |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-01 22:03:38 +02:00
										 |  |  |         this.noteIdToLinkCountMap = {...this.noteIdToLinkCountMap, ...resp.noteIdToLinkCountMap}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for (const link of resp.links) { | 
					
						
							|  |  |  |             this.linkIdToLinkMap[link.id] = link; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-31 23:38:47 +02:00
										 |  |  |         // preload all notes
 | 
					
						
							| 
									
										
										
										
											2021-06-01 22:03:38 +02:00
										 |  |  |         const notes = await froca.getNotes(Object.keys(this.noteIdToLinkCountMap), true); | 
					
						
							| 
									
										
										
										
											2021-05-31 23:38:47 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-01 22:03:38 +02:00
										 |  |  |         const noteIdToLinkIdMap = {}; | 
					
						
							| 
									
										
										
										
											2021-06-03 12:47:13 +02:00
										 |  |  |         noteIdToLinkIdMap[this.noteId] = new Set(); // for case there are no relations
 | 
					
						
							| 
									
										
										
										
											2021-06-01 22:03:38 +02:00
										 |  |  |         const linksGroupedBySourceTarget = {}; | 
					
						
							| 
									
										
										
										
											2021-05-31 21:31:07 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-01 22:03:38 +02:00
										 |  |  |         for (const link of Object.values(this.linkIdToLinkMap)) { | 
					
						
							|  |  |  |             noteIdToLinkIdMap[link.sourceNoteId] = noteIdToLinkIdMap[link.sourceNoteId] || new Set(); | 
					
						
							|  |  |  |             noteIdToLinkIdMap[link.sourceNoteId].add(link.id); | 
					
						
							| 
									
										
										
										
											2021-05-31 23:38:47 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-01 22:03:38 +02:00
										 |  |  |             noteIdToLinkIdMap[link.targetNoteId] = noteIdToLinkIdMap[link.targetNoteId] || new Set(); | 
					
						
							|  |  |  |             noteIdToLinkIdMap[link.targetNoteId].add(link.id); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const key = `${link.sourceNoteId}-${link.targetNoteId}`; | 
					
						
							| 
									
										
										
										
											2021-05-31 21:31:07 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-01 22:03:38 +02:00
										 |  |  |             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] | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-05-31 21:31:07 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         return { | 
					
						
							|  |  |  |             nodes: notes.map(note => ({ | 
					
						
							|  |  |  |                 id: note.noteId, | 
					
						
							|  |  |  |                 name: note.title, | 
					
						
							| 
									
										
										
										
											2021-05-31 23:38:47 +02:00
										 |  |  |                 type: note.type, | 
					
						
							| 
									
										
										
										
											2021-06-01 22:03:38 +02:00
										 |  |  |                 expanded: this.noteIdToLinkCountMap[note.noteId] === noteIdToLinkIdMap[note.noteId].size | 
					
						
							| 
									
										
										
										
											2021-05-31 21:31:07 +02:00
										 |  |  |             })), | 
					
						
							| 
									
										
										
										
											2021-06-01 22:03:38 +02:00
										 |  |  |             links: Object.values(linksGroupedBySourceTarget).map(link => ({ | 
					
						
							|  |  |  |                 id: link.id, | 
					
						
							| 
									
										
										
										
											2021-05-31 23:38:47 +02:00
										 |  |  |                 source: link.sourceNoteId, | 
					
						
							| 
									
										
										
										
											2021-05-31 21:31:07 +02:00
										 |  |  |                 target: link.targetNoteId, | 
					
						
							| 
									
										
										
										
											2021-06-01 22:03:38 +02:00
										 |  |  |                 name: link.names.join(", ") | 
					
						
							| 
									
										
										
										
											2021-05-31 21:31:07 +02:00
										 |  |  |             })) | 
					
						
							|  |  |  |         }; | 
					
						
							| 
									
										
										
										
											2021-05-28 23:19:11 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-31 21:31:07 +02:00
										 |  |  |     paintLink(link, ctx) { | 
					
						
							| 
									
										
										
										
											2021-06-02 21:39:18 +02:00
										 |  |  |         if (this.zoomLevel < 5) { | 
					
						
							| 
									
										
										
										
											2021-05-31 21:31:07 +02:00
										 |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-02 21:23:40 +02:00
										 |  |  |         ctx.font = '3px ' + this.css.fontFamily; | 
					
						
							| 
									
										
										
										
											2021-05-31 21:31:07 +02:00
										 |  |  |         ctx.textAlign = 'center'; | 
					
						
							|  |  |  |         ctx.textBaseline = 'middle'; | 
					
						
							| 
									
										
										
										
											2021-06-02 21:23:40 +02:00
										 |  |  |         ctx.fillStyle = this.css.mutedTextColor; | 
					
						
							| 
									
										
										
										
											2021-05-31 21:31:07 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         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; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-01 22:03:38 +02:00
										 |  |  |         ctx.fillStyle = node.id === this.noteId ? 'red' : color; | 
					
						
							| 
									
										
										
										
											2021-05-31 21:31:07 +02:00
										 |  |  |         ctx.beginPath(); | 
					
						
							| 
									
										
										
										
											2021-06-01 22:03:38 +02:00
										 |  |  |         ctx.arc(x, y, node.id === this.noteId ? 8 : 4, 0, 2 * Math.PI, false); | 
					
						
							| 
									
										
										
										
											2021-05-31 21:31:07 +02:00
										 |  |  |         ctx.fill(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (this.zoomLevel < 2) { | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (!node.expanded) { | 
					
						
							| 
									
										
										
										
											2021-06-02 21:23:40 +02:00
										 |  |  |             ctx.fillStyle =  this.css.textColor; | 
					
						
							|  |  |  |             ctx.font = 10 + 'px ' + this.css.fontFamily; | 
					
						
							| 
									
										
										
										
											2021-05-31 21:31:07 +02:00
										 |  |  |             ctx.textAlign = 'center'; | 
					
						
							|  |  |  |             ctx.textBaseline = 'middle'; | 
					
						
							| 
									
										
										
										
											2021-05-31 23:38:47 +02:00
										 |  |  |             ctx.fillText("+", x, y + 0.5); | 
					
						
							| 
									
										
										
										
											2021-05-31 21:31:07 +02:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-02 21:23:40 +02:00
										 |  |  |         ctx.fillStyle = this.css.textColor; | 
					
						
							|  |  |  |         ctx.font = 5 + 'px ' + this.css.fontFamily; | 
					
						
							| 
									
										
										
										
											2021-05-31 21:31:07 +02:00
										 |  |  |         ctx.textAlign = 'center'; | 
					
						
							|  |  |  |         ctx.textBaseline = 'middle'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         let title = node.name; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (title.length > 15) { | 
					
						
							|  |  |  |             title = title.substr(0, 15) + "..."; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-01 22:03:38 +02:00
										 |  |  |         ctx.fillText(title, x, y + (node.id === this.noteId ? 11 : 7)); | 
					
						
							| 
									
										
										
										
											2021-05-31 21:31:07 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     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); | 
					
						
							| 
									
										
										
										
											2021-05-28 23:19:11 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-05-31 21:31:07 +02:00
										 |  |  |         return colour; | 
					
						
							| 
									
										
										
										
											2021-05-28 23:19:11 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-02 21:23:40 +02:00
										 |  |  |     rgb2hex(rgb) { | 
					
						
							|  |  |  |         return `#${rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/) | 
					
						
							|  |  |  |             .slice(1) | 
					
						
							|  |  |  |             .map(n => parseInt(n, 10).toString(16).padStart(2, '0')) | 
					
						
							|  |  |  |             .join('')}`
 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-28 23:19:11 +02:00
										 |  |  |     entitiesReloadedEvent({loadResults}) { | 
					
						
							|  |  |  |         if (loadResults.getAttributes().find(attr => attr.type === 'relation' && (attr.noteId === this.noteId || attr.value === this.noteId))) { | 
					
						
							| 
									
										
										
										
											2021-06-01 22:03:38 +02:00
										 |  |  |             this.refresh(); | 
					
						
							| 
									
										
										
										
											2021-05-28 23:19:11 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } |