feat(web): 前端插槽设施,作品页与整理记录行挂「补字幕」
- 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
This commit is contained in:
1 parent
079c3eafe9
commit
62119a9b30
7 files changed
+313
-11
No files matched your search
@@ -0,0 +1,57 @@
|
||||
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,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user