- lib/slots.ts 按位置注册(侧栏入口、作品页操作区 / 面板区、整理记录行操作、设置分区),默认直接显示; components/slots 挂载点:面板类直接渲染,动作类渲染成按钮 / 菜单项、对话框在菜单外渲染 - 作品页操作区与面板区、整理记录行菜单接入插槽;补字幕改由 features/subtitles.tsx 注册, 作品页按钮行为不变,整理记录行菜单新增「补字幕」(成功 / 部分成功的入库类整理) Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
58 lines
1.8 KiB
TypeScript
58 lines
1.8 KiB
TypeScript
import { type ComponentType, type ReactNode, useState } from "react";
|
|
import "~/features/index.ts";
|
|
import {
|
|
type ActionPosition,
|
|
type ActionSlot,
|
|
type PanelPosition,
|
|
type PanelSlot,
|
|
type SlotProps,
|
|
type SlotRegistry,
|
|
slots,
|
|
visibleActions,
|
|
} from "~/lib/slots.ts";
|
|
|
|
// 插槽挂载点(ADR-006)。功能在 app/features/*.tsx 里注册,这里只负责在页面上渲染;没有注册时什么都不渲染。
|
|
|
|
/** 面板类插槽:按顺序渲染所有注册的组件 */
|
|
export function SlotPanels<P extends PanelPosition>({
|
|
position,
|
|
props,
|
|
registry = slots,
|
|
}: {
|
|
position: P;
|
|
props: SlotProps[P];
|
|
registry?: SlotRegistry;
|
|
}) {
|
|
const entries = registry.entries(position) as PanelSlot<P>[];
|
|
if (entries.length === 0) return null;
|
|
return (
|
|
<>
|
|
{entries.map(({ id, Component }) => (
|
|
<Component key={id} {...props} />
|
|
))}
|
|
</>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 动作类插槽:返回可见的项(页面按自己的样式渲染成按钮或菜单项)和当前打开的对话框。
|
|
* 对话框由页面渲染在菜单外面(菜单关了对话框也不会跟着卸载)。
|
|
*/
|
|
export function useSlotActions<P extends ActionPosition>(
|
|
position: P,
|
|
registry: SlotRegistry = slots,
|
|
): {
|
|
items: (props: SlotProps[P]) => ActionSlot<P>[];
|
|
open: (entry: ActionSlot<P>, props: SlotProps[P]) => void;
|
|
dialog: ReactNode;
|
|
} {
|
|
const [active, setActive] = useState<{ entry: ActionSlot<P>; props: SlotProps[P] } | null>(null);
|
|
// 泛型位置的 props 与 IntrinsicAttributes 交不出来,这里按具体对象渲染
|
|
const Dialog = active?.entry.Dialog as ComponentType<{ onClose: () => void }> | undefined;
|
|
return {
|
|
items: (props) => visibleActions(registry, position, props),
|
|
open: (entry, props) => setActive({ entry, props }),
|
|
dialog: Dialog && active ? <Dialog {...(active.props as object)} onClose={() => setActive(null)} /> : null,
|
|
};
|
|
}
|