diff --git a/apps/ai-game-creator-shell/scripts/check-config.mjs b/apps/ai-game-creator-shell/scripts/check-config.mjs index 62aa2205e..f2c19ac58 100644 --- a/apps/ai-game-creator-shell/scripts/check-config.mjs +++ b/apps/ai-game-creator-shell/scripts/check-config.mjs @@ -111,7 +111,6 @@ const allowedUncalledTauriCommands = [ 'chat_with_game_creator_agent', 'check_ui_editor_font_glyph_coverage', 'create_ui_design_resource', - 'normalize_local_project_raster_resource', 'open_game_creator_launcher_window', 'open_game_creator_workspace_window', 'read_direct_project_conversation', diff --git a/apps/ai-game-creator-shell/src-tauri/src/commands.rs b/apps/ai-game-creator-shell/src-tauri/src/commands.rs index b54425f61..c6ae93ded 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/commands.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/commands.rs @@ -2172,15 +2172,6 @@ pub(crate) async fn archive_failed_local_project_resource_edit( archive_failed_local_project_resource_edit_at(input).await } -#[tauri::command] -pub(crate) fn normalize_local_project_raster_resource( - input: NormalizeLocalProjectRasterResourceInput, -) -> Result { - let root = Path::new(input.project_path.trim()); - enforce_project_permission_policy(root, "asset.register")?; - normalize_local_project_raster_resource_at(input) -} - #[tauri::command] pub(crate) fn import_canvas_asset( project_path: String, diff --git a/apps/ai-game-creator-shell/src-tauri/src/main.rs b/apps/ai-game-creator-shell/src-tauri/src/main.rs index 8d635d1ba..ff4356a90 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/main.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/main.rs @@ -2622,7 +2622,6 @@ fn main() { request_local_project_resource_edit_service_identity_confirmation, confirm_local_project_resource_edit_service_identity, archive_failed_local_project_resource_edit, - normalize_local_project_raster_resource, import_canvas_asset, import_canvas_export, sync_canvas_project_assets, diff --git a/apps/ai-game-creator-shell/src/view/project-development/ResourceRenameDialog.tsx b/apps/ai-game-creator-shell/src/view/project-development/ResourceRenameDialog.tsx new file mode 100644 index 000000000..71b54e108 --- /dev/null +++ b/apps/ai-game-creator-shell/src/view/project-development/ResourceRenameDialog.tsx @@ -0,0 +1,103 @@ +import { useState } from 'react'; + +import { PlatformActionButton } from '../../../../../packages/shared/src/components/PlatformActionButton'; +import { PlatformTextField } from '../../../../../packages/shared/src/components/PlatformTextField'; +import type { GameCreationAppAssetManifestEntry } from '../../../../../packages/shared/src/contracts/gameCreationApp'; +import { ThemedModal } from '../../components/modal/ThemedModal'; + +export type RenameLocalProjectAssetResult = { + asset: GameCreationAppAssetManifestEntry; + previousLocalPath: string; + committedProjectRevision: number; +}; + +function assetFileName(localPath: string) { + return localPath.split(/[\\/]/u).pop() ?? localPath; +} + +type ResourceRenameDialogProps = { + open: boolean; + asset: GameCreationAppAssetManifestEntry; + renaming: boolean; + error: string | null; + onClose: () => void; + onConfirm: (newFileName: string) => void; +}; + +/** + * 素材重命名面板。 + * + * 只输入新文件名:目录由资产的 `localPath` 决定,用户不能换目录; + * 扩展名一致、同名冲突等规则由 Rust 侧 `rename_local_project_asset` 强校验, + * 这里只做输入与错误呈现。 + */ +export function ResourceRenameDialog({ + open, + asset, + renaming, + error, + onClose, + onConfirm, +}: ResourceRenameDialogProps) { + const [draft, setDraft] = useState(() => assetFileName(asset.localPath)); + + return ( + +
+
+

重命名素材

+

{asset.localPath}

+
+ +
+
+ setDraft(event.currentTarget.value)} + onKeyDown={(event) => { + if (event.key === 'Enter' && !renaming && draft.trim() !== '') { + event.preventDefault(); + onConfirm(draft); + } + }} + /> + {error ? ( +

+ {error} +

+ ) : null} +
+
+ + 取消 + + onConfirm(draft)} + disabled={renaming || draft.trim() === ''} + > + 重命名 + +
+
+ ); +} diff --git a/apps/ai-game-creator-shell/src/view/project-development/index.tsx b/apps/ai-game-creator-shell/src/view/project-development/index.tsx index 69c79c1f2..a0cdc3b0c 100644 --- a/apps/ai-game-creator-shell/src/view/project-development/index.tsx +++ b/apps/ai-game-creator-shell/src/view/project-development/index.tsx @@ -161,6 +161,10 @@ import { projectResourcesFromReadModels, projectResourceTypeLabel, } from './resourceProjectionModel'; +import { + type RenameLocalProjectAssetResult, + ResourceRenameDialog, +} from './ResourceRenameDialog'; import { clampProjectResourceSectionZoom, projectResourceSectionZoomFromWheel, @@ -1251,6 +1255,14 @@ export default function ProjectDevelopmentView({ useState(false); const [resourceClassificationAssetId, setResourceClassificationAssetId] = useState(null); + /** 正在重命名的素材;改名沿用分类面板同一条 manifest 重载路径。 */ + const [resourceRenameAssetId, setResourceRenameAssetId] = useState< + string | null + >(null); + const [resourceRenaming, setResourceRenaming] = useState(false); + const [resourceRenameError, setResourceRenameError] = useState( + null, + ); const [pendingResourceEditActionIds, setPendingResourceEditActionIds] = useState>(() => new Set()); const [pendingResourceEditActionErrors, setPendingResourceEditActionErrors] = @@ -2223,6 +2235,15 @@ export default function ProjectDevelopmentView({ : null, [manifest.assets, resourceClassificationAssetId], ); + const resourceRenameAsset = useMemo( + () => + resourceRenameAssetId + ? (manifest.assets.find( + (asset) => asset.id === resourceRenameAssetId, + ) ?? null) + : null, + [manifest.assets, resourceRenameAssetId], + ); /** * 资源分类更新与素材删除都只改 manifest,成功后重读一次并按新 revision 投影; * 两者共用同一条重载路径,避免出现两份略有差异的刷新逻辑。 @@ -2230,6 +2251,7 @@ export default function ProjectDevelopmentView({ const reloadManifestAfterAssetCommand = useCallback( async (committedProjectRevision: number, commitId: string) => { setResourceClassificationAssetId(null); + setResourceRenameAssetId(null); const invoke = window.__TAURI__?.core?.invoke; if (!invoke || !onManifestChange) return; try { @@ -2273,6 +2295,48 @@ export default function ProjectDevelopmentView({ }, [reloadManifestAfterAssetCommand], ); + /** + * 素材重命名:只改磁盘文件名与 manifest 的 `localPath`,资产 id 不变。 + * Rust 入参是 `deny_unknown_fields` 的结构体,这里必须只传这三个字段。 + */ + const confirmResourceRename = useCallback( + async (newFileName: string) => { + const asset = resourceRenameAsset; + const invoke = window.__TAURI__?.core?.invoke; + if (!asset) return; + if (!invoke) { + setResourceRenameError('重命名素材需要在客户端内执行'); + return; + } + setResourceRenaming(true); + setResourceRenameError(null); + try { + const result = await invoke( + 'rename_local_project_asset', + { + input: { + projectPath, + assetId: asset.id, + newFileName, + }, + }, + ); + await reloadManifestAfterAssetCommand( + result.committedProjectRevision, + `asset-rename:${asset.id}`, + ); + } catch (renameError) { + const message = + renameError instanceof Error + ? renameError.message + : String(renameError); + setResourceRenameError(message.trim() || '重命名素材失败'); + } finally { + setResourceRenaming(false); + } + }, + [projectPath, reloadManifestAfterAssetCommand, resourceRenameAsset], + ); const focusedResourceDependencyDetails = useMemo(() => { if (!focusedResource) { return { @@ -5043,6 +5107,20 @@ export default function ProjectDevelopmentView({ 分类与标签 ) : null} + {focusedResource.manifestAssetId ? ( + + ) : null}