diff --git a/.github/workflows/theseus-build.yml b/.github/workflows/theseus-build.yml index 90aa645e0..46bb693b8 100644 --- a/.github/workflows/theseus-build.yml +++ b/.github/workflows/theseus-build.yml @@ -21,6 +21,14 @@ on: type: boolean default: false required: false + environment: + description: Environment + type: choice + options: + - prod + - staging + default: prod + required: false jobs: build: @@ -94,12 +102,14 @@ jobs: shell: bash run: | APP_VERSION="$(git describe --tags --always | sed -E 's/-([0-9]+)-(g[0-9a-fA-F]+)$/-canary+\1.\2/')" + BUILD_ENVIRONMENT="${{ inputs.environment || 'prod' }}" echo "Setting application version to $APP_VERSION" + echo "Using environment $BUILD_ENVIRONMENT" dasel put -f apps/app/Cargo.toml -t string -v "${APP_VERSION#v}" 'package.version' dasel put -f packages/app-lib/Cargo.toml -t string -v "${APP_VERSION#v}" 'package.version' dasel put -f apps/app-frontend/package.json -t string -v "${APP_VERSION#v}" 'version' - cp packages/app-lib/.env.prod packages/app-lib/.env + cp "packages/app-lib/.env.${BUILD_ENVIRONMENT}" packages/app-lib/.env - name: Setup Turbo cache uses: rharkor/caching-for-turbo@v1.8 diff --git a/Cargo.lock b/Cargo.lock index 8245cfff0..8930c71b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -640,6 +640,19 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-minecraft-ping" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668b459c14dd8d9ef21e296af3f2a3651ff7dc3536e092fb0b09e528daaa6d89" +dependencies = [ + "async-trait", + "serde", + "serde_json", + "thiserror 1.0.69", + "tokio", +] + [[package]] name = "async-process" version = "2.5.0" @@ -1883,7 +1896,7 @@ checksum = "2eac901828f88a5241ee0600950ab981148a18f2f756900ffba1b125ca6a3ef9" dependencies = [ "cookie 0.18.1", "document-features", - "idna", + "idna 1.1.0", "log", "publicsuffix", "serde", @@ -2167,6 +2180,16 @@ dependencies = [ "darling_macro 0.21.3", ] +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core 0.23.0", + "darling_macro 0.23.0", +] + [[package]] name = "darling_core" version = "0.20.11" @@ -2195,6 +2218,19 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.106", +] + [[package]] name = "darling_macro" version = "0.20.11" @@ -2217,6 +2253,17 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core 0.23.0", + "quote", + "syn 2.0.106", +] + [[package]] name = "dashmap" version = "6.1.0" @@ -2651,6 +2698,24 @@ dependencies = [ "zeroize", ] +[[package]] +name = "elytra-ping" +version = "6.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "086ee116d28d0eb35ae108d1059d135e585dd1254e5df881c5b2505a6d67c445" +dependencies = [ + "bytes", + "chrono", + "mc-varint", + "rand 0.9.2", + "serde", + "serde_json", + "snafu", + "tokio", + "tracing", + "trust-dns-resolver", +] + [[package]] name = "email-encoding" version = "0.4.1" @@ -3754,7 +3819,7 @@ dependencies = [ "futures-channel", "futures-io", "futures-util", - "idna", + "idna 1.1.0", "ipnet", "once_cell", "rand 0.9.2", @@ -4224,6 +4289,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "idna" version = "1.1.0" @@ -4730,6 +4805,7 @@ dependencies = [ "arc-swap", "argon2", "ariadne", + "async-minecraft-ping", "async-stripe", "async-trait", "base64 0.22.1", @@ -4748,6 +4824,7 @@ dependencies = [ "dotenv-build", "dotenvy", "either", + "elytra-ping", "eyre", "futures", "futures-util", @@ -4811,6 +4888,16 @@ dependencies = [ "zxcvbn", ] +[[package]] +name = "labrinth-derive" +version = "0.0.0" +dependencies = [ + "darling 0.23.0", + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "language-tags" version = "0.3.2" @@ -4848,7 +4935,7 @@ dependencies = [ "futures-util", "hostname", "httpdate", - "idna", + "idna 1.1.0", "mime", "nom 8.0.0", "percent-encoding", @@ -4974,6 +5061,12 @@ dependencies = [ "zlib-rs", ] +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -5039,6 +5132,15 @@ dependencies = [ "imgref", ] +[[package]] +name = "lru-cache" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "lru-slab" version = "0.1.2" @@ -5150,6 +5252,12 @@ dependencies = [ "rayon", ] +[[package]] +name = "mc-varint" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6407d50d4e371d5f450a4a5f1abd4531e48e9a1627409c051b5d4c7c84f6bb09" + [[package]] name = "md-5" version = "0.10.6" @@ -7070,7 +7178,7 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf" dependencies = [ - "idna", + "idna 1.1.0", "psl-types", ] @@ -8776,6 +8884,28 @@ dependencies = [ "serde", ] +[[package]] +name = "snafu" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e84b3f4eacbf3a1ce05eac6763b4d629d60cbc94d632e4092c54ade71f1e1a2" +dependencies = [ + "backtrace", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "socket2" version = "0.5.10" @@ -10629,6 +10759,52 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "trust-dns-proto" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3119112651c157f4488931a01e586aa459736e9d6046d3bd9105ffb69352d374" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna 0.4.0", + "ipnet", + "once_cell", + "rand 0.8.5", + "smallvec", + "thiserror 1.0.69", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "trust-dns-resolver" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a3e6c3aff1718b3c73e395d1f35202ba2ffa847c6a62eea0db8fb4cfe30be6" +dependencies = [ + "cfg-if", + "futures-util", + "ipconfig", + "lru-cache", + "once_cell", + "parking_lot", + "rand 0.8.5", + "resolv-conf", + "smallvec", + "thiserror 1.0.69", + "tokio", + "tracing", + "trust-dns-proto", +] + [[package]] name = "try-lock" version = "0.2.5" @@ -10848,7 +11024,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" dependencies = [ "form_urlencoded", - "idna", + "idna 1.1.0", "percent-encoding", "serde", ] @@ -10996,7 +11172,7 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43fb22e1a008ece370ce08a3e9e4447a910e92621bb49b85d6e48a45397e7cfa" dependencies = [ - "idna", + "idna 1.1.0", "once_cell", "regex", "serde", diff --git a/Cargo.toml b/Cargo.toml index d5d9ba913..0ed38e677 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ members = [ "packages/app-lib", "packages/ariadne", "packages/daedalus", + "packages/labrinth-derive", "packages/modrinth-log", "packages/modrinth-maxmind", "packages/modrinth-util", @@ -32,6 +33,7 @@ arc-swap = "1.7.1" argon2 = { version = "0.5.3", features = ["std"] } ariadne = { path = "packages/ariadne" } async-compression = { version = "0.4.32", default-features = false } +async-minecraft-ping = { version = "0.8.0" } async-recursion = "1.1.1" async-stripe = { version = "0.41.0", default-features = false, features = [ "runtime-tokio-hyper-rustls", @@ -58,6 +60,7 @@ color-eyre = "0.6.5" color-thief = "0.2.2" const_format = "0.2.34" daedalus = { path = "packages/daedalus" } +darling = { version = "0.23" } dashmap = "6.1.0" data-url = "0.3.2" deadpool-redis = { git = "https://github.com/modrinth/deadpool", rev = "db5fb00b036ecc8fe5f18853c559b745ffe47bde", version = "0.22.1" } @@ -69,6 +72,7 @@ dotenv-build = "0.1.1" dotenvy = "0.15.7" dunce = "1.0.5" either = "1.15.0" +elytra-ping = "6.0.1" encoding_rs = "0.8.35" enumset = "1.1.10" eyre = "0.6.12" @@ -121,9 +125,11 @@ paste = "1.0.15" path-util = { path = "packages/path-util" } phf = { version = "0.13.1", features = ["macros"] } png = "0.18.0" +proc-macro2 = { version = "1.0" } prometheus = "0.14.0" quartz_nbt = "0.2.9" quick-xml = "0.38.3" +quote = { version = "1.0" } rand = "=0.8.5" # Locked on 0.8 until argon2 and p256 update to 0.9 rand_chacha = "=0.3.1" # Locked on 0.3 until we can update rand to 0.9 redis = "0.32.7" @@ -166,6 +172,7 @@ spdx = "0.12.0" sqlx = { version = "0.8.6", default-features = false } sqlx-tracing = { path = "packages/sqlx-tracing" } strum = "0.27.2" +syn = { version = "2.0" } sysinfo = { version = "0.37.2", default-features = false } tar = "0.4.44" tauri = "2.8.5" diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue index 601d7172a..ba05cea8d 100644 --- a/apps/app-frontend/src/App.vue +++ b/apps/app-frontend/src/App.vue @@ -36,14 +36,16 @@ import { NewsArticleCard, NotificationPanel, OverflowMenu, + PopupNotificationPanel, ProgressSpinner, provideModrinthClient, provideNotificationManager, providePageContext, + providePopupNotificationManager, useDebugLogger, useVIntl, } from '@modrinth/ui' -import { renderString } from '@modrinth/utils' +import { formatBytes, renderString } from '@modrinth/utils' import { useQuery } from '@tanstack/vue-query' import { getVersion } from '@tauri-apps/api/app' import { invoke } from '@tauri-apps/api/core' @@ -61,6 +63,7 @@ import AccountsCard from '@/components/ui/AccountsCard.vue' import Breadcrumbs from '@/components/ui/Breadcrumbs.vue' import ErrorModal from '@/components/ui/ErrorModal.vue' import FriendsList from '@/components/ui/friends/FriendsList.vue' +import AddServerToInstanceModal from '@/components/ui/install_flow/AddServerToInstanceModal.vue' import IncompatibilityWarningModal from '@/components/ui/install_flow/IncompatibilityWarningModal.vue' import InstallConfirmModal from '@/components/ui/install_flow/InstallConfirmModal.vue' import ModInstallModal from '@/components/ui/install_flow/ModInstallModal.vue' @@ -68,13 +71,13 @@ import InstanceCreationModal from '@/components/ui/InstanceCreationModal.vue' import MinecraftAuthErrorModal from '@/components/ui/minecraft-auth-error-modal/MinecraftAuthErrorModal.vue' import AppSettingsModal from '@/components/ui/modal/AppSettingsModal.vue' import AuthGrantFlowWaitModal from '@/components/ui/modal/AuthGrantFlowWaitModal.vue' +import InstallToPlayModal from '@/components/ui/modal/InstallToPlayModal.vue' +import UpdateToPlayModal from '@/components/ui/modal/UpdateToPlayModal.vue' import NavButton from '@/components/ui/NavButton.vue' import PromotionWrapper from '@/components/ui/PromotionWrapper.vue' import QuickInstanceSwitcher from '@/components/ui/QuickInstanceSwitcher.vue' import RunningAppBar from '@/components/ui/RunningAppBar.vue' import SplashScreen from '@/components/ui/SplashScreen.vue' -import UpdateAvailableToast from '@/components/ui/UpdateAvailableToast.vue' -import UpdateToast from '@/components/ui/UpdateToast.vue' import URLConfirmModal from '@/components/ui/URLConfirmModal.vue' import { useCheckDisableMouseover } from '@/composables/macCssFix.js' import { hide_ads_window, init_ads_window, show_ads_window } from '@/helpers/ads.js' @@ -101,13 +104,14 @@ import { subscribeToDownloadProgress, } from '@/providers/download-progress.ts' import { useError } from '@/store/error.js' -import { useInstall } from '@/store/install.js' +import { playServerProject, useInstall } from '@/store/install.js' import { useLoading, useTheming } from '@/store/state' import { create_profile_and_install_from_file } from './helpers/pack' import { generateSkinPreviews } from './helpers/rendering/batch-skin-renderer' import { get_available_capes, get_available_skins } from './helpers/skins' import { AppNotificationManager } from './providers/app-notifications' +import { AppPopupNotificationManager } from './providers/app-popup-notifications' const themeStore = useTheming() @@ -115,6 +119,10 @@ const notificationManager = new AppNotificationManager() provideNotificationManager(notificationManager) const { handleError, addNotification } = notificationManager +const popupNotificationManager = new AppPopupNotificationManager() +providePopupNotificationManager(popupNotificationManager) +const { addPopupNotification } = popupNotificationManager + const tauriApiClient = new TauriModrinthClient({ userAgent: `modrinth/theseus/${getVersion()} (support@modrinth.com)`, features: [ @@ -393,8 +401,11 @@ const minecraftAuthErrorModal = ref() const install = useInstall() const modInstallModal = ref() +const addServerToInstanceModal = ref() const installConfirmModal = ref() const incompatibilityWarningModal = ref() +const installToPlayModal = ref() +const updateToPlayModal = ref() const credentials = ref() @@ -403,7 +414,7 @@ const modrinthLoginFlowWaitModal = ref() async function fetchCredentials() { const creds = await getCreds().catch(handleError) if (creds && creds.user_id) { - creds.user = await get_user(creds.user_id).catch(handleError) + creds.user = await get_user(creds.user_id, 'bypass').catch(handleError) } credentials.value = creds ?? null } @@ -473,6 +484,10 @@ onMounted(() => { install.setIncompatibilityWarningModal(incompatibilityWarningModal) install.setInstallConfirmModal(installConfirmModal) install.setModInstallModal(modInstallModal) + install.setAddServerToInstanceModal(addServerToInstanceModal) + install.setInstallToPlayModal(installToPlayModal) + install.setUpdateToPlayModal(updateToPlayModal) + install.setPopupNotificationManager(popupNotificationManager) }) const accounts = ref(null) @@ -490,6 +505,9 @@ async function handleCommand(e) { source: 'CreationModalFileDrop', }) } + } else if (e.event === 'InstallServer') { + await router.push(`/project/${e.id}`) + await playServerProject(e.id).catch(handleError) } else { // Other commands are URL-based (deep linking) urlModal.value.show(e) @@ -508,14 +526,60 @@ const downloadPercent = computed(() => Math.trunc(appUpdateDownload.progress.val const metered = ref(true) const finishedDownloading = ref(false) const restarting = ref(false) -const updateToastDismissed = ref(false) const availableUpdate = ref(null) const updateSize = ref(null) const updatesEnabled = ref(true) + +const updatePopupMessages = defineMessages({ + updateAvailable: { + id: 'app.update-popup.title', + defaultMessage: 'Update available', + }, + downloadComplete: { + id: 'app.update-popup.download-complete', + defaultMessage: 'Download complete', + }, + body: { + id: 'app.update-popup.body', + defaultMessage: + 'Modrinth App v{version} is ready to install! Reload to update now, or automatically when you close Modrinth App.', + }, + meteredBody: { + id: 'app.update-popup.body.metered', + defaultMessage: `Modrinth App v{version} is available now! Since you're on a metered network, we didn't automatically download it.`, + }, + downloadedBody: { + id: 'app.update-popup.body.download-complete', + defaultMessage: `Modrinth App v{version} has finished downloading. Reload to update now, or automatically when you close Modrinth App.`, + }, + linuxBody: { + id: 'app.update-popup.body.linux', + defaultMessage: + 'Modrinth App v{version} is available. Use your package manager to update for the latest features and fixes!', + }, + reload: { + id: 'app.update-popup.reload', + defaultMessage: 'Reload', + }, + download: { + id: 'app.update-popup.download', + defaultMessage: 'Download ({size})', + }, + changelog: { + id: 'app.update-popup.changelog', + defaultMessage: 'Changelog', + }, +}) + async function checkUpdates() { if (!(await areUpdatesEnabled())) { console.log('Skipping update check as updates are disabled in this build or environment') updatesEnabled.value = false + + if (os.value === 'Linux' && !isDevEnvironment.value) { + checkLinuxUpdates() + setInterval(checkLinuxUpdates, 5 * 60 * 1000) + } return } @@ -535,7 +599,6 @@ async function checkUpdates() { appUpdateDownload.progress.value = 0 finishedDownloading.value = false - updateToastDismissed.value = false console.log(`Update ${update.version} is available.`) @@ -545,6 +608,28 @@ async function checkUpdates() { downloadUpdate(update) } else { console.log(`Metered connection detected, not auto-downloading update.`) + getUpdateSize(update.rid).then((size) => { + updateSize.value = size + addPopupNotification({ + title: formatMessage(updatePopupMessages.updateAvailable), + text: formatMessage(updatePopupMessages.meteredBody, { version: update.version }), + type: 'info', + autoCloseMs: null, + buttons: [ + { + label: formatMessage(updatePopupMessages.download, { + size: formatBytes(updateSize.value ?? 0), + }), + action: () => downloadAvailableUpdate(), + color: 'brand', + }, + { + label: formatMessage(updatePopupMessages.changelog), + action: () => openUrl('https://modrinth.com/news/changelog?filter=app'), + }, + ], + }) + }) } getUpdateSize(update.rid).then((size) => (updateSize.value = size)) @@ -561,8 +646,26 @@ async function checkUpdates() { ) } -async function showUpdateToast() { - updateToastDismissed.value = false +async function checkLinuxUpdates() { + try { + const [response, currentVersion] = await Promise.all([ + fetch('https://launcher-files.modrinth.com/updates.json'), + getVersion(), + ]) + const updates = await response.json() + const latestVersion = updates?.version + + if (latestVersion && latestVersion !== currentVersion) { + addPopupNotification({ + title: formatMessage(updatePopupMessages.updateAvailable), + text: formatMessage(updatePopupMessages.linuxBody, { version: latestVersion }), + type: 'info', + autoCloseMs: null, + }) + } + } catch (e) { + console.error('Failed to check for updates:', e) + } } async function downloadAvailableUpdate() { @@ -588,6 +691,26 @@ async function downloadUpdate(versionToDownload) { unlistenUpdateDownload = null }) console.log('Finished downloading!') + + addPopupNotification({ + title: formatMessage(updatePopupMessages.downloadComplete), + text: formatMessage(updatePopupMessages.downloadedBody, { + version: versionToDownload.version, + }), + type: 'success', + autoCloseMs: null, + buttons: [ + { + label: formatMessage(updatePopupMessages.reload), + action: () => installUpdate(), + color: 'brand', + }, + { + label: formatMessage(updatePopupMessages.changelog), + action: () => openUrl('https://modrinth.com/news/changelog?filter=app'), + }, + ], + }) }) unlistenUpdateDownload = await subscribeToDownloadProgress( appUpdateDownload, @@ -761,25 +884,6 @@ provideAppUpdateDownloadProgress(appUpdateDownload) class="app-grid-layout experimental-styles-within relative" :class="{ 'disable-advanced-rendering': !themeStore.advancedRendering }" > - - - - - -
-
+
- + diff --git a/apps/app-frontend/src/components/ui/QuickInstanceSwitcher.vue b/apps/app-frontend/src/components/ui/QuickInstanceSwitcher.vue index 7186203c1..442f424ec 100644 --- a/apps/app-frontend/src/components/ui/QuickInstanceSwitcher.vue +++ b/apps/app-frontend/src/components/ui/QuickInstanceSwitcher.vue @@ -69,7 +69,10 @@ onUnmounted(() => {
-
+
diff --git a/apps/app-frontend/src/components/ui/RunningAppBar.vue b/apps/app-frontend/src/components/ui/RunningAppBar.vue index 53f050362..95da2cdb6 100644 --- a/apps/app-frontend/src/components/ui/RunningAppBar.vue +++ b/apps/app-frontend/src/components/ui/RunningAppBar.vue @@ -52,10 +52,12 @@

{{ loadingBar.title }}

- -
- {{ Math.floor((100 * loadingBar.current) / loadingBar.total) }}% - {{ loadingBar.message }} +
+ +
+ {{ Math.floor((100 * loadingBar.current) / loadingBar.total) }}% + {{ loadingBar.message }} +
@@ -346,7 +348,7 @@ onBeforeUnmount(() => { .info-card { position: absolute; top: 3.5rem; - right: 0.5rem; + right: 2rem; z-index: 9; width: 20rem; background-color: var(--color-raised-bg); @@ -420,7 +422,7 @@ onBeforeUnmount(() => { display: flex; flex-direction: column; align-items: flex-start; - gap: 0.5rem; + gap: 0.75rem; margin: 0; padding: 0; } diff --git a/apps/app-frontend/src/components/ui/UpdateAvailableToast.vue b/apps/app-frontend/src/components/ui/UpdateAvailableToast.vue deleted file mode 100644 index ccae3ef5a..000000000 --- a/apps/app-frontend/src/components/ui/UpdateAvailableToast.vue +++ /dev/null @@ -1,84 +0,0 @@ - - diff --git a/apps/app-frontend/src/components/ui/UpdateToast.vue b/apps/app-frontend/src/components/ui/UpdateToast.vue deleted file mode 100644 index 6a7f104c6..000000000 --- a/apps/app-frontend/src/components/ui/UpdateToast.vue +++ /dev/null @@ -1,130 +0,0 @@ - - diff --git a/apps/app-frontend/src/components/ui/install_flow/AddServerToInstanceModal.vue b/apps/app-frontend/src/components/ui/install_flow/AddServerToInstanceModal.vue new file mode 100644 index 000000000..29b4d905c --- /dev/null +++ b/apps/app-frontend/src/components/ui/install_flow/AddServerToInstanceModal.vue @@ -0,0 +1,121 @@ + + + diff --git a/apps/app-frontend/src/components/ui/install_flow/ModInstallModal.vue b/apps/app-frontend/src/components/ui/install_flow/ModInstallModal.vue index 2f5c6a601..8b1edf424 100644 --- a/apps/app-frontend/src/components/ui/install_flow/ModInstallModal.vue +++ b/apps/app-frontend/src/components/ui/install_flow/ModInstallModal.vue @@ -16,6 +16,7 @@ import { useRouter } from 'vue-router' import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue' import { trackEvent } from '@/helpers/analytics' +import { get_project_v3_many } from '@/helpers/cache.js' import { add_project_from_version as installMod, check_installed, @@ -81,6 +82,22 @@ defineExpose({ handleError, ) } + + const linkedProjectIds = profilesVal + .filter((p) => p.linked_data?.project_id) + .map((p) => p.linked_data.project_id) + if (linkedProjectIds.length > 0) { + const linkedProjects = await get_project_v3_many(linkedProjectIds, 'must_revalidate').catch( + () => [], + ) + const serverProjectIds = new Set( + linkedProjects.filter((p) => p?.minecraft_server != null).map((p) => p.id), + ) + for (const profile of profilesVal) { + profile.isServerInstance = serverProjectIds.has(profile.linked_data?.project_id) + } + } + profiles.value = profilesVal installModal.value.show() diff --git a/apps/app-frontend/src/components/ui/instance_settings/InstallationSettings.vue b/apps/app-frontend/src/components/ui/instance_settings/InstallationSettings.vue index 93cf9bc55..b9fe4223d 100644 --- a/apps/app-frontend/src/components/ui/instance_settings/InstallationSettings.vue +++ b/apps/app-frontend/src/components/ui/instance_settings/InstallationSettings.vue @@ -29,7 +29,7 @@ import { computed, type ComputedRef, type Ref, ref, shallowRef, watch } from 'vu import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue' import ModpackVersionModal from '@/components/ui/ModpackVersionModal.vue' import { trackEvent } from '@/helpers/analytics' -import { get_project, get_version_many } from '@/helpers/cache' +import { get_project, get_version, get_version_many } from '@/helpers/cache' import { get_loader_versions } from '@/helpers/metadata' import { edit, install, update_repair_modrinth } from '@/helpers/profile' import { get_game_versions, get_loaders } from '@/helpers/tags' @@ -110,6 +110,12 @@ if (props.instance.linked_data && props.instance.linked_data.project_id && !prop versions.find( (version: Version) => version.id === props.instance.linked_data?.version_id, ) ?? null + + if (!modpackVersion.value) { + get_version(props.instance.linked_data?.version_id, 'bypass') + .then((version: Version) => (modpackVersion.value = version ?? null)) + .catch(handleError) + } }) .catch(handleError) .finally(() => { @@ -424,6 +430,18 @@ const messages = defineMessages({ id: 'instance.settings.tabs.installation.unlink.description', defaultMessage: `This instance is linked to a modpack, which means mods can't be updated and you can't change the mod loader or Minecraft version. Unlinking will permanently disconnect this instance from the modpack.`, }, + unlinkServerTitle: { + id: 'instance.settings.tabs.installation.unlink-server.title', + defaultMessage: 'Unlink from server', + }, + unlinkServerDescription: { + id: 'instance.settings.tabs.installation.unlink-server.description', + defaultMessage: `This instance is linked to a server, which means mods can't be updated and you can't change the mod loader or Minecraft version. Unlinking will permanently disconnect this instance from the server.`, + }, + unlinkServerVanillaDescription: { + id: 'instance.settings.tabs.installation.unlink-server-vanilla.description', + defaultMessage: `This instance is linked to a server, which means you can't change the Minecraft version. Unlinking will permanently disconnect this instance from the server.`, + }, unlinkInstanceButton: { id: 'instance.settings.tabs.installation.unlink.button', defaultMessage: 'Unlink instance', @@ -557,17 +575,27 @@ const messages = defineMessages({ }) }} - + {{ modpackProject ? modpackVersion ? modpackVersion?.version_number - : 'Unknown version' + : props.isMinecraftServer + ? '' + : 'Unknown version' : formatLoader(formatMessage, instance.loader) }} +
@@ -599,7 +627,10 @@ const messages = defineMessages({ }} - + -