Files
Genarrative/src/components/image-editor/ImageCanvasAssetFolderSectionView.tsx
T
kdletters 636934de88 补齐陶泥儿精选审核链路
新增 editor_showcase_asset、点赞和活动配置表,生成素材默认不公开。

补齐素材提交精选审核、后台审核通过返还泥点、展示开关和公开精选分页接口。

更新素材库右键菜单和创作主页精选瀑布流,展示作者昵称、陶泥号、成本和点赞状态。

新增后台素材查询页,支持时间、用户、分类、关键词查询和缩略图详情查看。

同步 SpacetimeDB 绑定、前后端契约、项目文档和决策记录。
2026-07-04 15:47:14 +08:00

265 lines
8.7 KiB
TypeScript

import {
Check,
ChevronDown,
ChevronRight,
Folder,
ImagePlus,
PencilLine,
Trash2,
X,
} from 'lucide-react';
import type { Dispatch, SetStateAction } from 'react';
import { PlatformTextField } from '../common/PlatformTextField';
import { EditorIconButton } from './ImageCanvasEditorPrimitives';
import {
ASSET_DRAG_MIME_TYPE,
getDraggedAssetId,
hasDataTransferType,
} from './ImageCanvasEditorModel';
import type {
AssetPointerDragState,
EditorAsset,
EditorAssetFolder,
UploadTarget,
} from './ImageCanvasEditorTypes';
import { ImageCanvasAssetRowView } from './ImageCanvasAssetRowView';
import type {
GroupedEditorAssetFolder,
UploadFilesOptions,
UploadRequestOptions,
} from './ImageCanvasAssetLibraryPanelView';
type ImageCanvasAssetFolderSectionViewProps = {
folder: GroupedEditorAssetFolder;
assetPointerDragRef: { current: AssetPointerDragState | null };
suppressAssetClickRef: { current: boolean };
isAssetSelectionMode: boolean;
selectedAssetIds: Set<string>;
assetMoveDropFolderId: string | null;
renamingFolder: { folderId: string; value: string } | null;
renamingAsset: { assetId: string; value: string } | null;
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>>;
setSelectedAssetIds: Dispatch<SetStateAction<Set<string>>>;
updateAssetMoveDropFolder: (folderId: string | null) => void;
addUploadedFiles: (files: FileList | File[], options?: UploadFilesOptions) => void;
requestUpload: (target: UploadTarget, options?: UploadRequestOptions) => void;
moveAssetToFolder: (assetId: string, folderId: string) => 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) => void;
addAssetLayer: (asset: EditorAsset) => void;
onDownloadAsset: (asset: EditorAsset) => void;
};
export function ImageCanvasAssetFolderSectionView({
folder,
assetPointerDragRef,
suppressAssetClickRef,
isAssetSelectionMode,
selectedAssetIds,
assetMoveDropFolderId,
renamingFolder,
renamingAsset,
setRenamingFolder,
setRenamingAsset,
setActiveUploadFolderId,
setUploadDropTarget,
setAssetPointerDrag,
setSelectedAssetIds,
updateAssetMoveDropFolder,
addUploadedFiles,
requestUpload,
moveAssetToFolder,
toggleAssetFolder,
startRenamingFolder,
commitFolderRename,
deleteAssetFolder,
startRenamingAsset,
commitAssetRename,
deleteUploadedAsset,
submitAssetToShowcase,
toggleAssetSelected,
addAssetLayer,
onDownloadAsset,
}: ImageCanvasAssetFolderSectionViewProps) {
return (
<section
className={[
'image-canvas-editor__asset-folder',
assetMoveDropFolderId === folder.id
? 'image-canvas-editor__asset-folder--move-target'
: '',
]
.filter(Boolean)
.join(' ')}
aria-label={folder.label}
data-asset-folder-id={folder.id}
onDragOver={(event) => {
if (hasDataTransferType(event.dataTransfer, ASSET_DRAG_MIME_TYPE)) {
event.preventDefault();
event.stopPropagation();
setUploadDropTarget(null);
updateAssetMoveDropFolder(folder.id);
event.dataTransfer.dropEffect = 'move';
return;
}
if (hasDataTransferType(event.dataTransfer, 'Files')) {
event.preventDefault();
event.stopPropagation();
setUploadDropTarget('assets');
event.dataTransfer.dropEffect = 'copy';
}
}}
onDrop={(event) => {
const movingAssetId = getDraggedAssetId(event.dataTransfer);
if (movingAssetId) {
event.preventDefault();
event.stopPropagation();
setUploadDropTarget(null);
updateAssetMoveDropFolder(null);
moveAssetToFolder(movingAssetId, folder.id);
return;
}
if (!event.dataTransfer.files.length) {
return;
}
event.preventDefault();
event.stopPropagation();
setUploadDropTarget(null);
updateAssetMoveDropFolder(null);
addUploadedFiles(event.dataTransfer.files, {
folderId: folder.id,
});
}}
>
<div
className="image-canvas-editor__asset-folder-header"
data-asset-folder-header-id={folder.id}
>
<EditorIconButton
label={`${folder.collapsed ? '展开' : '折叠'}${folder.label}`}
title={folder.collapsed ? '展开' : '折叠'}
icon={folder.collapsed ? ChevronRight : ChevronDown}
expanded={!folder.collapsed}
onClick={() => toggleAssetFolder(folder.id)}
/>
<Folder className="h-4 w-4" />
{renamingFolder?.folderId === folder.id ? (
<PlatformTextField
aria-label={`重命名文件夹${folder.label}`}
value={renamingFolder.value}
autoFocus
size="xs"
density="compact"
className="image-canvas-editor__folder-rename-input"
onChange={(event) =>
setRenamingFolder({
folderId: folder.id,
value: event.target.value,
})
}
onKeyDown={(event) => {
if (event.key === 'Enter') {
event.preventDefault();
commitFolderRename(folder);
}
if (event.key === 'Escape') {
event.preventDefault();
setRenamingFolder(null);
}
}}
/>
) : (
<span>{folder.label}</span>
)}
<span>{folder.assets.length}</span>
{renamingFolder?.folderId === folder.id ? (
<>
<EditorIconButton
label={`保存文件夹${folder.label}名称`}
title="保存"
icon={Check}
onClick={() => commitFolderRename(folder)}
/>
<EditorIconButton
label={`取消重命名文件夹${folder.label}`}
title="取消"
icon={X}
onClick={() => setRenamingFolder(null)}
/>
</>
) : (
<EditorIconButton
label={`重命名文件夹${folder.label}`}
title="重命名"
icon={PencilLine}
onClick={() => startRenamingFolder(folder)}
/>
)}
{!folder.systemDefault ? (
<EditorIconButton
label={`删除文件夹${folder.label}`}
title="删除"
icon={Trash2}
onClick={() => deleteAssetFolder(folder)}
/>
) : null}
<EditorIconButton
label={`上传到${folder.label}`}
title="上传"
icon={ImagePlus}
onClick={() => {
setActiveUploadFolderId(folder.id);
requestUpload('asset', { folderId: folder.id });
}}
/>
</div>
<div
className="image-canvas-editor__asset-folder-list"
hidden={folder.collapsed}
>
{folder.assets.map((asset) => (
<ImageCanvasAssetRowView
key={asset.id}
asset={asset}
assetPointerDragRef={assetPointerDragRef}
suppressAssetClickRef={suppressAssetClickRef}
isAssetSelectionMode={isAssetSelectionMode}
selectedAssetIds={selectedAssetIds}
renamingAsset={renamingAsset}
setRenamingAsset={setRenamingAsset}
setUploadDropTarget={setUploadDropTarget}
setAssetPointerDrag={setAssetPointerDrag}
setSelectedAssetIds={setSelectedAssetIds}
updateAssetMoveDropFolder={updateAssetMoveDropFolder}
addUploadedFiles={addUploadedFiles}
moveAssetToFolder={moveAssetToFolder}
startRenamingAsset={startRenamingAsset}
commitAssetRename={commitAssetRename}
deleteUploadedAsset={deleteUploadedAsset}
submitAssetToShowcase={submitAssetToShowcase}
toggleAssetSelected={toggleAssetSelected}
addAssetLayer={addAssetLayer}
onDownloadAsset={onDownloadAsset}
/>
))}
</div>
</section>
);
}