feat(v2): add tiling layout, sidebar controls, and keyboard shortcuts
- TilingGrid: dynamic CSS Grid with auto-preset based on pane count - Layout presets: 1-col, 2-col, 3-col, 2x2, master-stack - PaneContainer: close button, status indicator, focus highlight - SessionList: new terminal button, layout preset selector, pane list - Layout store: pane CRUD, focus management, grid template generation - Keyboard: Ctrl+N new terminal, Ctrl+1-4 focus pane by index
This commit is contained in:
parent
bb0e9283fc
commit
bfd4021909
5 changed files with 399 additions and 41 deletions
|
|
@ -1,28 +1,90 @@
|
|||
// Layout state management — Svelte 5 runes
|
||||
// Phase 2: pane positions, resize, presets
|
||||
|
||||
export type LayoutPreset = '1-col' | '2-col' | '3-col' | '2x2' | 'master-stack';
|
||||
|
||||
export interface PaneState {
|
||||
export type PaneType = 'terminal' | 'agent' | 'markdown' | 'empty';
|
||||
|
||||
export interface Pane {
|
||||
id: string;
|
||||
sessionId: string;
|
||||
row: number;
|
||||
col: number;
|
||||
rowSpan: number;
|
||||
colSpan: number;
|
||||
type: PaneType;
|
||||
title: string;
|
||||
shell?: string;
|
||||
cwd?: string;
|
||||
args?: string[];
|
||||
focused: boolean;
|
||||
}
|
||||
|
||||
let panes = $state<Pane[]>([]);
|
||||
let activePreset = $state<LayoutPreset>('1-col');
|
||||
let panes = $state<PaneState[]>([]);
|
||||
let focusedPaneId = $state<string | null>(null);
|
||||
|
||||
export function getActivePreset() {
|
||||
export function getPanes(): Pane[] {
|
||||
return panes;
|
||||
}
|
||||
|
||||
export function getActivePreset(): LayoutPreset {
|
||||
return activePreset;
|
||||
}
|
||||
|
||||
export function setPreset(preset: LayoutPreset) {
|
||||
export function getFocusedPaneId(): string | null {
|
||||
return focusedPaneId;
|
||||
}
|
||||
|
||||
export function addPane(pane: Omit<Pane, 'focused'>): void {
|
||||
panes.push({ ...pane, focused: false });
|
||||
focusPane(pane.id);
|
||||
autoPreset();
|
||||
}
|
||||
|
||||
export function removePane(id: string): void {
|
||||
panes = panes.filter(p => p.id !== id);
|
||||
if (focusedPaneId === id) {
|
||||
focusedPaneId = panes.length > 0 ? panes[0].id : null;
|
||||
}
|
||||
autoPreset();
|
||||
}
|
||||
|
||||
export function focusPane(id: string): void {
|
||||
focusedPaneId = id;
|
||||
panes = panes.map(p => ({ ...p, focused: p.id === id }));
|
||||
}
|
||||
|
||||
export function focusPaneByIndex(index: number): void {
|
||||
if (index >= 0 && index < panes.length) {
|
||||
focusPane(panes[index].id);
|
||||
}
|
||||
}
|
||||
|
||||
export function setPreset(preset: LayoutPreset): void {
|
||||
activePreset = preset;
|
||||
}
|
||||
|
||||
export function getPanes() {
|
||||
return panes;
|
||||
function autoPreset(): void {
|
||||
const count = panes.length;
|
||||
if (count <= 1) activePreset = '1-col';
|
||||
else if (count === 2) activePreset = '2-col';
|
||||
else if (count === 3) activePreset = 'master-stack';
|
||||
else activePreset = '2x2';
|
||||
}
|
||||
|
||||
/** CSS grid-template for current preset */
|
||||
export function getGridTemplate(): { columns: string; rows: string } {
|
||||
switch (activePreset) {
|
||||
case '1-col':
|
||||
return { columns: '1fr', rows: '1fr' };
|
||||
case '2-col':
|
||||
return { columns: '1fr 1fr', rows: '1fr' };
|
||||
case '3-col':
|
||||
return { columns: '1fr 1fr 1fr', rows: '1fr' };
|
||||
case '2x2':
|
||||
return { columns: '1fr 1fr', rows: '1fr 1fr' };
|
||||
case 'master-stack':
|
||||
return { columns: '2fr 1fr', rows: '1fr 1fr' };
|
||||
}
|
||||
}
|
||||
|
||||
/** For master-stack: first pane spans full height */
|
||||
export function getPaneGridArea(index: number): string | undefined {
|
||||
if (activePreset === 'master-stack' && index === 0) {
|
||||
return '1 / 1 / 3 / 2';
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue