mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-12-22 23:01:35 +03:00
revert: loadWorkspace unexpected behavior (#1172)
This commit is contained in:
parent
86346b284e
commit
0b072da346
@ -0,0 +1,80 @@
|
|||||||
|
import { Modal, ModalWrapper } from '@affine/component';
|
||||||
|
import { IconButton } from '@affine/component';
|
||||||
|
import { toast } from '@affine/component';
|
||||||
|
import { useTranslation } from '@affine/i18n';
|
||||||
|
import { CloseIcon } from '@blocksuite/icons';
|
||||||
|
import router from 'next/router';
|
||||||
|
import { useCallback, useState } from 'react';
|
||||||
|
|
||||||
|
import { useGlobalState } from '@/store/app';
|
||||||
|
|
||||||
|
import { Content, ContentTitle, Header, StyleButton, StyleTips } from './style';
|
||||||
|
interface EnableWorkspaceModalProps {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EnableWorkspaceModal = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
}: EnableWorkspaceModalProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const login = useGlobalState(store => store.login);
|
||||||
|
const user = useGlobalState(store => store.user);
|
||||||
|
const dataCenter = useGlobalState(store => store.dataCenter);
|
||||||
|
const currentWorkspace = useGlobalState(
|
||||||
|
useCallback(store => store.currentDataCenterWorkspace, [])
|
||||||
|
);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
return (
|
||||||
|
<Modal open={open} onClose={onClose} data-testid="logout-modal">
|
||||||
|
<ModalWrapper width={560} height={292}>
|
||||||
|
<Header>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Header>
|
||||||
|
<Content>
|
||||||
|
<ContentTitle>{t('Enable AFFiNE Cloud')}?</ContentTitle>
|
||||||
|
<StyleTips>{t('Enable AFFiNE Cloud Description')}</StyleTips>
|
||||||
|
{/* <StyleTips>{t('Retain local cached data')}</StyleTips> */}
|
||||||
|
<div>
|
||||||
|
<StyleButton
|
||||||
|
shape="round"
|
||||||
|
type="primary"
|
||||||
|
loading={loading}
|
||||||
|
onClick={async () => {
|
||||||
|
setLoading(true);
|
||||||
|
if (!user) {
|
||||||
|
await login();
|
||||||
|
}
|
||||||
|
if (currentWorkspace) {
|
||||||
|
const workspace = await dataCenter.enableWorkspaceCloud(
|
||||||
|
currentWorkspace
|
||||||
|
);
|
||||||
|
workspace &&
|
||||||
|
router.push(`/workspace/${workspace.id}/setting`);
|
||||||
|
toast(t('Enabled success'));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{user ? t('Enable') : t('Sign in and Enable')}
|
||||||
|
</StyleButton>
|
||||||
|
<StyleButton
|
||||||
|
shape="round"
|
||||||
|
onClick={() => {
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Not now')}
|
||||||
|
</StyleButton>
|
||||||
|
</div>
|
||||||
|
</Content>
|
||||||
|
</ModalWrapper>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
@ -12,7 +12,6 @@
|
|||||||
"@affine/component": "workspace:*",
|
"@affine/component": "workspace:*",
|
||||||
"@affine/datacenter": "workspace:*",
|
"@affine/datacenter": "workspace:*",
|
||||||
"@affine/i18n": "workspace:*",
|
"@affine/i18n": "workspace:*",
|
||||||
"@affine/store": "workspace:*",
|
|
||||||
"@affine/debug": "workspace:*",
|
"@affine/debug": "workspace:*",
|
||||||
"@blocksuite/blocks": "0.4.1",
|
"@blocksuite/blocks": "0.4.1",
|
||||||
"@blocksuite/editor": "0.4.1",
|
"@blocksuite/editor": "0.4.1",
|
||||||
|
@ -4,7 +4,7 @@ import { CloseIcon } from '@blocksuite/icons';
|
|||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
|
|
||||||
import { useDataCenter, useGlobalState } from '@/store/app';
|
import { useGlobalState } from '@/store/app';
|
||||||
|
|
||||||
import { Content, ContentTitle, Header, StyleButton, StyleTips } from './style';
|
import { Content, ContentTitle, Header, StyleButton, StyleTips } from './style';
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ export const EnableWorkspaceModal = ({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const login = useGlobalState(store => store.login);
|
const login = useGlobalState(store => store.login);
|
||||||
const user = useGlobalState(store => store.user);
|
const user = useGlobalState(store => store.user);
|
||||||
const dataCenter = useDataCenter();
|
const dataCenter = useGlobalState(store => store.dataCenter);
|
||||||
const currentWorkspace = useGlobalState(
|
const currentWorkspace = useGlobalState(
|
||||||
useCallback(store => store.currentDataCenterWorkspace, [])
|
useCallback(store => store.currentDataCenterWorkspace, [])
|
||||||
);
|
);
|
||||||
|
@ -2,9 +2,10 @@ import { styled } from '@affine/component';
|
|||||||
import { Modal, ModalCloseButton, ModalWrapper } from '@affine/component';
|
import { Modal, ModalCloseButton, ModalWrapper } from '@affine/component';
|
||||||
import { Button } from '@affine/component';
|
import { Button } from '@affine/component';
|
||||||
import { useTranslation } from '@affine/i18n';
|
import { useTranslation } from '@affine/i18n';
|
||||||
import { useGlobalState } from '@affine/store';
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
import { useAppState } from '@/providers/app-state-provider';
|
||||||
|
|
||||||
import { Check, UnCheck } from './icon';
|
import { Check, UnCheck } from './icon';
|
||||||
interface LoginModalProps {
|
interface LoginModalProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -13,7 +14,7 @@ interface LoginModalProps {
|
|||||||
|
|
||||||
export const LogoutModal = ({ open, onClose }: LoginModalProps) => {
|
export const LogoutModal = ({ open, onClose }: LoginModalProps) => {
|
||||||
const [localCache, setLocalCache] = useState(true);
|
const [localCache, setLocalCache] = useState(true);
|
||||||
const blobDataSynced = useGlobalState(store => store.blobDataSynced);
|
const { blobDataSynced } = useAppState();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -2,13 +2,13 @@ import { toast } from '@affine/component';
|
|||||||
import { MessageCenter } from '@affine/datacenter';
|
import { MessageCenter } from '@affine/datacenter';
|
||||||
import { AffineProvider } from '@affine/datacenter';
|
import { AffineProvider } from '@affine/datacenter';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { ReactNode, useEffect } from 'react';
|
import { ReactNode, useCallback, useEffect } from 'react';
|
||||||
|
|
||||||
import { useDataCenter } from '@/store/app';
|
import { useGlobalState } from '@/store/app';
|
||||||
|
|
||||||
export function MessageCenterHandler({ children }: { children?: ReactNode }) {
|
export function MessageCenterHandler({ children }: { children?: ReactNode }) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const dataCenter = useDataCenter();
|
const dataCenter = useGlobalState(useCallback(store => store.dataCenter, []));
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const instance = MessageCenter.getInstance();
|
const instance = MessageCenter.getInstance();
|
||||||
if (instance) {
|
if (instance) {
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { TableCell } from '@affine/component';
|
import { TableCell } from '@affine/component';
|
||||||
import type { PageMeta } from '@affine/store';
|
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import { PageMeta } from '@/providers/app-state-provider';
|
||||||
|
|
||||||
dayjs.extend(localizedFormat);
|
dayjs.extend(localizedFormat);
|
||||||
|
|
||||||
export const DateCell = ({
|
export const DateCell = ({
|
||||||
|
@ -7,7 +7,6 @@ import {
|
|||||||
} from '@affine/component';
|
} from '@affine/component';
|
||||||
import { toast } from '@affine/component';
|
import { toast } from '@affine/component';
|
||||||
import { useTranslation } from '@affine/i18n';
|
import { useTranslation } from '@affine/i18n';
|
||||||
import { PageMeta } from '@affine/store';
|
|
||||||
import {
|
import {
|
||||||
DeleteForeverIcon,
|
DeleteForeverIcon,
|
||||||
FavouritedIcon,
|
FavouritedIcon,
|
||||||
@ -19,6 +18,7 @@ import {
|
|||||||
} from '@blocksuite/icons';
|
} from '@blocksuite/icons';
|
||||||
|
|
||||||
import { usePageHelper } from '@/hooks/use-page-helper';
|
import { usePageHelper } from '@/hooks/use-page-helper';
|
||||||
|
import { PageMeta } from '@/providers/app-state-provider';
|
||||||
import { useConfirm } from '@/providers/ConfirmProvider';
|
import { useConfirm } from '@/providers/ConfirmProvider';
|
||||||
|
|
||||||
export const OperationCell = ({ pageMeta }: { pageMeta: PageMeta }) => {
|
export const OperationCell = ({ pageMeta }: { pageMeta: PageMeta }) => {
|
||||||
|
@ -10,7 +10,6 @@ import { IconButton } from '@affine/component';
|
|||||||
import { Tooltip } from '@affine/component';
|
import { Tooltip } from '@affine/component';
|
||||||
import { toast } from '@affine/component';
|
import { toast } from '@affine/component';
|
||||||
import { useTranslation } from '@affine/i18n';
|
import { useTranslation } from '@affine/i18n';
|
||||||
import { PageMeta } from '@affine/store';
|
|
||||||
import {
|
import {
|
||||||
EdgelessIcon,
|
EdgelessIcon,
|
||||||
FavouritedIcon,
|
FavouritedIcon,
|
||||||
@ -22,6 +21,7 @@ import React, { useCallback } from 'react';
|
|||||||
|
|
||||||
import DateCell from '@/components/page-list/DateCell';
|
import DateCell from '@/components/page-list/DateCell';
|
||||||
import { usePageHelper } from '@/hooks/use-page-helper';
|
import { usePageHelper } from '@/hooks/use-page-helper';
|
||||||
|
import { PageMeta } from '@/providers/app-state-provider';
|
||||||
import { useTheme } from '@/providers/ThemeProvider';
|
import { useTheme } from '@/providers/ThemeProvider';
|
||||||
import { useGlobalState } from '@/store/app';
|
import { useGlobalState } from '@/store/app';
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { useTranslation } from '@affine/i18n';
|
import { useTranslation } from '@affine/i18n';
|
||||||
import { PageMeta, useDataCenter } from '@affine/store';
|
|
||||||
import { EdgelessIcon, PaperIcon } from '@blocksuite/icons';
|
import { EdgelessIcon, PaperIcon } from '@blocksuite/icons';
|
||||||
import { Workspace } from '@blocksuite/store';
|
import { Workspace } from '@blocksuite/store';
|
||||||
import { Command } from 'cmdk';
|
import { Command } from 'cmdk';
|
||||||
@ -7,6 +6,8 @@ import { useRouter } from 'next/router';
|
|||||||
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
|
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import usePageHelper from '@/hooks/use-page-helper';
|
import usePageHelper from '@/hooks/use-page-helper';
|
||||||
|
import { PageMeta } from '@/providers/app-state-provider';
|
||||||
|
import { useGlobalState } from '@/store/app';
|
||||||
|
|
||||||
import { NoResultSVG } from './NoResultSVG';
|
import { NoResultSVG } from './NoResultSVG';
|
||||||
import { StyledListItem, StyledNotFound } from './style';
|
import { StyledListItem, StyledNotFound } from './style';
|
||||||
@ -23,7 +24,7 @@ export const PublishedResults = (props: {
|
|||||||
props;
|
props;
|
||||||
const { search } = usePageHelper();
|
const { search } = usePageHelper();
|
||||||
const [results, setResults] = useState(new Map<string, string | undefined>());
|
const [results, setResults] = useState(new Map<string, string | undefined>());
|
||||||
const dataCenter = useDataCenter();
|
const dataCenter = useGlobalState(store => store.dataCenter);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [pageList, setPageList] = useState<PageMeta[]>([]);
|
const [pageList, setPageList] = useState<PageMeta[]>([]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -1,14 +1,18 @@
|
|||||||
import { useGlobalState } from '@affine/store';
|
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { PropsWithChildren, useEffect } from 'react';
|
import { PropsWithChildren } from 'react';
|
||||||
|
|
||||||
import HelpIsland from '@/components/help-island';
|
import HelpIsland from '@/components/help-island';
|
||||||
import { WorkSpaceSliderBar } from '@/components/workspace-slider-bar';
|
import { WorkSpaceSliderBar } from '@/components/workspace-slider-bar';
|
||||||
import { useRouterTargetWorkspace } from '@/hooks/use-router-target-workspace';
|
import useEnsureWorkspace from '@/hooks/use-ensure-workspace';
|
||||||
|
|
||||||
import { PageLoading } from '../loading';
|
import { PageLoading } from '../loading';
|
||||||
import { StyledPage, StyledToolWrapper, StyledWrapper } from './styles';
|
import { StyledPage, StyledToolWrapper, StyledWrapper } from './styles';
|
||||||
|
|
||||||
|
export const WorkspaceDefender = ({ children }: PropsWithChildren) => {
|
||||||
|
const { workspaceLoaded } = useEnsureWorkspace();
|
||||||
|
return <>{workspaceLoaded ? children : <PageLoading />}</>;
|
||||||
|
};
|
||||||
|
|
||||||
export const WorkspaceLayout = ({ children }: PropsWithChildren) => {
|
export const WorkspaceLayout = ({ children }: PropsWithChildren) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@ -31,22 +35,10 @@ export const WorkspaceLayout = ({ children }: PropsWithChildren) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const Layout = ({ children }: PropsWithChildren) => {
|
export const Layout = ({ children }: PropsWithChildren) => {
|
||||||
const { targetWorkspace, exist } = useRouterTargetWorkspace();
|
return (
|
||||||
const router = useRouter();
|
<WorkspaceDefender>
|
||||||
const loadWorkspace = useGlobalState(store => store.loadWorkspace);
|
<WorkspaceLayout>{children}</WorkspaceLayout>
|
||||||
useEffect(() => {
|
</WorkspaceDefender>
|
||||||
if (!exist) {
|
);
|
||||||
router.replace('/404');
|
|
||||||
}
|
|
||||||
}, [exist, router]);
|
|
||||||
useEffect(() => {
|
|
||||||
if (exist && targetWorkspace) {
|
|
||||||
loadWorkspace(targetWorkspace.id);
|
|
||||||
}
|
|
||||||
}, [exist, loadWorkspace, targetWorkspace]);
|
|
||||||
if (!targetWorkspace) {
|
|
||||||
return <PageLoading />;
|
|
||||||
}
|
|
||||||
return <WorkspaceLayout>{children}</WorkspaceLayout>;
|
|
||||||
};
|
};
|
||||||
export default Layout;
|
export default Layout;
|
||||||
|
@ -5,7 +5,7 @@ import { HelpIcon, PlusIcon } from '@blocksuite/icons';
|
|||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { useDataCenter, useGlobalState } from '@/store/app';
|
import { useGlobalState } from '@/store/app';
|
||||||
|
|
||||||
import { CreateWorkspaceModal } from '../create-workspace';
|
import { CreateWorkspaceModal } from '../create-workspace';
|
||||||
import { LoginModal } from '../login-modal';
|
import { LoginModal } from '../login-modal';
|
||||||
@ -34,7 +34,7 @@ interface WorkspaceModalProps {
|
|||||||
export const WorkspaceModal = ({ open, onClose }: WorkspaceModalProps) => {
|
export const WorkspaceModal = ({ open, onClose }: WorkspaceModalProps) => {
|
||||||
const [createWorkspaceOpen, setCreateWorkspaceOpen] = useState(false);
|
const [createWorkspaceOpen, setCreateWorkspaceOpen] = useState(false);
|
||||||
const logout = useGlobalState(store => store.logout);
|
const logout = useGlobalState(store => store.logout);
|
||||||
const dataCenter = useDataCenter();
|
const dataCenter = useGlobalState(store => store.dataCenter);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [loginOpen, setLoginOpen] = useState(false);
|
const [loginOpen, setLoginOpen] = useState(false);
|
||||||
|
@ -11,6 +11,8 @@ import {
|
|||||||
StyledModalWrapper,
|
StyledModalWrapper,
|
||||||
StyledTextContent,
|
StyledTextContent,
|
||||||
} from './style';
|
} from './style';
|
||||||
|
// import { getDataCenter } from '@affine/datacenter';
|
||||||
|
// import { useAppState } from '@/providers/app-state-provider';
|
||||||
|
|
||||||
interface WorkspaceDeleteProps {
|
interface WorkspaceDeleteProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
@ -2,7 +2,7 @@ import { useCallback, useState } from 'react';
|
|||||||
|
|
||||||
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
|
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
|
||||||
import { WorkspaceModal } from '@/components/workspace-modal';
|
import { WorkspaceModal } from '@/components/workspace-modal';
|
||||||
import { useDataCenter, useGlobalState } from '@/store/app';
|
import { useGlobalState } from '@/store/app';
|
||||||
|
|
||||||
import { SelectorWrapper, WorkspaceName } from './styles';
|
import { SelectorWrapper, WorkspaceName } from './styles';
|
||||||
|
|
||||||
@ -11,7 +11,7 @@ export const WorkspaceSelector = () => {
|
|||||||
const currentWorkspace = useGlobalState(
|
const currentWorkspace = useGlobalState(
|
||||||
useCallback(store => store.currentDataCenterWorkspace, [])
|
useCallback(store => store.currentDataCenterWorkspace, [])
|
||||||
);
|
);
|
||||||
const dataCenter = useDataCenter();
|
const dataCenter = useGlobalState(useCallback(store => store.dataCenter, []));
|
||||||
|
|
||||||
if (dataCenter.workspaces.length === 0) {
|
if (dataCenter.workspaces.length === 0) {
|
||||||
setWorkspaceListShow(true);
|
setWorkspaceListShow(true);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { PageMeta } from '@affine/store';
|
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { PageMeta } from '@/providers/app-state-provider';
|
||||||
import { useGlobalState } from '@/store/app';
|
import { useGlobalState } from '@/store/app';
|
||||||
|
|
||||||
export type ChangePageMeta = (
|
export type ChangePageMeta = (
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { PageMeta } from '@affine/store';
|
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { PageMeta } from '@/providers/app-state-provider';
|
||||||
import { useGlobalState } from '@/store/app';
|
import { useGlobalState } from '@/store/app';
|
||||||
|
|
||||||
export const useCurrentPageMeta = (): PageMeta | null => {
|
export const useCurrentPageMeta = (): PageMeta | null => {
|
||||||
|
73
apps/web/src/hooks/use-ensure-workspace.ts
Normal file
73
apps/web/src/hooks/use-ensure-workspace.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import { assertEquals } from '@blocksuite/global/utils';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { 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 = useGlobalState(useCallback(store => store.dataCenter, []));
|
||||||
|
const currentWorkspace = useGlobalState(
|
||||||
|
useCallback(store => store.currentDataCenterWorkspace, [])
|
||||||
|
);
|
||||||
|
const loadWorkspace = useGlobalState(
|
||||||
|
useCallback(store => store.loadWorkspace, [])
|
||||||
|
);
|
||||||
|
const router = useRouter();
|
||||||
|
const [currentWorkspaceId, setCurrentWorkspaceId] = useState<string | null>(
|
||||||
|
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;
|
35
apps/web/src/hooks/use-load-public-workspace.ts
Normal file
35
apps/web/src/hooks/use-load-public-workspace.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { getDataCenter, WorkspaceUnit } from '@affine/datacenter';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
export function useLoadPublicWorkspace(workspaceId: string) {
|
||||||
|
const router = useRouter();
|
||||||
|
const [workspace, setWorkspace] = useState<WorkspaceUnit | null>();
|
||||||
|
const [status, setStatus] = useState<'loading' | 'error' | 'success'>(
|
||||||
|
'loading'
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setStatus('loading');
|
||||||
|
|
||||||
|
const init = async () => {
|
||||||
|
const dataCenter = await getDataCenter();
|
||||||
|
|
||||||
|
dataCenter
|
||||||
|
.loadPublicWorkspace(workspaceId)
|
||||||
|
.then(data => {
|
||||||
|
setWorkspace(data);
|
||||||
|
setStatus('success');
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// if (!cancel) {
|
||||||
|
// router.push('/404');
|
||||||
|
// }
|
||||||
|
setStatus('error');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
init();
|
||||||
|
}, [router, workspaceId]);
|
||||||
|
|
||||||
|
return { status, workspace };
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
import { Member } from '@affine/datacenter';
|
import { Member } from '@affine/datacenter';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { useDataCenter, useGlobalState } from '@/store/app';
|
import { useGlobalState } from '@/store/app';
|
||||||
export const useMembers = () => {
|
export const useMembers = () => {
|
||||||
const dataCenter = useDataCenter();
|
const dataCenter = useGlobalState(store => store.dataCenter);
|
||||||
const currentWorkspace = useGlobalState(
|
const currentWorkspace = useGlobalState(
|
||||||
useCallback(store => store.currentDataCenterWorkspace, [])
|
useCallback(store => store.currentDataCenterWorkspace, [])
|
||||||
);
|
);
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { WorkspaceUnit } from '@affine/datacenter';
|
import { WorkspaceUnit } from '@affine/datacenter';
|
||||||
import { PageMeta } from '@affine/store';
|
|
||||||
import { EditorContainer } from '@blocksuite/editor';
|
import { EditorContainer } from '@blocksuite/editor';
|
||||||
import { uuidv4, Workspace } from '@blocksuite/store';
|
import { uuidv4, Workspace } from '@blocksuite/store';
|
||||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||||
import type { QueryContent } from '@blocksuite/store/dist/workspace/search';
|
import type { QueryContent } from '@blocksuite/store/dist/workspace/search';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import { useChangePageMeta } from '@/hooks/use-change-page-meta';
|
import { useChangePageMeta } from '@/hooks/use-change-page-meta';
|
||||||
|
import { PageMeta } from '@/providers/app-state-provider';
|
||||||
import { useGlobalState } from '@/store/app';
|
import { useGlobalState } from '@/store/app';
|
||||||
|
|
||||||
export type EditorHandlers = {
|
export type EditorHandlers = {
|
||||||
@ -48,8 +48,7 @@ export const usePageHelper = (): EditorHandlers => {
|
|||||||
useCallback(store => store.currentDataCenterWorkspace, [])
|
useCallback(store => store.currentDataCenterWorkspace, [])
|
||||||
);
|
);
|
||||||
|
|
||||||
return useMemo(
|
return {
|
||||||
() => ({
|
|
||||||
createPage: ({
|
createPage: ({
|
||||||
pageId = uuidv4().replaceAll('-', ''),
|
pageId = uuidv4().replaceAll('-', ''),
|
||||||
title = '',
|
title = '',
|
||||||
@ -146,9 +145,7 @@ export const usePageHelper = (): EditorHandlers => {
|
|||||||
) as PageMeta) || null
|
) as PageMeta) || null
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
}),
|
};
|
||||||
[changePageMeta, currentWorkspace, editor, router]
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default usePageHelper;
|
export default usePageHelper;
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
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,
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,10 +1,10 @@
|
|||||||
import { WorkspaceUnit } from '@affine/datacenter';
|
import { WorkspaceUnit } from '@affine/datacenter';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import { useDataCenter, useGlobalState } from '@/store/app';
|
import { useGlobalState } from '@/store/app';
|
||||||
|
|
||||||
export const useWorkspaceHelper = () => {
|
export const useWorkspaceHelper = () => {
|
||||||
const dataCenter = useDataCenter();
|
const dataCenter = useGlobalState(store => store.dataCenter);
|
||||||
const currentWorkspace = useGlobalState(
|
const currentWorkspace = useGlobalState(
|
||||||
useCallback(store => store.currentDataCenterWorkspace, [])
|
useCallback(store => store.currentDataCenterWorkspace, [])
|
||||||
);
|
);
|
||||||
|
@ -6,22 +6,24 @@ import '../utils/print-build-info';
|
|||||||
import '@affine/i18n';
|
import '@affine/i18n';
|
||||||
|
|
||||||
import { useTranslation } from '@affine/i18n';
|
import { useTranslation } from '@affine/i18n';
|
||||||
import { DataCenterPreloader } from '@affine/store';
|
|
||||||
import { Logger } from '@toeverything/pathfinder-logger';
|
import { Logger } from '@toeverything/pathfinder-logger';
|
||||||
import type { NextPage } from 'next';
|
import type { NextPage } from 'next';
|
||||||
import type { AppProps } from 'next/app';
|
import type { AppProps } from 'next/app';
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
// import AppStateProvider2 from '@/providers/app-state-provider2/provider';
|
// import AppStateProvider2 from '@/providers/app-state-provider2/provider';
|
||||||
import type { ReactElement, ReactNode } from 'react';
|
import { useRouter } from 'next/router';
|
||||||
import { Suspense } from 'react';
|
import type { PropsWithChildren, ReactElement, ReactNode } from 'react';
|
||||||
|
import { Suspense, useEffect } from 'react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { PageLoading } from '@/components/loading';
|
import { PageLoading } from '@/components/loading';
|
||||||
import { MessageCenterHandler } from '@/components/message-center-handler';
|
import { MessageCenterHandler } from '@/components/message-center-handler';
|
||||||
import ProviderComposer from '@/components/provider-composer';
|
import ProviderComposer from '@/components/provider-composer';
|
||||||
|
import { AppStateProvider } from '@/providers/app-state-provider';
|
||||||
import ConfirmProvider from '@/providers/ConfirmProvider';
|
import ConfirmProvider from '@/providers/ConfirmProvider';
|
||||||
import { ThemeProvider } from '@/providers/ThemeProvider';
|
import { ThemeProvider } from '@/providers/ThemeProvider';
|
||||||
import { GlobalAppProvider } from '@/store/app';
|
import { GlobalAppProvider } from '@/store/app';
|
||||||
|
import { DataCenterPreloader } from '@/store/app/datacenter';
|
||||||
import { ModalProvider } from '@/store/globalModal';
|
import { ModalProvider } from '@/store/globalModal';
|
||||||
|
|
||||||
export type NextPageWithLayout<P = Record<string, unknown>, IP = P> = NextPage<
|
export type NextPageWithLayout<P = Record<string, unknown>, IP = P> = NextPage<
|
||||||
@ -35,9 +37,16 @@ type AppPropsWithLayout = AppProps & {
|
|||||||
Component: NextPageWithLayout;
|
Component: NextPageWithLayout;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Page list which do not rely on app state
|
||||||
|
const NoNeedAppStatePageList = [
|
||||||
|
'/404',
|
||||||
|
'/public-workspace/[workspaceId]',
|
||||||
|
'/public-workspace/[workspaceId]/[pageId]',
|
||||||
|
];
|
||||||
const App = ({ Component, pageProps }: AppPropsWithLayout) => {
|
const App = ({ Component, pageProps }: AppPropsWithLayout) => {
|
||||||
const getLayout = Component.getLayout || (page => page);
|
const getLayout = Component.getLayout || (page => page);
|
||||||
const { i18n } = useTranslation();
|
const { i18n } = useTranslation();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
document.documentElement.lang = i18n.language;
|
document.documentElement.lang = i18n.language;
|
||||||
@ -56,24 +65,43 @@ const App = ({ Component, pageProps }: AppPropsWithLayout) => {
|
|||||||
<title>AFFiNE</title>
|
<title>AFFiNE</title>
|
||||||
</Head>
|
</Head>
|
||||||
<Logger />
|
<Logger />
|
||||||
|
<GlobalAppProvider key="BlockSuiteProvider">
|
||||||
<ProviderComposer
|
<ProviderComposer
|
||||||
contexts={[
|
contexts={[
|
||||||
<GlobalAppProvider key="GlobalAppProvider" />,
|
|
||||||
<ThemeProvider key="ThemeProvider" />,
|
<ThemeProvider key="ThemeProvider" />,
|
||||||
|
<AppStateProvider key="appStateProvider" />,
|
||||||
<ModalProvider key="ModalProvider" />,
|
<ModalProvider key="ModalProvider" />,
|
||||||
<ConfirmProvider key="ConfirmProvider" />,
|
<ConfirmProvider key="ConfirmProvider" />,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
{NoNeedAppStatePageList.includes(router.route) ? (
|
||||||
|
getLayout(<Component {...pageProps} />)
|
||||||
|
) : (
|
||||||
<Suspense fallback={<PageLoading />}>
|
<Suspense fallback={<PageLoading />}>
|
||||||
<DataCenterPreloader>
|
<DataCenterPreloader>
|
||||||
<MessageCenterHandler>
|
<MessageCenterHandler>
|
||||||
|
<AppDefender>
|
||||||
{getLayout(<Component {...pageProps} />)}
|
{getLayout(<Component {...pageProps} />)}
|
||||||
|
</AppDefender>
|
||||||
</MessageCenterHandler>
|
</MessageCenterHandler>
|
||||||
</DataCenterPreloader>
|
</DataCenterPreloader>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
)}
|
||||||
</ProviderComposer>
|
</ProviderComposer>
|
||||||
|
</GlobalAppProvider>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const AppDefender = ({ children }: PropsWithChildren) => {
|
||||||
|
const router = useRouter();
|
||||||
|
useEffect(() => {
|
||||||
|
if (['/index.html', '/'].includes(router.asPath)) {
|
||||||
|
router.replace('/workspace');
|
||||||
|
}
|
||||||
|
}, [router]);
|
||||||
|
|
||||||
|
return <>{children}</>;
|
||||||
|
};
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
@ -1,15 +1,7 @@
|
|||||||
import type { NextPage } from 'next';
|
import type { NextPage } from 'next';
|
||||||
import { useRouter } from 'next/router';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
import { PageLoading } from '@/components/loading';
|
|
||||||
|
|
||||||
const Home: NextPage = () => {
|
const Home: NextPage = () => {
|
||||||
const router = useRouter();
|
return <div title="Home Page"></div>;
|
||||||
useEffect(() => {
|
|
||||||
router.replace('/workspace');
|
|
||||||
}, [router]);
|
|
||||||
return <PageLoading />;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Home;
|
export default Home;
|
||||||
|
@ -11,7 +11,7 @@ import { useEffect, useState } from 'react';
|
|||||||
|
|
||||||
import { PageLoading } from '@/components/loading';
|
import { PageLoading } from '@/components/loading';
|
||||||
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
|
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
|
||||||
import { useDataCenter } from '@/store/app';
|
import { useGlobalState } from '@/store/app';
|
||||||
|
|
||||||
import inviteError from '../../../public/imgs/invite-error.svg';
|
import inviteError from '../../../public/imgs/invite-error.svg';
|
||||||
import inviteSuccess from '../../../public/imgs/invite-success.svg';
|
import inviteSuccess from '../../../public/imgs/invite-success.svg';
|
||||||
@ -21,7 +21,7 @@ export default function DevPage() {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [inviteData, setInviteData] = useState<Permission | null>(null);
|
const [inviteData, setInviteData] = useState<Permission | null>(null);
|
||||||
const { acceptInvite } = useWorkspaceHelper();
|
const { acceptInvite } = useWorkspaceHelper();
|
||||||
const dataCenter = useDataCenter();
|
const dataCenter = useGlobalState(store => store.dataCenter);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
|
@ -2,7 +2,6 @@ import { displayFlex, styled } from '@affine/component';
|
|||||||
import { Breadcrumbs } from '@affine/component';
|
import { Breadcrumbs } from '@affine/component';
|
||||||
import { IconButton } from '@affine/component';
|
import { IconButton } from '@affine/component';
|
||||||
import { useTranslation } from '@affine/i18n';
|
import { useTranslation } from '@affine/i18n';
|
||||||
import { useDataCenterPublicWorkspace } from '@affine/store';
|
|
||||||
import { PaperIcon, SearchIcon } from '@blocksuite/icons';
|
import { PaperIcon, SearchIcon } from '@blocksuite/icons';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import NextLink from 'next/link';
|
import NextLink from 'next/link';
|
||||||
@ -11,6 +10,7 @@ import { ReactElement, useEffect, useMemo } from 'react';
|
|||||||
|
|
||||||
import { PageLoading } from '@/components/loading';
|
import { PageLoading } from '@/components/loading';
|
||||||
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
|
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
|
||||||
|
import { useLoadPublicWorkspace } from '@/hooks/use-load-public-workspace';
|
||||||
import { useModal } from '@/store/globalModal';
|
import { useModal } from '@/store/globalModal';
|
||||||
|
|
||||||
import type { NextPageWithLayout } from '../..//_app';
|
import type { NextPageWithLayout } from '../..//_app';
|
||||||
@ -21,17 +21,13 @@ const DynamicBlocksuite = dynamic(() => import('@/components/editor'), {
|
|||||||
|
|
||||||
const Page: NextPageWithLayout = () => {
|
const Page: NextPageWithLayout = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceId, pageId } = router.query;
|
const { workspaceId, pageId } = router.query as Record<string, string>;
|
||||||
const { error, workspace: workspaceUnit } = useDataCenterPublicWorkspace(
|
const { status, workspace: workspaceUnit } =
|
||||||
typeof workspaceId === 'string' ? workspaceId : null
|
useLoadPublicWorkspace(workspaceId);
|
||||||
);
|
|
||||||
const { triggerQuickSearchModal } = useModal();
|
const { triggerQuickSearchModal } = useModal();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const page = useMemo(() => {
|
const page = useMemo(() => {
|
||||||
if (typeof pageId !== 'string') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (workspaceUnit?.blocksuiteWorkspace) {
|
if (workspaceUnit?.blocksuiteWorkspace) {
|
||||||
return workspaceUnit.blocksuiteWorkspace.getPage(pageId);
|
return workspaceUnit.blocksuiteWorkspace.getPage(pageId);
|
||||||
}
|
}
|
||||||
@ -50,15 +46,18 @@ const Page: NextPageWithLayout = () => {
|
|||||||
}, [workspace, router, pageId]);
|
}, [workspace, router, pageId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (error) {
|
if (status === 'error') {
|
||||||
router.push('/404');
|
router.push('/404');
|
||||||
}
|
}
|
||||||
}, [router, error]);
|
}, [router, status]);
|
||||||
|
|
||||||
if (!workspace) {
|
if (status === 'loading') {
|
||||||
return <PageLoading />;
|
return <PageLoading />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (status === 'error') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
<NavContainer>
|
<NavContainer>
|
||||||
@ -87,7 +86,7 @@ const Page: NextPageWithLayout = () => {
|
|||||||
</SearchButton>
|
</SearchButton>
|
||||||
</NavContainer>
|
</NavContainer>
|
||||||
|
|
||||||
{page && (
|
{workspace && page && (
|
||||||
<DynamicBlocksuite
|
<DynamicBlocksuite
|
||||||
page={page}
|
page={page}
|
||||||
workspace={workspace}
|
workspace={workspace}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { Breadcrumbs } from '@affine/component';
|
import { Breadcrumbs } from '@affine/component';
|
||||||
import { PageMeta, useDataCenterPublicWorkspace } from '@affine/store';
|
|
||||||
import { SearchIcon } from '@blocksuite/icons';
|
import { SearchIcon } from '@blocksuite/icons';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { ReactElement, useEffect, useMemo } from 'react';
|
import { ReactElement, useEffect, useMemo } from 'react';
|
||||||
@ -7,6 +6,8 @@ import { ReactElement, useEffect, useMemo } from 'react';
|
|||||||
import { PageLoading } from '@/components/loading';
|
import { PageLoading } from '@/components/loading';
|
||||||
import { PageList } from '@/components/page-list';
|
import { PageList } from '@/components/page-list';
|
||||||
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
|
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
|
||||||
|
import { useLoadPublicWorkspace } from '@/hooks/use-load-public-workspace';
|
||||||
|
import { PageMeta } from '@/providers/app-state-provider';
|
||||||
import { useModal } from '@/store/globalModal';
|
import { useModal } from '@/store/globalModal';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -19,10 +20,8 @@ import {
|
|||||||
const All = () => {
|
const All = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { triggerQuickSearchModal } = useModal();
|
const { triggerQuickSearchModal } = useModal();
|
||||||
const { workspace, error } = useDataCenterPublicWorkspace(
|
const { status, workspace } = useLoadPublicWorkspace(
|
||||||
typeof router.query.workspaceId === 'string'
|
router.query.workspaceId as string
|
||||||
? router.query.workspaceId
|
|
||||||
: null
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const pageList = useMemo(() => {
|
const pageList = useMemo(() => {
|
||||||
@ -32,15 +31,19 @@ const All = () => {
|
|||||||
const workspaceName = workspace?.blocksuiteWorkspace?.meta.name;
|
const workspaceName = workspace?.blocksuiteWorkspace?.meta.name;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (error) {
|
if (status === 'error') {
|
||||||
router.push('/404');
|
router.push('/404');
|
||||||
}
|
}
|
||||||
}, [router, error]);
|
}, [router, status]);
|
||||||
|
|
||||||
if (!workspace) {
|
if (status === 'loading') {
|
||||||
return <PageLoading />;
|
return <PageLoading />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (status === 'error') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
<NavContainer>
|
<NavContainer>
|
||||||
|
@ -15,7 +15,7 @@ import { EditorHeader } from '@/components/header';
|
|||||||
import MobileModal from '@/components/mobile-modal';
|
import MobileModal from '@/components/mobile-modal';
|
||||||
import WorkspaceLayout from '@/components/workspace-layout';
|
import WorkspaceLayout from '@/components/workspace-layout';
|
||||||
import { usePageHelper } from '@/hooks/use-page-helper';
|
import { usePageHelper } from '@/hooks/use-page-helper';
|
||||||
import { useDataCenter, useGlobalState, useGlobalStateApi } from '@/store/app';
|
import { useGlobalState, useGlobalStateApi } from '@/store/app';
|
||||||
import exampleMarkdown from '@/templates/Welcome-to-AFFiNE-Alpha-Downhills.md';
|
import exampleMarkdown from '@/templates/Welcome-to-AFFiNE-Alpha-Downhills.md';
|
||||||
|
|
||||||
import type { NextPageWithLayout } from '../..//_app';
|
import type { NextPageWithLayout } from '../..//_app';
|
||||||
@ -109,7 +109,7 @@ const PageDefender = ({ children }: PropsWithChildren) => {
|
|||||||
const currentWorkspace = useGlobalState(
|
const currentWorkspace = useGlobalState(
|
||||||
useCallback(store => store.currentDataCenterWorkspace, [])
|
useCallback(store => store.currentDataCenterWorkspace, [])
|
||||||
);
|
);
|
||||||
const dataCenter = useDataCenter();
|
const dataCenter = useGlobalState(store => store.dataCenter);
|
||||||
const { createPage } = usePageHelper();
|
const { createPage } = usePageHelper();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -1,48 +1,44 @@
|
|||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useEffect } from 'react';
|
import { useCallback, useEffect } from 'react';
|
||||||
|
|
||||||
import { PageLoading } from '@/components/loading';
|
import { PageLoading } from '@/components/loading';
|
||||||
|
import useEnsureWorkspace from '@/hooks/use-ensure-workspace';
|
||||||
import usePageHelper from '@/hooks/use-page-helper';
|
import usePageHelper from '@/hooks/use-page-helper';
|
||||||
import { useRouterTargetWorkspace } from '@/hooks/use-router-target-workspace';
|
import { useGlobalState } from '@/store/app';
|
||||||
|
|
||||||
const WorkspaceIndex = () => {
|
const WorkspaceIndex = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { targetWorkspace, exist } = useRouterTargetWorkspace();
|
const currentWorkspace = useGlobalState(
|
||||||
|
useCallback(store => store.currentDataCenterWorkspace, [])
|
||||||
|
);
|
||||||
const { createPage } = usePageHelper();
|
const { createPage } = usePageHelper();
|
||||||
|
const { workspaceLoaded, activeWorkspaceId } = useEnsureWorkspace();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!exist) {
|
|
||||||
router.push('/404');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const abortController = new AbortController();
|
|
||||||
const initPage = async () => {
|
const initPage = async () => {
|
||||||
if (abortController.signal.aborted) {
|
if (!workspaceLoaded) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!targetWorkspace) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const savedPageId =
|
const savedPageId =
|
||||||
targetWorkspace.blocksuiteWorkspace?.meta.pageMetas.find(
|
currentWorkspace?.blocksuiteWorkspace?.meta.pageMetas.find(
|
||||||
meta => !meta.trash
|
meta => !meta.trash
|
||||||
)?.id;
|
)?.id;
|
||||||
if (savedPageId) {
|
if (savedPageId) {
|
||||||
router.replace(`/workspace/${targetWorkspace.id}/${savedPageId}`);
|
router.replace(`/workspace/${activeWorkspaceId}/${savedPageId}`);
|
||||||
return;
|
return;
|
||||||
} else {
|
}
|
||||||
|
|
||||||
const pageId = await createPage();
|
const pageId = await createPage();
|
||||||
if (abortController.signal.aborted) {
|
router.replace(`/workspace/${activeWorkspaceId}/${pageId}`);
|
||||||
return;
|
|
||||||
}
|
|
||||||
router.replace(`/workspace/${targetWorkspace.id}/${pageId}`);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
initPage();
|
initPage();
|
||||||
return () => {
|
}, [
|
||||||
abortController.abort();
|
currentWorkspace,
|
||||||
};
|
createPage,
|
||||||
}, [targetWorkspace, createPage, router, exist]);
|
router,
|
||||||
|
workspaceLoaded,
|
||||||
|
activeWorkspaceId,
|
||||||
|
]);
|
||||||
|
|
||||||
return <PageLoading />;
|
return <PageLoading />;
|
||||||
};
|
};
|
||||||
|
@ -1,31 +1,23 @@
|
|||||||
import { useGlobalStateApi } from '@affine/store';
|
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useEffect, useRef } from 'react';
|
import { useCallback, useEffect } from 'react';
|
||||||
|
|
||||||
import { PageLoading } from '@/components/loading';
|
import { PageLoading } from '@/components/loading';
|
||||||
import { useRouterTargetWorkspace } from '@/hooks/use-router-target-workspace';
|
import useEnsureWorkspace from '@/hooks/use-ensure-workspace';
|
||||||
|
import { useGlobalState } from '@/store/app';
|
||||||
|
|
||||||
export const WorkspaceIndex = () => {
|
export const WorkspaceIndex = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const api = useGlobalStateApi();
|
const currentWorkspace = useGlobalState(
|
||||||
const { targetWorkspace, exist } = useRouterTargetWorkspace();
|
useCallback(store => store.currentDataCenterWorkspace, [])
|
||||||
const onceRef = useRef(true);
|
);
|
||||||
|
const { workspaceLoaded } = useEnsureWorkspace();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!onceRef.current) {
|
if (workspaceLoaded) {
|
||||||
return;
|
router.push(`/workspace/${currentWorkspace?.id}`);
|
||||||
}
|
}
|
||||||
onceRef.current = true;
|
}, [currentWorkspace, router, workspaceLoaded]);
|
||||||
if (!exist) {
|
|
||||||
router.push('/404');
|
|
||||||
} else if (targetWorkspace) {
|
|
||||||
api
|
|
||||||
.getState()
|
|
||||||
.loadWorkspace(targetWorkspace.id)
|
|
||||||
.then(() => {
|
|
||||||
router.push(`/workspace/${targetWorkspace.id}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [targetWorkspace, exist, router, api]);
|
|
||||||
return <PageLoading />;
|
return <PageLoading />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
Theme,
|
||||||
ThemeMode,
|
ThemeMode,
|
||||||
ThemeProviderProps,
|
ThemeProviderProps,
|
||||||
ThemeProviderValue,
|
ThemeProviderValue,
|
||||||
@ -16,15 +17,7 @@ import {
|
|||||||
ThemeProvider as MuiThemeProvider,
|
ThemeProvider as MuiThemeProvider,
|
||||||
} from '@mui/material/styles';
|
} from '@mui/material/styles';
|
||||||
import type { PropsWithChildren } from 'react';
|
import type { PropsWithChildren } from 'react';
|
||||||
import {
|
import { createContext, useContext, useEffect, useState } from 'react';
|
||||||
createContext,
|
|
||||||
useCallback,
|
|
||||||
useContext,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useState,
|
|
||||||
useSyncExternalStore,
|
|
||||||
} from 'react';
|
|
||||||
|
|
||||||
import useCurrentPageMeta from '@/hooks/use-current-page-meta';
|
import useCurrentPageMeta from '@/hooks/use-current-page-meta';
|
||||||
|
|
||||||
@ -42,31 +35,16 @@ export const ThemeProvider = ({
|
|||||||
defaultTheme = 'light',
|
defaultTheme = 'light',
|
||||||
children,
|
children,
|
||||||
}: PropsWithChildren<ThemeProviderProps>) => {
|
}: PropsWithChildren<ThemeProviderProps>) => {
|
||||||
const localStorageThemeMode = useSyncExternalStore<ThemeMode>(
|
const [theme, setTheme] = useState<Theme>(defaultTheme);
|
||||||
useCallback(cb => {
|
const [mode, setMode] = useState<ThemeMode>('auto');
|
||||||
localStorageThemeHelper.callback.add(cb);
|
|
||||||
return () => {
|
|
||||||
localStorageThemeHelper.callback.delete(cb);
|
|
||||||
};
|
|
||||||
}, []),
|
|
||||||
useCallback(() => localStorageThemeHelper.get() ?? 'light', []),
|
|
||||||
useCallback(() => defaultTheme, [defaultTheme])
|
|
||||||
);
|
|
||||||
const [mode, setMode] = useState<ThemeMode>(defaultTheme);
|
|
||||||
if (localStorageThemeMode !== mode) {
|
|
||||||
setMode(localStorageThemeMode);
|
|
||||||
}
|
|
||||||
const { mode: editorMode = 'page' } = useCurrentPageMeta() || {};
|
const { mode: editorMode = 'page' } = useCurrentPageMeta() || {};
|
||||||
const themeStyle =
|
const themeStyle =
|
||||||
mode === 'light' ? getLightTheme(editorMode) : getDarkTheme(editorMode);
|
theme === 'light' ? getLightTheme(editorMode) : getDarkTheme(editorMode);
|
||||||
const changeMode = useCallback(
|
const changeMode = (themeMode: ThemeMode) => {
|
||||||
(themeMode: ThemeMode) => {
|
|
||||||
themeMode !== mode && setMode(themeMode);
|
themeMode !== mode && setMode(themeMode);
|
||||||
// Remember the theme mode which user selected for next time
|
// Remember the theme mode which user selected for next time
|
||||||
localStorageThemeHelper.set(themeMode);
|
localStorageThemeHelper.set(themeMode);
|
||||||
},
|
};
|
||||||
[mode]
|
|
||||||
);
|
|
||||||
|
|
||||||
// ===================== A temporary solution, just use system theme and not remember the user selected ====================
|
// ===================== A temporary solution, just use system theme and not remember the user selected ====================
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -79,9 +57,9 @@ export const ThemeProvider = ({
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// useEffect(() => {
|
useEffect(() => {
|
||||||
// setTheme(mode === 'auto' ? theme : mode);
|
setTheme(mode === 'auto' ? theme : mode);
|
||||||
// }, [mode, setTheme, theme]);
|
}, [mode, setTheme, theme]);
|
||||||
// ===================== ====================
|
// ===================== ====================
|
||||||
|
|
||||||
// useEffect(() => {
|
// useEffect(() => {
|
||||||
@ -115,12 +93,7 @@ export const ThemeProvider = ({
|
|||||||
return (
|
return (
|
||||||
// Use MuiThemeProvider is just because some Transitions in Mui components need it
|
// Use MuiThemeProvider is just because some Transitions in Mui components need it
|
||||||
<MuiThemeProvider theme={muiTheme}>
|
<MuiThemeProvider theme={muiTheme}>
|
||||||
<ThemeContext.Provider
|
<ThemeContext.Provider value={{ mode, changeMode, theme: themeStyle }}>
|
||||||
value={useMemo(
|
|
||||||
() => ({ mode, changeMode, theme: themeStyle }),
|
|
||||||
[changeMode, mode, themeStyle]
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Global
|
<Global
|
||||||
styles={css`
|
styles={css`
|
||||||
:root {
|
:root {
|
||||||
|
51
apps/web/src/providers/app-state-provider/Provider.tsx
Normal file
51
apps/web/src/providers/app-state-provider/Provider.tsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import type { Disposable } from '@blocksuite/global/utils';
|
||||||
|
import type { PropsWithChildren } from 'react';
|
||||||
|
import { createContext, useContext, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { useGlobalState } from '@/store/app';
|
||||||
|
|
||||||
|
import { AppStateContext } from './interface';
|
||||||
|
|
||||||
|
type AppStateContextProps = PropsWithChildren<Record<string, unknown>>;
|
||||||
|
|
||||||
|
export const AppState = createContext<AppStateContext>({} as AppStateContext);
|
||||||
|
|
||||||
|
export const useAppState = () => useContext(AppState);
|
||||||
|
export const AppStateProvider = ({
|
||||||
|
children,
|
||||||
|
}: PropsWithChildren<AppStateContextProps>) => {
|
||||||
|
const currentDataCenterWorkspace = useGlobalState(
|
||||||
|
store => store.currentDataCenterWorkspace
|
||||||
|
);
|
||||||
|
const [blobState, setBlobState] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let syncChangeDisposable: Disposable | undefined;
|
||||||
|
const currentWorkspace = currentDataCenterWorkspace;
|
||||||
|
if (!currentWorkspace) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const getBlobStorage = async () => {
|
||||||
|
const blobStorage = await currentWorkspace?.blocksuiteWorkspace?.blobs;
|
||||||
|
syncChangeDisposable = blobStorage?.signals.onBlobSyncStateChange.on(
|
||||||
|
() => {
|
||||||
|
setBlobState(blobStorage?.uploading);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
getBlobStorage();
|
||||||
|
return () => {
|
||||||
|
syncChangeDisposable?.dispose();
|
||||||
|
};
|
||||||
|
}, [currentDataCenterWorkspace]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AppState.Provider
|
||||||
|
value={{
|
||||||
|
blobDataSynced: blobState,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</AppState.Provider>
|
||||||
|
);
|
||||||
|
};
|
2
apps/web/src/providers/app-state-provider/index.ts
Normal file
2
apps/web/src/providers/app-state-provider/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './interface';
|
||||||
|
export * from './Provider';
|
28
apps/web/src/providers/app-state-provider/interface.ts
Normal file
28
apps/web/src/providers/app-state-provider/interface.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import type { EditorContainer } from '@blocksuite/editor';
|
||||||
|
import type {
|
||||||
|
Page as StorePage,
|
||||||
|
PageMeta as StorePageMeta,
|
||||||
|
} from '@blocksuite/store';
|
||||||
|
|
||||||
|
export interface PageMeta extends StorePageMeta {
|
||||||
|
favorite: boolean;
|
||||||
|
trash: boolean;
|
||||||
|
trashDate: number;
|
||||||
|
updatedDate: number;
|
||||||
|
mode: 'edgeless' | 'page';
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AppStateValue = {
|
||||||
|
blobDataSynced: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
export type AppStateFunction = {
|
||||||
|
// todo: remove this in the future
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AppStateContext = AppStateValue & AppStateFunction;
|
||||||
|
|
||||||
|
export type CreateEditorHandler = (page: StorePage) => EditorContainer | null;
|
9
apps/web/src/providers/app-state-provider/utils.ts
Normal file
9
apps/web/src/providers/app-state-provider/utils.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { DataCenter } from '@affine/datacenter';
|
||||||
|
|
||||||
|
const DEFAULT_WORKSPACE_NAME = 'Demo Workspace';
|
||||||
|
|
||||||
|
export const createDefaultWorkspace = async (dataCenter: DataCenter) => {
|
||||||
|
return dataCenter.createWorkspace({
|
||||||
|
name: DEFAULT_WORKSPACE_NAME,
|
||||||
|
});
|
||||||
|
};
|
@ -2,7 +2,7 @@ import { BlockHub } from '@blocksuite/blocks';
|
|||||||
import { EditorContainer } from '@blocksuite/editor';
|
import { EditorContainer } from '@blocksuite/editor';
|
||||||
import { Page, Workspace } from '@blocksuite/store';
|
import { Page, Workspace } from '@blocksuite/store';
|
||||||
|
|
||||||
import { GlobalActionsCreator } from '..';
|
import { GlobalActionsCreator } from '@/store/app';
|
||||||
|
|
||||||
export interface BlockSuiteState {
|
export interface BlockSuiteState {
|
||||||
currentWorkspace: Workspace | null;
|
currentWorkspace: Workspace | null;
|
142
apps/web/src/store/app/datacenter/index.tsx
Normal file
142
apps/web/src/store/app/datacenter/index.tsx
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
import type { DataCenter } from '@affine/datacenter';
|
||||||
|
import { getDataCenter, WorkspaceUnit } from '@affine/datacenter';
|
||||||
|
import { DisposableGroup } from '@blocksuite/global/utils';
|
||||||
|
import React, { useCallback, useEffect } from 'react';
|
||||||
|
|
||||||
|
import { PageMeta } from '@/providers/app-state-provider';
|
||||||
|
import { createDefaultWorkspace } from '@/providers/app-state-provider/utils';
|
||||||
|
import {
|
||||||
|
GlobalActionsCreator,
|
||||||
|
useGlobalState,
|
||||||
|
useGlobalStateApi,
|
||||||
|
} from '@/store/app';
|
||||||
|
|
||||||
|
export type DataCenterState = {
|
||||||
|
readonly dataCenter: DataCenter;
|
||||||
|
readonly dataCenterPromise: Promise<DataCenter>;
|
||||||
|
currentDataCenterWorkspace: WorkspaceUnit | null;
|
||||||
|
dataCenterPageList: PageMeta[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DataCenterActions = {
|
||||||
|
loadWorkspace: (
|
||||||
|
workspaceId: string,
|
||||||
|
signal?: AbortSignal
|
||||||
|
) => Promise<WorkspaceUnit | null>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createDataCenterState = (): DataCenterState => ({
|
||||||
|
dataCenter: null!,
|
||||||
|
|
||||||
|
dataCenterPromise: null!,
|
||||||
|
currentDataCenterWorkspace: null,
|
||||||
|
dataCenterPageList: [],
|
||||||
|
});
|
||||||
|
export const createDataCenterActions: GlobalActionsCreator<
|
||||||
|
DataCenterActions
|
||||||
|
> = (set, get) => ({
|
||||||
|
loadWorkspace: async (workspaceId, signal) => {
|
||||||
|
const { dataCenter, currentDataCenterWorkspace } = get();
|
||||||
|
if (!dataCenter.workspaces.find(v => v.id.toString() === workspaceId)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (workspaceId === currentDataCenterWorkspace?.id) {
|
||||||
|
return currentDataCenterWorkspace;
|
||||||
|
}
|
||||||
|
const workspace = (await dataCenter.loadWorkspace(workspaceId)) ?? null;
|
||||||
|
|
||||||
|
if (signal?.aborted) {
|
||||||
|
// do not update state if aborted
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let isOwner;
|
||||||
|
if (workspace?.provider === 'local') {
|
||||||
|
// isOwner is useful only in the cloud
|
||||||
|
isOwner = true;
|
||||||
|
} else {
|
||||||
|
const userInfo = get().user; // We must ensure workspace.owner exists, then ensure id same.
|
||||||
|
isOwner = userInfo?.id === workspace?.owner?.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pageList =
|
||||||
|
(workspace?.blocksuiteWorkspace?.meta.pageMetas as PageMeta[]) ?? [];
|
||||||
|
if (workspace?.blocksuiteWorkspace) {
|
||||||
|
set({
|
||||||
|
currentWorkspace: workspace.blocksuiteWorkspace,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
set({
|
||||||
|
isOwner,
|
||||||
|
});
|
||||||
|
|
||||||
|
set({
|
||||||
|
currentDataCenterWorkspace: workspace,
|
||||||
|
dataCenterPageList: pageList,
|
||||||
|
});
|
||||||
|
|
||||||
|
return workspace;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export function DataCenterPreloader({ children }: React.PropsWithChildren) {
|
||||||
|
const dataCenter = useGlobalState(useCallback(store => store.dataCenter, []));
|
||||||
|
const dataCenterPromise = useGlobalState(
|
||||||
|
useCallback(store => store.dataCenterPromise, [])
|
||||||
|
);
|
||||||
|
const api = useGlobalStateApi();
|
||||||
|
//# region effect for updating workspace page list
|
||||||
|
useEffect(() => {
|
||||||
|
return api.subscribe(
|
||||||
|
store => store.currentDataCenterWorkspace,
|
||||||
|
currentWorkspace => {
|
||||||
|
const disposableGroup = new DisposableGroup();
|
||||||
|
disposableGroup.add(
|
||||||
|
currentWorkspace?.blocksuiteWorkspace?.meta.pagesUpdated.on(() => {
|
||||||
|
if (
|
||||||
|
Array.isArray(
|
||||||
|
currentWorkspace.blocksuiteWorkspace?.meta.pageMetas
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
api.setState({
|
||||||
|
dataCenterPageList: currentWorkspace.blocksuiteWorkspace?.meta
|
||||||
|
.pageMetas as PageMeta[],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return () => {
|
||||||
|
disposableGroup.dispose();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}, [api]);
|
||||||
|
//# endregion
|
||||||
|
|
||||||
|
if (!dataCenter && !dataCenterPromise) {
|
||||||
|
const promise = getDataCenter();
|
||||||
|
api.setState({ dataCenterPromise: promise });
|
||||||
|
promise.then(async dataCenter => {
|
||||||
|
// Ensure datacenter has at least one workspace
|
||||||
|
if (dataCenter.workspaces.length === 0) {
|
||||||
|
await createDefaultWorkspace(dataCenter);
|
||||||
|
}
|
||||||
|
// set initial state
|
||||||
|
api.setState({
|
||||||
|
dataCenter,
|
||||||
|
currentWorkspace: null,
|
||||||
|
currentDataCenterWorkspace: null,
|
||||||
|
dataCenterPageList: [],
|
||||||
|
user:
|
||||||
|
(await dataCenter.getUserInfo(
|
||||||
|
dataCenter.providers.filter(p => p.id !== 'local')[0]?.id
|
||||||
|
)) || null,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
throw promise;
|
||||||
|
}
|
||||||
|
if (!dataCenter) {
|
||||||
|
throw dataCenterPromise;
|
||||||
|
}
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
@ -1 +1,89 @@
|
|||||||
export * from '@affine/store';
|
import type React from 'react';
|
||||||
|
import { createContext, useContext, useMemo } from 'react';
|
||||||
|
import { createStore, StateCreator, useStore } from 'zustand';
|
||||||
|
import { combine, subscribeWithSelector } from 'zustand/middleware';
|
||||||
|
import type { UseBoundStore } from 'zustand/react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BlockSuiteActions,
|
||||||
|
BlockSuiteState,
|
||||||
|
createBlockSuiteActions,
|
||||||
|
createBlockSuiteState,
|
||||||
|
} from '@/store/app/blocksuite';
|
||||||
|
import {
|
||||||
|
createDataCenterActions,
|
||||||
|
createDataCenterState,
|
||||||
|
DataCenterActions,
|
||||||
|
DataCenterState,
|
||||||
|
} from '@/store/app/datacenter';
|
||||||
|
import {
|
||||||
|
createUserActions,
|
||||||
|
createUserState,
|
||||||
|
UserActions,
|
||||||
|
UserState,
|
||||||
|
} from '@/store/app/user';
|
||||||
|
|
||||||
|
export type GlobalActionsCreator<Actions, Store = GlobalState> = StateCreator<
|
||||||
|
Store,
|
||||||
|
[['zustand/subscribeWithSelector', unknown]],
|
||||||
|
[],
|
||||||
|
Actions
|
||||||
|
>;
|
||||||
|
|
||||||
|
export interface GlobalState
|
||||||
|
extends BlockSuiteState,
|
||||||
|
UserState,
|
||||||
|
DataCenterState {}
|
||||||
|
|
||||||
|
export interface GlobalActions
|
||||||
|
extends BlockSuiteActions,
|
||||||
|
UserActions,
|
||||||
|
DataCenterActions {}
|
||||||
|
|
||||||
|
const create = () =>
|
||||||
|
createStore(
|
||||||
|
subscribeWithSelector(
|
||||||
|
combine<GlobalState, GlobalActions>(
|
||||||
|
{
|
||||||
|
...createBlockSuiteState(),
|
||||||
|
...createUserState(),
|
||||||
|
...createDataCenterState(),
|
||||||
|
},
|
||||||
|
/* deepscan-disable TOO_MANY_ARGS */
|
||||||
|
(set, get, api) => ({
|
||||||
|
...createBlockSuiteActions(set, get, api),
|
||||||
|
...createUserActions(set, get, api),
|
||||||
|
...createDataCenterActions(set, get, api),
|
||||||
|
})
|
||||||
|
/* deepscan-enable TOO_MANY_ARGS */
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
type Store = ReturnType<typeof create>;
|
||||||
|
|
||||||
|
const GlobalStateContext = createContext<Store | null>(null);
|
||||||
|
|
||||||
|
export const useGlobalStateApi = () => {
|
||||||
|
const api = useContext(GlobalStateContext);
|
||||||
|
if (!api) {
|
||||||
|
throw new Error('cannot find modal context');
|
||||||
|
}
|
||||||
|
return api;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGlobalState: UseBoundStore<Store> = ((
|
||||||
|
selector: Parameters<UseBoundStore<Store>>[0],
|
||||||
|
equals: Parameters<UseBoundStore<Store>>[1]
|
||||||
|
) => {
|
||||||
|
const api = useGlobalStateApi();
|
||||||
|
return useStore(api, selector, equals);
|
||||||
|
}) as any;
|
||||||
|
|
||||||
|
export const GlobalAppProvider: React.FC<React.PropsWithChildren> =
|
||||||
|
function ModelProvider({ children }) {
|
||||||
|
return (
|
||||||
|
<GlobalStateContext.Provider value={useMemo(() => create(), [])}>
|
||||||
|
{children}
|
||||||
|
</GlobalStateContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { User } from '@affine/datacenter';
|
import { User } from '@affine/datacenter';
|
||||||
import { DebugLogger } from '@affine/debug';
|
import { DebugLogger } from '@affine/debug';
|
||||||
|
|
||||||
import { GlobalActionsCreator } from '..';
|
import { GlobalActionsCreator } from '@/store/app';
|
||||||
import { dataCenterPromise } from '../datacenter';
|
|
||||||
|
|
||||||
export interface UserState {
|
export interface UserState {
|
||||||
user: User | null;
|
user: User | null;
|
||||||
@ -28,8 +27,7 @@ export const createUserActions: GlobalActionsCreator<UserActions> = (
|
|||||||
) => {
|
) => {
|
||||||
return {
|
return {
|
||||||
login: async () => {
|
login: async () => {
|
||||||
const { currentDataCenterWorkspace: workspace } = get();
|
const { dataCenter, currentDataCenterWorkspace: workspace } = get();
|
||||||
const dataCenter = await dataCenterPromise;
|
|
||||||
try {
|
try {
|
||||||
await dataCenter.login();
|
await dataCenter.login();
|
||||||
const user = (await dataCenter.getUserInfo()) as User;
|
const user = (await dataCenter.getUserInfo()) as User;
|
||||||
@ -58,7 +56,7 @@ export const createUserActions: GlobalActionsCreator<UserActions> = (
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
logout: async () => {
|
logout: async () => {
|
||||||
const dataCenter = await dataCenterPromise;
|
const { dataCenter } = get();
|
||||||
await dataCenter.logout();
|
await dataCenter.logout();
|
||||||
logger.debug('logout success');
|
logger.debug('logout success');
|
||||||
set({ user: null });
|
set({ user: null });
|
@ -2,13 +2,11 @@ import { ThemeMode } from '../types';
|
|||||||
|
|
||||||
export class LocalStorageThemeHelper {
|
export class LocalStorageThemeHelper {
|
||||||
name = 'Affine-theme-mode';
|
name = 'Affine-theme-mode';
|
||||||
callback = new Set<() => void>();
|
|
||||||
get = (): ThemeMode | null => {
|
get = (): ThemeMode | null => {
|
||||||
return localStorage.getItem(this.name) as ThemeMode | null;
|
return localStorage.getItem(this.name) as ThemeMode | null;
|
||||||
};
|
};
|
||||||
set = (mode: ThemeMode) => {
|
set = (mode: ThemeMode) => {
|
||||||
localStorage.setItem(this.name, mode);
|
localStorage.setItem(this.name, mode);
|
||||||
this.callback.forEach(cb => cb());
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,17 @@ const _initializeDataCenter = () => {
|
|||||||
return () => {
|
return () => {
|
||||||
if (!_dataCenterInstance) {
|
if (!_dataCenterInstance) {
|
||||||
_dataCenterInstance = DataCenter.init();
|
_dataCenterInstance = DataCenter.init();
|
||||||
|
_dataCenterInstance.then(dc => {
|
||||||
|
try {
|
||||||
|
if (window) {
|
||||||
|
(window as any).dc = dc;
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
return dc;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return _dataCenterInstance;
|
return _dataCenterInstance;
|
||||||
@ -14,7 +25,7 @@ const _initializeDataCenter = () => {
|
|||||||
|
|
||||||
export const getDataCenter = _initializeDataCenter();
|
export const getDataCenter = _initializeDataCenter();
|
||||||
|
|
||||||
export { DataCenter };
|
export type { DataCenter };
|
||||||
export * from './message';
|
export * from './message';
|
||||||
export { AffineProvider } from './provider/affine';
|
export { AffineProvider } from './provider/affine';
|
||||||
export * from './provider/affine/apis';
|
export * from './provider/affine/apis';
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@affine/store",
|
|
||||||
"private": true,
|
|
||||||
"main": "./src/index.ts",
|
|
||||||
"dependencies": {
|
|
||||||
"@affine/datacenter": "workspace:*",
|
|
||||||
"@affine/debug": "workspace:*",
|
|
||||||
"@blocksuite/blocks": "0.4.1",
|
|
||||||
"@blocksuite/editor": "0.4.1",
|
|
||||||
"@blocksuite/global": "0.4.1",
|
|
||||||
"@blocksuite/react": "0.4.1",
|
|
||||||
"@blocksuite/store": "0.4.1",
|
|
||||||
"lit": "^2.6.1",
|
|
||||||
"react": "^18.2.0",
|
|
||||||
"react-dom": "^18.2.0",
|
|
||||||
"swr": "^2.0.3",
|
|
||||||
"yjs": "^13.5.46",
|
|
||||||
"zustand": "^4.3.3"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/react": "^18.0.28",
|
|
||||||
"@types/react-dom": "^18.0.11"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,225 +0,0 @@
|
|||||||
import { getDataCenter, WorkspaceUnit } from '@affine/datacenter';
|
|
||||||
import { DataCenter } from '@affine/datacenter';
|
|
||||||
import { Disposable, DisposableGroup } from '@blocksuite/global/utils';
|
|
||||||
import type { PageMeta as StorePageMeta } from '@blocksuite/store';
|
|
||||||
import React, { useEffect } from 'react';
|
|
||||||
import useSWR from 'swr';
|
|
||||||
|
|
||||||
const DEFAULT_WORKSPACE_NAME = 'Demo Workspace';
|
|
||||||
|
|
||||||
export const createDefaultWorkspace = async (dataCenter: DataCenter) => {
|
|
||||||
return dataCenter.createWorkspace({
|
|
||||||
name: DEFAULT_WORKSPACE_NAME,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
// eslint-disable-next-line no-var
|
|
||||||
var dataCenterPromise: Promise<DataCenter>;
|
|
||||||
// eslint-disable-next-line no-var
|
|
||||||
var dc: DataCenter;
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
||||||
let dataCenterPromise: Promise<DataCenter> = null!;
|
|
||||||
if (!globalThis.dataCenterPromise) {
|
|
||||||
dataCenterPromise = getDataCenter();
|
|
||||||
dataCenterPromise.then(dataCenter => {
|
|
||||||
globalThis.dc = dataCenter;
|
|
||||||
return dataCenter;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
dataCenterPromise = globalThis.dataCenterPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export { dataCenterPromise };
|
|
||||||
|
|
||||||
export interface PageMeta extends StorePageMeta {
|
|
||||||
favorite: boolean;
|
|
||||||
trash: boolean;
|
|
||||||
trashDate: number;
|
|
||||||
updatedDate: number;
|
|
||||||
mode: 'edgeless' | 'page';
|
|
||||||
}
|
|
||||||
|
|
||||||
import { GlobalActionsCreator, useGlobalStateApi } from '..';
|
|
||||||
|
|
||||||
export type DataCenterState = {
|
|
||||||
currentDataCenterWorkspace: WorkspaceUnit | null;
|
|
||||||
dataCenterPageList: PageMeta[];
|
|
||||||
blobDataSynced: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type DataCenterActions = {
|
|
||||||
loadWorkspace: (
|
|
||||||
workspaceId: string,
|
|
||||||
signal?: AbortSignal
|
|
||||||
) => Promise<WorkspaceUnit | null>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createDataCenterState = (): DataCenterState => ({
|
|
||||||
currentDataCenterWorkspace: null,
|
|
||||||
dataCenterPageList: [],
|
|
||||||
blobDataSynced: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const createDataCenterActions: GlobalActionsCreator<
|
|
||||||
DataCenterActions
|
|
||||||
> = (set, get) => ({
|
|
||||||
loadWorkspace: async (workspaceId, signal) => {
|
|
||||||
const dataCenter = await dataCenterPromise;
|
|
||||||
const { currentDataCenterWorkspace } = get();
|
|
||||||
if (!dataCenter.workspaces.find(v => v.id.toString() === workspaceId)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (workspaceId === currentDataCenterWorkspace?.id) {
|
|
||||||
return currentDataCenterWorkspace;
|
|
||||||
}
|
|
||||||
const workspace = await dataCenter.loadWorkspace(workspaceId);
|
|
||||||
|
|
||||||
if (!workspace) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (signal?.aborted) {
|
|
||||||
// do not update state if aborted
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let isOwner;
|
|
||||||
if (workspace.provider === 'local') {
|
|
||||||
// isOwner is useful only in the cloud
|
|
||||||
isOwner = true;
|
|
||||||
} else {
|
|
||||||
const userInfo = get().user; // We must ensure workspace.owner exists, then ensure id same.
|
|
||||||
isOwner = userInfo?.id === workspace.owner?.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pageList =
|
|
||||||
(workspace.blocksuiteWorkspace?.meta.pageMetas as PageMeta[]) ?? [];
|
|
||||||
if (workspace.blocksuiteWorkspace) {
|
|
||||||
set({
|
|
||||||
currentWorkspace: workspace.blocksuiteWorkspace,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
set({
|
|
||||||
isOwner,
|
|
||||||
currentDataCenterWorkspace: workspace,
|
|
||||||
dataCenterPageList: pageList,
|
|
||||||
});
|
|
||||||
|
|
||||||
return workspace;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export function useDataCenter() {
|
|
||||||
const { data } = useSWR<DataCenter>(['datacenter'], {
|
|
||||||
fallbackData: DataCenter.initEmpty(),
|
|
||||||
});
|
|
||||||
return data as DataCenter;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useDataCenterWorkspace(
|
|
||||||
workspaceId: string | null
|
|
||||||
): WorkspaceUnit | null {
|
|
||||||
const { data } = useSWR<WorkspaceUnit | null>(['datacenter', workspaceId], {
|
|
||||||
fallbackData: null,
|
|
||||||
});
|
|
||||||
return data ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useDataCenterPublicWorkspace(workspaceId: string | null) {
|
|
||||||
const { data, error } = useSWR<WorkspaceUnit | null>(
|
|
||||||
['datacenter', workspaceId, 'public'],
|
|
||||||
{
|
|
||||||
fallbackData: null,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
workspace: data ?? null,
|
|
||||||
error,
|
|
||||||
} as const;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function DataCenterPreloader({ children }: React.PropsWithChildren) {
|
|
||||||
const api = useGlobalStateApi();
|
|
||||||
|
|
||||||
// init user info from datacenter
|
|
||||||
useEffect(() => {
|
|
||||||
dataCenterPromise.then(async dataCenter => {
|
|
||||||
const user = await dataCenter.getUserInfo();
|
|
||||||
if (!api.getState().user) {
|
|
||||||
api.setState({ user });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
//# region effect for updating workspace page list
|
|
||||||
useEffect(() => {
|
|
||||||
return api.subscribe(
|
|
||||||
store => store.currentDataCenterWorkspace,
|
|
||||||
currentWorkspace => {
|
|
||||||
const disposableGroup = new DisposableGroup();
|
|
||||||
disposableGroup.add(
|
|
||||||
currentWorkspace?.blocksuiteWorkspace?.meta.pagesUpdated.on(() => {
|
|
||||||
if (
|
|
||||||
Array.isArray(
|
|
||||||
currentWorkspace.blocksuiteWorkspace?.meta.pageMetas
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
api.setState({
|
|
||||||
dataCenterPageList: currentWorkspace.blocksuiteWorkspace?.meta
|
|
||||||
.pageMetas as PageMeta[],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
return () => {
|
|
||||||
disposableGroup.dispose();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}, [api]);
|
|
||||||
//# endregion
|
|
||||||
//# region effect for blobDataSynced
|
|
||||||
useEffect(
|
|
||||||
() =>
|
|
||||||
api.subscribe(
|
|
||||||
store => store.currentDataCenterWorkspace,
|
|
||||||
workspace => {
|
|
||||||
if (!workspace?.blocksuiteWorkspace) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const controller = new AbortController();
|
|
||||||
const blocksuiteWorkspace = workspace.blocksuiteWorkspace;
|
|
||||||
let syncChangeDisposable: Disposable | undefined;
|
|
||||||
async function subscribe() {
|
|
||||||
const blobStorage = await blocksuiteWorkspace.blobs;
|
|
||||||
if (controller.signal.aborted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
syncChangeDisposable =
|
|
||||||
blobStorage?.signals.onBlobSyncStateChange.on(() => {
|
|
||||||
if (controller.signal.aborted) {
|
|
||||||
syncChangeDisposable?.dispose();
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
api.setState({
|
|
||||||
blobDataSynced: blobStorage?.uploading,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
subscribe();
|
|
||||||
return () => {
|
|
||||||
controller.abort();
|
|
||||||
syncChangeDisposable?.dispose();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
),
|
|
||||||
[api]
|
|
||||||
);
|
|
||||||
//# endregion
|
|
||||||
return <>{children}</>;
|
|
||||||
}
|
|
@ -1,128 +0,0 @@
|
|||||||
import { assertEquals } from '@blocksuite/global/utils';
|
|
||||||
import type React from 'react';
|
|
||||||
import { createContext, useContext, useMemo } from 'react';
|
|
||||||
import { preload, SWRConfig, SWRConfiguration } from 'swr';
|
|
||||||
import { createStore, StateCreator, useStore } from 'zustand';
|
|
||||||
import { combine, subscribeWithSelector } from 'zustand/middleware';
|
|
||||||
import type { UseBoundStore } from 'zustand/react';
|
|
||||||
|
|
||||||
import {
|
|
||||||
BlockSuiteActions,
|
|
||||||
BlockSuiteState,
|
|
||||||
createBlockSuiteActions,
|
|
||||||
createBlockSuiteState,
|
|
||||||
} from './blocksuite';
|
|
||||||
import {
|
|
||||||
createDataCenterActions,
|
|
||||||
createDataCenterState,
|
|
||||||
createDefaultWorkspace,
|
|
||||||
DataCenterActions,
|
|
||||||
dataCenterPromise,
|
|
||||||
DataCenterState,
|
|
||||||
} from './datacenter';
|
|
||||||
import {
|
|
||||||
createUserActions,
|
|
||||||
createUserState,
|
|
||||||
UserActions,
|
|
||||||
UserState,
|
|
||||||
} from './user';
|
|
||||||
|
|
||||||
export type GlobalActionsCreator<Actions, Store = GlobalState> = StateCreator<
|
|
||||||
Store,
|
|
||||||
[['zustand/subscribeWithSelector', unknown]],
|
|
||||||
[],
|
|
||||||
Actions
|
|
||||||
>;
|
|
||||||
|
|
||||||
export interface GlobalState
|
|
||||||
extends BlockSuiteState,
|
|
||||||
UserState,
|
|
||||||
DataCenterState {}
|
|
||||||
|
|
||||||
export interface GlobalActions
|
|
||||||
extends BlockSuiteActions,
|
|
||||||
UserActions,
|
|
||||||
DataCenterActions {}
|
|
||||||
|
|
||||||
const create = () =>
|
|
||||||
createStore(
|
|
||||||
subscribeWithSelector(
|
|
||||||
combine<GlobalState, GlobalActions>(
|
|
||||||
{
|
|
||||||
...createBlockSuiteState(),
|
|
||||||
...createUserState(),
|
|
||||||
...createDataCenterState(),
|
|
||||||
},
|
|
||||||
/* deepscan-disable TOO_MANY_ARGS */
|
|
||||||
(set, get, api) => ({
|
|
||||||
...createBlockSuiteActions(set, get, api),
|
|
||||||
...createUserActions(set, get, api),
|
|
||||||
...createDataCenterActions(set, get, api),
|
|
||||||
})
|
|
||||||
/* deepscan-enable TOO_MANY_ARGS */
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
type Store = ReturnType<typeof create>;
|
|
||||||
|
|
||||||
const GlobalStateContext = createContext<Store | null>(null);
|
|
||||||
|
|
||||||
export const useGlobalStateApi = () => {
|
|
||||||
const api = useContext(GlobalStateContext);
|
|
||||||
if (!api) {
|
|
||||||
throw new Error('cannot find modal context');
|
|
||||||
}
|
|
||||||
return api;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useGlobalState: UseBoundStore<Store> = ((
|
|
||||||
selector: Parameters<UseBoundStore<Store>>[0],
|
|
||||||
equals: Parameters<UseBoundStore<Store>>[1]
|
|
||||||
) => {
|
|
||||||
const api = useGlobalStateApi();
|
|
||||||
return useStore(api, selector, equals);
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
}) as any;
|
|
||||||
|
|
||||||
export type DataKey =
|
|
||||||
| ['datacenter', string | null, 'public' | undefined]
|
|
||||||
| ['datacenter'];
|
|
||||||
|
|
||||||
const swrFetcher = async (keys: DataKey) => {
|
|
||||||
assertEquals(keys[0], 'datacenter');
|
|
||||||
if (keys.length === 1) {
|
|
||||||
return await dataCenterPromise.then(async dataCenter => {
|
|
||||||
if (dataCenter.workspaces.length === 0) {
|
|
||||||
await createDefaultWorkspace(dataCenter);
|
|
||||||
}
|
|
||||||
return dataCenter;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (keys[1] === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const dataCenter = await dataCenterPromise;
|
|
||||||
if (keys[2] === 'public') {
|
|
||||||
return dataCenter.loadPublicWorkspace(keys[1]);
|
|
||||||
}
|
|
||||||
return dataCenter.loadWorkspace(keys[1]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
preload(['datacenter'], swrFetcher);
|
|
||||||
|
|
||||||
const swrConfig: SWRConfiguration = {
|
|
||||||
fetcher: swrFetcher,
|
|
||||||
suspense: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const GlobalAppProvider: React.FC<React.PropsWithChildren> =
|
|
||||||
function ModelProvider({ children }) {
|
|
||||||
return (
|
|
||||||
<SWRConfig value={swrConfig}>
|
|
||||||
<GlobalStateContext.Provider value={useMemo(() => create(), [])}>
|
|
||||||
{children}
|
|
||||||
</GlobalStateContext.Provider>
|
|
||||||
</SWRConfig>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,9 +0,0 @@
|
|||||||
export * from './app';
|
|
||||||
export type { PageMeta } from './app/datacenter';
|
|
||||||
export { createDefaultWorkspace, DataCenterPreloader } from './app/datacenter';
|
|
||||||
export {
|
|
||||||
dataCenterPromise,
|
|
||||||
useDataCenter,
|
|
||||||
useDataCenterPublicWorkspace,
|
|
||||||
useDataCenterWorkspace,
|
|
||||||
} from './app/datacenter';
|
|
@ -1,38 +0,0 @@
|
|||||||
/**
|
|
||||||
* @vitest-environment happy-dom
|
|
||||||
*/
|
|
||||||
import 'fake-indexeddb/auto';
|
|
||||||
|
|
||||||
import { DataCenter, getDataCenter } from '@affine/datacenter';
|
|
||||||
import {
|
|
||||||
createDefaultWorkspace,
|
|
||||||
GlobalAppProvider,
|
|
||||||
useDataCenter,
|
|
||||||
useGlobalState,
|
|
||||||
} from '@affine/store';
|
|
||||||
import { render } from '@testing-library/react';
|
|
||||||
import { describe, expect, test } from 'vitest';
|
|
||||||
|
|
||||||
describe('App Store', () => {
|
|
||||||
test('init', async () => {
|
|
||||||
const dataCenterPromise = getDataCenter();
|
|
||||||
const dataCenter = await dataCenterPromise;
|
|
||||||
await createDefaultWorkspace(dataCenter);
|
|
||||||
const Inner = () => {
|
|
||||||
const state = useGlobalState();
|
|
||||||
const dataCenter = useDataCenter();
|
|
||||||
expect(state).toBeTypeOf('object');
|
|
||||||
expect(dataCenter).toBeInstanceOf(DataCenter);
|
|
||||||
return <div>Test2</div>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const App = () => (
|
|
||||||
<GlobalAppProvider>
|
|
||||||
<div>Test1</div>
|
|
||||||
<Inner />
|
|
||||||
</GlobalAppProvider>
|
|
||||||
);
|
|
||||||
const app = render(<App />);
|
|
||||||
app.getByText('Test2');
|
|
||||||
});
|
|
||||||
});
|
|
1881
pnpm-lock.yaml
1881
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -8,3 +8,4 @@ if [ "$1" == "--latest" ]; then
|
|||||||
else
|
else
|
||||||
pnpm up "@blocksuite/*@${1}" "!@blocksuite/icons" -r
|
pnpm up "@blocksuite/*@${1}" "!@blocksuite/icons" -r
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ import { test } from './libs/playwright';
|
|||||||
loadPage();
|
loadPage();
|
||||||
|
|
||||||
test.describe('Local first export page', () => {
|
test.describe('Local first export page', () => {
|
||||||
test('New a page ,then open it and export html', async ({ page }) => {
|
test.skip('New a page ,then open it and export html', async ({ page }) => {
|
||||||
await newPage(page);
|
await newPage(page);
|
||||||
await page.getByPlaceholder('Title').click();
|
await page.getByPlaceholder('Title').click();
|
||||||
await page
|
await page
|
||||||
@ -34,7 +34,9 @@ test.describe('Local first export page', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('New a page ,then open it and export markdown', async ({ page }) => {
|
test.skip('New a page ,then open it and export markdown', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
await newPage(page);
|
await newPage(page);
|
||||||
await page.getByPlaceholder('Title').click();
|
await page.getByPlaceholder('Title').click();
|
||||||
await page
|
await page
|
||||||
|
@ -1,13 +1,8 @@
|
|||||||
import path from 'node:path';
|
|
||||||
|
|
||||||
import react from '@vitejs/plugin-react';
|
|
||||||
import { fileURLToPath } from 'url';
|
|
||||||
import { defineConfig } from 'vitest/config';
|
import { defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
|
||||||
test: {
|
test: {
|
||||||
include: ['packages/**/*.spec.ts', 'packages/**/*.spec.tsx'],
|
include: ['packages/**/*.spec.ts'],
|
||||||
testTimeout: 5000,
|
testTimeout: 5000,
|
||||||
coverage: {
|
coverage: {
|
||||||
provider: 'istanbul', // or 'c8'
|
provider: 'istanbul', // or 'c8'
|
||||||
@ -15,11 +10,4 @@ export default defineConfig({
|
|||||||
reportsDirectory: '.coverage/store',
|
reportsDirectory: '.coverage/store',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
resolve: {
|
|
||||||
alias: {
|
|
||||||
'@affine/store': path.resolve(
|
|
||||||
fileURLToPath(new URL('packages/store', import.meta.url))
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user