新增 useImageCanvasGenerationSubmissionWorkflow 承载生成提交和结果落图副作用 补充生成提交流水线 hook 单测 精简 useImageCanvasGenerationWorkflow 的提交编排逻辑 更新 TRACKING.md 记录第四十三执行批次验证
500 lines
15 KiB
TypeScript
500 lines
15 KiB
TypeScript
import {
|
|
type Dispatch,
|
|
type MutableRefObject,
|
|
type SetStateAction,
|
|
useCallback,
|
|
useMemo,
|
|
useState,
|
|
} from 'react';
|
|
|
|
import type {
|
|
CanvasGenerationDialogState,
|
|
CanvasLayer,
|
|
CanvasTool,
|
|
CanvasViewport,
|
|
CharacterAnimationPanelState,
|
|
GenerateDialogState,
|
|
ImageContextMenuState,
|
|
QuickEditPanelState,
|
|
SidebarPanel,
|
|
SpecFormValues,
|
|
SpecGenerationType,
|
|
} from './ImageCanvasEditorTypes';
|
|
import {
|
|
appendCharacterReference,
|
|
appendIconDescriptionToDialog,
|
|
assignCharacterSpecReference,
|
|
assignIconSpecReference,
|
|
closeGenerateComposerDialog,
|
|
createCharacterAnimationPanelDraft,
|
|
createCharacterGenerationDialogDraft,
|
|
createEditDialogDraft,
|
|
createGenerateDialogDraft,
|
|
createIconGenerationDialogDraft,
|
|
createQuickEditPanelDraft,
|
|
createSpecDialogDraft,
|
|
hideGeneratedLayerComposerAfterBlur,
|
|
updateCharacterAnimationDurationPanel,
|
|
updateIconDescriptionInDialog,
|
|
updateSpecFormDialogValue,
|
|
} from './ImageCanvasGenerationDialogModel';
|
|
import {
|
|
buildQuickEditModelOptions,
|
|
buildQuickEditSizeOptions,
|
|
calculateCharacterAnimationPrice,
|
|
DEFAULT_ICON_DESCRIPTIONS,
|
|
DEFAULT_IMAGE_MODEL,
|
|
} from './ImageCanvasGenerationModel';
|
|
import { useImageCanvasGenerationSubmissionWorkflow } from './useImageCanvasGenerationSubmissionWorkflow';
|
|
|
|
type CanvasSize = { width: number; height: number };
|
|
|
|
type CanvasGenerationDialogUpdater = (
|
|
dialog: CanvasGenerationDialogState,
|
|
) => CanvasGenerationDialogState | null;
|
|
|
|
type GenerationWorkflowOptions = {
|
|
layers: CanvasLayer[];
|
|
canvasSize: CanvasSize;
|
|
viewport: CanvasViewport;
|
|
layerCounterRef: MutableRefObject<number>;
|
|
generateDialog: GenerateDialogState | null;
|
|
setGenerateDialog: Dispatch<SetStateAction<GenerateDialogState | null>>;
|
|
openCanvasGenerationDialog: (
|
|
dialog: Omit<CanvasGenerationDialogState, 'id'>,
|
|
) => void;
|
|
updateCanvasGenerationDialogById: (
|
|
dialogId: string,
|
|
updater: CanvasGenerationDialogUpdater,
|
|
) => void;
|
|
removeCanvasGenerationDialogById: (dialogId: string) => void;
|
|
removeCanvasGenerationDialogsByLayerId: (targetLayerId: string) => void;
|
|
getGeneratingDialogPlaceholder: (
|
|
dialog: GenerateDialogState,
|
|
) => GenerateDialogState['placeholder'];
|
|
appendCanvasLayersWithResources: (nextLayers: CanvasLayer[]) => void;
|
|
selectSingleLayer: (layerId: string | null) => void;
|
|
fitLayers: (targetLayers?: CanvasLayer[]) => void;
|
|
setActiveTool: Dispatch<SetStateAction<CanvasTool>>;
|
|
setActiveSidebarPanel: Dispatch<SetStateAction<SidebarPanel | null>>;
|
|
setMetadataLayer: Dispatch<SetStateAction<CanvasLayer | null>>;
|
|
setImageContextMenu: Dispatch<SetStateAction<ImageContextMenuState | null>>;
|
|
};
|
|
|
|
export function useImageCanvasGenerationWorkflow({
|
|
layers,
|
|
canvasSize,
|
|
viewport,
|
|
layerCounterRef,
|
|
generateDialog,
|
|
setGenerateDialog,
|
|
openCanvasGenerationDialog,
|
|
updateCanvasGenerationDialogById,
|
|
removeCanvasGenerationDialogById,
|
|
removeCanvasGenerationDialogsByLayerId,
|
|
getGeneratingDialogPlaceholder,
|
|
appendCanvasLayersWithResources,
|
|
selectSingleLayer,
|
|
fitLayers,
|
|
setActiveTool,
|
|
setActiveSidebarPanel,
|
|
setMetadataLayer,
|
|
setImageContextMenu,
|
|
}: GenerationWorkflowOptions) {
|
|
const [isSpecMenuOpen, setIsSpecMenuOpen] = useState(false);
|
|
const [isCharacterSpecMenuOpen, setIsCharacterSpecMenuOpen] = useState(false);
|
|
const [isCharacterReferenceMenuOpen, setIsCharacterReferenceMenuOpen] =
|
|
useState(false);
|
|
const [
|
|
isPickingCharacterSpecFromCanvas,
|
|
setIsPickingCharacterSpecFromCanvas,
|
|
] = useState(false);
|
|
const [
|
|
isPickingCharacterReferenceFromCanvas,
|
|
setIsPickingCharacterReferenceFromCanvas,
|
|
] = useState(false);
|
|
const [isIconSpecMenuOpen, setIsIconSpecMenuOpen] = useState(false);
|
|
const [isPickingIconSpecFromCanvas, setIsPickingIconSpecFromCanvas] =
|
|
useState(false);
|
|
const [quickEditPanel, setQuickEditPanel] =
|
|
useState<QuickEditPanelState | null>(null);
|
|
const [characterAnimationPanel, setCharacterAnimationPanel] =
|
|
useState<CharacterAnimationPanelState | null>(null);
|
|
const [lastImageModel, setLastImageModel] = useState(DEFAULT_IMAGE_MODEL);
|
|
|
|
const quickEditSourceLayer = quickEditPanel
|
|
? (layers.find((layer) => layer.id === quickEditPanel.sourceLayerId) ??
|
|
null)
|
|
: null;
|
|
const characterAnimationSourceLayer = characterAnimationPanel
|
|
? (layers.find(
|
|
(layer) => layer.id === characterAnimationPanel.sourceLayerId,
|
|
) ?? null)
|
|
: null;
|
|
const quickEditSizeOptions = useMemo(
|
|
() =>
|
|
quickEditPanel ? buildQuickEditSizeOptions(quickEditPanel.size) : [],
|
|
[quickEditPanel],
|
|
);
|
|
const quickEditModelOptions = useMemo(
|
|
() =>
|
|
quickEditPanel ? buildQuickEditModelOptions(quickEditPanel.model) : [],
|
|
[quickEditPanel],
|
|
);
|
|
const characterAnimationPrice = characterAnimationPanel
|
|
? calculateCharacterAnimationPrice(
|
|
characterAnimationPanel.resolution,
|
|
characterAnimationPanel.durationSeconds,
|
|
)
|
|
: 0;
|
|
const iconDescriptionValues =
|
|
generateDialog?.mode === 'icon'
|
|
? (generateDialog.iconDescriptions ?? DEFAULT_ICON_DESCRIPTIONS)
|
|
: DEFAULT_ICON_DESCRIPTIONS;
|
|
|
|
const openGenerateDialog = useCallback(() => {
|
|
openCanvasGenerationDialog(
|
|
createGenerateDialogDraft({ canvasSize, viewport }),
|
|
);
|
|
setActiveTool('generate');
|
|
selectSingleLayer(null);
|
|
setQuickEditPanel(null);
|
|
}, [
|
|
canvasSize,
|
|
openCanvasGenerationDialog,
|
|
selectSingleLayer,
|
|
setActiveTool,
|
|
viewport,
|
|
]);
|
|
|
|
const openSpecDialog = useCallback(
|
|
(specType: SpecGenerationType) => {
|
|
openCanvasGenerationDialog(
|
|
createSpecDialogDraft({ canvasSize, viewport, specType }),
|
|
);
|
|
setIsSpecMenuOpen(false);
|
|
setActiveTool('generate');
|
|
selectSingleLayer(null);
|
|
setQuickEditPanel(null);
|
|
},
|
|
[
|
|
canvasSize,
|
|
openCanvasGenerationDialog,
|
|
selectSingleLayer,
|
|
setActiveTool,
|
|
viewport,
|
|
],
|
|
);
|
|
|
|
const openCharacterAnimationPanel = useCallback(
|
|
(layer: CanvasLayer) => {
|
|
const nextPanel = createCharacterAnimationPanelDraft(layer);
|
|
if (!nextPanel) {
|
|
return;
|
|
}
|
|
setImageContextMenu(null);
|
|
setQuickEditPanel(null);
|
|
setCharacterAnimationPanel(nextPanel);
|
|
selectSingleLayer(layer.id);
|
|
},
|
|
[selectSingleLayer, setImageContextMenu],
|
|
);
|
|
|
|
const openCharacterGenerationDialog = useCallback(() => {
|
|
setIsSpecMenuOpen(false);
|
|
setIsCharacterReferenceMenuOpen(false);
|
|
setIsPickingCharacterSpecFromCanvas(false);
|
|
setIsPickingCharacterReferenceFromCanvas(false);
|
|
openCanvasGenerationDialog(
|
|
createCharacterGenerationDialogDraft({
|
|
canvasSize,
|
|
viewport,
|
|
imageModel: lastImageModel,
|
|
}),
|
|
);
|
|
setActiveTool('character');
|
|
selectSingleLayer(null);
|
|
setQuickEditPanel(null);
|
|
}, [
|
|
canvasSize,
|
|
lastImageModel,
|
|
openCanvasGenerationDialog,
|
|
selectSingleLayer,
|
|
setActiveTool,
|
|
viewport,
|
|
]);
|
|
|
|
const openIconGenerationDialog = useCallback(() => {
|
|
setIsSpecMenuOpen(false);
|
|
setIsCharacterReferenceMenuOpen(false);
|
|
setIsPickingCharacterSpecFromCanvas(false);
|
|
setIsPickingCharacterReferenceFromCanvas(false);
|
|
setIsPickingIconSpecFromCanvas(false);
|
|
openCanvasGenerationDialog(
|
|
createIconGenerationDialogDraft({
|
|
canvasSize,
|
|
viewport,
|
|
imageModel: lastImageModel,
|
|
}),
|
|
);
|
|
setActiveTool('icon');
|
|
selectSingleLayer(null);
|
|
setQuickEditPanel(null);
|
|
setCharacterAnimationPanel(null);
|
|
}, [
|
|
canvasSize,
|
|
lastImageModel,
|
|
openCanvasGenerationDialog,
|
|
selectSingleLayer,
|
|
setActiveTool,
|
|
viewport,
|
|
]);
|
|
|
|
const openEditDialog = useCallback(
|
|
(sourceLayer: CanvasLayer) => {
|
|
setMetadataLayer(null);
|
|
setImageContextMenu(null);
|
|
setQuickEditPanel(null);
|
|
setGenerateDialog(createEditDialogDraft(sourceLayer));
|
|
setActiveTool('generate');
|
|
},
|
|
[setActiveTool, setGenerateDialog, setImageContextMenu, setMetadataLayer],
|
|
);
|
|
|
|
const openQuickEditPanel = useCallback(
|
|
(sourceLayer: CanvasLayer) => {
|
|
setImageContextMenu(null);
|
|
setMetadataLayer(null);
|
|
setGenerateDialog(null);
|
|
setCharacterAnimationPanel(null);
|
|
setQuickEditPanel(createQuickEditPanelDraft(sourceLayer));
|
|
selectSingleLayer(sourceLayer.id);
|
|
setActiveTool('generate');
|
|
},
|
|
[
|
|
selectSingleLayer,
|
|
setActiveTool,
|
|
setGenerateDialog,
|
|
setImageContextMenu,
|
|
setMetadataLayer,
|
|
],
|
|
);
|
|
|
|
const pickCharacterSpecFromLayer = useCallback(
|
|
(layer: CanvasLayer) => {
|
|
setGenerateDialog((currentDialog) =>
|
|
assignCharacterSpecReference(currentDialog, layer),
|
|
);
|
|
setIsPickingCharacterSpecFromCanvas(false);
|
|
setIsCharacterSpecMenuOpen(false);
|
|
setImageContextMenu(null);
|
|
},
|
|
[setGenerateDialog, setImageContextMenu],
|
|
);
|
|
|
|
const pickCharacterReferenceFromLayer = useCallback(
|
|
(layer: CanvasLayer) => {
|
|
setGenerateDialog((currentDialog) =>
|
|
appendCharacterReference(currentDialog, layer),
|
|
);
|
|
setIsPickingCharacterReferenceFromCanvas(false);
|
|
setImageContextMenu(null);
|
|
},
|
|
[setGenerateDialog, setImageContextMenu],
|
|
);
|
|
|
|
const pickIconSpecFromLayer = useCallback(
|
|
(layer: CanvasLayer) => {
|
|
setGenerateDialog((currentDialog) =>
|
|
assignIconSpecReference(currentDialog, layer),
|
|
);
|
|
if (layer.assetKind !== 'icon-spec') {
|
|
return;
|
|
}
|
|
setIsPickingIconSpecFromCanvas(false);
|
|
setIsIconSpecMenuOpen(false);
|
|
setImageContextMenu(null);
|
|
},
|
|
[setGenerateDialog, setImageContextMenu],
|
|
);
|
|
|
|
const updateIconDescription = useCallback(
|
|
(index: number, value: string) => {
|
|
setGenerateDialog((currentDialog) =>
|
|
updateIconDescriptionInDialog(currentDialog, index, value),
|
|
);
|
|
},
|
|
[setGenerateDialog],
|
|
);
|
|
|
|
const addIconDescription = useCallback(() => {
|
|
setGenerateDialog(appendIconDescriptionToDialog);
|
|
}, [setGenerateDialog]);
|
|
|
|
const generationSubmissionWorkflow = useImageCanvasGenerationSubmissionWorkflow({
|
|
layers,
|
|
canvasSize,
|
|
viewport,
|
|
layerCounterRef,
|
|
quickEditPanel,
|
|
quickEditSourceLayer,
|
|
setQuickEditPanel,
|
|
characterAnimationPanel,
|
|
characterAnimationSourceLayer,
|
|
setCharacterAnimationPanel,
|
|
setGenerateDialog,
|
|
updateCanvasGenerationDialogById,
|
|
removeCanvasGenerationDialogById,
|
|
getGeneratingDialogPlaceholder,
|
|
appendCanvasLayersWithResources,
|
|
selectSingleLayer,
|
|
fitLayers,
|
|
setActiveTool,
|
|
setActiveSidebarPanel,
|
|
rememberImageModel: setLastImageModel,
|
|
});
|
|
const {
|
|
submitCharacterAnimation,
|
|
submitIconSpritesheetGeneration,
|
|
submitImageGeneration,
|
|
submitQuickEdit,
|
|
} = generationSubmissionWorkflow;
|
|
|
|
const updateSpecFormValue = useCallback(
|
|
(key: keyof SpecFormValues, value: string) => {
|
|
setGenerateDialog((currentDialog) =>
|
|
updateSpecFormDialogValue(currentDialog, key, value),
|
|
);
|
|
},
|
|
[setGenerateDialog],
|
|
);
|
|
|
|
const updateCharacterAnimationDuration = useCallback(
|
|
(frameCountValue: string) => {
|
|
setCharacterAnimationPanel((currentPanel) =>
|
|
updateCharacterAnimationDurationPanel(currentPanel, frameCountValue),
|
|
);
|
|
},
|
|
[],
|
|
);
|
|
|
|
const hideGeneratedLayerPanelAfterBlur = useCallback(() => {
|
|
setGenerateDialog((currentDialog) =>
|
|
hideGeneratedLayerComposerAfterBlur(currentDialog),
|
|
);
|
|
}, [setGenerateDialog]);
|
|
|
|
const closeGenerateComposer = useCallback(() => {
|
|
setGenerateDialog(closeGenerateComposerDialog);
|
|
setActiveTool('select');
|
|
}, [setActiveTool, setGenerateDialog]);
|
|
|
|
const clearDeletedLayerGenerationState = useCallback(
|
|
(targetLayerId: string) => {
|
|
setQuickEditPanel((currentPanel) =>
|
|
currentPanel?.sourceLayerId === targetLayerId ? null : currentPanel,
|
|
);
|
|
setCharacterAnimationPanel((currentPanel) =>
|
|
currentPanel?.sourceLayerId === targetLayerId ? null : currentPanel,
|
|
);
|
|
setGenerateDialog((currentDialog) =>
|
|
currentDialog?.mode === 'edit' &&
|
|
currentDialog.sourceLayerId === targetLayerId
|
|
? null
|
|
: currentDialog,
|
|
);
|
|
removeCanvasGenerationDialogsByLayerId(targetLayerId);
|
|
},
|
|
[removeCanvasGenerationDialogsByLayerId, setGenerateDialog],
|
|
);
|
|
|
|
return useMemo(
|
|
() => ({
|
|
quickEditPanel,
|
|
setQuickEditPanel,
|
|
quickEditSourceLayer,
|
|
quickEditSizeOptions,
|
|
quickEditModelOptions,
|
|
characterAnimationPanel,
|
|
setCharacterAnimationPanel,
|
|
characterAnimationSourceLayer,
|
|
characterAnimationPrice,
|
|
iconDescriptionValues,
|
|
isSpecMenuOpen,
|
|
setIsSpecMenuOpen,
|
|
isCharacterSpecMenuOpen,
|
|
setIsCharacterSpecMenuOpen,
|
|
isCharacterReferenceMenuOpen,
|
|
setIsCharacterReferenceMenuOpen,
|
|
isPickingCharacterSpecFromCanvas,
|
|
setIsPickingCharacterSpecFromCanvas,
|
|
isPickingCharacterReferenceFromCanvas,
|
|
setIsPickingCharacterReferenceFromCanvas,
|
|
isIconSpecMenuOpen,
|
|
setIsIconSpecMenuOpen,
|
|
isPickingIconSpecFromCanvas,
|
|
setIsPickingIconSpecFromCanvas,
|
|
openGenerateDialog,
|
|
openSpecDialog,
|
|
openCharacterAnimationPanel,
|
|
openCharacterGenerationDialog,
|
|
openIconGenerationDialog,
|
|
openEditDialog,
|
|
openQuickEditPanel,
|
|
pickCharacterSpecFromLayer,
|
|
pickCharacterReferenceFromLayer,
|
|
pickIconSpecFromLayer,
|
|
submitIconSpritesheetGeneration,
|
|
submitQuickEdit,
|
|
submitImageGeneration,
|
|
updateSpecFormValue,
|
|
updateIconDescription,
|
|
addIconDescription,
|
|
updateCharacterAnimationDuration,
|
|
rememberImageModel: setLastImageModel,
|
|
submitCharacterAnimation,
|
|
hideGeneratedLayerPanelAfterBlur,
|
|
closeGenerateComposer,
|
|
clearDeletedLayerGenerationState,
|
|
}),
|
|
[
|
|
addIconDescription,
|
|
characterAnimationPanel,
|
|
characterAnimationPrice,
|
|
characterAnimationSourceLayer,
|
|
clearDeletedLayerGenerationState,
|
|
closeGenerateComposer,
|
|
hideGeneratedLayerPanelAfterBlur,
|
|
iconDescriptionValues,
|
|
isCharacterReferenceMenuOpen,
|
|
isCharacterSpecMenuOpen,
|
|
isIconSpecMenuOpen,
|
|
isPickingCharacterReferenceFromCanvas,
|
|
isPickingCharacterSpecFromCanvas,
|
|
isPickingIconSpecFromCanvas,
|
|
isSpecMenuOpen,
|
|
openCharacterAnimationPanel,
|
|
openCharacterGenerationDialog,
|
|
openEditDialog,
|
|
openGenerateDialog,
|
|
openIconGenerationDialog,
|
|
openQuickEditPanel,
|
|
openSpecDialog,
|
|
pickCharacterReferenceFromLayer,
|
|
pickCharacterSpecFromLayer,
|
|
pickIconSpecFromLayer,
|
|
quickEditModelOptions,
|
|
quickEditPanel,
|
|
quickEditSizeOptions,
|
|
quickEditSourceLayer,
|
|
submitCharacterAnimation,
|
|
submitIconSpritesheetGeneration,
|
|
submitImageGeneration,
|
|
submitQuickEdit,
|
|
updateCharacterAnimationDuration,
|
|
updateIconDescription,
|
|
updateSpecFormValue,
|
|
],
|
|
);
|
|
}
|