refactor(core): refactor atom to use di (#5831)

To support multiple instances, this PR removes some atoms and implements them using the new DI system.

removed atom

- `pageSettingsAtom`
- `currentPageIdAtom`
- `currentModeAtom`
This commit is contained in:
EYHN
2024-02-27 03:50:53 +00:00
parent 0dabb08217
commit ad9b0303c4
60 changed files with 602 additions and 626 deletions

View File

@@ -0,0 +1,117 @@
import type { WorkspaceFlavour } from '@affine/env/workspace';
import type {
JobMiddleware,
Page,
PageSnapshot,
WorkspaceInfoSnapshot,
} from '@blocksuite/store';
import { Job } from '@blocksuite/store';
import { Map as YMap } from 'yjs';
import { getLatestVersions } from '../blocksuite/migration/blocksuite';
import { PageRecordList } from '../page';
import type { WorkspaceManager } from '../workspace';
import { replaceIdMiddleware } from './middleware';
export function initEmptyPage(page: Page, title?: string) {
page.load(() => {
const pageBlockId = page.addBlock('affine:page', {
title: new page.Text(title ?? ''),
});
page.addBlock('affine:surface', {}, pageBlockId);
const noteBlockId = page.addBlock('affine:note', {}, pageBlockId);
page.addBlock('affine:paragraph', {}, noteBlockId);
});
}
/**
* FIXME: Use exported json data to instead of building data.
*/
export async function buildShowcaseWorkspace(
workspaceManager: WorkspaceManager,
flavour: WorkspaceFlavour,
workspaceName: string
) {
const meta = await workspaceManager.createWorkspace(
flavour,
async blockSuiteWorkspace => {
blockSuiteWorkspace.meta.setName(workspaceName);
const { onboarding } = await import('@affine/templates');
const info = onboarding['info.json'] as WorkspaceInfoSnapshot;
const migrationMiddleware: JobMiddleware = ({ slots, workspace }) => {
slots.afterImport.on(payload => {
if (payload.type === 'page') {
workspace.schema.upgradePage(
info?.pageVersion ?? 0,
{},
payload.page.spaceDoc
);
}
});
};
const job = new Job({
workspace: blockSuiteWorkspace,
middlewares: [replaceIdMiddleware, migrationMiddleware],
});
job.snapshotToWorkspaceInfo(info);
// for now all onboarding assets are considered served via CDN
// hack assets so that every blob exists
// @ts-expect-error - rethinking API
job._assetsManager.writeToBlob = async () => {};
const pageSnapshots: PageSnapshot[] = Object.entries(onboarding)
.filter(([key]) => {
return key.endsWith('snapshot.json');
})
.map(([_, value]) => value as unknown as PageSnapshot);
await Promise.all(
pageSnapshots.map(snapshot => {
return job.snapshotToPage(snapshot);
})
);
const newVersions = getLatestVersions(blockSuiteWorkspace.schema);
blockSuiteWorkspace.doc
.getMap('meta')
.set('blockVersions', new YMap(Object.entries(newVersions)));
}
);
const { workspace, release } = workspaceManager.open(meta);
await workspace.engine.sync.waitForLoadedRootDoc();
const pageRecordList = workspace.services.get(PageRecordList);
// todo: find better way to do the following
// perhaps put them into middleware?
{
// the "AFFiNE - not just a note-taking app" page should be set to edgeless mode
const edgelessPage1 = pageRecordList.records.value.find(
p => p.title.value === 'AFFiNE - not just a note-taking app'
);
if (edgelessPage1) {
edgelessPage1.setMode('edgeless');
}
// should jump to "AFFiNE - not just a note-taking app" by default
const defaultPage = pageRecordList.records.value.find(p =>
p.title.value.startsWith('AFFiNE - not just a note-taking app')
);
if (defaultPage) {
defaultPage.setMeta({
jumpOnce: true,
});
}
}
release();
return meta;
}

View File

@@ -0,0 +1,142 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable @typescript-eslint/no-restricted-imports */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
// @ts-nocheck
// TODO: remove this file after blocksuite exposed it
import type {
DatabaseBlockModel,
ListBlockModel,
ParagraphBlockModel,
} from '@blocksuite/blocks/dist/models.js';
import { assertExists } from '@blocksuite/global/utils';
import type { DeltaOperation, JobMiddleware } from '@blocksuite/store';
export const replaceIdMiddleware: JobMiddleware = ({ slots, workspace }) => {
const idMap = new Map<string, string>();
slots.afterImport.on(payload => {
if (
payload.type === 'block' &&
payload.snapshot.flavour === 'affine:database'
) {
const model = payload.model as DatabaseBlockModel;
Object.keys(model.cells).forEach(cellId => {
if (idMap.has(cellId)) {
model.cells[idMap.get(cellId)!] = model.cells[cellId];
delete model.cells[cellId];
}
});
}
// replace LinkedPage pageId with new id in paragraph blocks
if (
payload.type === 'block' &&
['affine:paragraph', 'affine:list'].includes(payload.snapshot.flavour)
) {
const model = payload.model as ParagraphBlockModel | ListBlockModel;
let prev = 0;
const delta: DeltaOperation[] = [];
for (const d of model.text.toDelta()) {
if (d.attributes?.reference?.pageId) {
if (prev > 0) {
delta.push({ retain: prev });
}
delta.push({
retain: d.insert.length,
attributes: {
reference: {
...d.attributes.reference,
pageId: idMap.get(d.attributes.reference.pageId)!,
},
},
});
prev = 0;
} else {
prev += d.insert.length;
}
}
if (delta.length > 0) {
model.text.applyDelta(delta);
}
}
});
slots.beforeImport.on(payload => {
if (payload.type === 'page') {
const newId = workspace.idGenerator('page');
idMap.set(payload.snapshot.meta.id, newId);
payload.snapshot.meta.id = newId;
return;
}
if (payload.type === 'block') {
const { snapshot } = payload;
if (snapshot.flavour === 'affine:page') {
const index = snapshot.children.findIndex(
c => c.flavour === 'affine:surface'
);
if (index !== -1) {
const [surface] = snapshot.children.splice(index, 1);
snapshot.children.push(surface);
}
}
const original = snapshot.id;
let newId: string;
if (idMap.has(original)) {
newId = idMap.get(original)!;
} else {
newId = workspace.idGenerator('block');
idMap.set(original, newId);
}
snapshot.id = newId;
if (snapshot.flavour === 'affine:surface') {
// Generate new IDs for images and frames in advance.
snapshot.children.forEach(child => {
const original = child.id;
if (idMap.has(original)) {
newId = idMap.get(original)!;
} else {
newId = workspace.idGenerator('block');
idMap.set(original, newId);
}
});
Object.entries(
snapshot.props.elements as Record<string, Record<string, unknown>>
).forEach(([_, value]) => {
switch (value.type) {
case 'connector': {
let connection = value.source as Record<string, string>;
if (idMap.has(connection.id)) {
const newId = idMap.get(connection.id);
assertExists(newId, 'reference id must exist');
connection.id = newId;
}
connection = value.target as Record<string, string>;
if (idMap.has(connection.id)) {
const newId = idMap.get(connection.id);
assertExists(newId, 'reference id must exist');
connection.id = newId;
}
break;
}
case 'group': {
const json = value.children.json as Record<string, unknown>;
Object.entries(json).forEach(([key, value]) => {
if (idMap.has(key)) {
delete json[key];
const newKey = idMap.get(key);
assertExists(newKey, 'reference id must exist');
json[newKey] = value;
}
});
break;
}
default:
break;
}
});
}
}
});
};