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.
- package/README.md +167 -0
- package/app/api/auth/favorites/route.ts +56 -0
- package/app/api/auth/github/callback/route.ts +103 -0
- package/app/api/auth/github/route.ts +32 -0
- package/app/api/auth/me/route.ts +52 -0
- package/app/api/auth/positions/route.ts +50 -0
- package/app/api/chat/route.ts +101 -0
- package/app/api/connections/route.ts +72 -0
- package/app/api/github/repos/route.ts +111 -0
- package/app/api/positions/route.ts +80 -0
- package/app/api/repo/branch-diff/route.ts +201 -0
- package/app/api/repo/branches/route.ts +53 -0
- package/app/api/repo/browse/route.ts +55 -0
- package/app/api/repo/clone/route.ts +78 -0
- package/app/api/repo/clone-stream/route.ts +131 -0
- package/app/api/repo/file-content/route.ts +28 -0
- package/app/api/repo/file-delete/route.ts +62 -0
- package/app/api/repo/file-history/route.ts +45 -0
- package/app/api/repo/file-rename/route.ts +83 -0
- package/app/api/repo/file-save/route.ts +45 -0
- package/app/api/repo/files/route.ts +169 -0
- package/app/api/repo/git-blame/route.ts +86 -0
- package/app/api/repo/git-commit/route.ts +40 -0
- package/app/api/repo/git-heatmap/route.ts +55 -0
- package/app/api/repo/imports/route.ts +154 -0
- package/app/api/repo/load/route.ts +56 -0
- package/app/api/repo/mode/route.ts +14 -0
- package/app/api/repo/search/route.ts +127 -0
- package/app/api/repo/tree/route.ts +104 -0
- package/app/api/repo/upload/route.ts +53 -0
- package/app/api/repo/validate-path.ts +53 -0
- package/app/canvas_users.db +0 -0
- package/app/canvas_users.db-shm +0 -0
- package/app/canvas_users.db-wal +0 -0
- package/app/globals.css +7899 -0
- package/app/layout.tsx +493 -0
- package/app/lib/auth.ts +193 -0
- package/app/lib/auto-save.ts +137 -0
- package/app/lib/branch-compare.ts +443 -0
- package/app/lib/breadcrumbs.ts +170 -0
- package/app/lib/canvas-export.ts +358 -0
- package/app/lib/canvas-text.ts +912 -0
- package/app/lib/canvas.ts +564 -0
- package/app/lib/card-arrangement.ts +188 -0
- package/app/lib/card-context-menu.tsx +453 -0
- package/app/lib/card-diff-markers.ts +270 -0
- package/app/lib/card-expand.ts +189 -0
- package/app/lib/card-groups.ts +246 -0
- package/app/lib/cards.tsx +914 -0
- package/app/lib/chat.tsx +308 -0
- package/app/lib/code-editor.ts +508 -0
- package/app/lib/command-palette.ts +262 -0
- package/app/lib/connections.tsx +1037 -0
- package/app/lib/context.ts +94 -0
- package/app/lib/cursor-sharing.ts +281 -0
- package/app/lib/dependency-graph.ts +438 -0
- package/app/lib/events.tsx +1747 -0
- package/app/lib/file-card-plugin.ts +134 -0
- package/app/lib/file-modal.tsx +849 -0
- package/app/lib/file-preview.ts +400 -0
- package/app/lib/file-tabs.ts +318 -0
- package/app/lib/galaxydraw-bridge.ts +477 -0
- package/app/lib/galaxydraw.test.ts +229 -0
- package/app/lib/global-search.ts +264 -0
- package/app/lib/goto-definition.ts +224 -0
- package/app/lib/heatmap.ts +178 -0
- package/app/lib/hidden-files.tsx +222 -0
- package/app/lib/layers.ts +0 -0
- package/app/lib/layers.tsx +365 -0
- package/app/lib/loading.tsx +45 -0
- package/app/lib/multi-repo.ts +286 -0
- package/app/lib/new-file-dialog.tsx +230 -0
- package/app/lib/onboarding.tsx +213 -0
- package/app/lib/perf-overlay.ts +360 -0
- package/app/lib/positions.ts +176 -0
- package/app/lib/pr-review.ts +374 -0
- package/app/lib/production-mode.ts +47 -0
- package/app/lib/repo.tsx +977 -0
- package/app/lib/settings-modal.tsx +374 -0
- package/app/lib/settings.ts +97 -0
- package/app/lib/shortcuts-panel.ts +141 -0
- package/app/lib/status-bar.ts +128 -0
- package/app/lib/symbol-outline.ts +212 -0
- package/app/lib/syntax.ts +177 -0
- package/app/lib/tab-diff.ts +238 -0
- package/app/lib/user.tsx +133 -0
- package/app/lib/utils.ts +78 -0
- package/app/lib/viewport-culling.ts +728 -0
- package/app/page.client.tsx +215 -0
- package/app/page.tsx +291 -0
- package/app/state/machine.js +196 -0
- package/app/styles/main.css +2168 -0
- package/banner.png +0 -0
- package/cli.ts +44 -0
- package/package.json +75 -0
- package/packages/galaxydraw/README.md +296 -0
- package/packages/galaxydraw/banner.png +0 -0
- package/packages/galaxydraw/demo/build-static.ts +100 -0
- package/packages/galaxydraw/demo/client.ts +154 -0
- package/packages/galaxydraw/demo/dist/client.js +8 -0
- package/packages/galaxydraw/demo/index.html +256 -0
- package/packages/galaxydraw/demo/server.ts +96 -0
- package/packages/galaxydraw/dist/index.js +984 -0
- package/packages/galaxydraw/dist/index.js.map +16 -0
- package/packages/galaxydraw/node_modules/.bin/tsc.bunx +0 -0
- package/packages/galaxydraw/node_modules/.bin/tsc.exe +0 -0
- package/packages/galaxydraw/node_modules/.bin/tsserver.bunx +0 -0
- package/packages/galaxydraw/node_modules/.bin/tsserver.exe +0 -0
- package/packages/galaxydraw/package.json +49 -0
- package/packages/galaxydraw/perf.test.ts +284 -0
- package/packages/galaxydraw/src/core/cards.ts +435 -0
- package/packages/galaxydraw/src/core/engine.ts +339 -0
- package/packages/galaxydraw/src/core/events.ts +81 -0
- package/packages/galaxydraw/src/core/layout.ts +136 -0
- package/packages/galaxydraw/src/core/minimap.ts +216 -0
- package/packages/galaxydraw/src/core/state.ts +177 -0
- package/packages/galaxydraw/src/core/viewport.ts +106 -0
- package/packages/galaxydraw/src/galaxydraw.css +166 -0
- package/packages/galaxydraw/src/index.ts +40 -0
- package/packages/galaxydraw/tsconfig.json +30 -0
- package/server.ts +62 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Multi-tab file management for the modal.
|
|
4
|
+
* Allows opening multiple files that persist as tabs.
|
|
5
|
+
* Tab paths are saved to localStorage for session persistence.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const TAB_STORAGE_KEY = 'gitcanvas:openTabs';
|
|
9
|
+
|
|
10
|
+
export interface FileTab {
|
|
11
|
+
path: string;
|
|
12
|
+
name: string;
|
|
13
|
+
file: any; // Original file data object
|
|
14
|
+
rendered: { full: string; diff: string; full_raw: string };
|
|
15
|
+
currentView: string; // 'full' | 'diff' | 'edit' | 'chat'
|
|
16
|
+
scrollTop: number;
|
|
17
|
+
originalContent: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ─── Global tab state ───────────────────────────────
|
|
21
|
+
let openTabs: FileTab[] = [];
|
|
22
|
+
let activeTabIndex = -1;
|
|
23
|
+
let tabBarEl: HTMLElement | null = null;
|
|
24
|
+
|
|
25
|
+
export function getOpenTabs(): FileTab[] { return openTabs; }
|
|
26
|
+
export function getActiveTab(): FileTab | null { return openTabs[activeTabIndex] || null; }
|
|
27
|
+
export function getActiveTabIndex(): number { return activeTabIndex; }
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Initialize the tab bar element in the modal.
|
|
31
|
+
* Should be called once during app setup.
|
|
32
|
+
*/
|
|
33
|
+
export function initTabBar() {
|
|
34
|
+
if (tabBarEl) return tabBarEl;
|
|
35
|
+
|
|
36
|
+
const modal = document.getElementById('filePreviewModal');
|
|
37
|
+
if (!modal) return null;
|
|
38
|
+
|
|
39
|
+
const modalContent = modal.querySelector('.modal-content');
|
|
40
|
+
const header = modal.querySelector('.modal-header');
|
|
41
|
+
if (!modalContent || !header) return null;
|
|
42
|
+
|
|
43
|
+
// Check if tab bar already exists
|
|
44
|
+
tabBarEl = document.getElementById('modalFileTabBar');
|
|
45
|
+
if (!tabBarEl) {
|
|
46
|
+
tabBarEl = document.createElement('div');
|
|
47
|
+
tabBarEl.id = 'modalFileTabBar';
|
|
48
|
+
tabBarEl.className = 'modal-file-tab-bar';
|
|
49
|
+
// Insert after the header
|
|
50
|
+
header.after(tabBarEl);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return tabBarEl;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Add a file to the tab bar or activate it if already open.
|
|
58
|
+
* Returns the tab index.
|
|
59
|
+
*/
|
|
60
|
+
export function addTab(file: any): number {
|
|
61
|
+
// Check if already open
|
|
62
|
+
const existing = openTabs.findIndex(t => t.path === file.path);
|
|
63
|
+
if (existing >= 0) {
|
|
64
|
+
activeTabIndex = existing;
|
|
65
|
+
renderTabBar();
|
|
66
|
+
return existing;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const tab: FileTab = {
|
|
70
|
+
path: file.path,
|
|
71
|
+
name: file.name || file.path?.split('/').pop() || 'untitled',
|
|
72
|
+
file,
|
|
73
|
+
rendered: { full: '', diff: '', full_raw: '' },
|
|
74
|
+
currentView: 'full',
|
|
75
|
+
scrollTop: 0,
|
|
76
|
+
originalContent: file.content || '',
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
openTabs.push(tab);
|
|
80
|
+
activeTabIndex = openTabs.length - 1;
|
|
81
|
+
renderTabBar();
|
|
82
|
+
_persistTabPaths();
|
|
83
|
+
return activeTabIndex;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Close a tab by index. Returns the new active tab index, or -1 if none left.
|
|
88
|
+
*/
|
|
89
|
+
export function closeTab(index: number, hasUnsavedChanges?: () => boolean): number {
|
|
90
|
+
if (index < 0 || index >= openTabs.length) return activeTabIndex;
|
|
91
|
+
|
|
92
|
+
// Check for unsaved changes on the active tab
|
|
93
|
+
if (index === activeTabIndex && hasUnsavedChanges?.()) {
|
|
94
|
+
if (!confirm('You have unsaved changes. Discard them?')) return activeTabIndex;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
openTabs.splice(index, 1);
|
|
98
|
+
|
|
99
|
+
if (openTabs.length === 0) {
|
|
100
|
+
activeTabIndex = -1;
|
|
101
|
+
renderTabBar();
|
|
102
|
+
return -1;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Adjust active index
|
|
106
|
+
if (activeTabIndex >= openTabs.length) {
|
|
107
|
+
activeTabIndex = openTabs.length - 1;
|
|
108
|
+
} else if (index < activeTabIndex) {
|
|
109
|
+
activeTabIndex--;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
renderTabBar();
|
|
113
|
+
_persistTabPaths();
|
|
114
|
+
return activeTabIndex;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Set the active tab and update tab bar rendering.
|
|
119
|
+
*/
|
|
120
|
+
export function setActiveTab(index: number): void {
|
|
121
|
+
if (index < 0 || index >= openTabs.length) return;
|
|
122
|
+
activeTabIndex = index;
|
|
123
|
+
renderTabBar();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Cycle to the next tab (Ctrl+Tab).
|
|
128
|
+
*/
|
|
129
|
+
export function nextTab(): number {
|
|
130
|
+
if (openTabs.length <= 1) return activeTabIndex;
|
|
131
|
+
activeTabIndex = (activeTabIndex + 1) % openTabs.length;
|
|
132
|
+
renderTabBar();
|
|
133
|
+
return activeTabIndex;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Cycle to the previous tab (Ctrl+Shift+Tab).
|
|
138
|
+
*/
|
|
139
|
+
export function prevTab(): number {
|
|
140
|
+
if (openTabs.length <= 1) return activeTabIndex;
|
|
141
|
+
activeTabIndex = (activeTabIndex - 1 + openTabs.length) % openTabs.length;
|
|
142
|
+
renderTabBar();
|
|
143
|
+
return activeTabIndex;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Clear all tabs (on modal close).
|
|
148
|
+
* Keeps saved tab paths in localStorage for session restore.
|
|
149
|
+
*/
|
|
150
|
+
export function clearTabs(): void {
|
|
151
|
+
// Persist current tab paths before clearing in-memory state
|
|
152
|
+
_persistTabPaths();
|
|
153
|
+
openTabs = [];
|
|
154
|
+
activeTabIndex = -1;
|
|
155
|
+
renderTabBar();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Fully clear tabs AND remove from localStorage (discard all) */
|
|
159
|
+
export function clearTabsAndStorage(): void {
|
|
160
|
+
openTabs = [];
|
|
161
|
+
activeTabIndex = -1;
|
|
162
|
+
renderTabBar();
|
|
163
|
+
try { localStorage.removeItem(TAB_STORAGE_KEY); } catch { }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Get previously saved tab paths for session restore */
|
|
167
|
+
export function getSavedTabPaths(): { paths: string[]; activeIndex: number } {
|
|
168
|
+
try {
|
|
169
|
+
const raw = localStorage.getItem(TAB_STORAGE_KEY);
|
|
170
|
+
if (!raw) return { paths: [], activeIndex: -1 };
|
|
171
|
+
return JSON.parse(raw);
|
|
172
|
+
} catch {
|
|
173
|
+
return { paths: [], activeIndex: -1 };
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Save current tab paths to localStorage */
|
|
178
|
+
function _persistTabPaths(): void {
|
|
179
|
+
try {
|
|
180
|
+
if (openTabs.length === 0) return; // Don't overwrite with empty on close
|
|
181
|
+
const data = {
|
|
182
|
+
paths: openTabs.map(t => t.path),
|
|
183
|
+
activeIndex: activeTabIndex,
|
|
184
|
+
};
|
|
185
|
+
localStorage.setItem(TAB_STORAGE_KEY, JSON.stringify(data));
|
|
186
|
+
} catch { }
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ─── Tab change callback ────────────────────────────
|
|
190
|
+
let _onTabChange: ((tab: FileTab, index: number) => void) | null = null;
|
|
191
|
+
let _onTabClose: ((index: number) => boolean) | null = null;
|
|
192
|
+
|
|
193
|
+
export function onTabChange(cb: (tab: FileTab, index: number) => void) {
|
|
194
|
+
_onTabChange = cb;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function onTabCloseRequest(cb: (index: number) => boolean) {
|
|
198
|
+
_onTabClose = cb;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ─── Language icons ─────────────────────────────────
|
|
202
|
+
function getFileIcon(name: string): string {
|
|
203
|
+
const ext = name.split('.').pop()?.toLowerCase() || '';
|
|
204
|
+
const icons: Record<string, string> = {
|
|
205
|
+
ts: '🔷', tsx: '⚛️', js: '🟨', jsx: '⚛️',
|
|
206
|
+
py: '🐍', css: '🎨', html: '🌐', json: '📋',
|
|
207
|
+
md: '📝', yaml: '⚙️', yml: '⚙️', toml: '⚙️',
|
|
208
|
+
sh: '🐚', sql: '🗃️', rs: '🦀', go: '🐹',
|
|
209
|
+
svg: '🖼️', png: '🖼️', jpg: '🖼️',
|
|
210
|
+
};
|
|
211
|
+
return icons[ext] || '📄';
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ─── Render tab bar ─────────────────────────────────
|
|
215
|
+
function renderTabBar() {
|
|
216
|
+
if (!tabBarEl) initTabBar();
|
|
217
|
+
if (!tabBarEl) return;
|
|
218
|
+
|
|
219
|
+
// Hide tab bar if no tabs or only 1
|
|
220
|
+
if (openTabs.length <= 1) {
|
|
221
|
+
tabBarEl.style.display = 'none';
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
tabBarEl.style.display = 'flex';
|
|
226
|
+
tabBarEl.innerHTML = '';
|
|
227
|
+
|
|
228
|
+
openTabs.forEach((tab, i) => {
|
|
229
|
+
const tabEl = document.createElement('div');
|
|
230
|
+
tabEl.className = `file-tab${i === activeTabIndex ? ' active' : ''}`;
|
|
231
|
+
tabEl.title = tab.path;
|
|
232
|
+
|
|
233
|
+
const icon = document.createElement('span');
|
|
234
|
+
icon.className = 'file-tab-icon';
|
|
235
|
+
icon.textContent = getFileIcon(tab.name);
|
|
236
|
+
tabEl.appendChild(icon);
|
|
237
|
+
|
|
238
|
+
const label = document.createElement('span');
|
|
239
|
+
label.className = 'file-tab-label';
|
|
240
|
+
label.textContent = tab.name;
|
|
241
|
+
tabEl.appendChild(label);
|
|
242
|
+
|
|
243
|
+
// Modified indicator
|
|
244
|
+
if (tab.currentView === 'edit' && tab.rendered.full_raw && tab.rendered.full_raw !== tab.originalContent) {
|
|
245
|
+
const dot = document.createElement('span');
|
|
246
|
+
dot.className = 'file-tab-modified';
|
|
247
|
+
dot.textContent = '●';
|
|
248
|
+
tabEl.appendChild(dot);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Close button
|
|
252
|
+
const close = document.createElement('button');
|
|
253
|
+
close.className = 'file-tab-close';
|
|
254
|
+
close.textContent = '×';
|
|
255
|
+
close.title = 'Close tab';
|
|
256
|
+
close.addEventListener('click', (e) => {
|
|
257
|
+
e.stopPropagation();
|
|
258
|
+
// Check callback for unsaved changes
|
|
259
|
+
if (_onTabClose && !_onTabClose(i)) return;
|
|
260
|
+
const newIndex = closeTab(i);
|
|
261
|
+
if (newIndex === -1) {
|
|
262
|
+
// No tabs left — close modal
|
|
263
|
+
const modal = document.getElementById('filePreviewModal');
|
|
264
|
+
if (modal) modal.classList.remove('active');
|
|
265
|
+
} else if (_onTabChange) {
|
|
266
|
+
_onTabChange(openTabs[newIndex], newIndex);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
tabEl.appendChild(close);
|
|
270
|
+
|
|
271
|
+
// Click to switch
|
|
272
|
+
tabEl.addEventListener('click', () => {
|
|
273
|
+
if (i === activeTabIndex) return;
|
|
274
|
+
|
|
275
|
+
// Save current tab's scroll position
|
|
276
|
+
const currentTab = openTabs[activeTabIndex];
|
|
277
|
+
if (currentTab) {
|
|
278
|
+
const bodyPre = document.getElementById('modalBodyPre');
|
|
279
|
+
if (bodyPre) currentTab.scrollTop = bodyPre.scrollTop;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
setActiveTab(i);
|
|
283
|
+
if (_onTabChange) _onTabChange(openTabs[i], i);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// Middle-click to close
|
|
287
|
+
tabEl.addEventListener('auxclick', (e) => {
|
|
288
|
+
if (e.button === 1) { // middle click
|
|
289
|
+
e.preventDefault();
|
|
290
|
+
if (_onTabClose && !_onTabClose(i)) return;
|
|
291
|
+
const newIndex = closeTab(i);
|
|
292
|
+
if (newIndex === -1) {
|
|
293
|
+
const modal = document.getElementById('filePreviewModal');
|
|
294
|
+
if (modal) modal.classList.remove('active');
|
|
295
|
+
} else if (_onTabChange) {
|
|
296
|
+
_onTabChange(openTabs[newIndex], newIndex);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
tabBarEl!.appendChild(tabEl);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// Diff button — only when 2+ tabs are open
|
|
305
|
+
if (openTabs.length >= 2) {
|
|
306
|
+
const diffBtn = document.createElement('button');
|
|
307
|
+
diffBtn.className = 'file-tab file-tab-diff-btn';
|
|
308
|
+
diffBtn.title = 'Compare files (Diff)';
|
|
309
|
+
diffBtn.innerHTML = '<span style="font-size:11px">⇄ Diff</span>';
|
|
310
|
+
diffBtn.style.cssText = 'margin-left: auto; opacity: 0.6; font-size: 11px; border: 1px dashed rgba(139,92,246,0.3);';
|
|
311
|
+
diffBtn.addEventListener('click', () => {
|
|
312
|
+
import('./tab-diff').then(({ openTabDiffSelector }) => openTabDiffSelector());
|
|
313
|
+
});
|
|
314
|
+
diffBtn.addEventListener('mouseenter', () => { diffBtn.style.opacity = '1'; });
|
|
315
|
+
diffBtn.addEventListener('mouseleave', () => { diffBtn.style.opacity = '0.6'; });
|
|
316
|
+
tabBarEl!.appendChild(diffBtn);
|
|
317
|
+
}
|
|
318
|
+
}
|