恢复画布视频生成入口

恢复画布底部工具栏的视频生成和 UI 设计入口

恢复视频生成弹窗的参数、模型和提交状态处理

补齐规范参考、快捷键和生成流程相关测试
This commit is contained in:
2026-06-17 23:05:17 +08:00
parent 6d964937db
commit 17768119ea
12 changed files with 465 additions and 19 deletions

View File

@@ -31,11 +31,17 @@ describe('ImageCanvasBottomToolbarView', () => {
).toBe('false');
fireEvent.click(within(toolbar).getByRole('button', { name: '抓手工具' }));
fireEvent.click(within(toolbar).getByRole('button', { name: '生成视频' }));
fireEvent.click(within(toolbar).getByRole('button', { name: '生成规范' }));
fireEvent.click(
within(toolbar).getByRole('button', { name: '生成UI设计图' }),
);
fireEvent.click(within(toolbar).getByRole('button', { name: '文字工具' }));
expect(switchTool).toHaveBeenNthCalledWith(1, 'hand');
expect(switchTool).toHaveBeenNthCalledWith(2, 'spec');
expect(switchTool).toHaveBeenNthCalledWith(3, 'text');
expect(switchTool).toHaveBeenNthCalledWith(2, 'video');
expect(switchTool).toHaveBeenNthCalledWith(3, 'spec');
expect(switchTool).toHaveBeenNthCalledWith(4, 'ui-design');
expect(switchTool).toHaveBeenNthCalledWith(5, 'text');
});
});

View File

