diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index dae4f2e..e178cff 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,3 +1,5 @@ +## PowerShell + - Prefer `[void]` over `| OutNull` to suppress GUI output in the host console. - Always wrap error-prone code in try/catch. - Do not use if/else when it is possible to use if/return instead. diff --git a/.github/workflows/tauri.yml b/.github/workflows/tauri.yml new file mode 100644 index 0000000..82d9fdd --- /dev/null +++ b/.github/workflows/tauri.yml @@ -0,0 +1,68 @@ +name: Build Tauri App + +on: + push: + branches: [ main, 'v3/**' ] + paths: [ 'v3/**' ] + pull_request: + branches: [ main ] + paths: [ 'v3/**' ] + workflow_dispatch: + +# Cancel in-progress runs when a new workflow with the same group is triggered +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-tauri: + runs-on: windows-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Rust cache + uses: swatinem/rust-cache@v2 + with: + workspaces: './v3/src-tauri -> target' + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Install frontend dependencies + working-directory: ./v3 + run: bun install + + - name: Cache frontend dependencies + uses: actions/cache@v4 + with: + path: | + ./v3/node_modules + ~/.bun/install/cache + key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }} + restore-keys: | + ${{ runner.os }}-bun- + + - name: Build frontend + working-directory: ./v3 + run: bun run build + + - name: Build Tauri app + uses: tauri-apps/tauri-action@v0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + projectPath: ./v3 + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: brtx-installer-v3 + path: | + ./v3/src-tauri/target/release/bundle/ + if-no-files-found: warn diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..24d7cc6 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 37d7e32..3872551 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,6 @@ { - "github.copilot.chat.codeGeneration.useInstructionFiles": true + "github.copilot.chat.codeGeneration.useInstructionFiles": true, + "css.lint.unknownAtRules": "ignore", + "less.lint.unknownAtRules": "ignore", + "scss.lint.unknownAtRules": "ignore" } \ No newline at end of file diff --git a/.windsurf/rules/css.md b/.windsurf/rules/css.md new file mode 100644 index 0000000..f341739 --- /dev/null +++ b/.windsurf/rules/css.md @@ -0,0 +1,10 @@ +--- +trigger: glob +globs: **/*.css,**/*.tsx +--- + +# Project Code Style - CSS / Tailwind + +- Prefer grouping Tailwind styles into the styles.css file, using BEM and `@apply`. +- Always use theme variables instead of adding new colors, sizes, etc. +- Always use hover: and focus: \ No newline at end of file diff --git a/.windsurf/rules/tauri.md b/.windsurf/rules/tauri.md new file mode 100644 index 0000000..aa65a66 --- /dev/null +++ b/.windsurf/rules/tauri.md @@ -0,0 +1,12 @@ +--- +trigger: always_on +--- + +# Project Rules — BetterRTX Installer (Tauri v2) + +- Use **Tauri v2** APIs and docs; prefer NSIS installer on Windows. +- Frontend: React + TS; strict mode; Tailwind CSS v4; keep UI edits isolated. Reference Tailwind @docs +- Rust core: small commands with `#[tauri::command]`; pure helpers. +- use context7 to referecne Tauri docs +- Test with `bun run tauri dev` from the `.v3/` directory. Do not use NPM. +- Prefer Tauri plugins over implementing own features. \ No newline at end of file diff --git a/.windsurf/rules/typescript.md b/.windsurf/rules/typescript.md new file mode 100644 index 0000000..8569c4d --- /dev/null +++ b/.windsurf/rules/typescript.md @@ -0,0 +1,37 @@ +--- +trigger: glob +globs: **/*.ts,**.*.tsx +--- + +# General Code Style & Formatting +- Use English for all code and documentation. +- Always declare the type of each variable and function (parameters and return value). +- Avoid using any. +- Create necessary types. +- Use JSDoc to document public classes and methods. +- Don't leave blank lines within a function. +- One export per file. + +# Naming Conventions +- Use PascalCase for classes. +- Use camelCase for variables, functions, and methods. +- Use kebab-case for file and directory names. +- Use UPPERCASE for environment variables. +- Avoid magic numbers and define constants. + +# Functions & Logic +- Keep functions short and single-purpose (<20 lines). +- Avoid deeply nested blocks by: +- Using early returns. +- Extracting logic into utility functions. +- Use higher-order functions (map, filter, reduce) to simplify logic. +- Use arrow functions for simple cases (<3 instructions), named functions otherwise. +- Use default parameter values instead of null/undefined checks. +- Use RO-RO (Receive Object, Return Object) for passing and returning multiple parameters. + +# Data Handling +- Avoid excessive use of primitive types; encapsulate data in composite types. +- Avoid placing validation inside functions—use classes with internal validation instead. +- Prefer immutability for data: +- Use readonly for immutable properties. +- Use as const for literals that never change. \ No newline at end of file diff --git a/v2/installer.ps1 b/v2/installer.ps1 index 0b42290..97f1583 100644 --- a/v2/installer.ps1 +++ b/v2/installer.ps1 @@ -36,6 +36,8 @@ $T = Data { dlss_downloading = Downloading DLSS dlss_updating = Updating DLSS dlss_success = Successfully updated DLSS + update_options = Fix GFX Options + options_updated = Successfully updated GFX options '@ } $translationFilename = "installer.psd1" @@ -882,11 +884,68 @@ function Install-DLSS() { $StatusLabel.Text = $T.dlss_success } +# Modify the options.txt file to enable certain graphics options for best RTX performance +# This is a workaround for the fact that the game doesn't allow you to enable these options in the UI +function Update-OptionsFile { + param( + [Parameter(Mandatory = $true)] + [string]$OptionsFile + ) + + try { + if (Test-Path $OptionsFile) { + $content = Get-Content $OptionsFile -Raw + + if ($content -match "show_advanced_video_settings:0") { + $content = $content -replace "show_advanced_video_settings:0", "show_advanced_video_settings:1" + $content | Out-File $OptionsFile -Force -Encoding UTF8 + } + elseif ($content -notmatch "show_advanced_video_settings:1") { + # Add the setting if not found + $content += "`nshow_advanced_video_settings:1" + $content | Out-File $OptionsFile -Force -Encoding UTF8 + } + + Write-Host "Updated video settings in $OptionsFile" -ForegroundColor Green + } + else { + Write-Host "Options file not found: $OptionsFile" -ForegroundColor Red + } + } + catch { + Write-Error "Failed to update options file ${OptionsFile}: $_" + } +} + +function Enable-GfxOptions() { + $comMojang = [System.Environment]::GetFolderPath("LocalApplicationData") + "\Packages\Microsoft.MinecraftUWP_8wekyb3d8bbwe\LocalState\games\com.mojang" + $previewComMojang = [System.Environment]::GetFolderPath("LocalApplicationData") + "\Packages\Microsoft.MinecraftPreview_8wekyb3d8bbwe\LocalState\games\com.mojang" + + $optionsFile = "$comMojang\minecraftpe\options.txt"; + $previewOptionsFile = "$previewComMojang\minecraftpe\options.txt"; + + foreach ($mc in $dataSrc) { + if ($ListBox.SelectedItems -notcontains $mc.FriendlyName) { + continue + } + + if ($mc.Preview) { + Update-OptionsFile -OptionsFile $previewOptionsFile + } + else { + Update-OptionsFile -OptionsFile $optionsFile + } + } +} + # Advanced Section function Update-Advanced() { $hasSelectedItems = -not ($ListBox.SelectedItems.Count -eq 0) $dlssMenu = $advancedMenu.MenuItems | Where-Object { $_.Text -eq $T.update_dlss } $dlssMenu.Enabled = $hasSelectedItems + + $updateOptionsMenuItem = $advancedMenu.MenuItems | Where-Object { $_.Text -eq $T.update_options } + $updateOptionsMenuItem.Enabled = $hasSelectedItems } # Setup GUI @@ -1165,6 +1224,17 @@ $dlssUpdateMenuItem.Enabled = $false $dlssUpdateMenuItem.Add_Click({ Install-DLSS }) $advancedMenu.MenuItems.Add($dlssUpdateMenuItem) | Out-Null +$updateOptionsMenuItem = New-Object System.Windows.Forms.MenuItem +$updateOptionsMenuItem.Text = $T.update_options +$updateOptionsMenuItem.Enabled = $false +$updateOptionsMenuItem.Add_Click({ + Enable-GfxOptions + $StatusLabel.Text = $T.options_updated + $StatusLabel.ForeColor = 'Green' + $StatusLabel.Visible = $true + }) +$advancedMenu.MenuItems.Add($updateOptionsMenuItem) | Out-Null + $helpMenu = New-Object System.Windows.Forms.MenuItem $helpMenu.Text = $T.help $mainMenu.MenuItems.Add($helpMenu) | Out-Null diff --git a/v3/.gitignore b/v3/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/v3/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/v3/README.md b/v3/README.md new file mode 100644 index 0000000..b381dcf --- /dev/null +++ b/v3/README.md @@ -0,0 +1,7 @@ +# Tauri + Vanilla TS + +This template should help get you started developing with Tauri in vanilla HTML, CSS and Typescript. + +## Recommended IDE Setup + +- [VS Code](https://code.visualstudio.com/) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) diff --git a/v3/app-icon.png b/v3/app-icon.png new file mode 100644 index 0000000..01c1bfa Binary files /dev/null and b/v3/app-icon.png differ diff --git a/v3/bun.lock b/v3/bun.lock new file mode 100644 index 0000000..03c1b8f --- /dev/null +++ b/v3/bun.lock @@ -0,0 +1,508 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "v3", + "dependencies": { + "@fontsource-variable/inter": "^5.2.6", + "@fontsource-variable/martian-mono": "^5.2.6", + "@tauri-apps/api": "^2", + "@tauri-apps/plugin-dialog": "^2.3.2", + "@tauri-apps/plugin-fs": "^2.4.1", + "@tauri-apps/plugin-opener": "~2", + "@tauri-apps/plugin-shell": "^2.3.0", + "@tauri-apps/plugin-upload": "^2.3.1", + "classix": "^2.2.0", + "i18next": "^25.3.6", + "i18next-browser-languagedetector": "^8.2.0", + "i18next-http-backend": "^3.0.2", + "lucide-react": "^0.539.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-i18next": "^15.6.1", + "tailwindcss": "^4.1.12", + "zustand": "^5.0.7", + }, + "devDependencies": { + "@tailwindcss/vite": "^4.1.12", + "@tauri-apps/cli": "^2", + "@types/node": "^22.0.0", + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "@vitejs/plugin-react": "^4.3.1", + "typescript": "~5.6.2", + "vite": "^6.0.3", + "vite-plugin-svgr": "^4.2.0", + }, + }, + }, + "packages": { + "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], + + "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], + + "@babel/compat-data": ["@babel/compat-data@7.28.0", "", {}, "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw=="], + + "@babel/core": ["@babel/core@7.28.3", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.3", "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.3", "@babel/types": "^7.28.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ=="], + + "@babel/generator": ["@babel/generator@7.28.3", "", { "dependencies": { "@babel/parser": "^7.28.3", "@babel/types": "^7.28.2", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw=="], + + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="], + + "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], + + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="], + + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="], + + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="], + + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], + + "@babel/helpers": ["@babel/helpers@7.28.3", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.2" } }, "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw=="], + + "@babel/parser": ["@babel/parser@7.28.3", "", { "dependencies": { "@babel/types": "^7.28.2" }, "bin": "./bin/babel-parser.js" }, "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA=="], + + "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="], + + "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="], + + "@babel/runtime": ["@babel/runtime@7.28.3", "", {}, "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA=="], + + "@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], + + "@babel/traverse": ["@babel/traverse@7.28.3", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", "@babel/types": "^7.28.2", "debug": "^4.3.1" } }, "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ=="], + + "@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.9", "", { "os": "aix", "cpu": "ppc64" }, "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.9", "", { "os": "android", "cpu": "arm" }, "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.9", "", { "os": "android", "cpu": "arm64" }, "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.9", "", { "os": "android", "cpu": "x64" }, "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.9", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.9", "", { "os": "freebsd", "cpu": "x64" }, "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.9", "", { "os": "linux", "cpu": "arm" }, "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.9", "", { "os": "linux", "cpu": "ia32" }, "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.9", "", { "os": "linux", "cpu": "ppc64" }, "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.9", "", { "os": "linux", "cpu": "s390x" }, "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.9", "", { "os": "linux", "cpu": "x64" }, "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.9", "", { "os": "none", "cpu": "arm64" }, "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.9", "", { "os": "none", "cpu": "x64" }, "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.9", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.9", "", { "os": "openbsd", "cpu": "x64" }, "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.9", "", { "os": "none", "cpu": "arm64" }, "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.9", "", { "os": "sunos", "cpu": "x64" }, "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.9", "", { "os": "win32", "cpu": "ia32" }, "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.9", "", { "os": "win32", "cpu": "x64" }, "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ=="], + + "@fontsource-variable/inter": ["@fontsource-variable/inter@5.2.6", "", {}, "sha512-jks/bficUPQ9nn7GvXvHtlQIPudW7Wx8CrlZoY8bhxgeobNxlQan8DclUJuYF2loYRrGpfrhCIZZspXYysiVGg=="], + + "@fontsource-variable/martian-mono": ["@fontsource-variable/martian-mono@5.2.6", "", {}, "sha512-CmjMLTVJVivuZSNhKJf3nLe67eC9W2x0IkIhrkT0h/AKUP8NETCUnbdhkZN/NYUZryDLiLr8FaYf4dALdPDRMA=="], + + "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.30", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="], + + "@rollup/pluginutils": ["@rollup/pluginutils@5.2.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.46.2", "", { "os": "android", "cpu": "arm" }, "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.46.2", "", { "os": "android", "cpu": "arm64" }, "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.46.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.46.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.46.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.46.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.46.2", "", { "os": "linux", "cpu": "arm" }, "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.46.2", "", { "os": "linux", "cpu": "arm" }, "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.46.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.46.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg=="], + + "@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.46.2", "", { "os": "linux", "cpu": "none" }, "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA=="], + + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.46.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.46.2", "", { "os": "linux", "cpu": "none" }, "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.46.2", "", { "os": "linux", "cpu": "none" }, "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.46.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.46.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.46.2", "", { "os": "linux", "cpu": "x64" }, "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.46.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.46.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.46.2", "", { "os": "win32", "cpu": "x64" }, "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg=="], + + "@svgr/babel-plugin-add-jsx-attribute": ["@svgr/babel-plugin-add-jsx-attribute@8.0.0", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g=="], + + "@svgr/babel-plugin-remove-jsx-attribute": ["@svgr/babel-plugin-remove-jsx-attribute@8.0.0", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA=="], + + "@svgr/babel-plugin-remove-jsx-empty-expression": ["@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA=="], + + "@svgr/babel-plugin-replace-jsx-attribute-value": ["@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ=="], + + "@svgr/babel-plugin-svg-dynamic-title": ["@svgr/babel-plugin-svg-dynamic-title@8.0.0", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og=="], + + "@svgr/babel-plugin-svg-em-dimensions": ["@svgr/babel-plugin-svg-em-dimensions@8.0.0", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g=="], + + "@svgr/babel-plugin-transform-react-native-svg": ["@svgr/babel-plugin-transform-react-native-svg@8.1.0", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q=="], + + "@svgr/babel-plugin-transform-svg-component": ["@svgr/babel-plugin-transform-svg-component@8.0.0", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw=="], + + "@svgr/babel-preset": ["@svgr/babel-preset@8.1.0", "", { "dependencies": { "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", "@svgr/babel-plugin-transform-svg-component": "8.0.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug=="], + + "@svgr/core": ["@svgr/core@8.1.0", "", { "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", "camelcase": "^6.2.0", "cosmiconfig": "^8.1.3", "snake-case": "^3.0.4" } }, "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA=="], + + "@svgr/hast-util-to-babel-ast": ["@svgr/hast-util-to-babel-ast@8.0.0", "", { "dependencies": { "@babel/types": "^7.21.3", "entities": "^4.4.0" } }, "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q=="], + + "@svgr/plugin-jsx": ["@svgr/plugin-jsx@8.1.0", "", { "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", "@svgr/hast-util-to-babel-ast": "8.0.0", "svg-parser": "^2.0.4" }, "peerDependencies": { "@svgr/core": "*" } }, "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA=="], + + "@tailwindcss/node": ["@tailwindcss/node@4.1.12", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.5.1", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", "tailwindcss": "4.1.12" } }, "sha512-3hm9brwvQkZFe++SBt+oLjo4OLDtkvlE8q2WalaD/7QWaeM7KEJbAiY/LJZUaCs7Xa8aUu4xy3uoyX4q54UVdQ=="], + + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.12", "", { "dependencies": { "detect-libc": "^2.0.4", "tar": "^7.4.3" }, "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.12", "@tailwindcss/oxide-darwin-arm64": "4.1.12", "@tailwindcss/oxide-darwin-x64": "4.1.12", "@tailwindcss/oxide-freebsd-x64": "4.1.12", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.12", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.12", "@tailwindcss/oxide-linux-arm64-musl": "4.1.12", "@tailwindcss/oxide-linux-x64-gnu": "4.1.12", "@tailwindcss/oxide-linux-x64-musl": "4.1.12", "@tailwindcss/oxide-wasm32-wasi": "4.1.12", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.12", "@tailwindcss/oxide-win32-x64-msvc": "4.1.12" } }, "sha512-gM5EoKHW/ukmlEtphNwaGx45fGoEmP10v51t9unv55voWh6WrOL19hfuIdo2FjxIaZzw776/BUQg7Pck++cIVw=="], + + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.12", "", { "os": "android", "cpu": "arm64" }, "sha512-oNY5pq+1gc4T6QVTsZKwZaGpBb2N1H1fsc1GD4o7yinFySqIuRZ2E4NvGasWc6PhYJwGK2+5YT1f9Tp80zUQZQ=="], + + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-cq1qmq2HEtDV9HvZlTtrj671mCdGB93bVY6J29mwCyaMYCP/JaUBXxrQQQm7Qn33AXXASPUb2HFZlWiiHWFytw=="], + + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-6UCsIeFUcBfpangqlXay9Ffty9XhFH1QuUFn0WV83W8lGdX8cD5/+2ONLluALJD5+yJ7k8mVtwy3zMZmzEfbLg=="], + + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JOH/f7j6+nYXIrHobRYCtoArJdMJh5zy5lr0FV0Qu47MID/vqJAY3r/OElPzx1C/wdT1uS7cPq+xdYYelny1ww=="], + + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.12", "", { "os": "linux", "cpu": "arm" }, "sha512-v4Ghvi9AU1SYgGr3/j38PD8PEe6bRfTnNSUE3YCMIRrrNigCFtHZ2TCm8142X8fcSqHBZBceDx+JlFJEfNg5zQ=="], + + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-YP5s1LmetL9UsvVAKusHSyPlzSRqYyRB0f+Kl/xcYQSPLEw/BvGfxzbH+ihUciePDjiXwHh+p+qbSP3SlJw+6g=="], + + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-V8pAM3s8gsrXcCv6kCHSuwyb/gPsd863iT+v1PGXC4fSL/OJqsKhfK//v8P+w9ThKIoqNbEnsZqNy+WDnwQqCA=="], + + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.12", "", { "os": "linux", "cpu": "x64" }, "sha512-xYfqYLjvm2UQ3TZggTGrwxjYaLB62b1Wiysw/YE3Yqbh86sOMoTn0feF98PonP7LtjsWOWcXEbGqDL7zv0uW8Q=="], + + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.12", "", { "os": "linux", "cpu": "x64" }, "sha512-ha0pHPamN+fWZY7GCzz5rKunlv9L5R8kdh+YNvP5awe3LtuXb5nRi/H27GeL2U+TdhDOptU7T6Is7mdwh5Ar3A=="], + + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.12", "", { "dependencies": { "@emnapi/core": "^1.4.5", "@emnapi/runtime": "^1.4.5", "@emnapi/wasi-threads": "^1.0.4", "@napi-rs/wasm-runtime": "^0.2.12", "@tybys/wasm-util": "^0.10.0", "tslib": "^2.8.0" }, "cpu": "none" }, "sha512-4tSyu3dW+ktzdEpuk6g49KdEangu3eCYoqPhWNsZgUhyegEda3M9rG0/j1GV/JjVVsj+lG7jWAyrTlLzd/WEBg=="], + + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-iGLyD/cVP724+FGtMWslhcFyg4xyYyM+5F4hGvKA7eifPkXHRAUDFaimu53fpNg9X8dfP75pXx/zFt/jlNF+lg=="], + + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.12", "", { "os": "win32", "cpu": "x64" }, "sha512-NKIh5rzw6CpEodv/++r0hGLlfgT/gFN+5WNdZtvh6wpU2BpGNgdjvj6H2oFc8nCM839QM1YOhjpgbAONUb4IxA=="], + + "@tailwindcss/vite": ["@tailwindcss/vite@4.1.12", "", { "dependencies": { "@tailwindcss/node": "4.1.12", "@tailwindcss/oxide": "4.1.12", "tailwindcss": "4.1.12" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-4pt0AMFDx7gzIrAOIYgYP0KCBuKWqyW8ayrdiLEjoJTT4pKTjrzG/e4uzWtTLDziC+66R9wbUqZBccJalSE5vQ=="], + + "@tauri-apps/api": ["@tauri-apps/api@2.7.0", "", {}, "sha512-v7fVE8jqBl8xJFOcBafDzXFc8FnicoH3j8o8DNNs0tHuEBmXUDqrCOAzMRX0UkfpwqZLqvrvK0GNQ45DfnoVDg=="], + + "@tauri-apps/cli": ["@tauri-apps/cli@2.7.1", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.7.1", "@tauri-apps/cli-darwin-x64": "2.7.1", "@tauri-apps/cli-linux-arm-gnueabihf": "2.7.1", "@tauri-apps/cli-linux-arm64-gnu": "2.7.1", "@tauri-apps/cli-linux-arm64-musl": "2.7.1", "@tauri-apps/cli-linux-riscv64-gnu": "2.7.1", "@tauri-apps/cli-linux-x64-gnu": "2.7.1", "@tauri-apps/cli-linux-x64-musl": "2.7.1", "@tauri-apps/cli-win32-arm64-msvc": "2.7.1", "@tauri-apps/cli-win32-ia32-msvc": "2.7.1", "@tauri-apps/cli-win32-x64-msvc": "2.7.1" }, "bin": { "tauri": "tauri.js" } }, "sha512-RcGWR4jOUEl92w3uvI0h61Llkfj9lwGD1iwvDRD2isMrDhOzjeeeVn9aGzeW1jubQ/kAbMYfydcA4BA0Cy733Q=="], + + "@tauri-apps/cli-darwin-arm64": ["@tauri-apps/cli-darwin-arm64@2.7.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-j2NXQN6+08G03xYiyKDKqbCV2Txt+hUKg0a8hYr92AmoCU8fgCjHyva/p16lGFGUG3P2Yu0xiNe1hXL9ZuRMzA=="], + + "@tauri-apps/cli-darwin-x64": ["@tauri-apps/cli-darwin-x64@2.7.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-CdYAefeM35zKsc91qIyKzbaO7FhzTyWKsE8hj7tEJ1INYpoh1NeNNyL/NSEA3Nebi5ilugioJ5tRK8ZXG8y3gw=="], + + "@tauri-apps/cli-linux-arm-gnueabihf": ["@tauri-apps/cli-linux-arm-gnueabihf@2.7.1", "", { "os": "linux", "cpu": "arm" }, "sha512-dnvyJrTA1UJxJjQ8q1N/gWomjP8Twij1BUQu2fdcT3OPpqlrbOk5R1yT0oD/721xoKNjroB5BXCsmmlykllxNg=="], + + "@tauri-apps/cli-linux-arm64-gnu": ["@tauri-apps/cli-linux-arm64-gnu@2.7.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-FtBW6LJPNRTws3qyUc294AqCWU91l/H0SsFKq6q4Q45MSS4x6wxLxou8zB53tLDGEPx3JSoPLcDaSfPlSbyujQ=="], + + "@tauri-apps/cli-linux-arm64-musl": ["@tauri-apps/cli-linux-arm64-musl@2.7.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-/HXY0t4FHkpFzjeYS5c16mlA6z0kzn5uKLWptTLTdFSnYpr8FCnOP4Sdkvm2TDQPF2ERxXtNCd+WR/jQugbGnA=="], + + "@tauri-apps/cli-linux-riscv64-gnu": ["@tauri-apps/cli-linux-riscv64-gnu@2.7.1", "", { "os": "linux", "cpu": "none" }, "sha512-GeW5lVI2GhhnaYckiDzstG2j2Jwlud5d2XefRGwlOK+C/bVGLT1le8MNPYK8wgRlpeK8fG1WnJJYD6Ke7YQ8bg=="], + + "@tauri-apps/cli-linux-x64-gnu": ["@tauri-apps/cli-linux-x64-gnu@2.7.1", "", { "os": "linux", "cpu": "x64" }, "sha512-DprxKQkPxIPYwUgg+cscpv2lcIUhn2nxEPlk0UeaiV9vATxCXyytxr1gLcj3xgjGyNPlM0MlJyYaPy1JmRg1cA=="], + + "@tauri-apps/cli-linux-x64-musl": ["@tauri-apps/cli-linux-x64-musl@2.7.1", "", { "os": "linux", "cpu": "x64" }, "sha512-KLlq3kOK7OUyDR757c0zQjPULpGZpLhNB0lZmZpHXvoOUcqZoCXJHh4dT/mryWZJp5ilrem5l8o9ngrDo0X1AA=="], + + "@tauri-apps/cli-win32-arm64-msvc": ["@tauri-apps/cli-win32-arm64-msvc@2.7.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-dH7KUjKkSypCeWPiainHyXoES3obS+JIZVoSwSZfKq2gWgs48FY3oT0hQNYrWveE+VR4VoR3b/F3CPGbgFvksA=="], + + "@tauri-apps/cli-win32-ia32-msvc": ["@tauri-apps/cli-win32-ia32-msvc@2.7.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-1oeibfyWQPVcijOrTg709qhbXArjX3x1MPjrmA5anlygwrbByxLBcLXvotcOeULFcnH2FYUMMLLant8kgvwE5A=="], + + "@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.7.1", "", { "os": "win32", "cpu": "x64" }, "sha512-D7Q9kDObutuirCNLxYQ7KAg2Xxg99AjcdYz/KuMw5HvyEPbkC9Q7JL0vOrQOrHEHxIQ2lYzFOZvKKoC2yyqXcg=="], + + "@tauri-apps/plugin-dialog": ["@tauri-apps/plugin-dialog@2.3.2", "", { "dependencies": { "@tauri-apps/api": "^2.6.0" } }, "sha512-cNLo9YeQSC0MF4IgXnotHsqEgJk72MBZLXmQPrLA95qTaaWiiaFQ38hIMdZ6YbGUNkr3oni3EhU+AD5jLHcdUA=="], + + "@tauri-apps/plugin-fs": ["@tauri-apps/plugin-fs@2.4.1", "", { "dependencies": { "@tauri-apps/api": "^2.6.0" } }, "sha512-vJlKZVGF3UAFGoIEVT6Oq5L4HGDCD78WmA4uhzitToqYiBKWAvZR61M6zAyQzHqLs0ADemkE4RSy/5sCmZm6ZQ=="], + + "@tauri-apps/plugin-opener": ["@tauri-apps/plugin-opener@2.4.0", "", { "dependencies": { "@tauri-apps/api": "^2.6.0" } }, "sha512-43VyN8JJtvKWJY72WI/KNZszTpDpzHULFxQs0CJBIYUdCRowQ6Q1feWTDb979N7nldqSuDOaBupZ6wz2nvuWwQ=="], + + "@tauri-apps/plugin-shell": ["@tauri-apps/plugin-shell@2.3.0", "", { "dependencies": { "@tauri-apps/api": "^2.6.0" } }, "sha512-6GIRxO2z64uxPX4CCTuhQzefvCC0ew7HjdBhMALiGw74vFBDY95VWueAHOHgNOMV4UOUAFupyidN9YulTe5xlA=="], + + "@tauri-apps/plugin-upload": ["@tauri-apps/plugin-upload@2.3.1", "", { "dependencies": { "@tauri-apps/api": "^2.6.0" } }, "sha512-nqaMbmn78vdoFf/tiS8XtbEJ/S5MRUMzfFRZw+t49JKtXDPXUUsG3CYai9mGDpZQe5PW8zvfXHKto9eLj03/lg=="], + + "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], + + "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], + + "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="], + + "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], + + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@types/node": ["@types/node@22.17.2", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-gL6z5N9Jm9mhY+U2KXZpteb+09zyffliRkZyZOHODGATyC5B1Jt/7TzuuiLkFsSUMLbS1OLmlj/E+/3KF4Q/4w=="], + + "@types/prop-types": ["@types/prop-types@15.7.15", "", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="], + + "@types/react": ["@types/react@18.3.23", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" } }, "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w=="], + + "@types/react-dom": ["@types/react-dom@18.3.7", "", { "peerDependencies": { "@types/react": "^18.0.0" } }, "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ=="], + + "@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "browserslist": ["browserslist@4.25.2", "", { "dependencies": { "caniuse-lite": "^1.0.30001733", "electron-to-chromium": "^1.5.199", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA=="], + + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + + "camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001735", "", {}, "sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w=="], + + "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], + + "classix": ["classix@2.2.2", "", {}, "sha512-sHfNzza/LoiecpfDAjnVyan2SG4Q9fhcO0TrbnjK7nh5rvA9E9k/kFk4WGHdI24upUyzSBSxq+uy4oUPR9Mgig=="], + + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "cosmiconfig": ["cosmiconfig@8.3.6", "", { "dependencies": { "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0", "path-type": "^4.0.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA=="], + + "cross-fetch": ["cross-fetch@4.0.0", "", { "dependencies": { "node-fetch": "^2.6.12" } }, "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g=="], + + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + + "debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], + + "detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="], + + "dot-case": ["dot-case@3.0.4", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.203", "", {}, "sha512-uz4i0vLhfm6dLZWbz/iH88KNDV+ivj5+2SA+utpgjKaj9Q0iDLuwk6Idhe9BTxciHudyx6IvTvijhkPvFGUQ0g=="], + + "enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="], + + "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + + "error-ex": ["error-ex@1.3.2", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g=="], + + "esbuild": ["esbuild@0.25.9", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.9", "@esbuild/android-arm": "0.25.9", "@esbuild/android-arm64": "0.25.9", "@esbuild/android-x64": "0.25.9", "@esbuild/darwin-arm64": "0.25.9", "@esbuild/darwin-x64": "0.25.9", "@esbuild/freebsd-arm64": "0.25.9", "@esbuild/freebsd-x64": "0.25.9", "@esbuild/linux-arm": "0.25.9", "@esbuild/linux-arm64": "0.25.9", "@esbuild/linux-ia32": "0.25.9", "@esbuild/linux-loong64": "0.25.9", "@esbuild/linux-mips64el": "0.25.9", "@esbuild/linux-ppc64": "0.25.9", "@esbuild/linux-riscv64": "0.25.9", "@esbuild/linux-s390x": "0.25.9", "@esbuild/linux-x64": "0.25.9", "@esbuild/netbsd-arm64": "0.25.9", "@esbuild/netbsd-x64": "0.25.9", "@esbuild/openbsd-arm64": "0.25.9", "@esbuild/openbsd-x64": "0.25.9", "@esbuild/openharmony-arm64": "0.25.9", "@esbuild/sunos-x64": "0.25.9", "@esbuild/win32-arm64": "0.25.9", "@esbuild/win32-ia32": "0.25.9", "@esbuild/win32-x64": "0.25.9" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "html-parse-stringify": ["html-parse-stringify@3.0.1", "", { "dependencies": { "void-elements": "3.1.0" } }, "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg=="], + + "i18next": ["i18next@25.3.6", "", { "dependencies": { "@babel/runtime": "^7.27.6" }, "peerDependencies": { "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-dThZ0CTCM3sUG/qS0ZtQYZQcUI6DtBN8yBHK+SKEqihPcEYmjVWh/YJ4luic73Iq6Uxhp6q7LJJntRK5+1t7jQ=="], + + "i18next-browser-languagedetector": ["i18next-browser-languagedetector@8.2.0", "", { "dependencies": { "@babel/runtime": "^7.23.2" } }, "sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g=="], + + "i18next-http-backend": ["i18next-http-backend@3.0.2", "", { "dependencies": { "cross-fetch": "4.0.0" } }, "sha512-PdlvPnvIp4E1sYi46Ik4tBYh/v/NbYfFFgTjkwFl0is8A18s7/bx9aXqsrOax9WUbeNS6mD2oix7Z0yGGf6m5g=="], + + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + + "is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="], + + "jiti": ["jiti@2.5.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], + + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + + "json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="], + + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.1", "", { "os": "linux", "cpu": "arm" }, "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.1", "", { "os": "win32", "cpu": "x64" }, "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg=="], + + "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], + + "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], + + "lower-case": ["lower-case@2.0.2", "", { "dependencies": { "tslib": "^2.0.3" } }, "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg=="], + + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + + "lucide-react": ["lucide-react@0.539.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-VVISr+VF2krO91FeuCrm1rSOLACQUYVy7NQkzrOty52Y8TlTPcXcMdQFj9bYzBgXbWCiywlwSZ3Z8u6a+6bMlg=="], + + "magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="], + + "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + + "minizlib": ["minizlib@3.0.2", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA=="], + + "mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + + "no-case": ["no-case@3.0.4", "", { "dependencies": { "lower-case": "^2.0.2", "tslib": "^2.0.3" } }, "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg=="], + + "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], + + "node-releases": ["node-releases@2.0.19", "", {}, "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="], + + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + + "parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], + + "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + + "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + + "react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], + + "react-dom": ["react-dom@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw=="], + + "react-i18next": ["react-i18next@15.6.1", "", { "dependencies": { "@babel/runtime": "^7.27.6", "html-parse-stringify": "^3.0.1" }, "peerDependencies": { "i18next": ">= 23.2.3", "react": ">= 16.8.0", "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-uGrzSsOUUe2sDBG/+FJq2J1MM+Y4368/QW8OLEKSFvnDflHBbZhSd1u3UkW0Z06rMhZmnB/AQrhCpYfE5/5XNg=="], + + "react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="], + + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + + "rollup": ["rollup@4.46.2", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.46.2", "@rollup/rollup-android-arm64": "4.46.2", "@rollup/rollup-darwin-arm64": "4.46.2", "@rollup/rollup-darwin-x64": "4.46.2", "@rollup/rollup-freebsd-arm64": "4.46.2", "@rollup/rollup-freebsd-x64": "4.46.2", "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", "@rollup/rollup-linux-arm-musleabihf": "4.46.2", "@rollup/rollup-linux-arm64-gnu": "4.46.2", "@rollup/rollup-linux-arm64-musl": "4.46.2", "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", "@rollup/rollup-linux-ppc64-gnu": "4.46.2", "@rollup/rollup-linux-riscv64-gnu": "4.46.2", "@rollup/rollup-linux-riscv64-musl": "4.46.2", "@rollup/rollup-linux-s390x-gnu": "4.46.2", "@rollup/rollup-linux-x64-gnu": "4.46.2", "@rollup/rollup-linux-x64-musl": "4.46.2", "@rollup/rollup-win32-arm64-msvc": "4.46.2", "@rollup/rollup-win32-ia32-msvc": "4.46.2", "@rollup/rollup-win32-x64-msvc": "4.46.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg=="], + + "scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="], + + "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "snake-case": ["snake-case@3.0.4", "", { "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "svg-parser": ["svg-parser@2.0.4", "", {}, "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ=="], + + "tailwindcss": ["tailwindcss@4.1.12", "", {}, "sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA=="], + + "tapable": ["tapable@2.2.2", "", {}, "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg=="], + + "tar": ["tar@7.4.3", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.0.1", "mkdirp": "^3.0.1", "yallist": "^5.0.0" } }, "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw=="], + + "tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="], + + "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "typescript": ["typescript@5.6.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw=="], + + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "update-browserslist-db": ["update-browserslist-db@1.1.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw=="], + + "vite": ["vite@6.3.5", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ=="], + + "vite-plugin-svgr": ["vite-plugin-svgr@4.3.0", "", { "dependencies": { "@rollup/pluginutils": "^5.1.3", "@svgr/core": "^8.1.0", "@svgr/plugin-jsx": "^8.1.0" }, "peerDependencies": { "vite": ">=2.6.0" } }, "sha512-Jy9qLB2/PyWklpYy0xk0UU3TlU0t2UMpJXZvf+hWII1lAmRHrOUKi11Uw8N3rxoNk7atZNYO3pR3vI1f7oi+6w=="], + + "void-elements": ["void-elements@3.1.0", "", {}, "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w=="], + + "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], + + "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], + + "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], + + "zustand": ["zustand@5.0.7", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-Ot6uqHDW/O2VdYsKLLU8GQu8sCOM1LcoE8RwvLv9uuRT9s6SOHCKs0ZEOhxg+I1Ld+A1Q5lwx+UlKXXUoCZITg=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.5", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.4", "tslib": "^2.4.0" }, "bundled": true }, "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.4.5", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.0.4", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g=="], + + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" }, "bundled": true }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="], + + "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ=="], + + "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + } +} diff --git a/v3/index.html b/v3/index.html new file mode 100644 index 0000000..706de07 --- /dev/null +++ b/v3/index.html @@ -0,0 +1,14 @@ + + + + + + + BetterRTX Installer + + + +
+ + + diff --git a/v3/package.json b/v3/package.json new file mode 100644 index 0000000..b9cb17e --- /dev/null +++ b/v3/package.json @@ -0,0 +1,43 @@ +{ + "name": "brtx-installer", + "private": true, + "version": "3.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "tauri": "tauri" + }, + "dependencies": { + "@fontsource-variable/inter": "^5.2.6", + "@fontsource-variable/martian-mono": "^5.2.6", + "@tauri-apps/api": "^2", + "@tauri-apps/plugin-dialog": "^2.3.2", + "@tauri-apps/plugin-fs": "^2.4.1", + "@tauri-apps/plugin-opener": "~2", + "@tauri-apps/plugin-shell": "^2.3.0", + "@tauri-apps/plugin-upload": "^2.3.1", + "classix": "^2.2.0", + "i18next": "^25.3.6", + "i18next-browser-languagedetector": "^8.2.0", + "i18next-http-backend": "^3.0.2", + "lucide-react": "^0.539.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-i18next": "^15.6.1", + "tailwindcss": "^4.1.12", + "zustand": "^5.0.7" + }, + "devDependencies": { + "@tauri-apps/cli": "^2", + "@types/node": "^22.0.0", + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "@tailwindcss/vite": "^4.1.12", + "@vitejs/plugin-react": "^4.3.1", + "vite-plugin-svgr": "^4.2.0", + "vite": "^6.0.3", + "typescript": "~5.6.2" + } +} diff --git a/v3/public/locales/en/translation.json b/v3/public/locales/en/translation.json new file mode 100644 index 0000000..8db4f04 --- /dev/null +++ b/v3/public/locales/en/translation.json @@ -0,0 +1,165 @@ +{ + "welcome": "Welcome to the BetterRTX Installer", + "installations_title": "Minecraft Installations", + "status_select_installation_warning": "Please select at least one installation", + "status_installing_preset": "Installing preset...", + "log_installing_preset": "Installing preset {{uuid}} to {{count}} installation(s)", + "log_installed_to": "Installed to: {{installPath}}", + "status_install_success": "Preset installed successfully", + "log_install_complete": "Installation completed successfully", + "status_install_error": "Error installing preset: {{error}}", + "log_settings_clicked": "Settings clicked - Feature coming soon", + "log_help_clicked": "Help clicked - Feature coming soon", + "log_about_clicked": "About clicked - Feature coming soon", + "tab_installations": "Installations", + "tab_presets": "Presets", + "tab_actions": "Actions", + "tab_creator": "Creator", + "navigation": "Mod Options", + "toolbar_options_label": "Toolbar options", + "installations_found_count": "{{count}} found", + "installations_none_found": "No Minecraft installations found", + "add_installation": "Find Installation", + "presets_title": "Community Presets", + "presets_loaded_count": "{{count}} loaded", + "presets_none_available": "No presets available", + "actions_title": "Mod Capabilities", + "action_install_rtpack_title": "Install .rtpack File", + "action_install_rtpack_desc": "Install a preset from a local .rtpack file", + "action_install_materials_title": "Install Material Files", + "action_install_materials_desc": "Install individual material files", + "action_update_options_title": "Update Options", + "action_update_options_desc": "Re-enable RTX shader through Minecraft options.txt", + "action_backup_title": "Backup Selected", + "action_backup_desc": "Create backups of selected installations' current shader", + "action_install_dlss_title": "Install DLSS", + "action_install_dlss_desc": "Install NVIDIA DLSS for enhanced performance", + "rtpack_dialog_title": "Install RTpack File", + "rtpack_dialog_description": "Select which Minecraft instances to install this RTpack file to:", + "select_minecraft_instances": "Select Minecraft Instances", + "select_all": "Select All", + "deselect_all": "Deselect All", + "no_minecraft_installations": "No Minecraft installations found", + "preview": "Preview", + "cancel": "Cancel", + "install": "Install", + "installing": "Installing...", + "install_date": "Installed: {{date}}", + "installer_version": "BRTX Installer v{{version}}", + "status_installing_rtpack": "Installing RTpack file...", + "current_preset": "Current preset", + "no_preset_installed": "No preset installed", + "creator_name_modal_title": "Name Your Creator Preset", + "creator_name_label": "Preset Name", + "creator_name_placeholder": "Enter a name for this preset", + "creator_name_help": "This name will be displayed in your installations list", + "log_installing_creator_preset": "Installing creator preset: {{name}}", + "log_creator_install_complete": "Creator preset installation completed successfully", + "log_creator_install_error": "Creator preset installation failed: {{error}}", + "selected_count": "{{selected}} selected of {{total}}", + "install_to_selected": "Install to selected ({{count}})", + "install_preset_to_instances": "Install Preset", + "select_installations_description": "Select which Minecraft instances to install to:", + "update_to_selected": "Update options for selected ({{count}})", + "uninstall_to_selected": "Uninstall from selected ({{count}})", + "backup_to_selected": "Backup selected ({{count}})", + "dialog_install_dlss_title": "Install DLSS", + "dialog_install_dlss_desc": "Select which Minecraft instances to install DLSS to:", + "dialog_update_options_title": "Update Options", + "dialog_update_options_desc": "Select which Minecraft instances to update options for:", + "dialog_backup_title": "Backup Selected", + "dialog_backup_desc": "Select which Minecraft instances to back up:", + "dialog_uninstall_title": "Uninstall RTX", + "dialog_uninstall_desc": "Select which Minecraft instances to restore original materials to:", + "status_installing_dlss": "Installing DLSS...", + "status_install_dlss_success": "DLSS installed successfully", + "updating_options": "Updating options...", + "status_update_options_success": "Options updated successfully", + "backing_up": "Creating backups...", + "status_backup_success": "Backups completed successfully", + "action_uninstall_title": "Uninstall RTX", + "action_uninstall_desc": "Restore original material files to remove RTX modifications", + "action_register_protocol_title": "Register Protocol", + "action_register_protocol_desc": "Register brtx:// protocol for deep links", + "status_register_protocol_success": "Successfully registered brtx:// protocol", + "status_register_protocol_error": "Failed to register protocol: {{error}}", + "update": "Update", + "backup": "Backup", + "uninstall": "Uninstall", + "register": "Register", + "registered": "Registered", + "creator_title": "Import Custom Preset", + "creator_subtitle": "Install custom creator settings using a settings hash", + "creator_install_title": "Install Creator Settings", + "creator_settings_hash": "Settings Hash", + "creator_settings_hash_placeholder": "Enter settings hash", + "creator_settings_hash_help": "The settings hash from bedrock.graphics creator tools", + "creator_install_button": "Install Creator Settings", + "creator_installing": "Installing...", + "creator_material_files_title": "Material Files", + "creator_material_files_subtitle": "Upload required .material.bin files", + "creator_upload_materials": "Upload Material Files", + "creator_select_files": "Select Files", + "creator_files_selected": "{{count}} file(s) selected", + "creator_required_files": "Required files: RTXStub.material.bin, RTXPostFX.Tonemapping.material.bin, RTXPostFX.Bloom.material.bin", + "creator_please_enter_hash": "Please enter a settings hash", + "creator_install_success": "Creator preset '{{name}}' installed successfully", + "creator_no_materials_uploaded": "Please upload material files first", + "creator_install_materials": "Install Materials", + "creator_preset": "Custom Preset", + "community_preset": "Community Preset", + "log_installing_material_preset": "Installing material preset: {{name}}", + "creator_materials_install_success": "Material preset '{{name}}' installed successfully", + "log_material_install_complete": "Material preset installation completed successfully", + "creator_materials_install_error": "Material installation failed: {{error}}", + "log_material_install_error": "Material preset installation failed: {{error}}", + "creator_install_error": "Installation failed: {{error}}", + "creator_files_uploaded": "Successfully uploaded {{count}} material file(s)", + "creator_file_uploaded": "Successfully uploaded {{filename}}", + "creator_file_removed": "Removed {{filename}}", + "creator_upload_error": "Upload failed: {{error}}", + "creator_upload_error_single": "Failed to upload {{filename}}: {{error}}", + "creator_upload_button": "Upload Files", + "creator_upload_dialog_help": "Click to open a file dialog and select any .material.bin file to upload.", + "creator_uploaded_files": "Uploaded Files", + "remove": "Remove", + "add_custom_installation": "Locate Sideloaded Instance", + "open_installation": "Launch Minecraft Instance", + "currently_installed": "Minecraft Instances", + "iobit_path": "IObit Unlocker Path", + "iobit_found": "IObit Unlocker is available for WindowsApps installations", + "iobit_not_found": "IObit Unlocker not found - WindowsApps installations may not work", + "iobit_path_label": "Current Path:", + "iobit_browse": "Browse", + "iobit_auto_detect": "Auto Detect", + "iobit_status_found": "Found", + "iobit_status_not_found": "Not Found", + "iobit_help_text": "IObit Unlocker is required to modify files in WindowsApps installations. Click 'Auto Detect' to search automatically or 'Browse' to select manually.", + "iobit_installation": "IObit Installation", + "iobit_installation_description": "Prerequisite for copying to WindowsApps installations", + "creator_rtpack_title": "RTX Pack Files", + "creator_rtpack_subtitle": "Install RTX presets from .rtpack files", + "creator_select_rtpack": "Select RTX Pack", + "creator_browse_rtpack": "Browse for .rtpack", + "creator_rtpack_help": "Click to select an .rtpack file and install it immediately", + "creator_selected_rtpack": "Selected RTX Pack", + "creator_rtpack_selected": "RTX pack selected: {{filename}}", + "creator_rtpack_error": "Failed to select RTX pack: {{error}}", + "creator_no_rtpack_selected": "No RTX pack selected", + "creator_rtpack_install_success": "RTX pack '{{name}}' installed successfully", + "creator_rtpack_install_error": "RTX pack installation failed: {{error}}", + "log_installing_rtpack": "Installing RTX pack: {{name}}", + "log_rtpack_install_complete": "RTX pack installation completed successfully", + "log_rtpack_install_error": "RTX pack installation failed: {{error}}", + "log_force_refreshing_presets": "Fetching latest API data...", + "refresh_installations": "Refresh Installations", + "refresh_api": "Refresh API", + "clear_cache": "Clear Cache", + "installations_none_found_hint": "You can add sideloaded instances using the \"Find Installation\" button.", + "installation_name": "Installation Name", + "installation_name_placeholder": "Enter installation name", + "installation_path": "Installation Path", + "installation_path_placeholder": "Enter installation path", + "disclaimer": "Disclaimer", + "disclaimer_text": "BetterRTX is not affiliated with, approved, or endorsed by IObit, NVIDIA, Mojang, or Microsoft. The BetterRTX team is not responsible for any problems caused by its dependencies. Modding requires a certain level of technical knowledge and may cause issues with your game or system. Use at your own risk." +} diff --git a/v3/src-tauri/.gitignore b/v3/src-tauri/.gitignore new file mode 100644 index 0000000..b21bd68 --- /dev/null +++ b/v3/src-tauri/.gitignore @@ -0,0 +1,7 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Generated by Tauri +# will have schema files for capabilities auto-completion +/gen/schemas diff --git a/v3/src-tauri/Cargo.lock b/v3/src-tauri/Cargo.lock new file mode 100644 index 0000000..e9fa747 --- /dev/null +++ b/v3/src-tauri/Cargo.lock @@ -0,0 +1,6058 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" + +[[package]] +name = "ashpd" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cbdf310d77fd3aaee6ea2093db7011dc2d35d2eb3481e5607f1f8d942ed99df" +dependencies = [ + "enumflags2", + "futures-channel", + "futures-util", + "rand 0.9.2", + "raw-window-handle", + "serde", + "serde_repr", + "tokio", + "url", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "zbus", +] + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-io" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19634d6336019ef220f09fd31168ce5c184b295cbf80345437cc36094ef223ca" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.60.2", +] + +[[package]] +name = "async-lock" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65daa13722ad51e6ab1a1b9c01299142bc75135b337923cfa10e79bbbd669f00" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.105", +] + +[[package]] +name = "async-signal" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f567af260ef69e1d52c2b560ce0ea230763e6fbb9214a85d768760a920e3e3c1" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.60.2", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.105", +] + +[[package]] +name = "atk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" +dependencies = [ + "atk-sys", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +dependencies = [ + "serde", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2 0.5.2", +] + +[[package]] +name = "block2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "340d2f0bdb2a43c1d3cd40513185b2bd7def0aa1052f956455114bc98f82dcf2" +dependencies = [ + "objc2 0.6.2", +] + +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "brotli" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "brtx-installer" +version = "3.0.0" +dependencies = [ + "base64 0.21.7", + "chrono", + "reqwest", + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-dialog", + "tauri-plugin-fs", + "tauri-plugin-opener", + "tauri-plugin-shell", + "tauri-plugin-upload", + "tokio", + "url", + "walkdir", + "winreg 0.52.0", + "zip", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "bytemuck" +version = "1.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +dependencies = [ + "serde", +] + +[[package]] +name = "cairo-rs" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +dependencies = [ + "bitflags 2.9.1", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror 1.0.69", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "camino" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d07aa9a93b00c76f71bc35d598bed923f6d4f3a9ca5c24b7737ae1a292841c0" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.14", +] + +[[package]] +name = "cargo_toml" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" +dependencies = [ + "serde", + "toml 0.9.5", +] + +[[package]] +name = "cc" +version = "1.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e" +dependencies = [ + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfb" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "time", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" +dependencies = [ + "bitflags 2.9.1", + "core-foundation 0.10.1", + "core-graphics-types", + "foreign-types 0.5.0", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.9.1", + "core-foundation 0.10.1", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cssparser" +version = "0.29.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "matches", + "phf 0.10.1", + "proc-macro2", + "quote", + "smallvec", + "syn 1.0.109", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.105", +] + +[[package]] +name = "ctor" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" +dependencies = [ + "quote", + "syn 2.0.105", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.105", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.105", +] + +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "derive_more" +version = "0.99.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.105", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.60.2", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags 2.9.1", + "block2 0.6.1", + "libc", + "objc2 0.6.2", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.105", +] + +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading", +] + +[[package]] +name = "dlopen2" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1297103d2bbaea85724fcee6294c2d50b1081f9ad47d0f6f6f61eda65315a6" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dlopen2_derive" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "788160fb30de9cdd857af31c6a2675904b16ece8fc2737b2c7127ba368c9d0f4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.105", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +dependencies = [ + "serde", +] + +[[package]] +name = "dtoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "embed-resource" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6d81016d6c977deefb2ef8d8290da019e27cc26167e102185da528e6c0ab38" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml 0.9.5", + "vswhom", + "winreg 0.55.0", +] + +[[package]] +name = "embed_plist" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "endi" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.105", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7" +dependencies = [ + "serde", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + +[[package]] +name = "flate2" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared 0.3.1", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.105", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.105", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "gdk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", + "once_cell", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkwayland-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" +dependencies = [ + "gdk-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkx11" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" +dependencies = [ + "gdk", + "gdkx11-sys", + "gio", + "glib", + "libc", + "x11", +] + +[[package]] +name = "gdkx11-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" +dependencies = [ + "gdk-sys", + "glib-sys", + "libc", + "system-deps", + "x11", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +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]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "gio" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "glib" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +dependencies = [ + "bitflags 2.9.1", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "glib-macros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +dependencies = [ + "heck 0.4.1", + "proc-macro-crate 2.0.0", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.105", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" +dependencies = [ + "atk", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk3-macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.105", +] + +[[package]] +name = "h2" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.10.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "html5ever" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" +dependencies = [ + "log", + "mac", + "markup5ever", + "match_token", +] + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "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-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.6.0", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ico" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc50b891e4acf8fe0e71ef88ec43ad82ee07b3810ad09de10f1d01f072ed4b98" +dependencies = [ + "byteorder", + "png", +] + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown 0.15.5", + "serde", +] + +[[package]] +name = "infer" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +dependencies = [ + "cfb", +] + +[[package]] +name = "io-uring" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "libc", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "javascriptcore-rs" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" +dependencies = [ + "bitflags 1.3.2", + "glib", + "javascriptcore-rs-sys", +] + +[[package]] +name = "javascriptcore-rs-sys" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" +dependencies = [ + "jsonptr", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "jsonptr" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "keyboard-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +dependencies = [ + "bitflags 2.9.1", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "kuchikiki" +version = "0.8.8-speedreader" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" +dependencies = [ + "cssparser", + "html5ever", + "indexmap 2.10.0", + "selectors", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libappindicator" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +dependencies = [ + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", +] + +[[package]] +name = "libappindicator-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +dependencies = [ + "gtk-sys", + "libloading", + "once_cell", +] + +[[package]] +name = "libc" +version = "0.2.175" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libredox" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" +dependencies = [ + "bitflags 2.9.1", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "markup5ever" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" +dependencies = [ + "log", + "phf 0.11.3", + "phf_codegen 0.11.3", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "match_token" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.105", +] + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", +] + +[[package]] +name = "muda" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c1738382f66ed56b3b9c8119e794a2e23148ac8ea214eda86622d4cb9d415a" +dependencies = [ + "crossbeam-channel", + "dpi", + "gtk", + "keyboard-types", + "objc2 0.6.2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.1", + "once_cell", + "png", + "serde", + "thiserror 2.0.14", + "windows-sys 0.60.2", +] + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.9.1", + "jni-sys", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" +dependencies = [ + "proc-macro-crate 3.3.0", + "proc-macro2", + "quote", + "syn 2.0.105", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "561f357ba7f3a2a61563a186a163d0a3a5247e1089524a3981d49adb775078bc" +dependencies = [ + "objc2-encode", + "objc2-exception-helper", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" +dependencies = [ + "bitflags 2.9.1", + "block2 0.6.1", + "libc", + "objc2 0.6.2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-foundation 0.3.1", + "objc2-quartz-core 0.3.1", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17614fdcd9b411e6ff1117dfb1d0150f908ba83a7df81b1f118005fe0a8ea15d" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.2", + "objc2-foundation 0.3.1", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291fbbf7d29287518e8686417cf7239c74700fd4b607623140a7d4a3c834329d" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.2", + "objc2-foundation 0.3.1", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" +dependencies = [ + "bitflags 2.9.1", + "dispatch2", + "objc2 0.6.2", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" +dependencies = [ + "bitflags 2.9.1", + "dispatch2", + "objc2 0.6.2", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79b3dc0cc4386b6ccf21c157591b34a7f44c8e75b064f85502901ab2188c007e" +dependencies = [ + "objc2 0.6.2", + "objc2-foundation 0.3.1", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-exception-helper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" +dependencies = [ + "cc", +] + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.9.1", + "block2 0.5.1", + "libc", + "objc2 0.5.2", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" +dependencies = [ + "bitflags 2.9.1", + "block2 0.6.1", + "libc", + "objc2 0.6.2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.9.1", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.9.1", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ffb6a0cd5f182dc964334388560b12a57f7b74b3e2dec5e2722aa2dfb2ccd5" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.2", + "objc2-foundation 0.3.1", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25b1312ad7bc8a0e92adae17aa10f90aae1fb618832f9b993b022b591027daed" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.2", + "objc2-core-foundation", + "objc2-foundation 0.3.1", +] + +[[package]] +name = "objc2-web-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91672909de8b1ce1c2252e95bbee8c1649c9ad9d14b9248b3d7b4c47903c47ad" +dependencies = [ + "bitflags 2.9.1", + "block2 0.6.1", + "objc2 0.6.2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.1", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "open" +version = "5.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2483562e62ea94312f3576a7aca397306df7990b8d89033e18766744377ef95" +dependencies = [ + "dunce", + "is-wsl", + "libc", + "pathdiff", +] + +[[package]] +name = "openssl" +version = "0.10.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "foreign-types 0.3.2", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.105", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "os_pipe" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db335f4760b14ead6290116f2427bf33a14d4f0617d49f78a246de10c1831224" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "pango" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +dependencies = [ + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_shared 0.8.0", +] + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_macros 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared 0.8.0", + "rand 0.7.3", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand 0.8.5", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared 0.11.3", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", + "syn 2.0.105", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher 1.0.1", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "plist" +version = "1.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3af6b589e163c5a788fab00ce0c0366f6efbb9959c2f9874b224936af7fce7e1" +dependencies = [ + "base64 0.22.1", + "indexmap 2.10.0", + "quick-xml 0.38.1", + "serde", + "time", +] + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "polling" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5bd19146350fe804f7cb2669c851c03d69da628803dab0d98018142aaa5d829" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.60.2", +] + +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" +dependencies = [ + "toml_edit 0.20.7", +] + +[[package]] +name = "proc-macro-crate" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +dependencies = [ + "toml_edit 0.22.27", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + +[[package]] +name = "proc-macro2" +version = "1.0.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61789d7719defeb74ea5fe81f2fdfdbd28a803847077cecce2ff14e1472f6f1" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-xml" +version = "0.37.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +dependencies = [ + "memchr", +] + +[[package]] +name = "quick-xml" +version = "0.38.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9845d9dccf565065824e69f9f235fafba1587031eda353c1f1561cd6a6be78f4" +dependencies = [ + "memchr", +] + +[[package]] +name = "quinn" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2 0.5.10", + "thiserror 2.0.14", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" +dependencies = [ + "bytes", + "getrandom 0.3.3", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.14", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.5.10", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "read-progress-stream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6435842fc2fea44b528719eb8c32203bbc1bb2f5b619fbe0c0a3d8350fd8d2a8" +dependencies = [ + "bytes", + "futures", + "pin-project-lite", +] + +[[package]] +name = "redox_syscall" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 2.0.14", +] + +[[package]] +name = "ref-cast" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.105", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "reqwest" +version = "0.12.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tokio-rustls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots", +] + +[[package]] +name = "rfd" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2bee61e6cffa4635c72d7d81a84294e28f0930db0ddcb0f66d10244674ebed" +dependencies = [ + "ashpd", + "block2 0.6.1", + "dispatch2", + "glib-sys", + "gobject-sys", + "gtk-sys", + "js-sys", + "log", + "objc2 0.6.2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.1", + "raw-window-handle", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "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-demangle" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +dependencies = [ + "bitflags 2.9.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.60.2", +] + +[[package]] +name = "rustls" +version = "0.23.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" +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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "schemars" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +dependencies = [ + "dyn-clone", + "indexmap 1.9.3", + "schemars_derive", + "serde", + "serde_json", + "url", + "uuid", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.105", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.9.1", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "selectors" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" +dependencies = [ + "bitflags 1.3.2", + "cssparser", + "derive_more", + "fxhash", + "log", + "phf 0.8.0", + "phf_codegen 0.8.0", + "precomputed-hash", + "servo_arc", + "smallvec", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-untagged" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34836a629bcbc6f1afdf0907a744870039b1e14c0561cb26094fa683b158eff3" +dependencies = [ + "erased-serde", + "serde", + "typeid", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.105", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.105", +] + +[[package]] +name = "serde_json" +version = "1.0.142" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.105", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.10.0", + "schemars 0.9.0", + "schemars 1.0.4", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.105", +] + +[[package]] +name = "serialize-to-javascript" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9823f2d3b6a81d98228151fdeaf848206a7855a7a042bbf9bf870449a66cafb" +dependencies = [ + "serde", + "serde_json", + "serialize-to-javascript-impl", +] + +[[package]] +name = "serialize-to-javascript-impl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74064874e9f6a15f04c1f3cb627902d0e6b410abbf36668afa873c61889f1763" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "servo_arc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" +dependencies = [ + "nodrop", + "stable_deref_trait", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shared_child" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e362d9935bc50f019969e2f9ecd66786612daae13e8f277be7bfb66e8bed3f7" +dependencies = [ + "libc", + "sigchld", + "windows-sys 0.60.2", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "sigchld" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47106eded3c154e70176fc83df9737335c94ce22f821c32d17ed1db1f83badb1" +dependencies = [ + "libc", + "os_pipe", + "signal-hook", +] + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +dependencies = [ + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "softbuffer" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08" +dependencies = [ + "bytemuck", + "cfg_aliases", + "core-graphics", + "foreign-types 0.5.0", + "js-sys", + "log", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-quartz-core 0.2.2", + "raw-window-handle", + "redox_syscall", + "wasm-bindgen", + "web-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "soup3" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" +dependencies = [ + "futures-channel", + "gio", + "glib", + "libc", + "soup3-sys", +] + +[[package]] +name = "soup3-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.11.3", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", +] + +[[package]] +name = "strsim" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bc3fcb250e53458e712715cf74285c1f889686520d79294a9ef3bd7aa1fc619" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.105", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.9.1", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml 0.8.23", + "version-compare", +] + +[[package]] +name = "tao" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49c380ca75a231b87b6c9dd86948f035012e7171d1a7c40a9c2890489a7ffd8a" +dependencies = [ + "bitflags 2.9.1", + "core-foundation 0.10.1", + "core-graphics", + "crossbeam-channel", + "dispatch", + "dlopen2", + "dpi", + "gdkwayland-sys", + "gdkx11-sys", + "gtk", + "jni", + "lazy_static", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "objc2 0.6.2", + "objc2-app-kit", + "objc2-foundation 0.3.1", + "once_cell", + "parking_lot", + "raw-window-handle", + "scopeguard", + "tao-macros", + "unicode-segmentation", + "url", + "windows", + "windows-core", + "windows-version", + "x11-dl", +] + +[[package]] +name = "tao-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.105", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tauri" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "352a4bc7bf6c25f5624227e3641adf475a6535707451b09bb83271df8b7a6ac7" +dependencies = [ + "anyhow", + "bytes", + "dirs", + "dunce", + "embed_plist", + "getrandom 0.3.3", + "glob", + "gtk", + "heck 0.5.0", + "http", + "jni", + "libc", + "log", + "mime", + "muda", + "objc2 0.6.2", + "objc2-app-kit", + "objc2-foundation 0.3.1", + "objc2-ui-kit", + "percent-encoding", + "plist", + "raw-window-handle", + "reqwest", + "serde", + "serde_json", + "serde_repr", + "serialize-to-javascript", + "swift-rs", + "tauri-build", + "tauri-macros", + "tauri-runtime", + "tauri-runtime-wry", + "tauri-utils", + "thiserror 2.0.14", + "tokio", + "tray-icon", + "url", + "urlpattern", + "webkit2gtk", + "webview2-com", + "window-vibrancy", + "windows", +] + +[[package]] +name = "tauri-build" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "182d688496c06bf08ea896459bf483eb29cdff35c1c4c115fb14053514303064" +dependencies = [ + "anyhow", + "cargo_toml", + "dirs", + "glob", + "heck 0.5.0", + "json-patch", + "schemars 0.8.22", + "semver", + "serde", + "serde_json", + "tauri-utils", + "tauri-winres", + "toml 0.8.23", + "walkdir", +] + +[[package]] +name = "tauri-codegen" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b54a99a6cd8e01abcfa61508177e6096a4fe2681efecee9214e962f2f073ae4a" +dependencies = [ + "base64 0.22.1", + "brotli", + "ico", + "json-patch", + "plist", + "png", + "proc-macro2", + "quote", + "semver", + "serde", + "serde_json", + "sha2", + "syn 2.0.105", + "tauri-utils", + "thiserror 2.0.14", + "time", + "url", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-macros" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7945b14dc45e23532f2ded6e120170bbdd4af5ceaa45784a6b33d250fbce3f9e" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.105", + "tauri-codegen", + "tauri-utils", +] + +[[package]] +name = "tauri-plugin" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bd5c1e56990c70a906ef67a9851bbdba9136d26075ee9a2b19c8b46986b3e02" +dependencies = [ + "anyhow", + "glob", + "plist", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri-utils", + "toml 0.8.23", + "walkdir", +] + +[[package]] +name = "tauri-plugin-dialog" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e5858cc7b455a73ab4ea2ebc08b5be33682c00ff1bf4cad5537d4fb62499d9" +dependencies = [ + "log", + "raw-window-handle", + "rfd", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "tauri-plugin-fs", + "thiserror 2.0.14", + "url", +] + +[[package]] +name = "tauri-plugin-fs" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c6ef84ee2f2094ce093e55106d90d763ba343fad57566992962e8f76d113f99" +dependencies = [ + "anyhow", + "dunce", + "glob", + "percent-encoding", + "schemars 0.8.22", + "serde", + "serde_json", + "serde_repr", + "tauri", + "tauri-plugin", + "tauri-utils", + "thiserror 2.0.14", + "toml 0.8.23", + "url", +] + +[[package]] +name = "tauri-plugin-opener" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecee219f11cdac713ab32959db5d0cceec4810ba4f4458da992292ecf9660321" +dependencies = [ + "dunce", + "glob", + "objc2-app-kit", + "objc2-foundation 0.3.1", + "open", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.14", + "url", + "windows", + "zbus", +] + +[[package]] +name = "tauri-plugin-shell" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b9ffadec5c3523f11e8273465cacb3d86ea7652a28e6e2a2e9b5c182f791d25" +dependencies = [ + "encoding_rs", + "log", + "open", + "os_pipe", + "regex", + "schemars 0.8.22", + "serde", + "serde_json", + "shared_child", + "tauri", + "tauri-plugin", + "thiserror 2.0.14", + "tokio", +] + +[[package]] +name = "tauri-plugin-upload" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3a3a78df0924a08d166d2d8041c6b9e752e6d94913177b52f1bfa11b5f73d3" +dependencies = [ + "futures-util", + "log", + "read-progress-stream", + "reqwest", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.14", + "tokio", + "tokio-util", +] + +[[package]] +name = "tauri-runtime" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b1cc885be806ea15ff7b0eb47098a7b16323d9228876afda329e34e2d6c4676" +dependencies = [ + "cookie", + "dpi", + "gtk", + "http", + "jni", + "objc2 0.6.2", + "objc2-ui-kit", + "raw-window-handle", + "serde", + "serde_json", + "tauri-utils", + "thiserror 2.0.14", + "url", + "windows", +] + +[[package]] +name = "tauri-runtime-wry" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe653a2fbbef19fe898efc774bc52c8742576342a33d3d028c189b57eb1d2439" +dependencies = [ + "gtk", + "http", + "jni", + "log", + "objc2 0.6.2", + "objc2-app-kit", + "objc2-foundation 0.3.1", + "once_cell", + "percent-encoding", + "raw-window-handle", + "softbuffer", + "tao", + "tauri-runtime", + "tauri-utils", + "url", + "webkit2gtk", + "webview2-com", + "windows", + "wry", +] + +[[package]] +name = "tauri-utils" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330c15cabfe1d9f213478c9e8ec2b0c76dab26bb6f314b8ad1c8a568c1d186e" +dependencies = [ + "anyhow", + "brotli", + "cargo_metadata", + "ctor", + "dunce", + "glob", + "html5ever", + "http", + "infer", + "json-patch", + "kuchikiki", + "log", + "memchr", + "phf 0.11.3", + "proc-macro2", + "quote", + "regex", + "schemars 0.8.22", + "semver", + "serde", + "serde-untagged", + "serde_json", + "serde_with", + "swift-rs", + "thiserror 2.0.14", + "toml 0.8.23", + "url", + "urlpattern", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-winres" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd21509dd1fa9bd355dc29894a6ff10635880732396aa38c0066c1e6c1ab8074" +dependencies = [ + "embed-resource", + "toml 0.9.5", +] + +[[package]] +name = "tempfile" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +dependencies = [ + "fastrand", + "getrandom 0.3.3", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e" +dependencies = [ + "thiserror-impl 2.0.14", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.105", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.105", +] + +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +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.47.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +dependencies = [ + "backtrace", + "bytes", + "io-uring", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "slab", + "socket2 0.6.0", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + +[[package]] +name = "toml" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8" +dependencies = [ + "indexmap 2.10.0", + "serde", + "serde_spanned 1.0.0", + "toml_datetime 0.7.0", + "toml_parser", + "toml_writer", + "winnow 0.7.12", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.10.0", + "toml_datetime 0.6.11", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.20.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" +dependencies = [ + "indexmap 2.10.0", + "toml_datetime 0.6.11", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap 2.10.0", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow 0.7.12", +] + +[[package]] +name = "toml_parser" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" +dependencies = [ + "winnow 0.7.12", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "toml_writer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags 2.9.1", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.105", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tray-icon" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d92153331e7d02ec09137538996a7786fe679c629c279e82a6be762b7e6fe2" +dependencies = [ + "crossbeam-channel", + "dirs", + "libappindicator", + "muda", + "objc2 0.6.2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.1", + "once_cell", + "png", + "serde", + "thiserror 2.0.14", + "windows-sys 0.59.0", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset", + "tempfile", + "winapi", +] + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-ucd-ident" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-segmentation" +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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "urlpattern" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" +dependencies = [ + "regex", + "serde", + "unic-ucd-ident", + "url", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be" +dependencies = [ + "getrandom 0.3.3", + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.105", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.105", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wayland-backend" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" +dependencies = [ + "cc", + "downcast-rs", + "rustix", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" +dependencies = [ + "bitflags 2.9.1", + "rustix", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901" +dependencies = [ + "bitflags 2.9.1", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3" +dependencies = [ + "proc-macro2", + "quick-xml 0.37.5", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142" +dependencies = [ + "dlib", + "log", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76b1bc1e54c581da1e9f179d0b38512ba358fb1af2d634a1affe42e37172361a" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "gdk", + "gdk-sys", + "gio", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "gtk", + "gtk-sys", + "javascriptcore-rs", + "libc", + "once_cell", + "soup3", + "webkit2gtk-sys", +] + +[[package]] +name = "webkit2gtk-sys" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62daa38afc514d1f8f12b8693d30d5993ff77ced33ce30cd04deebc267a6d57c" +dependencies = [ + "bitflags 1.3.2", + "cairo-sys-rs", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk-sys", + "javascriptcore-rs-sys", + "libc", + "pkg-config", + "soup3-sys", + "system-deps", +] + +[[package]] +name = "webpki-roots" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webview2-com" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ba622a989277ef3886dd5afb3e280e3dd6d974b766118950a08f8f678ad6a4" +dependencies = [ + "webview2-com-macros", + "webview2-com-sys", + "windows", + "windows-core", + "windows-implement", + "windows-interface", +] + +[[package]] +name = "webview2-com-macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.105", +] + +[[package]] +name = "webview2-com-sys" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c" +dependencies = [ + "thiserror 2.0.14", + "windows", + "windows-core", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "window-vibrancy" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" +dependencies = [ + "objc2 0.6.2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.1", + "raw-window-handle", + "windows-sys 0.59.0", + "windows-version", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.105", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.105", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-registry" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.3", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-version" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e04a5c6627e310a23ad2358483286c7df260c964eb2d003d8efd6d0f4e79265c" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "winreg" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +dependencies = [ + "cfg-if", + "windows-sys 0.59.0", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "wry" +version = "0.52.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12a714d9ba7075aae04a6e50229d6109e3d584774b99a6a8c60de1698ca111b9" +dependencies = [ + "base64 0.22.1", + "block2 0.6.1", + "cookie", + "crossbeam-channel", + "dpi", + "dunce", + "gdkx11", + "gtk", + "html5ever", + "http", + "javascriptcore-rs", + "jni", + "kuchikiki", + "libc", + "ndk", + "objc2 0.6.2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.1", + "objc2-ui-kit", + "objc2-web-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "sha2", + "soup3", + "tao-macros", + "thiserror 2.0.14", + "url", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "windows", + "windows-core", + "windows-version", + "x11-dl", +] + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.105", + "synstructure", +] + +[[package]] +name = "zbus" +version = "5.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb4f9a464286d42851d18a605f7193b8febaf5b0919d71c6399b7b26e5b0aad" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "nix", + "ordered-stream", + "serde", + "serde_repr", + "tokio", + "tracing", + "uds_windows", + "windows-sys 0.59.0", + "winnow 0.7.12", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef9859f68ee0c4ee2e8cde84737c78e3f4c54f946f2a38645d0d4c7a95327659" +dependencies = [ + "proc-macro-crate 3.3.0", + "proc-macro2", + "quote", + "syn 2.0.105", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" +dependencies = [ + "serde", + "static_assertions", + "winnow 0.7.12", + "zvariant", +] + +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.105", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.105", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.105", +] + +[[package]] +name = "zip" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +dependencies = [ + "byteorder", + "crc32fast", + "crossbeam-utils", + "flate2", +] + +[[package]] +name = "zvariant" +version = "5.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91b3680bb339216abd84714172b5138a4edac677e641ef17e1d8cb1b3ca6e6f" +dependencies = [ + "endi", + "enumflags2", + "serde", + "url", + "winnow 0.7.12", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8c68501be459a8dbfffbe5d792acdd23b4959940fc87785fb013b32edbc208" +dependencies = [ + "proc-macro-crate 3.3.0", + "proc-macro2", + "quote", + "syn 2.0.105", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16edfee43e5d7b553b77872d99bc36afdda75c223ca7ad5e3fbecd82ca5fc34" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "static_assertions", + "syn 2.0.105", + "winnow 0.7.12", +] diff --git a/v3/src-tauri/Cargo.toml b/v3/src-tauri/Cargo.toml new file mode 100644 index 0000000..6cdaeca --- /dev/null +++ b/v3/src-tauri/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "brtx-installer" +version = "3.0.0" +description = "BetterRTX Installer" +authors = ["Jason J. Gardner"] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +# The `_lib` suffix may seem redundant but it is necessary +# to make the lib name unique and wouldn't conflict with the bin name. +# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519 +name = "brtx_installer_lib" +crate-type = ["staticlib", "cdylib", "rlib"] + +[build-dependencies] +tauri-build = { version = "2", features = [] } + +[dependencies] +tauri = { version = "2", features = [] } +tauri-plugin-opener = "2" +tauri-plugin-dialog = "2" +tauri-plugin-upload = "2" +tauri-plugin-fs = "2" +tauri-plugin-shell = "2" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +reqwest = { version = "0.12", features = ["json"] } +tokio = { version = "1", features = ["fs"] } +winreg = "0.52" +chrono = { version = "0.4", features = ["serde"] } +walkdir = "2.5" +zip = { version = "0.6", default-features = false, features = ["deflate"] } +url = "2.5" +base64 = "0.21" diff --git a/v3/src-tauri/build.rs b/v3/src-tauri/build.rs new file mode 100644 index 0000000..d860e1e --- /dev/null +++ b/v3/src-tauri/build.rs @@ -0,0 +1,3 @@ +fn main() { + tauri_build::build() +} diff --git a/v3/src-tauri/capabilities/default.json b/v3/src-tauri/capabilities/default.json new file mode 100644 index 0000000..c49fb08 --- /dev/null +++ b/v3/src-tauri/capabilities/default.json @@ -0,0 +1,19 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "Capability for the main window", + "windows": [ + "main" + ], + "permissions": [ + "core:default", + "core:event:allow-listen", + "core:event:allow-emit", + "dialog:default", + "fs:default", + "upload:default", + "opener:default", + "shell:allow-execute", + "shell:allow-open" + ] +} \ No newline at end of file diff --git a/v3/src-tauri/icons/128x128.png b/v3/src-tauri/icons/128x128.png new file mode 100644 index 0000000..fbf0857 Binary files /dev/null and b/v3/src-tauri/icons/128x128.png differ diff --git a/v3/src-tauri/icons/128x128@2x.png b/v3/src-tauri/icons/128x128@2x.png new file mode 100644 index 0000000..f06f47b Binary files /dev/null and b/v3/src-tauri/icons/128x128@2x.png differ diff --git a/v3/src-tauri/icons/32x32.png b/v3/src-tauri/icons/32x32.png new file mode 100644 index 0000000..0e297a8 Binary files /dev/null and b/v3/src-tauri/icons/32x32.png differ diff --git a/v3/src-tauri/icons/64x64.png b/v3/src-tauri/icons/64x64.png new file mode 100644 index 0000000..a08d8c5 Binary files /dev/null and b/v3/src-tauri/icons/64x64.png differ diff --git a/v3/src-tauri/icons/Square107x107Logo.png b/v3/src-tauri/icons/Square107x107Logo.png new file mode 100644 index 0000000..225121a Binary files /dev/null and b/v3/src-tauri/icons/Square107x107Logo.png differ diff --git a/v3/src-tauri/icons/Square142x142Logo.png b/v3/src-tauri/icons/Square142x142Logo.png new file mode 100644 index 0000000..90005bc Binary files /dev/null and b/v3/src-tauri/icons/Square142x142Logo.png differ diff --git a/v3/src-tauri/icons/Square150x150Logo.png b/v3/src-tauri/icons/Square150x150Logo.png new file mode 100644 index 0000000..0e059dc Binary files /dev/null and b/v3/src-tauri/icons/Square150x150Logo.png differ diff --git a/v3/src-tauri/icons/Square284x284Logo.png b/v3/src-tauri/icons/Square284x284Logo.png new file mode 100644 index 0000000..9968a45 Binary files /dev/null and b/v3/src-tauri/icons/Square284x284Logo.png differ diff --git a/v3/src-tauri/icons/Square30x30Logo.png b/v3/src-tauri/icons/Square30x30Logo.png new file mode 100644 index 0000000..8ec32cb Binary files /dev/null and b/v3/src-tauri/icons/Square30x30Logo.png differ diff --git a/v3/src-tauri/icons/Square310x310Logo.png b/v3/src-tauri/icons/Square310x310Logo.png new file mode 100644 index 0000000..b903f94 Binary files /dev/null and b/v3/src-tauri/icons/Square310x310Logo.png differ diff --git a/v3/src-tauri/icons/Square44x44Logo.png b/v3/src-tauri/icons/Square44x44Logo.png new file mode 100644 index 0000000..7103395 Binary files /dev/null and b/v3/src-tauri/icons/Square44x44Logo.png differ diff --git a/v3/src-tauri/icons/Square71x71Logo.png b/v3/src-tauri/icons/Square71x71Logo.png new file mode 100644 index 0000000..65a2f98 Binary files /dev/null and b/v3/src-tauri/icons/Square71x71Logo.png differ diff --git a/v3/src-tauri/icons/Square89x89Logo.png b/v3/src-tauri/icons/Square89x89Logo.png new file mode 100644 index 0000000..bb69fd7 Binary files /dev/null and b/v3/src-tauri/icons/Square89x89Logo.png differ diff --git a/v3/src-tauri/icons/StoreLogo.png b/v3/src-tauri/icons/StoreLogo.png new file mode 100644 index 0000000..dffa332 Binary files /dev/null and b/v3/src-tauri/icons/StoreLogo.png differ diff --git a/v3/src-tauri/icons/icon.icns b/v3/src-tauri/icons/icon.icns new file mode 100644 index 0000000..e8f33eb Binary files /dev/null and b/v3/src-tauri/icons/icon.icns differ diff --git a/v3/src-tauri/icons/icon.ico b/v3/src-tauri/icons/icon.ico new file mode 100644 index 0000000..612b3dd Binary files /dev/null and b/v3/src-tauri/icons/icon.ico differ diff --git a/v3/src-tauri/icons/icon.png b/v3/src-tauri/icons/icon.png new file mode 100644 index 0000000..abb94bd Binary files /dev/null and b/v3/src-tauri/icons/icon.png differ diff --git a/v3/src-tauri/src/lib.rs b/v3/src-tauri/src/lib.rs new file mode 100644 index 0000000..0354e55 --- /dev/null +++ b/v3/src-tauri/src/lib.rs @@ -0,0 +1,1673 @@ +// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ +use chrono::{Local, DateTime, Utc}; +use reqwest::Client; +use std::fs::{self, File}; +use std::io::Read; +use std::path::{Path, PathBuf}; +use serde::{Deserialize, Serialize}; +use walkdir::WalkDir; +use winreg::enums::*; +use winreg::RegKey; +use std::collections::HashMap; +use tauri::Emitter; +use tauri_plugin_dialog::DialogExt; +use tauri_plugin_shell::ShellExt; +use url::Url; + +const BRTX_DIR_NAME: &str = "graphics.bedrock"; + +#[derive(Serialize, Deserialize, Debug, Clone)] +struct Installation { + #[serde(rename = "FriendlyName")] + friendly_name: String, + #[serde(rename = "InstallLocation")] + install_location: String, + #[serde(rename = "Preview")] + preview: bool, + #[serde(skip_serializing_if = "Option::is_none")] + installed_preset: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +struct InstalledPreset { + uuid: String, + name: String, + installed_at: String, + #[serde(skip_serializing_if = "Option::is_none")] + is_creator: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PackInfo { + pub name: String, + pub uuid: String, + pub stub: String, + pub tonemapping: String, + pub bloom: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct CacheEntry { + data: T, + timestamp: DateTime, + expires_at: DateTime, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +struct Cache { + presets: Option>>, + downloads: HashMap>>, +} + +fn local_app_data() -> PathBuf { + std::env::var("LOCALAPPDATA") + .map(PathBuf::from) + .unwrap_or_else(|_| PathBuf::from("C:/Users/Public/AppData/Local")) +} + +fn brtx_dir() -> PathBuf { + local_app_data().join(BRTX_DIR_NAME) +} + +fn ensure_dir(p: &Path) -> std::io::Result<()> { + if !p.exists() { + fs::create_dir_all(p)?; + } + Ok(()) +} + +fn cache_file_path() -> PathBuf { + brtx_dir().join("cache.json") +} + +async fn load_cache() -> Cache { + let cache_file = brtx_dir().join("cache.json"); + if cache_file.exists() { + if let Ok(content) = tokio::fs::read_to_string(&cache_file).await { + if let Ok(cache) = serde_json::from_str::(&content) { + return cache; + } + } + } + Cache { + presets: None, + downloads: HashMap::new(), + } +} + +async fn save_cache(cache: &Cache) -> Result<(), String> { + let cache_path = cache_file_path(); + ensure_dir(cache_path.parent().unwrap()).map_err(|e| e.to_string())?; + let content = serde_json::to_string_pretty(cache).map_err(|e| e.to_string())?; + tokio::fs::write(&cache_path, content).await.map_err(|e| e.to_string()) +} + +fn is_cache_valid(cached: &CacheEntry) -> bool { + Utc::now() < cached.expires_at +} + +async fn run_powershell_async(app_handle: tauri::AppHandle, script: &str) -> Result { + let shell = app_handle.shell(); + let output = shell + .command("powershell.exe") + .args(["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", script]) + .output() + .await + .map_err(|e| format!("Failed to spawn PowerShell: {e}"))?; + + if output.status.success() { + Ok(String::from_utf8_lossy(&output.stdout).to_string()) + } else { + Err(String::from_utf8_lossy(&output.stderr).to_string()) + } +} + +fn is_sideloaded(install_location: &str) -> bool { + // Sideloaded if NOT under Program Files\WindowsApps + let lc = install_location.to_ascii_lowercase(); + !lc.contains("windowsapps") +} + +fn get_custom_iobit_path() -> Option { + let config_file = brtx_dir().join("iobit_path.txt"); + if config_file.exists() { + if let Ok(path_str) = fs::read_to_string(&config_file) { + let path = PathBuf::from(path_str.trim()); + if path.exists() { + return Some(path); + } + } + } + None +} + +fn get_iobit_path_cached() -> Option { + // Check if we have a cached path + let cache_file = brtx_dir().join("iobit_path.txt"); + if cache_file.exists() { + if let Ok(cached_path) = fs::read_to_string(&cache_file) { + let path = PathBuf::from(cached_path.trim()); + if path.exists() { + return Some(path); + } + } + } + + // Fall back to automatic detection + get_iobit_unlocker_exe() +} + +fn get_iobit_unlocker_exe() -> Option { + // First check if user has set a custom path + if let Some(custom_path) = get_custom_iobit_path() { + return Some(custom_path); + } + + // Try common install locations + let candidates = [ + r"C:\\Program Files (x86)\\IObit\\IObit Unlocker\\IObitUnlocker.exe", + r"C:\\Program Files\\IObit\\IObit Unlocker\\IObitUnlocker.exe", + ]; + for c in candidates { if Path::new(c).exists() { return Some(PathBuf::from(c)); } } + + // Fallback: search Program Files dirs shallowly + for root in [r"C:\\Program Files", r"C:\\Program Files (x86)"] { + let rootp = Path::new(root); + if rootp.exists() { + for entry in WalkDir::new(rootp).max_depth(3) { + if let Ok(e) = entry { + let p = e.path(); + if p.file_name().map(|n| n.to_string_lossy().eq_ignore_ascii_case("IObitUnlocker.exe")).unwrap_or(false) { + return Some(p.to_path_buf()); + } + } + } + } + } + None +} + +async fn iobit_delete_async(app_handle: &tauri::AppHandle, iobit: &Path, location: &Path, materials: &[PathBuf]) -> Result<(), String> { + // RTX material files that need to be deleted before installation + let rtx_files_to_delete = [ + "RTXStub.material.bin", + "RTXPostFX.Tonemapping.material.bin", + "RTXPostFX.Bloom.material.bin" + ]; + + // Collect all files to delete (RTX files + materials), avoiding duplicates + let mut files_to_delete = std::collections::HashSet::new(); + + // Add hardcoded RTX files + for file_name in &rtx_files_to_delete { + files_to_delete.insert(file_name.to_string()); + } + + // Add material files being installed + for m in materials { + if let Some(name) = m.file_name() { + if let Some(name_str) = name.to_str() { + files_to_delete.insert(name_str.to_string()); + } + } + } + + if files_to_delete.is_empty() { + println!("No files to delete"); + return Ok(()); + } + + // Build target paths for all files to delete + let targets: Vec<_> = files_to_delete + .iter() + .map(|file_name| location.join("data").join("renderer").join("materials").join(file_name)) + .collect(); + + // Build the exact ArgumentList string for single-pass delete: + // '/Delete "target1","target2","target3"' + let targets_joined = targets + .iter() + .map(|t| t.display().to_string()) + .collect::>() + .join("\",\""); + let arglist = format!("/Delete \"{}\"", targets_joined); + + // Escape single quotes for embedding in a single-quoted PS string + let ioexe_ps = iobit.display().to_string().replace("'", "''"); + let arglist_ps = arglist.replace("'", "''"); + + let ps_cmd = format!( + "Start-Process -FilePath '{}' -ArgumentList '{}' -Wait -PassThru", + ioexe_ps, arglist_ps + ); + + println!( + "Deleting materials via IObit (single pass): [{}]", + files_to_delete.iter().cloned().collect::>().join(", ") + ); + + let shell = app_handle.shell(); + let output = shell + .command("powershell.exe") + .args(["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", &ps_cmd]) + .output() + .await + .map_err(|e| format!("Failed to run PowerShell for IObit delete: {e}"))?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + println!("IObit delete reported non-success: {}", stderr); + } + + println!("IObit single-pass delete completed"); + Ok(()) +} + +async fn iobit_copy_async(app_handle: &tauri::AppHandle, iobit: &Path, destination: &Path, materials: &[PathBuf]) -> Result<(), String> { + // Best-effort ensure destination directory for sideloaded installs + let dest_dir = destination.join("data").join("renderer").join("materials"); + let _ = ensure_dir(&dest_dir); + + if materials.is_empty() { + println!("No materials to copy"); + return Ok(()); + } + + // Build the exact ArgumentList string used in PowerShell v2 implementation: + // '/Copy "src1","src2" "destDir"' + let sources_joined = materials + .iter() + .map(|m| m.display().to_string()) + .collect::>() + .join("\",\""); + let arglist = format!("/Copy \"{}\" \"{}\"", sources_joined, dest_dir.display()); + + // Escape single quotes for embedding in a single-quoted PS string + let ioexe_ps = iobit.display().to_string().replace("'", "''"); + let arglist_ps = arglist.replace("'", "''"); + + let ps_cmd = format!( + "Start-Process -FilePath '{}' -ArgumentList '{}' -Wait -PassThru", + ioexe_ps, arglist_ps + ); + + println!( + "Copying materials via IObit (single pass): [{}] -> {}", + materials + .iter() + .map(|m| m.file_name().unwrap_or_default().to_string_lossy().to_string()) + .collect::>() + .join(", "), + dest_dir.display() + ); + + let shell = app_handle.shell(); + let output = shell + .command("powershell.exe") + .args(["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", &ps_cmd]) + .output() + .await + .map_err(|e| format!("Failed to run PowerShell for IObit copy: {e}"))?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + return Err(format!("IObit copy via PowerShell failed: {}", stderr)); + } + + println!("IObit single-pass copy completed"); + Ok(()) +} + +async fn copy_shader_files_async(app_handle: &tauri::AppHandle, install_location: &str, materials: &[PathBuf], pack: &PackInfo) -> Result<(), String> { + let mc_dest = Path::new(install_location).join("data").join("renderer").join("materials"); + let sideloaded = is_sideloaded(install_location); + + // For WindowsApps installs, prefer IObit Unlocker; for sideloaded, use direct copy + // Only use IObit for WindowsApps (non-sideloaded) installations + + if sideloaded { + // Fallback for sideloaded installations: ensure dir, delete then copy directly + ensure_dir(&mc_dest).map_err(|e| format!("Failed to create materials dir: {e}"))?; + for m in materials { + if !m.exists() { return Err(format!("Source material not found: {}", m.display())); } + let dest = mc_dest.join(m.file_name().ok_or("invalid material filename")?); + if dest.exists() { + println!("Removing existing file before copy: {}", dest.display()); + let _ = fs::remove_file(&dest); + } + fs::copy(m, &dest).map_err(|e| format!("Direct copy failed to {}: {e}", dest.display()))?; + } + let installed_preset = InstalledPreset { + uuid: pack.uuid.clone(), + name: pack.name.clone(), + installed_at: chrono::Utc::now().to_rfc3339(), + is_creator: None, + }; + if let Err(e) = save_installed_preset(install_location, &installed_preset) { + println!("⚠ Failed to save preset tracking: {}", e); + } + return Ok(()); + } + + if let Some(ioexe) = get_iobit_path_cached() { + println!("Using IObit Unlocker for WindowsApps delete+copy"); + match try_iobit_copy_async(app_handle, &ioexe, install_location, materials).await { + Ok(_) => { + let installed_preset = InstalledPreset { + uuid: pack.uuid.clone(), + name: pack.name.clone(), + installed_at: chrono::Utc::now().to_rfc3339(), + is_creator: None, + }; + if let Err(e) = save_installed_preset(install_location, &installed_preset) { + println!("⚠ Failed to save preset tracking: {}", e); + } + return Ok(()); + } + Err(e) => { + println!("⚠ IObit Unlocker failed: {}", e); + // Continue to appropriate fallback below + } + } + } else { + println!("⚠ IObit Unlocker not found, using fallback method for WindowsApps"); + } + + println!("Attempting elevated PowerShell fallback..."); + match try_elevated_copy_async(app_handle, &mc_dest, materials).await { + Ok(_) => { + let installed_preset = InstalledPreset { + uuid: pack.uuid.clone(), + name: pack.name.clone(), + installed_at: chrono::Utc::now().to_rfc3339(), + is_creator: None, + }; + if let Err(e) = save_installed_preset(install_location, &installed_preset) { + println!("⚠ Failed to save preset tracking: {}", e); + } + Ok(()) + } + Err(e) => Err(e) + } +} + +async fn try_iobit_copy_async(app_handle: &tauri::AppHandle, ioexe: &Path, install_location: &str, materials: &[PathBuf]) -> Result<(), String> { + println!("Starting IObit operations for {} files", materials.len()); + let install_path = Path::new(install_location); + + // Delete existing files first + println!("Deleting existing material files..."); + iobit_delete_async(app_handle, ioexe, install_path, materials).await?; + + // Copy new files + println!("Copying material files with IObit Unlocker..."); + iobit_copy_async(app_handle, ioexe, install_path, materials).await?; + + // Skipping file verification per user preference + std::thread::sleep(std::time::Duration::from_millis(500)); + + println!("IObit operations completed successfully"); + Ok(()) +} + +async fn try_elevated_copy_async(app_handle: &tauri::AppHandle, mc_dest: &Path, materials: &[PathBuf]) -> Result<(), String> { + // Use PowerShell with elevation request to copy files + let mut ps_script = String::from("Start-Process powershell -Verb RunAs -ArgumentList '-Command', '"); + + for m in materials { + let src = m.to_string_lossy().replace('\\', "\\\\").replace('\'', "\'\'"); + let dest_file = mc_dest.join(m.file_name().unwrap()); + let dest = dest_file.to_string_lossy().replace('\\', "\\\\").replace('\'', "\'\'"); + ps_script.push_str(&format!("Copy-Item '{}' '{}' -Force; ", src, dest)); + } + + ps_script.push_str("' -Wait"); + + let shell = app_handle.shell(); + let output = shell + .command("powershell.exe") + .args(["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", &ps_script]) + .output() + .await + .map_err(|e| format!("Failed to run elevated PowerShell: {e}"))?; + + if output.status.success() { + // Skipping file verification per user preference + Ok(()) + } else { + Err(format!("Elevated PowerShell failed: {}", String::from_utf8_lossy(&output.stderr))) + } +} + +fn extract_rtpack_to(pack: &Path, out_dir: &Path) -> Result<(), String> { + ensure_dir(out_dir).map_err(|e| e.to_string())?; + let mut f = File::open(pack).map_err(|e| format!("Open pack failed: {e}"))?; + let mut data = Vec::new(); + f.read_to_end(&mut data).map_err(|e| format!("Read pack failed: {e}"))?; + let reader = std::io::Cursor::new(data); + let mut zip = zip::ZipArchive::new(reader).map_err(|e| format!("Invalid .rtpack: {e}"))?; + for i in 0..zip.len() { + let mut file = zip.by_index(i).map_err(|e| e.to_string())?; + let outpath = out_dir.join(file.name()); + if file.name().ends_with('/') { + ensure_dir(&outpath).map_err(|e| e.to_string())?; + } else { + if let Some(parent) = outpath.parent() { ensure_dir(parent).map_err(|e| e.to_string())?; } + let mut outfile = File::create(&outpath).map_err(|e| e.to_string())?; + std::io::copy(&mut file, &mut outfile).map_err(|e| e.to_string())?; + } + } + Ok(()) +} + +fn find_materials(root: &Path) -> Vec { + let mut v = Vec::new(); + for entry in WalkDir::new(root).into_iter().filter_map(Result::ok) { + let p = entry.path(); + if p.is_file() && p.extension().map(|e| e == "bin").unwrap_or(false) { + if let Some(stem) = p.file_stem() { + let s = stem.to_string_lossy(); + if s.contains("material") { v.push(p.to_path_buf()); } + } + } + } + v +} + +fn read_json_file Deserialize<'de>>(p: &Path) -> Option { + let s = fs::read_to_string(p).ok()?; + serde_json::from_str(&s).ok() +} + +fn write_json_file(p: &Path, val: &T) -> Result<(), String> { + if let Some(parent) = p.parent() { ensure_dir(parent).map_err(|e| e.to_string())?; } + let s = serde_json::to_string_pretty(val).map_err(|e| e.to_string())?; + fs::write(p, s).map_err(|e| e.to_string()) +} + +fn find_launcher_installs( + installations: &mut Vec, + launcher_name: &str, + base_path_env: &str, + sub_path: &str, +) { + let Ok(app_data) = std::env::var(base_path_env) else { return }; + let launcher_path = std::path::Path::new(&app_data).join(sub_path); + let Ok(entries) = std::fs::read_dir(launcher_path) else { return }; + + let new_installations = entries + .filter_map(Result::ok) + .filter(|entry| entry.file_type().map_or(false, |ft| ft.is_dir())) + .filter_map(|entry| { + let path = entry.path(); + let version_name = path.file_name()?.to_str()?; + Some(Installation { + friendly_name: format!("{} - {}", launcher_name, version_name), + install_location: path.to_str()?.to_string(), + preview: false, + installed_preset: None, + }) + }); + + installations.extend(new_installations); +} + +#[tauri::command] +async fn list_installations(app_handle: tauri::AppHandle) -> Result, String> { + let ps = r#" + $ErrorActionPreference='Stop'; + $pkgs = Get-AppxPackage -Name 'Microsoft.Minecraft*' | Where-Object { $_.InstallLocation -notlike '*Java*' }; + $res = @(); + foreach ($mc in $pkgs) { + $name = (Get-AppxPackageManifest -Package $mc).Package.Properties.DisplayName; + $res += [PSCustomObject]@{ FriendlyName=$name; InstallLocation=$mc.InstallLocation; Preview= ($mc.InstallLocation -like '*Beta*' -or $name -like '*Preview*') }; + } + $res | ConvertTo-Json -Depth 3 + "#; + let out = run_powershell_async(app_handle, ps).await?; + let out_trim = out.trim(); + if out_trim.is_empty() { return Ok(vec![]); } + + let mut installations: Vec = { + let parsed: Result, _> = serde_json::from_str(out_trim); + if let Ok(v) = parsed { + v + } else { + let parsed_single: Result = serde_json::from_str(out_trim); + if let Ok(i) = parsed_single { vec![i] } else { vec![] } + } + }; + + find_launcher_installs( + &mut installations, + "BedrockLauncher", + "APPDATA", + "BedrockLauncher/data/versions", + ); + find_launcher_installs( + &mut installations, + "MCLauncher", + "LOCALAPPDATA", + "MCLauncher/installs", + ); + + // Add installed preset information to each installation + for installation in &mut installations { + installation.installed_preset = get_installed_preset(&installation.install_location); + } + + Ok(installations) +} + +#[tauri::command] +async fn get_api_packs() -> Result, String> { + list_presets(false).await +} + +#[tauri::command] +async fn list_presets(force_refresh: bool) -> Result, String> { + let mut cache = load_cache().await; + + // Check if we have valid cached data and don't need to force refresh + if !force_refresh { + if let Some(ref cached_presets) = cache.presets { + if is_cache_valid(cached_presets) { + return Ok(cached_presets.data.clone()); + } + } + } + + // Fetch fresh data from API + let packs_dir = brtx_dir().join("packs"); + ensure_dir(&packs_dir).map_err(|e| e.to_string())?; + + let url = "https://bedrock.graphics/api"; + let client = Client::new(); + let text = client + .get(url) + .send().await.map_err(|e| e.to_string())? + .text().await.map_err(|e| e.to_string())?; + + // Parse the JSON response directly as PackInfo array + let presets: Vec = serde_json::from_str(&text) + .map_err(|e| format!("Failed to parse presets JSON: {}. Response was: {}", e, text))?; + + // Cache the results for 1 hour + let now = Utc::now(); + let expires_at = now + chrono::Duration::hours(1); + // Update cache with fresh data + cache.presets = Some(CacheEntry { + data: presets.clone(), + timestamp: now, + expires_at, + }); + + save_cache(&cache).await.map_err(|e| e.to_string())?; + + Ok(presets) +} + +fn get_installed_preset(install_location: &str) -> Option { + let presets_file = brtx_dir().join("installed_presets.json"); + if let Some(installations) = read_json_file::>(&presets_file) { + return installations.get(install_location).cloned(); + } + None +} + +fn save_installed_preset(install_location: &str, preset: &InstalledPreset) -> Result<(), String> { + let presets_file = brtx_dir().join("installed_presets.json"); + + // Load existing installations or create new map + let mut installations = read_json_file::>(&presets_file) + .unwrap_or_else(HashMap::new); + + // Update with new preset + installations.insert(install_location.to_string(), preset.clone()); + + // Save using write_json_file + write_json_file(&presets_file, &installations) +} + +async fn get_cached_download(url: &str) -> Option> { + let cache = load_cache().await; + if let Some(cached) = cache.downloads.get(url) { + if is_cache_valid(cached) { + return Some(cached.data.clone()); + } + } + None +} + +async fn cache_download(url: &str, data: &[u8]) -> Result<(), String> { + let mut cache = load_cache().await; + let now = Utc::now(); + let expires_at = now + chrono::Duration::hours(24); // Cache downloads for 24 hours + + cache.downloads.insert(url.to_string(), CacheEntry { + data: data.to_vec(), + timestamp: now, + expires_at, + }); + + save_cache(&cache).await +} + +// Helper: download a URL to a file path with caching +async fn download_to_file_with_cache(client: &Client, url: &str, file_path: &Path) -> Result<(), String> { + if let Some(cached_data) = get_cached_download(url).await { + tokio::fs::write(file_path, cached_data).await.map_err(|e| e.to_string())?; + return Ok(()); + } + let resp = client.get(url).send().await.map_err(|e| e.to_string())?; + let data = resp.bytes().await.map_err(|e| e.to_string())?; + let _ = cache_download(url, &data).await; + tokio::fs::write(file_path, data).await.map_err(|e| e.to_string())?; + Ok(()) +} + +#[tauri::command] +fn clear_cache() -> Result<(), String> { + let cache_path = cache_file_path(); + if cache_path.exists() { + fs::remove_file(cache_path).map_err(|e| e.to_string())?; + } + + // Also clear downloaded packs directory + let packs_dir = brtx_dir().join("packs"); + if packs_dir.exists() { + fs::remove_dir_all(&packs_dir).map_err(|e| e.to_string())?; + } + + Ok(()) +} + +#[tauri::command] +async fn get_cache_info() -> Result { + let cache = load_cache().await; + let mut info = serde_json::Map::new(); + + if let Some(ref presets_cache) = cache.presets { + info.insert("presets_cached".to_string(), serde_json::Value::Bool(true)); + info.insert("presets_count".to_string(), serde_json::Value::from(presets_cache.data.len() as u64)); + info.insert("presets_expires".to_string(), serde_json::Value::String(presets_cache.expires_at.to_rfc3339())); + } else { + info.insert("presets_cached".to_string(), serde_json::Value::Bool(false)); + } + + info.insert("downloads_cached".to_string(), serde_json::Value::from(cache.downloads.len() as u64)); + + Ok(serde_json::Value::Object(info)) +} + +#[tauri::command] +async fn download_and_install_pack(app_handle: tauri::AppHandle, uuid: String, selected_names: Vec) -> Result<(), String> { + let all = list_installations(app_handle.clone()).await?; + // Map by InstallLocation because the UI sends InstallLocation values + let map: std::collections::HashMap<_, _> = all + .into_iter() + .map(|i| (i.install_location.clone(), i)) + .collect(); + + // Get the preset info from cached API data + let packs = get_api_packs().await?; + let preset = packs.iter().find(|p| p.uuid == uuid).ok_or("Preset not found")?; + + let dir = brtx_dir().join("packs").join(&uuid); + ensure_dir(&dir).map_err(|e| e.to_string())?; + let client = Client::new(); + + // Download files with caching + let stub_path = dir.join("RTXStub.material.bin"); + download_to_file_with_cache(&client, &preset.stub, &stub_path).await?; + let tone_path = dir.join("RTXPostFX.Tonemapping.material.bin"); + download_to_file_with_cache(&client, &preset.tonemapping, &tone_path).await?; + let bloom_path = dir.join("RTXPostFX.Bloom.material.bin"); + download_to_file_with_cache(&client, &preset.bloom, &bloom_path).await?; + + let materials = vec![ + stub_path.clone(), + tone_path.clone(), + bloom_path.clone(), + ]; + for install_location in selected_names { + if let Some(ins) = map.get(&install_location) { + copy_shader_files_async(&app_handle, &ins.install_location, &materials, preset).await?; + } else { + println!("⚠ Skipping unknown selection (no matching installation): {}", install_location); + } + } + Ok(()) +} + +#[tauri::command] +async fn install_from_rtpack(app_handle: tauri::AppHandle, rtpack_path: String, selected_names: Vec) -> Result<(), String> { + if !rtpack_path.to_ascii_lowercase().ends_with(".rtpack") { return Err("Invalid file type; expected .rtpack".into()); } + let pack_name = Path::new(&rtpack_path).file_stem().and_then(|s| s.to_str()).ok_or("Invalid pack path")?.to_string(); + let out_dir = brtx_dir().join("packs").join(&pack_name); + extract_rtpack_to(Path::new(&rtpack_path), &out_dir)?; + let materials = find_materials(&out_dir); + if materials.is_empty() { return Err("No materials found in pack".into()); } + let all = list_installations(app_handle.clone()).await?; + let map: std::collections::HashMap<_, _> = all + .into_iter() + .map(|i| (i.install_location.clone(), i)) + .collect(); + for install_location in selected_names { + if let Some(ins) = map.get(&install_location) { + // Create a dummy pack for material file installation + let dummy_pack = PackInfo { + name: pack_name.clone().chars().take(20).collect(), + uuid: format!("material-files-{}", chrono::Utc::now().timestamp()), + stub: String::new(), + tonemapping: String::new(), + bloom: String::new(), + }; + copy_shader_files_async(&app_handle, &ins.install_location, &materials, &dummy_pack).await?; + } else { + println!("⚠ Skipping unknown selection (no matching installation): {}", install_location); + } + } + Ok(()) +} + +#[tauri::command] +async fn install_materials(app_handle: tauri::AppHandle, material_paths: Vec, selected_names: Vec) -> Result<(), String> { + if material_paths.is_empty() { return Err("No files provided".into()); } + let materials: Vec = material_paths.iter().map(PathBuf::from).collect(); + let all = list_installations(app_handle.clone()).await?; + let map: std::collections::HashMap<_, _> = all.into_iter().map(|i| (i.install_location.clone(), i)).collect(); + for install_location in selected_names { + if let Some(ins) = map.get(&install_location) { + // Create a dummy pack for material file installation + let dummy_pack = PackInfo { + name: "Material Files".to_string(), + uuid: "material-files".to_string(), + stub: String::new(), + tonemapping: String::new(), + bloom: String::new(), + }; + copy_shader_files_async(&app_handle, &ins.install_location, &materials, &dummy_pack).await?; + } else { + println!("⚠ Skipping unknown selection (no matching installation): {}", install_location); + } + } + Ok(()) +} + +fn backup_initial_shader_files(location: &str, backup_dir: &Path) -> Result<(), String> { + ensure_dir(backup_dir).map_err(|e| e.to_string())?; + let dlss = Path::new(location).join("nvngx_dlss.dll"); + if dlss.exists() { let _ = fs::copy(&dlss, backup_dir.join("nvngx_dlss.dll")); } + let materials = [ + "RTXStub.material.bin", + "RTXPostFX.Tonemapping.material.bin", + "RTXPostFX.Bloom.material.bin", + ]; + let mc_src = Path::new(location).join("data").join("renderer").join("materials"); + for m in materials { let src = mc_src.join(m); if src.exists() { let _ = fs::copy(&src, backup_dir.join(m)); } } + Ok(()) +} + +fn zip_dir(src_dir: &Path, dest_zip: &Path) -> Result<(), String> { + let file = File::create(dest_zip).map_err(|e| e.to_string())?; + let mut zip = zip::ZipWriter::new(file); + let options = zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Deflated); + let src = src_dir.to_path_buf(); + for entry in WalkDir::new(&src).into_iter().filter_map(Result::ok) { + let path = entry.path(); + let name = path.strip_prefix(&src).map_err(|e| e.to_string())?.to_string_lossy().replace('\\', "/"); + if path.is_file() { + zip.start_file(name, options).map_err(|e| e.to_string())?; + let mut f = File::open(path).map_err(|e| e.to_string())?; + std::io::copy(&mut f, &mut zip).map_err(|e| e.to_string())?; + } else if !name.is_empty() { + zip.add_directory(name + "/", options).map_err(|e| e.to_string())?; + } + } + zip.finish().map_err(|e| e.to_string())?; + Ok(()) +} + +#[tauri::command] +async fn backup_selected(app_handle: tauri::AppHandle, dest_dir: String, selected_names: Option>) -> Result, String> { + let dest = PathBuf::from(dest_dir); + if !dest.exists() { return Err("Destination directory does not exist".into()); } + let all = list_installations(app_handle).await?; + // UI sends InstallLocation values in selected_names + let targets: Vec = if let Some(names) = selected_names { + all + .into_iter() + .filter(|i| names.contains(&i.install_location)) + .collect() + } else { + all + }; + let mut created = Vec::new(); + for ins in targets { + let instance = ins.install_location.split(['\\', '/']).last().unwrap_or("instance").replace(' ', "_"); + let backup_dir = brtx_dir().join("backup").join(&ins.friendly_name); + ensure_dir(backup_dir.parent().unwrap()) .map_err(|e| e.to_string())?; + ensure_dir(&backup_dir).map_err(|e| e.to_string())?; + backup_initial_shader_files(&ins.install_location, &backup_dir)?; + let ts = Local::now().format("%Y-%m-%d_%H-%M"); + let zip_path = dest.join(format!("betterrtx_backup_{}_{}.zip", instance, ts)); + zip_dir(&backup_dir, &zip_path)?; + let rtpack = zip_path.with_extension("rtpack"); + fs::rename(&zip_path, &rtpack).map_err(|e| e.to_string())?; + // Clean temp backup dir + let _ = fs::remove_dir_all(&backup_dir); + created.push(rtpack.to_string_lossy().to_string()); + } + Ok(created) +} + +#[tauri::command] +async fn install_dlss_for_selected(app_handle: tauri::AppHandle, selected_names: Vec) -> Result<(), String> { + let dir = brtx_dir().join("dlss"); + if !dir.exists() { + ensure_dir(&dir).map_err(|e| e.to_string())?; + let client = Client::new(); + let versions: serde_json::Value = client.get("https://bedrock.graphics/api/dlss").send().await.map_err(|e| e.to_string())?.json().await.map_err(|e| e.to_string())?; + let latest = versions.get("latest").and_then(|v| v.as_str()).ok_or("Invalid DLSS API response")?; + let zip_path = dir.join("nvngx_dlss.zip"); + let resp = client.get(latest).send().await.map_err(|e| e.to_string())?; + let bytes = resp.bytes().await.map_err(|e| e.to_string())?; + tokio::fs::write(&zip_path, bytes).await.map_err(|e| e.to_string())?; + // extract + extract_rtpack_to(&zip_path, &dir).or_else(|_| { + // Some DLSS zips may not be .rtpack format; try normal zip extraction path + let file = File::open(&zip_path).map_err(|e| e.to_string())?; + let mut zip = zip::ZipArchive::new(file).map_err(|e| e.to_string())?; + for i in 0..zip.len() { + let mut file = zip.by_index(i).map_err(|e| e.to_string())?; + let out = dir.join(file.name()); + if file.name().ends_with('/') { ensure_dir(&out).map_err(|e| e.to_string())?; } else { + if let Some(par) = out.parent() { ensure_dir(par).map_err(|e| e.to_string())?; } + let mut of = File::create(&out).map_err(|e| e.to_string())?; + std::io::copy(&mut file, &mut of).map_err(|e| e.to_string())?; + } + } + Ok::<(), String>(()) + })?; + let _ = fs::remove_file(&zip_path); + } + + let all = list_installations(app_handle.clone()).await?; + let map: std::collections::HashMap<_, _> = all + .into_iter() + .map(|i| (i.install_location.clone(), i)) + .collect(); + for install_location in selected_names { + if let Some(ins) = map.get(&install_location) { + let src = dir.join("nvngx_dlss.dll"); + if !src.exists() { return Err("DLSS DLL not found".into()); } + let dest = Path::new(&ins.install_location).join("nvngx_dlss.dll"); + if is_sideloaded(&ins.install_location) { + fs::copy(&src, &dest).map_err(|e| e.to_string())?; + } else if let Some(ioexe) = get_iobit_unlocker_exe() { + // Use IObit via PowerShell to mirror quoting behavior from v2 + println!("Attempting to delete existing DLSS via IObit: {}", dest.display()); + let ioexe_ps = ioexe.display().to_string().replace("'", "''"); + let del_arglist = format!("/Delete \"{}\"", dest.display()); + let del_arglist_ps = del_arglist.replace("'", "''"); + let del_ps_cmd = format!( + "Start-Process -FilePath '{}' -ArgumentList '{}' -Wait -PassThru", + ioexe_ps, del_arglist_ps + ); + let out = app_handle.shell().command("powershell.exe") + .args(["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", &del_ps_cmd]) + .output() + .await + .map_err(|e| format!("Failed to run IObit Unlocker: {e}"))?; + if !out.status.success() { + let stderr = String::from_utf8_lossy(&out.stderr); + println!("IObit DLSS delete reported non-success for {}: {}", dest.display(), stderr); + } + println!("Copying DLSS via IObit: {} -> {}", src.display(), dest.display()); + let copy_arglist = format!("/Copy \"{}\" \"{}\"", src.display(), dest.display()); + let copy_arglist_ps = copy_arglist.replace("'", "''"); + let copy_ps_cmd = format!( + "Start-Process -FilePath '{}' -ArgumentList '{}' -Wait -PassThru", + ioexe_ps, copy_arglist_ps + ); + let out = app_handle.shell().command("powershell.exe") + .args(["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", ©_ps_cmd]) + .output() + .await + .map_err(|e| format!("Failed to run IObit Unlocker: {e}"))?; + if !out.status.success() { + let stderr = String::from_utf8_lossy(&out.stderr); + return Err(format!("IObit copy failed for {}: {}", src.display(), stderr)); + } + // Skipping post-copy file verification per user preference + } else { + return Err("IObit Unlocker not found; cannot update DLSS in WindowsApps".into()); + } + } else { + println!("⚠ Skipping unknown selection (no matching installation): {}", install_location); + } + } + Ok(()) +} + +fn update_options_file(path: &Path) -> Result<(), String> { + if !path.exists() { return Err(format!("Options file not found: {}", path.display())); } + let mut content = fs::read_to_string(path).map_err(|e| e.to_string())?; + if content.contains("show_advanced_video_settings:0") { + content = content.replace("show_advanced_video_settings:0", "show_advanced_video_settings:1"); + } else if !content.contains("show_advanced_video_settings:1") { + content.push_str("\nshow_advanced_video_settings:1"); + } + fs::write(path, content).map_err(|e| e.to_string()) +} + +#[tauri::command] +async fn update_options_for_selected(app_handle: tauri::AppHandle, selected_names: Vec) -> Result<(), String> { + let all = list_installations(app_handle.clone()).await?; + // Map by InstallLocation because the UI sends InstallLocation values + let map: std::collections::HashMap<_, _> = all + .into_iter() + .map(|i| (i.install_location.clone(), i)) + .collect(); + let com_mojang = local_app_data().join(r"Packages\Microsoft.MinecraftUWP_8wekyb3d8bbwe\LocalState\games\com.mojang\minecraftpe\options.txt"); + let preview_com_mojang = local_app_data().join(r"Packages\Microsoft.MinecraftPreview_8wekyb3d8bbwe\LocalState\games\com.mojang\minecraftpe\options.txt"); + for install_location in selected_names { + if let Some(ins) = map.get(&install_location) { + if ins.preview { update_options_file(&preview_com_mojang)?; } else { update_options_file(&com_mojang)?; } + } else { + println!("⚠ Skipping unknown selection (no matching installation): {}", install_location); + } + } + Ok(()) +} + +#[tauri::command] +fn is_brtx_protocol_registered() -> Result { + let hkcu = RegKey::predef(HKEY_CURRENT_USER); + let classes = hkcu.open_subkey("Software\\Classes").map_err(|e| e.to_string())?; + + match classes.open_subkey("brtx") { + Ok(brtx_key) => { + // Check if URL Protocol value exists + match brtx_key.get_value::("URL Protocol") { + Ok(_) => Ok(true), + Err(_) => Ok(false), + } + } + Err(_) => Ok(false), + } +} + +#[tauri::command] +fn register_brtx_protocol() -> Result<(), String> { + let hkcu = RegKey::predef(HKEY_CURRENT_USER); + let classes = hkcu.open_subkey_with_flags("Software\\Classes", KEY_ALL_ACCESS).map_err(|e| e.to_string())?; + + // Register brtx:// protocol + let brtx_key = classes.create_subkey("brtx").map_err(|e| e.to_string())?.0; + brtx_key.set_value("", &"URL:BetterRTX Protocol").map_err(|e| e.to_string())?; + brtx_key.set_value("URL Protocol", &"").map_err(|e| e.to_string())?; + + // Set icon + let icon_path = brtx_dir().join("brtx.ico"); + // Skipping network download of icon in sync context; path may be populated elsewhere. + let _ = brtx_key.set_value("DefaultIcon", &format!("{},0", icon_path.display())); + + // Shell open command -> this app exe + let exe = std::env::current_exe().map_err(|e| e.to_string())?; + let shell_open = brtx_key.create_subkey("shell\\open\\command").map_err(|e| e.to_string())?.0; + let cmd = format!("\"{}\" \"%1\"", exe.display()); + shell_open.set_value("", &cmd).map_err(|e| e.to_string())?; + + Ok(()) +} + +#[tauri::command] +fn register_rtpack_extension() -> Result<(), String> { + let hkcu = RegKey::predef(HKEY_CURRENT_USER); + let classes = hkcu.open_subkey_with_flags("Software\\Classes", KEY_ALL_ACCESS).map_err(|e| e.to_string())?; + // .rtpack -> BetterRTX.PackageFile + let rtpack_key = classes.create_subkey(".rtpack").map_err(|e| e.to_string())?.0; + rtpack_key.set_value("", &"BetterRTX.PackageFile").map_err(|e| e.to_string())?; + // BetterRTX.PackageFile + let app_key = classes.create_subkey("BetterRTX.PackageFile").map_err(|e| e.to_string())?.0; + app_key.set_value("", &"BetterRTX Preset").map_err(|e| e.to_string())?; + + // Icon file + let icon_path = brtx_dir().join("rtpack.ico"); + // Skipping network download of icon in sync context; path may be populated elsewhere. + let _ = app_key.set_value("DefaultIcon", &icon_path.to_string_lossy().to_string()); + + // Shell open command -> this app exe + let exe = std::env::current_exe().map_err(|e| e.to_string())?; + let shell_open = app_key.create_subkey("shell\\open\\command").map_err(|e| e.to_string())?.0; + let cmd = format!("\"{}\" \"%1\"", exe.display()); + shell_open.set_value("", &cmd).map_err(|e| e.to_string())?; + Ok(()) +} + +#[tauri::command] +fn check_iobit_unlocker() -> Result { + if let Some(path) = get_iobit_unlocker_exe() { + Ok(format!("IObit Unlocker found at: {}", path.display())) + } else { + Err("IObit Unlocker not found. Please install IObit Unlocker to modify WindowsApps installations.".into()) + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +struct MinecraftOptions { + gfx_options: HashMap, + instance_name: String, + options_path: String, +} + +#[tauri::command] +async fn get_minecraft_options() -> Result, String> { + let local_app_data = local_app_data(); + let packages_dir = local_app_data.join("Packages"); + + let minecraft_packages = [ + ("Microsoft.MinecraftUWP_8wekyb3d8bbwe", "Minecraft"), + ("Microsoft.MinecraftWindowsBeta_8wekyb3d8bbwe", "Minecraft Preview"), + ]; + + // Define which options are graphics-related + let graphics_related_keys = [ + "gfx_", // All gfx_ prefixed options + "show_advanced_video_settings", + "raytracing_viewdistance", + "graphics_mode", + "graphics_mode_switch", + "gfx_viewdistance", + "gfx_particleviewdistance", + "gfx_viewbobbing", + "gfx_fancygraphics", + "gfx_transparentleaves", + "gfx_smoothlighting", + "gfx_fancyskies", + "gfx_msaa", + "gfx_texel_aa", + "gfx_multithreaded_renderer", + "gfx_vsync", + "frame_pacing_enabled", + ]; + + let mut all_options = Vec::new(); + + for (package_name, friendly_name) in minecraft_packages { + let options_path = packages_dir + .join(package_name) + .join("LocalState") + .join("games") + .join("com.mojang") + .join("minecraftpe") + .join("options.txt"); + + if options_path.exists() { + match tokio::fs::read_to_string(&options_path).await { + Ok(content) => { + let mut gfx_options = HashMap::new(); + + for line in content.lines() { + if let Some((key, value)) = line.split_once(':') { + // Check if this key is graphics-related + let is_graphics = graphics_related_keys.iter().any(|&pattern| { + if pattern.ends_with('_') { + key.starts_with(pattern) + } else { + key == pattern + } + }); + + if is_graphics { + gfx_options.insert(key.to_string(), value.to_string()); + } + } + } + + all_options.push(MinecraftOptions { + gfx_options, + instance_name: friendly_name.to_string(), + options_path: options_path.to_string_lossy().to_string(), + }); + } + Err(e) => { + eprintln!("Failed to read options file for {}: {}", friendly_name, e); + } + } + } + } + + if all_options.is_empty() { + return Err("No Minecraft installations with options.txt found".into()); + } + + Ok(all_options) +} + +#[tauri::command] +async fn save_minecraft_options(options_path: String, gfx_options: HashMap) -> Result<(), String> { + let path = PathBuf::from(&options_path); + + if !path.exists() { + return Err(format!("Options file not found: {}", options_path)); + } + + // Read the current content + let content = tokio::fs::read_to_string(&path).await + .map_err(|e| format!("Failed to read options file: {}", e))?; + + let mut lines: Vec = Vec::new(); + let mut processed_keys = std::collections::HashSet::new(); + + // Update existing lines + for line in content.lines() { + if let Some((key, _)) = line.split_once(':') { + if key.starts_with("gfx_") { + // Update with new value if we have it + if let Some(new_value) = gfx_options.get(key) { + lines.push(format!("{}:{}", key, new_value)); + processed_keys.insert(key.to_string()); + } else { + // Keep the original line if not in our update map + lines.push(line.to_string()); + } + } else { + // Keep non-gfx options unchanged + lines.push(line.to_string()); + } + } else { + // Keep lines that aren't key:value pairs + lines.push(line.to_string()); + } + } + + // Add any new gfx_ options that weren't in the file + for (key, value) in &gfx_options { + if !processed_keys.contains(key) { + lines.push(format!("{}:{}", key, value)); + } + } + + // Write back to file + let new_content = lines.join("\n"); + tokio::fs::write(&path, new_content).await + .map_err(|e| format!("Failed to write options file: {}", e))?; + + Ok(()) +} + +#[tauri::command] +fn set_iobit_path(path: String) -> Result { + let path_buf = PathBuf::from(&path); + + // Validate the path exists and is an executable + if !path_buf.exists() { + return Err("The specified file does not exist.".into()); + } + + // Check if it's IObitUnlocker.exe or a shortcut to it + let file_name = path_buf.file_name() + .and_then(|n| n.to_str()) + .unwrap_or("") + .to_lowercase(); + + if !file_name.contains("iobitunlocker") && !file_name.ends_with(".lnk") { + return Err("Please select IObitUnlocker.exe or a shortcut to it.".into()); + } + + // Save the path to config file + let config_dir = brtx_dir(); + ensure_dir(&config_dir).map_err(|e| format!("Failed to create config directory: {e}"))?; + let config_file = config_dir.join("iobit_path.txt"); + fs::write(&config_file, &path).map_err(|e| format!("Failed to save IObit path: {e}"))?; + + Ok(format!("IObit Unlocker path set to: {}", path)) +} + +#[tauri::command] +fn handle_file_drop(_paths: Vec) -> Result<(), String> { + // This command will be called from the frontend when files are dropped + // The frontend will handle the actual file drop event and call this command + // We don't need to do anything here as the frontend handles the logic + Ok(()) +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +struct ProtocolData { + protocol_type: String, // "preset" or "creator" + id: String, +} + +#[tauri::command] +fn handle_deep_link(url: String) -> Result { + let parsed_url = Url::parse(&url).map_err(|e| format!("Invalid URL: {}", e))?; + + if parsed_url.scheme() != "brtx" { + return Err("Invalid protocol scheme".to_string()); + } + + let path = parsed_url.path(); + let parts: Vec<&str> = path.trim_start_matches('/').split('/').collect(); + + if parts.len() != 2 { + return Err("Invalid URL format. Expected brtx://preset/[uuid] or brtx://creator/[hash]".to_string()); + } + + let protocol_type = parts[0].to_string(); + let id = parts[1].to_string(); + + if protocol_type != "preset" && protocol_type != "creator" { + return Err("Invalid protocol type. Expected 'preset' or 'creator'".to_string()); + } + + Ok(ProtocolData { protocol_type, id }) +} + +#[tauri::command] +async fn download_preset_by_uuid(app_handle: tauri::AppHandle, uuid: String, selected_names: Vec) -> Result<(), String> { + // Get preset info from API + let url = format!("https://bedrock.graphics/api/preset/{}", uuid); + let client = Client::new(); + let response = client.get(&url).send().await.map_err(|e| e.to_string())?; + let preset: PackInfo = response.json().await.map_err(|e| e.to_string())?; + + // Use existing download and install logic + download_and_install_pack(app_handle, preset.uuid, selected_names).await +} + +#[tauri::command] +fn get_brtx_dir() -> Result { + let dir = brtx_dir(); + dir.to_str() + .ok_or_else(|| "Invalid path".to_string()) + .map(|s| s.to_string()) +} + + +#[tauri::command] +async fn download_creator_settings( + app_handle: tauri::AppHandle, + settings_hash: String, + selected_names: Vec, + preset_name: Option, + uuid: Option +) -> Result<(), String> { + let base_url = format!("https://bedrock.graphics/build/{}", settings_hash); + let dir = brtx_dir().join("creator").join(&settings_hash); + ensure_dir(&dir).map_err(|e| e.to_string())?; + + let client = Client::new(); + + // Download the three material.bin files directly + let stub_url = format!("{}/{}", base_url, "stubs/RTXStub.material.bin"); + let stub_path = dir.join("RTXStub.material.bin"); + download_to_file_with_cache(&client, &stub_url, &stub_path).await?; + let tone_url = format!("{}/{}", base_url, "RTXPostFX.Tonemapping.material.bin"); + let tone_path = dir.join("RTXPostFX.Tonemapping.material.bin"); + download_to_file_with_cache(&client, &tone_url, &tone_path).await?; + let bloom_url = format!("{}/{}", base_url, "RTXPostFX.Bloom.material.bin"); + let bloom_path = dir.join("RTXPostFX.Bloom.material.bin"); + download_to_file_with_cache(&client, &bloom_url, &bloom_path).await?; + + // Install the downloaded materials + let materials = vec![ + dir.join("RTXStub.material.bin"), + dir.join("RTXPostFX.Tonemapping.material.bin"), + dir.join("RTXPostFX.Bloom.material.bin"), + ]; + + let all = list_installations(app_handle.clone()).await?; + let map: std::collections::HashMap<_, _> = all + .into_iter() + .map(|i| (i.install_location.clone(), i)) + .collect(); + + // Use provided name or fallback to hash-based name + let display_name = preset_name.unwrap_or_else(|| { + let short_hash = settings_hash.get(0..8).unwrap_or(&settings_hash); + format!("Creator Settings ({})", short_hash) + }); + + // Use provided UUID or generate one from hash + let creator_uuid = uuid.unwrap_or_else(|| format!("creator-{}", settings_hash)); + + for install_location in selected_names { + if let Some(ins) = map.get(&install_location) { + let creator_pack = PackInfo { + name: display_name.clone(), + uuid: creator_uuid.clone(), + stub: String::new(), + tonemapping: String::new(), + bloom: String::new(), + }; + + // Install materials using existing infrastructure + copy_shader_files_async(&app_handle, &ins.install_location, &materials, &creator_pack).await?; + + // Override the saved preset to mark it as creator-made + let creator_preset = InstalledPreset { + uuid: creator_uuid.clone(), + name: display_name.clone(), + installed_at: chrono::Utc::now().to_rfc3339(), + is_creator: Some(true), + }; + + if let Err(e) = save_installed_preset(&ins.install_location, &creator_preset) { + println!("⚠ Failed to save creator preset tracking: {}", e); + } + } else { + println!("⚠ Skipping unknown selection (no matching installation): {}", install_location); + } + } + + Ok(()) +} + +#[tauri::command] +async fn install_uploaded_materials( + app_handle: tauri::AppHandle, + selected_names: Vec, + preset_name: Option +) -> Result<(), String> { + let uploaded_dir = brtx_dir().join("creator").join("uploaded"); + + // Get all uploaded material files + let materials: Vec = if uploaded_dir.exists() { + fs::read_dir(&uploaded_dir) + .map_err(|e| e.to_string())? + .filter_map(|entry| { + let entry = entry.ok()?; + let path = entry.path(); + if path.is_file() && path.extension()?.to_str()? == "bin" { + Some(path) + } else { + None + } + }) + .collect() + } else { + return Err("No uploaded material files found".to_string()); + }; + + if materials.is_empty() { + return Err("No material files to install".to_string()); + } + + let all = list_installations(app_handle.clone()).await?; + let map: std::collections::HashMap<_, _> = all + .into_iter() + .map(|i| (i.install_location.clone(), i)) + .collect(); + + let display_name = preset_name.unwrap_or_else(|| "Uploaded Materials".to_string()); + let material_uuid = format!("materials-{}", chrono::Utc::now().timestamp()); + + for install_location in selected_names { + if let Some(ins) = map.get(&install_location) { + let material_pack = PackInfo { + name: display_name.clone(), + uuid: material_uuid.clone(), + stub: String::new(), + tonemapping: String::new(), + bloom: String::new(), + }; + + // Install materials using existing infrastructure + copy_shader_files_async(&app_handle, &ins.install_location, &materials, &material_pack).await?; + + // Save as creator preset + let creator_preset = InstalledPreset { + uuid: material_uuid.clone(), + name: display_name.clone(), + installed_at: chrono::Utc::now().to_rfc3339(), + is_creator: Some(true), + }; + + if let Err(e) = save_installed_preset(&ins.install_location, &creator_preset) { + println!("⚠ Failed to save material preset tracking: {}", e); + } + } else { + println!("⚠ Skipping unknown selection (no matching installation): {}", install_location); + } + } + + Ok(()) +} + +#[tauri::command] +async fn upload_material_file(source_path: String) -> Result { + // Extract filename from source path + let source = Path::new(&source_path); + let filename = source.file_name() + .and_then(|name| name.to_str()) + .ok_or("Invalid filename")?; + + // Create target directory + let target_dir = brtx_dir().join("creator").join("uploaded"); + ensure_dir(&target_dir).map_err(|e| e.to_string())?; + + // Copy file to target directory + let target_path = target_dir.join(filename); + fs::copy(&source, &target_path).map_err(|e| e.to_string())?; + + Ok(filename.to_string()) +} + +#[tauri::command] +fn validate_minecraft_path(path: String) -> Result { + let minecraft_path = Path::new(&path); + + // Check if path exists + if !minecraft_path.exists() { + return Ok(false); + } + + // Check for common Minecraft files/directories + let required_paths = [ + "data", + "AppxManifest.xml", // For UWP apps + "Minecraft.exe", // For some installations + ]; + + // At least one of these should exist for a valid Minecraft installation + let has_minecraft_indicators = required_paths.iter().any(|&p| { + minecraft_path.join(p).exists() + }); + + // Also check for materials directory structure + let materials_dir = minecraft_path.join("data").join("renderer").join("materials"); + let has_materials_structure = materials_dir.exists() || minecraft_path.join("data").exists(); + + Ok(has_minecraft_indicators || has_materials_structure) +} + +#[tauri::command] +fn open_folder_dialog(app: tauri::AppHandle) -> Result, String> { + let result = app + .dialog() + .file() + .set_title("Select Minecraft Installation Folder") + .blocking_pick_folder(); + + match result { + Some(path) => Ok(Some(path.to_string())), + None => Ok(None), + } +} + +#[tauri::command] +fn open_iobit_file_dialog(app: tauri::AppHandle) -> Result, String> { + let result = app + .dialog() + .file() + .set_title("Select IObit Unlocker Executable") + .add_filter("Executable Files", &["exe"]) + .add_filter("Shortcut Files", &["lnk"]) + .add_filter("All Files", &["*"]) + .blocking_pick_file(); + + match result { + Some(path) => Ok(Some(path.to_string())), + None => Ok(None), + } +} + +#[tauri::command] +fn get_iobit_path() -> Result, String> { + if let Some(path) = get_iobit_path_cached() { + Ok(Some(path.to_string_lossy().to_string())) + } else { + Ok(None) + } +} + +#[tauri::command] +async fn uninstall_rtx(app_handle: tauri::AppHandle, selected_names: Vec) -> Result<(), String> { + // Download original material.bin files from the uninstall API endpoints + let dir = brtx_dir().join("uninstall"); + ensure_dir(&dir).map_err(|e| e.to_string())?; + let client = Client::new(); + + // Download original files with caching + let stub_path = dir.join("RTXStub.material.bin"); + download_to_file_with_cache(&client, "https://bedrock.graphics/api/uninstall/rtxstub", &stub_path).await?; + let tone_path = dir.join("RTXPostFX.Tonemapping.material.bin"); + download_to_file_with_cache(&client, "https://bedrock.graphics/api/uninstall/rtxpostfx", &tone_path).await?; + let bloom_path = dir.join("RTXPostFX.Bloom.material.bin"); + download_to_file_with_cache(&client, "https://bedrock.graphics/api/uninstall/bloom", &bloom_path).await?; + + let materials = vec![ + stub_path.clone(), + tone_path.clone(), + bloom_path.clone(), + ]; + + let all = list_installations(app_handle.clone()).await?; + let map: std::collections::HashMap<_, _> = all + .into_iter() + .map(|i| (i.install_location.clone(), i)) + .collect(); + + for install_location in selected_names { + if let Some(ins) = map.get(&install_location) { + // Create a dummy pack for uninstall + let uninstall_pack = PackInfo { + name: "Original Files".to_string(), + uuid: "uninstall-original".to_string(), + stub: String::new(), + tonemapping: String::new(), + bloom: String::new(), + }; + copy_shader_files_async(&app_handle, &ins.install_location, &materials, &uninstall_pack).await?; + + // Remove the installed preset tracking for this installation + let presets_file = brtx_dir().join("installed_presets.json"); + if let Some(mut installations) = read_json_file::>(&presets_file) { + installations.remove(&install_location); + let _ = write_json_file(&presets_file, &installations); + } + } else { + println!("⚠ Skipping unknown selection (no matching installation): {}", install_location); + } + } + Ok(()) +} + +#[tauri::command] +async fn uninstall_package(app_handle: tauri::AppHandle, restore_initial: bool) -> Result<(), String> { + if restore_initial { + let all = list_installations(app_handle.clone()).await?; + for ins in all { + let backup = brtx_dir().join("backup").join(&ins.friendly_name); + if backup.exists() { + let materials = vec![ + backup.join("RTXStub.material.bin"), + backup.join("RTXPostFX.Tonemapping.material.bin"), + backup.join("RTXPostFX.Bloom.material.bin"), + ]; + let existing: Vec = materials.into_iter().filter(|p| p.exists()).collect(); + if !existing.is_empty() { + let dummy_pack = PackInfo { + name: "Backup Restore".to_string(), + uuid: "backup-restore".to_string(), + stub: String::new(), + tonemapping: String::new(), + bloom: String::new(), + }; + copy_shader_files_async(&app_handle, &ins.install_location, &existing, &dummy_pack).await?; + } + } + } + } + + // Clear installed presets tracking + let presets_file = brtx_dir().join("installed_presets.json"); + if presets_file.exists() { + let _ = fs::remove_file(&presets_file); + } + + let _ = fs::remove_dir_all(brtx_dir()); + Ok(()) +} + +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + tauri::Builder::default() + .plugin(tauri_plugin_opener::init()) + .plugin(tauri_plugin_dialog::init()) + .plugin(tauri_plugin_upload::init()) + .plugin(tauri_plugin_fs::init()) + .plugin(tauri_plugin_shell::init()) + .setup(|app| { + // Handle command line arguments for file associations and deep links + let args: Vec = std::env::args().collect(); + if args.len() > 1 { + let arg = &args[1]; + if arg.to_lowercase().ends_with(".rtpack") && std::path::Path::new(arg).exists() { + let _ = app.emit("rtpack-file-opened", arg); + } else if arg.starts_with("brtx://") { + let _ = app.emit("deep-link-received", arg); + } + } + + Ok(()) + }) + .invoke_handler(tauri::generate_handler![ + list_installations, + get_api_packs, + list_presets, + download_and_install_pack, + install_from_rtpack, + install_materials, + backup_selected, + install_dlss_for_selected, + update_options_for_selected, + register_rtpack_extension, + register_brtx_protocol, + is_brtx_protocol_registered, + check_iobit_unlocker, + get_minecraft_options, + save_minecraft_options, + set_iobit_path, + get_iobit_path, + open_iobit_file_dialog, + handle_file_drop, + uninstall_package, + uninstall_rtx, + clear_cache, + get_cache_info, + handle_deep_link, + download_preset_by_uuid, + download_creator_settings, + upload_material_file, + install_uploaded_materials, + get_brtx_dir, + validate_minecraft_path, + open_folder_dialog, + ]) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} diff --git a/v3/src-tauri/src/main.rs b/v3/src-tauri/src/main.rs new file mode 100644 index 0000000..f0d227b --- /dev/null +++ b/v3/src-tauri/src/main.rs @@ -0,0 +1,6 @@ +// Prevents additional console window on Windows in release, DO NOT REMOVE!! +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +fn main() { + brtx_installer_lib::run(); +} diff --git a/v3/src-tauri/tauri.conf.json b/v3/src-tauri/tauri.conf.json new file mode 100644 index 0000000..2490a2f --- /dev/null +++ b/v3/src-tauri/tauri.conf.json @@ -0,0 +1,47 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "productName": "BetterRTX Installer", + "version": "3.0.0", + "identifier": "graphics.bedrock.brtx", + "build": { + "beforeDevCommand": "bun run dev", + "devUrl": "http://localhost:1420", + "beforeBuildCommand": "bun run build", + "frontendDist": "../dist" + }, + "app": { + "withGlobalTauri": true, + "windows": [ + { + "title": "BetterRTX Installer", + "width": 800, + "height": 600, + "minWidth": 512, + "minHeight": 480, + "resizable": true, + "theme": "Dark", + "dragDropEnabled": true + } + ], + "security": { + "csp": null + } + }, + "bundle": { + "active": true, + "targets": "all", + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ], + "fileAssociations": [ + { + "ext": [".rtpack"], + "description": "BetterRTX Preset File" + } + ] + } +} diff --git a/v3/src/components/App.tsx b/v3/src/components/App.tsx new file mode 100644 index 0000000..cb6f151 --- /dev/null +++ b/v3/src/components/App.tsx @@ -0,0 +1,208 @@ +import React, { memo, useCallback, useEffect, useMemo, useState } from "react"; +import { listen } from "@tauri-apps/api/event"; +import { getCurrentWebview } from "@tauri-apps/api/webview"; + +import { StatusBarContainer } from "./StatusBar"; +import { ConsolePanel } from "./ConsolePanel"; +import { RtpackDialog } from "./RtpackDialog"; +import { DeepLinkDialog } from "./DeepLinkDialog"; +import { useAppStore } from "../store/appStore"; +import AppHeader from "./AppHeader"; +import ActionsTab from "./actions/ActionsTab"; +import PresetsTab from "./presets/PresetsTab"; +import CreatorTab from "./creator/CreatorTab"; +import { SideNav } from "./ui/SideNav"; +import InstallationNav from "./installations/InstallationNav"; +import InstallationsTab from "./installations/InstallationsTab"; +import DropzoneIndicator from "./ui/DropzoneIndicator"; +import { cx } from "classix"; + +const App: React.FC = () => { + const [rtpackDialogOpen, setRtpackDialogOpen] = useState(false); + const [rtpackPath, setRtpackPath] = useState(""); + const [deepLinkDialogOpen, setDeepLinkDialogOpen] = useState(false); + const [deepLinkUrl, setDeepLinkUrl] = useState(""); + const [isDragging, setIsDragging] = useState(false); + const [animateTabs, setAnimateTabs] = useState(false); + const [isConsoleExpanded, setIsConsoleExpanded] = useState(false); + const { + consoleOutput, + activeTab, + clearConsole, + refreshInstallations, + refreshPresets, + } = useAppStore(); + + const handleRtpackDialogClose = useCallback(() => { + setRtpackDialogOpen(false); + }, []); + + const handleDeepLinkDialogClose = useCallback(() => { + setDeepLinkDialogOpen(false); + }, []); + + const tabClasses = useMemo(() => { + const getBaseClass = (tabName: string) => { + const isActive = activeTab === tabName; + return cx( + "tab-panel w-full", + isActive ? (animateTabs ? "block tab-view" : "block") : "hidden" + ); + }; + + return { + installations: getBaseClass("installations"), + presets: getBaseClass("presets"), + actions: getBaseClass("actions"), + creator: getBaseClass("creator"), + }; + }, [activeTab, animateTabs]); + + useEffect(() => { + refreshInstallations(); + refreshPresets(); + }, [refreshInstallations, refreshPresets]); + + // Listen for rtpack file open events + useEffect(() => { + const unlisten = listen("rtpack-file-opened", (event) => { + // In Tauri v2, the payload is accessed directly from the event + const filePath = typeof event.payload === 'string' ? event.payload : ''; + console.log('RTpack file opened:', filePath); + setRtpackPath(filePath); + setRtpackDialogOpen(true); + }); + + return () => { + unlisten.then((fn) => fn()); + }; + }, []); + + // Enable tab transition animations after initial mount only + useEffect(() => { + setAnimateTabs(true); + }, []); + + // Listen for deep link protocol events + useEffect(() => { + const unlisten = listen("deep-link-received", (event) => { + // In Tauri v2, the payload is accessed directly from the event + const url = typeof event.payload === 'string' ? event.payload : ''; + console.log('Deep link received:', url); + setDeepLinkUrl(url); + setDeepLinkDialogOpen(true); + }); + + return () => { + unlisten.then((fn) => fn()); + }; + }, []); + + // Handle drag-n-drop indicator and file drops + useEffect(() => { + let unlisten: (() => void) | undefined; + (async () => { + const webview = getCurrentWebview(); + unlisten = await webview.onDragDropEvent((event) => { + if (event.payload.type === 'enter') { + setIsDragging(true); + } else if (event.payload.type === 'drop') { + setIsDragging(false); + const paths = event.payload.paths || []; + for (const path of paths) { + if (path.toLowerCase().endsWith(".rtpack")) { + setRtpackPath(path); + setRtpackDialogOpen(true); + break; + } + } + } else if (event.payload.type === 'leave') { + setIsDragging(false); + } + }); + })(); + return () => { + unlisten?.(); + }; + }, []); + + return ( +
+ + + {/* Top Header */} + +
+ {/* Sidebar Navigation */} + + + {/* Main Application Area */} +
+ + + {/* Main Content Area */} +
+
+ {/* Tab Content */} +
+
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+
+ + {/* Fixed Console Panel at bottom */} +
+ +
+
+ + {/* RTpack Dialog */} + + + {/* Deep Link Dialog */} + +
+
+ ); +}; + +export const MemoizedApp = memo(App); diff --git a/v3/src/components/AppHeader.tsx b/v3/src/components/AppHeader.tsx new file mode 100644 index 0000000..f811783 --- /dev/null +++ b/v3/src/components/AppHeader.tsx @@ -0,0 +1,112 @@ +import { useAppStore } from "../store/appStore"; +import { useTranslation } from "react-i18next"; +import { Settings, X } from "lucide-react"; +import { ToolbarSection } from "./ToolbarSection"; +import Logo from "./Logo"; +import pkg from "../../package.json"; +import { open } from "@tauri-apps/plugin-shell"; +import cx from "classix"; +import { useStatusStore } from "../store/statusStore"; +import { memo, useCallback, useMemo } from "react"; +import LanguageSelector from "./LanguageSelector"; + +interface AppHeaderProps {} + +function AppHeader({}: AppHeaderProps) { + const { toolbarOpen, setToolbarOpen, addConsoleOutput } = useAppStore(); + const { t, i18n } = useTranslation(); + const { addMessage } = useStatusStore(); + + // Memoize version string to prevent unnecessary re-renders + const versionString = useMemo(() => { + return t('installer_version', { version: pkg.version }); + }, [t, pkg.version]); + + // Memoize toolbar button icon class + const toolbarButtonClass = useMemo(() => { + return cx("toolbar-menu-btn border", toolbarOpen ? "border-app-border" : "border-transparent"); + }, [toolbarOpen]); + + const handleVersionClick = useCallback(async () => { + try { + await open(`https://github.com/BetterRTX/BetterRTX-Installer/releases/tag/v${pkg.version}`); + } catch (error) { + console.error('Failed to open release page:', error); + } + }, []); + + const handleLanguageChange = useCallback((e: React.ChangeEvent) => { + i18n.changeLanguage(e.target.value, (err) => { + if (err) { + addConsoleOutput('Failed to change language: ' + err); + addMessage({ + type: 'error', + message: t('language_change_error', 'Failed to change language'), + }); + } + }); + }, [i18n, addConsoleOutput, addMessage, t]); + + const handleToggleToolbar = useCallback(() => { + setToolbarOpen(!toolbarOpen); + }, [toolbarOpen, setToolbarOpen]); + + const handleLogoClick = useCallback(async () => { + try { + await open(`https://bedrock.graphics`); + } catch (error) { + addConsoleOutput('Failed to launch bedrock.graphics: ' + error); + addMessage({ + type: 'error', + message: t('bedrock_graphics_open_error', 'Failed to launch bedrock.graphics'), + }); + } + }, []); + + return ( +
+
+ +
+ +
+ + {/* Menu button to show toolbar */} + + + {/* Toolbar popover */} + + + +
+
+ ); +} + +export default memo(AppHeader); diff --git a/v3/src/components/ConsolePanel.tsx b/v3/src/components/ConsolePanel.tsx new file mode 100644 index 0000000..d029834 --- /dev/null +++ b/v3/src/components/ConsolePanel.tsx @@ -0,0 +1,81 @@ +import React, { useRef, useEffect, useCallback } from "react"; +import { ChevronDown } from "lucide-react"; +import { cx } from "classix"; + +interface ConsolePanelProps { + isExpanded: boolean; + onToggle: (isExpanded: boolean) => void; + output?: string[]; + onClear?: () => void; +} + +export const ConsolePanel: React.FC = ({ + isExpanded, + onToggle, + output = [], + onClear, +}) => { + const outputRef = useRef(null); + + // Auto-scroll to bottom when new output is added + useEffect(() => { + if (outputRef.current) { + outputRef.current.scrollTop = outputRef.current.scrollHeight; + } + }, [output]); + + const handleToggle = useCallback((): void => { + onToggle(!isExpanded); + }, [isExpanded, onToggle]); + + const handleClear = useCallback((): void => { + onClear?.(); + }, [onClear]); + + return ( +
+
+

+ Console Output +

+ + + +
+ +
+
+ {output.length > 0 ? output.join("\n") : "No output yet..."} + + +
+
+
+ ); +}; diff --git a/v3/src/components/DeepLinkDialog.tsx b/v3/src/components/DeepLinkDialog.tsx new file mode 100644 index 0000000..3f93615 --- /dev/null +++ b/v3/src/components/DeepLinkDialog.tsx @@ -0,0 +1,183 @@ +import React, { useState, useEffect, useCallback } from "react"; +import { invoke } from "@tauri-apps/api/core"; +import Button from "./ui/Button"; +import { useAppStore } from "../store/appStore"; +import { useStatusStore } from "../store/statusStore"; +import { X } from "lucide-react"; + +interface DeepLinkDialogProps { + isOpen: boolean; + deepLinkUrl: string; + onClose: () => void; +} + +interface ProtocolData { + protocol_type: string; + id: string; +} + +export const DeepLinkDialog: React.FC = ({ + isOpen, + deepLinkUrl, + onClose, +}) => { + const { selectedInstallations, installations } = useAppStore(); + const { addMessage } = useStatusStore(); + const [protocolData, setProtocolData] = useState(null); + const [isProcessing, setIsProcessing] = useState(false); + const [error, setError] = useState(null); + + const parseDeepLink = useCallback(async (): Promise => { + try { + setError(null); + const url = new URL(deepLinkUrl); + const data = await invoke("handle_deep_link", { + url: url.toString(), + }); + setProtocolData(data); + } catch (err) { + setError(String(err)); + } + }, [deepLinkUrl]); + + useEffect(() => { + if (isOpen && deepLinkUrl) { + void parseDeepLink(); + } + }, [isOpen, deepLinkUrl, parseDeepLink]); + + const handleInstall = useCallback(async (): Promise => { + if (!protocolData || selectedInstallations.size === 0) { + addMessage({ + message: "Please select at least one installation", + type: "error", + }); + return; + } + setIsProcessing(true); + try { + const selectedNames = Array.from(selectedInstallations); + if (protocolData.protocol_type === "preset") { + await invoke("download_preset_by_uuid", { + uuid: protocolData.id, + selectedNames, + }); + addMessage({ + message: `Successfully installed preset ${protocolData.id}`, + type: "success", + }); + } else if (protocolData.protocol_type === "creator") { + await invoke("download_creator_settings", { + settingsHash: protocolData.id, + selectedNames, + }); + addMessage({ + message: `Successfully installed creator settings ${protocolData.id.slice( + 0, + 8 + )}...`, + type: "success", + }); + } + onClose(); + } catch (err) { + addMessage({ message: `Installation failed: ${err}`, type: "error" }); + } finally { + setIsProcessing(false); + } + }, [addMessage, onClose, protocolData, selectedInstallations]); + + if (!isOpen) return null; + + return ( +
+
+
+

+ {protocolData?.protocol_type === "preset" + ? "Install Preset" + : "Install Creator Settings"} +

+ +
+ +
+ {error ? ( +
+

Error parsing deep link:

+ {error} +
+ ) : protocolData ? ( +
+
+

Protocol URL:

+ + {deepLinkUrl} + +
+ +
+

+ Type: {protocolData.protocol_type} +

+

+ ID: {protocolData.id} +

+
+ +
+

+ Selected installations ({selectedInstallations.size}): +

+ {selectedInstallations.size === 0 ? ( +

+ No installations selected. Please go to the Installations + tab and select target installations. +

+ ) : ( +
    + {Array.from(selectedInstallations).map((path) => { + const installation = installations.find( + (i) => i.InstallLocation === path + ); + return ( +
  • + {installation?.FriendlyName || path} +
  • + ); + })} +
+ )} +
+
+ ) : ( +
+

Parsing deep link...

+
+ )} +
+ +
+ + +
+
+
+ ); +}; diff --git a/v3/src/components/Disclaimer.tsx b/v3/src/components/Disclaimer.tsx new file mode 100644 index 0000000..c36196d --- /dev/null +++ b/v3/src/components/Disclaimer.tsx @@ -0,0 +1,18 @@ +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { cx } from "classix"; +import { X, ChevronDown } from "lucide-react"; + +export default function Disclaimer() { + const { t } = useTranslation(); + const [collapsed, setCollapsed] = useState(false); + return ( +
+
setCollapsed(!collapsed)} role="button" aria-expanded={!collapsed}> +

{t("disclaimer")}

+ {collapsed ? : } +
+

{t("disclaimer_text")}

+
+ ); +} \ No newline at end of file diff --git a/v3/src/components/LanguageSelector.tsx b/v3/src/components/LanguageSelector.tsx new file mode 100644 index 0000000..54491c1 --- /dev/null +++ b/v3/src/components/LanguageSelector.tsx @@ -0,0 +1,37 @@ +import React, { memo } from 'react'; +import { Globe } from 'lucide-react'; + +interface LanguageSelectorProps { + currentLanguage: string; + onLanguageChange: (e: React.ChangeEvent) => void; + translatorDebugLabel: string; +} + +function LanguageSelector({ + currentLanguage, + onLanguageChange, + translatorDebugLabel +}: LanguageSelectorProps) { + return ( +
+ + +
+ ); +} + +export default memo(LanguageSelector); diff --git a/v3/src/components/Logo.tsx b/v3/src/components/Logo.tsx new file mode 100644 index 0000000..5c5063c --- /dev/null +++ b/v3/src/components/Logo.tsx @@ -0,0 +1,24 @@ +import { cx } from "classix"; +const Logo = (props: React.SVGProps) => ( + + + + + +); + +export default Logo; diff --git a/v3/src/components/OptionsDialog.tsx b/v3/src/components/OptionsDialog.tsx new file mode 100644 index 0000000..b01fce1 --- /dev/null +++ b/v3/src/components/OptionsDialog.tsx @@ -0,0 +1,385 @@ +import React, { useState, useEffect } from 'react'; +import { invoke } from '@tauri-apps/api/core'; +import { useTranslation } from 'react-i18next'; +import Button from './ui/Button'; +import Switch from './ui/Switch'; +import { Loader2, Save, RefreshCw, X, Hash, Search, AlertTriangle } from 'lucide-react'; + +interface MinecraftOptions { + gfx_options: Record; + instance_name: string; + options_path: string; +} + +interface OptionsDialogProps { + isOpen: boolean; + onClose: () => void; +} + +// Define which options should use numeric input +const NUMERIC_OPTIONS = [ + 'raytracing_viewdistance', + 'gfx_viewdistance', + 'gfx_particleviewdistance', + 'gfx_msaa', + 'gfx_max_framerate', + 'gfx_guiscale_offset', + 'gfx_splitscreen_guiscale_offset', + 'gfx_field_of_view', + 'gfx_gamma', + 'gfx_gui_accessibility_scaling', + 'graphics_mode', + 'deferred_viewdistance', + 'upscaling_percentage', + 'upscaling_mode', +]; + +// Define option metadata for better UX +const OPTION_METADATA: Record = { + // Ray Tracing & Rendering + 'raytracing_viewdistance': { label: 'Ray Tracing Render Distance', min: 4, max: 96, step: 4, description: 'Chunks rendered with ray tracing' }, + 'deferred_viewdistance': { label: 'Deferred Rendering Distance', min: 4, max: 96, step: 4, description: 'Chunks rendered with deferred rendering' }, + + // View Distance & Performance + 'gfx_viewdistance': { label: 'Render Distance', min: 4, max: 96, step: 4, description: 'Number of chunks visible in each direction' }, + 'gfx_particleviewdistance': { label: 'Particle Render Distance', min: 0, max: 100, step: 10, description: 'Maximum distance for particle effects' }, + + // Graphics Quality + 'gfx_msaa': { label: 'Anti-Aliasing (MSAA)', min: 0, max: 16, step: 1, description: 'Smooths jagged edges (0=Off, higher=smoother)' }, + 'gfx_texel_aa': { label: 'Texel Anti-Aliasing', description: 'Reduces texture shimmering at distances' }, + 'graphics_mode': { label: 'Graphics Quality', min: 0, max: 2, step: 1, description: '0=Fast, 1=Fancy, 2=Fabulous' }, + 'graphics_mode_switch': { label: 'Graphics Mode Toggle', description: 'Allow switching graphics modes' }, + + // Display Settings + 'gfx_fullscreen': { label: 'Fullscreen Mode', description: 'Run game in fullscreen' }, + 'gfx_vsync': { label: 'Vertical Sync', description: 'Sync framerate with monitor refresh rate' }, + 'gfx_max_framerate': { label: 'Max FPS Limit', min: 0, max: 240, step: 10, description: 'Maximum frames per second (0=Unlimited)' }, + 'frame_pacing_enabled': { label: 'Frame Pacing', description: 'Smooth out frame delivery timing' }, + + // UI & Accessibility + 'gfx_field_of_view': { label: 'FOV', min: 30, max: 110, step: 1, description: 'Field of view in degrees' }, + 'gfx_gamma': { label: 'Brightness', min: 0, max: 100, step: 1, description: 'Screen brightness level' }, + 'gfx_guiscale_offset': { label: 'GUI Scale', min: -10, max: 10, step: 1, description: 'Interface size adjustment' }, + 'gfx_splitscreen_guiscale_offset': { label: 'Split-Screen GUI Scale', min: -10, max: 10, step: 1, description: 'GUI scale for split-screen mode' }, + 'gfx_gui_accessibility_scaling': { label: 'Accessibility GUI Scale', min: 0, max: 5, step: 1, description: 'Additional GUI scaling for accessibility' }, + 'show_advanced_video_settings': { label: 'Advanced Video Settings', description: 'Show advanced graphics options in menu' }, + + // Visual Effects + 'gfx_viewbobbing': { label: 'View Bobbing', description: 'Camera sway when walking' }, + 'gfx_fancygraphics': { label: 'Fancy Graphics', description: 'Enhanced visual effects' }, + 'gfx_transparentleaves': { label: 'Transparent Leaves', description: 'See-through leaf blocks' }, + 'gfx_smoothlighting': { label: 'Smooth Lighting', description: 'Gradient lighting between blocks' }, + 'gfx_fancyskies': { label: 'Beautiful Skies', description: 'Enhanced sky rendering' }, + 'gfx_fancybubbles': { label: 'Fancy Bubble Particles', description: 'Enhanced underwater bubbles' }, + + // Performance + 'gfx_multithreaded_renderer': { label: 'Multithreaded Rendering', description: 'Use multiple CPU cores for rendering' }, + 'upscaling_percentage': { label: 'Resolution Scale', min: 50, max: 100, step: 5, description: 'Internal render resolution percentage' }, + 'upscaling_mode': { label: 'Upscaling Method', min: 0, max: 3, step: 1, description: '0=None, 1=FSR2, 2=Other methods' }, + + // Additional settings that might appear + 'gfx_better_grass': { label: 'Better Grass', description: 'Improved grass block sides' }, + 'gfx_clouds': { label: 'Clouds', description: 'Render clouds in sky' }, + 'gfx_stars': { label: 'Stars', description: 'Show stars at night' }, + 'gfx_sun_moon': { label: 'Sun & Moon', description: 'Show sun and moon' }, + 'gfx_weather': { label: 'Weather Effects', description: 'Rain and snow particles' }, +}; + +export const OptionsDialog: React.FC = ({ isOpen, onClose }) => { + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); + const [saving, setSaving] = useState(false); + const [instances, setInstances] = useState([]); + const [selectedInstance, setSelectedInstance] = useState(0); + const [modifiedOptions, setModifiedOptions] = useState>({}); + const [error, setError] = useState(null); + const [filterText, setFilterText] = useState(''); + + // Load options when dialog opens + useEffect(() => { + if (isOpen) { + loadOptions(); + } + }, [isOpen]); + + const loadOptions = async () => { + setLoading(true); + setError(null); + try { + const options = await invoke('get_minecraft_options'); + setInstances(options); + if (options.length > 0) { + setModifiedOptions({ ...options[0].gfx_options }); + } + } catch (err) { + setError(err as string); + console.error('Failed to load options:', err); + } finally { + setLoading(false); + } + }; + + const handleOptionToggle = (key: string, value: boolean) => { + setModifiedOptions(prev => ({ + ...prev, + [key]: value ? '1' : '0' + })); + }; + + const handleNumericChange = (key: string, value: string) => { + // Validate numeric input + if (value === '' || !isNaN(Number(value))) { + setModifiedOptions(prev => ({ + ...prev, + [key]: value + })); + } + }; + + const handleSave = async () => { + if (!instances[selectedInstance]) return; + + setSaving(true); + setError(null); + try { + await invoke('save_minecraft_options', { + optionsPath: instances[selectedInstance].options_path, + gfxOptions: modifiedOptions + }); + + // Reload to confirm changes + await loadOptions(); + + // Show success feedback + const successMsg = document.createElement('div'); + successMsg.className = 'fixed bottom-4 right-4 bg-green-600 text-white px-4 py-2 rounded-lg shadow-lg z-50'; + successMsg.textContent = 'Options saved successfully!'; + document.body.appendChild(successMsg); + setTimeout(() => successMsg.remove(), 3000); + } catch (err) { + setError(err as string); + console.error('Failed to save options:', err); + } finally { + setSaving(false); + } + }; + + const handleReset = () => { + if (instances[selectedInstance]) { + setModifiedOptions({ ...instances[selectedInstance].gfx_options }); + } + }; + + // Get translated label for an option + const getTranslatedLabel = (key: string) => { + const metadata = OPTION_METADATA[key]; + if (metadata?.label) { + // Try to get translation, fallback to hardcoded label + return t(`options.${key}.label`, metadata.label); + } + return key; + }; + + // Get translated description for an option + const getTranslatedDescription = (key: string) => { + const metadata = OPTION_METADATA[key]; + if (metadata?.description) { + return t(`options.${key}.description`, metadata.description); + } + return null; + }; + + // Filter and sort options (labeled options first, then unlabeled) + const filteredAndSortedOptions = Object.entries(modifiedOptions) + .filter(([key]) => { + if (!filterText) return true; + const label = getTranslatedLabel(key); + return label.toLowerCase().includes(filterText.toLowerCase()) || + key.toLowerCase().includes(filterText.toLowerCase()); + }) + .sort(([a], [b]) => { + const hasLabelA = !!OPTION_METADATA[a]?.label; + const hasLabelB = !!OPTION_METADATA[b]?.label; + + // Sort labeled options before unlabeled ones + if (hasLabelA && !hasLabelB) return -1; + if (!hasLabelA && hasLabelB) return 1; + + // Within each group, sort alphabetically by label or key + const labelA = getTranslatedLabel(a); + const labelB = getTranslatedLabel(b); + return labelA.localeCompare(labelB); + }); + + if (!isOpen) return null; + + return ( +
+ {/* Modal content */} +
+ {/* Header */} +
+

