108 lines
3.2 KiB
TypeScript
108 lines
3.2 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect } from "react";
|
|
import { OrchestratorPanel } from "@/components/mission-control/OrchestratorPanel";
|
|
import { Button } from "@/components/ui/button";
|
|
|
|
export interface PanelConfig {
|
|
sessionId?: string;
|
|
expanded?: boolean;
|
|
}
|
|
|
|
interface MissionControlPanelProps {
|
|
panels: PanelConfig[];
|
|
onAddPanel: () => void;
|
|
onRemovePanel: (index: number) => void;
|
|
onExpandPanel: (index: number) => void;
|
|
}
|
|
|
|
export const MIN_PANEL_COUNT = 1;
|
|
export const MAX_PANEL_COUNT = 6;
|
|
|
|
export function MissionControlPanel({
|
|
panels,
|
|
onAddPanel,
|
|
onRemovePanel,
|
|
onExpandPanel,
|
|
}: MissionControlPanelProps): React.JSX.Element {
|
|
const expandedPanelIndex = panels.findIndex((panel) => panel.expanded);
|
|
const expandedPanel = expandedPanelIndex >= 0 ? panels[expandedPanelIndex] : undefined;
|
|
const canAddPanel = panels.length < MAX_PANEL_COUNT;
|
|
const canRemovePanel = panels.length > MIN_PANEL_COUNT;
|
|
|
|
useEffect(() => {
|
|
if (expandedPanelIndex < 0) {
|
|
return;
|
|
}
|
|
|
|
const handleKeyDown = (event: KeyboardEvent): void => {
|
|
if (event.key === "Escape") {
|
|
onExpandPanel(expandedPanelIndex);
|
|
}
|
|
};
|
|
|
|
window.addEventListener("keydown", handleKeyDown);
|
|
|
|
return (): void => {
|
|
window.removeEventListener("keydown", handleKeyDown);
|
|
};
|
|
}, [expandedPanelIndex, onExpandPanel]);
|
|
|
|
return (
|
|
<div className="flex h-full min-h-0 flex-col gap-3">
|
|
<div className="flex items-center justify-between">
|
|
<h2 className="text-sm font-medium text-muted-foreground">Panels</h2>
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
size="icon"
|
|
onClick={onAddPanel}
|
|
disabled={!canAddPanel}
|
|
aria-label="Add panel"
|
|
title={canAddPanel ? "Add panel" : "Maximum of 6 panels"}
|
|
>
|
|
<span aria-hidden="true" className="text-lg leading-none">
|
|
+
|
|
</span>
|
|
</Button>
|
|
</div>
|
|
<div className="min-h-0 flex-1">
|
|
{expandedPanelIndex >= 0 && expandedPanel ? (
|
|
<div className="h-full min-h-0">
|
|
<OrchestratorPanel
|
|
{...(expandedPanel.sessionId !== undefined
|
|
? { sessionId: expandedPanel.sessionId }
|
|
: {})}
|
|
onClose={() => {
|
|
onRemovePanel(expandedPanelIndex);
|
|
}}
|
|
closeDisabled={!canRemovePanel}
|
|
onExpand={() => {
|
|
onExpandPanel(expandedPanelIndex);
|
|
}}
|
|
expanded
|
|
/>
|
|
</div>
|
|
) : (
|
|
<div className="grid h-full min-h-0 auto-rows-fr grid-cols-1 gap-4 overflow-y-auto pr-1 md:grid-cols-2 xl:grid-cols-3">
|
|
{panels.map((panel, index) => (
|
|
<OrchestratorPanel
|
|
key={`panel-${String(index)}`}
|
|
{...(panel.sessionId !== undefined ? { sessionId: panel.sessionId } : {})}
|
|
onClose={() => {
|
|
onRemovePanel(index);
|
|
}}
|
|
closeDisabled={!canRemovePanel}
|
|
onExpand={() => {
|
|
onExpandPanel(index);
|
|
}}
|
|
expanded={panel.expanded ?? false}
|
|
/>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|