mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-18 02:26:21 +08:00
refactor: lazy load workspaces (#3091)
This commit is contained in:
@@ -3,34 +3,14 @@
|
||||
*/
|
||||
import 'fake-indexeddb/auto';
|
||||
|
||||
import { initEmptyPage } from '@affine/env/blocksuite';
|
||||
import type {
|
||||
LocalIndexedDBBackgroundProvider,
|
||||
WorkspaceAdapter,
|
||||
} from '@affine/env/workspace';
|
||||
import { WorkspaceFlavour, WorkspaceVersion } from '@affine/env/workspace';
|
||||
import {
|
||||
rootCurrentWorkspaceIdAtom,
|
||||
rootWorkspacesMetadataAtom,
|
||||
workspaceAdaptersAtom,
|
||||
} from '@affine/workspace/atom';
|
||||
import { createIndexedDBBackgroundProvider } from '@affine/workspace/providers';
|
||||
import {
|
||||
_cleanupBlockSuiteWorkspaceCache,
|
||||
createEmptyBlockSuiteWorkspace,
|
||||
} from '@affine/workspace/utils';
|
||||
import type { ParagraphBlockModel } from '@blocksuite/blocks/models';
|
||||
import type { Page } from '@blocksuite/store';
|
||||
import { createStore } from 'jotai';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import { WorkspaceAdapters } from '../../adapters/workspace';
|
||||
import {
|
||||
pageSettingFamily,
|
||||
pageSettingsAtom,
|
||||
recentPageSettingsAtom,
|
||||
} from '../index';
|
||||
import { rootCurrentWorkspaceAtom } from '../root';
|
||||
|
||||
describe('page mode atom', () => {
|
||||
test('basic', () => {
|
||||
@@ -63,66 +43,3 @@ describe('page mode atom', () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('currentWorkspace atom', () => {
|
||||
test('should be defined', async () => {
|
||||
const store = createStore();
|
||||
store.set(
|
||||
workspaceAdaptersAtom,
|
||||
WorkspaceAdapters as Record<
|
||||
WorkspaceFlavour,
|
||||
WorkspaceAdapter<WorkspaceFlavour>
|
||||
>
|
||||
);
|
||||
let id: string;
|
||||
{
|
||||
const workspace = createEmptyBlockSuiteWorkspace(
|
||||
'test',
|
||||
WorkspaceFlavour.LOCAL
|
||||
);
|
||||
const page = workspace.createPage({ id: 'page0' });
|
||||
await initEmptyPage(page);
|
||||
const frameId = page.getBlockByFlavour('affine:note').at(0)?.id as string;
|
||||
id = page.addBlock(
|
||||
'affine:paragraph',
|
||||
{
|
||||
text: new page.Text('test 1'),
|
||||
},
|
||||
frameId
|
||||
);
|
||||
const provider = createIndexedDBBackgroundProvider(
|
||||
workspace.id,
|
||||
workspace.doc,
|
||||
{
|
||||
awareness: workspace.awarenessStore.awareness,
|
||||
}
|
||||
) as LocalIndexedDBBackgroundProvider;
|
||||
provider.connect();
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
provider.disconnect();
|
||||
const workspaceId = await WorkspaceAdapters[
|
||||
WorkspaceFlavour.LOCAL
|
||||
].CRUD.create(workspace);
|
||||
await store.set(rootWorkspacesMetadataAtom, [
|
||||
{
|
||||
id: workspaceId,
|
||||
flavour: WorkspaceFlavour.LOCAL,
|
||||
version: WorkspaceVersion.SubDoc,
|
||||
},
|
||||
]);
|
||||
_cleanupBlockSuiteWorkspaceCache();
|
||||
}
|
||||
store.set(
|
||||
rootCurrentWorkspaceIdAtom,
|
||||
(await store.get(rootWorkspacesMetadataAtom))[0].id
|
||||
);
|
||||
const workspace = await store.get(rootCurrentWorkspaceAtom);
|
||||
expect(workspace).toBeDefined();
|
||||
const page = workspace.blockSuiteWorkspace.getPage('page0') as Page;
|
||||
await page.waitForLoaded();
|
||||
expect(page).not.toBeNull();
|
||||
const paragraphBlock = page.getBlockById(id) as ParagraphBlockModel;
|
||||
expect(paragraphBlock).not.toBeNull();
|
||||
expect(paragraphBlock.text.toString()).toBe('test 1');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,20 +10,18 @@ export const openCreateWorkspaceModalAtom = atom<CreateWorkspaceMode>(false);
|
||||
export const openQuickSearchModalAtom = atom(false);
|
||||
export const openOnboardingModalAtom = atom(false);
|
||||
|
||||
export type SettingAtom = Pick<SettingProps, 'activeTab' | 'workspace'> & {
|
||||
export type SettingAtom = Pick<SettingProps, 'activeTab' | 'workspaceId'> & {
|
||||
open: boolean;
|
||||
};
|
||||
|
||||
export const openSettingModalAtom = atom<SettingAtom>({
|
||||
activeTab: 'appearance',
|
||||
workspace: null,
|
||||
workspaceId: null,
|
||||
open: false,
|
||||
});
|
||||
|
||||
export const openDisableCloudAlertModalAtom = atom(false);
|
||||
|
||||
export { workspacesAtom } from './root';
|
||||
|
||||
type PageMode = 'page' | 'edgeless';
|
||||
type PageLocalSetting = {
|
||||
mode: PageMode;
|
||||
|
||||
@@ -1,160 +0,0 @@
|
||||
//#region async atoms that to load the real workspace data
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
import type {
|
||||
WorkspaceAdapter,
|
||||
WorkspaceRegistry,
|
||||
} from '@affine/env/workspace';
|
||||
import type { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import {
|
||||
rootCurrentWorkspaceIdAtom,
|
||||
rootWorkspacesMetadataAtom,
|
||||
workspaceAdaptersAtom,
|
||||
} from '@affine/workspace/atom';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import type { ActiveDocProvider } from '@blocksuite/store';
|
||||
import { atom } from 'jotai';
|
||||
|
||||
import type { AllWorkspace } from '../shared';
|
||||
|
||||
const logger = new DebugLogger('web:atoms:root');
|
||||
|
||||
/**
|
||||
* Fetch all workspaces from the Plugin CRUD
|
||||
*/
|
||||
export const workspacesAtom = atom<Promise<AllWorkspace[]>>(
|
||||
async (get, { signal }) => {
|
||||
const WorkspaceAdapters = get(workspaceAdaptersAtom);
|
||||
const flavours: string[] = Object.values(WorkspaceAdapters).map(
|
||||
plugin => plugin.flavour
|
||||
);
|
||||
const jotaiWorkspaces = (await get(rootWorkspacesMetadataAtom)).filter(
|
||||
workspace => flavours.includes(workspace.flavour)
|
||||
);
|
||||
if (jotaiWorkspaces.some(meta => !('version' in meta))) {
|
||||
// wait until all workspaces have migrated to v2
|
||||
await new Promise((resolve, reject) => {
|
||||
signal.addEventListener('abort', reject);
|
||||
setTimeout(resolve, 1000);
|
||||
}).catch(() => {
|
||||
// do nothing
|
||||
});
|
||||
}
|
||||
const workspaces = await Promise.all(
|
||||
jotaiWorkspaces.map(workspace => {
|
||||
const adapter = WorkspaceAdapters[
|
||||
workspace.flavour
|
||||
] as WorkspaceAdapter<WorkspaceFlavour>;
|
||||
assertExists(adapter);
|
||||
const { CRUD } = adapter;
|
||||
return CRUD.get(workspace.id).then(workspace => {
|
||||
if (workspace === null) {
|
||||
console.warn(
|
||||
'workspace is null. this should not happen. If you see this error, please report it to the developer.'
|
||||
);
|
||||
}
|
||||
return workspace;
|
||||
});
|
||||
})
|
||||
).then(workspaces =>
|
||||
workspaces.filter(
|
||||
(workspace): workspace is WorkspaceRegistry['affine-cloud' | 'local'] =>
|
||||
workspace !== null
|
||||
)
|
||||
);
|
||||
const workspaceProviders = workspaces.map(workspace =>
|
||||
workspace.blockSuiteWorkspace.providers.filter(
|
||||
(provider): provider is ActiveDocProvider =>
|
||||
'active' in provider && provider.active
|
||||
)
|
||||
);
|
||||
const promises: Promise<void>[] = [];
|
||||
for (const providers of workspaceProviders) {
|
||||
for (const provider of providers) {
|
||||
provider.sync();
|
||||
promises.push(provider.whenReady);
|
||||
}
|
||||
}
|
||||
// we will wait for all the necessary providers to be ready
|
||||
await Promise.all(promises);
|
||||
logger.info('workspaces', workspaces);
|
||||
return workspaces;
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* This will throw an error if the workspace is not found,
|
||||
* should not be used on the root component,
|
||||
* use `rootCurrentWorkspaceIdAtom` instead
|
||||
*/
|
||||
export const rootCurrentWorkspaceAtom = atom<Promise<AllWorkspace>>(
|
||||
async (get, { signal }) => {
|
||||
const WorkspaceAdapters = get(workspaceAdaptersAtom);
|
||||
const metadata = await get(rootWorkspacesMetadataAtom);
|
||||
const targetId = get(rootCurrentWorkspaceIdAtom);
|
||||
if (targetId === null) {
|
||||
throw new Error(
|
||||
'current workspace id is null. this should not happen. If you see this error, please report it to the developer.'
|
||||
);
|
||||
}
|
||||
const targetWorkspace = metadata.find(meta => meta.id === targetId);
|
||||
if (!targetWorkspace) {
|
||||
throw new Error(`cannot find the workspace with id ${targetId}.`);
|
||||
}
|
||||
|
||||
if (!('version' in targetWorkspace)) {
|
||||
// wait until the workspace has migrated to v2
|
||||
await new Promise((resolve, reject) => {
|
||||
signal.addEventListener('abort', reject);
|
||||
setTimeout(resolve, 1000);
|
||||
}).catch(() => {
|
||||
// do nothing
|
||||
});
|
||||
}
|
||||
|
||||
const adapter = WorkspaceAdapters[
|
||||
targetWorkspace.flavour
|
||||
] as WorkspaceAdapter<WorkspaceFlavour>;
|
||||
assertExists(adapter);
|
||||
|
||||
const workspace = await adapter.CRUD.get(targetWorkspace.id);
|
||||
if (!workspace) {
|
||||
throw new Error(
|
||||
`cannot find the workspace with id ${targetId} in the plugin ${targetWorkspace.flavour}.`
|
||||
);
|
||||
}
|
||||
|
||||
const providers = workspace.blockSuiteWorkspace.providers.filter(
|
||||
(provider): provider is ActiveDocProvider =>
|
||||
'active' in provider && provider.active === true
|
||||
);
|
||||
for (const provider of providers) {
|
||||
provider.sync();
|
||||
// we will wait for the necessary providers to be ready
|
||||
await provider.whenReady;
|
||||
}
|
||||
logger.info('current workspace', workspace);
|
||||
globalThis.currentWorkspace = workspace;
|
||||
globalThis.dispatchEvent(
|
||||
new CustomEvent('affine:workspace:change', {
|
||||
detail: { id: workspace.id },
|
||||
})
|
||||
);
|
||||
return workspace;
|
||||
}
|
||||
);
|
||||
|
||||
declare global {
|
||||
/**
|
||||
* @internal debug only
|
||||
*/
|
||||
// eslint-disable-next-line no-var
|
||||
var currentWorkspace: AllWorkspace | undefined;
|
||||
interface WindowEventMap {
|
||||
'affine:workspace:change': CustomEvent<{ id: string }>;
|
||||
}
|
||||
}
|
||||
|
||||
// Do not add `rootCurrentWorkspacePageAtom`, this is not needed.
|
||||
// It can be derived from `rootCurrentWorkspaceAtom` and `rootCurrentPageIdAtom`
|
||||
|
||||
//#endregion
|
||||
Reference in New Issue
Block a user