拆分编辑器上传模型
抽出上传文件夹解析和画布落点计算 补充上传模型单测 更新 TRACKING.md 记录第三十六阶段验证
This commit is contained in:
@@ -152,3 +152,4 @@
|
||||
- 2026-06-17 前端拆分第三十三阶段:继续收口 `ImageCanvasAssetLibraryPanelView`,新增 `ImageCanvasAssetFolderSectionView` 和 `ImageCanvasAssetRowView`,把素材库文件夹头 / 文件夹 drop 区域、素材卡片 / 上传进度 / 重命名 / 选择模式从素材库父面板中拆成两个完整 surface;素材库父面板降至 279 行,只保留素材列表容器、新建文件夹表单、批量操作栏和框选遮罩。新增素材行单测覆盖普通点击加入画布、选择模式改为选中、重命名 Enter 提交和上传中禁用 / 进度显示。统一验证命令:`npm run test -- 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/useImageCanvasGenerationSurface.test.tsx src/components/image-editor/useImageCanvasGenerationWorkflow.test.tsx src/components/image-editor/ImageCanvasEditorView.test.tsx`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`;浏览器回归:`http://127.0.0.1:10007/editor/canvas` 临时 `XDG_CONFIG_HOME` 下 Chrome headless 打开成功,未登录显示 `账号入口` 且控制台仅有预期 `/api/auth/refresh` 401;默认素材栏显示 `项目素材` 文件夹、上传入口和底部 `AI画布工具栏`;关闭登录后 `画布背景设置` 可打开,点击 `暖灰` 后 viewport 背景为 `rgb(243, 240, 234)` 且 `background-image: none`;`打开图层` 切换后侧栏显示 `图层`;点击 `生成工具` 后 `Image Generator`、`生成图片` 对话框和底部工具栏均可见。
|
||||
- 2026-06-17 前端拆分第三十四阶段:继续收口 `ImageCanvasStageView`,新增 `ImageCanvasWorldView`,把画布世界表面、吸附参考线、可见图层排序 / 悬浮 / 选中 / 锁定 / 生成态、元数据角标、框选矩形、生成占位框和浮动生成状态从 StageView 内联 JSX 中抽出;StageView 降至 324 行,继续保留 viewport 宿主、drop overlay、左下 dock、底部工具栏、右键菜单和选中图片工具栏装配。新增 world view 单测覆盖隐藏图层过滤、悬浮尺寸、生成态、元数据按钮、吸附线、框选矩形和生成占位框事件。统一验证命令:`npm run test -- src/components/image-editor/ImageCanvasWorldView.test.tsx`、`npm run test -- 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/useImageCanvasGenerationSurface.test.tsx src/components/image-editor/useImageCanvasGenerationWorkflow.test.tsx src/components/image-editor/ImageCanvasEditorView.test.tsx`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`;浏览器回归:`http://127.0.0.1:10007/editor/canvas` 临时 `XDG_CONFIG_HOME` 下 Chrome headless 打开成功,未登录显示 `账号入口`;关闭后 `画布背景设置` 可打开,点击 `暖灰` 后 viewport 背景为 `rgb(243, 240, 234)` 且 `background-image: none`,`AI画布工具栏` 保持可见;`打开图层` 切换后侧栏显示 `图层`;点击 `生成工具` 后 `Image Generator`、`生成图片` 对话框和底部工具栏均可见,控制台仅有预期的未登录 `/api/auth/refresh` 401。
|
||||
- 2026-06-17 前端拆分第三十五阶段:继续收口 `useImageCanvasGenerationWorkflow`,新增 `ImageCanvasGenerationSubmissionModel`,把普通生图、修改图片、规范生成和角色形象生成的请求 payload、标准化 prompt、结果图层标题 / assetKind 和 generationInputs 快照构建从 workflow hook 中抽成纯模型;workflow hook 保留对话状态、真实 API 调用、图片引用解析、结果落图、选中和 fit 副作用,避免拆散生成生命周期。新增模型单测覆盖普通生图、修改图、带参考图的规范生成、带规范 / 常规参考图的角色生成和缺失源图异常;`useImageCanvasGenerationWorkflow` 从 1167 行降至 1104 行。统一验证命令:`npm run test -- src/components/image-editor/ImageCanvasGenerationSubmissionModel.test.ts src/components/image-editor/useImageCanvasGenerationWorkflow.test.tsx`、`npm run test -- src/components/image-editor/ImageCanvasGenerationSubmissionModel.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/useImageCanvasGenerationSurface.test.tsx src/components/image-editor/useImageCanvasGenerationWorkflow.test.tsx src/components/image-editor/ImageCanvasEditorView.test.tsx`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`;浏览器回归:`http://127.0.0.1:10007/editor/canvas` 临时 `XDG_CONFIG_HOME` 下 Chrome headless 打开成功,未登录显示 `账号入口`;关闭后 `画布背景设置` 可打开,点击 `暖灰` 后 viewport 背景为 `rgb(243, 240, 234)` 且 `background-image: none`;点击 `生成工具` 后 `Image Generator`、`生成图片` 对话框和 `AI画布工具栏` 均可见,控制台仅有预期的未登录 `/api/auth/refresh` 401。
|
||||
- 2026-06-17 前端拆分第三十六阶段:继续收口 `useImageCanvasUploadWorkflow`,新增 `ImageCanvasUploadModel`,把上传目标文件夹解析、上传中素材占位卡、上传到画布的临时图层、无效 drop 坐标兜底和图片真实尺寸回填坐标计算从 hook 中抽成纯模型;upload workflow hook 保留登录恢复、文件读取、真实素材创建 API、上传进度状态和生成面板参考图写入副作用。新增模型单测覆盖文件夹兜底、占位素材、画布落点、非法坐标兜底和真实尺寸修正;`useImageCanvasUploadWorkflow` 从 546 行降至 510 行。统一验证命令:`npm run test -- src/components/image-editor/ImageCanvasUploadModel.test.ts src/components/image-editor/useImageCanvasUploadWorkflow.test.tsx`、`npm run test -- src/components/image-editor/ImageCanvasUploadModel.test.ts src/components/image-editor/ImageCanvasGenerationSubmissionModel.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/useImageCanvasGenerationSurface.test.tsx src/components/image-editor/useImageCanvasGenerationWorkflow.test.tsx src/components/image-editor/useImageCanvasUploadWorkflow.test.tsx src/components/image-editor/ImageCanvasEditorView.test.tsx`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`;浏览器回归:`http://127.0.0.1:10007/editor/canvas` 临时 `XDG_CONFIG_HOME` 下 Chrome headless 打开成功,未登录显示 `账号入口`;关闭后 `画布背景设置` 可打开,点击 `暖灰` 后 viewport 背景为 `rgb(243, 240, 234)` 且 `background-image: none`;点击 `生成工具` 后 `Image Generator`、`生成图片` 对话框、`上传到项目素材` 入口和 `AI画布工具栏` 均可见,控制台仅有预期的未登录 `/api/auth/refresh` 401。
|
||||
|
||||
151
src/components/image-editor/ImageCanvasUploadModel.test.ts
Normal file
151
src/components/image-editor/ImageCanvasUploadModel.test.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import type {
|
||||
CanvasLayer,
|
||||
EditorAssetFolder,
|
||||
} from './ImageCanvasEditorTypes';
|
||||
import {
|
||||
createUploadCanvasLayer,
|
||||
createUploadingAssetPlaceholder,
|
||||
resizeUploadCanvasLayerToImage,
|
||||
resolveUploadFolderId,
|
||||
} from './ImageCanvasUploadModel';
|
||||
|
||||
function createFolder(
|
||||
overrides: Partial<EditorAssetFolder> = {},
|
||||
): EditorAssetFolder {
|
||||
return {
|
||||
id: 'project',
|
||||
label: '项目素材',
|
||||
collapsed: false,
|
||||
systemDefault: true,
|
||||
persisted: true,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function createLayer(overrides: Partial<CanvasLayer> = {}): CanvasLayer {
|
||||
return {
|
||||
id: 'layer-upload-1',
|
||||
resourceId: 'local-resource-upload-1',
|
||||
title: '上传图片',
|
||||
src: 'data:image/png;base64,upload',
|
||||
x: -160,
|
||||
y: -107.5,
|
||||
width: 420,
|
||||
height: 315,
|
||||
originalWidth: 420,
|
||||
originalHeight: 315,
|
||||
zIndex: 11,
|
||||
sourceType: 'uploaded',
|
||||
sourceAssetId: 'upload-1',
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe('ImageCanvasUploadModel', () => {
|
||||
it('resolves upload folder ids with project fallback', () => {
|
||||
const folders = [
|
||||
createFolder(),
|
||||
createFolder({ id: 'characters', label: '角色素材' }),
|
||||
];
|
||||
|
||||
expect(
|
||||
resolveUploadFolderId({
|
||||
assetFolders: folders,
|
||||
requestedFolderId: 'characters',
|
||||
activeUploadFolderId: 'project',
|
||||
}),
|
||||
).toBe('characters');
|
||||
expect(
|
||||
resolveUploadFolderId({
|
||||
assetFolders: folders,
|
||||
requestedFolderId: 'missing',
|
||||
activeUploadFolderId: 'characters',
|
||||
}),
|
||||
).toBe('project');
|
||||
});
|
||||
|
||||
it('creates uploading asset placeholders', () => {
|
||||
expect(
|
||||
createUploadingAssetPlaceholder({
|
||||
uploadIndex: 7,
|
||||
fileName: 'hero.png',
|
||||
folderId: 'characters',
|
||||
}),
|
||||
).toEqual({
|
||||
id: 'upload-7',
|
||||
label: 'hero.png',
|
||||
src: '',
|
||||
width: 420,
|
||||
height: 315,
|
||||
folderId: 'characters',
|
||||
sourceKind: 'uploaded',
|
||||
sourceType: 'uploaded',
|
||||
persisted: false,
|
||||
uploadStatus: 'uploading',
|
||||
uploadProgress: 8,
|
||||
uploadMessage: '准备上传',
|
||||
});
|
||||
});
|
||||
|
||||
it('creates upload canvas layers centered on the target canvas point', () => {
|
||||
const layer = createUploadCanvasLayer({
|
||||
uploadIndex: 1,
|
||||
fileName: '画布素材.png',
|
||||
imageSrc: 'data:image/png;base64,canvas',
|
||||
canvasPoint: { x: 110, y: 120 },
|
||||
canvasSize: { width: 900, height: 640 },
|
||||
viewport: { x: 10, y: 20, scale: 2 },
|
||||
});
|
||||
|
||||
expect(layer).toMatchObject({
|
||||
id: 'layer-upload-1',
|
||||
resourceId: 'local-resource-upload-1',
|
||||
title: '画布素材.png',
|
||||
x: -160,
|
||||
y: -107.5,
|
||||
width: 420,
|
||||
height: 315,
|
||||
originalWidth: 420,
|
||||
originalHeight: 315,
|
||||
zIndex: 11,
|
||||
sourceAssetId: 'upload-1',
|
||||
});
|
||||
});
|
||||
|
||||
it('uses viewport center fallback for invalid upload points', () => {
|
||||
const layer = createUploadCanvasLayer({
|
||||
uploadIndex: 2,
|
||||
fileName: '',
|
||||
imageSrc: 'data:image/png;base64,canvas',
|
||||
canvasPoint: { x: Number.NaN, y: Number.POSITIVE_INFINITY },
|
||||
canvasSize: { width: 900, height: 640 },
|
||||
viewport: { x: 10, y: 20, scale: 2 },
|
||||
});
|
||||
|
||||
expect(layer.title).toBe('上传图片');
|
||||
expect(layer.x).toBe(10);
|
||||
expect(layer.y).toBe(-7.5);
|
||||
});
|
||||
|
||||
it('resizes upload canvas layers around the same canvas point', () => {
|
||||
const resizedLayer = resizeUploadCanvasLayerToImage({
|
||||
layer: createLayer(),
|
||||
originalWidth: 1536,
|
||||
originalHeight: 1024,
|
||||
canvasPoint: { x: 110, y: 120 },
|
||||
canvasSize: { width: 900, height: 640 },
|
||||
viewport: { x: 10, y: 20, scale: 2 },
|
||||
});
|
||||
|
||||
expect(resizedLayer).toMatchObject({
|
||||
width: 1536,
|
||||
height: 1024,
|
||||
originalWidth: 1536,
|
||||
originalHeight: 1024,
|
||||
x: -718,
|
||||
y: -462,
|
||||
});
|
||||
});
|
||||
});
|
||||
148
src/components/image-editor/ImageCanvasUploadModel.ts
Normal file
148
src/components/image-editor/ImageCanvasUploadModel.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
import { resolveLayerResolutionSize } from './ImageCanvasEditorModel';
|
||||
import type {
|
||||
CanvasLayer,
|
||||
CanvasViewport,
|
||||
EditorAsset,
|
||||
EditorAssetFolder,
|
||||
} from './ImageCanvasEditorTypes';
|
||||
|
||||
type CanvasSize = { width: number; height: number };
|
||||
type CanvasPoint = { x: number; y: number };
|
||||
|
||||
export const UPLOAD_LAYER_FALLBACK_SIZE = {
|
||||
width: 420,
|
||||
height: 315,
|
||||
} as const;
|
||||
|
||||
export function resolveUploadFolderId({
|
||||
assetFolders,
|
||||
requestedFolderId,
|
||||
activeUploadFolderId,
|
||||
}: {
|
||||
assetFolders: EditorAssetFolder[];
|
||||
requestedFolderId?: string;
|
||||
activeUploadFolderId: string;
|
||||
}) {
|
||||
const targetFolderId = requestedFolderId ?? activeUploadFolderId;
|
||||
return assetFolders.some((folder) => folder.id === targetFolderId)
|
||||
? targetFolderId
|
||||
: 'project';
|
||||
}
|
||||
|
||||
export function createUploadingAssetPlaceholder({
|
||||
uploadIndex,
|
||||
fileName,
|
||||
folderId,
|
||||
}: {
|
||||
uploadIndex: number;
|
||||
fileName?: string;
|
||||
folderId: string;
|
||||
}): EditorAsset {
|
||||
return {
|
||||
id: `upload-${uploadIndex}`,
|
||||
label: fileName || '上传图片',
|
||||
src: '',
|
||||
width: UPLOAD_LAYER_FALLBACK_SIZE.width,
|
||||
height: UPLOAD_LAYER_FALLBACK_SIZE.height,
|
||||
folderId,
|
||||
sourceKind: 'uploaded',
|
||||
sourceType: 'uploaded',
|
||||
persisted: false,
|
||||
uploadStatus: 'uploading',
|
||||
uploadProgress: 8,
|
||||
uploadMessage: '准备上传',
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeUploadScreenPoint({
|
||||
canvasPoint,
|
||||
canvasSize,
|
||||
}: {
|
||||
canvasPoint?: CanvasPoint;
|
||||
canvasSize: CanvasSize;
|
||||
}): CanvasPoint {
|
||||
const fallbackScreenPoint = {
|
||||
x: canvasSize.width > 0 ? canvasSize.width / 2 : 640,
|
||||
y: canvasSize.height > 0 ? canvasSize.height / 2 : 360,
|
||||
};
|
||||
const screenPoint = canvasPoint ?? fallbackScreenPoint;
|
||||
return {
|
||||
x: Number.isFinite(screenPoint.x) ? screenPoint.x : fallbackScreenPoint.x,
|
||||
y: Number.isFinite(screenPoint.y) ? screenPoint.y : fallbackScreenPoint.y,
|
||||
};
|
||||
}
|
||||
|
||||
export function createUploadCanvasLayer({
|
||||
uploadIndex,
|
||||
fileName,
|
||||
imageSrc,
|
||||
canvasPoint,
|
||||
canvasSize,
|
||||
viewport,
|
||||
}: {
|
||||
uploadIndex: number;
|
||||
fileName?: string;
|
||||
imageSrc: string;
|
||||
canvasPoint?: CanvasPoint;
|
||||
canvasSize: CanvasSize;
|
||||
viewport: CanvasViewport;
|
||||
}): CanvasLayer {
|
||||
const screenPoint = normalizeUploadScreenPoint({ canvasPoint, canvasSize });
|
||||
const safeScale = viewport.scale > 0 ? viewport.scale : 1;
|
||||
const worldCenterX = (screenPoint.x - viewport.x) / safeScale;
|
||||
const worldCenterY = (screenPoint.y - viewport.y) / safeScale;
|
||||
return {
|
||||
id: `layer-upload-${uploadIndex}`,
|
||||
resourceId: `local-resource-upload-${uploadIndex}`,
|
||||
title: fileName || '上传图片',
|
||||
src: imageSrc,
|
||||
x: worldCenterX - UPLOAD_LAYER_FALLBACK_SIZE.width / 2,
|
||||
y: worldCenterY - UPLOAD_LAYER_FALLBACK_SIZE.height / 2,
|
||||
width: UPLOAD_LAYER_FALLBACK_SIZE.width,
|
||||
height: UPLOAD_LAYER_FALLBACK_SIZE.height,
|
||||
originalWidth: UPLOAD_LAYER_FALLBACK_SIZE.width,
|
||||
originalHeight: UPLOAD_LAYER_FALLBACK_SIZE.height,
|
||||
zIndex: uploadIndex + 10,
|
||||
sourceType: 'uploaded',
|
||||
sourceAssetId: `upload-${uploadIndex}`,
|
||||
};
|
||||
}
|
||||
|
||||
export function resizeUploadCanvasLayerToImage({
|
||||
layer,
|
||||
originalWidth,
|
||||
originalHeight,
|
||||
canvasPoint,
|
||||
canvasSize,
|
||||
viewport,
|
||||
}: {
|
||||
layer: CanvasLayer;
|
||||
originalWidth: number;
|
||||
originalHeight: number;
|
||||
canvasPoint?: CanvasPoint;
|
||||
canvasSize: CanvasSize;
|
||||
viewport: CanvasViewport;
|
||||
}): CanvasLayer {
|
||||
const safeOriginalWidth =
|
||||
originalWidth || UPLOAD_LAYER_FALLBACK_SIZE.width;
|
||||
const safeOriginalHeight =
|
||||
originalHeight || UPLOAD_LAYER_FALLBACK_SIZE.height;
|
||||
const { width, height } = resolveLayerResolutionSize(
|
||||
safeOriginalWidth,
|
||||
safeOriginalHeight,
|
||||
UPLOAD_LAYER_FALLBACK_SIZE,
|
||||
);
|
||||
const screenPoint = normalizeUploadScreenPoint({ canvasPoint, canvasSize });
|
||||
const safeScale = viewport.scale > 0 ? viewport.scale : 1;
|
||||
const worldCenterX = (screenPoint.x - viewport.x) / safeScale;
|
||||
const worldCenterY = (screenPoint.y - viewport.y) / safeScale;
|
||||
return {
|
||||
...layer,
|
||||
width,
|
||||
height,
|
||||
originalWidth: safeOriginalWidth,
|
||||
originalHeight: safeOriginalHeight,
|
||||
x: worldCenterX - width / 2,
|
||||
y: worldCenterY - height / 2,
|
||||
};
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
|
||||
import { ApiClientError } from '../../services/apiClient';
|
||||
import { createEditorAsset } from '../../services/image-editor/editorProjectClient';
|
||||
import { resolveLayerResolutionSize } from './ImageCanvasEditorModel';
|
||||
import type {
|
||||
CanvasLayer,
|
||||
CanvasTool,
|
||||
@@ -20,6 +19,12 @@ import type {
|
||||
UploadTarget,
|
||||
} from './ImageCanvasEditorTypes';
|
||||
import { isImageFile, readImageFileAsDataUrl } from './ImageCanvasFileModel';
|
||||
import {
|
||||
createUploadCanvasLayer,
|
||||
createUploadingAssetPlaceholder,
|
||||
resizeUploadCanvasLayerToImage,
|
||||
resolveUploadFolderId,
|
||||
} from './ImageCanvasUploadModel';
|
||||
|
||||
type UploadFilesOptions = {
|
||||
folderId?: string;
|
||||
@@ -210,28 +215,17 @@ export function useImageCanvasUploadWorkflow({
|
||||
return;
|
||||
}
|
||||
|
||||
const fallbackWidth = 420;
|
||||
const fallbackHeight = 315;
|
||||
const uploadFolderId = assetFolders.some(
|
||||
(folder) => folder.id === (options.folderId ?? activeUploadFolderId),
|
||||
)
|
||||
? (options.folderId ?? activeUploadFolderId)
|
||||
: 'project';
|
||||
const uploadFolderId = resolveUploadFolderId({
|
||||
assetFolders,
|
||||
requestedFolderId: options.folderId,
|
||||
activeUploadFolderId,
|
||||
});
|
||||
const uploadIndex = options.uploadIndex;
|
||||
const uploadedAsset: EditorAsset = {
|
||||
id: `upload-${uploadIndex}`,
|
||||
label: file.name || '上传图片',
|
||||
src: '',
|
||||
width: fallbackWidth,
|
||||
height: fallbackHeight,
|
||||
const uploadedAsset = createUploadingAssetPlaceholder({
|
||||
uploadIndex,
|
||||
fileName: file.name,
|
||||
folderId: uploadFolderId,
|
||||
sourceKind: 'uploaded',
|
||||
sourceType: 'uploaded',
|
||||
persisted: false,
|
||||
uploadStatus: 'uploading',
|
||||
uploadProgress: 8,
|
||||
uploadMessage: '准备上传',
|
||||
};
|
||||
});
|
||||
setAssets((currentAssets) => [...currentAssets, uploadedAsset]);
|
||||
setAssetFolders((currentFolders) =>
|
||||
currentFolders.map((folder) =>
|
||||
@@ -275,40 +269,14 @@ export function useImageCanvasUploadWorkflow({
|
||||
return;
|
||||
}
|
||||
|
||||
const screenPoint = options.canvasPoint ?? {
|
||||
x: canvasSize.width / 2,
|
||||
y: canvasSize.height / 2,
|
||||
};
|
||||
const fallbackScreenPoint = {
|
||||
x: canvasSize.width > 0 ? canvasSize.width / 2 : 640,
|
||||
y: canvasSize.height > 0 ? canvasSize.height / 2 : 360,
|
||||
};
|
||||
const normalizedScreenPoint = {
|
||||
x: Number.isFinite(screenPoint.x)
|
||||
? screenPoint.x
|
||||
: fallbackScreenPoint.x,
|
||||
y: Number.isFinite(screenPoint.y)
|
||||
? screenPoint.y
|
||||
: fallbackScreenPoint.y,
|
||||
};
|
||||
const safeScale = viewport.scale > 0 ? viewport.scale : 1;
|
||||
const worldCenterX = (normalizedScreenPoint.x - viewport.x) / safeScale;
|
||||
const worldCenterY = (normalizedScreenPoint.y - viewport.y) / safeScale;
|
||||
const nextLayer: CanvasLayer = {
|
||||
id: `layer-upload-${uploadIndex}`,
|
||||
resourceId: `local-resource-upload-${uploadIndex}`,
|
||||
title: file.name || '上传图片',
|
||||
src: imageSrc,
|
||||
x: worldCenterX - fallbackWidth / 2,
|
||||
y: worldCenterY - fallbackHeight / 2,
|
||||
width: fallbackWidth,
|
||||
height: fallbackHeight,
|
||||
originalWidth: fallbackWidth,
|
||||
originalHeight: fallbackHeight,
|
||||
zIndex: uploadIndex + 10,
|
||||
sourceType: 'uploaded',
|
||||
sourceAssetId: `upload-${uploadIndex}`,
|
||||
};
|
||||
const nextLayer = createUploadCanvasLayer({
|
||||
uploadIndex,
|
||||
fileName: file.name,
|
||||
imageSrc,
|
||||
canvasPoint: options.canvasPoint,
|
||||
canvasSize,
|
||||
viewport,
|
||||
});
|
||||
|
||||
if (options.addToCanvas) {
|
||||
appendCanvasLayersWithResources([nextLayer]);
|
||||
@@ -330,8 +298,8 @@ export function useImageCanvasUploadWorkflow({
|
||||
folderId: uploadFolderId,
|
||||
label: uploadedAsset.label,
|
||||
imageSrc,
|
||||
width: fallbackWidth,
|
||||
height: fallbackHeight,
|
||||
width: uploadedAsset.width,
|
||||
height: uploadedAsset.height,
|
||||
sourceType: 'uploaded',
|
||||
})
|
||||
.then((asset) => {
|
||||
@@ -394,26 +362,22 @@ export function useImageCanvasUploadWorkflow({
|
||||
if (imageSrc) {
|
||||
const uploadedImage = new Image();
|
||||
uploadedImage.onload = () => {
|
||||
const originalWidth = uploadedImage.naturalWidth || fallbackWidth;
|
||||
const originalHeight = uploadedImage.naturalHeight || fallbackHeight;
|
||||
const { width, height } = resolveLayerResolutionSize(
|
||||
originalWidth,
|
||||
originalHeight,
|
||||
{ width: fallbackWidth, height: fallbackHeight },
|
||||
);
|
||||
const originalWidth =
|
||||
uploadedImage.naturalWidth || uploadedAsset.width;
|
||||
const originalHeight =
|
||||
uploadedImage.naturalHeight || uploadedAsset.height;
|
||||
if (options.addToCanvas) {
|
||||
setLayers((currentLayers) =>
|
||||
currentLayers.map((layer) =>
|
||||
layer.id === nextLayer.id
|
||||
? {
|
||||
...layer,
|
||||
width,
|
||||
height,
|
||||
? resizeUploadCanvasLayerToImage({
|
||||
layer,
|
||||
originalWidth,
|
||||
originalHeight,
|
||||
x: worldCenterX - width / 2,
|
||||
y: worldCenterY - height / 2,
|
||||
}
|
||||
canvasPoint: options.canvasPoint,
|
||||
canvasSize,
|
||||
viewport,
|
||||
})
|
||||
: layer,
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user