mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-14 05:14:54 +00:00
refactor: workspace manager (#5060)
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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 />;
|
||||
};
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user