commit 906fb3fc22b1e865659a083d4de02d38b5bddd8c
parent 204010b9842201c033a1fab89cc95c593543f71e
Author: Hunter
Date:   Wed, 29 Jul 2026 15:17:56 -0400

clamp dropped images to grid bounds instead of wrapping to 0,0

Diffstat:
Mresources/images.js | 33++++++++++++++++++++-------------
Mresources/input.js | 12+++++-------
2 files changed, 25 insertions(+), 20 deletions(-)

diff --git a/resources/images.js b/resources/images.js @@ -79,24 +79,31 @@ async function processAndAddImages(files, dropX = 0, dropY = 0) { imageDataArray.push({ idx, ...data }); } + // Shift the whole fan back onto the grid before placing anything, so a drop near an edge keeps + // its diagonal spacing instead of collapsing image by image + const fanMaxX = Math.max(...imageDataArray.map(({ idx, widthCells }) => baseXCell + idx + widthCells)); + const fanMaxY = Math.max(...imageDataArray.map(({ idx, heightCells }) => baseYCell + idx + heightCells)); + const fanShiftX = Math.max(Math.min(0, GRID_COLS - fanMaxX), -baseXCell); + const fanShiftY = Math.max(Math.min(0, GRID_ROWS - fanMaxY), -baseYCell); + let wrappedOffset = 0; + const placed = []; for (const { idx, dataUrl, widthCells, heightCells, naturalWidth, naturalHeight } of imageDataArray) { - // Calculate position with diagonal offset - let xCell = baseXCell + idx; - let yCell = baseYCell + idx; - - // If out of bounds or would overlap with wrapped images, wrap to next diagonal position - const outOfBounds = xCell < 0 || yCell < 0 || - xCell + widthCells > GRID_COLS || - yCell + heightCells > GRID_ROWS; - const overlapsWrapped = xCell < wrappedOffset || yCell < wrappedOffset; - - if (outOfBounds || overlapsWrapped) { - xCell = wrappedOffset; - yCell = wrappedOffset; + // Calculate position with diagonal offset, kept as close to the drop point as the grid allows + const maxXCell = GRID_COLS - widthCells; + const maxYCell = GRID_ROWS - heightCells; + let xCell = Math.max(0, Math.min(baseXCell + idx + fanShiftX, maxXCell)); + let yCell = Math.max(0, Math.min(baseYCell + idx + fanShiftY, maxYCell)); + + // Clamping can pile a multi-image drop onto one cell; cascade those from the top-left instead + const stacked = placed.some(p => p.xCell === xCell && p.yCell === yCell); + if (stacked) { + xCell = Math.min(wrappedOffset, maxXCell); + yCell = Math.min(wrappedOffset, maxYCell); wrappedOffset++; } + placed.push({ xCell, yCell }); const imageData = addImage(dataUrl, xCell, yCell, widthCells, heightCells, { naturalWidth, naturalHeight }); imageData.container.style.zIndex = baseZIndex + idx; diff --git a/resources/input.js b/resources/input.js @@ -26,15 +26,13 @@ document.addEventListener('paste', async (e) => { return; } - // Use last known mouse position relative to grid, fall back to (0, 0) + // Use last known mouse position relative to grid; off-grid positions are clamped downstream, + // so only a cursor we've never seen falls back to (0, 0) const gridRect = grid.getBoundingClientRect(); - const mouseXInGrid = lastMouseX - gridRect.left; - const mouseYInGrid = lastMouseY - gridRect.top; - const isOnGrid = mouseXInGrid >= 0 && mouseYInGrid >= 0 && - mouseXInGrid <= gridRect.width && mouseYInGrid <= gridRect.height; + const hasCursor = lastMouseX >= 0 && lastMouseY >= 0; - const dropX = isOnGrid ? mouseXInGrid : 0; - const dropY = isOnGrid ? mouseYInGrid : 0; + const dropX = hasCursor ? lastMouseX - gridRect.left : 0; + const dropY = hasCursor ? lastMouseY - gridRect.top : 0; await processAndAddImages(imageFiles, dropX, dropY); });