UNPKG

quasar

Version:

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

599 lines (514 loc) 16.6 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( Array.isArray(notifTypes[config.type]?.actions) === true ? notifTypes[config.type].actions : [] ) const { closeBtn } = notif if (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() if (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[String(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 => 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) ] if (notif.progress === true) { child.push( h('div', { key: `${meta.uid}|p|${meta.badge}`, class: meta.progressClass, style: meta.progressStyle }) ) } if (notif.actions !== void 0) { child.push( h( 'div', { class: meta.actionsClass }, notif.actions.map(props => h(QBtn, props)) ) ) } if (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[String(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) { if (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) } } }