diff --git a/apps/web/src/components/workspace-layout/index.tsx b/apps/web/src/components/workspace-layout/index.tsx
index 839113afbb..6c02962fed 100644
--- a/apps/web/src/components/workspace-layout/index.tsx
+++ b/apps/web/src/components/workspace-layout/index.tsx
@@ -1,18 +1,14 @@
+import { useGlobalState } from '@affine/store';
import { useRouter } from 'next/router';
-import { PropsWithChildren } from 'react';
+import { PropsWithChildren, useEffect } from 'react';
import HelpIsland from '@/components/help-island';
import { WorkSpaceSliderBar } from '@/components/workspace-slider-bar';
-import useEnsureWorkspace from '@/hooks/use-ensure-workspace';
+import { useRouterTargetWorkspace } from '@/hooks/use-router-target-workspace';
import { PageLoading } from '../loading';
import { StyledPage, StyledToolWrapper, StyledWrapper } from './styles';
-export const WorkspaceDefender = ({ children }: PropsWithChildren) => {
- const { workspaceLoaded } = useEnsureWorkspace();
- return <>{workspaceLoaded ? children : }>;
-};
-
export const WorkspaceLayout = ({ children }: PropsWithChildren) => {
const router = useRouter();
@@ -35,10 +31,22 @@ export const WorkspaceLayout = ({ children }: PropsWithChildren) => {
};
export const Layout = ({ children }: PropsWithChildren) => {
- return (
-
- {children}
-
- );
+ const { targetWorkspace, exist } = useRouterTargetWorkspace();
+ const router = useRouter();
+ const loadWorkspace = useGlobalState(store => store.loadWorkspace);
+ useEffect(() => {
+ if (!exist) {
+ router.replace('/404');
+ }
+ }, [exist, router]);
+ useEffect(() => {
+ if (exist && targetWorkspace) {
+ loadWorkspace(targetWorkspace.id);
+ }
+ }, [exist, loadWorkspace, targetWorkspace]);
+ if (!targetWorkspace) {
+ return ;
+ }
+ return {children};
};
export default Layout;
diff --git a/apps/web/src/hooks/use-ensure-workspace.ts b/apps/web/src/hooks/use-ensure-workspace.ts
deleted file mode 100644
index 50f90c569a..0000000000
--- a/apps/web/src/hooks/use-ensure-workspace.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-import { assertEquals } from '@blocksuite/global/utils';
-import { useRouter } from 'next/router';
-import { useCallback, useEffect, useState } from 'react';
-
-import { useDataCenter, useGlobalState } from '@/store/app';
-
-// todo: refactor with suspense mode
-// It is a fully effective hook
-// Cause it not just ensure workspace loaded, but also have router change.
-export const useEnsureWorkspace = () => {
- const dataCenter = useDataCenter();
- const currentWorkspace = useGlobalState(
- useCallback(store => store.currentDataCenterWorkspace, [])
- );
- const loadWorkspace = useGlobalState(
- useCallback(store => store.loadWorkspace, [])
- );
- const router = useRouter();
- const [currentWorkspaceId, setCurrentWorkspaceId] = useState(
- typeof router.query.workspaceId === 'string'
- ? router.query.workspaceId
- : null
- );
-
- // const defaultOutLineWorkspaceId = '99ce7eb7';
- // console.log(defaultOutLineWorkspaceId);
- useEffect(() => {
- const abortController = new AbortController();
-
- const workspaceId =
- (router.query.workspaceId as string) || dataCenter.workspaces[0]?.id;
-
- // If router.query.workspaceId is not in workspace list, jump to 404 page
- // If workspaceList is empty, we need to create a default workspace but not jump to 404
- if (
- workspaceId &&
- dataCenter.workspaces.length &&
- dataCenter.workspaces.findIndex(
- meta => meta.id.toString() === workspaceId
- ) === -1
- ) {
- router.push('/404');
- return;
- }
- // If user is not login and input a custom workspaceId, jump to 404 page
- // if (
- // !user &&
- // router.query.workspaceId &&
- // router.query.workspaceId !== defaultOutLineWorkspaceId
- // ) {
- // router.push('/404');
- // return;
- // }
-
- loadWorkspace(workspaceId, abortController.signal).then(unit => {
- if (!abortController.signal.aborted && unit) {
- setCurrentWorkspaceId(unit.id);
- assertEquals(unit.id, workspaceId);
- }
- });
-
- return () => {
- abortController.abort();
- };
- }, [dataCenter, loadWorkspace, router]);
-
- return {
- workspaceLoaded: currentWorkspace?.id === currentWorkspaceId,
- activeWorkspaceId: currentWorkspace?.id ?? router.query.workspaceId,
- };
-};
-
-export default useEnsureWorkspace;
diff --git a/apps/web/src/hooks/use-page-helper.ts b/apps/web/src/hooks/use-page-helper.ts
index 02febbe804..67e1858435 100644
--- a/apps/web/src/hooks/use-page-helper.ts
+++ b/apps/web/src/hooks/use-page-helper.ts
@@ -5,7 +5,7 @@ import { uuidv4, Workspace } from '@blocksuite/store';
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
import type { QueryContent } from '@blocksuite/store/dist/workspace/search';
import { useRouter } from 'next/router';
-import { useCallback } from 'react';
+import { useCallback, useMemo } from 'react';
import { useChangePageMeta } from '@/hooks/use-change-page-meta';
import { useGlobalState } from '@/store/app';
@@ -48,104 +48,107 @@ export const usePageHelper = (): EditorHandlers => {
useCallback(store => store.currentDataCenterWorkspace, [])
);
- return {
- createPage: ({
- pageId = uuidv4().replaceAll('-', ''),
- title = '',
- } = {}) => {
- return new Promise(resolve => {
- if (!currentWorkspace) {
- return resolve(null);
- }
- currentWorkspace.blocksuiteWorkspace?.createPage(pageId);
- currentWorkspace.blocksuiteWorkspace?.signals.pageAdded.once(
- addedPageId => {
- currentWorkspace.blocksuiteWorkspace?.setPageMeta(addedPageId, {
- title,
- });
- resolve(addedPageId);
+ return useMemo(
+ () => ({
+ createPage: ({
+ pageId = uuidv4().replaceAll('-', ''),
+ title = '',
+ } = {}) => {
+ return new Promise(resolve => {
+ if (!currentWorkspace) {
+ return resolve(null);
}
- );
- });
- },
- toggleFavoritePage: async pageId => {
- const pageMeta = getPageMeta(currentWorkspace, pageId);
- if (!pageMeta) {
- return Promise.reject('No page');
- }
- const favorite = !pageMeta.favorite;
- changePageMeta(pageMeta.id, {
- favorite,
- });
- return favorite;
- },
- toggleDeletePage: async pageId => {
- const pageMeta = getPageMeta(currentWorkspace, pageId);
-
- if (!pageMeta) {
- return Promise.reject('No page');
- }
- const trash = !pageMeta.trash;
-
- changePageMeta(pageMeta.id, {
- trash,
- trashDate: +new Date(),
- });
- return trash;
- },
- search: (query: QueryContent, workspace?: Workspace) => {
- if (workspace) {
- return workspace.search(query);
- }
- if (currentWorkspace) {
- if (currentWorkspace.blocksuiteWorkspace) {
- return currentWorkspace.blocksuiteWorkspace.search(query);
+ currentWorkspace.blocksuiteWorkspace?.createPage(pageId);
+ currentWorkspace.blocksuiteWorkspace?.signals.pageAdded.once(
+ addedPageId => {
+ currentWorkspace.blocksuiteWorkspace?.setPageMeta(addedPageId, {
+ title,
+ });
+ resolve(addedPageId);
+ }
+ );
+ });
+ },
+ toggleFavoritePage: async pageId => {
+ const pageMeta = getPageMeta(currentWorkspace, pageId);
+ if (!pageMeta) {
+ return Promise.reject('No page');
}
- }
- return new Map();
- },
- changePageMode: async (pageId, mode) => {
- const pageMeta = getPageMeta(currentWorkspace, pageId);
- if (!pageMeta) {
- return Promise.reject('No page');
- }
+ const favorite = !pageMeta.favorite;
+ changePageMeta(pageMeta.id, {
+ favorite,
+ });
+ return favorite;
+ },
+ toggleDeletePage: async pageId => {
+ const pageMeta = getPageMeta(currentWorkspace, pageId);
- editor?.setAttribute('mode', mode as string);
+ if (!pageMeta) {
+ return Promise.reject('No page');
+ }
+ const trash = !pageMeta.trash;
- changePageMeta(pageMeta.id, {
- mode,
- });
- return mode;
- },
- permanentlyDeletePage: pageId => {
- // TODO: workspace.meta.removePage or workspace.removePage?
+ changePageMeta(pageMeta.id, {
+ trash,
+ trashDate: +new Date(),
+ });
+ return trash;
+ },
+ search: (query: QueryContent, workspace?: Workspace) => {
+ if (workspace) {
+ return workspace.search(query);
+ }
+ if (currentWorkspace) {
+ if (currentWorkspace.blocksuiteWorkspace) {
+ return currentWorkspace.blocksuiteWorkspace.search(query);
+ }
+ }
+ return new Map();
+ },
+ changePageMode: async (pageId, mode) => {
+ const pageMeta = getPageMeta(currentWorkspace, pageId);
+ if (!pageMeta) {
+ return Promise.reject('No page');
+ }
- currentWorkspace!.blocksuiteWorkspace?.meta.removePage(pageId);
- },
- openPage: (pageId, query = {}, newTab = false) => {
- pageId = pageId.replace('space:', '');
+ editor?.setAttribute('mode', mode as string);
- if (newTab) {
- window.open(`/workspace/${currentWorkspace?.id}/${pageId}`, '_blank');
- return Promise.resolve(true);
- }
- return router.push({
- pathname: `/workspace/${currentWorkspace?.id}/${pageId}`,
- query,
- });
- },
- getPageMeta: pageId => {
- if (!currentWorkspace) {
- return null;
- }
+ changePageMeta(pageMeta.id, {
+ mode,
+ });
+ return mode;
+ },
+ permanentlyDeletePage: pageId => {
+ // TODO: workspace.meta.removePage or workspace.removePage?
- return (
- (currentWorkspace.blocksuiteWorkspace?.meta.pageMetas.find(
- page => page.id === pageId
- ) as PageMeta) || null
- );
- },
- };
+ currentWorkspace!.blocksuiteWorkspace?.meta.removePage(pageId);
+ },
+ openPage: (pageId, query = {}, newTab = false) => {
+ pageId = pageId.replace('space:', '');
+
+ if (newTab) {
+ window.open(`/workspace/${currentWorkspace?.id}/${pageId}`, '_blank');
+ return Promise.resolve(true);
+ }
+ return router.push({
+ pathname: `/workspace/${currentWorkspace?.id}/${pageId}`,
+ query,
+ });
+ },
+ getPageMeta: pageId => {
+ if (!currentWorkspace) {
+ return null;
+ }
+
+ return (
+ (currentWorkspace.blocksuiteWorkspace?.meta.pageMetas.find(
+ page => page.id === pageId
+ ) as PageMeta) || null
+ );
+ },
+ }),
+ [changePageMeta, currentWorkspace, editor, router]
+ );
};
export default usePageHelper;
diff --git a/apps/web/src/hooks/use-router-target-workspace.ts b/apps/web/src/hooks/use-router-target-workspace.ts
new file mode 100644
index 0000000000..67a694c161
--- /dev/null
+++ b/apps/web/src/hooks/use-router-target-workspace.ts
@@ -0,0 +1,26 @@
+import { useDataCenter, useDataCenterWorkspace } from '@affine/store';
+import { useRouter } from 'next/router';
+import { useMemo } from 'react';
+
+export function useRouterTargetWorkspace() {
+ const router = useRouter();
+ const dataCenter = useDataCenter();
+ const workspaceId =
+ typeof router.query.workspaceId === 'string'
+ ? router.query.workspaceId
+ : dataCenter.workspaces.at(0)?.id ?? null;
+ const targetWorkspace = useDataCenterWorkspace(workspaceId);
+ const notExist = useMemo(
+ () =>
+ workspaceId &&
+ dataCenter.workspaces.length &&
+ dataCenter.workspaces.findIndex(
+ meta => meta.id.toString() === workspaceId
+ ) === -1,
+ [dataCenter.workspaces, workspaceId]
+ );
+ return {
+ targetWorkspace,
+ exist: !notExist,
+ };
+}
diff --git a/apps/web/src/pages/workspace/[workspaceId]/index.tsx b/apps/web/src/pages/workspace/[workspaceId]/index.tsx
index 5c79720f23..16ed6cd81f 100644
--- a/apps/web/src/pages/workspace/[workspaceId]/index.tsx
+++ b/apps/web/src/pages/workspace/[workspaceId]/index.tsx
@@ -1,44 +1,48 @@
import { useRouter } from 'next/router';
-import { useCallback, useEffect } from 'react';
+import { useEffect } from 'react';
import { PageLoading } from '@/components/loading';
-import useEnsureWorkspace from '@/hooks/use-ensure-workspace';
import usePageHelper from '@/hooks/use-page-helper';
-import { useGlobalState } from '@/store/app';
+import { useRouterTargetWorkspace } from '@/hooks/use-router-target-workspace';
const WorkspaceIndex = () => {
const router = useRouter();
- const currentWorkspace = useGlobalState(
- useCallback(store => store.currentDataCenterWorkspace, [])
- );
+ const { targetWorkspace, exist } = useRouterTargetWorkspace();
const { createPage } = usePageHelper();
- const { workspaceLoaded, activeWorkspaceId } = useEnsureWorkspace();
useEffect(() => {
+ if (!exist) {
+ router.push('/404');
+ return;
+ }
+ const abortController = new AbortController();
const initPage = async () => {
- if (!workspaceLoaded) {
+ if (abortController.signal.aborted) {
+ return;
+ }
+ if (!targetWorkspace) {
return;
}
const savedPageId =
- currentWorkspace?.blocksuiteWorkspace?.meta.pageMetas.find(
+ targetWorkspace.blocksuiteWorkspace?.meta.pageMetas.find(
meta => !meta.trash
)?.id;
if (savedPageId) {
- router.replace(`/workspace/${activeWorkspaceId}/${savedPageId}`);
+ router.replace(`/workspace/${targetWorkspace.id}/${savedPageId}`);
return;
+ } else {
+ const pageId = await createPage();
+ if (abortController.signal.aborted) {
+ return;
+ }
+ router.replace(`/workspace/${targetWorkspace.id}/${pageId}`);
}
-
- const pageId = await createPage();
- router.replace(`/workspace/${activeWorkspaceId}/${pageId}`);
};
initPage();
- }, [
- currentWorkspace,
- createPage,
- router,
- workspaceLoaded,
- activeWorkspaceId,
- ]);
+ return () => {
+ abortController.abort();
+ };
+ }, [targetWorkspace, createPage, router, exist]);
return ;
};
diff --git a/apps/web/src/pages/workspace/index.tsx b/apps/web/src/pages/workspace/index.tsx
index bbc89cdf83..b4c6fbf21b 100644
--- a/apps/web/src/pages/workspace/index.tsx
+++ b/apps/web/src/pages/workspace/index.tsx
@@ -1,23 +1,19 @@
import { useRouter } from 'next/router';
-import { useCallback, useEffect } from 'react';
+import { useEffect } from 'react';
import { PageLoading } from '@/components/loading';
-import useEnsureWorkspace from '@/hooks/use-ensure-workspace';
-import { useGlobalState } from '@/store/app';
+import { useRouterTargetWorkspace } from '@/hooks/use-router-target-workspace';
export const WorkspaceIndex = () => {
const router = useRouter();
- const currentWorkspace = useGlobalState(
- useCallback(store => store.currentDataCenterWorkspace, [])
- );
- const { workspaceLoaded } = useEnsureWorkspace();
-
+ const { targetWorkspace, exist } = useRouterTargetWorkspace();
useEffect(() => {
- if (workspaceLoaded) {
- router.push(`/workspace/${currentWorkspace?.id}`);
+ if (!exist) {
+ router.push('/404');
+ } else if (targetWorkspace) {
+ router.push(`/workspace/${targetWorkspace.id}`);
}
- }, [currentWorkspace, router, workspaceLoaded]);
-
+ }, [targetWorkspace, exist, router]);
return ;
};
diff --git a/packages/store/src/app/datacenter/index.tsx b/packages/store/src/app/datacenter/index.tsx
index f53dc12eb2..9f0a72caf6 100644
--- a/packages/store/src/app/datacenter/index.tsx
+++ b/packages/store/src/app/datacenter/index.tsx
@@ -116,6 +116,15 @@ export function useDataCenter() {
return data as DataCenter;
}
+export function useDataCenterWorkspace(
+ workspaceId: string | null
+): WorkspaceUnit | null {
+ const { data } = useSWR(['datacenter', workspaceId], {
+ fallbackData: null,
+ });
+ return data ?? null;
+}
+
export function DataCenterPreloader({ children }: React.PropsWithChildren) {
const api = useGlobalStateApi();
//# region effect for updating workspace page list
diff --git a/packages/store/src/app/index.tsx b/packages/store/src/app/index.tsx
index 4c3fa4e27a..3265ee2fb6 100644
--- a/packages/store/src/app/index.tsx
+++ b/packages/store/src/app/index.tsx
@@ -84,7 +84,7 @@ export const useGlobalState: UseBoundStore = ((
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}) as any;
-export type DataKey = ['datacenter', string] | ['datacenter'];
+export type DataKey = ['datacenter', string | null] | ['datacenter'];
const swrFetcher = async (keys: DataKey) => {
assertEquals(keys[0], 'datacenter');
@@ -96,6 +96,9 @@ const swrFetcher = async (keys: DataKey) => {
return dataCenter;
});
} else {
+ if (keys[1] === null) {
+ return null;
+ }
const dataCenter = await dataCenterPromise;
return dataCenter.loadWorkspace(keys[1]);
}
diff --git a/packages/store/src/index.ts b/packages/store/src/index.ts
index e9715f46d0..14b81b8956 100644
--- a/packages/store/src/index.ts
+++ b/packages/store/src/index.ts
@@ -1,4 +1,8 @@
export * from './app';
export type { PageMeta } from './app/datacenter';
export { createDefaultWorkspace, DataCenterPreloader } from './app/datacenter';
-export { dataCenterPromise, useDataCenter } from './app/datacenter';
+export {
+ dataCenterPromise,
+ useDataCenter,
+ useDataCenterWorkspace,
+} from './app/datacenter';