mirror of
				https://github.com/TriliumNext/Notes.git
				synced 2025-10-31 21:11:30 +08:00 
			
		
		
		
	extracted link map into a service
This commit is contained in:
		
							parent
							
								
									48a654630f
								
							
						
					
					
						commit
						ca2f14a2d0
					
				
							
								
								
									
										198
									
								
								src/public/javascripts/services/link_map.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										198
									
								
								src/public/javascripts/services/link_map.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,198 @@ | |||||||
|  | import libraryLoader from "./library_loader.js"; | ||||||
|  | import server from "./server.js"; | ||||||
|  | import treeCache from "./tree_cache.js"; | ||||||
|  | import linkService from "./link.js"; | ||||||
|  | 
 | ||||||
|  | const linkOverlays = [ | ||||||
|  |     [ "Arrow", { | ||||||
|  |         location: 1, | ||||||
|  |         id: "arrow", | ||||||
|  |         length: 10, | ||||||
|  |         width: 10, | ||||||
|  |         foldback: 0.7 | ||||||
|  |     } ] | ||||||
|  | ]; | ||||||
|  | 
 | ||||||
|  | export default class LinkMap { | ||||||
|  |     constructor(note, $linkMapContainer) { | ||||||
|  |         this.note = note; | ||||||
|  |         this.$linkMapContainer = $linkMapContainer; | ||||||
|  |         this.linkMapContainerId = this.$linkMapContainer.attr("id"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async render() { | ||||||
|  |         await libraryLoader.requireLibrary(libraryLoader.LINK_MAP); | ||||||
|  | 
 | ||||||
|  |         jsPlumb.ready(() => { | ||||||
|  |             this.initJsPlumbInstance(); | ||||||
|  | 
 | ||||||
|  |             this.initPanZoom(); | ||||||
|  | 
 | ||||||
|  |             this.loadNotesAndRelations(); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async loadNotesAndRelations() { | ||||||
|  |         this.cleanup(); | ||||||
|  | 
 | ||||||
|  |         const maxNotes = 50; | ||||||
|  | 
 | ||||||
|  |         const currentNoteId = this.note.noteId; | ||||||
|  | 
 | ||||||
|  |         const links = await server.post(`notes/${currentNoteId}/link-map`, { | ||||||
|  |             maxNotes, | ||||||
|  |             maxDepth: 1 | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         const noteIds = new Set(links.map(l => l.noteId).concat(links.map(l => l.targetNoteId))); | ||||||
|  | 
 | ||||||
|  |         if (noteIds.size === 0) { | ||||||
|  |             noteIds.add(currentNoteId); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // preload all notes
 | ||||||
|  |         const notes = await treeCache.getNotes(Array.from(noteIds)); | ||||||
|  | 
 | ||||||
|  |         const graph = new Springy.Graph(); | ||||||
|  |         graph.addNodes(...noteIds); | ||||||
|  |         graph.addEdges(...links.map(l => [l.noteId, l.targetNoteId])); | ||||||
|  | 
 | ||||||
|  |         const layout = new Springy.Layout.ForceDirected( | ||||||
|  |             graph, | ||||||
|  |             400.0, // Spring stiffness
 | ||||||
|  |             400.0, // Node repulsion
 | ||||||
|  |             0.5 // Damping
 | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         const getNoteBox = noteId => { | ||||||
|  |             const noteBoxId = this.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") | ||||||
|  |                 .prop("id", noteBoxId); | ||||||
|  | 
 | ||||||
|  |             linkService.createNoteLink(noteId, note.title).then($link => { | ||||||
|  |                 $noteBox.append($("<span>").addClass("title").append($link)); | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             if (noteId === currentNoteId) { | ||||||
|  |                 $noteBox.addClass("link-map-active-note"); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             this.$linkMapContainer.append($noteBox); | ||||||
|  | 
 | ||||||
|  |             this.jsPlumbInstance.draggable($noteBox[0], { | ||||||
|  |                 start: params => { | ||||||
|  |                     renderer.stop(); | ||||||
|  |                 }, | ||||||
|  |                 drag: params => {}, | ||||||
|  |                 stop: params => {} | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |             return $noteBox; | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         this.renderer = new Springy.Renderer( | ||||||
|  |             layout, | ||||||
|  |             () => {}, | ||||||
|  |             (edge, p1, p2) => { | ||||||
|  |                 const connectionId = edge.source.id + '-' + edge.target.id; | ||||||
|  | 
 | ||||||
|  |                 if ($("#" + connectionId).length > 0) { | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 getNoteBox(edge.source.id); | ||||||
|  |                 getNoteBox(edge.target.id); | ||||||
|  | 
 | ||||||
|  |                 const connection = this.jsPlumbInstance.connect({ | ||||||
|  |                     source: this.noteIdToId(edge.source.id), | ||||||
|  |                     target: this.noteIdToId(edge.target.id), | ||||||
|  |                     type: 'link' | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |                 connection.canvas.id = connectionId; | ||||||
|  |             }, | ||||||
|  |             (node, p) => { | ||||||
|  |                 const $noteBox = getNoteBox(node.id); | ||||||
|  |                 const middleW = this.$linkMapContainer.width() / 2; | ||||||
|  |                 const middleH = this.$linkMapContainer.height() / 2; | ||||||
|  | 
 | ||||||
|  |                 $noteBox | ||||||
|  |                     .css("left", (middleW + p.x * 100) + "px") | ||||||
|  |                     .css("top", (middleH + p.y * 100) + "px"); | ||||||
|  |             }, | ||||||
|  |             () => {}, | ||||||
|  |             () => {}, | ||||||
|  |             () => { | ||||||
|  |                 this.jsPlumbInstance.repaintEverything(); | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         this.renderer.start(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     initPanZoom() { | ||||||
|  |         if (this.pzInstance) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         this.pzInstance = panzoom(this.$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; | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     cleanup() { | ||||||
|  |         if (this.renderer) { | ||||||
|  |             this.renderer.stop(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // delete all endpoints and connections
 | ||||||
|  |         // this is done at this point (after async operations) to reduce flicker to the minimum
 | ||||||
|  |         this.jsPlumbInstance.deleteEveryEndpoint(); | ||||||
|  | 
 | ||||||
|  |         // without this we still end up with note boxes remaining in the canvas
 | ||||||
|  |         this.$linkMapContainer.empty(); | ||||||
|  | 
 | ||||||
|  |         // reset zoom/pan
 | ||||||
|  |         this.pzInstance.zoomTo(0, 0, 0.7); | ||||||
|  |         this.pzInstance.moveTo(0, 0); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     initJsPlumbInstance() { | ||||||
|  |         if (this.jsPlumbInstance) { | ||||||
|  |             this.cleanup(); | ||||||
|  | 
 | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         this.jsPlumbInstance = jsPlumb.getInstance({ | ||||||
|  |             Endpoint: ["Blank", {}], | ||||||
|  |             ConnectionOverlays: linkOverlays, | ||||||
|  |             PaintStyle: { stroke: "var(--muted-text-color)", strokeWidth: 1 }, | ||||||
|  |             HoverPaintStyle: { stroke: "var(--main-text-color)", strokeWidth: 1 }, | ||||||
|  |             Container: this.$linkMapContainer.attr("id") | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         this.jsPlumbInstance.registerConnectionType("link", { anchor: "Continuous", connector: "Straight", overlays: linkOverlays }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     noteIdToId(noteId) { | ||||||
|  |         return this.linkMapContainerId + "-note-" + noteId; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -1,7 +1,3 @@ | |||||||
| import libraryLoader from "../services/library_loader.js"; |  | ||||||
| import server from "../services/server.js"; |  | ||||||
| import treeCache from "../services/tree_cache.js"; |  | ||||||
| import linkService from "../services/link.js"; |  | ||||||
| import StandardWidget from "./standard_widget.js"; | import StandardWidget from "./standard_widget.js"; | ||||||
| 
 | 
 | ||||||
| let linkMapContainerIdCtr = 1; | let linkMapContainerIdCtr = 1; | ||||||
| @ -12,16 +8,6 @@ const TPL = ` | |||||||
| </div> | </div> | ||||||
| `;
 | `;
 | ||||||
| 
 | 
 | ||||||
| const linkOverlays = [ |  | ||||||
|     [ "Arrow", { |  | ||||||
|         location: 1, |  | ||||||
|         id: "arrow", |  | ||||||
|         length: 10, |  | ||||||
|         width: 10, |  | ||||||
|         foldback: 0.7 |  | ||||||
|     } ] |  | ||||||
| ]; |  | ||||||
| 
 |  | ||||||
| class LinkMapWidget extends StandardWidget { | class LinkMapWidget extends StandardWidget { | ||||||
|     getWidgetTitle() { return "Link map"; } |     getWidgetTitle() { return "Link map"; } | ||||||
| 
 | 
 | ||||||
| @ -38,182 +24,14 @@ class LinkMapWidget extends StandardWidget { | |||||||
|     async doRenderBody() { |     async doRenderBody() { | ||||||
|         this.$body.html(TPL); |         this.$body.html(TPL); | ||||||
| 
 | 
 | ||||||
|         this.$linkMapContainer = this.$body.find('.link-map-container'); |         const $linkMapContainer = this.$body.find('.link-map-container'); | ||||||
|         this.$linkMapContainer.attr("id", "link-map-container-" + linkMapContainerIdCtr++); |         $linkMapContainer.attr("id", "link-map-container-" + linkMapContainerIdCtr++); | ||||||
| 
 | 
 | ||||||
|         await libraryLoader.requireLibrary(libraryLoader.LINK_MAP); |         const LinkMapServiceClass = (await import('../services/link_map.js')).default; | ||||||
| 
 | 
 | ||||||
|         jsPlumb.ready(() => { |         const linkMapService = new LinkMapServiceClass(this.ctx.note, $linkMapContainer); | ||||||
|             this.initJsPlumbInstance(); |  | ||||||
| 
 | 
 | ||||||
|             this.initPanZoom(); |         await linkMapService.render(); | ||||||
| 
 |  | ||||||
|             this.loadNotesAndRelations(); |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     async loadNotesAndRelations() { |  | ||||||
|         this.cleanup(); |  | ||||||
| 
 |  | ||||||
|         const maxNotes = 50; |  | ||||||
| 
 |  | ||||||
|         const currentNoteId = this.ctx.note.noteId; |  | ||||||
| 
 |  | ||||||
|         const links = await server.post(`notes/${currentNoteId}/link-map`, { |  | ||||||
|             maxNotes, |  | ||||||
|             maxDepth: 1 |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|         const noteIds = new Set(links.map(l => l.noteId).concat(links.map(l => l.targetNoteId))); |  | ||||||
| 
 |  | ||||||
|         if (noteIds.size === 0) { |  | ||||||
|             noteIds.add(currentNoteId); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // preload all notes
 |  | ||||||
|         const notes = await treeCache.getNotes(Array.from(noteIds)); |  | ||||||
| 
 |  | ||||||
|         const graph = new Springy.Graph(); |  | ||||||
|         graph.addNodes(...noteIds); |  | ||||||
|         graph.addEdges(...links.map(l => [l.noteId, l.targetNoteId])); |  | ||||||
| 
 |  | ||||||
|         const layout = new Springy.Layout.ForceDirected( |  | ||||||
|             graph, |  | ||||||
|             400.0, // Spring stiffness
 |  | ||||||
|             400.0, // Node repulsion
 |  | ||||||
|             0.5 // Damping
 |  | ||||||
|         ); |  | ||||||
| 
 |  | ||||||
|         const getNoteBox = noteId => { |  | ||||||
|             const noteBoxId = this.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") |  | ||||||
|                 .prop("id", noteBoxId); |  | ||||||
| 
 |  | ||||||
|             linkService.createNoteLink(noteId, note.title).then($link => { |  | ||||||
|                 $noteBox.append($("<span>").addClass("title").append($link)); |  | ||||||
|             }); |  | ||||||
| 
 |  | ||||||
|             if (noteId === currentNoteId) { |  | ||||||
|                 $noteBox.addClass("link-map-active-note"); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             this.$linkMapContainer.append($noteBox); |  | ||||||
| 
 |  | ||||||
|             this.jsPlumbInstance.draggable($noteBox[0], { |  | ||||||
|                 start: params => { |  | ||||||
|                     renderer.stop(); |  | ||||||
|                 }, |  | ||||||
|                 drag: params => {}, |  | ||||||
|                 stop: params => {} |  | ||||||
|             }); |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|             return $noteBox; |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         this.renderer = new Springy.Renderer( |  | ||||||
|             layout, |  | ||||||
|             () => {}, |  | ||||||
|             (edge, p1, p2) => { |  | ||||||
|                 const connectionId = edge.source.id + '-' + edge.target.id; |  | ||||||
| 
 |  | ||||||
|                 if ($("#" + connectionId).length > 0) { |  | ||||||
|                     return; |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 getNoteBox(edge.source.id); |  | ||||||
|                 getNoteBox(edge.target.id); |  | ||||||
| 
 |  | ||||||
|                 const connection = this.jsPlumbInstance.connect({ |  | ||||||
|                     source: this.noteIdToId(edge.source.id), |  | ||||||
|                     target: this.noteIdToId(edge.target.id), |  | ||||||
|                     type: 'link' |  | ||||||
|                 }); |  | ||||||
| 
 |  | ||||||
|                 connection.canvas.id = connectionId; |  | ||||||
|             }, |  | ||||||
|             (node, p) => { |  | ||||||
|                 const $noteBox = getNoteBox(node.id); |  | ||||||
|                 const middleW = this.$linkMapContainer.width() / 2; |  | ||||||
|                 const middleH = this.$linkMapContainer.height() / 2; |  | ||||||
| 
 |  | ||||||
|                 $noteBox |  | ||||||
|                     .css("left", (middleW + p.x * 100) + "px") |  | ||||||
|                     .css("top", (middleH + p.y * 100) + "px"); |  | ||||||
|             }, |  | ||||||
|             () => {}, |  | ||||||
|             () => {}, |  | ||||||
|             () => { |  | ||||||
|                 this.jsPlumbInstance.repaintEverything(); |  | ||||||
|             } |  | ||||||
|         ); |  | ||||||
| 
 |  | ||||||
|         this.renderer.start(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     initPanZoom() { |  | ||||||
|         if (this.pzInstance) { |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         this.pzInstance = panzoom(this.$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; |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     cleanup() { |  | ||||||
|         if (this.renderer) { |  | ||||||
|             this.renderer.stop(); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // delete all endpoints and connections
 |  | ||||||
|         // this is done at this point (after async operations) to reduce flicker to the minimum
 |  | ||||||
|         this.jsPlumbInstance.deleteEveryEndpoint(); |  | ||||||
| 
 |  | ||||||
|         // without this we still end up with note boxes remaining in the canvas
 |  | ||||||
|         this.$linkMapContainer.empty(); |  | ||||||
| 
 |  | ||||||
|         // reset zoom/pan
 |  | ||||||
|         this.pzInstance.zoomTo(0, 0, 0.7); |  | ||||||
|         this.pzInstance.moveTo(0, 0); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     initJsPlumbInstance() { |  | ||||||
|         if (this.jsPlumbInstance) { |  | ||||||
|             this.cleanup(); |  | ||||||
| 
 |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         this.jsPlumbInstance = jsPlumb.getInstance({ |  | ||||||
|             Endpoint: ["Blank", {}], |  | ||||||
|             ConnectionOverlays: linkOverlays, |  | ||||||
|             PaintStyle: { stroke: "var(--muted-text-color)", strokeWidth: 1 }, |  | ||||||
|             HoverPaintStyle: { stroke: "var(--main-text-color)", strokeWidth: 1 }, |  | ||||||
|             Container: this.$linkMapContainer.attr("id") |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|         this.jsPlumbInstance.registerConnectionType("link", { anchor: "Continuous", connector: "Straight", overlays: linkOverlays }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     noteIdToId(noteId) { |  | ||||||
|         return "link-map-note-" + noteId; |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 zadam
						zadam