mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-26 10:45:57 +08:00
fix(admin): fix the admin app (#9233)
This commit is contained in:
@@ -66,7 +66,6 @@
|
|||||||
"update-shadcn": "shadcn-ui add -p src/components/ui"
|
"update-shadcn": "shadcn-ui add -p src/components/ui"
|
||||||
},
|
},
|
||||||
"exports": {
|
"exports": {
|
||||||
"./utils": "./src/utils.ts",
|
"./*": "./src/*"
|
||||||
"./components/ui/*": "./src/components/ui/*.tsx"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,5 @@
|
|||||||
import { Toaster } from '@affine/admin/components/ui/sonner';
|
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 { wrapCreateBrowserRouter } from '@sentry/react';
|
||||||
import {
|
|
||||||
configureGlobalContextModule,
|
|
||||||
configureGlobalStorageModule,
|
|
||||||
configureLifecycleModule,
|
|
||||||
Framework,
|
|
||||||
FrameworkRoot,
|
|
||||||
FrameworkScope,
|
|
||||||
LifecycleService,
|
|
||||||
} from '@toeverything/infra';
|
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import {
|
import {
|
||||||
createBrowserRouter as reactRouterCreateBrowserRouter,
|
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 = () => {
|
export const App = () => {
|
||||||
return (
|
return (
|
||||||
<FrameworkRoot framework={frameworkProvider}>
|
<TooltipProvider>
|
||||||
<FrameworkScope scope={serverService.server.scope}>
|
<SWRConfig
|
||||||
<TooltipProvider>
|
value={{
|
||||||
<SWRConfig
|
revalidateOnFocus: false,
|
||||||
value={{
|
revalidateOnMount: false,
|
||||||
revalidateOnFocus: false,
|
}}
|
||||||
revalidateOnMount: false,
|
>
|
||||||
}}
|
<RouterProvider router={router} />
|
||||||
>
|
</SWRConfig>
|
||||||
<RouterProvider router={router} />
|
<Toaster />
|
||||||
</SWRConfig>
|
</TooltipProvider>
|
||||||
<Toaster />
|
|
||||||
</TooltipProvider>
|
|
||||||
</FrameworkScope>
|
|
||||||
</FrameworkRoot>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Button } from '@affine/admin/components/ui/button';
|
import { Button } from '@affine/admin/components/ui/button';
|
||||||
import { Input } from '@affine/admin/components/ui/input';
|
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 { getUserByEmailQuery } from '@affine/graphql';
|
||||||
import { PlusIcon } from 'lucide-react';
|
import { PlusIcon } from 'lucide-react';
|
||||||
import type { SetStateAction } from 'react';
|
import type { SetStateAction } from 'react';
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
|
||||||
import {
|
import {
|
||||||
useMutateQueryResource,
|
useMutateQueryResource,
|
||||||
useMutation,
|
useMutation,
|
||||||
} from '@affine/core/components/hooks/use-mutation';
|
} from '@affine/admin/use-mutation';
|
||||||
import { useQuery } from '@affine/core/components/hooks/use-query';
|
import { useQuery } from '@affine/admin/use-query';
|
||||||
|
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||||
import {
|
import {
|
||||||
createChangePasswordUrlMutation,
|
createChangePasswordUrlMutation,
|
||||||
createUserMutation,
|
createUserMutation,
|
||||||
|
|||||||
@@ -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 { listUsersQuery } from '@affine/graphql';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
|
||||||
import {
|
import {
|
||||||
useMutateQueryResource,
|
useMutateQueryResource,
|
||||||
useMutation,
|
useMutation,
|
||||||
} from '@affine/core/components/hooks/use-mutation';
|
} from '@affine/admin/use-mutation';
|
||||||
import { useQuery } from '@affine/core/components/hooks/use-query';
|
import { useQuery } from '@affine/admin/use-query';
|
||||||
|
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||||
import { getPromptsQuery, updatePromptMutation } from '@affine/graphql';
|
import { getPromptsQuery, updatePromptMutation } from '@affine/graphql';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
|||||||
@@ -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 type { GetCurrentUserFeaturesQuery } from '@affine/graphql';
|
||||||
import {
|
import {
|
||||||
adminServerConfigQuery,
|
adminServerConfigQuery,
|
||||||
@@ -7,6 +5,9 @@ import {
|
|||||||
getCurrentUserFeaturesQuery,
|
getCurrentUserFeaturesQuery,
|
||||||
} from '@affine/graphql';
|
} from '@affine/graphql';
|
||||||
|
|
||||||
|
import { useMutateQueryResource } from '../use-mutation';
|
||||||
|
import { useQuery } from '../use-query';
|
||||||
|
|
||||||
export const useServerConfig = () => {
|
export const useServerConfig = () => {
|
||||||
const { data } = useQuery({
|
const { data } = useQuery({
|
||||||
query: adminServerConfigQuery,
|
query: adminServerConfigQuery,
|
||||||
|
|||||||
@@ -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 { getServerServiceConfigsQuery } from '@affine/graphql';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
|||||||
@@ -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 { getServerRuntimeConfigQuery } from '@affine/graphql';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { notify } from '@affine/component';
|
|
||||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
|
||||||
import {
|
import {
|
||||||
useMutateQueryResource,
|
useMutateQueryResource,
|
||||||
useMutation,
|
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 {
|
import {
|
||||||
getServerRuntimeConfigQuery,
|
getServerRuntimeConfigQuery,
|
||||||
updateServerRuntimeConfigsMutation,
|
updateServerRuntimeConfigsMutation,
|
||||||
|
|||||||
94
packages/frontend/admin/src/use-mutation.ts
Normal file
94
packages/frontend/admin/src/use-mutation.ts
Normal file
@@ -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<Mutation extends GraphQLQuery, K extends Key = Key>(
|
||||||
|
options: Omit<MutationOptions<Mutation>, 'variables'>,
|
||||||
|
config?: Omit<
|
||||||
|
SWRMutationConfiguration<
|
||||||
|
QueryResponse<Mutation>,
|
||||||
|
GraphQLError,
|
||||||
|
K,
|
||||||
|
QueryVariables<Mutation>
|
||||||
|
>,
|
||||||
|
'fetcher'
|
||||||
|
>
|
||||||
|
): SWRMutationResponse<
|
||||||
|
QueryResponse<Mutation>,
|
||||||
|
GraphQLError,
|
||||||
|
K,
|
||||||
|
QueryVariables<Mutation>
|
||||||
|
>;
|
||||||
|
export function useMutation(
|
||||||
|
options: Omit<MutationOptions<GraphQLQuery>, '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(
|
||||||
|
() =>
|
||||||
|
<Q extends GraphQLQuery>(
|
||||||
|
query: Q,
|
||||||
|
varsFilter: (
|
||||||
|
vars: RecursiveMaybeFields<QueryVariables<Q>>
|
||||||
|
) => 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;
|
||||||
|
};
|
||||||
131
packages/frontend/admin/src/use-query.ts
Normal file
131
packages/frontend/admin/src/use-query.ts
Normal file
@@ -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 = <Query extends GraphQLQuery>(
|
||||||
|
options?: QueryOptions<Query>,
|
||||||
|
config?: Omit<
|
||||||
|
SWRConfiguration<
|
||||||
|
QueryResponse<Query>,
|
||||||
|
GraphQLError,
|
||||||
|
(options: QueryOptions<Query>) => Promise<QueryResponse<Query>>
|
||||||
|
>,
|
||||||
|
'fetcher'
|
||||||
|
>
|
||||||
|
) => SWRResponse<
|
||||||
|
QueryResponse<Query>,
|
||||||
|
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<Query extends GraphQLQuery>(
|
||||||
|
options: Omit<QueryOptions<Query>, 'variables'> & {
|
||||||
|
getVariables: (
|
||||||
|
pageIndex: number,
|
||||||
|
previousPageData: QueryResponse<Query>
|
||||||
|
) => QueryOptions<Query>['variables'];
|
||||||
|
},
|
||||||
|
config?: Omit<
|
||||||
|
SWRConfiguration<
|
||||||
|
QueryResponse<Query>,
|
||||||
|
GraphQLError | GraphQLError[],
|
||||||
|
(options: QueryOptions<Query>) => Promise<QueryResponse<Query>>
|
||||||
|
>,
|
||||||
|
'fetcher'
|
||||||
|
>
|
||||||
|
) {
|
||||||
|
const configWithSuspense: SWRConfiguration = useMemo(
|
||||||
|
() => ({
|
||||||
|
suspense: true,
|
||||||
|
...config,
|
||||||
|
}),
|
||||||
|
[config]
|
||||||
|
);
|
||||||
|
|
||||||
|
const { data, setSize, size, error } = useSWRInfinite<
|
||||||
|
QueryResponse<Query>,
|
||||||
|
GraphQLError | GraphQLError[]
|
||||||
|
>(
|
||||||
|
(pageIndex: number, previousPageData: QueryResponse<Query>) => [
|
||||||
|
'cloud',
|
||||||
|
options.query.id,
|
||||||
|
options.getVariables(pageIndex, previousPageData),
|
||||||
|
],
|
||||||
|
async ([_, __, variables]) => {
|
||||||
|
const params = { ...options, variables } as QueryOptions<Query>;
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user