From b66a26810f366205f973f92f8f1d111bebaff2ba Mon Sep 17 00:00:00 2001 From: Till JS Date: Thu, 2 Apr 2026 23:49:39 +0200 Subject: [PATCH] feat(manacore/web): add 2D resize (width + height) to workbench pages Extend PageShell resize handle to support diagonal drag for both width and height. Height is persisted per page in workbench settings. Resize cursor changed from ew-resize to nwse-resize. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../page-carousel/PageCarousel.svelte | 1 + .../components/page-carousel/PageShell.svelte | 41 +++++++++++++------ .../lib/components/workbench/AppPage.svelte | 5 ++- .../apps/web/src/routes/(app)/+page.svelte | 27 +++++++++--- 4 files changed, 55 insertions(+), 19 deletions(-) diff --git a/apps/manacore/apps/web/src/lib/components/page-carousel/PageCarousel.svelte b/apps/manacore/apps/web/src/lib/components/page-carousel/PageCarousel.svelte index a7167596d..6e5f0499a 100644 --- a/apps/manacore/apps/web/src/lib/components/page-carousel/PageCarousel.svelte +++ b/apps/manacore/apps/web/src/lib/components/page-carousel/PageCarousel.svelte @@ -12,6 +12,7 @@ minimized: boolean; maximized?: boolean; widthPx: number; + heightPx?: number; title: string; color: string; } diff --git a/apps/manacore/apps/web/src/lib/components/page-carousel/PageShell.svelte b/apps/manacore/apps/web/src/lib/components/page-carousel/PageShell.svelte index cd8619dfe..f19f08533 100644 --- a/apps/manacore/apps/web/src/lib/components/page-carousel/PageShell.svelte +++ b/apps/manacore/apps/web/src/lib/components/page-carousel/PageShell.svelte @@ -10,11 +10,12 @@ interface Props { widthPx: number; + heightPx?: number; maximized?: boolean; onClose: () => void; onMinimize?: () => void; onMaximize?: () => void; - onResize?: (widthPx: number) => void; + onResize?: (widthPx: number, heightPx?: number) => void; // Default header title?: string; color?: string; @@ -28,6 +29,7 @@ let { widthPx, + heightPx, maximized = false, onClose, onMinimize, @@ -44,20 +46,31 @@ const MIN_WIDTH = 280; const MAX_WIDTH = 1200; + const MIN_HEIGHT = 200; + const MAX_HEIGHT = 2000; let resizing = $state(false); - function handleResizeStart(startX: number) { + function handleResizeStart(startX: number, startY: number) { if (!onResize) return; const startWidth = widthPx; + const startHeight = heightPx ?? 0; resizing = true; document.body.style.userSelect = 'none'; - document.body.style.cursor = 'ew-resize'; + document.body.style.cursor = 'nwse-resize'; - function onMove(clientX: number) { - const delta = clientX - startX; - const newWidth = Math.round(Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, startWidth + delta))); - onResize!(newWidth); + function onMove(clientX: number, clientY: number) { + const deltaX = clientX - startX; + const newWidth = Math.round(Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, startWidth + deltaX))); + if (startHeight > 0) { + const deltaY = clientY - startY; + const newHeight = Math.round( + Math.min(MAX_HEIGHT, Math.max(MIN_HEIGHT, startHeight + deltaY)) + ); + onResize!(newWidth, newHeight); + } else { + onResize!(newWidth); + } } function onEnd() { @@ -71,10 +84,10 @@ } function onMouseMove(e: MouseEvent) { - onMove(e.clientX); + onMove(e.clientX, e.clientY); } function onTouchMove(e: TouchEvent) { - onMove(e.touches[0].clientX); + onMove(e.touches[0].clientX, e.touches[0].clientY); } window.addEventListener('mousemove', onMouseMove); @@ -85,12 +98,12 @@ function onMouseDown(e: MouseEvent) { e.preventDefault(); - handleResizeStart(e.clientX); + handleResizeStart(e.clientX, e.clientY); } function onTouchStartHandle(e: TouchEvent) { e.preventDefault(); - handleResizeStart(e.touches[0].clientX); + handleResizeStart(e.touches[0].clientX, e.touches[0].clientY); } @@ -98,7 +111,9 @@ class="page-shell" class:maximized class:resizing - style="width: {maximized ? '100%' : `${widthPx}px`}" + style="width: {maximized ? '100%' : `${widthPx}px`}; {heightPx && !maximized + ? `height: ${heightPx}px; min-height: 0;` + : ''}" >
@@ -334,7 +349,7 @@ display: flex; align-items: center; justify-content: center; - cursor: ew-resize; + cursor: nwse-resize; color: #d1d5db; transition: color 0.15s; border-radius: 0.25rem 0 0.375rem 0; diff --git a/apps/manacore/apps/web/src/lib/components/workbench/AppPage.svelte b/apps/manacore/apps/web/src/lib/components/workbench/AppPage.svelte index 7d46ba2db..40739a508 100644 --- a/apps/manacore/apps/web/src/lib/components/workbench/AppPage.svelte +++ b/apps/manacore/apps/web/src/lib/components/workbench/AppPage.svelte @@ -12,16 +12,18 @@ interface Props { appId: string; widthPx: number; + heightPx?: number; maximized?: boolean; onClose: () => void; onMinimize?: () => void; onMaximize?: () => void; - onResize?: (widthPx: number) => void; + onResize?: (widthPx: number, heightPx?: number) => void; } let { appId, widthPx, + heightPx, maximized = false, onClose, onMinimize, @@ -165,6 +167,7 @@ { - openApps: { appId: string; minimized: boolean; maximized?: boolean; widthPx?: number }[]; + openApps: { + appId: string; + minimized: boolean; + maximized?: boolean; + widthPx?: number; + heightPx?: number; + }[]; } const workbenchStore = createAppSettingsStore('workbench-settings', { @@ -21,7 +27,13 @@ }); let openApps = $state< - { appId: string; minimized: boolean; maximized?: boolean; widthPx?: number }[] + { + appId: string; + minimized: boolean; + maximized?: boolean; + widthPx?: number; + heightPx?: number; + }[] >([ { appId: 'todo', minimized: false }, { appId: 'calendar', minimized: false }, @@ -40,6 +52,7 @@ minimized: a.minimized, maximized: a.maximized, widthPx: a.widthPx, + heightPx: a.heightPx, })), }); } @@ -53,6 +66,7 @@ minimized: a.minimized, maximized: a.maximized, widthPx: a.widthPx ?? DEFAULT_WIDTH, + heightPx: a.heightPx, title: entry?.name ?? a.appId, color: entry?.color ?? '#6B7280', }; @@ -94,8 +108,10 @@ persistState(); } - function handleResize(id: string, widthPx: number) { - openApps = openApps.map((a) => (a.appId === id ? { ...a, widthPx } : a)); + function handleResize(id: string, widthPx: number, heightPx?: number) { + openApps = openApps.map((a) => + a.appId === id ? { ...a, widthPx, ...(heightPx !== undefined ? { heightPx } : {}) } : a + ); persistState(); } @@ -131,11 +147,12 @@ handleRemoveApp(p.id)} onMinimize={() => handleMinimizeApp(p.id)} onMaximize={() => handleMaximizeApp(p.id)} - onResize={(w) => handleResize(p.id, w)} + onResize={(w, h) => handleResize(p.id, w, h)} /> {/snippet} {#snippet picker()}