Resizable Panels — alle Bereiche per Drag ziehbar

- 3 Resize-Handles zwischen den 4 Panels (Session/Chat/Aktivität/Agents)
- Mindestbreite 120px pro Panel
- Panel-Breiten werden in localStorage gespeichert und beim Neustart geladen
- Visuelles Feedback: Handle wird blau beim Hover/Drag

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Eddy 2026-04-13 19:13:50 +02:00
parent f101661016
commit 56c967b618

View file

@ -1,4 +1,5 @@
<script lang="ts"> <script lang="ts">
import { onMount } from 'svelte';
import SessionList from '$lib/components/SessionList.svelte'; import SessionList from '$lib/components/SessionList.svelte';
import ChatPanel from '$lib/components/ChatPanel.svelte'; import ChatPanel from '$lib/components/ChatPanel.svelte';
import ActivityPanel from '$lib/components/ActivityPanel.svelte'; import ActivityPanel from '$lib/components/ActivityPanel.svelte';
@ -7,10 +8,7 @@
import AuditLog from '$lib/components/AuditLog.svelte'; import AuditLog from '$lib/components/AuditLog.svelte';
import GuardRailsPanel from '$lib/components/GuardRailsPanel.svelte'; import GuardRailsPanel from '$lib/components/GuardRailsPanel.svelte';
// Tab-State für mittleres Panel
let activeMiddleTab = 'activity'; let activeMiddleTab = 'activity';
// Tab-State für rechtes Panel
let activeRightTab = 'agents'; let activeRightTab = 'agents';
const middleTabs = [ const middleTabs = [
@ -23,20 +21,111 @@
{ id: 'agents', label: 'Agents', icon: '🤖' }, { id: 'agents', label: 'Agents', icon: '🤖' },
{ id: 'guards', label: 'Guard-Rails', icon: '🛡️' }, { id: 'guards', label: 'Guard-Rails', icon: '🛡️' },
]; ];
// ============ Resizable Panels ============
// Breiten in Pixel (werden zu fr umgerechnet)
let panelWidths = [220, 400, 400, 380];
let container: HTMLDivElement;
let dragging: number | null = null;
let startX = 0;
let startWidths: number[] = [];
function getGridTemplate(): string {
return panelWidths.map(w => `${w}px`).join(' ');
}
function onMouseDown(index: number, event: MouseEvent) {
event.preventDefault();
dragging = index;
startX = event.clientX;
startWidths = [...panelWidths];
window.addEventListener('mousemove', onMouseMove);
window.addEventListener('mouseup', onMouseUp);
}
function onMouseMove(event: MouseEvent) {
if (dragging === null) return;
const dx = event.clientX - startX;
const minWidth = 120;
const leftIdx = dragging;
const rightIdx = dragging + 1;
let newLeft = startWidths[leftIdx] + dx;
let newRight = startWidths[rightIdx] - dx;
// Mindestbreiten einhalten
if (newLeft < minWidth) {
newRight -= (minWidth - newLeft);
newLeft = minWidth;
}
if (newRight < minWidth) {
newLeft -= (minWidth - newRight);
newRight = minWidth;
}
panelWidths[leftIdx] = Math.max(minWidth, newLeft);
panelWidths[rightIdx] = Math.max(minWidth, newRight);
panelWidths = panelWidths; // Svelte reactivity
}
function onMouseUp() {
dragging = null;
window.removeEventListener('mousemove', onMouseMove);
window.removeEventListener('mouseup', onMouseUp);
// Breiten in localStorage speichern
try {
localStorage.setItem('panel-widths', JSON.stringify(panelWidths));
} catch {}
}
onMount(() => {
// Gespeicherte Breiten laden
try {
const saved = localStorage.getItem('panel-widths');
if (saved) {
const parsed = JSON.parse(saved);
if (Array.isArray(parsed) && parsed.length === 4) {
panelWidths = parsed;
}
}
} catch {}
// Initiale Breiten an Fenster anpassen wenn zu breit
if (container) {
const total = panelWidths.reduce((a, b) => a + b, 0);
const available = container.clientWidth - 9; // 3 Handles à 3px
if (total > available) {
const scale = available / total;
panelWidths = panelWidths.map(w => Math.round(w * scale));
}
}
});
</script> </script>
<div class="panels"> <div
<!-- Session-Sidebar (links) --> class="panels"
class:dragging={dragging !== null}
bind:this={container}
style="grid-template-columns: {getGridTemplate()}"
>
<!-- Session-Sidebar -->
<aside class="panel panel-sessions"> <aside class="panel panel-sessions">
<SessionList /> <SessionList />
</aside> </aside>
<!-- Chat (Mitte-Links) --> <div class="resize-handle" on:mousedown={(e) => onMouseDown(0, e)} role="separator" aria-orientation="vertical"></div>
<!-- Chat -->
<section class="panel panel-chat"> <section class="panel panel-chat">
<ChatPanel /> <ChatPanel />
</section> </section>
<!-- Aktivität / Memory / Audit (Mitte-Rechts) --> <div class="resize-handle" on:mousedown={(e) => onMouseDown(1, e)} role="separator" aria-orientation="vertical"></div>
<!-- Aktivität / Memory / Audit -->
<section class="panel panel-activity"> <section class="panel panel-activity">
<div class="panel-tabs"> <div class="panel-tabs">
{#each middleTabs as tab} {#each middleTabs as tab}
@ -60,7 +149,9 @@
</div> </div>
</section> </section>
<!-- Agents / Guard-Rails (rechts) --> <div class="resize-handle" on:mousedown={(e) => onMouseDown(2, e)} role="separator" aria-orientation="vertical"></div>
<!-- Agents / Guard-Rails -->
<section class="panel panel-details"> <section class="panel panel-details">
<div class="panel-tabs"> <div class="panel-tabs">
{#each rightTabs as tab} {#each rightTabs as tab}
@ -86,10 +177,14 @@
<style> <style>
.panels { .panels {
display: grid; display: grid;
grid-template-columns: 220px 1fr 1fr 1fr; gap: 0;
gap: 1px;
height: 100%; height: 100%;
background: var(--border); background: var(--bg-primary);
}
.panels.dragging {
cursor: col-resize;
user-select: none;
} }
.panel { .panel {
@ -97,12 +192,24 @@
flex-direction: column; flex-direction: column;
background: var(--bg-primary); background: var(--bg-primary);
overflow: hidden; overflow: hidden;
min-width: 0;
} }
.panel-sessions { /* Resize Handle */
min-width: 180px; .resize-handle {
width: 3px;
cursor: col-resize;
background: var(--border);
transition: background 0.15s ease;
z-index: 10;
} }
.resize-handle:hover,
.panels.dragging .resize-handle {
background: var(--accent);
}
/* Tabs */
.panel-tabs { .panel-tabs {
display: flex; display: flex;
background: var(--bg-secondary); background: var(--bg-secondary);
@ -136,32 +243,4 @@
flex: 1; flex: 1;
overflow: auto; overflow: auto;
} }
/* Responsive */
@media (max-width: 1400px) {
.panels {
grid-template-columns: 200px 1fr 1fr;
}
.panel-details {
display: none;
}
}
@media (max-width: 1000px) {
.panels {
grid-template-columns: 180px 1fr;
}
.panel-activity {
display: none;
}
}
@media (max-width: 700px) {
.panels {
grid-template-columns: 1fr;
}
.panel-sessions {
display: none;
}
}
</style> </style>