UNPKG

quasar

Version:

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

289 lines (231 loc) 6.75 kB
import { h, ref, computed, onMounted, onBeforeUnmount, getCurrentInstance } from 'vue' import { createComponent } from '../../utils/private.create/create.js' import { between } from '../../utils/format/format.js' const xhr = __QUASAR_SSR_SERVER__ ? null : XMLHttpRequest, open = __QUASAR_SSR_SERVER__ ? null : xhr.prototype.open, positionValues = [ 'top', 'right', 'bottom', 'left' ] let stack = [] let highjackCount = 0 function translate ({ p, pos, active, horiz, reverse, dir }) { let x = 1, y = 1 if (horiz === true) { if (reverse === true) { x = -1 } if (pos === 'bottom') { y = -1 } return { transform: `translate3d(${ x * (p - 100) }%,${ active ? 0 : y * -200 }%,0)` } } if (reverse === true) { y = -1 } if (pos === 'right') { x = -1 } return { transform: `translate3d(${ active ? 0 : dir * x * -200 }%,${ y * (p - 100) }%,0)` } } function inc (p, amount) { if (typeof amount !== 'number') { if (p < 25) { amount = Math.random() * 3 + 3 } else if (p < 65) { amount = Math.random() * 3 } else if (p < 85) { amount = Math.random() * 2 } else if (p < 99) { amount = 0.6 } else { amount = 0 } } return between(p + amount, 0, 100) } function highjackAjax (stackEntry) { highjackCount++ stack.push(stackEntry) if (highjackCount > 1) return xhr.prototype.open = function (_, url) { const stopStack = [] const loadStart = () => { stack.forEach(entry => { if ( entry.hijackFilter.value === null || (entry.hijackFilter.value(url) === true) ) { entry.start() stopStack.push(entry.stop) } }) } const loadEnd = () => { stopStack.forEach(stop => { stop() }) } this.addEventListener('loadstart', loadStart, { once: true }) this.addEventListener('loadend', loadEnd, { once: true }) open.apply(this, arguments) } } function restoreAjax (start) { stack = stack.filter(entry => entry.start !== start) highjackCount = Math.max(0, highjackCount - 1) if (highjackCount === 0) { xhr.prototype.open = open } } export default createComponent({ name: 'QAjaxBar', props: { position: { type: String, default: 'top', validator: val => positionValues.includes(val) }, size: { type: String, default: '2px' }, color: String, skipHijack: Boolean, reverse: Boolean, hijackFilter: Function }, emits: [ 'start', 'stop' ], setup (props, { emit }) { const { proxy } = getCurrentInstance() const progress = ref(0) const onScreen = ref(false) const animate = ref(true) let sessions = 0, timer = null, speed const classes = computed(() => `q-loading-bar q-loading-bar--${ props.position }` + (props.color !== void 0 ? ` bg-${ props.color }` : '') + (animate.value === true ? '' : ' no-transition') ) const horizontal = computed(() => props.position === 'top' || props.position === 'bottom') const sizeProp = computed(() => (horizontal.value === true ? 'height' : 'width')) const style = computed(() => { const active = onScreen.value const obj = translate({ p: progress.value, pos: props.position, active, horiz: horizontal.value, reverse: proxy.$q.lang.rtl === true && [ 'top', 'bottom' ].includes(props.position) ? props.reverse === false : props.reverse, dir: proxy.$q.lang.rtl === true ? -1 : 1 }) obj[ sizeProp.value ] = props.size obj.opacity = active ? 1 : 0 return obj }) const attributes = computed(() => ( onScreen.value === true ? { role: 'progressbar', 'aria-valuemin': 0, 'aria-valuemax': 100, 'aria-valuenow': progress.value } : { 'aria-hidden': 'true' } )) function start (newSpeed = 300) { const oldSpeed = speed speed = Math.max(0, newSpeed) || 0 sessions++ if (sessions > 1) { if (oldSpeed === 0 && newSpeed > 0) { planNextStep() } else if (timer !== null && oldSpeed > 0 && newSpeed <= 0) { clearTimeout(timer) timer = null } return sessions } timer !== null && clearTimeout(timer) emit('start') progress.value = 0 /** * We're trying to avoid side effects if start() is called inside a watchEffect() * so we're accessing the _value property directly (under the covers implementation detail of ref()) * * Otherwise, any refs() accessed here would be marked as deps for the watchEffect() * -- and we are changing them below, which would cause an infinite loop */ timer = setTimeout(() => { timer = null animate.value = true newSpeed > 0 && planNextStep() // eslint-disable-next-line vue/no-ref-as-operand }, onScreen._value === true ? 500 : 1) // eslint-disable-next-line vue/no-ref-as-operand if (onScreen._value !== true) { onScreen.value = true animate.value = false } return sessions } function increment (amount) { if (sessions > 0) { progress.value = inc(progress.value, amount) } return sessions } function stop () { sessions = Math.max(0, sessions - 1) if (sessions > 0) { return sessions } if (timer !== null) { clearTimeout(timer) timer = null } emit('stop') const end = () => { animate.value = true progress.value = 100 timer = setTimeout(() => { timer = null onScreen.value = false }, 1000) } if (progress.value === 0) { timer = setTimeout(end, 1) } else { end() } return sessions } function planNextStep () { if (progress.value < 100) { timer = setTimeout(() => { timer = null increment() planNextStep() }, speed) } } let hijacked onMounted(() => { if (props.skipHijack !== true) { hijacked = true highjackAjax({ start, stop, hijackFilter: computed(() => props.hijackFilter || null) }) } }) onBeforeUnmount(() => { timer !== null && clearTimeout(timer) hijacked === true && restoreAjax(start) }) // expose public methods Object.assign(proxy, { start, stop, increment }) return () => h('div', { class: classes.value, style: style.value, ...attributes.value }) } })