mirror of
https://github.com/toeverything/AFFiNE.git
synced 2025-01-05 14:12:54 +03:00
refactor: use jotai-effect
(#4641)
This commit is contained in:
parent
62d2b09e3c
commit
a430266389
@ -19,10 +19,11 @@ import {
|
|||||||
globalBlockSuiteSchema,
|
globalBlockSuiteSchema,
|
||||||
} from '@affine/workspace/manager';
|
} from '@affine/workspace/manager';
|
||||||
import { createIndexedDBDownloadProvider } from '@affine/workspace/providers';
|
import { createIndexedDBDownloadProvider } from '@affine/workspace/providers';
|
||||||
import { useStaticBlockSuiteWorkspace } from '@toeverything/infra/__internal__/react';
|
import { getBlockSuiteWorkspaceAtom } from '@toeverything/infra/__internal__/workspace';
|
||||||
import { getCurrentStore } from '@toeverything/infra/atom';
|
import { getCurrentStore } from '@toeverything/infra/atom';
|
||||||
import { initEmptyPage } from '@toeverything/infra/blocksuite';
|
import { initEmptyPage } from '@toeverything/infra/blocksuite';
|
||||||
import { buildShowcaseWorkspace } from '@toeverything/infra/blocksuite';
|
import { buildShowcaseWorkspace } from '@toeverything/infra/blocksuite';
|
||||||
|
import { useAtomValue } from 'jotai';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
@ -87,7 +88,8 @@ export const LocalAdapter: WorkspaceAdapter<WorkspaceFlavour.LOCAL> = {
|
|||||||
Header: WorkspaceHeader,
|
Header: WorkspaceHeader,
|
||||||
Provider,
|
Provider,
|
||||||
PageDetail: ({ currentWorkspaceId, currentPageId, onLoadEditor }) => {
|
PageDetail: ({ currentWorkspaceId, currentPageId, onLoadEditor }) => {
|
||||||
const workspace = useStaticBlockSuiteWorkspace(currentWorkspaceId);
|
const [workspaceAtom] = getBlockSuiteWorkspaceAtom(currentWorkspaceId);
|
||||||
|
const workspace = useAtomValue(workspaceAtom);
|
||||||
const page = workspace.getPage(currentPageId);
|
const page = workspace.getPage(currentPageId);
|
||||||
if (!page) {
|
if (!page) {
|
||||||
throw new PageNotFoundError(workspace, currentPageId);
|
throw new PageNotFoundError(workspace, currentPageId);
|
||||||
|
@ -11,7 +11,7 @@ import { Avatar } from '@toeverything/components/avatar';
|
|||||||
import { Tooltip } from '@toeverything/components/tooltip';
|
import { Tooltip } from '@toeverything/components/tooltip';
|
||||||
import { useBlockSuiteWorkspaceAvatarUrl } from '@toeverything/hooks/use-block-suite-workspace-avatar-url';
|
import { useBlockSuiteWorkspaceAvatarUrl } from '@toeverything/hooks/use-block-suite-workspace-avatar-url';
|
||||||
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
||||||
import { useStaticBlockSuiteWorkspace } from '@toeverything/infra/__internal__/react';
|
import { getBlockSuiteWorkspaceAtom } from '@toeverything/infra/__internal__/workspace';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useAtom, useAtomValue } from 'jotai/react';
|
import { useAtom, useAtomValue } from 'jotai/react';
|
||||||
import { type ReactElement, Suspense, useCallback, useMemo } from 'react';
|
import { type ReactElement, Suspense, useCallback, useMemo } from 'react';
|
||||||
@ -209,7 +209,8 @@ const WorkspaceListItem = ({
|
|||||||
isCurrent: boolean;
|
isCurrent: boolean;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const workspace = useStaticBlockSuiteWorkspace(meta.id);
|
const [workspaceAtom] = getBlockSuiteWorkspaceAtom(meta.id);
|
||||||
|
const workspace = useAtomValue(workspaceAtom);
|
||||||
const [workspaceAvatar] = useBlockSuiteWorkspaceAvatarUrl(workspace);
|
const [workspaceAvatar] = useBlockSuiteWorkspaceAvatarUrl(workspace);
|
||||||
const [workspaceName] = useBlockSuiteWorkspaceName(workspace);
|
const [workspaceName] = useBlockSuiteWorkspaceName(workspace);
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@ import { WorkspaceSubPath } from '@affine/env/workspace';
|
|||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||||
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
||||||
import { usePassiveWorkspaceEffect } from '@toeverything/infra/__internal__/react';
|
|
||||||
import { useSetAtom } from 'jotai';
|
import { useSetAtom } from 'jotai';
|
||||||
import { useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
@ -34,7 +33,6 @@ export const WorkspaceSetting = ({ workspaceId }: { workspaceId: string }) => {
|
|||||||
const pushNotification = useSetAtom(pushNotificationAtom);
|
const pushNotification = useSetAtom(pushNotificationAtom);
|
||||||
|
|
||||||
const leaveWorkspace = useLeaveWorkspace();
|
const leaveWorkspace = useLeaveWorkspace();
|
||||||
usePassiveWorkspaceEffect(workspace.blockSuiteWorkspace);
|
|
||||||
const setSettingModal = useSetAtom(openSettingModalAtom);
|
const setSettingModal = useSetAtom(openSettingModalAtom);
|
||||||
const { deleteWorkspace } = useAppHelper();
|
const { deleteWorkspace } = useAppHelper();
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import { useAtom, useSetAtom } from 'jotai';
|
|||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect } from 'react';
|
||||||
|
|
||||||
import type { AllWorkspace } from '../../shared';
|
import type { AllWorkspace } from '../../shared';
|
||||||
import { useWorkspace } from '../use-workspace';
|
import { useWorkspace, useWorkspaceEffect } from '../use-workspace';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
/**
|
/**
|
||||||
@ -27,6 +27,8 @@ export function useCurrentWorkspace(): [
|
|||||||
const [id, setId] = useAtom(currentWorkspaceIdAtom);
|
const [id, setId] = useAtom(currentWorkspaceIdAtom);
|
||||||
assertExists(id);
|
assertExists(id);
|
||||||
const currentWorkspace = useWorkspace(id);
|
const currentWorkspace = useWorkspace(id);
|
||||||
|
// when you call current workspace, effect is always called
|
||||||
|
useWorkspaceEffect(currentWorkspace.id);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
globalThis.currentWorkspace = currentWorkspace;
|
globalThis.currentWorkspace = currentWorkspace;
|
||||||
globalThis.dispatchEvent(
|
globalThis.dispatchEvent(
|
||||||
|
@ -2,7 +2,7 @@ import type { AffineOfficialWorkspace } from '@affine/env/workspace';
|
|||||||
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||||
import { assertExists } from '@blocksuite/global/utils';
|
import { assertExists } from '@blocksuite/global/utils';
|
||||||
import type { Workspace } from '@blocksuite/store';
|
import type { Workspace } from '@blocksuite/store';
|
||||||
import { useStaticBlockSuiteWorkspace } from '@toeverything/infra/__internal__/react';
|
import { getBlockSuiteWorkspaceAtom } from '@toeverything/infra/__internal__/workspace';
|
||||||
import type { Atom } from 'jotai';
|
import type { Atom } from 'jotai';
|
||||||
import { atom, useAtomValue } from 'jotai';
|
import { atom, useAtomValue } from 'jotai';
|
||||||
|
|
||||||
@ -11,9 +11,18 @@ const workspaceWeakMap = new WeakMap<
|
|||||||
Atom<Promise<AffineOfficialWorkspace>>
|
Atom<Promise<AffineOfficialWorkspace>>
|
||||||
>();
|
>();
|
||||||
|
|
||||||
|
// workspace effect is the side effect like connect to the server/indexeddb,
|
||||||
|
// this will save the workspace updates permanently.
|
||||||
|
export function useWorkspaceEffect(workspaceId: string): void {
|
||||||
|
const [, effectAtom] = getBlockSuiteWorkspaceAtom(workspaceId);
|
||||||
|
useAtomValue(effectAtom);
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo(himself65): remove this hook
|
||||||
export function useWorkspace(workspaceId: string): AffineOfficialWorkspace {
|
export function useWorkspace(workspaceId: string): AffineOfficialWorkspace {
|
||||||
const blockSuiteWorkspace = useStaticBlockSuiteWorkspace(workspaceId);
|
const [workspaceAtom] = getBlockSuiteWorkspaceAtom(workspaceId);
|
||||||
if (!workspaceWeakMap.has(blockSuiteWorkspace)) {
|
const workspace = useAtomValue(workspaceAtom);
|
||||||
|
if (!workspaceWeakMap.has(workspace)) {
|
||||||
const baseAtom = atom(async get => {
|
const baseAtom = atom(async get => {
|
||||||
const metadata = await get(rootWorkspacesMetadataAtom);
|
const metadata = await get(rootWorkspacesMetadataAtom);
|
||||||
const flavour = metadata.find(({ id }) => id === workspaceId)?.flavour;
|
const flavour = metadata.find(({ id }) => id === workspaceId)?.flavour;
|
||||||
@ -21,15 +30,13 @@ export function useWorkspace(workspaceId: string): AffineOfficialWorkspace {
|
|||||||
return {
|
return {
|
||||||
id: workspaceId,
|
id: workspaceId,
|
||||||
flavour,
|
flavour,
|
||||||
blockSuiteWorkspace,
|
blockSuiteWorkspace: workspace,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
workspaceWeakMap.set(blockSuiteWorkspace, baseAtom);
|
workspaceWeakMap.set(workspace, baseAtom);
|
||||||
}
|
}
|
||||||
|
|
||||||
return useAtomValue(
|
return useAtomValue(
|
||||||
workspaceWeakMap.get(blockSuiteWorkspace) as Atom<
|
workspaceWeakMap.get(workspace) as Atom<Promise<AffineOfficialWorkspace>>
|
||||||
Promise<AffineOfficialWorkspace>
|
|
||||||
>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,6 @@ import {
|
|||||||
useSensors,
|
useSensors,
|
||||||
} from '@dnd-kit/core';
|
} from '@dnd-kit/core';
|
||||||
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
|
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
|
||||||
import { usePassiveWorkspaceEffect } from '@toeverything/infra/__internal__/react';
|
|
||||||
import { currentWorkspaceIdAtom } from '@toeverything/infra/atom';
|
import { currentWorkspaceIdAtom } from '@toeverything/infra/atom';
|
||||||
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
|
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
@ -172,8 +171,6 @@ export const WorkspaceLayoutInner = ({
|
|||||||
}
|
}
|
||||||
}, [currentWorkspace.blockSuiteWorkspace.doc]);
|
}, [currentWorkspace.blockSuiteWorkspace.doc]);
|
||||||
|
|
||||||
usePassiveWorkspaceEffect(currentWorkspace.blockSuiteWorkspace);
|
|
||||||
|
|
||||||
const handleCreatePage = useCallback(() => {
|
const handleCreatePage = useCallback(() => {
|
||||||
const id = nanoid();
|
const id = nanoid();
|
||||||
pageHelper.createPage(id);
|
pageHelper.createPage(id);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useCollectionManager } from '@affine/component/page-list';
|
import { useCollectionManager } from '@affine/component/page-list';
|
||||||
import { WorkspaceSubPath } from '@affine/env/workspace';
|
import { WorkspaceSubPath } from '@affine/env/workspace';
|
||||||
import { assertExists } from '@blocksuite/global/utils';
|
import { assertExists } from '@blocksuite/global/utils';
|
||||||
import { getActiveBlockSuiteWorkspaceAtom } from '@toeverything/infra/__internal__/workspace';
|
import { getBlockSuiteWorkspaceAtom } from '@toeverything/infra/__internal__/workspace';
|
||||||
import { getCurrentStore } from '@toeverything/infra/atom';
|
import { getCurrentStore } from '@toeverything/infra/atom';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import type { LoaderFunction } from 'react-router-dom';
|
import type { LoaderFunction } from 'react-router-dom';
|
||||||
@ -16,7 +16,7 @@ export const loader: LoaderFunction = async args => {
|
|||||||
const rootStore = getCurrentStore();
|
const rootStore = getCurrentStore();
|
||||||
const workspaceId = args.params.workspaceId;
|
const workspaceId = args.params.workspaceId;
|
||||||
assertExists(workspaceId);
|
assertExists(workspaceId);
|
||||||
const workspaceAtom = getActiveBlockSuiteWorkspaceAtom(workspaceId);
|
const [workspaceAtom] = getBlockSuiteWorkspaceAtom(workspaceId);
|
||||||
const workspace = await rootStore.get(workspaceAtom);
|
const workspace = await rootStore.get(workspaceAtom);
|
||||||
for (const pageId of workspace.pages.keys()) {
|
for (const pageId of workspace.pages.keys()) {
|
||||||
const page = workspace.getPage(pageId);
|
const page = workspace.getPage(pageId);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||||
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||||
import { getActiveBlockSuiteWorkspaceAtom } from '@toeverything/infra/__internal__/workspace';
|
import { getBlockSuiteWorkspaceAtom } from '@toeverything/infra/__internal__/workspace';
|
||||||
import {
|
import {
|
||||||
currentPageIdAtom,
|
currentPageIdAtom,
|
||||||
currentWorkspaceIdAtom,
|
currentWorkspaceIdAtom,
|
||||||
@ -31,7 +31,7 @@ export const loader: LoaderFunction = async args => {
|
|||||||
rootStore.set(currentPageIdAtom, null);
|
rootStore.set(currentPageIdAtom, null);
|
||||||
}
|
}
|
||||||
if (currentMetadata.flavour === WorkspaceFlavour.AFFINE_CLOUD) {
|
if (currentMetadata.flavour === WorkspaceFlavour.AFFINE_CLOUD) {
|
||||||
const workspaceAtom = getActiveBlockSuiteWorkspaceAtom(currentMetadata.id);
|
const [workspaceAtom] = getBlockSuiteWorkspaceAtom(currentMetadata.id);
|
||||||
const workspace = await rootStore.get(workspaceAtom);
|
const workspace = await rootStore.get(workspaceAtom);
|
||||||
return (() => {
|
return (() => {
|
||||||
const blockVersions = workspace.meta.blockVersions;
|
const blockVersions = workspace.meta.blockVersions;
|
||||||
|
@ -8,7 +8,8 @@ import { Divider } from '@toeverything/components/divider';
|
|||||||
import { Tooltip } from '@toeverything/components/tooltip';
|
import { Tooltip } from '@toeverything/components/tooltip';
|
||||||
import { useBlockSuiteWorkspaceAvatarUrl } from '@toeverything/hooks/use-block-suite-workspace-avatar-url';
|
import { useBlockSuiteWorkspaceAvatarUrl } from '@toeverything/hooks/use-block-suite-workspace-avatar-url';
|
||||||
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
||||||
import { useStaticBlockSuiteWorkspace } from '@toeverything/infra/__internal__/react';
|
import { getBlockSuiteWorkspaceAtom } from '@toeverything/infra/__internal__/workspace';
|
||||||
|
import { useAtomValue } from 'jotai/react';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -97,7 +98,8 @@ export const WorkspaceCard = ({
|
|||||||
meta,
|
meta,
|
||||||
isOwner = true,
|
isOwner = true,
|
||||||
}: WorkspaceCardProps) => {
|
}: WorkspaceCardProps) => {
|
||||||
const workspace = useStaticBlockSuiteWorkspace(meta.id);
|
const [workspaceAtom] = getBlockSuiteWorkspaceAtom(meta.id);
|
||||||
|
const workspace = useAtomValue(workspaceAtom);
|
||||||
const [name] = useBlockSuiteWorkspaceName(workspace);
|
const [name] = useBlockSuiteWorkspaceName(workspace);
|
||||||
const [workspaceAvatar] = useBlockSuiteWorkspaceAvatarUrl(workspace);
|
const [workspaceAvatar] = useBlockSuiteWorkspaceAvatarUrl(workspace);
|
||||||
return (
|
return (
|
||||||
|
@ -59,6 +59,7 @@
|
|||||||
"@blocksuite/global": "0.0.0-20230926212737-6d4b1569-nightly",
|
"@blocksuite/global": "0.0.0-20230926212737-6d4b1569-nightly",
|
||||||
"@blocksuite/store": "0.0.0-20230926212737-6d4b1569-nightly",
|
"@blocksuite/store": "0.0.0-20230926212737-6d4b1569-nightly",
|
||||||
"jotai": "^2.4.3",
|
"jotai": "^2.4.3",
|
||||||
|
"jotai-effect": "^0.1.0",
|
||||||
"tinykeys": "^2.1.0",
|
"tinykeys": "^2.1.0",
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
import type { Workspace } from '@blocksuite/store';
|
|
||||||
import { useAtomValue } from 'jotai/react';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
import {
|
|
||||||
disablePassiveProviders,
|
|
||||||
enablePassiveProviders,
|
|
||||||
getActiveBlockSuiteWorkspaceAtom,
|
|
||||||
} from './workspace.js';
|
|
||||||
|
|
||||||
export function useStaticBlockSuiteWorkspace(id: string): Workspace {
|
|
||||||
return useAtomValue(getActiveBlockSuiteWorkspaceAtom(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function usePassiveWorkspaceEffect(workspace: Workspace) {
|
|
||||||
useEffect(() => {
|
|
||||||
enablePassiveProviders(workspace);
|
|
||||||
return () => {
|
|
||||||
disablePassiveProviders(workspace);
|
|
||||||
};
|
|
||||||
}, [workspace]);
|
|
||||||
}
|
|
@ -2,9 +2,9 @@ import type { ActiveDocProvider, Workspace } from '@blocksuite/store';
|
|||||||
import type { PassiveDocProvider } from '@blocksuite/store';
|
import type { PassiveDocProvider } from '@blocksuite/store';
|
||||||
import type { Atom } from 'jotai/vanilla';
|
import type { Atom } from 'jotai/vanilla';
|
||||||
import { atom } from 'jotai/vanilla';
|
import { atom } from 'jotai/vanilla';
|
||||||
|
import { atomEffect } from 'jotai-effect';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DO NOT ACCESS THIS MAP IN PRODUCTION, OR YOU WILL BE FIRED
|
|
||||||
* Map: guid -> Workspace
|
* Map: guid -> Workspace
|
||||||
*/
|
*/
|
||||||
export const INTERNAL_BLOCKSUITE_HASH_MAP = new Map<string, Workspace>([]);
|
export const INTERNAL_BLOCKSUITE_HASH_MAP = new Map<string, Workspace>([]);
|
||||||
@ -14,49 +14,8 @@ const workspaceActiveAtomWeakMap = new WeakMap<
|
|||||||
Atom<Promise<Workspace>>
|
Atom<Promise<Workspace>>
|
||||||
>();
|
>();
|
||||||
|
|
||||||
// Whether the workspace is active to use
|
|
||||||
const workspaceActiveWeakMap = new WeakMap<Workspace, boolean>();
|
const workspaceActiveWeakMap = new WeakMap<Workspace, boolean>();
|
||||||
|
const workspaceEffectAtomWeakMap = new WeakMap<Workspace, Atom<void>>();
|
||||||
/**
|
|
||||||
* Whether the workspace has been enabled the passive effect (background)
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export const workspacePassiveEffectWeakMap = new WeakMap<Workspace, number>();
|
|
||||||
|
|
||||||
export function enablePassiveProviders(workspace: Workspace) {
|
|
||||||
const value = workspacePassiveEffectWeakMap.get(workspace);
|
|
||||||
if (value !== undefined && value !== 0) {
|
|
||||||
workspacePassiveEffectWeakMap.set(workspace, value + 1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const providers = workspace.providers.filter(
|
|
||||||
(provider): provider is PassiveDocProvider =>
|
|
||||||
'passive' in provider && provider.passive === true
|
|
||||||
);
|
|
||||||
providers.forEach(provider => {
|
|
||||||
provider.connect();
|
|
||||||
});
|
|
||||||
workspacePassiveEffectWeakMap.set(workspace, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function disablePassiveProviders(workspace: Workspace) {
|
|
||||||
const value = workspacePassiveEffectWeakMap.get(workspace);
|
|
||||||
if (value && value > 0) {
|
|
||||||
workspacePassiveEffectWeakMap.set(workspace, value - 1);
|
|
||||||
if (value - 1 === 0) {
|
|
||||||
const providers = workspace.providers.filter(
|
|
||||||
(provider): provider is PassiveDocProvider =>
|
|
||||||
'passive' in provider && provider.passive === true
|
|
||||||
);
|
|
||||||
providers.forEach(provider => {
|
|
||||||
provider.disconnect();
|
|
||||||
});
|
|
||||||
workspacePassiveEffectWeakMap.delete(workspace);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function waitForWorkspace(workspace: Workspace) {
|
export async function waitForWorkspace(workspace: Workspace) {
|
||||||
if (workspaceActiveWeakMap.get(workspace) !== true) {
|
if (workspaceActiveWeakMap.get(workspace) !== true) {
|
||||||
@ -69,6 +28,7 @@ export async function waitForWorkspace(workspace: Workspace) {
|
|||||||
// we will wait for the necessary providers to be ready
|
// we will wait for the necessary providers to be ready
|
||||||
await provider.whenReady;
|
await provider.whenReady;
|
||||||
}
|
}
|
||||||
|
// timeout is INFINITE
|
||||||
workspaceActiveWeakMap.set(workspace, true);
|
workspaceActiveWeakMap.set(workspace, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -80,9 +40,9 @@ export function getWorkspace(id: string) {
|
|||||||
return INTERNAL_BLOCKSUITE_HASH_MAP.get(id) as Workspace;
|
return INTERNAL_BLOCKSUITE_HASH_MAP.get(id) as Workspace;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getActiveBlockSuiteWorkspaceAtom(
|
export function getBlockSuiteWorkspaceAtom(
|
||||||
id: string
|
id: string
|
||||||
): Atom<Promise<Workspace>> {
|
): [workspaceAtom: Atom<Promise<Workspace>>, workspaceEffectAtom: Atom<void>] {
|
||||||
if (!INTERNAL_BLOCKSUITE_HASH_MAP.has(id)) {
|
if (!INTERNAL_BLOCKSUITE_HASH_MAP.has(id)) {
|
||||||
throw new Error('Workspace not found');
|
throw new Error('Workspace not found');
|
||||||
}
|
}
|
||||||
@ -94,5 +54,26 @@ export function getActiveBlockSuiteWorkspaceAtom(
|
|||||||
});
|
});
|
||||||
workspaceActiveAtomWeakMap.set(workspace, baseAtom);
|
workspaceActiveAtomWeakMap.set(workspace, baseAtom);
|
||||||
}
|
}
|
||||||
return workspaceActiveAtomWeakMap.get(workspace) as Atom<Promise<Workspace>>;
|
if (!workspaceEffectAtomWeakMap.has(workspace)) {
|
||||||
|
const effectAtom = atomEffect(() => {
|
||||||
|
const providers = workspace.providers.filter(
|
||||||
|
(provider): provider is PassiveDocProvider =>
|
||||||
|
'passive' in provider && provider.passive === true
|
||||||
|
);
|
||||||
|
providers.forEach(provider => {
|
||||||
|
provider.connect();
|
||||||
|
});
|
||||||
|
return () => {
|
||||||
|
providers.forEach(provider => {
|
||||||
|
provider.disconnect();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
});
|
||||||
|
workspaceEffectAtomWeakMap.set(workspace, effectAtom);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
workspaceActiveAtomWeakMap.get(workspace) as Atom<Promise<Workspace>>,
|
||||||
|
workspaceEffectAtomWeakMap.get(workspace) as Atom<void>,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
@ -2,20 +2,16 @@
|
|||||||
* @vitest-environment happy-dom
|
* @vitest-environment happy-dom
|
||||||
*/
|
*/
|
||||||
import { Schema, Workspace } from '@blocksuite/store';
|
import { Schema, Workspace } from '@blocksuite/store';
|
||||||
import { renderHook } from '@testing-library/react';
|
import { waitFor } from '@testing-library/react';
|
||||||
import { getDefaultStore } from 'jotai/vanilla';
|
import { getDefaultStore } from 'jotai/vanilla';
|
||||||
import { expect, test, vi } from 'vitest';
|
import { expect, test, vi } from 'vitest';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
usePassiveWorkspaceEffect,
|
getBlockSuiteWorkspaceAtom,
|
||||||
useStaticBlockSuiteWorkspace,
|
|
||||||
} from '../__internal__/react.js';
|
|
||||||
import {
|
|
||||||
getActiveBlockSuiteWorkspaceAtom,
|
|
||||||
INTERNAL_BLOCKSUITE_HASH_MAP,
|
INTERNAL_BLOCKSUITE_HASH_MAP,
|
||||||
} from '../__internal__/workspace.js';
|
} from '../__internal__/workspace.js';
|
||||||
|
|
||||||
test('useStaticBlockSuiteWorkspace', async () => {
|
test('blocksuite atom', async () => {
|
||||||
const sync = vi.fn();
|
const sync = vi.fn();
|
||||||
let connected = false;
|
let connected = false;
|
||||||
const connect = vi.fn(() => (connected = true));
|
const connect = vi.fn(() => (connected = true));
|
||||||
@ -45,27 +41,15 @@ test('useStaticBlockSuiteWorkspace', async () => {
|
|||||||
INTERNAL_BLOCKSUITE_HASH_MAP.set('1', workspace);
|
INTERNAL_BLOCKSUITE_HASH_MAP.set('1', workspace);
|
||||||
|
|
||||||
{
|
{
|
||||||
const workspaceHook = renderHook(() => useStaticBlockSuiteWorkspace('1'));
|
const [atom, effectAtom] = getBlockSuiteWorkspaceAtom('1');
|
||||||
// wait for suspense to resolve
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 100));
|
|
||||||
expect(workspaceHook.result.current).toBe(workspace);
|
|
||||||
expect(sync).toBeCalledTimes(1);
|
|
||||||
expect(connect).not.toHaveBeenCalled();
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const atom = getActiveBlockSuiteWorkspaceAtom('1');
|
|
||||||
const store = getDefaultStore();
|
const store = getDefaultStore();
|
||||||
const result = await store.get(atom);
|
const result = await store.get(atom);
|
||||||
expect(result).toBe(workspace);
|
expect(result).toBe(workspace);
|
||||||
expect(sync).toBeCalledTimes(1);
|
expect(sync).toBeCalledTimes(1);
|
||||||
expect(connect).not.toHaveBeenCalled();
|
expect(connect).not.toHaveBeenCalled();
|
||||||
}
|
|
||||||
|
|
||||||
{
|
store.sub(effectAtom, vi.fn());
|
||||||
renderHook(() => usePassiveWorkspaceEffect(workspace));
|
await waitFor(() => expect(connect).toBeCalledTimes(1));
|
||||||
expect(sync).toBeCalledTimes(1);
|
|
||||||
expect(connect).toBeCalledTimes(1);
|
|
||||||
expect(connected).toBe(true);
|
expect(connected).toBe(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
@ -1,90 +0,0 @@
|
|||||||
import { AffineSchemas } from '@blocksuite/blocks/models';
|
|
||||||
import type { DocProviderCreator } from '@blocksuite/store';
|
|
||||||
import { Schema, Workspace } from '@blocksuite/store';
|
|
||||||
import { getDefaultStore } from 'jotai/vanilla';
|
|
||||||
import { beforeEach, expect, test } from 'vitest';
|
|
||||||
|
|
||||||
import {
|
|
||||||
disablePassiveProviders,
|
|
||||||
enablePassiveProviders,
|
|
||||||
getActiveBlockSuiteWorkspaceAtom,
|
|
||||||
getWorkspace,
|
|
||||||
INTERNAL_BLOCKSUITE_HASH_MAP,
|
|
||||||
workspacePassiveEffectWeakMap,
|
|
||||||
} from '../__internal__/workspace';
|
|
||||||
|
|
||||||
const schema = new Schema();
|
|
||||||
|
|
||||||
schema.register(AffineSchemas);
|
|
||||||
|
|
||||||
const activeWorkspaceEnabled = new Set<string>();
|
|
||||||
const passiveWorkspaceEnabled = new Set<string>();
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
activeWorkspaceEnabled.clear();
|
|
||||||
});
|
|
||||||
|
|
||||||
const createWorkspace = (id: string) => {
|
|
||||||
const activeCreator: DocProviderCreator = () => ({
|
|
||||||
flavour: 'active',
|
|
||||||
active: true,
|
|
||||||
sync() {
|
|
||||||
activeWorkspaceEnabled.add(id);
|
|
||||||
},
|
|
||||||
get whenReady(): Promise<void> {
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const passiveCreator: DocProviderCreator = () => ({
|
|
||||||
flavour: 'passive',
|
|
||||||
passive: true,
|
|
||||||
connect() {
|
|
||||||
passiveWorkspaceEnabled.add(id);
|
|
||||||
},
|
|
||||||
disconnect() {
|
|
||||||
passiveWorkspaceEnabled.delete(id);
|
|
||||||
},
|
|
||||||
get connected() {
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return new Workspace({
|
|
||||||
id,
|
|
||||||
schema,
|
|
||||||
providerCreators: [activeCreator, passiveCreator],
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
test('workspace passive provider should enable correctly', () => {
|
|
||||||
INTERNAL_BLOCKSUITE_HASH_MAP.set('1', createWorkspace('1'));
|
|
||||||
INTERNAL_BLOCKSUITE_HASH_MAP.set('2', createWorkspace('2'));
|
|
||||||
expect(workspacePassiveEffectWeakMap.get(getWorkspace('1'))).toBe(undefined);
|
|
||||||
enablePassiveProviders(getWorkspace('1'));
|
|
||||||
expect(workspacePassiveEffectWeakMap.get(getWorkspace('1'))).toBe(1);
|
|
||||||
expect(workspacePassiveEffectWeakMap.get(getWorkspace('2'))).toBe(undefined);
|
|
||||||
enablePassiveProviders(getWorkspace('1'));
|
|
||||||
expect(workspacePassiveEffectWeakMap.get(getWorkspace('1'))).toBe(2);
|
|
||||||
disablePassiveProviders(getWorkspace('1'));
|
|
||||||
expect(workspacePassiveEffectWeakMap.get(getWorkspace('1'))).toBe(1);
|
|
||||||
disablePassiveProviders(getWorkspace('1'));
|
|
||||||
expect(workspacePassiveEffectWeakMap.get(getWorkspace('1'))).toBe(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('workspace provider should initialize correctly', async () => {
|
|
||||||
INTERNAL_BLOCKSUITE_HASH_MAP.set('1', createWorkspace('1'));
|
|
||||||
{
|
|
||||||
enablePassiveProviders(getWorkspace('1'));
|
|
||||||
expect(activeWorkspaceEnabled.size).toBe(0);
|
|
||||||
expect(passiveWorkspaceEnabled.size).toBe(1);
|
|
||||||
disablePassiveProviders(getWorkspace('1'));
|
|
||||||
expect(activeWorkspaceEnabled.size).toBe(0);
|
|
||||||
expect(passiveWorkspaceEnabled.size).toBe(0);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
const atom = getActiveBlockSuiteWorkspaceAtom('1');
|
|
||||||
await getDefaultStore().get(atom);
|
|
||||||
expect(activeWorkspaceEnabled.size).toBe(1);
|
|
||||||
expect(passiveWorkspaceEnabled.size).toBe(0);
|
|
||||||
}
|
|
||||||
});
|
|
@ -3,7 +3,7 @@ import { assertExists } from '@blocksuite/global/utils';
|
|||||||
import type { Workspace } from '@blocksuite/store';
|
import type { Workspace } from '@blocksuite/store';
|
||||||
import { atom, createStore } from 'jotai/vanilla';
|
import { atom, createStore } from 'jotai/vanilla';
|
||||||
|
|
||||||
import { getActiveBlockSuiteWorkspaceAtom } from './__internal__/workspace';
|
import { getBlockSuiteWorkspaceAtom } from './__internal__/workspace';
|
||||||
|
|
||||||
// global store
|
// global store
|
||||||
let rootStore = createStore();
|
let rootStore = createStore();
|
||||||
@ -26,7 +26,7 @@ export const currentPageIdAtom = atom<string | null>(null);
|
|||||||
export const currentWorkspaceAtom = atom<Promise<Workspace>>(async get => {
|
export const currentWorkspaceAtom = atom<Promise<Workspace>>(async get => {
|
||||||
const workspaceId = get(currentWorkspaceIdAtom);
|
const workspaceId = get(currentWorkspaceIdAtom);
|
||||||
assertExists(workspaceId);
|
assertExists(workspaceId);
|
||||||
const currentWorkspaceAtom = getActiveBlockSuiteWorkspaceAtom(workspaceId);
|
const [currentWorkspaceAtom] = getBlockSuiteWorkspaceAtom(workspaceId);
|
||||||
return get(currentWorkspaceAtom);
|
return get(currentWorkspaceAtom);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
10
yarn.lock
10
yarn.lock
@ -12424,6 +12424,7 @@ __metadata:
|
|||||||
async-call-rpc: ^6.3.1
|
async-call-rpc: ^6.3.1
|
||||||
electron: "link:../../apps/electron/node_modules/electron"
|
electron: "link:../../apps/electron/node_modules/electron"
|
||||||
jotai: ^2.4.3
|
jotai: ^2.4.3
|
||||||
|
jotai-effect: ^0.1.0
|
||||||
nanoid: ^5.0.1
|
nanoid: ^5.0.1
|
||||||
react: ^18.2.0
|
react: ^18.2.0
|
||||||
rxjs: ^7.8.1
|
rxjs: ^7.8.1
|
||||||
@ -24022,6 +24023,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"jotai-effect@npm:^0.1.0":
|
||||||
|
version: 0.1.0
|
||||||
|
resolution: "jotai-effect@npm:0.1.0"
|
||||||
|
peerDependencies:
|
||||||
|
jotai: ">=2.4.3"
|
||||||
|
checksum: fdf8794f9383c911978aa992a994fa5610e8bf2e7752ed995482dc480f87fc0cd7f68a9df5648a0f2fc7335a02b58bee507f3b1394c28fbbc226f81ca06dc694
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"jotai@npm:^2.4.3":
|
"jotai@npm:^2.4.3":
|
||||||
version: 2.4.3
|
version: 2.4.3
|
||||||
resolution: "jotai@npm:2.4.3"
|
resolution: "jotai@npm:2.4.3"
|
||||||
|
Loading…
Reference in New Issue
Block a user