UNPKG

quasar

Version:

Build high-performance VueJS user interfaces (SPA, PWA, SSR, Mobile and Desktop) in record time

545 lines (460 loc) 14.9 kB
import { h, ref, markRaw, TransitionGroup } from 'vue' import QAvatar from '../../components/avatar/QAvatar.js' import QIcon from '../../components/icon/QIcon.js' import QBtn from '../../components/btn/QBtn.js' import QSpinner from '../../components/spinner/QSpinner.js' import { createChildApp } from '../../install-quasar.js' import { createComponent } from '../../utils/private.create/create.js' import { noop } from '../../utils/event/event.js' import { createGlobalNode } from '../../utils/private.config/nodes.js' import { isObject } from '../../utils/is/is.js' let uid = 0 const defaults = {} const groups = {} const notificationsList = {} const positionClass = {} const emptyRE = /^\s*$/ const notifRefs = [] const invalidTimeoutValues = [ void 0, null, true, false, '' ] const positionList = [ 'top-left', 'top-right', 'bottom-left', 'bottom-right', 'top', 'bottom', 'left', 'right', 'center' ] const badgePositions = [ 'top-left', 'top-right', 'bottom-left', 'bottom-right' ] const notifTypes = { positive: { icon: $q => $q.iconSet.type.positive, color: 'positive' }, negative: { icon: $q => $q.iconSet.type.negative, color: 'negative' }, warning: { icon: $q => $q.iconSet.type.warning, color: 'warning', textColor: 'dark' }, info: { icon: $q => $q.iconSet.type.info, color: 'info' }, ongoing: { group: false, timeout: 0, spinner: true, color: 'grey-8' } } function addNotification (config, $q, originalApi) { if (!config) { return logError('parameter required') } let Api const notif = { textColor: 'white' } if (config.ignoreDefaults !== true) { Object.assign(notif, defaults) } if (isObject(config) === false) { if (notif.type) { Object.assign(notif, notifTypes[ notif.type ]) } config = { message: config } } Object.assign(notif, notifTypes[ config.type || notif.type ], config) if (typeof notif.icon === 'function') { notif.icon = notif.icon($q) } if (!notif.spinner) { notif.spinner = false } else { if (notif.spinner === true) { notif.spinner = QSpinner } notif.spinner = markRaw(notif.spinner) } notif.meta = { hasMedia: Boolean(notif.spinner !== false || notif.icon || notif.avatar), hasText: hasContent(notif.message) || hasContent(notif.caption) } if (notif.position) { if (positionList.includes(notif.position) === false) { return logError('wrong position', config) } } else { notif.position = 'bottom' } if (invalidTimeoutValues.includes(notif.timeout) === true) { notif.timeout = 5000 } else { const t = Number(notif.timeout) // we catch exponential notation too with Number() casting if (isNaN(t) || t < 0) { return logError('wrong timeout', config) } notif.timeout = Number.isFinite(t) ? t : 0 } if (notif.timeout === 0) { notif.progress = false } else if (notif.progress === true) { notif.meta.progressClass = 'q-notification__progress' + ( notif.progressClass ? ` ${ notif.progressClass }` : '' ) notif.meta.progressStyle = { animationDuration: `${ notif.timeout + 1000 }ms` } } const actions = ( Array.isArray(config.actions) === true ? config.actions : [] ).concat( config.ignoreDefaults !== true && Array.isArray(defaults.actions) === true ? defaults.actions : [] ).concat( notifTypes[ config.type ] !== void 0 && Array.isArray(notifTypes[ config.type ].actions) === true ? notifTypes[ config.type ].actions : [] ) const { closeBtn } = notif closeBtn && actions.push({ label: typeof closeBtn === 'string' ? closeBtn : $q.lang.label.close }) notif.actions = actions.map(({ handler, noDismiss, ...item }) => ({ flat: true, ...item, onClick: typeof handler === 'function' ? () => { handler() noDismiss !== true && dismiss() } : () => { dismiss() } })) if (notif.multiLine === void 0) { notif.multiLine = notif.actions.length > 1 } Object.assign(notif.meta, { class: 'q-notification row items-stretch' + ` q-notification--${ notif.multiLine === true ? 'multi-line' : 'standard' }` + (notif.color !== void 0 ? ` bg-${ notif.color }` : '') + (notif.textColor !== void 0 ? ` text-${ notif.textColor }` : '') + (notif.classes !== void 0 ? ` ${ notif.classes }` : ''), wrapperClass: 'q-notification__wrapper col relative-position border-radius-inherit ' + (notif.multiLine === true ? 'column no-wrap justify-center' : 'row items-center'), contentClass: 'q-notification__content row items-center' + (notif.multiLine === true ? '' : ' col'), leftClass: notif.meta.hasText === true ? 'additional' : 'single', attrs: { role: 'alert', ...notif.attrs } }) if (notif.group === false) { notif.group = void 0 notif.meta.group = void 0 } else { if (notif.group === void 0 || notif.group === true) { // do not replace notifications with different buttons notif.group = [ notif.message, notif.caption, notif.multiline ].concat( notif.actions.map(props => `${ props.label }*${ props.icon }`) ).join('|') } notif.meta.group = notif.group + '|' + notif.position } if (notif.actions.length === 0) { notif.actions = void 0 } else { notif.meta.actionsClass = 'q-notification__actions row items-center ' + (notif.multiLine === true ? 'justify-end' : 'col-auto') + (notif.meta.hasMedia === true ? ' q-notification__actions--with-media' : '') } if (originalApi !== void 0) { // reset timeout if any if (originalApi.notif.meta.timer) { clearTimeout(originalApi.notif.meta.timer) originalApi.notif.meta.timer = void 0 } // retain uid notif.meta.uid = originalApi.notif.meta.uid // replace notif const index = notificationsList[ notif.position ].value.indexOf(originalApi.notif) notificationsList[ notif.position ].value[ index ] = notif } else { const original = groups[ notif.meta.group ] // woohoo, it's a new notification if (original === void 0) { notif.meta.uid = uid++ notif.meta.badge = 1 if ([ 'left', 'right', 'center' ].indexOf(notif.position) !== -1) { notificationsList[ notif.position ].value.splice( Math.floor(notificationsList[ notif.position ].value.length / 2), 0, notif ) } else { const action = notif.position.indexOf('top') !== -1 ? 'unshift' : 'push' notificationsList[ notif.position ].value[ action ](notif) } if (notif.group !== void 0) { groups[ notif.meta.group ] = notif } } // ok, so it's NOT a new one else { // reset timeout if any if (original.meta.timer) { clearTimeout(original.meta.timer) original.meta.timer = void 0 } if (notif.badgePosition !== void 0) { if (badgePositions.includes(notif.badgePosition) === false) { return logError('wrong badgePosition', config) } } else { notif.badgePosition = `top-${ notif.position.indexOf('left') !== -1 ? 'right' : 'left' }` } notif.meta.uid = original.meta.uid notif.meta.badge = original.meta.badge + 1 notif.meta.badgeClass = `q-notification__badge q-notification__badge--${ notif.badgePosition }` + (notif.badgeColor !== void 0 ? ` bg-${ notif.badgeColor }` : '') + (notif.badgeTextColor !== void 0 ? ` text-${ notif.badgeTextColor }` : '') + (notif.badgeClass ? ` ${ notif.badgeClass }` : '') const index = notificationsList[ notif.position ].value.indexOf(original) notificationsList[ notif.position ].value[ index ] = groups[ notif.meta.group ] = notif } } const dismiss = () => { removeNotification(notif) Api = void 0 } if (notif.timeout > 0) { notif.meta.timer = setTimeout(() => { notif.meta.timer = void 0 dismiss() }, notif.timeout + /* show duration */ 1000) } // only non-groupable can be updated if (notif.group !== void 0) { return props => { if (props !== void 0) { logError('trying to update a grouped one which is forbidden', config) } else { dismiss() } } } Api = { dismiss, config, notif } if (originalApi !== void 0) { Object.assign(originalApi, Api) return } return props => { // if notification wasn't previously dismissed if (Api !== void 0) { // if no params, then we must dismiss the notification if (props === void 0) { Api.dismiss() } // otherwise we're updating it else { const newNotif = Object.assign({}, Api.config, props, { group: false, position: notif.position }) addNotification(newNotif, $q, Api) } } } } function removeNotification (notif) { if (notif.meta.timer) { clearTimeout(notif.meta.timer) notif.meta.timer = void 0 } const index = notificationsList[ notif.position ].value.indexOf(notif) if (index !== -1) { if (notif.group !== void 0) { delete groups[ notif.meta.group ] } const el = notifRefs[ '' + notif.meta.uid ] if (el) { const { width, height } = getComputedStyle(el) el.style.left = `${ el.offsetLeft }px` el.style.width = width el.style.height = height } notificationsList[ notif.position ].value.splice(index, 1) if (typeof notif.onDismiss === 'function') { notif.onDismiss() } } } function hasContent (str) { return str !== void 0 && str !== null && emptyRE.test(str) !== true } function logError (error, config) { console.error(`Notify: ${ error }`, config) return false } function getComponent () { return createComponent({ name: 'QNotifications', // hide App from Vue devtools devtools: { hide: true }, setup () { return () => h('div', { class: 'q-notifications' }, positionList.map(pos => { return h(TransitionGroup, { key: pos, class: positionClass[ pos ], tag: 'div', name: `q-notification--${ pos }` }, () => notificationsList[ pos ].value.map(notif => { const meta = notif.meta const mainChild = [] if (meta.hasMedia === true) { if (notif.spinner !== false) { mainChild.push( h(notif.spinner, { class: 'q-notification__spinner q-notification__spinner--' + meta.leftClass, color: notif.spinnerColor, size: notif.spinnerSize }) ) } else if (notif.icon) { mainChild.push( h(QIcon, { class: 'q-notification__icon q-notification__icon--' + meta.leftClass, name: notif.icon, color: notif.iconColor, size: notif.iconSize, role: 'img' }) ) } else if (notif.avatar) { mainChild.push( h(QAvatar, { class: 'q-notification__avatar q-notification__avatar--' + meta.leftClass }, () => h('img', { src: notif.avatar, 'aria-hidden': 'true' })) ) } } if (meta.hasText === true) { let msgChild const msgData = { class: 'q-notification__message col' } if (notif.html === true) { msgData.innerHTML = notif.caption ? `<div>${ notif.message }</div><div class="q-notification__caption">${ notif.caption }</div>` : notif.message } else { const msgNode = [ notif.message ] msgChild = notif.caption ? [ h('div', msgNode), h('div', { class: 'q-notification__caption' }, [ notif.caption ]) ] : msgNode } mainChild.push( h('div', msgData, msgChild) ) } const child = [ h('div', { class: meta.contentClass }, mainChild) ] notif.progress === true && child.push( h('div', { key: `${ meta.uid }|p|${ meta.badge }`, class: meta.progressClass, style: meta.progressStyle }) ) notif.actions !== void 0 && child.push( h('div', { class: meta.actionsClass }, notif.actions.map(props => h(QBtn, props))) ) meta.badge > 1 && child.push( h('div', { key: `${ meta.uid }|${ meta.badge }`, class: notif.meta.badgeClass, style: notif.badgeStyle }, [ meta.badge ]) ) return h('div', { ref: el => { notifRefs[ '' + meta.uid ] = el }, key: meta.uid, class: meta.class, ...meta.attrs }, [ h('div', { class: meta.wrapperClass }, child) ]) })) })) } }) } export default { setDefaults (opts) { if (__QUASAR_SSR_SERVER__ !== true) { isObject(opts) === true && Object.assign(defaults, opts) } }, registerType (typeName, typeOpts) { if (__QUASAR_SSR_SERVER__ !== true && isObject(typeOpts) === true) { notifTypes[ typeName ] = typeOpts } }, install ({ $q, parentApp }) { $q.notify = this.create = __QUASAR_SSR_SERVER__ ? noop : opts => addNotification(opts, $q) $q.notify.setDefaults = this.setDefaults $q.notify.registerType = this.registerType if ($q.config.notify !== void 0) { this.setDefaults($q.config.notify) } if (__QUASAR_SSR_SERVER__ !== true && this.__installed !== true) { positionList.forEach(pos => { notificationsList[ pos ] = ref([]) const vert = [ 'left', 'center', 'right' ].includes(pos) === true ? 'center' : (pos.indexOf('top') !== -1 ? 'top' : 'bottom'), align = pos.indexOf('left') !== -1 ? 'start' : (pos.indexOf('right') !== -1 ? 'end' : 'center'), classes = [ 'left', 'right' ].includes(pos) ? `items-${ pos === 'left' ? 'start' : 'end' } justify-center` : (pos === 'center' ? 'flex-center' : `items-${ align }`) positionClass[ pos ] = `q-notifications__list q-notifications__list--${ vert } fixed column no-wrap ${ classes }` }) const el = createGlobalNode('q-notify') createChildApp(getComponent(), parentApp).mount(el) } } }