refactor: workspace loading logic (#1966)

This commit is contained in:
Himself65
2023-04-16 16:02:41 -05:00
committed by GitHub
parent caa292e097
commit 7bbe67af43
88 changed files with 2684 additions and 2268 deletions
@@ -1,21 +1,24 @@
import { DebugLogger } from '@affine/debug';
import { config } from '@affine/env';
import { config, DEFAULT_HELLO_WORLD_PAGE_ID } from '@affine/env';
import { ensureRootPinboard, initPage } from '@affine/env/blocksuite';
import { setUpLanguage, useTranslation } from '@affine/i18n';
import { createAffineGlobalChannel } from '@affine/workspace/affine/sync';
import { jotaiStore, jotaiWorkspacesAtom } from '@affine/workspace/atom';
import {
rootCurrentPageIdAtom,
rootCurrentWorkspaceIdAtom,
rootStore,
rootWorkspacesMetadataAtom,
} from '@affine/workspace/atom';
import type { LocalIndexedDBProvider } from '@affine/workspace/type';
import { WorkspaceFlavour } from '@affine/workspace/type';
import { assertExists, nanoid } from '@blocksuite/store';
import { assertEquals, assertExists, nanoid } from '@blocksuite/store';
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
import Head from 'next/head';
import { useRouter } from 'next/router';
import type { FC, PropsWithChildren } from 'react';
import { lazy, Suspense, useCallback, useEffect } from 'react';
import type { FC, PropsWithChildren, ReactElement } from 'react';
import { lazy, Suspense, useCallback, useEffect, useState } from 'react';
import {
currentWorkspaceIdAtom,
openQuickSearchModalAtom,
openWorkspacesModalAtom,
} from '../atoms';
import { openQuickSearchModalAtom, openWorkspacesModalAtom } from '../atoms';
import {
publicWorkspaceAtom,
publicWorkspaceIdAtom,
@@ -23,18 +26,19 @@ import {
import { HelpIsland } from '../components/pure/help-island';
import { PageLoading } from '../components/pure/loading';
import WorkSpaceSliderBar from '../components/pure/workspace-slider-bar';
import { useCurrentPageId } from '../hooks/current/use-current-page-id';
import { useCurrentWorkspace } from '../hooks/current/use-current-workspace';
import { useBlockSuiteWorkspaceHelper } from '../hooks/use-blocksuite-workspace-helper';
import { useCreateFirstWorkspace } from '../hooks/use-create-first-workspace';
import { useRouterHelper } from '../hooks/use-router-helper';
import { useRouterTitle } from '../hooks/use-router-title';
import { useRouterWithWorkspaceIdDefense } from '../hooks/use-router-with-workspace-id-defense';
import {
useSidebarFloating,
useSidebarResizing,
useSidebarStatus,
useSidebarWidth,
} from '../hooks/use-sidebar-status';
import { useSyncRouterWithCurrentPageId } from '../hooks/use-sync-router-with-current-page-id';
import { useSyncRouterWithCurrentWorkspaceId } from '../hooks/use-sync-router-with-current-workspace-id';
import { useWorkspaces } from '../hooks/use-workspaces';
import { WorkspacePlugins } from '../plugins';
import { ModalProvider } from '../providers/ModalProvider';
@@ -114,6 +118,53 @@ const logger = new DebugLogger('workspace-layout');
const affineGlobalChannel = createAffineGlobalChannel(
WorkspacePlugins[WorkspaceFlavour.AFFINE].CRUD
);
export const AllWorkspaceContext = ({
children,
}: PropsWithChildren): ReactElement => {
const currentWorkspaceId = useAtomValue(rootCurrentWorkspaceIdAtom);
const workspaces = useWorkspaces();
useEffect(() => {
const providers = workspaces
// ignore current workspace
.filter(workspace => workspace.id !== currentWorkspaceId)
.flatMap(workspace =>
workspace.providers.filter(provider => provider.background)
);
providers.forEach(provider => {
provider.connect();
});
return () => {
providers.forEach(provider => {
provider.disconnect();
});
};
}, [currentWorkspaceId, workspaces]);
return <>{children}</>;
};
export const CurrentWorkspaceContext = ({
children,
}: PropsWithChildren): ReactElement => {
const router = useRouter();
const workspaceId = useAtomValue(rootCurrentWorkspaceIdAtom);
useSyncRouterWithCurrentWorkspaceId(router);
useSyncRouterWithCurrentPageId(router);
useRouterWithWorkspaceIdDefense(router);
const metadata = useAtomValue(rootWorkspacesMetadataAtom);
const exist = metadata.find(m => m.id === workspaceId);
if (!router.isReady) {
return <PageLoading text="Router is loading" />;
}
if (!workspaceId) {
return <PageLoading text="Finding workspace id" />;
}
if (!exist) {
return <PageLoading text="Workspace not found" />;
}
return <>{children}</>;
};
export const WorkspaceLayout: FC<PropsWithChildren> =
function WorkspacesSuspense({ children }) {
const { i18n } = useTranslation();
@@ -122,10 +173,9 @@ export const WorkspaceLayout: FC<PropsWithChildren> =
// todo(himself65): this is a hack, we should use a better way to set the language
setUpLanguage(i18n);
}, [i18n]);
useCreateFirstWorkspace();
const currentWorkspaceId = useAtomValue(currentWorkspaceIdAtom);
const jotaiWorkspaces = useAtomValue(jotaiWorkspacesAtom);
const set = useSetAtom(jotaiWorkspacesAtom);
const currentWorkspaceId = useAtomValue(rootCurrentWorkspaceIdAtom);
const jotaiWorkspaces = useAtomValue(rootWorkspacesMetadataAtom);
const set = useSetAtom(rootWorkspacesMetadataAtom);
useEffect(() => {
logger.info('mount');
const controller = new AbortController();
@@ -134,7 +184,7 @@ export const WorkspaceLayout: FC<PropsWithChildren> =
.map(({ CRUD }) => CRUD.list);
async function fetch() {
const jotaiWorkspaces = jotaiStore.get(jotaiWorkspacesAtom);
const jotaiWorkspaces = rootStore.get(rootWorkspacesMetadataAtom);
const items = [];
for (const list of lists) {
try {
@@ -180,22 +230,31 @@ export const WorkspaceLayout: FC<PropsWithChildren> =
return (
<>
{/* fixme(himself65): don't re-render whole modals */}
<ModalProvider key={currentWorkspaceId} />
<Suspense fallback={<PageLoading />}>
<WorkspaceLayoutInner>{children}</WorkspaceLayoutInner>
</Suspense>
<AllWorkspaceContext>
<CurrentWorkspaceContext>
<ModalProvider key={currentWorkspaceId} />
</CurrentWorkspaceContext>
</AllWorkspaceContext>
<CurrentWorkspaceContext>
<Suspense fallback={<PageLoading text="Finding current workspace" />}>
<WorkspaceLayoutInner>{children}</WorkspaceLayoutInner>
</Suspense>
</CurrentWorkspaceContext>
</>
);
};
export const WorkspaceLayoutInner: FC<PropsWithChildren> = ({ children }) => {
const [currentWorkspace] = useCurrentWorkspace();
const [currentPageId] = useCurrentPageId();
const workspaces = useWorkspaces();
const setCurrentPageId = useSetAtom(rootCurrentPageIdAtom);
const currentPageId = useAtomValue(rootCurrentPageIdAtom);
const router = useRouter();
const { jumpToPage } = useRouterHelper(router);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
logger.info('workspaces: ', workspaces);
}, [workspaces]);
logger.info('currentWorkspace: ', currentWorkspace);
}, [currentWorkspace]);
useEffect(() => {
if (currentWorkspace) {
@@ -203,38 +262,82 @@ export const WorkspaceLayoutInner: FC<PropsWithChildren> = ({ children }) => {
}
}, [currentWorkspace]);
useEffect(() => {
const providers = workspaces.flatMap(workspace =>
workspace.providers.filter(provider => provider.background)
);
providers.forEach(provider => {
provider.connect();
});
return () => {
providers.forEach(provider => {
provider.disconnect();
});
};
}, [workspaces]);
useEffect(() => {
if (currentWorkspace) {
currentWorkspace.providers.forEach(provider => {
if (provider.background) {
return;
}
provider.connect();
});
return () => {
currentWorkspace.providers.forEach(provider => {
if (provider.background) {
return;
}
provider.disconnect();
});
};
}
}, [currentWorkspace]);
const router = useRouter();
useEffect(() => {
if (!router.isReady) {
return;
}
if (!currentWorkspace) {
return;
}
const localProvider = currentWorkspace.providers.find(
provider => provider.flavour === 'local-indexeddb'
);
if (localProvider && localProvider.flavour === 'local-indexeddb') {
const provider = localProvider as LocalIndexedDBProvider;
const callback = () => {
setIsLoading(false);
if (currentWorkspace.blockSuiteWorkspace.isEmpty) {
// this is a new workspace, so we should redirect to the new page
const pageId = nanoid();
const page = currentWorkspace.blockSuiteWorkspace.createPage(pageId);
assertEquals(page.id, pageId);
currentWorkspace.blockSuiteWorkspace.setPageMeta(page.id, {
init: true,
});
initPage(page);
if (!router.query.pageId) {
setCurrentPageId(pageId);
void jumpToPage(currentWorkspace.id, pageId);
}
}
// no matter the workspace is empty, ensure the root pinboard exists
ensureRootPinboard(currentWorkspace.blockSuiteWorkspace);
};
provider.callbacks.add(callback);
return () => {
provider.callbacks.delete(callback);
};
}
}, [currentWorkspace, jumpToPage, router, setCurrentPageId]);
useEffect(() => {
if (!currentWorkspace) {
return;
}
const page = currentWorkspace.blockSuiteWorkspace.getPage(
DEFAULT_HELLO_WORLD_PAGE_ID
);
if (page && page.meta.jumpOnce) {
currentWorkspace.blockSuiteWorkspace.meta.setPageMeta(
DEFAULT_HELLO_WORLD_PAGE_ID,
{
jumpOnce: false,
}
);
setCurrentPageId(currentPageId);
void jumpToPage(currentWorkspace.id, page.id);
}
}, [
currentPageId,
currentWorkspace,
jumpToPage,
router.query.pageId,
setCurrentPageId,
]);
const { openPage } = useRouterHelper(router);
const [, setOpenWorkspacesModal] = useAtom(openWorkspacesModalAtom);
const helper = useBlockSuiteWorkspaceHelper(
@@ -339,7 +442,9 @@ export const WorkspaceLayoutInner: FC<PropsWithChildren> = ({ children }) => {
</StyledSpacer>
<MainContainerWrapper resizing={resizing} style={{ width: mainWidth }}>
<MainContainer className="main-container">
{children}
<Suspense fallback={<PageLoading text="Page is Loading" />}>
{isLoading ? <PageLoading text="Page is Loading" /> : children}
</Suspense>
<StyledToolWrapper>
{/* fixme(himself65): remove this */}
<div id="toolWrapper" style={{ marginBottom: '12px' }}>