refactor(store): extract workspace out of AppState (#1037)

This commit is contained in:
Himself65 2023-02-15 21:41:43 -06:00 committed by GitHub
parent a4d0813354
commit cdc2b449a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 323 additions and 245 deletions

View File

@ -1,7 +1,6 @@
import { Modal, ModalWrapper } from '@affine/component';
import { IconButton } from '@affine/component';
import { useTranslation } from '@affine/i18n';
import { useAppState } from '@/providers/app-state-provider';
import { useState } from 'react';
import router from 'next/router';
import { toast } from '@affine/component';
@ -22,7 +21,9 @@ export const EnableWorkspaceModal = ({
const login = useGlobalState(store => store.login);
const user = useGlobalState(store => store.user);
const dataCenter = useGlobalState(store => store.dataCenter);
const { currentWorkspace } = useAppState();
const currentWorkspace = useGlobalState(
useCallback(store => store.currentDataCenterWorkspace, [])
);
const [loading, setLoading] = useState(false);
return (
<Modal open={open} onClose={onClose} data-testid="logout-modal">

View File

@ -14,6 +14,7 @@
"@affine/i18n": "workspace:*",
"@blocksuite/blocks": "0.4.0-20230216011811-2776d93",
"@blocksuite/editor": "0.4.0-20230216011811-2776d93",
"@blocksuite/global": "0.4.0-20230216011811-2776d93",
"@blocksuite/icons": "^2.0.14",
"@blocksuite/store": "0.4.0-20230216011811-2776d93",
"@emotion/css": "^11.10.5",

View File

@ -1,9 +1,8 @@
import { useAppState } from '@/providers/app-state-provider';
import { IconButton, Modal, ModalWrapper, toast } from '@affine/component';
import { useTranslation } from '@affine/i18n';
import { CloseIcon } from '@blocksuite/icons';
import { useRouter } from 'next/router';
import { useState } from 'react';
import { useCallback, useState } from 'react';
import { Content, ContentTitle, Header, StyleButton, StyleTips } from './style';
import { useGlobalState } from '@/store/app';
@ -20,7 +19,9 @@ export const EnableWorkspaceModal = ({
const login = useGlobalState(store => store.login);
const user = useGlobalState(store => store.user);
const dataCenter = useGlobalState(store => store.dataCenter);
const { currentWorkspace } = useAppState();
const currentWorkspace = useGlobalState(
useCallback(store => store.currentDataCenterWorkspace, [])
);
const [loading, setLoading] = useState(false);
const router = useRouter();

View File

@ -1,10 +1,10 @@
import { LocalWorkspaceIcon, CloudWorkspaceIcon } from '@blocksuite/icons';
import { useAppState } from '@/providers/app-state-provider';
import { displayFlex, styled, Tooltip, IconButton } from '@affine/component';
import { useTranslation } from '@affine/i18n';
import { useModal } from '@/store/globalModal';
import { useEffect, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { WorkspaceUnit } from '@affine/datacenter';
import { useGlobalState } from '@/store/app';
const NoNetWorkIcon = () => {
return (
@ -43,7 +43,9 @@ const getStatus = (workspace: WorkspaceUnit | null) => {
return 'cloud';
};
export const SyncUser = () => {
const { currentWorkspace } = useAppState();
const currentWorkspace = useGlobalState(
useCallback(store => store.currentDataCenterWorkspace, [])
);
const { triggerEnableWorkspaceModal } = useModal();
const [status, setStatus] = useState<'offline' | 'local' | 'cloud'>(

View File

@ -1,14 +1,17 @@
import { Button } from '@affine/component';
import { usePageHelper } from '@/hooks/use-page-helper';
import { useAppState } from '@/providers/app-state-provider';
import { useConfirm } from '@/providers/ConfirmProvider';
import { useRouter } from 'next/router';
import useCurrentPageMeta from '@/hooks/use-current-page-meta';
import { useTranslation } from '@affine/i18n';
import { useGlobalState } from '@/store/app';
import { useCallback } from 'react';
export const TrashButtonGroup = () => {
const { permanentlyDeletePage } = usePageHelper();
const { currentWorkspace } = useAppState();
const currentWorkspace = useGlobalState(
useCallback(store => store.currentDataCenterWorkspace, [])
);
const { toggleDeletePage } = usePageHelper();
const confirm = useConfirm(store => store.confirm);
const router = useRouter();

View File

@ -4,9 +4,9 @@ import { Button } from '@affine/component';
import { Content, FlexWrapper } from '@affine/component';
import Loading from '@/components/loading';
import { usePageHelper } from '@/hooks/use-page-helper';
import { useAppState } from '@/providers/app-state-provider';
import { useEffect, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from '@affine/i18n';
import { useGlobalState } from '@/store/app';
// import { Tooltip } from '@affine/component';
type ImportModalProps = {
open: boolean;
@ -19,7 +19,9 @@ type Template = {
export const ImportModal = ({ open, onClose }: ImportModalProps) => {
const [status, setStatus] = useState<'unImported' | 'importing'>('importing');
const { openPage, createPage } = usePageHelper();
const { currentWorkspace } = useAppState();
const currentWorkspace = useGlobalState(
useCallback(store => store.currentDataCenterWorkspace, [])
);
const { t } = useTranslation();
const _applyTemplate = function (pageId: string, template: Template) {
const page = currentWorkspace?.blocksuiteWorkspace?.getPage(pageId);

View File

@ -21,16 +21,16 @@ import {
import { OperationCell, TrashOperationCell } from './OperationCell';
import Empty from './Empty';
import { Content } from '@affine/component';
import React from 'react';
import React, { useCallback } from 'react';
import DateCell from '@/components/page-list/DateCell';
import { IconButton } from '@affine/component';
import { Tooltip } from '@affine/component';
import { useRouter } from 'next/router';
import { useAppState } from '@/providers/app-state-provider';
import { toast } from '@affine/component';
import { usePageHelper } from '@/hooks/use-page-helper';
import { useTheme } from '@/providers/ThemeProvider';
import { useTranslation } from '@affine/i18n';
import { useGlobalState } from '@/store/app';
const FavoriteTag = ({
pageMeta: { favorite, id },
}: {
@ -83,7 +83,9 @@ export const PageList = ({
listType?: 'all' | 'trash' | 'favorite';
}) => {
const router = useRouter();
const { currentWorkspace } = useAppState();
const currentWorkspace = useGlobalState(
useCallback(store => store.currentDataCenterWorkspace, [])
);
const { t } = useTranslation();
if (pageList.length === 0) {
return <Empty listType={listType} />;

View File

@ -1,13 +1,19 @@
import { Command } from 'cmdk';
import { StyledListItem, StyledNotFound } from './style';
import { PaperIcon, EdgelessIcon } from '@blocksuite/icons';
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
import { useAppState } from '@/providers/app-state-provider';
import {
Dispatch,
SetStateAction,
useCallback,
useEffect,
useState,
} from 'react';
import { useRouter } from 'next/router';
import { useSwitchToConfig } from './config';
import { NoResultSVG } from './NoResultSVG';
import { useTranslation } from '@affine/i18n';
import usePageHelper from '@/hooks/use-page-helper';
import { useGlobalState } from '@/store/app';
export const Results = (props: {
query: string;
loading: boolean;
@ -18,7 +24,12 @@ export const Results = (props: {
const { query, loading, setLoading, setShowCreatePage, onClose } = props;
const { openPage } = usePageHelper();
const router = useRouter();
const { currentWorkspace, pageList } = useAppState();
const currentWorkspace = useGlobalState(
useCallback(store => store.currentDataCenterWorkspace, [])
);
const pageList = useGlobalState(
useCallback(store => store.dataCenterPageList, [])
);
const { search } = usePageHelper();
const List = useSwitchToConfig(currentWorkspace?.id);
const [results, setResults] = useState(new Map<string, string | undefined>());

View File

@ -7,10 +7,10 @@ import {
PublishIcon,
} from '@/components/icons';
import { WorkspaceUnit } from '@affine/datacenter';
import { useAppState } from '@/providers/app-state-provider';
import { StyleWorkspaceInfo, StyleWorkspaceTitle, StyledCard } from './styles';
import { useTranslation } from '@affine/i18n';
import { useGlobalState } from '@/store/app';
import { useCallback } from 'react';
const WorkspaceType = ({ workspaceData }: { workspaceData: WorkspaceUnit }) => {
const user = useGlobalState(store => store.user);
@ -46,7 +46,9 @@ export const WorkspaceCard = ({
workspaceData: WorkspaceUnit;
onClick: (data: WorkspaceUnit) => void;
}) => {
const { currentWorkspace } = useAppState();
const currentWorkspace = useGlobalState(
useCallback(store => store.currentDataCenterWorkspace, [])
);
const { t } = useTranslation();
return (
<StyledCard

View File

@ -7,9 +7,8 @@ import {
import { StyledSettingKey, StyledRow } from '../style';
import { FlexWrapper } from '@affine/component';
import { useState } from 'react';
import { useCallback, useState } from 'react';
import { Button } from '@affine/component';
import { useAppState } from '@/providers/app-state-provider';
import { WorkspaceDelete } from './delete';
import { WorkspaceLeave } from './leave';
import {
@ -31,7 +30,9 @@ export const GeneralPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
const [workspaceName, setWorkspaceName] = useState<string>(workspace?.name);
const [showEditInput, setShowEditInput] = useState(false);
const isOwner = useGlobalState(store => store.isOwner);
const { currentWorkspace } = useAppState();
const currentWorkspace = useGlobalState(
useCallback(store => store.currentDataCenterWorkspace, [])
);
const { updateWorkspace } = useWorkspaceHelper();
const { t } = useTranslation();

View File

@ -1,17 +1,19 @@
import { WorkspaceName, SelectorWrapper } from './styles';
import { useEffect, useState } from 'react';
import { useCallback, useState } from 'react';
import { WorkspaceModal } from '@/components/workspace-modal';
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
import { useAppState } from '@/providers/app-state-provider';
import { useGlobalState } from '@/store/app';
export const WorkspaceSelector = () => {
const [workspaceListShow, setWorkspaceListShow] = useState(false);
const { currentWorkspace, workspaceList } = useAppState();
const currentWorkspace = useGlobalState(
useCallback(store => store.currentDataCenterWorkspace, [])
);
const dataCenter = useGlobalState(useCallback(store => store.dataCenter, []));
useEffect(() => {
if (workspaceList.length === 0) {
setWorkspaceListShow(true);
}
}, [workspaceList]);
if (dataCenter.workspaces.length === 0) {
setWorkspaceListShow(true);
}
return (
<>
<SelectorWrapper

View File

@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useCallback, useState } from 'react';
import { useRouter } from 'next/router';
import {
StyledArrowButton,
@ -24,16 +24,16 @@ import Link from 'next/link';
import { MuiCollapse } from '@affine/component';
import { Tooltip } from '@affine/component';
import { useModal } from '@/store/globalModal';
import { useAppState } from '@/providers/app-state-provider';
import { IconButton } from '@affine/component';
import useLocalStorage from '@/hooks/use-local-storage';
import { usePageHelper } from '@/hooks/use-page-helper';
import { useTranslation } from '@affine/i18n';
import { WorkspaceSelector } from './WorkspaceSelector/WorkspaceSelector';
import { useGlobalState } from '@/store/app';
const FavoriteList = ({ showList }: { showList: boolean }) => {
const { openPage } = usePageHelper();
const { pageList } = useAppState();
const pageList = useGlobalState(store => store.dataCenterPageList);
const router = useRouter();
const { t } = useTranslation();
const favoriteList = pageList.filter(p => p.favorite && !p.trash);
@ -66,7 +66,9 @@ const FavoriteList = ({ showList }: { showList: boolean }) => {
export const WorkSpaceSliderBar = () => {
const { triggerQuickSearchModal } = useModal();
const [showSubFavorite, setShowSubFavorite] = useState(true);
const { currentWorkspace } = useAppState();
const currentWorkspace = useGlobalState(
useCallback(store => store.currentDataCenterWorkspace, [])
);
const { openPage, createPage } = usePageHelper();
const router = useRouter();
const { t } = useTranslation();

View File

@ -1,5 +1,6 @@
import { useCallback } from 'react';
import { useAppState, PageMeta } from '@/providers/app-state-provider';
import { PageMeta } from '@/providers/app-state-provider';
import { useGlobalState } from '@/store/app';
export type ChangePageMeta = (
pageId: string,
@ -7,7 +8,9 @@ export type ChangePageMeta = (
) => void;
export const useChangePageMeta = () => {
const { currentWorkspace } = useAppState();
const currentWorkspace = useGlobalState(
useCallback(store => store.currentDataCenterWorkspace, [])
);
return useCallback<ChangePageMeta>(
(pageId, pageMeta) => {

View File

@ -1,35 +1,42 @@
import { useAppState } from '@/providers/app-state-provider';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { useGlobalState } from '@/store/app';
import { assertEquals } from '@blocksuite/global/utils';
// 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 [workspaceLoaded, setWorkspaceLoaded] = useState(false);
const dataCenter = useGlobalState(store => store.dataCenter);
const { loadWorkspace } = useAppState();
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 [activeWorkspaceId, setActiveWorkspaceId] = useState(
router.query.workspaceId as string
const [currentWorkspaceId, setCurrentWorkspaceId] = useState<string | null>(
typeof router.query.workspaceId === 'string'
? router.query.workspaceId
: null
);
// const defaultOutLineWorkspaceId = '99ce7eb7';
// console.log(defaultOutLineWorkspaceId);
useEffect(() => {
setWorkspaceLoaded(false);
let aborted = false;
const abortController = new AbortController();
const workspaceList = dataCenter.workspaces;
const workspaceId =
(router.query.workspaceId as string) || workspaceList[0]?.id;
(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 &&
workspaceList.length &&
workspaceList.findIndex(meta => meta.id.toString() === workspaceId) === -1
dataCenter.workspaces.length &&
dataCenter.workspaces.findIndex(
meta => meta.id.toString() === workspaceId
) === -1
) {
router.push('/404');
return;
@ -44,22 +51,21 @@ export const useEnsureWorkspace = () => {
// return;
// }
loadWorkspace.current(workspaceId, abortController.signal).then(unit => {
if (!aborted && unit) {
setWorkspaceLoaded(true);
setActiveWorkspaceId(workspaceId);
loadWorkspace(workspaceId, abortController.signal).then(unit => {
if (!abortController.signal.aborted && unit) {
setCurrentWorkspaceId(unit.id);
assertEquals(unit.id, workspaceId);
}
});
return () => {
aborted = true;
abortController.abort();
};
}, [dataCenter, loadWorkspace, router]);
return {
workspaceLoaded,
activeWorkspaceId,
workspaceLoaded: currentWorkspace?.id === currentWorkspaceId,
activeWorkspaceId: currentWorkspace?.id ?? router.query.workspaceId,
};
};

View File

@ -1,10 +1,11 @@
import { useCallback, useEffect, useState } from 'react';
import { Member } from '@affine/datacenter';
import { useAppState } from '@/providers/app-state-provider';
import { useGlobalState } from '@/store/app';
export const useMembers = () => {
const dataCenter = useGlobalState(store => store.dataCenter);
const { currentWorkspace } = useAppState();
const currentWorkspace = useGlobalState(
useCallback(store => store.currentDataCenterWorkspace, [])
);
const [members, setMembers] = useState<Member[]>([]);
const [loaded, setLoaded] = useState(false);
const refreshMembers = useCallback(async () => {

View File

@ -1,11 +1,12 @@
import { uuidv4, Workspace } from '@blocksuite/store';
import { QueryContent } from '@blocksuite/store/dist/workspace/search';
import { PageMeta, useAppState } from '@/providers/app-state-provider';
import { PageMeta } from '@/providers/app-state-provider';
import { EditorContainer } from '@blocksuite/editor';
import { useChangePageMeta } from '@/hooks/use-change-page-meta';
import { useRouter } from 'next/router';
import { WorkspaceUnit } from '@affine/datacenter';
import { useGlobalState } from '@/store/app';
import { useCallback } from 'react';
export type EditorHandlers = {
createPage: (params?: {
@ -41,7 +42,9 @@ export const usePageHelper = (): EditorHandlers => {
const router = useRouter();
const changePageMeta = useChangePageMeta();
const editor = useGlobalState(store => store.editor);
const { currentWorkspace } = useAppState();
const currentWorkspace = useGlobalState(
useCallback(store => store.currentDataCenterWorkspace, [])
);
return {
createPage: ({

View File

@ -1,16 +1,19 @@
import { useAppState } from '@/providers/app-state-provider';
import { WorkspaceUnit } from '@affine/datacenter';
import { useGlobalState } from '@/store/app';
import { useCallback } from 'react';
export const useWorkspaceHelper = () => {
const dataCenter = useGlobalState(store => store.dataCenter);
const { currentWorkspace } = useAppState();
const currentWorkspace = useGlobalState(
useCallback(store => store.currentDataCenterWorkspace, [])
);
const loadWorkspace = useGlobalState(store => store.loadWorkspace);
const createWorkspace = async (name: string) => {
const workspaceInfo = await dataCenter.createWorkspace({
name: name,
});
if (workspaceInfo && workspaceInfo.id) {
return await dataCenter.loadWorkspace(workspaceInfo.id);
return loadWorkspace(workspaceInfo.id);
}
return null;
};

View File

@ -22,7 +22,8 @@ import Head from 'next/head';
import '@affine/i18n';
import { useTranslation } from '@affine/i18n';
import React from 'react';
import { DataCenterLoader, GlobalAppProvider } from '@/store/app';
import { GlobalAppProvider } from '@/store/app';
import { DataCenterLoader } from '@/store/app/datacenter';
const ThemeProvider = dynamic(() => import('@/providers/ThemeProvider'), {
ssr: false,

View File

@ -1,7 +1,12 @@
import { PropsWithChildren, ReactElement, useEffect, useState } from 'react';
import {
PropsWithChildren,
ReactElement,
useCallback,
useEffect,
useState,
} from 'react';
import { EditorHeader } from '@/components/header';
import MobileModal from '@/components/mobile-modal';
import { useAppState } from '@/providers/app-state-provider';
import type { NextPageWithLayout } from '../..//_app';
import WorkspaceLayout from '@/components/workspace-layout';
import { useRouter } from 'next/router';
@ -53,7 +58,9 @@ const BlockHubAppender = () => {
const Page: NextPageWithLayout = () => {
const currentPage = useGlobalState(store => store.currentPage);
const setEditor = useGlobalState(store => store.setEditor);
const { currentWorkspace } = useAppState();
const currentWorkspace = useGlobalState(
useCallback(store => store.currentDataCenterWorkspace, [])
);
const { t } = useTranslation();
@ -95,7 +102,9 @@ const PageDefender = ({ children }: PropsWithChildren) => {
const router = useRouter();
const [pageLoaded, setPageLoaded] = useState(false);
const loadPage = useGlobalState(store => store.loadPage);
const { currentWorkspace } = useAppState();
const currentWorkspace = useGlobalState(
useCallback(store => store.currentDataCenterWorkspace, [])
);
const { createPage } = usePageHelper();
useEffect(() => {

View File

@ -1,14 +1,17 @@
import { PageList } from '@/components/page-list';
import { AllPagesIcon } from '@blocksuite/icons';
import { PageListHeader } from '@/components/header';
import { ReactElement } from 'react';
import { ReactElement, useCallback } from 'react';
import WorkspaceLayout from '@/components/workspace-layout';
import { useTranslation } from '@affine/i18n';
import { PageMeta, useAppState } from '@/providers/app-state-provider';
import { PageMeta } from '@/providers/app-state-provider';
import Head from 'next/head';
import { useGlobalState } from '@/store/app';
const All = () => {
const { currentWorkspace } = useAppState();
const currentWorkspace = useGlobalState(
useCallback(store => store.currentDataCenterWorkspace, [])
);
const pageList = (currentWorkspace?.blocksuiteWorkspace?.meta.pageMetas ||
[]) as PageMeta[];
const { t } = useTranslation();

View File

@ -4,10 +4,11 @@ import { FavouritesIcon } from '@blocksuite/icons';
import { ReactElement } from 'react';
import WorkspaceLayout from '@/components/workspace-layout';
import { useTranslation } from '@affine/i18n';
import { useAppState } from '@/providers/app-state-provider';
import Head from 'next/head';
import { useGlobalState } from '@/store/app';
export const Favorite = () => {
const { pageList } = useAppState();
const pageList = useGlobalState(store => store.dataCenterPageList);
const { t } = useTranslation();
return (
<>

View File

@ -1,13 +1,15 @@
import { useEffect } from 'react';
import { useCallback, useEffect } from 'react';
import { useRouter } from 'next/router';
import { useAppState } from '@/providers/app-state-provider';
import useEnsureWorkspace from '@/hooks/use-ensure-workspace';
import { PageLoading } from '@/components/loading';
import usePageHelper from '@/hooks/use-page-helper';
import { useGlobalState } from '@/store/app';
const WorkspaceIndex = () => {
const router = useRouter();
const { currentWorkspace } = useAppState();
const currentWorkspace = useGlobalState(
useCallback(store => store.currentDataCenterWorkspace, [])
);
const { createPage } = usePageHelper();
const { workspaceLoaded, activeWorkspaceId } = useEnsureWorkspace();

View File

@ -10,6 +10,7 @@ import {
CSSProperties,
useEffect,
startTransition,
useCallback,
} from 'react';
import {
GeneralPage,
@ -18,7 +19,6 @@ import {
ExportPage,
} from '@/components/workspace-setting';
import { SettingsIcon } from '@blocksuite/icons';
import { useAppState } from '@/providers/app-state-provider';
import WorkspaceLayout from '@/components/workspace-layout';
import { WorkspaceUnit } from '@affine/datacenter';
import { useTranslation } from '@affine/i18n';
@ -98,7 +98,9 @@ const StyledTabButtonWrapper = styled.div(() => {
});
const WorkspaceSetting = () => {
const { t } = useTranslation();
const { currentWorkspace } = useAppState();
const currentWorkspace = useGlobalState(
useCallback(store => store.currentDataCenterWorkspace, [])
);
const { activeTabPanelRender, tableArr, handleTabChange, activeTab } =
useTabMap();
const [indicatorState, setIndicatorState] = useState<

View File

@ -4,10 +4,10 @@ import { TrashIcon } from '@blocksuite/icons';
import { ReactElement } from 'react';
import WorkspaceLayout from '@/components/workspace-layout';
import { useTranslation } from '@affine/i18n';
import { useAppState } from '@/providers/app-state-provider';
import Head from 'next/head';
import { useGlobalState } from '@/store/app';
export const Trash = () => {
const { pageList } = useAppState();
const pageList = useGlobalState(store => store.dataCenterPageList);
const { t } = useTranslation();
return (
<>

View File

@ -1,12 +1,14 @@
import { useEffect } from 'react';
import { useCallback, useEffect } from 'react';
import { useRouter } from 'next/router';
import { useAppState } from '@/providers/app-state-provider';
import useEnsureWorkspace from '@/hooks/use-ensure-workspace';
import { PageLoading } from '@/components/loading';
import { useGlobalState } from '@/store/app';
export const WorkspaceIndex = () => {
const router = useRouter();
const { currentWorkspace } = useAppState();
const currentWorkspace = useGlobalState(
useCallback(store => store.currentDataCenterWorkspace, [])
);
const { workspaceLoaded } = useEnsureWorkspace();
useEffect(() => {

View File

@ -1,16 +1,8 @@
import { createContext, useContext, useEffect, useState, useRef } from 'react';
import { createContext, useContext, useEffect, useState } from 'react';
import type { PropsWithChildren } from 'react';
import {
AppStateContext,
AppStateFunction,
AppStateValue,
PageMeta,
} from './interface';
import { useGlobalState, useGlobalStateApi } from '@/store/app';
export interface Disposable {
dispose(): void;
}
import { AppStateContext } from './interface';
import type { Disposable } from '@blocksuite/global/utils';
import { useGlobalState } from '@/store/app';
type AppStateContextProps = PropsWithChildren<Record<string, unknown>>;
@ -20,113 +12,14 @@ export const useAppState = () => useContext(AppState);
export const AppStateProvider = ({
children,
}: PropsWithChildren<AppStateContextProps>) => {
const globalStateApi = useGlobalStateApi();
const [appState, setAppState] = useState<AppStateValue>({} as AppStateValue);
const currentDataCenterWorkspace = useGlobalState(
store => store.currentDataCenterWorkspace
);
const [blobState, setBlobState] = useState(false);
useEffect(() => {
if (!appState?.currentWorkspace?.blocksuiteWorkspace) {
return;
}
const currentWorkspace = appState.currentWorkspace;
const dispose = currentWorkspace?.blocksuiteWorkspace?.meta.pagesUpdated.on(
() => {
setAppState({
...appState,
pageList: currentWorkspace.blocksuiteWorkspace?.meta
.pageMetas as PageMeta[],
});
}
).dispose;
return () => {
dispose && dispose();
};
}, [appState]);
const onceRef = useRef(true);
const dataCenter = useGlobalState(store => store.dataCenter);
if (onceRef.current && dataCenter) {
setAppState({
workspaceList: dataCenter.workspaces,
currentWorkspace: null,
pageList: [],
});
onceRef.current = false;
}
useEffect(() => {
// FIXME: onWorkspacesChange should have dispose function
return dataCenter?.onWorkspacesChange(
() => {
setAppState(_appState => ({
..._appState,
workspaceList: dataCenter.workspaces,
}));
},
{ immediate: false }
);
}, [dataCenter]);
const loadWorkspace: AppStateFunction['loadWorkspace'] =
useRef() as AppStateFunction['loadWorkspace'];
loadWorkspace.current = async (workspaceId, abort) => {
const { dataCenter } = globalStateApi.getState();
const { workspaceList, currentWorkspace } = appState;
if (!workspaceList.find(v => v.id.toString() === workspaceId)) {
return null;
}
if (workspaceId === currentWorkspace?.id) {
return currentWorkspace;
}
let aborted = false;
const onAbort = () => {
aborted = true;
};
abort?.addEventListener('abort', onAbort);
const workspace = (await dataCenter.loadWorkspace(workspaceId)) ?? null;
if (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 = globalStateApi.getState().user;
// We must ensure workspace.owner exists, then ensure id same.
isOwner = workspace?.owner && userInfo?.id === workspace.owner.id;
}
const pageList =
(workspace?.blocksuiteWorkspace?.meta.pageMetas as PageMeta[]) ?? [];
if (workspace?.blocksuiteWorkspace) {
globalStateApi.getState().setWorkspace(workspace.blocksuiteWorkspace);
}
globalStateApi.setState({
isOwner,
});
setAppState({
...appState,
currentWorkspace: workspace,
pageList: pageList,
});
abort?.removeEventListener('abort', onAbort);
return workspace;
};
useEffect(() => {
let syncChangeDisposable: Disposable | undefined;
const currentWorkspace = appState.currentWorkspace;
const currentWorkspace = currentDataCenterWorkspace;
if (!currentWorkspace) {
return;
}
@ -142,13 +35,11 @@ export const AppStateProvider = ({
return () => {
syncChangeDisposable?.dispose();
};
}, [appState.currentWorkspace]);
}, [currentDataCenterWorkspace]);
return (
<AppState.Provider
value={{
...appState,
loadWorkspace: loadWorkspace,
blobDataSynced: blobState,
}}
>

View File

@ -1,11 +1,10 @@
import { WorkspaceUnit } from '@affine/datacenter';
import type { EditorContainer } from '@blocksuite/editor';
import type {
Page as StorePage,
PageMeta as StorePageMeta,
} from '@blocksuite/store';
import { MutableRefObject } from 'react';
export interface PageMeta extends StorePageMeta {
favorite: boolean;
trash: boolean;
@ -15,16 +14,14 @@ export interface PageMeta extends StorePageMeta {
}
export type AppStateValue = {
workspaceList: WorkspaceUnit[];
currentWorkspace: WorkspaceUnit | null;
pageList: PageMeta[];
blobDataSynced?: boolean;
blobDataSynced: boolean;
};
/**
* @deprecated
*/
export type AppStateFunction = {
loadWorkspace: MutableRefObject<
(workspaceId: string, abort?: AbortSignal) => Promise<WorkspaceUnit | null>
>;
// todo: remove this in the future
};
export type AppStateContext = AppStateValue & AppStateFunction;

View File

@ -0,0 +1,139 @@
import {
GlobalActionsCreator,
useGlobalState,
useGlobalStateApi,
} from '@/store/app';
import type { DataCenter } from '@affine/datacenter';
import { PageMeta } from '@/providers/app-state-provider';
import { getDataCenter, WorkspaceUnit } from '@affine/datacenter';
import { createDefaultWorkspace } from '@/providers/app-state-provider/utils';
import { useCallback, useEffect } from 'react';
import { DisposableGroup } from '@blocksuite/global/utils';
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 => ({
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
dataCenter: null!,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
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 = workspace?.owner && 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 DataCenterLoader() {
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: [],
});
});
throw promise;
}
if (!dataCenter) {
throw dataCenterPromise;
}
return null;
}

View File

@ -15,8 +15,12 @@ import {
UserActions,
UserState,
} from '@/store/app/user';
import { DataCenter, getDataCenter } from '@affine/datacenter';
import { createDefaultWorkspace } from '@/providers/app-state-provider/utils';
import {
createDataCenterActions,
createDataCenterState,
DataCenterActions,
DataCenterState,
} from '@/store/app/datacenter';
export type GlobalActionsCreator<Actions, Store = GlobalState> = StateCreator<
Store,
@ -25,12 +29,15 @@ export type GlobalActionsCreator<Actions, Store = GlobalState> = StateCreator<
Actions
>;
export interface GlobalState extends BlockSuiteState, UserState {
readonly dataCenter: DataCenter;
readonly dataCenterPromise: Promise<DataCenter>;
}
export interface GlobalState
extends BlockSuiteState,
UserState,
DataCenterState {}
export interface GlobalActions extends BlockSuiteActions, UserActions {}
export interface GlobalActions
extends BlockSuiteActions,
UserActions,
DataCenterActions {}
const create = () =>
createStore(
@ -39,15 +46,13 @@ const create = () =>
{
...createBlockSuiteState(),
...createUserState(),
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
dataCenter: null!,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
dataCenterPromise: null!,
...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 */
)
@ -74,28 +79,6 @@ export const useGlobalState: UseBoundStore<Store> = ((
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}) as any;
export function DataCenterLoader() {
const dataCenter = useGlobalState(store => store.dataCenter);
const dataCenterPromise = useGlobalState(store => store.dataCenterPromise);
const api = useGlobalStateApi();
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);
}
api.setState({ dataCenter });
});
throw promise;
}
if (!dataCenter) {
throw dataCenterPromise;
}
return null;
}
export const GlobalAppProvider: React.FC<React.PropsWithChildren> =
function ModelProvider({ children }) {
return (

View File

@ -164,6 +164,7 @@ importers:
'@affine/i18n': workspace:*
'@blocksuite/blocks': 0.4.0-20230216011811-2776d93
'@blocksuite/editor': 0.4.0-20230216011811-2776d93
'@blocksuite/global': 0.4.0-20230216011811-2776d93
'@blocksuite/icons': ^2.0.14
'@blocksuite/store': 0.4.0-20230216011811-2776d93
'@emotion/css': ^11.10.5
@ -207,6 +208,7 @@ importers:
'@affine/i18n': link:../../packages/i18n
'@blocksuite/blocks': 0.4.0-20230216011811-2776d93_trq6tjva7swiv4fhloorah5nty
'@blocksuite/editor': 0.4.0-20230216011811-2776d93_6yfgczikbxxddpvfspgrnx2z2q
'@blocksuite/global': 0.4.0-20230216011811-2776d93_lit@2.6.1
'@blocksuite/icons': 2.0.14_w5j4k42lgipnm43s3brx6h3c34
'@blocksuite/store': 0.4.0-20230216011811-2776d93_lit@2.6.1+yjs@13.5.45
'@emotion/css': 11.10.5