调整图片编辑器参考图选择交互
- 常规参考图入口改为先弹出来源菜单,支持从画布选择和上传图片。 - 角色规范、图标规范和常规参考图来源菜单统一向上弹出。 - 画布参考图选择拦截普通图层选中逻辑,保持生成面板不隐藏。 - 补充图片编辑器交互测试与技术文档说明。
This commit is contained in:
@@ -2914,7 +2914,7 @@ describe('ImageCanvasEditorView', () => {
|
||||
|
||||
expect(generateEditorImageMock).toHaveBeenCalledWith({
|
||||
kind: 'spec',
|
||||
model: 'gpt-image-2',
|
||||
model: 'gemini-3.1-flash-image-preview',
|
||||
size: '2048x1152',
|
||||
prompt: expect.stringContaining('玩法设计:平台跳跃玩法'),
|
||||
});
|
||||
@@ -3009,23 +3009,29 @@ describe('ImageCanvasEditorView', () => {
|
||||
name: '生成角色形象',
|
||||
});
|
||||
expect(within(characterPanel).getByText('画面比例')).toBeTruthy();
|
||||
expect(within(characterPanel).getByText('大小尺寸')).toBeTruthy();
|
||||
expect(within(characterPanel).getByText('模型')).toBeTruthy();
|
||||
expect(
|
||||
within(characterPanel).getByRole('button', { name: '1:1' }),
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
within(characterPanel).getByRole('button', { name: 'GPT Image' }),
|
||||
within(characterPanel).getByRole('button', { name: '1K' }),
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
within(characterPanel).getByRole('button', { name: 'nanobanana2' }),
|
||||
).toBeTruthy();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '生成图标素材' }));
|
||||
const iconPanel = screen.getByRole('dialog', { name: '生成图标素材' });
|
||||
expect(within(iconPanel).getByText('画面比例')).toBeTruthy();
|
||||
expect(within(iconPanel).getByText('大小尺寸')).toBeTruthy();
|
||||
expect(within(iconPanel).getByText('模型')).toBeTruthy();
|
||||
expect(
|
||||
within(iconPanel).getByRole('button', { name: 'nanobanana2' }),
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('submits character generation without legacy dimension options', async () => {
|
||||
it('submits character generation with default model and dimension options', async () => {
|
||||
generateEditorImageMock.mockResolvedValueOnce({
|
||||
imageSrc: 'data:image/png;base64,character-model-options',
|
||||
width: 1024,
|
||||
@@ -3056,15 +3062,104 @@ describe('ImageCanvasEditorView', () => {
|
||||
expect.objectContaining({
|
||||
kind: 'character',
|
||||
prompt: '高个子游侠',
|
||||
model: 'gemini-3.1-flash-image-preview',
|
||||
aspectRatio: '1:1',
|
||||
imageSize: '1K',
|
||||
}),
|
||||
);
|
||||
});
|
||||
expect(generateEditorImageMock.mock.calls[0]?.[0]).not.toEqual(
|
||||
expect.objectContaining({
|
||||
aspectRatio: expect.any(String),
|
||||
imageSize: expect.any(String),
|
||||
}),
|
||||
});
|
||||
|
||||
it('remembers the last selected image model for character and icon generation', async () => {
|
||||
generateEditorImageMock.mockResolvedValueOnce({
|
||||
imageSrc: 'data:image/png;base64,character-gpt-model',
|
||||
width: 1024,
|
||||
height: 1024,
|
||||
sourceType: 'generated',
|
||||
prompt: '蓝衣剑士',
|
||||
actualPrompt: '蓝衣剑士',
|
||||
model: 'gpt-image-2',
|
||||
provider: 'VectorEngine',
|
||||
taskId: 'character-gpt-model-1',
|
||||
});
|
||||
generateEditorIconSpritesheetMock.mockResolvedValueOnce({
|
||||
spritesheetImageSrc: 'data:image/png;base64,sheet-gpt-model',
|
||||
spritesheetWidth: 1024,
|
||||
spritesheetHeight: 1024,
|
||||
iconImageSrcs: [
|
||||
{
|
||||
name: '返回按钮',
|
||||
imageSrc: 'data:image/png;base64,back',
|
||||
width: 128,
|
||||
height: 128,
|
||||
},
|
||||
],
|
||||
prompt: '图标 prompt',
|
||||
actualPrompt: '图标 prompt',
|
||||
model: 'gpt-image-2',
|
||||
provider: 'VectorEngine',
|
||||
taskId: 'icon-gpt-model-1',
|
||||
});
|
||||
render(<ImageCanvasEditorView />);
|
||||
await screen.findByAltText('画布图片:拼图素材');
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '生成角色形象' }));
|
||||
const characterPanel = screen.getByRole('dialog', {
|
||||
name: '生成角色形象',
|
||||
});
|
||||
fireEvent.click(
|
||||
within(characterPanel).getByRole('button', { name: 'gpt-image-2' }),
|
||||
);
|
||||
fireEvent.click(within(characterPanel).getByRole('button', { name: '2:3' }));
|
||||
fireEvent.click(within(characterPanel).getByRole('button', { name: '2K' }));
|
||||
fireEvent.change(within(characterPanel).getByLabelText('角色设定'), {
|
||||
target: { value: '蓝衣剑士' },
|
||||
});
|
||||
fireEvent.click(
|
||||
within(characterPanel).getByRole('button', { name: '生成' }),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(generateEditorImageMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
kind: 'character',
|
||||
prompt: '蓝衣剑士',
|
||||
model: 'gpt-image-2',
|
||||
aspectRatio: '2:3',
|
||||
imageSize: '2K',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '生成图标素材' }));
|
||||
const iconPanel = screen.getByRole('dialog', { name: '生成图标素材' });
|
||||
expect(
|
||||
within(iconPanel).getByRole('button', { name: 'gpt-image-2' }),
|
||||
).toBeTruthy();
|
||||
fireEvent.click(
|
||||
within(iconPanel).getByRole('button', { name: '图标素材规范' }),
|
||||
);
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: '上传图片' }));
|
||||
await userEvent.upload(
|
||||
screen.getByLabelText('上传图片文件'),
|
||||
new File(['icon-spec'], '图标规范.png', { type: 'image/png' }),
|
||||
);
|
||||
fireEvent.click(
|
||||
within(screen.getByRole('dialog', { name: '生成图标素材' })).getByRole(
|
||||
'button',
|
||||
{ name: '生成' },
|
||||
),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(generateEditorIconSpritesheetMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
model: 'gpt-image-2',
|
||||
aspectRatio: '1:1',
|
||||
imageSize: '1K',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps the bottom AI toolbar visible while generation panels are open', () => {
|
||||
@@ -3239,6 +3334,18 @@ describe('ImageCanvasEditorView', () => {
|
||||
const sourceMenu = screen.getByRole('menu', { name: '角色形象规范来源' });
|
||||
|
||||
expect(referenceRow?.contains(sourceMenu)).toBe(false);
|
||||
expect(sourceMenu.className).toContain('platform-floating-menu--top-start');
|
||||
|
||||
fireEvent.click(
|
||||
within(characterPanel).getByRole('button', { name: '上传常规参考图' }),
|
||||
);
|
||||
const regularReferenceMenu = screen.getByRole('menu', {
|
||||
name: '常规参考图来源',
|
||||
});
|
||||
expect(referenceRow?.contains(regularReferenceMenu)).toBe(false);
|
||||
expect(regularReferenceMenu.className).toContain(
|
||||
'platform-floating-menu--top-start',
|
||||
);
|
||||
});
|
||||
|
||||
it('uses Lovart-style reference tiles in the character generation panel', () => {
|
||||
@@ -3395,7 +3502,7 @@ describe('ImageCanvasEditorView', () => {
|
||||
|
||||
expect(generateEditorImageMock).toHaveBeenCalledWith({
|
||||
kind: 'spec',
|
||||
model: 'gpt-image-2',
|
||||
model: 'gemini-3.1-flash-image-preview',
|
||||
size: '2048x1152',
|
||||
prompt: expect.stringContaining('生成一张完整游戏UI规范汇总设定展板'),
|
||||
});
|
||||
@@ -3442,7 +3549,7 @@ describe('ImageCanvasEditorView', () => {
|
||||
|
||||
expect(generateEditorImageMock).toHaveBeenCalledWith({
|
||||
kind: 'spec',
|
||||
model: 'gpt-image-2',
|
||||
model: 'gemini-3.1-flash-image-preview',
|
||||
size: '2048x1152',
|
||||
prompt: '生成一张武器图标规范展板',
|
||||
});
|
||||
@@ -3504,15 +3611,51 @@ describe('ImageCanvasEditorView', () => {
|
||||
screen.queryByText('请选择画布中的图片作为角色形象规范,按 Esc 退出'),
|
||||
).toBeNull();
|
||||
|
||||
const canvasReferenceLayer = screen
|
||||
.getByAltText('画布图片:大鱼素材')
|
||||
.closest('button')!;
|
||||
expect(canvasReferenceLayer.className).not.toContain(
|
||||
'image-canvas-editor__layer--selected',
|
||||
);
|
||||
fireEvent.click(
|
||||
within(characterPanel).getByRole('button', { name: '上传常规参考图' }),
|
||||
);
|
||||
const regularReferenceMenu = screen.getByRole('menu', {
|
||||
name: '常规参考图来源',
|
||||
});
|
||||
fireEvent.click(
|
||||
within(regularReferenceMenu).getByRole('menuitem', {
|
||||
name: '从画布中选择',
|
||||
}),
|
||||
);
|
||||
expect(
|
||||
screen.getByText('请选择画布中的图片作为常规参考图,按 Esc 退出'),
|
||||
).toBeTruthy();
|
||||
fireEvent.pointerDown(canvasReferenceLayer, {
|
||||
button: 0,
|
||||
pointerId: 171,
|
||||
clientX: 180,
|
||||
clientY: 120,
|
||||
});
|
||||
expect(
|
||||
screen.queryByText('请选择画布中的图片作为常规参考图,按 Esc 退出'),
|
||||
).toBeNull();
|
||||
expect(screen.getByRole('dialog', { name: '生成角色形象' })).toBeTruthy();
|
||||
expect(canvasReferenceLayer.className).not.toContain(
|
||||
'image-canvas-editor__layer--selected',
|
||||
);
|
||||
expect(within(characterPanel).getByText('1')).toBeTruthy();
|
||||
|
||||
fireEvent.click(
|
||||
within(characterPanel).getByRole('button', { name: '上传常规参考图' }),
|
||||
);
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: '上传图片' }));
|
||||
await userEvent.upload(
|
||||
screen.getByLabelText('上传图片文件'),
|
||||
new File(['reference'], '常规参考.png', { type: 'image/png' }),
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(within(characterPanel).getByText('1')).toBeTruthy();
|
||||
expect(within(characterPanel).getByText('2')).toBeTruthy();
|
||||
});
|
||||
|
||||
fireEvent.change(within(characterPanel).getByLabelText('角色设定'), {
|
||||
@@ -3525,8 +3668,12 @@ describe('ImageCanvasEditorView', () => {
|
||||
expect(generateEditorImageMock).toHaveBeenCalledWith({
|
||||
kind: 'character',
|
||||
prompt: '银发游侠,蓝色披风,弓箭手,适合像素风战棋。',
|
||||
model: 'gemini-3.1-flash-image-preview',
|
||||
aspectRatio: '1:1',
|
||||
imageSize: '1K',
|
||||
referenceImageSrcs: [
|
||||
'/creation-type-references/puzzle.webp',
|
||||
'/creation-type-references/big-fish.webp',
|
||||
expect.stringMatching(/^data:image\/png;base64,/u),
|
||||
],
|
||||
});
|
||||
@@ -3554,6 +3701,8 @@ describe('ImageCanvasEditorView', () => {
|
||||
expect(within(characterInfoPanel).getByText('角色形象规范')).toBeTruthy();
|
||||
expect(within(characterInfoPanel).getByText('拼图素材')).toBeTruthy();
|
||||
expect(within(characterInfoPanel).getByText('常规参考图 1')).toBeTruthy();
|
||||
expect(within(characterInfoPanel).getByText('大鱼素材')).toBeTruthy();
|
||||
expect(within(characterInfoPanel).getByText('常规参考图 2')).toBeTruthy();
|
||||
expect(within(characterInfoPanel).getByText('常规参考.png')).toBeTruthy();
|
||||
await waitFor(() => {
|
||||
expect(saveEditorProjectLayoutMock).toHaveBeenCalledWith(
|
||||
@@ -3752,6 +3901,9 @@ describe('ImageCanvasEditorView', () => {
|
||||
expect(generateEditorIconSpritesheetMock).toHaveBeenCalledWith({
|
||||
referenceImageSrc: 'data:image/png;base64,icon-spec',
|
||||
iconDescriptions: ['返回按钮', '设置按钮'],
|
||||
model: 'gemini-3.1-flash-image-preview',
|
||||
aspectRatio: '1:1',
|
||||
imageSize: '1K',
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
|
||||
Reference in New Issue
Block a user