/**
* `PluginLayoutCellView` — renders a `customPluginLayout` top-level
* {@link LayoutNode} (ticket #43, F4, carnet v2 §3): the plugin's registered
* React component when its provider is loaded and declares the type,
* otherwise {@link PluginLayoutFallback}.
*
* State is opaque to the domain (`unknown`) and round-trips through
* `LayoutGateway` exactly like any other node field — `setState` here is the
* only way a plugin component is meant to persist it (never direct gateway
* calls from inside a plugin component).
*/
import { Component, type ErrorInfo, type ReactNode } from "react";
import type { CustomPluginLayoutCell, PluginAdmin } from "@/domain";
import { usePluginRuntime } from "./PluginRuntimeProvider";
import { PluginLayoutFallback } from "./PluginLayoutFallback";
import { resolvePluginLayoutAvailability } from "./layoutAvailability";
interface PluginLayoutCellViewProps {
projectId: string;
cell: CustomPluginLayoutCell;
installedPlugins?: PluginAdmin[];
onStateChange: (nextState: unknown) => void;
onOpenPlugins: () => void;
onChooseAnotherLayout: () => void;
}
interface PluginLayoutErrorBoundaryProps {
resetKey: string;
fallback: ReactNode;
children: ReactNode;
}
class PluginLayoutErrorBoundary extends Component<
PluginLayoutErrorBoundaryProps,
{ hasError: boolean }
> {
state = { hasError: false };
static getDerivedStateFromError(): { hasError: boolean } {
return { hasError: true };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
console.error("[plugin-layout] render failed", error, errorInfo);
}
componentDidUpdate(prevProps: PluginLayoutErrorBoundaryProps): void {
if (prevProps.resetKey !== this.props.resetKey && this.state.hasError) {
this.setState({ hasError: false });
}
}
render(): ReactNode {
if (this.state.hasError) return this.props.fallback;
return this.props.children;
}
}
export function PluginLayoutCellView({
projectId,
cell,
installedPlugins,
onStateChange,
onOpenPlugins,
onChooseAnotherLayout,
}: PluginLayoutCellViewProps) {
const { registry } = usePluginRuntime();
const availability = resolvePluginLayoutAvailability(registry, cell, installedPlugins);
const providerDisplayName =
registry.get(cell.pluginId)?.displayName ??
installedPlugins?.find((p) => p.id === cell.pluginId)?.displayName;
if (availability !== "available") {
return (
);
}
const Component = registry.layoutComponent(cell.pluginId, cell.layoutType)!;
const fallback = (
);
return (
);
}