refactor/clean up

Hunter
Wednesday, October 22nd, 2025 at 2:45 PM

script.js+81-133

script.js
253  	imageData.panY = Math.max(-maxPanY, Math.min(maxPanY, imageData.panY));
254  }
255  
256 +// Helper to transform screen-space coordinates to image coordinate space (accounting for rotation)
257 +function rotatePoint(x, y, angleDegrees) {
258 +	const angle = -angleDegrees * Math.PI / 180;
259 +	const cos = Math.cos(angle);
260 +	const sin = Math.sin(angle);
261 +	return {
262 +		x: x * cos - y * sin,
263 +		y: x * sin + y * cos
264 +	};
265 +}
266 +
267 +// Helper to apply pan adjustment based on screen delta
268 +function applyPanDelta(imageData, screenDeltaX, screenDeltaY) {
269 +	const rotated = rotatePoint(screenDeltaX, screenDeltaY, imageData.rotation);
270 +	const totalScale = imageData.baseScale * imageData.userScale;
271 +	imageData.panX += rotated.x / totalScale;
272 +	imageData.panY += rotated.y / totalScale;
273 +}
274 +
275 +// Helper to calculate zoom-centered pan adjustment
276 +function adjustPanForZoom(imageData, cursorX, cursorY, oldTotalScale, newTotalScale) {
277 +	const rotated = rotatePoint(cursorX, cursorY, imageData.rotation);
278 +	const scaleDiff = 1/newTotalScale - 1/oldTotalScale;
279 +	imageData.panX += rotated.x * scaleDiff;
280 +	imageData.panY += rotated.y * scaleDiff;
281 +}
282 +
283  function updateImagePosition(img) {
284  	const cellSize = getCellSize();
285  	img.container.style.left = img.xCell * cellSize.width + 'px';

355  	// Unified pointer start handler
356  	function handlePointerStart(clientX, clientY, isTouch = false) {
357  		// Clear any existing timers from other images
    -		if (longPressTimer) {
    -			clearTimeout(longPressTimer);
    -			longPressTimer = null;
    -		}
358 +		clearDragState();
359  
360  		// Lock cursor to move during drag operation
361  		document.body.style.cursor = 'move';

404  			// Two fingers - prepare for pinch/pan
405  			e.preventDefault();
406  
    -			// Cancel any ongoing drag and long press timer
    -			if (longPressTimer) {
    -				clearTimeout(longPressTimer);
    -				longPressTimer = null;
    -			}
    -			if (dragState) {
    -				dragState.image.container.classList.remove('dragging');
    -				dragState = null;
    -				document.body.style.cursor = '';
    -				document.body.classList.remove('dragging');
    -			}
407 +			// Cancel any ongoing drag
408 +			clearDragState();
409  
410  			const touch1 = e.touches[0];
411  			const touch2 = e.touches[1];

468  				const newUserScale = Math.max(1, Math.min(5, touchState.initialScale * scaleFactor));
469  				const newTotalScale = imageData.baseScale * newUserScale;
470  
    -				// Transform cursor position to account for rotation
    -				const angle = -imageData.rotation * Math.PI / 180;
    -				const cos = Math.cos(angle);
    -				const sin = Math.sin(angle);
    -				const rotatedCursorX = cursorX * cos - cursorY * sin;
    -				const rotatedCursorY = cursorX * sin + cursorY * cos;
    -
    -				// Calculate new pan to keep zoom centered on pinch point
    -				imageData.panX = rotatedCursorX * (1/newTotalScale - 1/oldTotalScale) + touchState.lastPanX;
    -				imageData.panY = rotatedCursorY * (1/newTotalScale - 1/oldTotalScale) + touchState.lastPanY;
471 +				// Restore previous pan state and apply zoom adjustment
472 +				imageData.panX = touchState.lastPanX;
473 +				imageData.panY = touchState.lastPanY;
474 +				adjustPanForZoom(imageData, cursorX, cursorY, oldTotalScale, newTotalScale);
475  				imageData.userScale = newUserScale;
476  
477  				clampPan(imageData);

479  				touchState.lastPanY = imageData.panY;
480  			} else {
481  				// Two-finger pan
    -				const deltaX = centerMoveX;
    -				const deltaY = centerMoveY;
    -
    -				// Transform deltas to account for image rotation
    -				const angle = -imageData.rotation * Math.PI / 180;
    -				const cos = Math.cos(angle);
    -				const sin = Math.sin(angle);
    -				const rotatedDeltaX = deltaX * cos - deltaY * sin;
    -				const rotatedDeltaY = deltaX * sin + deltaY * cos;
    -
    -				// Convert screen-space delta to pre-scale image space
    -				// Note: positive delta means moving fingers right/down, which should move image right/down
    -				const totalScale = imageData.baseScale * imageData.userScale;
    -				imageData.panX = touchState.lastPanX + rotatedDeltaX / totalScale;
    -				imageData.panY = touchState.lastPanY + rotatedDeltaY / totalScale;
482 +				imageData.panX = touchState.lastPanX;
483 +				imageData.panY = touchState.lastPanY;
484 +				applyPanDelta(imageData, centerMoveX, centerMoveY);
485  
486  				clampPan(imageData);
487  				touchState.lastPanX = imageData.panX;

619  		handlePointerStart(e.clientX, e.clientY);
620  	});
621  
    -	// Resizing
