From 4e9481edf3b5e7d4cbc1a7908189b3e41971ac58 Mon Sep 17 00:00:00 2001 From: vegard Date: Wed, 18 Mar 2026 07:43:19 +0000 Subject: [PATCH] =?UTF-8?q?Fullf=C3=B8rer=20oppgave=2019.5:=20Paneler=20ka?= =?UTF-8?q?n=20minimeres=20til=20kompakt=20header?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dobbeltklikk på panel-header toggler minimert tilstand. Minimerte paneler viser kun header (ikon + tittel), skjuler innhold og resize-handles. Posisjon, bredde og full høyde bevares i layout og gjenopprettes ved nytt dobbeltklikk. Tilstanden persisteres i edge metadata sammen med resten av workspace-layouten. Endringer: - PanelLayout: nytt `minimized?`-felt - BlockShellEvents: nytt `onMinimizeChange`-event - BlockShell: minimized-prop, dblclick-handler, minimize-knapp, skjuler content/resize når minimert - Workspace-side: håndterer minimize-state, oppdaterer canvas- objekthøyde til PANEL_HEADER_HEIGHT når minimert Co-Authored-By: Claude Opus 4.6 (1M context) --- .../components/blockshell/BlockShell.svelte | 62 +++++++++++++++---- .../src/lib/components/blockshell/types.ts | 2 + frontend/src/lib/workspace/types.ts | 5 ++ .../src/routes/collection/[id]/+page.svelte | 15 ++++- tasks.md | 3 +- 5 files changed, 73 insertions(+), 14 deletions(-) diff --git a/frontend/src/lib/components/blockshell/BlockShell.svelte b/frontend/src/lib/components/blockshell/BlockShell.svelte index 934f53f..41e18b4 100644 --- a/frontend/src/lib/components/blockshell/BlockShell.svelte +++ b/frontend/src/lib/components/blockshell/BlockShell.svelte @@ -40,6 +40,8 @@ receiver?: BlockReceiver; /** Whether to show close button */ closable?: boolean; + /** Whether panel is minimized (collapsed to header only) */ + minimized?: boolean; /** Extra CSS class */ class?: string; /** Panel content */ @@ -54,12 +56,14 @@ constraints = DEFAULT_CONSTRAINTS, receiver, closable = true, + minimized = false, class: extraClass = '', children, onClose, onDragMove, onResize, onFullscreenChange, + onMinimizeChange, onDrop, }: Props = $props(); @@ -127,6 +131,15 @@ onFullscreenChange?.(isFullscreen); } + // --- Minimize toggle (double-click header) --- + let hasDragged = false; + + function handleHeaderDblClick() { + if (hasDragged) return; + if (isFullscreen) return; + onMinimizeChange?.(!minimized); + } + // --- Drag (repositioning via header) --- function handleDragStart(e: PointerEvent) { // Only left button, not on buttons @@ -134,6 +147,7 @@ if ((e.target as HTMLElement).closest('button')) return; isDragging = true; + hasDragged = false; dragStartX = e.clientX; dragStartY = e.clientY; @@ -146,6 +160,9 @@ if (!isDragging) return; const dx = e.clientX - dragStartX; const dy = e.clientY - dragStartY; + if (Math.abs(dx) > 3 || Math.abs(dy) > 3) { + hasDragged = true; + } dragStartX = e.clientX; dragStartY = e.clientY; onDragMove?.(dx, dy); @@ -289,12 +306,13 @@ bind:this={containerEl} class="blockshell {extraClass}" class:blockshell-fullscreen={isFullscreen} + class:blockshell-minimized={minimized && !isFullscreen} class:blockshell-dragging={isDragging} class:blockshell-resizing={isResizing} class:blockshell-drop-compatible={dropZoneState === 'compatible'} class:blockshell-drop-incompatible={dropZoneState === 'incompatible'} style:width={isFullscreen ? undefined : `${clampedWidth}px`} - style:height={isFullscreen ? undefined : `${clampedHeight}px`} + style:height={isFullscreen ? undefined : minimized ? undefined : `${clampedHeight}px`} ondragenter={handleDragEnter} ondragover={handleDragOver} ondragleave={handleDragLeave} @@ -309,6 +327,7 @@ onpointerdown={handleDragStart} onpointermove={handleDragMove} onpointerup={handleDragEnd} + ondblclick={handleHeaderDblClick} >
{#if icon} @@ -318,6 +337,16 @@
+ {#if !isFullscreen} + + {/if}
- -
- {#if children} - {@render children()} - {:else} -

Ingen innhold ennå.

- {/if} -
+ + {#if !minimized || isFullscreen} +
+ {#if children} + {@render children()} + {:else} +

Ingen innhold ennå.

+ {/if} +
+ {/if} {#if dropZoneState !== 'idle'} @@ -361,8 +392,8 @@ {/if} - - {#if !isFullscreen && !isMobile} + + {#if !isFullscreen && !isMobile && !minimized} {#each resizeDirections as dir}
void; /** Panel fullscreen state changed */ onFullscreenChange?: (isFullscreen: boolean) => void; + /** Panel minimize state changed (double-click header to toggle) */ + onMinimizeChange?: (isMinimized: boolean) => void; /** Drop received on this panel */ onDrop?: (payload: DragPayload) => void; } diff --git a/frontend/src/lib/workspace/types.ts b/frontend/src/lib/workspace/types.ts index efef069..e1d5143 100644 --- a/frontend/src/lib/workspace/types.ts +++ b/frontend/src/lib/workspace/types.ts @@ -9,6 +9,9 @@ * Ref: docs/retninger/arbeidsflaten.md § "Tre lag" */ +/** Height of the BlockShell header — used as minimized panel height */ +export const PANEL_HEADER_HEIGHT = 36; + /** Position and size of a single panel on the canvas */ export interface PanelLayout { /** Trait name this panel represents */ @@ -21,6 +24,8 @@ export interface PanelLayout { width: number; /** Panel height */ height: number; + /** Whether panel is minimized (collapsed to header only) */ + minimized?: boolean; } /** Full workspace layout state */ diff --git a/frontend/src/routes/collection/[id]/+page.svelte b/frontend/src/routes/collection/[id]/+page.svelte index 6537db3..7e39ec8 100644 --- a/frontend/src/routes/collection/[id]/+page.svelte +++ b/frontend/src/routes/collection/[id]/+page.svelte @@ -16,6 +16,7 @@ getPanelInfo, generateDefaultLayout, resolveLayout, + PANEL_HEADER_HEIGHT, } from '$lib/workspace/types.js'; // Context header @@ -139,7 +140,7 @@ x: p.x, y: p.y, width: p.width, - height: p.height, + height: p.minimized ? PANEL_HEADER_HEIGHT : p.height, })) ); @@ -197,6 +198,16 @@ persistLayout(); } + /** Handle panel minimize/restore toggle */ + function handlePanelMinimize(trait: string, isMinimized: boolean) { + const idx = layout.panels.findIndex(p => p.trait === trait); + if (idx >= 0) { + layout.panels[idx] = { ...layout.panels[idx], minimized: isMinimized }; + layout = { ...layout }; + persistLayout(); + } + } + /** Handle adding a new panel from the tool menu */ function handleAddPanel(trait: string) { // Don't add duplicate panels @@ -369,8 +380,10 @@ icon={info.icon} width={panel?.width ?? obj.width} height={panel?.height ?? obj.height} + minimized={panel?.minimized ?? false} onResize={(w, h) => handlePanelResize(trait, w, h)} onClose={() => handlePanelClose(trait)} + onMinimizeChange={(m) => handlePanelMinimize(trait, m)} > {#if knownTraits.has(trait)} {#if trait === 'editor'} diff --git a/tasks.md b/tasks.md index 0d31437..5d9d245 100644 --- a/tasks.md +++ b/tasks.md @@ -216,8 +216,7 @@ Ref: `docs/retninger/arbeidsflaten.md`, `docs/features/canvas_primitiv.md` - [x] 19.2 BlockShell wrapper-komponent: header med tittel + fullskjerm/resize/lukk-knapper, drag-handles for repositionering, resize-handles, drop-sone rendering (highlight ved drag-over). Responsivt (min-size, max-size). - [x] 19.3 Arbeidsflaten layout: skriv om `/collection/[id]` fra vertikal stack til Canvas + BlockShell. Last brukerens lagrede arrangement eller bruk defaults fra samlingens traits. Persist arrangement i bruker-edge metadata. Desktop: spatial canvas, mobil: stacked/tabs. Ref: `docs/retninger/arbeidsflaten.md` § "Tre lag". - [x] 19.4 Kontekst-header: header tilhører flaten, viser gjeldende node som nedtrekksmeny/kontekst-velger. Mest brukte noder øverst (frekvens/recency), søkbart. Verktøymeny for å instansiere nye paneler. Ref: `docs/retninger/arbeidsflaten.md` § "Kontekst-header". -- [~] 19.5 Snarveier: paneler kan minimeres til kompakt ikon/fane. Dobbeltklikk → minimer/gjenopprett. Bevarer posisjon og størrelse. Ref: `docs/retninger/arbeidsflaten.md` § "Snarveier". - > Påbegynt: 2026-03-18T07:39 +- [x] 19.5 Snarveier: paneler kan minimeres til kompakt ikon/fane. Dobbeltklikk → minimer/gjenopprett. Bevarer posisjon og størrelse. Ref: `docs/retninger/arbeidsflaten.md` § "Snarveier". - [ ] 19.6 Personlig flate: brukerens standard arbeidsflate (node_kind: 'workspace'). Vises når ikke koblet til en annen node. Persistent layout. ## Fase 20: Universell overføring + panelrework