Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a80cadbc0 | ||
|
|
13ba1ae18f | ||
|
|
88888c6b8e | ||
|
|
1f98dbb939 | ||
|
|
a95df01482 | ||
|
|
9181bde08a | ||
|
|
3dc1f6a8fd | ||
|
|
a529f5f5d2 | ||
|
|
5234208662 | ||
|
|
c53037fac8 | ||
|
|
0753f3faba | ||
|
|
5cb7c17eae | ||
|
|
56ffa26cf5 | ||
|
|
944cc6adfb | ||
|
|
8f2d69cc08 | ||
|
|
e3e36cd158 | ||
|
|
daedc88f2d | ||
|
|
b62b471da2 | ||
|
|
108e2b0fab | ||
|
|
b97568b60b | ||
|
|
01c84cec6f | ||
|
|
ae13fb44a3 | ||
|
|
2f3d030f6c |
No files matched your search
@@ -0,0 +1,119 @@
|
||||
name: 🐛 Bug report
|
||||
description: Something in Modrinth Enhanced does not work
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Modrinth Enhanced is the Modrinth App with a series of patches applied. If the same thing
|
||||
happens in the official Modrinth App, it belongs
|
||||
[upstream](https://github.com/modrinth/code/issues/new/choose) instead.
|
||||
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Please confirm the following.
|
||||
options:
|
||||
- label: I checked the [existing issues](https://github.com/Felitendo/Modrinth-Enhanced/issues?q=is%3Aissue) for the same problem
|
||||
required: true
|
||||
- label: I am on the [latest release](https://github.com/Felitendo/Modrinth-Enhanced/releases/latest)
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Which version are you on?
|
||||
description: Settings, bottom left, next to the Modrinth logo.
|
||||
placeholder: Modrinth Enhanced 0.21.4
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: install
|
||||
attributes:
|
||||
label: How did you install it?
|
||||
options:
|
||||
- AppImage
|
||||
- Windows installer
|
||||
- macOS disk image
|
||||
- Debian or Ubuntu package
|
||||
- Fedora package
|
||||
- Arch Linux (modrinth-enhanced-bin)
|
||||
- Built it myself
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: os
|
||||
attributes:
|
||||
label: Which operating system?
|
||||
multiple: true
|
||||
options:
|
||||
- Windows
|
||||
- macOS
|
||||
- Linux
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: area
|
||||
attributes:
|
||||
label: What is it about?
|
||||
options:
|
||||
- Accounts (offline, Ely.by, custom servers)
|
||||
- Skins
|
||||
- Instances, launching or logs
|
||||
- Sidebar, title bar or window
|
||||
- Updates or installation
|
||||
- Something else
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: upstream
|
||||
attributes:
|
||||
label: Does the official Modrinth App do it too?
|
||||
options:
|
||||
- I have not tried it
|
||||
- 'No: only Modrinth Enhanced'
|
||||
- 'Yes: the official app as well'
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: What happens?
|
||||
description: What goes wrong, and what you expected instead. Screenshots help.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: steps
|
||||
attributes:
|
||||
label: How can it be reproduced?
|
||||
placeholder: |
|
||||
1. Open ...
|
||||
2. Click ...
|
||||
3. See ...
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Launcher log
|
||||
description: |
|
||||
The newest file in `launcher_logs`, in the app's folder:
|
||||
`%APPDATA%\ModrinthApp` on Windows, `~/Library/Application Support/ModrinthApp` on macOS,
|
||||
`~/.local/share/ModrinthApp` on Linux. Remove anything you would rather not share.
|
||||
render: shell
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: context
|
||||
attributes:
|
||||
label: Anything else?
|
||||
description: Whatever else may matter, such as the account server or the instance it happens with.
|
||||
validations:
|
||||
required: false
|
||||
@@ -0,0 +1,64 @@
|
||||
name: 💡 Feature request
|
||||
description: Suggest something for Modrinth Enhanced
|
||||
labels: [enhancement]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Everything here is carried as a patch on top of the Modrinth App, and every patch has to be
|
||||
kept working as upstream moves. Ideas that would suit the app for everyone are better off
|
||||
[upstream](https://github.com/modrinth/code/issues/new/choose), where they reach far more
|
||||
players.
|
||||
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Please confirm the following.
|
||||
options:
|
||||
- label: I checked the [existing issues](https://github.com/Felitendo/Modrinth-Enhanced/issues?q=is%3Aissue) for the same idea
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: area
|
||||
attributes:
|
||||
label: What is it about?
|
||||
options:
|
||||
- Accounts (offline, Ely.by, custom servers)
|
||||
- Skins
|
||||
- Instances, launching or logs
|
||||
- Sidebar, title bar or window
|
||||
- Updates or installation
|
||||
- Something else
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: What is the problem?
|
||||
description: What you are trying to do, and what stands in the way today.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: solution
|
||||
attributes:
|
||||
label: What should it do instead?
|
||||
description: How you picture it working. Screenshots or mockups help.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: Is there another way?
|
||||
description: Other launchers that do it, mods that do it, or ways you work around it now.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: context
|
||||
attributes:
|
||||
label: Anything else?
|
||||
description: Links to the service, mod or documentation involved, and whatever else may matter.
|
||||
validations:
|
||||
required: false
|
||||
@@ -0,0 +1,11 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 🎮 Bug in the Modrinth App itself
|
||||
about: Anything that the official Modrinth App does too belongs upstream.
|
||||
url: https://github.com/modrinth/code/issues/new/choose
|
||||
- name: 🫶 Using the Modrinth App
|
||||
about: Questions about the app, your account or Modrinth itself.
|
||||
url: https://support.modrinth.com
|
||||
- name: 📦 Downloads
|
||||
about: Installers for Windows, macOS and Linux, and what changed in each release.
|
||||
url: https://github.com/Felitendo/Modrinth-Enhanced/releases/latest
|
||||
@@ -100,7 +100,10 @@ jobs:
|
||||
needs: [detect, build]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# The whole history: the release notes are the commits since the last one.
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Record the upstream release we build against
|
||||
env:
|
||||
@@ -200,18 +203,47 @@ jobs:
|
||||
}' "$patch"
|
||||
done
|
||||
)"
|
||||
patch_count="$(printf '%s\n' "$patches" | grep -c '^- ' || true)"
|
||||
|
||||
# What changed here since the last release, as its commit subjects.
|
||||
# The bump of upstream.txt is left out: the line above already says
|
||||
# which Modrinth App this is built from.
|
||||
previous="$(gh release view --json tagName --jq .tagName 2>/dev/null || true)"
|
||||
changes=""
|
||||
if [ -n "$previous" ]; then
|
||||
changes="$(
|
||||
git log --reverse --no-merges --pretty='- %s' "$previous..HEAD" |
|
||||
grep -v '^- Track upstream ' || true
|
||||
)"
|
||||
fi
|
||||
if [ -z "$changes" ]; then
|
||||
changes="- Rebuilt on Modrinth App $UPSTREAM, with the patches unchanged."
|
||||
fi
|
||||
if [ -n "$previous" ]; then
|
||||
changes="$changes
|
||||
|
||||
[Everything since $previous](https://github.com/$GITHUB_REPOSITORY/compare/$previous...$TAG)"
|
||||
fi
|
||||
|
||||
notes="$(cat <<EOF
|
||||
Modrinth Enhanced $TAG, built from [Modrinth App $UPSTREAM](https://github.com/modrinth/code/releases/tag/$UPSTREAM).
|
||||
|
||||
Same app as upstream, with the patches in \`patches/\` applied:
|
||||
## What changed
|
||||
|
||||
$changes
|
||||
|
||||
Modrinth's own changes are in the [Modrinth App $UPSTREAM release notes](https://github.com/modrinth/code/releases/tag/$UPSTREAM).
|
||||
|
||||
<details>
|
||||
<summary>The $patch_count patches in this build</summary>
|
||||
|
||||
$patches
|
||||
|
||||
</details>
|
||||
|
||||
It keeps using the same data directory as the official Modrinth App,
|
||||
so instances, settings and accounts carry over. Do not run both at
|
||||
the same time.
|
||||
|
||||
See the upstream release notes for everything else that changed.
|
||||
EOF
|
||||
)"
|
||||
# The tag goes on the commit that was built, not on whatever main is
|
||||
|
||||
@@ -12,3 +12,19 @@ Keep everything short — replies, explanations, comments, docs.
|
||||
- Body only when something genuinely cannot be inferred from the diff.
|
||||
- Never add `Co-Authored-By`, "Generated with" or any other AI attribution to commits or PR descriptions.
|
||||
- Exception: the commits in `build/upstream` that `patches/` is exported from. Their subjects become the patch file names, so they stay plain and descriptive.
|
||||
|
||||
## Dev builds
|
||||
|
||||
Never build over an installed Modrinth Enhanced. The stable app on this machine is installed
|
||||
separately, from the AUR, and stays untouched.
|
||||
|
||||
- `DEV=1 scripts/build.sh` builds "Modrinth Enhanced (dev)". Its bundle identifier is
|
||||
`ModrinthAppDev`, so it has a data directory of its own and shares no instances, accounts or
|
||||
settings with the stable app, and it has no updater.
|
||||
- Install it beside the stable app, never over it: `~/.local/bin/Modrinth.Enhanced.dev.AppImage`
|
||||
with a desktop entry of its own.
|
||||
- `FAST=1` cuts the build to about a third of the time, at the cost of a few megabytes. Use it for
|
||||
anything that is not meant to be release-like: `DEV=1 FAST=1 scripts/build.sh`.
|
||||
- Build after every change that touches the app, and install it over the last dev build, so it can
|
||||
be tried out right away. Neither pnpm nor a JDK is on PATH by default:
|
||||
`PATH="$PWD/build/bin:$PWD/build/jdk-17.0.20.1+1/bin:$PATH" JAVA_HOME="$PWD/build/jdk-17.0.20.1+1" DEV=1 FAST=1 scripts/build.sh`
|
||||
@@ -12,3 +12,19 @@ Keep everything short — replies, explanations, comments, docs.
|
||||
- Body only when something genuinely cannot be inferred from the diff.
|
||||
- Never add `Co-Authored-By`, "Generated with" or any other AI attribution to commits or PR descriptions.
|
||||
- Exception: the commits in `build/upstream` that `patches/` is exported from. Their subjects become the patch file names, so they stay plain and descriptive.
|
||||
|
||||
## Dev builds
|
||||
|
||||
Never build over an installed Modrinth Enhanced. The stable app on this machine is installed
|
||||
separately, from the AUR, and stays untouched.
|
||||
|
||||
- `DEV=1 scripts/build.sh` builds "Modrinth Enhanced (dev)". Its bundle identifier is
|
||||
`ModrinthAppDev`, so it has a data directory of its own and shares no instances, accounts or
|
||||
settings with the stable app, and it has no updater.
|
||||
- Install it beside the stable app, never over it: `~/.local/bin/Modrinth.Enhanced.dev.AppImage`
|
||||
with a desktop entry of its own.
|
||||
- `FAST=1` cuts the build to about a third of the time, at the cost of a few megabytes. Use it for
|
||||
anything that is not meant to be release-like: `DEV=1 FAST=1 scripts/build.sh`.
|
||||
- Build after every change that touches the app, and install it over the last dev build, so it can
|
||||
be tried out right away. Neither pnpm nor a JDK is on PATH by default:
|
||||
`PATH="$PWD/build/bin:$PWD/build/jdk-17.0.20.1+1/bin:$PATH" JAVA_HOME="$PWD/build/jdk-17.0.20.1+1" DEV=1 FAST=1 scripts/build.sh`
|
||||
@@ -60,6 +60,9 @@ sudo dnf install "$(curl -fsSL https://api.github.com/repos/Felitendo/Modrinth-E
|
||||
| `0021-Show-and-change-skins-of-custom-...` | A custom server account's skin is shown and changed on the skin page, and its head in the account list. |
|
||||
| `0022-Sign-in-to-custom-servers-in-...` | Servers with Yggdrasil Connect, such as LittleSkin, sign in on their own page, with two-factor authentication. |
|
||||
| `0023-Browse-LittleSkin-s-skin-library` | LittleSkin's skin library in the Browse tab, with search, sorting and likes. |
|
||||
| `0024-Offer-the-launchers-instances-...` | Every launcher the importers read is offered, with the folder it keeps instances in. |
|
||||
| `0025-Arrange-the-left-sidebar` | The buttons in the left sidebar are reordered and hidden in a settings tab of their own. |
|
||||
| `0026-Delete-an-instance-from-the-sidebar` | Right-clicking an instance in the sidebar can delete it. |
|
||||
|
||||
### Offline accounts
|
||||
|
||||
@@ -127,12 +130,17 @@ on the server's website: it only knows the one uploaded there, and taking it off
|
||||
|
||||
### Sidebar and news
|
||||
|
||||
The Modrinth Servers button in the left sidebar can be switched off under
|
||||
Settings > Features > Sidebar.
|
||||
Settings > Sidebar has the buttons above the instances — Home, Discover, Skins, Screenshots and
|
||||
Modrinth Hosting — in a list: move one up or down, hide it, or put the usual order back.
|
||||
Right-clicking one of them in the sidebar hides it or opens that tab. Where it all sits is kept on
|
||||
this machine, since it is about how the app looks here.
|
||||
|
||||
Right-clicking an instance in the sidebar plays or stops it, opens it or its folder, and deletes it,
|
||||
which asks first and is refused while the instance is running.
|
||||
|
||||
The news section in the right sidebar folds away by clicking its heading. The right sidebar itself
|
||||
folds away with the arrow button in the title bar, which upstream only shows once "Hide right
|
||||
sidebar" is turned on in settings. Both remember what they were set to across restarts, and the
|
||||
folds away with the arrow button in the title bar. Upstream shows that button only once "Hide right
|
||||
sidebar" is turned on in settings; here it is always there and that setting is gone. Both remember what they were set to across restarts, and the
|
||||
title bar button briefly shows a check once its state is saved; the first time, a short note
|
||||
explains this. On pages that need the sidebar, such as the mod browser, the button stays in place,
|
||||
greyed out. While the sidebar is folded away, the
|
||||
@@ -181,6 +189,11 @@ you open there is previewed and can be added.
|
||||
|
||||
### Instances and crashes
|
||||
|
||||
Importing instances offers every launcher the importers read — Prism Launcher, MultiMC, PolyMC,
|
||||
ATLauncher, GDLauncher and CurseForge — whether they are installed or not, each with the folder it
|
||||
keeps its instances in on this system, Flatpak and macOS bundles included, and each with its own
|
||||
logo. Upstream listed only the launchers it found, and left the rest to a path typed in by hand.
|
||||
|
||||
A running instance can be started again from the button next to Stop, as whichever account is
|
||||
selected, and the Logs tab then shows a console for each copy.
|
||||
|
||||
@@ -236,6 +249,13 @@ scripts/check.sh # assert the patches still do what they claim
|
||||
scripts/build.sh # build installers into build/artifacts
|
||||
```
|
||||
|
||||
`FAST=1 scripts/build.sh` builds with thin link-time optimization instead of the full one, which
|
||||
roughly halves the Rust build at the cost of a few megabytes. Releases are built without it.
|
||||
|
||||
`DEV=1 scripts/build.sh` builds "Modrinth Enhanced (dev)" instead, under the bundle identifier
|
||||
`ModrinthAppDev`. It has a data directory of its own and no updater, so it can be installed and run
|
||||
beside a release without sharing instances, accounts or settings with it.
|
||||
|
||||
`build/` is scratch space and is never committed.
|
||||
|
||||
## Working on the patches
|
||||
@@ -273,7 +293,8 @@ scripts/prepare.sh
|
||||
- **Upstream release** (`.github/workflows/upstream-release.yml`) runs daily. If Modrinth has
|
||||
published a newer release than `upstream.txt`, it rebases the patches onto it, rebuilds and — only if every
|
||||
platform built and every check passed — commits the bump, tags it with the upstream version and
|
||||
publishes a release with the installers.
|
||||
publishes a release with the installers. Its notes are the commits since the last release, with
|
||||
the patch list folded away underneath.
|
||||
- **Revisions** come from the same run: when `patches/` or `scripts/` changed since the last
|
||||
release of that upstream version, it is released again as `v0.21.2-2`, `v0.21.2-3` and so on.
|
||||
The app and installers still carry the upstream version: RPM and the Windows installers do not
|
||||
|
||||
@@ -30,14 +30,15 @@ page headed "Sync features across devices" and would otherwise look like
|
||||
it syncs. The other two are not settings entries - a heading and a title
|
||||
bar button - so nothing there promises anything.
|
||||
---
|
||||
apps/app-frontend/src/App.vue | 184 ++++++++++++++++--
|
||||
apps/app-frontend/src/App.vue | 180 ++++++++++++++++--
|
||||
.../ui/settings/display/BehaviorSettings.vue | 18 --
|
||||
.../ui/settings/display/FeaturesSettings.vue | 28 +++
|
||||
.../src/composables/use-app-settings.ts | 4 +
|
||||
packages/app-lib/src/state/settings.rs | 4 +
|
||||
4 files changed, 205 insertions(+), 15 deletions(-)
|
||||
5 files changed, 201 insertions(+), 33 deletions(-)
|
||||
|
||||
diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue
|
||||
index d750a5f..c47484d 100644
|
||||
index d750a5f..76da736 100644
|
||||
--- a/apps/app-frontend/src/App.vue
|
||||
+++ b/apps/app-frontend/src/App.vue
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
@@ -65,14 +66,14 @@ index d750a5f..c47484d 100644
|
||||
NewsArticleCard,
|
||||
NotificationPanel,
|
||||
PopupNotificationPanel,
|
||||
@@ -254,12 +258,46 @@ const credentials = ref()
|
||||
@@ -254,12 +258,42 @@ const credentials = ref()
|
||||
const storedModrinthAccounts = ref([])
|
||||
let credentialsRefreshId = 0
|
||||
const sidebarToggled = ref(true)
|
||||
+// Whether the right sidebar is folded away, remembered across restarts in the
|
||||
+// feature flags. The title bar button below is what normally sets it; the
|
||||
+// "Hide right sidebar" setting still seeds it, so turning that on folds the
|
||||
+// sidebar away as it always did.
|
||||
+// feature flags. The title bar button below is the only thing that sets it:
|
||||
+// upstream's "Hide right sidebar" setting, which used to be what showed that
|
||||
+// button, is gone.
|
||||
watch(
|
||||
- () => appSettings.toggleSidebar,
|
||||
- (toggleSidebar) => {
|
||||
@@ -82,10 +83,6 @@ index d750a5f..c47484d 100644
|
||||
+ sidebarToggled.value = !collapsed
|
||||
},
|
||||
+ { immediate: true },
|
||||
+)
|
||||
+watch(
|
||||
+ () => appSettings.toggleSidebar,
|
||||
+ (hide) => setSidebarCollapsed(hide),
|
||||
)
|
||||
+
|
||||
+// Briefly shows a check on the fold button once the state is saved, and the
|
||||
@@ -115,7 +112,7 @@ index d750a5f..c47484d 100644
|
||||
const forceSidebar = computed(
|
||||
() =>
|
||||
route.path.startsWith('/browse') ||
|
||||
@@ -358,6 +396,21 @@ useQuery({
|
||||
@@ -358,6 +392,21 @@ useQuery({
|
||||
// ad and the gradient being drawn above it.
|
||||
const showAd = computed(() => false)
|
||||
const adConsentAvailable = computed(() => false)
|
||||
@@ -137,7 +134,7 @@ index d750a5f..c47484d 100644
|
||||
providePageContext({
|
||||
hierarchicalSidebarAvailable: ref(true),
|
||||
showAds: showAd,
|
||||
@@ -588,6 +641,25 @@ const messages = defineMessages({
|
||||
@@ -588,6 +637,25 @@ const messages = defineMessages({
|
||||
goBack: { id: 'app.navigation.go-back', defaultMessage: 'Go back' },
|
||||
goForward: { id: 'app.navigation.go-forward', defaultMessage: 'Go forward' },
|
||||
nextImage: { id: 'app.navigation.next-image', defaultMessage: 'Next image' },
|
||||
@@ -163,7 +160,7 @@ index d750a5f..c47484d 100644
|
||||
updateDownloadMissingVersion: {
|
||||
id: 'app.update.download-error.missing-version',
|
||||
defaultMessage: 'Failed to download update: no version available',
|
||||
@@ -2252,6 +2324,7 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
|
||||
@@ -2252,6 +2320,7 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
|
||||
<ImageIcon />
|
||||
</NavButton>
|
||||
<NavButton
|
||||
@@ -171,7 +168,7 @@ index d750a5f..c47484d 100644
|
||||
v-tooltip.right="formatMessage(messages.modrinthHosting)"
|
||||
to="/hosting/manage"
|
||||
:is-primary="(r) => r.path === '/hosting/manage' || r.path === '/hosting/manage/'"
|
||||
@@ -2377,16 +2450,42 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
|
||||
@@ -2377,16 +2446,42 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
|
||||
<Breadcrumbs />
|
||||
</div>
|
||||
<section data-tauri-drag-region class="flex shrink-0 ml-auto items-center">
|
||||
@@ -223,7 +220,7 @@ index d750a5f..c47484d 100644
|
||||
<div class="flex mr-3">
|
||||
<Suspense>
|
||||
<AppActionBar />
|
||||
@@ -2502,10 +2601,18 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
|
||||
@@ -2502,10 +2597,18 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
|
||||
class="p-4 border-0 border-b-[1px] border-[--brand-gradient-border] border-solid"
|
||||
/>
|
||||
<div v-if="news && news.length > 0" class="p-4 flex flex-col items-center">
|
||||
@@ -245,7 +242,7 @@ index d750a5f..c47484d 100644
|
||||
<NewsArticleCard
|
||||
v-for="(item, index) in news"
|
||||
:key="`news-${index}`"
|
||||
@@ -2531,6 +2638,21 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
|
||||
@@ -2531,6 +2634,21 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
|
||||
<I18nDebugPanel />
|
||||
<NotificationPanel :has-sidebar="sidebarVisible" />
|
||||
<PopupNotificationPanel :has-sidebar="sidebarVisible" />
|
||||
@@ -267,7 +264,7 @@ index d750a5f..c47484d 100644
|
||||
<ErrorModal ref="errorModal" />
|
||||
<MinecraftAuthErrorModal ref="minecraftAuthErrorModal" />
|
||||
<MinecraftRequiredModal ref="minecraftRequiredModal" />
|
||||
@@ -2743,6 +2865,38 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
|
||||
@@ -2743,6 +2861,38 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
|
||||
display: contents;
|
||||
}
|
||||
|
||||
@@ -306,6 +303,42 @@ index d750a5f..c47484d 100644
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.nav-button-animated-enter-active {
|
||||
transition: all 0.5s cubic-bezier(0.15, 1.4, 0.64, 0.96);
|
||||
diff --git a/apps/app-frontend/src/components/ui/settings/display/BehaviorSettings.vue b/apps/app-frontend/src/components/ui/settings/display/BehaviorSettings.vue
|
||||
index 55622d7..6c6ba20 100644
|
||||
--- a/apps/app-frontend/src/components/ui/settings/display/BehaviorSettings.vue
|
||||
+++ b/apps/app-frontend/src/components/ui/settings/display/BehaviorSettings.vue
|
||||
@@ -80,14 +80,6 @@ const messages = defineMessages({
|
||||
id: 'app.appearance-settings.default-landing-page.library',
|
||||
defaultMessage: 'Library',
|
||||
},
|
||||
- toggleSidebarTitle: {
|
||||
- id: 'app.appearance-settings.toggle-sidebar.title',
|
||||
- defaultMessage: 'Hide right sidebar',
|
||||
- },
|
||||
- toggleSidebarDescription: {
|
||||
- id: 'app.appearance-settings.toggle-sidebar.description',
|
||||
- defaultMessage: 'Hide the right sidebar by default and add a button to show or hide it.',
|
||||
- },
|
||||
compactModeTitle: {
|
||||
id: 'app.appearance-settings.compact-mode.title',
|
||||
defaultMessage: 'Compact mode',
|
||||
@@ -288,16 +280,6 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
<Toggle id="minimize-launcher" v-model="current.minimizeApp" />
|
||||
</div>
|
||||
-
|
||||
- <div class="flex items-center justify-between gap-4">
|
||||
- <div>
|
||||
- <h3 class="m-0 text-lg font-semibold text-contrast">
|
||||
- {{ formatMessage(messages.toggleSidebarTitle) }}
|
||||
- </h3>
|
||||
- <p class="m-0 mt-1">{{ formatMessage(messages.toggleSidebarDescription) }}</p>
|
||||
- </div>
|
||||
- <Toggle id="toggle-sidebar" v-model="current.hideRightSidebar" />
|
||||
- </div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
diff --git a/apps/app-frontend/src/components/ui/settings/display/FeaturesSettings.vue b/apps/app-frontend/src/components/ui/settings/display/FeaturesSettings.vue
|
||||
index 1d3e379..ae88550 100644
|
||||
--- a/apps/app-frontend/src/components/ui/settings/display/FeaturesSettings.vue
|
||||
|
||||
@@ -11,10 +11,10 @@ Subject: [PATCH] Round the window corners on Linux
|
||||
4 files changed, 53 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue
|
||||
index c47484d..556ef25 100644
|
||||
index 76da736..dcae1f1 100644
|
||||
--- a/apps/app-frontend/src/App.vue
|
||||
+++ b/apps/app-frontend/src/App.vue
|
||||
@@ -547,6 +547,16 @@ watch([os, isFullscreen], ([osName, fullscreen]) => {
|
||||
@@ -543,6 +543,16 @@ watch([os, isFullscreen], ([osName, fullscreen]) => {
|
||||
document.documentElement.classList.toggle('mac-traffic-lights', osName === 'MacOS' && !fullscreen)
|
||||
})
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ Subject: [PATCH] Scroll with the middle mouse button
|
||||
create mode 100644 apps/app-frontend/src/helpers/autoscroll.ts
|
||||
|
||||
diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue
|
||||
index 556ef25..0ffddff 100644
|
||||
index dcae1f1..e18904b 100644
|
||||
--- a/apps/app-frontend/src/App.vue
|
||||
+++ b/apps/app-frontend/src/App.vue
|
||||
@@ -126,6 +126,7 @@ import {
|
||||
@@ -22,7 +22,7 @@ index 556ef25..0ffddff 100644
|
||||
import { get_user, get_user_many, get_version } from '@/helpers/cache.js'
|
||||
import { gameSettingsQueryOptions } from '@/helpers/game-options'
|
||||
import { install_create_modpack_instance, install_get_modpack_preview } from '@/helpers/install'
|
||||
@@ -607,11 +608,18 @@ onMounted(async () => {
|
||||
@@ -603,11 +604,18 @@ onMounted(async () => {
|
||||
document.querySelector('body').addEventListener('auxclick', handleAuxClick)
|
||||
document.querySelector('body').addEventListener('contextmenu', handleContextMenu)
|
||||
document.addEventListener('fullscreenchange', handleFullscreenChange)
|
||||
|
||||
@@ -22,7 +22,7 @@ menu closing.
|
||||
create mode 100644 apps/app-frontend/src/components/ui/TitleBarAccountSwitcher.vue
|
||||
|
||||
diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue
|
||||
index 0ffddff..5cbe7d8 100644
|
||||
index e18904b..4d95408 100644
|
||||
--- a/apps/app-frontend/src/App.vue
|
||||
+++ b/apps/app-frontend/src/App.vue
|
||||
@@ -79,6 +79,7 @@ import { computed, nextTick, onMounted, onUnmounted, provide, ref, watch } from
|
||||
@@ -33,7 +33,7 @@ index 0ffddff..5cbe7d8 100644
|
||||
import AppActionBar from '@/components/ui/AppActionBar.vue'
|
||||
import Breadcrumbs from '@/components/ui/Breadcrumbs.vue'
|
||||
import ErrorModal from '@/components/ui/ErrorModal.vue'
|
||||
@@ -2509,6 +2510,13 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
|
||||
@@ -2505,6 +2506,13 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
|
||||
<AppActionBar />
|
||||
</Suspense>
|
||||
</div>
|
||||
|
||||
@@ -19,10 +19,10 @@ get a notice with a button to the release on GitHub.
|
||||
3 files changed, 76 insertions(+), 21 deletions(-)
|
||||
|
||||
diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue
|
||||
index 5cbe7d8..dfdcdb1 100644
|
||||
index 4d95408..6539d4a 100644
|
||||
--- a/apps/app-frontend/src/App.vue
|
||||
+++ b/apps/app-frontend/src/App.vue
|
||||
@@ -1888,9 +1888,13 @@ const updatePopupMessages = defineMessages({
|
||||
@@ -1884,9 +1884,13 @@ const updatePopupMessages = defineMessages({
|
||||
defaultMessage: `Modrinth App v{version} has finished downloading. Reload to update now, or automatically when you close Modrinth App.`,
|
||||
},
|
||||
linuxBody: {
|
||||
@@ -38,7 +38,7 @@ index 5cbe7d8..dfdcdb1 100644
|
||||
},
|
||||
reload: {
|
||||
id: 'app.update-popup.reload',
|
||||
@@ -2065,28 +2069,47 @@ async function checkUpdates() {
|
||||
@@ -2061,28 +2065,47 @@ async function checkUpdates() {
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,899 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Modrinth Enhanced <patches@modrinth-enhanced.invalid>
|
||||
Date: Fri, 18 Sep 2026 00:03:21 +0200
|
||||
Subject: [PATCH] Offer the launchers instances can be imported from
|
||||
|
||||
Importing only listed the launchers that were found and had instances.
|
||||
Anyone whose launcher sits somewhere else, or who has not installed it
|
||||
where the launcher usually goes, was left with an empty list and a path
|
||||
to type in by hand.
|
||||
|
||||
Every launcher the importers read is offered now, installed or not:
|
||||
Prism Launcher, MultiMC, PolyMC, ATLauncher, GDLauncher and CurseForge,
|
||||
each with the folder it keeps its instances in on this system, Flatpak
|
||||
and macOS bundles included. Picking one reads that folder; with nothing
|
||||
there, its path lands in the field to be corrected. PolyMC and other
|
||||
MultiMC forks are read by Prism's importer, so the name shown and the
|
||||
importer used are no longer the same thing.
|
||||
|
||||
The initials in a badge tell the entries apart without carrying anyone
|
||||
else's logo around.
|
||||
---
|
||||
apps/app-frontend/src/helpers/import.js | 6 +
|
||||
.../src/providers/setup/creation-modal.ts | 7 +-
|
||||
.../src/providers/setup/instance-import.ts | 26 +++-
|
||||
apps/app/build.rs | 1 +
|
||||
apps/app/src/api/import.rs | 8 +
|
||||
packages/app-lib/src/api/pack/import/mod.rs | 146 ++++++++++++++----
|
||||
packages/ui/src/assets/launchers/README.md | 16 ++
|
||||
.../ui/src/assets/launchers/atlauncher.svg | 1 +
|
||||
.../ui/src/assets/launchers/gdlauncher.png | Bin 0 -> 7218 bytes
|
||||
packages/ui/src/assets/launchers/multimc.svg | 1 +
|
||||
packages/ui/src/assets/launchers/polymc.svg | 21 +++
|
||||
.../src/assets/launchers/prism-launcher.svg | 57 +++++++
|
||||
.../components/ImportInstanceStage.vue | 109 ++++++++++++-
|
||||
.../components/LauncherMark.vue | 55 +++++++
|
||||
packages/ui/src/providers/instance-import.ts | 14 ++
|
||||
15 files changed, 426 insertions(+), 42 deletions(-)
|
||||
create mode 100644 packages/ui/src/assets/launchers/README.md
|
||||
create mode 100644 packages/ui/src/assets/launchers/atlauncher.svg
|
||||
create mode 100644 packages/ui/src/assets/launchers/gdlauncher.png
|
||||
create mode 100644 packages/ui/src/assets/launchers/multimc.svg
|
||||
create mode 100644 packages/ui/src/assets/launchers/polymc.svg
|
||||
create mode 100644 packages/ui/src/assets/launchers/prism-launcher.svg
|
||||
create mode 100644 packages/ui/src/components/flows/creation-flow-modal/components/LauncherMark.vue
|
||||
|
||||
diff --git a/apps/app-frontend/src/helpers/import.js b/apps/app-frontend/src/helpers/import.js
|
||||
index 34cf250..bfc728e 100644
|
||||
--- a/apps/app-frontend/src/helpers/import.js
|
||||
+++ b/apps/app-frontend/src/helpers/import.js
|
||||
@@ -45,6 +45,12 @@ export async function is_valid_importable_instance(instanceFolder, launcherType)
|
||||
})
|
||||
}
|
||||
|
||||
+/// Gets every launcher instances can be imported from, with the folder each one
|
||||
+/// uses on this system, whether it is installed or not
|
||||
+export async function launcher_suggestions() {
|
||||
+ return await invoke('plugin:import|launcher_suggestions')
|
||||
+}
|
||||
+
|
||||
/// Gets the default path for the given launcher type
|
||||
/// null if it can't be found or doesn't exist
|
||||
/// eg: get_default_launcher_path("MultiMC")
|
||||
diff --git a/apps/app-frontend/src/providers/setup/creation-modal.ts b/apps/app-frontend/src/providers/setup/creation-modal.ts
|
||||
index 964b17c..53ba797 100644
|
||||
--- a/apps/app-frontend/src/providers/setup/creation-modal.ts
|
||||
+++ b/apps/app-frontend/src/providers/setup/creation-modal.ts
|
||||
@@ -113,7 +113,12 @@ export function setupCreationModal(
|
||||
if (!launcher || instanceSet.size === 0) continue
|
||||
for (const name of instanceSet) {
|
||||
importCount += 1
|
||||
- const job = await import_instance(launcher.name, launcher.path, name).catch(handleError)
|
||||
+ // PolyMC and other forks are read by another launcher's importer.
|
||||
+ const job = await import_instance(
|
||||
+ launcher.launcher ?? launcher.name,
|
||||
+ launcher.path,
|
||||
+ name,
|
||||
+ ).catch(handleError)
|
||||
if (job) {
|
||||
importedInstanceId = installJobInstanceId(job)
|
||||
}
|
||||
diff --git a/apps/app-frontend/src/providers/setup/instance-import.ts b/apps/app-frontend/src/providers/setup/instance-import.ts
|
||||
index 851e44d..752832c 100644
|
||||
--- a/apps/app-frontend/src/providers/setup/instance-import.ts
|
||||
+++ b/apps/app-frontend/src/providers/setup/instance-import.ts
|
||||
@@ -2,9 +2,9 @@ import { type AbstractWebNotificationManager, provideInstanceImport } from '@mod
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
|
||||
import {
|
||||
- get_default_launcher_path,
|
||||
get_importable_instances,
|
||||
import_instance,
|
||||
+ launcher_suggestions,
|
||||
} from '@/helpers/import.js'
|
||||
|
||||
export function setupInstanceImportProvider(notificationManager: AbstractWebNotificationManager) {
|
||||
@@ -12,15 +12,18 @@ export function setupInstanceImportProvider(notificationManager: AbstractWebNoti
|
||||
|
||||
provideInstanceImport({
|
||||
async getDetectedLaunchers() {
|
||||
- const launcherNames = ['MultiMC', 'GDLauncher', 'ATLauncher', 'Curseforge', 'PrismLauncher']
|
||||
const launchers = []
|
||||
- for (const name of launcherNames) {
|
||||
+ for (const suggestion of await launcher_suggestions()) {
|
||||
+ if (!suggestion.installed || !suggestion.path) continue
|
||||
try {
|
||||
- const path = await get_default_launcher_path(name)
|
||||
- if (!path) continue
|
||||
- const instances = await get_importable_instances(name, path)
|
||||
+ const instances = await get_importable_instances(suggestion.launcher, suggestion.path)
|
||||
if (instances?.length > 0) {
|
||||
- launchers.push({ name, path, instances })
|
||||
+ launchers.push({
|
||||
+ name: suggestion.name,
|
||||
+ launcher: suggestion.launcher,
|
||||
+ path: suggestion.path,
|
||||
+ instances,
|
||||
+ })
|
||||
}
|
||||
} catch {
|
||||
// Skip launchers that fail detection
|
||||
@@ -28,6 +31,15 @@ export function setupInstanceImportProvider(notificationManager: AbstractWebNoti
|
||||
}
|
||||
return launchers
|
||||
},
|
||||
+ async getLauncherSuggestions() {
|
||||
+ const suggestions = await launcher_suggestions()
|
||||
+ return suggestions.map((suggestion) => ({
|
||||
+ name: suggestion.name,
|
||||
+ launcher: suggestion.launcher,
|
||||
+ path: suggestion.path ?? '',
|
||||
+ installed: suggestion.installed,
|
||||
+ }))
|
||||
+ },
|
||||
async getImportableInstances(launcherName: string, path: string) {
|
||||
return (await get_importable_instances(launcherName, path)) ?? []
|
||||
},
|
||||
diff --git a/apps/app/build.rs b/apps/app/build.rs
|
||||
index 8cb21f2..8e8a0f8 100644
|
||||
--- a/apps/app/build.rs
|
||||
+++ b/apps/app/build.rs
|
||||
@@ -108,6 +108,7 @@ fn main() {
|
||||
"get_importable_instances",
|
||||
"is_valid_importable_instance",
|
||||
"get_default_launcher_path",
|
||||
+ "launcher_suggestions",
|
||||
])
|
||||
.default_permission(
|
||||
DefaultPermissionRule::AllowAllCommands,
|
||||
diff --git a/apps/app/src/api/import.rs b/apps/app/src/api/import.rs
|
||||
index 773fcb2..cda652f 100644
|
||||
--- a/apps/app/src/api/import.rs
|
||||
+++ b/apps/app/src/api/import.rs
|
||||
@@ -11,6 +11,7 @@ pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
get_importable_instances,
|
||||
is_valid_importable_instance,
|
||||
get_default_launcher_path,
|
||||
+ launcher_suggestions,
|
||||
])
|
||||
.build()
|
||||
}
|
||||
@@ -39,6 +40,13 @@ pub async fn is_valid_importable_instance(
|
||||
)
|
||||
}
|
||||
|
||||
+/// Returns every launcher that can be imported from, installed or not, with
|
||||
+/// the folder each one keeps its instances in on this system.
|
||||
+#[tauri::command]
|
||||
+pub async fn launcher_suggestions() -> Result<Vec<import::LauncherSuggestion>> {
|
||||
+ Ok(import::launcher_suggestions())
|
||||
+}
|
||||
+
|
||||
/// Returns the default path for the given launcher type
|
||||
/// None if it can't be found or doesn't exist
|
||||
#[tauri::command]
|
||||
diff --git a/packages/app-lib/src/api/pack/import/mod.rs b/packages/app-lib/src/api/pack/import/mod.rs
|
||||
index 72f315d..15da6da 100644
|
||||
--- a/packages/app-lib/src/api/pack/import/mod.rs
|
||||
+++ b/packages/app-lib/src/api/pack/import/mod.rs
|
||||
@@ -247,36 +247,130 @@ async fn import_instance_inner(
|
||||
pub fn get_default_launcher_path(
|
||||
r#type: ImportLauncherType,
|
||||
) -> Option<PathBuf> {
|
||||
- let path = match r#type {
|
||||
- ImportLauncherType::MultiMC => {
|
||||
- return find_multimc_path();
|
||||
- }
|
||||
- ImportLauncherType::PrismLauncher => {
|
||||
- Some(dirs::data_dir()?.join("PrismLauncher"))
|
||||
- }
|
||||
- ImportLauncherType::ATLauncher => {
|
||||
- Some(dirs::data_dir()?.join("ATLauncher"))
|
||||
- }
|
||||
- ImportLauncherType::GDLauncher => {
|
||||
- Some(dirs::data_dir()?.join("gdlauncher_next"))
|
||||
- }
|
||||
- ImportLauncherType::Curseforge => {
|
||||
- let home = dirs::home_dir()?;
|
||||
- let primary = home.join("curseforge").join("minecraft");
|
||||
- if primary.exists() {
|
||||
- return Some(primary);
|
||||
+ let name = match r#type {
|
||||
+ ImportLauncherType::MultiMC => "MultiMC",
|
||||
+ ImportLauncherType::PrismLauncher => "Prism Launcher",
|
||||
+ ImportLauncherType::ATLauncher => "ATLauncher",
|
||||
+ ImportLauncherType::GDLauncher => "GDLauncher",
|
||||
+ ImportLauncherType::Curseforge => "CurseForge",
|
||||
+ ImportLauncherType::Unknown => return None,
|
||||
+ };
|
||||
+
|
||||
+ installed_path(r#type, name)
|
||||
+}
|
||||
+
|
||||
+/// A launcher instances can be imported from, and where it keeps them.
|
||||
+#[derive(Debug, Clone, Serialize)]
|
||||
+pub struct LauncherSuggestion {
|
||||
+ /// The importer that reads this launcher's instances.
|
||||
+ pub launcher: String,
|
||||
+ pub name: String,
|
||||
+ /// Where it is, or where it would be on this system.
|
||||
+ pub path: Option<PathBuf>,
|
||||
+ /// Whether it is actually there.
|
||||
+ pub installed: bool,
|
||||
+}
|
||||
+
|
||||
+/// The launchers worth offering, with the importer each one is read by.
|
||||
+///
|
||||
+/// PolyMC and other MultiMC forks keep their instances the way Prism does, so
|
||||
+/// that importer reads them as well.
|
||||
+const SUGGESTED_LAUNCHERS: &[(ImportLauncherType, &str)] = &[
|
||||
+ (ImportLauncherType::PrismLauncher, "Prism Launcher"),
|
||||
+ (ImportLauncherType::MultiMC, "MultiMC"),
|
||||
+ (ImportLauncherType::PrismLauncher, "PolyMC"),
|
||||
+ (ImportLauncherType::ATLauncher, "ATLauncher"),
|
||||
+ (ImportLauncherType::GDLauncher, "GDLauncher"),
|
||||
+ (ImportLauncherType::Curseforge, "CurseForge"),
|
||||
+];
|
||||
+
|
||||
+/// Every launcher that can be imported from, whether it is installed or not:
|
||||
+/// one that is not still gets the folder it would be in, to start looking from.
|
||||
+pub fn launcher_suggestions() -> Vec<LauncherSuggestion> {
|
||||
+ SUGGESTED_LAUNCHERS
|
||||
+ .iter()
|
||||
+ .map(|&(launcher, name)| {
|
||||
+ let installed = installed_path(launcher, name);
|
||||
+ LauncherSuggestion {
|
||||
+ launcher: launcher.to_string(),
|
||||
+ name: name.to_owned(),
|
||||
+ path: installed
|
||||
+ .clone()
|
||||
+ .or_else(|| launcher_paths(name).into_iter().next()),
|
||||
+ installed: installed.is_some(),
|
||||
}
|
||||
- Some(dirs::document_dir()?.join("curseforge").join("minecraft"))
|
||||
- }
|
||||
- ImportLauncherType::Unknown => None,
|
||||
+ })
|
||||
+ .collect()
|
||||
+}
|
||||
+
|
||||
+/// Where a launcher of this name actually is, if it is installed.
|
||||
+fn installed_path(r#type: ImportLauncherType, name: &str) -> Option<PathBuf> {
|
||||
+ let paths = launcher_paths(name);
|
||||
+
|
||||
+ // MultiMC is unpacked wherever the player put it, so its own config file is
|
||||
+ // what tells one of those places from any other folder of that name.
|
||||
+ if r#type == ImportLauncherType::MultiMC {
|
||||
+ return paths
|
||||
+ .into_iter()
|
||||
+ .find(|path| path.join("multimc.cfg").exists());
|
||||
+ }
|
||||
+
|
||||
+ paths.into_iter().find(|path| path.exists())
|
||||
+}
|
||||
+
|
||||
+/// Everywhere a launcher of this name keeps its data on this system, likeliest
|
||||
+/// first.
|
||||
+fn launcher_paths(name: &str) -> Vec<PathBuf> {
|
||||
+ let data = dirs::data_dir();
|
||||
+ let home = dirs::home_dir();
|
||||
+ // Flatpak gives each app a data directory of its own.
|
||||
+ let flatpak = |app: &str, folder: &str| {
|
||||
+ home.as_ref().map(|home| {
|
||||
+ home.join(".var")
|
||||
+ .join("app")
|
||||
+ .join(app)
|
||||
+ .join("data")
|
||||
+ .join(folder)
|
||||
+ })
|
||||
};
|
||||
- let path = path?;
|
||||
- if path.exists() { Some(path) } else { None }
|
||||
+
|
||||
+ match name {
|
||||
+ "Prism Launcher" => [
|
||||
+ data.map(|data| data.join("PrismLauncher")),
|
||||
+ flatpak("org.prismlauncher.PrismLauncher", "PrismLauncher"),
|
||||
+ ]
|
||||
+ .into_iter()
|
||||
+ .flatten()
|
||||
+ .collect(),
|
||||
+ "PolyMC" => [
|
||||
+ data.map(|data| data.join("PolyMC")),
|
||||
+ flatpak("org.polymc.PolyMC", "PolyMC"),
|
||||
+ ]
|
||||
+ .into_iter()
|
||||
+ .flatten()
|
||||
+ .collect(),
|
||||
+ "MultiMC" => multimc_paths(),
|
||||
+ "ATLauncher" => data
|
||||
+ .map(|data| vec![data.join("ATLauncher")])
|
||||
+ .unwrap_or_default(),
|
||||
+ "GDLauncher" => data
|
||||
+ .map(|data| vec![data.join("gdlauncher_next")])
|
||||
+ .unwrap_or_default(),
|
||||
+ "CurseForge" => [
|
||||
+ home.map(|home| home.join("curseforge").join("minecraft")),
|
||||
+ dirs::document_dir()
|
||||
+ .map(|docs| docs.join("curseforge").join("minecraft")),
|
||||
+ ]
|
||||
+ .into_iter()
|
||||
+ .flatten()
|
||||
+ .collect(),
|
||||
+ _ => Vec::new(),
|
||||
+ }
|
||||
}
|
||||
|
||||
-/// Searches common locations for a MultiMC installation.
|
||||
+/// The common locations for a MultiMC installation.
|
||||
/// MultiMC stores data in its own application directory (not a standard data dir)
|
||||
-fn find_multimc_path() -> Option<PathBuf> {
|
||||
+fn multimc_paths() -> Vec<PathBuf> {
|
||||
let mut candidates: Vec<PathBuf> = Vec::new();
|
||||
|
||||
// Linux/macOS: ~/.local/share/multimc is the typical location
|
||||
@@ -318,8 +412,6 @@ fn find_multimc_path() -> Option<PathBuf> {
|
||||
}
|
||||
|
||||
candidates
|
||||
- .into_iter()
|
||||
- .find(|p| p.join("multimc.cfg").exists())
|
||||
}
|
||||
|
||||
/// Checks if this PathBuf is a valid instance for the given launcher type
|
||||
diff --git a/packages/ui/src/assets/launchers/README.md b/packages/ui/src/assets/launchers/README.md
|
||||
new file mode 100644
|
||||
index 0000000..4c7d7a6
|
||||
--- /dev/null
|
||||
+++ b/packages/ui/src/assets/launchers/README.md
|
||||
@@ -0,0 +1,16 @@
|
||||
+# Launcher logos
|
||||
+
|
||||
+The logos of the launchers instances can be imported from, shown beside their name in the import
|
||||
+dialog.
|
||||
+
|
||||
+| File | From | Licence |
|
||||
+| -------------------- | --------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
|
||||
+| `prism-launcher.svg` | [PrismLauncher/PrismLauncher](https://github.com/PrismLauncher/PrismLauncher), `program_info` | [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/), unchanged |
|
||||
+| `polymc.svg` | [PolyMC/PolyMC](https://github.com/PolyMC/PolyMC), `program_info` | [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/), unchanged |
|
||||
+| `atlauncher.svg` | [ATLauncher/ATLauncher](https://github.com/ATLauncher/ATLauncher), `packaging/linux` | GPL-3.0, unchanged |
|
||||
+| `gdlauncher.png` | [gorilla-devs/GDLauncher](https://github.com/gorilla-devs/GDLauncher), `public/icon.png` | GPL-3.0, scaled down to 128×128 |
|
||||
+| `multimc.svg` | [MultiMC/Launcher](https://github.com/MultiMC/Launcher), `branding` | No licence given; shown to name the launcher, optimised with svgo |
|
||||
+
|
||||
+CurseForge's logo is the one Modrinth already ships in `@modrinth/assets`.
|
||||
+
|
||||
+A launcher without a logo here falls back to a badge with its initials.
|
||||
diff --git a/packages/ui/src/assets/launchers/atlauncher.svg b/packages/ui/src/assets/launchers/atlauncher.svg
|
||||
new file mode 100644
|
||||
index 0000000..5dd0000
|
||||
--- /dev/null
|
||||
+++ b/packages/ui/src/assets/launchers/atlauncher.svg
|
||||
@@ -0,0 +1 @@
|
||||
+<svg viewBox="0 0 2084 2084" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2"><g fill-rule="nonzero"><path d="M1041.67 81.38l272.437 159.032-825.246 478.685-272.438-157.971L1041.67 81.38zm87.28 371.074l274.024-159.032 463.937 271.945-276.14 153.73-461.821-266.643z" fill="#3b3b3b"/><path d="M216.42 561.126v961.081l825.247 479.746V1684.95l-551.222-321.774-1.587-644.079L216.42 561.126z" fill="#2e2e2e"/><path d="M1866.91 1517.97l-825.246 483.986v-317.003l550.164-320.714-1.058-645.139 276.14-153.73v952.6z" fill="#333"/><path d="M1590.77 719.097l-549.106 310.112v165.393l214.246-122.984v488.757l138.599-81.106V989.451l196.261-115.563V719.097z" fill="#89c236"/><path d="M488.858 719.097l1.587 644.079 152.353 90.118v-198.79l230.645 132.527v199.319l168.753 98.6v-655.741L488.858 719.097zm383.527 531.166l-227.471-131.466v-150.02l227.471 127.225v154.261z" fill="#7baf31"/></g></svg>
|
||||
\ No newline at end of file
|
||||
diff --git a/packages/ui/src/assets/launchers/gdlauncher.png b/packages/ui/src/assets/launchers/gdlauncher.png
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..9d369eb6d30a38821f97569a41fbb0f2346f7fc6
|
||||
GIT binary patch
|
||||
literal 7218
|
||||
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
|
||||
LS3j3^P6<r_Bp?1#
|
||||
|
||||
literal 0
|
||||
HcmV?d00001
|
||||
|
||||
diff --git a/packages/ui/src/assets/launchers/multimc.svg b/packages/ui/src/assets/launchers/multimc.svg
|
||||
new file mode 100644
|
||||
index 0000000..0c072ef
|
||||
--- /dev/null
|
||||
+++ b/packages/ui/src/assets/launchers/multimc.svg
|
||||
@@ -0,0 +1 @@
|
||||
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="68.267" height="68.267"><defs><linearGradient id="g"><stop offset="0" style="stop-color:#98c867;stop-opacity:1"/><stop offset="1" style="stop-color:#5c9a33;stop-opacity:1"/></linearGradient><linearGradient id="a"><stop offset="0" style="stop-color:#75b54b;stop-opacity:1"/><stop offset="1" style="stop-color:#75b54b;stop-opacity:.6"/></linearGradient><linearGradient id="b"><stop offset="0" style="stop-color:#000;stop-opacity:.8"/><stop offset="1" style="stop-color:#000;stop-opacity:.35"/></linearGradient><linearGradient xlink:href="#a" id="n" x1="6.734" x2="50.507" y1="28.511" y2="61.774" gradientTransform="matrix(1.26715 0 0 .8979 -.02 -.008)" gradientUnits="userSpaceOnUse"/><linearGradient xlink:href="#b" id="m" x1="14.312" x2="44.097" y1="9.795" y2="82.973" gradientTransform="scale(1.26715 .8979)" gradientUnits="userSpaceOnUse"/><linearGradient id="d"><stop offset="0" style="stop-color:#000;stop-opacity:.0627451"/><stop offset="1" style="stop-color:#322217;stop-opacity:.58823532"/></linearGradient><linearGradient id="f"><stop offset="0" style="stop-color:#a3704b;stop-opacity:1"/><stop offset="1" style="stop-color:#6a4a33;stop-opacity:1"/></linearGradient><linearGradient id="c"><stop offset="0" style="stop-color:#966c4a;stop-opacity:1"/><stop offset="1" style="stop-color:#593d29;stop-opacity:1"/></linearGradient><linearGradient xlink:href="#c" id="h" x1="36.546" x2="86.416" y1="33.805" y2="97.066" gradientUnits="userSpaceOnUse"/><linearGradient xlink:href="#d" id="k" x1="29.867" x2="38.4" y1="29.868" y2="38.401" gradientTransform="translate(-.001)" gradientUnits="userSpaceOnUse"/><linearGradient xlink:href="#b" id="l" x1="14.312" x2="44.097" y1="9.795" y2="82.973" gradientTransform="matrix(1.26715 0 0 .8979 -.829 -1.001)" gradientUnits="userSpaceOnUse"/><radialGradient xlink:href="#f" id="i" cx="34.133" cy="34.133" r="29.867" fx="34.133" fy="34.133" gradientTransform="translate(0 -.001)" gradientUnits="userSpaceOnUse"/><radialGradient xlink:href="#g" id="j" cx="-42.668" cy="-34.134" r="34.133" fx="-42.668" fy="-34.134" gradientTransform="matrix(1.75003 .125 -.01781 .24936 95.394 18.11)" gradientUnits="userSpaceOnUse"/></defs><g transform="translate(0 .001)"><rect width="68.267" height="68.267" rx="8.533" ry="8.533" style="fill:url(#h);fill-opacity:1;stroke:none;stroke-width:17.06666756"/><rect width="59.733" height="59.733" x="4.266" y="4.266" rx="4.267" ry="4.267" style="fill:url(#i);fill-opacity:1;stroke:none;stroke-width:14.93333435"/><path d="m4.267 4.265-.001 8.533h4.268v4.267h8.533v4.266h4.265V8.532H25.6v8.533h4.266v-4.267h4.267v8.533H38.4v-4.266h4.267v4.266h4.268v-4.266H51.2v-4.267h4.267v4.267H64v-12.8z" style="opacity:.6;fill:#593d29;fill-opacity:1;stroke:none;stroke-width:17.06666756"/><path d="M8.533-.002A8.514 8.514 0 0 0 0 8.531V12.8h4.265V8.531h4.268V12.8h8.533v4.266h4.266v-12.8h4.267v8.533h4.266V8.531h4.268v8.534h4.265v-4.266h4.268v4.266h4.267v-4.266H51.2V8.531h4.268V12.8H64V8.531h4.265a8.514 8.514 0 0 0-8.533-8.533zm-3.02 5.514" style="fill:url(#j);fill-opacity:1;stroke:none;stroke-width:17.06666756"/><path d="M8.532 0A8.514 8.514 0 0 0 0 8.531v51.2a8.516 8.516 0 0 0 8.533 8.535h51.2a8.516 8.516 0 0 0 8.533-8.535v-51.2A8.514 8.514 0 0 0 59.732 0zm0 4.267h51.2a4.257 4.257 0 0 1 4.267 4.265v51.2a4.26 4.26 0 0 1-4.267 4.267h-51.2a4.26 4.26 0 0 1-4.267-4.267v-51.2a4.257 4.257 0 0 1 4.267-4.265" style="opacity:1;fill:url(#k);fill-opacity:1;stroke:none;stroke-width:17.06666756"/></g><path d="M38.887 44.94q-1.462-1.2-3.772-4.008-3.157 4.009-5.94 5.78-3.488 2.136-8.909 2.137-6.363 0-10.56-3.374-4.43-3.54-4.43-9.386 0-5.611 4.43-9.42 3.96-3.374 10.655-3.374 3.442 0 5.986 1.002 2.97 1.102 4.997 3.007 1.885 1.704 3.771 4.008 3.159-4.008 5.94-5.779 3.489-2.137 8.91-2.137 6.363 0 10.56 3.373 4.43 3.54 4.43 9.387 0 5.61-4.43 9.42-3.96 3.373-10.655 3.373-3.442 0-5.986-1.002-2.546-.901-4.997-3.006m-19.14-.468q7.779 0 12.446-8.017-5.988-8.751-12.445-8.751-4.715 0-7.119 2.405-2.592 2.572-2.592 5.979 0 3.741 2.592 6.046 2.64 2.338 7.119 2.338m30.735-16.702q-7.023 0-12.445 8.017 5.94 8.752 12.445 8.752 4.715 0 7.118-2.405 2.593-2.572 2.593-5.98 0-3.74-2.593-6.045-2.64-2.34-7.118-2.339" style="font-style:normal;font-variant:normal;font-weight:400;font-stretch:normal;font-size:76.18933868px;line-height:125%;font-family:"DejaVu Sans";-inkscape-font-specification:"DejaVu Sans";letter-spacing:0;word-spacing:0;fill:url(#l);fill-opacity:1;stroke:none;stroke-width:1.06666672;opacity:.5"/><path d="M39.715 45.942q-1.461-1.203-3.77-4.008-3.16 4.008-5.94 5.779-3.489 2.137-8.91 2.137-6.364 0-10.56-3.373-4.43-3.54-4.43-9.387 0-5.61 4.43-9.42 3.96-3.373 10.654-3.373 3.441 0 5.987 1.002 2.97 1.103 4.997 3.006 1.886 1.704 3.771 4.009 3.159-4.009 5.94-5.78 3.488-2.137 8.91-2.137 6.363 0 10.559 3.374 4.43 3.54 4.43 9.386 0 5.611-4.43 9.42-3.96 3.374-10.654 3.374-3.441 0-5.987-1.003-2.546-.9-4.997-3.006m-19.139-.467q7.779
Line truncated
|
||||
\ No newline at end of file
|
||||
diff --git a/packages/ui/src/assets/launchers/polymc.svg b/packages/ui/src/assets/launchers/polymc.svg
|
||||
new file mode 100644
|
||||
index 0000000..c192d50
|
||||
--- /dev/null
|
||||
+++ b/packages/ui/src/assets/launchers/polymc.svg
|
||||
@@ -0,0 +1,21 @@
|
||||
+<?xml version="1.0" encoding="UTF-8"?>
|
||||
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
+<svg width="64" height="64" version="1.1" viewBox="0 0 16.933 16.933" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
+ <defs>
|
||||
+ <linearGradient id="linearGradient84726" x1="4.4979" x2="12.435" y1="3.8011" y2="9.5681" gradientUnits="userSpaceOnUse">
|
||||
+ <stop stop-color="#88b858" offset="0"/>
|
||||
+ <stop stop-color="#72b147" offset=".5"/>
|
||||
+ <stop stop-color="#5a9a30" offset="1"/>
|
||||
+ </linearGradient>
|
||||
+ </defs>
|
||||
+ <g>
|
||||
+ <path d="m3.561 16.016s0-3.5642 4.9056-3.5642c4.9069 0 4.9056 3.5642 4.9056 3.5642z" fill="#765338"/>
|
||||
+ <path d="m8.4667 12.452-4.9056 3.5642-3.0319-9.3311z" fill="#b7835a"/>
|
||||
+ <path d="m8.4667 12.452 7.9375-5.7669-3.0319 9.3311z" fill="#5b422d"/>
|
||||
+ <path d="m8.8308 12.716-0.36417 0.26458-0.36417-0.26458c0-0.26458 0.36417-0.26458 0.36417-0.26458s0.36417 0 0.36417 0.26458z" fill="#72b147"/>
|
||||
+ <path d="m8.4667 12.452s-2e-7 -5.7669 7.9375-5.7669l-0.22507 0.69269-0.91853 1.1965-0.91853 0.13819-0.91853 1.1965-0.91853 0.13819-0.91853 1.1965-0.91853 0.13819-0.91853 1.1965-0.91853 0.13819z" fill="#5a9a30"/>
|
||||
+ <path d="m8.1025 12.716-0.91853-0.13819-0.91853-1.1965-0.91853-0.13819-0.91853-1.1965-0.91853-0.13819-0.91853-1.1965-0.91853-0.13819-0.91853-1.1965-0.22507-0.69269c7.9375 1e-7 7.9375 5.7669 7.9375 5.7669z" fill="#88b858"/>
|
||||
+ <path d="m0.52917 6.6846 7.9375 5.7669 7.9375-5.7669-7.9375-5.7669z" fill="url(#linearGradient84726)"/>
|
||||
+ </g>
|
||||
+ <path d="m0.75424 7.3773-0.22507-0.69269 7.9375 5.7669 7.9375-5.7669-0.22507 0.69269-7.7124 5.6034z" fill-opacity="0"/>
|
||||
+</svg>
|
||||
diff --git a/packages/ui/src/assets/launchers/prism-launcher.svg b/packages/ui/src/assets/launchers/prism-launcher.svg
|
||||
new file mode 100644
|
||||
index 0000000..aeee843
|
||||
--- /dev/null
|
||||
+++ b/packages/ui/src/assets/launchers/prism-launcher.svg
|
||||
@@ -0,0 +1,57 @@
|
||||
+<?xml version="1.0" encoding="UTF-8"?>
|
||||
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
+<svg width="48" height="48" version="1.1" viewBox="0 0 12.7 12.7" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
+ <title>Prism Launcher Logo</title>
|
||||
+ <g stroke-width=".26458">
|
||||
+ <path d="m6.35 6.35" fill="#99cd61"/>
|
||||
+ <path d="m6.35 0.52917-2.5208 4.3656 2.5208 1.4552 2.5203-1.4552 0.10955-3.0996c-1.1511-0.66459-2.3388-1.2661-2.6298-1.2661z" fill="#df6277"/>
|
||||
+ <path d="m8.9798 1.7952-2.6298 4.5548 2.5203 1.4552 2.5208-4.3656c-0.14552-0.25205-1.2601-0.97975-2.4112-1.6443z" fill="#fb9168"/>
|
||||
+ <path d="m11.391 3.4396-5.041 2.9104 2.5203 1.4552 2.7389-1.4552c0-1.3292-0.072554-2.6584-0.21808-2.9104z" fill="#f3db6c"/>
|
||||
+ <path d="m6.35 6.35v2.9104h5.041c0.14552-0.25205 0.21807-1.5812 0.21808-2.9104h-5.2591z" fill="#7ab392"/>
|
||||
+ <path d="m6.35 6.35v2.9104l2.6298 1.6443c1.1511-0.66459 2.2657-1.3923 2.4112-1.6443l-5.041-2.9104z" fill="#4b7cbc"/>
|
||||
+ <path d="m6.35 6.35-2.5208 1.4552 2.5208 4.3656c0.29104 0 1.4787-0.60148 2.6298-1.2661l-2.6298-4.5548z" fill="#6f488c"/>
|
||||
+ <path d="m3.8292 4.8948-2.5203 4.3656c0.29104 0.5041 4.459 2.9104 5.041 2.9104v-5.8208l-2.5208-1.4552z" fill="#4d3f33"/>
|
||||
+ <path d="m1.309 3.4396c-0.29104 0.5041-0.29104 5.3167 0 5.8208l5.041-2.9104v-2.9104h-5.041z" fill="#7a573b"/>
|
||||
+ <path d="m6.35 0.52917c-0.58208-2e-8 -4.75 2.4063-5.041 2.9104l5.041 2.9104v-5.8208z" fill="#99cd61"/>
|
||||
+ </g>
|
||||
+ <g transform="matrix(.88 0 0 .88 -10.906 -1.2421)">
|
||||
+ <g transform="translate(13.26 2.2776)">
|
||||
+ <path transform="matrix(.96975 0 0 .96975 .1921 .1921)" d="m6.3498 2.9393c-0.34105 0-2.7827 1.4099-2.9532 1.7052l2.9532 5.1157 2.9538-5.1157c-0.17052-0.29535-2.6127-1.7052-2.9538-1.7052z" fill="#fff" stroke-width=".26458"/>
|
||||
+ </g>
|
||||
+ <path d="m16.746 6.9737 2.8639 4.9609c0.33073 0 2.6991-1.3672 2.8644-1.6536 0.16536-0.28642 0.16536-3.0209 0-3.3073l-2.8644 1.6536z" fill="#dfdfdf" stroke-width=".26458"/>
|
||||
+ </g>
|
||||
+ <path d="m3.8299 4.8948c-0.14551 0.25205-0.14553 2.6584 0 2.9104 0.14553 0.25204 2.2292 1.4552 2.5203 1.4552v-2.9104z" fill="#d6d2d2" stroke-width=".26458"/>
|
||||
+ <metadata>
|
||||
+ <rdf:RDF>
|
||||
+ <cc:Work rdf:about="">
|
||||
+ <dc:title>Prism Launcher Logo</dc:title>
|
||||
+ <dc:date>19/10/2022</dc:date>
|
||||
+ <dc:creator>
|
||||
+ <cc:Agent>
|
||||
+ <dc:title>Prism Launcher</dc:title>
|
||||
+ </cc:Agent>
|
||||
+ </dc:creator>
|
||||
+ <dc:contributor>
|
||||
+ <cc:Agent>
|
||||
+ <dc:title>AutiOne, Boba, ely, Fulmine, gon sawa, Pankakes, tobimori, Zeke</dc:title>
|
||||
+ </cc:Agent>
|
||||
+ </dc:contributor>
|
||||
+ <dc:source>https://github.com/PrismLauncher/PrismLauncher</dc:source>
|
||||
+ <dc:publisher>
|
||||
+ <cc:Agent>
|
||||
+ <dc:title>Prism Launcher</dc:title>
|
||||
+ </cc:Agent>
|
||||
+ </dc:publisher>
|
||||
+ <cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/"/>
|
||||
+ </cc:Work>
|
||||
+ <cc:License rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
|
||||
+ <cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/>
|
||||
+ <cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/>
|
||||
+ <cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/>
|
||||
+ <cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/>
|
||||
+ <cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/>
|
||||
+ <cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/>
|
||||
+ </cc:License>
|
||||
+ </rdf:RDF>
|
||||
+ </metadata>
|
||||
+</svg>
|
||||
diff --git a/packages/ui/src/components/flows/creation-flow-modal/components/ImportInstanceStage.vue b/packages/ui/src/components/flows/creation-flow-modal/components/ImportInstanceStage.vue
|
||||
index a4dfcdb..5384d29 100644
|
||||
--- a/packages/ui/src/components/flows/creation-flow-modal/components/ImportInstanceStage.vue
|
||||
+++ b/packages/ui/src/components/flows/creation-flow-modal/components/ImportInstanceStage.vue
|
||||
@@ -51,6 +51,7 @@
|
||||
@update:model-value="toggleLauncherAll(launcher, $event)"
|
||||
@click.stop
|
||||
/>
|
||||
+ <LauncherMark :name="launcher.name" />
|
||||
<span class="font-semibold text-contrast">{{ launcher.name }}</span>
|
||||
</button>
|
||||
|
||||
@@ -75,6 +76,30 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+ <!-- Launchers that were not found, with where they usually are -->
|
||||
+ <div v-if="otherLaunchers.length > 0" class="flex flex-col gap-2">
|
||||
+ <span class="text-sm font-semibold text-secondary">
|
||||
+ {{ formatMessage(messages.otherLaunchers) }}
|
||||
+ </span>
|
||||
+ <button
|
||||
+ v-for="suggestion in otherLaunchers"
|
||||
+ :key="suggestion.name"
|
||||
+ class="button-base flex w-full cursor-pointer items-center gap-3 rounded-2xl border border-solid border-surface-4 bg-surface-2 p-3 text-left"
|
||||
+ :disabled="scanning === suggestion.name"
|
||||
+ @click="useSuggestion(suggestion)"
|
||||
+ >
|
||||
+ <LauncherMark :name="suggestion.name" />
|
||||
+ <span class="flex min-w-0 flex-col">
|
||||
+ <span class="font-semibold text-contrast">{{ suggestion.name }}</span>
|
||||
+ <span class="truncate text-xs text-secondary">
|
||||
+ {{ suggestion.path || formatMessage(messages.suggestionNoPath) }}
|
||||
+ </span>
|
||||
+ </span>
|
||||
+ <SpinnerIcon v-if="scanning === suggestion.name" class="ml-auto size-5 animate-spin" />
|
||||
+ <PlusIcon v-else class="ml-auto size-5 shrink-0 text-secondary" />
|
||||
+ </button>
|
||||
+ </div>
|
||||
+
|
||||
<!-- Add launcher path -->
|
||||
<div v-if="!showAddPath">
|
||||
<Button class="w-full" @click="showAddPath = true">
|
||||
@@ -99,18 +124,25 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
-import { ChevronRightIcon, FolderSearchIcon, SearchIcon } from '@modrinth/assets'
|
||||
+import {
|
||||
+ ChevronRightIcon,
|
||||
+ FolderSearchIcon,
|
||||
+ PlusIcon,
|
||||
+ SearchIcon,
|
||||
+ SpinnerIcon,
|
||||
+} from '@modrinth/assets'
|
||||
import { defineMessages, useVIntl } from '@modrinth/ui'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
|
||||
import { Button, IconButton } from '#ui/components/base/buttons'
|
||||
|
||||
import { injectInstanceImport, injectNotificationManager } from '../../../../providers'
|
||||
-import type { ImportableLauncher } from '../../../../providers/instance-import'
|
||||
+import type { ImportableLauncher, LauncherSuggestion } from '../../../../providers/instance-import'
|
||||
import Checkbox from '../../../base/Checkbox.vue'
|
||||
import Collapsible from '../../../base/Collapsible.vue'
|
||||
import Input from '../../../base/inputs/Input.vue'
|
||||
import { injectCreationFlowContext } from '../creation-flow-context'
|
||||
+import LauncherMark from './LauncherMark.vue'
|
||||
|
||||
const ctx = injectCreationFlowContext()
|
||||
const importProvider = injectInstanceImport()
|
||||
@@ -122,6 +154,8 @@ const expandedLaunchers = ref(new Set<string>())
|
||||
const expandedBeforeSearch = ref<Set<string> | null>(null)
|
||||
const showAddPath = ref(false)
|
||||
const newLauncherPath = ref('')
|
||||
+const suggestions = ref<LauncherSuggestion[]>([])
|
||||
+const scanning = ref<string | null>(null)
|
||||
|
||||
const messages = defineMessages({
|
||||
launcherInstancesTitle: {
|
||||
@@ -160,6 +194,14 @@ const messages = defineMessages({
|
||||
id: 'creation-flow.modal.import-instance.notification.no-instances-found.text',
|
||||
defaultMessage: 'No importable instances were found at the specified path.',
|
||||
},
|
||||
+ otherLaunchers: {
|
||||
+ id: 'creation-flow.modal.import-instance.other-launchers',
|
||||
+ defaultMessage: 'Other launchers',
|
||||
+ },
|
||||
+ suggestionNoPath: {
|
||||
+ id: 'creation-flow.modal.import-instance.other-launchers.no-path',
|
||||
+ defaultMessage: 'Pick the folder it keeps its instances in',
|
||||
+ },
|
||||
customLauncherName: {
|
||||
id: 'creation-flow.modal.import-instance.custom-launcher.name',
|
||||
defaultMessage: 'Custom ({pathName})',
|
||||
@@ -182,9 +224,65 @@ onMounted(async () => {
|
||||
} catch {
|
||||
ctx.importLaunchers.value = []
|
||||
}
|
||||
+
|
||||
+ try {
|
||||
+ suggestions.value = (await importProvider.getLauncherSuggestions?.()) ?? []
|
||||
+ } catch {
|
||||
+ suggestions.value = []
|
||||
+ }
|
||||
loading.value = false
|
||||
})
|
||||
|
||||
+// Whatever is already listed above needs no offer of its own.
|
||||
+const otherLaunchers = computed(() =>
|
||||
+ suggestions.value.filter(
|
||||
+ (suggestion) =>
|
||||
+ !ctx.importLaunchers.value.some((launcher) => launcher.name === suggestion.name),
|
||||
+ ),
|
||||
+)
|
||||
+
|
||||
+/**
|
||||
+ * Reads the instances a launcher keeps where it usually does. With none there,
|
||||
+ * its folder goes into the path field to be corrected by hand.
|
||||
+ */
|
||||
+async function useSuggestion(suggestion: LauncherSuggestion) {
|
||||
+ if (scanning.value) return
|
||||
+ scanning.value = suggestion.name
|
||||
+
|
||||
+ try {
|
||||
+ const instances = suggestion.path
|
||||
+ ? await importProvider.getImportableInstances(suggestion.launcher, suggestion.path)
|
||||
+ : []
|
||||
+ if (instances.length > 0) {
|
||||
+ addLauncher({
|
||||
+ name: suggestion.name,
|
||||
+ launcher: suggestion.launcher,
|
||||
+ path: suggestion.path,
|
||||
+ instances,
|
||||
+ })
|
||||
+ return
|
||||
+ }
|
||||
+ } catch {
|
||||
+ // Falls through to picking the folder by hand.
|
||||
+ } finally {
|
||||
+ scanning.value = null
|
||||
+ }
|
||||
+
|
||||
+ newLauncherPath.value = suggestion.path
|
||||
+ showAddPath.value = true
|
||||
+ addNotification({
|
||||
+ type: 'warning',
|
||||
+ title: formatMessage(messages.noInstancesFoundTitle),
|
||||
+ text: formatMessage(messages.noInstancesFoundText),
|
||||
+ })
|
||||
+}
|
||||
+
|
||||
+function addLauncher(launcher: ImportableLauncher) {
|
||||
+ ctx.importLaunchers.value = [...ctx.importLaunchers.value, launcher]
|
||||
+ expandedLaunchers.value.add(launcher.name)
|
||||
+ expandedLaunchers.value = new Set(expandedLaunchers.value)
|
||||
+}
|
||||
+
|
||||
// Filter instances by search query
|
||||
function filteredInstances(launcher: ImportableLauncher): string[] {
|
||||
const query = ctx.importSearchQuery.value.toLowerCase().trim()
|
||||
@@ -316,16 +414,13 @@ async function addLauncherPath() {
|
||||
})
|
||||
return
|
||||
}
|
||||
- const launcher: ImportableLauncher = {
|
||||
+ addLauncher({
|
||||
name: formatMessage(messages.customLauncherName, {
|
||||
pathName: path.split(/[\\/]/).pop() || path,
|
||||
}),
|
||||
path,
|
||||
instances,
|
||||
- }
|
||||
- ctx.importLaunchers.value = [...ctx.importLaunchers.value, launcher]
|
||||
- expandedLaunchers.value.add(launcher.name)
|
||||
- expandedLaunchers.value = new Set(expandedLaunchers.value)
|
||||
+ })
|
||||
} catch {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
diff --git a/packages/ui/src/components/flows/creation-flow-modal/components/LauncherMark.vue b/packages/ui/src/components/flows/creation-flow-modal/components/LauncherMark.vue
|
||||
new file mode 100644
|
||||
index 0000000..aa1b97d
|
||||
--- /dev/null
|
||||
+++ b/packages/ui/src/components/flows/creation-flow-modal/components/LauncherMark.vue
|
||||
@@ -0,0 +1,55 @@
|
||||
+<!--
|
||||
+ A launcher's own logo, or its initials in a badge for one we have none of.
|
||||
+ See src/assets/launchers/README.md.
|
||||
+-->
|
||||
+<template>
|
||||
+ <!-- CurseForge's is a wide mark rather than a square icon, so it gets a badge. -->
|
||||
+ <span
|
||||
+ v-if="name === 'CurseForge'"
|
||||
+ class="flex size-8 shrink-0 items-center justify-center rounded-lg bg-surface-4"
|
||||
+ >
|
||||
+ <CurseForgeIcon class="size-5 text-contrast" />
|
||||
+ </span>
|
||||
+ <img v-else-if="logo" :src="logo" :alt="''" class="size-8 shrink-0 rounded-lg" />
|
||||
+ <span
|
||||
+ v-else
|
||||
+ class="flex size-8 shrink-0 items-center justify-center rounded-lg bg-surface-4 text-xs font-extrabold text-contrast"
|
||||
+ aria-hidden="true"
|
||||
+ >
|
||||
+ {{ mark }}
|
||||
+ </span>
|
||||
+</template>
|
||||
+
|
||||
+<script setup lang="ts">
|
||||
+import { CurseForgeIcon } from '@modrinth/assets'
|
||||
+import { computed } from 'vue'
|
||||
+
|
||||
+// As URLs, not as components: these are the launchers' own artwork, colours
|
||||
+// and all, rather than an icon that takes the colour around it.
|
||||
+import atlauncher from '../../../../assets/launchers/atlauncher.svg?url'
|
||||
+import gdlauncher from '../../../../assets/launchers/gdlauncher.png'
|
||||
+import multimc from '../../../../assets/launchers/multimc.svg?url'
|
||||
+import polymc from '../../../../assets/launchers/polymc.svg?url'
|
||||
+import prismLauncher from '../../../../assets/launchers/prism-launcher.svg?url'
|
||||
+
|
||||
+const props = defineProps<{ name: string }>()
|
||||
+
|
||||
+const LOGOS: Record<string, string> = {
|
||||
+ 'Prism Launcher': prismLauncher,
|
||||
+ MultiMC: multimc,
|
||||
+ PolyMC: polymc,
|
||||
+ ATLauncher: atlauncher,
|
||||
+ GDLauncher: gdlauncher,
|
||||
+}
|
||||
+
|
||||
+const logo = computed(() => LOGOS[props.name])
|
||||
+
|
||||
+// Custom (foo) gives C, MultiMC would give MM.
|
||||
+const mark = computed(
|
||||
+ () =>
|
||||
+ props.name
|
||||
+ .match(/\p{Lu}/gu)
|
||||
+ ?.slice(0, 2)
|
||||
+ .join('') ?? props.name.slice(0, 2).toUpperCase(),
|
||||
+)
|
||||
+</script>
|
||||
diff --git a/packages/ui/src/providers/instance-import.ts b/packages/ui/src/providers/instance-import.ts
|
||||
index 13c73a1..e9a9767 100644
|
||||
--- a/packages/ui/src/providers/instance-import.ts
|
||||
+++ b/packages/ui/src/providers/instance-import.ts
|
||||
@@ -2,13 +2,27 @@ import { createContext } from '.'
|
||||
|
||||
export interface ImportableLauncher {
|
||||
name: string
|
||||
+ /** The importer that reads it, where that is not the name shown. */
|
||||
+ launcher?: string
|
||||
path: string
|
||||
instances: string[]
|
||||
}
|
||||
|
||||
+/** A launcher instances can be imported from, installed here or not. */
|
||||
+export interface LauncherSuggestion {
|
||||
+ name: string
|
||||
+ /** The importer that reads it. */
|
||||
+ launcher: string
|
||||
+ /** Where it is, or where it would be on this system. */
|
||||
+ path: string
|
||||
+ installed: boolean
|
||||
+}
|
||||
+
|
||||
export interface InstanceImportProvider {
|
||||
/** Returns launchers with instances already populated (one round trip on mount) */
|
||||
getDetectedLaunchers: () => Promise<ImportableLauncher[]>
|
||||
+ /** Every launcher that can be imported from, to offer the ones not found */
|
||||
+ getLauncherSuggestions?: () => Promise<LauncherSuggestion[]>
|
||||
/** Only needed for manually-added launcher paths */
|
||||
getImportableInstances: (launcherName: string, path: string) => Promise<string[]>
|
||||
/** Perform the actual import */
|
||||
@@ -0,0 +1,591 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Modrinth Enhanced <patches@modrinth-enhanced.invalid>
|
||||
Date: Fri, 18 Sep 2026 09:43:57 +0200
|
||||
Subject: [PATCH] Arrange the left sidebar
|
||||
|
||||
The buttons above the instances were in a fixed order, and hiding one
|
||||
meant finding its switch among the feature settings, where three of them
|
||||
sat next to things that have nothing to do with the sidebar.
|
||||
|
||||
Settings has a Sidebar tab now: every button in a list, moved up or down
|
||||
and shown or hidden, with a way back to the usual order. Right-clicking a
|
||||
button in the sidebar hides it, or opens that tab. The order and what is
|
||||
hidden are kept on this machine, not sent to Modrinth with the synced
|
||||
preferences: it is how the app looks here.
|
||||
|
||||
The three switches move out of the feature settings, since the tab is
|
||||
where they belong; only the number of instances stays there.
|
||||
---
|
||||
apps/app-frontend/src/App.vue | 138 ++++++++++++------
|
||||
.../components/ui/modal/AppSettingsModal.vue | 22 ++-
|
||||
.../ui/settings/display/FeaturesSettings.vue | 70 ---------
|
||||
.../ui/settings/display/SidebarSettings.vue | 125 ++++++++++++++++
|
||||
.../src/composables/use-sidebar-layout.ts | 72 +++++++++
|
||||
5 files changed, 310 insertions(+), 117 deletions(-)
|
||||
create mode 100644 apps/app-frontend/src/components/ui/settings/display/SidebarSettings.vue
|
||||
create mode 100644 apps/app-frontend/src/composables/use-sidebar-layout.ts
|
||||
|
||||
diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue
|
||||
index 6539d4a..871a8b9 100644
|
||||
--- a/apps/app-frontend/src/App.vue
|
||||
+++ b/apps/app-frontend/src/App.vue
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
ChevronLeftIcon,
|
||||
ChevronRightIcon,
|
||||
CompassIcon,
|
||||
+ EyeOffIcon,
|
||||
ImageIcon,
|
||||
LogInIcon,
|
||||
LogOutIcon,
|
||||
@@ -42,6 +43,7 @@ import {
|
||||
commonSettingsMessages,
|
||||
ContentInstallModal,
|
||||
ContentUpdaterModal,
|
||||
+ ContextMenu,
|
||||
CreationFlowModal,
|
||||
defineMessages,
|
||||
I18nDebugPanel,
|
||||
@@ -79,7 +81,6 @@ import { computed, nextTick, onMounted, onUnmounted, provide, ref, watch } from
|
||||
import { RouterView, useRoute, useRouter } from 'vue-router'
|
||||
|
||||
import AccountsCard from '@/components/ui/AccountsCard.vue'
|
||||
-import TitleBarAccountSwitcher from '@/components/ui/TitleBarAccountSwitcher.vue'
|
||||
import AppActionBar from '@/components/ui/AppActionBar.vue'
|
||||
import Breadcrumbs from '@/components/ui/Breadcrumbs.vue'
|
||||
import ErrorModal from '@/components/ui/ErrorModal.vue'
|
||||
@@ -107,6 +108,7 @@ import {
|
||||
markSyncInstancesUpdateNotificationShown,
|
||||
shouldShowSyncInstancesUpdateNotification,
|
||||
} from '@/components/ui/sync-instances-update-modal/show-notification'
|
||||
+import TitleBarAccountSwitcher from '@/components/ui/TitleBarAccountSwitcher.vue'
|
||||
import WindowControls from '@/components/ui/WindowControls.vue'
|
||||
import { useCheckDisableMouseover } from '@/composables/macCssFix.js'
|
||||
import { useAppEvent } from '@/composables/use-app-event'
|
||||
@@ -114,6 +116,7 @@ import { useAppSettings } from '@/composables/use-app-settings.ts'
|
||||
import { useError } from '@/composables/use-error.js'
|
||||
import { useInstanceMetadataRefresh } from '@/composables/use-instance-metadata-refresh'
|
||||
import { useQuickInstanceLimit } from '@/composables/use-quick-instance-limit.ts'
|
||||
+import { useSidebarLayout } from '@/composables/use-sidebar-layout'
|
||||
import { isDarkTheme, useTheme } from '@/composables/use-theme.ts'
|
||||
import { config } from '@/config'
|
||||
import { getAccountAppearance, rememberAccountAppearance } from '@/helpers/account-appearance.ts'
|
||||
@@ -259,6 +262,73 @@ const PRIDE_FUNDRAISER_END_DATE = new Date('2026-07-01T00:00:00Z').getTime()
|
||||
const credentials = ref()
|
||||
const storedModrinthAccounts = ref([])
|
||||
let credentialsRefreshId = 0
|
||||
+// The buttons above the instances. Which of them are shown, and in what order,
|
||||
+// is up to the player: Settings > Sidebar, or a right-click on one of them.
|
||||
+const sidebarLayout = useSidebarLayout()
|
||||
+const sidebarMenu = ref()
|
||||
+
|
||||
+const sidebarButtons = computed(() => {
|
||||
+ const buttons = {
|
||||
+ home: {
|
||||
+ label: () => formatMessage(messages.home),
|
||||
+ icon: PlayIcon,
|
||||
+ iconClass: 'ml-0.5',
|
||||
+ to: '/',
|
||||
+ isPrimary: (r) => r.path === '/',
|
||||
+ isSubpage: () =>
|
||||
+ (route.path.startsWith('/browse') || route.path.startsWith('/project')) && !!route.query.i,
|
||||
+ },
|
||||
+ discover: {
|
||||
+ label: () => formatMessage(commonMessages.discoverContentLabel),
|
||||
+ icon: CompassIcon,
|
||||
+ to: '/browse/modpack',
|
||||
+ isPrimary: () => route.path.startsWith('/browse') && !route.query.i && !route.query.sid,
|
||||
+ isSubpage: (r) => r.path.startsWith('/project') && !r.query.i && !r.query.sid,
|
||||
+ },
|
||||
+ skins: {
|
||||
+ label: () => formatMessage(appMessages.skinSelectorLabel),
|
||||
+ icon: ShirtIcon,
|
||||
+ to: '/skins',
|
||||
+ },
|
||||
+ screenshots: {
|
||||
+ label: () => formatMessage(messages.screenshots),
|
||||
+ icon: ImageIcon,
|
||||
+ to: '/screenshots',
|
||||
+ },
|
||||
+ hosting: {
|
||||
+ label: () => formatMessage(messages.modrinthHosting),
|
||||
+ icon: ServerStackIcon,
|
||||
+ to: '/hosting/manage',
|
||||
+ isPrimary: (r) => r.path === '/hosting/manage' || r.path === '/hosting/manage/',
|
||||
+ isSubpage: (r) =>
|
||||
+ (r.path.startsWith('/hosting/manage/') && r.path !== '/hosting/manage/') ||
|
||||
+ ((r.path.startsWith('/browse') || r.path.startsWith('/project')) && !!r.query.sid),
|
||||
+ },
|
||||
+ }
|
||||
+
|
||||
+ return sidebarLayout.order.value
|
||||
+ .filter((id) => !sidebarLayout.isHidden(id))
|
||||
+ .map((id) => ({ id, ...buttons[id] }))
|
||||
+})
|
||||
+
|
||||
+function openSidebarMenu(event, button) {
|
||||
+ sidebarMenu.value?.open(event, [
|
||||
+ {
|
||||
+ id: 'hide',
|
||||
+ label: formatMessage(messages.hideFromSidebar, { name: button.label() }),
|
||||
+ icon: EyeOffIcon,
|
||||
+ action: () => sidebarLayout.setHidden(button.id, true),
|
||||
+ },
|
||||
+ { type: 'divider' },
|
||||
+ {
|
||||
+ id: 'arrange',
|
||||
+ label: formatMessage(messages.arrangeSidebar),
|
||||
+ icon: SettingsIcon,
|
||||
+ action: () => appSettingsModal.value?.showSidebar(),
|
||||
+ },
|
||||
+ ])
|
||||
+}
|
||||
+
|
||||
const sidebarToggled = ref(true)
|
||||
// Whether the right sidebar is folded away, remembered across restarts in the
|
||||
// feature flags. The title bar button below is the only thing that sets it:
|
||||
@@ -729,6 +799,18 @@ const messages = defineMessages({
|
||||
id: 'app.nav.screenshots',
|
||||
defaultMessage: 'Screenshots',
|
||||
},
|
||||
+ sidebarActions: {
|
||||
+ id: 'app.nav.sidebar-actions',
|
||||
+ defaultMessage: 'Sidebar button',
|
||||
+ },
|
||||
+ hideFromSidebar: {
|
||||
+ id: 'app.nav.hide-from-sidebar',
|
||||
+ defaultMessage: 'Hide “{name}”',
|
||||
+ },
|
||||
+ arrangeSidebar: {
|
||||
+ id: 'app.nav.arrange-sidebar',
|
||||
+ defaultMessage: 'Arrange the sidebar…',
|
||||
+ },
|
||||
createNewInstance: {
|
||||
id: 'app.nav.create-new-instance',
|
||||
defaultMessage: 'Create new instance',
|
||||
@@ -2327,52 +2409,15 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
|
||||
class="app-grid-navbar bg-bg-raised flex flex-col p-[0.5rem] pt-0 gap-[0.25rem] w-[--left-bar-width]"
|
||||
>
|
||||
<NavButton
|
||||
- v-tooltip.right="formatMessage(messages.home)"
|
||||
- to="/"
|
||||
- :is-primary="(route) => route.path === '/'"
|
||||
- :is-subpage="
|
||||
- () =>
|
||||
- (route.path.startsWith('/browse') || route.path.startsWith('/project')) && route.query.i
|
||||
- "
|
||||
- >
|
||||
- <PlayIcon class="ml-0.5" />
|
||||
- </NavButton>
|
||||
- <NavButton
|
||||
- v-tooltip.right="formatMessage(commonMessages.discoverContentLabel)"
|
||||
- to="/browse/modpack"
|
||||
- :is-primary="() => route.path.startsWith('/browse') && !route.query.i && !route.query.sid"
|
||||
- :is-subpage="
|
||||
- (route) => route.path.startsWith('/project') && !route.query.i && !route.query.sid
|
||||
- "
|
||||
- >
|
||||
- <CompassIcon />
|
||||
- </NavButton>
|
||||
- <NavButton
|
||||
- v-if="appSettings.showSkinSelectorInSidebar"
|
||||
- v-tooltip.right="formatMessage(appMessages.skinSelectorLabel)"
|
||||
- to="/skins"
|
||||
- >
|
||||
- <ShirtIcon />
|
||||
- </NavButton>
|
||||
- <NavButton
|
||||
- v-if="globalSyncedOptionsQuery.data.value?.screenshots"
|
||||
- v-tooltip.right="formatMessage(messages.screenshots)"
|
||||
- to="/screenshots"
|
||||
- >
|
||||
- <ImageIcon />
|
||||
- </NavButton>
|
||||
- <NavButton
|
||||
- v-if="appSettings.getFeatureFlag('show_hosting_in_sidebar')"
|
||||
- v-tooltip.right="formatMessage(messages.modrinthHosting)"
|
||||
- to="/hosting/manage"
|
||||
- :is-primary="(r) => r.path === '/hosting/manage' || r.path === '/hosting/manage/'"
|
||||
- :is-subpage="
|
||||
- (r) =>
|
||||
- (r.path.startsWith('/hosting/manage/') && r.path !== '/hosting/manage/') ||
|
||||
- ((r.path.startsWith('/browse') || r.path.startsWith('/project')) && r.query.sid)
|
||||
- "
|
||||
+ v-for="button in sidebarButtons"
|
||||
+ :key="button.id"
|
||||
+ v-tooltip.right="button.label()"
|
||||
+ :to="button.to"
|
||||
+ :is-primary="button.isPrimary"
|
||||
+ :is-subpage="button.isSubpage"
|
||||
+ @contextmenu.prevent.stop="(event) => openSidebarMenu(event, button)"
|
||||
>
|
||||
- <ServerStackIcon />
|
||||
+ <component :is="button.icon" :class="button.iconClass" />
|
||||
</NavButton>
|
||||
<suspense>
|
||||
<QuickInstanceSwitcher>
|
||||
@@ -2456,6 +2501,7 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
|
||||
<LogInIcon class="text-brand" />
|
||||
</NavButton>
|
||||
</div>
|
||||
+ <ContextMenu ref="sidebarMenu" :label="formatMessage(messages.sidebarActions)" />
|
||||
<div data-tauri-drag-region class="app-grid-statusbar bg-bg-raised h-[--top-bar-height] flex">
|
||||
<div data-tauri-drag-region class="flex min-w-0 flex-1 items-center overflow-hidden p-2">
|
||||
<TextLogo class="h-7 w-auto shrink-0 text-contrast pointer-events-none" />
|
||||
diff --git a/apps/app-frontend/src/components/ui/modal/AppSettingsModal.vue b/apps/app-frontend/src/components/ui/modal/AppSettingsModal.vue
|
||||
index 32a86a3..e409510 100644
|
||||
--- a/apps/app-frontend/src/components/ui/modal/AppSettingsModal.vue
|
||||
+++ b/apps/app-frontend/src/components/ui/modal/AppSettingsModal.vue
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
CoffeeIcon,
|
||||
HeartHandshakeIcon,
|
||||
LanguagesIcon,
|
||||
+ LayoutTemplateIcon,
|
||||
LightBulbIcon,
|
||||
MicrochipIcon,
|
||||
ModrinthIcon,
|
||||
@@ -36,6 +37,7 @@ import AppearanceSettings from '@/components/ui/settings/display/AppearanceSetti
|
||||
import BehaviorSettings from '@/components/ui/settings/display/BehaviorSettings.vue'
|
||||
import FeatureFlagSettings from '@/components/ui/settings/display/FeatureFlagSettings.vue'
|
||||
import FeaturesSettings from '@/components/ui/settings/display/FeaturesSettings.vue'
|
||||
+import SidebarSettings from '@/components/ui/settings/display/SidebarSettings.vue'
|
||||
import LanguageSettings from '@/components/ui/settings/display/LanguageSettings.vue'
|
||||
import InstancesSyncedSettings from '@/components/ui/settings/instances/instances-synced-settings/index.vue'
|
||||
import JavaSettings from '@/components/ui/settings/instances/JavaSettings.vue'
|
||||
@@ -96,6 +98,15 @@ const tabs = [
|
||||
icon: LightBulbIcon,
|
||||
content: FeaturesSettings,
|
||||
},
|
||||
+ {
|
||||
+ name: defineMessage({
|
||||
+ id: 'app.settings.tabs.sidebar',
|
||||
+ defaultMessage: 'Sidebar',
|
||||
+ }),
|
||||
+ category: tabCategories.display,
|
||||
+ icon: LayoutTemplateIcon,
|
||||
+ content: SidebarSettings,
|
||||
+ },
|
||||
{
|
||||
name: defineMessage({
|
||||
id: 'app.settings.tabs.behavior',
|
||||
@@ -247,6 +258,15 @@ function showFeatureFlags(): void {
|
||||
modal.value?.show()
|
||||
}
|
||||
|
||||
+/** Opens the settings at the sidebar tab, for the menu on a sidebar button. */
|
||||
+function showSidebar(): void {
|
||||
+ show()
|
||||
+ const sidebarTabIndex = availableTabs.value.findIndex((tab) => tab.content === SidebarSettings)
|
||||
+ if (sidebarTabIndex >= 0) {
|
||||
+ modal.value?.setTab(sidebarTabIndex)
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
function showSyncedOptions(): void {
|
||||
const syncedOptionsTabIndex = availableTabs.value.findIndex(
|
||||
(tab) => tab.content === InstancesSyncedSettings,
|
||||
@@ -257,7 +277,7 @@ function showSyncedOptions(): void {
|
||||
modal.value?.show()
|
||||
}
|
||||
|
||||
-defineExpose({ show, showProfile, showFeatureFlags, showSyncedOptions })
|
||||
+defineExpose({ show, showProfile, showFeatureFlags, showSidebar, showSyncedOptions })
|
||||
|
||||
const { progress, version: downloadingVersion } = injectAppUpdateDownloadProgress()
|
||||
|
||||
diff --git a/apps/app-frontend/src/components/ui/settings/display/FeaturesSettings.vue b/apps/app-frontend/src/components/ui/settings/display/FeaturesSettings.vue
|
||||
index 9a9e8ef..7636c46 100644
|
||||
--- a/apps/app-frontend/src/components/ui/settings/display/FeaturesSettings.vue
|
||||
+++ b/apps/app-frontend/src/components/ui/settings/display/FeaturesSettings.vue
|
||||
@@ -83,32 +83,6 @@ const messages = defineMessages({
|
||||
id: 'app.features-settings.sidebar.title',
|
||||
defaultMessage: 'Sidebar',
|
||||
},
|
||||
- showAllScreenshotsTitle: {
|
||||
- id: 'app.features-settings.show-all-screenshots.title',
|
||||
- defaultMessage: 'Show all screenshots in sidebar',
|
||||
- },
|
||||
- showAllScreenshotsDescription: {
|
||||
- id: 'app.features-settings.show-all-screenshots.description',
|
||||
- defaultMessage:
|
||||
- 'Show a button in the left sidebar to view screenshots from all your instances.',
|
||||
- },
|
||||
- showSkinSelectorTitle: {
|
||||
- id: 'app.features-settings.show-skin-selector.title',
|
||||
- defaultMessage: 'Show skin selector in sidebar',
|
||||
- },
|
||||
- showSkinSelectorDescription: {
|
||||
- id: 'app.features-settings.show-skin-selector.description',
|
||||
- defaultMessage: 'Show a button in the left sidebar to open the skin selector.',
|
||||
- },
|
||||
- showHostingTitle: {
|
||||
- id: 'app.features-settings.show-hosting.title',
|
||||
- defaultMessage: 'Show Modrinth Servers in sidebar',
|
||||
- },
|
||||
- showHostingDescription: {
|
||||
- id: 'app.features-settings.show-hosting.description',
|
||||
- defaultMessage:
|
||||
- 'Show a button in the left sidebar to manage Modrinth Servers. Added by Modrinth Enhanced, so it stays on this device and is not synced to your Modrinth account.',
|
||||
- },
|
||||
quickInstancesTitle: {
|
||||
id: 'app.features-settings.quick-instances.title',
|
||||
defaultMessage: 'Quick instances in sidebar',
|
||||
@@ -385,50 +359,6 @@ onBeforeUnmount(() => {
|
||||
{{ formatMessage(messages.sidebarTitle) }}
|
||||
</h2>
|
||||
<div class="mt-4 flex flex-col gap-6">
|
||||
- <div class="flex items-center justify-between gap-4">
|
||||
- <div>
|
||||
- <h3 class="m-0 text-lg font-semibold text-contrast">
|
||||
- {{ formatMessage(messages.showAllScreenshotsTitle) }}
|
||||
- </h3>
|
||||
- <p class="m-0 mt-1">
|
||||
- {{ formatMessage(messages.showAllScreenshotsDescription) }}
|
||||
- </p>
|
||||
- </div>
|
||||
- <Toggle
|
||||
- id="show-all-screenshots"
|
||||
- v-model="current.showAllScreenshots"
|
||||
- :aria-label="formatMessage(messages.showAllScreenshotsTitle)"
|
||||
- />
|
||||
- </div>
|
||||
-
|
||||
- <div class="flex items-center justify-between gap-4">
|
||||
- <div>
|
||||
- <h3 class="m-0 text-lg font-semibold text-contrast">
|
||||
- {{ formatMessage(messages.showSkinSelectorTitle) }}
|
||||
- </h3>
|
||||
- <p class="m-0 mt-1">{{ formatMessage(messages.showSkinSelectorDescription) }}</p>
|
||||
- </div>
|
||||
- <Toggle
|
||||
- id="show-skin-selector-in-sidebar"
|
||||
- v-model="current.showSkinSelector"
|
||||
- :aria-label="formatMessage(messages.showSkinSelectorTitle)"
|
||||
- />
|
||||
- </div>
|
||||
-
|
||||
- <div class="flex items-center justify-between gap-4">
|
||||
- <div>
|
||||
- <h3 class="m-0 text-lg font-semibold text-contrast">
|
||||
- {{ formatMessage(messages.showHostingTitle) }}
|
||||
- </h3>
|
||||
- <p class="m-0 mt-1">{{ formatMessage(messages.showHostingDescription) }}</p>
|
||||
- </div>
|
||||
- <Toggle
|
||||
- id="show-hosting-in-sidebar"
|
||||
- v-model="current.showHosting"
|
||||
- :aria-label="formatMessage(messages.showHostingTitle)"
|
||||
- />
|
||||
- </div>
|
||||
-
|
||||
<div class="flex flex-col gap-2.5">
|
||||
<h3 class="m-0 text-lg font-semibold text-contrast">
|
||||
{{ formatMessage(messages.quickInstancesTitle) }}
|
||||
diff --git a/apps/app-frontend/src/components/ui/settings/display/SidebarSettings.vue b/apps/app-frontend/src/components/ui/settings/display/SidebarSettings.vue
|
||||
new file mode 100644
|
||||
index 0000000..dc0cd1f
|
||||
--- /dev/null
|
||||
+++ b/apps/app-frontend/src/components/ui/settings/display/SidebarSettings.vue
|
||||
@@ -0,0 +1,125 @@
|
||||
+<template>
|
||||
+ <h2 class="m-0 text-xl font-semibold text-contrast">{{ formatMessage(messages.title) }}</h2>
|
||||
+ <p class="mt-1 mb-0">{{ formatMessage(messages.description) }}</p>
|
||||
+
|
||||
+ <Draggable
|
||||
+ v-model="orderedItems"
|
||||
+ class="mt-4 flex flex-col gap-2"
|
||||
+ item-key="id"
|
||||
+ :animation="200"
|
||||
+ filter=".sidebar-item-toggle"
|
||||
+ :prevent-on-filter="false"
|
||||
+ ghost-class="sidebar-item-ghost"
|
||||
+ >
|
||||
+ <template #item="{ element: item }">
|
||||
+ <div
|
||||
+ class="flex cursor-grab items-center gap-3 rounded-2xl border border-solid border-surface-4 bg-surface-2 p-3 active:cursor-grabbing"
|
||||
+ >
|
||||
+ <button
|
||||
+ v-tooltip="formatMessage(messages.reorder)"
|
||||
+ type="button"
|
||||
+ class="flex cursor-grab border-none bg-transparent p-0 text-secondary active:cursor-grabbing"
|
||||
+ :aria-label="formatMessage(messages.reorder)"
|
||||
+ @keydown.up.prevent="layout.move(item.id, -1)"
|
||||
+ @keydown.down.prevent="layout.move(item.id, 1)"
|
||||
+ >
|
||||
+ <GripVerticalIcon class="size-5" />
|
||||
+ </button>
|
||||
+ <component :is="item.icon" class="size-5 shrink-0 text-secondary" />
|
||||
+ <span class="font-semibold text-contrast">{{ item.label }}</span>
|
||||
+ <Toggle
|
||||
+ :id="`sidebar-${item.id}`"
|
||||
+ :model-value="!layout.isHidden(item.id)"
|
||||
+ :aria-label="formatMessage(messages.show, { name: item.label })"
|
||||
+ class="sidebar-item-toggle ml-auto"
|
||||
+ @update:model-value="(shown: boolean) => layout.setHidden(item.id, !shown)"
|
||||
+ />
|
||||
+ </div>
|
||||
+ </template>
|
||||
+ </Draggable>
|
||||
+
|
||||
+ <Button class="mt-4" @click="layout.reset()">
|
||||
+ <RotateCounterClockwiseIcon />
|
||||
+ {{ formatMessage(messages.reset) }}
|
||||
+ </Button>
|
||||
+</template>
|
||||
+
|
||||
+<script setup lang="ts">
|
||||
+import {
|
||||
+ CompassIcon,
|
||||
+ GripVerticalIcon,
|
||||
+ ImageIcon,
|
||||
+ PlayIcon,
|
||||
+ RotateCounterClockwiseIcon,
|
||||
+ ServerStackIcon,
|
||||
+ ShirtIcon,
|
||||
+} from '@modrinth/assets'
|
||||
+import { Button, commonMessages, defineMessages, Toggle, useVIntl } from '@modrinth/ui'
|
||||
+import { computed } from 'vue'
|
||||
+import Draggable from 'vuedraggable'
|
||||
+
|
||||
+import { type SidebarItemId, useSidebarLayout } from '@/composables/use-sidebar-layout'
|
||||
+import { appMessages } from '@/utils/app-messages'
|
||||
+
|
||||
+const { formatMessage } = useVIntl()
|
||||
+const layout = useSidebarLayout()
|
||||
+
|
||||
+const messages = defineMessages({
|
||||
+ title: {
|
||||
+ id: 'app.sidebar-settings.title',
|
||||
+ defaultMessage: 'Sidebar',
|
||||
+ },
|
||||
+ description: {
|
||||
+ id: 'app.sidebar-settings.description',
|
||||
+ defaultMessage:
|
||||
+ 'The buttons above the instances in the left sidebar. Right-clicking one of them hides it as well.',
|
||||
+ },
|
||||
+ reorder: {
|
||||
+ id: 'app.sidebar-settings.reorder',
|
||||
+ defaultMessage: 'Drag to reorder, or use the arrow keys',
|
||||
+ },
|
||||
+ show: {
|
||||
+ id: 'app.sidebar-settings.show',
|
||||
+ defaultMessage: 'Show {name}',
|
||||
+ },
|
||||
+ reset: {
|
||||
+ id: 'app.sidebar-settings.reset',
|
||||
+ defaultMessage: 'Back to the usual order',
|
||||
+ },
|
||||
+ home: {
|
||||
+ id: 'app.sidebar-settings.home',
|
||||
+ defaultMessage: 'Home',
|
||||
+ },
|
||||
+ screenshots: {
|
||||
+ id: 'app.sidebar-settings.screenshots',
|
||||
+ defaultMessage: 'Screenshots',
|
||||
+ },
|
||||
+ hosting: {
|
||||
+ id: 'app.sidebar-settings.hosting',
|
||||
+ defaultMessage: 'Modrinth Hosting',
|
||||
+ },
|
||||
+})
|
||||
+
|
||||
+const ITEMS: Record<SidebarItemId, { icon: unknown; label: () => string }> = {
|
||||
+ home: { icon: PlayIcon, label: () => formatMessage(messages.home) },
|
||||
+ discover: {
|
||||
+ icon: CompassIcon,
|
||||
+ label: () => formatMessage(commonMessages.discoverContentLabel),
|
||||
+ },
|
||||
+ skins: { icon: ShirtIcon, label: () => formatMessage(appMessages.skinSelectorLabel) },
|
||||
+ screenshots: { icon: ImageIcon, label: () => formatMessage(messages.screenshots) },
|
||||
+ hosting: { icon: ServerStackIcon, label: () => formatMessage(messages.hosting) },
|
||||
+}
|
||||
+
|
||||
+const orderedItems = computed({
|
||||
+ get: () =>
|
||||
+ layout.order.value.map((id) => ({ id, icon: ITEMS[id].icon, label: ITEMS[id].label() })),
|
||||
+ set: (items: { id: SidebarItemId }[]) => layout.setOrder(items.map((item) => item.id)),
|
||||
+})
|
||||
+</script>
|
||||
+
|
||||
+<style scoped>
|
||||
+.sidebar-item-ghost {
|
||||
+ opacity: 0.4;
|
||||
+}
|
||||
+</style>
|
||||
diff --git a/apps/app-frontend/src/composables/use-sidebar-layout.ts b/apps/app-frontend/src/composables/use-sidebar-layout.ts
|
||||
new file mode 100644
|
||||
index 0000000..7225c21
|
||||
--- /dev/null
|
||||
+++ b/apps/app-frontend/src/composables/use-sidebar-layout.ts
|
||||
@@ -0,0 +1,72 @@
|
||||
+/**
|
||||
+ * The order of the buttons above the instances in the left sidebar, and which
|
||||
+ * of them are shown.
|
||||
+ *
|
||||
+ * This is about how the app looks on this machine, so it stays here rather than
|
||||
+ * going to Modrinth with the synced preferences.
|
||||
+ */
|
||||
+import { useStorage } from '@vueuse/core'
|
||||
+import { computed } from 'vue'
|
||||
+
|
||||
+/** The buttons, in the order upstream has them. */
|
||||
+export const SIDEBAR_ITEMS = ['home', 'discover', 'skins', 'screenshots', 'hosting'] as const
|
||||
+
|
||||
+export type SidebarItemId = (typeof SIDEBAR_ITEMS)[number]
|
||||
+
|
||||
+type SidebarLayout = {
|
||||
+ order: SidebarItemId[]
|
||||
+ hidden: SidebarItemId[]
|
||||
+}
|
||||
+
|
||||
+const layout = useStorage<SidebarLayout>('modrinth-enhanced:sidebar-layout', {
|
||||
+ order: [...SIDEBAR_ITEMS],
|
||||
+ hidden: [],
|
||||
+})
|
||||
+
|
||||
+/** The stored order, with anything unknown dropped and anything new appended. */
|
||||
+const order = computed<SidebarItemId[]>(() => {
|
||||
+ const stored = (layout.value.order ?? []).filter((id) => SIDEBAR_ITEMS.includes(id))
|
||||
+ return [...stored, ...SIDEBAR_ITEMS.filter((id) => !stored.includes(id))]
|
||||
+})
|
||||
+
|
||||
+const hidden = computed<SidebarItemId[]>(() =>
|
||||
+ (layout.value.hidden ?? []).filter((id) => SIDEBAR_ITEMS.includes(id)),
|
||||
+)
|
||||
+
|
||||
+export function useSidebarLayout() {
|
||||
+ function isHidden(id: SidebarItemId): boolean {
|
||||
+ return hidden.value.includes(id)
|
||||
+ }
|
||||
+
|
||||
+ function setHidden(id: SidebarItemId, hide: boolean) {
|
||||
+ layout.value = {
|
||||
+ order: order.value,
|
||||
+ hidden: hide ? [...hidden.value, id] : hidden.value.filter((other) => other !== id),
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ /** Moves a button by `by` places, as far as there is room. */
|
||||
+ function move(id: SidebarItemId, by: number) {
|
||||
+ const ids = [...order.value]
|
||||
+ const from = ids.indexOf(id)
|
||||
+ const to = from + by
|
||||
+ if (from < 0 || to < 0 || to >= ids.length) return
|
||||
+
|
||||
+ ids.splice(to, 0, ...ids.splice(from, 1))
|
||||
+ layout.value = { order: ids, hidden: hidden.value }
|
||||
+ }
|
||||
+
|
||||
+ /** The order the buttons were dragged into, anything unknown dropped. */
|
||||
+ function setOrder(ids: SidebarItemId[]) {
|
||||
+ layout.value = {
|
||||
+ order: ids.filter((id) => SIDEBAR_ITEMS.includes(id)),
|
||||
+ hidden: hidden.value,
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ function reset() {
|
||||
+ layout.value = { order: [...SIDEBAR_ITEMS], hidden: [] }
|
||||
+ }
|
||||
+
|
||||
+ return { order, hidden, isHidden, setHidden, move, setOrder, reset }
|
||||
+}
|
||||
@@ -0,0 +1,116 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Modrinth Enhanced <patches@modrinth-enhanced.invalid>
|
||||
Date: Fri, 18 Sep 2026 09:43:57 +0200
|
||||
Subject: [PATCH] Delete an instance from the sidebar
|
||||
|
||||
Right-clicking an instance in the sidebar offered play, stop, open and
|
||||
show in folder, but deleting one meant going to the library first. It is
|
||||
in that menu now, in red and below a divider, and asks first with the
|
||||
same dialog the library uses. An instance that is running has to be
|
||||
stopped first, which the entry says.
|
||||
---
|
||||
.../components/ui/QuickInstanceSwitcher.vue | 49 ++++++++++++++++++-
|
||||
1 file changed, 47 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/apps/app-frontend/src/components/ui/QuickInstanceSwitcher.vue b/apps/app-frontend/src/components/ui/QuickInstanceSwitcher.vue
|
||||
index 5c58a50..2ad9979 100644
|
||||
--- a/apps/app-frontend/src/components/ui/QuickInstanceSwitcher.vue
|
||||
+++ b/apps/app-frontend/src/components/ui/QuickInstanceSwitcher.vue
|
||||
@@ -1,5 +1,12 @@
|
||||
<script setup>
|
||||
-import { EyeIcon, FolderOpenIcon, PlayIcon, SpinnerIcon, StopCircleIcon } from '@modrinth/assets'
|
||||
+import {
|
||||
+ EyeIcon,
|
||||
+ FolderOpenIcon,
|
||||
+ PlayIcon,
|
||||
+ SpinnerIcon,
|
||||
+ StopCircleIcon,
|
||||
+ TrashIcon,
|
||||
+} from '@modrinth/assets'
|
||||
import {
|
||||
Avatar,
|
||||
commonMessages,
|
||||
@@ -13,6 +20,7 @@ import dayjs from 'dayjs'
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
+import ConfirmDeleteInstanceModal from '@/components/ui/modal/ConfirmDeleteInstanceModal.vue'
|
||||
import NavButton from '@/components/ui/NavButton.vue'
|
||||
import { useAppEvent } from '@/composables/use-app-event'
|
||||
import { handleSevereError } from '@/composables/use-error.js'
|
||||
@@ -21,7 +29,7 @@ import {
|
||||
useQuickInstanceLimit,
|
||||
} from '@/composables/use-quick-instance-limit.ts'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
-import { getInstanceIconUrl, kill, run } from '@/helpers/instance'
|
||||
+import { getInstanceIconUrl, kill, remove, run } from '@/helpers/instance'
|
||||
import { get_all } from '@/helpers/process'
|
||||
import { showInstanceInFolder } from '@/helpers/utils'
|
||||
import { instanceListQueryOptions } from '@/pages/instance/query-options'
|
||||
@@ -31,6 +39,9 @@ const { handleError } = injectNotificationManager()
|
||||
const instancesQuery = useQuery(instanceListQueryOptions())
|
||||
const router = useRouter()
|
||||
const instanceOptions = ref()
|
||||
+const confirmDeleteModal = ref()
|
||||
+// The instance the delete dialog is about, kept for its text and the deleting.
|
||||
+const instanceToDelete = ref(null)
|
||||
const runningInstances = ref([])
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
@@ -212,6 +223,10 @@ const messages = defineMessages({
|
||||
id: 'app.quick-instance-switcher.instance-locked',
|
||||
defaultMessage: 'This instance has been locked',
|
||||
},
|
||||
+ deleteWhileRunning: {
|
||||
+ id: 'app.quick-instance-switcher.delete-while-running',
|
||||
+ defaultMessage: 'Stop the instance first',
|
||||
+ },
|
||||
title: {
|
||||
id: 'app.quick-instance-switcher.title',
|
||||
defaultMessage: 'Recent instances',
|
||||
@@ -285,8 +300,33 @@ function openContextMenu(event, instance) {
|
||||
icon: FolderOpenIcon,
|
||||
action: () => showInstanceInFolder(instance.id),
|
||||
},
|
||||
+ { type: 'divider' },
|
||||
+ {
|
||||
+ id: 'delete',
|
||||
+ label: formatMessage(commonMessages.deleteLabel),
|
||||
+ icon: TrashIcon,
|
||||
+ tone: 'red',
|
||||
+ disabled: playing,
|
||||
+ tooltip: playing ? formatMessage(messages.deleteWhileRunning) : undefined,
|
||||
+ action: () => askToDelete(instance),
|
||||
+ },
|
||||
])
|
||||
}
|
||||
+
|
||||
+/** Deleting an instance takes everything in it, so it is asked about first. */
|
||||
+function askToDelete(instance) {
|
||||
+ instanceToDelete.value = instance
|
||||
+ confirmDeleteModal.value?.show()
|
||||
+}
|
||||
+
|
||||
+async function deleteInstance() {
|
||||
+ const instance = instanceToDelete.value
|
||||
+ if (!instance) return
|
||||
+
|
||||
+ instanceToDelete.value = null
|
||||
+ await remove(instance.id).catch(handleError)
|
||||
+ trackEvent('InstanceRemove', { source: 'QuickInstanceSwitcher' })
|
||||
+}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -326,6 +366,11 @@ function openContextMenu(event, instance) {
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
<ContextMenu ref="instanceOptions" :label="formatMessage(messages.instanceActions)" />
|
||||
+ <ConfirmDeleteInstanceModal
|
||||
+ ref="confirmDeleteModal"
|
||||
+ :instances="instanceToDelete ? [instanceToDelete] : []"
|
||||
+ @delete="deleteInstance"
|
||||
+ />
|
||||
<div
|
||||
v-tooltip.right="dividerTooltip"
|
||||
role="separator"
|
||||
@@ -0,0 +1,88 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Modrinth Enhanced <patches@modrinth-enhanced.invalid>
|
||||
Date: Fri, 18 Sep 2026 10:42:13 +0200
|
||||
Subject: [PATCH] Make an instance group from the library's free space
|
||||
|
||||
Right-clicking the empty space around the library offered a new instance
|
||||
and nothing else, so making a group meant finding the toolbar button.
|
||||
The same menu now offers it, and opens the dialog that button does.
|
||||
---
|
||||
.../src/components/ui/library/index.vue | 4 ++++
|
||||
apps/app-frontend/src/pages/Index.vue | 16 ++++++++++++++--
|
||||
2 files changed, 18 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/apps/app-frontend/src/components/ui/library/index.vue b/apps/app-frontend/src/components/ui/library/index.vue
|
||||
index 20000b6..1849835 100644
|
||||
--- a/apps/app-frontend/src/components/ui/library/index.vue
|
||||
+++ b/apps/app-frontend/src/components/ui/library/index.vue
|
||||
@@ -53,6 +53,7 @@ const {
|
||||
currentDeleteInstances,
|
||||
clearLibraryInstanceSelection,
|
||||
deleteInstance,
|
||||
+ openNewGroupModal,
|
||||
handleInstanceIconSaved,
|
||||
selectedLibraryInstances,
|
||||
setSelectedLibraryInstances,
|
||||
@@ -260,6 +261,9 @@ watch(selectedLibraryInstances, (selectedInstances) => {
|
||||
anchorInstance.value = null
|
||||
}
|
||||
})
|
||||
+
|
||||
+// So the page's own menu, on the free space around the library, can offer it.
|
||||
+defineExpose({ openNewGroupModal })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
diff --git a/apps/app-frontend/src/pages/Index.vue b/apps/app-frontend/src/pages/Index.vue
|
||||
index 1c5fb5d..074e709 100644
|
||||
--- a/apps/app-frontend/src/pages/Index.vue
|
||||
+++ b/apps/app-frontend/src/pages/Index.vue
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
-import { PlayIcon, PlusIcon } from '@modrinth/assets'
|
||||
+import { PlayIcon, PlusIcon, SquarePlusIcon } from '@modrinth/assets'
|
||||
import { ContextMenu, defineMessages, injectNotificationManager, useVIntl } from '@modrinth/ui'
|
||||
import { useQuery } from '@tanstack/vue-query'
|
||||
import dayjs from 'dayjs'
|
||||
@@ -25,6 +25,7 @@ const { handleError } = injectNotificationManager()
|
||||
const { hasCreatedInstance, isReady } = injectOnboardingChecklist()
|
||||
const showCreationModal = inject<() => void>('showCreationModal')
|
||||
const pageOptions = ref<InstanceType<typeof ContextMenu>>()
|
||||
+const librarySection = ref<InstanceType<typeof LibrarySection>>()
|
||||
const appSettings = useAppSettings()
|
||||
onBeforeRouteLeave(() => {
|
||||
libraryScrollTop.value = document.querySelector('.app-viewport')?.scrollTop ?? 0
|
||||
@@ -39,6 +40,11 @@ const messages = defineMessages({
|
||||
id: 'app.library.context-menu.create-instance',
|
||||
defaultMessage: 'New instance',
|
||||
},
|
||||
+ // The library toolbar's button, so this is translated wherever that is.
|
||||
+ newGroup: {
|
||||
+ id: 'app.library.group.new',
|
||||
+ defaultMessage: 'New group',
|
||||
+ },
|
||||
libraryActionsLabel: {
|
||||
id: 'app.library.actions.label',
|
||||
defaultMessage: 'Library actions',
|
||||
@@ -84,6 +90,12 @@ function openPageContextMenu(event: MouseEvent) {
|
||||
icon: PlusIcon,
|
||||
action: () => showCreationModal?.(),
|
||||
},
|
||||
+ {
|
||||
+ id: 'new_group',
|
||||
+ label: formatMessage(messages.newGroup),
|
||||
+ icon: SquarePlusIcon,
|
||||
+ action: () => librarySection.value?.openNewGroupModal(),
|
||||
+ },
|
||||
])
|
||||
}
|
||||
</script>
|
||||
@@ -100,7 +112,7 @@ function openPageContextMenu(event: MouseEvent) {
|
||||
v-if="recentInstances?.length > 0 && appSettings.getFeatureFlag('worlds_in_home')"
|
||||
:recent-instances="recentInstances"
|
||||
/>
|
||||
- <LibrarySection :instances="instances" />
|
||||
+ <LibrarySection ref="librarySection" :instances="instances" />
|
||||
<ContextMenu ref="pageOptions" :label="formatMessage(messages.libraryActionsLabel)" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,129 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Modrinth Enhanced <patches@modrinth-enhanced.invalid>
|
||||
Date: Fri, 18 Sep 2026 10:24:21 +0200
|
||||
Subject: [PATCH] Start in the language the system is set to
|
||||
|
||||
The app started in English until someone found the language setting,
|
||||
even on a desktop that is not in English. The first start now takes the
|
||||
language from the system, as long as upstream has that one translated to
|
||||
at least 70 percent - below that the app would be half English anyway,
|
||||
which reads worse than all of it being. The system is only asked once,
|
||||
and never over a language that was picked in the settings.
|
||||
---
|
||||
apps/app-frontend/src/App.vue | 17 ++++-
|
||||
.../app-frontend/src/helpers/system-locale.ts | 71 +++++++++++++++++++
|
||||
2 files changed, 85 insertions(+), 3 deletions(-)
|
||||
create mode 100644 apps/app-frontend/src/helpers/system-locale.ts
|
||||
|
||||
diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue
|
||||
index 871a8b9..0bc4490 100644
|
||||
--- a/apps/app-frontend/src/App.vue
|
||||
+++ b/apps/app-frontend/src/App.vue
|
||||
@@ -166,6 +166,7 @@ import {
|
||||
syncedServersQueryOptions,
|
||||
} from '@/helpers/synced-options'
|
||||
import { syncedPackQueryOptions } from '@/helpers/synced-packs'
|
||||
+import { localeFromSystem } from '@/helpers/system-locale'
|
||||
import { get_user_preferences } from '@/helpers/user-preferences.ts'
|
||||
import { parse_modrinth_user_link } from '@/helpers/users'
|
||||
import {
|
||||
@@ -938,9 +939,19 @@ async function setupApp() {
|
||||
pending_update_toast_for_version,
|
||||
} = await traceStartupStep('Read startup settings', getSettings)
|
||||
|
||||
- // Initialize locale from saved settings
|
||||
- if (locale) {
|
||||
- await traceStartupStep('Apply startup locale', () => setLocale(locale))
|
||||
+ // Initialize locale from saved settings, or on the very first start from the
|
||||
+ // language the desktop is set to.
|
||||
+ const startupLocale =
|
||||
+ (await traceStartupStep('Read the system language', () => localeFromSystem(locale)).catch(
|
||||
+ () => undefined,
|
||||
+ )) ?? locale
|
||||
+ if (startupLocale) {
|
||||
+ await traceStartupStep('Apply startup locale', () => setLocale(startupLocale))
|
||||
+ }
|
||||
+ if (startupLocale !== locale) {
|
||||
+ await getSettings()
|
||||
+ .then((settings) => setSettings({ ...settings, locale: startupLocale }))
|
||||
+ .catch(handleError)
|
||||
}
|
||||
|
||||
Object.assign(appSettings.featureFlags, feature_flags)
|
||||
diff --git a/apps/app-frontend/src/helpers/system-locale.ts b/apps/app-frontend/src/helpers/system-locale.ts
|
||||
new file mode 100644
|
||||
index 0000000..2e98fa7
|
||||
--- /dev/null
|
||||
+++ b/apps/app-frontend/src/helpers/system-locale.ts
|
||||
@@ -0,0 +1,71 @@
|
||||
+/**
|
||||
+ * The language the app starts in the first time it runs, taken from the one the
|
||||
+ * desktop is set to.
|
||||
+ *
|
||||
+ * Only languages upstream has translated far enough are taken. Below that the
|
||||
+ * app would be half English anyway, which reads worse than all of it being.
|
||||
+ */
|
||||
+import { LOCALES } from '@modrinth/ui'
|
||||
+import { languageCoverage } from '@modrinth/ui/src/layouts/wrapped/settings/language-settings/language-settings-coverage.generated'
|
||||
+import { locale as osLocale } from '@tauri-apps/plugin-os'
|
||||
+
|
||||
+/** How much of the app has to be in a language for it to be picked. */
|
||||
+const MIN_COVERAGE = 70
|
||||
+
|
||||
+/** Written once the system has been asked, so that only ever happens once. */
|
||||
+const ASKED_KEY = 'modrinth-enhanced:language-from-system'
|
||||
+
|
||||
+/** Windows says zh-Hans-CN; the script is what tells the two Chinese apart. */
|
||||
+const BY_SCRIPT: Record<string, string> = { hans: 'zh-CN', hant: 'zh-TW' }
|
||||
+
|
||||
+function coverage(code: string): number {
|
||||
+ return languageCoverage.app[code]?.percentage ?? 0
|
||||
+}
|
||||
+
|
||||
+/** The language the app calls a tag that is not one of its own codes. */
|
||||
+function preferred(language: string, region: string | undefined, script: string | undefined) {
|
||||
+ if (script) return BY_SCRIPT[script]
|
||||
+ // Spanish outside Spain is the Latin American one.
|
||||
+ if (language === 'es' && region && region !== 'es') return 'es-419'
|
||||
+ // Otherwise the country the language is named after: de → de-DE, fr → fr-FR.
|
||||
+ return `${language}-${language.toUpperCase()}`
|
||||
+}
|
||||
+
|
||||
+/** The best translated language the app has for `tag`, if it has one at all. */
|
||||
+function bestMatch(tag: string): string | undefined {
|
||||
+ // de_DE.UTF-8 is a language, a country and how it is encoded.
|
||||
+ const wanted = tag
|
||||
+ .replace(/[.@].*$/, '')
|
||||
+ .replaceAll('_', '-')
|
||||
+ .toLowerCase()
|
||||
+ const parts = wanted.split('-')
|
||||
+ const [language, region] = [parts[0], parts.at(-1)]
|
||||
+ const second = preferred(
|
||||
+ language,
|
||||
+ region,
|
||||
+ parts.find((part) => part in BY_SCRIPT),
|
||||
+ )
|
||||
+
|
||||
+ const rank = (code: string) => (code.toLowerCase() === wanted ? 2 : code === second ? 1 : 0)
|
||||
+
|
||||
+ return LOCALES.map((locale) => locale.code)
|
||||
+ .filter((code) => code.split('-')[0] === language)
|
||||
+ .sort((a, b) => rank(b) - rank(a) || coverage(b) - coverage(a))
|
||||
+ .find((code) => coverage(code) >= MIN_COVERAGE)
|
||||
+}
|
||||
+
|
||||
+/**
|
||||
+ * The language to start in, or nothing to stay with `current`.
|
||||
+ *
|
||||
+ * The system is only asked once, and never over a language that was picked in
|
||||
+ * the settings.
|
||||
+ */
|
||||
+export async function localeFromSystem(current: string): Promise<string | undefined> {
|
||||
+ if (localStorage.getItem(ASKED_KEY) || (current && current !== 'en-US')) return undefined
|
||||
+
|
||||
+ const system = await osLocale().catch(() => null)
|
||||
+ localStorage.setItem(ASKED_KEY, new Date().toISOString())
|
||||
+
|
||||
+ const match = bestMatch(system ?? navigator.language ?? '')
|
||||
+ return match && match !== current ? match : undefined
|
||||
+}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,43 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Modrinth Enhanced <patches@modrinth-enhanced.invalid>
|
||||
Date: Sat, 19 Sep 2026 02:04:37 +0200
|
||||
Subject: [PATCH] Keep WebKit's own menu off the interface's own images
|
||||
|
||||
---
|
||||
apps/app-frontend/src/App.vue | 11 ++++++++++-
|
||||
apps/app-frontend/src/pages/project/Gallery.vue | 2 +-
|
||||
2 files changed, 11 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue
|
||||
index 0bc4490..fcfb5e1 100644
|
||||
--- a/apps/app-frontend/src/App.vue
|
||||
+++ b/apps/app-frontend/src/App.vue
|
||||
@@ -2340,7 +2340,16 @@ function handleAuxClick(e) {
|
||||
function handleContextMenu(event) {
|
||||
const target = event.target
|
||||
if (target instanceof Element) {
|
||||
- if (target.closest('img, textarea, [contenteditable="true"]')) return
|
||||
+ // WebKit's own menu is worth having on the images a player may want to
|
||||
+ // save or copy — the ones in descriptions and galleries — but not on
|
||||
+ // every avatar and icon the interface is built from.
|
||||
+ if (
|
||||
+ target instanceof HTMLImageElement &&
|
||||
+ target.closest('.markdown-body, [data-native-menu]')
|
||||
+ ) {
|
||||
+ return
|
||||
+ }
|
||||
+ if (target.closest('textarea, [contenteditable="true"]')) return
|
||||
const input = target.closest('input')
|
||||
if (
|
||||
input &&
|
||||
diff --git a/apps/app-frontend/src/pages/project/Gallery.vue b/apps/app-frontend/src/pages/project/Gallery.vue
|
||||
index ac6a31c..70036d1 100644
|
||||
--- a/apps/app-frontend/src/pages/project/Gallery.vue
|
||||
+++ b/apps/app-frontend/src/pages/project/Gallery.vue
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
- <div class="gallery">
|
||||
+ <div class="gallery" data-native-menu>
|
||||
<Card v-for="(image, index) in filteredGallery" :key="image.url" class="gallery-item">
|
||||
<a @click="expandImage(image, index)">
|
||||
<img :src="image.url" :alt="image.title" class="gallery-image" />
|
||||
@@ -0,0 +1,30 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Modrinth Enhanced <patches@modrinth-enhanced.invalid>
|
||||
Date: Sat, 19 Sep 2026 02:04:37 +0200
|
||||
Subject: [PATCH] Fit the Minecraft sign-in modal to longer translations
|
||||
|
||||
---
|
||||
.../ui/minecraft-required-modal/MinecraftRequiredModal.vue | 4 ++--
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/apps/app-frontend/src/components/ui/minecraft-required-modal/MinecraftRequiredModal.vue b/apps/app-frontend/src/components/ui/minecraft-required-modal/MinecraftRequiredModal.vue
|
||||
index 99695db..2cb2e5d 100644
|
||||
--- a/apps/app-frontend/src/components/ui/minecraft-required-modal/MinecraftRequiredModal.vue
|
||||
+++ b/apps/app-frontend/src/components/ui/minecraft-required-modal/MinecraftRequiredModal.vue
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<NewModal ref="modal" :header="formatMessage(messages.header)" max-width="544px" no-padding>
|
||||
- <div class="grid grid-cols-[1fr_auto] gap-2.5 h-[154px] px-7 pt-4 pb-1 pr-9">
|
||||
+ <div class="grid grid-cols-[1fr_auto] gap-2.5 min-h-[154px] px-7 pt-4 pb-1 pr-9">
|
||||
<div class="flex flex-col gap-2.5 items-start justify-center h-min mt-5">
|
||||
<div class="font-semibold text-xl text-contrast">
|
||||
{{ formatMessage(messages.descriptionHeader) }}
|
||||
@@ -10,7 +10,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative h-full w-[96px] overflow-hidden mx-3">
|
||||
- <div class="absolute top-0 left-0 z-0 w-full flex grow-0 flex-col items-end p-0">
|
||||
+ <div class="absolute bottom-0 left-0 z-0 w-full flex grow-0 flex-col items-end p-0">
|
||||
<img :src="steveImage" alt="" class="self-stretch" />
|
||||
</div>
|
||||
<div
|
||||
@@ -0,0 +1,44 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Modrinth Enhanced <patches@modrinth-enhanced.invalid>
|
||||
Date: Sat, 19 Sep 2026 02:22:32 +0200
|
||||
Subject: [PATCH] Hold a button's label still as the pointer arrives
|
||||
|
||||
---
|
||||
.../src/components/ui/TitleBarAccountSwitcher.vue | 4 +++-
|
||||
packages/ui/src/components/base/buttons/ButtonFrame.vue | 6 +++++-
|
||||
2 files changed, 8 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/apps/app-frontend/src/components/ui/TitleBarAccountSwitcher.vue b/apps/app-frontend/src/components/ui/TitleBarAccountSwitcher.vue
|
||||
index b21fae7..f9be6c5 100644
|
||||
--- a/apps/app-frontend/src/components/ui/TitleBarAccountSwitcher.vue
|
||||
+++ b/apps/app-frontend/src/components/ui/TitleBarAccountSwitcher.vue
|
||||
@@ -28,8 +28,10 @@
|
||||
:key="account.profile.id"
|
||||
class="flex min-w-0 items-center gap-1"
|
||||
>
|
||||
+ <!-- btn-transparent: the same hover background as the buttons below,
|
||||
+ rather than button-base's filter, which makes the text shift. -->
|
||||
<button
|
||||
- class="button-base flex min-w-0 flex-1 cursor-pointer items-center gap-2 rounded-lg border-0 bg-transparent p-2 text-left"
|
||||
+ class="button-base btn-transparent flex min-w-0 flex-1 cursor-pointer items-center gap-2 rounded-lg border-0 bg-transparent p-2 text-left hover:bg-surface-4 focus-visible:bg-surface-4"
|
||||
@click="choose(account, close)"
|
||||
>
|
||||
<RadioButtonCheckedIcon v-if="isSelected(account)" class="size-5 shrink-0 text-brand" />
|
||||
diff --git a/packages/ui/src/components/base/buttons/ButtonFrame.vue b/packages/ui/src/components/base/buttons/ButtonFrame.vue
|
||||
index cc33922..6afd6a5 100644
|
||||
--- a/packages/ui/src/components/base/buttons/ButtonFrame.vue
|
||||
+++ b/packages/ui/src/components/base/buttons/ButtonFrame.vue
|
||||
@@ -53,8 +53,12 @@ const typeClasses: Record<ButtonType, string> = {
|
||||
}
|
||||
|
||||
const interactionClasses: Record<ButtonInteraction, string> = {
|
||||
+ // The background carries the hover on its own. Brightening the button as
|
||||
+ // well puts its label in a filter of its own, which WebKit rasterizes apart
|
||||
+ // from the page and lands a glyph or two off, so the text appears to shift
|
||||
+ // as the pointer arrives and again as it leaves.
|
||||
surface:
|
||||
- '[&:not(:disabled):not([aria-disabled=true]):hover]:bg-surface-4 [&:not(:disabled):not([aria-disabled=true]):focus-visible]:bg-surface-4',
|
||||
+ '[&:not(:disabled):not([aria-disabled=true]):hover]:bg-surface-4 [&:not(:disabled):not([aria-disabled=true]):focus-visible]:bg-surface-4 [&:not(:disabled):not([aria-disabled=true]):hover]:!filter-none [&:not(:disabled):not([aria-disabled=true]):focus-visible]:!filter-none',
|
||||
filled:
|
||||
'[&:not(:disabled):not([aria-disabled=true]):hover]:!bg-[--button-color] [&:not(:disabled):not([aria-disabled=true]):focus-visible]:!bg-[--button-color] [&:not(:disabled):not([aria-disabled=true]):hover]:!text-[var(--color-accent-contrast)] [&:not(:disabled):not([aria-disabled=true]):focus-visible]:!text-[var(--color-accent-contrast)]',
|
||||
none: '[&:not(:disabled):not([aria-disabled=true]):hover]:!brightness-100 [&:not(:disabled):not([aria-disabled=true]):focus-visible]:!brightness-100',
|
||||
@@ -0,0 +1,27 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Modrinth Enhanced <patches@modrinth-enhanced.invalid>
|
||||
Date: Sat, 19 Sep 2026 02:22:32 +0200
|
||||
Subject: [PATCH] Stay behind the game while another instance is running
|
||||
|
||||
---
|
||||
packages/app-lib/src/state/process.rs | 6 +++++-
|
||||
1 file changed, 5 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/packages/app-lib/src/state/process.rs b/packages/app-lib/src/state/process.rs
|
||||
index e9aaa3a..a4b24d0 100644
|
||||
--- a/packages/app-lib/src/state/process.rs
|
||||
+++ b/packages/app-lib/src/state/process.rs
|
||||
@@ -1064,8 +1064,12 @@ impl Process {
|
||||
let _ = state.friends_socket.update_status(None).await;
|
||||
|
||||
// If in tauri, window should show itself again after process exists if it was hidden
|
||||
+ //
|
||||
+ // Only once the last instance is gone, though: while another one is
|
||||
+ // still being played, taking the screen back from it is the last thing
|
||||
+ // the player wants.
|
||||
#[cfg(feature = "tauri")]
|
||||
- {
|
||||
+ if state.process_manager.get_all().is_empty() {
|
||||
let window = crate::EventState::get_main_window().await?;
|
||||
if let Some(window) = window {
|
||||
window.unminimize()?;
|
||||
@@ -0,0 +1,24 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Modrinth Enhanced <patches@modrinth-enhanced.invalid>
|
||||
Date: Sat, 19 Sep 2026 02:22:32 +0200
|
||||
Subject: [PATCH] Drop the tooltip from the sidebar settings' drag handles
|
||||
|
||||
---
|
||||
.../src/components/ui/settings/display/SidebarSettings.vue | 3 ++-
|
||||
1 file changed, 2 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/apps/app-frontend/src/components/ui/settings/display/SidebarSettings.vue b/apps/app-frontend/src/components/ui/settings/display/SidebarSettings.vue
|
||||
index dc0cd1f..8f2ea3a 100644
|
||||
--- a/apps/app-frontend/src/components/ui/settings/display/SidebarSettings.vue
|
||||
+++ b/apps/app-frontend/src/components/ui/settings/display/SidebarSettings.vue
|
||||
@@ -15,8 +15,9 @@
|
||||
<div
|
||||
class="flex cursor-grab items-center gap-3 rounded-2xl border border-solid border-surface-4 bg-surface-2 p-3 active:cursor-grabbing"
|
||||
>
|
||||
+ <!-- No tooltip: it would follow the pointer around a list whose
|
||||
+ every row has one, and stay up through the drag itself. -->
|
||||
<button
|
||||
- v-tooltip="formatMessage(messages.reorder)"
|
||||
type="button"
|
||||
class="flex cursor-grab border-none bg-transparent p-0 text-secondary active:cursor-grabbing"
|
||||
:aria-label="formatMessage(messages.reorder)"
|
||||
@@ -0,0 +1,535 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Modrinth Enhanced <patches@modrinth-enhanced.invalid>
|
||||
Date: Sat, 19 Sep 2026 02:22:32 +0200
|
||||
Subject: [PATCH] Keep Modrinth's translations off the text this fork rewrote
|
||||
|
||||
---
|
||||
.../minecraft-required-modal/MinecraftRequiredModal.vue | 4 ++--
|
||||
.../src/components/ui/modal/AppSettingsModal.vue | 2 +-
|
||||
.../components/ui/settings/account/PrivacySettings.vue | 2 +-
|
||||
apps/app-frontend/src/locales/de-CH/index.json | 9 +++++++++
|
||||
apps/app-frontend/src/locales/de-DE/index.json | 9 +++++++++
|
||||
apps/app-frontend/src/locales/es-419/index.json | 9 +++++++++
|
||||
apps/app-frontend/src/locales/es-ES/index.json | 9 +++++++++
|
||||
apps/app-frontend/src/locales/fr-FR/index.json | 9 +++++++++
|
||||
apps/app-frontend/src/locales/hu-HU/index.json | 9 +++++++++
|
||||
apps/app-frontend/src/locales/it-IT/index.json | 9 +++++++++
|
||||
apps/app-frontend/src/locales/ja-JP/index.json | 9 +++++++++
|
||||
apps/app-frontend/src/locales/nl-NL/index.json | 9 +++++++++
|
||||
apps/app-frontend/src/locales/pl-PL/index.json | 9 +++++++++
|
||||
apps/app-frontend/src/locales/pt-BR/index.json | 9 +++++++++
|
||||
apps/app-frontend/src/locales/ru-RU/index.json | 9 +++++++++
|
||||
apps/app-frontend/src/locales/sv-SE/index.json | 9 +++++++++
|
||||
apps/app-frontend/src/locales/tr-TR/index.json | 9 +++++++++
|
||||
apps/app-frontend/src/locales/uk-UA/index.json | 9 +++++++++
|
||||
apps/app-frontend/src/locales/zh-CN/index.json | 9 +++++++++
|
||||
apps/app-frontend/src/locales/zh-TW/index.json | 9 +++++++++
|
||||
20 files changed, 157 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/apps/app-frontend/src/components/ui/minecraft-required-modal/MinecraftRequiredModal.vue b/apps/app-frontend/src/components/ui/minecraft-required-modal/MinecraftRequiredModal.vue
|
||||
index 2cb2e5d..4c5e246 100644
|
||||
--- a/apps/app-frontend/src/components/ui/minecraft-required-modal/MinecraftRequiredModal.vue
|
||||
+++ b/apps/app-frontend/src/components/ui/minecraft-required-modal/MinecraftRequiredModal.vue
|
||||
@@ -87,7 +87,7 @@ const accountsCard = inject('accountsCard') as Ref<InstanceType<typeof AccountsC
|
||||
|
||||
const messages = defineMessages({
|
||||
header: {
|
||||
- id: 'minecraft-required.header',
|
||||
+ id: 'minecraft-required.modal-title',
|
||||
defaultMessage: 'Sign in to Minecraft',
|
||||
},
|
||||
descriptionHeader: {
|
||||
@@ -95,7 +95,7 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Sign in to a Microsoft account',
|
||||
},
|
||||
description: {
|
||||
- id: 'minecraft-required.description',
|
||||
+ id: 'minecraft-required.microsoft-explainer',
|
||||
defaultMessage:
|
||||
'A Microsoft account that owns Minecraft is what lets you play online and keep your skin.',
|
||||
},
|
||||
diff --git a/apps/app-frontend/src/components/ui/modal/AppSettingsModal.vue b/apps/app-frontend/src/components/ui/modal/AppSettingsModal.vue
|
||||
index e409510..77d3a15 100644
|
||||
--- a/apps/app-frontend/src/components/ui/modal/AppSettingsModal.vue
|
||||
+++ b/apps/app-frontend/src/components/ui/modal/AppSettingsModal.vue
|
||||
@@ -331,7 +331,7 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Downloading v{version}',
|
||||
},
|
||||
appVersion: {
|
||||
- id: 'app.settings.app-version',
|
||||
+ id: 'app.settings.enhanced-version',
|
||||
defaultMessage: 'Modrinth Enhanced {version}',
|
||||
},
|
||||
macos: {
|
||||
diff --git a/apps/app-frontend/src/components/ui/settings/account/PrivacySettings.vue b/apps/app-frontend/src/components/ui/settings/account/PrivacySettings.vue
|
||||
index d31e8c5..5061572 100644
|
||||
--- a/apps/app-frontend/src/components/ui/settings/account/PrivacySettings.vue
|
||||
+++ b/apps/app-frontend/src/components/ui/settings/account/PrivacySettings.vue
|
||||
@@ -38,7 +38,7 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Telemetry',
|
||||
},
|
||||
telemetryDescription: {
|
||||
- id: 'app.settings.privacy.telemetry.description',
|
||||
+ id: 'app.settings.privacy.telemetry.none',
|
||||
defaultMessage:
|
||||
'Modrinth Enhanced collects no analytics or usage data and sends no crash reports, so there is nothing here to turn off.',
|
||||
},
|
||||
diff --git a/apps/app-frontend/src/locales/de-CH/index.json b/apps/app-frontend/src/locales/de-CH/index.json
|
||||
index 0488f04..7e88f9e 100644
|
||||
--- a/apps/app-frontend/src/locales/de-CH/index.json
|
||||
+++ b/apps/app-frontend/src/locales/de-CH/index.json
|
||||
@@ -3173,6 +3173,9 @@
|
||||
"app.settings.privacy.telemetry.description": {
|
||||
"message": "Modrinth sammelt anonymisierte Analyse- und Nutzungsdaten, um unsere Nutzererfahrung zu verbessern und deine Erfahrung anzupassen. Wenn du diese Option deaktivierst, entscheidest du dich dagegen und deine Daten werden nicht mehr gesammelt."
|
||||
},
|
||||
+ "app.settings.privacy.telemetry.none": {
|
||||
+ "message": "Modrinth Enhanced sammelt keine Analyse- oder Nutzungsdaten und sendet keine Absturzberichte – hier gibt es also nichts abzuschalten."
|
||||
+ },
|
||||
"app.settings.privacy.telemetry.title": {
|
||||
"message": "Telemetrie"
|
||||
},
|
||||
@@ -5030,6 +5033,12 @@
|
||||
"minecraft-required.header": {
|
||||
"message": "Minecraft erforderlich"
|
||||
},
|
||||
+ "minecraft-required.microsoft-explainer": {
|
||||
+ "message": "Mit einem Microsoft-Konto, das Minecraft besitzt, kannst du online spielen und behältst deinen Skin."
|
||||
+ },
|
||||
+ "minecraft-required.modal-title": {
|
||||
+ "message": "Bei Minecraft anmelden"
|
||||
+ },
|
||||
"minecraft-required.offline-hint": {
|
||||
"message": "Oder melde dich mit Ely.by oder einem eigenen Server an – oder spiele Einzelspieler und Offline-Server ganz ohne Account."
|
||||
},
|
||||
diff --git a/apps/app-frontend/src/locales/de-DE/index.json b/apps/app-frontend/src/locales/de-DE/index.json
|
||||
index e84b362..cf8348e 100644
|
||||
--- a/apps/app-frontend/src/locales/de-DE/index.json
|
||||
+++ b/apps/app-frontend/src/locales/de-DE/index.json
|
||||
@@ -3173,6 +3173,9 @@
|
||||
"app.settings.privacy.telemetry.description": {
|
||||
"message": "Modrinth sammelt anonymisierte Analyse- und Nutzungsdaten, um unsere Nutzererfahrung zu verbessern und deine Erfahrung anzupassen. Wenn du diese Option deaktivierst, entscheidest du dich dagegen und deine Daten werden nicht mehr gesammelt."
|
||||
},
|
||||
+ "app.settings.privacy.telemetry.none": {
|
||||
+ "message": "Modrinth Enhanced sammelt keine Analyse- oder Nutzungsdaten und sendet keine Absturzberichte – hier gibt es also nichts abzuschalten."
|
||||
+ },
|
||||
"app.settings.privacy.telemetry.title": {
|
||||
"message": "Telemetrie"
|
||||
},
|
||||
@@ -5030,6 +5033,12 @@
|
||||
"minecraft-required.header": {
|
||||
"message": "Minecraft erforderlich"
|
||||
},
|
||||
+ "minecraft-required.microsoft-explainer": {
|
||||
+ "message": "Mit einem Microsoft-Konto, das Minecraft besitzt, kannst du online spielen und behältst deinen Skin."
|
||||
+ },
|
||||
+ "minecraft-required.modal-title": {
|
||||
+ "message": "Bei Minecraft anmelden"
|
||||
+ },
|
||||
"minecraft-required.offline-hint": {
|
||||
"message": "Oder melde dich mit Ely.by oder einem eigenen Server an – oder spiele Einzelspieler und Offline-Server ganz ohne Account."
|
||||
},
|
||||
diff --git a/apps/app-frontend/src/locales/es-419/index.json b/apps/app-frontend/src/locales/es-419/index.json
|
||||
index 20ca77b..0091df5 100644
|
||||
--- a/apps/app-frontend/src/locales/es-419/index.json
|
||||
+++ b/apps/app-frontend/src/locales/es-419/index.json
|
||||
@@ -3128,6 +3128,9 @@
|
||||
"app.settings.privacy.telemetry.description": {
|
||||
"message": "Modrinth recolecta datos analíticos anónimos y datos de uso para mejorar la experiencia de nuestros usuarios y para personalizar tu experiencia. Al desactivar esta opción, tus datos dejarán de ser recolectados."
|
||||
},
|
||||
+ "app.settings.privacy.telemetry.none": {
|
||||
+ "message": "Modrinth Enhanced no recopila datos de análisis ni de uso, y no envía informes de fallos, así que aquí no hay nada que desactivar."
|
||||
+ },
|
||||
"app.settings.privacy.telemetry.title": {
|
||||
"message": "Telemetría"
|
||||
},
|
||||
@@ -4979,6 +4982,12 @@
|
||||
"minecraft-required.header": {
|
||||
"message": "Minecraft requerido"
|
||||
},
|
||||
+ "minecraft-required.microsoft-explainer": {
|
||||
+ "message": "Una cuenta de Microsoft que tenga Minecraft es lo que te permite jugar en línea y conservar tu skin."
|
||||
+ },
|
||||
+ "minecraft-required.modal-title": {
|
||||
+ "message": "Inicia sesión en Minecraft"
|
||||
+ },
|
||||
"minecraft-required.offline-hint": {
|
||||
"message": "O inicia sesión con Ely.by o con un servidor personalizado, o juega en un jugador y en servidores sin conexión sin ninguna cuenta."
|
||||
},
|
||||
diff --git a/apps/app-frontend/src/locales/es-ES/index.json b/apps/app-frontend/src/locales/es-ES/index.json
|
||||
index 3a542e3..2e1d85b 100644
|
||||
--- a/apps/app-frontend/src/locales/es-ES/index.json
|
||||
+++ b/apps/app-frontend/src/locales/es-ES/index.json
|
||||
@@ -3101,6 +3101,9 @@
|
||||
"app.settings.privacy.telemetry.description": {
|
||||
"message": "Modrinth recopila análisis y datos de uso anónimos para mejorar la experiencia de usuario y personalizar tu experiencia. Deshabilitando esta opción sus datos dejarán de ser recopilados."
|
||||
},
|
||||
+ "app.settings.privacy.telemetry.none": {
|
||||
+ "message": "Modrinth Enhanced no recopila datos de análisis ni de uso, y no envía informes de fallos, así que aquí no hay nada que desactivar."
|
||||
+ },
|
||||
"app.settings.privacy.telemetry.title": {
|
||||
"message": "Telemetría"
|
||||
},
|
||||
@@ -4952,6 +4955,12 @@
|
||||
"minecraft-required.header": {
|
||||
"message": "Minecraft requerido"
|
||||
},
|
||||
+ "minecraft-required.microsoft-explainer": {
|
||||
+ "message": "Una cuenta de Microsoft que tenga Minecraft es lo que te permite jugar en línea y conservar tu skin."
|
||||
+ },
|
||||
+ "minecraft-required.modal-title": {
|
||||
+ "message": "Inicia sesión en Minecraft"
|
||||
+ },
|
||||
"minecraft-required.offline-hint": {
|
||||
"message": "O inicia sesión con Ely.by o con un servidor personalizado, o juega en un jugador y en servidores sin conexión sin ninguna cuenta."
|
||||
},
|
||||
diff --git a/apps/app-frontend/src/locales/fr-FR/index.json b/apps/app-frontend/src/locales/fr-FR/index.json
|
||||
index d554017..c376646 100644
|
||||
--- a/apps/app-frontend/src/locales/fr-FR/index.json
|
||||
+++ b/apps/app-frontend/src/locales/fr-FR/index.json
|
||||
@@ -2726,6 +2726,9 @@
|
||||
"app.settings.privacy.telemetry.description": {
|
||||
"message": "Modrinth collecte des données d'analyse et d'utilisation anonymes afin d'améliorer l'expérience utilisateur et de personnaliser votre expérience. En désactivant cette option, vous choisissez de ne plus participer et vos données ne seront plus collectées."
|
||||
},
|
||||
+ "app.settings.privacy.telemetry.none": {
|
||||
+ "message": "Modrinth Enhanced ne collecte aucune donnée d'analyse ou d'utilisation et n'envoie aucun rapport de plantage : il n'y a donc rien à désactiver ici."
|
||||
+ },
|
||||
"app.settings.privacy.telemetry.title": {
|
||||
"message": "Télémétrie"
|
||||
},
|
||||
@@ -4115,6 +4118,12 @@
|
||||
"minecraft-required.header": {
|
||||
"message": "Minecraft requis"
|
||||
},
|
||||
+ "minecraft-required.microsoft-explainer": {
|
||||
+ "message": "Un compte Microsoft qui possède Minecraft est ce qui permet de jouer en ligne et de garder son skin."
|
||||
+ },
|
||||
+ "minecraft-required.modal-title": {
|
||||
+ "message": "Se connecter à Minecraft"
|
||||
+ },
|
||||
"minecraft-required.offline-hint": {
|
||||
"message": "Ou connectez-vous avec Ely.by ou un serveur personnalisé, ou jouez en solo et sur des serveurs en mode hors ligne sans aucun compte."
|
||||
},
|
||||
diff --git a/apps/app-frontend/src/locales/hu-HU/index.json b/apps/app-frontend/src/locales/hu-HU/index.json
|
||||
index 090daaa..70ab085 100644
|
||||
--- a/apps/app-frontend/src/locales/hu-HU/index.json
|
||||
+++ b/apps/app-frontend/src/locales/hu-HU/index.json
|
||||
@@ -3170,6 +3170,9 @@
|
||||
"app.settings.privacy.telemetry.description": {
|
||||
"message": "A Modrinth névtelen használati és telemetriai adatokat gyűjt a felhasználói élmény javítása és személyre szabása érdekében. Ha kikapcsolod ezt a beállítást, leiratkozol erről, és a rendszer többé nem gyűjti az adataidat."
|
||||
},
|
||||
+ "app.settings.privacy.telemetry.none": {
|
||||
+ "message": "A Modrinth Enhanced nem gyűjt elemzési vagy használati adatokat, és nem küld összeomlási jelentéseket, így itt nincs mit kikapcsolni."
|
||||
+ },
|
||||
"app.settings.privacy.telemetry.title": {
|
||||
"message": "Telemetria"
|
||||
},
|
||||
@@ -5027,6 +5030,12 @@
|
||||
"minecraft-required.header": {
|
||||
"message": "Minecraft szükséges"
|
||||
},
|
||||
+ "minecraft-required.microsoft-explainer": {
|
||||
+ "message": "Egy Minecrafttal rendelkező Microsoft-fiók az, ami lehetővé teszi az online játékot és a skin megtartását."
|
||||
+ },
|
||||
+ "minecraft-required.modal-title": {
|
||||
+ "message": "Bejelentkezés a Minecraftba"
|
||||
+ },
|
||||
"minecraft-required.offline-hint": {
|
||||
"message": "Vagy jelentkezz be Ely.byjal vagy saját szerverrel, vagy játssz egyjátékos módban és offline szervereken bármilyen fiók nélkül."
|
||||
},
|
||||
diff --git a/apps/app-frontend/src/locales/it-IT/index.json b/apps/app-frontend/src/locales/it-IT/index.json
|
||||
index 4fbc1d0..c915929 100644
|
||||
--- a/apps/app-frontend/src/locales/it-IT/index.json
|
||||
+++ b/apps/app-frontend/src/locales/it-IT/index.json
|
||||
@@ -3167,6 +3167,9 @@
|
||||
"app.settings.privacy.telemetry.description": {
|
||||
"message": "Modrinth raccoglie dati anonimizzati di analisi e utilizzo per migliorare l'esperienza utente e personalizzarla. Disabilitando questa opzione neghi il consenso e i tuoi dati non saranno più raccolti."
|
||||
},
|
||||
+ "app.settings.privacy.telemetry.none": {
|
||||
+ "message": "Modrinth Enhanced non raccoglie dati di analisi o di utilizzo e non invia segnalazioni di arresto anomalo: qui non c'è nulla da disattivare."
|
||||
+ },
|
||||
"app.settings.privacy.telemetry.title": {
|
||||
"message": "Telemetria"
|
||||
},
|
||||
@@ -5024,6 +5027,12 @@
|
||||
"minecraft-required.header": {
|
||||
"message": "Minecraft richiesto"
|
||||
},
|
||||
+ "minecraft-required.microsoft-explainer": {
|
||||
+ "message": "Un account Microsoft che possiede Minecraft è ciò che ti permette di giocare online e di mantenere la tua skin."
|
||||
+ },
|
||||
+ "minecraft-required.modal-title": {
|
||||
+ "message": "Accedi a Minecraft"
|
||||
+ },
|
||||
"minecraft-required.offline-hint": {
|
||||
"message": "Oppure accedi con Ely.by o con un server personalizzato, o gioca in singolo e sui server in modalità offline senza alcun account."
|
||||
},
|
||||
diff --git a/apps/app-frontend/src/locales/ja-JP/index.json b/apps/app-frontend/src/locales/ja-JP/index.json
|
||||
index 9b98f35..9d1581a 100644
|
||||
--- a/apps/app-frontend/src/locales/ja-JP/index.json
|
||||
+++ b/apps/app-frontend/src/locales/ja-JP/index.json
|
||||
@@ -2099,6 +2099,9 @@
|
||||
"app.settings.privacy.telemetry.description": {
|
||||
"message": "Modrinthはユーザー体験の向上および最適化のために、匿名化されたアナリティクスと利用データを収集しています。このオプションをオフにすることで無効化(オプトアウト)され、以降データの収集は行われません。"
|
||||
},
|
||||
+ "app.settings.privacy.telemetry.none": {
|
||||
+ "message": "Modrinth Enhanced は分析データや利用データを一切収集せず、クラッシュレポートも送信しません。そのため、ここで無効にするものはありません。"
|
||||
+ },
|
||||
"app.settings.privacy.telemetry.title": {
|
||||
"message": "テレメトリ"
|
||||
},
|
||||
@@ -3605,6 +3608,12 @@
|
||||
"minecraft-required.header": {
|
||||
"message": "Minecraftが必要です"
|
||||
},
|
||||
+ "minecraft-required.microsoft-explainer": {
|
||||
+ "message": "Minecraft を所有する Microsoft アカウントがあれば、オンラインでプレイでき、スキンもそのまま使えます。"
|
||||
+ },
|
||||
+ "minecraft-required.modal-title": {
|
||||
+ "message": "Minecraft にサインイン"
|
||||
+ },
|
||||
"minecraft-required.offline-hint": {
|
||||
"message": "Ely.by やカスタムサーバーでサインインすることも、アカウントなしでシングルプレイやオフラインモードのサーバーで遊ぶこともできます。"
|
||||
},
|
||||
diff --git a/apps/app-frontend/src/locales/nl-NL/index.json b/apps/app-frontend/src/locales/nl-NL/index.json
|
||||
index b153a78..8d634da 100644
|
||||
--- a/apps/app-frontend/src/locales/nl-NL/index.json
|
||||
+++ b/apps/app-frontend/src/locales/nl-NL/index.json
|
||||
@@ -3035,6 +3035,9 @@
|
||||
"app.settings.privacy.telemetry.description": {
|
||||
"message": "Modrinth verzamelt geanonimiseerde analyse- en gebruiksgegevens om de gebruikerservaring te verbeteren en uw ervaring te personaliseren. Door deze optie uit te schakelen, meldt u zich af en worden uw gegevens niet langer verzameld."
|
||||
},
|
||||
+ "app.settings.privacy.telemetry.none": {
|
||||
+ "message": "Modrinth Enhanced verzamelt geen analyse- of gebruiksgegevens en stuurt geen crashrapporten, dus hier valt niets uit te schakelen."
|
||||
+ },
|
||||
"app.settings.privacy.telemetry.title": {
|
||||
"message": "Telemetrie"
|
||||
},
|
||||
@@ -4634,6 +4637,12 @@
|
||||
"minecraft-required.header": {
|
||||
"message": "Minecraft vereist"
|
||||
},
|
||||
+ "minecraft-required.microsoft-explainer": {
|
||||
+ "message": "Met een Microsoft-account dat Minecraft bezit kun je online spelen en houd je je skin."
|
||||
+ },
|
||||
+ "minecraft-required.modal-title": {
|
||||
+ "message": "Inloggen bij Minecraft"
|
||||
+ },
|
||||
"minecraft-required.offline-hint": {
|
||||
"message": "Of log in met Ely.by of een eigen server, of speel singleplayer en servers in offlinemodus zonder enig account."
|
||||
},
|
||||
diff --git a/apps/app-frontend/src/locales/pl-PL/index.json b/apps/app-frontend/src/locales/pl-PL/index.json
|
||||
index 42122c9..6c8cf09 100644
|
||||
--- a/apps/app-frontend/src/locales/pl-PL/index.json
|
||||
+++ b/apps/app-frontend/src/locales/pl-PL/index.json
|
||||
@@ -3161,6 +3161,9 @@
|
||||
"app.settings.privacy.telemetry.description": {
|
||||
"message": "Modrinth zbiera anonimowe dane analityczne i użytkowania, aby poprawić komfort naszych użytkowników i dostosować Twoje doświadczenia. Wyłączając tę opcję, rezygnujesz i Twoje dane nie będą już gromadzone."
|
||||
},
|
||||
+ "app.settings.privacy.telemetry.none": {
|
||||
+ "message": "Modrinth Enhanced nie zbiera żadnych danych analitycznych ani danych o użyciu i nie wysyła raportów o awariach, więc nie ma tu czego wyłączać."
|
||||
+ },
|
||||
"app.settings.privacy.telemetry.title": {
|
||||
"message": "Telemetria"
|
||||
},
|
||||
@@ -5018,6 +5021,12 @@
|
||||
"minecraft-required.header": {
|
||||
"message": "Minecraft wymagany"
|
||||
},
|
||||
+ "minecraft-required.microsoft-explainer": {
|
||||
+ "message": "Konto Microsoft z Minecraftem pozwala grać online i zachować swój skin."
|
||||
+ },
|
||||
+ "minecraft-required.modal-title": {
|
||||
+ "message": "Zaloguj się do Minecrafta"
|
||||
+ },
|
||||
"minecraft-required.offline-hint": {
|
||||
"message": "Albo zaloguj się przez Ely.by lub własny serwer, albo graj w trybie jednoosobowym i na serwerach offline zupełnie bez konta."
|
||||
},
|
||||
diff --git a/apps/app-frontend/src/locales/pt-BR/index.json b/apps/app-frontend/src/locales/pt-BR/index.json
|
||||
index ad277b3..a49acbe 100644
|
||||
--- a/apps/app-frontend/src/locales/pt-BR/index.json
|
||||
+++ b/apps/app-frontend/src/locales/pt-BR/index.json
|
||||
@@ -3173,6 +3173,9 @@
|
||||
"app.settings.privacy.telemetry.description": {
|
||||
"message": "O Modrinth coleta analíticas anonimizadas e uso de dados para aprimorar nossa experiência de usuário e personalizar sua experiência. Ao desativar esta opção, você negará e seus dados não serão mais coletados."
|
||||
},
|
||||
+ "app.settings.privacy.telemetry.none": {
|
||||
+ "message": "O Modrinth Enhanced não coleta dados de análise ou de uso e não envia relatórios de falha, então não há nada para desativar aqui."
|
||||
+ },
|
||||
"app.settings.privacy.telemetry.title": {
|
||||
"message": "Telemetria"
|
||||
},
|
||||
@@ -5030,6 +5033,12 @@
|
||||
"minecraft-required.header": {
|
||||
"message": "Minecraft necessário"
|
||||
},
|
||||
+ "minecraft-required.microsoft-explainer": {
|
||||
+ "message": "Uma conta Microsoft que possui o Minecraft é o que permite jogar online e manter sua skin."
|
||||
+ },
|
||||
+ "minecraft-required.modal-title": {
|
||||
+ "message": "Entrar no Minecraft"
|
||||
+ },
|
||||
"minecraft-required.offline-hint": {
|
||||
"message": "Ou entre com o Ely.by ou com um servidor personalizado, ou jogue sozinho e em servidores offline sem conta nenhuma."
|
||||
},
|
||||
diff --git a/apps/app-frontend/src/locales/ru-RU/index.json b/apps/app-frontend/src/locales/ru-RU/index.json
|
||||
index 460fc40..526c357 100644
|
||||
--- a/apps/app-frontend/src/locales/ru-RU/index.json
|
||||
+++ b/apps/app-frontend/src/locales/ru-RU/index.json
|
||||
@@ -3149,6 +3149,9 @@
|
||||
"app.settings.privacy.telemetry.description": {
|
||||
"message": "Modrinth собирает анонимные данные об использовании для персонализации и улучшения сервиса. Отключите этот параметр, чтобы отказаться от сбора данных."
|
||||
},
|
||||
+ "app.settings.privacy.telemetry.none": {
|
||||
+ "message": "Modrinth Enhanced не собирает аналитику и данные об использовании и не отправляет отчёты о сбоях, поэтому отключать здесь нечего."
|
||||
+ },
|
||||
"app.settings.privacy.telemetry.title": {
|
||||
"message": "Телеметрия"
|
||||
},
|
||||
@@ -5003,6 +5006,12 @@
|
||||
"minecraft-required.header": {
|
||||
"message": "Требуется Minecraft"
|
||||
},
|
||||
+ "minecraft-required.microsoft-explainer": {
|
||||
+ "message": "Аккаунт Microsoft с купленным Minecraft — это то, что позволяет играть по сети и сохранять свой скин."
|
||||
+ },
|
||||
+ "minecraft-required.modal-title": {
|
||||
+ "message": "Вход в Minecraft"
|
||||
+ },
|
||||
"minecraft-required.offline-hint": {
|
||||
"message": "Или войдите через Ely.by либо свой сервер — или играйте в одиночной игре и на офлайн-серверах вообще без учётной записи."
|
||||
},
|
||||
diff --git a/apps/app-frontend/src/locales/sv-SE/index.json b/apps/app-frontend/src/locales/sv-SE/index.json
|
||||
index b3ab7e7..90aa3c3 100644
|
||||
--- a/apps/app-frontend/src/locales/sv-SE/index.json
|
||||
+++ b/apps/app-frontend/src/locales/sv-SE/index.json
|
||||
@@ -2540,6 +2540,9 @@
|
||||
"app.settings.privacy.telemetry.description": {
|
||||
"message": "Modrinth samlar in anonymiserad statistik och användningsdata för att förbättra vår användarupplevelse och anpassa din upplevelse. Genom att inaktivera det här alternativet avanmäler du dig och din data kommer inte längre samlas in."
|
||||
},
|
||||
+ "app.settings.privacy.telemetry.none": {
|
||||
+ "message": "Modrinth Enhanced samlar inte in någon analys- eller användningsdata och skickar inga kraschrapporter, så här finns inget att stänga av."
|
||||
+ },
|
||||
"app.settings.privacy.telemetry.title": {
|
||||
"message": "Telemetri"
|
||||
},
|
||||
@@ -4091,6 +4094,12 @@
|
||||
"minecraft-required.header": {
|
||||
"message": "Minecraft krävs"
|
||||
},
|
||||
+ "minecraft-required.microsoft-explainer": {
|
||||
+ "message": "Ett Microsoft-konto som äger Minecraft är det som låter dig spela online och behålla ditt skin."
|
||||
+ },
|
||||
+ "minecraft-required.modal-title": {
|
||||
+ "message": "Logga in på Minecraft"
|
||||
+ },
|
||||
"minecraft-required.offline-hint": {
|
||||
"message": "Eller logga in med Ely.by eller en egen server, eller spela ensamspelarvärldar och servrar i offlineläge helt utan konto."
|
||||
},
|
||||
diff --git a/apps/app-frontend/src/locales/tr-TR/index.json b/apps/app-frontend/src/locales/tr-TR/index.json
|
||||
index 2a96b4d..ea63a7b 100644
|
||||
--- a/apps/app-frontend/src/locales/tr-TR/index.json
|
||||
+++ b/apps/app-frontend/src/locales/tr-TR/index.json
|
||||
@@ -2501,6 +2501,9 @@
|
||||
"app.settings.privacy.telemetry.description": {
|
||||
"message": "Modrinth kullanıcı deneyimimizi geliştirmek için ve deneyiminizi kişiselleştirmek için anonimleştirilmiş analiz ve kullanım verilerini toplar. Bu seçeneği devre dışı bırakarak, bundan vazgeçmiş olursunuz ve verileriniz artık toplanmayacaktır."
|
||||
},
|
||||
+ "app.settings.privacy.telemetry.none": {
|
||||
+ "message": "Modrinth Enhanced hiçbir analiz veya kullanım verisi toplamaz ve çökme raporu göndermez; burada kapatılacak bir şey yok."
|
||||
+ },
|
||||
"app.settings.privacy.telemetry.title": {
|
||||
"message": "Telemetri"
|
||||
},
|
||||
@@ -4010,6 +4013,12 @@
|
||||
"minecraft-required.header": {
|
||||
"message": "Minecraft gereklidir"
|
||||
},
|
||||
+ "minecraft-required.microsoft-explainer": {
|
||||
+ "message": "Minecraft'a sahip bir Microsoft hesabı, çevrim içi oynamanı ve görünümünü korumanı sağlayan şeydir."
|
||||
+ },
|
||||
+ "minecraft-required.modal-title": {
|
||||
+ "message": "Minecraft'a giriş yap"
|
||||
+ },
|
||||
"minecraft-required.offline-hint": {
|
||||
"message": "Ya da Ely.by veya özel bir sunucuyla oturum açın; ya da hiçbir hesap olmadan tek oyunculu ve çevrimdışı sunucularda oynayın."
|
||||
},
|
||||
diff --git a/apps/app-frontend/src/locales/uk-UA/index.json b/apps/app-frontend/src/locales/uk-UA/index.json
|
||||
index 74ba938..0f6a6a4 100644
|
||||
--- a/apps/app-frontend/src/locales/uk-UA/index.json
|
||||
+++ b/apps/app-frontend/src/locales/uk-UA/index.json
|
||||
@@ -3161,6 +3161,9 @@
|
||||
"app.settings.privacy.telemetry.description": {
|
||||
"message": "Modrinth збирає анонімізовану аналітику та дані про використання для покращення зручності та адаптації сервісу. Вимкнувши цю опцію, ви відмовляєтеся від збору, і ваші дані більше не збиратимуться."
|
||||
},
|
||||
+ "app.settings.privacy.telemetry.none": {
|
||||
+ "message": "Modrinth Enhanced не збирає аналітику та дані про використання й не надсилає звіти про збої, тож тут нема чого вимикати."
|
||||
+ },
|
||||
"app.settings.privacy.telemetry.title": {
|
||||
"message": "Телеметрія"
|
||||
},
|
||||
@@ -5018,6 +5021,12 @@
|
||||
"minecraft-required.header": {
|
||||
"message": "Потрібний Minecraft"
|
||||
},
|
||||
+ "minecraft-required.microsoft-explainer": {
|
||||
+ "message": "Обліковий запис Microsoft із придбаним Minecraft — це те, що дозволяє грати онлайн і зберігати свій скін."
|
||||
+ },
|
||||
+ "minecraft-required.modal-title": {
|
||||
+ "message": "Вхід у Minecraft"
|
||||
+ },
|
||||
"minecraft-required.offline-hint": {
|
||||
"message": "Або увійдіть через Ely.by чи власний сервер — або грайте в одиночній грі та на офлайн-серверах узагалі без облікового запису."
|
||||
},
|
||||
diff --git a/apps/app-frontend/src/locales/zh-CN/index.json b/apps/app-frontend/src/locales/zh-CN/index.json
|
||||
index e408a3d..9c0cf39 100644
|
||||
--- a/apps/app-frontend/src/locales/zh-CN/index.json
|
||||
+++ b/apps/app-frontend/src/locales/zh-CN/index.json
|
||||
@@ -3122,6 +3122,9 @@
|
||||
"app.settings.privacy.telemetry.description": {
|
||||
"message": "Modrinth 收集匿名分析和使用数据,以提升用户体验并为您提供个性化服务。若禁用此选项,即表示您选择退出,您的数据将不再被收集。"
|
||||
},
|
||||
+ "app.settings.privacy.telemetry.none": {
|
||||
+ "message": "Modrinth Enhanced 不收集任何分析或使用数据,也不发送崩溃报告,因此这里没有可关闭的内容。"
|
||||
+ },
|
||||
"app.settings.privacy.telemetry.title": {
|
||||
"message": "遥测"
|
||||
},
|
||||
@@ -4901,6 +4904,12 @@
|
||||
"minecraft-required.header": {
|
||||
"message": "需要 Minecraft"
|
||||
},
|
||||
+ "minecraft-required.microsoft-explainer": {
|
||||
+ "message": "拥有 Minecraft 的 Microsoft 账号才能联机游戏并保留你的皮肤。"
|
||||
+ },
|
||||
+ "minecraft-required.modal-title": {
|
||||
+ "message": "登录 Minecraft"
|
||||
+ },
|
||||
"minecraft-required.offline-hint": {
|
||||
"message": "也可以使用 Ely.by 或自定义服务器登录,或者不用任何账户,直接玩单人游戏和离线模式服务器。"
|
||||
},
|
||||
diff --git a/apps/app-frontend/src/locales/zh-TW/index.json b/apps/app-frontend/src/locales/zh-TW/index.json
|
||||
index 6b5ec6d..50bd256 100644
|
||||
--- a/apps/app-frontend/src/locales/zh-TW/index.json
|
||||
+++ b/apps/app-frontend/src/locales/zh-TW/index.json
|
||||
@@ -3173,6 +3173,9 @@
|
||||
"app.settings.privacy.telemetry.description": {
|
||||
"message": "Modrinth 會收集匿名化的分析與使用資料,以提升使用者體驗並提供客製化服務。若停用此選項,即表示你選擇退出,你的資料將不再被收集。"
|
||||
},
|
||||
+ "app.settings.privacy.telemetry.none": {
|
||||
+ "message": "Modrinth Enhanced 不會收集任何分析或使用資料,也不會傳送當機報告,因此這裡沒有可以關閉的項目。"
|
||||
+ },
|
||||
"app.settings.privacy.telemetry.title": {
|
||||
"message": "遙測"
|
||||
},
|
||||
@@ -5030,6 +5033,12 @@
|
||||
"minecraft-required.header": {
|
||||
"message": "需要 Minecraft"
|
||||
},
|
||||
+ "minecraft-required.microsoft-explainer": {
|
||||
+ "message": "擁有 Minecraft 的 Microsoft 帳號才能連線遊玩並保留你的外觀。"
|
||||
+ },
|
||||
+ "minecraft-required.modal-title": {
|
||||
+ "message": "登入 Minecraft"
|
||||
+ },
|
||||
"minecraft-required.offline-hint": {
|
||||
"message": "也可以用 Ely.by 或自訂伺服器登入,或者不用任何帳號,直接玩單人遊戲和離線模式的伺服器。"
|
||||
},
|
||||
@@ -0,0 +1,222 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Modrinth Enhanced <patches@modrinth-enhanced.invalid>
|
||||
Date: Sat, 19 Sep 2026 02:42:55 +0200
|
||||
Subject: [PATCH] Show the running instance bar only while one is running
|
||||
|
||||
---
|
||||
.../src/components/ui/AppActionBar.vue | 189 +++++++++---------
|
||||
1 file changed, 91 insertions(+), 98 deletions(-)
|
||||
|
||||
diff --git a/apps/app-frontend/src/components/ui/AppActionBar.vue b/apps/app-frontend/src/components/ui/AppActionBar.vue
|
||||
index eff399e..d4648a5 100644
|
||||
--- a/apps/app-frontend/src/components/ui/AppActionBar.vue
|
||||
+++ b/apps/app-frontend/src/components/ui/AppActionBar.vue
|
||||
@@ -17,106 +17,103 @@
|
||||
<span class="text-sm text-contrast"> {{ formatMessage(messages.offline) }} </span>
|
||||
</div>
|
||||
<AppUpdateButton />
|
||||
+ <!-- Nothing running is worth no chip at all: it is there while an
|
||||
+ instance is, and out of the way the rest of the time. -->
|
||||
<div
|
||||
+ v-if="selectedProcess"
|
||||
class="flex border-solid border-surface-5 text-sm font-medium items-center gap-2 py-1.5 px-3 rounded-xl border"
|
||||
>
|
||||
- <template v-if="selectedProcess">
|
||||
- <OnlineIndicatorIcon />
|
||||
- <div class="text-contrast flex items-center gap-2">
|
||||
- <router-link
|
||||
- v-tooltip="formatMessage(messages.viewInstance)"
|
||||
- :to="`/instance/${encodeURIComponent(selectedProcess.instance.id)}`"
|
||||
- class="hover:underline"
|
||||
- >
|
||||
- {{ selectedProcess.instance.name }}
|
||||
- </router-link>
|
||||
- <FloatingMenu
|
||||
- v-if="currentProcesses.length > 1"
|
||||
- placement="bottom"
|
||||
- @open="showInstances = true"
|
||||
- @close="showInstances = false"
|
||||
+ <OnlineIndicatorIcon />
|
||||
+ <div class="text-contrast flex items-center gap-2">
|
||||
+ <router-link
|
||||
+ v-tooltip="formatMessage(messages.viewInstance)"
|
||||
+ :to="`/instance/${encodeURIComponent(selectedProcess.instance.id)}`"
|
||||
+ class="hover:underline"
|
||||
+ >
|
||||
+ {{ selectedProcess.instance.name }}
|
||||
+ </router-link>
|
||||
+ <FloatingMenu
|
||||
+ v-if="currentProcesses.length > 1"
|
||||
+ placement="bottom"
|
||||
+ @open="showInstances = true"
|
||||
+ @close="showInstances = false"
|
||||
+ >
|
||||
+ <IconButton
|
||||
+ v-tooltip="
|
||||
+ showInstances
|
||||
+ ? formatMessage(messages.hideMoreRunningInstances)
|
||||
+ : formatMessage(messages.showMoreRunningInstances)
|
||||
+ "
|
||||
+ class="!size-6"
|
||||
+ type="quiet"
|
||||
+ size="xs"
|
||||
+ :label="
|
||||
+ showInstances
|
||||
+ ? formatMessage(messages.hideMoreRunningInstances)
|
||||
+ : formatMessage(messages.showMoreRunningInstances)
|
||||
+ "
|
||||
>
|
||||
- <IconButton
|
||||
- v-tooltip="
|
||||
- showInstances
|
||||
- ? formatMessage(messages.hideMoreRunningInstances)
|
||||
- : formatMessage(messages.showMoreRunningInstances)
|
||||
- "
|
||||
- class="!size-6"
|
||||
- type="quiet"
|
||||
- size="xs"
|
||||
- :label="
|
||||
- showInstances
|
||||
- ? formatMessage(messages.hideMoreRunningInstances)
|
||||
- : formatMessage(messages.showMoreRunningInstances)
|
||||
- "
|
||||
- >
|
||||
- <DropdownIcon :class="{ 'rotate-180': !!showInstances }" />
|
||||
- </IconButton>
|
||||
- <template #popper>
|
||||
- <div class="flex w-[20rem] max-h-[24rem] flex-col gap-2 overflow-auto">
|
||||
- <div
|
||||
- v-for="process in currentProcesses"
|
||||
- :key="process.uuid"
|
||||
- class="flex w-full items-center gap-2 rounded-xl bg-surface-4 p-2 text-sm"
|
||||
+ <DropdownIcon :class="{ 'rotate-180': !!showInstances }" />
|
||||
+ </IconButton>
|
||||
+ <template #popper>
|
||||
+ <div class="flex w-[20rem] max-h-[24rem] flex-col gap-2 overflow-auto">
|
||||
+ <div
|
||||
+ v-for="process in currentProcesses"
|
||||
+ :key="process.uuid"
|
||||
+ class="flex w-full items-center gap-2 rounded-xl bg-surface-4 p-2 text-sm"
|
||||
+ >
|
||||
+ <button
|
||||
+ v-tooltip.left="
|
||||
+ process.uuid === selectedProcess.uuid
|
||||
+ ? formatMessage(messages.primaryInstance)
|
||||
+ : formatMessage(messages.makePrimaryInstance)
|
||||
+ "
|
||||
+ class="flex flex-grow items-center gap-2"
|
||||
+ :class="{
|
||||
+ 'active:scale-95 transition-transform': process.uuid !== selectedProcess.uuid,
|
||||
+ }"
|
||||
+ :disabled="process.uuid === selectedProcess.uuid"
|
||||
+ @click="selectProcess(process)"
|
||||
+ >
|
||||
+ <OnlineIndicatorIcon />
|
||||
+ <span class="mr-auto text-contrast flex items-center gap-2">
|
||||
+ {{ process.instance.name }}
|
||||
+ <StarIcon v-if="process.uuid === selectedProcess.uuid" class="text-orange" />
|
||||
+ </span>
|
||||
+ </button>
|
||||
+ <button
|
||||
+ v-tooltip="formatMessage(messages.stopInstance)"
|
||||
+ class="active:scale-95 flex"
|
||||
+ @click.stop="stop(process)"
|
||||
+ >
|
||||
+ <StopCircleIcon class="text-red size-5" />
|
||||
+ </button>
|
||||
+ <button
|
||||
+ v-tooltip="formatMessage(messages.viewLogs)"
|
||||
+ class="active:scale-95 flex"
|
||||
+ @click.stop="goToTerminal(process.instance.id)"
|
||||
>
|
||||
- <button
|
||||
- v-tooltip.left="
|
||||
- process.uuid === selectedProcess.uuid
|
||||
- ? formatMessage(messages.primaryInstance)
|
||||
- : formatMessage(messages.makePrimaryInstance)
|
||||
- "
|
||||
- class="flex flex-grow items-center gap-2"
|
||||
- :class="{
|
||||
- 'active:scale-95 transition-transform': process.uuid !== selectedProcess.uuid,
|
||||
- }"
|
||||
- :disabled="process.uuid === selectedProcess.uuid"
|
||||
- @click="selectProcess(process)"
|
||||
- >
|
||||
- <OnlineIndicatorIcon />
|
||||
- <span class="mr-auto text-contrast flex items-center gap-2">
|
||||
- {{ process.instance.name }}
|
||||
- <StarIcon v-if="process.uuid === selectedProcess.uuid" class="text-orange" />
|
||||
- </span>
|
||||
- </button>
|
||||
- <button
|
||||
- v-tooltip="formatMessage(messages.stopInstance)"
|
||||
- class="active:scale-95 flex"
|
||||
- @click.stop="stop(process)"
|
||||
- >
|
||||
- <StopCircleIcon class="text-red size-5" />
|
||||
- </button>
|
||||
- <button
|
||||
- v-tooltip="formatMessage(messages.viewLogs)"
|
||||
- class="active:scale-95 flex"
|
||||
- @click.stop="goToTerminal(process.instance.id)"
|
||||
- >
|
||||
- <TerminalSquareIcon class="text-secondary size-5" />
|
||||
- </button>
|
||||
- </div>
|
||||
+ <TerminalSquareIcon class="text-secondary size-5" />
|
||||
+ </button>
|
||||
</div>
|
||||
- </template>
|
||||
- </FloatingMenu>
|
||||
- </div>
|
||||
- <button
|
||||
- v-tooltip="formatMessage(messages.stopInstance)"
|
||||
- class="active:scale-95 flex"
|
||||
- @click="stop(selectedProcess)"
|
||||
- >
|
||||
- <StopCircleIcon class="text-red size-5" />
|
||||
- </button>
|
||||
- <button
|
||||
- v-tooltip="formatMessage(messages.viewLogs)"
|
||||
- class="active:scale-95 flex"
|
||||
- @click="goToTerminal()"
|
||||
- >
|
||||
- <TerminalSquareIcon class="text-secondary size-5" />
|
||||
- </button>
|
||||
- </template>
|
||||
- <template v-else>
|
||||
- <span class="size-2 rounded-full bg-secondary" />
|
||||
- <span class="text-secondary"> {{ formatMessage(messages.noInstancesRunning) }} </span>
|
||||
- </template>
|
||||
+ </div>
|
||||
+ </template>
|
||||
+ </FloatingMenu>
|
||||
+ </div>
|
||||
+ <button
|
||||
+ v-tooltip="formatMessage(messages.stopInstance)"
|
||||
+ class="active:scale-95 flex"
|
||||
+ @click="stop(selectedProcess)"
|
||||
+ >
|
||||
+ <StopCircleIcon class="text-red size-5" />
|
||||
+ </button>
|
||||
+ <button
|
||||
+ v-tooltip="formatMessage(messages.viewLogs)"
|
||||
+ class="active:scale-95 flex"
|
||||
+ @click="goToTerminal()"
|
||||
+ >
|
||||
+ <TerminalSquareIcon class="text-secondary size-5" />
|
||||
+ </button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -202,10 +199,6 @@ const messages = defineMessages({
|
||||
id: 'app.action-bar.view-logs',
|
||||
defaultMessage: 'View logs',
|
||||
},
|
||||
- noInstancesRunning: {
|
||||
- id: 'app.action-bar.no-instances-running',
|
||||
- defaultMessage: 'No instances running',
|
||||
- },
|
||||
downloadingJava: {
|
||||
id: 'app.action-bar.downloading-java',
|
||||
defaultMessage: 'Downloading Java {version}',
|
||||
+39
-1
@@ -63,7 +63,9 @@ export MODRINTH_ENHANCED_REPOSITORY="${GITHUB_REPOSITORY:-$(git -C "$REPO_ROOT"
|
||||
# (the public half is updater.pub). A build without the private key, such as
|
||||
# one for a pull request or a local one, has nothing to sign them with and
|
||||
# leaves the updater out.
|
||||
if [ -n "${TAURI_SIGNING_PRIVATE_KEY:-}" ]; then
|
||||
if [ -n "${DEV:-}" ]; then
|
||||
warn "DEV is set, so this build has no updater"
|
||||
elif [ -n "${TAURI_SIGNING_PRIVATE_KEY:-}" ]; then
|
||||
repository="$MODRINTH_ENHANCED_REPOSITORY"
|
||||
updater_conf="$REPO_ROOT/build/updater.conf.json"
|
||||
mkdir -p "$(dirname "$updater_conf")"
|
||||
@@ -97,6 +99,42 @@ fi
|
||||
# v1.2.3-2 from v1.2.3, since the app's own version cannot carry it.
|
||||
export MODRINTH_ENHANCED_REVISION="${MODRINTH_ENHANCED_REVISION:-1}"
|
||||
|
||||
# A dev build is an app of its own: its own name, and with it its own bundle
|
||||
# identifier, which is what decides the data directory. It shares no instances,
|
||||
# accounts or settings with an installed Modrinth Enhanced, and is installed
|
||||
# beside one rather than over it.
|
||||
if [ -n "${DEV:-}" ]; then
|
||||
dev_conf="$REPO_ROOT/build/dev.conf.json"
|
||||
mkdir -p "$(dirname "$dev_conf")"
|
||||
node -e '
|
||||
const fs = require("fs")
|
||||
const [base, platform] = process.argv.slice(1)
|
||||
const config = JSON.parse(fs.readFileSync(base, "utf8"))
|
||||
// The platform file replaces the windows of the one it is merged into.
|
||||
const platformConfig = fs.existsSync(platform)
|
||||
? JSON.parse(fs.readFileSync(platform, "utf8"))
|
||||
: {}
|
||||
const windows = platformConfig.app?.windows ?? config.app.windows
|
||||
const name = "Modrinth Enhanced (dev)"
|
||||
console.log(JSON.stringify({
|
||||
productName: name,
|
||||
identifier: "ModrinthAppDev",
|
||||
app: { windows: windows.map((window) => ({ ...window, title: name })) },
|
||||
}, null, "\t"))
|
||||
' "$WORKTREE/apps/app/tauri.conf.json" "$WORKTREE/apps/app/tauri.$platform.conf.json" >"$dev_conf"
|
||||
tauri_args+=(--config "$dev_conf")
|
||||
log "Building Modrinth Enhanced (dev), which shares nothing with a stable install"
|
||||
fi
|
||||
|
||||
# A local build to try something out does not need the last few percent of the
|
||||
# release profile. Thin LTO across several cores takes well under half the time
|
||||
# of the full one, for an installer some megabytes larger.
|
||||
if [ -n "${FAST:-}" ]; then
|
||||
export CARGO_PROFILE_RELEASE_LTO=thin
|
||||
export CARGO_PROFILE_RELEASE_CODEGEN_UNITS=16
|
||||
warn "FAST is set, so this build is optimized less than a release"
|
||||
fi
|
||||
|
||||
# Emptied before the build, not after it: a build that fails halfway would
|
||||
# otherwise leave the previous run's installers sitting here, where
|
||||
# scripts/check.sh would happily pass them off as this build's output.
|
||||
|
||||
+28
-2
@@ -135,6 +135,12 @@ check "the launcher turns it on" \
|
||||
check "the skins folder can be opened" \
|
||||
contains "$WORKTREE/apps/app/build.rs" '"show_player_skins_folder",'
|
||||
|
||||
log "Importing instances"
|
||||
check "every launcher is offered, installed or not" \
|
||||
contains "$WORKTREE/packages/app-lib/src/api/pack/import/mod.rs" 'pub fn launcher_suggestions'
|
||||
check "the import stage shows them" \
|
||||
contains "$WORKTREE/packages/ui/src/components/flows/creation-flow-modal/components/ImportInstanceStage.vue" 'otherLaunchers'
|
||||
|
||||
log "Another copy of a running instance"
|
||||
check "a running instance can start again" \
|
||||
contains "$WORKTREE/packages/app-lib/src/api/instance/run.rs" 'pub async fn run_additional'
|
||||
@@ -164,8 +170,16 @@ check "the skin page has a Browse tab" \
|
||||
contains "$WORKTREE/apps/app-frontend/src/pages/Skins.vue" '<SkinBrowser'
|
||||
|
||||
log "Sidebar and news"
|
||||
check "Modrinth Servers is behind a flag" \
|
||||
contains "$WORKTREE/apps/app-frontend/src/App.vue" "getFeatureFlag('show_hosting_in_sidebar')"
|
||||
check "the sidebar buttons follow the saved order" \
|
||||
contains "$WORKTREE/apps/app-frontend/src/App.vue" 'sidebarLayout.order.value'
|
||||
check "they can be arranged in the settings" \
|
||||
contains "$WORKTREE/apps/app-frontend/src/components/ui/modal/AppSettingsModal.vue" 'content: SidebarSettings'
|
||||
check "right-clicking one hides it" \
|
||||
contains "$WORKTREE/apps/app-frontend/src/App.vue" 'openSidebarMenu'
|
||||
check "an instance can be deleted from the sidebar" \
|
||||
contains "$WORKTREE/apps/app-frontend/src/components/ui/QuickInstanceSwitcher.vue" 'askToDelete'
|
||||
check "the library's free space offers a new group" \
|
||||
contains "$WORKTREE/apps/app-frontend/src/pages/Index.vue" 'openNewGroupModal()'
|
||||
check "the news section can be collapsed" \
|
||||
contains "$WORKTREE/apps/app-frontend/src/App.vue" 'setNewsCollapsed'
|
||||
check "the right sidebar has a fold button" \
|
||||
@@ -202,6 +216,18 @@ check "the settings tabs scroll instead of covering the version" \
|
||||
check "NVIDIA under Wayland does not crash the webview" \
|
||||
contains "$WORKTREE/apps/app/src/main.rs" 'set_var("WEBKIT_DMABUF_RENDERER_FORCE_SHM", "1")'
|
||||
|
||||
log "Language"
|
||||
check "the first start takes the system's language" \
|
||||
contains "$WORKTREE/apps/app-frontend/src/App.vue" 'localeFromSystem(locale)'
|
||||
check "a barely translated language is left alone" \
|
||||
contains "$WORKTREE/apps/app-frontend/src/helpers/system-locale.ts" 'MIN_COVERAGE = 70'
|
||||
# The app's own text lives outside Modrinth's catalogues, so nothing but this
|
||||
# keeps it English in languages the rest of the app is translated to.
|
||||
for locale in de-CH de-DE es-419 es-ES fr-FR hu-HU it-IT ja-JP nl-NL pl-PL pt-BR ru-RU sv-SE tr-TR uk-UA zh-CN zh-TW; do
|
||||
check "our own text is in $locale" \
|
||||
contains "$WORKTREE/apps/app-frontend/src/locales/$locale/index.json" '"app.crash.header"'
|
||||
done
|
||||
|
||||
log "Updates"
|
||||
check "updates do not come from Modrinth" \
|
||||
missing "$WORKTREE/apps/app-frontend/src/App.vue" 'launcher-files.modrinth.com/updates.json'
|
||||
|
||||
Reference in new issue
Block a user