diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..6c2b9be4 --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +link-workspace-packages=true +prefer-workspace-packages=true diff --git a/packages/core/src/ECS/Utils/EntityList.ts b/packages/core/src/ECS/Utils/EntityList.ts index a7f0938a..24a75b0b 100644 --- a/packages/core/src/ECS/Utils/EntityList.ts +++ b/packages/core/src/ECS/Utils/EntityList.ts @@ -253,6 +253,27 @@ export class EntityList { } } + /** + * 重新排序实体 + * @param entityId 要移动的实体ID + * @param newIndex 新的索引位置 + */ + public reorderEntity(entityId: number, newIndex: number): void { + const entity = this._idToEntity.get(entityId); + if (!entity) return; + + const currentIndex = this.buffer.indexOf(entity); + if (currentIndex === -1 || currentIndex === newIndex) return; + + // 限制索引范围 + const clampedIndex = Math.max(0, Math.min(newIndex, this.buffer.length - 1)); + + // 从当前位置移除 + this.buffer.splice(currentIndex, 1); + // 插入到新位置 + this.buffer.splice(clampedIndex, 0, entity); + } + /** * 获取实体列表的统计信息 * @returns 统计信息 diff --git a/packages/core/tests/ECS/Utils/EntityList.test.ts b/packages/core/tests/ECS/Utils/EntityList.test.ts new file mode 100644 index 00000000..5b0ea0c9 --- /dev/null +++ b/packages/core/tests/ECS/Utils/EntityList.test.ts @@ -0,0 +1,330 @@ +import { EntityList } from '../../../src/ECS/Utils/EntityList'; +import { Entity } from '../../../src/ECS/Entity'; +import { Component } from '../../../src/ECS/Component'; + +class TestComponent extends Component { + public value: number = 0; + + constructor(...args: unknown[]) { + super(); + const [value = 0] = args as [number?]; + this.value = value; + } +} + +// Mock scene with identifier pool +function createMockScene() { + const recycledIds: number[] = []; + return { + identifierPool: { + checkIn: (id: number) => { + recycledIds.push(id); + }, + getRecycledIds: () => recycledIds + } + }; +} + +// Mock entity +function createMockEntity(id: number, name: string = '', tag: number = 0, options?: { enabled?: boolean; isDestroyed?: boolean }): Entity { + const entity = { + id, + name, + tag, + enabled: options?.enabled ?? true, + isDestroyed: options?.isDestroyed ?? false, + destroy: jest.fn(), + hasComponent: jest.fn().mockReturnValue(false) + } as unknown as Entity; + return entity; +} + +describe('EntityList', () => { + let entityList: EntityList; + let mockScene: ReturnType; + + beforeEach(() => { + mockScene = createMockScene(); + entityList = new EntityList(mockScene); + }); + + describe('add and remove', () => { + it('should add entity to list', () => { + const entity = createMockEntity(1, 'test'); + entityList.add(entity); + + expect(entityList.count).toBe(1); + expect(entityList.buffer[0]).toBe(entity); + }); + + it('should not add duplicate entity', () => { + const entity = createMockEntity(1, 'test'); + entityList.add(entity); + entityList.add(entity); + + expect(entityList.count).toBe(1); + }); + + it('should remove entity from list', () => { + const entity = createMockEntity(1, 'test'); + entityList.add(entity); + entityList.remove(entity); + + expect(entityList.count).toBe(0); + }); + + it('should recycle entity id on remove', () => { + const entity = createMockEntity(1, 'test'); + entityList.add(entity); + entityList.remove(entity); + + expect(mockScene.identifierPool.getRecycledIds()).toContain(1); + }); + }); + + describe('findEntity methods', () => { + it('should find entity by name', () => { + const entity = createMockEntity(1, 'player'); + entityList.add(entity); + + const found = entityList.findEntity('player'); + expect(found).toBe(entity); + }); + + it('should return null for non-existent name', () => { + const found = entityList.findEntity('nonexistent'); + expect(found).toBeNull(); + }); + + it('should find entity by id', () => { + const entity = createMockEntity(42, 'test'); + entityList.add(entity); + + const found = entityList.findEntityById(42); + expect(found).toBe(entity); + }); + + it('should return null for non-existent id', () => { + const found = entityList.findEntityById(999); + expect(found).toBeNull(); + }); + + it('should find all entities by name', () => { + const entity1 = createMockEntity(1, 'enemy'); + const entity2 = createMockEntity(2, 'enemy'); + const entity3 = createMockEntity(3, 'player'); + entityList.add(entity1); + entityList.add(entity2); + entityList.add(entity3); + + const enemies = entityList.findEntitiesByName('enemy'); + expect(enemies).toHaveLength(2); + expect(enemies).toContain(entity1); + expect(enemies).toContain(entity2); + }); + + it('should find entities by tag', () => { + const entity1 = createMockEntity(1, 'e1', 1); + const entity2 = createMockEntity(2, 'e2', 2); + const entity3 = createMockEntity(3, 'e3', 1); + entityList.add(entity1); + entityList.add(entity2); + entityList.add(entity3); + + const tagged = entityList.findEntitiesByTag(1); + expect(tagged).toHaveLength(2); + expect(tagged).toContain(entity1); + expect(tagged).toContain(entity3); + }); + + it('should find entities with component', () => { + const entity1 = createMockEntity(1, 'e1'); + const entity2 = createMockEntity(2, 'e2'); + (entity1.hasComponent as jest.Mock).mockReturnValue(true); + (entity2.hasComponent as jest.Mock).mockReturnValue(false); + + entityList.add(entity1); + entityList.add(entity2); + + const withComponent = entityList.findEntitiesWithComponent(TestComponent); + expect(withComponent).toHaveLength(1); + expect(withComponent[0]).toBe(entity1); + }); + }); + + describe('removeAllEntities', () => { + it('should remove all entities and clear indices', () => { + const entity1 = createMockEntity(1, 'e1'); + const entity2 = createMockEntity(2, 'e2'); + entityList.add(entity1); + entityList.add(entity2); + + entityList.removeAllEntities(); + + expect(entityList.count).toBe(0); + expect(entityList.findEntityById(1)).toBeNull(); + expect(entityList.findEntityById(2)).toBeNull(); + }); + + it('should call destroy on all entities', () => { + const entity1 = createMockEntity(1, 'e1'); + const entity2 = createMockEntity(2, 'e2'); + entityList.add(entity1); + entityList.add(entity2); + + entityList.removeAllEntities(); + + expect(entity1.destroy).toHaveBeenCalled(); + expect(entity2.destroy).toHaveBeenCalled(); + }); + + it('should recycle all entity ids', () => { + const entity1 = createMockEntity(1, 'e1'); + const entity2 = createMockEntity(2, 'e2'); + entityList.add(entity1); + entityList.add(entity2); + + entityList.removeAllEntities(); + + const recycled = mockScene.identifierPool.getRecycledIds(); + expect(recycled).toContain(1); + expect(recycled).toContain(2); + }); + }); + + describe('reorderEntity', () => { + it('should reorder entity to new position', () => { + const entity1 = createMockEntity(1, 'e1'); + const entity2 = createMockEntity(2, 'e2'); + const entity3 = createMockEntity(3, 'e3'); + entityList.add(entity1); + entityList.add(entity2); + entityList.add(entity3); + + entityList.reorderEntity(3, 0); + + expect(entityList.buffer[0]).toBe(entity3); + expect(entityList.buffer[1]).toBe(entity1); + expect(entityList.buffer[2]).toBe(entity2); + }); + + it('should clamp index to valid range', () => { + const entity1 = createMockEntity(1, 'e1'); + const entity2 = createMockEntity(2, 'e2'); + entityList.add(entity1); + entityList.add(entity2); + + entityList.reorderEntity(1, 100); + + expect(entityList.buffer[1]).toBe(entity1); + }); + + it('should do nothing for non-existent entity', () => { + const entity1 = createMockEntity(1, 'e1'); + entityList.add(entity1); + + entityList.reorderEntity(999, 0); + + expect(entityList.buffer[0]).toBe(entity1); + }); + + it('should do nothing if already at target position', () => { + const entity1 = createMockEntity(1, 'e1'); + const entity2 = createMockEntity(2, 'e2'); + entityList.add(entity1); + entityList.add(entity2); + + entityList.reorderEntity(1, 0); + + expect(entityList.buffer[0]).toBe(entity1); + expect(entityList.buffer[1]).toBe(entity2); + }); + }); + + describe('getStats', () => { + it('should return correct statistics', () => { + const entity1 = createMockEntity(1, 'e1'); + const entity2 = createMockEntity(2, 'e2', 0, { enabled: false }); + const entity3 = createMockEntity(3, 'e3', 0, { isDestroyed: true }); + + entityList.add(entity1); + entityList.add(entity2); + entityList.add(entity3); + + const stats = entityList.getStats(); + + expect(stats.totalEntities).toBe(3); + expect(stats.activeEntities).toBe(1); + expect(stats.pendingAdd).toBe(0); + expect(stats.pendingRemove).toBe(0); + expect(stats.nameIndexSize).toBe(3); + }); + }); + + describe('forEach methods', () => { + it('should iterate all entities with forEach', () => { + const entity1 = createMockEntity(1, 'e1'); + const entity2 = createMockEntity(2, 'e2'); + entityList.add(entity1); + entityList.add(entity2); + + const visited: number[] = []; + entityList.forEach((entity) => { + visited.push(entity.id); + }); + + expect(visited).toEqual([1, 2]); + }); + + it('should iterate filtered entities with forEachWhere', () => { + const entity1 = createMockEntity(1, 'e1', 1); + const entity2 = createMockEntity(2, 'e2', 2); + const entity3 = createMockEntity(3, 'e3', 1); + entityList.add(entity1); + entityList.add(entity2); + entityList.add(entity3); + + const visited: number[] = []; + entityList.forEachWhere( + (entity) => entity.tag === 1, + (entity) => visited.push(entity.id) + ); + + expect(visited).toEqual([1, 3]); + }); + }); + + describe('name index management', () => { + it('should update name index when entity is removed', () => { + const entity1 = createMockEntity(1, 'shared'); + const entity2 = createMockEntity(2, 'shared'); + entityList.add(entity1); + entityList.add(entity2); + + entityList.remove(entity1); + + const found = entityList.findEntitiesByName('shared'); + expect(found).toHaveLength(1); + expect(found[0]).toBe(entity2); + }); + + it('should handle entities without names', () => { + const entity = createMockEntity(1, ''); + entityList.add(entity); + + expect(entityList.count).toBe(1); + expect(entityList.findEntity('')).toBeNull(); + }); + }); + + describe('updateLists', () => { + it('should process pending operations via update', () => { + const entity = createMockEntity(1, 'test'); + entityList.add(entity); + + entityList.update(); + + expect(entityList.count).toBe(1); + }); + }); +}); diff --git a/packages/editor-app/.gitignore b/packages/editor-app/.gitignore new file mode 100644 index 00000000..07e39c29 --- /dev/null +++ b/packages/editor-app/.gitignore @@ -0,0 +1,2 @@ +# Generated runtime files +src-tauri/runtime/ diff --git a/packages/editor-app/package.json b/packages/editor-app/package.json index 2dd8305f..dff54246 100644 --- a/packages/editor-app/package.json +++ b/packages/editor-app/package.json @@ -18,16 +18,18 @@ "dependencies": { "@esengine/asset-system": "workspace:*", "@esengine/behavior-tree": "workspace:*", - "@esengine/ecs-engine-bindgen": "workspace:*", "@esengine/ecs-components": "workspace:*", + "@esengine/ecs-engine-bindgen": "workspace:*", "@esengine/ecs-framework": "workspace:*", "@esengine/editor-core": "workspace:*", + "@esengine/engine": "workspace:*", + "@monaco-editor/react": "^4.7.0", "@tauri-apps/api": "^2.2.0", + "@tauri-apps/plugin-cli": "^2.4.1", "@tauri-apps/plugin-dialog": "^2.4.0", "@tauri-apps/plugin-fs": "^2.4.2", "@tauri-apps/plugin-http": "^2.5.4", "@tauri-apps/plugin-shell": "^2.0.0", - "@esengine/engine": "workspace:*", "flexlayout-react": "^0.8.17", "i18next": "^25.6.0", "json5": "^2.2.3", diff --git a/packages/editor-app/public/runtime.config.json b/packages/editor-app/public/runtime.config.json new file mode 100644 index 00000000..11d59888 --- /dev/null +++ b/packages/editor-app/public/runtime.config.json @@ -0,0 +1,27 @@ +{ + "runtime": { + "version": "1.0.0", + "modules": { + "platform-web": { + "type": "javascript", + "main": "runtime.browser.js", + "development": { + "path": "../platform-web/dist" + }, + "production": { + "bundled": true + } + }, + "engine": { + "type": "wasm", + "files": ["es_engine_bg.wasm", "es_engine.js"], + "development": { + "path": "../engine/pkg" + }, + "production": { + "bundled": true + } + } + } + } +} \ No newline at end of file diff --git a/packages/editor-app/scripts/bundle-runtime.mjs b/packages/editor-app/scripts/bundle-runtime.mjs index 3c1c0371..102cd625 100644 --- a/packages/editor-app/scripts/bundle-runtime.mjs +++ b/packages/editor-app/scripts/bundle-runtime.mjs @@ -69,12 +69,23 @@ if (success) { config.bundle = {}; } if (!config.bundle.resources) { - config.bundle.resources = []; + config.bundle.resources = {}; } - if (!config.bundle.resources.includes('runtime/**/*')) { - config.bundle.resources.push('runtime/**/*'); - fs.writeFileSync(tauriConfigPath, JSON.stringify(config, null, 2)); - console.log('✓ Updated tauri.conf.json with runtime resources'); + + // Handle both array and object format for resources + if (Array.isArray(config.bundle.resources)) { + if (!config.bundle.resources.includes('runtime/**/*')) { + config.bundle.resources.push('runtime/**/*'); + fs.writeFileSync(tauriConfigPath, JSON.stringify(config, null, 2)); + console.log('✓ Updated tauri.conf.json with runtime resources'); + } + } else if (typeof config.bundle.resources === 'object') { + // Object format - add runtime files if not present + if (!config.bundle.resources['runtime/**/*']) { + config.bundle.resources['runtime/**/*'] = '.'; + fs.writeFileSync(tauriConfigPath, JSON.stringify(config, null, 2)); + console.log('✓ Updated tauri.conf.json with runtime resources'); + } } } diff --git a/packages/editor-app/src-tauri/Cargo.lock b/packages/editor-app/src-tauri/Cargo.lock index 4dd218e9..0674cada 100644 --- a/packages/editor-app/src-tauri/Cargo.lock +++ b/packages/editor-app/src-tauri/Cargo.lock @@ -70,6 +70,56 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + [[package]] name = "anyhow" version = "1.0.100" @@ -553,12 +603,45 @@ dependencies = [ "inout", ] +[[package]] +name = "clap" +version = "4.5.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_lex" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" + [[package]] name = "color_quant" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + [[package]] name = "combine" version = "4.6.7" @@ -1023,6 +1106,7 @@ dependencies = [ "serde_json", "tauri", "tauri-build", + "tauri-plugin-cli", "tauri-plugin-dialog", "tauri-plugin-fs", "tauri-plugin-http", @@ -2176,6 +2260,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itertools" version = "0.14.0" @@ -2978,6 +3068,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "open" version = "5.3.2" @@ -4770,6 +4866,21 @@ dependencies = [ "walkdir", ] +[[package]] +name = "tauri-plugin-cli" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28e78fb2c09a81546bcd376d34db4bda5769270d00990daa9f0d6e7ac1107e25" +dependencies = [ + "clap", + "log", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.17", +] + [[package]] name = "tauri-plugin-dialog" version = "2.4.0" @@ -5540,6 +5651,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.18.1" diff --git a/packages/editor-app/src-tauri/Cargo.toml b/packages/editor-app/src-tauri/Cargo.toml index 3636e941..3025ff86 100644 --- a/packages/editor-app/src-tauri/Cargo.toml +++ b/packages/editor-app/src-tauri/Cargo.toml @@ -19,6 +19,7 @@ tauri-plugin-dialog = "2.0" tauri-plugin-fs = "2.0" tauri-plugin-updater = "2" tauri-plugin-http = "2.0" +tauri-plugin-cli = "2.0" serde = { version = "1", features = ["derive"] } serde_json = "1" glob = "0.3" diff --git a/packages/editor-app/src-tauri/src/main.rs b/packages/editor-app/src-tauri/src/main.rs index 48021090..ccb88ae5 100644 --- a/packages/editor-app/src-tauri/src/main.rs +++ b/packages/editor-app/src-tauri/src/main.rs @@ -29,6 +29,7 @@ fn main() { .plugin(tauri_plugin_fs::init()) .plugin(tauri_plugin_updater::Builder::new().build()) .plugin(tauri_plugin_http::init()) + .plugin(tauri_plugin_cli::init()) // Register custom URI scheme for project files .register_uri_scheme_protocol("project", move |_app, request| { handle_project_protocol(request, &project_paths_for_protocol) diff --git a/packages/editor-app/src-tauri/tauri.conf.json b/packages/editor-app/src-tauri/tauri.conf.json index 8882dbee..469f40bd 100644 --- a/packages/editor-app/src-tauri/tauri.conf.json +++ b/packages/editor-app/src-tauri/tauri.conf.json @@ -12,6 +12,12 @@ "active": true, "targets": "all", "createUpdaterArtifacts": true, + "resources": { + "../../platform-web/dist/runtime.browser.js": "runtime.browser.js", + "../../engine/pkg/es_engine_bg.wasm": "es_engine_bg.wasm", + "../../engine/pkg/es_engine.js": "es_engine.js", + "runtime/**/*": "." + }, "icon": [ "icons/32x32.png", "icons/128x128.png", @@ -22,8 +28,21 @@ "windows": { "certificateThumbprint": null, "digestAlgorithm": "sha256", - "timestampUrl": "" + "timestampUrl": "", + "webviewInstallMode": { + "type": "downloadBootstrapper" + } }, + "fileAssociations": [ + { + "ext": [ + "ecs" + ], + "name": "ECS Scene File", + "description": "ECS Framework Scene File", + "role": "Editor" + } + ], "macOS": { "frameworks": [], "minimumSystemVersion": "10.13", @@ -68,6 +87,7 @@ ], "permissions": [ "core:default", + "core:window:allow-start-dragging", "shell:default", "dialog:default", "updater:default", @@ -111,6 +131,15 @@ "shell": { "open": true }, + "cli": { + "args": [ + { + "name": "file", + "index": 1, + "takesValue": true + } + ] + }, "fs": { "requireLiteralLeadingDot": false }, @@ -123,4 +152,4 @@ "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDFDQjNFNDIxREFBODNDNkMKUldSc1BLamFJZVN6SEJIRXRWWEovVXRta08yNWFkZmtKNnZoSHFmbi9ZdGxubUMzSHJaN3J0VEcK" } } -} +} \ No newline at end of file diff --git a/packages/editor-app/src/App.tsx b/packages/editor-app/src/App.tsx index b39d743d..277ff19a 100644 --- a/packages/editor-app/src/App.tsx +++ b/packages/editor-app/src/App.tsx @@ -21,6 +21,7 @@ import type { IDialogExtended } from './services/TauriDialogService'; import { GlobalBlackboardService } from '@esengine/behavior-tree'; import { ServiceRegistry, PluginInstaller, useDialogStore } from './app/managers'; import { StartupPage } from './components/StartupPage'; +import { ProjectCreationWizard } from './components/ProjectCreationWizard'; import { SceneHierarchy } from './components/SceneHierarchy'; import { Inspector } from './components/inspectors/Inspector'; import { AssetBrowser } from './components/AssetBrowser'; @@ -49,7 +50,8 @@ import { CompilerConfigDialog } from './components/CompilerConfigDialog'; import { checkForUpdatesOnStartup } from './utils/updater'; import { useLocale } from './hooks/useLocale'; import { en, zh } from './locales'; -import { Loader2, Globe } from 'lucide-react'; +import type { Locale } from '@esengine/editor-core'; +import { Loader2, Globe, ChevronDown } from 'lucide-react'; import './styles/App.css'; const coreInstance = Core.create({ debug: true }); @@ -98,6 +100,7 @@ function App() { const [pluginUpdateTrigger, setPluginUpdateTrigger] = useState(0); const [isRemoteConnected, setIsRemoteConnected] = useState(false); const [isProfilerMode, setIsProfilerMode] = useState(false); + const [showProjectWizard, setShowProjectWizard] = useState(false); const { showPluginManager, setShowPluginManager, @@ -120,6 +123,8 @@ function App() { compilerId: string; currentFileName?: string; }>({ isOpen: false, compilerId: '' }); + const [showLocaleMemu, setShowLocaleMenu] = useState(false); + const localeMenuRef = useRef(null); useEffect(() => { // 禁用默认右键菜单 @@ -127,22 +132,56 @@ function App() { e.preventDefault(); }; - // 添加快捷键监听(Ctrl+R 重新加载插件) - const handleKeyDown = (e: KeyboardEvent) => { - if ((e.ctrlKey || e.metaKey) && e.key === 'r') { - e.preventDefault(); - handleReloadPlugins(); - } - }; - document.addEventListener('contextmenu', handleContextMenu); - document.addEventListener('keydown', handleKeyDown); return () => { document.removeEventListener('contextmenu', handleContextMenu); + }; + }, []); + + // 语言菜单点击外部关闭 + useEffect(() => { + const handleClickOutside = (e: MouseEvent) => { + if (localeMenuRef.current && !localeMenuRef.current.contains(e.target as Node)) { + setShowLocaleMenu(false); + } + }; + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, []); + + // 快捷键监听 + useEffect(() => { + const handleKeyDown = async (e: KeyboardEvent) => { + if (e.ctrlKey || e.metaKey) { + switch (e.key.toLowerCase()) { + case 's': + e.preventDefault(); + if (sceneManager) { + try { + await sceneManager.saveScene(); + const sceneState = sceneManager.getSceneState(); + showToast(locale === 'zh' ? `已保存场景: ${sceneState.sceneName}` : `Scene saved: ${sceneState.sceneName}`, 'success'); + } catch (error) { + console.error('Failed to save scene:', error); + showToast(locale === 'zh' ? '保存场景失败' : 'Failed to save scene', 'error'); + } + } + break; + case 'r': + e.preventDefault(); + handleReloadPlugins(); + break; + } + } + }; + + document.addEventListener('keydown', handleKeyDown); + + return () => { document.removeEventListener('keydown', handleKeyDown); }; - }, [currentProjectPath, pluginManager, locale]); + }, [sceneManager, locale, currentProjectPath, pluginManager]); useEffect(() => { if (messageHub) { @@ -396,13 +435,14 @@ function App() { } }; - const handleCreateProject = async () => { - let selectedProjectPath: string | null = null; + const handleCreateProject = () => { + setShowProjectWizard(true); + }; + + const handleCreateProjectFromWizard = async (projectName: string, projectPath: string, _templateId: string) => { + const fullProjectPath = `${projectPath}\\${projectName}`; try { - selectedProjectPath = await TauriAPI.openProjectDialog(); - if (!selectedProjectPath) return; - setIsLoading(true); setLoadingMessage(locale === 'zh' ? '正在创建项目...' : 'Creating project...'); @@ -417,19 +457,18 @@ function App() { return; } - await projectService.createProject(selectedProjectPath); + await projectService.createProject(fullProjectPath); setLoadingMessage(locale === 'zh' ? '项目创建成功,正在打开...' : 'Project created, opening...'); - await handleOpenRecentProject(selectedProjectPath); + await handleOpenRecentProject(fullProjectPath); } catch (error) { console.error('Failed to create project:', error); setIsLoading(false); const errorMessage = error instanceof Error ? error.message : String(error); - const pathToOpen = selectedProjectPath; - if (errorMessage.includes('already exists') && pathToOpen) { + if (errorMessage.includes('already exists')) { setConfirmDialog({ title: locale === 'zh' ? '项目已存在' : 'Project Already Exists', message: locale === 'zh' @@ -441,7 +480,7 @@ function App() { setConfirmDialog(null); setIsLoading(true); setLoadingMessage(locale === 'zh' ? '正在打开项目...' : 'Opening project...'); - handleOpenRecentProject(pathToOpen).catch((err) => { + handleOpenRecentProject(fullProjectPath).catch((err) => { console.error('Failed to open project:', err); setIsLoading(false); setErrorDialog({ @@ -465,8 +504,19 @@ function App() { } }; + const handleBrowseProjectPath = async (): Promise => { + try { + const path = await TauriAPI.openProjectDialog(); + return path || null; + } catch (error) { + console.error('Failed to browse path:', error); + return null; + } + }; + const handleProfilerMode = async () => { setIsProfilerMode(true); + setIsRemoteConnected(true); setProjectLoaded(true); setStatus(t('header.status.profilerMode') || 'Profiler Mode - Waiting for connection...'); }; @@ -571,8 +621,7 @@ function App() { window.close(); }; - const handleLocaleChange = () => { - const newLocale = locale === 'en' ? 'zh' : 'en'; + const handleLocaleChange = (newLocale: Locale) => { changeLocale(newLocale); // 通知所有已加载的插件更新语言 @@ -650,7 +699,7 @@ function App() { { id: 'scene-hierarchy', title: locale === 'zh' ? '场景层级' : 'Scene Hierarchy', - content: , + content: , closable: false }, { @@ -777,9 +826,17 @@ function App() { onCreateProject={handleCreateProject} onOpenRecentProject={handleOpenRecentProject} onProfilerMode={handleProfilerMode} + onLocaleChange={handleLocaleChange} recentProjects={recentProjects} locale={locale} /> + setShowProjectWizard(false)} + onCreateProject={handleCreateProjectFromWizard} + onBrowsePath={handleBrowseProjectPath} + locale={locale} + /> {isLoading && (
@@ -817,11 +874,18 @@ function App() { ); } + const projectName = currentProjectPath ? currentProjectPath.split(/[\\/]/).pop() : 'Untitled'; + return (
{!isEditorFullscreen && ( -
- +
+ {projectName} + ECS Framework Editor +
+
+ setShowDashboard(true)} locale={locale} /> - +
+ + {showLocaleMemu && ( +
+ + +
+ )} +
{status}
-
+
+ )} {showLoginDialog && ( diff --git a/packages/editor-app/src/app/managers/ServiceRegistry.ts b/packages/editor-app/src/app/managers/ServiceRegistry.ts index cc7a16ec..ee3a532e 100644 --- a/packages/editor-app/src/app/managers/ServiceRegistry.ts +++ b/packages/editor-app/src/app/managers/ServiceRegistry.ts @@ -126,7 +126,7 @@ export class ServiceRegistry { const propertyMetadata = new PropertyMetadataService(); const logService = new LogService(); const settingsRegistry = new SettingsRegistry(); - const sceneManager = new SceneManagerService(messageHub, fileAPI, projectService); + const sceneManager = new SceneManagerService(messageHub, fileAPI, projectService, entityStore); const fileActionRegistry = new FileActionRegistry(); Core.services.registerInstance(UIRegistry, uiRegistry); diff --git a/packages/editor-app/src/components/AssetBrowser.tsx b/packages/editor-app/src/components/AssetBrowser.tsx index 4746db18..27b330ed 100644 --- a/packages/editor-app/src/components/AssetBrowser.tsx +++ b/packages/editor-app/src/components/AssetBrowser.tsx @@ -289,7 +289,9 @@ export function AssetBrowser({ projectPath, locale, onOpenScene }: AssetBrowserP setCurrentPath(asset.path); loadAssets(asset.path); } else if (asset.type === 'file') { - if (asset.extension === 'ecs' && onOpenScene) { + const ext = asset.extension?.toLowerCase(); + if (ext === 'ecs' && onOpenScene) { + console.log('[AssetBrowser] Opening scene:', asset.path); onOpenScene(asset.path); return; } @@ -696,6 +698,7 @@ export function AssetBrowser({ projectPath, locale, onOpenScene }: AssetBrowserP messageHub={messageHub} searchQuery={searchQuery} showFiles={false} + onOpenScene={onOpenScene} />
} @@ -817,6 +820,7 @@ export function AssetBrowser({ projectPath, locale, onOpenScene }: AssetBrowserP messageHub={messageHub} searchQuery={searchQuery} showFiles={true} + onOpenScene={onOpenScene} />
)} diff --git a/packages/editor-app/src/components/ConsolePanel.tsx b/packages/editor-app/src/components/ConsolePanel.tsx index 2342bd42..59223a0b 100644 --- a/packages/editor-app/src/components/ConsolePanel.tsx +++ b/packages/editor-app/src/components/ConsolePanel.tsx @@ -298,9 +298,9 @@ export function ConsolePanel({ logService }: ConsolePanelProps) {

No logs to display

) : ( - filteredLogs.map((log) => ( + filteredLogs.map((log, index) => ( diff --git a/packages/editor-app/src/components/FileTree.tsx b/packages/editor-app/src/components/FileTree.tsx index 10de0aaf..e4f37f03 100644 --- a/packages/editor-app/src/components/FileTree.tsx +++ b/packages/editor-app/src/components/FileTree.tsx @@ -28,6 +28,7 @@ interface FileTreeProps { messageHub?: MessageHub; searchQuery?: string; showFiles?: boolean; + onOpenScene?: (scenePath: string) => void; } export interface FileTreeHandle { @@ -36,7 +37,7 @@ export interface FileTreeHandle { revealPath: (targetPath: string) => Promise; } -export const FileTree = forwardRef(({ rootPath, onSelectFile, onSelectFiles, selectedPath, selectedPaths, messageHub, searchQuery, showFiles = true }, ref) => { +export const FileTree = forwardRef(({ rootPath, onSelectFile, onSelectFiles, selectedPath, selectedPaths, messageHub, searchQuery, showFiles = true, onOpenScene }, ref) => { const [tree, setTree] = useState([]); const [loading, setLoading] = useState(false); const [internalSelectedPath, setInternalSelectedPath] = useState(null); @@ -714,6 +715,14 @@ export const FileTree = forwardRef(({ rootPath, o const handleNodeDoubleClick = async (node: TreeNode) => { if (node.type === 'file') { + // Handle .ecs scene files + const ext = node.name.split('.').pop()?.toLowerCase(); + if (ext === 'ecs' && onOpenScene) { + console.log('[FileTree] Opening scene:', node.path); + onOpenScene(node.path); + return; + } + if (fileActionRegistry) { const handled = await fileActionRegistry.handleDoubleClick(node.path); if (handled) { diff --git a/packages/editor-app/src/components/ProjectCreationWizard.tsx b/packages/editor-app/src/components/ProjectCreationWizard.tsx new file mode 100644 index 00000000..7829137a --- /dev/null +++ b/packages/editor-app/src/components/ProjectCreationWizard.tsx @@ -0,0 +1,169 @@ +import { useState } from 'react'; +import { Folder, Sparkles, X } from 'lucide-react'; +import '../styles/ProjectCreationWizard.css'; + +// 项目模板类型 +interface ProjectTemplate { + id: string; + name: string; + nameZh: string; + description: string; + descriptionZh: string; +} + +const templates: ProjectTemplate[] = [ + { + id: 'blank', + name: 'Blank', + nameZh: '空白', + description: 'A blank project with no starter content. Perfect for starting from scratch.', + descriptionZh: '不包含任何启动内容的空白项目,适合从零开始创建。' + } +]; + +interface ProjectCreationWizardProps { + isOpen: boolean; + onClose: () => void; + onCreateProject: (projectName: string, projectPath: string, templateId: string) => void; + onBrowsePath: () => Promise; + locale: string; +} + +export function ProjectCreationWizard({ + isOpen, + onClose, + onCreateProject, + onBrowsePath, + locale +}: ProjectCreationWizardProps) { + const [selectedTemplate, setSelectedTemplate] = useState('blank'); + const [projectName, setProjectName] = useState('MyProject'); + const [projectPath, setProjectPath] = useState(''); + + const t = { + title: locale === 'zh' ? '项目浏览器' : 'Project Browser', + recentProjects: locale === 'zh' ? '最近打开的项目' : 'Recent Projects', + newProject: locale === 'zh' ? '新建项目' : 'New Project', + projectName: locale === 'zh' ? '项目名称' : 'Project Name', + projectLocation: locale === 'zh' ? '项目位置' : 'Project Location', + browse: locale === 'zh' ? '浏览...' : 'Browse...', + create: locale === 'zh' ? '创建' : 'Create', + cancel: locale === 'zh' ? '取消' : 'Cancel', + selectTemplate: locale === 'zh' ? '选择模板' : 'Select a Template', + projectSettings: locale === 'zh' ? '项目设置' : 'Project Settings', + blank: locale === 'zh' ? '空白' : 'Blank', + blankDesc: locale === 'zh' ? '不含任何代码的空白项目。' : 'A blank project with no code.' + }; + + if (!isOpen) return null; + + const currentTemplate = templates.find(t => t.id === selectedTemplate); + + const handleBrowse = async () => { + const path = await onBrowsePath(); + if (path) { + setProjectPath(path); + } + }; + + const handleCreate = () => { + if (projectName && projectPath) { + onCreateProject(projectName, projectPath, selectedTemplate); + onClose(); + } + }; + + return ( +
+
+
+

{t.title}

+ +
+ +
+ {/* Templates grid */} +
+
+

{t.selectTemplate}

+
+
+ {templates.map(template => ( + + ))} +
+
+ + {/* Right - Preview and settings */} +
+
+
+ +
+
+ +
+

{locale === 'zh' ? currentTemplate?.nameZh : currentTemplate?.name}

+

{locale === 'zh' ? currentTemplate?.descriptionZh : currentTemplate?.description}

+
+ +
+

{t.projectSettings}

+ +
+ + setProjectName(e.target.value)} + placeholder="MyProject" + /> +
+ +
+ +
+ setProjectPath(e.target.value)} + placeholder="D:\Projects" + /> + +
+
+
+
+
+ +
+ + +
+
+
+ ); +} diff --git a/packages/editor-app/src/components/SceneHierarchy.tsx b/packages/editor-app/src/components/SceneHierarchy.tsx index 905e0b47..9a7cc496 100644 --- a/packages/editor-app/src/components/SceneHierarchy.tsx +++ b/packages/editor-app/src/components/SceneHierarchy.tsx @@ -14,13 +14,14 @@ interface SceneHierarchyProps { entityStore: EntityStoreService; messageHub: MessageHub; commandManager: CommandManager; + isProfilerMode?: boolean; } -export function SceneHierarchy({ entityStore, messageHub, commandManager }: SceneHierarchyProps) { +export function SceneHierarchy({ entityStore, messageHub, commandManager, isProfilerMode = false }: SceneHierarchyProps) { const [entities, setEntities] = useState([]); const [remoteEntities, setRemoteEntities] = useState([]); const [isRemoteConnected, setIsRemoteConnected] = useState(false); - const [viewMode, setViewMode] = useState('local'); + const [viewMode, setViewMode] = useState(isProfilerMode ? 'remote' : 'local'); const [selectedId, setSelectedId] = useState(null); const [searchQuery, setSearchQuery] = useState(''); const [sceneName, setSceneName] = useState('Untitled'); @@ -28,6 +29,8 @@ export function SceneHierarchy({ entityStore, messageHub, commandManager }: Scen const [sceneFilePath, setSceneFilePath] = useState(null); const [isSceneModified, setIsSceneModified] = useState(false); const [contextMenu, setContextMenu] = useState<{ x: number; y: number; entityId: number | null } | null>(null); + const [draggedEntityId, setDraggedEntityId] = useState(null); + const [dropTargetIndex, setDropTargetIndex] = useState(null); const { t, locale } = useLocale(); const isShowingRemote = viewMode === 'remote' && isRemoteConnected; @@ -41,6 +44,7 @@ export function SceneHierarchy({ entityStore, messageHub, commandManager }: Scen const state = sceneManager.getSceneState(); setSceneName(state.sceneName); setIsSceneModified(state.isModified); + setSceneFilePath(state.currentScenePath || null); } }; @@ -61,9 +65,7 @@ export function SceneHierarchy({ entityStore, messageHub, commandManager }: Scen const unsubSaved = messageHub.subscribe('scene:saved', () => { updateSceneInfo(); }); - const unsubModified = messageHub.subscribe('scene:modified', () => { - updateSceneInfo(); - }); + const unsubModified = messageHub.subscribe('scene:modified', updateSceneInfo); return () => { unsubLoaded(); @@ -76,7 +78,7 @@ export function SceneHierarchy({ entityStore, messageHub, commandManager }: Scen // Subscribe to local entity changes useEffect(() => { const updateEntities = () => { - setEntities(entityStore.getRootEntities()); + setEntities([...entityStore.getRootEntities()]); }; const handleSelection = (data: { entity: Entity | null }) => { @@ -89,12 +91,18 @@ export function SceneHierarchy({ entityStore, messageHub, commandManager }: Scen const unsubRemove = messageHub.subscribe('entity:removed', updateEntities); const unsubClear = messageHub.subscribe('entities:cleared', updateEntities); const unsubSelect = messageHub.subscribe('entity:selected', handleSelection); + const unsubSceneLoaded = messageHub.subscribe('scene:loaded', updateEntities); + const unsubSceneNew = messageHub.subscribe('scene:new', updateEntities); + const unsubReordered = messageHub.subscribe('entity:reordered', updateEntities); return () => { unsubAdd(); unsubRemove(); unsubClear(); unsubSelect(); + unsubSceneLoaded(); + unsubSceneNew(); + unsubReordered(); }; }, [entityStore, messageHub]); @@ -162,6 +170,36 @@ export function SceneHierarchy({ entityStore, messageHub, commandManager }: Scen entityStore.selectEntity(entity); }; + const handleDragStart = (e: React.DragEvent, entityId: number) => { + setDraggedEntityId(entityId); + e.dataTransfer.effectAllowed = 'move'; + e.dataTransfer.setData('text/plain', entityId.toString()); + }; + + const handleDragOver = (e: React.DragEvent, index: number) => { + e.preventDefault(); + e.dataTransfer.dropEffect = 'move'; + setDropTargetIndex(index); + }; + + const handleDragLeave = () => { + setDropTargetIndex(null); + }; + + const handleDrop = (e: React.DragEvent, targetIndex: number) => { + e.preventDefault(); + if (draggedEntityId !== null) { + entityStore.reorderEntity(draggedEntityId, targetIndex); + } + setDraggedEntityId(null); + setDropTargetIndex(null); + }; + + const handleDragEnd = () => { + setDraggedEntityId(null); + setDropTargetIndex(null); + }; + const handleRemoteEntityClick = (entity: RemoteEntity) => { setSelectedId(entity.id); @@ -342,15 +380,24 @@ export function SceneHierarchy({ entityStore, messageHub, commandManager }: Scen

{t('hierarchy.title')}

- {displaySceneName}{!isRemoteConnected && isSceneModified ? '*' : ''} + {displaySceneName} + {!isRemoteConnected && isSceneModified && ( + + )}
- {isRemoteConnected && ( + {isRemoteConnected && !isProfilerMode && (
+ {showLangMenu && ( +
+ {LANGUAGES.map(lang => ( + + ))} +
+ )} +
+ )} ); diff --git a/packages/editor-app/src/components/Viewport.tsx b/packages/editor-app/src/components/Viewport.tsx index 1486029e..0161ca48 100644 --- a/packages/editor-app/src/components/Viewport.tsx +++ b/packages/editor-app/src/components/Viewport.tsx @@ -748,61 +748,55 @@ export function Viewport({ locale = 'en', messageHub }: ViewportProps) {
- {/* Transform tools */} - - - - + {/* Transform tools group */} +
+ + + + +
- {/* Playback controls */} - - - + {/* View options group */} +
+ + +
{/* Run options dropdown */}
@@ -811,8 +805,8 @@ export function Viewport({ locale = 'en', messageHub }: ViewportProps) { onClick={() => setShowRunMenu(!showRunMenu)} title={locale === 'zh' ? '运行选项' : 'Run Options'} > - - + + {showRunMenu && (
@@ -827,43 +821,59 @@ export function Viewport({ locale = 'en', messageHub }: ViewportProps) {
)}
+
+ {/* Centered playback controls */} +
+
+ + + +
+
+
+ {Math.round(camera2DZoom * 100)}% +
-
- - -
-
diff --git a/packages/editor-app/src/components/inspectors/common/CodePreview.tsx b/packages/editor-app/src/components/inspectors/common/CodePreview.tsx new file mode 100644 index 00000000..f1836a9f --- /dev/null +++ b/packages/editor-app/src/components/inspectors/common/CodePreview.tsx @@ -0,0 +1,123 @@ +import Editor from '@monaco-editor/react'; + +interface CodePreviewProps { + content: string; + language?: string; + height?: string | number; +} + +// 根据文件扩展名获取语言 +export function getLanguageFromExtension(extension?: string): string { + if (!extension) return 'plaintext'; + + const languageMap: Record = { + // JavaScript/TypeScript + 'js': 'javascript', + 'jsx': 'javascript', + 'ts': 'typescript', + 'tsx': 'typescript', + 'mjs': 'javascript', + 'cjs': 'javascript', + + // Web + 'html': 'html', + 'htm': 'html', + 'css': 'css', + 'scss': 'scss', + 'less': 'less', + 'vue': 'html', + 'svelte': 'html', + + // Data formats + 'json': 'json', + 'xml': 'xml', + 'yaml': 'yaml', + 'yml': 'yaml', + 'toml': 'ini', + + // Markdown + 'md': 'markdown', + 'mdx': 'markdown', + + // Shell + 'sh': 'shell', + 'bash': 'shell', + 'zsh': 'shell', + 'ps1': 'powershell', + 'bat': 'bat', + 'cmd': 'bat', + + // Other languages + 'py': 'python', + 'rs': 'rust', + 'go': 'go', + 'java': 'java', + 'cpp': 'cpp', + 'c': 'c', + 'h': 'c', + 'hpp': 'cpp', + 'cs': 'csharp', + 'rb': 'ruby', + 'php': 'php', + 'lua': 'lua', + 'sql': 'sql', + 'graphql': 'graphql', + 'gql': 'graphql', + + // Config files + 'gitignore': 'ini', + 'env': 'ini', + 'ini': 'ini', + 'conf': 'ini', + 'properties': 'ini', + + // ECS specific + 'ecs': 'json', + 'btree': 'json' + }; + + return languageMap[extension.toLowerCase()] || 'plaintext'; +} + +export function CodePreview({ content, language = 'plaintext', height = 300 }: CodePreviewProps) { + return ( +
+ + 加载中... +
+ } + /> +
+ ); +} diff --git a/packages/editor-app/src/components/inspectors/common/index.ts b/packages/editor-app/src/components/inspectors/common/index.ts index ac58e047..f0fc4cb2 100644 --- a/packages/editor-app/src/components/inspectors/common/index.ts +++ b/packages/editor-app/src/components/inspectors/common/index.ts @@ -1,5 +1,6 @@ export { ComponentItem } from './ComponentItem'; export { ImagePreview } from './ImagePreview'; export { PropertyField } from './PropertyField'; +export { CodePreview, getLanguageFromExtension } from './CodePreview'; export type { ComponentItemProps } from './ComponentItem'; export type { ImagePreviewProps } from './ImagePreview'; diff --git a/packages/editor-app/src/components/inspectors/fields/AssetField.css b/packages/editor-app/src/components/inspectors/fields/AssetField.css index 2639d6ac..aa41b205 100644 --- a/packages/editor-app/src/components/inspectors/fields/AssetField.css +++ b/packages/editor-app/src/components/inspectors/fields/AssetField.css @@ -1,4 +1,4 @@ -/* 虚幻引擎风格的资产选择框 */ +/* 资产选择框 */ .asset-field { margin-bottom: 6px; } diff --git a/packages/editor-app/src/components/inspectors/views/AssetFileInspector.tsx b/packages/editor-app/src/components/inspectors/views/AssetFileInspector.tsx index cbf9d4a8..3ad61178 100644 --- a/packages/editor-app/src/components/inspectors/views/AssetFileInspector.tsx +++ b/packages/editor-app/src/components/inspectors/views/AssetFileInspector.tsx @@ -1,7 +1,7 @@ import { Folder, File as FileIcon, Image as ImageIcon, Clock, HardDrive } from 'lucide-react'; import { convertFileSrc } from '@tauri-apps/api/core'; import { AssetFileInfo } from '../types'; -import { ImagePreview } from '../common'; +import { ImagePreview, CodePreview, getLanguageFromExtension } from '../common'; import '../../../styles/EntityInspector.css'; interface AssetFileInspectorProps { @@ -100,9 +100,13 @@ export function AssetFileInspector({ fileInfo, content, isImage }: AssetFileInsp )} {content && ( -
+
文件预览
-
{content}
+
)} diff --git a/packages/editor-app/src/components/inspectors/views/EntityInspector.tsx b/packages/editor-app/src/components/inspectors/views/EntityInspector.tsx index 6b27cec1..1017900f 100644 --- a/packages/editor-app/src/components/inspectors/views/EntityInspector.tsx +++ b/packages/editor-app/src/components/inspectors/views/EntityInspector.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; import { Settings, ChevronDown, ChevronRight, X, Plus, Box } from 'lucide-react'; -import { Entity, Component, Core, getComponentDependencies, getComponentTypeName } from '@esengine/ecs-framework'; +import { Entity, Component, Core, getComponentDependencies, getComponentTypeName, getComponentInstanceTypeName } from '@esengine/ecs-framework'; import { MessageHub, CommandManager, ComponentRegistry } from '@esengine/editor-core'; import { PropertyInspector } from '../../PropertyInspector'; import { NotificationService } from '../../../services/NotificationService'; @@ -200,7 +200,7 @@ export function EntityInspector({ entity, messageHub, commandManager, componentV ) : ( entity.components.map((component: Component, index: number) => { const isExpanded = expandedComponents.has(index); - const componentName = component.constructor?.name || 'Component'; + const componentName = getComponentInstanceTypeName(component); const componentInfo = componentRegistry?.getComponent(componentName); const iconName = (componentInfo as { icon?: string } | undefined)?.icon; const IconComponent = iconName && (LucideIcons as unknown as Record>)[iconName]; diff --git a/packages/editor-app/src/services/RuntimeResolver.ts b/packages/editor-app/src/services/RuntimeResolver.ts index 77eac301..83c4134a 100644 --- a/packages/editor-app/src/services/RuntimeResolver.ts +++ b/packages/editor-app/src/services/RuntimeResolver.ts @@ -61,6 +61,13 @@ export class RuntimeResolver { async initialize(): Promise { // Load runtime configuration const response = await fetch('/runtime.config.json'); + if (!response.ok) { + throw new Error(`Failed to load runtime configuration: ${response.status} ${response.statusText}`); + } + const contentType = response.headers.get('content-type'); + if (!contentType || !contentType.includes('application/json')) { + throw new Error(`Invalid runtime configuration response type: ${contentType}. Expected JSON but received ${await response.text().then(t => t.substring(0, 100))}`); + } this.config = await response.json(); // Determine base directory based on environment diff --git a/packages/editor-app/src/styles/App.css b/packages/editor-app/src/styles/App.css index e7dd94a3..9fe832f0 100644 --- a/packages/editor-app/src/styles/App.css +++ b/packages/editor-app/src/styles/App.css @@ -24,50 +24,57 @@ color: var(--color-text-primary); } +.editor-titlebar { + display: flex; + align-items: center; + justify-content: center; + height: 22px; + background: linear-gradient(to bottom, #3a3a3f, #2a2a2f); + border-bottom: 1px solid rgba(0, 0, 0, 0.3); + flex-shrink: 0; + position: relative; + -webkit-app-region: drag; +} + +.titlebar-project-name { + position: absolute; + left: 8px; + font-size: 11px; + font-weight: 500; + color: #fff; +} + +.titlebar-app-name { + font-size: 11px; + color: #888; +} + .editor-header { display: flex; align-items: center; justify-content: space-between; - height: var(--layout-header-height); - padding: 0 var(--spacing-lg); - background-color: var(--color-bg-elevated); - border-bottom: 1px solid var(--color-border-default); + height: 28px; + padding: 0 8px; + background-color: #1a1a1f; + border-bottom: 1px solid rgba(255, 255, 255, 0.06); flex-shrink: 0; transition: all 0.3s ease; z-index: 100; position: relative; } -.editor-header.remote-connected { - background-color: rgba(16, 185, 129, 0.15); - border-bottom-color: rgba(16, 185, 129, 0.5); -} - .editor-header.remote-connected .status { - color: rgb(16, 185, 129); - font-weight: 600; + color: #4ade80; } .editor-header.remote-connected .status::before { - background-color: rgb(16, 185, 129); - animation: pulse-green 1.5s ease-in-out infinite; -} - -@keyframes pulse-green { - 0%, 100% { - opacity: 1; - transform: scale(1); - } - 50% { - opacity: 0.6; - transform: scale(1.2); - } + background-color: #4ade80; } .header-right { display: flex; align-items: center; - gap: var(--spacing-md); + gap: 6px; margin-left: auto; } @@ -119,35 +126,94 @@ } .locale-btn { - width: var(--size-button-sm); - height: var(--size-button-sm); + width: 22px; + height: 22px; padding: 0; background-color: transparent; - color: var(--color-text-primary); - border: 1px solid var(--color-border-default); + color: #888; + border: none; + border-radius: 2px; } .locale-btn:hover:not(:disabled) { - background-color: var(--color-bg-hover); - color: var(--color-primary); - border-color: var(--color-primary); + background-color: rgba(255, 255, 255, 0.08); + color: #ccc; + border-color: transparent; + transform: none; + box-shadow: none; +} + +.locale-dropdown { + position: relative; +} + +.locale-btn { + display: flex; + align-items: center; + gap: 4px; + width: auto; + padding: 0 6px; +} + +.locale-label { + font-size: 10px; +} + +.locale-menu { + position: absolute; + top: 100%; + right: 0; + margin-top: 4px; + min-width: 100px; + background: #252529; + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 3px; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.6); + padding: 4px 0; + z-index: 1000; +} + +.locale-menu-item { + display: block; + width: 100%; + padding: 6px 12px; + background: transparent; + border: none; + color: #ccc; + font-size: 11px; + text-align: left; + cursor: pointer; + transition: all 0.1s; +} + +.locale-menu-item:hover { + background: #3b82f6; + color: #fff; +} + +.locale-menu-item.active { + color: #3b82f6; +} + +.locale-menu-item.active:hover { + color: #fff; } .editor-header .status { display: flex; align-items: center; - gap: var(--spacing-xs); - font-size: var(--font-size-sm); - color: var(--color-success); + gap: 4px; + font-size: 10px; + color: #4ade80; white-space: nowrap; } .editor-header .status::before { content: ''; - width: 6px; - height: 6px; - background-color: var(--color-success); - border-radius: var(--radius-full); + width: 5px; + height: 5px; + background-color: #4ade80; + border-radius: 50%; animation: pulse 2s ease-in-out infinite; } diff --git a/packages/editor-app/src/styles/EntityInspector.css b/packages/editor-app/src/styles/EntityInspector.css index cb76340b..c475e702 100644 --- a/packages/editor-app/src/styles/EntityInspector.css +++ b/packages/editor-app/src/styles/EntityInspector.css @@ -2,7 +2,7 @@ display: flex; flex-direction: column; height: 100%; - background-color: var(--color-bg-base); + background-color: #1a1a1f; color: var(--color-text-primary); position: relative; } @@ -10,11 +10,11 @@ .inspector-header { display: flex; align-items: center; - gap: var(--spacing-sm); - height: var(--layout-panel-header); - padding: 0 var(--spacing-md); - border-bottom: 1px solid var(--color-border-default); - background-color: var(--color-bg-elevated); + gap: 8px; + height: 32px; + padding: 0 10px; + border-bottom: 1px solid rgba(255, 255, 255, 0.08); + background-color: #252529; flex-shrink: 0; } @@ -34,9 +34,11 @@ .inspector-content { flex: 1; + display: flex; + flex-direction: column; overflow-y: scroll; overflow-x: hidden; - padding: var(--spacing-md); + padding: 8px; min-height: 0; } @@ -63,10 +65,10 @@ } .inspector-section { - margin-bottom: 20px; - padding: 12px; - background-color: rgba(255, 255, 255, 0.02); - border-radius: 6px; + margin-bottom: 8px; + padding: 0; + background-color: transparent; + border-radius: 0; } .inspector-section:last-child { @@ -306,32 +308,34 @@ } .entity-name { - font-size: 14px; - font-weight: 600; + font-size: 12px; + font-weight: 500; color: var(--color-text-primary); } .section-title { - font-size: 11px; + font-size: 10px; font-weight: 600; - color: var(--color-text-secondary); + color: #888; text-transform: uppercase; - letter-spacing: 0.05em; - margin: -4px -4px 12px -4px; - padding-bottom: 8px; - border-bottom: 1px solid var(--color-border-subtle); + letter-spacing: 0.08em; + margin: 0 0 6px 0; + padding: 6px 8px; + background: rgba(0, 0, 0, 0.3); + border-radius: 3px; } .property-field { display: flex; justify-content: space-between; - align-items: flex-start; - padding: 8px 6px; - font-size: 12px; - gap: 16px; - background-color: rgba(255, 255, 255, 0.01); - border-radius: 4px; - margin-bottom: 4px; + align-items: center; + padding: 4px 8px; + font-size: 11px; + gap: 8px; + background-color: transparent; + border-radius: 0; + margin-bottom: 1px; + min-height: 24px; } .property-field:last-child { @@ -343,19 +347,20 @@ } .property-label { - color: var(--color-text-secondary); - font-weight: 500; + color: #888; + font-weight: 400; flex-shrink: 0; - min-width: 80px; - font-size: 12px; + min-width: 70px; + font-size: 11px; } .property-value-text { - color: var(--color-text-primary); + color: #ccc; text-align: right; word-break: break-word; - font-size: 12px; + font-size: 11px; flex: 1; + font-family: var(--font-family-mono); } .component-remove-btn { @@ -480,26 +485,24 @@ .add-component-trigger { display: flex; align-items: center; - gap: 4px; - padding: 4px 8px; - background: var(--color-primary); + gap: 3px; + padding: 3px 6px; + background: #3b82f6; border: none; - border-radius: 4px; - color: var(--color-text-inverse); + border-radius: 2px; + color: #fff; cursor: pointer; - font-size: 11px; + font-size: 10px; font-weight: 500; - transition: all 0.15s ease; + transition: all 0.1s ease; } .add-component-trigger:hover { - background: var(--color-primary-hover); - transform: translateY(-1px); - box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3); + background: #2563eb; } .add-component-trigger:active { - transform: translateY(0); + background: #1d4ed8; } .component-dropdown-overlay { @@ -630,55 +633,73 @@ /* 组件列表项样式 */ .component-item-card { - margin-bottom: 4px; - background: var(--color-bg-elevated); - border: 1px solid var(--color-border-default); - border-radius: 6px; + margin-bottom: 2px; + background: #2a2a2f; + border: none; + border-radius: 0; overflow: hidden; - transition: all 0.15s ease; + transition: none; + border-left: 3px solid #4a4a50; } .component-item-card:hover { - border-color: var(--color-border-strong); + background: #2e2e33; } .component-item-card.expanded { - border-color: var(--color-primary); - box-shadow: 0 2px 8px rgba(59, 130, 246, 0.15); + border-left-color: #3b82f6; + background: #252529; } .component-item-header { display: flex; align-items: center; - padding: 8px 10px; - background: var(--color-bg-base); + padding: 6px 8px; + background: transparent; cursor: pointer; user-select: none; transition: background 0.1s ease; + min-height: 28px; } .component-item-header:hover { - background: var(--color-bg-hover); + background: rgba(255, 255, 255, 0.03); } .component-expand-icon { display: flex; align-items: center; - color: var(--color-text-tertiary); + color: #666; transition: color 0.1s ease; + width: 14px; } .component-item-card:hover .component-expand-icon, .component-item-card.expanded .component-expand-icon { - color: var(--color-primary); + color: #3b82f6; +} + +.component-icon { + display: flex; + align-items: center; + color: #666; + margin-left: 4px; + margin-right: 6px; +} + +.component-item-card.expanded .component-icon { + color: #3b82f6; } .component-item-name { flex: 1; - margin-left: 6px; - font-size: 12px; + font-size: 11px; font-weight: 500; - color: var(--color-text-primary); + color: #ccc; +} + +.component-item-card.expanded .component-item-name { + color: #fff; } .component-item-card .component-remove-btn { @@ -687,10 +708,10 @@ justify-content: center; background: transparent; border: none; - color: var(--color-text-tertiary); + color: #555; cursor: pointer; - padding: 4px; - border-radius: 4px; + padding: 3px; + border-radius: 2px; opacity: 0; transition: all 0.1s ease; } @@ -700,14 +721,55 @@ } .component-item-card .component-remove-btn:hover { - background: var(--color-error); - color: var(--color-text-inverse); + background: #ef4444; + color: #fff; } .component-item-content { - padding: 8px 10px; - border-top: 1px solid var(--color-border-default); - background: var(--color-bg-base); + padding: 6px 8px 8px 8px; + border-top: 1px solid rgba(255, 255, 255, 0.05); + background: #1e1e23; overflow: hidden; min-width: 0; } + +/* Property rows inside component */ +.component-item-content .property-row { + display: flex; + align-items: center; + min-height: 22px; + padding: 2px 0; +} + +.component-item-content .property-row:hover { + background: rgba(255, 255, 255, 0.02); +} + +/* Code Preview */ +.code-preview-section { + flex: 1; + display: flex; + flex-direction: column; + min-height: 200px; +} + +.code-preview-section .code-preview-container { + flex: 1; + min-height: 0; +} + +.code-preview-container { + border-radius: 4px; + overflow: hidden; + border: 1px solid rgba(255, 255, 255, 0.08); + height: 100%; +} + +.code-preview-loading { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + color: #888; + font-size: 12px; +} diff --git a/packages/editor-app/src/styles/MenuBar.css b/packages/editor-app/src/styles/MenuBar.css index 04424dc6..4c81fd0a 100644 --- a/packages/editor-app/src/styles/MenuBar.css +++ b/packages/editor-app/src/styles/MenuBar.css @@ -1,7 +1,7 @@ .menu-bar { display: flex; align-items: center; - gap: 2px; + gap: 0; height: 100%; user-select: none; } @@ -11,37 +11,39 @@ } .menu-button { - height: 32px; - padding: 0 12px; + height: 100%; + padding: 0 10px; background: transparent; border: none; - color: var(--color-text-primary, #cccccc); - font-size: 13px; + color: #999; + font-size: 12px; cursor: pointer; - transition: background-color 0.15s; - border-radius: 3px; + transition: all 0.1s; + border-radius: 0; } .menu-button:hover { - background-color: var(--color-bg-hover, rgba(255, 255, 255, 0.1)); + background-color: rgba(255, 255, 255, 0.06); + color: #ccc; } .menu-button.active { - background-color: var(--color-bg-active, rgba(255, 255, 255, 0.15)); + background-color: rgba(255, 255, 255, 0.1); + color: #fff; } .menu-dropdown { position: absolute; top: 100%; left: 0; - min-width: 200px; - background: var(--color-bg-elevated, #252526); - border: 1px solid var(--color-border-default, #3e3e42); - border-radius: 4px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); + min-width: 180px; + background: #252529; + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 3px; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.6); padding: 4px 0; z-index: 1000; - margin-top: 2px; + margin-top: 0; } .menu-dropdown-item { @@ -49,22 +51,23 @@ display: flex; justify-content: space-between; align-items: center; - padding: 6px 16px; + padding: 5px 12px; background: transparent; border: none; - color: var(--color-text-primary, #cccccc); - font-size: 13px; + color: #ccc; + font-size: 11px; cursor: pointer; text-align: left; - transition: background-color 0.15s; + transition: background-color 0.1s; } .menu-dropdown-item:hover:not(.disabled) { - background-color: var(--color-bg-hover, rgba(255, 255, 255, 0.1)); + background-color: #3b82f6; + color: #fff; } .menu-dropdown-item.disabled { - color: var(--color-text-disabled, #6e6e6e); + color: #555; cursor: not-allowed; opacity: 0.5; } @@ -72,17 +75,23 @@ .menu-item-content { display: flex; align-items: center; - gap: 8px; + gap: 6px; +} + +.menu-item-content svg { + width: 12px; + height: 12px; + opacity: 0.7; } .menu-shortcut { - margin-left: 24px; - color: var(--color-text-secondary, #858585); - font-size: 12px; + margin-left: 16px; + color: #666; + font-size: 10px; } .menu-separator { height: 1px; - background-color: var(--color-border-default, #3e3e42); + background-color: rgba(255, 255, 255, 0.08); margin: 4px 8px; } diff --git a/packages/editor-app/src/styles/ProjectCreationWizard.css b/packages/editor-app/src/styles/ProjectCreationWizard.css new file mode 100644 index 00000000..744008fc --- /dev/null +++ b/packages/editor-app/src/styles/ProjectCreationWizard.css @@ -0,0 +1,333 @@ +.project-wizard-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.8); + display: flex; + align-items: center; + justify-content: center; + z-index: 10000; + backdrop-filter: blur(4px); +} + +.project-wizard { + width: 1000px; + max-width: 90vw; + height: 650px; + max-height: 85vh; + background: #1e1e23; + border-radius: 8px; + display: flex; + flex-direction: column; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5); + overflow: hidden; +} + +.wizard-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 20px; + background: #252529; + border-bottom: 1px solid rgba(255, 255, 255, 0.08); +} + +.wizard-header h1 { + font-size: 16px; + font-weight: 600; + color: #fff; + margin: 0; +} + +.wizard-close-btn { + display: flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + background: transparent; + border: none; + border-radius: 4px; + color: #888; + cursor: pointer; + transition: all 0.15s; +} + +.wizard-close-btn:hover { + background: rgba(255, 255, 255, 0.1); + color: #fff; +} + +.wizard-body { + flex: 1; + display: flex; + overflow: hidden; +} + +/* Templates grid */ +.wizard-templates { + flex: 1; + padding: 16px; + overflow-y: auto; + background: #1a1a1f; + display: flex; + flex-direction: column; +} + +.templates-header { + margin-bottom: 16px; +} + +.templates-header h3 { + font-size: 12px; + font-weight: 600; + color: #888; + text-transform: uppercase; + letter-spacing: 0.08em; + margin: 0; +} + +.templates-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); + gap: 12px; +} + +.template-card { + display: flex; + flex-direction: column; + align-items: center; + padding: 12px; + background: #252529; + border: 2px solid transparent; + border-radius: 6px; + cursor: pointer; + transition: all 0.15s; +} + +.template-card:hover { + background: #2a2a2f; + border-color: rgba(255, 255, 255, 0.1); +} + +.template-card.selected { + border-color: #3b82f6; + background: rgba(59, 130, 246, 0.1); +} + +.template-preview { + width: 64px; + height: 64px; + display: flex; + align-items: center; + justify-content: center; + background: rgba(0, 0, 0, 0.3); + border-radius: 4px; + color: #666; + margin-bottom: 8px; +} + +.template-card.selected .template-preview { + color: #3b82f6; +} + +.template-name { + font-size: 11px; + color: #ccc; + text-align: center; + word-break: break-word; +} + +.template-card.selected .template-name { + color: #fff; +} + +/* Details panel */ +.wizard-details { + width: 280px; + background: #252529; + border-left: 1px solid rgba(255, 255, 255, 0.08); + display: flex; + flex-direction: column; + overflow-y: auto; +} + +.details-preview { + padding: 16px; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); +} + +.preview-placeholder { + width: 100%; + height: 140px; + background: #1a1a1f; + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; + color: #444; +} + +.details-info { + padding: 16px; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); +} + +.details-info h2 { + font-size: 14px; + font-weight: 600; + color: #fff; + margin: 0 0 8px 0; +} + +.details-info p { + font-size: 12px; + color: #888; + margin: 0; + line-height: 1.5; +} + +.details-settings { + padding: 16px; + flex: 1; +} + +.details-settings h3 { + font-size: 11px; + font-weight: 600; + color: #666; + text-transform: uppercase; + letter-spacing: 0.08em; + margin: 0 0 12px 0; +} + +.setting-field { + margin-bottom: 12px; +} + +.setting-field label { + display: block; + font-size: 11px; + color: #888; + margin-bottom: 6px; +} + +.setting-field input { + width: 100%; + padding: 8px 10px; + background: #1a1a1f; + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 4px; + color: #fff; + font-size: 12px; + outline: none; + transition: border-color 0.15s; +} + +.setting-field input:focus { + border-color: #3b82f6; +} + +.setting-field input::placeholder { + color: #555; +} + +.path-input-group { + display: flex; + gap: 4px; +} + +.path-input-group input { + flex: 1; +} + +.browse-btn { + display: flex; + align-items: center; + justify-content: center; + width: 32px; + background: #3b82f6; + border: none; + border-radius: 4px; + color: #fff; + cursor: pointer; + transition: background 0.15s; +} + +.browse-btn:hover { + background: #2563eb; +} + +/* Footer */ +.wizard-footer { + display: flex; + align-items: center; + justify-content: flex-end; + gap: 8px; + padding: 16px 20px; + background: #252529; + border-top: 1px solid rgba(255, 255, 255, 0.08); +} + +.wizard-btn { + padding: 8px 20px; + border: none; + border-radius: 4px; + font-size: 12px; + font-weight: 500; + cursor: pointer; + transition: all 0.15s; +} + +.wizard-btn.secondary { + background: transparent; + color: #888; +} + +.wizard-btn.secondary:hover { + background: rgba(255, 255, 255, 0.05); + color: #ccc; +} + +.wizard-btn.primary { + background: #3b82f6; + color: #fff; +} + +.wizard-btn.primary:hover { + background: #2563eb; +} + +.wizard-btn.primary:disabled { + background: #3b3b3f; + color: #666; + cursor: not-allowed; +} + +/* Scrollbar */ +.wizard-sidebar::-webkit-scrollbar, +.wizard-templates::-webkit-scrollbar, +.wizard-details::-webkit-scrollbar { + width: 6px; +} + +.wizard-sidebar::-webkit-scrollbar-track, +.wizard-templates::-webkit-scrollbar-track, +.wizard-details::-webkit-scrollbar-track { + background: transparent; +} + +.wizard-sidebar::-webkit-scrollbar-thumb, +.wizard-templates::-webkit-scrollbar-thumb, +.wizard-details::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.1); + border-radius: 3px; +} + +.wizard-sidebar::-webkit-scrollbar-thumb:hover, +.wizard-templates::-webkit-scrollbar-thumb:hover, +.wizard-details::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.2); +} diff --git a/packages/editor-app/src/styles/SceneHierarchy.css b/packages/editor-app/src/styles/SceneHierarchy.css index e9559837..15df0693 100644 --- a/packages/editor-app/src/styles/SceneHierarchy.css +++ b/packages/editor-app/src/styles/SceneHierarchy.css @@ -39,13 +39,14 @@ .scene-name-container { display: flex; align-items: center; - padding: 2px 8px; + padding: 1px 6px; background-color: var(--color-bg-base); border: 1px solid var(--color-border-default); border-radius: var(--radius-sm); margin-left: auto; margin-right: var(--spacing-sm); transition: all var(--transition-fast); + height: 20px; } .scene-name-container.clickable { @@ -61,6 +62,32 @@ color: var(--color-primary); } +.scene-name-container.modified { + background-color: rgba(251, 191, 36, 0.25); + border-color: rgb(251, 191, 36); +} + +.scene-name-container.modified .scene-name { + color: rgb(251, 191, 36); + font-weight: 600; +} + +.modified-indicator { + color: rgb(251, 191, 36); + font-size: 8px; + margin-left: 4px; + animation: pulse-modified 2s ease-in-out infinite; +} + +@keyframes pulse-modified { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0.4; + } +} + .scene-name { font-size: var(--font-size-xs); color: var(--color-text-secondary); @@ -355,6 +382,14 @@ color: var(--color-text-tertiary); } +.entity-item.dragging { + opacity: 0.5; +} + +.entity-item.drop-target { + border-top: 2px solid var(--color-primary); +} + .entity-tag { display: flex; align-items: center; diff --git a/packages/editor-app/src/styles/StartupPage.css b/packages/editor-app/src/styles/StartupPage.css index 5fb6d65d..61e793e4 100644 --- a/packages/editor-app/src/styles/StartupPage.css +++ b/packages/editor-app/src/styles/StartupPage.css @@ -175,11 +175,78 @@ } .startup-footer { + display: flex; + align-items: center; + justify-content: space-between; padding: 20px 40px; - border-top: 1px solid #1e1e1e; + border-top: 1px solid #2a2a2f; } .startup-version { font-size: 11px; color: #6e6e6e; } + +.startup-locale-dropdown { + position: relative; +} + +.startup-locale-btn { + display: flex; + align-items: center; + gap: 6px; + padding: 6px 12px; + background: transparent; + border: 1px solid #3e3e42; + border-radius: 3px; + color: #888; + font-size: 11px; + cursor: pointer; + transition: all 0.15s; +} + +.startup-locale-btn:hover { + background: rgba(255, 255, 255, 0.05); + border-color: #555; + color: #ccc; +} + +.startup-locale-menu { + position: absolute; + bottom: 100%; + right: 0; + margin-bottom: 4px; + min-width: 120px; + background: #252529; + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 3px; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.6); + padding: 4px 0; + z-index: 1000; +} + +.startup-locale-item { + display: block; + width: 100%; + padding: 6px 12px; + background: transparent; + border: none; + color: #ccc; + font-size: 11px; + text-align: left; + cursor: pointer; + transition: all 0.1s; +} + +.startup-locale-item:hover { + background: #3b82f6; + color: #fff; +} + +.startup-locale-item.active { + color: #3b82f6; +} + +.startup-locale-item.active:hover { + color: #fff; +} diff --git a/packages/editor-app/src/styles/UserProfile.css b/packages/editor-app/src/styles/UserProfile.css index d9a9cb81..89c703e2 100644 --- a/packages/editor-app/src/styles/UserProfile.css +++ b/packages/editor-app/src/styles/UserProfile.css @@ -7,20 +7,21 @@ .login-button { display: flex; align-items: center; - gap: 6px; - padding: 6px 12px; - background: var(--color-accent, #0e639c); + gap: 4px; + padding: 3px 8px; + background: transparent; border: none; - border-radius: 6px; - color: white; - font-size: 13px; + border-radius: 2px; + color: #888; + font-size: 10px; font-weight: 500; cursor: pointer; - transition: all 0.2s; + transition: all 0.1s; } .login-button:hover:not(:disabled) { - background: var(--color-accent-hover, #1177bb); + background: rgba(255, 255, 255, 0.08); + color: #ccc; } .login-button:disabled { @@ -44,46 +45,46 @@ .user-avatar-button { display: flex; align-items: center; - gap: 8px; - padding: 4px 12px 4px 4px; - background: var(--color-bg-secondary, #252526); - border: 1px solid var(--color-border, #333); - border-radius: 20px; - color: var(--color-text-primary, #cccccc); - font-size: 13px; + gap: 4px; + padding: 2px 6px 2px 2px; + background: transparent; + border: none; + border-radius: 2px; + color: #888; + font-size: 10px; font-weight: 500; cursor: pointer; - transition: all 0.2s; + transition: all 0.1s; } .user-avatar-button:hover { - background: var(--color-bg-hover, #2d2d30); - border-color: var(--color-accent, #0e639c); + background: rgba(255, 255, 255, 0.08); + color: #ccc; } .user-avatar, .user-avatar-placeholder { - width: 28px; - height: 28px; + width: 16px; + height: 16px; border-radius: 50%; } .user-avatar { object-fit: cover; - border: 2px solid var(--color-accent, #0e639c); + border: none; } .user-avatar-placeholder { display: flex; align-items: center; justify-content: center; - background: var(--color-accent-bg, rgba(14, 99, 156, 0.2)); - color: var(--color-accent, #0e639c); - border: 2px solid var(--color-accent, #0e639c); + background: rgba(255, 255, 255, 0.1); + color: #888; + border: none; } .user-name { - max-width: 120px; + max-width: 80px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; diff --git a/packages/editor-app/src/styles/Viewport.css b/packages/editor-app/src/styles/Viewport.css index 56097fdc..83f64c7a 100644 --- a/packages/editor-app/src/styles/Viewport.css +++ b/packages/editor-app/src/styles/Viewport.css @@ -12,66 +12,125 @@ display: flex; align-items: center; justify-content: space-between; - padding: 6px 8px; + padding: 4px 6px; background: var(--color-bg-elevated); border-bottom: 1px solid var(--color-border-default); flex-shrink: 0; - gap: 8px; + gap: 4px; z-index: 10; + height: 36px; } .viewport-toolbar-left { display: flex; align-items: center; - gap: 4px; + gap: 2px; +} + +.viewport-toolbar-center { + display: flex; + align-items: center; + gap: 2px; + position: absolute; + left: 50%; + transform: translateX(-50%); } .viewport-toolbar-right { display: flex; align-items: center; - gap: 4px; + gap: 2px; } .viewport-btn { display: inline-flex; align-items: center; justify-content: center; - width: 32px; - height: 32px; + width: 26px; + height: 26px; background: transparent; border: 1px solid transparent; - border-radius: var(--radius-sm); + border-radius: 3px; color: var(--color-text-secondary); cursor: pointer; - transition: all var(--transition-fast); + transition: all 0.1s ease; padding: 0; } .viewport-btn:hover { - background: var(--color-bg-hover); + background: rgba(255, 255, 255, 0.08); color: var(--color-text-primary); - border-color: var(--color-border-hover); } .viewport-btn.active { background: var(--color-primary); color: var(--color-text-inverse); - border-color: var(--color-primary); } .viewport-btn:active { - transform: scale(0.95); + transform: scale(0.96); } .viewport-btn:disabled { - opacity: 0.4; + opacity: 0.35; cursor: not-allowed; } .viewport-btn:disabled:hover { background: transparent; color: var(--color-text-secondary); - border-color: transparent; +} + +/* Button group styling */ +.viewport-btn-group { + display: flex; + align-items: center; + background: rgba(0, 0, 0, 0.2); + border-radius: 4px; + padding: 2px; + gap: 1px; +} + +.viewport-btn-group .viewport-btn { + border-radius: 2px; +} + +/* Playback controls special styling */ +.viewport-playback { + display: flex; + align-items: center; + background: rgba(0, 0, 0, 0.3); + border-radius: 4px; + padding: 2px 4px; + gap: 2px; +} + +.viewport-playback .viewport-btn { + width: 28px; + height: 28px; +} + +.viewport-playback .viewport-btn.play-btn { + color: #4ade80; +} + +.viewport-playback .viewport-btn.play-btn:hover { + background: rgba(74, 222, 128, 0.15); +} + +.viewport-playback .viewport-btn.play-btn.active { + background: #4ade80; + color: #000; +} + +.viewport-playback .viewport-btn.pause-btn.active { + background: #fbbf24; + color: #000; +} + +.viewport-playback .viewport-btn.stop-btn:hover { + background: rgba(239, 68, 68, 0.15); + color: #ef4444; } /* Dropdown */ @@ -119,9 +178,43 @@ .viewport-divider { width: 1px; - height: 24px; - background: var(--color-border-default); - margin: 0 4px; + height: 20px; + background: rgba(255, 255, 255, 0.1); + margin: 0 6px; +} + +/* Coordinate system indicator */ +.viewport-coord-indicator { + display: flex; + align-items: center; + gap: 4px; + padding: 2px 6px; + background: rgba(0, 0, 0, 0.2); + border-radius: 3px; + font-size: 10px; + font-weight: 600; + color: var(--color-text-secondary); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.viewport-coord-indicator.world { + color: #60a5fa; +} + +.viewport-coord-indicator.local { + color: #fbbf24; +} + +/* Zoom indicator */ +.viewport-zoom { + display: flex; + align-items: center; + padding: 2px 6px; + font-size: 10px; + font-weight: 500; + color: var(--color-text-secondary); + font-variant-numeric: tabular-nums; } .viewport-canvas { diff --git a/packages/editor-core/src/Services/EntityStoreService.ts b/packages/editor-core/src/Services/EntityStoreService.ts index 5472e847..1ac7898a 100644 --- a/packages/editor-core/src/Services/EntityStoreService.ts +++ b/packages/editor-core/src/Services/EntityStoreService.ts @@ -1,4 +1,4 @@ -import { Injectable, IService, Entity } from '@esengine/ecs-framework'; +import { Injectable, IService, Entity, Core } from '@esengine/ecs-framework'; import { MessageHub } from './MessageHub'; export interface EntityTreeNode { @@ -14,21 +14,21 @@ export interface EntityTreeNode { export class EntityStoreService implements IService { private entities: Map = new Map(); private selectedEntity: Entity | null = null; - private rootEntities: Set = new Set(); + private rootEntityIds: number[] = []; constructor(private messageHub: MessageHub) {} public dispose(): void { this.entities.clear(); - this.rootEntities.clear(); + this.rootEntityIds = []; this.selectedEntity = null; } public addEntity(entity: Entity, parent?: Entity): void { this.entities.set(entity.id, entity); - if (!parent) { - this.rootEntities.add(entity.id); + if (!parent && !this.rootEntityIds.includes(entity.id)) { + this.rootEntityIds.push(entity.id); } this.messageHub.publish('entity:added', { entity, parent }); @@ -36,7 +36,10 @@ export class EntityStoreService implements IService { public removeEntity(entity: Entity): void { this.entities.delete(entity.id); - this.rootEntities.delete(entity.id); + const idx = this.rootEntityIds.indexOf(entity.id); + if (idx !== -1) { + this.rootEntityIds.splice(idx, 1); + } if (this.selectedEntity?.id === entity.id) { this.selectedEntity = null; @@ -60,7 +63,7 @@ export class EntityStoreService implements IService { } public getRootEntities(): Entity[] { - return Array.from(this.rootEntities) + return this.rootEntityIds .map((id) => this.entities.get(id)) .filter((e): e is Entity => e !== undefined); } @@ -71,8 +74,40 @@ export class EntityStoreService implements IService { public clear(): void { this.entities.clear(); - this.rootEntities.clear(); + this.rootEntityIds = []; this.selectedEntity = null; this.messageHub.publish('entities:cleared', {}); } + + public syncFromScene(): void { + const scene = Core.scene; + if (!scene) return; + + this.entities.clear(); + this.rootEntityIds = []; + + scene.entities.forEach((entity) => { + this.entities.set(entity.id, entity); + if (!entity.parent) { + this.rootEntityIds.push(entity.id); + } + }); + } + + public reorderEntity(entityId: number, newIndex: number): void { + const idx = this.rootEntityIds.indexOf(entityId); + if (idx === -1 || idx === newIndex) return; + + const clampedIndex = Math.max(0, Math.min(newIndex, this.rootEntityIds.length - 1)); + + this.rootEntityIds.splice(idx, 1); + this.rootEntityIds.splice(clampedIndex, 0, entityId); + + const scene = Core.scene; + if (scene) { + scene.entities.reorderEntity(entityId, clampedIndex); + } + + this.messageHub.publish('entity:reordered', { entityId, newIndex: clampedIndex }); + } } diff --git a/packages/editor-core/src/Services/LogService.ts b/packages/editor-core/src/Services/LogService.ts index 61936e74..56c54368 100644 --- a/packages/editor-core/src/Services/LogService.ts +++ b/packages/editor-core/src/Services/LogService.ts @@ -186,14 +186,6 @@ export class LogService implements IService { */ public clear(): void { this.logs = []; - this.notifyListeners({ - id: -1, - timestamp: new Date(), - level: LogLevel.Info, - source: 'system', - message: 'Logs cleared', - args: [] - }); } /** diff --git a/packages/editor-core/src/Services/SceneManagerService.ts b/packages/editor-core/src/Services/SceneManagerService.ts index 3ee80d73..3c92e56a 100644 --- a/packages/editor-core/src/Services/SceneManagerService.ts +++ b/packages/editor-core/src/Services/SceneManagerService.ts @@ -3,6 +3,7 @@ import { Injectable, Core, createLogger, SceneSerializer, Scene } from '@esengin import type { MessageHub } from './MessageHub'; import type { IFileAPI } from '../Types/IFileAPI'; import type { ProjectService } from './ProjectService'; +import type { EntityStoreService } from './EntityStoreService'; const logger = createLogger('SceneManagerService'); @@ -27,7 +28,8 @@ export class SceneManagerService implements IService { constructor( private messageHub: MessageHub, private fileAPI: IFileAPI, - private projectService?: ProjectService + private projectService?: ProjectService, + private entityStore?: EntityStoreService ) { this.setupAutoModificationTracking(); logger.info('SceneManagerService initialized'); @@ -55,6 +57,7 @@ export class SceneManagerService implements IService { isSaved: false }; + this.entityStore?.syncFromScene(); await this.messageHub.publish('scene:new', {}); logger.info('New scene created'); } @@ -98,6 +101,7 @@ export class SceneManagerService implements IService { isSaved: true }; + this.entityStore?.syncFromScene(); await this.messageHub.publish('scene:loaded', { path, sceneName, @@ -268,7 +272,11 @@ export class SceneManagerService implements IService { this.markAsModified(); }); - this.unsubscribeHandlers.push(unsubscribeEntityAdded, unsubscribeEntityRemoved); + const unsubscribeEntityReordered = this.messageHub.subscribe('entity:reordered', () => { + this.markAsModified(); + }); + + this.unsubscribeHandlers.push(unsubscribeEntityAdded, unsubscribeEntityRemoved, unsubscribeEntityReordered); logger.debug('Auto modification tracking setup complete'); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 48d4ebbb..f92caa67 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -92,7 +92,7 @@ importers: version: 9.39.1(jiti@2.6.1) jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.19.25)(ts-node@10.9.2(@swc/core@1.15.3)(@swc/wasm@1.15.3)(@types/node@20.19.25)(typescript@5.9.3)) + version: 29.7.0(@types/node@20.19.25) jest-environment-jsdom: specifier: ^29.7.0 version: 29.7.0 @@ -125,7 +125,7 @@ importers: version: 11.2.0 ts-jest: specifier: ^29.4.0 - version: 29.4.5(@babel/core@7.28.5)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.5))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.25)(ts-node@10.9.2(@swc/core@1.15.3)(@swc/wasm@1.15.3)(@types/node@20.19.25)(typescript@5.9.3)))(typescript@5.9.3) + version: 29.4.5(@babel/core@7.28.5)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.5))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.25))(typescript@5.9.3) typedoc: specifier: ^0.28.13 version: 0.28.14(typescript@5.9.3) @@ -149,7 +149,7 @@ importers: dependencies: '@esengine/ecs-framework': specifier: ^2.0.0 - version: 2.2.14 + version: link:../core devDependencies: '@rollup/plugin-commonjs': specifier: ^28.0.3 @@ -177,7 +177,7 @@ importers: dependencies: '@esengine/ecs-framework': specifier: ^2.2.8 - version: 2.2.14 + version: link:../core tslib: specifier: ^2.8.1 version: 2.8.1 @@ -214,7 +214,7 @@ importers: version: 20.19.25 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.19.25)(ts-node@10.9.2(@swc/core@1.15.3)(@swc/wasm@1.15.3)(@types/node@20.19.25)(typescript@5.9.3)) + version: 29.7.0(@types/node@20.19.25) rimraf: specifier: ^5.0.0 version: 5.0.10 @@ -226,7 +226,7 @@ importers: version: 6.2.3(rollup@4.53.3)(typescript@5.9.3) ts-jest: specifier: ^29.4.0 - version: 29.4.5(@babel/core@7.28.5)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.5))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.25)(ts-node@10.9.2(@swc/core@1.15.3)(@swc/wasm@1.15.3)(@types/node@20.19.25)(typescript@5.9.3)))(typescript@5.9.3) + version: 29.4.5(@babel/core@7.28.5)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.5))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.25))(typescript@5.9.3) typescript: specifier: ^5.8.3 version: 5.9.3 @@ -290,7 +290,7 @@ importers: version: 6.2.3(rollup@4.53.3)(typescript@5.9.3) rollup-plugin-postcss: specifier: ^4.0.2 - version: 4.0.2(postcss@8.5.6)(ts-node@10.9.2(@swc/core@1.15.3)(@swc/wasm@1.15.3)(@types/node@20.19.25)(typescript@5.9.3)) + version: 4.0.2(postcss@8.5.6) typescript: specifier: ^5.8.2 version: 5.9.3 @@ -305,7 +305,7 @@ importers: version: link:../asset-system '@esengine/ecs-framework': specifier: ^2.2.8 - version: 2.2.14 + version: link:../core tslib: specifier: ^2.8.1 version: 2.8.1 @@ -364,7 +364,7 @@ importers: version: 9.39.1(jiti@2.6.1) jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.19.25)(ts-node@10.9.2(@swc/core@1.15.3)(@swc/wasm@1.15.3)(@types/node@20.19.25)(typescript@5.9.3)) + version: 29.7.0(@types/node@20.19.25) jest-environment-jsdom: specifier: ^29.7.0 version: 29.7.0 @@ -379,7 +379,7 @@ importers: version: 6.2.3(rollup@4.53.3)(typescript@5.9.3) ts-jest: specifier: ^29.4.0 - version: 29.4.5(@babel/core@7.28.5)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.5))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.25)(ts-node@10.9.2(@swc/core@1.15.3)(@swc/wasm@1.15.3)(@types/node@20.19.25)(typescript@5.9.3)))(typescript@5.9.3) + version: 29.4.5(@babel/core@7.28.5)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.5))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.25))(typescript@5.9.3) typescript: specifier: ^5.8.3 version: 5.9.3 @@ -433,9 +433,15 @@ importers: '@esengine/engine': specifier: workspace:* version: link:../engine + '@monaco-editor/react': + specifier: ^4.7.0 + version: 4.7.0(monaco-editor@0.55.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@tauri-apps/api': specifier: ^2.2.0 version: 2.9.0 + '@tauri-apps/plugin-cli': + specifier: ^2.4.1 + version: 2.4.1 '@tauri-apps/plugin-dialog': specifier: ^2.4.0 version: 2.4.2 @@ -538,7 +544,7 @@ importers: dependencies: '@esengine/ecs-framework': specifier: ^2.2.8 - version: 2.2.14 + version: link:../core react: specifier: ^18.2.0 version: 18.3.1 @@ -593,7 +599,7 @@ importers: version: 9.39.1(jiti@2.6.1) jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.19.25)(ts-node@10.9.2(@swc/core@1.15.3)(@swc/wasm@1.15.3)(@types/node@20.19.25)(typescript@5.9.3)) + version: 29.7.0(@types/node@20.19.25) rimraf: specifier: ^5.0.0 version: 5.0.10 @@ -605,7 +611,7 @@ importers: version: 6.2.3(rollup@4.53.3)(typescript@5.9.3) ts-jest: specifier: ^29.4.0 - version: 29.4.5(@babel/core@7.28.5)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.5))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.25)(ts-node@10.9.2(@swc/core@1.15.3)(@swc/wasm@1.15.3)(@types/node@20.19.25)(typescript@5.9.3)))(typescript@5.9.3) + version: 29.4.5(@babel/core@7.28.5)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.5))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.25))(typescript@5.9.3) typescript: specifier: ^5.8.3 version: 5.9.3 @@ -638,7 +644,7 @@ importers: version: 20.19.25 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.19.25)(ts-node@10.9.2(@swc/core@1.15.3)(@swc/wasm@1.15.3)(@types/node@20.19.25)(typescript@5.9.3)) + version: 29.7.0(@types/node@20.19.25) jest-environment-jsdom: specifier: ^29.7.0 version: 29.7.0 @@ -653,7 +659,7 @@ importers: version: 6.2.3(rollup@4.53.3)(typescript@5.9.3) ts-jest: specifier: ^29.4.0 - version: 29.4.5(@babel/core@7.28.5)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.5))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.25)(ts-node@10.9.2(@swc/core@1.15.3)(@swc/wasm@1.15.3)(@types/node@20.19.25)(typescript@5.9.3)))(typescript@5.9.3) + version: 29.4.5(@babel/core@7.28.5)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.5))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.25))(typescript@5.9.3) typescript: specifier: ^5.8.3 version: 5.9.3 @@ -1508,10 +1514,6 @@ packages: resolution: {integrity: sha512-gwRLBLra/Dozj2OywopeuHj2ac26gjGkz2cZ+86cTJOdtWfiRRr4+e77ZDAGc6MDWxaWheI+mAV5TLWWRwqrFg==} engines: {node: '>=v18'} - '@cspotcode/source-map-support@0.8.1': - resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} - engines: {node: '>=12'} - '@docsearch/css@3.8.2': resolution: {integrity: sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==} @@ -1838,10 +1840,6 @@ packages: cpu: [x64] os: [win32] - '@esengine/ecs-framework@2.2.14': - resolution: {integrity: sha512-HWnXxkmHgATC3rS5RGfpHOXXF7JEWgdKLkCy6ROAoNJobkF/d27eyeTtOWdFsBNmJFYEdTl9/qDIACNNEnUvcA==} - engines: {node: '>=16.0.0'} - '@eslint-community/eslint-utils@4.9.0': resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2169,13 +2167,20 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@jridgewell/trace-mapping@0.3.9': - resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - '@lerna/create@8.2.4': resolution: {integrity: sha512-A8AlzetnS2WIuhijdAzKUyFpR5YbLLfV3luQ4lzBgIBgRfuoBDZeF+RSZPhra+7A6/zTUlrbhKZIOi/MNhqgvQ==} engines: {node: '>=18.0.0'} + '@monaco-editor/loader@1.7.0': + resolution: {integrity: sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA==} + + '@monaco-editor/react@4.7.0': + resolution: {integrity: sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==} + peerDependencies: + monaco-editor: '>= 0.25.0 < 1' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@napi-rs/wasm-runtime@0.2.4': resolution: {integrity: sha512-9zESzOO5aDByvhIAsOy9TbpZ0Ur2AJbUI7UT73kcUTS2mxAMHOBaa1st/jAymNoCtvrit99kkzT1FZuXVcgfIQ==} @@ -3004,6 +3009,9 @@ packages: engines: {node: '>= 10'} hasBin: true + '@tauri-apps/plugin-cli@2.4.1': + resolution: {integrity: sha512-8JXofQFI5cmiGolh1PlU4hzE2YJgrgB1lyaztyBYiiMCy13luVxBXaXChYPeqMkUo46J1UadxvYdjRjj0E8zaw==} + '@tauri-apps/plugin-dialog@2.4.2': resolution: {integrity: sha512-lNIn5CZuw8WZOn8zHzmFmDSzg5zfohWoa3mdULP0YFh/VogVdMVWZPcWSHlydsiJhRQYaTNSYKN7RmZKE2lCYQ==} @@ -3027,18 +3035,6 @@ packages: resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} engines: {node: '>=10.13.0'} - '@tsconfig/node10@1.0.12': - resolution: {integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==} - - '@tsconfig/node12@1.0.11': - resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} - - '@tsconfig/node14@1.0.3': - resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} - - '@tsconfig/node16@1.0.4': - resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} - '@tufjs/canonical-json@2.0.0': resolution: {integrity: sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==} engines: {node: ^16.14.0 || >=18.0.0} @@ -3188,6 +3184,9 @@ packages: '@types/tough-cookie@4.0.5': resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@types/unist@2.0.11': resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} @@ -3495,9 +3494,6 @@ packages: aproba@2.0.0: resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} - arg@4.1.3: - resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} - argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -3970,9 +3966,6 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true - create-require@1.1.1: - resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -4147,10 +4140,6 @@ packages: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - diff@4.0.2: - resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} - engines: {node: '>=0.3.1'} - dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -4170,6 +4159,9 @@ packages: resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} engines: {node: '>= 4'} + dompurify@3.2.7: + resolution: {integrity: sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==} + domutils@2.8.0: resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} @@ -5643,6 +5635,11 @@ packages: engines: {node: '>= 18'} hasBin: true + marked@14.0.0: + resolution: {integrity: sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==} + engines: {node: '>= 18'} + hasBin: true + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -5936,6 +5933,9 @@ packages: resolution: {integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==} engines: {node: '>=0.10.0'} + monaco-editor@0.55.1: + resolution: {integrity: sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -7278,6 +7278,9 @@ packages: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} + state-local@1.0.7: + resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==} + stream-combiner2@1.1.1: resolution: {integrity: sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==} @@ -7553,20 +7556,6 @@ packages: jest-util: optional: true - ts-node@10.9.2: - resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} - hasBin: true - peerDependencies: - '@swc/core': '>=1.2.50' - '@swc/wasm': '>=1.2.50' - '@types/node': '*' - typescript: '>=2.7' - peerDependenciesMeta: - '@swc/core': - optional: true - '@swc/wasm': - optional: true - tsconfig-paths@4.2.0: resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} engines: {node: '>=6'} @@ -7810,9 +7799,6 @@ packages: resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} hasBin: true - v8-compile-cache-lib@3.0.1: - resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} - v8-to-istanbul@9.3.0: resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} engines: {node: '>=10.12.0'} @@ -8114,10 +8100,6 @@ packages: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} - yn@3.1.1: - resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} - engines: {node: '>=6'} - yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -9130,11 +9112,6 @@ snapshots: dependencies: chalk: 4.1.2 - '@cspotcode/source-map-support@0.8.1': - dependencies: - '@jridgewell/trace-mapping': 0.3.9 - optional: true - '@docsearch/css@3.8.2': {} '@docsearch/js@3.8.2(@algolia/client-search@5.44.0)(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)': @@ -9322,8 +9299,6 @@ snapshots: '@esbuild/win32-x64@0.25.12': optional: true - '@esengine/ecs-framework@2.2.14': {} - '@eslint-community/eslint-utils@4.9.0(eslint@9.39.1(jiti@2.6.1))': dependencies: eslint: 9.39.1(jiti@2.6.1) @@ -9554,7 +9529,7 @@ snapshots: jest-util: 29.7.0 slash: 3.0.0 - '@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.15.3)(@swc/wasm@1.15.3)(@types/node@20.19.25)(typescript@5.9.3))': + '@jest/core@29.7.0': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0 @@ -9568,7 +9543,7 @@ snapshots: exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.19.25)(ts-node@10.9.2(@swc/core@1.15.3)(@swc/wasm@1.15.3)(@types/node@20.19.25)(typescript@5.9.3)) + jest-config: 29.7.0(@types/node@20.19.25) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -9731,12 +9706,6 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping@0.3.9': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - optional: true - '@lerna/create@8.2.4(@swc/core@1.15.3)(@types/node@20.19.25)(encoding@0.1.13)(typescript@5.9.3)': dependencies: '@npmcli/arborist': 7.5.4 @@ -9819,6 +9788,17 @@ snapshots: - supports-color - typescript + '@monaco-editor/loader@1.7.0': + dependencies: + state-local: 1.0.7 + + '@monaco-editor/react@4.7.0(monaco-editor@0.55.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@monaco-editor/loader': 1.7.0 + monaco-editor: 0.55.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + '@napi-rs/wasm-runtime@0.2.4': dependencies: '@emnapi/core': 1.7.1 @@ -10726,6 +10706,10 @@ snapshots: '@tauri-apps/cli-win32-ia32-msvc': 2.9.4 '@tauri-apps/cli-win32-x64-msvc': 2.9.4 + '@tauri-apps/plugin-cli@2.4.1': + dependencies: + '@tauri-apps/api': 2.9.0 + '@tauri-apps/plugin-dialog@2.4.2': dependencies: '@tauri-apps/api': 2.9.0 @@ -10750,18 +10734,6 @@ snapshots: '@trysound/sax@0.2.0': {} - '@tsconfig/node10@1.0.12': - optional: true - - '@tsconfig/node12@1.0.11': - optional: true - - '@tsconfig/node14@1.0.3': - optional: true - - '@tsconfig/node16@1.0.4': - optional: true - '@tufjs/canonical-json@2.0.0': {} '@tufjs/models@2.0.1': @@ -10939,6 +10911,9 @@ snapshots: '@types/tough-cookie@4.0.5': {} + '@types/trusted-types@2.0.7': + optional: true + '@types/unist@2.0.11': {} '@types/unist@3.0.3': {} @@ -11313,9 +11288,6 @@ snapshots: aproba@2.0.0: {} - arg@4.1.3: - optional: true - argparse@1.0.10: dependencies: sprintf-js: 1.0.3 @@ -11827,13 +11799,13 @@ snapshots: optionalDependencies: typescript: 5.9.3 - create-jest@29.7.0(@types/node@20.19.25)(ts-node@10.9.2(@swc/core@1.15.3)(@swc/wasm@1.15.3)(@types/node@20.19.25)(typescript@5.9.3)): + create-jest@29.7.0(@types/node@20.19.25): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.19.25)(ts-node@10.9.2(@swc/core@1.15.3)(@swc/wasm@1.15.3)(@types/node@20.19.25)(typescript@5.9.3)) + jest-config: 29.7.0(@types/node@20.19.25) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -11842,9 +11814,6 @@ snapshots: - supports-color - ts-node - create-require@1.1.1: - optional: true - cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -12010,9 +11979,6 @@ snapshots: diff-sequences@29.6.3: {} - diff@4.0.2: - optional: true - dir-glob@3.0.1: dependencies: path-type: 4.0.0 @@ -12033,6 +11999,10 @@ snapshots: dependencies: domelementtype: 2.3.0 + dompurify@3.2.7: + optionalDependencies: + '@types/trusted-types': 2.0.7 + domutils@2.8.0: dependencies: dom-serializer: 1.4.1 @@ -13224,16 +13194,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@20.19.25)(ts-node@10.9.2(@swc/core@1.15.3)(@swc/wasm@1.15.3)(@types/node@20.19.25)(typescript@5.9.3)): + jest-cli@29.7.0(@types/node@20.19.25): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.15.3)(@swc/wasm@1.15.3)(@types/node@20.19.25)(typescript@5.9.3)) + '@jest/core': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.19.25)(ts-node@10.9.2(@swc/core@1.15.3)(@swc/wasm@1.15.3)(@types/node@20.19.25)(typescript@5.9.3)) + create-jest: 29.7.0(@types/node@20.19.25) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@20.19.25)(ts-node@10.9.2(@swc/core@1.15.3)(@swc/wasm@1.15.3)(@types/node@20.19.25)(typescript@5.9.3)) + jest-config: 29.7.0(@types/node@20.19.25) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -13243,7 +13213,7 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@20.19.25)(ts-node@10.9.2(@swc/core@1.15.3)(@swc/wasm@1.15.3)(@types/node@20.19.25)(typescript@5.9.3)): + jest-config@29.7.0(@types/node@20.19.25): dependencies: '@babel/core': 7.28.5 '@jest/test-sequencer': 29.7.0 @@ -13269,7 +13239,6 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 20.19.25 - ts-node: 10.9.2(@swc/core@1.15.3)(@swc/wasm@1.15.3)(@types/node@20.19.25)(typescript@5.9.3) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -13504,12 +13473,12 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@20.19.25)(ts-node@10.9.2(@swc/core@1.15.3)(@swc/wasm@1.15.3)(@types/node@20.19.25)(typescript@5.9.3)): + jest@29.7.0(@types/node@20.19.25): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.15.3)(@swc/wasm@1.15.3)(@types/node@20.19.25)(typescript@5.9.3)) + '@jest/core': 29.7.0 '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@20.19.25)(ts-node@10.9.2(@swc/core@1.15.3)(@swc/wasm@1.15.3)(@types/node@20.19.25)(typescript@5.9.3)) + jest-cli: 29.7.0(@types/node@20.19.25) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -13950,6 +13919,8 @@ snapshots: marked@12.0.2: {} + marked@14.0.0: {} + math-intrinsics@1.1.0: {} mdast-util-find-and-replace@3.0.2: @@ -14441,6 +14412,11 @@ snapshots: modify-values@1.0.1: {} + monaco-editor@0.55.1: + dependencies: + dompurify: 3.2.7 + marked: 14.0.0 + ms@2.1.3: {} multimatch@5.0.0: @@ -14999,13 +14975,12 @@ snapshots: dependencies: postcss: 8.5.6 - postcss-load-config@3.1.4(postcss@8.5.6)(ts-node@10.9.2(@swc/core@1.15.3)(@swc/wasm@1.15.3)(@types/node@20.19.25)(typescript@5.9.3)): + postcss-load-config@3.1.4(postcss@8.5.6): dependencies: lilconfig: 2.1.0 yaml: 1.10.2 optionalDependencies: postcss: 8.5.6 - ts-node: 10.9.2(@swc/core@1.15.3)(@swc/wasm@1.15.3)(@types/node@20.19.25)(typescript@5.9.3) postcss-merge-longhand@5.1.7(postcss@8.5.6): dependencies: @@ -15543,7 +15518,7 @@ snapshots: optionalDependencies: '@babel/code-frame': 7.27.1 - rollup-plugin-postcss@4.0.2(postcss@8.5.6)(ts-node@10.9.2(@swc/core@1.15.3)(@swc/wasm@1.15.3)(@types/node@20.19.25)(typescript@5.9.3)): + rollup-plugin-postcss@4.0.2(postcss@8.5.6): dependencies: chalk: 4.1.2 concat-with-sourcemaps: 1.1.0 @@ -15552,7 +15527,7 @@ snapshots: p-queue: 6.6.2 pify: 5.0.0 postcss: 8.5.6 - postcss-load-config: 3.1.4(postcss@8.5.6)(ts-node@10.9.2(@swc/core@1.15.3)(@swc/wasm@1.15.3)(@types/node@20.19.25)(typescript@5.9.3)) + postcss-load-config: 3.1.4(postcss@8.5.6) postcss-modules: 4.3.1(postcss@8.5.6) promise.series: 0.2.0 resolve: 1.22.11 @@ -15882,6 +15857,8 @@ snapshots: dependencies: escape-string-regexp: 2.0.0 + state-local@1.0.7: {} + stream-combiner2@1.1.1: dependencies: duplexer2: 0.1.4 @@ -16133,12 +16110,12 @@ snapshots: dependencies: typescript: 5.9.3 - ts-jest@29.4.5(@babel/core@7.28.5)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.5))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.25)(ts-node@10.9.2(@swc/core@1.15.3)(@swc/wasm@1.15.3)(@types/node@20.19.25)(typescript@5.9.3)))(typescript@5.9.3): + ts-jest@29.4.5(@babel/core@7.28.5)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.5))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.25))(typescript@5.9.3): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 handlebars: 4.7.8 - jest: 29.7.0(@types/node@20.19.25)(ts-node@10.9.2(@swc/core@1.15.3)(@swc/wasm@1.15.3)(@types/node@20.19.25)(typescript@5.9.3)) + jest: 29.7.0(@types/node@20.19.25) json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 @@ -16153,28 +16130,6 @@ snapshots: babel-jest: 29.7.0(@babel/core@7.28.5) jest-util: 29.7.0 - ts-node@10.9.2(@swc/core@1.15.3)(@swc/wasm@1.15.3)(@types/node@20.19.25)(typescript@5.9.3): - dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@tsconfig/node10': 1.0.12 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.4 - '@types/node': 20.19.25 - acorn: 8.15.0 - acorn-walk: 8.3.4 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.2 - make-error: 1.3.6 - typescript: 5.9.3 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 - optionalDependencies: - '@swc/core': 1.15.3 - '@swc/wasm': 1.15.3 - optional: true - tsconfig-paths@4.2.0: dependencies: json5: 2.2.3 @@ -16380,9 +16335,6 @@ snapshots: uuid@10.0.0: {} - v8-compile-cache-lib@3.0.1: - optional: true - v8-to-istanbul@9.3.0: dependencies: '@jridgewell/trace-mapping': 0.3.31 @@ -16693,9 +16645,6 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 - yn@3.1.1: - optional: true - yocto-queue@0.1.0: {} yoctocolors@2.1.2: {}