gitmaps 1.1.13 → 1.1.15
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
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
import { estimatePreviewCharsPerLine, estimatePreviewLineCapacity, estimatePreviewMaxScroll, estimateTitleCharsPerLine, getLowZoomPreviewText, getLowZoomScale, wrapPreviewText } from './low-zoom-preview';
|
|
2
|
+
import { collectPreviewDiffMarkers, estimatePreviewCharsPerLine, estimatePreviewLineCapacity, estimatePreviewMaxScroll, estimateTitleCharsPerLine, getLowZoomPreviewText, getLowZoomScale, getPreviewScrollMetrics, wrapPreviewText } from './low-zoom-preview';
|
|
3
3
|
|
|
4
4
|
describe('low zoom preview helpers', () => {
|
|
5
5
|
test('anchors preview text to approximate saved scroll position', () => {
|
|
@@ -52,6 +52,23 @@ describe('low zoom preview helpers', () => {
|
|
|
52
52
|
expect(estimatePreviewMaxScroll(longFile, 700, 1)).toBeGreaterThan(estimatePreviewMaxScroll(shortFile, 700, 1));
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
+
test('scroll metrics produce a visible thumb position', () => {
|
|
56
|
+
const file = { content: Array.from({ length: 120 }, (_, i) => `line-${i}`).join('\n') };
|
|
57
|
+
const metrics = getPreviewScrollMetrics(file, 700, 1, 120);
|
|
58
|
+
expect(metrics.thumbHeight).toBeGreaterThan(0);
|
|
59
|
+
expect(metrics.thumbY).toBeGreaterThanOrEqual(metrics.trackPadding);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('collects diff markers from added and deleted lines', () => {
|
|
63
|
+
const markers = collectPreviewDiffMarkers({
|
|
64
|
+
addedLines: new Set([2, 10]),
|
|
65
|
+
deletedBeforeLine: new Map([[5, ['gone']]]),
|
|
66
|
+
}, 20);
|
|
67
|
+
expect(markers.length).toBe(3);
|
|
68
|
+
expect(markers.some((m) => m.color === '#22c55e')).toBe(true);
|
|
69
|
+
expect(markers.some((m) => m.color === '#ef4444')).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
|
|
55
72
|
test('title typography is larger than body typography for readability', () => {
|
|
56
73
|
const scale = getLowZoomScale(0.18);
|
|
57
74
|
expect(scale.titleFont).toBeGreaterThan(scale.bodyFont);
|
|
@@ -122,6 +122,33 @@ export function estimatePreviewMaxScroll(file: any, height: number, zoom: number
|
|
|
122
122
|
return Math.max(0, (totalLines - visibleLines) * 20);
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
export function getPreviewScrollMetrics(file: any, height: number, zoom: number, scrollTop: number) {
|
|
126
|
+
const totalLines = Math.max(1, String(file?.content || '').split('\n').length);
|
|
127
|
+
const visibleLines = Math.max(1, estimatePreviewLineCapacity(height, zoom));
|
|
128
|
+
const maxScroll = Math.max(0, (totalLines - visibleLines) * 20);
|
|
129
|
+
const trackPadding = 10;
|
|
130
|
+
const trackHeight = Math.max(24, height - trackPadding * 2);
|
|
131
|
+
const thumbHeight = Math.max(18, (visibleLines / totalLines) * trackHeight);
|
|
132
|
+
const thumbTravel = Math.max(0, trackHeight - thumbHeight);
|
|
133
|
+
const thumbY = trackPadding + (maxScroll > 0 ? (scrollTop / maxScroll) * thumbTravel : 0);
|
|
134
|
+
return { totalLines, visibleLines, maxScroll, trackPadding, trackHeight, thumbHeight, thumbY };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function collectPreviewDiffMarkers(file: any, totalLines: number) {
|
|
138
|
+
const markers: Array<{ ratio: number; color: string }> = [];
|
|
139
|
+
const safeTotal = Math.max(1, totalLines);
|
|
140
|
+
const added = file?.addedLines instanceof Set ? Array.from(file.addedLines) : [];
|
|
141
|
+
const deletedBefore = file?.deletedBeforeLine instanceof Map ? Array.from(file.deletedBeforeLine.keys()) : [];
|
|
142
|
+
|
|
143
|
+
for (const line of added) {
|
|
144
|
+
markers.push({ ratio: Math.max(0, Math.min(1, (line - 1) / safeTotal)), color: '#22c55e' });
|
|
145
|
+
}
|
|
146
|
+
for (const line of deletedBefore) {
|
|
147
|
+
markers.push({ ratio: Math.max(0, Math.min(1, (line - 1) / safeTotal)), color: '#ef4444' });
|
|
148
|
+
}
|
|
149
|
+
return markers;
|
|
150
|
+
}
|
|
151
|
+
|
|
125
152
|
export function renderLowZoomPreviewCanvas(
|
|
126
153
|
canvas: HTMLCanvasElement,
|
|
127
154
|
params: {
|
|
@@ -140,6 +167,7 @@ export function renderLowZoomPreviewCanvas(
|
|
|
140
167
|
const scale = getLowZoomScale(zoom);
|
|
141
168
|
const ctx = canvas.getContext('2d');
|
|
142
169
|
if (!ctx) return;
|
|
170
|
+
const scrollMetrics = getPreviewScrollMetrics(file, height, zoom, scrollTop);
|
|
143
171
|
|
|
144
172
|
canvas.width = Math.max(1, Math.floor(width * dpr));
|
|
145
173
|
canvas.height = Math.max(1, Math.floor(height * dpr));
|
|
@@ -157,12 +185,15 @@ export function renderLowZoomPreviewCanvas(
|
|
|
157
185
|
ctx.fill();
|
|
158
186
|
|
|
159
187
|
const accentWidth = Math.max(6, width * 0.012);
|
|
188
|
+
const scrollbarWidth = 6;
|
|
189
|
+
const markerLaneWidth = 4;
|
|
190
|
+
const rightRailWidth = scrollbarWidth + markerLaneWidth + 8;
|
|
160
191
|
ctx.fillStyle = accentColor;
|
|
161
192
|
ctx.fillRect(0, 0, accentWidth, height);
|
|
162
193
|
|
|
163
194
|
const leftInset = scale.padding + accentWidth + scale.gap * 0.8;
|
|
164
195
|
const topInset = scale.padding;
|
|
165
|
-
const maxTextWidth = Math.max(40, width - leftInset - scale.padding);
|
|
196
|
+
const maxTextWidth = Math.max(40, width - leftInset - scale.padding - rightRailWidth);
|
|
166
197
|
|
|
167
198
|
ctx.textBaseline = 'top';
|
|
168
199
|
ctx.font = `700 ${scale.titleFont}px "JetBrains Mono", monospace`;
|
|
@@ -206,6 +237,26 @@ export function renderLowZoomPreviewCanvas(
|
|
|
206
237
|
if (y > height - scale.padding) return;
|
|
207
238
|
ctx.fillText(line, leftInset, y);
|
|
208
239
|
});
|
|
240
|
+
|
|
241
|
+
const trackX = width - scrollbarWidth - 4;
|
|
242
|
+
const markerX = trackX - markerLaneWidth - 3;
|
|
243
|
+
const trackY = scrollMetrics.trackPadding;
|
|
244
|
+
const trackHeight = scrollMetrics.trackHeight;
|
|
245
|
+
|
|
246
|
+
ctx.fillStyle = 'rgba(255,255,255,0.08)';
|
|
247
|
+
roundRect(ctx, trackX, trackY, scrollbarWidth, trackHeight, scrollbarWidth / 2);
|
|
248
|
+
ctx.fill();
|
|
249
|
+
|
|
250
|
+
const markers = collectPreviewDiffMarkers(file, scrollMetrics.totalLines);
|
|
251
|
+
for (const marker of markers) {
|
|
252
|
+
const y = trackY + marker.ratio * trackHeight;
|
|
253
|
+
ctx.fillStyle = marker.color;
|
|
254
|
+
ctx.fillRect(markerX, Math.max(trackY, y), markerLaneWidth, 3);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
ctx.fillStyle = 'rgba(196,181,253,0.9)';
|
|
258
|
+
roundRect(ctx, trackX, scrollMetrics.thumbY, scrollbarWidth, scrollMetrics.thumbHeight, scrollbarWidth / 2);
|
|
259
|
+
ctx.fill();
|
|
209
260
|
}
|
|
210
261
|
|
|
211
262
|
function trimToWidth(ctx: CanvasRenderingContext2D, text: string, maxWidth: number) {
|
|
@@ -37,6 +37,7 @@ const VIEWPORT_MARGIN = 500;
|
|
|
37
37
|
// LOD threshold: below this zoom level, use lightweight pill placeholders
|
|
38
38
|
const LOD_ZOOM_THRESHOLD = 0.25;
|
|
39
39
|
const LOW_ZOOM_MODE_STORAGE_KEY = 'gitmaps:detailMode';
|
|
40
|
+
const PREVIEW_HINT_SHOWN_KEY = 'gitmaps:previewModeHintShown';
|
|
40
41
|
|
|
41
42
|
// Maximum deferred cards to materialize per animation frame
|
|
42
43
|
// Prevents frame drops when zooming out then back in on huge repos
|
|
@@ -120,6 +121,21 @@ export function isPreviewModeForced() {
|
|
|
120
121
|
return _detailMode === 'preview';
|
|
121
122
|
}
|
|
122
123
|
|
|
124
|
+
function maybeShowPreviewModeHint() {
|
|
125
|
+
if (_detailMode !== 'preview') return;
|
|
126
|
+
try {
|
|
127
|
+
if (localStorage.getItem(PREVIEW_HINT_SHOWN_KEY) === 'true') return;
|
|
128
|
+
localStorage.setItem(PREVIEW_HINT_SHOWN_KEY, 'true');
|
|
129
|
+
} catch { }
|
|
130
|
+
|
|
131
|
+
setTimeout(() => {
|
|
132
|
+
try {
|
|
133
|
+
const { showToast } = require('./utils');
|
|
134
|
+
showToast('Preview mode: wheel scrolls card content · right rail shows scroll + diff markers · top-right switch returns to Classic', 'info');
|
|
135
|
+
} catch { }
|
|
136
|
+
}, 120);
|
|
137
|
+
}
|
|
138
|
+
|
|
123
139
|
// ── Status colors for low-zoom cards
|
|
124
140
|
const PILL_COLORS: Record<string, string> = {
|
|
125
141
|
'ts': '#3178c6',
|
|
@@ -441,6 +457,7 @@ export function performViewportCulling(ctx: CanvasContext) {
|
|
|
441
457
|
if (isChanged) pill.dataset.changed = 'true';
|
|
442
458
|
ctx.canvas.appendChild(pill);
|
|
443
459
|
pillCards.set(path, pill);
|
|
460
|
+
maybeShowPreviewModeHint();
|
|
444
461
|
} else if (inView && pillCards.has(path)) {
|
|
445
462
|
updatePillCardLayout(ctx, pillCards.get(path)!, zoom, !!isChanged);
|
|
446
463
|
} else if (!inView && pillCards.has(path)) {
|
|
@@ -470,6 +487,7 @@ export function performViewportCulling(ctx: CanvasContext) {
|
|
|
470
487
|
if (isChanged) pill.dataset.changed = 'true';
|
|
471
488
|
ctx.canvas.appendChild(pill);
|
|
472
489
|
pillCards.set(path, pill);
|
|
490
|
+
maybeShowPreviewModeHint();
|
|
473
491
|
}
|
|
474
492
|
} else {
|
|
475
493
|
updatePillCardLayout(ctx, pillCards.get(path)!, zoom, card.dataset.changed === 'true');
|