From 47ce579f3975e124f361461e93e9916aa3f74849 Mon Sep 17 00:00:00 2001
From: aliang <1098486429@qq.com>
Date: Wed, 20 Mar 2024 14:52:39 +0800
Subject: [PATCH] refactor(ui): Optimize LoadingWrapper experience (#1692)
* add debounce in loading-wrapper
* table
* [autofix.ci] apply automated fixes
* update
* update
* [autofix.ci] apply automated fixes
* general form
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
---
.../git/components/repository-table.tsx | 133 +++++-----
.../sso/components/oauth-credential-list.tsx | 18 +-
.../general/components/network-form.tsx | 14 +-
.../general/components/security-form.tsx | 11 +-
.../team/components/invitation-table.tsx | 75 +++---
.../settings/team/components/user-table.tsx | 247 +++++++++---------
ee/tabby-ui/components/loading-wrapper.tsx | 11 +-
ee/tabby-ui/components/skeleton.tsx | 18 +-
ee/tabby-ui/lib/hooks/use-debounce.ts | 4 +-
ee/tabby-ui/lib/hooks/use-network-setting.tsx | 4 +-
10 files changed, 285 insertions(+), 250 deletions(-)
diff --git a/ee/tabby-ui/app/(dashboard)/settings/(integrations)/git/components/repository-table.tsx b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/git/components/repository-table.tsx
index a12d903a923f..492522aea745 100644
--- a/ee/tabby-ui/app/(dashboard)/settings/(integrations)/git/components/repository-table.tsx
+++ b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/git/components/repository-table.tsx
@@ -10,7 +10,7 @@ import {
RepositoriesQueryVariables,
RepositoryEdge
} from '@/lib/gql/generates/graphql'
-import { useIsQueryInitialized, useMutation } from '@/lib/tabby/gql'
+import { useMutation } from '@/lib/tabby/gql'
import { listRepositories } from '@/lib/tabby/query'
import { Button } from '@/components/ui/button'
import { IconTrash } from '@/components/ui/icons'
@@ -29,7 +29,7 @@ import {
TableHeader,
TableRow
} from '@/components/ui/table'
-import { ListSkeleton } from '@/components/skeleton'
+import LoadingWrapper from '@/components/loading-wrapper'
const deleteRepositoryMutation = graphql(/* GraphQL */ `
mutation deleteRepository($id: ID!) {
@@ -40,11 +40,10 @@ const deleteRepositoryMutation = graphql(/* GraphQL */ `
const PAGE_SIZE = DEFAULT_PAGE_SIZE
export default function RepositoryTable() {
const client = useClient()
- const [{ data, error, fetching, stale }] = useQuery({
+ const [{ data, fetching }] = useQuery({
query: listRepositories,
variables: { first: PAGE_SIZE }
})
- const [initialized] = useIsQueryInitialized({ data, error, stale })
const [currentPage, setCurrentPage] = React.useState(1)
const edges = data?.repositories?.edges
@@ -115,75 +114,65 @@ export default function RepositoryTable() {
}, [pageNum, currentPage])
return (
-
- {initialized ? (
- <>
-
-
-
- Name
- Git URL
-
-
-
-
- {!currentPageRepos?.length && currentPage === 1 ? (
-
-
- No Data
-
-
- ) : (
- <>
- {currentPageRepos?.map(x => {
- return (
-
-
- {x.node.name}
-
-
- {x.node.gitUrl}
-
-
-
-
-
-
-
- )
- })}
- >
- )}
-
-
- {showPagination && (
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+ Name
+ Git URL
+
+
+
+
+ {!currentPageRepos?.length && currentPage === 1 ? (
+
+
+ No Data
+
+
+ ) : (
+ <>
+ {currentPageRepos?.map(x => {
+ return (
+
+ {x.node.name}
+ {x.node.gitUrl}
+
+
+
+
+
+
+ )
+ })}
+ >
)}
- >
- ) : (
-
+
+
+ {showPagination && (
+
+
+
+
+
+
+
+
+
+
)}
-
+
)
}
diff --git a/ee/tabby-ui/app/(dashboard)/settings/(integrations)/sso/components/oauth-credential-list.tsx b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/sso/components/oauth-credential-list.tsx
index 820d4bdbe9d3..e09e7a18b22b 100644
--- a/ee/tabby-ui/app/(dashboard)/settings/(integrations)/sso/components/oauth-credential-list.tsx
+++ b/ee/tabby-ui/app/(dashboard)/settings/(integrations)/sso/components/oauth-credential-list.tsx
@@ -16,6 +16,7 @@ import { Button, buttonVariants } from '@/components/ui/button'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Skeleton } from '@/components/ui/skeleton'
import { LicenseGuard } from '@/components/license-guard'
+import LoadingWrapper from '@/components/loading-wrapper'
import { PROVIDER_METAS } from './constant'
import { SSOHeader } from './sso-header'
@@ -64,12 +65,15 @@ const OAuthCredentialList = () => {
return (
- {isLoading ? (
-
-
-
-
- ) : (
+
+
+
+
+ }
+ >
No Data
@@ -81,7 +85,7 @@ const OAuthCredentialList = () => {
- )}
+
)
}
diff --git a/ee/tabby-ui/app/(dashboard)/settings/general/components/network-form.tsx b/ee/tabby-ui/app/(dashboard)/settings/general/components/network-form.tsx
index 5fe54da59da8..95ba4a3c8ec8 100644
--- a/ee/tabby-ui/app/(dashboard)/settings/general/components/network-form.tsx
+++ b/ee/tabby-ui/app/(dashboard)/settings/general/components/network-form.tsx
@@ -21,6 +21,7 @@ import {
FormMessage
} from '@/components/ui/form'
import { Input } from '@/components/ui/input'
+import LoadingWrapper from '@/components/loading-wrapper'
import { FormSkeleton } from '@/components/skeleton'
const updateNetworkSettingMutation = graphql(/* GraphQL */ `
@@ -114,9 +115,14 @@ export const GeneralNetworkForm = () => {
reexecuteQuery()
}
- return data && !stale ? (
-
- ) : (
-
+ return (
+
+ }>
+
+
+
)
}
diff --git a/ee/tabby-ui/app/(dashboard)/settings/general/components/security-form.tsx b/ee/tabby-ui/app/(dashboard)/settings/general/components/security-form.tsx
index e822d7103e4f..1a5ad47e8462 100644
--- a/ee/tabby-ui/app/(dashboard)/settings/general/components/security-form.tsx
+++ b/ee/tabby-ui/app/(dashboard)/settings/general/components/security-form.tsx
@@ -26,6 +26,7 @@ import {
import { IconTrash } from '@/components/ui/icons'
import { Input } from '@/components/ui/input'
import { LicenseGuard } from '@/components/license-guard'
+import LoadingWrapper from '@/components/loading-wrapper'
import { FormSkeleton } from '@/components/skeleton'
const updateSecuritySettingMutation = graphql(/* GraphQL */ `
@@ -227,7 +228,7 @@ function buildListValuesFromField(fieldListValue?: Array<{ value: string }>) {
}
export const GeneralSecurityForm = () => {
- const [{ data, stale }, reexecuteQuery] = useQuery({
+ const [{ data, stale, fetching }, reexecuteQuery] = useQuery({
query: securitySetting
})
const onSuccess = () => {
@@ -241,9 +242,9 @@ export const GeneralSecurityForm = () => {
)
}
- return data && !stale ? (
-
- ) : (
-
+ return (
+ }>
+
+
)
}
diff --git a/ee/tabby-ui/app/(dashboard)/settings/team/components/invitation-table.tsx b/ee/tabby-ui/app/(dashboard)/settings/team/components/invitation-table.tsx
index fea134592a2d..bfc240c79dd1 100644
--- a/ee/tabby-ui/app/(dashboard)/settings/team/components/invitation-table.tsx
+++ b/ee/tabby-ui/app/(dashboard)/settings/team/components/invitation-table.tsx
@@ -32,6 +32,7 @@ import {
TableRow
} from '@/components/ui/table'
import { CopyButton } from '@/components/copy-button'
+import LoadingWrapper from '@/components/loading-wrapper'
import CreateInvitationForm from './create-invitation-form'
@@ -148,40 +149,46 @@ export default function InvitationTable() {
return (
-
- {!!currentPageInvits?.length && (
-
-
- Invitee
- Created
-
-
-
- )}
-
- {currentPageInvits?.map(x => {
- const link = `${externalUrl}/auth/signup?invitationCode=${x.node.code}`
- return (
-
- {x.node.email}
- {moment.utc(x.node.createdAt).fromNow()}
-
-
-
-
-
-
-
- )
- })}
-
-
+
+
+
+ {!!currentPageInvits?.length && (
+
+
+ Invitee
+ Created
+
+
+
+ )}
+
+ {currentPageInvits?.map(x => {
+ const link = `${externalUrl}/auth/signup?invitationCode=${x.node.code}`
+ return (
+
+ {x.node.email}
+
+ {moment.utc(x.node.createdAt).fromNow()}
+
+
+
+
+
+
+
+
+ )
+ })}
+
+
+
+
{(hasNextPage || hasPrevPage) && (
diff --git a/ee/tabby-ui/app/(dashboard)/settings/team/components/user-table.tsx b/ee/tabby-ui/app/(dashboard)/settings/team/components/user-table.tsx
index d447d1f1b2c8..a4c230f18510 100644
--- a/ee/tabby-ui/app/(dashboard)/settings/team/components/user-table.tsx
+++ b/ee/tabby-ui/app/(dashboard)/settings/team/components/user-table.tsx
@@ -35,6 +35,7 @@ import {
TableHeader,
TableRow
} from '@/components/ui/table'
+import LoadingWrapper from '@/components/loading-wrapper'
import { UpdateUserRoleDialog } from './user-role-dialog'
@@ -76,7 +77,7 @@ export default function UsersTable() {
const [queryVariables, setQueryVariables] = React.useState<
QueryVariables
>({ first: PAGE_SIZE })
- const [{ data, error }, reexecuteQuery] = useQuery({
+ const [{ data, error, fetching }, reexecuteQuery] = useQuery({
query: listUsers,
variables: queryVariables
})
@@ -131,125 +132,135 @@ export default function UsersTable() {
)
return (
- !!users?.edges?.length && (
- <>
-
-
-
- Email
- Joined
- Status
- Level
-
-
-
-
- {users.edges.map(x => {
- const showOperation =
- !x.node.isOwner && me?.me && x.node.id !== me.me.id
-
- return (
-
- {x.node.email}
-
- {moment.utc(x.node.createdAt).fromNow()}
-
-
- {x.node.active ? (
- Active
- ) : (
- Inactive
- )}
-
-
- {makeBadge(x.node)}
-
-
- {showOperation && (
-
-
-
-
-
- {!!x.node.active && (
- onUpdateUserRole(x.node)}
- className="cursor-pointer"
- >
-
- {x.node.isAdmin
- ? 'Downgrade to member'
- : 'Upgrade to admin'}
-
-
- )}
- {!!x.node.active && (
- onUpdateUserActive(x.node, false)}
- className="cursor-pointer"
- >
- Deactivate
-
- )}
- {!x.node.active && (
- onUpdateUserActive(x.node, true)}
- className="cursor-pointer"
- >
- Activate
-
- )}
-
-
- )}
-
+ <>
+
+ {!!users?.edges?.length && (
+ <>
+
+
+
+ Email
+ Joined
+ Status
+ Level
+
- )
- })}
-
-
- {(pageInfo?.hasNextPage || pageInfo?.hasPreviousPage) && (
-
-
-
-
- setQueryVariables({
- last: PAGE_SIZE,
- before: pageInfo?.startCursor
- })
- }
- />
-
-
-
- setQueryVariables({
- first: PAGE_SIZE,
- after: pageInfo?.endCursor
- })
- }
- />
-
-
-
+
+
+ {users.edges.map(x => {
+ const showOperation =
+ !x.node.isOwner && me?.me && x.node.id !== me.me.id
+
+ return (
+
+ {x.node.email}
+
+ {moment.utc(x.node.createdAt).fromNow()}
+
+
+ {x.node.active ? (
+ Active
+ ) : (
+ Inactive
+ )}
+
+
+ {makeBadge(x.node)}
+
+
+ {showOperation && (
+
+
+
+
+
+ {!!x.node.active && (
+ onUpdateUserRole(x.node)}
+ className="cursor-pointer"
+ >
+
+ {x.node.isAdmin
+ ? 'Downgrade to member'
+ : 'Upgrade to admin'}
+
+
+ )}
+ {!!x.node.active && (
+
+ onUpdateUserActive(x.node, false)
+ }
+ className="cursor-pointer"
+ >
+ Deactivate
+
+ )}
+ {!x.node.active && (
+
+ onUpdateUserActive(x.node, true)
+ }
+ className="cursor-pointer"
+ >
+ Activate
+
+ )}
+
+
+ )}
+
+
+ )
+ })}
+
+
+ {(pageInfo?.hasNextPage || pageInfo?.hasPreviousPage) && (
+
+
+
+
+ setQueryVariables({
+ last: PAGE_SIZE,
+ before: pageInfo?.startCursor
+ })
+ }
+ />
+
+
+
+ setQueryVariables({
+ first: PAGE_SIZE,
+ after: pageInfo?.endCursor
+ })
+ }
+ />
+
+
+
+ )}
+ >
)}
+
- {
- reexecuteQuery()
- setUpdateRoleVisible(false)
- }}
- user={currentUser}
- isPromote={isPromote}
- open={updateRoleVisible}
- onOpenChange={setUpdateRoleVisible}
- />
- >
- )
+ {
+ reexecuteQuery()
+ setUpdateRoleVisible(false)
+ }}
+ user={currentUser}
+ isPromote={isPromote}
+ open={updateRoleVisible}
+ onOpenChange={setUpdateRoleVisible}
+ />
+ >
)
}
diff --git a/ee/tabby-ui/components/loading-wrapper.tsx b/ee/tabby-ui/components/loading-wrapper.tsx
index 0bf19559ae6f..7c2ac01e43f3 100644
--- a/ee/tabby-ui/components/loading-wrapper.tsx
+++ b/ee/tabby-ui/components/loading-wrapper.tsx
@@ -2,18 +2,25 @@
import React from 'react'
+import { useDebounceValue } from '@/lib/hooks/use-debounce'
+
+import { ListSkeleton } from './skeleton'
+
interface LoadingWrapperProps {
loading?: boolean
children?: React.ReactNode
fallback?: React.ReactNode
+ delay?: number
}
export const LoadingWrapper: React.FC = ({
loading,
fallback,
+ delay,
children
}) => {
const [loaded, setLoaded] = React.useState(!loading)
+ const [debouncedLoaded] = useDebounceValue(loaded, delay ?? 200)
React.useEffect(() => {
if (!loading && !loaded) {
@@ -21,8 +28,8 @@ export const LoadingWrapper: React.FC = ({
}
}, [loading])
- if (!loaded) {
- return fallback
+ if (!debouncedLoaded) {
+ return fallback ? fallback :
} else {
return children
}
diff --git a/ee/tabby-ui/components/skeleton.tsx b/ee/tabby-ui/components/skeleton.tsx
index 0c5cfc940453..e985f9c4f480 100644
--- a/ee/tabby-ui/components/skeleton.tsx
+++ b/ee/tabby-ui/components/skeleton.tsx
@@ -1,10 +1,17 @@
'use client'
+import { HTMLAttributes } from 'react'
+
+import { cn } from '@/lib/utils'
+
import { Skeleton } from './ui/skeleton'
-export const ListSkeleton = () => {
+export const ListSkeleton: React.FC> = ({
+ className,
+ ...props
+}) => {
return (
-
+
@@ -13,9 +20,12 @@ export const ListSkeleton = () => {
)
}
-export const FormSkeleton = () => {
+export const FormSkeleton: React.FC
> = ({
+ className,
+ ...props
+}) => {
return (
-
+
diff --git a/ee/tabby-ui/lib/hooks/use-debounce.ts b/ee/tabby-ui/lib/hooks/use-debounce.ts
index 180711764893..534e55426e95 100644
--- a/ee/tabby-ui/lib/hooks/use-debounce.ts
+++ b/ee/tabby-ui/lib/hooks/use-debounce.ts
@@ -12,7 +12,7 @@ type noop = (...args: any[]) => any
function useDebounceCallback(
fn: T,
- wait: number,
+ wait?: number,
options?: DebounceSettings
) {
const fnRef = useLatest(fn)
@@ -39,7 +39,7 @@ function useDebounceCallback(
function useDebounceValue(
value: T,
- wait: number,
+ wait?: number,
options?: DebounceSettings
): [T, React.Dispatch>] {
const [debouncedValue, setDebouncedValue] = React.useState(value)
diff --git a/ee/tabby-ui/lib/hooks/use-network-setting.tsx b/ee/tabby-ui/lib/hooks/use-network-setting.tsx
index d7fde046e265..d4097eb0fb50 100644
--- a/ee/tabby-ui/lib/hooks/use-network-setting.tsx
+++ b/ee/tabby-ui/lib/hooks/use-network-setting.tsx
@@ -12,8 +12,8 @@ const networkSettingQuery = graphql(/* GraphQL */ `
}
`)
-const useNetworkSetting = () => {
- return useQuery({ query: networkSettingQuery })
+const useNetworkSetting = (options?: any) => {
+ return useQuery({ query: networkSettingQuery, ...options })
}
const useExternalURL = () => {