@kbve/droid 0.0.5 → 0.1.1
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 +3 -1
- package/comlink.js +1 -7
- package/droid.mjs +191 -61
- package/index.d.ts +29 -0
- package/index.js +9054 -2763
- package/lib/gateway/SupabaseGateway.d.ts +38 -0
- package/lib/gateway/SupabaseGateway.d.ts.map +1 -0
- package/lib/gateway/WorkerCommunication.d.ts +39 -0
- package/lib/gateway/WorkerCommunication.d.ts.map +1 -0
- package/lib/gateway/WorkerPool.d.ts +55 -0
- package/lib/gateway/WorkerPool.d.ts.map +1 -0
- package/lib/gateway/capabilities.d.ts +23 -0
- package/lib/gateway/capabilities.d.ts.map +1 -0
- package/lib/gateway/index.d.ts +9 -0
- package/lib/gateway/index.d.ts.map +1 -0
- package/lib/gateway/strategies/DirectStrategy.d.ts +52 -0
- package/lib/gateway/strategies/DirectStrategy.d.ts.map +1 -0
- package/lib/gateway/strategies/SharedWorkerStrategy.d.ts +46 -0
- package/lib/gateway/strategies/SharedWorkerStrategy.d.ts.map +1 -0
- package/lib/gateway/strategies/WebWorkerStrategy.d.ts +45 -0
- package/lib/gateway/strategies/WebWorkerStrategy.d.ts.map +1 -0
- package/lib/gateway/types.d.ts +81 -0
- package/lib/gateway/types.d.ts.map +1 -0
- package/lib/workers/canvas-ui-renderer.d.ts +25 -0
- package/lib/workers/canvas-ui-renderer.d.ts.map +1 -0
- package/lib/workers/canvas-worker.d.ts +14 -0
- package/lib/workers/canvas-worker.d.ts.map +1 -0
- package/lib/workers/canvas-worker.js +291 -145
- package/lib/workers/data.d.ts +40 -0
- package/lib/workers/data.d.ts.map +1 -0
- package/lib/workers/db-worker.d.ts +69 -0
- package/lib/workers/db-worker.d.ts.map +1 -0
- package/lib/workers/db-worker.js +2680 -2147
- package/lib/workers/events.d.ts +11 -0
- package/lib/workers/events.d.ts.map +1 -0
- package/lib/workers/flexbuilder.d.ts +4 -0
- package/lib/workers/flexbuilder.d.ts.map +1 -0
- package/lib/workers/init.d.ts +11 -0
- package/lib/workers/init.d.ts.map +1 -0
- package/lib/workers/main.d.ts +107 -0
- package/lib/workers/main.d.ts.map +1 -0
- package/lib/workers/supabase-db-worker.d.ts +2 -0
- package/lib/workers/supabase-db-worker.d.ts.map +1 -0
- package/lib/workers/supabase-db-worker.js +9337 -0
- package/lib/workers/supabase-shared-worker.d.ts +2 -0
- package/lib/workers/supabase-shared-worker.d.ts.map +1 -0
- package/lib/workers/supabase-shared-worker.js +12635 -0
- package/lib/workers/tools.d.ts +4 -0
- package/lib/workers/tools.d.ts.map +1 -0
- package/lib/workers/validate.d.ts +15 -0
- package/lib/workers/validate.d.ts.map +1 -0
- package/lib/workers/ws-worker.d.ts +11 -0
- package/lib/workers/ws-worker.d.ts.map +1 -0
- package/lib/workers/ws-worker.js +174 -115
- package/main.js +4875 -0
- package/package.json +17 -6
- package/reference.js +688 -228
- package/src/index.ts +38 -13
- package/src/lib/droid.spec.ts +14 -19
- package/src/lib/droid.ts +48 -4
- package/src/lib/gateway/SupabaseGateway.ts +183 -0
- package/src/lib/gateway/WorkerCommunication.ts +132 -0
- package/src/lib/gateway/WorkerPool.ts +304 -0
- package/src/lib/gateway/capabilities.ts +80 -0
- package/src/lib/gateway/index.ts +15 -0
- package/src/lib/gateway/strategies/DirectStrategy.ts +443 -0
- package/src/lib/gateway/strategies/SharedWorkerStrategy.ts +278 -0
- package/src/lib/gateway/strategies/WebWorkerStrategy.ts +278 -0
- package/src/lib/gateway/types.ts +112 -0
- package/src/lib/mod/mod-manager.ts +13 -8
- package/src/lib/mod/module/bento/mod-bento.worker.ts +1 -1
- package/src/lib/mod/module/supabase/mod-supabase.spec.ts +21 -25
- package/src/lib/mod/module/supabase/mod-supabase.ts +68 -0
- package/src/lib/mod/module/supabase/mod-supabase.worker.ts +1 -56
- package/src/lib/state/auth.spec.ts +58 -0
- package/src/lib/state/auth.ts +30 -0
- package/src/lib/state/index.ts +34 -0
- package/src/lib/state/overlay-manager.ts +125 -0
- package/src/lib/state/router.ts +24 -0
- package/src/lib/state/theme-sync.ts +75 -0
- package/src/lib/state/toasts.spec.ts +106 -0
- package/src/lib/state/toasts.ts +34 -0
- package/src/lib/state/ui.spec.ts +148 -0
- package/src/lib/state/ui.ts +45 -0
- package/src/lib/state/welcome-toast.ts +76 -0
- package/src/lib/types/bento.spec.ts +116 -0
- package/src/lib/types/bento.ts +94 -55
- package/src/lib/types/event-types.spec.ts +93 -0
- package/src/lib/types/event-types.ts +39 -3
- package/src/lib/types/modules.ts +12 -5
- package/src/lib/types/ui-event-types.ts +66 -0
- package/src/lib/workers/canvas-ui-renderer.ts +266 -0
- package/src/lib/workers/canvas-worker.ts +94 -9
- package/src/lib/workers/data.ts +59 -36
- package/src/lib/workers/db-worker.ts +79 -32
- package/src/lib/workers/events.spec.ts +208 -0
- package/src/lib/workers/events.ts +27 -14
- package/src/lib/workers/flexbuilder.ts +2 -2
- package/src/lib/workers/init.ts +25 -17
- package/src/lib/workers/main.ts +509 -272
- package/src/lib/workers/supabase-db-worker.ts +221 -0
- package/src/lib/workers/supabase-shared-worker.ts +572 -0
- package/src/lib/workers/validate.ts +64 -0
- package/src/lib/workers/ws-worker.ts +156 -25
- package/src/setup-vitest.ts +4 -2
- package/src/types.ts +30 -0
- package/src/vite-env.d.ts +1 -0
- package/workers/main.js +10 -1089
- package/src/types.d.ts +0 -31
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ToastPayload,
|
|
3
|
+
TooltipPayload,
|
|
4
|
+
ModalPayload,
|
|
5
|
+
} from '../types/ui-event-types';
|
|
6
|
+
|
|
7
|
+
interface ThemeColors {
|
|
8
|
+
accent: string;
|
|
9
|
+
accentLow: string;
|
|
10
|
+
accentHigh: string;
|
|
11
|
+
bg: string;
|
|
12
|
+
bgAccent: string;
|
|
13
|
+
text: string;
|
|
14
|
+
textAccent: string;
|
|
15
|
+
border: string;
|
|
16
|
+
white: string;
|
|
17
|
+
black: string;
|
|
18
|
+
mode: 'light' | 'dark';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const SEVERITY_COLORS: Record<
|
|
22
|
+
string,
|
|
23
|
+
{ bg: string; border: string; text: string }
|
|
24
|
+
> = {
|
|
25
|
+
success: { bg: '#065f46', border: '#10b981', text: '#d1fae5' },
|
|
26
|
+
warning: { bg: '#78350f', border: '#f59e0b', text: '#fef3c7' },
|
|
27
|
+
error: { bg: '#7f1d1d', border: '#ef4444', text: '#fee2e2' },
|
|
28
|
+
info: { bg: '#1e3a5f', border: '#3b82f6', text: '#dbeafe' },
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
interface ActiveToast extends ToastPayload {
|
|
32
|
+
createdAt: number;
|
|
33
|
+
opacity: number;
|
|
34
|
+
y: number;
|
|
35
|
+
targetY: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class CanvasUIRenderer {
|
|
39
|
+
private ctx: OffscreenCanvasRenderingContext2D | null = null;
|
|
40
|
+
private canvas: OffscreenCanvas | null = null;
|
|
41
|
+
private toasts: ActiveToast[] = [];
|
|
42
|
+
private tooltip: TooltipPayload | null = null;
|
|
43
|
+
private modal: ModalPayload | null = null;
|
|
44
|
+
private theme: ThemeColors = {
|
|
45
|
+
accent: '#8b5cf6',
|
|
46
|
+
accentLow: '#1e1033',
|
|
47
|
+
accentHigh: '#c4b5fd',
|
|
48
|
+
bg: '#0a0a0a',
|
|
49
|
+
bgAccent: '#3b1f6e',
|
|
50
|
+
text: '#e0e0e0',
|
|
51
|
+
textAccent: '#a78bfa',
|
|
52
|
+
border: '#4c1d95',
|
|
53
|
+
white: '#ffffff',
|
|
54
|
+
black: '#000000',
|
|
55
|
+
mode: 'dark',
|
|
56
|
+
};
|
|
57
|
+
private animFrame: number | null = null;
|
|
58
|
+
private dbGet: ((key: string) => Promise<string | null>) | null = null;
|
|
59
|
+
|
|
60
|
+
async bind(
|
|
61
|
+
canvas: OffscreenCanvas,
|
|
62
|
+
dbGet: (key: string) => Promise<string | null>,
|
|
63
|
+
): Promise<void> {
|
|
64
|
+
this.canvas = canvas;
|
|
65
|
+
this.ctx = canvas.getContext('2d');
|
|
66
|
+
this.dbGet = dbGet;
|
|
67
|
+
await this.refreshTheme();
|
|
68
|
+
this.startLoop();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async refreshTheme(): Promise<void> {
|
|
72
|
+
if (!this.dbGet) return;
|
|
73
|
+
this.theme = {
|
|
74
|
+
accent: (await this.dbGet('theme:accent')) ?? '#8b5cf6',
|
|
75
|
+
accentLow: (await this.dbGet('theme:accent-low')) ?? '#1e1033',
|
|
76
|
+
accentHigh: (await this.dbGet('theme:accent-high')) ?? '#c4b5fd',
|
|
77
|
+
bg: (await this.dbGet('theme:bg')) ?? '#0a0a0a',
|
|
78
|
+
bgAccent: (await this.dbGet('theme:bg-accent')) ?? '#3b1f6e',
|
|
79
|
+
text: (await this.dbGet('theme:text')) ?? '#e0e0e0',
|
|
80
|
+
textAccent: (await this.dbGet('theme:text-accent')) ?? '#a78bfa',
|
|
81
|
+
border: (await this.dbGet('theme:border')) ?? '#4c1d95',
|
|
82
|
+
white: (await this.dbGet('theme:white')) ?? '#ffffff',
|
|
83
|
+
black: (await this.dbGet('theme:black')) ?? '#000000',
|
|
84
|
+
mode:
|
|
85
|
+
((await this.dbGet('theme:mode')) as 'light' | 'dark') ??
|
|
86
|
+
'dark',
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
addToast(payload: ToastPayload): void {
|
|
91
|
+
const yOffset = 20 + this.toasts.length * 80;
|
|
92
|
+
this.toasts.push({
|
|
93
|
+
...payload,
|
|
94
|
+
createdAt: Date.now(),
|
|
95
|
+
opacity: 0,
|
|
96
|
+
y: yOffset - 20,
|
|
97
|
+
targetY: yOffset,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
removeToast(id: string): void {
|
|
102
|
+
const idx = this.toasts.findIndex((t) => t.id === id);
|
|
103
|
+
if (idx !== -1) this.toasts.splice(idx, 1);
|
|
104
|
+
this.recalcPositions();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
showTooltip(payload: TooltipPayload | null): void {
|
|
108
|
+
this.tooltip = payload;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
showModal(payload: ModalPayload | null): void {
|
|
112
|
+
this.modal = payload;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private recalcPositions(): void {
|
|
116
|
+
this.toasts.forEach((t, i) => {
|
|
117
|
+
t.targetY = 20 + i * 80;
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private startLoop(): void {
|
|
122
|
+
const draw = () => {
|
|
123
|
+
this.render();
|
|
124
|
+
this.animFrame = requestAnimationFrame(draw);
|
|
125
|
+
};
|
|
126
|
+
draw();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private render(): void {
|
|
130
|
+
if (!this.ctx || !this.canvas) return;
|
|
131
|
+
const { width, height } = this.canvas;
|
|
132
|
+
this.ctx.clearRect(0, 0, width, height);
|
|
133
|
+
|
|
134
|
+
// Auto-dismiss expired toasts
|
|
135
|
+
const now = Date.now();
|
|
136
|
+
this.toasts = this.toasts.filter((t) => {
|
|
137
|
+
if (
|
|
138
|
+
t.duration &&
|
|
139
|
+
t.duration > 0 &&
|
|
140
|
+
now - t.createdAt > t.duration
|
|
141
|
+
) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
return true;
|
|
145
|
+
});
|
|
146
|
+
this.recalcPositions();
|
|
147
|
+
|
|
148
|
+
// Animate toasts
|
|
149
|
+
for (const toast of this.toasts) {
|
|
150
|
+
if (toast.opacity < 1)
|
|
151
|
+
toast.opacity = Math.min(1, toast.opacity + 0.05);
|
|
152
|
+
toast.y += (toast.targetY - toast.y) * 0.15;
|
|
153
|
+
this.drawToast(toast, width);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Draw modal backdrop + content
|
|
157
|
+
if (this.modal) {
|
|
158
|
+
this.drawModal(width, height);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private drawToast(toast: ActiveToast, canvasWidth: number): void {
|
|
163
|
+
if (!this.ctx) return;
|
|
164
|
+
const ctx = this.ctx;
|
|
165
|
+
const w = 320;
|
|
166
|
+
const h = 64;
|
|
167
|
+
const x = canvasWidth - w - 20;
|
|
168
|
+
const y = toast.y;
|
|
169
|
+
const colors =
|
|
170
|
+
SEVERITY_COLORS[toast.severity] ?? SEVERITY_COLORS['info'];
|
|
171
|
+
|
|
172
|
+
ctx.globalAlpha = toast.opacity;
|
|
173
|
+
|
|
174
|
+
// Background
|
|
175
|
+
ctx.fillStyle = colors.bg;
|
|
176
|
+
this.roundRect(ctx, x, y, w, h, 8);
|
|
177
|
+
ctx.fill();
|
|
178
|
+
|
|
179
|
+
// Border
|
|
180
|
+
ctx.strokeStyle = colors.border;
|
|
181
|
+
ctx.lineWidth = 1.5;
|
|
182
|
+
this.roundRect(ctx, x, y, w, h, 8);
|
|
183
|
+
ctx.stroke();
|
|
184
|
+
|
|
185
|
+
// Text
|
|
186
|
+
ctx.fillStyle = colors.text;
|
|
187
|
+
ctx.font = '14px system-ui, -apple-system, sans-serif';
|
|
188
|
+
ctx.fillText(toast.message, x + 12, y + 24, w - 24);
|
|
189
|
+
|
|
190
|
+
// Severity label
|
|
191
|
+
ctx.font = 'bold 10px system-ui, sans-serif';
|
|
192
|
+
ctx.fillStyle = colors.border;
|
|
193
|
+
ctx.fillText(toast.severity.toUpperCase(), x + 12, y + 48);
|
|
194
|
+
|
|
195
|
+
ctx.globalAlpha = 1;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private drawModal(canvasWidth: number, canvasHeight: number): void {
|
|
199
|
+
if (!this.ctx || !this.modal) return;
|
|
200
|
+
const ctx = this.ctx;
|
|
201
|
+
|
|
202
|
+
// Backdrop
|
|
203
|
+
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
|
|
204
|
+
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
|
|
205
|
+
|
|
206
|
+
// Modal box
|
|
207
|
+
const w = Math.min(500, canvasWidth - 40);
|
|
208
|
+
const h = 300;
|
|
209
|
+
const x = (canvasWidth - w) / 2;
|
|
210
|
+
const y = (canvasHeight - h) / 2;
|
|
211
|
+
|
|
212
|
+
ctx.fillStyle = this.theme.bg;
|
|
213
|
+
this.roundRect(ctx, x, y, w, h, 12);
|
|
214
|
+
ctx.fill();
|
|
215
|
+
|
|
216
|
+
ctx.strokeStyle = this.theme.border;
|
|
217
|
+
ctx.lineWidth = 1;
|
|
218
|
+
this.roundRect(ctx, x, y, w, h, 12);
|
|
219
|
+
ctx.stroke();
|
|
220
|
+
|
|
221
|
+
// Title
|
|
222
|
+
if (this.modal.title) {
|
|
223
|
+
ctx.fillStyle = this.theme.text;
|
|
224
|
+
ctx.font = 'bold 18px system-ui, sans-serif';
|
|
225
|
+
ctx.fillText(this.modal.title, x + 20, y + 36, w - 40);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ID label
|
|
229
|
+
ctx.fillStyle = this.theme.textAccent;
|
|
230
|
+
ctx.font = '12px system-ui, sans-serif';
|
|
231
|
+
ctx.fillText(this.modal.id, x + 20, y + 60, w - 40);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private roundRect(
|
|
235
|
+
ctx: OffscreenCanvasRenderingContext2D,
|
|
236
|
+
x: number,
|
|
237
|
+
y: number,
|
|
238
|
+
w: number,
|
|
239
|
+
h: number,
|
|
240
|
+
r: number,
|
|
241
|
+
): void {
|
|
242
|
+
ctx.beginPath();
|
|
243
|
+
ctx.moveTo(x + r, y);
|
|
244
|
+
ctx.lineTo(x + w - r, y);
|
|
245
|
+
ctx.quadraticCurveTo(x + w, y, x + w, y + r);
|
|
246
|
+
ctx.lineTo(x + w, y + h - r);
|
|
247
|
+
ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
|
|
248
|
+
ctx.lineTo(x + r, y + h);
|
|
249
|
+
ctx.quadraticCurveTo(x, y + h, x, y + h - r);
|
|
250
|
+
ctx.lineTo(x, y + r);
|
|
251
|
+
ctx.quadraticCurveTo(x, y, x + r, y);
|
|
252
|
+
ctx.closePath();
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
unbind(): void {
|
|
256
|
+
if (this.animFrame !== null) {
|
|
257
|
+
cancelAnimationFrame(this.animFrame);
|
|
258
|
+
this.animFrame = null;
|
|
259
|
+
}
|
|
260
|
+
this.ctx = null;
|
|
261
|
+
this.canvas = null;
|
|
262
|
+
this.toasts = [];
|
|
263
|
+
this.tooltip = null;
|
|
264
|
+
this.modal = null;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
@@ -1,8 +1,28 @@
|
|
|
1
1
|
import { expose } from 'comlink';
|
|
2
|
+
import { CanvasUIRenderer } from './canvas-ui-renderer';
|
|
3
|
+
import type {
|
|
4
|
+
ToastPayload,
|
|
5
|
+
TooltipPayload,
|
|
6
|
+
ModalPayload,
|
|
7
|
+
} from '../types/ui-event-types';
|
|
2
8
|
|
|
3
9
|
export interface CanvasWorkerAPI {
|
|
4
|
-
bindCanvas(
|
|
10
|
+
bindCanvas(
|
|
11
|
+
panelId: string,
|
|
12
|
+
canvas: OffscreenCanvas,
|
|
13
|
+
mode?: CanvasDrawMode,
|
|
14
|
+
): Promise<void>;
|
|
5
15
|
unbindCanvas(panelId: string): Promise<void>;
|
|
16
|
+
bindUICanvas(
|
|
17
|
+
canvas: OffscreenCanvas,
|
|
18
|
+
dbGet: (key: string) => Promise<string | null>,
|
|
19
|
+
): Promise<void>;
|
|
20
|
+
unbindUICanvas(): void;
|
|
21
|
+
addCanvasToast(payload: ToastPayload): void;
|
|
22
|
+
removeCanvasToast(id: string): void;
|
|
23
|
+
showCanvasTooltip(payload: TooltipPayload | null): void;
|
|
24
|
+
showCanvasModal(payload: ModalPayload | null): void;
|
|
25
|
+
refreshUITheme(): Promise<void>;
|
|
6
26
|
}
|
|
7
27
|
|
|
8
28
|
interface CanvasBinding {
|
|
@@ -15,18 +35,38 @@ interface CanvasBinding {
|
|
|
15
35
|
|
|
16
36
|
export type CanvasDrawMode = 'static' | 'animated' | 'dynamic';
|
|
17
37
|
|
|
38
|
+
const uiRenderer = new CanvasUIRenderer();
|
|
39
|
+
|
|
40
|
+
// Listen for theme changes from main thread
|
|
41
|
+
try {
|
|
42
|
+
const bc = new BroadcastChannel('kbve_theme');
|
|
43
|
+
bc.onmessage = () => {
|
|
44
|
+
void uiRenderer.refreshTheme();
|
|
45
|
+
};
|
|
46
|
+
} catch {
|
|
47
|
+
// BroadcastChannel unavailable in this context
|
|
48
|
+
}
|
|
49
|
+
|
|
18
50
|
const CanvasManager = {
|
|
19
51
|
bindings: new Map<string, CanvasBinding>(),
|
|
20
52
|
|
|
21
|
-
async bindCanvas(
|
|
53
|
+
async bindCanvas(
|
|
54
|
+
panelId: string,
|
|
55
|
+
canvas: OffscreenCanvas,
|
|
56
|
+
mode: CanvasDrawMode = 'animated',
|
|
57
|
+
) {
|
|
22
58
|
const ctx = canvas.getContext('2d');
|
|
23
59
|
|
|
24
60
|
if (!ctx) {
|
|
25
|
-
console.error(
|
|
61
|
+
console.error(
|
|
62
|
+
`[CanvasWorker] Failed to get 2D context for panel ${panelId}`,
|
|
63
|
+
);
|
|
26
64
|
return;
|
|
27
65
|
}
|
|
28
66
|
|
|
29
|
-
console.log(
|
|
67
|
+
console.log(
|
|
68
|
+
`[CanvasWorker] Successfully bound canvas for panel ${panelId} with mode ${mode}`,
|
|
69
|
+
);
|
|
30
70
|
|
|
31
71
|
this.bindings.set(panelId, { ctx, canvas, panelId, mode });
|
|
32
72
|
|
|
@@ -48,7 +88,9 @@ const CanvasManager = {
|
|
|
48
88
|
this.drawDynamic(binding);
|
|
49
89
|
break;
|
|
50
90
|
default:
|
|
51
|
-
console.warn(
|
|
91
|
+
console.warn(
|
|
92
|
+
`[CanvasWorker] Unknown draw mode for panel ${panelId}`,
|
|
93
|
+
);
|
|
52
94
|
}
|
|
53
95
|
},
|
|
54
96
|
|
|
@@ -63,7 +105,12 @@ const CanvasManager = {
|
|
|
63
105
|
const drawFrame = () => {
|
|
64
106
|
hue = (hue + 1) % 360;
|
|
65
107
|
binding.ctx.fillStyle = `hsl(${hue}, 100%, 50%)`;
|
|
66
|
-
binding.ctx.fillRect(
|
|
108
|
+
binding.ctx.fillRect(
|
|
109
|
+
0,
|
|
110
|
+
0,
|
|
111
|
+
binding.canvas.width,
|
|
112
|
+
binding.canvas.height,
|
|
113
|
+
);
|
|
67
114
|
|
|
68
115
|
binding.animationFrame = requestAnimationFrame(drawFrame);
|
|
69
116
|
};
|
|
@@ -76,18 +123,23 @@ const CanvasManager = {
|
|
|
76
123
|
|
|
77
124
|
const drawFrame = () => {
|
|
78
125
|
time += 0.05;
|
|
79
|
-
binding.ctx.clearRect(
|
|
126
|
+
binding.ctx.clearRect(
|
|
127
|
+
0,
|
|
128
|
+
0,
|
|
129
|
+
binding.canvas.width,
|
|
130
|
+
binding.canvas.height,
|
|
131
|
+
);
|
|
80
132
|
binding.ctx.beginPath();
|
|
81
133
|
binding.ctx.arc(
|
|
82
134
|
binding.canvas.width / 2 + Math.sin(time) * 50,
|
|
83
135
|
binding.canvas.height / 2 + Math.cos(time) * 50,
|
|
84
136
|
30,
|
|
85
137
|
0,
|
|
86
|
-
Math.PI * 2
|
|
138
|
+
Math.PI * 2,
|
|
87
139
|
);
|
|
88
140
|
binding.ctx.fillStyle = 'orange';
|
|
89
141
|
binding.ctx.fill();
|
|
90
|
-
|
|
142
|
+
|
|
91
143
|
binding.animationFrame = requestAnimationFrame(drawFrame);
|
|
92
144
|
};
|
|
93
145
|
|
|
@@ -102,6 +154,39 @@ const CanvasManager = {
|
|
|
102
154
|
this.bindings.delete(panelId);
|
|
103
155
|
console.log(`[CanvasWorker] Unbound canvas for panel ${panelId}`);
|
|
104
156
|
},
|
|
157
|
+
|
|
158
|
+
// ── UI Overlay Canvas ──
|
|
159
|
+
|
|
160
|
+
async bindUICanvas(
|
|
161
|
+
canvas: OffscreenCanvas,
|
|
162
|
+
dbGet: (key: string) => Promise<string | null>,
|
|
163
|
+
) {
|
|
164
|
+
await uiRenderer.bind(canvas, dbGet);
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
unbindUICanvas() {
|
|
168
|
+
uiRenderer.unbind();
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
addCanvasToast(payload: ToastPayload) {
|
|
172
|
+
uiRenderer.addToast(payload);
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
removeCanvasToast(id: string) {
|
|
176
|
+
uiRenderer.removeToast(id);
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
showCanvasTooltip(payload: TooltipPayload | null) {
|
|
180
|
+
uiRenderer.showTooltip(payload);
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
showCanvasModal(payload: ModalPayload | null) {
|
|
184
|
+
uiRenderer.showModal(payload);
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
refreshUITheme() {
|
|
188
|
+
return uiRenderer.refreshTheme();
|
|
189
|
+
},
|
|
105
190
|
};
|
|
106
191
|
|
|
107
192
|
expose(CanvasManager);
|
package/src/lib/workers/data.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { PayloadFormat, JediEnvelopeFlex, MessageKind } from '../types/jedi';
|
|
2
2
|
import { builder, toReference } from './flexbuilder';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
function buildFlexPayload<T extends Record<string, unknown>>(
|
|
5
|
+
payload: T,
|
|
6
|
+
): Uint8Array {
|
|
6
7
|
const b = builder();
|
|
7
8
|
b.startMap();
|
|
8
9
|
|
|
@@ -15,7 +16,7 @@ function buildFlexPayload<T extends Record<string, any>>(payload: T): Uint8Array
|
|
|
15
16
|
return b.finish();
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
export function wrapEnvelope<T extends Record<string,
|
|
19
|
+
export function wrapEnvelope<T extends Record<string, unknown>>(
|
|
19
20
|
payload: T,
|
|
20
21
|
kind: number,
|
|
21
22
|
format: PayloadFormat,
|
|
@@ -34,22 +35,30 @@ export function wrapEnvelope<T extends Record<string, any>>(
|
|
|
34
35
|
|
|
35
36
|
const b = builder();
|
|
36
37
|
b.startMap();
|
|
37
|
-
b.addKey('version');
|
|
38
|
-
b.
|
|
39
|
-
b.addKey('
|
|
40
|
-
b.
|
|
41
|
-
b.addKey('
|
|
38
|
+
b.addKey('version');
|
|
39
|
+
b.add(version);
|
|
40
|
+
b.addKey('kind');
|
|
41
|
+
b.add(kind);
|
|
42
|
+
b.addKey('format');
|
|
43
|
+
b.add(format);
|
|
44
|
+
b.addKey('payload');
|
|
45
|
+
b.add(serializedPayload);
|
|
46
|
+
b.addKey('metadata');
|
|
47
|
+
b.add(metadata ?? new Uint8Array());
|
|
42
48
|
b.end();
|
|
43
49
|
|
|
44
50
|
return b.finish();
|
|
45
51
|
}
|
|
46
52
|
|
|
47
53
|
export function unwrapEnvelope<T = unknown>(
|
|
48
|
-
buffer: Uint8Array | ArrayBuffer
|
|
54
|
+
buffer: Uint8Array | ArrayBuffer,
|
|
49
55
|
): { envelope: JediEnvelopeFlex; payload: T } {
|
|
50
56
|
const view = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
|
|
51
57
|
|
|
52
|
-
const root = toReference(view.buffer as ArrayBuffer).toObject() as Record<
|
|
58
|
+
const root = toReference(view.buffer as ArrayBuffer).toObject() as Record<
|
|
59
|
+
string,
|
|
60
|
+
unknown
|
|
61
|
+
>;
|
|
53
62
|
|
|
54
63
|
if (
|
|
55
64
|
typeof root['version'] !== 'number' ||
|
|
@@ -65,8 +74,10 @@ export function unwrapEnvelope<T = unknown>(
|
|
|
65
74
|
version: root['version'],
|
|
66
75
|
kind: root['kind'],
|
|
67
76
|
format: root['format'],
|
|
68
|
-
payload: new Uint8Array(root['payload']),
|
|
69
|
-
metadata: root['metadata']
|
|
77
|
+
payload: new Uint8Array(root['payload'] as ArrayLike<number>),
|
|
78
|
+
metadata: root['metadata']
|
|
79
|
+
? new Uint8Array(root['metadata'] as ArrayLike<number>)
|
|
80
|
+
: undefined,
|
|
70
81
|
};
|
|
71
82
|
|
|
72
83
|
let parsedPayload: T;
|
|
@@ -75,9 +86,13 @@ export function unwrapEnvelope<T = unknown>(
|
|
|
75
86
|
const ref = toReference(envelope.payload.buffer as ArrayBuffer);
|
|
76
87
|
parsedPayload = ref.toObject() as T;
|
|
77
88
|
} else if (envelope.format === PayloadFormat.JSON) {
|
|
78
|
-
parsedPayload = JSON.parse(
|
|
89
|
+
parsedPayload = JSON.parse(
|
|
90
|
+
new TextDecoder().decode(envelope.payload),
|
|
91
|
+
) as T;
|
|
79
92
|
} else {
|
|
80
|
-
throw new Error(
|
|
93
|
+
throw new Error(
|
|
94
|
+
`[unwrapEnvelope] Unsupported format: ${envelope.format}`,
|
|
95
|
+
);
|
|
81
96
|
}
|
|
82
97
|
|
|
83
98
|
return { envelope, payload: parsedPayload };
|
|
@@ -89,7 +104,8 @@ export function unwrapFlexToJson(buffer: Uint8Array) {
|
|
|
89
104
|
|
|
90
105
|
export function inspectFlex(buffer: Uint8Array | ArrayBuffer): void {
|
|
91
106
|
try {
|
|
92
|
-
const view =
|
|
107
|
+
const view =
|
|
108
|
+
buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
|
|
93
109
|
const obj = unwrapFlexToJson(view);
|
|
94
110
|
console.log('[FlexObject]', JSON.stringify(obj, null, 2));
|
|
95
111
|
} catch (err) {
|
|
@@ -101,14 +117,13 @@ export function hasKind(kind: number, flag: number): boolean {
|
|
|
101
117
|
return (kind & flag) === flag;
|
|
102
118
|
}
|
|
103
119
|
|
|
104
|
-
|
|
105
120
|
// Redis Helpers
|
|
106
121
|
|
|
107
122
|
export function wrapRedisSet(key: string, value: string): Uint8Array {
|
|
108
123
|
return wrapEnvelope(
|
|
109
124
|
{ key, value },
|
|
110
125
|
MessageKind.SET | MessageKind.REDIS,
|
|
111
|
-
PayloadFormat.FLEX
|
|
126
|
+
PayloadFormat.FLEX,
|
|
112
127
|
);
|
|
113
128
|
}
|
|
114
129
|
|
|
@@ -116,7 +131,7 @@ export function wrapRedisGet(key: string): Uint8Array {
|
|
|
116
131
|
return wrapEnvelope(
|
|
117
132
|
{ key },
|
|
118
133
|
MessageKind.GET | MessageKind.REDIS,
|
|
119
|
-
PayloadFormat.FLEX
|
|
134
|
+
PayloadFormat.FLEX,
|
|
120
135
|
);
|
|
121
136
|
}
|
|
122
137
|
|
|
@@ -124,11 +139,13 @@ export function wrapRedisDel(key: string): Uint8Array {
|
|
|
124
139
|
return wrapEnvelope(
|
|
125
140
|
{ key },
|
|
126
141
|
MessageKind.DEL | MessageKind.REDIS,
|
|
127
|
-
PayloadFormat.FLEX
|
|
142
|
+
PayloadFormat.FLEX,
|
|
128
143
|
);
|
|
129
144
|
}
|
|
130
145
|
|
|
131
|
-
export function parseRedisPayload<T = unknown>(
|
|
146
|
+
export function parseRedisPayload<T = unknown>(
|
|
147
|
+
envelopeBytes: Uint8Array,
|
|
148
|
+
): {
|
|
132
149
|
envelope: JediEnvelopeFlex;
|
|
133
150
|
payload: T;
|
|
134
151
|
} {
|
|
@@ -156,25 +173,28 @@ export function wrapRedisXRead(
|
|
|
156
173
|
count?: number,
|
|
157
174
|
block?: number,
|
|
158
175
|
): Uint8Array {
|
|
159
|
-
|
|
160
176
|
const inner = builder();
|
|
161
177
|
inner.startMap();
|
|
162
178
|
|
|
163
179
|
inner.addKey('streams');
|
|
164
|
-
inner.startVector();
|
|
180
|
+
inner.startVector();
|
|
165
181
|
for (const { stream, id } of streams) {
|
|
166
182
|
inner.startMap();
|
|
167
|
-
inner.addKey('stream');
|
|
168
|
-
inner.
|
|
183
|
+
inner.addKey('stream');
|
|
184
|
+
inner.add(stream);
|
|
185
|
+
inner.addKey('id');
|
|
186
|
+
inner.add(id);
|
|
169
187
|
inner.end();
|
|
170
|
-
|
|
188
|
+
}
|
|
171
189
|
inner.end();
|
|
172
190
|
|
|
173
191
|
if (count !== undefined) {
|
|
174
|
-
inner.addKey('count');
|
|
192
|
+
inner.addKey('count');
|
|
193
|
+
inner.add(count);
|
|
175
194
|
}
|
|
176
195
|
if (block !== undefined) {
|
|
177
|
-
inner.addKey('block');
|
|
196
|
+
inner.addKey('block');
|
|
197
|
+
inner.add(block);
|
|
178
198
|
}
|
|
179
199
|
|
|
180
200
|
inner.end();
|
|
@@ -182,18 +202,21 @@ export function wrapRedisXRead(
|
|
|
182
202
|
|
|
183
203
|
const b = builder();
|
|
184
204
|
b.startMap();
|
|
185
|
-
b.addKey('version');
|
|
186
|
-
b.
|
|
187
|
-
b.addKey('
|
|
188
|
-
b.
|
|
189
|
-
b.addKey('
|
|
205
|
+
b.addKey('version');
|
|
206
|
+
b.add(1);
|
|
207
|
+
b.addKey('kind');
|
|
208
|
+
b.add(MessageKind.READ | MessageKind.STREAM | MessageKind.REDIS);
|
|
209
|
+
b.addKey('format');
|
|
210
|
+
b.add(PayloadFormat.FLEX);
|
|
211
|
+
b.addKey('payload');
|
|
212
|
+
b.add(serializedPayload);
|
|
213
|
+
b.addKey('metadata');
|
|
214
|
+
b.add(new Uint8Array());
|
|
190
215
|
b.end();
|
|
191
216
|
|
|
192
217
|
return b.finish();
|
|
193
218
|
}
|
|
194
219
|
|
|
195
|
-
|
|
196
|
-
|
|
197
220
|
export const scopeData = {
|
|
198
221
|
wrapEnvelope,
|
|
199
222
|
unwrapEnvelope,
|
|
@@ -202,14 +225,14 @@ export const scopeData = {
|
|
|
202
225
|
unwrapFlexToJson,
|
|
203
226
|
inspectFlex,
|
|
204
227
|
hasKind,
|
|
205
|
-
|
|
228
|
+
redis: {
|
|
206
229
|
wrapRedisSet,
|
|
207
230
|
wrapRedisGet,
|
|
208
231
|
wrapRedisDel,
|
|
209
232
|
wrapRedisXAdd,
|
|
210
233
|
wrapRedisXRead,
|
|
211
234
|
parseRedisPayload,
|
|
212
|
-
}
|
|
235
|
+
},
|
|
213
236
|
};
|
|
214
237
|
|
|
215
238
|
export type FlexDataAPI = typeof scopeData;
|