84818f9bd5
新增 useImageCanvasGenerationSurface 收口生成浮层编排。 主视图移除生成 Composer 大段 props 胶水。 舞台控制模型移除重复生成锚点派生。 补充生成表面 hook 单测并更新拆分文档与跟踪记录。
804 lines
25 KiB
TypeScript
804 lines
25 KiB
TypeScript
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
|
|
import { useAuthUi } from '../auth/AuthUiContext';
|
|
import { ImageCanvasMetadataModalView } from './ImageCanvasMetadataModalView';
|
|
import { ImageCanvasSidebarView } from './ImageCanvasSidebarView';
|
|
import { ImageCanvasStageView } from './ImageCanvasStageView';
|
|
import { ImageCanvasTopbarView } from './ImageCanvasTopbarView';
|
|
import { resolveContextMenuPosition } from './ImageCanvasEditorModel';
|
|
import { isCanvasGenerationComposerVisible } from './ImageCanvasOverlayModel';
|
|
import type {
|
|
AssetPointerDragState,
|
|
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 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 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,
|
|
removeCanvasGenerationDialogById,
|
|
activateCanvasGenerationDialog,
|
|
removeCanvasGenerationDialogsByLayerId,
|
|
getGeneratingDialogPlaceholder,
|
|
} = useCanvasGenerationDialogs({
|
|
onActivate: handleActivateCanvasGenerationDialog,
|
|
});
|
|
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: false,
|
|
}
|
|
: currentDialog,
|
|
);
|
|
}
|
|
}, []);
|
|
const projectPersistenceRefs = useMemo(
|
|
() => ({
|
|
layersRef,
|
|
viewportRef,
|
|
}),
|
|
[],
|
|
);
|
|
const projectPersistenceSetters = useMemo(
|
|
() => ({
|
|
setProjectTitle,
|
|
setProjectRenameValue,
|
|
setViewport,
|
|
setLayers,
|
|
selectSingleLayer,
|
|
setLayerCounter: (value: number) => {
|
|
layerCounterRef.current = value;
|
|
},
|
|
}),
|
|
[selectSingleLayer],
|
|
);
|
|
const { projectId, appendCanvasLayersWithResources } =
|
|
useImageCanvasProjectPersistence({
|
|
refs: projectPersistenceRefs,
|
|
setters: projectPersistenceSetters,
|
|
layers,
|
|
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,
|
|
setActiveSidebarPanel,
|
|
appendCanvasLayersWithResources,
|
|
selectSingleLayer,
|
|
});
|
|
const generationSurface = useImageCanvasGenerationSurface({
|
|
layers,
|
|
canvasSize,
|
|
viewport,
|
|
layerCounterRef,
|
|
specToolWrapRef,
|
|
characterSpecButtonRef,
|
|
characterReferenceButtonRef,
|
|
iconSpecButtonRef,
|
|
generateDialog,
|
|
setGenerateDialog,
|
|
activeCanvasGenerationDialog,
|
|
openCanvasGenerationDialog,
|
|
updateCanvasGenerationDialogById,
|
|
removeCanvasGenerationDialogById,
|
|
removeCanvasGenerationDialogsByLayerId,
|
|
getGeneratingDialogPlaceholder,
|
|
appendCanvasLayersWithResources,
|
|
selectSingleLayer,
|
|
fitLayers,
|
|
setActiveTool,
|
|
setActiveSidebarPanel,
|
|
setMetadataLayer,
|
|
setImageContextMenu,
|
|
requestUpload,
|
|
});
|
|
const {
|
|
quickEditPanel,
|
|
setQuickEditPanel,
|
|
setIsSpecMenuOpen,
|
|
setIsCharacterSpecMenuOpen,
|
|
setIsCharacterReferenceMenuOpen,
|
|
isPickingCharacterSpecFromCanvas,
|
|
setIsPickingCharacterSpecFromCanvas,
|
|
isPickingCharacterReferenceFromCanvas,
|
|
setIsPickingCharacterReferenceFromCanvas,
|
|
setIsIconSpecMenuOpen,
|
|
isPickingIconSpecFromCanvas,
|
|
setIsPickingIconSpecFromCanvas,
|
|
openCharacterAnimationPanel,
|
|
openEditDialog,
|
|
openQuickEditPanel,
|
|
pickCharacterSpecFromLayer,
|
|
pickCharacterReferenceFromLayer,
|
|
pickIconSpecFromLayer,
|
|
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,
|
|
isPickingCharacterReferenceFromCanvas,
|
|
isPickingIconSpecFromCanvas,
|
|
clearCanvasFocus,
|
|
pickCharacterSpecFromLayer,
|
|
pickCharacterReferenceFromLayer,
|
|
pickIconSpecFromLayer,
|
|
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,
|
|
setIsCharacterSpecMenuOpen,
|
|
setIsCharacterReferenceMenuOpen,
|
|
setIsPickingCharacterSpecFromCanvas,
|
|
setIsPickingCharacterReferenceFromCanvas,
|
|
setIsIconSpecMenuOpen,
|
|
setIsPickingIconSpecFromCanvas,
|
|
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);
|
|
};
|
|
|
|
return (
|
|
<section
|
|
ref={editorRootRef}
|
|
className="image-canvas-editor"
|
|
aria-label="图片画布编辑器"
|
|
onContextMenu={(event) => event.preventDefault()}
|
|
>
|
|
<input
|
|
ref={uploadInputRef}
|
|
type="file"
|
|
accept="image/*"
|
|
multiple
|
|
aria-label="上传图片文件"
|
|
hidden
|
|
onChange={handleUploadInputChange}
|
|
/>
|
|
{assetPointerDrag?.active ? (
|
|
<div
|
|
className="image-canvas-editor__asset-drag-preview"
|
|
style={{
|
|
left: Number.isFinite(assetPointerDrag.currentClientX)
|
|
? assetPointerDrag.currentClientX
|
|
: 0,
|
|
top: Number.isFinite(assetPointerDrag.currentClientY)
|
|
? assetPointerDrag.currentClientY
|
|
: 0,
|
|
}}
|
|
aria-hidden="true"
|
|
>
|
|
{assets.find((asset) => asset.id === assetPointerDrag.assetId)
|
|
?.label ?? '素材'}
|
|
</div>
|
|
) : null}
|
|
<ImageCanvasSidebarView
|
|
activeSidebarPanel={activeSidebarPanel}
|
|
assetListRef={assetListRef}
|
|
assetPointerDragRef={assetPointerDragRef}
|
|
suppressAssetClickRef={suppressAssetClickRef}
|
|
assets={assets}
|
|
groupedAssets={groupedAssets}
|
|
assetFolders={assetFolders}
|
|
layers={layers}
|
|
selectedLayerId={selectedLayerId}
|
|
selectedLayerIds={selectedLayerIds}
|
|
isAssetSelectionMode={isAssetSelectionMode}
|
|
selectedAssetIds={selectedAssetIds}
|
|
assetMoveDropFolderId={assetMoveDropFolderId}
|
|
pinnedAssetMoveFolderId={pinnedAssetMoveFolderId}
|
|
creatingFolder={creatingFolder}
|
|
newFolderName={newFolderName}
|
|
renamingFolder={renamingFolder}
|
|
renamingAsset={renamingAsset}
|
|
allSelectableAssetsSelected={allSelectableAssetsSelected}
|
|
assetMarquee={assetMarquee}
|
|
setIsAssetSelectionMode={setIsAssetSelectionMode}
|
|
setCreatingFolder={setCreatingFolder}
|
|
setNewFolderName={setNewFolderName}
|
|
setRenamingFolder={setRenamingFolder}
|
|
setRenamingAsset={setRenamingAsset}
|
|
setActiveUploadFolderId={setActiveUploadFolderId}
|
|
setUploadDropTarget={setUploadDropTarget}
|
|
setAssetPointerDrag={setAssetPointerDrag}
|
|
setSelectedAssetIds={setSelectedAssetIds}
|
|
setImageContextMenu={setImageContextMenu}
|
|
setContextMenu={setContextMenu}
|
|
onAssetMarqueePointerDown={handleAssetMarqueePointerDown}
|
|
onAssetMarqueePointerMove={handleAssetMarqueePointerMove}
|
|
onAssetMarqueePointerUp={handleAssetMarqueePointerUp}
|
|
updateAssetMoveDropFolder={updateAssetMoveDropFolder}
|
|
addUploadedFiles={addUploadedFiles}
|
|
requestUpload={requestUpload}
|
|
moveAssetToFolder={moveAssetToFolder}
|
|
commitNewAssetFolder={commitNewAssetFolder}
|
|
toggleAssetFolder={toggleAssetFolder}
|
|
startRenamingFolder={startRenamingFolder}
|
|
commitFolderRename={commitFolderRename}
|
|
deleteAssetFolder={deleteAssetFolder}
|
|
startRenamingAsset={startRenamingAsset}
|
|
commitAssetRename={commitAssetRename}
|
|
deleteUploadedAsset={deleteUploadedAsset}
|
|
toggleAssetSelected={toggleAssetSelected}
|
|
addAssetLayer={addAssetLayer}
|
|
toggleAllAssetsSelected={toggleAllAssetsSelected}
|
|
deleteSelectedAssets={deleteSelectedAssets}
|
|
closeAssetSelectionMode={closeAssetSelectionMode}
|
|
groupSelectedLayers={groupSelectedLayers}
|
|
selectSingleLayer={selectSingleLayer}
|
|
resolveContextMenuPosition={resolveContextMenuPosition}
|
|
getCanvasPointFromClient={getCanvasPointFromClient}
|
|
/>
|
|
|
|
<div className="image-canvas-editor__main">
|
|
<ImageCanvasTopbarView
|
|
projectId={projectId}
|
|
projectTitle={projectTitle}
|
|
projectRenameValue={projectRenameValue}
|
|
isRenamingProject={isRenamingProject}
|
|
isProjectRenameSaving={isProjectRenameSaving}
|
|
projectRenameError={projectRenameError}
|
|
layers={layers}
|
|
assetExportStatus={assetExportStatus}
|
|
isExportingAssets={isExportingAssets}
|
|
setProjectRenameValue={setProjectRenameValue}
|
|
startProjectRename={startProjectRename}
|
|
cancelProjectRename={cancelProjectRename}
|
|
submitProjectRename={submitProjectRename}
|
|
resetProjectRenameError={resetProjectRenameError}
|
|
exportCanvasAssets={exportCanvasAssets}
|
|
/>
|
|
|
|
<ImageCanvasStageView
|
|
canvasViewportRef={canvasViewportRef}
|
|
specToolWrapRef={specToolWrapRef}
|
|
isPanning={isPanning}
|
|
effectiveTool={effectiveTool}
|
|
canvasBackgroundColor={canvasBackgroundColor}
|
|
canvasBackgroundHexValue={canvasBackgroundHexValue}
|
|
viewport={viewport}
|
|
snapGuide={snapGuide}
|
|
layers={layers}
|
|
selectedLayer={selectedLayer}
|
|
selectedLayerIds={selectedLayerIds}
|
|
hoveredLayerId={hoveredLayerId}
|
|
canvasMarquee={canvasMarquee}
|
|
canvasGenerationDialogs={canvasGenerationDialogs}
|
|
generateDialog={generateDialog}
|
|
quickEditPanel={quickEditPanel}
|
|
generationComposerStyle={generationComposerStyle}
|
|
selectedToolbarStyle={selectedToolbarStyle}
|
|
uploadDropTarget={uploadDropTarget}
|
|
contextMenu={contextMenu}
|
|
canvasClipboard={canvasClipboard}
|
|
imageContextMenu={imageContextMenu}
|
|
imageContextMenuLayer={imageContextMenuLayer}
|
|
contextShouldShowLayer={contextShouldShowLayer}
|
|
contextShouldUnlockLayer={contextShouldUnlockLayer}
|
|
canUndo={canUndo}
|
|
canRedo={canRedo}
|
|
isZoomMenuOpen={isZoomMenuOpen}
|
|
isBackgroundSettingsOpen={isBackgroundSettingsOpen}
|
|
activeSidebarPanel={activeSidebarPanel}
|
|
isMinimapOpen={isMinimapOpen}
|
|
minimapModel={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) =>
|
|
setHoveredLayerId((currentId) =>
|
|
currentId === layerId ? null : currentId,
|
|
)
|
|
}
|
|
onOpenLayerMetadata={(layer) => {
|
|
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}
|
|
>
|
|
{generationComposerNode}
|
|
</ImageCanvasStageView>
|
|
</div>
|
|
|
|
<ImageCanvasMetadataModalView
|
|
layer={metadataLayer}
|
|
onClose={() => setMetadataLayer(null)}
|
|
/>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
export default ImageCanvasEditorView;
|