feat(frontend): système de plugins — runtime, menus, layouts custom (#43)
Lots F1-F4 : runtime de chargement/registre plugin, extension des menus existants, panneau de gestion des plugins, types de layout custom (sélecteur, fallback, cellule dédiée) branchés sur le port plugin. Suite npm typecheck/test verte (947/947). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
73
frontend/src/features/plugins/PluginLayoutCellView.tsx
Normal file
73
frontend/src/features/plugins/PluginLayoutCellView.tsx
Normal file
@ -0,0 +1,73 @@
|
||||
/**
|
||||
* `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 type { CustomPluginLayoutCell, PluginAdmin } from "@/domain";
|
||||
import { useGateways } from "@/app/di";
|
||||
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;
|
||||
}
|
||||
|
||||
export function PluginLayoutCellView({
|
||||
projectId,
|
||||
cell,
|
||||
installedPlugins,
|
||||
onStateChange,
|
||||
onOpenPlugins,
|
||||
onChooseAnotherLayout,
|
||||
}: PluginLayoutCellViewProps) {
|
||||
const { registry } = usePluginRuntime();
|
||||
const gateways = useGateways();
|
||||
const availability = resolvePluginLayoutAvailability(registry, cell, installedPlugins);
|
||||
|
||||
if (availability !== "available") {
|
||||
const providerDisplayName =
|
||||
registry.get(cell.pluginId)?.displayName ??
|
||||
installedPlugins?.find((p) => p.id === cell.pluginId)?.displayName;
|
||||
return (
|
||||
<PluginLayoutFallback
|
||||
cell={cell}
|
||||
availability={availability}
|
||||
providerDisplayName={providerDisplayName}
|
||||
onOpenPlugins={onOpenPlugins}
|
||||
onChooseAnotherLayout={onChooseAnotherLayout}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const Component = registry.layoutComponent(cell.pluginId, cell.layoutType)!;
|
||||
return (
|
||||
<Component
|
||||
projectId={projectId}
|
||||
nodeId={cell.id}
|
||||
layoutType={cell.layoutType}
|
||||
state={cell.state}
|
||||
setState={onStateChange}
|
||||
availability="available"
|
||||
gateways={{
|
||||
project: gateways.project,
|
||||
git: gateways.git,
|
||||
terminal: gateways.terminal,
|
||||
agents: gateways.agent,
|
||||
system: gateways.system,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user