Merge branch 'feat/workspace' into feat/dev

# Conflicts:
#	packages/app/src/components/header/page-header.tsx
#	packages/app/src/components/workspace-slider-bar/index.tsx
This commit is contained in:
QiShaoXuan
2022-12-09 19:28:30 +08:00
26 changed files with 889 additions and 301 deletions
+69 -9
View File
@@ -16,13 +16,16 @@ import {
} from '@blocksuite/icons';
import { useEditor } from '@/providers/editor-provider';
import ThemeModeSwitch from '@/components/theme-mode-switch';
import { IconButton } from '@/ui/button';
import { IconButton, Button } from '@/ui/button';
import CloseIcon from '@mui/icons-material/Close';
import { getWarningMessage, shouldShowWarning } from './utils';
import { Menu, MenuItem } from '@/ui/menu';
import { useRouter } from 'next/router';
import { useConfirm } from '@/providers/confirm-provider';
const PopoverContent = () => {
const { editor, mode, setMode } = useEditor();
return (
<>
<MenuItem
@@ -81,9 +84,71 @@ const BrowserWarning = ({
);
};
export const Header = ({ children }: PropsWithChildren<{}>) => {
const [showWarning, setShowWarning] = useState(shouldShowWarning());
const HeaderRight = () => {
const { pageList, toggleDeletePage, permanentlyDeletePage } = useEditor();
const { confirm } = useConfirm();
const router = useRouter();
const currentPageMeta = pageList.find(p => p.id === router.query.pageId);
const isTrash = !!currentPageMeta?.trash;
if (isTrash) {
const { id } = currentPageMeta;
return (
<>
<Button
bold={true}
shape="round"
style={{ marginRight: '24px' }}
onClick={() => {
toggleDeletePage(id);
}}
>
Restore it
</Button>
<Button
bold={true}
shape="round"
type="danger"
onClick={() => {
confirm({
title: 'Permanently delete',
content:
"Once deleted, you can't undo this action. Do you confirm?",
confirmText: 'Delete',
confirmType: 'danger',
}).then(confirm => {
if (confirm) {
router.push({ pathname: '/page-list/all' });
permanentlyDeletePage(id);
}
});
}}
>
Delete permanently
</Button>
</>
);
}
return (
<>
<ThemeModeSwitch />
<Menu content={<PopoverContent />} placement="bottom-end">
<IconButton>
<MoreVertical_24pxIcon />
</IconButton>
</Menu>
</>
);
};
export const Header = ({ children }: PropsWithChildren<{}>) => {
const { pageList } = useEditor();
const [showWarning, setShowWarning] = useState(shouldShowWarning());
const router = useRouter();
const currentPageMeta = pageList.find(p => p.id === router.query.pageId);
const isTrash = !!currentPageMeta?.trash;
console.log('isTrash', isTrash);
return (
<StyledHeaderContainer hasWarning={showWarning}>
<BrowserWarning
@@ -95,12 +160,7 @@ export const Header = ({ children }: PropsWithChildren<{}>) => {
<StyledHeader hasWarning={showWarning}>
{children}
<StyledHeaderRightSide>
<ThemeModeSwitch />
<Menu content={<PopoverContent />} placement="bottom-end">
<IconButton>
<MoreVertical_24pxIcon />
</IconButton>
</Menu>
<HeaderRight />
</StyledHeaderRightSide>
</StyledHeader>
</StyledHeaderContainer>
@@ -1,14 +1,21 @@
import React, { useEffect, useState } from 'react';
import { StyledTitle, StyledTitleWrapper } from './styles';
import {
StyledSearchArrowWrapper,
StyledSwitchWrapper,
StyledTitle,
StyledTitleWrapper,
} from './styles';
import { IconButton } from '@/ui/button';
import { Content } from '@/ui/layout';
import { useEditor } from '@/providers/editor-provider';
import EditorModeSwitch from '@/components/editor-mode-switch';
import { MiddleIconArrowDownSmallIcon } from '@blocksuite/icons';
import { useModal } from '@/providers/global-modal-provider';
import IconButton from '@/ui/button/icon-button';
import Header from './header';
export const PageHeader = () => {
const [title, setTitle] = useState('');
const [title, setTitle] = useState('Untitled');
const [isHover, setIsHover] = useState(false);
const { editor } = useEditor();
@@ -24,35 +31,35 @@ export const PageHeader = () => {
return (
<Header>
{title ? (
<StyledTitle
onMouseEnter={() => {
setIsHover(true);
}}
onMouseLeave={() => {
setIsHover(false);
}}
>
<EditorModeSwitch
isHover={isHover}
style={{
marginRight: '12px',
}}
/>
<StyledTitleWrapper>{title}</StyledTitleWrapper>
<IconButton
style={{
marginLeft: '6px',
}}
>
<MiddleIconArrowDownSmallIcon
<StyledTitle
onMouseEnter={() => {
setIsHover(true);
}}
onMouseLeave={() => {
setIsHover(false);
}}
>
<StyledTitleWrapper>
<StyledSwitchWrapper>
<EditorModeSwitch
isHover={isHover}
style={{
marginRight: '12px',
}}
/>
</StyledSwitchWrapper>
<Content ellipsis={true}>{title}</Content>
<StyledSearchArrowWrapper>
<IconButton
onClick={() => {
triggerQuickSearchModal(true);
}}
/>
</IconButton>
</StyledTitle>
) : null}
>
<MiddleIconArrowDownSmallIcon />
</IconButton>
</StyledSearchArrowWrapper>
</StyledTitleWrapper>
</StyledTitle>
</Header>
);
};
+24 -3
View File
@@ -40,10 +40,9 @@ export const StyledTitle = styled('div')(({ theme }) => ({
export const StyledTitleWrapper = styled('div')({
maxWidth: '720px',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
height: '100%',
position: 'relative',
...displayFlex('center', 'center'),
});
export const StyledHeaderRightSide = styled('div')({
@@ -89,3 +88,25 @@ export const StyledCloseButton = styled.div(({ theme }) => {
},
};
});
export const StyledSwitchWrapper = styled.div(() => {
return {
position: 'absolute',
right: '100%',
top: 0,
bottom: 0,
margin: 'auto',
...displayFlex('center', 'center'),
};
});
export const StyledSearchArrowWrapper = styled.div(() => {
return {
position: 'absolute',
left: 'calc(100% + 4px)',
top: 0,
bottom: 0,
margin: 'auto',
...displayFlex('center', 'center'),
};
});
@@ -0,0 +1,12 @@
import React from 'react';
export const Empty = () => {
return (
<div style={{ textAlign: 'center' }}>
<p>Tips: Click Add to Favourites/Trash and the page will appear here.</p>
<p>(Designer is grappling with designing)</p>
</div>
);
};
export default Empty;
@@ -0,0 +1,97 @@
import { PageMeta, useEditor } from '@/providers/editor-provider';
import {
MiddleFavouritedStatus2Icon,
MiddleFavouritesIcon,
} from '@blocksuite/icons';
import {
StyledFavoriteButton,
StyledTableContainer,
StyledTableRow,
StyledTitleContent,
StyledTitleWrapper,
} from './styles';
import { Table, TableBody, TableCell, TableHead, TableRow } from '@/ui/table';
import { OperationCell, TrashOperationCell } from './operation-cell';
import Empty from './empty';
import Link from 'next/link';
import React from 'react';
const FavoriteTag = ({ pageMeta }: { pageMeta: PageMeta }) => {
const { toggleFavoritePage } = useEditor();
return (
<StyledFavoriteButton
className="favorite-button"
favorite={pageMeta.favorite}
onClick={() => {
toggleFavoritePage(pageMeta.id);
}}
>
{pageMeta.favorite ? (
<MiddleFavouritedStatus2Icon />
) : (
<MiddleFavouritesIcon />
)}
</StyledFavoriteButton>
);
};
export const PageList = ({
pageList,
showFavoriteTag = false,
isTrash = false,
}: {
pageList: PageMeta[];
showFavoriteTag?: boolean;
isTrash?: boolean;
}) => {
if (pageList.length === 0) {
return <Empty />;
}
return (
<StyledTableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell proportion={0.5}>Documents</TableCell>
<TableCell proportion={0.2}>Created</TableCell>
<TableCell proportion={0.2}>
{isTrash ? 'Uploaded' : 'Moved to Trash'}
</TableCell>
<TableCell proportion={0.1}></TableCell>
</TableRow>
</TableHead>
<TableBody>
{pageList.map((pageMeta, index) => {
return (
<StyledTableRow key={`${pageMeta.id}-${index}`}>
<TableCell>
<StyledTitleWrapper>
<Link
href={{ pathname: '/', query: { pageId: pageMeta.id } }}
>
<StyledTitleContent>
{pageMeta.title || pageMeta.id}
</StyledTitleContent>
</Link>
{showFavoriteTag && <FavoriteTag pageMeta={pageMeta} />}
</StyledTitleWrapper>
</TableCell>
<TableCell ellipsis={true}>{pageMeta.createDate}</TableCell>
<TableCell ellipsis={true}>{pageMeta.createDate}</TableCell>
<TableCell>
{isTrash ? (
<TrashOperationCell pageMeta={pageMeta} />
) : (
<OperationCell pageMeta={pageMeta} />
)}
</TableCell>
</StyledTableRow>
);
})}
</TableBody>
</Table>
</StyledTableContainer>
);
};
export default PageList;
@@ -0,0 +1,96 @@
import { PageMeta, useEditor } from '@/providers/editor-provider';
import { useConfirm } from '@/providers/confirm-provider';
import { Menu, MenuItem } from '@/ui/menu';
import { Wrapper } from '@/ui/layout';
import { IconButton } from '@/ui/button';
import {
MoreVertical_24pxIcon,
RestoreIcon,
TrashDeleteforeverIcon,
} from '@blocksuite/icons';
import React from 'react';
export const OperationCell = ({ pageMeta }: { pageMeta: PageMeta }) => {
const { id, favorite } = pageMeta;
const { openPage, toggleFavoritePage, toggleDeletePage } = useEditor();
const { confirm } = useConfirm();
const OperationMenu = (
<>
<MenuItem
onClick={() => {
toggleFavoritePage(id);
}}
>
{favorite ? 'Remove' : 'Add'} to favourites
</MenuItem>
<MenuItem
onClick={() => {
openPage(id);
}}
>
Open in new tab
</MenuItem>
<MenuItem
onClick={() => {
confirm({
title: 'Delete',
content:
'Deleted items will be moved to Trash Bin. Do you confirm?',
confirmText: 'Delete',
confirmType: 'danger',
}).then(confirm => {
confirm && toggleDeletePage(id);
});
}}
>
Delete
</MenuItem>
</>
);
return (
<Wrapper alignItems="center" justifyContent="center">
<Menu content={OperationMenu} placement="bottom-end" disablePortal={true}>
<IconButton hoverBackground="#E0E6FF">
<MoreVertical_24pxIcon />
</IconButton>
</Menu>
</Wrapper>
);
};
export const TrashOperationCell = ({ pageMeta }: { pageMeta: PageMeta }) => {
const { id } = pageMeta;
const { permanentlyDeletePage, toggleDeletePage } = useEditor();
const { confirm } = useConfirm();
return (
<Wrapper>
<IconButton
hoverBackground="#E0E6FF"
style={{ marginRight: '12px' }}
onClick={() => {
toggleDeletePage(id);
}}
>
<RestoreIcon />
</IconButton>
<IconButton
hoverBackground="#E0E6FF"
onClick={() => {
confirm({
title: 'Permanently delete',
content:
"Once deleted, you can't undo this action. Do you confirm?",
confirmText: 'Delete',
confirmType: 'danger',
}).then(confirm => {
confirm && permanentlyDeletePage(id);
});
}}
>
<TrashDeleteforeverIcon />
</IconButton>
</Wrapper>
);
};
@@ -0,0 +1,55 @@
import { displayFlex, styled, textEllipsis } from '@/styles';
import { TableRow } from '@/ui/table';
export const StyledTableContainer = styled.div(() => {
return {
height: 'calc(100vh - 60px)',
padding: '78px 72px',
overflowY: 'auto',
};
});
export const StyledTitleWrapper = styled.div(({ theme }) => {
return {
...displayFlex('flex-start', 'center'),
a: {
color: 'inherit',
},
'a:visited': {
color: 'unset',
},
'a:hover': {
color: theme.colors.primaryColor,
},
};
});
export const StyledTitleContent = styled.div(({ theme }) => {
return {
maxWidth: '90%',
marginRight: '18px',
...textEllipsis(1),
};
});
export const StyledFavoriteButton = styled.button<{ favorite: boolean }>(
({ theme, favorite }) => {
return {
width: '32px',
height: '32px',
justifyContent: 'center',
alignItems: 'center',
display: 'none',
color: favorite ? theme.colors.primaryColor : theme.colors.iconColor,
'&:hover': {
color: theme.colors.primaryColor,
},
};
}
);
export const StyledTableRow = styled(TableRow)(({ theme }) => {
return {
'&:hover': {
'.favorite-button': {
display: 'flex',
},
},
};
});
@@ -8,14 +8,51 @@ import {
StyledSubListItem,
} from './style';
import { Arrow } from './icons';
import Collapse from '@mui/material/Collapse';
import { MiddleIconArrowDownSmallIcon } from '@blocksuite/icons';
import Link from 'next/link';
import { useEditor } from '@/providers/editor-provider';
import { useModal } from '@/providers/global-modal-provider';
import { IconButton } from '@/ui/button';
const FavoriteList = ({ showList }: { showList: boolean }) => {
const { pageList, openPage } = useEditor();
const router = useRouter();
const favoriteList = pageList.filter(p => p.favorite);
return (
<Collapse in={showList}>
{favoriteList.map((pageMeta, index) => {
const active = router.query.pageId === pageMeta.id;
return (
<StyledSubListItem
active={active}
key={`${pageMeta}-${index}`}
onClick={() => {
if (active) {
return;
}
openPage(pageMeta.id);
}}
>
{pageMeta.title || pageMeta.id}
</StyledSubListItem>
);
})}
{favoriteList.length === 0 && (
<StyledSubListItem disable={true}>No item</StyledSubListItem>
)}
</Collapse>
);
};
export const WorkSpaceSliderBar = () => {
const { triggerQuickSearchModal } = useModal();
const [show, setShow] = useState(false);
const [showSubFavorite, setShowSubFavorite] = useState(false);
const { createPage } = useEditor();
const router = useRouter();
return (
<>
<StyledSliderBar show={show}>
@@ -38,18 +75,40 @@ export const WorkSpaceSliderBar = () => {
>
Back to Doc
</StyledListItem>
<Link href={{ pathname: '/all-page', query: { name: 'test' } }}>
<StyledListItem>All pages</StyledListItem>
<Link href={{ pathname: '/page-list/all' }}>
<StyledListItem active={router.pathname === '/page-list/all'}>
All pages
</StyledListItem>
</Link>
<StyledListItem>Favourites</StyledListItem>
<StyledSubListItem>
document 1, this is a paper icondocument 1
</StyledSubListItem>
<StyledSubListItem>document 2</StyledSubListItem>
<StyledSubListItem>document 4</StyledSubListItem>
<StyledListItem>Import</StyledListItem>
<StyledListItem>Bin</StyledListItem>
<StyledListItem active={router.pathname === '/page-list/favorite'}>
<Link
href={{ pathname: '/page-list/favorite' }}
style={{ flexGrow: 1, textAlign: 'left', color: 'inherit' }}
>
Favourites
</Link>
<IconButton
hoverBackground="#E0E6FF"
onClick={() => {
setShowSubFavorite(!showSubFavorite);
}}
>
<MiddleIconArrowDownSmallIcon
style={{
transform: `rotate(${showSubFavorite ? '180' : '0'}deg)`,
}}
/>
</IconButton>
</StyledListItem>
<FavoriteList showList={showSubFavorite} />
<StyledListItem>Import</StyledListItem>
<Link href={{ pathname: '/page-list/trash' }}>
<StyledListItem active={router.pathname === '/page-list/trash'}>
Trash
</StyledListItem>
</Link>
<StyledNewPageButton
onClick={() => {
createPage();
@@ -5,7 +5,7 @@ export const StyledSliderBar = styled.div<{ show: boolean }>(
return {
width: show ? '320px' : '0',
height: '100vh',
background: '#FBFBFC',
background: theme.mode === 'dark' ? '#272727' : '#FBFBFC',
boxShadow: theme.shadow.modal,
transition: 'width .15s',
position: 'relative',
@@ -42,22 +42,25 @@ export const StyledArrowButton = styled.button<{ isShow: boolean }>(
}
);
export const StyledListItem = styled.button(({ theme }) => {
return {
width: '296px',
height: '32px',
marginTop: '12px',
fontSize: theme.font.sm,
color: theme.colors.popoverColor,
padding: '0 12px',
borderRadius: '5px',
...displayFlex('flex-start', 'center'),
':hover': {
color: theme.colors.primaryColor,
backgroundColor: theme.colors.hoverBackground,
},
};
});
export const StyledListItem = styled.button<{ active?: boolean }>(
({ theme, active }) => {
return {
width: '296px',
height: '32px',
marginTop: '12px',
fontSize: theme.font.sm,
color: active ? theme.colors.primaryColor : theme.colors.popoverColor,
backgroundColor: active ? theme.colors.hoverBackground : 'unset',
paddingLeft: '12px',
borderRadius: '5px',
...displayFlex('flex-start', 'center'),
':hover': {
color: theme.colors.primaryColor,
backgroundColor: theme.colors.hoverBackground,
},
};
}
);
export const StyledNewPageButton = styled(StyledListItem)(({ theme }) => {
return {
@@ -69,20 +72,32 @@ export const StyledNewPageButton = styled(StyledListItem)(({ theme }) => {
};
});
export const StyledSubListItem = styled.button(({ theme }) => {
export const StyledSubListItem = styled.button<{
disable?: boolean;
active?: boolean;
}>(({ theme, disable, active }) => {
return {
width: '296px',
height: '32px',
marginTop: '4px',
fontSize: theme.font.sm,
color: theme.colors.popoverColor,
color: disable
? theme.colors.iconColor
: active
? theme.colors.primaryColor
: theme.colors.popoverColor,
backgroundColor: active ? theme.colors.hoverBackground : 'unset',
cursor: disable ? 'not-allowed' : 'pointer',
paddingLeft: '45px',
lineHeight: '32px',
textAlign: 'start',
...textEllipsis(1),
':hover': {
color: theme.colors.primaryColor,
backgroundColor: theme.colors.hoverBackground,
},
':hover': disable
? {}
: {
color: theme.colors.primaryColor,
backgroundColor: theme.colors.hoverBackground,
},
};
});