From 011e43811aa565e88a04021e61d93348d4d8c830 Mon Sep 17 00:00:00 2001 From: YHH <359807859@qq.com> Date: Thu, 16 Oct 2025 22:26:50 +0800 Subject: [PATCH] =?UTF-8?q?=E7=83=AD=E6=9B=B4=E6=96=B0=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/release-editor.yml | 59 +++ package-lock.json | 11 + packages/editor-app/package.json | 4 +- packages/editor-app/scripts/sync-version.js | 33 ++ packages/editor-app/src-tauri/Cargo.lock | 345 ++++++++++++++++++ packages/editor-app/src-tauri/Cargo.toml | 1 + packages/editor-app/src-tauri/src/main.rs | 1 + packages/editor-app/src-tauri/tauri.conf.json | 25 +- packages/editor-app/src/App.tsx | 15 + .../editor-app/src/components/AboutDialog.tsx | 213 +++++++++++ .../editor-app/src/components/MenuBar.tsx | 9 +- .../editor-app/src/styles/AboutDialog.css | 258 +++++++++++++ packages/editor-app/src/utils/updater.ts | 60 +++ 13 files changed, 1030 insertions(+), 4 deletions(-) create mode 100644 packages/editor-app/scripts/sync-version.js create mode 100644 packages/editor-app/src/components/AboutDialog.tsx create mode 100644 packages/editor-app/src/styles/AboutDialog.css create mode 100644 packages/editor-app/src/utils/updater.ts diff --git a/.github/workflows/release-editor.yml b/.github/workflows/release-editor.yml index 9300dd64..886a0acd 100644 --- a/.github/workflows/release-editor.yml +++ b/.github/workflows/release-editor.yml @@ -59,6 +59,14 @@ jobs: - name: Install frontend dependencies run: npm ci + - name: Update version in config files (for manual trigger) + if: github.event_name == 'workflow_dispatch' + run: | + cd packages/editor-app + # 临时更新版本号用于构建(不提交到仓库) + npm version ${{ github.event.inputs.version }} --no-git-tag-version + node scripts/sync-version.js + - name: Cache TypeScript build uses: actions/cache@v4 with: @@ -81,6 +89,8 @@ jobs: uses: tauri-apps/tauri-action@v0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} with: projectPath: packages/editor-app tagName: ${{ github.event.inputs.version || github.ref_name }} @@ -88,4 +98,53 @@ jobs: releaseBody: 'See the assets to download this version and install.' releaseDraft: false prerelease: false + includeUpdaterJson: true args: ${{ matrix.platform == 'macos-latest' && format('--target {0}', matrix.target) || '' }} + + # 构建成功后,创建 PR 更新版本号 + update-version-pr: + needs: build-tauri + if: github.event_name == 'workflow_dispatch' && success() + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20.x' + + - name: Update version files + run: | + cd packages/editor-app + npm version ${{ github.event.inputs.version }} --no-git-tag-version + node scripts/sync-version.js + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v6 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: "chore(editor): bump version to ${{ github.event.inputs.version }}" + branch: release/editor-v${{ github.event.inputs.version }} + delete-branch: true + title: "chore(editor): Release v${{ github.event.inputs.version }}" + body: | + ## 🚀 Release v${{ github.event.inputs.version }} + + This PR updates the editor version after successful release build. + + ### Changes + - ✅ Updated `packages/editor-app/package.json` → `${{ github.event.inputs.version }}` + - ✅ Updated `packages/editor-app/src-tauri/tauri.conf.json` → `${{ github.event.inputs.version }}` + + ### Release + - 📦 [GitHub Release](https://github.com/${{ github.repository }}/releases/tag/editor-v${{ github.event.inputs.version }}) + + --- + *This PR was automatically created by the release workflow.* + labels: | + release + editor + automated pr diff --git a/package-lock.json b/package-lock.json index 0963a846..cc2ba142 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6040,6 +6040,16 @@ "@tauri-apps/api": "^2.8.0" } }, + "node_modules/@tauri-apps/plugin-updater": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-updater/-/plugin-updater-2.9.0.tgz", + "integrity": "sha512-j++sgY8XpeDvzImTrzWA08OqqGqgkNyxczLD7FjNJJx/uXxMZFz5nDcfkyoI/rCjYuj2101Tci/r/HFmOmoxCg==", + "dev": true, + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.6.0" + } + }, "node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -17667,6 +17677,7 @@ }, "devDependencies": { "@tauri-apps/cli": "^2.2.0", + "@tauri-apps/plugin-updater": "^2.9.0", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "@vitejs/plugin-react": "^4.3.4", diff --git a/packages/editor-app/package.json b/packages/editor-app/package.json index a2e50c8d..b8d72cdd 100644 --- a/packages/editor-app/package.json +++ b/packages/editor-app/package.json @@ -10,7 +10,8 @@ "preview": "vite preview", "tauri": "tauri", "tauri:dev": "tauri dev", - "tauri:build": "tauri build" + "tauri:build": "tauri build", + "version": "node scripts/sync-version.js && git add src-tauri/tauri.conf.json" }, "dependencies": { "@esengine/ecs-framework": "file:../core", @@ -24,6 +25,7 @@ }, "devDependencies": { "@tauri-apps/cli": "^2.2.0", + "@tauri-apps/plugin-updater": "^2.9.0", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "@vitejs/plugin-react": "^4.3.4", diff --git a/packages/editor-app/scripts/sync-version.js b/packages/editor-app/scripts/sync-version.js new file mode 100644 index 00000000..6a871665 --- /dev/null +++ b/packages/editor-app/scripts/sync-version.js @@ -0,0 +1,33 @@ +#!/usr/bin/env node + +/** + * 同步 package.json 和 tauri.conf.json 的版本号 + * 在 npm version 命令执行后自动运行 + */ + +import { readFileSync, writeFileSync } from 'fs'; +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +// 读取 package.json +const packageJsonPath = join(__dirname, '../package.json'); +const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')); +const newVersion = packageJson.version; + +// 读取 tauri.conf.json +const tauriConfigPath = join(__dirname, '../src-tauri/tauri.conf.json'); +const tauriConfig = JSON.parse(readFileSync(tauriConfigPath, 'utf8')); + +// 更新 tauri.conf.json 的版本号 +const oldVersion = tauriConfig.version; +tauriConfig.version = newVersion; + +// 写回文件(保持格式) +writeFileSync(tauriConfigPath, JSON.stringify(tauriConfig, null, 2) + '\n', 'utf8'); + +console.log(`✓ Version synced: ${oldVersion} → ${newVersion}`); +console.log(` - package.json: ${newVersion}`); +console.log(` - tauri.conf.json: ${newVersion}`); diff --git a/packages/editor-app/src-tauri/Cargo.lock b/packages/editor-app/src-tauri/Cargo.lock index 951d38c3..3f57ae6c 100644 --- a/packages/editor-app/src-tauri/Cargo.lock +++ b/packages/editor-app/src-tauri/Cargo.lock @@ -47,6 +47,15 @@ version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "ashpd" version = "0.11.0" @@ -575,6 +584,17 @@ dependencies = [ "serde_core", ] +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "derive_more" version = "0.99.20" @@ -735,6 +755,7 @@ dependencies = [ "tauri-build", "tauri-plugin-dialog", "tauri-plugin-shell", + "tauri-plugin-updater", "tokio", "tokio-tungstenite", ] @@ -868,6 +889,18 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "filetime" +version = "0.2.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.60.2", +] + [[package]] name = "find-msvc-tools" version = "0.1.4" @@ -1157,8 +1190,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -1168,9 +1203,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", "wasi 0.14.7+wasi-0.2.4", + "wasm-bindgen", ] [[package]] @@ -1430,6 +1467,23 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + [[package]] name = "hyper-util" version = "0.1.17" @@ -1828,6 +1882,7 @@ checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ "bitflags 2.9.4", "libc", + "redox_syscall", ] [[package]] @@ -1857,6 +1912,12 @@ version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "mac" version = "0.1.1" @@ -1915,6 +1976,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minisign-verify" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e856fdd13623a2f5f2f54676a4ee49502a96a80ef4a62bcedd23d52427c44d43" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -2250,6 +2317,18 @@ dependencies = [ "objc2-foundation 0.2.2", ] +[[package]] +name = "objc2-osa-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0" +dependencies = [ + "bitflags 2.9.4", + "objc2 0.6.3", + "objc2-app-kit", + "objc2-foundation 0.3.2", +] + [[package]] name = "objc2-quartz-core" version = "0.2.2" @@ -2357,6 +2436,20 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "osakit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b" +dependencies = [ + "objc2 0.6.3", + "objc2-foundation 0.3.2", + "objc2-osa-kit", + "serde", + "serde_json", + "thiserror 2.0.17", +] + [[package]] name = "pango" version = "0.18.3" @@ -2717,6 +2810,61 @@ dependencies = [ "memchr", ] +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.17", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "bytes", + "getrandom 0.3.3", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.17", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + [[package]] name = "quote" version = "1.0.41" @@ -2931,16 +3079,21 @@ dependencies = [ "http-body", "http-body-util", "hyper", + "hyper-rustls", "hyper-util", "js-sys", "log", "percent-encoding", "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "tokio", + "tokio-rustls", "tokio-util", "tower", "tower-http", @@ -2950,6 +3103,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", + "webpki-roots", ] [[package]] @@ -2977,6 +3131,26 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustc_version" version = "0.4.1" @@ -2999,6 +3173,41 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustls" +version = "0.23.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.22" @@ -3481,6 +3690,12 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "swift-rs" version = "1.0.7" @@ -3598,6 +3813,17 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "tar" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "target-lexicon" version = "0.12.16" @@ -3798,6 +4024,38 @@ dependencies = [ "tokio", ] +[[package]] +name = "tauri-plugin-updater" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27cbc31740f4d507712550694749572ec0e43bdd66992db7599b89fbfd6b167b" +dependencies = [ + "base64 0.22.1", + "dirs", + "flate2", + "futures-util", + "http", + "infer", + "log", + "minisign-verify", + "osakit", + "percent-encoding", + "reqwest", + "semver", + "serde", + "serde_json", + "tar", + "tauri", + "tauri-plugin", + "tempfile", + "thiserror 2.0.17", + "time", + "tokio", + "url", + "windows-sys 0.60.2", + "zip", +] + [[package]] name = "tauri-runtime" version = "2.8.0" @@ -4003,6 +4261,21 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.48.0" @@ -4032,6 +4305,16 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-tungstenite" version = "0.21.0" @@ -4352,6 +4635,12 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.7" @@ -4636,6 +4925,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webkit2gtk" version = "2.0.1" @@ -4680,6 +4979,15 @@ dependencies = [ "system-deps", ] +[[package]] +name = "webpki-roots" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b130c0d2d49f8b6889abc456e795e82525204f27c42cf767cf0d7734e089b8" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webview2-com" version = "0.38.0" @@ -4910,6 +5218,15 @@ dependencies = [ "windows-targets 0.42.2", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.59.0" @@ -5247,6 +5564,16 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + [[package]] name = "yoke" version = "0.8.0" @@ -5367,6 +5694,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + [[package]] name = "zerotrie" version = "0.2.2" @@ -5400,6 +5733,18 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "zip" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1" +dependencies = [ + "arbitrary", + "crc32fast", + "indexmap 2.11.4", + "memchr", +] + [[package]] name = "zvariant" version = "5.7.0" diff --git a/packages/editor-app/src-tauri/Cargo.toml b/packages/editor-app/src-tauri/Cargo.toml index c7181f5a..06bc113a 100644 --- a/packages/editor-app/src-tauri/Cargo.toml +++ b/packages/editor-app/src-tauri/Cargo.toml @@ -16,6 +16,7 @@ tauri-build = { version = "2.0", features = [] } tauri = { version = "2.0", features = ["protocol-asset"] } tauri-plugin-shell = "2.0" tauri-plugin-dialog = "2.0" +tauri-plugin-updater = "2" 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 37d99ff5..25cb067e 100644 --- a/packages/editor-app/src-tauri/src/main.rs +++ b/packages/editor-app/src-tauri/src/main.rs @@ -243,6 +243,7 @@ fn main() { tauri::Builder::default() .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_dialog::init()) + .plugin(tauri_plugin_updater::Builder::new().build()) .register_uri_scheme_protocol("project", move |_app, request| { let project_paths = Arc::clone(&project_paths_clone); diff --git a/packages/editor-app/src-tauri/tauri.conf.json b/packages/editor-app/src-tauri/tauri.conf.json index 52a89382..d773504d 100644 --- a/packages/editor-app/src-tauri/tauri.conf.json +++ b/packages/editor-app/src-tauri/tauri.conf.json @@ -55,12 +55,35 @@ "scope": { "allow": ["**"] } - } + }, + "capabilities": [ + { + "identifier": "main", + "windows": ["main"], + "permissions": [ + "core:default", + "shell:default", + "dialog:default", + "updater:default", + "updater:allow-check", + "updater:allow-download", + "updater:allow-install" + ] + } + ] } }, "plugins": { "shell": { "open": true + }, + "updater": { + "active": true, + "endpoints": [ + "https://github.com/esengine/ecs-framework/releases/latest/download/latest.json" + ], + "dialog": true, + "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDFDQjNFNDIxREFBODNDNkMKUldSc1BLamFJZVN6SEJIRXRWWEovVXRta08yNWFkZmtKNnZoSHFmbi9ZdGxubUMzSHJaN3J0VEcK" } } } diff --git a/packages/editor-app/src/App.tsx b/packages/editor-app/src/App.tsx index 08b18aaa..09b58363 100644 --- a/packages/editor-app/src/App.tsx +++ b/packages/editor-app/src/App.tsx @@ -12,11 +12,13 @@ import { PluginManagerWindow } from './components/PluginManagerWindow'; import { ProfilerWindow } from './components/ProfilerWindow'; import { PortManager } from './components/PortManager'; import { SettingsWindow } from './components/SettingsWindow'; +import { AboutDialog } from './components/AboutDialog'; import { Viewport } from './components/Viewport'; import { MenuBar } from './components/MenuBar'; import { DockContainer, DockablePanel } from './components/DockContainer'; import { TauriAPI } from './api/tauri'; import { SettingsService } from './services/SettingsService'; +import { checkForUpdatesOnStartup } from './utils/updater'; import { useLocale } from './hooks/useLocale'; import { en, zh } from './locales'; import { Loader2, Globe } from 'lucide-react'; @@ -49,6 +51,7 @@ function App() { const [showProfiler, setShowProfiler] = useState(false); const [showPortManager, setShowPortManager] = useState(false); const [showSettings, setShowSettings] = useState(false); + const [showAbout, setShowAbout] = useState(false); const [pluginUpdateTrigger, setPluginUpdateTrigger] = useState(0); const [isRemoteConnected, setIsRemoteConnected] = useState(false); const [isProfilerMode, setIsProfilerMode] = useState(false); @@ -193,6 +196,9 @@ function App() { setUiRegistry(uiRegistry); setSettingsRegistry(settingsRegistry); setStatus(t('header.status.ready')); + + // Check for updates on startup (after 3 seconds) + checkForUpdatesOnStartup(); } catch (error) { console.error('Failed to initialize editor:', error); setStatus(t('header.status.failed')); @@ -326,6 +332,10 @@ function App() { } }; + const handleOpenAbout = () => { + setShowAbout(true); + }; + useEffect(() => { if (projectLoaded && entityStore && messageHub && logService && uiRegistry && pluginManager) { let corePanels: DockablePanel[]; @@ -491,6 +501,7 @@ function App() { onOpenPortManager={() => setShowPortManager(true)} onOpenSettings={() => setShowSettings(true)} onToggleDevtools={handleToggleDevtools} + onOpenAbout={handleOpenAbout} />
); } diff --git a/packages/editor-app/src/components/AboutDialog.tsx b/packages/editor-app/src/components/AboutDialog.tsx new file mode 100644 index 00000000..166b2da5 --- /dev/null +++ b/packages/editor-app/src/components/AboutDialog.tsx @@ -0,0 +1,213 @@ +import { useState, useEffect } from 'react'; +import { X, RefreshCw, Check, AlertCircle, Download } from 'lucide-react'; +import { checkForUpdates } from '../utils/updater'; +import { getVersion } from '@tauri-apps/api/app'; +import { open } from '@tauri-apps/plugin-shell'; +import '../styles/AboutDialog.css'; + +interface AboutDialogProps { + onClose: () => void; + locale?: string; +} + +export function AboutDialog({ onClose, locale = 'en' }: AboutDialogProps) { + const [checking, setChecking] = useState(false); + const [updateStatus, setUpdateStatus] = useState<'idle' | 'checking' | 'available' | 'latest' | 'error'>('idle'); + const [version, setVersion] = useState('1.0.0'); + const [newVersion, setNewVersion] = useState(''); + + useEffect(() => { + // Fetch version on mount + const fetchVersion = async () => { + try { + const currentVersion = await getVersion(); + setVersion(currentVersion); + } catch (error) { + console.error('Failed to get version:', error); + } + }; + fetchVersion(); + }, []); + + const t = (key: string) => { + const translations: Record> = { + en: { + title: 'About ECS Framework Editor', + version: 'Version', + description: 'High-performance ECS framework editor for game development', + checkUpdate: 'Check for Updates', + checking: 'Checking...', + updateAvailable: 'New version available', + latest: 'You are using the latest version', + error: 'Failed to check for updates', + download: 'Download Update', + close: 'Close', + copyright: '© 2025 ESEngine. All rights reserved.', + website: 'Website', + github: 'GitHub' + }, + zh: { + title: '关于 ECS Framework Editor', + version: '版本', + description: '高性能 ECS 框架编辑器,用于游戏开发', + checkUpdate: '检查更新', + checking: '检查中...', + updateAvailable: '发现新版本', + latest: '您正在使用最新版本', + error: '检查更新失败', + download: '下载更新', + close: '关闭', + copyright: '© 2025 ESEngine. 保留所有权利。', + website: '官网', + github: 'GitHub' + } + }; + return translations[locale]?.[key] || key; + }; + + const handleCheckUpdate = async () => { + setChecking(true); + setUpdateStatus('checking'); + + try { + const currentVersion = await getVersion(); + setVersion(currentVersion); + + // 使用我们的 updater 工具检查更新 + const result = await checkForUpdates(false); + + if (result.error) { + setUpdateStatus('error'); + } else if (result.available) { + setUpdateStatus('available'); + if (result.version) { + setNewVersion(result.version); + } + } else { + setUpdateStatus('latest'); + } + } catch (error: any) { + console.error('Check update failed:', error); + setUpdateStatus('error'); + } finally { + setChecking(false); + } + }; + + const getStatusIcon = () => { + switch (updateStatus) { + case 'checking': + return ; + case 'available': + return ; + case 'latest': + return ; + case 'error': + return ; + default: + return null; + } + }; + + const getStatusText = () => { + switch (updateStatus) { + case 'checking': + return t('checking'); + case 'available': + return `${t('updateAvailable')} (v${newVersion})`; + case 'latest': + return t('latest'); + case 'error': + return t('error'); + default: + return ''; + } + }; + + const handleOpenGithub = async () => { + try { + await open('https://github.com/esengine/ecs-framework'); + } catch (error) { + console.error('Failed to open GitHub link:', error); + } + }; + + return ( +
+
e.stopPropagation()}> +
+

{t('title')}

+ +
+ +
+
+
ECS
+
+ +
+

ECS Framework Editor

+

+ {t('version')}: {version} +

+

+ {t('description')} +

+
+ +
+ + + {updateStatus !== 'idle' && ( +
+ {getStatusIcon()} + {getStatusText()} +
+ )} +
+ + + +
+

{t('copyright')}

+
+
+ +
+ +
+
+
+ ); +} diff --git a/packages/editor-app/src/components/MenuBar.tsx b/packages/editor-app/src/components/MenuBar.tsx index 8db32e32..29b90a58 100644 --- a/packages/editor-app/src/components/MenuBar.tsx +++ b/packages/editor-app/src/components/MenuBar.tsx @@ -29,6 +29,7 @@ interface MenuBarProps { onOpenPortManager?: () => void; onOpenSettings?: () => void; onToggleDevtools?: () => void; + onOpenAbout?: () => void; } export function MenuBar({ @@ -47,7 +48,8 @@ export function MenuBar({ onOpenProfiler, onOpenPortManager, onOpenSettings, - onToggleDevtools + onToggleDevtools, + onOpenAbout }: MenuBarProps) { const [openMenu, setOpenMenu] = useState(null); const [pluginMenuItems, setPluginMenuItems] = useState([]); @@ -144,6 +146,7 @@ export function MenuBar({ settings: 'Settings', help: 'Help', documentation: 'Documentation', + checkForUpdates: 'Check for Updates', about: 'About', devtools: 'Developer Tools' }, @@ -176,6 +179,7 @@ export function MenuBar({ settings: '设置', help: '帮助', documentation: '文档', + checkForUpdates: '检查更新', about: '关于', devtools: '开发者工具' } @@ -233,7 +237,8 @@ export function MenuBar({ help: [ { label: t('documentation'), disabled: true }, { separator: true }, - { label: t('about'), disabled: true } + { label: t('checkForUpdates'), onClick: onOpenAbout }, + { label: t('about'), onClick: onOpenAbout } ] }; diff --git a/packages/editor-app/src/styles/AboutDialog.css b/packages/editor-app/src/styles/AboutDialog.css new file mode 100644 index 00000000..e8e9b0eb --- /dev/null +++ b/packages/editor-app/src/styles/AboutDialog.css @@ -0,0 +1,258 @@ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.6); + display: flex; + align-items: center; + justify-content: center; + z-index: 10000; + backdrop-filter: blur(4px); +} + +.about-dialog { + background: var(--color-bg-elevated); + border-radius: 8px; + padding: 0; + width: 500px; + max-width: 90vw; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); + animation: slideIn 0.2s ease-out; +} + +@keyframes slideIn { + from { + opacity: 0; + transform: translateY(-20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.about-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px 24px; + border-bottom: 1px solid var(--color-border-default); +} + +.about-header h2 { + margin: 0; + font-size: 18px; + font-weight: 600; + color: var(--color-text-primary); +} + +.about-header .close-btn { + background: none; + border: none; + color: var(--color-text-secondary); + cursor: pointer; + padding: 4px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 4px; + transition: all 0.2s; +} + +.about-header .close-btn:hover { + background: var(--color-bg-hover); + color: var(--color-text-primary); +} + +.about-content { + padding: 32px 24px; + display: flex; + flex-direction: column; + align-items: center; + gap: 24px; +} + +.about-logo { + width: 80px; + height: 80px; +} + +.logo-placeholder { + width: 100%; + height: 100%; + background: linear-gradient(135deg, #569CD6, #4EC9B0); + border-radius: 16px; + display: flex; + align-items: center; + justify-content: center; + font-size: 28px; + font-weight: bold; + color: white; + box-shadow: 0 4px 12px rgba(86, 156, 214, 0.3); +} + +.about-info { + text-align: center; +} + +.about-info h3 { + margin: 0 0 8px 0; + font-size: 20px; + font-weight: 600; + color: var(--color-text-primary); +} + +.about-version { + margin: 0 0 12px 0; + font-size: 14px; + color: var(--color-text-secondary); +} + +.about-description { + margin: 0; + font-size: 14px; + color: var(--color-text-secondary); + line-height: 1.5; + max-width: 400px; +} + +.about-update { + width: 100%; + display: flex; + flex-direction: column; + gap: 12px; + align-items: center; +} + +.update-btn { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 24px; + background: var(--color-bg-overlay); + border: 1px solid var(--color-border-default); + border-radius: 6px; + color: var(--color-text-primary); + font-size: 14px; + cursor: pointer; + transition: all 0.2s; +} + +.update-btn:hover:not(:disabled) { + background: var(--color-bg-hover); + border-color: var(--color-primary); +} + +.update-btn:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.update-status { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + border-radius: 6px; + font-size: 13px; +} + +.update-status.status-checking { + background: rgba(86, 156, 214, 0.1); + color: #569CD6; +} + +.update-status.status-available { + background: rgba(78, 201, 176, 0.1); + color: #4EC9B0; +} + +.update-status.status-latest { + background: rgba(78, 201, 176, 0.1); + color: #4EC9B0; +} + +.update-status.status-error { + background: rgba(206, 145, 120, 0.1); + color: #CE9178; +} + +.update-status .status-available { + color: #4EC9B0; +} + +.update-status .status-latest { + color: #4EC9B0; +} + +.update-status .status-error { + color: #CE9178; +} + +.about-links { + display: flex; + gap: 16px; + margin-top: 8px; +} + +.about-link { + color: var(--color-primary); + text-decoration: none; + font-size: 14px; + transition: color 0.2s; +} + +.about-link:hover { + color: var(--color-primary-hover); + text-decoration: underline; +} + +.about-footer { + margin-top: 8px; + padding-top: 16px; + border-top: 1px solid var(--color-border-default); + text-align: center; +} + +.about-footer p { + margin: 0; + font-size: 12px; + color: var(--color-text-tertiary); +} + +.about-actions { + padding: 16px 24px; + border-top: 1px solid var(--color-border-default); + display: flex; + justify-content: flex-end; +} + +.btn-primary { + padding: 8px 24px; + background: var(--color-primary); + border: none; + border-radius: 6px; + color: white; + font-size: 14px; + cursor: pointer; + transition: background 0.2s; +} + +.btn-primary:hover { + background: var(--color-primary-hover); +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +.animate-spin { + animation: spin 1s linear infinite; +} diff --git a/packages/editor-app/src/utils/updater.ts b/packages/editor-app/src/utils/updater.ts new file mode 100644 index 00000000..2fe76542 --- /dev/null +++ b/packages/editor-app/src/utils/updater.ts @@ -0,0 +1,60 @@ +import { check } from '@tauri-apps/plugin-updater'; + +export interface UpdateCheckResult { + available: boolean; + version?: string; + currentVersion?: string; + error?: string; +} + +/** + * 检查应用更新 + * + * 自动检查 GitHub Releases 是否有新版本 + * 如果有更新,提示用户并可选择安装 + */ +export async function checkForUpdates(silent: boolean = false): Promise { + try { + const update = await check(); + + if (update?.available) { + console.log(`发现新版本: ${update.version}`); + console.log(`当前版本: ${update.currentVersion}`); + console.log(`更新日期: ${update.date}`); + console.log(`更新说明:\n${update.body}`); + + if (!silent) { + // Tauri 会自动显示更新对话框(因为配置了 dialog: true) + // 用户点击确认后会自动下载并安装,安装完成后会自动重启 + await update.downloadAndInstall(); + } + + return { + available: true, + version: update.version, + currentVersion: update.currentVersion + }; + } else { + if (!silent) { + console.log('当前已是最新版本'); + } + return { available: false }; + } + } catch (error) { + console.error('检查更新失败:', error); + return { + available: false, + error: error instanceof Error ? error.message : '检查更新失败' + }; + } +} + +/** + * 应用启动时静默检查更新 + */ +export async function checkForUpdatesOnStartup(): Promise { + // 延迟 3 秒后检查,避免影响启动速度 + setTimeout(() => { + checkForUpdates(true); + }, 3000); +}