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:
parent
6101bf00b1
commit
79371c20ac
3 changed files with 30 additions and 16 deletions
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue