From 88766adeea814afe888bb85c1903d5d97cdc7508 Mon Sep 17 00:00:00 2001 From: Ajit Mehrotra Date: Tue, 2 Dec 2025 16:39:40 -0500 Subject: [PATCH] refactor(ui): replace vue-sonner toasts with nuxtui toasts > [!Note] This stubs the unraid-ui/src/components/common/toast. Initially created a shim to convert vue-sonnner toasts to nuxtui. However, since there weren't that many, I just did a clean replacement. - replace router link with window.location.assign The `UButton` component attempts to inject the Vue Router instance when the `:to` prop is used. In the standalone component environment (where the router is not installed), this caused a "TypeError: inject(...) is undefined" crash when rendering notifications with links. This change replaces the `:to` prop with a standard `@click` handler that uses `window.location.assign`, ensuring navigation works correctly without requiring the router context. --- unraid-ui/src/global.d.ts | 8 ---- web/app.config.ts | 18 +++++--- .../CriticalNotifications.standalone.vue | 43 ++++++++++--------- web/src/components/Notifications/Item.vue | 14 +++++- web/src/components/Notifications/List.vue | 4 +- web/src/components/Notifications/Sidebar.vue | 25 ++++++----- web/src/components/UnraidToaster.vue | 31 ------------- .../components/Wrapper/component-registry.ts | 5 --- web/src/components/Wrapper/mount-engine.ts | 3 ++ web/src/composables/useClipboardWithToast.ts | 14 ++---- 10 files changed, 72 insertions(+), 93 deletions(-) delete mode 100644 unraid-ui/src/global.d.ts delete mode 100644 web/src/components/UnraidToaster.vue diff --git a/unraid-ui/src/global.d.ts b/unraid-ui/src/global.d.ts deleted file mode 100644 index c9108b0ce..000000000 --- a/unraid-ui/src/global.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* eslint-disable no-var */ -declare global { - /** loaded by Toaster.vue */ - var toast: (typeof import('vue-sonner'))['toast']; -} - -// an export or import statement is required to make this file a module -export {}; diff --git a/web/app.config.ts b/web/app.config.ts index f542e723c..fd09f3554 100644 --- a/web/app.config.ts +++ b/web/app.config.ts @@ -32,10 +32,18 @@ export default { right: {}, }, }, - }, - toaster: { - position: 'bottom-right' as const, - expand: true, - duration: 5000, + + //css theming/style-overrides for the toast component + // https://ui.nuxt.com/docs/components/toast#theme + toast: {}, + + // Also, for toasts, BUT this is imported in the Root UApp in mount-engine.ts + // https://ui.nuxt.com/docs/components/toast#examples + toaster: { + position: 'top-center' as const, + // expand: false, --> buggy + duration: 5000, + max: 3, + }, }, }; diff --git a/web/src/components/Notifications/CriticalNotifications.standalone.vue b/web/src/components/Notifications/CriticalNotifications.standalone.vue index 0e4cf8b92..45cddda1d 100644 --- a/web/src/components/Notifications/CriticalNotifications.standalone.vue +++ b/web/src/components/Notifications/CriticalNotifications.standalone.vue @@ -1,8 +1,7 @@ - - diff --git a/web/src/components/Wrapper/component-registry.ts b/web/src/components/Wrapper/component-registry.ts index 905e1d1e7..10c1d02d3 100644 --- a/web/src/components/Wrapper/component-registry.ts +++ b/web/src/components/Wrapper/component-registry.ts @@ -134,11 +134,6 @@ export const componentMappings: ComponentMapping[] = [ selector: 'unraid-color-switcher', appId: 'color-switcher', }, - { - component: defineAsyncComponent(() => import('@/components/UnraidToaster.vue')), - selector: ['unraid-toaster', 'uui-toaster'], - appId: 'toaster', - }, { component: defineAsyncComponent(() => import('../UpdateOs/TestUpdateModal.standalone.vue')), selector: 'unraid-test-update-modal', diff --git a/web/src/components/Wrapper/mount-engine.ts b/web/src/components/Wrapper/mount-engine.ts index c2da2f5b6..d0dbbb1b3 100644 --- a/web/src/components/Wrapper/mount-engine.ts +++ b/web/src/components/Wrapper/mount-engine.ts @@ -12,6 +12,8 @@ import { createI18nInstance, ensureLocale, getWindowLocale } from '~/helpers/i18 // Import Pinia for use in Vue apps import { globalPinia } from '~/store/globalPinia'; import { ensureUnapiScope, ensureUnapiScopeForSelectors, observeUnapiScope } from '~/utils/unapiScope'; +// Import the app config to pass runtime settings (like toaster position) +import appConfig from '../../../app.config'; // Ensure Apollo client is singleton const apolloClient = (typeof window !== 'undefined' && window.apolloClient) || client; @@ -244,6 +246,7 @@ export async function mountUnifiedApp() { UApp, { portal: portalTarget, + toaster: appConfig.ui.toaster, }, { default: () => h(component, props), diff --git a/web/src/composables/useClipboardWithToast.ts b/web/src/composables/useClipboardWithToast.ts index c7aedd16b..655130ab0 100644 --- a/web/src/composables/useClipboardWithToast.ts +++ b/web/src/composables/useClipboardWithToast.ts @@ -5,6 +5,7 @@ import { useClipboard } from '@vueuse/core'; */ export function useClipboardWithToast() { const { copy, copied, isSupported } = useClipboard(); + const toast = useToast(); /** * Copy text and show toast @@ -19,10 +20,7 @@ export function useClipboardWithToast() { if (isSupported.value) { try { await copy(text); - // Use global toast if available - if (globalThis.toast) { - globalThis.toast.success(successMessage); - } + toast.add({ title: successMessage, color: 'success' }); return true; } catch (error) { console.error('Failed to copy to clipboard:', error); @@ -43,9 +41,7 @@ export function useClipboardWithToast() { document.body.removeChild(textarea); if (success) { - if (globalThis.toast) { - globalThis.toast.success(successMessage); - } + toast.add({ title: successMessage, color: 'success' }); return true; } } catch (error) { @@ -53,9 +49,7 @@ export function useClipboardWithToast() { } // Both methods failed - if (globalThis.toast) { - globalThis.toast.error('Failed to copy to clipboard'); - } + toast.add({ title: 'Failed to copy to clipboard', color: 'error' }); return false; };