diff --git a/packages/data-center/src/apis/business.ts b/packages/data-center/src/apis/business.ts new file mode 100644 index 0000000000..ab5b258d29 --- /dev/null +++ b/packages/data-center/src/apis/business.ts @@ -0,0 +1,115 @@ +import { uuidv4 } from '@blocksuite/store'; +import { getDataCenter } from 'src'; +import { DataCenter } from 'src/datacenter'; +import { Workspace, WorkspaceMeta, WorkspaceType } from '../style'; +import { token } from './token'; + +export class Business { + private _dc: DataCenter | undefined; + + private async _getDc() { + if (!this._dc) { + this._dc = await getDataCenter(); + } + return this._dc; + } + + async createWorkspace( + name: string + ): Promise> { + let id = ''; + let type = WorkspaceType.local; + if (token.isLogin) { + // TODO: add default avatar + const data = await this._dc?.apis.createWorkspace({ name, avatar: '' }); + id = data?.id || ''; + type = WorkspaceType.cloud; + this._dc?.load(id, { providerId: 'affine' }); + } else { + this._dc?.load(uuidv4(), { providerId: 'local' }); + } + const newWorkspaces = (await this.getWorkspaces()).find(w => w.id === id); + return { + id: newWorkspaces?.id || '', + name, + avatar: '', + type, + }; + } + + // updateWorkspaceMeta( + // id: string, + // meta: { name?: string; avatar: Partial } + // ) {} + + async getWorkspaces(focusUpdated?: boolean): Promise { + const dc = await this._getDc(); + if (focusUpdated) { + await dc.workspacesList.refreshWorkspaceList(); + } + return dc.workspacesList.getWorkspaces(); + } + + async onWorkspaceChange(cb: (workspaces: Workspace[]) => void) { + const dc = await this._getDc(); + dc.workspacesList.on('change', cb); + } + + async deleteWorkspace(id: string) { + const dc = await this._getDc(); + const workspace = dc.workspacesList.getWorkspaces().find(w => w.id === id); + if (workspace?.type === WorkspaceType.cloud) { + dc.apis.deleteWorkspace({ id }); + } + dc.delete(id); + } + + async leaveWorkspace(id: string) { + const dc = await this._getDc(); + const workspace = dc.workspacesList.getWorkspaces().find(w => w.id === id); + if (workspace?.type === WorkspaceType.cloud) { + dc.apis.leaveWorkspace({ id }); + dc.delete(id); + } + } + + setWorkspacePublish(id: string, isPublish: boolean): boolean { + return isPublish; + } + + async getWorkspaceById(id: string) { + const dc = await this._getDc(); + const workspace = dc.workspacesList.getWorkspaces().find(w => w.id === id); + if (workspace?.type === WorkspaceType.cloud) { + return dc.load(id, { providerId: 'affine' }); + } else { + return dc.load(id, { providerId: 'local' }); + } + } + + // getMembers(id: string): any {} + + // inviteMember(id: string, email: string) {} + + async acceptInvitation(invitingCode: string) { + const dc = await this._getDc(); + dc.apis.acceptInviting({ invitingCode }); + } + + // getUserInfo(): any {} + + async login() { + const dc = await this._getDc(); + await dc.auth('affine'); + } + + // logout() {} + + // setWorkspaceSyncType(id: string, type: 'local' | 'cloud') {} + + // importWorkspace(file: File) {} + + // exportWorkspace(id: string) {} + + // enableWorkspaceCloud(id: string) {} +} diff --git a/packages/data-center/src/apis/index.ts b/packages/data-center/src/apis/index.ts index de62a9fcb1..57c003d8fd 100644 --- a/packages/data-center/src/apis/index.ts +++ b/packages/data-center/src/apis/index.ts @@ -4,16 +4,24 @@ export type { Callback } from './token.js'; import { getAuthorizer } from './token.js'; import * as user from './user.js'; import * as workspace from './workspace.js'; +import * as business from './business.js'; export type Apis = typeof user & typeof workspace & { + business: typeof business; signInWithGoogle: ReturnType[0]; onAuthStateChanged: ReturnType[1]; }; export const getApis = (): Apis => { const [signInWithGoogle, onAuthStateChanged] = getAuthorizer(); - return { ...user, ...workspace, signInWithGoogle, onAuthStateChanged }; + return { + ...user, + ...workspace, + business, + signInWithGoogle, + onAuthStateChanged, + }; }; export type { AccessTokenMessage } from './token'; diff --git a/packages/data-center/src/apis/workspace.ts b/packages/data-center/src/apis/workspace.ts index 396aa46f8e..0c145928d5 100644 --- a/packages/data-center/src/apis/workspace.ts +++ b/packages/data-center/src/apis/workspace.ts @@ -84,7 +84,7 @@ export interface CreateWorkspaceParams { export async function createWorkspace( params: CreateWorkspaceParams -): Promise { +): Promise<{ id: string }> { return client.post('api/workspace', { json: params }).json(); } diff --git a/packages/data-center/src/datacenter.ts b/packages/data-center/src/datacenter.ts index 3e31e9f1d8..dd01e7785f 100644 --- a/packages/data-center/src/datacenter.ts +++ b/packages/data-center/src/datacenter.ts @@ -7,6 +7,7 @@ import { getApis, Apis } from './apis/index.js'; import { AffineProvider, BaseProvider } from './provider/index.js'; import { LocalProvider } from './provider/index.js'; import { getKVConfigure } from './store.js'; +import { Workspaces } from './workspaces'; // load workspace's config type LoadConfig = { @@ -33,6 +34,7 @@ export class DataCenter { private readonly _workspaces = new Map>(); private readonly _config; private readonly _logger; + public readonly workspacesList = new Workspaces(this); readonly signals = { listAdd: new Signal(), @@ -62,6 +64,7 @@ export class DataCenter { this.signals.listRemove.on(workspace => { this._config.delete(`list:${workspace}`); }); + this.workspacesList.init(); } get apis(): Readonly { @@ -195,10 +198,11 @@ export class DataCenter { * data state is also map, the key is the provider id, and the data exists locally when the value is true, otherwise it does not exist */ async list(): Promise>> { + const listString = 'list:'; const entries: [string, WorkspaceItem][] = await this._config.entries(); return entries.reduce((acc, [k, i]) => { - if (k.startsWith('list:')) { - const key = k.slice(5); + if (k.startsWith(listString)) { + const key = k.slice(listString.length); acc[key] = acc[key] || {}; acc[key][i.provider] = i.locally; } diff --git a/packages/data-center/src/style/index.ts b/packages/data-center/src/style/index.ts new file mode 100644 index 0000000000..522946a099 --- /dev/null +++ b/packages/data-center/src/style/index.ts @@ -0,0 +1,24 @@ +export enum WorkspaceType { + local = 'local', + cloud = 'cloud', +} + +export type Workspace = { + name: string; + id: string; + isPublish?: boolean; + avatar?: string; + type: WorkspaceType; + owner?: User; + isLocal?: boolean; + memberCount: number; +}; + +export type User = { + name: string; + id: string; + email: string; + avatar: string; +}; + +export type WorkspaceMeta = Pick; diff --git a/packages/data-center/src/workspaces/index.ts b/packages/data-center/src/workspaces/index.ts new file mode 100644 index 0000000000..05a26f8e2b --- /dev/null +++ b/packages/data-center/src/workspaces/index.ts @@ -0,0 +1 @@ +export { Workspaces } from './workspaces'; diff --git a/packages/data-center/src/workspaces/workspaces.ts b/packages/data-center/src/workspaces/workspaces.ts new file mode 100644 index 0000000000..2c2211614d --- /dev/null +++ b/packages/data-center/src/workspaces/workspaces.ts @@ -0,0 +1,113 @@ +import { Workspace } from '@blocksuite/store'; +import { Observable } from 'lib0/observable'; +import { WorkspaceDetail } from 'src/apis/workspace'; +import { DataCenter } from 'src/datacenter'; +import { User, Workspace as Wp, WorkspaceType } from 'src/style'; + +function getProvider(providerList: Record) { + return Object.keys(providerList)[0]; +} + +function isCloudWorkspace(provider: string) { + return provider === 'affine'; +} + +export class Workspaces extends Observable { + private _workspaces: Wp[]; + private readonly workspaceInstances: Map = new Map(); + // cache cloud workspace owner + _dc: DataCenter; + + constructor(dataCenter: DataCenter) { + super(); + this._workspaces = []; + this.workspaceInstances = new Map(); + this._dc = dataCenter; + } + + init() { + // IMP: init local providers + this._dc.auth('local'); + // add listener on list change + this._dc.signals.listAdd.on(e => { + this.refreshWorkspaceList(); + }); + this._dc.signals.listRemove.on(e => { + this.refreshWorkspaceList(); + }); + } + + async refreshWorkspaceList() { + const workspaceList = await this._dc.list(); + + const workspaceMap = Object.keys(workspaceList).map(([id]) => { + return this._dc.load(id).then(w => { + return { id, workspace: w, provider: getProvider(workspaceList[id]) }; + }); + }); + + const workspaces = (await Promise.all(workspaceMap)).map(w => { + const { id, workspace, provider } = w; + if (workspace && !this.workspaceInstances.has(id)) { + this.workspaceInstances.set(id, workspace); + } + return { + id, + name: (workspace?.doc?.meta.name as string) || '', + avatar: (workspace?.meta.avatar as string) || '', + type: isCloudWorkspace(provider) + ? WorkspaceType.cloud + : WorkspaceType.local, + isLocal: false, + isPublish: false, + owner: undefined, + memberCount: 1, + } as Wp; + }); + const getDetailList = (await Promise.all(workspaceMap)).map(w => { + const { id, provider } = w; + if (provider === 'workspaces') { + return new Promise<{ id: string; detail: WorkspaceDetail | null }>( + resolve => { + this._dc.apis.getWorkspaceDetail({ id }).then(data => { + resolve({ id, detail: data || null }); + }); + } + ); + } + }); + const ownerList = await Promise.all(getDetailList); + (await Promise.all(ownerList)).forEach(detail => { + if (detail) { + const { id, detail: workspaceDetail } = detail; + if (workspaceDetail) { + const { owner, member_count } = workspaceDetail; + const currentWorkspace = workspaces.find(w => w.id === id); + if (currentWorkspace) { + currentWorkspace.owner = { + id: owner.id, + name: owner.name, + avatar: owner.avatar_url, + email: owner.email, + }; + currentWorkspace.memberCount = member_count; + } + } + } + }); + this._updateWorkspaces(workspaces); + } + + getWorkspaces() { + return this._workspaces; + } + + _updateWorkspaces(workspaces: Wp[]) { + this._workspaces = workspaces; + this.emit('change', this._workspaces); + } + + onWorkspaceChange(cb: (workspace: Wp) => void) { + this.on('change', cb); + } +}