refactor: workspace manager (#5060)

This commit is contained in:
EYHN
2023-12-15 07:20:50 +00:00
parent af15aa06d4
commit fe2851d3e9
217 changed files with 3605 additions and 4244 deletions

View File

@@ -9,7 +9,7 @@ import { SignOutModal } from '../components/affine/sign-out-modal';
import { RouteLogic, useNavigateHelper } from '../hooks/use-navigate-helper';
import { signOutCloud } from '../utils/cloud-utils';
export const Component = (): ReactElement => {
export const PageNotFound = (): ReactElement => {
const { data: session } = useSession();
const { jumpToIndex } = useNavigateHelper();
const [open, setOpen] = useState(false);
@@ -52,3 +52,5 @@ export const Component = (): ReactElement => {
</>
);
};
export const Component = PageNotFound;

View File

@@ -1,13 +1,12 @@
import { Menu } from '@affine/component/ui/menu';
import { DebugLogger } from '@affine/debug';
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
import { getWorkspace } from '@toeverything/infra/__internal__/workspace';
import { getCurrentStore } from '@toeverything/infra/atom';
import { lazy } from 'react';
import type { LoaderFunction } from 'react-router-dom';
import { redirect } from 'react-router-dom';
import { workspaceListAtom } from '@affine/workspace/atom';
import { useAtomValue } from 'jotai';
import { lazy, useEffect } from 'react';
import { createFirstAppData } from '../bootstrap/first-app-data';
import { UserWithWorkspaceList } from '../components/pure/workspace-slider-bar/user-with-workspace-list';
import { useNavigateHelper } from '../hooks/use-navigate-helper';
import { WorkspaceSubPath } from '../shared';
const AllWorkspaceModals = lazy(() =>
import('../providers/modal-provider').then(({ AllWorkspaceModals }) => ({
@@ -15,44 +14,27 @@ const AllWorkspaceModals = lazy(() =>
}))
);
const logger = new DebugLogger('index-page');
export const loader: LoaderFunction = async () => {
const rootStore = getCurrentStore();
const { createFirstAppData } = await import('../bootstrap/setup');
createFirstAppData(rootStore);
const meta = await rootStore.get(rootWorkspacesMetadataAtom);
const lastId = localStorage.getItem('last_workspace_id');
const lastPageId = localStorage.getItem('last_page_id');
const target = (lastId && meta.find(({ id }) => id === lastId)) || meta.at(0);
if (target) {
const targetWorkspace = getWorkspace(target.id);
const nonTrashPages = targetWorkspace.meta.pageMetas.filter(
({ trash }) => !trash
);
const helloWorldPage = nonTrashPages.find(({ jumpOnce }) => jumpOnce)?.id;
const pageId =
nonTrashPages.find(({ id }) => id === lastPageId)?.id ??
nonTrashPages.at(0)?.id;
if (helloWorldPage) {
logger.debug(
'Found target workspace. Jump to hello world page',
helloWorldPage
);
return redirect(`/workspace/${targetWorkspace.id}/${helloWorldPage}`);
} else if (pageId) {
logger.debug('Found target workspace. Jump to page', pageId);
return redirect(`/workspace/${targetWorkspace.id}/${pageId}`);
} else {
logger.debug('Found target workspace. Jump to all page');
return redirect(`/workspace/${targetWorkspace.id}/all`);
}
}
return null;
};
export const Component = () => {
const list = useAtomValue(workspaceListAtom);
const { openPage } = useNavigateHelper();
useEffect(() => {
if (list.length === 0) {
return;
}
// open last workspace
const lastId = localStorage.getItem('last_workspace_id');
const openWorkspace = list.find(w => w.id === lastId) ?? list[0];
openPage(openWorkspace.id, WorkspaceSubPath.ALL);
}, [list, openPage]);
useEffect(() => {
createFirstAppData().catch(err => {
console.error('Failed to create first app data', err);
});
}, []);
// TODO: We need a no workspace page
return (
<>

View File

@@ -14,7 +14,6 @@ import { authAtom } from '../atoms';
import { setOnceSignedInEventAtom } from '../atoms/event';
import { useCurrentLoginStatus } from '../hooks/affine/use-current-login-status';
import { RouteLogic, useNavigateHelper } from '../hooks/use-navigate-helper';
import { useAppHelper } from '../hooks/use-workspaces';
export const loader: LoaderFunction = async args => {
const inviteId = args.params.inviteId || '';
@@ -49,7 +48,6 @@ export const loader: LoaderFunction = async args => {
export const Component = () => {
const loginStatus = useCurrentLoginStatus();
const { jumpToSignIn } = useNavigateHelper();
const { addCloudWorkspace } = useAppHelper();
const { jumpToSubPath } = useNavigateHelper();
const setOnceSignedInEvent = useSetAtom(setOnceSignedInEventAtom);
@@ -61,13 +59,12 @@ export const Component = () => {
};
const openWorkspace = useCallback(() => {
addCloudWorkspace(inviteInfo.workspace.id);
jumpToSubPath(
inviteInfo.workspace.id,
WorkspaceSubPath.ALL,
RouteLogic.REPLACE
);
}, [addCloudWorkspace, inviteInfo.workspace.id, jumpToSubPath]);
}, [inviteInfo.workspace.id, jumpToSubPath]);
useEffect(() => {
if (loginStatus === 'unauthenticated') {

View File

@@ -1,11 +1,13 @@
import { MainContainer } from '@affine/component/workspace';
import { DebugLogger } from '@affine/debug';
import { WorkspaceFlavour } from '@affine/env/workspace';
import type { CloudDoc } from '@affine/workspace/affine/download';
import { downloadBinaryFromCloud } from '@affine/workspace/affine/download';
import { getOrCreateWorkspace } from '@affine/workspace/manager';
import { fetchWithTraceReport } from '@affine/graphql';
import {
createAffineCloudBlobStorage,
createStaticBlobStorage,
globalBlockSuiteSchema,
} from '@affine/workspace';
import { assertExists } from '@blocksuite/global/utils';
import type { Page } from '@blocksuite/store';
import { type Page, Workspace } from '@blocksuite/store';
import { noop } from 'foxact/noop';
import type { ReactElement } from 'react';
import { useCallback } from 'react';
@@ -24,6 +26,36 @@ import { PageDetailEditor } from '../../components/page-detail-editor';
import { SharePageNotFoundError } from '../../components/share-page-not-found-error';
import { ShareHeader } from './share-header';
type DocPublishMode = 'edgeless' | 'page';
export type CloudDoc = {
arrayBuffer: ArrayBuffer;
publishMode: DocPublishMode;
};
export async function downloadBinaryFromCloud(
rootGuid: string,
pageGuid: string
): Promise<CloudDoc | null> {
const response = await fetchWithTraceReport(
runtimeConfig.serverUrlPrefix +
`/api/workspaces/${rootGuid}/docs/${pageGuid}`,
{
priority: 'high',
}
);
if (response.ok) {
const publishMode = (response.headers.get('publish-mode') ||
'page') as DocPublishMode;
const arrayBuffer = await response.arrayBuffer();
// return both arrayBuffer and publish mode
return { arrayBuffer, publishMode };
}
return null;
}
type LoaderData = {
page: Page;
publishMode: PageMode;
@@ -49,10 +81,18 @@ export const loader: LoaderFunction = async ({ params }) => {
if (!workspaceId || !pageId) {
return redirect('/404');
}
const workspace = getOrCreateWorkspace(
workspaceId,
WorkspaceFlavour.AFFINE_PUBLIC
);
const workspace = new Workspace({
id: workspaceId,
blobStorages: [
() => ({
crud: createAffineCloudBlobStorage(workspaceId),
}),
() => ({
crud: createStaticBlobStorage(),
}),
],
schema: globalBlockSuiteSchema,
});
// download root workspace
{
const response = await downloadBinaryFromCloud(workspaceId, workspaceId);
@@ -84,9 +124,9 @@ export const Component = (): ReactElement => {
<AppContainer>
<MainContainer>
<ShareHeader
workspace={page.workspace}
pageId={page.id}
publishMode={publishMode}
blockSuiteWorkspace={page.workspace}
/>
<PageDetailEditor
isPublic

View File

@@ -1,30 +1,27 @@
import type { Workspace } from '@blocksuite/store';
import type { Workspace as BlockSuiteWorkspace } from '@blocksuite/store';
import type { PageMode } from '../../atoms';
import { BlockSuiteHeaderTitle } from '../../components/blocksuite/block-suite-header-title';
import ShareHeaderLeftItem from '../../components/cloud/share-header-left-item';
import ShareHeaderRightItem from '../../components/cloud/share-header-right-item';
import { Header } from '../../components/pure/header';
import { useWorkspace } from '../../hooks/use-workspace';
export function ShareHeader({
workspace,
pageId,
publishMode,
blockSuiteWorkspace,
}: {
workspace: Workspace;
pageId: string;
publishMode: PageMode;
blockSuiteWorkspace: BlockSuiteWorkspace;
}) {
const currentWorkspace = useWorkspace(workspace.id);
return (
<Header
isFloat={publishMode === 'edgeless'}
left={<ShareHeaderLeftItem />}
center={
<BlockSuiteHeaderTitle
workspace={currentWorkspace}
blockSuiteWorkspace={blockSuiteWorkspace}
pageId={pageId}
isPublic={true}
publicMode={publishMode}
@@ -32,7 +29,7 @@ export function ShareHeader({
}
right={
<ShareHeaderRightItem
workspaceId={workspace.id}
workspaceId={blockSuiteWorkspace.id}
pageId={pageId}
publishMode={publishMode}
/>

View File

@@ -4,32 +4,32 @@ import {
useCollectionManager,
} from '@affine/component/page-list';
import type { Collection, Filter } from '@affine/env/filter';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { waitForCurrentWorkspaceAtom } from '@affine/workspace/atom';
import { useAtomValue } from 'jotai';
import { useCallback } from 'react';
import { collectionsCRUDAtom } from '../../../atoms/collections';
import { filterContainerStyle } from '../../../components/filter-container.css';
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
import { useWorkspace } from '../../../hooks/use-workspace';
export const FilterContainer = ({ workspaceId }: { workspaceId: string }) => {
const currentWorkspace = useWorkspace(workspaceId);
export const FilterContainer = () => {
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
const navigateHelper = useNavigateHelper();
const setting = useCollectionManager(collectionsCRUDAtom);
const saveToCollection = useCallback(
async (collection: Collection) => {
await setting.createCollection({
(collection: Collection) => {
setting.createCollection({
...collection,
filterList: setting.currentCollection.filterList,
});
navigateHelper.jumpToCollection(workspaceId, collection.id);
navigateHelper.jumpToCollection(currentWorkspace.id, collection.id);
},
[setting, navigateHelper, workspaceId]
[setting, navigateHelper, currentWorkspace.id]
);
const onFilterChange = useAsyncCallback(
async (filterList: Filter[]) => {
await setting.updateCollection({
const onFilterChange = useCallback(
(filterList: Filter[]) => {
setting.updateCollection({
...setting.currentCollection,
filterList,
});

View File

@@ -9,10 +9,9 @@ import {
useCollectionManager,
VirtualizedPageList,
} from '@affine/component/page-list';
import { WorkspaceFlavour } from '@affine/env/workspace';
import { Trans } from '@affine/i18n';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { assertExists } from '@blocksuite/global/utils';
import { waitForCurrentWorkspaceAtom } from '@affine/workspace/atom';
import {
CloseIcon,
DeleteIcon,
@@ -21,18 +20,16 @@ import {
} from '@blocksuite/icons';
import type { PageMeta, Workspace } from '@blocksuite/store';
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
import { getBlockSuiteWorkspaceAtom } from '@toeverything/infra/__internal__/workspace';
import { getCurrentStore } from '@toeverything/infra/atom';
import clsx from 'clsx';
import { useAtomValue, useSetAtom } from 'jotai';
import {
type PropsWithChildren,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import type { LoaderFunction } from 'react-router-dom';
import { redirect } from 'react-router-dom';
import { NIL } from 'uuid';
import { collectionsCRUDAtom } from '../../../atoms/collections';
@@ -45,32 +42,13 @@ import { useAllPageListConfig } from '../../../hooks/affine/use-all-page-list-co
import { useBlockSuiteMetaHelper } from '../../../hooks/affine/use-block-suite-meta-helper';
import { useDeleteCollectionInfo } from '../../../hooks/affine/use-delete-collection-info';
import { useTrashModalHelper } from '../../../hooks/affine/use-trash-modal-helper';
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
import { performanceRenderLogger } from '../../../shared';
import { EmptyPageList } from '../page-list-empty';
import { useFilteredPageMetas } from '../pages';
import * as styles from './all-page.css';
import { FilterContainer } from './all-page-filter';
export const loader: LoaderFunction = async args => {
const rootStore = getCurrentStore();
const workspaceId = args.params.workspaceId;
assertExists(workspaceId);
const [workspaceAtom] = getBlockSuiteWorkspaceAtom(workspaceId);
const workspace = await rootStore.get(workspaceAtom);
for (const pageId of workspace.pages.keys()) {
const page = workspace.getPage(pageId);
if (page && page.meta.jumpOnce) {
workspace.meta.setPageMeta(page.id, {
jumpOnce: false,
});
return redirect(`/workspace/${workspace.id}/${page.id}`);
}
}
rootStore.set(currentCollectionAtom, NIL);
return null;
};
const PageListHeader = () => {
const t = useAFFiNEI18N();
const setting = useCollectionManager(collectionsCRUDAtom);
@@ -100,7 +78,7 @@ const PageListHeader = () => {
};
const usePageOperationsRenderer = () => {
const [currentWorkspace] = useCurrentWorkspace();
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
const { setTrashModal } = useTrashModalHelper(
currentWorkspace.blockSuiteWorkspace
);
@@ -155,7 +133,7 @@ const PageListFloatingToolbar = ({
selectedIds: string[];
onClose: () => void;
}) => {
const [currentWorkspace] = useCurrentWorkspace();
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
const { setTrashModal } = useTrashModalHelper(
currentWorkspace.blockSuiteWorkspace
);
@@ -206,7 +184,7 @@ const NewPageButton = ({
className?: string;
size?: 'small' | 'default';
}>) => {
const [currentWorkspace] = useCurrentWorkspace();
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
const { importFile, createEdgeless, createPage } = usePageHelper(
currentWorkspace.blockSuiteWorkspace
);
@@ -263,14 +241,14 @@ const AllPageHeader = ({
}
center={<WorkspaceModeFilterTab />}
/>
<FilterContainer workspaceId={workspace.id} />
<FilterContainer />
</>
);
};
// even though it is called all page, it is also being used for collection route as well
export const AllPage = () => {
const [currentWorkspace] = useCurrentWorkspace();
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
const { isPreferredEdgeless } = usePageHelper(
currentWorkspace.blockSuiteWorkspace
);
@@ -300,12 +278,10 @@ export const AllPage = () => {
return (
<div className={styles.root}>
{currentWorkspace.flavour !== WorkspaceFlavour.AFFINE_PUBLIC ? (
<AllPageHeader
workspace={currentWorkspace.blockSuiteWorkspace}
showCreateNew={!hideHeaderCreateNewPage}
/>
) : null}
<AllPageHeader
workspace={currentWorkspace.blockSuiteWorkspace}
showCreateNew={!hideHeaderCreateNewPage}
/>
{filteredPageMetas.length > 0 ? (
<>
<VirtualizedPageList
@@ -345,5 +321,35 @@ export const AllPage = () => {
export const Component = () => {
performanceRenderLogger.info('AllPage');
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
const currentCollection = useSetAtom(currentCollectionAtom);
const navigateHelper = useNavigateHelper();
useEffect(() => {
function checkJumpOnce() {
for (const [pageId] of currentWorkspace.blockSuiteWorkspace.pages) {
const page = currentWorkspace.blockSuiteWorkspace.getPage(pageId);
if (page && page.meta.jumpOnce) {
currentWorkspace.blockSuiteWorkspace.meta.setPageMeta(page.id, {
jumpOnce: false,
});
navigateHelper.jumpToPage(currentWorkspace.id, pageId);
}
}
}
checkJumpOnce();
return currentWorkspace.blockSuiteWorkspace.slots.pagesUpdated.on(
checkJumpOnce
).dispose;
}, [
currentWorkspace.blockSuiteWorkspace,
currentWorkspace.id,
navigateHelper,
]);
useEffect(() => {
currentCollection(NIL);
}, [currentCollection]);
return <AllPage />;
};

View File

@@ -13,6 +13,7 @@ import { WindowsAppControls } from '@affine/core/components/pure/header/windows-
import type { Collection } from '@affine/env/filter';
import { Trans } from '@affine/i18n';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { waitForCurrentWorkspaceAtom } from '@affine/workspace/atom';
import {
CloseIcon,
FilterIcon,
@@ -31,7 +32,6 @@ import {
pageCollectionBaseAtom,
} from '../../atoms/collections';
import { useAllPageListConfig } from '../../hooks/affine/use-all-page-list-config';
import { useCurrentWorkspace } from '../../hooks/current/use-current-workspace';
import { useNavigateHelper } from '../../hooks/use-navigate-helper';
import { WorkspaceSubPath } from '../../shared';
import { getWorkspaceSetting } from '../../utils/workspace-setting';
@@ -51,7 +51,7 @@ export const Component = function CollectionPage() {
const { collections, loading } = useAtomValue(pageCollectionBaseAtom);
const navigate = useNavigateHelper();
const params = useParams();
const [workspace] = useCurrentWorkspace();
const workspace = useAtomValue(waitForCurrentWorkspaceAtom);
const collection = collections.find(v => v.id === params.collectionId);
const pushNotification = useSetAtom(pushNotificationAtom);
useEffect(() => {
@@ -102,11 +102,11 @@ const Placeholder = ({ collection }: { collection: Collection }) => {
const { node, open } = useEditCollection(useAllPageListConfig());
const openPageEdit = useAsyncCallback(async () => {
const ret = await open({ ...collection }, 'page');
await updateCollection(ret);
updateCollection(ret);
}, [open, collection, updateCollection]);
const openRuleEdit = useAsyncCallback(async () => {
const ret = await open({ ...collection }, 'rule');
await updateCollection(ret);
updateCollection(ret);
}, [collection, open, updateCollection]);
const [showTips, setShowTips] = useState(false);
useEffect(() => {

View File

@@ -4,7 +4,7 @@ import {
appSidebarOpenAtom,
SidebarSwitch,
} from '@affine/component/app-sidebar';
import type { AllWorkspace } from '@affine/core/shared';
import type { Workspace } from '@affine/workspace';
import { RightSidebarIcon } from '@blocksuite/icons';
import type { Page } from '@blocksuite/store';
import { useAtomValue, useSetAtom } from 'jotai';
@@ -106,7 +106,7 @@ export function DetailPageHeader({
showSidebarSwitch = true,
}: {
page: Page;
workspace: AllWorkspace;
workspace: Workspace;
showSidebarSwitch?: boolean;
}) {
const leftSidebarOpen = useAtomValue(appSidebarOpenAtom);
@@ -117,7 +117,10 @@ export function DetailPageHeader({
<Header className={styles.mainHeader}>
<SidebarSwitch show={!leftSidebarOpen} />
{!leftSidebarOpen ? <HeaderDivider /> : null}
<BlockSuiteHeaderTitle pageId={page.id} workspace={workspace} />
<BlockSuiteHeaderTitle
pageId={page.id}
blockSuiteWorkspace={workspace.blockSuiteWorkspace}
/>
<div className={styles.spacer} />
{page ? <SharePageButton workspace={workspace} page={page} /> : null}
<RightHeader showSidebarSwitch={showSidebarSwitch} />

View File

@@ -5,17 +5,13 @@ import {
} from '@affine/component/page-list';
import { ResizePanel } from '@affine/component/resize-panel';
import { WorkspaceSubPath } from '@affine/env/workspace';
import { globalBlockSuiteSchema } from '@affine/workspace/manager';
import { SyncEngineStep } from '@affine/workspace/providers';
import { assertExists } from '@blocksuite/global/utils';
import { globalBlockSuiteSchema, SyncEngineStep } from '@affine/workspace';
import { waitForCurrentWorkspaceAtom } from '@affine/workspace/atom';
import type { AffineEditorContainer } from '@blocksuite/presets';
import type { Page, Workspace } from '@blocksuite/store';
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
import {
appSettingAtom,
currentPageIdAtom,
currentWorkspaceIdAtom,
} from '@toeverything/infra/atom';
import { useWorkspaceStatus } from '@toeverything/hooks/use-workspace-status';
import { appSettingAtom, currentPageIdAtom } from '@toeverything/infra/atom';
import { useAtomValue, useSetAtom } from 'jotai';
import {
type ReactElement,
@@ -24,7 +20,7 @@ import {
useEffect,
useState,
} from 'react';
import { type LoaderFunction, useParams } from 'react-router-dom';
import { useParams } from 'react-router-dom';
import type { Map as YMap } from 'yjs';
import { setPageModeAtom } from '../../../atoms';
@@ -37,13 +33,9 @@ import { PageDetailEditor } from '../../../components/page-detail-editor';
import { TrashPageFooter } from '../../../components/pure/trash-page-footer';
import { TopTip } from '../../../components/top-tip';
import { useRegisterBlocksuiteEditorCommands } from '../../../hooks/affine/use-register-blocksuite-editor-commands';
import {
useCurrentSyncEngine,
useCurrentSyncEngineStatus,
} from '../../../hooks/current/use-current-sync-engine';
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
import { performanceRenderLogger } from '../../../shared';
import { PageNotFound } from '../../404';
import * as styles from './detail-page.css';
import { DetailPageHeader, RightSidebarHeader } from './detail-page-header';
import {
@@ -112,11 +104,7 @@ const DetailPageLayout = ({
const DetailPageImpl = ({ page }: { page: Page }) => {
const currentPageId = page.id;
const { openPage, jumpToSubPath } = useNavigateHelper();
const [currentWorkspace] = useCurrentWorkspace();
assertExists(
currentWorkspace,
'current workspace is null when rendering detail'
);
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
const blockSuiteWorkspace = currentWorkspace.blockSuiteWorkspace;
const pageMeta = useBlockSuitePageMeta(blockSuiteWorkspace).find(
@@ -186,7 +174,7 @@ const DetailPageImpl = ({ page }: { page: Page }) => {
workspace={currentWorkspace}
showSidebarSwitch={!isInTrash}
/>
<TopTip workspace={currentWorkspace} />
<TopTip pageId={currentPageId} workspace={currentWorkspace} />
</>
}
main={
@@ -231,31 +219,23 @@ const useSafePage = (workspace: Workspace, pageId: string) => {
};
export const DetailPage = ({ pageId }: { pageId: string }): ReactElement => {
const [currentWorkspace] = useCurrentWorkspace();
const currentSyncEngineStatus = useCurrentSyncEngineStatus();
const currentSyncEngine = useCurrentSyncEngine();
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
const currentSyncEngineStep = useWorkspaceStatus(
currentWorkspace,
s => s.engine.sync.step
);
// set sync engine priority target
useEffect(() => {
currentSyncEngine?.setPriorityRule(id => id.endsWith(pageId));
}, [pageId, currentSyncEngine, currentWorkspace]);
currentWorkspace.setPriorityRule(id => id.endsWith(pageId));
}, [pageId, currentWorkspace]);
const page = useSafePage(currentWorkspace?.blockSuiteWorkspace, pageId);
const navigate = useNavigateHelper();
// if sync engine has been synced and the page is null, wait 1s and jump to 404 page.
useEffect(() => {
if (currentSyncEngineStatus?.step === SyncEngineStep.Synced && !page) {
const timeout = setTimeout(() => {
navigate.jumpTo404();
}, 1000);
return () => {
clearTimeout(timeout);
};
}
return;
}, [currentSyncEngineStatus, navigate, page]);
// if sync engine has been synced and the page is null, show 404 page.
if (currentSyncEngineStep === SyncEngineStep.Synced && !page) {
return <PageNotFound />;
}
if (!page) {
return <PageDetailSkeleton key="current-page-is-null" />;
@@ -270,27 +250,18 @@ export const DetailPage = ({ pageId }: { pageId: string }): ReactElement => {
return <DetailPageImpl page={page} />;
};
export const loader: LoaderFunction = async () => {
return null;
};
export const Component = () => {
performanceRenderLogger.info('DetailPage');
const setCurrentWorkspaceId = useSetAtom(currentWorkspaceIdAtom);
const setCurrentPageId = useSetAtom(currentPageIdAtom);
const params = useParams();
useEffect(() => {
if (params.workspaceId) {
localStorage.setItem('last_workspace_id', params.workspaceId);
setCurrentWorkspaceId(params.workspaceId);
}
if (params.pageId) {
localStorage.setItem('last_page_id', params.pageId);
setCurrentPageId(params.pageId);
}
}, [params, setCurrentPageId, setCurrentWorkspaceId]);
}, [params, setCurrentPageId]);
const pageId = params.pageId;

View File

@@ -1,88 +1,88 @@
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
import { getBlockSuiteWorkspaceAtom } from '@toeverything/infra/__internal__/workspace';
import { WorkspaceFallback } from '@affine/component/workspace';
import { type Workspace } from '@affine/workspace';
import {
currentPageIdAtom,
currentWorkspaceIdAtom,
getCurrentStore,
} from '@toeverything/infra/atom';
import type { MigrationPoint } from '@toeverything/infra/blocksuite';
import {
checkWorkspaceCompatibility,
fixWorkspaceVersion,
guidCompatibilityFix,
} from '@toeverything/infra/blocksuite';
import { useSetAtom } from 'jotai';
import { type ReactElement, useEffect } from 'react';
import {
type LoaderFunction,
Outlet,
redirect,
useLoaderData,
useParams,
} from 'react-router-dom';
currentWorkspaceAtom,
workspaceListAtom,
workspaceListLoadingStatusAtom,
workspaceManagerAtom,
} from '@affine/workspace/atom';
import { useWorkspace } from '@toeverything/hooks/use-workspace';
import { useAtom, useAtomValue } from 'jotai';
import { type ReactElement, Suspense, useEffect, useMemo } from 'react';
import { Outlet, useParams } from 'react-router-dom';
import { AffineErrorBoundary } from '../../components/affine/affine-error-boundary';
import { WorkspaceLayout } from '../../layouts/workspace-layout';
import { performanceLogger, performanceRenderLogger } from '../../shared';
import { performanceRenderLogger } from '../../shared';
import { PageNotFound } from '../404';
const workspaceLoaderLogger = performanceLogger.namespace('workspace_loader');
export const loader: LoaderFunction = async args => {
workspaceLoaderLogger.info('start');
const rootStore = getCurrentStore();
if (args.params.workspaceId) {
localStorage.setItem('last_workspace_id', args.params.workspaceId);
rootStore.set(currentWorkspaceIdAtom, args.params.workspaceId);
declare global {
/**
* @internal debug only
*/
// eslint-disable-next-line no-var
var currentWorkspace: Workspace | undefined;
interface WindowEventMap {
'affine:workspace:change': CustomEvent<{ id: string }>;
}
const meta = await rootStore.get(rootWorkspacesMetadataAtom);
workspaceLoaderLogger.info('meta loaded');
const currentMetadata = meta.find(({ id }) => id === args.params.workspaceId);
if (!currentMetadata) {
return redirect('/404');
}
if (args.params.pageId) {
localStorage.setItem('last_page_id', args.params.pageId);
rootStore.set(currentPageIdAtom, args.params.pageId);
} else {
rootStore.set(currentPageIdAtom, null);
}
const [workspaceAtom] = getBlockSuiteWorkspaceAtom(currentMetadata.id);
workspaceLoaderLogger.info('get cloud workspace atom');
const workspace = await rootStore.get(workspaceAtom);
workspaceLoaderLogger.info('workspace loaded');
guidCompatibilityFix(workspace.doc);
fixWorkspaceVersion(workspace.doc);
return checkWorkspaceCompatibility(workspace);
};
}
export const Component = (): ReactElement => {
performanceRenderLogger.info('WorkspaceLayout');
const setCurrentWorkspaceId = useSetAtom(currentWorkspaceIdAtom);
const [
_ /* read this atom here to make sure children refresh when currentWorkspace changed */,
setCurrentWorkspace,
] = useAtom(currentWorkspaceAtom);
const params = useParams();
useEffect(() => {
if (params.workspaceId) {
localStorage.setItem('last_workspace_id', params.workspaceId);
setCurrentWorkspaceId(params.workspaceId);
}
}, [params, setCurrentWorkspaceId]);
const list = useAtomValue(workspaceListAtom);
const listLoading = useAtomValue(workspaceListLoadingStatusAtom);
const workspaceManager = useAtomValue(workspaceManagerAtom);
const meta = useMemo(() => {
return list.find(({ id }) => id === params.workspaceId);
}, [list, params.workspaceId]);
const workspace = useWorkspace(meta);
useEffect(() => {
if (!workspace) {
setCurrentWorkspace(null);
return undefined;
}
setCurrentWorkspace(workspace ?? null);
// for debug purpose
window.currentWorkspace = workspace;
window.dispatchEvent(
new CustomEvent('affine:workspace:change', {
detail: {
id: workspace.id,
},
})
);
localStorage.setItem('last_workspace_id', workspace.id);
}, [setCurrentWorkspace, meta, workspaceManager, workspace]);
// if listLoading is false, we can show 404 page, otherwise we should show loading page.
if (listLoading === false && meta === undefined) {
return <PageNotFound />;
}
if (!workspace) {
return <WorkspaceFallback key="workspaceLoading" />;
}
const migration = useLoaderData() as MigrationPoint | undefined;
return (
<AffineErrorBoundary key={params.workspaceId} height="100vh">
<WorkspaceLayout migration={migration}>
<Outlet />
</WorkspaceLayout>
</AffineErrorBoundary>
<Suspense fallback={<WorkspaceFallback key="workspaceFallback" />}>
<AffineErrorBoundary height="100vh">
<WorkspaceLayout>
<Outlet />
</WorkspaceLayout>
</AffineErrorBoundary>
</Suspense>
);
};

View File

@@ -5,11 +5,13 @@ import {
VirtualizedPageList,
} from '@affine/component/page-list';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { waitForCurrentWorkspaceAtom } from '@affine/workspace/atom';
import { assertExists } from '@blocksuite/global/utils';
import { DeleteIcon } from '@blocksuite/icons';
import type { PageMeta } from '@blocksuite/store';
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
import { getCurrentStore } from '@toeverything/infra/atom';
import { useAtomValue } from 'jotai';
import { useCallback } from 'react';
import { type LoaderFunction } from 'react-router-dom';
import { NIL } from 'uuid';
@@ -18,7 +20,6 @@ import { usePageHelper } from '../../components/blocksuite/block-suite-page-list
import { Header } from '../../components/pure/header';
import { WindowsAppControls } from '../../components/pure/header/windows-app-controls';
import { useBlockSuiteMetaHelper } from '../../hooks/affine/use-block-suite-meta-helper';
import { useCurrentWorkspace } from '../../hooks/current/use-current-workspace';
import { EmptyPageList } from './page-list-empty';
import { useFilteredPageMetas } from './pages';
import * as styles from './trash-page.css';
@@ -56,7 +57,7 @@ export const loader: LoaderFunction = async () => {
};
export const TrashPage = () => {
const [currentWorkspace] = useCurrentWorkspace();
const currentWorkspace = useAtomValue(waitForCurrentWorkspaceAtom);
// todo(himself65): refactor to plugin
const blockSuiteWorkspace = currentWorkspace.blockSuiteWorkspace;
assertExists(blockSuiteWorkspace);