31900df2b8
重构多选逻辑到一个单独的hook 不在最终提交时过滤, 只有选择操作会影响最终结果集合
326 lines
11 KiB
TypeScript
326 lines
11 KiB
TypeScript
import {
|
|
Check,
|
|
CheckSquare,
|
|
Download,
|
|
Folder,
|
|
Square,
|
|
Trash2,
|
|
X,
|
|
} from 'lucide-react';
|
|
import type {
|
|
Dispatch,
|
|
PointerEvent as ReactPointerEvent,
|
|
RefObject,
|
|
SetStateAction,
|
|
} from 'react';
|
|
|
|
import { PlatformActionButton } from '../common/PlatformActionButton';
|
|
import { PlatformBatchActionToolbar } from '../common/PlatformBatchActionToolbar';
|
|
import { PlatformTextField } from '../common/PlatformTextField';
|
|
import { ImageCanvasAssetFolderSectionView } from './ImageCanvasAssetFolderSectionView';
|
|
import { EditorIconButton } from './ImageCanvasEditorPrimitives';
|
|
import type {
|
|
AssetMarqueeState,
|
|
AssetPointerDragState,
|
|
EditorAsset,
|
|
EditorAssetFolder,
|
|
UploadTarget,
|
|
} from './ImageCanvasEditorTypes';
|
|
import type { AssetSelectionInteraction } from './useImageCanvasAssetSelection';
|
|
|
|
export type GroupedEditorAssetFolder = EditorAssetFolder & {
|
|
assets: EditorAsset[];
|
|
};
|
|
|
|
export type UploadFilesOptions = {
|
|
folderId?: string;
|
|
canvasPoint?: { x: number; y: number };
|
|
addToCanvas?: boolean;
|
|
};
|
|
|
|
export type UploadRequestOptions = {
|
|
folderId?: string;
|
|
addToCanvas?: boolean;
|
|
};
|
|
|
|
export type ImageCanvasAssetLibraryPanelViewProps = {
|
|
assetListRef: RefObject<HTMLDivElement | null>;
|
|
assetPointerDragRef: { current: AssetPointerDragState | null };
|
|
suppressAssetClickRef: { current: boolean };
|
|
groupedAssets: GroupedEditorAssetFolder[];
|
|
assetFolders: EditorAssetFolder[];
|
|
isAssetSelectionMode: boolean;
|
|
selectedAssetIds: Set<string>;
|
|
visibleAssetIds: string[];
|
|
assetMoveDropFolderId: string | null;
|
|
pinnedAssetMoveFolderId: string | null;
|
|
creatingFolder: boolean;
|
|
newFolderName: string;
|
|
renamingFolder: { folderId: string; value: string } | null;
|
|
renamingAsset: { assetId: string; value: string } | null;
|
|
allVisibleAssetsSelected: boolean;
|
|
isExportingSelectedAssets: boolean;
|
|
assetMarquee: AssetMarqueeState | null;
|
|
setCreatingFolder: Dispatch<SetStateAction<boolean>>;
|
|
setNewFolderName: Dispatch<SetStateAction<string>>;
|
|
setRenamingFolder: Dispatch<
|
|
SetStateAction<{ folderId: string; value: string } | null>
|
|
>;
|
|
setRenamingAsset: Dispatch<
|
|
SetStateAction<{ assetId: string; value: string } | null>
|
|
>;
|
|
setActiveUploadFolderId: Dispatch<SetStateAction<string>>;
|
|
setUploadDropTarget: Dispatch<SetStateAction<'canvas' | 'assets' | null>>;
|
|
setAssetPointerDrag: Dispatch<SetStateAction<AssetPointerDragState | null>>;
|
|
onAssetMarqueePointerDown: (event: ReactPointerEvent<HTMLDivElement>) => void;
|
|
onAssetMarqueePointerMove: (event: ReactPointerEvent<HTMLDivElement>) => void;
|
|
onAssetMarqueePointerUp: (event: ReactPointerEvent<HTMLDivElement>) => void;
|
|
updateAssetMoveDropFolder: (folderId: string | null) => void;
|
|
addUploadedFiles: (
|
|
files: FileList | File[],
|
|
options?: UploadFilesOptions,
|
|
) => void;
|
|
requestUpload: (target: UploadTarget, options?: UploadRequestOptions) => void;
|
|
moveAssetToFolder: (assetId: string, folderId: string) => void;
|
|
commitNewAssetFolder: () => void | Promise<void>;
|
|
toggleAssetFolder: (folderId: string) => void;
|
|
startRenamingFolder: (folder: EditorAssetFolder) => void;
|
|
commitFolderRename: (folder: EditorAssetFolder) => void;
|
|
deleteAssetFolder: (folder: EditorAssetFolder) => void;
|
|
startRenamingAsset: (asset: EditorAsset) => void;
|
|
commitAssetRename: (asset: EditorAsset) => void;
|
|
deleteUploadedAsset: (asset: EditorAsset) => void;
|
|
submitAssetToShowcase: (asset: EditorAsset) => void;
|
|
toggleAssetSelected: (
|
|
assetId: string,
|
|
interaction: AssetSelectionInteraction,
|
|
) => void;
|
|
addAssetLayer: (asset: EditorAsset) => void;
|
|
onDownloadAsset: (asset: EditorAsset) => void;
|
|
toggleAllAssetsSelected: (visibleAssetIds: string[]) => void;
|
|
downloadSelectedAssets: () => void | Promise<void>;
|
|
deleteSelectedAssets: () => void;
|
|
closeAssetSelectionMode: () => void;
|
|
};
|
|
|
|
export function ImageCanvasAssetLibraryPanelView({
|
|
assetListRef,
|
|
assetPointerDragRef,
|
|
suppressAssetClickRef,
|
|
groupedAssets,
|
|
assetFolders,
|
|
isAssetSelectionMode,
|
|
selectedAssetIds,
|
|
visibleAssetIds,
|
|
assetMoveDropFolderId,
|
|
pinnedAssetMoveFolderId,
|
|
creatingFolder,
|
|
newFolderName,
|
|
renamingFolder,
|
|
renamingAsset,
|
|
allVisibleAssetsSelected,
|
|
isExportingSelectedAssets,
|
|
assetMarquee,
|
|
setCreatingFolder,
|
|
setNewFolderName,
|
|
setRenamingFolder,
|
|
setRenamingAsset,
|
|
setActiveUploadFolderId,
|
|
setUploadDropTarget,
|
|
setAssetPointerDrag,
|
|
onAssetMarqueePointerDown,
|
|
onAssetMarqueePointerMove,
|
|
onAssetMarqueePointerUp,
|
|
updateAssetMoveDropFolder,
|
|
addUploadedFiles,
|
|
requestUpload,
|
|
moveAssetToFolder,
|
|
commitNewAssetFolder,
|
|
toggleAssetFolder,
|
|
startRenamingFolder,
|
|
commitFolderRename,
|
|
deleteAssetFolder,
|
|
startRenamingAsset,
|
|
commitAssetRename,
|
|
deleteUploadedAsset,
|
|
submitAssetToShowcase,
|
|
toggleAssetSelected,
|
|
addAssetLayer,
|
|
onDownloadAsset,
|
|
toggleAllAssetsSelected,
|
|
downloadSelectedAssets,
|
|
deleteSelectedAssets,
|
|
closeAssetSelectionMode,
|
|
}: ImageCanvasAssetLibraryPanelViewProps) {
|
|
let selectionToggleActionText = '全选';
|
|
if (allVisibleAssetsSelected) {
|
|
selectionToggleActionText = '取消全选';
|
|
}
|
|
let selectionToggleLabel = selectionToggleActionText;
|
|
if (selectedAssetIds.size > 0) {
|
|
selectionToggleLabel = `${selectionToggleActionText} · 已选 ${selectedAssetIds.size}`;
|
|
}
|
|
|
|
return (
|
|
<div className="image-canvas-editor__asset-library">
|
|
<div
|
|
ref={assetListRef}
|
|
className="image-canvas-editor__asset-list"
|
|
onPointerDown={onAssetMarqueePointerDown}
|
|
onPointerMove={onAssetMarqueePointerMove}
|
|
onPointerUp={onAssetMarqueePointerUp}
|
|
onPointerCancel={onAssetMarqueePointerUp}
|
|
>
|
|
{pinnedAssetMoveFolderId ? (
|
|
<div
|
|
className="image-canvas-editor__asset-folder-sticky-target"
|
|
aria-hidden="true"
|
|
>
|
|
<Folder className="h-4 w-4" />
|
|
<span>
|
|
{assetFolders.find(
|
|
(folder) => folder.id === pinnedAssetMoveFolderId,
|
|
)?.label ?? '目标文件夹'}
|
|
</span>
|
|
</div>
|
|
) : null}
|
|
{creatingFolder ? (
|
|
<form
|
|
className="image-canvas-editor__folder-create"
|
|
onSubmit={(event) => {
|
|
event.preventDefault();
|
|
void commitNewAssetFolder();
|
|
}}
|
|
>
|
|
<PlatformTextField
|
|
aria-label="素材文件夹名称"
|
|
value={newFolderName}
|
|
autoFocus
|
|
size="xs"
|
|
density="compact"
|
|
className="image-canvas-editor__folder-create-input"
|
|
onChange={(event) => setNewFolderName(event.target.value)}
|
|
onKeyDown={(event) => {
|
|
if (event.key === 'Escape') {
|
|
event.preventDefault();
|
|
setCreatingFolder(false);
|
|
setNewFolderName('');
|
|
}
|
|
}}
|
|
/>
|
|
<EditorIconButton
|
|
type="submit"
|
|
label="保存素材文件夹"
|
|
icon={Check}
|
|
/>
|
|
<EditorIconButton
|
|
label="取消新建素材文件夹"
|
|
icon={X}
|
|
onClick={() => {
|
|
setCreatingFolder(false);
|
|
setNewFolderName('');
|
|
}}
|
|
/>
|
|
</form>
|
|
) : null}
|
|
{groupedAssets.map((folder) => (
|
|
<ImageCanvasAssetFolderSectionView
|
|
key={folder.id}
|
|
folder={folder}
|
|
assetPointerDragRef={assetPointerDragRef}
|
|
suppressAssetClickRef={suppressAssetClickRef}
|
|
isAssetSelectionMode={isAssetSelectionMode}
|
|
selectedAssetIds={selectedAssetIds}
|
|
visibleAssetIds={visibleAssetIds}
|
|
assetMoveDropFolderId={assetMoveDropFolderId}
|
|
renamingFolder={renamingFolder}
|
|
renamingAsset={renamingAsset}
|
|
setRenamingFolder={setRenamingFolder}
|
|
setRenamingAsset={setRenamingAsset}
|
|
setActiveUploadFolderId={setActiveUploadFolderId}
|
|
setUploadDropTarget={setUploadDropTarget}
|
|
setAssetPointerDrag={setAssetPointerDrag}
|
|
updateAssetMoveDropFolder={updateAssetMoveDropFolder}
|
|
addUploadedFiles={addUploadedFiles}
|
|
requestUpload={requestUpload}
|
|
moveAssetToFolder={moveAssetToFolder}
|
|
toggleAssetFolder={toggleAssetFolder}
|
|
startRenamingFolder={startRenamingFolder}
|
|
commitFolderRename={commitFolderRename}
|
|
deleteAssetFolder={deleteAssetFolder}
|
|
startRenamingAsset={startRenamingAsset}
|
|
commitAssetRename={commitAssetRename}
|
|
deleteUploadedAsset={deleteUploadedAsset}
|
|
submitAssetToShowcase={submitAssetToShowcase}
|
|
toggleAssetSelected={toggleAssetSelected}
|
|
addAssetLayer={addAssetLayer}
|
|
onDownloadAsset={onDownloadAsset}
|
|
/>
|
|
))}
|
|
{assetMarquee ? (
|
|
<div
|
|
className="image-canvas-editor__asset-marquee"
|
|
aria-hidden="true"
|
|
style={{
|
|
left: Math.min(assetMarquee.startX, assetMarquee.currentX),
|
|
top: Math.min(assetMarquee.startY, assetMarquee.currentY),
|
|
width: Math.abs(assetMarquee.currentX - assetMarquee.startX),
|
|
height: Math.abs(assetMarquee.currentY - assetMarquee.startY),
|
|
}}
|
|
/>
|
|
) : null}
|
|
</div>
|
|
{isAssetSelectionMode ? (
|
|
<PlatformBatchActionToolbar
|
|
className="static! left-auto! bottom-auto! grid! w-full! max-w-none! transform-none! grid-cols-3! gap-1.5! overflow-hidden! rounded-none! border-x-0! border-b-0! px-2.5! pt-2.5! pb-[calc(0.55rem+env(safe-area-inset-bottom,0px))]! shadow-[0_-8px_22px_rgba(15,23,42,0.08)]!"
|
|
label="素材批量操作"
|
|
>
|
|
<PlatformActionButton
|
|
className="image-canvas-editor__asset-batch-action col-span-3"
|
|
tone="secondary"
|
|
size="sm"
|
|
disabled={visibleAssetIds.length === 0 || isExportingSelectedAssets}
|
|
onClick={() => toggleAllAssetsSelected(visibleAssetIds)}
|
|
>
|
|
{allVisibleAssetsSelected ? (
|
|
<CheckSquare className="h-4 w-4" />
|
|
) : (
|
|
<Square className="h-4 w-4" />
|
|
)}
|
|
<span className="min-w-0 truncate">{selectionToggleLabel}</span>
|
|
</PlatformActionButton>
|
|
<PlatformActionButton
|
|
className="image-canvas-editor__asset-batch-action"
|
|
tone="secondary"
|
|
size="sm"
|
|
disabled={selectedAssetIds.size === 0 || isExportingSelectedAssets}
|
|
onClick={() => void downloadSelectedAssets()}
|
|
>
|
|
<Download className="h-4 w-4" />
|
|
{isExportingSelectedAssets ? '下载中' : '下载'}
|
|
</PlatformActionButton>
|
|
<PlatformActionButton
|
|
className="image-canvas-editor__asset-batch-action"
|
|
tone="warning"
|
|
size="sm"
|
|
disabled={selectedAssetIds.size === 0 || isExportingSelectedAssets}
|
|
onClick={deleteSelectedAssets}
|
|
>
|
|
<Trash2 className="h-4 w-4" />
|
|
删除
|
|
</PlatformActionButton>
|
|
<PlatformActionButton
|
|
className="image-canvas-editor__asset-batch-action"
|
|
tone="secondary"
|
|
size="sm"
|
|
onClick={closeAssetSelectionMode}
|
|
>
|
|
<X />
|
|
取消
|
|
</PlatformActionButton>
|
|
</PlatformBatchActionToolbar>
|
|
) : null}
|
|
</div>
|
|
);
|
|
}
|