commit 25c34220d2a2f8155c1e708ac815b87b0d52d503
parent e894d2e9d2a42aa68de9d5a72302b970f29ebcd9
Author: Hunter
Date:   Mon, 20 Oct 2025 12:57:04 -0400

first pass at option+click to rotate

Diffstat:
Mscript.js | 56+++++++++++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 49 insertions(+), 7 deletions(-)

diff --git a/script.js b/script.js @@ -159,6 +159,7 @@ function addImage(src, xCell, yCell, widthCells, heightCells) { panX: 0, panY: 0, userScale: 1, // user zoom level (1-5) + rotation: 0, // rotation in degrees (0, 90, 180, 270) // Store natural image dimensions for calculations naturalWidth: 0, naturalHeight: 0, @@ -197,10 +198,15 @@ function calculatePanBounds(imageData) { return { maxPanX: 0, maxPanY: 0 }; } + // When rotated 90° or 270°, the image dimensions are effectively swapped + const isRotated90or270 = imageData.rotation % 180 !== 0; + const effectiveWidth = isRotated90or270 ? imageData.naturalHeight : imageData.naturalWidth; + const effectiveHeight = isRotated90or270 ? imageData.naturalWidth : imageData.naturalHeight; + // Calculate actual rendered size with both base scale and user scale const totalScale = imageData.baseScale * imageData.userScale; - const renderedWidth = imageData.naturalWidth * totalScale; - const renderedHeight = imageData.naturalHeight * totalScale; + const renderedWidth = effectiveWidth * totalScale; + const renderedHeight = effectiveHeight * totalScale; // Calculate maximum pan in each direction const maxPanX = Math.max(0, (renderedWidth - containerWidth) / 2); @@ -231,8 +237,14 @@ function updateImagePosition(img) { if (img.naturalWidth > 0 && img.naturalHeight > 0) { const containerWidth = img.widthCells * CELL_SIZE_PX; const containerHeight = img.heightCells * CELL_SIZE_PX; - const scaleX = containerWidth / img.naturalWidth; - const scaleY = containerHeight / img.naturalHeight; + + // When rotated 90° or 270°, the image dimensions are effectively swapped + const isRotated90or270 = img.rotation % 180 !== 0; + const effectiveWidth = isRotated90or270 ? img.naturalHeight : img.naturalWidth; + const effectiveHeight = isRotated90or270 ? img.naturalWidth : img.naturalHeight; + + const scaleX = containerWidth / effectiveWidth; + const scaleY = containerHeight / effectiveHeight; img.baseScale = Math.max(scaleX, scaleY); // Reclamp pan after recalculating base scale @@ -243,8 +255,9 @@ function updateImagePosition(img) { const imgElement = img.container.querySelector('img'); if (imgElement) { const totalScale = img.baseScale * img.userScale; - // Transform: translate from center (-50%, -50%), then pan, then scale - imgElement.style.transform = `translate(-50%, -50%) translate(${img.panX}px, ${img.panY}px) scale(${totalScale})`; + // Transform: translate from center (-50%, -50%), scale, rotate, then pan + // Pan is applied after rotation so it stays relative to the image's rotated state + imgElement.style.transform = `translate(-50%, -50%) scale(${totalScale}) rotate(${img.rotation}deg) translate(${img.panX}px, ${img.panY}px)`; } } @@ -277,6 +290,32 @@ function setupImageHandlers(imageData) { return; } + // Option-click (or Alt-click on Windows/Linux) to rotate + if (e.altKey) { + // Rotate 90 degrees clockwise + const oldRotation = imageData.rotation; + imageData.rotation = (imageData.rotation + 90) % 360; + + // Don't rotate pan coordinates - they stay in the image's original coordinate system + // The CSS transform applies rotation before pan, so pan is relative to the rotated image + + // When rotating between portrait and landscape (90° or 270°), we need to swap dimensions + if ((oldRotation % 180 === 0 && imageData.rotation % 180 !== 0) || + (oldRotation % 180 !== 0 && imageData.rotation % 180 === 0)) { + // Swap width and height + const temp = imageData.widthCells; + imageData.widthCells = imageData.heightCells; + imageData.heightCells = temp; + + // Adjust position to keep image within bounds after dimension swap + imageData.xCell = Math.max(0, Math.min(GRID_COLS - imageData.widthCells, imageData.xCell)); + imageData.yCell = Math.max(0, Math.min(GRID_ROWS - imageData.heightCells, imageData.yCell)); + } + + updateImagePosition(imageData); + return; + } + // Cmd-click (or Ctrl-click on Windows/Linux) to duplicate if (e.metaKey || e.ctrlKey) { // Calculate target position (2 cells to the right if there's room) @@ -306,11 +345,12 @@ function setupImageHandlers(imageData) { imageData.heightCells ); - // Copy pan and zoom settings from original + // Copy pan, zoom, and rotation settings from original // Store the original settings to apply after image loads const originalPanX = imageData.panX; const originalPanY = imageData.panY; const originalUserScale = imageData.userScale; + const originalRotation = imageData.rotation; // Override the onload to copy settings const newImg = newImageData.container.querySelector('img'); @@ -323,6 +363,7 @@ function setupImageHandlers(imageData) { newImageData.panX = originalPanX; newImageData.panY = originalPanY; newImageData.userScale = originalUserScale; + newImageData.rotation = originalRotation; updateImagePosition(newImageData); }; @@ -331,6 +372,7 @@ function setupImageHandlers(imageData) { newImageData.panX = originalPanX; newImageData.panY = originalPanY; newImageData.userScale = originalUserScale; + newImageData.rotation = originalRotation; updateImagePosition(newImageData); }