Files
Genarrative/src/components/image-editor/useImageCanvasGenerationSurface.tsx
T
agent 3508957eda 补齐 AGC V3 C9 参考图选择弹窗与提交快照
- 新增 ImageCanvasProjectAssetPickerDialog:独立弹窗,网格卡片、多选、筛选+搜索、清空/确认/取消,筛选状态自持

- 新增 projectAssetReferencePickerModel:素材分类与筛选判据、素材 id 快照引用构造、弹窗引用前缀判定

- 生成 composer 参考图来源菜单新增「从项目素材中选择」,由宿主决定是否出现,不改变原有从画布选择与上传两条路径

- 新增 replaceProjectAssetPickerReferences:确认时整组替换素材库来源的参考图,保留画布点选与上传的参考图

- 素材 id 经 sourceAssetId 走既有 createGenerationInputReference 落进 generationInputs.references,不新增后端接口

- 新增 13 条测试覆盖弹窗交互、筛选叠加、快照落库与整组替换语义
2026-09-10 15:13:40 +08:00

658 lines
23 KiB
TypeScript

import { X } from 'lucide-react';
import {
type CSSProperties,
type Dispatch,
type MutableRefObject,
type ReactNode,
type RefObject,
type SetStateAction,
useCallback,
useEffect,
useRef,
useState,
} from 'react';
import type {
EditorAssetSnapshot,
EditorProjectSnapshot,
} from '../../services/image-editor/editorProjectClient';
import {
PlatformFloatingMenu,
PlatformFloatingMenuItem,
} from '../common/PlatformFloatingMenu';
import { PlatformRuntimeStatusToast } from '../common/PlatformRuntimeStatusToast';
import { ImageCanvasEditorPortal } from './ImageCanvasEditorPortal';
import type {
CanvasGenerationDialogState,
CanvasHistoryAction,
CanvasLayer,
CanvasTool,
CanvasViewport,
EditorAsset,
GenerateDialogState,
ImageContextMenuState,
PublicationMaterialsWorkflowId,
SidebarPanel,
UploadTarget,
} from './ImageCanvasEditorTypes';
import { ImageCanvasGenerationComposerView } from './ImageCanvasGenerationComposerView';
import { replaceProjectAssetPickerReferences } from './ImageCanvasGenerationDialogModel';
import {
resolveCharacterAnimationPanelStyle,
resolveCropExpandPanelStyle,
resolveGenerationAnchor,
resolveGenerationComposerStyle,
resolveIconComposerStyle,
resolveQuickEditPanelStyle,
} from './ImageCanvasOverlayModel';
import { ImageCanvasProjectAssetPickerDialog } from './ImageCanvasProjectAssetPickerDialog';
import { PUBLICATION_MATERIALS_WORKFLOWS } from './ImageCanvasPublicationMaterialsModel';
import {
isProjectAssetPickerReference,
projectAssetPickerReferences,
} from './projectAssetReferencePickerModel';
import { useImageCanvasGenerationWorkflow } from './useImageCanvasGenerationWorkflow';
type CanvasGenerationDialogUpdater = (
dialog: CanvasGenerationDialogState,
) => CanvasGenerationDialogState | null;
type ToolbarOptionTool = Extract<CanvasTool, 'music' | 'spec' | 'publication'>;
type ImageCanvasGenerationSurfaceOptions = {
layers: CanvasLayer[];
canvasSize: { width: number; height: number };
viewport: CanvasViewport;
setViewport: Dispatch<SetStateAction<CanvasViewport>>;
canvasViewportRef: RefObject<HTMLDivElement | null>;
setLayers: Dispatch<SetStateAction<CanvasLayer[]>>;
layerCounterRef: MutableRefObject<number>;
specToolWrapRef: RefObject<HTMLSpanElement | null>;
musicToolWrapRef: RefObject<HTMLSpanElement | null>;
publicationToolWrapRef: RefObject<HTMLSpanElement | null>;
characterSpecButtonRef: RefObject<HTMLButtonElement | null>;
characterReferenceButtonRef: RefObject<HTMLButtonElement | null>;
iconSpecButtonRef: RefObject<HTMLButtonElement | null>;
generationReferenceButtonRef: RefObject<HTMLButtonElement | null>;
publicationReferenceButtonRef: RefObject<HTMLButtonElement | null>;
hasPendingImageReferenceUploads?: boolean;
generateDialog: GenerateDialogState | null;
setGenerateDialog: Dispatch<SetStateAction<GenerateDialogState | null>>;
activeCanvasGenerationDialog: CanvasGenerationDialogState | null;
canvasGenerationDialogs: CanvasGenerationDialogState[];
openCanvasGenerationDialog: (
dialog: Omit<CanvasGenerationDialogState, 'id'>,
) => string;
activateCanvasGenerationDialog: (
targetDialog: CanvasGenerationDialogState,
) => void;
updateCanvasGenerationDialogById: (
dialogId: string,
updater: CanvasGenerationDialogUpdater,
) => void;
hasCanvasGenerationDialogById: (dialogId: string) => boolean;
getCanvasGenerationDialogById: (
dialogId: string,
) => CanvasGenerationDialogState | undefined;
archiveActiveCanvasGenerationDialog: () => void;
removeCanvasGenerationDialogsByLayerId: (targetLayerId: string) => void;
getGeneratingDialogPlaceholder: (
dialog: GenerateDialogState,
) => GenerateDialogState['placeholder'];
appendCanvasLayersWithResources: (nextLayers: CanvasLayer[]) => void;
selectSingleLayer: (layerId: string | null) => void;
fitLayers: (
targetLayers?: CanvasLayer[],
options?: { captureHistory?: boolean },
) => void;
captureCanvasHistory: (action: CanvasHistoryAction) => void;
setActiveTool: Dispatch<SetStateAction<CanvasTool>>;
setActiveSidebarPanel: Dispatch<SetStateAction<SidebarPanel | null>>;
setMetadataLayer: Dispatch<SetStateAction<CanvasLayer | null>>;
setImageContextMenu: Dispatch<SetStateAction<ImageContextMenuState | null>>;
requestUpload: (target: UploadTarget) => void;
/** 项目已登记素材,用于参考图弹窗;为空时参考图来源菜单不出现该入口。 */
projectAssets?: readonly EditorAsset[];
persistGeneratedAsset?: (layer: CanvasLayer) => void;
persistUpdatedLayerResource?: (layer: CanvasLayer) => void;
projectId?: string | null;
currentUserId?: string | null;
assetFolderId?: string | null;
upsertGeneratedAsset?: (asset: EditorAssetSnapshot) => void;
applyProjectSnapshot?: (
project: EditorProjectSnapshot,
action?: CanvasHistoryAction,
) => void;
applyProjectSnapshotWithoutHistory?: (
project: EditorProjectSnapshot,
) => boolean | void;
flushProjectPersistence?: (options?: {
preferLatestGenerationDialogs?: boolean;
}) => Promise<void>;
refreshAssetLibrary?: () => Promise<unknown> | void;
onWalletBalanceMayHaveChanged?: () => void;
};
function renderEditorPortal(node: ReactNode) {
return <ImageCanvasEditorPortal>{node}</ImageCanvasEditorPortal>;
}
function buildToolbarPortalMenuStyle(
anchor: HTMLElement | null,
): CSSProperties {
const rect = anchor?.getBoundingClientRect();
if (!rect) {
return {
position: 'fixed',
left: 0,
top: 0,
right: 'auto',
bottom: 'auto',
zIndex: 70,
};
}
return {
position: 'fixed',
left: Math.round(rect.left),
top: Math.round(rect.top),
right: 'auto',
bottom: 'auto',
zIndex: 70,
transform: 'translateY(calc(-100% - 0.45rem))',
};
}
const TOOLBAR_OPTION_CLOSE_DELAY_MS = 160;
export function useImageCanvasGenerationSurface({
layers,
canvasSize,
viewport,
setViewport,
setLayers,
layerCounterRef,
specToolWrapRef,
musicToolWrapRef,
publicationToolWrapRef,
characterSpecButtonRef,
characterReferenceButtonRef,
iconSpecButtonRef,
generationReferenceButtonRef,
publicationReferenceButtonRef,
hasPendingImageReferenceUploads = false,
generateDialog,
setGenerateDialog,
activeCanvasGenerationDialog,
canvasGenerationDialogs,
openCanvasGenerationDialog,
activateCanvasGenerationDialog,
updateCanvasGenerationDialogById,
hasCanvasGenerationDialogById,
getCanvasGenerationDialogById,
archiveActiveCanvasGenerationDialog,
removeCanvasGenerationDialogsByLayerId,
getGeneratingDialogPlaceholder,
appendCanvasLayersWithResources,
selectSingleLayer,
fitLayers,
captureCanvasHistory,
setActiveTool,
setActiveSidebarPanel,
setMetadataLayer,
setImageContextMenu,
requestUpload,
projectAssets = [],
persistGeneratedAsset,
persistUpdatedLayerResource,
projectId,
currentUserId,
assetFolderId,
upsertGeneratedAsset,
applyProjectSnapshot,
applyProjectSnapshotWithoutHistory,
flushProjectPersistence,
refreshAssetLibrary,
onWalletBalanceMayHaveChanged,
}: ImageCanvasGenerationSurfaceOptions) {
const toolbarOptionCloseTimerRef = useRef<ReturnType<
typeof setTimeout
> | null>(null);
const generationWorkflow = useImageCanvasGenerationWorkflow({
layers,
canvasSize,
viewport,
setViewport,
setLayers,
canvasGenerationDialogs,
layerCounterRef,
generateDialog,
setGenerateDialog,
hasPendingImageReferenceUploads,
openCanvasGenerationDialog,
activateCanvasGenerationDialog,
updateCanvasGenerationDialogById,
hasCanvasGenerationDialogById,
getCanvasGenerationDialogById,
archiveActiveCanvasGenerationDialog,
removeCanvasGenerationDialogsByLayerId,
getGeneratingDialogPlaceholder,
appendCanvasLayersWithResources,
selectSingleLayer,
fitLayers,
captureCanvasHistory,
setActiveTool,
setActiveSidebarPanel,
setMetadataLayer,
setImageContextMenu,
persistGeneratedAsset,
persistUpdatedLayerResource,
projectId,
currentUserId,
assetFolderId,
upsertGeneratedAsset,
applyProjectSnapshot,
applyProjectSnapshotWithoutHistory,
flushProjectPersistence,
refreshAssetLibrary,
onWalletBalanceMayHaveChanged,
});
const activeGenerationLayer = activeCanvasGenerationDialog?.generatedLayerId
? (layers.find(
(layer) => layer.id === activeCanvasGenerationDialog.generatedLayerId,
) ?? null)
: null;
const generationAnchor = resolveGenerationAnchor({
dialog: activeCanvasGenerationDialog,
generatedLayer: activeGenerationLayer,
});
const generationComposerStyle = resolveGenerationComposerStyle({
dialog: activeCanvasGenerationDialog,
anchor: generationAnchor,
viewport,
});
const iconComposerStyle = resolveIconComposerStyle({
dialog: activeCanvasGenerationDialog,
composerStyle: generationComposerStyle,
});
const quickEditPanelStyle = resolveQuickEditPanelStyle({
panel: generationWorkflow.quickEditPanel,
sourceLayer: generationWorkflow.quickEditSourceLayer,
viewport,
canvasSize,
});
const cropExpandPanelStyle = resolveCropExpandPanelStyle({
panel: generationWorkflow.cropExpandPanel,
sourceLayer: generationWorkflow.cropExpandSourceLayer,
viewport,
canvasSize,
});
const characterAnimationPanelStyle =
activeCanvasGenerationDialog?.mode === 'character-animation'
? generationComposerStyle
: resolveCharacterAnimationPanelStyle({
panel: generationWorkflow.characterAnimationPanel,
sourceLayer: generationWorkflow.characterAnimationSourceLayer,
viewport,
canvasSize,
});
const clearToolbarOptionCloseTimer = useCallback(() => {
if (!toolbarOptionCloseTimerRef.current) {
return;
}
clearTimeout(toolbarOptionCloseTimerRef.current);
toolbarOptionCloseTimerRef.current = null;
}, []);
useEffect(
() => () => {
clearToolbarOptionCloseTimer();
},
[clearToolbarOptionCloseTimer],
);
const openToolbarOptionMenu = useCallback(
(tool: ToolbarOptionTool) => {
clearToolbarOptionCloseTimer();
generationWorkflow.setIsMusicMenuOpen(tool === 'music');
generationWorkflow.setIsSpecMenuOpen(tool === 'spec');
generationWorkflow.setIsPublicationMenuOpen(tool === 'publication');
},
[clearToolbarOptionCloseTimer, generationWorkflow],
);
const closeToolbarOptionMenu = useCallback(
(tool: ToolbarOptionTool) => {
clearToolbarOptionCloseTimer();
toolbarOptionCloseTimerRef.current = setTimeout(() => {
if (tool === 'music') {
generationWorkflow.setIsMusicMenuOpen(false);
return;
}
if (tool === 'publication') {
generationWorkflow.setIsPublicationMenuOpen(false);
return;
}
generationWorkflow.setIsSpecMenuOpen(false);
}, TOOLBAR_OPTION_CLOSE_DELAY_MS);
},
[clearToolbarOptionCloseTimer, generationWorkflow],
);
const switchGenerationTool = useCallback(
(tool: CanvasTool) => {
if (tool === 'generate') {
generationWorkflow.openGenerateDialog();
return true;
}
if (tool === 'scene') {
generationWorkflow.openSceneGenerationDialog();
return true;
}
if (tool === 'video') {
generationWorkflow.openVideoGenerationDialog();
return true;
}
if (tool === 'music') {
openToolbarOptionMenu('music');
setActiveTool('music');
return true;
}
if (tool === 'spec') {
openToolbarOptionMenu('spec');
setActiveTool('spec');
return true;
}
if (tool === 'publication') {
openToolbarOptionMenu('publication');
setActiveTool('publication');
return true;
}
if (tool === 'character') {
generationWorkflow.openCharacterGenerationDialog();
return true;
}
if (tool === 'icon') {
generationWorkflow.openIconGenerationDialog();
return true;
}
if (tool === 'ui-design') {
generationWorkflow.openUiDesignGenerationDialog();
return true;
}
return false;
},
[generationWorkflow, openToolbarOptionMenu, setActiveTool],
);
/**
* 参考图弹窗的开关与初始选择。选择列表来自「项目素材」这一组引用,
* 与画布点选 / 上传来的参考图互不覆盖。
*/
const [projectAssetPickerOpen, setProjectAssetPickerOpen] = useState(false);
const projectAssetPickerSelectedIds = (
generateDialog?.generationReferences ?? []
)
.filter((reference) => isProjectAssetPickerReference(reference))
.flatMap((reference) => {
const assetId = reference.sourceAssetId?.trim();
return assetId ? [assetId] : [];
});
const applyProjectAssetPickerSelection = useCallback(
(assetIds: string[]) => {
const references = projectAssetPickerReferences(projectAssets, assetIds);
const nonImageCount = references.filter(
(reference) => (reference.mediaType ?? 'image') !== 'image',
).length;
if (nonImageCount > 0) {
generationWorkflow.showGenerationWarning('参考图只支持图片素材');
}
setGenerateDialog((currentDialog) =>
replaceProjectAssetPickerReferences(currentDialog, references),
);
setProjectAssetPickerOpen(false);
},
[generationWorkflow, projectAssets, setGenerateDialog],
);
const generationComposerNode = (
<>
{generationWorkflow.generationWarning ? (
<PlatformRuntimeStatusToast
key={generationWorkflow.generationWarningVersion}
tone="warning"
surface="solid"
size="sm"
className="image-canvas-editor__reference-pick-toast"
role="alert"
>
<span>{generationWorkflow.generationWarning}</span>
<button
type="button"
className="image-canvas-editor__reference-pick-toast-close"
aria-label="关闭提示"
onClick={generationWorkflow.clearGenerationWarning}
>
<X aria-hidden="true" size={14} strokeWidth={2.4} />
</button>
</PlatformRuntimeStatusToast>
) : null}
<ImageCanvasGenerationComposerView
specToolWrapRef={specToolWrapRef}
publicationReferenceButtonRef={publicationReferenceButtonRef}
characterSpecButtonRef={characterSpecButtonRef}
characterReferenceButtonRef={characterReferenceButtonRef}
generationReferenceButtonRef={generationReferenceButtonRef}
iconSpecButtonRef={iconSpecButtonRef}
isPublicationReferenceMenuOpen={
generationWorkflow.isPublicationReferenceMenuOpen
}
isSpecMenuOpen={generationWorkflow.isSpecMenuOpen}
isGenerationReferenceMenuOpen={
generationWorkflow.isGenerationReferenceMenuOpen
}
isCharacterSpecMenuOpen={generationWorkflow.isCharacterSpecMenuOpen}
isCharacterReferenceMenuOpen={
generationWorkflow.isCharacterReferenceMenuOpen
}
isIconSpecMenuOpen={generationWorkflow.isIconSpecMenuOpen}
isUiDesignSpecMenuOpen={generationWorkflow.isUiDesignSpecMenuOpen}
isPickingPublicationReferenceFromCanvas={
generationWorkflow.isPickingPublicationReferenceFromCanvas
}
isPickingGenerationReferenceFromCanvas={
generationWorkflow.isPickingGenerationReferenceFromCanvas
}
isPickingQuickEditReferenceFromCanvas={
generationWorkflow.isPickingQuickEditReferenceFromCanvas
}
isPickingCharacterSpecFromCanvas={
generationWorkflow.isPickingCharacterSpecFromCanvas
}
isPickingCharacterReferenceFromCanvas={
generationWorkflow.isPickingCharacterReferenceFromCanvas
}
isPickingIconSpecFromCanvas={
generationWorkflow.isPickingIconSpecFromCanvas
}
isPickingUiDesignSpecFromCanvas={
generationWorkflow.isPickingUiDesignSpecFromCanvas
}
generateDialog={generateDialog}
backgroundMusicPromptAssist={
generationWorkflow.backgroundMusicPromptAssist
}
soundEffectPromptAssist={generationWorkflow.soundEffectPromptAssist}
updateCanvasGenerationDialogById={updateCanvasGenerationDialogById}
hasPendingImageReferenceUploads={hasPendingImageReferenceUploads}
generationComposerStyle={generationComposerStyle}
iconComposerStyle={iconComposerStyle}
quickEditPanel={generationWorkflow.quickEditPanel}
quickEditSourceLayer={generationWorkflow.quickEditSourceLayer}
quickEditPanelStyle={quickEditPanelStyle}
cropExpandPanel={generationWorkflow.cropExpandPanel}
cropExpandSourceLayer={generationWorkflow.cropExpandSourceLayer}
cropExpandPanelStyle={cropExpandPanelStyle}
characterAnimationPanel={generationWorkflow.characterAnimationPanel}
characterAnimationSourceLayer={
generationWorkflow.characterAnimationSourceLayer
}
characterAnimationPanelStyle={characterAnimationPanelStyle}
characterAnimationPrice={generationWorkflow.characterAnimationPrice}
setGenerateDialog={setGenerateDialog}
setQuickEditPanel={generationWorkflow.setQuickEditPanel}
setCropExpandPanel={generationWorkflow.setCropExpandPanel}
setCharacterAnimationPanel={
generationWorkflow.setCharacterAnimationPanel
}
setIsCharacterSpecMenuOpen={
generationWorkflow.setIsCharacterSpecMenuOpen
}
setIsCharacterReferenceMenuOpen={
generationWorkflow.setIsCharacterReferenceMenuOpen
}
setIsGenerationReferenceMenuOpen={
generationWorkflow.setIsGenerationReferenceMenuOpen
}
setIsPublicationReferenceMenuOpen={
generationWorkflow.setIsPublicationReferenceMenuOpen
}
setIsIconSpecMenuOpen={generationWorkflow.setIsIconSpecMenuOpen}
setIsUiDesignSpecMenuOpen={generationWorkflow.setIsUiDesignSpecMenuOpen}
setIsPickingGenerationReferenceFromCanvas={
generationWorkflow.setIsPickingGenerationReferenceFromCanvas
}
setIsPickingQuickEditReferenceFromCanvas={
generationWorkflow.setIsPickingQuickEditReferenceFromCanvas
}
setIsPickingPublicationReferenceFromCanvas={
generationWorkflow.setIsPickingPublicationReferenceFromCanvas
}
setIsPickingCharacterSpecFromCanvas={
generationWorkflow.setIsPickingCharacterSpecFromCanvas
}
setIsPickingCharacterReferenceFromCanvas={
generationWorkflow.setIsPickingCharacterReferenceFromCanvas
}
setIsPickingIconSpecFromCanvas={
generationWorkflow.setIsPickingIconSpecFromCanvas
}
setIsPickingUiDesignSpecFromCanvas={
generationWorkflow.setIsPickingUiDesignSpecFromCanvas
}
onOpenSpecDialog={generationWorkflow.openSpecDialog}
onRequestUpload={requestUpload}
onRequestProjectAssetPicker={
projectAssets.length > 0 &&
(generateDialog?.mode === 'generate' ||
generateDialog?.mode === 'scene' ||
generateDialog?.mode === 'quick-edit')
? () => {
generationWorkflow.setIsGenerationReferenceMenuOpen(false);
generationWorkflow.setIsPickingGenerationReferenceFromCanvas(
false,
);
setProjectAssetPickerOpen(true);
}
: undefined
}
onSubmitImageGeneration={(dialog) =>
void generationWorkflow.submitImageGeneration(dialog)
}
onSubmitIconSpritesheetGeneration={(dialog) =>
void generationWorkflow.submitIconSpritesheetGeneration(dialog)
}
onSubmitQuickEdit={() => void generationWorkflow.submitQuickEdit()}
onSubmitCropExpand={() => void generationWorkflow.submitCropExpand()}
onSubmitCharacterAnimation={() =>
void generationWorkflow.submitCharacterAnimation()
}
onRetryPerfectPixelOperation={(dialogId) =>
void generationWorkflow.retryPerfectPixelOperation(dialogId)
}
onUpdateSpecFormValue={generationWorkflow.updateSpecFormValue}
onUpdateIconDescriptionText={
generationWorkflow.updateIconDescriptionsText
}
onUpdateCharacterAnimationDuration={
generationWorkflow.updateCharacterAnimationDuration
}
onRememberImageModel={generationWorkflow.rememberImageModel}
onSpecMenuPointerEnter={() => openToolbarOptionMenu('spec')}
onSpecMenuPointerLeave={() => closeToolbarOptionMenu('spec')}
/>
{generationWorkflow.isPublicationMenuOpen
? renderEditorPortal(
<PlatformFloatingMenu
className="image-canvas-editor__spec-menu image-canvas-editor__portal-menu image-canvas-editor__publication-menu"
label="宣发素材类型"
placement="top-start"
style={buildToolbarPortalMenuStyle(
publicationToolWrapRef.current,
)}
onPointerEnter={() => openToolbarOptionMenu('publication')}
onPointerLeave={() => closeToolbarOptionMenu('publication')}
onFocus={() => openToolbarOptionMenu('publication')}
onBlur={() => closeToolbarOptionMenu('publication')}
>
{PUBLICATION_MATERIALS_WORKFLOWS.map((workflow) => (
<PlatformFloatingMenuItem
key={workflow.id}
onClick={() =>
generationWorkflow.openPublicationGenerationDialog(
workflow.id as PublicationMaterialsWorkflowId,
)
}
>
{workflow.label}
</PlatformFloatingMenuItem>
))}
</PlatformFloatingMenu>,
)
: null}
{generationWorkflow.isMusicMenuOpen
? renderEditorPortal(
<PlatformFloatingMenu
className="image-canvas-editor__spec-menu image-canvas-editor__portal-menu image-canvas-editor__music-menu"
label="生成音乐类型"
placement="top-start"
style={buildToolbarPortalMenuStyle(musicToolWrapRef.current)}
onPointerEnter={() => openToolbarOptionMenu('music')}
onPointerLeave={() => closeToolbarOptionMenu('music')}
onFocus={() => openToolbarOptionMenu('music')}
onBlur={() => closeToolbarOptionMenu('music')}
>
<PlatformFloatingMenuItem
onClick={generationWorkflow.openSoundEffectGenerationDialog}
>
生成游戏音效
</PlatformFloatingMenuItem>
<PlatformFloatingMenuItem
onClick={generationWorkflow.openBackgroundMusicGenerationDialog}
>
生成游戏背景音乐
</PlatformFloatingMenuItem>
</PlatformFloatingMenu>,
)
: null}
<ImageCanvasProjectAssetPickerDialog
open={projectAssetPickerOpen}
assets={projectAssets}
selectedAssetIds={projectAssetPickerSelectedIds}
onCancel={() => setProjectAssetPickerOpen(false)}
onConfirm={applyProjectAssetPickerSelection}
/>
</>
);
return {
...generationWorkflow,
generationComposerStyle,
generationComposerNode,
openToolbarOptionMenu,
closeToolbarOptionMenu,
switchGenerationTool,
};
}