From 6a8aff9e5693336c3666ed81704cedf616ae0186 Mon Sep 17 00:00:00 2001 From: Himself65 Date: Tue, 14 Feb 2023 23:38:21 -0600 Subject: [PATCH] refactor(store): port to `useGlobalState` with zustand (#1012) --- .../enable-workspace-modal/index.tsx | 6 +- .../src/components/edgeless-toolbar/index.tsx | 4 +- .../enable-workspace-modal/index.tsx | 6 +- .../src/components/header/EditorHeader.tsx | 4 +- .../header-right-items/EditorOptionMenu.tsx | 4 +- apps/web/src/components/help-island/index.tsx | 4 +- apps/web/src/components/login-modal/index.tsx | 4 +- .../quick-search/PublishedResults.tsx | 5 +- .../src/components/workspace-modal/Footer.tsx | 4 +- .../workspace-modal/WorkspaceCard.tsx | 3 +- .../src/components/workspace-modal/index.tsx | 5 +- .../components/workspace-setting/SyncPage.tsx | 4 +- .../workspace-setting/general/General.tsx | 4 +- apps/web/src/hooks/use-current-page-meta.ts | 6 +- apps/web/src/hooks/use-ensure-workspace.ts | 4 +- apps/web/src/hooks/use-history-update.ts | 4 +- apps/web/src/hooks/use-members.ts | 4 +- apps/web/src/hooks/use-page-helper.ts | 4 +- apps/web/src/hooks/use-props-updated.ts | 4 +- apps/web/src/hooks/use-workspace-helper.ts | 4 +- apps/web/src/pages/_app.tsx | 6 +- .../workspace/[workspaceId]/[pageId].tsx | 12 +- .../pages/workspace/[workspaceId]/setting.tsx | 3 +- .../providers/app-state-provider/Provider.tsx | 86 +++++--------- .../providers/app-state-provider/interface.ts | 8 +- apps/web/src/store/app/blocksuite/index.ts | 60 ++++++++++ apps/web/src/store/app/index.tsx | 106 ++++++++++++++++++ apps/web/src/store/app/user/index.ts | 45 ++++++++ apps/web/src/store/workspace/index.tsx | 98 ---------------- 29 files changed, 304 insertions(+), 207 deletions(-) create mode 100644 apps/web/src/store/app/blocksuite/index.ts create mode 100644 apps/web/src/store/app/index.tsx create mode 100644 apps/web/src/store/app/user/index.ts delete mode 100644 apps/web/src/store/workspace/index.tsx diff --git a/apps/desktop/src/preload/components/enable-workspace-modal/index.tsx b/apps/desktop/src/preload/components/enable-workspace-modal/index.tsx index 316c6fc38a..f4d7fda9f3 100644 --- a/apps/desktop/src/preload/components/enable-workspace-modal/index.tsx +++ b/apps/desktop/src/preload/components/enable-workspace-modal/index.tsx @@ -6,6 +6,7 @@ import { useState } from 'react'; import router from 'next/router'; import { toast } from '@affine/component'; import { CloseIcon } from '@blocksuite/icons'; +import { useGlobalState } from '@/store/app'; import { Header, Content, ContentTitle, StyleTips, StyleButton } from './style'; interface EnableWorkspaceModalProps { @@ -18,7 +19,10 @@ export const EnableWorkspaceModal = ({ onClose, }: EnableWorkspaceModalProps) => { 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); return ( diff --git a/apps/web/src/components/edgeless-toolbar/index.tsx b/apps/web/src/components/edgeless-toolbar/index.tsx index d8e697ea0d..07db5646a5 100644 --- a/apps/web/src/components/edgeless-toolbar/index.tsx +++ b/apps/web/src/components/edgeless-toolbar/index.tsx @@ -19,7 +19,7 @@ import { Tooltip } from '@affine/component'; import useCurrentPageMeta from '@/hooks/use-current-page-meta'; import useHistoryUpdated from '@/hooks/use-history-update'; import { useTranslation } from '@affine/i18n'; -import { useBlockSuite } from '@/store/workspace'; +import { useGlobalState } from '@/store/app'; const useToolbarList1 = () => { const { t } = useTranslation(); @@ -87,7 +87,7 @@ const useToolbarList1 = () => { const UndoRedo = () => { const [canUndo, setCanUndo] = useState(false); const [canRedo, setCanRedo] = useState(false); - const currentPage = useBlockSuite(store => store.currentPage); + const currentPage = useGlobalState(store => store.currentPage); const onHistoryUpdated = useHistoryUpdated(); const { t } = useTranslation(); useEffect(() => { diff --git a/apps/web/src/components/enable-workspace-modal/index.tsx b/apps/web/src/components/enable-workspace-modal/index.tsx index 5a0955784c..e65e5055c1 100644 --- a/apps/web/src/components/enable-workspace-modal/index.tsx +++ b/apps/web/src/components/enable-workspace-modal/index.tsx @@ -5,6 +5,7 @@ import { CloseIcon } from '@blocksuite/icons'; import { useRouter } from 'next/router'; import { useState } from 'react'; import { Content, ContentTitle, Header, StyleButton, StyleTips } from './style'; +import { useGlobalState } from '@/store/app'; interface EnableWorkspaceModalProps { open: boolean; @@ -16,7 +17,10 @@ export const EnableWorkspaceModal = ({ onClose, }: EnableWorkspaceModalProps) => { 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 router = useRouter(); diff --git a/apps/web/src/components/header/EditorHeader.tsx b/apps/web/src/components/header/EditorHeader.tsx index 66509e4ed1..902f08ba3f 100644 --- a/apps/web/src/components/header/EditorHeader.tsx +++ b/apps/web/src/components/header/EditorHeader.tsx @@ -11,12 +11,12 @@ import QuickSearchButton from './QuickSearchButton'; import Header from './Header'; import usePropsUpdated from '@/hooks/use-props-updated'; import useCurrentPageMeta from '@/hooks/use-current-page-meta'; -import { useBlockSuite } from '@/store/workspace'; +import { useGlobalState } from '@/store/app'; export const EditorHeader = () => { const [title, setTitle] = useState(''); const [isHover, setIsHover] = useState(false); - const editor = useBlockSuite(store => store.editor); + const editor = useGlobalState(store => store.editor); const { trash: isTrash = false } = useCurrentPageMeta() || {}; const onPropsUpdated = usePropsUpdated(); diff --git a/apps/web/src/components/header/header-right-items/EditorOptionMenu.tsx b/apps/web/src/components/header/header-right-items/EditorOptionMenu.tsx index 210bb3df86..4c31252373 100644 --- a/apps/web/src/components/header/header-right-items/EditorOptionMenu.tsx +++ b/apps/web/src/components/header/header-right-items/EditorOptionMenu.tsx @@ -16,9 +16,9 @@ import { useConfirm } from '@/providers/ConfirmProvider'; import useCurrentPageMeta from '@/hooks/use-current-page-meta'; import { toast } from '@affine/component'; import { useTranslation } from '@affine/i18n'; -import { useBlockSuite } from '@/store/workspace'; +import { useGlobalState } from '@/store/app'; const PopoverContent = () => { - const editor = useBlockSuite(store => store.editor); + const editor = useGlobalState(store => store.editor); const { toggleFavoritePage, toggleDeletePage } = usePageHelper(); const { changePageMode } = usePageHelper(); const confirm = useConfirm(store => store.confirm); diff --git a/apps/web/src/components/help-island/index.tsx b/apps/web/src/components/help-island/index.tsx index b0f33bade1..ff27b390bb 100644 --- a/apps/web/src/components/help-island/index.tsx +++ b/apps/web/src/components/help-island/index.tsx @@ -11,7 +11,7 @@ import { Tooltip } from '@affine/component'; import { useTranslation } from '@affine/i18n'; import { useModal } from '@/store/globalModal'; import { MuiFade } from '@affine/component'; -import { useBlockSuite } from '@/store/workspace'; +import { useGlobalState } from '@/store/app'; export type IslandItemNames = 'contact' | 'shortcuts'; export const HelpIsland = ({ showList = ['contact', 'shortcuts'], @@ -20,7 +20,7 @@ export const HelpIsland = ({ }) => { const [spread, setShowSpread] = useState(false); const { triggerShortcutsModal, triggerContactModal } = useModal(); - const blockHub = useBlockSuite(store => store.blockHub); + const blockHub = useGlobalState(store => store.blockHub); const { t } = useTranslation(); useEffect(() => { diff --git a/apps/web/src/components/login-modal/index.tsx b/apps/web/src/components/login-modal/index.tsx index 105334a77a..3e4d081363 100644 --- a/apps/web/src/components/login-modal/index.tsx +++ b/apps/web/src/components/login-modal/index.tsx @@ -1,16 +1,16 @@ import { positionAbsolute, styled } from '@affine/component'; import { Modal, ModalWrapper, ModalCloseButton } from '@affine/component'; import { Button } from '@affine/component'; -import { useAppState } from '@/providers/app-state-provider'; import { useTranslation } from '@affine/i18n'; import { GoogleIcon } from './GoogleIcon'; +import { useGlobalState } from '@/store/app'; interface LoginModalProps { open: boolean; onClose: () => void; } export const LoginModal = ({ open, onClose }: LoginModalProps) => { - const { login } = useAppState(); + const login = useGlobalState(store => store.login); const { t } = useTranslation(); return ( diff --git a/apps/web/src/components/quick-search/PublishedResults.tsx b/apps/web/src/components/quick-search/PublishedResults.tsx index d0564b4cd7..89193d09b0 100644 --- a/apps/web/src/components/quick-search/PublishedResults.tsx +++ b/apps/web/src/components/quick-search/PublishedResults.tsx @@ -2,12 +2,13 @@ import { Command } from 'cmdk'; import { StyledListItem, StyledNotFound } from './style'; import { PaperIcon, EdgelessIcon } from '@blocksuite/icons'; 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 { NoResultSVG } from './NoResultSVG'; import { useTranslation } from '@affine/i18n'; import usePageHelper from '@/hooks/use-page-helper'; import { Workspace } from '@blocksuite/store'; +import { useGlobalState } from '@/store/app'; export const PublishedResults = (props: { query: string; @@ -21,7 +22,7 @@ export const PublishedResults = (props: { props; const { search } = usePageHelper(); const [results, setResults] = useState(new Map()); - const { dataCenter } = useAppState(); + const dataCenter = useGlobalState(store => store.dataCenter); const router = useRouter(); const [pageList, setPageList] = useState([]); useEffect(() => { diff --git a/apps/web/src/components/workspace-modal/Footer.tsx b/apps/web/src/components/workspace-modal/Footer.tsx index 917c77da5a..49b88f41ef 100644 --- a/apps/web/src/components/workspace-modal/Footer.tsx +++ b/apps/web/src/components/workspace-modal/Footer.tsx @@ -2,10 +2,10 @@ import { CloudWorkspaceIcon, SignOutIcon } from '@blocksuite/icons'; import { FlexWrapper } from '@affine/component'; import { WorkspaceAvatar } from '@/components/workspace-avatar'; import { IconButton } from '@affine/component'; -import { useAppState } from '@/providers/app-state-provider'; import { StyledFooter, StyleUserInfo, StyledSignInButton } from './styles'; import { useTranslation } from '@affine/i18n'; import { Tooltip } from '@affine/component'; +import { useGlobalState } from '@/store/app'; export const Footer = ({ onLogin, onLogout, @@ -13,7 +13,7 @@ export const Footer = ({ onLogin: () => void; onLogout: () => void; }) => { - const { user } = useAppState(); + const user = useGlobalState(store => store.user); const { t } = useTranslation(); return ( diff --git a/apps/web/src/components/workspace-modal/WorkspaceCard.tsx b/apps/web/src/components/workspace-modal/WorkspaceCard.tsx index 0596cad968..1a0f355f39 100644 --- a/apps/web/src/components/workspace-modal/WorkspaceCard.tsx +++ b/apps/web/src/components/workspace-modal/WorkspaceCard.tsx @@ -10,9 +10,10 @@ 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'; const WorkspaceType = ({ workspaceData }: { workspaceData: WorkspaceUnit }) => { - const { user } = useAppState(); + const user = useGlobalState(store => store.user); const { t } = useTranslation(); const isOwner = user?.id === workspaceData.owner?.id; diff --git a/apps/web/src/components/workspace-modal/index.tsx b/apps/web/src/components/workspace-modal/index.tsx index 8d9d77c100..827a1d4d01 100644 --- a/apps/web/src/components/workspace-modal/index.tsx +++ b/apps/web/src/components/workspace-modal/index.tsx @@ -6,7 +6,6 @@ import { Tooltip } from '@affine/component'; import { PlusIcon, HelpIcon } from '@blocksuite/icons'; -import { useAppState } from '@/providers/app-state-provider'; import { useRouter } from 'next/router'; import { useTranslation } from '@affine/i18n'; import { LanguageMenu } from './SelectLanguageMenu'; @@ -28,6 +27,7 @@ import { } from './styles'; import { WorkspaceCard } from './WorkspaceCard'; import { Footer } from './Footer'; +import { useGlobalState } from '@/store/app'; interface WorkspaceModalProps { open: boolean; onClose: () => void; @@ -35,7 +35,8 @@ interface WorkspaceModalProps { export const WorkspaceModal = ({ open, onClose }: WorkspaceModalProps) => { 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 { t } = useTranslation(); const [loginOpen, setLoginOpen] = useState(false); diff --git a/apps/web/src/components/workspace-setting/SyncPage.tsx b/apps/web/src/components/workspace-setting/SyncPage.tsx index 520b52c379..2f586d04ed 100644 --- a/apps/web/src/components/workspace-setting/SyncPage.tsx +++ b/apps/web/src/components/workspace-setting/SyncPage.tsx @@ -2,9 +2,9 @@ import { StyledWorkspaceName } from './style'; import { WorkspaceUnit } from '@affine/datacenter'; import { useTranslation, Trans } from '@affine/i18n'; import { WorkspaceUnitAvatar } from '@/components/workspace-avatar'; -import { useAppState } from '@/providers/app-state-provider'; import { FlexWrapper, Content, Wrapper, Button } from '@affine/component'; import { useModal } from '@/store/globalModal'; +import { useGlobalState } from '@/store/app'; // // FIXME: Temporary solution, since the @blocksuite/icons is broken // const ActiveIcon = () => { @@ -34,7 +34,7 @@ import { useModal } from '@/store/globalModal'; export const SyncPage = ({ workspace }: { workspace: WorkspaceUnit }) => { const { t } = useTranslation(); - const { user } = useAppState(); + const user = useGlobalState(store => store.user); const { triggerEnableWorkspaceModal } = useModal(); if (workspace.provider === 'local') { diff --git a/apps/web/src/components/workspace-setting/general/General.tsx b/apps/web/src/components/workspace-setting/general/General.tsx index 9035772a81..b2967883fa 100644 --- a/apps/web/src/components/workspace-setting/general/General.tsx +++ b/apps/web/src/components/workspace-setting/general/General.tsx @@ -24,12 +24,14 @@ import { useTranslation } from '@affine/i18n'; import { CameraIcon } from './icons'; import { Upload } from '@/components/file-upload'; import { MuiFade } from '@affine/component'; +import { useGlobalState } from '@/store/app'; export const GeneralPage = ({ workspace }: { workspace: WorkspaceUnit }) => { const [showDelete, setShowDelete] = useState(false); const [showLeave, setShowLeave] = useState(false); const [workspaceName, setWorkspaceName] = useState(workspace?.name); const [showEditInput, setShowEditInput] = useState(false); - const { currentWorkspace, isOwner } = useAppState(); + const isOwner = useGlobalState(store => store.isOwner); + const { currentWorkspace } = useAppState(); const { updateWorkspace } = useWorkspaceHelper(); const { t } = useTranslation(); diff --git a/apps/web/src/hooks/use-current-page-meta.ts b/apps/web/src/hooks/use-current-page-meta.ts index 580a12e46b..f8eeb71571 100644 --- a/apps/web/src/hooks/use-current-page-meta.ts +++ b/apps/web/src/hooks/use-current-page-meta.ts @@ -1,10 +1,10 @@ import { useCallback, useEffect, useState } from 'react'; import { PageMeta } from '@/providers/app-state-provider'; -import { useBlockSuite } from '@/store/workspace'; +import { useGlobalState } from '@/store/app'; export const useCurrentPageMeta = (): PageMeta | null => { - const currentPage = useBlockSuite(store => store.currentPage); - const currentBlockSuiteWorkspace = useBlockSuite( + const currentPage = useGlobalState(store => store.currentPage); + const currentBlockSuiteWorkspace = useGlobalState( store => store.currentWorkspace ); diff --git a/apps/web/src/hooks/use-ensure-workspace.ts b/apps/web/src/hooks/use-ensure-workspace.ts index ae7de84865..473787d057 100644 --- a/apps/web/src/hooks/use-ensure-workspace.ts +++ b/apps/web/src/hooks/use-ensure-workspace.ts @@ -1,11 +1,13 @@ import { useAppState } from '@/providers/app-state-provider'; import { useRouter } from 'next/router'; import { useEffect, useState } from 'react'; +import { useGlobalState } from '@/store/app'; // 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, loadWorkspace } = useAppState(); + const dataCenter = useGlobalState(store => store.dataCenter); + const { loadWorkspace } = useAppState(); const router = useRouter(); const [activeWorkspaceId, setActiveWorkspaceId] = useState( router.query.workspaceId as string diff --git a/apps/web/src/hooks/use-history-update.ts b/apps/web/src/hooks/use-history-update.ts index d8cf380d5a..d133ab0189 100644 --- a/apps/web/src/hooks/use-history-update.ts +++ b/apps/web/src/hooks/use-history-update.ts @@ -1,12 +1,12 @@ import { Page } from '@blocksuite/store'; import { useEffect, useRef } from 'react'; -import { useBlockSuite } from '@/store/workspace'; +import { useGlobalState } from '@/store/app'; export type EventCallBack = (callback: (props: T) => void) => void; export type UseHistoryUpdated = (page?: Page) => EventCallBack; export const useHistoryUpdate: UseHistoryUpdated = () => { - const currentPage = useBlockSuite(store => store.currentPage); + const currentPage = useGlobalState(store => store.currentPage); const callbackQueue = useRef<((page: Page) => void)[]>([]); useEffect(() => { diff --git a/apps/web/src/hooks/use-members.ts b/apps/web/src/hooks/use-members.ts index a0e7cd8156..5869840cb0 100644 --- a/apps/web/src/hooks/use-members.ts +++ b/apps/web/src/hooks/use-members.ts @@ -1,8 +1,10 @@ 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, currentWorkspace } = useAppState(); + const dataCenter = useGlobalState(store => store.dataCenter); + const { currentWorkspace } = useAppState(); const [members, setMembers] = useState([]); const [loaded, setLoaded] = useState(false); const refreshMembers = useCallback(async () => { diff --git a/apps/web/src/hooks/use-page-helper.ts b/apps/web/src/hooks/use-page-helper.ts index 7814dc2ff6..05085974a9 100644 --- a/apps/web/src/hooks/use-page-helper.ts +++ b/apps/web/src/hooks/use-page-helper.ts @@ -5,7 +5,7 @@ import { EditorContainer } from '@blocksuite/editor'; import { useChangePageMeta } from '@/hooks/use-change-page-meta'; import { useRouter } from 'next/router'; import { WorkspaceUnit } from '@affine/datacenter'; -import { useBlockSuite } from '@/store/workspace'; +import { useGlobalState } from '@/store/app'; export type EditorHandlers = { createPage: (params?: { @@ -40,7 +40,7 @@ const getPageMeta = (workspace: WorkspaceUnit | null, pageId: string) => { export const usePageHelper = (): EditorHandlers => { const router = useRouter(); const changePageMeta = useChangePageMeta(); - const editor = useBlockSuite(store => store.editor); + const editor = useGlobalState(store => store.editor); const { currentWorkspace } = useAppState(); return { diff --git a/apps/web/src/hooks/use-props-updated.ts b/apps/web/src/hooks/use-props-updated.ts index 34af78c836..98178cc090 100644 --- a/apps/web/src/hooks/use-props-updated.ts +++ b/apps/web/src/hooks/use-props-updated.ts @@ -1,6 +1,6 @@ import { useEffect, useRef } from 'react'; import { EditorContainer } from '@blocksuite/editor'; -import { useBlockSuite } from '@/store/workspace'; +import { useGlobalState } from '@/store/app'; export type EventCallBack = (callback: (props: T) => void) => void; export type UsePropsUpdated = ( @@ -8,7 +8,7 @@ export type UsePropsUpdated = ( ) => EventCallBack; export const usePropsUpdated: UsePropsUpdated = () => { - const editor = useBlockSuite(store => store.editor); + const editor = useGlobalState(store => store.editor); const callbackQueue = useRef<((editor: EditorContainer) => void)[]>([]); diff --git a/apps/web/src/hooks/use-workspace-helper.ts b/apps/web/src/hooks/use-workspace-helper.ts index 46ce5cafae..e69e8dc8c4 100644 --- a/apps/web/src/hooks/use-workspace-helper.ts +++ b/apps/web/src/hooks/use-workspace-helper.ts @@ -1,8 +1,10 @@ import { useAppState } from '@/providers/app-state-provider'; import { WorkspaceUnit } from '@affine/datacenter'; +import { useGlobalState } from '@/store/app'; export const useWorkspaceHelper = () => { - const { dataCenter, currentWorkspace } = useAppState(); + const dataCenter = useGlobalState(store => store.dataCenter); + const { currentWorkspace } = useAppState(); const createWorkspace = async (name: string) => { const workspaceInfo = await dataCenter.createWorkspace({ name: name, diff --git a/apps/web/src/pages/_app.tsx b/apps/web/src/pages/_app.tsx index e3b992cc81..cadf6ca02d 100644 --- a/apps/web/src/pages/_app.tsx +++ b/apps/web/src/pages/_app.tsx @@ -23,7 +23,7 @@ import Head from 'next/head'; import '@affine/i18n'; import { useTranslation } from '@affine/i18n'; import React from 'react'; -import { BlockSuiteProvider } from '@/store/workspace'; +import { GlobalAppProvider } from '@/store/app'; const ThemeProvider = dynamic(() => import('@/providers/ThemeProvider'), { ssr: false, @@ -68,7 +68,7 @@ const App = ({ Component, pageProps }: AppPropsWithLayout) => { AFFiNE - + , @@ -83,7 +83,7 @@ const App = ({ Component, pageProps }: AppPropsWithLayout) => { {getLayout()} )} - + ); }; diff --git a/apps/web/src/pages/workspace/[workspaceId]/[pageId].tsx b/apps/web/src/pages/workspace/[workspaceId]/[pageId].tsx index 458460e392..315bcd2ca6 100644 --- a/apps/web/src/pages/workspace/[workspaceId]/[pageId].tsx +++ b/apps/web/src/pages/workspace/[workspaceId]/[pageId].tsx @@ -9,14 +9,14 @@ import { usePageHelper } from '@/hooks/use-page-helper'; import dynamic from 'next/dynamic'; import Head from 'next/head'; import { useTranslation } from '@affine/i18n'; -import { useBlockSuite } from '@/store/workspace'; +import { useGlobalState } from '@/store/app'; const DynamicBlocksuite = dynamic(() => import('@/components/editor'), { ssr: false, }); const BlockHubAppender = () => { - const setBlockHub = useBlockSuite(store => store.setBlockHub); - const editor = useBlockSuite(store => store.editor); + const setBlockHub = useGlobalState(store => store.setBlockHub); + const editor = useGlobalState(store => store.editor); useEffect(() => { let blockHubElement: HTMLElement | null = null; @@ -38,8 +38,8 @@ const BlockHubAppender = () => { }; const Page: NextPageWithLayout = () => { - const currentPage = useBlockSuite(store => store.currentPage); - const setEditor = useBlockSuite(store => store.setEditor); + const currentPage = useGlobalState(store => store.currentPage); + const setEditor = useGlobalState(store => store.setEditor); const { currentWorkspace } = useAppState(); const { t } = useTranslation(); @@ -69,7 +69,7 @@ const Page: NextPageWithLayout = () => { const PageDefender = ({ children }: PropsWithChildren) => { const router = useRouter(); const [pageLoaded, setPageLoaded] = useState(false); - const loadPage = useBlockSuite(store => store.loadPage); + const loadPage = useGlobalState(store => store.loadPage); const { currentWorkspace } = useAppState(); const { createPage } = usePageHelper(); diff --git a/apps/web/src/pages/workspace/[workspaceId]/setting.tsx b/apps/web/src/pages/workspace/[workspaceId]/setting.tsx index d91212ca8b..9ddd114fdf 100644 --- a/apps/web/src/pages/workspace/[workspaceId]/setting.tsx +++ b/apps/web/src/pages/workspace/[workspaceId]/setting.tsx @@ -25,10 +25,11 @@ import { useTranslation } from '@affine/i18n'; import { PageListHeader } from '@/components/header'; import Head from 'next/head'; import { styled } from '@affine/component'; +import { useGlobalState } from '@/store/app'; const useTabMap = () => { const { t } = useTranslation(); - const { isOwner } = useAppState(); + const isOwner = useGlobalState(store => store.isOwner); const tabMap: { name: string; panelRender: (workspace: WorkspaceUnit) => ReactNode; diff --git a/apps/web/src/providers/app-state-provider/Provider.tsx b/apps/web/src/providers/app-state-provider/Provider.tsx index 9434ddabf0..285dfd8d3d 100644 --- a/apps/web/src/providers/app-state-provider/Provider.tsx +++ b/apps/web/src/providers/app-state-provider/Provider.tsx @@ -1,15 +1,12 @@ import { createContext, useContext, useEffect, useState, useRef } from 'react'; import type { PropsWithChildren } from 'react'; -import { getDataCenter } from '@affine/datacenter'; import { AppStateContext, AppStateFunction, AppStateValue, PageMeta, } from './interface'; -import { createDefaultWorkspace } from './utils'; -import { User } from '@affine/datacenter'; -import { useBlockSuiteApi } from '@/store/workspace'; +import { useGlobalState, useGlobalStateApi } from '@/store/app'; export interface Disposable { dispose(): void; @@ -23,35 +20,9 @@ export const useAppState = () => useContext(AppState); export const AppStateProvider = ({ children, }: PropsWithChildren) => { - const blocksuiteApi = useBlockSuiteApi(); + const globalStateApi = useGlobalStateApi(); const [appState, setAppState] = useState({} as AppStateValue); - const { dataCenter } = appState; const [blobState, setBlobState] = useState(false); - const [userInfo, setUser] = useState({} 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(() => { if (!appState?.currentWorkspace?.blocksuiteWorkspace) { @@ -72,6 +43,24 @@ export const AppStateProvider = ({ }; }, [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(() => { // FIXME: onWorkspacesChange should have dispose function return dataCenter?.onWorkspacesChange( @@ -88,7 +77,8 @@ export const AppStateProvider = ({ const loadWorkspace: AppStateFunction['loadWorkspace'] = useRef() as AppStateFunction['loadWorkspace']; 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)) { return null; } @@ -116,6 +106,7 @@ export const AppStateProvider = ({ // 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; } @@ -123,13 +114,16 @@ export const AppStateProvider = ({ const pageList = (workspace?.blocksuiteWorkspace?.meta.pageMetas as PageMeta[]) ?? []; if (workspace?.blocksuiteWorkspace) { - blocksuiteApi.getState().setWorkspace(workspace.blocksuiteWorkspace); + globalStateApi.getState().setWorkspace(workspace.blocksuiteWorkspace); } + globalStateApi.setState({ + isOwner, + }); + setAppState({ ...appState, currentWorkspace: workspace, pageList: pageList, - isOwner, }); abort?.removeEventListener('abort', onAbort); @@ -157,36 +151,12 @@ export const AppStateProvider = ({ }; }, [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 ( {children} diff --git a/apps/web/src/providers/app-state-provider/interface.ts b/apps/web/src/providers/app-state-provider/interface.ts index e5947b6f06..e1a81ca041 100644 --- a/apps/web/src/providers/app-state-provider/interface.ts +++ b/apps/web/src/providers/app-state-provider/interface.ts @@ -1,4 +1,4 @@ -import { DataCenter, User, WorkspaceUnit } from '@affine/datacenter'; +import { WorkspaceUnit } from '@affine/datacenter'; import type { EditorContainer } from '@blocksuite/editor'; import type { @@ -15,13 +15,10 @@ export interface PageMeta extends StorePageMeta { } export type AppStateValue = { - dataCenter: DataCenter; - user?: User | null; workspaceList: WorkspaceUnit[]; currentWorkspace: WorkspaceUnit | null; pageList: PageMeta[]; synced: boolean; - isOwner?: boolean; blobDataSynced?: boolean; }; @@ -29,9 +26,6 @@ export type AppStateFunction = { loadWorkspace: MutableRefObject< (workspaceId: string, abort?: AbortSignal) => Promise >; - - login: () => Promise; - logout: () => Promise; }; export type AppStateContext = AppStateValue & AppStateFunction; diff --git a/apps/web/src/store/app/blocksuite/index.ts b/apps/web/src/store/app/blocksuite/index.ts new file mode 100644 index 0000000000..438479103d --- /dev/null +++ b/apps/web/src/store/app/blocksuite/index.ts @@ -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, + }); + }, +}); diff --git a/apps/web/src/store/app/index.tsx b/apps/web/src/store/app/index.tsx new file mode 100644 index 0000000000..60bb9abbda --- /dev/null +++ b/apps/web/src/store/app/index.tsx @@ -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 = 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( + { + ...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; + +const GlobalStateContext = createContext(null); + +export const useGlobalStateApi = () => { + const api = useContext(GlobalStateContext); + if (!api) { + throw new Error('cannot find modal context'); + } + return api; +}; + +export const useGlobalState: UseBoundStore = (( + selector: Parameters>[0], + equals: Parameters>[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 = + function ModelProvider({ children }) { + return ( + create(), [])}> + + {children} + + ); + }; diff --git a/apps/web/src/store/app/user/index.ts b/apps/web/src/store/app/user/index.ts new file mode 100644 index 0000000000..a147ba3e02 --- /dev/null +++ b/apps/web/src/store/app/user/index.ts @@ -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; + logout: () => Promise; +} + +export const createUserState = (): UserState => ({ + user: null, + isOwner: false, +}); + +export const createUserActions: GlobalActionsCreator = ( + 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 }); + }, + }; +}; diff --git a/apps/web/src/store/workspace/index.tsx b/apps/web/src/store/workspace/index.tsx deleted file mode 100644 index 4dc109e8ca..0000000000 --- a/apps/web/src/store/workspace/index.tsx +++ /dev/null @@ -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( - { - 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; - -const BlockSuiteContext = createContext(null); - -export const useBlockSuiteApi = () => { - const api = useContext(BlockSuiteContext); - if (!api) { - throw new Error('cannot find modal context'); - } - return api; -}; - -export const useBlockSuite: UseBoundStore = (( - selector: Parameters>[0], - equals: Parameters>[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 = - function ModelProvider({ children }) { - return ( - create(), [])}> - {children} - - ); - };