修复画布快速编辑还原生成器
直接点击生成图时还原原生成器对话框
非生成图直接点击只选中不打开生成器
快速编辑按钮和右键入口按素材元数据打开对应生成器
补齐资源元数据回填和入口边界测试
(cherry picked from commit 45e4b6e64a)
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -645,11 +645,12 @@ export function hydrateLayer(
|
||||
? imageSequenceFrames
|
||||
: undefined,
|
||||
previewVideoPath: stringOrNull(snapshot.previewVideoPath),
|
||||
prompt: stringOrNull(snapshot.prompt),
|
||||
actualPrompt: stringOrNull(snapshot.actualPrompt),
|
||||
model: stringOrNull(snapshot.model),
|
||||
provider: stringOrNull(snapshot.provider),
|
||||
taskId: stringOrNull(snapshot.taskId),
|
||||
prompt: stringOrNull(snapshot.prompt) ?? stringOrNull(resource?.prompt),
|
||||
actualPrompt:
|
||||
stringOrNull(snapshot.actualPrompt) ?? stringOrNull(resource?.actualPrompt),
|
||||
model: stringOrNull(snapshot.model) ?? stringOrNull(resource?.model),
|
||||
provider: stringOrNull(snapshot.provider) ?? stringOrNull(resource?.provider),
|
||||
taskId: stringOrNull(snapshot.taskId) ?? stringOrNull(resource?.taskId),
|
||||
objectKey: stringOrNull(snapshot.objectKey) ?? stringOrNull(resource?.objectKey),
|
||||
assetObjectId:
|
||||
stringOrNull(snapshot.assetObjectId) ?? stringOrNull(resource?.assetObjectId),
|
||||
|
||||
@@ -48,6 +48,7 @@ import type {
|
||||
QuickEditPanelState,
|
||||
} from './ImageCanvasEditorTypes';
|
||||
import { getEditorUploadAccept, isImageFile } from './ImageCanvasFileModel';
|
||||
import { createLayerGenerationDialogDraft } from './ImageCanvasGenerationDialogModel';
|
||||
import { applyEditorGenerationPricingConfig } from './ImageCanvasGenerationModel';
|
||||
import { isCanvasGenerationComposerVisible } from './ImageCanvasOverlayModel';
|
||||
import {
|
||||
@@ -88,6 +89,25 @@ function resolveLayerProjectResourceImageSrc(layer: CanvasLayer) {
|
||||
return layer.src;
|
||||
}
|
||||
|
||||
function findSourceAnimationLayer(
|
||||
layers: CanvasLayer[],
|
||||
sourceLayer: CanvasLayer,
|
||||
) {
|
||||
if (sourceLayer.assetKind !== 'character-animation') {
|
||||
return null;
|
||||
}
|
||||
const sourceResourceId = sourceLayer.sourceResourceId?.trim();
|
||||
if (!sourceResourceId) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
layers.find(
|
||||
(layer) =>
|
||||
layer.resourceId === sourceResourceId || layer.id === sourceResourceId,
|
||||
) ?? null
|
||||
);
|
||||
}
|
||||
|
||||
function isEditablePasteTarget(target: EventTarget | null) {
|
||||
const element = target instanceof HTMLElement ? target : null;
|
||||
if (!element) {
|
||||
@@ -892,7 +912,6 @@ export function ImageCanvasEditorView() {
|
||||
isPickingUiDesignSpecFromCanvas,
|
||||
setIsPickingUiDesignSpecFromCanvas,
|
||||
openCharacterAnimationPanel,
|
||||
openQuickEditPanel,
|
||||
openRedrawPanel,
|
||||
openCropExpandPanel,
|
||||
removeSelectedLayerBackground,
|
||||
@@ -960,6 +979,51 @@ export function ImageCanvasEditorView() {
|
||||
clearGenerationPanelsAfterBlur,
|
||||
getCanvasPointFromClient,
|
||||
});
|
||||
const openLayerGenerationDialog = useCallback(
|
||||
(layer: CanvasLayer) => {
|
||||
const existingDialog = [...canvasGenerationDialogsRef.current]
|
||||
.reverse()
|
||||
.find((dialog) => dialog.generatedLayerId === layer.id);
|
||||
if (existingDialog) {
|
||||
activateCanvasGenerationDialog(existingDialog);
|
||||
setSelectedLayerId(layer.id);
|
||||
setSelectedLayerIds([layer.id]);
|
||||
return true;
|
||||
}
|
||||
const restoredDraft = createLayerGenerationDialogDraft({
|
||||
sourceLayer: layer,
|
||||
canvasSize,
|
||||
viewport: viewportRef.current,
|
||||
sourceAnimationLayer: findSourceAnimationLayer(
|
||||
layersRef.current,
|
||||
layer,
|
||||
),
|
||||
});
|
||||
if (!restoredDraft) {
|
||||
return false;
|
||||
}
|
||||
openCanvasGenerationDialog(restoredDraft);
|
||||
setSelectedLayerId(layer.id);
|
||||
setSelectedLayerIds([layer.id]);
|
||||
return true;
|
||||
},
|
||||
[
|
||||
activateCanvasGenerationDialog,
|
||||
canvasSize,
|
||||
openCanvasGenerationDialog,
|
||||
setSelectedLayerId,
|
||||
setSelectedLayerIds,
|
||||
],
|
||||
);
|
||||
const openGeneratedLayerGenerationDialog = useCallback(
|
||||
(layer: CanvasLayer) => {
|
||||
if (layer.sourceType !== 'generated') {
|
||||
return false;
|
||||
}
|
||||
return openLayerGenerationDialog(layer);
|
||||
},
|
||||
[openLayerGenerationDialog],
|
||||
);
|
||||
const contextMenuLayer =
|
||||
contextMenu?.kind === 'layer'
|
||||
? (layers.find((layer) => layer.id === contextMenu.layerId) ?? null)
|
||||
@@ -1046,6 +1110,7 @@ export function ImageCanvasEditorView() {
|
||||
pickCharacterReferenceFromLayer,
|
||||
pickIconSpecFromLayer,
|
||||
pickUiDesignSpecFromLayer,
|
||||
openLayerGenerationDialog: openGeneratedLayerGenerationDialog,
|
||||
activateCanvasGenerationDialog,
|
||||
updateCanvasGenerationDialogById,
|
||||
moveViewportFromMinimapPointer,
|
||||
@@ -1549,7 +1614,7 @@ export function ImageCanvasEditorView() {
|
||||
onActivateGenerationDialog: activateCanvasGenerationDialog,
|
||||
onToggleTaskSidebar: generationSurface.toggleTaskSidebar,
|
||||
onCropExpandHandlePointerDown: generationSurface.startCropExpandFrameResize,
|
||||
onOpenQuickEditPanel: openQuickEditPanel,
|
||||
onOpenQuickEditPanel: openLayerGenerationDialog,
|
||||
onOpenRedrawPanel: openRedrawPanel,
|
||||
onOpenCropExpandPanel: openCropExpandPanel,
|
||||
onRemoveBackground: removeSelectedLayerBackground,
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
createEditDialogDraft,
|
||||
createGenerateDialogDraft,
|
||||
createIconGenerationDialogDraft,
|
||||
createLayerGenerationDialogDraft,
|
||||
createPublicationGenerationDialogDraft,
|
||||
createQuickEditPanelDraft,
|
||||
createRedrawPanelDraft,
|
||||
@@ -593,6 +594,61 @@ describe('ImageCanvasGenerationDialogModel', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('restores a plain generated layer from its own metadata', () => {
|
||||
const canvasSize = { width: 960, height: 720 };
|
||||
const viewport = { x: 0, y: 0, scale: 1 };
|
||||
|
||||
expect(
|
||||
createLayerGenerationDialogDraft({
|
||||
sourceLayer: createLayer({
|
||||
sourceType: 'generated',
|
||||
prompt: '图层提示词',
|
||||
model: 'gpt-image-2',
|
||||
generationInputs: {
|
||||
fields: [{ title: '生成提示词', value: '元数据提示词' }],
|
||||
references: [],
|
||||
},
|
||||
}),
|
||||
canvasSize,
|
||||
viewport,
|
||||
}),
|
||||
).toMatchObject({
|
||||
mode: 'generate',
|
||||
prompt: '元数据提示词',
|
||||
generatedLayerId: 'layer-source',
|
||||
imageModel: 'gpt-image-2',
|
||||
placeholder: {
|
||||
x: 120,
|
||||
y: 140,
|
||||
width: 320,
|
||||
height: 240,
|
||||
originalWidth: 1024,
|
||||
originalHeight: 768,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('restores a tagged uploaded layer from its current asset kind', () => {
|
||||
expect(
|
||||
createLayerGenerationDialogDraft({
|
||||
sourceLayer: createLayer({
|
||||
sourceType: 'uploaded',
|
||||
assetKind: 'character',
|
||||
generationInputs: {
|
||||
fields: [{ title: '角色设定', value: '黑衣剑士' }],
|
||||
references: [],
|
||||
},
|
||||
}),
|
||||
canvasSize: { width: 960, height: 720 },
|
||||
viewport: { x: 0, y: 0, scale: 1 },
|
||||
}),
|
||||
).toMatchObject({
|
||||
mode: 'character',
|
||||
prompt: '黑衣剑士',
|
||||
generatedLayerId: 'layer-source',
|
||||
});
|
||||
});
|
||||
|
||||
it('normalizes legacy nanobanana model aliases in quick edit drafts', () => {
|
||||
expect(
|
||||
createQuickEditPanelDraft(
|
||||
|
||||
@@ -649,9 +649,55 @@ function resolveGeneratedSourceDialogMode({
|
||||
if (sourceLayer.assetKind === 'character-animation') {
|
||||
return 'character-animation';
|
||||
}
|
||||
if (sourceLayer.sourceType === 'generated') {
|
||||
return 'generate';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function buildLayerGenerationPlaceholder(sourceLayer: CanvasLayer) {
|
||||
return {
|
||||
x: sourceLayer.x,
|
||||
y: sourceLayer.y,
|
||||
width: sourceLayer.width,
|
||||
height: sourceLayer.height,
|
||||
originalWidth: sourceLayer.originalWidth,
|
||||
originalHeight: sourceLayer.originalHeight,
|
||||
};
|
||||
}
|
||||
|
||||
export function createLayerGenerationDialogDraft({
|
||||
sourceLayer,
|
||||
canvasSize,
|
||||
viewport,
|
||||
sourceDialog,
|
||||
sourceAnimationLayer,
|
||||
}: Omit<SourceGenerationDialogDraftContext, 'mode'>): Omit<
|
||||
CanvasGenerationDialogState,
|
||||
'id'
|
||||
> | null {
|
||||
const draft = createSameSourceGenerationDialogDraft({
|
||||
sourceLayer,
|
||||
canvasSize,
|
||||
viewport,
|
||||
sourceDialog,
|
||||
sourceAnimationLayer,
|
||||
mode: 'redraw',
|
||||
});
|
||||
if (!draft) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
...draft,
|
||||
prompt: draft.prompt || sourceLayer.prompt?.trim() || '',
|
||||
imageModel: sourceDialog?.imageModel ?? sourceLayer.model ?? draft.imageModel,
|
||||
status: 'idle',
|
||||
composerOpen: true,
|
||||
generatedLayerId: sourceLayer.id,
|
||||
placeholder: buildLayerGenerationPlaceholder(sourceLayer),
|
||||
};
|
||||
}
|
||||
|
||||
function getSourceDraftPrompt({
|
||||
sourceLayer,
|
||||
mode,
|
||||
|
||||
@@ -70,6 +70,7 @@ type UseImageCanvasStageInteractionsOptions = {
|
||||
pickIconSpecFromLayer: (layer: CanvasLayer) => void;
|
||||
pickUiDesignSpecFromLayer: (layer: CanvasLayer) => void;
|
||||
pickPublicationReferenceFromLayer: (layer: CanvasLayer) => void;
|
||||
openLayerGenerationDialog?: (layer: CanvasLayer) => boolean;
|
||||
activateCanvasGenerationDialog: (
|
||||
dialog: CanvasGenerationDialogState,
|
||||
) => void;
|
||||
@@ -120,6 +121,7 @@ export function useImageCanvasStageInteractions({
|
||||
pickIconSpecFromLayer,
|
||||
pickUiDesignSpecFromLayer,
|
||||
pickPublicationReferenceFromLayer,
|
||||
openLayerGenerationDialog,
|
||||
activateCanvasGenerationDialog,
|
||||
updateCanvasGenerationDialogById,
|
||||
moveViewportFromMinimapPointer,
|
||||
@@ -389,9 +391,11 @@ export function useImageCanvasStageInteractions({
|
||||
}
|
||||
setSelectedLayerId(layer.id);
|
||||
setSelectedLayerIds([layer.id]);
|
||||
setGenerateDialog((currentDialog) =>
|
||||
updateGenerateDialogForLayerClick(currentDialog),
|
||||
);
|
||||
if (!openLayerGenerationDialog?.(layer)) {
|
||||
setGenerateDialog((currentDialog) =>
|
||||
updateGenerateDialogForLayerClick(currentDialog),
|
||||
);
|
||||
}
|
||||
onCloseImageContextMenu();
|
||||
},
|
||||
[
|
||||
@@ -402,6 +406,7 @@ export function useImageCanvasStageInteractions({
|
||||
isPickingIconSpecFromCanvas,
|
||||
isPickingPublicationReferenceFromCanvas,
|
||||
onCloseImageContextMenu,
|
||||
openLayerGenerationDialog,
|
||||
setGenerateDialog,
|
||||
setSelectedLayerId,
|
||||
setSelectedLayerIds,
|
||||
|
||||
Reference in New Issue
Block a user