Workspaces layout, automation page

This commit is contained in:
2026-02-18 17:04:32 +00:00
parent 89f23f9426
commit 485ae4c3d7
89 changed files with 2168 additions and 514 deletions
@@ -1,14 +1,15 @@
import { useCallback, useRef } from 'react';
import { ResizablePanel, ResizablePanelGroup, ResizableHandle } from '../ui/resizable';
import type { LayoutNode, WidgetRegistry } from './types';
import { pruneEmptyPanels, countPanels } from './layout-utils';
import type { LayoutNode, AppRegistry, PanelComponents } from './types';
import { countPanels } from './layout-utils';
import { PanelSlot } from './PanelSlot';
type WorkspaceRendererProps = {
layout: LayoutNode;
registry: WidgetRegistry;
registry: AppRegistry;
components?: PanelComponents;
editing: boolean;
onSetWidget: (panelId: string, widgetType: string | null) => void;
onSetApp: (panelId: string, appType: string | null) => void;
onSplit: (panelId: string, direction: 'horizontal' | 'vertical') => void;
onRemove: (panelId: string) => void;
onResized: (groupId: string, sizes: number[]) => void;
@@ -17,27 +18,24 @@ type WorkspaceRendererProps = {
export const WorkspaceRenderer = ({
layout,
registry,
components,
editing,
onSetWidget,
onSetApp,
onSplit,
onRemove,
onResized,
}: WorkspaceRendererProps) => {
const displayLayout = editing ? layout : pruneEmptyPanels(layout);
if (!displayLayout) {
return <div className="flex h-full items-center justify-center text-sm text-muted-foreground">No widgets</div>;
}
const totalPanels = countPanels(layout);
return (
<div className="h-full w-full">
<LayoutNodeRenderer
node={displayLayout}
node={layout}
registry={registry}
components={components}
editing={editing}
totalPanels={totalPanels}
onSetWidget={onSetWidget}
onSetApp={onSetApp}
onSplit={onSplit}
onRemove={onRemove}
onResized={onResized}
@@ -48,30 +46,42 @@ export const WorkspaceRenderer = ({
type LayoutNodeRendererProps = {
node: LayoutNode;
registry: WidgetRegistry;
registry: AppRegistry;
components?: PanelComponents;
editing: boolean;
totalPanels: number;
onSetWidget: (panelId: string, widgetType: string | null) => void;
onSetApp: (panelId: string, appType: string | null) => void;
onSplit: (panelId: string, direction: 'horizontal' | 'vertical') => void;
onRemove: (panelId: string) => void;
onResized: (groupId: string, sizes: number[]) => void;
};
const getFixedHeight = (node: LayoutNode, registry: AppRegistry): number | undefined => {
if (node.type === 'panel' && node.appType) return registry[node.appType]?.fixedHeight;
return undefined;
};
const LayoutNodeRenderer = ({
node,
registry,
components,
editing,
totalPanels,
onSetWidget,
onSetApp,
onSplit,
onRemove,
onResized,
}: LayoutNodeRendererProps) => {
const debounceRef = useRef<ReturnType<typeof setTimeout>>(null);
const mountedRef = useRef(false);
const handleLayout = useCallback(
(sizes: number[]) => {
if (node.type !== 'group') return;
if (!mountedRef.current) {
mountedRef.current = true;
return;
}
if (debounceRef.current) clearTimeout(debounceRef.current);
debounceRef.current = setTimeout(() => {
onResized(node.id, sizes);
@@ -85,27 +95,56 @@ const LayoutNodeRenderer = ({
<PanelSlot
panel={node}
registry={registry}
components={components}
editing={editing}
isLastPanel={totalPanels <= 1}
onSetWidget={onSetWidget}
onSetApp={onSetApp}
onSplit={onSplit}
onRemove={onRemove}
/>
);
}
const hasFixedChild = node.direction === 'vertical' && node.children.some((c) => getFixedHeight(c.node, registry) !== undefined);
if (hasFixedChild) {
return (
<div className="flex h-full w-full flex-col">
{node.children.map((child) => {
const fixed = getFixedHeight(child.node, registry);
return (
<div key={child.node.id} className={fixed !== undefined ? 'shrink-0' : 'min-h-0 flex-1'} style={fixed !== undefined ? { height: fixed } : undefined}>
<LayoutNodeRenderer
node={child.node}
registry={registry}
components={components}
editing={editing}
totalPanels={totalPanels}
onSetApp={onSetApp}
onSplit={onSplit}
onRemove={onRemove}
onResized={onResized}
/>
</div>
);
})}
</div>
);
}
return (
<ResizablePanelGroup direction={node.direction} onLayout={handleLayout} className="h-full w-full">
{node.children.map((child, i) => (
<ChildEntry key={child.node.id} index={i} total={node.children.length}>
<ResizablePanel defaultSize={child.size} minSize={5}>
<div className="h-full w-full p-1">
<div className="h-full w-full">
<LayoutNodeRenderer
node={child.node}
registry={registry}
components={components}
editing={editing}
totalPanels={totalPanels}
onSetWidget={onSetWidget}
onSetApp={onSetApp}
onSplit={onSplit}
onRemove={onRemove}
onResized={onResized}