拆分图片画布编辑器侧栏视图
抽出素材和图层左侧整合面板为 ImageCanvasSidebarView 保留上传、登录、拖到画布和持久化状态机在主视图 更新前端拆分计划和 TRACKING 验证记录
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import {
|
||||
Braces,
|
||||
Check,
|
||||
CheckSquare,
|
||||
ChevronDown,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
@@ -10,7 +9,6 @@ import {
|
||||
Crop,
|
||||
Download,
|
||||
Folder,
|
||||
FolderPlus,
|
||||
Hand,
|
||||
ImageIcon,
|
||||
ImagePlus,
|
||||
@@ -19,13 +17,11 @@ import {
|
||||
Map as MapIcon,
|
||||
MousePointer2,
|
||||
Pencil,
|
||||
PencilLine,
|
||||
Redo2,
|
||||
RotateCcw,
|
||||
Shapes,
|
||||
SlidersHorizontal,
|
||||
Sparkles,
|
||||
Square,
|
||||
Trash2,
|
||||
Type,
|
||||
Undo2,
|
||||
@@ -71,7 +67,6 @@ import {
|
||||
updateEditorAssetFolder,
|
||||
} from '../../services/image-editor/editorProjectClient';
|
||||
import { PlatformActionButton } from '../common/PlatformActionButton';
|
||||
import { PlatformBatchActionToolbar } from '../common/PlatformBatchActionToolbar';
|
||||
import { PlatformFieldLabel } from '../common/PlatformFieldLabel';
|
||||
import {
|
||||
PlatformFloatingMenu,
|
||||
@@ -87,10 +82,8 @@ import {
|
||||
} from '../common/PlatformTextField';
|
||||
import { UnifiedModal } from '../common/UnifiedModal';
|
||||
import { useAuthUi } from '../auth/AuthUiContext';
|
||||
import {
|
||||
EditorIconButton,
|
||||
SidebarMediaItem,
|
||||
} from './ImageCanvasEditorPrimitives';
|
||||
import { EditorIconButton } from './ImageCanvasEditorPrimitives';
|
||||
import { ImageCanvasSidebarView } from './ImageCanvasSidebarView';
|
||||
import {
|
||||
ASSET_DRAG_MIME_TYPE,
|
||||
CANVAS_BACKGROUND_OPTIONS,
|
||||
@@ -4172,662 +4165,64 @@ export function ImageCanvasEditorView() {
|
||||
?.label ?? '素材'}
|
||||
</div>
|
||||
) : null}
|
||||
{activeSidebarPanel ? (
|
||||
<aside className="image-canvas-editor__sidebar" aria-label="图片资源栏">
|
||||
<div className="image-canvas-editor__sidebar-header">
|
||||
<div className="min-w-0">
|
||||
<h2 className="image-canvas-editor__sidebar-title">
|
||||
{activeSidebarPanel === 'assets' ? '素材' : '图层'}
|
||||
</h2>
|
||||
<div className="image-canvas-editor__sidebar-count">
|
||||
{activeSidebarPanel === 'assets'
|
||||
? assets.length
|
||||
: layers.length}
|
||||
</div>
|
||||
</div>
|
||||
{activeSidebarPanel === 'assets' ? (
|
||||
<div className="image-canvas-editor__sidebar-header-actions">
|
||||
<EditorIconButton
|
||||
className="image-canvas-editor__icon-button"
|
||||
label="素材选择模式"
|
||||
title="选择"
|
||||
icon={isAssetSelectionMode ? CheckSquare : Square}
|
||||
pressed={isAssetSelectionMode}
|
||||
onClick={() =>
|
||||
setIsAssetSelectionMode((currentMode) => !currentMode)
|
||||
}
|
||||
/>
|
||||
<EditorIconButton
|
||||
className="image-canvas-editor__icon-button"
|
||||
label="新建素材文件夹"
|
||||
title="新建文件夹"
|
||||
icon={FolderPlus}
|
||||
onClick={() => setCreatingFolder(true)}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<EditorIconButton
|
||||
className="image-canvas-editor__icon-button"
|
||||
label="图层打组"
|
||||
title="打组"
|
||||
icon={FolderPlus}
|
||||
disabled={!selectedLayerId && selectedLayerIds.length === 0}
|
||||
onClick={groupSelectedLayers}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{activeSidebarPanel === 'assets' ? (
|
||||
<div
|
||||
ref={assetListRef}
|
||||
className="image-canvas-editor__asset-list"
|
||||
onPointerDown={handleAssetMarqueePointerDown}
|
||||
onPointerMove={handleAssetMarqueePointerMove}
|
||||
onPointerUp={handleAssetMarqueePointerUp}
|
||||
onPointerCancel={handleAssetMarqueePointerUp}
|
||||
>
|
||||
{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) => (
|
||||
<section
|
||||
key={folder.id}
|
||||
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);
|
||||
setUploadTarget('asset');
|
||||
uploadInputRef.current?.click();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="image-canvas-editor__asset-folder-list"
|
||||
hidden={folder.collapsed}
|
||||
>
|
||||
{folder.assets.map((asset) => {
|
||||
const isRenaming = renamingAsset?.assetId === asset.id;
|
||||
const isUploadingAsset =
|
||||
asset.uploadStatus === 'uploading';
|
||||
const isFailedUpload = asset.uploadStatus === 'failed';
|
||||
const uploadProgress = clamp(
|
||||
asset.uploadProgress ?? 0,
|
||||
0,
|
||||
100,
|
||||
);
|
||||
const titleNode = isRenaming ? (
|
||||
<PlatformTextField
|
||||
aria-label={`重命名素材${asset.label}`}
|
||||
value={renamingAsset.value}
|
||||
autoFocus
|
||||
size="xs"
|
||||
density="compact"
|
||||
className="image-canvas-editor__asset-rename-input"
|
||||
onChange={(event) =>
|
||||
setRenamingAsset({
|
||||
assetId: asset.id,
|
||||
value: event.target.value,
|
||||
})
|
||||
}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
commitAssetRename(asset);
|
||||
}
|
||||
if (event.key === 'Escape') {
|
||||
event.preventDefault();
|
||||
setRenamingAsset(null);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
) : undefined;
|
||||
const actions = isUploadingAsset ? (
|
||||
<div className="image-canvas-editor__asset-upload-status">
|
||||
<span>{asset.uploadMessage ?? '上传中'}</span>
|
||||
<strong>{Math.round(uploadProgress)}%</strong>
|
||||
</div>
|
||||
) : isRenaming ? (
|
||||
<div className="image-canvas-editor__asset-actions">
|
||||
<EditorIconButton
|
||||
label={`保存素材${asset.label}名称`}
|
||||
title="保存"
|
||||
icon={Check}
|
||||
onClick={() => commitAssetRename(asset)}
|
||||
/>
|
||||
<EditorIconButton
|
||||
label={`取消重命名素材${asset.label}`}
|
||||
title="取消"
|
||||
icon={X}
|
||||
onClick={() => setRenamingAsset(null)}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="image-canvas-editor__asset-actions">
|
||||
<EditorIconButton
|
||||
label={`重命名素材${asset.label}`}
|
||||
title="重命名"
|
||||
icon={Pencil}
|
||||
onClick={() => startRenamingAsset(asset)}
|
||||
/>
|
||||
{asset.sourceKind === 'uploaded' ? (
|
||||
<EditorIconButton
|
||||
label={`删除素材${asset.label}`}
|
||||
title="删除"
|
||||
icon={Trash2}
|
||||
onClick={() => deleteUploadedAsset(asset)}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div key={asset.id} data-asset-id={asset.id}>
|
||||
<SidebarMediaItem
|
||||
title={asset.label}
|
||||
detail={`${asset.width} x ${asset.height}`}
|
||||
imageSrc={asset.src}
|
||||
imageAlt={`素材:${asset.label}`}
|
||||
primaryLabel={
|
||||
isUploadingAsset
|
||||
? `上传中${asset.label}`
|
||||
: isFailedUpload
|
||||
? `上传失败${asset.label}`
|
||||
: isAssetSelectionMode
|
||||
? `选择素材${asset.label}`
|
||||
: `添加${asset.label}`
|
||||
}
|
||||
onPrimaryClick={() => {
|
||||
if (isUploadingAsset || isFailedUpload) {
|
||||
return;
|
||||
}
|
||||
if (suppressAssetClickRef.current) {
|
||||
return;
|
||||
}
|
||||
if (isAssetSelectionMode) {
|
||||
toggleAssetSelected(asset.id);
|
||||
return;
|
||||
}
|
||||
addAssetLayer(asset);
|
||||
}}
|
||||
selected={selectedAssetIds.has(asset.id)}
|
||||
rowClassName={[
|
||||
'image-canvas-editor__asset-row',
|
||||
isUploadingAsset
|
||||
? 'image-canvas-editor__asset-row--uploading'
|
||||
: '',
|
||||
isFailedUpload
|
||||
? 'image-canvas-editor__asset-row--upload-failed'
|
||||
: '',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
primaryClassName="image-canvas-editor__asset-button"
|
||||
thumbnailClassName="image-canvas-editor__asset-thumb"
|
||||
metaClassName="image-canvas-editor__asset-meta"
|
||||
titleNode={
|
||||
isUploadingAsset || isFailedUpload ? (
|
||||
<span>{asset.label}</span>
|
||||
) : (
|
||||
titleNode
|
||||
)
|
||||
}
|
||||
actions={actions}
|
||||
draggable={
|
||||
!isRenaming && !isUploadingAsset && !isFailedUpload
|
||||
}
|
||||
previewOverlay={
|
||||
isUploadingAsset ? (
|
||||
<div className="image-canvas-editor__asset-upload-overlay">
|
||||
<span>上传中</span>
|
||||
</div>
|
||||
) : undefined
|
||||
}
|
||||
footerNode={
|
||||
isUploadingAsset || isFailedUpload ? (
|
||||
<div className="image-canvas-editor__asset-upload-progress">
|
||||
<div>
|
||||
<span>
|
||||
{isFailedUpload
|
||||
? (asset.uploadMessage ?? '上传失败')
|
||||
: (asset.uploadMessage ?? '上传中')}
|
||||
</span>
|
||||
<strong>{Math.round(uploadProgress)}%</strong>
|
||||
</div>
|
||||
<progress
|
||||
aria-label={`素材${asset.label}上传进度`}
|
||||
max={100}
|
||||
value={uploadProgress}
|
||||
/>
|
||||
</div>
|
||||
) : undefined
|
||||
}
|
||||
onDragStart={(event) => {
|
||||
if (
|
||||
isRenaming ||
|
||||
isUploadingAsset ||
|
||||
isFailedUpload
|
||||
) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (
|
||||
assetPointerDragRef.current?.assetId ===
|
||||
asset.id
|
||||
) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
event.dataTransfer.effectAllowed = 'move';
|
||||
event.dataTransfer.setData(
|
||||
ASSET_DRAG_MIME_TYPE,
|
||||
asset.id,
|
||||
);
|
||||
event.dataTransfer.setData(
|
||||
'text/plain',
|
||||
asset.label,
|
||||
);
|
||||
event.dataTransfer.setData(
|
||||
'text/uri-list',
|
||||
asset.src,
|
||||
);
|
||||
updateAssetMoveDropFolder(asset.folderId);
|
||||
}}
|
||||
onDragEnd={() => {
|
||||
setUploadDropTarget(null);
|
||||
updateAssetMoveDropFolder(null);
|
||||
}}
|
||||
onPointerDown={(event) => {
|
||||
if (event.button !== 0) {
|
||||
return;
|
||||
}
|
||||
const target = event.target as HTMLElement;
|
||||
if (isAssetSelectionMode) {
|
||||
if (target.closest('button')) {
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (
|
||||
isRenaming ||
|
||||
isUploadingAsset ||
|
||||
isFailedUpload ||
|
||||
target.closest('input, textarea, select')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const primaryAssetButton = target.closest(
|
||||
'.image-canvas-editor__asset-button',
|
||||
);
|
||||
if (target.closest('button') && !primaryAssetButton) {
|
||||
return;
|
||||
}
|
||||
const nextDrag: AssetPointerDragState = {
|
||||
pointerId: event.pointerId,
|
||||
assetId: asset.id,
|
||||
startClientX: event.clientX,
|
||||
startClientY: event.clientY,
|
||||
currentClientX: event.clientX,
|
||||
currentClientY: event.clientY,
|
||||
active: false,
|
||||
dropFolderId: null,
|
||||
};
|
||||
if (!primaryAssetButton) {
|
||||
try {
|
||||
event.currentTarget.setPointerCapture?.(
|
||||
event.pointerId,
|
||||
);
|
||||
} catch {
|
||||
// 自动化环境可能没有 active pointer,拖拽状态仍可走 window 事件完成。
|
||||
}
|
||||
event.preventDefault();
|
||||
}
|
||||
event.stopPropagation();
|
||||
assetPointerDragRef.current = nextDrag;
|
||||
setAssetPointerDrag(nextDrag);
|
||||
}}
|
||||
onPointerEnter={(event) => {
|
||||
if (isAssetSelectionMode && event.buttons === 1) {
|
||||
setSelectedAssetIds((currentIds) => {
|
||||
const nextIds = new Set(currentIds);
|
||||
nextIds.add(asset.id);
|
||||
return nextIds;
|
||||
});
|
||||
}
|
||||
}}
|
||||
onDragOver={(event) => {
|
||||
if (
|
||||
hasDataTransferType(
|
||||
event.dataTransfer,
|
||||
ASSET_DRAG_MIME_TYPE,
|
||||
)
|
||||
) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
setUploadDropTarget(null);
|
||||
updateAssetMoveDropFolder(asset.folderId);
|
||||
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, asset.folderId);
|
||||
return;
|
||||
}
|
||||
if (!event.dataTransfer.files.length) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
setUploadDropTarget(null);
|
||||
updateAssetMoveDropFolder(null);
|
||||
addUploadedFiles(event.dataTransfer.files, {
|
||||
folderId: asset.folderId,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
))}
|
||||
{isAssetSelectionMode ? (
|
||||
<PlatformBatchActionToolbar
|
||||
className="image-canvas-editor__asset-batch-toolbar"
|
||||
label="素材批量操作"
|
||||
>
|
||||
<PlatformActionButton
|
||||
tone="secondary"
|
||||
size="sm"
|
||||
onClick={toggleAllAssetsSelected}
|
||||
>
|
||||
{allSelectableAssetsSelected ? (
|
||||
<CheckSquare className="h-4 w-4" />
|
||||
) : (
|
||||
<Square className="h-4 w-4" />
|
||||
)}
|
||||
{selectedAssetIds.size > 0
|
||||
? `${allSelectableAssetsSelected ? '取消全选' : '全选'} · 已选 ${selectedAssetIds.size}`
|
||||
: '全选'}
|
||||
</PlatformActionButton>
|
||||
<PlatformActionButton
|
||||
tone="warning"
|
||||
size="sm"
|
||||
disabled={selectedAssetIds.size === 0}
|
||||
onClick={deleteSelectedAssets}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
删除
|
||||
</PlatformActionButton>
|
||||
<PlatformActionButton
|
||||
tone="secondary"
|
||||
size="sm"
|
||||
onClick={closeAssetSelectionMode}
|
||||
>
|
||||
取消
|
||||
</PlatformActionButton>
|
||||
</PlatformBatchActionToolbar>
|
||||
) : null}
|
||||
{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>
|
||||
) : (
|
||||
<div className="image-canvas-editor__layers-list">
|
||||
{layers
|
||||
.slice()
|
||||
.sort((left, right) => right.zIndex - left.zIndex)
|
||||
.map((layer) => (
|
||||
<SidebarMediaItem
|
||||
key={layer.id}
|
||||
title={layer.title}
|
||||
detail={[
|
||||
`${Math.round(layer.width)} x ${Math.round(layer.height)}`,
|
||||
layer.groupId ? '已打组' : null,
|
||||
layer.hidden ? '已隐藏' : null,
|
||||
layer.locked ? '已锁定' : null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' · ')}
|
||||
imageSrc={layer.src}
|
||||
imageAlt={`图层缩略图:${layer.title}`}
|
||||
selected={selectedLayerId === layer.id}
|
||||
primaryLabel={`选择图层${layer.title}`}
|
||||
onPrimaryClick={() => selectSingleLayer(layer.id)}
|
||||
rowClassName="image-canvas-editor__layer-row"
|
||||
primaryClassName="image-canvas-editor__layer-row-button"
|
||||
thumbnailClassName="image-canvas-editor__layer-row-thumb"
|
||||
metaClassName="image-canvas-editor__layer-row-meta"
|
||||
onContextMenu={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (!selectedLayerIds.includes(layer.id)) {
|
||||
selectSingleLayer(layer.id);
|
||||
}
|
||||
const position = resolveContextMenuPosition(
|
||||
event.clientX,
|
||||
event.clientY,
|
||||
'layer',
|
||||
);
|
||||
setImageContextMenu(null);
|
||||
setContextMenu({
|
||||
kind: 'layer',
|
||||
layerId: layer.id,
|
||||
...position,
|
||||
canvasPoint: getCanvasPointFromClient(
|
||||
event.clientX,
|
||||
event.clientY,
|
||||
),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</aside>
|
||||
) : null}
|
||||
<ImageCanvasSidebarView
|
||||
activeSidebarPanel={activeSidebarPanel}
|
||||
assetListRef={assetListRef}
|
||||
uploadInputRef={uploadInputRef}
|
||||
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}
|
||||
setUploadTarget={setUploadTarget}
|
||||
setUploadDropTarget={setUploadDropTarget}
|
||||
setAssetPointerDrag={setAssetPointerDrag}
|
||||
setSelectedAssetIds={setSelectedAssetIds}
|
||||
setImageContextMenu={setImageContextMenu}
|
||||
setContextMenu={setContextMenu}
|
||||
onAssetMarqueePointerDown={handleAssetMarqueePointerDown}
|
||||
onAssetMarqueePointerMove={handleAssetMarqueePointerMove}
|
||||
onAssetMarqueePointerUp={handleAssetMarqueePointerUp}
|
||||
updateAssetMoveDropFolder={updateAssetMoveDropFolder}
|
||||
addUploadedFiles={addUploadedFiles}
|
||||
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">
|
||||
<div className="image-canvas-editor__topbar">
|
||||
|
||||
Reference in New Issue
Block a user