diff --git a/packages/frontend/admin/package.json b/packages/frontend/admin/package.json index 9f969a735ef71..c3c97b72cf6c2 100644 --- a/packages/frontend/admin/package.json +++ b/packages/frontend/admin/package.json @@ -66,7 +66,6 @@ "update-shadcn": "shadcn-ui add -p src/components/ui" }, "exports": { - "./utils": "./src/utils.ts", - "./components/ui/*": "./src/components/ui/*.tsx" + "./*": "./src/*" } } diff --git a/packages/frontend/admin/src/app.tsx b/packages/frontend/admin/src/app.tsx index c17f25fe0958e..199452062ed25 100644 --- a/packages/frontend/admin/src/app.tsx +++ b/packages/frontend/admin/src/app.tsx @@ -1,20 +1,5 @@ import { Toaster } from '@affine/admin/components/ui/sonner'; -import { - configureCloudModule, - DefaultServerService, -} from '@affine/core/modules/cloud'; -import { configureLocalStorageStateStorageImpls } from '@affine/core/modules/storage'; -import { configureUrlModule } from '@affine/core/modules/url'; import { wrapCreateBrowserRouter } from '@sentry/react'; -import { - configureGlobalContextModule, - configureGlobalStorageModule, - configureLifecycleModule, - Framework, - FrameworkRoot, - FrameworkScope, - LifecycleService, -} from '@toeverything/infra'; import { useEffect } from 'react'; import { createBrowserRouter as reactRouterCreateBrowserRouter, @@ -124,38 +109,18 @@ export const router = _createBrowserRouter( } ); -const framework = new Framework(); -configureLifecycleModule(framework); -configureLocalStorageStateStorageImpls(framework); -configureGlobalStorageModule(framework); -configureGlobalContextModule(framework); -configureUrlModule(framework); -configureCloudModule(framework); -const frameworkProvider = framework.provider(); - -// setup application lifecycle events, and emit application start event -window.addEventListener('focus', () => { - frameworkProvider.get(LifecycleService).applicationFocus(); -}); -frameworkProvider.get(LifecycleService).applicationStart(); -const serverService = frameworkProvider.get(DefaultServerService); - export const App = () => { return ( - - - - - - - - - - + + + + + + ); }; diff --git a/packages/frontend/admin/src/modules/accounts/components/data-table-toolbar.tsx b/packages/frontend/admin/src/modules/accounts/components/data-table-toolbar.tsx index 0a9fb406f52e9..270bd82e02f3d 100644 --- a/packages/frontend/admin/src/modules/accounts/components/data-table-toolbar.tsx +++ b/packages/frontend/admin/src/modules/accounts/components/data-table-toolbar.tsx @@ -1,6 +1,6 @@ import { Button } from '@affine/admin/components/ui/button'; import { Input } from '@affine/admin/components/ui/input'; -import { useQuery } from '@affine/core/components/hooks/use-query'; +import { useQuery } from '@affine/admin/use-query'; import { getUserByEmailQuery } from '@affine/graphql'; import { PlusIcon } from 'lucide-react'; import type { SetStateAction } from 'react'; diff --git a/packages/frontend/admin/src/modules/accounts/components/use-user-management.ts b/packages/frontend/admin/src/modules/accounts/components/use-user-management.ts index f062ecf4c4331..94ee0bcca5c50 100644 --- a/packages/frontend/admin/src/modules/accounts/components/use-user-management.ts +++ b/packages/frontend/admin/src/modules/accounts/components/use-user-management.ts @@ -1,9 +1,9 @@ -import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { useMutateQueryResource, useMutation, -} from '@affine/core/components/hooks/use-mutation'; -import { useQuery } from '@affine/core/components/hooks/use-query'; +} from '@affine/admin/use-mutation'; +import { useQuery } from '@affine/admin/use-query'; +import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { createChangePasswordUrlMutation, createUserMutation, diff --git a/packages/frontend/admin/src/modules/accounts/use-user-list.ts b/packages/frontend/admin/src/modules/accounts/use-user-list.ts index 58656010c2199..6b1abeaadfa26 100644 --- a/packages/frontend/admin/src/modules/accounts/use-user-list.ts +++ b/packages/frontend/admin/src/modules/accounts/use-user-list.ts @@ -1,4 +1,4 @@ -import { useQuery } from '@affine/core/components/hooks/use-query'; +import { useQuery } from '@affine/admin/use-query'; import { listUsersQuery } from '@affine/graphql'; import { useState } from 'react'; diff --git a/packages/frontend/admin/src/modules/ai/use-prompt.ts b/packages/frontend/admin/src/modules/ai/use-prompt.ts index cc41bcfa1c6ca..3c84273c6c6a6 100644 --- a/packages/frontend/admin/src/modules/ai/use-prompt.ts +++ b/packages/frontend/admin/src/modules/ai/use-prompt.ts @@ -1,9 +1,9 @@ -import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { useMutateQueryResource, useMutation, -} from '@affine/core/components/hooks/use-mutation'; -import { useQuery } from '@affine/core/components/hooks/use-query'; +} from '@affine/admin/use-mutation'; +import { useQuery } from '@affine/admin/use-query'; +import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { getPromptsQuery, updatePromptMutation } from '@affine/graphql'; import { toast } from 'sonner'; diff --git a/packages/frontend/admin/src/modules/common.ts b/packages/frontend/admin/src/modules/common.ts index 2447cb6c32eb0..046e44fc98448 100644 --- a/packages/frontend/admin/src/modules/common.ts +++ b/packages/frontend/admin/src/modules/common.ts @@ -1,5 +1,3 @@ -import { useMutateQueryResource } from '@affine/core/components/hooks/use-mutation'; -import { useQuery } from '@affine/core/components/hooks/use-query'; import type { GetCurrentUserFeaturesQuery } from '@affine/graphql'; import { adminServerConfigQuery, @@ -7,6 +5,9 @@ import { getCurrentUserFeaturesQuery, } from '@affine/graphql'; +import { useMutateQueryResource } from '../use-mutation'; +import { useQuery } from '../use-query'; + export const useServerConfig = () => { const { data } = useQuery({ query: adminServerConfigQuery, diff --git a/packages/frontend/admin/src/modules/config/use-server-service-configs.ts b/packages/frontend/admin/src/modules/config/use-server-service-configs.ts index a027e3598bf98..bcd439ca0f81e 100644 --- a/packages/frontend/admin/src/modules/config/use-server-service-configs.ts +++ b/packages/frontend/admin/src/modules/config/use-server-service-configs.ts @@ -1,4 +1,4 @@ -import { useQueryImmutable } from '@affine/core/components/hooks/use-query'; +import { useQueryImmutable } from '@affine/admin/use-query'; import { getServerServiceConfigsQuery } from '@affine/graphql'; import { useMemo } from 'react'; diff --git a/packages/frontend/admin/src/modules/settings/use-get-server-runtime-config.ts b/packages/frontend/admin/src/modules/settings/use-get-server-runtime-config.ts index 594efa58fff10..cc0988d399470 100644 --- a/packages/frontend/admin/src/modules/settings/use-get-server-runtime-config.ts +++ b/packages/frontend/admin/src/modules/settings/use-get-server-runtime-config.ts @@ -1,4 +1,4 @@ -import { useQuery } from '@affine/core/components/hooks/use-query'; +import { useQuery } from '@affine/admin/use-query'; import { getServerRuntimeConfigQuery } from '@affine/graphql'; import { useMemo } from 'react'; diff --git a/packages/frontend/admin/src/modules/settings/use-update-server-runtime-config.ts b/packages/frontend/admin/src/modules/settings/use-update-server-runtime-config.ts index 3c7232fd68b04..7c10f921505b1 100644 --- a/packages/frontend/admin/src/modules/settings/use-update-server-runtime-config.ts +++ b/packages/frontend/admin/src/modules/settings/use-update-server-runtime-config.ts @@ -1,9 +1,9 @@ -import { notify } from '@affine/component'; -import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { useMutateQueryResource, useMutation, -} from '@affine/core/components/hooks/use-mutation'; +} from '@affine/admin/use-mutation'; +import { notify } from '@affine/component'; +import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { getServerRuntimeConfigQuery, updateServerRuntimeConfigsMutation, diff --git a/packages/frontend/admin/src/use-mutation.ts b/packages/frontend/admin/src/use-mutation.ts new file mode 100644 index 0000000000000..b17150e5fffc5 --- /dev/null +++ b/packages/frontend/admin/src/use-mutation.ts @@ -0,0 +1,94 @@ +import type { + GraphQLQuery, + MutationOptions, + QueryResponse, + QueryVariables, + RecursiveMaybeFields, +} from '@affine/graphql'; +import type { GraphQLError } from 'graphql'; +import { useMemo } from 'react'; +import type { Key } from 'swr'; +import { useSWRConfig } from 'swr'; +import type { + SWRMutationConfiguration, + SWRMutationResponse, +} from 'swr/mutation'; +import useSWRMutation from 'swr/mutation'; + +import { gqlFetcher } from './use-query'; + +/** + * A useSWRMutation wrapper for sending graphql mutations + * + * @example + * + * ```ts + * import { someMutation } from '@affine/graphql' + * + * const { trigger } = useMutation({ + * mutation: someMutation, + * }) + * + * trigger({ name: 'John Doe' }) + */ +export function useMutation( + options: Omit, 'variables'>, + config?: Omit< + SWRMutationConfiguration< + QueryResponse, + GraphQLError, + K, + QueryVariables + >, + 'fetcher' + > +): SWRMutationResponse< + QueryResponse, + GraphQLError, + K, + QueryVariables +>; +export function useMutation( + options: Omit, 'variables'>, + config?: any +) { + return useSWRMutation( + () => ['cloud', options.mutation.id], + (_: unknown[], { arg }: { arg: any }) => + gqlFetcher({ + ...options, + query: options.mutation, + variables: arg, + }), + config + ); +} + +// use this to revalidate all queries that match the filter +export const useMutateQueryResource = () => { + const { mutate } = useSWRConfig(); + const revalidateResource = useMemo( + () => + ( + query: Q, + varsFilter: ( + vars: RecursiveMaybeFields> + ) => boolean = _vars => true + ) => { + return mutate(key => { + const res = + Array.isArray(key) && + key[0] === 'cloud' && + key[1] === query.id && + varsFilter(key[2]); + if (res) { + console.debug('revalidate resource', key); + } + return res; + }); + }, + [mutate] + ); + + return revalidateResource; +}; diff --git a/packages/frontend/admin/src/use-query.ts b/packages/frontend/admin/src/use-query.ts new file mode 100644 index 0000000000000..414a1a6b18dee --- /dev/null +++ b/packages/frontend/admin/src/use-query.ts @@ -0,0 +1,131 @@ +import { + gqlFetcherFactory, + type GraphQLQuery, + type QueryOptions, + type QueryResponse, +} from '@affine/graphql'; +import type { GraphQLError } from 'graphql'; +import { useCallback, useMemo } from 'react'; +import type { SWRConfiguration, SWRResponse } from 'swr'; +import useSWR from 'swr'; +import useSWRImmutable from 'swr/immutable'; +import useSWRInfinite from 'swr/infinite'; + +/** + * A `useSWR` wrapper for sending graphql queries + * + * @example + * + * ```ts + * import { someQuery, someQueryWithNoVars } from '@affine/graphql' + * + * const swrResponse1 = useQuery({ + * query: workspaceByIdQuery, + * variables: { id: '1' } + * }) + * + * const swrResponse2 = useQuery({ + * query: someQueryWithNoVars + * }) + * ``` + */ +type useQueryFn = ( + options?: QueryOptions, + config?: Omit< + SWRConfiguration< + QueryResponse, + GraphQLError, + (options: QueryOptions) => Promise> + >, + 'fetcher' + > +) => SWRResponse< + QueryResponse, + GraphQLError, + { + suspense: true; + } +>; + +const createUseQuery = + (immutable: boolean): useQueryFn => + (options, config) => { + const configWithSuspense: SWRConfiguration = useMemo( + () => ({ + suspense: true, + ...config, + }), + [config] + ); + + const useSWRFn = immutable ? useSWRImmutable : useSWR; + return useSWRFn( + options ? () => ['cloud', options.query.id, options.variables] : null, + options ? () => gqlFetcher(options) : null, + configWithSuspense + ); + }; + +export const useQuery = createUseQuery(false); +export const useQueryImmutable = createUseQuery(true); + +export const gqlFetcher = gqlFetcherFactory('/graphql', window.fetch); + +export function useQueryInfinite( + options: Omit, 'variables'> & { + getVariables: ( + pageIndex: number, + previousPageData: QueryResponse + ) => QueryOptions['variables']; + }, + config?: Omit< + SWRConfiguration< + QueryResponse, + GraphQLError | GraphQLError[], + (options: QueryOptions) => Promise> + >, + 'fetcher' + > +) { + const configWithSuspense: SWRConfiguration = useMemo( + () => ({ + suspense: true, + ...config, + }), + [config] + ); + + const { data, setSize, size, error } = useSWRInfinite< + QueryResponse, + GraphQLError | GraphQLError[] + >( + (pageIndex: number, previousPageData: QueryResponse) => [ + 'cloud', + options.query.id, + options.getVariables(pageIndex, previousPageData), + ], + async ([_, __, variables]) => { + const params = { ...options, variables } as QueryOptions; + return gqlFetcher(params); + }, + configWithSuspense + ); + + const loadingMore = size > 0 && data && !data[size - 1]; + + // TODO(@Peng): find a generic way to know whether or not there are more items to load + const loadMore = useCallback(() => { + if (loadingMore) { + return; + } + setSize(size => size + 1).catch(err => { + console.error(err); + }); + }, [loadingMore, setSize]); + return { + data, + error, + loadingMore, + loadMore, + }; +}