gitmaps 1.1.0 → 1.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.
Files changed (121) hide show
  1. package/README.md +267 -118
  2. package/app/[...slug]/page.client.tsx +1 -0
  3. package/app/[...slug]/page.tsx +6 -0
  4. package/app/analytics.db +0 -0
  5. package/app/api/analytics/route.ts +64 -0
  6. package/app/api/auth/positions/route.ts +95 -33
  7. package/app/api/build-info/route.ts +19 -0
  8. package/app/api/chat/route.ts +13 -2
  9. package/app/api/og-image/route.ts +14 -0
  10. package/app/api/repo/file-content/route.ts +73 -20
  11. package/app/api/repo/load/route.test.ts +62 -0
  12. package/app/api/repo/load/route.ts +41 -1
  13. package/app/api/repo/pdf-thumb/route.ts +127 -0
  14. package/app/api/repo/resolve-slug/route.ts +51 -0
  15. package/app/api/repo/tree/route.ts +188 -104
  16. package/app/api/version/route.ts +26 -0
  17. package/app/globals.css +5706 -4938
  18. package/app/layout.tsx +1279 -490
  19. package/app/lib/auto-arrange.test.ts +158 -0
  20. package/app/lib/auto-arrange.ts +147 -0
  21. package/app/lib/canvas-export.ts +358 -358
  22. package/app/lib/canvas.ts +625 -564
  23. package/app/lib/cards.tsx +1361 -916
  24. package/app/lib/chat.tsx +65 -9
  25. package/app/lib/code-editor.ts +86 -2
  26. package/app/lib/context.test.ts +32 -0
  27. package/app/lib/context.ts +19 -3
  28. package/app/lib/cursor-sharing.ts +34 -0
  29. package/app/lib/events.tsx +71 -93
  30. package/app/lib/export-canvas.ts +287 -0
  31. package/app/lib/file-card-plugin.ts +148 -148
  32. package/app/lib/file-modal.tsx +49 -0
  33. package/app/lib/file-preview.ts +486 -427
  34. package/app/lib/github-import.test.ts +424 -0
  35. package/app/lib/initial-route-hydration.test.ts +283 -0
  36. package/app/lib/initial-route-hydration.ts +202 -0
  37. package/app/lib/landing-reset.test.ts +99 -0
  38. package/app/lib/landing-reset.ts +106 -0
  39. package/app/lib/landing-shell.test.ts +75 -0
  40. package/app/lib/large-repo-optimization.ts +37 -0
  41. package/app/lib/layout-snapshots.ts +320 -0
  42. package/app/lib/loading.test.ts +69 -0
  43. package/app/lib/loading.tsx +160 -45
  44. package/app/lib/mount-cleanup.test.ts +52 -0
  45. package/app/lib/mount-cleanup.ts +34 -0
  46. package/app/lib/mount-init.test.ts +123 -0
  47. package/app/lib/mount-init.ts +107 -0
  48. package/app/lib/mount-lifecycle.test.ts +39 -0
  49. package/app/lib/mount-lifecycle.ts +12 -0
  50. package/app/lib/mount-route-wiring.test.ts +87 -0
  51. package/app/lib/mount-route-wiring.ts +84 -0
  52. package/app/lib/multi-repo.ts +14 -0
  53. package/app/lib/onboarding-tutorial.ts +278 -0
  54. package/app/lib/positions.ts +190 -121
  55. package/app/lib/recent-commits.test.ts +869 -0
  56. package/app/lib/recent-commits.ts +227 -0
  57. package/app/lib/repo-handoff.test.ts +23 -0
  58. package/app/lib/repo-handoff.ts +16 -0
  59. package/app/lib/repo-progressive.ts +119 -0
  60. package/app/lib/repo-select.test.ts +61 -0
  61. package/app/lib/repo-select.ts +74 -0
  62. package/app/lib/repo.tsx +1383 -987
  63. package/app/lib/role.ts +228 -0
  64. package/app/lib/route-catchall.test.ts +27 -0
  65. package/app/lib/route-repo-entry.test.ts +95 -0
  66. package/app/lib/route-repo-entry.ts +36 -0
  67. package/app/lib/router-contract.test.ts +22 -0
  68. package/app/lib/router-contract.ts +19 -0
  69. package/app/lib/shared-layout.test.ts +86 -0
  70. package/app/lib/shared-layout.ts +82 -0
  71. package/app/lib/status-bar.test.ts +118 -0
  72. package/app/lib/status-bar.ts +365 -128
  73. package/app/lib/sync-controls.test.ts +43 -0
  74. package/app/lib/sync-controls.tsx +303 -0
  75. package/app/lib/test-dom.ts +145 -0
  76. package/app/lib/test-fixtures/router-contract/[...slug]/page.tsx +3 -0
  77. package/app/lib/test-fixtures/router-contract/api/health/route.ts +3 -0
  78. package/app/lib/test-fixtures/router-contract/api/version/route.ts +3 -0
  79. package/app/lib/test-fixtures/router-contract/galaxy-canvas/page.tsx +3 -0
  80. package/app/lib/test-fixtures/router-contract/page.tsx +3 -0
  81. package/app/lib/transclusion-smoke.test.ts +163 -0
  82. package/app/lib/tutorial.ts +301 -0
  83. package/app/lib/version.ts +93 -0
  84. package/app/lib/viewport-culling.ts +740 -735
  85. package/app/lib/virtual-files.ts +456 -0
  86. package/app/lib/webgl-text.ts +189 -0
  87. package/app/lib/{galaxydraw-bridge.ts → xydraw-bridge.ts} +485 -482
  88. package/app/lib/{galaxydraw.test.ts → xydraw.test.ts} +228 -229
  89. package/app/og-image.png +0 -0
  90. package/app/page.client.tsx +70 -269
  91. package/app/page.tsx +15 -16
  92. package/app/state/machine.js +13 -0
  93. package/package.json +16 -7
  94. package/server.ts +10 -0
  95. package/app/[owner]/[repo]/page.tsx +0 -6
  96. package/app/[slug]/page.tsx +0 -6
  97. package/packages/galaxydraw/README.md +0 -296
  98. package/packages/galaxydraw/banner.png +0 -0
  99. package/packages/galaxydraw/demo/build-static.ts +0 -100
  100. package/packages/galaxydraw/demo/client.ts +0 -154
  101. package/packages/galaxydraw/demo/dist/client.js +0 -8
  102. package/packages/galaxydraw/demo/index.html +0 -256
  103. package/packages/galaxydraw/demo/server.ts +0 -96
  104. package/packages/galaxydraw/dist/index.js +0 -984
  105. package/packages/galaxydraw/dist/index.js.map +0 -16
  106. package/packages/galaxydraw/node_modules/.bin/tsc.bunx +0 -0
  107. package/packages/galaxydraw/node_modules/.bin/tsc.exe +0 -0
  108. package/packages/galaxydraw/node_modules/.bin/tsserver.bunx +0 -0
  109. package/packages/galaxydraw/node_modules/.bin/tsserver.exe +0 -0
  110. package/packages/galaxydraw/package.json +0 -49
  111. package/packages/galaxydraw/perf.test.ts +0 -284
  112. package/packages/galaxydraw/src/core/cards.ts +0 -435
  113. package/packages/galaxydraw/src/core/engine.ts +0 -339
  114. package/packages/galaxydraw/src/core/events.ts +0 -81
  115. package/packages/galaxydraw/src/core/layout.ts +0 -136
  116. package/packages/galaxydraw/src/core/minimap.ts +0 -216
  117. package/packages/galaxydraw/src/core/state.ts +0 -177
  118. package/packages/galaxydraw/src/core/viewport.ts +0 -106
  119. package/packages/galaxydraw/src/galaxydraw.css +0 -166
  120. package/packages/galaxydraw/src/index.ts +0 -40
  121. package/packages/galaxydraw/tsconfig.json +0 -30
