gitmaps 1.1.4 → 1.1.6
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.
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { estimatePreviewCharsPerLine, estimatePreviewLineCapacity, getLowZoomPreviewText, getLowZoomScale, wrapPreviewText } from './low-zoom-preview';
|
|
3
|
+
|
|
4
|
+
describe('low zoom preview helpers', () => {
|
|
5
|
+
test('anchors preview text to approximate saved scroll position', () => {
|
|
6
|
+
const file = {
|
|
7
|
+
path: 'src/example.ts',
|
|
8
|
+
ext: 'ts',
|
|
9
|
+
content: Array.from({ length: 12 }, (_, i) => `line-${i + 1}`).join('\n'),
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
expect(getLowZoomPreviewText(file, 0).startsWith('line-1')).toBe(true);
|
|
13
|
+
expect(getLowZoomPreviewText(file, 40).startsWith('line-3')).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('skips binary or unsupported files', () => {
|
|
17
|
+
expect(getLowZoomPreviewText({ path: 'image.png', ext: 'png', content: 'abc' }, 0)).toBe('');
|
|
18
|
+
expect(getLowZoomPreviewText({ path: 'bin.dat', ext: 'dat', isBinary: true, content: 'abc' }, 0)).toBe('');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('increases world-space font size as zoom goes down', () => {
|
|
22
|
+
const near = getLowZoomScale(0.25);
|
|
23
|
+
const far = getLowZoomScale(0.1);
|
|
24
|
+
expect(far.titleFont).toBeGreaterThan(near.titleFont);
|
|
25
|
+
expect(far.bodyFont).toBeGreaterThan(near.bodyFont);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('wraps preview text into bounded lines with ellipsis', () => {
|
|
29
|
+
const lines = wrapPreviewText('alpha beta gamma delta epsilon zeta eta theta', 10, 3);
|
|
30
|
+
expect(lines.length).toBeLessThanOrEqual(3);
|
|
31
|
+
expect(lines[lines.length - 1]?.endsWith('…')).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('preview capacity estimates stay positive', () => {
|
|
35
|
+
expect(estimatePreviewCharsPerLine(580, 0.25)).toBeGreaterThan(8);
|
|
36
|
+
expect(estimatePreviewLineCapacity(700, 0.25)).toBeGreaterThanOrEqual(2);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
const PREVIEWABLE_EXTS = new Set([
|
|
2
|
+
'ts', 'tsx', 'js', 'jsx', 'json', 'css', 'scss', 'html', 'md', 'py', 'rs', 'go', 'vue', 'svelte', 'toml', 'yaml', 'yml', 'sh', 'sql', 'txt'
|
|
3
|
+
]);
|
|
4
|
+
|
|
5
|
+
export function getLowZoomScale(zoom: number) {
|
|
6
|
+
const clampedZoom = Math.max(0.08, Math.min(0.25, zoom));
|
|
7
|
+
const progress = (0.25 - clampedZoom) / (0.25 - 0.08);
|
|
8
|
+
const desiredScreenTitle = 10 + progress * 4;
|
|
9
|
+
const desiredScreenBody = 8 + progress * 4;
|
|
10
|
+
return {
|
|
11
|
+
titleFont: desiredScreenTitle / clampedZoom,
|
|
12
|
+
bodyFont: desiredScreenBody / clampedZoom,
|
|
13
|
+
bodyLineHeight: (desiredScreenBody * 1.45) / clampedZoom,
|
|
14
|
+
padding: (10 + progress * 4) / clampedZoom,
|
|
15
|
+
gap: (6 + progress * 3) / clampedZoom,
|
|
16
|
+
radius: 8 / clampedZoom,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getLowZoomPreviewText(file: any, scrollTop: number): string {
|
|
21
|
+
if (!file || file.isBinary || !file.content) return '';
|
|
22
|
+
|
|
23
|
+
const ext = (file.ext || file.path?.split('.').pop() || '').toLowerCase();
|
|
24
|
+
if (!PREVIEWABLE_EXTS.has(ext)) return '';
|
|
25
|
+
|
|
26
|
+
const normalized = String(file.content).replace(/\t/g, ' ');
|
|
27
|
+
const lines = normalized.split('\n');
|
|
28
|
+
const approxLineHeight = 20;
|
|
29
|
+
const startLine = Math.max(0, Math.floor(scrollTop / approxLineHeight));
|
|
30
|
+
return lines.slice(startLine, startLine + 60).join('\n').trim();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function wrapPreviewText(text: string, maxCharsPerLine: number, maxLines: number): string[] {
|
|
34
|
+
const safeMaxChars = Math.max(8, Math.floor(maxCharsPerLine));
|
|
35
|
+
const safeMaxLines = Math.max(1, Math.floor(maxLines));
|
|
36
|
+
const sourceLines = String(text || '').split('\n');
|
|
37
|
+
const out: string[] = [];
|
|
38
|
+
|
|
39
|
+
for (const sourceLine of sourceLines) {
|
|
40
|
+
const words = sourceLine.length === 0 ? [''] : sourceLine.split(/(\s+)/).filter(Boolean);
|
|
41
|
+
let current = '';
|
|
42
|
+
|
|
43
|
+
for (const part of words) {
|
|
44
|
+
if (part.length > safeMaxChars) {
|
|
45
|
+
if (current.trim().length > 0) {
|
|
46
|
+
out.push(current.trimEnd());
|
|
47
|
+
if (out.length >= safeMaxLines) return ellipsizeWrappedLines(out, safeMaxLines);
|
|
48
|
+
current = '';
|
|
49
|
+
}
|
|
50
|
+
for (let i = 0; i < part.length; i += safeMaxChars) {
|
|
51
|
+
out.push(part.slice(i, i + safeMaxChars));
|
|
52
|
+
if (out.length >= safeMaxLines) return ellipsizeWrappedLines(out, safeMaxLines);
|
|
53
|
+
}
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if ((current + part).length > safeMaxChars && current.length > 0) {
|
|
58
|
+
out.push(current.trimEnd());
|
|
59
|
+
if (out.length >= safeMaxLines) return ellipsizeWrappedLines(out, safeMaxLines);
|
|
60
|
+
current = part.trimStart();
|
|
61
|
+
} else {
|
|
62
|
+
current += part;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (current.length > 0 || sourceLine.length === 0) {
|
|
67
|
+
out.push(current.trimEnd());
|
|
68
|
+
if (out.length >= safeMaxLines) return ellipsizeWrappedLines(out, safeMaxLines);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return out.slice(0, safeMaxLines);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function ellipsizeWrappedLines(lines: string[], maxLines: number) {
|
|
76
|
+
const sliced = lines.slice(0, maxLines);
|
|
77
|
+
if (sliced.length === 0) return sliced;
|
|
78
|
+
const last = sliced[sliced.length - 1].replace(/[\s.…]+$/g, '');
|
|
79
|
+
sliced[sliced.length - 1] = `${last}…`;
|
|
80
|
+
return sliced;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function estimatePreviewLineCapacity(height: number, zoom: number): number {
|
|
84
|
+
const scale = getLowZoomScale(zoom);
|
|
85
|
+
const available = Math.max(scale.bodyLineHeight, height - scale.padding * 2 - scale.titleFont - scale.bodyFont - scale.gap * 3);
|
|
86
|
+
return Math.max(2, Math.floor(available / scale.bodyLineHeight));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function estimatePreviewCharsPerLine(width: number, zoom: number): number {
|
|
90
|
+
const scale = getLowZoomScale(zoom);
|
|
91
|
+
const available = Math.max(60, width - scale.padding * 2 - Math.max(14, width * 0.02));
|
|
92
|
+
const avgCharWidth = Math.max(6, scale.bodyFont * 0.6);
|
|
93
|
+
return Math.max(8, Math.floor(available / avgCharWidth));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function renderLowZoomPreviewCanvas(
|
|
97
|
+
canvas: HTMLCanvasElement,
|
|
98
|
+
params: {
|
|
99
|
+
path: string;
|
|
100
|
+
file: any;
|
|
101
|
+
width: number;
|
|
102
|
+
height: number;
|
|
103
|
+
zoom: number;
|
|
104
|
+
scrollTop: number;
|
|
105
|
+
accentColor: string;
|
|
106
|
+
isChanged: boolean;
|
|
107
|
+
},
|
|
108
|
+
) {
|
|
109
|
+
const { path, file, width, height, zoom, scrollTop, accentColor } = params;
|
|
110
|
+
const dpr = (globalThis.devicePixelRatio || 1);
|
|
111
|
+
const scale = getLowZoomScale(zoom);
|
|
112
|
+
const ctx = canvas.getContext('2d');
|
|
113
|
+
if (!ctx) return;
|
|
114
|
+
|
|
115
|
+
canvas.width = Math.max(1, Math.floor(width * dpr));
|
|
116
|
+
canvas.height = Math.max(1, Math.floor(height * dpr));
|
|
117
|
+
canvas.style.width = `${width}px`;
|
|
118
|
+
canvas.style.height = `${height}px`;
|
|
119
|
+
|
|
120
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
121
|
+
ctx.clearRect(0, 0, width, height);
|
|
122
|
+
|
|
123
|
+
const gradient = ctx.createLinearGradient(0, 0, 0, height);
|
|
124
|
+
gradient.addColorStop(0, 'rgba(15,23,42,0.96)');
|
|
125
|
+
gradient.addColorStop(1, 'rgba(2,6,23,0.96)');
|
|
126
|
+
ctx.fillStyle = gradient;
|
|
127
|
+
roundRect(ctx, 0, 0, width, height, Math.max(6, scale.radius));
|
|
128
|
+
ctx.fill();
|
|
129
|
+
|
|
130
|
+
ctx.fillStyle = accentColor;
|
|
131
|
+
ctx.fillRect(0, 0, Math.max(10, width * 0.02), height);
|
|
132
|
+
|
|
133
|
+
const leftInset = scale.padding + Math.max(14, width * 0.02);
|
|
134
|
+
const topInset = scale.padding;
|
|
135
|
+
const maxTextWidth = Math.max(40, width - leftInset - scale.padding);
|
|
136
|
+
|
|
137
|
+
ctx.textBaseline = 'top';
|
|
138
|
+
ctx.font = `700 ${scale.titleFont}px "JetBrains Mono", monospace`;
|
|
139
|
+
ctx.fillStyle = '#f8fafc';
|
|
140
|
+
const title = path.split('/').pop() || path;
|
|
141
|
+
ctx.fillText(trimToWidth(ctx, title, maxTextWidth), leftInset, topInset);
|
|
142
|
+
|
|
143
|
+
const subtitleY = topInset + scale.titleFont + scale.gap;
|
|
144
|
+
ctx.font = `${Math.max(scale.bodyFont * 0.78, 8 / Math.max(zoom, 0.08))}px "JetBrains Mono", monospace`;
|
|
145
|
+
ctx.fillStyle = 'rgba(226,232,240,0.72)';
|
|
146
|
+
const subtitle = path.includes('/') ? path.split('/').slice(0, -1).join('/') : 'root';
|
|
147
|
+
ctx.fillText(trimToWidth(ctx, subtitle, maxTextWidth), leftInset, subtitleY);
|
|
148
|
+
|
|
149
|
+
const previewY = subtitleY + Math.max(scale.bodyFont * 0.78, 8 / Math.max(zoom, 0.08)) + scale.gap * 1.5;
|
|
150
|
+
const rawPreview = getLowZoomPreviewText(file, scrollTop) || 'Preview unavailable';
|
|
151
|
+
const wrapped = wrapPreviewText(
|
|
152
|
+
rawPreview,
|
|
153
|
+
estimatePreviewCharsPerLine(width, zoom),
|
|
154
|
+
estimatePreviewLineCapacity(height, zoom),
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
ctx.font = `${scale.bodyFont}px "JetBrains Mono", monospace`;
|
|
158
|
+
ctx.fillStyle = 'rgba(226,232,240,0.92)';
|
|
159
|
+
|
|
160
|
+
const fadeStart = Math.max(previewY, height - scale.bodyLineHeight * 2.2);
|
|
161
|
+
const bodyHeight = Math.max(scale.bodyLineHeight * 2, height - previewY - scale.padding);
|
|
162
|
+
const mask = ctx.createLinearGradient(0, previewY, 0, previewY + bodyHeight);
|
|
163
|
+
mask.addColorStop(0, 'rgba(226,232,240,0.92)');
|
|
164
|
+
mask.addColorStop(Math.max(0, (fadeStart - previewY) / Math.max(1, bodyHeight)), 'rgba(226,232,240,0.92)');
|
|
165
|
+
mask.addColorStop(1, 'rgba(226,232,240,0)');
|
|
166
|
+
ctx.fillStyle = mask;
|
|
167
|
+
|
|
168
|
+
wrapped.forEach((line, index) => {
|
|
169
|
+
const y = previewY + index * scale.bodyLineHeight;
|
|
170
|
+
if (y > height - scale.padding) return;
|
|
171
|
+
ctx.fillText(line, leftInset, y);
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function trimToWidth(ctx: CanvasRenderingContext2D, text: string, maxWidth: number) {
|
|
176
|
+
if (ctx.measureText(text).width <= maxWidth) return text;
|
|
177
|
+
let out = text;
|
|
178
|
+
while (out.length > 1 && ctx.measureText(`${out}…`).width > maxWidth) {
|
|
179
|
+
out = out.slice(0, -1);
|
|
180
|
+
}
|
|
181
|
+
return `${out}…`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function roundRect(ctx: CanvasRenderingContext2D, x: number, y: number, width: number, height: number, radius: number) {
|
|
185
|
+
const r = Math.min(radius, width / 2, height / 2);
|
|
186
|
+
ctx.beginPath();
|
|
187
|
+
ctx.moveTo(x + r, y);
|
|
188
|
+
ctx.arcTo(x + width, y, x + width, y + height, r);
|
|
189
|
+
ctx.arcTo(x + width, y + height, x, y + height, r);
|
|
190
|
+
ctx.arcTo(x, y + height, x, y, r);
|
|
191
|
+
ctx.arcTo(x, y, x + width, y, r);
|
|
192
|
+
ctx.closePath();
|
|
193
|
+
}
|
|
@@ -23,6 +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
27
|
import { materializeViewport } from './xydraw-bridge';
|
|
27
28
|
|
|
28
29
|
// ── Culling state ──────────────────────────────────────────
|
|
@@ -89,7 +90,7 @@ export function getPinnedCards(): Set<string> {
|
|
|
89
90
|
return _pinnedCards;
|
|
90
91
|
}
|
|
91
92
|
|
|
92
|
-
// ── Status colors for
|
|
93
|
+
// ── Status colors for low-zoom cards
|
|
93
94
|
const PILL_COLORS: Record<string, string> = {
|
|
94
95
|
'ts': '#3178c6',
|
|
95
96
|
'tsx': '#3178c6',
|
|
@@ -118,25 +119,30 @@ function getPillColor(path: string, isChanged: boolean): string {
|
|
|
118
119
|
return PILL_COLORS[ext] || '#6b7280'; // Default gray
|
|
119
120
|
}
|
|
120
121
|
|
|
122
|
+
function getSavedScrollTop(ctx: CanvasContext, path: string): number {
|
|
123
|
+
const saved = ctx.positions.get(`scroll:${path}`);
|
|
124
|
+
return saved?.x || 0;
|
|
125
|
+
}
|
|
126
|
+
|
|
121
127
|
/**
|
|
122
128
|
* Create a lightweight pill placeholder for a file.
|
|
123
129
|
* ~3 DOM nodes vs ~100+ for a full card = massive perf win at low zoom.
|
|
124
130
|
* Uses vertical text to fit file names in compact card footprint.
|
|
125
131
|
*/
|
|
126
|
-
function createPillCard(path: string, x: number, y: number, w: number, h: number, isChanged: boolean, animate = false): HTMLElement {
|
|
132
|
+
function createPillCard(ctx: CanvasContext, file: any, path: string, x: number, y: number, w: number, h: number, isChanged: boolean, zoom: number, animate = false): HTMLElement {
|
|
127
133
|
const pill = document.createElement('div');
|
|
128
134
|
pill.className = 'file-pill';
|
|
129
135
|
pill.dataset.path = path;
|
|
136
|
+
pill.dataset.previewMode = 'canvas';
|
|
130
137
|
pill.style.cssText = `
|
|
131
138
|
position: absolute;
|
|
132
139
|
left: ${x}px;
|
|
133
140
|
top: ${y}px;
|
|
134
141
|
width: ${w}px;
|
|
135
142
|
height: ${h}px;
|
|
136
|
-
background: ${getPillColor(path, isChanged)};
|
|
137
143
|
border-radius: 6px;
|
|
138
|
-
opacity: ${animate ? '0' : '0.
|
|
139
|
-
contain: layout style;
|
|
144
|
+
opacity: ${animate ? '0' : '0.94'};
|
|
145
|
+
contain: layout style paint;
|
|
140
146
|
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
|
141
147
|
border: 1px solid rgba(255,255,255,0.12);
|
|
142
148
|
overflow: hidden;
|
|
@@ -144,49 +150,51 @@ function createPillCard(path: string, x: number, y: number, w: number, h: number
|
|
|
144
150
|
user-select: none;
|
|
145
151
|
transition: opacity 0.25s ease, box-shadow 0.2s ease, transform 0.25s ease;
|
|
146
152
|
transform: ${animate ? 'scale(0.92)' : 'scale(1)'};
|
|
153
|
+
background: transparent;
|
|
147
154
|
`;
|
|
148
155
|
|
|
149
|
-
|
|
156
|
+
const canvas = document.createElement('canvas');
|
|
157
|
+
canvas.className = 'file-pill-canvas';
|
|
158
|
+
canvas.style.cssText = 'display:block;width:100%;height:100%;pointer-events:none;';
|
|
159
|
+
pill.appendChild(canvas);
|
|
160
|
+
|
|
161
|
+
(pill as any)._fileData = file;
|
|
162
|
+
updatePillCardLayout(ctx, pill, zoom, isChanged);
|
|
163
|
+
|
|
150
164
|
if (animate) {
|
|
151
165
|
requestAnimationFrame(() => {
|
|
152
|
-
pill.style.opacity = '0.
|
|
166
|
+
pill.style.opacity = '0.94';
|
|
153
167
|
pill.style.transform = 'scale(1)';
|
|
154
168
|
});
|
|
155
169
|
}
|
|
156
170
|
|
|
157
|
-
// File name label — show parent dir for common ambiguous filenames
|
|
158
|
-
const parts = path.split('/');
|
|
159
|
-
const filename = parts.pop() || path;
|
|
160
|
-
const AMBIGUOUS = ['route.ts', 'route.tsx', 'page.tsx', 'page.ts', 'index.ts', 'index.tsx', 'index.js', 'layout.tsx', 'middleware.ts'];
|
|
161
|
-
const name = AMBIGUOUS.includes(filename) && parts.length > 0
|
|
162
|
-
? `${parts[parts.length - 1]}/${filename}`
|
|
163
|
-
: filename;
|
|
164
|
-
const label = document.createElement('span');
|
|
165
|
-
label.className = 'file-pill-label';
|
|
166
|
-
label.textContent = name;
|
|
167
|
-
label.style.cssText = `
|
|
168
|
-
position: absolute;
|
|
169
|
-
top: 50%;
|
|
170
|
-
left: 50%;
|
|
171
|
-
transform: translate(-50%, -50%) rotate(-90deg);
|
|
172
|
-
white-space: nowrap;
|
|
173
|
-
font-size: 48px;
|
|
174
|
-
font-weight: 700;
|
|
175
|
-
color: #fff;
|
|
176
|
-
overflow: hidden;
|
|
177
|
-
text-overflow: ellipsis;
|
|
178
|
-
max-width: ${h - 40}px;
|
|
179
|
-
line-height: 1;
|
|
180
|
-
letter-spacing: 2px;
|
|
181
|
-
font-family: 'JetBrains Mono', monospace;
|
|
182
|
-
text-shadow: 0 2px 8px rgba(0,0,0,0.7);
|
|
183
|
-
pointer-events: none;
|
|
184
|
-
`;
|
|
185
|
-
pill.appendChild(label);
|
|
186
|
-
|
|
187
171
|
return pill;
|
|
188
172
|
}
|
|
189
173
|
|
|
174
|
+
function updatePillCardLayout(ctx: CanvasContext, pill: HTMLElement, zoom: number, isChanged?: boolean) {
|
|
175
|
+
const w = parseFloat(pill.style.width) || 580;
|
|
176
|
+
const h = parseFloat(pill.style.height) || 700;
|
|
177
|
+
const scale = getLowZoomScale(zoom);
|
|
178
|
+
const path = pill.dataset.path || '';
|
|
179
|
+
const canvas = pill.querySelector('.file-pill-canvas') as HTMLCanvasElement | null;
|
|
180
|
+
const file = (pill as any)._fileData || ctx.allFilesData?.find(f => f.path === path) || ctx.commitFilesData?.find(f => f.path === path) || null;
|
|
181
|
+
const changed = isChanged ?? pill.dataset.changed === 'true';
|
|
182
|
+
pill.dataset.zoomBucket = zoom.toFixed(3);
|
|
183
|
+
pill.style.borderRadius = `${Math.max(6, scale.radius)}px`;
|
|
184
|
+
if (!canvas) return;
|
|
185
|
+
|
|
186
|
+
renderLowZoomPreviewCanvas(canvas, {
|
|
187
|
+
path,
|
|
188
|
+
file,
|
|
189
|
+
width: w,
|
|
190
|
+
height: h,
|
|
191
|
+
zoom,
|
|
192
|
+
scrollTop: getSavedScrollTop(ctx, path),
|
|
193
|
+
accentColor: getPillColor(path, changed),
|
|
194
|
+
isChanged: changed,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
190
198
|
/**
|
|
191
199
|
* Computes the visible world-coordinate rectangle from the current
|
|
192
200
|
* viewport size, zoom, and offset.
|
|
@@ -399,9 +407,12 @@ export function performViewportCulling(ctx: CanvasContext) {
|
|
|
399
407
|
);
|
|
400
408
|
|
|
401
409
|
if (inView && !pillCards.has(path)) {
|
|
402
|
-
const pill = createPillCard(path, x, y, cardW, cardH, !!isChanged, true);
|
|
410
|
+
const pill = createPillCard(ctx, file, path, x, y, cardW, cardH, !!isChanged, zoom, true);
|
|
411
|
+
if (isChanged) pill.dataset.changed = 'true';
|
|
403
412
|
ctx.canvas.appendChild(pill);
|
|
404
413
|
pillCards.set(path, pill);
|
|
414
|
+
} else if (inView && pillCards.has(path)) {
|
|
415
|
+
updatePillCardLayout(ctx, pillCards.get(path)!, zoom, !!isChanged);
|
|
405
416
|
} else if (!inView && pillCards.has(path)) {
|
|
406
417
|
removePillForPath(path);
|
|
407
418
|
}
|
|
@@ -424,10 +435,14 @@ export function performViewportCulling(ctx: CanvasContext) {
|
|
|
424
435
|
);
|
|
425
436
|
|
|
426
437
|
if (inView) {
|
|
427
|
-
const
|
|
438
|
+
const file = ctx.allFilesData?.find(f => f.path === path) || ctx.commitFilesData?.find(f => f.path === path) || null;
|
|
439
|
+
const pill = createPillCard(ctx, file, path, x, y, w, h, isChanged, zoom, true);
|
|
440
|
+
if (isChanged) pill.dataset.changed = 'true';
|
|
428
441
|
ctx.canvas.appendChild(pill);
|
|
429
442
|
pillCards.set(path, pill);
|
|
430
443
|
}
|
|
444
|
+
} else {
|
|
445
|
+
updatePillCardLayout(ctx, pillCards.get(path)!, zoom, card.dataset.changed === 'true');
|
|
431
446
|
}
|
|
432
447
|
}
|
|
433
448
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gitmaps",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"gitmaps": "cli.ts"
|
|
@@ -16,13 +16,14 @@
|
|
|
16
16
|
"scripts": {
|
|
17
17
|
"dev": "bun run server.ts",
|
|
18
18
|
"start": "bun run server.ts",
|
|
19
|
-
"test": "bun test app/api/repo/load/route.test.ts app/lib/route-catchall.test.ts app/lib/status-bar.test.ts app/lib/xydraw.test.ts app/lib/transclusion-smoke.test.ts app/lib/repo-select.test.ts packages/galaxydraw/perf.test.ts",
|
|
19
|
+
"test": "bun test app/api/repo/load/route.test.ts app/lib/route-catchall.test.ts app/lib/status-bar.test.ts app/lib/xydraw.test.ts app/lib/transclusion-smoke.test.ts app/lib/repo-select.test.ts app/lib/low-zoom-preview.test.ts packages/galaxydraw/perf.test.ts",
|
|
20
20
|
"smoke:browser": "bun scripts/browser-smoke-local.ts",
|
|
21
21
|
"smoke:browser-tools": "bash scripts/browser-smoke-local.sh",
|
|
22
22
|
"smoke:browser-tools:load": "bash scripts/browser-repo-load-smoke.sh",
|
|
23
23
|
"smoke:browser-tools:guard": "bash scripts/browser-smoke-guard.sh",
|
|
24
24
|
"smoke:browser-tools:self-check": "bash scripts/browser-smoke-self-check.sh",
|
|
25
25
|
"smoke:browser-tools:check": "bash scripts/browser-smoke-check.sh",
|
|
26
|
+
"smoke:docker-image": "bash scripts/docker-image-smoke.sh",
|
|
26
27
|
"prepublishOnly": "echo 'Publishing gitmaps to npm'"
|
|
27
28
|
},
|
|
28
29
|
"keywords": [
|