feat(astralrinth): add launcher update installer selection

This commit is contained in:
2026-06-21 17:33:43 +03:00
parent c3ca512395
commit f23f220a0c
8 changed files with 214 additions and 91 deletions
@@ -1,14 +1,15 @@
<script setup lang="ts">
import { Button, defineMessages, useVIntl } from '@modrinth/ui'
import { computed, ref } from 'vue'
import { Button, Combobox, defineMessages, useVIntl } from '@modrinth/ui'
import { computed, ref, watch } from 'vue'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import {
downloadLatestRelease,
getAvailableInstallers,
isUpdateInstalling,
LAUNCHER_RELEASES_URL,
LAUNCHER_REPOSITORY_URL,
latestLauncherRelease,
latestLauncherReleases,
} from '@/helpers/astralrinth/update'
type ModalHandle = {
@@ -24,9 +25,17 @@ const { formatMessage } = useVIntl()
const updateModalView = ref<ModalHandle | null>(null)
const updateRequestFailView = ref<ModalHandle | null>(null)
const selectedInstallerName = ref<string | null>(null)
const releaseTag = computed(() => latestLauncherRelease.value?.tag_name ?? '')
const releaseTitle = computed(() => latestLauncherRelease.value?.name ?? '')
const releaseTag = computed(() => latestLauncherReleases.value?.tag_name ?? '')
const releaseTitle = computed(() => latestLauncherReleases.value?.name ?? '')
const availableInstallers = computed(() => getAvailableInstallers())
const selectedInstaller = computed(
() =>
availableInstallers.value.find((installer) => installer.name === selectedInstallerName.value) ??
null,
)
const selectedInstallerUrl = computed(() => selectedInstaller.value?.browser_download_url ?? null)
const messages = defineMessages({
updateHeader: {
@@ -63,6 +72,18 @@ const messages = defineMessages({
id: 'astralrinth.app.launcher-update-modal.update.notice-outro',
defaultMessage: 'To avoid data loss, keep a backup copy in a safe place before continuing.',
},
installerTitle: {
id: 'astralrinth.app.launcher-update-modal.update.installer-title',
defaultMessage: 'Installer type',
},
installerDescription: {
id: 'astralrinth.app.launcher-update-modal.update.installer-description',
defaultMessage: 'Choose the installer package you want to continue with.',
},
selectInstaller: {
id: 'astralrinth.app.launcher-update-modal.update.select-installer',
defaultMessage: 'Select an installer',
},
latestReleaseTag: {
id: 'astralrinth.app.launcher-update-modal.update.latest-release-tag',
defaultMessage: '☁️ Latest release tag:',
@@ -85,7 +106,7 @@ const messages = defineMessages({
},
downloadAction: {
id: 'astralrinth.app.launcher-update-modal.update.download-action',
defaultMessage: 'Download update and close',
defaultMessage: 'Download update',
},
errorHeader: {
id: 'astralrinth.app.launcher-update-modal.error.header',
@@ -121,13 +142,29 @@ const messages = defineMessages({
},
})
watch(
availableInstallers,
(installers) => {
const hasSelectedInstaller = installers.some(
(installer) => installer.name === selectedInstallerName.value,
)
if (hasSelectedInstaller) {
return
}
selectedInstallerName.value = installers.length === 1 ? installers[0].name : null
},
{ immediate: true },
)
async function show() {
updateModalView.value?.show()
}
async function initDownload() {
updateModalView.value?.hide()
const result = await downloadLatestRelease()
const result = await downloadLatestRelease(selectedInstaller.value)
if (!result) {
updateRequestFailView.value?.show()
@@ -197,11 +234,37 @@ defineExpose({
</a>
</div>
<div class="space-y-2 rounded-2xl border border-solid border-[rgba(255,255,255,0.12)] p-3">
<div>
<p class="m-0 text-base">
<strong>{{ formatMessage(messages.installerTitle) }}</strong>
</p>
<p class="m-0 text-secondary text-sm">
{{ formatMessage(messages.installerDescription) }}
</p>
</div>
<Combobox
v-model="selectedInstallerName"
name="AstralRinth launcher installer"
:options="
availableInstallers.map((installer) => ({
value: installer.name,
label: installer.name,
}))
"
:display-value="selectedInstallerName ?? formatMessage(messages.selectInstaller)"
/>
</div>
<div class="absolute bottom-4 right-4 flex items-center gap-4 neon-button neon">
<Button class="bordered" @click="updateModalView?.hide()">
{{ formatMessage(messages.cancelAction) }}
</Button>
<Button class="bordered" :disabled="isUpdateInstalling" @click="initDownload()">
<Button
class="bordered"
:disabled="isUpdateInstalling || !selectedInstallerUrl"
@click="initDownload()"
>
{{ formatMessage(messages.downloadAction) }}
</Button>
</div>
@@ -215,7 +278,9 @@ defineExpose({
>
<div class="space-y-3 pb-16">
<div class="space-y-2 rounded-2xl border border-solid border-[rgba(255,255,255,0.12)] p-3">
<p><strong>{{ formatMessage(messages.errorTitle) }}</strong></p>
<p>
<strong>{{ formatMessage(messages.errorTitle) }}</strong>
</p>
<p class="m-0 text-secondary">{{ formatMessage(messages.errorDescription) }}</p>
<p class="m-0 text-sm">
{{ formatMessage(messages.errorHelpText) }}
@@ -231,7 +296,9 @@ defineExpose({
</p>
</div>
<div class="rounded-2xl border border-solid border-[rgba(255,255,255,0.12)] p-3 text-sm text-secondary">
<div
class="rounded-2xl border border-solid border-[rgba(255,255,255,0.12)] p-3 text-sm text-secondary"
>
<p class="m-0">
<strong>{{ formatMessage(messages.localVersion) }}</strong>
<span class="neon-text">v{{ props.version }}</span>
@@ -22,15 +22,15 @@ const LAUNCHER_LATEST_RELEASE_API = `${import.meta.env.GIT_ASTRALIUM_API_URL}rep
export const isUpdateInstalling = ref(false)
export const isUpdateAvailable = ref(false)
export const latestLauncherRelease = ref<LauncherRelease | null>(null)
export const latestLauncherReleases = ref<LauncherRelease | null>(null)
const currentOS = ref('')
const systems = ['macos', 'windows', 'linux'] as const
const osExtensions = {
"linux": ['.deb'],
"macos": ['.dmg', '.pkg', '.app'],
"windows": ['.exe', '.msi']
linux: ['.deb', '.rpm', '.AppImage'],
macos: ['.dmg', '.pkg', '.app'],
windows: ['.exe', '.msi'],
}
const isDeveloper = await isDev()
@@ -47,15 +47,17 @@ const blacklistBeginPrefixes = [
export async function fetchRemote(): Promise<void> {
currentOS.value = (await getOS()).toLowerCase()
try {
if (!currentOS.value) {
throw new Error(String('Current OS is undefined'))
}
const response = await fetch(LAUNCHER_LATEST_RELEASE_API)
if (!response.ok) {
throw new Error(String(response.status))
}
const remoteData = (await response.json()) as LauncherRelease
latestLauncherRelease.value = remoteData
latestLauncherReleases.value = remoteData
if (systems.includes(currentOS.value as (typeof systems)[number])) {
const rawLocalVersion = await getVersion()
@@ -89,14 +91,16 @@ export async function fetchRemote(): Promise<void> {
}
} catch (error) {
console.error('Failed to fetch remote releases:', error)
latestLauncherRelease.value = null
latestLauncherReleases.value = null
isUpdateAvailable.value = false
isUpdateInstalling.value = false
}
}
export async function downloadLatestRelease(): Promise<boolean> {
if (!latestLauncherRelease.value) {
export async function downloadLatestRelease(
selectedInstaller?: LauncherReleaseAsset | null,
): Promise<boolean> {
if (!latestLauncherReleases.value) {
return false
}
@@ -104,7 +108,7 @@ export async function downloadLatestRelease(): Promise<boolean> {
currentOS.value = (await getOS()).toLowerCase()
}
const installer = getInstaller(resolveOperationalSystemExtension(), latestLauncherRelease.value.assets)
const installer = selectedInstaller ?? null
if (isDeveloper) {
console.debug(installer)
}
@@ -119,43 +123,51 @@ export async function downloadLatestRelease(): Promise<boolean> {
installer.browser_download_url,
installer.name,
currentOS.value,
true,
)
} finally {
isUpdateInstalling.value = false
}
}
function getInstaller(
osExtensions: string[],
builds: LauncherReleaseAsset[],
): LauncherReleaseAsset | null {
for (const build of builds) {
if (blacklistBeginPrefixes.some((prefix) => build.name.startsWith(prefix))) {
continue
}
if (osExtensions.some((extension) => build.name.endsWith(extension))) {
if (isDeveloper) {
console.debug(build.name, build.browser_download_url)
}
return build
}
export function getAvailableInstallers(): LauncherReleaseAsset[] {
if (!latestLauncherReleases.value) {
return []
}
return null
return getInstallers(resolveOperationalSystemExtension(), latestLauncherReleases.value.assets)
}
function getInstallers(os: string[], builds: LauncherReleaseAsset[]): LauncherReleaseAsset[] {
return builds.filter((build) => {
if (blacklistBeginPrefixes.some((prefix) => build.name.startsWith(prefix))) {
return false
}
const matchesExtension = os.some((extension) => build.name.endsWith(extension))
if (matchesExtension && isDeveloper) {
console.debug(build.name, build.browser_download_url)
}
return matchesExtension
})
}
function resolveOperationalSystemExtension(): string[] {
if (currentOS.value === 'macos') {
return osExtensions["macos"]
try {
switch (currentOS.value) {
case 'macos':
return osExtensions.macos
case 'windows':
return osExtensions.windows
case 'linux':
return osExtensions.linux
default:
throw new Error(String("Operational System can't be resolved"))
}
} catch (error) {
console.error("Operational System can't be resolved")
return []
}
if (currentOS.value === 'linux') {
return osExtensions["linux"]
}
return osExtensions["windows"]
}
function normalizeVersion(version: string): string {
+2 -2
View File
@@ -33,8 +33,8 @@ export async function getOS() {
// This code is modified by AstralRinth
export async function initUpdateLauncher(downloadUrl, filename, osType, autoUpdateSupported) {
console.log('Downloading build', downloadUrl, filename, osType, autoUpdateSupported)
return await invoke('plugin:utils|init_update_launcher', { downloadUrl, filename, osType, autoUpdateSupported })
console.log('Downloading build', downloadUrl, filename, osType)
return await invoke('plugin:utils|init_update_launcher', { downloadUrl, filename, osType })
}
export async function isNetworkMetered() {
+10 -1
View File
@@ -165,11 +165,17 @@
"message": "You are using an older version. We recommend updating now for the latest fixes and improvements."
},
"astralrinth.app.launcher-update-modal.update.download-action": {
"message": "Download update and close"
"message": "Download update"
},
"astralrinth.app.launcher-update-modal.update.header": {
"message": "AstralRinth launcher update"
},
"astralrinth.app.launcher-update-modal.update.installer-description": {
"message": "Choose the installer package you want to continue with."
},
"astralrinth.app.launcher-update-modal.update.installer-title": {
"message": "Installer type"
},
"astralrinth.app.launcher-update-modal.update.installed-version": {
"message": "💾 Installed & Running version:"
},
@@ -197,6 +203,9 @@
"astralrinth.app.launcher-update-modal.update.repository-link": {
"message": "Open the project repository"
},
"astralrinth.app.launcher-update-modal.update.select-installer": {
"message": "Select an installer"
},
"astralrinth.app.launcher-update-modal.update.title": {
"message": "A new version of the AstralRinth launcher is available."
},
+10 -1
View File
@@ -156,11 +156,17 @@
"message": "Вы используете более старую версию. Рекомендуем обновиться сейчас, чтобы получить последние исправления и улучшения."
},
"astralrinth.app.launcher-update-modal.update.download-action": {
"message": "Скачать обновление и закрыть"
"message": "Скачать обновление"
},
"astralrinth.app.launcher-update-modal.update.header": {
"message": "Обновление лаунчера AstralRinth"
},
"astralrinth.app.launcher-update-modal.update.installer-description": {
"message": "Выберите пакет установщика, с которым хотите продолжить."
},
"astralrinth.app.launcher-update-modal.update.installer-title": {
"message": "Тип установщика"
},
"astralrinth.app.launcher-update-modal.update.installed-version": {
"message": "💾 Установленная и запущенная версия:"
},
@@ -188,6 +194,9 @@
"astralrinth.app.launcher-update-modal.update.repository-link": {
"message": "Открыть репозиторий проекта"
},
"astralrinth.app.launcher-update-modal.update.select-installer": {
"message": "Выберите установщик"
},
"astralrinth.app.launcher-update-modal.update.title": {
"message": "Доступна новая версия лаунчера AstralRinth."
},
-2
View File
@@ -35,13 +35,11 @@ pub async fn init_update_launcher(
download_url: &str,
filename: &str,
os_type: &str,
auto_update_supported: bool,
) -> Result<()> {
let _ = utils::init_update_launcher(
download_url,
filename,
os_type,
auto_update_supported,
)
.await;
Ok(())