mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-18 06:47:02 +08:00
refactor: support suspense mode in workspaces (#1304)
This commit is contained in:
@@ -1,172 +1,25 @@
|
||||
import { Workspace } from '@affine/datacenter';
|
||||
import { config, getEnvironment } from '@affine/env';
|
||||
import { nanoid } from '@blocksuite/store';
|
||||
import { useCallback, useMemo, useSyncExternalStore } from 'react';
|
||||
import useSWR from 'swr';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { lockMutex } from '../atoms';
|
||||
import { createLocalProviders } from '../blocksuite';
|
||||
import { jotaiWorkspacesAtom, workspacesAtom } from '../atoms';
|
||||
import { WorkspacePlugins } from '../plugins';
|
||||
import { QueryKey } from '../plugins/affine/fetcher';
|
||||
import { kStoreKey } from '../plugins/local';
|
||||
import { LocalPlugin } from '../plugins/local';
|
||||
import { LocalWorkspace, RemWorkspace, RemWorkspaceFlavour } from '../shared';
|
||||
import { createEmptyBlockSuiteWorkspace } from '../utils';
|
||||
|
||||
// fixme(himself65): refactor with jotai atom using async
|
||||
export const dataCenter = {
|
||||
workspaces: [] as RemWorkspace[],
|
||||
isLoaded: false,
|
||||
callbacks: new Set<() => void>(),
|
||||
};
|
||||
|
||||
export function vitestRefreshWorkspaces() {
|
||||
dataCenter.workspaces = [];
|
||||
dataCenter.callbacks.clear();
|
||||
}
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line no-var
|
||||
var dataCenter: {
|
||||
workspaces: RemWorkspace[];
|
||||
isLoaded: boolean;
|
||||
callbacks: Set<() => void>;
|
||||
};
|
||||
}
|
||||
|
||||
globalThis.dataCenter = dataCenter;
|
||||
|
||||
function createRemLocalWorkspace(name: string) {
|
||||
const id = nanoid();
|
||||
const blockSuiteWorkspace = createEmptyBlockSuiteWorkspace(
|
||||
id,
|
||||
(_: string) => undefined
|
||||
);
|
||||
blockSuiteWorkspace.meta.setName(name);
|
||||
const workspace: LocalWorkspace = {
|
||||
flavour: RemWorkspaceFlavour.LOCAL,
|
||||
blockSuiteWorkspace: blockSuiteWorkspace,
|
||||
providers: [...createLocalProviders(blockSuiteWorkspace)],
|
||||
id,
|
||||
};
|
||||
if (config.enableIndexedDBProvider) {
|
||||
let ids: string[];
|
||||
try {
|
||||
ids = JSON.parse(localStorage.getItem(kStoreKey) ?? '[]');
|
||||
if (!Array.isArray(ids)) {
|
||||
localStorage.setItem(kStoreKey, '[]');
|
||||
ids = [];
|
||||
}
|
||||
} catch (e) {
|
||||
localStorage.setItem(kStoreKey, '[]');
|
||||
ids = [];
|
||||
}
|
||||
ids.push(id);
|
||||
localStorage.setItem(kStoreKey, JSON.stringify(ids));
|
||||
}
|
||||
dataCenter.workspaces = [...dataCenter.workspaces, workspace];
|
||||
dataCenter.callbacks.forEach(cb => cb());
|
||||
return id;
|
||||
}
|
||||
|
||||
const emptyWorkspaces: RemWorkspace[] = [];
|
||||
|
||||
export async function refreshDataCenter(signal?: AbortSignal) {
|
||||
dataCenter.isLoaded = false;
|
||||
dataCenter.callbacks.forEach(cb => cb());
|
||||
if (getEnvironment().isServer) {
|
||||
return;
|
||||
}
|
||||
// fixme(himself65): `prefetchWorkspace` is not used
|
||||
// use `config.enablePlugin = ['affine', 'local']` instead
|
||||
// if (!config.prefetchWorkspace) {
|
||||
// console.info('prefetchNecessaryData: skip prefetching');
|
||||
// return;
|
||||
// }
|
||||
const plugins = Object.values(WorkspacePlugins).sort(
|
||||
(a, b) => a.loadPriority - b.loadPriority
|
||||
);
|
||||
// prefetch data in order
|
||||
for (const plugin of plugins) {
|
||||
console.info('prefetchNecessaryData: plugin', plugin.flavour);
|
||||
try {
|
||||
if (signal?.aborted) {
|
||||
break;
|
||||
}
|
||||
const oldData = dataCenter.workspaces;
|
||||
await plugin.prefetchData(dataCenter, signal);
|
||||
const newData = dataCenter.workspaces;
|
||||
if (!Object.is(oldData, newData)) {
|
||||
console.info('prefetchNecessaryData: data changed');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('error prefetch data', plugin.flavour, e);
|
||||
}
|
||||
}
|
||||
dataCenter.isLoaded = true;
|
||||
dataCenter.callbacks.forEach(cb => cb());
|
||||
}
|
||||
|
||||
export function useWorkspaces(): RemWorkspace[] {
|
||||
return useSyncExternalStore(
|
||||
useCallback(onStoreChange => {
|
||||
dataCenter.callbacks.add(onStoreChange);
|
||||
return () => {
|
||||
dataCenter.callbacks.delete(onStoreChange);
|
||||
};
|
||||
}, []),
|
||||
useCallback(() => dataCenter.workspaces, []),
|
||||
useCallback(() => emptyWorkspaces, [])
|
||||
);
|
||||
}
|
||||
|
||||
export function useWorkspacesIsLoaded(): boolean {
|
||||
return useSyncExternalStore(
|
||||
useCallback(onStoreChange => {
|
||||
dataCenter.callbacks.add(onStoreChange);
|
||||
return () => {
|
||||
dataCenter.callbacks.delete(onStoreChange);
|
||||
};
|
||||
}, []),
|
||||
useCallback(() => dataCenter.isLoaded, []),
|
||||
useCallback(() => true, [])
|
||||
);
|
||||
}
|
||||
|
||||
export function useSyncWorkspaces() {
|
||||
return useSWR<Workspace[]>(QueryKey.getWorkspaces, {
|
||||
fallbackData: [],
|
||||
revalidateOnReconnect: true,
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnMount: true,
|
||||
revalidateIfStale: false,
|
||||
});
|
||||
}
|
||||
|
||||
async function deleteWorkspace(workspaceId: string) {
|
||||
return lockMutex(async () => {
|
||||
console.warn('deleting workspace');
|
||||
const idx = dataCenter.workspaces.findIndex(
|
||||
workspace => workspace.id === workspaceId
|
||||
);
|
||||
if (idx === -1) {
|
||||
throw new Error('workspace not found');
|
||||
}
|
||||
try {
|
||||
const [workspace] = dataCenter.workspaces.splice(idx, 1);
|
||||
// @ts-expect-error
|
||||
await WorkspacePlugins[workspace.flavour].deleteWorkspace(workspace);
|
||||
dataCenter.callbacks.forEach(cb => cb());
|
||||
} catch (e) {
|
||||
console.error('error deleting workspace', e);
|
||||
}
|
||||
});
|
||||
return useAtomValue(workspacesAtom);
|
||||
}
|
||||
|
||||
export function useWorkspacesHelper() {
|
||||
return useMemo(
|
||||
() => ({
|
||||
createWorkspacePage: (workspaceId: string, pageId: string) => {
|
||||
const workspace = dataCenter.workspaces.find(
|
||||
const workspaces = useWorkspaces();
|
||||
const jotaiWorkspaces = useAtomValue(jotaiWorkspacesAtom);
|
||||
const set = useSetAtom(jotaiWorkspacesAtom);
|
||||
return {
|
||||
createWorkspacePage: useCallback(
|
||||
(workspaceId: string, pageId: string) => {
|
||||
const workspace = workspaces.find(
|
||||
ws => ws.id === workspaceId
|
||||
) as LocalWorkspace;
|
||||
if (workspace && 'blockSuiteWorkspace' in workspace) {
|
||||
@@ -175,9 +28,46 @@ export function useWorkspacesHelper() {
|
||||
throw new Error('cannot create page. blockSuiteWorkspace not found');
|
||||
}
|
||||
},
|
||||
createRemLocalWorkspace,
|
||||
deleteWorkspace,
|
||||
}),
|
||||
[]
|
||||
);
|
||||
[workspaces]
|
||||
),
|
||||
createLocalWorkspace: useCallback(
|
||||
async (name: string): Promise<string> => {
|
||||
const blockSuiteWorkspace = createEmptyBlockSuiteWorkspace(
|
||||
nanoid(),
|
||||
_ => undefined
|
||||
);
|
||||
blockSuiteWorkspace.meta.setName(name);
|
||||
const id = await LocalPlugin.CRUD.create(blockSuiteWorkspace);
|
||||
set(workspaces => [
|
||||
...workspaces,
|
||||
{
|
||||
id,
|
||||
flavour: RemWorkspaceFlavour.LOCAL,
|
||||
},
|
||||
]);
|
||||
return id;
|
||||
},
|
||||
[set]
|
||||
),
|
||||
deleteWorkspace: useCallback(
|
||||
async (workspaceId: string) => {
|
||||
const targetJotaiWorkspace = jotaiWorkspaces.find(
|
||||
ws => ws.id === workspaceId
|
||||
);
|
||||
const targetWorkspace = workspaces.find(ws => ws.id === workspaceId);
|
||||
if (!targetJotaiWorkspace || !targetWorkspace) {
|
||||
throw new Error('page cannot be found');
|
||||
}
|
||||
|
||||
// delete workspace from plugin
|
||||
await WorkspacePlugins[targetWorkspace.flavour].CRUD.delete(
|
||||
// fixme: type casting
|
||||
targetWorkspace as any
|
||||
);
|
||||
// delete workspace from jotai storage
|
||||
set(workspaces => workspaces.filter(ws => ws.id !== workspaceId));
|
||||
},
|
||||
[jotaiWorkspaces, set, workspaces]
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user