diff --git a/packages/app/src/components/workspace-modal/Footer.tsx b/packages/app/src/components/workspace-modal/Footer.tsx new file mode 100644 index 000000000..ea38de2a9 --- /dev/null +++ b/packages/app/src/components/workspace-modal/Footer.tsx @@ -0,0 +1,56 @@ +import { CloudInsyncIcon, LogOutIcon } from '@blocksuite/icons'; +import { Wrapper } from '@/ui/layout'; +import { WorkspaceAvatar } from '@/components/workspace-avatar'; +import { IconButton } from '@/ui/button'; +import { useAppState } from '@/providers/app-state-provider'; +import { StyledFooter, StyleUserInfo, StyleSignIn } from './styles'; + +export const Footer = ({ + onLogin, + onLogout, +}: { + onLogin: () => void; + onLogout: () => void; +}) => { + const { user } = useAppState(); + + return ( + + {user && ( + <> + + + +

{user.name}

+

{user.email}

+
+
+ { + onLogout(); + }} + > + + + + )} + + {!user && ( + { + onLogin(); + }} + > + + + + Sign in to sync with AFFINE Cloud + + )} +
+ ); +}; diff --git a/packages/app/src/components/workspace-modal/LanguageMenu.tsx b/packages/app/src/components/workspace-modal/LanguageMenu.tsx new file mode 100644 index 000000000..ad579e145 --- /dev/null +++ b/packages/app/src/components/workspace-modal/LanguageMenu.tsx @@ -0,0 +1,61 @@ +import { LOCALES } from '@affine/i18n'; +import { styled } from '@/styles'; +import { useTranslation } from '@affine/i18n'; +import { ArrowDownIcon } from '@blocksuite/icons'; +import { Button } from '@/ui/button'; +import { Menu, MenuItem } from '@/ui/menu'; + +const LanguageMenuContent = () => { + const { i18n } = useTranslation(); + const changeLanguage = (event: string) => { + i18n.changeLanguage(event); + }; + return ( + <> + {LOCALES.map(option => { + return ( + { + changeLanguage(option.tag); + }} + > + {option.originalName} + + ); + })} + + ); +}; +export const LanguageMenu = () => { + const { i18n } = useTranslation(); + + const currentLanguage = LOCALES.find(item => item.tag === i18n.language); + + return ( + } + placement="bottom" + trigger="click" + disablePortal={true} + > + + + ); +}; + +const ListItem = styled(MenuItem)(({ theme }) => ({ + height: '38px', + color: theme.colors.popoverColor, + fontSize: theme.font.sm, + textTransform: 'capitalize', + padding: '0 24px', +})); diff --git a/packages/app/src/components/workspace-modal/WorkspaceCard.tsx b/packages/app/src/components/workspace-modal/WorkspaceCard.tsx new file mode 100644 index 000000000..1a6032d65 --- /dev/null +++ b/packages/app/src/components/workspace-modal/WorkspaceCard.tsx @@ -0,0 +1,69 @@ +import { WorkspaceUnitAvatar } from '@/components/workspace-avatar'; +import { + CloudIcon, + LocalIcon, + OfflineIcon, +} from '@/components/workspace-modal/icons'; +import { PublishIcon, UsersIcon } from '@blocksuite/icons'; +import { WorkspaceUnit } from '@affine/datacenter'; +import { useAppState } from '@/providers/app-state-provider'; +import { StyleWorkspaceInfo, StyleWorkspaceTitle, StyledCard } from './styles'; +import { Wrapper } from '@/ui/layout'; + +export const WorkspaceCard = ({ + workspaceData, + onClick, +}: { + workspaceData: WorkspaceUnit; + onClick: (data: WorkspaceUnit) => void; +}) => { + const { currentWorkspace, isOwner } = useAppState(); + + return ( + { + onClick(workspaceData); + }} + active={workspaceData.id === currentWorkspace?.id} + > + + + + + + + {workspaceData.name || 'AFFiNE'} + + {isOwner ? ( + workspaceData.provider === 'local' ? ( +

+ + Local Workspace +

+ ) : ( +

+ + Cloud Workspace +

+ ) + ) : ( +

+ + Joined Workspace +

+ )} + {workspaceData.provider === 'local' && ( +

+ + All data can be accessed offline +

+ )} + {workspaceData.published && ( +

+ Published to Web +

+ )} +
+
+ ); +}; diff --git a/packages/app/src/components/workspace-modal/index.tsx b/packages/app/src/components/workspace-modal/index.tsx index c0dd2a8bd..93b1bb727 100644 --- a/packages/app/src/components/workspace-modal/index.tsx +++ b/packages/app/src/components/workspace-modal/index.tsx @@ -1,28 +1,35 @@ -import { styled } from '@/styles'; -import { Modal, ModalWrapper } from '@/ui/modal'; -import { Button, IconButton } from '@/ui/button'; +import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal'; +import { Wrapper } from '@/ui/layout'; import { useState } from 'react'; import { CreateWorkspaceModal } from '../create-workspace'; -import { - UsersIcon, - AddIcon, - LogOutIcon, - CloudInsyncIcon, - PublishIcon, - CloseIcon, -} from '@blocksuite/icons'; -import { - WorkspaceAvatar, - WorkspaceUnitAvatar, -} from '@/components/workspace-avatar'; +import { Tooltip } from '@/ui/tooltip'; +import { toast } from '@/ui/toast'; + +import { AddIcon, HelpCenterIcon } from '@blocksuite/icons'; + import { useAppState } from '@/providers/app-state-provider'; import { useRouter } from 'next/router'; import { useTranslation } from '@affine/i18n'; -import { LanguageMenu } from './languageMenu'; +import { LanguageMenu } from './LanguageMenu'; -import { CloudIcon, LineIcon, LocalIcon, OfflineIcon } from './icons'; import { LoginModal } from '../login-modal'; import { LogoutModal } from '../logout-modal'; +import { + StyledCard, + StyledSplitLine, + StyleWorkspaceInfo, + StyleWorkspaceTitle, + StyledModalHeaderLeft, + StyledModalTitle, + StyledHelperContainer, + StyledModalContent, + StyledOperationWrapper, + StyleWorkspaceAdd, + StyledModalHeader, +} from './styles'; +import { WorkspaceCard } from './WorkspaceCard'; +import { Footer } from './Footer'; +import { useConfirm } from '@/providers/ConfirmProvider'; interface WorkspaceModalProps { open: boolean; onClose: () => void; @@ -30,337 +37,128 @@ interface WorkspaceModalProps { export const WorkspaceModal = ({ open, onClose }: WorkspaceModalProps) => { const [createWorkspaceOpen, setCreateWorkspaceOpen] = useState(false); - const { workspaceList, currentWorkspace, user, logout, isOwner } = - useAppState(); + const { workspaceList, logout } = useAppState(); const router = useRouter(); const { t } = useTranslation(); const [loginOpen, setLoginOpen] = useState(false); const [logoutOpen, setLogoutOpen] = useState(false); + const { confirm } = useConfirm(); + return ( -
+ <> -
- {t('My Workspaces')} - - -
+ + {t('My Workspaces')} + - -
+ + + + + - -
-
- - - {workspaceList.map((item, index) => { - return ( - { - router.replace(`/workspace/${item.id}`); - onClose(); - }} - active={item.id === currentWorkspace?.id} - key={index} - > -
- -
+ absolute={false} + /> + + - - - {item.name || 'AFFiNE'} - - {isOwner ? ( - item.provider === 'local' ? ( -

- - Local Workspace -

- ) : ( -

- - Cloud Workspace -

- ) - ) : ( -

- - Joined Workspace -

- )} - {item.provider === 'local' && ( -

- - All data can be accessed offline -

- )} - {item.published && ( -

- Published to Web -

- )} -
-
- ); - })} - { - setCreateWorkspaceOpen(true); - }} - > -
- - - -
+ + {workspaceList.map((item, index) => { + return ( + { + router.replace(`/workspace/${workspaceData.id}`); + onClose(); + }} + key={index} + > + ); + })} + { + setCreateWorkspaceOpen(true); + }} + > + + + + + - - New workspace -

