refactor: merge plugin-infra into infra (#3540)

This commit is contained in:
Alex Yang
2023-08-03 01:48:59 -07:00
committed by GitHub
parent 3282344d4a
commit 0e32803247
62 changed files with 169 additions and 284 deletions

View File

@@ -0,0 +1,35 @@
import type { Workspace } from '@blocksuite/store';
import { type PassiveDocProvider } from '@blocksuite/store';
import { useAtomValue } from 'jotai/react';
import { useEffect } from 'react';
import {
getActiveBlockSuiteWorkspaceAtom,
workspacePassiveEffectWeakMap,
} from './workspace.js';
export function useStaticBlockSuiteWorkspace(id: string): Workspace {
return useAtomValue(getActiveBlockSuiteWorkspaceAtom(id));
}
export function usePassiveWorkspaceEffect(workspace: Workspace) {
useEffect(() => {
if (workspacePassiveEffectWeakMap.get(workspace) === true) {
return;
}
const providers = workspace.providers.filter(
(provider): provider is PassiveDocProvider =>
'passive' in provider && provider.passive === true
);
providers.forEach(provider => {
provider.connect();
});
workspacePassiveEffectWeakMap.set(workspace, true);
return () => {
providers.forEach(provider => {
provider.disconnect();
});
workspacePassiveEffectWeakMap.delete(workspace);
};
}, [workspace]);
}

View File

@@ -0,0 +1,59 @@
import type { ActiveDocProvider, Workspace } from '@blocksuite/store';
import type { Atom } from 'jotai/vanilla';
import { atom } from 'jotai/vanilla';
/**
* DO NOT ACCESS THIS MAP IN PRODUCTION, OR YOU WILL BE FIRED
* Map: guid -> Workspace
*/
export const INTERNAL_BLOCKSUITE_HASH_MAP = new Map<string, Workspace>([]);
const workspacePassiveAtomWeakMap = new WeakMap<
Workspace,
Atom<Promise<Workspace>>
>();
// Whether the workspace is active to use
export const workspaceActiveWeakMap = new WeakMap<Workspace, boolean>();
// Whether the workspace has been enabled the passive effect (background)
export const workspacePassiveEffectWeakMap = new WeakMap<Workspace, boolean>();
export async function waitForWorkspace(workspace: Workspace) {
if (workspaceActiveWeakMap.get(workspace) !== true) {
const providers = workspace.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;
}
workspaceActiveWeakMap.set(workspace, true);
}
}
export function getWorkspace(id: string) {
if (!INTERNAL_BLOCKSUITE_HASH_MAP.has(id)) {
throw new Error('Workspace not found');
}
return INTERNAL_BLOCKSUITE_HASH_MAP.get(id) as Workspace;
}
export function getActiveBlockSuiteWorkspaceAtom(
id: string
): Atom<Promise<Workspace>> {
if (!INTERNAL_BLOCKSUITE_HASH_MAP.has(id)) {
throw new Error('Workspace not found');
}
const workspace = INTERNAL_BLOCKSUITE_HASH_MAP.get(id) as Workspace;
if (!workspacePassiveAtomWeakMap.has(workspace)) {
const baseAtom = atom(async () => {
await waitForWorkspace(workspace);
return workspace;
});
workspacePassiveAtomWeakMap.set(workspace, baseAtom);
}
return workspacePassiveAtomWeakMap.get(workspace) as Atom<Promise<Workspace>>;
}

View File

