Canvas: venstreklikk-dra for pan + kameraposisjon lagres

- 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) <noreply@anthropic.com>
This commit is contained in:
vegard 2026-03-19 06:13:02 +00:00
parent 6101bf00b1
commit 79371c20ac
3 changed files with 30 additions and 16 deletions

View file

@ -11,6 +11,7 @@
clampZoom, clampZoom,
ZOOM_MIN, ZOOM_MIN,
ZOOM_MAX, ZOOM_MAX,
ZOOM_FIT_MIN,
} from './types.js'; } from './types.js';
/** /**
@ -211,21 +212,23 @@
return; return;
} }
// Left button on empty space = lasso or deselect // Left button on empty space = pan (or lasso with shift)
if (!spaceHeld) { if (e.shiftKey) {
if (e.shiftKey) { // Shift+drag = lasso selection
// Start lasso isLassoing = true;
isLassoing = true; lassoStart = { x: sx, y: sy };
lassoStart = { x: sx, y: sy }; lassoEnd = { x: sx, y: sy };
lassoEnd = { x: sx, y: sy }; } else {
} else { // Normal drag on empty space = pan
// Deselect all isPanning = true;
selectedIds = new Set(); panStart = { x: e.clientX, y: e.clientY };
onSelectionChange?.([]); cameraStart = { x: camera.x, y: camera.y };
} // Deselect all
containerEl.setPointerCapture(e.pointerId); selectedIds = new Set();
e.preventDefault(); onSelectionChange?.([]);
} }
containerEl.setPointerCapture(e.pointerId);
e.preventDefault();
} }
} }

View file

@ -113,8 +113,10 @@ export function visibleObjects<T extends CanvasObject>(
} }
/** Clamp zoom to allowed range */ /** Clamp zoom to allowed range */
export const ZOOM_MIN = 0.1; export const ZOOM_MIN = 0.05;
export const ZOOM_MAX = 3.0; 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 { export function clampZoom(zoom: number): number {
return Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, zoom)); return Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, zoom));

View file

@ -8,7 +8,7 @@
// Canvas + BlockShell // Canvas + BlockShell
import Canvas from '$lib/components/canvas/Canvas.svelte'; 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'; import BlockShell from '$lib/components/blockshell/BlockShell.svelte';
// Workspace layout // Workspace layout
@ -76,6 +76,11 @@
// Load theme preferences // Load theme preferences
if (res.metadata) { if (res.metadata) {
loadThemeFromMetadata(res.metadata as Record<string, unknown>); loadThemeFromMetadata(res.metadata as Record<string, unknown>);
// Load saved camera position
const cam = (res.metadata as Record<string, unknown>).camera as Camera | undefined;
if (cam && typeof cam.x === 'number') {
savedCamera = cam;
}
} }
}) })
.catch((err) => { .catch((err) => {
@ -97,6 +102,7 @@
let layout = $state<WorkspaceLayout>({ panels: [] }); let layout = $state<WorkspaceLayout>({ panels: [] });
let layoutInitialized = $state(false); let layoutInitialized = $state(false);
let savedCamera = $state<Camera>({ x: 0, y: 0, zoom: 1.0 });
// When workspace node appears in store (after creation), load its layout // When workspace node appears in store (after creation), load its layout
$effect(() => { $effect(() => {
@ -141,6 +147,7 @@
metadata: { metadata: {
...currentMeta, ...currentMeta,
workspace_layout: layout, workspace_layout: layout,
camera: savedCamera,
preferences: { preferences: {
...(currentMeta.preferences ?? {}), ...(currentMeta.preferences ?? {}),
theme: { hueBg: themeHueBg, hueSurface: themeHueSurface, hueAccent: themeHueAccent }, theme: { hueBg: themeHueBg, hueSurface: themeHueSurface, hueAccent: themeHueAccent },
@ -778,6 +785,8 @@
objects={canvasObjects} objects={canvasObjects}
onObjectMove={handleObjectMove} onObjectMove={handleObjectMove}
grid={{ enabled: false, size: 20 }} grid={{ enabled: false, size: 20 }}
initialCamera={savedCamera}
onCameraChange={(cam) => { savedCamera = cam; persistMetadata(); }}
> >
{#snippet renderObject(obj)} {#snippet renderObject(obj)}
{@const trait = obj.id} {@const trait = obj.id}