622 +	// Resizing - unified handler for mouse and touch
623 +	function startResize(clientX, clientY, direction, cursorStyle = null) {
624 +		if (cursorStyle) {
625 +			document.body.style.cursor = cursorStyle;
626 +		}
627 +		document.body.classList.add('resizing');
628 +
629 +		resizeState = {
630 +			image: imageData,
631 +			direction: direction,
632 +			startX: clientX,
633 +			startY: clientY,
634 +			startXCell: imageData.xCell,
635 +			startYCell: imageData.yCell,
636 +			startWidthCells: imageData.widthCells,
637 +			startHeightCells: imageData.heightCells
638 +		};
639 +		container.classList.add('resizing');
640 +	}
641 +
642  	container.querySelectorAll('.resize-handle').forEach(handle => {
643  		handle.addEventListener('mousedown', (e) => {
644  			e.preventDefault();
645  			e.stopPropagation();
    -
    -			// Get the cursor style from the handle and apply it to the body
646  			const cursorStyle = window.getComputedStyle(handle).cursor;
    -			document.body.style.cursor = cursorStyle;
    -			document.body.classList.add('resizing');
    -
    -			resizeState = {
    -				image: imageData,
    -				direction: handle.dataset.direction,
    -				startX: e.clientX,
    -				startY: e.clientY,
    -				startXCell: imageData.xCell,
    -				startYCell: imageData.yCell,
    -				startWidthCells: imageData.widthCells,
    -				startHeightCells: imageData.heightCells
    -			};
    -			container.classList.add('resizing');
647 +			startResize(e.clientX, e.clientY, handle.dataset.direction, cursorStyle);
648  		});
649  
    -		// Touch support for resize handles
650  		handle.addEventListener('touchstart', (e) => {
651  			e.preventDefault();
652  			e.stopPropagation();
    -
653  			const touch = e.touches[0];
    -			document.body.classList.add('resizing');
    -
    -			resizeState = {
    -				image: imageData,
    -				direction: handle.dataset.direction,
    -				startX: touch.clientX,
    -				startY: touch.clientY,
    -				startXCell: imageData.xCell,
    -				startYCell: imageData.yCell,
    -				startWidthCells: imageData.widthCells,
    -				startHeightCells: imageData.heightCells
    -			};
    -			container.classList.add('resizing');
654 +			startResize(touch.clientX, touch.clientY, handle.dataset.direction);
655  		}, { passive: false });
656  	});
657  

