feat(updater): 增加应用自动更新入口 (#84)
H-Chris233: closes #33。 实现 Tauri 内置 updater 集成: - Cargo: tauri-plugin-updater v2 - tauri.conf.json: 加 updater 配置(pubkey + endpoint 指向 GitHub Releases latest) - capabilities/default.json: 授予 updater:default 能力 - lib.rs: 注册 updater 插件 + restart_app 命令 - src/pages/Settings.tsx + i18n: 关于区域加'检查'按钮、检查/下载/安装/重启对话框流 - 新脚本 scripts/write-updater-manifest.mjs: release 侧从 build artifact 拉签名生成 manifest - workflow: TAURI_SIGNING_PRIVATE_KEY env + 各平台 build 时 createUpdaterArtifacts true 注意:下次 v*-tauri tag 必须配 TAURI_SIGNING_PRIVATE_KEY secret,否则 CI 硬失败。
This commit is contained in:
1 parent
786bc574a7
commit
a9c81e6e0e
16 files changed
+695
-31
No files matched your search
@@ -30,11 +30,20 @@ jobs:
|
|||||||
include:
|
include:
|
||||||
- platform: macos-latest
|
- platform: macos-latest
|
||||||
rust-target: aarch64-apple-darwin
|
rust-target: aarch64-apple-darwin
|
||||||
|
updater-target: darwin
|
||||||
|
updater-arch: aarch64
|
||||||
- platform: windows-latest
|
- platform: windows-latest
|
||||||
rust-target: x86_64-pc-windows-msvc
|
rust-target: x86_64-pc-windows-msvc
|
||||||
|
updater-target: windows
|
||||||
|
updater-arch: x86_64
|
||||||
- platform: ubuntu-22.04
|
- platform: ubuntu-22.04
|
||||||
rust-target: x86_64-unknown-linux-gnu
|
rust-target: x86_64-unknown-linux-gnu
|
||||||
|
updater-target: linux
|
||||||
|
updater-arch: x86_64
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
|
env:
|
||||||
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||||
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
@@ -76,6 +85,17 @@ jobs:
|
|||||||
working-directory: 'openless-all/app'
|
working-directory: 'openless-all/app'
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Check updater signing availability
|
||||||
|
if: startsWith(github.ref, 'refs/tags/v') && endsWith(github.ref, '-tauri')
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||||
|
run: |
|
||||||
|
if [ -z "${TAURI_SIGNING_PRIVATE_KEY:-}" ]; then
|
||||||
|
echo "::error::TAURI_SIGNING_PRIVATE_KEY is required for signed auto-update artifacts."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Check Apple signing availability
|
- name: Check Apple signing availability
|
||||||
if: matrix.platform == 'macos-latest' && startsWith(github.ref, 'refs/tags/v') && endsWith(github.ref, '-tauri')
|
if: matrix.platform == 'macos-latest' && startsWith(github.ref, 'refs/tags/v') && endsWith(github.ref, '-tauri')
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -159,19 +179,50 @@ jobs:
|
|||||||
working-directory: 'openless-all/app'
|
working-directory: 'openless-all/app'
|
||||||
env:
|
env:
|
||||||
INSTALL: '0' # CI 不要装到 /Applications,也不要 reset TCC
|
INSTALL: '0' # CI 不要装到 /Applications,也不要 reset TCC
|
||||||
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||||
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||||
run: bash scripts/build-mac.sh
|
run: bash scripts/build-mac.sh
|
||||||
|
|
||||||
# ── Windows:仅 Windows 跑通用 tauri build(Linux 用下方专用 step,避免重复构建)──
|
# ── Windows:仅 Windows 跑通用 tauri build(Linux 用下方专用 step,避免重复构建)──
|
||||||
- name: Build (Windows)
|
- name: Build (Windows)
|
||||||
if: matrix.platform == 'windows-latest'
|
if: matrix.platform == 'windows-latest'
|
||||||
|
shell: bash
|
||||||
working-directory: 'openless-all/app'
|
working-directory: 'openless-all/app'
|
||||||
run: npm run tauri build
|
env:
|
||||||
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||||
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||||
|
run: |
|
||||||
|
if [ -n "${TAURI_SIGNING_PRIVATE_KEY:-}" ]; then
|
||||||
|
npm run tauri -- build --config '{"bundle":{"createUpdaterArtifacts":true}}'
|
||||||
|
else
|
||||||
|
npm run tauri build
|
||||||
|
fi
|
||||||
|
|
||||||
# ── Linux:产 deb / rpm / AppImage ──
|
# ── Linux:产 deb / rpm / AppImage ──
|
||||||
- name: Build (Linux)
|
- name: Build (Linux)
|
||||||
if: matrix.platform == 'ubuntu-22.04'
|
if: matrix.platform == 'ubuntu-22.04'
|
||||||
|
shell: bash
|
||||||
working-directory: 'openless-all/app'
|
working-directory: 'openless-all/app'
|
||||||
run: npm run tauri -- build --bundles deb,rpm,appimage
|
env:
|
||||||
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||||
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||||
|
run: |
|
||||||
|
if [ -n "${TAURI_SIGNING_PRIVATE_KEY:-}" ]; then
|
||||||
|
npm run tauri -- build --bundles deb,rpm,appimage --config '{"bundle":{"createUpdaterArtifacts":true}}'
|
||||||
|
else
|
||||||
|
npm run tauri -- build --bundles deb,rpm,appimage
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Write updater manifest
|
||||||
|
if: env.TAURI_SIGNING_PRIVATE_KEY != ''
|
||||||
|
shell: bash
|
||||||
|
working-directory: 'openless-all/app'
|
||||||
|
env:
|
||||||
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||||
|
OPENLESS_UPDATE_TARGET: ${{ matrix.updater-target }}
|
||||||
|
OPENLESS_UPDATE_ARCH: ${{ matrix.updater-arch }}
|
||||||
|
OPENLESS_UPDATE_REPO: appergb/openless
|
||||||
|
run: node scripts/write-updater-manifest.mjs
|
||||||
|
|
||||||
# ── 收集产物 ──
|
# ── 收集产物 ──
|
||||||
- name: List artifacts (debug)
|
- name: List artifacts (debug)
|
||||||
@@ -206,6 +257,17 @@ jobs:
|
|||||||
openless-all/app/src-tauri/target/release/bundle/dmg/*.dmg
|
openless-all/app/src-tauri/target/release/bundle/dmg/*.dmg
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Upload macOS updater artifacts
|
||||||
|
if: matrix.platform == 'macos-latest' && env.TAURI_SIGNING_PRIVATE_KEY != ''
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: openless-macos-arm64-updater
|
||||||
|
path: |
|
||||||
|
openless-all/app/src-tauri/target/release/bundle/macos/*.app.tar.gz
|
||||||
|
openless-all/app/src-tauri/target/release/bundle/macos/*.app.tar.gz.sig
|
||||||
|
openless-all/app/src-tauri/target/release/bundle/latest-darwin-aarch64.json
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Upload Windows artifacts
|
- name: Upload Windows artifacts
|
||||||
if: matrix.platform == 'windows-latest'
|
if: matrix.platform == 'windows-latest'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
@@ -216,6 +278,17 @@ jobs:
|
|||||||
openless-all/app/src-tauri/target/release/bundle/msi/*.msi
|
openless-all/app/src-tauri/target/release/bundle/msi/*.msi
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Upload Windows updater artifacts
|
||||||
|
if: matrix.platform == 'windows-latest' && env.TAURI_SIGNING_PRIVATE_KEY != ''
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: openless-windows-x64-updater
|
||||||
|
path: |
|
||||||
|
openless-all/app/src-tauri/target/release/bundle/nsis/*.exe.sig
|
||||||
|
openless-all/app/src-tauri/target/release/bundle/msi/*.msi.sig
|
||||||
|
openless-all/app/src-tauri/target/release/bundle/latest-windows-x86_64.json
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Upload Linux artifacts
|
- name: Upload Linux artifacts
|
||||||
if: matrix.platform == 'ubuntu-22.04'
|
if: matrix.platform == 'ubuntu-22.04'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
@@ -227,6 +300,16 @@ jobs:
|
|||||||
openless-all/app/src-tauri/target/release/bundle/appimage/*.AppImage
|
openless-all/app/src-tauri/target/release/bundle/appimage/*.AppImage
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Upload Linux updater artifacts
|
||||||
|
if: matrix.platform == 'ubuntu-22.04' && env.TAURI_SIGNING_PRIVATE_KEY != ''
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: openless-linux-x64-updater
|
||||||
|
path: |
|
||||||
|
openless-all/app/src-tauri/target/release/bundle/appimage/*.AppImage.sig
|
||||||
|
openless-all/app/src-tauri/target/release/bundle/latest-linux-x86_64.json
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
# ── tag 推送时,同步上传到 GitHub Release ──
|
# ── tag 推送时,同步上传到 GitHub Release ──
|
||||||
- name: Create / update release
|
- name: Create / update release
|
||||||
if: startsWith(github.ref, 'refs/tags/v') && endsWith(github.ref, '-tauri')
|
if: startsWith(github.ref, 'refs/tags/v') && endsWith(github.ref, '-tauri')
|
||||||
@@ -239,8 +322,14 @@ jobs:
|
|||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
files: |
|
files: |
|
||||||
openless-all/app/src-tauri/target/release/bundle/dmg/*.dmg
|
openless-all/app/src-tauri/target/release/bundle/dmg/*.dmg
|
||||||
|
openless-all/app/src-tauri/target/release/bundle/macos/*.app.tar.gz
|
||||||
|
openless-all/app/src-tauri/target/release/bundle/macos/*.app.tar.gz.sig
|
||||||
openless-all/app/src-tauri/target/release/bundle/nsis/*.exe
|
openless-all/app/src-tauri/target/release/bundle/nsis/*.exe
|
||||||
|
openless-all/app/src-tauri/target/release/bundle/nsis/*.exe.sig
|
||||||
openless-all/app/src-tauri/target/release/bundle/msi/*.msi
|
openless-all/app/src-tauri/target/release/bundle/msi/*.msi
|
||||||
|
openless-all/app/src-tauri/target/release/bundle/msi/*.msi.sig
|
||||||
openless-all/app/src-tauri/target/release/bundle/deb/*.deb
|
openless-all/app/src-tauri/target/release/bundle/deb/*.deb
|
||||||
openless-all/app/src-tauri/target/release/bundle/rpm/*.rpm
|
openless-all/app/src-tauri/target/release/bundle/rpm/*.rpm
|
||||||
openless-all/app/src-tauri/target/release/bundle/appimage/*.AppImage
|
openless-all/app/src-tauri/target/release/bundle/appimage/*.AppImage
|
||||||
|
openless-all/app/src-tauri/target/release/bundle/appimage/*.AppImage.sig
|
||||||
|
openless-all/app/src-tauri/target/release/bundle/latest-*.json
|
||||||
Generated
+12
-2
@@ -1,15 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "openless-app",
|
"name": "openless-app",
|
||||||
"version": "1.1.4",
|
"version": "1.2.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "openless-app",
|
"name": "openless-app",
|
||||||
"version": "1.1.4",
|
"version": "1.2.2",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tauri-apps/api": "^2.1.1",
|
"@tauri-apps/api": "^2.1.1",
|
||||||
"@tauri-apps/plugin-shell": "^2.0.1",
|
"@tauri-apps/plugin-shell": "^2.0.1",
|
||||||
|
"@tauri-apps/plugin-updater": "^2.10.1",
|
||||||
"i18next": "^26.0.8",
|
"i18next": "^26.0.8",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
@@ -1349,6 +1350,15 @@
|
|||||||
"@tauri-apps/api": "^2.10.1"
|
"@tauri-apps/api": "^2.10.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tauri-apps/plugin-updater": {
|
||||||
|
"version": "2.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-updater/-/plugin-updater-2.10.1.tgz",
|
||||||
|
"integrity": "sha512-NFYMg+tWOZPJdzE/PpFj2qfqwAWwNS3kXrb1tm1gnBJ9mYzZ4WDRrwy8udzWoAnfGCHLuePNLY1WVCNHnh3eRA==",
|
||||||
|
"license": "MIT OR Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@tauri-apps/api": "^2.10.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/babel__core": {
|
"node_modules/@types/babel__core": {
|
||||||
"version": "7.20.5",
|
"version": "7.20.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tauri-apps/api": "^2.1.1",
|
"@tauri-apps/api": "^2.1.1",
|
||||||
"@tauri-apps/plugin-shell": "^2.0.1",
|
"@tauri-apps/plugin-shell": "^2.0.1",
|
||||||
|
"@tauri-apps/plugin-updater": "^2.10.1",
|
||||||
"i18next": "^26.0.8",
|
"i18next": "^26.0.8",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
|
|||||||
@@ -25,7 +25,11 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "▶ tauri build"
|
echo "▶ tauri build"
|
||||||
npm run tauri build
|
TAURI_BUILD_ARGS=(build)
|
||||||
|
if [ -n "${TAURI_SIGNING_PRIVATE_KEY:-}" ] || [ -n "${TAURI_SIGNING_PRIVATE_KEY_PATH:-}" ]; then
|
||||||
|
TAURI_BUILD_ARGS+=(--config '{"bundle":{"createUpdaterArtifacts":true}}')
|
||||||
|
fi
|
||||||
|
npm run tauri -- "${TAURI_BUILD_ARGS[@]}"
|
||||||
|
|
||||||
echo "▶ 校验 Info.plist / 签名"
|
echo "▶ 校验 Info.plist / 签名"
|
||||||
/usr/libexec/PlistBuddy -c "Print :NSMicrophoneUsageDescription" "$INFO" >/dev/null
|
/usr/libexec/PlistBuddy -c "Print :NSMicrophoneUsageDescription" "$INFO" >/dev/null
|
||||||
|
|||||||
+63
@@ -0,0 +1,63 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
import { existsSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
||||||
|
import { basename, join } from 'node:path';
|
||||||
|
import process from 'node:process';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
const target = process.env.OPENLESS_UPDATE_TARGET;
|
||||||
|
const arch = process.env.OPENLESS_UPDATE_ARCH;
|
||||||
|
const repo = process.env.OPENLESS_UPDATE_REPO || 'appergb/openless';
|
||||||
|
|
||||||
|
if (!target || !arch) {
|
||||||
|
throw new Error('OPENLESS_UPDATE_TARGET and OPENLESS_UPDATE_ARCH are required');
|
||||||
|
}
|
||||||
|
|
||||||
|
const packageJson = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
||||||
|
const bundleDir = fileURLToPath(new URL('../src-tauri/target/release/bundle/', import.meta.url));
|
||||||
|
|
||||||
|
const candidatesByTarget = {
|
||||||
|
darwin: ['macos/OpenLess.app.tar.gz'],
|
||||||
|
windows: ['nsis/OpenLess_*_x64-setup.exe', 'nsis/OpenLess*_x64-setup.exe'],
|
||||||
|
linux: ['appimage/OpenLess_*.AppImage', 'appimage/OpenLess*.AppImage'],
|
||||||
|
};
|
||||||
|
|
||||||
|
function findFirst(patterns) {
|
||||||
|
for (const pattern of patterns) {
|
||||||
|
if (!pattern.includes('*')) {
|
||||||
|
const path = join(bundleDir, pattern);
|
||||||
|
if (existsSync(path)) return path;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const [dir, namePattern] = pattern.split('/');
|
||||||
|
const dirPath = join(bundleDir, dir);
|
||||||
|
if (!existsSync(dirPath)) continue;
|
||||||
|
const prefix = namePattern.split('*')[0];
|
||||||
|
const suffix = namePattern.split('*').at(-1);
|
||||||
|
const match = readdirSync(dirPath)
|
||||||
|
.filter(name => name.startsWith(prefix) && name.endsWith(suffix))
|
||||||
|
.sort()[0];
|
||||||
|
if (match) return join(dirPath, match);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const artifact = findFirst(candidatesByTarget[target] || []);
|
||||||
|
if (!artifact) {
|
||||||
|
throw new Error(`No updater artifact found for ${target} in ${bundleDir}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const signaturePath = `${artifact}.sig`;
|
||||||
|
if (!existsSync(signaturePath)) {
|
||||||
|
throw new Error(`Missing updater signature: ${signaturePath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const assetName = basename(artifact);
|
||||||
|
const manifestName = `latest-${target}-${arch}.json`;
|
||||||
|
const manifest = {
|
||||||
|
version: packageJson.version,
|
||||||
|
pub_date: new Date().toISOString(),
|
||||||
|
url: `https://github.com/${repo}/releases/latest/download/${assetName}`,
|
||||||
|
signature: readFileSync(signaturePath, 'utf8').trim(),
|
||||||
|
};
|
||||||
|
|
||||||
|
writeFileSync(join(bundleDir, manifestName), `${JSON.stringify(manifest, null, 2)}\n`);
|
||||||
|
console.log(`Wrote ${manifestName} for ${assetName}`);
|
||||||
Generated
+245
-9
@@ -119,6 +119,15 @@ version = "1.0.102"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arbitrary"
|
||||||
|
version = "1.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
|
||||||
|
dependencies = [
|
||||||
|
"derive_arbitrary",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arboard"
|
name = "arboard"
|
||||||
version = "3.6.1"
|
version = "3.6.1"
|
||||||
@@ -858,7 +867,7 @@ dependencies = [
|
|||||||
"core-foundation-sys 0.8.7",
|
"core-foundation-sys 0.8.7",
|
||||||
"coreaudio-rs",
|
"coreaudio-rs",
|
||||||
"dasp_sample",
|
"dasp_sample",
|
||||||
"jni",
|
"jni 0.21.1",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"mach2",
|
"mach2",
|
||||||
@@ -1026,6 +1035,17 @@ dependencies = [
|
|||||||
"serde_core",
|
"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.117",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_more"
|
name = "derive_more"
|
||||||
version = "0.99.20"
|
version = "0.99.20"
|
||||||
@@ -1375,6 +1395,17 @@ dependencies = [
|
|||||||
"rustc_version",
|
"rustc_version",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "filetime"
|
||||||
|
version = "0.2.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"libredox",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "find-msvc-tools"
|
name = "find-msvc-tools"
|
||||||
version = "0.1.9"
|
version = "0.1.9"
|
||||||
@@ -2397,6 +2428,36 @@ dependencies = [
|
|||||||
"windows-sys 0.45.0",
|
"windows-sys 0.45.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jni"
|
||||||
|
version = "0.22.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"combine",
|
||||||
|
"jni-macros",
|
||||||
|
"jni-sys 0.4.1",
|
||||||
|
"log",
|
||||||
|
"simd_cesu8",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"walkdir",
|
||||||
|
"windows-link 0.2.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jni-macros"
|
||||||
|
version = "0.22.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"rustc_version",
|
||||||
|
"simd_cesu8",
|
||||||
|
"syn 2.0.117",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jni-sys"
|
name = "jni-sys"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@@ -2560,7 +2621,10 @@ version = "0.1.16"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c"
|
checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bitflags 2.11.1",
|
||||||
"libc",
|
"libc",
|
||||||
|
"plain",
|
||||||
|
"redox_syscall 0.7.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2708,6 +2772,12 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "minisign-verify"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "22f9645cb765ea72b8111f36c522475d2daa0d22c957a9826437e97534bc4e9e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.8.9"
|
version = "0.8.9"
|
||||||
@@ -3040,6 +3110,7 @@ checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.1",
|
"bitflags 2.11.1",
|
||||||
"block2 0.6.2",
|
"block2 0.6.2",
|
||||||
|
"libc",
|
||||||
"objc2 0.6.4",
|
"objc2 0.6.4",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
]
|
]
|
||||||
@@ -3067,6 +3138,18 @@ dependencies = [
|
|||||||
"objc2-foundation 0.2.2",
|
"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.11.1",
|
||||||
|
"objc2 0.6.4",
|
||||||
|
"objc2-app-kit 0.3.2",
|
||||||
|
"objc2-foundation 0.3.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-quartz-core"
|
name = "objc2-quartz-core"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@@ -3124,7 +3207,7 @@ version = "0.6.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e8b61bebd49e5d43f5f8cc7ee2891c16e0f41ec7954d36bcb6c14c5e0de867fb"
|
checksum = "e8b61bebd49e5d43f5f8cc7ee2891c16e0f41ec7954d36bcb6c14c5e0de867fb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jni",
|
"jni 0.21.1",
|
||||||
"ndk 0.8.0",
|
"ndk 0.8.0",
|
||||||
"ndk-context",
|
"ndk-context",
|
||||||
"num-derive",
|
"num-derive",
|
||||||
@@ -3196,6 +3279,7 @@ dependencies = [
|
|||||||
"tauri-build",
|
"tauri-build",
|
||||||
"tauri-plugin-shell",
|
"tauri-plugin-shell",
|
||||||
"tauri-plugin-single-instance",
|
"tauri-plugin-single-instance",
|
||||||
|
"tauri-plugin-updater",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-tungstenite",
|
"tokio-tungstenite",
|
||||||
@@ -3237,6 +3321,20 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"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.4",
|
||||||
|
"objc2-foundation 0.3.2",
|
||||||
|
"objc2-osa-kit",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pango"
|
name = "pango"
|
||||||
version = "0.18.3"
|
version = "0.18.3"
|
||||||
@@ -3286,7 +3384,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"redox_syscall",
|
"redox_syscall 0.5.18",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"windows-link 0.2.1",
|
"windows-link 0.2.1",
|
||||||
]
|
]
|
||||||
@@ -3513,6 +3611,12 @@ version = "0.3.33"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
|
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "plain"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "plist"
|
name = "plist"
|
||||||
version = "1.9.0"
|
version = "1.9.0"
|
||||||
@@ -3927,6 +4031,15 @@ dependencies = [
|
|||||||
"bitflags 2.11.1",
|
"bitflags 2.11.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_syscall"
|
||||||
|
version = "0.7.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.11.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_users"
|
name = "redox_users"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
@@ -4041,15 +4154,20 @@ dependencies = [
|
|||||||
"http-body",
|
"http-body",
|
||||||
"http-body-util",
|
"http-body-util",
|
||||||
"hyper",
|
"hyper",
|
||||||
|
"hyper-rustls",
|
||||||
"hyper-util",
|
"hyper-util",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
"rustls",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"rustls-platform-verifier",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sync_wrapper",
|
"sync_wrapper",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tokio-rustls",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"tower",
|
"tower",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
@@ -4139,6 +4257,33 @@ dependencies = [
|
|||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-platform-verifier"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation 0.10.1",
|
||||||
|
"core-foundation-sys 0.8.7",
|
||||||
|
"jni 0.22.4",
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"rustls",
|
||||||
|
"rustls-native-certs",
|
||||||
|
"rustls-platform-verifier-android",
|
||||||
|
"rustls-webpki",
|
||||||
|
"security-framework",
|
||||||
|
"security-framework-sys",
|
||||||
|
"webpki-root-certs",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-platform-verifier-android"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-webpki"
|
name = "rustls-webpki"
|
||||||
version = "0.103.13"
|
version = "0.103.13"
|
||||||
@@ -4562,6 +4707,22 @@ version = "0.3.9"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
|
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simd_cesu8"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33"
|
||||||
|
dependencies = [
|
||||||
|
"rustc_version",
|
||||||
|
"simdutf8",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simdutf8"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "simplelog"
|
name = "simplelog"
|
||||||
version = "0.12.2"
|
version = "0.12.2"
|
||||||
@@ -4622,7 +4783,7 @@ dependencies = [
|
|||||||
"objc2-foundation 0.3.2",
|
"objc2-foundation 0.3.2",
|
||||||
"objc2-quartz-core 0.3.2",
|
"objc2-quartz-core 0.3.2",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"redox_syscall",
|
"redox_syscall 0.5.18",
|
||||||
"tracing",
|
"tracing",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
@@ -4805,7 +4966,7 @@ dependencies = [
|
|||||||
"gdkwayland-sys",
|
"gdkwayland-sys",
|
||||||
"gdkx11-sys",
|
"gdkx11-sys",
|
||||||
"gtk",
|
"gtk",
|
||||||
"jni",
|
"jni 0.21.1",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"ndk 0.9.0",
|
"ndk 0.9.0",
|
||||||
@@ -4837,6 +4998,17 @@ dependencies = [
|
|||||||
"syn 2.0.117",
|
"syn 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tar"
|
||||||
|
version = "0.4.45"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973"
|
||||||
|
dependencies = [
|
||||||
|
"filetime",
|
||||||
|
"libc",
|
||||||
|
"xattr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "target-lexicon"
|
name = "target-lexicon"
|
||||||
version = "0.12.16"
|
version = "0.12.16"
|
||||||
@@ -4860,7 +5032,7 @@ dependencies = [
|
|||||||
"gtk",
|
"gtk",
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"http",
|
"http",
|
||||||
"jni",
|
"jni 0.21.1",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"mime",
|
"mime",
|
||||||
@@ -5010,6 +5182,39 @@ dependencies = [
|
|||||||
"zbus",
|
"zbus",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tauri-plugin-updater"
|
||||||
|
version = "2.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "806d9dac662c2e4594ff03c647a552f2c9bd544e7d0f683ec58f872f952ce4af"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.22.1",
|
||||||
|
"dirs",
|
||||||
|
"flate2",
|
||||||
|
"futures-util",
|
||||||
|
"http",
|
||||||
|
"infer",
|
||||||
|
"log",
|
||||||
|
"minisign-verify",
|
||||||
|
"osakit",
|
||||||
|
"percent-encoding",
|
||||||
|
"reqwest 0.13.3",
|
||||||
|
"rustls",
|
||||||
|
"semver",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tar",
|
||||||
|
"tauri",
|
||||||
|
"tauri-plugin",
|
||||||
|
"tempfile",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"time",
|
||||||
|
"tokio",
|
||||||
|
"url",
|
||||||
|
"windows-sys 0.60.2",
|
||||||
|
"zip",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-runtime"
|
name = "tauri-runtime"
|
||||||
version = "2.10.1"
|
version = "2.10.1"
|
||||||
@@ -5020,7 +5225,7 @@ dependencies = [
|
|||||||
"dpi",
|
"dpi",
|
||||||
"gtk",
|
"gtk",
|
||||||
"http",
|
"http",
|
||||||
"jni",
|
"jni 0.21.1",
|
||||||
"objc2 0.6.4",
|
"objc2 0.6.4",
|
||||||
"objc2-ui-kit",
|
"objc2-ui-kit",
|
||||||
"objc2-web-kit",
|
"objc2-web-kit",
|
||||||
@@ -5043,7 +5248,7 @@ checksum = "e11ea2e6f801d275fdd890d6c9603736012742a1c33b96d0db788c9cdebf7f9e"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"gtk",
|
"gtk",
|
||||||
"http",
|
"http",
|
||||||
"jni",
|
"jni 0.21.1",
|
||||||
"log",
|
"log",
|
||||||
"objc2 0.6.4",
|
"objc2 0.6.4",
|
||||||
"objc2-app-kit 0.3.2",
|
"objc2-app-kit 0.3.2",
|
||||||
@@ -5984,6 +6189,15 @@ dependencies = [
|
|||||||
"system-deps",
|
"system-deps",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "webpki-root-certs"
|
||||||
|
version = "1.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c"
|
||||||
|
dependencies = [
|
||||||
|
"rustls-pki-types",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webpki-roots"
|
name = "webpki-roots"
|
||||||
version = "1.0.7"
|
version = "1.0.7"
|
||||||
@@ -6773,7 +6987,7 @@ dependencies = [
|
|||||||
"gtk",
|
"gtk",
|
||||||
"http",
|
"http",
|
||||||
"javascriptcore-rs",
|
"javascriptcore-rs",
|
||||||
"jni",
|
"jni 0.21.1",
|
||||||
"libc",
|
"libc",
|
||||||
"ndk 0.9.0",
|
"ndk 0.9.0",
|
||||||
"objc2 0.6.4",
|
"objc2 0.6.4",
|
||||||
@@ -6837,6 +7051,16 @@ version = "0.13.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
|
checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xattr"
|
||||||
|
version = "1.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rustix",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xkbcommon"
|
name = "xkbcommon"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
@@ -7018,6 +7242,18 @@ dependencies = [
|
|||||||
"syn 2.0.117",
|
"syn 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zip"
|
||||||
|
version = "4.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1"
|
||||||
|
dependencies = [
|
||||||
|
"arbitrary",
|
||||||
|
"crc32fast",
|
||||||
|
"indexmap 2.14.0",
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zmij"
|
name = "zmij"
|
||||||
version = "1.0.21"
|
version = "1.0.21"
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ tauri-build = { version = "2", features = [] }
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
tauri = { version = "2", features = ["macos-private-api", "tray-icon"] }
|
tauri = { version = "2", features = ["macos-private-api", "tray-icon"] }
|
||||||
tauri-plugin-shell = "2"
|
tauri-plugin-shell = "2"
|
||||||
|
tauri-plugin-updater = "2"
|
||||||
tauri-plugin-single-instance = "2"
|
tauri-plugin-single-instance = "2"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
"core:window:allow-close",
|
"core:window:allow-close",
|
||||||
"core:webview:default",
|
"core:webview:default",
|
||||||
"core:event:default",
|
"core:event:default",
|
||||||
"shell:allow-open"
|
"shell:allow-open",
|
||||||
|
"updater:default"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -46,6 +46,7 @@ pub fn run() {
|
|||||||
show_main_window(app);
|
show_main_window(app);
|
||||||
}))
|
}))
|
||||||
.plugin(tauri_plugin_shell::init())
|
.plugin(tauri_plugin_shell::init())
|
||||||
|
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||||
.manage(coordinator.clone())
|
.manage(coordinator.clone())
|
||||||
.setup(move |app| {
|
.setup(move |app| {
|
||||||
// Capsule 启动时定位到屏幕底部居中并隐藏;coordinator 按需显示。
|
// Capsule 启动时定位到屏幕底部居中并隐藏;coordinator 按需显示。
|
||||||
@@ -165,6 +166,7 @@ pub fn run() {
|
|||||||
commands::read_credential,
|
commands::read_credential,
|
||||||
commands::set_active_asr_provider,
|
commands::set_active_asr_provider,
|
||||||
commands::set_active_llm_provider,
|
commands::set_active_llm_provider,
|
||||||
|
restart_app,
|
||||||
])
|
])
|
||||||
.build(tauri::generate_context!())
|
.build(tauri::generate_context!())
|
||||||
.expect("error while building tauri application")
|
.expect("error while building tauri application")
|
||||||
@@ -187,6 +189,11 @@ pub fn run() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn restart_app(app: AppHandle) {
|
||||||
|
app.restart();
|
||||||
|
}
|
||||||
|
|
||||||
/// 把日志同时写到 stderr + ~/Library/Logs/OpenLess/openless.log(match Swift `Log.swift`)。
|
/// 把日志同时写到 stderr + ~/Library/Logs/OpenLess/openless.log(match Swift `Log.swift`)。
|
||||||
fn init_file_logger() {
|
fn init_file_logger() {
|
||||||
use simplelog::{
|
use simplelog::{
|
||||||
|
|||||||
@@ -65,5 +65,13 @@
|
|||||||
"infoPlist": "Info.plist",
|
"infoPlist": "Info.plist",
|
||||||
"entitlements": "Entitlements.plist"
|
"entitlements": "Entitlements.plist"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"updater": {
|
||||||
|
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDFERUFBODAzNTY0QzMyM0YKUldRL01reFdBNmpxSGE1K0JadlpONXNWTzhJcGZCRGxjUVdIWExNNFJpeUNsSGZwazdlQThhemkK",
|
||||||
|
"endpoints": [
|
||||||
|
"https://github.com/appergb/openless/releases/latest/download/latest-{{target}}-{{arch}}.json"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,8 +7,7 @@
|
|||||||
import { useEffect, useState, type CSSProperties } from 'react';
|
import { useEffect, useState, type CSSProperties } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Icon } from './Icon';
|
import { Icon } from './Icon';
|
||||||
import { APP_VERSION_LABEL } from '../lib/appVersion';
|
import { AboutUpdateControl, Settings as SettingsContent, type SettingsSectionId } from '../pages/Settings';
|
||||||
import { Settings as SettingsContent, type SettingsSectionId } from '../pages/Settings';
|
|
||||||
import { Row } from './ui/Row';
|
import { Row } from './ui/Row';
|
||||||
import { openExternal } from '../lib/ipc';
|
import { openExternal } from '../lib/ipc';
|
||||||
import {
|
import {
|
||||||
@@ -219,17 +218,9 @@ function AboutMini() {
|
|||||||
<img src="AppIcon.png" alt="" style={{ width: 56, height: 56, borderRadius: 13, boxShadow: '0 4px 10px rgba(0,0,0,.10), 0 0 0 0.5px rgba(0,0,0,.06)' }} />
|
<img src="AppIcon.png" alt="" style={{ width: 56, height: 56, borderRadius: 13, boxShadow: '0 4px 10px rgba(0,0,0,.10), 0 0 0 0.5px rgba(0,0,0,.06)' }} />
|
||||||
<div>
|
<div>
|
||||||
<div style={{ fontSize: 17, fontWeight: 600 }}>OpenLess</div>
|
<div style={{ fontSize: 17, fontWeight: 600 }}>OpenLess</div>
|
||||||
<div style={{ fontSize: 12, color: 'var(--ol-ink-3)' }}>{t('modal.about.tagline')} · {APP_VERSION_LABEL}</div>
|
<AboutUpdateControl tagline={t('modal.about.tagline')} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Row label={t('modal.about.checkUpdate')}>
|
|
||||||
<button
|
|
||||||
style={btnGhost}
|
|
||||||
onClick={() => openExternal('https://github.com/appergb/openless/releases')}
|
|
||||||
>
|
|
||||||
{t('modal.about.checkUpdateBtn')}
|
|
||||||
</button>
|
|
||||||
</Row>
|
|
||||||
<Row label={t('modal.about.docs')}>
|
<Row label={t('modal.about.docs')}>
|
||||||
<button
|
<button
|
||||||
style={btnGhost}
|
style={btnGhost}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export const en: typeof zhCN = {
|
|||||||
copy: 'Copy',
|
copy: 'Copy',
|
||||||
delete: 'Delete',
|
delete: 'Delete',
|
||||||
later: 'Later',
|
later: 'Later',
|
||||||
|
cancel: 'Cancel',
|
||||||
close: 'Close',
|
close: 'Close',
|
||||||
show: 'Show',
|
show: 'Show',
|
||||||
hide: 'Hide',
|
hide: 'Hide',
|
||||||
@@ -243,6 +244,10 @@ export const en: typeof zhCN = {
|
|||||||
about: {
|
about: {
|
||||||
tagline: 'Speak naturally, write perfectly',
|
tagline: 'Speak naturally, write perfectly',
|
||||||
checkUpdate: 'Check for updates',
|
checkUpdate: 'Check for updates',
|
||||||
|
checkUpdateBtn: 'Check',
|
||||||
|
checkingUpdate: 'Checking…',
|
||||||
|
upToDate: 'You are already on the latest version.',
|
||||||
|
updateError: 'Update check or install failed. Please try again later.',
|
||||||
openReleases: 'Open Releases',
|
openReleases: 'Open Releases',
|
||||||
source: 'Source',
|
source: 'Source',
|
||||||
docs: 'Docs',
|
docs: 'Docs',
|
||||||
@@ -253,6 +258,31 @@ export const en: typeof zhCN = {
|
|||||||
privacy: 'Privacy',
|
privacy: 'Privacy',
|
||||||
privacyDesc: 'All transcripts stay on this device. Cloud APIs are only called for real-time transcription/polish; no recordings are retained.',
|
privacyDesc: 'All transcripts stay on this device. Cloud APIs are only called for real-time transcription/polish; no recordings are retained.',
|
||||||
localFirst: 'Local-first',
|
localFirst: 'Local-first',
|
||||||
|
updateDialog: {
|
||||||
|
available: {
|
||||||
|
title: 'Update available',
|
||||||
|
desc: 'OpenLess {{version}} is available. Update now?',
|
||||||
|
},
|
||||||
|
downloading: {
|
||||||
|
title: 'Downloading update',
|
||||||
|
desc: 'Downloading OpenLess {{version}}. Keep the app open.',
|
||||||
|
},
|
||||||
|
downloaded: {
|
||||||
|
title: 'Update ready',
|
||||||
|
desc: 'OpenLess {{version}} has been installed. Restart automatically now to apply it?',
|
||||||
|
},
|
||||||
|
installing: {
|
||||||
|
title: 'Installing update',
|
||||||
|
desc: 'Installing OpenLess {{version}}. Keep the app open.',
|
||||||
|
},
|
||||||
|
install: 'Update now',
|
||||||
|
downloadingLabel: 'Downloading…',
|
||||||
|
installingLabel: 'Installing…',
|
||||||
|
later: 'Restart manually later',
|
||||||
|
restartNow: 'Restart now',
|
||||||
|
progress: '{{progress}}% · {{downloaded}} / {{total}}',
|
||||||
|
progressUnknown: '{{downloaded}} downloaded',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
modal: {
|
modal: {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export const zhCN = {
|
|||||||
copy: '复制',
|
copy: '复制',
|
||||||
delete: '删除',
|
delete: '删除',
|
||||||
later: '稍后',
|
later: '稍后',
|
||||||
|
cancel: '取消',
|
||||||
close: '关闭',
|
close: '关闭',
|
||||||
show: '显示',
|
show: '显示',
|
||||||
hide: '隐藏',
|
hide: '隐藏',
|
||||||
@@ -241,6 +242,10 @@ export const zhCN = {
|
|||||||
about: {
|
about: {
|
||||||
tagline: '自然说话,完美书写',
|
tagline: '自然说话,完美书写',
|
||||||
checkUpdate: '检查更新',
|
checkUpdate: '检查更新',
|
||||||
|
checkUpdateBtn: '检查',
|
||||||
|
checkingUpdate: '检查中…',
|
||||||
|
upToDate: '当前已是最新版本。',
|
||||||
|
updateError: '检查或更新失败,请稍后重试。',
|
||||||
openReleases: '打开 Releases',
|
openReleases: '打开 Releases',
|
||||||
source: '源码',
|
source: '源码',
|
||||||
docs: '文档',
|
docs: '文档',
|
||||||
@@ -251,6 +256,31 @@ export const zhCN = {
|
|||||||
privacy: '隐私',
|
privacy: '隐私',
|
||||||
privacyDesc: '所有识别结果仅保存在本机。云端 API 仅用于实时转写与润色,不会保留你的录音。',
|
privacyDesc: '所有识别结果仅保存在本机。云端 API 仅用于实时转写与润色,不会保留你的录音。',
|
||||||
localFirst: '本地优先',
|
localFirst: '本地优先',
|
||||||
|
updateDialog: {
|
||||||
|
available: {
|
||||||
|
title: '发现新版本',
|
||||||
|
desc: '发现 OpenLess {{version}},是否现在更新?',
|
||||||
|
},
|
||||||
|
downloading: {
|
||||||
|
title: '正在下载更新',
|
||||||
|
desc: '正在下载 OpenLess {{version}},请保持应用打开。',
|
||||||
|
},
|
||||||
|
downloaded: {
|
||||||
|
title: '更新已准备好',
|
||||||
|
desc: 'OpenLess {{version}} 已安装完成。是否现在自动重启以应用更新?',
|
||||||
|
},
|
||||||
|
installing: {
|
||||||
|
title: '正在安装更新',
|
||||||
|
desc: '正在安装 OpenLess {{version}},请保持应用打开。',
|
||||||
|
},
|
||||||
|
install: '现在更新',
|
||||||
|
downloadingLabel: '下载中…',
|
||||||
|
installingLabel: '安装中…',
|
||||||
|
later: '稍后手动重启',
|
||||||
|
restartNow: '现在重启',
|
||||||
|
progress: '{{progress}}% · {{downloaded}} / {{total}}',
|
||||||
|
progressUnknown: '已下载 {{downloaded}}',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
modal: {
|
modal: {
|
||||||
|
|||||||
@@ -216,6 +216,10 @@ export function triggerMicrophonePrompt(): Promise<void> {
|
|||||||
return invokeOrMock('trigger_microphone_prompt', undefined, () => undefined);
|
return invokeOrMock('trigger_microphone_prompt', undefined, () => undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function restartApp(): Promise<void> {
|
||||||
|
return invokeOrMock('restart_app', undefined, () => undefined);
|
||||||
|
}
|
||||||
|
|
||||||
export async function openExternal(url: string): Promise<void> {
|
export async function openExternal(url: string): Promise<void> {
|
||||||
if (!isTauri) {
|
if (!isTauri) {
|
||||||
window.open(url, '_blank', 'noopener,noreferrer');
|
window.open(url, '_blank', 'noopener,noreferrer');
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
// keep their inline-style literals 1:1 with the source JSX.
|
// keep their inline-style literals 1:1 with the source JSX.
|
||||||
|
|
||||||
import { useEffect, useRef, useState, type CSSProperties, type ReactNode } from 'react';
|
import { useEffect, useRef, useState, type CSSProperties, type ReactNode } from 'react';
|
||||||
|
import type { Update, DownloadEvent } from '@tauri-apps/plugin-updater';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Icon } from '../components/Icon';
|
import { Icon } from '../components/Icon';
|
||||||
import { APP_VERSION_LABEL } from '../lib/appVersion';
|
import { APP_VERSION_LABEL } from '../lib/appVersion';
|
||||||
@@ -11,11 +12,13 @@ import {
|
|||||||
checkAccessibilityPermission,
|
checkAccessibilityPermission,
|
||||||
checkMicrophonePermission,
|
checkMicrophonePermission,
|
||||||
getHotkeyStatus,
|
getHotkeyStatus,
|
||||||
|
isTauri,
|
||||||
openExternal,
|
openExternal,
|
||||||
openSystemSettings,
|
openSystemSettings,
|
||||||
readCredential,
|
readCredential,
|
||||||
requestAccessibilityPermission,
|
requestAccessibilityPermission,
|
||||||
requestMicrophonePermission,
|
requestMicrophonePermission,
|
||||||
|
restartApp,
|
||||||
setActiveAsrProvider,
|
setActiveAsrProvider,
|
||||||
setActiveLlmProvider,
|
setActiveLlmProvider,
|
||||||
setCredential,
|
setCredential,
|
||||||
@@ -752,10 +755,9 @@ function AboutSection() {
|
|||||||
>OL</div>
|
>OL</div>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ fontSize: 16, fontWeight: 600 }}>OpenLess</div>
|
<div style={{ fontSize: 16, fontWeight: 600 }}>OpenLess</div>
|
||||||
<div style={{ fontSize: 12, color: 'var(--ol-ink-3)' }}>{t('settings.about.tagline')} · {APP_VERSION_LABEL}</div>
|
<AboutUpdateControl tagline={t('settings.about.tagline')} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<SettingRow label={t('settings.about.checkUpdate')}><Btn variant="ghost" size="sm" onClick={() => openExternal('https://github.com/appergb/openless/releases')}>{t('settings.about.openReleases')}</Btn></SettingRow>
|
|
||||||
<SettingRow label={t('settings.about.source')}><Btn variant="ghost" size="sm" icon="link" onClick={() => openExternal('https://github.com/appergb/openless')}>GitHub</Btn></SettingRow>
|
<SettingRow label={t('settings.about.source')}><Btn variant="ghost" size="sm" icon="link" onClick={() => openExternal('https://github.com/appergb/openless')}>GitHub</Btn></SettingRow>
|
||||||
<SettingRow label={t('settings.about.docs')}><Btn variant="ghost" size="sm" icon="link" onClick={() => openExternal('https://github.com/appergb/openless#readme')}>README</Btn></SettingRow>
|
<SettingRow label={t('settings.about.docs')}><Btn variant="ghost" size="sm" icon="link" onClick={() => openExternal('https://github.com/appergb/openless#readme')}>README</Btn></SettingRow>
|
||||||
<SettingRow label={t('settings.about.feedback')}><Btn variant="ghost" size="sm" icon="link" onClick={() => openExternal('https://github.com/appergb/openless/issues')}>GitHub Issues</Btn></SettingRow>
|
<SettingRow label={t('settings.about.feedback')}><Btn variant="ghost" size="sm" icon="link" onClick={() => openExternal('https://github.com/appergb/openless/issues')}>GitHub Issues</Btn></SettingRow>
|
||||||
@@ -781,6 +783,190 @@ function AboutSection() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UpdateStatus = 'idle' | 'checking' | 'available' | 'none' | 'downloading' | 'installing' | 'downloaded' | 'error';
|
||||||
|
|
||||||
|
export function AboutUpdateControl({ tagline }: { tagline: string }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const updateRef = useRef<Update | null>(null);
|
||||||
|
const [updateStatus, setUpdateStatus] = useState<UpdateStatus>('idle');
|
||||||
|
const [version, setVersion] = useState('');
|
||||||
|
const [downloaded, setDownloaded] = useState(0);
|
||||||
|
const [contentLength, setContentLength] = useState<number | null>(null);
|
||||||
|
|
||||||
|
const checking = updateStatus === 'checking';
|
||||||
|
const busy = updateStatus === 'downloading' || updateStatus === 'installing';
|
||||||
|
const progress = contentLength && contentLength > 0 ? Math.min(100, Math.round((downloaded / contentLength) * 100)) : null;
|
||||||
|
|
||||||
|
const closeUpdate = async () => {
|
||||||
|
const current = updateRef.current;
|
||||||
|
updateRef.current = null;
|
||||||
|
if (current) {
|
||||||
|
try {
|
||||||
|
await current.close();
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[settings] failed to close update resource', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
void closeUpdate();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const resetProgress = () => {
|
||||||
|
setDownloaded(0);
|
||||||
|
setContentLength(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkForUpdates = async () => {
|
||||||
|
setUpdateStatus('checking');
|
||||||
|
setVersion('');
|
||||||
|
resetProgress();
|
||||||
|
await closeUpdate();
|
||||||
|
try {
|
||||||
|
if (!isTauri) {
|
||||||
|
setUpdateStatus('none');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { check } = await import('@tauri-apps/plugin-updater');
|
||||||
|
const next = await check();
|
||||||
|
updateRef.current = next;
|
||||||
|
setVersion(next?.version ?? '');
|
||||||
|
setUpdateStatus(next ? 'available' : 'none');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[settings] failed to check update', error);
|
||||||
|
setUpdateStatus('error');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const installUpdate = async () => {
|
||||||
|
const update = updateRef.current;
|
||||||
|
if (!update) return;
|
||||||
|
resetProgress();
|
||||||
|
setUpdateStatus('downloading');
|
||||||
|
try {
|
||||||
|
await update.download((event: DownloadEvent) => {
|
||||||
|
if (event.event === 'Started') {
|
||||||
|
resetProgress();
|
||||||
|
setContentLength(event.data.contentLength ?? null);
|
||||||
|
} else if (event.event === 'Progress') {
|
||||||
|
setDownloaded(value => value + event.data.chunkLength);
|
||||||
|
} else if (event.event === 'Finished') {
|
||||||
|
setUpdateStatus('installing');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setUpdateStatus('installing');
|
||||||
|
await update.install();
|
||||||
|
await closeUpdate();
|
||||||
|
setUpdateStatus('downloaded');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[settings] failed to install update', error);
|
||||||
|
await closeUpdate();
|
||||||
|
setUpdateStatus('error');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const dismissUpdateDialog = async () => {
|
||||||
|
if (busy) return;
|
||||||
|
await closeUpdate();
|
||||||
|
setUpdateStatus('idle');
|
||||||
|
setVersion('');
|
||||||
|
resetProgress();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 2 }}>
|
||||||
|
<span style={{ fontSize: 12, color: 'var(--ol-ink-3)' }}>{tagline} · {APP_VERSION_LABEL}</span>
|
||||||
|
<Btn variant="ghost" size="sm" onClick={checkForUpdates} disabled={checking || busy}>
|
||||||
|
{checking ? t('settings.about.checkingUpdate') : t('settings.about.checkUpdateBtn')}
|
||||||
|
</Btn>
|
||||||
|
</div>
|
||||||
|
{(updateStatus === 'none' || updateStatus === 'error') && (
|
||||||
|
<div style={{ fontSize: 11, color: updateStatus === 'error' ? 'var(--ol-err)' : 'var(--ol-ink-4)', marginTop: 4 }}>
|
||||||
|
{updateStatus === 'none' ? t('settings.about.upToDate') : t('settings.about.updateError')}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{(updateStatus === 'available' || updateStatus === 'downloading' || updateStatus === 'installing' || updateStatus === 'downloaded') && (
|
||||||
|
<UpdateDialog
|
||||||
|
status={updateStatus}
|
||||||
|
version={version}
|
||||||
|
progress={progress}
|
||||||
|
downloaded={downloaded}
|
||||||
|
contentLength={contentLength}
|
||||||
|
onInstall={installUpdate}
|
||||||
|
onRestart={restartApp}
|
||||||
|
onClose={dismissUpdateDialog}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function UpdateDialog({
|
||||||
|
status,
|
||||||
|
version,
|
||||||
|
progress,
|
||||||
|
downloaded,
|
||||||
|
contentLength,
|
||||||
|
onInstall,
|
||||||
|
onRestart,
|
||||||
|
onClose,
|
||||||
|
}: {
|
||||||
|
status: 'available' | 'downloading' | 'installing' | 'downloaded';
|
||||||
|
version: string;
|
||||||
|
progress: number | null;
|
||||||
|
downloaded: number;
|
||||||
|
contentLength: number | null;
|
||||||
|
onInstall: () => void;
|
||||||
|
onRestart: () => void;
|
||||||
|
onClose: () => void;
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const downloading = status === 'downloading';
|
||||||
|
const installing = status === 'installing';
|
||||||
|
return (
|
||||||
|
<div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.18)', display: 'grid', placeItems: 'center', zIndex: 40 }}>
|
||||||
|
<div style={{ width: 360, borderRadius: 16, background: 'var(--ol-surface)', border: '0.5px solid var(--ol-line-strong)', boxShadow: '0 18px 42px rgba(0,0,0,0.18)', padding: 18 }}>
|
||||||
|
<div style={{ fontSize: 15, fontWeight: 650, marginBottom: 8 }}>{t(`settings.about.updateDialog.${status}.title`)}</div>
|
||||||
|
<div style={{ fontSize: 12, color: 'var(--ol-ink-3)', lineHeight: 1.6, marginBottom: 14 }}>
|
||||||
|
{t(`settings.about.updateDialog.${status}.desc`, { version })}
|
||||||
|
</div>
|
||||||
|
{(downloading || installing || status === 'downloaded') && (
|
||||||
|
<div style={{ marginBottom: 14 }}>
|
||||||
|
<div style={{ height: 8, borderRadius: 999, background: 'var(--ol-surface-2)', overflow: 'hidden', border: '0.5px solid var(--ol-line)' }}>
|
||||||
|
<div style={{ height: '100%', width: `${status === 'downloaded' || installing ? 100 : progress ?? 8}%`, background: 'var(--ol-blue)', transition: 'width 0.18s ease-out' }} />
|
||||||
|
</div>
|
||||||
|
<div style={{ marginTop: 6, fontSize: 11, color: 'var(--ol-ink-4)' }}>
|
||||||
|
{installing
|
||||||
|
? t('settings.about.updateDialog.installingLabel')
|
||||||
|
: progress === null
|
||||||
|
? t('settings.about.updateDialog.progressUnknown', { downloaded: formatBytes(downloaded) })
|
||||||
|
: t('settings.about.updateDialog.progress', { progress, downloaded: formatBytes(downloaded), total: formatBytes(contentLength ?? 0) })}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8 }}>
|
||||||
|
{status === 'available' && <Btn size="sm" onClick={onClose}>{t('common.cancel')}</Btn>}
|
||||||
|
{status === 'available' && <Btn variant="blue" size="sm" onClick={onInstall}>{t('settings.about.updateDialog.install')}</Btn>}
|
||||||
|
{(downloading || installing) && <Btn size="sm" disabled>{installing ? t('settings.about.updateDialog.installingLabel') : t('settings.about.updateDialog.downloadingLabel')}</Btn>}
|
||||||
|
{status === 'downloaded' && <Btn size="sm" onClick={onClose}>{t('settings.about.updateDialog.later')}</Btn>}
|
||||||
|
{status === 'downloaded' && <Btn variant="blue" size="sm" onClick={onRestart}>{t('settings.about.updateDialog.restartNow')}</Btn>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatBytes(value: number) {
|
||||||
|
if (!Number.isFinite(value) || value <= 0) return '0 B';
|
||||||
|
if (value < 1024) return `${value} B`;
|
||||||
|
if (value < 1024 * 1024) return `${(value / 1024).toFixed(1)} KB`;
|
||||||
|
return `${(value / 1024 / 1024).toFixed(1)} MB`;
|
||||||
|
}
|
||||||
|
|
||||||
function HotkeyStatusPill({ status }: { status: HotkeyStatus | null }) {
|
function HotkeyStatusPill({ status }: { status: HotkeyStatus | null }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
if (!status) {
|
if (!status) {
|
||||||
|
|||||||
@@ -103,9 +103,10 @@ interface BtnProps {
|
|||||||
icon?: string;
|
icon?: string;
|
||||||
style?: CSSProperties;
|
style?: CSSProperties;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Btn({ children, variant = 'ghost', size = 'md', icon, style, onClick }: BtnProps) {
|
export function Btn({ children, variant = 'ghost', size = 'md', icon, style, onClick, disabled = false }: BtnProps) {
|
||||||
const variants: Record<BtnVariant, { bg: string; color: string; bd: string; sh: string }> = {
|
const variants: Record<BtnVariant, { bg: string; color: string; bd: string; sh: string }> = {
|
||||||
primary: { bg: 'var(--ol-ink)', color: '#fff', bd: 'transparent', sh: '0 1px 2px rgba(0,0,0,.08)' },
|
primary: { bg: 'var(--ol-ink)', color: '#fff', bd: 'transparent', sh: '0 1px 2px rgba(0,0,0,.08)' },
|
||||||
blue: { bg: 'var(--ol-blue)', color: '#fff', bd: 'transparent', sh: '0 1px 2px rgba(37,99,235,.18)' },
|
blue: { bg: 'var(--ol-blue)', color: '#fff', bd: 'transparent', sh: '0 1px 2px rgba(37,99,235,.18)' },
|
||||||
@@ -119,7 +120,8 @@ export function Btn({ children, variant = 'ghost', size = 'md', icon, style, onC
|
|||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={onClick}
|
onClick={disabled ? undefined : onClick}
|
||||||
|
disabled={disabled}
|
||||||
style={{
|
style={{
|
||||||
display: 'inline-flex', alignItems: 'center', gap: 6,
|
display: 'inline-flex', alignItems: 'center', gap: 6,
|
||||||
background: v.bg, color: v.color,
|
background: v.bg, color: v.color,
|
||||||
@@ -127,7 +129,8 @@ export function Btn({ children, variant = 'ghost', size = 'md', icon, style, onC
|
|||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
boxShadow: v.sh,
|
boxShadow: v.sh,
|
||||||
fontFamily: 'inherit', fontWeight: 500,
|
fontFamily: 'inherit', fontWeight: 500,
|
||||||
cursor: 'default',
|
cursor: disabled ? 'not-allowed' : 'default',
|
||||||
|
opacity: disabled ? 0.55 : 1,
|
||||||
transition: 'background 0.12s ease-out, color 0.12s ease-out, border-color 0.12s ease-out, box-shadow 0.12s ease-out, transform 0.08s ease-out',
|
transition: 'background 0.12s ease-out, color 0.12s ease-out, border-color 0.12s ease-out, box-shadow 0.12s ease-out, transform 0.08s ease-out',
|
||||||
...sizes[size],
|
...sizes[size],
|
||||||
...style,
|
...style,
|
||||||
|
|||||||
Reference in new issue
Block a user