Navigasjon: scroll=pan, Ctrl+scroll=zoom, piltaster, viewport-plassering
Canvas: scroll=pan, Ctrl+scroll=zoom, piltaster, dblclick=100%. BlockShell: Ctrl+scroll zoomer panel-innhold. Nye paneler plasseres i viewport-sentrum, ikke utenfor bildet. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0d9837a917
commit
e94c22fcb8
3 changed files with 51 additions and 12 deletions
|
|
@ -73,6 +73,7 @@
|
||||||
let dropFeedback = $state<string>('');
|
let dropFeedback = $state<string>('');
|
||||||
let isResizing = $state(false);
|
let isResizing = $state(false);
|
||||||
let isDragging = $state(false);
|
let isDragging = $state(false);
|
||||||
|
let contentZoom = $state(1.0);
|
||||||
let containerEl: HTMLDivElement | undefined = $state();
|
let containerEl: HTMLDivElement | undefined = $state();
|
||||||
let containerWidth = $state(0);
|
let containerWidth = $state(0);
|
||||||
|
|
||||||
|
|
@ -422,7 +423,20 @@
|
||||||
|
|
||||||
<!-- Content area (hidden when minimized) -->
|
<!-- Content area (hidden when minimized) -->
|
||||||
{#if !minimized || isFullscreen}
|
{#if !minimized || isFullscreen}
|
||||||
<div class="blockshell-content">
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
|
<div
|
||||||
|
class="blockshell-content"
|
||||||
|
style:transform="scale({contentZoom})"
|
||||||
|
style:transform-origin="top left"
|
||||||
|
onwheel={(e) => {
|
||||||
|
if (e.ctrlKey || e.metaKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
const factor = e.deltaY > 0 ? 0.95 : 1.05;
|
||||||
|
contentZoom = Math.min(3, Math.max(0.3, contentZoom * factor));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
{#if children}
|
{#if children}
|
||||||
{@render children()}
|
{@render children()}
|
||||||
{:else}
|
{:else}
|
||||||
|
|
|
||||||
|
|
@ -132,6 +132,12 @@
|
||||||
grid = { ...grid, enabled: !grid.enabled };
|
grid = { ...grid, enabled: !grid.enabled };
|
||||||
onGridChange?.(grid.enabled);
|
onGridChange?.(grid.enabled);
|
||||||
}
|
}
|
||||||
|
// Piltaster = pan
|
||||||
|
const PAN_STEP = 50;
|
||||||
|
if (e.code === 'ArrowUp') { camera = { ...camera, y: camera.y + PAN_STEP }; e.preventDefault(); }
|
||||||
|
if (e.code === 'ArrowDown') { camera = { ...camera, y: camera.y - PAN_STEP }; e.preventDefault(); }
|
||||||
|
if (e.code === 'ArrowLeft') { camera = { ...camera, x: camera.x + PAN_STEP }; e.preventDefault(); }
|
||||||
|
if (e.code === 'ArrowRight') { camera = { ...camera, x: camera.x - PAN_STEP }; e.preventDefault(); }
|
||||||
}
|
}
|
||||||
function onKeyUp(e: KeyboardEvent) {
|
function onKeyUp(e: KeyboardEvent) {
|
||||||
if (e.code === 'Space') {
|
if (e.code === 'Space') {
|
||||||
|
|
@ -146,11 +152,24 @@
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- Zoom ---
|
// --- Scroll: Ctrl = zoom, uten Ctrl = pan ---
|
||||||
function handleWheel(e: WheelEvent) {
|
function handleWheel(e: WheelEvent) {
|
||||||
|
// Ikke fang scroll inne i paneler (la innhold scrolle)
|
||||||
|
if ((e.target as HTMLElement).closest('[data-canvas-object-id]')) return;
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const zoomFactor = e.deltaY > 0 ? 0.92 : 1.08;
|
if (e.ctrlKey || e.metaKey) {
|
||||||
zoomAt(e.clientX, e.clientY, zoomFactor);
|
// Ctrl+scroll = zoom
|
||||||
|
const zoomFactor = e.deltaY > 0 ? 0.92 : 1.08;
|
||||||
|
zoomAt(e.clientX, e.clientY, zoomFactor);
|
||||||
|
} else {
|
||||||
|
// Scroll = pan
|
||||||
|
camera = {
|
||||||
|
...camera,
|
||||||
|
x: camera.x - e.deltaX - (e.shiftKey ? e.deltaY : 0),
|
||||||
|
y: camera.y - (e.shiftKey ? 0 : e.deltaY),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function zoomAt(screenX: number, screenY: number, factor: number) {
|
function zoomAt(screenX: number, screenY: number, factor: number) {
|
||||||
|
|
@ -459,7 +478,7 @@
|
||||||
ondblclick={(e) => {
|
ondblclick={(e) => {
|
||||||
if (!(e.target as HTMLElement).closest('[data-canvas-object-id]') &&
|
if (!(e.target as HTMLElement).closest('[data-canvas-object-id]') &&
|
||||||
!(e.target as HTMLElement).closest('.canvas-toolbar')) {
|
!(e.target as HTMLElement).closest('.canvas-toolbar')) {
|
||||||
zoomToFit();
|
camera = { ...camera, zoom: 1.0 };
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
ontouchstart={handleTouchStart}
|
ontouchstart={handleTouchStart}
|
||||||
|
|
|
||||||
|
|
@ -230,18 +230,24 @@
|
||||||
if (layout.panels.some(p => p.trait === trait)) return;
|
if (layout.panels.some(p => p.trait === trait)) return;
|
||||||
const info = getPanelInfo(trait);
|
const info = getPanelInfo(trait);
|
||||||
const remembered = panelSizes[trait];
|
const remembered = panelSizes[trait];
|
||||||
const maxY = layout.panels.length > 0
|
const w = remembered?.width ?? info.defaultWidth;
|
||||||
? Math.max(...layout.panels.map(p => p.y + p.height))
|
const h = remembered?.height ?? info.defaultHeight;
|
||||||
: 0;
|
|
||||||
|
// Plasser i viewport-sentrum (hensyn til kamera pan/zoom)
|
||||||
|
const vpW = typeof window !== 'undefined' ? window.innerWidth : 1024;
|
||||||
|
const vpH = typeof window !== 'undefined' ? window.innerHeight - 44 : 700; // minus header
|
||||||
|
const centerX = (vpW / 2 - savedCamera.x) / savedCamera.zoom - w / 2;
|
||||||
|
const centerY = (vpH / 2 - savedCamera.y) / savedCamera.zoom - h / 2;
|
||||||
|
|
||||||
layout = {
|
layout = {
|
||||||
panels: [
|
panels: [
|
||||||
...layout.panels,
|
...layout.panels,
|
||||||
{
|
{
|
||||||
trait,
|
trait,
|
||||||
x: 30,
|
x: Math.round(centerX),
|
||||||
y: maxY + 30,
|
y: Math.round(centerY),
|
||||||
width: remembered?.width ?? info.defaultWidth,
|
width: w,
|
||||||
height: remembered?.height ?? info.defaultHeight,
|
height: h,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue