fix(core): refresh metadata after refresh (#4054)
This commit is contained in:
10
.eslintrc.js
10
.eslintrc.js
@@ -37,6 +37,11 @@ const createPattern = packageName => [
|
|||||||
// useSession is type unsafe
|
// useSession is type unsafe
|
||||||
importNames: ['useSession'],
|
importNames: ['useSession'],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
group: ['next-auth/react'],
|
||||||
|
message: "Import hooks from 'cloud-utils.ts'",
|
||||||
|
importNames: ['signIn', 'signOut'],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
group: ['yjs'],
|
group: ['yjs'],
|
||||||
message: 'Do not use this API because it has a bug',
|
message: 'Do not use this API because it has a bug',
|
||||||
@@ -172,6 +177,11 @@ const config = {
|
|||||||
// useSession is type unsafe
|
// useSession is type unsafe
|
||||||
importNames: ['useSession'],
|
importNames: ['useSession'],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
group: ['next-auth/react'],
|
||||||
|
message: "Import hooks from 'cloud-utils.ts'",
|
||||||
|
importNames: ['signIn', 'signOut'],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
group: ['yjs'],
|
group: ['yjs'],
|
||||||
message: 'Do not use this API because it has a bug',
|
message: 'Do not use this API because it has a bug',
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import {
|
|||||||
} from '@affine/component/auth-components';
|
} from '@affine/component/auth-components';
|
||||||
import { Trans } from '@affine/i18n';
|
import { Trans } from '@affine/i18n';
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import { signIn } from 'next-auth/react';
|
|
||||||
import { type FC, useCallback } from 'react';
|
import { type FC, useCallback } from 'react';
|
||||||
|
|
||||||
|
import { signInCloud } from '../../../utils/cloud-utils';
|
||||||
import { buildCallbackUrl } from './callback-url';
|
import { buildCallbackUrl } from './callback-url';
|
||||||
import type { AuthPanelProps } from './index';
|
import type { AuthPanelProps } from './index';
|
||||||
import * as style from './style.css';
|
import * as style from './style.css';
|
||||||
@@ -33,7 +33,7 @@ export const AfterSignInSendEmail: FC<AuthPanelProps> = ({
|
|||||||
|
|
||||||
<ResendButton
|
<ResendButton
|
||||||
onClick={useCallback(() => {
|
onClick={useCallback(() => {
|
||||||
signIn('email', {
|
signInCloud('email', {
|
||||||
email,
|
email,
|
||||||
callbackUrl: buildCallbackUrl('signIn'),
|
callbackUrl: buildCallbackUrl('signIn'),
|
||||||
redirect: true,
|
redirect: true,
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import {
|
|||||||
ResendButton,
|
ResendButton,
|
||||||
} from '@affine/component/auth-components';
|
} from '@affine/component/auth-components';
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import { signIn } from 'next-auth/react';
|
|
||||||
import { type FC, useCallback } from 'react';
|
import { type FC, useCallback } from 'react';
|
||||||
|
|
||||||
|
import { signInCloud } from '../../../utils/cloud-utils';
|
||||||
import { buildCallbackUrl } from './callback-url';
|
import { buildCallbackUrl } from './callback-url';
|
||||||
import type { AuthPanelProps } from './index';
|
import type { AuthPanelProps } from './index';
|
||||||
import * as style from './style.css';
|
import * as style from './style.css';
|
||||||
@@ -32,7 +32,7 @@ export const AfterSignUpSendEmail: FC<AuthPanelProps> = ({
|
|||||||
|
|
||||||
<ResendButton
|
<ResendButton
|
||||||
onClick={useCallback(() => {
|
onClick={useCallback(() => {
|
||||||
signIn('email', {
|
signInCloud('email', {
|
||||||
email: email,
|
email: email,
|
||||||
callbackUrl: buildCallbackUrl('signUp'),
|
callbackUrl: buildCallbackUrl('signUp'),
|
||||||
redirect: true,
|
redirect: true,
|
||||||
|
|||||||
@@ -2,8 +2,15 @@ import {
|
|||||||
AuthModal as AuthModalBase,
|
AuthModal as AuthModalBase,
|
||||||
type AuthModalProps as AuthModalBaseProps,
|
type AuthModalProps as AuthModalBaseProps,
|
||||||
} from '@affine/component/auth-components';
|
} from '@affine/component/auth-components';
|
||||||
import { atom, useAtom } from 'jotai';
|
import { refreshRootMetadataAtom } from '@affine/workspace/atom';
|
||||||
import { type FC, useCallback, useEffect, useMemo } from 'react';
|
import { atom, useAtom, useSetAtom } from 'jotai';
|
||||||
|
import {
|
||||||
|
type FC,
|
||||||
|
startTransition,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
import { AfterSignInSendEmail } from './after-sign-in-send-email';
|
import { AfterSignInSendEmail } from './after-sign-in-send-email';
|
||||||
import { AfterSignUpSendEmail } from './after-sign-up-send-email';
|
import { AfterSignUpSendEmail } from './after-sign-up-send-email';
|
||||||
@@ -79,9 +86,14 @@ export const AuthModal: FC<AuthModalBaseProps & AuthProps> = ({
|
|||||||
}
|
}
|
||||||
}, [open, setAuthEmail, setAuthStore]);
|
}, [open, setAuthEmail, setAuthStore]);
|
||||||
|
|
||||||
|
const refreshMetadata = useSetAtom(refreshRootMetadataAtom);
|
||||||
|
|
||||||
const onSignedIn = useCallback(() => {
|
const onSignedIn = useCallback(() => {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}, [setOpen]);
|
startTransition(() => {
|
||||||
|
refreshMetadata();
|
||||||
|
});
|
||||||
|
}, [refreshMetadata, setOpen]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthModalBase open={open} setOpen={setOpen}>
|
<AuthModalBase open={open} setOpen={setOpen}>
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ import { useMutation } from '@affine/workspace/affine/gql';
|
|||||||
import { ArrowDownBigIcon, GoogleDuotoneIcon } from '@blocksuite/icons';
|
import { ArrowDownBigIcon, GoogleDuotoneIcon } from '@blocksuite/icons';
|
||||||
import { Button } from '@toeverything/components/button';
|
import { Button } from '@toeverything/components/button';
|
||||||
import { useSetAtom } from 'jotai';
|
import { useSetAtom } from 'jotai';
|
||||||
import { signIn, type SignInResponse } from 'next-auth/react';
|
import { type SignInResponse } from 'next-auth/react';
|
||||||
import { type FC, useState } from 'react';
|
import { type FC, useState } from 'react';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { signInCloud } from '../../../utils/cloud-utils';
|
||||||
import { emailRegex } from '../../../utils/email-regex';
|
import { emailRegex } from '../../../utils/email-regex';
|
||||||
import { buildCallbackUrl } from './callback-url';
|
import { buildCallbackUrl } from './callback-url';
|
||||||
import type { AuthPanelProps } from './index';
|
import type { AuthPanelProps } from './index';
|
||||||
@@ -66,7 +67,7 @@ export const SignIn: FC<AuthPanelProps> = ({
|
|||||||
|
|
||||||
setAuthEmail(email);
|
setAuthEmail(email);
|
||||||
if (user) {
|
if (user) {
|
||||||
signIn('email', {
|
signInCloud('email', {
|
||||||
email: email,
|
email: email,
|
||||||
callbackUrl: buildCallbackUrl('signIn'),
|
callbackUrl: buildCallbackUrl('signIn'),
|
||||||
redirect: false,
|
redirect: false,
|
||||||
@@ -75,7 +76,7 @@ export const SignIn: FC<AuthPanelProps> = ({
|
|||||||
.catch(console.error);
|
.catch(console.error);
|
||||||
setAuthState('afterSignInSendEmail');
|
setAuthState('afterSignInSendEmail');
|
||||||
} else {
|
} else {
|
||||||
signIn('email', {
|
signInCloud('email', {
|
||||||
email: email,
|
email: email,
|
||||||
callbackUrl: buildCallbackUrl('signUp'),
|
callbackUrl: buildCallbackUrl('signUp'),
|
||||||
redirect: false,
|
redirect: false,
|
||||||
@@ -102,7 +103,7 @@ export const SignIn: FC<AuthPanelProps> = ({
|
|||||||
}}
|
}}
|
||||||
icon={<GoogleDuotoneIcon />}
|
icon={<GoogleDuotoneIcon />}
|
||||||
onClick={useCallback(() => {
|
onClick={useCallback(() => {
|
||||||
signIn('google').catch(console.error);
|
signInCloud('google').catch(console.error);
|
||||||
}, [])}
|
}, [])}
|
||||||
>
|
>
|
||||||
{t['Continue with Google']()}
|
{t['Continue with Google']()}
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ import { useMutation } from '@affine/workspace/affine/gql';
|
|||||||
import { ArrowRightSmallIcon, CameraIcon, DoneIcon } from '@blocksuite/icons';
|
import { ArrowRightSmallIcon, CameraIcon, DoneIcon } from '@blocksuite/icons';
|
||||||
import { Button, IconButton } from '@toeverything/components/button';
|
import { Button, IconButton } from '@toeverything/components/button';
|
||||||
import { useAtom } from 'jotai/index';
|
import { useAtom } from 'jotai/index';
|
||||||
import { signOut } from 'next-auth/react';
|
|
||||||
import { type FC, useCallback, useState } from 'react';
|
import { type FC, useCallback, useState } from 'react';
|
||||||
|
|
||||||
import { authAtom } from '../../../../atoms';
|
import { authAtom } from '../../../../atoms';
|
||||||
import { useCurrentUser } from '../../../../hooks/affine/use-current-user';
|
import { useCurrentUser } from '../../../../hooks/affine/use-current-user';
|
||||||
|
import { signOutCloud } from '../../../../utils/cloud-utils';
|
||||||
import { Upload } from '../../../pure/file-upload';
|
import { Upload } from '../../../pure/file-upload';
|
||||||
import * as style from './style.css';
|
import * as style from './style.css';
|
||||||
|
|
||||||
@@ -160,7 +160,7 @@ export const AccountSetting: FC = () => {
|
|||||||
desc={t['com.affine.setting.sign.out.message']()}
|
desc={t['com.affine.setting.sign.out.message']()}
|
||||||
style={{ cursor: 'pointer' }}
|
style={{ cursor: 'pointer' }}
|
||||||
onClick={useCallback(() => {
|
onClick={useCallback(() => {
|
||||||
signOut().catch(console.error);
|
signOutCloud().catch(console.error);
|
||||||
}, [])}
|
}, [])}
|
||||||
>
|
>
|
||||||
<ArrowRightSmallIcon />
|
<ArrowRightSmallIcon />
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { UserAvatar } from '@affine/component/user-avatar';
|
import { UserAvatar } from '@affine/component/user-avatar';
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import { CloudWorkspaceIcon } from '@blocksuite/icons';
|
import { CloudWorkspaceIcon } from '@blocksuite/icons';
|
||||||
import { signIn } from 'next-auth/react';
|
|
||||||
|
|
||||||
import { useCurrentLoginStatus } from '../../hooks/affine/use-current-login-status';
|
import { useCurrentLoginStatus } from '../../hooks/affine/use-current-login-status';
|
||||||
import { useCurrentUser } from '../../hooks/affine/use-current-user';
|
import { useCurrentUser } from '../../hooks/affine/use-current-user';
|
||||||
|
import { signInCloud } from '../../utils/cloud-utils';
|
||||||
import { StyledSignInButton } from '../pure/footer/styles';
|
import { StyledSignInButton } from '../pure/footer/styles';
|
||||||
|
|
||||||
export const LoginCard = () => {
|
export const LoginCard = () => {
|
||||||
@@ -17,8 +17,7 @@ export const LoginCard = () => {
|
|||||||
<StyledSignInButton
|
<StyledSignInButton
|
||||||
data-testid="sign-in-button"
|
data-testid="sign-in-button"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
// jump to login page
|
signInCloud().catch(console.error);
|
||||||
signIn().catch(console.error);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="circle">
|
<div className="circle">
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import { CloudWorkspaceIcon } from '@blocksuite/icons';
|
import { CloudWorkspaceIcon } from '@blocksuite/icons';
|
||||||
import { signIn } from 'next-auth/react';
|
|
||||||
import { type CSSProperties, type FC, forwardRef, useCallback } from 'react';
|
import { type CSSProperties, type FC, forwardRef, useCallback } from 'react';
|
||||||
|
|
||||||
import { useCurrentLoginStatus } from '../../../hooks/affine/use-current-login-status';
|
import { useCurrentLoginStatus } from '../../../hooks/affine/use-current-login-status';
|
||||||
// import { openDisableCloudAlertModalAtom } from '../../../atoms';
|
|
||||||
import { stringToColour } from '../../../utils';
|
import { stringToColour } from '../../../utils';
|
||||||
|
import { signInCloud } from '../../../utils/cloud-utils';
|
||||||
import { StyledFooter, StyledSignInButton } from './styles';
|
import { StyledFooter, StyledSignInButton } from './styles';
|
||||||
|
|
||||||
export const Footer: FC = () => {
|
export const Footer: FC = () => {
|
||||||
const loginStatus = useCurrentLoginStatus();
|
const loginStatus = useCurrentLoginStatus();
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ const SignInButton = () => {
|
|||||||
<StyledSignInButton
|
<StyledSignInButton
|
||||||
data-testid="sign-in-button"
|
data-testid="sign-in-button"
|
||||||
onClick={useCallback(() => {
|
onClick={useCallback(() => {
|
||||||
signIn().catch(console.error);
|
signInCloud().catch(console.error);
|
||||||
}, [])}
|
}, [])}
|
||||||
>
|
>
|
||||||
<div className="circle">
|
<div className="circle">
|
||||||
|
|||||||
24
apps/core/src/utils/cloud-utils.tsx
Normal file
24
apps/core/src/utils/cloud-utils.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { refreshRootMetadataAtom } from '@affine/workspace/atom';
|
||||||
|
import { getCurrentStore } from '@toeverything/infra/atom';
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||||
|
import { signIn, signOut } from 'next-auth/react';
|
||||||
|
import { startTransition } from 'react';
|
||||||
|
|
||||||
|
export const signInCloud: typeof signIn = async (...args) => {
|
||||||
|
return signIn(...args).then(result => {
|
||||||
|
// do not refresh root metadata,
|
||||||
|
// because the session won't change in this callback
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const signOutCloud: typeof signOut = async (...args) => {
|
||||||
|
return signOut(...args).then(result => {
|
||||||
|
if (result) {
|
||||||
|
startTransition(() => {
|
||||||
|
getCurrentStore().set(refreshRootMetadataAtom);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
currentWorkspaceIdAtom,
|
currentWorkspaceIdAtom,
|
||||||
} from '@toeverything/infra/atom';
|
} from '@toeverything/infra/atom';
|
||||||
import { WorkspaceVersion } from '@toeverything/infra/blocksuite';
|
import { WorkspaceVersion } from '@toeverything/infra/blocksuite';
|
||||||
import { atom } from 'jotai';
|
import { type Atom, atom } from 'jotai/vanilla';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { getOrCreateWorkspace } from './manager';
|
import { getOrCreateWorkspace } from './manager';
|
||||||
@@ -68,26 +68,25 @@ const METADATA_STORAGE_KEY = 'jotai-workspaces';
|
|||||||
const rootWorkspacesMetadataPrimitiveAtom = atom<Promise<
|
const rootWorkspacesMetadataPrimitiveAtom = atom<Promise<
|
||||||
RootWorkspaceMetadata[]
|
RootWorkspaceMetadata[]
|
||||||
> | null>(null);
|
> | null>(null);
|
||||||
const rootWorkspacesMetadataPromiseAtom = atom<
|
|
||||||
Promise<RootWorkspaceMetadata[]>
|
type Getter = <Value>(atom: Atom<Value>) => Value;
|
||||||
>(async (get, { signal }) => {
|
|
||||||
|
type FetchMetadata = (
|
||||||
|
get: Getter,
|
||||||
|
options: { signal: AbortSignal }
|
||||||
|
) => Promise<RootWorkspaceMetadata[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
const fetchMetadata: FetchMetadata = async (get, { signal }) => {
|
||||||
const WorkspaceAdapters = get(workspaceAdaptersAtom);
|
const WorkspaceAdapters = get(workspaceAdaptersAtom);
|
||||||
assertExists(WorkspaceAdapters, 'workspace adapter should be defined');
|
assertExists(WorkspaceAdapters, 'workspace adapter should be defined');
|
||||||
const primitiveMetadata = get(rootWorkspacesMetadataPrimitiveAtom);
|
|
||||||
assertEquals(
|
|
||||||
primitiveMetadata,
|
|
||||||
null,
|
|
||||||
'rootWorkspacesMetadataPrimitiveAtom should be null'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (environment.isServer) {
|
|
||||||
// return a promise in SSR to avoid the hydration mismatch
|
|
||||||
return Promise.resolve([]);
|
|
||||||
} else {
|
|
||||||
const metadata: RootWorkspaceMetadata[] = [];
|
const metadata: RootWorkspaceMetadata[] = [];
|
||||||
|
|
||||||
// fixme(himself65): we might not need step 1
|
// step 1: try load metadata from localStorage.
|
||||||
// step 1: try load metadata from localStorage
|
//
|
||||||
|
// we need this step because workspaces have the order.
|
||||||
{
|
{
|
||||||
const loadFromLocalStorage = (): RootWorkspaceMetadata[] => {
|
const loadFromLocalStorage = (): RootWorkspaceMetadata[] => {
|
||||||
// don't change this key,
|
// don't change this key,
|
||||||
@@ -138,6 +137,12 @@ const rootWorkspacesMetadataPromiseAtom = atom<
|
|||||||
!(await Adapter.Events['app:access']())
|
!(await Adapter.Events['app:access']())
|
||||||
) {
|
) {
|
||||||
// skip the adapter if the user doesn't have access to it
|
// skip the adapter if the user doesn't have access to it
|
||||||
|
const removed = metadata.filter(
|
||||||
|
meta => meta.flavour === currentFlavour
|
||||||
|
);
|
||||||
|
removed.forEach(meta => {
|
||||||
|
metadata.splice(metadata.indexOf(meta), 1);
|
||||||
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -187,8 +192,21 @@ const rootWorkspacesMetadataPromiseAtom = atom<
|
|||||||
throw new Error(`unknown flavour ${meta.flavour}`);
|
throw new Error(`unknown flavour ${meta.flavour}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return Array.from(metadataMap.values());
|
const result = Array.from(metadataMap.values());
|
||||||
}
|
console.info('metadata', result);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const rootWorkspacesMetadataPromiseAtom = atom<
|
||||||
|
Promise<RootWorkspaceMetadata[]>
|
||||||
|
>(async (get, { signal }) => {
|
||||||
|
const primitiveMetadata = get(rootWorkspacesMetadataPrimitiveAtom);
|
||||||
|
assertEquals(
|
||||||
|
primitiveMetadata,
|
||||||
|
null,
|
||||||
|
'rootWorkspacesMetadataPrimitiveAtom should be null'
|
||||||
|
);
|
||||||
|
return fetchMetadata(get, { signal });
|
||||||
});
|
});
|
||||||
|
|
||||||
type SetStateAction<Value> = Value | ((prev: Value) => Value);
|
type SetStateAction<Value> = Value | ((prev: Value) => Value);
|
||||||
@@ -246,6 +264,14 @@ export const rootWorkspacesMetadataAtom = atom<
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const refreshRootMetadataAtom = atom(null, (get, set) => {
|
||||||
|
const abortController = new AbortController();
|
||||||
|
set(
|
||||||
|
rootWorkspacesMetadataPrimitiveAtom,
|
||||||
|
fetchMetadata(get, { signal: abortController.signal })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
// blocksuite atoms,
|
// blocksuite atoms,
|
||||||
// each app should have only one block-hub in the same time
|
// each app should have only one block-hub in the same time
|
||||||
export const rootBlockHubAtom = atom<Readonly<BlockHub> | null>(null);
|
export const rootBlockHubAtom = atom<Readonly<BlockHub> | null>(null);
|
||||||
|
|||||||
Reference in New Issue
Block a user