diff --git a/packages/editor-app/src-tauri/Cargo.lock b/packages/editor-app/src-tauri/Cargo.lock index 1f7d8205..dba1d8eb 100644 --- a/packages/editor-app/src-tauri/Cargo.lock +++ b/packages/editor-app/src-tauri/Cargo.lock @@ -47,6 +47,61 @@ version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +[[package]] +name = "ashpd" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cbdf310d77fd3aaee6ea2093db7011dc2d35d2eb3481e5607f1f8d942ed99df" +dependencies = [ + "enumflags2", + "futures-channel", + "futures-util", + "rand 0.9.2", + "raw-window-handle", + "serde", + "serde_repr", + "tokio", + "url", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "zbus", +] + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "atk" version = "0.18.2" @@ -322,6 +377,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "convert_case" version = "0.4.0" @@ -560,6 +624,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" dependencies = [ "bitflags 2.9.4", + "block2 0.6.2", + "libc", "objc2 0.6.3", ] @@ -574,6 +640,15 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading", +] + [[package]] name = "dlopen2" version = "0.8.0" @@ -597,6 +672,12 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + [[package]] name = "dpi" version = "0.1.2" @@ -637,10 +718,12 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" name = "ecs-editor" version = "1.0.0" dependencies = [ + "glob", "serde", "serde_json", "tauri", "tauri-build", + "tauri-plugin-dialog", "tauri-plugin-shell", ] @@ -673,6 +756,33 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "endi" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -690,6 +800,43 @@ dependencies = [ "typeid", ] +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + [[package]] name = "fdeflate" version = "0.3.7" @@ -809,6 +956,19 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.31" @@ -1652,6 +1812,12 @@ dependencies = [ "libc", ] +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + [[package]] name = "litemap" version = "0.8.0" @@ -1809,6 +1975,19 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.9.4", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + [[package]] name = "nodrop" version = "0.1.14" @@ -2140,6 +2319,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + [[package]] name = "os_pipe" version = "1.2.3" @@ -2175,6 +2364,12 @@ dependencies = [ "system-deps", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.5" @@ -2370,7 +2565,7 @@ checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" dependencies = [ "base64 0.22.1", "indexmap 2.11.4", - "quick-xml", + "quick-xml 0.38.3", "serde", "time", ] @@ -2486,6 +2681,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quick-xml" +version = "0.37.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +dependencies = [ + "memchr", +] + [[package]] name = "quick-xml" version = "0.38.3" @@ -2535,6 +2739,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + [[package]] name = "rand_chacha" version = "0.2.2" @@ -2555,6 +2769,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + [[package]] name = "rand_core" version = "0.5.1" @@ -2573,6 +2797,15 @@ dependencies = [ "getrandom 0.2.16", ] +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + [[package]] name = "rand_hc" version = "0.2.0" @@ -2701,6 +2934,31 @@ dependencies = [ "web-sys", ] +[[package]] +name = "rfd" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2bee61e6cffa4635c72d7d81a84294e28f0930db0ddcb0f66d10244674ebed" +dependencies = [ + "ashpd", + "block2 0.6.2", + "dispatch2", + "glib-sys", + "gobject-sys", + "gtk-sys", + "js-sys", + "log", + "objc2 0.6.3", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "raw-window-handle", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.59.0", +] + [[package]] name = "rustc_version" version = "0.4.1" @@ -2710,6 +2968,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags 2.9.4", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + [[package]] name = "rustversion" version = "1.0.22" @@ -2782,6 +3053,12 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -3138,6 +3415,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "string_cache" version = "0.8.9" @@ -3424,6 +3707,46 @@ dependencies = [ "walkdir", ] +[[package]] +name = "tauri-plugin-dialog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beee42a4002bc695550599b011728d9dfabf82f767f134754ed6655e434824e" +dependencies = [ + "log", + "raw-window-handle", + "rfd", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "tauri-plugin-fs", + "thiserror 2.0.17", + "url", +] + +[[package]] +name = "tauri-plugin-fs" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "315784ec4be45e90a987687bae7235e6be3d6e9e350d2b75c16b8a4bf22c1db7" +dependencies = [ + "anyhow", + "dunce", + "glob", + "percent-encoding", + "schemars 0.8.22", + "serde", + "serde_json", + "serde_repr", + "tauri", + "tauri-plugin", + "tauri-utils", + "thiserror 2.0.17", + "toml 0.9.8", + "url", +] + [[package]] name = "tauri-plugin-shell" version = "2.3.1" @@ -3545,6 +3868,19 @@ dependencies = [ "toml 0.9.8", ] +[[package]] +name = "tempfile" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +dependencies = [ + "fastrand", + "getrandom 0.3.3", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + [[package]] name = "tendril" version = "0.4.3" @@ -3647,7 +3983,9 @@ dependencies = [ "libc", "mio", "pin-project-lite", + "signal-hook-registry", "socket2", + "tracing", "windows-sys 0.61.2", ] @@ -3812,9 +4150,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "tracing-core" version = "0.1.34" @@ -3864,6 +4214,17 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset", + "tempfile", + "winapi", +] + [[package]] name = "unic-char-property" version = "0.9.0" @@ -4131,6 +4492,66 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wayland-backend" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" +dependencies = [ + "cc", + "downcast-rs", + "rustix", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" +dependencies = [ + "bitflags 2.9.4", + "rustix", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901" +dependencies = [ + "bitflags 2.9.4", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3" +dependencies = [ + "proc-macro2", + "quick-xml 0.37.5", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142" +dependencies = [ + "dlib", + "log", + "pkg-config", +] + [[package]] name = "web-sys" version = "0.3.81" @@ -4776,6 +5197,61 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zbus" +version = "5.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d07e46d035fb8e375b2ce63ba4e4ff90a7f73cf2ffb0138b29e1158d2eaadf7" +dependencies = [ + "async-broadcast", + "async-recursion", + "async-trait", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "nix", + "ordered-stream", + "serde", + "serde_repr", + "tokio", + "tracing", + "uds_windows", + "windows-sys 0.60.2", + "winnow 0.7.13", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57e797a9c847ed3ccc5b6254e8bcce056494b375b511b3d6edcec0aeb4defaca" +dependencies = [ + "proc-macro-crate 3.4.0", + "proc-macro2", + "quote", + "syn 2.0.106", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" +dependencies = [ + "serde", + "static_assertions", + "winnow 0.7.13", + "zvariant", +] + [[package]] name = "zerocopy" version = "0.8.27" @@ -4849,3 +5325,44 @@ dependencies = [ "quote", "syn 2.0.106", ] + +[[package]] +name = "zvariant" +version = "5.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "999dd3be73c52b1fccd109a4a81e4fcd20fab1d3599c8121b38d04e1419498db" +dependencies = [ + "endi", + "enumflags2", + "serde", + "url", + "winnow 0.7.13", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6643fd0b26a46d226bd90d3f07c1b5321fe9bb7f04673cb37ac6d6883885b68e" +dependencies = [ + "proc-macro-crate 3.4.0", + "proc-macro2", + "quote", + "syn 2.0.106", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6949d142f89f6916deca2232cf26a8afacf2b9fdc35ce766105e104478be599" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 2.0.106", + "winnow 0.7.13", +] diff --git a/packages/editor-app/src-tauri/Cargo.toml b/packages/editor-app/src-tauri/Cargo.toml index 1712737a..9fe0c8da 100644 --- a/packages/editor-app/src-tauri/Cargo.toml +++ b/packages/editor-app/src-tauri/Cargo.toml @@ -15,8 +15,10 @@ tauri-build = { version = "2.0", features = [] } [dependencies] tauri = { version = "2.0", features = [] } tauri-plugin-shell = "2.0" +tauri-plugin-dialog = "2.0" serde = { version = "1", features = ["derive"] } serde_json = "1" +glob = "0.3" [profile.dev] incremental = true diff --git a/packages/editor-app/src-tauri/src/main.rs b/packages/editor-app/src-tauri/src/main.rs index 21c7bb1d..438c6ef2 100644 --- a/packages/editor-app/src-tauri/src/main.rs +++ b/packages/editor-app/src-tauri/src/main.rs @@ -33,18 +33,58 @@ fn export_binary(data: Vec, output_path: String) -> Result<(), String> { #[tauri::command] async fn open_project_dialog(app: AppHandle) -> Result, String> { - use tauri::api::dialog::blocking::FileDialogBuilder; + use tauri_plugin_dialog::DialogExt; - let result = FileDialogBuilder::new() + let folder = app.dialog() + .file() .set_title("Select Project Directory") - .pick_folder(); + .blocking_pick_folder(); - Ok(result.map(|path| path.to_string_lossy().to_string())) + Ok(folder.map(|path| path.to_string())) +} + +#[tauri::command] +fn scan_directory(path: String, pattern: String) -> Result, String> { + use glob::glob; + use std::path::Path; + + let base_path = Path::new(&path); + if !base_path.exists() { + return Err(format!("Directory does not exist: {}", path)); + } + + let glob_pattern = format!("{}/{}", path, pattern); + let mut files = Vec::new(); + + match glob(&glob_pattern) { + Ok(entries) => { + for entry in entries { + match entry { + Ok(path) => { + if path.is_file() { + files.push(path.to_string_lossy().to_string()); + } + } + Err(e) => eprintln!("Error reading entry: {}", e), + } + } + } + Err(e) => return Err(format!("Failed to scan directory: {}", e)), + } + + Ok(files) +} + +#[tauri::command] +fn read_file_content(path: String) -> Result { + std::fs::read_to_string(&path) + .map_err(|e| format!("Failed to read file {}: {}", path, e)) } fn main() { tauri::Builder::default() .plugin(tauri_plugin_shell::init()) + .plugin(tauri_plugin_dialog::init()) .setup(|app| { // 应用启动时的初始化逻辑 #[cfg(debug_assertions)] @@ -59,7 +99,9 @@ fn main() { open_project, save_project, export_binary, - open_project_dialog + open_project_dialog, + scan_directory, + read_file_content ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/packages/editor-app/src/App.tsx b/packages/editor-app/src/App.tsx index a5a96105..a8ec53ec 100644 --- a/packages/editor-app/src/App.tsx +++ b/packages/editor-app/src/App.tsx @@ -1,6 +1,6 @@ import { useState, useEffect } from 'react'; import { Core, Scene } from '@esengine/ecs-framework'; -import { EditorPluginManager, UIRegistry, MessageHub, SerializerRegistry, EntityStoreService, ComponentRegistry, LocaleService, PropertyMetadataService, ProjectService } from '@esengine/editor-core'; +import { EditorPluginManager, UIRegistry, MessageHub, SerializerRegistry, EntityStoreService, ComponentRegistry, LocaleService, PropertyMetadataService, ProjectService, ComponentDiscoveryService, ComponentLoaderService } from '@esengine/editor-core'; import { SceneInspectorPlugin } from './plugins/SceneInspectorPlugin'; import { SceneHierarchy } from './components/SceneHierarchy'; import { EntityInspector } from './components/EntityInspector'; @@ -70,6 +70,8 @@ function App() { const entityStore = new EntityStoreService(messageHub); const componentRegistry = new ComponentRegistry(); const projectService = new ProjectService(messageHub); + const componentDiscovery = new ComponentDiscoveryService(messageHub); + const componentLoader = new ComponentLoaderService(messageHub, componentRegistry); componentRegistry.register({ name: 'Transform', @@ -98,6 +100,8 @@ function App() { Core.services.registerInstance(EntityStoreService, entityStore); Core.services.registerInstance(ComponentRegistry, componentRegistry); Core.services.registerInstance(ProjectService, projectService); + Core.services.registerInstance(ComponentDiscoveryService, componentDiscovery); + Core.services.registerInstance(ComponentLoaderService, componentLoader); const pluginMgr = new EditorPluginManager(); pluginMgr.initialize(coreInstance, Core.services); @@ -147,12 +151,36 @@ function App() { const handleOpenProject = async () => { try { const projectPath = await TauriAPI.openProjectDialog(); - if (projectPath) { - const projectService = Core.services.resolve(ProjectService); - if (projectService) { - await projectService.openProject(projectPath); - setStatus(t('header.status.projectOpened')); - } + if (!projectPath) return; + + const projectService = Core.services.resolve(ProjectService); + const discoveryService = Core.services.resolve(ComponentDiscoveryService); + const loaderService = Core.services.resolve(ComponentLoaderService); + + if (!projectService || !discoveryService || !loaderService) { + console.error('Required services not available'); + return; + } + + await projectService.openProject(projectPath); + setStatus('Scanning components...'); + + const componentsPath = projectService.getComponentsPath(); + if (componentsPath) { + const componentInfos = await discoveryService.scanComponents({ + basePath: componentsPath, + pattern: '**/*Component.ts', + scanFunction: TauriAPI.scanDirectory, + readFunction: TauriAPI.readFileContent + }); + + setStatus(`Loading ${componentInfos.length} components...`); + + await loaderService.loadComponents(componentInfos); + + setStatus(t('header.status.projectOpened') + ` (${componentInfos.length} components loaded)`); + } else { + setStatus(t('header.status.projectOpened')); } } catch (error) { console.error('Failed to open project:', error); diff --git a/packages/editor-app/src/api/tauri.ts b/packages/editor-app/src/api/tauri.ts index 56d363f4..578bf0ef 100644 --- a/packages/editor-app/src/api/tauri.ts +++ b/packages/editor-app/src/api/tauri.ts @@ -35,6 +35,20 @@ export class TauriAPI { outputPath }); } + + /** + * 扫描目录查找匹配模式的文件 + */ + static async scanDirectory(path: string, pattern: string): Promise { + return await invoke('scan_directory', { path, pattern }); + } + + /** + * 读取文件内容 + */ + static async readFileContent(path: string): Promise { + return await invoke('read_file_content', { path }); + } } /** diff --git a/packages/editor-core/src/Services/ComponentDiscoveryService.ts b/packages/editor-core/src/Services/ComponentDiscoveryService.ts new file mode 100644 index 00000000..9e6ce4df --- /dev/null +++ b/packages/editor-core/src/Services/ComponentDiscoveryService.ts @@ -0,0 +1,102 @@ +import type { IService } from '@esengine/ecs-framework'; +import { Injectable } from '@esengine/ecs-framework'; +import { createLogger } from '@esengine/ecs-framework'; +import { MessageHub } from './MessageHub'; + +const logger = createLogger('ComponentDiscoveryService'); + +export interface ComponentFileInfo { + path: string; + fileName: string; + className: string | null; +} + +export interface ComponentScanOptions { + basePath: string; + pattern: string; + scanFunction: (path: string, pattern: string) => Promise; + readFunction: (path: string) => Promise; +} + +@Injectable() +export class ComponentDiscoveryService implements IService { + private discoveredComponents: Map = new Map(); + private messageHub: MessageHub; + + constructor(messageHub: MessageHub) { + this.messageHub = messageHub; + } + + public async scanComponents(options: ComponentScanOptions): Promise { + try { + logger.info('Scanning for components', { + basePath: options.basePath, + pattern: options.pattern + }); + + const files = await options.scanFunction(options.basePath, options.pattern); + logger.info(`Found ${files.length} component files`); + + const componentInfos: ComponentFileInfo[] = []; + + for (const filePath of files) { + try { + const fileContent = await options.readFunction(filePath); + const componentInfo = this.parseComponentFile(filePath, fileContent); + + if (componentInfo) { + componentInfos.push(componentInfo); + this.discoveredComponents.set(filePath, componentInfo); + } + } catch (error) { + logger.warn(`Failed to parse component file: ${filePath}`, error); + } + } + + await this.messageHub.publish('components:discovered', { + count: componentInfos.length, + components: componentInfos + }); + + logger.info(`Successfully parsed ${componentInfos.length} components`); + return componentInfos; + } catch (error) { + logger.error('Failed to scan components', error); + throw error; + } + } + + public getDiscoveredComponents(): ComponentFileInfo[] { + return Array.from(this.discoveredComponents.values()); + } + + public clearDiscoveredComponents(): void { + this.discoveredComponents.clear(); + logger.info('Cleared discovered components'); + } + + private parseComponentFile(filePath: string, content: string): ComponentFileInfo | null { + const fileName = filePath.split(/[\\/]/).pop() || ''; + + const classMatch = content.match(/export\s+class\s+(\w+)\s+extends\s+Component/); + + if (classMatch) { + const className = classMatch[1]; + logger.debug(`Found component class: ${className} in ${fileName}`); + + return { + path: filePath, + fileName, + className + }; + } + + logger.debug(`No valid component class found in ${fileName}`); + return null; + } + + public dispose(): void { + this.discoveredComponents.clear(); + logger.info('ComponentDiscoveryService disposed'); + } +} diff --git a/packages/editor-core/src/Services/ComponentLoaderService.ts b/packages/editor-core/src/Services/ComponentLoaderService.ts new file mode 100644 index 00000000..1e2f8d42 --- /dev/null +++ b/packages/editor-core/src/Services/ComponentLoaderService.ts @@ -0,0 +1,141 @@ +import type { IService } from '@esengine/ecs-framework'; +import { Injectable, Component } from '@esengine/ecs-framework'; +import { createLogger } from '@esengine/ecs-framework'; +import { MessageHub } from './MessageHub'; +import type { ComponentFileInfo } from './ComponentDiscoveryService'; +import { ComponentRegistry } from './ComponentRegistry'; + +const logger = createLogger('ComponentLoaderService'); + +export interface LoadedComponentInfo { + fileInfo: ComponentFileInfo; + componentClass: typeof Component; + loadedAt: number; +} + +@Injectable() +export class ComponentLoaderService implements IService { + private loadedComponents: Map = new Map(); + private messageHub: MessageHub; + private componentRegistry: ComponentRegistry; + + constructor(messageHub: MessageHub, componentRegistry: ComponentRegistry) { + this.messageHub = messageHub; + this.componentRegistry = componentRegistry; + } + + public async loadComponents( + componentInfos: ComponentFileInfo[], + modulePathTransform?: (filePath: string) => string + ): Promise { + logger.info(`Loading ${componentInfos.length} components`); + + const loadedComponents: LoadedComponentInfo[] = []; + + for (const componentInfo of componentInfos) { + try { + const loadedComponent = await this.loadComponent(componentInfo, modulePathTransform); + if (loadedComponent) { + loadedComponents.push(loadedComponent); + } + } catch (error) { + logger.error(`Failed to load component: ${componentInfo.fileName}`, error); + } + } + + await this.messageHub.publish('components:loaded', { + count: loadedComponents.length, + components: loadedComponents + }); + + logger.info(`Successfully loaded ${loadedComponents.length} components`); + return loadedComponents; + } + + public async loadComponent( + componentInfo: ComponentFileInfo, + modulePathTransform?: (filePath: string) => string + ): Promise { + try { + const modulePath = modulePathTransform + ? modulePathTransform(componentInfo.path) + : this.convertToModulePath(componentInfo.path); + + logger.debug(`Loading component from: ${modulePath}`); + + const module = await import(/* @vite-ignore */ modulePath); + + if (!componentInfo.className) { + logger.warn(`No class name found for component: ${componentInfo.fileName}`); + return null; + } + + const componentClass = module[componentInfo.className]; + + if (!componentClass || !(componentClass.prototype instanceof Component)) { + logger.error(`Invalid component class: ${componentInfo.className}`); + return null; + } + + this.componentRegistry.register({ + name: componentInfo.className, + type: componentClass + }); + + const loadedInfo: LoadedComponentInfo = { + fileInfo: componentInfo, + componentClass, + loadedAt: Date.now() + }; + + this.loadedComponents.set(componentInfo.path, loadedInfo); + + logger.info(`Component loaded and registered: ${componentInfo.className}`); + + return loadedInfo; + } catch (error) { + logger.error(`Failed to load component: ${componentInfo.fileName}`, error); + return null; + } + } + + public getLoadedComponents(): LoadedComponentInfo[] { + return Array.from(this.loadedComponents.values()); + } + + public unloadComponent(filePath: string): boolean { + const loadedComponent = this.loadedComponents.get(filePath); + + if (!loadedComponent || !loadedComponent.fileInfo.className) { + return false; + } + + this.componentRegistry.unregister(loadedComponent.fileInfo.className); + this.loadedComponents.delete(filePath); + + logger.info(`Component unloaded: ${loadedComponent.fileInfo.className}`); + return true; + } + + public clearLoadedComponents(): void { + for (const [filePath] of this.loadedComponents) { + this.unloadComponent(filePath); + } + logger.info('Cleared all loaded components'); + } + + private convertToModulePath(filePath: string): string { + const normalizedPath = filePath.replace(/\\/g, '/'); + + if (normalizedPath.startsWith('http://') || normalizedPath.startsWith('https://')) { + return normalizedPath; + } + + return `file:///${normalizedPath}`; + } + + public dispose(): void { + this.clearLoadedComponents(); + logger.info('ComponentLoaderService disposed'); + } +} diff --git a/packages/editor-core/src/index.ts b/packages/editor-core/src/index.ts index b459be8f..59c10972 100644 --- a/packages/editor-core/src/index.ts +++ b/packages/editor-core/src/index.ts @@ -15,5 +15,7 @@ export * from './Services/ComponentRegistry'; export * from './Services/LocaleService'; export * from './Services/PropertyMetadata'; export * from './Services/ProjectService'; +export * from './Services/ComponentDiscoveryService'; +export * from './Services/ComponentLoaderService'; export * from './Types/UITypes';