diff --git a/.github/workflows/theseus-build.yml b/.github/workflows/theseus-build.yml
index 3f882f506..3c0f7b138 100644
--- a/.github/workflows/theseus-build.yml
+++ b/.github/workflows/theseus-build.yml
@@ -30,6 +30,10 @@ on:
- prod-with-staging-archon
default: prod
required: false
+ app-version-override:
+ description: Temporary app version override for updater testing
+ type: string
+ required: false
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -126,7 +130,10 @@ jobs:
- name: Set application version and environment
shell: bash
run: |
- APP_VERSION="$(git describe --tags --always | sed -E 's/-([0-9]+)-(g[0-9a-fA-F]+)$/-canary+\1.\2/')"
+ APP_VERSION="${{ inputs.app-version-override }}"
+ if [ -z "$APP_VERSION" ]; then
+ APP_VERSION="$(git describe --tags --always | sed -E 's/-([0-9]+)-(g[0-9a-fA-F]+)$/-canary+\1.\2/')"
+ fi
BUILD_ENVIRONMENT="${{ inputs.environment || 'prod' }}"
echo "Setting application version to $APP_VERSION"
echo "Using environment $BUILD_ENVIRONMENT"
@@ -153,7 +160,7 @@ jobs:
fi
- name: Build macOS app
- run: ${{ (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) && 'pnpm --filter=@modrinth/app run tauri build --target universal-apple-darwin --config tauri-release.conf.json' || 'pnpm --filter=@modrinth/app run tauri build --target universal-apple-darwin --config tauri-dev.conf.json' }}
+ run: ${{ (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || inputs.app-version-override != '') && 'pnpm --filter=@modrinth/app run tauri build --target universal-apple-darwin --config tauri-release.conf.json' || 'pnpm --filter=@modrinth/app run tauri build --target universal-apple-darwin --config tauri-dev.conf.json' }}
if: contains(matrix.platform, 'macos')
env:
TAURI_BUNDLER_DMG_IGNORE_CI: 'true'
@@ -168,7 +175,7 @@ jobs:
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
- name: Build Linux app
- run: ${{ (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) && 'pnpm --filter=@modrinth/app run tauri build --config tauri-release.conf.json' || 'pnpm --filter=@modrinth/app run tauri build --config tauri-dev.conf.json' }}
+ run: ${{ (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || inputs.app-version-override != '') && 'pnpm --filter=@modrinth/app run tauri build --config tauri-release.conf.json' || 'pnpm --filter=@modrinth/app run tauri build --config tauri-dev.conf.json' }}
if: contains(matrix.platform, 'ubuntu')
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
@@ -179,7 +186,7 @@ jobs:
[System.Convert]::FromBase64String("$env:DIGICERT_ONE_SIGNER_CLIENT_CERTIFICATE_BASE64") | Set-Content -Path signer-client-cert.p12 -AsByteStream
$env:DIGICERT_ONE_SIGNER_CREDENTIALS = "$env:DIGICERT_ONE_SIGNER_API_KEY|$PWD\signer-client-cert.p12|$env:DIGICERT_ONE_SIGNER_CLIENT_CERTIFICATE_PASSWORD"
$env:JAVA_HOME = "$env:JAVA_HOME_17_X64"
- ${{ (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) && 'pnpm --filter=@modrinth/app run tauri build --config tauri-release.conf.json --verbose --bundles "nsis,updater"' || 'pnpm --filter=@modrinth/app run tauri build --config tauri-dev.conf.json --verbose --bundles "nsis,updater"' }}
+ ${{ (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || inputs.app-version-override != '') && 'pnpm --filter=@modrinth/app run tauri build --config tauri-release.conf.json --verbose --bundles "nsis,updater"' || 'pnpm --filter=@modrinth/app run tauri build --config tauri-dev.conf.json --verbose --bundles "nsis,updater"' }}
Remove-Item -Path signer-client-cert.p12 -ErrorAction SilentlyContinue
if: contains(matrix.platform, 'windows')
env:
diff --git a/Cargo.lock b/Cargo.lock
index 0353475e1..907395468 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1378,7 +1378,7 @@ dependencies = [
"bitflags 2.9.4",
"cexpr",
"clang-sys",
- "itertools 0.12.1",
+ "itertools 0.13.0",
"log",
"prettyplease",
"proc-macro2",
@@ -2927,7 +2927,7 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
dependencies = [
- "libloading 0.7.4",
+ "libloading 0.8.8",
]
[[package]]
@@ -3260,7 +3260,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
- "windows-sys 0.52.0",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -4577,7 +4577,7 @@ dependencies = [
"libc",
"percent-encoding",
"pin-project-lite",
- "socket2 0.5.10",
+ "socket2 0.6.1",
"system-configuration",
"tokio",
"tower-service",
@@ -4817,7 +4817,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5"
dependencies = [
"equivalent",
- "hashbrown 0.16.0",
+ "hashbrown 0.15.5",
"serde",
"serde_core",
]
@@ -4974,7 +4974,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
dependencies = [
"hermit-abi 0.5.2",
"libc",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
]
[[package]]
@@ -5439,7 +5439,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
dependencies = [
"cfg-if",
- "windows-targets 0.48.5",
+ "windows-targets 0.53.5",
]
[[package]]
@@ -7701,7 +7701,7 @@ dependencies = [
"quinn-udp",
"rustc-hash",
"rustls 0.23.32",
- "socket2 0.5.10",
+ "socket2 0.6.1",
"thiserror 2.0.17",
"tokio",
"tracing",
@@ -7738,9 +7738,9 @@ dependencies = [
"cfg_aliases",
"libc",
"once_cell",
- "socket2 0.5.10",
+ "socket2 0.6.1",
"tracing",
- "windows-sys 0.52.0",
+ "windows-sys 0.60.2",
]
[[package]]
@@ -8159,15 +8159,20 @@ dependencies = [
"http-body 1.0.1",
"http-body-util",
"hyper 1.7.0",
+ "hyper-rustls 0.27.7",
"hyper-util",
"js-sys",
"log",
"percent-encoding",
"pin-project-lite",
+ "rustls 0.23.32",
+ "rustls-pki-types",
+ "rustls-platform-verifier",
"serde",
"serde_json",
"sync_wrapper",
"tokio",
+ "tokio-rustls 0.26.4",
"tokio-util",
"tower",
"tower-http",
@@ -8413,7 +8418,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys 0.4.15",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
]
[[package]]
@@ -8426,7 +8431,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys 0.11.0",
- "windows-sys 0.52.0",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -8509,6 +8514,33 @@ dependencies = [
"zeroize",
]
+[[package]]
+name = "rustls-platform-verifier"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784"
+dependencies = [
+ "core-foundation 0.10.1",
+ "core-foundation-sys",
+ "jni",
+ "log",
+ "once_cell",
+ "rustls 0.23.32",
+ "rustls-native-certs 0.8.1",
+ "rustls-platform-verifier-android",
+ "rustls-webpki 0.103.7",
+ "security-framework 3.5.1",
+ "security-framework-sys",
+ "webpki-root-certs",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "rustls-platform-verifier-android"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
+
[[package]]
name = "rustls-webpki"
version = "0.101.7"
@@ -9654,7 +9686,6 @@ dependencies = [
"cfg-if",
"libc",
"psm",
- "windows-sys 0.52.0",
"windows-sys 0.59.0",
]
@@ -10264,9 +10295,8 @@ dependencies = [
[[package]]
name = "tauri-plugin-updater"
-version = "2.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "27cbc31740f4d507712550694749572ec0e43bdd66992db7599b89fbfd6b167b"
+version = "2.10.1"
+source = "git+https://github.com/modrinth/plugins-workspace?rev=0d30f2aa28ec668ce187d527da1c475da3c01cbc#0d30f2aa28ec668ce187d527da1c475da3c01cbc"
dependencies = [
"base64 0.22.1",
"dirs",
@@ -10278,7 +10308,8 @@ dependencies = [
"minisign-verify",
"osakit",
"percent-encoding",
- "reqwest 0.12.24",
+ "reqwest 0.13.2",
+ "rustls 0.23.32",
"semver",
"serde",
"serde_json",
@@ -10419,7 +10450,7 @@ dependencies = [
"getrandom 0.3.3",
"once_cell",
"rustix 1.1.2",
- "windows-sys 0.52.0",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -11957,6 +11988,15 @@ dependencies = [
"libwebp-sys",
]
+[[package]]
+name = "webpki-root-certs"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c"
+dependencies = [
+ "rustls-pki-types",
+]
+
[[package]]
name = "webpki-roots"
version = "0.26.11"
@@ -12078,7 +12118,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
- "windows-sys 0.48.0",
+ "windows-sys 0.61.2",
]
[[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 529fc9cb6..7ce30a47f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -192,7 +192,7 @@ tauri-plugin-http = "2.5.7"
tauri-plugin-opener = "2.5.0"
tauri-plugin-os = "2.3.1"
tauri-plugin-single-instance = "2.3.4"
-tauri-plugin-updater = { version = "2.9.0", default-features = false, features = [
+tauri-plugin-updater = { git = "https://github.com/modrinth/plugins-workspace", rev = "0d30f2aa28ec668ce187d527da1c475da3c01cbc", default-features = false, features = [
"rustls-tls",
"zip",
] }
diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue
index d9faac640..dc727147c 100644
--- a/apps/app-frontend/src/App.vue
+++ b/apps/app-frontend/src/App.vue
@@ -12,7 +12,6 @@ import {
ArrowBigUpDashIcon,
ChangeSkinIcon,
CompassIcon,
- DownloadIcon,
ExternalIcon,
HomeIcon,
LeftArrowIcon,
@@ -45,7 +44,6 @@ import {
NotificationPanel,
OverflowMenu,
PopupNotificationPanel,
- ProgressSpinner,
provideModalBehavior,
provideModrinthClient,
provideNotificationManager,
@@ -113,6 +111,16 @@ import {
setRestartAfterPendingUpdate,
} from '@/helpers/utils.js'
import i18n from '@/i18n.config'
+import {
+ appUpdateState,
+ downloadAvailableAppUpdate,
+ getNextAppUpdatePopupTime,
+ installAvailableAppUpdate,
+ markAppUpdateActionable,
+ markAppUpdatePopupShown,
+ openAppUpdateChangelog,
+ setAppUpdateActions,
+} from '@/providers/app-update.ts'
import { createContentInstall, provideContentInstall } from '@/providers/content-install'
import {
provideAppUpdateDownloadProgress,
@@ -297,6 +305,7 @@ onUnmounted(async () => {
document.querySelector('body').removeEventListener('click', handleClick)
document.querySelector('body').removeEventListener('auxclick', handleAuxClick)
unsubscribeSidebarToggle()
+ clearDelayedUpdatePopup()
await unlistenUpdateDownload?.()
})
@@ -313,18 +322,6 @@ const messages = defineMessages({
id: 'app.update.complete-toast.text',
defaultMessage: 'Click here to view the changelog.',
},
- reloadToUpdate: {
- id: 'app.update.reload-to-update',
- defaultMessage: 'Reload to install update',
- },
- downloadUpdate: {
- id: 'app.update.download-update',
- defaultMessage: 'Download update',
- },
- downloadingUpdate: {
- id: 'app.update.downloading-update',
- defaultMessage: 'Downloading update ({percent}%)',
- },
authUnreachableHeader: {
id: 'app.auth-servers.unreachable.header',
defaultMessage: 'Cannot reach authentication servers',
@@ -882,20 +879,21 @@ async function handleCommand(e) {
}
const appUpdateDownload = {
- progress: ref(0),
+ progress: appUpdateState.progress,
version: ref(),
}
let unlistenUpdateDownload
-const downloadProgress = computed(() => appUpdateDownload.progress.value)
-const downloadPercent = computed(() => Math.trunc(appUpdateDownload.progress.value * 100))
-
-const metered = ref(true)
-const finishedDownloading = ref(false)
-const restarting = ref(false)
-const availableUpdate = ref(null)
-const updateSize = ref(null)
-const updatesEnabled = ref(true)
+const {
+ metered,
+ finishedDownloading,
+ downloading,
+ restarting,
+ availableUpdate,
+ updateSize,
+ updatesEnabled,
+} = appUpdateState
+let delayedUpdatePopupTimeout = null
const updatePopupMessages = defineMessages({
updateAvailable: {
@@ -906,11 +904,6 @@ const updatePopupMessages = defineMessages({
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.`,
@@ -926,7 +919,7 @@ const updatePopupMessages = defineMessages({
},
reload: {
id: 'app.update-popup.reload',
- defaultMessage: 'Reload',
+ defaultMessage: 'Reload to update',
},
download: {
id: 'app.update-popup.download',
@@ -938,6 +931,106 @@ const updatePopupMessages = defineMessages({
},
})
+function clearDelayedUpdatePopup() {
+ if (delayedUpdatePopupTimeout !== null) {
+ clearTimeout(delayedUpdatePopupTimeout)
+ delayedUpdatePopupTimeout = null
+ }
+}
+
+function getCurrentUpdatePromptStage() {
+ return finishedDownloading.value ? 'downloaded' : 'available'
+}
+
+function scheduleDelayedUpdatePopup() {
+ clearDelayedUpdatePopup()
+
+ const version = availableUpdate.value?.version
+ if (!version) {
+ return
+ }
+
+ const nextPopupTime = getNextAppUpdatePopupTime(version, getCurrentUpdatePromptStage())
+ if (nextPopupTime === null) {
+ return
+ }
+
+ const delay = nextPopupTime - Date.now()
+ if (delay <= 0) {
+ showDelayedUpdatePopup()
+ return
+ }
+
+ delayedUpdatePopupTimeout = setTimeout(showDelayedUpdatePopup, Math.min(delay, 2_147_483_647))
+}
+
+function showDelayedUpdatePopup() {
+ const update = availableUpdate.value
+ if (!update) {
+ return
+ }
+
+ const stage = getCurrentUpdatePromptStage()
+ const nextPopupTime = getNextAppUpdatePopupTime(update.version, stage)
+ if (nextPopupTime === null) {
+ return
+ }
+
+ if (Date.now() < nextPopupTime) {
+ scheduleDelayedUpdatePopup()
+ return
+ }
+
+ if (metered.value && !finishedDownloading.value) {
+ 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: () => downloadAvailableAppUpdate(),
+ color: 'brand',
+ },
+ {
+ label: formatMessage(updatePopupMessages.changelog),
+ action: () => openAppUpdateChangelog(),
+ keepOpen: true,
+ },
+ ],
+ })
+ } else if (finishedDownloading.value) {
+ addPopupNotification({
+ title: formatMessage(updatePopupMessages.downloadComplete),
+ text: formatMessage(updatePopupMessages.downloadedBody, {
+ version: update.version,
+ }),
+ type: 'success',
+ autoCloseMs: null,
+ buttons: [
+ {
+ label: formatMessage(updatePopupMessages.reload),
+ action: () => installAvailableAppUpdate(),
+ color: 'brand',
+ },
+ {
+ label: formatMessage(updatePopupMessages.changelog),
+ action: () => openAppUpdateChangelog(),
+ keepOpen: true,
+ },
+ ],
+ })
+ } else {
+ scheduleDelayedUpdatePopup()
+ return
+ }
+
+ markAppUpdatePopupShown(update.version, stage)
+}
+
async function checkUpdates() {
if (!(await areUpdatesEnabled())) {
console.log('Skipping update check as updates are disabled in this build or environment')
@@ -961,11 +1054,15 @@ async function checkUpdates() {
if (isExistingUpdate) {
console.log('Update is already known')
+ scheduleDelayedUpdatePopup()
return
}
appUpdateDownload.progress.value = 0
finishedDownloading.value = false
+ downloading.value = false
+ updateSize.value = null
+ availableUpdate.value = update
console.log(`Update ${update.version} is available.`)
@@ -975,34 +1072,11 @@ 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'),
- keepOpen: true,
- },
- ],
- })
- })
+ markAppUpdateActionable(update.version)
+ scheduleDelayedUpdatePopup()
}
getUpdateSize(update.rid).then((size) => (updateSize.value = size))
-
- availableUpdate.value = update
}
await performCheck()
@@ -1024,12 +1098,17 @@ async function checkLinuxUpdates() {
const latestVersion = updates?.version
if (latestVersion && latestVersion !== currentVersion) {
- addPopupNotification({
- title: formatMessage(updatePopupMessages.updateAvailable),
- text: formatMessage(updatePopupMessages.linuxBody, { version: latestVersion }),
- type: 'info',
- autoCloseMs: null,
- })
+ markAppUpdateActionable(latestVersion)
+ const nextPopupTime = getNextAppUpdatePopupTime(latestVersion)
+ if (nextPopupTime !== null && Date.now() >= nextPopupTime) {
+ addPopupNotification({
+ title: formatMessage(updatePopupMessages.updateAvailable),
+ text: formatMessage(updatePopupMessages.linuxBody, { version: latestVersion }),
+ type: 'info',
+ autoCloseMs: null,
+ })
+ markAppUpdatePopupShown(latestVersion)
+ }
}
} catch (e) {
console.error('Failed to check for updates:', e)
@@ -1043,55 +1122,48 @@ async function downloadAvailableUpdate() {
async function downloadUpdate(versionToDownload) {
if (!versionToDownload) {
handleError(`Failed to download update: no version available`)
+ return
}
- if (appUpdateDownload.progress.value !== 0) {
+ if (downloading.value || appUpdateDownload.progress.value !== 0) {
console.error(`Update ${versionToDownload.version} already downloading`)
return
}
console.log(`Downloading update ${versionToDownload.version}`)
+ downloading.value = true
try {
- enqueueUpdateForInstallation(versionToDownload.rid).then(() => {
- finishedDownloading.value = true
- unlistenUpdateDownload?.().then(() => {
- unlistenUpdateDownload = null
+ enqueueUpdateForInstallation(versionToDownload.rid)
+ .then(() => {
+ downloading.value = false
+ finishedDownloading.value = true
+ unlistenUpdateDownload?.().then(() => {
+ unlistenUpdateDownload = null
+ })
+ console.log('Finished downloading!')
+ markAppUpdateActionable(versionToDownload.version, 'downloaded')
+ scheduleDelayedUpdatePopup()
})
- 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'),
- keepOpen: true,
- },
- ],
+ .catch((e) => {
+ downloading.value = false
+ appUpdateDownload.progress.value = 0
+ handleError(e)
})
- })
unlistenUpdateDownload = await subscribeToDownloadProgress(
appUpdateDownload,
versionToDownload.version,
)
} catch (e) {
+ downloading.value = false
+ appUpdateDownload.progress.value = 0
handleError(e)
}
}
async function installUpdate() {
restarting.value = true
+
try {
await setRestartAfterPendingUpdate(true)
} catch (e) {
@@ -1104,6 +1176,12 @@ async function installUpdate() {
}, 250)
}
+setAppUpdateActions({
+ download: downloadAvailableUpdate,
+ install: installUpdate,
+ changelog: () => openUrl('https://modrinth.com/news/changelog?filter=app'),
+})
+
async function openModrinthProjectLinkInApp(parsed) {
const { slug, pathSuffix, url } = parsed
const loadToken = loading.begin()
@@ -1373,33 +1451,6 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
-
-
-
-
+
{{ formatMessage(messages.offline) }}
+
+
+
@@ -119,6 +144,7 @@ import {
DownloadIcon,
DropdownIcon,
OnlineIndicatorIcon,
+ RefreshCwIcon,
StarIcon,
StopCircleIcon,
TerminalSquareIcon,
@@ -135,7 +161,7 @@ import {
} from '@modrinth/ui'
import { convertFileSrc } from '@tauri-apps/api/core'
import { Dropdown } from 'floating-vue'
-import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
+import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import { trackEvent } from '@/helpers/analytics'
@@ -145,6 +171,11 @@ import { get_many as getInstances } from '@/helpers/profile.js'
import type { LoadingBar } from '@/helpers/state'
import { progress_bars_list } from '@/helpers/state'
import type { GameInstance } from '@/helpers/types'
+import {
+ appUpdateState,
+ downloadAvailableAppUpdate,
+ installAvailableAppUpdate,
+} from '@/providers/app-update'
const { handleError } = injectNotificationManager()
const popupNotificationManager = injectPopupNotificationManager()
@@ -209,8 +240,96 @@ const messages = defineMessages({
id: 'app.action-bar.view-active-downloads',
defaultMessage: 'View active downloads',
},
+ update: {
+ id: 'app.action-bar.update',
+ defaultMessage: 'Update',
+ },
+ downloadingUpdate: {
+ id: 'app.action-bar.downloading-update',
+ defaultMessage: 'Downloading update',
+ },
+ reloadToUpdate: {
+ id: 'app.action-bar.reload-to-update',
+ defaultMessage: 'Reload to update',
+ },
})
+const {
+ downloading,
+ downloadPercent,
+ downloadProgress,
+ finishedDownloading,
+ isVisible: isUpdateVisible,
+ metered,
+ restarting,
+} = appUpdateState
+
+const isUpdateDownloading = computed(
+ () =>
+ downloading.value ||
+ (downloadProgress.value > 0 && downloadProgress.value < 1 && !finishedDownloading.value),
+)
+const showUpdatePill = computed(
+ () => isUpdateVisible.value && (finishedDownloading.value || metered.value),
+)
+const animateReadyPill = ref(false)
+const updateLabel = computed(() => {
+ if (isUpdateDownloading.value) {
+ return formatMessage(messages.downloadingUpdate)
+ }
+
+ if (finishedDownloading.value) {
+ return formatMessage(messages.reloadToUpdate)
+ }
+
+ return formatMessage(messages.update)
+})
+const updatePillWidthClass = computed(() => {
+ if (isUpdateDownloading.value) {
+ return 'w-[219px]'
+ }
+
+ if (finishedDownloading.value) {
+ return 'w-[166px]'
+ }
+
+ return '!w-[96px]'
+})
+let readyPillAnimationFrame: number | null = null
+watch([showUpdatePill, finishedDownloading], async ([show, ready], [wasShown, wasReady]) => {
+ if (readyPillAnimationFrame !== null) {
+ cancelAnimationFrame(readyPillAnimationFrame)
+ readyPillAnimationFrame = null
+ }
+
+ if (!show || !ready) {
+ animateReadyPill.value = false
+ return
+ }
+
+ if (wasShown && wasReady) {
+ return
+ }
+
+ animateReadyPill.value = false
+ await nextTick()
+ readyPillAnimationFrame = requestAnimationFrame(() => {
+ animateReadyPill.value = true
+ readyPillAnimationFrame = null
+ })
+})
+async function handleUpdateClick() {
+ if (isUpdateDownloading.value) {
+ return
+ }
+
+ if (finishedDownloading.value) {
+ await installAvailableAppUpdate()
+ } else {
+ await downloadAvailableAppUpdate()
+ }
+}
+
const currentProcesses = ref
([])
const selectedProcess = ref()
@@ -469,5 +588,20 @@ onBeforeUnmount(() => {
window.removeEventListener('online', handleOnline)
unlistenProcess()
unlistenLoading()
+ if (readyPillAnimationFrame !== null) {
+ cancelAnimationFrame(readyPillAnimationFrame)
+ }
})
+
+
diff --git a/apps/app-frontend/src/locales/ar-SA/index.json b/apps/app-frontend/src/locales/ar-SA/index.json
index 9df2e329a..35dd33e56 100644
--- a/apps/app-frontend/src/locales/ar-SA/index.json
+++ b/apps/app-frontend/src/locales/ar-SA/index.json
@@ -236,9 +236,6 @@
"app.settings.tabs.resource-management": {
"message": "إدارة الموارد"
},
- "app.update-popup.body": {
- "message": "تطبيق Modrinth v{version} جاهز للتثبيت! أعد التحميل للتحديث الآن، أو تلقائيًا عند إغلاق تطبيق Modrinth."
- },
"app.update-popup.body.download-complete": {
"message": "انتهى تنزيل تطبيق Modrinth v{version}. أعد التحميل للتحديث الآن، أو تلقائيًا عند إغلاق تطبيق Modrinth."
},
@@ -269,15 +266,6 @@
"app.update.complete-toast.title": {
"message": "تم تثبيت الإصدار {version} بنجاح!"
},
- "app.update.download-update": {
- "message": "تنزيل التحديث"
- },
- "app.update.downloading-update": {
- "message": "جار تنزيل التحديث ({percent}٪)"
- },
- "app.update.reload-to-update": {
- "message": "أعد التحميل لتثبيت التحديث"
- },
"app.world.server-modal.placeholder-address": {
"message": "مثال.مودرنث.جج"
},
diff --git a/apps/app-frontend/src/locales/cs-CZ/index.json b/apps/app-frontend/src/locales/cs-CZ/index.json
index 0a5d63ff6..2da79e2fe 100644
--- a/apps/app-frontend/src/locales/cs-CZ/index.json
+++ b/apps/app-frontend/src/locales/cs-CZ/index.json
@@ -359,9 +359,6 @@
"app.skins.section.saved-skins": {
"message": "Uložené skiny"
},
- "app.update-popup.body": {
- "message": "Aplikace Modrinth v{version} je připravena k instalaci! Nainstalujte aktualizaci nyní nebo automaticky po zavření aplikace Modrinth."
- },
"app.update-popup.body.download-complete": {
"message": "Stahování aplikace Modrinth v{version} bylo dokončeno. Nainstalujte aktualizaci nyní nebo automaticky po zavření aplikace Modrinth."
},
@@ -392,15 +389,6 @@
"app.update.complete-toast.title": {
"message": "Verze {version} byla úspěšně nainstalována!"
},
- "app.update.download-update": {
- "message": "Stáhnout aktualizaci"
- },
- "app.update.downloading-update": {
- "message": "Stahování aktualizace ({percent}%)"
- },
- "app.update.reload-to-update": {
- "message": "Restartovat aplikaci pro nainstalování aktualizace"
- },
"app.world.server-modal.placeholder-address": {
"message": "priklad.modrinth.gg"
},
diff --git a/apps/app-frontend/src/locales/da-DK/index.json b/apps/app-frontend/src/locales/da-DK/index.json
index 3b65f3b22..24ad58074 100644
--- a/apps/app-frontend/src/locales/da-DK/index.json
+++ b/apps/app-frontend/src/locales/da-DK/index.json
@@ -455,9 +455,6 @@
"app.skins.title": {
"message": "Skin vælger"
},
- "app.update-popup.body": {
- "message": "Modrinth App v{version} er klar til at blive installeret! Genindlæs for at opdatere nu, eller automatisk når du lukker Modrinth App."
- },
"app.update-popup.body.download-complete": {
"message": "Modrinth App v{version} er færdig med at download. Genindlæs for at opdatere nu, eller automatisk når du lukker Modrinth App."
},
@@ -488,15 +485,6 @@
"app.update.complete-toast.title": {
"message": "Version {version} var installeret med succes!"
},
- "app.update.download-update": {
- "message": "Download opdatering"
- },
- "app.update.downloading-update": {
- "message": "Downloader opdatering ({percent}%)"
- },
- "app.update.reload-to-update": {
- "message": "Genindlæs for at installere opdatering"
- },
"app.world.server-modal.placeholder-address": {
"message": "eksemple.modrinth.gg"
},
diff --git a/apps/app-frontend/src/locales/de-CH/index.json b/apps/app-frontend/src/locales/de-CH/index.json
index 604b2e955..dc3ff7a8e 100644
--- a/apps/app-frontend/src/locales/de-CH/index.json
+++ b/apps/app-frontend/src/locales/de-CH/index.json
@@ -473,9 +473,6 @@
"app.skins.title": {
"message": "Skin-Auswahl"
},
- "app.update-popup.body": {
- "message": "Modrinth App v{version} ist bereit zur Installation! Lade die App neu um jetzt zu aktualisieren, oder automatisch nach dem schliessen der Modrinth App."
- },
"app.update-popup.body.download-complete": {
"message": "Modrinth App v{version} wurde heruntergeladen. Lade die App neu um jetzt zu aktualisieren, oder automatisch nach dem schliessen der Modrinth App."
},
@@ -506,15 +503,6 @@
"app.update.complete-toast.title": {
"message": "Version {version} wurde erfolgreich installiert!"
},
- "app.update.download-update": {
- "message": "Aktualisierung herunterladen"
- },
- "app.update.downloading-update": {
- "message": "Lade Aktualisierung herunter ({percent}%)"
- },
- "app.update.reload-to-update": {
- "message": "Lade neu um Aktualisierung zu installieren"
- },
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
diff --git a/apps/app-frontend/src/locales/de-DE/index.json b/apps/app-frontend/src/locales/de-DE/index.json
index e21e16a0d..e557f185d 100644
--- a/apps/app-frontend/src/locales/de-DE/index.json
+++ b/apps/app-frontend/src/locales/de-DE/index.json
@@ -473,9 +473,6 @@
"app.skins.title": {
"message": "Skin-Auswahl"
},
- "app.update-popup.body": {
- "message": "Modrinth App v{version} ist bereit zur Installation! Neu laden, um jetzt zu aktualisieren, oder automatisch, wenn du die Modrinth App schließt."
- },
"app.update-popup.body.download-complete": {
"message": "Modrinth App v{version} wurde heruntergeladen. Neu laden, um jetzt zu aktualisieren, oder automatisch aktualisieren, wenn du die Modrinth App schließt."
},
@@ -506,15 +503,6 @@
"app.update.complete-toast.title": {
"message": "Version {version} wurde erfolgreich installiert!"
},
- "app.update.download-update": {
- "message": "Update herunterladen"
- },
- "app.update.downloading-update": {
- "message": "Update wird heruntergeladen ({percent}%)"
- },
- "app.update.reload-to-update": {
- "message": "Neu laden, um das Update zu installieren"
- },
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
diff --git a/apps/app-frontend/src/locales/en-US/index.json b/apps/app-frontend/src/locales/en-US/index.json
index 251cba9a9..0c387a124 100644
--- a/apps/app-frontend/src/locales/en-US/index.json
+++ b/apps/app-frontend/src/locales/en-US/index.json
@@ -2,6 +2,9 @@
"app.action-bar.downloading-java": {
"message": "Downloading Java {version}"
},
+ "app.action-bar.downloading-update": {
+ "message": "Downloading update"
+ },
"app.action-bar.downloads": {
"message": "Downloads"
},
@@ -20,12 +23,18 @@
"app.action-bar.primary-instance": {
"message": "Primary instance"
},
+ "app.action-bar.reload-to-update": {
+ "message": "Reload to update"
+ },
"app.action-bar.show-more-running-instances": {
"message": "Show more running instances"
},
"app.action-bar.stop-instance": {
"message": "Stop instance"
},
+ "app.action-bar.update": {
+ "message": "Update"
+ },
"app.action-bar.view-active-downloads": {
"message": "View active downloads"
},
@@ -482,9 +491,6 @@
"app.skins.title": {
"message": "Skin selector"
},
- "app.update-popup.body": {
- "message": "Modrinth App v{version} is ready to install! Reload to update now, or automatically when you close Modrinth App."
- },
"app.update-popup.body.download-complete": {
"message": "Modrinth App v{version} has finished downloading. Reload to update now, or automatically when you close Modrinth App."
},
@@ -504,7 +510,7 @@
"message": "Download complete"
},
"app.update-popup.reload": {
- "message": "Reload"
+ "message": "Reload to update"
},
"app.update-popup.title": {
"message": "Update available"
@@ -515,15 +521,6 @@
"app.update.complete-toast.title": {
"message": "Version {version} was successfully installed!"
},
- "app.update.download-update": {
- "message": "Download update"
- },
- "app.update.downloading-update": {
- "message": "Downloading update ({percent}%)"
- },
- "app.update.reload-to-update": {
- "message": "Reload to install update"
- },
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
diff --git a/apps/app-frontend/src/locales/es-419/index.json b/apps/app-frontend/src/locales/es-419/index.json
index 346bab26d..9b9c059a7 100644
--- a/apps/app-frontend/src/locales/es-419/index.json
+++ b/apps/app-frontend/src/locales/es-419/index.json
@@ -473,9 +473,6 @@
"app.skins.title": {
"message": "Selector de skin"
},
- "app.update-popup.body": {
- "message": "¡Modrinth App v{version} está lista para instalarse! Actualiza ahora o automáticamente al cerrar la Modrinth App."
- },
"app.update-popup.body.download-complete": {
"message": "La descarga de la Modrinth App v{version} ha finalizado. Actualiza ahora o automáticamente al cerrar la Modrinth App."
},
@@ -506,15 +503,6 @@
"app.update.complete-toast.title": {
"message": "¡La versión {version} se ha instalado correctamente!"
},
- "app.update.download-update": {
- "message": "Descargar actualización"
- },
- "app.update.downloading-update": {
- "message": "Descargando actualización ({percent}%)"
- },
- "app.update.reload-to-update": {
- "message": "Recarga para instalar la actualización"
- },
"app.world.server-modal.placeholder-address": {
"message": "ejemplo.modrinth.gg"
},
diff --git a/apps/app-frontend/src/locales/es-ES/index.json b/apps/app-frontend/src/locales/es-ES/index.json
index 7776e4168..174616ca9 100644
--- a/apps/app-frontend/src/locales/es-ES/index.json
+++ b/apps/app-frontend/src/locales/es-ES/index.json
@@ -482,9 +482,6 @@
"app.skins.title": {
"message": "Selector de Skin"
},
- "app.update-popup.body": {
- "message": "¡La versión v{version} de Modrinth está lista para instalarse! Actualiza ahora o automáticamente al cerrar la aplicación."
- },
"app.update-popup.body.download-complete": {
"message": "Modrinth App v{version} se ha descargado. Actualiza la página ahora o espera a que se actualice automáticamente al cerrar Modrinth App."
},
@@ -515,15 +512,6 @@
"app.update.complete-toast.title": {
"message": "¡La versión {version} se ha instalado correctamente!"
},
- "app.update.download-update": {
- "message": "Descarga actualización"
- },
- "app.update.downloading-update": {
- "message": "Descargando actualización ({percent}%)"
- },
- "app.update.reload-to-update": {
- "message": "Recarga para instalar la actualización"
- },
"app.world.server-modal.placeholder-address": {
"message": "ejemplo.modrinth.gg"
},
diff --git a/apps/app-frontend/src/locales/fi-FI/index.json b/apps/app-frontend/src/locales/fi-FI/index.json
index 852d97b88..25225683c 100644
--- a/apps/app-frontend/src/locales/fi-FI/index.json
+++ b/apps/app-frontend/src/locales/fi-FI/index.json
@@ -332,9 +332,6 @@
"app.settings.tabs.resource-management": {
"message": "Resurssien hallinta"
},
- "app.update-popup.body": {
- "message": "Modrinth App versio {version} on valmis asennettavaksi. Voit käynnistää sovelluksen uudelleen päivittääksesi heti tai antaa päivityksen asentua automaattisesti, kun suljet Modrinth Appin."
- },
"app.update-popup.body.download-complete": {
"message": "Modrinth App versio {version} on ladattu. Voit käynnistää sovelluksen uudelleen päivittääksesi heti tai antaa päivityksen asentua automaattisesti, kun suljet Modrinth Appin."
},
@@ -365,15 +362,6 @@
"app.update.complete-toast.title": {
"message": "Versio {version} asennettiin onnistuneesti!"
},
- "app.update.download-update": {
- "message": "Lataa päivitys"
- },
- "app.update.downloading-update": {
- "message": "Ladataan päivitystä ({percent}%)"
- },
- "app.update.reload-to-update": {
- "message": "Lataa uudelleen asentaaksesi päivityksen"
- },
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
diff --git a/apps/app-frontend/src/locales/fil-PH/index.json b/apps/app-frontend/src/locales/fil-PH/index.json
index 04a3bb00b..4f7964836 100644
--- a/apps/app-frontend/src/locales/fil-PH/index.json
+++ b/apps/app-frontend/src/locales/fil-PH/index.json
@@ -359,9 +359,6 @@
"app.skins.section.modrinth-pride": {
"message": "Modrinth Pride"
},
- "app.update-popup.body": {
- "message": "Ang Modrinth App v{version} ay handa nang ma-install. Mag-reload upang ma-update ngayon, o awtomatiko sa pagsara ng Modrinth App."
- },
"app.update-popup.body.download-complete": {
"message": "Tapos nang ma-download ang Modrinth App v{version}. Mag-reload upang ma-update ngayon, o awtomatiko sa pagsara ng Modrinth App."
},
@@ -392,15 +389,6 @@
"app.update.complete-toast.title": {
"message": "Tagumpay na na-install ang bersiyong {version}!"
},
- "app.update.download-update": {
- "message": "I-download ang update"
- },
- "app.update.downloading-update": {
- "message": "Nagdadownload ng update ({percent}%)"
- },
- "app.update.reload-to-update": {
- "message": "Handang ma-install ang update"
- },
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
diff --git a/apps/app-frontend/src/locales/fr-FR/index.json b/apps/app-frontend/src/locales/fr-FR/index.json
index 9c9878687..7517e0396 100644
--- a/apps/app-frontend/src/locales/fr-FR/index.json
+++ b/apps/app-frontend/src/locales/fr-FR/index.json
@@ -473,9 +473,6 @@
"app.skins.title": {
"message": "Sélecteur de skin"
},
- "app.update-popup.body": {
- "message": "Modrinth App v{version} est prête à être installée ! Relancez l'application pour faire la mise à jour maintenant, ou automatiquement à la fermeture de Modrinth App."
- },
"app.update-popup.body.download-complete": {
"message": "Modrinth App v{version} a finie d'être téléchargée. Rechargez pour mettre à jour maintenant, ou automatiquement quand vous fermez Modrinth App."
},
@@ -506,15 +503,6 @@
"app.update.complete-toast.title": {
"message": "La version {version} a été téléchargée avec succès !"
},
- "app.update.download-update": {
- "message": "Télécharger la mise à jour"
- },
- "app.update.downloading-update": {
- "message": "Téléchargement de la mise à jour ({percent}%)"
- },
- "app.update.reload-to-update": {
- "message": "Relancez l'application pour installer la mise à jour"
- },
"app.world.server-modal.placeholder-address": {
"message": "exemple.modrinth.gg"
},
diff --git a/apps/app-frontend/src/locales/he-IL/index.json b/apps/app-frontend/src/locales/he-IL/index.json
index 1b9512b68..bd1d891a5 100644
--- a/apps/app-frontend/src/locales/he-IL/index.json
+++ b/apps/app-frontend/src/locales/he-IL/index.json
@@ -203,9 +203,6 @@
"app.settings.tabs.resource-management": {
"message": "ניהול משאבים"
},
- "app.update-popup.body": {
- "message": "Modrinth App v{version} מוכנה להורדה!\nיש לרענן כדי לעדכן עכשיו, או באופן אוטומטי בעת סגירת האפליקציה."
- },
"app.update-popup.body.download-complete": {
"message": "Modrinth App v{version} סיימה את תהליך ההורדה. יש לרענן כדי לעדכן עכשיו, או באופן אוטומטי בעת סגירת האפליקציה."
},
@@ -236,15 +233,6 @@
"app.update.complete-toast.title": {
"message": "גרסה {version} הותקנה בהצלחה!"
},
- "app.update.download-update": {
- "message": "הורדת עדכון"
- },
- "app.update.downloading-update": {
- "message": "מוריד עדכון ({percent}%)"
- },
- "app.update.reload-to-update": {
- "message": "צריך לרענן כדי להתקין את העדכון"
- },
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
diff --git a/apps/app-frontend/src/locales/hu-HU/index.json b/apps/app-frontend/src/locales/hu-HU/index.json
index d6f2bba47..c30ff66cd 100644
--- a/apps/app-frontend/src/locales/hu-HU/index.json
+++ b/apps/app-frontend/src/locales/hu-HU/index.json
@@ -479,9 +479,6 @@
"app.skins.title": {
"message": "Kinézetváltó"
},
- "app.update-popup.body": {
- "message": "A Modrinth App v{version} telepítésre kész! Frissítéshez válaszd ki a Frissítés opciót, vagy az alkalmazás a bezárásakor automatikusan frissül."
- },
"app.update-popup.body.download-complete": {
"message": "A Modrinth App v{version} letöltése befejeződött. Frissítéshez válaszd ki a Frissítés opciót, vagy az alkalmazás a bezárásakor automatikusan frissül."
},
@@ -512,15 +509,6 @@
"app.update.complete-toast.title": {
"message": "Verzió {version} sikeresen telepítve!"
},
- "app.update.download-update": {
- "message": "Frissítés letöltése"
- },
- "app.update.downloading-update": {
- "message": "Frissítés letöltése ({percent}%)"
- },
- "app.update.reload-to-update": {
- "message": "A telepítéshez újraindítás szükséges"
- },
"app.world.server-modal.placeholder-address": {
"message": "pelda.modrinth.gg"
},
diff --git a/apps/app-frontend/src/locales/id-ID/index.json b/apps/app-frontend/src/locales/id-ID/index.json
index 8b939a59d..5699622bc 100644
--- a/apps/app-frontend/src/locales/id-ID/index.json
+++ b/apps/app-frontend/src/locales/id-ID/index.json
@@ -470,9 +470,6 @@
"app.skins.title": {
"message": "Pemilih rupa"
},
- "app.update-popup.body": {
- "message": "Modrinth App v{version} siap dipasang! Muat ulang untuk memperbarui sekarang, atau secara otomatis saat Anda menutup Modrinth App."
- },
"app.update-popup.body.download-complete": {
"message": "Modrinth App v{version} telah selesai mengunduh. Muat ulang untuk memperbarui sekarang, atau secara otomatis saat Anda menutup Modrinth App."
},
@@ -503,15 +500,6 @@
"app.update.complete-toast.title": {
"message": "Versi {version} berhasil dipasang!"
},
- "app.update.download-update": {
- "message": "Unduh pembaruan"
- },
- "app.update.downloading-update": {
- "message": "Mengunduh pembaruan ({percent}%)"
- },
- "app.update.reload-to-update": {
- "message": "Muat ulang untuk memasang pembaruan"
- },
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
diff --git a/apps/app-frontend/src/locales/it-IT/index.json b/apps/app-frontend/src/locales/it-IT/index.json
index da28a5717..0c2bfead7 100644
--- a/apps/app-frontend/src/locales/it-IT/index.json
+++ b/apps/app-frontend/src/locales/it-IT/index.json
@@ -470,9 +470,6 @@
"app.skins.title": {
"message": "Seleziona una skin"
},
- "app.update-popup.body": {
- "message": "Modrinth App v{version} è pronta per essere installata! Ricarica per aggiornare ora, o avverrà in automatico alla chiusura dell'app."
- },
"app.update-popup.body.download-complete": {
"message": "Modrinth App v{version} è stata scaricata. Ricarica per aggiornare ora, o avverrà in automatico alla chiusura dell'app."
},
@@ -503,15 +500,6 @@
"app.update.complete-toast.title": {
"message": "La versione {version} è stata installata con successo!"
},
- "app.update.download-update": {
- "message": "Scarica aggiornamento"
- },
- "app.update.downloading-update": {
- "message": "Scaricando l'aggiornamento ({percent}%)"
- },
- "app.update.reload-to-update": {
- "message": "Ricarica per installare l'aggiornamento"
- },
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
diff --git a/apps/app-frontend/src/locales/ja-JP/index.json b/apps/app-frontend/src/locales/ja-JP/index.json
index 3e3b6cf60..8cac11878 100644
--- a/apps/app-frontend/src/locales/ja-JP/index.json
+++ b/apps/app-frontend/src/locales/ja-JP/index.json
@@ -311,9 +311,6 @@
"app.skins.preview.edit-button": {
"message": "編集"
},
- "app.update-popup.body": {
- "message": "Modrinth App v{version} のインストール準備が整いました!今すぐ更新するには再読み込みするか、Modrinth Appを閉じる際に自動更新されます。"
- },
"app.update-popup.body.download-complete": {
"message": "Modrinth App v{version} のダウンロードが完了しました。今すぐ更新するには再読み込みするか、Modrinth Appを閉じる際に自動更新されます。"
},
@@ -344,15 +341,6 @@
"app.update.complete-toast.title": {
"message": "バージョン {version} が正常にインストールされました!"
},
- "app.update.download-update": {
- "message": "アップデートをダウンロード"
- },
- "app.update.downloading-update": {
- "message": "アップデートをダウンロード中 ({percent}%)"
- },
- "app.update.reload-to-update": {
- "message": "再起動して今すぐ更新"
- },
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
diff --git a/apps/app-frontend/src/locales/ko-KR/index.json b/apps/app-frontend/src/locales/ko-KR/index.json
index 30b559f9e..0ca5feb14 100644
--- a/apps/app-frontend/src/locales/ko-KR/index.json
+++ b/apps/app-frontend/src/locales/ko-KR/index.json
@@ -482,9 +482,6 @@
"app.skins.title": {
"message": "스킨 선택"
},
- "app.update-popup.body": {
- "message": "Modrinth App v{version}을 설치할 준비가 완료되었습니다! 새로고침하거나 Modrinth App을 종료하면 자동으로 업데이트됩니다."
- },
"app.update-popup.body.download-complete": {
"message": "Modrinth App v{version} 다운로드가 완료되었습니다. 새로고침하거나 Modrinth App을 종료하면 자동으로 업데이트됩니다."
},
@@ -515,15 +512,6 @@
"app.update.complete-toast.title": {
"message": "{version} 버전이 성공적으로 설치되었습니다!"
},
- "app.update.download-update": {
- "message": "업데이트 다운로드"
- },
- "app.update.downloading-update": {
- "message": "업데이트 다운로드 중 ({percent}%)"
- },
- "app.update.reload-to-update": {
- "message": "새로고침하여 업데이트 설치"
- },
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
diff --git a/apps/app-frontend/src/locales/ms-MY/index.json b/apps/app-frontend/src/locales/ms-MY/index.json
index ac946f4e6..e19cddb6e 100644
--- a/apps/app-frontend/src/locales/ms-MY/index.json
+++ b/apps/app-frontend/src/locales/ms-MY/index.json
@@ -437,9 +437,6 @@
"app.skins.title": {
"message": "Pemilih kekulit"
},
- "app.update-popup.body": {
- "message": "Modrinth App v{version} sudah bersedia untuk dipasang! Muat semula untuk kemas kini sekarang, atau kemas kini secara automatik apabila anda menutup Modrinth App."
- },
"app.update-popup.body.download-complete": {
"message": "Modrinth App v{version} sudah selesai dimuat turun! Muat semula untuk kemas kini sekarang, atau kemas kini secara automatik apabila anda menutup Modrinth App."
},
@@ -470,15 +467,6 @@
"app.update.complete-toast.title": {
"message": "Versi {version} telah berjaya dipasang!"
},
- "app.update.download-update": {
- "message": "Muat turun kemas kini"
- },
- "app.update.downloading-update": {
- "message": "Sedang memuat turun kemas kini ({percent}%)"
- },
- "app.update.reload-to-update": {
- "message": "Muat semula untuk memasang kemas kini"
- },
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
diff --git a/apps/app-frontend/src/locales/nl-NL/index.json b/apps/app-frontend/src/locales/nl-NL/index.json
index 3256f94ee..4150352d1 100644
--- a/apps/app-frontend/src/locales/nl-NL/index.json
+++ b/apps/app-frontend/src/locales/nl-NL/index.json
@@ -323,9 +323,6 @@
"app.settings.tabs.resource-management": {
"message": "Bronnenbeheer"
},
- "app.update-popup.body": {
- "message": "Modrinth App v{version} is klaar om geïnstalleerd te worden! Herlaad om nu te updaten, of automatisch wanneer je de Modrinth App afsluit."
- },
"app.update-popup.body.download-complete": {
"message": "Modrinth App v{version} is klaar met downloaden. Herlaad om nu te updaten, of automatisch wanneer je de Modrinth App afsluit."
},
@@ -356,15 +353,6 @@
"app.update.complete-toast.title": {
"message": "Versie {version} is succesvol geïnstalleerd!"
},
- "app.update.download-update": {
- "message": "Download update"
- },
- "app.update.downloading-update": {
- "message": "Update downloaden ({percent}%)"
- },
- "app.update.reload-to-update": {
- "message": "Herlaad om de update te installeren"
- },
"app.world.server-modal.placeholder-address": {
"message": "voorbeeld.modrinth.gg"
},
diff --git a/apps/app-frontend/src/locales/no-NO/index.json b/apps/app-frontend/src/locales/no-NO/index.json
index 757904d09..aefea1b9d 100644
--- a/apps/app-frontend/src/locales/no-NO/index.json
+++ b/apps/app-frontend/src/locales/no-NO/index.json
@@ -59,15 +59,6 @@
"app.update.complete-toast.title": {
"message": "Versjon {version} ble installert!"
},
- "app.update.download-update": {
- "message": "Last ned oppdatering"
- },
- "app.update.downloading-update": {
- "message": "Laster ned oppdatering ({percent}%)"
- },
- "app.update.reload-to-update": {
- "message": "Last inn på nytt for å installere oppdateringen"
- },
"friends.action.add-friend": {
"message": "Legg til en venn"
},
diff --git a/apps/app-frontend/src/locales/pl-PL/index.json b/apps/app-frontend/src/locales/pl-PL/index.json
index f9eec6db6..94fa160a7 100644
--- a/apps/app-frontend/src/locales/pl-PL/index.json
+++ b/apps/app-frontend/src/locales/pl-PL/index.json
@@ -476,9 +476,6 @@
"app.skins.title": {
"message": "Wybierz skórkę"
},
- "app.update-popup.body": {
- "message": "Wersja Modrinth App v{version} jest gotowa do zainstalowania! Załaduj ponownie, żeby zaktualizować teraz, albo automatycznie, gdy zamkniesz Modrinth App."
- },
"app.update-popup.body.download-complete": {
"message": "Wersja Modrinth App v{version} została pobrana. Załaduj ponownie, żeby zaktualizować teraz, albo automatycznie, gdy zamkniesz Modrinth App."
},
@@ -509,15 +506,6 @@
"app.update.complete-toast.title": {
"message": "Wersja {version} została pomyślnie zainstalowana!"
},
- "app.update.download-update": {
- "message": "Pobierz aktualizację"
- },
- "app.update.downloading-update": {
- "message": "Pobieranie aktualizacji ({percent}%)"
- },
- "app.update.reload-to-update": {
- "message": "Załaduj ponownie, aby zainstalować aktualizację"
- },
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
diff --git a/apps/app-frontend/src/locales/pt-BR/index.json b/apps/app-frontend/src/locales/pt-BR/index.json
index 77afd24fa..4807bc5fb 100644
--- a/apps/app-frontend/src/locales/pt-BR/index.json
+++ b/apps/app-frontend/src/locales/pt-BR/index.json
@@ -479,9 +479,6 @@
"app.skins.title": {
"message": "Seletor de skins"
},
- "app.update-popup.body": {
- "message": "O Modrinth App v{version} está pronto para ser instalado! Você pode recarregar para atualizar agora ou a atualização será feita automaticamente ao fechar o Modrinth App."
- },
"app.update-popup.body.download-complete": {
"message": "O Modrinth App v{version} foi baixado. Recarregue para atualizar agora ou a atualização será aplicada automaticamente ao fechar o Modrinth App."
},
@@ -512,15 +509,6 @@
"app.update.complete-toast.title": {
"message": "Versão {version} instalada!"
},
- "app.update.download-update": {
- "message": "Baixar atualização"
- },
- "app.update.downloading-update": {
- "message": "Baixando atualização ({percent}%)"
- },
- "app.update.reload-to-update": {
- "message": "Recarregue para instalar a atualização"
- },
"app.world.server-modal.placeholder-address": {
"message": "exemplo.modrinth.gg"
},
diff --git a/apps/app-frontend/src/locales/pt-PT/index.json b/apps/app-frontend/src/locales/pt-PT/index.json
index e7e404efb..24f3b8a1b 100644
--- a/apps/app-frontend/src/locales/pt-PT/index.json
+++ b/apps/app-frontend/src/locales/pt-PT/index.json
@@ -239,9 +239,6 @@
"app.settings.tabs.resource-management": {
"message": "Gestão de recursos"
},
- "app.update-popup.body": {
- "message": "Modrinth App v{version} está pronta para ser instalada! Recarrega para atualizar agora, ou automaticamente quando fechares a Modrinth App."
- },
"app.update-popup.body.download-complete": {
"message": "Modrinth App v{version} acabou de ser transferida. Recarrega para atualizar agora, ou automaticamente quando fechares a Modrinth App."
},
@@ -272,15 +269,6 @@
"app.update.complete-toast.title": {
"message": "Versão {version} foi instalada com sucesso!"
},
- "app.update.download-update": {
- "message": "Transferir atualização"
- },
- "app.update.downloading-update": {
- "message": "A transferir atualização ({percent}%)"
- },
- "app.update.reload-to-update": {
- "message": "Recarrega para instalar a atualização"
- },
"app.world.server-modal.placeholder-address": {
"message": "exemplo.modrinth.gg"
},
diff --git a/apps/app-frontend/src/locales/ro-RO/index.json b/apps/app-frontend/src/locales/ro-RO/index.json
index f56a70549..7ab60f8ee 100644
--- a/apps/app-frontend/src/locales/ro-RO/index.json
+++ b/apps/app-frontend/src/locales/ro-RO/index.json
@@ -65,9 +65,6 @@
"app.settings.tabs.resource-management": {
"message": "Administrare resurse"
},
- "app.update-popup.body": {
- "message": "Aplicația Modrinth v{version} este gata de instalat! Reîncărcați pentru a actualiza acum sau automat când închideți aplicația Modrinth."
- },
"app.update-popup.body.download-complete": {
"message": "Aplicația Modrinth v{version} a terminat descărcarea. Reîncărcați pentru a actualiza acum sau automat când închideți aplicația Modrinth."
},
@@ -98,15 +95,6 @@
"app.update.complete-toast.title": {
"message": "Versiunea {version} a fost instalată cu succes!"
},
- "app.update.download-update": {
- "message": "Descarcă actualizarea"
- },
- "app.update.downloading-update": {
- "message": "Se descarcă actualizarea ({percent}%)"
- },
- "app.update.reload-to-update": {
- "message": "Reîncarcă pentru a instala actualizarea"
- },
"app.world.server-modal.select-an-option": {
"message": "Selectează o opțiune"
},
diff --git a/apps/app-frontend/src/locales/ru-RU/index.json b/apps/app-frontend/src/locales/ru-RU/index.json
index 4816556c4..9e1416013 100644
--- a/apps/app-frontend/src/locales/ru-RU/index.json
+++ b/apps/app-frontend/src/locales/ru-RU/index.json
@@ -464,9 +464,6 @@
"app.skins.title": {
"message": "Выбор скина"
},
- "app.update-popup.body": {
- "message": "Версия Modrinth App {version} готова к установке! Перезапустите приложение, чтобы обновить его, или оно обновится автоматически после закрытия."
- },
"app.update-popup.body.download-complete": {
"message": "Скачивание версии Modrinth App {version} завершено. Перезапустите приложение, чтобы обновить его, или оно обновится автоматически после закрытия."
},
@@ -497,15 +494,6 @@
"app.update.complete-toast.title": {
"message": "Версия {version} успешно установлена!"
},
- "app.update.download-update": {
- "message": "Скачать обновление"
- },
- "app.update.downloading-update": {
- "message": "Скачивание обновления ({percent}%)"
- },
- "app.update.reload-to-update": {
- "message": "Перезапустить и обновить"
- },
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
diff --git a/apps/app-frontend/src/locales/sv-SE/index.json b/apps/app-frontend/src/locales/sv-SE/index.json
index 51f1a1a43..ece76a636 100644
--- a/apps/app-frontend/src/locales/sv-SE/index.json
+++ b/apps/app-frontend/src/locales/sv-SE/index.json
@@ -353,9 +353,6 @@
"app.skins.modal.texture-section": {
"message": "Textur"
},
- "app.update-popup.body": {
- "message": "Modrinth App v{version} är redo att laddas ner! Ladda om för att uppdatera nu, eller automatiskt när du stänger Modrinth App."
- },
"app.update-popup.body.download-complete": {
"message": "Modrinth App v{version} har laddats ner. Ladda om för att uppdatera nu, eller automatiskt när du stänger Modrinth App."
},
@@ -386,15 +383,6 @@
"app.update.complete-toast.title": {
"message": "Version {version} har installerats!"
},
- "app.update.download-update": {
- "message": "Ladda ner uppdatering"
- },
- "app.update.downloading-update": {
- "message": "Laddar ner uppdatering ({percent}%)"
- },
- "app.update.reload-to-update": {
- "message": "Ladda om för att installera uppdatering"
- },
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
diff --git a/apps/app-frontend/src/locales/th-TH/index.json b/apps/app-frontend/src/locales/th-TH/index.json
index 1ce9dd603..02aca05b3 100644
--- a/apps/app-frontend/src/locales/th-TH/index.json
+++ b/apps/app-frontend/src/locales/th-TH/index.json
@@ -308,9 +308,6 @@
"app.settings.tabs.resource-management": {
"message": "การจัดการทรัพยากร"
},
- "app.update-popup.body": {
- "message": "Modrinth v{version} พร้อมสำหรับการติดตั้งแล้ว! เปิดโปรแกรมใหม่อีกครั้งเพื่ออัปเดตตอนนี้ หรือจะรออัปเดตอัตโนมัติ ซึ่งจะเกิดขึ้นเมื่อคุณกดปิดโปรแกรม Modrinth"
- },
"app.update-popup.body.download-complete": {
"message": "ดาวน์โหลด Modrinth v{version} สำเร็จแล้ว เปิดโปรแกรมใหม่อีกครั้งเพื่ออัปเดตตอนนี้ หรือจะรออัปเดตอัตโนมัติ ซึ่งจะเกิดขึ้นเมื่อคุณกดปิดโปรแกรม Modrinth"
},
@@ -341,15 +338,6 @@
"app.update.complete-toast.title": {
"message": "เวอร์ชั่น {version} ถูกติดตั้งแล้ว"
},
- "app.update.download-update": {
- "message": "ดาวน์โหลดอัปเดต"
- },
- "app.update.downloading-update": {
- "message": "ดาวน์โหลดอัปเดตไปแล้ว ({percent}%)"
- },
- "app.update.reload-to-update": {
- "message": "รีโหลดเพื่อติดตั้งอัปเดต"
- },
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
diff --git a/apps/app-frontend/src/locales/tr-TR/index.json b/apps/app-frontend/src/locales/tr-TR/index.json
index f93845bea..2ce634059 100644
--- a/apps/app-frontend/src/locales/tr-TR/index.json
+++ b/apps/app-frontend/src/locales/tr-TR/index.json
@@ -482,9 +482,6 @@
"app.skins.title": {
"message": "Skin seçici"
},
- "app.update-popup.body": {
- "message": "Modrinth Uygulaması v{version} yüklenmeye hazır! Güncellemek için yeniden başlatın veya Modrinth Uygulamasını kapattığınızda otomatik olarak güncellenecektir."
- },
"app.update-popup.body.download-complete": {
"message": "Modrinth App v{version} indirildi. Güncellemek için yeniden başlatın veya Modrinth App’i kapattığınızda otomatik olarak güncellenecektir."
},
@@ -515,15 +512,6 @@
"app.update.complete-toast.title": {
"message": "{version} sürümü başarıyla kuruldu!"
},
- "app.update.download-update": {
- "message": "Güncellemeyi indir"
- },
- "app.update.downloading-update": {
- "message": "Güncelleme indiriliyor (%{percent})"
- },
- "app.update.reload-to-update": {
- "message": "Güncellemeyi kurmak için yeniden başlatın"
- },
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
diff --git a/apps/app-frontend/src/locales/uk-UA/index.json b/apps/app-frontend/src/locales/uk-UA/index.json
index 989597163..ca11cd087 100644
--- a/apps/app-frontend/src/locales/uk-UA/index.json
+++ b/apps/app-frontend/src/locales/uk-UA/index.json
@@ -467,9 +467,6 @@
"app.skins.title": {
"message": "Вибір скіну"
},
- "app.update-popup.body": {
- "message": "Modrinth App v{version} готовий до встановлення! Перезапустіть, щоб оновити зараз. Або, оновлення буде здійснено автоматично, коли закриєте застосунок Modrinth."
- },
"app.update-popup.body.download-complete": {
"message": "Modrinth App v{version} завантажено. Перезапустіть зараз, щоб оновити його, або це відбудеться автоматично після закриття Modrinth App."
},
@@ -500,15 +497,6 @@
"app.update.complete-toast.title": {
"message": "Версію {version} успішно встановлено!"
},
- "app.update.download-update": {
- "message": "Завантажити оновлення"
- },
- "app.update.downloading-update": {
- "message": "Завантаження оновлення ({percent}%)"
- },
- "app.update.reload-to-update": {
- "message": "Перезавантажте, щоб установити оновлення"
- },
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
diff --git a/apps/app-frontend/src/locales/vi-VN/index.json b/apps/app-frontend/src/locales/vi-VN/index.json
index 878b927e6..51ed242ef 100644
--- a/apps/app-frontend/src/locales/vi-VN/index.json
+++ b/apps/app-frontend/src/locales/vi-VN/index.json
@@ -476,9 +476,6 @@
"app.skins.title": {
"message": "Đổi skin"
},
- "app.update-popup.body": {
- "message": "Phiên bản v{version} của Modrinth đã được chuẩn bị để cài đặt! Khởi động lại ứng dụng để cập nhật ngay bây giờ, hoặc cập nhật tự động khi bạn đóng Modrinth."
- },
"app.update-popup.body.download-complete": {
"message": "Phiên bản v{version} của Modrinth đã sẵn sàng để có thể cài đặt. Khởi động lại ứng dụng để cập nhật ngay bây giờ, hoặc tự động cập nhật sau khi bạn tắt Modrinth."
},
@@ -509,15 +506,6 @@
"app.update.complete-toast.title": {
"message": "Phiên bản {version} đã được cài đặt thành công!"
},
- "app.update.download-update": {
- "message": "Tải về bản cập nhật"
- },
- "app.update.downloading-update": {
- "message": "Đang tải xuống bản cập nhật ({percent}%)"
- },
- "app.update.reload-to-update": {
- "message": "Hãy khởi động lại để cài đặt bản cập nhật"
- },
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
diff --git a/apps/app-frontend/src/locales/zh-CN/index.json b/apps/app-frontend/src/locales/zh-CN/index.json
index 42b5faae6..90cff01f8 100644
--- a/apps/app-frontend/src/locales/zh-CN/index.json
+++ b/apps/app-frontend/src/locales/zh-CN/index.json
@@ -470,9 +470,6 @@
"app.skins.title": {
"message": "皮肤选择器"
},
- "app.update-popup.body": {
- "message": "Modrinth App v{version} 更新已就绪!立即重启更新,或退出时自动安装。"
- },
"app.update-popup.body.download-complete": {
"message": "Modrinth App v{version} 更新已下载完成!立即重启更新,或退出时自动安装。"
},
@@ -503,15 +500,6 @@
"app.update.complete-toast.title": {
"message": "版本 {version} 已成功安装!"
},
- "app.update.download-update": {
- "message": "下载更新"
- },
- "app.update.downloading-update": {
- "message": "下载更新中({percent}%)"
- },
- "app.update.reload-to-update": {
- "message": "重启以安装更新"
- },
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
diff --git a/apps/app-frontend/src/locales/zh-TW/index.json b/apps/app-frontend/src/locales/zh-TW/index.json
index a56ea0ee2..d54f38d82 100644
--- a/apps/app-frontend/src/locales/zh-TW/index.json
+++ b/apps/app-frontend/src/locales/zh-TW/index.json
@@ -404,9 +404,6 @@
"app.skins.title": {
"message": "皮膚選擇"
},
- "app.update-popup.body": {
- "message": "Modrinth App v{version} 已準備好安裝!立即重新載入以更新,或在關閉 Modrinth App 時自動更新。"
- },
"app.update-popup.body.download-complete": {
"message": "Modrinth App v{version} 已完成下載!立即重新載入以更新,或在關閉 Modrinth App 時自動更新。"
},
@@ -437,15 +434,6 @@
"app.update.complete-toast.title": {
"message": "版本 {version} 已成功安裝!"
},
- "app.update.download-update": {
- "message": "下載更新"
- },
- "app.update.downloading-update": {
- "message": "正在下載更新 ({percent}%)"
- },
- "app.update.reload-to-update": {
- "message": "重新載入即可安裝更新"
- },
"app.world.server-modal.placeholder-address": {
"message": "example.modrinth.gg"
},
diff --git a/apps/app-frontend/src/providers/app-update.ts b/apps/app-frontend/src/providers/app-update.ts
new file mode 100644
index 000000000..2a3a24b80
--- /dev/null
+++ b/apps/app-frontend/src/providers/app-update.ts
@@ -0,0 +1,171 @@
+import { computed, ref } from 'vue'
+
+export const APP_UPDATE_POPUP_DELAY_MS = 24 * 60 * 60 * 1000
+
+const UPDATE_PROMPT_STORAGE_KEY = 'modrinth-app-update-prompt-state'
+
+export interface AppUpdate {
+ rid: number
+ version: string
+ currentVersion?: string
+}
+
+interface UpdatePromptState {
+ version: string
+ stage: AppUpdatePromptStage
+ actionableSince: number
+ lastUserActionAt?: number
+ popupShownAt?: number
+}
+
+export type AppUpdatePromptStage = 'available' | 'downloaded'
+
+interface AppUpdateActions {
+ download?: () => Promise | void
+ install?: () => Promise | void
+ changelog?: () => Promise | void
+}
+
+const progress = ref(0)
+const metered = ref(true)
+const finishedDownloading = ref(false)
+const downloading = ref(false)
+const restarting = ref(false)
+const availableUpdate = ref(null)
+const updateSize = ref(null)
+const updatesEnabled = ref(true)
+
+let actions: AppUpdateActions = {}
+
+function getCurrentAppUpdatePromptStage(): AppUpdatePromptStage {
+ return finishedDownloading.value ? 'downloaded' : 'available'
+}
+
+export const appUpdateState = {
+ progress,
+ metered,
+ finishedDownloading,
+ downloading,
+ restarting,
+ availableUpdate,
+ updateSize,
+ updatesEnabled,
+ downloadProgress: computed(() => progress.value),
+ downloadPercent: computed(() => Math.trunc(progress.value * 100)),
+ isVisible: computed(() => !!availableUpdate.value && !restarting.value && updatesEnabled.value),
+}
+
+function readPromptState(): UpdatePromptState | null {
+ try {
+ const raw = localStorage.getItem(UPDATE_PROMPT_STORAGE_KEY)
+ if (!raw) {
+ return null
+ }
+
+ const parsed = JSON.parse(raw) as Partial
+ if (!parsed.version || typeof parsed.actionableSince !== 'number') {
+ return null
+ }
+
+ return {
+ ...parsed,
+ stage: parsed.stage ?? 'available',
+ } as UpdatePromptState
+ } catch {
+ return null
+ }
+}
+
+function writePromptState(state: UpdatePromptState): void {
+ try {
+ localStorage.setItem(UPDATE_PROMPT_STORAGE_KEY, JSON.stringify(state))
+ } catch (error) {
+ console.warn('Failed to persist update prompt state:', error)
+ }
+}
+
+export function markAppUpdateActionable(
+ version: string,
+ stage: AppUpdatePromptStage = 'available',
+ now = Date.now(),
+): void {
+ const existing = readPromptState()
+ if (existing?.version === version && existing.stage === stage) {
+ return
+ }
+
+ writePromptState({
+ version,
+ stage,
+ actionableSince: now,
+ })
+}
+
+export function recordAppUpdateUserAction(
+ version = availableUpdate.value?.version,
+ stage: AppUpdatePromptStage = getCurrentAppUpdatePromptStage(),
+): void {
+ if (!version) {
+ return
+ }
+
+ const now = Date.now()
+ const existing = readPromptState()
+ const isSamePrompt = existing?.version === version && existing.stage === stage
+ writePromptState({
+ version,
+ stage,
+ actionableSince: isSamePrompt ? existing.actionableSince : now,
+ lastUserActionAt: now,
+ popupShownAt: isSamePrompt ? existing.popupShownAt : undefined,
+ })
+}
+
+export function markAppUpdatePopupShown(
+ version: string,
+ stage: AppUpdatePromptStage = 'available',
+ now = Date.now(),
+): void {
+ const existing = readPromptState()
+ const isSamePrompt = existing?.version === version && existing.stage === stage
+ writePromptState({
+ version,
+ stage,
+ actionableSince: isSamePrompt ? existing.actionableSince : now,
+ lastUserActionAt: isSamePrompt ? existing.lastUserActionAt : undefined,
+ popupShownAt: now,
+ })
+}
+
+export function getNextAppUpdatePopupTime(
+ version: string,
+ stage: AppUpdatePromptStage = 'available',
+): number | null {
+ const existing = readPromptState()
+ if (existing?.version !== version || existing.stage !== stage || existing.popupShownAt) {
+ return null
+ }
+
+ return (
+ Math.max(existing.actionableSince, existing.lastUserActionAt ?? 0) + APP_UPDATE_POPUP_DELAY_MS
+ )
+}
+
+export function setAppUpdateActions(nextActions: AppUpdateActions): void {
+ actions = nextActions
+}
+
+export async function downloadAvailableAppUpdate(): Promise {
+ recordAppUpdateUserAction(undefined, 'available')
+ await actions.download?.()
+}
+
+export async function installAvailableAppUpdate(): Promise {
+ recordAppUpdateUserAction(undefined, 'downloaded')
+ await actions.install?.()
+}
+
+export async function openAppUpdateChangelog(): Promise {
+ recordAppUpdateUserAction()
+ await actions.changelog?.()
+}
diff --git a/apps/app/src/main.rs b/apps/app/src/main.rs
index 4df5dcd8e..33d2da879 100644
--- a/apps/app/src/main.rs
+++ b/apps/app/src/main.rs
@@ -307,6 +307,11 @@ fn main() {
}
set_changelog_toast(Some(update.version.clone()));
+ let update = if should_restart {
+ (**update).clone()
+ } else {
+ (**update).clone().restart_after_install(false)
+ };
match update.install(data) {
Ok(()) => {
if should_restart {
diff --git a/apps/app/tauri-release.conf.json b/apps/app/tauri-release.conf.json
index e3585db77..1f3972883 100644
--- a/apps/app/tauri-release.conf.json
+++ b/apps/app/tauri-release.conf.json
@@ -31,7 +31,10 @@
"plugins": {
"updater": {
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDIwMzM5QkE0M0FCOERBMzkKUldRNTJyZzZwSnN6SUdPRGdZREtUUGxMblZqeG9OVHYxRUlRTzJBc2U3MUNJaDMvZDQ1UytZZmYK",
- "endpoints": ["https://launcher-files.modrinth.com/updates.json"]
+ "endpoints": ["https://launcher-files.modrinth.com/updates.json"],
+ "windows": {
+ "installMode": "quiet"
+ }
}
}
}