mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-11 20:08:37 +00:00
feat: add bootstrap (#3299)
This commit is contained in:
@@ -10,6 +10,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@affine-test/fixtures": "workspace:*",
|
"@affine-test/fixtures": "workspace:*",
|
||||||
|
"@affine/bookmark-block": "workspace:*",
|
||||||
"@affine/component": "workspace:*",
|
"@affine/component": "workspace:*",
|
||||||
"@affine/debug": "workspace:*",
|
"@affine/debug": "workspace:*",
|
||||||
"@affine/env": "workspace:*",
|
"@affine/env": "workspace:*",
|
||||||
|
|||||||
132
apps/core/src/bootstrap/before-app.ts
Normal file
132
apps/core/src/bootstrap/before-app.ts
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
import { migrateToSubdoc } from '@affine/env/blocksuite';
|
||||||
|
import { setupGlobal } from '@affine/env/global';
|
||||||
|
import type {
|
||||||
|
LocalIndexedDBDownloadProvider,
|
||||||
|
WorkspaceAdapter,
|
||||||
|
} from '@affine/env/workspace';
|
||||||
|
import { WorkspaceFlavour, WorkspaceVersion } from '@affine/env/workspace';
|
||||||
|
import type { RootWorkspaceMetadata } from '@affine/workspace/atom';
|
||||||
|
import {
|
||||||
|
type RootWorkspaceMetadataV2,
|
||||||
|
rootWorkspacesMetadataAtom,
|
||||||
|
workspaceAdaptersAtom,
|
||||||
|
} from '@affine/workspace/atom';
|
||||||
|
import {
|
||||||
|
migrateLocalBlobStorage,
|
||||||
|
upgradeV1ToV2,
|
||||||
|
} from '@affine/workspace/migration';
|
||||||
|
import { createIndexedDBDownloadProvider } from '@affine/workspace/providers';
|
||||||
|
import { assertExists } from '@blocksuite/global/utils';
|
||||||
|
import { rootStore } from '@toeverything/plugin-infra/manager';
|
||||||
|
|
||||||
|
import { WorkspaceAdapters } from '../adapters/workspace';
|
||||||
|
|
||||||
|
setupGlobal();
|
||||||
|
|
||||||
|
rootStore.set(
|
||||||
|
workspaceAdaptersAtom,
|
||||||
|
WorkspaceAdapters as Record<
|
||||||
|
WorkspaceFlavour,
|
||||||
|
WorkspaceAdapter<WorkspaceFlavour>
|
||||||
|
>
|
||||||
|
);
|
||||||
|
|
||||||
|
const value = localStorage.getItem('jotai-workspaces');
|
||||||
|
if (value) {
|
||||||
|
try {
|
||||||
|
const metadata = JSON.parse(value) as RootWorkspaceMetadata[];
|
||||||
|
const promises: Promise<void>[] = [];
|
||||||
|
const newMetadata = [...metadata];
|
||||||
|
metadata.forEach(oldMeta => {
|
||||||
|
if (!('version' in oldMeta)) {
|
||||||
|
const adapter = WorkspaceAdapters[oldMeta.flavour];
|
||||||
|
assertExists(adapter);
|
||||||
|
const upgrade = async () => {
|
||||||
|
const workspace = await adapter.CRUD.get(oldMeta.id);
|
||||||
|
if (!workspace) {
|
||||||
|
console.warn('cannot find workspace', oldMeta.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (workspace.flavour !== WorkspaceFlavour.LOCAL) {
|
||||||
|
console.warn('not supported');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const doc = workspace.blockSuiteWorkspace.doc;
|
||||||
|
const provider = createIndexedDBDownloadProvider(workspace.id, doc, {
|
||||||
|
awareness: workspace.blockSuiteWorkspace.awarenessStore.awareness,
|
||||||
|
}) as LocalIndexedDBDownloadProvider;
|
||||||
|
provider.sync();
|
||||||
|
await provider.whenReady;
|
||||||
|
const newDoc = migrateToSubdoc(doc);
|
||||||
|
if (doc === newDoc) {
|
||||||
|
console.log('doc not changed');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const newWorkspace = upgradeV1ToV2(workspace);
|
||||||
|
|
||||||
|
const newId = await adapter.CRUD.create(
|
||||||
|
newWorkspace.blockSuiteWorkspace
|
||||||
|
);
|
||||||
|
|
||||||
|
await adapter.CRUD.delete(workspace as any);
|
||||||
|
console.log('migrated', oldMeta.id, newId);
|
||||||
|
const index = newMetadata.findIndex(meta => meta.id === oldMeta.id);
|
||||||
|
newMetadata[index] = {
|
||||||
|
...oldMeta,
|
||||||
|
id: newId,
|
||||||
|
version: WorkspaceVersion.SubDoc,
|
||||||
|
};
|
||||||
|
await migrateLocalBlobStorage(workspace.id, newId);
|
||||||
|
};
|
||||||
|
|
||||||
|
// create a new workspace and push it to metadata
|
||||||
|
promises.push(upgrade());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(promises)
|
||||||
|
.then(() => {
|
||||||
|
console.log('migration done');
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
console.error('migration failed');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
localStorage.setItem('jotai-workspaces', JSON.stringify(newMetadata));
|
||||||
|
window.dispatchEvent(new CustomEvent('migration-done'));
|
||||||
|
window.$migrationDone = true;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error('error when migrating data', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createFirst = (): RootWorkspaceMetadataV2[] => {
|
||||||
|
const Plugins = Object.values(WorkspaceAdapters).sort(
|
||||||
|
(a, b) => a.loadPriority - b.loadPriority
|
||||||
|
);
|
||||||
|
|
||||||
|
return Plugins.flatMap(Plugin => {
|
||||||
|
return Plugin.Events['app:init']?.().map(
|
||||||
|
id =>
|
||||||
|
({
|
||||||
|
id,
|
||||||
|
flavour: Plugin.flavour,
|
||||||
|
// new workspace should all support sub-doc feature
|
||||||
|
version: WorkspaceVersion.SubDoc,
|
||||||
|
}) satisfies RootWorkspaceMetadataV2
|
||||||
|
);
|
||||||
|
}).filter((ids): ids is RootWorkspaceMetadataV2 => !!ids);
|
||||||
|
};
|
||||||
|
|
||||||
|
await rootStore
|
||||||
|
.get(rootWorkspacesMetadataAtom)
|
||||||
|
.then(meta => {
|
||||||
|
if (meta.length === 0 && localStorage.getItem('is-first-open') === null) {
|
||||||
|
const result = createFirst();
|
||||||
|
console.info('create first workspace', result);
|
||||||
|
localStorage.setItem('is-first-open', 'false');
|
||||||
|
rootStore.set(rootWorkspacesMetadataAtom, result).catch(console.error);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
3
apps/core/src/bootstrap/register-plugins.ts
Normal file
3
apps/core/src/bootstrap/register-plugins.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import('@affine/bookmark-block');
|
||||||
|
|
||||||
|
export {};
|
||||||
@@ -1,106 +1,14 @@
|
|||||||
import { migrateToSubdoc } from '@affine/env/blocksuite';
|
|
||||||
import { setupGlobal } from '@affine/env/global';
|
|
||||||
import type { LocalIndexedDBDownloadProvider } from '@affine/env/workspace';
|
|
||||||
import { WorkspaceFlavour, WorkspaceVersion } from '@affine/env/workspace';
|
|
||||||
import { type WorkspaceAdapter } from '@affine/env/workspace';
|
|
||||||
import type { RootWorkspaceMetadata } from '@affine/workspace/atom';
|
|
||||||
import { workspaceAdaptersAtom } from '@affine/workspace/atom';
|
|
||||||
import {
|
|
||||||
migrateLocalBlobStorage,
|
|
||||||
upgradeV1ToV2,
|
|
||||||
} from '@affine/workspace/migration';
|
|
||||||
import { createIndexedDBDownloadProvider } from '@affine/workspace/providers';
|
|
||||||
import { assertExists } from '@blocksuite/global/utils';
|
import { assertExists } from '@blocksuite/global/utils';
|
||||||
import { rootStore } from '@toeverything/plugin-infra/manager';
|
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
|
|
||||||
import { WorkspaceAdapters } from './adapters/workspace';
|
async function main() {
|
||||||
|
await import('./bootstrap/before-app');
|
||||||
// bootstrap
|
const { App } = await import('./app');
|
||||||
setupGlobal();
|
|
||||||
|
|
||||||
rootStore.set(
|
|
||||||
workspaceAdaptersAtom,
|
|
||||||
WorkspaceAdapters as Record<
|
|
||||||
WorkspaceFlavour,
|
|
||||||
WorkspaceAdapter<WorkspaceFlavour>
|
|
||||||
>
|
|
||||||
);
|
|
||||||
|
|
||||||
const value = localStorage.getItem('jotai-workspaces');
|
|
||||||
if (value) {
|
|
||||||
try {
|
|
||||||
const metadata = JSON.parse(value) as RootWorkspaceMetadata[];
|
|
||||||
const promises: Promise<void>[] = [];
|
|
||||||
const newMetadata = [...metadata];
|
|
||||||
metadata.forEach(oldMeta => {
|
|
||||||
if (!('version' in oldMeta)) {
|
|
||||||
const adapter = WorkspaceAdapters[oldMeta.flavour];
|
|
||||||
assertExists(adapter);
|
|
||||||
const upgrade = async () => {
|
|
||||||
const workspace = await adapter.CRUD.get(oldMeta.id);
|
|
||||||
if (!workspace) {
|
|
||||||
console.warn('cannot find workspace', oldMeta.id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (workspace.flavour !== WorkspaceFlavour.LOCAL) {
|
|
||||||
console.warn('not supported');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const doc = workspace.blockSuiteWorkspace.doc;
|
|
||||||
const provider = createIndexedDBDownloadProvider(workspace.id, doc, {
|
|
||||||
awareness: workspace.blockSuiteWorkspace.awarenessStore.awareness,
|
|
||||||
}) as LocalIndexedDBDownloadProvider;
|
|
||||||
provider.sync();
|
|
||||||
await provider.whenReady;
|
|
||||||
const newDoc = migrateToSubdoc(doc);
|
|
||||||
if (doc === newDoc) {
|
|
||||||
console.log('doc not changed');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const newWorkspace = upgradeV1ToV2(workspace);
|
|
||||||
|
|
||||||
const newId = await adapter.CRUD.create(
|
|
||||||
newWorkspace.blockSuiteWorkspace
|
|
||||||
);
|
|
||||||
|
|
||||||
await adapter.CRUD.delete(workspace as any);
|
|
||||||
console.log('migrated', oldMeta.id, newId);
|
|
||||||
const index = newMetadata.findIndex(meta => meta.id === oldMeta.id);
|
|
||||||
newMetadata[index] = {
|
|
||||||
...oldMeta,
|
|
||||||
id: newId,
|
|
||||||
version: WorkspaceVersion.SubDoc,
|
|
||||||
};
|
|
||||||
await migrateLocalBlobStorage(workspace.id, newId);
|
|
||||||
};
|
|
||||||
|
|
||||||
// create a new workspace and push it to metadata
|
|
||||||
promises.push(upgrade());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Promise.all(promises)
|
|
||||||
.then(() => {
|
|
||||||
console.log('migration done');
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
console.error('migration failed');
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
localStorage.setItem('jotai-workspaces', JSON.stringify(newMetadata));
|
|
||||||
window.dispatchEvent(new CustomEvent('migration-done'));
|
|
||||||
window.$migrationDone = true;
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.error('error when migrating data', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// start app
|
|
||||||
import('./app').then(({ App }) => {
|
|
||||||
const root = document.getElementById('app');
|
const root = document.getElementById('app');
|
||||||
assertExists(root);
|
assertExists(root);
|
||||||
|
|
||||||
createRoot(root).render(<App />);
|
createRoot(root).render(<App />);
|
||||||
});
|
await import('./bootstrap/register-plugins');
|
||||||
|
}
|
||||||
|
|
||||||
|
await main();
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
import { DebugLogger } from '@affine/debug';
|
import { DebugLogger } from '@affine/debug';
|
||||||
import { WorkspaceSubPath, WorkspaceVersion } from '@affine/env/workspace';
|
import { WorkspaceSubPath } from '@affine/env/workspace';
|
||||||
import {
|
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||||
type RootWorkspaceMetadataV2,
|
|
||||||
rootWorkspacesMetadataAtom,
|
|
||||||
} from '@affine/workspace/atom';
|
|
||||||
import { getWorkspace } from '@toeverything/plugin-infra/__internal__/workspace';
|
import { getWorkspace } from '@toeverything/plugin-infra/__internal__/workspace';
|
||||||
import { rootStore } from '@toeverything/plugin-infra/manager';
|
|
||||||
import { useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
import { lazy, useEffect, useRef } from 'react';
|
import { lazy, useEffect, useRef } from 'react';
|
||||||
|
|
||||||
import { WorkspaceAdapters } from '../adapters/workspace';
|
|
||||||
import { RouteLogic, useNavigateHelper } from '../hooks/use-navigate-helper';
|
import { RouteLogic, useNavigateHelper } from '../hooks/use-navigate-helper';
|
||||||
import { useWorkspace } from '../hooks/use-workspace';
|
import { useWorkspace } from '../hooks/use-workspace';
|
||||||
|
|
||||||
@@ -23,36 +18,6 @@ type WorkspaceLoaderProps = {
|
|||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const createFirst = (): RootWorkspaceMetadataV2[] => {
|
|
||||||
const Plugins = Object.values(WorkspaceAdapters).sort(
|
|
||||||
(a, b) => a.loadPriority - b.loadPriority
|
|
||||||
);
|
|
||||||
|
|
||||||
return Plugins.flatMap(Plugin => {
|
|
||||||
return Plugin.Events['app:init']?.().map(
|
|
||||||
id =>
|
|
||||||
({
|
|
||||||
id,
|
|
||||||
flavour: Plugin.flavour,
|
|
||||||
// new workspace should all support sub-doc feature
|
|
||||||
version: WorkspaceVersion.SubDoc,
|
|
||||||
}) satisfies RootWorkspaceMetadataV2
|
|
||||||
);
|
|
||||||
}).filter((ids): ids is RootWorkspaceMetadataV2 => !!ids);
|
|
||||||
};
|
|
||||||
|
|
||||||
rootStore
|
|
||||||
.get(rootWorkspacesMetadataAtom)
|
|
||||||
.then(meta => {
|
|
||||||
if (meta.length === 0 && localStorage.getItem('is-first-open') === null) {
|
|
||||||
const result = createFirst();
|
|
||||||
console.info('create first workspace', result);
|
|
||||||
localStorage.setItem('is-first-open', 'false');
|
|
||||||
rootStore.set(rootWorkspacesMetadataAtom, result).catch(console.error);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(console.error);
|
|
||||||
|
|
||||||
const WorkspaceLoader = (props: WorkspaceLoaderProps): null => {
|
const WorkspaceLoader = (props: WorkspaceLoaderProps): null => {
|
||||||
useWorkspace(props.id);
|
useWorkspace(props.id);
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
"@radix-ui/react-avatar": "^1.0.3",
|
"@radix-ui/react-avatar": "^1.0.3",
|
||||||
"@radix-ui/react-collapsible": "^1.0.3",
|
"@radix-ui/react-collapsible": "^1.0.3",
|
||||||
"@radix-ui/react-radio-group": "^1.1.3",
|
"@radix-ui/react-radio-group": "^1.1.3",
|
||||||
|
"@radix-ui/react-scroll-area": "^1.0.4",
|
||||||
"@radix-ui/react-toast": "^1.1.4",
|
"@radix-ui/react-toast": "^1.1.4",
|
||||||
"@toeverything/hooks": "workspace:*",
|
"@toeverything/hooks": "workspace:*",
|
||||||
"@toeverything/plugin-infra": "workspace:*",
|
"@toeverything/plugin-infra": "workspace:*",
|
||||||
|
|||||||
41
yarn.lock
41
yarn.lock
@@ -64,7 +64,7 @@ __metadata:
|
|||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
"@affine/bookmark-block@workspace:plugins/bookmark-block":
|
"@affine/bookmark-block@workspace:*, @affine/bookmark-block@workspace:plugins/bookmark-block":
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@affine/bookmark-block@workspace:plugins/bookmark-block"
|
resolution: "@affine/bookmark-block@workspace:plugins/bookmark-block"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -123,6 +123,7 @@ __metadata:
|
|||||||
"@radix-ui/react-avatar": ^1.0.3
|
"@radix-ui/react-avatar": ^1.0.3
|
||||||
"@radix-ui/react-collapsible": ^1.0.3
|
"@radix-ui/react-collapsible": ^1.0.3
|
||||||
"@radix-ui/react-radio-group": ^1.1.3
|
"@radix-ui/react-radio-group": ^1.1.3
|
||||||
|
"@radix-ui/react-scroll-area": ^1.0.4
|
||||||
"@radix-ui/react-toast": ^1.1.4
|
"@radix-ui/react-toast": ^1.1.4
|
||||||
"@toeverything/hooks": "workspace:*"
|
"@toeverything/hooks": "workspace:*"
|
||||||
"@toeverything/plugin-infra": "workspace:*"
|
"@toeverything/plugin-infra": "workspace:*"
|
||||||
@@ -186,6 +187,7 @@ __metadata:
|
|||||||
resolution: "@affine/core@workspace:apps/core"
|
resolution: "@affine/core@workspace:apps/core"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@affine-test/fixtures": "workspace:*"
|
"@affine-test/fixtures": "workspace:*"
|
||||||
|
"@affine/bookmark-block": "workspace:*"
|
||||||
"@affine/component": "workspace:*"
|
"@affine/component": "workspace:*"
|
||||||
"@affine/debug": "workspace:*"
|
"@affine/debug": "workspace:*"
|
||||||
"@affine/env": "workspace:*"
|
"@affine/env": "workspace:*"
|
||||||
@@ -8222,6 +8224,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@radix-ui/number@npm:1.0.1":
|
||||||
|
version: 1.0.1
|
||||||
|
resolution: "@radix-ui/number@npm:1.0.1"
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime": ^7.13.10
|
||||||
|
checksum: 621ea8b7d4195d1a65a9c0aee918e8335e7f198088eec91577512c89c2ba3a3bab4a767cfb872a2b9c3092a78ff41cad9a924845a939f6bb87fe9356241ea0ea
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@radix-ui/primitive@npm:1.0.0":
|
"@radix-ui/primitive@npm:1.0.0":
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
resolution: "@radix-ui/primitive@npm:1.0.0"
|
resolution: "@radix-ui/primitive@npm:1.0.0"
|
||||||
@@ -8704,6 +8715,34 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@radix-ui/react-scroll-area@npm:^1.0.4":
|
||||||
|
version: 1.0.4
|
||||||
|
resolution: "@radix-ui/react-scroll-area@npm:1.0.4"
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime": ^7.13.10
|
||||||
|
"@radix-ui/number": 1.0.1
|
||||||
|
"@radix-ui/primitive": 1.0.1
|
||||||
|
"@radix-ui/react-compose-refs": 1.0.1
|
||||||
|
"@radix-ui/react-context": 1.0.1
|
||||||
|
"@radix-ui/react-direction": 1.0.1
|
||||||
|
"@radix-ui/react-presence": 1.0.1
|
||||||
|
"@radix-ui/react-primitive": 1.0.3
|
||||||
|
"@radix-ui/react-use-callback-ref": 1.0.1
|
||||||
|
"@radix-ui/react-use-layout-effect": 1.0.1
|
||||||
|
peerDependencies:
|
||||||
|
"@types/react": "*"
|
||||||
|
"@types/react-dom": "*"
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
"@types/react":
|
||||||
|
optional: true
|
||||||
|
"@types/react-dom":
|
||||||
|
optional: true
|
||||||
|
checksum: f959006a731806f3046652b318b9a5dda03c8433832a33c3e29102ec0c9ce84c000060d4b5409a159c203276032cecd34ed88e7250ea70764ad8a67447415bf1
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@radix-ui/react-slot@npm:1.0.0":
|
"@radix-ui/react-slot@npm:1.0.0":
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
resolution: "@radix-ui/react-slot@npm:1.0.0"
|
resolution: "@radix-ui/react-slot@npm:1.0.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user