virtual-tree-canvas 0.3.9 → 0.4.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/README.md +164 -225
- package/docs/inspector-demo.png +0 -0
- package/docs/tree-demo.png +0 -0
- package/package.json +2 -1
- package/src/assets/icons/air.svg +1 -0
- package/src/assets/icons/aircraft.svg +1 -0
- package/src/assets/icons/bus.svg +1 -0
- package/src/assets/icons/control.svg +1 -0
- package/src/assets/icons/damage.svg +1 -0
- package/src/assets/icons/error.svg +1 -0
- package/src/assets/icons/folder.svg +1 -0
- package/src/assets/icons/ground.svg +1 -0
- package/src/assets/icons/inspector-array.svg +1 -0
- package/src/assets/icons/inspector-object.svg +1 -0
- package/src/assets/icons/inspector-value.svg +1 -0
- package/src/assets/icons/munition.svg +1 -0
- package/src/assets/icons/placeholder.svg +1 -0
- package/src/assets/icons/point.svg +1 -0
- package/src/assets/icons/radar.svg +1 -0
- package/src/assets/icons/situation.svg +1 -0
- package/src/assets/icons/space.svg +1 -0
- package/src/assets/icons/subsurface.svg +1 -0
- package/src/assets/icons/surface.svg +1 -0
- package/src/assets/icons/task.svg +1 -0
- package/src/assets/icons/track.svg +1 -0
- package/src/assets/icons/warning.svg +1 -0
- package/src/assets/icons.js +25 -0
- package/src/core/icon-registry.js +173 -495
- package/src/core/tree-worker-client.js +17 -0
- package/src/index.d.ts +13 -0
- package/src/renderers/tree-row-renderer.js +35 -0
- package/src/tree-view-controller.js +9 -2
|
@@ -6,6 +6,8 @@ export class TreeWorkerClient {
|
|
|
6
6
|
this.pending = new Map();
|
|
7
7
|
this.ready = Promise.resolve();
|
|
8
8
|
this.worker.addEventListener('message', this.#onMessage);
|
|
9
|
+
this.worker.addEventListener('error', this.#onError);
|
|
10
|
+
this.worker.addEventListener('messageerror', this.#onMessageError);
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
setData(nodes) {
|
|
@@ -27,6 +29,8 @@ export class TreeWorkerClient {
|
|
|
27
29
|
for (const { reject } of this.pending.values()) reject(new Error('Tree worker destroyed'));
|
|
28
30
|
this.pending.clear();
|
|
29
31
|
this.worker.removeEventListener('message', this.#onMessage);
|
|
32
|
+
this.worker.removeEventListener('error', this.#onError);
|
|
33
|
+
this.worker.removeEventListener('messageerror', this.#onMessageError);
|
|
30
34
|
this.worker.terminate();
|
|
31
35
|
}
|
|
32
36
|
|
|
@@ -47,4 +51,17 @@ export class TreeWorkerClient {
|
|
|
47
51
|
if (ok) pending.resolve(result);
|
|
48
52
|
else pending.reject(new Error(error || 'Tree worker request failed'));
|
|
49
53
|
};
|
|
54
|
+
|
|
55
|
+
#onError = (event) => {
|
|
56
|
+
this.#rejectPending(new Error(event.message || 'Tree worker failed'));
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
#onMessageError = () => {
|
|
60
|
+
this.#rejectPending(new Error('Tree worker returned an unreadable message'));
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
#rejectPending(error) {
|
|
64
|
+
for (const { reject } of this.pending.values()) reject(error);
|
|
65
|
+
this.pending.clear();
|
|
66
|
+
}
|
|
50
67
|
}
|
package/src/index.d.ts
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
export type TreeViewAlign = 'start' | 'center' | 'end' | 'nearest';
|
|
2
2
|
|
|
3
|
+
export type IconDrawFunction = (ctx: CanvasRenderingContext2D, x: number, y: number, size: number, color: string) => void;
|
|
4
|
+
export type IconSource = string | CanvasImageSource | IconDrawFunction;
|
|
5
|
+
|
|
6
|
+
export class IconRegistry {
|
|
7
|
+
constructor(options?: { pixelRatio?: number });
|
|
8
|
+
register(name: string, icon: IconSource): any;
|
|
9
|
+
get(name: string): any;
|
|
10
|
+
onChange(listener: () => void): () => void;
|
|
11
|
+
prepare(options?: { icons?: Iterable<string>; size?: number; color?: string; pixelRatio?: number }): Promise<any[]>;
|
|
12
|
+
draw(ctx: CanvasRenderingContext2D, name: string, x: number, y: number, size: number, color: string): void;
|
|
13
|
+
}
|
|
14
|
+
|
|
3
15
|
export type TreeNode = {
|
|
4
16
|
id: string;
|
|
5
17
|
parentId?: string | null;
|
|
@@ -86,6 +98,7 @@ export class TreeViewController {
|
|
|
86
98
|
setDynamicState(patches: DynamicPatch[]): void;
|
|
87
99
|
setTheme(theme: any): void;
|
|
88
100
|
setLayoutMetrics(options?: { rowHeight?: number; indentWidth?: number; headerHeight?: number }): void;
|
|
101
|
+
registerIcon(name: string, icon: IconSource): any;
|
|
89
102
|
resize(width: number, height: number): void;
|
|
90
103
|
render(time?: number): void;
|
|
91
104
|
renderMeasured(time?: number): any;
|
|
@@ -9,6 +9,9 @@ export class TreeRowRenderer {
|
|
|
9
9
|
this.scene = null;
|
|
10
10
|
this.iconRegistry = iconRegistry ?? new IconRegistry();
|
|
11
11
|
this.renderedRows = 0;
|
|
12
|
+
this.iconRenderQueued = false;
|
|
13
|
+
this.preparedIconThemeKey = null;
|
|
14
|
+
this.stopIconListener = this.iconRegistry.onChange?.(() => this.#scheduleIconRender()) ?? null;
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
/** @param {HTMLCanvasElement} canvas */
|
|
@@ -22,6 +25,14 @@ export class TreeRowRenderer {
|
|
|
22
25
|
this.scene = scene;
|
|
23
26
|
}
|
|
24
27
|
|
|
28
|
+
destroy() {
|
|
29
|
+
this.stopIconListener?.();
|
|
30
|
+
this.stopIconListener = null;
|
|
31
|
+
this.canvas = null;
|
|
32
|
+
this.ctx = null;
|
|
33
|
+
this.scene = null;
|
|
34
|
+
}
|
|
35
|
+
|
|
25
36
|
updateDynamicState(_patches) {
|
|
26
37
|
// Canvas2D reads the state map directly; patches still stay on the hot path.
|
|
27
38
|
}
|
|
@@ -43,6 +54,7 @@ export class TreeRowRenderer {
|
|
|
43
54
|
|
|
44
55
|
const ctx = this.ctx;
|
|
45
56
|
const colors = theme.colors;
|
|
57
|
+
this.#prepareThemeIcons(theme, dpr);
|
|
46
58
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
47
59
|
ctx.fillStyle = colors.background;
|
|
48
60
|
ctx.fillRect(0, 0, viewportWidth, viewportHeight);
|
|
@@ -55,6 +67,29 @@ export class TreeRowRenderer {
|
|
|
55
67
|
ctx.restore();
|
|
56
68
|
}
|
|
57
69
|
|
|
70
|
+
#scheduleIconRender() {
|
|
71
|
+
if (this.iconRenderQueued) return;
|
|
72
|
+
this.iconRenderQueued = true;
|
|
73
|
+
const schedule = globalThis.requestAnimationFrame ?? ((callback) => setTimeout(callback, 0));
|
|
74
|
+
schedule(() => {
|
|
75
|
+
this.iconRenderQueued = false;
|
|
76
|
+
this.render();
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
#prepareThemeIcons(theme, pixelRatio) {
|
|
81
|
+
const iconColors = new Map([['placeholder', theme.colors.textMuted]]);
|
|
82
|
+
for (const style of Object.values(theme.types ?? {})) {
|
|
83
|
+
if (style?.icon) iconColors.set(style.icon, style.color ?? theme.colors.progressFill);
|
|
84
|
+
}
|
|
85
|
+
const key = `${pixelRatio}|${[...iconColors].map(([icon, color]) => `${icon}:${color}`).join(',')}`;
|
|
86
|
+
if (key === this.preparedIconThemeKey) return;
|
|
87
|
+
this.preparedIconThemeKey = key;
|
|
88
|
+
for (const [icon, color] of iconColors) {
|
|
89
|
+
this.iconRegistry.prepare?.({ icons: [icon], size: 15, color, pixelRatio });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
58
93
|
#drawHeader(ctx) {
|
|
59
94
|
const { viewport, columns, theme, sort, headerFilter, filterQuery } = this.scene;
|
|
60
95
|
if (viewport.headerHeight <= 0) return;
|
|
@@ -113,6 +113,7 @@ export class TreeViewController {
|
|
|
113
113
|
this.#nativeScroll = null;
|
|
114
114
|
this.inputController = null;
|
|
115
115
|
this.cellEditor = null;
|
|
116
|
+
this.renderer?.destroy?.();
|
|
116
117
|
this.canvas = null;
|
|
117
118
|
}
|
|
118
119
|
|
|
@@ -212,8 +213,14 @@ export class TreeViewController {
|
|
|
212
213
|
|
|
213
214
|
enableWorkers(workerUrl) {
|
|
214
215
|
if (this.workerClient) return Promise.resolve(this);
|
|
215
|
-
|
|
216
|
-
|
|
216
|
+
const client = new TreeWorkerClient(workerUrl);
|
|
217
|
+
this.workerClient = client;
|
|
218
|
+
return client.setData(this.model.nodes)
|
|
219
|
+
.then(() => this)
|
|
220
|
+
.catch((error) => {
|
|
221
|
+
if (this.workerClient === client) this.disableWorkers();
|
|
222
|
+
throw error;
|
|
223
|
+
});
|
|
217
224
|
}
|
|
218
225
|
|
|
219
226
|
disableWorkers() {
|