json-canvas-viewer 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +206 -0
- package/dist/canvasViewer.cjs.js +2 -0
- package/dist/canvasViewer.d.ts +68 -0
- package/dist/canvasViewer.esm.js +1 -0
- package/dist/controls.d.ts +27 -0
- package/dist/interactor.d.ts +40 -0
- package/dist/minimap.d.ts +39 -0
- package/dist/mistouchPreventer.d.ts +15 -0
- package/dist/overlayManager.d.ts +22 -0
- package/dist/previewModal.d.ts +20 -0
- package/dist/renderer.d.ts +52 -0
- package/package.json +64 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Hesprs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# JSON Canvas Viewer
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
[](https://www.npmjs.com/package/json-canvas-viewer)
|
|
5
|
+
[](https://packagephobia.now.sh/result?p=json-canvas-viewer)
|
|
6
|
+
[](https://github.com/hesprs/JSON-Canvas-Viewer/commits/main)
|
|
7
|
+
|
|
8
|
+

|
|
9
|
+
|
|
10
|
+
A **TypeScript-based** viewer for **JSON Canvas** files. View and interact with your canvas files directly in the browser, or embed the viewer in front-end projects with ease. It is built without any web framework so it can be easily integrated into any framework.
|
|
11
|
+
|
|
12
|
+
This project is inspired by [sofanati-nour/obsidian-canvas-web-renderer](https://github.com/sofanati-nour/obsidian-canvas-web-renderer), but is far more developed and optimized.
|
|
13
|
+
|
|
14
|
+
For more about **JSON Canvas**, also known as **Obsidian Canvas**, please visit [jsoncanvas.org](https://jsoncanvas.org/).
|
|
15
|
+
|
|
16
|
+
## 📦 Installation
|
|
17
|
+
|
|
18
|
+
We recommend using your favorite package manager to install the package. **Note: This package requires `marked` as dependency, your package manager will automatically install it.**
|
|
19
|
+
|
|
20
|
+
``` bash
|
|
21
|
+
# npm
|
|
22
|
+
npm install json-canvas-viewer
|
|
23
|
+
|
|
24
|
+
# pnpm
|
|
25
|
+
pnpm add json-canvas-viewer
|
|
26
|
+
|
|
27
|
+
# yarn
|
|
28
|
+
yarn add json-canvas-viewer
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The integrated version is a choice if your project *doesn't use Node.js*, which is built with `marked` and `json-canvas-viewer` inlined, so it can be deployed in vanilla JavaScript projects. Find the integrated version in [Release page](https://github.com/hesprs/JSON-Canvas-Viewer/releases).
|
|
32
|
+
|
|
33
|
+
After installation, you can import the package as a module. It supports both ES module and Common JS, here we take ESM as example:
|
|
34
|
+
|
|
35
|
+
``` TypeScript
|
|
36
|
+
// with Node.js
|
|
37
|
+
import canvasViewer from 'json-canvas-viewer';
|
|
38
|
+
|
|
39
|
+
// using integrated version
|
|
40
|
+
import canvasViewer from 'path/to/canvasViewer.js';
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## 🚀 Quick Start
|
|
44
|
+
|
|
45
|
+
As a custom element (a simple way to embed, already defined in the code):
|
|
46
|
+
|
|
47
|
+
``` HTML
|
|
48
|
+
<canvas-viewer
|
|
49
|
+
src="example/introduction.canvas"
|
|
50
|
+
extensions="minimap mistouchPrevention"
|
|
51
|
+
options="minimapCollapsed"
|
|
52
|
+
></canvas-viewer>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Or instantiate the viewer (more flexible, but requires more code):
|
|
56
|
+
|
|
57
|
+
``` HTML
|
|
58
|
+
<div id="myCanvasContainer" style="width:800px; height:600px;"></div>
|
|
59
|
+
<script>
|
|
60
|
+
const viewer = new canvasViewer(
|
|
61
|
+
document.getElementById('myCanvasContainer'),
|
|
62
|
+
['minimap', 'mistouchPrevention'],
|
|
63
|
+
['minimapCollapsed']
|
|
64
|
+
);
|
|
65
|
+
viewer.loadCanvas('example/introduction.canvas');
|
|
66
|
+
viewer.addEventListener('interact', e => {
|
|
67
|
+
// handle node interaction
|
|
68
|
+
});
|
|
69
|
+
// dispose when not needed
|
|
70
|
+
viewer.dispose();
|
|
71
|
+
</script>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Tip**: All emited events are realized by `JavaScript CustomEvent`, so the event callback is stored in `event.detail`.
|
|
75
|
+
|
|
76
|
+
If you are coding in TypeScript, and intend to retrieve Custom Event callback, please use the pattern below to make the type validator believe your code is type safe:
|
|
77
|
+
|
|
78
|
+
``` TypeScript
|
|
79
|
+
const viewer: canvasViewer = new canvasViewer(...);
|
|
80
|
+
viewer.loadCanvas(...);
|
|
81
|
+
viewer.addEventListener('...', (e: Event) => {
|
|
82
|
+
if (e instanceof CustomEvent) {
|
|
83
|
+
// use e.detail safely here
|
|
84
|
+
console.log(e.detail);
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## 🐶 Features
|
|
90
|
+
|
|
91
|
+
- View JSON Canvas files (`.canvas`) in a web browser
|
|
92
|
+
- Full markdown syntax support (auto-parsed to HTML)
|
|
93
|
+
- Embed into front-end projects using a container element or custom element
|
|
94
|
+
- Interactive pan and zoom functionality
|
|
95
|
+
- Support for different node types:
|
|
96
|
+
- Text nodes
|
|
97
|
+
- File nodes (including Markdown files)
|
|
98
|
+
- Link nodes (embedded web content)
|
|
99
|
+
- Group nodes with custom colors
|
|
100
|
+
- Edge connections between nodes with labels
|
|
101
|
+
- Minimap for easy navigation (optional extension)
|
|
102
|
+
- Mistouch prevention (optional extension)
|
|
103
|
+
- Responsive design with mobile and touchpad adaption
|
|
104
|
+
- 🔥 **Much more performant** than rendering canvas in Obsidian!
|
|
105
|
+
|
|
106
|
+
## 🔌 API Reference
|
|
107
|
+
|
|
108
|
+
### Constructor
|
|
109
|
+
|
|
110
|
+
``` TypeScript
|
|
111
|
+
new canvasViewer(container, extensions, options);
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
- `container`: HTMLElement where the viewer will be rendered
|
|
115
|
+
- `extensions`: (optional) Array (or space-separated string in case of custom element) of extension names to enable:
|
|
116
|
+
- `minimap` - Adds navigation minimap
|
|
117
|
+
- `mistouchPrevention` - Frozes canvas when clicking outside. **Warning: navigation methods (like `zoomIn()` or `resetView()`) cannot take effect if the canvas is frozen**.
|
|
118
|
+
- `options`: (optional) Array (or space-separated string in case of custom element) of config options:
|
|
119
|
+
- `minimapCollapsed` - Starts with minimap collapsed
|
|
120
|
+
- `controlsHidden` - Hides the control panel
|
|
121
|
+
- `controlsCollapsed` - Starts with controls collapsed
|
|
122
|
+
- `proControlSchema` - Uses control keybindings in professional softwares (`mouse wheel`: scroll vertically; `mouse wheel` + `shift`: scroll horizontally; `mouse wheel` + `ctrl`: zoom), rather than mouse wheel to zoom. The canvas viewer automatically detect and adjust control schema by default, but you can explicitly configure it. This option doesn't affect mobile control.
|
|
123
|
+
|
|
124
|
+
### Methods
|
|
125
|
+
|
|
126
|
+
- `loadCanvas(path)` — Load a canvas file (by path), **please put all the related files (files embeded in the canvas) in the same folder as the canvas file, wherever they originally are**.
|
|
127
|
+
- `shiftFullscreen(option)` — Toggle fullscreen mode ('toggle', 'enter', 'exit')
|
|
128
|
+
- `resetView()` — Reset pan/zoom to fit canvas content
|
|
129
|
+
- `zoomIn()` — Zoom in by a fixed step
|
|
130
|
+
- `zoomOut()` — Zoom out by a fixed step
|
|
131
|
+
- `setScale(scale)` — Set zoom level to a specific value (number, 0.05–20)
|
|
132
|
+
- `panTo(x, y)` — Pan the view to a specific world coordinate
|
|
133
|
+
- `destroy()` — Clean up and remove viewer from DOM
|
|
134
|
+
|
|
135
|
+
### Events
|
|
136
|
+
|
|
137
|
+
Register with `viewer.addEventListener(event, callback)`.
|
|
138
|
+
|
|
139
|
+
- `interact` — Fired when a node is interacted with (`callback(node id, type: 'select' | 'preview')`)
|
|
140
|
+
- `loaded` — Fired when a canvas file is loaded (`callback(canvasData)`)
|
|
141
|
+
|
|
142
|
+
## 📂 Canvas File Structure
|
|
143
|
+
|
|
144
|
+
The viewer expects JSON Canvas files in JSON format:
|
|
145
|
+
|
|
146
|
+
``` JSON
|
|
147
|
+
{
|
|
148
|
+
"nodes": [
|
|
149
|
+
{
|
|
150
|
+
"id": "unique-id",
|
|
151
|
+
"type": "text|file|link|group",
|
|
152
|
+
"x": 0,
|
|
153
|
+
"y": 0,
|
|
154
|
+
"width": 400,
|
|
155
|
+
"height": 400,
|
|
156
|
+
"text": "Content for text nodes",
|
|
157
|
+
"file": "filename for file nodes",
|
|
158
|
+
"url": "URL for link nodes",
|
|
159
|
+
"color": "color-id for groups"
|
|
160
|
+
}
|
|
161
|
+
],
|
|
162
|
+
"edges": [
|
|
163
|
+
{
|
|
164
|
+
"id": "edge-id",
|
|
165
|
+
"fromNode": "source-node-id",
|
|
166
|
+
"toNode": "target-node-id",
|
|
167
|
+
"fromSide": "top|bottom|left|right",
|
|
168
|
+
"toSide": "top|bottom|left|right",
|
|
169
|
+
"label": "Optional edge label"
|
|
170
|
+
}
|
|
171
|
+
]
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## 💻 Development
|
|
176
|
+
|
|
177
|
+
Built with `TypeScript`, `SCSS` and `HTML5 Canvas`.
|
|
178
|
+
|
|
179
|
+
**Project Structure**:
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
root
|
|
183
|
+
├── src/
|
|
184
|
+
│ ├── canvasViewer.ts // Main class-based component
|
|
185
|
+
│ ├── controls.ts // Controls panel
|
|
186
|
+
│ ├── interactor.ts // Handles pointer events for user pan/zoom
|
|
187
|
+
│ ├── minimap.ts // Minimap extension
|
|
188
|
+
│ ├── mistouchPreventer.ts // MistouchPrevention extension
|
|
189
|
+
│ ├── overlayManager.ts // Renderer for interactive nodes
|
|
190
|
+
│ ├── previewModal.ts // Preview modal
|
|
191
|
+
│ ├── renderer.ts // Renderer for non-interactive stuff + some shared exports
|
|
192
|
+
│ ├── declarations.d.ts // Public types
|
|
193
|
+
│ └── styles.scss // Styles for the viewer
|
|
194
|
+
└── example/
|
|
195
|
+
└── index.html // Example/test entry point
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**Development Standards**:
|
|
199
|
+
- Strict type validation, no non-null assertion operator `!` allowed.
|
|
200
|
+
- Meticulous resource disposal, no memory leak ever possible.
|
|
201
|
+
- Modularized components, avoid monolithic class.
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
## 📝 Copyright & License
|
|
205
|
+
|
|
206
|
+
Copyright ©️ 2025 Hesprs (Hēsperus) | [MIT License](https://mit-license.org/)
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";const M=require("marked"),m=new Error("This error is unexpected, probably caused by file corruption. If you assure the error is not caused by accident, please contact the author and show how to reproduce."),l=new Error("Resource hasn't been set up or has been disposed.");class L{constructor(t){this._nodeMap=null,this._canvasData=null,this.ARROW_LENGTH=12,this.ARROW_WIDTH=7,this.FILE_NODE_RADIUS=12,this.FONT_COLOR="#fff",this.CSS_ZOOM_REDRAW_INTERVAL=500,this._canvas=document.createElement("canvas"),this._canvas.className="main-canvas",this._container=t,this._container.appendChild(this._canvas),this._ctx=this._canvas.getContext("2d"),this.zoomInOptimize={lastDrawnScale:0,lastDrawnViewport:{left:0,right:0,top:0,bottom:0},timeout:null,lastCallTime:0},y(this._canvas,t.offsetWidth,t.offsetHeight)}get nodeMap(){if(this._nodeMap===null)throw l;return this._nodeMap}get canvasData(){if(this._canvasData===null)throw l;return this._canvasData}get canvas(){if(this._canvas===null)throw l;return this._canvas}get ctx(){if(this._ctx===null)throw l;return this._ctx}get container(){if(this._container===null)throw l;return this._container}receiveData(t,e){this._nodeMap=t,this._canvasData=e}resizeCanvasForDPR(){y(this.canvas,this.container.offsetWidth,this.container.offsetHeight)}redraw(t,e,n){this.zoomInOptimize.timeout&&(clearTimeout(this.zoomInOptimize.timeout),this.zoomInOptimize.timeout=null);const i=Date.now(),o=this.getCurrentViewport(t,e,n);if(this.isViewportInside(o,this.zoomInOptimize.lastDrawnViewport)&&n!==this.zoomInOptimize.lastDrawnScale&&i-this.zoomInOptimize.lastCallTime<this.CSS_ZOOM_REDRAW_INTERVAL){this.zoomInOptimize.timeout=setTimeout(()=>{this.trueRedraw(t,e,n,o),this.zoomInOptimize.lastCallTime=i,this.zoomInOptimize.timeout=null},60),this.fakeRedraw(o,n);return}this.zoomInOptimize.lastCallTime=i,this.trueRedraw(t,e,n,o)}dispose(){this.zoomInOptimize.timeout&&clearTimeout(this.zoomInOptimize.timeout),this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height),this.canvas.remove(),this.zoomInOptimize.timeout=null,this._ctx=null,this._canvas=null,this._container=null,this._canvasData=null,this._nodeMap=null}trueRedraw(t,e,n,i){this.zoomInOptimize.lastDrawnViewport=i,this.zoomInOptimize.lastDrawnScale=n,this.canvas.style.transform="",this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height),this.ctx.save(),this.ctx.translate(t,e),this.ctx.scale(n,n),this.canvasData.nodes.forEach(o=>o.inViewport=this.isNodeInViewport(o,t,e,n)),this.canvasData.nodes.forEach(o=>{switch(o.type){case"group":this.drawGroup(o,n);break;case"file":this.drawFileNode(o);break}}),this.canvasData.edges.forEach(o=>this.drawEdge(o)),this.ctx.restore()}fakeRedraw(t,e){const n=e/this.zoomInOptimize.lastDrawnScale,i=(this.zoomInOptimize.lastDrawnViewport.left-t.left)*e,o=(this.zoomInOptimize.lastDrawnViewport.top-t.top)*e;this.canvas.style.transform=`translate(${i}px, ${o}px) scale(${n})`}isViewportInside(t,e){return t.left>e.left&&t.top>e.top&&t.right<e.right&&t.bottom<e.bottom}isNodeInViewport(t,e,n,i,o=200){const s=-e/i-o,r=-n/i-o,a=s+this.container.clientWidth/i+2*o,c=r+this.container.clientHeight/i+2*o;return t.x+t.width>s&&t.x<a&&t.y+t.height>r&&t.y<c}getCurrentViewport(t,e,n){const i=-t/n,o=-e/n,s=i+this.container.clientWidth/n,r=o+this.container.clientHeight/n;return{left:i,top:o,right:s,bottom:r}}drawLabelBar(t,e,n,i,o){const s=30*o,r=6*o,a=8*o,c=16*o,p=6*o;this.ctx.save(),this.ctx.translate(t,e),this.ctx.scale(1/o,1/o),this.ctx.font=`${c}px 'Inter', sans-serif`;const d=this.ctx.measureText(n).width+2*p;this.ctx.translate(0,-s-a),this.ctx.fillStyle=i,this.ctx.beginPath(),this.ctx.moveTo(r,0),this.ctx.lineTo(d-r,0),this.ctx.quadraticCurveTo(d,0,d,r),this.ctx.lineTo(d,s-r),this.ctx.quadraticCurveTo(d,s,d-r,s),this.ctx.lineTo(r,s),this.ctx.quadraticCurveTo(0,s,0,s-r),this.ctx.lineTo(0,r),this.ctx.quadraticCurveTo(0,0,r,0),this.ctx.closePath(),this.ctx.fill(),this.ctx.fillStyle=this.FONT_COLOR,this.ctx.fillText(n,p,s*.65),this.ctx.restore()}drawNodeBackground(t){const e=E(t.color),n=this.FILE_NODE_RADIUS;this.ctx.globalAlpha=1,this.ctx.fillStyle=e.background,C(this.ctx,t.x+1,t.y+1,t.width-2,t.height-2,n),this.ctx.fill(),this.ctx.strokeStyle=e.border,this.ctx.lineWidth=2,C(this.ctx,t.x,t.y,t.width,t.height,n),this.ctx.stroke()}drawGroup(t,e){this.drawNodeBackground(t),t.label&&this.drawLabelBar(t.x,t.y,t.label,E(t.color).border,e)}drawFileNode(t){if(!t.file)throw m;t.file.match(/\.md|png|jpg|jpeg|gif|svg$/i)||(this.drawNodeBackground(t),t.file.match(/\.mp3$/i)&&(this.ctx.fillStyle=this.FONT_COLOR,this.ctx.textAlign="center",this.ctx.textBaseline="middle",this.ctx.fillText("🎵 Click to Preview 🎵",t.x+t.width/2,t.y+t.height/2),this.ctx.textAlign="left")),this.ctx.fillStyle=this.FONT_COLOR,this.ctx.font="16px sans-serif",this.ctx.fillText(t.file,t.x+5,t.y-10)}drawEdge(t){const{fromNode:e,toNode:n}=this.getEdgeNodes(t);if(!e||!n)throw m;const[i,o]=_(e,t.fromSide),[s,r]=_(n,t.toSide);let[a,c,p,d]=[0,0,0,0];if(t.controlPoints?[a,c,p,d]=t.controlPoints:([a,c,p,d]=this.getControlPoints(i,o,s,r,t.fromSide,t.toSide),t.controlPoints=[a,c,p,d]),this.drawCurvedPath(i,o,s,r,a,c,p,d),this.drawArrowhead(s,r,p,d),t.label){const v=Math.pow(.5,3)*i+3*Math.pow(.5,2)*.5*a+.375*p+Math.pow(.5,3)*s,f=Math.pow(1-.5,3)*o+3*Math.pow(1-.5,2)*.5*c+3*(1-.5)*.5*.5*d+Math.pow(.5,3)*r;this.ctx.font="18px sans-serif";const x=this.ctx.measureText(t.label).width+8*2,b=20;this.ctx.fillStyle="#222",this.ctx.beginPath(),C(this.ctx,v-x/2,f-b/2-2,x,b,4),this.ctx.fill(),this.ctx.fillStyle="#ccc",this.ctx.textAlign="center",this.ctx.textBaseline="middle",this.ctx.fillText(t.label,v,f-2),this.ctx.textAlign="left",this.ctx.textBaseline="alphabetic"}}getEdgeNodes(t){return{fromNode:this.nodeMap[t.fromNode],toNode:this.nodeMap[t.toNode]}}getControlPoints(t,e,n,i,o,s){const r=n-t,a=i-e,c=Math.min(Math.abs(r),Math.abs(a))+.3*Math.max(Math.abs(r),Math.abs(a)),d=((w,x,b)=>Math.max(x,Math.min(b,w)))(c*.5,60,300);let u=t,v=e,f=n,g=i;switch(o){case"top":v=e-d;break;case"bottom":v=e+d;break;case"left":u=t-d;break;case"right":u=t+d;break}switch(s){case"top":g=i-d;break;case"bottom":g=i+d;break;case"left":f=n-d;break;case"right":f=n+d;break}return[u,v,f,g]}drawCurvedPath(t,e,n,i,o,s,r,a){this.ctx.beginPath(),this.ctx.moveTo(t,e),this.ctx.bezierCurveTo(o,s,r,a,n,i),this.ctx.strokeStyle="#ccc",this.ctx.lineWidth=2,this.ctx.stroke()}drawArrowhead(t,e,n,i){const o=t-n,s=e-i,r=Math.sqrt(o*o+s*s);if(r===0)return;const a=o/r,c=s/r,p=t-a*this.ARROW_LENGTH-c*this.ARROW_WIDTH,d=e-c*this.ARROW_LENGTH+a*this.ARROW_WIDTH,u=t-a*this.ARROW_LENGTH+c*this.ARROW_WIDTH,v=e-c*this.ARROW_LENGTH-a*this.ARROW_WIDTH;this.ctx.beginPath(),this.ctx.fillStyle="#ccc",this.ctx.moveTo(t,e),this.ctx.lineTo(p,d),this.ctx.lineTo(u,v),this.ctx.closePath(),this.ctx.fill()}}function _(h,t){const e=h.x+h.width/2,n=h.y+h.height/2;switch(t){case"top":return[e,h.y];case"bottom":return[e,h.y+h.height];case"left":return[h.x,n];case"right":return[h.x+h.width,n];default:return[e,n]}}function C(h,t,e,n,i,o){h.beginPath(),h.moveTo(t+o,e),h.lineTo(t+n-o,e),h.quadraticCurveTo(t+n,e,t+n,e+o),h.lineTo(t+n,e+i-o),h.quadraticCurveTo(t+n,e+i,t+n-o,e+i),h.lineTo(t+o,e+i),h.quadraticCurveTo(t,e+i,t,e+i-o),h.lineTo(t,e+o),h.quadraticCurveTo(t,e,t+o,e),h.closePath()}function E(h){let t=null;switch(h){case"1":t="rgba(255, 120, 129, ?)";break;case"2":t="rgba(251, 187, 131, ?)";break;case"3":t="rgba(255, 232, 139, ?)";break;case"4":t="rgba(124, 211, 124, ?)";break;case"5":t="rgba(134, 223, 226, ?)";break;case"6":t="rgba(203, 158, 255, ?)";break;default:t="rgba(140, 140, 140, ?)"}return{border:t.replace("?","0.75"),background:t.replace("?","0.1"),active:t.replace("?","1")}}function y(h,t,e){const n=window.devicePixelRatio||1,i=h.getContext("2d");if(!i)throw m;h.width=Math.round(t*n),h.height=Math.round(e*n),i.setTransform(1,0,0,1,0,0),i.scale(n,n)}class z extends EventTarget{constructor(t,e){super(),this.pointers=new Map,this.onPointerDown=i=>{if(!(this.pointers.size>=2)&&(i.isPrimary&&this.pointers.clear(),this.pointers.set(i.pointerId,{startX:i.clientX,startY:i.clientY,lastX:i.clientX,lastY:i.clientY,interrupted:!1,target:i.target}),this.pointers.size===2)){const o=this.getNthValue(0),s=this.pointers.get(i.pointerId);if(!o||!s)throw m;o.interrupted=!0,s.interrupted=!0,this.pinchZoomState.lastDistance=this.getPointerDistance(),this.pinchZoomState.lastMidpoint=this.S2C(this.getPointerMidpoint())}},this.onPointerMove=i=>{const o=this.pointers.get(i.pointerId);if(o){if(this.pointers.size===1){const s=i.clientX-o.lastX,r=i.clientY-o.lastY;this.dispatchPanEvent({x:s,y:r})}if(this.pointers.set(i.pointerId,{startX:o.startX,startY:o.startY,lastX:i.clientX,lastY:i.clientY,interrupted:o.interrupted,target:o.target}),this.pointers.size===2){const s=this.getPointerDistance(),r=this.getPointerMidpoint();if(!s||!r)throw m;let a=s/this.pinchZoomState.lastDistance;this.pinchZoomState.lastDistance=s;const c=this.S2C(r),p=c.x-this.pinchZoomState.lastMidpoint.x,d=c.y-this.pinchZoomState.lastMidpoint.y;this.pinchZoomState.lastMidpoint=c,this.dispatchPanEvent({x:p,y:d}),this.dispatchZoomEvent(a,c)}}},this.onPointerUp=i=>{const o=this.pointers.get(i.pointerId);if(o&&(this.pointers.delete(i.pointerId),this.pointers.size===0&&!o.interrupted&&Math.abs(o.startX-i.clientX)+Math.abs(o.startY-i.clientY)<5)){const s=this.S2C({x:i.clientX,y:i.clientY}),r=new CustomEvent("trueClick",{detail:{position:s,target:o.target}});this.dispatchEvent(r)}},this.onWheel=i=>{if(!this.lockControlSchema&&!this.proControlSchema&&(i.ctrlKey||Math.abs(i.deltaX)>Math.abs(i.deltaY))&&(this.proControlSchema=!0),this.preventDefault&&i.preventDefault(),this.proControlSchema&&!i.ctrlKey)this.dispatchPanEvent({x:i.deltaX,y:i.deltaY});else{const o=1-this.zoomFactor*i.deltaY,s=this.S2C({x:i.clientX,y:i.clientY});this.dispatchZoomEvent(o,s)}},this.preventDefaultFunction=i=>i.preventDefault,this._monitoringElement=t;const n=e||{};this.preventDefault=n.preventDefault||!1,this.proControlSchema=n.proControlSchema||!1,this.zoomFactor=n.zoomFactor||.002,this.lockControlSchema=n.lockControlSchema||!1,this.pointers.clear(),this.pinchZoomState={lastDistance:0,lastMidpoint:{x:0,y:0}},this.panDump={x:0,y:0},this.zoomDump={factor:1,origin:{x:0,y:0}}}get monitoringElement(){if(this._monitoringElement===null)throw l;return this._monitoringElement}getNthValue(t){if(t<0||t>=this.pointers.size)throw m;let e=0;for(const n of this.pointers.values()){if(e===t)return n;e++}}getPointerDistance(){const t=this.getNthValue(0),e=this.getNthValue(1);if(!t||!e)throw m;const n=t.lastX-e.lastX,i=t.lastY-e.lastY;return Math.sqrt(n*n+i*i)}getPointerMidpoint(){const t=this.getNthValue(0),e=this.getNthValue(1);if(!t||!e)throw m;return{x:(t.lastX+e.lastX)/2,y:(t.lastY+e.lastY)/2}}S2C({x:t,y:e}){const n=this.monitoringElement.getBoundingClientRect();return{x:t-n.left,y:e-n.top}}dispatchPanEvent(t){const e={x:this.round(t.x,1),y:this.round(t.y,1)};this.panDump.x+=e.x,this.panDump.y+=e.y;const n=new CustomEvent("pan",{detail:e});this.dispatchEvent(n)}dispatchZoomEvent(t,e){const n=this.round(t,4);this.zoomDump.factor*=n,this.zoomDump.origin=e;const i=new CustomEvent("zoom",{detail:{factor:n,origin:e}});this.dispatchEvent(i)}round(t,e){const n=10**e;return Math.round(t*n)/n}getZoomDump(){return this.zoomDump}resetZoomDump(){this.zoomDump.factor=1}getPanDump(){return this.panDump}resetPanDump(){this.panDump={x:0,y:0}}stop(){this.monitoringElement.removeEventListener("pointerdown",this.onPointerDown),window.removeEventListener("pointermove",this.onPointerMove),window.removeEventListener("pointerup",this.onPointerUp),this.monitoringElement.removeEventListener("wheel",this.onWheel),this.preventDefault&&(this.monitoringElement.style.touchAction="",this.monitoringElement.removeEventListener("gesturestart",this.preventDefaultFunction),this.monitoringElement.removeEventListener("gesturechange",this.preventDefaultFunction))}start(){this.monitoringElement.addEventListener("pointerdown",this.onPointerDown),window.addEventListener("pointermove",this.onPointerMove),window.addEventListener("pointerup",this.onPointerUp),this.monitoringElement.addEventListener("wheel",this.onWheel,this.preventDefault?{passive:!1}:{}),this.preventDefault&&(this.monitoringElement.style.touchAction="none",this.monitoringElement.addEventListener("gesturestart",this.preventDefaultFunction,{passive:!1}),this.monitoringElement.addEventListener("gesturechange",this.preventDefaultFunction,{passive:!1}))}dispose(){this.stop(),this.pointers.clear(),this._monitoringElement=null}}class D extends EventTarget{constructor(t,e=!1){super(),this._canvasData=null,this._nodeMap=null,this._nodeBounds=null,this.toggleVisisbility=()=>{this.isMinimapVisible=!this.isMinimapVisible,this.minimapContainer.classList.toggle("collapsed"),this.dispatchEvent(new CustomEvent(this.isMinimapVisible?"minimapExpanded":"minimapCollapsed"))},this._minimapContainer=document.createElement("div"),this._minimapContainer.className="minimap-container",this._toggleMinimapBtn=document.createElement("button"),this._toggleMinimapBtn.className="toggle-minimap collapse-button",this._toggleMinimapBtn.innerHTML='<svg viewBox="-3.6 -3.6 31.2 31.2" stroke-width=".4"><path d="M15.707 4.293a1 1 0 0 1 0 1.414L9.414 12l6.293 6.293a1 1 0 0 1-1.414 1.414l-7-7a1 1 0 0 1 0-1.414l7-7a1 1 0 0 1 1.414 0Z" /></svg>',this._minimapContainer.appendChild(this._toggleMinimapBtn),this._minimap=document.createElement("div"),this._minimap.className="minimap";const n=document.createElement("canvas");n.className="minimap-canvas",n.width=200,n.height=150,this._minimap.appendChild(n),this._minimapCtx=n.getContext("2d"),this._viewportRectangle=document.createElement("div"),this._viewportRectangle.className="viewport-rectangle",this._minimap.appendChild(this._viewportRectangle),this._minimapContainer.appendChild(this._minimap),t.appendChild(this._minimapContainer),this._container=t,this.isMinimapVisible=!e,this._minimapContainer.classList.toggle("collapsed",e),this.minimapCache={scale:1,centerX:0,centerY:0},this._toggleMinimapBtn.addEventListener("click",this.toggleVisisbility),y(n,n.width,n.height)}get minimapCtx(){if(this._minimapCtx===null)throw l;return this._minimapCtx}get canvasData(){if(this._canvasData===null)throw l;return this._canvasData}get container(){if(this._container===null)throw l;return this._container}get minimap(){if(this._minimap===null)throw l;return this._minimap}get nodeBounds(){if(this._nodeBounds===null)throw l;return this._nodeBounds}get nodeMap(){if(this._nodeMap===null)throw l;return this._nodeMap}get viewportRectangle(){if(this._viewportRectangle===null)throw l;return this._viewportRectangle}get minimapContainer(){if(this._minimapContainer===null)throw l;return this._minimapContainer}get toggleMinimapBtn(){if(this._toggleMinimapBtn===null)throw l;return this._toggleMinimapBtn}receiveData(t,e,n){this._nodeBounds=t,this._canvasData=e,this._nodeMap=n,this.drawMinimap()}drawMinimap(){const t=this.nodeBounds;if(!t)return;const e=this.minimap.clientWidth,n=this.minimap.clientHeight,i=e/t.width,o=n/t.height;this.minimapCache.scale=Math.min(i,o)*.9,this.minimapCache.centerX=e/2,this.minimapCache.centerY=n/2,this.minimapCtx.clearRect(0,0,e,n),this.minimapCtx.save(),this.minimapCtx.translate(this.minimapCache.centerX,this.minimapCache.centerY),this.minimapCtx.scale(this.minimapCache.scale,this.minimapCache.scale),this.minimapCtx.translate(-t.centerX,-t.centerY);for(let s of this.canvasData.edges)this.drawMinimapEdge(s);for(let s of this.canvasData.nodes)this.drawMinimapNode(s);this.minimapCtx.restore()}drawMinimapNode(t){const e=E(t.color),n=25;this.minimapCtx.fillStyle=e.border,this.minimapCtx.globalAlpha=.3,C(this.minimapCtx,t.x,t.y,t.width,t.height,n),this.minimapCtx.fill(),this.minimapCtx.globalAlpha=1}drawMinimapEdge(t){const e=this.nodeMap[t.fromNode],n=this.nodeMap[t.toNode];if(!e||!n)return;const[i,o]=_(e,t.fromSide),[s,r]=_(n,t.toSide);this.minimapCtx.beginPath(),this.minimapCtx.moveTo(i,o),this.minimapCtx.lineTo(s,r),this.minimapCtx.strokeStyle="#555",this.minimapCtx.lineWidth=10,this.minimapCtx.stroke()}updateViewportRectangle(t,e,n){if(!this.isMinimapVisible)return;const i=this.nodeBounds;if(!i)return;const o=this.container.clientWidth/n,s=this.container.clientHeight/n,r=-t/n+this.container.clientWidth/(2*n),a=-e/n+this.container.clientHeight/(2*n),c=this.minimapCache.centerX+(r-o/2-i.centerX)*this.minimapCache.scale,p=this.minimapCache.centerY+(a-s/2-i.centerY)*this.minimapCache.scale,d=o*this.minimapCache.scale,u=s*this.minimapCache.scale;this.viewportRectangle.style.left=c+"px",this.viewportRectangle.style.top=p+"px",this.viewportRectangle.style.width=d+"px",this.viewportRectangle.style.height=u+"px"}dispose(){this.toggleMinimapBtn.removeEventListener("click",this.toggleVisisbility),this.minimapCtx.clearRect(0,0,this.minimap.clientWidth,this.minimap.clientHeight),this.minimapContainer.remove(),this._minimapContainer=null,this._minimapCtx=null,this._toggleMinimapBtn=null,this._canvasData=null,this._container=null,this._nodeMap=null,this._nodeBounds=null,this._viewportRectangle=null,this._minimap=null}}class S extends EventTarget{constructor(t,e=!1){super(),this.toggleCollapse=()=>this.controlsPanel.classList.toggle("collapsed"),this.zoomIn=()=>this.dispatchEvent(new CustomEvent("zoomIn")),this.zoomOut=()=>this.dispatchEvent(new CustomEvent("zoomOut")),this.slide=()=>this.dispatchEvent(new CustomEvent("slide",{detail:Math.pow(1.1,Number(this.zoomSlider.value))})),this.resetView=()=>this.dispatchEvent(new CustomEvent("resetView")),this.toggleFullscreen=()=>this.dispatchEvent(new CustomEvent("toggleFullscreen")),this.updateSlider=i=>this.zoomSlider.value=String(this.scaleToSlider(i)),this.scaleToSlider=i=>Math.log(i)/Math.log(1.1),this._controlsPanel=document.createElement("div"),this._controlsPanel.className="controls",this._controlsPanel.classList.toggle("collapsed",e),this._toggleCollapseBtn=document.createElement("button"),this._toggleCollapseBtn.className="collapse-button",this._toggleCollapseBtn.innerHTML='<svg viewBox="-3.6 -3.6 31.2 31.2" stroke-width=".4"><path d="M15.707 4.293a1 1 0 0 1 0 1.414L9.414 12l6.293 6.293a1 1 0 0 1-1.414 1.414l-7-7a1 1 0 0 1 0-1.414l7-7a1 1 0 0 1 1.414 0Z" /></svg>',this._controlsPanel.appendChild(this._toggleCollapseBtn);const n=document.createElement("div");n.className="controls-content",this._toggleFullscreenBtn=document.createElement("button"),this._toggleFullscreenBtn.innerHTML='<svg viewBox="-5.28 -5.28 34.56 34.56" fill="none"><path d="M4 9V5.6c0-.56 0-.84.109-1.054a1 1 0 0 1 .437-.437C4.76 4 5.04 4 5.6 4H9M4 15v3.4c0 .56 0 .84.109 1.054a1 1 0 0 0 .437.437C4.76 20 5.04 20 5.6 20H9m6-16h3.4c.56 0 .84 0 1.054.109a1 1 0 0 1 .437.437C20 4.76 20 5.04 20 5.6V9m0 6v3.4c0 .56 0 .84-.109 1.054a1 1 0 0 1-.437.437C19.24 20 18.96 20 18.4 20H15" stroke-width="2.4" stroke-linecap="round"/></svg>',n.appendChild(this._toggleFullscreenBtn),this._zoomOutBtn=document.createElement("button"),this._zoomOutBtn.innerHTML='<svg viewBox="-1.2 -1.2 26.4 26.4"><path d="M6 12h12" stroke-width="2" stroke-linecap="round" /></svg>',n.appendChild(this._zoomOutBtn),this._zoomSlider=document.createElement("input"),this._zoomSlider.type="range",this._zoomSlider.className="zoom-slider",this._zoomSlider.min="-30",this._zoomSlider.max="30",this._zoomSlider.value="0",n.appendChild(this._zoomSlider),this._zoomInBtn=document.createElement("button"),this._zoomInBtn.innerHTML='<svg viewBox="-1.2 -1.2 26.4 26.4"><path d="M6 12h12m-6-6v12" stroke-width="2" stroke-linecap="round" /></svg>',n.appendChild(this._zoomInBtn),this._resetViewBtn=document.createElement("button"),this._resetViewBtn.innerHTML='<svg viewBox="-6 -6 30 30" stroke-width=".08"><path d="m14.955 7.986.116.01a1 1 0 0 1 .85 1.13 8 8 0 0 1-13.374 4.728l-.84.84c-.63.63-1.707.184-1.707-.707V10h3.987c.89 0 1.337 1.077.707 1.707l-.731.731a6 6 0 0 0 8.347-.264 6 6 0 0 0 1.63-3.33 1 1 0 0 1 1.131-.848zM11.514.813a8 8 0 0 1 1.942 1.336l.837-.837c.63-.63 1.707-.184 1.707.707V6h-3.981c-.89 0-1.337-1.077-.707-1.707l.728-.729a6 6 0 0 0-9.98 3.591 1 1 0 1 1-1.98-.281A8 8 0 0 1 11.514.813Z" /></svg>',n.appendChild(this._resetViewBtn),this._controlsPanel.appendChild(n),t.appendChild(this._controlsPanel),this._toggleCollapseBtn.addEventListener("click",this.toggleCollapse),this._zoomInBtn.addEventListener("click",this.zoomIn),this._zoomOutBtn.addEventListener("click",this.zoomOut),this._zoomSlider.addEventListener("input",this.slide),this._resetViewBtn.addEventListener("click",this.resetView),this._toggleFullscreenBtn.addEventListener("click",this.toggleFullscreen)}get controlsPanel(){if(this._controlsPanel===null)throw l;return this._controlsPanel}get toggleCollapseBtn(){if(this._toggleCollapseBtn===null)throw l;return this._toggleCollapseBtn}get toggleFullscreenBtn(){if(this._toggleFullscreenBtn===null)throw l;return this._toggleFullscreenBtn}get zoomOutBtn(){if(this._zoomOutBtn===null)throw l;return this._zoomOutBtn}get zoomSlider(){if(this._zoomSlider===null)throw l;return this._zoomSlider}get zoomInBtn(){if(this._zoomInBtn===null)throw l;return this._zoomInBtn}get resetViewBtn(){if(this._resetViewBtn===null)throw l;return this._resetViewBtn}updateFullscreenBtn(){document.fullscreenElement===null?this.toggleFullscreenBtn.innerHTML='<svg viewBox="-40.32 -40.32 176.64 176.64"><path d="M30 60H6a6 6 0 0 0 0 12h18v18a6 6 0 0 0 12 0V66a5.997 5.997 0 0 0-6-6Zm60 0H66a5.997 5.997 0 0 0-6 6v24a6 6 0 0 0 12 0V72h18a6 6 0 0 0 0-12ZM66 36h24a6 6 0 0 0 0-12H72V6a6 6 0 0 0-12 0v24a5.997 5.997 0 0 0 6 6ZM30 0a5.997 5.997 0 0 0-6 6v18H6a6 6 0 0 0 0 12h24a5.997 5.997 0 0 0 6-6V6a5.997 5.997 0 0 0-6-6Z"/></svg>':this.toggleFullscreenBtn.innerHTML='<svg viewBox="-5.28 -5.28 34.56 34.56" fill="none"><path d="M4 9V5.6c0-.56 0-.84.109-1.054a1 1 0 0 1 .437-.437C4.76 4 5.04 4 5.6 4H9M4 15v3.4c0 .56 0 .84.109 1.054a1 1 0 0 0 .437.437C4.76 20 5.04 20 5.6 20H9m6-16h3.4c.56 0 .84 0 1.054.109a1 1 0 0 1 .437.437C20 4.76 20 5.04 20 5.6V9m0 6v3.4c0 .56 0 .84-.109 1.054a1 1 0 0 1-.437.437C19.24 20 18.96 20 18.4 20H15" stroke-width="2.4" stroke-linecap="round"/></svg>'}dispose(){this.toggleCollapseBtn.removeEventListener("click",this.toggleCollapse),this.zoomInBtn.removeEventListener("click",this.zoomIn),this.zoomOutBtn.removeEventListener("click",this.zoomOut),this.zoomSlider.removeEventListener("input",this.slide),this.resetViewBtn.removeEventListener("click",this.resetView),this.toggleFullscreenBtn.removeEventListener("click",this.toggleFullscreen),this.controlsPanel.remove(),this._controlsPanel=null,this._toggleCollapseBtn=null,this._zoomInBtn=null,this._zoomOutBtn=null,this._zoomSlider=null,this._resetViewBtn=null,this._toggleFullscreenBtn=null}}class I extends EventTarget{constructor(t){super(),this._canvasBaseDir=null,this.hidePreviewModal=()=>{this.previewModalBackdrop.classList.add("hidden"),this.previewModal.classList.add("hidden"),this.dispatchEvent(new CustomEvent("previewModalHidden"))},this._previewModalBackdrop=document.createElement("div"),this._previewModalBackdrop.className="preview-modal-backdrop hidden",t.appendChild(this._previewModalBackdrop),this._previewModal=document.createElement("div"),this._previewModal.className="preview-modal hidden",this._previewModalClose=document.createElement("button"),this._previewModalClose.className="preview-modal-close",this._previewModalClose.innerHTML='<svg viewBox="0 0 24 24"><path d="M6.758 17.243 12.001 12m5.243-5.243L12 12m0 0L6.758 6.757M12.001 12l5.243 5.243" stroke-width="2" stroke-linecap="round" /></svg>',this._previewModal.appendChild(this._previewModalClose),this._previewModalContent=document.createElement("div"),this._previewModalContent.className="preview-modal-content",this._previewModal.appendChild(this._previewModalContent),t.appendChild(this._previewModal),this._previewModalClose.addEventListener("click",this.hidePreviewModal),this._previewModalBackdrop.addEventListener("click",this.hidePreviewModal)}get previewModalBackdrop(){if(!this._previewModalBackdrop)throw l;return this._previewModalBackdrop}get previewModal(){if(!this._previewModal)throw l;return this._previewModal}get previewModalContent(){if(!this._previewModalContent)throw l;return this._previewModalContent}get canvasBaseDir(){if(!this._canvasBaseDir)throw l;return this._canvasBaseDir}get previewModalClose(){if(!this._previewModalClose)throw l;return this._previewModalClose}receiveData(t){this._canvasBaseDir=t}showPreviewModal(t){const e=this.processContent(t);if(!e)throw m;this.previewModalContent.innerHTML="",this.previewModalBackdrop.classList.remove("hidden"),this.previewModal.classList.remove("hidden"),this.previewModalContent.appendChild(e),this.dispatchEvent(new CustomEvent("previewModalShown"))}resize(t,e){this.previewModal.style.setProperty("--preview-max-width",`${t*.9}px`),this.previewModal.style.setProperty("--preview-max-height",`${e*.9}px`)}processContent(t){if(!t.file)throw m;let e;return t.file.match(/\.(png|jpg|jpeg|gif|svg)$/i)?(e=new Image,e.src=this.canvasBaseDir+t.file):t.file.match(/\.mp3$/i)&&(e=document.createElement("audio"),e.controls=!0,e.src=this.canvasBaseDir+t.file),e}dispose(){for(this.previewModalClose.removeEventListener("click",this.hidePreviewModal),this.previewModalBackdrop.removeEventListener("click",this.hidePreviewModal);this.previewModalContent.firstElementChild;)this.previewModalContent.firstElementChild.remove();this.previewModal.remove(),this.previewModalBackdrop.remove(),this._previewModal=null,this._previewModalClose=null,this._previewModalBackdrop=null,this._previewModalContent=null}}class B extends EventTarget{constructor(t){super(),this._nodeMap=null,this._canvasBaseDir=null,this.selectedId=null,this.eventListeners={},this.startInteract=()=>this.dispatchEvent(new CustomEvent("interactionStart")),this.endInteract=()=>this.dispatchEvent(new CustomEvent("interactionEnd")),this._overlaysLayer=document.createElement("div"),this._overlaysLayer.className="overlays",t.appendChild(this.overlaysLayer),this._overlays={}}get nodeMap(){if(!this._nodeMap)throw l;return this._nodeMap}get canvasBaseDir(){if(!this._canvasBaseDir)throw l;return this._canvasBaseDir}get overlays(){if(!this._overlays)throw l;return this._overlays}get overlaysLayer(){if(!this._overlaysLayer)throw l;return this._overlaysLayer}receiveData(t,e){this._canvasBaseDir=t,this._nodeMap=e}select(t){const e=this.selectedId?this.overlays[this.selectedId]:null,n=t?this.overlays[t]:null;e&&e.classList.remove("active"),n?(n.classList.add("active"),this.startInteract()):this.endInteract(),this.selectedId=t}async loadMarkdownForNode(t){if(!t.mdContent){t.mdContent="Loading...",this.updateOrCreateOverlay(t,t.mdContent,"markdown");try{if(!t.file)throw m;const n=await(await fetch(this.canvasBaseDir+t.file)).text(),i=n.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);if(i){const o=i[1].split(`
|
|
2
|
+
`).reduce((s,r)=>{const[a,c]=r.split(":").map(p=>p.trim());return s[a]=c,s},{});t.mdContent=await M.marked.parse(i[2].trim()),t.mdFrontmatter=o}else t.mdContent=await M.marked.parse(n)}catch(e){console.error("Failed to load markdown:",e),t.mdContent="Failed to load content."}}this.updateOrCreateOverlay(t,t.mdContent,"markdown")}updateAllOverlays(t,e,n){this.overlaysLayer.style.transform=`translate(${t}px, ${e}px) scale(${n})`;const i=new Set,o={text:s=>{if(!s.text)throw m;s.inViewport&&(i.add(s.id),this.updateOrCreateOverlay(s,s.text,"text"))},file:s=>{if(!s.file)throw m;s.inViewport&&(i.add(s.id),s.file.match(/\.md$/i)?this.loadMarkdownForNode(s):s.file.match(/\.(png|jpg|jpeg|gif|svg)$/i)&&this.updateOrCreateOverlay(s,this.canvasBaseDir+s.file,"image"))},link:s=>{if(!s.url)throw m;i.add(s.id),this.updateOrCreateOverlay(s,s.url,"link")},group:()=>{}};Object.values(this.nodeMap).forEach(s=>o[s.type](s)),Object.keys(this.overlays).forEach(s=>{if(!i.has(s)){const r=this.overlays[s];r&&r.parentNode&&r.parentNode.removeChild(r),delete this.overlays[s]}})}async updateOrCreateOverlay(t,e,n){let i=this.overlays[t.id];if(i||(i=await this.constructOverlay(t,e,n),this.overlaysLayer.appendChild(i),this.overlays[t.id]=i,i.style.left=t.x+"px",i.style.top=t.y+"px",i.style.width=t.width+"px",i.style.height=t.height+"px"),n==="markdown"){const o=i.getElementsByClassName("parsed-content-wrapper")[0];if(!t.mdContent)throw m;o.innerHTML!==t.mdContent&&(o.innerHTML=t.mdContent),!i.classList.contains("rtl")&&t.mdFrontmatter?.direction==="rtl"&&i.classList.add("rtl")}}async constructOverlay(t,e,n){const i=document.createElement("div");i.classList.add("overlay-container"),i.id=t.id;const o=document.createElement("div");if(o.className="overlay-border",i.appendChild(o),n==="text"||n==="markdown"){i.classList.add("markdown-content");const r=document.createElement("div");r.innerHTML=await M.marked.parse(e||""),r.classList.add("parsed-content-wrapper"),i.appendChild(r)}else if(n==="link"){const r=document.createElement("iframe");r.src=e,r.sandbox="allow-scripts allow-same-origin",r.className="link-iframe",r.loading="lazy";const a=document.createElement("div");a.className="link-click-layer",i.appendChild(r),i.appendChild(a)}else if(n==="image"){const r=document.createElement("img");r.src=e,r.loading="lazy",i.appendChild(r)}const s=t.color==null?"color-0":"color-"+t.color;if(i.classList.add(s),n!=="image"){this.selectedId===t.id&&i.classList.add("active");const r=()=>{t.id===this.selectedId&&this.startInteract()},a=()=>{t.id===this.selectedId&&this.endInteract()};i.addEventListener("pointerenter",r),i.addEventListener("pointerleave",a),i.addEventListener("touchstart",r),i.addEventListener("touchend",a),this.eventListeners[t.id]=[r,a]}return i}dispose(){for(this._overlays=null;this.overlaysLayer.firstElementChild;){const t=this.overlaysLayer.firstElementChild;if(this.eventListeners[t.id]){const e=this.eventListeners[t.id][0],n=this.eventListeners[t.id][1];if(!e||!n)throw l;t.removeEventListener("pointerenter",e),t.removeEventListener("pointerleave",n),t.removeEventListener("touchstart",e),t.removeEventListener("touchend",n),this.eventListeners[t.id][0]=null,this.eventListeners[t.id][1]=null}t.remove()}this.overlaysLayer.remove(),this._overlaysLayer=null,this._nodeMap=null}}class P extends EventTarget{constructor(t,e=!0){super(),this.preventMt=!1,this.onPointerDown=i=>{const o=this.container.getBoundingClientRect();i.clientX<o.left||i.clientX>o.right||i.clientY<o.top||i.clientY>o.bottom?this.preventMt||this.startPrevention():this.preventMt&&(this.preventMistouch.initialX=i.clientX,this.preventMistouch.initialY=i.clientY,this.preventMistouch.lastX=i.clientX,this.preventMistouch.lastY=i.clientY,this.preventMistouch.record=!0)},this.onPointerMove=i=>{this.preventMistouch.record&&(this.preventMistouch.lastX=i.clientX,this.preventMistouch.lastY=i.clientY)},this.onPointerUp=()=>{this.preventMistouch.record&&(this.preventMistouch.record=!1,Math.abs(this.preventMistouch.lastX-this.preventMistouch.initialX)+Math.abs(this.preventMistouch.lastY-this.preventMistouch.initialY)<5&&this.endPrevention())},this._preventionContainer=document.createElement("div"),this._preventionContainer.className="prevention-container hidden";const n=document.createElement("div");n.className="prevention-banner",n.innerHTML="Frozen to prevent mistouch, click on to unlock.",this._preventionContainer.appendChild(n),t.appendChild(this._preventionContainer),this._container=t,this.preventMistouch={record:!1,lastX:0,lastY:0,initialX:0,initialY:0},e&&this.startPrevention(),window.addEventListener("pointerdown",this.onPointerDown),window.addEventListener("pointermove",this.onPointerMove),window.addEventListener("pointerup",this.onPointerUp)}get preventionContainer(){if(this._preventionContainer===null)throw l;return this._preventionContainer}get container(){if(this._container===null)throw l;return this._container}startPrevention(){this.preventionContainer.classList.remove("hidden"),this.container.classList.add("numb"),this.preventMt=!0,this.dispatchEvent(new CustomEvent("preventionStart"))}endPrevention(){this.preventMt=!1,this.preventionContainer.classList.add("hidden"),setTimeout(()=>this.container.classList.remove("numb"),50),this.dispatchEvent(new CustomEvent("preventionEnd"))}dispose(){window.removeEventListener("pointerdown",this.onPointerDown),window.removeEventListener("pointermove",this.onPointerMove),window.removeEventListener("pointerup",this.onPointerUp),this.preventionContainer.remove(),this._container=null,this._preventionContainer=null}}const T=".color-1{--border-color: rgba(255, 120, 129, .75);--background-color: rgba(255, 120, 129, .1);--active-color: rgba(255, 120, 129, 1)}.color-2{--border-color: rgba(251, 187, 131, .75);--background-color: rgba(251, 187, 131, .1);--active-color: rgba(251, 187, 131, 1)}.color-3{--border-color: rgba(255, 232, 139, .75);--background-color: rgba(255, 232, 139, .1);--active-color: rgba(255, 232, 139, 1)}.color-4{--border-color: rgba(124, 211, 124, .75);--background-color: rgba(124, 211, 124, .1);--active-color: rgba(124, 211, 124, 1)}.color-5{--border-color: rgba(134, 223, 226, .75);--background-color: rgba(134, 223, 226, .1);--active-color: rgba(134, 223, 226, 1)}.color-6{--border-color: rgba(203, 158, 255, .75);--background-color: rgba(203, 158, 255, .1);--active-color: rgba(203, 158, 255, 1)}.color-0{--border-color: rgba(140, 140, 140, .75);--background-color: rgba(140, 140, 140, .1);--active-color: rgba(140, 140, 140, 1)}.full,.preview-modal,.preview-modal-backdrop,.link-click-layer,.link-iframe,.prevention-container{top:0;left:0;width:100%;height:100%;position:absolute}.flex-center,.overlay-container.markdown-content,.prevention-container{display:flex;justify-content:center;align-items:center}.overlay-border{z-index:1}.container{--themeColor: rgb(254, 247, 167);--background-color: #141414;--contentTransition: color .2s, opacity .2s, text-shadow .2s, fill .2s;--containerTransition: background .2s, opacity .2s, box-shadow .2s, border .2s, filter .2s, backdrop-filter .2s;color:#fff;fill:#fff;stroke:#fff;position:relative;width:100%;height:100%;overflow:hidden;background-color:var(--background-color)}.container.numb,.container.numb *{pointer-events:none!important}.prevention-container{overflow:visible;transition:background .2s,opacity .2s,box-shadow .2s,border .2s,filter .2s,backdrop-filter .2s}.prevention-container.hidden{pointer-events:none}.prevention-container .prevention-banner{background:#0006;border-radius:12px;padding:12px;margin:12px;-webkit-backdrop-filter:blur(8px) saturate(1.5);backdrop-filter:blur(8px) saturate(1.5);border:2px solid rgba(140,140,140,.75);color:#fff;font-size:calc(14px + .3vw);line-height:calc(17px + .3vw);text-align:center}.main-canvas{width:100%;height:100%;transform-origin:top left}button{cursor:pointer;font-size:18px;height:32px;border:none;transition:var(--containerTransition);text-align:center;background-color:#444;width:32px;padding:5px 0}button svg{width:100%;height:100%}.overlays{position:absolute;top:0;left:0;width:100%;height:100%;transform-origin:top left;will-change:transform}.parsed-content-wrapper{font-family:sans-serif;box-sizing:border-box;max-width:100%;max-height:100%;padding:10px 6px;pointer-events:none;overflow:hidden;scrollbar-gutter:stable both-edges;display:flex;flex-direction:column;gap:12px}@supports not (scrollbar-gutter: stable both-edges){.parsed-content-wrapper{padding:10px}}.minimap-container{position:absolute;bottom:10px;right:10px;display:flex;pointer-events:none;transition:transform .2s}.minimap-container.collapsed{transform:translate(calc(100% - 32px))}@media (max-width: 768px){.minimap-container{transform:translateY(60px) translate(80px)}.minimap-container.collapsed{transform:translateY(60px) translate(calc(100% - 32px))}}.toggle-minimap{margin:auto 10px 0 0;pointer-events:auto}.collapsed .toggle-minimap{transform:rotate(180deg)}@media (max-width: 768px){.toggle-minimap{transform:translateY(-60px)}.collapsed .toggle-minimap{transform:translateY(-60px) rotate(180deg)}}.minimap{position:relative;width:200px;height:150px;overflow:hidden;border-radius:12px;background:#202020;-webkit-backdrop-filter:blur(8px) saturate(1.5);backdrop-filter:blur(8px) saturate(1.5);border:2px solid rgba(140,140,140,.75);transform-origin:0 0}@media (max-width: 768px){.minimap{transform:scale(.6)}}.minimap .minimap-canvas{width:100%;height:100%}.minimap .viewport-rectangle{position:absolute;top:0;left:0;pointer-events:none;border:2px solid #fff;border-radius:6px;box-sizing:border-box;background:transparent}.collapse-button{border-radius:8px;transition:transform .2s}.collapse-button:hover{background:#444c}.controls{position:absolute;top:10px;right:10px;display:flex;align-items:center;transition:transform .2s;border-radius:8px;gap:10px}.controls.collapsed{transform:translate(calc(100% - 32px))}.controls.collapsed .collapse-button{transform:rotate(180deg)}.controls .controls-content{display:flex;gap:1px;align-items:center;border-radius:8px;overflow:hidden;background:#333c}.controls button:hover{background:#555}.zoom-slider{width:100px;margin:0 10px}.overlay-container{position:absolute;box-sizing:border-box;border-radius:12px;overflow:hidden;background-color:var(--background-color);-webkit-user-select:none;user-select:none;contain:strict;transition:var(--containerTransition)}.overlay-container:hover{box-shadow:0 2px 12px #00000080}.overlay-container .overlay-border{box-sizing:border-box;pointer-events:none;position:absolute;top:0;left:0;width:100%;height:100%;border:2px solid var(--border-color);border-radius:12px;transition:var(--containerTransition)}.overlay-container img{width:100%;height:100%;object-fit:cover;pointer-events:none}.overlay-container.active .overlay-border{border:6px solid var(--active-color)}.overlay-container.markdown-content{position:absolute;padding:0 7px}.overlay-container.markdown-content.active .parsed-content-wrapper{overflow:auto;-webkit-user-select:text;user-select:text;pointer-events:auto}.overlay-container.markdown-content.rtl{direction:rtl;text-align:right}.link-iframe{contain-intrinsic-size:100% 100%;border:none}.link-click-layer{background:transparent;pointer-events:auto}.active .link-click-layer{pointer-events:none}.preview-modal-backdrop{-webkit-backdrop-filter:blur(8px) saturate(1.5);backdrop-filter:blur(8px) saturate(1.5);transition:var(--containerTransition)}.preview-modal-backdrop.hidden{pointer-events:none}.preview-modal{transition:var(--containerTransition);pointer-events:none}.preview-modal-close{position:absolute;right:10px;top:10px;background:none;border:none;font-size:24px;cursor:pointer;pointer-events:none}.preview-modal:not(.hidden) .preview-modal-close{pointer-events:auto}.preview-modal-content{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:fit-content;height:fit-content;pointer-events:none}.preview-modal:not(.hidden) .preview-modal-content{pointer-events:auto}.preview-modal-content>*{max-width:var(--preview-max-width);max-height:var(--preview-max-height);border:2px solid rgba(162,162,162,.5);box-shadow:0 2px 6px #0000004d;box-sizing:border-box}.preview-modal-content>img{border-radius:12px}.preview-modal-content>audio{border-radius:27px}.hidden{opacity:0}::-webkit-scrollbar{width:4px}::-webkit-scrollbar-track{background-color:transparent}::-webkit-scrollbar-thumb{border-radius:2px;background:#ffffff40}::-webkit-scrollbar-thumb:hover{background:#1e1e1ebf}p{font-size:16px;line-height:21px}.parsed-content-wrapper img{width:100%;border-radius:8px}h1{font-size:25px}h2{font-size:23px}h3{font-size:22px}h4{font-size:20px}h5{font-size:19px}h6{font-size:17px}p,h1,h2,h3,h4,h5,h6,ol,ul{margin:0}h1,h2{font-weight:800}h3,h4{font-weight:700}h5,h6{font-weight:600}code{background:#ffffff1a;padding:2px 4px;border-radius:8px}pre code{display:block;box-sizing:border-box;width:100%}pre:has(code),table{margin:6px 0}strong{color:#fe8e7c}em{color:#5affb2}a{text-decoration:none;color:#6dadd0;font-weight:800;font-style:italic;cursor:pointer;transition:var(--contentTransition)}a:hover{color:#86d3fd}hr{height:1px;width:100%;background-color:#fff3;border:none}li{margin:5px 0}ul{padding-left:16px}ol{padding-left:15px;padding-right:7.5px}table{border-collapse:collapse;border-spacing:0;border-radius:8px;border:1px solid rgba(255,255,255,.2);overflow:hidden;width:100%}table th,table td{border:1px solid rgba(255,255,255,.2);padding:6px 10px;background:#ffffff0f;text-align:left}table th{background:#ffffff1f;font-weight:700}";class k extends EventTarget{constructor(t,e=[],n=[]){super(),this.controls=null,this.minimap=null,this.mistouchPreventer=null,this.canvasBaseDir=null,this.animationId=null,this._canvasData=null,this._nodeMap=null,this._nodeBounds=null,this.spatialGrid=null,this.draw=()=>{if(!this.perFrame.needAnimating&&!this.perFrame.pan&&!this.perFrame.resize&&!this.perFrame.zoom&&!this.perFrame.smoothZoom){this.animationId=requestAnimationFrame(this.draw);return}if(this.perFrame.needAnimating&&(this.perFrame.needAnimating=!1),this.perFrame.zoom){this.perFrame.zoom=!1;const s=this.interactor.getZoomDump();this.zoom(s.factor,s.origin),this.interactor.resetZoomDump()}this.perFrame.pan&&(this.perFrame.pan=!1,this.pan(this.interactor.getPanDump()),this.interactor.resetPanDump()),this.perFrame.smoothZoom&&this.smoothZoom(),this.perFrame.resize&&this.resize(),this.renderer.redraw(this.offsetX,this.offsetY,this.scale),this.overlayManager.updateAllOverlays(this.offsetX,this.offsetY,this.scale),this.minimap&&this.minimap.updateViewportRectangle(this.offsetX,this.offsetY,this.scale),this.animationId=requestAnimationFrame(this.draw)},this.onClick=s=>{if(s instanceof CustomEvent){if(this.isUIControl(s.detail.target))return;const r=this.findNodeAt(this.C2W(this.C2C(s.detail.position))),a=this.judgeInteract(r);switch(a){case"non-interactive":this.overlayManager.select(null);break;case"select":if(!r)throw m;this.overlayManager.select(r.id);break;case"preview":if(!r)throw m;this.previewModal.showPreviewModal(r);break}r&&a!=="non-interactive"&&this.dispatchEvent(new CustomEvent("interact",{detail:{node:r.id,type:a}}))}else throw m},this.onResize=()=>{this.perFrame.resize=!0,this.perFrame.resizeScale.width=this.container.clientWidth,this.perFrame.resizeScale.height=this.container.clientHeight},this.onPan=()=>this.perFrame.pan=!0,this.onZoom=()=>this.perFrame.zoom=!0,this.onSlide=s=>{if(s instanceof CustomEvent)this.setScale(s.detail);else throw m},this.onMinimapExpanded=()=>{this.minimap&&this.minimap.updateViewportRectangle(this.offsetX,this.offsetY,this.scale)},this.onPreventionStart=()=>{this.animationId&&(cancelAnimationFrame(this.animationId),this.animationId=null)},this.onPreventionEnd=()=>this.animationId=requestAnimationFrame(this.draw),this.stopInteractor=()=>this.interactor.stop(),this.startInteractor=()=>this.interactor.start(),this.onToggleFullscreen=()=>this.shiftFullscreen(),this.zoomIn=()=>this.setScale(this.scale*1.2),this.zoomOut=()=>this.setScale(this.scale/1.2),this.resetView=()=>{const s=this.nodeBounds;if(!s)return{scale:1,offsetX:this.container.clientWidth/2,offsetY:this.container.clientHeight/2};const r=s.width+this.INITIAL_VIEWPORT_PADDING*2,a=s.height+this.INITIAL_VIEWPORT_PADDING*2,c=this.container.clientWidth,p=this.container.clientHeight,d=c/r,u=p/a,v=Math.round(Math.min(d,u)*1e3)/1e3,f=s.centerX,g=s.centerY,w={scale:v,offsetX:c/2-f*v,offsetY:p/2-g*v};this.scale=w.scale,this.offsetX=w.offsetX,this.offsetY=w.offsetY,this.controls&&this.controls.updateSlider(this.scale),this.perFrame.needAnimating=!0};const i=t.attachShadow({mode:"open"}),o=document.createElement("style");o.innerHTML=T,i.appendChild(o),this._container=document.createElement("div"),this._container.classList.add("container"),this.renderer=new L(this._container),this.overlayManager=new B(this._container),this.interactor=new z(this._container,{preventDefault:!0,proControlSchema:n.includes("proControlSchema")}),this.resizeObserver=new ResizeObserver(this.onResize),e.includes("minimap")?this.minimap=new D(this._container,n.includes("minimapCollapsed")):n.includes("minimapCollapsed")&&console.warn('CanvasViewer: "minimapCollapsed" option is not supported without minimap extension.'),n.includes("controlsHidden")?n.includes("controlsCollapsed")&&console.warn('CanvasViewer: "controlsCollapsed" option is overridden by "controlsHidden" option.'):this.controls=new S(this._container,n.includes("controlsCollapsed")),this.previewModal=new I(this._container),e.includes("mistouchPrevention")&&(this.mistouchPreventer=new P(this._container,!n.includes("noPreventionAtStart"))),i.appendChild(this._container),this.extensions=e,this.options=n,this.offsetX=0,this.offsetY=0,this.scale=1,this.ZOOM_SMOOTHNESS=.25,this.INITIAL_VIEWPORT_PADDING=100,this.GRID_CELL_SIZE=800,this.perFrame={needAnimating:!1,zoom:!1,smoothZoom:!1,targetScale:1,pan:!1,resize:!1,resizeScale:{width:0,height:0,lastCentreX:null,lastCentreY:null}}}get canvasData(){if(this._canvasData===null)throw l;return this._canvasData}get nodeMap(){if(this._nodeMap===null)throw l;return this._nodeMap}get nodeBounds(){if(this._nodeBounds===null)throw l;return this._nodeBounds}get container(){if(this._container===null)throw l;return this._container}isUIControl(t){return t.closest&&(t.closest(".controls")||t.closest("button")||t.closest("input"))}findNodeAt({x:t,y:e}){let n=[];if(!this.spatialGrid)n=this.canvasData.nodes;else{const i=Math.floor(t/this.GRID_CELL_SIZE),o=Math.floor(e/this.GRID_CELL_SIZE),s=`${i},${o}`;n=this.spatialGrid[s]||[]}for(const i of n)if(!(t<i.x||t>i.x+i.width||e<i.y||e>i.y+i.height||this.judgeInteract(i)==="non-interactive"))return i}C2C({x:t,y:e}){return{x:t-this.offsetX,y:e-this.offsetY}}C2W({x:t,y:e}){return{x:t/this.scale,y:e/this.scale}}middleScreen(){return{x:this.container.clientWidth/2,y:this.container.clientHeight/2}}judgeInteract(t){switch(t?t.type:"default"){case"text":return"select";case"link":return"select";case"file":if(!t||!t.file)throw m;return t.file.match(/\.md$/i)?"select":"preview";default:return"non-interactive"}}zoomToScale(t,e){const n=Math.max(Math.min(t,20),.05);if(n===this.scale)return;const i=this.C2C(e);this.offsetX=e.x-i.x*n/this.scale,this.offsetY=e.y-i.y*n/this.scale,this.scale=n,this.controls&&this.controls.updateSlider(this.scale)}buildSpatialGrid(){if(!(!this.canvasData||this.canvasData.nodes.length<50)){this.spatialGrid={};for(let t of this.canvasData.nodes){const e=Math.floor(t.x/this.GRID_CELL_SIZE),n=Math.floor((t.x+t.width)/this.GRID_CELL_SIZE),i=Math.floor(t.y/this.GRID_CELL_SIZE),o=Math.floor((t.y+t.height)/this.GRID_CELL_SIZE);for(let s=e;s<=n;s++)for(let r=i;r<=o;r++){const a=`${s},${r}`;this.spatialGrid[a]||(this.spatialGrid[a]=[]),this.spatialGrid[a].push(t)}}}}calculateNodeBounds(){if(!this.canvasData||!this.canvasData.nodes.length)return null;let t=1/0,e=1/0,n=-1/0,i=-1/0;this.canvasData.nodes.forEach(c=>{t=Math.min(t,c.x),e=Math.min(e,c.y),n=Math.max(n,c.x+c.width),i=Math.max(i,c.y+c.height)});const o=n-t,s=i-e,r=t+o/2,a=e+s/2;return{minX:t,minY:e,maxX:n,maxY:i,width:o,height:s,centerX:r,centerY:a}}resize(){this.perFrame.resize=!1;const t=this.perFrame.resizeScale;t.lastCentreX&&t.lastCentreY&&(this.offsetX+=t.width/2-t.lastCentreX,this.offsetY+=t.height/2-t.lastCentreY),t.lastCentreX=t.width/2,t.lastCentreY=t.height/2,this.renderer.resizeCanvasForDPR(),this.previewModal.resize(t.width,t.height)}pan({x:t,y:e}){this.offsetX+=t,this.offsetY+=e}zoom(t,e){const n=this.scale*t;this.zoomToScale(n,e)}smoothZoom(){const t=this.perFrame.targetScale-this.scale;let e;Math.abs(t)<this.perFrame.targetScale*.01+.002?(e=this.perFrame.targetScale,this.perFrame.smoothZoom=!1):e=Math.round((this.scale+t*this.ZOOM_SMOOTHNESS)*1e3)/1e3,this.zoomToScale(e,this.middleScreen())}setScale(t){this.perFrame.targetScale=t,this.perFrame.smoothZoom=!0}panTo(t,e){this.offsetX=this.container.clientWidth/2-t*this.scale,this.offsetY=this.container.clientHeight/2-e*this.scale,this.perFrame.needAnimating=!0}shiftFullscreen(t="toggle"){!document.fullscreenElement&&(t==="toggle"||t==="enter")?this.container.requestFullscreen():document.fullscreenElement&&(t==="toggle"||t==="exit")&&document.exitFullscreen(),this.controls&&this.controls.updateFullscreenBtn()}async loadCanvas(t){try{if(/^https?:\/\//.test(t))this.canvasBaseDir=t.substring(0,t.lastIndexOf("/")+1);else{const e=t.lastIndexOf("/");this.canvasBaseDir=e!==-1?t.substring(0,e+1):"./"}this._canvasData=await fetch(t).then(e=>e.json()),this._nodeMap={},this.canvasData.nodes.forEach(e=>{if(e.type==="file"&&e.file&&!e.file.includes("http")){const n=e.file.split("/");e.file=n[n.length-1]}this.nodeMap[e.id]=e}),this.buildSpatialGrid(),this._nodeBounds=this.calculateNodeBounds(),this.resetView(),this.resizeObserver.observe(this.container),this.interactor.start(),this.renderer.receiveData(this.nodeMap,this.canvasData),this.overlayManager.receiveData(this.canvasBaseDir,this.nodeMap),this.previewModal.receiveData(this.canvasBaseDir),this.interactor.addEventListener("trueClick",this.onClick),this.interactor.addEventListener("pan",this.onPan),this.interactor.addEventListener("zoom",this.onZoom),this.previewModal.addEventListener("previewModalShown",this.stopInteractor),this.previewModal.addEventListener("previewModalHidden",this.startInteractor),this.overlayManager.addEventListener("interactionStart",this.stopInteractor),this.overlayManager.addEventListener("interactionEnd",this.startInteractor),this.controls&&this.controls.addEventListener("zoomIn",this.zoomIn),this.controls&&this.controls.addEventListener("zoomOut",this.zoomOut),this.controls&&this.controls.addEventListener("slide",this.onSlide),this.controls&&this.controls.addEventListener("toggleFullscreen",this.onToggleFullscreen),this.controls&&this.controls.addEventListener("resetView",this.resetView),this.minimap&&(this.minimap.receiveData(this.nodeBounds,this.canvasData,this.nodeMap),this.minimap.addEventListener("minimapExpanded",this.onMinimapExpanded)),this.mistouchPreventer&&(this.mistouchPreventer.addEventListener("preventionStart",this.onPreventionStart),this.mistouchPreventer.addEventListener("preventionEnd",this.onPreventionEnd)),(!this.extensions.includes("mistouchPrevention")||this.options.includes("noPreventionAtStart"))&&(this.animationId=requestAnimationFrame(this.draw)),this.dispatchEvent(new CustomEvent("loaded",{detail:this.canvasData}))}catch(e){console.error("Failed to load canvas data:",e)}}dispose(){this.interactor.removeEventListener("trueClick",this.onClick),this.interactor.removeEventListener("pan",this.onPan),this.interactor.removeEventListener("zoom",this.onZoom),this.previewModal.removeEventListener("previewModalShown",this.stopInteractor),this.previewModal.removeEventListener("previewModalHidden",this.startInteractor),this.overlayManager.removeEventListener("interactionStart",this.stopInteractor),this.overlayManager.removeEventListener("interactionEnd",this.startInteractor),this.controls&&this.controls.removeEventListener("zoomIn",this.zoomIn),this.controls&&this.controls.removeEventListener("zoomOut",this.zoomOut),this.controls&&this.controls.removeEventListener("slide",this.onSlide),this.controls&&this.controls.removeEventListener("toggleFullscreen",this.onToggleFullscreen),this.controls&&this.controls.removeEventListener("resetView",this.resetView),this.minimap&&this.minimap.removeEventListener("minimapExpanded",this.onMinimapExpanded),this.mistouchPreventer&&(this.mistouchPreventer.removeEventListener("preventionStart",this.onPreventionStart),this.mistouchPreventer.removeEventListener("preventionEnd",this.onPreventionEnd)),this.animationId&&cancelAnimationFrame(this.animationId),this.resizeObserver.disconnect(),this.interactor.dispose(),this.previewModal.dispose(),this.controls&&this.controls.dispose(),this.minimap&&this.minimap.dispose(),this.mistouchPreventer&&this.mistouchPreventer.dispose(),this.overlayManager.dispose(),this.renderer.dispose(),this.container.remove(),this._container=null}}class O extends HTMLElement{constructor(){super(),this.style.display="block",this.style.overflow="hidden",this.style.maxWidth="120vw",this.style.maxHeight="120vh";const t=this.getAttribute("extensions"),e=this.getAttribute("options"),n=t?t.split(" "):[],i=e?e.split(" "):[];this.viewer=new k(this,n,i)}connectedCallback(){const t=this.getAttribute("src");if(!t)throw new Error("No source canvas path provided.");this.viewer&&this.viewer.loadCanvas(t)}disconnectedCallback(){this.viewer&&this.viewer.dispose(),this.remove()}}customElements.define("canvas-viewer",O);module.exports=k;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export default class canvasViewer extends EventTarget {
|
|
2
|
+
private controls;
|
|
3
|
+
private minimap;
|
|
4
|
+
private mistouchPreventer;
|
|
5
|
+
private canvasBaseDir;
|
|
6
|
+
private animationId;
|
|
7
|
+
private _canvasData;
|
|
8
|
+
private _nodeMap;
|
|
9
|
+
private _nodeBounds;
|
|
10
|
+
private _container;
|
|
11
|
+
private renderer;
|
|
12
|
+
private overlayManager;
|
|
13
|
+
private interactor;
|
|
14
|
+
private resizeObserver;
|
|
15
|
+
private previewModal;
|
|
16
|
+
private extensions;
|
|
17
|
+
private options;
|
|
18
|
+
private offsetX;
|
|
19
|
+
private offsetY;
|
|
20
|
+
private scale;
|
|
21
|
+
private spatialGrid;
|
|
22
|
+
private ZOOM_SMOOTHNESS;
|
|
23
|
+
private INITIAL_VIEWPORT_PADDING;
|
|
24
|
+
private perFrame;
|
|
25
|
+
private GRID_CELL_SIZE;
|
|
26
|
+
private get canvasData();
|
|
27
|
+
private get nodeMap();
|
|
28
|
+
private get nodeBounds();
|
|
29
|
+
private get container();
|
|
30
|
+
constructor(container: HTMLElement, extensions?: Array<string>, options?: Array<string>);
|
|
31
|
+
private isUIControl;
|
|
32
|
+
private findNodeAt;
|
|
33
|
+
private C2C;
|
|
34
|
+
private C2W;
|
|
35
|
+
private middleScreen;
|
|
36
|
+
private judgeInteract;
|
|
37
|
+
private zoomToScale;
|
|
38
|
+
private buildSpatialGrid;
|
|
39
|
+
private calculateNodeBounds;
|
|
40
|
+
private draw;
|
|
41
|
+
private resize;
|
|
42
|
+
private pan;
|
|
43
|
+
private zoom;
|
|
44
|
+
private smoothZoom;
|
|
45
|
+
private onClick;
|
|
46
|
+
private onResize;
|
|
47
|
+
private onPan;
|
|
48
|
+
private onZoom;
|
|
49
|
+
private onSlide;
|
|
50
|
+
private onMinimapExpanded;
|
|
51
|
+
private onPreventionStart;
|
|
52
|
+
private onPreventionEnd;
|
|
53
|
+
private stopInteractor;
|
|
54
|
+
private startInteractor;
|
|
55
|
+
private onToggleFullscreen;
|
|
56
|
+
zoomIn: () => void;
|
|
57
|
+
zoomOut: () => void;
|
|
58
|
+
setScale(scale: number): void;
|
|
59
|
+
panTo(x: number, y: number): void;
|
|
60
|
+
shiftFullscreen(option?: string): void;
|
|
61
|
+
loadCanvas(path: string): Promise<void>;
|
|
62
|
+
resetView: () => {
|
|
63
|
+
scale: number;
|
|
64
|
+
offsetX: number;
|
|
65
|
+
offsetY: number;
|
|
66
|
+
} | undefined;
|
|
67
|
+
dispose(): void;
|
|
68
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{marked as t}from"marked";const e=new Error("This error is unexpected, probably caused by file corruption. If you assure the error is not caused by accident, please contact the author and show how to reproduce."),i=new Error("Resource hasn't been set up or has been disposed.");class n{constructor(t){this._nodeMap=null,this._canvasData=null,this.ARROW_LENGTH=12,this.ARROW_WIDTH=7,this.FILE_NODE_RADIUS=12,this.FONT_COLOR="#fff",this.CSS_ZOOM_REDRAW_INTERVAL=500,this._canvas=document.createElement("canvas"),this._canvas.className="main-canvas",this._container=t,this._container.appendChild(this._canvas),this._ctx=this._canvas.getContext("2d"),this.zoomInOptimize={lastDrawnScale:0,lastDrawnViewport:{left:0,right:0,top:0,bottom:0},timeout:null,lastCallTime:0},a(this._canvas,t.offsetWidth,t.offsetHeight)}get nodeMap(){if(null===this._nodeMap)throw i;return this._nodeMap}get canvasData(){if(null===this._canvasData)throw i;return this._canvasData}get canvas(){if(null===this._canvas)throw i;return this._canvas}get ctx(){if(null===this._ctx)throw i;return this._ctx}get container(){if(null===this._container)throw i;return this._container}receiveData(t,e){this._nodeMap=t,this._canvasData=e}resizeCanvasForDPR(){a(this.canvas,this.container.offsetWidth,this.container.offsetHeight)}redraw(t,e,i){this.zoomInOptimize.timeout&&(clearTimeout(this.zoomInOptimize.timeout),this.zoomInOptimize.timeout=null);const n=Date.now(),s=this.getCurrentViewport(t,e,i);if(this.isViewportInside(s,this.zoomInOptimize.lastDrawnViewport)&&i!==this.zoomInOptimize.lastDrawnScale&&n-this.zoomInOptimize.lastCallTime<this.CSS_ZOOM_REDRAW_INTERVAL)return this.zoomInOptimize.timeout=setTimeout(()=>{this.trueRedraw(t,e,i,s),this.zoomInOptimize.lastCallTime=n,this.zoomInOptimize.timeout=null},60),void this.fakeRedraw(s,i);this.zoomInOptimize.lastCallTime=n,this.trueRedraw(t,e,i,s)}dispose(){this.zoomInOptimize.timeout&&clearTimeout(this.zoomInOptimize.timeout),this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height),this.canvas.remove(),this.zoomInOptimize.timeout=null,this._ctx=null,this._canvas=null,this._container=null,this._canvasData=null,this._nodeMap=null}trueRedraw(t,e,i,n){this.zoomInOptimize.lastDrawnViewport=n,this.zoomInOptimize.lastDrawnScale=i,this.canvas.style.transform="",this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height),this.ctx.save(),this.ctx.translate(t,e),this.ctx.scale(i,i),this.canvasData.nodes.forEach(n=>n.inViewport=this.isNodeInViewport(n,t,e,i)),this.canvasData.nodes.forEach(t=>{switch(t.type){case"group":this.drawGroup(t,i);break;case"file":this.drawFileNode(t)}}),this.canvasData.edges.forEach(t=>this.drawEdge(t)),this.ctx.restore()}fakeRedraw(t,e){const i=e/this.zoomInOptimize.lastDrawnScale,n=(this.zoomInOptimize.lastDrawnViewport.left-t.left)*e,s=(this.zoomInOptimize.lastDrawnViewport.top-t.top)*e;this.canvas.style.transform=`translate(${n}px, ${s}px) scale(${i})`}isViewportInside(t,e){return t.left>e.left&&t.top>e.top&&t.right<e.right&&t.bottom<e.bottom}isNodeInViewport(t,e,i,n,s=200){const o=-e/n-s,r=-i/n-s,a=o+this.container.clientWidth/n+2*s,l=r+this.container.clientHeight/n+2*s;return t.x+t.width>o&&t.x<a&&t.y+t.height>r&&t.y<l}getCurrentViewport(t,e,i){const n=-t/i,s=-e/i;return{left:n,top:s,right:n+this.container.clientWidth/i,bottom:s+this.container.clientHeight/i}}drawLabelBar(t,e,i,n,s){const o=30*s,r=6*s,a=8*s,l=16*s,h=6*s;this.ctx.save(),this.ctx.translate(t,e),this.ctx.scale(1/s,1/s),this.ctx.font=`${l}px 'Inter', sans-serif`;const c=this.ctx.measureText(i).width+2*h;this.ctx.translate(0,-o-a),this.ctx.fillStyle=n,this.ctx.beginPath(),this.ctx.moveTo(r,0),this.ctx.lineTo(c-r,0),this.ctx.quadraticCurveTo(c,0,c,r),this.ctx.lineTo(c,o-r),this.ctx.quadraticCurveTo(c,o,c-r,o),this.ctx.lineTo(r,o),this.ctx.quadraticCurveTo(0,o,0,o-r),this.ctx.lineTo(0,r),this.ctx.quadraticCurveTo(0,0,r,0),this.ctx.closePath(),this.ctx.fill(),this.ctx.fillStyle=this.FONT_COLOR,this.ctx.fillText(i,h,.65*o),this.ctx.restore()}drawNodeBackground(t){const e=r(t.color),i=this.FILE_NODE_RADIUS;this.ctx.globalAlpha=1,this.ctx.fillStyle=e.background,o(this.ctx,t.x+1,t.y+1,t.width-2,t.height-2,i),this.ctx.fill(),this.ctx.strokeStyle=e.border,this.ctx.lineWidth=2,o(this.ctx,t.x,t.y,t.width,t.height,i),this.ctx.stroke()}drawGroup(t,e){this.drawNodeBackground(t),t.label&&this.drawLabelBar(t.x,t.y,t.label,r(t.color).border,e)}drawFileNode(t){if(!t.file)throw e;t.file.match(/\.md|png|jpg|jpeg|gif|svg$/i)||(this.drawNodeBackground(t),t.file.match(/\.mp3$/i)&&(this.ctx.fillStyle=this.FONT_COLOR,this.ctx.textAlign="center",this.ctx.textBaseline="middle",this.ctx.fillText("🎵 Click to Preview 🎵",t.x+t.width/2,t.y+t.height/2),this.ctx.textAlign="left")),this.ctx.fillStyle=this.FONT_COLOR,this.ctx.font="16px sans-serif",this.ctx.fillText(t.file,t.x+5,t.y-10)}drawEdge(t){const{fromNode:i,toNode:n}=this.getEdgeNodes(t);if(!i||!n)throw e;const[r,a]=s(i,t.fromSide),[l,h]=s(n,t.toSide);let[c,d,p,m]=[0,0,0,0];if(t.controlPoints?[c,d,p,m]=t.controlPoints:([c,d,p,m]=this.getControlPoints(r,a,l,h,t.fromSide,t.toSide),t.controlPoints=[c,d,p,m]),this.drawCurvedPath(r,a,l,h,c,d,p,m),this.drawArrowhead(l,h,p,m),t.label){const e=Math.pow(.5,3)*r+3*Math.pow(.5,2)*.5*c+.375*p+Math.pow(.5,3)*l,i=Math.pow(.5,3)*a+3*Math.pow(.5,2)*.5*d+.375*m+Math.pow(.5,3)*h;this.ctx.font="18px sans-serif";const n=this.ctx.measureText(t.label).width+16,s=20;this.ctx.fillStyle="#222",this.ctx.beginPath(),o(this.ctx,e-n/2,i-s/2-2,n,s,4),this.ctx.fill(),this.ctx.fillStyle="#ccc",this.ctx.textAlign="center",this.ctx.textBaseline="middle",this.ctx.fillText(t.label,e,i-2),this.ctx.textAlign="left",this.ctx.textBaseline="alphabetic"}}getEdgeNodes(t){return{fromNode:this.nodeMap[t.fromNode],toNode:this.nodeMap[t.toNode]}}getControlPoints(t,e,i,n,s,o){const r=i-t,a=n-e,l=Math.min(Math.abs(r),Math.abs(a))+.3*Math.max(Math.abs(r),Math.abs(a)),h=(c=.5*l,Math.max(60,Math.min(300,c)));var c;let d=t,p=e,m=i,v=n;switch(s){case"top":p=e-h;break;case"bottom":p=e+h;break;case"left":d=t-h;break;case"right":d=t+h}switch(o){case"top":v=n-h;break;case"bottom":v=n+h;break;case"left":m=i-h;break;case"right":m=i+h}return[d,p,m,v]}drawCurvedPath(t,e,i,n,s,o,r,a){this.ctx.beginPath(),this.ctx.moveTo(t,e),this.ctx.bezierCurveTo(s,o,r,a,i,n),this.ctx.strokeStyle="#ccc",this.ctx.lineWidth=2,this.ctx.stroke()}drawArrowhead(t,e,i,n){const s=t-i,o=e-n,r=Math.sqrt(s*s+o*o);if(0===r)return;const a=s/r,l=o/r,h=t-a*this.ARROW_LENGTH-l*this.ARROW_WIDTH,c=e-l*this.ARROW_LENGTH+a*this.ARROW_WIDTH,d=t-a*this.ARROW_LENGTH+l*this.ARROW_WIDTH,p=e-l*this.ARROW_LENGTH-a*this.ARROW_WIDTH;this.ctx.beginPath(),this.ctx.fillStyle="#ccc",this.ctx.moveTo(t,e),this.ctx.lineTo(h,c),this.ctx.lineTo(d,p),this.ctx.closePath(),this.ctx.fill()}}function s(t,e){const i=t.x+t.width/2,n=t.y+t.height/2;switch(e){case"top":return[i,t.y];case"bottom":return[i,t.y+t.height];case"left":return[t.x,n];case"right":return[t.x+t.width,n];default:return[i,n]}}function o(t,e,i,n,s,o){t.beginPath(),t.moveTo(e+o,i),t.lineTo(e+n-o,i),t.quadraticCurveTo(e+n,i,e+n,i+o),t.lineTo(e+n,i+s-o),t.quadraticCurveTo(e+n,i+s,e+n-o,i+s),t.lineTo(e+o,i+s),t.quadraticCurveTo(e,i+s,e,i+s-o),t.lineTo(e,i+o),t.quadraticCurveTo(e,i,e+o,i),t.closePath()}function r(t){let e=null;switch(t){case"1":e="rgba(255, 120, 129, ?)";break;case"2":e="rgba(251, 187, 131, ?)";break;case"3":e="rgba(255, 232, 139, ?)";break;case"4":e="rgba(124, 211, 124, ?)";break;case"5":e="rgba(134, 223, 226, ?)";break;case"6":e="rgba(203, 158, 255, ?)";break;default:e="rgba(140, 140, 140, ?)"}return{border:e.replace("?","0.75"),background:e.replace("?","0.1"),active:e.replace("?","1")}}function a(t,i,n){const s=window.devicePixelRatio||1,o=t.getContext("2d");if(!o)throw e;t.width=Math.round(i*s),t.height=Math.round(n*s),o.setTransform(1,0,0,1,0,0),o.scale(s,s)}class l extends EventTarget{constructor(t,i){super(),this.pointers=new Map,this.onPointerDown=t=>{if(!(this.pointers.size>=2)&&(t.isPrimary&&this.pointers.clear(),this.pointers.set(t.pointerId,{startX:t.clientX,startY:t.clientY,lastX:t.clientX,lastY:t.clientY,interrupted:!1,target:t.target}),2===this.pointers.size)){const i=this.getNthValue(0),n=this.pointers.get(t.pointerId);if(!i||!n)throw e;i.interrupted=!0,n.interrupted=!0,this.pinchZoomState.lastDistance=this.getPointerDistance(),this.pinchZoomState.lastMidpoint=this.S2C(this.getPointerMidpoint())}},this.onPointerMove=t=>{const i=this.pointers.get(t.pointerId);if(i){if(1===this.pointers.size){const e=t.clientX-i.lastX,n=t.clientY-i.lastY;this.dispatchPanEvent({x:e,y:n})}if(this.pointers.set(t.pointerId,{startX:i.startX,startY:i.startY,lastX:t.clientX,lastY:t.clientY,interrupted:i.interrupted,target:i.target}),2===this.pointers.size){const t=this.getPointerDistance(),i=this.getPointerMidpoint();if(!t||!i)throw e;let n=t/this.pinchZoomState.lastDistance;this.pinchZoomState.lastDistance=t;const s=this.S2C(i),o=s.x-this.pinchZoomState.lastMidpoint.x,r=s.y-this.pinchZoomState.lastMidpoint.y;this.pinchZoomState.lastMidpoint=s,this.dispatchPanEvent({x:o,y:r}),this.dispatchZoomEvent(n,s)}}},this.onPointerUp=t=>{const e=this.pointers.get(t.pointerId);if(e&&(this.pointers.delete(t.pointerId),0===this.pointers.size&&!e.interrupted&&Math.abs(e.startX-t.clientX)+Math.abs(e.startY-t.clientY)<5)){const i=this.S2C({x:t.clientX,y:t.clientY}),n=new CustomEvent("trueClick",{detail:{position:i,target:e.target}});this.dispatchEvent(n)}},this.onWheel=t=>{if(!this.lockControlSchema&&!this.proControlSchema&&(t.ctrlKey||Math.abs(t.deltaX)>Math.abs(t.deltaY))&&(this.proControlSchema=!0),this.preventDefault&&t.preventDefault(),this.proControlSchema&&!t.ctrlKey)this.dispatchPanEvent({x:t.deltaX,y:t.deltaY});else{const e=1-this.zoomFactor*t.deltaY,i=this.S2C({x:t.clientX,y:t.clientY});this.dispatchZoomEvent(e,i)}},this.preventDefaultFunction=t=>t.preventDefault,this._monitoringElement=t;const n=i||{};this.preventDefault=n.preventDefault||!1,this.proControlSchema=n.proControlSchema||!1,this.zoomFactor=n.zoomFactor||.002,this.lockControlSchema=n.lockControlSchema||!1,this.pointers.clear(),this.pinchZoomState={lastDistance:0,lastMidpoint:{x:0,y:0}},this.panDump={x:0,y:0},this.zoomDump={factor:1,origin:{x:0,y:0}}}get monitoringElement(){if(null===this._monitoringElement)throw i;return this._monitoringElement}getNthValue(t){if(t<0||t>=this.pointers.size)throw e;let i=0;for(const e of this.pointers.values()){if(i===t)return e;i++}}getPointerDistance(){const t=this.getNthValue(0),i=this.getNthValue(1);if(!t||!i)throw e;const n=t.lastX-i.lastX,s=t.lastY-i.lastY;return Math.sqrt(n*n+s*s)}getPointerMidpoint(){const t=this.getNthValue(0),i=this.getNthValue(1);if(!t||!i)throw e;return{x:(t.lastX+i.lastX)/2,y:(t.lastY+i.lastY)/2}}S2C({x:t,y:e}){const i=this.monitoringElement.getBoundingClientRect();return{x:t-i.left,y:e-i.top}}dispatchPanEvent(t){const e={x:this.round(t.x,1),y:this.round(t.y,1)};this.panDump.x+=e.x,this.panDump.y+=e.y;const i=new CustomEvent("pan",{detail:e});this.dispatchEvent(i)}dispatchZoomEvent(t,e){const i=this.round(t,4);this.zoomDump.factor*=i,this.zoomDump.origin=e;const n=new CustomEvent("zoom",{detail:{factor:i,origin:e}});this.dispatchEvent(n)}round(t,e){const i=10**e;return Math.round(t*i)/i}getZoomDump(){return this.zoomDump}resetZoomDump(){this.zoomDump.factor=1}getPanDump(){return this.panDump}resetPanDump(){this.panDump={x:0,y:0}}stop(){this.monitoringElement.removeEventListener("pointerdown",this.onPointerDown),window.removeEventListener("pointermove",this.onPointerMove),window.removeEventListener("pointerup",this.onPointerUp),this.monitoringElement.removeEventListener("wheel",this.onWheel),this.preventDefault&&(this.monitoringElement.style.touchAction="",this.monitoringElement.removeEventListener("gesturestart",this.preventDefaultFunction),this.monitoringElement.removeEventListener("gesturechange",this.preventDefaultFunction))}start(){this.monitoringElement.addEventListener("pointerdown",this.onPointerDown),window.addEventListener("pointermove",this.onPointerMove),window.addEventListener("pointerup",this.onPointerUp),this.monitoringElement.addEventListener("wheel",this.onWheel,this.preventDefault?{passive:!1}:{}),this.preventDefault&&(this.monitoringElement.style.touchAction="none",this.monitoringElement.addEventListener("gesturestart",this.preventDefaultFunction,{passive:!1}),this.monitoringElement.addEventListener("gesturechange",this.preventDefaultFunction,{passive:!1}))}dispose(){this.stop(),this.pointers.clear(),this._monitoringElement=null}}class h extends EventTarget{constructor(t,e=!1){super(),this._canvasData=null,this._nodeMap=null,this._nodeBounds=null,this.toggleVisisbility=()=>{this.isMinimapVisible=!this.isMinimapVisible,this.minimapContainer.classList.toggle("collapsed"),this.dispatchEvent(new CustomEvent(this.isMinimapVisible?"minimapExpanded":"minimapCollapsed"))},this._minimapContainer=document.createElement("div"),this._minimapContainer.className="minimap-container",this._toggleMinimapBtn=document.createElement("button"),this._toggleMinimapBtn.className="toggle-minimap collapse-button",this._toggleMinimapBtn.innerHTML='<svg viewBox="-3.6 -3.6 31.2 31.2" stroke-width=".4"><path d="M15.707 4.293a1 1 0 0 1 0 1.414L9.414 12l6.293 6.293a1 1 0 0 1-1.414 1.414l-7-7a1 1 0 0 1 0-1.414l7-7a1 1 0 0 1 1.414 0Z" /></svg>',this._minimapContainer.appendChild(this._toggleMinimapBtn),this._minimap=document.createElement("div"),this._minimap.className="minimap";const i=document.createElement("canvas");i.className="minimap-canvas",i.width=200,i.height=150,this._minimap.appendChild(i),this._minimapCtx=i.getContext("2d"),this._viewportRectangle=document.createElement("div"),this._viewportRectangle.className="viewport-rectangle",this._minimap.appendChild(this._viewportRectangle),this._minimapContainer.appendChild(this._minimap),t.appendChild(this._minimapContainer),this._container=t,this.isMinimapVisible=!e,this._minimapContainer.classList.toggle("collapsed",e),this.minimapCache={scale:1,centerX:0,centerY:0},this._toggleMinimapBtn.addEventListener("click",this.toggleVisisbility),a(i,i.width,i.height)}get minimapCtx(){if(null===this._minimapCtx)throw i;return this._minimapCtx}get canvasData(){if(null===this._canvasData)throw i;return this._canvasData}get container(){if(null===this._container)throw i;return this._container}get minimap(){if(null===this._minimap)throw i;return this._minimap}get nodeBounds(){if(null===this._nodeBounds)throw i;return this._nodeBounds}get nodeMap(){if(null===this._nodeMap)throw i;return this._nodeMap}get viewportRectangle(){if(null===this._viewportRectangle)throw i;return this._viewportRectangle}get minimapContainer(){if(null===this._minimapContainer)throw i;return this._minimapContainer}get toggleMinimapBtn(){if(null===this._toggleMinimapBtn)throw i;return this._toggleMinimapBtn}receiveData(t,e,i){this._nodeBounds=t,this._canvasData=e,this._nodeMap=i,this.drawMinimap()}drawMinimap(){const t=this.nodeBounds;if(!t)return;const e=this.minimap.clientWidth,i=this.minimap.clientHeight,n=e/t.width,s=i/t.height;this.minimapCache.scale=.9*Math.min(n,s),this.minimapCache.centerX=e/2,this.minimapCache.centerY=i/2,this.minimapCtx.clearRect(0,0,e,i),this.minimapCtx.save(),this.minimapCtx.translate(this.minimapCache.centerX,this.minimapCache.centerY),this.minimapCtx.scale(this.minimapCache.scale,this.minimapCache.scale),this.minimapCtx.translate(-t.centerX,-t.centerY);for(let t of this.canvasData.edges)this.drawMinimapEdge(t);for(let t of this.canvasData.nodes)this.drawMinimapNode(t);this.minimapCtx.restore()}drawMinimapNode(t){const e=r(t.color);this.minimapCtx.fillStyle=e.border,this.minimapCtx.globalAlpha=.3,o(this.minimapCtx,t.x,t.y,t.width,t.height,25),this.minimapCtx.fill(),this.minimapCtx.globalAlpha=1}drawMinimapEdge(t){const e=this.nodeMap[t.fromNode],i=this.nodeMap[t.toNode];if(!e||!i)return;const[n,o]=s(e,t.fromSide),[r,a]=s(i,t.toSide);this.minimapCtx.beginPath(),this.minimapCtx.moveTo(n,o),this.minimapCtx.lineTo(r,a),this.minimapCtx.strokeStyle="#555",this.minimapCtx.lineWidth=10,this.minimapCtx.stroke()}updateViewportRectangle(t,e,i){if(!this.isMinimapVisible)return;const n=this.nodeBounds;if(!n)return;const s=this.container.clientWidth/i,o=this.container.clientHeight/i,r=-t/i+this.container.clientWidth/(2*i),a=-e/i+this.container.clientHeight/(2*i),l=this.minimapCache.centerX+(r-s/2-n.centerX)*this.minimapCache.scale,h=this.minimapCache.centerY+(a-o/2-n.centerY)*this.minimapCache.scale,c=s*this.minimapCache.scale,d=o*this.minimapCache.scale;this.viewportRectangle.style.left=l+"px",this.viewportRectangle.style.top=h+"px",this.viewportRectangle.style.width=c+"px",this.viewportRectangle.style.height=d+"px"}dispose(){this.toggleMinimapBtn.removeEventListener("click",this.toggleVisisbility),this.minimapCtx.clearRect(0,0,this.minimap.clientWidth,this.minimap.clientHeight),this.minimapContainer.remove(),this._minimapContainer=null,this._minimapCtx=null,this._toggleMinimapBtn=null,this._canvasData=null,this._container=null,this._nodeMap=null,this._nodeBounds=null,this._viewportRectangle=null,this._minimap=null}}class c extends EventTarget{constructor(t,e=!1){super(),this.toggleCollapse=()=>this.controlsPanel.classList.toggle("collapsed"),this.zoomIn=()=>this.dispatchEvent(new CustomEvent("zoomIn")),this.zoomOut=()=>this.dispatchEvent(new CustomEvent("zoomOut")),this.slide=()=>this.dispatchEvent(new CustomEvent("slide",{detail:Math.pow(1.1,Number(this.zoomSlider.value))})),this.resetView=()=>this.dispatchEvent(new CustomEvent("resetView")),this.toggleFullscreen=()=>this.dispatchEvent(new CustomEvent("toggleFullscreen")),this.updateSlider=t=>this.zoomSlider.value=String(this.scaleToSlider(t)),this.scaleToSlider=t=>Math.log(t)/Math.log(1.1),this._controlsPanel=document.createElement("div"),this._controlsPanel.className="controls",this._controlsPanel.classList.toggle("collapsed",e),this._toggleCollapseBtn=document.createElement("button"),this._toggleCollapseBtn.className="collapse-button",this._toggleCollapseBtn.innerHTML='<svg viewBox="-3.6 -3.6 31.2 31.2" stroke-width=".4"><path d="M15.707 4.293a1 1 0 0 1 0 1.414L9.414 12l6.293 6.293a1 1 0 0 1-1.414 1.414l-7-7a1 1 0 0 1 0-1.414l7-7a1 1 0 0 1 1.414 0Z" /></svg>',this._controlsPanel.appendChild(this._toggleCollapseBtn);const i=document.createElement("div");i.className="controls-content",this._toggleFullscreenBtn=document.createElement("button"),this._toggleFullscreenBtn.innerHTML='<svg viewBox="-5.28 -5.28 34.56 34.56" fill="none"><path d="M4 9V5.6c0-.56 0-.84.109-1.054a1 1 0 0 1 .437-.437C4.76 4 5.04 4 5.6 4H9M4 15v3.4c0 .56 0 .84.109 1.054a1 1 0 0 0 .437.437C4.76 20 5.04 20 5.6 20H9m6-16h3.4c.56 0 .84 0 1.054.109a1 1 0 0 1 .437.437C20 4.76 20 5.04 20 5.6V9m0 6v3.4c0 .56 0 .84-.109 1.054a1 1 0 0 1-.437.437C19.24 20 18.96 20 18.4 20H15" stroke-width="2.4" stroke-linecap="round"/></svg>',i.appendChild(this._toggleFullscreenBtn),this._zoomOutBtn=document.createElement("button"),this._zoomOutBtn.innerHTML='<svg viewBox="-1.2 -1.2 26.4 26.4"><path d="M6 12h12" stroke-width="2" stroke-linecap="round" /></svg>',i.appendChild(this._zoomOutBtn),this._zoomSlider=document.createElement("input"),this._zoomSlider.type="range",this._zoomSlider.className="zoom-slider",this._zoomSlider.min="-30",this._zoomSlider.max="30",this._zoomSlider.value="0",i.appendChild(this._zoomSlider),this._zoomInBtn=document.createElement("button"),this._zoomInBtn.innerHTML='<svg viewBox="-1.2 -1.2 26.4 26.4"><path d="M6 12h12m-6-6v12" stroke-width="2" stroke-linecap="round" /></svg>',i.appendChild(this._zoomInBtn),this._resetViewBtn=document.createElement("button"),this._resetViewBtn.innerHTML='<svg viewBox="-6 -6 30 30" stroke-width=".08"><path d="m14.955 7.986.116.01a1 1 0 0 1 .85 1.13 8 8 0 0 1-13.374 4.728l-.84.84c-.63.63-1.707.184-1.707-.707V10h3.987c.89 0 1.337 1.077.707 1.707l-.731.731a6 6 0 0 0 8.347-.264 6 6 0 0 0 1.63-3.33 1 1 0 0 1 1.131-.848zM11.514.813a8 8 0 0 1 1.942 1.336l.837-.837c.63-.63 1.707-.184 1.707.707V6h-3.981c-.89 0-1.337-1.077-.707-1.707l.728-.729a6 6 0 0 0-9.98 3.591 1 1 0 1 1-1.98-.281A8 8 0 0 1 11.514.813Z" /></svg>',i.appendChild(this._resetViewBtn),this._controlsPanel.appendChild(i),t.appendChild(this._controlsPanel),this._toggleCollapseBtn.addEventListener("click",this.toggleCollapse),this._zoomInBtn.addEventListener("click",this.zoomIn),this._zoomOutBtn.addEventListener("click",this.zoomOut),this._zoomSlider.addEventListener("input",this.slide),this._resetViewBtn.addEventListener("click",this.resetView),this._toggleFullscreenBtn.addEventListener("click",this.toggleFullscreen)}get controlsPanel(){if(null===this._controlsPanel)throw i;return this._controlsPanel}get toggleCollapseBtn(){if(null===this._toggleCollapseBtn)throw i;return this._toggleCollapseBtn}get toggleFullscreenBtn(){if(null===this._toggleFullscreenBtn)throw i;return this._toggleFullscreenBtn}get zoomOutBtn(){if(null===this._zoomOutBtn)throw i;return this._zoomOutBtn}get zoomSlider(){if(null===this._zoomSlider)throw i;return this._zoomSlider}get zoomInBtn(){if(null===this._zoomInBtn)throw i;return this._zoomInBtn}get resetViewBtn(){if(null===this._resetViewBtn)throw i;return this._resetViewBtn}updateFullscreenBtn(){null===document.fullscreenElement?this.toggleFullscreenBtn.innerHTML='<svg viewBox="-40.32 -40.32 176.64 176.64"><path d="M30 60H6a6 6 0 0 0 0 12h18v18a6 6 0 0 0 12 0V66a5.997 5.997 0 0 0-6-6Zm60 0H66a5.997 5.997 0 0 0-6 6v24a6 6 0 0 0 12 0V72h18a6 6 0 0 0 0-12ZM66 36h24a6 6 0 0 0 0-12H72V6a6 6 0 0 0-12 0v24a5.997 5.997 0 0 0 6 6ZM30 0a5.997 5.997 0 0 0-6 6v18H6a6 6 0 0 0 0 12h24a5.997 5.997 0 0 0 6-6V6a5.997 5.997 0 0 0-6-6Z"/></svg>':this.toggleFullscreenBtn.innerHTML='<svg viewBox="-5.28 -5.28 34.56 34.56" fill="none"><path d="M4 9V5.6c0-.56 0-.84.109-1.054a1 1 0 0 1 .437-.437C4.76 4 5.04 4 5.6 4H9M4 15v3.4c0 .56 0 .84.109 1.054a1 1 0 0 0 .437.437C4.76 20 5.04 20 5.6 20H9m6-16h3.4c.56 0 .84 0 1.054.109a1 1 0 0 1 .437.437C20 4.76 20 5.04 20 5.6V9m0 6v3.4c0 .56 0 .84-.109 1.054a1 1 0 0 1-.437.437C19.24 20 18.96 20 18.4 20H15" stroke-width="2.4" stroke-linecap="round"/></svg>'}dispose(){this.toggleCollapseBtn.removeEventListener("click",this.toggleCollapse),this.zoomInBtn.removeEventListener("click",this.zoomIn),this.zoomOutBtn.removeEventListener("click",this.zoomOut),this.zoomSlider.removeEventListener("input",this.slide),this.resetViewBtn.removeEventListener("click",this.resetView),this.toggleFullscreenBtn.removeEventListener("click",this.toggleFullscreen),this.controlsPanel.remove(),this._controlsPanel=null,this._toggleCollapseBtn=null,this._zoomInBtn=null,this._zoomOutBtn=null,this._zoomSlider=null,this._resetViewBtn=null,this._toggleFullscreenBtn=null}}class d extends EventTarget{constructor(t){super(),this._canvasBaseDir=null,this.hidePreviewModal=()=>{this.previewModalBackdrop.classList.add("hidden"),this.previewModal.classList.add("hidden"),this.dispatchEvent(new CustomEvent("previewModalHidden"))},this._previewModalBackdrop=document.createElement("div"),this._previewModalBackdrop.className="preview-modal-backdrop hidden",t.appendChild(this._previewModalBackdrop),this._previewModal=document.createElement("div"),this._previewModal.className="preview-modal hidden",this._previewModalClose=document.createElement("button"),this._previewModalClose.className="preview-modal-close",this._previewModalClose.innerHTML='<svg viewBox="0 0 24 24"><path d="M6.758 17.243 12.001 12m5.243-5.243L12 12m0 0L6.758 6.757M12.001 12l5.243 5.243" stroke-width="2" stroke-linecap="round" /></svg>',this._previewModal.appendChild(this._previewModalClose),this._previewModalContent=document.createElement("div"),this._previewModalContent.className="preview-modal-content",this._previewModal.appendChild(this._previewModalContent),t.appendChild(this._previewModal),this._previewModalClose.addEventListener("click",this.hidePreviewModal),this._previewModalBackdrop.addEventListener("click",this.hidePreviewModal)}get previewModalBackdrop(){if(!this._previewModalBackdrop)throw i;return this._previewModalBackdrop}get previewModal(){if(!this._previewModal)throw i;return this._previewModal}get previewModalContent(){if(!this._previewModalContent)throw i;return this._previewModalContent}get canvasBaseDir(){if(!this._canvasBaseDir)throw i;return this._canvasBaseDir}get previewModalClose(){if(!this._previewModalClose)throw i;return this._previewModalClose}receiveData(t){this._canvasBaseDir=t}showPreviewModal(t){const i=this.processContent(t);if(!i)throw e;this.previewModalContent.innerHTML="",this.previewModalBackdrop.classList.remove("hidden"),this.previewModal.classList.remove("hidden"),this.previewModalContent.appendChild(i),this.dispatchEvent(new CustomEvent("previewModalShown"))}resize(t,e){this.previewModal.style.setProperty("--preview-max-width",.9*t+"px"),this.previewModal.style.setProperty("--preview-max-height",.9*e+"px")}processContent(t){if(!t.file)throw e;let i;return t.file.match(/\.(png|jpg|jpeg|gif|svg)$/i)?(i=new Image,i.src=this.canvasBaseDir+t.file):t.file.match(/\.mp3$/i)&&(i=document.createElement("audio"),i.controls=!0,i.src=this.canvasBaseDir+t.file),i}dispose(){for(this.previewModalClose.removeEventListener("click",this.hidePreviewModal),this.previewModalBackdrop.removeEventListener("click",this.hidePreviewModal);this.previewModalContent.firstElementChild;)this.previewModalContent.firstElementChild.remove();this.previewModal.remove(),this.previewModalBackdrop.remove(),this._previewModal=null,this._previewModalClose=null,this._previewModalBackdrop=null,this._previewModalContent=null}}class p extends EventTarget{constructor(t){super(),this._nodeMap=null,this._canvasBaseDir=null,this.selectedId=null,this.eventListeners={},this.startInteract=()=>this.dispatchEvent(new CustomEvent("interactionStart")),this.endInteract=()=>this.dispatchEvent(new CustomEvent("interactionEnd")),this._overlaysLayer=document.createElement("div"),this._overlaysLayer.className="overlays",t.appendChild(this.overlaysLayer),this._overlays={}}get nodeMap(){if(!this._nodeMap)throw i;return this._nodeMap}get canvasBaseDir(){if(!this._canvasBaseDir)throw i;return this._canvasBaseDir}get overlays(){if(!this._overlays)throw i;return this._overlays}get overlaysLayer(){if(!this._overlaysLayer)throw i;return this._overlaysLayer}receiveData(t,e){this._canvasBaseDir=t,this._nodeMap=e}select(t){const e=this.selectedId?this.overlays[this.selectedId]:null,i=t?this.overlays[t]:null;e&&e.classList.remove("active"),i?(i.classList.add("active"),this.startInteract()):this.endInteract(),this.selectedId=t}async loadMarkdownForNode(i){if(!i.mdContent){i.mdContent="Loading...",this.updateOrCreateOverlay(i,i.mdContent,"markdown");try{if(!i.file)throw e;const n=await(await fetch(this.canvasBaseDir+i.file)).text(),s=n.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);if(s){const e=s[1].split("\n").reduce((t,e)=>{const[i,n]=e.split(":").map(t=>t.trim());return t[i]=n,t},{});i.mdContent=await t.parse(s[2].trim()),i.mdFrontmatter=e}else i.mdContent=await t.parse(n)}catch(t){console.error("Failed to load markdown:",t),i.mdContent="Failed to load content."}}this.updateOrCreateOverlay(i,i.mdContent,"markdown")}updateAllOverlays(t,i,n){this.overlaysLayer.style.transform=`translate(${t}px, ${i}px) scale(${n})`;const s=new Set,o={text:t=>{if(!t.text)throw e;t.inViewport&&(s.add(t.id),this.updateOrCreateOverlay(t,t.text,"text"))},file:t=>{if(!t.file)throw e;t.inViewport&&(s.add(t.id),t.file.match(/\.md$/i)?this.loadMarkdownForNode(t):t.file.match(/\.(png|jpg|jpeg|gif|svg)$/i)&&this.updateOrCreateOverlay(t,this.canvasBaseDir+t.file,"image"))},link:t=>{if(!t.url)throw e;s.add(t.id),this.updateOrCreateOverlay(t,t.url,"link")},group:()=>{}};Object.values(this.nodeMap).forEach(t=>o[t.type](t)),Object.keys(this.overlays).forEach(t=>{if(!s.has(t)){const e=this.overlays[t];e&&e.parentNode&&e.parentNode.removeChild(e),delete this.overlays[t]}})}async updateOrCreateOverlay(t,i,n){let s=this.overlays[t.id];if(s||(s=await this.constructOverlay(t,i,n),this.overlaysLayer.appendChild(s),this.overlays[t.id]=s,s.style.left=t.x+"px",s.style.top=t.y+"px",s.style.width=t.width+"px",s.style.height=t.height+"px"),"markdown"===n){const i=s.getElementsByClassName("parsed-content-wrapper")[0];if(!t.mdContent)throw e;i.innerHTML!==t.mdContent&&(i.innerHTML=t.mdContent),!s.classList.contains("rtl")&&"rtl"===t.mdFrontmatter?.direction&&s.classList.add("rtl")}}async constructOverlay(e,i,n){const s=document.createElement("div");s.classList.add("overlay-container"),s.id=e.id;const o=document.createElement("div");if(o.className="overlay-border",s.appendChild(o),"text"===n||"markdown"===n){s.classList.add("markdown-content");const e=document.createElement("div");e.innerHTML=await t.parse(i||""),e.classList.add("parsed-content-wrapper"),s.appendChild(e)}else if("link"===n){const t=document.createElement("iframe");t.src=i,t.sandbox="allow-scripts allow-same-origin",t.className="link-iframe",t.loading="lazy";const e=document.createElement("div");e.className="link-click-layer",s.appendChild(t),s.appendChild(e)}else if("image"===n){const t=document.createElement("img");t.src=i,t.loading="lazy",s.appendChild(t)}const r=null==e.color?"color-0":"color-"+e.color;if(s.classList.add(r),"image"!==n){this.selectedId===e.id&&s.classList.add("active");const t=()=>{e.id===this.selectedId&&this.startInteract()},i=()=>{e.id===this.selectedId&&this.endInteract()};s.addEventListener("pointerenter",t),s.addEventListener("pointerleave",i),s.addEventListener("touchstart",t),s.addEventListener("touchend",i),this.eventListeners[e.id]=[t,i]}return s}dispose(){for(this._overlays=null;this.overlaysLayer.firstElementChild;){const t=this.overlaysLayer.firstElementChild;if(this.eventListeners[t.id]){const e=this.eventListeners[t.id][0],n=this.eventListeners[t.id][1];if(!e||!n)throw i;t.removeEventListener("pointerenter",e),t.removeEventListener("pointerleave",n),t.removeEventListener("touchstart",e),t.removeEventListener("touchend",n),this.eventListeners[t.id][0]=null,this.eventListeners[t.id][1]=null}t.remove()}this.overlaysLayer.remove(),this._overlaysLayer=null,this._nodeMap=null}}class m extends EventTarget{constructor(t,e=!0){super(),this.preventMt=!1,this.onPointerDown=t=>{const e=this.container.getBoundingClientRect();t.clientX<e.left||t.clientX>e.right||t.clientY<e.top||t.clientY>e.bottom?this.preventMt||this.startPrevention():this.preventMt&&(this.preventMistouch.initialX=t.clientX,this.preventMistouch.initialY=t.clientY,this.preventMistouch.lastX=t.clientX,this.preventMistouch.lastY=t.clientY,this.preventMistouch.record=!0)},this.onPointerMove=t=>{this.preventMistouch.record&&(this.preventMistouch.lastX=t.clientX,this.preventMistouch.lastY=t.clientY)},this.onPointerUp=()=>{this.preventMistouch.record&&(this.preventMistouch.record=!1,Math.abs(this.preventMistouch.lastX-this.preventMistouch.initialX)+Math.abs(this.preventMistouch.lastY-this.preventMistouch.initialY)<5&&this.endPrevention())},this._preventionContainer=document.createElement("div"),this._preventionContainer.className="prevention-container hidden";const i=document.createElement("div");i.className="prevention-banner",i.innerHTML="Frozen to prevent mistouch, click on to unlock.",this._preventionContainer.appendChild(i),t.appendChild(this._preventionContainer),this._container=t,this.preventMistouch={record:!1,lastX:0,lastY:0,initialX:0,initialY:0},e&&this.startPrevention(),window.addEventListener("pointerdown",this.onPointerDown),window.addEventListener("pointermove",this.onPointerMove),window.addEventListener("pointerup",this.onPointerUp)}get preventionContainer(){if(null===this._preventionContainer)throw i;return this._preventionContainer}get container(){if(null===this._container)throw i;return this._container}startPrevention(){this.preventionContainer.classList.remove("hidden"),this.container.classList.add("numb"),this.preventMt=!0,this.dispatchEvent(new CustomEvent("preventionStart"))}endPrevention(){this.preventMt=!1,this.preventionContainer.classList.add("hidden"),setTimeout(()=>this.container.classList.remove("numb"),50),this.dispatchEvent(new CustomEvent("preventionEnd"))}dispose(){window.removeEventListener("pointerdown",this.onPointerDown),window.removeEventListener("pointermove",this.onPointerMove),window.removeEventListener("pointerup",this.onPointerUp),this.preventionContainer.remove(),this._container=null,this._preventionContainer=null}}class v extends EventTarget{constructor(t,i=[],s=[]){super(),this.controls=null,this.minimap=null,this.mistouchPreventer=null,this.canvasBaseDir=null,this.animationId=null,this._canvasData=null,this._nodeMap=null,this._nodeBounds=null,this.spatialGrid=null,this.draw=()=>{if(this.perFrame.needAnimating||this.perFrame.pan||this.perFrame.resize||this.perFrame.zoom||this.perFrame.smoothZoom){if(this.perFrame.needAnimating&&(this.perFrame.needAnimating=!1),this.perFrame.zoom){this.perFrame.zoom=!1;const t=this.interactor.getZoomDump();this.zoom(t.factor,t.origin),this.interactor.resetZoomDump()}this.perFrame.pan&&(this.perFrame.pan=!1,this.pan(this.interactor.getPanDump()),this.interactor.resetPanDump()),this.perFrame.smoothZoom&&this.smoothZoom(),this.perFrame.resize&&this.resize(),this.renderer.redraw(this.offsetX,this.offsetY,this.scale),this.overlayManager.updateAllOverlays(this.offsetX,this.offsetY,this.scale),this.minimap&&this.minimap.updateViewportRectangle(this.offsetX,this.offsetY,this.scale),this.animationId=requestAnimationFrame(this.draw)}else this.animationId=requestAnimationFrame(this.draw)},this.onClick=t=>{if(!(t instanceof CustomEvent))throw e;{if(this.isUIControl(t.detail.target))return;const i=this.findNodeAt(this.C2W(this.C2C(t.detail.position))),n=this.judgeInteract(i);switch(n){case"non-interactive":this.overlayManager.select(null);break;case"select":if(!i)throw e;this.overlayManager.select(i.id);break;case"preview":if(!i)throw e;this.previewModal.showPreviewModal(i)}i&&"non-interactive"!==n&&this.dispatchEvent(new CustomEvent("interact",{detail:{node:i.id,type:n}}))}},this.onResize=()=>{this.perFrame.resize=!0,this.perFrame.resizeScale.width=this.container.clientWidth,this.perFrame.resizeScale.height=this.container.clientHeight},this.onPan=()=>this.perFrame.pan=!0,this.onZoom=()=>this.perFrame.zoom=!0,this.onSlide=t=>{if(!(t instanceof CustomEvent))throw e;this.setScale(t.detail)},this.onMinimapExpanded=()=>{this.minimap&&this.minimap.updateViewportRectangle(this.offsetX,this.offsetY,this.scale)},this.onPreventionStart=()=>{this.animationId&&(cancelAnimationFrame(this.animationId),this.animationId=null)},this.onPreventionEnd=()=>this.animationId=requestAnimationFrame(this.draw),this.stopInteractor=()=>this.interactor.stop(),this.startInteractor=()=>this.interactor.start(),this.onToggleFullscreen=()=>this.shiftFullscreen(),this.zoomIn=()=>this.setScale(1.2*this.scale),this.zoomOut=()=>this.setScale(this.scale/1.2),this.resetView=()=>{const t=this.nodeBounds;if(!t)return{scale:1,offsetX:this.container.clientWidth/2,offsetY:this.container.clientHeight/2};const e=t.width+2*this.INITIAL_VIEWPORT_PADDING,i=t.height+2*this.INITIAL_VIEWPORT_PADDING,n=this.container.clientWidth,s=this.container.clientHeight,o=n/e,r=s/i,a=Math.round(1e3*Math.min(o,r))/1e3,l={scale:a,offsetX:n/2-t.centerX*a,offsetY:s/2-t.centerY*a};this.scale=l.scale,this.offsetX=l.offsetX,this.offsetY=l.offsetY,this.controls&&this.controls.updateSlider(this.scale),this.perFrame.needAnimating=!0};const o=t.attachShadow({mode:"open"}),r=document.createElement("style");r.innerHTML=".color-1{--border-color: rgba(255, 120, 129, .75);--background-color: rgba(255, 120, 129, .1);--active-color: rgba(255, 120, 129, 1)}.color-2{--border-color: rgba(251, 187, 131, .75);--background-color: rgba(251, 187, 131, .1);--active-color: rgba(251, 187, 131, 1)}.color-3{--border-color: rgba(255, 232, 139, .75);--background-color: rgba(255, 232, 139, .1);--active-color: rgba(255, 232, 139, 1)}.color-4{--border-color: rgba(124, 211, 124, .75);--background-color: rgba(124, 211, 124, .1);--active-color: rgba(124, 211, 124, 1)}.color-5{--border-color: rgba(134, 223, 226, .75);--background-color: rgba(134, 223, 226, .1);--active-color: rgba(134, 223, 226, 1)}.color-6{--border-color: rgba(203, 158, 255, .75);--background-color: rgba(203, 158, 255, .1);--active-color: rgba(203, 158, 255, 1)}.color-0{--border-color: rgba(140, 140, 140, .75);--background-color: rgba(140, 140, 140, .1);--active-color: rgba(140, 140, 140, 1)}.full,.preview-modal,.preview-modal-backdrop,.link-click-layer,.link-iframe,.prevention-container{top:0;left:0;width:100%;height:100%;position:absolute}.flex-center,.overlay-container.markdown-content,.prevention-container{display:flex;justify-content:center;align-items:center}.overlay-border{z-index:1}.container{--themeColor: rgb(254, 247, 167);--background-color: #141414;--contentTransition: color .2s, opacity .2s, text-shadow .2s, fill .2s;--containerTransition: background .2s, opacity .2s, box-shadow .2s, border .2s, filter .2s, backdrop-filter .2s;color:#fff;fill:#fff;stroke:#fff;position:relative;width:100%;height:100%;overflow:hidden;background-color:var(--background-color)}.container.numb,.container.numb *{pointer-events:none!important}.prevention-container{overflow:visible;transition:background .2s,opacity .2s,box-shadow .2s,border .2s,filter .2s,backdrop-filter .2s}.prevention-container.hidden{pointer-events:none}.prevention-container .prevention-banner{background:#0006;border-radius:12px;padding:12px;margin:12px;-webkit-backdrop-filter:blur(8px) saturate(1.5);backdrop-filter:blur(8px) saturate(1.5);border:2px solid rgba(140,140,140,.75);color:#fff;font-size:calc(14px + .3vw);line-height:calc(17px + .3vw);text-align:center}.main-canvas{width:100%;height:100%;transform-origin:top left}button{cursor:pointer;font-size:18px;height:32px;border:none;transition:var(--containerTransition);text-align:center;background-color:#444;width:32px;padding:5px 0}button svg{width:100%;height:100%}.overlays{position:absolute;top:0;left:0;width:100%;height:100%;transform-origin:top left;will-change:transform}.parsed-content-wrapper{font-family:sans-serif;box-sizing:border-box;max-width:100%;max-height:100%;padding:10px 6px;pointer-events:none;overflow:hidden;scrollbar-gutter:stable both-edges;display:flex;flex-direction:column;gap:12px}@supports not (scrollbar-gutter: stable both-edges){.parsed-content-wrapper{padding:10px}}.minimap-container{position:absolute;bottom:10px;right:10px;display:flex;pointer-events:none;transition:transform .2s}.minimap-container.collapsed{transform:translate(calc(100% - 32px))}@media (max-width: 768px){.minimap-container{transform:translateY(60px) translate(80px)}.minimap-container.collapsed{transform:translateY(60px) translate(calc(100% - 32px))}}.toggle-minimap{margin:auto 10px 0 0;pointer-events:auto}.collapsed .toggle-minimap{transform:rotate(180deg)}@media (max-width: 768px){.toggle-minimap{transform:translateY(-60px)}.collapsed .toggle-minimap{transform:translateY(-60px) rotate(180deg)}}.minimap{position:relative;width:200px;height:150px;overflow:hidden;border-radius:12px;background:#202020;-webkit-backdrop-filter:blur(8px) saturate(1.5);backdrop-filter:blur(8px) saturate(1.5);border:2px solid rgba(140,140,140,.75);transform-origin:0 0}@media (max-width: 768px){.minimap{transform:scale(.6)}}.minimap .minimap-canvas{width:100%;height:100%}.minimap .viewport-rectangle{position:absolute;top:0;left:0;pointer-events:none;border:2px solid #fff;border-radius:6px;box-sizing:border-box;background:transparent}.collapse-button{border-radius:8px;transition:transform .2s}.collapse-button:hover{background:#444c}.controls{position:absolute;top:10px;right:10px;display:flex;align-items:center;transition:transform .2s;border-radius:8px;gap:10px}.controls.collapsed{transform:translate(calc(100% - 32px))}.controls.collapsed .collapse-button{transform:rotate(180deg)}.controls .controls-content{display:flex;gap:1px;align-items:center;border-radius:8px;overflow:hidden;background:#333c}.controls button:hover{background:#555}.zoom-slider{width:100px;margin:0 10px}.overlay-container{position:absolute;box-sizing:border-box;border-radius:12px;overflow:hidden;background-color:var(--background-color);-webkit-user-select:none;user-select:none;contain:strict;transition:var(--containerTransition)}.overlay-container:hover{box-shadow:0 2px 12px #00000080}.overlay-container .overlay-border{box-sizing:border-box;pointer-events:none;position:absolute;top:0;left:0;width:100%;height:100%;border:2px solid var(--border-color);border-radius:12px;transition:var(--containerTransition)}.overlay-container img{width:100%;height:100%;object-fit:cover;pointer-events:none}.overlay-container.active .overlay-border{border:6px solid var(--active-color)}.overlay-container.markdown-content{position:absolute;padding:0 7px}.overlay-container.markdown-content.active .parsed-content-wrapper{overflow:auto;-webkit-user-select:text;user-select:text;pointer-events:auto}.overlay-container.markdown-content.rtl{direction:rtl;text-align:right}.link-iframe{contain-intrinsic-size:100% 100%;border:none}.link-click-layer{background:transparent;pointer-events:auto}.active .link-click-layer{pointer-events:none}.preview-modal-backdrop{-webkit-backdrop-filter:blur(8px) saturate(1.5);backdrop-filter:blur(8px) saturate(1.5);transition:var(--containerTransition)}.preview-modal-backdrop.hidden{pointer-events:none}.preview-modal{transition:var(--containerTransition);pointer-events:none}.preview-modal-close{position:absolute;right:10px;top:10px;background:none;border:none;font-size:24px;cursor:pointer;pointer-events:none}.preview-modal:not(.hidden) .preview-modal-close{pointer-events:auto}.preview-modal-content{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:fit-content;height:fit-content;pointer-events:none}.preview-modal:not(.hidden) .preview-modal-content{pointer-events:auto}.preview-modal-content>*{max-width:var(--preview-max-width);max-height:var(--preview-max-height);border:2px solid rgba(162,162,162,.5);box-shadow:0 2px 6px #0000004d;box-sizing:border-box}.preview-modal-content>img{border-radius:12px}.preview-modal-content>audio{border-radius:27px}.hidden{opacity:0}::-webkit-scrollbar{width:4px}::-webkit-scrollbar-track{background-color:transparent}::-webkit-scrollbar-thumb{border-radius:2px;background:#ffffff40}::-webkit-scrollbar-thumb:hover{background:#1e1e1ebf}p{font-size:16px;line-height:21px}.parsed-content-wrapper img{width:100%;border-radius:8px}h1{font-size:25px}h2{font-size:23px}h3{font-size:22px}h4{font-size:20px}h5{font-size:19px}h6{font-size:17px}p,h1,h2,h3,h4,h5,h6,ol,ul{margin:0}h1,h2{font-weight:800}h3,h4{font-weight:700}h5,h6{font-weight:600}code{background:#ffffff1a;padding:2px 4px;border-radius:8px}pre code{display:block;box-sizing:border-box;width:100%}pre:has(code),table{margin:6px 0}strong{color:#fe8e7c}em{color:#5affb2}a{text-decoration:none;color:#6dadd0;font-weight:800;font-style:italic;cursor:pointer;transition:var(--contentTransition)}a:hover{color:#86d3fd}hr{height:1px;width:100%;background-color:#fff3;border:none}li{margin:5px 0}ul{padding-left:16px}ol{padding-left:15px;padding-right:7.5px}table{border-collapse:collapse;border-spacing:0;border-radius:8px;border:1px solid rgba(255,255,255,.2);overflow:hidden;width:100%}table th,table td{border:1px solid rgba(255,255,255,.2);padding:6px 10px;background:#ffffff0f;text-align:left}table th{background:#ffffff1f;font-weight:700}",o.appendChild(r),this._container=document.createElement("div"),this._container.classList.add("container"),this.renderer=new n(this._container),this.overlayManager=new p(this._container),this.interactor=new l(this._container,{preventDefault:!0,proControlSchema:s.includes("proControlSchema")}),this.resizeObserver=new ResizeObserver(this.onResize),i.includes("minimap")?this.minimap=new h(this._container,s.includes("minimapCollapsed")):s.includes("minimapCollapsed")&&console.warn('CanvasViewer: "minimapCollapsed" option is not supported without minimap extension.'),s.includes("controlsHidden")?s.includes("controlsCollapsed")&&console.warn('CanvasViewer: "controlsCollapsed" option is overridden by "controlsHidden" option.'):this.controls=new c(this._container,s.includes("controlsCollapsed")),this.previewModal=new d(this._container),i.includes("mistouchPrevention")&&(this.mistouchPreventer=new m(this._container,!s.includes("noPreventionAtStart"))),o.appendChild(this._container),this.extensions=i,this.options=s,this.offsetX=0,this.offsetY=0,this.scale=1,this.ZOOM_SMOOTHNESS=.25,this.INITIAL_VIEWPORT_PADDING=100,this.GRID_CELL_SIZE=800,this.perFrame={needAnimating:!1,zoom:!1,smoothZoom:!1,targetScale:1,pan:!1,resize:!1,resizeScale:{width:0,height:0,lastCentreX:null,lastCentreY:null}}}get canvasData(){if(null===this._canvasData)throw i;return this._canvasData}get nodeMap(){if(null===this._nodeMap)throw i;return this._nodeMap}get nodeBounds(){if(null===this._nodeBounds)throw i;return this._nodeBounds}get container(){if(null===this._container)throw i;return this._container}isUIControl(t){return t.closest&&(t.closest(".controls")||t.closest("button")||t.closest("input"))}findNodeAt({x:t,y:e}){let i=[];if(this.spatialGrid){const n=`${Math.floor(t/this.GRID_CELL_SIZE)},${Math.floor(e/this.GRID_CELL_SIZE)}`;i=this.spatialGrid[n]||[]}else i=this.canvasData.nodes;for(const n of i)if(!(t<n.x||t>n.x+n.width||e<n.y||e>n.y+n.height||"non-interactive"===this.judgeInteract(n)))return n}C2C({x:t,y:e}){return{x:t-this.offsetX,y:e-this.offsetY}}C2W({x:t,y:e}){return{x:t/this.scale,y:e/this.scale}}middleScreen(){return{x:this.container.clientWidth/2,y:this.container.clientHeight/2}}judgeInteract(t){switch(t?t.type:"default"){case"text":case"link":return"select";case"file":if(!t||!t.file)throw e;return t.file.match(/\.md$/i)?"select":"preview";default:return"non-interactive"}}zoomToScale(t,e){const i=Math.max(Math.min(t,20),.05);if(i===this.scale)return;const n=this.C2C(e);this.offsetX=e.x-n.x*i/this.scale,this.offsetY=e.y-n.y*i/this.scale,this.scale=i,this.controls&&this.controls.updateSlider(this.scale)}buildSpatialGrid(){if(this.canvasData&&!(this.canvasData.nodes.length<50)){this.spatialGrid={};for(let t of this.canvasData.nodes){const e=Math.floor(t.x/this.GRID_CELL_SIZE),i=Math.floor((t.x+t.width)/this.GRID_CELL_SIZE),n=Math.floor(t.y/this.GRID_CELL_SIZE),s=Math.floor((t.y+t.height)/this.GRID_CELL_SIZE);for(let o=e;o<=i;o++)for(let e=n;e<=s;e++){const i=`${o},${e}`;this.spatialGrid[i]||(this.spatialGrid[i]=[]),this.spatialGrid[i].push(t)}}}}calculateNodeBounds(){if(!this.canvasData||!this.canvasData.nodes.length)return null;let t=1/0,e=1/0,i=-1/0,n=-1/0;this.canvasData.nodes.forEach(s=>{t=Math.min(t,s.x),e=Math.min(e,s.y),i=Math.max(i,s.x+s.width),n=Math.max(n,s.y+s.height)});const s=i-t,o=n-e;return{minX:t,minY:e,maxX:i,maxY:n,width:s,height:o,centerX:t+s/2,centerY:e+o/2}}resize(){this.perFrame.resize=!1;const t=this.perFrame.resizeScale;t.lastCentreX&&t.lastCentreY&&(this.offsetX+=t.width/2-t.lastCentreX,this.offsetY+=t.height/2-t.lastCentreY),t.lastCentreX=t.width/2,t.lastCentreY=t.height/2,this.renderer.resizeCanvasForDPR(),this.previewModal.resize(t.width,t.height)}pan({x:t,y:e}){this.offsetX+=t,this.offsetY+=e}zoom(t,e){const i=this.scale*t;this.zoomToScale(i,e)}smoothZoom(){const t=this.perFrame.targetScale-this.scale;let e;Math.abs(t)<.01*this.perFrame.targetScale+.002?(e=this.perFrame.targetScale,this.perFrame.smoothZoom=!1):e=Math.round(1e3*(this.scale+t*this.ZOOM_SMOOTHNESS))/1e3,this.zoomToScale(e,this.middleScreen())}setScale(t){this.perFrame.targetScale=t,this.perFrame.smoothZoom=!0}panTo(t,e){this.offsetX=this.container.clientWidth/2-t*this.scale,this.offsetY=this.container.clientHeight/2-e*this.scale,this.perFrame.needAnimating=!0}shiftFullscreen(t="toggle"){document.fullscreenElement||"toggle"!==t&&"enter"!==t?document.fullscreenElement&&("toggle"===t||"exit"===t)&&document.exitFullscreen():this.container.requestFullscreen(),this.controls&&this.controls.updateFullscreenBtn()}async loadCanvas(t){try{if(/^https?:\/\//.test(t))this.canvasBaseDir=t.substring(0,t.lastIndexOf("/")+1);else{const e=t.lastIndexOf("/");this.canvasBaseDir=-1!==e?t.substring(0,e+1):"./"}this._canvasData=await fetch(t).then(t=>t.json()),this._nodeMap={},this.canvasData.nodes.forEach(t=>{if("file"===t.type&&t.file&&!t.file.includes("http")){const e=t.file.split("/");t.file=e[e.length-1]}this.nodeMap[t.id]=t}),this.buildSpatialGrid(),this._nodeBounds=this.calculateNodeBounds(),this.resetView(),this.resizeObserver.observe(this.container),this.interactor.start(),this.renderer.receiveData(this.nodeMap,this.canvasData),this.overlayManager.receiveData(this.canvasBaseDir,this.nodeMap),this.previewModal.receiveData(this.canvasBaseDir),this.interactor.addEventListener("trueClick",this.onClick),this.interactor.addEventListener("pan",this.onPan),this.interactor.addEventListener("zoom",this.onZoom),this.previewModal.addEventListener("previewModalShown",this.stopInteractor),this.previewModal.addEventListener("previewModalHidden",this.startInteractor),this.overlayManager.addEventListener("interactionStart",this.stopInteractor),this.overlayManager.addEventListener("interactionEnd",this.startInteractor),this.controls&&this.controls.addEventListener("zoomIn",this.zoomIn),this.controls&&this.controls.addEventListener("zoomOut",this.zoomOut),this.controls&&this.controls.addEventListener("slide",this.onSlide),this.controls&&this.controls.addEventListener("toggleFullscreen",this.onToggleFullscreen),this.controls&&this.controls.addEventListener("resetView",this.resetView),this.minimap&&(this.minimap.receiveData(this.nodeBounds,this.canvasData,this.nodeMap),this.minimap.addEventListener("minimapExpanded",this.onMinimapExpanded)),this.mistouchPreventer&&(this.mistouchPreventer.addEventListener("preventionStart",this.onPreventionStart),this.mistouchPreventer.addEventListener("preventionEnd",this.onPreventionEnd)),(!this.extensions.includes("mistouchPrevention")||this.options.includes("noPreventionAtStart"))&&(this.animationId=requestAnimationFrame(this.draw)),this.dispatchEvent(new CustomEvent("loaded",{detail:this.canvasData}))}catch(t){console.error("Failed to load canvas data:",t)}}dispose(){this.interactor.removeEventListener("trueClick",this.onClick),this.interactor.removeEventListener("pan",this.onPan),this.interactor.removeEventListener("zoom",this.onZoom),this.previewModal.removeEventListener("previewModalShown",this.stopInteractor),this.previewModal.removeEventListener("previewModalHidden",this.startInteractor),this.overlayManager.removeEventListener("interactionStart",this.stopInteractor),this.overlayManager.removeEventListener("interactionEnd",this.startInteractor),this.controls&&this.controls.removeEventListener("zoomIn",this.zoomIn),this.controls&&this.controls.removeEventListener("zoomOut",this.zoomOut),this.controls&&this.controls.removeEventListener("slide",this.onSlide),this.controls&&this.controls.removeEventListener("toggleFullscreen",this.onToggleFullscreen),this.controls&&this.controls.removeEventListener("resetView",this.resetView),this.minimap&&this.minimap.removeEventListener("minimapExpanded",this.onMinimapExpanded),this.mistouchPreventer&&(this.mistouchPreventer.removeEventListener("preventionStart",this.onPreventionStart),this.mistouchPreventer.removeEventListener("preventionEnd",this.onPreventionEnd)),this.animationId&&cancelAnimationFrame(this.animationId),this.resizeObserver.disconnect(),this.interactor.dispose(),this.previewModal.dispose(),this.controls&&this.controls.dispose(),this.minimap&&this.minimap.dispose(),this.mistouchPreventer&&this.mistouchPreventer.dispose(),this.overlayManager.dispose(),this.renderer.dispose(),this.container.remove(),this._container=null}}class u extends HTMLElement{constructor(){super(),this.style.display="block",this.style.overflow="hidden",this.style.maxWidth="120vw",this.style.maxHeight="120vh";const t=this.getAttribute("extensions"),e=this.getAttribute("options"),i=t?t.split(" "):[],n=e?e.split(" "):[];this.viewer=new v(this,i,n)}connectedCallback(){const t=this.getAttribute("src");if(!t)throw new Error("No source canvas path provided.");this.viewer&&this.viewer.loadCanvas(t)}disconnectedCallback(){this.viewer&&this.viewer.dispose(),this.remove()}}customElements.define("canvas-viewer",u);export{v as default};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export default class controls extends EventTarget {
|
|
2
|
+
private _controlsPanel;
|
|
3
|
+
private _toggleCollapseBtn;
|
|
4
|
+
private _toggleFullscreenBtn;
|
|
5
|
+
private _zoomOutBtn;
|
|
6
|
+
private _zoomSlider;
|
|
7
|
+
private _zoomInBtn;
|
|
8
|
+
private _resetViewBtn;
|
|
9
|
+
private get controlsPanel();
|
|
10
|
+
private get toggleCollapseBtn();
|
|
11
|
+
private get toggleFullscreenBtn();
|
|
12
|
+
private get zoomOutBtn();
|
|
13
|
+
private get zoomSlider();
|
|
14
|
+
private get zoomInBtn();
|
|
15
|
+
private get resetViewBtn();
|
|
16
|
+
constructor(container: HTMLElement, controlsCollapsed?: boolean);
|
|
17
|
+
private toggleCollapse;
|
|
18
|
+
private zoomIn;
|
|
19
|
+
private zoomOut;
|
|
20
|
+
private slide;
|
|
21
|
+
private resetView;
|
|
22
|
+
private toggleFullscreen;
|
|
23
|
+
updateFullscreenBtn(): void;
|
|
24
|
+
updateSlider: (scale: number) => string;
|
|
25
|
+
private scaleToSlider;
|
|
26
|
+
dispose(): void;
|
|
27
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export default class interactor extends EventTarget {
|
|
2
|
+
private _monitoringElement;
|
|
3
|
+
private pointers;
|
|
4
|
+
private pinchZoomState;
|
|
5
|
+
private proControlSchema;
|
|
6
|
+
private panDump;
|
|
7
|
+
private zoomDump;
|
|
8
|
+
private zoomFactor;
|
|
9
|
+
private preventDefault;
|
|
10
|
+
private lockControlSchema;
|
|
11
|
+
private get monitoringElement();
|
|
12
|
+
constructor(monitoringElement: HTMLElement, options?: {
|
|
13
|
+
preventDefault?: boolean;
|
|
14
|
+
proControlSchema?: boolean;
|
|
15
|
+
zoomFactor?: number;
|
|
16
|
+
lockControlSchema?: boolean;
|
|
17
|
+
});
|
|
18
|
+
private getNthValue;
|
|
19
|
+
private getPointerDistance;
|
|
20
|
+
private getPointerMidpoint;
|
|
21
|
+
private S2C;
|
|
22
|
+
private onPointerDown;
|
|
23
|
+
private onPointerMove;
|
|
24
|
+
private onPointerUp;
|
|
25
|
+
private onWheel;
|
|
26
|
+
private dispatchPanEvent;
|
|
27
|
+
private dispatchZoomEvent;
|
|
28
|
+
private preventDefaultFunction;
|
|
29
|
+
private round;
|
|
30
|
+
getZoomDump(): {
|
|
31
|
+
factor: number;
|
|
32
|
+
origin: Coordinates;
|
|
33
|
+
};
|
|
34
|
+
resetZoomDump(): void;
|
|
35
|
+
getPanDump(): Coordinates;
|
|
36
|
+
resetPanDump(): void;
|
|
37
|
+
stop(): void;
|
|
38
|
+
start(): void;
|
|
39
|
+
dispose(): void;
|
|
40
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export default class minimap extends EventTarget {
|
|
2
|
+
private _minimapCtx;
|
|
3
|
+
private _canvasData;
|
|
4
|
+
private _nodeMap;
|
|
5
|
+
private _nodeBounds;
|
|
6
|
+
private _viewportRectangle;
|
|
7
|
+
private _minimap;
|
|
8
|
+
private _container;
|
|
9
|
+
private _minimapContainer;
|
|
10
|
+
private _toggleMinimapBtn;
|
|
11
|
+
private isMinimapVisible;
|
|
12
|
+
private minimapCache;
|
|
13
|
+
private get minimapCtx();
|
|
14
|
+
private get canvasData();
|
|
15
|
+
private get container();
|
|
16
|
+
private get minimap();
|
|
17
|
+
private get nodeBounds();
|
|
18
|
+
private get nodeMap();
|
|
19
|
+
private get viewportRectangle();
|
|
20
|
+
private get minimapContainer();
|
|
21
|
+
private get toggleMinimapBtn();
|
|
22
|
+
constructor(container: HTMLElement, minimapCollapsed?: boolean);
|
|
23
|
+
receiveData(nodeBounds: {
|
|
24
|
+
minX: number;
|
|
25
|
+
minY: number;
|
|
26
|
+
maxX: number;
|
|
27
|
+
maxY: number;
|
|
28
|
+
width: number;
|
|
29
|
+
height: number;
|
|
30
|
+
centerX: number;
|
|
31
|
+
centerY: number;
|
|
32
|
+
}, canvasData: JSONCanvas, nodeMap: Record<string, JSONCanvasNode>): void;
|
|
33
|
+
private toggleVisisbility;
|
|
34
|
+
private drawMinimap;
|
|
35
|
+
private drawMinimapNode;
|
|
36
|
+
private drawMinimapEdge;
|
|
37
|
+
updateViewportRectangle(offsetX: number, offsetY: number, scale: number): void;
|
|
38
|
+
dispose(): void;
|
|
39
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export default class mistouchPreventer extends EventTarget {
|
|
2
|
+
private _container;
|
|
3
|
+
private _preventionContainer;
|
|
4
|
+
private preventMt;
|
|
5
|
+
private preventMistouch;
|
|
6
|
+
private get preventionContainer();
|
|
7
|
+
private get container();
|
|
8
|
+
constructor(container: HTMLElement, prevent?: boolean);
|
|
9
|
+
private onPointerDown;
|
|
10
|
+
private onPointerMove;
|
|
11
|
+
private onPointerUp;
|
|
12
|
+
startPrevention(): void;
|
|
13
|
+
endPrevention(): void;
|
|
14
|
+
dispose(): void;
|
|
15
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export default class overlayManager extends EventTarget {
|
|
2
|
+
private _overlaysLayer;
|
|
3
|
+
private _overlays;
|
|
4
|
+
private _nodeMap;
|
|
5
|
+
private _canvasBaseDir;
|
|
6
|
+
private selectedId;
|
|
7
|
+
private eventListeners;
|
|
8
|
+
private get nodeMap();
|
|
9
|
+
private get canvasBaseDir();
|
|
10
|
+
private get overlays();
|
|
11
|
+
private get overlaysLayer();
|
|
12
|
+
constructor(container: HTMLElement);
|
|
13
|
+
receiveData(canvasBaseDir: string, nodeMap: Record<string, JSONCanvasNode>): void;
|
|
14
|
+
select(id: string | null): void;
|
|
15
|
+
private loadMarkdownForNode;
|
|
16
|
+
updateAllOverlays(offsetX: number, offsetY: number, scale: number): void;
|
|
17
|
+
private updateOrCreateOverlay;
|
|
18
|
+
private constructOverlay;
|
|
19
|
+
private startInteract;
|
|
20
|
+
private endInteract;
|
|
21
|
+
dispose(): void;
|
|
22
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { RuntimeJSONCanvasNode } from './renderer';
|
|
2
|
+
export default class previewModal extends EventTarget {
|
|
3
|
+
private _previewModalBackdrop;
|
|
4
|
+
private _previewModal;
|
|
5
|
+
private _previewModalContent;
|
|
6
|
+
private _canvasBaseDir;
|
|
7
|
+
private _previewModalClose;
|
|
8
|
+
private get previewModalBackdrop();
|
|
9
|
+
private get previewModal();
|
|
10
|
+
private get previewModalContent();
|
|
11
|
+
private get canvasBaseDir();
|
|
12
|
+
private get previewModalClose();
|
|
13
|
+
constructor(container: HTMLElement);
|
|
14
|
+
receiveData(canvasBaseDir: string): void;
|
|
15
|
+
showPreviewModal(node: RuntimeJSONCanvasNode): void;
|
|
16
|
+
hidePreviewModal: () => void;
|
|
17
|
+
resize(width: number, height: number): void;
|
|
18
|
+
private processContent;
|
|
19
|
+
dispose(): void;
|
|
20
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export interface RuntimeJSONCanvasNode extends JSONCanvasNode {
|
|
2
|
+
inViewport?: boolean;
|
|
3
|
+
mdContent?: string;
|
|
4
|
+
mdFrontmatter?: Record<string, string>;
|
|
5
|
+
}
|
|
6
|
+
export declare const unexpectedError: Error;
|
|
7
|
+
export declare const destroyError: Error;
|
|
8
|
+
export declare class renderer {
|
|
9
|
+
private _nodeMap;
|
|
10
|
+
private _canvasData;
|
|
11
|
+
private _canvas;
|
|
12
|
+
private _ctx;
|
|
13
|
+
private _container;
|
|
14
|
+
private ARROW_LENGTH;
|
|
15
|
+
private ARROW_WIDTH;
|
|
16
|
+
private FILE_NODE_RADIUS;
|
|
17
|
+
private FONT_COLOR;
|
|
18
|
+
private CSS_ZOOM_REDRAW_INTERVAL;
|
|
19
|
+
private zoomInOptimize;
|
|
20
|
+
private get nodeMap();
|
|
21
|
+
private get canvasData();
|
|
22
|
+
private get canvas();
|
|
23
|
+
private get ctx();
|
|
24
|
+
private get container();
|
|
25
|
+
constructor(canvasContainer: HTMLElement);
|
|
26
|
+
receiveData(nodeMap: Record<string, JSONCanvasNode>, canvasDava: JSONCanvas): void;
|
|
27
|
+
resizeCanvasForDPR(): void;
|
|
28
|
+
redraw(offsetX: number, offsetY: number, scale: number): void;
|
|
29
|
+
dispose(): void;
|
|
30
|
+
private trueRedraw;
|
|
31
|
+
private fakeRedraw;
|
|
32
|
+
private isViewportInside;
|
|
33
|
+
private isNodeInViewport;
|
|
34
|
+
private getCurrentViewport;
|
|
35
|
+
private drawLabelBar;
|
|
36
|
+
private drawNodeBackground;
|
|
37
|
+
private drawGroup;
|
|
38
|
+
private drawFileNode;
|
|
39
|
+
private drawEdge;
|
|
40
|
+
private getEdgeNodes;
|
|
41
|
+
private getControlPoints;
|
|
42
|
+
private drawCurvedPath;
|
|
43
|
+
private drawArrowhead;
|
|
44
|
+
}
|
|
45
|
+
export declare function getAnchorCoord(node: JSONCanvasNode, side: 'top' | 'bottom' | 'left' | 'right'): number[];
|
|
46
|
+
export declare function drawRoundRect(ctx: CanvasRenderingContext2D, x: number, y: number, width: number, height: number, radius: number): void;
|
|
47
|
+
export declare function getColor(colorIndex?: string): {
|
|
48
|
+
border: string;
|
|
49
|
+
background: string;
|
|
50
|
+
active: string;
|
|
51
|
+
};
|
|
52
|
+
export declare function resizeCanvasForDPR(canvas: HTMLCanvasElement, width: number, height: number): void;
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "json-canvas-viewer",
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "A TypeScript-based viewer for JSON Canvas, easy to embed into websites.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"types": "dist/canvasViewer.d.ts",
|
|
7
|
+
"main": "dist/canvasViewer.cjs.js",
|
|
8
|
+
"module": "dist/canvasViewer.esm.js",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/my-library.esm.js",
|
|
12
|
+
"require": "./dist/my-library.cjs.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/hesprs/JSON-Canvas-Viewer.git"
|
|
18
|
+
},
|
|
19
|
+
"homepage": "https://github.com/hesprs/JSON-Canvas-Viewer#readme",
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/hesprs/JSON-Canvas-Viewer/issues"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"Obsidian",
|
|
25
|
+
"JSON Canvas",
|
|
26
|
+
"Viewer",
|
|
27
|
+
"Front-end",
|
|
28
|
+
"HTML Canvas",
|
|
29
|
+
"TypeScript"
|
|
30
|
+
],
|
|
31
|
+
"author": {
|
|
32
|
+
"name": "Hēsperus",
|
|
33
|
+
"email": "hesprs@outlook.com"
|
|
34
|
+
},
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"packageManager": "pnpm@10.13.1",
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@rollup/plugin-terser": "^0.4.4",
|
|
39
|
+
"@types/node": "^24.2.1",
|
|
40
|
+
"@typescript-eslint/eslint-plugin": "^8.39.1",
|
|
41
|
+
"@typescript-eslint/parser": "^8.39.1",
|
|
42
|
+
"cross-env": "^10.0.0",
|
|
43
|
+
"eslint": "^9.32.0",
|
|
44
|
+
"eslint-config-prettier": "^10.1.8",
|
|
45
|
+
"globals": "^16.3.0",
|
|
46
|
+
"sass": "^1.90.0",
|
|
47
|
+
"typescript": "^5.9.2",
|
|
48
|
+
"vite": "^7.1.1"
|
|
49
|
+
},
|
|
50
|
+
"files": [
|
|
51
|
+
"dist"
|
|
52
|
+
],
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"marked": "^16.1.2"
|
|
55
|
+
},
|
|
56
|
+
"scripts": {
|
|
57
|
+
"test": "vite --open",
|
|
58
|
+
"lint": "eslint .",
|
|
59
|
+
"build-types": "tsc --project tsconfig.json",
|
|
60
|
+
"build-for-distribution": "vite build && pnpm run build-types",
|
|
61
|
+
"build-standalone": "cross-env standalone=true vite build",
|
|
62
|
+
"build": "pnpm run build-for-distribution && pnpm run build-standalone"
|
|
63
|
+
}
|
|
64
|
+
}
|