Crete or import

-
-
-
- {/*

- {t('Tips')} - {t('Workspace description')} -

*/} -
- { - setLoginOpen(false); + + New workspace +

Crete or import

+
+ + + +
+ + { + setLoginOpen(false); + }} + /> + { + if (!wait) { + await logout(); + router.replace(`/workspace`); + } + setLogoutOpen(false); + }} + /> + { + setCreateWorkspaceOpen(false); + }} + /> + ); }; - -const Header = styled('div')({ - display: 'flex', -}); - -const Content = styled('div')({ - flexDirection: 'column', - alignItems: 'center', - gap: '16px', - flex: 1, -}); -const HeaderOption = styled.div(() => { - return { - marginLeft: '16px', - }; -}); -const ContentTitle = styled('div')({ - fontSize: '20px', - lineHeight: '24px', - fontWeight: 600, - textAlign: 'left', - flex: 1, -}); - -const WorkspaceList = styled('div')({ - maxHeight: '500px', - overflow: 'auto', - display: 'grid', - gridRowGap: '24px', - gridColumnGap: '24px', - fontSize: '16px', - marginTop: '36px', - gridTemplateColumns: 'repeat(2, 1fr)', -}); - -export const WorkspaceItem = styled.div<{ - active?: boolean; -}>(({ theme, active }) => { - const borderColor = active ? theme.colors.primaryColor : 'transparent'; - return { - cursor: 'pointer', - padding: '16px', - height: '124px', - boxShadow: theme.shadow.modal, - display: 'flex', - borderRadius: '12px', - border: `1px solid ${borderColor}`, - ':hover': { - background: theme.colors.hoverBackground, - '.add-icon': { - border: `1.5px dashed ${theme.colors.primaryColor}`, - svg: { - fill: theme.colors.primaryColor, - }, - }, - }, - }; -}); - -const StyleWorkspaceInfo = styled.div(({ theme }) => { - return { - marginLeft: '16px', - p: { - fontSize: theme.font.xs, - lineHeight: '16px', - }, - svg: { - verticalAlign: 'text-bottom', - marginRight: '8px', - }, - }; -}); - -const StyleWorkspaceTitle = styled.div(({ theme }) => { - return { - fontSize: theme.font.base, - fontWeight: 600, - lineHeight: '24px', - marginBottom: '8px', - }; -}); - -const StyleWorkspaceAdd = styled.div(() => { - return { - width: '58px', - height: '58px', - borderRadius: '100%', - textAlign: 'center', - background: '#f4f5fa', - border: '1.5px dashed #f4f5fa', - lineHeight: '58px', - marginTop: '2px', - }; -}); - -const Footer = styled('div')({ - paddingTop: '16px', -}); - -const StyleUserInfo = styled.div(({ theme }) => { - return { - textAlign: 'left', - marginLeft: '16px', - marginTop: '16px', - flex: 1, - p: { - lineHeight: '24px', - color: theme.colors.iconColor, - }, - 'p:nth-child(1)': { - color: theme.colors.textColor, - fontWeight: 600, - }, - }; -}); - -const StyleSignIn = styled.div(({ theme }) => { - return { - cursor: 'pointer', - fontSize: '16px', - fontWeight: 700, - color: theme.colors.iconColor, - span: { - display: 'inline-block', - width: '40px', - height: '40px', - borderRadius: '40px', - backgroundColor: theme.colors.innerHoverBackground, - textAlign: 'center', - lineHeight: '44px', - marginRight: '16px', - svg: { - fill: theme.colors.primaryColor, - }, - }, - }; -}); diff --git a/packages/app/src/components/workspace-modal/languageMenu.tsx b/packages/app/src/components/workspace-modal/languageMenu.tsx index d8f907663..ad579e145 100644 --- a/packages/app/src/components/workspace-modal/languageMenu.tsx +++ b/packages/app/src/components/workspace-modal/languageMenu.tsx @@ -1,93 +1,61 @@ import { LOCALES } from '@affine/i18n'; -import { TooltipProps } from '@mui/material'; import { styled } from '@/styles'; -import { Tooltip } from '@mui/material'; -import { useState } from 'react'; import { useTranslation } from '@affine/i18n'; import { ArrowDownIcon } from '@blocksuite/icons'; import { Button } from '@/ui/button'; +import { Menu, MenuItem } from '@/ui/menu'; -export const LanguageMenu = () => { +const LanguageMenuContent = () => { const { i18n } = useTranslation(); const changeLanguage = (event: string) => { i18n.changeLanguage(event); }; - const [show, setShow] = useState(false); - const currentLanguage = LOCALES.find(item => item.tag === i18n.language); - const [languageName, setLanguageName] = useState( - currentLanguage?.originalName - ); return ( - - {LOCALES.map(option => { - return ( - { - changeLanguage(option.tag); - setShow(false); - setLanguageName(option.originalName); - }} - > - {option.originalName} - - ); - })} - - } - open={show} + <> + {LOCALES.map(option => { + return ( + { + changeLanguage(option.tag); + }} + > + {option.originalName} + + ); + })} + + ); +}; +export const LanguageMenu = () => { + const { i18n } = useTranslation(); + + const currentLanguage = LOCALES.find(item => item.tag === i18n.language); + + return ( + } + placement="bottom" + trigger="click" + disablePortal={true} > - { - setShow(!show); - }} + + ); }; -const StyledContainer = styled('div')(() => ({ - display: 'flex', - alignItems: 'center', -})); - -const StyledText = styled('span')(({ theme }) => ({ - marginRight: '4px', - marginLeft: '16px', - fontSize: theme.font.sm, - fontWeight: '500', - textTransform: 'capitalize', -})); -const StyledTooltip = styled(({ className, ...props }: TooltipProps) => ( - -))(({ theme }) => ({ - zIndex: theme.zIndex.modal, - '& .MuiTooltip-tooltip': { - backgroundColor: theme.colors.popoverBackground, - boxShadow: theme.shadow.modal, - color: theme.colors.popoverColor, - }, -})); - -const ListItem = styled(Button)(({ theme }) => ({ - display: 'block', - width: '100%', +const ListItem = styled(MenuItem)(({ theme }) => ({ + height: '38px', color: theme.colors.popoverColor, fontSize: theme.font.sm, textTransform: 'capitalize', -})); - -const StyledTitleButton = styled(Button)(({ theme }) => ({ - color: theme.colors.popoverColor, - fontSize: theme.font.sm, + padding: '0 24px', })); diff --git a/packages/app/src/components/workspace-modal/styles.ts b/packages/app/src/components/workspace-modal/styles.ts index 40a8bcc37..57146afbd 100644 --- a/packages/app/src/components/workspace-modal/styles.ts +++ b/packages/app/src/components/workspace-modal/styles.ts @@ -1,38 +1,161 @@ import { displayFlex, styled } from '@/styles'; -export const StyledTitle = styled.div(() => { +export const StyledSplitLine = styled.div(({ theme }) => { return { - ...displayFlex('center', 'center'), - fontSize: '20px', - fontWeight: 500, - marginTop: '60px', - lineHeight: 1, + width: '1px', + height: '20px', + background: theme.colors.iconColor, + marginRight: '24px', }; }); -export const StyledContent = styled.div(() => { +export const StyleWorkspaceInfo = styled.div(({ theme }) => { return { - padding: '0 40px', - marginTop: '32px', - fontSize: '18px', - lineHeight: '25px', - 'p:not(last-of-type)': { - marginBottom: '10px', + marginLeft: '15px', + p: { + height: '20px', + fontSize: theme.font.xs, + ...displayFlex('flex-start', 'center'), + }, + svg: { + marginRight: '10px', }, }; }); -export const StyledButton = styled.div(({ theme }) => { +export const StyleWorkspaceTitle = styled.div(({ theme }) => { return { - width: '146px', - height: '42px', - background: theme.colors.primaryColor, - color: '#FFFFFF', - fontSize: '18px', - fontWeight: 500, - borderRadius: '21px', - margin: '52px auto 0', + fontSize: theme.font.base, + fontWeight: 600, + lineHeight: '24px', + marginBottom: '10px', + }; +}); + +export const StyledCard = styled.div<{ + active?: boolean; +}>(({ theme, active }) => { + const borderColor = active ? theme.colors.primaryColor : 'transparent'; + return { + width: '310px', + height: '124px', cursor: 'pointer', + padding: '16px', + boxShadow: '0px 0px 8px rgba(0, 0, 0, 0.1)', + borderRadius: '12px', + border: `1px solid ${borderColor}`, + ...displayFlex('flex-start', 'flex-start'), + ':hover': { + background: theme.colors.hoverBackground, + '.add-icon': { + border: `1.5px dashed ${theme.colors.primaryColor}`, + svg: { + fill: theme.colors.primaryColor, + }, + }, + }, + }; +}); + +export const StyledFooter = styled('div')({ + height: '84px', + padding: '0 40px', + ...displayFlex('space-between', 'center'), +}); + +export const StyleUserInfo = styled.div(({ theme }) => { + return { + textAlign: 'left', + marginLeft: '16px', + flex: 1, + p: { + lineHeight: '24px', + color: theme.colors.iconColor, + }, + 'p:nth-child(1)': { + color: theme.colors.textColor, + fontWeight: 600, + }, + }; +}); + +export const StyleSignIn = styled.div(({ theme }) => { + return { + cursor: 'pointer', + fontSize: '16px', + fontWeight: 700, + color: theme.colors.iconColor, + span: { + display: 'inline-block', + width: '40px', + height: '40px', + borderRadius: '40px', + backgroundColor: theme.colors.innerHoverBackground, + textAlign: 'center', + lineHeight: '44px', + marginRight: '16px', + svg: { + fill: theme.colors.primaryColor, + }, + }, + }; +}); + +export const StyledModalHeaderLeft = styled.div(() => { + return { ...displayFlex('flex-start', 'center') }; +}); +export const StyledModalTitle = styled.div(({ theme }) => { + return { + fontWeight: 600, + fontSize: theme.font.h6, + }; +}); + +export const StyledHelperContainer = styled.div(({ theme }) => { + return { + color: theme.colors.iconColor, + marginLeft: '15px', + fontWeight: 400, ...displayFlex('center', 'center'), }; }); + +export const StyledModalContent = styled('div')({ + height: '534px', + padding: '8px 40px', + marginTop: '72px', + overflow: 'auto', + ...displayFlex('space-between', 'flex-start', 'column'), + flexWrap: 'wrap', +}); +export const StyledOperationWrapper = styled.div(() => { + return { + ...displayFlex('flex-end', 'center'), + }; +}); + +export const StyleWorkspaceAdd = styled.div(() => { + return { + width: '58px', + height: '58px', + borderRadius: '100%', + textAlign: 'center', + background: '#f4f5fa', + border: '1.5px dashed #f4f5fa', + lineHeight: '58px', + marginTop: '2px', + }; +}); +export const StyledModalHeader = styled('div')(({ theme }) => { + return { + width: '100%', + height: '72px', + position: 'absolute', + left: 0, + top: 0, + background: theme.colors.pageBackground, + borderRadius: '24px 24px 0 0', + padding: '0 40px', + ...displayFlex('space-between', 'center'), + }; +});