gitmaps 1.1.16 → 1.1.18

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
@@ -23,7 +23,7 @@
23
23
  */
24
24
  import { measure } from 'measure-fn';
25
25
  import type { CanvasContext } from './context';
26
- import { estimatePreviewMaxScroll, getLowZoomScale, renderLowZoomPreviewCanvas } from './low-zoom-preview';
26
+ import { collectPreviewDiffMarkers, estimatePreviewMaxScroll, getLowZoomScale, getPreviewScrollMetrics, renderLowZoomPreviewCanvas } from './low-zoom-preview';
27
27
  import { materializeViewport } from './xydraw-bridge';
28
28
 
29
29
  // ── Culling state ──────────────────────────────────────────
@@ -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.
@@ -204,6 +210,16 @@ function createPillCard(ctx: CanvasContext, file: any, path: string, x: number,
204
210
  canvas.style.cssText = 'display:block;width:100%;height:100%;pointer-events:none;';
205
211
  pill.appendChild(canvas);
206
212
 
213
+ const diffOverlay = document.createElement('div');
214
+ diffOverlay.className = 'file-pill-diff-overlay';
215
+ diffOverlay.style.cssText = 'position:absolute;top:10px;right:26px;bottom:10px;width:12px;pointer-events:none;z-index:2;';
216
+ pill.appendChild(diffOverlay);
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
+
207
223
  (pill as any)._fileData = file;
208
224
  updatePillCardLayout(ctx, pill, zoom, isChanged);
209
225
 
@@ -223,8 +239,11 @@ function updatePillCardLayout(ctx: CanvasContext, pill: HTMLElement, zoom: numbe
223
239
  const scale = getLowZoomScale(zoom);
224
240
  const path = pill.dataset.path || '';
225
241
  const canvas = pill.querySelector('.file-pill-canvas') as HTMLCanvasElement | null;
226
- const file = (pill as any)._fileData || ctx.allFilesData?.find(f => f.path === path) || ctx.commitFilesData?.find(f => f.path === path) || null;
242
+ const diffOverlay = pill.querySelector('.file-pill-diff-overlay') as HTMLElement | null;
243
+ const scrollOverlay = pill.querySelector('.file-pill-scroll-overlay') as HTMLElement | null;
244
+ const file = resolvePreviewFile(ctx, path, (pill as any)._fileData);
227
245
  const changed = isChanged ?? pill.dataset.changed === 'true';
246
+ const scrollTop = getSavedScrollTop(ctx, path);
228
247
  pill.dataset.zoomBucket = zoom.toFixed(3);
229
248
  pill.style.borderRadius = `${Math.max(6, scale.radius)}px`;
230
249
  if (!canvas) return;
@@ -235,10 +254,116 @@ function updatePillCardLayout(ctx: CanvasContext, pill: HTMLElement, zoom: numbe
235
254
  width: w,
236
255
  height: h,
237
256
  zoom,
238
- scrollTop: getSavedScrollTop(ctx, path),
257
+ scrollTop,
239
258
  accentColor: getPillColor(path, changed),
240
259
  isChanged: changed,
241
260
  });
261
+
262
+ const metrics = getPreviewScrollMetrics(file, h, zoom, scrollTop);
263
+
264
+ if (diffOverlay) {
265
+ const markers = collectPreviewDiffMarkers(file, metrics.totalLines);
266
+ diffOverlay.innerHTML = '';
267
+ diffOverlay.style.top = `${metrics.trackPadding}px`;
268
+ diffOverlay.style.bottom = `${metrics.trackPadding}px`;
269
+ diffOverlay.style.right = '26px';
270
+ diffOverlay.style.width = '12px';
271
+ diffOverlay.style.pointerEvents = 'auto';
272
+
273
+ for (const marker of markers) {
274
+ const btn = document.createElement('button');
275
+ const markerHeight = marker.height === 1 ? metrics.trackHeight : 10;
276
+ const y = marker.height === 1 ? 0 : marker.ratio * Math.max(0, metrics.trackHeight - markerHeight);
277
+ btn.type = 'button';
278
+ btn.className = 'file-pill-diff-marker';
279
+ btn.title = marker.color === '#22c55e' ? 'Jump to added lines' : 'Jump to deleted lines';
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;`;
281
+ btn.addEventListener('mousedown', (e) => {
282
+ e.preventDefault();
283
+ e.stopPropagation();
284
+ });
285
+ btn.addEventListener('click', (e) => {
286
+ e.preventDefault();
287
+ e.stopPropagation();
288
+ const maxScroll = estimatePreviewMaxScroll(file, h, zoom);
289
+ const next = marker.height === 1 ? 0 : Math.max(0, Math.min(maxScroll, marker.ratio * maxScroll));
290
+ const key = `scroll:${path}`;
291
+ const existing = ctx.positions.get(key) || {};
292
+ ctx.positions.set(key, { ...existing, x: next, y: existing.y || 0 });
293
+ try {
294
+ const { debounceSaveScroll } = require('./cards');
295
+ debounceSaveScroll(ctx, path, next);
296
+ } catch { }
297
+ updatePillCardLayout(ctx, pill, zoom, changed);
298
+ });
299
+ diffOverlay.appendChild(btn);
300
+ }
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
+ }
242
367
  }
243
368
 
244
369
  /**
@@ -482,7 +607,7 @@ export function performViewportCulling(ctx: CanvasContext) {
482
607
  );
483
608
 
484
609
  if (inView) {
485
- const file = ctx.allFilesData?.find(f => f.path === path) || ctx.commitFilesData?.find(f => f.path === path) || null;
610
+ const file = resolvePreviewFile(ctx, path);
486
611
  const pill = createPillCard(ctx, file, path, x, y, w, h, isChanged, zoom, true);
487
612
  if (isChanged) pill.dataset.changed = 'true';
488
613
  ctx.canvas.appendChild(pill);
@@ -661,7 +786,7 @@ export function setupPillInteraction(ctx: CanvasContext) {
661
786
  const path = pill.dataset.path || '';
662
787
  if (!path) return;
663
788
 
664
- const file = (pill as any)._fileData || ctx.allFilesData?.find(f => f.path === path) || ctx.commitFilesData?.find(f => f.path === path) || null;
789
+ const file = resolvePreviewFile(ctx, path, (pill as any)._fileData);
665
790
  const zoom = ctx.snap().context.zoom || 1;
666
791
  const height = parseFloat(pill.style.height) || 700;
667
792
  const current = getSavedScrollTop(ctx, path);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitmaps",
3
- "version": "1.1.16",
3
+ "version": "1.1.18",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "gitmaps": "cli.ts"