mirror of
https://github.com/LizardByte/Sunshine.git
synced 2026-08-07 18:36:34 +00:00
151 lines
5.0 KiB
Vue
151 lines
5.0 KiB
Vue
<template>
|
|
<div class="notification-container" v-if="state.notifications.length > 0">
|
|
<div
|
|
v-for="n in state.notifications"
|
|
:key="n.id"
|
|
class="alert d-flex align-items-start gap-2 mb-2"
|
|
:class="'alert-' + n.type"
|
|
role="alert"
|
|
>
|
|
<component :is="iconFor(n.type)" :size="18" class="icon flex-shrink-0 mt-1"></component>
|
|
<div class="flex-grow-1">
|
|
<div v-if="n.titleKey || n.title">
|
|
<strong>{{ n.titleKey ? $t(n.titleKey) : n.title }}</strong>
|
|
</div>
|
|
<span>{{ n.messageKey ? $t(n.messageKey) : n.message }}</span>
|
|
</div>
|
|
<button type="button" class="btn-close" :aria-label="$t('_common.dismiss')" @click="dismiss(n.id)"></button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { reactive } from 'vue'
|
|
import { AlertCircle, AlertTriangle, CheckCircle, Info } from '@lucide/vue'
|
|
|
|
/**
|
|
* Singleton reactive notification state shared across the app instance.
|
|
* Using reactive() at module scope means all consumers — including plain JS
|
|
* modules like fetch_utils.js — mutate the same reactive object, and any
|
|
* mounted Notification component will update automatically.
|
|
*/
|
|
export const state = reactive({
|
|
notifications: [],
|
|
_nextId: 1,
|
|
})
|
|
|
|
/**
|
|
* Push a notification using raw strings.
|
|
*
|
|
* @param {'danger'|'warning'|'success'|'info'} type - Bootstrap color variant.
|
|
* @param {string} message - The notification body text.
|
|
* @param {string} [title] - Optional bold title prefix.
|
|
*/
|
|
function push(type, message, title) {
|
|
state.notifications.push({ id: state._nextId++, type, message, title: title || null, messageKey: null, titleKey: null })
|
|
}
|
|
|
|
/**
|
|
* Push a notification using i18n translation keys.
|
|
*
|
|
* @param {'danger'|'warning'|'success'|'info'} type - Bootstrap color variant.
|
|
* @param {string} messageKey - i18n key for the notification body.
|
|
* @param {string} [titleKey] - Optional i18n key for the bold title prefix.
|
|
*/
|
|
function pushKey(type, messageKey, titleKey) {
|
|
state.notifications.push({ id: state._nextId++, type, message: null, title: null, messageKey, titleKey: titleKey || null })
|
|
}
|
|
|
|
/**
|
|
* Map a Bootstrap color variant to its corresponding @lucide/vue icon name.
|
|
*
|
|
* @param {'danger'|'warning'|'success'|'info'} type - Bootstrap color variant.
|
|
* @returns {string} The lucide icon component name.
|
|
*/
|
|
function iconFor(type) {
|
|
return { danger: 'AlertCircle', warning: 'AlertTriangle', success: 'CheckCircle', info: 'Info' }[type] || 'Info'
|
|
}
|
|
|
|
/**
|
|
* Convenience helpers for the four common variants using raw strings.
|
|
*/
|
|
export const notify = {
|
|
/**
|
|
* @param {string} message - The notification body text.
|
|
* @param {string} [title] - Optional bold title shown above the message.
|
|
*/
|
|
error: (message, title) => push('danger', message, title),
|
|
/**
|
|
* @param {string} message - The notification body text.
|
|
* @param {string} [title] - Optional bold title shown above the message.
|
|
*/
|
|
warning: (message, title) => push('warning', message, title),
|
|
/**
|
|
* @param {string} message - The notification body text.
|
|
* @param {string} [title] - Optional bold title shown above the message.
|
|
*/
|
|
success: (message, title) => push('success', message, title),
|
|
/**
|
|
* @param {string} message - The notification body text.
|
|
* @param {string} [title] - Optional bold title shown above the message.
|
|
*/
|
|
info: (message, title) => push('info', message, title),
|
|
}
|
|
|
|
/**
|
|
* Convenience helpers for the four common variants using i18n keys.
|
|
*/
|
|
export const notifyKey = {
|
|
/**
|
|
* @param {string} messageKey - i18n key for the notification body text.
|
|
* @param {string} [titleKey] - Optional i18n key for the bold title shown above the message.
|
|
*/
|
|
error: (messageKey, titleKey) => pushKey('danger', messageKey, titleKey),
|
|
/**
|
|
* @param {string} messageKey - i18n key for the notification body text.
|
|
* @param {string} [titleKey] - Optional i18n key for the bold title shown above the message.
|
|
*/
|
|
warning: (messageKey, titleKey) => pushKey('warning', messageKey, titleKey),
|
|
/**
|
|
* @param {string} messageKey - i18n key for the notification body text.
|
|
* @param {string} [titleKey] - Optional i18n key for the bold title shown above the message.
|
|
*/
|
|
success: (messageKey, titleKey) => pushKey('success', messageKey, titleKey),
|
|
/**
|
|
* @param {string} messageKey - i18n key for the notification body text.
|
|
* @param {string} [titleKey] - Optional i18n key for the bold title shown above the message.
|
|
*/
|
|
info: (messageKey, titleKey) => pushKey('info', messageKey, titleKey),
|
|
}
|
|
|
|
export default {
|
|
components: { AlertCircle, AlertTriangle, CheckCircle, Info },
|
|
|
|
setup() {
|
|
function dismiss(id) {
|
|
const idx = state.notifications.findIndex(n => n.id === id)
|
|
if (idx !== -1) state.notifications.splice(idx, 1)
|
|
}
|
|
|
|
return { state, dismiss, iconFor }
|
|
},
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.notification-container {
|
|
position: fixed;
|
|
bottom: 1rem;
|
|
right: 1rem;
|
|
left: 1rem;
|
|
z-index: 1090;
|
|
}
|
|
|
|
@media (min-width: 576px) {
|
|
.notification-container {
|
|
left: auto;
|
|
width: 22rem;
|
|
}
|
|
}
|
|
</style>
|