diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue
index 564be92c7d3..c2213e48829 100644
--- a/apps/app-frontend/src/App.vue
+++ b/apps/app-frontend/src/App.vue
@@ -99,11 +99,6 @@ import QuickInstanceSwitcher from '@/components/ui/QuickInstanceSwitcher.vue'
import SharedInstanceInviteHandler from '@/components/ui/shared-instances/shared-instance-invite-handler/index.vue'
import SplashScreen from '@/components/ui/SplashScreen.vue'
import SurveyPopup from '@/components/ui/SurveyPopup.vue'
-import SyncInstancesUpdateModal from '@/components/ui/sync-instances-update-modal/index.vue'
-import {
- markSyncInstancesUpdateNotificationShown,
- shouldShowSyncInstancesUpdateNotification,
-} from '@/components/ui/sync-instances-update-modal/show-notification'
import WindowControls from '@/components/ui/WindowControls.vue'
import { useCheckDisableMouseover } from '@/composables/macCssFix.js'
import { useAppEvent } from '@/composables/use-app-event'
@@ -113,6 +108,7 @@ import { useInstanceMetadataRefresh } from '@/composables/use-instance-metadata-
import { useQuickInstanceLimit } from '@/composables/use-quick-instance-limit.ts'
import { isDarkTheme, useTheme } from '@/composables/use-theme.ts'
import { config } from '@/config'
+import NewUpdateModal from '@/features/updates/new-update-modal/index.vue'
import { getAccountAppearance, rememberAccountAppearance } from '@/helpers/account-appearance.ts'
import {
hide_ads_window,
@@ -579,23 +575,6 @@ const { formatMessage } = useVIntl()
const formatBytes = useFormatBytes()
const messages = defineMessages({
- syncUpdateTitle: {
- id: 'app.sync-instances-update.notification.title',
- defaultMessage: 'Sync your instances',
- },
- syncUpdateDescription: {
- id: 'app.sync-instances-update.notification.description',
- defaultMessage:
- 'Keep game settings, servers, resource packs, and more in sync across your instances.',
- },
- syncUpdateView: {
- id: 'app.sync-instances-update.notification.view-update',
- defaultMessage: 'View update',
- },
- syncUpdateDismiss: {
- id: 'app.sync-instances-update.notification.dismiss',
- defaultMessage: 'Dismiss',
- },
warning: { id: 'app.notification.warning', defaultMessage: 'Warning' },
goBack: { id: 'app.navigation.go-back', defaultMessage: 'Go back' },
goForward: { id: 'app.navigation.go-forward', defaultMessage: 'Go forward' },
@@ -851,22 +830,7 @@ async function setupApp() {
stateInitialized.value = true
debugStartup('App state initialized')
await traceStartupStep('Render initialized app', nextTick)
- const isSyncUpdateVersion = version.startsWith('0.20.')
- if (isSyncUpdateVersion && pending_update_toast_for_version !== version) {
- markSyncInstancesUpdateNotificationShown()
- }
- if (
- appSettings.getFeatureFlag('show_sync_instances_update_modal') ||
- (isSyncUpdateVersion &&
- pending_update_toast_for_version === version &&
- (
- await traceStartupStep('Load instances for update notification', () =>
- queryClient.fetchQuery(instanceListQueryOptions()),
- )
- ).length > 0)
- ) {
- showSyncInstancesUpdateNotification()
- }
+ await newUpdateModal.value?.notifyForVersion(version, pending_update_toast_for_version)
await traceStartupStep('Register window resize listener', () =>
getCurrentWindow().onResized(async () => {
@@ -1166,55 +1130,11 @@ const updateToPlayModal = ref()
const modrinthLoginModal = ref()
const appSettingsModal = ref()
-const syncInstancesUpdateModal = ref()
-let syncInstancesUpdateNotificationId = null
-
-function showSyncInstancesUpdateNotification() {
- if (
- popupNotificationManager
- .getNotifications()
- .some((notification) => notification.id === syncInstancesUpdateNotificationId)
- ) {
- return
- }
-
- if (!shouldShowSyncInstancesUpdateNotification()) return
-
- const notification = addPopupNotification({
- contentType: 'standard',
- title: formatMessage(messages.syncUpdateTitle),
- text: formatMessage(messages.syncUpdateDescription),
- type: 'info',
- hideIcon: true,
- autoCloseMs: null,
- buttons: [
- {
- label: formatMessage(messages.syncUpdateDismiss),
- color: 'standard',
- action: () => popupNotificationManager.removeNotification(notification.id),
- },
- {
- label: formatMessage(messages.syncUpdateView),
- color: 'brand',
- action: () => syncInstancesUpdateModal.value?.show(),
- },
- ],
- })
- syncInstancesUpdateNotificationId = notification.id
-}
+const newUpdateModal = ref()
provide(appSettingsModalOpenProfileKey, () => appSettingsModal.value?.showProfile())
provide(appSettingsModalOpenSyncedOptionsKey, () => appSettingsModal.value?.showSyncedOptions())
-watch(
- () => appSettings.getFeatureFlag('show_sync_instances_update_modal'),
- (enabled) => {
- if (enabled && stateInitialized.value) {
- showSyncInstancesUpdateNotification()
- }
- },
-)
-
watch(incompatibilityWarningModal, (modal) => {
if (modal) {
setContentIncompatibilityWarningModal(modal)
@@ -2234,7 +2154,7 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
-
+
diff --git a/apps/app-frontend/src/components/ui/sync-instances-update-modal/index.vue b/apps/app-frontend/src/features/updates/new-update-modal/index.vue
similarity index 98%
rename from apps/app-frontend/src/components/ui/sync-instances-update-modal/index.vue
rename to apps/app-frontend/src/features/updates/new-update-modal/index.vue
index 56b145df190..75ba61a79db 100644
--- a/apps/app-frontend/src/components/ui/sync-instances-update-modal/index.vue
+++ b/apps/app-frontend/src/features/updates/new-update-modal/index.vue
@@ -13,6 +13,7 @@ import { computed, nextTick, useTemplateRef } from 'vue'
import SyncSourceModal from '@/components/ui/settings/instances/SyncSourceModal.vue'
+import { useNewUpdateNotification } from './use-notification'
import { type SyncUpdateOption, syncUpdateOptions, useSyncInstancesUpdate } from './use-sync'
const modal = useTemplateRef>('modal')
@@ -37,6 +38,7 @@ const {
chooseSource,
retrySources,
} = useSyncInstancesUpdate()
+const { notifyForVersion } = useNewUpdateNotification(show)
const messages = defineMessages({
badge: {
@@ -265,7 +267,7 @@ async function confirmSource() {
sourceModal.value?.hide()
}
-defineExpose({ show, hide })
+defineExpose({ show, hide, notifyForVersion })
diff --git a/apps/app-frontend/src/components/ui/sync-instances-update-modal/show-notification.ts b/apps/app-frontend/src/features/updates/new-update-modal/show-notification.ts
similarity index 100%
rename from apps/app-frontend/src/components/ui/sync-instances-update-modal/show-notification.ts
rename to apps/app-frontend/src/features/updates/new-update-modal/show-notification.ts
diff --git a/apps/app-frontend/src/features/updates/new-update-modal/use-notification.ts b/apps/app-frontend/src/features/updates/new-update-modal/use-notification.ts
new file mode 100644
index 00000000000..221c59e9cdc
--- /dev/null
+++ b/apps/app-frontend/src/features/updates/new-update-modal/use-notification.ts
@@ -0,0 +1,107 @@
+import {
+ defineMessages,
+ injectPopupNotificationManager,
+ type PopupNotification,
+ useVIntl,
+} from '@modrinth/ui'
+import { useQueryClient } from '@tanstack/vue-query'
+import { watch } from 'vue'
+
+import { useAppSettings } from '@/composables/use-app-settings'
+import { traceStartupStep } from '@/helpers/startup-debug'
+import { instanceListQueryOptions } from '@/pages/instance/query-options'
+
+import {
+ markSyncInstancesUpdateNotificationShown,
+ shouldShowSyncInstancesUpdateNotification,
+} from './show-notification'
+
+const messages = defineMessages({
+ title: {
+ id: 'app.sync-instances-update.notification.title',
+ defaultMessage: 'Sync your instances',
+ },
+ description: {
+ id: 'app.sync-instances-update.notification.description',
+ defaultMessage:
+ 'Keep game settings, servers, resource packs, and more in sync across your instances.',
+ },
+ view: {
+ id: 'app.sync-instances-update.notification.view-update',
+ defaultMessage: 'View update',
+ },
+ dismiss: {
+ id: 'app.sync-instances-update.notification.dismiss',
+ defaultMessage: 'Dismiss',
+ },
+})
+
+export function useNewUpdateNotification(showModal: () => void) {
+ const appSettings = useAppSettings()
+ const queryClient = useQueryClient()
+ const popupNotificationManager = injectPopupNotificationManager()
+ const { formatMessage } = useVIntl()
+ let notificationId: PopupNotification['id'] | null = null
+
+ function showNotification() {
+ if (
+ popupNotificationManager
+ .getNotifications()
+ .some((notification) => notification.id === notificationId)
+ ) {
+ return
+ }
+
+ if (!shouldShowSyncInstancesUpdateNotification()) return
+
+ const notification = popupNotificationManager.addPopupNotification({
+ contentType: 'standard',
+ title: formatMessage(messages.title),
+ text: formatMessage(messages.description),
+ type: 'info',
+ hideIcon: true,
+ autoCloseMs: null,
+ buttons: [
+ {
+ label: formatMessage(messages.dismiss),
+ color: 'standard',
+ action: () => popupNotificationManager.removeNotification(notification.id),
+ },
+ {
+ label: formatMessage(messages.view),
+ color: 'brand',
+ action: showModal,
+ },
+ ],
+ })
+ notificationId = notification.id
+ }
+
+ async function notifyForVersion(version: string, pendingUpdateToastForVersion: string | null) {
+ const isSyncUpdateVersion = version.startsWith('0.20.')
+ if (isSyncUpdateVersion && pendingUpdateToastForVersion !== version) {
+ markSyncInstancesUpdateNotificationShown()
+ }
+ if (
+ appSettings.getFeatureFlag('show_sync_instances_update_modal') ||
+ (isSyncUpdateVersion &&
+ pendingUpdateToastForVersion === version &&
+ (
+ await traceStartupStep('Load instances for update notification', () =>
+ queryClient.fetchQuery(instanceListQueryOptions()),
+ )
+ ).length > 0)
+ ) {
+ showNotification()
+ }
+ }
+
+ watch(
+ () => appSettings.getFeatureFlag('show_sync_instances_update_modal'),
+ (enabled) => {
+ if (enabled) showNotification()
+ },
+ )
+
+ return { notifyForVersion }
+}
diff --git a/apps/app-frontend/src/components/ui/sync-instances-update-modal/use-sync.ts b/apps/app-frontend/src/features/updates/new-update-modal/use-sync.ts
similarity index 100%
rename from apps/app-frontend/src/components/ui/sync-instances-update-modal/use-sync.ts
rename to apps/app-frontend/src/features/updates/new-update-modal/use-sync.ts
diff --git a/apps/app-frontend/tailwind.config.ts b/apps/app-frontend/tailwind.config.ts
index e81246e2165..df3a727abd1 100644
--- a/apps/app-frontend/tailwind.config.ts
+++ b/apps/app-frontend/tailwind.config.ts
@@ -4,6 +4,7 @@ import type { Config } from 'tailwindcss'
const config: Config = {
content: [
'./src/components/**/*.{js,vue,ts}',
+ './src/features/**/*.{js,vue,ts}',
'./src/layouts/**/*.vue',
'./src/pages/**/*.vue',
'./src/plugins/**/*.{js,ts}',