diff --git a/apps/app-frontend/package.json b/apps/app-frontend/package.json index 77a697c21..d760df6d3 100644 --- a/apps/app-frontend/package.json +++ b/apps/app-frontend/package.json @@ -9,7 +9,7 @@ "tsc:check": "vue-tsc --noEmit", "lint": "eslint . && prettier --check .", "fix": "eslint . --fix && prettier --write .", - "intl:extract": "formatjs extract \"{,src/components,src/composables,src/helpers,src/pages,src/store}/**/*.{vue,ts,tsx,js,jsx,mts,cts,mjs,cjs}\" --ignore \"**/*.d.ts\" --ignore node_modules --out-file src/locales/en-US/index.json --format crowdin --preserve-whitespace", + "intl:extract": "formatjs extract \"src/**/*.{vue,ts,tsx,js,jsx,mts,cts,mjs,cjs}\" --ignore \"**/*.d.ts\" --ignore node_modules --out-file src/locales/en-US/index.json --format crowdin --preserve-whitespace", "test": "vue-tsc --noEmit" }, "dependencies": { diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue index 7a90c9f49..78262624c 100644 --- a/apps/app-frontend/src/App.vue +++ b/apps/app-frontend/src/App.vue @@ -71,6 +71,7 @@ import QuickInstanceSwitcher from '@/components/ui/QuickInstanceSwitcher.vue' import UpdateModal from '@/components/ui/UpdateModal.vue' import { get_available_capes, get_available_skins } from './helpers/skins' import { generateSkinPreviews } from './helpers/rendering/batch-skin-renderer' +import { defineMessages, useVIntl } from '@vintl/vintl' const themeStore = useTheming() @@ -109,6 +110,18 @@ onUnmounted(() => { document.querySelector('body').removeEventListener('auxclick', handleAuxClick) }) +const { formatMessage } = useVIntl() +const messages = defineMessages({ + updateInstalledToastTitle: { + id: 'app.update.complete-toast.title', + defaultMessage: 'Version {version} was successfully installed!', + }, + updateInstalledToastText: { + id: 'app.update.complete-toast.text', + defaultMessage: 'Click here to view the changelog.', + }, +}) + async function setupApp() { stateInitialized.value = true const { @@ -122,6 +135,7 @@ async function setupApp() { toggle_sidebar, developer_mode, feature_flags, + pending_update_toast_for_version, } = await getSettings() if (default_page === 'Library') { @@ -218,6 +232,22 @@ async function setupApp() { } catch (error) { console.warn('Failed to generate skin previews in app setup.', error) } + + if (pending_update_toast_for_version !== null) { + const settings = await getSettings() + settings.pending_update_toast_for_version = null + await setSettings(settings) + + const version = await getVersion() + if (pending_update_toast_for_version === version) { + notifications.addNotification({ + type: 'success', + title: formatMessage(messages.updateInstalledToastTitle, { version }), + text: formatMessage(messages.updateInstalledToastText), + clickAction: () => openUrl('https://modrinth.com/news/changelog?filter=app'), + }) + } + } } const stateFailed = ref(false) diff --git a/apps/app-frontend/src/components/ui/UpdateModal.vue b/apps/app-frontend/src/components/ui/UpdateModal.vue index 1f7b899d9..bfd977191 100644 --- a/apps/app-frontend/src/components/ui/UpdateModal.vue +++ b/apps/app-frontend/src/components/ui/UpdateModal.vue @@ -93,7 +93,6 @@ const emit = defineEmits<{ }>() const { formatMessage } = useVIntl() - const messages = defineMessages({ header: { id: 'app.update.modal-header', diff --git a/apps/app-frontend/src/helpers/settings.ts b/apps/app-frontend/src/helpers/settings.ts index 6b00e9bb8..fcc0a8144 100644 --- a/apps/app-frontend/src/helpers/settings.ts +++ b/apps/app-frontend/src/helpers/settings.ts @@ -62,7 +62,9 @@ export type AppSettings = { developer_mode: boolean feature_flags: Record + skipped_update: string | null + pending_update_toast_for_version: string | null } // Get full settings object diff --git a/apps/app-frontend/src/locales/en-US/index.json b/apps/app-frontend/src/locales/en-US/index.json index 558480cfa..0952c26e7 100644 --- a/apps/app-frontend/src/locales/en-US/index.json +++ b/apps/app-frontend/src/locales/en-US/index.json @@ -23,6 +23,12 @@ "app.update.changelog": { "message": "Changelog" }, + "app.update.complete-toast.text": { + "message": "Click here to view the changelog." + }, + "app.update.complete-toast.title": { + "message": "Version {version} was successfully installed!" + }, "app.update.copied-error": { "message": "Copied to clipboard!" }, diff --git a/apps/app/src/main.rs b/apps/app/src/main.rs index 5d901fe9d..f3a177d2d 100644 --- a/apps/app/src/main.rs +++ b/apps/app/src/main.rs @@ -262,8 +262,22 @@ fn main() { if matches!(event, tauri::RunEvent::Exit) { let update_data = app.state::().inner(); if let Some((update, data)) = &*update_data.0.lock().unwrap() { + fn set_changelog_toast(version: Option) { + let toast_result: theseus::Result<()> = tauri::async_runtime::block_on(async move { + let mut settings = settings::get().await?; + settings.pending_update_toast_for_version = version; + settings::set(settings).await?; + Ok(()) + }); + if let Err(e) = toast_result { + tracing::warn!("Failed to set pending_update_toast: {e}") + } + } + + set_changelog_toast(Some(update.version.clone())); if let Err(e) = update.install(data) { tracing::error!("Error while updating: {e}"); + set_changelog_toast(None); DialogBuilder::message() .set_level(MessageLevel::Error) diff --git a/packages/app-lib/.sqlx/query-cb20c8117eeec23ea1ebc2ef293caf425d722bd103cd6164c2b9d6abe5539aef.json b/packages/app-lib/.sqlx/query-30fecc13c6ea4da1e99e35d307aa8e7c4e7f15ea99527e7974619ef8ed946abe.json similarity index 92% rename from packages/app-lib/.sqlx/query-cb20c8117eeec23ea1ebc2ef293caf425d722bd103cd6164c2b9d6abe5539aef.json rename to packages/app-lib/.sqlx/query-30fecc13c6ea4da1e99e35d307aa8e7c4e7f15ea99527e7974619ef8ed946abe.json index d87a4032c..ce2fdf1c4 100644 --- a/packages/app-lib/.sqlx/query-cb20c8117eeec23ea1ebc2ef293caf425d722bd103cd6164c2b9d6abe5539aef.json +++ b/packages/app-lib/.sqlx/query-30fecc13c6ea4da1e99e35d307aa8e7c4e7f15ea99527e7974619ef8ed946abe.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "\n SELECT\n max_concurrent_writes, max_concurrent_downloads,\n theme, default_page, collapsed_navigation, hide_nametag_skins_page, advanced_rendering, native_decorations,\n discord_rpc, developer_mode, telemetry, personalized_ads,\n onboarded,\n json(extra_launch_args) extra_launch_args, json(custom_env_vars) custom_env_vars,\n mc_memory_max, mc_force_fullscreen, mc_game_resolution_x, mc_game_resolution_y, hide_on_process_start,\n hook_pre_launch, hook_wrapper, hook_post_exit,\n custom_dir, prev_custom_dir, migrated, json(feature_flags) feature_flags, toggle_sidebar,\n skipped_update\n FROM settings\n ", + "query": "\n SELECT\n max_concurrent_writes, max_concurrent_downloads,\n theme, default_page, collapsed_navigation, hide_nametag_skins_page, advanced_rendering, native_decorations,\n discord_rpc, developer_mode, telemetry, personalized_ads,\n onboarded,\n json(extra_launch_args) extra_launch_args, json(custom_env_vars) custom_env_vars,\n mc_memory_max, mc_force_fullscreen, mc_game_resolution_x, mc_game_resolution_y, hide_on_process_start,\n hook_pre_launch, hook_wrapper, hook_post_exit,\n custom_dir, prev_custom_dir, migrated, json(feature_flags) feature_flags, toggle_sidebar,\n skipped_update, pending_update_toast_for_version\n FROM settings\n ", "describe": { "columns": [ { @@ -147,6 +147,11 @@ "name": "skipped_update", "ordinal": 28, "type_info": "Text" + }, + { + "name": "pending_update_toast_for_version", + "ordinal": 29, + "type_info": "Text" } ], "parameters": { @@ -181,8 +186,9 @@ false, null, false, + true, true ] }, - "hash": "cb20c8117eeec23ea1ebc2ef293caf425d722bd103cd6164c2b9d6abe5539aef" + "hash": "30fecc13c6ea4da1e99e35d307aa8e7c4e7f15ea99527e7974619ef8ed946abe" } diff --git a/packages/app-lib/.sqlx/query-6dd3b14f3736735ee9a734062c990d152b0ffb7abf87a729931dc6c54754be6d.json b/packages/app-lib/.sqlx/query-cd7c9f394e4f0ec8fd0043352f7382ac2570aaa096cd2ebb4de990f4d42cc5c9.json similarity index 82% rename from packages/app-lib/.sqlx/query-6dd3b14f3736735ee9a734062c990d152b0ffb7abf87a729931dc6c54754be6d.json rename to packages/app-lib/.sqlx/query-cd7c9f394e4f0ec8fd0043352f7382ac2570aaa096cd2ebb4de990f4d42cc5c9.json index 63320e4c4..1565c4acb 100644 --- a/packages/app-lib/.sqlx/query-6dd3b14f3736735ee9a734062c990d152b0ffb7abf87a729931dc6c54754be6d.json +++ b/packages/app-lib/.sqlx/query-cd7c9f394e4f0ec8fd0043352f7382ac2570aaa096cd2ebb4de990f4d42cc5c9.json @@ -1,12 +1,12 @@ { "db_name": "SQLite", - "query": "\n UPDATE settings\n SET\n max_concurrent_writes = $1,\n max_concurrent_downloads = $2,\n\n theme = $3,\n default_page = $4,\n collapsed_navigation = $5,\n advanced_rendering = $6,\n native_decorations = $7,\n\n discord_rpc = $8,\n developer_mode = $9,\n telemetry = $10,\n personalized_ads = $11,\n\n onboarded = $12,\n\n extra_launch_args = jsonb($13),\n custom_env_vars = jsonb($14),\n mc_memory_max = $15,\n mc_force_fullscreen = $16,\n mc_game_resolution_x = $17,\n mc_game_resolution_y = $18,\n hide_on_process_start = $19,\n\n hook_pre_launch = $20,\n hook_wrapper = $21,\n hook_post_exit = $22,\n\n custom_dir = $23,\n prev_custom_dir = $24,\n migrated = $25,\n\n toggle_sidebar = $26,\n feature_flags = $27,\n hide_nametag_skins_page = $28,\n skipped_update = $29\n ", + "query": "\n UPDATE settings\n SET\n max_concurrent_writes = $1,\n max_concurrent_downloads = $2,\n\n theme = $3,\n default_page = $4,\n collapsed_navigation = $5,\n advanced_rendering = $6,\n native_decorations = $7,\n\n discord_rpc = $8,\n developer_mode = $9,\n telemetry = $10,\n personalized_ads = $11,\n\n onboarded = $12,\n\n extra_launch_args = jsonb($13),\n custom_env_vars = jsonb($14),\n mc_memory_max = $15,\n mc_force_fullscreen = $16,\n mc_game_resolution_x = $17,\n mc_game_resolution_y = $18,\n hide_on_process_start = $19,\n\n hook_pre_launch = $20,\n hook_wrapper = $21,\n hook_post_exit = $22,\n\n custom_dir = $23,\n prev_custom_dir = $24,\n migrated = $25,\n\n toggle_sidebar = $26,\n feature_flags = $27,\n hide_nametag_skins_page = $28,\n\n skipped_update = $29,\n pending_update_toast_for_version = $30\n ", "describe": { "columns": [], "parameters": { - "Right": 29 + "Right": 30 }, "nullable": [] }, - "hash": "6dd3b14f3736735ee9a734062c990d152b0ffb7abf87a729931dc6c54754be6d" + "hash": "cd7c9f394e4f0ec8fd0043352f7382ac2570aaa096cd2ebb4de990f4d42cc5c9" } diff --git a/packages/app-lib/migrations/20250711195037_add-pending-update-toast.sql b/packages/app-lib/migrations/20250711195037_add-pending-update-toast.sql new file mode 100644 index 000000000..c9dfe65f2 --- /dev/null +++ b/packages/app-lib/migrations/20250711195037_add-pending-update-toast.sql @@ -0,0 +1,2 @@ +ALTER TABLE settings +ADD COLUMN pending_update_toast_for_version TEXT NULL; diff --git a/packages/app-lib/src/state/settings.rs b/packages/app-lib/src/state/settings.rs index 0248f907d..5aa9cc327 100644 --- a/packages/app-lib/src/state/settings.rs +++ b/packages/app-lib/src/state/settings.rs @@ -38,7 +38,9 @@ pub struct Settings { pub developer_mode: bool, pub feature_flags: HashMap, + pub skipped_update: Option, + pub pending_update_toast_for_version: Option, } #[derive(Serialize, Deserialize, Debug, Clone, Copy, Eq, Hash, PartialEq)] @@ -65,7 +67,7 @@ impl Settings { mc_memory_max, mc_force_fullscreen, mc_game_resolution_x, mc_game_resolution_y, hide_on_process_start, hook_pre_launch, hook_wrapper, hook_post_exit, custom_dir, prev_custom_dir, migrated, json(feature_flags) feature_flags, toggle_sidebar, - skipped_update + skipped_update, pending_update_toast_for_version FROM settings " ) @@ -120,6 +122,8 @@ impl Settings { .and_then(|x| serde_json::from_str(x).ok()) .unwrap_or_default(), skipped_update: res.skipped_update, + pending_update_toast_for_version: res + .pending_update_toast_for_version, }) } @@ -174,7 +178,9 @@ impl Settings { toggle_sidebar = $26, feature_flags = $27, hide_nametag_skins_page = $28, - skipped_update = $29 + + skipped_update = $29, + pending_update_toast_for_version = $30 ", max_concurrent_writes, max_concurrent_downloads, @@ -204,7 +210,8 @@ impl Settings { self.toggle_sidebar, feature_flags, self.hide_nametag_skins_page, - self.skipped_update + self.skipped_update, + self.pending_update_toast_for_version, ) .execute(exec) .await?; diff --git a/packages/ui/src/components/base/Notifications.vue b/packages/ui/src/components/base/Notifications.vue index dd174a0f4..04f208ed2 100644 --- a/packages/ui/src/components/base/Notifications.vue +++ b/packages/ui/src/components/base/Notifications.vue @@ -9,7 +9,11 @@ @mouseenter="stopTimer(item)" @mouseleave="setNotificationTimer(item)" > -
+
@@ -31,11 +35,14 @@ const notifications = ref([]) defineExpose({ addNotification: (notification) => { + notification.clickAction = notification.clickAction ?? (() => {}) + const existingNotif = notifications.value.find( (x) => x.text === notification.text && x.title === notification.title && - x.type === notification.type, + x.type === notification.type && + x.clickAction === notification.clickAction, ) if (existingNotif) { setNotificationTimer(existingNotif)