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; generateDialog: GenerateDialogState | null; setGenerateDialog: Dispatch>; openCanvasGenerationDialog: ( dialog: Omit, ) => 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>; setActiveSidebarPanel: Dispatch>; setMetadataLayer: Dispatch>; setImageContextMenu: Dispatch>; }; 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(null); const [characterAnimationPanel, setCharacterAnimationPanel] = useState(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, ], ); }