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
package/banner.png ADDED
Binary file
package/cli.ts ADDED
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * gitmaps CLI — run GitMaps locally on any repository
4
+ * Usage: npx gitmaps [path] [--port 3335]
5
+ */
6
+
7
+ const args = process.argv.slice(2);
8
+ let repoPath = process.cwd();
9
+ let port = 3335;
10
+
11
+ for (let i = 0; i < args.length; i++) {
12
+ if (args[i] === '--port' && args[i + 1]) {
13
+ port = parseInt(args[i + 1]);
14
+ i++;
15
+ } else if (args[i] === '--help' || args[i] === '-h') {
16
+ console.log(`
17
+ 🪐 GitMaps — Spatial Code Explorer
18
+
19
+ Usage:
20
+ npx gitmaps # Open current directory
21
+ npx gitmaps /path/to/repo # Open specific repo
22
+ npx gitmaps --port 4000 # Custom port
23
+
24
+ Options:
25
+ --port <number> Port to run on (default: 3335)
26
+ --help, -h Show this help
27
+ `);
28
+ process.exit(0);
29
+ } else if (!args[i].startsWith('-')) {
30
+ repoPath = args[i];
31
+ }
32
+ }
33
+
34
+ process.env.BUN_PORT = String(port);
35
+ process.env.GITMAPS_REPO = repoPath;
36
+
37
+ console.log(`🪐 GitMaps starting...`);
38
+ console.log(` Repo: ${repoPath}`);
39
+ console.log(` Port: ${port}`);
40
+ console.log(` URL: http://localhost:${port}`);
41
+ console.log();
42
+
43
+ // Import and run the server
44
+ import('./server.ts');
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "gitmaps",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "bin": {
6
+ "gitmaps": "cli.ts"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/7flash/git-on-canvas"
11
+ },
12
+ "homepage": "https://gitmaps.xyz",
13
+ "workspaces": [
14
+ "packages/*"
15
+ ],
16
+ "scripts": {
17
+ "dev": "bun run server.ts",
18
+ "start": "bun run server.ts",
19
+ "test": "bun test app/lib/galaxydraw.test.ts packages/galaxydraw/perf.test.ts",
20
+ "prepublishOnly": "echo 'Publishing gitmaps to npm'"
21
+ },
22
+ "keywords": [
23
+ "git",
24
+ "canvas",
25
+ "visualization",
26
+ "code-review",
27
+ "spatial",
28
+ "infinite-canvas",
29
+ "diff",
30
+ "repository",
31
+ "minimap",
32
+ "codemirror"
33
+ ],
34
+ "author": "mements",
35
+ "license": "ISC",
36
+ "description": "Spatial code explorer — see every file at once on an infinite canvas with diffs, layers, and time-travel",
37
+ "files": [
38
+ "cli.ts",
39
+ "server.ts",
40
+ "app/",
41
+ "packages/galaxydraw/",
42
+ "README.md",
43
+ "banner.png"
44
+ ],
45
+ "dependencies": {
46
+ "@codemirror/autocomplete": "^6.20.1",
47
+ "@codemirror/commands": "^6.10.2",
48
+ "@codemirror/lang-css": "^6.3.1",
49
+ "@codemirror/lang-html": "^6.4.11",
50
+ "@codemirror/lang-javascript": "^6.2.5",
51
+ "@codemirror/lang-json": "^6.0.2",
52
+ "@codemirror/lang-markdown": "^6.5.0",
53
+ "@codemirror/lang-python": "^6.2.1",
54
+ "@codemirror/lang-yaml": "^6.1.2",
55
+ "@codemirror/language": "^6.12.2",
56
+ "@codemirror/lint": "^6.9.5",
57
+ "@codemirror/search": "^6.6.0",
58
+ "@codemirror/state": "^6.5.4",
59
+ "@codemirror/view": "^6.39.16",
60
+ "@lezer/highlight": "^1.2.3",
61
+ "galaxydraw": "0.2.0",
62
+ "jsx-ai": "0.1.2",
63
+ "measure-fn": "3.10.1",
64
+ "melina": "^2.5.0",
65
+ "simple-git": "^3.30.0",
66
+ "sqlite-zod-orm": "^3.26.0",
67
+ "xstate": "^5.26.0"
68
+ },
69
+ "devDependencies": {
70
+ "@types/bun": "^1.3.10"
71
+ },
72
+ "overrides": {
73
+ "measure-fn": "3.10.1"
74
+ }
75
+ }
@@ -0,0 +1,296 @@
1
+ <p align="center">
2
+ <img src="banner.png" alt="galaxydraw" width="100%" />
3
+ </p>
4
+
5
+ <p align="center">
6
+ <b>Infinite canvas framework for spatial applications. Zero dependencies, ~31KB.</b>
7
+ </p>
8
+
9
+ <p align="center">
10
+ <a href="https://7flash.github.io/git-on-canvas/"><img src="https://img.shields.io/badge/🌌_Live_Demo-galaxydraw-9382ff?style=flat-square" alt="Live Demo"></a>
11
+ <a href="https://www.npmjs.com/package/galaxydraw"><img src="https://img.shields.io/npm/v/galaxydraw.svg?style=flat-square" alt="npm version"></a>
12
+ <a href="https://www.npmjs.com/package/galaxydraw"><img src="https://img.shields.io/npm/dm/galaxydraw.svg?style=flat-square" alt="npm downloads"></a>
13
+ <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg?style=flat-square" alt="License"></a>
14
+ </p>
15
+
16
+ ---
17
+
18
+ **Before** — 760 lines of custom pan/zoom/drag/touch/minimap/resize code per project:
19
+
20
+ ```ts
21
+ let state = { zoom: 1, offsetX: 0, offsetY: 0 };
22
+ viewport.addEventListener('wheel', (e) => {
23
+ e.preventDefault();
24
+ const zoomFactor = e.deltaY < 0 ? 1.08 : 1 / 1.08;
25
+ const newZoom = Math.max(0.15, Math.min(3, state.zoom * zoomFactor));
26
+ const rect = viewport.getBoundingClientRect();
27
+ const mouseX = e.clientX - rect.left;
28
+ const mouseY = e.clientY - rect.top;
29
+ const worldX = (mouseX - state.offsetX) / state.zoom;
30
+ const worldY = (mouseY - state.offsetY) / state.zoom;
31
+ state.zoom = newZoom;
32
+ state.offsetX = mouseX - worldX * newZoom;
33
+ state.offsetY = mouseY - worldY * newZoom;
34
+ content.style.transform = `translate(${state.offsetX}px,${state.offsetY}px) scale(${state.zoom})`;
35
+ });
36
+ // + 700 more lines for mouse pan, touch, drag, resize, minimap, z-order...
37
+ ```
38
+
39
+ **After** — galaxydraw handles all of it:
40
+
41
+ ```ts
42
+ import { GalaxyDraw } from 'galaxydraw';
43
+
44
+ const gd = new GalaxyDraw(document.getElementById('app'), { mode: 'simple' });
45
+ // → Pan, zoom, touch, keyboard shortcuts — all working.
46
+ // → Cards, viewport culling, minimap — opt-in via plugins.
47
+ ```
48
+
49
+ ## Installation
50
+
51
+ ```sh
52
+ bun add galaxydraw
53
+ # or: npm install galaxydraw
54
+ ```
55
+
56
+ For local development across repos, use a `file:` dependency:
57
+
58
+ ```json
59
+ "galaxydraw": "file:../galaxy-canvas/packages/galaxydraw"
60
+ ```
61
+
62
+ ## ✨ What You Get
63
+
64
+ Every `new GalaxyDraw()` automatically:
65
+
66
+ - 🖱️ **Mouse pan/zoom** → wheel zoom toward cursor, click-drag pan
67
+ - 📱 **Touch support** → single-finger pan, pinch-to-zoom (native, no libraries)
68
+ - ⌨️ **Keyboard** → Space+drag pan (advanced mode), input element passthrough
69
+ - 🃏 **Card system** → drag, resize, z-order, selection via plugins
70
+ - 🔍 **Viewport culling** → only visible cards stay in DOM, deferred lazy-creation
71
+ - 🗺️ **Minimap** → optional overview with click-to-navigate
72
+ - 📐 **Layout persistence** → save/restore positions (localStorage or custom)
73
+ - 🎛️ **Dual control modes** → Simple (dashboard-style) or Advanced (design-tool-style)
74
+ - 🔌 **Plugin architecture** → custom card types with event passthrough
75
+
76
+ ## ⚙️ Constructor Options
77
+
78
+ ```ts
79
+ const gd = new GalaxyDraw(containerEl, {
80
+ mode: 'simple', // or 'advanced'
81
+ minimap: true, // render overview panel
82
+ cullMargin: 200, // px beyond viewport to keep cards alive
83
+ className: 'my-canvas', // custom CSS class on root
84
+ cards: {
85
+ defaultWidth: 400,
86
+ defaultHeight: 300,
87
+ minWidth: 200,
88
+ minHeight: 150,
89
+ gridSize: 20, // snap-to-grid resolution (0 = off)
90
+ cornerSize: 40, // resize handle hit area
91
+ },
92
+ });
93
+ ```
94
+
95
+ | Option | Type | Default | Description |
96
+ |--------|------|---------|-------------|
97
+ | `mode` | `'simple' \| 'advanced'` | `'simple'` | Control scheme |
98
+ | `minimap` | `boolean` | `false` | Render overview panel |
99
+ | `cullMargin` | `number` | `200` | Viewport buffer in px |
100
+ | `className` | `string` | — | Custom root CSS class |
101
+ | `cards.defaultWidth` | `number` | `400` | Initial card width |
102
+ | `cards.defaultHeight` | `number` | `300` | Initial card height |
103
+ | `cards.minWidth` | `number` | `200` | Minimum resize width |
104
+ | `cards.minHeight` | `number` | `150` | Minimum resize height |
105
+ | `cards.gridSize` | `number` | `0` | Shift+drag snap grid (0 = off) |
106
+ | `cards.cornerSize` | `number` | `40` | Resize handle hit area |
107
+
108
+ ## 🎛️ Control Modes
109
+
110
+ | Mode | Left-click on canvas | Left-click on card | Space+drag |
111
+ |------|---------------------|--------------------|------------|
112
+ | `simple` | Pan | — | Pan |
113
+ | `advanced` | — | Select | Pan |
114
+
115
+ ```ts
116
+ gd.setMode('advanced');
117
+ console.log(gd.getMode()); // → 'advanced'
118
+ ```
119
+
120
+ ## 🔌 Card Plugins
121
+
122
+ Cards are rendered by plugins. Each plugin handles one card type:
123
+
124
+ ```ts
125
+ import { GalaxyDraw } from 'galaxydraw';
126
+ import type { CardPlugin, CardData } from 'galaxydraw';
127
+
128
+ const widgetPlugin: CardPlugin = {
129
+ type: 'widget',
130
+
131
+ render(data: CardData): HTMLElement {
132
+ const el = document.createElement('div');
133
+ el.innerHTML = `
134
+ <div class="gd-card-header">${data.meta?.title || 'Widget'}</div>
135
+ <div class="gd-card-body">Content here</div>
136
+ `;
137
+ return el;
138
+ },
139
+
140
+ // Optional: claim mouse/wheel events for interactive content
141
+ consumesWheel(target) {
142
+ return !!target.closest('.maplibregl-map');
143
+ },
144
+ consumesMouse(target) {
145
+ return !!target.closest('.maplibregl-map');
146
+ },
147
+
148
+ onResize(el, w, h) { /* handle resize */ },
149
+ onDestroy(el) { /* cleanup */ },
150
+ };
151
+
152
+ const gd = new GalaxyDraw(containerEl, { mode: 'simple' });
153
+ gd.registerPlugin(widgetPlugin);
154
+ ```
155
+
156
+ ### CardPlugin Interface
157
+
158
+ | Method | Required | Description |
159
+ |--------|----------|-------------|
160
+ | `type` | Yes | Unique string identifier |
161
+ | `render(data)` | Yes | Returns the card's DOM element |
162
+ | `consumesWheel(target)` | No | Return `true` to let the card handle wheel events (e.g., maps) |
163
+ | `consumesMouse(target)` | No | Return `true` to let the card handle mouse events |
164
+ | `onResize(el, w, h)` | No | Called after card resize |
165
+ | `onDestroy(el)` | No | Cleanup callback |
166
+
167
+ ### Creating Cards
168
+
169
+ ```ts
170
+ // Immediate creation (visible cards)
171
+ const el = gd.cards.create('widget', {
172
+ id: 'w1', x: 100, y: 100,
173
+ meta: { title: 'Map' },
174
+ });
175
+ // → HTMLElement appended to canvas at (100, 100)
176
+
177
+ // Deferred creation (off-screen cards, lazy-materialized on scroll)
178
+ gd.cards.defer('widget', {
179
+ id: 'w2', x: 3000, y: 3000, width: 400, height: 300,
180
+ meta: { title: 'Far Away' },
181
+ });
182
+ // → Stored in memory, created when user scrolls near (3000, 3000)
183
+
184
+ // Remove
185
+ gd.cards.remove('w1');
186
+ // → Calls onDestroy, removes from DOM
187
+
188
+ // Clear all
189
+ gd.cards.clear();
190
+ ```
191
+
192
+ ## 📡 Event Bus
193
+
194
+ Subscribe to card and engine events:
195
+
196
+ ```ts
197
+ gd.bus.on('card:move', ({ id, x, y }) => {
198
+ console.log(`Card ${id} moved to (${x}, ${y})`);
199
+ // → "Card w1 moved to (250, 180)"
200
+ });
201
+
202
+ gd.bus.on('card:resize', ({ id, width, height }) => {
203
+ console.log(`Card ${id} resized: ${width}x${height}`);
204
+ // → "Card w1 resized: 500x400"
205
+ });
206
+
207
+ gd.bus.on('card:select', ({ ids }) => {
208
+ console.log(`Selected: ${ids.join(', ')}`);
209
+ // → "Selected: w1, w2"
210
+ });
211
+
212
+ gd.bus.on('card:collapse', ({ id, collapsed }) => {
213
+ console.log(`Card ${id} ${collapsed ? 'collapsed' : 'expanded'}`);
214
+ });
215
+
216
+ gd.bus.on('mode:change', ({ mode }) => {
217
+ console.log(`Switched to ${mode} mode`);
218
+ });
219
+ ```
220
+
221
+ | Event | Payload | When |
222
+ |-------|---------|------|
223
+ | `card:create` | `{ id, x, y }` | Card added to canvas |
224
+ | `card:move` | `{ id, x, y }` | Card drag ended |
225
+ | `card:resize` | `{ id, width, height }` | Card resize ended |
226
+ | `card:select` | `{ ids: string[] }` | Selection changed |
227
+ | `card:deselect` | `{ ids: string[] }` | Cards deselected |
228
+ | `card:collapse` | `{ id, collapsed }` | Collapse toggled |
229
+ | `card:remove` | `{ id }` | Card removed |
230
+ | `mode:change` | `{ mode }` | Control mode switched |
231
+
232
+ ## 🧭 Canvas State
233
+
234
+ Direct access to pan/zoom state:
235
+
236
+ ```ts
237
+ // Read current state
238
+ const { zoom, offsetX, offsetY } = gd.state.getSnapshot();
239
+ // → { zoom: 1.2, offsetX: -340, offsetY: -120 }
240
+
241
+ // Programmatic control
242
+ gd.state.set(1.5, -200, -100); // set zoom, offsetX, offsetY
243
+ gd.state.zoomToward(400, 300, 1.2); // zoom toward screen point
244
+ gd.state.pan(50, 0); // delta pan
245
+
246
+ // Subscribe to changes
247
+ const unsub = gd.state.subscribe(() => {
248
+ console.log('Zoom:', gd.state.zoom); // → "Zoom: 1.5"
249
+ });
250
+
251
+ // Coordinate conversion
252
+ const worldPt = gd.state.screenToWorld(e.clientX, e.clientY);
253
+ // → { x: 842, y: 316 }
254
+
255
+ // Fit all content into view
256
+ gd.fitAll(60); // 60px padding
257
+ ```
258
+
259
+ ## 🏗️ Architecture
260
+
261
+ ```
262
+ src/
263
+ ├── index.ts # Package entry — re-exports everything
264
+ └── core/
265
+ ├── engine.ts # GalaxyDraw class (337 lines)
266
+ ├── state.ts # CanvasState — zoom/offset/transform
267
+ ├── cards.ts # CardManager — create/defer/drag/resize/z-order
268
+ ├── viewport.ts # ViewportCuller — show/hide by visibility
269
+ ├── events.ts # EventBus — typed pub/sub
270
+ ├── layout.ts # LayoutManager — save/restore positions
271
+ └── minimap.ts # Minimap — overview with click navigation
272
+ ```
273
+
274
+ Total: ~1,200 lines of engine code. Zero dependencies.
275
+
276
+ ## 🚀 Used By
277
+
278
+ - **[GitMaps](https://github.com/7flash/git-on-canvas)** — Repository visualization on an infinite canvas. Uses `advanced` mode with FileCardPlugin + DiffCardPlugin. Renders 6,800+ file cards with viewport culling (~35ms).
279
+ - **[WARMAPS](https://github.com/7flash/starwar)** — Real-time geopolitical intelligence dashboard. Uses `simple` mode with WarmapsContainerPlugin for MapLibre/WebSocket feed passthrough.
280
+
281
+ ## 🧪 Testing
282
+
283
+ 24 unit tests covering the core engine:
284
+
285
+ ```sh
286
+ bun test
287
+ ```
288
+
289
+ | Suite | Tests | Coverage |
290
+ |-------|-------|----------|
291
+ | CanvasState | 14 | zoom, pan, clamp, screenToWorld, subscribe, fitAll |
292
+ | EventBus | 10 | on/off, emit, multi-listener, wildcard, once |
293
+
294
+ ## License
295
+
296
+ MIT
Binary file
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Build a standalone static index.html demo for GitHub Pages
3
+ */
4
+ const css = await Bun.file(import.meta.dir + '/../src/galaxydraw.css').text();
5
+
6
+ // Bundle the client code
7
+ const buildResult = await Bun.build({
8
+ entrypoints: [import.meta.dir + '/client.ts'],
9
+ target: 'browser',
10
+ format: 'esm',
11
+ minify: true,
12
+ });
13
+ const js = await buildResult.outputs[0].text();
14
+
15
+ const html = `<!DOCTYPE html>
16
+ <html lang="en">
17
+ <head>
18
+ <meta charset="UTF-8">
19
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
20
+ <title>galaxydraw — Interactive Demo</title>
21
+ <meta name="description" content="Interactive demo of galaxydraw, a zero-dependency infinite canvas framework for spatial applications.">
22
+ <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🌌</text></svg>">
23
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
24
+ <style>
25
+ * { margin: 0; padding: 0; box-sizing: border-box; }
26
+ html, body { width: 100%; height: 100%; background: #060812; color: #e4e4e7; font-family: 'Inter', sans-serif; overflow: hidden; }
27
+ #app { width: 100vw; height: 100vh; }
28
+
29
+ .demo-toolbar {
30
+ position: fixed;
31
+ top: 16px;
32
+ left: 50%;
33
+ transform: translateX(-50%);
34
+ z-index: 1000;
35
+ display: flex;
36
+ gap: 8px;
37
+ padding: 8px 16px;
38
+ border-radius: 12px;
39
+ background: rgba(13, 15, 28, 0.85);
40
+ backdrop-filter: blur(16px);
41
+ border: 1px solid rgba(255, 255, 255, 0.08);
42
+ }
43
+
44
+ .demo-toolbar button {
45
+ padding: 6px 14px;
46
+ border-radius: 8px;
47
+ border: 1px solid rgba(255, 255, 255, 0.08);
48
+ background: transparent;
49
+ color: rgba(255, 255, 255, 0.7);
50
+ cursor: pointer;
51
+ font-size: 12px;
52
+ font-family: 'Inter', sans-serif;
53
+ font-weight: 500;
54
+ transition: all 0.15s ease;
55
+ }
56
+
57
+ .demo-toolbar button:hover {
58
+ background: rgba(147, 130, 255, 0.15);
59
+ border-color: rgba(147, 130, 255, 0.3);
60
+ color: #fff;
61
+ }
62
+
63
+ .demo-toolbar button.active {
64
+ background: rgba(147, 130, 255, 0.2);
65
+ border-color: rgba(147, 130, 255, 0.4);
66
+ color: #fff;
67
+ }
68
+
69
+ .demo-toolbar .mode-label {
70
+ font-size: 11px;
71
+ color: rgba(255, 255, 255, 0.4);
72
+ display: flex;
73
+ align-items: center;
74
+ padding: 0 8px;
75
+ }
76
+
77
+ /* Responsive toolbar */
78
+ @media (max-width: 640px) {
79
+ .demo-toolbar {
80
+ flex-wrap: wrap;
81
+ max-width: 90vw;
82
+ justify-content: center;
83
+ }
84
+ .demo-toolbar button {
85
+ font-size: 11px;
86
+ padding: 5px 10px;
87
+ }
88
+ }
89
+
90
+ ${css}
91
+ </style>
92
+ </head>
93
+ <body>
94
+ <div id="app"></div>
95
+ <script type="module">${js}</script>
96
+ </body>
97
+ </html>`;
98
+
99
+ await Bun.write(import.meta.dir + '/index.html', html);
100
+ console.log(`✓ demo/index.html created (${(html.length / 1024).toFixed(1)}KB)`);
@@ -0,0 +1,154 @@
1
+ /**
2
+ * galaxydraw demo — client entry point
3
+ */
4
+ import { GalaxyDraw } from '../src/core/engine';
5
+ import type { CardPlugin } from '../src/core/cards';
6
+
7
+ // ─── Text card plugin ─────────────────────────────────────
8
+ const TextCardPlugin: CardPlugin = {
9
+ type: 'text',
10
+ render(data) {
11
+ const card = document.createElement('div');
12
+ const header = document.createElement('div');
13
+ header.className = 'gd-card-header';
14
+ header.innerHTML = `<span class="title">${data.meta?.title || data.id}</span>`;
15
+
16
+ const body = document.createElement('div');
17
+ body.className = 'gd-card-body';
18
+ body.style.cssText = 'padding:12px; font-size:13px; color:rgba(255,255,255,0.6); line-height:1.6;';
19
+ body.innerHTML = data.meta?.content || 'Drag by the header, resize from the corner.';
20
+
21
+ card.appendChild(header);
22
+ card.appendChild(body);
23
+ return card;
24
+ }
25
+ };
26
+
27
+ // ─── Note card plugin ─────────────────────────────────────
28
+ const NoteCardPlugin: CardPlugin = {
29
+ type: 'note',
30
+ render(data) {
31
+ const colors = ['#22c55e', '#eab308', '#ef4444', '#3b82f6', '#a855f7'];
32
+ const color = colors[Math.floor(Math.random() * colors.length)];
33
+ const card = document.createElement('div');
34
+
35
+ const header = document.createElement('div');
36
+ header.className = 'gd-card-header';
37
+ header.style.borderLeft = `3px solid ${color}`;
38
+ header.innerHTML = `
39
+ <span class="title">${data.meta?.title || 'Note'}</span>
40
+ <span style="font-size:10px; color:${color}; text-transform:uppercase; letter-spacing:0.05em;">Note</span>
41
+ `;
42
+
43
+ const body = document.createElement('div');
44
+ body.className = 'gd-card-body';
45
+ body.style.padding = '16px';
46
+ body.innerHTML = `
47
+ <div contenteditable="true" style="font-size:13px; color:rgba(255,255,255,0.7); outline:none; min-height:60px; line-height:1.6;">
48
+ ${data.meta?.text || 'Click to edit...'}
49
+ </div>
50
+ `;
51
+
52
+ card.appendChild(header);
53
+ card.appendChild(body);
54
+ return card;
55
+ },
56
+ consumesMouse(target: HTMLElement) {
57
+ return target.closest('[contenteditable]') !== null;
58
+ }
59
+ };
60
+
61
+ // ─── Init ─────────────────────────────────────────────────
62
+ const container = document.getElementById('app')!;
63
+ const gd = new GalaxyDraw(container, {
64
+ mode: 'simple',
65
+ cards: { defaultWidth: 320, defaultHeight: 240 },
66
+ });
67
+
68
+ gd.registerPlugin(TextCardPlugin);
69
+ gd.registerPlugin(NoteCardPlugin);
70
+
71
+ // ─── Create demo cards ────────────────────────────────────
72
+ const items = [
73
+ {
74
+ type: 'text', id: 'welcome', x: 100, y: 100, width: 380, height: 220, meta: {
75
+ title: 'galaxydraw',
76
+ content: '<div style="font-size:20px; font-weight:600; color:#fff; margin-bottom:8px;">Infinite Canvas Framework</div><div>The engine behind <strong>GitMaps</strong> and <strong>WARMAPS</strong>.</div><br/><div style="font-size:11px; color:rgba(255,255,255,0.35);">Pan: drag empty space | Zoom: scroll | Drag cards by headers</div>'
77
+ }
78
+ },
79
+ { type: 'note', id: 'note1', x: 550, y: 80, width: 280, height: 200, meta: { title: 'Architecture', text: 'EventBus > CanvasState > CardManager > ViewportCuller' } },
80
+ {
81
+ type: 'text', id: 'features', x: 100, y: 380, width: 350, height: 280, meta: {
82
+ title: 'Features',
83
+ content: '<ul style="padding-left:16px;"><li>Virtualized rendering</li><li>Card plugins for custom content</li><li>Dual control modes (Simple / Advanced)</li><li>Viewport culling</li><li>Layout persistence</li><li>Minimap</li><li>Type-safe EventBus</li></ul>'
84
+ }
85
+ },
86
+ { type: 'note', id: 'note2', x: 550, y: 340, width: 280, height: 180, meta: { title: 'Performance', text: 'React repo: 6833 files, only 9 DOM cards created. 6824 deferred. Over 300x speedup.' } },
87
+ {
88
+ type: 'text', id: 'modes', x: 900, y: 100, width: 300, height: 200, meta: {
89
+ title: 'Control Modes',
90
+ content: '<div><strong>Simple</strong> (WARMAPS): Drag = pan canvas<br/><strong>Advanced</strong> (GitMaps): Space+Drag = pan, Click = select</div>'
91
+ }
92
+ },
93
+ { type: 'note', id: 'note3', x: 900, y: 360, width: 280, height: 160, meta: { title: 'In Production', text: 'Powering GitMaps (repo visualization) and WARMAPS (geopolitical intelligence dashboard). Touch support included.' } },
94
+ ];
95
+
96
+ for (const c of items) {
97
+ gd.cards.create(c.type, c);
98
+ }
99
+
100
+ // ─── Toolbar ──────────────────────────────────────────────
101
+ const toolbar = document.createElement('div');
102
+ toolbar.className = 'demo-toolbar';
103
+
104
+ const label = document.createElement('span');
105
+ label.className = 'mode-label';
106
+ label.textContent = 'Mode:';
107
+ toolbar.appendChild(label);
108
+
109
+ const btnSimple = document.createElement('button');
110
+ btnSimple.textContent = 'Simple (WARMAPS)';
111
+ btnSimple.className = 'active';
112
+ btnSimple.id = 'modeSimple';
113
+ toolbar.appendChild(btnSimple);
114
+
115
+ const btnAdvanced = document.createElement('button');
116
+ btnAdvanced.textContent = 'Advanced (GitMaps)';
117
+ btnAdvanced.id = 'modeAdvanced';
118
+ toolbar.appendChild(btnAdvanced);
119
+
120
+ const btnAdd = document.createElement('button');
121
+ btnAdd.textContent = '+ Card';
122
+ toolbar.appendChild(btnAdd);
123
+
124
+ const btnFit = document.createElement('button');
125
+ btnFit.textContent = 'Fit All';
126
+ toolbar.appendChild(btnFit);
127
+
128
+ document.body.appendChild(toolbar);
129
+
130
+ btnSimple.onclick = () => {
131
+ gd.setMode('simple');
132
+ btnSimple.classList.add('active');
133
+ btnAdvanced.classList.remove('active');
134
+ };
135
+
136
+ btnAdvanced.onclick = () => {
137
+ gd.setMode('advanced');
138
+ btnAdvanced.classList.add('active');
139
+ btnSimple.classList.remove('active');
140
+ };
141
+
142
+ btnAdd.onclick = () => {
143
+ const id = 'card-' + Date.now();
144
+ gd.cards.create('note', {
145
+ id, x: 200 + Math.random() * 600, y: 200 + Math.random() * 400,
146
+ width: 260, height: 180,
147
+ meta: { title: 'New Note', text: 'Click to edit...' }
148
+ });
149
+ };
150
+
151
+ btnFit.onclick = () => gd.fitAll();
152
+
153
+ // Global debug access
154
+ (window as any).gd = gd;