Feat/cloud integration (#569)

* Chore/unit test (#538)

* chore: add unit test

* chore: add github action for unit test

* feat: init firebase

* chore: add development secrets

* fix: rename auth -> data-services

* feat: update blocksuite 0.3.0-alpha.4 (#543)

* feat: add requests

* feat: optimize  swr cache

* feat: add Authorization

* feat: add confirm-invitation page

* feat: add account sdk api and proxy

* docs: update contributing (#550)

* docs: update contributing

* Update CONTRIBUTING.md

* Update CONTRIBUTING.md

Co-authored-by: ShortCipher5 <me@shortcipher.me>

* feat: update api

* feat: remove babelrc setting

* feat: add create workspace ui

* feat: choose workspaces

* feat: login modal

* feat: authorization api

* feat: login status

* fix: remove unused variables

* feat: login button

* fix: lint

* fix: workspace id

* fix: i18n type error

Co-authored-by: MingLiang Wang <mingliangwang0o0@gmail.com>
Co-authored-by: ShortCipher5 <me@shortcipher.me>
This commit is contained in:
zuomeng wang
2022-12-19 10:50:22 +08:00
committed by GitHub
parent 820d8d476a
commit 5870a6097a
91 changed files with 5391 additions and 524 deletions
@@ -0,0 +1,85 @@
import { InformationIcon, LogOutIcon } from '@blocksuite/icons';
import { styled } from '@/styles';
import { Divider } from '@/ui/divider';
import { useAppState } from '@/providers/app-state-provider';
import { SelectorPopperContainer } from './styles';
import {
PrivateWorkspaceItem,
WorkspaceItem,
CreateWorkspaceItem,
ListItem,
LoginItem,
} from './WorkspaceItem';
const workspaces = [
{
name: 'Design',
icon: '',
},
{
name: 'Operation',
icon: '',
},
{
name: 'Something is too long to show in this box',
icon: '',
},
];
export const SelectorPopperContent = () => {
const { user, workspaces } = useAppState();
return !user ? (
<SelectorPopperContainer placement="bottom-start">
<LoginItem />
<StyledDivider />
<ListItem
icon={<InformationIcon />}
name="About AFFiNE"
onClick={() => console.log('About AFFiNE')}
/>
</SelectorPopperContainer>
) : (
<SelectorPopperContainer placement="bottom-start">
<PrivateWorkspaceItem />
<StyledDivider />
<WorkspaceGroupTitle>Workspace</WorkspaceGroupTitle>
{workspaces.map(workspace => {
return (
<WorkspaceItem
key={workspace.id}
id={workspace.id}
name={`workspace-${workspace.id}`}
icon={''}
/>
);
})}
<CreateWorkspaceItem />
<StyledDivider />
<ListItem
icon={<InformationIcon />}
name="About AFFiNE"
onClick={() => console.log('About AFFiNE')}
/>
<ListItem
icon={<LogOutIcon />}
name="Sign out"
onClick={() => console.log('Sign out')}
/>
</SelectorPopperContainer>
);
};
const StyledDivider = styled(Divider)({
margin: '8px 12px',
width: 'calc(100% - 24px)',
});
const WorkspaceGroupTitle = styled('div')(({ theme }) => {
return {
color: theme.colors.iconColor,
fontSize: theme.font.sm,
lineHeight: '30px',
height: '30px',
padding: '0 12px',
};
});
@@ -0,0 +1,39 @@
import { useState } from 'react';
import { AddIcon } from '@blocksuite/icons';
import { styled } from '@/styles';
import {
WorkspaceItemAvatar,
WorkspaceItemWrapper,
WorkspaceItemContent,
} from '../styles';
import { WorkspaceCreate } from './workspace-create';
const name = 'Create new Workspace';
export const CreateWorkspaceItem = () => {
const [open, setOpen] = useState(false);
return (
<>
<WorkspaceItemWrapper onClick={() => setOpen(true)}>
<WorkspaceItemAvatar>
<AddIcon />
</WorkspaceItemAvatar>
<WorkspaceItemContent>
<Name title={name}>{name}</Name>
</WorkspaceItemContent>
</WorkspaceItemWrapper>
<WorkspaceCreate open={open} onClose={() => setOpen(false)} />
</>
);
};
const Name = styled('div')(({ theme }) => {
return {
color: theme.colors.quoteColor,
fontSize: theme.font.base,
fontWeight: 400,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
};
});
@@ -0,0 +1 @@
export * from './CreateWorkspaceItem';
@@ -0,0 +1,55 @@
import { styled } from '@/styles';
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',
};
});
@@ -0,0 +1,58 @@
import { createWorkspace } from '@pathfinder/data-services';
import Modal from '@/ui/modal';
import Input from '@/ui/input';
import { Button } from '@/ui/button';
import {
StyledModalHeader,
StyledTextContent,
StyledModalWrapper,
StyledInputContent,
StyledButtonContent,
} from './style';
import { useState } from 'react';
import { ModalCloseButton } from '@/ui/modal';
interface WorkspaceCreateProps {
open: boolean;
onClose: () => void;
}
export const WorkspaceCreate = ({ open, onClose }: WorkspaceCreateProps) => {
const [workspaceName, setWorkspaceId] = useState<string>('');
const handlerInputChange = (workspaceName: string) => {
setWorkspaceId(workspaceName);
};
console.log('workspaceName', workspaceName);
return (
<Modal open={open} onClose={onClose}>
<StyledModalWrapper>
<ModalCloseButton />
<StyledModalHeader>Create new Workspace</StyledModalHeader>
<StyledTextContent>
Workspaces are shared environments where teams can collaborate. After
creating a Workspace, you can invite others to join.
</StyledTextContent>
<StyledInputContent>
<Input
onChange={handlerInputChange}
placeholder="Set a Workspace name"
value={workspaceName}
></Input>
</StyledInputContent>
<StyledButtonContent>
<Button
disabled={!workspaceName.length}
style={{ width: '260px' }}
onClick={() => {
createWorkspace({ name: workspaceName, avatar: '' });
}}
>
Create
</Button>
</StyledButtonContent>
</StyledModalWrapper>
</Modal>
);
};
export default WorkspaceCreate;
@@ -0,0 +1,37 @@
import type { ReactNode } from 'react';
import { styled } from '@/styles';
import { WorkspaceItemWrapper, WorkspaceItemContent } from './styles';
interface ListItemProps {
name: string;
icon: ReactNode;
onClick: () => void;
}
export const ListItem = ({ name, icon, onClick }: ListItemProps) => {
return (
<WorkspaceItemWrapper onClick={onClick}>
<StyledIconWrapper>{icon}</StyledIconWrapper>
<WorkspaceItemContent>
<Name title={name}>{name}</Name>
</WorkspaceItemContent>
</WorkspaceItemWrapper>
);
};
const Name = styled('div')(({ theme }) => {
return {
color: theme.colors.quoteColor,
fontSize: theme.font.sm,
fontWeight: 400,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
};
});
const StyledIconWrapper = styled('div')({
width: '20px',
height: '20px',
fontSize: '20px',
});
@@ -0,0 +1,48 @@
import { useModal } from '@/providers/global-modal-provider';
import { styled } from '@/styles';
import {
WorkspaceItemAvatar,
LoginItemWrapper,
WorkspaceItemContent,
} from './styles';
export const LoginItem = () => {
const { triggerLoginModal } = useModal();
return (
<LoginItemWrapper onClick={() => triggerLoginModal()}>
<WorkspaceItemAvatar alt="AFFiNE" src={''}>
A
</WorkspaceItemAvatar>
<WorkspaceItemContent>
<Name title="AFFiNE">AFFiNE</Name>
<Description
title="Log in to sync with affine"
className="login-description"
>
Log in to sync with affine
</Description>
</WorkspaceItemContent>
</LoginItemWrapper>
);
};
const Name = styled('div')(({ theme }) => {
return {
color: theme.colors.quoteColor,
fontSize: theme.font.base,
fontWeight: 500,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
};
});
const Description = styled('div')(({ theme }) => {
return {
color: theme.colors.iconColor,
fontSize: theme.font.sm,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
};
});
@@ -0,0 +1,44 @@
import { styled } from '@/styles';
import { useAppState } from '@/providers/app-state-provider';
import {
WorkspaceItemAvatar,
PrivateWorkspaceWrapper,
WorkspaceItemContent,
} from './styles';
export const PrivateWorkspaceItem = () => {
const { user } = useAppState();
return !user ? null : (
<PrivateWorkspaceWrapper>
<WorkspaceItemAvatar alt={user.name} src={user.avatar_url}>
{user.name.charAt(0)}
</WorkspaceItemAvatar>
<WorkspaceItemContent>
<Name title={user.name}>{user.name}</Name>
<Email title={user.email}>{user.email}</Email>
</WorkspaceItemContent>
</PrivateWorkspaceWrapper>
);
};
const Name = styled('div')(({ theme }) => {
return {
color: theme.colors.quoteColor,
fontSize: theme.font.base,
fontWeight: 500,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
};
});
const Email = styled('div')(({ theme }) => {
return {
color: theme.colors.iconColor,
fontSize: theme.font.sm,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
};
});
@@ -0,0 +1,17 @@
import { SettingsIcon } from '@blocksuite/icons';
import { styled } from '@/styles';
import { IconButton } from '@/ui/button';
export const FooterSetting = () => {
return (
<Wrapper className="footer-setting">
<SettingsIcon />
</Wrapper>
);
};
const Wrapper = styled(IconButton)(({ theme }) => {
return {
fontSize: '20px',
};
});
@@ -0,0 +1,25 @@
import { UsersIcon } from '@blocksuite/icons';
import { styled } from '@/styles';
import { IconButton } from '@/ui/button';
export const FooterUsers = () => {
return (
<Wrapper className="footer-users">
<>
<UsersIcon />
<Tip>99+</Tip>
</>
</Wrapper>
);
};
const Wrapper = styled(IconButton)({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '16px',
});
const Tip = styled('span')({
fontSize: '12px',
});
@@ -0,0 +1,64 @@
import { styled } from '@/styles';
import {
WorkspaceItemAvatar,
WorkspaceItemWrapper,
WorkspaceItemContent,
} from '../styles';
import { FooterSetting } from './FooterSetting';
import { FooterUsers } from './FooterUsers';
interface WorkspaceItemProps {
id: string;
name: string;
icon: string;
}
export const WorkspaceItem = ({ name, icon }: WorkspaceItemProps) => {
return (
<StyledWrapper>
<WorkspaceItemAvatar alt={name} src={icon}>
{name.charAt(0)}
</WorkspaceItemAvatar>
<WorkspaceItemContent>
<Name title={name}>{name}</Name>
</WorkspaceItemContent>
<Footer>
<FooterUsers />
<FooterSetting />
</Footer>
</StyledWrapper>
);
};
const Name = styled('div')(({ theme }) => {
return {
color: theme.colors.quoteColor,
fontSize: theme.font.sm,
fontWeight: 400,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
};
});
const StyledWrapper = styled(WorkspaceItemWrapper)({
'& .footer-setting': {
display: 'none',
},
':hover .footer-users': {
display: 'none',
},
':hover .footer-setting': {
display: 'block',
},
});
const Footer = styled('div')({
width: '42px',
flex: '0 42px',
fontSize: '20px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
marginLeft: '12px',
});
@@ -0,0 +1 @@
export * from './WorkspaceItem';
@@ -0,0 +1,5 @@
export * from './PrivateWorkspaceItem';
export * from './WorkspaceItem';
export * from './CreateWorkspaceItem';
export * from './ListItem';
export * from './LoginItem';
@@ -0,0 +1,41 @@
import MuiAvatar from '@mui/material/Avatar';
import { styled } from '@/styles';
export const WorkspaceItemWrapper = styled('div')(({ theme }) => ({
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
cursor: 'pointer',
borderRadius: '5px',
padding: '6px 12px',
':hover': {
color: theme.colors.primaryColor,
backgroundColor: theme.colors.hoverBackground,
},
}));
export const PrivateWorkspaceWrapper = styled(WorkspaceItemWrapper)({
padding: '10px 12px',
});
export const LoginItemWrapper = styled(WorkspaceItemWrapper)(({ theme }) => {
return {
padding: '10px 12px',
':hover .login-description': {
color: theme.colors.primaryColor,
},
};
});
export const WorkspaceItemAvatar = styled(MuiAvatar)({
height: '40px',
width: '40px',
});
export const WorkspaceItemContent = styled('div')({
minWidth: 0,
marginLeft: '12px',
flexGrow: 1,
});
@@ -0,0 +1,18 @@
import { Popper } from '@/ui/popper';
import { Avatar, WorkspaceName, SelectorWrapper } from './styles';
import { SelectorPopperContent } from './SelectorPopperContent';
export const WorkspaceSelector = () => {
return (
<Popper
content={<SelectorPopperContent />}
zIndex={1000}
placement="bottom-start"
>
<SelectorWrapper>
<Avatar alt="Affine" />
<WorkspaceName>AFFiNE</WorkspaceName>
</SelectorWrapper>
</Popper>
);
};
@@ -0,0 +1 @@
export * from './WorkspaceSelector';
@@ -0,0 +1,38 @@
import MuiAvatar from '@mui/material/Avatar';
import { styled } from '@/styles';
import { StyledPopperContainer } from '@/ui/shared/Container';
export const SelectorWrapper = styled('div')({
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
});
export const Avatar = styled(MuiAvatar)({
height: '28px',
width: '28px',
});
export const WorkspaceName = styled('span')(({ theme }) => {
return {
marginLeft: '12px',
lineHeight: 1,
fontSize: '18px',
fontWeight: 500,
color: theme.colors.iconColor,
};
});
export const SelectorPopperContainer = styled(StyledPopperContainer)(
({ theme }) => {
return {
width: '334px',
boxShadow: theme.shadow.tooltip,
padding: '24px 12px',
backgroundColor: theme.colors.pageBackground,
fontSize: theme.font.xs,
};
}
);
@@ -4,6 +4,7 @@ import {
StyledArrowButton,
StyledLink,
StyledListItem,
StyledListItemForWorkspace,
StyledNewPageButton,
StyledSliderBar,
StyledSubListItem,
@@ -25,6 +26,7 @@ import { useEditor } from '@/providers/editor-provider';
import { useModal } from '@/providers/global-modal-provider';
import { IconButton } from '@/ui/button';
import { WorkspaceSelector } from './WorkspaceSelector';
import useLocalStorage from '@/hooks/use-local-storage';
import { useTranslation } from '@/libs/i18n';
const FavoriteList = ({ showList }: { showList: boolean }) => {
@@ -69,6 +71,9 @@ export const WorkSpaceSliderBar = () => {
return (
<>
<StyledSliderBar show={show}>
<StyledListItemForWorkspace>
<WorkspaceSelector />
</StyledListItemForWorkspace>
<Tooltip content="Search and quickly jump to a page" placement="right">
<StyledListItem
onClick={() => {
@@ -80,7 +85,7 @@ export const WorkSpaceSliderBar = () => {
</Tooltip>
<Link href={{ pathname: '/page-list/all' }}>
<StyledListItem active={router.pathname === '/page-list/all'}>
<AllPagesIcon /> {t('All pages')}
<AllPagesIcon /> <span>All pages</span>
</StyledListItem>
</Link>
<StyledListItem active={router.pathname === '/page-list/favorite'}>
@@ -67,6 +67,10 @@ export const StyledListItem = styled.button<{ active?: boolean }>(
}
);
export const StyledListItemForWorkspace = styled(StyledListItem)({
height: '52px',
});
export const StyledLink = styled(Link)(({ theme }) => {
return {
flexGrow: 1,