feat:merge dev

This commit is contained in:
DiamondThree
2023-01-13 14:07:17 +08:00
38 changed files with 825 additions and 938 deletions
+15 -1
View File
@@ -1,10 +1,24 @@
import { NotFoundTitle, PageContainer } from './styles';
import { useTranslation } from '@affine/i18n';
import { Button } from '@/ui/button';
import { useRouter } from 'next/router';
export const NotfoundPage = () => {
const { t } = useTranslation();
const router = useRouter();
return (
<PageContainer>
<NotFoundTitle>{t('404 - Page Not Found')}</NotFoundTitle>
<NotFoundTitle>
{t('404 - Page Not Found')}
<p>
<Button
onClick={() => {
router.push('/workspace');
}}
>
Back Home
</Button>
</p>
</NotFoundTitle>
</PageContainer>
);
};
@@ -23,7 +23,11 @@ export const WorkspaceAvatar = (props: IWorkspaceAvatar) => {
}}
>
<picture>
<img src={props.avatar} alt="" />
<img
style={{ width: sizeStr, height: sizeStr }}
src={props.avatar}
alt=""
/>
</picture>
</div>
) : (
@@ -0,0 +1,39 @@
import { useState, useEffect } from 'react';
import type { WorkspaceUnit } from '@affine/datacenter';
import { WorkspaceAvatar as Avatar } from './Avatar';
const useAvatar = (workspaceUnit?: WorkspaceUnit) => {
const [avatarUrl, setAvatarUrl] = useState('');
const avatarId =
workspaceUnit?.avatar || workspaceUnit?.blocksuiteWorkspace?.meta.avatar;
useEffect(() => {
if (avatarId && workspaceUnit?.blocksuiteWorkspace?.blobs) {
workspaceUnit.blocksuiteWorkspace.blobs.then(blobs => {
blobs?.get(avatarId).then(url => setAvatarUrl(url || ''));
});
} else {
setAvatarUrl('');
}
}, [avatarId]);
return avatarUrl;
};
export const WorkspaceUnitAvatar = ({
size = 20,
name,
workspaceUnit,
}: {
size?: number;
name?: string;
workspaceUnit?: WorkspaceUnit | null;
}) => {
const avatarUrl = useAvatar(workspaceUnit || undefined);
return (
<Avatar
size={size}
name={name || workspaceUnit?.name || ''}
avatar={avatarUrl}
/>
);
};
@@ -0,0 +1,2 @@
export { WorkspaceAvatar } from './Avatar';
export { WorkspaceUnitAvatar } from './WorkspaceUnitAvatar';
@@ -10,7 +10,7 @@ import {
AddIcon,
} from '@blocksuite/icons';
import { toast } from '@/ui/toast';
import { WorkspaceAvatar } from '@/components/workspace-avatar';
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
import { useAppState } from '@/providers/app-state-provider';
import { useRouter } from 'next/router';
import { useConfirm } from '@/providers/ConfirmProvider';
@@ -67,11 +67,7 @@ export const WorkspaceModal = ({ open, onClose }: WorkspaceModalProps) => {
marginRight: '10px',
}}
>
<WorkspaceAvatar
size={50}
name={item.name}
avatar={item.avatar ?? ''}
/>
<WorkspaceUnitAvatar size={50} workspaceUnit={item} />
</div>
<span
@@ -13,7 +13,7 @@ import { useAppState } from '@/providers/app-state-provider';
import { WorkspaceDelete } from './delete';
import { WorkspaceLeave } from './leave';
import { Upload } from '@/components/file-upload';
import { WorkspaceAvatar } from '@/components/workspace-avatar';
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
import { WorkspaceUnit } from '@affine/datacenter';
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
import { useTranslation } from '@affine/i18n';
@@ -22,34 +22,19 @@ export const GeneralPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
const [showLeave, setShowLeave] = useState<boolean>(false);
const [uploading, setUploading] = useState<boolean>(false);
const [workspaceName, setWorkspaceName] = useState<string>(workspace.name);
const { currentWorkspace } = useAppState();
const { currentWorkspace, isOwner } = useAppState();
const { updateWorkspace } = useWorkspaceHelper();
const { t } = useTranslation();
const isOwner = true;
const handleChangeWorkSpaceName = (newName: string) => {
setWorkspaceName(newName);
};
const handleClickDelete = () => {
setShowDelete(true);
};
const handleCloseDelete = () => {
setShowDelete(false);
};
const handleClickLeave = () => {
setShowLeave(true);
};
const handleCloseLeave = () => {
setShowLeave(false);
};
const handleUpdateWorkspaceName = () => {
currentWorkspace &&
updateWorkspace({ name: workspaceName }, currentWorkspace);
};
const fileChange = async (file: File) => {
// console.log('file: ', file);
setUploading(true);
const blob = new Blob([file], { type: file.type });
currentWorkspace &&
@@ -67,10 +52,10 @@ export const GeneralPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
marginRight: '20px',
}}
>
<WorkspaceAvatar
<WorkspaceUnitAvatar
size={60}
name={workspace.name}
avatar={workspace.avatar ?? ''}
workspaceUnit={workspace}
/>
</div>
<Upload
@@ -89,16 +74,19 @@ export const GeneralPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
placeholder={t('Workspace Name')}
maxLength={14}
minLength={1}
disabled={!isOwner}
onChange={handleChangeWorkSpaceName}
></Input>
<TextButton
onClick={() => {
handleUpdateWorkspaceName();
}}
style={{ marginLeft: '0px' }}
>
</TextButton>
{isOwner ? (
<TextButton
onClick={() => {
handleUpdateWorkspaceName();
}}
style={{ marginLeft: '0px' }}
>
</TextButton>
) : null}
</StyledSettingInputContainer>
<StyledSettingH2 marginTop={20}>{t('Workspace Type')}</StyledSettingH2>
<StyledSettingInputContainer>
@@ -107,25 +95,39 @@ export const GeneralPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
<StyledDeleteButtonContainer>
{isOwner ? (
<>
<Button type="danger" shape="circle" onClick={handleClickDelete}>
<Button
type="danger"
shape="circle"
onClick={() => {
setShowDelete(true);
}}
>
{t('Delete Workspace')}
</Button>
<WorkspaceDelete
open={showDelete}
onClose={handleCloseDelete}
onClose={() => {
setShowDelete(false);
}}
workspace={workspace}
/>
</>
) : (
<>
<Button type="danger" shape="circle" onClick={handleClickLeave}>
<Button
type="danger"
shape="circle"
onClick={() => {
setShowLeave(true);
}}
>
{t('Leave Workspace')}
</Button>
<WorkspaceLeave
open={showLeave}
onClose={handleCloseLeave}
workspaceName={workspaceName}
workspaceId={workspace.id}
onClose={() => {
setShowLeave(false);
}}
/>
</>
)}
@@ -12,13 +12,11 @@ import { useState } from 'react';
import { ModalCloseButton } from '@/ui/modal';
import { Button } from '@/ui/button';
import { useRouter } from 'next/router';
import {
deleteWorkspace,
getWorkspaces,
// Workspace,
} from '@/hooks/mock-data/mock';
import { WorkspaceUnit } from '@affine/datacenter';
import { Trans, useTranslation } from '@affine/i18n';
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
interface WorkspaceDeleteProps {
open: boolean;
onClose: () => void;
@@ -33,23 +31,15 @@ export const WorkspaceDelete = ({
const [deleteStr, setDeleteStr] = useState<string>('');
const { t } = useTranslation();
const router = useRouter();
const { deleteWorkSpace } = useWorkspaceHelper();
const handlerInputChange = (workspaceName: string) => {
setDeleteStr(workspaceName);
};
const handleDelete = async () => {
// const dc = await getDataCenter();
// await dc.apis.deleteWorkspace({ id: workspaceId });
// router.push(`/workspace/${nextWorkSpaceId}`);
deleteWorkspace(workspace.id);
const workspaceList = getWorkspaces();
if (workspaceList.length) {
router.push(`/workspace/${workspaceList[0].id}`);
} else {
router.push(`/workspace`);
}
await deleteWorkSpace();
onClose();
router.push(`/workspace`);
};
return (
@@ -8,30 +8,20 @@ import {
import { ModalCloseButton } from '@/ui/modal';
import { Button } from '@/ui/button';
import { useTranslation } from '@affine/i18n';
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
// import { getDataCenter } from '@affine/datacenter';
// import { useAppState } from '@/providers/app-state-provider';
interface WorkspaceDeleteProps {
open: boolean;
onClose: () => void;
workspaceName: string;
workspaceId: string;
}
export const WorkspaceLeave = ({
open,
onClose,
workspaceId,
}: WorkspaceDeleteProps) => {
export const WorkspaceLeave = ({ open, onClose }: WorkspaceDeleteProps) => {
const { leaveWorkSpace } = useWorkspaceHelper();
const { t } = useTranslation();
console.log('workspaceId: ', workspaceId);
// const router = useRouter();
// const { refreshWorkspacesMeta } = useAppState();
const handleLeave = async () => {
// const dc = await getDataCenter();
// await dc.apis.leaveWorkspace({ id: workspaceId });
// // router.push(`/workspace/${nextWorkSpaceId}`);
// refreshWorkspacesMeta();
await leaveWorkSpace();
onClose();
};
@@ -20,20 +20,20 @@ import { InviteMemberModal } from './InviteMemberModal';
import { Menu, MenuItem } from '@/ui/menu';
import { Empty } from '@/ui/empty';
import { WorkspaceUnit } from '@affine/datacenter';
import { useTemporaryHelper } from '@/providers/temporary-helper-provider';
import { useConfirm } from '@/providers/ConfirmProvider';
import { toast } from '@/ui/toast';
import useMembers from '@/hooks/use-members';
import Loading from '@/components/loading';
import { Wrapper } from '@/ui/layout';
import { useTranslation } from '@affine/i18n';
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
export const MembersPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
const [isInviteModalShow, setIsInviteModalShow] = useState(false);
const { members, removeMember, loaded } = useMembers();
const { t } = useTranslation();
// FIXME: DELETE THIS
const { user, login, updateWorkspaceMeta } = useTemporaryHelper();
const { enableWorkspace } = useWorkspaceHelper();
const { confirm } = useConfirm();
if (workspace.provider === 'affine') {
@@ -164,22 +164,8 @@ export const MembersPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
<Button
type="primary"
shape="circle"
onClick={() => {
confirm({
title: `${t('Enable AFFiNE Cloud')}?`,
content: t('Enable AFFiNE Cloud Description'),
confirmText: user ? t('Enable') : t('Sign in and Enable'),
cancelText: t('Skip'),
}).then(confirm => {
if (confirm) {
if (user) {
updateWorkspaceMeta(workspace.id, { isPublish: true });
} else {
login();
updateWorkspaceMeta(workspace.id, { isPublish: true });
}
}
});
onClick={async () => {
await enableWorkspace();
}}
>
{t('Enable AFFiNE Cloud')}
@@ -1,7 +1,7 @@
import { Avatar, WorkspaceName, SelectorWrapper } from './styles';
import { useEffect, useState } from 'react';
import { WorkspaceModal } from '@/components/workspace-modal';
import { WorkspaceAvatar } from '@/components/workspace-avatar';
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
import { useAppState } from '@/providers/app-state-provider';
export const WorkspaceSelector = () => {
const [workspaceListShow, setWorkspaceListShow] = useState(false);
@@ -23,17 +23,17 @@ export const WorkspaceSelector = () => {
<Avatar
alt="Affine"
data-testid="workspace-avatar"
src={currentWorkspace?.avatar}
// src={currentWorkspace?.avatar}
>
<div
style={{
float: 'left',
}}
>
<WorkspaceAvatar
<WorkspaceUnitAvatar
size={28}
name={currentWorkspace?.name ?? 'AFFiNE'}
avatar={currentWorkspace?.avatar ?? ''}
workspaceUnit={currentWorkspace}
/>
</div>
</Avatar>
-160
View File
@@ -1,160 +0,0 @@
export interface Workspace {
name: string; // 名称
id: string; //唯一标识
isPublish?: boolean; // 是否公开
isLocal?: boolean; // 是否全部数据都在本地
avatar?: string; // 封面
type: 'local' | 'cloud' | 'join'; // cloud: 云端(本次暂不支持),local: 本地,join : 加入别人的
workspaceOwner?: User; // 本地工作空间的拥有者
}
export interface User {
name: string;
id: string;
email: string;
avatar: string;
}
export function updateWorkspaceMeta(
workspaceId: string,
workspaceData: {
name?: string;
avatar?: string;
type?: 'local' | 'cloud' | 'join';
}
) {
const workspacesMeta = getWorkspaces();
const newWorkspacesMeta = workspacesMeta.map((workspace: Workspace) => {
if (workspace.id === workspaceId) {
workspaceData.name && (workspace.name = workspaceData.name);
workspaceData.avatar && (workspace.avatar = workspaceData.avatar);
workspaceData.type && (workspace.type = workspaceData.type);
return workspaceData;
}
return workspace;
});
localStorage.setItem('affine-workspace', JSON.stringify(newWorkspacesMeta));
const activeWorkspace = getActiveWorkspace();
workspaceData.name && (activeWorkspace.name = workspaceData.name);
workspaceData.avatar && (activeWorkspace.avatar = workspaceData.avatar);
workspaceData.type && (activeWorkspace.type = workspaceData.type);
console.log(workspaceData);
setActiveWorkspace(activeWorkspace);
}
export function createWorkspace(workspaceName: string) {
const workspaceId = 'workspace-' + Date.now();
const workspaceData = {
name: workspaceName,
id: workspaceId,
isPublish: false,
isLocal: true,
avatar: '',
type: 'local',
} as Workspace;
const workspacesMeta = getWorkspaces();
workspacesMeta.push(workspaceData);
localStorage.setItem('affine-workspace', JSON.stringify(workspacesMeta));
setActiveWorkspace(workspaceData);
return { workspaceId };
}
export function getWorkspaces(): Workspace[] {
const workspacesMeta = JSON.parse(
localStorage.getItem('affine-workspace') ?? '[]'
);
return workspacesMeta;
}
export function deleteWorkspace(workspaceId: string) {
const workspacesMeta = getWorkspaces();
const newWorkspacesMeta = workspacesMeta.filter(() => {
return workspaceId !== workspaceId;
});
localStorage.setItem('affine-workspace', JSON.stringify(newWorkspacesMeta));
}
export function getMembers(id: string): User[] {
const memberMap = JSON.parse(localStorage.getItem('affine-member') ?? '{}');
return memberMap[id] || [];
}
export function setMember(workspaceId: string, member: User) {
const memberMap = JSON.parse(localStorage.getItem('affine-member') ?? '{}');
memberMap[workspaceId] = memberMap[workspaceId] || [];
memberMap[workspaceId].push(member);
localStorage.setItem('affine-member', JSON.stringify(memberMap));
}
export function deleteMember(workspaceId: string, index: number) {
const memberMap = JSON.parse(localStorage.getItem('affine-member') ?? '{}');
const memberList = memberMap[workspaceId];
memberList.splice(index, 1);
memberMap[workspaceId] = memberList;
localStorage.setItem('affine-member', JSON.stringify(memberMap));
}
export function leaveWorkspace() {
return true;
}
export function setWorkspacePublish(id: string, isPublish: boolean): boolean {
const workspacesMeta = getWorkspaces();
const newWorkspacesMeta = workspacesMeta.map((workspace: Workspace) => {
if (workspace.id === id) {
workspace.isPublish = isPublish;
}
return workspace;
});
localStorage.setItem('affine-workspace', JSON.stringify(newWorkspacesMeta));
return isPublish;
}
export function getWorkspaceById(id: string) {
const workspacesMeta = getWorkspaces();
return workspacesMeta.find((workspace: Workspace) => {
return workspace.id === id;
});
}
export function getPagesByWorkspaceId(workspaceId: string) {
if (!workspaceId) return [];
const workspacesMeta = [];
for (let i = 0; i < 10; i++) {
workspacesMeta.push({
id: 'page-' + i,
name: 'page ' + i,
});
}
}
export function getActiveWorkspace(): Workspace {
return JSON.parse(localStorage.getItem('affine-active-workspace') ?? '{}');
}
export function setActiveWorkspace(workspaceData: Workspace) {
console.log('workspaceData: ', workspaceData);
localStorage.setItem(
'affine-active-workspace',
JSON.stringify(workspaceData)
);
}
export function getUserInfo(): User {
return JSON.parse(localStorage.getItem('affine-user') ?? 'null');
}
export function Login(): void {
localStorage.setItem(
'affine-user',
JSON.stringify({
name: 'Diamond',
id: 'ttt',
email: 'diamond.shx@gmail.com',
avatar: 'string',
})
);
}
export function SignOut(): void {
localStorage.removeItem('affine-user');
}
+22 -3
View File
@@ -44,22 +44,41 @@ export const useWorkspaceHelper = () => {
cancelText: 'Skip',
}).then(async confirm => {
if (confirm && currentWorkspace) {
if (user) {
if (!user) {
await login();
}
const newWorkspaceId = await dataCenter.enableWorkspaceCloud(
const workspace = await dataCenter.enableWorkspaceCloud(
currentWorkspace
);
router.push(`/workspace/${newWorkspaceId}/setting`);
workspace && router.push(`/workspace/${workspace.id}/setting`);
toast('Enabled success');
}
});
};
const deleteWorkSpace = async () => {
currentWorkspace &&
(await dataCenter.deleteWorkspace(currentWorkspace?.id));
};
const leaveWorkSpace = async () => {
currentWorkspace && (await dataCenter.leaveWorkspace(currentWorkspace?.id));
};
const acceptInvite = async (inviteCode: string) => {
let inviteInfo;
if (inviteCode) {
inviteInfo = await dataCenter.acceptInvitation(inviteCode);
}
return inviteInfo;
};
return {
createWorkspace,
publishWorkspace,
updateWorkspace,
enableWorkspace,
deleteWorkSpace,
leaveWorkSpace,
acceptInvite,
};
};
-4
View File
@@ -1,9 +1,5 @@
import NotfoundPage from '@/components/404';
import { useAppState } from '@/providers/app-state-provider';
export default function Custom404() {
const { workspaceList } = useAppState();
console.log('workspaceList: ', workspaceList);
return <NotfoundPage></NotfoundPage>;
}
-3
View File
@@ -21,7 +21,6 @@ import { useAppState } from '@/providers/app-state-provider';
import { PageLoading } from '@/components/loading';
import Head from 'next/head';
import '@affine/i18n';
import TemporaryHelperProvider from '@/providers/temporary-helper-provider';
const ThemeProvider = dynamic(() => import('@/providers/ThemeProvider'), {
ssr: false,
@@ -55,10 +54,8 @@ const App = ({ Component, pageProps }: AppPropsWithLayout) => {
<Logger />
<ProviderComposer
contexts={[
<TemporaryHelperProvider key="TemporaryHelperProvider" />,
<ThemeProvider key="ThemeProvider" />,
<AppStateProvider key="appStateProvider" />,
// <AppStateProvider2 key="appStateProvider2" />,
<ModalProvider key="ModalProvider" />,
<ConfirmProvider key="ConfirmProvider" />,
]}
+29 -35
View File
@@ -1,44 +1,37 @@
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
import { useAppState } from '@/providers/app-state-provider';
import { styled } from '@/styles';
import { Empty } from '@/ui/empty';
import { Avatar } from '@mui/material';
// import { getDataCenter } from '@affine/datacenter';
// import { Avatar } from '@mui/material';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
const User = ({ name, avatar }: { name: string; avatar?: string }) => {
return (
<UserContent>
{avatar ? (
<Avatar src={avatar}></Avatar>
) : (
<UserIcon>{name.slice(0, 1)}</UserIcon>
)}
<span>{name}</span>
</UserContent>
);
};
// const User = ({ name, avatar }: { name: string; avatar?: string }) => {
// return (
// <UserContent>
// {avatar ? (
// <Avatar src={avatar}></Avatar>
// ) : (
// <UserIcon>{name.slice(0, 1)}</UserIcon>
// )}
// <span>{name}</span>
// </UserContent>
// );
// };
export default function DevPage() {
const router = useRouter();
const [successInvited, setSuccessInvited] = useState<boolean>(false);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const [inviteData, setInviteData] = useState<any>(null);
const { acceptInvite } = useWorkspaceHelper();
const { user } = useAppState();
useEffect(() => {
// getDataCenter()
// .then(dc =>
// dc.apis.acceptInviting({
// invitingCode: router.query.invite_code as string,
// })
// )
// .then(data => {
// setSuccessInvited(true);
// setInviteData(data);
// })
// .catch(err => {
// console.log('err: ', err);
// });
setSuccessInvited(true);
setInviteData(null);
router.query.invite_code &&
acceptInvite(router.query.invite_code as string).then(data => {
if (data && data.accepted) {
setSuccessInvited(true);
user && router.push(`/workspace/${data.workspace_id}`);
}
});
}, [router.query.invite_code]);
return (
@@ -47,11 +40,12 @@ export default function DevPage() {
<Empty width={310} height={310}></Empty>
<Content>
<User name={inviteData?.name ? inviteData.name : '-'}></User> invited
you to join
<User
{/* TODO add inviteInfo*/}
{/* <User name={inviteData?.name ? inviteData.name : '-'}></User> invited */}
{/* you to join */}
{/* <User
name={inviteData?.workspaceName ? inviteData.workspaceName : '-'}
></User>
></User> */}
{successInvited ? (
<Status>
<svg
@@ -1,47 +0,0 @@
import { WorkspaceModal } from '@/components/workspace-modal';
import { getWorkspaces } from '@/hooks/mock-data/mock';
import { useEffect, useState } from 'react';
import { styled } from '@/styles';
import Button from '@/ui/button/Button';
const Page = () => {
const [open, setOpen] = useState<boolean>(false);
useEffect(() => {
const data = getWorkspaces();
if (!data.length) {
setOpen(true);
}
}, []);
return (
<Workspace>
<h1>workspace</h1>
<div>
<Button
onClick={() => {
setOpen(true);
}}
>
View Workspace List
</Button>
</div>
<WorkspaceModal
open={open}
onClose={() => {
setOpen(false);
}}
></WorkspaceModal>
</Workspace>
);
};
export default Page;
const Workspace = styled.div(({ theme }) => {
return {
height: '100vh',
background: theme.colors.pageBackground,
color: '#FFFFFF',
fontSize: '18px',
fontWeight: 500,
};
});
@@ -61,8 +61,9 @@ const tabMap: {
];
const WorkspaceSetting = () => {
const { currentWorkspace } = useAppState();
const { t } = useTranslation();
const { currentWorkspace, isOwner } = useAppState();
const [activeTab, setActiveTab] = useState<TabNames>(tabMap[0].name);
const handleTabChange = (tab: TabNames) => {
setActiveTab(tab);
@@ -71,7 +72,20 @@ const WorkspaceSetting = () => {
const activeTabPanelRender = tabMap.find(
tab => tab.name === activeTab
)?.panelRender;
let tableArr: {
name: TabNames;
icon: ReactNode;
panelRender: (workspace: WorkspaceUnit) => ReactNode;
}[] = tabMap;
if (!isOwner) {
tableArr = [
{
name: 'general',
icon: <EditIcon />,
panelRender: workspace => <GeneralPage workspace={workspace} />,
},
];
}
return (
<StyledSettingContainer>
<StyledSettingSidebar>
@@ -79,7 +93,7 @@ const WorkspaceSetting = () => {
{t('Workspace Settings')}
</StyledSettingSidebarHeader>
<StyledSettingTabContainer>
{tabMap.map(({ icon, name }) => {
{tableArr.map(({ icon, name }) => {
return (
<WorkspaceSettingTagItem
key={name}
@@ -37,6 +37,7 @@ export const AppStateProvider = ({
currentPage: null,
editor: null,
synced: true,
isOwner: false,
});
};
@@ -91,7 +92,7 @@ export const AppStateProvider = ({
const loadWorkspace = useRef<AppStateFunction['loadWorkspace']>();
loadWorkspace.current = async (workspaceId: string) => {
const { dataCenter, workspaceList, currentWorkspace } = appState;
const { dataCenter, workspaceList, currentWorkspace, user } = appState;
if (!workspaceList.find(v => v.id.toString() === workspaceId)) {
return null;
}
@@ -99,6 +100,13 @@ export const AppStateProvider = ({
return currentWorkspace;
}
const workspace = (await dataCenter.loadWorkspace(workspaceId)) ?? null;
let isOwner;
if (workspace.provider === 'local') {
// isOwner is useful only in the cloud
isOwner = true;
} else {
isOwner = workspace?.owner && user?.id === workspace?.owner?.id;
}
const pageList =
(workspace?.blocksuiteWorkspace?.meta.pageMetas as PageMeta[]) ?? [];
setAppState({
@@ -107,6 +115,7 @@ export const AppStateProvider = ({
pageList: pageList,
currentPage: null,
editor: null,
isOwner,
});
return workspace;
@@ -23,6 +23,7 @@ export type AppStateValue = {
currentPage: StorePage | null;
editor?: EditorContainer | null;
synced: boolean;
isOwner?: boolean;
};
export type AppStateFunction = {
@@ -1,183 +0,0 @@
import { createContext, useContext, useEffect, useState } from 'react';
import type { PropsWithChildren } from 'react';
import { getWorkspaces, User, Workspace } from '@/hooks/mock-data/mock';
type TemporaryContextValue = {
workspaceMetaList: Workspace[];
currentWorkspace: Workspace | null;
user: User | null;
updateWorkspaceMeta: (
workspaceId: string,
workspaceData: {
name?: string;
avatar?: string;
isPublish?: boolean;
type?: 'local' | 'cloud' | 'join';
}
) => void;
createWorkspace: (workspaceName: string) => Workspace;
deleteWorkspace: (workspaceId: string) => void;
setWorkspacePublish: (id: string, isPublish: boolean) => void;
setActiveWorkspace: (workspaceData: Workspace) => void;
login: () => void;
signOut: () => void;
};
type TemporaryContextProps = PropsWithChildren<Record<string, unknown>>;
export const TemporaryContext = createContext<TemporaryContextValue>({
workspaceMetaList: [],
currentWorkspace: null,
user: null,
// eslint-disable-next-line @typescript-eslint/no-empty-function
updateWorkspaceMeta: () => {},
createWorkspace: () => {
return {} as Workspace;
},
// eslint-disable-next-line @typescript-eslint/no-empty-function
deleteWorkspace: () => {},
// eslint-disable-next-line @typescript-eslint/no-empty-function
setWorkspacePublish: () => {},
// eslint-disable-next-line @typescript-eslint/no-empty-function
setActiveWorkspace: () => {},
// eslint-disable-next-line @typescript-eslint/no-empty-function
login: () => {},
// eslint-disable-next-line @typescript-eslint/no-empty-function
signOut: () => {},
});
export const useTemporaryHelper = () => useContext(TemporaryContext);
export const TemporaryHelperProvider = ({
children,
}: PropsWithChildren<TemporaryContextProps>) => {
const [workspaceMetaList, setWorkspaceMetaList] = useState<Workspace[]>([]);
const [currentWorkspace, setCurrentWorkspace] = useState<Workspace | null>(
null
);
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
setWorkspaceMetaList(getWorkspaces());
setCurrentWorkspace(
JSON.parse(localStorage.getItem('affine-active-workspace') ?? '{}')
);
}, []);
return (
<TemporaryContext.Provider
value={{
workspaceMetaList,
currentWorkspace,
user,
updateWorkspaceMeta: (workspaceId, workspaceData) => {
const workspacesMeta = getWorkspaces();
const newWorkspacesMeta = workspacesMeta.map(
(workspace: Workspace) => {
if (workspace.id === workspaceId) {
const workspaceObj = Object.assign(workspace, workspaceData);
return workspaceObj;
}
return workspace;
}
);
localStorage.setItem(
'affine-workspace',
JSON.stringify(newWorkspacesMeta)
);
setWorkspaceMetaList(newWorkspacesMeta);
if (workspaceId === currentWorkspace?.id) {
setCurrentWorkspace(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
newWorkspacesMeta.find(v => v.id === currentWorkspace.id)
);
}
},
createWorkspace: workspaceName => {
const workspaceData = {
name: workspaceName,
id: 'workspace-' + Date.now(),
isPublish: false,
isLocal: true,
avatar: '',
type: 'local',
} as Workspace;
const workspacesMeta = getWorkspaces();
workspacesMeta.push(workspaceData);
localStorage.setItem(
'affine-workspace',
JSON.stringify(workspacesMeta)
);
setWorkspaceMetaList([...workspacesMeta]);
return workspaceData;
},
deleteWorkspace: workspaceId => {
const workspacesMeta = getWorkspaces();
const newWorkspacesMeta = workspacesMeta.filter(() => {
return workspaceId !== workspaceId;
});
localStorage.setItem(
'affine-workspace',
JSON.stringify(newWorkspacesMeta)
);
setWorkspaceMetaList(workspacesMeta);
},
setWorkspacePublish: (id, isPublish) => {
const workspacesMeta = getWorkspaces();
const newWorkspacesMeta = workspacesMeta.map(
(workspace: Workspace) => {
if (workspace.id === id) {
workspace.isPublish = isPublish;
}
return workspace;
}
);
localStorage.setItem(
'affine-workspace',
JSON.stringify(newWorkspacesMeta)
);
setWorkspaceMetaList(workspacesMeta);
},
setActiveWorkspace(workspaceData) {
localStorage.setItem(
'affine-active-workspace',
JSON.stringify(workspaceData)
);
setCurrentWorkspace(workspaceData);
},
login: () => {
const userInfo = {
name: 'Diamond',
id: 'ttt',
email: 'diamond.shx@gmail.com',
avatar: 'string',
};
localStorage.setItem('affine-user', JSON.stringify(userInfo));
setUser(userInfo);
},
signOut(): void {
localStorage.removeItem('affine-user');
setUser(null);
},
}}
>
{children}
</TemporaryContext.Provider>
);
};
export default TemporaryHelperProvider;
+23 -47
View File
@@ -23,7 +23,7 @@ export class DataCenter {
private readonly _workspaceUnitCollection = new WorkspaceUnitCollection();
private readonly _logger = getLogger('dc');
private _workspaceInstances: Map<string, BlocksuiteWorkspace> = new Map();
private _messageCenter = new MessageCenter();
private _messageCenter = MessageCenter.getInstance();
/**
* A mainProvider must exist as the only data trustworthy source.
*/
@@ -96,12 +96,7 @@ export class DataCenter {
'There is no provider. You should add provider first.'
);
const workspaceMeta = await this._mainProvider.createWorkspaceInfo(params);
const workspace = createBlocksuiteWorkspace(workspaceMeta.id);
await this._mainProvider.createWorkspace(workspace, workspaceMeta);
const workspaceUnit = this._workspaceUnitCollection.find(workspaceMeta.id);
const workspaceUnit = await this._mainProvider.createWorkspace(params);
return workspaceUnit;
}
@@ -169,7 +164,9 @@ export class DataCenter {
this._workspaceInstances.set(workspaceId, workspace);
await provider.warpWorkspace(workspace);
this._workspaceUnitCollection.workspaces.forEach(workspaceUnit => {
workspaceUnit.setBlocksuiteWorkspace(null);
const provider = this.providerMap.get(workspaceUnit.provider);
assert(provider);
provider.closeWorkspace(workspaceUnit.id);
});
workspaceUnit.setBlocksuiteWorkspace(workspace);
return workspaceUnit;
@@ -322,55 +319,33 @@ export class DataCenter {
}
}
private async _transWorkspaceProvider(
workspace: BlocksuiteWorkspace,
providerId: string
public async enableProvider(
workspaceUnit: WorkspaceUnit,
providerId = 'affine'
) {
assert(workspace.room, 'No workspace id');
const workspaceInfo = this._workspaceUnitCollection.find(workspace.room);
assert(workspaceInfo, 'Workspace not found');
if (workspaceInfo.provider === providerId) {
if (workspaceUnit.provider === providerId) {
this._logger('Workspace provider is same');
return;
}
const currentProvider = this.providerMap.get(workspaceInfo.provider);
assert(currentProvider, 'Provider not found');
const newProvider = this.providerMap.get(providerId);
assert(newProvider, `provide '${providerId}' is not registered`);
this._logger(`create ${providerId} workspace: `, workspaceInfo.name);
const newWorkspaceInfo = await newProvider.createWorkspaceInfo({
name: workspaceInfo.name,
// avatar: workspaceInfo.avatar,
});
const newWorkspace = createBlocksuiteWorkspace(newWorkspaceInfo.id);
// TODO optimize this function
await newProvider.createWorkspace(newWorkspace, {
...newWorkspaceInfo,
name: workspaceInfo.name,
avatar: workspaceInfo.avatar,
});
const provider = this.providerMap.get(providerId);
assert(provider);
const newWorkspaceUnit = await provider.extendWorkspace(workspaceUnit);
assert(newWorkspace, 'Create workspace failed');
this._logger(
`update workspace data from ${workspaceInfo.provider} to ${providerId}`
);
await newProvider.assign(newWorkspace, workspace);
assert(newWorkspace, 'Create workspace failed');
await currentProvider.deleteWorkspace(workspace.room);
return newWorkspace.room;
// Currently we only allow enable one provider, so after enable new provider,
// delete the old workspace from its provider.
const oldProvider = this.providerMap.get(workspaceUnit.provider);
assert(oldProvider);
await oldProvider.deleteWorkspace(workspaceUnit.id);
return newWorkspaceUnit;
}
/**
* Enable workspace cloud
* @param {string} id ID of workspace.
*/
public async enableWorkspaceCloud(workspace: WorkspaceUnit) {
assert(workspace?.id, 'No workspace to enable cloud');
assert(workspace.blocksuiteWorkspace);
return await this._transWorkspaceProvider(
workspace.blocksuiteWorkspace,
'affine'
);
public async enableWorkspaceCloud(workspaceUnit: WorkspaceUnit) {
return this.enableProvider(workspaceUnit);
}
/**
@@ -441,13 +416,14 @@ export class DataCenter {
/**
* accept invitation
* @param {string} inviteCode
* @returns {Promise<Permission | null>} permission
*/
async acceptInvitation(inviteCode: string, providerStr = 'affine') {
const provider = this.providerMap.get(providerStr);
if (provider) {
return await provider.acceptInvitation(inviteCode);
}
return [];
return null;
}
onMessage(cb: (message: Message) => void) {
+58
View File
@@ -1,3 +1,61 @@
export enum MessageCode {
loginError,
noPermission,
loadListFailed,
getDetailFailed,
createWorkspaceFailed,
getMembersFailed,
updateWorkspaceFailed,
deleteWorkspaceFailed,
inviteMemberFailed,
removeMemberFailed,
acceptInvitingFailed,
getBlobFailed,
leaveWorkspaceFailed,
downloadWorkspaceFailed,
}
export const messages = {
[MessageCode.loginError]: {
message: 'Login failed',
},
[MessageCode.noPermission]: {
message: 'No permission',
},
[MessageCode.loadListFailed]: {
message: 'Load list failed',
},
[MessageCode.getDetailFailed]: {
message: 'Get detail failed',
},
[MessageCode.createWorkspaceFailed]: {
message: 'Create workspace failed',
},
[MessageCode.getMembersFailed]: {
message: 'Get members failed',
},
[MessageCode.updateWorkspaceFailed]: {
message: 'Update workspace failed',
},
[MessageCode.deleteWorkspaceFailed]: {
message: 'Delete workspace failed',
},
[MessageCode.inviteMemberFailed]: {
message: 'Invite member failed',
},
[MessageCode.removeMemberFailed]: {
message: 'Remove member failed',
},
[MessageCode.acceptInvitingFailed]: {
message: 'Accept inviting failed',
},
[MessageCode.getBlobFailed]: {
message: 'Get blob failed',
},
[MessageCode.leaveWorkspaceFailed]: {
message: 'Leave workspace failed',
},
[MessageCode.downloadWorkspaceFailed]: {
message: 'Download workspace failed',
},
};
+22 -10
View File
@@ -1,24 +1,36 @@
import { Observable } from 'lib0/observable';
import { Message } from '../types';
import { MessageCode } from './code.js';
import { MessageCode, messages } from './code.js';
export class MessageCenter extends Observable<string> {
private _messages: Record<number, Omit<Message, 'provider' | 'code'>> =
messages;
constructor() {
super();
}
public send(message: MessageCode) {
this.emit('message', [message]);
static instance: MessageCenter;
static getInstance() {
if (!MessageCenter.instance) {
MessageCenter.instance = new MessageCenter();
}
return MessageCenter.instance;
}
static messageCode = MessageCode;
public getMessageSender(provider: string) {
return this._send.bind(this, provider);
}
private _send(provider: string, messageCode: MessageCode) {
this.emit('message', [
{ ...this._messages[messageCode], provider, code: messageCode },
]);
}
public onMessage(callback: (message: Message) => void) {
this.on('message', callback);
}
private messages: Record<number, Message> = {
[MessageCode.loginError]: {
code: MessageCode.loginError,
message: 'Login failed',
},
};
}
+107 -156
View File
@@ -2,22 +2,26 @@ import { BaseProvider } from '../base.js';
import type {
ProviderConstructorParams,
CreateWorkspaceInfoParams,
WorkspaceMeta0,
} from '../base';
import type { User } from '../../types';
import { Workspace as BlocksuiteWorkspace } from '@blocksuite/store';
import { BlockSchema } from '@blocksuite/blocks/models';
import { storage } from './storage.js';
import assert from 'assert';
import { WebsocketProvider } from './sync.js';
// import { IndexedDBProvider } from '../local/indexeddb';
import { getApis, Workspace } from './apis/index.js';
import type { Apis, WorkspaceDetail, Callback } from './apis';
import { setDefaultAvatar } from '../utils.js';
import { MessageCode } from '../../message';
import { token } from './apis/token.js';
import { WebsocketClient } from './channel';
import { SyncMode } from '../../workspace-unit';
import {
loadWorkspaceUnit,
createWorkspaceUnit,
syncToCloud,
} from './utils.js';
import { WorkspaceUnit } from '../../workspace-unit.js';
import { createBlocksuiteWorkspace, applyUpdate } from '../../utils/index.js';
import type { SyncMode } from '../../workspace-unit';
import { MessageCenter } from '../../message/index.js';
type ChannelMessage = {
ws_list: Workspace[];
@@ -31,12 +35,11 @@ export interface AffineProviderConstructorParams
}
const {
Y: { applyUpdate, encodeStateAsUpdate },
Y: { encodeStateAsUpdate },
} = BlocksuiteWorkspace;
export class AffineProvider extends BaseProvider {
public id = 'affine';
private _workspacesCache: Map<string, BlocksuiteWorkspace> = new Map();
private _onTokenRefresh?: Callback = undefined;
private _wsMap: Map<string, WebsocketProvider> = new Map();
private _apis: Apis;
@@ -95,14 +98,23 @@ export class AffineProvider extends BaseProvider {
);
}
this._channel.on('message', (msg: ChannelMessage) => {
this.handlerAffineListMessage(msg);
this._handlerAffineListMessage(msg);
});
}
private handlerAffineListMessage({ ws_details, metadata }: ChannelMessage) {
private async _handlerAffineListMessage({
ws_details,
metadata,
}: ChannelMessage) {
this._logger('receive server message');
Object.entries(ws_details).forEach(([id, detail]) => {
const addedWorkspaces: WorkspaceUnit[] = [];
const removeWorkspaceList = this._workspaces.list().map(w => w.id);
for (const [id, detail] of Object.entries(ws_details)) {
const { name, avatar } = metadata[id];
const index = removeWorkspaceList.indexOf(id);
if (index !== -1) {
removeWorkspaceList.splice(index, 1);
}
assert(name);
const workspace = {
name: name,
@@ -115,15 +127,24 @@ export class AffineProvider extends BaseProvider {
},
published: detail.public,
memberCount: detail.member_count,
provider: 'affine',
provider: this.id,
syncMode: 'core' as SyncMode,
};
if (this._workspaces.get(id)) {
// update workspaces
this._workspaces.update(id, workspace);
} else {
this._workspaces.add({ id, ...workspace });
const workspaceUnit = await loadWorkspaceUnit(
{ id, ...workspace },
this._apis
);
addedWorkspaces.push(workspaceUnit);
}
});
}
// add workspaces
this._workspaces.add(addedWorkspaces);
// remove workspaces
this._workspaces.remove(removeWorkspaceList);
}
private _getWebsocketProvider(workspace: BlocksuiteWorkspace) {
@@ -147,17 +168,10 @@ export class AffineProvider extends BaseProvider {
blocksuiteWorkspace: BlocksuiteWorkspace,
published = false
) {
const { doc, room: workspaceId } = blocksuiteWorkspace;
const { room: workspaceId } = blocksuiteWorkspace;
assert(workspaceId, 'Blocksuite Workspace without room(workspaceId).');
const updates = await this._apis.downloadWorkspace(workspaceId, published);
if (updates && updates.byteLength) {
await new Promise(resolve => {
doc.once('update', () => {
setTimeout(resolve, 100);
});
BlocksuiteWorkspace.Y.applyUpdate(doc, new Uint8Array(updates));
});
}
await applyUpdate(blocksuiteWorkspace, new Uint8Array(updates));
}
override async loadPublicWorkspace(blocksuiteWorkspace: BlocksuiteWorkspace) {
@@ -194,79 +208,25 @@ export class AffineProvider extends BaseProvider {
return [];
}
const workspacesList = await this._apis.getWorkspaces();
const workspaces: WorkspaceMeta0[] = workspacesList.map(w => {
return {
...w,
published: w.public,
memberCount: 0,
name: '',
provider: 'affine',
syncMode: 'core',
};
});
const workspaceInstances = workspaces.map(({ id }) => {
const workspace =
this._workspacesCache.get(id) ||
new BlocksuiteWorkspace({
room: id,
}).register(BlockSchema);
this._workspacesCache.set(id, workspace);
if (workspace) {
return new Promise<BlocksuiteWorkspace>(resolve => {
this._apis.downloadWorkspace(id).then(data => {
applyUpdate(workspace.doc, new Uint8Array(data));
resolve(workspace);
});
});
} else {
return Promise.resolve(null);
}
});
(await Promise.all(workspaceInstances)).forEach((workspace, i) => {
if (workspace) {
workspaces[i] = {
...workspaces[i],
name: workspace.meta.name,
avatar: workspace.meta.avatar,
};
}
});
const getDetailList = workspacesList.map(w => {
const { id } = w;
return new Promise<{ id: string; detail: WorkspaceDetail | null }>(
resolve => {
this._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;
}
}
}
});
workspaces.forEach(workspace => {
this._workspaces.add(workspace);
});
return workspaces;
const workspaceUnits = await Promise.all(
workspacesList.map(w => {
return loadWorkspaceUnit(
{
id: w.id,
name: '',
avatar: undefined,
owner: undefined,
published: w.public,
memberCount: 1,
provider: this.id,
syncMode: 'core',
},
this._apis
);
})
);
this._workspaces.add(workspaceUnits);
return workspaceUnits;
}
override async auth() {
@@ -283,7 +243,7 @@ export class AffineProvider extends BaseProvider {
this._connectChannel();
}
if (!user) {
this._messageCenter.send(MessageCode.loginError);
this._sendMessage(MessageCenter.messageCode.loginError);
}
}
@@ -309,17 +269,17 @@ export class AffineProvider extends BaseProvider {
}
public override async clear(): Promise<void> {
for (const w of this._workspacesCache.values()) {
if (w.room) {
for (const w of this._workspaces.list()) {
if (w.id) {
try {
await this.deleteWorkspace(w.room);
this._workspaces.remove(w.room);
await this.deleteWorkspace(w.id);
this._workspaces.remove(w.id);
} catch (e) {
this._logger('has a problem of delete workspace ', e);
}
}
}
this._workspacesCache.clear();
this._workspaces.clear();
}
public override async closeWorkspace(id: string) {
@@ -353,52 +313,29 @@ export class AffineProvider extends BaseProvider {
// return workspace;
}
public override async createWorkspaceInfo(
public override async createWorkspace(
meta: CreateWorkspaceInfoParams
): Promise<WorkspaceMeta0> {
): Promise<WorkspaceUnit | undefined> {
const { id } = await this._apis.createWorkspace(meta);
const workspaceInfo: WorkspaceMeta0 = {
const workspaceUnit = await createWorkspaceUnit({
id,
name: meta.name,
id: id,
published: false,
avatar: '',
avatar: undefined,
owner: await this.getUserInfo(),
syncMode: 'core',
memberCount: 1,
provider: 'affine',
};
return workspaceInfo;
}
public override async createWorkspace(
blocksuiteWorkspace: BlocksuiteWorkspace,
meta: WorkspaceMeta0
): 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: WorkspaceMeta0 = {
name: meta.name,
id: workspaceId,
published: false,
avatar: '',
owner: undefined,
syncMode: 'core',
memberCount: 1,
provider: 'affine',
};
provider: this.id,
syncMode: 'core',
});
if (!blocksuiteWorkspace.meta.avatar) {
await setDefaultAvatar(blocksuiteWorkspace);
workspaceInfo.avatar = blocksuiteWorkspace.meta.avatar;
}
this._workspaces.add(workspaceInfo);
return blocksuiteWorkspace;
await syncToCloud(
workspaceUnit.blocksuiteWorkspace!,
this._apis.token.refresh
);
this._workspaces.add(workspaceUnit);
return workspaceUnit;
}
public override async publish(id: string, isPublish: boolean): Promise<void> {
@@ -420,22 +357,36 @@ export class AffineProvider extends BaseProvider {
: null;
}
public override async assign(
to: BlocksuiteWorkspace,
from: BlocksuiteWorkspace
): Promise<BlocksuiteWorkspace> {
assert(to.room, 'Blocksuite Workspace without room(workspaceId).');
const ws = this._getWebsocketProvider(to);
applyUpdate(to.doc, encodeStateAsUpdate(from.doc));
// TODO: upload blobs and make sure doc is synced
await new Promise<void>((resolve, reject) => {
ws.once('synced', () => {
setTimeout(() => resolve(), 1000);
});
ws.once('lost-connection', () => reject());
ws.once('connection-error', () => reject());
public override async extendWorkspace(
workspaceUnit: WorkspaceUnit
): Promise<WorkspaceUnit> {
const { id } = await this._apis.createWorkspace({
name: workspaceUnit.name,
});
return to;
const newWorkspaceUnit = new WorkspaceUnit({
id,
name: workspaceUnit.name,
avatar: undefined,
owner: await this.getUserInfo(),
published: false,
memberCount: 1,
provider: this.id,
syncMode: 'core',
});
const blocksuiteWorkspace = createBlocksuiteWorkspace(id);
assert(workspaceUnit.blocksuiteWorkspace);
await applyUpdate(
blocksuiteWorkspace,
encodeStateAsUpdate(workspaceUnit.blocksuiteWorkspace.doc)
);
await syncToCloud(blocksuiteWorkspace, this._apis.token.refresh);
newWorkspaceUnit.setBlocksuiteWorkspace(blocksuiteWorkspace);
this._workspaces.add(newWorkspaceUnit);
return newWorkspaceUnit;
}
public override async logout(): Promise<void> {
@@ -449,7 +400,7 @@ export class AffineProvider extends BaseProvider {
return this._apis.getWorkspaceMembers({ id });
}
public override async acceptInvitation(invitingCode: string): Promise<void> {
await this._apis.acceptInviting({ invitingCode });
public override async acceptInvitation(invitingCode: string) {
return await this._apis.acceptInviting({ invitingCode });
}
}
@@ -1,7 +1,11 @@
import kyOrigin from 'ky';
import ky from 'ky-universal';
import { MessageCenter } from '../../../message/index.js';
import { token } from './token.js';
const messageCenter = MessageCenter.getInstance();
const _sendMessage = messageCenter.getMessageSender('affine');
export const bareClient = ky.extend({
prefixUrl: 'http://localhost:8080',
retry: 1,
@@ -41,5 +45,15 @@ export const client = bareClient.extend({
request.headers.set('Authorization', token.token);
},
],
beforeError: [
error => {
const { response } = error;
if (response.status === 401) {
_sendMessage(MessageCenter.messageCode.noPermission);
}
return error;
},
],
},
});
@@ -1,6 +1,20 @@
import { MessageCenter } from '../../../message/index.js';
import { bareClient, client } from './request.js';
import type { User } from './user';
const messageCenter = MessageCenter.getInstance();
const sendMessage = messageCenter.getMessageSender('affine');
const { messageCode } = MessageCenter;
class RequestError extends Error {
constructor(message: string, cause: unknown | null = null) {
super(message);
this.name = 'RequestError';
this.cause = cause;
}
}
export interface GetWorkspaceDetailParams {
id: string;
}
@@ -26,13 +40,18 @@ export interface Workspace {
}
export async function getWorkspaces(): Promise<Workspace[]> {
return client
.get('api/workspace', {
headers: {
'Cache-Control': 'no-cache',
},
})
.json();
try {
return client
.get('api/workspace', {
headers: {
'Cache-Control': 'no-cache',
},
})
.json();
} catch (error) {
sendMessage(messageCode.loadListFailed);
throw new RequestError('load list failed', error);
}
}
export interface WorkspaceDetail extends Workspace {
@@ -43,7 +62,13 @@ export interface WorkspaceDetail extends Workspace {
export async function getWorkspaceDetail(
params: GetWorkspaceDetailParams
): Promise<WorkspaceDetail | null> {
return client.get(`api/workspace/${params.id}`).json();
try {
const response = client.get(`api/workspace/${params.id}`);
return response.json();
} catch (error) {
sendMessage(messageCode.getDetailFailed);
throw new RequestError('get detail failed', error);
}
}
export interface Permission {
@@ -74,7 +99,12 @@ export interface GetWorkspaceMembersParams {
export async function getWorkspaceMembers(
params: GetWorkspaceDetailParams
): Promise<Member[]> {
return client.get(`api/workspace/${params.id}/permission`).json();
try {
return client.get(`api/workspace/${params.id}/permission`).json();
} catch (error) {
sendMessage(messageCode.getMembersFailed);
throw new RequestError('get members failed', error);
}
}
export interface CreateWorkspaceParams {
@@ -84,10 +114,12 @@ export interface CreateWorkspaceParams {
export async function createWorkspace(
params: CreateWorkspaceParams
): Promise<{ id: string }> {
// FIXME: avatar should be optional
return client
.post('api/workspace', { json: { ...params, avatar: '123' } })
.json();
try {
return client.post('api/workspace', { json: params }).json();
} catch (error) {
sendMessage(messageCode.createWorkspaceFailed);
throw new RequestError('create workspace failed', error);
}
}
export interface UpdateWorkspaceParams {
@@ -98,13 +130,18 @@ export interface UpdateWorkspaceParams {
export async function updateWorkspace(
params: UpdateWorkspaceParams
): Promise<{ public: boolean | null }> {
return client
.post(`api/workspace/${params.id}`, {
json: {
public: params.public,
},
})
.json();
try {
return client
.post(`api/workspace/${params.id}`, {
json: {
public: params.public,
},
})
.json();
} catch (error) {
sendMessage(messageCode.updateWorkspaceFailed);
throw new RequestError('update workspace failed', error);
}
}
export interface DeleteWorkspaceParams {
@@ -114,7 +151,10 @@ export interface DeleteWorkspaceParams {
export async function deleteWorkspace(
params: DeleteWorkspaceParams
): Promise<void> {
await client.delete(`api/workspace/${params.id}`);
await client.delete(`api/workspace/${params.id}`).catch(error => {
sendMessage(messageCode.deleteWorkspaceFailed);
throw new RequestError('delete workspace failed', error);
});
}
export interface InviteMemberParams {
@@ -126,13 +166,18 @@ export interface InviteMemberParams {
* Notice: Only support normal(contrast to private) workspace.
*/
export async function inviteMember(params: InviteMemberParams): Promise<void> {
return client
.post(`api/workspace/${params.id}/permission`, {
json: {
email: params.email,
},
})
.json();
try {
return client
.post(`api/workspace/${params.id}/permission`, {
json: {
email: params.email,
},
})
.json();
} catch (error) {
sendMessage(messageCode.inviteMemberFailed);
throw new RequestError('invite member failed', error);
}
}
export interface RemoveMemberParams {
@@ -140,7 +185,10 @@ export interface RemoveMemberParams {
}
export async function removeMember(params: RemoveMemberParams): Promise<void> {
await client.delete(`api/permission/${params.permissionId}`);
await client.delete(`api/permission/${params.permissionId}`).catch(error => {
sendMessage(messageCode.removeMemberFailed);
throw new RequestError('remove member failed', error);
});
}
export interface AcceptInvitingParams {
@@ -149,8 +197,13 @@ export interface AcceptInvitingParams {
export async function acceptInviting(
params: AcceptInvitingParams
): Promise<void> {
await bareClient.post(`api/invitation/${params.invitingCode}`);
): Promise<Permission> {
try {
return bareClient.post(`api/invitation/${params.invitingCode}`).json();
} catch (error) {
sendMessage(messageCode.acceptInvitingFailed);
throw new RequestError('accept inviting failed', error);
}
}
export async function uploadBlob(params: { blob: Blob }): Promise<string> {
@@ -160,7 +213,12 @@ export async function uploadBlob(params: { blob: Blob }): Promise<string> {
export async function getBlob(params: {
blobId: string;
}): Promise<ArrayBuffer> {
return client.get(`api/blob/${params.blobId}`).arrayBuffer();
try {
return client.get(`api/blob/${params.blobId}`).arrayBuffer();
} catch (error) {
sendMessage(messageCode.getBlobFailed);
throw new RequestError('get blob failed', error);
}
}
export interface LeaveWorkspaceParams {
@@ -168,15 +226,26 @@ export interface LeaveWorkspaceParams {
}
export async function leaveWorkspace({ id }: LeaveWorkspaceParams) {
await client.delete(`api/workspace/${id}/permission`).json();
await client
.delete(`api/workspace/${id}/permission`)
.json()
.catch(error => {
sendMessage(messageCode.leaveWorkspaceFailed);
throw new RequestError('leave workspace failed', error);
});
}
export async function downloadWorkspace(
workspaceId: string,
published = false
): Promise<ArrayBuffer> {
if (published) {
return bareClient.get(`api/public/doc/${workspaceId}`).arrayBuffer();
try {
if (published) {
return bareClient.get(`api/public/doc/${workspaceId}`).arrayBuffer();
}
return client.get(`api/workspace/${workspaceId}/doc`).arrayBuffer();
} catch (error) {
sendMessage(messageCode.downloadWorkspaceFailed);
throw new RequestError('download workspace failed', error);
}
return client.get(`api/workspace/${workspaceId}/doc`).arrayBuffer();
}
@@ -0,0 +1,90 @@
import assert from 'assert';
import { Workspace as BlocksuiteWorkspace } from '@blocksuite/store';
import { WorkspaceUnit } from '../../workspace-unit.js';
import type { WorkspaceUnitCtorParams } from '../../workspace-unit';
import { createBlocksuiteWorkspace } from '../../utils/index.js';
import type { Apis } from './apis';
import { WebsocketProvider } from './sync.js';
import { setDefaultAvatar } from '../utils.js';
import { applyUpdate } from '../../utils/index.js';
export const loadWorkspaceUnit = async (
params: WorkspaceUnitCtorParams,
apis: Apis
) => {
const workspaceUnit = new WorkspaceUnit(params);
const blocksuiteWorkspace = createBlocksuiteWorkspace(workspaceUnit.id);
const updates = await apis.downloadWorkspace(
workspaceUnit.id,
params.published
);
applyUpdate(blocksuiteWorkspace, new Uint8Array(updates));
const details = await apis.getWorkspaceDetail({ id: workspaceUnit.id });
const owner = details?.owner;
workspaceUnit.setBlocksuiteWorkspace(blocksuiteWorkspace);
workspaceUnit.update({
name: blocksuiteWorkspace.meta.name,
avatar: blocksuiteWorkspace.meta.avatar,
memberCount: details?.member_count || 1,
owner: owner
? {
id: owner.id,
name: owner.name,
avatar: owner.avatar_url,
email: owner.email,
}
: undefined,
});
return workspaceUnit;
};
export const syncToCloud = async (
blocksuiteWorkspace: BlocksuiteWorkspace,
refreshToken: string
) => {
const workspaceId = blocksuiteWorkspace.room;
assert(workspaceId, 'Blocksuite workspace without room(workspaceId).');
const wsUrl = `${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${
window.location.host
}/api/sync/`;
const ws = new WebsocketProvider(
wsUrl,
workspaceId,
blocksuiteWorkspace.doc,
{
params: { token: refreshToken },
}
);
await new Promise((resolve, reject) => {
ws.once('synced', () => {
// FIXME: we don't when send local data to cloud successfully, so hack to wait 1s.
// Server will support this by add a new api.
setTimeout(resolve, 1000);
});
ws.once('lost-connection', () => reject());
ws.once('connection-error', () => reject());
});
};
export const createWorkspaceUnit = async (params: WorkspaceUnitCtorParams) => {
const workspaceUnit = new WorkspaceUnit(params);
const blocksuiteWorkspace = createBlocksuiteWorkspace(workspaceUnit.id);
blocksuiteWorkspace.meta.setName(workspaceUnit.name);
if (!workspaceUnit.avatar) {
await setDefaultAvatar(blocksuiteWorkspace);
workspaceUnit.update({ avatar: blocksuiteWorkspace.meta.avatar });
}
workspaceUnit.setBlocksuiteWorkspace(blocksuiteWorkspace);
return workspaceUnit;
};
+25 -28
View File
@@ -1,9 +1,10 @@
import { Workspace as BlocksuiteWorkspace, uuidv4 } from '@blocksuite/store';
import { Workspace as BlocksuiteWorkspace } from '@blocksuite/store';
import { MessageCenter } from '../message';
import { Logger, User } from '../types';
import type { WorkspaceUnitCollectionScope } from '../workspace-unit-collection';
import type { WorkspaceUnitCtorParams } from '../workspace-unit';
import type { WorkspaceUnitCtorParams, WorkspaceUnit } from '../workspace-unit';
import { Member } from './affine/apis';
import { Permission } from './affine/apis/workspace.js';
const defaultLogger = () => {
return;
@@ -22,10 +23,15 @@ export type UpdateWorkspaceMetaParams = Partial<
>;
export class BaseProvider {
/** provider id */
public readonly id: string = 'base';
/** workspace unit collection */
protected _workspaces!: WorkspaceUnitCollectionScope;
protected _logger!: Logger;
protected _messageCenter!: MessageCenter;
/** send message with message center */
protected _sendMessage!: ReturnType<
InstanceType<typeof MessageCenter>['getMessageSender']
>;
public constructor({
logger,
@@ -34,7 +40,7 @@ export class BaseProvider {
}: ProviderConstructorParams) {
this._logger = (logger || defaultLogger) as Logger;
this._workspaces = workspaces;
this._messageCenter = messageCenter;
this._sendMessage = messageCenter.getMessageSender(this.id);
}
/**
@@ -44,12 +50,6 @@ export class BaseProvider {
return;
}
public async createWorkspaceInfo(
params: CreateWorkspaceInfoParams
): Promise<WorkspaceMeta0> {
throw new Error(`provider: ${this.id} createWorkspaceInfo Not implemented`);
}
/**
* auth provider
*/
@@ -87,7 +87,7 @@ export class BaseProvider {
/**
* load workspaces
**/
public async loadWorkspaces(): Promise<WorkspaceMeta0[]> {
public async loadWorkspaces(): Promise<WorkspaceUnit[]> {
throw new Error(`provider: ${this.id} loadWorkSpace Not implemented`);
}
@@ -183,13 +183,18 @@ export class BaseProvider {
/**
* create workspace by workspace meta
* @param {WorkspaceMeta} meta
* @param {CreateWorkspaceInfoParams} meta
*/
public async createWorkspace(
blocksuiteWorkspace: BlocksuiteWorkspace,
meta: WorkspaceMeta0
): Promise<BlocksuiteWorkspace | undefined> {
return blocksuiteWorkspace;
meta: CreateWorkspaceInfoParams
): Promise<WorkspaceUnit | undefined> {
throw new Error(`provider: ${this.id} createWorkspace not implemented`);
}
public async extendWorkspace(
workspaceUnit: WorkspaceUnit
): Promise<WorkspaceUnit | undefined> {
throw new Error(`provider: ${this.id} extendWorkspace not implemented`);
}
/**
@@ -214,16 +219,6 @@ export class BaseProvider {
return workspace;
}
/**
* merge one workspaces to another
* @param workspace
* @returns
*/
public async assign(to: BlocksuiteWorkspace, from: BlocksuiteWorkspace) {
from;
return to;
}
/**
* get workspace members
* @param {string} workspaceId
@@ -239,8 +234,10 @@ export class BaseProvider {
* @param {string} inviteCode
* @returns
*/
public async acceptInvitation(inviteCode: string): Promise<void> {
public async acceptInvitation(
inviteCode: string
): Promise<Permission | null> {
inviteCode;
return;
return null;
}
}
@@ -1,10 +1,13 @@
import assert from 'assert';
import * as idb from 'lib0/indexeddb.js';
import { Workspace as BlocksuiteWorkspace } from '@blocksuite/store';
import { applyUpdate } from '../../../utils/index.js';
const { encodeStateAsUpdate } = BlocksuiteWorkspace.Y;
const { encodeStateAsUpdate, mergeUpdates } = BlocksuiteWorkspace.Y;
export const initStore = async (blocksuiteWorkspace: BlocksuiteWorkspace) => {
export const writeUpdatesToLocal = async (
blocksuiteWorkspace: BlocksuiteWorkspace
) => {
const workspaceId = blocksuiteWorkspace.room;
assert(workspaceId);
await idb.deleteDB(workspaceId);
@@ -17,4 +20,23 @@ export const initStore = async (blocksuiteWorkspace: BlocksuiteWorkspace) => {
if (updatesStore) {
await idb.addAutoKey(updatesStore, currState);
}
db.close();
};
export const applyLocalUpdates = async (
blocksuiteWorkspace: BlocksuiteWorkspace
) => {
const workspaceId = blocksuiteWorkspace.room;
assert(workspaceId, 'Blocksuite workspace without room(workspaceId).');
const db = await idb.openDB(workspaceId, db =>
idb.createStores(db, [['updates', { autoIncrement: true }], ['custom']])
);
const [updatesStore] = idb.transact(db, ['updates']); // , 'readonly')
if (updatesStore) {
const updates = await idb.getAll(updatesStore);
const mergedUpdates = mergeUpdates(updates);
await applyUpdate(blocksuiteWorkspace, mergedUpdates);
}
return blocksuiteWorkspace;
};
@@ -16,12 +16,10 @@ test.describe.serial('local provider', () => {
let workspaceId: string | undefined;
test('create workspace', async () => {
const workspaceInfo = await provider.createWorkspaceInfo({
const workspaceUnit = await provider.createWorkspace({
name: workspaceName,
});
workspaceId = workspaceInfo.id;
const blocksuiteWorkspace = createBlocksuiteWorkspace(workspaceId);
await provider.createWorkspace(blocksuiteWorkspace, workspaceInfo);
workspaceId = workspaceUnit?.id;
expect(workspaceMetaCollection.workspaces.length).toEqual(1);
expect(workspaceMetaCollection.workspaces[0].name).toEqual(workspaceName);
@@ -8,9 +8,10 @@ import type {
import { varStorage as storage } from 'lib0/storage';
import { Workspace as BlocksuiteWorkspace, uuidv4 } from '@blocksuite/store';
import { IndexedDBProvider } from './indexeddb/indexeddb.js';
import { initStore } from './indexeddb/utils.js';
import { applyLocalUpdates } from './indexeddb/utils.js';
import assert from 'assert';
import { setDefaultAvatar } from '../utils.js';
import { loadWorkspaceUnit, createWorkspaceUnit } from './utils.js';
import type { WorkspaceUnit } from '../../workspace-unit';
const WORKSPACE_KEY = 'workspaces';
@@ -22,21 +23,12 @@ export class LocalProvider extends BaseProvider {
super(params);
}
private _storeWorkspaces(workspaces: WorkspaceMeta0[]) {
private _storeWorkspaces(workspaceUnits: WorkspaceUnit[]) {
storage.setItem(
WORKSPACE_KEY,
JSON.stringify(
workspaces.map(w => {
return {
id: w.id,
name: w.name,
avatar: w.avatar,
owner: w.owner,
published: w.published,
memberCount: w.memberCount,
provider: w.provider,
syncMode: w.syncMode,
};
workspaceUnits.map(w => {
return w.toJSON();
})
)
);
@@ -57,24 +49,28 @@ export class LocalProvider extends BaseProvider {
workspace: BlocksuiteWorkspace
): Promise<BlocksuiteWorkspace> {
assert(workspace.room);
await applyLocalUpdates(workspace);
await this.linkLocal(workspace);
return workspace;
}
override loadWorkspaces(): Promise<WorkspaceMeta0[]> {
override async loadWorkspaces(): Promise<WorkspaceUnit[]> {
const workspaceStr = storage.getItem(WORKSPACE_KEY);
let workspaces: WorkspaceMeta0[] = [];
if (workspaceStr) {
try {
workspaces = JSON.parse(workspaceStr) as WorkspaceMeta0[];
workspaces.forEach(workspace => {
this._workspaces.add(workspace);
});
const workspaceMetas = JSON.parse(workspaceStr) as WorkspaceMeta0[];
const workspaceUnits = await Promise.all(
workspaceMetas.map(meta => {
return loadWorkspaceUnit(meta);
})
);
this._workspaces.add(workspaceUnits);
return workspaceUnits;
} catch (error) {
this._logger(`Failed to parse workspaces from storage`);
}
}
return Promise.resolve(workspaces);
return [];
}
public override async deleteWorkspace(id: string): Promise<void> {
@@ -96,10 +92,10 @@ export class LocalProvider extends BaseProvider {
this._storeWorkspaces(this._workspaces.list());
}
public override async createWorkspaceInfo(
public override async createWorkspace(
meta: CreateWorkspaceInfoParams
): Promise<WorkspaceMeta0> {
const workspaceInfo: WorkspaceMeta0 = {
): Promise<WorkspaceUnit | undefined> {
const workspaceUnit = await createWorkspaceUnit({
name: meta.name,
id: uuidv4(),
published: false,
@@ -107,36 +103,11 @@ export class LocalProvider extends BaseProvider {
owner: undefined,
syncMode: 'core',
memberCount: 1,
provider: 'local',
};
return Promise.resolve(workspaceInfo);
}
public override async createWorkspace(
blocksuiteWorkspace: BlocksuiteWorkspace,
meta: WorkspaceMeta0
): Promise<BlocksuiteWorkspace | undefined> {
const workspaceId = blocksuiteWorkspace.room;
assert(workspaceId, 'Blocksuite Workspace without room(workspaceId).');
this._logger('Creating affine workspace');
const workspaceInfo: WorkspaceMeta0 = {
...meta,
};
blocksuiteWorkspace.meta.setName(meta.name);
if (!meta.avatar) {
await setDefaultAvatar(blocksuiteWorkspace);
workspaceInfo.avatar = blocksuiteWorkspace.meta.avatar;
}
await initStore(blocksuiteWorkspace);
this._workspaces.add(workspaceInfo);
provider: this.id,
});
this._workspaces.add(workspaceUnit);
this._storeWorkspaces(this._workspaces.list());
return blocksuiteWorkspace;
return workspaceUnit;
}
public override async clear(): Promise<void> {
@@ -0,0 +1,34 @@
import { WorkspaceUnit } from '../../workspace-unit.js';
import type { WorkspaceUnitCtorParams } from '../../workspace-unit';
import { createBlocksuiteWorkspace } from '../../utils/index.js';
import { applyLocalUpdates, writeUpdatesToLocal } from './indexeddb/utils.js';
import { setDefaultAvatar } from '../utils.js';
export const loadWorkspaceUnit = async (params: WorkspaceUnitCtorParams) => {
const workspaceUnit = new WorkspaceUnit(params);
const blocksuiteWorkspace = createBlocksuiteWorkspace(workspaceUnit.id);
await applyLocalUpdates(blocksuiteWorkspace);
workspaceUnit.setBlocksuiteWorkspace(blocksuiteWorkspace);
return workspaceUnit;
};
export const createWorkspaceUnit = async (params: WorkspaceUnitCtorParams) => {
const workspaceUnit = new WorkspaceUnit(params);
const blocksuiteWorkspace = createBlocksuiteWorkspace(workspaceUnit.id);
blocksuiteWorkspace.meta.setName(workspaceUnit.name);
if (!workspaceUnit.avatar) {
await setDefaultAvatar(blocksuiteWorkspace);
workspaceUnit.update({ avatar: blocksuiteWorkspace.meta.avatar });
}
await writeUpdatesToLocal(blocksuiteWorkspace);
workspaceUnit.setBlocksuiteWorkspace(blocksuiteWorkspace);
return workspaceUnit;
};
+1
View File
@@ -25,4 +25,5 @@ export type Logger = ReturnType<typeof getLogger>;
export type Message = {
code: number;
message: string;
provider: string;
};
+22
View File
@@ -45,3 +45,25 @@ export async function getDefaultHeadImgBlob(
}
});
}
export const applyUpdate = async (
blocksuiteWorkspace: BlocksuiteWorkspace,
updates: Uint8Array
) => {
if (updates && updates.byteLength) {
await new Promise(resolve => {
// FIXME: if we merge two empty doc, there will no update event.
// So we set a timer to cancel update listener.
const doc = blocksuiteWorkspace.doc;
const timer = setTimeout(() => {
doc.off('update', resolve);
resolve(undefined);
}, 1000);
doc.once('update', () => {
clearTimeout(timer);
setTimeout(resolve, 100);
});
BlocksuiteWorkspace.Y.applyUpdate(doc, new Uint8Array(updates));
});
}
};
@@ -1,6 +1,7 @@
import { test, expect } from '@playwright/test';
import { WorkspaceUnitCollection } from './workspace-unit-collection.js';
import type { WorkspaceUnitCollectionChangeEvent } from './workspace-unit-collection';
import { WorkspaceUnit } from './workspace-unit.js';
test.describe.serial('workspace meta collection observable', () => {
const workspaceUnitCollection = new WorkspaceUnitCollection();
@@ -14,13 +15,15 @@ test.describe.serial('workspace meta collection observable', () => {
expect(event.added?.[0]?.id).toEqual('123');
}
);
scope.add({
const workspaceUnit = new WorkspaceUnit({
id: '123',
name: 'test',
avatar: undefined,
memberCount: 1,
provider: '',
syncMode: 'core',
});
scope.add(workspaceUnit);
});
test('list workspace', () => {
@@ -1,25 +1,24 @@
import { Observable } from 'lib0/observable';
import { WorkspaceUnit } from './workspace-unit.js';
import type {
WorkspaceUnitCtorParams,
WorkspaceUnit,
UpdateWorkspaceUnitParams,
} from './workspace-unit';
export interface WorkspaceUnitCollectionScope {
get: (workspaceId: string) => WorkspaceUnit | undefined;
list: () => WorkspaceUnit[];
add: (workspace: WorkspaceUnitCtorParams) => void;
remove: (workspaceId: string) => boolean;
add: (workspace: WorkspaceUnit | WorkspaceUnit[]) => void;
remove: (workspaceId: string | string[]) => boolean;
clear: () => void;
update: (
workspaceId: string,
workspaceMeta: UpdateWorkspaceUnitParams
workspaceUnit: UpdateWorkspaceUnitParams
) => void;
}
export interface WorkspaceUnitCollectionChangeEvent {
added?: WorkspaceUnit[];
deleted?: WorkspaceUnit;
deleted?: WorkspaceUnit[];
updated?: WorkspaceUnit;
}
@@ -59,45 +58,66 @@ export class WorkspaceUnitCollection {
return this._workspaceUnitMap.get(workspaceId);
};
const add = (workspace: WorkspaceUnitCtorParams) => {
if (this._workspaceUnitMap.has(workspace.id)) {
// FIXME: multiple add same workspace
const add = (workspaceUnit: WorkspaceUnit | WorkspaceUnit[]) => {
const workspaceUnits = Array.isArray(workspaceUnit)
? workspaceUnit
: [workspaceUnit];
let added = false;
workspaceUnits.forEach(workspaceUnit => {
if (this._workspaceUnitMap.has(workspaceUnit.id)) {
// FIXME: multiple add same workspace
return;
}
added = true;
this._workspaceUnitMap.set(workspaceUnit.id, workspaceUnit);
scopedWorkspaceIds.add(workspaceUnit.id);
});
if (!added) {
return;
}
const workspaceUnit = new WorkspaceUnit(workspace);
this._workspaceUnitMap.set(workspace.id.toString(), workspaceUnit);
scopedWorkspaceIds.add(workspace.id);
this._events.emit('change', [
{
added: [workspaceUnit],
added: workspaceUnits,
} as WorkspaceUnitCollectionChangeEvent,
]);
};
const remove = (workspaceId: string) => {
if (!scopedWorkspaceIds.has(workspaceId)) {
return true;
}
const remove = (workspaceId: string | string[]) => {
const workspaceIds = Array.isArray(workspaceId)
? workspaceId
: [workspaceId];
const workspaceUnits: WorkspaceUnit[] = [];
const workspaceUnit = this._workspaceUnitMap.get(workspaceId);
if (workspaceUnit) {
const ret = this._workspaceUnitMap.delete(workspaceId);
// If deletion failed, return.
if (!ret) {
return ret;
workspaceIds.forEach(workspaceId => {
if (!scopedWorkspaceIds.has(workspaceId)) {
return;
}
const workspaceUnit = this._workspaceUnitMap.get(workspaceId);
if (workspaceUnit) {
const ret = this._workspaceUnitMap.delete(workspaceId);
// If deletion failed, return.
if (!ret) {
return;
}
scopedWorkspaceIds.delete(workspaceId);
workspaceUnits.push(workspaceUnit);
scopedWorkspaceIds.delete(workspaceId);
}
});
this._events.emit('change', [
{
deleted: workspaceUnit,
} as WorkspaceUnitCollectionChangeEvent,
]);
if (!workspaceUnits.length) {
return false;
}
this._events.emit('change', [
{
deleted: workspaceUnits,
} as WorkspaceUnitCollectionChangeEvent,
]);
return true;
};
@@ -107,10 +127,7 @@ export class WorkspaceUnitCollection {
});
};
const update = (
workspaceId: string,
workspaceMeta: UpdateWorkspaceUnitParams
) => {
const update = (workspaceId: string, meta: UpdateWorkspaceUnitParams) => {
if (!scopedWorkspaceIds.has(workspaceId)) {
return true;
}
@@ -120,7 +137,7 @@ export class WorkspaceUnitCollection {
return true;
}
workspaceUnit.update(workspaceMeta);
workspaceUnit.update(meta);
this._events.emit('change', [
{
+21 -8
View File
@@ -37,6 +37,16 @@ export class WorkspaceUnit {
this.update(params);
}
get isPublish() {
console.error('Suggest changing to published');
return this.published;
}
get isLocal() {
console.error('Suggest changing to syncMode');
return this.syncMode === 'all';
}
get blocksuiteWorkspace() {
return this._blocksuiteWorkspace;
}
@@ -52,13 +62,16 @@ export class WorkspaceUnit {
Object.assign(this, params);
}
get isPublish() {
console.error('Suggest changing to published');
return this.published;
}
get isLocal() {
console.error('Suggest changing to syncMode');
return this.syncMode === 'all';
toJSON(): Omit<WorkspaceUnitCtorParams, 'blocksuiteWorkspace'> {
return {
id: this.id,
name: this.name,
avatar: this.avatar,
owner: this.owner,
published: this.published,
memberCount: this.memberCount,
provider: this.provider,
syncMode: this.syncMode,
};
}
}
-24
View File
@@ -3011,7 +3011,6 @@ packages:
/@next/env/13.1.0:
resolution: {integrity: sha512-6iNixFzCndH+Bl4FetQzOMjxCJqg8fs0LAlZviig1K6mIjOWH2m2oPcHcOg1Ta5VCe7Bx5KG1Hs+NrWDUkBt9A==}
dev: false
/@next/eslint-plugin-next/12.3.1:
resolution: {integrity: sha512-sw+lTf6r6P0j+g/n9y4qdWWI2syPqZx+uc0+B/fRENqfR3KpSid6MIKqc9gNwGhJASazEQ5b3w8h4cAET213jw==}
@@ -3034,7 +3033,6 @@ packages:
cpu: [arm]
os: [android]
requiresBuild: true
dev: false
optional: true
/@next/swc-android-arm64/12.3.1:
@@ -3052,7 +3050,6 @@ packages:
cpu: [arm64]
os: [android]
requiresBuild: true
dev: false
optional: true
/@next/swc-darwin-arm64/12.3.1:
@@ -3070,7 +3067,6 @@ packages:
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: false
optional: true
/@next/swc-darwin-x64/12.3.1:
@@ -3088,7 +3084,6 @@ packages:
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: false
optional: true
/@next/swc-freebsd-x64/12.3.1:
@@ -3106,7 +3101,6 @@ packages:
cpu: [x64]
os: [freebsd]
requiresBuild: true
dev: false
optional: true
/@next/swc-linux-arm-gnueabihf/12.3.1:
@@ -3124,7 +3118,6 @@ packages:
cpu: [arm]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@next/swc-linux-arm64-gnu/12.3.1:
@@ -3142,7 +3135,6 @@ packages:
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@next/swc-linux-arm64-musl/12.3.1:
@@ -3160,7 +3152,6 @@ packages:
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@next/swc-linux-x64-gnu/12.3.1:
@@ -3178,7 +3169,6 @@ packages:
cpu: [x64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@next/swc-linux-x64-musl/12.3.1:
@@ -3196,7 +3186,6 @@ packages:
cpu: [x64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@next/swc-win32-arm64-msvc/12.3.1:
@@ -3214,7 +3203,6 @@ packages:
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: false
optional: true
/@next/swc-win32-ia32-msvc/12.3.1:
@@ -3232,7 +3220,6 @@ packages:
cpu: [ia32]
os: [win32]
requiresBuild: true
dev: false
optional: true
/@next/swc-win32-x64-msvc/12.3.1:
@@ -3250,7 +3237,6 @@ packages:
cpu: [x64]
os: [win32]
requiresBuild: true
dev: false
optional: true
/@nodelib/fs.scandir/2.1.5:
@@ -3613,7 +3599,6 @@ packages:
resolution: {integrity: sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==}
dependencies:
tslib: 2.4.0
dev: false
/@szmarczak/http-timer/5.0.1:
resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==}
@@ -4580,7 +4565,6 @@ packages:
/client-only/0.0.1:
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
dev: false
/cliui/6.0.0:
resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
@@ -7512,7 +7496,6 @@ packages:
resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
dev: false
/natural-compare-lite/1.4.0:
resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==}
@@ -7635,7 +7618,6 @@ packages:
transitivePeerDependencies:
- '@babel/core'
- babel-plugin-macros
dev: false
/node-domexception/1.0.0:
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
@@ -7986,7 +7968,6 @@ packages:
nanoid: 3.3.4
picocolors: 1.0.0
source-map-js: 1.0.2
dev: false
/preferred-pm/3.0.3:
resolution: {integrity: sha512-+wZgbxNES/KlJs9q40F/1sfOd/j7f1O9JaHcW5Dsn3aUUOZg3L2bjpVUcKV2jvtElYfoTuQiNeMfQJ4kwUAhCQ==}
@@ -8156,7 +8137,6 @@ packages:
loose-envify: 1.4.0
react: 18.2.0
scheduler: 0.23.0
dev: false
/react-i18next/11.18.6_i18next@21.10.0:
resolution: {integrity: sha512-yHb2F9BiT0lqoQDt8loZ5gWP331GwctHz9tYQ8A2EIEUu+CcEdjBLQWli1USG3RdWQt3W+jqQLg/d4rrQR96LA==}
@@ -8253,7 +8233,6 @@ packages:
engines: {node: '>=0.10.0'}
dependencies:
loose-envify: 1.4.0
dev: false
/read-pkg-up/7.0.1:
resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==}
@@ -8531,7 +8510,6 @@ packages:
resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==}
dependencies:
loose-envify: 1.4.0
dev: false
/schema-utils/2.7.1:
resolution: {integrity: sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==}
@@ -8690,7 +8668,6 @@ packages:
/source-map-js/1.0.2:
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
engines: {node: '>=0.10.0'}
dev: false
/source-map-support/0.5.13:
resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==}
@@ -8939,7 +8916,6 @@ packages:
dependencies:
client-only: 0.0.1
react: 18.2.0
dev: false
/stylis/4.0.13:
resolution: {integrity: sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==}