667  		if (e.ctrlKey) {
668  			// Zoom at cursor position
669  			const rect = container.getBoundingClientRect();
    -			const cursorX = e.clientX - rect.left - rect.width / 2; // Screen pixels from center
    -			const cursorY = e.clientY - rect.top - rect.height / 2; // Screen pixels from center
670 +			const cursorX = e.clientX - rect.left - rect.width / 2;
671 +			const cursorY = e.clientY - rect.top - rect.height / 2;
672  
673  			const oldUserScale = imageData.userScale;
674  			const oldTotalScale = imageData.baseScale * oldUserScale;

676  			const newUserScale = Math.max(1, Math.min(5, oldUserScale * (1 + zoomDelta)));
677  			const newTotalScale = imageData.baseScale * newUserScale;
678  
    -			// Transform cursor position to account for rotation
    -			// The CSS transform applies rotation before pan, so we need to rotate the cursor position
    -			// into the image's coordinate system (inverse rotation)
    -			const angle = -imageData.rotation * Math.PI / 180; // Negative for inverse rotation
    -			const cos = Math.cos(angle);
    -			const sin = Math.sin(angle);
    -			const rotatedCursorX = cursorX * cos - cursorY * sin;
    -			const rotatedCursorY = cursorX * sin + cursorY * cos;
    -
    -			// The point under the cursor in pre-scale image space is:
    -			// imagePoint = (cursorScreen / oldTotalScale) - panX
    -			// We want: (imagePoint + newPanX) * newTotalScale = cursorScreen
    -			// So: newPanX = (cursorScreen / newTotalScale) - imagePoint
    -			//            = (cursorScreen / newTotalScale) - (cursorScreen / oldTotalScale - panX)
    -			//            = cursorScreen * (1/newTotalScale - 1/oldTotalScale) + panX
    -			imageData.panX = rotatedCursorX * (1/newTotalScale - 1/oldTotalScale) + imageData.panX;
    -			imageData.panY = rotatedCursorY * (1/newTotalScale - 1/oldTotalScale) + imageData.panY;
679 +			adjustPanForZoom(imageData, cursorX, cursorY, oldTotalScale, newTotalScale);
680  			imageData.userScale = newUserScale;
681  
    -			// Clamp after zooming to prevent whitespace
682  			clampPan(imageData);
683  		} else {
684  			// Pan (two-finger scroll on macOS trackpad)
    -			// Transform deltas to account for image rotation
    -			const angle = -imageData.rotation * Math.PI / 180; // Negative because we want screen-to-image transform
    -			const cos = Math.cos(angle);
    -			const sin = Math.sin(angle);
    -
    -			// Rotate the delta vector by the negative of the image rotation
    -			const rotatedDeltaX = e.deltaX * cos - e.deltaY * sin;
    -			const rotatedDeltaY = e.deltaX * sin + e.deltaY * cos;
    -
    -			// Convert screen-space delta to pre-scale image space
    -			const totalScale = imageData.baseScale * imageData.userScale;
    -			imageData.panX -= rotatedDeltaX / totalScale;
    -			imageData.panY -= rotatedDeltaY / totalScale;
    -
    -			// Clamp pan to prevent whitespace
685 +			applyPanDelta(imageData, -e.deltaX, -e.deltaY);
686  			clampPan(imageData);
687  		}
688  

690  	}, { passive: false });
691  }
692  
693 +// Helper to clear drag state and timers
694 +function clearDragState() {
695 +	if (longPressTimer) {
696 +		clearTimeout(longPressTimer);
697 +		longPressTimer = null;
698 +	}
699 +	if (dragState) {
700 +		dragState.image.container.classList.remove('dragging');
701 +		dragState = null;
702 +		document.body.style.cursor = '';
703 +		document.body.classList.remove('dragging');
704 +	}
705 +}
706 +
707  function handleMove(clientX, clientY) {
708  	if (dragState) {
709  		const dx = clientX - dragState.startX;

712  		if (dragState.isPanMode) {
713  			// Pan mode - move the image within its container
714  			const imageData = dragState.image;
    -
    -			// Transform deltas to account for image rotation
    -			const angle = -imageData.rotation * Math.PI / 180;
    -			const cos = Math.cos(angle);
    -			const sin = Math.sin(angle);
    -			const rotatedDeltaX = dx * cos - dy * sin;
    -			const rotatedDeltaY = dx * sin + dy * cos;
    -
    -			// Convert screen-space delta to pre-scale image space
    -			const totalScale = imageData.baseScale * imageData.userScale;
    -			imageData.panX = dragState.initialPanX + rotatedDeltaX / totalScale;
    -			imageData.panY = dragState.initialPanY + rotatedDeltaY / totalScale;
    -
715 +			imageData.panX = dragState.initialPanX;
716 +			imageData.panY = dragState.initialPanY;
717 +			applyPanDelta(imageData, dx, dy);
718  			clampPan(imageData);
719  			updateImagePosition(imageData);
720  		} else {

791  }
792  
793  function handleEnd() {
    -	if (longPressTimer) {
    -		clearTimeout(longPressTimer);
    -		longPressTimer = null;
    -	}
    -	if (dragState) {
    -		dragState.image.container.classList.remove('dragging');
    -		dragState.image.container.style.opacity = '';
    -		dragState = null;
    -		// Restore the default cursor
    -		document.body.style.cursor = '';
    -		document.body.classList.remove('dragging');
    -	}
794 +	clearDragState();
795  	if (resizeState) {
796  		resizeState.image.container.classList.remove('resizing');
797  		resizeState = null;
    -		// Restore the default cursor
798  		document.body.style.cursor = '';
799  		document.body.classList.remove('resizing');
800  	}