Files
Genarrative/src/components/image-editor/ImageCanvasBottomToolbarView.tsx
T
k88936 b41d156edd 底部工具栏的子选项锚点 ref 改用查表
- src/components/image-editor/ImageCanvasBottomToolbarView.tsx:把 spec / music / model3d / publication 的三层三元 ref 选择链提成 toolWrapRefs 映射表,新增子选项时不再延长三元链

验证:npx vitest run src/components/image-editor/ImageCanvasBottomToolbarView.test.tsx(3 用例全绿)、npm run typecheck
2026-09-22 13:31:46 +08:00

177 lines
5.0 KiB
TypeScript

import {
CanvasToolbar,
CanvasToolbarDivider,
CanvasToolbarGroup,
} from '@genarrative/image-canvas-react';
import {
AppWindow,
Box,
Clapperboard,
ClipboardList,
Grid2X2,
Hand,
ImageIcon,
Megaphone,
Mountain,
MousePointer2,
Music,
Upload,
UserRound,
} from 'lucide-react';
import type { RefObject } from 'react';
import { EditorIconButton } from './ImageCanvasEditorPrimitives';
import type { CanvasTool } from './ImageCanvasEditorTypes';
type ToolbarOptionTool = Extract<
CanvasTool,
'music' | 'spec' | 'publication' | 'model3d'
>;
type ImageCanvasBottomToolbarViewProps = {
specToolWrapRef: RefObject<HTMLSpanElement | null>;
musicToolWrapRef: RefObject<HTMLSpanElement | null>;
publicationToolWrapRef: RefObject<HTMLSpanElement | null>;
model3dToolWrapRef: RefObject<HTMLSpanElement | null>;
effectiveTool: CanvasTool;
highlight?: boolean;
onSwitchTool: (tool: CanvasTool) => void;
onOpenToolOptions: (tool: ToolbarOptionTool) => void;
onCloseToolOptions: (tool: ToolbarOptionTool) => void;
};
const canvasTools: Array<{
id: CanvasTool;
label: string;
icon: typeof MousePointer2;
separatorBefore?: boolean;
}> = [
{ id: 'select', label: '选择工具', icon: MousePointer2 },
{ id: 'hand', label: '抓手工具', icon: Hand },
{ id: 'upload', label: '上传工具', icon: Upload },
{
id: 'generate',
label: '生成图片',
icon: ImageIcon,
separatorBefore: true,
},
{ id: 'video', label: '生成视频', icon: Clapperboard },
{ id: 'music', label: '生成音乐', icon: Music },
{
id: 'spec',
label: '生成规范',
icon: ClipboardList,
separatorBefore: true,
},
{ id: 'character', label: '生成角色形象', icon: UserRound },
{ id: 'icon', label: '生成图标素材', icon: Grid2X2 },
{ id: 'ui-design', label: '生成UI设计图', icon: AppWindow },
{ id: 'scene', label: '生成游戏场景', icon: Mountain },
{
id: 'model3d',
label: '生成 3D 模型',
icon: Box,
separatorBefore: true,
},
{ id: 'publication', label: '宣发素材', icon: Megaphone },
];
function isToolbarOptionTool(tool: CanvasTool): tool is ToolbarOptionTool {
return (
tool === 'music' ||
tool === 'spec' ||
tool === 'publication' ||
tool === 'model3d'
);
}
function hasPersistentPressedState(tool: CanvasTool) {
return tool === 'select' || tool === 'hand';
}
export function ImageCanvasBottomToolbarView({
specToolWrapRef,
musicToolWrapRef,
publicationToolWrapRef,
model3dToolWrapRef,
effectiveTool,
highlight = false,
onSwitchTool,
onOpenToolOptions,
onCloseToolOptions,
}: ImageCanvasBottomToolbarViewProps) {
// 每个可展开子选项挂在自己那一格上的定位锚点:查表比三层三元链更好加新工具。
const toolWrapRefs: Record<
ToolbarOptionTool,
RefObject<HTMLSpanElement | null>
> = {
spec: specToolWrapRef,
music: musicToolWrapRef,
model3d: model3dToolWrapRef,
publication: publicationToolWrapRef,
};
return (
<CanvasToolbar
className={[
'image-canvas-editor__bottom-toolbar',
highlight ? 'image-canvas-editor__bottom-toolbar--guided' : '',
]
.filter(Boolean)
.join(' ')}
label="AI画布工具栏"
surface="floating"
onPointerDown={(event) => event.stopPropagation()}
>
{canvasTools.map(({ id, label, icon: Icon, separatorBefore }) => {
if (isToolbarOptionTool(id)) {
return (
<CanvasToolbarGroup key={id}>
{separatorBefore ? <CanvasToolbarDivider /> : null}
<span
ref={toolWrapRefs[id]}
className={[
'image-canvas-editor__bottom-toolbar-option-wrap',
id === 'spec' ? 'image-canvas-editor__spec-tool-wrap' : '',
id === 'publication'
? 'image-canvas-editor__publication-tool-wrap'
: '',
]
.filter(Boolean)
.join(' ')}
onPointerEnter={() => onOpenToolOptions(id)}
onPointerLeave={() => onCloseToolOptions(id)}
onFocus={() => onOpenToolOptions(id)}
onBlur={() => onCloseToolOptions(id)}
>
<EditorIconButton
label={label}
title={label}
icon={Icon}
pressed={
hasPersistentPressedState(id) && effectiveTool === id
}
onClick={() => onSwitchTool(id)}
/>
</span>
</CanvasToolbarGroup>
);
}
return (
<CanvasToolbarGroup key={id}>
{separatorBefore ? <CanvasToolbarDivider /> : null}
<EditorIconButton
label={label}
title={label}
icon={Icon}
pressed={hasPersistentPressedState(id) && effectiveTool === id}
onClick={() => onSwitchTool(id)}
/>
</CanvasToolbarGroup>
);
})}
</CanvasToolbar>
);
}