mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-14 21:27:20 +00:00
refactor: rename plugins to adapters (#2480)
This commit is contained in:
50
apps/web/src/adapters/affine/__tests__/index.spec.tsx
Normal file
50
apps/web/src/adapters/affine/__tests__/index.spec.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* @vitest-environment happy-dom
|
||||
*/
|
||||
import {
|
||||
getLoginStorage,
|
||||
isExpired,
|
||||
loginResponseSchema,
|
||||
parseIdToken,
|
||||
setLoginStorage,
|
||||
} from '@affine/workspace/affine/login';
|
||||
import user1 from '@affine-test/fixtures/built-in-user1.json';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { afterEach, describe, expect, test } from 'vitest';
|
||||
|
||||
import { useAffineRefreshAuthToken } from '../../../hooks/affine/use-affine-refresh-auth-token';
|
||||
|
||||
afterEach(() => {
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
describe('AFFiNE workspace', () => {
|
||||
test('Provider', async () => {
|
||||
expect(getLoginStorage()).toBeNull();
|
||||
const data = await fetch('http://127.0.0.1:3000/api/user/token', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
type: 'DebugLoginUser',
|
||||
email: user1.email,
|
||||
password: user1.password,
|
||||
}),
|
||||
}).then(r => r.json());
|
||||
loginResponseSchema.parse(data);
|
||||
setLoginStorage({
|
||||
// expired token that already expired
|
||||
token:
|
||||
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2ODA4MjE0OTQsImlkIjoiaFd0dkFoM1E3SGhiWVlNeGxyX1I0IiwibmFtZSI6ImRlYnVnMSIsImVtYWlsIjoiZGVidWcxQHRvZXZlcnl0aGluZy5pbmZvIiwiYXZhdGFyX3VybCI6bnVsbCwiY3JlYXRlZF9hdCI6MTY4MDgxNTcxMTAwMH0.fDSkbM-ovmGD21sKYSTuiqC1dTiceOfcgIUfI2dLsBk',
|
||||
// but refresh is still valid
|
||||
refresh: data.refresh,
|
||||
});
|
||||
const hook = renderHook(() => useAffineRefreshAuthToken(1));
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
const userData = parseIdToken(getLoginStorage()?.token as string);
|
||||
expect(userData).not.toBeNull();
|
||||
expect(isExpired(userData)).toBe(false);
|
||||
hook.unmount();
|
||||
});
|
||||
});
|
||||
94
apps/web/src/adapters/affine/fetcher.ts
Normal file
94
apps/web/src/adapters/affine/fetcher.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { Unreachable } from '@affine/env/constant';
|
||||
import { rootStore } from '@affine/workspace/atom';
|
||||
import type { AffineLegacyCloudWorkspace } from '@affine/workspace/type';
|
||||
import { WorkspaceFlavour } from '@affine/workspace/type';
|
||||
import { createEmptyBlockSuiteWorkspace } from '@affine/workspace/utils';
|
||||
import { assertExists } from '@blocksuite/store';
|
||||
|
||||
import { workspacesAtom } from '../../atoms';
|
||||
import { createAffineProviders } from '../../blocksuite';
|
||||
import { affineApis } from '../../shared/apis';
|
||||
|
||||
type Query = (typeof QueryKey)[keyof typeof QueryKey];
|
||||
|
||||
export const fetcher = async (
|
||||
query:
|
||||
| Query
|
||||
| [Query, string, boolean]
|
||||
| [Query, string]
|
||||
| [Query, string, string]
|
||||
) => {
|
||||
if (Array.isArray(query)) {
|
||||
if (query[0] === QueryKey.downloadWorkspace) {
|
||||
if (typeof query[2] !== 'boolean') {
|
||||
throw new Unreachable();
|
||||
}
|
||||
return affineApis.downloadWorkspace(query[1], query[2]);
|
||||
} else if (query[0] === QueryKey.getMembers) {
|
||||
return affineApis.getWorkspaceMembers({
|
||||
id: query[1],
|
||||
});
|
||||
} else if (query[0] === QueryKey.getUserByEmail) {
|
||||
if (typeof query[2] !== 'string') {
|
||||
throw new Unreachable();
|
||||
}
|
||||
return affineApis.getUserByEmail({
|
||||
workspace_id: query[1],
|
||||
email: query[2],
|
||||
});
|
||||
} else if (query[0] === QueryKey.getImage) {
|
||||
const workspaceId = query[1];
|
||||
const key = query[2];
|
||||
if (typeof key !== 'string') {
|
||||
throw new TypeError('key must be a string');
|
||||
}
|
||||
const workspaces = await rootStore.get(workspacesAtom);
|
||||
const workspace = workspaces.find(({ id }) => id === workspaceId);
|
||||
assertExists(workspace);
|
||||
const storage = await workspace.blockSuiteWorkspace.blobs;
|
||||
if (!storage) {
|
||||
return null;
|
||||
}
|
||||
return storage.get(key);
|
||||
} else if (query[0] === QueryKey.acceptInvite) {
|
||||
const invitingCode = query[1];
|
||||
if (typeof invitingCode !== 'string') {
|
||||
throw new TypeError('invitingCode must be a string');
|
||||
}
|
||||
return affineApis.acceptInviting({
|
||||
invitingCode,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (query === QueryKey.getWorkspaces) {
|
||||
return affineApis.getWorkspaces().then(workspaces => {
|
||||
return workspaces.map(workspace => {
|
||||
const blockSuiteWorkspace = createEmptyBlockSuiteWorkspace(
|
||||
workspace.id,
|
||||
WorkspaceFlavour.AFFINE,
|
||||
{
|
||||
workspaceApis: affineApis,
|
||||
}
|
||||
);
|
||||
const remWorkspace: AffineLegacyCloudWorkspace = {
|
||||
...workspace,
|
||||
flavour: WorkspaceFlavour.AFFINE,
|
||||
blockSuiteWorkspace,
|
||||
providers: [...createAffineProviders(blockSuiteWorkspace)],
|
||||
};
|
||||
return remWorkspace;
|
||||
});
|
||||
});
|
||||
}
|
||||
return (affineApis as any)[query]();
|
||||
}
|
||||
};
|
||||
|
||||
export const QueryKey = {
|
||||
acceptInvite: 'acceptInvite',
|
||||
getImage: 'getImage',
|
||||
getWorkspaces: 'getWorkspaces',
|
||||
downloadWorkspace: 'downloadWorkspace',
|
||||
getMembers: 'getMembers',
|
||||
getUserByEmail: 'getUserByEmail',
|
||||
} as const;
|
||||
355
apps/web/src/adapters/affine/index.tsx
Normal file
355
apps/web/src/adapters/affine/index.tsx
Normal file
@@ -0,0 +1,355 @@
|
||||
import { AFFINE_STORAGE_KEY, config } from '@affine/env';
|
||||
import { initPage } from '@affine/env/blocksuite';
|
||||
import { PageNotFoundError } from '@affine/env/constant';
|
||||
import { currentAffineUserAtom } from '@affine/workspace/affine/atom';
|
||||
import {
|
||||
clearLoginStorage,
|
||||
getLoginStorage,
|
||||
isExpired,
|
||||
parseIdToken,
|
||||
setLoginStorage,
|
||||
SignMethod,
|
||||
} from '@affine/workspace/affine/login';
|
||||
import { rootStore, rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||
import { createIndexedDBBackgroundProvider } from '@affine/workspace/providers';
|
||||
import type { AffineLegacyCloudWorkspace } from '@affine/workspace/type';
|
||||
import {
|
||||
LoadPriority,
|
||||
ReleaseType,
|
||||
WorkspaceFlavour,
|
||||
} from '@affine/workspace/type';
|
||||
import {
|
||||
cleanupWorkspace,
|
||||
createEmptyBlockSuiteWorkspace,
|
||||
} from '@affine/workspace/utils';
|
||||
import { createJSONStorage } from 'jotai/utils';
|
||||
import type { PropsWithChildren, ReactElement } from 'react';
|
||||
import { Suspense, useEffect } from 'react';
|
||||
import { mutate } from 'swr';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { createAffineProviders } from '../../blocksuite';
|
||||
import { createAffineDownloadProvider } from '../../blocksuite/providers/affine';
|
||||
import { WorkspaceSettingDetail } from '../../components/affine/workspace-setting-detail';
|
||||
import { BlockSuitePageList } from '../../components/blocksuite/block-suite-page-list';
|
||||
import { PageDetailEditor } from '../../components/page-detail-editor';
|
||||
import { PageLoading } from '../../components/pure/loading';
|
||||
import { useAffineRefreshAuthToken } from '../../hooks/affine/use-affine-refresh-auth-token';
|
||||
import { BlockSuiteWorkspace } from '../../shared';
|
||||
import { affineApis, affineAuth } from '../../shared/apis';
|
||||
import { toast } from '../../utils';
|
||||
import type { WorkspaceAdapter } from '../type';
|
||||
import { QueryKey } from './fetcher';
|
||||
|
||||
const storage = createJSONStorage(() => localStorage);
|
||||
const schema = z.object({
|
||||
id: z.string(),
|
||||
type: z.number(),
|
||||
public: z.boolean(),
|
||||
permission: z.number(),
|
||||
});
|
||||
|
||||
const getPersistenceAllWorkspace = () => {
|
||||
const items = storage.getItem(AFFINE_STORAGE_KEY, []);
|
||||
const allWorkspaces: AffineLegacyCloudWorkspace[] = [];
|
||||
if (
|
||||
Array.isArray(items) &&
|
||||
items.every(item => schema.safeParse(item).success)
|
||||
) {
|
||||
allWorkspaces.push(
|
||||
...items.map((item: z.infer<typeof schema>) => {
|
||||
const blockSuiteWorkspace = createEmptyBlockSuiteWorkspace(
|
||||
item.id,
|
||||
WorkspaceFlavour.AFFINE,
|
||||
{
|
||||
workspaceApis: affineApis,
|
||||
}
|
||||
);
|
||||
const affineWorkspace: AffineLegacyCloudWorkspace = {
|
||||
...item,
|
||||
flavour: WorkspaceFlavour.AFFINE,
|
||||
blockSuiteWorkspace,
|
||||
providers: [...createAffineProviders(blockSuiteWorkspace)],
|
||||
};
|
||||
return affineWorkspace;
|
||||
})
|
||||
);
|
||||
}
|
||||
return allWorkspaces;
|
||||
};
|
||||
|
||||
function AuthContext({ children }: PropsWithChildren): ReactElement {
|
||||
const login = useAffineRefreshAuthToken();
|
||||
|
||||
useEffect(() => {
|
||||
if (!login) {
|
||||
console.warn('No login, redirecting to local workspace page...');
|
||||
}
|
||||
}, [login]);
|
||||
if (!login) {
|
||||
return <PageLoading />;
|
||||
}
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
export const AffinePlugin: WorkspaceAdapter<WorkspaceFlavour.AFFINE> = {
|
||||
releaseType: ReleaseType.STABLE,
|
||||
flavour: WorkspaceFlavour.AFFINE,
|
||||
loadPriority: LoadPriority.HIGH,
|
||||
Events: {
|
||||
'workspace:access': async () => {
|
||||
if (!config.enableLegacyCloud) {
|
||||
console.warn('Legacy cloud is disabled');
|
||||
return;
|
||||
}
|
||||
const response = await affineAuth.generateToken(SignMethod.Google);
|
||||
if (response) {
|
||||
setLoginStorage(response);
|
||||
const user = parseIdToken(response.token);
|
||||
rootStore.set(currentAffineUserAtom, user);
|
||||
} else {
|
||||
toast('Login failed');
|
||||
}
|
||||
},
|
||||
'workspace:revoke': async () => {
|
||||
if (!config.enableLegacyCloud) {
|
||||
console.warn('Legacy cloud is disabled');
|
||||
return;
|
||||
}
|
||||
rootStore.set(rootWorkspacesMetadataAtom, workspaces =>
|
||||
workspaces.filter(
|
||||
workspace => workspace.flavour !== WorkspaceFlavour.AFFINE
|
||||
)
|
||||
);
|
||||
storage.removeItem(AFFINE_STORAGE_KEY);
|
||||
clearLoginStorage();
|
||||
rootStore.set(currentAffineUserAtom, null);
|
||||
},
|
||||
},
|
||||
CRUD: {
|
||||
create: async blockSuiteWorkspace => {
|
||||
const binary = BlockSuiteWorkspace.Y.encodeStateAsUpdate(
|
||||
blockSuiteWorkspace.doc
|
||||
);
|
||||
const { id } = await affineApis.createWorkspace(binary);
|
||||
// fixme: syncing images
|
||||
const newWorkspaceId = id;
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
const blobManager = blockSuiteWorkspace.blobs;
|
||||
for (const id of await blobManager.list()) {
|
||||
const blob = await blobManager.get(id);
|
||||
if (blob) {
|
||||
await affineApis.uploadBlob(
|
||||
newWorkspaceId,
|
||||
await blob.arrayBuffer(),
|
||||
blob.type
|
||||
);
|
||||
}
|
||||
}
|
||||
{
|
||||
const bs = createEmptyBlockSuiteWorkspace(id, WorkspaceFlavour.AFFINE, {
|
||||
workspaceApis: affineApis,
|
||||
});
|
||||
// fixme:
|
||||
// force to download workspace binary
|
||||
// to make sure the workspace is synced
|
||||
const provider = createAffineDownloadProvider(bs);
|
||||
const indexedDBProvider = createIndexedDBBackgroundProvider(bs);
|
||||
await new Promise<void>(resolve => {
|
||||
indexedDBProvider.callbacks.add(() => {
|
||||
resolve();
|
||||
});
|
||||
provider.callbacks.add(() => {
|
||||
indexedDBProvider.connect();
|
||||
});
|
||||
provider.connect();
|
||||
});
|
||||
provider.disconnect();
|
||||
indexedDBProvider.disconnect();
|
||||
}
|
||||
|
||||
await mutate(matcher => matcher === QueryKey.getWorkspaces);
|
||||
// refresh the local storage
|
||||
await AffinePlugin.CRUD.list();
|
||||
return id;
|
||||
},
|
||||
delete: async workspace => {
|
||||
const items = storage.getItem(AFFINE_STORAGE_KEY, []);
|
||||
if (
|
||||
Array.isArray(items) &&
|
||||
items.every(item => schema.safeParse(item).success)
|
||||
) {
|
||||
storage.setItem(
|
||||
AFFINE_STORAGE_KEY,
|
||||
items.filter(item => item.id !== workspace.id)
|
||||
);
|
||||
}
|
||||
await affineApis.deleteWorkspace({
|
||||
id: workspace.id,
|
||||
});
|
||||
await mutate(matcher => matcher === QueryKey.getWorkspaces);
|
||||
},
|
||||
get: async workspaceId => {
|
||||
// fixme(himself65): rewrite the auth logic
|
||||
try {
|
||||
const loginStorage = getLoginStorage();
|
||||
if (
|
||||
loginStorage == null ||
|
||||
isExpired(parseIdToken(loginStorage.token))
|
||||
) {
|
||||
rootStore.set(currentAffineUserAtom, null);
|
||||
storage.removeItem(AFFINE_STORAGE_KEY);
|
||||
cleanupWorkspace(WorkspaceFlavour.AFFINE);
|
||||
return null;
|
||||
}
|
||||
const workspaces: AffineLegacyCloudWorkspace[] =
|
||||
await AffinePlugin.CRUD.list();
|
||||
return (
|
||||
workspaces.find(workspace => workspace.id === workspaceId) ?? null
|
||||
);
|
||||
} catch (e) {
|
||||
const workspaces = getPersistenceAllWorkspace();
|
||||
return (
|
||||
workspaces.find(workspace => workspace.id === workspaceId) ?? null
|
||||
);
|
||||
}
|
||||
},
|
||||
list: async () => {
|
||||
const allWorkspaces = getPersistenceAllWorkspace();
|
||||
const loginStorage = getLoginStorage();
|
||||
// fixme(himself65): rewrite the auth logic
|
||||
try {
|
||||
if (
|
||||
loginStorage == null ||
|
||||
isExpired(parseIdToken(loginStorage.token))
|
||||
) {
|
||||
rootStore.set(currentAffineUserAtom, null);
|
||||
storage.removeItem(AFFINE_STORAGE_KEY);
|
||||
return [];
|
||||
}
|
||||
} catch (e) {
|
||||
storage.removeItem(AFFINE_STORAGE_KEY);
|
||||
return [];
|
||||
}
|
||||
try {
|
||||
const workspaces = await affineApis.getWorkspaces().then(workspaces => {
|
||||
return workspaces.map(workspace => {
|
||||
const blockSuiteWorkspace = createEmptyBlockSuiteWorkspace(
|
||||
workspace.id,
|
||||
WorkspaceFlavour.AFFINE,
|
||||
{
|
||||
workspaceApis: affineApis,
|
||||
}
|
||||
);
|
||||
const dump = workspaces.map(workspace => {
|
||||
return {
|
||||
id: workspace.id,
|
||||
type: workspace.type,
|
||||
public: workspace.public,
|
||||
permission: workspace.permission,
|
||||
} satisfies z.infer<typeof schema>;
|
||||
});
|
||||
const old = storage.getItem(AFFINE_STORAGE_KEY, []);
|
||||
if (
|
||||
Array.isArray(old) &&
|
||||
old.every(item => schema.safeParse(item).success)
|
||||
) {
|
||||
const data = [...dump];
|
||||
old.forEach((item: z.infer<typeof schema>) => {
|
||||
const has = dump.find(dump => dump.id === item.id);
|
||||
if (!has) {
|
||||
data.push(item);
|
||||
}
|
||||
});
|
||||
storage.setItem(AFFINE_STORAGE_KEY, [...data]);
|
||||
}
|
||||
|
||||
const affineWorkspace: AffineLegacyCloudWorkspace = {
|
||||
...workspace,
|
||||
flavour: WorkspaceFlavour.AFFINE,
|
||||
blockSuiteWorkspace,
|
||||
providers: [...createAffineProviders(blockSuiteWorkspace)],
|
||||
};
|
||||
return affineWorkspace;
|
||||
});
|
||||
});
|
||||
workspaces.forEach(workspace => {
|
||||
const idx = allWorkspaces.findIndex(({ id }) => id === workspace.id);
|
||||
if (idx !== -1) {
|
||||
allWorkspaces.splice(idx, 1, workspace);
|
||||
} else {
|
||||
allWorkspaces.push(workspace);
|
||||
}
|
||||
});
|
||||
|
||||
// only save data when login in
|
||||
const dump = allWorkspaces.map(workspace => {
|
||||
return {
|
||||
id: workspace.id,
|
||||
type: workspace.type,
|
||||
public: workspace.public,
|
||||
permission: workspace.permission,
|
||||
} satisfies z.infer<typeof schema>;
|
||||
});
|
||||
storage.setItem(AFFINE_STORAGE_KEY, [...dump]);
|
||||
} catch (e) {
|
||||
console.error('fetch affine workspaces failed', e);
|
||||
}
|
||||
return [...allWorkspaces];
|
||||
},
|
||||
},
|
||||
UI: {
|
||||
Provider: ({ children }) => {
|
||||
return (
|
||||
<Suspense fallback={<PageLoading />}>
|
||||
<AuthContext>{children}</AuthContext>
|
||||
</Suspense>
|
||||
);
|
||||
},
|
||||
PageDetail: ({ currentWorkspace, currentPageId }) => {
|
||||
const page = currentWorkspace.blockSuiteWorkspace.getPage(currentPageId);
|
||||
if (!page) {
|
||||
throw new PageNotFoundError(
|
||||
currentWorkspace.blockSuiteWorkspace,
|
||||
currentPageId
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<PageDetailEditor
|
||||
pageId={currentPageId}
|
||||
workspace={currentWorkspace}
|
||||
onInit={initPage}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
},
|
||||
PageList: ({ blockSuiteWorkspace, onOpenPage }) => {
|
||||
return (
|
||||
<BlockSuitePageList
|
||||
listType="all"
|
||||
onOpenPage={onOpenPage}
|
||||
blockSuiteWorkspace={blockSuiteWorkspace}
|
||||
/>
|
||||
);
|
||||
},
|
||||
SettingsDetail: ({
|
||||
currentWorkspace,
|
||||
onChangeTab,
|
||||
currentTab,
|
||||
onDeleteWorkspace,
|
||||
onTransformWorkspace,
|
||||
}) => {
|
||||
return (
|
||||
<WorkspaceSettingDetail
|
||||
onDeleteWorkspace={onDeleteWorkspace}
|
||||
onChangeTab={onChangeTab}
|
||||
currentTab={currentTab}
|
||||
workspace={currentWorkspace}
|
||||
onTransferWorkspace={onTransformWorkspace}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
129
apps/web/src/adapters/local/index.tsx
Normal file
129
apps/web/src/adapters/local/index.tsx
Normal file
@@ -0,0 +1,129 @@
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
import {
|
||||
DEFAULT_HELLO_WORLD_PAGE_ID,
|
||||
DEFAULT_WORKSPACE_NAME,
|
||||
} from '@affine/env';
|
||||
import { initPage } from '@affine/env/blocksuite';
|
||||
import { PageNotFoundError } from '@affine/env/constant';
|
||||
import {
|
||||
CRUD,
|
||||
saveWorkspaceToLocalStorage,
|
||||
} from '@affine/workspace/local/crud';
|
||||
import { createIndexedDBBackgroundProvider } from '@affine/workspace/providers';
|
||||
import {
|
||||
LoadPriority,
|
||||
ReleaseType,
|
||||
WorkspaceFlavour,
|
||||
} from '@affine/workspace/type';
|
||||
import { createEmptyBlockSuiteWorkspace } from '@affine/workspace/utils';
|
||||
import { nanoid } from '@blocksuite/store';
|
||||
import { lazy } from 'react';
|
||||
|
||||
import type { WorkspaceAdapter } from '../type';
|
||||
|
||||
const WorkspaceSettingDetail = lazy(() =>
|
||||
import('../../components/affine/workspace-setting-detail').then(
|
||||
({ WorkspaceSettingDetail }) => ({
|
||||
default: WorkspaceSettingDetail,
|
||||
})
|
||||
)
|
||||
);
|
||||
const BlockSuitePageList = lazy(() =>
|
||||
import('../../components/blocksuite/block-suite-page-list').then(
|
||||
({ BlockSuitePageList }) => ({
|
||||
default: BlockSuitePageList,
|
||||
})
|
||||
)
|
||||
);
|
||||
const PageDetailEditor = lazy(() =>
|
||||
import('../../components/page-detail-editor').then(
|
||||
({ PageDetailEditor }) => ({
|
||||
default: PageDetailEditor,
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
const logger = new DebugLogger('use-create-first-workspace');
|
||||
|
||||
export const LocalPlugin: WorkspaceAdapter<WorkspaceFlavour.LOCAL> = {
|
||||
releaseType: ReleaseType.STABLE,
|
||||
flavour: WorkspaceFlavour.LOCAL,
|
||||
loadPriority: LoadPriority.LOW,
|
||||
Events: {
|
||||
'app:init': () => {
|
||||
const blockSuiteWorkspace = createEmptyBlockSuiteWorkspace(
|
||||
nanoid(),
|
||||
WorkspaceFlavour.LOCAL
|
||||
);
|
||||
blockSuiteWorkspace.meta.setName(DEFAULT_WORKSPACE_NAME);
|
||||
const page = blockSuiteWorkspace.createPage({
|
||||
id: DEFAULT_HELLO_WORLD_PAGE_ID,
|
||||
});
|
||||
blockSuiteWorkspace.setPageMeta(page.id, {
|
||||
init: true,
|
||||
});
|
||||
initPage(page);
|
||||
blockSuiteWorkspace.setPageMeta(page.id, {
|
||||
jumpOnce: true,
|
||||
});
|
||||
const provider = createIndexedDBBackgroundProvider(blockSuiteWorkspace);
|
||||
provider.connect();
|
||||
provider.callbacks.add(() => {
|
||||
provider.disconnect();
|
||||
});
|
||||
saveWorkspaceToLocalStorage(blockSuiteWorkspace.id);
|
||||
logger.debug('create first workspace');
|
||||
return [blockSuiteWorkspace.id];
|
||||
},
|
||||
},
|
||||
CRUD,
|
||||
UI: {
|
||||
Provider: ({ children }) => {
|
||||
return <>{children}</>;
|
||||
},
|
||||
PageDetail: ({ currentWorkspace, currentPageId }) => {
|
||||
const page = currentWorkspace.blockSuiteWorkspace.getPage(currentPageId);
|
||||
if (!page) {
|
||||
throw new PageNotFoundError(
|
||||
currentWorkspace.blockSuiteWorkspace,
|
||||
currentPageId
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<PageDetailEditor
|
||||
pageId={currentPageId}
|
||||
onInit={initPage}
|
||||
workspace={currentWorkspace}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
},
|
||||
PageList: ({ blockSuiteWorkspace, onOpenPage }) => {
|
||||
return (
|
||||
<BlockSuitePageList
|
||||
listType="all"
|
||||
onOpenPage={onOpenPage}
|
||||
blockSuiteWorkspace={blockSuiteWorkspace}
|
||||
/>
|
||||
);
|
||||
},
|
||||
SettingsDetail: ({
|
||||
currentWorkspace,
|
||||
onChangeTab,
|
||||
currentTab,
|
||||
onDeleteWorkspace,
|
||||
onTransformWorkspace,
|
||||
}) => {
|
||||
return (
|
||||
<WorkspaceSettingDetail
|
||||
onDeleteWorkspace={onDeleteWorkspace}
|
||||
onChangeTab={onChangeTab}
|
||||
currentTab={currentTab}
|
||||
workspace={currentWorkspace}
|
||||
onTransferWorkspace={onTransformWorkspace}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
21
apps/web/src/adapters/type.tsx
Normal file
21
apps/web/src/adapters/type.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import type {
|
||||
AppEvents,
|
||||
WorkspaceCRUD,
|
||||
WorkspaceUISchema,
|
||||
} from '@affine/workspace/type';
|
||||
import type {
|
||||
LoadPriority,
|
||||
ReleaseType,
|
||||
WorkspaceFlavour,
|
||||
} from '@affine/workspace/type';
|
||||
|
||||
export interface WorkspaceAdapter<Flavour extends WorkspaceFlavour> {
|
||||
releaseType: ReleaseType;
|
||||
flavour: Flavour;
|
||||
// Plugin will be loaded according to the priority
|
||||
loadPriority: LoadPriority;
|
||||
Events: Partial<AppEvents>;
|
||||
// Fetch necessary data for the first render
|
||||
CRUD: WorkspaceCRUD<Flavour>;
|
||||
UI: WorkspaceUISchema<Flavour>;
|
||||
}
|
||||
61
apps/web/src/adapters/workspace.ts
Normal file
61
apps/web/src/adapters/workspace.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import type { AppEvents } from '@affine/workspace/type';
|
||||
import {
|
||||
LoadPriority,
|
||||
ReleaseType,
|
||||
WorkspaceFlavour,
|
||||
} from '@affine/workspace/type';
|
||||
|
||||
import { AffinePlugin } from './affine';
|
||||
import { LocalPlugin } from './local';
|
||||
import type { WorkspaceAdapter } from './type';
|
||||
|
||||
const unimplemented = () => {
|
||||
throw new Error('Not implemented');
|
||||
};
|
||||
|
||||
export const WorkspaceAdapters = {
|
||||
[WorkspaceFlavour.AFFINE]: AffinePlugin,
|
||||
[WorkspaceFlavour.LOCAL]: LocalPlugin,
|
||||
[WorkspaceFlavour.AFFINE_CLOUD]: {
|
||||
releaseType: ReleaseType.UNRELEASED,
|
||||
flavour: WorkspaceFlavour.AFFINE_CLOUD,
|
||||
loadPriority: LoadPriority.HIGH,
|
||||
Events: {} as Partial<AppEvents>,
|
||||
// todo: implement this
|
||||
CRUD: {
|
||||
get: unimplemented,
|
||||
list: unimplemented,
|
||||
delete: unimplemented,
|
||||
create: unimplemented,
|
||||
},
|
||||
// todo: implement this
|
||||
UI: {
|
||||
Provider: unimplemented,
|
||||
PageDetail: unimplemented,
|
||||
PageList: unimplemented,
|
||||
SettingsDetail: unimplemented,
|
||||
},
|
||||
},
|
||||
[WorkspaceFlavour.PUBLIC]: {
|
||||
releaseType: ReleaseType.UNRELEASED,
|
||||
flavour: WorkspaceFlavour.PUBLIC,
|
||||
loadPriority: LoadPriority.LOW,
|
||||
Events: {} as Partial<AppEvents>,
|
||||
// todo: implement this
|
||||
CRUD: {
|
||||
get: unimplemented,
|
||||
list: unimplemented,
|
||||
delete: unimplemented,
|
||||
create: unimplemented,
|
||||
},
|
||||
// todo: implement this
|
||||
UI: {
|
||||
Provider: unimplemented,
|
||||
PageDetail: unimplemented,
|
||||
PageList: unimplemented,
|
||||
SettingsDetail: unimplemented,
|
||||
},
|
||||
},
|
||||
} satisfies {
|
||||
[Key in WorkspaceFlavour]: WorkspaceAdapter<Key>;
|
||||
};
|
||||
Reference in New Issue
Block a user