From d8bd371c69f519d25a005d916ad767d36031d0ad Mon Sep 17 00:00:00 2001 From: kdletters Date: Wed, 17 Jun 2026 20:36:11 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8A=BD=E5=87=BA=E7=B4=A0=E6=9D=90=E4=B8=8A?= =?UTF-8?q?=E4=BC=A0=E7=94=9F=E5=91=BD=E5=91=A8=E6=9C=9F=E6=A8=A1=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 扩展 ImageCanvasUploadModel 承载素材上传占位、进度、失败和持久化回写状态迁移 精简 useImageCanvasUploadWorkflow 中的资产与图层状态补丁逻辑 补充上传模型单测覆盖生命周期状态和图层绑定 更新 TRACKING.md 记录第四十六执行批次与验证结果 --- TRACKING.md | 2 + .../ImageCanvasUploadModel.test.ts | 182 ++++++++++++++++++ .../image-editor/ImageCanvasUploadModel.ts | 179 +++++++++++++++++ .../useImageCanvasUploadWorkflow.ts | 132 +++++-------- 4 files changed, 409 insertions(+), 86 deletions(-) diff --git a/TRACKING.md b/TRACKING.md index b3e47ea1..366d27a8 100644 --- a/TRACKING.md +++ b/TRACKING.md @@ -171,3 +171,5 @@ - 2026-06-17 前端拆分第四十四执行批次验证补充:统一编辑器回归命令 `npm run test -- src/components/image-editor/ImageCanvasStageInteractionModel.test.ts src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.test.tsx src/components/image-editor/ImageCanvasGenerationDialogModel.test.ts src/components/image-editor/ImageCanvasAssetLibraryModel.test.ts src/components/image-editor/ImageCanvasUploadModel.test.ts src/components/image-editor/ImageCanvasGenerationSubmissionModel.test.ts src/components/image-editor/ImageCanvasGenerationLayerModel.test.ts src/components/image-editor/ImageCanvasAssetRowView.test.tsx src/components/image-editor/ImageCanvasSidebarView.test.tsx src/components/image-editor/ImageCanvasBasicGenerationComposerView.test.tsx src/components/image-editor/ImageCanvasCharacterGenerationComposerView.test.tsx src/components/image-editor/ImageCanvasEditGenerationModalView.test.tsx src/components/image-editor/ImageCanvasEditorShellView.test.tsx src/components/image-editor/ImageCanvasBottomToolbarView.test.tsx src/components/image-editor/ImageCanvasPanelDockView.test.tsx src/components/image-editor/ImageCanvasContextMenusView.test.tsx src/components/image-editor/ImageCanvasSelectedLayerToolbarView.test.tsx src/components/image-editor/ImageCanvasIconSpritesheetComposerView.test.tsx src/components/image-editor/ImageCanvasSpecGenerationPanelView.test.tsx src/components/image-editor/ImageCanvasGenerationImageOptionsView.test.tsx src/components/image-editor/ImageCanvasQuickEditPanelView.test.tsx src/components/image-editor/ImageCanvasCharacterAnimationPanelView.test.tsx src/components/image-editor/ImageCanvasWorldView.test.tsx src/components/image-editor/useImageCanvasAssetLibrary.test.tsx src/components/image-editor/useImageCanvasGenerationSurface.test.tsx src/components/image-editor/useImageCanvasGenerationWorkflow.test.tsx src/components/image-editor/useImageCanvasUploadWorkflow.test.tsx src/components/image-editor/useImageCanvasStageInteractions.test.tsx src/components/image-editor/useImageCanvasAssetPointerDragBridge.test.tsx src/components/image-editor/useImageCanvasAssetCanvasBridge.test.tsx src/components/image-editor/ImageCanvasEditorAssetsIntegration.test.tsx src/components/image-editor/ImageCanvasEditorGenerationIntegration.test.tsx src/components/image-editor/ImageCanvasEditorView.test.tsx` 通过 33 个文件 / 219 个测试;`npm run typecheck`、`npm run check:encoding`、`git diff --check` 均通过。浏览器回归:`http://127.0.0.1:10007/editor/canvas` 使用 Playwright CLI headless 打开成功,未登录显示 `账号入口`;关闭登录后 `画布背景设置` 可打开,点击 `暖灰` 后 viewport 背景为 `rgb(243, 240, 234)` 且 `background-image: none`;默认素材侧栏显示 `素材` / `项目素材` / `上传到项目素材`,切换 `打开图层` 后侧栏显示 `图层`,点击 `生成工具` 后 `Image Generator` 占位、`生成图片` 对话框和 `AI画布工具栏` 均可见;网络请求仅有预期未登录 `/api/auth/refresh` 401,其余 editor smoke 相关请求正常。 - 2026-06-17 前端拆分第四十五执行批次:继续收口 `useImageCanvasUploadWorkflow`,扩展 `ImageCanvasUploadModel`,把生成参考图上传后的 dialog 状态转换从 hook 中抽成纯模型:统一创建上传参考图对象、将 failed 生成面板恢复为 idle、按 `spec-reference` / `character-spec` / `character-reference` / `icon-spec` 写入对应引用字段,并保持不匹配 dialog 不变;upload workflow hook 继续保留文件筛选、Data URL 读取、文件 input、未登录素材上传续传、真实素材创建 API、上传进度和画布落图副作用。新增模型单测覆盖参考图 fallback label、failed 状态清理、规范 / 角色 / 图标参考图写入和角色参考图追加;局部验证命令:`npm run test -- src/components/image-editor/ImageCanvasUploadModel.test.ts src/components/image-editor/useImageCanvasUploadWorkflow.test.tsx`。 - 2026-06-17 前端拆分第四十五执行批次验证补充:统一编辑器回归命令 `npm run test -- src/components/image-editor/ImageCanvasStageInteractionModel.test.ts src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.test.tsx src/components/image-editor/ImageCanvasGenerationDialogModel.test.ts src/components/image-editor/ImageCanvasAssetLibraryModel.test.ts src/components/image-editor/ImageCanvasUploadModel.test.ts src/components/image-editor/ImageCanvasGenerationSubmissionModel.test.ts src/components/image-editor/ImageCanvasGenerationLayerModel.test.ts src/components/image-editor/ImageCanvasAssetRowView.test.tsx src/components/image-editor/ImageCanvasSidebarView.test.tsx src/components/image-editor/ImageCanvasBasicGenerationComposerView.test.tsx src/components/image-editor/ImageCanvasCharacterGenerationComposerView.test.tsx src/components/image-editor/ImageCanvasEditGenerationModalView.test.tsx src/components/image-editor/ImageCanvasEditorShellView.test.tsx src/components/image-editor/ImageCanvasBottomToolbarView.test.tsx src/components/image-editor/ImageCanvasPanelDockView.test.tsx src/components/image-editor/ImageCanvasContextMenusView.test.tsx src/components/image-editor/ImageCanvasSelectedLayerToolbarView.test.tsx src/components/image-editor/ImageCanvasIconSpritesheetComposerView.test.tsx src/components/image-editor/ImageCanvasSpecGenerationPanelView.test.tsx src/components/image-editor/ImageCanvasGenerationImageOptionsView.test.tsx src/components/image-editor/ImageCanvasQuickEditPanelView.test.tsx src/components/image-editor/ImageCanvasCharacterAnimationPanelView.test.tsx src/components/image-editor/ImageCanvasWorldView.test.tsx src/components/image-editor/useImageCanvasAssetLibrary.test.tsx src/components/image-editor/useImageCanvasGenerationSurface.test.tsx src/components/image-editor/useImageCanvasGenerationWorkflow.test.tsx src/components/image-editor/useImageCanvasUploadWorkflow.test.tsx src/components/image-editor/useImageCanvasStageInteractions.test.tsx src/components/image-editor/useImageCanvasAssetPointerDragBridge.test.tsx src/components/image-editor/useImageCanvasAssetCanvasBridge.test.tsx src/components/image-editor/ImageCanvasEditorAssetsIntegration.test.tsx src/components/image-editor/ImageCanvasEditorGenerationIntegration.test.tsx src/components/image-editor/ImageCanvasEditorView.test.tsx` 通过 33 个文件 / 223 个测试;`npm run typecheck`、`npm run check:encoding`、`git diff --check` 均通过。浏览器回归:`http://127.0.0.1:10007/editor/canvas` 使用 Playwright CLI headless 打开成功,未登录显示 `账号入口`;关闭登录后 `画布背景设置` 可打开,点击 `暖灰` 后 viewport 背景为 `rgb(243, 240, 234)` 且 `background-image: none`;默认素材侧栏显示 `素材` / `项目素材` / `上传到项目素材`,切换 `打开图层` 后侧栏显示 `图层`,点击 `生成工具` 后 `Image Generator` 占位、`生成图片` 对话框和 `AI画布工具栏` 均可见;网络请求仅有预期未登录 `/api/auth/refresh` 401,其余 editor smoke 相关请求正常。只读子代理复核生成提交链路后建议暂停继续硬拆,除非后续新增生成模式再考虑按“生成成功后的落图提交事务”抽内部 hook。 +- 2026-06-17 前端拆分第四十六执行批次:继续收口 `useImageCanvasUploadWorkflow`,扩展 `ImageCanvasUploadModel`,把素材上传生命周期中的文件夹展开、读取成功 / 失败、上传中、持久化资产替换、上传失败、图片尺寸回写和画布图层绑定持久化素材抽成纯模型;upload workflow hook 继续保留文件筛选、Data URL 读取、登录弹窗、真实素材创建 API、Image 尺寸读取、画布落图和 React setter 编排。新增模型单测覆盖上传占位卡各阶段状态、持久化后清理上传态、尺寸回写和图层绑定;局部验证命令:`npm run test -- src/components/image-editor/ImageCanvasUploadModel.test.ts src/components/image-editor/useImageCanvasUploadWorkflow.test.tsx`。 +- 2026-06-17 前端拆分第四十六执行批次验证补充:统一编辑器回归命令 `npm run test -- src/components/image-editor/ImageCanvasStageInteractionModel.test.ts src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.test.tsx src/components/image-editor/ImageCanvasGenerationDialogModel.test.ts src/components/image-editor/ImageCanvasAssetLibraryModel.test.ts src/components/image-editor/ImageCanvasUploadModel.test.ts src/components/image-editor/ImageCanvasGenerationSubmissionModel.test.ts src/components/image-editor/ImageCanvasGenerationLayerModel.test.ts src/components/image-editor/ImageCanvasAssetRowView.test.tsx src/components/image-editor/ImageCanvasSidebarView.test.tsx src/components/image-editor/ImageCanvasBasicGenerationComposerView.test.tsx src/components/image-editor/ImageCanvasCharacterGenerationComposerView.test.tsx src/components/image-editor/ImageCanvasEditGenerationModalView.test.tsx src/components/image-editor/ImageCanvasEditorShellView.test.tsx src/components/image-editor/ImageCanvasBottomToolbarView.test.tsx src/components/image-editor/ImageCanvasPanelDockView.test.tsx src/components/image-editor/ImageCanvasContextMenusView.test.tsx src/components/image-editor/ImageCanvasSelectedLayerToolbarView.test.tsx src/components/image-editor/ImageCanvasIconSpritesheetComposerView.test.tsx src/components/image-editor/ImageCanvasSpecGenerationPanelView.test.tsx src/components/image-editor/ImageCanvasGenerationImageOptionsView.test.tsx src/components/image-editor/ImageCanvasQuickEditPanelView.test.tsx src/components/image-editor/ImageCanvasCharacterAnimationPanelView.test.tsx src/components/image-editor/ImageCanvasWorldView.test.tsx src/components/image-editor/useImageCanvasAssetLibrary.test.tsx src/components/image-editor/useImageCanvasGenerationSurface.test.tsx src/components/image-editor/useImageCanvasGenerationWorkflow.test.tsx src/components/image-editor/useImageCanvasUploadWorkflow.test.tsx src/components/image-editor/useImageCanvasStageInteractions.test.tsx src/components/image-editor/useImageCanvasAssetPointerDragBridge.test.tsx src/components/image-editor/useImageCanvasAssetCanvasBridge.test.tsx src/components/image-editor/ImageCanvasEditorAssetsIntegration.test.tsx src/components/image-editor/ImageCanvasEditorGenerationIntegration.test.tsx src/components/image-editor/ImageCanvasEditorView.test.tsx` 通过 33 个文件 / 228 个测试;`npm run typecheck`、`npm run check:encoding`、`git diff --check` 均通过。浏览器回归:`http://127.0.0.1:10007/editor/canvas` 使用系统 Chrome headless 打开成功,未登录显示 `账号入口`;默认素材侧栏显示 `素材` / `项目素材`;关闭登录后 `画布背景设置` 可打开,点击 `暖灰` 后 viewport 背景为 `rgb(243, 240, 234)` 且 `background-image: none`;切换 `打开图层` 后侧栏显示 `图层`;点击 `生成工具` 后 `Image Generator` 占位、生成表单控件和 `AI画布工具栏` 均可见;除预期未登录 `/api/auth/refresh` 401 外无额外 API 失败或控制台错误。 diff --git a/src/components/image-editor/ImageCanvasUploadModel.test.ts b/src/components/image-editor/ImageCanvasUploadModel.test.ts index 663d289c..562b7714 100644 --- a/src/components/image-editor/ImageCanvasUploadModel.test.ts +++ b/src/components/image-editor/ImageCanvasUploadModel.test.ts @@ -7,9 +7,17 @@ import type { } from './ImageCanvasEditorTypes'; import { applyGenerationReferenceUpload, + applyMeasuredUploadAssetSize, + applyPersistedUploadAsset, + applyUploadAssetCreateFailure, + applyUploadAssetCreatePending, + applyUploadAssetReadFailure, + applyUploadAssetReadSuccess, + bindUploadLayerToPersistedAsset, createUploadCanvasLayer, createUploadedGenerationReference, createUploadingAssetPlaceholder, + expandUploadFolder, resizeUploadCanvasLayerToImage, resolveUploadFolderId, setFailedGenerationIdle, @@ -47,6 +55,19 @@ function createLayer(overrides: Partial = {}): CanvasLayer { }; } +function createUploadingAsset( + overrides: Partial> = {}, +) { + return { + ...createUploadingAssetPlaceholder({ + uploadIndex: 1, + fileName: '上传图片.png', + folderId: 'project', + }), + ...overrides, + }; +} + function createDialog( overrides: Partial = {}, ): GenerateDialogState { @@ -109,6 +130,167 @@ describe('ImageCanvasUploadModel', () => { }); }); + it('opens the target upload folder without changing other folders', () => { + expect( + expandUploadFolder({ + folders: [ + createFolder({ id: 'project', collapsed: true }), + createFolder({ id: 'characters', label: '角色素材', collapsed: true }), + ], + folderId: 'characters', + }), + ).toEqual([ + createFolder({ id: 'project', collapsed: true }), + createFolder({ id: 'characters', label: '角色素材', collapsed: false }), + ]); + }); + + it('applies upload asset lifecycle patches', () => { + const asset = createUploadingAsset(); + const otherAsset = createUploadingAsset({ + id: 'upload-other', + label: '其他图片.png', + }); + + expect( + applyUploadAssetReadSuccess({ + assets: [asset, otherAsset], + uploadAssetId: asset.id, + imageSrc: 'data:image/png;base64,read', + }), + ).toMatchObject([ + { + id: 'upload-1', + src: 'data:image/png;base64,read', + uploadProgress: 42, + uploadMessage: '读取图片', + }, + { id: 'upload-other', label: '其他图片.png' }, + ]); + + expect( + applyUploadAssetCreatePending({ + assets: [asset], + uploadAssetId: asset.id, + }), + ).toMatchObject([ + { + id: 'upload-1', + uploadStatus: 'uploading', + uploadProgress: 68, + uploadMessage: '上传中', + }, + ]); + + expect( + applyUploadAssetReadFailure({ + assets: [asset], + uploadAssetId: asset.id, + }), + ).toMatchObject([ + { + id: 'upload-1', + uploadStatus: 'failed', + uploadProgress: 100, + uploadMessage: '读取失败', + }, + ]); + + expect( + applyUploadAssetCreateFailure({ + assets: [asset], + uploadAssetId: asset.id, + message: '请先登录', + }), + ).toMatchObject([ + { + id: 'upload-1', + uploadStatus: 'failed', + uploadProgress: 100, + uploadMessage: '请先登录', + }, + ]); + }); + + it('replaces upload placeholders with persisted assets and clears upload state', () => { + expect( + applyPersistedUploadAsset({ + assets: [createUploadingAsset()], + uploadAssetId: 'upload-1', + persistedAsset: { + assetId: 'asset-1', + folderId: 'characters', + label: '角色.png', + imageSrc: 'data:image/png;base64,persisted', + width: 1024, + height: 768, + objectKey: 'object-key', + assetObjectId: 'asset-object', + }, + }), + ).toEqual([ + { + id: 'asset-1', + label: '角色.png', + src: 'data:image/png;base64,persisted', + width: 1024, + height: 768, + folderId: 'characters', + sourceKind: 'uploaded', + sourceType: 'uploaded', + persisted: true, + objectKey: 'object-key', + assetObjectId: 'asset-object', + uploadStatus: undefined, + uploadProgress: undefined, + uploadMessage: undefined, + }, + ]); + }); + + it('updates measured asset size after the browser loads the uploaded image', () => { + expect( + applyMeasuredUploadAssetSize({ + assets: [createUploadingAsset()], + uploadAssetId: 'upload-1', + originalWidth: 1600, + originalHeight: 900, + }), + ).toMatchObject([ + { + id: 'upload-1', + width: 1600, + height: 900, + }, + ]); + }); + + it('binds uploaded canvas layers to persisted asset metadata', () => { + expect( + bindUploadLayerToPersistedAsset({ + layers: [createLayer({ objectKey: 'local-object' })], + layerId: 'layer-upload-1', + persistedAsset: { + assetId: 'asset-1', + folderId: 'project', + label: '上传图片.png', + imageSrc: 'data:image/png;base64,persisted', + width: 420, + height: 315, + objectKey: 'object-key', + assetObjectId: 'asset-object', + }, + }), + ).toMatchObject([ + { + id: 'layer-upload-1', + sourceAssetId: 'asset-1', + objectKey: 'object-key', + assetObjectId: 'asset-object', + }, + ]); + }); + it('creates uploaded generation references with fallback labels', () => { vi.useFakeTimers(); vi.setSystemTime(new Date('2026-06-17T12:00:00.000Z')); diff --git a/src/components/image-editor/ImageCanvasUploadModel.ts b/src/components/image-editor/ImageCanvasUploadModel.ts index 5337c0b6..7d5a1026 100644 --- a/src/components/image-editor/ImageCanvasUploadModel.ts +++ b/src/components/image-editor/ImageCanvasUploadModel.ts @@ -10,6 +10,16 @@ import type { type CanvasSize = { width: number; height: number }; type CanvasPoint = { x: number; y: number }; +type PersistedUploadAsset = { + assetId: string; + folderId: string; + label: string; + imageSrc: string; + width: number; + height: number; + objectKey?: string | null; + assetObjectId?: string | null; +}; type GenerationReferenceUploadTarget = | 'character-reference' | 'character-spec' @@ -140,6 +150,175 @@ export function createUploadingAssetPlaceholder({ }; } +export function expandUploadFolder({ + folders, + folderId, +}: { + folders: EditorAssetFolder[]; + folderId: string; +}) { + return folders.map((folder) => + folder.id === folderId + ? { + ...folder, + collapsed: false, + } + : folder, + ); +} + +export function applyUploadAssetReadSuccess({ + assets, + uploadAssetId, + imageSrc, +}: { + assets: EditorAsset[]; + uploadAssetId: string; + imageSrc: string; +}) { + return assets.map((asset) => + asset.id === uploadAssetId + ? { + ...asset, + src: imageSrc, + uploadProgress: 42, + uploadMessage: '读取图片', + } + : asset, + ); +} + +export function applyUploadAssetReadFailure({ + assets, + uploadAssetId, +}: { + assets: EditorAsset[]; + uploadAssetId: string; +}) { + return assets.map((asset) => + asset.id === uploadAssetId + ? { + ...asset, + uploadStatus: 'failed' as const, + uploadProgress: 100, + uploadMessage: '读取失败', + } + : asset, + ); +} + +export function applyUploadAssetCreatePending({ + assets, + uploadAssetId, +}: { + assets: EditorAsset[]; + uploadAssetId: string; +}) { + return assets.map((asset) => + asset.id === uploadAssetId + ? { + ...asset, + uploadProgress: 68, + uploadMessage: '上传中', + } + : asset, + ); +} + +export function applyPersistedUploadAsset({ + assets, + uploadAssetId, + persistedAsset, +}: { + assets: EditorAsset[]; + uploadAssetId: string; + persistedAsset: PersistedUploadAsset; +}) { + return assets.map((asset) => + asset.id === uploadAssetId + ? { + ...asset, + id: persistedAsset.assetId, + folderId: persistedAsset.folderId, + label: persistedAsset.label, + src: persistedAsset.imageSrc, + width: persistedAsset.width, + height: persistedAsset.height, + objectKey: persistedAsset.objectKey ?? undefined, + assetObjectId: persistedAsset.assetObjectId ?? undefined, + persisted: true, + uploadStatus: undefined, + uploadProgress: undefined, + uploadMessage: undefined, + } + : asset, + ); +} + +export function applyUploadAssetCreateFailure({ + assets, + uploadAssetId, + message, +}: { + assets: EditorAsset[]; + uploadAssetId: string; + message: string; +}) { + return assets.map((asset) => + asset.id === uploadAssetId + ? { + ...asset, + uploadStatus: 'failed' as const, + uploadProgress: 100, + uploadMessage: message, + } + : asset, + ); +} + +export function applyMeasuredUploadAssetSize({ + assets, + uploadAssetId, + originalWidth, + originalHeight, +}: { + assets: EditorAsset[]; + uploadAssetId: string; + originalWidth: number; + originalHeight: number; +}) { + return assets.map((asset) => + asset.id === uploadAssetId + ? { + ...asset, + width: originalWidth, + height: originalHeight, + } + : asset, + ); +} + +export function bindUploadLayerToPersistedAsset({ + layers, + layerId, + persistedAsset, +}: { + layers: CanvasLayer[]; + layerId: string; + persistedAsset: PersistedUploadAsset; +}) { + return layers.map((layer) => + layer.id === layerId + ? { + ...layer, + sourceAssetId: persistedAsset.assetId, + objectKey: persistedAsset.objectKey ?? layer.objectKey, + assetObjectId: persistedAsset.assetObjectId ?? layer.assetObjectId, + } + : layer, + ); +} + export function normalizeUploadScreenPoint({ canvasPoint, canvasSize, diff --git a/src/components/image-editor/useImageCanvasUploadWorkflow.ts b/src/components/image-editor/useImageCanvasUploadWorkflow.ts index 2dc5fefb..61168d7c 100644 --- a/src/components/image-editor/useImageCanvasUploadWorkflow.ts +++ b/src/components/image-editor/useImageCanvasUploadWorkflow.ts @@ -21,9 +21,17 @@ import type { import { isImageFile, readImageFileAsDataUrl } from './ImageCanvasFileModel'; import { applyGenerationReferenceUpload, + applyMeasuredUploadAssetSize, + applyPersistedUploadAsset, + applyUploadAssetCreateFailure, + applyUploadAssetCreatePending, + applyUploadAssetReadFailure, + applyUploadAssetReadSuccess, + bindUploadLayerToPersistedAsset, createUploadCanvasLayer, createUploadedGenerationReference, createUploadingAssetPlaceholder, + expandUploadFolder, resizeUploadCanvasLayerToImage, resolveUploadFolderId, } from './ImageCanvasUploadModel'; @@ -232,43 +240,28 @@ export function useImageCanvasUploadWorkflow({ }); setAssets((currentAssets) => [...currentAssets, uploadedAsset]); setAssetFolders((currentFolders) => - currentFolders.map((folder) => - folder.id === uploadFolderId - ? { - ...folder, - collapsed: false, - } - : folder, - ), + expandUploadFolder({ + folders: currentFolders, + folderId: uploadFolderId, + }), ); let imageSrc = ''; try { imageSrc = await readImageFileAsDataUrl(file); setAssets((currentAssets) => - currentAssets.map((asset) => - asset.id === uploadedAsset.id - ? { - ...asset, - src: imageSrc, - uploadProgress: 42, - uploadMessage: '读取图片', - } - : asset, - ), + applyUploadAssetReadSuccess({ + assets: currentAssets, + uploadAssetId: uploadedAsset.id, + imageSrc, + }), ); } catch { setAssets((currentAssets) => - currentAssets.map((asset) => - asset.id === uploadedAsset.id - ? { - ...asset, - uploadStatus: 'failed', - uploadProgress: 100, - uploadMessage: '读取失败', - } - : asset, - ), + applyUploadAssetReadFailure({ + assets: currentAssets, + uploadAssetId: uploadedAsset.id, + }), ); return; } @@ -288,15 +281,10 @@ export function useImageCanvasUploadWorkflow({ } setAssets((currentAssets) => - currentAssets.map((asset) => - asset.id === uploadedAsset.id - ? { - ...asset, - uploadProgress: 68, - uploadMessage: '上传中', - } - : asset, - ), + applyUploadAssetCreatePending({ + assets: currentAssets, + uploadAssetId: uploadedAsset.id, + }), ); createEditorAsset({ folderId: uploadFolderId, @@ -308,39 +296,19 @@ export function useImageCanvasUploadWorkflow({ }) .then((asset) => { setAssets((currentAssets) => - currentAssets.map((currentAsset) => - currentAsset.id === uploadedAsset.id - ? { - ...currentAsset, - id: asset.assetId, - folderId: asset.folderId, - label: asset.label, - src: asset.imageSrc, - width: asset.width, - height: asset.height, - objectKey: asset.objectKey ?? undefined, - assetObjectId: asset.assetObjectId ?? undefined, - persisted: true, - uploadStatus: undefined, - uploadProgress: undefined, - uploadMessage: undefined, - } - : currentAsset, - ), + applyPersistedUploadAsset({ + assets: currentAssets, + uploadAssetId: uploadedAsset.id, + persistedAsset: asset, + }), ); if (options.addToCanvas) { setLayers((currentLayers) => - currentLayers.map((currentLayer) => - currentLayer.id === nextLayer.id - ? { - ...currentLayer, - sourceAssetId: asset.assetId, - objectKey: asset.objectKey ?? currentLayer.objectKey, - assetObjectId: - asset.assetObjectId ?? currentLayer.assetObjectId, - } - : currentLayer, - ), + bindUploadLayerToPersistedAsset({ + layers: currentLayers, + layerId: nextLayer.id, + persistedAsset: asset, + }), ); } }) @@ -350,16 +318,11 @@ export function useImageCanvasUploadWorkflow({ openEditorLoginModal(); } setAssets((currentAssets) => - currentAssets.map((asset) => - asset.id === uploadedAsset.id - ? { - ...asset, - uploadStatus: 'failed', - uploadProgress: 100, - uploadMessage: isAuthError ? '请先登录' : '上传失败', - } - : asset, - ), + applyUploadAssetCreateFailure({ + assets: currentAssets, + uploadAssetId: uploadedAsset.id, + message: isAuthError ? '请先登录' : '上传失败', + }), ); }); @@ -387,15 +350,12 @@ export function useImageCanvasUploadWorkflow({ ); } setAssets((currentAssets) => - currentAssets.map((asset) => - asset.id === uploadedAsset.id - ? { - ...asset, - width: originalWidth, - height: originalHeight, - } - : asset, - ), + applyMeasuredUploadAssetSize({ + assets: currentAssets, + uploadAssetId: uploadedAsset.id, + originalWidth, + originalHeight, + }), ); }; uploadedImage.src = imageSrc;