Browse Source

feat(gui-v2): add global state for running requests and loading

pull/2877/head
braks 2 years ago
parent
commit
adcb588ccd
  1. 42
      packages/nc-gui-v2/composables/useApi/index.ts
  2. 16
      packages/nc-gui-v2/composables/useApi/interceptors.ts
  3. 12
      packages/nc-gui-v2/composables/useGlobal/getters.ts
  4. 4
      packages/nc-gui-v2/composables/useGlobal/state.ts
  5. 4
      packages/nc-gui-v2/composables/useGlobal/types.ts
  6. 5
      packages/nc-gui-v2/plugins/api.ts

42
packages/nc-gui-v2/composables/useApi/index.ts

@ -3,8 +3,7 @@ import { Api } from 'nocodb-sdk'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import type { EventHook, MaybeRef } from '@vueuse/core' import type { EventHook, MaybeRef } from '@vueuse/core'
import { addAxiosInterceptors } from './interceptors' import { addAxiosInterceptors } from './interceptors'
import { createEventHook, ref, unref, useNuxtApp } from '#imports' import { createEventHook, ref, unref, useGlobal } from '#imports'
import type { NuxtApp } from '#app'
interface UseApiReturn<D = any, R = any> { interface UseApiReturn<D = any, R = any> {
api: Api<any> api: Api<any>
@ -15,23 +14,26 @@ interface UseApiReturn<D = any, R = any> {
onResponse: EventHook<AxiosResponse<D, R>>['on'] onResponse: EventHook<AxiosResponse<D, R>>['on']
} }
export function createApiInstance(app: NuxtApp, baseURL = 'http://localhost:8080') { interface CreateApiOptions {
const api = new Api({ baseURL?: string
baseURL, }
})
addAxiosInterceptors(api, app)
return api export function createApiInstance<SecurityDataType = any>(options: CreateApiOptions = {}): Api<SecurityDataType> {
return addAxiosInterceptors(
new Api<SecurityDataType>({
baseURL: options.baseURL ?? 'http://localhost:8080',
}),
)
} }
/** todo: add props? */
interface UseApiProps<D = any> { interface UseApiProps<D = any> {
axiosConfig?: MaybeRef<AxiosRequestConfig<D>> axiosConfig?: MaybeRef<AxiosRequestConfig<D>>
useGlobalInstance?: MaybeRef<boolean> apiOptions?: CreateApiOptions
} }
export function useApi<Data = any, RequestConfig = any>(props: UseApiProps<Data> = {}): UseApiReturn<Data, RequestConfig> { export function useApi<Data = any, RequestConfig = any>(props: UseApiProps<Data> = {}): UseApiReturn<Data, RequestConfig> {
const state = $(useGlobal())
const isLoading = ref(false) const isLoading = ref(false)
const error = ref(null) const error = ref(null)
@ -42,7 +44,15 @@ export function useApi<Data = any, RequestConfig = any>(props: UseApiProps<Data>
const responseHook = createEventHook<AxiosResponse<Data, RequestConfig>>() const responseHook = createEventHook<AxiosResponse<Data, RequestConfig>>()
const api = unref(props.useGlobalInstance) ? useNuxtApp().$api : createApiInstance(useNuxtApp()) const api = createApiInstance(props.apiOptions)
function addRequest() {
state.runningRequests.push(state.runningRequests.length + 1)
}
function removeRequest() {
state.runningRequests.pop()
}
api.instance.interceptors.request.use( api.instance.interceptors.request.use(
(config) => { (config) => {
@ -50,6 +60,8 @@ export function useApi<Data = any, RequestConfig = any>(props: UseApiProps<Data>
response.value = null response.value = null
isLoading.value = true isLoading.value = true
addRequest()
return { return {
...config, ...config,
...unref(props), ...unref(props),
@ -61,6 +73,8 @@ export function useApi<Data = any, RequestConfig = any>(props: UseApiProps<Data>
response.value = null response.value = null
isLoading.value = false isLoading.value = false
removeRequest()
return requestError return requestError
}, },
) )
@ -72,6 +86,8 @@ export function useApi<Data = any, RequestConfig = any>(props: UseApiProps<Data>
response.value = apiResponse response.value = apiResponse
isLoading.value = false isLoading.value = false
removeRequest()
return apiResponse return apiResponse
}, },
(apiError) => { (apiError) => {
@ -79,6 +95,8 @@ export function useApi<Data = any, RequestConfig = any>(props: UseApiProps<Data>
error.value = apiError error.value = apiError
isLoading.value = false isLoading.value = false
removeRequest()
return apiError return apiError
}, },
) )

16
packages/nc-gui-v2/composables/useApi/interceptors.ts

@ -1,17 +1,17 @@
import type { Api } from 'nocodb-sdk' import type { Api } from 'nocodb-sdk'
import { navigateTo, useRoute, useRouter } from '#imports' import { navigateTo, useGlobal, useRoute, useRouter } from '#imports'
import type { NuxtApp } from '#app'
const DbNotFoundMsg = 'Database config not found' const DbNotFoundMsg = 'Database config not found'
export function addAxiosInterceptors(api: Api<any>, app: NuxtApp) { export function addAxiosInterceptors(api: Api<any>) {
const state = $(useGlobal())
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
api.instance.interceptors.request.use((config) => { api.instance.interceptors.request.use((config) => {
config.headers['xc-gui'] = 'true' config.headers['xc-gui'] = 'true'
if (app.$state.token.value) config.headers['xc-auth'] = app.$state.token.value if (state.token) config.headers['xc-auth'] = state.token
if (!config.url?.endsWith('/user/me') && !config.url?.endsWith('/admin/roles')) { if (!config.url?.endsWith('/user/me') && !config.url?.endsWith('/admin/roles')) {
// config.headers['xc-preview'] = store.state.users.previewAs // config.headers['xc-preview'] = store.state.users.previewAs
@ -38,7 +38,7 @@ export function addAxiosInterceptors(api: Api<any>, app: NuxtApp) {
// Logout user if token refresh didn't work or user is disabled // Logout user if token refresh didn't work or user is disabled
if (error.config.url === '/auth/refresh-token') { if (error.config.url === '/auth/refresh-token') {
app.$state.signOut() state.signOut()
return Promise.reject(error) return Promise.reject(error)
} }
@ -52,7 +52,7 @@ export function addAxiosInterceptors(api: Api<any>, app: NuxtApp) {
// New request with new token // New request with new token
const config = error.config const config = error.config
config.headers['xc-auth'] = token.data.token config.headers['xc-auth'] = token.data.token
app.$state.signIn(token.data.token) state.signIn(token.data.token)
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
api.instance api.instance
@ -66,7 +66,7 @@ export function addAxiosInterceptors(api: Api<any>, app: NuxtApp) {
}) })
}) })
.catch(async (error) => { .catch(async (error) => {
app.$state.signOut() state.signOut()
// todo: handle new user // todo: handle new user
navigateTo('/signIn') navigateTo('/signIn')
@ -75,4 +75,6 @@ export function addAxiosInterceptors(api: Api<any>, app: NuxtApp) {
}) })
}, },
) )
return api
} }

12
packages/nc-gui-v2/composables/useGlobal/getters.ts

@ -1,8 +1,7 @@
import type { Getters, State } from './types' import type { Getters, State } from './types'
import { computed } from '#imports' import { computed } from '#imports'
export function useGlobalGetters(state: State) { export function useGlobalGetters(state: State): Getters {
/** Getters */
/** Verify that a user is signed in by checking if token exists and is not expired */ /** Verify that a user is signed in by checking if token exists and is not expired */
const signedIn: Getters['signedIn'] = computed( const signedIn: Getters['signedIn'] = computed(
() => () =>
@ -15,5 +14,12 @@ export function useGlobalGetters(state: State) {
), ),
) )
return { signedIn } /** global loading state */
let loading = $ref(false)
const isLoading = computed({
get: () => state.runningRequests.value.length > 0 || loading,
set: (_loading) => (loading = _loading),
})
return { signedIn, isLoading }
} }

4
packages/nc-gui-v2/composables/useGlobal/state.ts

@ -68,9 +68,6 @@ export function useGlobalState(): State {
/** is sidebar open */ /** is sidebar open */
const sidebarOpen = ref(false) const sidebarOpen = ref(false)
/** global loading state */
const isLoading = ref(false)
/** currently running requests */ /** currently running requests */
const runningRequests = ref<number[]>([]) const runningRequests = ref<number[]>([])
@ -83,7 +80,6 @@ export function useGlobalState(): State {
jwtPayload: payload, jwtPayload: payload,
sidebarOpen, sidebarOpen,
timestamp, timestamp,
isLoading,
runningRequests, runningRequests,
error, error,
} }

4
packages/nc-gui-v2/composables/useGlobal/types.ts

@ -11,17 +11,17 @@ export interface StoredState {
} }
export type State = ToRefs<Omit<StoredState, 'token'>> & { export type State = ToRefs<Omit<StoredState, 'token'>> & {
token: WritableComputedRef<string | null> token: WritableComputedRef<string>
jwtPayload: ComputedRef<(JwtPayload & User) | null> jwtPayload: ComputedRef<(JwtPayload & User) | null>
sidebarOpen: Ref<boolean> sidebarOpen: Ref<boolean>
timestamp: Ref<number> timestamp: Ref<number>
isLoading: Ref<boolean>
runningRequests: Ref<number[]> runningRequests: Ref<number[]>
error: Ref<any> error: Ref<any>
} }
export interface Getters { export interface Getters {
signedIn: ComputedRef<boolean> signedIn: ComputedRef<boolean>
isLoading: WritableComputedRef<boolean>
} }
export interface Actions { export interface Actions {

5
packages/nc-gui-v2/plugins/api.ts

@ -1,7 +1,6 @@
import { defineNuxtPlugin } from '#imports' import { defineNuxtPlugin, useApi } from '#imports'
import { createApiInstance } from '~/composables/useApi'
export default defineNuxtPlugin((nuxtApp) => { export default defineNuxtPlugin((nuxtApp) => {
/** injects a global api instance */ /** injects a global api instance */
nuxtApp.provide('api', createApiInstance(nuxtApp)) nuxtApp.provide('api', useApi().api)
}) })

Loading…
Cancel
Save