mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-11 20:08:37 +00:00
feat(core): use zip snapshot for onboarding page (#6495)
This commit is contained in:
@@ -1,17 +1,4 @@
|
||||
import type { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import type {
|
||||
CollectionInfoSnapshot,
|
||||
Doc,
|
||||
DocSnapshot,
|
||||
JobMiddleware,
|
||||
} 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';
|
||||
import type { Doc } from '@blocksuite/store';
|
||||
|
||||
export function initEmptyPage(page: Doc, title?: string) {
|
||||
page.load(() => {
|
||||
@@ -38,108 +25,3 @@ export function initEmptyPage(page: Doc, title?: string) {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (docCollection, blobStorage) => {
|
||||
docCollection.meta.setName(workspaceName);
|
||||
const { onboarding } = await import('@affine/templates');
|
||||
|
||||
const info = onboarding['info.json'] as CollectionInfoSnapshot;
|
||||
const blob = onboarding['blob.json'] as { [key: string]: string };
|
||||
|
||||
const migrationMiddleware: JobMiddleware = ({ slots, collection }) => {
|
||||
slots.afterImport.on(payload => {
|
||||
if (payload.type === 'page') {
|
||||
collection.schema.upgradeDoc(
|
||||
info?.pageVersion ?? 0,
|
||||
{},
|
||||
payload.page.spaceDoc
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const job = new Job({
|
||||
collection: docCollection,
|
||||
middlewares: [replaceIdMiddleware, migrationMiddleware],
|
||||
});
|
||||
|
||||
job.snapshotToCollectionInfo(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 docSnapshots: DocSnapshot[] = Object.entries(onboarding)
|
||||
.filter(([key]) => {
|
||||
return key.endsWith('snapshot.json');
|
||||
})
|
||||
.map(([_, value]) => value as unknown as DocSnapshot);
|
||||
|
||||
await Promise.all(
|
||||
docSnapshots.map(snapshot => {
|
||||
return job.snapshotToDoc(snapshot);
|
||||
})
|
||||
);
|
||||
|
||||
const newVersions = getLatestVersions(docCollection.schema);
|
||||
docCollection.doc
|
||||
.getMap('meta')
|
||||
.set('blockVersions', new YMap(Object.entries(newVersions)));
|
||||
|
||||
for (const [key, base64] of Object.entries(blob)) {
|
||||
await blobStorage.set(key, new Blob([base64ToUint8Array(base64)]));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const { workspace, release } = workspaceManager.open(meta);
|
||||
|
||||
await workspace.engine.waitForRootDocReady();
|
||||
|
||||
const pageRecordList = workspace.services.get(PageRecordList);
|
||||
|
||||
// todo: find better way to do the following
|
||||
// perhaps put them into middleware?
|
||||
{
|
||||
// the "Write, Draw, Plan all at Once." page should be set to edgeless mode
|
||||
const edgelessPage1 = pageRecordList.records$.value.find(
|
||||
p => p.title$.value === 'Write, Draw, Plan all at Once.'
|
||||
);
|
||||
|
||||
if (edgelessPage1) {
|
||||
edgelessPage1.setMode('edgeless');
|
||||
}
|
||||
|
||||
// should jump to "Write, Draw, Plan all at Once." by default
|
||||
const defaultPage = pageRecordList.records$.value.find(p =>
|
||||
p.title$.value.startsWith('Write, Draw, Plan all at Once.')
|
||||
);
|
||||
|
||||
if (defaultPage) {
|
||||
defaultPage.setMeta({
|
||||
jumpOnce: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
release();
|
||||
return meta;
|
||||
}
|
||||
|
||||
function base64ToUint8Array(base64: string) {
|
||||
const binaryString = atob(base64);
|
||||
const binaryArray = binaryString.split('').map(function (char) {
|
||||
return char.charCodeAt(0);
|
||||
});
|
||||
return new Uint8Array(binaryArray);
|
||||
}
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
/* 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, collection }) => {
|
||||
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 = collection.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 = collection.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 = collection.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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -57,6 +57,13 @@ export class TestingLocalWorkspaceListProvider
|
||||
id: id,
|
||||
idGenerator: () => nanoid(),
|
||||
schema: globalBlockSuiteSchema,
|
||||
blobStorages: [
|
||||
() => {
|
||||
return {
|
||||
crud: blobStorage,
|
||||
};
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// apply initial state
|
||||
|
||||
Reference in New Issue
Block a user