快速编辑改造接入统一画布生成器,视频改造走视频流程
快速编辑不再使用独立 quickEditPanel,改为 openPlacedCanvasGenerationDialog 创建 quick-edit 模式的画布生成器占位及作曲面板 改造操作区分媒体类型:视频层改造走 createVideoRedrawGenerationDialogDraft 自动打开视频生成面板 appendGenerationReference 扩展支持 quick-edit 模式,pickQuickEditReferenceFromLayer / updateQuickEditSelection 走统一 generateDialog 状态 去掉 appendQuickEditReference / archiveActiveCanvasGenerationDialog 调用,新增 createVideoRedrawGenerationDialogDraft 集成测试更新对话框 aria-label 为统一生成器标签
This commit is contained in:
@@ -959,9 +959,9 @@ describe('ImageCanvasEditorView generation integration', () => {
|
||||
clientY: 120,
|
||||
});
|
||||
fireEvent.click(screen.getByRole('button', { name: '快速编辑' }));
|
||||
expect(screen.getByRole('dialog', { name: '快速编辑图片' })).toBeTruthy();
|
||||
expect(screen.getByRole('dialog', { name: '改造图片' })).toBeTruthy();
|
||||
expect(
|
||||
screen.queryByRole('button', { name: '关闭快速编辑图片' }),
|
||||
screen.queryByRole('button', { name: '关闭改造图片' }),
|
||||
).toBeNull();
|
||||
|
||||
fireEvent.pointerDown(viewport, {
|
||||
@@ -971,7 +971,7 @@ describe('ImageCanvasEditorView generation integration', () => {
|
||||
clientY: 180,
|
||||
});
|
||||
|
||||
expect(screen.queryByRole('dialog', { name: '快速编辑图片' })).toBeNull();
|
||||
expect(screen.queryByRole('dialog', { name: '改造图片' })).toBeNull();
|
||||
|
||||
fireEvent.pointerDown(sourceLayer, {
|
||||
button: 0,
|
||||
@@ -2785,7 +2785,7 @@ describe('ImageCanvasEditorView generation integration', () => {
|
||||
expect(screen.getByText('角色')).toBeTruthy();
|
||||
expect(screen.getByRole('button', { name: '生成动画' })).toBeTruthy();
|
||||
expect(screen.queryByRole('dialog', { name: '重绘图片' })).toBeNull();
|
||||
expect(screen.queryByRole('dialog', { name: '快速编辑图片' })).toBeNull();
|
||||
expect(screen.queryByRole('dialog', { name: '改造图片' })).toBeNull();
|
||||
expect(
|
||||
screen.queryByRole('dialog', { name: '角色动画生成面板' }),
|
||||
).toBeNull();
|
||||
@@ -2796,7 +2796,7 @@ describe('ImageCanvasEditorView generation integration', () => {
|
||||
expect(screen.getByRole('menuitem', { name: '生成动画' })).toBeTruthy();
|
||||
|
||||
expect(screen.queryByRole('dialog', { name: '重绘图片' })).toBeNull();
|
||||
expect(screen.queryByRole('dialog', { name: '快速编辑图片' })).toBeNull();
|
||||
expect(screen.queryByRole('dialog', { name: '改造图片' })).toBeNull();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '生成动画' }));
|
||||
expect(
|
||||
@@ -3242,7 +3242,7 @@ describe('ImageCanvasEditorView generation integration', () => {
|
||||
});
|
||||
fireEvent.click(screen.getByRole('button', { name: '快速编辑' }));
|
||||
|
||||
const quickPanel = screen.getByRole('dialog', { name: '快速编辑图片' });
|
||||
const quickPanel = screen.getByRole('dialog', { name: '改造图片' });
|
||||
expect(quickPanel.className).toContain(
|
||||
'image-canvas-editor__quick-edit-panel',
|
||||
);
|
||||
@@ -3284,7 +3284,7 @@ describe('ImageCanvasEditorView generation integration', () => {
|
||||
);
|
||||
|
||||
const updatedQuickPanel = screen.getByRole('dialog', {
|
||||
name: '快速编辑图片',
|
||||
name: '改造图片',
|
||||
});
|
||||
expect(within(updatedQuickPanel).getByText('星空参考')).toBeTruthy();
|
||||
expect(within(updatedQuickPanel).queryByLabelText('原图 图2')).toBeNull();
|
||||
@@ -3411,7 +3411,7 @@ describe('ImageCanvasEditorView generation integration', () => {
|
||||
).toBeTruthy();
|
||||
fireEvent.click(within(menu).getByRole('menuitem', { name: '快速编辑' }));
|
||||
|
||||
const panel = screen.getByRole('dialog', { name: '快速编辑图片' });
|
||||
const panel = screen.getByRole('dialog', { name: '改造图片' });
|
||||
expect(within(panel).queryByText('右键图片')).toBeNull();
|
||||
fireEvent.change(within(panel).getByLabelText('快速编辑提示词'), {
|
||||
target: { value: '换成夜晚' },
|
||||
@@ -3502,7 +3502,7 @@ describe('ImageCanvasEditorView generation integration', () => {
|
||||
});
|
||||
fireEvent.click(screen.getByRole('button', { name: '快速编辑' }));
|
||||
|
||||
const quickPanel = screen.getByRole('dialog', { name: '快速编辑图片' });
|
||||
const quickPanel = screen.getByRole('dialog', { name: '改造图片' });
|
||||
fireEvent.change(within(quickPanel).getByLabelText('快速编辑提示词'), {
|
||||
target: { value: '改成陶泥风格' },
|
||||
});
|
||||
@@ -3753,13 +3753,13 @@ describe('ImageCanvasEditorView generation integration', () => {
|
||||
clientY: 180,
|
||||
});
|
||||
fireEvent.click(screen.getByRole('button', { name: '快速编辑' }));
|
||||
const quickPanel = screen.getByRole('dialog', { name: '快速编辑图片' });
|
||||
const quickPanel = screen.getByRole('dialog', { name: '改造图片' });
|
||||
fireEvent.change(within(quickPanel).getByLabelText('快速编辑提示词'), {
|
||||
target: { value: '加一层暖光' },
|
||||
});
|
||||
fireEvent.click(within(quickPanel).getByRole('button', { name: '生成' }));
|
||||
|
||||
expect(screen.queryByRole('dialog', { name: '快速编辑图片' })).toBeNull();
|
||||
expect(screen.queryByRole('dialog', { name: '改造图片' })).toBeNull();
|
||||
expect(screen.getByAltText('画布图片:快速编辑源图')).toBeTruthy();
|
||||
expect(sourceLayer.className).not.toContain(
|
||||
'image-canvas-editor__layer--generating',
|
||||
|
||||
@@ -117,6 +117,36 @@ function renderComposer(
|
||||
}
|
||||
|
||||
describe('ImageCanvasGenerationComposerView', () => {
|
||||
it('让快速编辑复用普通图片生成面板结构', () => {
|
||||
renderComposer({
|
||||
mode: 'quick-edit',
|
||||
prompt: '',
|
||||
status: 'idle',
|
||||
composerOpen: true,
|
||||
sourceLayerId: 'layer-source',
|
||||
generationReferences: [],
|
||||
imageModel: 'gpt-image-2',
|
||||
aspectRatio: '1:1',
|
||||
imageSize: '1K',
|
||||
});
|
||||
|
||||
const panel = screen.getByRole('dialog', { name: '改造图片' });
|
||||
expect(panel.className).toContain(
|
||||
'image-canvas-editor__generation-composer',
|
||||
);
|
||||
expect(panel.className).toContain(
|
||||
'image-canvas-editor__generation-composer--image',
|
||||
);
|
||||
expect(panel.className).not.toContain(
|
||||
'image-canvas-editor__quick-edit-panel',
|
||||
);
|
||||
expect(within(panel).getByRole('textbox', { name: '改造提示词' }))
|
||||
.toBeTruthy();
|
||||
expect(
|
||||
panel.querySelector('.image-canvas-editor__generation-composer-footer'),
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('让生成UI设计图面板复用普通图片生成面板的纵向结构', () => {
|
||||
const setGenerateDialog = vi.fn();
|
||||
renderComposer({
|
||||
|
||||
@@ -559,6 +559,41 @@ export function createAudioRedrawGenerationDialogDraft(
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function createVideoRedrawGenerationDialogDraft(
|
||||
sourceLayer: CanvasLayer,
|
||||
): Omit<CanvasGenerationDialogState, 'id'> | null {
|
||||
if (sourceLayer.mediaType !== 'video') {
|
||||
return null;
|
||||
}
|
||||
const placeholderSize = resolveEditorVideoGenerationPixelSize({
|
||||
aspectRatio: DEFAULT_VIDEO_ASPECT_RATIO,
|
||||
resolution: '480p',
|
||||
});
|
||||
return {
|
||||
mode: 'video',
|
||||
sourceLayerId: sourceLayer.id,
|
||||
prompt: resolveUserGenerationPromptSnapshot(sourceLayer),
|
||||
status: 'idle',
|
||||
composerOpen: true,
|
||||
generationReferences: [createCanvasLayerReference(sourceLayer)],
|
||||
videoModel: DEFAULT_VIDEO_MODEL,
|
||||
videoAspectRatio: DEFAULT_VIDEO_ASPECT_RATIO,
|
||||
videoResolution: '480p',
|
||||
videoDurationSeconds: DEFAULT_VIDEO_DURATION_SECONDS,
|
||||
videoMode: 'std',
|
||||
videoSound: DEFAULT_VIDEO_SOUND,
|
||||
videoWebSearchEnabled: DEFAULT_VIDEO_WEB_SEARCH_ENABLED,
|
||||
placeholder: {
|
||||
x: sourceLayer.x + sourceLayer.width + 32,
|
||||
y: sourceLayer.y,
|
||||
width: placeholderSize.width,
|
||||
height: placeholderSize.height,
|
||||
originalWidth: placeholderSize.width,
|
||||
originalHeight: placeholderSize.height,
|
||||
},
|
||||
};
|
||||
}
|
||||
export function createUiDesignGenerationDialogDraft({
|
||||
canvasSize,
|
||||
viewport,
|
||||
@@ -784,7 +819,7 @@ export function appendGenerationReference(
|
||||
if (dialog?.mode === 'video' && !isSeedanceVideoModel(dialog.videoModel)) {
|
||||
return dialog;
|
||||
}
|
||||
if (dialog?.mode === 'generate') {
|
||||
if (dialog?.mode === 'generate' || dialog?.mode === 'quick-edit') {
|
||||
if (getReferenceMediaType(layer) !== 'image') {
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@ import {
|
||||
appendCharacterReference,
|
||||
appendGenerationReference,
|
||||
appendPublicationReference,
|
||||
appendQuickEditReference,
|
||||
assignCharacterSpecReference,
|
||||
assignIconSpecReference,
|
||||
assignUiDesignSpecReference,
|
||||
@@ -55,6 +54,7 @@ import {
|
||||
createSoundEffectGenerationDialogDraft,
|
||||
createSpecDialogDraft,
|
||||
createUiDesignGenerationDialogDraft,
|
||||
createVideoRedrawGenerationDialogDraft,
|
||||
createVideoGenerationDialogDraft,
|
||||
hideGeneratedLayerComposerAfterBlur,
|
||||
isCharacterSpecReferenceLayer,
|
||||
@@ -900,11 +900,30 @@ export function useImageCanvasGenerationWorkflow({
|
||||
setImageContextMenu(null);
|
||||
setMetadataLayer(null);
|
||||
setUiAssetExtractionState(null);
|
||||
archiveActiveCanvasGenerationDialog();
|
||||
setGenerateDialog(null);
|
||||
setCropExpandPanel(null);
|
||||
setCharacterAnimationPanel(null);
|
||||
setQuickEditPanel(createQuickEditPanelDraft(sourceLayer));
|
||||
const quickEditDraft = createQuickEditPanelDraft(sourceLayer);
|
||||
const quickEditSize = resolveEditorImageGenerationPixelSize({
|
||||
model: quickEditDraft.model,
|
||||
aspectRatio: quickEditDraft.aspectRatio,
|
||||
imageSize: quickEditDraft.imageSize,
|
||||
});
|
||||
openPlacedCanvasGenerationDialog({
|
||||
...createQuickEditGenerationDialogDraft({
|
||||
sourceLayer,
|
||||
prompt: quickEditDraft.prompt,
|
||||
references: quickEditDraft.quickEditReferences ?? [],
|
||||
model: quickEditDraft.model,
|
||||
aspectRatio: quickEditDraft.aspectRatio,
|
||||
imageSize: quickEditDraft.imageSize,
|
||||
frame: {
|
||||
width: quickEditSize.width,
|
||||
height: quickEditSize.height,
|
||||
},
|
||||
}),
|
||||
composerOpen: true,
|
||||
});
|
||||
setQuickEditPanel(null);
|
||||
const nextSelectionState = createUiAssetExtractionState(sourceLayer.id, {
|
||||
initialTool: null,
|
||||
});
|
||||
@@ -917,10 +936,9 @@ export function useImageCanvasGenerationWorkflow({
|
||||
},
|
||||
[
|
||||
canvasSize,
|
||||
openPlacedCanvasGenerationDialog,
|
||||
selectSingleLayer,
|
||||
archiveActiveCanvasGenerationDialog,
|
||||
setActiveTool,
|
||||
setGenerateDialog,
|
||||
setImageContextMenu,
|
||||
setMetadataLayer,
|
||||
setViewport,
|
||||
@@ -973,6 +991,17 @@ export function useImageCanvasGenerationWorkflow({
|
||||
setActiveTool('music');
|
||||
return;
|
||||
}
|
||||
if (sourceLayer.mediaType === 'video') {
|
||||
const videoDraft = createVideoRedrawGenerationDialogDraft(sourceLayer);
|
||||
if (!videoDraft) {
|
||||
return;
|
||||
}
|
||||
setQuickEditPanel(null);
|
||||
selectSingleLayer(sourceLayer.id);
|
||||
openCanvasGenerationDialog(videoDraft);
|
||||
setActiveTool('video');
|
||||
return;
|
||||
}
|
||||
if (
|
||||
sourceLayer.mediaType === undefined ||
|
||||
sourceLayer.mediaType === 'image'
|
||||
@@ -1002,17 +1031,12 @@ export function useImageCanvasGenerationWorkflow({
|
||||
setActiveTool('generate');
|
||||
return;
|
||||
}
|
||||
setGenerateDialog(null);
|
||||
setQuickEditPanel(createRedrawPanelDraft(sourceLayer));
|
||||
selectSingleLayer(sourceLayer.id);
|
||||
setActiveTool('generate');
|
||||
},
|
||||
[
|
||||
openCanvasGenerationDialog,
|
||||
openPlacedCanvasGenerationDialog,
|
||||
selectSingleLayer,
|
||||
setActiveTool,
|
||||
setGenerateDialog,
|
||||
setImageContextMenu,
|
||||
setMetadataLayer,
|
||||
],
|
||||
@@ -1275,13 +1299,14 @@ export function useImageCanvasGenerationWorkflow({
|
||||
|
||||
const pickQuickEditReferenceFromLayer = useCallback(
|
||||
(layer: CanvasLayer) => {
|
||||
setQuickEditPanel((currentPanel) =>
|
||||
appendQuickEditReference(currentPanel, layer),
|
||||
setGenerateDialog((currentDialog) =>
|
||||
appendGenerationReference(currentDialog, layer),
|
||||
);
|
||||
setIsPickingQuickEditReferenceFromCanvas(false);
|
||||
setIsGenerationReferenceMenuOpen(false);
|
||||
setImageContextMenu(null);
|
||||
},
|
||||
[setImageContextMenu],
|
||||
[setGenerateDialog, setImageContextMenu],
|
||||
);
|
||||
|
||||
const pickCharacterReferenceFromLayer = useCallback(
|
||||
@@ -1626,31 +1651,39 @@ export function useImageCanvasGenerationWorkflow({
|
||||
draftMark: null,
|
||||
marks: [...currentSelectionState.marks, normalizedMark],
|
||||
}));
|
||||
setQuickEditPanel((currentPanel) =>
|
||||
currentPanel
|
||||
setGenerateDialog((currentDialog) =>
|
||||
currentDialog?.mode === 'quick-edit' &&
|
||||
currentDialog.sourceLayerId === currentSelectionState.sourceLayerId
|
||||
? {
|
||||
...currentPanel,
|
||||
...currentDialog,
|
||||
status:
|
||||
currentPanel.status === 'failed' ? 'idle' : currentPanel.status,
|
||||
currentDialog.status === 'failed' ? 'idle' : currentDialog.status,
|
||||
errorMessage:
|
||||
currentPanel.status === 'failed'
|
||||
currentDialog.status === 'failed'
|
||||
? undefined
|
||||
: currentPanel.errorMessage,
|
||||
: currentDialog.errorMessage,
|
||||
prompt: appendQuickEditSelectionPrompt(
|
||||
currentPanel.prompt,
|
||||
currentDialog.prompt,
|
||||
nextMarkNumber,
|
||||
),
|
||||
composerOpen: true,
|
||||
}
|
||||
: currentPanel,
|
||||
: currentDialog,
|
||||
);
|
||||
}, [updateQuickEditSelectionState]);
|
||||
}, [setGenerateDialog, updateQuickEditSelectionState]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!quickEditPanel && quickEditSelectionState) {
|
||||
if (!quickEditSelectionState) {
|
||||
return;
|
||||
}
|
||||
const hasActiveQuickEditDialog =
|
||||
generateDialog?.mode === 'quick-edit' &&
|
||||
generateDialog.sourceLayerId === quickEditSelectionState.sourceLayerId;
|
||||
if (!hasActiveQuickEditDialog && !quickEditPanel) {
|
||||
quickEditSelectionStateRef.current = null;
|
||||
setQuickEditSelectionState(null);
|
||||
}
|
||||
}, [quickEditPanel, quickEditSelectionState]);
|
||||
}, [generateDialog, quickEditPanel, quickEditSelectionState]);
|
||||
|
||||
const cancelUiAssetExtraction = useCallback(() => {
|
||||
setUiAssetExtractionState(null);
|
||||
|
||||
Reference in New Issue
Block a user