fix(fe2): ugly function card hydration issue (#2381)
* fix(fe2): ugly function card hydration issue * comment adjustment * minor optimization
This commit is contained in:
Родитель
84ac3e4598
Коммит
ef4bb520cf
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -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]
|
||||
}))
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче