{
- if (!event.currentTarget.contains(event.relatedTarget as Node | null)) {
- setUploadDropTarget((currentTarget) =>
- currentTarget === 'assets' ? null : currentTarget,
- );
- updateAssetMoveDropFolder(null);
- }
- }}
- >
- {assetMoveDropFolder && isAssetMoveDropHeaderPinned ? (
-
-
-
{assetMoveDropFolder.label}
+ className="image-canvas-editor__asset-list"
+ onPointerDown={handleAssetMarqueePointerDown}
+ onPointerMove={handleAssetMarqueePointerMove}
+ onPointerUp={handleAssetMarqueePointerUp}
+ onPointerCancel={handleAssetMarqueePointerUp}
+ >
+ {pinnedAssetMoveFolderId ? (
+
+
+
+ {assetFolders.find(
+ (folder) => folder.id === pinnedAssetMoveFolderId,
+ )?.label ?? '目标文件夹'}
+
) : null}
{creatingFolder ? (
@@ -3967,9 +5442,15 @@ export function ImageCanvasEditorView() {
aria-label={folder.label}
data-asset-folder-id={folder.id}
onDragOver={(event) => {
- if (hasDataTransferType(event.dataTransfer, ASSET_DRAG_MIME_TYPE)) {
+ if (
+ hasDataTransferType(
+ event.dataTransfer,
+ ASSET_DRAG_MIME_TYPE,
+ )
+ ) {
event.preventDefault();
event.stopPropagation();
+ setUploadDropTarget(null);
updateAssetMoveDropFolder(folder.id);
event.dataTransfer.dropEffect = 'move';
return;
@@ -3998,7 +5479,9 @@ export function ImageCanvasEditorView() {
event.stopPropagation();
setUploadDropTarget(null);
updateAssetMoveDropFolder(null);
- addUploadedFiles(event.dataTransfer.files, { folderId: folder.id });
+ addUploadedFiles(event.dataTransfer.files, {
+ folderId: folder.id,
+ });
}}
>
{
setActiveUploadFolderId(folder.id);
+ setUploadTarget('asset');
uploadInputRef.current?.click();
}}
/>
@@ -4089,9 +5573,14 @@ export function ImageCanvasEditorView() {
>
{folder.assets.map((asset) => {
const isRenaming = renamingAsset?.assetId === asset.id;
- const isUploadingAsset = asset.uploadStatus === 'uploading';
+ const isUploadingAsset =
+ asset.uploadStatus === 'uploading';
const isFailedUpload = asset.uploadStatus === 'failed';
- const uploadProgress = clamp(asset.uploadProgress ?? 0, 0, 100);
+ const uploadProgress = clamp(
+ asset.uploadProgress ?? 0,
+ 0,
+ 100,
+ );
const titleNode = isRenaming ? (
{
if (isUploadingAsset || isFailedUpload) {
@@ -4208,7 +5697,9 @@ export function ImageCanvasEditorView() {
)
}
actions={actions}
- draggable={!isRenaming && !isUploadingAsset && !isFailedUpload}
+ draggable={
+ !isRenaming && !isUploadingAsset && !isFailedUpload
+ }
previewOverlay={
isUploadingAsset ? (
@@ -4236,11 +5727,18 @@ export function ImageCanvasEditorView() {
) : undefined
}
onDragStart={(event) => {
- if (isRenaming || isUploadingAsset || isFailedUpload) {
+ if (
+ isRenaming ||
+ isUploadingAsset ||
+ isFailedUpload
+ ) {
event.preventDefault();
return;
}
- if (assetPointerDragRef.current?.assetId === asset.id) {
+ if (
+ assetPointerDragRef.current?.assetId ===
+ asset.id
+ ) {
event.preventDefault();
return;
}
@@ -4249,11 +5747,18 @@ export function ImageCanvasEditorView() {
ASSET_DRAG_MIME_TYPE,
asset.id,
);
- event.dataTransfer.setData('text/plain', asset.label);
- event.dataTransfer.setData('text/uri-list', asset.src);
+ 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) => {
@@ -4265,18 +5770,14 @@ export function ImageCanvasEditorView() {
if (target.closest('button')) {
return;
}
- event.currentTarget.setPointerCapture?.(event.pointerId);
- event.preventDefault();
- event.stopPropagation();
- handleAssetMarqueePointerDown(
- event as ReactPointerEvent
,
- );
return;
}
- if (target.closest('input, textarea, select')) {
- return;
- }
- if (isUploadingAsset || isFailedUpload) {
+ if (
+ isRenaming ||
+ isUploadingAsset ||
+ isFailedUpload ||
+ target.closest('input, textarea, select')
+ ) {
return;
}
const primaryAssetButton = target.closest(
@@ -4296,9 +5797,13 @@ export function ImageCanvasEditorView() {
dropFolderId: null,
};
if (!primaryAssetButton) {
- event.currentTarget.setPointerCapture?.(
- event.pointerId,
- );
+ try {
+ event.currentTarget.setPointerCapture?.(
+ event.pointerId,
+ );
+ } catch {
+ // 自动化环境可能没有 active pointer,拖拽状态仍可走 window 事件完成。
+ }
event.preventDefault();
}
event.stopPropagation();
@@ -4315,9 +5820,15 @@ export function ImageCanvasEditorView() {
}
}}
onDragOver={(event) => {
- if (hasDataTransferType(event.dataTransfer, ASSET_DRAG_MIME_TYPE)) {
+ if (
+ hasDataTransferType(
+ event.dataTransfer,
+ ASSET_DRAG_MIME_TYPE,
+ )
+ ) {
event.preventDefault();
event.stopPropagation();
+ setUploadDropTarget(null);
updateAssetMoveDropFolder(asset.folderId);
event.dataTransfer.dropEffect = 'move';
return;
@@ -4403,20 +5914,15 @@ export function ImageCanvasEditorView() {
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),
+ width: Math.abs(
+ assetMarquee.currentX - assetMarquee.startX,
+ ),
+ height: Math.abs(
+ assetMarquee.currentY - assetMarquee.startY,
+ ),
}}
/>
) : null}
- {uploadDropTarget === 'assets' ? (
-
- 添加到素材
- 松开即可添加
-
- ) : null}
) : (
@@ -4425,27 +5931,48 @@ export function ImageCanvasEditorView() {
.sort((left, right) => right.zIndex - left.zIndex)
.map((layer) => (
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) => handleLayerContextMenu(event, layer)}
- />
+ 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,
+ ),
+ });
+ }}
+ />
))}
)}
@@ -4454,15 +5981,15 @@ export function ImageCanvasEditorView() {
-
-
-
-
+
+
+
+
{isRenamingProject ? (
) : (
-
-
-
-
-
-
画布
+
+
+
- )}
-
+ )}
+
画布
+
+
layer.src.trim().length > 0)
+ }
+ onClick={() => void exportCanvasAssets()}
+ />
{assetExportStatus ? (
) : null}
- isLayerValidForAssetLibrary(layer, assets))
- }
- onClick={() => {
- void exportCanvasAssets();
- }}
- />
-
+
{
+ event.preventDefault();
+ event.stopPropagation();
+ const position = resolveContextMenuPosition(
+ event.clientX,
+ event.clientY,
+ 'blank',
+ );
+ setImageContextMenu(null);
+ setContextMenu({
+ kind: 'blank',
+ ...position,
+ canvasPoint: getCanvasPointFromClient(event.clientX, event.clientY),
+ });
+ }}
>
{uploadDropTarget === 'canvas' ? (
{
const isSelected = selectedLayerIds.includes(layer.id);
const isHovered = hoveredLayerId === layer.id;
+ const kindLabel = getLayerKindLabel(layer);
+ const layerGeneratingLabel =
+ generateDialog?.mode === 'edit' &&
+ generateDialog.status === 'generating' &&
+ generateDialog.sourceLayerId === layer.id
+ ? '修改中'
+ : quickEditPanel?.status === 'generating' &&
+ quickEditPanel.sourceLayerId === layer.id
+ ? '生成中'
+ : null;
return (
);
})}
@@ -4673,53 +6288,107 @@ export function ImageCanvasEditorView() {
className="image-canvas-editor__canvas-marquee"
aria-hidden="true"
style={{
- left: (Math.min(canvasMarquee.startX, canvasMarquee.currentX) - viewport.x) / viewport.scale,
- top: (Math.min(canvasMarquee.startY, canvasMarquee.currentY) - viewport.y) / viewport.scale,
- width: Math.abs(canvasMarquee.currentX - canvasMarquee.startX) / viewport.scale,
- height: Math.abs(canvasMarquee.currentY - canvasMarquee.startY) / viewport.scale,
+ left:
+ (Math.min(canvasMarquee.startX, canvasMarquee.currentX) -
+ viewport.x) /
+ viewport.scale,
+ top:
+ (Math.min(canvasMarquee.startY, canvasMarquee.currentY) -
+ viewport.y) /
+ viewport.scale,
+ width:
+ Math.abs(canvasMarquee.currentX - canvasMarquee.startX) /
+ viewport.scale,
+ height:
+ Math.abs(canvasMarquee.currentY - canvasMarquee.startY) /
+ viewport.scale,
}}
/>
) : null}
- {generateDialog?.mode === 'generate' && generateDialog.placeholder ? (
-
- setGenerateDialog((currentDialog) =>
- currentDialog?.mode === 'generate'
- ? {
- ...currentDialog,
- composerOpen: true,
- }
- : currentDialog,
- )
- }
+ {canvasGenerationDialogs.map((dialog) =>
+ dialog.placeholder ? (
+
+ handleGenerationFramePointerDown(event, dialog)
+ }
+ onDoubleClick={() => activateCanvasGenerationDialog(dialog)}
+ >
+
+
+ {getGenerationFrameLabel(dialog)}
+
+ {dialog.mode === 'character' ? (
+
+ 角色
+
+ ) : null}
+ {dialog.mode === 'spec' ? (
+
+ 规范
+
+ ) : null}
+ {dialog.mode === 'icon' ? (
+
+ 图标
+
+ ) : null}
+
+ {dialog.placeholder.originalWidth} x{' '}
+ {dialog.placeholder.originalHeight}
+
+
+
+
+ {dialog.status === 'generating' ? (
+
+ 生成中
+
+ ) : null}
+
+ ) : null,
+ )}
+ {(generateDialog?.mode === 'generate' ||
+ generateDialog?.mode === 'spec' ||
+ generateDialog?.mode === 'character' ||
+ generateDialog?.mode === 'icon') &&
+ generateDialog.status === 'generating' &&
+ generationComposerStyle ? (
+
-
-
- Image Generator
-
-
- {generateDialog.placeholder.originalWidth} x{' '}
- {generateDialog.placeholder.originalHeight}
-
-
-
-
-
+ 生成中
+
) : null}
- {selectedLayer && !selectedLayer.hidden && selectedToolbarStyle ? (
+ {selectedLayer && selectedToolbarStyle ? (
- {!hasMultipleSelectedLayers && isGeneratedLayer(selectedLayer) ? (
+
openQuickEditPanel(selectedLayer)}
+ />
+ {isGeneratedLayer(selectedLayer) ? (
<>
setMetadataLayer(selectedLayer)}
/>
@@ -4758,6 +6433,14 @@ export function ImageCanvasEditorView() {
/>
>
) : null}
+ {selectedLayer.assetKind === 'character' ? (
+ openCharacterAnimationPanel(selectedLayer)}
+ />
+ ) : null}
) : null}
@@ -4766,7 +6449,7 @@ export function ImageCanvasEditorView() {
className="image-canvas-editor__context-menu"
role="menu"
aria-label={
- contextMenu.kind === 'blank' ? '画布右键菜单' : '图层右键菜单'
+ contextMenu.kind === 'blank' ? '画布右键菜单' : '图片功能面板'
}
style={{
left: contextMenu.x,
@@ -4847,17 +6530,13 @@ export function ImageCanvasEditorView() {
下移一层
-
{isMinimapOpen && minimapModel ? (
@@ -5107,19 +6837,58 @@ export function ImageCanvasEditorView() {
aria-label="AI画布工具栏"
onPointerDown={(event) => event.stopPropagation()}
>
- {canvasTools.map(({ id, label, icon: Icon }) => (
- switchTool(id)}
- />
- ))}
+ {canvasTools.map(({ id, label, icon: Icon }) =>
+ id === 'spec' ? (
+
+ switchTool(id)}
+ />
+
+ ) : (
+ switchTool(id)}
+ />
+ ),
+ )}
- {generateDialog?.mode === 'generate' && generationComposerStyle ? (
+ {isSpecMenuOpen
+ ? renderEditorPortal(
+
+ {(['character', 'ui', 'custom'] as const).map((specType) => (
+ openSpecDialog(specType)}
+ >
+ {SPEC_TYPE_LABEL[specType]}
+
+ ))}
+ ,
+ )
+ : null}
+
+ {generateDialog?.mode === 'generate' &&
+ generateDialog.composerOpen !== false &&
+ generationComposerStyle ? (
) : null}
+
+ {generateDialog?.mode === 'spec' &&
+ generateDialog.composerOpen !== false &&
+ generationComposerStyle ? (
+
+ ) : null}
+
+ {generateDialog?.mode === 'character' && generationComposerStyle ? (
+
+ ) : null}
+
+ {generateDialog?.mode === 'icon' &&
+ generateDialog.composerOpen !== false &&
+ iconComposerStyle ? (
+
+ ) : null}
+
+ {isPickingCharacterSpecFromCanvas ? (
+
+ 请选择画布中的图片作为角色形象规范,按 Esc 退出
+
+ ) : null}
+ {isPickingIconSpecFromCanvas ? (
+
+ 请选择画布中的图标素材规范,按 Esc 退出
+
+ ) : null}
+
+ {quickEditPanel &&
+ quickEditPanel.status !== 'generating' &&
+ quickEditSourceLayer &&
+ quickEditPanelStyle ? (
+
+ ) : null}
+
+ {imageContextMenu && imageContextMenuLayer && !contextMenu ? (
+
event.stopPropagation()}
+ >
+
+ openQuickEditPanel(imageContextMenuLayer)}
+ >
+ 快速编辑
+
+ {
+ setMetadataLayer(imageContextMenuLayer);
+ setImageContextMenu(null);
+ }}
+ >
+ 查看图片信息
+
+ {imageContextMenuLayer.assetKind === 'character' ? (
+
+ openCharacterAnimationPanel(imageContextMenuLayer)
+ }
+ >
+ 生成动画
+
+ ) : null}
+ deleteLayerById(imageContextMenuLayer.id)}
+ >
+ 删除图片
+
+
+
+ ) : null}
+
+ {characterAnimationPanel &&
+ characterAnimationSourceLayer &&
+ characterAnimationPanelStyle ? (
+
+ ) : null}