Files
IdeA/frontend/src/features/plugins/usePluginMenus.ts
Blomios ac726d075e 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>
2026-07-22 07:37:11 +02:00

64 lines
2.5 KiB
TypeScript

/**
* `usePluginMenus` — turns the loaded plugin runtime registry into ready-to-
* splice `MenuBarMenu`/`MenuBarItem` entries for `ProjectsView`'s menu bar
* (ticket #43, F3, carnet §7.1/§7.2).
*
* `whenCtx` is supplied by the caller (`ProjectsView` knows `projectOpen`
* directly; git/agent/terminal/cell-focus signals are best-effort — see the
* F3 gap noted in the delivery report: there is no existing global
* agent-selected/terminal-focused/layout-cell-focused signal at the menu-bar
* level in this codebase today, so those three default to `false` until a
* future lot threads real focus state up to this level).
*/
import { useMemo } from "react";
import type { MenuTargetId } from "@/domain";
import type { MenuBarItem, MenuBarMenu } from "@/shared";
import type { PluginRuntimeRegistry, WhenContext } from "@/plugins/runtime";
import { resolveMenuItems, resolveTopLevelMenus } from "./menus";
export interface UsePluginMenusResult {
/** Top-level plugin menus, in order, ready to splice between Panneaux/Paramètres. */
topLevelMenus: MenuBarMenu[];
/** Resolved+ordered `MenuBarItem`s to append to a native menu's items. */
itemsFor: (targetMenuId: MenuTargetId) => MenuBarItem[];
runCommand: (pluginId: string, commandId: string) => Promise<void>;
}
export function usePluginMenus(
registry: PluginRuntimeRegistry,
whenCtx: WhenContext,
): UsePluginMenusResult {
const runCommand = useMemo(
() => (pluginId: string, commandId: string) => registry.runCommand(pluginId, commandId),
[registry],
);
const itemsFor = useMemo(() => {
return (targetMenuId: MenuTargetId): MenuBarItem[] =>
resolveMenuItems(registry, targetMenuId, whenCtx).map((item) => ({
id: item.id,
// Provenance is shown discreetly (carnet §0 UX decision) rather than
// with a visual section divider, which the shared `MenuBar` primitive
// doesn't support — a trailing "· Plugin Name" suffix is the least
// intrusive option that still surfaces where the item came from.
label: `${item.label} · ${item.pluginDisplayName}`,
disabled: !item.enabled,
onSelect: () => {
void runCommand(item.pluginId, item.command);
},
}));
}, [registry, whenCtx, runCommand]);
const topLevelMenus = useMemo<MenuBarMenu[]>(() => {
return resolveTopLevelMenus(registry).map((menu) => ({
id: menu.id,
label: menu.label,
items: itemsFor(menu.id),
}));
}, [registry, itemsFor]);
return { topLevelMenus, itemsFor, runCommand };
}