fix: add router defend

This commit is contained in:
QiShaoXuan
2022-12-22 18:35:51 +08:00
parent cc910dd9e9
commit a64f77c9d2
18 changed files with 284 additions and 120 deletions
+2 -19
View File
@@ -1,20 +1,3 @@
import {
StyledLoadingWrapper,
StyledLoading,
StyledLoadingItem,
} from './styled';
export const Loading = ({ size = 40 }: { size?: number }) => {
return (
<StyledLoadingWrapper size={size}>
<StyledLoading>
<StyledLoadingItem size={size} />
<StyledLoadingItem size={size} />
<StyledLoadingItem size={size} />
<StyledLoadingItem size={size} />
</StyledLoading>
</StyledLoadingWrapper>
);
};
import Loading from './loading';
export * from './page-loading';
export default Loading;
@@ -0,0 +1,20 @@
import {
StyledLoadingWrapper,
StyledLoading,
StyledLoadingItem,
} from './styled';
export const Loading = ({ size = 40 }: { size?: number }) => {
return (
<StyledLoadingWrapper size={size}>
<StyledLoading>
<StyledLoadingItem size={size} />
<StyledLoadingItem size={size} />
<StyledLoadingItem size={size} />
<StyledLoadingItem size={size} />
</StyledLoading>
</StyledLoadingWrapper>
);
};
export default Loading;
@@ -0,0 +1,31 @@
import { styled } from '@/styles';
import Loading from './loading';
// Used for the full page loading
const StyledLoadingContainer = styled('div')(({ theme }) => {
return {
height: '100vh',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
color: '#6880FF',
h1: {
fontSize: '2em',
marginTop: '15px',
fontWeight: '600',
},
};
});
export const PageLoading = ({ text = 'Loading...' }: { text?: string }) => {
return (
<StyledLoadingContainer>
<div className="wrapper">
<Loading />
<h1>{text}</h1>
</div>
</StyledLoadingContainer>
);
};
export default PageLoading;
@@ -2,10 +2,43 @@ import HelpIsland from '@/components/help-island';
import { WorkSpaceSliderBar } from '@/components/workspace-slider-bar';
import { useRouter } from 'next/router';
import { StyledPage, StyledWrapper } from './styles';
import { PropsWithChildren } from 'react';
import { PropsWithChildren, useEffect, useState } from 'react';
import { useAppState } from '@/providers/app-state-provider';
export const WorkspaceDefender = ({ children }: PropsWithChildren) => {
const router = useRouter();
const [workspaceLoaded, setWorkspaceLoaded] = useState(false);
const { synced, loadWorkspace, workspacesMeta } = useAppState();
useEffect(() => {
const initWorkspace = async () => {
const workspaceId =
(router.query.workspaceId as string) ||
workspacesMeta?.[0]?.id ||
new Date().getTime().toString();
await loadWorkspace(workspaceId);
setWorkspaceLoaded(true);
};
if (!synced) {
return;
}
initWorkspace();
}, [
loadWorkspace,
router.pathname,
router.query.workspaceId,
synced,
workspaceLoaded,
workspacesMeta,
]);
return <>{workspaceLoaded ? children : null}</>;
};
export const WorkspaceLayout = ({ children }: PropsWithChildren) => {
const router = useRouter();
return (
<StyledPage>
<WorkSpaceSliderBar />
@@ -17,4 +50,11 @@ export const WorkspaceLayout = ({ children }: PropsWithChildren) => {
);
};
export default WorkspaceLayout;
export const Layout = ({ children }: PropsWithChildren) => {
return (
<WorkspaceDefender>
<WorkspaceLayout>{children}</WorkspaceLayout>
</WorkspaceDefender>
);
};
export default Layout;
@@ -140,7 +140,7 @@ export const WorkSpaceSliderBar = () => {
</Link>
<StyledNewPageButton
onClick={async () => {
const pageId = await createPage?.current?.();
const pageId = await createPage?.();
if (pageId) {
openPage(pageId);
}
+23 -6
View File
@@ -8,12 +8,15 @@ import '@fontsource/space-mono';
import '@fontsource/poppins';
import '../utils/print-build-info';
import ProviderComposer from '@/components/provider-composer';
import type { ReactElement, ReactNode } from 'react';
import type { PropsWithChildren, ReactElement, ReactNode } from 'react';
import type { NextPage } from 'next';
import { AppStateProvider } from '@/providers/app-state-provider/provider';
import ConfirmProvider from '@/providers/confirm-provider';
import { ModalProvider } from '@/providers/global-modal-provider';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
import { useAppState } from '@/providers/app-state-provider';
import { PageLoading } from '@/components/loading';
const ThemeProvider = dynamic(() => import('@/providers/themeProvider'), {
ssr: false,
});
@@ -26,7 +29,7 @@ type AppPropsWithLayout = AppProps & {
Component: NextPageWithLayout;
};
function MyApp({ Component, pageProps }: AppPropsWithLayout) {
const App = ({ Component, pageProps }: AppPropsWithLayout) => {
const getLayout = Component.getLayout || (page => page);
return (
@@ -40,10 +43,24 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
<ConfirmProvider key="ConfirmProvider" />,
]}
>
{getLayout(<Component {...pageProps} />)}
<AppDefender>{getLayout(<Component {...pageProps} />)}</AppDefender>
</ProviderComposer>
</>
);
}
};
export default MyApp;
const AppDefender = ({ children }: PropsWithChildren) => {
const router = useRouter();
const { synced } = useAppState();
useEffect(() => {
if (router.pathname === '/') {
router.replace('/workspace');
}
}, [router]);
return <div>{synced ? children : <PageLoading />}</div>;
};
export default App;
@@ -1,16 +1,19 @@
import { useRef, useEffect, ReactElement } from 'react';
import {
useRef,
useEffect,
useState,
ReactElement,
PropsWithChildren,
} from 'react';
import { styled } from '@/styles';
import { EditorHeader } from '@/components/header';
import EdgelessToolbar from '@/components/edgeless-toolbar';
import MobileModal from '@/components/mobile-modal';
import {
useLoadWorkspace,
useLoadPage,
} from '@/providers/app-state-provider/hooks';
import { useAppState } from '@/providers/app-state-provider/context';
import exampleMarkdown from '@/static/example-markdown';
import type { NextPageWithLayout } from '../..//_app';
import WorkspaceLayout from '@/components/workspace-layout';
import { useRouter } from 'next/router';
const StyledEditorContainer = styled('div')(({ theme }) => {
return {
@@ -20,9 +23,8 @@ const StyledEditorContainer = styled('div')(({ theme }) => {
const Page: NextPageWithLayout = () => {
const editorContainer = useRef<HTMLDivElement>(null);
const workspace = useLoadWorkspace();
const page = useLoadPage();
const { createEditor, setEditor } = useAppState();
const { createEditor, setEditor, currentPage, currentWorkspace } =
useAppState();
useEffect(() => {
const ret = () => {
@@ -32,29 +34,29 @@ const Page: NextPageWithLayout = () => {
}
};
if (!workspace || !page) {
return ret;
}
const editor = createEditor?.current?.(page);
const editor = createEditor?.current?.(currentPage!);
if (editor) {
editorContainer.current?.appendChild(editor);
setEditor?.current?.(editor);
if (page.isEmpty) {
if (currentPage!.isEmpty) {
const title = 'Welcome to the AFFiNE Alpha';
const pageId = page.addBlock({
const pageId = currentPage!.addBlock({
flavour: 'affine:page',
title,
});
const groupId = page.addBlock({ flavour: 'affine:group' }, pageId);
const groupId = currentPage!.addBlock(
{ flavour: 'affine:group' },
pageId
);
editor.clipboard.importMarkdown(exampleMarkdown, `${groupId}`);
workspace.setPageMeta(page.id, { title });
page.resetHistory();
currentWorkspace!.setPageMeta(currentPage!.id, { title });
currentPage!.resetHistory();
}
}
return ret;
}, [workspace, page, createEditor, setEditor]);
}, [currentWorkspace, currentPage, createEditor, setEditor]);
return (
<>
@@ -66,8 +68,34 @@ const Page: NextPageWithLayout = () => {
);
};
const PageDefender = ({ children }: PropsWithChildren) => {
const router = useRouter();
const [pageLoaded, setPageLoaded] = useState(false);
const { currentWorkspace, createPage, loadPage } = useAppState();
useEffect(() => {
const initPage = async () => {
const pageId = router.query.pageId as string;
const isPageExist = !!currentWorkspace!.meta.pageMetas.find(
p => p.id === pageId
);
if (!isPageExist) {
await createPage(pageId);
}
await loadPage(pageId);
setPageLoaded(true);
};
initPage();
}, [createPage, currentWorkspace, loadPage, router.query.pageId]);
return <>{pageLoaded ? children : null}</>;
};
Page.getLayout = function getLayout(page: ReactElement) {
return <WorkspaceLayout>{page}</WorkspaceLayout>;
return (
<WorkspaceLayout>
<PageDefender>{page}</PageDefender>
</WorkspaceLayout>
);
};
export default Page;
@@ -1,17 +1,14 @@
import { useLoadWorkspace } from '@/providers/app-state-provider/hooks';
import { PageList } from '@/components/page-list';
import { AllPagesIcon } from '@blocksuite/icons';
import usePageMetaList from '@/hooks/use-page-meta-list';
import { PageListHeader } from '@/components/header';
import { NextPageWithLayout } from '@/pages/_app';
import { ReactElement } from 'react';
import WorkspaceLayout from '@/components/workspace-layout';
const All: NextPageWithLayout = () => {
const workspace = useLoadWorkspace();
const All = () => {
const pageMetaList = usePageMetaList();
return workspace ? (
return (
<>
<PageListHeader icon={<AllPagesIcon />}>All Page</PageListHeader>
<PageList
@@ -19,7 +16,7 @@ const All: NextPageWithLayout = () => {
showFavoriteTag={true}
/>
</>
) : null;
);
};
All.getLayout = function getLayout(page: ReactElement) {
@@ -1,21 +1,18 @@
import { useLoadWorkspace } from '@/providers/app-state-provider/hooks';
import { PageListHeader } from '@/components/header';
import { PageList } from '@/components/page-list';
import { FavouritesIcon } from '@blocksuite/icons';
import usePageMetaList from '@/hooks/use-page-meta-list';
import { NextPageWithLayout } from '@/pages/_app';
import { ReactElement } from 'react';
import WorkspaceLayout from '@/components/workspace-layout';
export const Favorite: NextPageWithLayout = () => {
const workspace = useLoadWorkspace();
export const Favorite = () => {
const pageMetaList = usePageMetaList();
return workspace ? (
return (
<>
<PageListHeader icon={<FavouritesIcon />}>Favourites</PageListHeader>
<PageList pageList={pageMetaList.filter(p => p.favorite && !p.trash)} />
</>
) : null;
);
};
Favorite.getLayout = function getLayout(page: ReactElement) {
return <WorkspaceLayout>{page}</WorkspaceLayout>;
@@ -1,13 +1,31 @@
import {
useLoadWorkspace,
useLoadPage,
} from '@/providers/app-state-provider/hooks';
import React, { ReactElement, useEffect } from 'react';
import { useRouter } from 'next/router';
import { useAppState } from '@/providers/app-state-provider/context';
import WorkspaceLayout from '@/components/workspace-layout';
const Page = () => {
useLoadWorkspace();
useLoadPage();
const WorkspaceIndex = () => {
const router = useRouter();
const { createPage, currentWorkspaceId, currentWorkspace } = useAppState();
useEffect(() => {
const initPage = async () => {
const savedPageId = currentWorkspace!.meta.pageMetas[0]?.id;
if (savedPageId) {
router.replace(`/workspace/${currentWorkspaceId}/${savedPageId}`);
return;
}
const pageId = await createPage();
router.replace(`/workspace/${currentWorkspaceId}/${pageId}`);
};
initPage();
}, [currentWorkspace, currentWorkspaceId, createPage, router]);
return <></>;
};
export default Page;
WorkspaceIndex.getLayout = function getLayout(page: ReactElement) {
return <WorkspaceLayout>{page}</WorkspaceLayout>;
};
export default WorkspaceIndex;
@@ -1,21 +1,18 @@
import { useLoadWorkspace } from '@/providers/app-state-provider/hooks';
import { PageListHeader } from '@/components/header';
import { PageList } from '@/components/page-list';
import { TrashIcon } from '@blocksuite/icons';
import usePageMetaList from '@/hooks/use-page-meta-list';
import { NextPageWithLayout } from '@/pages/_app';
import { ReactElement } from 'react';
import WorkspaceLayout from '@/components/workspace-layout';
export const Trash: NextPageWithLayout = () => {
const workspace = useLoadWorkspace();
export const Trash = () => {
const pageMetaList = usePageMetaList();
return workspace ? (
return (
<>
<PageListHeader icon={<TrashIcon />}>Trash</PageListHeader>
<PageList pageList={pageMetaList.filter(p => p.trash)} isTrash={true} />
</>
) : null;
);
};
Trash.getLayout = function getLayout(page: ReactElement) {
+19 -3
View File
@@ -1,5 +1,21 @@
const Page = () => {
return <div>Hello, there is [workspace]/index.</div>;
import React, { ReactElement, useEffect } from 'react';
import WorkspaceLayout from '@/components/workspace-layout';
import { useAppState } from '@/providers/app-state-provider';
import { useRouter } from 'next/router';
export const WorkspaceIndex = () => {
const router = useRouter();
const { currentWorkspaceId } = useAppState();
useEffect(() => {
if (currentWorkspaceId) {
router.replace(`/workspace/${currentWorkspaceId}`);
}
}, [currentWorkspaceId, router]);
return <></>;
};
export default Page;
WorkspaceIndex.getLayout = function getLayout(page: ReactElement) {
return <WorkspaceLayout>{page}</WorkspaceLayout>;
};
export default WorkspaceIndex;
@@ -23,6 +23,7 @@ export interface AppStateValue {
currentPage: StorePage | null;
editor: EditorContainer | null;
synced: boolean;
}
export interface AppStateContext extends AppStateValue {
@@ -31,15 +32,9 @@ export interface AppStateContext extends AppStateValue {
((page: StorePage) => EditorContainer | null) | undefined
>;
setEditor?: MutableRefObject<((page: EditorContainer) => void) | undefined>;
loadWorkspace?: MutableRefObject<
((workspaceId: string) => Promise<StoreWorkspace | null> | null) | undefined
>;
loadPage?: MutableRefObject<
((pageId: string) => Promise<StorePage | null> | null) | undefined
>;
createPage?: MutableRefObject<
((pageId?: string) => Promise<string | null>) | undefined
>;
loadWorkspace: (workspaceId: string) => Promise<StoreWorkspace | null>;
loadPage: (pageId: string) => Promise<StorePage | null>;
createPage: (pageId?: string) => Promise<string | null>;
}
export const AppState = createContext<AppStateContext>({
@@ -56,9 +51,10 @@ export const AppState = createContext<AppStateContext>({
setState: () => {},
createEditor: undefined,
setEditor: undefined,
loadWorkspace: undefined,
loadPage: undefined,
createPage: undefined,
loadWorkspace: () => Promise.resolve(null),
loadPage: () => Promise.resolve(null),
createPage: () => Promise.resolve(null),
synced: false,
workspaces: {},
});
@@ -89,15 +89,12 @@ const DynamicBlocksuite = ({
p => p instanceof IndexedDBDocProvider
);
if (indexDBProvider) {
(indexDBProvider as IndexedDBDocProvider).on('synced', async () => {
const updates = await downloadWorkspace({ workspaceId });
if (updates && updates.byteLength) {
Workspace.Y.applyUpdate(workspace.doc, new Uint8Array(updates));
// if after update, the space:meta is empty, then we need to get map with doc
workspace.doc.getMap('space:meta');
}
(indexDBProvider as IndexedDBDocProvider)?.on('synced', async () => {
// const updates = await downloadWorkspace({ workspaceId });
// updates &&
// Workspace.Y.applyUpdate(workspace.doc, new Uint8Array(updates));
// if after update, the space:meta is empty, then we need to get map with doc
workspace.doc.getMap('space:meta');
resolve(workspace);
});
} else {
@@ -9,7 +9,7 @@ export const useLoadWorkspace = () => {
const workspaceId = router.query.workspaceId as string;
useEffect(() => {
loadWorkspace?.current?.(workspaceId);
loadWorkspace?.(workspaceId);
}, [workspaceId, loadWorkspace]);
return currentWorkspaceId === workspaceId ? currentWorkspace : null;
@@ -29,7 +29,7 @@ export const useLoadPage = () => {
}
const page = pageId ? workspace?.getPage(pageId) : null;
if (page) {
loadPage?.current?.(pageId);
loadPage?.(pageId);
return;
}
@@ -39,7 +39,7 @@ export const useLoadPage = () => {
return;
}
createPage?.current?.()?.then(async pageId => {
createPage?.()?.then(async pageId => {
if (!pageId) {
return;
}
@@ -8,7 +8,7 @@ import {
WorkspaceType,
getWorkspaceDetail,
} from '@pathfinder/data-services';
import { AppState } from './context';
import { AppState, AppStateContext } from './context';
import type {
AppStateValue,
CreateEditorHandler,
@@ -29,6 +29,9 @@ export const AppStateProvider = ({ children }: { children?: ReactNode }) => {
currentPage: null,
editor: null,
workspaces: {},
// Synced is used to ensure that the provider has synced with the server,
// So after Synced set to true, the other state is sure to be set.
synced: false,
});
const [loadWorkspaceHandler, _setLoadWorkspaceHandler] =
@@ -50,12 +53,14 @@ export const AppStateProvider = ({ children }: { children?: ReactNode }) => {
[_setCreateEditorHandler]
);
const loadWorkspace =
useRef<(workspaceId: string) => Promise<Workspace | null> | null>();
const loadWorkspace = useRef<AppStateContext['loadWorkspace']>(() =>
Promise.resolve(null)
);
loadWorkspace.current = async (workspaceId: string) => {
if (state.currentWorkspaceId === workspaceId) {
return state.currentWorkspace;
}
const workspace = (await loadWorkspaceHandler?.(workspaceId, true)) || null;
// @ts-expect-error
@@ -72,7 +77,9 @@ export const AppStateProvider = ({ children }: { children?: ReactNode }) => {
}));
return workspace;
};
const loadPage = useRef<(pageId: string) => Promise<Page | null> | null>();
const loadPage = useRef<AppStateContext['loadPage']>(() =>
Promise.resolve(null)
);
loadPage.current = async (pageId: string) => {
const { currentWorkspace, currentPage } = state;
if (pageId === currentPage?.pageId) {
@@ -114,7 +121,9 @@ export const AppStateProvider = ({ children }: { children?: ReactNode }) => {
setState(state => ({ ...state, editor }));
};
const createPage = useRef<(pageId?: string) => Promise<string | null>>();
const createPage = useRef<AppStateContext['createPage']>(() =>
Promise.resolve(null)
);
createPage.current = (pageId: string = Date.now().toString()) =>
new Promise<string | null>(resolve => {
@@ -130,8 +139,16 @@ export const AppStateProvider = ({ children }: { children?: ReactNode }) => {
});
useEffect(() => {
if (!loadWorkspaceHandler) {
return;
}
const callback = async (user: AccessTokenMessage | null) => {
const workspacesMeta = user ? await getWorkspaces() : [];
const workspacesMeta = user
? await getWorkspaces().catch(() => {
return [];
})
: [];
const workspacesList = await Promise.all(
workspacesMeta.map(async ({ id }) => {
const workspace = (await loadWorkspaceHandler?.(id)) || null;
@@ -146,7 +163,13 @@ export const AppStateProvider = ({ children }: { children?: ReactNode }) => {
});
// TODO: add meta info to workspace meta
setState(state => ({ ...state, user: user, workspacesMeta, workspaces }));
setState(state => ({
...state,
user: user,
workspacesMeta,
workspaces,
synced: true,
}));
};
authorizationEvent.onChange(callback);
@@ -161,9 +184,9 @@ export const AppStateProvider = ({ children }: { children?: ReactNode }) => {
setState,
createEditor,
setEditor,
loadWorkspace,
loadPage,
createPage,
loadWorkspace: loadWorkspace.current,
loadPage: loadPage.current,
createPage: createPage.current,
}),
[state, setState, loadPage, loadWorkspace]
);
@@ -174,7 +197,7 @@ export const AppStateProvider = ({ children }: { children?: ReactNode }) => {
setLoadWorkspaceHandler={setLoadWorkspaceHandler}
setCreateEditorHandler={setCreateEditorHandler}
/>
{loadWorkspaceHandler ? children : null}
{children}
</AppState.Provider>
);
};
@@ -1,4 +1,4 @@
import { createContext, useContext, useState } from 'react';
import { createContext, useContext, useEffect, useState } from 'react';
import type { PropsWithChildren } from 'react';
import ShortcutsModal from '@/components/shortcuts-modal';
import ContactModal from '@/components/contact-modal';
@@ -49,6 +49,10 @@ export const ModalProvider = ({
[key]: visible ?? !modalMap[key],
});
};
useEffect(() => {
// @ts-ignore
window.triggerHandler = () => triggerHandler('login');
}, [triggerHandler]);
return (
<ModalContext.Provider