fix(fe2): ugly function card hydration issue (#2381)

* fix(fe2): ugly function card hydration issue

* comment adjustment

* minor optimization
This commit is contained in:
Kristaps Fabians Geikins 2024-06-18 08:40:25 +03:00 коммит произвёл GitHub
Родитель 84ac3e4598
Коммит ef4bb520cf
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
12 изменённых файлов: 913 добавлений и 55 удалений

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -12,19 +12,30 @@
<div class="flex gap-3 items-center" :class="{ 'w-4/5': hasLabel }">
<AutomateFunctionLogo :logo="fn.logo" />
<div class="flex flex-col truncate">
<div class="normal font-semibold text-foreground truncate hover:underline">
<RouterLink
<div
:class="[
'normal font-semibold text-foreground truncate',
noButtons ? '' : 'hover:underline'
]"
>
<Component
:is="noButtons ? 'div' : NuxtLink"
:to="automationFunctionRoute(fn.id)"
:target="externalMoreInfo ? '_blank' : undefined"
>
{{ fn.name }}
</RouterLink>
</Component>
</div>
<div class="label-light flex items-center space-x-1">
<span>by</span>
<CommonTextLink external :to="fn.repo.url" size="sm">
<Component
:is="noButtons ? 'div' : CommonTextLink"
external
:to="fn.repo.url"
size="sm"
>
{{ fn.repo.owner }}
</CommonTextLink>
</Component>
</div>
</div>
</div>
@ -81,6 +92,7 @@ import type { AutomationsFunctionsCard_AutomateFunctionFragment } from '~/lib/co
import { CheckIcon, PencilIcon } from '@heroicons/vue/24/outline'
import { automationFunctionRoute } from '~/lib/common/helpers/route'
import { useMarkdown } from '~/lib/common/composables/markdown'
import { CommonTextLink } from '@speckle/ui-components'
graphql(`
fragment AutomationsFunctionsCard_AutomateFunction on AutomateFunction {

Просмотреть файл

@ -58,6 +58,7 @@ const route = useRoute()
const projectId = computed(() => route.params.id as string)
const search = ref('')
const isAutomateEnabled = useIsAutomateModuleEnabled()
const pageFetchPolicy = usePageQueryStandardFetchPolicy()
// Base tab query (no pagination)
const { result, loading } = useQuery(
@ -69,7 +70,7 @@ const { result, loading } = useQuery(
}),
() => ({
enabled: isAutomateEnabled.value,
fetchPolicy: 'cache-and-network'
fetchPolicy: pageFetchPolicy.value
})
)

Просмотреть файл

@ -1,4 +1,5 @@
import { useActiveUser } from '~/lib/auth/composables/activeUser'
import { usePageQueryStandardFetchPolicy } from '~/lib/common/composables/graphql'
import { useGlobalToast } from '~/lib/common/composables/toast'
export const useIsAutomateModuleEnabled = () => {
@ -16,4 +17,4 @@ export const useIsGendoModuleEnabled = () => {
return ref(FF_GENDOAI_MODULE_ENABLED)
}
export { useGlobalToast, useActiveUser }
export { useGlobalToast, useActiveUser, usePageQueryStandardFetchPolicy }

Просмотреть файл

@ -1,5 +1,9 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { OperationVariables, QueryOptions } from '@apollo/client/core'
import type {
OperationVariables,
QueryOptions,
WatchQueryFetchPolicy
} from '@apollo/client/core'
import type {
DocumentParameter,
OptionsParameter
@ -8,7 +12,8 @@ import { useQuery } from '@vue/apollo-composable'
import { convertThrowIntoFetchResult } from '~/lib/common/helpers/graphql'
import type { InfiniteLoaderState } from '@speckle/ui-components'
import { isUndefined } from 'lodash-es'
import type { MaybeNullOrUndefined } from '@speckle/shared'
import type { MaybeNullOrUndefined, Optional } from '@speckle/shared'
import { useScopedState } from '~/lib/common/composables/scopedState'
export const useApolloClientIfAvailable = () => {
const nuxt = useNuxtApp()
@ -205,3 +210,33 @@ export const usePaginatedQuery = <
bustCache
}
}
/**
* We want our page queries to have the cache-and-network fetch policy, so that when you switch to a new page, the data
* gets refreshed, but in the background - while the old data is still shown.
*
* This, however, is unnecessary when hydrating the SSR page in CSR for the first time, and also
* causes weird hydration mismatches.
*
* So this sets the correct fetch policy based on whether this is a CSR->CSR navigation
*/
export const usePageQueryStandardFetchPolicy = () => {
if (import.meta.server) return computed(() => undefined)
const router = useRouter()
const hasNavigatedInCSR = useScopedState(
'usePageQueryStandardFetchPolicy-state',
() => ref(false)
)
const quitTracking = router.beforeEach((to, from) => {
if (!from || !to) return
hasNavigatedInCSR.value = true
quitTracking()
})
return computed((): Optional<WatchQueryFetchPolicy> => {
// use cache, but reload in background
// we only wanna do this when transitioning between CSR routes
return hasNavigatedInCSR.value ? 'cache-and-network' : undefined
})
}

Просмотреть файл

@ -60,8 +60,7 @@ const pageQuery = graphql(`
definePageMeta({
middleware: ['auth', 'require-valid-function']
})
// const { activeUser } = useActiveUser()
const pageFetchPolicy = usePageQueryStandardFetchPolicy()
const route = useRoute()
const functionId = computed(() => route.params.fid as string)
const loading = useQueryLoading()
@ -70,9 +69,9 @@ const { result, onResult } = useQuery(
() => ({
functionId: functionId.value
}),
{
fetchPolicy: 'cache-and-network'
}
() => ({
fetchPolicy: pageFetchPolicy.value
})
)
const queryLoadedOnce = useQueryLoaded({ onResult })

Просмотреть файл

@ -4,9 +4,9 @@
v-model:search="search"
:active-user="result?.activeUser"
:server-info="result?.serverInfo"
class="mb-8"
class="mb-6"
/>
<CommonLoadingBar :loading="pageQueryLoading" />
<CommonLoadingBar :loading="pageQueryLoading" client-only class="mb-2" />
<AutomateFunctionsPageItems
:functions="finalResult"
:search="!!search"
@ -27,7 +27,10 @@ import { CommonLoadingBar } from '@speckle/ui-components'
import { useQuery } from '@vue/apollo-composable'
import { automateFunctionsPagePaginationQuery } from '~/lib/automate/graphql/queries'
import type { CreateAutomationSelectableFunction } from '~/lib/automate/helpers/automations'
import { usePaginatedQuery } from '~/lib/common/composables/graphql'
import {
usePageQueryStandardFetchPolicy,
usePaginatedQuery
} from '~/lib/common/composables/graphql'
import { graphql } from '~/lib/common/generated/gql'
definePageMeta({
@ -42,14 +45,15 @@ const pageQuery = graphql(`
`)
const search = ref('')
const pageFetchPolicy = usePageQueryStandardFetchPolicy()
const { result, loading: pageQueryLoading } = useQuery(
pageQuery,
() => ({
search: search.value?.length ? search.value : null
}),
{
fetchPolicy: 'cache-and-network'
}
() => ({
fetchPolicy: pageFetchPolicy.value
})
)
const {
@ -69,9 +73,9 @@ const {
cursor
}),
resolveCursorFromVariables: (vars) => vars.cursor,
options: {
fetchPolicy: 'cache-and-network'
}
options: () => ({
fetchPolicy: pageFetchPolicy.value
})
})
const showNewAutomationDialog = ref(false)

Просмотреть файл

@ -74,6 +74,7 @@ const projectId = computed(() => route.params.id as string)
const shouldAutoAcceptInvite = computed(() => route.query.accept === 'true')
const token = computed(() => route.query.token as Optional<string>)
const pageFetchPolicy = usePageQueryStandardFetchPolicy()
useGeneralProjectPageUpdateTracking({ projectId }, { notifyOnProjectUpdate: true })
const { result: projectPageResult } = useQuery(
projectPageQuery,
@ -82,7 +83,7 @@ const { result: projectPageResult } = useQuery(
...(token.value?.length ? { token: token.value } : {})
}),
() => ({
fetchPolicy: 'cache-and-network',
fetchPolicy: pageFetchPolicy.value,
// Custom error policy so that a failing invitedTeam resolver (due to access rights)
// doesn't kill the entire query
errorPolicy: 'all'

Просмотреть файл

@ -43,6 +43,7 @@ graphql(`
}
`)
const pageFetchPolicy = usePageQueryStandardFetchPolicy()
const route = useRoute()
const projectId = computed(() => route.params.id as string)
const automationId = computed(() => route.params.aid as string)
@ -53,9 +54,9 @@ const { result, loading } = useQuery(
projectId: projectId.value,
automationId: automationId.value
}),
{
fetchPolicy: 'cache-and-network'
}
() => ({
fetchPolicy: pageFetchPolicy.value
})
)
const automation = computed(() => result.value?.project.automation || null)
const project = computed(() => result.value?.project)

Просмотреть файл

@ -19,7 +19,7 @@ REDIS_URL="redis://127.0.0.1:6379"
############################################################
# Whether server is meant to be used with Frontend 2.0
USE_FRONTEND_2=false
USE_FRONTEND_2=true
FRONTEND_ORIGIN="http://127.0.0.1:8081"
# URL of a project on any FE2 speckle server that will be pulled in and used as the onboarding stream

Просмотреть файл

@ -2,14 +2,20 @@
<div
:class="[
'relative w-full h-1 bg-blue-500/30 text-xs text-foreground-on-primary overflow-hidden rounded-xl',
loading ? 'opacity-100' : 'opacity-0'
showBar ? 'opacity-100' : 'opacity-0'
]"
>
<div class="swoosher relative top-0 bg-blue-500/50"></div>
</div>
</template>
<script setup lang="ts">
defineProps<{ loading: boolean }>()
import { useMounted } from '@vueuse/core'
import { computed } from 'vue'
const props = defineProps<{ loading: boolean; clientOnly?: boolean }>()
const mounted = useMounted()
const showBar = computed(() => (mounted.value || !props.clientOnly) && props.loading)
</script>
<style scoped>
.swoosher {

Просмотреть файл

@ -61,14 +61,15 @@ const resolvePackageContexts = async (absoluteFileNames) => {
throw new Error(`File ${absoluteFileName} does not belong to any package`)
}
if (!contexts.has(pkg.absolutePath)) {
contexts.set(pkg, new Set())
const contextsKey = pkg.absolutePath
if (!contexts.has(contextsKey)) {
contexts.set(contextsKey, new Set())
}
contexts.get(pkg).add(absoluteFileName)
contexts.get(contextsKey).add(absoluteFileName)
}
return [...contexts.entries()].map(([pkg, files]) => ({
absolutePath: pkg.absolutePath,
return [...contexts.entries()].map(([pkgPath, files]) => ({
absolutePath: pkgPath,
files: [...files]
}))
}