+ {t('options.title', 'Minecraft Graphics Options Editor')} +

+ +
+ + {/* Content */} +
+ {loading ? ( +
+ +
+ ) : error ? ( +
+

{error}

+ +
+ ) : instances.length === 0 ? ( +
+
+ +

+ {t('options.no_installations', 'No Minecraft installations found with options.txt files.')} +

+
+
+ ) : ( + <> + {/* Instance selector */} + {instances.length > 1 && ( +
+
+ {instances.map((instance, index) => ( + + ))} +
+
+ )} + + {/* Filter input */} +
+ + setFilterText(e.target.value)} + className="dialog__search-input" + /> +
+ + {/* Options list */} +
+ {filteredAndSortedOptions.length === 0 ? ( +

+ {filterText ? + t('options.no_filter_results', 'No options match your filter.') : + t('options.no_options', 'No graphics options found in this installation.')} +

+ ) : ( + filteredAndSortedOptions.map(([key, value]) => { + const metadata = OPTION_METADATA[key] || {}; + // Check if option should be numeric based on name or explicit list + const isNumeric = NUMERIC_OPTIONS.includes(key) || + key.toLowerCase().includes('scale') || + key.toLowerCase().includes('opacity'); + const label = getTranslatedLabel(key); + const description = getTranslatedDescription(key); + + return ( +
+
+
+
{label}
+ {description && ( +

+ {description} +

+ )} + {instances[selectedInstance]?.gfx_options[key] !== value && ( +
+ {t('options.original', 'Original')}: {isNumeric + ? instances[selectedInstance].gfx_options[key] + : instances[selectedInstance].gfx_options[key] === '1' ? t('common.on', 'On') : t('common.off', 'Off')} +
+ )} +
+ {isNumeric ? ( +
+ + handleNumericChange(key, e.target.value)} + min={metadata.min} + max={metadata.max} + step={metadata.step} + className="dialog__option-input" + /> +
+ ) : ( + handleOptionToggle(key, checked)} + /> + )} +
+
+ ); + }) + )} +
+ + {/* Action buttons */} +
+ + +
+ + )} +
+
+
+ ); +}; diff --git a/v3/src/components/RtpackDialog.tsx b/v3/src/components/RtpackDialog.tsx new file mode 100644 index 0000000..cc036ed --- /dev/null +++ b/v3/src/components/RtpackDialog.tsx @@ -0,0 +1,201 @@ +import React, { useState, useEffect, useCallback } from "react"; +import { useTranslation } from "react-i18next"; +import { invoke } from "@tauri-apps/api/core"; +import { cx } from "classix"; +import Modal from "./ui/Modal"; +import Button from "./ui/Button"; +import { useAppStore } from "../store/appStore"; +import { useStatusStore } from "../store/statusStore"; + +interface RtpackDialogProps { + isOpen: boolean; + rtpackPath: string; + onClose: () => void; +} + +export const RtpackDialog: React.FC = ({ + isOpen, + rtpackPath, + onClose, +}) => { + const { t } = useTranslation(); + const { installations, refreshInstallations } = useAppStore(); + const { addMessage } = useStatusStore(); + const [selectedInstallations, setSelectedInstallations] = useState< + Set + >(new Set()); + const [isInstalling, setIsInstalling] = useState(false); + + useEffect(() => { + if (isOpen && installations.length === 0) { + refreshInstallations(); + } + }, [isOpen, installations.length, refreshInstallations]); + + const handleInstallationToggle = useCallback((installLocation: string): void => { + const newSet = new Set(selectedInstallations); + if (newSet.has(installLocation)) { + newSet.delete(installLocation); + } else { + newSet.add(installLocation); + } + setSelectedInstallations(newSet); + }, [selectedInstallations]); + + const handleInstall = useCallback(async (): Promise => { + if (!rtpackPath.toLowerCase().endsWith(".rtpack")) { + addMessage({ message: t("invalid_rtpack", "Invalid RTpack file"), type: "error" }); + return; + } + if (selectedInstallations.size === 0) { + addMessage({ + message: t("status_select_installation_warning"), + type: "error", + }); + return; + } + setIsInstalling(true); + try { + addMessage({ message: t("status_installing_rtpack"), type: "loading" }); + await invoke("install_from_rtpack", { + rtpackPath, + selectedNames: Array.from(selectedInstallations), + }); + addMessage({ message: t("status_install_success"), type: "success" }); + await refreshInstallations(); + onClose(); + } catch (error) { + const errorMsg = t("status_install_error", { error }); + addMessage({ message: errorMsg, type: "error" }); + } finally { + setIsInstalling(false); + } + }, [addMessage, onClose, refreshInstallations, rtpackPath, selectedInstallations, t]); + + const handleSelectAll = useCallback((): void => { + if (selectedInstallations.size === installations.length) { + setSelectedInstallations(new Set()); + } else { + setSelectedInstallations(new Set(installations.map((i) => i.InstallLocation))); + } + }, [installations, selectedInstallations]); + + const handleClose = useCallback((): void => { + setSelectedInstallations(new Set()); + onClose(); + }, [onClose]); + + const fileName = rtpackPath.split(/[\\\/]/).pop() || rtpackPath; + + return ( + +
+

+ {t("rtpack_dialog_description")} +

+ +
+
+

+ {fileName} +

+
+
+ +
+ + + {t("selected_count", { + selected: selectedInstallations.size, + total: installations.length, + })} + +
+ + {installations.length === 0 ? ( +
+

{t("no_minecraft_installations")}

+
+ ) : ( +
+ {installations.map((installation) => ( +
+ +
+ ))} +
+ )} + +
+ + +
+
+
+ ); +}; diff --git a/v3/src/components/SelectInstallationsDialog.tsx b/v3/src/components/SelectInstallationsDialog.tsx new file mode 100644 index 0000000..9a6651f --- /dev/null +++ b/v3/src/components/SelectInstallationsDialog.tsx @@ -0,0 +1,159 @@ +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { cx } from "classix"; +import Modal from "./ui/Modal"; +import Button from "./ui/Button"; +import { useAppStore } from "../store/appStore"; +import { useStatusStore } from "../store/statusStore"; + +interface SelectInstallationsDialogProps { + isOpen: boolean; + onClose: () => void; + title: string; + description?: string; + confirmKey: string; // i18n key for idle confirm label; expects { count } + busyKey: string; // i18n key for busy label + onConfirm: (selected: string[]) => Promise; +} + +const SelectInstallationsDialog: React.FC = ({ + isOpen, + onClose, + title, + description, + confirmKey, + busyKey, + onConfirm, +}) => { + const { t } = useTranslation(); + const { installations, selectedInstallations, refreshInstallations } = useAppStore(); + const { addMessage } = useStatusStore(); + const [selected, setSelected] = useState>(new Set()); + const [isProcessing, setIsProcessing] = useState(false); + + useEffect(() => { + if (isOpen) { + // Pre-select current selections from the store + setSelected(new Set(selectedInstallations)); + if (installations.length === 0) { + // Mirror RtpackDialog behavior + refreshInstallations(); + } + } else { + setSelected(new Set()); + setIsProcessing(false); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isOpen]); + + const toggle = (path: string): void => { + const next = new Set(selected); + if (next.has(path)) next.delete(path); + else next.add(path); + setSelected(next); + }; + + const handleSelectAll = (): void => { + if (selected.size === installations.length) { + setSelected(new Set()); + } else { + setSelected(new Set(installations.map((i) => i.InstallLocation))); + } + }; + + const handleClose = (): void => { + setSelected(new Set()); + setIsProcessing(false); + onClose(); + }; + + const handleConfirm = async (): Promise => { + if (selected.size === 0) { + addMessage({ message: t("status_select_installation_warning"), type: "error" }); + return; + } + setIsProcessing(true); + try { + const ok = await onConfirm(Array.from(selected)); + if (ok) handleClose(); + } finally { + setIsProcessing(false); + } + }; + + if (!isOpen) return null; + + return ( + +
+ {description && ( +

{description}

+ )} + +
+ + + {t("selected_count", { selected: selected.size, total: installations.length })} + +
+ + {installations.length === 0 ? ( +
+

{t("no_minecraft_installations")}

+
+ ) : ( +
+ {installations.map((installation) => ( +
+ +
+ ))} +
+ )} + +
+ + +
+
+
+ ); +}; + +export default SelectInstallationsDialog; diff --git a/v3/src/components/StatusBar.tsx b/v3/src/components/StatusBar.tsx new file mode 100644 index 0000000..6fb0331 --- /dev/null +++ b/v3/src/components/StatusBar.tsx @@ -0,0 +1,95 @@ +import React, { useEffect, useState } from "react"; +import { X, Info, AlertTriangle, CheckCircle } from "lucide-react"; +import { cx } from "classix"; +import { useStatusStore, StatusMessage } from "../store/statusStore"; + +const StatusIcon: React.FC<{ type: StatusMessage["type"] }> = ({ type }) => { + switch (type) { + case "error": + return ; + case "success": + return ; + case "loading": + return ( +
+ ); + default: + return ; + } +}; + +const StatusToast: React.FC<{ + message: StatusMessage; + onDismiss: (id: string) => void; + onMouseEnter: (id: string) => void; + onMouseLeave: (id: string) => void; +}> = ({ message, onDismiss, onMouseEnter, onMouseLeave }) => { + const [isVisible, setIsVisible] = useState(false); + + useEffect(() => { + const timer = setTimeout(() => setIsVisible(true), 10); + return () => clearTimeout(timer); + }, []); + + const handleDismiss = (): void => { + setIsVisible(false); + setTimeout(() => onDismiss(message.id), 200); + }; + + return ( +
onMouseEnter(message.id)} + onMouseLeave={() => onMouseLeave(message.id)} + > + + + {message.message} + + +
+ ); +}; + +export const StatusBarContainer: React.FC = () => { + const { messages, removeMessage, clearTimer } = useStatusStore(); + + const handleMouseEnter = (id: string): void => { + clearTimer(id); + }; + + const handleMouseLeave = (id: string): void => { + const message = messages.find((msg) => msg.id === id); + if (message && message.type !== "loading") { + setTimeout(() => { + removeMessage(id); + }, 5000); + } + }; + + return ( +
+ {messages + .sort((a, b) => b.timestamp - a.timestamp) + .map((msg) => ( + + ))} +
+ ); +}; diff --git a/v3/src/components/ToolbarSection.tsx b/v3/src/components/ToolbarSection.tsx new file mode 100644 index 0000000..1d29051 --- /dev/null +++ b/v3/src/components/ToolbarSection.tsx @@ -0,0 +1,56 @@ +import React from "react"; +import { + RefreshCw, + RefreshCcw, + Trash2, +} from "lucide-react"; +import { useAppStore } from "../store/appStore"; +import { useTranslation } from "react-i18next"; +import Button from "./ui/Button"; + +export const ToolbarSection: React.FC = () => { + const { t } = useTranslation(); + const { + addConsoleOutput, + refreshInstallations, + refreshPresets, + clearCache, + } = useAppStore(); + + const handleRefreshClick = () => { + addConsoleOutput(t("log_refreshing_installations")); + refreshInstallations(); + }; + + const handleForceRefreshClick = () => { + addConsoleOutput(t("log_force_refreshing_presets")); + refreshPresets(true); + }; + + const handleClearCacheClick = () => { + addConsoleOutput(t("log_clearing_cache")); + clearCache(); + }; + return ( +
+ + + +
+ ); +}; diff --git a/v3/src/components/actions/ActionsTab.tsx b/v3/src/components/actions/ActionsTab.tsx new file mode 100644 index 0000000..64286ae --- /dev/null +++ b/v3/src/components/actions/ActionsTab.tsx @@ -0,0 +1,272 @@ +import { useAppStore } from "../../store/appStore"; +import { useStatusStore } from "../../store/statusStore"; +import { useTranslation } from "react-i18next"; +import { invoke } from "@tauri-apps/api/core"; +import { useState, useEffect } from "react"; +import Button from "../ui/Button"; +import SelectInstallationsDialog from "../SelectInstallationsDialog"; +import { CheckCircle } from "lucide-react"; +import { OptionsDialog } from "../OptionsDialog"; + +type ModalType = "dlss" | "update" | "backup" | "uninstall"; + +export default function ActionsTab() { + const { t } = useTranslation(); + const { addMessage } = useStatusStore(); + const { + installRTX, + updateOptions, + backupSupportFiles, + uninstallRTX, + } = useAppStore(); + const [isProtocolRegistered, setIsProtocolRegistered] = useState(false); + const [actionDialogOpen, setActionDialogOpen] = useState(false); + const [actionType, setActionType] = useState( + null + ); + const [optionsDialogOpen, setOptionsDialogOpen] = useState(false); + + + const openSelectDialog = (type: ModalType): void => { + setActionType(type); + setActionDialogOpen(true); + }; + + const closeSelectDialog = (): void => { + setActionDialogOpen(false); + setActionType(null); + }; + + const handleActionConfirm = async (paths: string[]): Promise => { + if (!actionType) return false; + + try { + switch (actionType) { + case "dlss": + await Promise.all(paths.map(path => installRTX(path))); + addMessage({ message: t("actions.dlss.success"), type: "success" }); + break; + case "update": + await Promise.all(paths.map(path => updateOptions(path))); + addMessage({ message: t("actions.update.success"), type: "success" }); + break; + case "backup": + await Promise.all(paths.map(path => backupSupportFiles(path))); + addMessage({ message: t("actions.backup.success"), type: "success" }); + break; + case "uninstall": + await uninstallRTX(paths); + addMessage({ message: t("actions.uninstall.success"), type: "success" }); + break; + } + return true; + } catch (error) { + addMessage({ + message: t(`actions.${actionType}.error`, { + error: error instanceof Error ? error.message : String(error), + }), + type: "error" + }); + return false; + } + }; + + const checkProtocolStatus = async () => { + try { + const registered = await invoke("is_brtx_protocol_registered"); + setIsProtocolRegistered(registered); + } catch (error) { + console.error("Failed to check protocol status:", error); + } + }; + + const handleProtocolToggle = async (checked: boolean) => { + if (checked) { + try { + await invoke("register_brtx_protocol"); + setIsProtocolRegistered(true); + addMessage({ + message: t("status_register_protocol_success"), + type: "success", + }); + } catch (error) { + addMessage({ + message: t("status_register_protocol_error", { error: String(error) }), + type: "error", + }); + } + } + // Note: We don't handle unregistration as it's typically not needed + }; + + useEffect(() => { + checkProtocolStatus(); + }, []); + + return ( +
+
+

{t("actions_title")}

+
+
+
+
+
+

+ {t("action_install_dlss_title")} +

+

{t("action_install_dlss_desc")}

+
+
+ +
+
+
+
+
+
+

+ {t("action_graphics_options_title", "Graphics Options Editor")} +

+

+ {t("action_graphics_options_desc", "Edit Minecraft graphics settings directly from options.txt")} +

+
+
+ +
+
+
+ +
+
+
+

{t("action_backup_title")}

+

{t("action_backup_desc")}

+
+
+ +
+
+
+
+
+
+

{t("action_uninstall_title")}

+

{t("action_uninstall_desc")}

+
+
+ +
+
+
+
+
+
+

{t("action_register_protocol_title")}

+

+ {t("action_register_protocol_desc")} +

+
+
+ +
+
+
+ +
+ + setOptionsDialogOpen(false)} + /> +
+ ); +} diff --git a/v3/src/components/creator/CreatorIcon.tsx b/v3/src/components/creator/CreatorIcon.tsx new file mode 100644 index 0000000..9dad175 --- /dev/null +++ b/v3/src/components/creator/CreatorIcon.tsx @@ -0,0 +1,29 @@ +import { useState } from "react" +import { cx } from "classix"; +import { Wand } from "lucide-react"; + +export default function CreatorIcon() { + + const getRandomBackgroundColor = () => { + const colors = [ + "bg-red-500", + "bg-blue-500", + "bg-green-500", + "bg-yellow-500", + "bg-purple-500", + "bg-pink-500", + "bg-orange-500", + ]; + return colors[Math.floor(Math.random() * colors.length)]; + }; + + const [color] = useState(getRandomBackgroundColor); + + return ( +
+ + + +
+ ); +} \ No newline at end of file diff --git a/v3/src/components/creator/CreatorNameModal.tsx b/v3/src/components/creator/CreatorNameModal.tsx new file mode 100644 index 0000000..7b48869 --- /dev/null +++ b/v3/src/components/creator/CreatorNameModal.tsx @@ -0,0 +1,87 @@ +import React, { useState, useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import Button from "../ui/Button"; + +interface CreatorNameModalProps { + isOpen: boolean; + onClose: () => void; + onConfirm: (name: string) => void; + defaultName: string; + isProcessing: boolean; +} + +export default function CreatorNameModal({ + isOpen, + onClose, + onConfirm, + defaultName, + isProcessing, +}: CreatorNameModalProps) { + const { t } = useTranslation(); + const [name, setName] = useState(defaultName); + + useEffect(() => { + setName(defaultName); + }, [defaultName]); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (name.trim()) { + onConfirm(name.trim()); + } + }; + + if (!isOpen) return null; + + return ( +
+
+
+

{t("creator_name_modal_title")}

+
+ +
+
+ +
+ setName(e.target.value)} + disabled={isProcessing} + placeholder={t("creator_name_placeholder")} + autoFocus + /> +
+

+ {t("creator_name_help")} +

+
+
+ +
+ + +
+
+
+ ); +} diff --git a/v3/src/components/creator/CreatorTab.tsx b/v3/src/components/creator/CreatorTab.tsx new file mode 100644 index 0000000..d0bc893 --- /dev/null +++ b/v3/src/components/creator/CreatorTab.tsx @@ -0,0 +1,531 @@ +import React, { useState } from "react"; +import { invoke } from "@tauri-apps/api/core"; +import { useTranslation } from "react-i18next"; +import { open } from "@tauri-apps/plugin-dialog"; +import Button from "../ui/Button"; +import { useAppStore } from "../../store/appStore"; +import { useStatusStore } from "../../store/statusStore"; +import InstallationInstanceModal from "../installations/InstallationInstanceModal"; +import CreatorNameModal from "./CreatorNameModal"; + +export default function CreatorTab() { + const { t } = useTranslation(); + const { installations } = useAppStore(); + const { addMessage } = useStatusStore(); + const [settingsHash, setSettingsHash] = useState(""); + const [uploadedFiles, setUploadedFiles] = useState([]); + const [isProcessing, setIsProcessing] = useState(false); + const [isModalOpen, setIsModalOpen] = useState(false); + const [isNameModalOpen, setIsNameModalOpen] = useState(false); + const [pendingInstallData, setPendingInstallData] = useState<{ + selectedNames: string[]; + presetName: string; + } | null>(null); + const [isMaterialModalOpen, setIsMaterialModalOpen] = useState(false); + const [isMaterialNameModalOpen, setIsMaterialNameModalOpen] = useState(false); + const [pendingMaterialData, setPendingMaterialData] = useState<{ + selectedNames: string[]; + presetName: string; + } | null>(null); + const [selectedRtpack, setSelectedRtpack] = useState(""); + const [isRtpackModalOpen, setIsRtpackModalOpen] = useState(false); + + const handleInstall = async (selectedNames: string[]) => { + if (selectedNames.length === 0) { + addMessage({ + message: t("status_select_installation_warning"), + type: "error", + }); + return; + } + + const defaultName = `Settings ${settingsHash.slice(0, 8)}`; + setPendingInstallData({ selectedNames, presetName: defaultName }); + setIsModalOpen(false); + setIsNameModalOpen(true); + }; + + const handleNameConfirm = async (presetName: string) => { + if (!pendingInstallData) return; + + setIsProcessing(true); + setIsNameModalOpen(false); + const { refreshInstallations, addConsoleOutput } = useAppStore.getState(); + + try { + addConsoleOutput(t("log_installing_creator_preset", { name: presetName })); + + // Create a unique UUID for the creator preset using settings hash + const creatorUuid = `creator-${settingsHash.trim()}`; + + await invoke("download_creator_settings", { + settingsHash: settingsHash.trim(), + selectedNames: pendingInstallData.selectedNames, + presetName, + uuid: creatorUuid, + }); + + addMessage({ + message: t("creator_install_success", { name: presetName }), + type: "success", + }); + + addConsoleOutput(t("log_creator_install_complete")); + // Refresh installations to show the new creator preset + await refreshInstallations(); + setSettingsHash(""); + } catch (error) { + addMessage({ + message: t("creator_install_error", { error }), + type: "error", + }); + addConsoleOutput(t("log_creator_install_error", { error })); + } finally { + setIsProcessing(false); + setPendingInstallData(null); + } + }; + + const handleMaterialInstall = async (selectedNames: string[]) => { + if (selectedNames.length === 0) { + addMessage({ + message: t("status_select_installation_warning"), + type: "error", + }); + return; + } + + if (uploadedFiles.length === 0) { + addMessage({ + message: t("creator_no_materials_uploaded"), + type: "error", + }); + return; + } + + const defaultName = `Materials (${uploadedFiles.length} files)`; + setPendingMaterialData({ selectedNames, presetName: defaultName }); + setIsMaterialModalOpen(false); + setIsMaterialNameModalOpen(true); + }; + + const handleMaterialNameConfirm = async (presetName: string) => { + if (!pendingMaterialData) return; + + setIsProcessing(true); + setIsMaterialNameModalOpen(false); + const { refreshInstallations, addConsoleOutput } = useAppStore.getState(); + + try { + addConsoleOutput(t("log_installing_material_preset", { name: presetName })); + + await invoke("install_uploaded_materials", { + selectedNames: pendingMaterialData.selectedNames, + presetName, + }); + + addMessage({ + message: t("creator_materials_install_success", { name: presetName }), + type: "success", + }); + + addConsoleOutput(t("log_material_install_complete")); + // Refresh installations to show the new material preset + await refreshInstallations(); + // Clear uploaded files after successful installation + setUploadedFiles([]); + } catch (error) { + addMessage({ + message: t("creator_materials_install_error", { error }), + type: "error", + }); + addConsoleOutput(t("log_material_install_error", { error })); + } finally { + setIsProcessing(false); + setPendingMaterialData(null); + } + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + if (!settingsHash.trim()) { + addMessage({ + message: t("creator_please_enter_hash"), + type: "error", + }); + return; + } + + setIsModalOpen(true); + }; + + const handleHashChange = (e: React.ChangeEvent) => { + setSettingsHash(e.target.value); + }; + + const handleFileUpload = async () => { + setIsProcessing(true); + try { + // Use Tauri dialog plugin to select multiple files + const filePaths = await open({ + title: "Select .material.bin files", + filters: [{ + name: "Material Files", + extensions: ["material.bin"] + }], + multiple: true + }); + + if (!filePaths || filePaths.length === 0) { + setIsProcessing(false); + return; + } + + // Process each selected file + const uploadedFilenames: string[] = []; + for (const filePath of filePaths) { + try { + // Use Tauri command to upload the file (handles permissions properly) + const filename = await invoke("upload_material_file", { + sourcePath: filePath + }) as string; + uploadedFilenames.push(filename); + } catch (error) { + addMessage({ + message: t("creator_upload_error_single", { + filename: filePath.split(/[\\\/]/).pop() || filePath, + error + }), + type: "error", + }); + } + } + + if (uploadedFilenames.length > 0) { + addMessage({ + message: t("creator_files_uploaded", { count: uploadedFilenames.length }), + type: "success", + }); + + // Add to uploaded files list + setUploadedFiles(prev => [...prev, ...uploadedFilenames]); + } + + } catch (error) { + addMessage({ + message: t("creator_upload_error", { error }), + type: "error", + }); + } finally { + setIsProcessing(false); + } + }; + + const handleRemoveFile = (filename: string) => { + setUploadedFiles(prev => prev.filter(f => f !== filename)); + addMessage({ + message: t("creator_file_removed", { filename }), + type: "info", + }); + }; + + const handleRtpackUpload = async () => { + setIsProcessing(true); + try { + // Use Tauri dialog plugin to select .rtpack file + const filePath = await open({ + title: "Select .rtpack file", + filters: [{ + name: "RTX Pack Files", + extensions: ["rtpack"] + }], + multiple: false + }); + + if (!filePath) { + setIsProcessing(false); + return; + } + + // Store the selected rtpack path + setSelectedRtpack(filePath); + + addMessage({ + message: t("creator_rtpack_selected", { filename: filePath.split(/[\\\/]/).pop() || filePath }), + type: "success", + }); + + // Immediately open the installation modal + setIsRtpackModalOpen(true); + + } catch (error) { + addMessage({ + message: t("creator_rtpack_error", { error }), + type: "error", + }); + } finally { + setIsProcessing(false); + } + }; + + const handleRtpackInstall = async (selectedNames: string[]) => { + if (selectedNames.length === 0) { + addMessage({ + message: t("status_select_installation_warning"), + type: "error", + }); + return; + } + + if (!selectedRtpack) { + addMessage({ + message: t("creator_no_rtpack_selected"), + type: "error", + }); + return; + } + + setIsProcessing(true); + setIsRtpackModalOpen(false); + const { refreshInstallations, addConsoleOutput } = useAppStore.getState(); + + try { + const filename = selectedRtpack.split(/[\\\/]/).pop() || selectedRtpack; + addConsoleOutput(t("log_installing_rtpack", { name: filename })); + + await invoke("install_from_rtpack", { + rtpackPath: selectedRtpack, + selectedNames, + }); + + addMessage({ + message: t("creator_rtpack_install_success", { name: filename }), + type: "success", + }); + + addConsoleOutput(t("log_rtpack_install_complete")); + // Refresh installations to show the new installation + await refreshInstallations(); + // Clear selected rtpack after successful installation + setSelectedRtpack(""); + } catch (error) { + addMessage({ + message: t("creator_rtpack_install_error", { error }), + type: "error", + }); + addConsoleOutput(t("log_rtpack_install_error", { error })); + } finally { + setIsProcessing(false); + } + }; + + const isValidHash = settingsHash.trim().length >= 8; + + return ( +
+
+
+

{t("creator_title")}

+ + {t("creator_subtitle")} + +
+
+ +
+
+
+

{t("creator_install_title")}

+
+
+
+
+ +
+ +
+

+ {t("creator_settings_hash_help")} +

+
+ +
+ +
+
+
+
+ +
+
+

{t("creator_material_files_title")}

+
+
+
+

+ {t("creator_material_files_subtitle")} +

+ +
+ +
+ +
+

+ {t("creator_upload_dialog_help")} +

+
+ + {uploadedFiles.length > 0 && ( +
+

{t("creator_uploaded_files")}

+
+ {uploadedFiles.map((filename, index) => ( +
+ {filename} + +
+ ))} +
+ +
+ +
+
+ )} +
+
+
+ +
+
+

{t("creator_rtpack_title")}

+
+
+
+

+ {t("creator_rtpack_subtitle")} +

+ +
+ +
+ +
+

+ {t("creator_rtpack_help")} +

+
+ + {selectedRtpack && ( +
+

{t("creator_selected_rtpack")}

+
+ {selectedRtpack.split(/[\\\/]/).pop() || selectedRtpack} +
+
+ )} +
+
+
+
+ setIsModalOpen(false)} + installations={installations} + presetName={`settings ${settingsHash.slice(0, 8)}...`} + onInstall={handleInstall} + isInstalling={isProcessing} + /> + { + setIsNameModalOpen(false); + setPendingInstallData(null); + }} + onConfirm={handleNameConfirm} + defaultName={pendingInstallData?.presetName || ""} + isProcessing={isProcessing} + /> + setIsMaterialModalOpen(false)} + installations={installations} + presetName={`${uploadedFiles.length} material files`} + onInstall={handleMaterialInstall} + isInstalling={isProcessing} + /> + { + setIsMaterialNameModalOpen(false); + setPendingMaterialData(null); + }} + onConfirm={handleMaterialNameConfirm} + defaultName={pendingMaterialData?.presetName || ""} + isProcessing={isProcessing} + /> + { + setIsRtpackModalOpen(false); + setSelectedRtpack(""); + }} + installations={installations} + presetName={selectedRtpack ? `${selectedRtpack.split(/[\\\/]/).pop() || selectedRtpack}` : "RTX Pack"} + onInstall={handleRtpackInstall} + isInstalling={isProcessing} + /> +
+ ); +} diff --git a/v3/src/components/installations/InstallationCard.tsx b/v3/src/components/installations/InstallationCard.tsx new file mode 100644 index 0000000..5184f2d --- /dev/null +++ b/v3/src/components/installations/InstallationCard.tsx @@ -0,0 +1,133 @@ +import React from "react"; +import { cx } from "classix"; +import { useTranslation } from "react-i18next"; +import { Lock } from "lucide-react"; +import PresetIcon from "../presets/PresetIcon"; + +export interface Installation { + FriendlyName: string; + InstallLocation: string; + Preview: boolean; + installed_preset?: { + uuid: string; + name: string; + installed_at: string; + is_creator?: boolean; + is_api?: boolean; + }; +} + +interface InstallationCardProps { + installation: Installation; + selected?: boolean; + onSelectionChange?: (path: string, selected: boolean) => void; +} + +const formatDate = (dateString: string) => { + const date = new Date(dateString); + return date.toLocaleDateString().replace(/\//g, "-"); +}; + +const isSideloadedInstallation = (installation: Installation): boolean => { + // Consider an installation sideloaded if it's in a non-standard location + // Standard locations include Microsoft Store paths and common installation directories + const path = installation.InstallLocation.toLowerCase(); + + // Standard Microsoft Store and official installation paths + const standardPaths = [ + 'microsoft.minecraftuwp', + 'microsoft.minecraftpreview', + 'windowsapps', + 'program files', + 'program files (x86)', + ]; + + // If the path contains any standard location indicators, it's not sideloaded + return !standardPaths.some(standardPath => path.includes(standardPath)); +}; + +export const InstallationCard: React.FC = ({ + installation, + selected = false, + onSelectionChange, +}) => { + const { t } = useTranslation(); + + const handleCardClick = () => { + const newSelected = !selected; + onSelectionChange?.(installation.InstallLocation, newSelected); + }; + + const presetIcon = installation.installed_preset && !installation.installed_preset.is_creator && installation.installed_preset.uuid !== "material-files" ? ( +
+ ) : null; + + return ( +
+

+ {!isSideloadedInstallation(installation) && ( + + )} + {installation.FriendlyName} + {installation.Preview && ( + Preview + )} + {installation.installed_preset?.is_creator && ( + {t("creator_preset")} + )} + {installation.installed_preset?.is_api && ( + {t("community_preset")} + )} +

+ +
+ {presetIcon || ( +
+ + {installation.Preview ? "Preview Edition" : "Minecraft Release"} + +
+ )} + {installation.installed_preset ? ( +
+

+ {t("current_preset")} +

+

{installation.installed_preset.name}

+ +
+ ) : ( +

+ {t("no_preset_installed")} +

+ )} +
+ +
+

+ {installation.InstallLocation} +

+
+
+ ); +}; diff --git a/v3/src/components/installations/InstallationInstanceModal.tsx b/v3/src/components/installations/InstallationInstanceModal.tsx new file mode 100644 index 0000000..5fb1a9b --- /dev/null +++ b/v3/src/components/installations/InstallationInstanceModal.tsx @@ -0,0 +1,159 @@ +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { cx } from "classix"; +import Modal from "../ui/Modal"; +import Button from "../ui/Button"; +import { Installation } from "../../store/appStore"; +import Switch from "../ui/Switch"; + +interface InstallationInstanceModalProps { + isOpen: boolean; + onClose: () => void; + installations: Installation[]; + presetName: string; + onInstall: (selectedInstallations: string[]) => void; + isInstalling?: boolean; +} + +const InstallationInstanceModal: React.FC = ({ + isOpen, + onClose, + installations, + presetName, + onInstall, + isInstalling = false, +}) => { + const { t } = useTranslation(); + const [selectedInstallations, setSelectedInstallations] = useState< + Set + >(new Set()); + + const handleInstallationToggle = (installPath: string): void => { + const newSelected = new Set(selectedInstallations); + if (newSelected.has(installPath)) { + newSelected.delete(installPath); + } else { + newSelected.add(installPath); + } + setSelectedInstallations(newSelected); + }; + + const handleSelectAll = (): void => { + if (selectedInstallations.size === installations.length) { + setSelectedInstallations(new Set()); + } else { + setSelectedInstallations( + new Set(installations.map((inst) => inst.InstallLocation)) + ); + } + }; + + const handleInstall = (): void => { + if (selectedInstallations.size > 0) { + onInstall(Array.from(selectedInstallations)); + onClose(); + } + }; + + const handleClose = (): void => { + setSelectedInstallations(new Set()); + onClose(); + }; + + return ( + +
+

+ {t("select_installations_description")} +

+ +
+ + + {t("selected_count", { + selected: selectedInstallations.size, + total: installations.length, + })} + +
+ +
+ {installations.map((installation) => ( +
+
handleInstallationToggle(installation.InstallLocation)}> + + handleInstallationToggle(installation.InstallLocation) + } + /> +
+ + {installation.FriendlyName} + + + {installation.InstallLocation} + + {installation.Preview && ( + + {t("preview")} + + )} + {installation.installed_preset && ( + + {t("current_preset")}:{" "} + {installation.installed_preset.name} + + )} +
+
+
+ ))} +
+ +
+ + +
+
+
+ ); +}; + +export default InstallationInstanceModal; diff --git a/v3/src/components/installations/InstallationNav.tsx b/v3/src/components/installations/InstallationNav.tsx new file mode 100644 index 0000000..da870ce --- /dev/null +++ b/v3/src/components/installations/InstallationNav.tsx @@ -0,0 +1,61 @@ +import { useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { open } from "@tauri-apps/plugin-shell"; +import PresetIcon from "../presets/PresetIcon"; +import { useAppStore } from "../../store/appStore"; +import CreatorIcon from "../creator/CreatorIcon"; + +export default function InstallationNav() { + const { t } = useTranslation(); + const { installations } = useAppStore(); + + // Extract installations with presets + const presetInstallations = useMemo(() => { + return installations + .map((installation) => { + if (!installation.installed_preset) return null; + + return { + uuid: installation.installed_preset.uuid, + name: installation.installed_preset.name, + installed_at: installation.installed_preset.installed_at, + installation, + }; + }) + .filter((preset): preset is NonNullable => preset !== null); + }, [installations]); + + return ( +
+ {presetInstallations.map((preset) => ( + + ))} +
+ ); +} diff --git a/v3/src/components/installations/InstallationsPanel.tsx b/v3/src/components/installations/InstallationsPanel.tsx new file mode 100644 index 0000000..5b20717 --- /dev/null +++ b/v3/src/components/installations/InstallationsPanel.tsx @@ -0,0 +1,191 @@ +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { open } from "@tauri-apps/plugin-dialog"; +import { invoke } from "@tauri-apps/api/core"; +import { InstallationCard, Installation } from "./InstallationCard"; +import Button from "../ui/Button"; +import { PlusIcon } from "lucide-react"; + +interface InstallationsPanelProps { + installations: Installation[]; + selectedInstallations: Set; + onInstallationSelection: (path: string, selected: boolean) => void; + onInstallationAdded: () => void; +} + +export default function InstallationsPanel({ + installations, + selectedInstallations, + onInstallationSelection, + onInstallationAdded, +}: InstallationsPanelProps) { + const { t } = useTranslation(); + const [showAddDialog, setShowAddDialog] = useState(false); + const [newInstallPath, setNewInstallPath] = useState(""); + const [newInstallName, setNewInstallName] = useState(""); + const [isAdding, setIsAdding] = useState(false); + + const handleAddInstallation = async () => { + if (!newInstallPath.trim()) return; + + setIsAdding(true); + try { + const isValid = await invoke("validate_minecraft_path", { + path: newInstallPath, + }); + + if (!isValid) { + alert("Invalid Minecraft installation path"); + return; + } + + // Trigger refresh of installations list + onInstallationAdded(); + + // Reset form + setShowAddDialog(false); + setNewInstallPath(""); + setNewInstallName(""); + } catch (error) { + console.error("Error adding installation:", error); + alert(`Error adding installation: ${error}`); + } finally { + setIsAdding(false); + } + }; + + const handleBrowseFolder = async () => { + try { + const selected = await open({ + directory: true, + multiple: false, + title: t("add_installation") + }); + if (selected) { + setNewInstallPath(selected as string); + } + } catch (error) { + console.error("Error opening folder dialog:", error); + } + }; + + return ( +
+
+
+

{t("installations_title")}

+ + {t("installations_found_count", { count: installations.length })} + +
+ + +
+ +
+ {installations.length > 0 + ? installations.map((installation) => ( + + )) + : ( +
+

{t("installations_none_found")}

+

+ {t("installations_none_found_hint")} +

+
+ )} +
+ + + + {/* Add Installation Dialog */} + {showAddDialog && ( +
+
+
+

{t("add_custom_installation")}

+ +
+
+
+
+ + setNewInstallName(e.target.value)} + placeholder={t("installation_name_placeholder")} + /> +
+
+ +
+ setNewInstallPath(e.target.value)} + placeholder={t("installation_path_placeholder")} + /> + +
+
+
+
+
+ + +
+
+
+ )} +
+ ); +} diff --git a/v3/src/components/installations/InstallationsTab.tsx b/v3/src/components/installations/InstallationsTab.tsx new file mode 100644 index 0000000..1c3d47e --- /dev/null +++ b/v3/src/components/installations/InstallationsTab.tsx @@ -0,0 +1,43 @@ +import { useCallback } from "react"; +import { useAppStore } from "../../store/appStore"; +import InstallationsPanel from "./InstallationsPanel"; +import IoBitPanel from "./IoBitPanel"; + +export default function InstallationsTab() { + const { + installations, + selectedInstallations, + setSelectedInstallations, + refreshInstallations, + } = useAppStore(); + + const handleInstallationSelection = useCallback( + (path: string, selected: boolean): void => { + const newSet = new Set(selectedInstallations); + if (selected) { + newSet.add(path); + } else { + newSet.delete(path); + } + setSelectedInstallations(newSet); + }, + [selectedInstallations] + ); + + const handleInstallationAdded = useCallback(async (): Promise => { + await refreshInstallations(); + }, [refreshInstallations]); + + return ( +
+ + + +
+ ); +} diff --git a/v3/src/components/installations/InstallationsToolbar.tsx b/v3/src/components/installations/InstallationsToolbar.tsx new file mode 100644 index 0000000..583eed3 --- /dev/null +++ b/v3/src/components/installations/InstallationsToolbar.tsx @@ -0,0 +1,27 @@ +import { useTranslation } from "react-i18next"; +import { useAppStore } from "../../store/appStore"; +import { InstallationCard } from "./InstallationCard"; + +export default function InstallationsToolbar() { + const { t } = useTranslation(); + const { installations } = useAppStore(); + + return ( +
+
+ {installations.length > 0 ? ( + installations.map((installation) => ( + + )) + ) : ( +
+

{t("installations_none_found")}

+
+ )} +
+
+ ); +} diff --git a/v3/src/components/installations/IoBitPanel.tsx b/v3/src/components/installations/IoBitPanel.tsx new file mode 100644 index 0000000..9cd91f1 --- /dev/null +++ b/v3/src/components/installations/IoBitPanel.tsx @@ -0,0 +1,96 @@ +import { useTranslation } from "react-i18next"; +import { useAppStore } from "../../store/appStore"; +import { useEffect, useState } from "react"; +import { invoke } from "@tauri-apps/api/core"; +import Button from "../ui/Button"; + +export default function IoBitPanel() { + const { t } = useTranslation(); + const { iobitPath, setIobitPath, refreshIobitPath, selectIobitPath } = useAppStore(); + const [isLoading, setIsLoading] = useState(false); + + const isFound = iobitPath !== null && iobitPath !== ""; + + useEffect(() => { + // Load IOBit path on component mount + const loadIobitPath = async () => { + try { + const path = await invoke('get_iobit_path'); + setIobitPath(path); + } catch (error) { + console.error('Failed to get IOBit path:', error); + } + }; + loadIobitPath(); + }, [setIobitPath]); + + const handleAutoDetect = async () => { + setIsLoading(true); + try { + await refreshIobitPath(); + } finally { + setIsLoading(false); + } + }; + + const handleBrowse = async () => { + setIsLoading(true); + try { + await selectIobitPath(); + } finally { + setIsLoading(false); + } + }; + + return ( +
+
+
+

{t("iobit_installation")}

+ + {t("iobit_installation_description")} + +
+
+
+

+ {t("iobit_path")} +

+ +
+
+ {isFound ? ( +

{t("iobit_found")}

+ ) : ( +

{t("iobit_not_found")}

+ )} +
+ + +
+
+
+ +
+

+ {isFound ? iobitPath : t("iobit_help_text")} +

+
+
+
+ ); +} \ No newline at end of file diff --git a/v3/src/components/presets/PresetCard.tsx b/v3/src/components/presets/PresetCard.tsx new file mode 100644 index 0000000..8a3fe4f --- /dev/null +++ b/v3/src/components/presets/PresetCard.tsx @@ -0,0 +1,115 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import { cx } from "classix"; +import Button from "../ui/Button"; +import BlockPath from "../ui/BlockPath"; +import PresetIcon from "./PresetIcon"; +import { ChevronDown } from "lucide-react"; +import BedrockGraphicsLink from "../ui/BedrockGraphicsLink"; + +export interface PackInfo { + name: string; + uuid: string; + slug?: string; + stub: string; + tonemapping: string; + bloom: string; +} + +interface PresetCardProps { + preset: PackInfo; + selected?: boolean; + isInstalling?: boolean; + onSelectionChange?: (uuid: string, selected: boolean) => void; + onInstall?: (uuid: string) => void; +} + +export const PresetCard: React.FC = ({ + preset, + selected = false, + isInstalling = false, + onSelectionChange, + onInstall, +}) => { + const { t } = useTranslation(); + const handleCardClick = (e: React.MouseEvent) => { + if ((e.target as HTMLElement).closest(".install-preset-btn")) return; + + const newSelected = !selected; + onSelectionChange?.(preset.uuid, newSelected); + }; + + const handleInstallClick = (e: React.MouseEvent) => { + e.stopPropagation(); + onInstall?.(preset.uuid); + }; + + return ( +
+
+ +
+

{preset.name}

+ +
+
+
+
+ +
+
RTX Stub
+
+ +
+
Tonemapping
+
+ +
+
Bloom
+
+ +
+
+
+
+
+ +
+
+ ); +}; diff --git a/v3/src/components/presets/PresetIcon.tsx b/v3/src/components/presets/PresetIcon.tsx new file mode 100644 index 0000000..e2b7993 --- /dev/null +++ b/v3/src/components/presets/PresetIcon.tsx @@ -0,0 +1,46 @@ +import { cx } from "classix"; +import { useState } from "react"; + +export default function PresetIcon({ + uuid, + size, + extra, +}: { + uuid: string; + size?: string; + extra?: string; +}) { + const iconSize = size === "lg" ? 64 : 48; + const [mainImgError, setMainImgError] = useState(false); + const [bgImgError, setBgImgError] = useState(false); + + return ( +
+
+ {!mainImgError && ( + {`${uuid} setMainImgError(true)} + width={iconSize} + height={iconSize} + /> + )} +
+ {!bgImgError && ( + {`${uuid} setBgImgError(true)} + width={iconSize} + height={iconSize} + /> + )} +
+
+ ); +} diff --git a/v3/src/components/presets/PresetsTab.tsx b/v3/src/components/presets/PresetsTab.tsx new file mode 100644 index 0000000..56873a5 --- /dev/null +++ b/v3/src/components/presets/PresetsTab.tsx @@ -0,0 +1,164 @@ +import { PresetCard } from "./PresetCard"; +import { useTranslation } from "react-i18next"; +import { useAppStore } from "../../store/appStore"; +import { usePresetsStore } from "../../store/presetsStore"; +import InstallationInstanceModal from "../installations/InstallationInstanceModal"; +import { useState, useMemo } from "react"; + +export default function PresetsTab() { + const { t } = useTranslation(); + const { presets, installations } = useAppStore(); + const [searchQuery, setSearchQuery] = useState(""); + const [selectedFilter, setSelectedFilter] = useState("all"); + const { + selectedPreset, + installingPresets, + installModalOpen, + installModalPresetUuid, + handlePresetSelection, + openInstallModal, + closeInstallModal, + handleInstallToSelected, + } = usePresetsStore(); + + const onPresetInstall = (uuid: string) => openInstallModal(uuid); + + const handleModalInstall = (selectedInstallations: string[]) => { + if (installModalPresetUuid) { + handleInstallToSelected(installModalPresetUuid, selectedInstallations, t); + } + }; + + const getPresetName = (uuid: string): string => { + const preset = presets.find((p) => p.uuid === uuid); + return preset?.name || "Unknown Preset"; + }; + + const filteredPresets = useMemo(() => { + let filtered = presets; + + // Apply search filter + if (searchQuery.trim()) { + const query = searchQuery.toLowerCase(); + filtered = filtered.filter( + (preset) => + preset.name.toLowerCase().includes(query) || + preset.stub.toLowerCase().includes(query) || + preset.tonemapping.toLowerCase().includes(query) || + preset.bloom.toLowerCase().includes(query) + ); + } + + // Apply version filter + if (selectedFilter !== "all") { + filtered = filtered.filter((preset) => { + // Extract BetterRTX version from preset name + const versionMatch = preset.name.match(/BetterRTX\s+(\d+\.\d+(?:\.\d+)?)/i); + + if (selectedFilter === "other") { + // Show presets that don't have a BetterRTX version pattern + return !versionMatch; + } + + if (!versionMatch) { + return false; + } + + const presetVersion = versionMatch[1]; + + // Match major.minor version (e.g., "1.4" matches "1.4.0", "1.4.1", etc.) + return presetVersion.startsWith(selectedFilter); + }); + } + + return filtered; + }, [presets, searchQuery, selectedFilter]); + + return ( +
+
+
+

{t("presets_title")}

+ + {t("presets_loaded_count", { count: filteredPresets.length })} /{" "} + {presets.length} + +
+
+
+
+ setSearchQuery(e.target.value)} + className="w-full px-3 py-2 bg-app-panel border border-app-border rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-brand-accent focus:border-transparent" + /> +
+
+ +
+
+
+ {filteredPresets.length > 0 ? ( + filteredPresets.map((preset) => ( + + )) + ) : ( +
+

+ {searchQuery || selectedFilter !== "all" + ? t( + "presets_none_match_filter", + "No presets match your search criteria" + ) + : t("presets_none_available")} +

+ {(searchQuery || selectedFilter !== "all") && ( + + )} +
+ )} +
+ +
+ ); +} diff --git a/v3/src/components/ui/BedrockGraphicsLink.tsx b/v3/src/components/ui/BedrockGraphicsLink.tsx new file mode 100644 index 0000000..d8844ba --- /dev/null +++ b/v3/src/components/ui/BedrockGraphicsLink.tsx @@ -0,0 +1,30 @@ +import { ExternalLinkIcon } from "lucide-react"; +import Button from "./Button"; +import { useTranslation } from "react-i18next"; +import { openUrl } from "@tauri-apps/plugin-opener"; + +export default function BedrockGraphicsLink({ + preset +}: { + preset: string; +}) { + const { t } = useTranslation(); + return ( + + ); +} diff --git a/v3/src/components/ui/BlockPath.tsx b/v3/src/components/ui/BlockPath.tsx new file mode 100644 index 0000000..56af1ee --- /dev/null +++ b/v3/src/components/ui/BlockPath.tsx @@ -0,0 +1,42 @@ +import React from "react"; + +interface BlockPathProps { + /** The path or URL to display */ + path?: string; + /** Optional href for making the path clickable */ + href?: string; + /** Whether to open links in a new tab (default: true) */ + openInNewTab?: boolean; + /** Additional CSS classes */ + className?: string; +} + +/** + * A component that displays a path or URL using the same styling as preset stub links. + * Features overflow handling with ellipsis when not hovered and horizontal scroll on hover. + */ +export const BlockPath: React.FC = ({ + path, + href, + openInNewTab = true, + className = "", +}) => { + return ( +
+ {href ? ( + + {path ?? href} + + ) : ( + {path ?? href} + )} +
+ ); +}; + +export default BlockPath; diff --git a/v3/src/components/ui/Button.tsx b/v3/src/components/ui/Button.tsx new file mode 100644 index 0000000..11f9101 --- /dev/null +++ b/v3/src/components/ui/Button.tsx @@ -0,0 +1,40 @@ +import cx from "classix"; + +export default function Button({ + children, + theme = null, + size = null, + disabled = false, + block = false, + extra = null, + ...rest +}: { + children: React.ReactNode; + theme?: "primary" | "secondary" | null; + size?: "sm" | "md" | "lg" | null; + disabled?: boolean; + block?: boolean; + extra?: string | null; + [key: string]: any; +}) { + return ( + + ); +} diff --git a/v3/src/components/ui/DropzoneIndicator.tsx b/v3/src/components/ui/DropzoneIndicator.tsx new file mode 100644 index 0000000..05bd3ee --- /dev/null +++ b/v3/src/components/ui/DropzoneIndicator.tsx @@ -0,0 +1,22 @@ +import { cx } from "classix"; +import { FileUp } from "lucide-react"; + +interface DropzoneIndicatorProps { + isDragging: boolean; +} + +export default function DropzoneIndicator({ isDragging }: DropzoneIndicatorProps) { + return ( +
+
+
+ +

Drop .rtpack file to install

+
+
+
+ ); +} diff --git a/v3/src/components/ui/Modal.tsx b/v3/src/components/ui/Modal.tsx new file mode 100644 index 0000000..7575ed2 --- /dev/null +++ b/v3/src/components/ui/Modal.tsx @@ -0,0 +1,50 @@ +import React from "react"; +import { createPortal } from "react-dom"; +import { cx } from "classix"; + +interface ModalProps { + isOpen: boolean; + onClose: () => void; + title: string; + children: React.ReactNode; + className?: string; +} + +const Modal: React.FC = ({ + isOpen, + onClose, + title, + children, + className, +}) => { + if (!isOpen) return null; + + const handleBackdropClick = (e: React.MouseEvent) => { + if (e.target === e.currentTarget) { + onClose(); + } + }; + + const modalNode = ( +
+
+
+

{title}

+ +
+
{children}
+
+
+ ); + + return createPortal(modalNode, document.body); +}; + +export default Modal; diff --git a/v3/src/components/ui/SecondaryToolbar.tsx b/v3/src/components/ui/SecondaryToolbar.tsx new file mode 100644 index 0000000..54ae8dd --- /dev/null +++ b/v3/src/components/ui/SecondaryToolbar.tsx @@ -0,0 +1,30 @@ +import React from "react"; + +interface SecondaryToolbarProps { + title: string; + subtitle?: string; + actions?: React.ReactNode; + children?: React.ReactNode; +} + +const SecondaryToolbar: React.FC = ({ + title, + subtitle, + actions, + children, +}) => { + return ( +
+
+
+

{title}

+ {subtitle && {subtitle}} +
+ {actions &&
{actions}
} +
+ {children} +
+ ); +}; + +export default SecondaryToolbar; diff --git a/v3/src/components/ui/SideNav.tsx b/v3/src/components/ui/SideNav.tsx new file mode 100644 index 0000000..fb37b69 --- /dev/null +++ b/v3/src/components/ui/SideNav.tsx @@ -0,0 +1,86 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import { useAppStore } from "../../store/appStore"; +import { cx } from "classix"; +import { DownloadIcon, Package, Wrench, Palette } from "lucide-react"; +import Disclaimer from "../Disclaimer"; + +interface SideNavProps { + className?: string; + children?: React.ReactNode; +} + +export const SideNav: React.FC = ({ className, children }) => { + const { activeTab, setActiveTab } = useAppStore(); + const { t } = useTranslation(); + + const navItems = [ + { + id: "presets" as const, + label: t("tab_presets"), + icon: Package, + description: t("presets_title"), + }, + { + id: "creator" as const, + label: "Creator", + icon: Palette, + description: t("creator_title"), + }, + { + id: "actions" as const, + label: t("tab_actions"), + icon: Wrench, + description: t("actions_title"), + }, + { + id: "installations" as const, + label: t("tab_installations"), + icon: DownloadIcon, + description: t("installations_title"), + }, + ]; + + return ( +
+
+

{t("currently_installed")}

+
+ {children} + +
+

{t("navigation")}

+
+
+ {navItems.map((item) => { + const Icon = item.icon; + const isActive = activeTab === item.id; + + return ( + + ); + })} +
+ + +
+ ); +}; diff --git a/v3/src/components/ui/Switch.tsx b/v3/src/components/ui/Switch.tsx new file mode 100644 index 0000000..50adb36 --- /dev/null +++ b/v3/src/components/ui/Switch.tsx @@ -0,0 +1,69 @@ +import { Circle, CircleMinus } from "lucide-react"; + +interface SwitchProps { + checked: boolean; + onCheckedChange: (checked: boolean) => void; + label?: string; + description?: string; + id?: string; +} + +export default function Switch({ + checked, + onCheckedChange, + label, + description, + id, +}: SwitchProps) { + const handleClick = () => { + onCheckedChange(!checked); + }; + + return ( +
+ {label && ( + + )} + + {description &&
{description}
} + +
{ + if (e.key === " " || e.key === "Enter") { + e.preventDefault(); + handleClick(); + } + }} + > +
+ +
+
+ +
+ +
+
+
+
+ + {/* Hidden input for form compatibility */} + {}} // Controlled by the div click handler + style={{ display: "none" }} + tabIndex={-1} + /> +
+ ); +} diff --git a/v3/src/i18n.ts b/v3/src/i18n.ts new file mode 100644 index 0000000..15ba2ed --- /dev/null +++ b/v3/src/i18n.ts @@ -0,0 +1,25 @@ +import i18n from 'i18next'; +import { initReactI18next } from 'react-i18next'; +import LanguageDetector from 'i18next-browser-languagedetector'; +import HttpApi from 'i18next-http-backend'; + +i18n + .use(initReactI18next) // passes i18n down to react-i18next + .use(LanguageDetector) + .use(HttpApi) + .init({ + supportedLngs: ['en'], + fallbackLng: 'en', + detection: { + order: ['path', 'cookie', 'htmlTag', 'localStorage', 'subdomain'], + caches: ['cookie'], + }, + backend: { + loadPath: '/locales/{{lng}}/translation.json', + }, + react: { + useSuspense: true, + }, + }); + +export default i18n; diff --git a/v3/src/main.tsx b/v3/src/main.tsx new file mode 100644 index 0000000..0d860b5 --- /dev/null +++ b/v3/src/main.tsx @@ -0,0 +1,14 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import { MemoizedApp as App } from "./components/App"; +import "@fontsource-variable/martian-mono"; +import "./styles.css"; +import "./i18n"; + +// Mount React app +const root = ReactDOM.createRoot(document.getElementById("root")!); +root.render( + + + +); diff --git a/v3/src/store/appStore.ts b/v3/src/store/appStore.ts new file mode 100644 index 0000000..6883e20 --- /dev/null +++ b/v3/src/store/appStore.ts @@ -0,0 +1,228 @@ +import { create } from 'zustand'; +import { invoke } from '@tauri-apps/api/core'; + +export interface Installation { + FriendlyName: string; + InstallLocation: string; + Preview: boolean; + installed_preset?: { + uuid: string; + name: string; + installed_at: string; + is_creator?: boolean; + }; +} + +export interface PackInfo { + name: string; + uuid: string; + stub: string; + tonemapping: string; + bloom: string; +} + +interface AppState { + // State + installations: Installation[]; + presets: PackInfo[]; + selectedInstallations: Set; + selectedPreset: string | null; + consoleOutput: string[]; + activeTab: 'installations' | 'presets' | 'actions' | 'creator'; + toolbarOpen: boolean; + iobitPath: string | null; + + // Actions + setInstallations: (installations: Installation[]) => void; + setPresets: (presets: PackInfo[]) => void; + setSelectedInstallations: (selected: Set) => void; + setSelectedPreset: (preset: string | null) => void; + setActiveTab: (tab: 'installations' | 'presets' | 'actions' | 'creator') => void; + setToolbarOpen: (open: boolean) => void; + addConsoleOutput: (message: string) => void; + clearConsole: () => void; + addInstallation: (installation: Installation) => void; + removeInstallation: (path: string) => void; + setIobitPath: (path: string | null) => void; + + // Async actions + refreshInstallations: () => Promise; + refreshPresets: (forceRefresh?: boolean) => Promise; + clearCache: () => Promise; + installRTX: (installPath: string) => Promise; + updateOptions: (installPath: string) => Promise; + backupSupportFiles: (installPath: string) => Promise; + uninstallRTX: (installPaths: string[]) => Promise; + refreshIobitPath: () => Promise; + selectIobitPath: () => Promise; +} + +export const useAppStore = create((set, get) => ({ + // Initial state + installations: [], + presets: [], + selectedInstallations: new Set(), + selectedPreset: null, + consoleOutput: [], + activeTab: 'installations', + toolbarOpen: false, + iobitPath: null, + + // Setters + setInstallations: (installations) => set({ installations }), + setPresets: (presets) => set({ presets }), + setSelectedInstallations: (selectedInstallations) => set({ selectedInstallations }), + setSelectedPreset: (selectedPreset) => set({ selectedPreset }), + setActiveTab: (activeTab) => set({ activeTab }), + setToolbarOpen: (toolbarOpen) => set({ toolbarOpen }), + setIobitPath: (iobitPath) => set({ iobitPath }), + + addConsoleOutput: (message) => { + const timestamp = new Date().toLocaleTimeString(); + set((state) => ({ + consoleOutput: [...state.consoleOutput, `[${timestamp}] ${message}`] + })); + }, + + clearConsole: () => set({ consoleOutput: [] }), + + addInstallation: (installation) => { + set((state) => ({ + installations: [...state.installations, installation] + })); + }, + + removeInstallation: (path) => { + set((state) => ({ + installations: state.installations.filter(inst => inst.InstallLocation !== path), + selectedInstallations: new Set([...state.selectedInstallations].filter(p => p !== path)) + })); + }, + + // Async actions + refreshInstallations: async () => { + const { addConsoleOutput, setInstallations } = get(); + + try { + addConsoleOutput('Scanning for Minecraft installations...'); + + const data = await invoke('list_installations'); + setInstallations(data); + addConsoleOutput(`Found ${data.length} installations`); + } catch (error) { + const errorMsg = `Error loading installations: ${error}`; + addConsoleOutput(errorMsg); + } + }, + + refreshPresets: async (forceRefresh = false) => { + const { addConsoleOutput, setPresets } = get(); + + try { + addConsoleOutput('Fetching RTX presets...'); + + const data = await invoke('list_presets', { forceRefresh }); + setPresets(data); + addConsoleOutput(`Loaded ${data.length} presets`); + } catch (error) { + const errorMsg = `Error loading presets: ${error}`; + addConsoleOutput(errorMsg); + } + }, + + clearCache: async () => { + const { addConsoleOutput } = get(); + + try { + addConsoleOutput('Clearing cache...'); + await invoke('clear_cache'); + addConsoleOutput('Cache cleared successfully'); + } catch (error) { + const errorMsg = `Error clearing cache: ${error}`; + addConsoleOutput(errorMsg); + } + }, + + installRTX: async (installPath) => { + const { addConsoleOutput } = get(); + try { + addConsoleOutput(`Installing RTX DLSS to ${installPath}...`); + await invoke('install_dlss_for_selected', { selectedNames: [installPath] }); + addConsoleOutput('RTX DLSS installed successfully'); + } catch (error) { + const errorMsg = `Error installing RTX DLSS: ${error}`; + addConsoleOutput(errorMsg); + } + }, + + updateOptions: async (installPath) => { + const { addConsoleOutput } = get(); + try { + addConsoleOutput(`Updating options for ${installPath}...`); + await invoke('update_options_for_selected', { selectedNames: [installPath] }); + addConsoleOutput('Options updated successfully'); + } catch (error) { + const errorMsg = `Error updating options: ${error}`; + addConsoleOutput(errorMsg); + } + }, + + backupSupportFiles: async (installPath) => { + const { addConsoleOutput } = get(); + try { + addConsoleOutput(`Creating backup for ${installPath}...`); + const backupDir = await invoke('backup_selected', { destDir: 'C:\\Users\\Public\\Documents', selectedNames: [installPath] }); + addConsoleOutput(`Backup created successfully: ${backupDir}`); + } catch (error) { + const errorMsg = `Error creating backup: ${error}`; + addConsoleOutput(errorMsg); + } + }, + + uninstallRTX: async (installPaths) => { + const { addConsoleOutput } = get(); + try { + addConsoleOutput(`Uninstalling RTX from ${installPaths.length} installation(s)...`); + await invoke('uninstall_rtx', { selectedNames: installPaths }); + addConsoleOutput('RTX uninstalled successfully'); + } catch (error) { + const errorMsg = `Error uninstalling RTX: ${error}`; + addConsoleOutput(errorMsg); + throw error; + } + }, + + refreshIobitPath: async () => { + const { addConsoleOutput, setIobitPath } = get(); + + try { + const result = await invoke('check_iobit_unlocker'); + const pathMatch = result.match(/IObit Unlocker found at: (.+)/); + if (pathMatch) { + setIobitPath(pathMatch[1]); + addConsoleOutput('IObit Unlocker detected'); + } else { + setIobitPath(null); + } + } catch (error) { + setIobitPath(null); + addConsoleOutput(`IObit Unlocker not found: ${error}`); + } + }, + + selectIobitPath: async () => { + const { addConsoleOutput, setIobitPath } = get(); + + try { + const selectedPath = await invoke('open_iobit_file_dialog'); + if (selectedPath) { + const result = await invoke('set_iobit_path', { path: selectedPath }); + setIobitPath(selectedPath); + addConsoleOutput(result); + } + } catch (error) { + const errorMsg = `Error setting IObit path: ${error}`; + addConsoleOutput(errorMsg); + } + }, +})); diff --git a/v3/src/store/presetsStore.ts b/v3/src/store/presetsStore.ts new file mode 100644 index 0000000..850f2a1 --- /dev/null +++ b/v3/src/store/presetsStore.ts @@ -0,0 +1,126 @@ +import { create } from 'zustand'; +import { invoke } from "@tauri-apps/api/core"; +import { useAppStore } from './appStore'; +import { useStatusStore } from './statusStore'; + +interface PresetsStore { + selectedPreset: string | null; + installingPresets: Set; + installModalOpen: boolean; + installModalPresetUuid: string | null; + setSelectedPreset: (uuid: string | null) => void; + handlePresetSelection: (uuid: string, selected: boolean) => void; + handlePresetInstall: (uuid: string, t: (key: string, options?: any) => string) => Promise; + openInstallModal: (uuid: string) => void; + closeInstallModal: () => void; + handleInstallToSelected: (uuid: string, selectedInstallations: string[], t: (key: string, options?: any) => string) => Promise; +} + +export const usePresetsStore = create((set) => ({ + selectedPreset: null, + installingPresets: new Set(), + installModalOpen: false, + installModalPresetUuid: null, + + setSelectedPreset: (uuid: string | null) => { + set({ selectedPreset: uuid }); + }, + + handlePresetSelection: (uuid: string, selected: boolean) => { + set({ selectedPreset: selected ? uuid : null }); + }, + + handlePresetInstall: async (uuid: string, t: (key: string, options?: any) => string) => { + const { selectedInstallations, addConsoleOutput, refreshInstallations } = useAppStore.getState(); + const { addMessage } = useStatusStore.getState(); + + if (selectedInstallations.size === 0) { + addMessage({ + message: t("status_select_installation_warning"), + type: "error", + }); + return; + } + + // Add preset to installing set + set((state) => ({ + installingPresets: new Set(state.installingPresets).add(uuid) + })); + + try { + addMessage({ message: t("status_installing_preset"), type: "loading" }); + addConsoleOutput( + t("log_installing_preset", { uuid, count: selectedInstallations.size }) + ); + + for (const installPath of selectedInstallations) { + addConsoleOutput(t("log_installing_to", { installPath })); + await invoke("download_and_install_pack", { uuid, selectedNames: [installPath] }); + addConsoleOutput(t("log_installed_to", { installPath })); + } + + addMessage({ message: t("status_install_success"), type: "success" }); + addConsoleOutput(t("log_install_complete")); + // Refresh installations to show updated preset info + await refreshInstallations(); + } catch (error) { + const errorMsg = t("status_install_error", { error }); + addMessage({ message: errorMsg, type: "error" }); + addConsoleOutput(errorMsg); + } finally { + // Remove preset from installing set + set((state) => { + const newSet = new Set(state.installingPresets); + newSet.delete(uuid); + return { installingPresets: newSet }; + }); + } + }, + + openInstallModal: (uuid: string) => { + set({ installModalOpen: true, installModalPresetUuid: uuid }); + }, + + closeInstallModal: () => { + set({ installModalOpen: false, installModalPresetUuid: null }); + }, + + handleInstallToSelected: async (uuid: string, selectedInstallations: string[], t: (key: string, options?: any) => string) => { + const { addConsoleOutput, refreshInstallations } = useAppStore.getState(); + const { addMessage } = useStatusStore.getState(); + + // Add preset to installing set + set((state) => ({ + installingPresets: new Set(state.installingPresets).add(uuid) + })); + + try { + addMessage({ message: t("status_installing_preset"), type: "loading" }); + addConsoleOutput( + t("log_installing_preset", { uuid, count: selectedInstallations.length }) + ); + + for (const installPath of selectedInstallations) { + addConsoleOutput(t("log_installing_to", { installPath })); + await invoke("download_and_install_pack", { uuid, selectedNames: [installPath] }); + addConsoleOutput(t("log_installed_to", { installPath })); + } + + addMessage({ message: t("status_install_success"), type: "success" }); + addConsoleOutput(t("log_install_complete")); + // Refresh installations to show updated preset info + await refreshInstallations(); + } catch (error) { + const errorMsg = t("status_install_error", { error }); + addMessage({ message: errorMsg, type: "error" }); + addConsoleOutput(errorMsg); + } finally { + // Remove preset from installing set + set((state) => { + const newSet = new Set(state.installingPresets); + newSet.delete(uuid); + return { installingPresets: newSet }; + }); + } + }, +})); diff --git a/v3/src/store/statusStore.ts b/v3/src/store/statusStore.ts new file mode 100644 index 0000000..05cbb64 --- /dev/null +++ b/v3/src/store/statusStore.ts @@ -0,0 +1,75 @@ +import { create } from 'zustand'; + +export type StatusType = 'info' | 'error' | 'loading' | 'success'; + +export interface StatusMessage { + id: string; + message: string; + type: StatusType; + timestamp: number; +} + +interface StatusState { + messages: StatusMessage[]; + timers: Map; + addMessage: (message: Omit) => void; + removeMessage: (id: string) => void; + clearTimer: (id: string) => void; +} + +const AUTO_DISMISS_DELAY = 5000; // 5 seconds + +export const useStatusStore = create((set, get) => ({ + messages: [], + timers: new Map(), + addMessage: (message) => { + const id = new Date().toISOString() + Math.random(); + const timestamp = Date.now(); + const newMessage: StatusMessage = { ...message, id, timestamp }; + + set((state) => ({ + messages: [...state.messages, newMessage] + })); + + // Set up auto-dismiss timer (except for loading messages) + if (message.type !== 'loading') { + const timer = setTimeout(() => { + get().removeMessage(id); + }, AUTO_DISMISS_DELAY); + + set((state) => { + const newTimers = new Map(state.timers); + newTimers.set(id, timer); + return { timers: newTimers }; + }); + } + }, + removeMessage: (id) => { + const { timers } = get(); + const timer = timers.get(id); + if (timer) { + clearTimeout(timer); + } + + set((state) => { + const newTimers = new Map(state.timers); + newTimers.delete(id); + return { + messages: state.messages.filter((msg) => msg.id !== id), + timers: newTimers + }; + }); + }, + clearTimer: (id) => { + const { timers } = get(); + const timer = timers.get(id); + if (timer) { + clearTimeout(timer); + set((state) => { + const newTimers = new Map(state.timers); + newTimers.delete(id); + return { timers: newTimers }; + }); + } + } +})); diff --git a/v3/src/styles.css b/v3/src/styles.css new file mode 100644 index 0000000..226548a --- /dev/null +++ b/v3/src/styles.css @@ -0,0 +1,1294 @@ +@import "tailwindcss"; +@import "./styles/theme.css"; +@import "./styles/ui.css"; + +@keyframes rainbow-cycle { + 0% { + fill: oklch(0.901 0.058 230.902); + border-color: oklch(0.951 0.026 236.824); + box-shadow: + 0 4px 6px -1px rgba(0, 0, 0, 0.5), + 0 2px 4px -2px rgba(0, 0, 0, 0.5); + } + + 10% { + fill: oklch(0.882 0.059 254.128); + border-color: oklch(0.932 0.032 255.585); + box-shadow: + 0 4px 6px -1px rgba(0, 0, 0, 0.75), + 0 2px 4px -2px rgba(0, 0, 0, 0.5); + } + + 20% { + fill: oklch(0.894 0.057 293.283); + border-color: oklch(0.943 0.029 294.588); + box-shadow: + 0 4px 6px -1px rgba(0, 0, 0, 0.85), + 0 2px 4px -2px rgba(0, 0, 0, 0.5); + } + + 30% { + fill: oklch(0.892 0.058 10.001); + border-color: oklch(0.941 0.03 12.58); + box-shadow: + 0 4px 6px -1px rgba(0, 0, 0, 0.75), + 0 2px 4px -2px rgba(0, 0, 0, 0.5); + } + + 40% { + fill: oklch(0.885 0.062 18.334); + border-color: oklch(0.936 0.032 17.717); + box-shadow: + 0 4px 6px -1px rgba(0, 0, 0, 0.65), + 0 2px 4px -2px rgba(0, 0, 0, 0.5); + } + + 50% { + fill: oklch(0.901 0.076 70.697); + border-color: oklch(0.954 0.038 75.164); + box-shadow: + 0 4px 6px -1px rgba(0, 0, 0, 0.75), + 0 2px 4px -2px rgba(0, 0, 0, 0.5); + } + + 60% { + fill: oklch(0.924 0.12 95.746); + border-color: oklch(0.962 0.059 95.617); + box-shadow: + 0 4px 6px -1px rgba(0, 0, 0, 0.5), + 0 2px 4px -2px rgba(0, 0, 0, 0.5); + } + + 70% { + fill: oklch(0.945 0.129 101.54); + border-color: oklch(0.973 0.071 103.193); + box-shadow: + 0 4px 6px -1px rgba(0, 0, 0, 0.65), + 0 2px 4px -2px rgba(0, 0, 0, 0.5); + } + + 80% { + fill: oklch(0.938 0.127 124.321); + border-color: oklch(0.967 0.067 122.328); + box-shadow: + 0 4px 6px -1px rgba(0, 0, 0, 0.85), + 0 2px 4px -2px rgba(0, 0, 0, 0.85); + } + + 90% { + fill: oklch(0.905 0.093 164.15); + border-color: oklch(0.95 0.052 163.051); + box-shadow: + 0 4px 6px -1px rgba(0, 0, 0, 0.75), + 0 2px 4px -2px rgba(0, 0, 0, 0.75); + } + + 100% { + fill: oklch(0.917 0.08 205.041); + border-color: oklch(0.956 0.045 203.388); + box-shadow: + 0 4px 6px -1px rgba(0, 0, 0, 0.5), + 0 2px 4px -2px rgba(0, 0, 0, 0.5); + } +} + +@keyframes tab-view-enter { + 0% { + opacity: 0; + transform: translateY(4px) scale(0.995); + } + + 100% { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +/* Custom utilities - must be at top level, not nested in @layer */ +@utility panel { + @apply rounded-lg border shadow-sm bg-app-panel border-app-border; +} + +@utility panel__header { + @apply border-b px-4 py-3 border-app-border; +} + +@utility panel__title { + @apply text-base font-semibold; +} + +@utility panel__body { + @apply p-4; +} + +@utility button { + @apply btn; +} + +@utility button--primary { + @apply btn; +} + +@utility button--ghost { + background-color: transparent; + box-shadow: none; + + &:hover { + box-shadow: none; + } + + &:active { + box-shadow: none; + } +} + +@utility button--danger { + @apply text-white bg-danger-600 border-danger-700; + box-shadow: 0 3px 0 0 color-mix(in oklab, var(--color-danger-700) 80%, black 20%); + + &:hover { + @apply bg-danger-700; + transform: translateY(1px); + box-shadow: 0 2px 0 0 color-mix(in oklab, var(--color-danger-700) 80%, black 20%); + } + + &:active { + transform: translateY(3px); + box-shadow: 0 0 0 0 color-mix(in oklab, var(--color-danger-700) 80%, black 20%); + } +} + +@utility badge { + @apply inline-flex items-center rounded-md border px-2 py-0.5 text-xs font-semibold border-app-border; +} + +@utility hover-lift { + @apply transition-transform; + + &:hover { + transform: translateY(-1px); + } +} + +@utility max-h-app-tab { + @apply max-h-[calc(100vh-6rem)]; +} + +@utility max-h-app-tab-expanded { + @apply max-h-[calc(100vh-22rem)]; +} + +@utility rainbow-animate { + animation: rainbow-cycle 2s infinite; + fill: currentColor; +} + +/* Tailwind layers + BEM-style component classes for the installer UI */ +@layer base { + + html, + body, + #root { + @apply h-full; + } + + body { + @apply antialiased font-sans bg-app-bg text-app-fg overflow-hidden; + } + +} + +@layer utilities { + .max-h-app-tab { + @apply max-h-app-tab; + } + + body:has(.dialog-overlay,.console-panel--expanded) .max-h-app-tab { + @apply max-h-app-tab-expanded; + } +} + +@layer components { + + /* App shell */ + .app { + @apply grid min-h-screen; + grid-template-rows: 3rem 1fr; + } + + .app__header { + @apply sticky top-0 z-40 flex items-center gap-3 border-b px-4 border-app-border; + background-color: color-mix(in oklab, var(--color-app-bg) 85%, black 15%); + } + + .app__brand { + @apply text-sm font-semibold tracking-wide; + } + + .app__badge { + @apply inline-flex items-center rounded px-2 py-0.5 text-xs font-semibold border border-app-border bg-success-600 text-white; + } + + .app__body { + @apply grid; + grid-template-columns: 16rem 1fr; + } + + .app__sidebar { + @apply hidden md:flex md:flex-col gap-1 border-r p-4 bg-app-panel border-app-border; + } + + .app__content { + @apply p-4 md:p-6; + } + + /* Tab view transitions */ + .tab-content { + @apply relative w-full max-w-fit mx-auto p-4 h-fit overflow-auto max-h-app-tab scrollbar; + } + + body:has(.dialog-overlay,.console-panel--expanded) .tab-content { + @apply max-h-app-tab-expanded; + } + + .tab-view { + animation: tab-view-enter 180ms ease-out; + will-change: opacity, transform; + } + + @media (prefers-reduced-motion: reduce) { + .tab-view { + animation: none; + } + } + + /* Sidebar navigation */ + .nav { + @apply flex flex-col gap-1; + } + + .nav__link { + @apply w-full text-left rounded-md px-3 py-2 text-sm border transition-colors text-app-fg border-transparent bg-app-panel/95; + } + + .nav__link:hover { + @apply border-app-border bg-app-panel/90; + } + + .nav__link--active { + @apply border-minecraft-blue-600 bg-minecraft-blue-600/20; + } + + /* Panels / cards - utilities moved to top level */ + + /* Sections */ + .section { + @apply space-y-2; + } + + .section__title { + @apply text-xs font-semibold uppercase tracking-wider text-app-muted; + } + + .divider { + @apply my-4 h-px w-full bg-app-border; + } + + /* Buttons - utilities moved to top level */ + + /* Form controls */ + .field { + @apply grid gap-1; + } + + .field__label { + @apply text-xs font-medium text-app-muted; + } + + .field__control { + @apply relative; + } + + .field__input { + @apply w-full rounded-md border bg-transparent px-3 py-2 text-sm outline-none focus:ring-2 border-app-border text-app-fg; + } + + .field__input:focus { + @apply ring-brand-accent border-minecraft-blue-600; + } + + /* Badges - utilities moved to top level */ + .badge--on { + @apply text-black bg-minecraft-green-300 border-minecraft-green-400; + } + + .main-content { + @apply overflow-y-auto scrollbar m-0 mt-auto min-h-min max-h-app-tab p-0 w-full; + } + + body:has(.console-panel--expanded) .main-content { + @apply max-h-app-tab-expanded; + } + + /* Top toolbar */ + .top-toolbar { + @apply sticky top-0 z-50 flex h-12 items-center justify-between border-b px-3 md:px-4 border-app-border; + background-color: color-mix(in oklab, var(--color-app-bg) 85%, black 15%); + } + + .toolbar-right { + @apply flex items-center gap-2; + } + + .app-version { + @apply text-xs text-app-muted font-mono cursor-default; + } + + .app-version--link { + @apply cursor-pointer hover:text-brand-accent transition-colors border-0 bg-transparent p-0; + } + + .app-version--link:hover { + @apply underline; + } + + /* Secondary toolbar */ + .secondary-toolbar { + @apply sticky top-12 z-40 border-b px-4 py-3 border-app-border bg-app-panel; + } + + .secondary-toolbar__content { + @apply flex items-center justify-between; + } + + .installations-toolbar { + @apply sticky top-12 z-40 border-b px-4 py-3 border-app-border bg-app-panel; + } + + .installations-toolbar .installation-card { + @apply border-0; + } + + .app-title { + @apply text-sm md:text-base font-semibold; + } + + .nav-tabs { + @apply flex border-2 border-minecraft-slate-800 bg-minecraft-slate-900 rounded-sm; + } + + .nav-btn { + @apply flex-1 py-1.5 px-4 text-center cursor-pointer bg-minecraft-slate-600 text-white/80 border-2 border-transparent -m-px font-semibold uppercase tracking-wider text-xs; + } + + .nav-btn:hover { + @apply bg-minecraft-slate-500 text-white; + } + + .nav-btn.active { + @apply bg-minecraft-slate-500 text-white border-b-2 border-b-white/90; + } + + .toolbar-icon-btn { + @apply grid h-8 w-8 place-items-center rounded-md border text-sm border-app-border; + } + + /* Layout */ + .app-layout { + @apply relative; + } + + + .app-header h1 { + @apply text-xl font-semibold; + } + + .app-header p { + @apply text-sm; + } + + /* Slideout sidepanel (mobile) */ + .mobile-menu-toggle { + @apply md:hidden absolute left-3 top-14 z-40 grid h-10 w-10 place-items-center rounded-md border border-app-border bg-app-panel; + } + + .mobile-menu-toggle>span { + @apply block h-0.5 w-5 rounded bg-app-fg; + } + + .mobile-menu-toggle>span+span { + @apply mt-1; + } + + .actions-sidepanel { + @apply fixed left-0 top-0 z-50 h-full w-72 -translate-x-full transform border-r p-4 transition-transform md:hidden bg-app-panel border-app-border; + } + + .actions-sidepanel.open { + @apply translate-x-0; + } + + .sidepanel-header { + @apply mb-3 flex items-center justify-between; + } + + .sidepanel-content { + @apply overflow-y-auto; + } + + .sidepanel-overlay { + @apply fixed inset-0 z-40 hidden bg-black/50 md:hidden; + } + + .sidepanel-overlay.visible { + @apply block; + } + + /* Desktop/Mobile visibility helpers */ + .desktop-only { + @apply hidden md:block; + } + + .mobile-only { + @apply md:hidden; + } + + /* Section toolbars */ + .section-toolbar { + @apply mb-3 flex items-center justify-between; + } + + .toolbar-title { + @apply flex items-center gap-2; + } + + .toolbar-title h2 { + @apply text-lg font-semibold; + } + + .item-count { + @apply text-xs text-app-muted; + } + + .toolbar-actions { + @apply flex items-center gap-2; + } + + .toolbar-btn { + @apply button; + } + + /* Actions */ + .actions-list { + @apply grid gap-2; + } + + .actions-grid { + @apply grid gap-2 sm:grid-cols-2 lg:grid-cols-3; + } + + /* Installations grid/cards */ + .installations-container { + @apply flex flex-col gap-4 w-full max-w-full; + } + + .installations-grid { + @apply flex flex-col md:flex-row; + } + + .installation-card { + @apply panel flex flex-col flex-1 cursor-pointer contain-layout transition-all duration-200 rounded-lg border hover:shadow-lg bg-minecraft-slate-800/50; + } + + .installation-card.selected { + @apply border-brand-accent; + } + + .installation-header { + @apply text-sm font-semibold mb-2 flex items-center justify-between border-b border-app-border p-2 bg-app-panel rounded-t-lg; + } + + .installation-footer { + @apply p-2 bg-app-panel rounded-b-lg mt-auto; + } + + .installation-placeholder { + @apply w-full h-32 bg-app-border/20 rounded flex items-center justify-center mb-2; + } + + .installation-details { + @apply flex flex-row gap-3 transition-all duration-200 overflow-hidden my-auto; + } + + .installation-title { + @apply flex items-center gap-2; + } + + .installation-title h3 { + @apply text-sm font-semibold; + } + + .installation-checkbox { + @apply cursor-pointer accent-brand-accent bg-minecraft-slate-500 border-minecraft-slate-800 appearance-none rounded-xs; + } + + .preset-icon { + @apply w-full h-auto rounded object-cover mb-2; + } + + .preview-badge { + @apply badge bg-brand-accent-600 text-white text-xs; + border-color: color-mix(in oklab, var(--color-brand-accent-600) 70%, black 30%); + } + + .creator-badge { + @apply badge bg-green-600 text-white text-xs; + border-color: color-mix(in oklab, var(--color-green-600) 70%, black 30%); + } + + .creator-icon { + @apply w-6 h-6 rounded-full bg-gradient-to-tr from-transparent via-white/25 to-transparent hover:to-black/25 hover:from-black/25 transition-all duration-200 bg-blend-overlay text-shadow-2xs; + background-size: 200%; + + &:hover { + @apply bg-black/25; + background-position: 100% 0; + } + } + + .creator-container { + @apply flex flex-col; + } + + .creator-content { + @apply space-y-2 flex flex-col sm:grid sm:grid-cols-2 gap-4 sm:space-y-0; + } + + .installation-path { + @apply px-4 pt-2 text-xs text-app-muted break-all font-mono select-all; + } + + .installed-preset-info { + @apply space-y-1; + } + + .no-preset-info { + @apply text-app-muted; + } + + .installation-actions { + @apply panel__body flex items-center gap-2; + } + + .checkbox-label { + @apply text-xs text-app-muted; + } + + .installation-action-btn { + @apply m-0; + } + + /* Presets */ + .presets-grid { + @apply flex flex-col max-w-full gap-4; + } + + .presets-list { + @apply grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4; + } + + .preset-card { + @apply panel flex flex-col h-full cursor-pointer contain-layout transition-all duration-200 rounded-lg border p-4 hover:shadow-lg; + } + + .preset-card.selected { + @apply border-brand-accent; + } + + .preset-list:has(.preset-card.selected) .preset-card:not(.selected) { + @apply bg-red-500; + } + + .preset-header { + @apply flex items-center gap-3 px-4 py-3; + } + + .preset-title { + @apply text-sm font-semibold; + } + + .preset-header__toggle { + @apply ml-auto pl-3 border-l border-app-border text-app-muted hover:text-app-fg transition-colors h-6 flex items-center; + } + + .preset-header__chevron { + @apply transition-transform; + } + + .preset-header__chevron--rotated { + transform: rotate(180deg); + } + + .preset-link { + @apply hover:underline cursor-pointer; + } + + .preset-stub-container { + @apply w-full min-w-0 overflow-hidden relative h-10 inline-block; + scrollbar-gutter: stable; + } + + .preset-stub-container:hover { + @apply overflow-x-auto; + } + + .preset-stub-link { + @apply hover:underline cursor-pointer font-mono text-xs whitespace-nowrap block transition-all duration-200; + } + + .preset-stub-container:not(:hover) .preset-stub-link { + @apply overflow-hidden max-w-full text-ellipsis; + } + + .preset-details { + @apply flex flex-col gap-3; + } + + .preset-card__footer { + @apply mt-auto pt-4; + } + + .install-preset-btn { + @apply btn; + } + + .installation-action-btn { + @apply m-0; + } + + /* Empty state */ + .empty-state { + @apply panel p-6 text-center text-sm text-app-muted; + } + + /* Drag & drop */ + .drop-zone { + @apply my-6 grid h-28 place-items-center rounded-md border-2 border-dashed border-app-border bg-app-panel/90; + } + + .drop-zone.drag-over { + @apply border-brand-accent bg-brand-accent/10; + } + + /* Status & progress */ + .status { + @apply text-sm; + } + + .status.error { + @apply text-danger-600; + } + + .progress-container { + @apply mt-2 h-2 w-full overflow-hidden rounded bg-black/30; + } + + .progress-bar { + @apply h-full w-1/2 bg-brand-accent-600; + } + + /* Console */ + .console-container { + @apply flex flex-col overflow-hidden relative h-min; + } + + .console-header { + @apply outline-1 outline-black mt-6 flex cursor-pointer items-center justify-between border px-4 py-2 border-app-border bg-app-panel; + } + + .console-panel { + @apply overflow-hidden rounded-b-md border border-app-border bg-app-panel/50 backdrop-blur-sm; + max-height: 18rem; + transition: max-height .2s ease; + } + + .console-panel.collapsed { + max-height: 0; + border-top-width: 0; + } + + .console-panel.expanded { + max-height: 18rem; + } + + .console-output { + @apply font-mono p-4 text-xs h-64 overflow-y-auto whitespace-pre-wrap break-all relative text-app-muted contain-paint scrollbar; + } + + .console-clear-btn { + @apply m-2 self-end button; + } + + /* Toolbar menu button */ + .toolbar-menu-btn { + @apply p-2 rounded-md text-app-muted hover:text-app-fg hover:bg-app-border transition-colors border-0; + } + + /* Popovers */ + .settings-popover, + .help-popover { + @apply rounded-md border shadow-lg border-app-border bg-app-panel text-app-fg; + } + + .toolbar-popover { + @apply rounded-md border shadow-2xl outline outline-black/75 border-app-border bg-app-panel/50 backdrop-blur-xl text-app-fg fixed top-16 right-4 z-1000 min-w-80 max-w-[calc(100vw-2rem)]; + } + + .popover-header { + @apply flex items-center justify-between border-b px-4 py-2 border-app-border; + } + + .popover-content { + @apply p-4 space-y-4; + } + + .setting-group>label { + @apply block text-xs font-semibold text-muted mb-1; + } + + .setting-buttons { + @apply flex flex-wrap gap-2; + } + + .setting-btn { + @apply button; + } + + .setting-btn.danger { + @apply button--danger; + } + + .status-alert { + @apply fixed right-2 bottom-14 z-40 flex items-center gap-3 px-4 py-3 rounded-lg border shadow-lg cursor-pointer select-none; + } + + /* Console Panel Component */ + .console-container { + @apply flex flex-col overflow-hidden relative; + } + + .console-output { + @apply font-mono; + font-feature-settings: "liga" 0, "calt" 0; + } + + .console-clear-btn { + @apply font-sans; + } + + .console-panel--expanded { + @apply max-h-80; + } + + .console-panel--collapsed { + @apply max-h-0 rounded-b-xl; + } + + body:has(.console-panel--expanded) .status-alert { + @apply bottom-80; + } + + /* Switch Component */ + .switch { + @apply flex w-fit min-w-min max-w-48; + } + + .switch:hover .switch__track .switch__button { + @apply rounded-md bg-minecraft-slate-300/50; + } + + .switch:hover .switch__track[data-checked] .switch__button { + @apply bg-minecraft-slate-200; + } + + .switch:hover .switch__handle { + @apply border-x-minecraft-slate-700 border-b-minecraft-slate-800; + } + + .switch:hover .switch__track[data-checked] { + @apply border-minecraft-green-100 bg-minecraft-green-200; + } + + .switch__label { + @apply cursor-pointer select-none text-sm font-medium text-minecraft-slate-100 col-span-2 row-span-2 place-items-center; + } + + .switch__description { + @apply cursor-default select-none text-sm text-minecraft-slate-300 col-span-2; + } + + .switch__handle { + @apply -mt-2 flex size-12 -translate-x-1 items-stretch rounded-md border border-b-4 border-minecraft-slate-400 border-b-minecraft-slate-600 bg-minecraft-slate-200 shadow-md outline-1 outline-minecraft-slate-900/50 transition duration-300 ease-out group-data-checked:translate-x-12 cursor-pointer; + } + + .switch:hover .switch__handle { + @apply border-x-minecraft-slate-700 border-b-minecraft-slate-800 cursor-pointer; + } + + .switch__button { + @apply h-full w-full rounded-sm border-2 border-minecraft-slate-50/75 pt-4 transition duration-200 ease-out cursor-pointer; + } + + .switch:hover .switch__track .switch__button { + @apply rounded-md bg-minecraft-slate-300/50; + } + + .switch:hover .switch__track[data-checked] .switch__button { + @apply bg-minecraft-slate-200; + } + + .switch__track { + @apply relative row-span-2 mt-2 inline-flex h-10 w-24 items-center place-self-center justify-self-end rounded-sm border-2 border-minecraft-slate-900/60 bg-minecraft-slate-300 shadow-xs outline-1 outline-black/50 transition data-checked:border-minecraft-green-200 data-checked:bg-minecraft-green-300 cursor-pointer; + } + + .switch:hover .switch__track .switch__button { + @apply rounded-md bg-minecraft-slate-300/50; + } + + .switch:hover .switch__track[data-checked] .switch__button { + @apply bg-minecraft-slate-200; + } + + .switch:hover .switch__track[data-checked] { + @apply border-minecraft-green-100 bg-minecraft-green-200; + } + + .switch__track-background { + @apply absolute flex h-full w-1/2 select-none items-center justify-center text-white/75 opacity-0 transition-opacity group-data-checked:opacity-100; + } + + .switch__track-background+.switch__track-background { + @apply right-0 rounded-r border-2 border-l-0 border-minecraft-slate-200/30 text-minecraft-slate-900/50 opacity-100 group-data-checked:opacity-0; + } + + /* Dialog Components */ + .dialog-overlay { + @apply fixed inset-0 z-50 flex items-start justify-center bg-black/50 w-full h-full max-h-app-tab; + } + + body:has(.dialog-overlay) .app-content { + @apply bg-black; + } + + body:has(.dialog-overlay,:not(.console-panel--expanded)) .main-content { + @apply max-h-app-tab; + } + + .dialog { + @apply panel max-w-md w-full mx-4 max-h-app-tab overflow-hidden flex flex-col m-10; + } + + .dialog__header { + @apply panel__header flex items-center justify-between; + } + + .dialog__title { + @apply panel__title select-none cursor-default; + } + + .dialog__close { + @apply w-6 h-6 flex items-center justify-center rounded-full text-app-muted hover:text-app-fg hover:bg-app-border transition-colors cursor-pointer leading-none; + } + + .dialog__content { + @apply panel__body flex-1 overflow-y-auto; + } + + .dialog__actions { + @apply flex justify-end gap-2 p-4 border-t border-app-border; + } + + /* Dialog Options List Styles */ + .dialog__search { + @apply relative mb-4; + } + + .dialog__search-icon { + @apply absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-app-muted; + } + + .dialog__search-input { + @apply w-full rounded-md border bg-transparent pl-10 pr-3 py-2 text-sm outline-none focus:ring-2 border-app-border text-app-fg focus:ring-brand-accent focus:border-minecraft-blue-600; + } + + .dialog__options-list { + @apply space-y-4 max-h-[60vh] overflow-y-auto scrollbar pr-1; + } + + .dialog__option-item { + @apply p-3 rounded-md border border-app-border bg-app-panel/50 hover:bg-app-panel transition-colors; + } + + .dialog__option-label { + @apply font-semibold text-sm mb-0.5; + } + + .dialog__option-description { + @apply text-xs text-app-muted mb-1; + } + + .dialog__option-original { + @apply text-xs text-brand-accent; + } + + .dialog__option-input { + @apply w-20 rounded-md border bg-transparent px-2 py-1 text-sm outline-none focus:ring-2 border-app-border text-app-fg focus:ring-brand-accent focus:border-minecraft-blue-600; + } + + .dialog__empty-message { + @apply text-center text-sm text-app-muted py-8; + } + + .dialog__selector { + @apply mb-4 border-b border-app-border pb-3; + } + + .error-message { + @apply p-3 rounded border border-danger-600 bg-danger-600/10 text-danger-600; + } + + .protocol-info { + @apply space-y-2; + } + + .protocol-details { + @apply space-y-1 text-sm; + } + + .installation-selection { + @apply space-y-2; + } + + .loading { + @apply text-center text-app-muted; + } + + /* Modal Components */ + .modal-backdrop { + @apply fixed inset-0 z-[1000] flex items-center justify-center bg-black/50; + } + + .modal { + @apply panel max-w-2xl w-full mx-4 max-h-[90vh] overflow-hidden flex flex-col; + } + + .modal__header { + @apply panel__header flex items-center justify-between; + } + + .modal__title { + @apply panel__title; + } + + .modal__close-btn { + @apply w-8 h-8 flex items-center justify-center rounded text-app-muted hover:text-app-fg hover:bg-app-border transition-colors text-xl leading-none cursor-pointer; + } + + .modal__content { + @apply panel__body flex-1 overflow-y-auto; + } + + /* Installation Instance Modal */ + .installation-modal { + @apply max-w-3xl; + } + + .installation-modal__content { + @apply space-y-4; + } + + .installation-modal__description { + @apply text-sm text-app-muted; + } + + .installation-modal__controls { + @apply flex items-center justify-between border-b pb-3 border-app-border; + } + + .installation-modal__count { + @apply text-sm text-app-muted; + } + + .installation-modal__list { + @apply space-y-2 max-h-80 overflow-y-auto; + } + + .installation-item { + @apply panel cursor-pointer transition-all duration-200 hover:border-brand-accent/50; + } + + .installation-item--selected { + @apply border-brand-accent bg-brand-accent/5; + } + + .installation-item__label { + @apply flex items-center gap-3 p-3 cursor-pointer; + } + + .installation-item__checkbox { + @apply mt-1 cursor-pointer accent-brand-accent; + } + + .installation-item__info { + @apply flex flex-col items-start flex-1 space-y-1 pl-1 cursor-pointer select-none; + } + + .installation-item__name { + @apply font-semibold text-sm; + } + + .installation-item__path { + @apply text-xs text-app-muted font-mono break-all; + } + + .installation-item__badge { + @apply badge bg-brand-accent-600 text-white text-xs; + } + + .installation-item__preset { + @apply text-xs text-app-muted; + } + + .installation-modal__actions { + @apply flex justify-end gap-2 pt-4 border-t border-app-border; + } + + /* Sidebar Navigation */ + .sidebar-nav { + @apply flex flex-col h-full bg-app-panel border-r border-app-border w-64; + } + + .sidebar-nav__header { + @apply px-4 py-3 border-b border-app-border; + } + + .sidebar-nav__title { + @apply text-xs font-semibold uppercase tracking-wider text-app-muted; + } + + .sidebar-nav__content { + @apply flex flex-col gap-1 p-2 overflow-y-auto scrollbar max-h-app-tab-expanded; + scrollbar-gutter: stable; + } + + .sidebar-nav__item { + @apply flex items-center gap-3 w-full text-left rounded-md px-3 py-3 text-sm border transition-colors text-app-fg border-transparent bg-transparent hover:bg-app-panel/50; + } + + .sidebar-nav__item:hover { + @apply border-app-border bg-app-panel/90; + } + + .sidebar-nav__item--active { + @apply border-brand-accent bg-brand-accent/10 text-minecraft-blue-50; + } + + .sidebar-nav__item-icon { + @apply flex-shrink-0 text-app-muted; + } + + .sidebar-nav__item--active .sidebar-nav__item-icon { + @apply text-brand-accent; + } + + .sidebar-nav__item-content { + @apply flex flex-col gap-0.5 min-w-0 flex-1; + } + + .sidebar-nav__item-label { + @apply font-medium text-sm; + } + + .sidebar-nav__item-description { + @apply text-xs text-app-muted leading-tight; + } + + .sidebar-nav__item--active .sidebar-nav__item-description { + @apply text-minecraft-blue-200; + } + + .disclaimer { + @apply border-t border-t-app-border/75 mt-auto text-sm min-h-72 h-fit bg-black/25 border-x border-x-minecraft-slate-900; + } + + .disclaimer__header { + @apply flex items-center justify-between px-2 h-12 border-t border-t-minecraft-slate-900/50 cursor-pointer z-10; + } + + .disclaimer.disclaimer--collapsed { + @apply h-36 min-h-fit; + } + + .disclaimer h2 { + @apply text-xs font-semibold uppercase tracking-wider text-app-muted select-none cursor-pointer; + } + + .disclaimer p { + @apply text-xs h-full text-minecraft-slate-200 mt-2 pb-4 select-none cursor-default px-2 transition-[height] duration-150 ease-out will-change-[height]; + } + + .disclaimer.disclaimer--collapsed p { + @apply h-0; + } + + /* App Layout with Sidebar */ + .app-with-sidebar { + @apply flex min-h-screen bg-app-bg text-app-fg; + } + + .app-sidebar { + @apply flex-shrink-0 border-t border-l border-l-minecraft-slate-500 border-black; + } + + .app-main { + @apply flex-1 flex flex-col min-w-0; + } + + .app-content { + @apply flex-1 overflow-hidden border-l border-black; + } + + /* Dropzone Overlay */ + .dropzone-overlay { + @apply fixed inset-0 z-50 pointer-events-none transition-opacity duration-300 ease-in-out; + } + + .dropzone-overlay--hidden { + @apply opacity-0; + } + + .dropzone-overlay--active { + @apply opacity-100; + } + + .dropzone-overlay__glow { + @apply absolute inset-0 bg-gradient-to-r from-brand-accent/20 via-brand-accent/10 to-brand-accent/20; + box-shadow: + inset 0 0 100px 20px rgba(var(--color-brand-accent-rgb), 0.15), + inset 0 0 200px 40px rgba(var(--color-brand-accent-rgb), 0.1), + inset 0 0 300px 60px rgba(var(--color-brand-accent-rgb), 0.05); + backdrop-filter: blur(1px); + } + + .dropzone-overlay__content { + @apply absolute inset-0 flex flex-col items-center justify-center text-center; + } + + .dropzone-overlay__icon { + @apply text-brand-accent mb-4 drop-shadow-lg; + filter: drop-shadow(0 0 20px rgba(var(--color-brand-accent-rgb), 0.5)); + } + + .dropzone-overlay__text { + @apply text-2xl font-bold text-brand-accent drop-shadow-lg; + text-shadow: 0 0 20px rgba(var(--color-brand-accent-rgb), 0.8); + } + + /* IOBit Panel Components */ + .iobit-panel { + @apply panel space-y-4; + } + + .iobit-header { + @apply flex items-center justify-between; + } + + .iobit-title { + @apply text-lg font-semibold; + } + + .iobit-status { + @apply badge text-xs font-medium; + } + + .iobit-status.found { + @apply bg-minecraft-green-600 text-white border-minecraft-green-700; + } + + .iobit-status.not-found { + @apply bg-minecraft-red-600 text-white border-minecraft-red-700; + } + + .iobit-content { + @apply space-y-4; + } + + .iobit-help { + @apply text-sm text-app-muted; + } + + .iobit-path-section { + @apply space-y-2; + } + + .iobit-path-label { + @apply text-xs font-semibold; + } + + .iobit-path-display { + @apply panel__body min-h-10 flex items-center bg-app-panel/50; + } + + .iobit-path-text { + @apply font-mono text-sm text-app-fg break-all select-all; + } + + .iobit-path-empty { + @apply text-sm text-app-muted italic; + } + + .iobit-actions { + @apply flex items-center gap-3; + } + + .iobit-button { + @apply button; + } + + .iobit-button:hover { + @apply hover-lift; + } + + .iobit-button:disabled { + @apply opacity-50 cursor-not-allowed; + transform: none; + } + + .iobit-button.auto-detect { + @apply bg-minecraft-blue-600 text-white border-minecraft-blue-600; + } + + .iobit-button.browse { + @apply bg-app-panel text-app-fg border-app-border; + } + + .iobit-success { + @apply p-3 rounded-md border border-minecraft-green-600 bg-minecraft-green-600/10 text-minecraft-green-600; + } + + .iobit-success p { + @apply text-sm font-medium; + } + + .language-select { + @apply flex items-center scheme-dark cursor-pointer bg-minecraft-slate-800 text-minecraft-slate-100 rounded-md border border-minecraft-slate-700 p-1 text-sm has-[select:focus]:border-minecraft-blue-600; + } + + .language-select select { + @apply appearance-none bg-minecraft-slate-800 text-minecraft-slate-100 focus:outline-none w-fit mr-2; + } + + .language-select + .language-menu-icon { + @apply pointer-events-none relative z-10 -left-8; + } +} \ No newline at end of file diff --git a/v3/src/styles/theme.css b/v3/src/styles/theme.css new file mode 100644 index 0000000..9994efc --- /dev/null +++ b/v3/src/styles/theme.css @@ -0,0 +1,86 @@ +@import "tailwindcss"; +@import "@fontsource-variable/inter"; +@import "@fontsource-variable/martian-mono"; +@theme { + --font-mono: 'Martian Mono Variable', 'Martian Mono', system-monospace, monospace; + --font-sans: 'Inter Variable', Inter, Helvetica, Arial, system-ui, sans-serif; + --height-app: calc(100vh - (--spacing(16))); + + --width-app: calc(100vw - (--spacing(64))); + --width-sidenav: --spacing(64); + + --color-minecraft-slate-50: oklch(99.33% 0.0011 197.14deg); + --color-minecraft-slate-100: oklch(92.75% 0.0017 247.84deg); + --color-minecraft-slate-200: oklch(86.08% 0.0043 271.36deg); + --color-minecraft-slate-300: oklch(64.35% 0.0046 271.34deg); + --color-minecraft-slate-400: oklch(53.09% 0.0067 264.51deg); + --color-minecraft-slate-500: oklch(49.54% 0.0054 258.34deg); + --color-minecraft-slate-600: oklch(47.06% 0.0021 247.87deg); + --color-minecraft-slate-700: oklch(40.48% 0.0021 247.88deg); + --color-minecraft-slate-800: oklch(31.65% 0.0023 247.90deg); + --color-minecraft-slate-900: oklch(23.54% 0.0019 286.25deg); + + --color-minecraft-blue-50: oklch(97.09% 0.0125 255.50deg); + --color-minecraft-blue-100: oklch(93.43% 0.0293 252.95deg); + --color-minecraft-blue-200: oklch(88.50% 0.0529 252.79deg); + --color-minecraft-blue-300: oklch(81.38% 0.0890 249.96deg); + --color-minecraft-blue-400: oklch(72.20% 0.1313 252.45deg); + --color-minecraft-blue-500: oklch(63.16% 0.1729 258.52deg); + --color-minecraft-blue-600: oklch(55.97% 0.1962 262.16deg); + --color-minecraft-blue-700: oklch(49.75% 0.2002 263.90deg); + --color-minecraft-blue-800: oklch(43.15% 0.1669 265.52deg); + --color-minecraft-blue-900: oklch(38.65% 0.1273 264.74deg); + + --color-minecraft-green-50: oklch(71.41% 0.1403 137.92deg); + --color-minecraft-green-100: oklch(64.63% 0.1684 138.72deg); + --color-minecraft-green-200: oklch(54.98% 0.1477 139.52deg); + --color-minecraft-green-300: oklch(46.35% 0.1168 139.76deg); + --color-minecraft-green-400: oklch(37.34% 0.1020 140.48deg); + --color-minecraft-green-500: oklch(40.21% 0.0936 139.86deg); + --color-minecraft-green-600: oklch(36.45% 0.0776 140.27deg); + --color-minecraft-green-700: oklch(33.22% 0.0730 141.61deg); + --color-minecraft-green-800: oklch(29.53% 0.0610 141.10deg); + --color-minecraft-green-900: oklch(27.32% 0.0505 138.53deg); + + --color-minecraft-purple-50: oklch(97.00% 0.0133 286.15deg); + --color-minecraft-purple-100: oklch(94.42% 0.0255 290.74deg); + --color-minecraft-purple-200: oklch(89.59% 0.0491 289.19deg); + --color-minecraft-purple-300: oklch(81.32% 0.0925 290.43deg); + --color-minecraft-purple-400: oklch(71.10% 0.1449 290.41deg); + --color-minecraft-purple-500: oklch(60.38% 0.2013 289.50deg); + --color-minecraft-purple-600: oklch(53.85% 0.2266 289.76deg); + --color-minecraft-purple-700: oklch(47.97% 0.2262 288.96deg); + --color-minecraft-purple-800: oklch(42.29% 0.1967 289.28deg); + --color-minecraft-purple-900: oklch(37.21% 0.1651 289.87deg); + --color-minecraft-purple-950: oklch(27.78% 0.1260 287.63deg); + + --color-minecraft-red-50: oklch(97.19% 0.0108 17.34deg); + --color-minecraft-red-100: oklch(93.96% 0.0241 17.59deg); + --color-minecraft-red-200: oklch(89.04% 0.0475 18.08deg); + --color-minecraft-red-300: oklch(81.48% 0.0837 19.04deg); + --color-minecraft-red-400: oklch(71.46% 0.1345 20.98deg); + --color-minecraft-red-500: oklch(63.05% 0.1719 23.41deg); + --color-minecraft-red-600: oklch(55.90% 0.1851 25.52deg); + --color-minecraft-red-700: oklch(49.07% 0.1638 25.63deg); + --color-minecraft-red-800: oklch(43.37% 0.1374 24.96deg); + --color-minecraft-red-900: oklch(39.10% 0.1124 23.91deg); + --color-minecraft-red-950: oklch(25.35% 0.0735 23.99deg); + + --color-muted: oklch(60% 0.1719 23.41deg); + + /* App semantic color tokens */ + --color-app-bg: var(--color-minecraft-slate-900); + --color-app-panel: var(--color-minecraft-slate-800); + --color-app-elev: var(--color-minecraft-slate-700); + --color-app-fg: var(--color-minecraft-slate-50); + --color-app-muted: var(--color-minecraft-slate-300); + --color-app-border: var(--color-minecraft-slate-700); + --color-brand-accent: var(--color-minecraft-blue-500); + --color-brand-accent-600: var(--color-minecraft-blue-600); + --color-success: var(--color-minecraft-green-500); + --color-success-600: var(--color-minecraft-green-600); + --color-danger: var(--color-minecraft-red-600); + --color-danger-600: var(--color-minecraft-red-600); + --color-danger-700: var(--color-minecraft-red-700); + --color-danger-800: var(--color-minecraft-red-800); + } \ No newline at end of file diff --git a/v3/src/styles/ui.css b/v3/src/styles/ui.css new file mode 100644 index 0000000..561690e --- /dev/null +++ b/v3/src/styles/ui.css @@ -0,0 +1,93 @@ +@import 'tailwindcss'; +@import "./theme.css"; + +@utility scrollbar { + scrollbar-color: #5a5b5c #1e1e1f; + scrollbar-width: thin; +} + +@utility btn { + @apply mt-2 flex items-stretch justify-stretch overflow-hidden rounded-sm rounded-b-md border-b-4 border-minecraft-slate-900 border-b-minecraft-slate-800 bg-minecraft-slate-700 text-lg font-semibold uppercase tracking-wide text-minecraft-slate-100 cursor-pointer outline-2 outline-minecraft-slate-900 transition-colors duration-200 ease-out; + + .btn__wrapper { + @apply flex w-full flex-col items-center justify-center whitespace-nowrap rounded-sm border-2 border-minecraft-slate-600 p-4 text-sm font-medium transition-colors duration-200 ease-out; + } + + &:hover { + @apply bg-minecraft-slate-600; + } + + &:hover .btn__wrapper { + @apply border-minecraft-slate-400; + } + + &:active, + &:active .btn__wrapper { + @apply border-b-2; + } + + &:active, + &[disabled] { + @apply mt-2 translate-y-0.5; + } + + &[disabled] { + @apply mt-1 cursor-not-allowed border-b; + } + + &[disabled] &__wrapper { + @apply border-minecraft-slate-400 bg-minecraft-slate-600 text-minecraft-slate-100; + } + + &.btn--primary { + @apply border-minecraft-green-300 bg-minecraft-green-500 text-white/90; + + .btn__wrapper { + @apply border-minecraft-green-200 bg-minecraft-green-400 text-white/90; + } + + &:hover { + @apply bg-minecraft-green-300; + } + + &:hover .btn__wrapper { + @apply border-minecraft-green-100 bg-minecraft-green-300; + } + + &[disabled] { + @apply border-minecraft-green-900 bg-minecraft-slate-400; + } + + &[disabled] .btn__wrapper { + @apply border-minecraft-green-500/50 bg-minecraft-green-800/50 text-white/25; + } + } + + &.btn--secondary { + @apply border-minecraft-slate-400 bg-minecraft-slate-200/90; + + .btn__wrapper { + @apply border-minecraft-slate-50/75 text-minecraft-slate-900; + } + + &:hover { + @apply bg-minecraft-slate-300; + } + + &:hover .btn__wrapper { + @apply border-minecraft-slate-100 bg-minecraft-slate-200; + } + + &[disabled] { + @apply border-minecraft-slate-900 bg-minecraft-slate-400; + } + + &[disabled] .btn__wrapper { + @apply border-minecraft-slate-50/50 bg-minecraft-slate-200/50 text-minecraft-slate-900/50; + } + } + + &.btn--small .btn__wrapper { + @apply p-2 text-xs; + } +} \ No newline at end of file diff --git a/v3/src/vite-env.d.ts b/v3/src/vite-env.d.ts new file mode 100644 index 0000000..ffbaa47 --- /dev/null +++ b/v3/src/vite-env.d.ts @@ -0,0 +1,9 @@ +/// + +declare module '*.svg' { + import * as React from 'react'; + + export const ReactComponent: React.FunctionComponent & { title?: string }>; + const src: string; + export default src; +} diff --git a/v3/tsconfig.json b/v3/tsconfig.json new file mode 100644 index 0000000..95d1426 --- /dev/null +++ b/v3/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* React */ + "jsx": "react-jsx", + "jsxImportSource": "react", + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/v3/vite.config.ts b/v3/vite.config.ts new file mode 100644 index 0000000..b4bae00 --- /dev/null +++ b/v3/vite.config.ts @@ -0,0 +1,32 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import tailwindcss from "@tailwindcss/vite"; + +const host = process.env.TAURI_DEV_HOST; + +// https://vite.dev/config/ +export default defineConfig(async () => ({ + + // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` + // + // 1. prevent Vite from obscuring rust errors + clearScreen: false, + // 2. tauri expects a fixed port, fail if that port is not available + server: { + port: 1420, + strictPort: true, + host, + hmr: host + ? { + protocol: "ws", + host, + port: 1421, + } + : undefined, + watch: { + // 3. tell Vite to ignore watching `src-tauri` + ignored: ["**/src-tauri/**"], + }, + }, + plugins: [react(), tailwindcss()], +}));