mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-01 01:29:31 +08:00
refactor(infra): directory structure (#4615)
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* @vitest-environment happy-dom
|
||||
*/
|
||||
import 'fake-indexeddb/auto';
|
||||
|
||||
import type { WorkspaceCRUD } from '@affine/env/workspace';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { __unstableSchemas, AffineSchemas } from '@blocksuite/blocks/models';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { Schema, Workspace } from '@blocksuite/store';
|
||||
import { afterEach, assertType, describe, expect, test } from 'vitest';
|
||||
|
||||
import { CRUD } from '../crud';
|
||||
|
||||
const schema = new Schema();
|
||||
|
||||
schema.register(AffineSchemas).register(__unstableSchemas);
|
||||
|
||||
afterEach(() => {
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
describe('crud', () => {
|
||||
test('type', () => {
|
||||
assertType<WorkspaceCRUD<WorkspaceFlavour.LOCAL>>(CRUD);
|
||||
});
|
||||
|
||||
test('basic', async () => {
|
||||
const workspace = await CRUD.get('not_exist');
|
||||
expect(workspace).toBeNull();
|
||||
|
||||
expect(await CRUD.list()).toEqual([]);
|
||||
});
|
||||
|
||||
test('delete not exist', async () => {
|
||||
await expect(async () =>
|
||||
CRUD.delete(new Workspace({ id: 'test', schema }))
|
||||
).rejects.toThrowError();
|
||||
});
|
||||
|
||||
test('create & delete', async () => {
|
||||
const workspace = new Workspace({ id: 'test', schema });
|
||||
const page = workspace.createPage({ id: 'page0' });
|
||||
await page.waitForLoaded();
|
||||
const pageBlockId = page.addBlock('affine:page', {
|
||||
title: new page.Text(''),
|
||||
});
|
||||
page.addBlock('affine:surface', {}, pageBlockId);
|
||||
const frameId = page.addBlock('affine:note', {}, pageBlockId);
|
||||
page.addBlock('affine:paragraph', {}, frameId);
|
||||
|
||||
const id = await CRUD.create(workspace);
|
||||
const list = await CRUD.list();
|
||||
expect(list.length).toBe(1);
|
||||
expect(list[0].id).toBe(id);
|
||||
const localWorkspace = list.at(0);
|
||||
assertExists(localWorkspace);
|
||||
expect(localWorkspace.id).toBe(id);
|
||||
expect(localWorkspace.flavour).toBe(WorkspaceFlavour.LOCAL);
|
||||
expect(localWorkspace.blockSuiteWorkspace.doc.toJSON()).toEqual({
|
||||
meta: expect.anything(),
|
||||
spaces: expect.objectContaining({
|
||||
page0: expect.anything(),
|
||||
}),
|
||||
});
|
||||
|
||||
await CRUD.delete(localWorkspace.blockSuiteWorkspace);
|
||||
expect(await CRUD.get(id)).toBeNull();
|
||||
expect(await CRUD.list()).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,112 @@
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
import type { LocalWorkspace, WorkspaceCRUD } from '@affine/env/workspace';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { Workspace as BlockSuiteWorkspace } from '@blocksuite/store';
|
||||
import { createJSONStorage } from 'jotai/utils';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { getOrCreateWorkspace } from '../manager';
|
||||
|
||||
const getStorage = () => createJSONStorage(() => localStorage);
|
||||
|
||||
const kStoreKey = 'affine-local-workspace';
|
||||
const schema = z.array(z.string());
|
||||
|
||||
const logger = new DebugLogger('affine:workspace:local:crud');
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function saveWorkspaceToLocalStorage(workspaceId: string) {
|
||||
const storage = getStorage();
|
||||
!Array.isArray(storage.getItem(kStoreKey, [])) &&
|
||||
storage.setItem(kStoreKey, []);
|
||||
const data = storage.getItem(kStoreKey, []) as z.infer<typeof schema>;
|
||||
const id = data.find(id => id === workspaceId);
|
||||
if (!id) {
|
||||
logger.debug('saveWorkspaceToLocalStorage', workspaceId);
|
||||
storage.setItem(kStoreKey, [...data, workspaceId]);
|
||||
}
|
||||
}
|
||||
|
||||
export const CRUD: WorkspaceCRUD<WorkspaceFlavour.LOCAL> = {
|
||||
get: async workspaceId => {
|
||||
logger.debug('get', workspaceId);
|
||||
const storage = getStorage();
|
||||
!Array.isArray(storage.getItem(kStoreKey, [])) &&
|
||||
storage.setItem(kStoreKey, []);
|
||||
const data = storage.getItem(kStoreKey, []) as z.infer<typeof schema>;
|
||||
const id = data.find(id => id === workspaceId);
|
||||
if (!id) {
|
||||
return null;
|
||||
}
|
||||
const blockSuiteWorkspace = getOrCreateWorkspace(
|
||||
id,
|
||||
WorkspaceFlavour.LOCAL
|
||||
);
|
||||
const workspace: LocalWorkspace = {
|
||||
id,
|
||||
flavour: WorkspaceFlavour.LOCAL,
|
||||
blockSuiteWorkspace: blockSuiteWorkspace,
|
||||
};
|
||||
return workspace;
|
||||
},
|
||||
create: async ({ doc }) => {
|
||||
logger.debug('create', doc);
|
||||
const storage = getStorage();
|
||||
!Array.isArray(storage.getItem(kStoreKey, [])) &&
|
||||
storage.setItem(kStoreKey, []);
|
||||
const binary = BlockSuiteWorkspace.Y.encodeStateAsUpdate(doc);
|
||||
const id = nanoid();
|
||||
const blockSuiteWorkspace = getOrCreateWorkspace(
|
||||
id,
|
||||
WorkspaceFlavour.LOCAL
|
||||
);
|
||||
BlockSuiteWorkspace.Y.applyUpdate(blockSuiteWorkspace.doc, binary);
|
||||
|
||||
doc.getSubdocs().forEach(subdoc => {
|
||||
blockSuiteWorkspace.doc.getSubdocs().forEach(newDoc => {
|
||||
if (subdoc.guid === newDoc.guid) {
|
||||
BlockSuiteWorkspace.Y.applyUpdate(
|
||||
newDoc,
|
||||
BlockSuiteWorkspace.Y.encodeStateAsUpdate(subdoc)
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
// todo: do we need to persist doc to persistence datasource?
|
||||
saveWorkspaceToLocalStorage(id);
|
||||
return id;
|
||||
},
|
||||
delete: async workspace => {
|
||||
logger.debug('delete', workspace);
|
||||
const storage = getStorage();
|
||||
!Array.isArray(storage.getItem(kStoreKey, [])) &&
|
||||
storage.setItem(kStoreKey, []);
|
||||
const data = storage.getItem(kStoreKey, []) as z.infer<typeof schema>;
|
||||
const idx = data.findIndex(id => id === workspace.id);
|
||||
if (idx === -1) {
|
||||
throw new Error('workspace not found');
|
||||
}
|
||||
data.splice(idx, 1);
|
||||
storage.setItem(kStoreKey, [...data]);
|
||||
// flywire
|
||||
if (window.apis && environment.isDesktop) {
|
||||
await window.apis.workspace.delete(workspace.id);
|
||||
}
|
||||
},
|
||||
list: async () => {
|
||||
logger.debug('list');
|
||||
const storage = getStorage();
|
||||
const allWorkspaceIDs: string[] = storage.getItem(kStoreKey, []) as z.infer<
|
||||
typeof schema
|
||||
>;
|
||||
|
||||
const workspaces = (
|
||||
await Promise.all(allWorkspaceIDs.map(id => CRUD.get(id)))
|
||||
).filter(item => item !== null) as LocalWorkspace[];
|
||||
|
||||
return workspaces;
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user