Files
Genarrative/apps/ai-game-creator-shell/src/view/project-development/useResourceAssetDeleteFlow.ts
T
suzmii d1581892d7 修复 PR #316 review(前端 TS/TSX):资源工作台与资源画布
- 素材重命名(跨范围契约):`rename_local_project_asset` 现在要求 `expectedProjectId` + `expectedProjectRevision`,前端按删除 / 分类保存同一套 CAS 口径补齐——先 `get_local_game_project_revision` 读当前 revision 再连同项目身份提交,不用可能过期的本地缓存值
- 失败文案统一:新增 `projectAssetCommandErrorMessage`,把 `project-identity-conflict` / `project-revision-conflict` 翻成用户可读中文,重命名 / 删除 / 分类保存三条链路共用一份映射,其余错误原样透出
- index.tsx:资源面板下载按 `resolveDownloadablePanelEntries` 过滤,版本卡这类合成 path 的条目不再进入落盘链路
- index.tsx:`characterAnimationPanel` 纳入画布浮层开关判据(抽 `resolveResourceCanvasFloatingPanelOpen`),「生成动画」面板与快速编辑 / 信息浮层共用同一条「点外部 / Esc 关闭」规则
- index.tsx:去掉 `ResourceBookScene` 上重复的 `onWheel`,滚轮只由 manager 上的原生 `passive:false` 监听处理,平移 / 缩放不再被翻倍
- index.tsx:快速编辑的源资源在层 id 命中不到时回落到已正规化的 `asset:<id>`,重试不再静默什么都不做;取不到时给出可见失败
- ResourceCanvasPanelView.tsx:下载按钮按可下载条数判可用、不再在上传中显示下载转圈;上传中禁掉 × / Esc / 遮罩三条关闭路径;预览占位文案抽 helper
- GameRunVersionPicker.tsx:portal 菜单在滚动 / 缩放后重算位置;外部点击判定沿用共享 `useImageCanvasFloatingOptionDismiss` + `menuRef` 边界(review 该条已修,保留现状并补位置用例)
- ResourceCanvasGenerationPanelView.tsx:`submitting` 改 `finally` 收回,成功路径不再永久锁住面板
- ResourceAssetDeleteDialog.tsx / ResourceRenameDialog.tsx / ResourceClassificationPanel.tsx:在飞时 `closeOnEscape` / `closeOnBackdrop` 与头部 × 一起挡住
- resourceEditModel.ts / resourceCanvasToolbarModel.ts / useProjectResourceCardPreviews.ts / projectResourceLiveUpdateModel.ts:抽 `resourceBaseName`、`canonicalProjectedResourceMediaType` 参数收窄去掉强转、删未使用参数、补「IO 失败会 reject」的签名说明
- resourceCanvasHistoryModel.ts:`resourceCanvasSnapshotsEqual` 不再比较不可恢复的 `manuallyPlaced`,消除「压进历史但撤销是空操作」
- resourceCanvasSectionMapping.ts:删掉从未接线、且注释与实现相矛盾的 `LEGACY_RESOURCE_CANVAS_SECTION_FALLBACK`
- resourceCanvasChrome.css:`.game-resource-panel-grid` 补 `flex: 1 1 auto; min-height: 0`,卡片网格成为真正的滚动区
- 用例:重命名 CAS 载荷与读序、CAS 拒绝文案、版本卡下载门禁、上传中关闭路径、浮层判据、菜单跟随滚动、分区映射
2026-09-12 20:49:03 +08:00

