gitmaps 1.1.11 → 1.1.13
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,16 +1,19 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
import { estimatePreviewCharsPerLine, estimatePreviewLineCapacity, estimateTitleCharsPerLine, getLowZoomPreviewText, getLowZoomScale, wrapPreviewText } from './low-zoom-preview';
|
|
2
|
+
import { estimatePreviewCharsPerLine, estimatePreviewLineCapacity, estimatePreviewMaxScroll, estimateTitleCharsPerLine, getLowZoomPreviewText, getLowZoomScale, 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', () => {
|
|
6
6
|
const file = {
|
|
7
7
|
path: 'src/example.ts',
|
|
8
8
|
ext: 'ts',
|
|
9
|
-
content: Array.from({ length:
|
|
9
|
+
content: Array.from({ length: 80 }, (_, i) => `line-${i + 1}`).join('\n'),
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
const top = getLowZoomPreviewText(file, 0);
|
|
13
|
+
const scrolled = getLowZoomPreviewText(file, 40);
|
|
14
|
+
expect(top.startsWith('line-1')).toBe(true);
|
|
15
|
+
expect(scrolled.startsWith('line-3')).toBe(true);
|
|
16
|
+
expect(scrolled.includes('line-80')).toBe(true);
|
|
14
17
|
});
|
|
15
18
|
|
|
16
19
|
test('skips binary or unsupported files', () => {
|
|
@@ -42,6 +45,13 @@ describe('low zoom preview helpers', () => {
|
|
|
42
45
|
expect(estimatePreviewLineCapacity(700, 0.1)).toBeLessThan(estimatePreviewLineCapacity(700, 1));
|
|
43
46
|
});
|
|
44
47
|
|
|
48
|
+
test('preview scroll range grows with longer files', () => {
|
|
49
|
+
const shortFile = { content: Array.from({ length: 8 }, (_, i) => `line-${i}`).join('\n') };
|
|
50
|
+
const longFile = { content: Array.from({ length: 80 }, (_, i) => `line-${i}`).join('\n') };
|
|
51
|
+
expect(estimatePreviewMaxScroll(shortFile, 700, 1)).toBeGreaterThanOrEqual(0);
|
|
52
|
+
expect(estimatePreviewMaxScroll(longFile, 700, 1)).toBeGreaterThan(estimatePreviewMaxScroll(shortFile, 700, 1));
|
|
53
|
+
});
|
|
54
|
+
|
|
45
55
|
test('title typography is larger than body typography for readability', () => {
|
|
46
56
|
const scale = getLowZoomScale(0.18);
|
|
47
57
|
expect(scale.titleFont).toBeGreaterThan(scale.bodyFont);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { getSettings } from './settings';
|
|
2
|
+
|
|
1
3
|
const PREVIEWABLE_EXTS = new Set([
|
|
2
4
|
'ts', 'tsx', 'js', 'jsx', 'json', 'css', 'scss', 'html', 'md', 'py', 'rs', 'go', 'vue', 'svelte', 'toml', 'yaml', 'yml', 'sh', 'sql', 'txt'
|
|
3
5
|
]);
|
|
@@ -5,8 +7,9 @@ const PREVIEWABLE_EXTS = new Set([
|
|
|
5
7
|
export function getLowZoomScale(zoom: number) {
|
|
6
8
|
const clampedZoom = Math.max(0.08, Math.min(1, zoom));
|
|
7
9
|
const progress = (clampedZoom - 0.08) / (1 - 0.08);
|
|
10
|
+
const settings = getSettings();
|
|
8
11
|
|
|
9
|
-
const desiredScreenTitle =
|
|
12
|
+
const desiredScreenTitle = settings.previewFarTitlePx + progress * (settings.previewNearTitlePx - settings.previewFarTitlePx);
|
|
10
13
|
const desiredScreenBody = 5.5 + progress * 6.5;
|
|
11
14
|
const desiredScreenPadding = 6 + progress * 8;
|
|
12
15
|
const desiredScreenGap = 4 + progress * 4;
|
|
@@ -32,7 +35,7 @@ export function getLowZoomPreviewText(file: any, scrollTop: number): string {
|
|
|
32
35
|
const lines = normalized.split('\n');
|
|
33
36
|
const approxLineHeight = 20;
|
|
34
37
|
const startLine = Math.max(0, Math.floor(scrollTop / approxLineHeight));
|
|
35
|
-
return lines.slice(startLine
|
|
38
|
+
return lines.slice(startLine).join('\n').trim();
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
export function wrapPreviewText(text: string, maxCharsPerLine: number, maxLines: number): string[] {
|
|
@@ -87,12 +90,15 @@ function ellipsizeWrappedLines(lines: string[], maxLines: number) {
|
|
|
87
90
|
|
|
88
91
|
export function estimatePreviewLineCapacity(height: number, zoom: number): number {
|
|
89
92
|
const scale = getLowZoomScale(zoom);
|
|
93
|
+
const settings = getSettings();
|
|
90
94
|
const titleLines = zoom >= 0.35 ? 2 : 1;
|
|
91
95
|
const available = Math.max(
|
|
92
96
|
scale.bodyLineHeight * 2,
|
|
93
97
|
height - scale.padding * 2 - scale.titleLineHeight * titleLines - scale.bodyFont - scale.gap * 3,
|
|
94
98
|
);
|
|
95
|
-
|
|
99
|
+
const progress = (Math.max(0.08, Math.min(1, zoom)) - 0.08) / (1 - 0.08);
|
|
100
|
+
const targetLines = settings.previewFarLines + progress * (settings.previewNearLines - settings.previewFarLines);
|
|
101
|
+
return Math.max(Math.round(targetLines), Math.floor(available / scale.bodyLineHeight));
|
|
96
102
|
}
|
|
97
103
|
|
|
98
104
|
export function estimateTitleCharsPerLine(width: number, zoom: number): number {
|
|
@@ -109,6 +115,13 @@ export function estimatePreviewCharsPerLine(width: number, zoom: number): number
|
|
|
109
115
|
return Math.max(10, Math.floor(available / avgCharWidth));
|
|
110
116
|
}
|
|
111
117
|
|
|
118
|
+
export function estimatePreviewMaxScroll(file: any, height: number, zoom: number): number {
|
|
119
|
+
if (!file || !file.content) return 0;
|
|
120
|
+
const totalLines = String(file.content).split('\n').length;
|
|
121
|
+
const visibleLines = estimatePreviewLineCapacity(height, zoom);
|
|
122
|
+
return Math.max(0, (totalLines - visibleLines) * 20);
|
|
123
|
+
}
|
|
124
|
+
|
|
112
125
|
export function renderLowZoomPreviewCanvas(
|
|
113
126
|
canvas: HTMLCanvasElement,
|
|
114
127
|
params: {
|
|
@@ -144,6 +144,26 @@ function SettingsPanel({ settings }: { settings: GitCanvasSettings }) {
|
|
|
144
144
|
</SettingsRow>
|
|
145
145
|
</SettingsSection>
|
|
146
146
|
|
|
147
|
+
{/* Preview Mode Section */}
|
|
148
|
+
<SettingsSection title="Preview Mode">
|
|
149
|
+
<SettingsRow label="Far Zoom Title" desc="Filename size when zoomed far out">
|
|
150
|
+
<Slider id="settingPreviewFarTitlePx" valueId="previewFarTitlePxValue"
|
|
151
|
+
min={6} max={14} step={1} value={settings.previewFarTitlePx} suffix="px" />
|
|
152
|
+
</SettingsRow>
|
|
153
|
+
<SettingsRow label="Near Zoom Title" desc="Filename size when zoomed in within preview mode">
|
|
154
|
+
<Slider id="settingPreviewNearTitlePx" valueId="previewNearTitlePxValue"
|
|
155
|
+
min={10} max={24} step={1} value={settings.previewNearTitlePx} suffix="px" />
|
|
156
|
+
</SettingsRow>
|
|
157
|
+
<SettingsRow label="Far Zoom Lines" desc="Minimum content lines shown when zoomed far out">
|
|
158
|
+
<Slider id="settingPreviewFarLines" valueId="previewFarLinesValue"
|
|
159
|
+
min={1} max={8} step={1} value={settings.previewFarLines} suffix="" />
|
|
160
|
+
</SettingsRow>
|
|
161
|
+
<SettingsRow label="Near Zoom Lines" desc="Target content lines shown when zoomed in">
|
|
162
|
+
<Slider id="settingPreviewNearLines" valueId="previewNearLinesValue"
|
|
163
|
+
min={8} max={40} step={1} value={settings.previewNearLines} suffix="" />
|
|
164
|
+
</SettingsRow>
|
|
165
|
+
</SettingsSection>
|
|
166
|
+
|
|
147
167
|
{/* Advanced Section */}
|
|
148
168
|
<SettingsSection title="Advanced">
|
|
149
169
|
<SettingsRow label="Max Visible Lines" desc="Lines shown per card before virtual scroll">
|
|
@@ -261,6 +281,38 @@ export function openSettingsModal(ctx?: any) {
|
|
|
261
281
|
updateSettings({ maxVisibleLines: parseInt(maxLinesSlider.value) });
|
|
262
282
|
});
|
|
263
283
|
|
|
284
|
+
const previewFarTitleSlider = _modal.querySelector('#settingPreviewFarTitlePx') as HTMLInputElement;
|
|
285
|
+
const previewFarTitleValue = _modal.querySelector('#previewFarTitlePxValue')!;
|
|
286
|
+
previewFarTitleSlider?.addEventListener('input', () => {
|
|
287
|
+
previewFarTitleValue.textContent = `${previewFarTitleSlider.value}px`;
|
|
288
|
+
updateSettings({ previewFarTitlePx: parseInt(previewFarTitleSlider.value) });
|
|
289
|
+
window.dispatchEvent(new CustomEvent('gitcanvas:preview-settings-changed'));
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
const previewNearTitleSlider = _modal.querySelector('#settingPreviewNearTitlePx') as HTMLInputElement;
|
|
293
|
+
const previewNearTitleValue = _modal.querySelector('#previewNearTitlePxValue')!;
|
|
294
|
+
previewNearTitleSlider?.addEventListener('input', () => {
|
|
295
|
+
previewNearTitleValue.textContent = `${previewNearTitleSlider.value}px`;
|
|
296
|
+
updateSettings({ previewNearTitlePx: parseInt(previewNearTitleSlider.value) });
|
|
297
|
+
window.dispatchEvent(new CustomEvent('gitcanvas:preview-settings-changed'));
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
const previewFarLinesSlider = _modal.querySelector('#settingPreviewFarLines') as HTMLInputElement;
|
|
301
|
+
const previewFarLinesValue = _modal.querySelector('#previewFarLinesValue')!;
|
|
302
|
+
previewFarLinesSlider?.addEventListener('input', () => {
|
|
303
|
+
previewFarLinesValue.textContent = previewFarLinesSlider.value;
|
|
304
|
+
updateSettings({ previewFarLines: parseInt(previewFarLinesSlider.value) });
|
|
305
|
+
window.dispatchEvent(new CustomEvent('gitcanvas:preview-settings-changed'));
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
const previewNearLinesSlider = _modal.querySelector('#settingPreviewNearLines') as HTMLInputElement;
|
|
309
|
+
const previewNearLinesValue = _modal.querySelector('#previewNearLinesValue')!;
|
|
310
|
+
previewNearLinesSlider?.addEventListener('input', () => {
|
|
311
|
+
previewNearLinesValue.textContent = previewNearLinesSlider.value;
|
|
312
|
+
updateSettings({ previewNearLines: parseInt(previewNearLinesSlider.value) });
|
|
313
|
+
window.dispatchEvent(new CustomEvent('gitcanvas:preview-settings-changed'));
|
|
314
|
+
});
|
|
315
|
+
|
|
264
316
|
// Wire switches
|
|
265
317
|
const minimapSwitch = _modal.querySelector('#settingMinimap') as HTMLInputElement;
|
|
266
318
|
minimapSwitch?.addEventListener('change', () => {
|
package/app/lib/settings.ts
CHANGED
|
@@ -31,6 +31,14 @@ export interface GitCanvasSettings {
|
|
|
31
31
|
heatmapEnabled: boolean;
|
|
32
32
|
/** Heatmap time range in days */
|
|
33
33
|
heatmapDays: number;
|
|
34
|
+
/** Preview mode far-zoom title size (screen px) */
|
|
35
|
+
previewFarTitlePx: number;
|
|
36
|
+
/** Preview mode near-zoom title size (screen px) */
|
|
37
|
+
previewNearTitlePx: number;
|
|
38
|
+
/** Preview mode far-zoom minimum visible lines */
|
|
39
|
+
previewFarLines: number;
|
|
40
|
+
/** Preview mode near-zoom target visible lines */
|
|
41
|
+
previewNearLines: number;
|
|
34
42
|
}
|
|
35
43
|
|
|
36
44
|
const DEFAULTS: GitCanvasSettings = {
|
|
@@ -46,6 +54,10 @@ const DEFAULTS: GitCanvasSettings = {
|
|
|
46
54
|
popupFontSize: 14,
|
|
47
55
|
heatmapEnabled: false,
|
|
48
56
|
heatmapDays: 90,
|
|
57
|
+
previewFarTitlePx: 8,
|
|
58
|
+
previewNearTitlePx: 16,
|
|
59
|
+
previewFarLines: 3,
|
|
60
|
+
previewNearLines: 20,
|
|
49
61
|
};
|
|
50
62
|
|
|
51
63
|
let _settings: GitCanvasSettings | null = null;
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
import { measure } from 'measure-fn';
|
|
25
25
|
import type { CanvasContext } from './context';
|
|
26
|
-
import { getLowZoomScale, renderLowZoomPreviewCanvas } from './low-zoom-preview';
|
|
26
|
+
import { estimatePreviewMaxScroll, getLowZoomScale, renderLowZoomPreviewCanvas } from './low-zoom-preview';
|
|
27
27
|
import { materializeViewport } from './xydraw-bridge';
|
|
28
28
|
|
|
29
29
|
// ── Culling state ──────────────────────────────────────────
|
|
@@ -632,6 +632,42 @@ export function setupPillInteraction(ctx: CanvasContext) {
|
|
|
632
632
|
let pillMoveInfos: { pill: HTMLElement; path: string; startLeft: number; startTop: number }[] = [];
|
|
633
633
|
const DRAG_THRESHOLD = 5;
|
|
634
634
|
|
|
635
|
+
ctx.canvas.addEventListener('wheel', (e: WheelEvent) => {
|
|
636
|
+
const pill = (e.target as HTMLElement).closest('.file-pill') as HTMLElement;
|
|
637
|
+
if (!pill) return;
|
|
638
|
+
if (e.ctrlKey || e.metaKey) return;
|
|
639
|
+
|
|
640
|
+
e.preventDefault();
|
|
641
|
+
e.stopPropagation();
|
|
642
|
+
|
|
643
|
+
const path = pill.dataset.path || '';
|
|
644
|
+
if (!path) return;
|
|
645
|
+
|
|
646
|
+
const file = (pill as any)._fileData || ctx.allFilesData?.find(f => f.path === path) || ctx.commitFilesData?.find(f => f.path === path) || null;
|
|
647
|
+
const zoom = ctx.snap().context.zoom || 1;
|
|
648
|
+
const height = parseFloat(pill.style.height) || 700;
|
|
649
|
+
const current = getSavedScrollTop(ctx, path);
|
|
650
|
+
const maxScroll = estimatePreviewMaxScroll(file, height, zoom);
|
|
651
|
+
const next = Math.max(0, Math.min(maxScroll, current + e.deltaY));
|
|
652
|
+
if (next === current) return;
|
|
653
|
+
|
|
654
|
+
const key = `scroll:${path}`;
|
|
655
|
+
const existing = ctx.positions.get(key) || {};
|
|
656
|
+
ctx.positions.set(key, { ...existing, x: next, y: existing.y || 0 });
|
|
657
|
+
try {
|
|
658
|
+
const { debounceSaveScroll } = require('./cards');
|
|
659
|
+
debounceSaveScroll(ctx, path, next);
|
|
660
|
+
} catch { }
|
|
661
|
+
updatePillCardLayout(ctx, pill, zoom, pill.dataset.changed === 'true');
|
|
662
|
+
}, { passive: false });
|
|
663
|
+
|
|
664
|
+
window.addEventListener('gitcanvas:preview-settings-changed', () => {
|
|
665
|
+
const zoom = ctx.snap().context.zoom || 1;
|
|
666
|
+
for (const [, pill] of pillCards) {
|
|
667
|
+
updatePillCardLayout(ctx, pill, zoom, pill.dataset.changed === 'true');
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
|
|
635
671
|
ctx.canvas.addEventListener('mousedown', (e: MouseEvent) => {
|
|
636
672
|
if (e.button !== 0) return;
|
|
637
673
|
const pill = (e.target as HTMLElement).closest('.file-pill') as HTMLElement;
|