feat: allow global tracking outside of components

This commit is contained in:
Guillaume Chau
2023-05-16 16:05:24 +02:00
parent d5f6275261
commit 5967e160be
5 changed files with 61 additions and 45 deletions
@@ -1,4 +1,5 @@
<template>
<GlobalLoading />
<div class="flex h-screen items-stretch bg-gray-100">
<ChannelList class="w-1/4 border-r border-gray-200" />
<router-view class="flex-1 overflow-auto" />
@@ -8,12 +9,14 @@
<script lang="ts">
import { defineComponent } from 'vue'
import ChannelList from './ChannelList.vue'
import GlobalLoading from './GlobalLoading.vue'
export default defineComponent({
name: 'App',
components: {
ChannelList,
GlobalLoading,
},
})
</script>
@@ -0,0 +1,16 @@
<script lang="ts" setup>
import { useGlobalQueryLoading } from '@vue/apollo-composable'
const loading = useGlobalQueryLoading()
</script>
<template>
<div
class="fixed bg-white p-4 rounded-br top-0 right-0"
data-test-id="global-loading"
>
<div v-if="loading">
Global loading...
</div>
</div>
</template>
@@ -128,4 +128,21 @@ describe('Vue 3 + Apollo Composable', () => {
cy.get('input[type="checkbox"]').click()
cy.get('[data-test-id="data"]').should('contain', 'Loaded channel: General')
})
it('global loading', () => {
cy.get('[data-test-id="global-loading"]').should('contain', 'Global loading...')
cy.get('.channel-link').should('have.lengthOf', 2)
cy.get('[data-test-id="global-loading"]').should('not.contain', 'Global loading...')
cy.get('.channel-link').eq(0).click()
cy.get('[data-test-id="global-loading"]').should('contain', 'Global loading...')
cy.contains('#app', 'Currently viewing # General')
cy.get('[data-test-id="global-loading"]').should('not.contain', 'Global loading...')
cy.get('.channel-link').eq(1).click()
cy.get('[data-test-id="global-loading"]').should('contain', 'Global loading...')
cy.contains('#app', 'Currently viewing # Random')
cy.get('[data-test-id="global-loading"]').should('not.contain', 'Global loading...')
cy.get('.channel-link').eq(0).click()
cy.get('[data-test-id="global-loading"]').should('not.contain', 'Global loading...')
cy.contains('#app', 'Currently viewing # General')
})
})
@@ -1,32 +1,32 @@
import { getCurrentTracking, getAppTracking } from './util/loadingTracking'
import { getCurrentTracking, globalTracking } from './util/loadingTracking'
import { computed } from 'vue-demi'
export function useQueryLoading () {
const { tracking } = getCurrentTracking()
if (!tracking) throw new Error('useQueryLoading must be called inside a setup function.')
return computed(() => tracking.queries.value > 0)
}
export function useMutationLoading () {
const { tracking } = getCurrentTracking()
if (!tracking) throw new Error('useMutationLoading must be called inside a setup function.')
return computed(() => tracking.mutations.value > 0)
}
export function useSubscriptionLoading () {
const { tracking } = getCurrentTracking()
if (!tracking) throw new Error('useSubscriptionLoading must be called inside a setup function.')
return computed(() => tracking.subscriptions.value > 0)
}
export function useGlobalQueryLoading () {
const { appTracking } = getAppTracking()
return computed(() => appTracking.queries.value > 0)
return computed(() => globalTracking.queries.value > 0)
}
export function useGlobalMutationLoading () {
const { appTracking } = getAppTracking()
return computed(() => appTracking.mutations.value > 0)
return computed(() => globalTracking.mutations.value > 0)
}
export function useGlobalSubscriptionLoading () {
const { appTracking } = getAppTracking()
return computed(() => appTracking.subscriptions.value > 0)
return computed(() => globalTracking.subscriptions.value > 0)
}
@@ -1,5 +1,5 @@
import { Ref, watch, onUnmounted, ref, getCurrentInstance, onBeforeUnmount } from 'vue-demi'
import type { CurrentInstance } from './types'
import { isServer } from './env.js'
export interface LoadingTracking {
queries: Ref<number>
@@ -11,71 +11,51 @@ export interface AppLoadingTracking extends LoadingTracking {
components: Map<any, LoadingTracking>
}
export function getAppTracking () {
const vm = getCurrentInstance() as CurrentInstance | null
const root = vm?.$root ?? vm?.root ?? vm?.proxy?.$root as CurrentInstance | null | undefined
if (!root) {
throw new Error('Instance $root not found')
}
let appTracking: AppLoadingTracking
if (!root._apolloAppTracking) {
// Add per Vue tracking
appTracking = root._apolloAppTracking = {
queries: ref(0),
mutations: ref(0),
subscriptions: ref(0),
components: new Map(),
}
} else {
appTracking = root._apolloAppTracking
}
return {
appTracking,
}
export const globalTracking: AppLoadingTracking = {
queries: ref(0),
mutations: ref(0),
subscriptions: ref(0),
components: new Map(),
}
export function getCurrentTracking () {
const vm = getCurrentInstance()
if (!vm) {
throw new Error('getCurrentTracking must be used during a component setup')
return {}
}
const { appTracking } = getAppTracking()
let tracking: LoadingTracking
if (!appTracking.components.has(vm)) {
if (!globalTracking.components.has(vm)) {
// Add per-component tracking
appTracking.components.set(vm, tracking = {
globalTracking.components.set(vm, tracking = {
queries: ref(0),
mutations: ref(0),
subscriptions: ref(0),
})
// Cleanup
onUnmounted(() => {
appTracking.components.delete(vm)
globalTracking.components.delete(vm)
})
} else {
tracking = appTracking.components.get(vm) as LoadingTracking
tracking = globalTracking.components.get(vm) as LoadingTracking
}
return {
appTracking,
tracking,
}
}
function track (loading: Ref<boolean>, type: keyof LoadingTracking) {
const { appTracking, tracking } = getCurrentTracking()
if (isServer) return
const { tracking } = getCurrentTracking()
watch(loading, (value, oldValue) => {
if (oldValue != null && value !== oldValue) {
const mod = value ? 1 : -1
tracking[type].value += mod
appTracking[type].value += mod
if (tracking) tracking[type].value += mod
globalTracking[type].value += mod
}
}, {
immediate: true,
@@ -83,8 +63,8 @@ function track (loading: Ref<boolean>, type: keyof LoadingTracking) {
onBeforeUnmount(() => {
if (loading.value) {
tracking[type].value--
appTracking[type].value--
if (tracking) tracking[type].value--
globalTracking[type].value--
}
})
}