Files
Genarrative/src/components/image-editor/ImageCanvasEditorView.tsx
T
kdletters 946308b75e 保存图片画布生成器快照
将生成器对话框作为画布布局项序列化和恢复

生成成功后保留生成器快照并锚定到成品图层

图片类生成结果同步写入账号素材库

补充生成器持久化测试和浏览器回归相关文档
2026-06-17 23:57:25 +08:00

886 lines
26 KiB
TypeScript

import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { createEditorAsset } from '../../services/image-editor/editorProjectClient';
import { useAuthUi } from '../auth/AuthUiContext';
import { ImageCanvasEditorShellView } from './ImageCanvasEditorShellView';
import { resolveContextMenuPosition } from './ImageCanvasEditorModel';
import { isCanvasGenerationComposerVisible } from './ImageCanvasOverlayModel';
import type {
AssetPointerDragState,
CanvasGenerationDialogState,
CanvasContextMenuState,
CanvasLayer,
CanvasTool,
CanvasViewport,
ImageContextMenuState,
} from './ImageCanvasEditorTypes';
import { useCanvasHistory } from './useCanvasHistory';
import { useCanvasGenerationDialogs } from './useCanvasGenerationDialogs';
import { useImageCanvasAssetLibrary } from './useImageCanvasAssetLibrary';
import {
useImageCanvasAssetCanvasBridge,
useImageCanvasAssetLayerCleanup,
} from './useImageCanvasAssetCanvasBridge';
import { useImageCanvasAssetExportWorkflow } from './useImageCanvasAssetExportWorkflow';
import { useImageCanvasEditorChrome } from './useImageCanvasEditorChrome';
import { useImageCanvasGenerationSurface } from './useImageCanvasGenerationSurface';
import { useImageCanvasKeyboardShortcuts } from './useImageCanvasKeyboardShortcuts';
import { useImageCanvasLayerCommands } from './useImageCanvasLayerCommands';
import { useImageCanvasProjectPersistence } from './useImageCanvasProjectPersistence';
import { useImageCanvasStageController } from './useImageCanvasStageController';
import { useImageCanvasStageInteractions } from './useImageCanvasStageInteractions';
import { useImageCanvasUploadWorkflow } from './useImageCanvasUploadWorkflow';
import {
DEFAULT_IMAGE_CANVAS_VIEWPORT,
useImageCanvasViewportControls,
} from './useImageCanvasViewportControls';
export function ImageCanvasEditorView() {
const authUi = useAuthUi();
const editorRootRef = useRef<HTMLElement | null>(null);
const canvasViewportRef = useRef<HTMLDivElement | null>(null);
const assetListRef = useRef<HTMLDivElement | null>(null);
const assetPointerDragRef = useRef<AssetPointerDragState | null>(null);
const authUiRef = useRef(authUi);
const hasRequestedLoginRef = useRef(false);
const layerCounterRef = useRef(0);
const layersRef = useRef<CanvasLayer[]>([]);
const canvasGenerationDialogsRef = useRef<CanvasGenerationDialogState[]>([]);
const viewportRef = useRef<CanvasViewport>(DEFAULT_IMAGE_CANVAS_VIEWPORT);
const captureCanvasHistoryRef = useRef<() => void>(() => {});
const resetCanvasInteractionStateRef = useRef<() => void>(() => {});
const specToolWrapRef = useRef<HTMLSpanElement | null>(null);
const characterSpecButtonRef = useRef<HTMLButtonElement | null>(null);
const characterReferenceButtonRef = useRef<HTMLButtonElement | null>(null);
const generationReferenceButtonRef = useRef<HTMLButtonElement | null>(null);
const iconSpecButtonRef = useRef<HTMLButtonElement | null>(null);
const selectedLayerIdRef = useRef<string | null>(null);
const selectedLayerIdsRef = useRef<string[]>([]);
const deleteLayerByIdRef = useRef<(targetLayerId: string | null) => void>(
() => {},
);
const suppressAssetClickRef = useRef(false);
const [layers, setLayers] = useState<CanvasLayer[]>([]);
const [selectedLayerId, setSelectedLayerId] = useState<string | null>(null);
const [selectedLayerIds, setSelectedLayerIds] = useState<string[]>([]);
const [hoveredLayerId, setHoveredLayerId] = useState<string | null>(null);
const [metadataLayer, setMetadataLayer] = useState<CanvasLayer | null>(null);
const [imageContextMenu, setImageContextMenu] =
useState<ImageContextMenuState | null>(null);
const [contextMenu, setContextMenu] = useState<CanvasContextMenuState | null>(
null,
);
const [uploadDropTarget, setUploadDropTarget] = useState<
'canvas' | 'assets' | null
>(null);
const captureViewportHistory = useCallback(() => {
captureCanvasHistoryRef.current();
}, []);
const {
viewport,
setViewport,
canvasSize,
minimapModel,
updateScaleFromCenter,
fitLayers,
resolveCanvasPoint,
getCanvasDropPoint,
getCanvasPointFromClient,
moveViewportFromMinimapPointer,
updateViewportFromMinimapDrag,
} = useImageCanvasViewportControls({
canvasViewportRef,
layers,
captureCanvasHistory: captureViewportHistory,
});
selectedLayerIdRef.current = selectedLayerId;
selectedLayerIdsRef.current = selectedLayerIds;
layersRef.current = layers;
viewportRef.current = viewport;
authUiRef.current = authUi;
const openEditorLoginModal = useCallback(
(postLoginAction?: (() => void) | null) => {
authUiRef.current?.openLoginModal(postLoginAction);
},
[],
);
useEffect(() => {
if (!authUi || authUi.user || authUi.canAccessProtectedData) {
hasRequestedLoginRef.current = false;
return;
}
if (hasRequestedLoginRef.current) {
return;
}
hasRequestedLoginRef.current = true;
authUi.openLoginModal(() => {
window.location.reload();
});
}, [authUi]);
const {
projectTitle,
setProjectTitle,
projectRenameValue,
setProjectRenameValue,
isRenamingProject,
isProjectRenameSaving,
projectRenameError,
activeSidebarPanel,
setActiveSidebarPanel,
activeTool,
setActiveTool,
isZoomMenuOpen,
isBackgroundSettingsOpen,
isMinimapOpen,
canvasBackgroundColor,
canvasBackgroundHexValue,
startProjectRename,
cancelProjectRename,
submitProjectRename,
resetProjectRenameError,
applyCanvasBackgroundColor,
handleCanvasBackgroundHexChange,
closeEditorChromePanels,
toggleSidebarPanel,
toggleZoomMenu,
closeZoomMenu,
toggleBackgroundSettings,
toggleMinimap,
} = useImageCanvasEditorChrome({ openEditorLoginModal });
const removeCanvasLayersLinkedToAssets = useImageCanvasAssetLayerCleanup({
layers,
setLayers,
setSelectedLayerId,
setSelectedLayerIds,
});
const {
assetFolders,
setAssetFolders,
assets,
setAssets,
groupedAssets,
allSelectableAssetsSelected,
renamingAsset,
setRenamingAsset,
renamingFolder,
setRenamingFolder,
creatingFolder,
setCreatingFolder,
newFolderName,
setNewFolderName,
activeUploadFolderId,
setActiveUploadFolderId,
isAssetSelectionMode,
setIsAssetSelectionMode,
selectedAssetIds,
setSelectedAssetIds,
assetMarquee,
assetPointerDrag,
setAssetPointerDrag,
assetMoveDropFolderId,
pinnedAssetMoveFolderId,
resolveAssetFolderId,
updateAssetMoveDropFolder,
startRenamingAsset,
commitAssetRename,
toggleAssetFolder,
commitNewAssetFolder,
deleteUploadedAsset,
startRenamingFolder,
commitFolderRename,
deleteAssetFolder,
toggleAssetSelected,
toggleAllAssetsSelected,
deleteSelectedAssets,
moveAssetToFolder,
closeAssetSelectionMode,
handleAssetMarqueePointerDown,
handleAssetMarqueePointerMove,
handleAssetMarqueePointerUp,
} = useImageCanvasAssetLibrary({
assetListRef,
canAccessProtectedData: authUi ? authUi.canAccessProtectedData : true,
openEditorLoginModal,
onDeleteAssets: removeCanvasLayersLinkedToAssets,
});
const handleActivateCanvasGenerationDialog = useCallback(() => {
setSelectedLayerId(null);
setSelectedLayerIds([]);
setImageContextMenu(null);
}, []);
const {
generateDialog,
setGenerateDialog,
generateDialogRef,
inactiveGenerateDialogs,
setInactiveGenerateDialogs,
inactiveGenerateDialogsRef,
activeCanvasGenerationDialog,
canvasGenerationDialogs,
openCanvasGenerationDialog,
updateCanvasGenerationDialogById,
activateCanvasGenerationDialog,
restoreCanvasGenerationDialogs,
removeCanvasGenerationDialogsByLayerId,
getGeneratingDialogPlaceholder,
} = useCanvasGenerationDialogs({
onActivate: handleActivateCanvasGenerationDialog,
});
canvasGenerationDialogsRef.current = canvasGenerationDialogs;
const canvasHistoryRefs = useMemo(
() => ({
layersRef,
viewportRef,
generateDialogRef,
inactiveGenerateDialogsRef,
selectedLayerIdRef,
selectedLayerIdsRef,
}),
[],
);
const canvasHistorySetters = useMemo(
() => ({
setLayers,
setViewport,
setGenerateDialog,
setInactiveGenerateDialogs,
setSelectedLayerId,
setSelectedLayerIds,
}),
[],
);
const canvasHistoryResetters = useMemo(
() => ({
setHoveredLayerId,
setMetadataLayer,
resetCanvasInteractionState: () =>
resetCanvasInteractionStateRef.current(),
}),
[],
);
const {
canUndo,
canRedo,
captureCanvasHistory,
undoCanvasChange,
redoCanvasChange,
} = useCanvasHistory({
refs: canvasHistoryRefs,
setters: canvasHistorySetters,
resetters: canvasHistoryResetters,
});
captureCanvasHistoryRef.current = captureCanvasHistory;
const selectSingleLayer = useCallback((layerId: string | null) => {
setSelectedLayerId(layerId);
setSelectedLayerIds(layerId ? [layerId] : []);
if (layerId) {
setGenerateDialog((currentDialog) =>
isCanvasGenerationComposerVisible(currentDialog)
? {
...currentDialog,
composerOpen: currentDialog.generatedLayerId === layerId,
}
: currentDialog,
);
}
}, []);
const persistGeneratedAsset = useCallback(
(layer: CanvasLayer) => {
if (authUiRef.current && !authUiRef.current.canAccessProtectedData) {
openEditorLoginModal();
return;
}
createEditorAsset({
folderId: activeUploadFolderId,
label: layer.title,
imageSrc: layer.src,
objectKey: layer.objectKey,
assetObjectId: layer.assetObjectId,
width: layer.originalWidth,
height: layer.originalHeight,
sourceType: 'generated',
prompt: layer.prompt,
actualPrompt: layer.actualPrompt,
model: layer.model,
provider: layer.provider,
taskId: layer.taskId,
})
.then((asset) => {
setAssets((currentAssets) => [
...currentAssets.filter(
(currentAsset) => currentAsset.id !== asset.assetId,
),
{
id: asset.assetId,
label: asset.label,
src: asset.imageSrc,
width: asset.width,
height: asset.height,
folderId: asset.folderId,
sourceKind: 'uploaded',
sourceType: asset.sourceType,
persisted: true,
prompt: asset.prompt ?? undefined,
actualPrompt: asset.actualPrompt ?? undefined,
model: asset.model ?? undefined,
provider: asset.provider ?? undefined,
taskId: asset.taskId ?? undefined,
objectKey: asset.objectKey ?? undefined,
assetObjectId: asset.assetObjectId ?? undefined,
},
]);
setAssetFolders((currentFolders) =>
currentFolders.map((folder) =>
folder.id === asset.folderId
? {
...folder,
collapsed: false,
}
: folder,
),
);
setLayers((currentLayers) =>
currentLayers.map((currentLayer) =>
currentLayer.id === layer.id
? {
...currentLayer,
sourceAssetId: asset.assetId,
objectKey: asset.objectKey ?? currentLayer.objectKey,
assetObjectId:
asset.assetObjectId ?? currentLayer.assetObjectId,
}
: currentLayer,
),
);
})
.catch((error: unknown) => {
if (
error instanceof Error &&
'status' in error &&
(error.status === 401 || error.status === 403)
) {
openEditorLoginModal();
}
});
},
[activeUploadFolderId, openEditorLoginModal, setAssetFolders, setAssets],
);
const projectPersistenceRefs = useMemo(
() => ({
layersRef,
viewportRef,
canvasGenerationDialogsRef,
}),
[],
);
const projectPersistenceSetters = useMemo(
() => ({
setProjectTitle,
setProjectRenameValue,
setViewport,
setLayers,
selectSingleLayer,
setLayerCounter: (value: number) => {
layerCounterRef.current = value;
},
restoreCanvasGenerationDialogs,
}),
[restoreCanvasGenerationDialogs, selectSingleLayer],
);
const { projectId, appendCanvasLayersWithResources } =
useImageCanvasProjectPersistence({
refs: projectPersistenceRefs,
setters: projectPersistenceSetters,
layers,
canvasGenerationDialogs,
viewport,
canAccessProtectedData: authUi ? authUi.canAccessProtectedData : true,
openEditorLoginModal,
});
const {
assetExportStatus,
isExportingAssets,
exportCanvasAssets,
exportLayerImage,
} = useImageCanvasAssetExportWorkflow({
layers,
projectId,
projectTitle,
});
const {
uploadInputRef,
setUploadTarget,
requestUpload,
handleUploadInputChange,
addUploadedFiles,
} = useImageCanvasUploadWorkflow({
canAccessProtectedData: authUi ? authUi.canAccessProtectedData : true,
openEditorLoginModal,
assetFolders,
activeUploadFolderId,
canvasSize,
viewport,
activeTool,
allocateUploadIndex: () => {
layerCounterRef.current += 1;
return layerCounterRef.current;
},
setAssetFolders,
setAssets,
setLayers,
setGenerateDialog,
appendCanvasLayersWithResources,
selectSingleLayer,
});
const generationSurface = useImageCanvasGenerationSurface({
layers,
canvasSize,
viewport,
setViewport,
layerCounterRef,
specToolWrapRef,
characterSpecButtonRef,
characterReferenceButtonRef,
generationReferenceButtonRef,
iconSpecButtonRef,
generateDialog,
setGenerateDialog,
activeCanvasGenerationDialog,
canvasGenerationDialogs,
openCanvasGenerationDialog,
updateCanvasGenerationDialogById,
removeCanvasGenerationDialogsByLayerId,
getGeneratingDialogPlaceholder,
appendCanvasLayersWithResources,
selectSingleLayer,
fitLayers,
setActiveTool,
setActiveSidebarPanel,
setMetadataLayer,
setImageContextMenu,
requestUpload,
persistGeneratedAsset,
});
const {
quickEditPanel,
setQuickEditPanel,
setIsSpecMenuOpen,
setIsGenerationReferenceMenuOpen,
setIsCharacterSpecMenuOpen,
setIsCharacterReferenceMenuOpen,
isPickingGenerationReferenceFromCanvas,
setIsPickingGenerationReferenceFromCanvas,
isPickingCharacterSpecFromCanvas,
setIsPickingCharacterSpecFromCanvas,
isPickingCharacterReferenceFromCanvas,
setIsPickingCharacterReferenceFromCanvas,
setIsIconSpecMenuOpen,
isPickingIconSpecFromCanvas,
setIsPickingIconSpecFromCanvas,
setIsUiDesignSpecMenuOpen,
isPickingUiDesignSpecFromCanvas,
setIsPickingUiDesignSpecFromCanvas,
openCharacterAnimationPanel,
openEditDialog,
openQuickEditPanel,
pickCharacterSpecFromLayer,
pickGenerationReferenceFromLayer,
pickCharacterReferenceFromLayer,
pickIconSpecFromLayer,
pickUiDesignSpecFromLayer,
hideGeneratedLayerPanelAfterBlur,
clearDeletedLayerGenerationState,
generationComposerStyle,
generationComposerNode,
switchGenerationTool,
} = generationSurface;
const {
selectedLayer,
selectedToolbarStyle,
imageContextMenuLayer,
contextShouldShowLayer,
contextShouldUnlockLayer,
clearCanvasFocus,
handleCanvasContextMenu,
handleLayerContextMenu,
} = useImageCanvasStageController({
layers,
selectedLayerId,
selectedLayerIds,
imageContextMenu,
setImageContextMenu,
contextMenu,
setContextMenu,
viewport,
canvasSize,
selectSingleLayer,
hideGeneratedLayerPanelAfterBlur,
getCanvasPointFromClient,
});
const {
canvasClipboard,
pasteCanvasClipboard,
copyContextLayers,
duplicateContextLayers,
moveContextLayers,
groupContextLayers,
ungroupContextLayers,
toggleContextLayerVisibility,
toggleContextLayerLock,
flipContextLayers,
deleteContextLayers,
exportContextLayer,
deleteLayerById,
deleteSelectedLayer,
groupSelectedLayers,
} = useImageCanvasLayerCommands({
layers,
contextMenu,
selectedLayerId,
selectedLayerIds,
setLayers,
setSelectedLayerId,
setSelectedLayerIds,
setHoveredLayerId,
setMetadataLayer,
setContextMenu,
setImageContextMenu,
setActiveTool,
captureCanvasHistory,
selectSingleLayer,
onDeleteLayerSideEffects: clearDeletedLayerGenerationState,
exportLayerImage,
});
const {
canvasMarquee,
isPanning,
snapGuide,
effectiveTool,
setIsSpacePanning,
setShiftPressed,
clearActiveInteraction,
handleCanvasPointerDown,
handleLayerPointerDown,
handleLayerClick,
handleGenerationFramePointerDown,
handleMinimapPointerDown,
handlePointerMove,
finishDrag,
} = useImageCanvasStageInteractions({
canvasViewportRef,
activeTool,
layers,
setLayers,
viewport,
setViewport,
selectedLayerIds,
setSelectedLayerId,
setSelectedLayerIds,
generateDialog,
setGenerateDialog,
isPickingCharacterSpecFromCanvas,
isPickingGenerationReferenceFromCanvas,
isPickingCharacterReferenceFromCanvas,
isPickingIconSpecFromCanvas,
isPickingUiDesignSpecFromCanvas,
clearCanvasFocus,
pickCharacterSpecFromLayer,
pickGenerationReferenceFromLayer,
pickCharacterReferenceFromLayer,
pickIconSpecFromLayer,
pickUiDesignSpecFromLayer,
activateCanvasGenerationDialog,
updateCanvasGenerationDialogById,
moveViewportFromMinimapPointer,
updateViewportFromMinimapDrag,
minimapScale: minimapModel?.scale ?? 1,
onCloseImageContextMenu: () => setImageContextMenu(null),
});
resetCanvasInteractionStateRef.current = clearActiveInteraction;
const deleteLayerByIdFromShortcut = useCallback(
(layerId: string | null) => deleteLayerByIdRef.current(layerId),
[],
);
useImageCanvasKeyboardShortcuts({
generateDialogRef,
selectedLayerIdRef,
redoCanvasChange,
undoCanvasChange,
deleteLayerById: deleteLayerByIdFromShortcut,
setActiveTool,
setGenerateDialog,
setImageContextMenu,
setContextMenu,
setQuickEditPanel,
closeEditorChromePanels,
setIsSpecMenuOpen,
setIsGenerationReferenceMenuOpen,
setIsPickingGenerationReferenceFromCanvas,
setIsCharacterSpecMenuOpen,
setIsCharacterReferenceMenuOpen,
setIsPickingCharacterSpecFromCanvas,
setIsPickingCharacterReferenceFromCanvas,
setIsIconSpecMenuOpen,
setIsPickingIconSpecFromCanvas,
setIsUiDesignSpecMenuOpen,
setIsPickingUiDesignSpecFromCanvas,
setIsSpacePanning,
setShiftPressed,
});
useEffect(() => {
const blockBrowserZoom = (event: WheelEvent) => {
const editorElement = editorRootRef.current;
if (
editorElement &&
event.target instanceof Node &&
editorElement.contains(event.target) &&
(event.ctrlKey || event.metaKey)
) {
event.preventDefault();
}
};
window.addEventListener('wheel', blockBrowserZoom, {
capture: true,
passive: false,
});
return () => {
window.removeEventListener('wheel', blockBrowserZoom, { capture: true });
};
}, []);
const {
addAssetLayer,
handleCanvasDragOver,
handleCanvasDragLeave,
handleCanvasDrop,
} = useImageCanvasAssetCanvasBridge({
assetPointerDragRef,
suppressAssetClickRef,
assets,
assetFolders,
layerCounterRef,
viewport,
canvasSize,
resolveAssetFolderId,
resolveCanvasPoint,
getCanvasDropPoint,
setAssetPointerDrag,
setActiveUploadFolderId,
setUploadDropTarget,
setHoveredLayerId,
updateAssetMoveDropFolder,
moveAssetToFolder,
captureCanvasHistory,
appendCanvasLayersWithResources,
selectSingleLayer,
addUploadedFiles,
});
deleteLayerByIdRef.current = deleteLayerById;
const switchTool = (tool: CanvasTool) => {
clearActiveInteraction();
if (tool === 'upload') {
requestUpload('asset');
return;
}
if (switchGenerationTool(tool)) {
return;
}
setActiveTool(tool);
};
const assetDragPreview = assetPointerDrag?.active
? {
x: assetPointerDrag.currentClientX,
y: assetPointerDrag.currentClientY,
label:
assets.find((asset) => asset.id === assetPointerDrag.assetId)
?.label ?? '素材',
}
: null;
const sidebarProps = {
activeSidebarPanel,
assetListRef,
assetPointerDragRef,
suppressAssetClickRef,
assets,
groupedAssets,
assetFolders,
layers,
selectedLayerId,
selectedLayerIds,
isAssetSelectionMode,
selectedAssetIds,
assetMoveDropFolderId,
pinnedAssetMoveFolderId,
creatingFolder,
newFolderName,
renamingFolder,
renamingAsset,
allSelectableAssetsSelected,
assetMarquee,
setIsAssetSelectionMode,
setCreatingFolder,
setNewFolderName,
setRenamingFolder,
setRenamingAsset,
setActiveUploadFolderId,
setUploadDropTarget,
setAssetPointerDrag,
setSelectedAssetIds,
setImageContextMenu,
setContextMenu,
onAssetMarqueePointerDown: handleAssetMarqueePointerDown,
onAssetMarqueePointerMove: handleAssetMarqueePointerMove,
onAssetMarqueePointerUp: handleAssetMarqueePointerUp,
updateAssetMoveDropFolder,
addUploadedFiles,
requestUpload,
moveAssetToFolder,
commitNewAssetFolder,
toggleAssetFolder,
startRenamingFolder,
commitFolderRename,
deleteAssetFolder,
startRenamingAsset,
commitAssetRename,
deleteUploadedAsset,
toggleAssetSelected,
addAssetLayer,
toggleAllAssetsSelected,
deleteSelectedAssets,
closeAssetSelectionMode,
groupSelectedLayers,
selectSingleLayer,
resolveContextMenuPosition,
getCanvasPointFromClient,
};
const topbarProps = {
projectId,
projectTitle,
projectRenameValue,
isRenamingProject,
isProjectRenameSaving,
projectRenameError,
layers,
assetExportStatus,
isExportingAssets,
setProjectRenameValue,
startProjectRename,
cancelProjectRename,
submitProjectRename,
resetProjectRenameError,
exportCanvasAssets,
};
const stageProps = {
canvasViewportRef,
specToolWrapRef,
isPanning,
effectiveTool,
canvasBackgroundColor,
canvasBackgroundHexValue,
viewport,
snapGuide,
layers,
selectedLayer,
selectedLayerIds,
hoveredLayerId,
canvasMarquee,
canvasGenerationDialogs,
generateDialog,
quickEditPanel,
generationComposerStyle,
selectedToolbarStyle,
uploadDropTarget,
contextMenu,
canvasClipboard,
imageContextMenu,
imageContextMenuLayer,
contextShouldShowLayer,
contextShouldUnlockLayer,
canUndo,
canRedo,
isZoomMenuOpen,
isBackgroundSettingsOpen,
activeSidebarPanel,
isMinimapOpen,
minimapModel,
onCanvasPointerDown: handleCanvasPointerDown,
onCanvasPointerMove: handlePointerMove,
onCanvasPointerUp: finishDrag,
onCanvasDragOver: handleCanvasDragOver,
onCanvasDragLeave: handleCanvasDragLeave,
onCanvasDrop: handleCanvasDrop,
onCanvasContextMenu: handleCanvasContextMenu,
onLayerPointerDown: handleLayerPointerDown,
onLayerClick: handleLayerClick,
onLayerContextMenu: handleLayerContextMenu,
onLayerMouseEnter: setHoveredLayerId,
onLayerMouseLeave: (layerId: string) =>
setHoveredLayerId((currentId) =>
currentId === layerId ? null : currentId,
),
onOpenLayerMetadata: (layer: CanvasLayer) => {
setMetadataLayer(layer);
selectSingleLayer(layer.id);
},
onGenerationFramePointerDown: handleGenerationFramePointerDown,
onActivateGenerationDialog: activateCanvasGenerationDialog,
onDeleteSelectedLayer: deleteSelectedLayer,
onOpenQuickEditPanel: openQuickEditPanel,
onOpenEditDialog: openEditDialog,
onOpenCharacterAnimationPanel: openCharacterAnimationPanel,
onPasteCanvasClipboard: pasteCanvasClipboard,
onCopyContextLayers: copyContextLayers,
onDuplicateContextLayers: duplicateContextLayers,
onMoveContextLayers: moveContextLayers,
onGroupContextLayers: groupContextLayers,
onUngroupContextLayers: ungroupContextLayers,
onToggleContextLayerVisibility: toggleContextLayerVisibility,
onToggleContextLayerLock: toggleContextLayerLock,
onFlipContextLayers: flipContextLayers,
onExportContextLayer: exportContextLayer,
onDeleteContextLayers: deleteContextLayers,
onDeleteLayerById: deleteLayerById,
onCloseContextMenu: () => setContextMenu(null),
onCloseImageContextMenu: () => setImageContextMenu(null),
onUpdateScaleFromCenter: updateScaleFromCenter,
onFitLayers: fitLayers,
onUndoCanvasChange: undoCanvasChange,
onRedoCanvasChange: redoCanvasChange,
onToggleZoomMenu: toggleZoomMenu,
onCloseZoomMenu: closeZoomMenu,
onToggleBackgroundSettings: toggleBackgroundSettings,
onApplyCanvasBackgroundColor: applyCanvasBackgroundColor,
onCanvasBackgroundHexChange: handleCanvasBackgroundHexChange,
onToggleSidebarPanel: toggleSidebarPanel,
onToggleMinimap: toggleMinimap,
onMinimapPointerDown: handleMinimapPointerDown,
onSwitchTool: switchTool,
children: generationComposerNode,
};
return (
<ImageCanvasEditorShellView
editorRootRef={editorRootRef}
uploadInputRef={uploadInputRef}
onUploadInputChange={handleUploadInputChange}
assetDragPreview={assetDragPreview}
sidebarProps={sidebarProps}
topbarProps={topbarProps}
stageProps={stageProps}
metadataProps={{
layer: metadataLayer,
onClose: () => setMetadataLayer(null),
}}
/>
);
}
export default ImageCanvasEditorView;