gitmaps 1.0.0 → 1.1.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 +5 -11
- package/app/[owner]/[repo]/page.client.tsx +5 -0
- package/app/[owner]/[repo]/page.tsx +6 -0
- package/app/[slug]/page.client.tsx +5 -0
- package/app/[slug]/page.tsx +6 -0
- package/app/api/manifest.json/route.ts +20 -0
- package/app/api/pwa-icon/route.ts +14 -0
- package/app/api/repo/clone-stream/route.ts +20 -12
- package/app/api/repo/imports/route.ts +21 -3
- package/app/api/repo/list/route.ts +30 -0
- package/app/api/repo/upload/route.ts +6 -9
- package/app/api/sw.js/route.ts +70 -0
- package/app/galaxy-canvas/page.client.tsx +2 -0
- package/app/galaxy-canvas/page.tsx +5 -0
- package/app/globals.css +477 -95
- package/app/icon.png +0 -0
- package/app/layout.tsx +30 -2
- package/app/lib/canvas-text.ts +4 -72
- package/app/lib/canvas.ts +1 -1
- 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 +3 -1
- package/app/lib/connections.tsx +34 -43
- package/app/lib/events.tsx +25 -0
- package/app/lib/file-card-plugin.ts +14 -0
- package/app/lib/file-preview.ts +68 -41
- package/app/lib/galaxydraw-bridge.ts +5 -0
- package/app/lib/global-search.ts +48 -27
- package/app/lib/layers.tsx +17 -18
- package/app/lib/perf-overlay.ts +78 -0
- package/app/lib/positions.ts +1 -1
- package/app/lib/repo.tsx +18 -8
- package/app/lib/shortcuts-panel.ts +2 -0
- package/app/lib/viewport-culling.ts +7 -0
- package/app/page.client.tsx +72 -18
- package/app/page.tsx +22 -86
- package/banner.png +0 -0
- package/package.json +2 -2
- package/packages/galaxydraw/README.md +2 -2
- package/packages/galaxydraw/package.json +1 -1
- package/server.ts +1 -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/app/page.tsx
CHANGED
|
@@ -51,10 +51,7 @@ async function getFeaturedRepos() {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
export default function Page() {
|
|
54
|
-
// Use fallback values synchronously — Melina does NOT support async page components.
|
|
55
|
-
// The GitHub star counts are fetched client-side or pre-cached.
|
|
56
54
|
const featuredRepos = _cachedRepos || FEATURED_REPOS_FALLBACK;
|
|
57
|
-
// Trigger async cache fill for next render
|
|
58
55
|
getFeaturedRepos().catch(() => { });
|
|
59
56
|
|
|
60
57
|
const langColors: Record<string, string> = {
|
|
@@ -98,7 +95,7 @@ export default function Page() {
|
|
|
98
95
|
<div className="landing-mesh"></div>
|
|
99
96
|
|
|
100
97
|
<div className="landing-content">
|
|
101
|
-
{/* Hero Section */}
|
|
98
|
+
{/* Hero Section — integrates the pitch message */}
|
|
102
99
|
<div className="landing-hero">
|
|
103
100
|
<div className="landing-badge">
|
|
104
101
|
<span className="badge-dot"></span>
|
|
@@ -107,39 +104,27 @@ export default function Page() {
|
|
|
107
104
|
|
|
108
105
|
<div className="landing-icon">
|
|
109
106
|
<svg viewBox="0 0 140 140" width="100" height="100" fill="none">
|
|
110
|
-
{/* Outer ring with dash animation */}
|
|
111
107
|
<circle cx="70" cy="70" r="64" stroke="url(#lgHero)" strokeWidth="1.5" opacity="0.2" strokeDasharray="6 8" className="ring-outer" />
|
|
112
108
|
<circle cx="70" cy="70" r="48" stroke="url(#lgHero)" strokeWidth="1.2" opacity="0.15" strokeDasharray="4 6" className="ring-mid" />
|
|
113
109
|
<circle cx="70" cy="70" r="30" stroke="url(#lgHero)" strokeWidth="1" opacity="0.1" />
|
|
114
|
-
|
|
115
|
-
{/* Center glow core */}
|
|
116
110
|
<circle cx="70" cy="70" r="8" fill="url(#lgHero)" opacity="0.9" className="core-glow" />
|
|
117
111
|
<circle cx="70" cy="70" r="4" fill="#fff" opacity="0.8" />
|
|
118
|
-
|
|
119
|
-
{/* Orbiting nodes with connection lines */}
|
|
120
112
|
<g className="orbit-group">
|
|
121
113
|
<line x1="70" y1="6" x2="70" y2="62" stroke="#a78bfa" strokeWidth="0.6" opacity="0.2" />
|
|
122
114
|
<circle cx="70" cy="6" r="5" fill="#a78bfa" opacity="0.85" className="node-orbit n1" />
|
|
123
115
|
<circle cx="70" cy="6" r="2" fill="#fff" opacity="0.6" />
|
|
124
|
-
|
|
125
116
|
<line x1="126" y1="46" x2="76" y2="68" stroke="#60a5fa" strokeWidth="0.6" opacity="0.2" />
|
|
126
117
|
<circle cx="126" cy="46" r="4.5" fill="#60a5fa" opacity="0.8" className="node-orbit n2" />
|
|
127
118
|
<circle cx="126" cy="46" r="1.8" fill="#fff" opacity="0.5" />
|
|
128
|
-
|
|
129
119
|
<line x1="112" y1="110" x2="74" y2="74" stroke="#34d399" strokeWidth="0.6" opacity="0.18" />
|
|
130
120
|
<circle cx="112" cy="110" r="4" fill="#34d399" opacity="0.75" className="node-orbit n3" />
|
|
131
|
-
|
|
132
121
|
<line x1="28" y1="110" x2="66" y2="74" stroke="#f472b6" strokeWidth="0.6" opacity="0.18" />
|
|
133
122
|
<circle cx="28" cy="110" r="4" fill="#f472b6" opacity="0.65" className="node-orbit n4" />
|
|
134
|
-
|
|
135
123
|
<line x1="14" y1="46" x2="64" y2="68" stroke="#fbbf24" strokeWidth="0.6" opacity="0.2" />
|
|
136
124
|
<circle cx="14" cy="46" r="3.5" fill="#fbbf24" opacity="0.7" className="node-orbit n5" />
|
|
137
|
-
|
|
138
|
-
{/* Extra subtle node */}
|
|
139
125
|
<circle cx="98" cy="20" r="2.5" fill="#818cf8" opacity="0.4" className="node-orbit n6" />
|
|
140
126
|
<circle cx="42" cy="20" r="2" fill="#c084fc" opacity="0.35" />
|
|
141
127
|
</g>
|
|
142
|
-
|
|
143
128
|
<defs>
|
|
144
129
|
<linearGradient id="lgHero" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
145
130
|
<stop offset="0%" stopColor="#a78bfa" />
|
|
@@ -159,89 +144,35 @@ export default function Page() {
|
|
|
159
144
|
<span className="title-maps">Maps</span>
|
|
160
145
|
</h1>
|
|
161
146
|
<p className="landing-subtitle">
|
|
162
|
-
See your codebase in
|
|
163
|
-
<span className="subtitle-accent">Every file
|
|
147
|
+
Transcend the file tree. See your codebase in four dimensions.<br />
|
|
148
|
+
<span className="subtitle-accent">Every file rendered on an infinite canvas —</span><br />
|
|
149
|
+
<span className="subtitle-accent">review AI-generated code spatially instead of one-file-at-a-time.</span>
|
|
164
150
|
</p>
|
|
165
|
-
</div>
|
|
166
151
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
<div className="feature-
|
|
171
|
-
<div className="feature-icon
|
|
172
|
-
|
|
173
|
-
<rect x="3" y="3" width="7" height="7" rx="1" />
|
|
174
|
-
<rect x="14" y="3" width="7" height="7" rx="1" />
|
|
175
|
-
<rect x="3" y="14" width="7" height="7" rx="1" />
|
|
176
|
-
<rect x="14" y="14" width="7" height="7" rx="1" />
|
|
177
|
-
</svg>
|
|
178
|
-
</div>
|
|
179
|
-
<h3>Spatial Code Review</h3>
|
|
180
|
-
<p>Every file becomes a card on an infinite 2D canvas. Arrange, group, and navigate them spatially — not as a flat list.</p>
|
|
181
|
-
</div>
|
|
182
|
-
|
|
183
|
-
<div className="feature-card fc-2">
|
|
184
|
-
<div className="feature-glow"></div>
|
|
185
|
-
<div className="feature-icon-wrap">
|
|
186
|
-
<svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
187
|
-
<circle cx="6" cy="6" r="3" />
|
|
188
|
-
<circle cx="18" cy="18" r="3" />
|
|
189
|
-
<path d="M8.59 13.51l6.83 6.83" />
|
|
190
|
-
<line x1="21" y1="3" x2="14" y2="10" />
|
|
191
|
-
<polyline points="21 10 21 3 14 3" />
|
|
192
|
-
</svg>
|
|
193
|
-
</div>
|
|
194
|
-
<h3>Interwingled Files</h3>
|
|
195
|
-
<p>Draw connections between related lines across files. See how <code>auth.ts:42</code> relates to <code>middleware.ts:15</code> — visually.</p>
|
|
196
|
-
</div>
|
|
197
|
-
|
|
198
|
-
<div className="feature-card fc-3">
|
|
199
|
-
<div className="feature-glow"></div>
|
|
200
|
-
<div className="feature-icon-wrap">
|
|
201
|
-
<svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
202
|
-
<polygon points="12 2 22 8.5 22 15.5 12 22 2 15.5 2 8.5 12 2" />
|
|
203
|
-
<line x1="12" y1="22" x2="12" y2="15.5" />
|
|
204
|
-
<polyline points="22 8.5 12 15.5 2 8.5" />
|
|
205
|
-
<polyline points="2 15.5 12 8.5 22 15.5" />
|
|
206
|
-
<line x1="12" y1="2" x2="12" y2="8.5" />
|
|
207
|
-
</svg>
|
|
208
|
-
</div>
|
|
209
|
-
<h3>Layers of Focus</h3>
|
|
210
|
-
<p>Create layers to isolate subsets of files. "Auth layer" shows only auth-related changes. Switch context without losing position.</p>
|
|
211
|
-
</div>
|
|
212
|
-
|
|
213
|
-
<div className="feature-card fc-4">
|
|
214
|
-
<div className="feature-glow"></div>
|
|
215
|
-
<div className="feature-icon-wrap">
|
|
216
|
-
<svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
217
|
-
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" />
|
|
218
|
-
<polyline points="7.5 4.21 12 6.81 16.5 4.21" />
|
|
219
|
-
<polyline points="7.5 19.79 7.5 14.6 3 12" />
|
|
220
|
-
<polyline points="21 12 16.5 14.6 16.5 19.79" />
|
|
221
|
-
<polyline points="3.27 6.96 12 12.01 20.73 6.96" />
|
|
222
|
-
<line x1="12" y1="22.08" x2="12" y2="12" />
|
|
223
|
-
</svg>
|
|
224
|
-
</div>
|
|
225
|
-
<h3>Clone & Explore</h3>
|
|
226
|
-
<p>Paste any GitHub URL and explore it instantly. Real-time clone progress, smart caching, and full commit timeline navigation.</p>
|
|
152
|
+
{/* Key features inline */}
|
|
153
|
+
<div className="landing-features">
|
|
154
|
+
<div className="feature-pill"><span className="feature-icon">📐</span> Spatial canvas</div>
|
|
155
|
+
<div className="feature-pill"><span className="feature-icon">🗂️</span> Focus layers</div>
|
|
156
|
+
<div className="feature-pill"><span className="feature-icon">🔗</span> File connections</div>
|
|
157
|
+
<div className="feature-pill"><span className="feature-icon">⏱️</span> Git timeline</div>
|
|
227
158
|
</div>
|
|
228
159
|
</div>
|
|
229
160
|
|
|
230
|
-
{/* Featured Repos —
|
|
161
|
+
{/* Featured Repos — Open in browser */}
|
|
231
162
|
<div className="landing-repos">
|
|
232
163
|
<div className="repos-header">
|
|
233
164
|
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
234
165
|
<path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4" />
|
|
235
166
|
<path d="M9 18c-4.51 2-5-2-7-2" />
|
|
236
167
|
</svg>
|
|
237
|
-
<span>Explore popular repositories</span>
|
|
168
|
+
<span>Explore popular repositories — click to open instantly</span>
|
|
238
169
|
</div>
|
|
239
170
|
<div className="repos-grid">
|
|
240
171
|
{featuredRepos.map((repo) => (
|
|
241
|
-
<
|
|
172
|
+
<a
|
|
242
173
|
key={repo.name}
|
|
243
174
|
className="repo-card-btn"
|
|
244
|
-
|
|
175
|
+
href={`/${repo.name}`}
|
|
245
176
|
id={`landing-repo-${repo.name.replace('/', '-')}`}
|
|
246
177
|
>
|
|
247
178
|
<div className="repo-card-name">{repo.name.split('/')[1]}</div>
|
|
@@ -253,7 +184,7 @@ export default function Page() {
|
|
|
253
184
|
</span>
|
|
254
185
|
<span className="repo-card-stars">★ {repo.stars}</span>
|
|
255
186
|
</div>
|
|
256
|
-
</
|
|
187
|
+
</a>
|
|
257
188
|
))}
|
|
258
189
|
</div>
|
|
259
190
|
</div>
|
|
@@ -262,13 +193,18 @@ export default function Page() {
|
|
|
262
193
|
<div className="landing-cta">
|
|
263
194
|
<div className="cta-arrows">
|
|
264
195
|
<span className="cta-arrow a1">←</span>
|
|
265
|
-
<span className="cta-text">Select a
|
|
196
|
+
<span className="cta-text">Select a repo from the sidebar, or click a card above</span>
|
|
266
197
|
</div>
|
|
267
|
-
<div className="cta-or">
|
|
198
|
+
<div className="cta-or">Import any GitHub repo with the sidebar button</div>
|
|
268
199
|
</div>
|
|
269
200
|
|
|
270
201
|
{/* Stats Row */}
|
|
271
202
|
<div className="landing-stats">
|
|
203
|
+
<div className="landing-stat">
|
|
204
|
+
<span className="stat-num">4D</span>
|
|
205
|
+
<span className="stat-desc">Dimensional Space</span>
|
|
206
|
+
</div>
|
|
207
|
+
<div className="stat-divider"></div>
|
|
272
208
|
<div className="landing-stat">
|
|
273
209
|
<span className="stat-num">∞</span>
|
|
274
210
|
<span className="stat-desc">Infinite Canvas</span>
|
package/banner.png
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gitmaps",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"gitmaps": "cli.ts"
|
|
7
7
|
},
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "https://github.com/7flash/
|
|
10
|
+
"url": "https://github.com/7flash/gitmaps"
|
|
11
11
|
},
|
|
12
12
|
"homepage": "https://gitmaps.xyz",
|
|
13
13
|
"workspaces": [
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
</p>
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
|
-
<a href="https://7flash.github.io/
|
|
10
|
+
<a href="https://7flash.github.io/gitmaps/"><img src="https://img.shields.io/badge/🌌_Live_Demo-galaxydraw-9382ff?style=flat-square" alt="Live Demo"></a>
|
|
11
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
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
13
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg?style=flat-square" alt="License"></a>
|
|
@@ -275,7 +275,7 @@ Total: ~1,200 lines of engine code. Zero dependencies.
|
|
|
275
275
|
|
|
276
276
|
## 🚀 Used By
|
|
277
277
|
|
|
278
|
-
- **[GitMaps](https://github.com/7flash/
|
|
278
|
+
- **[GitMaps](https://github.com/7flash/gitmaps)** — Repository visualization on an infinite canvas. Uses `advanced` mode with FileCardPlugin + DiffCardPlugin. Renders 6,800+ file cards with viewport culling (~35ms).
|
|
279
279
|
- **[WARMAPS](https://github.com/7flash/starwar)** — Real-time geopolitical intelligence dashboard. Uses `simple` mode with WarmapsContainerPlugin for MapLibre/WebSocket feed passthrough.
|
|
280
280
|
|
|
281
281
|
## 🧪 Testing
|
package/server.ts
CHANGED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { measure } from 'measure-fn';
|
|
2
|
-
import { Database, z } from 'sqlite-zod-orm';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
|
|
5
|
-
const dbPath = path.join(process.cwd(), 'db', 'connections_v1.sqlite');
|
|
6
|
-
|
|
7
|
-
const db = new Database(dbPath, {
|
|
8
|
-
connections: z.object({
|
|
9
|
-
conn_id: z.string(),
|
|
10
|
-
source_file: z.string(),
|
|
11
|
-
source_line_start: z.number(),
|
|
12
|
-
source_line_end: z.number(),
|
|
13
|
-
target_file: z.string(),
|
|
14
|
-
target_line_start: z.number(),
|
|
15
|
-
target_line_end: z.number(),
|
|
16
|
-
comment: z.string().default(''),
|
|
17
|
-
created_at: z.string().default(() => new Date().toISOString()),
|
|
18
|
-
}),
|
|
19
|
-
}, {
|
|
20
|
-
indexes: { connections: ['source_file', 'target_file'] },
|
|
21
|
-
reactive: false,
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
export async function GET() {
|
|
25
|
-
return measure('api:connections:get', async () => {
|
|
26
|
-
try {
|
|
27
|
-
const connections = db.connections.select().all();
|
|
28
|
-
return Response.json({ connections });
|
|
29
|
-
} catch (error: any) {
|
|
30
|
-
console.error('api:connections:get:error', error);
|
|
31
|
-
return new Response(`Error: ${error.message}`, { status: 500 });
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export async function POST(req: Request) {
|
|
37
|
-
return measure('api:connections:save', async () => {
|
|
38
|
-
try {
|
|
39
|
-
const body = await req.json();
|
|
40
|
-
const { connections } = body;
|
|
41
|
-
|
|
42
|
-
if (!Array.isArray(connections)) {
|
|
43
|
-
return new Response('connections array is required', { status: 400 });
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Delete all existing, then re-insert
|
|
47
|
-
const existing = db.connections.select().all();
|
|
48
|
-
for (const e of existing) {
|
|
49
|
-
e.delete();
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
for (const conn of connections) {
|
|
53
|
-
db.connections.insert({
|
|
54
|
-
conn_id: conn.id,
|
|
55
|
-
source_file: conn.sourceFile,
|
|
56
|
-
source_line_start: conn.sourceLineStart,
|
|
57
|
-
source_line_end: conn.sourceLineEnd,
|
|
58
|
-
target_file: conn.targetFile,
|
|
59
|
-
target_line_start: conn.targetLineStart,
|
|
60
|
-
target_line_end: conn.targetLineEnd,
|
|
61
|
-
comment: conn.comment || '',
|
|
62
|
-
created_at: conn.createdAt || new Date().toISOString(),
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return Response.json({ success: true, count: connections.length });
|
|
67
|
-
} catch (error: any) {
|
|
68
|
-
console.error('api:connections:save:error', error);
|
|
69
|
-
return new Response(`Error: ${error.message}`, { status: 500 });
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { measure, measureSync } from 'measure-fn';
|
|
2
|
-
import { Database, z } from 'sqlite-zod-orm';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
|
|
5
|
-
const dbPath = path.join(process.cwd(), 'db', 'positions_v3.sqlite');
|
|
6
|
-
|
|
7
|
-
const db = new Database(dbPath, {
|
|
8
|
-
positions: z.object({
|
|
9
|
-
commit_hash: z.string(),
|
|
10
|
-
file_path: z.string(),
|
|
11
|
-
x: z.number(),
|
|
12
|
-
y: z.number(),
|
|
13
|
-
width: z.number().optional(),
|
|
14
|
-
height: z.number().optional(),
|
|
15
|
-
}),
|
|
16
|
-
}, {
|
|
17
|
-
indexes: { positions: ['commit_hash'] },
|
|
18
|
-
reactive: false,
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
export async function GET(req: Request) {
|
|
22
|
-
return measure('api:positions:get', async () => {
|
|
23
|
-
try {
|
|
24
|
-
const url = new URL(req.url);
|
|
25
|
-
const commitHash = url.searchParams.get('commit');
|
|
26
|
-
|
|
27
|
-
const query = commitHash
|
|
28
|
-
? db.positions.select().where({ commit_hash: commitHash })
|
|
29
|
-
: db.positions.select();
|
|
30
|
-
|
|
31
|
-
const positions = query.all();
|
|
32
|
-
|
|
33
|
-
// Convert to map format
|
|
34
|
-
const positionMap: Record<string, { x: number; y: number; width?: number; height?: number }> = {};
|
|
35
|
-
for (const pos of positions) {
|
|
36
|
-
positionMap[`${pos.commit_hash}:${pos.file_path}`] = {
|
|
37
|
-
x: pos.x,
|
|
38
|
-
y: pos.y,
|
|
39
|
-
width: pos.width ?? undefined,
|
|
40
|
-
height: pos.height ?? undefined,
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return Response.json(positionMap);
|
|
45
|
-
} catch (error: any) {
|
|
46
|
-
console.error('api:positions:get:error', error);
|
|
47
|
-
return new Response(`Error: ${error.message}`, { status: 500 });
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export async function POST(req: Request) {
|
|
53
|
-
return measure('api:positions:save', async () => {
|
|
54
|
-
try {
|
|
55
|
-
const body = await req.json();
|
|
56
|
-
const { commitHash, filePath, x, y, width, height } = body;
|
|
57
|
-
|
|
58
|
-
if (!commitHash || !filePath || x === undefined || y === undefined) {
|
|
59
|
-
return new Response('commitHash, filePath, x, and y are required', { status: 400 });
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
db.positions.upsert(
|
|
63
|
-
{ commit_hash: commitHash, file_path: filePath },
|
|
64
|
-
{
|
|
65
|
-
commit_hash: commitHash,
|
|
66
|
-
file_path: filePath,
|
|
67
|
-
x,
|
|
68
|
-
y,
|
|
69
|
-
width: width || undefined,
|
|
70
|
-
height: height || undefined,
|
|
71
|
-
},
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
return Response.json({ success: true });
|
|
75
|
-
} catch (error: any) {
|
|
76
|
-
console.error('api:positions:save:error', error);
|
|
77
|
-
return new Response(`Error: ${error.message}`, { status: 500 });
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { measure } from 'measure-fn';
|
|
2
|
-
import { exec } from 'child_process';
|
|
3
|
-
import { promisify } from 'util';
|
|
4
|
-
import { blockInProduction } from '../validate-path';
|
|
5
|
-
|
|
6
|
-
const execAsync = promisify(exec);
|
|
7
|
-
|
|
8
|
-
export async function POST(req: Request) {
|
|
9
|
-
const blocked = blockInProduction('Folder browser');
|
|
10
|
-
if (blocked) return blocked;
|
|
11
|
-
|
|
12
|
-
return measure('api:repo:browse', async () => {
|
|
13
|
-
try {
|
|
14
|
-
// Use PowerShell with -EncodedCommand to avoid quoting issues
|
|
15
|
-
// Use TopMost Form to ensure the dialog pops up ABOVE the browser window
|
|
16
|
-
const psScript = `
|
|
17
|
-
Add-Type -AssemblyName System.Windows.Forms
|
|
18
|
-
$dialog = New-Object System.Windows.Forms.FolderBrowserDialog
|
|
19
|
-
$dialog.Description = "Select Git Repository"
|
|
20
|
-
$dialog.ShowNewFolderButton = $false
|
|
21
|
-
|
|
22
|
-
$form = New-Object System.Windows.Forms.Form
|
|
23
|
-
$form.TopMost = $true
|
|
24
|
-
$result = $dialog.ShowDialog($form)
|
|
25
|
-
|
|
26
|
-
if ($result -eq [System.Windows.Forms.DialogResult]::OK) {
|
|
27
|
-
Write-Output $dialog.SelectedPath
|
|
28
|
-
} else {
|
|
29
|
-
Write-Output ""
|
|
30
|
-
}
|
|
31
|
-
$form.Dispose()
|
|
32
|
-
`.trim();
|
|
33
|
-
|
|
34
|
-
const encoded = Buffer.from(psScript, 'utf16le').toString('base64');
|
|
35
|
-
|
|
36
|
-
// Use async exec so we don't block the entire melina server (which freezes the UI's live updates)
|
|
37
|
-
const { stdout } = await execAsync(
|
|
38
|
-
`powershell -sta -WindowStyle Hidden -NoProfile -EncodedCommand ${encoded}`,
|
|
39
|
-
{ encoding: 'utf-8', timeout: 86400000 }
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
const selected = stdout.trim();
|
|
43
|
-
|
|
44
|
-
if (!selected) {
|
|
45
|
-
return Response.json({ cancelled: true, path: null });
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return Response.json({ cancelled: false, path: selected });
|
|
49
|
-
} catch (error: any) {
|
|
50
|
-
console.error('api:repo:browse:error', error);
|
|
51
|
-
// Even if cancelled or errored, don't crash the server. Let UI reset the dropdown.
|
|
52
|
-
return Response.json({ cancelled: true, path: null, error: error.message });
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
}
|