From 73e83b4cb5b1adcd0463fc32d558d71a391b9264 Mon Sep 17 00:00:00 2001 From: NtskwK Date: Fri, 16 Jan 2026 09:13:38 +0800 Subject: chore: add UI linter workflow with oxlint and oxfmt --- .github/workflows/check.yml | 2 +- .github/workflows/lint.yml | 93 +++++++++++++++++++++++ ui/package.json | 7 +- ui/pnpm-lock.yaml | 179 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 279 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/lint.yml diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 0cbcf35..702cc0a 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -4,7 +4,7 @@ on: push: paths: - "ui/**" - - ".github/workflows/ui_check.yml" + - ".github/workflows/check.yml" pull_request: branches: ["main", "master", "dev"] workflow_dispatch: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..c8d1b27 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,93 @@ +name: UI Linter + +on: + workflow_run: + workflows: ["UI Checker"] + types: + - completed + workflow_dispatch: + inputs: + commit_message: + description: "Commit Message" + type: string + required: false + +jobs: + check: + if: | + (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') || + github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + steps: + - name: Check if it is a direct push + id: check_push + run: | + if [[ "${{ github.event_name }}" == "workflow_run" ]]; then + if [[ "${{ github.event.workflow_run.event }}" == "pull_request" ]]; then + echo "PR detected. Exiting." + echo "is_pr=True" >> $GITHUB_OUTPUT + else + echo "Direct push or other event detected. Proceeding..." + echo "is_pr=False" >> $GITHUB_OUTPUT + fi + else + echo "Manual trigger detected. Proceeding..." + echo "is_pr=False" >> $GITHUB_OUTPUT + fi + + - name: Checkout repository + if: steps.check_push.outputs.is_pr != 'True' + uses: actions/checkout@v6 + with: + show-progress: false + persist-credentials: false + + - name: Install pnpm + if: steps.check_push.outputs.is_pr != 'True' + uses: pnpm/action-setup@v4 + with: + version: 9 + run_install: true + package_json_file: ui/package.json + + - name: Install Node.js + if: steps.check_push.outputs.is_pr != 'True' + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: "pnpm" + cache-dependency-path: ui/pnpm-lock.yaml + + - run: pnpm format + working-directory: ui + + - run: pnpm lint:fix + working-directory: ui + + - name: Commit changes + id: commit_changes + if: steps.check_push.outputs.is_pr != 'True' + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + git add . + + if git diff-index --quiet HEAD --; then + echo "No changes to commit" + else + commit_msg="${{ github.event.inputs.commit_message }}" + if [ -z "$commit_msg" ]; then + commit_msg="chore: Auto Templates Optimization" + fi + git commit -m "$commit_msg" -m "Triggered by ${{github.sha}}" -m "[skip changelog]" + git pull origin $(git rev-parse --abbrev-ref HEAD) --unshallow --rebase + echo "have_commits=True" >> $GITHUB_OUTPUT + fi + + - name: Push changes + if: steps.check_push.outputs.is_pr != 'True' && steps.commit_changes.outputs.have_commits == 'True' && github.repository_owner == 'MAA1999' + uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + branch: ${{ github.ref }} diff --git a/ui/package.json b/ui/package.json index 2db214a..05dd2b2 100644 --- a/ui/package.json +++ b/ui/package.json @@ -7,7 +7,10 @@ "dev": "vite", "build": "vite build", "preview": "vite preview", - "check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json" + "check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json", + "lint": "oxlint .", + "lint:fix": "oxlint . --fix", + "format": "oxfmt . --write" }, "devDependencies": { "@sveltejs/vite-plugin-svelte": "^6.2.1", @@ -15,6 +18,8 @@ "@tsconfig/svelte": "^5.0.6", "@types/node": "^24.10.1", "autoprefixer": "^10.4.23", + "oxfmt": "^0.24.0", + "oxlint": "^1.39.0", "postcss": "^8.5.6", "svelte": "^5.46.4", "svelte-check": "^4.3.4", diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml index df9ddc7..390862c 100644 --- a/ui/pnpm-lock.yaml +++ b/ui/pnpm-lock.yaml @@ -48,6 +48,12 @@ importers: autoprefixer: specifier: ^10.4.23 version: 10.4.23(postcss@8.5.6) + oxfmt: + specifier: ^0.24.0 + version: 0.24.0 + oxlint: + specifier: ^1.39.0 + version: 1.39.0 postcss: specifier: ^8.5.6 version: 8.5.6 @@ -104,6 +110,86 @@ packages: '@oxc-project/types@0.97.0': resolution: {integrity: sha512-lxmZK4xFrdvU0yZiDwgVQTCvh2gHWBJCBk5ALsrtsBWhs0uDIi+FTOnXRQeQfs304imdvTdaakT/lqwQ8hkOXQ==} + '@oxfmt/darwin-arm64@0.24.0': + resolution: {integrity: sha512-aYXuGf/yq8nsyEcHindGhiz9I+GEqLkVq8CfPbd+6VE259CpPEH+CaGHEO1j6vIOmNr8KHRq+IAjeRO2uJpb8A==} + cpu: [arm64] + os: [darwin] + + '@oxfmt/darwin-x64@0.24.0': + resolution: {integrity: sha512-vs3b8Bs53hbiNvcNeBilzE/+IhDTWKjSBB3v/ztr664nZk65j0xr+5IHMBNz3CFppmX7o/aBta2PxY+t+4KoPg==} + cpu: [x64] + os: [darwin] + + '@oxfmt/linux-arm64-gnu@0.24.0': + resolution: {integrity: sha512-ItPDOPoQ0wLj/s8osc5ch57uUcA1Wk8r0YdO8vLRpXA3UNg7KPOm1vdbkIZRRiSUphZcuX5ioOEetEK8H7RlTw==} + cpu: [arm64] + os: [linux] + + '@oxfmt/linux-arm64-musl@0.24.0': + resolution: {integrity: sha512-JkQO3WnQjQTJONx8nxdgVBfl6BBFfpp9bKhChYhWeakwJdr7QPOAWJ/v3FGZfr0TbqINwnNR74aVZayDDRyXEA==} + cpu: [arm64] + os: [linux] + + '@oxfmt/linux-x64-gnu@0.24.0': + resolution: {integrity: sha512-N/SXlFO+2kak5gMt0oxApi0WXQDhwA0PShR0UbkY0PwtHjfSiDqJSOumyNqgQVoroKr1GNnoRmUqjZIz6DKIcw==} + cpu: [x64] + os: [linux] + + '@oxfmt/linux-x64-musl@0.24.0': + resolution: {integrity: sha512-WM0pek5YDCQf50XQ7GLCE9sMBCMPW/NPAEPH/Hx6Qyir37lEsP4rUmSECo/QFNTU6KBc9NnsviAyJruWPpCMXw==} + cpu: [x64] + os: [linux] + + '@oxfmt/win32-arm64@0.24.0': + resolution: {integrity: sha512-vFCseli1KWtwdHrVlT/jWfZ8jP8oYpnPPEjI23mPLW8K/6GEJmmvy0PZP5NpWUFNTzX0lqie58XnrATJYAe9Xw==} + cpu: [arm64] + os: [win32] + + '@oxfmt/win32-x64@0.24.0': + resolution: {integrity: sha512-0tmlNzcyewAnauNeBCq0xmAkmiKzl+H09p0IdHy+QKrTQdtixtf+AOjDAADbRfihkS+heF15Pjc4IyJMdAAJjw==} + cpu: [x64] + os: [win32] + + '@oxlint/darwin-arm64@1.39.0': + resolution: {integrity: sha512-lT3hNhIa02xCujI6YGgjmYGg3Ht/X9ag5ipUVETaMpx5Rd4BbTNWUPif1WN1YZHxt3KLCIqaAe7zVhatv83HOQ==} + cpu: [arm64] + os: [darwin] + + '@oxlint/darwin-x64@1.39.0': + resolution: {integrity: sha512-UT+rfTWd+Yr7iJeSLd/7nF8X4gTYssKh+n77hxl6Oilp3NnG1CKRHxZDy3o3lIBnwgzJkdyUAiYWO1bTMXQ1lA==} + cpu: [x64] + os: [darwin] + + '@oxlint/linux-arm64-gnu@1.39.0': + resolution: {integrity: sha512-qocBkvS2V6rH0t9AT3DfQunMnj3xkM7srs5/Ycj2j5ZqMoaWd/FxHNVJDFP++35roKSvsRJoS0mtA8/77jqm6Q==} + cpu: [arm64] + os: [linux] + + '@oxlint/linux-arm64-musl@1.39.0': + resolution: {integrity: sha512-arZzAc1PPcz9epvGBBCMHICeyQloKtHX3eoOe62B3Dskn7gf6Q14wnDHr1r9Vp4vtcBATNq6HlKV14smdlC/qA==} + cpu: [arm64] + os: [linux] + + '@oxlint/linux-x64-gnu@1.39.0': + resolution: {integrity: sha512-ZVt5qsECpuNprdWxAPpDBwoixr1VTcZ4qAEQA2l/wmFyVPDYFD3oBY/SWACNnWBddMrswjTg9O8ALxYWoEpmXw==} + cpu: [x64] + os: [linux] + + '@oxlint/linux-x64-musl@1.39.0': + resolution: {integrity: sha512-pB0hlGyKPbxr9NMIV783lD6cWL3MpaqnZRM9MWni4yBdHPTKyFNYdg5hGD0Bwg+UP4S2rOevq/+OO9x9Bi7E6g==} + cpu: [x64] + os: [linux] + + '@oxlint/win32-arm64@1.39.0': + resolution: {integrity: sha512-Gg2SFaJohI9+tIQVKXlPw3FsPQFi/eCSWiCgwPtPn5uzQxHRTeQEZKuluz1fuzR5U70TXubb2liZi4Dgl8LJQA==} + cpu: [arm64] + os: [win32] + + '@oxlint/win32-x64@1.39.0': + resolution: {integrity: sha512-sbi25lfj74hH+6qQtb7s1wEvd1j8OQbTaH8v3xTcDjrwm579Cyh0HBv1YSZ2+gsnVwfVDiCTL1D0JsNqYXszVA==} + cpu: [x64] + os: [win32] + '@rolldown/binding-android-arm64@1.0.0-beta.50': resolution: {integrity: sha512-XlEkrOIHLyGT3avOgzfTFSjG+f+dZMw+/qd+Y3HLN86wlndrB/gSimrJCk4gOhr1XtRtEKfszpadI3Md4Z4/Ag==} engines: {node: ^20.19.0 || >=22.12.0} @@ -535,6 +621,21 @@ packages: obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + oxfmt@0.24.0: + resolution: {integrity: sha512-UjeM3Peez8Tl7IJ9s5UwAoZSiDRMww7BEc21gDYxLq3S3/KqJnM3mjNxsoSHgmBvSeX6RBhoVc2MfC/+96RdSw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + oxlint@1.39.0: + resolution: {integrity: sha512-wSiLr0wjG+KTU6c1LpVoQk7JZ7l8HCKlAkVDVTJKWmCGazsNxexxnOXl7dsar92mQcRnzko5g077ggP3RINSjA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + oxlint-tsgolint: '>=0.10.0' + peerDependenciesMeta: + oxlint-tsgolint: + optional: true + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -633,6 +734,10 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + tinypool@2.0.0: + resolution: {integrity: sha512-/RX9RzeH2xU5ADE7n2Ykvmi9ED3FBGPAjw9u3zucrNNaEBIO0HPSYgL0NT7+3p147ojeSdaVu08F6hjpv31HJg==} + engines: {node: ^20.0.0 || >=22.0.0} + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -713,6 +818,54 @@ snapshots: '@oxc-project/types@0.97.0': {} + '@oxfmt/darwin-arm64@0.24.0': + optional: true + + '@oxfmt/darwin-x64@0.24.0': + optional: true + + '@oxfmt/linux-arm64-gnu@0.24.0': + optional: true + + '@oxfmt/linux-arm64-musl@0.24.0': + optional: true + + '@oxfmt/linux-x64-gnu@0.24.0': + optional: true + + '@oxfmt/linux-x64-musl@0.24.0': + optional: true + + '@oxfmt/win32-arm64@0.24.0': + optional: true + + '@oxfmt/win32-x64@0.24.0': + optional: true + + '@oxlint/darwin-arm64@1.39.0': + optional: true + + '@oxlint/darwin-x64@1.39.0': + optional: true + + '@oxlint/linux-arm64-gnu@1.39.0': + optional: true + + '@oxlint/linux-arm64-musl@1.39.0': + optional: true + + '@oxlint/linux-x64-gnu@1.39.0': + optional: true + + '@oxlint/linux-x64-musl@1.39.0': + optional: true + + '@oxlint/win32-arm64@1.39.0': + optional: true + + '@oxlint/win32-x64@1.39.0': + optional: true + '@rolldown/binding-android-arm64@1.0.0-beta.50': optional: true @@ -1028,6 +1181,30 @@ snapshots: obug@2.1.1: {} + oxfmt@0.24.0: + dependencies: + tinypool: 2.0.0 + optionalDependencies: + '@oxfmt/darwin-arm64': 0.24.0 + '@oxfmt/darwin-x64': 0.24.0 + '@oxfmt/linux-arm64-gnu': 0.24.0 + '@oxfmt/linux-arm64-musl': 0.24.0 + '@oxfmt/linux-x64-gnu': 0.24.0 + '@oxfmt/linux-x64-musl': 0.24.0 + '@oxfmt/win32-arm64': 0.24.0 + '@oxfmt/win32-x64': 0.24.0 + + oxlint@1.39.0: + optionalDependencies: + '@oxlint/darwin-arm64': 1.39.0 + '@oxlint/darwin-x64': 1.39.0 + '@oxlint/linux-arm64-gnu': 1.39.0 + '@oxlint/linux-arm64-musl': 1.39.0 + '@oxlint/linux-x64-gnu': 1.39.0 + '@oxlint/linux-x64-musl': 1.39.0 + '@oxlint/win32-arm64': 1.39.0 + '@oxlint/win32-x64': 1.39.0 + picocolors@1.1.1: {} picomatch@4.0.3: {} @@ -1125,6 +1302,8 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 + tinypool@2.0.0: {} + tslib@2.8.1: optional: true -- cgit v1.2.3-70-g09d2 From 36138be8e858cb35845014421aeaa7fa6a426217 Mon Sep 17 00:00:00 2001 From: 简律纯 Date: Fri, 16 Jan 2026 09:26:30 +0800 Subject: refactor(ci): Refactor lint workflow for auto fixes on push Updated the lint workflow to automatically fix issues on push to main or dev branches. Changed the checkout action version and adjusted the commit message for auto commits. --- .github/workflows/lint.yml | 83 ++++++++++------------------------------------ 1 file changed, 17 insertions(+), 66 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c8d1b27..5188f4e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,62 +1,35 @@ -name: UI Linter +name: UI Auto Fix on: - workflow_run: - workflows: ["UI Checker"] - types: - - completed + push: + branches: ["main", "dev"] + paths: + - "ui/**" workflow_dispatch: - inputs: - commit_message: - description: "Commit Message" - type: string - required: false jobs: - check: - if: | - (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') || - github.event_name == 'workflow_dispatch' + fix: + if: github.event_name != 'pull_request' runs-on: ubuntu-latest steps: - - name: Check if it is a direct push - id: check_push - run: | - if [[ "${{ github.event_name }}" == "workflow_run" ]]; then - if [[ "${{ github.event.workflow_run.event }}" == "pull_request" ]]; then - echo "PR detected. Exiting." - echo "is_pr=True" >> $GITHUB_OUTPUT - else - echo "Direct push or other event detected. Proceeding..." - echo "is_pr=False" >> $GITHUB_OUTPUT - fi - else - echo "Manual trigger detected. Proceeding..." - echo "is_pr=False" >> $GITHUB_OUTPUT - fi - - - name: Checkout repository - if: steps.check_push.outputs.is_pr != 'True' - uses: actions/checkout@v6 + - uses: actions/checkout@v4 with: - show-progress: false - persist-credentials: false - + token: ${{ secrets.GITHUB_TOKEN }} + - name: Install pnpm - if: steps.check_push.outputs.is_pr != 'True' uses: pnpm/action-setup@v4 with: version: 9 - run_install: true - package_json_file: ui/package.json - name: Install Node.js - if: steps.check_push.outputs.is_pr != 'True' uses: actions/setup-node@v4 with: node-version: 22 cache: "pnpm" - cache-dependency-path: ui/pnpm-lock.yaml + cache-dependency-path: "ui/pnpm-lock.yaml" + + - run: pnpm install + working-directory: ui - run: pnpm format working-directory: ui @@ -65,29 +38,7 @@ jobs: working-directory: ui - name: Commit changes - id: commit_changes - if: steps.check_push.outputs.is_pr != 'True' - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - - git add . - - if git diff-index --quiet HEAD --; then - echo "No changes to commit" - else - commit_msg="${{ github.event.inputs.commit_message }}" - if [ -z "$commit_msg" ]; then - commit_msg="chore: Auto Templates Optimization" - fi - git commit -m "$commit_msg" -m "Triggered by ${{github.sha}}" -m "[skip changelog]" - git pull origin $(git rev-parse --abbrev-ref HEAD) --unshallow --rebase - echo "have_commits=True" >> $GITHUB_OUTPUT - fi - - - name: Push changes - if: steps.check_push.outputs.is_pr != 'True' && steps.commit_changes.outputs.have_commits == 'True' && github.repository_owner == 'MAA1999' - uses: ad-m/github-push-action@master + uses: stefanzweifel/git-auto-commit-action@v5 with: - github_token: ${{ secrets.GITHUB_TOKEN }} - branch: ${{ github.ref }} + commit_message: "style: auto format and lint fix [skip ci]" + file_pattern: "ui/**" -- cgit v1.2.3-70-g09d2 From 3fa38e2d8a081db6f41ef65c37157442f77845d4 Mon Sep 17 00:00:00 2001 From: 简律纯 Date: Fri, 16 Jan 2026 09:30:39 +0800 Subject: chore(ci): Rename workflow from 'UI Auto Fix' to 'UI Auto Lint' --- .github/workflows/lint.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 5188f4e..5c4a69d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,4 +1,4 @@ -name: UI Auto Fix +name: UI Auto Lint on: push: @@ -7,6 +7,9 @@ on: - "ui/**" workflow_dispatch: +permissions: + contents: write + jobs: fix: if: github.event_name != 'pull_request' -- cgit v1.2.3-70-g09d2 From a3a1991fbd04dbe283734a8826e60d05ab3fa92a Mon Sep 17 00:00:00 2001 From: 简律纯 Date: Fri, 16 Jan 2026 09:33:04 +0800 Subject: fix(ci): Enhance GitHub Actions workflow with lint and format Add linting and formatting checks to workflow. --- .github/workflows/check.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 702cc0a..58fc378 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -6,7 +6,9 @@ on: - "ui/**" - ".github/workflows/check.yml" pull_request: - branches: ["main", "master", "dev"] + paths: + - "ui/**" + - ".github/workflows/check.yml" workflow_dispatch: jobs: @@ -32,3 +34,10 @@ jobs: - run: pnpm check working-directory: ui + + - run: pnpm lint + working-directory: ui + + - run: pnpm format --check + working-directory: ui + -- cgit v1.2.3-70-g09d2 From 963b4b8567ac1bd8b23c41e1bfbd6a99d202d1ed Mon Sep 17 00:00:00 2001 From: HsiangNianian <44714368+HsiangNianian@users.noreply.github.com> Date: Fri, 16 Jan 2026 01:35:23 +0000 Subject: style: auto format and lint fix [skip ci] --- ui/README.md | 4 +- ui/package.json | 20 +-- ui/src/app.css | 6 +- ui/src/lib/effects/ConstellationEffect.ts | 129 ++++++++--------- ui/src/lib/effects/SaturnEffect.ts | 233 +++++++++++++++--------------- ui/src/lib/modLoaderApi.ts | 12 +- ui/src/main.ts | 12 +- ui/src/stores/auth.svelte.ts | 27 ++-- ui/src/stores/logs.svelte.ts | 46 +++--- ui/src/stores/settings.svelte.ts | 89 ++++++------ ui/src/stores/ui.svelte.ts | 4 +- ui/src/types/index.ts | 1 - ui/svelte.config.js | 4 +- ui/tsconfig.json | 5 +- ui/vite.config.ts | 18 +-- 15 files changed, 311 insertions(+), 299 deletions(-) diff --git a/ui/README.md b/ui/README.md index e6cd94f..a45e2a0 100644 --- a/ui/README.md +++ b/ui/README.md @@ -42,6 +42,6 @@ If you have state that's important to retain within a component, consider creati ```ts // store.ts // An extremely simple external store -import { writable } from 'svelte/store' -export default writable(0) +import { writable } from "svelte/store"; +export default writable(0); ``` diff --git a/ui/package.json b/ui/package.json index 05dd2b2..82f8db3 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,7 +1,7 @@ { "name": "@dropout/ui", - "private": true, "version": "0.0.0", + "private": true, "type": "module", "scripts": { "dev": "vite", @@ -12,6 +12,15 @@ "lint:fix": "oxlint . --fix", "format": "oxfmt . --write" }, + "dependencies": { + "@tauri-apps/api": "^2.9.1", + "@tauri-apps/plugin-dialog": "^2.5.0", + "@tauri-apps/plugin-fs": "^2.4.5", + "@tauri-apps/plugin-shell": "^2.3.4", + "lucide-svelte": "^0.562.0", + "marked": "^17.0.1", + "node-emoji": "^2.2.0" + }, "devDependencies": { "@sveltejs/vite-plugin-svelte": "^6.2.1", "@tailwindcss/vite": "^4.1.18", @@ -31,14 +40,5 @@ "overrides": { "vite": "npm:rolldown-vite@7.2.5" } - }, - "dependencies": { - "@tauri-apps/api": "^2.9.1", - "@tauri-apps/plugin-dialog": "^2.5.0", - "@tauri-apps/plugin-fs": "^2.4.5", - "@tauri-apps/plugin-shell": "^2.3.4", - "lucide-svelte": "^0.562.0", - "marked": "^17.0.1", - "node-emoji": "^2.2.0" } } diff --git a/ui/src/app.css b/ui/src/app.css index 82aa72f..63449b7 100644 --- a/ui/src/app.css +++ b/ui/src/app.css @@ -100,7 +100,9 @@ input[type="email"], textarea { background-color: rgba(0, 0, 0, 0.4); border: 1px solid rgba(255, 255, 255, 0.1); - transition: border-color 0.2s ease, box-shadow 0.2s ease; + transition: + border-color 0.2s ease, + box-shadow 0.2s ease; } input[type="text"]:focus, @@ -148,7 +150,7 @@ input[type="checkbox"]:checked { } input[type="checkbox"]:checked::after { - content: ''; + content: ""; position: absolute; left: 5px; top: 2px; diff --git a/ui/src/lib/effects/ConstellationEffect.ts b/ui/src/lib/effects/ConstellationEffect.ts index 2cc702e..d2db529 100644 --- a/ui/src/lib/effects/ConstellationEffect.ts +++ b/ui/src/lib/effects/ConstellationEffect.ts @@ -1,4 +1,3 @@ - export class ConstellationEffect { private canvas: HTMLCanvasElement; private ctx: CanvasRenderingContext2D; @@ -17,7 +16,7 @@ export class ConstellationEffect { constructor(canvas: HTMLCanvasElement) { this.canvas = canvas; this.ctx = canvas.getContext("2d", { alpha: true })!; - + // Bind methods this.animate = this.animate.bind(this); this.handleMouseMove = this.handleMouseMove.bind(this); @@ -25,7 +24,7 @@ export class ConstellationEffect { // Initial setup this.resize(window.innerWidth, window.innerHeight); this.initParticles(); - + // Mouse interaction window.addEventListener("mousemove", this.handleMouseMove); @@ -44,10 +43,10 @@ export class ConstellationEffect { this.canvas.style.height = `${height}px`; this.ctx.scale(dpr, dpr); - + // Re-initialize if screen size changes significantly to maintain density if (this.particles.length === 0) { - this.initParticles(); + this.initParticles(); } } @@ -71,9 +70,9 @@ export class ConstellationEffect { animate() { this.ctx.clearRect(0, 0, this.width, this.height); - + // Update and draw particles - this.particles.forEach(p => { + this.particles.forEach((p) => { p.update(this.width, this.height); p.draw(this.ctx); }); @@ -86,41 +85,41 @@ export class ConstellationEffect { private drawConnections() { this.ctx.lineWidth = 1; - + for (let i = 0; i < this.particles.length; i++) { - const p1 = this.particles[i]; - - // Connect to mouse if close - const distMouse = Math.hypot(p1.x - this.mouseX, p1.y - this.mouseY); - if (distMouse < this.connectionDistance + 50) { - const alpha = 1 - (distMouse / (this.connectionDistance + 50)); - this.ctx.strokeStyle = `rgba(255, 255, 255, ${alpha * 0.4})`; // Brighter near mouse - this.ctx.beginPath(); - this.ctx.moveTo(p1.x, p1.y); - this.ctx.lineTo(this.mouseX, this.mouseY); - this.ctx.stroke(); - - // Gently attract to mouse - if (distMouse > 10) { - p1.x += (this.mouseX - p1.x) * 0.005; - p1.y += (this.mouseY - p1.y) * 0.005; - } + const p1 = this.particles[i]; + + // Connect to mouse if close + const distMouse = Math.hypot(p1.x - this.mouseX, p1.y - this.mouseY); + if (distMouse < this.connectionDistance + 50) { + const alpha = 1 - distMouse / (this.connectionDistance + 50); + this.ctx.strokeStyle = `rgba(255, 255, 255, ${alpha * 0.4})`; // Brighter near mouse + this.ctx.beginPath(); + this.ctx.moveTo(p1.x, p1.y); + this.ctx.lineTo(this.mouseX, this.mouseY); + this.ctx.stroke(); + + // Gently attract to mouse + if (distMouse > 10) { + p1.x += (this.mouseX - p1.x) * 0.005; + p1.y += (this.mouseY - p1.y) * 0.005; } - - // Connect to other particles - for (let j = i + 1; j < this.particles.length; j++) { - const p2 = this.particles[j]; - const dist = Math.hypot(p1.x - p2.x, p1.y - p2.y); - - if (dist < this.connectionDistance) { - const alpha = 1 - (dist / this.connectionDistance); - this.ctx.strokeStyle = `rgba(255, 255, 255, ${alpha * 0.15})`; - this.ctx.beginPath(); - this.ctx.moveTo(p1.x, p1.y); - this.ctx.lineTo(p2.x, p2.y); - this.ctx.stroke(); - } + } + + // Connect to other particles + for (let j = i + 1; j < this.particles.length; j++) { + const p2 = this.particles[j]; + const dist = Math.hypot(p1.x - p2.x, p1.y - p2.y); + + if (dist < this.connectionDistance) { + const alpha = 1 - dist / this.connectionDistance; + this.ctx.strokeStyle = `rgba(255, 255, 255, ${alpha * 0.15})`; + this.ctx.beginPath(); + this.ctx.moveTo(p1.x, p1.y); + this.ctx.lineTo(p2.x, p2.y); + this.ctx.stroke(); } + } } } @@ -131,33 +130,33 @@ export class ConstellationEffect { } class Particle { - x: number; - y: number; - vx: number; - vy: number; - size: number; - - constructor(w: number, h: number, speed: number) { - this.x = Math.random() * w; - this.y = Math.random() * h; - this.vx = (Math.random() - 0.5) * speed; - this.vy = (Math.random() - 0.5) * speed; - this.size = Math.random() * 2 + 1; - } + x: number; + y: number; + vx: number; + vy: number; + size: number; + + constructor(w: number, h: number, speed: number) { + this.x = Math.random() * w; + this.y = Math.random() * h; + this.vx = (Math.random() - 0.5) * speed; + this.vy = (Math.random() - 0.5) * speed; + this.size = Math.random() * 2 + 1; + } - update(w: number, h: number) { - this.x += this.vx; - this.y += this.vy; + update(w: number, h: number) { + this.x += this.vx; + this.y += this.vy; - // Bounce off walls - if (this.x < 0 || this.x > w) this.vx *= -1; - if (this.y < 0 || this.y > h) this.vy *= -1; - } + // Bounce off walls + if (this.x < 0 || this.x > w) this.vx *= -1; + if (this.y < 0 || this.y > h) this.vy *= -1; + } - draw(ctx: CanvasRenderingContext2D) { - ctx.fillStyle = "rgba(255, 255, 255, 0.4)"; - ctx.beginPath(); - ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); - ctx.fill(); - } + draw(ctx: CanvasRenderingContext2D) { + ctx.fillStyle = "rgba(255, 255, 255, 0.4)"; + ctx.beginPath(); + ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); + ctx.fill(); + } } diff --git a/ui/src/lib/effects/SaturnEffect.ts b/ui/src/lib/effects/SaturnEffect.ts index 42aee66..357da9d 100644 --- a/ui/src/lib/effects/SaturnEffect.ts +++ b/ui/src/lib/effects/SaturnEffect.ts @@ -6,7 +6,7 @@ export class SaturnEffect { private ctx: CanvasRenderingContext2D; private width: number = 0; private height: number = 0; - + // Data-oriented design for performance // xyz: Float32Array where [i*3, i*3+1, i*3+2] corresponds to x, y, z private xyz: Float32Array | null = null; @@ -23,7 +23,7 @@ export class SaturnEffect { private lastMouseX: number = 0; private lastMouseTime: number = 0; private mouseVelocities: number[] = []; // Store recent velocities for averaging - + // Rotation speed control private readonly baseSpeed: number = 0.005; // Original rotation speed private currentSpeed: number = 0.005; // Current rotation speed (can be modified by mouse) @@ -35,15 +35,15 @@ export class SaturnEffect { constructor(canvas: HTMLCanvasElement) { this.canvas = canvas; - this.ctx = canvas.getContext('2d', { + this.ctx = canvas.getContext("2d", { alpha: true, - desynchronized: false // default is usually fine, 'desynchronized' can help latency but might flicker + desynchronized: false, // default is usually fine, 'desynchronized' can help latency but might flicker })!; - + // Initial resize will set up everything this.resize(window.innerWidth, window.innerHeight); this.initParticles(); - + this.animate = this.animate.bind(this); this.animate(); } @@ -60,24 +60,24 @@ export class SaturnEffect { handleMouseMove(clientX: number) { if (!this.isDragging) return; - + const currentTime = performance.now(); const deltaTime = currentTime - this.lastMouseTime; - + if (deltaTime > 0) { const deltaX = clientX - this.lastMouseX; const velocity = deltaX / deltaTime; // pixels per millisecond - + // Store recent velocities (keep last 5 for smoothing) this.mouseVelocities.push(velocity); if (this.mouseVelocities.length > 5) { this.mouseVelocities.shift(); } - + // Apply direct rotation while dragging this.angle += deltaX * 0.002; } - + this.lastMouseX = clientX; this.lastMouseTime = currentTime; } @@ -98,22 +98,22 @@ export class SaturnEffect { handleTouchMove(clientX: number) { if (!this.isDragging) return; - + const currentTime = performance.now(); const deltaTime = currentTime - this.lastMouseTime; - + if (deltaTime > 0) { const deltaX = clientX - this.lastMouseX; const velocity = deltaX / deltaTime; - + this.mouseVelocities.push(velocity); if (this.mouseVelocities.length > 5) { this.mouseVelocities.shift(); } - + this.angle += deltaX * 0.002; } - + this.lastMouseX = clientX; this.lastMouseTime = currentTime; } @@ -127,39 +127,40 @@ export class SaturnEffect { private applyFlingVelocity() { // Calculate average velocity from recent samples - const avgVelocity = this.mouseVelocities.reduce((a, b) => a + b, 0) / this.mouseVelocities.length; - + const avgVelocity = + this.mouseVelocities.reduce((a, b) => a + b, 0) / this.mouseVelocities.length; + // Threshold for considering it a "fling" (pixels per millisecond) const flingThreshold = 0.3; // Threshold for considering the rotation as "stopped" by user const stopThreshold = 0.1; - + if (Math.abs(avgVelocity) > flingThreshold) { // User flung it - start rotating again this.isStopped = false; - + // Determine new direction based on fling direction const newDirection = avgVelocity > 0 ? 1 : -1; - + // If direction changed, update it permanently if (newDirection !== this.rotationDirection) { this.rotationDirection = newDirection; } - + // Calculate speed boost based on fling strength // Map velocity to speed multiplier (stronger fling = faster rotation) const speedMultiplier = Math.min( this.maxSpeedMultiplier, - this.minSpeedMultiplier + Math.abs(avgVelocity) * 10 + this.minSpeedMultiplier + Math.abs(avgVelocity) * 10, ); - + this.currentSpeed = this.baseSpeed * speedMultiplier; } else if (Math.abs(avgVelocity) < stopThreshold) { // User gently released - keep it stopped this.isStopped = true; this.currentSpeed = 0; } - // If velocity is between stopThreshold and flingThreshold, + // If velocity is between stopThreshold and flingThreshold, // keep current state (don't change isStopped) } @@ -167,17 +168,17 @@ export class SaturnEffect { const dpr = window.devicePixelRatio || 1; this.width = width; this.height = height; - + this.canvas.width = width * dpr; this.canvas.height = height * dpr; this.canvas.style.width = `${width}px`; this.canvas.style.height = `${height}px`; - + this.ctx.scale(dpr, dpr); // Dynamic scaling based on screen size const minDim = Math.min(width, height); - this.scaleFactor = minDim * 0.45; + this.scaleFactor = minDim * 0.45; } initParticles() { @@ -197,65 +198,68 @@ export class SaturnEffect { // 1. Planet for (let i = 0; i < planetCount; i++) { - const theta = Math.random() * Math.PI * 2; - const phi = Math.acos((Math.random() * 2) - 1); - const r = 1.0; - - // x, y, z - this.xyz[idx * 3] = r * Math.sin(phi) * Math.cos(theta); - this.xyz[idx * 3 + 1] = r * Math.sin(phi) * Math.sin(theta); - this.xyz[idx * 3 + 2] = r * Math.cos(phi); - - this.types[idx] = 0; // 0 for planet - idx++; + const theta = Math.random() * Math.PI * 2; + const phi = Math.acos(Math.random() * 2 - 1); + const r = 1.0; + + // x, y, z + this.xyz[idx * 3] = r * Math.sin(phi) * Math.cos(theta); + this.xyz[idx * 3 + 1] = r * Math.sin(phi) * Math.sin(theta); + this.xyz[idx * 3 + 2] = r * Math.cos(phi); + + this.types[idx] = 0; // 0 for planet + idx++; } // 2. Rings - const ringInner = 1.4; + const ringInner = 1.4; const ringOuter = 2.3; - + for (let i = 0; i < ringCount; i++) { - const angle = Math.random() * Math.PI * 2; - const dist = Math.sqrt(Math.random() * (ringOuter*ringOuter - ringInner*ringInner) + ringInner*ringInner); - - // x, y, z - this.xyz[idx * 3] = dist * Math.cos(angle); - this.xyz[idx * 3 + 1] = (Math.random() - 0.5) * 0.05; - this.xyz[idx * 3 + 2] = dist * Math.sin(angle); - - this.types[idx] = 1; // 1 for ring - idx++; + const angle = Math.random() * Math.PI * 2; + const dist = Math.sqrt( + Math.random() * (ringOuter * ringOuter - ringInner * ringInner) + ringInner * ringInner, + ); + + // x, y, z + this.xyz[idx * 3] = dist * Math.cos(angle); + this.xyz[idx * 3 + 1] = (Math.random() - 0.5) * 0.05; + this.xyz[idx * 3 + 2] = dist * Math.sin(angle); + + this.types[idx] = 1; // 1 for ring + idx++; } } animate() { this.ctx.clearRect(0, 0, this.width, this.height); - + // Normal blending - this.ctx.globalCompositeOperation = 'source-over'; - + this.ctx.globalCompositeOperation = "source-over"; + // Update rotation speed - decay towards base speed while maintaining direction if (!this.isDragging && !this.isStopped) { if (this.currentSpeed > this.baseSpeed) { // Gradually decay speed back to base speed - this.currentSpeed = this.baseSpeed + (this.currentSpeed - this.baseSpeed) * this.speedDecayRate; - + this.currentSpeed = + this.baseSpeed + (this.currentSpeed - this.baseSpeed) * this.speedDecayRate; + // Snap to base speed when close enough if (this.currentSpeed - this.baseSpeed < 0.00001) { this.currentSpeed = this.baseSpeed; } } - + // Apply rotation with current speed and direction this.angle += this.currentSpeed * this.rotationDirection; } - const cx = this.width * 0.6; + const cx = this.width * 0.6; const cy = this.height * 0.5; - + // Pre-calculate rotation matrices const rotationY = this.angle; - const rotationX = 0.4; + const rotationX = 0.4; const rotationZ = 0.15; const sinY = Math.sin(rotationY); @@ -265,66 +269,66 @@ export class SaturnEffect { const sinZ = Math.sin(rotationZ); const cosZ = Math.cos(rotationZ); - const fov = 1500; + const fov = 1500; const scaleFactor = this.scaleFactor; if (!this.xyz || !this.types) return; for (let i = 0; i < this.count; i++) { - const x = this.xyz[i * 3]; - const y = this.xyz[i * 3 + 1]; - const z = this.xyz[i * 3 + 2]; - - // Apply Scale - const px = x * scaleFactor; - const py = y * scaleFactor; - const pz = z * scaleFactor; - - // 1. Rotate Y - const x1 = px * cosY - pz * sinY; - const z1 = pz * cosY + px * sinY; - // y1 = py - - // 2. Rotate X - const y2 = py * cosX - z1 * sinX; - const z2 = z1 * cosX + py * sinX; - // x2 = x1 - - // 3. Rotate Z - const x3 = x1 * cosZ - y2 * sinZ; - const y3 = y2 * cosZ + x1 * sinZ; - const z3 = z2; - - const scale = fov / (fov + z3); - - if (z3 > -fov) { - const x2d = cx + x3 * scale; - const y2d = cy + y3 * scale; - - // Size calculation - slightly larger dots to compensate for lower count - // Previously Planet 2.0 -> 2.4, Ring 1.3 -> 1.5 - const type = this.types[i]; - const sizeBase = type === 0 ? 2.4 : 1.5; - const size = sizeBase * scale; - - // Opacity - let alpha = (scale * scale * scale); - if (alpha > 1) alpha = 1; - if (alpha < 0.15) continue; // Skip very faint particles for performance - - // Optimization: Planet color vs Ring color - if (type === 0) { - // Planet: Warm White - this.ctx.fillStyle = `rgba(255, 240, 220, ${alpha})`; - } else { - // Ring: Cool White - this.ctx.fillStyle = `rgba(220, 240, 255, ${alpha})`; - } - - // Render as squares (fillRect) instead of circles (arc) - // This is significantly faster for software rendering and reduces GPU usage. - this.ctx.fillRect(x2d, y2d, size, size); + const x = this.xyz[i * 3]; + const y = this.xyz[i * 3 + 1]; + const z = this.xyz[i * 3 + 2]; + + // Apply Scale + const px = x * scaleFactor; + const py = y * scaleFactor; + const pz = z * scaleFactor; + + // 1. Rotate Y + const x1 = px * cosY - pz * sinY; + const z1 = pz * cosY + px * sinY; + // y1 = py + + // 2. Rotate X + const y2 = py * cosX - z1 * sinX; + const z2 = z1 * cosX + py * sinX; + // x2 = x1 + + // 3. Rotate Z + const x3 = x1 * cosZ - y2 * sinZ; + const y3 = y2 * cosZ + x1 * sinZ; + const z3 = z2; + + const scale = fov / (fov + z3); + + if (z3 > -fov) { + const x2d = cx + x3 * scale; + const y2d = cy + y3 * scale; + + // Size calculation - slightly larger dots to compensate for lower count + // Previously Planet 2.0 -> 2.4, Ring 1.3 -> 1.5 + const type = this.types[i]; + const sizeBase = type === 0 ? 2.4 : 1.5; + const size = sizeBase * scale; + + // Opacity + let alpha = scale * scale * scale; + if (alpha > 1) alpha = 1; + if (alpha < 0.15) continue; // Skip very faint particles for performance + + // Optimization: Planet color vs Ring color + if (type === 0) { + // Planet: Warm White + this.ctx.fillStyle = `rgba(255, 240, 220, ${alpha})`; + } else { + // Ring: Cool White + this.ctx.fillStyle = `rgba(220, 240, 255, ${alpha})`; } + + // Render as squares (fillRect) instead of circles (arc) + // This is significantly faster for software rendering and reduces GPU usage. + this.ctx.fillRect(x2d, y2d, size, size); + } } this.animationId = requestAnimationFrame(this.animate); @@ -334,4 +338,3 @@ export class SaturnEffect { cancelAnimationFrame(this.animationId); } } - diff --git a/ui/src/lib/modLoaderApi.ts b/ui/src/lib/modLoaderApi.ts index 9d0d09d..75f404a 100644 --- a/ui/src/lib/modLoaderApi.ts +++ b/ui/src/lib/modLoaderApi.ts @@ -34,7 +34,7 @@ export async function getFabricLoaderVersions(): Promise * Get Fabric loaders available for a specific Minecraft version. */ export async function getFabricLoadersForVersion( - gameVersion: string + gameVersion: string, ): Promise { return invoke("get_fabric_loaders_for_version", { gameVersion, @@ -46,7 +46,7 @@ export async function getFabricLoadersForVersion( */ export async function installFabric( gameVersion: string, - loaderVersion: string + loaderVersion: string, ): Promise { return invoke("install_fabric", { gameVersion, @@ -66,7 +66,7 @@ export async function listInstalledFabricVersions(): Promise { */ export async function isFabricInstalled( gameVersion: string, - loaderVersion: string + loaderVersion: string, ): Promise { return invoke("is_fabric_installed", { gameVersion, @@ -86,9 +86,7 @@ export async function getForgeGameVersions(): Promise { /** * Get Forge versions available for a specific Minecraft version. */ -export async function getForgeVersionsForGame( - gameVersion: string -): Promise { +export async function getForgeVersionsForGame(gameVersion: string): Promise { return invoke("get_forge_versions_for_game", { gameVersion, }); @@ -99,7 +97,7 @@ export async function getForgeVersionsForGame( */ export async function installForge( gameVersion: string, - forgeVersion: string + forgeVersion: string, ): Promise { return invoke("install_forge", { gameVersion, diff --git a/ui/src/main.ts b/ui/src/main.ts index 664a057..d47b930 100644 --- a/ui/src/main.ts +++ b/ui/src/main.ts @@ -1,9 +1,9 @@ -import { mount } from 'svelte' -import './app.css' -import App from './App.svelte' +import { mount } from "svelte"; +import "./app.css"; +import App from "./App.svelte"; const app = mount(App, { - target: document.getElementById('app')!, -}) + target: document.getElementById("app")!, +}); -export default app +export default app; diff --git a/ui/src/stores/auth.svelte.ts b/ui/src/stores/auth.svelte.ts index eb9dccd..1b613a7 100644 --- a/ui/src/stores/auth.svelte.ts +++ b/ui/src/stores/auth.svelte.ts @@ -14,7 +14,7 @@ export class AuthState { deviceCodeData = $state(null); msLoginLoading = $state(false); msLoginStatus = $state("Waiting for authorization..."); - + private pollInterval: ReturnType | null = null; private isPollingRequestActive = false; private authProgressUnlisten: UnlistenFn | null = null; @@ -87,9 +87,7 @@ export class AuthState { this.setupAuthProgressListener(); try { - this.deviceCodeData = (await invoke( - "start_microsoft_login" - )) as DeviceCodeResponse; + this.deviceCodeData = (await invoke("start_microsoft_login")) as DeviceCodeResponse; if (this.deviceCodeData) { try { @@ -99,13 +97,17 @@ export class AuthState { } open(this.deviceCodeData.verification_uri); - logsState.addLog("info", "Auth", "Microsoft login started, waiting for browser authorization..."); + logsState.addLog( + "info", + "Auth", + "Microsoft login started, waiting for browser authorization...", + ); console.log("Starting polling for token..."); const intervalMs = (this.deviceCodeData.interval || 5) * 1000; this.pollInterval = setInterval( () => this.checkLoginStatus(this.deviceCodeData!.device_code), - intervalMs + intervalMs, ); } } catch (e) { @@ -159,7 +161,11 @@ export class AuthState { this.stopPolling(); this.cleanupAuthListener(); this.isLoginModalOpen = false; - logsState.addLog("info", "Auth", `Login successful! Welcome, ${this.currentAccount.username}`); + logsState.addLog( + "info", + "Auth", + `Login successful! Welcome, ${this.currentAccount.username}`, + ); uiState.setStatus("Welcome back, " + this.currentAccount.username); } catch (e: any) { const errStr = e.toString(); @@ -169,11 +175,8 @@ export class AuthState { console.error("Polling Error:", errStr); this.msLoginStatus = "Error: " + errStr; logsState.addLog("error", "Auth", `Login error: ${errStr}`); - - if ( - errStr.includes("expired_token") || - errStr.includes("access_denied") - ) { + + if (errStr.includes("expired_token") || errStr.includes("access_denied")) { this.stopPolling(); this.cleanupAuthListener(); alert("Login failed: " + errStr); diff --git a/ui/src/stores/logs.svelte.ts b/ui/src/stores/logs.svelte.ts index 5491f70..5df9abc 100644 --- a/ui/src/stores/logs.svelte.ts +++ b/ui/src/stores/logs.svelte.ts @@ -17,7 +17,14 @@ function parseGameLogLevel(levelStr: string): LogEntry["level"] { if (upper === "INFO") return "info"; if (upper === "WARN" || upper === "WARNING") return "warn"; if (upper === "ERROR" || upper === "SEVERE") return "error"; - if (upper === "DEBUG" || upper === "TRACE" || upper === "FINE" || upper === "FINER" || upper === "FINEST") return "debug"; + if ( + upper === "DEBUG" || + upper === "TRACE" || + upper === "FINE" || + upper === "FINER" || + upper === "FINEST" + ) + return "debug"; if (upper === "FATAL") return "fatal"; return "info"; } @@ -37,8 +44,9 @@ export class LogsState { addLog(level: LogEntry["level"], source: string, message: string) { const now = new Date(); - const timestamp = now.toLocaleTimeString() + "." + now.getMilliseconds().toString().padStart(3, "0"); - + const timestamp = + now.toLocaleTimeString() + "." + now.getMilliseconds().toString().padStart(3, "0"); + this.logs.push({ id: this.nextId++, timestamp, @@ -60,7 +68,7 @@ export class LogsState { // Parse game output and extract level/source addGameLog(rawLine: string, isStderr: boolean) { const match = rawLine.match(GAME_LOG_REGEX); - + if (match) { const [, thread, levelStr, extraSource, message] = match; const level = parseGameLogLevel(levelStr); @@ -105,33 +113,33 @@ export class LogsState { // Download Events (Summarized) await listen("download-start", (e) => { - this.addLog("info", "Downloader", `Starting batch download of ${e.payload} files...`); + this.addLog("info", "Downloader", `Starting batch download of ${e.payload} files...`); }); await listen("download-complete", () => { - this.addLog("info", "Downloader", "All downloads completed."); + this.addLog("info", "Downloader", "All downloads completed."); }); // Listen to file download progress to log finished files await listen("download-progress", (e) => { - const p = e.payload; - if (p.status === "Finished") { - if (p.file.endsWith(".jar")) { - this.addLog("info", "Downloader", `Downloaded ${p.file}`); - } + const p = e.payload; + if (p.status === "Finished") { + if (p.file.endsWith(".jar")) { + this.addLog("info", "Downloader", `Downloaded ${p.file}`); } + } }); // Java Download await listen("java-download-progress", (e) => { - const p = e.payload; - if (p.status === "Downloading" && p.percentage === 0) { - this.addLog("info", "JavaInstaller", `Downloading Java: ${p.file_name}`); - } else if (p.status === "Completed") { - this.addLog("info", "JavaInstaller", `Java installed: ${p.file_name}`); - } else if (p.status === "Error") { - this.addLog("error", "JavaInstaller", `Java download error`); - } + const p = e.payload; + if (p.status === "Downloading" && p.percentage === 0) { + this.addLog("info", "JavaInstaller", `Downloading Java: ${p.file_name}`); + } else if (p.status === "Completed") { + this.addLog("info", "JavaInstaller", `Java installed: ${p.file_name}`); + } else if (p.status === "Error") { + this.addLog("error", "JavaInstaller", `Java download error`); + } }); } } diff --git a/ui/src/stores/settings.svelte.ts b/ui/src/stores/settings.svelte.ts index b85e5fb..12e4a1c 100644 --- a/ui/src/stores/settings.svelte.ts +++ b/ui/src/stores/settings.svelte.ts @@ -28,7 +28,7 @@ export class SettingsState { log_upload_service: "paste.rs", pastebin_api_key: undefined, }); - + // Convert background path to proper asset URL get backgroundUrl(): string | undefined { if (this.settings.custom_background_path) { @@ -42,59 +42,59 @@ export class SettingsState { // Java download modal state showJavaDownloadModal = $state(false); selectedDownloadSource = $state("adoptium"); - + // Java catalog state javaCatalog = $state(null); isLoadingCatalog = $state(false); catalogError = $state(""); - + // Version selection state selectedMajorVersion = $state(null); selectedImageType = $state<"jre" | "jdk">("jre"); showOnlyRecommended = $state(true); searchQuery = $state(""); - + // Download progress state isDownloadingJava = $state(false); downloadProgress = $state(null); javaDownloadStatus = $state(""); - + // Pending downloads pendingDownloads = $state([]); - + // Event listener cleanup private progressUnlisten: UnlistenFn | null = null; // Computed: filtered releases based on selection get filteredReleases(): JavaReleaseInfo[] { if (!this.javaCatalog) return []; - + let releases = this.javaCatalog.releases; - + // Filter by major version if selected if (this.selectedMajorVersion !== null) { - releases = releases.filter(r => r.major_version === this.selectedMajorVersion); + releases = releases.filter((r) => r.major_version === this.selectedMajorVersion); } - + // Filter by image type - releases = releases.filter(r => r.image_type === this.selectedImageType); - + releases = releases.filter((r) => r.image_type === this.selectedImageType); + // Filter by recommended (LTS) versions if (this.showOnlyRecommended) { - releases = releases.filter(r => r.is_lts); + releases = releases.filter((r) => r.is_lts); } - + // Filter by search query if (this.searchQuery.trim()) { const query = this.searchQuery.toLowerCase(); releases = releases.filter( - r => + (r) => r.release_name.toLowerCase().includes(query) || r.version.toLowerCase().includes(query) || - r.major_version.toString().includes(query) + r.major_version.toString().includes(query), ); } - + return releases; } @@ -102,36 +102,39 @@ export class SettingsState { get availableMajorVersions(): number[] { if (!this.javaCatalog) return []; let versions = [...this.javaCatalog.available_major_versions]; - + // Filter by LTS if showOnlyRecommended is enabled if (this.showOnlyRecommended) { - versions = versions.filter(v => this.javaCatalog!.lts_versions.includes(v)); + versions = versions.filter((v) => this.javaCatalog!.lts_versions.includes(v)); } - + // Sort descending (newest first) return versions.sort((a, b) => b - a); } // Get installation status for a release: 'installed' | 'download' - getInstallStatus(release: JavaReleaseInfo): 'installed' | 'download' { + getInstallStatus(release: JavaReleaseInfo): "installed" | "download" { // Find installed Java that matches the major version and image type (by path pattern) - const matchingInstallations = this.javaInstallations.filter(inst => { + const matchingInstallations = this.javaInstallations.filter((inst) => { // Check if this is a DropOut-managed Java (path contains temurin-XX-jre/jdk pattern) const pathLower = inst.path.toLowerCase(); const pattern = `temurin-${release.major_version}-${release.image_type}`; return pathLower.includes(pattern); }); - + // If any matching installation exists, it's installed - return matchingInstallations.length > 0 ? 'installed' : 'download'; + return matchingInstallations.length > 0 ? "installed" : "download"; } // Computed: selected release details get selectedRelease(): JavaReleaseInfo | null { if (!this.javaCatalog || this.selectedMajorVersion === null) return null; - return this.javaCatalog.releases.find( - r => r.major_version === this.selectedMajorVersion && r.image_type === this.selectedImageType - ) || null; + return ( + this.javaCatalog.releases.find( + (r) => + r.major_version === this.selectedMajorVersion && r.image_type === this.selectedImageType, + ) || null + ); } async loadSettings() { @@ -145,7 +148,7 @@ export class SettingsState { } // Ensure custom_background_path is reactive if (!this.settings.custom_background_path) { - this.settings.custom_background_path = undefined; + this.settings.custom_background_path = undefined; } } catch (e) { console.error("Failed to load settings:", e); @@ -158,7 +161,7 @@ export class SettingsState { if (this.settings.custom_background_path === "") { this.settings.custom_background_path = undefined; } - + await invoke("save_settings", { config: this.settings }); uiState.setStatus("Settings saved!"); } catch (e) { @@ -193,13 +196,13 @@ export class SettingsState { this.javaDownloadStatus = ""; this.catalogError = ""; this.downloadProgress = null; - + // Setup progress event listener await this.setupProgressListener(); - + // Load catalog await this.loadJavaCatalog(false); - + // Check for pending downloads await this.loadPendingDownloads(); } @@ -219,13 +222,13 @@ export class SettingsState { if (this.progressUnlisten) { this.progressUnlisten(); } - + this.progressUnlisten = await listen( "java-download-progress", (event) => { this.downloadProgress = event.payload; this.javaDownloadStatus = event.payload.status; - + if (event.payload.status === "Completed") { this.isDownloadingJava = false; setTimeout(async () => { @@ -235,18 +238,18 @@ export class SettingsState { } else if (event.payload.status === "Error") { this.isDownloadingJava = false; } - } + }, ); } async loadJavaCatalog(forceRefresh: boolean) { this.isLoadingCatalog = true; this.catalogError = ""; - + try { const command = forceRefresh ? "refresh_java_catalog" : "fetch_java_catalog"; this.javaCatalog = await invoke(command); - + // Auto-select first LTS version if (this.selectedMajorVersion === null && this.javaCatalog.lts_versions.length > 0) { // Select most recent LTS (21 or highest) @@ -283,21 +286,21 @@ export class SettingsState { uiState.setStatus("Selected Java version is not available for this platform"); return; } - + this.isDownloadingJava = true; this.javaDownloadStatus = "Starting download..."; this.downloadProgress = null; - + try { const result: JavaInstallation = await invoke("download_adoptium_java", { majorVersion: this.selectedMajorVersion, imageType: this.selectedImageType, customPath: null, }); - + this.settings.java_path = result.path; await this.detectJava(); - + setTimeout(() => { this.showJavaDownloadModal = false; uiState.setStatus(`Java ${this.selectedMajorVersion} is ready to use!`); @@ -324,10 +327,10 @@ export class SettingsState { async resumeDownloads() { if (this.pendingDownloads.length === 0) return; - + this.isDownloadingJava = true; this.javaDownloadStatus = "Resuming download..."; - + try { const installed = await invoke("resume_java_downloads"); if (installed.length > 0) { diff --git a/ui/src/stores/ui.svelte.ts b/ui/src/stores/ui.svelte.ts index 9c29c25..e88f6b4 100644 --- a/ui/src/stores/ui.svelte.ts +++ b/ui/src/stores/ui.svelte.ts @@ -10,9 +10,9 @@ export class UIState { setStatus(msg: string) { if (this.statusTimeout) clearTimeout(this.statusTimeout); - + this.status = msg; - + if (msg !== "Ready") { this.statusTimeout = setTimeout(() => { this.status = "Ready"; diff --git a/ui/src/types/index.ts b/ui/src/types/index.ts index 0f02d64..83e7f9e 100644 --- a/ui/src/types/index.ts +++ b/ui/src/types/index.ts @@ -159,4 +159,3 @@ export interface InstalledForgeVersion { // ==================== Mod Loader Type ==================== export type ModLoaderType = "vanilla" | "fabric" | "forge"; - diff --git a/ui/svelte.config.js b/ui/svelte.config.js index 96b3455..a710f1b 100644 --- a/ui/svelte.config.js +++ b/ui/svelte.config.js @@ -1,8 +1,8 @@ -import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' +import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; /** @type {import("@sveltejs/vite-plugin-svelte").SvelteConfig} */ export default { // Consult https://svelte.dev/docs#compile-time-svelte-preprocess // for more information about preprocessors preprocess: vitePreprocess(), -} +}; diff --git a/ui/tsconfig.json b/ui/tsconfig.json index 1ffef60..d32ff68 100644 --- a/ui/tsconfig.json +++ b/ui/tsconfig.json @@ -1,7 +1,4 @@ { "files": [], - "references": [ - { "path": "./tsconfig.app.json" }, - { "path": "./tsconfig.node.json" } - ] + "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }] } diff --git a/ui/vite.config.ts b/ui/vite.config.ts index d5fcbc5..32610e2 100644 --- a/ui/vite.config.ts +++ b/ui/vite.config.ts @@ -1,26 +1,26 @@ -import { defineConfig } from 'vite' -import { svelte } from '@sveltejs/vite-plugin-svelte' -import tailwindcss from '@tailwindcss/vite' +import { defineConfig } from "vite"; +import { svelte } from "@sveltejs/vite-plugin-svelte"; +import tailwindcss from "@tailwindcss/vite"; // https://vite.dev/config/ export default defineConfig({ plugins: [tailwindcss(), svelte()], - + // Fix for Tauri + Vite HMR server: { host: true, strictPort: true, hmr: { - protocol: 'ws', - host: 'localhost', + protocol: "ws", + host: "localhost", port: 5173, }, watch: { usePolling: true, }, }, - + // Ensure compatibility with Tauri clearScreen: false, - envPrefix: ['VITE_', 'TAURI_'], -}) + envPrefix: ["VITE_", "TAURI_"], +}); -- cgit v1.2.3-70-g09d2