修正改造按钮显隐语义
按生成 action 能力决定改造按钮显隐 保留支持 action 的错误入口并在点击后提示原因 补充损坏配方与缺失来源回归测试 同步画布编辑器设计文档
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -494,7 +494,7 @@ describe('ImageCanvasGenerationModel', () => {
|
||||
sourceType: 'uploaded',
|
||||
generationInputs: buildImageGenerationInputs('上传文件'),
|
||||
}),
|
||||
).toBe(false);
|
||||
).toBe(true);
|
||||
for (const action of [
|
||||
'image.perfect-pixel',
|
||||
'spritesheet.split',
|
||||
@@ -556,6 +556,20 @@ describe('ImageCanvasGenerationModel', () => {
|
||||
},
|
||||
}),
|
||||
).not.toThrow();
|
||||
expect(
|
||||
canOpenRedrawPanel({
|
||||
...buildSourceLayer(),
|
||||
sourceType: 'generated',
|
||||
generationInputs: {
|
||||
version: 2,
|
||||
action: 'image.generate',
|
||||
fields: [
|
||||
{ id: 42, title: '生成提示词', value: '损坏数据' } as never,
|
||||
],
|
||||
references: [],
|
||||
},
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('stores the video web-search setting in reusable metadata', () => {
|
||||
|
||||
@@ -1544,48 +1544,17 @@ const LEGACY_REUSABLE_GENERATION_INPUT_TITLES = new Set(
|
||||
].map((title) => title.toLowerCase()),
|
||||
);
|
||||
|
||||
// 中文注释:只拒绝 source 丢失后无法由用户手动补齐的动作。image.edit 的快速编辑
|
||||
// 面板不能替换编辑目标,角色动作面板不能重新选择原角色图层,UI 素材 / spritesheet
|
||||
// 提取也不能替换原 UI 设计图;缺少这些必需 source 时,当前 UI 根本无法构造并提交
|
||||
// 可执行请求,所以即使 prompt、模型、尺寸等字段仍可恢复也必须拒绝改造。其它拥有
|
||||
// 手动补选入口的 reference 缺失时不拒绝,继续按 best-effort 恢复其余参数。
|
||||
const REQUIRED_SOURCE_GENERATION_ACTIONS = new Set<CanvasGenerationAction>([
|
||||
'image.edit',
|
||||
'character-animation.generate',
|
||||
'ui-design.extract-assets',
|
||||
]);
|
||||
|
||||
export function canOpenRedrawPanel(
|
||||
layer: CanvasLayer,
|
||||
availableLayers: CanvasLayer[] = [],
|
||||
) {
|
||||
if (
|
||||
layer.sourceType === 'uploaded' ||
|
||||
layer.taskId?.startsWith('pixel-art-snap-')
|
||||
) {
|
||||
export function canOpenRedrawPanel(layer: CanvasLayer) {
|
||||
// 中文注释:按钮显隐只表达 action capability。参数损坏或必需来源丢失属于
|
||||
// 点击后的恢复错误,不能在这里静默吞掉入口。
|
||||
if (layer.taskId?.startsWith('pixel-art-snap-')) {
|
||||
return false;
|
||||
}
|
||||
const decodedInputs = decodeCanvasGenerationInputs(layer.generationInputs);
|
||||
if (decodedInputs.ok) {
|
||||
if (!isRemixableCanvasGenerationAction(decodedInputs.inputs.action)) {
|
||||
return false;
|
||||
}
|
||||
if (!REQUIRED_SOURCE_GENERATION_ACTIONS.has(decodedInputs.inputs.action)) {
|
||||
return true;
|
||||
}
|
||||
const sourceReference = decodedInputs.inputs.references.find(
|
||||
(reference) => reference.id === 'source',
|
||||
);
|
||||
if (!sourceReference) {
|
||||
return false;
|
||||
}
|
||||
return availableLayers.some((candidate) =>
|
||||
sourceReference.refType === 'project-resource'
|
||||
? candidate.resourceId === sourceReference.refId
|
||||
: candidate.sourceAssetId === sourceReference.refId,
|
||||
);
|
||||
const action = layer.generationInputs?.action;
|
||||
if (action !== undefined) {
|
||||
return isRemixableCanvasGenerationAction(action);
|
||||
}
|
||||
if (layer.generationInputs?.version === 2) {
|
||||
if (layer.sourceType === 'uploaded' || layer.generationInputs?.version === 2) {
|
||||
return false;
|
||||
}
|
||||
return Boolean(
|
||||
|
||||
@@ -44,7 +44,6 @@ function renderSelectedToolbar(
|
||||
const selectedLayer = createLayer();
|
||||
const props: Parameters<typeof ImageCanvasSelectedLayerToolbarView>[0] = {
|
||||
selectedLayer,
|
||||
availableLayers: [selectedLayer],
|
||||
selectedToolbarStyle: { left: 12, top: 24 },
|
||||
onOpenQuickEditPanel: vi.fn(),
|
||||
onOpenRedrawPanel: vi.fn(),
|
||||
@@ -454,14 +453,10 @@ describe('ImageCanvasSelectedLayerToolbarView', () => {
|
||||
within(actionToolbar)
|
||||
.getAllByRole('button')
|
||||
.map((button) => button.getAttribute('aria-label')),
|
||||
).toEqual(['快速编辑', '下载按钮']);
|
||||
).toEqual(['快速编辑', '改造', '下载按钮']);
|
||||
});
|
||||
|
||||
it('shows redraw only when a required V2 source is available', () => {
|
||||
const sourceLayer = createLayer({
|
||||
id: 'source-layer',
|
||||
resourceId: 'source-resource',
|
||||
});
|
||||
it('keeps redraw visible when a supported action has lost its required source', () => {
|
||||
const generatedLayer = createLayer({
|
||||
id: 'animation-layer',
|
||||
resourceId: 'animation-resource',
|
||||
@@ -477,18 +472,17 @@ describe('ImageCanvasSelectedLayerToolbarView', () => {
|
||||
title: '原角色图',
|
||||
label: '角色',
|
||||
refType: 'project-resource',
|
||||
refId: 'source-resource',
|
||||
refId: 'missing-source-resource',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
renderSelectedToolbar({
|
||||
selectedLayer: generatedLayer,
|
||||
availableLayers: [generatedLayer, sourceLayer],
|
||||
});
|
||||
const props = renderSelectedToolbar({ selectedLayer: generatedLayer });
|
||||
|
||||
expect(screen.getByRole('button', { name: '改造' })).toBeTruthy();
|
||||
fireEvent.click(screen.getByRole('button', { name: '改造' }));
|
||||
|
||||
expect(props.onOpenRedrawPanel).toHaveBeenCalledWith(generatedLayer);
|
||||
});
|
||||
|
||||
it('renders character animation only for character layers', () => {
|
||||
@@ -542,7 +536,6 @@ describe('ImageCanvasSelectedLayerToolbarView', () => {
|
||||
const { rerender } = render(
|
||||
<ImageCanvasSelectedLayerToolbarView
|
||||
selectedLayer={null}
|
||||
availableLayers={[]}
|
||||
selectedToolbarStyle={{ left: 0, top: 0 }}
|
||||
onOpenQuickEditPanel={vi.fn()}
|
||||
onOpenRedrawPanel={vi.fn()}
|
||||
@@ -565,7 +558,6 @@ describe('ImageCanvasSelectedLayerToolbarView', () => {
|
||||
rerender(
|
||||
<ImageCanvasSelectedLayerToolbarView
|
||||
selectedLayer={createLayer()}
|
||||
availableLayers={[]}
|
||||
selectedToolbarStyle={null}
|
||||
onOpenQuickEditPanel={vi.fn()}
|
||||
onOpenRedrawPanel={vi.fn()}
|
||||
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
|
||||
type ImageCanvasSelectedLayerToolbarViewProps = {
|
||||
selectedLayer: CanvasLayer | null;
|
||||
availableLayers: CanvasLayer[];
|
||||
selectedToolbarStyle: CSSProperties | null;
|
||||
onOpenQuickEditPanel: (layer: CanvasLayer) => void;
|
||||
onOpenRedrawPanel: (layer: CanvasLayer) => void;
|
||||
@@ -40,7 +39,6 @@ type ImageCanvasSelectedLayerToolbarViewProps = {
|
||||
|
||||
export function ImageCanvasSelectedLayerToolbarView({
|
||||
selectedLayer,
|
||||
availableLayers,
|
||||
selectedToolbarStyle,
|
||||
onOpenQuickEditPanel,
|
||||
onOpenRedrawPanel,
|
||||
@@ -59,10 +57,7 @@ export function ImageCanvasSelectedLayerToolbarView({
|
||||
if (!selectedLayer || !selectedToolbarStyle) {
|
||||
return null;
|
||||
}
|
||||
const canRedraw = canOpenRedrawPanel(
|
||||
selectedLayer,
|
||||
availableLayers,
|
||||
);
|
||||
const canRedraw = canOpenRedrawPanel(selectedLayer);
|
||||
|
||||
if (selectedLayer.mediaType === 'audio') {
|
||||
return (
|
||||
|
||||
@@ -404,7 +404,6 @@ export function ImageCanvasStageView({
|
||||
/>
|
||||
|
||||
<ImageCanvasSelectedLayerToolbarView
|
||||
availableLayers={layers}
|
||||
selectedLayer={
|
||||
uiAssetExtractionState || quickEditSelectionState
|
||||
? null
|
||||
|
||||
@@ -5001,6 +5001,37 @@ describe('useImageCanvasGenerationWorkflow', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('reports malformed metadata after clicking a supported redraw action', () => {
|
||||
render(
|
||||
<GenerationWorkflowHarness
|
||||
initialLayers={[
|
||||
createLayer({
|
||||
sourceType: 'generated',
|
||||
generationInputs: {
|
||||
version: 2,
|
||||
action: 'image.generate',
|
||||
fields: [
|
||||
{
|
||||
id: 42,
|
||||
title: '生成提示词',
|
||||
value: '损坏数据',
|
||||
} as never,
|
||||
],
|
||||
references: [],
|
||||
},
|
||||
}),
|
||||
]}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '打开图片改造' }));
|
||||
|
||||
expect(screen.getByTestId('dialog').textContent).toBe('-');
|
||||
expect(screen.getByTestId('reference-pick-warning').textContent).toBe(
|
||||
'保存的生成参数版本不受支持或数据损坏,无法改造。',
|
||||
);
|
||||
});
|
||||
|
||||
it('warns when a legacy recipe is restored successfully', () => {
|
||||
render(
|
||||
<GenerationWorkflowHarness
|
||||
|
||||
@@ -1791,6 +1791,18 @@ export function useImageCanvasGenerationWorkflow({
|
||||
const decodedGenerationInputs = decodeCanvasGenerationInputs(
|
||||
sourceLayer.generationInputs,
|
||||
);
|
||||
const hasInvalidVersionedGenerationInputs = Boolean(
|
||||
sourceLayer.generationInputs &&
|
||||
(sourceLayer.generationInputs.version !== undefined ||
|
||||
sourceLayer.generationInputs.action !== undefined) &&
|
||||
!decodedGenerationInputs.ok,
|
||||
);
|
||||
if (hasInvalidVersionedGenerationInputs) {
|
||||
showGenerationWarning(
|
||||
'保存的生成参数版本不受支持或数据损坏,无法改造。',
|
||||
);
|
||||
return;
|
||||
}
|
||||
const normalizedGenerationInputs = decodedGenerationInputs.ok
|
||||
? decodedGenerationInputs.inputs
|
||||
: null;
|
||||
|
||||
Reference in New Issue
Block a user