gitmaps 1.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.
Files changed (121) hide show
  1. package/README.md +167 -0
  2. package/app/api/auth/favorites/route.ts +56 -0
  3. package/app/api/auth/github/callback/route.ts +103 -0
  4. package/app/api/auth/github/route.ts +32 -0
  5. package/app/api/auth/me/route.ts +52 -0
  6. package/app/api/auth/positions/route.ts +50 -0
  7. package/app/api/chat/route.ts +101 -0
  8. package/app/api/connections/route.ts +72 -0
  9. package/app/api/github/repos/route.ts +111 -0
  10. package/app/api/positions/route.ts +80 -0
  11. package/app/api/repo/branch-diff/route.ts +201 -0
  12. package/app/api/repo/branches/route.ts +53 -0
  13. package/app/api/repo/browse/route.ts +55 -0
  14. package/app/api/repo/clone/route.ts +78 -0
  15. package/app/api/repo/clone-stream/route.ts +131 -0
  16. package/app/api/repo/file-content/route.ts +28 -0
  17. package/app/api/repo/file-delete/route.ts +62 -0
  18. package/app/api/repo/file-history/route.ts +45 -0
  19. package/app/api/repo/file-rename/route.ts +83 -0
  20. package/app/api/repo/file-save/route.ts +45 -0
  21. package/app/api/repo/files/route.ts +169 -0
  22. package/app/api/repo/git-blame/route.ts +86 -0
  23. package/app/api/repo/git-commit/route.ts +40 -0
  24. package/app/api/repo/git-heatmap/route.ts +55 -0
  25. package/app/api/repo/imports/route.ts +154 -0
  26. package/app/api/repo/load/route.ts +56 -0
  27. package/app/api/repo/mode/route.ts +14 -0
  28. package/app/api/repo/search/route.ts +127 -0
  29. package/app/api/repo/tree/route.ts +104 -0
  30. package/app/api/repo/upload/route.ts +53 -0
  31. package/app/api/repo/validate-path.ts +53 -0
  32. package/app/canvas_users.db +0 -0
  33. package/app/canvas_users.db-shm +0 -0
  34. package/app/canvas_users.db-wal +0 -0
  35. package/app/globals.css +7899 -0
  36. package/app/layout.tsx +493 -0
  37. package/app/lib/auth.ts +193 -0
  38. package/app/lib/auto-save.ts +137 -0
  39. package/app/lib/branch-compare.ts +443 -0
  40. package/app/lib/breadcrumbs.ts +170 -0
  41. package/app/lib/canvas-export.ts +358 -0
  42. package/app/lib/canvas-text.ts +912 -0
  43. package/app/lib/canvas.ts +564 -0
  44. package/app/lib/card-arrangement.ts +188 -0
  45. package/app/lib/card-context-menu.tsx +453 -0
  46. package/app/lib/card-diff-markers.ts +270 -0
  47. package/app/lib/card-expand.ts +189 -0
  48. package/app/lib/card-groups.ts +246 -0
  49. package/app/lib/cards.tsx +914 -0
  50. package/app/lib/chat.tsx +308 -0
  51. package/app/lib/code-editor.ts +508 -0
  52. package/app/lib/command-palette.ts +262 -0
  53. package/app/lib/connections.tsx +1037 -0
  54. package/app/lib/context.ts +94 -0
  55. package/app/lib/cursor-sharing.ts +281 -0
  56. package/app/lib/dependency-graph.ts +438 -0
  57. package/app/lib/events.tsx +1747 -0
  58. package/app/lib/file-card-plugin.ts +134 -0
  59. package/app/lib/file-modal.tsx +849 -0
  60. package/app/lib/file-preview.ts +400 -0
  61. package/app/lib/file-tabs.ts +318 -0
  62. package/app/lib/galaxydraw-bridge.ts +477 -0
  63. package/app/lib/galaxydraw.test.ts +229 -0
  64. package/app/lib/global-search.ts +264 -0
  65. package/app/lib/goto-definition.ts +224 -0
  66. package/app/lib/heatmap.ts +178 -0
  67. package/app/lib/hidden-files.tsx +222 -0
  68. package/app/lib/layers.ts +0 -0
  69. package/app/lib/layers.tsx +365 -0
  70. package/app/lib/loading.tsx +45 -0
  71. package/app/lib/multi-repo.ts +286 -0
  72. package/app/lib/new-file-dialog.tsx +230 -0
  73. package/app/lib/onboarding.tsx +213 -0
  74. package/app/lib/perf-overlay.ts +360 -0
  75. package/app/lib/positions.ts +176 -0
  76. package/app/lib/pr-review.ts +374 -0
  77. package/app/lib/production-mode.ts +47 -0
  78. package/app/lib/repo.tsx +977 -0
  79. package/app/lib/settings-modal.tsx +374 -0
  80. package/app/lib/settings.ts +97 -0
  81. package/app/lib/shortcuts-panel.ts +141 -0
  82. package/app/lib/status-bar.ts +128 -0
  83. package/app/lib/symbol-outline.ts +212 -0
  84. package/app/lib/syntax.ts +177 -0
  85. package/app/lib/tab-diff.ts +238 -0
  86. package/app/lib/user.tsx +133 -0
  87. package/app/lib/utils.ts +78 -0
  88. package/app/lib/viewport-culling.ts +728 -0
  89. package/app/page.client.tsx +215 -0
  90. package/app/page.tsx +291 -0
  91. package/app/state/machine.js +196 -0
  92. package/app/styles/main.css +2168 -0
  93. package/banner.png +0 -0
  94. package/cli.ts +44 -0
  95. package/package.json +75 -0
  96. package/packages/galaxydraw/README.md +296 -0
  97. package/packages/galaxydraw/banner.png +0 -0
  98. package/packages/galaxydraw/demo/build-static.ts +100 -0
  99. package/packages/galaxydraw/demo/client.ts +154 -0
  100. package/packages/galaxydraw/demo/dist/client.js +8 -0
  101. package/packages/galaxydraw/demo/index.html +256 -0
  102. package/packages/galaxydraw/demo/server.ts +96 -0
  103. package/packages/galaxydraw/dist/index.js +984 -0
  104. package/packages/galaxydraw/dist/index.js.map +16 -0
  105. package/packages/galaxydraw/node_modules/.bin/tsc.bunx +0 -0
  106. package/packages/galaxydraw/node_modules/.bin/tsc.exe +0 -0
  107. package/packages/galaxydraw/node_modules/.bin/tsserver.bunx +0 -0
  108. package/packages/galaxydraw/node_modules/.bin/tsserver.exe +0 -0
  109. package/packages/galaxydraw/package.json +49 -0
  110. package/packages/galaxydraw/perf.test.ts +284 -0
  111. package/packages/galaxydraw/src/core/cards.ts +435 -0
  112. package/packages/galaxydraw/src/core/engine.ts +339 -0
  113. package/packages/galaxydraw/src/core/events.ts +81 -0
  114. package/packages/galaxydraw/src/core/layout.ts +136 -0
  115. package/packages/galaxydraw/src/core/minimap.ts +216 -0
  116. package/packages/galaxydraw/src/core/state.ts +177 -0
  117. package/packages/galaxydraw/src/core/viewport.ts +106 -0
  118. package/packages/galaxydraw/src/galaxydraw.css +166 -0
  119. package/packages/galaxydraw/src/index.ts +40 -0
  120. package/packages/galaxydraw/tsconfig.json +30 -0
  121. package/server.ts +62 -0
@@ -0,0 +1,984 @@
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