Update completion toasts (#3978)

This commit is contained in:
Josiah Glosson
2025-07-12 15:05:17 -05:00
committed by GitHub
parent 6fa0ee487d
commit 1e934312a4
11 changed files with 85 additions and 12 deletions

View File

@@ -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": {

View File

@@ -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)

View File

@@ -93,7 +93,6 @@ const emit = defineEmits<{
}>()
const { formatMessage } = useVIntl()
const messages = defineMessages({
header: {
id: 'app.update.modal-header',

View File

@@ -62,7 +62,9 @@ export type AppSettings = {
developer_mode: boolean
feature_flags: Record<FeatureFlag, boolean>
skipped_update: string | null
pending_update_toast_for_version: string | null
}
// Get full settings object

View File

@@ -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!"
},

View File

@@ -262,8 +262,22 @@ fn main() {
if matches!(event, tauri::RunEvent::Exit) {
let update_data = app.state::<PendingUpdateData>().inner();
if let Some((update, data)) = &*update_data.0.lock().unwrap() {
fn set_changelog_toast(version: Option<String>) {
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)

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -0,0 +1,2 @@
ALTER TABLE settings
ADD COLUMN pending_update_toast_for_version TEXT NULL;

View File

@@ -38,7 +38,9 @@ pub struct Settings {
pub developer_mode: bool,
pub feature_flags: HashMap<FeatureFlag, bool>,
pub skipped_update: Option<String>,
pub pending_update_toast_for_version: Option<String>,
}
#[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?;

View File

@@ -9,7 +9,11 @@
@mouseenter="stopTimer(item)"
@mouseleave="setNotificationTimer(item)"
>
<div class="vue-notification-template vue-notification" :class="{ [item.type]: true }">
<div
class="vue-notification-template vue-notification"
:class="{ [item.type]: true }"
@click="item.clickAction"
>
<div class="notification-title" v-html="item.title"></div>
<div class="notification-content" v-html="item.text"></div>
</div>
@@ -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)