@@ -1,984 +0,0 @@
1
- // src/core/state.ts
2
- class CanvasState {
3
- zoom = 1;
4
- offsetX = 0;
5
- offsetY = 0;
6
- viewportEl = null;
7
- contentEl = null;
8
- listeners = new Set;
9
- MIN_ZOOM = 0.05;
10
- MAX_ZOOM = 5;
11
- constructor(viewport, content) {
12
- this.viewportEl = viewport ?? null;
13
- this.contentEl = content ?? null;
14
- }
15
- bind(viewport, content) {
16
- this.viewportEl = viewport;
17
- this.contentEl = content;
18
- this.applyTransform();
19
- }
20
- snapshot() {
21
- return { zoom: this.zoom, offsetX: this.offsetX, offsetY: this.offsetY };
22
- }
23
- subscribe(fn) {
24
- this.listeners.add(fn);
25
- return () => this.listeners.delete(fn);
26
- }
27
- notify() {
28
- for (const fn of this.listeners)
29
- fn();
30
- }
31
- applyTransform() {
32
- if (!this.contentEl)
33
- return;
34
- this.contentEl.style.transform = `translate(${this.offsetX}px, ${this.offsetY}px) scale(${this.zoom})`;
35
- }
36
- set(zoom, offsetX, offsetY) {
37
- this.zoom = Math.max(this.MIN_ZOOM, Math.min(this.MAX_ZOOM, zoom));
38
- this.offsetX = offsetX;
39
- this.offsetY = offsetY;
40
- this.applyTransform();
41
- this.notify();
42
- }
43
- pan(dx, dy) {
44
- this.offsetX += dx;
45
- this.offsetY += dy;
46
- this.applyTransform();
47
- this.notify();
48
- }
49
- panTo(worldX, worldY) {
50
- if (!this.viewportEl)
51
- return;
52
- const vpW = this.viewportEl.clientWidth;
53
- const vpH = this.viewportEl.clientHeight;
54
- this.offsetX = vpW / 2 - worldX * this.zoom;
55
- this.offsetY = vpH / 2 - worldY * this.zoom;
56
- this.applyTransform();
57
- this.notify();
58
- }
59
- zoomToward(screenX, screenY, factor) {
60
- const newZoom = Math.max(this.MIN_ZOOM, Math.min(this.MAX_ZOOM, this.zoom * factor));
61
- if (newZoom === this.zoom)
62
- return;
63
- const rect = this.viewportEl?.getBoundingClientRect();
64
- const mouseX = screenX - (rect?.left ?? 0);
65
- const mouseY = screenY - (rect?.top ?? 0);
66
- const worldX = (mouseX - this.offsetX) / this.zoom;
67
- const worldY = (mouseY - this.offsetY) / this.zoom;
68
- this.zoom = newZoom;
69
- this.offsetX = mouseX - worldX * newZoom;
70
- this.offsetY = mouseY - worldY * newZoom;
71
- this.applyTransform();
72
- this.notify();
73
- }
74
- screenToWorld(screenX, screenY) {
75
- const rect = this.viewportEl?.getBoundingClientRect();
76
- const localX = screenX - (rect?.left ?? 0);
77
- const localY = screenY - (rect?.top ?? 0);
78
- return {
79
- x: (localX - this.offsetX) / this.zoom,
80
- y: (localY - this.offsetY) / this.zoom
81
- };
82
- }
83
- worldToScreen(worldX, worldY) {
84
- const rect = this.viewportEl?.getBoundingClientRect();
85
- return {
86
- x: worldX * this.zoom + this.offsetX + (rect?.left ?? 0),
87
- y: worldY * this.zoom + this.offsetY + (rect?.top ?? 0)
88
- };
89
- }
90
- getVisibleWorldRect(margin = 0) {
91
- if (!this.viewportEl)
92
- return null;
93
- const vpW = this.viewportEl.clientWidth;
94
- const vpH = this.viewportEl.clientHeight;
95
- const left = (-this.offsetX - margin) / this.zoom;
96
- const top = (-this.offsetY - margin) / this.zoom;
97
- const right = (vpW - this.offsetX + margin) / this.zoom;
98
- const bottom = (vpH - this.offsetY + margin) / this.zoom;
99
- return { left, top, right, bottom, width: right - left, height: bottom - top };
100
- }
101
- fitRect(worldLeft, worldTop, worldRight, worldBottom, padding = 60) {
102
- if (!this.viewportEl)
103
- return;
104
- const vpW = this.viewportEl.clientWidth;
105
- const vpH = this.viewportEl.clientHeight;
106
- const w = worldRight - worldLeft + padding * 2;
107
- const h = worldBottom - worldTop + padding * 2;
108
- const zoom = Math.min(vpW / w, vpH / h, this.MAX_ZOOM);
109
- this.set(zoom, (vpW - w * zoom) / 2 - (worldLeft - padding) * zoom, (vpH - h * zoom) / 2 - (worldTop - padding) * zoom);
110
- }
111
- }
112
-
113
- // src/core/cards.ts
114
- var DEFAULT_OPTIONS = {
115
- defaultWidth: 400,
116
- defaultHeight: 300,
117
- minWidth: 200,
118
- minHeight: 150,
119
- gridSize: 0,
120
- cornerSize: 40
121
- };
122
-
123
- class CardManager {
124
- state;
125
- bus;
126
- canvas;
127
- cards = new Map;
128
- deferred = new Map;
129
- selected = new Set;
130
- topZ = 10;
131
- plugins = new Map;
132
- opts;
133
- constructor(state, bus, canvas, options) {
134
- this.state = state;
135
- this.bus = bus;
136
- this.canvas = canvas;
137
- this.opts = { ...DEFAULT_OPTIONS, ...options };
138
- }
139
- registerPlugin(plugin) {
140
- this.plugins.set(plugin.type, plugin);
141
- }
142
- create(type, data) {
143
- const plugin = this.plugins.get(type);
144
- if (!plugin) {
145
- console.warn(`[galaxydraw] No plugin registered for card type "${type}"`);
146
- return null;
147
- }
148
- const full = {
149
- x: data.x ?? 0,
150
- y: data.y ?? 0,
151
- width: data.width ?? this.opts.defaultWidth,
152
- height: data.height ?? this.opts.defaultHeight,
153
- collapsed: data.collapsed ?? false,
154
- meta: data.meta ?? {},
155
- ...data
156
- };
157
- const el = plugin.render(full);
158
- el.classList.add("gd-card");
159
- el.dataset.cardId = full.id;
160
- el.dataset.cardType = type;
161
- el.style.left = `${full.x}px`;
162
- el.style.top = `${full.y}px`;
163
- el.style.width = `${full.width}px`;
164
- if (!full.collapsed) {
165
- el.style.height = `${full.height}px`;
166
- }
167
- this.canvas.appendChild(el);
168
- this.cards.set(full.id, el);
169
- this.bringToFront(el);
170
- this.setupDrag(el);
171
- this.setupResize(el, type);
172
- this.bus.emit("card:create", { id: full.id, x: full.x, y: full.y });
173
- return el;
174
- }
175
- remove(id) {
176
- const el = this.cards.get(id);
177
- if (!el) {
178
- this.deferred.delete(id);
179
- return;
180
- }
181
- const type = el.dataset.cardType;
182
- if (type) {
183
- this.plugins.get(type)?.onDestroy?.(el);
184
- }
185
- el.remove();
186
- this.cards.delete(id);
187
- this.selected.delete(id);
188
- this.bus.emit("card:remove", { id });
189
- }
190
- defer(type, data) {
191
- this.deferred.set(data.id, { ...data, plugin: type });
192
- }
193
- materializeInRect(worldRect) {
194
- let count = 0;
195
- const toRemove = [];
196
- for (const [id, entry] of this.deferred) {
197
- const { x, y, width, height, plugin } = entry;
198
- const w = width || this.opts.defaultWidth;
199
- const h = height || this.opts.defaultHeight;
200
- if (x + w > worldRect.left && x < worldRect.right && y + h > worldRect.top && y < worldRect.bottom) {
201
- if (plugin) {
202
- this.create(plugin, entry);
203
- }
204
- toRemove.push(id);
205
- count++;
206
- }
207
- }
208
- for (const id of toRemove) {
209
- this.deferred.delete(id);
210
- }
211
- return count;
212
- }
213
- clear() {
214
- for (const [id, el] of this.cards) {
215
- const type = el.dataset.cardType;
216
- if (type)
217
- this.plugins.get(type)?.onDestroy?.(el);
218
- el.remove();
219
- }
220
- this.cards.clear();
221
- this.deferred.clear();
222
- this.selected.clear();
223
- }
224
- bringToFront(el) {
225
- this.topZ++;
226
- el.style.zIndex = String(this.topZ);
227
- }
228
- select(id, multi = false) {
229
- if (!multi) {
230
- this.deselectAll();
231
- }
232
- this.selected.add(id);
233
- this.cards.get(id)?.classList.add("gd-card--selected");
234
- this.bus.emit("card:select", { ids: [...this.selected] });
235
- }
236
- deselect(id) {
237
- this.selected.delete(id);
238
- this.cards.get(id)?.classList.remove("gd-card--selected");
239
- this.bus.emit("card:deselect", { ids: [id] });
240
- }
241
- deselectAll() {
242
- for (const id of this.selected) {
243
- this.cards.get(id)?.classList.remove("gd-card--selected");
244
- }
245
- const prev = [...this.selected];
246
- this.selected.clear();
247
- if (prev.length > 0) {
248
- this.bus.emit("card:deselect", { ids: prev });
249
- }
250
- }
251
- toggleCollapse(id) {
252
- const el = this.cards.get(id);
253
- if (!el)
254
- return;
255
- const collapsed = el.classList.toggle("gd-card--collapsed");
256
- this.bus.emit("card:collapse", { id, collapsed });
257
- }
258
- setupDrag(card) {
259
- const header = card.querySelector(".gd-card-header");
260
- const handle = header || card;
261
- let dragging = false;
262
- let startWorldX = 0, startWorldY = 0;
263
- let cardStartX = 0, cardStartY = 0;
264
- handle.addEventListener("mousedown", (e) => {
265
- if (e.button !== 0)
266
- return;
267
- if (header && e.target !== header && !header.contains(e.target))
268
- return;
269
- e.preventDefault();
270
- dragging = true;
271
- this.bringToFront(card);
272
- const world = this.state.screenToWorld(e.clientX, e.clientY);
273
- cardStartX = parseFloat(card.style.left) || 0;
274
- cardStartY = parseFloat(card.style.top) || 0;
275
- startWorldX = world.x;
276
- startWorldY = world.y;
277
- card.classList.add("gd-card--dragging");
278
- const onMove = (ev) => {
279
- if (!dragging)
280
- return;
281
- const curr = this.state.screenToWorld(ev.clientX, ev.clientY);
282
- let newX = cardStartX + (curr.x - startWorldX);
283
- let newY = cardStartY + (curr.y - startWorldY);
284
- if (this.opts.gridSize > 0 && ev.shiftKey) {
285
- newX = Math.round(newX / this.opts.gridSize) * this.opts.gridSize;
286
- newY = Math.round(newY / this.opts.gridSize) * this.opts.gridSize;
287
- }
288
- card.style.left = `${newX}px`;
289
- card.style.top = `${newY}px`;
290
- };
291
- const onUp = () => {
292
- dragging = false;
293
- card.classList.remove("gd-card--dragging");
294
- window.removeEventListener("mousemove", onMove);
295
- window.removeEventListener("mouseup", onUp);
296
- const x = parseFloat(card.style.left) || 0;
297
- const y = parseFloat(card.style.top) || 0;
298
- this.bus.emit("card:move", { id: card.dataset.cardId, x, y });
299
- };
300
- window.addEventListener("mousemove", onMove);
301
- window.addEventListener("mouseup", onUp);
302
- });
303
- }
304
- setupResize(card, type) {
305
- const handle = document.createElement("div");
306
- handle.className = "gd-resize-handle";
307
- card.appendChild(handle);
308
- let resizing = false;
309
- let startW = 0, startH = 0, startX = 0, startY = 0;
310
- handle.addEventListener("mousedown", (e) => {
311
- e.preventDefault();
312
- e.stopPropagation();
313
- resizing = true;
314
- startW = card.offsetWidth;
315
- startH = card.offsetHeight;
316
- startX = e.clientX;
317
- startY = e.clientY;
318
- card.classList.add("gd-card--resizing");
319
- const onMove = (ev) => {
320
- if (!resizing)
321
- return;
322
- const dw = (ev.clientX - startX) / this.state.zoom;
323
- const dh = (ev.clientY - startY) / this.state.zoom;
324
- const w = Math.max(this.opts.minWidth, startW + dw);
325
- const h = Math.max(this.opts.minHeight, startH + dh);
326
- card.style.width = `${w}px`;
327
- card.style.height = `${h}px`;
328
- this.plugins.get(type)?.onResize?.(card, w, h);
329
- };
330
- const onUp = () => {
331
- resizing = false;
332
- card.classList.remove("gd-card--resizing");
333
- window.removeEventListener("mousemove", onMove);
334
- window.removeEventListener("mouseup", onUp);
335
- this.bus.emit("card:resize", {
336
- id: card.dataset.cardId,
337
- width: card.offsetWidth,
338
- height: card.offsetHeight
339
- });
340
- };
341
- window.addEventListener("mousemove", onMove);
342
- window.addEventListener("mouseup", onUp);
343
- });
344
- }
345
- consumesWheel(target) {
346
- const card = target.closest(".gd-card") || target.closest("[data-card-type]");
347
- if (!card)
348
- return false;
349
- const type = card.dataset.cardType;
350
- if (!type)
351
- return false;
352
- return this.plugins.get(type)?.consumesWheel?.(target) ?? false;
353
- }
354
- consumesMouse(target) {
355
- const card = target.closest(".gd-card") || target.closest("[data-card-type]");
356
- if (!card)
357
- return false;
358
- const type = card.dataset.cardType;
359
- if (!type)
360
- return false;
361
- return this.plugins.get(type)?.consumesMouse?.(target) ?? false;
362
- }
363
- }
364
-
365
- // src/core/viewport.ts
366
- class ViewportCuller {
367
- state;
368
- cards;
369
- bus;
370
- rafPending = false;
371
- enabled = true;
372
- margin = 500;
373
- constructor(state, cards, bus) {
374
- this.state = state;
375
- this.cards = cards;
376
- this.bus = bus;
377
- }
378
- setEnabled(enabled) {
379
- this.enabled = enabled;
380
- }
381
- schedule() {
382
- if (this.rafPending || !this.enabled)
383
- return;
384
- this.rafPending = true;
385
- requestAnimationFrame(() => {
386
- this.rafPending = false;
387
- this.perform();
388
- });
389
- }
390
- perform() {
391
- const result = { shown: 0, culled: 0, materialized: 0, total: 0 };
392
- if (!this.enabled)
393
- return result;
394
- const worldRect = this.state.getVisibleWorldRect(this.margin);
395
- if (!worldRect)
396
- return result;
397
- for (const [id, card] of this.cards.cards) {
398
- const visible = this.isCardInRect(card, worldRect);
399
- const wasCulled = card.dataset.culled === "true";
400
- if (visible && wasCulled) {
401
- card.style.contentVisibility = "";
402
- card.style.visibility = "";
403
- card.dataset.culled = "false";
404
- result.shown++;
405
- } else if (!visible && !wasCulled) {
406
- card.style.contentVisibility = "hidden";
407
- card.style.visibility = "hidden";
408
- card.dataset.culled = "true";
409
- result.culled++;
410
- } else if (visible) {
411
- result.shown++;
412
- } else {
413
- result.culled++;
414
- }
415
- }
416
- if (this.cards.deferred.size > 0) {
417
- result.materialized = this.cards.materializeInRect(worldRect);
418
- }
419
- result.total = this.cards.cards.size + this.cards.deferred.size;
420
- if (result.materialized > 0) {
421
- this.bus.emit("viewport:cull", result);
422
- }
423
- return result;
424
- }
425
- uncullAll() {
426
- for (const [, card] of this.cards.cards) {
427
- card.style.contentVisibility = "";
428
- card.style.visibility = "";
429
- card.dataset.culled = "false";
430
- }
431
- }
432
- isCardInRect(card, rect) {
433
- const x = parseFloat(card.style.left) || 0;
434
- const y = parseFloat(card.style.top) || 0;
435
- const w = card.offsetWidth || 400;
436
- const h = card.offsetHeight || 300;
437
- return x + w > rect.left && x < rect.right && y + h > rect.top && y < rect.bottom;
438
- }
439
- }
440
-
441
- // src/core/events.ts
442
- class EventBus {
443
- handlers = new Map;
444
- on(event, handler) {
445
- if (!this.handlers.has(event)) {
446
- this.handlers.set(event, new Set);
447
- }
448
- this.handlers.get(event).add(handler);
449
- return () => {
450
- this.handlers.get(event)?.delete(handler);
451
- };
452
- }
453
- once(event, handler) {
454
- const wrapper = (data) => {
455
- unsub();
456
- handler(data);
457
- };
458
- const unsub = this.on(event, wrapper);
459
- return unsub;
460
- }
461
- emit(event, data) {
462
- const handlers = this.handlers.get(event);
463
- if (!handlers)
464
- return;
465
- for (const handler of handlers) {
466
- try {
467
- handler(data);
468
- } catch (err) {
469
- console.error(`[galaxydraw] Event handler error for "${event}":`, err);
470
- }
471
- }
472
- }
473
- off(event, handler) {
474
- if (handler) {
475
- this.handlers.get(event)?.delete(handler);
476
- } else {
477
- this.handlers.delete(event);
478
- }
479
- }
480
- clear() {
481
- this.handlers.clear();
482
- }
483
- }
484
-
485
- // src/core/engine.ts
486
- class GalaxyDraw {
487
- state;
488
- cards;
489
- culler;
490
- bus;
491
- mode;
492
- viewport;
493
- canvas;
494
- spaceHeld = false;
495
- isDragging = false;
496
- dragStartX = 0;
497
- dragStartY = 0;
498
- cleanupFns = [];
499
- touchStartX = 0;
500
- touchStartY = 0;
501
- lastPinchDist = 0;
502
- constructor(container, options) {
503
- this.mode = options?.mode ?? "simple";
504
- this.bus = new EventBus;
505
- this.viewport = document.createElement("div");
506
- this.viewport.className = `gd-viewport ${options?.className ?? ""}`.trim();
507
- this.viewport.style.cssText = "position:relative;width:100%;height:100%;overflow:hidden;";
508
- this.canvas = document.createElement("div");
509
- this.canvas.className = "gd-canvas";
510
- this.canvas.style.cssText = "position:absolute;top:0;left:0;transform-origin:0 0;will-change:transform;";
511
- this.viewport.appendChild(this.canvas);
512
- container.appendChild(this.viewport);
513
- this.state = new CanvasState;
514
- this.state.bind(this.viewport, this.canvas);
515
- this.cards = new CardManager(this.state, this.bus, this.canvas, options?.cards);
516
- this.culler = new ViewportCuller(this.state, this.cards, this.bus);
517
- if (options?.cullMargin)
518
- this.culler.margin = options.cullMargin;
519
- this.setupWheel();
520
- this.setupMouse();
521
- this.setupTouch();
522
- this.setupKeyboard();
523
- const unsub = this.state.subscribe(() => this.culler.schedule());
524
- this.cleanupFns.push(unsub);
525
- }
526
- setMode(mode) {
527
- this.mode = mode;
528
- this.bus.emit("mode:change", { mode });
529
- }
530
- getMode() {
531
- return this.mode;
532
- }
533
- registerPlugin(plugin) {
534
- this.cards.registerPlugin(plugin);
535
- }
536
- fitAll(padding = 60) {
537
- this.culler.uncullAll();
538
- if (this.cards.cards.size === 0)
539
- return;
540
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
541
- for (const [, card] of this.cards.cards) {
542
- const x = parseFloat(card.style.left) || 0;
543
- const y = parseFloat(card.style.top) || 0;
544
- const w = card.offsetWidth || 400;
545
- const h = card.offsetHeight || 300;
546
- minX = Math.min(minX, x);
547
- minY = Math.min(minY, y);
548
- maxX = Math.max(maxX, x + w);
549
- maxY = Math.max(maxY, y + h);
550
- }
551
- this.state.fitRect(minX, minY, maxX, maxY, padding);
552
- }
553
- getViewport() {
554
- return this.viewport;
555
- }
556
- getCanvas() {
557
- return this.canvas;
558
- }
559
- destroy() {
560
- this.cleanupFns.forEach((fn) => fn());
561
- this.cleanupFns = [];
562
- this.cards.clear();
563
- this.bus.clear();
564
- this.viewport.remove();
565
- }
566
- setupWheel() {
567
- this.viewport.addEventListener("wheel", (e) => {
568
- const target = e.target;
569
- if (this.cards.consumesWheel(target))
570
- return;
571
- const cardEl = target.closest(".gd-card") || target.closest("[data-card-type]");
572
- if (cardEl) {
573
- const scrollBody = target.closest(".gd-card-body") || target.closest(".wm-container-body");
574
- if (scrollBody && scrollBody.scrollHeight > scrollBody.clientHeight) {
575
- const atTop = scrollBody.scrollTop <= 0 && e.deltaY < 0;
576
- const atBottom = scrollBody.scrollTop + scrollBody.clientHeight >= scrollBody.scrollHeight - 1 && e.deltaY > 0;
577
- if (!atTop && !atBottom)
578
- return;
579
- }
580
- }
581
- e.preventDefault();
582
- const factor = e.deltaY < 0 ? 1.08 : 1 / 1.08;
583
- this.state.zoomToward(e.clientX, e.clientY, factor);
584
- }, { passive: false });
585
- }
586
- setupMouse() {
587
- this.viewport.addEventListener("mousedown", (e) => {
588
- const target = e.target;
589
- if (this.cards.consumesMouse(target))
590
- return;
591
- if (target.closest(".gd-card-header") || target.closest(".wm-container-header") || target.closest(".gd-resize-handle"))
592
- return;
593
- const card = target.closest(".gd-card") || target.closest("[data-card-type]");
594
- if (card && e.button === 0) {
595
- const id = card.dataset.cardId;
596
- if (id) {
597
- this.cards.bringToFront(card);
598
- this.cards.select(id, e.shiftKey);
599
- }
600
- if (this.mode === "advanced")
601
- return;
602
- }
603
- const shouldPan = e.button === 1 || this.mode === "simple" && e.button === 0 && !card || this.mode === "advanced" && this.spaceHeld;
604
- if (shouldPan) {
605
- this.isDragging = true;
606
- this.dragStartX = e.clientX - this.state.offsetX;
607
- this.dragStartY = e.clientY - this.state.offsetY;
608
- this.viewport.style.cursor = "grabbing";
609
- e.preventDefault();
610
- }
611
- });
612
- window.addEventListener("mousemove", (e) => {
613
- if (this.isDragging) {
614
- this.state.set(this.state.zoom, e.clientX - this.dragStartX, e.clientY - this.dragStartY);
615
- }
616
- });
617
- window.addEventListener("mouseup", () => {
618
- if (this.isDragging) {
619
- this.isDragging = false;
620
- this.viewport.style.cursor = "";
621
- }
622
- });
623
- }
624
- setupTouch() {
625
- const onTouchStart = (e) => {
626
- const target = e.touches[0]?.target;
627
- if (!target)
628
- return;
629
- if (this.cards.consumesMouse(target))
630
- return;
631
- if (e.touches.length === 1) {
632
- const touch = e.touches[0];
633
- const card = target.closest(".gd-card") || target.closest("[data-card-type]");
634
- const shouldPan = this.mode === "simple" && !card || this.mode === "advanced" && this.spaceHeld;
635
- if (shouldPan) {
636
- this.isDragging = true;
637
- this.touchStartX = touch.clientX - this.state.offsetX;
638
- this.touchStartY = touch.clientY - this.state.offsetY;
639
- e.preventDefault();
640
- }
641
- } else if (e.touches.length === 2) {
642
- this.isDragging = false;
643
- const dx = e.touches[0].clientX - e.touches[1].clientX;
644
- const dy = e.touches[0].clientY - e.touches[1].clientY;
645
- this.lastPinchDist = Math.sqrt(dx * dx + dy * dy);
646
- e.preventDefault();
647
- }
648
- };
649
- const onTouchMove = (e) => {
650
- if (this.isDragging && e.touches.length === 1) {
651
- const touch = e.touches[0];
652
- this.state.set(this.state.zoom, touch.clientX - this.touchStartX, touch.clientY - this.touchStartY);
653
- e.preventDefault();
654
- }
655
- if (e.touches.length === 2) {
656
- const dx = e.touches[0].clientX - e.touches[1].clientX;
657
- const dy = e.touches[0].clientY - e.touches[1].clientY;
658
- const dist = Math.sqrt(dx * dx + dy * dy);
659
- if (this.lastPinchDist > 0) {
660
- const midX = (e.touches[0].clientX + e.touches[1].clientX) / 2;
661
- const midY = (e.touches[0].clientY + e.touches[1].clientY) / 2;
662
- const factor = dist / this.lastPinchDist;
663
- this.state.zoomToward(midX, midY, factor);
664
- }
665
- this.lastPinchDist = dist;
666
- e.preventDefault();
667
- }
668
- };
669
- const onTouchEnd = () => {
670
- this.isDragging = false;
671
- this.lastPinchDist = 0;
672
- };
673
- this.viewport.addEventListener("touchstart", onTouchStart, { passive: false });
674
- this.viewport.addEventListener("touchmove", onTouchMove, { passive: false });
675
- this.viewport.addEventListener("touchend", onTouchEnd);
676
- this.cleanupFns.push(() => {
677
- this.viewport.removeEventListener("touchstart", onTouchStart);
678
- this.viewport.removeEventListener("touchmove", onTouchMove);
679
- this.viewport.removeEventListener("touchend", onTouchEnd);
680
- });
681
- }
682
- setupKeyboard() {
683
- const onKeyDown = (e) => {
684
- if (e.code === "Space" && !e.repeat) {
685
- const tag = e.target.tagName;
686
- if (tag === "INPUT" || tag === "TEXTAREA")
687
- return;
688
- e.preventDefault();
689
- this.spaceHeld = true;
690
- this.viewport.classList.add("gd-space-pan");
691
- }
692
- };
693
- const onKeyUp = (e) => {
694
- if (e.code === "Space") {
695
- this.spaceHeld = false;
696
- this.viewport.classList.remove("gd-space-pan");
697
- if (this.isDragging) {
698
- this.isDragging = false;
699
- this.viewport.style.cursor = "";
700
- }
701
- }
702
- };
703
- window.addEventListener("keydown", onKeyDown);
704
- window.addEventListener("keyup", onKeyUp);
705
- this.cleanupFns.push(() => {
706
- window.removeEventListener("keydown", onKeyDown);
707
- window.removeEventListener("keyup", onKeyUp);
708
- });
709
- }
710
- }
711
- // src/core/layout.ts
712
- class LayoutManager {
713
- cards;
714
- bus;
715
- storagePrefix;
716
- saveTimer = null;
717
- debounceMs = 300;
718
- provider = null;
719
- constructor(cards, bus, storagePrefix = "galaxydraw") {
720
- this.cards = cards;
721
- this.bus = bus;
722
- this.storagePrefix = storagePrefix;
723
- this.bus.on("card:move", () => this.debounceSave());
724
- this.bus.on("card:resize", () => this.debounceSave());
725
- }
726
- setProvider(provider) {
727
- this.provider = provider;
728
- }
729
- async save(key) {
730
- const layouts = [];
731
- for (const [id, el] of this.cards.cards) {
732
- layouts.push({
733
- id,
734
- x: parseFloat(el.style.left) || 0,
735
- y: parseFloat(el.style.top) || 0,
736
- width: el.offsetWidth,
737
- height: el.offsetHeight,
738
- collapsed: el.classList.contains("gd-card--collapsed")
739
- });
740
- }
741
- const lsKey = `${this.storagePrefix}:layout:${key}`;
742
- try {
743
- localStorage.setItem(lsKey, JSON.stringify(layouts));
744
- } catch {}
745
- if (this.provider) {
746
- try {
747
- await this.provider.save(key, layouts);
748
- } catch (err) {
749
- console.warn("[galaxydraw] Layout save to provider failed:", err);
750
- }
751
- }
752
- this.bus.emit("layout:save", { layouts });
753
- }
754
- async load(key) {
755
- if (this.provider) {
756
- try {
757
- const remote = await this.provider.load(key);
758
- if (remote.length > 0)
759
- return remote;
760
- } catch {}
761
- }
762
- const lsKey = `${this.storagePrefix}:layout:${key}`;
763
- try {
764
- const raw = localStorage.getItem(lsKey);
765
- if (raw)
766
- return JSON.parse(raw);
767
- } catch {}
768
- return [];
769
- }
770
- apply(layouts) {
771
- const layoutMap = new Map(layouts.map((l) => [l.id, l]));
772
- for (const [id, el] of this.cards.cards) {
773
- const layout = layoutMap.get(id);
774
- if (!layout)
775
- continue;
776
- el.style.left = `${layout.x}px`;
777
- el.style.top = `${layout.y}px`;
778
- el.style.width = `${layout.width}px`;
779
- el.style.height = `${layout.height}px`;
780
- if (layout.collapsed) {
781
- el.classList.add("gd-card--collapsed");
782
- }
783
- }
784
- this.bus.emit("layout:restore", { layouts });
785
- }
786
- reset(key) {
787
- const lsKey = `${this.storagePrefix}:layout:${key}`;
788
- try {
789
- localStorage.removeItem(lsKey);
790
- } catch {}
791
- this.bus.emit("layout:reset", {});
792
- }
793
- _currentKey = "";
794
- setCurrentKey(key) {
795
- this._currentKey = key;
796
- }
797
- debounceSave() {
798
- if (!this._currentKey)
799
- return;
800
- if (this.saveTimer)
801
- clearTimeout(this.saveTimer);
802
- this.saveTimer = setTimeout(() => {
803
- this.save(this._currentKey);
804
- }, this.debounceMs);
805
- }
806
- }
807
- // src/core/minimap.ts
808
- class Minimap {
809
- state;
810
- cards;
811
- el;
812
- mapCanvas;
813
- ctx2d;
814
- rafPending = false;
815
- width = 180;
816
- height = 120;
817
- constructor(state, cards, container) {
818
- this.state = state;
819
- this.cards = cards;
820
- this.el = document.createElement("div");
821
- this.el.className = "gd-minimap";
822
- this.el.style.cssText = `
823
- position: absolute;
824
- bottom: 12px;
825
- right: 12px;
826
- width: ${this.width}px;
827
- height: ${this.height}px;
828
- border-radius: 8px;
829
- overflow: hidden;
830
- backdrop-filter: blur(12px);
831
- background: rgba(0, 0, 0, 0.5);
832
- border: 1px solid rgba(255, 255, 255, 0.1);
833
- cursor: pointer;
834
- z-index: 999;
835
- `;
836
- this.mapCanvas = document.createElement("canvas");
837
- this.mapCanvas.width = this.width;
838
- this.mapCanvas.height = this.height;
839
- this.el.appendChild(this.mapCanvas);
840
- container.appendChild(this.el);
841
- this.ctx2d = this.mapCanvas.getContext("2d");
842
- this.el.addEventListener("mousedown", (e) => this.handleClick(e));
843
- this.state.subscribe(() => this.scheduleRebuild());
844
- }
845
- scheduleRebuild() {
846
- if (this.rafPending)
847
- return;
848
- this.rafPending = true;
849
- requestAnimationFrame(() => {
850
- this.rafPending = false;
851
- this.rebuild();
852
- });
853
- }
854
- rebuild() {
855
- const ctx = this.ctx2d;
856
- if (!ctx)
857
- return;
858
- ctx.clearRect(0, 0, this.width, this.height);
859
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
860
- for (const [, card] of this.cards.cards) {
861
- const x = parseFloat(card.style.left) || 0;
862
- const y = parseFloat(card.style.top) || 0;
863
- const w = card.offsetWidth || 400;
864
- const h = card.offsetHeight || 300;
865
- minX = Math.min(minX, x);
866
- minY = Math.min(minY, y);
867
- maxX = Math.max(maxX, x + w);
868
- maxY = Math.max(maxY, y + h);
869
- }
870
- for (const [, data] of this.cards.deferred) {
871
- minX = Math.min(minX, data.x);
872
- minY = Math.min(minY, data.y);
873
- maxX = Math.max(maxX, data.x + data.width);
874
- maxY = Math.max(maxY, data.y + data.height);
875
- }
876
- if (minX === Infinity)
877
- return;
878
- const pad = 50;
879
- const worldW = maxX - minX + pad * 2;
880
- const worldH = maxY - minY + pad * 2;
881
- const scale = Math.min(this.width / worldW, this.height / worldH);
882
- const ox = (this.width - worldW * scale) / 2;
883
- const oy = (this.height - worldH * scale) / 2;
884
- ctx.fillStyle = "rgba(147, 130, 255, 0.6)";
885
- for (const [, card] of this.cards.cards) {
886
- const x = (parseFloat(card.style.left) || 0) - minX + pad;
887
- const y = (parseFloat(card.style.top) || 0) - minY + pad;
888
- const w = card.offsetWidth || 400;
889
- const h = card.offsetHeight || 300;
890
- ctx.fillRect(ox + x * scale, oy + y * scale, Math.max(2, w * scale), Math.max(2, h * scale));
891
- }
892
- ctx.fillStyle = "rgba(147, 130, 255, 0.2)";
893
- for (const [, data] of this.cards.deferred) {
894
- const x = data.x - minX + pad;
895
- const y = data.y - minY + pad;
896
- ctx.fillRect(ox + x * scale, oy + y * scale, Math.max(2, data.width * scale), Math.max(2, data.height * scale));
897
- }
898
- const vp = this.state.getVisibleWorldRect();
899
- if (vp) {
900
- ctx.strokeStyle = "rgba(255, 255, 255, 0.7)";
901
- ctx.lineWidth = 1.5;
902
- const rx = ox + (vp.left - minX + pad) * scale;
903
- const ry = oy + (vp.top - minY + pad) * scale;
904
- const rw = vp.width * scale;
905
- const rh = vp.height * scale;
906
- ctx.strokeRect(rx, ry, rw, rh);
907
- }
908
- }
909
- handleClick(e) {
910
- e.stopPropagation();
911
- e.preventDefault();
912
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
913
- for (const [, card] of this.cards.cards) {
914
- const x = parseFloat(card.style.left) || 0;
915
- const y = parseFloat(card.style.top) || 0;
916
- const w = card.offsetWidth || 400;
917
- const h = card.offsetHeight || 300;
918
- minX = Math.min(minX, x);
919
- minY = Math.min(minY, y);
920
- maxX = Math.max(maxX, x + w);
921
- maxY = Math.max(maxY, y + h);
922
- }
923
- for (const [, data] of this.cards.deferred) {
924
- minX = Math.min(minX, data.x);
925
- minY = Math.min(minY, data.y);
926
- maxX = Math.max(maxX, data.x + data.width);
927
- maxY = Math.max(maxY, data.y + data.height);
928
- }
929
- if (minX === Infinity)
930
- return;
931
- const pad = 50;
932
- const worldW = maxX - minX + pad * 2;
933
- const worldH = maxY - minY + pad * 2;
934
- const scale = Math.min(this.width / worldW, this.height / worldH);
935
- const ox = (this.width - worldW * scale) / 2;
936
- const oy = (this.height - worldH * scale) / 2;
937
- const rect = this.el.getBoundingClientRect();
938
- const clickX = e.clientX - rect.left;
939
- const clickY = e.clientY - rect.top;
940
- const worldX = (clickX - ox) / scale + minX - pad;
941
- const worldY = (clickY - oy) / scale + minY - pad;
942
- const vp = this.state.getVisibleWorldRect();
943
- if (vp) {
944
- const vpWorldW = vp.width;
945
- const vpWorldH = vp.height;
946
- const newOffsetX = -(worldX - vpWorldW / 2) * this.state.zoom;
947
- const newOffsetY = -(worldY - vpWorldH / 2) * this.state.zoom;
948
- this.state.set(this.state.zoom, newOffsetX, newOffsetY);
949
- }
950
- const onMove = (ev) => {
951
- const mx = ev.clientX - rect.left;
952
- const my = ev.clientY - rect.top;
953
- const wx = (mx - ox) / scale + minX - pad;
954
- const wy = (my - oy) / scale + minY - pad;
955
- const v = this.state.getVisibleWorldRect();
956
- if (v) {
957
- this.state.set(this.state.zoom, -(wx - v.width / 2) * this.state.zoom, -(wy - v.height / 2) * this.state.zoom);
958
- }
959
- };
960
- const onUp = () => {
961
- window.removeEventListener("mousemove", onMove);
962
- window.removeEventListener("mouseup", onUp);
963
- };
964
- window.addEventListener("mousemove", onMove);
965
- window.addEventListener("mouseup", onUp);
966
- }
967
- setVisible(visible) {
968
- this.el.style.display = visible ? "" : "none";
969
- }
970
- destroy() {
971
- this.el.remove();
972
- }
973
- }
974
- export {
975
- ViewportCuller,
976
- Minimap,
977
- LayoutManager,
978
- GalaxyDraw,
979
- EventBus,
980
- CardManager,
981
- CanvasState
982
- };
983
-
984
- //# debugId=A4ED09BD658332D164756E2164756E21