diff --git a/src/lib/Canvas.svelte b/src/lib/Canvas.svelte index d08868f..580d6a1 100644 --- a/src/lib/Canvas.svelte +++ b/src/lib/Canvas.svelte @@ -53,8 +53,8 @@ if (!files || files.length === 0) return; const rect = container.getBoundingClientRect(); - const dropX = (e.clientX - rect.left - state.viewport.x) / state.viewport.zoom; - const dropY = (e.clientY - rect.top - state.viewport.y) / state.viewport.zoom; + const dropX = state.snap((e.clientX - rect.left - state.viewport.x) / state.viewport.zoom); + const dropY = state.snap((e.clientY - rect.top - state.viewport.y) / state.viewport.zoom); for (const file of files) { handleFile(file, dropX, dropY); diff --git a/src/lib/Item.svelte b/src/lib/Item.svelte index 02d0710..5ab76d2 100644 --- a/src/lib/Item.svelte +++ b/src/lib/Item.svelte @@ -100,8 +100,8 @@ const dy = (e.clientY - dragStart.y) / appState.viewport.zoom; for (const [id, startPos] of dragStartPositions) { appState.updateItem(id, { - x: startPos.x + dx, - y: startPos.y + dy + x: appState.snap(startPos.x + dx), + y: appState.snap(startPos.y + dy) }); } } else if (isResizing) { @@ -126,15 +126,17 @@ newHeight = constrained.height; } - const deltaWidth = newWidth - resizeStart.width; - const deltaHeight = newHeight - resizeStart.height; + const snappedWidth = appState.snap(newWidth); + const snappedHeight = appState.snap(newHeight); + const deltaWidth = snappedWidth - resizeStart.width; + const deltaHeight = snappedHeight - resizeStart.height; const offset = calculateCenterOffset(resizeStart.corner, deltaWidth, deltaHeight, item.rotation); appState.updateItem(item.id, { - width: newWidth, - height: newHeight, - x: resizeStart.itemX + offset.x, - y: resizeStart.itemY + offset.y + width: snappedWidth, + height: snappedHeight, + x: appState.snap(resizeStart.itemX + offset.x), + y: appState.snap(resizeStart.itemY + offset.y) }); } else if (isRotating) { const el = document.querySelector(`[data-item-id="${item.id}"]`); diff --git a/src/lib/Palette.svelte b/src/lib/Palette.svelte index a6e736b..20871c5 100644 --- a/src/lib/Palette.svelte +++ b/src/lib/Palette.svelte @@ -8,14 +8,22 @@ let soundInput: HTMLInputElement; let videoInput: HTMLInputElement; + function getSpawnPosition() { + return { + x: state.snap(-state.viewport.x / state.viewport.zoom + 400), + y: state.snap(-state.viewport.y / state.viewport.zoom + 300) + }; + } + function addTile() { const id = crypto.randomUUID(); + const pos = getSpawnPosition(); state.addItem({ id, html: '', css: '', - x: -state.viewport.x / state.viewport.zoom + 400, - y: -state.viewport.y / state.viewport.zoom + 300, + x: pos.x, + y: pos.y, width: 200, height: 200, rotation: 0, @@ -26,6 +34,7 @@ function addText() { const id = crypto.randomUUID(); + const pos = getSpawnPosition(); state.addItem({ id, html: '
Text
', @@ -34,8 +43,8 @@ font-size: 24px; font-family: 'Departure Mono', monospace; }`, - x: -state.viewport.x / state.viewport.zoom + 400, - y: -state.viewport.y / state.viewport.zoom + 300, + x: pos.x, + y: pos.y, width: 200, height: 50, rotation: 0, @@ -48,6 +57,7 @@ const url = prompt('Enter URL to embed:'); if (!url) return; const id = crypto.randomUUID(); + const pos = getSpawnPosition(); state.addItem({ id, html: ``, @@ -56,8 +66,8 @@ height: 100%; border: none; }`, - x: -state.viewport.x / state.viewport.zoom + 400, - y: -state.viewport.y / state.viewport.zoom + 300, + x: pos.x, + y: pos.y, width: 640, height: 480, rotation: 0, @@ -91,8 +101,7 @@ const id = crypto.randomUUID(); const assetId = crypto.randomUUID(); const url = URL.createObjectURL(file); - const x = -state.viewport.x / state.viewport.zoom + 400; - const y = -state.viewport.y / state.viewport.zoom + 300; + const pos = getSpawnPosition(); const img = document.createElement('img'); img.onload = () => { @@ -106,8 +115,8 @@ height: 100%; object-fit: contain; }`, - x, - y, + x: pos.x, + y: pos.y, width: img.naturalWidth, height: img.naturalHeight, rotation: 0, @@ -122,8 +131,7 @@ const id = crypto.randomUUID(); const assetId = crypto.randomUUID(); const url = URL.createObjectURL(file); - const x = -state.viewport.x / state.viewport.zoom + 400; - const y = -state.viewport.y / state.viewport.zoom + 300; + const pos = getSpawnPosition(); state.addAsset(assetId, { blob: file, url, filename: file.name }); state.addItem({ @@ -133,8 +141,8 @@ css: `audio { width: 100%; }`, - x, - y, + x: pos.x, + y: pos.y, width: 300, height: 54, rotation: 0, @@ -147,8 +155,7 @@ const id = crypto.randomUUID(); const assetId = crypto.randomUUID(); const url = URL.createObjectURL(file); - const x = -state.viewport.x / state.viewport.zoom + 400; - const y = -state.viewport.y / state.viewport.zoom + 300; + const pos = getSpawnPosition(); const video = document.createElement('video'); video.onloadedmetadata = () => { @@ -161,8 +168,8 @@ width: 100%; height: 100%; }`, - x, - y, + x: pos.x, + y: pos.y, width: video.videoWidth || 640, height: video.videoHeight || 360, rotation: 0, diff --git a/src/lib/Toolbar.svelte b/src/lib/Toolbar.svelte index 1276843..9194971 100644 --- a/src/lib/Toolbar.svelte +++ b/src/lib/Toolbar.svelte @@ -1,5 +1,5 @@