import type { MediaKind, PublicOrganizationRecord, SlotPosition } from "@umt/contracts"; import type { ComponentType } from "react"; // 前端插槽(ADR-006):功能模块按位置注册组件,页面在挂载点渲染。默认直接显示,不做开关(内部用,做了就是要用)。 // 注册写在 app/features/*.tsx,由 app/features/index.ts 汇总;挂载点组件在 components/slots/slot-mounts.tsx。 // 位置分两类: // - 动作类(entity.actions、organize.record.actions):一个按钮 / 菜单项,点了打开功能自己的对话框; // - 面板类(entity.panels、settings.sections、sidebar.entry):直接渲染组件。 /** 插槽里拿到的作品(作品页) */ export interface SlotEntity { id: string; title: string; kind: MediaKind; status: string; } export interface SlotProps { "sidebar.entry": Record; "entity.actions": { entity: SlotEntity }; "entity.panels": { entity: SlotEntity }; "organize.record.actions": { record: PublicOrganizationRecord }; "settings.sections": Record; } export type ActionPosition = "entity.actions" | "organize.record.actions"; export type PanelPosition = Exclude; interface SlotBase { /** 全局唯一,建议 <功能>.<位置> */ id: string; /** 所属功能(contracts FEATURES 的 id) */ feature: string; /** 越小越靠前,缺省 100;同序按注册顺序 */ order?: number; } export interface ActionSlot

extends SlotBase { label: string; icon?: ComponentType<{ className?: string }>; /** 不显示时返回 false(缺省显示) */ visible?: (props: SlotProps[P]) => boolean; /** 点了之后渲染的对话框;关闭时调 onClose */ Dialog: ComponentType void }>; } export interface PanelSlot

extends SlotBase { Component: ComponentType; } export type SlotEntry

= P extends ActionPosition ? ActionSlot

: P extends PanelPosition ? PanelSlot

: never; export interface SlotRegistry { register

(position: P, entry: SlotEntry

): void; entries

(position: P): SlotEntry

[]; } export function createSlotRegistry(): SlotRegistry { const byPos = new Map(); return { register(position, entry) { const list = byPos.get(position) ?? []; if (list.some((e) => e.id === entry.id)) throw new Error(`插槽 ${position} 已有 ${entry.id}`); byPos.set(position, [...list, entry]); }, entries

(position: P) { const list = (byPos.get(position) ?? []) as SlotEntry

[]; // 稳定排序:order 相同保持注册顺序 return list .map((e, i) => ({ e, i })) .sort((a, b) => (a.e.order ?? 100) - (b.e.order ?? 100) || a.i - b.i) .map((x) => x.e); }, }; } /** 应用里唯一的插槽表(features/index.ts 往这里注册) */ export const slots: SlotRegistry = createSlotRegistry(); /** 动作类插槽里当前可见的项 */ export function visibleActions

( registry: SlotRegistry, position: P, props: SlotProps[P], ): ActionSlot

[] { return (registry.entries(position) as ActionSlot

[]).filter((a) => a.visible?.(props) ?? true); }