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>
64 lines
2.5 KiB
TypeScript
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 };
|
|
}
|