gitmaps 1.0.0 → 1.1.1
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 +265 -122
- package/app/[...slug]/page.client.tsx +1 -0
- package/app/[...slug]/page.tsx +6 -0
- package/app/[owner]/[repo]/page.client.tsx +5 -0
- package/app/[slug]/page.client.tsx +5 -0
- package/app/analytics.db +0 -0
- package/app/api/analytics/route.ts +64 -0
- package/app/api/auth/positions/route.ts +95 -33
- package/app/api/build-info/route.ts +19 -0
- package/app/api/chat/route.ts +13 -2
- package/app/api/manifest.json/route.ts +20 -0
- package/app/api/og-image/route.ts +14 -0
- package/app/api/pwa-icon/route.ts +14 -0
- package/app/api/repo/clone-stream/route.ts +20 -12
- package/app/api/repo/file-content/route.ts +73 -20
- package/app/api/repo/imports/route.ts +21 -3
- package/app/api/repo/list/route.ts +30 -0
- package/app/api/repo/load/route.test.ts +62 -0
- package/app/api/repo/load/route.ts +41 -1
- package/app/api/repo/pdf-thumb/route.ts +127 -0
- package/app/api/repo/resolve-slug/route.ts +51 -0
- package/app/api/repo/tree/route.ts +188 -104
- package/app/api/repo/upload/route.ts +6 -9
- package/app/api/sw.js/route.ts +70 -0
- package/app/api/version/route.ts +26 -0
- package/app/galaxy-canvas/page.client.tsx +2 -0
- package/app/galaxy-canvas/page.tsx +5 -0
- package/app/globals.css +5844 -4694
- package/app/icon.png +0 -0
- package/app/layout.tsx +1284 -467
- package/app/lib/auto-arrange.test.ts +158 -0
- package/app/lib/auto-arrange.ts +147 -0
- package/app/lib/canvas-export.ts +358 -358
- package/app/lib/canvas-text.ts +4 -72
- package/app/lib/canvas.ts +625 -564
- package/app/lib/card-arrangement.ts +21 -7
- package/app/lib/card-context-menu.tsx +2 -2
- package/app/lib/card-groups.ts +9 -2
- package/app/lib/cards.tsx +1361 -914
- package/app/lib/chat.tsx +65 -9
- package/app/lib/code-editor.ts +86 -2
- package/app/lib/connections.tsx +34 -43
- package/app/lib/context.test.ts +32 -0
- package/app/lib/context.ts +19 -3
- package/app/lib/cursor-sharing.ts +34 -0
- package/app/lib/events.tsx +76 -73
- package/app/lib/export-canvas.ts +287 -0
- package/app/lib/file-card-plugin.ts +148 -134
- package/app/lib/file-modal.tsx +49 -0
- package/app/lib/file-preview.ts +486 -400
- package/app/lib/github-import.test.ts +424 -0
- package/app/lib/global-search.ts +48 -27
- package/app/lib/initial-route-hydration.test.ts +283 -0
- package/app/lib/initial-route-hydration.ts +202 -0
- package/app/lib/landing-reset.test.ts +99 -0
- package/app/lib/landing-reset.ts +106 -0
- package/app/lib/landing-shell.test.ts +75 -0
- package/app/lib/large-repo-optimization.ts +37 -0
- package/app/lib/layers.tsx +17 -18
- package/app/lib/layout-snapshots.ts +320 -0
- package/app/lib/loading.test.ts +69 -0
- package/app/lib/loading.tsx +160 -45
- package/app/lib/mount-cleanup.test.ts +52 -0
- package/app/lib/mount-cleanup.ts +34 -0
- package/app/lib/mount-init.test.ts +123 -0
- package/app/lib/mount-init.ts +107 -0
- package/app/lib/mount-lifecycle.test.ts +39 -0
- package/app/lib/mount-lifecycle.ts +12 -0
- package/app/lib/mount-route-wiring.test.ts +87 -0
- package/app/lib/mount-route-wiring.ts +84 -0
- package/app/lib/multi-repo.ts +14 -0
- package/app/lib/onboarding-tutorial.ts +278 -0
- package/app/lib/perf-overlay.ts +78 -0
- package/app/lib/positions.ts +191 -122
- package/app/lib/recent-commits.test.ts +869 -0
- package/app/lib/recent-commits.ts +227 -0
- package/app/lib/repo-handoff.test.ts +23 -0
- package/app/lib/repo-handoff.ts +16 -0
- package/app/lib/repo-progressive.ts +119 -0
- package/app/lib/repo-select.test.ts +61 -0
- package/app/lib/repo-select.ts +74 -0
- package/app/lib/repo.tsx +1383 -977
- package/app/lib/role.ts +228 -0
- package/app/lib/route-catchall.test.ts +27 -0
- package/app/lib/route-repo-entry.test.ts +95 -0
- package/app/lib/route-repo-entry.ts +36 -0
- package/app/lib/router-contract.test.ts +22 -0
- package/app/lib/router-contract.ts +19 -0
- package/app/lib/shared-layout.test.ts +86 -0
- package/app/lib/shared-layout.ts +82 -0
- package/app/lib/shortcuts-panel.ts +2 -0
- package/app/lib/status-bar.test.ts +118 -0
- package/app/lib/status-bar.ts +365 -128
- package/app/lib/sync-controls.test.ts +43 -0
- package/app/lib/sync-controls.tsx +303 -0
- package/app/lib/test-dom.ts +145 -0
- package/app/lib/test-fixtures/router-contract/[...slug]/page.tsx +3 -0
- package/app/lib/test-fixtures/router-contract/api/health/route.ts +3 -0
- package/app/lib/test-fixtures/router-contract/api/version/route.ts +3 -0
- package/app/lib/test-fixtures/router-contract/galaxy-canvas/page.tsx +3 -0
- package/app/lib/test-fixtures/router-contract/page.tsx +3 -0
- package/app/lib/transclusion-smoke.test.ts +163 -0
- package/app/lib/tutorial.ts +301 -0
- package/app/lib/version.ts +93 -0
- package/app/lib/viewport-culling.ts +740 -728
- package/app/lib/virtual-files.ts +456 -0
- package/app/lib/webgl-text.ts +189 -0
- package/app/lib/{galaxydraw-bridge.ts → xydraw-bridge.ts} +485 -477
- package/app/lib/{galaxydraw.test.ts → xydraw.test.ts} +228 -229
- package/app/og-image.png +0 -0
- package/app/page.client.tsx +70 -215
- package/app/page.tsx +27 -92
- package/app/state/machine.js +13 -0
- package/banner.png +0 -0
- package/package.json +17 -8
- package/server.ts +11 -1
- package/app/api/connections/route.ts +0 -72
- package/app/api/positions/route.ts +0 -80
- package/app/api/repo/browse/route.ts +0 -55
- package/app/lib/pr-review.ts +0 -374
- package/packages/galaxydraw/README.md +0 -296
- package/packages/galaxydraw/banner.png +0 -0
- package/packages/galaxydraw/demo/build-static.ts +0 -100
- package/packages/galaxydraw/demo/client.ts +0 -154
- package/packages/galaxydraw/demo/dist/client.js +0 -8
- package/packages/galaxydraw/demo/index.html +0 -256
- package/packages/galaxydraw/demo/server.ts +0 -96
- package/packages/galaxydraw/dist/index.js +0 -984
- package/packages/galaxydraw/dist/index.js.map +0 -16
- 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 +0 -49
- package/packages/galaxydraw/perf.test.ts +0 -284
- package/packages/galaxydraw/src/core/cards.ts +0 -435
- package/packages/galaxydraw/src/core/engine.ts +0 -339
- package/packages/galaxydraw/src/core/events.ts +0 -81
- package/packages/galaxydraw/src/core/layout.ts +0 -136
- package/packages/galaxydraw/src/core/minimap.ts +0 -216
- package/packages/galaxydraw/src/core/state.ts +0 -177
- package/packages/galaxydraw/src/core/viewport.ts +0 -106
- package/packages/galaxydraw/src/galaxydraw.css +0 -166
- package/packages/galaxydraw/src/index.ts +0 -40
- package/packages/galaxydraw/tsconfig.json +0 -30
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { CanvasContext } from './context';
|
|
2
|
+
|
|
3
|
+
export function initTutorial(ctx: CanvasContext) {
|
|
4
|
+
if (typeof window === 'undefined') return;
|
|
5
|
+
|
|
6
|
+
// Check if tutorial was already completed
|
|
7
|
+
if (localStorage.getItem('gitcanvas:tutorial_completed') === 'true') {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Only start tutorial if we actually have a repo loaded (hiding landing overlay)
|
|
12
|
+
const landing = document.getElementById('landingOverlay');
|
|
13
|
+
if (landing && landing.style.display !== 'none') {
|
|
14
|
+
// We'll wait until a repo is loaded. We can do this safely by polling or listening.
|
|
15
|
+
// For simplicity, we just poll until the landing overlay is hidden.
|
|
16
|
+
const interval = setInterval(() => {
|
|
17
|
+
if (landing.style.display === 'none') {
|
|
18
|
+
clearInterval(interval);
|
|
19
|
+
startTutorialSequence(ctx);
|
|
20
|
+
}
|
|
21
|
+
}, 500);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
startTutorialSequence(ctx);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function startTutorialSequence(ctx: CanvasContext) {
|
|
29
|
+
const steps = [
|
|
30
|
+
{
|
|
31
|
+
title: "Welcome to GitMaps 🌌",
|
|
32
|
+
text: "You are now viewing your codebase as a 5-dimensional spatial canvas. Let's learn how to navigate it.",
|
|
33
|
+
target: null,
|
|
34
|
+
position: "center",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
title: "Exploring the Canvas ✋",
|
|
38
|
+
text: "Click and drag anywhere on the empty background to <b>pan</b> around the map.",
|
|
39
|
+
target: "#canvasViewport",
|
|
40
|
+
position: "center",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
title: "Deep Dive 🔍",
|
|
44
|
+
text: "Use your <b>scroll wheel</b> or trackpad to zoom in and out. The canvas will automatically reveal more details as you get closer.",
|
|
45
|
+
target: null,
|
|
46
|
+
position: "center",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
title: "Semantic Layers 🥞",
|
|
50
|
+
text: "Use the layer selector at the bottom to instantly switch your perspective. See the codebase by Files, Functions, or Tokens.",
|
|
51
|
+
target: "#layersBarContainer",
|
|
52
|
+
position: "above",
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
title: "Load Another Repo 📂",
|
|
56
|
+
text: "You can map any public GitHub repository instantly. Just use the search bar or import button here.",
|
|
57
|
+
target: ".repo-selector",
|
|
58
|
+
position: "right-of",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
title: "Select & Inspect 🎯",
|
|
62
|
+
text: "Click any file or function block to select it. Right click to access powerful tools.",
|
|
63
|
+
target: null,
|
|
64
|
+
position: "center",
|
|
65
|
+
}
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
let currentStep = 0;
|
|
69
|
+
|
|
70
|
+
const overlay = document.createElement('div');
|
|
71
|
+
overlay.id = 'tutorialOverlay';
|
|
72
|
+
overlay.className = 'tutorial-overlay';
|
|
73
|
+
|
|
74
|
+
// Inject styles explicitly so it's guaranteed to match the premium theme
|
|
75
|
+
const style = document.createElement('style');
|
|
76
|
+
style.innerHTML = `
|
|
77
|
+
.tutorial-overlay {
|
|
78
|
+
position: fixed;
|
|
79
|
+
top: 0; left: 0; right: 0; bottom: 0;
|
|
80
|
+
z-index: 100000;
|
|
81
|
+
pointer-events: none;
|
|
82
|
+
display: flex;
|
|
83
|
+
align-items: center;
|
|
84
|
+
justify-content: center;
|
|
85
|
+
transition: opacity 0.3s ease;
|
|
86
|
+
}
|
|
87
|
+
.tutorial-dialog {
|
|
88
|
+
pointer-events: auto;
|
|
89
|
+
background: rgba(15, 23, 42, 0.85);
|
|
90
|
+
backdrop-filter: blur(16px);
|
|
91
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
92
|
+
border-radius: 16px;
|
|
93
|
+
padding: 24px;
|
|
94
|
+
width: 320px;
|
|
95
|
+
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
|
96
|
+
color: #fff;
|
|
97
|
+
position: absolute;
|
|
98
|
+
transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1);
|
|
99
|
+
transform: scale(0.95) translateY(10px);
|
|
100
|
+
opacity: 0;
|
|
101
|
+
animation: tutorialSlide 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
|
102
|
+
}
|
|
103
|
+
@keyframes tutorialSlide {
|
|
104
|
+
to { transform: scale(1) translateY(0); opacity: 1; }
|
|
105
|
+
}
|
|
106
|
+
.tutorial-title {
|
|
107
|
+
font-size: 18px;
|
|
108
|
+
font-weight: 600;
|
|
109
|
+
margin: 0 0 12px 0;
|
|
110
|
+
background: linear-gradient(135deg, #a78bfa, #60a5fa);
|
|
111
|
+
-webkit-background-clip: text;
|
|
112
|
+
-webkit-text-fill-color: transparent;
|
|
113
|
+
}
|
|
114
|
+
.tutorial-text {
|
|
115
|
+
font-size: 14px;
|
|
116
|
+
color: #cbd5e1;
|
|
117
|
+
line-height: 1.5;
|
|
118
|
+
margin-bottom: 24px;
|
|
119
|
+
}
|
|
120
|
+
.tutorial-footer {
|
|
121
|
+
display: flex;
|
|
122
|
+
justify-content: space-between;
|
|
123
|
+
align-items: center;
|
|
124
|
+
border-top: 1px solid rgba(255, 255, 255, 0.05);
|
|
125
|
+
padding-top: 16px;
|
|
126
|
+
}
|
|
127
|
+
.tutorial-dots {
|
|
128
|
+
display: flex;
|
|
129
|
+
gap: 6px;
|
|
130
|
+
}
|
|
131
|
+
.tutorial-dot {
|
|
132
|
+
width: 6px; height: 6px;
|
|
133
|
+
border-radius: 50%;
|
|
134
|
+
background: rgba(255, 255, 255, 0.2);
|
|
135
|
+
transition: all 0.3s;
|
|
136
|
+
}
|
|
137
|
+
.tutorial-dot.active {
|
|
138
|
+
background: #a78bfa;
|
|
139
|
+
width: 16px;
|
|
140
|
+
border-radius: 4px;
|
|
141
|
+
}
|
|
142
|
+
.tutorial-btn {
|
|
143
|
+
background: linear-gradient(135deg, #a78bfa, #60a5fa);
|
|
144
|
+
border: none;
|
|
145
|
+
border-radius: 8px;
|
|
146
|
+
color: #fff;
|
|
147
|
+
font-weight: 600;
|
|
148
|
+
padding: 8px 16px;
|
|
149
|
+
cursor: pointer;
|
|
150
|
+
transition: all 0.2s;
|
|
151
|
+
box-shadow: 0 4px 12px rgba(96, 165, 250, 0.3);
|
|
152
|
+
}
|
|
153
|
+
.tutorial-btn:hover {
|
|
154
|
+
transform: translateY(-1px);
|
|
155
|
+
box-shadow: 0 6px 16px rgba(96, 165, 250, 0.4);
|
|
156
|
+
}
|
|
157
|
+
.tutorial-skip {
|
|
158
|
+
position: absolute;
|
|
159
|
+
top: 24px; right: 24px;
|
|
160
|
+
color: rgba(255, 255, 255, 0.4);
|
|
161
|
+
background: none; border: none;
|
|
162
|
+
font-size: 12px; cursor: pointer;
|
|
163
|
+
transition: color 0.2s;
|
|
164
|
+
pointer-events: auto;
|
|
165
|
+
}
|
|
166
|
+
.tutorial-skip:hover {
|
|
167
|
+
color: #fff;
|
|
168
|
+
}
|
|
169
|
+
.tutorial-highlight {
|
|
170
|
+
position: fixed;
|
|
171
|
+
box-shadow: 0 0 0 9999px rgba(0,0,0,0.7), 0 0 20px 5px rgba(167, 139, 250, 0.4);
|
|
172
|
+
border-radius: 12px;
|
|
173
|
+
pointer-events: none;
|
|
174
|
+
z-index: -1;
|
|
175
|
+
transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1);
|
|
176
|
+
border: 2px solid #a78bfa;
|
|
177
|
+
}
|
|
178
|
+
.tutorial-bg {
|
|
179
|
+
position: fixed;
|
|
180
|
+
top: 0; left: 0; right: 0; bottom: 0;
|
|
181
|
+
background: rgba(0, 0, 0, 0.7);
|
|
182
|
+
z-index: -2;
|
|
183
|
+
pointer-events: none;
|
|
184
|
+
transition: opacity 0.4s;
|
|
185
|
+
}
|
|
186
|
+
`;
|
|
187
|
+
document.head.appendChild(style);
|
|
188
|
+
|
|
189
|
+
const dialog = document.createElement('div');
|
|
190
|
+
dialog.className = 'tutorial-dialog';
|
|
191
|
+
|
|
192
|
+
const skipBtn = document.createElement('button');
|
|
193
|
+
skipBtn.className = 'tutorial-skip';
|
|
194
|
+
skipBtn.innerText = 'Skip';
|
|
195
|
+
skipBtn.onclick = finishTutorial;
|
|
196
|
+
|
|
197
|
+
const highlightBox = document.createElement('div');
|
|
198
|
+
highlightBox.className = 'tutorial-highlight';
|
|
199
|
+
highlightBox.style.display = 'none';
|
|
200
|
+
|
|
201
|
+
const bgShadow = document.createElement('div');
|
|
202
|
+
bgShadow.className = 'tutorial-bg';
|
|
203
|
+
bgShadow.style.display = 'block';
|
|
204
|
+
|
|
205
|
+
overlay.appendChild(bgShadow);
|
|
206
|
+
overlay.appendChild(highlightBox);
|
|
207
|
+
overlay.appendChild(skipBtn);
|
|
208
|
+
overlay.appendChild(dialog);
|
|
209
|
+
document.body.appendChild(overlay);
|
|
210
|
+
|
|
211
|
+
function renderStep() {
|
|
212
|
+
const step = steps[currentStep];
|
|
213
|
+
|
|
214
|
+
const dotsHtml = steps.map((_, i) =>
|
|
215
|
+
`<div class="tutorial-dot ${i === currentStep ? 'active' : ''}"></div>`
|
|
216
|
+
).join('');
|
|
217
|
+
|
|
218
|
+
dialog.innerHTML = `
|
|
219
|
+
<h3 class="tutorial-title">${step.title}</h3>
|
|
220
|
+
<div class="tutorial-text">${step.text}</div>
|
|
221
|
+
<div class="tutorial-footer">
|
|
222
|
+
<div class="tutorial-dots">${dotsHtml}</div>
|
|
223
|
+
<button class="tutorial-btn" id="tutorialNextBtn">${currentStep === steps.length - 1 ? 'Start Mapping 🚀' : 'Next'}</button>
|
|
224
|
+
</div>
|
|
225
|
+
`;
|
|
226
|
+
|
|
227
|
+
document.getElementById('tutorialNextBtn')!.onclick = nextStep;
|
|
228
|
+
|
|
229
|
+
// Handle target highlighting
|
|
230
|
+
if (step.target) {
|
|
231
|
+
const el = document.querySelector(step.target);
|
|
232
|
+
if (el) {
|
|
233
|
+
const rect = el.getBoundingClientRect();
|
|
234
|
+
highlightBox.style.display = 'block';
|
|
235
|
+
bgShadow.style.display = 'none';
|
|
236
|
+
// Add some padding to highlight
|
|
237
|
+
const p = 8;
|
|
238
|
+
highlightBox.style.top = `${rect.top - p}px`;
|
|
239
|
+
highlightBox.style.left = `${rect.left - p}px`;
|
|
240
|
+
highlightBox.style.width = `${rect.width + p * 2}px`;
|
|
241
|
+
highlightBox.style.height = `${rect.height + p * 2}px`;
|
|
242
|
+
|
|
243
|
+
if (step.position === 'left-of') {
|
|
244
|
+
// Position dialog to the left of the highlight
|
|
245
|
+
dialog.style.top = `${rect.top}px`;
|
|
246
|
+
dialog.style.left = `${rect.left - 340}px`;
|
|
247
|
+
dialog.style.bottom = 'auto';
|
|
248
|
+
dialog.style.right = 'auto';
|
|
249
|
+
dialog.style.transform = 'translateY(0)';
|
|
250
|
+
} else if (step.position === 'right-of') {
|
|
251
|
+
// Position dialog to the right of the highlight
|
|
252
|
+
dialog.style.top = `${rect.top}px`;
|
|
253
|
+
dialog.style.left = `${rect.right + 20}px`;
|
|
254
|
+
dialog.style.bottom = 'auto';
|
|
255
|
+
dialog.style.right = 'auto';
|
|
256
|
+
dialog.style.transform = 'translateY(0)';
|
|
257
|
+
} else if (step.position === 'above') {
|
|
258
|
+
dialog.style.top = `${Math.max(20, rect.top - 200)}px`;
|
|
259
|
+
dialog.style.left = `${rect.left + (rect.width / 2) - 160}px`;
|
|
260
|
+
dialog.style.bottom = 'auto';
|
|
261
|
+
dialog.style.right = 'auto';
|
|
262
|
+
dialog.style.transform = 'translateY(0)';
|
|
263
|
+
}
|
|
264
|
+
} else {
|
|
265
|
+
highlightBox.style.display = 'none';
|
|
266
|
+
bgShadow.style.display = 'block';
|
|
267
|
+
centerDialog();
|
|
268
|
+
}
|
|
269
|
+
} else {
|
|
270
|
+
highlightBox.style.display = 'none';
|
|
271
|
+
bgShadow.style.display = 'block';
|
|
272
|
+
centerDialog();
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function centerDialog() {
|
|
277
|
+
dialog.style.top = '50%';
|
|
278
|
+
dialog.style.left = '50%';
|
|
279
|
+
dialog.style.transform = 'translate(-50%, -50%)';
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function nextStep() {
|
|
283
|
+
if (currentStep < steps.length - 1) {
|
|
284
|
+
currentStep++;
|
|
285
|
+
renderStep();
|
|
286
|
+
} else {
|
|
287
|
+
finishTutorial();
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function finishTutorial() {
|
|
292
|
+
overlay.style.opacity = '0';
|
|
293
|
+
setTimeout(() => {
|
|
294
|
+
overlay.remove();
|
|
295
|
+
style.remove();
|
|
296
|
+
}, 300);
|
|
297
|
+
localStorage.setItem('gitcanvas:tutorial_completed', 'true');
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
renderStep();
|
|
301
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { showToast } from './utils';
|
|
2
|
+
|
|
3
|
+
let cachedCommit = '220aa78';
|
|
4
|
+
let cachedCommitDate = '2026-03-17';
|
|
5
|
+
|
|
6
|
+
export function getVersion(): string {
|
|
7
|
+
return cachedCommit;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function getVersionDate(): string {
|
|
11
|
+
return cachedCommitDate;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function fetchVersion(): Promise<{ commit: string; commitDate: string }> {
|
|
15
|
+
const bootstrapCommit = (window as any).__GITMAPS_BUILD_COMMIT__ || '';
|
|
16
|
+
const bootstrapDate = (window as any).__GITMAPS_BUILD_DATE__ || '';
|
|
17
|
+
|
|
18
|
+
if (bootstrapCommit) {
|
|
19
|
+
cachedCommit = bootstrapCommit;
|
|
20
|
+
cachedCommitDate = bootstrapDate;
|
|
21
|
+
return { commit: cachedCommit, commitDate: cachedCommitDate };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const response = await fetch('/api/build-info', { cache: 'no-store' });
|
|
26
|
+
if (!response.ok) throw new Error(await response.text());
|
|
27
|
+
const data = await response.json();
|
|
28
|
+
cachedCommit = data.commit || 'unknown';
|
|
29
|
+
cachedCommitDate = data.commitDate || '';
|
|
30
|
+
return { commit: cachedCommit, commitDate: cachedCommitDate };
|
|
31
|
+
} catch {
|
|
32
|
+
return { commit: cachedCommit, commitDate: cachedCommitDate };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function renderVersionBadge(): Promise<void> {
|
|
37
|
+
const existing = document.getElementById('versionBadge');
|
|
38
|
+
if (existing) existing.remove();
|
|
39
|
+
|
|
40
|
+
const badge = document.createElement('button');
|
|
41
|
+
badge.id = 'versionBadge';
|
|
42
|
+
badge.type = 'button';
|
|
43
|
+
badge.style.cssText = `
|
|
44
|
+
position: fixed;
|
|
45
|
+
top: 12px;
|
|
46
|
+
right: 12px;
|
|
47
|
+
padding: 6px 12px;
|
|
48
|
+
background: rgba(15, 23, 42, 0.88);
|
|
49
|
+
border: 1px solid rgba(124, 58, 237, 0.32);
|
|
50
|
+
border-radius: 8px;
|
|
51
|
+
font-size: 10px;
|
|
52
|
+
color: rgba(167, 139, 250, 0.9);
|
|
53
|
+
font-family: 'JetBrains Mono', monospace;
|
|
54
|
+
z-index: 10002;
|
|
55
|
+
backdrop-filter: blur(8px);
|
|
56
|
+
cursor: pointer;
|
|
57
|
+
transition: all 0.2s;
|
|
58
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.22);
|
|
59
|
+
`;
|
|
60
|
+
badge.innerHTML = `
|
|
61
|
+
<span style="opacity:0.64">GitMaps</span>
|
|
62
|
+
<span style="margin:0 6px">·</span>
|
|
63
|
+
<span id="versionBadgeCommit" style="color:#c4b5fd">loading...</span>
|
|
64
|
+
`;
|
|
65
|
+
badge.title = 'Loading build version...';
|
|
66
|
+
|
|
67
|
+
badge.addEventListener('click', async () => {
|
|
68
|
+
if (!cachedCommit || cachedCommit === 'unknown') {
|
|
69
|
+
showToast('Build commit not available yet', 'error');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
await navigator.clipboard.writeText(cachedCommit);
|
|
73
|
+
showToast(`Copied commit ${cachedCommit}`, 'success');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
badge.addEventListener('mouseenter', () => {
|
|
77
|
+
badge.style.background = 'rgba(15, 23, 42, 0.96)';
|
|
78
|
+
badge.style.borderColor = 'rgba(124, 58, 237, 0.62)';
|
|
79
|
+
});
|
|
80
|
+
badge.addEventListener('mouseleave', () => {
|
|
81
|
+
badge.style.background = 'rgba(15, 23, 42, 0.88)';
|
|
82
|
+
badge.style.borderColor = 'rgba(124, 58, 237, 0.32)';
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
document.body.appendChild(badge);
|
|
86
|
+
|
|
87
|
+
const { commit, commitDate } = await fetchVersion();
|
|
88
|
+
const commitEl = document.getElementById('versionBadgeCommit');
|
|
89
|
+
if (commitEl) commitEl.textContent = commit;
|
|
90
|
+
badge.title = commitDate
|
|
91
|
+
? `GitMaps ${commit}\nBuilt from commit on ${commitDate}\nClick to copy commit hash`
|
|
92
|
+
: `GitMaps ${commit}\nClick to copy commit hash`;
|
|
93
|
+
}
|