When showing Inspect view, fit images to client area initially

This commit is contained in:
Taras Greben 2025-12-21 19:59:28 +02:00
commit e852547d01
2 changed files with 50 additions and 24 deletions

View file

@ -1,4 +1,4 @@
/* canvas-ui.js - Shared Canvas Logic (Mobile/Touch Ready) */ /* canvas-ui.js - Shared Canvas Logic (Mobile/Touch Ready + Resize Hook) */
class PanZoomCanvas { class PanZoomCanvas {
constructor(id, onDraw, onClick, onDragPt) { constructor(id, onDraw, onClick, onDragPt) {
@ -17,7 +17,10 @@ class PanZoomCanvas {
this.onClick = onClick; this.onClick = onClick;
this.onDragPt = onDragPt; this.onDragPt = onDragPt;
this.onMouseMove = null; this.onMouseMove = null;
this.onPointerDown = null; // New Hook this.onPointerDown = null;
// New: Resize Callback hook
this.onResize = null;
this.evCache = []; this.evCache = [];
this.prevDiff = -1; this.prevDiff = -1;
@ -30,6 +33,8 @@ class PanZoomCanvas {
this.canvas.width = this.container.clientWidth; this.canvas.width = this.container.clientWidth;
this.canvas.height = this.container.clientHeight; this.canvas.height = this.container.clientHeight;
this.draw(); this.draw();
// Trigger Hook
if (this.onResize) this.onResize(this.canvas.width, this.canvas.height);
} }
}).observe(this.container); }).observe(this.container);
this.initEvents(); this.initEvents();
@ -75,7 +80,6 @@ class PanZoomCanvas {
this.evCache.push(e); this.evCache.push(e);
this.totalDragDist = 0; this.totalDragDist = 0;
// Trigger Hook if defined (Before internal logic)
if (this.onPointerDown) this.onPointerDown(e); if (this.onPointerDown) this.onPointerDown(e);
const coords = this.getImgCoords(e.clientX, e.clientY); const coords = this.getImgCoords(e.clientX, e.clientY);
@ -100,7 +104,6 @@ class PanZoomCanvas {
if (index > -1) this.evCache[index] = e; if (index > -1) this.evCache[index] = e;
if (this.evCache.length === 2) { if (this.evCache.length === 2) {
// Pinch Zoom
const dx = this.evCache[0].clientX - this.evCache[1].clientX; const dx = this.evCache[0].clientX - this.evCache[1].clientX;
const dy = this.evCache[0].clientY - this.evCache[1].clientY; const dy = this.evCache[0].clientY - this.evCache[1].clientY;
const curDiff = Math.hypot(dx, dy); const curDiff = Math.hypot(dx, dy);
@ -112,14 +115,13 @@ class PanZoomCanvas {
this.zoomAt(cx, cy, factor); this.zoomAt(cx, cy, factor);
} }
this.prevDiff = curDiff; this.prevDiff = curDiff;
this.totalDragDist += 20; // Ensure pinch doesn't trigger click this.totalDragDist += 20;
return; return;
} }
if (this.isDragging && this.evCache.length === 1) { if (this.isDragging && this.evCache.length === 1) {
const dx = e.clientX - this.lm.x; const dx = e.clientX - this.lm.x;
const dy = e.clientY - this.lm.y; const dy = e.clientY - this.lm.y;
this.totalDragDist += Math.hypot(dx, dy); this.totalDragDist += Math.hypot(dx, dy);
if (this.activePtIdx !== -1) { if (this.activePtIdx !== -1) {
@ -146,7 +148,6 @@ class PanZoomCanvas {
if (this.evCache.length < 2) this.prevDiff = -1; if (this.evCache.length < 2) this.prevDiff = -1;
if (this.evCache.length === 0) { if (this.evCache.length === 0) {
// Threshold increased to 20px for mobile tap tolerance
if (this.totalDragDist < 20) { if (this.totalDragDist < 20) {
if (this.activePtIdx === -1 && this.onClick) { if (this.activePtIdx === -1 && this.onClick) {
const coords = this.getImgCoords(e.clientX, e.clientY); const coords = this.getImgCoords(e.clientX, e.clientY);

View file

@ -71,12 +71,18 @@ class Inspector {
} }
async renderGrid() { async renderGrid() {
// --- 1. Snapshot State (Zoom/Pan) --- // 1. Snapshot State
// We save state ONLY if the viewer exists.
// We also capture 'interacted' status to know if we should restore specific zoom or just auto-fit.
const savedStates = {}; const savedStates = {};
if (this.viewers) { if (this.viewers) {
Object.entries(this.viewers).forEach(([id, v]) => { Object.entries(this.viewers).forEach(([id, v]) => {
// We clone the object to ensure safety, though not strictly required if (v.t) {
if (v.t) savedStates[id] = { ...v.t }; savedStates[id] = {
t: { ...v.t },
interacted: v.userInteracted || false // Capture the flag from the instance
};
}
}); });
} }
@ -131,13 +137,12 @@ class Inspector {
this.grid.appendChild(cell); this.grid.appendChild(cell);
let wasActiveBeforeDown = false; let wasActiveBeforeDown = false;
let stateRestored = false;
const viewer = new PanZoomCanvas(cvs.id, const viewer = new PanZoomCanvas(cvs.id,
(ctx, k) => this.drawOverlay(id, ctx, k), (ctx, k) => this.drawOverlay(id, ctx, k),
async (x, y, e) => { async (x, y, e) => {
if (e.button !== 0) return; if (e.button !== 0) return;
if (wasActiveBeforeDown) { if (wasActiveBeforeDown) {
const hit = await this.handleNodeClick(id, x, y); const hit = await this.handleNodeClick(id, x, y);
if (!hit) { if (!hit) {
@ -150,10 +155,16 @@ class Inspector {
} }
); );
// Initialize interaction flag on the instance
viewer.userInteracted = false;
// --- EVENT HANDLERS ---
viewer.onPointerDown = (e) => { viewer.onPointerDown = (e) => {
viewer.userInteracted = true; // Mark as dirty/manual
if (e.isPrimary || e.button === 0) { if (e.isPrimary || e.button === 0) {
wasActiveBeforeDown = (this.masterId === id); wasActiveBeforeDown = (this.masterId === id);
if (this.masterId !== id) { if (this.masterId !== id) {
this.masterId = id; this.masterId = id;
} }
@ -162,10 +173,33 @@ class Inspector {
} }
}; };
// Detect wheel usage to stop auto-fit
cvs.addEventListener('wheel', () => { viewer.userInteracted = true; });
viewer.onMouseMove = (x, y) => { viewer.onMouseMove = (x, y) => {
if(this.masterId === id) this.syncCursors(id, x, y); if(this.masterId === id) this.syncCursors(id, x, y);
}; };
// --- SMART RESIZE/FIT LOGIC ---
const updateView = () => {
if (!viewer.bmp || cvs.width < 20 || cvs.height < 20) return;
// 1. Restore State (Only if user had manually interacted before)
if (savedStates[id] && savedStates[id].interacted && !stateRestored) {
viewer.t = savedStates[id].t;
stateRestored = true;
viewer.userInteracted = true; // Keep it marked as manual
viewer.draw();
}
// 2. Auto-Fit (Default behavior, continues on resize until user interacts)
else if (!viewer.userInteracted) {
viewer.fit();
}
};
// Attempt fit on Resize
viewer.onResize = (w, h) => updateView();
cvs.addEventListener('contextmenu', (e) => { cvs.addEventListener('contextmenu', (e) => {
e.preventDefault(); e.stopPropagation(); e.preventDefault(); e.stopPropagation();
this.masterId = null; this.masterId = null;
@ -180,18 +214,9 @@ class Inspector {
try { try {
const bmp = await createImageBitmap(imgRec.blob); const bmp = await createImageBitmap(imgRec.blob);
viewer.setImage(bmp); viewer.setImage(bmp);
if (cell.clientWidth && cell.clientHeight) {
viewer.canvas.width = cell.clientWidth;
viewer.canvas.height = cell.clientHeight;
}
// --- 2. Restore State or Fit --- // Attempt fit on Load
if (savedStates[id]) { updateView();
viewer.t = savedStates[id];
viewer.draw(); // Force redraw with restored transform
} else {
viewer.fit();
}
if(imgRec.name.toLowerCase().includes('bot') && !imgRec.name.toLowerCase().includes('top')) { if(imgRec.name.toLowerCase().includes('bot') && !imgRec.name.toLowerCase().includes('top')) {
viewer.setMirror(true); viewer.setMirror(true);