190 lines
5.5 KiB
TypeScript
190 lines
5.5 KiB
TypeScript
/**
|
|
* HUD container - main dashboard interface
|
|
*/
|
|
|
|
import { useMemo } from "react";
|
|
import { Button } from "@mosaic/ui";
|
|
import { RotateCcw } from "lucide-react";
|
|
import { WidgetGrid } from "./WidgetGrid";
|
|
import { WidgetRenderer } from "./WidgetRenderer";
|
|
import { useLayout } from "@/lib/hooks/useLayout";
|
|
import type { WidgetPlacement } from "@mosaic/shared";
|
|
|
|
export interface HUDProps {
|
|
className?: string;
|
|
}
|
|
|
|
/**
|
|
* Registry of available widget components
|
|
* This will be populated with actual widget components
|
|
*/
|
|
const WIDGET_REGISTRY = {
|
|
TasksWidget: {
|
|
name: "tasks",
|
|
displayName: "Tasks",
|
|
description: "View and manage your tasks",
|
|
defaultWidth: 2,
|
|
defaultHeight: 3,
|
|
minWidth: 1,
|
|
minHeight: 2,
|
|
},
|
|
CalendarWidget: {
|
|
name: "calendar",
|
|
displayName: "Calendar",
|
|
description: "Upcoming events and schedule",
|
|
defaultWidth: 2,
|
|
defaultHeight: 2,
|
|
minWidth: 1,
|
|
minHeight: 2,
|
|
},
|
|
QuickCaptureWidget: {
|
|
name: "quick-capture",
|
|
displayName: "Quick Capture",
|
|
description: "Capture ideas and notes",
|
|
defaultWidth: 2,
|
|
defaultHeight: 1,
|
|
minWidth: 1,
|
|
minHeight: 1,
|
|
},
|
|
AgentStatusWidget: {
|
|
name: "agent-status",
|
|
displayName: "Agent Status",
|
|
description: "View running agent sessions",
|
|
defaultWidth: 2,
|
|
defaultHeight: 2,
|
|
minWidth: 1,
|
|
minHeight: 1,
|
|
},
|
|
OrchestratorEventsWidget: {
|
|
name: "orchestrator-events",
|
|
displayName: "Orchestrator Events",
|
|
description: "Recent events and stream health for orchestration",
|
|
defaultWidth: 2,
|
|
defaultHeight: 2,
|
|
minWidth: 1,
|
|
minHeight: 1,
|
|
},
|
|
} as const;
|
|
|
|
type WidgetRegistryKey = keyof typeof WIDGET_REGISTRY;
|
|
|
|
export function HUD({ className = "" }: HUDProps): React.JSX.Element {
|
|
const { currentLayout, updateLayout, addWidget, removeWidget, switchLayout, resetLayout } =
|
|
useLayout();
|
|
|
|
const isEditing = true; // For now, always in edit mode (can be toggled later)
|
|
|
|
const handleLayoutChange = (
|
|
newLayout: readonly { i: string; x: number; y: number; w: number; h: number }[]
|
|
): void => {
|
|
updateLayout([...newLayout] as WidgetPlacement[]);
|
|
};
|
|
|
|
const handleAddWidget = (widgetType: WidgetRegistryKey): void => {
|
|
const widgetConfig = WIDGET_REGISTRY[widgetType];
|
|
const widgetId = `${widgetConfig.name}-${String(Date.now())}`;
|
|
|
|
// Find the next available position
|
|
const maxY = currentLayout?.layout.reduce((max, w): number => Math.max(max, w.y + w.h), 0) ?? 0;
|
|
|
|
const newWidget = {
|
|
i: widgetId,
|
|
x: 0,
|
|
y: maxY,
|
|
w: widgetConfig.defaultWidth,
|
|
h: widgetConfig.defaultHeight,
|
|
minW: widgetConfig.minWidth,
|
|
minH: widgetConfig.minHeight,
|
|
isDraggable: true,
|
|
isResizable: true,
|
|
};
|
|
|
|
addWidget(newWidget);
|
|
};
|
|
|
|
const handleResetLayout = (): void => {
|
|
if (confirm("Are you sure you want to reset the layout? This will remove all widgets.")) {
|
|
resetLayout();
|
|
}
|
|
};
|
|
|
|
const widgetComponents = useMemo(() => {
|
|
if (!currentLayout?.layout) return [];
|
|
|
|
return currentLayout.layout.map((widget) => (
|
|
<WidgetRenderer
|
|
key={widget.i}
|
|
widget={widget}
|
|
isEditing={isEditing}
|
|
onRemove={removeWidget}
|
|
/>
|
|
));
|
|
}, [currentLayout?.layout, isEditing, removeWidget]);
|
|
|
|
return (
|
|
<div className={`hud-container ${className}`}>
|
|
{/* Toolbar */}
|
|
<div className="bg-white border-b border-gray-200 px-6 py-4 mb-4">
|
|
<div className="flex items-center justify-between">
|
|
<h1 className="text-2xl font-bold text-gray-900">Dashboard</h1>
|
|
<div className="flex items-center gap-2">
|
|
<select
|
|
value={currentLayout?.id ?? ""}
|
|
onChange={(e): void => {
|
|
switchLayout(e.target.value);
|
|
}}
|
|
className="px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
>
|
|
<option value="">Default Layout</option>
|
|
{/* Add more layout options here */}
|
|
</select>
|
|
|
|
<Button
|
|
onClick={handleResetLayout}
|
|
variant="secondary"
|
|
size="sm"
|
|
className="flex items-center gap-2"
|
|
>
|
|
<RotateCcw className="w-4 h-4" />
|
|
Reset
|
|
</Button>
|
|
|
|
{/* Widget type selector */}
|
|
<div className="relative">
|
|
<select
|
|
onChange={(e): void => {
|
|
const widgetType = e.target.value;
|
|
if (widgetType && widgetType in WIDGET_REGISTRY) {
|
|
handleAddWidget(widgetType as WidgetRegistryKey);
|
|
e.target.value = "";
|
|
}
|
|
}}
|
|
className="px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 cursor-pointer"
|
|
defaultValue=""
|
|
>
|
|
<option value="" disabled>
|
|
Add Widget
|
|
</option>
|
|
{Object.entries(WIDGET_REGISTRY).map(([key, config]) => (
|
|
<option key={key} value={key}>
|
|
{config.displayName}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Widget Grid */}
|
|
<WidgetGrid
|
|
layout={currentLayout?.layout ?? []}
|
|
onLayoutChange={handleLayoutChange}
|
|
isEditing={isEditing}
|
|
>
|
|
{widgetComponents}
|
|
</WidgetGrid>
|
|
</div>
|
|
);
|
|
}
|