feat: delete work space

This commit is contained in:
MingLiang Wang
2022-12-22 21:56:12 +08:00
parent 88bb20505e
commit 15f04aea48
10 changed files with 243 additions and 24 deletions
@@ -0,0 +1,81 @@
import Modal from '@/ui/modal';
import Input from '@/ui/input';
import {
StyledModalHeader,
StyledTextContent,
StyledModalWrapper,
StyledInputContent,
StyledButtonContent,
StyledWorkspaceName,
} from './style';
import { useState } from 'react';
import { ModalCloseButton } from '@/ui/modal';
import { Button } from '@/ui/button';
import { deleteWorkspace } from '@pathfinder/data-services';
import { useRouter } from 'next/router';
interface WorkspaceDeleteProps {
open: boolean;
onClose: () => void;
workspaceName: string;
workspaceId: string;
nextWorkSpaceId: string;
}
export const WorkspaceDelete = ({
open,
onClose,
workspaceId,
workspaceName,
nextWorkSpaceId,
}: WorkspaceDeleteProps) => {
const [deleteStr, setDeleteStr] = useState<string>('');
const router = useRouter();
const handlerInputChange = (workspaceName: string) => {
setDeleteStr(workspaceName);
};
const handleDelete = async () => {
await deleteWorkspace({ id: workspaceId });
router.push(`/workspace/${nextWorkSpaceId}`);
onClose();
};
return (
<Modal open={open} onClose={onClose}>
<StyledModalWrapper>
<ModalCloseButton onClick={onClose} />
<StyledModalHeader>Delete Workspace</StyledModalHeader>
<StyledTextContent>
This action cannot be undone. This will permanently delete (
<StyledWorkspaceName>{workspaceName}</StyledWorkspaceName>) along with
all its content.
</StyledTextContent>
<StyledInputContent>
<Input
onChange={handlerInputChange}
placeholder="Please type “delete” to confirm"
value={deleteStr}
></Input>
</StyledInputContent>
<StyledButtonContent>
<Button shape="circle" onClick={onClose}>
Cancel
</Button>
<Button
disabled={deleteStr !== 'delete'}
onClick={handleDelete}
type="danger"
shape="circle"
style={{ marginLeft: '24px' }}
>
Delete
</Button>
</StyledButtonContent>
</StyledModalWrapper>
</Modal>
);
};
export default WorkspaceDelete;
@@ -0,0 +1 @@
export * from './delete';
@@ -0,0 +1,76 @@
import { styled } from '@/styles';
import { Button } from '@/ui/button';
export const StyledModalWrapper = styled('div')(({ theme }) => {
return {
position: 'relative',
padding: '0px',
width: '460px',
background: theme.colors.popoverBackground,
borderRadius: '12px',
};
});
export const StyledModalHeader = styled('div')(({ theme }) => {
return {
margin: '44px 0px 12px 0px',
width: '460px',
fontWeight: '600',
fontSize: '20px;',
textAlign: 'center',
color: theme.colors.popoverColor,
};
});
// export const StyledModalContent = styled('div')(({ theme }) => {});
export const StyledTextContent = styled('div')(({ theme }) => {
return {
margin: 'auto',
width: '425px',
fontFamily: 'Avenir Next',
fontStyle: 'normal',
fontWeight: '400',
fontSize: '18px',
lineHeight: '26px',
textAlign: 'center',
};
});
export const StyledInputContent = styled('div')(({ theme }) => {
return {
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
margin: '40px 0 24px 0',
};
});
export const StyledButtonContent = styled('div')(({ theme }) => {
return {
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
margin: '0px 0 32px 0',
};
});
export const StyledWorkspaceName = styled('span')(({ theme }) => {
return {
color: '#E8178A',
};
});
// export const StyledCancelButton = styled(Button)(({ theme }) => {
// return {
// width: '100px',
// justifyContent: 'center',
// };
// });
// export const StyledDeleteButton = styled(Button)(({ theme }) => {
// return {
// width: '100px',
// justifyContent: 'center',
// };
// });
@@ -1,10 +1,10 @@
import Modal, { ModalCloseButton } from '@/ui/modal';
import {
StyledDeleteButtonContainer,
StyledSettingAvatar,
StyledSettingAvatarContent,
StyledSettingInputContainer,
} from './style';
import { StyledSettingH2 } from '../style';
import { useState } from 'react';
import { Button } from '@/ui/button';
@@ -12,15 +12,20 @@ import Input from '@/ui/input';
import { Workspace, WorkspaceType } from '@pathfinder/data-services';
import { useAppState } from '@/providers/app-state-provider';
import { WorkspaceDetails } from '@/components/workspace-slider-bar/WorkspaceSelector/SelectorPopperContent';
import { StyledSettingH2 } from '../style';
import { WorkspaceDelete } from './delete';
import { Workspace as StoreWorkspaces } from '@blocksuite/store';
export const GeneralPage = ({
workspace,
owner,
workspaces,
}: {
workspace: Workspace;
owner: WorkspaceDetails[string]['owner'];
workspaces: Record<string, StoreWorkspaces | null>;
}) => {
const { workspaces, user, currentWorkspace } = useAppState();
const { user, currentWorkspace, workspacesMeta } = useAppState();
const [showDelete, setShowDelete] = useState<boolean>(false);
const [workspaceName, setWorkspaceName] = useState<string>(
workspaces[workspace.id]?.meta.name ||
(workspace.type === WorkspaceType.Private && user
@@ -33,8 +38,28 @@ export const GeneralPage = ({
currentWorkspace?.meta.setName(newName);
workspaces[workspace.id]?.meta.setName(newName);
};
const currentWorkspaceIndex = workspacesMeta.findIndex(
meta => meta.id === workspace.id
);
const nextWorkSpaceId =
currentWorkspaceIndex > workspacesMeta.length - 1
? workspacesMeta[currentWorkspaceIndex - 1]?.id
: workspacesMeta[currentWorkspaceIndex + 1]?.id;
const handleCloseDelete = () => {
setShowDelete(false);
};
const handleClickDelete = () => {
setShowDelete(true);
};
return workspace ? (
<div>
<WorkspaceDelete
open={showDelete}
onClose={handleCloseDelete}
workspaceName={workspaceName}
workspaceId={workspace.id}
nextWorkSpaceId={nextWorkSpaceId}
></WorkspaceDelete>
<StyledSettingH2 marginTop={56}>Workspace Avatar</StyledSettingH2>
<StyledSettingAvatarContent>
<StyledSettingAvatar
@@ -43,6 +68,7 @@ export const GeneralPage = ({
>
W
</StyledSettingAvatar>
{/* TODO: add upload logic */}
{/* {isOwner ? (
<StyledAvatarUploadBtn shape="round">upload</StyledAvatarUploadBtn>
) : null} */}
@@ -70,7 +96,7 @@ export const GeneralPage = ({
></Input>
</StyledSettingInputContainer>
<StyledDeleteButtonContainer>
<Button type="danger" shape="circle">
<Button type="danger" shape="circle" onClick={handleClickDelete}>
Delete Workspace
</Button>
</StyledDeleteButtonContainer>
@@ -40,17 +40,16 @@ import { InviteMembers } from '../invite-members/index';
import {
getWorkspaceMembers,
Workspace,
WorkspaceType,
Member,
removeMember,
} from '@pathfinder/data-services';
import { Avatar } from '@mui/material';
import { Menu, MenuItem } from '@/ui/menu';
import { toast } from '@/ui/toast';
import { useConfirm } from '@/providers/confirm-provider';
import { useAppState } from '@/providers/app-state-provider';
import { WorkspaceDetails } from '../workspace-slider-bar/WorkspaceSelector/SelectorPopperContent';
import { GeneralPage } from './general';
import { Workspace as StoreWorkspaces } from '@blocksuite/store';
enum ActiveTab {
'general' = 'general',
@@ -68,6 +67,7 @@ type WorkspaceSettingProps = {
onClose?: () => void;
workspace: Workspace;
owner: WorkspaceDetails[string]['owner'];
workspaces: Record<string, StoreWorkspaces | null>;
};
const WorkspaceSettingTab = ({ activeTab, onTabChange }: SettingTabProps) => {
@@ -115,8 +115,9 @@ export const WorkspaceSetting = ({
onClose,
workspace,
owner,
workspaces,
}: WorkspaceSettingProps) => {
const { workspaces, user } = useAppState();
const { user } = useAppState();
const [activeTab, setActiveTab] = useState<ActiveTab>(ActiveTab.general);
const handleTabChange = (tab: ActiveTab) => {
setActiveTab(tab);
@@ -148,7 +149,11 @@ export const WorkspaceSetting = ({
) : null}
<StyledSettingContent>
{activeTab === ActiveTab.general && (
<GeneralPage workspace={workspace} owner={owner} />
<GeneralPage
workspace={workspace}
owner={owner}
workspaces={workspaces}
/>
)}
{activeTab === ActiveTab.members && workspace && (
<MembersPage workspace={workspace} />
@@ -12,7 +12,12 @@ import {
} from './WorkspaceItem';
import { WorkspaceSetting } from '@/components/workspace-setting';
import { useCallback, useEffect, useState } from 'react';
import { getWorkspaceDetail, WorkspaceType } from '@pathfinder/data-services';
import {
downloadWorkspace,
getWorkspaceDetail,
WorkspaceType,
} from '@pathfinder/data-services';
import { Workspace } from '@blocksuite/store';
export type WorkspaceDetails = Record<
string,
@@ -26,13 +31,16 @@ type SelectorPopperContentProps = {
export const SelectorPopperContent = ({
isShow,
}: SelectorPopperContentProps) => {
const { user, workspacesMeta, workspaces } = useAppState();
const { user, workspacesMeta } = useAppState();
const [settingWorkspaceId, setSettingWorkspaceId] = useState<string | null>(
null
);
const [workSpaceDetails, setWorkSpaceDetails] = useState<WorkspaceDetails>(
{}
);
const [workspaces, setWorkspaces] = useState<
Record<string, Workspace | null>
>({});
const handleClickSettingWorkspace = (workspaceId: string) => {
setSettingWorkspaceId(workspaceId);
};
@@ -73,10 +81,38 @@ export const SelectorPopperContent = ({
setWorkSpaceDetails(workSpaceDetails);
}, [user, workspacesMeta]);
// TODO: add to context
const refreshWorkspaces = useCallback(async () => {
const workspacesList = await Promise.all(
workspacesMeta.map(async ({ id }) => {
const workspace = new Workspace({
room: id,
providers: [],
});
const updates = await downloadWorkspace({ workspaceId: id });
updates &&
Workspace.Y.applyUpdate(workspace.doc, new Uint8Array(updates));
// if after update, the space:meta is empty, then we need to get map with doc
workspace.doc.getMap('space:meta');
return { id, workspace };
})
);
const workspaces: Record<string, Workspace | null> = {};
workspacesList.forEach(({ id, workspace }) => {
workspaces[id] = workspace;
});
console.log('workspaces', workspaces);
setWorkspaces(workspaces);
}, []);
useEffect(() => {
if (isShow) {
setSettingWorkspaceId(null);
refreshDetails();
refreshWorkspaces();
}
}, [isShow, refreshDetails]);
@@ -128,6 +164,7 @@ export const SelectorPopperContent = ({
name: user.name,
}
}
workspaces={workspaces}
/>
) : null}
<StyledDivider />
@@ -15,7 +15,6 @@ export type CreateEditorHandler = (page: StorePage) => EditorContainer | null;
export interface AppStateValue {
user: AccessTokenMessage | null;
workspacesMeta: Workspace[];
workspaces: Record<string, StoreWorkspace | null>;
currentWorkspaceId: string;
currentWorkspace: StoreWorkspace | null;
@@ -56,7 +55,7 @@ export const AppState = createContext<AppStateContext>({
loadPage: () => Promise.resolve(null),
createPage: () => Promise.resolve(null),
synced: false,
workspaces: {},
// workspaces: {},
});
export const useAppState = () => {
@@ -13,7 +13,7 @@ import type {
LoadWorkspaceHandler,
CreateEditorHandler,
} from './context';
import { downloadWorkspace, token } from '@pathfinder/data-services';
import { token } from '@pathfinder/data-services';
import { WebsocketProvider } from './y-websocket';
const getEditorParams = (workspaceId: string) => {
@@ -90,19 +90,9 @@ const DynamicBlocksuite = ({
);
if (indexDBProvider) {
(indexDBProvider as IndexedDBDocProvider).on('synced', async () => {
// const updates = await downloadWorkspace({ workspaceId });
// updates &&
// Workspace.Y.applyUpdate(workspace.doc, new Uint8Array(updates));
// if after update, the space:meta is empty, then we need to get map with doc
workspace.doc.getMap('space:meta');
resolve(workspace);
});
} else {
const updates = await downloadWorkspace({ workspaceId });
updates &&
Workspace.Y.applyUpdate(workspace.doc, new Uint8Array(updates));
// if after update, the space:meta is empty, then we need to get map with doc
workspace.doc.getMap('space:meta');
resolve(workspace);
}
});
@@ -26,7 +26,6 @@ export const AppStateProvider = ({ children }: { children?: ReactNode }) => {
currentWorkspace: null,
currentPage: null,
editor: null,
workspaces: {},
// Synced is used to ensure that the provider has synced with the server,
// So after Synced set to true, the other state is sure to be set.
synced: false,
+5
View File
@@ -25,6 +25,11 @@ export const Input = (props: inputProps) => {
} = props;
const [value, setValue] = useState<string>(valueProp || '');
const handleChange: InputHTMLAttributes<HTMLInputElement>['onChange'] = e => {
if (
(maxLength && e.target.value.length > maxLength) ||
(minLength && e.target.value.length < minLength)
)
return;
setValue(e.target.value);
onChange && onChange(e.target.value);
};