refactor: add workspace events (#1838)

This commit is contained in:
Himself65 2023-04-06 16:14:23 -05:00 committed by GitHub
parent b6bdf257e4
commit 5ac36b6f0a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 105 additions and 71 deletions

View File

@ -16,9 +16,9 @@ import { assertEquals, assertExists } from '@blocksuite/store';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { affineAuth } from '../../../../hooks/affine/use-affine-log-in';
import { useCurrentWorkspace } from '../../../../hooks/current/use-current-workspace'; import { useCurrentWorkspace } from '../../../../hooks/current/use-current-workspace';
import { useTransformWorkspace } from '../../../../hooks/use-transform-workspace'; import { useTransformWorkspace } from '../../../../hooks/use-transform-workspace';
import { affineAuth } from '../../../../plugins/affine';
import type { AffineOfficialWorkspace } from '../../../../shared'; import type { AffineOfficialWorkspace } from '../../../../shared';
import { TransformWorkspaceToAffineModal } from '../../../affine/transform-workspace-to-affine-modal'; import { TransformWorkspaceToAffineModal } from '../../../affine/transform-workspace-to-affine-modal';

View File

@ -36,6 +36,7 @@ export const Footer: React.FC<FooterProps> = ({ user, onLogin, onLogout }) => {
</FlexWrapper> </FlexWrapper>
<Tooltip content={t('Sign out')} disablePortal={true}> <Tooltip content={t('Sign out')} disablePortal={true}>
<IconButton <IconButton
data-testid="workspace-list-modal-sign-out"
onClick={() => { onClick={() => {
onLogout(); onLogout();
}} }}

View File

@ -3,8 +3,8 @@ import { setLoginStorage, SignMethod } from '@affine/workspace/affine/login';
import type React from 'react'; import type React from 'react';
import { memo, useEffect, useState } from 'react'; import { memo, useEffect, useState } from 'react';
import { affineAuth } from '../../../hooks/affine/use-affine-log-in';
import { useAffineLogOut } from '../../../hooks/affine/use-affine-log-out'; import { useAffineLogOut } from '../../../hooks/affine/use-affine-log-out';
import { affineAuth } from '../../../plugins/affine';
import { toast } from '../../../utils'; import { toast } from '../../../utils';
declare global { declare global {

View File

@ -1,30 +1,16 @@
import { currentAffineUserAtom } from '@affine/workspace/affine/atom'; import { WorkspaceFlavour } from '@affine/workspace/type';
import {
createAffineAuth,
parseIdToken,
setLoginStorage,
SignMethod,
} from '@affine/workspace/affine/login';
import { useSetAtom } from 'jotai';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { toast } from '../../utils'; import { WorkspacePlugins } from '../../plugins';
export const affineAuth = createAffineAuth();
export function useAffineLogIn() { export function useAffineLogIn() {
const router = useRouter(); const router = useRouter();
const setUser = useSetAtom(currentAffineUserAtom);
return useCallback(async () => { return useCallback(async () => {
const response = await affineAuth.generateToken(SignMethod.Google); await WorkspacePlugins[WorkspaceFlavour.AFFINE].Events[
if (response) { 'workspace:access'
setLoginStorage(response); ]?.();
const user = parseIdToken(response.token); // todo: remove reload page requirement
setUser(user); router.reload();
router.reload(); }, [router]);
} else {
toast('Login failed');
}
}, [router, setUser]);
} }

View File

@ -1,26 +1,15 @@
import { currentAffineUserAtom } from '@affine/workspace/affine/atom';
import { clearLoginStorage } from '@affine/workspace/affine/login';
import { jotaiWorkspacesAtom } from '@affine/workspace/atom';
import { WorkspaceFlavour } from '@affine/workspace/type'; import { WorkspaceFlavour } from '@affine/workspace/type';
import { useSetAtom } from 'jotai';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { WorkspacePlugins } from '../../plugins'; import { WorkspacePlugins } from '../../plugins';
export function useAffineLogOut() { export function useAffineLogOut() {
const set = useSetAtom(jotaiWorkspacesAtom);
const router = useRouter(); const router = useRouter();
const setCurrentUser = useSetAtom(currentAffineUserAtom); return useCallback(async () => {
return useCallback(() => { await WorkspacePlugins[WorkspaceFlavour.AFFINE].Events[
set(workspaces => 'workspace:revoke'
workspaces.filter( ]?.();
workspace => workspace.flavour !== WorkspaceFlavour.AFFINE
)
);
WorkspacePlugins[WorkspaceFlavour.AFFINE].cleanup?.();
clearLoginStorage();
setCurrentUser(null);
router.reload(); router.reload();
}, [router, set, setCurrentUser]); }, [router]);
} }

View File

@ -7,7 +7,7 @@ import {
} from '@affine/workspace/affine/login'; } from '@affine/workspace/affine/login';
import useSWR from 'swr'; import useSWR from 'swr';
import { affineAuth } from './use-affine-log-in'; import { affineAuth } from '../../plugins/affine';
const logger = new DebugLogger('auth-token'); const logger = new DebugLogger('auth-token');

View File

@ -1,14 +1,7 @@
import { DebugLogger } from '@affine/debug';
import { DEFAULT_WORKSPACE_NAME } from '@affine/env';
import { jotaiStore, jotaiWorkspacesAtom } from '@affine/workspace/atom'; import { jotaiStore, jotaiWorkspacesAtom } from '@affine/workspace/atom';
import { WorkspaceFlavour } from '@affine/workspace/type';
import { createEmptyBlockSuiteWorkspace } from '@affine/workspace/utils';
import { assertEquals, assertExists, nanoid } from '@blocksuite/store';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { LocalPlugin } from '../plugins/local'; import { WorkspacePlugins } from '../plugins';
const logger = new DebugLogger('use-create-first-workspace');
export function useCreateFirstWorkspace() { export function useCreateFirstWorkspace() {
// may not need use effect at all, right? // may not need use effect at all, right?
@ -24,22 +17,13 @@ export function useCreateFirstWorkspace() {
* Create a first workspace, only just once for a browser * Create a first workspace, only just once for a browser
*/ */
async function createFirst() { async function createFirst() {
const blockSuiteWorkspace = createEmptyBlockSuiteWorkspace( const Plugins = Object.values(WorkspacePlugins).sort(
nanoid(), (a, b) => a.loadPriority - b.loadPriority
(_: string) => undefined
); );
blockSuiteWorkspace.meta.setName(DEFAULT_WORKSPACE_NAME);
const id = await LocalPlugin.CRUD.create(blockSuiteWorkspace); for (const Plugin of Plugins) {
const workspace = await LocalPlugin.CRUD.get(id); await Plugin.Events['app:first-init']?.();
assertExists(workspace); }
assertEquals(workspace.id, id);
jotaiStore.set(jotaiWorkspacesAtom, [
{
id: workspace.id,
flavour: WorkspaceFlavour.LOCAL,
},
]);
logger.info('created local workspace', id);
} }
}); });
}, []); }, []);

