diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue index fcf98e6bd..398d8ae09 100644 --- a/apps/app-frontend/src/App.vue +++ b/apps/app-frontend/src/App.vue @@ -85,7 +85,7 @@ import { useCheckDisableMouseover } from '@/composables/macCssFix.js' import { config } from '@/config' import { check_reachable } from '@/helpers/auth.js' import { get_user, get_version } from '@/helpers/cache.js' -import { command_listener, notification_listener, warning_listener, log_listener } from '@/helpers/events.js' +import { command_listener, notification_listener, warning_listener } from '@/helpers/events.js' import { cancelLogin, get as getCreds, login, logout } from '@/helpers/mr_auth.ts' import { create_profile_and_install_from_file } from '@/helpers/pack' import { list } from '@/helpers/profile.js' @@ -109,7 +109,7 @@ import { AppNotificationManager } from './providers/app-notifications' import { AppPopupNotificationManager } from './providers/app-popup-notifications' // This code line modified by AstralRinth -import { getRemote, updateState } from '@/helpers/update.js' +import { fetchRemote, isUpdateAvailable } from '@/helpers/astralrinth/update' const themeStore = useTheming() const router = useRouter() @@ -119,23 +119,23 @@ const APP_SIDEBAR_WIDTH = 300 const INTERCOM_BUBBLE_DEFAULT_PADDING = 20 // This code line modified by AstralRinth const filteredNewsPhrases = [ - "LGBT", - "LGBTQ", - "LGBTQ+", - "LGBTQIA+", - "gay", - "lesbian", - "bisexual", - "pansexual", - "asexual", - "aromantic", - "transgender", - "nonbinary", - "intersex", - "homosexual", - "homosexuality", - "pride", -]; + 'LGBT', + 'LGBTQ', + 'LGBTQ+', + 'LGBTQIA+', + 'gay', + 'lesbian', + 'bisexual', + 'pansexual', + 'asexual', + 'aromantic', + 'transgender', + 'nonbinary', + 'intersex', + 'homosexual', + 'homosexuality', + 'pride', +] const credentials = ref() const sidebarToggled = ref(true) const unsubscribeSidebarToggle = themeStore.$subscribe(() => { @@ -172,6 +172,8 @@ const popupNotificationManager = new AppPopupNotificationManager() providePopupNotificationManager(popupNotificationManager) const { addPopupNotification } = popupNotificationManager +const settingsModal = ref(null) + const appVersion = getVersion() const tauriApiClient = new TauriModrinthClient({ userAgent: async () => `modrinth/theseus/${await appVersion} (support@modrinth.com)`, @@ -281,7 +283,22 @@ const authUnreachable = computed(() => { onMounted(async () => { await useCheckDisableMouseover() // This code line modified by AstralRinth - await getRemote(false) + await fetchRemote() + if (isUpdateAvailable.value) { + addPopupNotification({ + title: formatMessage(messages.launcherUpdateAvailableTitle), + text: formatMessage(messages.launcherUpdateAvailableText), + type: 'info', + autoCloseMs: 12000, + buttons: [ + { + label: formatMessage(messages.launcherUpdateAvailableAction), + action: () => settingsModal.value?.showUpdateModal?.(), + color: 'brand', + }, + ], + }) + } document.querySelector('body').addEventListener('click', handleClick) document.querySelector('body').addEventListener('auxclick', handleAuxClick) @@ -305,6 +322,18 @@ const messages = defineMessages({ defaultMessage: 'Minecraft authentication servers may be down right now. Check your internet connection and try again later.', }, + launcherUpdateAvailableTitle: { + id: 'astralrinth.app.launcher-update.available.title', + defaultMessage: 'Launcher update available', + }, + launcherUpdateAvailableText: { + id: 'astralrinth.app.launcher-update.available.text', + defaultMessage: 'New version of AstralRinth is available for download.', + }, + launcherUpdateAvailableAction: { + id: 'astralrinth.app.launcher-update.available.action', + defaultMessage: 'View update', + }, }) // This code line modified by AstralRinth @@ -705,10 +734,7 @@ async function logOut() { } // This code line modified by AstralRinth -const hasPlus = computed( - () => - !!credentials.value?.user, -) +const hasPlus = computed(() => !!credentials.value?.user) async function fetchIntercomToken() { const creds = await getCreds() @@ -1121,18 +1147,20 @@ provideAppUpdateDownloadProgress(appUpdateDownload) // [AR Note] If delete this
- diff --git a/apps/app-frontend/src/helpers/astralrinth/update.ts b/apps/app-frontend/src/helpers/astralrinth/update.ts new file mode 100644 index 000000000..7fc2ddae8 --- /dev/null +++ b/apps/app-frontend/src/helpers/astralrinth/update.ts @@ -0,0 +1,148 @@ +import { getVersion } from '@tauri-apps/api/app' +import { ref } from 'vue' + +import { getOS, initUpdateLauncher, isDev } from '@/helpers/utils.js' + +export type LauncherReleaseAsset = { + name: string + browser_download_url: string +} + +export type LauncherRelease = { + tag_name: string + name: string + assets: LauncherReleaseAsset[] +} + +// import.meta.env uses `vite.config.ts` +// Environments can be configured in `packages/app-lib/` directory. +export const LAUNCHER_REPOSITORY_URL = `${import.meta.env.GIT_ASTRALIUM_URL}didirus/AstralRinth/` +export const LAUNCHER_RELEASES_URL = `${LAUNCHER_REPOSITORY_URL}releases` +const LAUNCHER_LATEST_RELEASE_API = `${import.meta.env.GIT_ASTRALIUM_API_URL}repos/didirus/AstralRinth/releases/latest` + +export const isUpdateInstalling = ref(false) +export const isUpdateAvailable = ref(false) +export const latestLauncherRelease = ref(null) + +const currentOS = ref('') + +const systems = ['macos', 'windows', 'linux'] as const +const osExtensions = { + "linux": ['.deb'], + "macos": ['.dmg', '.pkg', '.app'], + "windows": ['.exe', '.msi'] +} + +const isDeveloper = await isDev() + +const blacklistBeginPrefixes = [ + 'dev', + 'nightly', + 'dirty', + 'dirty-dev', + 'dirty-nightly', + 'dirty_dev', + 'dirty_nightly', +] + +export async function fetchRemote(): Promise { + currentOS.value = (await getOS()).toLowerCase() + + try { + 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 + + if (systems.includes(currentOS.value as (typeof systems)[number])) { + const localVersion = normalizeVersion(await getVersion()) + const remoteVersion = normalizeVersion(remoteData.tag_name) + isUpdateAvailable.value = remoteVersion !== localVersion + } else { + isUpdateAvailable.value = false + } + + if (isDeveloper) { + console.debug('Update available state is', isUpdateAvailable.value) + console.debug('Remote version is', remoteData.tag_name) + console.debug('Remote title is', remoteData.name) + console.debug('Local version is', await getVersion()) + console.debug('Operating System is', currentOS.value) + } + } catch (error) { + console.error('Failed to fetch remote releases:', error) + latestLauncherRelease.value = null + isUpdateAvailable.value = false + isUpdateInstalling.value = false + } +} + +export async function downloadLatestRelease(): Promise { + if (!latestLauncherRelease.value) { + return false + } + + if (!currentOS.value) { + currentOS.value = (await getOS()).toLowerCase() + } + + const installer = getInstaller(resolveOperationalSystemExtension(), latestLauncherRelease.value.assets) + if (isDeveloper) { + console.debug(installer) + } + if (!installer) { + isUpdateInstalling.value = false + return false + } + + try { + isUpdateInstalling.value = true + return await initUpdateLauncher( + 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 + } + } + + return null +} + +function resolveOperationalSystemExtension(): string[] { + if (currentOS.value === 'macos') { + return osExtensions["macos"] + } + + if (currentOS.value === 'linux') { + return osExtensions["linux"] + } + + return osExtensions["windows"] +} + +function normalizeVersion(version: string): string { + return version.trim().replace(/^v/i, '') +} diff --git a/apps/app-frontend/src/helpers/update.js b/apps/app-frontend/src/helpers/update.js deleted file mode 100644 index 2aff285ba..000000000 --- a/apps/app-frontend/src/helpers/update.js +++ /dev/null @@ -1,98 +0,0 @@ -import { getVersion } from '@tauri-apps/api/app' -import { ref } from 'vue' - -import { getOS, initUpdateLauncher } from '@/helpers/utils.js' - -export const allowState = ref(false) -export const installState = ref(false) -export const updateState = ref(false) - -const currentOS = ref('') -const api = `https://git.astralium.su/api/v1/repos/didirus/AstralRinth/releases/latest` - -const systems = ['macos', 'windows', 'linux'] -const macosExtensions = ['.dmg', '.pkg', '.app'] -const windowsExtensions = ['.exe', '.msi'] - -const blacklistBeginPrefixes = [ - `dev`, - `nightly`, - `dirty`, - `dirty-dev`, - `dirty-nightly`, - `dirty_dev`, - `dirty_nightly`, -] // This is blacklisted builds for download. For example, file.startsWith('dev') is not allowed. - -export async function getRemote(isDownloadState) { - var releaseTag = null; - var releaseTitle = null; - var result = false; - currentOS.value = await getOS(); - try { - const response = await fetch(api); - if (!response.ok) { - throw new Error(response.status); - } - const remoteData = await response.json(); - releaseTag = document.getElementById('releaseTag'); - releaseTitle = document.getElementById('releaseTitle'); - if (releaseTag && releaseTitle) { - releaseTag.textContent = remoteData.tag_name; - releaseTitle.textContent = remoteData.name; - } - if (systems.includes(currentOS.value.toLowerCase())) { - const localVersion = await getVersion(); - const isUpdateAvailable = !remoteData.tag_name.includes(localVersion); - updateState.value = isUpdateAvailable; - allowState.value = isUpdateAvailable; - } else { - updateState.value = false; - allowState.value = false; - } - if (isDownloadState) { - try { - installState.value = true; - const builds = remoteData.assets; - const fileName = getInstaller(getExtension(), builds); - result = fileName ? await initUpdateLauncher(fileName[1], fileName[0], currentOS.value, true) : false; - installState.value = false; - } catch (err) { - installState.value = false; - } - } - console.log('Update available state is', updateState.value); - console.log('Remote version is', remoteData.tag_name); - console.log('Remote title is', remoteData.name); - console.log('Local version is', await getVersion()); - console.log('Operating System is', currentOS.value); - return result; - } catch (error) { - console.error("Failed to fetch remote releases:", error); - if (!releaseTag) { - updateState.value = false; - allowState.value = false; - installState.value = false; - } - } -} - -function getInstaller(osExtension, builds) { - console.log(osExtension, builds) - for (const build of builds) { - if (blacklistBeginPrefixes.some(prefix => build.name.startsWith(prefix))) { - continue; - } - if (osExtension.some(ext => build.name.endsWith(ext))) { - console.log(build.name, build.browser_download_url); - return [build.name, build.browser_download_url]; - } - } - return null; -} - -function getExtension() { - return systems.find(osName => osName === currentOS.value.toLowerCase())?.endsWith('macos') - ? macosExtensions - : windowsExtensions; -} diff --git a/apps/app-frontend/src/locales/en-US/index.json b/apps/app-frontend/src/locales/en-US/index.json index b58c39dc8..5efe45082 100644 --- a/apps/app-frontend/src/locales/en-US/index.json +++ b/apps/app-frontend/src/locales/en-US/index.json @@ -119,6 +119,87 @@ "app.auth-servers.unreachable.header": { "message": "Cannot reach authentication servers" }, + "astralrinth.app.launcher-update.available.action": { + "message": "View update" + }, + "astralrinth.app.launcher-update.available.text": { + "message": "New version of AstralRinth is available for download." + }, + "astralrinth.app.launcher-update.available.title": { + "message": "Launcher update available" + }, + "astralrinth.app.settings.update-installing": { + "message": "Installing update..." + }, + "astralrinth.app.settings.view-update-info": { + "message": "View update info" + }, + "astralrinth.app.launcher-update-modal.error.close-action": { + "message": "Close" + }, + "astralrinth.app.launcher-update-modal.error.description": { + "message": "AstralRinth could not download the update file from the server." + }, + "astralrinth.app.launcher-update-modal.error.header": { + "message": "Could not download the update" + }, + "astralrinth.app.launcher-update-modal.error.help-link": { + "message": "AstralRinth repository releases" + }, + "astralrinth.app.launcher-update-modal.error.help-suffix": { + "message": "if a newer release is available there." + }, + "astralrinth.app.launcher-update-modal.error.help-text": { + "message": "You can try downloading it manually from" + }, + "astralrinth.app.launcher-update-modal.error.local-version": { + "message": "Local AstralRinth:" + }, + "astralrinth.app.launcher-update-modal.error.title": { + "message": "Download failed" + }, + "astralrinth.app.launcher-update-modal.update.cancel-action": { + "message": "Cancel" + }, + "astralrinth.app.launcher-update-modal.update.description": { + "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" + }, + "astralrinth.app.launcher-update-modal.update.header": { + "message": "AstralRinth launcher update" + }, + "astralrinth.app.launcher-update-modal.update.installed-version": { + "message": "💾 Installed & Running version:" + }, + "astralrinth.app.launcher-update-modal.update.latest-release-tag": { + "message": "☁️ Latest release tag:" + }, + "astralrinth.app.launcher-update-modal.update.latest-release-title": { + "message": "☁️ Latest release title:" + }, + "astralrinth.app.launcher-update-modal.update.notice-lead": { + "message": "Save your work, close all running launcher instances, and back up your launcher data before installing the update." + }, + "astralrinth.app.launcher-update-modal.update.notice-macos": { + "message": "On macOS, important data may be stored in" + }, + "astralrinth.app.launcher-update-modal.update.notice-outro": { + "message": "To avoid data loss, keep a backup copy in a safe place before continuing." + }, + "astralrinth.app.launcher-update-modal.update.notice-windows": { + "message": "On Windows, important data may be stored in" + }, + "astralrinth.app.launcher-update-modal.update.notice-title": { + "message": "⚠️ Before you continue" + }, + "astralrinth.app.launcher-update-modal.update.repository-link": { + "message": "Open the project repository" + }, + "astralrinth.app.launcher-update-modal.update.title": { + "message": "A new version of the AstralRinth launcher is available." + }, "astralrinth.app.minecraft-account.add-elyby-account": { "message": "Add Ely.by account" }, diff --git a/apps/app-frontend/src/locales/ru-RU/index.json b/apps/app-frontend/src/locales/ru-RU/index.json index 369c971f0..3a3ebf834 100644 --- a/apps/app-frontend/src/locales/ru-RU/index.json +++ b/apps/app-frontend/src/locales/ru-RU/index.json @@ -110,6 +110,87 @@ "app.auth-servers.unreachable.header": { "message": "Нет связи с серверами аутентификации" }, + "astralrinth.app.launcher-update.available.action": { + "message": "Посмотреть обновление" + }, + "astralrinth.app.launcher-update.available.text": { + "message": "Новая версия AstralRinth уже доступна для загрузки." + }, + "astralrinth.app.launcher-update.available.title": { + "message": "Доступно обновление лаунчера" + }, + "astralrinth.app.settings.update-installing": { + "message": "Установка обновления..." + }, + "astralrinth.app.settings.view-update-info": { + "message": "Посмотреть информацию об обновлении" + }, + "astralrinth.app.launcher-update-modal.error.close-action": { + "message": "Закрыть" + }, + "astralrinth.app.launcher-update-modal.error.description": { + "message": "AstralRinth не удалось скачать файл обновления с сервера." + }, + "astralrinth.app.launcher-update-modal.error.header": { + "message": "Не удалось скачать обновление" + }, + "astralrinth.app.launcher-update-modal.error.help-link": { + "message": "релизы репозитория AstralRinth" + }, + "astralrinth.app.launcher-update-modal.error.help-suffix": { + "message": "если там доступен более новый релиз." + }, + "astralrinth.app.launcher-update-modal.error.help-text": { + "message": "Вы можете скачать его вручную из" + }, + "astralrinth.app.launcher-update-modal.error.local-version": { + "message": "Локальная версия AstralRinth:" + }, + "astralrinth.app.launcher-update-modal.error.title": { + "message": "Не удалось скачать файл" + }, + "astralrinth.app.launcher-update-modal.update.cancel-action": { + "message": "Отмена" + }, + "astralrinth.app.launcher-update-modal.update.description": { + "message": "Вы используете более старую версию. Рекомендуем обновиться сейчас, чтобы получить последние исправления и улучшения." + }, + "astralrinth.app.launcher-update-modal.update.download-action": { + "message": "Скачать обновление и закрыть" + }, + "astralrinth.app.launcher-update-modal.update.header": { + "message": "Обновление лаунчера AstralRinth" + }, + "astralrinth.app.launcher-update-modal.update.installed-version": { + "message": "💾 Установленная и запущенная версия:" + }, + "astralrinth.app.launcher-update-modal.update.latest-release-tag": { + "message": "☁️ Тег последнего релиза:" + }, + "astralrinth.app.launcher-update-modal.update.latest-release-title": { + "message": "☁️ Название последнего релиза:" + }, + "astralrinth.app.launcher-update-modal.update.notice-lead": { + "message": "Перед установкой обновления сохраните работу, закройте все запущенные экземпляры лаунчера и сделайте резервную копию его данных." + }, + "astralrinth.app.launcher-update-modal.update.notice-macos": { + "message": "На macOS важные данные могут находиться в" + }, + "astralrinth.app.launcher-update-modal.update.notice-outro": { + "message": "Чтобы избежать потери данных, сохраните резервную копию в безопасном месте перед продолжением." + }, + "astralrinth.app.launcher-update-modal.update.notice-windows": { + "message": "На Windows важные данные могут находиться в" + }, + "astralrinth.app.launcher-update-modal.update.notice-title": { + "message": "⚠️ Перед продолжением" + }, + "astralrinth.app.launcher-update-modal.update.repository-link": { + "message": "Открыть репозиторий проекта" + }, + "astralrinth.app.launcher-update-modal.update.title": { + "message": "Доступна новая версия лаунчера AstralRinth." + }, "astralrinth.app.minecraft-account.add-elyby-account": { "message": "Добавить Ely.by аккаунт" }, diff --git a/apps/app-frontend/vite.config.ts b/apps/app-frontend/vite.config.ts index 664046257..1faba49ce 100644 --- a/apps/app-frontend/vite.config.ts +++ b/apps/app-frontend/vite.config.ts @@ -101,7 +101,7 @@ export default defineConfig({ }, // to make use of `TAURI_ENV_DEBUG` and other env variables // https://v2.tauri.app/reference/environment-variables/#tauri-cli-hook-commands - envPrefix: ['VITE_', 'TAURI_', 'MODRINTH_'], + envPrefix: ['VITE_', 'TAURI_', 'MODRINTH_', 'GIT_ASTRALIUM_'], build: { rolldownOptions: { onwarn(warning, defaultHandler) { diff --git a/packages/app-lib/.env.prod b/packages/app-lib/.env.prod index 94f2c2d34..ae83e3592 100644 --- a/packages/app-lib/.env.prod +++ b/packages/app-lib/.env.prod @@ -5,8 +5,10 @@ MODRINTH_API_URL=https://api.modrinth.com/v2/ MODRINTH_API_URL_V3=https://api.modrinth.com/v3/ MODRINTH_SOCKET_URL=wss://api.modrinth.com/ MODRINTH_LAUNCHER_META_URL=https://launcher-meta.modrinth.com/ +GIT_ASTRALIUM_URL=https://git.astralium.su/ +GIT_ASTRALIUM_API_URL=https://git.astralium.su/api/v1/ # SQLite database file used by sqlx for type checking. Uncomment this to a valid path # in your system and run `cargo sqlx database setup` to generate an empty database that # can be used for developing the app DB schema -#DATABASE_URL=sqlite:///tmp/Modrinth/code/packages/app-lib/.sqlx/generated/state.db +#DATABASE_URL=sqlite:///tmp/AstralRinth/code/packages/app-lib/.sqlx/generated/state.db diff --git a/packages/assets/styles/astralrinth/neon-text.scss b/packages/assets/styles/astralrinth/neon-text.scss index fe6f32205..f8f852a1e 100644 --- a/packages/assets/styles/astralrinth/neon-text.scss +++ b/packages/assets/styles/astralrinth/neon-text.scss @@ -24,3 +24,10 @@ 0 0 2px rgba(16, 250, 229, 0.4), 0 0 4px rgba(16, 250, 229, 0.25); } + +code { + background: linear-gradient(90deg, #005eff, #00cfff); + background-clip: text; + -webkit-background-clip: text; + color: transparent; +}