You've already forked AstralRinth
Compare commits
348 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8e720ccef5 | |||
| 03b49284e1 | |||
| ac6c26a5f9 | |||
| cc963cfc40 | |||
| 75e3994c6e | |||
| 71e28e1ea5 | |||
| 6dbd1e5236 | |||
| 77afdb1cc4 | |||
| 7fa442fb28 | |||
| 2535156dac | |||
| 8972c9a198 | |||
| 03ed64c99f | |||
| 4cd8ccd319 | |||
| ea594ec27c | |||
| 2a61916d1e | |||
| e66b131a5d | |||
| 0c66fa3f12 | |||
| aec49cff7c | |||
| c88bdda3e6 | |||
| 1a72d55e2d | |||
| 55eae7ec7e | |||
| 351b3da337 | |||
| 9ee0626e8b | |||
| e9735bd9ba | |||
| 15a7815ec3 | |||
| 6919c8dea9 | |||
| f32558cf97 | |||
| ad705fa66f | |||
| 87f8773401 | |||
| 3c578108de | |||
| cb5600ad45 | |||
| 59e48ea2b1 | |||
| 7658e1c653 | |||
| 9589e23118 | |||
| dbc64afe48 | |||
| 7e682c22bb | |||
| fd80f1217d | |||
| d98394d8d5 | |||
| e303655727 | |||
| 95de8977d4 | |||
| 92e91a0606 | |||
| 98269842f3 | |||
| ab6e9dd5d7 | |||
| a13647b9e2 | |||
| 2af7ecc077 | |||
| bea0ba017c | |||
| f874856452 | |||
| b96c5cd5ab | |||
| 7e84659249 | |||
| 24504cb94d | |||
| 6fe4235358 | |||
| 04f0f53104 | |||
| c169b48228 | |||
| beff2fcaa9 | |||
| 9315af9b20 | |||
| 4d11dc821b | |||
| 8fd40f46c5 | |||
| 28e9f017e3 | |||
| beb1bdb31f | |||
| 895b040ad7 | |||
| 54747aa628 | |||
| 53c9699b46 | |||
| 671fd22389 | |||
| bddc40e601 | |||
| 324ad65d7c | |||
| 7eace32d93 | |||
| a5108ecc5d | |||
| a538b99c18 | |||
| f6f66a313f | |||
| d5f756fd86 | |||
| b4eba5a0d5 | |||
| d418eaee12 | |||
| f466470d06 | |||
| 3f55711f9e | |||
| bb9ce52c9d | |||
| 14af3d0763 | |||
| d43451e398 | |||
| 2492b11ec0 | |||
| 4228a193e9 | |||
| 47020f34b6 | |||
| 5c00cb06f1 | |||
| e6edf07eae | |||
| 5d7bd3b177 | |||
| 71d63fbe17 | |||
| f33efed91b | |||
| d41b31c775 | |||
| 20281c4efc | |||
| afcdb1d0a1 | |||
| f3060cd9b4 | |||
| 1a1b9f54df | |||
| 716f293e8e | |||
| f5825f1065 | |||
| 5b44454e18 | |||
| b425c66832 | |||
| 0b8762cd0a | |||
| ff50964f25 | |||
| 36d0760a3e | |||
| 4def0e8407 | |||
| 6da190ed01 | |||
| 8149618187 | |||
| 902d749293 | |||
| 1491642209 | |||
| 7bc2c1dd4d | |||
| 9f11759292 | |||
| cef425b6be | |||
| 3fc55184a7 | |||
| 67e090565e | |||
| d8d9720495 | |||
| 9361acb78e | |||
| 58aac642a9 | |||
| af3b829449 | |||
| 567e31401d | |||
| b95ece04c4 | |||
| 3dfd035b50 | |||
| 01b19424cd | |||
| cb3130f998 | |||
| 4d3e1ade67 | |||
| 1b33a3619f | |||
| ea607c1a04 | |||
| fda06cfc60 | |||
| 0d61945956 | |||
| c017038f71 | |||
| a323bf6c25 | |||
| e2f07a7848 | |||
| 0511a14bd9 | |||
| 8f8a4af9eb | |||
| 9ed094a1e7 | |||
| aa6de3cc80 | |||
| f5aece1fb1 | |||
| 79aa41fd7a | |||
| bd918c7616 | |||
| d23b925bb9 | |||
| 8aaddb9d8a | |||
| f48eaee336 | |||
| 749fd32307 | |||
| 2e95a8a117 | |||
| 2194ae774c | |||
| 052637d402 | |||
| c1a092e55c | |||
| bd3342badf | |||
| d832ca1e5a | |||
| 5b7f025094 | |||
| d0c67b368a | |||
| c43d359561 | |||
| 8b2a89d4e0 | |||
| 8aede4e082 | |||
| 3d80201112 | |||
| 8d14f34994 | |||
| 6f34130633 | |||
| 5a699eec22 | |||
| 9fa490aa6a | |||
| d119b301d0 | |||
| 15c31f04a3 | |||
| 48e5319134 | |||
| 8058993578 | |||
| 28337c88f6 | |||
| a6d08e9d50 | |||
| 7943f77655 | |||
| dc4ef332f8 | |||
| 652f2e241f | |||
| 5fd27bcb65 | |||
| 8fa01b937d | |||
| 8b98087936 | |||
| 7afe35a6cd | |||
| debaf1381c | |||
| 697468e910 | |||
| 46c325f78a | |||
| 0ac42344e7 | |||
| df261dad95 | |||
| d30643b5a0 | |||
| ab95dcf951 | |||
| ab539a313f | |||
| a2c07c92f8 | |||
| 0925abfd1c | |||
| 8cf42471a3 | |||
| 006b19e3c9 | |||
| ca36d11570 | |||
| c612c8b009 | |||
| f9cf3d5ef9 | |||
| e7d933411e | |||
| 44cbbd9ed7 | |||
| 87dbb6dcbc | |||
| 3d1cafdcec | |||
| e114c7466e | |||
| 20059e6cf0 | |||
| 6b10b4d30b | |||
| a47dde972c | |||
| e8b0c9df4c | |||
| b8bc2c4cb6 | |||
| 328500d381 | |||
| f56672fb68 | |||
| d3459e4b12 | |||
| 07703e49ef | |||
| 08011161c8 | |||
| 9b29694907 | |||
| 805c0b86a5 | |||
| d19bf82cb1 | |||
| 2e6cff7efc | |||
| b2ff2d8737 | |||
| eaa4b44a16 | |||
| 76d0ef03e7 | |||
| ee8c47adcb | |||
| 5d3ca3ba02 | |||
| 235717b01c | |||
| 518f7adafb | |||
| 490b994d7b | |||
| 14eac461be | |||
| 9af1391e0e | |||
| bcfa6941e4 | |||
| 5ffe14f058 | |||
| 166d14e7e1 | |||
| b03d754a57 | |||
| 674f29959d | |||
| 3e735b99eb | |||
| 74d2d85cb5 | |||
| 3a92adfb82 | |||
| af4c627a04 | |||
| 1e725e6d03 | |||
| 1454e3351e | |||
| 6f59f4c110 | |||
| 8e0732bf01 | |||
| 0cf3c1a88e | |||
| 8a3171d7c4 | |||
| e25d726da4 | |||
| 11e99cb9d3 | |||
| 632b09ff3f | |||
| 713571d50e | |||
| 4ad6daa45c | |||
| 9b5f172170 | |||
| 4f789a0ebc | |||
| ee3ac37967 | |||
| 2aabcf36ee | |||
| 82697278dc | |||
| 0bc6502443 | |||
| 5ffcc48d75 | |||
| b81e727204 | |||
| 9ea43a12fd | |||
| b279c43069 | |||
| 9497ba70a4 | |||
| c02b809601 | |||
| 1d000bb238 | |||
| df1499047c | |||
| 80eb297284 | |||
| 58645b9ba9 | |||
| 544f63512a | |||
| 3b8cd661bc | |||
| 8af65f58d9 | |||
| ab79e84398 | |||
| cf190d86d5 | |||
| ca0c16b1fe | |||
| 17c9e4a721 | |||
| d7f1029b54 | |||
| ad208536b0 | |||
| 553db55c7b | |||
| d22c9e24f4 | |||
| e31197f649 | |||
| 0dee21814d | |||
| 0657e4466f | |||
| 13dbb4c57e | |||
| 4c6290ead6 | |||
| 99493b9917 | |||
| 72a52eb7b1 | |||
| b33e12c71d | |||
| 82d86839c7 | |||
| 3a20e15340 | |||
| 1c89b84314 | |||
| 8d36c14554 | |||
| 6387fb21c6 | |||
| c7d0839bfb | |||
| 2b43e26a85 | |||
| 175b90be5a | |||
| 13103b4950 | |||
| 8804478221 | |||
| b8982a6d17 | |||
| ff88724d01 | |||
| 7dffb352d5 | |||
| 1df6e29aa1 | |||
| 5deb4179ad | |||
| 358cf31c87 | |||
| 7cea4b21a8 | |||
| 7846fd00aa | |||
| cebc195fe0 | |||
| 6db1d66591 | |||
| 8052fda840 | |||
| ae58f3844d | |||
| acd4b1696a | |||
| 5ea78b78c2 | |||
| f90998157d | |||
| 634000cdb6 | |||
| 5fd8c38c1c | |||
| 15892a88d3 | |||
| 32793c50e1 | |||
| 0e0ca1971a | |||
| bb9af18eed | |||
| d4516d3527 | |||
| 87de47fe5e | |||
| 7d76fe1b6a | |||
| 46d30e491a | |||
| 059c0618f1 | |||
| 7ef60fcafe | |||
| ec17e79014 | |||
| e351d674f4 | |||
| f555fa916a | |||
| dbe38cb4e7 | |||
| 2e40e26116 | |||
| ae25a15abd | |||
| 0f755b94ce | |||
| bcf46d440b | |||
| 526561f2de | |||
| a8caa1afc3 | |||
| 98e9a8473d | |||
| 936395484e | |||
| 0c3e23db96 | |||
| 013ba4d86d | |||
| 93813c448c | |||
| c20b869e62 | |||
| 56c556821b | |||
| 44267619b6 | |||
| 10afd673db | |||
| 90043fe84d | |||
| a6a98ff63e | |||
| 911652133b | |||
| cee1b5f522 | |||
| 62f5a23fcb | |||
| 5a10292add | |||
| 3f606a08aa | |||
| eb595cdc3e | |||
| 572cd065ed | |||
| 76dc8a0897 | |||
| 4723de6269 | |||
| e15fa35bad | |||
| 7716a0c524 | |||
| 2cc6bc8ce4 | |||
| 5d19d31b2c | |||
| c1b95ede07 | |||
| 058185c7fd | |||
| 6fb125cf0f | |||
| a945e9b005 | |||
| b943638afb | |||
| 207dc0e2bb | |||
| 359fbd4738 | |||
| f7700acce4 | |||
| 87a3e2d022 | |||
| 5d17663040 | |||
| cff3c72f94 | |||
| fadf475f06 | |||
| 7228499737 | |||
| bca467a634 |
+7
-4
@@ -1,6 +1,9 @@
|
||||
# Windows has stack overflows when calling from Tauri, so we increase the default stack size used by the compiler
|
||||
[target.'cfg(windows)']
|
||||
rustflags = ["-C", "link-args=/STACK:16777220", "--cfg", "tokio_unstable"]
|
||||
|
||||
[build]
|
||||
rustflags = ["--cfg", "tokio_unstable"]
|
||||
|
||||
# Windows has stack overflows when calling from Tauri, so we increase the default stack size used by the compiler
|
||||
[target.'cfg(windows)']
|
||||
rustflags = ["--cfg", "tokio_unstable", "-C", "link-args=/STACK:16777220"]
|
||||
|
||||
[target.x86_64-pc-windows-msvc]
|
||||
linker = "rust-lld"
|
||||
|
||||
+12
-5
@@ -3,16 +3,23 @@ root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
indent_style = tab
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
max_line_length = 100
|
||||
|
||||
[*.md]
|
||||
indent_size = 2
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.{rs,java,kts}]
|
||||
indent_size = 4
|
||||
[*.{toml,json}]
|
||||
indent_size = 2
|
||||
|
||||
# YAML requires space indentation by spec
|
||||
[*.{yml,yaml}]
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
|
||||
[*.rs]
|
||||
indent_style = space
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
---
|
||||
applyTo: '**/*.vue'
|
||||
---
|
||||
|
||||
You are given a Nuxt/Vue single-file component (.vue). Your task is to convert every hard-coded natural-language string in the <template> into our localization system using @vintl/vintl-nuxt (which wraps FormatJS).
|
||||
|
||||
Please follow these rules precisely:
|
||||
|
||||
1. Identify translatable strings
|
||||
|
||||
- Scan the <template> for all user-visible strings (inner text, alt attributes, placeholders, button labels, etc.). Do not extract dynamic expressions (like {{ user.name }}) or HTML tags. Only extract static human-readable text.
|
||||
|
||||
2. Create message definitions
|
||||
|
||||
- In the <script setup> block, import `defineMessage` or `defineMessages` from `@vintl/vintl`.
|
||||
- For each extracted string, define a message with a unique `id` (use a descriptive prefix based on the component path, e.g. `auth.welcome.long-title`) and a `defaultMessage` equal to the original English string.
|
||||
Example:
|
||||
const messages = defineMessages({
|
||||
welcomeTitle: { id: 'auth.welcome.title', defaultMessage: 'Welcome' },
|
||||
welcomeDescription: { id: 'auth.welcome.description', defaultMessage: 'You’re now part of the community…' },
|
||||
})
|
||||
|
||||
3. Handle variables and ICU formats
|
||||
|
||||
- Replace dynamic parts with ICU placeholders: "Hello, ${user.name}!" → `{name}` and defaultMessage: 'Hello, {name}!'
|
||||
- For numbers/dates/times, use ICU/FormatJS options (e.g., currency): `{price, number, ::currency/USD}`
|
||||
- For plurals/selects, use ICU: `'{count, plural, one {# message} other {# messages}}'`
|
||||
|
||||
4. Rich-text messages (links/markup)
|
||||
|
||||
- In `defaultMessage`, wrap link/markup ranges with tags, e.g.:
|
||||
"By creating an account, you agree to our <terms-link>Terms</terms-link> and <privacy-link>Privacy Policy</privacy-link>."
|
||||
- Render rich-text messages with `<IntlFormatted>` from `@vintl/vintl/components` and map tags via `values`:
|
||||
<IntlFormatted
|
||||
:message="messages.tosLabel"
|
||||
:values="{
|
||||
'terms-link': (chunks) => <NuxtLink to='/terms'>{chunks}</NuxtLink>,
|
||||
'privacy-link': (chunks) => <NuxtLink to='/privacy'>{chunks}</NuxtLink>,
|
||||
}"
|
||||
/>
|
||||
- For simple emphasis: `'Welcome to <strong>Modrinth</strong>!'` and map `'strong': (c) => <strong>{c}</strong>`
|
||||
|
||||
5. Formatting in templates
|
||||
|
||||
- Import and use `useVIntl()`; prefer `formatMessage` for simple strings:
|
||||
`const { formatMessage } = useVIntl()`
|
||||
`<button>{{ formatMessage(messages.welcomeTitle) }}</button>`
|
||||
- Vue methods like `$formatMessage`, `$formatNumber`, `$formatDate` are also available if needed.
|
||||
|
||||
6. Naming conventions and id stability
|
||||
|
||||
- Make `id`s descriptive and stable (e.g., `error.generic.default.title`). Group related messages with `defineMessages`.
|
||||
|
||||
7. Avoid Vue/ICU delimiter collisions
|
||||
|
||||
- If an ICU placeholder would end right before `}}` in a Vue template, insert a space so it becomes `} }` to avoid parsing issues.
|
||||
|
||||
8. Update imports and remove literals
|
||||
|
||||
- Ensure imports for `defineMessage`/`defineMessages`, `useVIntl`, and `<IntlFormatted>` are present. Replace all hard-coded strings with `formatMessage(...)` or `<IntlFormatted>` and remove the literals.
|
||||
|
||||
9. Preserve functionality
|
||||
|
||||
- Do not change logic, layout, reactivity, or bindings—only refactor strings into i18n.
|
||||
|
||||
Use existing patterns from our codebase:
|
||||
|
||||
- Variables/plurals: see `apps/frontend/src/pages/frog.vue`
|
||||
- Rich-text link tags: see `apps/frontend/src/pages/auth/welcome.vue` and `apps/frontend/src/error.vue`
|
||||
|
||||
When you finish, there should be no hard-coded English strings left in the template—everything comes from `formatMessage` or `<IntlFormatted>`.
|
||||
@@ -0,0 +1,4 @@
|
||||
This pull request is created according to the `.github/workflows/i18n-pull.yml` file.
|
||||
|
||||
- 🌐 [Contribute to translations on Crowdin](https://translate.modrinth.com/)
|
||||
- 🔄 [Dispatch this workflow again to update this PR](https://github.com/Modrinth/code/actions/workflows/i18n-pull.yml)
|
||||
@@ -4,9 +4,14 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
- prod
|
||||
- release
|
||||
- beta
|
||||
- feature*
|
||||
tags:
|
||||
- 'v*'
|
||||
- release-*
|
||||
- beta-*
|
||||
paths:
|
||||
- .github/workflows/astralrinth-build.yml
|
||||
- 'apps/app/**'
|
||||
@@ -97,6 +102,11 @@ jobs:
|
||||
xdg-utils \
|
||||
openjdk-11-jdk
|
||||
|
||||
- name: ⚙️ Set application environment
|
||||
shell: bash
|
||||
run: |
|
||||
cp packages/app-lib/.env.prod packages/app-lib/.env
|
||||
|
||||
- name: 💨 Setup Turbo cache
|
||||
uses: rharkor/caching-for-turbo@v1.8
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
SQLX_OFFLINE: true
|
||||
|
||||
jobs:
|
||||
typos:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: crate-ci/typos@master
|
||||
|
||||
tombi:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: tombi-toml/setup-tombi@v1
|
||||
- run: tombi lint
|
||||
- run: tombi fmt --check
|
||||
@@ -0,0 +1,19 @@
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
SQLX_OFFLINE: true
|
||||
|
||||
jobs:
|
||||
shear:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: cargo-bins/cargo-binstall@main
|
||||
- run: cargo binstall --no-confirm cargo-shear
|
||||
- run: cargo shear
|
||||
@@ -0,0 +1,108 @@
|
||||
name: Crowdin (pull)
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 7 * * MON' # every monday at 7 am
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: i18n-management
|
||||
|
||||
jobs:
|
||||
pull_translations:
|
||||
name: 'Pull translations from Crowdin'
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.ref == 'refs/heads/main'
|
||||
concurrency:
|
||||
group: i18n-pull:${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- name: Preflight check
|
||||
run: |
|
||||
PREFLIGHT_CHECK_RESULT=true
|
||||
|
||||
function flight_failure () {
|
||||
if [ "$PREFLIGHT_CHECK_RESULT" = true ]; then
|
||||
echo "One or more pre-flight checks failed!"
|
||||
echo ""
|
||||
PREFLIGHT_CHECK_RESULT=false
|
||||
fi
|
||||
echo "- $1"
|
||||
}
|
||||
|
||||
if [ "$CROWDIN_PROJECT_ID_DEFINED" != true ]; then
|
||||
flight_failure "CROWDIN_PROJECT_ID variable is not defined (required to push)"
|
||||
fi
|
||||
|
||||
if [ "$CROWDIN_PERSONAL_TOKEN_DEFINED" != true ]; then
|
||||
flight_failure "CROWDIN_PERSONAL_TOKEN secret is not defined (required to push)"
|
||||
fi
|
||||
|
||||
if [ "$CROWDIN_GH_TOKEN_DEFINED" != true ]; then
|
||||
flight_failure "CROWDIN_GH_TOKEN secret is not defined (required to make pull requests)"
|
||||
fi
|
||||
|
||||
if [ "$PREFLIGHT_CHECK_RESULT" = false ]; then
|
||||
exit 1
|
||||
fi
|
||||
env:
|
||||
CROWDIN_PROJECT_ID_DEFINED: ${{ vars.CROWDIN_PROJECT_ID != '' }}
|
||||
CROWDIN_PERSONAL_TOKEN_DEFINED: ${{ secrets.CROWDIN_PERSONAL_TOKEN != '' }}
|
||||
CROWDIN_GH_TOKEN_DEFINED: ${{ secrets.CROWDIN_GH_TOKEN != '' }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
token: ${{ secrets.CROWDIN_GH_TOKEN }}
|
||||
|
||||
- name: Configure Git author
|
||||
id: git-author
|
||||
uses: MarcoIeni/git-config@v0.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.CROWDIN_GH_TOKEN }}
|
||||
|
||||
# # Because --all flag of Crowdin CLI is currently broken we need to create a fake source file
|
||||
# # so that the CLI won't omit translations for it. See https://github.com/crowdin/crowdin-cli/issues/724
|
||||
# - name: Write fake sources
|
||||
# shell: bash
|
||||
# run: echo "{}" > locales/en-US/index.json
|
||||
|
||||
- name: Query branch name
|
||||
id: branch-name
|
||||
shell: bash
|
||||
run: |
|
||||
BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)
|
||||
SAFE_BRANCH_NAME=$(echo "$BRANCH_NAME" | sed -e "s/[\\\\/\\:*?\"<>|]/_/g")
|
||||
echo "Branch name is $BRANCH_NAME (escaped as $SAFE_BRANCH_NAME)"
|
||||
echo "branch_name=$BRANCH_NAME" >> "$GITHUB_OUTPUT"
|
||||
echo "safe_branch_name=$SAFE_BRANCH_NAME" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Download translations from Crowdin
|
||||
uses: crowdin/github-action@v2
|
||||
with:
|
||||
upload_sources: false
|
||||
upload_translations: false
|
||||
download_translations: true
|
||||
push_translations: false
|
||||
create_pull_request: false
|
||||
crowdin_branch_name: '[${{ github.repository_owner }}.${{ github.event.repository.name }}] ${{ steps.branch-name.outputs.safe_branch_name }}'
|
||||
env:
|
||||
CROWDIN_PROJECT_ID: ${{ vars.CROWDIN_PROJECT_ID }}
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||
|
||||
- name: Fix broken permissions
|
||||
shell: bash
|
||||
run: sudo chown -R $USER:$USER .
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
title: 'New translations from Crowdin (${{ steps.branch-name.outputs.branch_name }})'
|
||||
body-path: .github/templates/crowdin-pr.md
|
||||
commit-message: 'New translations from Crowdin (${{ steps.branch-name.outputs.branch_name }})'
|
||||
branch: crowdin-pull/${{ steps.branch-name.outputs.branch_name }}
|
||||
author: '${{ steps.git-author.outputs.name }} <${{ steps.git-author.outputs.email }}>'
|
||||
committer: '${{ steps.git-author.outputs.name }} <${{ steps.git-author.outputs.email }}>'
|
||||
labels: sync
|
||||
token: ${{ secrets.CROWDIN_GH_TOKEN }}
|
||||
@@ -0,0 +1,81 @@
|
||||
name: Crowdin (push)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['main']
|
||||
paths:
|
||||
- '.github/workflows/i18n.push.yml'
|
||||
- 'apps/*/src/locales/en-US/**'
|
||||
- 'apps/*/locales/en-US/**'
|
||||
- 'packages/*/src/locales/en-US/**'
|
||||
- 'packages/*/locales/en-US/**'
|
||||
- 'crowdin.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: i18n-management
|
||||
|
||||
jobs:
|
||||
push_translations:
|
||||
name: Push sources to Crowdin
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.ref == 'refs/heads/main'
|
||||
concurrency:
|
||||
group: i18n-push:${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- name: Preflight check
|
||||
run: |
|
||||
PREFLIGHT_CHECK_RESULT=true
|
||||
|
||||
function flight_failure () {
|
||||
if [ "$PREFLIGHT_CHECK_RESULT" = true ]; then
|
||||
echo "One or more pre-flight checks failed!"
|
||||
echo ""
|
||||
PREFLIGHT_CHECK_RESULT=false
|
||||
fi
|
||||
echo "- $1"
|
||||
}
|
||||
|
||||
if [ "$CROWDIN_PROJECT_ID_DEFINED" != true ]; then
|
||||
flight_failure "CROWDIN_PROJECT_ID variable is not defined (required to push)"
|
||||
fi
|
||||
|
||||
if [ "$CROWDIN_PERSONAL_TOKEN_DEFINED" != true ]; then
|
||||
flight_failure "CROWDIN_PERSONAL_TOKEN secret is not defined (required to push)"
|
||||
fi
|
||||
|
||||
if [ "$PREFLIGHT_CHECK_RESULT" = false ]; then
|
||||
exit 1
|
||||
fi
|
||||
env:
|
||||
CROWDIN_PROJECT_ID_DEFINED: ${{ vars.CROWDIN_PROJECT_ID != '' }}
|
||||
CROWDIN_PERSONAL_TOKEN_DEFINED: ${{ secrets.CROWDIN_PERSONAL_TOKEN != '' }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- name: Query branch name
|
||||
id: branch-name
|
||||
shell: bash
|
||||
run: |
|
||||
BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)
|
||||
SAFE_BRANCH_NAME=$(echo "$BRANCH_NAME" | sed -e "s/[\\\\/\\:*?\"<>|]/_/g")
|
||||
echo "Branch name is $BRANCH_NAME (escaped as $SAFE_BRANCH_NAME)"
|
||||
echo "branch_name=$BRANCH_NAME" >> "$GITHUB_OUTPUT"
|
||||
echo "safe_branch_name=$SAFE_BRANCH_NAME" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Upload translations to Crowdin
|
||||
uses: crowdin/github-action@v1
|
||||
with:
|
||||
upload_sources: true
|
||||
upload_translations: false
|
||||
download_translations: false
|
||||
push_translations: false
|
||||
create_pull_request: false
|
||||
crowdin_branch_name: '[${{ github.repository_owner }}.${{ github.event.repository.name }}] ${{ steps.branch-name.outputs.safe_branch_name }}'
|
||||
env:
|
||||
CROWDIN_PROJECT_ID: ${{ vars.CROWDIN_PROJECT_ID }}
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||
+9
-5
@@ -9,7 +9,6 @@ tmp
|
||||
node_modules
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
@@ -24,6 +23,14 @@ node_modules
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
# IDE - IntelliJ
|
||||
.idea/*
|
||||
!.idea/code.iml
|
||||
!.idea/gradle.xml
|
||||
!.idea/icon.svg
|
||||
!.idea/modules.xml
|
||||
!.idea/vcs.xml
|
||||
|
||||
# misc
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
@@ -56,8 +63,5 @@ generated
|
||||
# app testing dir
|
||||
app-playground-data/*
|
||||
|
||||
# soley because i need the PORT to be 3002 due to WSL stuff
|
||||
.env
|
||||
apps/frontend/.env
|
||||
|
||||
.astro
|
||||
.claude
|
||||
|
||||
Generated
-8
@@ -1,8 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
Generated
+1
-1
@@ -10,8 +10,8 @@
|
||||
<sourceFolder url="file://$MODULE_DIR$/apps/labrinth/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/apps/labrinth/tests" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/app-lib/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/rust-common/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/ariadne/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/packages/path-util/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
|
||||
Generated
-7
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DiscordProjectSettings">
|
||||
<option name="show" value="PROJECT_FILES" />
|
||||
<option name="description" value="" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+17
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1"/>
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$/packages/app-lib/java"/>
|
||||
<option name="gradleJvm" value="#JAVA_HOME"/>
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$/packages/app-lib/java"/>
|
||||
</set>
|
||||
</option>
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+4
@@ -0,0 +1,4 @@
|
||||
<svg width="512" height="514" viewBox="0 0 512 514" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M503.16 323.56C514.55 281.47 515.32 235.91 503.2 190.76C466.57 54.2299 326.04 -26.8001 189.33 9.77991C83.8101 38.0199 11.3899 128.07 0.689941 230.47H43.99C54.29 147.33 113.74 74.7298 199.75 51.7098C306.05 23.2598 415.13 80.6699 453.17 181.38L411.03 192.65C391.64 145.8 352.57 111.45 306.3 96.8198L298.56 140.66C335.09 154.13 364.72 184.5 375.56 224.91C391.36 283.8 361.94 344.14 308.56 369.17L320.09 412.16C390.25 383.21 432.4 310.3 422.43 235.14L464.41 223.91C468.91 252.62 467.35 281.16 460.55 308.07L503.16 323.56Z" fill="#00af5c"/>
|
||||
<path d="M321.99 504.22C185.27 540.8 44.7501 459.77 8.11011 323.24C3.84011 307.31 1.17 291.33 0 275.46H43.27C44.36 287.37 46.4699 299.35 49.6799 311.29C53.0399 323.8 57.45 335.75 62.79 347.07L101.38 323.92C98.1299 316.42 95.39 308.6 93.21 300.47C69.17 210.87 122.41 118.77 212.13 94.7601C229.13 90.2101 246.23 88.4401 262.93 89.1501L255.19 133C244.73 133.05 234.11 134.42 223.53 137.25C157.31 154.98 118.01 222.95 135.75 289.09C136.85 293.16 138.13 297.13 139.59 300.99L188.94 271.38L174.07 231.95L220.67 184.08L279.57 171.39L296.62 192.38L269.47 219.88L245.79 227.33L228.87 244.72L237.16 267.79C237.16 267.79 253.95 285.63 253.98 285.64L277.7 279.33L294.58 260.79L331.44 249.12L342.42 273.82L304.39 320.45L240.66 340.63L212.08 308.81L162.26 338.7C187.8 367.78 226.2 383.93 266.01 380.56L277.54 423.55C218.13 431.41 160.1 406.82 124.05 361.64L85.6399 384.68C136.25 451.17 223.84 484.11 309.61 461.16C371.35 444.64 419.4 402.56 445.42 349.38L488.06 364.88C457.17 431.16 398.22 483.82 321.99 504.22Z" fill="#00af5c"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
Generated
-26
@@ -1,26 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="KotlinJavaRuntime" type="repository">
|
||||
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.0" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.8.0/kotlin-stdlib-jdk8-1.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.8.0/kotlin-stdlib-1.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.8.0/kotlin-stdlib-common-1.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.8.0/kotlin-stdlib-jdk7-1.8.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.8.0/kotlin-stdlib-jdk8-1.8.0-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.8.0/kotlin-stdlib-1.8.0-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.8.0/kotlin-stdlib-common-1.8.0-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.8.0/kotlin-stdlib-jdk7-1.8.0-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.8.0/kotlin-stdlib-jdk8-1.8.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.8.0/kotlin-stdlib-1.8.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.8.0/kotlin-stdlib-common-1.8.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.8.0/kotlin-stdlib-jdk7-1.8.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
Generated
+8
-1
@@ -2,7 +2,14 @@
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/code.iml" filepath="$PROJECT_DIR$/.idea/code.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/code.iml"
|
||||
filepath="$PROJECT_DIR$/.idea/code.iml"/>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/theseus.iml"
|
||||
filepath="$PROJECT_DIR$/.idea/modules/theseus.iml"/>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/theseus.main.iml"
|
||||
filepath="$PROJECT_DIR$/.idea/modules/theseus.main.iml"/>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/theseus.test.iml"
|
||||
filepath="$PROJECT_DIR$/.idea/modules/theseus.test.iml"/>
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+5
-3
@@ -2,11 +2,13 @@
|
||||
<project version="4">
|
||||
<component name="CommitMessageInspectionProfile">
|
||||
<profile version="1.0">
|
||||
<inspection_tool class="CommitFormat" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CommitNamingConvention" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CommitFormat" enabled="true" level="WARNING"
|
||||
enabled_by_default="true"/>
|
||||
<inspection_tool class="CommitNamingConvention" enabled="true" level="WARNING"
|
||||
enabled_by_default="true"/>
|
||||
</profile>
|
||||
</component>
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
<mapping directory="" vcs="Git"/>
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,3 @@
|
||||
Cargo.lock
|
||||
pnpm-lock.yaml
|
||||
.github/**/*.png
|
||||
Vendored
+9
-3
@@ -2,8 +2,14 @@
|
||||
"prettier.endOfLine": "lf",
|
||||
"editor.formatOnSave": true,
|
||||
"eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"],
|
||||
"editor.detectIndentation": true,
|
||||
"editor.detectIndentation": false,
|
||||
"editor.insertSpaces": false,
|
||||
"files.eol": "\n",
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"files.insertFinalNewline": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit"
|
||||
}
|
||||
"source.fixAll.eslint": "explicit",
|
||||
"source.organizeImports": "always"
|
||||
},
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# Architecture
|
||||
|
||||
## Labrinth
|
||||
|
||||
Labrinth is the backend API service for Modrinth.
|
||||
|
||||
### Testing
|
||||
|
||||
Before a pull request can be opened, run `cargo clippy -p labrinth --all-targets` and make sure there are ZERO warnings, otherwise CI will fail.
|
||||
|
||||
Use `cargo test -p labrinth --all-targets` to test your changes. All tests must pass, otherwise CI will fail.
|
||||
|
||||
To prepare the sqlx cache, cd into `apps/labrinth` and run `cargo sqlx prepare`. Make sure to NEVER run `cargo sqlx prepare --workspace`.
|
||||
|
||||
Read the root `docker-compose.yml` to see what running services are available while developing. Use `docker exec` to access these services.
|
||||
|
||||
When the user refers to "performing pre-PR checks", do the following:
|
||||
- Run clippy as described above
|
||||
- DO NOT run tests unless explicitly requested (they take a long time)
|
||||
- Prepare the sqlx cache
|
||||
|
||||
### Clickhouse
|
||||
|
||||
Use `docker exec labrinth-clickhouse clickhouse-client` to access the Clickhouse instance. We use the `staging_ariadne` database to store data in testing.
|
||||
|
||||
### Postgres
|
||||
|
||||
Use `docker exec labrinth-postgres psql -U postgres` to access the PostgreSQL instance.
|
||||
+6
-2
@@ -2,12 +2,16 @@
|
||||
|
||||
All packages in this repository are licensed under their respective licenses. For more information, refer to the LICENSE file in each package.
|
||||
|
||||
For detailed information, consult each package's COPYING.md file, if available.
|
||||
For detailed information, consult each package's COPYING.md, LICENSE.txt, or LICENSE file, if available.
|
||||
|
||||
## Modrinth Branding
|
||||
|
||||
The use of Modrinth branding elements, including but not limited to the wrench-in-labyrinth logo, the landing image, and any variations thereof, is strictly prohibited without explicit written permission from Rinth, Inc. This includes trademarks, logos, or other branding elements.
|
||||
|
||||
All rights reserved. © 2020-2024 Rinth, Inc.
|
||||
> All rights reserved. © 2020-2024 Rinth, Inc.
|
||||
|
||||
This includes, but may not be limited to, the following files:
|
||||
|
||||
- .idea/icon.svg
|
||||
|
||||
If you fork this repository, you must remove all Modrinth branding assets from your fork.
|
||||
|
||||
Generated
+1632
-1279
File diff suppressed because it is too large
Load Diff
+67
-46
@@ -8,6 +8,7 @@ members = [
|
||||
"packages/app-lib",
|
||||
"packages/ariadne",
|
||||
"packages/daedalus",
|
||||
"packages/path-util",
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
@@ -24,32 +25,38 @@ actix-web-prom = "0.10.0"
|
||||
actix-ws = "0.3.0"
|
||||
argon2 = { version = "0.5.3", features = ["std"] }
|
||||
ariadne = { path = "packages/ariadne" }
|
||||
async_zip = "0.0.17"
|
||||
async-compression = { version = "0.4.25", default-features = false }
|
||||
async-compression = { version = "0.4.27", default-features = false }
|
||||
async-recursion = "1.1.1"
|
||||
async-stripe = { version = "0.41.0", default-features = false, features = [
|
||||
"runtime-tokio-hyper-rustls",
|
||||
] }
|
||||
async-trait = "0.1.88"
|
||||
async-tungstenite = { version = "0.29.1", default-features = false, features = [
|
||||
async-tungstenite = { version = "0.30.0", default-features = false, features = [
|
||||
"futures-03-sink",
|
||||
] }
|
||||
async-walkdir = "2.1.0"
|
||||
async_zip = "0.0.17"
|
||||
base64 = "0.22.1"
|
||||
bitflags = "2.9.1"
|
||||
bytemuck = "1.23.0"
|
||||
bytemuck = "1.23.1"
|
||||
bytes = "1.10.1"
|
||||
censor = "0.3.0"
|
||||
chardetng = "0.1.17"
|
||||
chrono = "0.4.41"
|
||||
clap = "4.5.40"
|
||||
cidre = { version = "0.11.2", default-features = false, features = [
|
||||
"macos_15_0",
|
||||
] }
|
||||
clap = "4.5.43"
|
||||
clickhouse = "0.13.3"
|
||||
color-eyre = "0.6.5"
|
||||
color-thief = "0.2.2"
|
||||
console-subscriber = "0.4.1"
|
||||
const_format = "0.2.34"
|
||||
daedalus = { path = "packages/daedalus" }
|
||||
dashmap = "6.1.0"
|
||||
data-url = "0.3.1"
|
||||
deadpool-redis = "0.21.1"
|
||||
deadpool-redis = "0.22.0"
|
||||
derive_more = "2.0.1"
|
||||
dirs = "6.0.0"
|
||||
discord-rich-presence = "0.2.5"
|
||||
dotenv-build = "0.1.1"
|
||||
@@ -57,26 +64,32 @@ dotenvy = "0.15.7"
|
||||
dunce = "1.0.5"
|
||||
either = "1.15.0"
|
||||
encoding_rs = "0.8.35"
|
||||
enumset = "1.1.6"
|
||||
enumset = "1.1.7"
|
||||
eyre = "0.6.12"
|
||||
flate2 = "1.1.2"
|
||||
fs4 = { version = "0.13.1", default-features = false }
|
||||
futures = { version = "0.3.31", default-features = false }
|
||||
futures-util = "0.3.31"
|
||||
hashlink = "0.10.0"
|
||||
heck = "0.5.0"
|
||||
hex = "0.4.3"
|
||||
hickory-resolver = "0.25.2"
|
||||
hmac = "0.12.1"
|
||||
hyper-tls = "0.6.0"
|
||||
hyper-util = "0.1.14"
|
||||
hyper = "1.6.0"
|
||||
hyper-rustls = { version = "0.27.7", default-features = false, features = [
|
||||
"http1",
|
||||
"native-tokio",
|
||||
"ring",
|
||||
"tls12",
|
||||
] }
|
||||
hyper-util = "0.1.16"
|
||||
iana-time-zone = "0.1.63"
|
||||
image = { version = "0.25.6", default-features = false, features = ["rayon"] }
|
||||
indexmap = "2.9.0"
|
||||
indicatif = "0.17.11"
|
||||
indexmap = "2.10.0"
|
||||
indicatif = "0.18.0"
|
||||
itertools = "0.14.0"
|
||||
jemalloc_pprof = "0.7.0"
|
||||
jemalloc_pprof = "0.8.1"
|
||||
json-patch = { version = "4.0.0", default-features = false }
|
||||
lettre = { version = "0.11.17", default-features = false, features = [
|
||||
lettre = { version = "0.11.18", default-features = false, features = [
|
||||
"builder",
|
||||
"hostname",
|
||||
"pool",
|
||||
@@ -84,25 +97,29 @@ lettre = { version = "0.11.17", default-features = false, features = [
|
||||
"rustls",
|
||||
"rustls-native-certs",
|
||||
"smtp-transport",
|
||||
"tokio1",
|
||||
"tokio1-rustls",
|
||||
] }
|
||||
maxminddb = "0.26.0"
|
||||
meilisearch-sdk = { version = "0.28.0", default-features = false }
|
||||
meilisearch-sdk = { version = "0.29.1", default-features = false }
|
||||
murmur2 = "0.1.0"
|
||||
native-dialog = "0.9.0"
|
||||
notify = { version = "8.0.0", default-features = false }
|
||||
notify-debouncer-mini = { version = "0.6.0", default-features = false }
|
||||
notify = { version = "8.2.0", default-features = false }
|
||||
notify-debouncer-mini = { version = "0.7.0", default-features = false }
|
||||
p256 = "0.13.2"
|
||||
paste = "1.0.15"
|
||||
path-util = { path = "packages/path-util" }
|
||||
phf = { version = "0.12.1", features = ["macros"] }
|
||||
png = "0.17.16"
|
||||
prometheus = "0.14.0"
|
||||
quartz_nbt = "0.2.9"
|
||||
quick-xml = "0.37.5"
|
||||
quick-xml = "0.38.1"
|
||||
rand = "=0.8.5" # Locked on 0.8 until argon2 and p256 update to 0.9
|
||||
rand_chacha = "=0.3.1" # Locked on 0.3 until we can update rand to 0.9
|
||||
redis = "=0.31.0" # Locked on 0.31 until deadpool-redis updates to 0.32
|
||||
redis = "0.32.4"
|
||||
regex = "1.11.1"
|
||||
reqwest = { version = "0.12.20", default-features = false }
|
||||
rgb = "0.8.50"
|
||||
reqwest = { version = "0.12.22", default-features = false }
|
||||
rgb = "0.8.52"
|
||||
rust_decimal = { version = "1.37.2", features = [
|
||||
"serde-with-float",
|
||||
"serde-with-str",
|
||||
@@ -114,7 +131,7 @@ rust-s3 = { version = "0.35.1", default-features = false, features = [
|
||||
"tokio-rustls-tls",
|
||||
] }
|
||||
rusty-money = "0.4.1"
|
||||
sentry = { version = "0.41.0", default-features = false, features = [
|
||||
sentry = { version = "0.42.0", default-features = false, features = [
|
||||
"backtrace",
|
||||
"contexts",
|
||||
"debug-images",
|
||||
@@ -122,57 +139,63 @@ sentry = { version = "0.41.0", default-features = false, features = [
|
||||
"reqwest",
|
||||
"rustls",
|
||||
] }
|
||||
sentry-actix = "0.41.0"
|
||||
sentry-actix = "0.42.0"
|
||||
serde = "1.0.219"
|
||||
serde_bytes = "0.11.17"
|
||||
serde_cbor = "0.11.2"
|
||||
serde_ini = "0.2.0"
|
||||
serde_json = "1.0.140"
|
||||
serde_with = "3.13.0"
|
||||
serde_json = "1.0.142"
|
||||
serde_with = "3.14.0"
|
||||
serde-xml-rs = "0.8.1" # Also an XML (de)serializer, consider dropping yaserde in favor of this
|
||||
sha1 = "0.10.6"
|
||||
sha1_smol = { version = "1.0.1", features = ["std"] }
|
||||
sha2 = "0.10.9"
|
||||
spdx = "0.10.8"
|
||||
spdx = "0.10.9"
|
||||
sqlx = { version = "0.8.6", default-features = false }
|
||||
sysinfo = { version = "0.35.2", default-features = false }
|
||||
sysinfo = { version = "0.36.1", default-features = false }
|
||||
tar = "0.4.44"
|
||||
tauri = "2.6.1"
|
||||
tauri-build = "2.3.0"
|
||||
tauri-plugin-deep-link = "2.4.0"
|
||||
tauri-plugin-dialog = "2.3.0"
|
||||
tauri-plugin-http = "2.5.0"
|
||||
tauri = "2.7.0"
|
||||
tauri-build = "2.3.1"
|
||||
tauri-plugin-deep-link = "2.4.1"
|
||||
tauri-plugin-dialog = "2.3.2"
|
||||
tauri-plugin-http = "2.5.1"
|
||||
tauri-plugin-opener = "2.4.0"
|
||||
tauri-plugin-os = "2.3.0"
|
||||
tauri-plugin-single-instance = "2.3.0"
|
||||
tauri-plugin-single-instance = "2.3.2"
|
||||
tauri-plugin-updater = { version = "2.9.0", default-features = false, features = [
|
||||
"rustls-tls",
|
||||
"zip",
|
||||
] }
|
||||
tauri-plugin-window-state = "2.3.0"
|
||||
tauri-plugin-window-state = "2.4.0"
|
||||
tempfile = "3.20.0"
|
||||
theseus = { path = "packages/app-lib" }
|
||||
thiserror = "2.0.12"
|
||||
tikv-jemalloc-ctl = "0.6.0"
|
||||
tikv-jemallocator = "0.6.0"
|
||||
tokio = "1.45.1"
|
||||
tokio = "1.47.1"
|
||||
tokio-stream = "0.1.17"
|
||||
tokio-util = "0.7.15"
|
||||
tokio-util = "0.7.16"
|
||||
totp-rs = "5.7.0"
|
||||
tracing = "0.1.41"
|
||||
tracing-actix-web = "0.7.18"
|
||||
tracing-actix-web = { version = "0.7.19", default-features = false }
|
||||
tracing-ecs = "0.5.0"
|
||||
tracing-error = "0.2.1"
|
||||
tracing-subscriber = "0.3.19"
|
||||
typed-path = "0.11.0"
|
||||
url = "2.5.4"
|
||||
urlencoding = "2.1.3"
|
||||
uuid = "1.17.0"
|
||||
validator = "0.20.0"
|
||||
webp = { version = "0.3.0", default-features = false }
|
||||
webview2-com = "0.38.0" # Should be updated in lockstep with wry
|
||||
whoami = "1.6.0"
|
||||
windows = "0.61.3"
|
||||
windows-core = "0.61.2"
|
||||
winreg = "0.55.0"
|
||||
woothee = "0.13.0"
|
||||
yaserde = "0.12.0"
|
||||
zip = { version = "4.2.0", default-features = false, features = [
|
||||
zbus = "5.9.0"
|
||||
zip = { version = "4.3.0", default-features = false, features = [
|
||||
"bzip2",
|
||||
"deflate",
|
||||
"deflate64",
|
||||
@@ -214,12 +237,8 @@ todo = "warn"
|
||||
unnested_or_patterns = "warn"
|
||||
wildcard_dependencies = "warn"
|
||||
|
||||
[workspace.lints.rust]
|
||||
# Turn warnings into errors by default
|
||||
warnings = "deny"
|
||||
|
||||
[patch.crates-io]
|
||||
wry = { git = "https://github.com/modrinth/wry", rev = "21db186" }
|
||||
[profile.dev.package.sqlx-macros]
|
||||
opt-level = 3
|
||||
|
||||
# Optimize for speed and reduce size on release builds
|
||||
[profile.release]
|
||||
@@ -229,5 +248,7 @@ lto = true # Enables link to optimizations
|
||||
panic = "abort" # Strip expensive panic clean-up logic
|
||||
codegen-units = 1 # Compile crates one after another so the compiler can optimize better
|
||||
|
||||
[profile.dev.package.sqlx-macros]
|
||||
opt-level = 3
|
||||
# Specific profile for labrinth production builds
|
||||
[profile.release-labrinth]
|
||||
inherits = "release"
|
||||
panic = "unwind" # Don't exit the whole app on panic in production
|
||||
|
||||
@@ -119,5 +119,4 @@ To begin using AstralRinth:
|
||||
|
||||
If you'd like to support development, you can donate via the following crypto wallets:
|
||||
|
||||
- BTC (Telegram): 14g6asNYzcUoaQtB8B2QGKabgEvn55wfLj
|
||||
- TONCOIN (Telegram): UQAqUJ2_hVBI6k_gPyfp_jd-1K0OS61nIFPZuJWN9BwGAvKe
|
||||
- TON (Telegram): UQCG0a2fm5YzKzIm6E8M3_CqBQ5Q1RT95MU60c9UPjKn_NEN
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
[files]
|
||||
extend-exclude = [
|
||||
"**/src/locales/",
|
||||
"apps/frontend/",
|
||||
"patches/",
|
||||
"packages/utils/",
|
||||
"packages/ui/",
|
||||
"packages/blog/",
|
||||
# contains licenses like `CC-BY-ND-4.0`
|
||||
"packages/moderation/src/data/stages/license.ts",
|
||||
# contains payment card IDs like `IY1VMST1MOXS` which are flagged
|
||||
"apps/labrinth/src/queue/payouts.rs",
|
||||
]
|
||||
|
||||
[default.extend-words]
|
||||
# Terms Of Use in `tou-link`
|
||||
tou = "tou"
|
||||
# Google Ad Manager
|
||||
gam = "gam"
|
||||
@@ -1,2 +1,4 @@
|
||||
**/dist
|
||||
*.gltf
|
||||
src/locales/
|
||||
src/assets/**/*.svg
|
||||
|
||||
@@ -1,22 +1,2 @@
|
||||
import { createConfigForNuxt } from '@nuxt/eslint-config/flat'
|
||||
import { fixupPluginRules } from '@eslint/compat'
|
||||
import turboPlugin from 'eslint-plugin-turbo'
|
||||
|
||||
export default createConfigForNuxt().append([
|
||||
{
|
||||
name: 'turbo',
|
||||
plugins: {
|
||||
turbo: fixupPluginRules(turboPlugin),
|
||||
},
|
||||
rules: {
|
||||
'turbo/no-undeclared-env-vars': 'error',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'modrinth',
|
||||
rules: {
|
||||
'vue/html-self-closing': 'off',
|
||||
'vue/multi-word-component-names': 'off',
|
||||
},
|
||||
},
|
||||
])
|
||||
import config from '@modrinth/tooling-config/eslint/nuxt.mjs'
|
||||
export default config
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="https://tally.so/widgets/embed.js" async></script>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
"tsc:check": "vue-tsc --noEmit",
|
||||
"lint": "eslint . && prettier --check .",
|
||||
"fix": "eslint . --fix && prettier --write .",
|
||||
"intl:extract": "formatjs extract \"{,src/components,src/composables,src/helpers,src/pages,src/store}/**/*.{vue,ts,tsx,js,jsx,mts,cts,mjs,cjs}\" --ignore '**/*.d.ts' --ignore 'node_modules' --out-file src/locales/en-US/index.json --format crowdin --preserve-whitespace",
|
||||
"intl:extract": "formatjs extract \"src/**/*.{vue,ts,tsx,js,jsx,mts,cts,mjs,cjs}\" --ignore \"**/*.d.ts\" --ignore node_modules --out-file src/locales/en-US/index.json --format crowdin --preserve-whitespace",
|
||||
"test": "vue-tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@geometrically/minecraft-motd-parser": "^1.1.4",
|
||||
"@sfirew/minecraft-motd-parser": "^1.1.6",
|
||||
"@modrinth/assets": "workspace:*",
|
||||
"@modrinth/ui": "workspace:*",
|
||||
"@modrinth/utils": "workspace:*",
|
||||
@@ -41,6 +41,7 @@
|
||||
"vue-virtual-scroller": "v2.0.0-beta.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@modrinth/tooling-config": "workspace:*",
|
||||
"@eslint/compat": "^1.1.1",
|
||||
"@formatjs/cli": "^6.2.12",
|
||||
"@nuxt/eslint-config": "^0.5.6",
|
||||
@@ -48,13 +49,11 @@
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"eslint": "^9.9.1",
|
||||
"eslint-config-custom": "workspace:*",
|
||||
"eslint-plugin-turbo": "^2.5.4",
|
||||
"postcss": "^8.4.39",
|
||||
"prettier": "^3.2.5",
|
||||
"sass": "^1.74.1",
|
||||
"tailwindcss": "^3.4.4",
|
||||
"tsconfig": "workspace:*",
|
||||
"typescript": "^5.5.4",
|
||||
"vite": "^5.4.6",
|
||||
"vue-tsc": "^2.1.6"
|
||||
|
||||
+427
-134
@@ -1,6 +1,4 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, onUnmounted, ref, watch, provide } from 'vue'
|
||||
import { RouterView, useRoute, useRouter } from 'vue-router'
|
||||
import {
|
||||
ArrowBigUpDashIcon,
|
||||
ChangeSkinIcon,
|
||||
@@ -13,71 +11,92 @@ import {
|
||||
LogOutIcon,
|
||||
MaximizeIcon,
|
||||
MinimizeIcon,
|
||||
NewspaperIcon,
|
||||
NotepadTextIcon,
|
||||
PlusIcon,
|
||||
RefreshCwIcon,
|
||||
RestoreIcon,
|
||||
RightArrowIcon,
|
||||
SettingsIcon,
|
||||
WorldIcon,
|
||||
XIcon,
|
||||
NewspaperIcon,
|
||||
} from '@modrinth/assets'
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
ButtonStyled,
|
||||
Notifications,
|
||||
OverflowMenu,
|
||||
commonMessages,
|
||||
NewsArticleCard,
|
||||
NotificationPanel,
|
||||
OverflowMenu,
|
||||
ProgressSpinner,
|
||||
provideNotificationManager
|
||||
} from '@modrinth/ui'
|
||||
import { useLoading, useTheming } from '@/store/state'
|
||||
// import ModrinthAppLogo from '@/assets/modrinth_app.svg?component'
|
||||
import AccountsCard from '@/components/ui/AccountsCard.vue'
|
||||
import InstanceCreationModal from '@/components/ui/InstanceCreationModal.vue'
|
||||
import { get, set } from '@/helpers/settings.ts'
|
||||
import Breadcrumbs from '@/components/ui/Breadcrumbs.vue'
|
||||
import RunningAppBar from '@/components/ui/RunningAppBar.vue'
|
||||
import SplashScreen from '@/components/ui/SplashScreen.vue'
|
||||
import ErrorModal from '@/components/ui/ErrorModal.vue'
|
||||
import ModrinthLoadingIndicator from '@/components/LoadingIndicatorBar.vue'
|
||||
import { handleError, useNotifications } from '@/store/notifications.js'
|
||||
import { command_listener, warning_listener } from '@/helpers/events.js'
|
||||
import { type } from '@tauri-apps/plugin-os'
|
||||
import { getOS, isDev } from '@/helpers/utils.js'
|
||||
import { debugAnalytics, initAnalytics, optOutAnalytics, trackEvent } from '@/helpers/analytics'
|
||||
import { getCurrentWindow } from '@tauri-apps/api/window'
|
||||
import { renderString } from '@modrinth/utils'
|
||||
import { getVersion } from '@tauri-apps/api/app'
|
||||
import URLConfirmModal from '@/components/ui/URLConfirmModal.vue'
|
||||
import { create_profile_and_install_from_file } from './helpers/pack'
|
||||
import { useError } from '@/store/error.js'
|
||||
import { useCheckDisableMouseover } from '@/composables/macCssFix.js'
|
||||
import ModInstallModal from '@/components/ui/install_flow/ModInstallModal.vue'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { getCurrentWindow } from '@tauri-apps/api/window'
|
||||
import { openUrl } from '@tauri-apps/plugin-opener'
|
||||
import { type } from '@tauri-apps/plugin-os'
|
||||
import { saveWindowState, StateFlags } from '@tauri-apps/plugin-window-state'
|
||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
||||
import { $fetch } from 'ofetch'
|
||||
import { computed, onMounted, onUnmounted, provide, ref } from 'vue'
|
||||
import { RouterView, useRoute, useRouter } from 'vue-router'
|
||||
|
||||
import ModrinthLoadingIndicator from '@/components/LoadingIndicatorBar.vue'
|
||||
import AccountsCard from '@/components/ui/AccountsCard.vue'
|
||||
import Breadcrumbs from '@/components/ui/Breadcrumbs.vue'
|
||||
import ErrorModal from '@/components/ui/ErrorModal.vue'
|
||||
import FriendsList from '@/components/ui/friends/FriendsList.vue'
|
||||
import IncompatibilityWarningModal from '@/components/ui/install_flow/IncompatibilityWarningModal.vue'
|
||||
import InstallConfirmModal from '@/components/ui/install_flow/InstallConfirmModal.vue'
|
||||
import { useInstall } from '@/store/install.js'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { get_opening_command, initialize_state } from '@/helpers/state'
|
||||
import { saveWindowState, StateFlags } from '@tauri-apps/plugin-window-state'
|
||||
import { renderString } from '@modrinth/utils'
|
||||
import { useFetch } from '@/helpers/fetch.js'
|
||||
// import { check } from '@tauri-apps/plugin-updater'
|
||||
import NavButton from '@/components/ui/NavButton.vue'
|
||||
import { get as getCreds, login, logout } from '@/helpers/mr_auth.js'
|
||||
import { get_user } from '@/helpers/cache.js'
|
||||
import ModInstallModal from '@/components/ui/install_flow/ModInstallModal.vue'
|
||||
import InstanceCreationModal from '@/components/ui/InstanceCreationModal.vue'
|
||||
import AppSettingsModal from '@/components/ui/modal/AppSettingsModal.vue'
|
||||
// import PromotionWrapper from '@/components/ui/PromotionWrapper.vue'
|
||||
// import { hide_ads_window, init_ads_window } from '@/helpers/ads.js'
|
||||
import FriendsList from '@/components/ui/friends/FriendsList.vue'
|
||||
import { openUrl } from '@tauri-apps/plugin-opener'
|
||||
import AuthGrantFlowWaitModal from '@/components/ui/modal/AuthGrantFlowWaitModal.vue'
|
||||
import NavButton from '@/components/ui/NavButton.vue'
|
||||
import QuickInstanceSwitcher from '@/components/ui/QuickInstanceSwitcher.vue'
|
||||
import { get_available_capes, get_available_skins } from './helpers/skins'
|
||||
import { generateSkinPreviews } from './helpers/rendering/batch-skin-renderer'
|
||||
import RunningAppBar from '@/components/ui/RunningAppBar.vue'
|
||||
import SplashScreen from '@/components/ui/SplashScreen.vue'
|
||||
import URLConfirmModal from '@/components/ui/URLConfirmModal.vue'
|
||||
import { useCheckDisableMouseover } from '@/composables/macCssFix.js'
|
||||
import { debugAnalytics, optOutAnalytics, trackEvent } from '@/helpers/analytics'
|
||||
import { get_user } from '@/helpers/cache.js'
|
||||
import { command_listener, warning_listener } from '@/helpers/events.js'
|
||||
import { useFetch } from '@/helpers/fetch.js'
|
||||
import { cancelLogin, get as getCreds, login, logout } from '@/helpers/mr_auth.js'
|
||||
import { list } from '@/helpers/profile.js'
|
||||
import { get as getSettings, set as setSettings } from '@/helpers/settings.ts'
|
||||
import { get_opening_command, initialize_state } from '@/helpers/state'
|
||||
import {
|
||||
getOS,
|
||||
isDev
|
||||
} from '@/helpers/utils.js'
|
||||
import {
|
||||
provideAppUpdateDownloadProgress
|
||||
} from '@/providers/download-progress.ts'
|
||||
import { useError } from '@/store/error.js'
|
||||
import { useInstall } from '@/store/install.js'
|
||||
import { useLoading, useTheming } from '@/store/state'
|
||||
|
||||
// [AR] Feature
|
||||
import { create_profile_and_install_from_file } from './helpers/pack'
|
||||
import { generateSkinPreviews } from './helpers/rendering/batch-skin-renderer'
|
||||
import { get_available_capes, get_available_skins } from './helpers/skins'
|
||||
import { AppNotificationManager } from './providers/app-notifications'
|
||||
|
||||
// [AR] Imports
|
||||
import { get, set } from '@/helpers/settings.ts'
|
||||
import { getRemote, updateState } from '@/helpers/update.js'
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
const notificationManager = new AppNotificationManager()
|
||||
provideNotificationManager(notificationManager)
|
||||
const { handleError, addNotification } = notificationManager
|
||||
|
||||
const news = ref([])
|
||||
const availableSurvey = ref(false)
|
||||
|
||||
const urlModal = ref(null)
|
||||
|
||||
@@ -108,21 +127,43 @@ onMounted(async () => {
|
||||
document.querySelector('body').addEventListener('auxclick', handleAuxClick)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
onUnmounted(async () => {
|
||||
document.querySelector('body').removeEventListener('click', handleClick)
|
||||
document.querySelector('body').removeEventListener('auxclick', handleAuxClick)
|
||||
})
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const messages = defineMessages({
|
||||
updateInstalledToastTitle: {
|
||||
id: 'app.update.complete-toast.title',
|
||||
defaultMessage: 'Version {version} was successfully installed!',
|
||||
},
|
||||
updateInstalledToastText: {
|
||||
id: 'app.update.complete-toast.text',
|
||||
defaultMessage: 'Click here to view the changelog.',
|
||||
},
|
||||
reloadToUpdate: {
|
||||
id: 'app.update.reload-to-update',
|
||||
defaultMessage: 'Reload to install update',
|
||||
},
|
||||
downloadUpdate: {
|
||||
id: 'app.update.download-update',
|
||||
defaultMessage: 'Download update',
|
||||
},
|
||||
downloadingUpdate: {
|
||||
id: 'app.update.downloading-update',
|
||||
defaultMessage: 'Downloading update ({percent}%)',
|
||||
},
|
||||
})
|
||||
|
||||
async function setupApp() {
|
||||
stateInitialized.value = true
|
||||
|
||||
// [AR] Patched
|
||||
const settings = await get()
|
||||
|
||||
// Patched
|
||||
settings.personalized_ads = false
|
||||
settings.telemetry = false
|
||||
await set(settings)
|
||||
|
||||
stateInitialized.value = true
|
||||
const {
|
||||
native_decorations,
|
||||
theme,
|
||||
@@ -135,8 +176,8 @@ async function setupApp() {
|
||||
toggle_sidebar,
|
||||
developer_mode,
|
||||
feature_flags,
|
||||
} = settings
|
||||
|
||||
pending_update_toast_for_version,
|
||||
} = await getSettings()
|
||||
|
||||
if (default_page === 'Library') {
|
||||
await router.push('/library')
|
||||
@@ -163,7 +204,7 @@ async function setupApp() {
|
||||
isMaximized.value = await getCurrentWindow().isMaximized()
|
||||
})
|
||||
|
||||
initAnalytics()
|
||||
// [AR] Patched
|
||||
if (!telemetry) {
|
||||
console.info("[AR] • Telemetry disabled by default (Hard patched).")
|
||||
optOutAnalytics()
|
||||
@@ -171,7 +212,6 @@ async function setupApp() {
|
||||
if (!personalized_ads) {
|
||||
console.info("[AR] • Personalized ads disabled by default (Hard patched).")
|
||||
}
|
||||
|
||||
if (dev) debugAnalytics()
|
||||
trackEvent('Launched', { version, dev, onboarded })
|
||||
|
||||
@@ -185,30 +225,29 @@ async function setupApp() {
|
||||
}
|
||||
|
||||
await warning_listener((e) =>
|
||||
notificationsWrapper.value.addNotification({
|
||||
addNotification({
|
||||
title: 'Warning',
|
||||
text: e.message,
|
||||
type: 'warn',
|
||||
}),
|
||||
)
|
||||
|
||||
/// [AR] Patch
|
||||
// useFetch(
|
||||
// `https://api.modrinth.com/appCriticalAnnouncement.json?version=${version}`,
|
||||
// 'criticalAnnouncements',
|
||||
// true,
|
||||
// )
|
||||
// .then((response) => response.json())
|
||||
// .then((res) => {
|
||||
// if (res && res.header && res.body) {
|
||||
// criticalErrorMessage.value = res
|
||||
// }
|
||||
// })
|
||||
// .catch(() => {
|
||||
// console.log(
|
||||
// `No critical announcement found at https://api.modrinth.com/appCriticalAnnouncement.json?version=${version}`,
|
||||
// )
|
||||
// })
|
||||
useFetch(
|
||||
`https://api.modrinth.com/appCriticalAnnouncement.json?version=${version}`,
|
||||
'criticalAnnouncements',
|
||||
true,
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then((res) => {
|
||||
if (res && res.header && res.body) {
|
||||
criticalErrorMessage.value = res
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
console.log(
|
||||
`No critical announcement found at https://api.modrinth.com/appCriticalAnnouncement.json?version=${version}`,
|
||||
)
|
||||
})
|
||||
|
||||
useFetch(`https://modrinth.com/news/feed/articles.json`, 'news', true)
|
||||
.then((response) => response.json())
|
||||
@@ -229,7 +268,6 @@ async function setupApp() {
|
||||
})
|
||||
|
||||
get_opening_command().then(handleCommand)
|
||||
// checkUpdates()
|
||||
fetchCredentials()
|
||||
|
||||
try {
|
||||
@@ -239,6 +277,18 @@ async function setupApp() {
|
||||
} catch (error) {
|
||||
console.warn('Failed to generate skin previews in app setup.', error)
|
||||
}
|
||||
|
||||
if (pending_update_toast_for_version !== null) {
|
||||
const settings = await getSettings()
|
||||
settings.pending_update_toast_for_version = null
|
||||
await setSettings(settings)
|
||||
}
|
||||
|
||||
if (osType === 'windows') {
|
||||
await processPendingSurveys()
|
||||
} else {
|
||||
console.info('Skipping user surveys on non-Windows platforms')
|
||||
}
|
||||
}
|
||||
|
||||
const stateFailed = ref(false)
|
||||
@@ -270,9 +320,6 @@ const route = useRoute()
|
||||
const loading = useLoading()
|
||||
loading.setEnabled(false)
|
||||
|
||||
const notifications = useNotifications()
|
||||
const notificationsWrapper = ref()
|
||||
|
||||
const error = useError()
|
||||
const errorModal = ref()
|
||||
|
||||
@@ -283,6 +330,8 @@ const incompatibilityWarningModal = ref()
|
||||
|
||||
const credentials = ref()
|
||||
|
||||
const modrinthLoginFlowWaitModal = ref()
|
||||
|
||||
async function fetchCredentials() {
|
||||
const creds = await getCreds().catch(handleError)
|
||||
if (creds && creds.user_id) {
|
||||
@@ -292,8 +341,24 @@ async function fetchCredentials() {
|
||||
}
|
||||
|
||||
async function signIn() {
|
||||
await login().catch(handleError)
|
||||
modrinthLoginFlowWaitModal.value.show()
|
||||
|
||||
try {
|
||||
await login()
|
||||
await fetchCredentials()
|
||||
} catch (error) {
|
||||
if (
|
||||
typeof error === 'object' &&
|
||||
typeof error['message'] === 'string' &&
|
||||
error.message.includes('Login canceled')
|
||||
) {
|
||||
// Not really an error due to being a result of user interaction, show nothing
|
||||
} else {
|
||||
handleError(error)
|
||||
}
|
||||
} finally {
|
||||
modrinthLoginFlowWaitModal.value.hide()
|
||||
}
|
||||
}
|
||||
|
||||
async function logOut() {
|
||||
@@ -319,25 +384,10 @@ const forceSidebar = computed(
|
||||
() => route.path.startsWith('/browse') || route.path.startsWith('/project'),
|
||||
)
|
||||
const sidebarVisible = computed(() => sidebarToggled.value || forceSidebar.value)
|
||||
// const showAd = computed(() => !(!sidebarVisible.value || hasPlus.value))
|
||||
|
||||
// watch(
|
||||
// showAd,
|
||||
// () => {
|
||||
// if (!showAd.value) {
|
||||
// hide_ads_window(true)
|
||||
// } else {
|
||||
// init_ads_window(true)
|
||||
// }
|
||||
// },
|
||||
// { immediate: true },
|
||||
// )
|
||||
|
||||
onMounted(() => {
|
||||
invoke('show_window')
|
||||
|
||||
notifications.setNotifs(notificationsWrapper.value)
|
||||
|
||||
error.setErrorModal(errorModal.value)
|
||||
|
||||
install.setIncompatibilityWarningModal(incompatibilityWarningModal)
|
||||
@@ -366,18 +416,10 @@ async function handleCommand(e) {
|
||||
}
|
||||
}
|
||||
|
||||
// const updateAvailable = ref(false)
|
||||
// async function checkUpdates() {
|
||||
// const update = await check()
|
||||
// updateAvailable.value = !!update
|
||||
|
||||
// setTimeout(
|
||||
// () => {
|
||||
// checkUpdates()
|
||||
// },
|
||||
// 5 * 1000 * 60,
|
||||
// )
|
||||
// }
|
||||
const appUpdateDownload = {
|
||||
progress: ref(0),
|
||||
version: ref(),
|
||||
}
|
||||
|
||||
function handleClick(e) {
|
||||
let target = e.target
|
||||
@@ -413,6 +455,114 @@ function handleAuxClick(e) {
|
||||
e.target.dispatchEvent(event)
|
||||
}
|
||||
}
|
||||
|
||||
function cleanupOldSurveyDisplayData() {
|
||||
const threeWeeksAgo = new Date()
|
||||
threeWeeksAgo.setDate(threeWeeksAgo.getDate() - 21)
|
||||
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const key = localStorage.key(i)
|
||||
|
||||
if (key.startsWith('survey-') && key.endsWith('-display')) {
|
||||
const dateValue = new Date(localStorage.getItem(key))
|
||||
if (dateValue < threeWeeksAgo) {
|
||||
localStorage.removeItem(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function openSurvey() {
|
||||
if (!availableSurvey.value) {
|
||||
console.error('No survey to open')
|
||||
return
|
||||
}
|
||||
|
||||
const creds = await getCreds().catch(handleError)
|
||||
const userId = creds?.user_id
|
||||
|
||||
const formId = availableSurvey.value.tally_id
|
||||
|
||||
const popupOptions = {
|
||||
layout: 'modal',
|
||||
width: 700,
|
||||
autoClose: 2000,
|
||||
hideTitle: true,
|
||||
hiddenFields: {
|
||||
user_id: userId,
|
||||
},
|
||||
onOpen: () => console.info('Opened user survey'),
|
||||
onClose: () => {
|
||||
console.info('Closed user survey')
|
||||
},
|
||||
onSubmit: () => console.info('Active user survey submitted'),
|
||||
}
|
||||
|
||||
try {
|
||||
if (window.Tally?.openPopup) {
|
||||
console.info(`Opening Tally popup for user survey (form ID: ${formId})`)
|
||||
dismissSurvey()
|
||||
window.Tally.openPopup(formId, popupOptions)
|
||||
} else {
|
||||
console.warn('Tally script not yet loaded')
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error opening Tally popup:', e)
|
||||
}
|
||||
|
||||
console.info(`Found user survey to show with tally_id: ${formId}`)
|
||||
window.Tally.openPopup(formId, popupOptions)
|
||||
}
|
||||
|
||||
function dismissSurvey() {
|
||||
localStorage.setItem(`survey-${availableSurvey.value.id}-display`, new Date())
|
||||
availableSurvey.value = undefined
|
||||
}
|
||||
|
||||
async function processPendingSurveys() {
|
||||
function isWithinLastTwoWeeks(date) {
|
||||
const twoWeeksAgo = new Date()
|
||||
twoWeeksAgo.setDate(twoWeeksAgo.getDate() - 14)
|
||||
return date >= twoWeeksAgo
|
||||
}
|
||||
|
||||
cleanupOldSurveyDisplayData()
|
||||
|
||||
const creds = await getCreds().catch(handleError)
|
||||
const userId = creds?.user_id
|
||||
|
||||
const instances = await list().catch(handleError)
|
||||
const isActivePlayer =
|
||||
instances.findIndex(
|
||||
(instance) =>
|
||||
isWithinLastTwoWeeks(instance.last_played) && !isWithinLastTwoWeeks(instance.created),
|
||||
) >= 0
|
||||
|
||||
let surveys = []
|
||||
try {
|
||||
surveys = await $fetch('https://api.modrinth.com/v2/surveys')
|
||||
} catch (e) {
|
||||
console.error('Error fetching surveys:', e)
|
||||
}
|
||||
|
||||
const surveyToShow = surveys.find(
|
||||
(survey) =>
|
||||
!!(
|
||||
localStorage.getItem(`survey-${survey.id}-display`) === null &&
|
||||
survey.type === 'tally_app' &&
|
||||
((survey.condition === 'active_player' && isActivePlayer) ||
|
||||
(survey.assigned_users?.includes(userId) && !survey.dismissed_users?.includes(userId)))
|
||||
),
|
||||
)
|
||||
|
||||
if (surveyToShow) {
|
||||
availableSurvey.value = surveyToShow
|
||||
} else {
|
||||
console.info('No user survey to show')
|
||||
}
|
||||
}
|
||||
|
||||
provideAppUpdateDownloadProgress(appUpdateDownload) // [AR Note] If delete this shit line -> SettingsModal will not work.
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -422,6 +572,9 @@ function handleAuxClick(e) {
|
||||
<Suspense>
|
||||
<AppSettingsModal ref="settingsModal" />
|
||||
</Suspense>
|
||||
<Suspense>
|
||||
<AuthGrantFlowWaitModal ref="modrinthLoginFlowWaitModal" @flow-cancel="cancelLogin" />
|
||||
</Suspense>
|
||||
<Suspense>
|
||||
<InstanceCreationModal ref="installationModal" />
|
||||
</Suspense>
|
||||
@@ -469,17 +622,58 @@ function handleAuxClick(e) {
|
||||
<PlusIcon />
|
||||
</NavButton>
|
||||
<div class="flex flex-grow"></div>
|
||||
<!-- [AR] TODO -->
|
||||
<!-- <NavButton v-if="updateAvailable" v-tooltip.right="'Install update'" :to="() => restartApp()">
|
||||
<DownloadIcon />
|
||||
</NavButton> -->
|
||||
<Transition name="nav-button-animated">
|
||||
<div
|
||||
v-if="
|
||||
availableUpdate &&
|
||||
updateToastDismissed &&
|
||||
!restarting &&
|
||||
(finishedDownloading || metered)
|
||||
"
|
||||
>
|
||||
<NavButton
|
||||
v-tooltip.right="
|
||||
formatMessage(
|
||||
finishedDownloading
|
||||
? messages.reloadToUpdate
|
||||
: downloadProgress === 0
|
||||
? messages.downloadUpdate
|
||||
: messages.downloadingUpdate,
|
||||
{
|
||||
percent: downloadPercent,
|
||||
},
|
||||
)
|
||||
"
|
||||
:to="
|
||||
finishedDownloading
|
||||
? installUpdate
|
||||
: downloadProgress > 0 && downloadProgress < 1
|
||||
? showUpdateToast
|
||||
: downloadAvailableUpdate
|
||||
"
|
||||
>
|
||||
<ProgressSpinner
|
||||
v-if="downloadProgress > 0 && downloadProgress < 1"
|
||||
class="text-brand"
|
||||
:progress="downloadProgress"
|
||||
/>
|
||||
<RefreshCwIcon v-else-if="finishedDownloading" class="text-brand" />
|
||||
<DownloadIcon v-else class="text-brand" />
|
||||
</NavButton>
|
||||
</div>
|
||||
</Transition>
|
||||
<template v-if="updateState">
|
||||
<NavButton class="neon-icon pulse" v-tooltip.right="'Settings'" :to="() => $refs.settingsModal.show()">
|
||||
<NavButton
|
||||
class="neon-icon pulse"
|
||||
v-tooltip.right="formatMessage(commonMessages.settingsLabel)"
|
||||
:to="() => $refs.settingsModal.show()">
|
||||
<SettingsIcon />
|
||||
</NavButton>
|
||||
</template>
|
||||
<template v-else>
|
||||
<NavButton v-tooltip.right="'Settings'" :to="() => $refs.settingsModal.show()">
|
||||
<NavButton
|
||||
v-tooltip.right="formatMessage(commonMessages.settingsLabel)"
|
||||
:to="() => $refs.settingsModal.show()">
|
||||
<SettingsIcon />
|
||||
</NavButton>
|
||||
</template>
|
||||
@@ -510,8 +704,7 @@ function handleAuxClick(e) {
|
||||
</div>
|
||||
<div data-tauri-drag-region class="app-grid-statusbar bg-bg-raised h-[--top-bar-height] flex">
|
||||
<div data-tauri-drag-region class="flex p-3">
|
||||
<!-- <ModrinthAppLogo class="h-full w-auto text-contrast pointer-events-none" /> -->
|
||||
<div class="flex items-center gap-1 ml-3">
|
||||
<div data-tauri-drag-region class="flex items-center gap-1 ml-3">
|
||||
<button
|
||||
class="cursor-pointer p-0 m-0 text-contrast border-none outline-none bg-button-bg rounded-full flex items-center justify-center w-6 h-6 hover:brightness-75 transition-all"
|
||||
@click="router.back()"
|
||||
@@ -527,7 +720,7 @@ function handleAuxClick(e) {
|
||||
</div>
|
||||
<Breadcrumbs class="pt-[2px]" />
|
||||
</div>
|
||||
<section class="flex ml-auto items-center">
|
||||
<section data-tauri-drag-region class="flex ml-auto items-center">
|
||||
<ButtonStyled
|
||||
v-if="!forceSidebar && themeStore.toggleSidebar"
|
||||
:type="sidebarToggled ? 'standard' : 'transparent'"
|
||||
@@ -571,6 +764,28 @@ function handleAuxClick(e) {
|
||||
:class="{ 'sidebar-enabled': sidebarVisible }"
|
||||
>
|
||||
<div class="app-viewport flex-grow router-view">
|
||||
<transition name="popup-survey">
|
||||
<div
|
||||
v-if="availableSurvey"
|
||||
class="w-[400px] z-20 fixed -bottom-12 pb-16 right-[--right-bar-width] mr-4 rounded-t-2xl card-shadow bg-bg-raised border-divider border-[1px] border-solid border-b-0 p-4"
|
||||
>
|
||||
<h2 class="text-lg font-extrabold mt-0 mb-2">Hey there Modrinth user!</h2>
|
||||
<p class="m-0 leading-tight">
|
||||
Would you mind answering a few questions about your experience with Modrinth App?
|
||||
</p>
|
||||
<p class="mt-3 mb-4 leading-tight">
|
||||
This feedback will go directly to the Modrinth team and help guide future updates!
|
||||
</p>
|
||||
<div class="flex gap-2">
|
||||
<ButtonStyled color="brand">
|
||||
<button @click="openSurvey"><NotepadTextIcon /> Take survey</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
<button @click="dismissSurvey"><XIcon /> No thanks</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
<div
|
||||
class="loading-indicator-container h-8 fixed z-50"
|
||||
:style="{
|
||||
@@ -650,7 +865,7 @@ function handleAuxClick(e) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <template v-if="showAd">
|
||||
<template v-if="showAd">
|
||||
<a
|
||||
href="https://modrinth.plus?app"
|
||||
class="absolute bottom-[250px] w-full flex justify-center items-center gap-1 px-4 py-3 text-purple font-medium hover:underline z-10"
|
||||
@@ -659,11 +874,11 @@ function handleAuxClick(e) {
|
||||
<ArrowBigUpDashIcon class="text-2xl" /> Upgrade to Modrinth+
|
||||
</a>
|
||||
<PromotionWrapper />
|
||||
</template> -->
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<URLConfirmModal ref="urlModal" />
|
||||
<Notifications ref="notificationsWrapper" sidebar />
|
||||
<NotificationPanel has-sidebar />
|
||||
<ErrorModal ref="errorModal" />
|
||||
<ModInstallModal ref="modInstallModal" />
|
||||
<IncompatibilityWarningModal ref="incompatibilityWarningModal" />
|
||||
@@ -673,7 +888,6 @@ function handleAuxClick(e) {
|
||||
<style lang="scss" scoped>
|
||||
@import '../../../packages/assets/styles/neon-icon.scss';
|
||||
@import '../../../packages/assets/styles/neon-text.scss';
|
||||
|
||||
.window-controls {
|
||||
z-index: 20;
|
||||
display: none;
|
||||
@@ -766,10 +980,6 @@ function handleAuxClick(e) {
|
||||
grid-area: status;
|
||||
}
|
||||
|
||||
[data-tauri-drag-region] {
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
|
||||
[data-tauri-drag-region-exclude] {
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
@@ -812,21 +1022,6 @@ function handleAuxClick(e) {
|
||||
--color-divider-dark: var(--brand-gradient-border);
|
||||
}
|
||||
|
||||
// .app-sidebar::after {
|
||||
// content: '';
|
||||
// position: absolute;
|
||||
// bottom: 250px;
|
||||
// left: 0;
|
||||
// right: 0;
|
||||
// height: 5rem;
|
||||
// background: var(--brand-gradient-fade-out-color);
|
||||
// pointer-events: none;
|
||||
// }
|
||||
|
||||
// .app-sidebar.has-plus::after {
|
||||
// display: none;
|
||||
// }
|
||||
|
||||
.app-sidebar::before {
|
||||
content: '';
|
||||
box-shadow: -15px 0 15px -15px rgba(0, 0, 0, 0.2) inset;
|
||||
@@ -871,6 +1066,104 @@ function handleAuxClick(e) {
|
||||
.sidebar-teleport-content:empty + .sidebar-default-content.sidebar-enabled {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.popup-survey-enter-active {
|
||||
transition:
|
||||
opacity 0.25s ease,
|
||||
transform 0.25s cubic-bezier(0.51, 1.08, 0.35, 1.15);
|
||||
transform-origin: top center;
|
||||
}
|
||||
|
||||
.popup-survey-leave-active {
|
||||
transition:
|
||||
opacity 0.25s ease,
|
||||
transform 0.25s cubic-bezier(0.68, -0.17, 0.23, 0.11);
|
||||
transform-origin: top center;
|
||||
}
|
||||
|
||||
.popup-survey-enter-from,
|
||||
.popup-survey-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(10rem) scale(0.8) scaleY(1.6);
|
||||
}
|
||||
|
||||
.toast-enter-active {
|
||||
transition: opacity 0.25s linear;
|
||||
}
|
||||
|
||||
.toast-enter-from,
|
||||
.toast-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.toast-enter-active,
|
||||
.nav-button-animated-enter-active {
|
||||
transition: all 0.5s cubic-bezier(0.15, 1.4, 0.64, 0.96);
|
||||
}
|
||||
|
||||
.toast-leave-active,
|
||||
.nav-button-animated-leave-active {
|
||||
transition: all 0.25s ease;
|
||||
}
|
||||
|
||||
.toast-enter-from {
|
||||
scale: 0.5;
|
||||
translate: 0 -10rem;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.toast-leave-to {
|
||||
scale: 0.96;
|
||||
translate: 20rem 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.nav-button-animated-enter-active {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nav-button-animated-enter-active::before {
|
||||
content: '';
|
||||
inset: 0;
|
||||
border-radius: 100vw;
|
||||
background-color: var(--color-brand-highlight);
|
||||
position: absolute;
|
||||
animation: pop 0.5s ease-in forwards;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@keyframes pop {
|
||||
0% {
|
||||
scale: 0.5;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
100% {
|
||||
scale: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-button-animated-enter-from {
|
||||
scale: 0.5;
|
||||
translate: -2rem 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.nav-button-animated-leave-to {
|
||||
scale: 0.75;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.fade-enter-active {
|
||||
transition: 0.25s ease-in-out;
|
||||
}
|
||||
|
||||
.fade-enter-from {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
.mac {
|
||||
|
||||
+12
-12
@@ -1,18 +1,18 @@
|
||||
export { default as ATLauncherIcon } from './atlauncher.svg'
|
||||
export { default as BuyMeACoffeeIcon } from './bmac.svg'
|
||||
export { default as DiscordIcon } from './discord.svg'
|
||||
export { default as GDLauncherIcon } from './gdlauncher.png'
|
||||
export { default as GithubIcon } from './github.svg'
|
||||
export { default as GitLabIcon } from './gitlab.svg'
|
||||
export { default as GoogleIcon } from './google.svg'
|
||||
export { default as KoFiIcon } from './kofi.svg'
|
||||
export { default as MastodonIcon } from './mastodon.svg'
|
||||
export { default as MicrosoftIcon } from './microsoft.svg'
|
||||
export { default as MultiMCIcon } from './multimc.webp'
|
||||
export { default as OpenCollectiveIcon } from './opencollective.svg'
|
||||
export { default as PatreonIcon } from './patreon.svg'
|
||||
export { default as PaypalIcon } from './paypal.svg'
|
||||
export { default as OpenCollectiveIcon } from './opencollective.svg'
|
||||
export { default as TwitterIcon } from './twitter.svg'
|
||||
export { default as GithubIcon } from './github.svg'
|
||||
export { default as MastodonIcon } from './mastodon.svg'
|
||||
export { default as RedditIcon } from './reddit.svg'
|
||||
export { default as GoogleIcon } from './google.svg'
|
||||
export { default as MicrosoftIcon } from './microsoft.svg'
|
||||
export { default as SteamIcon } from './steam.svg'
|
||||
export { default as GitLabIcon } from './gitlab.svg'
|
||||
export { default as ATLauncherIcon } from './atlauncher.svg'
|
||||
export { default as GDLauncherIcon } from './gdlauncher.png'
|
||||
export { default as MultiMCIcon } from './multimc.webp'
|
||||
export { default as PrismIcon } from './prism.svg'
|
||||
export { default as RedditIcon } from './reddit.svg'
|
||||
export { default as SteamIcon } from './steam.svg'
|
||||
export { default as TwitterIcon } from './twitter.svg'
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
export { default as SwapIcon } from './arrow-left-right.svg'
|
||||
export { default as ToggleIcon } from './toggle.svg'
|
||||
export { default as PackageIcon } from './package.svg'
|
||||
export { default as VersionIcon } from './milestone.svg'
|
||||
export { default as TextInputIcon } from './text-cursor-input.svg'
|
||||
export { default as AddProjectImage } from './add-project.svg'
|
||||
export { default as NewInstanceImage } from './new-instance.svg'
|
||||
export { default as SwapIcon } from './arrow-left-right.svg'
|
||||
export { default as MenuIcon } from './menu.svg'
|
||||
export { default as ChatIcon } from './messages-square.svg'
|
||||
export { default as Pirate } from './pirate.svg'
|
||||
export { default as Microsoft } from './microsoft.svg'
|
||||
export { default as PirateShip } from './pirate-ship.svg'
|
||||
export { default as VersionIcon } from './milestone.svg'
|
||||
export { default as NewInstanceImage } from './new-instance.svg'
|
||||
export { default as PackageIcon } from './package.svg'
|
||||
export { default as TextInputIcon } from './text-cursor-input.svg'
|
||||
export { default as ToggleIcon } from './toggle.svg'
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
<script setup>
|
||||
import Instance from '@/components/ui/Instance.vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import {
|
||||
ClipboardCopyIcon,
|
||||
EyeIcon,
|
||||
FolderOpenIcon,
|
||||
PlayIcon,
|
||||
PlusIcon,
|
||||
TrashIcon,
|
||||
StopCircleIcon,
|
||||
EyeIcon,
|
||||
SearchIcon,
|
||||
StopCircleIcon,
|
||||
TrashIcon,
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { Button, DropdownSelect } from '@modrinth/ui'
|
||||
import { Button, DropdownSelect, injectNotificationManager } from '@modrinth/ui'
|
||||
import { formatCategoryHeader } from '@modrinth/utils'
|
||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { duplicate, remove } from '@/helpers/profile.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||
import Instance from '@/components/ui/Instance.vue'
|
||||
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
|
||||
import { duplicate, remove } from '@/helpers/profile.js'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
|
||||
const props = defineProps({
|
||||
instances: {
|
||||
@@ -136,7 +138,7 @@ const filteredResults = computed(() => {
|
||||
|
||||
if (sortBy.value === 'Game version') {
|
||||
instances.sort((a, b) => {
|
||||
return a.game_version.localeCompare(b.game_version)
|
||||
return a.game_version.localeCompare(b.game_version, undefined, { numeric: true })
|
||||
})
|
||||
}
|
||||
|
||||
@@ -213,6 +215,17 @@ const filteredResults = computed(() => {
|
||||
instanceMap.set(entry[0], entry[1])
|
||||
})
|
||||
}
|
||||
// default sorting would do 1.20.4 < 1.8.9 because 2 < 8
|
||||
// localeCompare with numeric=true puts 1.8.9 < 1.20.4 because 8 < 20
|
||||
if (group.value === 'Game version') {
|
||||
const sortedEntries = [...instanceMap.entries()].sort((a, b) => {
|
||||
return a[0].localeCompare(b[0], undefined, { numeric: true })
|
||||
})
|
||||
instanceMap.clear()
|
||||
sortedEntries.forEach((entry) => {
|
||||
instanceMap.set(entry[0], entry[1])
|
||||
})
|
||||
}
|
||||
|
||||
return instanceMap
|
||||
})
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup>
|
||||
import { computed, onBeforeUnmount, ref, watch } from 'vue'
|
||||
|
||||
import { useLoading } from '@/store/state.js'
|
||||
|
||||
const props = defineProps({
|
||||
|
||||
@@ -1,31 +1,33 @@
|
||||
<script setup>
|
||||
import {
|
||||
ClipboardCopyIcon,
|
||||
FolderOpenIcon,
|
||||
PlayIcon,
|
||||
PlusIcon,
|
||||
TrashIcon,
|
||||
DownloadIcon,
|
||||
GlobeIcon,
|
||||
StopCircleIcon,
|
||||
ExternalIcon,
|
||||
EyeIcon,
|
||||
FolderOpenIcon,
|
||||
GlobeIcon,
|
||||
PlayIcon,
|
||||
PlusIcon,
|
||||
StopCircleIcon,
|
||||
TrashIcon,
|
||||
} from '@modrinth/assets'
|
||||
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
|
||||
import Instance from '@/components/ui/Instance.vue'
|
||||
import { HeadingLink, injectNotificationManager } from '@modrinth/ui'
|
||||
import { openUrl } from '@tauri-apps/plugin-opener'
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||
import ProjectCard from '@/components/ui/ProjectCard.vue'
|
||||
import { get_by_profile_path } from '@/helpers/process.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { duplicate, kill, remove, run } from '@/helpers/profile.js'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { showProfileInFolder } from '@/helpers/utils.js'
|
||||
|
||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||
import Instance from '@/components/ui/Instance.vue'
|
||||
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
|
||||
import ProjectCard from '@/components/ui/ProjectCard.vue'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { get_by_profile_path } from '@/helpers/process.js'
|
||||
import { duplicate, kill, remove, run } from '@/helpers/profile.js'
|
||||
import { showProfileInFolder } from '@/helpers/utils.js'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
import { install as installVersion } from '@/store/install.js'
|
||||
import { openUrl } from '@tauri-apps/plugin-opener'
|
||||
import { HeadingLink } from '@modrinth/ui'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
@@ -163,7 +165,14 @@ const handleOptionsClick = async (args) => {
|
||||
await navigator.clipboard.writeText(args.item.path)
|
||||
break
|
||||
case 'install': {
|
||||
await installVersion(args.item.project_id, null, null, 'ProjectCardContextMenu')
|
||||
await installVersion(
|
||||
args.item.project_id,
|
||||
null,
|
||||
null,
|
||||
'ProjectCardContextMenu',
|
||||
() => {},
|
||||
() => {},
|
||||
).catch(handleError)
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="mode !== 'isolated'"
|
||||
ref="button"
|
||||
<div v-if="mode !== 'isolated'" ref="button"
|
||||
class="button-base mt-2 px-3 py-2 bg-button-bg rounded-xl flex items-center gap-2"
|
||||
:class="{ expanded: mode === 'expanded' }"
|
||||
@click="toggleMenu"
|
||||
>
|
||||
<Avatar
|
||||
size="36px"
|
||||
:src="
|
||||
selectedAccount ? avatarUrl : 'https://launcher-files.modrinth.com/assets/steve_head.png'
|
||||
"
|
||||
/>
|
||||
:class="{ expanded: mode === 'expanded' }" @click="toggleMenu">
|
||||
<Avatar size="36px" :src="selectedAccount ? avatarUrl : 'https://launcher-files.modrinth.com/assets/steve_head.png'
|
||||
" />
|
||||
<div class="flex flex-col w-full">
|
||||
<span>
|
||||
<component :is="getAccountType(selectedAccount)" v-if="selectedAccount" class="vector-icon" />
|
||||
@@ -28,34 +20,28 @@
|
||||
<Avatar size="xs" :src="avatarUrl" />
|
||||
<div>
|
||||
<h4>
|
||||
<component :is="getAccountType(selectedAccount)" class="vector-icon" /> {{ selectedAccount.profile.name }}
|
||||
<component :is="getAccountType(selectedAccount)" class="vector-icon" /> {{
|
||||
selectedAccount.profile.name }}
|
||||
</h4>
|
||||
<p>Selected</p>
|
||||
</div>
|
||||
<Button
|
||||
v-tooltip="'Log out'"
|
||||
icon-only
|
||||
color="raised"
|
||||
@click="logout(selectedAccount.profile.id)"
|
||||
>
|
||||
<Button v-tooltip="'Log out'" icon-only color="raised" @click="logout(selectedAccount.profile.id)">
|
||||
<TrashIcon />
|
||||
</Button>
|
||||
</div>
|
||||
<div v-else class="login-section account">
|
||||
<h4>Not signed in</h4>
|
||||
<Button
|
||||
v-tooltip="'Log in'"
|
||||
:disabled="loginDisabled"
|
||||
icon-only
|
||||
color="primary"
|
||||
@click="login()"
|
||||
>
|
||||
<MicrosoftIcon v-if="!loginDisabled"/>
|
||||
<Button v-tooltip="'Log via Microsoft'" :disabled="microsoftLoginDisabled" icon-only @click="login()">
|
||||
<MicrosoftIcon v-if="!microsoftLoginDisabled" />
|
||||
<SpinnerIcon v-else class="animate-spin" />
|
||||
</Button>
|
||||
<Button v-tooltip="'Add offline'" icon-only @click="tryOfflineLogin()">
|
||||
<Button v-tooltip="'Add offline account'" icon-only @click="showOfflineLoginModal()">
|
||||
<PirateIcon />
|
||||
</Button>
|
||||
<Button v-tooltip="'Log via Ely.by'" icon-only @click="showElybyLoginModal()">
|
||||
<ElyByIcon v-if="!elybyLoginDisabled" />
|
||||
<SpinnerIcon v-else class="animate-spin" />
|
||||
</Button>
|
||||
</div>
|
||||
<div v-if="displayAccounts.length > 0" class="account-group">
|
||||
<div v-for="account in displayAccounts" :key="account.profile.id" class="account-row">
|
||||
@@ -72,35 +58,112 @@
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="accounts.length > 0" class="login-section account centered">
|
||||
<Button v-tooltip="'Log in'" icon-only @click="login()">
|
||||
<MicrosoftIcon />
|
||||
<Button v-tooltip="'Log via Microsoft'" icon-only @click="login()">
|
||||
<MicrosoftIcon v-if="!microsoftLoginDisabled" />
|
||||
<SpinnerIcon v-else class="animate-spin" />
|
||||
</Button>
|
||||
<Button v-tooltip="'Add offline'" icon-only @click="tryOfflineLogin()">
|
||||
<Button v-tooltip="'Add offline account'" icon-only @click="showOfflineLoginModal()">
|
||||
<PirateIcon />
|
||||
</Button>
|
||||
<Button v-tooltip="'Log via Ely.by'" icon-only @click="showElybyLoginModal()">
|
||||
<ElyByIcon v-if="!elybyLoginDisabled" />
|
||||
<SpinnerIcon v-else class="animate-spin" />
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</transition>
|
||||
<ModalWrapper ref="loginOfflineModal" class="modal" header="Add new offline account">
|
||||
<div class="modal-body">
|
||||
<div class="label">Enter offline username</div>
|
||||
<input type="text" v-model="playerName" placeholder="Provide offline player name" />
|
||||
<Button icon-only color="secondary" @click="offlineLoginFinally()">
|
||||
<ModalWrapper ref="addElybyModal" class="modal" header="Authenticate with Ely.by">
|
||||
<ModalWrapper ref="requestElybyTwoFactorCodeModal" class="modal"
|
||||
header="Ely.by requested 2FA code for authentication">
|
||||
<div class="flex flex-col gap-4 px-6 py-5">
|
||||
<label class="label">Enter your 2FA code</label>
|
||||
<input v-model="elybyTwoFactorCode" type="text" placeholder="Your 2FA code here..." class="input" />
|
||||
<div class="mt-6 ml-auto">
|
||||
<Button icon-only color="primary" class="continue-button" @click="addElybyProfile()">
|
||||
Continue
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</ModalWrapper>
|
||||
<ModalWrapper ref="loginErrorModal" class="modal" header="Error while proceed">
|
||||
<div class="modal-body">
|
||||
<div class="label">Error occurred while adding offline account</div>
|
||||
<Button color="primary" @click="retryOfflineLogin()">
|
||||
<div class="flex flex-col gap-4 px-6 py-5">
|
||||
<label class="label">Enter your player name or email (preferred)</label>
|
||||
<input v-model="elybyLogin" type="text" placeholder="Your player name or email here..." class="input" />
|
||||
<label class="label">Enter your password</label>
|
||||
<input v-model="elybyPassword" type="password" placeholder="Your password here..." class="input" />
|
||||
<div class="mt-6 ml-auto">
|
||||
<Button icon-only color="primary" class="continue-button" @click="addElybyProfile()">
|
||||
Login
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</ModalWrapper>
|
||||
<ModalWrapper ref="addOfflineModal" class="modal" header="Add new offline account">
|
||||
<div class="flex flex-col gap-4 px-6 py-5">
|
||||
<label class="label">Enter your player name</label>
|
||||
<input v-model="offlinePlayerName" type="text" placeholder="Your player name here..." class="input" />
|
||||
<div class="mt-6 ml-auto">
|
||||
<Button icon-only color="primary" class="continue-button" @click="addOfflineProfile()">
|
||||
Login
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</ModalWrapper>
|
||||
<ModalWrapper ref="authenticationElybyErrorModal" class="modal"
|
||||
header="Error while proceeding authentication event with Ely.by">
|
||||
<div class="flex flex-col gap-4 px-6 py-5">
|
||||
<label class="text-base font-medium text-red-700">
|
||||
An error occurred while logging in.
|
||||
</label>
|
||||
|
||||
<div class="mt-6 ml-auto">
|
||||
<Button color="primary" class="retry-button" @click="retryAddElybyProfile">
|
||||
Try again
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</ModalWrapper>
|
||||
<ModalWrapper ref="unexpectedErrorModal" class="modal" header="Ошибка">
|
||||
<ModalWrapper ref="inputElybyErrorModal" class="modal" header="Error while proceeding input event with Ely.by">
|
||||
<div class="flex flex-col gap-4 px-6 py-5">
|
||||
<label class="text-base font-medium text-red-700">
|
||||
An error occurred while adding the Ely.by account. Please follow the instructions below.
|
||||
</label>
|
||||
|
||||
<ul class="list-disc list-inside text-sm space-y-1">
|
||||
<li>Check that you have entered the correct player name or email.</li>
|
||||
<li>Check that you have entered the correct password.</li>
|
||||
</ul>
|
||||
|
||||
<div class="mt-6 ml-auto">
|
||||
<Button color="primary" class="retry-button" @click="retryAddElybyProfile">
|
||||
Try again
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</ModalWrapper>
|
||||
<ModalWrapper ref="inputErrorModal" class="modal" header="Error while proceeding input event with offline account">
|
||||
<div class="flex flex-col gap-4 px-6 py-5">
|
||||
<label class="text-base font-medium text-red-700">
|
||||
An error occurred while adding the offline account. Please follow the instructions below.
|
||||
</label>
|
||||
|
||||
<ul class="list-disc list-inside text-sm space-y-1">
|
||||
<li>Check that you have entered the correct player name.</li>
|
||||
<li>
|
||||
Player name must be at least {{ minOfflinePlayerNameLength }} characters long and no more than
|
||||
{{ maxOfflinePlayerNameLength }} characters.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="mt-6 ml-auto">
|
||||
<Button color="primary" class="retry-button" @click="retryAddOfflineProfile">
|
||||
Try again
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</ModalWrapper>
|
||||
<ModalWrapper ref="exceptionErrorModal" class="modal" header="Unexpected error occurred">
|
||||
<div class="modal-body">
|
||||
<div class="label">Unexcepted error</div>
|
||||
<label class="label">An unexpected error has occurred. Please try again later.</label>
|
||||
</div>
|
||||
</ModalWrapper>
|
||||
</template>
|
||||
@@ -108,17 +171,20 @@
|
||||
<script setup>
|
||||
import {
|
||||
DropdownIcon,
|
||||
PlusIcon,
|
||||
TrashIcon,
|
||||
LogInIcon,
|
||||
PirateIcon as Offline,
|
||||
MicrosoftIcon as License,
|
||||
ElyByIcon as Elyby,
|
||||
MicrosoftIcon,
|
||||
PirateIcon,
|
||||
SpinnerIcon } from '@modrinth/assets'
|
||||
import { Avatar, Button, Card } from '@modrinth/ui'
|
||||
ElyByIcon,
|
||||
SpinnerIcon
|
||||
} from '@modrinth/assets'
|
||||
import { Avatar, Button, Card, injectNotificationManager } from '@modrinth/ui'
|
||||
import { ref, computed, onMounted, onBeforeUnmount, onUnmounted } from 'vue'
|
||||
import {
|
||||
elyby_auth_authenticate,
|
||||
elyby_login,
|
||||
offline_login,
|
||||
users,
|
||||
remove_user,
|
||||
@@ -126,13 +192,14 @@ import {
|
||||
login as login_flow,
|
||||
get_default_user,
|
||||
} from '@/helpers/auth'
|
||||
import { handleError } from '@/store/state.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { process_listener } from '@/helpers/events'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import { get_available_skins } from '@/helpers/skins'
|
||||
import { getPlayerHeadUrl } from '@/helpers/rendering/batch-skin-renderer.ts'
|
||||
import { get_available_skins } from '@/helpers/skins'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
|
||||
defineProps({
|
||||
mode: {
|
||||
@@ -145,48 +212,180 @@ defineProps({
|
||||
const emit = defineEmits(['change'])
|
||||
|
||||
const accounts = ref({})
|
||||
const loginDisabled = ref(false)
|
||||
const microsoftLoginDisabled = ref(false)
|
||||
const elybyLoginDisabled = ref(false)
|
||||
const defaultUser = ref()
|
||||
const loginOfflineModal = ref(null)
|
||||
const loginErrorModal = ref(null)
|
||||
const unexpectedErrorModal = ref(null)
|
||||
const playerName = ref('')
|
||||
|
||||
async function tryOfflineLogin() { // [AR] Feature
|
||||
loginOfflineModal.value.show()
|
||||
// [AR] • Feature
|
||||
const clientToken = "astralrinth"
|
||||
const addOfflineModal = ref(null)
|
||||
const addElybyModal = ref(null)
|
||||
const requestElybyTwoFactorCodeModal = ref(null)
|
||||
const authenticationElybyErrorModal = ref(null)
|
||||
const inputElybyErrorModal = ref(null)
|
||||
const inputErrorModal = ref(null)
|
||||
const exceptionErrorModal = ref(null)
|
||||
const offlinePlayerName = ref('')
|
||||
const elybyLogin = ref('')
|
||||
const elybyPassword = ref('')
|
||||
const elybyTwoFactorCode = ref('')
|
||||
const minOfflinePlayerNameLength = 2
|
||||
const maxOfflinePlayerNameLength = 20
|
||||
|
||||
// [AR] • Feature
|
||||
function getAccountType(account) {
|
||||
switch (account.account_type) {
|
||||
case 'microsoft':
|
||||
return License
|
||||
case 'pirate':
|
||||
return Offline
|
||||
case 'elyby':
|
||||
return Elyby
|
||||
}
|
||||
}
|
||||
|
||||
async function offlineLoginFinally() { // [AR] Feature
|
||||
const name = playerName.value
|
||||
if (name.length > 1 && name.length < 20 && name !== '') {
|
||||
const loggedIn = await offline_login(name).catch(handleError)
|
||||
loginOfflineModal.value.hide()
|
||||
if (loggedIn) {
|
||||
await setAccount(loggedIn)
|
||||
// [AR] • Feature
|
||||
function showOfflineLoginModal() {
|
||||
addOfflineModal.value?.show()
|
||||
}
|
||||
|
||||
// [AR] • Feature
|
||||
function showElybyLoginModal() {
|
||||
addElybyModal.value?.show()
|
||||
}
|
||||
|
||||
// [AR] • Feature
|
||||
function retryAddOfflineProfile() {
|
||||
inputErrorModal.value?.hide()
|
||||
clearOfflineFields()
|
||||
showOfflineLoginModal()
|
||||
}
|
||||
|
||||
// [AR] • Feature
|
||||
function retryAddElybyProfile() {
|
||||
authenticationElybyErrorModal.value?.hide()
|
||||
inputElybyErrorModal.value?.hide()
|
||||
clearElybyFields()
|
||||
showElybyLoginModal()
|
||||
}
|
||||
|
||||
// [AR] • Feature
|
||||
function clearElybyFields() {
|
||||
elybyLogin.value = ''
|
||||
elybyPassword.value = ''
|
||||
elybyTwoFactorCode.value = ''
|
||||
}
|
||||
|
||||
// [AR] • Feature
|
||||
function clearOfflineFields() {
|
||||
offlinePlayerName.value = ''
|
||||
}
|
||||
|
||||
// [AR] • Feature
|
||||
async function addOfflineProfile() {
|
||||
const name = offlinePlayerName.value.trim()
|
||||
const isValidName = name.length >= minOfflinePlayerNameLength && name.length <= maxOfflinePlayerNameLength
|
||||
|
||||
if (!isValidName) {
|
||||
addOfflineModal.value?.hide()
|
||||
inputErrorModal.value?.show()
|
||||
clearOfflineFields()
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await offline_login(name)
|
||||
|
||||
addOfflineModal.value?.hide()
|
||||
|
||||
if (result) {
|
||||
await setAccount(result)
|
||||
await refreshValues()
|
||||
} else {
|
||||
unexpectedErrorModal.value.show()
|
||||
exceptionErrorModal.value?.show()
|
||||
}
|
||||
playerName.value = ''
|
||||
} else {
|
||||
playerName.value = ''
|
||||
loginOfflineModal.value.hide()
|
||||
loginErrorModal.value.show()
|
||||
} catch (error) {
|
||||
handleError(error)
|
||||
exceptionErrorModal.value?.show()
|
||||
} finally {
|
||||
clearOfflineFields()
|
||||
}
|
||||
}
|
||||
|
||||
function retryOfflineLogin() { // [AR] Feature
|
||||
loginErrorModal.value.hide()
|
||||
tryOfflineLogin()
|
||||
}
|
||||
// [AR] • Feature
|
||||
async function addElybyProfile() {
|
||||
if (!elybyLogin.value || !elybyPassword.value) {
|
||||
addElybyModal.value?.hide()
|
||||
inputElybyErrorModal.value?.show()
|
||||
clearElybyFields()
|
||||
return
|
||||
}
|
||||
elybyLoginDisabled.value = true
|
||||
|
||||
function getAccountType(account) { // [AR] Feature
|
||||
if (account.access_token != "null" && account.access_token != null && account.access_token != "") {
|
||||
return License
|
||||
} else {
|
||||
return Offline
|
||||
const login = elybyLogin.value.trim()
|
||||
let password = elybyPassword.value.trim()
|
||||
const twoFactorCode = elybyTwoFactorCode.value.trim()
|
||||
if (password && twoFactorCode) {
|
||||
password = `${password}:${twoFactorCode}`
|
||||
}
|
||||
|
||||
try {
|
||||
const raw_result = await elyby_auth_authenticate(
|
||||
login,
|
||||
password,
|
||||
clientToken
|
||||
)
|
||||
|
||||
const json_data = JSON.parse(raw_result)
|
||||
|
||||
console.log(json_data?.error)
|
||||
console.log(json_data?.errorMessage)
|
||||
|
||||
if (!json_data.accessToken) {
|
||||
if (
|
||||
json_data.error === 'ForbiddenOperationException' &&
|
||||
json_data.errorMessage?.includes('two factor')
|
||||
) {
|
||||
requestElybyTwoFactorCodeModal.value?.show()
|
||||
return
|
||||
}
|
||||
|
||||
addElybyModal.value?.hide()
|
||||
requestElybyTwoFactorCodeModal.value?.hide()
|
||||
authenticationElybyErrorModal.value?.show()
|
||||
return
|
||||
}
|
||||
|
||||
const accessToken = json_data.accessToken
|
||||
const selectedProfileId = convertRawStringToUUIDv4(json_data.selectedProfile.id)
|
||||
const selectedProfileName = json_data.selectedProfile.name
|
||||
|
||||
const result = await elyby_login(selectedProfileId, selectedProfileName, accessToken)
|
||||
|
||||
addElybyModal.value?.hide()
|
||||
requestElybyTwoFactorCodeModal.value?.hide()
|
||||
|
||||
clearElybyFields()
|
||||
|
||||
await setAccount(result)
|
||||
await refreshValues()
|
||||
} catch (err) {
|
||||
handleError(err)
|
||||
exceptionErrorModal.value?.show()
|
||||
} finally {
|
||||
elybyLoginDisabled.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// [AR] • Feature
|
||||
function convertRawStringToUUIDv4(rawId) {
|
||||
if (rawId.length !== 32) {
|
||||
console.warn('Invalid UUID string:', rawId)
|
||||
return rawId
|
||||
}
|
||||
return `${rawId.slice(0, 8)}-${rawId.slice(8, 12)}-${rawId.slice(12, 16)}-${rawId.slice(16, 20)}-${rawId.slice(20)}`
|
||||
}
|
||||
|
||||
const equippedSkin = ref(null)
|
||||
const headUrlCache = ref(new Map())
|
||||
|
||||
@@ -212,13 +411,13 @@ async function refreshValues() {
|
||||
}
|
||||
|
||||
function setLoginDisabled(value) {
|
||||
loginDisabled.value = value
|
||||
microsoftLoginDisabled.value = value
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
refreshValues,
|
||||
setLoginDisabled,
|
||||
loginDisabled,
|
||||
microsoftLoginDisabled,
|
||||
})
|
||||
await refreshValues()
|
||||
|
||||
@@ -264,7 +463,7 @@ async function setAccount(account) {
|
||||
}
|
||||
|
||||
async function login() {
|
||||
loginDisabled.value = true
|
||||
microsoftLoginDisabled.value = true
|
||||
const loggedIn = await login_flow().catch(handleSevereError)
|
||||
|
||||
if (loggedIn) {
|
||||
@@ -273,7 +472,7 @@ async function login() {
|
||||
}
|
||||
|
||||
trackEvent('AccountLogIn')
|
||||
loginDisabled.value = false
|
||||
microsoftLoginDisabled.value = false
|
||||
}
|
||||
|
||||
const logout = async (id) => {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { DropdownIcon, PlusIcon, FolderOpenIcon } from '@modrinth/assets'
|
||||
import { ButtonStyled, OverflowMenu } from '@modrinth/ui'
|
||||
import { DropdownIcon, FolderOpenIcon, PlusIcon } from '@modrinth/assets'
|
||||
import { ButtonStyled, injectNotificationManager, OverflowMenu } from '@modrinth/ui'
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { add_project_from_path } from '@/helpers/profile.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import { add_project_from_path } from '@/helpers/profile.js'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
|
||||
const props = defineProps({
|
||||
instance: {
|
||||
type: Object,
|
||||
|
||||
@@ -42,11 +42,12 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ChevronRightIcon, ChevronLeftIcon } from '@modrinth/assets'
|
||||
import { ChevronLeftIcon, ChevronRightIcon } from '@modrinth/assets'
|
||||
import { Button } from '@modrinth/ui'
|
||||
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
<template>
|
||||
<transition name="fade">
|
||||
<div v-show="shown" ref="contextMenu" class="context-menu" :style="{
|
||||
<div
|
||||
v-show="shown"
|
||||
ref="contextMenu"
|
||||
class="context-menu"
|
||||
:style="{
|
||||
left: left,
|
||||
top: top,
|
||||
}">
|
||||
}"
|
||||
>
|
||||
<div v-for="(option, index) in options" :key="index" @click.stop="optionClicked(option.name)">
|
||||
<hr v-if="option.type === 'divider'" class="divider" />
|
||||
<div v-else-if="!(isLinkedData(item) && option.name === `add_content`)" class="item clickable"
|
||||
:class="[option.color ?? 'base']">
|
||||
<div
|
||||
v-else-if="!(isLinkedData(item) && option.name === `add_content`)"
|
||||
class="item clickable"
|
||||
:class="[option.color ?? 'base']"
|
||||
>
|
||||
<slot :name="option.name" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,380 @@
|
||||
<template>
|
||||
<ModalWrapper ref="modal" :header="'Import from CurseForge Profile Code'">
|
||||
<div class="modal-body">
|
||||
<div class="input-row">
|
||||
<p class="input-label">Profile Code</p>
|
||||
<div class="iconified-input">
|
||||
<SearchIcon aria-hidden="true" class="text-lg" />
|
||||
<input ref="codeInput" v-model="profileCode" autocomplete="off" class="h-12 card-shadow"
|
||||
spellcheck="false" type="text" placeholder="Enter CurseForge profile code" maxlength="20"
|
||||
@keyup.enter="importProfile" />
|
||||
<Button v-if="profileCode" class="r-btn" @click="() => (profileCode = '')">
|
||||
<XIcon />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="metadata && !importing" class="profile-info">
|
||||
<h3>Profile Information</h3>
|
||||
<p><strong>Name:</strong> {{ metadata.name }}</p>
|
||||
</div>
|
||||
|
||||
<div v-if="error" class="error-message">
|
||||
<p>{{ error }}</p>
|
||||
</div>
|
||||
|
||||
<div v-if="importing && importProgress.visible" class="progress-section">
|
||||
<div class="progress-info">
|
||||
<span class="progress-text">{{ importProgress.message }}</span>
|
||||
<span class="progress-percentage">{{ Math.floor(importProgress.percentage) }}%</span>
|
||||
</div>
|
||||
<div class="progress-bar-container">
|
||||
<div class="progress-bar" :style="{ width: `${importProgress.percentage}%` }"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="button-row">
|
||||
<Button @click="hide" :disabled="importing">
|
||||
<XIcon />
|
||||
Cancel
|
||||
</Button>
|
||||
<Button v-if="!metadata" @click="fetchMetadata" :disabled="!profileCode.trim() || fetching"
|
||||
color="secondary">
|
||||
<SearchIcon v-if="!fetching" />
|
||||
{{ fetching ? 'Checking...' : 'Check Profile' }}
|
||||
</Button>
|
||||
<Button v-if="metadata" @click="importProfile" :disabled="importing" color="primary">
|
||||
<DownloadIcon v-if="!importing" />
|
||||
{{ importing ? 'Importing...' : 'Import Profile' }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</ModalWrapper>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, nextTick, onMounted, onUnmounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import { Button } from '@modrinth/ui'
|
||||
import {
|
||||
XIcon,
|
||||
SearchIcon,
|
||||
DownloadIcon
|
||||
} from '@modrinth/assets'
|
||||
import {
|
||||
fetch_curseforge_profile_metadata,
|
||||
import_curseforge_profile
|
||||
} from '@/helpers/import.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { loading_listener } from '@/helpers/events.js'
|
||||
|
||||
const props = defineProps({
|
||||
closeParent: {
|
||||
type: Function,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
const modal = ref(null)
|
||||
const codeInput = ref(null)
|
||||
const profileCode = ref('')
|
||||
const metadata = ref(null)
|
||||
const fetching = ref(false)
|
||||
const importing = ref(false)
|
||||
const error = ref('')
|
||||
const importProgress = ref({
|
||||
visible: false,
|
||||
percentage: 0,
|
||||
message: 'Starting import...',
|
||||
totalMods: 0,
|
||||
downloadedMods: 0
|
||||
})
|
||||
|
||||
let unlistenLoading = null
|
||||
let activeLoadingBarId = null
|
||||
let progressFallbackTimer = null
|
||||
|
||||
defineExpose({
|
||||
show: () => {
|
||||
profileCode.value = ''
|
||||
metadata.value = null
|
||||
fetching.value = false
|
||||
importing.value = false
|
||||
error.value = ''
|
||||
importProgress.value = {
|
||||
visible: false,
|
||||
percentage: 0,
|
||||
message: 'Starting import...',
|
||||
totalMods: 0,
|
||||
downloadedMods: 0
|
||||
}
|
||||
modal.value?.show()
|
||||
|
||||
nextTick(() => {
|
||||
setTimeout(() => {
|
||||
codeInput.value?.focus()
|
||||
}, 100)
|
||||
})
|
||||
|
||||
trackEvent('CurseForgeProfileImportStart', { source: 'ImportModal' })
|
||||
},
|
||||
})
|
||||
|
||||
const hide = () => {
|
||||
modal.value?.hide()
|
||||
}
|
||||
|
||||
const fetchMetadata = async () => {
|
||||
if (!profileCode.value.trim()) return
|
||||
|
||||
fetching.value = true
|
||||
error.value = ''
|
||||
|
||||
try {
|
||||
const result = await fetch_curseforge_profile_metadata(profileCode.value.trim())
|
||||
metadata.value = result
|
||||
trackEvent('CurseForgeProfileMetadataFetched', {
|
||||
profileCode: profileCode.value.trim()
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch CurseForge profile metadata:', err)
|
||||
error.value = 'Failed to fetch profile information. Please check the code and try again.'
|
||||
handleError(err)
|
||||
} finally {
|
||||
fetching.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const importProfile = async () => {
|
||||
if (!profileCode.value.trim()) return
|
||||
|
||||
importing.value = true
|
||||
error.value = ''
|
||||
activeLoadingBarId = null // Reset for new import session
|
||||
importProgress.value = {
|
||||
visible: true,
|
||||
percentage: 0,
|
||||
message: 'Starting import...',
|
||||
totalMods: 0,
|
||||
downloadedMods: 0
|
||||
}
|
||||
|
||||
// Fallback progress timer in case loading events don't work
|
||||
progressFallbackTimer = setInterval(() => {
|
||||
if (importing.value && importProgress.value.percentage < 90) {
|
||||
// Slowly increment progress as a fallback
|
||||
importProgress.value.percentage = Math.min(90, importProgress.value.percentage + 1)
|
||||
}
|
||||
}, 1000)
|
||||
|
||||
try {
|
||||
const { result, profilePath } = await import_curseforge_profile(profileCode.value.trim())
|
||||
|
||||
trackEvent('CurseForgeProfileImported', {
|
||||
profileCode: profileCode.value.trim()
|
||||
})
|
||||
|
||||
hide()
|
||||
|
||||
// Close the parent modal if provided
|
||||
if (props.closeParent) {
|
||||
props.closeParent()
|
||||
}
|
||||
|
||||
// Navigate to the imported profile
|
||||
await router.push(`/instance/${encodeURIComponent(profilePath)}`)
|
||||
} catch (err) {
|
||||
console.error('Failed to import CurseForge profile:', err)
|
||||
error.value = 'Failed to import profile. Please try again.'
|
||||
handleError(err)
|
||||
} finally {
|
||||
importing.value = false
|
||||
importProgress.value.visible = false
|
||||
if (progressFallbackTimer) {
|
||||
clearInterval(progressFallbackTimer)
|
||||
progressFallbackTimer = null
|
||||
}
|
||||
activeLoadingBarId = null
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
// Listen for loading events to update progress
|
||||
unlistenLoading = await loading_listener((event) => {
|
||||
console.log('Loading event received:', event) // Debug log
|
||||
|
||||
// Handle all loading events that could be related to CurseForge profile import
|
||||
const isCurseForgeEvent = event.event?.type === 'curseforge_profile_download'
|
||||
const hasProfileName = event.event?.profile_name && importing.value
|
||||
|
||||
if ((isCurseForgeEvent || hasProfileName) && importing.value) {
|
||||
// Store the loading bar ID for this import session
|
||||
if (!activeLoadingBarId) {
|
||||
activeLoadingBarId = event.loader_uuid
|
||||
}
|
||||
|
||||
// Only process events for our current import session
|
||||
if (event.loader_uuid === activeLoadingBarId) {
|
||||
if (event.fraction !== null && event.fraction !== undefined) {
|
||||
const baseProgress = (event.fraction || 0) * 100
|
||||
|
||||
// Calculate custom progress based on the message
|
||||
let finalProgress = baseProgress
|
||||
const message = event.message || 'Importing profile...'
|
||||
|
||||
// Custom progress calculation for different stages
|
||||
if (message.includes('Fetching') || message.includes('metadata')) {
|
||||
finalProgress = Math.min(10, baseProgress)
|
||||
} else if (message.includes('Downloading profile ZIP') || message.includes('profile ZIP')) {
|
||||
finalProgress = Math.min(15, 10 + (baseProgress - 10) * 0.5)
|
||||
} else if (message.includes('Extracting') || message.includes('ZIP')) {
|
||||
finalProgress = Math.min(20, 15 + (baseProgress - 15) * 0.5)
|
||||
} else if (message.includes('Configuring') || message.includes('profile')) {
|
||||
finalProgress = Math.min(30, 20 + (baseProgress - 20) * 0.5)
|
||||
} else if (message.includes('Copying') || message.includes('files')) {
|
||||
finalProgress = Math.min(40, 30 + (baseProgress - 30) * 0.5)
|
||||
} else if (message.includes('Downloaded mod') && message.includes(' of ')) {
|
||||
// Parse "Downloaded mod X of Y" message
|
||||
const match = message.match(/Downloaded mod (\d+) of (\d+)/)
|
||||
if (match) {
|
||||
const current = parseInt(match[1])
|
||||
const total = parseInt(match[2])
|
||||
// Mods take 40% of progress (from 40% to 80%)
|
||||
const modProgress = (current / total) * 40
|
||||
finalProgress = 40 + modProgress
|
||||
} else {
|
||||
finalProgress = Math.min(80, 40 + (baseProgress - 40) * 0.5)
|
||||
}
|
||||
} else if (message.includes('Downloading mod') || message.includes('mods')) {
|
||||
// General mod downloading stage (40% to 80%)
|
||||
finalProgress = Math.min(80, 40 + (baseProgress - 40) * 0.4)
|
||||
} else if (message.includes('Installing Minecraft') || message.includes('Minecraft')) {
|
||||
finalProgress = Math.min(95, 80 + (baseProgress - 80) * 0.75)
|
||||
} else if (message.includes('Finalizing') || message.includes('completed')) {
|
||||
finalProgress = Math.min(100, 95 + (baseProgress - 95))
|
||||
} else {
|
||||
// Default: use the base progress but ensure minimum progression
|
||||
finalProgress = Math.max(importProgress.value.percentage, baseProgress)
|
||||
}
|
||||
|
||||
importProgress.value.percentage = Math.min(100, Math.max(0, finalProgress))
|
||||
importProgress.value.message = message
|
||||
} else {
|
||||
// Loading complete
|
||||
importProgress.value.percentage = 100
|
||||
importProgress.value.message = 'Import completed!'
|
||||
activeLoadingBarId = null
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (unlistenLoading) {
|
||||
unlistenLoading()
|
||||
}
|
||||
if (progressFallbackTimer) {
|
||||
clearInterval(progressFallbackTimer)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.modal-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.input-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.input-label {
|
||||
font-weight: 600;
|
||||
color: var(--color-contrast);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.profile-info {
|
||||
background: var(--color-bg);
|
||||
border: 1px solid var(--color-button);
|
||||
border-radius: var(--radius-md);
|
||||
padding: 1rem;
|
||||
|
||||
h3 {
|
||||
margin: 0 0 0.5rem 0;
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0.25rem 0;
|
||||
color: var(--color-base);
|
||||
}
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background: var(--color-red);
|
||||
border: 1px solid var(--color-red);
|
||||
border-radius: var(--radius-md);
|
||||
padding: 0.75rem;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
color: var(--color-contrast);
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.progress-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.progress-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.progress-text {
|
||||
color: var(--color-base);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.progress-percentage {
|
||||
color: var(--color-contrast);
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.progress-bar-container {
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background: var(--color-button);
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background: var(--color-brand);
|
||||
border-radius: 2px;
|
||||
transition: width 0.3s ease;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.button-row {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
justify-content: flex-end;
|
||||
margin-top: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -1,31 +1,34 @@
|
||||
<script setup>
|
||||
import {
|
||||
CheckIcon,
|
||||
CopyIcon,
|
||||
DropdownIcon,
|
||||
XIcon,
|
||||
HammerIcon,
|
||||
LogInIcon,
|
||||
UpdatedIcon,
|
||||
CopyIcon,
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { ButtonStyled, Collapsible, injectNotificationManager } from '@modrinth/ui'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { ChatIcon } from '@/assets/icons'
|
||||
import { ButtonStyled, Collapsible } from '@modrinth/ui'
|
||||
import { ref, computed } from 'vue'
|
||||
import { login as login_flow, set_default_user } from '@/helpers/auth.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
import { cancel_directory_change } from '@/helpers/settings.ts'
|
||||
import { install } from '@/helpers/profile.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { login as login_flow, set_default_user } from '@/helpers/auth.js'
|
||||
import { install } from '@/helpers/profile.js'
|
||||
import { cancel_directory_change } from '@/helpers/settings.ts'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
|
||||
// [AR] Imports
|
||||
import { applyMigrationFix } from '@/helpers/utils.js'
|
||||
import { restartApp } from '@/helpers/utils.js'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
|
||||
const errorModal = ref()
|
||||
const error = ref()
|
||||
const closable = ref(true)
|
||||
const errorCollapsed = ref(false)
|
||||
const language = ref('en')
|
||||
const migrationFixSuccess = ref(null) // null | true | false
|
||||
const migrationFixCallbackModel = ref()
|
||||
|
||||
@@ -75,7 +78,7 @@ defineExpose({
|
||||
supportLink.value = 'https://support.modrinth.com'
|
||||
metadata.value.profilePath = context.profilePath
|
||||
} else if (source === 'state_init') {
|
||||
title.value = 'Error initializing Modrinth App'
|
||||
title.value = 'Error initializing AstralRinth App'
|
||||
errorType.value = 'state_init'
|
||||
supportLink.value = 'https://support.modrinth.com'
|
||||
} else {
|
||||
@@ -154,10 +157,6 @@ async function copyToClipboard(text) {
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
function toggleLanguage() {
|
||||
language.value = language.value === 'en' ? 'ru' : 'en'
|
||||
}
|
||||
|
||||
async function onApplyMigrationFix(eol) {
|
||||
console.log(`[AR] • Attempting to apply migration ${eol.toUpperCase()} fix`)
|
||||
try {
|
||||
@@ -187,7 +186,7 @@ async function onApplyMigrationFix(eol) {
|
||||
<template v-if="metadata.network">
|
||||
<h3>Network issues</h3>
|
||||
<p>
|
||||
It looks like there were issues with the Modrinth App connecting to Microsoft's
|
||||
It looks like there were issues with the AstralRinth App connecting to Microsoft's
|
||||
servers. This is often the result of a poor connection, so we recommend trying again
|
||||
to see if it works. If issues continue to persist, follow the steps in
|
||||
<a
|
||||
@@ -201,7 +200,7 @@ async function onApplyMigrationFix(eol) {
|
||||
<template v-else-if="metadata.hostsFile">
|
||||
<h3>Network issues</h3>
|
||||
<p>
|
||||
The Modrinth App tried to connect to Microsoft / Xbox / Minecraft services, but the
|
||||
The AstralRinth App tried to connect to Microsoft / Xbox / Minecraft services, but the
|
||||
remote server rejected the connection. This may indicate that these services are
|
||||
blocked by the hosts file. Please visit
|
||||
<a
|
||||
@@ -240,7 +239,7 @@ async function onApplyMigrationFix(eol) {
|
||||
<template v-if="metadata.readOnly">
|
||||
<h3>Change directory permissions</h3>
|
||||
<p>
|
||||
It looks like the Modrinth App is unable to write to the directory you selected.
|
||||
It looks like the AstralRinth App is unable to write to the directory you selected.
|
||||
Please adjust the permissions of the directory and try again or cancel the directory
|
||||
change.
|
||||
</p>
|
||||
@@ -254,7 +253,7 @@ async function onApplyMigrationFix(eol) {
|
||||
</template>
|
||||
<template v-else>
|
||||
<p>
|
||||
The Modrinth App is unable to migrate to the new directory you selected. Please
|
||||
The AstralRinth App is unable to migrate to the new directory you selected. Please
|
||||
contact support for help or cancel the directory change.
|
||||
</p>
|
||||
</template>
|
||||
@@ -284,7 +283,7 @@ async function onApplyMigrationFix(eol) {
|
||||
</div>
|
||||
<template v-else-if="errorType === 'state_init'">
|
||||
<p>
|
||||
Modrinth App failed to load correctly. This may be because of a corrupted file, or
|
||||
AstralRinth App failed to load correctly. This may be because of a corrupted file, or
|
||||
because the app is missing crucial files.
|
||||
</p>
|
||||
<p>You may be able to fix it through one of the following ways:</p>
|
||||
@@ -294,7 +293,7 @@ async function onApplyMigrationFix(eol) {
|
||||
</ul>
|
||||
</template>
|
||||
<template v-else-if="errorType === 'no_loader_version'">
|
||||
<p>The Modrinth App failed to find the loader version for this instance.</p>
|
||||
<p>The AstralRinth App failed to find the loader version for this instance.</p>
|
||||
<p>To resolve this, you need to repair the instance. Click the button below to do so.</p>
|
||||
<div class="cta-button">
|
||||
<button class="btn btn-primary" :disabled="loadingRepair" @click="repairInstance">
|
||||
@@ -327,20 +326,10 @@ async function onApplyMigrationFix(eol) {
|
||||
<template v-if="copied"> <CheckIcon class="text-green" /> Copied! </template>
|
||||
<template v-else> <CopyIcon /> Copy debug info </template>
|
||||
</button>
|
||||
<ButtonStyled class="neon-button neon">
|
||||
<a href="https://me.astralium.su/get/ar/help" target="_blank" rel="noopener noreferrer">
|
||||
Get AstralRinth support
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled class="neon-button neon" >
|
||||
<a href="https://me.astralium.su/get/ar" target="_blank" rel="noopener noreferrer">
|
||||
Checkout latest releases
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<template v-if="hasDebugInfo">
|
||||
<div class="bg-button-bg rounded-xl mt-2 overflow-hidden">
|
||||
<div class="bg-button-bg rounded-xl mt-2 overflow-clip">
|
||||
<button
|
||||
class="flex items-center justify-between w-full bg-transparent border-0 px-4 py-3 cursor-pointer"
|
||||
@click="errorCollapsed = !errorCollapsed"
|
||||
@@ -352,76 +341,45 @@ async function onApplyMigrationFix(eol) {
|
||||
/>
|
||||
</button>
|
||||
<Collapsible :collapsed="errorCollapsed">
|
||||
<pre
|
||||
class="m-0 px-4 py-3 bg-bg rounded-none whitespace-pre-wrap break-words overflow-x-auto max-w-full"
|
||||
<pre class="m-0 px-4 py-3 bg-bg rounded-none whitespace-pre-wrap break-words overflow-x-auto max-w-full"
|
||||
>{{ debugInfo }}</pre>
|
||||
</Collapsible>
|
||||
</div>
|
||||
<template v-if="errorType === 'state_init'">
|
||||
<div class="notice">
|
||||
<div class="flex justify-between items-center">
|
||||
<h3 v-if="language === 'en'" class="notice__title">⚠️ Migration Issue • Important Notice ⚠️</h3>
|
||||
<h3 v-if="language === 'ru'" class="notice__title">⚠️ Проблема миграции • Важное уведомление ⚠️</h3>
|
||||
<ButtonStyled>
|
||||
<button @click="toggleLanguage">
|
||||
{{ language === 'en' ? '📖 Русский' : '📖 English' }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<p v-if="language === 'en'" class="notice__text">
|
||||
We're experiencing an issue with our database migration system due to differences in how different operating systems handle line endings. This might cause problems with our app's functionality.
|
||||
<h2>⚠️ Migration Issue • Important Notice</h2>
|
||||
<p>We've detected a problem with our database migration system caused by inconsistent line endings between operating systems (Windows vs. macOS/Linux). This may affect app stability.</p>
|
||||
<p><strong>What’s happening?</strong> Our migration validator misreads modified migrations when line endings differ (CRLF ↔ LF), which can make the app unusable.</p>
|
||||
<p><strong>Why?</strong> Git’s automatic line-ending conversions and OS differences can cause these inconsistencies during builds.</p>
|
||||
<p><strong>What’s next?</strong> We’re working on a permanent fix. In the meantime, you can apply one of the quick fixes below depending on your system.</p>
|
||||
<h3>Do I need to apply a fix now?</h3>
|
||||
<div>
|
||||
<p class="notice__text">
|
||||
If you're encountering an error while applying migrations, such as "Error while applying migrations: migration XXXXXXXXXX was previously applied but has been modified", or a similar issue with migration, the following actions might help:
|
||||
</p>
|
||||
<p v-if="language === 'en'" class="notice__text">
|
||||
<strong>What's happening?</strong> When we build our app, we use a system that checks the integrity of our database migrations. However, this system can get confused when it encounters different line endings (like CRLF vs LF) used by different operating systems. This can lead to errors and make our app unusable.
|
||||
</p>
|
||||
<p v-if="language === 'en'" class="notice__text">
|
||||
<strong>Why is this happening?</strong> This issue is caused by a combination of factors, including different operating systems handling line endings differently, Git's line ending conversion settings, and our app's build process.
|
||||
</p>
|
||||
<p v-if="language === 'en'" class="notice__text">
|
||||
<strong>What are we doing about it?</strong> We're working to resolve this issue and ensure that our app works smoothly for all users. In the meantime, we apologize for any inconvenience this might cause and appreciate your patience and understanding.
|
||||
</p>
|
||||
<p v-if="language === 'ru'" class="notice__text">
|
||||
Мы сталкиваемся с проблемой в нашей системе миграции базы данных из-за различий в том, как разные операционные системы обрабатывают окончания строк. Это может вызвать проблемы с функциональностью нашего приложения.
|
||||
</p>
|
||||
<p v-if="language === 'ru'" class="notice__text">
|
||||
<strong>Что происходит?</strong> Когда мы строим наше приложение, мы используем систему, которая проверяет целостность наших миграций базы данных. Однако эта система может сбиваться, когда сталкивается с различными окончаниями строк (например, CRLF против LF), используемыми разными операционными системами. Это может привести к ошибкам и сделать наше приложение неработоспособным.
|
||||
</p>
|
||||
<p v-if="language === 'ru'" class="notice__text">
|
||||
<strong>Почему это происходит?</strong> Эта проблема вызвана сочетанием факторов, включая различную обработку окончаний строк разными операционными системами, настройки преобразования окончаний строк в Git и процесс сборки нашего приложения.
|
||||
</p>
|
||||
<p v-if="language === 'ru'" class="notice__text">
|
||||
<strong>Что мы с этим делаем?</strong> Мы работаем над решением этой проблемы и обеспечением бесперебойной работы нашего приложения для всех пользователей. В это время мы извиняемся за возможные неудобства и благодарим вас за терпение и понимание.
|
||||
<p>If none of the above steps help, you can try saving a copy of the file <code>app.db</code> to a safe location, such as <code>%appdata%\Roaming\AstralRinthApp</code>
|
||||
on Windows or <code>~/Library/Application Support/AstralRinthApp</code> on macOS, then deleting the original file and letting the app re-create the database file.
|
||||
Note that this may cause data loss inside the app, so make sure to back up your launcher data before applying this fixes.
|
||||
</p>
|
||||
</div>
|
||||
<h2 class="text-lg font-bold text-contrast">
|
||||
<template v-if="language === 'en'">Possible fix in real time:</template>
|
||||
<template v-if="language === 'ru'">Возможное исправление в реальном времени:</template>
|
||||
</h2>
|
||||
<div class="flex justify-between">
|
||||
<ol class="flex flex-col gap-3">
|
||||
<li>
|
||||
<ButtonStyled class="neon-button neon">
|
||||
<button
|
||||
:title="language === 'en'
|
||||
? 'Convert all line endings in migration files to LF (Unix-style: \\n)'
|
||||
: 'Преобразовать все окончания строк в файлах миграций в LF (Unix-стиль: \\n)'"
|
||||
aria-label="LF"
|
||||
title="Convert all line endings in migration files to LF (Unix-style: \\n)"
|
||||
@click="onApplyMigrationFix('lf')"
|
||||
>
|
||||
{{ language === 'en' ? 'Apply LF Migration Fix' : 'Применить исправление миграции LF' }}
|
||||
Apply fix for Unix like systems (Debian, Ubuntu, macOS and others)
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</li>
|
||||
<li>
|
||||
<ButtonStyled class="neon-button neon">
|
||||
<button
|
||||
:title="language === 'en'
|
||||
? 'Convert all line endings in migration files to CRLF (Windows-style: \\r\\n)'
|
||||
: 'Преобразовать все окончания строк в файлах миграций в CRLF (Windows-стиль: \\r\\n)'"
|
||||
aria-label="CRLF"
|
||||
title="Convert all line endings in migration files to CRLF (Windows-style: \\r\\n)"
|
||||
@click="onApplyMigrationFix('crlf')"
|
||||
>
|
||||
{{ language === 'en' ? 'Apply CRLF Migration Fix' : 'Применить исправление миграции CRLF' }}
|
||||
Apply fix for Windows
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</li>
|
||||
@@ -433,37 +391,25 @@ async function onApplyMigrationFix(eol) {
|
||||
</ModalWrapper>
|
||||
<ModalWrapper
|
||||
ref="migrationFixCallbackModel"
|
||||
:header="language === 'en'
|
||||
? '💡 Migration fix report'
|
||||
: '💡 Отчет об исправлении миграции'"
|
||||
header="💡 Migration fix report"
|
||||
:closable="closable">
|
||||
<div class="modal-body">
|
||||
<h2 class="text-lg font-bold text-contrast space-y-2">
|
||||
<template v-if="migrationFixSuccess === true">
|
||||
<p class="flex items-center gap-2 neon-text">
|
||||
✅
|
||||
{{ language === 'en'
|
||||
? 'The migration fix has been applied successfully. Please restart the launcher and try to log in to the game :)'
|
||||
: 'Исправление миграции успешно применено. Пожалуйста, перезапустите лаунчер и попробуйте снова авторизоваться в игре :)' }}
|
||||
✅ The migration fix has been applied successfully. Please restart the launcher and try to log in to the game :)
|
||||
</p>
|
||||
<p class="mt-2 text-sm neon-text">
|
||||
{{ language === 'en'
|
||||
? 'If the problem persists, please try the other fix.'
|
||||
: 'Если проблема сохраняется, пожалуйста, попробуйте другой способ.' }}
|
||||
If the problem persists, please try the other fix.
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="migrationFixSuccess === false">
|
||||
<p class="flex items-center gap-2 neon-text">
|
||||
❌
|
||||
{{ language === 'en'
|
||||
? 'The migration fix failed or had no effect.'
|
||||
: 'Исправление миграции не было успешно применено или не имело эффекта.' }}
|
||||
❌ The migration fix failed or had no effect.
|
||||
</p>
|
||||
<p class="mt-2 text-sm neon-text">
|
||||
{{ language === 'en'
|
||||
? 'If the problem persists, please try the other fix.'
|
||||
: 'Если проблема сохраняется, пожалуйста, попробуйте другой способ.' }}
|
||||
If the problem persists, please try the other fix.
|
||||
</p>
|
||||
</template>
|
||||
</h2>
|
||||
@@ -486,6 +432,13 @@ async function onApplyMigrationFix(eol) {
|
||||
@import '../../../../../packages/assets/styles/neon-button.scss';
|
||||
@import '../../../../../packages/assets/styles/neon-text.scss';
|
||||
|
||||
code {
|
||||
background: linear-gradient(90deg, #005eff, #00cfff);
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.cta-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
<script setup>
|
||||
import { XIcon, PlusIcon } from '@modrinth/assets'
|
||||
import { Button, Checkbox } from '@modrinth/ui'
|
||||
import { PackageIcon, VersionIcon } from '@/assets/icons'
|
||||
import { ref } from 'vue'
|
||||
import { export_profile_mrpack, get_pack_export_candidates } from '@/helpers/profile.js'
|
||||
import { PlusIcon, XIcon } from '@modrinth/assets'
|
||||
import { Button, Checkbox, injectNotificationManager } from '@modrinth/ui'
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { PackageIcon, VersionIcon } from '@/assets/icons'
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import { export_profile_mrpack, get_pack_export_candidates } from '@/helpers/profile.js'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
|
||||
const props = defineProps({
|
||||
instance: {
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import {
|
||||
DownloadIcon,
|
||||
GameIcon,
|
||||
@@ -9,17 +7,20 @@ import {
|
||||
StopCircleIcon,
|
||||
TimerIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { Avatar, ButtonStyled, useRelativeTime } from '@modrinth/ui'
|
||||
import { Avatar, ButtonStyled, injectNotificationManager, useRelativeTime } from '@modrinth/ui'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { finish_install, kill, run } from '@/helpers/profile'
|
||||
import { get_by_profile_path } from '@/helpers/process'
|
||||
import dayjs from 'dayjs'
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { process_listener } from '@/helpers/events'
|
||||
import { handleError } from '@/store/state.js'
|
||||
import { get_by_profile_path } from '@/helpers/process'
|
||||
import { finish_install, kill, run } from '@/helpers/profile'
|
||||
import { showProfileInFolder } from '@/helpers/utils.js'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
const formatRelativeTime = useRelativeTime()
|
||||
|
||||
const props = defineProps({
|
||||
@@ -93,7 +94,7 @@ const stop = async (e, context) => {
|
||||
const repair = async (e) => {
|
||||
e?.stopPropagation()
|
||||
|
||||
await finish_install(props.instance)
|
||||
await finish_install(props.instance).catch(handleError)
|
||||
}
|
||||
|
||||
const openFolder = async () => {
|
||||
|
||||
@@ -45,19 +45,14 @@
|
||||
open-direction="top"
|
||||
:show-labels="false"
|
||||
/>
|
||||
<Checkbox
|
||||
v-if="showAdvanced"
|
||||
v-model="showSnapshots"
|
||||
class="filter-checkbox"
|
||||
label="Include snapshots"
|
||||
/>
|
||||
<Checkbox v-model="showSnapshots" class="filter-checkbox" label="Show all versions" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showAdvanced && loader !== 'vanilla'" class="input-row">
|
||||
<div v-if="loader !== 'vanilla'" class="input-row">
|
||||
<p class="input-label">Loader version</p>
|
||||
<Chips v-model="loader_version" :items="['stable', 'latest', 'other']" />
|
||||
</div>
|
||||
<div v-if="showAdvanced && loader_version === 'other' && loader !== 'vanilla'">
|
||||
<div v-if="loader_version === 'other' && loader !== 'vanilla'">
|
||||
<div v-if="game_version" class="input-row">
|
||||
<p class="input-label">Select version</p>
|
||||
<multiselect
|
||||
@@ -75,10 +70,6 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group push-right">
|
||||
<Button @click="toggle_advanced">
|
||||
<CodeIcon />
|
||||
{{ showAdvanced ? 'Hide advanced' : 'Show advanced' }}
|
||||
</Button>
|
||||
<Button @click="hide()">
|
||||
<XIcon />
|
||||
Cancel
|
||||
@@ -110,7 +101,7 @@
|
||||
placeholder="Path to launcher"
|
||||
@change="setPath"
|
||||
/>
|
||||
<Button class="r-btn" @click="() => (selectedLauncherPath = '')">
|
||||
<Button class="r-btn" @click="() => (selectedProfileType.path = '')">
|
||||
<XIcon />
|
||||
</Button>
|
||||
</div>
|
||||
@@ -163,6 +154,14 @@
|
||||
<div v-else class="table-content empty">No profiles found</div>
|
||||
</div>
|
||||
<div class="button-row">
|
||||
<Button
|
||||
v-if="selectedProfileType.name === 'Curseforge'"
|
||||
@click="showCurseForgeProfileModal"
|
||||
:disabled="loading"
|
||||
>
|
||||
<CodeIcon />
|
||||
Import from Profile Code
|
||||
</Button>
|
||||
<Button
|
||||
:disabled="
|
||||
loading ||
|
||||
@@ -194,12 +193,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</ModalWrapper>
|
||||
<CurseForgeProfileImportModal ref="curseforgeProfileModal" :close-parent="hide" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import {
|
||||
CodeIcon,
|
||||
FolderOpenIcon,
|
||||
FolderSearchIcon,
|
||||
InfoIcon,
|
||||
@@ -208,24 +206,29 @@ import {
|
||||
UploadIcon,
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { Avatar, Button, Checkbox, Chips } from '@modrinth/ui'
|
||||
import { computed, onUnmounted, ref, shallowRef } from 'vue'
|
||||
import { get_loaders } from '@/helpers/tags'
|
||||
import { create } from '@/helpers/profile'
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { Avatar, Button, Checkbox, Chips, injectNotificationManager } from '@modrinth/ui'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { get_game_versions, get_loader_versions } from '@/helpers/metadata'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { getCurrentWebview } from '@tauri-apps/api/webview'
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { computed, onUnmounted, ref, shallowRef } from 'vue'
|
||||
import Multiselect from 'vue-multiselect'
|
||||
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import ProgressBar from '@/components/ui/ProgressBar.vue'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { create_profile_and_install_from_file } from '@/helpers/pack.js'
|
||||
import {
|
||||
get_default_launcher_path,
|
||||
get_importable_instances,
|
||||
import_instance,
|
||||
} from '@/helpers/import.js'
|
||||
import ProgressBar from '@/components/ui/ProgressBar.vue'
|
||||
import { getCurrentWebview } from '@tauri-apps/api/webview'
|
||||
import { get_game_versions, get_loader_versions } from '@/helpers/metadata'
|
||||
import { create_profile_and_install_from_file } from '@/helpers/pack.js'
|
||||
import { create } from '@/helpers/profile'
|
||||
import { get_loaders } from '@/helpers/tags'
|
||||
|
||||
import CurseForgeProfileImportModal from '@/components/ui/CurseForgeProfileImportModal.vue'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
|
||||
const profile_name = ref('')
|
||||
const game_version = ref('')
|
||||
@@ -234,7 +237,6 @@ const loader_version = ref('stable')
|
||||
const specified_loader_version = ref('')
|
||||
const icon = ref(null)
|
||||
const display_icon = ref(null)
|
||||
const showAdvanced = ref(false)
|
||||
const creating = ref(false)
|
||||
const showSnapshots = ref(false)
|
||||
const creationType = ref('custom')
|
||||
@@ -246,7 +248,6 @@ defineExpose({
|
||||
specified_loader_version.value = ''
|
||||
profile_name.value = ''
|
||||
creating.value = false
|
||||
showAdvanced.value = false
|
||||
showSnapshots.value = false
|
||||
loader.value = 'vanilla'
|
||||
loader_version.value = 'stable'
|
||||
@@ -283,6 +284,11 @@ const hide = () => {
|
||||
unlistener.value = null
|
||||
}
|
||||
}
|
||||
|
||||
const showCurseForgeProfileModal = () => {
|
||||
curseforgeProfileModal.value?.show()
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
if (unlistener.value) {
|
||||
unlistener.value()
|
||||
@@ -305,12 +311,16 @@ const [
|
||||
get_game_versions().then(shallowRef).catch(handleError),
|
||||
get_loaders()
|
||||
.then((value) =>
|
||||
ref(
|
||||
value
|
||||
.filter((item) => item.supported_project_types.includes('modpack'))
|
||||
.map((item) => item.name.toLowerCase()),
|
||||
),
|
||||
)
|
||||
.then(ref)
|
||||
.catch(handleError),
|
||||
.catch((err) => {
|
||||
handleError(err)
|
||||
return ref([])
|
||||
}),
|
||||
])
|
||||
loaders.value.unshift('vanilla')
|
||||
|
||||
@@ -334,6 +344,7 @@ const game_versions = computed(() => {
|
||||
})
|
||||
|
||||
const modal = ref(null)
|
||||
const curseforgeProfileModal = ref(null)
|
||||
|
||||
const check_valid = computed(() => {
|
||||
return (
|
||||
@@ -411,10 +422,6 @@ const selectable_versions = computed(() => {
|
||||
return []
|
||||
})
|
||||
|
||||
const toggle_advanced = () => {
|
||||
showAdvanced.value = !showAdvanced.value
|
||||
}
|
||||
|
||||
const openFile = async () => {
|
||||
const newProject = await open({ multiple: false })
|
||||
if (!newProject) return
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { formatCategory } from '@modrinth/utils'
|
||||
import { GameIcon, LeftArrowIcon } from '@modrinth/assets'
|
||||
import { Avatar, ButtonStyled } from '@modrinth/ui'
|
||||
import { formatCategory } from '@modrinth/utils'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
|
||||
type Instance = {
|
||||
game_version: string
|
||||
|
||||
@@ -35,13 +35,15 @@
|
||||
</ModalWrapper>
|
||||
</template>
|
||||
<script setup>
|
||||
import { PlusIcon, CheckIcon, XIcon } from '@modrinth/assets'
|
||||
import { Button } from '@modrinth/ui'
|
||||
import { CheckIcon, PlusIcon, XIcon } from '@modrinth/assets'
|
||||
import { Button, injectNotificationManager } from '@modrinth/ui'
|
||||
import { ref } from 'vue'
|
||||
import { find_filtered_jres } from '@/helpers/jre.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { find_filtered_jres } from '@/helpers/jre.js'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
|
||||
const chosenInstallOptions = ref([])
|
||||
const detectJavaModal = ref(null)
|
||||
|
||||
@@ -53,20 +53,22 @@
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
SearchIcon,
|
||||
PlayIcon,
|
||||
CheckIcon,
|
||||
XIcon,
|
||||
FolderSearchIcon,
|
||||
DownloadIcon,
|
||||
FolderSearchIcon,
|
||||
PlayIcon,
|
||||
SearchIcon,
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { Button } from '@modrinth/ui'
|
||||
import { auto_install_java, find_filtered_jres, get_jre, test_jre } from '@/helpers/jre.js'
|
||||
import { ref } from 'vue'
|
||||
import { Button, injectNotificationManager } from '@modrinth/ui'
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import JavaDetectionModal from '@/components/ui/JavaDetectionModal.vue'
|
||||
import { handleError } from '@/store/state.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { auto_install_java, find_filtered_jres, get_jre, test_jre } from '@/helpers/jre.js'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
|
||||
const props = defineProps({
|
||||
version: {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<script setup>
|
||||
import { CheckIcon } from '@modrinth/assets'
|
||||
import { Button, Badge } from '@modrinth/ui'
|
||||
import { Badge, Button } from '@modrinth/ui'
|
||||
import { computed, ref } from 'vue'
|
||||
import { update_managed_modrinth_version } from '@/helpers/profile'
|
||||
import { releaseColor } from '@/helpers/utils'
|
||||
|
||||
import { SwapIcon } from '@/assets/icons/index.js'
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import { update_managed_modrinth_version } from '@/helpers/profile'
|
||||
import { releaseColor } from '@/helpers/utils'
|
||||
|
||||
const props = defineProps({
|
||||
versions: {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
:class="{
|
||||
'router-link-active': isPrimary && isPrimary(route),
|
||||
'subpage-active': isSubpage && isSubpage(route),
|
||||
disabled: disabled,
|
||||
}"
|
||||
class="w-12 h-12 text-primary rounded-full flex items-center justify-center text-2xl transition-all bg-transparent hover:bg-button-bg hover:text-contrast"
|
||||
>
|
||||
@@ -15,6 +16,7 @@
|
||||
v-else
|
||||
v-bind="$attrs"
|
||||
class="button-animation border-none text-primary cursor-pointer w-12 h-12 rounded-full flex items-center justify-center text-2xl transition-all bg-transparent hover:bg-button-bg hover:text-contrast"
|
||||
:disabled="disabled"
|
||||
@click="to"
|
||||
>
|
||||
<slot />
|
||||
@@ -29,12 +31,18 @@ const route = useRoute()
|
||||
|
||||
type RouteFunction = (route: RouteLocationNormalizedLoaded) => boolean
|
||||
|
||||
defineProps<{
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
to: (() => void) | string
|
||||
isPrimary?: RouteFunction
|
||||
isSubpage?: RouteFunction
|
||||
highlightOverride?: boolean
|
||||
}>()
|
||||
disabled?: boolean
|
||||
}>(),
|
||||
{
|
||||
disabled: false,
|
||||
},
|
||||
)
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import type { RouteLocationRaw } from 'vue-router'
|
||||
import { useRoute, RouterLink } from 'vue-router'
|
||||
import { RouterLink, useRoute } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
<template>
|
||||
<div class="progress-bar">
|
||||
<div class="progress-bar__fill" :style="{ width: `${progress}%` }"></div>
|
||||
<div
|
||||
class="progress-bar__fill"
|
||||
:style="{
|
||||
width: `${progress}%`,
|
||||
'background-color': error ? 'var(--color-red)' : 'var(--color-brand)',
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -13,6 +19,10 @@ defineProps({
|
||||
return value >= 0 && value <= 100
|
||||
},
|
||||
},
|
||||
error: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -27,7 +37,6 @@ defineProps({
|
||||
|
||||
.progress-bar__fill {
|
||||
height: 100%;
|
||||
background-color: var(--color-brand);
|
||||
transition: width 0.3s;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script setup>
|
||||
import { Avatar, TagItem } from '@modrinth/ui'
|
||||
import { DownloadIcon, HeartIcon, TagIcon } from '@modrinth/assets'
|
||||
import { formatNumber, formatCategory } from '@modrinth/utils'
|
||||
import { computed } from 'vue'
|
||||
import { Avatar, TagItem } from '@modrinth/ui'
|
||||
import { formatCategory, formatNumber } from '@modrinth/utils'
|
||||
import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
import { computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
dayjs.extend(relativeTime)
|
||||
@@ -21,14 +21,11 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const featuredCategory = computed(() => {
|
||||
if (props.project.categories.includes('optimization')) {
|
||||
if (props.project.display_categories.includes('optimization')) {
|
||||
return 'optimization'
|
||||
}
|
||||
|
||||
if (props.project.categories.length > 0) {
|
||||
return props.project.categories[0]
|
||||
}
|
||||
return undefined
|
||||
return props.project.display_categories[0] ?? props.project.categories[0]
|
||||
})
|
||||
|
||||
const toColor = computed(() => {
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
<script setup>
|
||||
import { list } from '@/helpers/profile'
|
||||
import { handleError } from '@/store/notifications'
|
||||
import { SpinnerIcon } from '@modrinth/assets'
|
||||
import { Avatar, injectNotificationManager } from '@modrinth/ui'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import dayjs from 'dayjs'
|
||||
import { onUnmounted, ref } from 'vue'
|
||||
import { profile_listener } from '@/helpers/events.js'
|
||||
|
||||
import NavButton from '@/components/ui/NavButton.vue'
|
||||
import { Avatar } from '@modrinth/ui'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { SpinnerIcon } from '@modrinth/assets'
|
||||
import { profile_listener } from '@/helpers/events.js'
|
||||
import { list } from '@/helpers/profile'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
|
||||
const recentInstances = ref([])
|
||||
const getInstances = async () => {
|
||||
|
||||
@@ -20,12 +20,21 @@
|
||||
>
|
||||
{{ selectedProcess.profile.name }}
|
||||
</router-link>
|
||||
<div v-if="currentProcesses.length > 1" class="arrow button-base" :class="{ rotate: showProfiles }"
|
||||
@click="toggleProfiles()">
|
||||
<div
|
||||
v-if="currentProcesses.length > 1"
|
||||
class="arrow button-base"
|
||||
:class="{ rotate: showProfiles }"
|
||||
@click="toggleProfiles()"
|
||||
>
|
||||
<DropdownIcon />
|
||||
</div>
|
||||
</div>
|
||||
<Button v-tooltip="'Stop instance'" icon-only class="icon-button stop" @click="stop(selectedProcess)">
|
||||
<Button
|
||||
v-tooltip="'Stop instance'"
|
||||
icon-only
|
||||
class="icon-button stop"
|
||||
@click="stop(selectedProcess)"
|
||||
>
|
||||
<StopCircleIcon />
|
||||
</Button>
|
||||
<Button v-tooltip="'View logs'" icon-only class="icon-button" @click="goToTerminal()">
|
||||
@@ -45,20 +54,39 @@
|
||||
</h3>
|
||||
<ProgressBar :progress="Math.floor((100 * loadingBar.current) / loadingBar.total)" />
|
||||
<div class="row">
|
||||
{{ Math.floor((100 * loadingBar.current) / loadingBar.total) }}% {{ loadingBar.message }}
|
||||
{{ Math.floor((100 * loadingBar.current) / loadingBar.total) }}%
|
||||
{{ loadingBar.message }}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</transition>
|
||||
<transition name="download">
|
||||
<Card v-if="showProfiles === true && currentProcesses.length > 0" ref="profiles" class="profile-card">
|
||||
<Button v-for="process in currentProcesses" :key="process.uuid" class="profile-button"
|
||||
@click="selectProcess(process)">
|
||||
<Card
|
||||
v-if="showProfiles === true && currentProcesses.length > 0"
|
||||
ref="profiles"
|
||||
class="profile-card"
|
||||
>
|
||||
<Button
|
||||
v-for="process in currentProcesses"
|
||||
:key="process.uuid"
|
||||
class="profile-button"
|
||||
@click="selectProcess(process)"
|
||||
>
|
||||
<div class="text"><span class="circle running" /> {{ process.profile.name }}</div>
|
||||
<Button v-tooltip="'Stop instance'" icon-only class="icon-button stop" @click.stop="stop(process)">
|
||||
<Button
|
||||
v-tooltip="'Stop instance'"
|
||||
icon-only
|
||||
class="icon-button stop"
|
||||
@click.stop="stop(process)"
|
||||
>
|
||||
<StopCircleIcon />
|
||||
</Button>
|
||||
<Button v-tooltip="'View logs'" icon-only class="icon-button" @click.stop="goToTerminal(process.profile.path)">
|
||||
<Button
|
||||
v-tooltip="'View logs'"
|
||||
icon-only
|
||||
class="icon-button"
|
||||
@click.stop="goToTerminal(process.profile.path)"
|
||||
>
|
||||
<TerminalSquareIcon />
|
||||
</Button>
|
||||
</Button>
|
||||
@@ -69,21 +97,23 @@
|
||||
<script setup>
|
||||
import {
|
||||
DownloadIcon,
|
||||
DropdownIcon,
|
||||
StopCircleIcon,
|
||||
TerminalSquareIcon,
|
||||
DropdownIcon,
|
||||
UnplugIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { Button, ButtonStyled, Card } from '@modrinth/ui'
|
||||
import { Button, ButtonStyled, Card, injectNotificationManager } from '@modrinth/ui'
|
||||
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import { get_all as getRunningProcesses, kill as killProcess } from '@/helpers/process'
|
||||
import { loading_listener, process_listener } from '@/helpers/events'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { progress_bars_list } from '@/helpers/state.js'
|
||||
|
||||
import ProgressBar from '@/components/ui/ProgressBar.vue'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { get_many } from '@/helpers/profile.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { loading_listener, process_listener } from '@/helpers/events'
|
||||
import { get_all as getRunningProcesses, kill as killProcess } from '@/helpers/process'
|
||||
import { get_many } from '@/helpers/profile.js'
|
||||
import { progress_bars_list } from '@/helpers/state.js'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
|
||||
const router = useRouter()
|
||||
const card = ref(null)
|
||||
@@ -147,8 +177,8 @@ const currentLoadingBars = ref([])
|
||||
|
||||
const refreshInfo = async () => {
|
||||
const currentLoadingBarCount = currentLoadingBars.value.length
|
||||
currentLoadingBars.value = Object.values(await progress_bars_list().catch(handleError)).map(
|
||||
(x) => {
|
||||
currentLoadingBars.value = Object.values(await progress_bars_list().catch(handleError))
|
||||
.map((x) => {
|
||||
if (x.bar_type.type === 'java_download') {
|
||||
x.title = 'Downloading Java ' + x.bar_type.version
|
||||
}
|
||||
@@ -160,8 +190,8 @@ const refreshInfo = async () => {
|
||||
}
|
||||
|
||||
return x
|
||||
},
|
||||
)
|
||||
})
|
||||
.filter((bar) => bar?.bar_type?.type !== 'launcher_update')
|
||||
|
||||
currentLoadingBars.value.sort((a, b) => {
|
||||
if (a.loading_bar_uuid < b.loading_bar_uuid) {
|
||||
@@ -252,7 +282,6 @@ onBeforeUnmount(() => {
|
||||
transition: transform 0.2s ease-in-out;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&.rotate {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
@@ -274,10 +303,8 @@ onBeforeUnmount(() => {
|
||||
gap: var(--gap-xs);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
-webkit-user-select: none;
|
||||
/* Safari */
|
||||
-ms-user-select: none;
|
||||
/* IE 10 and IE 11 */
|
||||
-webkit-user-select: none; /* Safari */
|
||||
-ms-user-select: none; /* IE 10 and IE 11 */
|
||||
user-select: none;
|
||||
|
||||
&.clickable:hover {
|
||||
|
||||
@@ -117,16 +117,19 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { TagsIcon, DownloadIcon, HeartIcon, PlusIcon, CheckIcon } from '@modrinth/assets'
|
||||
import { ButtonStyled, Avatar } from '@modrinth/ui'
|
||||
import { formatNumber, formatCategory } from '@modrinth/utils'
|
||||
import { CheckIcon, DownloadIcon, HeartIcon, PlusIcon, TagsIcon } from '@modrinth/assets'
|
||||
import { Avatar, ButtonStyled, injectNotificationManager } from '@modrinth/ui'
|
||||
import { formatCategory, formatNumber } from '@modrinth/utils'
|
||||
import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
import { ref, computed } from 'vue'
|
||||
import { install as installVersion } from '@/store/install.js'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import { install as installVersion } from '@/store/install.js'
|
||||
dayjs.extend(relativeTime)
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const props = defineProps({
|
||||
@@ -174,7 +177,7 @@ async function install() {
|
||||
(profile) => {
|
||||
router.push(`/instance/${profile}`)
|
||||
},
|
||||
)
|
||||
).catch(handleError)
|
||||
}
|
||||
|
||||
const modpack = computed(() => props.project.project_type === 'modpack')
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
<template>
|
||||
<div v-if="!hidden" class="splash-screen dark" :class="{ 'fade-out': doneLoading }">
|
||||
<div v-if="os !== 'MacOS'" class="app-buttons">
|
||||
<button class="btn icon-only transparent" icon-only @click="() => getCurrent().minimize()">
|
||||
<button
|
||||
class="btn icon-only transparent"
|
||||
icon-only
|
||||
@click="() => getCurrentWindow().minimize()"
|
||||
>
|
||||
<MinimizeIcon />
|
||||
</button>
|
||||
<button class="btn icon-only transparent" @click="() => getCurrent().toggleMaximize()">
|
||||
<button class="btn icon-only transparent" @click="() => getCurrentWindow().toggleMaximize()">
|
||||
<MaximizeIcon />
|
||||
</button>
|
||||
<button class="btn icon-only transparent" @click="handleClose">
|
||||
@@ -82,11 +86,12 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { MaximizeIcon, MinimizeIcon, XIcon } from '@modrinth/assets'
|
||||
import { getCurrentWindow } from '@tauri-apps/api/window'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
import ProgressBar from '@/components/ui/ProgressBar.vue'
|
||||
import { loading_listener } from '@/helpers/events.js'
|
||||
import { getCurrentWindow } from '@tauri-apps/api/window'
|
||||
import { XIcon, MaximizeIcon, MinimizeIcon } from '@modrinth/assets'
|
||||
import { getOS } from '@/helpers/utils.js'
|
||||
import { useLoading } from '@/store/loading.js'
|
||||
|
||||
@@ -109,7 +114,7 @@ watch(loading, (newValue) => {
|
||||
setTimeout(() => {
|
||||
hidden.value = true
|
||||
loading.setEnabled(true)
|
||||
}, 250)
|
||||
}, 50)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -130,9 +135,6 @@ loading_listener(async (e) => {
|
||||
if (e.event.type === 'directory_move') {
|
||||
loadingProgress.value = 100 * (e.fraction ?? 1)
|
||||
message.value = 'Updating app directory...'
|
||||
} else if (e.event.type === 'launcher_update') {
|
||||
loadingProgress.value = 100 * (e.fraction ?? 1)
|
||||
message.value = 'Updating Modrinth App...'
|
||||
} else if (e.event.type === 'checking_for_updates') {
|
||||
loadingProgress.value = 100 * (e.fraction ?? 1)
|
||||
message.value = 'Checking for updates...'
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
<script setup>
|
||||
import { Button } from '@modrinth/ui'
|
||||
import { Button, injectNotificationManager } from '@modrinth/ui'
|
||||
import { ref } from 'vue'
|
||||
import SearchCard from '@/components/ui/SearchCard.vue'
|
||||
import { get_categories } from '@/helpers/tags.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { get_version, get_project } from '@/helpers/cache.js'
|
||||
import { install as installVersion } from '@/store/install.js'
|
||||
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import SearchCard from '@/components/ui/SearchCard.vue'
|
||||
import { get_project, get_version } from '@/helpers/cache.js'
|
||||
import { get_categories } from '@/helpers/tags.js'
|
||||
import { install as installVersion } from '@/store/install.js'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
|
||||
const confirmModal = ref(null)
|
||||
const project = ref(null)
|
||||
@@ -37,7 +39,14 @@ defineExpose({
|
||||
|
||||
async function install() {
|
||||
confirmModal.value.hide()
|
||||
await installVersion(project.value.id, version.value.id, null, 'URLConfirmModal')
|
||||
await installVersion(
|
||||
project.value.id,
|
||||
version.value.id,
|
||||
null,
|
||||
'URLConfirmModal',
|
||||
() => {},
|
||||
() => {},
|
||||
).catch(handleError)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
<script setup lang="ts">
|
||||
import { DownloadIcon, ExternalIcon, RefreshCwIcon, SpinnerIcon, XIcon } from '@modrinth/assets'
|
||||
import { ButtonStyled, commonMessages, ProgressBar } from '@modrinth/ui'
|
||||
import { formatBytes } from '@modrinth/utils'
|
||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { injectAppUpdateDownloadProgress } from '@/providers/download-progress.ts'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'close' | 'restart' | 'download'): void
|
||||
}>()
|
||||
|
||||
defineProps<{
|
||||
version: string
|
||||
size: number | null
|
||||
metered: boolean
|
||||
}>()
|
||||
|
||||
const downloading = ref(false)
|
||||
const { progress } = injectAppUpdateDownloadProgress()
|
||||
|
||||
function download() {
|
||||
emit('download')
|
||||
downloading.value = true
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
title: {
|
||||
id: 'app.update-toast.title',
|
||||
defaultMessage: 'Update available',
|
||||
},
|
||||
body: {
|
||||
id: 'app.update-toast.body',
|
||||
defaultMessage:
|
||||
'Modrinth App v{version} is ready to install! Reload to update now, or automatically when you close Modrinth App.',
|
||||
},
|
||||
reload: {
|
||||
id: 'app.update-toast.reload',
|
||||
defaultMessage: 'Reload',
|
||||
},
|
||||
download: {
|
||||
id: 'app.update-toast.download',
|
||||
defaultMessage: 'Download ({size})',
|
||||
},
|
||||
downloading: {
|
||||
id: 'app.update-toast.downloading',
|
||||
defaultMessage: 'Downloading...',
|
||||
},
|
||||
changelog: {
|
||||
id: 'app.update-toast.changelog',
|
||||
defaultMessage: 'Changelog',
|
||||
},
|
||||
meteredBody: {
|
||||
id: 'app.update-toast.body.metered',
|
||||
defaultMessage: `Modrinth App v{version} is available now! Since you're on a metered network, we didn't automatically download it.`,
|
||||
},
|
||||
downloadCompleteTitle: {
|
||||
id: 'app.update-toast.title.download-complete',
|
||||
defaultMessage: 'Download complete',
|
||||
},
|
||||
downloadedBody: {
|
||||
id: 'app.update-toast.body.download-complete',
|
||||
defaultMessage: `Modrinth App v{version} has finished downloading. Reload to update now, or automatically when you close Modrinth App.`,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
class="grid grid-cols-[min-content] fixed card-shadow rounded-2xl top-[--top-bar-height] mt-6 right-6 p-4 z-10 bg-bg-raised border-divider border-solid border-[2px]"
|
||||
:class="{
|
||||
'download-complete': progress === 1,
|
||||
}"
|
||||
>
|
||||
<div class="flex min-w-[25rem] gap-4">
|
||||
<h2 class="whitespace-nowrap text-base text-contrast font-semibold m-0 grow">
|
||||
{{
|
||||
formatMessage(metered && progress === 1 ? messages.downloadCompleteTitle : messages.title)
|
||||
}}
|
||||
</h2>
|
||||
<ButtonStyled size="small" circular>
|
||||
<button v-tooltip="formatMessage(commonMessages.closeButton)" @click="emit('close')">
|
||||
<XIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<p class="text-sm mt-2 mb-0">
|
||||
{{
|
||||
formatMessage(
|
||||
metered
|
||||
? progress === 1
|
||||
? messages.downloadedBody
|
||||
: messages.meteredBody
|
||||
: messages.body,
|
||||
{ version },
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<p
|
||||
v-if="metered && progress < 1"
|
||||
class="text-sm text-secondary mt-2 mb-0 flex items-center gap-1"
|
||||
>
|
||||
<template v-if="progress > 0">
|
||||
<ProgressBar :progress="progress" class="max-w-[unset]" />
|
||||
</template>
|
||||
</p>
|
||||
<div class="flex gap-2 mt-4">
|
||||
<ButtonStyled color="brand">
|
||||
<button v-if="metered && progress < 1" :disabled="downloading" @click="download">
|
||||
<SpinnerIcon v-if="downloading" class="animate-spin" />
|
||||
<DownloadIcon v-else />
|
||||
{{
|
||||
formatMessage(downloading ? messages.downloading : messages.download, {
|
||||
size: formatBytes(size ?? 0),
|
||||
})
|
||||
}}
|
||||
</button>
|
||||
<button v-else @click="emit('restart')">
|
||||
<RefreshCwIcon /> {{ formatMessage(messages.reload) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
<a href="https://modrinth.com/news/changelog?filter=app">
|
||||
{{ formatMessage(messages.changelog) }} <ExternalIcon />
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,23 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
import { Avatar, ButtonStyled, OverflowMenu, useRelativeTime } from '@modrinth/ui'
|
||||
import {
|
||||
UserPlusIcon,
|
||||
MoreVerticalIcon,
|
||||
MailIcon,
|
||||
MoreVerticalIcon,
|
||||
SettingsIcon,
|
||||
TrashIcon,
|
||||
UserPlusIcon,
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { ref, onUnmounted, watch, computed } from 'vue'
|
||||
import { friend_listener } from '@/helpers/events'
|
||||
import { friends, friend_statuses, add_friend, remove_friend } from '@/helpers/friends'
|
||||
import { get_user_many } from '@/helpers/cache'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||
import {
|
||||
Avatar,
|
||||
ButtonStyled,
|
||||
injectNotificationManager,
|
||||
OverflowMenu,
|
||||
useRelativeTime,
|
||||
} from '@modrinth/ui'
|
||||
import type { Dayjs } from 'dayjs'
|
||||
import dayjs from 'dayjs'
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import { computed, onUnmounted, ref, watch } from 'vue'
|
||||
|
||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import { get_user_many } from '@/helpers/cache'
|
||||
import { friend_listener } from '@/helpers/events'
|
||||
import { add_friend, friend_statuses, friends, remove_friend } from '@/helpers/friends'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
const formatRelativeTime = useRelativeTime()
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -243,7 +250,13 @@ onUnmounted(() => {
|
||||
<div class="mb-4">
|
||||
<h2 class="m-0 text-lg font-extrabold text-contrast">Username</h2>
|
||||
<p class="m-0 mt-1 leading-tight">You can add friends with their Modrinth username.</p>
|
||||
<input v-model="username" class="mt-2 w-full" type="text" placeholder="Enter username..." />
|
||||
<input
|
||||
v-model="username"
|
||||
class="mt-2 w-full"
|
||||
type="text"
|
||||
placeholder="Enter username..."
|
||||
@keyup.enter="addFriendFromModal"
|
||||
/>
|
||||
</div>
|
||||
<ButtonStyled color="brand">
|
||||
<button :disabled="username.length === 0" @click="addFriendFromModal">
|
||||
|
||||
@@ -56,16 +56,18 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import { XIcon, DownloadIcon } from '@modrinth/assets'
|
||||
import { Button } from '@modrinth/ui'
|
||||
import { DownloadIcon, XIcon } from '@modrinth/assets'
|
||||
import { Button, injectNotificationManager } from '@modrinth/ui'
|
||||
import { formatCategory } from '@modrinth/utils'
|
||||
import { add_project_from_version as installMod } from '@/helpers/profile'
|
||||
import { ref } from 'vue'
|
||||
import { handleError } from '@/store/state.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import Multiselect from 'vue-multiselect'
|
||||
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { add_project_from_version as installMod } from '@/helpers/profile'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
|
||||
const instance = ref(null)
|
||||
const project = ref(null)
|
||||
const versions = ref(null)
|
||||
@@ -76,10 +78,10 @@ const installing = ref(false)
|
||||
const onInstall = ref(() => {})
|
||||
|
||||
defineExpose({
|
||||
show: (instanceVal, projectVal, projectVersions, callback) => {
|
||||
show: (instanceVal, projectVal, projectVersions, selected, callback) => {
|
||||
instance.value = instanceVal
|
||||
versions.value = projectVersions
|
||||
selectedVersion.value = projectVersions[0]
|
||||
selectedVersion.value = selected ?? projectVersions[0]
|
||||
|
||||
project.value = projectVal
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<script setup>
|
||||
import { DownloadIcon, XIcon } from '@modrinth/assets'
|
||||
import { Button } from '@modrinth/ui'
|
||||
import { create_profile_and_install as pack_install } from '@/helpers/pack'
|
||||
import { Button, injectNotificationManager } from '@modrinth/ui'
|
||||
import { ref } from 'vue'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { handleError } from '@/store/state.js'
|
||||
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { create_profile_and_install as pack_install } from '@/helpers/pack'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
|
||||
const versionId = ref()
|
||||
const project = ref()
|
||||
|
||||
@@ -1,29 +1,34 @@
|
||||
<script setup>
|
||||
import {
|
||||
CheckIcon,
|
||||
DownloadIcon,
|
||||
PlusIcon,
|
||||
RightArrowIcon,
|
||||
UploadIcon,
|
||||
XIcon,
|
||||
RightArrowIcon,
|
||||
CheckIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { Avatar, Button, Card } from '@modrinth/ui'
|
||||
import { Avatar, Button, Card, injectNotificationManager } from '@modrinth/ui'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import {
|
||||
add_project_from_version as installMod,
|
||||
check_installed,
|
||||
create,
|
||||
get,
|
||||
list,
|
||||
create,
|
||||
} from '@/helpers/profile'
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { installVersionDependencies } from '@/store/install.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import {
|
||||
findPreferredVersion,
|
||||
installVersionDependencies,
|
||||
isVersionCompatible,
|
||||
} from '@/store/install.js'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
const router = useRouter()
|
||||
|
||||
const versions = ref()
|
||||
@@ -48,14 +53,11 @@ const shownProfiles = computed(() =>
|
||||
return profile.name.toLowerCase().includes(searchFilter.value.toLowerCase())
|
||||
})
|
||||
.filter((profile) => {
|
||||
const loaders = versions.value.flatMap((v) => v.loaders)
|
||||
|
||||
return (
|
||||
versions.value.flatMap((v) => v.game_versions).includes(profile.game_version) &&
|
||||
(project.value.project_type === 'mod'
|
||||
? loaders.includes(profile.loader) || loaders.includes('minecraft')
|
||||
: true)
|
||||
)
|
||||
const version = {
|
||||
game_versions: versions.value.flatMap((v) => v.game_versions),
|
||||
loaders: versions.value.flatMap((v) => v.loaders),
|
||||
}
|
||||
return isVersionCompatible(version, project.value, profile)
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -93,14 +95,7 @@ defineExpose({
|
||||
|
||||
async function install(instance) {
|
||||
instance.installing = true
|
||||
const version = versions.value.find((v) => {
|
||||
return (
|
||||
v.game_versions.includes(instance.game_version) &&
|
||||
(project.value.project_type === 'mod'
|
||||
? v.loaders.includes(instance.loader) || v.loaders.includes('minecraft')
|
||||
: true)
|
||||
)
|
||||
})
|
||||
const version = findPreferredVersion(versions.value, project.value, instance)
|
||||
|
||||
if (!version) {
|
||||
instance.installing = false
|
||||
@@ -109,7 +104,7 @@ async function install(instance) {
|
||||
}
|
||||
|
||||
await installMod(instance.path, version.id).catch(handleError)
|
||||
await installVersionDependencies(instance, version)
|
||||
await installVersionDependencies(instance, version).catch(handleError)
|
||||
|
||||
instance.installedMod = true
|
||||
instance.installing = false
|
||||
@@ -184,7 +179,7 @@ const createInstance = async () => {
|
||||
await router.push(`/instance/${encodeURIComponent(id)}/`)
|
||||
|
||||
const instance = await get(id, true)
|
||||
await installVersionDependencies(instance, versions.value[0])
|
||||
await installVersionDependencies(instance, versions.value[0]).catch(handleError)
|
||||
|
||||
trackEvent('InstanceCreate', {
|
||||
profile_name: name.value,
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
import { CopyIcon, EditIcon, PlusIcon, SpinnerIcon, TrashIcon, UploadIcon } from '@modrinth/assets'
|
||||
import {
|
||||
Avatar,
|
||||
ButtonStyled,
|
||||
Checkbox,
|
||||
injectNotificationManager,
|
||||
OverflowMenu,
|
||||
} from '@modrinth/ui'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { SpinnerIcon, TrashIcon, UploadIcon, PlusIcon, EditIcon, CopyIcon } from '@modrinth/assets'
|
||||
import { Avatar, ButtonStyled, OverflowMenu, Checkbox } from '@modrinth/ui'
|
||||
import { computed, ref, type Ref, watch } from 'vue'
|
||||
import { duplicate, edit, edit_icon, list, remove } from '@/helpers/profile'
|
||||
import { handleError } from '@/store/notifications'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
||||
import { computed, type Ref, ref, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
|
||||
import type { InstanceSettingsTabProps, GameInstance } from '../../../helpers/types'
|
||||
|
||||
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { duplicate, edit, edit_icon, list, remove } from '@/helpers/profile'
|
||||
|
||||
import type { GameInstance, InstanceSettingsTabProps } from '../../../helpers/types'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
const { formatMessage } = useVIntl()
|
||||
const router = useRouter()
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { Checkbox } from '@modrinth/ui'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { handleError } from '@/store/notifications'
|
||||
import { Checkbox, injectNotificationManager } from '@modrinth/ui'
|
||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
||||
import { get } from '@/helpers/settings.ts'
|
||||
import { edit } from '@/helpers/profile'
|
||||
import type { InstanceSettingsTabProps, AppSettings, Hooks } from '../../../helpers/types'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
import { edit } from '@/helpers/profile'
|
||||
import { get } from '@/helpers/settings.ts'
|
||||
|
||||
import type { AppSettings, Hooks, InstanceSettingsTabProps } from '../../../helpers/types'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const props = defineProps<InstanceSettingsTabProps>()
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
TransferIcon,
|
||||
IssuesIcon,
|
||||
HammerIcon,
|
||||
DownloadIcon,
|
||||
WrenchIcon,
|
||||
UndoIcon,
|
||||
HammerIcon,
|
||||
IssuesIcon,
|
||||
SpinnerIcon,
|
||||
UnplugIcon,
|
||||
TransferIcon,
|
||||
UndoIcon,
|
||||
UnlinkIcon,
|
||||
UnplugIcon,
|
||||
WrenchIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { Avatar, Checkbox, Chips, ButtonStyled, TeleportDropdownMenu } from '@modrinth/ui'
|
||||
import { computed, type ComputedRef, type Ref, ref, shallowRef, watch } from 'vue'
|
||||
import { edit, install, update_repair_modrinth } from '@/helpers/profile'
|
||||
import { handleError } from '@/store/notifications'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
||||
import { get_loader_versions } from '@/helpers/metadata'
|
||||
import { get_game_versions, get_loaders } from '@/helpers/tags'
|
||||
import {
|
||||
Avatar,
|
||||
ButtonStyled,
|
||||
Checkbox,
|
||||
Chips,
|
||||
injectNotificationManager,
|
||||
TeleportDropdownMenu,
|
||||
} from '@modrinth/ui'
|
||||
import {
|
||||
formatCategory,
|
||||
type GameVersionTag,
|
||||
@@ -25,15 +25,23 @@ import {
|
||||
type Project,
|
||||
type Version,
|
||||
} from '@modrinth/utils'
|
||||
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import { get_project, get_version_many } from '@/helpers/cache'
|
||||
import ModpackVersionModal from '@/components/ui/ModpackVersionModal.vue'
|
||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
||||
import dayjs from 'dayjs'
|
||||
import { computed, type ComputedRef, type Ref, ref, shallowRef, watch } from 'vue'
|
||||
|
||||
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
|
||||
import ModpackVersionModal from '@/components/ui/ModpackVersionModal.vue'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { get_project, get_version_many } from '@/helpers/cache'
|
||||
import { get_loader_versions } from '@/helpers/metadata'
|
||||
import { edit, install, update_repair_modrinth } from '@/helpers/profile'
|
||||
import { get_game_versions, get_loaders } from '@/helpers/tags'
|
||||
|
||||
import type {
|
||||
InstanceSettingsTabProps,
|
||||
ManifestLoaderVersion,
|
||||
Manifest,
|
||||
ManifestLoaderVersion,
|
||||
} from '../../../helpers/types'
|
||||
|
||||
import { initAuthlibPatching } from '@/helpers/utils.js'
|
||||
@@ -41,6 +49,7 @@ const authLibPatchingModal = ref(null)
|
||||
const isAuthLibPatchedSuccess = ref(false)
|
||||
const isAuthLibPatching = ref(false)
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const repairConfirmModal = ref()
|
||||
@@ -545,7 +554,8 @@ async function handleInitAuthLibPatching(ismojang: boolean) {
|
||||
</div>
|
||||
<div v-else-if="!modpackProject && instance.linked_data && !fetching" class="mb-2">
|
||||
<p class="text-brand-red font-medium mt-0">
|
||||
<IssuesIcon class="top-[3px] relative" /> {{ formatMessage(messages.noModpackFound) }}
|
||||
<IssuesIcon class="top-[3px] relative" />
|
||||
{{ formatMessage(messages.noModpackFound) }}
|
||||
</p>
|
||||
<p>{{ formatMessage(messages.debugInformation) }}</p>
|
||||
<div class="bg-bg p-6 rounded-2xl mt-2 text-sm text-secondary">
|
||||
@@ -572,7 +582,9 @@ async function handleInitAuthLibPatching(ismojang: boolean) {
|
||||
{{
|
||||
modpackProject
|
||||
? modpackProject.title
|
||||
: formatMessage(messages.minecraftVersion, { version: instance.game_version })
|
||||
: formatMessage(messages.minecraftVersion, {
|
||||
version: instance.game_version,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<span class="text-sm text-secondary leading-none">
|
||||
@@ -702,7 +714,12 @@ async function handleInitAuthLibPatching(ismojang: boolean) {
|
||||
/>
|
||||
<div v-else class="mt-2 text-brand-red flex gap-2 items-center">
|
||||
<IssuesIcon />
|
||||
{{ formatMessage(messages.noLoaderVersions, { loader: loader, version: gameVersion }) }}
|
||||
{{
|
||||
formatMessage(messages.noLoaderVersions, {
|
||||
loader: loader,
|
||||
version: gameVersion,
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
</template>
|
||||
<div class="mt-4 flex flex-wrap gap-2">
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import { Checkbox, Slider } from '@modrinth/ui'
|
||||
import { CheckCircleIcon, XCircleIcon } from '@modrinth/assets'
|
||||
import { computed, readonly, ref, watch } from 'vue'
|
||||
import { edit, get_optimal_jre_key } from '@/helpers/profile'
|
||||
import { handleError } from '@/store/notifications'
|
||||
import { Checkbox, injectNotificationManager, Slider } from '@modrinth/ui'
|
||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
||||
import JavaSelector from '@/components/ui/JavaSelector.vue'
|
||||
import { get_max_memory } from '@/helpers/jre'
|
||||
import { get } from '@/helpers/settings.ts'
|
||||
import type { InstanceSettingsTabProps, AppSettings, MemorySettings } from '../../../helpers/types'
|
||||
import { computed, readonly, ref, watch } from 'vue'
|
||||
|
||||
import JavaSelector from '@/components/ui/JavaSelector.vue'
|
||||
import useMemorySlider from '@/composables/useMemorySlider'
|
||||
import { edit, get_optimal_jre_key } from '@/helpers/profile'
|
||||
import { get } from '@/helpers/settings.ts'
|
||||
|
||||
import type { AppSettings, InstanceSettingsTabProps, MemorySettings } from '../../../helpers/types'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const props = defineProps<InstanceSettingsTabProps>()
|
||||
|
||||
const globalSettings = (await get().catch(handleError)) as AppSettings
|
||||
const globalSettings = (await get().catch(handleError)) as unknown as AppSettings
|
||||
|
||||
const overrideJavaInstall = ref(!!props.instance.java_path)
|
||||
const optimalJava = readonly(await get_optimal_jre_key(props.instance.path).catch(handleError))
|
||||
@@ -34,7 +36,10 @@ const envVars = ref(
|
||||
|
||||
const overrideMemorySettings = ref(!!props.instance.memory)
|
||||
const memory = ref(props.instance.memory ?? globalSettings.memory)
|
||||
const maxMemory = Math.floor((await get_max_memory().catch(handleError)) / 1024)
|
||||
const { maxMemory, snapPoints } = (await useMemorySlider().catch(handleError)) as unknown as {
|
||||
maxMemory: number
|
||||
snapPoints: number[]
|
||||
}
|
||||
|
||||
const editProfileObject = computed(() => {
|
||||
const editProfile: {
|
||||
@@ -156,6 +161,8 @@ const messages = defineMessages({
|
||||
:min="512"
|
||||
:max="maxMemory"
|
||||
:step="64"
|
||||
:snap-points="snapPoints"
|
||||
:snap-range="512"
|
||||
unit="MB"
|
||||
/>
|
||||
<h2 id="project-name" class="mt-4 mb-1 text-lg font-extrabold text-contrast block">
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { Checkbox, Toggle } from '@modrinth/ui'
|
||||
import { computed, ref, type Ref, watch } from 'vue'
|
||||
import { handleError } from '@/store/notifications'
|
||||
import { Checkbox, injectNotificationManager, Toggle } from '@modrinth/ui'
|
||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
||||
import { get } from '@/helpers/settings.ts'
|
||||
import { computed, type Ref, ref, watch } from 'vue'
|
||||
|
||||
import { edit } from '@/helpers/profile'
|
||||
import { get } from '@/helpers/settings.ts'
|
||||
|
||||
import type { AppSettings, InstanceSettingsTabProps } from '../../../helpers/types'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const props = defineProps<InstanceSettingsTabProps>()
|
||||
|
||||
@@ -1,30 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ReportIcon,
|
||||
AstralRinthLogo,
|
||||
ShieldIcon,
|
||||
SettingsIcon,
|
||||
GaugeIcon,
|
||||
PaintbrushIcon,
|
||||
GameIcon,
|
||||
CoffeeIcon,
|
||||
GameIcon,
|
||||
GaugeIcon,
|
||||
AstralRinthLogo,
|
||||
DownloadIcon,
|
||||
SpinnerIcon,
|
||||
PaintbrushIcon,
|
||||
ReportIcon,
|
||||
SettingsIcon,
|
||||
ShieldIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { TabbedModal } from '@modrinth/ui'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useVIntl, defineMessage } from '@vintl/vintl'
|
||||
import AppearanceSettings from '@/components/ui/settings/AppearanceSettings.vue'
|
||||
import JavaSettings from '@/components/ui/settings/JavaSettings.vue'
|
||||
import ResourceManagementSettings from '@/components/ui/settings/ResourceManagementSettings.vue'
|
||||
import PrivacySettings from '@/components/ui/settings/PrivacySettings.vue'
|
||||
import DefaultInstanceSettings from '@/components/ui/settings/DefaultInstanceSettings.vue'
|
||||
import { ProgressBar, TabbedModal } from '@modrinth/ui'
|
||||
import { getVersion } from '@tauri-apps/api/app'
|
||||
import { version as getOsVersion, platform as getOsPlatform } from '@tauri-apps/plugin-os'
|
||||
import { useTheming } from '@/store/state'
|
||||
import FeatureFlagSettings from '@/components/ui/settings/FeatureFlagSettings.vue'
|
||||
import { platform as getOsPlatform, version as getOsVersion } from '@tauri-apps/plugin-os'
|
||||
import { defineMessage, defineMessages, useVIntl } from '@vintl/vintl'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import AppearanceSettings from '@/components/ui/settings/AppearanceSettings.vue'
|
||||
import DefaultInstanceSettings from '@/components/ui/settings/DefaultInstanceSettings.vue'
|
||||
import FeatureFlagSettings from '@/components/ui/settings/FeatureFlagSettings.vue'
|
||||
import JavaSettings from '@/components/ui/settings/JavaSettings.vue'
|
||||
import PrivacySettings from '@/components/ui/settings/PrivacySettings.vue'
|
||||
import ResourceManagementSettings from '@/components/ui/settings/ResourceManagementSettings.vue'
|
||||
import { get, set } from '@/helpers/settings.ts'
|
||||
|
||||
// [AR] Imports
|
||||
import { installState, getRemote, updateState } from '@/helpers/update.js'
|
||||
|
||||
@@ -42,6 +43,8 @@ const initDownload = async () => {
|
||||
updateRequestFailView.value.show()
|
||||
}
|
||||
}
|
||||
import { injectAppUpdateDownloadProgress } from '@/providers/download-progress.ts'
|
||||
import { useTheming } from '@/store/state'
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
@@ -116,6 +119,8 @@ const isOpen = computed(() => modal.value?.isOpen)
|
||||
|
||||
defineExpose({ show, isOpen })
|
||||
|
||||
const { progress, version: downloadingVersion } = injectAppUpdateDownloadProgress()
|
||||
|
||||
const version = await getVersion()
|
||||
const osPlatform = getOsPlatform()
|
||||
const osVersion = getOsVersion()
|
||||
@@ -141,6 +146,13 @@ function devModeCount() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
downloading: {
|
||||
id: 'app.settings.downloading',
|
||||
defaultMessage: 'Downloading v{version}',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<ModalWrapper ref="modal">
|
||||
@@ -153,20 +165,32 @@ function devModeCount() {
|
||||
<TabbedModal :tabs="tabs.filter((t) => !t.developerOnly || themeStore.devMode)">
|
||||
<template #footer>
|
||||
<div class="mt-auto text-secondary text-sm">
|
||||
<div class="mb-3">
|
||||
<template v-if="progress > 0 && progress < 1">
|
||||
<p class="m-0 mb-2">
|
||||
{{ formatMessage(messages.downloading, { version: downloadingVersion }) }}
|
||||
</p>
|
||||
<ProgressBar :progress="progress" />
|
||||
</template>
|
||||
</div>
|
||||
<p v-if="themeStore.devMode" class="text-brand font-semibold m-0 mb-2">
|
||||
{{ formatMessage(developerModeEnabled) }}
|
||||
</p>
|
||||
<div class="flex items-center gap-3">
|
||||
<button
|
||||
class="p-0 m-0 bg-transparent border-none cursor-pointer button-animation"
|
||||
:class="{ 'text-brand': themeStore.devMode, 'text-secondary': !themeStore.devMode }"
|
||||
@click="devModeCount">
|
||||
:class="{
|
||||
'text-brand': themeStore.devMode,
|
||||
'text-secondary': !themeStore.devMode,
|
||||
}"
|
||||
@click="devModeCount"
|
||||
>
|
||||
<AstralRinthLogo class="w-6 h-6" />
|
||||
</button>
|
||||
<div>
|
||||
<p class="m-0">AstralRinth App {{ version }}</p>
|
||||
<p class="m-0">
|
||||
<span v-if="osPlatform === 'macos'">MacOS</span>
|
||||
<span v-if="osPlatform === 'macos'">macOS</span>
|
||||
<span v-else class="capitalize">{{ osPlatform }}</span>
|
||||
{{ osVersion }}
|
||||
</p>
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<script setup lang="ts">
|
||||
import { LogInIcon, SpinnerIcon } from '@modrinth/assets'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
|
||||
defineProps({
|
||||
onFlowCancel: {
|
||||
type: Function,
|
||||
default() {
|
||||
return async () => {}
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const modal = ref()
|
||||
|
||||
function show() {
|
||||
modal.value.show()
|
||||
}
|
||||
|
||||
function hide() {
|
||||
modal.value.hide()
|
||||
}
|
||||
|
||||
defineExpose({ show, hide })
|
||||
</script>
|
||||
<template>
|
||||
<ModalWrapper ref="modal" @hide="onFlowCancel">
|
||||
<template #title>
|
||||
<span class="items-center gap-2 text-lg font-extrabold text-contrast">
|
||||
<LogInIcon /> Sign in
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<div class="flex justify-center gap-2">
|
||||
<SpinnerIcon class="w-12 h-12 animate-spin" />
|
||||
</div>
|
||||
<p class="text-sm text-secondary">
|
||||
Please sign in at the browser window that just opened to continue.
|
||||
</p>
|
||||
</ModalWrapper>
|
||||
</template>
|
||||
@@ -1,8 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ConfirmModal } from '@modrinth/ui'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { useTheming } from '@/store/theme.js'
|
||||
// import { hide_ads_window, show_ads_window } from '@/helpers/ads.js'
|
||||
import { useTheming } from '@/store/theme.ts'
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
@@ -52,10 +53,11 @@ const modal = ref(null)
|
||||
|
||||
defineExpose({
|
||||
show: () => {
|
||||
// hide_ads_window()
|
||||
modal.value.show()
|
||||
},
|
||||
hide: () => {
|
||||
// onModalHide()
|
||||
onModalHide()
|
||||
modal.value.hide()
|
||||
},
|
||||
})
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { ChevronRightIcon } from '@modrinth/assets'
|
||||
import { Avatar } from '@modrinth/ui'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
|
||||
import type { GameInstance } from '@/helpers/types'
|
||||
|
||||
defineProps<{
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ChevronRightIcon,
|
||||
CodeIcon,
|
||||
CoffeeIcon,
|
||||
InfoIcon,
|
||||
WrenchIcon,
|
||||
MonitorIcon,
|
||||
CodeIcon,
|
||||
WrenchIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { Avatar, TabbedModal, type TabbedModalTab } from '@modrinth/ui'
|
||||
import { ref } from 'vue'
|
||||
import { defineMessage, useVIntl } from '@vintl/vintl'
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import GeneralSettings from '@/components/ui/instance_settings/GeneralSettings.vue'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { defineMessage, useVIntl } from '@vintl/vintl'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import GeneralSettings from '@/components/ui/instance_settings/GeneralSettings.vue'
|
||||
import HooksSettings from '@/components/ui/instance_settings/HooksSettings.vue'
|
||||
import InstallationSettings from '@/components/ui/instance_settings/InstallationSettings.vue'
|
||||
import JavaSettings from '@/components/ui/instance_settings/JavaSettings.vue'
|
||||
import WindowSettings from '@/components/ui/instance_settings/WindowSettings.vue'
|
||||
import HooksSettings from '@/components/ui/instance_settings/HooksSettings.vue'
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
|
||||
import type { InstanceSettingsTabProps } from '../../../helpers/types'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { useTemplateRef } from 'vue'
|
||||
import { NewModal as Modal } from '@modrinth/ui'
|
||||
// import { show_ads_window, hide_ads_window } from '@/helpers/ads.js'
|
||||
import { useTheming } from '@/store/theme.js'
|
||||
import { useTemplateRef } from 'vue'
|
||||
|
||||
// import { hide_ads_window, show_ads_window } from '@/helpers/ads.js'
|
||||
import { useTheming } from '@/store/theme.ts'
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
@@ -11,6 +12,10 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
hideHeader: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
closable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
@@ -18,7 +23,7 @@ const props = defineProps({
|
||||
onHide: {
|
||||
type: Function,
|
||||
default() {
|
||||
return () => { }
|
||||
return () => {}
|
||||
},
|
||||
},
|
||||
// showAdOnClose: {
|
||||
@@ -40,15 +45,22 @@ defineExpose({
|
||||
})
|
||||
|
||||
function onModalHide() {
|
||||
// if (props.showAdOnClose) {
|
||||
// show_ads_window()
|
||||
// }
|
||||
// if (props.showAdOnClose) {
|
||||
// show_ads_window()
|
||||
// }
|
||||
props.onHide?.()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal ref="modal" :header="header" :noblur="!themeStore.advancedRendering" @hide="onModalHide">
|
||||
<Modal
|
||||
ref="modal"
|
||||
:header="header"
|
||||
:noblur="!themeStore.advancedRendering"
|
||||
:closable="closable"
|
||||
:hide-header="hideHeader"
|
||||
@hide="onModalHide"
|
||||
>
|
||||
<template #title>
|
||||
<slot name="title" />
|
||||
</template>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ShareModal } from '@modrinth/ui'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { useTheming } from '@/store/theme.js'
|
||||
// import { hide_ads_window, show_ads_window } from '@/helpers/ads.js'
|
||||
import { useTheming } from '@/store/theme.ts'
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
@@ -33,6 +34,7 @@ const modal = ref(null)
|
||||
|
||||
defineExpose({
|
||||
show: (passedContent) => {
|
||||
// hide_ads_window()
|
||||
modal.value.show(passedContent)
|
||||
},
|
||||
hide: () => {
|
||||
@@ -40,9 +42,21 @@ defineExpose({
|
||||
modal.value.hide()
|
||||
},
|
||||
})
|
||||
|
||||
// function onModalHide() {
|
||||
// show_ads_window()
|
||||
// }
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ShareModal ref="modal" :header="header" :share-title="shareTitle" :share-text="shareText" :link="link"
|
||||
:open-in-new-tab="openInNewTab" :on-hide="onModalHide" :noblur="!themeStore.advancedRendering" />
|
||||
<ShareModal
|
||||
ref="modal"
|
||||
:header="header"
|
||||
:share-title="shareTitle"
|
||||
:share-text="shareText"
|
||||
:link="link"
|
||||
:open-in-new-tab="openInNewTab"
|
||||
:on-hide="onModalHide"
|
||||
:noblur="!themeStore.advancedRendering"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { TeleportDropdownMenu, ThemeSelector, Toggle } from '@modrinth/ui'
|
||||
import { useTheming } from '@/store/state'
|
||||
import { get, set } from '@/helpers/settings.ts'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
import { get, set } from '@/helpers/settings.ts'
|
||||
import { getOS } from '@/helpers/utils'
|
||||
import { useTheming } from '@/store/state'
|
||||
import type { ColorTheme } from '@/store/theme.ts'
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { get, set } from '@/helpers/settings.ts'
|
||||
import { injectNotificationManager, Slider, Toggle } from '@modrinth/ui'
|
||||
import { ref, watch } from 'vue'
|
||||
import { get_max_memory } from '@/helpers/jre'
|
||||
import { handleError } from '@/store/notifications'
|
||||
import { Slider, Toggle } from '@modrinth/ui'
|
||||
|
||||
import useMemorySlider from '@/composables/useMemorySlider'
|
||||
import { get, set } from '@/helpers/settings.ts'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
|
||||
const fetchSettings = await get()
|
||||
fetchSettings.launchArgs = fetchSettings.extra_launch_args.join(' ')
|
||||
@@ -11,7 +13,10 @@ fetchSettings.envVars = fetchSettings.custom_env_vars.map((x) => x.join('=')).jo
|
||||
|
||||
const settings = ref(fetchSettings)
|
||||
|
||||
const maxMemory = ref(Math.floor((await get_max_memory().catch(handleError)) / 1024))
|
||||
const { maxMemory, snapPoints } = (await useMemorySlider().catch(handleError)) as unknown as {
|
||||
maxMemory: number
|
||||
snapPoints: number[]
|
||||
}
|
||||
|
||||
watch(
|
||||
settings,
|
||||
@@ -107,6 +112,8 @@ watch(
|
||||
:min="512"
|
||||
:max="maxMemory"
|
||||
:step="64"
|
||||
:snap-points="snapPoints"
|
||||
:snap-range="512"
|
||||
unit="MB"
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { Toggle } from '@modrinth/ui'
|
||||
import { useTheming } from '@/store/state'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
import { get as getSettings, set as setSettings } from '@/helpers/settings.ts'
|
||||
import { useTheming } from '@/store/state'
|
||||
import { DEFAULT_FEATURE_FLAGS, type FeatureFlag } from '@/store/theme.ts'
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
<script setup>
|
||||
import { injectNotificationManager } from '@modrinth/ui'
|
||||
import { ref } from 'vue'
|
||||
import { get_java_versions, set_java_version } from '@/helpers/jre'
|
||||
import { handleError } from '@/store/notifications'
|
||||
|
||||
import JavaSelector from '@/components/ui/JavaSelector.vue'
|
||||
import { get_java_versions, set_java_version } from '@/helpers/jre'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
|
||||
const javaVersions = ref(await get_java_versions().catch(handleError))
|
||||
async function updateJavaVersion(version) {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { get, set } from '@/helpers/settings.ts'
|
||||
import { Toggle } from '@modrinth/ui'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
import { optInAnalytics, optOutAnalytics } from '@/helpers/analytics'
|
||||
import { get, set } from '@/helpers/settings.ts'
|
||||
|
||||
const settings = ref(await get())
|
||||
|
||||
@@ -26,7 +27,7 @@ watch(
|
||||
<div>
|
||||
<h2 class="m-0 text-lg font-extrabold text-contrast">Personalized ads</h2>
|
||||
<p class="m-0 text-sm">
|
||||
(Hard disabled by AR) • Modrinth's ad provider, Aditude, shows ads based on your preferences. By disabling this
|
||||
Modrinth's ad provider, Aditude, shows ads based on your preferences. By disabling this
|
||||
option, you opt out and ads will no longer be shown based on your interests.
|
||||
</p>
|
||||
</div>
|
||||
@@ -38,7 +39,7 @@ watch(
|
||||
<div>
|
||||
<h2 class="m-0 text-lg font-extrabold text-contrast">Telemetry</h2>
|
||||
<p class="m-0 text-sm">
|
||||
(Hard disabled by AR) • Modrinth collects anonymized analytics and usage data to improve our user experience and
|
||||
Modrinth collects anonymized analytics and usage data to improve our user experience and
|
||||
customize your experience. By disabling this option, you opt out and your data will no
|
||||
longer be collected.
|
||||
</p>
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
<script setup>
|
||||
import { Button, Slider } from '@modrinth/ui'
|
||||
import { ref, watch } from 'vue'
|
||||
import { get, set } from '@/helpers/settings.ts'
|
||||
import { purge_cache_types } from '@/helpers/cache.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { BoxIcon, FolderSearchIcon, TrashIcon } from '@modrinth/assets'
|
||||
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
|
||||
import { Button, injectNotificationManager, Slider } from '@modrinth/ui'
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
|
||||
import { purge_cache_types } from '@/helpers/cache.js'
|
||||
import { get, set } from '@/helpers/settings.ts'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
const settings = ref(await get())
|
||||
|
||||
watch(
|
||||
|
||||
@@ -100,36 +100,40 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, useTemplateRef } from 'vue'
|
||||
import SelectCapeModal from '@/components/ui/skin/SelectCapeModal.vue'
|
||||
import {
|
||||
SkinPreviewRenderer,
|
||||
CheckIcon,
|
||||
ChevronRightIcon,
|
||||
SaveIcon,
|
||||
SpinnerIcon,
|
||||
UploadIcon,
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
import {
|
||||
Button,
|
||||
RadioButtons,
|
||||
ButtonStyled,
|
||||
CapeButton,
|
||||
CapeLikeTextButton,
|
||||
ButtonStyled,
|
||||
injectNotificationManager,
|
||||
RadioButtons,
|
||||
SkinPreviewRenderer,
|
||||
} from '@modrinth/ui'
|
||||
import { computed, ref, useTemplateRef, watch } from 'vue'
|
||||
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import SelectCapeModal from '@/components/ui/skin/SelectCapeModal.vue'
|
||||
import UploadSkinModal from '@/components/ui/skin/UploadSkinModal.vue'
|
||||
import {
|
||||
add_and_equip_custom_skin,
|
||||
remove_custom_skin,
|
||||
unequip_skin,
|
||||
type Skin,
|
||||
type Cape,
|
||||
type SkinModel,
|
||||
determineModelType,
|
||||
get_normalized_skin_texture,
|
||||
remove_custom_skin,
|
||||
type Skin,
|
||||
type SkinModel,
|
||||
unequip_skin,
|
||||
} from '@/helpers/skins.ts'
|
||||
import { handleError } from '@/store/notifications'
|
||||
import {
|
||||
UploadIcon,
|
||||
CheckIcon,
|
||||
SaveIcon,
|
||||
XIcon,
|
||||
ChevronRightIcon,
|
||||
SpinnerIcon,
|
||||
} from '@modrinth/assets'
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import UploadSkinModal from '@/components/ui/skin/UploadSkinModal.vue'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
|
||||
const modal = useTemplateRef('modal')
|
||||
const selectCapeModal = useTemplateRef('selectCapeModal')
|
||||
@@ -253,7 +257,7 @@ async function showNew(e: MouseEvent, skinTextureUrl: string) {
|
||||
mode.value = 'new'
|
||||
currentSkin.value = null
|
||||
uploadedTextureUrl.value = skinTextureUrl
|
||||
variant.value = 'CLASSIC'
|
||||
variant.value = await determineModelType(skinTextureUrl)
|
||||
selectedCape.value = undefined
|
||||
visibleCapeList.value = []
|
||||
initVisibleCapeList()
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { useTemplateRef, ref, computed } from 'vue'
|
||||
import type { Cape, SkinModel } from '@/helpers/skins.ts'
|
||||
import { CheckIcon, XIcon } from '@modrinth/assets'
|
||||
import {
|
||||
ButtonStyled,
|
||||
ScrollablePanel,
|
||||
CapeButton,
|
||||
CapeLikeTextButton,
|
||||
ScrollablePanel,
|
||||
SkinPreviewRenderer,
|
||||
} from '@modrinth/ui'
|
||||
import { CheckIcon, XIcon } from '@modrinth/assets'
|
||||
import { computed, ref, useTemplateRef } from 'vue'
|
||||
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import type { Cape, SkinModel } from '@/helpers/skins.ts'
|
||||
|
||||
const modal = useTemplateRef('modal')
|
||||
|
||||
|
||||
@@ -27,14 +27,15 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onBeforeUnmount, watch } from 'vue'
|
||||
import { UploadIcon } from '@modrinth/assets'
|
||||
import { useNotifications } from '@/store/state'
|
||||
import { injectNotificationManager } from '@modrinth/ui'
|
||||
import { getCurrentWebview } from '@tauri-apps/api/webview'
|
||||
import { onBeforeUnmount, ref, watch } from 'vue'
|
||||
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import { get_dragged_skin_data } from '@/helpers/skins'
|
||||
|
||||
const notifications = useNotifications()
|
||||
const { addNotification } = injectNotificationManager()
|
||||
|
||||
const modal = ref()
|
||||
const fileInput = ref<HTMLInputElement>()
|
||||
@@ -99,7 +100,7 @@ async function setupDragDropListener() {
|
||||
const data = await get_dragged_skin_data(filePath)
|
||||
await processData(data.buffer)
|
||||
} catch (error) {
|
||||
notifications.addNotification({
|
||||
addNotification({
|
||||
title: 'Error processing file',
|
||||
text: error instanceof Error ? error.message : 'Failed to read the dropped file.',
|
||||
type: 'error',
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
import type { Dayjs } from 'dayjs'
|
||||
import dayjs from 'dayjs'
|
||||
import {
|
||||
EyeIcon,
|
||||
FolderOpenIcon,
|
||||
@@ -13,25 +11,29 @@ import {
|
||||
Avatar,
|
||||
ButtonStyled,
|
||||
commonMessages,
|
||||
injectNotificationManager,
|
||||
OverflowMenu,
|
||||
SmartClickable,
|
||||
useRelativeTime,
|
||||
} from '@modrinth/ui'
|
||||
import { useVIntl } from '@vintl/vintl'
|
||||
import { computed, nextTick, ref, onMounted, onUnmounted } from 'vue'
|
||||
import { showProfileInFolder } from '@/helpers/utils'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { useRouter } from 'vue-router'
|
||||
import type { GameInstance } from '@/helpers/types'
|
||||
import { get_project } from '@/helpers/cache'
|
||||
import { capitalizeString } from '@modrinth/utils'
|
||||
import { kill, run } from '@/helpers/profile'
|
||||
import { handleSevereError } from '@/store/error'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { get_by_profile_path } from '@/helpers/process'
|
||||
import { handleError } from '@/store/notifications'
|
||||
import { process_listener } from '@/helpers/events'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { useVIntl } from '@vintl/vintl'
|
||||
import type { Dayjs } from 'dayjs'
|
||||
import dayjs from 'dayjs'
|
||||
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { get_project } from '@/helpers/cache'
|
||||
import { process_listener } from '@/helpers/events'
|
||||
import { get_by_profile_path } from '@/helpers/process'
|
||||
import { kill, run } from '@/helpers/profile'
|
||||
import type { GameInstance } from '@/helpers/types'
|
||||
import { showProfileInFolder } from '@/helpers/utils'
|
||||
import { handleSevereError } from '@/store/error'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
const { formatMessage } = useVIntl()
|
||||
const formatRelativeTime = useRelativeTime()
|
||||
|
||||
|
||||
@@ -1,29 +1,36 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
type ServerWorld,
|
||||
type ServerData,
|
||||
type WorldWithProfile,
|
||||
get_recent_worlds,
|
||||
getWorldIdentifier,
|
||||
get_profile_protocol_version,
|
||||
refreshServerData,
|
||||
start_join_server,
|
||||
start_join_singleplayer_world,
|
||||
} from '@/helpers/worlds.ts'
|
||||
import { HeadingLink, GAME_MODES } from '@modrinth/ui'
|
||||
import WorldItem from '@/components/ui/world/WorldItem.vue'
|
||||
import InstanceItem from '@/components/ui/world/InstanceItem.vue'
|
||||
import { watch, onMounted, onUnmounted, ref, computed } from 'vue'
|
||||
import type { GameVersion } from '@modrinth/ui'
|
||||
import { GAME_MODES, HeadingLink, injectNotificationManager } from '@modrinth/ui'
|
||||
import type { Dayjs } from 'dayjs'
|
||||
import dayjs from 'dayjs'
|
||||
import { useTheming } from '@/store/theme.ts'
|
||||
import { kill, run } from '@/helpers/profile'
|
||||
import { handleError } from '@/store/notifications'
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
|
||||
import InstanceItem from '@/components/ui/world/InstanceItem.vue'
|
||||
import WorldItem from '@/components/ui/world/WorldItem.vue'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { process_listener, profile_listener } from '@/helpers/events'
|
||||
import { get_all } from '@/helpers/process'
|
||||
import { kill, run } from '@/helpers/profile'
|
||||
import { get_game_versions } from '@/helpers/tags'
|
||||
import type { GameInstance } from '@/helpers/types'
|
||||
import {
|
||||
get_profile_protocol_version,
|
||||
get_recent_worlds,
|
||||
getWorldIdentifier,
|
||||
hasServerQuickPlaySupport,
|
||||
hasWorldQuickPlaySupport,
|
||||
type ProtocolVersion,
|
||||
refreshServerData,
|
||||
type ServerData,
|
||||
type ServerWorld,
|
||||
start_join_server,
|
||||
start_join_singleplayer_world,
|
||||
type WorldWithProfile,
|
||||
} from '@/helpers/worlds.ts'
|
||||
import { handleSevereError } from '@/store/error'
|
||||
import { useTheming } from '@/store/theme.ts'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
|
||||
const props = defineProps<{
|
||||
recentInstances: GameInstance[]
|
||||
@@ -33,7 +40,8 @@ const theme = useTheming()
|
||||
|
||||
const jumpBackInItems = ref<JumpBackInItem[]>([])
|
||||
const serverData = ref<Record<string, ServerData>>({})
|
||||
const protocolVersions = ref<Record<string, number | null>>({})
|
||||
const protocolVersions = ref<Record<string, ProtocolVersion | null>>({})
|
||||
const gameVersions = ref<GameVersion[]>(await get_game_versions().catch(() => []))
|
||||
|
||||
const MIN_JUMP_BACK_IN = 3
|
||||
const MAX_JUMP_BACK_IN = 6
|
||||
@@ -121,11 +129,8 @@ async function populateJumpBackIn() {
|
||||
}
|
||||
})
|
||||
|
||||
// fetch each server's data
|
||||
Promise.all(
|
||||
servers.map(({ instancePath, address }) =>
|
||||
servers.forEach(({ instancePath, address }) =>
|
||||
refreshServerData(serverData.value[address], protocolVersions.value[instancePath], address),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -150,8 +155,8 @@ async function populateJumpBackIn() {
|
||||
.slice(0, MAX_JUMP_BACK_IN)
|
||||
}
|
||||
|
||||
async function refreshServer(address: string, instancePath: string) {
|
||||
await refreshServerData(serverData.value[address], protocolVersions.value[instancePath], address)
|
||||
function refreshServer(address: string, instancePath: string) {
|
||||
refreshServerData(serverData.value[address], protocolVersions.value[instancePath], address)
|
||||
}
|
||||
|
||||
async function joinWorld(world: WorldWithProfile) {
|
||||
@@ -255,7 +260,14 @@ onUnmounted(() => {
|
||||
? serverData[item.world.address].refreshing && !serverData[item.world.address].status
|
||||
: undefined
|
||||
"
|
||||
supports-quick-play
|
||||
:supports-server-quick-play="
|
||||
item.world.type === 'server' &&
|
||||
hasServerQuickPlaySupport(gameVersions, item.instance.game_version || '')
|
||||
"
|
||||
:supports-world-quick-play="
|
||||
item.world.type === 'singleplayer' &&
|
||||
hasWorldQuickPlaySupport(gameVersions, item.instance.game_version || '')
|
||||
"
|
||||
:server-status="
|
||||
item.world.type === 'server' ? serverData[item.world.address].status : undefined
|
||||
"
|
||||
|
||||
@@ -1,22 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import dayjs from 'dayjs'
|
||||
import type { ServerStatus, ServerWorld, SingleplayerWorld, World } from '@/helpers/worlds.ts'
|
||||
import { set_world_display_status, getWorldIdentifier } from '@/helpers/worlds.ts'
|
||||
import { formatNumber, getPingLevel } from '@modrinth/utils'
|
||||
import {
|
||||
useRelativeTime,
|
||||
Avatar,
|
||||
ButtonStyled,
|
||||
commonMessages,
|
||||
OverflowMenu,
|
||||
SmartClickable,
|
||||
} from '@modrinth/ui'
|
||||
import {
|
||||
IssuesIcon,
|
||||
EyeIcon,
|
||||
ClipboardCopyIcon,
|
||||
EditIcon,
|
||||
EyeIcon,
|
||||
FolderOpenIcon,
|
||||
IssuesIcon,
|
||||
MoreVerticalIcon,
|
||||
NoSignalIcon,
|
||||
PlayIcon,
|
||||
@@ -29,14 +17,33 @@ import {
|
||||
UserIcon,
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
import {
|
||||
Avatar,
|
||||
ButtonStyled,
|
||||
commonMessages,
|
||||
OverflowMenu,
|
||||
SmartClickable,
|
||||
useRelativeTime,
|
||||
} from '@modrinth/ui'
|
||||
import { formatNumber, getPingLevel } from '@modrinth/utils'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import type { MessageDescriptor } from '@vintl/vintl'
|
||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
||||
import dayjs from 'dayjs'
|
||||
import { Tooltip } from 'floating-vue'
|
||||
import type { Component } from 'vue'
|
||||
import { computed } from 'vue'
|
||||
import { copyToClipboard } from '@/helpers/utils'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { Tooltip } from 'floating-vue'
|
||||
|
||||
import { copyToClipboard } from '@/helpers/utils'
|
||||
import type {
|
||||
ProtocolVersion,
|
||||
ServerStatus,
|
||||
ServerWorld,
|
||||
SingleplayerWorld,
|
||||
World,
|
||||
} from '@/helpers/worlds.ts'
|
||||
import { getWorldIdentifier, set_world_display_status } from '@/helpers/worlds.ts'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const formatRelativeTime = useRelativeTime()
|
||||
@@ -54,8 +61,9 @@ const props = withDefaults(
|
||||
playingInstance?: boolean
|
||||
playingWorld?: boolean
|
||||
startingInstance?: boolean
|
||||
supportsQuickPlay?: boolean
|
||||
currentProtocol?: number | null
|
||||
supportsServerQuickPlay?: boolean
|
||||
supportsWorldQuickPlay?: boolean
|
||||
currentProtocol?: ProtocolVersion | null
|
||||
highlighted?: boolean
|
||||
|
||||
// Server only
|
||||
@@ -78,7 +86,8 @@ const props = withDefaults(
|
||||
playingInstance: false,
|
||||
playingWorld: false,
|
||||
startingInstance: false,
|
||||
supportsQuickPlay: false,
|
||||
supportsServerQuickPlay: true,
|
||||
supportsWorldQuickPlay: false,
|
||||
currentProtocol: null,
|
||||
|
||||
refreshing: false,
|
||||
@@ -102,7 +111,8 @@ const serverIncompatible = computed(
|
||||
!!props.serverStatus &&
|
||||
!!props.serverStatus.version?.protocol &&
|
||||
!!props.currentProtocol &&
|
||||
props.serverStatus.version.protocol !== props.currentProtocol,
|
||||
(props.serverStatus.version.protocol !== props.currentProtocol.version ||
|
||||
props.serverStatus.version.legacy !== props.currentProtocol.legacy),
|
||||
)
|
||||
|
||||
const locked = computed(() => props.world.type === 'singleplayer' && props.world.locked)
|
||||
@@ -120,14 +130,26 @@ const messages = defineMessages({
|
||||
id: 'instance.worlds.a_minecraft_server',
|
||||
defaultMessage: 'A Minecraft Server',
|
||||
},
|
||||
noQuickPlay: {
|
||||
id: 'instance.worlds.no_quick_play',
|
||||
defaultMessage: 'You can only jump straight into worlds on Minecraft 1.20+',
|
||||
noServerQuickPlay: {
|
||||
id: 'instance.worlds.no_server_quick_play',
|
||||
defaultMessage: 'You can only jump straight into servers on Minecraft Alpha 1.0.5+',
|
||||
},
|
||||
noSingleplayerQuickPlay: {
|
||||
id: 'instance.worlds.no_singleplayer_quick_play',
|
||||
defaultMessage: 'You can only jump straight into singleplayer worlds on Minecraft 1.20+',
|
||||
},
|
||||
gameAlreadyOpen: {
|
||||
id: 'instance.worlds.game_already_open',
|
||||
defaultMessage: 'Instance is already open',
|
||||
},
|
||||
noContact: {
|
||||
id: 'instance.worlds.no_contact',
|
||||
defaultMessage: "Server couldn't be contacted",
|
||||
},
|
||||
incompatibleServer: {
|
||||
id: 'instance.worlds.incompatible_server',
|
||||
defaultMessage: 'Server is incompatible',
|
||||
},
|
||||
copyAddress: {
|
||||
id: 'instance.worlds.copy_address',
|
||||
defaultMessage: 'Copy address',
|
||||
@@ -136,10 +158,6 @@ const messages = defineMessages({
|
||||
id: 'instance.worlds.view_instance',
|
||||
defaultMessage: 'View instance',
|
||||
},
|
||||
playAnyway: {
|
||||
id: 'instance.worlds.play_anyway',
|
||||
defaultMessage: 'Play anyway',
|
||||
},
|
||||
playInstance: {
|
||||
id: 'instance.worlds.play_instance',
|
||||
defaultMessage: 'Play instance',
|
||||
@@ -218,7 +236,8 @@ const messages = defineMessages({
|
||||
/>
|
||||
<Tooltip :disabled="!hasPlayersTooltip">
|
||||
<span :class="{ 'cursor-help': hasPlayersTooltip }">
|
||||
{{ formatNumber(serverStatus.players?.online, false) }} online
|
||||
{{ formatNumber(serverStatus.players?.online, false) }}
|
||||
online
|
||||
</span>
|
||||
<template #popper>
|
||||
<div class="flex flex-col gap-1">
|
||||
@@ -231,7 +250,8 @@ const messages = defineMessages({
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<NoSignalIcon aria-hidden="true" stroke-width="3px" class="shrink-0" /> Offline
|
||||
<NoSignalIcon aria-hidden="true" stroke-width="3px" class="shrink-0" />
|
||||
Offline
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@@ -241,7 +261,9 @@ const messages = defineMessages({
|
||||
world.last_played ? dayjs(world.last_played).format('MMMM D, YYYY [at] h:mm A') : null
|
||||
"
|
||||
class="w-fit shrink-0"
|
||||
:class="{ 'cursor-help smart-clickable:allow-pointer-events': world.last_played }"
|
||||
:class="{
|
||||
'cursor-help smart-clickable:allow-pointer-events': world.last_played,
|
||||
}"
|
||||
>
|
||||
<template v-if="world.last_played">
|
||||
{{
|
||||
@@ -302,7 +324,6 @@ const messages = defineMessages({
|
||||
</template>
|
||||
</div>
|
||||
<div class="flex gap-1 justify-end smart-clickable:allow-pointer-events">
|
||||
<template v-if="world.type === 'singleplayer' || serverStatus">
|
||||
<ButtonStyled
|
||||
v-if="(playingWorld || (locked && playingInstance)) && !startingInstance"
|
||||
color="red"
|
||||
@@ -315,15 +336,28 @@ const messages = defineMessages({
|
||||
<ButtonStyled v-else>
|
||||
<button
|
||||
v-tooltip="
|
||||
serverIncompatible
|
||||
? 'Server is incompatible'
|
||||
: !supportsQuickPlay
|
||||
? formatMessage(messages.noQuickPlay)
|
||||
world.type === 'server'
|
||||
? !supportsServerQuickPlay
|
||||
? formatMessage(messages.noServerQuickPlay)
|
||||
: playingOtherWorld
|
||||
? formatMessage(messages.gameAlreadyOpen)
|
||||
: !serverStatus
|
||||
? formatMessage(messages.noContact)
|
||||
: serverIncompatible
|
||||
? formatMessage(messages.incompatibleServer)
|
||||
: null
|
||||
: !supportsWorldQuickPlay
|
||||
? formatMessage(messages.noSingleplayerQuickPlay)
|
||||
: playingOtherWorld || locked
|
||||
? formatMessage(messages.gameAlreadyOpen)
|
||||
: null
|
||||
"
|
||||
:disabled="!supportsQuickPlay || playingOtherWorld || startingInstance"
|
||||
:disabled="
|
||||
playingOtherWorld ||
|
||||
startingInstance ||
|
||||
(world.type == 'server' && !supportsServerQuickPlay) ||
|
||||
(world.type == 'singleplayer' && !supportsWorldQuickPlay)
|
||||
"
|
||||
@click="emit('play')"
|
||||
>
|
||||
<SpinnerIcon v-if="startingInstance && playingWorld" class="animate-spin" />
|
||||
@@ -331,13 +365,6 @@ const messages = defineMessages({
|
||||
{{ formatMessage(commonMessages.playButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
<ButtonStyled v-else>
|
||||
<button class="invisible">
|
||||
<PlayIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonMessages.playButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled circular type="transparent">
|
||||
<OverflowMenu
|
||||
:options="[
|
||||
@@ -347,11 +374,6 @@ const messages = defineMessages({
|
||||
disabled: playingInstance,
|
||||
action: () => emit('play-instance'),
|
||||
},
|
||||
{
|
||||
id: 'play-anyway',
|
||||
shown: serverIncompatible && !playingInstance && supportsQuickPlay,
|
||||
action: () => emit('play'),
|
||||
},
|
||||
{
|
||||
id: 'open-instance',
|
||||
shown: !!instancePath,
|
||||
@@ -417,26 +439,25 @@ const messages = defineMessages({
|
||||
<PlayIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.playInstance) }}
|
||||
</template>
|
||||
<template #play-anyway>
|
||||
<PlayIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.playAnyway) }}
|
||||
</template>
|
||||
<template #open-instance>
|
||||
<EyeIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.viewInstance) }}
|
||||
</template>
|
||||
<template #edit>
|
||||
<EditIcon aria-hidden="true" /> {{ formatMessage(commonMessages.editButton) }}
|
||||
<EditIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonMessages.editButton) }}
|
||||
</template>
|
||||
<template #open-folder>
|
||||
<FolderOpenIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonMessages.openFolderButton) }}
|
||||
</template>
|
||||
<template #copy-address>
|
||||
<ClipboardCopyIcon aria-hidden="true" /> {{ formatMessage(messages.copyAddress) }}
|
||||
<ClipboardCopyIcon aria-hidden="true" />
|
||||
{{ formatMessage(messages.copyAddress) }}
|
||||
</template>
|
||||
<template #refresh>
|
||||
<UpdatedIcon aria-hidden="true" /> {{ formatMessage(commonMessages.refreshButton) }}
|
||||
<UpdatedIcon aria-hidden="true" />
|
||||
{{ formatMessage(commonMessages.refreshButton) }}
|
||||
</template>
|
||||
<template #dont-show-on-home>
|
||||
<XIcon aria-hidden="true" />
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { PlayIcon, PlusIcon, XIcon } from '@modrinth/assets'
|
||||
import { ButtonStyled, commonMessages } from '@modrinth/ui'
|
||||
import { ref } from 'vue'
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import type { GameInstance } from '@/helpers/types'
|
||||
import InstanceModalTitlePrefix from '@/components/ui/modal/InstanceModalTitlePrefix.vue'
|
||||
import { add_server_to_profile, type ServerPackStatus, type ServerWorld } from '@/helpers/worlds.ts'
|
||||
import { ButtonStyled, commonMessages, injectNotificationManager } from '@modrinth/ui'
|
||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
||||
import { handleError } from '@/store/notifications'
|
||||
import ServerModalBody from '@/components/ui/world/modal/ServerModalBody.vue'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import InstanceModalTitlePrefix from '@/components/ui/modal/InstanceModalTitlePrefix.vue'
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import ServerModalBody from '@/components/ui/world/modal/ServerModalBody.vue'
|
||||
import type { GameInstance } from '@/helpers/types'
|
||||
import { add_server_to_profile, type ServerPackStatus, type ServerWorld } from '@/helpers/worlds.ts'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import { SaveIcon, XIcon } from '@modrinth/assets'
|
||||
import { ButtonStyled, commonMessages } from '@modrinth/ui'
|
||||
import { ButtonStyled, commonMessages, injectNotificationManager } from '@modrinth/ui'
|
||||
import { defineMessage, useVIntl } from '@vintl/vintl'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import HideFromHomeOption from '@/components/ui/world/modal/HideFromHomeOption.vue'
|
||||
import ServerModalBody from '@/components/ui/world/modal/ServerModalBody.vue'
|
||||
import type { GameInstance } from '@/helpers/types'
|
||||
import {
|
||||
type ServerPackStatus,
|
||||
type DisplayStatus,
|
||||
edit_server_in_profile,
|
||||
type ServerPackStatus,
|
||||
type ServerWorld,
|
||||
set_world_display_status,
|
||||
type DisplayStatus,
|
||||
} from '@/helpers/worlds.ts'
|
||||
import { defineMessage, useVIntl } from '@vintl/vintl'
|
||||
import { handleError } from '@/store/notifications'
|
||||
import ServerModalBody from '@/components/ui/world/modal/ServerModalBody.vue'
|
||||
import HideFromHomeOption from '@/components/ui/world/modal/HideFromHomeOption.vue'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { ChevronRightIcon, SaveIcon, XIcon, UndoIcon } from '@modrinth/assets'
|
||||
import { Avatar, ButtonStyled, commonMessages } from '@modrinth/ui'
|
||||
import { ChevronRightIcon, SaveIcon, UndoIcon, XIcon } from '@modrinth/assets'
|
||||
import { Avatar, ButtonStyled, commonMessages, injectNotificationManager } from '@modrinth/ui'
|
||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import HideFromHomeOption from '@/components/ui/world/modal/HideFromHomeOption.vue'
|
||||
import type { GameInstance } from '@/helpers/types'
|
||||
import type { DisplayStatus, SingleplayerWorld } from '@/helpers/worlds.ts'
|
||||
import { set_world_display_status, rename_world, reset_world_icon } from '@/helpers/worlds.ts'
|
||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
||||
import { handleError } from '@/store/notifications'
|
||||
import HideFromHomeOption from '@/components/ui/world/modal/HideFromHomeOption.vue'
|
||||
import { rename_world, reset_world_icon, set_world_display_status } from '@/helpers/worlds.ts'
|
||||
|
||||
const { handleError } = injectNotificationManager()
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { Checkbox } from '@modrinth/ui'
|
||||
import { defineMessage, useVIntl } from '@vintl/vintl'
|
||||
import { computed } from 'vue'
|
||||
import { Checkbox } from '@modrinth/ui'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const value = defineModel<boolean>({ required: true })
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { TeleportDropdownMenu } from '@modrinth/ui'
|
||||
import { defineMessages, type MessageDescriptor, useVIntl } from '@vintl/vintl'
|
||||
|
||||
import type { ServerPackStatus } from '@/helpers/worlds.ts'
|
||||
import { type MessageDescriptor, defineMessages, useVIntl } from '@vintl/vintl'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
|
||||
import cssContent from '@/assets/stylesheets/macFix.css?inline'
|
||||
|
||||
export async function useCheckDisableMouseover() {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { get_max_memory } from '@/helpers/jre.js'
|
||||
|
||||
export default async function () {
|
||||
const maxMemory = ref(Math.floor((await get_max_memory()) / 1024))
|
||||
|
||||
const snapPoints = computed(() => {
|
||||
let points = []
|
||||
let memory = 2048
|
||||
|
||||
while (memory <= maxMemory.value) {
|
||||
points.push(memory)
|
||||
memory *= 2
|
||||
}
|
||||
|
||||
return points
|
||||
})
|
||||
|
||||
return { maxMemory, snapPoints }
|
||||
}
|
||||
@@ -1,24 +1,24 @@
|
||||
import { posthog } from 'posthog-js'
|
||||
// import { posthog } from 'posthog-js'
|
||||
|
||||
export const initAnalytics = () => {
|
||||
posthog.init('phc_9Iqi6lFs9sr5BSqh9RRNRSJ0mATS9PSgirDiX3iOYJ', {
|
||||
persistence: 'localStorage',
|
||||
api_host: 'https://posthog.modrinth.com',
|
||||
})
|
||||
// posthog.init('phc_9Iqi6lFs9sr5BSqh9RRNRSJ0mATS9PSgirDiX3iOYJ', {
|
||||
// persistence: 'localStorage',
|
||||
// api_host: 'https://posthog.modrinth.com',
|
||||
// })
|
||||
}
|
||||
|
||||
export const debugAnalytics = () => {
|
||||
posthog.debug()
|
||||
// posthog.debug()
|
||||
}
|
||||
|
||||
export const optOutAnalytics = () => {
|
||||
posthog.opt_out_capturing()
|
||||
// posthog.opt_out_capturing()
|
||||
}
|
||||
|
||||
export const optInAnalytics = () => {
|
||||
posthog.opt_in_capturing()
|
||||
// posthog.opt_in_capturing()
|
||||
}
|
||||
|
||||
export const trackEvent = (eventName, properties) => {
|
||||
posthog.capture(eventName, properties)
|
||||
// posthog.capture(eventName, properties)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,24 @@ export async function offline_login(name) {
|
||||
return await invoke('plugin:auth|offline_login', { name: name })
|
||||
}
|
||||
|
||||
// [AR] • Feature
|
||||
export async function elyby_login(uuid, login, accessToken) {
|
||||
return await invoke('plugin:auth|elyby_login', {
|
||||
uuid,
|
||||
login,
|
||||
accessToken
|
||||
})
|
||||
}
|
||||
|
||||
// [AR] • Feature
|
||||
export async function elyby_auth_authenticate(login, password, clientToken) {
|
||||
return await invoke('plugin:auth|elyby_auth_authenticate', {
|
||||
login,
|
||||
password,
|
||||
clientToken,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate a user with Hydra - part 1.
|
||||
* This begins the authentication flow quasi-synchronously.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { fetch } from '@tauri-apps/plugin-http'
|
||||
import { handleError } from '@/store/state.js'
|
||||
import { getVersion } from '@tauri-apps/api/app'
|
||||
import { fetch } from '@tauri-apps/plugin-http'
|
||||
|
||||
export const useFetch = async (url, item, isSilent) => {
|
||||
try {
|
||||
@@ -11,8 +10,9 @@ export const useFetch = async (url, item, isSilent) => {
|
||||
})
|
||||
} catch (err) {
|
||||
if (!isSilent) {
|
||||
handleError({ message: `Error fetching ${item}` })
|
||||
}
|
||||
throw err
|
||||
} else {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* and deserialized into a usable JS object.
|
||||
*/
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
|
||||
import { create } from './profile'
|
||||
|
||||
/*
|
||||
@@ -61,3 +62,31 @@ export async function is_valid_importable_instance(instanceFolder, launcherType)
|
||||
export async function get_default_launcher_path(launcherType) {
|
||||
return await invoke('plugin:import|get_default_launcher_path', { launcherType })
|
||||
}
|
||||
|
||||
/// Fetch CurseForge profile metadata from profile code
|
||||
/// eg: fetch_curseforge_profile_metadata("eSrNlKNo")
|
||||
export async function fetch_curseforge_profile_metadata(profileCode) {
|
||||
return await invoke('plugin:import|fetch_curseforge_profile_metadata', { profileCode })
|
||||
}
|
||||
|
||||
/// Import a CurseForge profile from profile code
|
||||
/// eg: import_curseforge_profile("eSrNlKNo")
|
||||
export async function import_curseforge_profile(profileCode) {
|
||||
try {
|
||||
// First, fetch the profile metadata to get the actual name
|
||||
const metadata = await fetch_curseforge_profile_metadata(profileCode)
|
||||
|
||||
// create a basic, empty instance using the actual profile name
|
||||
const profilePath = await create(metadata.name, '1.19.4', 'vanilla', 'latest', null, true)
|
||||
|
||||
const result = await invoke('plugin:import|import_curseforge_profile', {
|
||||
profilePath,
|
||||
profileCode,
|
||||
})
|
||||
|
||||
// Return the profile path for navigation
|
||||
return { result, profilePath }
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,11 @@ export async function get_logs_by_filename(profilePath, logType, filename) {
|
||||
|
||||
/// Get a profile's log text only by filename
|
||||
export async function get_output_by_filename(profilePath, logType, filename) {
|
||||
return await invoke('plugin:logs|logs_get_output_by_filename', { profilePath, logType, filename })
|
||||
return await invoke('plugin:logs|logs_get_output_by_filename', {
|
||||
profilePath,
|
||||
logType,
|
||||
filename,
|
||||
})
|
||||
}
|
||||
|
||||
/// Delete a profile's log by filename
|
||||
|
||||
@@ -16,3 +16,7 @@ export async function logout() {
|
||||
export async function get() {
|
||||
return await invoke('plugin:mr-auth|get')
|
||||
}
|
||||
|
||||
export async function cancelLogin() {
|
||||
return await invoke('plugin:mr-auth|cancel_modrinth_login')
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user