@@ -1,9 +1,11 @@
import {
ClipboardList,
Clapperboard,
Download,
Hand,
ImageIcon,
ImagePlus,
LayoutTemplate,
MousePointer2,
Shapes,
Sparkles,
@@ -30,9 +32,11 @@ const canvasTools: Array<{
{ id: 'hand', label: '抓手工具', icon: Hand },
{ id: 'upload', label: '上传工具', icon: ImagePlus },
{ id: 'generate', label: '生成工具', icon: WandSparkles },
{ id: 'video', label: '生成视频', icon: Clapperboard },
{ id: 'spec', label: '生成规范', icon: ClipboardList },
{ id: 'character', label: '生成角色形象', icon: Sparkles },
{ id: 'icon', label: '生成图标素材', icon: ImageIcon },
{ id: 'ui-design', label: '生成UI设计图', icon: LayoutTemplate },
{ id: 'text', label: '文字工具', icon: Type },
{ id: 'shape', label: '形状标注工具', icon: Shapes },
{ id: 'export', label: '导出工具', icon: Download },

View File

@@ -94,6 +94,7 @@ function renderComposer(
describe('ImageCanvasGenerationComposerView', () => {
it('让生成UI设计图面板复用普通图片生成面板的纵向结构', () => {
const setGenerateDialog = vi.fn();
renderComposer({
mode: 'ui-design',
prompt: '',
@@ -103,6 +104,12 @@ describe('ImageCanvasGenerationComposerView', () => {
imageModel: 'gpt-image-2',
aspectRatio: '16:9',
imageSize: '1K',
}, {
isUiDesignSpecMenuOpen: true,
setGenerateDialog:
setGenerateDialog as unknown as Dispatch<
SetStateAction<GenerateDialogState | null>
>,
});
const panel = screen.getByRole('dialog', { name: '生成UI设计图' });
@@ -122,6 +129,14 @@ describe('ImageCanvasGenerationComposerView', () => {
expect(
panel.querySelector('.image-canvas-editor__generation-composer-footer'),
).toBeTruthy();
fireEvent.change(within(panel).getByRole('textbox', { name: 'UI设计要求' }), {
target: { value: '主界面和结算弹窗' },
});
expect(setGenerateDialog).toHaveBeenCalled();
const menu = screen.getByRole('menu', { name: '参考图来源' });
expect(
within(menu).getByRole('menuitem', { name: '新建图标素材规范' }),
).toBeTruthy();
});
it('让生成规范图片面板复用生成类面板 shell 和底部按钮结构', () => {
@@ -256,6 +271,50 @@ describe('ImageCanvasGenerationComposerView', () => {
within(menu).getByRole('menuitem', { name: '上传图片' }),
).toBeTruthy();
});
it('生成视频面板可切换时长、清晰度和模型并更新泥点', () => {
const setGenerateDialog = vi.fn();
renderComposer(
{
mode: 'video',
prompt: '',
status: 'idle',
composerOpen: true,
generationReferences: [],
videoModel: 'seedance2.0',
videoAspectRatio: '16:9',
videoDurationSeconds: 4,
videoResolution: '480p',
videoMode: 'std',
videoSound: 'off',
},
{
setGenerateDialog:
setGenerateDialog as unknown as Dispatch<
SetStateAction<GenerateDialogState | null>
>,
},
);
const panel = screen.getByRole('dialog', { name: '生成视频' });
expect(within(panel).getByRole('button', { name: '视频参数 16:9 · 4秒 · 480p' }))
.toBeTruthy();
expect(within(panel).getByRole('button', { name: '模型 Seedance 2.0' }))
.toBeTruthy();
expect(within(panel).getByRole('button', { name: '清晰度 480p' }))
.toBeTruthy();
expect(within(panel).getByRole('button', { name: '生成视频' }).textContent)
.toBe('40');
fireEvent.click(
within(panel).getByRole('button', { name: '视频参数 16:9 · 4秒 · 480p' }),
);
fireEvent.click(within(panel).getByRole('button', { name: '模型 Seedance 2.0' }));
fireEvent.click(within(panel).getByRole('button', { name: '清晰度 480p' }));
expect(setGenerateDialog).toHaveBeenCalledTimes(3);
});
it('生成规范参考图点击先弹来源菜单,不直接打开上传', () => {
const onRequestUpload = vi.fn();
const setIsGenerationReferenceMenuOpen = vi.fn();
@@ -293,4 +352,3 @@ describe('ImageCanvasGenerationComposerView', () => {
expect(within(menu).getByRole('menuitem', { name: '上传图片' })).toBeTruthy();
});
});

View File

@@ -1,4 +1,4 @@
import { ImageIcon } from 'lucide-react';
import { ChevronDown, ImageIcon } from 'lucide-react';
import {
type CSSProperties,
type Dispatch,
@@ -14,6 +14,7 @@ import {
} from '../common/PlatformFloatingMenu';
import { PlatformActionButton } from '../common/PlatformActionButton';
import { PlatformIconButton } from '../common/PlatformIconButton';
import { PlatformInlineOptionButton } from '../common/PlatformInlineOptionButton';
import { PlatformTextField } from '../common/PlatformTextField';
import { ImageCanvasBasicGenerationComposerView } from './ImageCanvasBasicGenerationComposerView';
import { ImageCanvasCharacterAnimationPanelView } from './ImageCanvasCharacterAnimationPanelView';
@@ -23,6 +24,7 @@ import { ImageCanvasIconSpritesheetComposerView } from './ImageCanvasIconSprites
import { ImageCanvasQuickEditPanelView } from './ImageCanvasQuickEditPanelView';
import { ImageCanvasSpecGenerationPanelView } from './ImageCanvasSpecGenerationPanelView';
import {
EDITOR_VIDEO_MODEL_OPTIONS,
SPEC_TYPE_LABEL,
calculateEditorVideoPrice,
} from './ImageCanvasGenerationModel';
@@ -155,6 +157,9 @@ function ImageCanvasVideoGenerationComposerView({
}) {
const resolution = dialog.videoResolution ?? '480p';
const durationSeconds = dialog.videoDurationSeconds ?? 4;
const currentModel =
EDITOR_VIDEO_MODEL_OPTIONS.find((item) => item.value === dialog.videoModel)
?? EDITOR_VIDEO_MODEL_OPTIONS[0];
const price = calculateEditorVideoPrice(resolution, durationSeconds);
return (
<>
@@ -211,9 +216,97 @@ function ImageCanvasVideoGenerationComposerView({
}
/>
<div className="image-canvas-editor__generation-composer-footer">
<span className="image-canvas-editor__generation-ratio">
<PlatformInlineOptionButton
className="image-canvas-editor__generation-ratio"
aria-label={`视频参数 16:9 · ${durationSeconds}秒 · ${resolution}`}
disabled={dialog.status === 'generating'}
trailingIcon={<ChevronDown className="h-3 w-3" />}
onClick={() => {
setGenerateDialog((currentDialog) =>
currentDialog?.mode === 'video'
? {
...currentDialog,
videoDurationSeconds:
currentDialog.videoDurationSeconds === 5 ? 4 : 5,
status:
currentDialog.status === 'failed'
? 'idle'
: currentDialog.status,
errorMessage:
currentDialog.status === 'failed'
? undefined
: currentDialog.errorMessage,
}
: currentDialog,
);
}}
>
16:9 · {resolution} · {durationSeconds}
</span>
</PlatformInlineOptionButton>
<PlatformInlineOptionButton
className="image-canvas-editor__generation-model"
aria-label={`模型 ${currentModel.label}`}
disabled={dialog.status === 'generating'}
trailingIcon={<ChevronDown className="h-3 w-3" />}
onClick={() => {
setGenerateDialog((currentDialog) => {
if (currentDialog?.mode !== 'video') {
return currentDialog;
}
const currentIndex = EDITOR_VIDEO_MODEL_OPTIONS.findIndex(
(item) => item.value === currentDialog.videoModel,
);
const nextModel =
EDITOR_VIDEO_MODEL_OPTIONS[
(Math.max(currentIndex, 0) + 1) %
EDITOR_VIDEO_MODEL_OPTIONS.length
];
return {
...currentDialog,
videoModel: nextModel.value,
status:
currentDialog.status === 'failed'
? 'idle'
: currentDialog.status,
errorMessage:
currentDialog.status === 'failed'
? undefined
: currentDialog.errorMessage,
};
});
}}
>
{currentModel.label}
</PlatformInlineOptionButton>
<PlatformInlineOptionButton
className="image-canvas-editor__generation-ratio"
aria-label={`清晰度 ${resolution}`}
disabled={dialog.status === 'generating'}
trailingIcon={<ChevronDown className="h-3 w-3" />}
onClick={() => {
setGenerateDialog((currentDialog) =>
currentDialog?.mode === 'video'
? {
...currentDialog,
videoResolution:
currentDialog.videoResolution === '720p'
? '480p'
: '720p',
status:
currentDialog.status === 'failed'
? 'idle'
: currentDialog.status,
errorMessage:
currentDialog.status === 'failed'
? undefined
: currentDialog.errorMessage,
}
: currentDialog,
);
}}
>
{resolution}
</PlatformInlineOptionButton>
<PlatformActionButton
type="submit"
tone="secondary"
@@ -378,6 +471,8 @@ export function ImageCanvasGenerationComposerView({
}
renderEditorPortal={renderEditorPortal}
buildPortalMenuStyle={buildPortalMenuStyle}
setGenerateDialog={setGenerateDialog}
onOpenSpecDialog={onOpenSpecDialog}
onUpdateSpecFormValue={onUpdateSpecFormValue}
onRequestUpload={onRequestUpload}
onSubmit={onSubmitImageGeneration}
@@ -398,6 +493,8 @@ export function ImageCanvasGenerationComposerView({
}
renderEditorPortal={renderEditorPortal}
buildPortalMenuStyle={buildPortalMenuStyle}
setGenerateDialog={setGenerateDialog}
onOpenSpecDialog={onOpenSpecDialog}
onUpdateSpecFormValue={onUpdateSpecFormValue}
onRequestUpload={onRequestUpload}
onSubmit={onSubmitImageGeneration}

View File

@@ -7,9 +7,11 @@ import type {
} from './ImageCanvasEditorTypes';
import {
appendCharacterReference,
appendGenerationReference,
appendIconDescriptionToDialog,
assignCharacterSpecReference,
assignIconSpecReference,
assignUiDesignSpecReference,
closeGenerateComposerDialog,
createCharacterAnimationPanelDraft,
createCharacterGenerationDialogDraft,
@@ -18,6 +20,8 @@ import {
createIconGenerationDialogDraft,
createQuickEditPanelDraft,
createSpecDialogDraft,
createUiDesignGenerationDialogDraft,
createVideoGenerationDialogDraft,
hideGeneratedLayerComposerAfterBlur,
updateCharacterAnimationDurationPanel,
updateIconDescriptionInDialog,
@@ -132,6 +136,52 @@ describe('ImageCanvasGenerationDialogModel', () => {
});
});
it('creates video and UI design drafts restored from the original generation entry work', () => {
const canvasSize = { width: 960, height: 720 };
const viewport = { x: 0, y: 0, scale: 1 };
expect(createVideoGenerationDialogDraft({ canvasSize, viewport }))
.toMatchObject({
mode: 'video',
status: 'idle',
composerOpen: true,
generationReferences: [],
videoModel: 'seedance2.0',
videoAspectRatio: '16:9',
videoResolution: '480p',
videoDurationSeconds: 4,
videoMode: 'std',
videoSound: 'off',
placeholder: {
x: 200,
y: 202.5,
width: 560,
height: 315,
originalWidth: 1280,
originalHeight: 720,
},
});
expect(
createUiDesignGenerationDialogDraft({
canvasSize,
viewport,
imageModel: 'gpt-image-2',
}),
).toMatchObject({
mode: 'ui-design',
imageModel: 'gpt-image-2',
aspectRatio: '16:9',
imageSize: '1K',
uiDesignSpecReference: null,
placeholder: {
x: 200,
y: 202.5,
width: 560,
height: 315,
},
});
});
it('creates edit, quick-edit, and character animation panel drafts', () => {
const sourceLayer = createLayer({
prompt: '原图提示',
@@ -214,6 +264,56 @@ describe('ImageCanvasGenerationDialogModel', () => {
expect(assignIconSpecReference(iconDialog, sourceLayer)).toBe(iconDialog);
});
it('routes picked canvas references to spec, image, video, and UI design fields', () => {
const sourceLayer = createLayer({ title: '参考图' });
expect(
appendGenerationReference(
{
mode: 'spec',
prompt: '',
status: 'failed',
errorMessage: '失败',
},
sourceLayer,
),
).toMatchObject({
status: 'idle',
errorMessage: undefined,
specReference: { label: '参考图' },
});
expect(
appendGenerationReference(
{
mode: 'video',
prompt: '',
status: 'idle',
generationReferences: [],
},
sourceLayer,
),
).toMatchObject({
generationReferences: [{ label: '参考图' }],
});
const uiDialog: GenerateDialogState = {
mode: 'ui-design',
prompt: '',
status: 'failed',
errorMessage: '失败',
};
expect(assignUiDesignSpecReference(uiDialog, sourceLayer)).toBe(uiDialog);
expect(
assignUiDesignSpecReference(
uiDialog,
createLayer({ title: '图标规范', assetKind: 'icon-spec' }),
),
).toMatchObject({
status: 'idle',
errorMessage: undefined,
uiDesignSpecReference: { label: '图标规范' },
});
});
it('updates failed spec and icon dialog fields back to idle state', () => {
const specDialog: GenerateDialogState = {
mode: 'spec',

View File

@@ -18,6 +18,7 @@ import {
DEFAULT_IMAGE_MODEL,
DEFAULT_SPEC_FORM_VALUES,
EDITOR_IMAGE_DIMENSION_OPTIONS,
EDITOR_VIDEO_MODEL_OPTIONS,
ICON_DESCRIPTION_LIMIT,
ICON_FRAME_DISPLAY_SIZE,
ICON_FRAME_ORIGINAL_SIZE,
@@ -25,6 +26,8 @@ import {
SPEC_FRAME_ORIGINAL_SIZE,
UI_DESIGN_FRAME_DISPLAY_SIZE,
UI_DESIGN_FRAME_ORIGINAL_SIZE,
VIDEO_FRAME_DISPLAY_SIZE,
VIDEO_FRAME_ORIGINAL_SIZE,
} from './ImageCanvasGenerationModel';
type CanvasSize = { width: number; height: number };
@@ -182,6 +185,37 @@ export function createIconGenerationDialogDraft({
};
}
export function createVideoGenerationDialogDraft({
canvasSize,
viewport,
}: {
canvasSize: CanvasSize;
viewport: CanvasViewport;
}): Omit<CanvasGenerationDialogState, 'id'> {
const worldCenter = getViewportWorldCenter({ canvasSize, viewport });
return {
mode: 'video',
prompt: '',
status: 'idle',
composerOpen: true,
generationReferences: [],
videoModel: EDITOR_VIDEO_MODEL_OPTIONS[0].value,
videoAspectRatio: '16:9',
videoResolution: '480p',
videoDurationSeconds: 4,
videoMode: 'std',
videoSound: 'off',
placeholder: {
x: worldCenter.x - VIDEO_FRAME_DISPLAY_SIZE.width / 2,
y: worldCenter.y - VIDEO_FRAME_DISPLAY_SIZE.height / 2,
width: VIDEO_FRAME_DISPLAY_SIZE.width,
height: VIDEO_FRAME_DISPLAY_SIZE.height,
originalWidth: VIDEO_FRAME_ORIGINAL_SIZE.width,
originalHeight: VIDEO_FRAME_ORIGINAL_SIZE.height,
},
};
}
export function createUiDesignGenerationDialogDraft({
canvasSize,
viewport,
@@ -200,7 +234,7 @@ export function createUiDesignGenerationDialogDraft({
composerOpen: true,
uiDesignSpecReference: null,
imageModel,
aspectRatio: dimensionDefaults.aspectRatio,
aspectRatio: '16:9',
imageSize: dimensionDefaults.imageSize,
placeholder: {
x: worldCenter.x - UI_DESIGN_FRAME_DISPLAY_SIZE.width / 2,
@@ -292,9 +326,14 @@ export function appendGenerationReference(
dialog: GenerateDialogState | null,
layer: CanvasLayer,
): GenerateDialogState | null {
return dialog?.mode === 'generate' ||
dialog?.mode === 'video' ||
dialog?.mode === 'spec'
if (dialog?.mode === 'spec') {
return {
...resetFailedGenerationDialog(dialog),
specReference: createCanvasLayerReference(layer),
composerOpen: true,
};
}
return dialog?.mode === 'generate' || dialog?.mode === 'video'
? {
...resetFailedGenerationDialog(dialog),
generationReferences: [
@@ -326,6 +365,9 @@ export function assignUiDesignSpecReference(
dialog: GenerateDialogState | null,
layer: CanvasLayer,
): GenerateDialogState | null {
if (layer.assetKind !== 'icon-spec') {
return dialog;
}
return dialog?.mode === 'ui-design'
? {
...resetFailedGenerationDialog(dialog),

View File

@@ -183,6 +183,8 @@ export function isCanvasGenerationComposerVisible(
dialog?.mode === 'generate' ||
dialog?.mode === 'spec' ||
dialog?.mode === 'character' ||
dialog?.mode === 'icon'
dialog?.mode === 'icon' ||
dialog?.mode === 'ui-design' ||
dialog?.mode === 'video'
);
}

View File

@@ -25,6 +25,7 @@ import {
import type {
GenerateDialogState,
SpecFormValues,
SpecGenerationType,
UploadTarget,
} from './ImageCanvasEditorTypes';
@@ -35,11 +36,13 @@ type ImageCanvasSpecGenerationPanelViewProps = {
generationReferenceButtonRef?: RefObject<HTMLButtonElement | null>;
setIsGenerationReferenceMenuOpen?: Dispatch<SetStateAction<boolean>>;
setIsPickingGenerationReferenceFromCanvas?: Dispatch<SetStateAction<boolean>>;
setGenerateDialog?: Dispatch<SetStateAction<GenerateDialogState | null>>;
renderEditorPortal?: (node: ReactNode) => ReactNode;
buildPortalMenuStyle?: (
anchor: HTMLElement | null,
placement: 'above' | 'below',
) => CSSProperties;
onOpenSpecDialog?: (specType: SpecGenerationType) => void;
onUpdateSpecFormValue: (key: keyof SpecFormValues, value: string) => void;
onRequestUpload: (target: UploadTarget) => void;
onSubmit: (dialog: GenerateDialogState) => void;
@@ -52,8 +55,10 @@ export function ImageCanvasSpecGenerationPanelView({
generationReferenceButtonRef,
setIsGenerationReferenceMenuOpen,
setIsPickingGenerationReferenceFromCanvas,
setGenerateDialog,
renderEditorPortal = (node) => node,
buildPortalMenuStyle = () => ({}),
onOpenSpecDialog,
onUpdateSpecFormValue,
onRequestUpload,
onSubmit,
@@ -107,9 +112,29 @@ export function ImageCanvasSpecGenerationPanelView({
size="sm"
density="compact"
className="image-canvas-editor__generation-prompt"
onChange={(event) =>
onUpdateSpecFormValue('customPrompt', event.target.value)
}
onChange={(event) => {
const nextPrompt = event.target.value;
if (setGenerateDialog) {
setGenerateDialog((currentDialog) =>
currentDialog?.mode === 'ui-design'
? {
...currentDialog,
prompt: nextPrompt,
status:
currentDialog.status === 'failed'
? 'idle'
: currentDialog.status,
errorMessage:
currentDialog.status === 'failed'
? undefined
: currentDialog.errorMessage,
}
: currentDialog,
);
return;
}
onUpdateSpecFormValue('customPrompt', nextPrompt);
}}
/>
</label>
) : dialog.specType === 'custom' ? (
@@ -304,6 +329,16 @@ export function ImageCanvasSpecGenerationPanelView({
>
</PlatformFloatingMenuItem>
{isUiDesignDialog ? (
<PlatformFloatingMenuItem
onClick={() => {
setIsGenerationReferenceMenuOpen?.(false);
onOpenSpecDialog?.('icon');
}}
>
</PlatformFloatingMenuItem>
) : null}
<PlatformFloatingMenuItem
onClick={() => {
setIsGenerationReferenceMenuOpen?.(false);

View File

@@ -125,6 +125,9 @@ function GenerationSurfaceHarness() {
>
</button>
<button type="button" onClick={() => surface.switchGenerationTool('video')}>
</button>
<button type="button" onClick={() => surface.switchGenerationTool('spec')}>
</button>
@@ -137,6 +140,12 @@ function GenerationSurfaceHarness() {
<button type="button" onClick={() => surface.switchGenerationTool('icon')}>
</button>
<button
type="button"
onClick={() => surface.switchGenerationTool('ui-design')}
>
UI设计
</button>
<button type="button" onClick={() => surface.switchGenerationTool('text')}>
</button>
@@ -183,4 +192,22 @@ describe('useImageCanvasGenerationSurface', () => {
const menu = screen.getByRole('menu', { name: '生成规范类型' });
expect(within(menu).getByRole('menuitem', { name: '角色形象规范' })).toBeTruthy();
});
it('opens video and UI design generation dialogs through the generation surface', () => {
render(<GenerationSurfaceHarness />);
fireEvent.click(screen.getByRole('button', { name: '切换视频' }));
expect(screen.getByTestId('tool').textContent).toBe('video');
expect(screen.getByTestId('dialog').textContent).toBe(
'video:open:placeholder',
);
expect(screen.getByRole('dialog', { name: '生成视频' })).toBeTruthy();
fireEvent.click(screen.getByRole('button', { name: '切换UI设计' }));
expect(screen.getByTestId('tool').textContent).toBe('ui-design');
expect(screen.getByTestId('dialog').textContent).toBe(
'ui-design:open:placeholder',
);
expect(screen.getByRole('dialog', { name: '生成UI设计图' })).toBeTruthy();
});
});

View File

@@ -158,6 +158,10 @@ export function useImageCanvasGenerationSurface({
generationWorkflow.openGenerateDialog();
return true;
}
if (tool === 'video') {
generationWorkflow.openVideoGenerationDialog();
return true;
}
if (tool === 'spec') {
generationWorkflow.setIsSpecMenuOpen((open) => !open);
setActiveTool('spec');
@@ -171,6 +175,10 @@ export function useImageCanvasGenerationSurface({
generationWorkflow.openIconGenerationDialog();
return true;
}
if (tool === 'ui-design') {
generationWorkflow.openUiDesignGenerationDialog();
return true;
}
return false;
},
[generationWorkflow, setActiveTool],

View File

@@ -35,6 +35,8 @@ import {
createIconGenerationDialogDraft,
createQuickEditPanelDraft,
createSpecDialogDraft,
createUiDesignGenerationDialogDraft,
createVideoGenerationDialogDraft,
hideGeneratedLayerComposerAfterBlur,
updateCharacterAnimationDurationPanel,
updateIconDescriptionInDialog,
@@ -300,6 +302,62 @@ export function useImageCanvasGenerationWorkflow({
viewport,
]);
const openVideoGenerationDialog = useCallback(() => {
setIsSpecMenuOpen(false);
setIsGenerationReferenceMenuOpen(false);
setIsCharacterReferenceMenuOpen(false);
setIsPickingGenerationReferenceFromCanvas(false);
setIsPickingCharacterSpecFromCanvas(false);
setIsPickingCharacterReferenceFromCanvas(false);
setIsIconSpecMenuOpen(false);
setIsPickingIconSpecFromCanvas(false);
setIsUiDesignSpecMenuOpen(false);
setIsPickingUiDesignSpecFromCanvas(false);
openCanvasGenerationDialog(
createVideoGenerationDialogDraft({ canvasSize, viewport }),
);
setActiveTool('video');
selectSingleLayer(null);
setQuickEditPanel(null);
setCharacterAnimationPanel(null);
}, [
canvasSize,
openCanvasGenerationDialog,
selectSingleLayer,
setActiveTool,
viewport,
]);
const openUiDesignGenerationDialog = useCallback(() => {
setIsSpecMenuOpen(false);
setIsGenerationReferenceMenuOpen(false);
setIsCharacterReferenceMenuOpen(false);
setIsPickingGenerationReferenceFromCanvas(false);
setIsPickingCharacterSpecFromCanvas(false);
setIsPickingCharacterReferenceFromCanvas(false);
setIsIconSpecMenuOpen(false);
setIsPickingIconSpecFromCanvas(false);
setIsUiDesignSpecMenuOpen(false);
setIsPickingUiDesignSpecFromCanvas(false);
openCanvasGenerationDialog(
createUiDesignGenerationDialogDraft({
canvasSize,
viewport,
imageModel: 'gpt-image-2',
}),
);
setActiveTool('ui-design');
selectSingleLayer(null);
setQuickEditPanel(null);
setCharacterAnimationPanel(null);
}, [
canvasSize,
openCanvasGenerationDialog,
selectSingleLayer,
setActiveTool,
viewport,
]);
const openEditDialog = useCallback(
(sourceLayer: CanvasLayer) => {
setMetadataLayer(null);
@@ -521,6 +579,8 @@ export function useImageCanvasGenerationWorkflow({
openCharacterAnimationPanel,
openCharacterGenerationDialog,
openIconGenerationDialog,
openVideoGenerationDialog,
openUiDesignGenerationDialog,
openEditDialog,
openQuickEditPanel,
pickCharacterSpecFromLayer,
@@ -566,8 +626,10 @@ export function useImageCanvasGenerationWorkflow({
openEditDialog,
openGenerateDialog,
openIconGenerationDialog,
openUiDesignGenerationDialog,
openQuickEditPanel,
openSpecDialog,
openVideoGenerationDialog,
pickCharacterReferenceFromLayer,
pickCharacterSpecFromLayer,
pickGenerationReferenceFromLayer,

View File

@@ -66,7 +66,8 @@ function isCanvasGenerationPlaceholderDialog(
dialog?.mode === 'spec' ||
dialog?.mode === 'character' ||
dialog?.mode === 'icon' ||
dialog?.mode === 'ui-design')
dialog?.mode === 'ui-design' ||
dialog?.mode === 'video')
);
}
@@ -119,7 +120,12 @@ export function useImageCanvasKeyboardShortcuts({
if (!currentDialog || currentDialog.status === 'generating') {
return currentDialog;
}
if (currentDialog.mode === 'generate' || currentDialog.mode === 'spec') {
if (
currentDialog.mode === 'generate' ||
currentDialog.mode === 'spec' ||
currentDialog.mode === 'ui-design' ||
currentDialog.mode === 'video'
) {
return {
...currentDialog,
composerOpen: false,
@@ -131,9 +137,6 @@ export function useImageCanvasKeyboardShortcuts({
if (currentDialog.mode === 'icon') {
return currentDialog;
}
if (currentDialog.mode === 'ui-design') {
return currentDialog;
}
return null;
});
};
@@ -178,6 +181,8 @@ export function useImageCanvasKeyboardShortcuts({
setIsPickingCharacterReferenceFromCanvas(false);
setIsIconSpecMenuOpen(false);
setIsPickingIconSpecFromCanvas(false);
setIsUiDesignSpecMenuOpen(false);
setIsPickingUiDesignSpecFromCanvas(false);
return;
}
}