抽出素材上传生命周期模型
扩展 ImageCanvasUploadModel 承载素材上传占位、进度、失败和持久化回写状态迁移 精简 useImageCanvasUploadWorkflow 中的资产与图层状态补丁逻辑 补充上传模型单测覆盖生命周期状态和图层绑定 更新 TRACKING.md 记录第四十六执行批次与验证结果
This commit is contained in:
@@ -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 失败或控制台错误。
|
||||
|
||||
@@ -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> = {}): CanvasLayer {
|
||||
};
|
||||
}
|
||||
|
||||
function createUploadingAsset(
|
||||
overrides: Partial<ReturnType<typeof createUploadingAssetPlaceholder>> = {},
|
||||
) {
|
||||
return {
|
||||
...createUploadingAssetPlaceholder({
|
||||
uploadIndex: 1,
|
||||
fileName: '上传图片.png',
|
||||
folderId: 'project',
|
||||
}),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function createDialog(
|
||||
overrides: Partial<GenerateDialogState> = {},
|
||||
): 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'));
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user