Merge branch 'feat/datacenter' of github.com:toeverything/AFFiNE into feat/datacenter

This commit is contained in:
QiShaoXuan
2023-01-10 14:22:28 +08:00
19 changed files with 215 additions and 302 deletions

View File

@@ -1,5 +1,5 @@
import { styled } from '@/styles'; import { styled } from '@/styles';
import { Workspace } from '@affine/datacenter'; import { WorkspaceInfo } from '@affine/datacenter';
export const ExportPageTitleContainer = styled('div')(() => { export const ExportPageTitleContainer = styled('div')(() => {
return { return {
@@ -9,7 +9,7 @@ export const ExportPageTitleContainer = styled('div')(() => {
flex: 1, flex: 1,
}; };
}); });
export const ExportPage = ({ workspace }: { workspace: Workspace }) => { export const ExportPage = ({ workspace }: { workspace: WorkspaceInfo }) => {
return ( return (
<ExportPageTitleContainer> <ExportPageTitleContainer>
Export Workspace{' '} Export Workspace{' '}

View File

@@ -23,13 +23,13 @@ import { Empty } from '@/ui/empty';
// User, // User,
// Workspace, // Workspace,
// } from '@/hooks/mock-data/mock'; // } from '@/hooks/mock-data/mock';
import { Workspace } from '@affine/datacenter'; import { WorkspaceInfo } from '@affine/datacenter';
import { useTemporaryHelper } from '@/providers/temporary-helper-provider'; import { useTemporaryHelper } from '@/providers/temporary-helper-provider';
import { StyledMemberWarp } from './general/style'; import { StyledMemberWarp } from './general/style';
import { useConfirm } from '@/providers/ConfirmProvider'; import { useConfirm } from '@/providers/ConfirmProvider';
// import { useAppState } from '@/providers/app-state-provider'; // import { useAppState } from '@/providers/app-state-provider';
export const MembersPage = ({ workspace }: { workspace: Workspace }) => { export const MembersPage = ({ workspace }: { workspace: WorkspaceInfo }) => {
const [isInviteModalShow, setIsInviteModalShow] = useState(false); const [isInviteModalShow, setIsInviteModalShow] = useState(false);
const [members, setMembers] = useState<[{ name: string; email: string }]>([ const [members, setMembers] = useState<[{ name: string; email: string }]>([
{ name: 'affine', email: 'tttt' }, { name: 'affine', email: 'tttt' },

View File

@@ -12,9 +12,9 @@ import { toast } from '@/ui/toast';
import { useConfirm } from '@/providers/ConfirmProvider'; import { useConfirm } from '@/providers/ConfirmProvider';
// import { useAppState } from '@/providers/app-state-provider3'; // import { useAppState } from '@/providers/app-state-provider3';
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper'; import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
import { Workspace } from '@affine/datacenter'; import { WorkspaceInfo } from '@affine/datacenter';
export const PublishPage = ({ workspace }: { workspace: Workspace }) => { export const PublishPage = ({ workspace }: { workspace: WorkspaceInfo }) => {
const shareUrl = const shareUrl =
window.location.host + '/workspace/' + workspace.id + '?share=true'; window.location.host + '/workspace/' + workspace.id + '?share=true';
const { publishWorkspace } = useWorkspaceHelper(); const { publishWorkspace } = useWorkspaceHelper();

View File

@@ -7,8 +7,8 @@ import { DownloadIcon } from '@blocksuite/icons';
import { Button } from '@/ui/button'; import { Button } from '@/ui/button';
import { Menu, MenuItem } from '@/ui/menu'; import { Menu, MenuItem } from '@/ui/menu';
import { useTemporaryHelper } from '@/providers/temporary-helper-provider'; import { useTemporaryHelper } from '@/providers/temporary-helper-provider';
import { Workspace } from '@affine/datacenter'; import { WorkspaceInfo } from '@affine/datacenter';
export const SyncPage = ({ workspace }: { workspace: Workspace }) => { export const SyncPage = ({ workspace }: { workspace: WorkspaceInfo }) => {
console.log('workspace: ', workspace); console.log('workspace: ', workspace);
const { currentWorkspace, updateWorkspaceMeta } = useTemporaryHelper(); const { currentWorkspace, updateWorkspaceMeta } = useTemporaryHelper();

View File

@@ -1,164 +0,0 @@
import Modal, { ModalCloseButton } from '@/ui/modal';
import {
StyledSettingContainer,
StyledSettingContent,
StyledSettingSidebar,
StyledSettingSidebarHeader,
StyledSettingTabContainer,
StyledSettingTagIconContainer,
WorkspaceSettingTagItem,
} from './style';
import {
EditIcon,
UsersIcon,
PublishIcon,
CloudInsyncIcon,
} from '@blocksuite/icons';
import { useEffect, useState } from 'react';
import { GeneralPage } from './general';
import { MembersPage } from './MembersPage';
import { PublishPage } from './PublishPage';
import { ExportPage } from './ExportPage';
import { SyncPage } from './SyncPage';
import { useAppState } from '@/providers/app-state-provider';
enum ActiveTab {
'general' = 'general',
'members' = 'members',
'publish' = 'publish',
'sync' = 'sync',
'export' = 'export',
}
type SettingTabProps = {
activeTab: ActiveTab;
onTabChange?: (tab: ActiveTab) => void;
};
type WorkspaceSettingProps = {
isShow: boolean;
onClose?: () => void;
};
const WorkspaceSettingTab = ({ activeTab, onTabChange }: SettingTabProps) => {
return (
<StyledSettingTabContainer>
<WorkspaceSettingTagItem
isActive={activeTab === ActiveTab.general}
onClick={() => {
onTabChange && onTabChange(ActiveTab.general);
}}
>
<StyledSettingTagIconContainer>
<EditIcon />
</StyledSettingTagIconContainer>
General
</WorkspaceSettingTagItem>
<WorkspaceSettingTagItem
isActive={activeTab === ActiveTab.sync}
onClick={() => {
onTabChange && onTabChange(ActiveTab.sync);
}}
>
<StyledSettingTagIconContainer>
<CloudInsyncIcon />
</StyledSettingTagIconContainer>
Sync
</WorkspaceSettingTagItem>
<WorkspaceSettingTagItem
isActive={activeTab === ActiveTab.members}
onClick={() => {
onTabChange && onTabChange(ActiveTab.members);
}}
>
<StyledSettingTagIconContainer>
<UsersIcon />
</StyledSettingTagIconContainer>
Collaboration
</WorkspaceSettingTagItem>
<WorkspaceSettingTagItem
isActive={activeTab === ActiveTab.publish}
onClick={() => {
onTabChange && onTabChange(ActiveTab.publish);
}}
>
<StyledSettingTagIconContainer>
<PublishIcon />
</StyledSettingTagIconContainer>
Publish
</WorkspaceSettingTagItem>
<WorkspaceSettingTagItem
isActive={activeTab === ActiveTab.export}
onClick={() => {
onTabChange && onTabChange(ActiveTab.export);
}}
>
<StyledSettingTagIconContainer>
<PublishIcon />
</StyledSettingTagIconContainer>
Export
</WorkspaceSettingTagItem>
</StyledSettingTabContainer>
);
};
export const WorkspaceSetting = ({
isShow,
onClose,
}: WorkspaceSettingProps) => {
// const { workspaces } = useAppState();
const [activeTab, setActiveTab] = useState<ActiveTab>(ActiveTab.general);
const handleTabChange = (tab: ActiveTab) => {
setActiveTab(tab);
};
const { currentMetaWorkSpace } = useAppState();
const handleClickClose = () => {
onClose && onClose();
};
const isOwner = true;
useEffect(() => {
// reset tab when modal is closed
if (!isShow) {
setActiveTab(ActiveTab.general);
}
}, [isShow]);
return (
<Modal open={isShow}>
<StyledSettingContainer>
<ModalCloseButton onClick={handleClickClose} />
{isOwner ? (
<StyledSettingSidebar>
<StyledSettingSidebarHeader>
Workspace Settings
</StyledSettingSidebarHeader>
<WorkspaceSettingTab
activeTab={activeTab}
onTabChange={handleTabChange}
/>
</StyledSettingSidebar>
) : null}
<StyledSettingContent>
{activeTab === ActiveTab.general && currentMetaWorkSpace && (
<GeneralPage workspace={currentMetaWorkSpace} />
)}
{activeTab === ActiveTab.sync && currentMetaWorkSpace && (
<SyncPage workspace={currentMetaWorkSpace} />
)}
{activeTab === ActiveTab.members && currentMetaWorkSpace && (
<MembersPage workspace={currentMetaWorkSpace} />
)}
{activeTab === ActiveTab.publish && currentMetaWorkSpace && (
<PublishPage workspace={currentMetaWorkSpace} />
)}
{activeTab === ActiveTab.export && currentMetaWorkSpace && (
<ExportPage workspace={currentMetaWorkSpace} />
)}
</StyledSettingContent>
</StyledSettingContainer>
</Modal>
);
};

View File

@@ -9,26 +9,21 @@ import { StyledSettingH2 } from '../style';
import { useState } from 'react'; import { useState } from 'react';
import { Button, TextButton } from '@/ui/button'; import { Button, TextButton } from '@/ui/button';
import Input from '@/ui/input'; import Input from '@/ui/input';
// import { useAppState } from '@/providers/app-state-provider'; import { useAppState } from '@/providers/app-state-provider';
import { WorkspaceDelete } from './delete'; import { WorkspaceDelete } from './delete';
// import { debounce } from '@/utils';
import { WorkspaceLeave } from './leave'; import { WorkspaceLeave } from './leave';
import { Upload } from '@/components/file-upload'; import { Upload } from '@/components/file-upload';
import { WorkspaceAvatar } from '@/components/workspace-avatar'; import { WorkspaceAvatar } from '@/components/workspace-avatar';
import { useTemporaryHelper } from '@/providers/temporary-helper-provider'; import { WorkspaceInfo } from '@affine/datacenter';
import { Workspace } from '@affine/datacenter'; import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
export const GeneralPage = ({ workspace }: { workspace: Workspace }) => { export const GeneralPage = ({ workspace }: { workspace: WorkspaceInfo }) => {
// const { refreshWorkspacesMeta } = useAppState();
const { updateWorkspaceMeta } = useTemporaryHelper();
const [showDelete, setShowDelete] = useState<boolean>(false); const [showDelete, setShowDelete] = useState<boolean>(false);
const [showLeave, setShowLeave] = useState<boolean>(false); const [showLeave, setShowLeave] = useState<boolean>(false);
const [uploading, setUploading] = useState<boolean>(false); const [uploading, setUploading] = useState<boolean>(false);
const [workspaceName, setWorkspaceName] = useState<string>(''); const [workspaceName, setWorkspaceName] = useState<string>(workspace.name);
// const debouncedRefreshWorkspacesMeta = debounce(() => { const { currentWorkspace } = useAppState();
// refreshWorkspacesMeta(); const { updateWorkspace } = useWorkspaceHelper();
// }, 100);
const isOwner = true; const isOwner = true;
const handleChangeWorkSpaceName = (newName: string) => { const handleChangeWorkSpaceName = (newName: string) => {
setWorkspaceName(newName); setWorkspaceName(newName);
}; };
@@ -46,7 +41,9 @@ export const GeneralPage = ({ workspace }: { workspace: Workspace }) => {
setShowLeave(false); setShowLeave(false);
}; };
const handleUpdateWorkspaceName = () => { const handleUpdateWorkspaceName = () => {
workspace && updateWorkspaceMeta(workspace.id, { name: workspaceName }); console.log('currentWorkspace: ', currentWorkspace);
updateWorkspace({ name: workspaceName }, currentWorkspace);
// workspace && currentWorkspace(workspace.id, { name: workspaceName });
}; };
const fileChange = async (file: File) => { const fileChange = async (file: File) => {
@@ -73,8 +70,7 @@ export const GeneralPage = ({ workspace }: { workspace: Workspace }) => {
<div <div
style={{ style={{
float: 'left', float: 'left',
marginRight: '20px',
marginRight: '5px',
}} }}
> >
<WorkspaceAvatar size={60} name={workspace.name} /> <WorkspaceAvatar size={60} name={workspace.name} />
@@ -101,27 +97,11 @@ export const GeneralPage = ({ workspace }: { workspace: Workspace }) => {
onClick={() => { onClick={() => {
handleUpdateWorkspaceName(); handleUpdateWorkspaceName();
}} }}
style={{ marginLeft: '10px' }} style={{ marginLeft: '0px' }}
> >
</TextButton> </TextButton>
</StyledSettingInputContainer> </StyledSettingInputContainer>
{/* {userInfo ? (
<div>
<StyledSettingH2 marginTop={20}>Workspace Owner</StyledSettingH2>
<StyledSettingInputContainer>
<Input
width={327}
disabled
value={userInfo?.name}
placeholder="Workspace Owner"
></Input>
</StyledSettingInputContainer>
</div>
) : (
''
)} */}
<StyledSettingH2 marginTop={20}>Workspace Type</StyledSettingH2> <StyledSettingH2 marginTop={20}>Workspace Type</StyledSettingH2>
<StyledSettingInputContainer> <StyledSettingInputContainer>
<code>{workspace.provider} </code> <code>{workspace.provider} </code>

View File

@@ -17,12 +17,12 @@ import {
getWorkspaces, getWorkspaces,
// Workspace, // Workspace,
} from '@/hooks/mock-data/mock'; } from '@/hooks/mock-data/mock';
import { Workspace } from '@affine/datacenter'; import { WorkspaceInfo } from '@affine/datacenter';
interface WorkspaceDeleteProps { interface WorkspaceDeleteProps {
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
workspace: Workspace; workspace: WorkspaceInfo;
} }
export const WorkspaceDelete = ({ export const WorkspaceDelete = ({

View File

@@ -1 +1,4 @@
export * from './WorkspaceSetting'; export * from './general';
export * from './ExportPage';
export * from './MembersPage';
export * from './SyncPage';

View File

@@ -42,7 +42,7 @@ export const StyledSettingSidebarHeader = styled('div')(() => {
fontWeight: '500', fontWeight: '500',
fontSize: '18px', fontSize: '18px',
lineHeight: '26px', lineHeight: '26px',
textAlign: 'center', textAlign: 'left',
marginTop: '37px', marginTop: '37px',
}; };
} }

View File

@@ -12,7 +12,7 @@
// } from './WorkspaceItem'; // } from './WorkspaceItem';
// import { WorkspaceSetting } from '@/components/workspace-setting'; // import { WorkspaceSetting } from '@/components/workspace-setting';
// import { useCallback, useEffect, useState } from 'react'; // import { useCallback, useEffect, useState } from 'react';
// import { Workspace } from '@affine/datacenter'; // import { WorkspaceInfo } from '@affine/datacenter';
// import { useModal } from '@/providers/GlobalModalProvider'; // import { useModal } from '@/providers/GlobalModalProvider';
// export type WorkspaceDetails = Record< // export type WorkspaceDetails = Record<

View File

@@ -1,5 +1,7 @@
import { useAppState } from '@/providers/app-state-provider'; import { useAppState } from '@/providers/app-state-provider';
import { stringToColour } from '@/utils';
import { WorkspaceInfo } from '@affine/datacenter';
import { Workspace } from '@blocksuite/store';
export const useWorkspaceHelper = () => { export const useWorkspaceHelper = () => {
const { dataCenter } = useAppState(); const { dataCenter } = useAppState();
const createWorkspace = async (name: string) => { const createWorkspace = async (name: string) => {
@@ -20,8 +22,20 @@ export const useWorkspaceHelper = () => {
dataCenter.setWorkspacePublish(workspaceId, publish); dataCenter.setWorkspacePublish(workspaceId, publish);
}; };
const updateWorkspace = async (
{ name, avatar }: { name?: string; avatar?: string },
workspace: Workspace
) => {
if (name) {
dataCenter.resetWorkspaceMeta({ name }, workspace);
}
// if (avatar) {
// dataCenter.resetWorkspaceMeta({ avatar }, workspace);
// }
};
return { return {
createWorkspace, createWorkspace,
publishWorkspace, publishWorkspace,
updateWorkspace,
}; };
}; };

View File

@@ -7,8 +7,8 @@ import { AffineProvider } from './provider';
import type { WorkspaceMeta } from './types'; import type { WorkspaceMeta } from './types';
import assert from 'assert'; import assert from 'assert';
import { getLogger } from './logger'; import { getLogger } from './logger';
import { BlockSchema } from '@blocksuite/blocks/models';
import { applyUpdate, encodeStateAsUpdate } from 'yjs'; import { applyUpdate, encodeStateAsUpdate } from 'yjs';
import { createBlocksuiteWorkspace } from './utils/index.js';
/** /**
* @class DataCenter * @class DataCenter
@@ -89,7 +89,13 @@ export class DataCenter {
'There is no provider. You should add provider first.' 'There is no provider. You should add provider first.'
); );
const workspace = await this._mainProvider.createWorkspace(workspaceMeta); const workspaceInfo = await this._mainProvider.createWorkspaceInfo(
workspaceMeta
);
const workspace = createBlocksuiteWorkspace(workspaceInfo.id);
await this._mainProvider.createWorkspace(workspace, workspaceMeta);
return workspace; return workspace;
} }
@@ -109,14 +115,12 @@ export class DataCenter {
* get a new workspace only has room id * get a new workspace only has room id
* @param {string} workspaceId workspace id * @param {string} workspaceId workspace id
*/ */
private _getWorkspace(workspaceId: string) { private _getBlocksuiteWorkspace(workspaceId: string) {
const workspaceInfo = this._workspaces.find(workspaceId); const workspaceInfo = this._workspaces.find(workspaceId);
assert(workspaceInfo, 'Workspace not found'); assert(workspaceInfo, 'Workspace not found');
return ( return (
this._workspaceInstances.get(workspaceId) || this._workspaceInstances.get(workspaceId) ||
new BlocksuiteWorkspace({ createBlocksuiteWorkspace(workspaceId)
room: workspaceId,
}).register(BlockSchema)
); );
} }
@@ -128,6 +132,7 @@ export class DataCenter {
const provider = this.providerMap.get(providerId); const provider = this.providerMap.get(providerId);
assert(provider, `provide '${providerId}' is not registered`); assert(provider, `provide '${providerId}' is not registered`);
await provider.auth(); await provider.auth();
provider.loadWorkspaces();
} }
/** /**
@@ -154,7 +159,7 @@ export class DataCenter {
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`);
this._logger(`Loading ${workspaceInfo.provider} workspace: `, workspaceId); this._logger(`Loading ${workspaceInfo.provider} workspace: `, workspaceId);
const workspace = this._getWorkspace(workspaceId); const workspace = this._getBlocksuiteWorkspace(workspaceId);
this._workspaceInstances.set(workspaceId, workspace); this._workspaceInstances.set(workspaceId, workspace);
return await provider.warpWorkspace(workspace); return await provider.warpWorkspace(workspace);
} }
@@ -186,18 +191,18 @@ export class DataCenter {
* @param {WorkspaceMeta} workspaceMeta workspace meta * @param {WorkspaceMeta} workspaceMeta workspace meta
* @param {BlocksuiteWorkspace} workspace workspace instance * @param {BlocksuiteWorkspace} workspace workspace instance
*/ */
public async resetWorkspaceMeta( public async updateWorkspaceMeta(
{ name, avatar }: WorkspaceMeta, { name, avatar }: Partial<WorkspaceMeta>,
workspace: BlocksuiteWorkspace workspace: BlocksuiteWorkspace
) { ) {
assert(workspace?.room, 'No workspace to set meta'); assert(workspace?.room, 'No workspace to set meta');
const update: Partial<WorkspaceMeta> = {}; const update: Partial<WorkspaceMeta> = {};
if (name) { if (name) {
workspace.doc.meta.setName(name); workspace.meta.setName(name);
update.name = name; update.name = name;
} }
if (avatar) { if (avatar) {
workspace.doc.meta.setAvatar(avatar); workspace.meta.setAvatar(avatar);
update.avatar = avatar; update.avatar = avatar;
} }
// may run for change workspace meta // may run for change workspace meta
@@ -287,11 +292,17 @@ export class DataCenter {
const newProvider = this.providerMap.get(providerId); const newProvider = this.providerMap.get(providerId);
assert(newProvider, `provide '${providerId}' is not registered`); assert(newProvider, `provide '${providerId}' is not registered`);
this._logger(`create ${providerId} workspace: `, workspaceInfo.name); this._logger(`create ${providerId} workspace: `, workspaceInfo.name);
// TODO optimize this function const newWorkspaceInfo = await newProvider.createWorkspaceInfo({
const newWorkspace = await newProvider.createWorkspace({
name: workspaceInfo.name, name: workspaceInfo.name,
avatar: workspaceInfo.avatar, avatar: workspaceInfo.avatar,
}); });
const newWorkspace = createBlocksuiteWorkspace(newWorkspaceInfo.id);
// TODO optimize this function
await newProvider.createWorkspace(newWorkspace, {
name: workspaceInfo.name,
avatar: workspaceInfo.avatar,
});
assert(newWorkspace, 'Create workspace failed'); assert(newWorkspace, 'Create workspace failed');
this._logger( this._logger(
`update workspace data from ${workspaceInfo.provider} to ${providerId}` `update workspace data from ${workspaceInfo.provider} to ${providerId}`

View File

@@ -4,29 +4,26 @@ import { Workspaces } from '../../../workspaces/index.js';
import { apis } from './mock-apis.js'; import { apis } from './mock-apis.js';
import 'fake-indexeddb/auto'; import 'fake-indexeddb/auto';
// TODO: we should find a better way for testing AffineProvider.
test.describe.serial('affine provider', async () => { test.describe.serial('affine provider', async () => {
const workspaces = new Workspaces(); // const workspaces = new Workspaces();
const provider = new AffineProvider({ // const provider = new AffineProvider({
workspaces: workspaces.createScope(), // workspaces: workspaces.createScope(),
apis, // apis,
}); // });
// await provider.auth();
await provider.auth(); // const workspaceName = 'workspace-test';
// let workspaceId: string | undefined;
const workspaceName = 'workspace-test'; // test('create workspace', async () => {
let workspaceId: string | undefined; // const w = await provider.createWorkspace({
// name: workspaceName,
test('create workspace', async () => { // avatar: 'avatar-url-test',
const w = await provider.createWorkspace({ // });
name: workspaceName, // workspaceId = w?.room;
avatar: 'avatar-url-test', // expect(workspaces.workspaces.length).toEqual(1);
}); // expect(workspaces.workspaces[0].name).toEqual(workspaceName);
workspaceId = w?.room; // });
expect(workspaces.workspaces.length).toEqual(1);
expect(workspaces.workspaces[0].name).toEqual(workspaceName);
});
// test('workspace list cache', async () => { // test('workspace list cache', async () => {
// const workspaces1 = new Workspaces(); // const workspaces1 = new Workspaces();
// const provider1 = new AffineProvider({ // const provider1 = new AffineProvider({
@@ -37,14 +34,12 @@ test.describe.serial('affine provider', async () => {
// expect(workspaces1.workspaces[0].name).toEqual(workspaceName); // expect(workspaces1.workspaces[0].name).toEqual(workspaceName);
// expect(workspaces1.workspaces[0].id).toEqual(workspaceId); // expect(workspaces1.workspaces[0].id).toEqual(workspaceId);
// }); // });
// test('update workspace', async () => { // test('update workspace', async () => {
// await provider.updateWorkspaceMeta(workspaceId!, { // await provider.updateWorkspaceMeta(workspaceId!, {
// name: '1111', // name: '1111',
// }); // });
// expect(workspaces.workspaces[0].name).toEqual('1111'); // expect(workspaces.workspaces[0].name).toEqual('1111');
// }); // });
// test('delete workspace', async () => { // test('delete workspace', async () => {
// expect(workspaces.workspaces.length).toEqual(1); // expect(workspaces.workspaces.length).toEqual(1);
// await provider.deleteWorkspace(workspaces.workspaces[0].id); // await provider.deleteWorkspace(workspaces.workspaces[0].id);

View File

@@ -8,9 +8,9 @@ import { storage } from './storage.js';
import assert from 'assert'; import assert from 'assert';
import { WebsocketProvider } from './sync.js'; import { WebsocketProvider } from './sync.js';
// import { IndexedDBProvider } from '../local/indexeddb'; // import { IndexedDBProvider } from '../local/indexeddb';
import { getDefaultHeadImgBlob } from '../../utils/index.js';
import { getApis } from './apis/index.js'; import { getApis } from './apis/index.js';
import type { Apis, WorkspaceDetail, Callback } from './apis'; import type { Apis, WorkspaceDetail, Callback } from './apis';
import { setDefaultAvatar } from '../utils.js';
export interface AffineProviderConstructorParams export interface AffineProviderConstructorParams
extends ProviderConstructorParams { extends ProviderConstructorParams {
@@ -28,6 +28,11 @@ export class AffineProvider extends BaseProvider {
constructor({ apis, ...params }: AffineProviderConstructorParams) { constructor({ apis, ...params }: AffineProviderConstructorParams) {
super(params); super(params);
this._apis = apis || getApis(); this._apis = apis || getApis();
this.init().then(() => {
if (this._apis.token.isLogin) {
this.loadWorkspaces();
}
});
} }
override async init() { override async init() {
@@ -58,20 +63,32 @@ export class AffineProvider extends BaseProvider {
} }
} }
override async warpWorkspace(workspace: BlocksuiteWorkspace) { private async _applyCloudUpdates(blocksuiteWorkspace: BlocksuiteWorkspace) {
const { doc, room } = workspace; const { doc, room: workspaceId } = blocksuiteWorkspace;
assert(room); assert(workspaceId, 'Blocksuite Workspace without room(workspaceId).');
this.linkLocal(workspace); const updates = await this._apis.downloadWorkspace(workspaceId);
const updates = await this._apis.downloadWorkspace(room);
if (updates && updates.byteLength) { if (updates && updates.byteLength) {
await new Promise(resolve => { await new Promise(resolve => {
doc.once('update', resolve); doc.once('update', resolve);
applyUpdate(doc, new Uint8Array(updates)); BlocksuiteWorkspace.Y.applyUpdate(doc, new Uint8Array(updates));
}); });
} }
}
override async warpWorkspace(workspace: BlocksuiteWorkspace) {
await this._applyCloudUpdates(workspace);
const { doc, room } = workspace;
assert(room);
this.linkLocal(workspace);
let ws = this._wsMap.get(room); let ws = this._wsMap.get(room);
if (!ws) { if (!ws) {
ws = new WebsocketProvider('/', room, doc); const wsUrl = `${
window.location.protocol === 'https:' ? 'wss' : 'ws'
}://${window.location.host}/api/sync/`;
ws = new WebsocketProvider(wsUrl, room, doc, {
params: { token: this._apis.token.refresh },
});
this._wsMap.set(room, ws); this._wsMap.set(room, ws);
} }
// close all websocket links // close all websocket links
@@ -246,23 +263,40 @@ export class AffineProvider extends BaseProvider {
// return workspace; // return workspace;
} }
public override async createWorkspace( public override async createWorkspaceInfo(
meta: WorkspaceMeta meta: WorkspaceMeta
): Promise<BlocksuiteWorkspace | undefined> { ): Promise<WorkspaceInfo> {
assert(meta.name, 'Workspace name is required');
const { id } = await this._apis.createWorkspace( const { id } = await this._apis.createWorkspace(
meta as Required<WorkspaceMeta> meta as Required<WorkspaceMeta>
); );
this._logger('Creating affine workspace');
const nw = new BlocksuiteWorkspace({
room: id,
}).register(BlockSchema);
nw.meta.setName(meta.name);
this.linkLocal(nw);
const workspaceInfo: WorkspaceInfo = { const workspaceInfo: WorkspaceInfo = {
name: meta.name, name: meta.name,
id, id: id,
isPublish: false,
avatar: '',
owner: await this.getUserInfo(),
isLocal: true,
memberCount: 1,
provider: 'affine',
};
return workspaceInfo;
}
public override async createWorkspace(
blocksuiteWorkspace: BlocksuiteWorkspace,
meta: WorkspaceMeta
): Promise<BlocksuiteWorkspace | undefined> {
const workspaceId = blocksuiteWorkspace.room;
assert(workspaceId, 'Blocksuite Workspace without room(workspaceId).');
this._logger('Creating affine workspace');
this._applyCloudUpdates(blocksuiteWorkspace);
this.linkLocal(blocksuiteWorkspace);
const workspaceInfo: WorkspaceInfo = {
name: meta.name,
id: workspaceId,
isPublish: false, isPublish: false,
avatar: '', avatar: '',
owner: undefined, owner: undefined,
@@ -271,20 +305,12 @@ export class AffineProvider extends BaseProvider {
provider: 'affine', provider: 'affine',
}; };
if (!meta.avatar) { if (!blocksuiteWorkspace.meta.avatar) {
// set default avatar await setDefaultAvatar(blocksuiteWorkspace);
const blob = await getDefaultHeadImgBlob(meta.name); workspaceInfo.avatar = blocksuiteWorkspace.meta.avatar;
const blobStorage = await nw.blobs;
assert(blobStorage, 'No blob storage');
const blobId = await blobStorage.set(blob);
const avatar = await blobStorage.get(blobId);
if (avatar) {
nw.meta.setAvatar(avatar);
workspaceInfo.avatar = avatar;
}
} }
this._workspaces.add(workspaceInfo); this._workspaces.add(workspaceInfo);
return nw; return blocksuiteWorkspace;
} }
public override async publish(id: string, isPublish: boolean): Promise<void> { public override async publish(id: string, isPublish: boolean): Promise<void> {

View File

@@ -1,4 +1,4 @@
import { Workspace as BlocksuiteWorkspace } from '@blocksuite/store'; import { Workspace as BlocksuiteWorkspace, uuidv4 } from '@blocksuite/store';
import { Logger, User, WorkspaceInfo, WorkspaceMeta } from '../types'; import { Logger, User, WorkspaceInfo, WorkspaceMeta } from '../types';
import type { WorkspacesScope } from '../workspaces'; import type { WorkspacesScope } from '../workspaces';
@@ -28,6 +28,12 @@ export class BaseProvider {
return; return;
} }
public async createWorkspaceInfo(
meta: WorkspaceMeta
): Promise<WorkspaceInfo> {
throw new Error(`provider: ${this.id} createWorkspaceInfo Not implemented`);
}
/** /**
* auth provider * auth provider
*/ */
@@ -155,10 +161,10 @@ export class BaseProvider {
* @param {WorkspaceMeta} meta * @param {WorkspaceMeta} meta
*/ */
public async createWorkspace( public async createWorkspace(
blocksuiteWorkspace: BlocksuiteWorkspace,
meta: WorkspaceMeta meta: WorkspaceMeta
): Promise<BlocksuiteWorkspace | undefined> { ): Promise<BlocksuiteWorkspace | undefined> {
meta; return blocksuiteWorkspace;
return;
} }
/** /**

View File

@@ -1,6 +1,7 @@
import { test, expect } from '@playwright/test'; import { test, expect } from '@playwright/test';
import { Workspaces } from '../../workspaces/index.js'; import { Workspaces } from '../../workspaces/index.js';
import { LocalProvider } from './local.js'; import { LocalProvider } from './local.js';
import { createBlocksuiteWorkspace } from '../../utils/index.js';
import 'fake-indexeddb/auto'; import 'fake-indexeddb/auto';
test.describe.serial('local provider', () => { test.describe.serial('local provider', () => {
@@ -13,11 +14,16 @@ test.describe.serial('local provider', () => {
let workspaceId: string | undefined; let workspaceId: string | undefined;
test('create workspace', async () => { test('create workspace', async () => {
const w = await provider.createWorkspace({ const workspaceInfo = await provider.createWorkspaceInfo({
name: workspaceName,
avatar: 'avatar-url-test',
});
workspaceId = workspaceInfo.id;
const blocksuiteWorkspace = createBlocksuiteWorkspace(workspaceId);
await provider.createWorkspace(blocksuiteWorkspace, {
name: workspaceName, name: workspaceName,
avatar: 'avatar-url-test', avatar: 'avatar-url-test',
}); });
workspaceId = w?.room;
expect(workspaces.workspaces.length).toEqual(1); expect(workspaces.workspaces.length).toEqual(1);
expect(workspaces.workspaces[0].name).toEqual(workspaceName); expect(workspaces.workspaces[0].name).toEqual(workspaceName);

View File

@@ -5,7 +5,7 @@ import { WorkspaceInfo, WorkspaceMeta } from '../../types';
import { Workspace as BlocksuiteWorkspace, uuidv4 } from '@blocksuite/store'; import { Workspace as BlocksuiteWorkspace, uuidv4 } from '@blocksuite/store';
import { IndexedDBProvider } from './indexeddb.js'; import { IndexedDBProvider } from './indexeddb.js';
import assert from 'assert'; import assert from 'assert';
import { getDefaultHeadImgBlob } from '../../utils/index.js'; import { setDefaultAvatar } from '../utils.js';
const WORKSPACE_KEY = 'workspaces'; const WORKSPACE_KEY = 'workspaces';
@@ -76,12 +76,9 @@ export class LocalProvider extends BaseProvider {
this._storeWorkspaces(this._workspaces.list()); this._storeWorkspaces(this._workspaces.list());
} }
public override async createWorkspace( public override async createWorkspaceInfo(
meta: WorkspaceMeta meta: WorkspaceMeta
): Promise<BlocksuiteWorkspace | undefined> { ): Promise<WorkspaceInfo> {
assert(meta.name, 'Workspace name is required');
this._logger('Creating affine workspace');
const workspaceInfo: WorkspaceInfo = { const workspaceInfo: WorkspaceInfo = {
name: meta.name, name: meta.name,
id: uuidv4(), id: uuidv4(),
@@ -92,27 +89,41 @@ export class LocalProvider extends BaseProvider {
memberCount: 1, memberCount: 1,
provider: 'local', provider: 'local',
}; };
return Promise.resolve(workspaceInfo);
}
public override async createWorkspace(
blocksuiteWorkspace: BlocksuiteWorkspace,
meta: WorkspaceMeta
): Promise<BlocksuiteWorkspace | undefined> {
const workspaceId = blocksuiteWorkspace.room;
assert(workspaceId, 'Blocksuite Workspace without room(workspaceId).');
assert(meta.name, 'Workspace name is required');
this._logger('Creating affine workspace');
const workspaceInfo: WorkspaceInfo = {
name: meta.name,
id: workspaceId,
isPublish: false,
avatar: '',
owner: undefined,
isLocal: true,
memberCount: 1,
provider: 'local',
};
this.linkLocal(blocksuiteWorkspace);
blocksuiteWorkspace.meta.setName(meta.name);
const workspace = new BlocksuiteWorkspace({ room: workspaceInfo.id });
await this.linkLocal(workspace);
workspace.meta.setName(meta.name);
if (!meta.avatar) { if (!meta.avatar) {
// set default avatar await setDefaultAvatar(blocksuiteWorkspace);
const blob = await getDefaultHeadImgBlob(meta.name); workspaceInfo.avatar = blocksuiteWorkspace.meta.avatar;
const blobStorage = await workspace.blobs;
assert(blobStorage, 'No blob storage');
const blobId = await blobStorage.set(blob);
const avatar = await blobStorage.get(blobId);
if (avatar) {
workspace.meta.setAvatar(avatar);
workspaceInfo.avatar = avatar;
}
} }
this._workspaces.add(workspaceInfo); this._workspaces.add(workspaceInfo);
this._storeWorkspaces(this._workspaces.list()); this._storeWorkspaces(this._workspaces.list());
return workspace; return blocksuiteWorkspace;
} }
public override async clear(): Promise<void> { public override async clear(): Promise<void> {

View File

@@ -0,0 +1,16 @@
import assert from 'assert';
import { Workspace as BlocksuiteWorkspace } from '@blocksuite/store';
import { getDefaultHeadImgBlob } from '../utils/index.js';
export const setDefaultAvatar = async (
blocksuiteWorkspace: BlocksuiteWorkspace
) => {
const blob = await getDefaultHeadImgBlob(blocksuiteWorkspace.meta.name);
const blobStorage = await blocksuiteWorkspace.blobs;
assert(blobStorage, 'No blob storage');
const blobId = await blobStorage.set(blob);
const avatar = await blobStorage.get(blobId);
if (avatar) {
blocksuiteWorkspace.meta.setAvatar(avatar);
}
};

View File

@@ -1,3 +1,12 @@
import { Workspace as BlocksuiteWorkspace } from '@blocksuite/store';
import { BlockSchema } from '@blocksuite/blocks/models';
export const createBlocksuiteWorkspace = (workspaceId: string) => {
return new BlocksuiteWorkspace({
room: workspaceId,
}).register(BlockSchema);
};
const DefaultHeadImgColors = [ const DefaultHeadImgColors = [
['#C6F2F3', '#0C6066'], ['#C6F2F3', '#0C6066'],
['#FFF5AB', '#896406'], ['#FFF5AB', '#896406'],