View File

@ -23,13 +23,13 @@ import React, { useCallback, useEffect } from 'react';
import { Unreachable } from '../../../components/affine/affine-error-eoundary'; import { Unreachable } from '../../../components/affine/affine-error-eoundary';
import { PageLoading } from '../../../components/pure/loading'; import { PageLoading } from '../../../components/pure/loading';
import { WorkspaceTitle } from '../../../components/pure/workspace-title'; import { WorkspaceTitle } from '../../../components/pure/workspace-title';
import { affineAuth } from '../../../hooks/affine/use-affine-log-in';
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace'; import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
import { useSyncRouterWithCurrentWorkspace } from '../../../hooks/use-sync-router-with-current-workspace'; import { useSyncRouterWithCurrentWorkspace } from '../../../hooks/use-sync-router-with-current-workspace';
import { useTransformWorkspace } from '../../../hooks/use-transform-workspace'; import { useTransformWorkspace } from '../../../hooks/use-transform-workspace';
import { useWorkspacesHelper } from '../../../hooks/use-workspaces'; import { useWorkspacesHelper } from '../../../hooks/use-workspaces';
import { WorkspaceLayout } from '../../../layouts'; import { WorkspaceLayout } from '../../../layouts';
import { WorkspacePlugins } from '../../../plugins'; import { WorkspacePlugins } from '../../../plugins';
import { affineAuth } from '../../../plugins/affine';
import type { NextPageWithLayout } from '../../../shared'; import type { NextPageWithLayout } from '../../../shared';
const settingPanelAtom = atomWithSyncStorage<SettingPanel>( const settingPanelAtom = atomWithSyncStorage<SettingPanel>(

View File

@ -1,4 +1,13 @@
import { getLoginStorage } from '@affine/workspace/affine/login'; import { currentAffineUserAtom } from '@affine/workspace/affine/atom';
import {
clearLoginStorage,
createAffineAuth,
getLoginStorage,
parseIdToken,
setLoginStorage,
SignMethod,
} from '@affine/workspace/affine/login';
import { jotaiStore, jotaiWorkspacesAtom } from '@affine/workspace/atom';
import type { AffineWorkspace } from '@affine/workspace/type'; import type { AffineWorkspace } from '@affine/workspace/type';
import { LoadPriority, WorkspaceFlavour } from '@affine/workspace/type'; import { LoadPriority, WorkspaceFlavour } from '@affine/workspace/type';
import { createEmptyBlockSuiteWorkspace } from '@affine/workspace/utils'; import { createEmptyBlockSuiteWorkspace } from '@affine/workspace/utils';
@ -15,7 +24,7 @@ import { PageDetailEditor } from '../../components/page-detail-editor';
import { AffineSWRConfigProvider } from '../../providers/AffineSWRConfigProvider'; import { AffineSWRConfigProvider } from '../../providers/AffineSWRConfigProvider';
import { BlockSuiteWorkspace } from '../../shared'; import { BlockSuiteWorkspace } from '../../shared';
import { affineApis } from '../../shared/apis'; import { affineApis } from '../../shared/apis';
import { initPage } from '../../utils'; import { initPage, toast } from '../../utils';
import type { WorkspacePlugin } from '..'; import type { WorkspacePlugin } from '..';
import { QueryKey } from './fetcher'; import { QueryKey } from './fetcher';
@ -56,11 +65,32 @@ const getPersistenceAllWorkspace = () => {
return allWorkspaces; return allWorkspaces;
}; };
export const affineAuth = createAffineAuth();
export const AffinePlugin: WorkspacePlugin<WorkspaceFlavour.AFFINE> = { export const AffinePlugin: WorkspacePlugin<WorkspaceFlavour.AFFINE> = {
flavour: WorkspaceFlavour.AFFINE, flavour: WorkspaceFlavour.AFFINE,
loadPriority: LoadPriority.HIGH, loadPriority: LoadPriority.HIGH,
cleanup: () => { Events: {
storage.removeItem(kAffineLocal); 'workspace:access': async () => {
const response = await affineAuth.generateToken(SignMethod.Google);
if (response) {
setLoginStorage(response);
const user = parseIdToken(response.token);
jotaiStore.set(currentAffineUserAtom, user);
} else {
toast('Login failed');
}
},
'workspace:revoke': async () => {
jotaiStore.set(jotaiWorkspacesAtom, workspaces =>
workspaces.filter(
workspace => workspace.flavour !== WorkspaceFlavour.AFFINE
)
);
storage.removeItem(kAffineLocal);
clearLoginStorage();
jotaiStore.set(currentAffineUserAtom, null);
},
}, },
CRUD: { CRUD: {
create: async blockSuiteWorkspace => { create: async blockSuiteWorkspace => {

View File

@ -1,4 +1,5 @@
import type { import type {
AppEvents,
LoadPriority, LoadPriority,
WorkspaceCRUD, WorkspaceCRUD,
WorkspaceUISchema, WorkspaceUISchema,
@ -12,8 +13,7 @@ export interface WorkspacePlugin<Flavour extends WorkspaceFlavour> {
flavour: Flavour; flavour: Flavour;
// Plugin will be loaded according to the priority // Plugin will be loaded according to the priority
loadPriority: LoadPriority; loadPriority: LoadPriority;
// fixme: this is a hack Events: Partial<AppEvents>;
cleanup?: () => void;
// Fetch necessary data for the first render // Fetch necessary data for the first render
CRUD: WorkspaceCRUD<Flavour>; CRUD: WorkspaceCRUD<Flavour>;
UI: WorkspaceUISchema<Flavour>; UI: WorkspaceUISchema<Flavour>;

View File

@ -1,5 +1,10 @@
import { DebugLogger } from '@affine/debug';
import { DEFAULT_WORKSPACE_NAME } from '@affine/env';
import { jotaiStore, jotaiWorkspacesAtom } from '@affine/workspace/atom';
import { CRUD } from '@affine/workspace/local/crud'; import { CRUD } from '@affine/workspace/local/crud';
import { LoadPriority, WorkspaceFlavour } from '@affine/workspace/type'; import { LoadPriority, WorkspaceFlavour } from '@affine/workspace/type';
import { createEmptyBlockSuiteWorkspace } from '@affine/workspace/utils';
import { assertEquals, assertExists, nanoid } from '@blocksuite/store';
import React from 'react'; import React from 'react';
import { PageNotFoundError } from '../../components/affine/affine-error-eoundary'; import { PageNotFoundError } from '../../components/affine/affine-error-eoundary';
@ -9,9 +14,33 @@ import { PageDetailEditor } from '../../components/page-detail-editor';
import { initPage } from '../../utils'; import { initPage } from '../../utils';
import type { WorkspacePlugin } from '..'; import type { WorkspacePlugin } from '..';
const logger = new DebugLogger('use-create-first-workspace');
export const LocalPlugin: WorkspacePlugin<WorkspaceFlavour.LOCAL> = { export const LocalPlugin: WorkspacePlugin<WorkspaceFlavour.LOCAL> = {
flavour: WorkspaceFlavour.LOCAL, flavour: WorkspaceFlavour.LOCAL,
loadPriority: LoadPriority.LOW, loadPriority: LoadPriority.LOW,
Events: {
'app:first-init': async () => {
const blockSuiteWorkspace = createEmptyBlockSuiteWorkspace(
nanoid(),
(_: string) => undefined
);
blockSuiteWorkspace.meta.setName(DEFAULT_WORKSPACE_NAME);
const id = await LocalPlugin.CRUD.create(blockSuiteWorkspace);
const workspace = await LocalPlugin.CRUD.get(id);
assertExists(workspace);
assertEquals(workspace.id, id);
// todo: use a better way to set initial workspace
jotaiStore.set(jotaiWorkspacesAtom, ws => [
...ws,
{
id: workspace.id,
flavour: WorkspaceFlavour.LOCAL,
},
]);
logger.debug('create first workspace', workspace);
},
},
CRUD, CRUD,
UI: { UI: {
Provider: ({ children }) => { Provider: ({ children }) => {

View File

@ -128,3 +128,13 @@ export interface WorkspaceUISchema<Flavour extends keyof WorkspaceRegistry> {
SettingsDetail: FC<SettingProps<Flavour>>; SettingsDetail: FC<SettingProps<Flavour>>;
Provider: FC<PropsWithChildren>; Provider: FC<PropsWithChildren>;
} }
export interface AppEvents {
// event when app is first initialized
// usually used to initialize workspace plugin
'app:first-init': () => Promise<void>;
// request to gain access to workspace plugin
'workspace:access': () => Promise<void>;
// request to revoke access to workspace plugin
'workspace:revoke': () => Promise<void>;
}

View File

@ -18,6 +18,7 @@ import { createFakeUser, loginUser, openHomePage } from '../../libs/utils';
import { import {
assertCurrentWorkspaceFlavour, assertCurrentWorkspaceFlavour,
createWorkspace, createWorkspace,
openWorkspaceListModal,
} from '../../libs/workspace'; } from '../../libs/workspace';
test.describe('affine workspace', () => { test.describe('affine workspace', () => {
@ -55,5 +56,9 @@ test.describe('affine workspace', () => {
delay: 50, delay: 50,
}); });
await assertCurrentWorkspaceFlavour('affine', page); await assertCurrentWorkspaceFlavour('affine', page);
await openWorkspaceListModal(page);
await page.getByTestId('workspace-list-modal-sign-out').click();
await page.waitForTimeout(1000);
await assertCurrentWorkspaceFlavour('local', page);
}); });
}); });