refactor(store): port to useGlobalState with zustand (#1012)

This commit is contained in:
Himself65
2023-02-14 23:38:21 -06:00
committed by GitHub
parent 2b3ec1240a
commit 6a8aff9e56
29 changed files with 304 additions and 207 deletions

View File

@@ -6,6 +6,7 @@ import { useState } from 'react';
import router from 'next/router'; import router from 'next/router';
import { toast } from '@affine/component'; import { toast } from '@affine/component';
import { CloseIcon } from '@blocksuite/icons'; import { CloseIcon } from '@blocksuite/icons';
import { useGlobalState } from '@/store/app';
import { Header, Content, ContentTitle, StyleTips, StyleButton } from './style'; import { Header, Content, ContentTitle, StyleTips, StyleButton } from './style';
interface EnableWorkspaceModalProps { interface EnableWorkspaceModalProps {
@@ -18,7 +19,10 @@ export const EnableWorkspaceModal = ({
onClose, onClose,
}: EnableWorkspaceModalProps) => { }: EnableWorkspaceModalProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { user, dataCenter, login, currentWorkspace } = useAppState(); const login = useGlobalState(store => store.login);
const user = useGlobalState(store => store.user);
const dataCenter = useGlobalState(store => store.dataCenter);
const { currentWorkspace } = useAppState();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
return ( return (
<Modal open={open} onClose={onClose} data-testid="logout-modal"> <Modal open={open} onClose={onClose} data-testid="logout-modal">

View File

@@ -19,7 +19,7 @@ import { Tooltip } from '@affine/component';
import useCurrentPageMeta from '@/hooks/use-current-page-meta'; import useCurrentPageMeta from '@/hooks/use-current-page-meta';
import useHistoryUpdated from '@/hooks/use-history-update'; import useHistoryUpdated from '@/hooks/use-history-update';
import { useTranslation } from '@affine/i18n'; import { useTranslation } from '@affine/i18n';
import { useBlockSuite } from '@/store/workspace'; import { useGlobalState } from '@/store/app';
const useToolbarList1 = () => { const useToolbarList1 = () => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -87,7 +87,7 @@ const useToolbarList1 = () => {
const UndoRedo = () => { const UndoRedo = () => {
const [canUndo, setCanUndo] = useState(false); const [canUndo, setCanUndo] = useState(false);
const [canRedo, setCanRedo] = useState(false); const [canRedo, setCanRedo] = useState(false);
const currentPage = useBlockSuite(store => store.currentPage); const currentPage = useGlobalState(store => store.currentPage);
const onHistoryUpdated = useHistoryUpdated(); const onHistoryUpdated = useHistoryUpdated();
const { t } = useTranslation(); const { t } = useTranslation();
useEffect(() => { useEffect(() => {

View File

@@ -5,6 +5,7 @@ import { CloseIcon } from '@blocksuite/icons';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useState } from 'react'; import { useState } from 'react';
import { Content, ContentTitle, Header, StyleButton, StyleTips } from './style'; import { Content, ContentTitle, Header, StyleButton, StyleTips } from './style';
import { useGlobalState } from '@/store/app';
interface EnableWorkspaceModalProps { interface EnableWorkspaceModalProps {
open: boolean; open: boolean;
@@ -16,7 +17,10 @@ export const EnableWorkspaceModal = ({
onClose, onClose,
}: EnableWorkspaceModalProps) => { }: EnableWorkspaceModalProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { user, dataCenter, login, currentWorkspace } = useAppState(); const login = useGlobalState(store => store.login);
const user = useGlobalState(store => store.user);
const dataCenter = useGlobalState(store => store.dataCenter);
const { currentWorkspace } = useAppState();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const router = useRouter(); const router = useRouter();

View File

@@ -11,12 +11,12 @@ import QuickSearchButton from './QuickSearchButton';
import Header from './Header'; import Header from './Header';
import usePropsUpdated from '@/hooks/use-props-updated'; import usePropsUpdated from '@/hooks/use-props-updated';
import useCurrentPageMeta from '@/hooks/use-current-page-meta'; import useCurrentPageMeta from '@/hooks/use-current-page-meta';
import { useBlockSuite } from '@/store/workspace'; import { useGlobalState } from '@/store/app';
export const EditorHeader = () => { export const EditorHeader = () => {
const [title, setTitle] = useState(''); const [title, setTitle] = useState('');
const [isHover, setIsHover] = useState(false); const [isHover, setIsHover] = useState(false);
const editor = useBlockSuite(store => store.editor); const editor = useGlobalState(store => store.editor);
const { trash: isTrash = false } = useCurrentPageMeta() || {}; const { trash: isTrash = false } = useCurrentPageMeta() || {};
const onPropsUpdated = usePropsUpdated(); const onPropsUpdated = usePropsUpdated();

View File

@@ -16,9 +16,9 @@ import { useConfirm } from '@/providers/ConfirmProvider';
import useCurrentPageMeta from '@/hooks/use-current-page-meta'; import useCurrentPageMeta from '@/hooks/use-current-page-meta';
import { toast } from '@affine/component'; import { toast } from '@affine/component';
import { useTranslation } from '@affine/i18n'; import { useTranslation } from '@affine/i18n';
import { useBlockSuite } from '@/store/workspace'; import { useGlobalState } from '@/store/app';
const PopoverContent = () => { const PopoverContent = () => {
const editor = useBlockSuite(store => store.editor); const editor = useGlobalState(store => store.editor);
const { toggleFavoritePage, toggleDeletePage } = usePageHelper(); const { toggleFavoritePage, toggleDeletePage } = usePageHelper();
const { changePageMode } = usePageHelper(); const { changePageMode } = usePageHelper();
const confirm = useConfirm(store => store.confirm); const confirm = useConfirm(store => store.confirm);

View File

@@ -11,7 +11,7 @@ import { Tooltip } from '@affine/component';
import { useTranslation } from '@affine/i18n'; import { useTranslation } from '@affine/i18n';
import { useModal } from '@/store/globalModal'; import { useModal } from '@/store/globalModal';
import { MuiFade } from '@affine/component'; import { MuiFade } from '@affine/component';
import { useBlockSuite } from '@/store/workspace'; import { useGlobalState } from '@/store/app';
export type IslandItemNames = 'contact' | 'shortcuts'; export type IslandItemNames = 'contact' | 'shortcuts';
export const HelpIsland = ({ export const HelpIsland = ({
showList = ['contact', 'shortcuts'], showList = ['contact', 'shortcuts'],
@@ -20,7 +20,7 @@ export const HelpIsland = ({
}) => { }) => {
const [spread, setShowSpread] = useState(false); const [spread, setShowSpread] = useState(false);
const { triggerShortcutsModal, triggerContactModal } = useModal(); const { triggerShortcutsModal, triggerContactModal } = useModal();
const blockHub = useBlockSuite(store => store.blockHub); const blockHub = useGlobalState(store => store.blockHub);
const { t } = useTranslation(); const { t } = useTranslation();
useEffect(() => { useEffect(() => {

View File

@@ -1,16 +1,16 @@
import { positionAbsolute, styled } from '@affine/component'; import { positionAbsolute, styled } from '@affine/component';
import { Modal, ModalWrapper, ModalCloseButton } from '@affine/component'; import { Modal, ModalWrapper, ModalCloseButton } from '@affine/component';
import { Button } from '@affine/component'; import { Button } from '@affine/component';
import { useAppState } from '@/providers/app-state-provider';
import { useTranslation } from '@affine/i18n'; import { useTranslation } from '@affine/i18n';
import { GoogleIcon } from './GoogleIcon'; import { GoogleIcon } from './GoogleIcon';
import { useGlobalState } from '@/store/app';
interface LoginModalProps { interface LoginModalProps {
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
} }
export const LoginModal = ({ open, onClose }: LoginModalProps) => { export const LoginModal = ({ open, onClose }: LoginModalProps) => {
const { login } = useAppState(); const login = useGlobalState(store => store.login);
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<Modal open={open} onClose={onClose} data-testid="login-modal"> <Modal open={open} onClose={onClose} data-testid="login-modal">

View File

@@ -2,12 +2,13 @@ import { Command } from 'cmdk';
import { StyledListItem, StyledNotFound } from './style'; import { StyledListItem, StyledNotFound } from './style';
import { PaperIcon, EdgelessIcon } from '@blocksuite/icons'; import { PaperIcon, EdgelessIcon } from '@blocksuite/icons';
import { Dispatch, SetStateAction, useEffect, useState } from 'react'; import { Dispatch, SetStateAction, useEffect, useState } from 'react';
import { useAppState, PageMeta } from '@/providers/app-state-provider'; import { PageMeta } from '@/providers/app-state-provider';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { NoResultSVG } from './NoResultSVG'; import { NoResultSVG } from './NoResultSVG';
import { useTranslation } from '@affine/i18n'; import { useTranslation } from '@affine/i18n';
import usePageHelper from '@/hooks/use-page-helper'; import usePageHelper from '@/hooks/use-page-helper';
import { Workspace } from '@blocksuite/store'; import { Workspace } from '@blocksuite/store';
import { useGlobalState } from '@/store/app';
export const PublishedResults = (props: { export const PublishedResults = (props: {
query: string; query: string;
@@ -21,7 +22,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 } = useAppState(); const dataCenter = useGlobalState(store => store.dataCenter);
const router = useRouter(); const router = useRouter();
const [pageList, setPageList] = useState<PageMeta[]>([]); const [pageList, setPageList] = useState<PageMeta[]>([]);
useEffect(() => { useEffect(() => {

View File

@@ -2,10 +2,10 @@ import { CloudWorkspaceIcon, SignOutIcon } from '@blocksuite/icons';
import { FlexWrapper } from '@affine/component'; import { FlexWrapper } from '@affine/component';
import { WorkspaceAvatar } from '@/components/workspace-avatar'; import { WorkspaceAvatar } from '@/components/workspace-avatar';
import { IconButton } from '@affine/component'; import { IconButton } from '@affine/component';
import { useAppState } from '@/providers/app-state-provider';
import { StyledFooter, StyleUserInfo, StyledSignInButton } from './styles'; import { StyledFooter, StyleUserInfo, StyledSignInButton } from './styles';
import { useTranslation } from '@affine/i18n'; import { useTranslation } from '@affine/i18n';
import { Tooltip } from '@affine/component'; import { Tooltip } from '@affine/component';
import { useGlobalState } from '@/store/app';
export const Footer = ({ export const Footer = ({
onLogin, onLogin,
onLogout, onLogout,
@@ -13,7 +13,7 @@ export const Footer = ({
onLogin: () => void; onLogin: () => void;
onLogout: () => void; onLogout: () => void;
}) => { }) => {
const { user } = useAppState(); const user = useGlobalState(store => store.user);
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (

View File

@@ -10,9 +10,10 @@ import { WorkspaceUnit } from '@affine/datacenter';
import { useAppState } from '@/providers/app-state-provider'; import { useAppState } from '@/providers/app-state-provider';
import { StyleWorkspaceInfo, StyleWorkspaceTitle, StyledCard } from './styles'; import { StyleWorkspaceInfo, StyleWorkspaceTitle, StyledCard } from './styles';
import { useTranslation } from '@affine/i18n'; import { useTranslation } from '@affine/i18n';
import { useGlobalState } from '@/store/app';
const WorkspaceType = ({ workspaceData }: { workspaceData: WorkspaceUnit }) => { const WorkspaceType = ({ workspaceData }: { workspaceData: WorkspaceUnit }) => {
const { user } = useAppState(); const user = useGlobalState(store => store.user);
const { t } = useTranslation(); const { t } = useTranslation();
const isOwner = user?.id === workspaceData.owner?.id; const isOwner = user?.id === workspaceData.owner?.id;

View File

@@ -6,7 +6,6 @@ import { Tooltip } from '@affine/component';
import { PlusIcon, HelpIcon } from '@blocksuite/icons'; import { PlusIcon, HelpIcon } from '@blocksuite/icons';
import { useAppState } from '@/providers/app-state-provider';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useTranslation } from '@affine/i18n'; import { useTranslation } from '@affine/i18n';
import { LanguageMenu } from './SelectLanguageMenu'; import { LanguageMenu } from './SelectLanguageMenu';
@@ -28,6 +27,7 @@ import {
} from './styles'; } from './styles';
import { WorkspaceCard } from './WorkspaceCard'; import { WorkspaceCard } from './WorkspaceCard';
import { Footer } from './Footer'; import { Footer } from './Footer';
import { useGlobalState } from '@/store/app';
interface WorkspaceModalProps { interface WorkspaceModalProps {
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
@@ -35,7 +35,8 @@ 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, dataCenter } = useAppState(); const logout = useGlobalState(store => store.logout);
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);

View File

@@ -2,9 +2,9 @@ import { StyledWorkspaceName } from './style';
import { WorkspaceUnit } from '@affine/datacenter'; import { WorkspaceUnit } from '@affine/datacenter';
import { useTranslation, Trans } from '@affine/i18n'; import { useTranslation, Trans } from '@affine/i18n';
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar'; import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
import { useAppState } from '@/providers/app-state-provider';
import { FlexWrapper, Content, Wrapper, Button } from '@affine/component'; import { FlexWrapper, Content, Wrapper, Button } from '@affine/component';
import { useModal } from '@/store/globalModal'; import { useModal } from '@/store/globalModal';
import { useGlobalState } from '@/store/app';
// // FIXME: Temporary solution, since the @blocksuite/icons is broken // // FIXME: Temporary solution, since the @blocksuite/icons is broken
// const ActiveIcon = () => { // const ActiveIcon = () => {
@@ -34,7 +34,7 @@ import { useModal } from '@/store/globalModal';
export const SyncPage = ({ workspace }: { workspace: WorkspaceUnit }) => { export const SyncPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { user } = useAppState(); const user = useGlobalState(store => store.user);
const { triggerEnableWorkspaceModal } = useModal(); const { triggerEnableWorkspaceModal } = useModal();
if (workspace.provider === 'local') { if (workspace.provider === 'local') {

View File

@@ -24,12 +24,14 @@ import { useTranslation } from '@affine/i18n';
import { CameraIcon } from './icons'; import { CameraIcon } from './icons';
import { Upload } from '@/components/file-upload'; import { Upload } from '@/components/file-upload';
import { MuiFade } from '@affine/component'; import { MuiFade } from '@affine/component';
import { useGlobalState } from '@/store/app';
export const GeneralPage = ({ workspace }: { workspace: WorkspaceUnit }) => { export const GeneralPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
const [showDelete, setShowDelete] = useState<boolean>(false); const [showDelete, setShowDelete] = useState<boolean>(false);
const [showLeave, setShowLeave] = useState<boolean>(false); const [showLeave, setShowLeave] = useState<boolean>(false);
const [workspaceName, setWorkspaceName] = useState<string>(workspace?.name); const [workspaceName, setWorkspaceName] = useState<string>(workspace?.name);
const [showEditInput, setShowEditInput] = useState(false); const [showEditInput, setShowEditInput] = useState(false);
const { currentWorkspace, isOwner } = useAppState(); const isOwner = useGlobalState(store => store.isOwner);
const { currentWorkspace } = useAppState();
const { updateWorkspace } = useWorkspaceHelper(); const { updateWorkspace } = useWorkspaceHelper();
const { t } = useTranslation(); const { t } = useTranslation();

View File

@@ -1,10 +1,10 @@
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { PageMeta } from '@/providers/app-state-provider'; import { PageMeta } from '@/providers/app-state-provider';
import { useBlockSuite } from '@/store/workspace'; import { useGlobalState } from '@/store/app';
export const useCurrentPageMeta = (): PageMeta | null => { export const useCurrentPageMeta = (): PageMeta | null => {
const currentPage = useBlockSuite(store => store.currentPage); const currentPage = useGlobalState(store => store.currentPage);
const currentBlockSuiteWorkspace = useBlockSuite( const currentBlockSuiteWorkspace = useGlobalState(
store => store.currentWorkspace store => store.currentWorkspace
); );

View File

@@ -1,11 +1,13 @@
import { useAppState } from '@/providers/app-state-provider'; import { useAppState } from '@/providers/app-state-provider';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useGlobalState } from '@/store/app';
// It is a fully effective hook // It is a fully effective hook
// Cause it not just ensure workspace loaded, but also have router change. // Cause it not just ensure workspace loaded, but also have router change.
export const useEnsureWorkspace = () => { export const useEnsureWorkspace = () => {
const [workspaceLoaded, setWorkspaceLoaded] = useState(false); const [workspaceLoaded, setWorkspaceLoaded] = useState(false);
const { dataCenter, loadWorkspace } = useAppState(); const dataCenter = useGlobalState(store => store.dataCenter);
const { loadWorkspace } = useAppState();
const router = useRouter(); const router = useRouter();
const [activeWorkspaceId, setActiveWorkspaceId] = useState( const [activeWorkspaceId, setActiveWorkspaceId] = useState(
router.query.workspaceId as string router.query.workspaceId as string

View File

@@ -1,12 +1,12 @@
import { Page } from '@blocksuite/store'; import { Page } from '@blocksuite/store';
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import { useBlockSuite } from '@/store/workspace'; import { useGlobalState } from '@/store/app';
export type EventCallBack<T> = (callback: (props: T) => void) => void; export type EventCallBack<T> = (callback: (props: T) => void) => void;
export type UseHistoryUpdated = (page?: Page) => EventCallBack<Page>; export type UseHistoryUpdated = (page?: Page) => EventCallBack<Page>;
export const useHistoryUpdate: UseHistoryUpdated = () => { export const useHistoryUpdate: UseHistoryUpdated = () => {
const currentPage = useBlockSuite(store => store.currentPage); const currentPage = useGlobalState(store => store.currentPage);
const callbackQueue = useRef<((page: Page) => void)[]>([]); const callbackQueue = useRef<((page: Page) => void)[]>([]);
useEffect(() => { useEffect(() => {

View File

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

View File

@@ -5,7 +5,7 @@ import { EditorContainer } from '@blocksuite/editor';
import { useChangePageMeta } from '@/hooks/use-change-page-meta'; import { useChangePageMeta } from '@/hooks/use-change-page-meta';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { WorkspaceUnit } from '@affine/datacenter'; import { WorkspaceUnit } from '@affine/datacenter';
import { useBlockSuite } from '@/store/workspace'; import { useGlobalState } from '@/store/app';
export type EditorHandlers = { export type EditorHandlers = {
createPage: (params?: { createPage: (params?: {
@@ -40,7 +40,7 @@ const getPageMeta = (workspace: WorkspaceUnit | null, pageId: string) => {
export const usePageHelper = (): EditorHandlers => { export const usePageHelper = (): EditorHandlers => {
const router = useRouter(); const router = useRouter();
const changePageMeta = useChangePageMeta(); const changePageMeta = useChangePageMeta();
const editor = useBlockSuite(store => store.editor); const editor = useGlobalState(store => store.editor);
const { currentWorkspace } = useAppState(); const { currentWorkspace } = useAppState();
return { return {

View File

@@ -1,6 +1,6 @@
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import { EditorContainer } from '@blocksuite/editor'; import { EditorContainer } from '@blocksuite/editor';
import { useBlockSuite } from '@/store/workspace'; import { useGlobalState } from '@/store/app';
export type EventCallBack<T> = (callback: (props: T) => void) => void; export type EventCallBack<T> = (callback: (props: T) => void) => void;
export type UsePropsUpdated = ( export type UsePropsUpdated = (
@@ -8,7 +8,7 @@ export type UsePropsUpdated = (
) => EventCallBack<EditorContainer>; ) => EventCallBack<EditorContainer>;
export const usePropsUpdated: UsePropsUpdated = () => { export const usePropsUpdated: UsePropsUpdated = () => {
const editor = useBlockSuite(store => store.editor); const editor = useGlobalState(store => store.editor);
const callbackQueue = useRef<((editor: EditorContainer) => void)[]>([]); const callbackQueue = useRef<((editor: EditorContainer) => void)[]>([]);

View File

@@ -1,8 +1,10 @@
import { useAppState } from '@/providers/app-state-provider'; import { useAppState } from '@/providers/app-state-provider';
import { WorkspaceUnit } from '@affine/datacenter'; import { WorkspaceUnit } from '@affine/datacenter';
import { useGlobalState } from '@/store/app';
export const useWorkspaceHelper = () => { export const useWorkspaceHelper = () => {
const { dataCenter, currentWorkspace } = useAppState(); const dataCenter = useGlobalState(store => store.dataCenter);
const { currentWorkspace } = useAppState();
const createWorkspace = async (name: string) => { const createWorkspace = async (name: string) => {
const workspaceInfo = await dataCenter.createWorkspace({ const workspaceInfo = await dataCenter.createWorkspace({
name: name, name: name,

View File

@@ -23,7 +23,7 @@ import Head from 'next/head';
import '@affine/i18n'; import '@affine/i18n';
import { useTranslation } from '@affine/i18n'; import { useTranslation } from '@affine/i18n';
import React from 'react'; import React from 'react';
import { BlockSuiteProvider } from '@/store/workspace'; import { GlobalAppProvider } from '@/store/app';
const ThemeProvider = dynamic(() => import('@/providers/ThemeProvider'), { const ThemeProvider = dynamic(() => import('@/providers/ThemeProvider'), {
ssr: false, ssr: false,
@@ -68,7 +68,7 @@ const App = ({ Component, pageProps }: AppPropsWithLayout) => {
<title>AFFiNE</title> <title>AFFiNE</title>
</Head> </Head>
<Logger /> <Logger />
<BlockSuiteProvider key="BlockSuiteProvider"> <GlobalAppProvider key="BlockSuiteProvider">
<ProviderComposer <ProviderComposer
contexts={[ contexts={[
<ThemeProvider key="ThemeProvider" />, <ThemeProvider key="ThemeProvider" />,
@@ -83,7 +83,7 @@ const App = ({ Component, pageProps }: AppPropsWithLayout) => {
<AppDefender>{getLayout(<Component {...pageProps} />)}</AppDefender> <AppDefender>{getLayout(<Component {...pageProps} />)}</AppDefender>
)} )}
</ProviderComposer> </ProviderComposer>
</BlockSuiteProvider> </GlobalAppProvider>
</> </>
); );
}; };

View File

@@ -9,14 +9,14 @@ import { usePageHelper } from '@/hooks/use-page-helper';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import Head from 'next/head'; import Head from 'next/head';
import { useTranslation } from '@affine/i18n'; import { useTranslation } from '@affine/i18n';
import { useBlockSuite } from '@/store/workspace'; import { useGlobalState } from '@/store/app';
const DynamicBlocksuite = dynamic(() => import('@/components/editor'), { const DynamicBlocksuite = dynamic(() => import('@/components/editor'), {
ssr: false, ssr: false,
}); });
const BlockHubAppender = () => { const BlockHubAppender = () => {
const setBlockHub = useBlockSuite(store => store.setBlockHub); const setBlockHub = useGlobalState(store => store.setBlockHub);
const editor = useBlockSuite(store => store.editor); const editor = useGlobalState(store => store.editor);
useEffect(() => { useEffect(() => {
let blockHubElement: HTMLElement | null = null; let blockHubElement: HTMLElement | null = null;
@@ -38,8 +38,8 @@ const BlockHubAppender = () => {
}; };
const Page: NextPageWithLayout = () => { const Page: NextPageWithLayout = () => {
const currentPage = useBlockSuite(store => store.currentPage); const currentPage = useGlobalState(store => store.currentPage);
const setEditor = useBlockSuite(store => store.setEditor); const setEditor = useGlobalState(store => store.setEditor);
const { currentWorkspace } = useAppState(); const { currentWorkspace } = useAppState();
const { t } = useTranslation(); const { t } = useTranslation();
@@ -69,7 +69,7 @@ const Page: NextPageWithLayout = () => {
const PageDefender = ({ children }: PropsWithChildren) => { const PageDefender = ({ children }: PropsWithChildren) => {
const router = useRouter(); const router = useRouter();
const [pageLoaded, setPageLoaded] = useState(false); const [pageLoaded, setPageLoaded] = useState(false);
const loadPage = useBlockSuite(store => store.loadPage); const loadPage = useGlobalState(store => store.loadPage);
const { currentWorkspace } = useAppState(); const { currentWorkspace } = useAppState();
const { createPage } = usePageHelper(); const { createPage } = usePageHelper();

View File

@@ -25,10 +25,11 @@ import { useTranslation } from '@affine/i18n';
import { PageListHeader } from '@/components/header'; import { PageListHeader } from '@/components/header';
import Head from 'next/head'; import Head from 'next/head';
import { styled } from '@affine/component'; import { styled } from '@affine/component';
import { useGlobalState } from '@/store/app';
const useTabMap = () => { const useTabMap = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { isOwner } = useAppState(); const isOwner = useGlobalState(store => store.isOwner);
const tabMap: { const tabMap: {
name: string; name: string;
panelRender: (workspace: WorkspaceUnit) => ReactNode; panelRender: (workspace: WorkspaceUnit) => ReactNode;

View File

@@ -1,15 +1,12 @@
import { createContext, useContext, useEffect, useState, useRef } from 'react'; import { createContext, useContext, useEffect, useState, useRef } from 'react';
import type { PropsWithChildren } from 'react'; import type { PropsWithChildren } from 'react';
import { getDataCenter } from '@affine/datacenter';
import { import {
AppStateContext, AppStateContext,
AppStateFunction, AppStateFunction,
AppStateValue, AppStateValue,
PageMeta, PageMeta,
} from './interface'; } from './interface';
import { createDefaultWorkspace } from './utils'; import { useGlobalState, useGlobalStateApi } from '@/store/app';
import { User } from '@affine/datacenter';
import { useBlockSuiteApi } from '@/store/workspace';
export interface Disposable { export interface Disposable {
dispose(): void; dispose(): void;
@@ -23,35 +20,9 @@ export const useAppState = () => useContext(AppState);
export const AppStateProvider = ({ export const AppStateProvider = ({
children, children,
}: PropsWithChildren<AppStateContextProps>) => { }: PropsWithChildren<AppStateContextProps>) => {
const blocksuiteApi = useBlockSuiteApi(); const globalStateApi = useGlobalStateApi();
const [appState, setAppState] = useState<AppStateValue>({} as AppStateValue); const [appState, setAppState] = useState<AppStateValue>({} as AppStateValue);
const { dataCenter } = appState;
const [blobState, setBlobState] = useState(false); const [blobState, setBlobState] = useState(false);
const [userInfo, setUser] = useState<User | null>({} as User);
useEffect(() => {
const initState = async () => {
const dataCenter = await getDataCenter();
// Ensure datacenter has at least one workspace
if (dataCenter.workspaces.length === 0) {
await createDefaultWorkspace(dataCenter);
}
setUser(
(await dataCenter.getUserInfo(
dataCenter.providers.filter(p => p.id !== 'local')[0]?.id
)) || null
);
setAppState({
dataCenter,
workspaceList: dataCenter.workspaces,
currentWorkspace: null,
pageList: [],
synced: true,
isOwner: false,
});
};
initState();
}, []);
useEffect(() => { useEffect(() => {
if (!appState?.currentWorkspace?.blocksuiteWorkspace) { if (!appState?.currentWorkspace?.blocksuiteWorkspace) {
@@ -72,6 +43,24 @@ export const AppStateProvider = ({
}; };
}, [appState]); }, [appState]);
const onceRef = useRef(true);
const dataCenter = useGlobalState(store => store.dataCenter);
useEffect(() => {
if (dataCenter !== null) {
if (onceRef.current) {
setAppState({
workspaceList: dataCenter.workspaces,
currentWorkspace: null,
pageList: [],
synced: true,
});
onceRef.current = false;
} else {
console.warn('dataCenter Effect called twice. Please fix this ASAP');
}
}
}, [dataCenter]);
useEffect(() => { useEffect(() => {
// FIXME: onWorkspacesChange should have dispose function // FIXME: onWorkspacesChange should have dispose function
return dataCenter?.onWorkspacesChange( return dataCenter?.onWorkspacesChange(
@@ -88,7 +77,8 @@ export const AppStateProvider = ({
const loadWorkspace: AppStateFunction['loadWorkspace'] = const loadWorkspace: AppStateFunction['loadWorkspace'] =
useRef() as AppStateFunction['loadWorkspace']; useRef() as AppStateFunction['loadWorkspace'];
loadWorkspace.current = async (workspaceId, abort) => { loadWorkspace.current = async (workspaceId, abort) => {
const { dataCenter, workspaceList, currentWorkspace } = appState; const { dataCenter } = globalStateApi.getState();
const { workspaceList, currentWorkspace } = appState;
if (!workspaceList.find(v => v.id.toString() === workspaceId)) { if (!workspaceList.find(v => v.id.toString() === workspaceId)) {
return null; return null;
} }
@@ -116,6 +106,7 @@ export const AppStateProvider = ({
// isOwner is useful only in the cloud // isOwner is useful only in the cloud
isOwner = true; isOwner = true;
} else { } else {
const userInfo = globalStateApi.getState().user;
// We must ensure workspace.owner exists, then ensure id same. // We must ensure workspace.owner exists, then ensure id same.
isOwner = workspace?.owner && userInfo?.id === workspace.owner.id; isOwner = workspace?.owner && userInfo?.id === workspace.owner.id;
} }
@@ -123,13 +114,16 @@ export const AppStateProvider = ({
const pageList = const pageList =
(workspace?.blocksuiteWorkspace?.meta.pageMetas as PageMeta[]) ?? []; (workspace?.blocksuiteWorkspace?.meta.pageMetas as PageMeta[]) ?? [];
if (workspace?.blocksuiteWorkspace) { if (workspace?.blocksuiteWorkspace) {
blocksuiteApi.getState().setWorkspace(workspace.blocksuiteWorkspace); globalStateApi.getState().setWorkspace(workspace.blocksuiteWorkspace);
} }
globalStateApi.setState({
isOwner,
});
setAppState({ setAppState({
...appState, ...appState,
currentWorkspace: workspace, currentWorkspace: workspace,
pageList: pageList, pageList: pageList,
isOwner,
}); });
abort?.removeEventListener('abort', onAbort); abort?.removeEventListener('abort', onAbort);
@@ -157,36 +151,12 @@ export const AppStateProvider = ({
}; };
}, [appState.currentWorkspace]); }, [appState.currentWorkspace]);
const login = async () => {
const { dataCenter } = appState;
try {
await dataCenter.login();
const user = (await dataCenter.getUserInfo()) as User;
if (!user) {
throw new Error('User info not found');
}
setUser(user);
return user;
} catch (error) {
return null; // login failed
}
};
const logout = async () => {
const { dataCenter } = appState;
await dataCenter.logout();
setUser(null);
};
return ( return (
<AppState.Provider <AppState.Provider
value={{ value={{
...appState, ...appState,
loadWorkspace: loadWorkspace, loadWorkspace: loadWorkspace,
login,
logout,
blobDataSynced: blobState, blobDataSynced: blobState,
user: userInfo,
}} }}
> >
{children} {children}

View File

@@ -1,4 +1,4 @@
import { DataCenter, User, WorkspaceUnit } from '@affine/datacenter'; import { WorkspaceUnit } from '@affine/datacenter';
import type { EditorContainer } from '@blocksuite/editor'; import type { EditorContainer } from '@blocksuite/editor';
import type { import type {
@@ -15,13 +15,10 @@ export interface PageMeta extends StorePageMeta {
} }
export type AppStateValue = { export type AppStateValue = {
dataCenter: DataCenter;
user?: User | null;
workspaceList: WorkspaceUnit[]; workspaceList: WorkspaceUnit[];
currentWorkspace: WorkspaceUnit | null; currentWorkspace: WorkspaceUnit | null;
pageList: PageMeta[]; pageList: PageMeta[];
synced: boolean; synced: boolean;
isOwner?: boolean;
blobDataSynced?: boolean; blobDataSynced?: boolean;
}; };
@@ -29,9 +26,6 @@ export type AppStateFunction = {
loadWorkspace: MutableRefObject< loadWorkspace: MutableRefObject<
(workspaceId: string, abort?: AbortSignal) => Promise<WorkspaceUnit | null> (workspaceId: string, abort?: AbortSignal) => Promise<WorkspaceUnit | null>
>; >;
login: () => Promise<User | null>;
logout: () => Promise<void>;
}; };
export type AppStateContext = AppStateValue & AppStateFunction; export type AppStateContext = AppStateValue & AppStateFunction;

View File

@@ -0,0 +1,60 @@
import { Page, Workspace } from '@blocksuite/store';
import { EditorContainer } from '@blocksuite/editor';
import { BlockHub } from '@blocksuite/blocks';
import { GlobalActionsCreator } from '@/store/app';
export interface BlockSuiteState {
currentWorkspace: Workspace | null;
editor: EditorContainer | null;
currentPage: Page | null;
blockHub: BlockHub | null;
}
export const createBlockSuiteState = (): BlockSuiteState => ({
currentWorkspace: null,
currentPage: null,
blockHub: null,
editor: null,
});
export interface BlockSuiteActions {
loadPage: (pageId: string) => void;
setEditor: (editor: EditorContainer) => void;
setWorkspace: (workspace: Workspace) => void;
setBlockHub: (blockHub: BlockHub) => void;
}
export const createBlockSuiteActions: GlobalActionsCreator<
BlockSuiteActions
> = (set, get) => ({
setWorkspace: workspace => {
set({
currentWorkspace: workspace,
});
},
setEditor: editor => {
set({
editor,
});
},
loadPage: pageId => {
const { currentWorkspace } = get();
if (currentWorkspace === null) {
console.warn('currentWorkspace is null');
return;
}
const page = currentWorkspace.getPage(pageId);
if (page === null) {
console.warn('cannot find page ', pageId);
return;
}
set({
currentPage: page,
});
},
setBlockHub: blockHub => {
set({
blockHub,
});
},
});

View File

@@ -0,0 +1,106 @@
import type React from 'react';
import { createContext, useContext, useEffect, useMemo, useRef } 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 {
createUserActions,
createUserState,
UserActions,
UserState,
} from '@/store/app/user';
import { DataCenter, getDataCenter } from '@affine/datacenter';
import { createDefaultWorkspace } from '@/providers/app-state-provider/utils';
export type GlobalActionsCreator<Actions, Store = GlobalState> = StateCreator<
Store,
[['zustand/subscribeWithSelector', unknown]],
[],
Actions
>;
export interface GlobalState extends BlockSuiteState, UserState {
readonly dataCenter: DataCenter;
}
export interface GlobalActions extends BlockSuiteActions, UserActions {}
const create = () =>
createStore(
subscribeWithSelector(
combine<GlobalState, GlobalActions>(
{
...createBlockSuiteState(),
...createUserState(),
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
dataCenter: null!,
},
/* deepscan-disable TOO_MANY_ARGS */
(set, get, api) => ({
...createBlockSuiteActions(set, get, api),
...createUserActions(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;
function DataCenterSideEffect() {
const onceRef = useRef(true);
const api = useGlobalStateApi();
useEffect(() => {
async function init() {
const dataCenterPromise = getDataCenter();
dataCenterPromise.then(async dataCenter => {
// Ensure datacenter has at least one workspace
if (dataCenter.workspaces.length === 0) {
await createDefaultWorkspace(dataCenter);
}
api.setState({ dataCenter });
});
}
if (onceRef.current) {
onceRef.current = false;
init().then(() => {
console.log('datacenter init success');
});
}
}, [api]);
return null;
}
export const GlobalAppProvider: React.FC<React.PropsWithChildren> =
function ModelProvider({ children }) {
return (
<GlobalStateContext.Provider value={useMemo(() => create(), [])}>
<DataCenterSideEffect />
{children}
</GlobalStateContext.Provider>
);
};

View File

@@ -0,0 +1,45 @@
import { GlobalActionsCreator } from '@/store/app';
import { User } from '@affine/datacenter';
export interface UserState {
user: User | null;
isOwner: boolean;
}
export interface UserActions {
login: () => Promise<User | null>;
logout: () => Promise<void>;
}
export const createUserState = (): UserState => ({
user: null,
isOwner: false,
});
export const createUserActions: GlobalActionsCreator<UserActions> = (
set,
get
) => {
return {
login: async () => {
const { dataCenter } = get();
try {
await dataCenter.login();
const user = (await dataCenter.getUserInfo()) as User;
if (!user) {
// Add ErrorBoundary
throw new Error('User info not found');
}
set({ user });
return user;
} catch (error) {
return null; // login failed
}
},
logout: async () => {
const { dataCenter } = get();
await dataCenter.logout();
set({ user: null });
},
};
};

View File

@@ -1,98 +0,0 @@
import type React from 'react';
import { createContext, useContext, useMemo } from 'react';
import { createStore, useStore } from 'zustand';
import { combine, subscribeWithSelector } from 'zustand/middleware';
import type { UseBoundStore } from 'zustand/react';
import type { Page } from '@blocksuite/store';
import type { BlockHub } from '@blocksuite/blocks';
import type { Workspace } from '@blocksuite/store';
import type { EditorContainer } from '@blocksuite/editor';
export type BlockSuiteState = {
currentWorkspace: Workspace | null;
editor: EditorContainer | null;
currentPage: Page | null;
blockHub: BlockHub | null;
};
export type BlockSuiteActions = {
loadPage: (pageId: string) => void;
setEditor: (editor: EditorContainer) => void;
setWorkspace: (workspace: Workspace) => void;
setBlockHub: (blockHub: BlockHub) => void;
};
const create = () =>
createStore(
subscribeWithSelector(
combine<BlockSuiteState, BlockSuiteActions>(
{
currentWorkspace: null,
currentPage: null,
blockHub: null,
editor: null,
},
(set, get) => ({
setWorkspace: workspace => {
set({
currentWorkspace: workspace,
});
},
setEditor: editor => {
set({
editor,
});
},
loadPage: pageId => {
const { currentWorkspace } = get();
if (currentWorkspace === null) {
console.warn('currentWorkspace is null');
return;
}
const page = currentWorkspace.getPage(pageId);
if (page === null) {
console.warn('cannot find page ', pageId);
return;
}
set({
currentPage: page,
});
},
setBlockHub: blockHub => {
set({
blockHub,
});
},
})
)
)
);
type Store = ReturnType<typeof create>;
const BlockSuiteContext = createContext<Store | null>(null);
export const useBlockSuiteApi = () => {
const api = useContext(BlockSuiteContext);
if (!api) {
throw new Error('cannot find modal context');
}
return api;
};
export const useBlockSuite: UseBoundStore<Store> = ((
selector: Parameters<UseBoundStore<Store>>[0],
equals: Parameters<UseBoundStore<Store>>[1]
) => {
const api = useBlockSuiteApi();
return useStore(api, selector, equals);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}) as any;
export const BlockSuiteProvider: React.FC<React.PropsWithChildren> =
function ModelProvider({ children }) {
return (
<BlockSuiteContext.Provider value={useMemo(() => create(), [])}>
{children}
</BlockSuiteContext.Provider>
);
};