gitmaps 1.1.17 → 1.1.19
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/app/analytics.db
CHANGED
|
Binary file
|
|
@@ -76,6 +76,25 @@ describe('low zoom preview helpers', () => {
|
|
|
76
76
|
expect(deleted[0]?.height).toBe(1);
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
+
test('derives diff markers from hunks when line maps are absent', () => {
|
|
80
|
+
const markers = collectPreviewDiffMarkers({
|
|
81
|
+
hunks: [
|
|
82
|
+
{
|
|
83
|
+
newStart: 8,
|
|
84
|
+
lines: [
|
|
85
|
+
{ type: 'ctx', content: 'a' },
|
|
86
|
+
{ type: 'del', content: 'old-1' },
|
|
87
|
+
{ type: 'del', content: 'old-2' },
|
|
88
|
+
{ type: 'add', content: 'new-1' },
|
|
89
|
+
{ type: 'ctx', content: 'b' },
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
}, 20);
|
|
94
|
+
expect(markers.some((m) => m.color === '#22c55e')).toBe(true);
|
|
95
|
+
expect(markers.some((m) => m.color === '#ef4444')).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
|
|
79
98
|
test('title typography is larger than body typography for readability', () => {
|
|
80
99
|
const scale = getLowZoomScale(0.18);
|
|
81
100
|
expect(scale.titleFont).toBeGreaterThan(scale.bodyFont);
|
|
@@ -137,8 +137,8 @@ export function getPreviewScrollMetrics(file: any, height: number, zoom: number,
|
|
|
137
137
|
export function collectPreviewDiffMarkers(file: any, totalLines: number) {
|
|
138
138
|
const markers: Array<{ ratio: number; color: string; height?: number }> = [];
|
|
139
139
|
const safeTotal = Math.max(1, totalLines);
|
|
140
|
-
|
|
141
|
-
|
|
140
|
+
let added = file?.addedLines instanceof Set ? Array.from(file.addedLines) : [];
|
|
141
|
+
let deletedBefore = file?.deletedBeforeLine instanceof Map ? Array.from(file.deletedBeforeLine.keys()) : [];
|
|
142
142
|
|
|
143
143
|
if (file?.status === 'added') {
|
|
144
144
|
markers.push({ ratio: 0, color: '#22c55e', height: 1 });
|
|
@@ -149,6 +149,38 @@ export function collectPreviewDiffMarkers(file: any, totalLines: number) {
|
|
|
149
149
|
return markers;
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
+
if ((added.length === 0 && deletedBefore.length === 0) && Array.isArray(file?.hunks)) {
|
|
153
|
+
const derivedAdded: number[] = [];
|
|
154
|
+
const derivedDeleted: number[] = [];
|
|
155
|
+
for (const hunk of file.hunks) {
|
|
156
|
+
let newLine = hunk?.newStart || 1;
|
|
157
|
+
let sawPendingDelete = false;
|
|
158
|
+
for (const line of hunk?.lines || []) {
|
|
159
|
+
if (line?.type === 'add') {
|
|
160
|
+
derivedAdded.push(newLine);
|
|
161
|
+
if (sawPendingDelete) {
|
|
162
|
+
derivedDeleted.push(newLine);
|
|
163
|
+
sawPendingDelete = false;
|
|
164
|
+
}
|
|
165
|
+
newLine += 1;
|
|
166
|
+
} else if (line?.type === 'del') {
|
|
167
|
+
sawPendingDelete = true;
|
|
168
|
+
} else {
|
|
169
|
+
if (sawPendingDelete) {
|
|
170
|
+
derivedDeleted.push(newLine);
|
|
171
|
+
sawPendingDelete = false;
|
|
172
|
+
}
|
|
173
|
+
newLine += 1;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (sawPendingDelete) {
|
|
177
|
+
derivedDeleted.push(newLine);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
added = derivedAdded;
|
|
181
|
+
deletedBefore = derivedDeleted;
|
|
182
|
+
}
|
|
183
|
+
|
|
152
184
|
for (const line of added) {
|
|
153
185
|
markers.push({ ratio: Math.max(0, Math.min(1, (line - 1) / safeTotal)), color: '#22c55e' });
|
|
154
186
|
}
|
|
@@ -247,31 +279,6 @@ export function renderLowZoomPreviewCanvas(
|
|
|
247
279
|
ctx.fillText(line, leftInset, y);
|
|
248
280
|
});
|
|
249
281
|
|
|
250
|
-
const trackX = width - scrollbarWidth - 5;
|
|
251
|
-
const markerX = trackX - markerLaneWidth - 4;
|
|
252
|
-
const trackY = scrollMetrics.trackPadding;
|
|
253
|
-
const trackHeight = scrollMetrics.trackHeight;
|
|
254
|
-
|
|
255
|
-
ctx.fillStyle = 'rgba(255,255,255,0.08)';
|
|
256
|
-
roundRect(ctx, markerX, trackY, markerLaneWidth, trackHeight, 3);
|
|
257
|
-
ctx.fill();
|
|
258
|
-
|
|
259
|
-
ctx.fillStyle = 'rgba(255,255,255,0.12)';
|
|
260
|
-
roundRect(ctx, trackX, trackY, scrollbarWidth, trackHeight, scrollbarWidth / 2);
|
|
261
|
-
ctx.fill();
|
|
262
|
-
|
|
263
|
-
const markers = collectPreviewDiffMarkers(file, scrollMetrics.totalLines);
|
|
264
|
-
for (const marker of markers) {
|
|
265
|
-
const markerHeight = marker.height === 1 ? trackHeight : 5;
|
|
266
|
-
const y = marker.height === 1 ? trackY : trackY + marker.ratio * Math.max(0, trackHeight - markerHeight);
|
|
267
|
-
ctx.fillStyle = marker.color;
|
|
268
|
-
roundRect(ctx, markerX, Math.max(trackY, y), markerLaneWidth, markerHeight, 2);
|
|
269
|
-
ctx.fill();
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
ctx.fillStyle = 'rgba(196,181,253,0.96)';
|
|
273
|
-
roundRect(ctx, trackX, scrollMetrics.thumbY, scrollbarWidth, scrollMetrics.thumbHeight, scrollbarWidth / 2);
|
|
274
|
-
ctx.fill();
|
|
275
282
|
}
|
|
276
283
|
|
|
277
284
|
function trimToWidth(ctx: CanvasRenderingContext2D, text: string, maxWidth: number) {
|
|
@@ -170,6 +170,12 @@ function getSavedScrollTop(ctx: CanvasContext, path: string): number {
|
|
|
170
170
|
return saved?.x || 0;
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
+
function resolvePreviewFile(ctx: CanvasContext, path: string, fallback?: any) {
|
|
174
|
+
const commitFile = ctx.commitFilesData?.find(f => f.path === path) || null;
|
|
175
|
+
const allFile = ctx.allFilesData?.find(f => f.path === path) || null;
|
|
176
|
+
return commitFile || fallback || allFile || null;
|
|
177
|
+
}
|
|
178
|
+
|
|
173
179
|
/**
|
|
174
180
|
* Create a lightweight pill placeholder for a file.
|
|
175
181
|
* ~3 DOM nodes vs ~100+ for a full card = massive perf win at low zoom.
|
|
@@ -206,9 +212,14 @@ function createPillCard(ctx: CanvasContext, file: any, path: string, x: number,
|
|
|
206
212
|
|
|
207
213
|
const diffOverlay = document.createElement('div');
|
|
208
214
|
diffOverlay.className = 'file-pill-diff-overlay';
|
|
209
|
-
diffOverlay.style.cssText = 'position:absolute;top:10px;right:
|
|
215
|
+
diffOverlay.style.cssText = 'position:absolute;top:10px;right:26px;bottom:10px;width:12px;pointer-events:none;z-index:2;';
|
|
210
216
|
pill.appendChild(diffOverlay);
|
|
211
217
|
|
|
218
|
+
const scrollOverlay = document.createElement('div');
|
|
219
|
+
scrollOverlay.className = 'file-pill-scroll-overlay';
|
|
220
|
+
scrollOverlay.style.cssText = 'position:absolute;top:10px;right:15px;bottom:10px;width:9px;pointer-events:auto;z-index:3;';
|
|
221
|
+
pill.appendChild(scrollOverlay);
|
|
222
|
+
|
|
212
223
|
(pill as any)._fileData = file;
|
|
213
224
|
updatePillCardLayout(ctx, pill, zoom, isChanged);
|
|
214
225
|
|
|
@@ -229,7 +240,8 @@ function updatePillCardLayout(ctx: CanvasContext, pill: HTMLElement, zoom: numbe
|
|
|
229
240
|
const path = pill.dataset.path || '';
|
|
230
241
|
const canvas = pill.querySelector('.file-pill-canvas') as HTMLCanvasElement | null;
|
|
231
242
|
const diffOverlay = pill.querySelector('.file-pill-diff-overlay') as HTMLElement | null;
|
|
232
|
-
const
|
|
243
|
+
const scrollOverlay = pill.querySelector('.file-pill-scroll-overlay') as HTMLElement | null;
|
|
244
|
+
const file = resolvePreviewFile(ctx, path, (pill as any)._fileData);
|
|
233
245
|
const changed = isChanged ?? pill.dataset.changed === 'true';
|
|
234
246
|
const scrollTop = getSavedScrollTop(ctx, path);
|
|
235
247
|
pill.dataset.zoomBucket = zoom.toFixed(3);
|
|
@@ -247,25 +259,27 @@ function updatePillCardLayout(ctx: CanvasContext, pill: HTMLElement, zoom: numbe
|
|
|
247
259
|
isChanged: changed,
|
|
248
260
|
});
|
|
249
261
|
|
|
262
|
+
const metrics = getPreviewScrollMetrics(file, h, zoom, scrollTop);
|
|
263
|
+
|
|
250
264
|
if (diffOverlay) {
|
|
251
|
-
const metrics = getPreviewScrollMetrics(file, h, zoom, scrollTop);
|
|
252
265
|
const markers = collectPreviewDiffMarkers(file, metrics.totalLines);
|
|
253
266
|
diffOverlay.innerHTML = '';
|
|
254
267
|
diffOverlay.style.top = `${metrics.trackPadding}px`;
|
|
255
268
|
diffOverlay.style.bottom = `${metrics.trackPadding}px`;
|
|
256
|
-
diffOverlay.style.right = '
|
|
269
|
+
diffOverlay.style.right = '26px';
|
|
257
270
|
diffOverlay.style.width = '12px';
|
|
258
271
|
diffOverlay.style.pointerEvents = 'auto';
|
|
259
272
|
|
|
260
273
|
for (const marker of markers) {
|
|
261
274
|
const btn = document.createElement('button');
|
|
262
|
-
const markerHeight = marker.height === 1 ? metrics.trackHeight :
|
|
275
|
+
const markerHeight = marker.height === 1 ? metrics.trackHeight : 10;
|
|
263
276
|
const y = marker.height === 1 ? 0 : marker.ratio * Math.max(0, metrics.trackHeight - markerHeight);
|
|
264
277
|
btn.type = 'button';
|
|
265
278
|
btn.className = 'file-pill-diff-marker';
|
|
266
279
|
btn.title = marker.color === '#22c55e' ? 'Jump to added lines' : 'Jump to deleted lines';
|
|
267
|
-
btn.style.cssText = `position:absolute;left:0;width:12px;height:${markerHeight}px;top:${y}px;border:none;border-radius:3px;background:${marker.color};box-shadow:0 0 0 1px rgba(255,255,255,0.
|
|
280
|
+
btn.style.cssText = `position:absolute;left:0;width:12px;height:${markerHeight}px;top:${y}px;border:none;border-radius:3px;background:${marker.color};box-shadow:0 0 0 1px rgba(255,255,255,0.28),0 0 10px ${marker.color}88;cursor:pointer;padding:0;pointer-events:auto;opacity:1;`;
|
|
268
281
|
btn.addEventListener('mousedown', (e) => {
|
|
282
|
+
e.preventDefault();
|
|
269
283
|
e.stopPropagation();
|
|
270
284
|
});
|
|
271
285
|
btn.addEventListener('click', (e) => {
|
|
@@ -285,6 +299,71 @@ function updatePillCardLayout(ctx: CanvasContext, pill: HTMLElement, zoom: numbe
|
|
|
285
299
|
diffOverlay.appendChild(btn);
|
|
286
300
|
}
|
|
287
301
|
}
|
|
302
|
+
|
|
303
|
+
if (scrollOverlay) {
|
|
304
|
+
scrollOverlay.innerHTML = '';
|
|
305
|
+
scrollOverlay.style.top = `${metrics.trackPadding}px`;
|
|
306
|
+
scrollOverlay.style.bottom = `${metrics.trackPadding}px`;
|
|
307
|
+
|
|
308
|
+
const track = document.createElement('button');
|
|
309
|
+
track.type = 'button';
|
|
310
|
+
track.className = 'file-pill-scroll-track';
|
|
311
|
+
track.title = 'Scroll preview';
|
|
312
|
+
track.style.cssText = `position:absolute;inset:0;border:none;border-radius:999px;background:rgba(255,255,255,0.08);padding:0;cursor:pointer;`;
|
|
313
|
+
track.addEventListener('mousedown', (e) => {
|
|
314
|
+
e.preventDefault();
|
|
315
|
+
e.stopPropagation();
|
|
316
|
+
const rect = track.getBoundingClientRect();
|
|
317
|
+
const thumbHeight = Math.max(18, metrics.thumbHeight);
|
|
318
|
+
const localY = Math.max(0, Math.min(rect.height, e.clientY - rect.top));
|
|
319
|
+
const thumbCenter = localY - thumbHeight / 2;
|
|
320
|
+
const ratio = rect.height > thumbHeight ? Math.max(0, Math.min(1, thumbCenter / (rect.height - thumbHeight))) : 0;
|
|
321
|
+
const next = ratio * metrics.maxScroll;
|
|
322
|
+
const key = `scroll:${path}`;
|
|
323
|
+
const existing = ctx.positions.get(key) || {};
|
|
324
|
+
ctx.positions.set(key, { ...existing, x: next, y: existing.y || 0 });
|
|
325
|
+
try {
|
|
326
|
+
const { debounceSaveScroll } = require('./cards');
|
|
327
|
+
debounceSaveScroll(ctx, path, next);
|
|
328
|
+
} catch { }
|
|
329
|
+
updatePillCardLayout(ctx, pill, zoom, changed);
|
|
330
|
+
});
|
|
331
|
+
scrollOverlay.appendChild(track);
|
|
332
|
+
|
|
333
|
+
const thumb = document.createElement('div');
|
|
334
|
+
thumb.className = 'file-pill-scroll-thumb';
|
|
335
|
+
thumb.style.cssText = `position:absolute;left:0;width:9px;height:${metrics.thumbHeight}px;top:${Math.max(0, metrics.thumbY - metrics.trackPadding)}px;border-radius:999px;background:rgba(196,181,253,0.98);box-shadow:0 0 0 1px rgba(255,255,255,0.24),0 4px 12px rgba(0,0,0,0.35);cursor:grab;`;
|
|
336
|
+
thumb.addEventListener('mousedown', (e) => {
|
|
337
|
+
e.preventDefault();
|
|
338
|
+
e.stopPropagation();
|
|
339
|
+
const startY = e.clientY;
|
|
340
|
+
const startScroll = scrollTop;
|
|
341
|
+
const dragTravel = Math.max(1, metrics.trackHeight - metrics.thumbHeight);
|
|
342
|
+
const maxScroll = Math.max(0, metrics.maxScroll);
|
|
343
|
+
|
|
344
|
+
const onMove = (moveEvent: MouseEvent) => {
|
|
345
|
+
moveEvent.preventDefault();
|
|
346
|
+
const deltaY = moveEvent.clientY - startY;
|
|
347
|
+
const next = maxScroll > 0 ? Math.max(0, Math.min(maxScroll, startScroll + (deltaY / dragTravel) * maxScroll)) : 0;
|
|
348
|
+
const key = `scroll:${path}`;
|
|
349
|
+
const existing = ctx.positions.get(key) || {};
|
|
350
|
+
ctx.positions.set(key, { ...existing, x: next, y: existing.y || 0 });
|
|
351
|
+
updatePillCardLayout(ctx, pill, zoom, changed);
|
|
352
|
+
};
|
|
353
|
+
const onUp = () => {
|
|
354
|
+
const finalScroll = getSavedScrollTop(ctx, path);
|
|
355
|
+
try {
|
|
356
|
+
const { debounceSaveScroll } = require('./cards');
|
|
357
|
+
debounceSaveScroll(ctx, path, finalScroll);
|
|
358
|
+
} catch { }
|
|
359
|
+
window.removeEventListener('mousemove', onMove);
|
|
360
|
+
window.removeEventListener('mouseup', onUp);
|
|
361
|
+
};
|
|
362
|
+
window.addEventListener('mousemove', onMove);
|
|
363
|
+
window.addEventListener('mouseup', onUp);
|
|
364
|
+
});
|
|
365
|
+
scrollOverlay.appendChild(thumb);
|
|
366
|
+
}
|
|
288
367
|
}
|
|
289
368
|
|
|
290
369
|
/**
|
|
@@ -528,7 +607,7 @@ export function performViewportCulling(ctx: CanvasContext) {
|
|
|
528
607
|
);
|
|
529
608
|
|
|
530
609
|
if (inView) {
|
|
531
|
-
const file =
|
|
610
|
+
const file = resolvePreviewFile(ctx, path);
|
|
532
611
|
const pill = createPillCard(ctx, file, path, x, y, w, h, isChanged, zoom, true);
|
|
533
612
|
if (isChanged) pill.dataset.changed = 'true';
|
|
534
613
|
ctx.canvas.appendChild(pill);
|
|
@@ -707,7 +786,7 @@ export function setupPillInteraction(ctx: CanvasContext) {
|
|
|
707
786
|
const path = pill.dataset.path || '';
|
|
708
787
|
if (!path) return;
|
|
709
788
|
|
|
710
|
-
const file = (pill as any)._fileData
|
|
789
|
+
const file = resolvePreviewFile(ctx, path, (pill as any)._fileData);
|
|
711
790
|
const zoom = ctx.snap().context.zoom || 1;
|
|
712
791
|
const height = parseFloat(pill.style.height) || 700;
|
|
713
792
|
const current = getSavedScrollTop(ctx, path);
|