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>
74 lines
2.8 KiB
TypeScript
74 lines
2.8 KiB
TypeScript
/**
|
|
* `PluginLayoutSelectorSection` — the "Layouts plugins" section building block
|
|
* (ticket #43, F4, carnet §10: "Section `Layouts plugins` dans le sélecteur").
|
|
*
|
|
* Lists every plugin layout contribution currently loaded (disabled/missing
|
|
* plugins never appear — carnet §1.3: only `enabled` plugins are loaded, so
|
|
* there is nothing to filter here beyond what the registry already omits).
|
|
*
|
|
* Presentational only; not yet mounted in a layout-creation flow. The
|
|
* existing "layout" concept in this codebase (`LayoutTabs`, `LayoutKind`) is
|
|
* a whole-tab kind (`"terminal" | "gitGraph"`) picked via a fixed two-item
|
|
* dropdown, backed by a `create(name, kind)` Tauri command that only knows
|
|
* those two kinds. Wiring an actual "create a plugin layout" action needs a
|
|
* backend `LayoutKind`/`create_layout` extension (carnet §10 flags F4 as
|
|
* "DevFrontend + DevBackend si ajustement DTO layout") — this component is
|
|
* the frontend half, ready to drop into that flow once the DTO lands; see the
|
|
* F4 delivery report's open point.
|
|
*/
|
|
|
|
import type { PluginLayoutContribution } from "@/domain";
|
|
import type { PluginRuntimeRegistry } from "@/plugins/runtime";
|
|
|
|
export interface PluginLayoutChoice {
|
|
pluginId: string;
|
|
pluginDisplayName: string;
|
|
layout: PluginLayoutContribution;
|
|
}
|
|
|
|
export function listPluginLayoutChoices(registry: PluginRuntimeRegistry): PluginLayoutChoice[] {
|
|
return registry
|
|
.layoutContributions()
|
|
.map(({ pluginId, pluginDisplayName, layout }) => ({ pluginId, pluginDisplayName, layout }))
|
|
.sort(
|
|
(a, b) =>
|
|
(a.layout.order ?? 0) - (b.layout.order ?? 0) ||
|
|
a.pluginDisplayName.localeCompare(b.pluginDisplayName) ||
|
|
a.layout.label.localeCompare(b.layout.label),
|
|
);
|
|
}
|
|
|
|
interface PluginLayoutSelectorSectionProps {
|
|
registry: PluginRuntimeRegistry;
|
|
onSelect: (choice: PluginLayoutChoice) => void;
|
|
}
|
|
|
|
export function PluginLayoutSelectorSection({
|
|
registry,
|
|
onSelect,
|
|
}: PluginLayoutSelectorSectionProps) {
|
|
const choices = listPluginLayoutChoices(registry);
|
|
if (choices.length === 0) return null;
|
|
|
|
return (
|
|
<div role="group" aria-label="Layouts plugins">
|
|
<p className="px-3 pt-2 text-[0.65rem] font-semibold uppercase tracking-wide text-faint">
|
|
Layouts plugins
|
|
</p>
|
|
{choices.map((choice) => (
|
|
<button
|
|
key={`${choice.pluginId}:${choice.layout.type}`}
|
|
type="button"
|
|
role="menuitem"
|
|
aria-label={`create ${choice.layout.label} layout`}
|
|
onClick={() => onSelect(choice)}
|
|
className="flex w-full items-center justify-between gap-2 px-3 py-2 text-left text-sm text-content hover:bg-raised"
|
|
>
|
|
<span>{choice.layout.label}</span>
|
|
<span className="text-xs text-faint">{choice.pluginDisplayName}</span>
|
|
</button>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|