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,358 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Canvas Export — capture the current canvas view as PNG.
4
+ *
5
+ * Two modes:
6
+ * 1. Viewport capture: exports what's currently visible on screen
7
+ * 2. Full canvas: exports ALL cards at their actual positions (may be large)
8
+ *
9
+ * Uses a <canvas> element to render card thumbnails with file names,
10
+ * diff markers, and connection lines. No external dependencies.
11
+ */
12
+ import { measure } from 'measure-fn';
13
+ import type { CanvasContext } from './context';
14
+ import { getGalaxyDrawState } from './galaxydraw-bridge';
15
+
16
+ // ─── Config ──────────────────────────────────────────────
17
+ const CARD_BG = '#1a1a2e';
18
+ const CARD_BORDER = '#2d2d44';
19
+ const CARD_HEADER_BG = '#252540';
20
+ const TEXT_COLOR = '#e2e8f0';
21
+ const MUTED_COLOR = '#64748b';
22
+ const ACCENT = '#7c3aed';
23
+ const ADD_COLOR = '#22c55e';
24
+ const DEL_COLOR = '#ef4444';
25
+ const MOD_COLOR = '#eab308';
26
+
27
+ const LANG_COLORS: Record<string, string> = {
28
+ ts: '#3178c6', tsx: '#3178c6', js: '#f7df1e', jsx: '#f7df1e',
29
+ py: '#3776ab', rs: '#ce412b', go: '#00add8', css: '#1572b6',
30
+ html: '#e34f26', json: '#5bc0de', md: '#083fa1', toml: '#9c4221',
31
+ };
32
+
33
+ function getLangColor(path: string): string {
34
+ const ext = path.split('.').pop()?.toLowerCase() || '';
35
+ return LANG_COLORS[ext] || '#888';
36
+ }
37
+
38
+ // ─── Get card bounds ─────────────────────────────────────
39
+ function getAllCardBounds(ctx: CanvasContext): { x: number; y: number; w: number; h: number; path: string; changed: boolean }[] {
40
+ const cards: { x: number; y: number; w: number; h: number; path: string; changed: boolean }[] = [];
41
+
42
+ for (const [path, card] of ctx.fileCards) {
43
+ const x = parseFloat(card.style.left) || 0;
44
+ const y = parseFloat(card.style.top) || 0;
45
+ const w = card.offsetWidth || 580;
46
+ const h = card.offsetHeight || 700;
47
+ const changed = card.dataset.changed === 'true';
48
+ cards.push({ x, y, w, h, path, changed });
49
+ }
50
+
51
+ // Include deferred cards
52
+ for (const [path, entry] of ctx.deferredCards) {
53
+ const { x, y, size, isChanged } = entry;
54
+ const w = size?.width || 580;
55
+ const h = size?.height || 700;
56
+ cards.push({ x, y, w, h, path, changed: isChanged || false });
57
+ }
58
+
59
+ return cards;
60
+ }
61
+
62
+ function getBoundingRect(cards: { x: number; y: number; w: number; h: number }[]) {
63
+ if (cards.length === 0) return { x: 0, y: 0, w: 800, h: 600 };
64
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
65
+ for (const c of cards) {
66
+ minX = Math.min(minX, c.x);
67
+ minY = Math.min(minY, c.y);
68
+ maxX = Math.max(maxX, c.x + c.w);
69
+ maxY = Math.max(maxY, c.y + c.h);
70
+ }
71
+ return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
72
+ }
73
+
74
+ // ─── Render cards to canvas ──────────────────────────────
75
+ function renderToCanvas(
76
+ cards: { x: number; y: number; w: number; h: number; path: string; changed: boolean }[],
77
+ bounds: { x: number; y: number; w: number; h: number },
78
+ scale: number,
79
+ repoName: string
80
+ ): HTMLCanvasElement {
81
+ const padding = 60;
82
+ const headerHeight = 50;
83
+ const canvas = document.createElement('canvas');
84
+ const cw = Math.ceil(bounds.w * scale + padding * 2);
85
+ const ch = Math.ceil(bounds.h * scale + padding * 2 + headerHeight);
86
+
87
+ // Limit to reasonable size
88
+ const maxDim = 8192;
89
+ const finalScale = Math.min(scale, maxDim / Math.max(bounds.w, bounds.h));
90
+ canvas.width = Math.min(maxDim, Math.ceil(bounds.w * finalScale + padding * 2));
91
+ canvas.height = Math.min(maxDim, Math.ceil(bounds.h * finalScale + padding * 2 + headerHeight));
92
+
93
+ const c = canvas.getContext('2d')!;
94
+
95
+ // Background
96
+ c.fillStyle = '#0f0f1a';
97
+ c.fillRect(0, 0, canvas.width, canvas.height);
98
+
99
+ // Subtle grid
100
+ c.strokeStyle = 'rgba(124, 58, 237, 0.04)';
101
+ c.lineWidth = 1;
102
+ const gridSize = 100 * finalScale;
103
+ for (let gx = padding; gx < canvas.width; gx += gridSize) {
104
+ c.beginPath(); c.moveTo(gx, headerHeight); c.lineTo(gx, canvas.height); c.stroke();
105
+ }
106
+ for (let gy = padding + headerHeight; gy < canvas.height; gy += gridSize) {
107
+ c.beginPath(); c.moveTo(0, gy); c.lineTo(canvas.width, gy); c.stroke();
108
+ }
109
+
110
+ // Header
111
+ c.fillStyle = 'rgba(0, 0, 0, 0.5)';
112
+ c.fillRect(0, 0, canvas.width, headerHeight);
113
+ c.fillStyle = ACCENT;
114
+ c.font = `bold 16px system-ui, -apple-system, sans-serif`;
115
+ c.fillText('GitMaps', 20, 30);
116
+ c.fillStyle = MUTED_COLOR;
117
+ c.font = `13px system-ui, -apple-system, sans-serif`;
118
+ c.fillText(`${repoName} · ${cards.length} files`, 95, 30);
119
+ // Timestamp
120
+ const now = new Date().toLocaleString();
121
+ const timeW = c.measureText(now).width;
122
+ c.fillText(now, canvas.width - timeW - 20, 30);
123
+
124
+ // Draw cards
125
+ for (const card of cards) {
126
+ const cx = (card.x - bounds.x) * finalScale + padding;
127
+ const cy = (card.y - bounds.y) * finalScale + padding + headerHeight;
128
+ const cw = card.w * finalScale;
129
+ const ch = card.h * finalScale;
130
+
131
+ // Card shadow
132
+ c.fillStyle = 'rgba(0, 0, 0, 0.3)';
133
+ c.fillRect(cx + 3, cy + 3, cw, ch);
134
+
135
+ // Card body
136
+ c.fillStyle = CARD_BG;
137
+ c.fillRect(cx, cy, cw, ch);
138
+
139
+ // Card border
140
+ c.strokeStyle = card.changed ? MOD_COLOR + '88' : CARD_BORDER;
141
+ c.lineWidth = card.changed ? 2 : 1;
142
+ c.strokeRect(cx, cy, cw, ch);
143
+
144
+ // Header bar
145
+ const hh = Math.min(28 * finalScale, 28);
146
+ c.fillStyle = CARD_HEADER_BG;
147
+ c.fillRect(cx, cy, cw, hh);
148
+
149
+ // Language dot
150
+ const langColor = getLangColor(card.path);
151
+ c.fillStyle = langColor;
152
+ c.beginPath();
153
+ c.arc(cx + 10, cy + hh / 2, 4, 0, Math.PI * 2);
154
+ c.fill();
155
+
156
+ // File name
157
+ const fileName = card.path.split('/').pop() || card.path;
158
+ const fontSize = Math.max(8, Math.min(12, 12 * finalScale));
159
+ c.fillStyle = TEXT_COLOR;
160
+ c.font = `600 ${fontSize}px system-ui, -apple-system, sans-serif`;
161
+ const maxTextW = cw - 24;
162
+ let displayName = fileName;
163
+ if (c.measureText(displayName).width > maxTextW) {
164
+ while (displayName.length > 3 && c.measureText(displayName + '…').width > maxTextW) {
165
+ displayName = displayName.slice(0, -1);
166
+ }
167
+ displayName += '…';
168
+ }
169
+ c.fillText(displayName, cx + 20, cy + hh / 2 + fontSize / 3);
170
+
171
+ // Changed marker
172
+ if (card.changed) {
173
+ c.fillStyle = MOD_COLOR;
174
+ c.beginPath();
175
+ c.arc(cx + cw - 10, cy + hh / 2, 4, 0, Math.PI * 2);
176
+ c.fill();
177
+ }
178
+
179
+ // Simulated code lines
180
+ const lineH = Math.max(2, 3 * finalScale);
181
+ const lineGap = Math.max(1, 1.5 * finalScale);
182
+ const startY = cy + hh + 8;
183
+ const endY = cy + ch - 4;
184
+ const lineX = cx + 6;
185
+ c.globalAlpha = 0.15;
186
+ for (let ly = startY; ly < endY; ly += lineH + lineGap) {
187
+ const lineW = (Math.random() * 0.5 + 0.3) * (cw - 16);
188
+ c.fillStyle = TEXT_COLOR;
189
+ c.fillRect(lineX, ly, lineW, lineH);
190
+ }
191
+ c.globalAlpha = 1;
192
+
193
+ // Full path (if cards are large enough)
194
+ if (ch > 60) {
195
+ const pathFontSize = Math.max(6, Math.min(9, 9 * finalScale));
196
+ c.fillStyle = MUTED_COLOR;
197
+ c.font = `${pathFontSize}px monospace`;
198
+ const dirPath = card.path.includes('/') ? card.path.substring(0, card.path.lastIndexOf('/')) : '';
199
+ if (dirPath) {
200
+ c.fillText(dirPath + '/', cx + 6, cy + hh + 6 + pathFontSize);
201
+ }
202
+ }
203
+ }
204
+
205
+ // Watermark
206
+ c.fillStyle = 'rgba(124, 58, 237, 0.15)';
207
+ c.font = 'bold 11px system-ui';
208
+ c.fillText('Exported from GitMaps', canvas.width - 150, canvas.height - 12);
209
+
210
+ return canvas;
211
+ }
212
+
213
+ // ─── Download helper ─────────────────────────────────────
214
+ function downloadCanvas(canvas: HTMLCanvasElement, filename: string) {
215
+ canvas.toBlob((blob) => {
216
+ if (!blob) return;
217
+ const url = URL.createObjectURL(blob);
218
+ const a = document.createElement('a');
219
+ a.href = url;
220
+ a.download = filename;
221
+ document.body.appendChild(a);
222
+ a.click();
223
+ document.body.removeChild(a);
224
+ setTimeout(() => URL.revokeObjectURL(url), 5000);
225
+ }, 'image/png');
226
+ }
227
+
228
+ // ─── Public API ──────────────────────────────────────────
229
+
230
+ /** Export the full canvas (all cards) as PNG */
231
+ export function exportCanvasAsPNG(ctx: CanvasContext) {
232
+ measure('export:png', () => {
233
+ const cards = getAllCardBounds(ctx);
234
+ if (cards.length === 0) {
235
+ console.warn('[canvas-export] No cards to export');
236
+ return;
237
+ }
238
+
239
+ const bounds = getBoundingRect(cards);
240
+
241
+ // Auto-scale: fit into ~4000px max dimension
242
+ const maxDim = 4096;
243
+ const scale = Math.min(1, maxDim / Math.max(bounds.w, bounds.h));
244
+
245
+ const repoName = (() => {
246
+ const el = document.querySelector('.repo-dropdown-trigger, .repo-name');
247
+ return el?.textContent?.trim() || 'Repository';
248
+ })();
249
+
250
+ const canvas = renderToCanvas(cards, bounds, scale, repoName);
251
+
252
+ const timestamp = new Date().toISOString().slice(0, 10);
253
+ const safeName = repoName.replace(/[^a-zA-Z0-9]/g, '-').toLowerCase();
254
+ downloadCanvas(canvas, `gitmaps-${safeName}-${timestamp}.png`);
255
+
256
+ // Show toast
257
+ showExportToast(`Exported ${cards.length} files as PNG`);
258
+ });
259
+ }
260
+
261
+ /** Export just the current viewport as PNG */
262
+ export function exportViewportAsPNG(ctx: CanvasContext) {
263
+ measure('export:viewport', () => {
264
+ const gdState = getGalaxyDrawState();
265
+ if (!gdState) return;
266
+
267
+ const vpEl = ctx.canvasViewport;
268
+ if (!vpEl) return;
269
+
270
+ const vpW = vpEl.clientWidth;
271
+ const vpH = vpEl.clientHeight;
272
+ const zoom = gdState.zoom || 1;
273
+ const offsetX = gdState.offsetX || 0;
274
+ const offsetY = gdState.offsetY || 0;
275
+
276
+ // World coordinates of viewport
277
+ const worldLeft = -offsetX / zoom;
278
+ const worldTop = -offsetY / zoom;
279
+ const worldRight = (vpW - offsetX) / zoom;
280
+ const worldBottom = (vpH - offsetY) / zoom;
281
+
282
+ const allCards = getAllCardBounds(ctx);
283
+ // Filter to cards visible in viewport
284
+ const visibleCards = allCards.filter(card =>
285
+ card.x + card.w > worldLeft &&
286
+ card.x < worldRight &&
287
+ card.y + card.h > worldTop &&
288
+ card.y < worldBottom
289
+ );
290
+
291
+ if (visibleCards.length === 0) {
292
+ showExportToast('No cards visible in viewport');
293
+ return;
294
+ }
295
+
296
+ const bounds = {
297
+ x: worldLeft,
298
+ y: worldTop,
299
+ w: worldRight - worldLeft,
300
+ h: worldBottom - worldTop,
301
+ };
302
+
303
+ const scale = Math.min(2, vpW / bounds.w); // 2x for retina quality
304
+
305
+ const repoName = (() => {
306
+ const el = document.querySelector('.repo-dropdown-trigger, .repo-name');
307
+ return el?.textContent?.trim() || 'Repository';
308
+ })();
309
+
310
+ const canvas = renderToCanvas(visibleCards, bounds, scale, repoName);
311
+
312
+ const timestamp = new Date().toISOString().slice(0, 10);
313
+ const safeName = repoName.replace(/[^a-zA-Z0-9]/g, '-').toLowerCase();
314
+ downloadCanvas(canvas, `gitmaps-viewport-${safeName}-${timestamp}.png`);
315
+
316
+ showExportToast(`Exported viewport (${visibleCards.length} files) as PNG`);
317
+ });
318
+ }
319
+
320
+ // ─── Toast notification ──────────────────────────────────
321
+ function showExportToast(message: string) {
322
+ const existing = document.getElementById('exportToast');
323
+ if (existing) existing.remove();
324
+
325
+ const toast = document.createElement('div');
326
+ toast.id = 'exportToast';
327
+ toast.textContent = `📸 ${message}`;
328
+ toast.style.cssText = `
329
+ position: fixed;
330
+ bottom: 60px;
331
+ left: 50%;
332
+ transform: translateX(-50%) translateY(10px);
333
+ background: rgba(124, 58, 237, 0.9);
334
+ color: #fff;
335
+ padding: 10px 20px;
336
+ border-radius: 8px;
337
+ font-size: 13px;
338
+ font-weight: 500;
339
+ z-index: 10001;
340
+ backdrop-filter: blur(8px);
341
+ box-shadow: 0 4px 20px rgba(124, 58, 237, 0.3);
342
+ opacity: 0;
343
+ transition: opacity 0.3s ease, transform 0.3s ease;
344
+ pointer-events: none;
345
+ `;
346
+
347
+ document.body.appendChild(toast);
348
+ requestAnimationFrame(() => {
349
+ toast.style.opacity = '1';
350
+ toast.style.transform = 'translateX(-50%) translateY(0)';
351
+ });
352
+
353
+ setTimeout(() => {
354
+ toast.style.opacity = '0';
355
+ toast.style.transform = 'translateX(-50%) translateY(10px)';
356
+ setTimeout(() => toast.remove(), 300);
357
+ }, 3000);
358
+ }