refactor: workspace crud

This commit is contained in:
alt0
2023-01-09 00:41:29 +08:00
parent d75e5fff92
commit 055b63382b
7 changed files with 335 additions and 167 deletions

View File

@@ -1,4 +1,5 @@
import { Workspaces } from './workspaces'; import { Workspaces } from './workspaces';
import type { WorkspacesChangeEvent } from './workspaces';
import { Workspace } from '@blocksuite/store'; import { Workspace } from '@blocksuite/store';
import { BaseProvider } from './provider/base'; import { BaseProvider } from './provider/base';
import { LocalProvider } from './provider/local/local'; import { LocalProvider } from './provider/local/local';
@@ -14,9 +15,10 @@ import { applyUpdate, encodeStateAsUpdate } from 'yjs';
* @classdesc DataCenter is a data center, it can manage different providers for business * @classdesc DataCenter is a data center, it can manage different providers for business
*/ */
export class DataCenter { export class DataCenter {
private readonly workspaces = new Workspaces(this); private readonly _workspaces = new Workspaces();
private currentWorkspace: Workspace | null = null; private currentWorkspace: Workspace | null = null;
private readonly _logger = getLogger('dc'); private readonly _logger = getLogger('dc');
private _defaultProvider?: BaseProvider;
providerMap: Map<string, BaseProvider> = new Map(); providerMap: Map<string, BaseProvider> = new Map();
constructor(debug: boolean) { constructor(debug: boolean) {
@@ -28,32 +30,43 @@ export class DataCenter {
// TODO: switch different provider // TODO: switch different provider
dc.registerProvider(new LocalProvider()); dc.registerProvider(new LocalProvider());
dc.registerProvider(new AffineProvider()); dc.registerProvider(new AffineProvider());
dc.workspaces.init();
return dc; return dc;
} }
/**
* Register provider.
* We will automatically set the first provider to default provider.
*/
registerProvider(provider: BaseProvider) { registerProvider(provider: BaseProvider) {
if (!this._defaultProvider) {
this._defaultProvider = provider;
}
// inject data in provider // inject data in provider
provider.inject({ logger: this._logger, workspaces: this.workspaces }); provider.inject({
logger: this._logger,
workspaces: this._workspaces.createScope(),
});
provider.init(); provider.init();
this.providerMap.set(provider.id, provider); this.providerMap.set(provider.id, provider);
} }
setDefaultProvider(providerId: string) {
this._defaultProvider = this.providerMap.get(providerId);
}
get providers() { get providers() {
return Array.from(this.providerMap.values()); return Array.from(this.providerMap.values());
} }
/** public get workspaces() {
* get workspaces list if focusUpdate is true, it will refresh workspaces return this._workspaces.workspaces;
* @param {string} name workspace name
* @returns {Promise<WS[]>}
*/
public async getWorkspaces(focusUpdate = false) {
if (focusUpdate) {
this.workspaces.refreshWorkspaces();
} }
return this.workspaces.workspaces;
public async refreshWorkspaces() {
return Promise.allSettled(
Object.values(this.providerMap).map(provider => provider.loadWorkspaces())
);
} }
/** /**
@@ -61,11 +74,16 @@ export class DataCenter {
* @param {string} name workspace name * @param {string} name workspace name
* @returns {Promise<WS>} * @returns {Promise<WS>}
*/ */
public async createWorkspace(name: string) { public async createWorkspace(workspaceMeta: WorkspaceMeta) {
const workspaceInfo = this.workspaces.addLocalWorkspace(name); assert(
// IMP: create workspace in local provider this._defaultProvider,
this.providerMap.get('local')?.createWorkspace({ name }); 'There is no provider. You should add provider first.'
return workspaceInfo; );
const workspace = await this._defaultProvider.createWorkspace(
workspaceMeta
);
return workspace;
} }
/** /**
@@ -73,14 +91,11 @@ export class DataCenter {
* @param {string} workspaceId workspace id * @param {string} workspaceId workspace id
*/ */
public async deleteWorkspace(workspaceId: string) { public async deleteWorkspace(workspaceId: string) {
const workspaceInfo = this.workspaces.getWorkspace(workspaceId); const workspaceInfo = this._workspaces.find(workspaceId);
assert(workspaceInfo, 'Workspace not found'); assert(workspaceInfo, 'Workspace not found');
const provider = this.providerMap.get(workspaceInfo.provider); const provider = this.providerMap.get(workspaceInfo.provider);
if (provider && this.workspaces.hasWorkspace(workspaceId)) { assert(provider, `Workspace exists, but we couldn't find its provider.`);
await provider.delete(workspaceId); await provider.deleteWorkspace(workspaceId);
// may be refresh all workspaces
this.workspaces.delete(workspaceId);
}
} }
/** /**
@@ -119,11 +134,11 @@ export class DataCenter {
* @returns {Promise<Workspace>} * @returns {Promise<Workspace>}
*/ */
public async loadWorkspace(workspaceId: string) { public async loadWorkspace(workspaceId: string) {
const workspaceInfo = this.workspaces.getWorkspace(workspaceId); const workspaceInfo = this._workspaces.find(workspaceId);
assert(workspaceInfo, 'Workspace not found'); assert(workspaceInfo, 'Workspace not found');
const currentProvider = this.providerMap.get(workspaceInfo.provider); const currentProvider = this.providerMap.get(workspaceInfo.provider);
if (currentProvider) { if (currentProvider) {
currentProvider.close(workspaceId); currentProvider.closeWorkspace(workspaceId);
} }
const provider = this.providerMap.get(workspaceInfo.provider); const provider = this.providerMap.get(workspaceInfo.provider);
assert(provider, `provide '${workspaceInfo.provider}' is not registered`); assert(provider, `provide '${workspaceInfo.provider}' is not registered`);
@@ -149,8 +164,10 @@ export class DataCenter {
* listen workspaces list change * listen workspaces list change
* @param {Function} callback callback function * @param {Function} callback callback function
*/ */
public async onWorkspacesChange(callback: (workspaces: WS[]) => void) { public async onWorkspacesChange(
this.workspaces.onWorkspacesChange(callback); callback: (workspaces: WorkspacesChangeEvent) => void
) {
this._workspaces.on('change', callback);
} }
/** /**
@@ -166,20 +183,18 @@ export class DataCenter {
assert(w?.room, 'No workspace to set meta'); assert(w?.room, 'No workspace to set meta');
const update: Partial<WorkspaceMeta> = {}; const update: Partial<WorkspaceMeta> = {};
if (name) { if (name) {
w.doc.meta.setName(name); w.meta.setName(name);
update.name = name; update.name = name;
} }
if (avatar) { if (avatar) {
w.doc.meta.setAvatar(avatar); w.meta.setAvatar(avatar);
update.avatar = avatar; update.avatar = avatar;
} }
// may run for change workspace meta // may run for change workspace meta
const workspaceInfo = this.workspaces.getWorkspace(w.room); const workspaceInfo = this._workspaces.find(w.room);
assert(workspaceInfo, 'Workspace not found'); assert(workspaceInfo, 'Workspace not found');
const provider = this.providerMap.get(workspaceInfo.provider); const provider = this.providerMap.get(workspaceInfo.provider);
provider?.updateWorkspaceMeta(w.room, update); provider?.updateWorkspaceMeta(w.room, update);
// update workspace list directly
this.workspaces.updateWorkspaceInfo(w.room, update);
} }
/** /**
@@ -188,27 +203,26 @@ export class DataCenter {
* @param id workspace id * @param id workspace id
*/ */
public async leaveWorkspace(workspaceId: string) { public async leaveWorkspace(workspaceId: string) {
const workspaceInfo = this.workspaces.getWorkspace(workspaceId); const workspaceInfo = this._workspaces.find(workspaceId);
assert(workspaceInfo, 'Workspace not found'); assert(workspaceInfo, 'Workspace not found');
const provider = this.providerMap.get(workspaceInfo.provider); const provider = this.providerMap.get(workspaceInfo.provider);
if (provider) { if (provider) {
await provider.close(workspaceId); await provider.closeWorkspace(workspaceId);
await provider.leave(workspaceId); await provider.leaveWorkspace(workspaceId);
} }
} }
public async setWorkspacePublish(workspaceId: string, isPublish: boolean) { public async setWorkspacePublish(workspaceId: string, isPublish: boolean) {
const workspaceInfo = this.workspaces.getWorkspace(workspaceId); const workspaceInfo = this._workspaces.find(workspaceId);
assert(workspaceInfo, 'Workspace not found'); assert(workspaceInfo, 'Workspace not found');
const provider = this.providerMap.get(workspaceInfo.provider); const provider = this.providerMap.get(workspaceInfo.provider);
if (provider) { if (provider) {
await provider.publish(workspaceId, isPublish); await provider.publish(workspaceId, isPublish);
this.workspaces.updateWorkspaceInfo(workspaceId, { isPublish });
} }
} }
public async inviteMember(id: string, email: string) { public async inviteMember(id: string, email: string) {
const workspaceInfo = this.workspaces.getWorkspace(id); const workspaceInfo = this._workspaces.find(id);
assert(workspaceInfo, 'Workspace not found'); assert(workspaceInfo, 'Workspace not found');
const provider = this.providerMap.get(workspaceInfo.provider); const provider = this.providerMap.get(workspaceInfo.provider);
if (provider) { if (provider) {
@@ -221,7 +235,7 @@ export class DataCenter {
* @param {number} permissionId permission id * @param {number} permissionId permission id
*/ */
public async removeMember(workspaceId: string, permissionId: number) { public async removeMember(workspaceId: string, permissionId: number) {
const workspaceInfo = this.workspaces.getWorkspace(workspaceId); const workspaceInfo = this._workspaces.find(workspaceId);
assert(workspaceInfo, 'Workspace not found'); assert(workspaceInfo, 'Workspace not found');
const provider = this.providerMap.get(workspaceInfo.provider); const provider = this.providerMap.get(workspaceInfo.provider);
if (provider) { if (provider) {
@@ -235,13 +249,11 @@ export class DataCenter {
*/ */
public async closeCurrentWorkspace() { public async closeCurrentWorkspace() {
assert(this.currentWorkspace?.room, 'No workspace to close'); assert(this.currentWorkspace?.room, 'No workspace to close');
const currentWorkspace = this.workspaces.getWorkspace( const currentWorkspace = this._workspaces.find(this.currentWorkspace.room);
this.currentWorkspace.room
);
assert(currentWorkspace, 'Workspace not found'); assert(currentWorkspace, 'Workspace not found');
const provider = this.providerMap.get(currentWorkspace.provider); const provider = this.providerMap.get(currentWorkspace.provider);
assert(provider, 'Provider not found'); assert(provider, 'Provider not found');
await provider.close(currentWorkspace.id); await provider.closeWorkspace(currentWorkspace.id);
} }
private async _transWorkspaceProvider( private async _transWorkspaceProvider(
@@ -249,7 +261,7 @@ export class DataCenter {
providerId: string providerId: string
) { ) {
assert(workspace.room, 'No workspace id'); assert(workspace.room, 'No workspace id');
const workspaceInfo = this.workspaces.getWorkspace(workspace.room); const workspaceInfo = this._workspaces.find(workspace.room);
assert(workspaceInfo, 'Workspace not found'); assert(workspaceInfo, 'Workspace not found');
if (workspaceInfo.provider === providerId) { if (workspaceInfo.provider === providerId) {
this._logger('Workspace provider is same'); this._logger('Workspace provider is same');
@@ -270,8 +282,7 @@ export class DataCenter {
); );
applyUpdate(newWorkspace.doc, encodeStateAsUpdate(workspace.doc)); applyUpdate(newWorkspace.doc, encodeStateAsUpdate(workspace.doc));
assert(newWorkspace, 'Create workspace failed'); assert(newWorkspace, 'Create workspace failed');
await currentProvider.delete(workspace.room); await currentProvider.deleteWorkspace(workspace.room);
this.workspaces.refreshWorkspaces();
} }
/** /**

View File

@@ -119,9 +119,8 @@ export class AffineProvider extends BaseProvider {
return Promise.resolve(null); return Promise.resolve(null);
} }
}); });
await (
await Promise.all(workspaceInstances) (await Promise.all(workspaceInstances)).forEach((workspace, i) => {
).forEach((workspace, i) => {
if (workspace) { if (workspace) {
workspaces[i] = { workspaces[i] = {
...workspaces[i], ...workspaces[i],
@@ -159,6 +158,11 @@ export class AffineProvider extends BaseProvider {
} }
} }
}); });
workspaces.forEach(workspace => {
this._workspaces.add(workspace);
});
return workspaces; return workspaces;
} }
@@ -185,17 +189,19 @@ export class AffineProvider extends BaseProvider {
return this._user; return this._user;
} }
public override async delete(id: string): Promise<void> { public override async deleteWorkspace(id: string): Promise<void> {
await this.close(id); await this.closeWorkspace(id);
IndexedDBProvider.delete(id); IndexedDBProvider.delete(id);
await deleteWorkspace({ id }); await deleteWorkspace({ id });
this._workspaces.remove(id);
} }
public override async clear(): Promise<void> { public override async clear(): Promise<void> {
for (const w of this._workspacesCache.values()) { for (const w of this._workspacesCache.values()) {
if (w.room) { if (w.room) {
try { try {
await this.delete(w.room); await this.deleteWorkspace(w.room);
this._workspaces.remove(w.room);
} catch (e) { } catch (e) {
this._logger('has a problem of delete workspace ', e); this._logger('has a problem of delete workspace ', e);
} }
@@ -204,14 +210,14 @@ export class AffineProvider extends BaseProvider {
this._workspacesCache.clear(); this._workspacesCache.clear();
} }
public override async close(id: string) { public override async closeWorkspace(id: string) {
const idb = this._idbMap.get(id); const idb = this._idbMap.get(id);
idb?.destroy(); idb?.destroy();
const ws = this._wsMap.get(id); const ws = this._wsMap.get(id);
ws?.disconnect(); ws?.disconnect();
} }
public override async leave(id: string): Promise<void> { public override async leaveWorkspace(id: string): Promise<void> {
await leaveWorkspace({ id }); await leaveWorkspace({ id });
} }
@@ -251,6 +257,19 @@ export class AffineProvider extends BaseProvider {
nw.meta.setName(meta.name); nw.meta.setName(meta.name);
nw.meta.setAvatar(meta.avatar); nw.meta.setAvatar(meta.avatar);
this._initWorkspaceDb(nw); this._initWorkspaceDb(nw);
const workspaceInfo: WS = {
name: meta.name,
id,
isPublish: false,
avatar: '',
owner: undefined,
isLocal: true,
memberCount: 1,
provider: 'local',
};
this._workspaces.add(workspaceInfo);
return nw; return nw;
} }

View File

@@ -1,10 +1,10 @@
import { BlobStorage, Workspace } from '@blocksuite/store'; import { BlobStorage, Workspace } from '@blocksuite/store';
import { Logger, User, Workspace as WS, WorkspaceMeta } from '../types'; import { Logger, User, Workspace as WS, WorkspaceMeta } from '../types';
import { Workspaces } from '../workspaces'; import type { WorkspacesScope } from '../workspaces';
export class BaseProvider { export class BaseProvider {
public readonly id: string = 'base'; public readonly id: string = 'base';
protected _workspaces!: Workspaces; protected _workspaces!: WorkspacesScope;
protected _logger!: Logger; protected _logger!: Logger;
protected _blobs!: BlobStorage; protected _blobs!: BlobStorage;
@@ -13,7 +13,7 @@ export class BaseProvider {
workspaces, workspaces,
}: { }: {
logger: Logger; logger: Logger;
workspaces: Workspaces; workspaces: WorkspacesScope;
}) { }) {
this._logger = logger; this._logger = logger;
this._workspaces = workspaces; this._workspaces = workspaces;
@@ -83,7 +83,7 @@ export class BaseProvider {
* delete workspace include all data * delete workspace include all data
* @param id workspace id * @param id workspace id
*/ */
public async delete(id: string): Promise<void> { public async deleteWorkspace(id: string): Promise<void> {
id; id;
return; return;
} }
@@ -92,7 +92,7 @@ export class BaseProvider {
* leave workspace by workspace id * leave workspace by workspace id
* @param id workspace id * @param id workspace id
*/ */
public async leave(id: string): Promise<void> { public async leaveWorkspace(id: string): Promise<void> {
id; id;
return; return;
} }
@@ -101,7 +101,7 @@ export class BaseProvider {
* close db link and websocket connection and other resources * close db link and websocket connection and other resources
* @param id workspace id * @param id workspace id
*/ */
public async close(id: string) { public async closeWorkspace(id: string) {
id; id;
return; return;
} }

View File

@@ -1,7 +1,7 @@
import { BaseProvider } from '../base'; import { BaseProvider } from '../base';
import { varStorage as storage } from 'lib0/storage'; import { varStorage as storage } from 'lib0/storage';
import { Workspace as WS, WorkspaceMeta } from '../../types'; import { Workspace as WS, WorkspaceMeta } from '../../types';
import { Workspace } from '@blocksuite/store'; import { Workspace, uuidv4 } from '@blocksuite/store';
import { IndexedDBProvider } from '../indexeddb'; import { IndexedDBProvider } from '../indexeddb';
import assert from 'assert'; import assert from 'assert';
import { getDefaultHeadImgBlob } from '../../utils'; import { getDefaultHeadImgBlob } from '../../utils';
@@ -11,11 +11,10 @@ const WORKSPACE_KEY = 'workspaces';
export class LocalProvider extends BaseProvider { export class LocalProvider extends BaseProvider {
public id = 'local'; public id = 'local';
private _idbMap: Map<string, IndexedDBProvider> = new Map(); private _idbMap: Map<string, IndexedDBProvider> = new Map();
private _workspacesList: WS[] = [];
constructor() { constructor() {
super(); super();
this._workspacesList = []; this.loadWorkspaces();
} }
private _storeWorkspaces(workspaces: WS[]) { private _storeWorkspaces(workspaces: WS[]) {
@@ -46,6 +45,9 @@ export class LocalProvider extends BaseProvider {
if (workspaceStr) { if (workspaceStr) {
try { try {
workspaces = JSON.parse(workspaceStr) as WS[]; workspaces = JSON.parse(workspaceStr) as WS[];
workspaces.forEach(workspace => {
this._workspaces.add(workspace);
});
} catch (error) { } catch (error) {
this._logger(`Failed to parse workspaces from storage`); this._logger(`Failed to parse workspaces from storage`);
} }
@@ -53,12 +55,12 @@ export class LocalProvider extends BaseProvider {
return Promise.resolve(workspaces); return Promise.resolve(workspaces);
} }
public override async delete(id: string): Promise<void> { public override async deleteWorkspace(id: string): Promise<void> {
const index = this._workspacesList.findIndex(ws => ws.id === id); const workspace = this._workspaces.get(id);
if (index !== -1) { if (workspace) {
IndexedDBProvider.delete(id); IndexedDBProvider.delete(id);
this._workspacesList.splice(index, 1); this._workspaces.remove(id);
this._storeWorkspaces(this._workspacesList); this._storeWorkspaces(this._workspaces.list());
} else { } else {
this._logger(`Failed to delete workspace ${id}`); this._logger(`Failed to delete workspace ${id}`);
} }
@@ -68,15 +70,8 @@ export class LocalProvider extends BaseProvider {
id: string, id: string,
meta: Partial<WorkspaceMeta> meta: Partial<WorkspaceMeta>
) { ) {
const index = this._workspacesList.findIndex(ws => ws.id === id); this._workspaces.update(id, meta);
if (index !== -1) { this._storeWorkspaces(this._workspaces.list());
const workspace = this._workspacesList[index];
meta.name && (workspace.name = meta.name);
meta.avatar && (workspace.avatar = meta.avatar);
this._storeWorkspaces(this._workspacesList);
} else {
this._logger(`Failed to update workspace ${id}`);
}
} }
public override async createWorkspace( public override async createWorkspace(
@@ -88,13 +83,27 @@ export class LocalProvider extends BaseProvider {
const blob = await getDefaultHeadImgBlob(meta.name); const blob = await getDefaultHeadImgBlob(meta.name);
meta.avatar = (await this.setBlob(blob)) || ''; meta.avatar = (await this.setBlob(blob)) || '';
} }
const workspaceInfos = this._workspaces.addLocalWorkspace(meta.name);
this._logger('Creating affine workspace'); this._logger('Creating affine workspace');
const workspace = new Workspace({ room: workspaceInfos.id });
const workspaceInfo: WS = {
name: meta.name,
id: uuidv4(),
isPublish: false,
avatar: '',
owner: undefined,
isLocal: true,
memberCount: 1,
provider: 'local',
};
const workspace = new Workspace({ room: workspaceInfo.id });
this._initWorkspaceDb(workspace);
workspace.meta.setName(meta.name); workspace.meta.setName(meta.name);
workspace.meta.setAvatar(meta.avatar); workspace.meta.setAvatar(meta.avatar);
this._storeWorkspaces([...this._workspacesList, workspaceInfos]);
this._initWorkspaceDb(workspace); this._workspaces.add(workspaceInfo);
this._storeWorkspaces(this._workspaces.list());
return workspace; return workspace;
} }
@@ -102,5 +111,6 @@ export class LocalProvider extends BaseProvider {
const workspaces = await this.loadWorkspaces(); const workspaces = await this.loadWorkspaces();
workspaces.forEach(ws => IndexedDBProvider.delete(ws.id)); workspaces.forEach(ws => IndexedDBProvider.delete(ws.id));
this._storeWorkspaces([]); this._storeWorkspaces([]);
this._workspaces.clear();
} }
} }

View File

@@ -1 +1,2 @@
export { Workspaces } from './workspaces'; export { Workspaces } from './workspaces';
export type { WorkspacesScope, WorkspacesChangeEvent } from './workspaces';

View File

@@ -1,101 +1,127 @@
import { Workspace as WS } from '../types';
import { Observable } from 'lib0/observable'; import { Observable } from 'lib0/observable';
import { uuidv4 } from '@blocksuite/store'; import type { Workspace, WorkspaceMeta } from '../types';
import { DataCenter } from '../datacenter';
export class Workspaces extends Observable<string> { export interface WorkspacesScope {
private _workspaces: WS[]; get: (workspaceId: string) => Workspace | undefined;
private readonly _dc: DataCenter; list: () => Workspace[];
add: (workspace: Workspace) => void;
constructor(dc: DataCenter) { remove: (workspaceId: string) => boolean;
super(); clear: () => void;
this._workspaces = []; update: (workspaceId: string, workspaceMeta: Partial<WorkspaceMeta>) => void;
this._dc = dc;
} }
public init() { export interface WorkspacesChangeEvent {
this._loadWorkspaces(); added?: Workspace;
deleted?: Workspace;
updated?: Workspace;
} }
get workspaces() { export class Workspaces extends Observable<'change'> {
return this._workspaces; private _workspacesMap = new Map<string, Workspace>();
get workspaces(): Workspace[] {
return Object.values(this._workspacesMap);
} }
/** find(workspaceId: string) {
* emit when workspaces changed return this._workspacesMap.get(workspaceId);
* @param {(workspace: WS[]) => void} cb
*/
onWorkspacesChange(cb: (workspace: WS[]) => void) {
this.on('change', cb);
} }
private async _loadWorkspaces() { createScope(): WorkspacesScope {
const providers = this._dc.providers; const scopedWorkspaceIds = new Set<string>();
let workspaces: WS[] = [];
providers.forEach(async p => { const get = (workspaceId: string) => {
const pWorkspaces = await p.loadWorkspaces(); if (!scopedWorkspaceIds.has(workspaceId)) {
workspaces = [...workspaces, ...pWorkspaces]; return;
this._updateWorkspaces([...workspaces, ...pWorkspaces]); }
return this._workspacesMap.get(workspaceId);
};
const add = (workspace: Workspace) => {
if (this._workspacesMap.has(workspace.id)) {
throw new Error(`Duplicate workspace id.`);
}
this._workspacesMap.set(workspace.id, workspace);
scopedWorkspaceIds.add(workspace.id);
this.emit('change', [
{
added: workspace,
} as WorkspacesChangeEvent,
]);
};
const remove = (workspaceId: string) => {
if (!scopedWorkspaceIds.has(workspaceId)) {
return true;
}
const workspace = this._workspacesMap.get(workspaceId);
if (workspace) {
const ret = this._workspacesMap.delete(workspaceId);
// If deletion failed, return.
if (!ret) {
return ret;
}
scopedWorkspaceIds.delete(workspaceId);
this.emit('change', [
{
deleted: workspace,
} as WorkspacesChangeEvent,
]);
}
return true;
};
const clear = () => {
scopedWorkspaceIds.forEach(id => {
remove(id);
}); });
};
const update = (
workspaceId: string,
workspaceMeta: Partial<WorkspaceMeta>
) => {
if (!scopedWorkspaceIds.has(workspaceId)) {
return true;
} }
/** const workspace = this._workspacesMap.get(workspaceId);
* focus load all workspaces list if (!workspace) {
*/ return true;
public async refreshWorkspaces() {
this._loadWorkspaces();
} }
private _updateWorkspaces(workspaces: WS[]) { this._workspacesMap.set(workspaceId, { ...workspace, ...workspaceMeta });
this._workspaces = workspaces;
this.emit('change', this._workspaces); this.emit('change', [
} {
updated: this._workspacesMap.get(workspaceId),
} as WorkspacesChangeEvent,
]);
};
// TODO: need to optimize
const list = () => {
const workspaces: Workspace[] = [];
scopedWorkspaceIds.forEach(id => {
const workspace = this._workspacesMap.get(id);
if (workspace) {
workspaces.push(workspace);
}
});
return workspaces;
};
private _getDefaultWorkspace(name: string): WS {
return { return {
name, get,
id: uuidv4(), list,
isPublish: false, add,
avatar: '', remove,
owner: undefined, clear,
isLocal: true, update,
memberCount: 1,
provider: 'local',
}; };
} }
/** add a local workspaces */
public addLocalWorkspace(name: string) {
const workspace = this._getDefaultWorkspace(name);
this._updateWorkspaces([...this._workspaces, workspace]);
return workspace;
}
/** delete a workspaces by id */
public delete(id: string) {
const index = this._workspaces.findIndex(w => w.id === id);
if (index >= 0) {
this._workspaces.splice(index, 1);
this._updateWorkspaces(this._workspaces);
}
}
/** get workspace info by id */
public getWorkspace(id: string) {
return this._workspaces.find(w => w.id === id);
}
/** check if workspace exists */
public hasWorkspace(id: string) {
return this._workspaces.some(w => w.id === id);
}
public updateWorkspaceInfo(id: string, info: Partial<WS>) {
const index = this._workspaces.findIndex(w => w.id === id);
if (index >= 0) {
this._workspaces[index] = { ...this._workspaces[index], ...info };
this._updateWorkspaces(this._workspaces);
}
}
} }

View File

@@ -0,0 +1,101 @@
import { Workspace as WS } from '../types';
import { Observable } from 'lib0/observable';
import { uuidv4 } from '@blocksuite/store';
import { DataCenter } from '../datacenter';
export class Workspaces extends Observable<string> {
private _workspaces: WS[];
private readonly _dc: DataCenter;
constructor(dc: DataCenter) {
super();
this._workspaces = [];
this._dc = dc;
}
public init() {
this._loadWorkspaces();
}
get workspaces() {
return this._workspaces;
}
/**
* emit when workspaces changed
* @param {(workspace: WS[]) => void} cb
*/
onWorkspacesChange(cb: (workspace: WS[]) => void) {
this.on('change', cb);
}
private async _loadWorkspaces() {
const providers = this._dc.providers;
let workspaces: WS[] = [];
providers.forEach(async p => {
const pWorkspaces = await p.loadWorkspaces();
workspaces = [...workspaces, ...pWorkspaces];
this._updateWorkspaces([...workspaces, ...pWorkspaces]);
});
}
/**
* focus load all workspaces list
*/
public async refreshWorkspaces() {
this._loadWorkspaces();
}
private _updateWorkspaces(workspaces: WS[]) {
this._workspaces = workspaces;
this.emit('change', this._workspaces);
}
private _getDefaultWorkspace(name: string): WS {
return {
name,
id: uuidv4(),
isPublish: false,
avatar: '',
owner: undefined,
isLocal: true,
memberCount: 1,
provider: 'local',
};
}
/** add a local workspaces */
public addLocalWorkspace(name: string) {
const workspace = this._getDefaultWorkspace(name);
this._updateWorkspaces([...this._workspaces, workspace]);
return workspace;
}
/** delete a workspaces by id */
public delete(id: string) {
const index = this._workspaces.findIndex(w => w.id === id);
if (index >= 0) {
this._workspaces.splice(index, 1);
this._updateWorkspaces(this._workspaces);
}
}
/** get workspace info by id */
public getWorkspace(id: string) {
return this._workspaces.find(w => w.id === id);
}
/** check if workspace exists */
public hasWorkspace(id: string) {
return this._workspaces.some(w => w.id === id);
}
public updateWorkspaceInfo(id: string, info: Partial<WS>) {
const index = this._workspaces.findIndex(w => w.id === id);
if (index >= 0) {
this._workspaces[index] = { ...this._workspaces[index], ...info };
this._updateWorkspaces(this._workspaces);
}
}
}