feat(asset): 增强资产管理系统和编辑器 UI (#291)
* fix(editor): 修复粒子实体创建和优化检视器 - 添加 effects 分类到右键菜单,修复粒子实体无法创建的问题 - 添加粒子效果的本地化标签 - 简化粒子组件检视器,优先显示资产文件选择 - 高级属性只在未选择资产时显示,且默认折叠 - 添加可折叠的属性分组提升用户体验 * fix(particle): 修复粒子系统在浏览器预览中的资产加载和渲染 - 添加粒子 Gizmo 支持,显示发射形状并响应 Transform 缩放/旋转 - 修复资产热重载:添加 reloadAsset() 方法和 assets:refresh 事件监听 - 修复 VectorFieldEditors 数值输入精度(step 改为 0.01) - 修复浏览器预览中粒子资产加载失败的问题: - 将相对路径转换为绝对路径以正确复制资产文件 - 使用原始 GUID 而非生成的 GUID 构建 asset catalog - 初始化全局 assetManager 单例的 catalog 和 loader - 在 GameRuntime 的 systemContext 中添加 engineIntegration - 公开 AssetManager.initializeFromCatalog 方法供运行时使用 * feat(asset): 增强资产管理系统和编辑器 UI 主要改动: - 添加 loaderType 字段支持显式指定加载器类型覆盖 - 添加 .particle 扩展名和类型映射 - 新增 MANAGED_ASSET_DIRECTORIES 常量和相关工具方法 - EngineService 使用全局 assetManager 并同步 AssetRegistry 数据 - 修复插件启用逻辑,defaultEnabled=true 的新插件不被旧配置禁用 - ContentBrowser 添加 GUID 管理目录指示和非托管目录警告 - AssetPickerDialog 和 AssetFileInspector UI 增强
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
import { Folder, File as FileIcon, Image as ImageIcon, Clock, HardDrive } from 'lucide-react';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { Folder, File as FileIcon, Image as ImageIcon, Clock, HardDrive, Settings2 } from 'lucide-react';
|
||||
import { convertFileSrc } from '@tauri-apps/api/core';
|
||||
import { Core } from '@esengine/ecs-framework';
|
||||
import { AssetRegistryService } from '@esengine/editor-core';
|
||||
import { assetManager as globalAssetManager } from '@esengine/asset-system';
|
||||
import { AssetFileInfo } from '../types';
|
||||
import { ImagePreview, CodePreview, getLanguageFromExtension } from '../common';
|
||||
import '../../../styles/EntityInspector.css';
|
||||
@@ -10,6 +14,18 @@ interface AssetFileInspectorProps {
|
||||
isImage?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Built-in loader types (always available)
|
||||
* 内置加载器类型(始终可用)
|
||||
*/
|
||||
const BUILTIN_LOADER_TYPES = [
|
||||
'texture',
|
||||
'audio',
|
||||
'json',
|
||||
'text',
|
||||
'binary'
|
||||
];
|
||||
|
||||
function formatFileSize(bytes?: number): string {
|
||||
if (!bytes) return '0 B';
|
||||
const units = ['B', 'KB', 'MB', 'GB'];
|
||||
@@ -38,6 +54,68 @@ export function AssetFileInspector({ fileInfo, content, isImage }: AssetFileInsp
|
||||
const IconComponent = fileInfo.isDirectory ? Folder : isImage ? ImageIcon : FileIcon;
|
||||
const iconColor = fileInfo.isDirectory ? '#dcb67a' : isImage ? '#a78bfa' : '#90caf9';
|
||||
|
||||
// State for loader type selector
|
||||
const [currentLoaderType, setCurrentLoaderType] = useState<string | null>(null);
|
||||
const [availableLoaderTypes, setAvailableLoaderTypes] = useState<string[]>([]);
|
||||
const [detectedType, setDetectedType] = useState<string | null>(null);
|
||||
const [isUpdating, setIsUpdating] = useState(false);
|
||||
|
||||
// Load meta info and available loader types
|
||||
useEffect(() => {
|
||||
if (fileInfo.isDirectory) return;
|
||||
|
||||
const loadMetaInfo = async () => {
|
||||
try {
|
||||
const assetRegistry = Core.services.tryResolve(AssetRegistryService) as AssetRegistryService | null;
|
||||
if (!assetRegistry?.isReady) return;
|
||||
|
||||
const metaManager = assetRegistry.metaManager;
|
||||
const meta = await metaManager.getOrCreateMeta(fileInfo.path);
|
||||
|
||||
// Get current loader type from meta
|
||||
setCurrentLoaderType(meta.loaderType || null);
|
||||
setDetectedType(meta.type);
|
||||
|
||||
// Get available loader types from assetManager
|
||||
const loaderFactory = globalAssetManager.getLoaderFactory();
|
||||
const registeredTypes = loaderFactory?.getRegisteredTypes() || [];
|
||||
|
||||
// Combine built-in types with registered types (deduplicated)
|
||||
const allTypes = new Set([...BUILTIN_LOADER_TYPES, ...registeredTypes]);
|
||||
setAvailableLoaderTypes(Array.from(allTypes).sort());
|
||||
} catch (error) {
|
||||
console.warn('Failed to load meta info:', error);
|
||||
}
|
||||
};
|
||||
|
||||
loadMetaInfo();
|
||||
}, [fileInfo.path, fileInfo.isDirectory]);
|
||||
|
||||
// Handle loader type change
|
||||
const handleLoaderTypeChange = useCallback(async (newType: string) => {
|
||||
if (fileInfo.isDirectory || isUpdating) return;
|
||||
|
||||
setIsUpdating(true);
|
||||
try {
|
||||
const assetRegistry = Core.services.tryResolve(AssetRegistryService) as AssetRegistryService | null;
|
||||
if (!assetRegistry?.isReady) return;
|
||||
|
||||
const metaManager = assetRegistry.metaManager;
|
||||
|
||||
// Update meta with new loader type
|
||||
// Empty string means use auto-detection (remove override)
|
||||
const loaderType = newType === '' ? undefined : newType;
|
||||
await metaManager.updateMeta(fileInfo.path, { loaderType });
|
||||
|
||||
setCurrentLoaderType(loaderType || null);
|
||||
console.log(`[AssetFileInspector] Updated loader type for ${fileInfo.name}: ${loaderType || '(auto)'}`);
|
||||
} catch (error) {
|
||||
console.error('Failed to update loader type:', error);
|
||||
} finally {
|
||||
setIsUpdating(false);
|
||||
}
|
||||
}, [fileInfo.path, fileInfo.name, fileInfo.isDirectory, isUpdating]);
|
||||
|
||||
return (
|
||||
<div className="entity-inspector">
|
||||
<div className="inspector-header">
|
||||
@@ -92,6 +170,56 @@ export function AssetFileInspector({ fileInfo, content, isImage }: AssetFileInsp
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Loader Type Section - only for files, not directories */}
|
||||
{!fileInfo.isDirectory && availableLoaderTypes.length > 0 && (
|
||||
<div className="inspector-section">
|
||||
<div className="section-title">
|
||||
<Settings2 size={14} style={{ verticalAlign: 'middle', marginRight: '4px' }} />
|
||||
加载设置
|
||||
</div>
|
||||
<div className="property-field">
|
||||
<label className="property-label">加载器类型</label>
|
||||
<select
|
||||
className="property-select"
|
||||
value={currentLoaderType || ''}
|
||||
onChange={(e) => handleLoaderTypeChange(e.target.value)}
|
||||
disabled={isUpdating}
|
||||
style={{
|
||||
flex: 1,
|
||||
padding: '4px 8px',
|
||||
fontSize: '12px',
|
||||
borderRadius: '4px',
|
||||
border: '1px solid #444',
|
||||
backgroundColor: '#2a2a2a',
|
||||
color: '#e0e0e0',
|
||||
cursor: isUpdating ? 'wait' : 'pointer'
|
||||
}}
|
||||
>
|
||||
<option value="">
|
||||
自动检测 {detectedType ? `(${detectedType})` : ''}
|
||||
</option>
|
||||
{availableLoaderTypes.map((type) => (
|
||||
<option key={type} value={type}>
|
||||
{type}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
{currentLoaderType && (
|
||||
<div
|
||||
style={{
|
||||
marginTop: '4px',
|
||||
fontSize: '11px',
|
||||
color: '#888',
|
||||
fontStyle: 'italic'
|
||||
}}
|
||||
>
|
||||
已覆盖自动检测,使用 "{currentLoaderType}" 加载器
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isImage && (
|
||||
<div className="inspector-section">
|
||||
<div className="section-title">图片预览</div>
|
||||
|
||||
Reference in New Issue
Block a user