From 79371c20acb0fc2a6c8012679cfcce8e1b745c55 Mon Sep 17 00:00:00 2001 From: vegard Date: Thu, 19 Mar 2026 06:13:02 +0000 Subject: [PATCH] Canvas: venstreklikk-dra for pan + kameraposisjon lagres MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Klikk-hold-dra på tom bakgrunn = pan (erstatter midtre museknapp) - Shift+dra = lasso-seleksjon (som før) - Kameraposisjon (x, y, zoom) lagres i workspace-metadata og gjenopprettes ved neste besøk - Senket ZOOM_MIN til 5% for å tillate zoom ut på store canvas Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/lib/components/canvas/Canvas.svelte | 31 ++++++++++--------- frontend/src/lib/components/canvas/types.ts | 4 ++- frontend/src/routes/+page.svelte | 11 ++++++- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/frontend/src/lib/components/canvas/Canvas.svelte b/frontend/src/lib/components/canvas/Canvas.svelte index 036eb53..9b2bd83 100644 --- a/frontend/src/lib/components/canvas/Canvas.svelte +++ b/frontend/src/lib/components/canvas/Canvas.svelte @@ -11,6 +11,7 @@ clampZoom, ZOOM_MIN, ZOOM_MAX, + ZOOM_FIT_MIN, } from './types.js'; /** @@ -211,21 +212,23 @@ return; } - // Left button on empty space = lasso or deselect - if (!spaceHeld) { - if (e.shiftKey) { - // Start lasso - isLassoing = true; - lassoStart = { x: sx, y: sy }; - lassoEnd = { x: sx, y: sy }; - } else { - // Deselect all - selectedIds = new Set(); - onSelectionChange?.([]); - } - containerEl.setPointerCapture(e.pointerId); - e.preventDefault(); + // Left button on empty space = pan (or lasso with shift) + if (e.shiftKey) { + // Shift+drag = lasso selection + isLassoing = true; + lassoStart = { x: sx, y: sy }; + lassoEnd = { x: sx, y: sy }; + } else { + // Normal drag on empty space = pan + isPanning = true; + panStart = { x: e.clientX, y: e.clientY }; + cameraStart = { x: camera.x, y: camera.y }; + // Deselect all + selectedIds = new Set(); + onSelectionChange?.([]); } + containerEl.setPointerCapture(e.pointerId); + e.preventDefault(); } } diff --git a/frontend/src/lib/components/canvas/types.ts b/frontend/src/lib/components/canvas/types.ts index f0698f9..7edb1a2 100644 --- a/frontend/src/lib/components/canvas/types.ts +++ b/frontend/src/lib/components/canvas/types.ts @@ -113,8 +113,10 @@ export function visibleObjects( } /** Clamp zoom to allowed range */ -export const ZOOM_MIN = 0.1; +export const ZOOM_MIN = 0.05; export const ZOOM_MAX = 3.0; +/** Lower bound for zoomToFit — allows fitting very spread-out layouts */ +export const ZOOM_FIT_MIN = 0.02; export function clampZoom(zoom: number): number { return Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, zoom)); diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index 0eea6e5..6142bed 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -8,7 +8,7 @@ // Canvas + BlockShell import Canvas from '$lib/components/canvas/Canvas.svelte'; - import type { CanvasObject } from '$lib/components/canvas/types.js'; + import type { CanvasObject, Camera } from '$lib/components/canvas/types.js'; import BlockShell from '$lib/components/blockshell/BlockShell.svelte'; // Workspace layout @@ -76,6 +76,11 @@ // Load theme preferences if (res.metadata) { loadThemeFromMetadata(res.metadata as Record); + // Load saved camera position + const cam = (res.metadata as Record).camera as Camera | undefined; + if (cam && typeof cam.x === 'number') { + savedCamera = cam; + } } }) .catch((err) => { @@ -97,6 +102,7 @@ let layout = $state({ panels: [] }); let layoutInitialized = $state(false); + let savedCamera = $state({ x: 0, y: 0, zoom: 1.0 }); // When workspace node appears in store (after creation), load its layout $effect(() => { @@ -141,6 +147,7 @@ metadata: { ...currentMeta, workspace_layout: layout, + camera: savedCamera, preferences: { ...(currentMeta.preferences ?? {}), theme: { hueBg: themeHueBg, hueSurface: themeHueSurface, hueAccent: themeHueAccent }, @@ -778,6 +785,8 @@ objects={canvasObjects} onObjectMove={handleObjectMove} grid={{ enabled: false, size: 20 }} + initialCamera={savedCamera} + onCameraChange={(cam) => { savedCamera = cam; persistMetadata(); }} > {#snippet renderObject(obj)} {@const trait = obj.id}