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,400 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * File Preview — renders the EXACT same card component when hovering
4
+ * over pill placeholders or file cards at low zoom levels.
5
+ *
6
+ * Instead of a simplified tooltip with plain text, this clones or
7
+ * re-renders the full file card (diff markers, syntax highlighting,
8
+ * status badges, connections) and shows it in a fixed popup container
9
+ * at readable scale.
10
+ *
11
+ * Architecture:
12
+ * - Single shared popup container (avoids DOM thrashing)
13
+ * - Debounced show (180ms) to prevent flicker during fast panning
14
+ * - Looks up file data from ctx.fileCards (clone existing) or
15
+ * ctx.deferredCards (render fresh via createAllFileCard)
16
+ * - Positioned near cursor, clamped to viewport bounds
17
+ * - Hides on mouseout, zoom change above threshold, or scroll
18
+ */
19
+
20
+ import { getGalaxyDrawState } from './galaxydraw-bridge';
21
+ import type { CanvasContext } from './context';
22
+
23
+ // ─── Config ──────────────────────────────────────────────
24
+ const PREVIEW_ZOOM_THRESHOLD = 0.25; // Match LOD_ZOOM_THRESHOLD in viewport-culling.ts
25
+ const SHOW_DELAY_MS = 180;
26
+ const OFFSET_X = 16;
27
+ const OFFSET_Y = 16;
28
+ const POPUP_MAX_W = 520;
29
+ const POPUP_MAX_H = 600;
30
+
31
+ // ─── State ───────────────────────────────────────────────
32
+ let popup: HTMLElement | null = null;
33
+ let showTimer: ReturnType<typeof setTimeout> | null = null;
34
+ let currentCardPath: string | null = null;
35
+ let isInitialized = false;
36
+ let _ctx: CanvasContext | null = null;
37
+ let isPreviewEnabled = localStorage.getItem('gitmaps:previewEnabled') !== 'false'; // enabled by default
38
+ let _isHoveringPopup = false;
39
+
40
+ // ─── Popup container ─────────────────────────────────────
41
+ function ensurePopup(): HTMLElement {
42
+ if (popup) return popup;
43
+
44
+ popup = document.createElement('div');
45
+ popup.className = 'file-preview-popup';
46
+ popup.style.cssText = `
47
+ position: fixed;
48
+ z-index: 9999;
49
+ pointer-events: auto;
50
+ opacity: 0;
51
+ transform: translateY(6px) scale(0.97);
52
+ transition: opacity 0.18s ease, transform 0.18s ease;
53
+ max-width: ${POPUP_MAX_W}px;
54
+ max-height: ${POPUP_MAX_H}px;
55
+ overflow-y: auto;
56
+ overflow-x: hidden;
57
+ border-radius: 12px;
58
+ box-shadow:
59
+ 0 12px 48px rgba(0, 0, 0, 0.6),
60
+ 0 0 0 1px rgba(124, 58, 237, 0.25),
61
+ 0 0 24px rgba(124, 58, 237, 0.12);
62
+ background: var(--bg-primary, #0a0a14);
63
+ `;
64
+ // Keep popup visible while hovering over it (for scrolling)
65
+ popup.addEventListener('mouseenter', () => { _isHoveringPopup = true; });
66
+ popup.addEventListener('mouseleave', () => {
67
+ _isHoveringPopup = false;
68
+ hidePopup();
69
+ });
70
+ // Capture wheel events to scroll popup content, not zoom canvas
71
+ popup.addEventListener('wheel', (e) => {
72
+ e.stopPropagation();
73
+ // Manually scroll the popup content
74
+ popup.scrollTop += e.deltaY;
75
+ e.preventDefault();
76
+ }, { passive: false });
77
+ document.body.appendChild(popup);
78
+ return popup;
79
+ }
80
+
81
+ /**
82
+ * Render the full card preview inside the popup container.
83
+ * Strategy:
84
+ * 1. If the card is already materialized in ctx.fileCards → deep clone it
85
+ * 2. If it's deferred in ctx.deferredCards → render a fresh card
86
+ *
87
+ * Important: Canvas-text rendering (CanvasTextRenderer) doesn't survive
88
+ * cloning, so we always force DOM-based HTML rendering for previews.
89
+ */
90
+ function renderPreviewCard(path: string): HTMLElement | null {
91
+ if (!_ctx) return null;
92
+
93
+ // Strategy 1: Clone existing materialized card
94
+ const existingCard = _ctx.fileCards.get(path);
95
+ if (existingCard) {
96
+ const clone = existingCard.cloneNode(true) as HTMLElement;
97
+ // Reset positioning — we'll position the popup itself
98
+ clone.style.position = 'relative';
99
+ clone.style.left = '0';
100
+ clone.style.top = '0';
101
+ clone.style.visibility = 'visible';
102
+ clone.style.contentVisibility = 'visible';
103
+ clone.style.opacity = '1';
104
+ clone.style.maxHeight = 'none';
105
+ clone.style.width = `${POPUP_MAX_W - 2}px`;
106
+ clone.style.overflow = 'visible';
107
+ clone.style.pointerEvents = 'auto';
108
+ clone.style.transition = 'none';
109
+ clone.style.transform = 'none';
110
+ clone.style.outline = 'none';
111
+ clone.style.boxShadow = 'none';
112
+ delete clone.dataset.culled;
113
+ delete clone.dataset.expanded;
114
+
115
+ // If the card used canvas-text rendering, re-render body as DOM HTML
116
+ const canvasContainer = clone.querySelector('.canvas-container');
117
+ if (canvasContainer) {
118
+ const { _getCardFileData, _buildFileContentHTML } = require('./cards');
119
+ const file = _getCardFileData(existingCard);
120
+ if (file?.content) {
121
+ const addedLines = file.addedLines || new Set();
122
+ const deletedBeforeLine = file.deletedBeforeLine || new Map();
123
+ const isAllAdded = file.status === 'added';
124
+ const isAllDeleted = file.status === 'deleted';
125
+ const html = _buildFileContentHTML(
126
+ file.content, file.layerSections, addedLines, deletedBeforeLine,
127
+ isAllAdded, isAllDeleted, false, file.lines
128
+ );
129
+ canvasContainer.outerHTML = html;
130
+ }
131
+ }
132
+
133
+ return clone;
134
+ }
135
+
136
+ // Strategy 2: Render from deferred card data
137
+ const deferred = _ctx.deferredCards.get(path);
138
+ if (deferred) {
139
+ // Temporarily force DOM rendering (canvas-text doesn't work in detached elements)
140
+ const wasCanvasText = _ctx.useCanvasText;
141
+ _ctx.useCanvasText = false;
142
+ const { createAllFileCard } = require('./cards');
143
+ const card = createAllFileCard(_ctx, deferred.file, 0, 0, null, true) as HTMLElement;
144
+ _ctx.useCanvasText = wasCanvasText;
145
+ card.style.position = 'relative';
146
+ card.style.left = '0';
147
+ card.style.top = '0';
148
+ card.style.maxHeight = 'none';
149
+ card.style.width = `${POPUP_MAX_W - 2}px`;
150
+ card.style.overflow = 'visible';
151
+ card.style.pointerEvents = 'auto';
152
+
153
+ card.style.transition = 'none';
154
+ return card;
155
+ }
156
+
157
+ return null;
158
+ }
159
+
160
+ // ─── Image preview support ───────────────────────────────
161
+ const IMAGE_EXTENSIONS = new Set(['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'ico', 'bmp', 'avif']);
162
+
163
+ function renderImagePreview(path: string): HTMLElement | null {
164
+ if (!_ctx) return null;
165
+ const state = _ctx.snap().context;
166
+ const repoPath = state.repoPath;
167
+ if (!repoPath) return null;
168
+
169
+ const container = document.createElement('div');
170
+ container.style.cssText = `
171
+ padding: 12px;
172
+ display: flex;
173
+ flex-direction: column;
174
+ align-items: center;
175
+ gap: 8px;
176
+ `;
177
+
178
+ // File name label
179
+ const label = document.createElement('div');
180
+ label.style.cssText = 'font-size: 11px; color: var(--text-muted); font-family: monospace;';
181
+ label.textContent = path.split('/').pop() || path;
182
+ container.appendChild(label);
183
+
184
+ // Image element
185
+ const img = document.createElement('img');
186
+ img.src = `/api/repo/file-raw?path=${encodeURIComponent(repoPath)}&filePath=${encodeURIComponent(path)}`;
187
+ img.alt = path;
188
+ img.style.cssText = `
189
+ max-width: ${POPUP_MAX_W - 24}px;
190
+ max-height: ${POPUP_MAX_H - 50}px;
191
+ object-fit: contain;
192
+ border-radius: 6px;
193
+ background: repeating-conic-gradient(#222 0% 25%, #333 0% 50%) 50% / 16px 16px; /* checkerboard for transparency */
194
+ `;
195
+ img.onerror = () => {
196
+ img.style.display = 'none';
197
+ label.textContent = `⚠ Could not load: ${path.split('/').pop()}`;
198
+ };
199
+ container.appendChild(img);
200
+
201
+ return container;
202
+ }
203
+
204
+ function showPopup(path: string, screenX: number, screenY: number) {
205
+ const el = ensurePopup();
206
+
207
+ // Check if this is an image file
208
+ const ext = path.split('.').pop()?.toLowerCase() || '';
209
+ let previewContent: HTMLElement | null;
210
+
211
+ if (IMAGE_EXTENSIONS.has(ext)) {
212
+ previewContent = renderImagePreview(path);
213
+ } else {
214
+ previewContent = renderPreviewCard(path);
215
+ }
216
+
217
+ if (!previewContent) {
218
+ hidePopup();
219
+ return;
220
+ }
221
+
222
+ // Clear previous and insert
223
+ el.innerHTML = '';
224
+ el.appendChild(previewContent);
225
+
226
+ // Position: near mouse, clamped to viewport
227
+ const vw = window.innerWidth;
228
+ const vh = window.innerHeight;
229
+
230
+ let x = screenX + OFFSET_X;
231
+ let y = screenY + OFFSET_Y;
232
+
233
+ // Clamp right edge
234
+ if (x + POPUP_MAX_W > vw - 12) x = screenX - POPUP_MAX_W - OFFSET_X;
235
+ // Clamp bottom edge
236
+ if (y + POPUP_MAX_H > vh - 12) y = screenY - POPUP_MAX_H - OFFSET_Y;
237
+ // Clamp left/top
238
+ x = Math.max(8, x);
239
+ y = Math.max(8, y);
240
+
241
+ el.style.left = `${x}px`;
242
+ el.style.top = `${y}px`;
243
+ el.style.opacity = '1';
244
+ el.style.transform = 'translateY(0) scale(1)';
245
+ }
246
+
247
+ function hidePopup() {
248
+ if (showTimer) {
249
+ clearTimeout(showTimer);
250
+ showTimer = null;
251
+ }
252
+ currentCardPath = null;
253
+ if (popup) {
254
+ popup.style.opacity = '0';
255
+ popup.style.transform = 'translateY(6px) scale(0.97)';
256
+ // Clear content after fade to free memory
257
+ setTimeout(() => {
258
+ if (popup && popup.style.opacity === '0') {
259
+ popup.innerHTML = '';
260
+ }
261
+ }, 200);
262
+ }
263
+ }
264
+
265
+ // ─── Event handlers ──────────────────────────────────────
266
+ function onMouseMove(e: MouseEvent) {
267
+ if (!isPreviewEnabled) return;
268
+ if (_isHoveringPopup) return; // Don't hide while interacting with popup
269
+
270
+ const gdState = getGalaxyDrawState();
271
+ if (!gdState || gdState.zoom >= PREVIEW_ZOOM_THRESHOLD) {
272
+ hidePopup();
273
+ return;
274
+ }
275
+
276
+ // Find the closest pill card or file card ancestor
277
+ const target = e.target as HTMLElement;
278
+ const pill = target.closest?.('.file-pill') as HTMLElement | null;
279
+ const card = target.closest?.('.file-card') as HTMLElement | null;
280
+ const element = pill || card;
281
+
282
+ if (!element) {
283
+ hidePopup();
284
+ return;
285
+ }
286
+
287
+ const path = element.dataset.path || '';
288
+ if (!path) {
289
+ hidePopup();
290
+ return;
291
+ }
292
+
293
+ if (path === currentCardPath) {
294
+ // Already showing for this card — just reposition
295
+ if (popup && popup.style.opacity === '1') {
296
+ const vw = window.innerWidth;
297
+ const vh = window.innerHeight;
298
+ let x = e.clientX + OFFSET_X;
299
+ let y = e.clientY + OFFSET_Y;
300
+ if (x + POPUP_MAX_W > vw - 12) x = e.clientX - POPUP_MAX_W - OFFSET_X;
301
+ if (y + POPUP_MAX_H > vh - 12) y = e.clientY - POPUP_MAX_H - OFFSET_Y;
302
+ x = Math.max(8, x);
303
+ y = Math.max(8, y);
304
+ popup.style.left = `${x}px`;
305
+ popup.style.top = `${y}px`;
306
+ }
307
+ return;
308
+ }
309
+
310
+ // New card — debounce show
311
+ hidePopup();
312
+ currentCardPath = path;
313
+ showTimer = setTimeout(() => {
314
+ // Re-verify zoom is still low
315
+ const gd = getGalaxyDrawState();
316
+ if (!gd || gd.zoom >= PREVIEW_ZOOM_THRESHOLD) return;
317
+ showPopup(path, e.clientX, e.clientY);
318
+ }, SHOW_DELAY_MS);
319
+ }
320
+
321
+ function onMouseOut(e: MouseEvent) {
322
+ const related = e.relatedTarget as HTMLElement | null;
323
+ if (related?.closest?.('.file-pill') || related?.closest?.('.file-card')) return;
324
+ // Don't hide if mouse moved to the popup itself
325
+ if (related?.closest?.('.file-preview-popup')) return;
326
+ if (_isHoveringPopup) return;
327
+ hidePopup();
328
+ }
329
+
330
+ // ─── Public API ──────────────────────────────────────────
331
+
332
+ /**
333
+ * Initialize file preview on the canvas viewport.
334
+ * Call once after the canvas is mounted.
335
+ * @param viewportEl - The canvas viewport element
336
+ * @param ctx - The CanvasContext for looking up file data
337
+ */
338
+ export function initFilePreview(viewportEl: HTMLElement, ctx?: CanvasContext) {
339
+ if (isInitialized) return;
340
+ isInitialized = true;
341
+ if (ctx) _ctx = ctx;
342
+
343
+ viewportEl.addEventListener('mousemove', onMouseMove, { passive: true });
344
+ viewportEl.addEventListener('mouseout', onMouseOut, { passive: true });
345
+
346
+ // Hide on zoom change (catches scroll-zoom)
347
+ viewportEl.addEventListener('wheel', () => {
348
+ // Don't hide popup if user is hovering/scrolling it
349
+ if (_isHoveringPopup) return;
350
+ setTimeout(() => {
351
+ if (_isHoveringPopup) return;
352
+ const gd = getGalaxyDrawState();
353
+ if (gd && gd.zoom >= PREVIEW_ZOOM_THRESHOLD) {
354
+ hidePopup();
355
+ }
356
+ }, 50);
357
+ }, { passive: true });
358
+
359
+ console.log('[file-preview] Initialized — full card preview below', (PREVIEW_ZOOM_THRESHOLD * 100).toFixed(0) + '% zoom');
360
+ }
361
+
362
+ /**
363
+ * Destroy file preview. Call on cleanup.
364
+ */
365
+ export function destroyFilePreview(viewportEl: HTMLElement) {
366
+ viewportEl.removeEventListener('mousemove', onMouseMove);
367
+ viewportEl.removeEventListener('mouseout', onMouseOut);
368
+ if (popup) {
369
+ popup.remove();
370
+ popup = null;
371
+ }
372
+ _ctx = null;
373
+ isInitialized = false;
374
+ }
375
+
376
+ /**
377
+ * Toggle file preview on/off. Persists to localStorage.
378
+ */
379
+ export function toggleFilePreview(): boolean {
380
+ isPreviewEnabled = !isPreviewEnabled;
381
+ localStorage.setItem('gitmaps:previewEnabled', String(isPreviewEnabled));
382
+ if (!isPreviewEnabled) hidePopup();
383
+ return isPreviewEnabled;
384
+ }
385
+
386
+ /**
387
+ * Set file preview enabled state. Persists to localStorage.
388
+ */
389
+ export function setFilePreviewEnabled(enabled: boolean) {
390
+ isPreviewEnabled = enabled;
391
+ localStorage.setItem('gitmaps:previewEnabled', String(enabled));
392
+ if (!enabled) hidePopup();
393
+ }
394
+
395
+ /**
396
+ * Get current preview enabled state.
397
+ */
398
+ export function isFilePreviewEnabled(): boolean {
399
+ return isPreviewEnabled;
400
+ }