172 lines
5.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useCallback, useState } from 'react';
import { projectAssetCommandErrorMessage } from './projectAssetCommandErrorMessage';
import type { ResourceAssetReferenceVersion } from './ResourceAssetDeleteDialog';
/**
* `delete_local_project_asset` 的返回:素材不可变,删除只摘掉 manifest 登记,磁盘文件保留。
*/
export type DeleteLocalProjectAssetResult = {
assetId: string;
localPath: string;
committedProjectRevision: number;
fileRetained: boolean;
};
type ReadLocalProjectAssetReferencesResult = {
assetId: string;
versions: ResourceAssetReferenceVersion[];
};
/** 待删除的素材:`assetId` 是 manifest 资产 id,`localPath` 只用于确认面板的副标题。 */
export type ResourceAssetDeleteTarget = {
assetId: string;
localPath: string;
};
type UseResourceAssetDeleteFlowInput = {
projectPath: string;
projectId: string;
/**
* 失败原因出口(读引用失败 / 删除失败 / 不在客户端内)。
*
* 宿主自己决定显示在哪里:面板内联成 `role="alert"`,资源卡工具条落到工作台提示条。
* 删除流程本身不持有错误态,避免出现"两处各显示半句失败原因"。
*/
onError: (message: string) => void;
onDeleted: (result: DeleteLocalProjectAssetResult) => void;
};
/**
* 删除失败原因:项目身份 / 版本 CAS 拒绝翻成用户可读中文,其余原样透出。
* 与重命名、分类保存共用同一份映射(`projectAssetCommandErrorMessage`)。
*/
export function resourceDeleteErrorMessage(error: unknown) {
return projectAssetCommandErrorMessage(error, '删除资源失败');
}
/**
* 素材删除流程:读引用信息 → 二次确认 → 落盘删除。
*
* 「编辑素材标签」面板与资源卡选中工具条**共用这一份实现**(同一个原生命令、同一个二次确认
* 面板、同一套 `deleteReferencedVersions` 三分支),所以再没有第二份删除实现可以分叉。
*/
export function useResourceAssetDeleteFlow({
projectPath,
projectId,
onError,
onDeleted,
}: UseResourceAssetDeleteFlowInput) {
const [target, setTarget] = useState<ResourceAssetDeleteTarget | null>(null);
const [dialogOpen, setDialogOpen] = useState(false);
const [preparing, setPreparing] = useState(false);
const [deleting, setDeleting] = useState(false);
const [referencedVersions, setReferencedVersions] = useState<
readonly ResourceAssetReferenceVersion[]
>([]);
const [deleteReferencedVersions, setDeleteReferencedVersions] =
useState(false);
/**
* 点删除先读引用信息,再打开独立确认面板;这一步不写盘。
*/
const requestDelete = useCallback(
async (next: ResourceAssetDeleteTarget) => {
const invoke = window.__TAURI__?.core?.invoke;
if (!invoke) {
onError('删除资源需要在客户端内执行');
return;
}
setPreparing(true);
setTarget(next);
try {
const references = await invoke<ReadLocalProjectAssetReferencesResult>(
'read_local_project_asset_references',
{ input: { projectPath, assetId: next.assetId } },
);
setReferencedVersions(references.versions);
setDeleteReferencedVersions(false);
setDialogOpen(true);
} catch (referenceError) {
setTarget(null);
onError(resourceDeleteErrorMessage(referenceError));
} finally {
setPreparing(false);
}
},
[onError, projectPath],
);
const confirmDelete = useCallback(async () => {
const invoke = window.__TAURI__?.core?.invoke;
if (!invoke) {
onError('删除资源需要在客户端内执行');
return;
}
if (!target) return;
setDeleting(true);
try {
const status = await invoke<{ revision: number }>(
'get_local_game_project_revision',
{ projectPath },
);
if (!Number.isSafeInteger(status.revision) || status.revision < 0) {
throw new Error('项目 revision 无效');
}
const result = await invoke<DeleteLocalProjectAssetResult>(
'delete_local_project_asset',
{
input: {
projectPath,
expectedProjectId: projectId,
expectedProjectRevision: status.revision,
assetId: target.assetId,
deleteReferencedVersions,
},
},
);
setDialogOpen(false);
setTarget(null);
onDeleted(result);
} catch (deleteError) {
onError(resourceDeleteErrorMessage(deleteError));
} finally {
setDeleting(false);
}
}, [
deleteReferencedVersions,
onDeleted,
onError,
projectId,
projectPath,
target,
]);
const closeDeleteDialog = useCallback(() => {
setDialogOpen(false);
setTarget(null);
}, []);
return {
requestDelete,
/** 读引用信息在途:入口按钮据此禁用,避免连点发出两次确认面板。 */
preparing,
deleting,
/**
* 现有确认面板的现成入参。没有待删除目标时为 `null`,宿主直接不渲染 `ResourceAssetDeleteDialog`。
*/
deleteDialogProps: target
? {
open: dialogOpen,
localPath: target.localPath,
referencedVersions,
deleteReferencedVersions,
deleting,
onChangeDeleteReferencedVersions: setDeleteReferencedVersions,
onClose: closeDeleteDialog,
onConfirm: () => void confirmDelete(),
}
: null,
};
}