@@ -0,0 +1,70 @@
/**
* @vitest-environment happy-dom
*/
import { Workspace } from '@blocksuite/store';
import { renderHook } from '@testing-library/react';
import { getDefaultStore } from 'jotai/vanilla';
import { expect, test, vi } from 'vitest';
import {
usePassiveWorkspaceEffect,
useStaticBlockSuiteWorkspace,
} from '../__internal__/react.js';
import {
getActiveBlockSuiteWorkspaceAtom,
INTERNAL_BLOCKSUITE_HASH_MAP,
} from '../__internal__/workspace.js';
test('useStaticBlockSuiteWorkspace', async () => {
const sync = vi.fn();
let connected = false;
const connect = vi.fn(() => (connected = true));
const workspace = new Workspace({
id: '1',
providerCreators: [
() => ({
flavour: 'fake',
active: true,
sync,
get whenReady(): Promise<void> {
return Promise.resolve();
},
}),
() => ({
flavour: 'fake-2',
passive: true,
get connected() {
return connected;
},
connect,
disconnect: vi.fn(),
}),
],
});
INTERNAL_BLOCKSUITE_HASH_MAP.set('1', workspace);
{
const workspaceHook = renderHook(() => useStaticBlockSuiteWorkspace('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 result = await store.get(atom);
expect(result).toBe(workspace);
expect(sync).toBeCalledTimes(1);
expect(connect).not.toHaveBeenCalled();
}
{
renderHook(() => usePassiveWorkspaceEffect(workspace));
expect(sync).toBeCalledTimes(1);
expect(connect).toBeCalledTimes(1);
expect(connected).toBe(true);
}
});

View File

@@ -0,0 +1,79 @@
import type { CallbackMap, ExpectedLayout } from '@affine/sdk/entry';
import { assertExists } from '@blocksuite/global/utils';
import type { Page, Workspace } from '@blocksuite/store';
import { atom, createStore } from 'jotai/vanilla';
import { getWorkspace, waitForWorkspace } from './__internal__/workspace.js';
// global store
export const rootStore = createStore();
// id -> HTML element
export const headerItemsAtom = atom<Record<string, CallbackMap['headerItem']>>(
{}
);
export const editorItemsAtom = atom<Record<string, CallbackMap['editor']>>({});
export const registeredPluginAtom = atom<string[]>([]);
export const windowItemsAtom = atom<Record<string, CallbackMap['window']>>({});
export const settingItemsAtom = atom<Record<string, CallbackMap['setting']>>(
{}
);
export const formatBarItemsAtom = atom<
Record<string, CallbackMap['formatBar']>
>({});
export const currentWorkspaceIdAtom = atom<string | null>(null);
export const currentPageIdAtom = atom<string | null>(null);
export const currentWorkspaceAtom = atom<Promise<Workspace>>(async get => {
const currentWorkspaceId = get(currentWorkspaceIdAtom);
assertExists(currentWorkspaceId, 'current workspace id');
const workspace = getWorkspace(currentWorkspaceId);
await waitForWorkspace(workspace);
return workspace;
});
export const currentPageAtom = atom<Promise<Page>>(async get => {
const currentWorkspaceId = get(currentWorkspaceIdAtom);
assertExists(currentWorkspaceId, 'current workspace id');
const currentPageId = get(currentPageIdAtom);
assertExists(currentPageId, 'current page id');
const workspace = getWorkspace(currentWorkspaceId);
await waitForWorkspace(workspace);
const page = workspace.getPage(currentPageId);
assertExists(page);
if (!page.loaded) {
await page.waitForLoaded();
}
return page;
});
const contentLayoutBaseAtom = atom<ExpectedLayout>('editor');
type SetStateAction<Value> = Value | ((prev: Value) => Value);
export const contentLayoutAtom = atom<
ExpectedLayout,
[SetStateAction<ExpectedLayout>],
void
>(
get => get(contentLayoutBaseAtom),
(_, set, layout) => {
set(contentLayoutBaseAtom, prev => {
let setV: (prev: ExpectedLayout) => ExpectedLayout;
if (typeof layout !== 'function') {
setV = () => layout;
} else {
setV = layout;
}
const nextValue = setV(prev);
if (nextValue === 'editor') {
return nextValue;
}
if (nextValue.first !== 'editor') {
throw new Error('The first element of the layout should be editor.');
}
if (nextValue.splitPercentage && nextValue.splitPercentage < 70) {
throw new Error('The split percentage should be greater than 70.');
}
return nextValue;
});
}
);

View File

@@ -1,5 +1,45 @@
import type { ExpectedLayout } from '@affine/sdk/entry';
import type { WritableAtom } from 'jotai';
import { z } from 'zod';
import type { TypedEventEmitter } from './core/event-emitter.js';
export const packageJsonInputSchema = z.object({
name: z.string(),
version: z.string(),
description: z.string(),
affinePlugin: z.object({
release: z.boolean(),
entry: z.object({
core: z.string(),
server: z.string().optional(),
}),
serverCommand: z.array(z.string()).optional(),
}),
});
export const packageJsonOutputSchema = z.object({
name: z.string(),
version: z.string(),
description: z.string(),
affinePlugin: z.object({
release: z.boolean(),
entry: z.object({
core: z.string(),
}),
assets: z.array(z.string()),
serverCommand: z.array(z.string()).optional(),
}),
});
type SetStateAction<Value> = Value | ((prev: Value) => Value);
export type ContentLayoutAtom = WritableAtom<
ExpectedLayout,
[SetStateAction<ExpectedLayout>],
void
>;
export abstract class HandlerManager<
Namespace extends string,
Handlers extends Record<string, PrimitiveHandlers>,