mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-25 14:28:51 +08:00
Merge branch 'master'
Conflicts: .vscode/settings.json package.json packages/data-center/package.json pnpm-lock.yaml
This commit is contained in:
@@ -7,7 +7,7 @@ export const NotfoundPage = () => {
|
||||
const router = useRouter();
|
||||
return (
|
||||
<PageContainer>
|
||||
<NotFoundTitle>
|
||||
<NotFoundTitle data-testid="notFound">
|
||||
{t('404 - Page Not Found')}
|
||||
<p>
|
||||
<Button
|
||||
@@ -15,7 +15,7 @@ export const NotfoundPage = () => {
|
||||
router.push('/workspace');
|
||||
}}
|
||||
>
|
||||
Back Home
|
||||
{t('Back Home')}
|
||||
</Button>
|
||||
</p>
|
||||
</NotFoundTitle>
|
||||
|
||||
@@ -91,8 +91,6 @@ export const ContactModal = ({
|
||||
<span>Alpha</span>
|
||||
</StyledModalHeaderLeft>
|
||||
<ModalCloseButton
|
||||
top={6}
|
||||
right={6}
|
||||
onClick={() => {
|
||||
onClose();
|
||||
}}
|
||||
|
||||
@@ -53,13 +53,12 @@ export const CreateWorkspaceModal = ({ open, onClose }: ModalProps) => {
|
||||
</Header>
|
||||
<Content>
|
||||
<ContentTitle>{t('New Workspace')}</ContentTitle>
|
||||
<p>
|
||||
Workspace is your virtual space to capture, create and plan as
|
||||
just one person or together as a team.
|
||||
</p>
|
||||
<p>{t('Workspace description')}</p>
|
||||
<Input
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder={'Set a Workspace name'}
|
||||
placeholder={t('Set a Workspace name')}
|
||||
maxLength={15}
|
||||
minLength={0}
|
||||
onChange={value => {
|
||||
setWorkspaceName(value);
|
||||
}}
|
||||
|
||||
@@ -29,8 +29,6 @@ export const DeleteModal = ({
|
||||
<ModalWrapper width={620} height={334}>
|
||||
<Header>
|
||||
<ModalCloseButton
|
||||
top={6}
|
||||
right={6}
|
||||
onClick={() => {
|
||||
onClose();
|
||||
}}
|
||||
|
||||
@@ -14,8 +14,8 @@ import {
|
||||
UndoIcon,
|
||||
RedoIcon,
|
||||
} from './Icons';
|
||||
import { MuiSlide } from '@/ui/mui';
|
||||
import { Tooltip } from '@/ui/tooltip';
|
||||
import Slide from '@mui/material/Slide';
|
||||
import useCurrentPageMeta from '@/hooks/use-current-page-meta';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import useHistoryUpdated from '@/hooks/use-history-update';
|
||||
@@ -127,7 +127,7 @@ export const EdgelessToolbar = () => {
|
||||
const { mode } = useCurrentPageMeta() || {};
|
||||
|
||||
return (
|
||||
<Slide
|
||||
<MuiSlide
|
||||
direction="right"
|
||||
in={mode === 'edgeless'}
|
||||
mountOnEnter
|
||||
@@ -155,7 +155,7 @@ export const EdgelessToolbar = () => {
|
||||
</StyledToolbarWrapper>
|
||||
<UndoRedo />
|
||||
</StyledEdgelessToolbar>
|
||||
</Slide>
|
||||
</MuiSlide>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useEffect, useRef } from 'react';
|
||||
import type { Page, Workspace } from '@blocksuite/store';
|
||||
import '@blocksuite/blocks';
|
||||
import { EditorContainer } from '@blocksuite/editor';
|
||||
import exampleMarkdown from '@/templates/Welcome-to-AFFiNE-Alpha-Downhill.md';
|
||||
import exampleMarkdown from '@/templates/Welcome-to-AFFiNE-Alpha-Downhills.md';
|
||||
import { styled } from '@/styles';
|
||||
|
||||
const StyledEditorContainer = styled('div')(() => {
|
||||
@@ -39,22 +39,21 @@ export const Editor = ({ page, workspace, setEditor }: Props) => {
|
||||
const title = metaTitle
|
||||
? metaTitle
|
||||
: isFirstPage
|
||||
? 'Welcome to AFFiNE Alpha "Downhill"'
|
||||
? 'Welcome to AFFiNE Alpha "Downhills"'
|
||||
: '';
|
||||
workspace?.setPageMeta(page.id, { title });
|
||||
|
||||
const pageId = page.addBlock({
|
||||
flavour: 'affine:page',
|
||||
title,
|
||||
});
|
||||
page.addBlock({ flavour: 'affine:surface' });
|
||||
const frameId = page.addBlock({ flavour: 'affine:frame' }, pageId);
|
||||
page.addBlock({ flavour: 'affine:frame' }, pageId);
|
||||
const pageBlockId = page.addBlockByFlavour('affine:page', { title });
|
||||
page.addBlockByFlavour('affine:surface', {}, null);
|
||||
// Add frame block inside page block
|
||||
const frameId = page.addBlockByFlavour('affine:frame', {}, pageBlockId);
|
||||
// Add paragraph block inside frame block
|
||||
// If this is a first page in workspace, init an introduction markdown
|
||||
if (isFirstPage) {
|
||||
editor.clipboard.importMarkdown(exampleMarkdown, `${frameId}`);
|
||||
editor.clipboard.importMarkdown(exampleMarkdown, frameId);
|
||||
workspace.setPageMeta(page.id, { title });
|
||||
page.resetHistory();
|
||||
} else {
|
||||
page.addBlockByFlavour('affine:paragraph', {}, frameId);
|
||||
}
|
||||
page.resetHistory();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import { styled } from '@/styles';
|
||||
import { Modal, ModalWrapper } from '@/ui/modal';
|
||||
import { Button, IconButton } from '@/ui/button';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { useState } from 'react';
|
||||
import router from 'next/router';
|
||||
import { toast } from '@/ui/toast';
|
||||
import { CloseIcon } from '@blocksuite/icons';
|
||||
interface EnableWorkspaceModalProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const EnableWorkspaceModal = ({
|
||||
open,
|
||||
onClose,
|
||||
}: EnableWorkspaceModalProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { user, dataCenter, login, currentWorkspace } = useAppState();
|
||||
const [loading, setLoading] = useState(false);
|
||||
return (
|
||||
<Modal open={open} onClose={onClose} data-testid="logout-modal">
|
||||
<ModalWrapper width={560} height={292}>
|
||||
<Header>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</Header>
|
||||
<Content>
|
||||
<ContentTitle>{t('Enable AFFiNE Cloud')}?</ContentTitle>
|
||||
<StyleTips>{t('Enable AFFiNE Cloud Description')}</StyleTips>
|
||||
{/* <StyleTips>{t('Retain local cached data')}</StyleTips> */}
|
||||
<div>
|
||||
<StyleButton
|
||||
shape="round"
|
||||
type="primary"
|
||||
loading={loading}
|
||||
onClick={async () => {
|
||||
setLoading(true);
|
||||
if (!user) {
|
||||
await login();
|
||||
}
|
||||
if (currentWorkspace) {
|
||||
const workspace = await dataCenter.enableWorkspaceCloud(
|
||||
currentWorkspace
|
||||
);
|
||||
workspace &&
|
||||
router.push(`/workspace/${workspace.id}/setting`);
|
||||
toast(t('Enabled success'));
|
||||
}
|
||||
}}
|
||||
>
|
||||
{user ? t('Enable') : t('Sign in and Enable')}
|
||||
</StyleButton>
|
||||
<StyleButton
|
||||
shape="round"
|
||||
onClick={() => {
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
{t('Not now')}
|
||||
</StyleButton>
|
||||
</div>
|
||||
</Content>
|
||||
</ModalWrapper>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const Header = styled('div')({
|
||||
height: '44px',
|
||||
display: 'flex',
|
||||
flexDirection: 'row-reverse',
|
||||
paddingRight: '10px',
|
||||
paddingTop: '10px',
|
||||
});
|
||||
|
||||
const Content = styled('div')({
|
||||
textAlign: 'center',
|
||||
});
|
||||
|
||||
const ContentTitle = styled('h1')({
|
||||
fontSize: '20px',
|
||||
lineHeight: '28px',
|
||||
fontWeight: 600,
|
||||
textAlign: 'center',
|
||||
});
|
||||
|
||||
const StyleTips = styled('div')(() => {
|
||||
return {
|
||||
userSelect: 'none',
|
||||
width: '400px',
|
||||
margin: 'auto',
|
||||
marginBottom: '32px',
|
||||
marginTop: '12px',
|
||||
};
|
||||
});
|
||||
|
||||
const StyleButton = styled(Button)(() => {
|
||||
return {
|
||||
width: '284px',
|
||||
display: 'block',
|
||||
margin: 'auto',
|
||||
marginTop: '16px',
|
||||
};
|
||||
});
|
||||
@@ -1,24 +1,28 @@
|
||||
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
|
||||
import { Button } from '@/ui/button';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useState } from 'react';
|
||||
import { EnableWorkspaceModal } from './EnableWorkspaceModal';
|
||||
|
||||
export const EnableWorkspaceButton = () => {
|
||||
const { t } = useTranslation();
|
||||
const { enableWorkspace } = useWorkspaceHelper();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [enableModalOpen, setEnableModalOpen] = useState(false);
|
||||
return (
|
||||
<Button
|
||||
type="primary"
|
||||
shape="circle"
|
||||
loading={loading}
|
||||
onClick={async () => {
|
||||
setLoading(true);
|
||||
await enableWorkspace();
|
||||
setLoading(false);
|
||||
}}
|
||||
>
|
||||
{t('Enable AFFiNE Cloud')}
|
||||
</Button>
|
||||
<>
|
||||
<Button
|
||||
type="light"
|
||||
shape="circle"
|
||||
onClick={async () => {
|
||||
setEnableModalOpen(true);
|
||||
}}
|
||||
>
|
||||
{t('Enable AFFiNE Cloud')}
|
||||
</Button>
|
||||
<EnableWorkspaceModal
|
||||
open={enableModalOpen}
|
||||
onClose={() => {
|
||||
setEnableModalOpen(false);
|
||||
}}
|
||||
></EnableWorkspaceModal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
StyledBrowserWarning,
|
||||
StyledCloseButton,
|
||||
} from './styles';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import { CloseIcon } from '@blocksuite/icons';
|
||||
import { useWarningMessage, shouldShowWarning } from './utils';
|
||||
import EditorOptionMenu from './header-right-items/EditorOptionMenu';
|
||||
import TrashButtonGroup from './header-right-items/TrashButtonGroup';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { PropsWithChildren, ReactNode } from 'react';
|
||||
import Header from './Header';
|
||||
import QuickSearchButton from './QuickSearchButton';
|
||||
import { StyledPageListTittleWrapper } from './styles';
|
||||
// import QuickSearchButton from './QuickSearchButton';
|
||||
|
||||
@@ -12,7 +13,7 @@ export const PageListHeader = ({ icon, children }: PageListHeaderProps) => {
|
||||
<StyledPageListTittleWrapper>
|
||||
{icon}
|
||||
{children}
|
||||
{/* <QuickSearchButton style={{ marginLeft: '5px' }} /> */}
|
||||
<QuickSearchButton />
|
||||
</StyledPageListTittleWrapper>
|
||||
</Header>
|
||||
);
|
||||
|
||||
@@ -1,15 +1,41 @@
|
||||
import { CloudUnsyncedIcon, CloudInsyncIcon } from '@blocksuite/icons';
|
||||
import { CloudUnsyncedIcon } from '@blocksuite/icons';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { IconButton } from '@/ui/button';
|
||||
|
||||
// Temporary solution to use this component, since the @blocksuite/icons has not been published yet
|
||||
const DefaultSyncIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M3 13.6493C3 16.6044 5.41766 19 8.4 19L16.5 19C18.9853 19 21 16.9839 21 14.4969C21 12.6503 19.8893 10.9449 18.3 10.25C18.1317 7.32251 15.684 5 12.6893 5C10.3514 5 8.34694 6.48637 7.5 8.5C4.8 8.9375 3 11.2001 3 13.6493Z"
|
||||
stroke="#888A9E"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M11.4571 9L9 16H10.4392L12.0021 11.1586L13.5657 16H15L12.5425 9H11.4571Z"
|
||||
fill="#888A9E"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
export const SyncUser = () => {
|
||||
const { triggerLoginModal } = useModal();
|
||||
const appState = useAppState();
|
||||
|
||||
return appState.user ? (
|
||||
<IconButton iconSize="middle" disabled>
|
||||
<CloudInsyncIcon />
|
||||
<DefaultSyncIcon />
|
||||
</IconButton>
|
||||
) : (
|
||||
<IconButton
|
||||
|
||||
@@ -103,7 +103,7 @@ export const StyledSearchArrowWrapper = styled.div(() => {
|
||||
|
||||
export const StyledPageListTittleWrapper = styled(StyledTitle)(({ theme }) => {
|
||||
return {
|
||||
fontSize: theme.font.sm,
|
||||
fontSize: theme.font.base,
|
||||
color: theme.colors.textColor,
|
||||
'>svg': {
|
||||
fontSize: '20px',
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
StyledTransformIcon,
|
||||
} from './style';
|
||||
import { CloseIcon, ContactIcon, HelpIcon, KeyboardIcon } from './Icons';
|
||||
import Grow from '@mui/material/Grow';
|
||||
import { MuiGrow } from '@/ui/mui';
|
||||
import { Tooltip } from '@/ui/tooltip';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
@@ -35,7 +35,7 @@ export const HelpIsland = ({
|
||||
setShowContent(false);
|
||||
}}
|
||||
>
|
||||
<Grow in={showContent}>
|
||||
<MuiGrow in={showContent}>
|
||||
<StyledIslandWrapper>
|
||||
{showList.includes('contact') && (
|
||||
<Tooltip content={t('Contact Us')} placement="left-end">
|
||||
@@ -66,7 +66,7 @@ export const HelpIsland = ({
|
||||
</Tooltip>
|
||||
)}
|
||||
</StyledIslandWrapper>
|
||||
</Grow>
|
||||
</MuiGrow>
|
||||
|
||||
<div style={{ position: 'relative' }}>
|
||||
<StyledIconWrapper
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal';
|
||||
import { StyledButtonWrapper, StyledTitle } from './styles';
|
||||
import { Button } from '@/ui/button';
|
||||
import { Wrapper, Content } from '@/ui/layout';
|
||||
import { Content, FlexWrapper } from '@/ui/layout';
|
||||
import Loading from '@/components/loading';
|
||||
import { usePageHelper } from '@/hooks/use-page-helper';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
@@ -116,7 +116,7 @@ export const ImportModal = ({ open, onClose }: ImportModalProps) => {
|
||||
)}
|
||||
|
||||
{status === 'importing' && (
|
||||
<Wrapper
|
||||
<FlexWrapper
|
||||
wrap={true}
|
||||
justifyContent="center"
|
||||
style={{ marginTop: 22, paddingBottom: '32px' }}
|
||||
@@ -126,7 +126,7 @@ export const ImportModal = ({ open, onClose }: ImportModalProps) => {
|
||||
OOOOPS! Sorry forgot to remind you that we are working on the
|
||||
import function
|
||||
</Content>
|
||||
</Wrapper>
|
||||
</FlexWrapper>
|
||||
)}
|
||||
</ModalWrapper>
|
||||
</Modal>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
export const GoogleIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="25"
|
||||
height="24"
|
||||
viewBox="0 0 25 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M22.3055 10.0415H21.5V10H12.5V14H18.1515C17.327 16.3285 15.1115 18 12.5 18C9.1865 18 6.5 15.3135 6.5 12C6.5 8.6865 9.1865 6 12.5 6C14.0295 6 15.421 6.577 16.4805 7.5195L19.309 4.691C17.523 3.0265 15.134 2 12.5 2C6.9775 2 2.5 6.4775 2.5 12C2.5 17.5225 6.9775 22 12.5 22C18.0225 22 22.5 17.5225 22.5 12C22.5 11.3295 22.431 10.675 22.3055 10.0415Z"
|
||||
fill="#FFC107"
|
||||
/>
|
||||
<path
|
||||
d="M3.65234 7.3455L6.93784 9.755C7.82684 7.554 9.97984 6 12.4993 6C14.0288 6 15.4203 6.577 16.4798 7.5195L19.3083 4.691C17.5223 3.0265 15.1333 2 12.4993 2C8.65834 2 5.32734 4.1685 3.65234 7.3455Z"
|
||||
fill="#FF3D00"
|
||||
/>
|
||||
<path
|
||||
d="M12.5002 22.0003C15.0832 22.0003 17.4302 21.0118 19.2047 19.4043L16.1097 16.7853C15.0719 17.5745 13.8039 18.0014 12.5002 18.0003C9.89916 18.0003 7.69066 16.3418 6.85866 14.0273L3.59766 16.5398C5.25266 19.7783 8.61366 22.0003 12.5002 22.0003Z"
|
||||
fill="#4CAF50"
|
||||
/>
|
||||
<path
|
||||
d="M22.3055 10.0415H21.5V10H12.5V14H18.1515C17.7571 15.1082 17.0467 16.0766 16.108 16.7855L16.1095 16.7845L19.2045 19.4035C18.9855 19.6025 22.5 17 22.5 12C22.5 11.3295 22.431 10.675 22.3055 10.0415Z"
|
||||
fill="#1976D2"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -1,40 +0,0 @@
|
||||
import { CloudUnsyncedIcon } from '@blocksuite/icons';
|
||||
import { styled } from '@/styles';
|
||||
import GoogleSvg from './google.svg';
|
||||
|
||||
export const GoogleIcon = () => {
|
||||
return (
|
||||
<GoogleIconWrapper>
|
||||
<picture>
|
||||
<img src={GoogleSvg.src} alt="Google" />
|
||||
</picture>
|
||||
</GoogleIconWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
const GoogleIconWrapper = styled('div')(({ theme }) => ({
|
||||
background: theme.colors.pageBackground,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}));
|
||||
|
||||
export const StayLogOutIcon = () => {
|
||||
return (
|
||||
<StayLogOutWrapper>
|
||||
<CloudUnsyncedIcon />
|
||||
</StayLogOutWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
const StayLogOutWrapper = styled('div')(({ theme }) => {
|
||||
return {
|
||||
width: '48px',
|
||||
height: '48px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontSize: '24px',
|
||||
background: theme.colors.hoverBackground,
|
||||
};
|
||||
});
|
||||
@@ -1,99 +0,0 @@
|
||||
import { styled } from '@/styles';
|
||||
import { Button } from '@/ui/button';
|
||||
import { GoogleIcon, StayLogOutIcon } from './Icons';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
export const GoogleLoginButton = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<StyledGoogleButton>
|
||||
<ButtonWrapper>
|
||||
<IconWrapper>
|
||||
<GoogleIcon />
|
||||
</IconWrapper>
|
||||
<TextWrapper>{t('Continue with Google')}</TextWrapper>
|
||||
</ButtonWrapper>
|
||||
</StyledGoogleButton>
|
||||
);
|
||||
};
|
||||
|
||||
export const StayLogOutButton = () => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<StyledStayLogOutButton>
|
||||
<ButtonWrapper>
|
||||
<IconWrapper>
|
||||
<StayLogOutIcon />
|
||||
</IconWrapper>
|
||||
<TextWrapper>
|
||||
<Title>{t('Stay logged out')}</Title>
|
||||
<Description>{t('All changes are saved locally')}</Description>
|
||||
</TextWrapper>
|
||||
</ButtonWrapper>
|
||||
</StyledStayLogOutButton>
|
||||
);
|
||||
};
|
||||
|
||||
const StyledGoogleButton = styled('div')(({ theme }) => {
|
||||
return {
|
||||
width: '284px',
|
||||
height: '40px',
|
||||
marginTop: '30px',
|
||||
fontSize: '16px',
|
||||
cursor: 'pointer',
|
||||
borderRadius: '40px',
|
||||
border: `1px solid ${theme.colors.iconColor}`,
|
||||
overflow: 'hidden',
|
||||
':hover': {
|
||||
border: `1px solid ${theme.colors.primaryColor}`,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const StyledStayLogOutButton = styled(Button)(() => {
|
||||
return {
|
||||
width: '361px',
|
||||
height: '56px',
|
||||
padding: '4px',
|
||||
':hover': {
|
||||
borderColor: '#6880FF',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const ButtonWrapper = styled('div')({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
width: '100%',
|
||||
});
|
||||
|
||||
const IconWrapper = styled('div')({
|
||||
flex: '0 48px',
|
||||
borderRadius: '5px',
|
||||
overflow: 'hidden',
|
||||
marginRight: '12px',
|
||||
marginTop: '8px',
|
||||
});
|
||||
|
||||
const TextWrapper = styled('div')({
|
||||
flex: 1,
|
||||
textAlign: 'left',
|
||||
height: '40px',
|
||||
lineHeight: '40px',
|
||||
});
|
||||
|
||||
const Title = styled('h1')(() => {
|
||||
return {
|
||||
fontSize: '18px',
|
||||
lineHeight: '26px',
|
||||
fontWeight: 500,
|
||||
};
|
||||
});
|
||||
|
||||
const Description = styled('p')(() => {
|
||||
return {
|
||||
fontSize: '16px',
|
||||
lineHeight: '22px',
|
||||
fontWeight: 400,
|
||||
};
|
||||
});
|
||||
@@ -1,6 +0,0 @@
|
||||
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M22.3055 10.0415H21.5V10H12.5V14H18.1515C17.327 16.3285 15.1115 18 12.5 18C9.1865 18 6.5 15.3135 6.5 12C6.5 8.6865 9.1865 6 12.5 6C14.0295 6 15.421 6.577 16.4805 7.5195L19.309 4.691C17.523 3.0265 15.134 2 12.5 2C6.9775 2 2.5 6.4775 2.5 12C2.5 17.5225 6.9775 22 12.5 22C18.0225 22 22.5 17.5225 22.5 12C22.5 11.3295 22.431 10.675 22.3055 10.0415Z" fill="#FFC107"/>
|
||||
<path d="M3.65234 7.3455L6.93784 9.755C7.82684 7.554 9.97984 6 12.4993 6C14.0288 6 15.4203 6.577 16.4798 7.5195L19.3083 4.691C17.5223 3.0265 15.1333 2 12.4993 2C8.65834 2 5.32734 4.1685 3.65234 7.3455Z" fill="#FF3D00"/>
|
||||
<path d="M12.5002 22.0003C15.0832 22.0003 17.4302 21.0118 19.2047 19.4043L16.1097 16.7853C15.0719 17.5745 13.8039 18.0014 12.5002 18.0003C9.89916 18.0003 7.69066 16.3418 6.85866 14.0273L3.59766 16.5398C5.25266 19.7783 8.61366 22.0003 12.5002 22.0003Z" fill="#4CAF50"/>
|
||||
<path d="M22.3055 10.0415H21.5V10H12.5V14H18.1515C17.7571 15.1082 17.0467 16.0766 16.108 16.7855L16.1095 16.7845L19.2045 19.4035C18.9855 19.6025 22.5 17 22.5 12C22.5 11.3295 22.431 10.675 22.3055 10.0415Z" fill="#1976D2"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -1,7 +1,9 @@
|
||||
import { styled } from '@/styles';
|
||||
import { positionAbsolute, styled } from '@/styles';
|
||||
import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal';
|
||||
import { GoogleLoginButton } from './LoginOptionButton';
|
||||
import { Button } from '@/ui/button';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { GoogleIcon } from './GoogleIcon';
|
||||
interface LoginModalProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
@@ -9,38 +11,44 @@ interface LoginModalProps {
|
||||
|
||||
export const LoginModal = ({ open, onClose }: LoginModalProps) => {
|
||||
const { login } = useAppState();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Modal open={open} onClose={onClose} data-testid="login-modal">
|
||||
<ModalWrapper width={560} height={292}>
|
||||
<Header>
|
||||
<ModalCloseButton
|
||||
top={6}
|
||||
right={6}
|
||||
onClick={() => {
|
||||
onClose();
|
||||
}}
|
||||
/>
|
||||
</Header>
|
||||
<ModalWrapper width={560} height={292} style={{ paddingTop: '44px' }}>
|
||||
<ModalCloseButton
|
||||
onClick={() => {
|
||||
onClose();
|
||||
}}
|
||||
/>
|
||||
<Content>
|
||||
<ContentTitle>{'Sign in'}</ContentTitle>
|
||||
<SignDes>Set up an AFFINE account to sync data</SignDes>
|
||||
<span
|
||||
<ContentTitle>{t('Sign in')}</ContentTitle>
|
||||
<SignDes>{t('Set up an AFFiNE account to sync data')}</SignDes>
|
||||
<StyledLoginButton
|
||||
shape="round"
|
||||
onClick={async () => {
|
||||
await login();
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
<GoogleLoginButton />
|
||||
</span>
|
||||
<GoogleIcon />
|
||||
{t('Continue with Google')}
|
||||
</StyledLoginButton>
|
||||
</Content>
|
||||
</ModalWrapper>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const Header = styled('div')({
|
||||
position: 'relative',
|
||||
height: '44px',
|
||||
const StyledLoginButton = styled(Button)(() => {
|
||||
return {
|
||||
width: '284px',
|
||||
marginTop: '30px',
|
||||
position: 'relative',
|
||||
svg: {
|
||||
...positionAbsolute({ left: '18px', top: '0', bottom: '0' }),
|
||||
margin: 'auto',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const Content = styled('div')({
|
||||
|
||||
@@ -8,7 +8,7 @@ export const Check = () => {
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clip-path="url(#clip0_9266_16831)">
|
||||
<g clipPath="url(#clip0_9266_16831)">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
|
||||
@@ -3,28 +3,34 @@ import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal';
|
||||
import { Button } from '@/ui/button';
|
||||
import { Check, UnCheck } from './icon';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
interface LoginModalProps {
|
||||
open: boolean;
|
||||
onClose: (wait: boolean) => void;
|
||||
}
|
||||
|
||||
export const LogoutModal = ({ open, onClose }: LoginModalProps) => {
|
||||
const [localCache, setLocalCache] = useState(false);
|
||||
const [localCache, setLocalCache] = useState(true);
|
||||
const { blobDataSynced } = useAppState();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Modal open={open} onClose={onClose} data-testid="logout-modal">
|
||||
<ModalWrapper width={560} height={292}>
|
||||
<Header>
|
||||
<ModalCloseButton
|
||||
top={6}
|
||||
right={6}
|
||||
onClick={() => {
|
||||
onClose(true);
|
||||
}}
|
||||
/>
|
||||
</Header>
|
||||
<Content>
|
||||
<ContentTitle>{'Sign out?'}</ContentTitle>
|
||||
<SignDes>Set up an AFFINE account to sync data</SignDes>
|
||||
<ContentTitle>{t('Sign out')}?</ContentTitle>
|
||||
<SignDes>
|
||||
{blobDataSynced
|
||||
? t('Set up an AFFiNE account to sync data')
|
||||
: 'All data has been stored in the cloud'}
|
||||
</SignDes>
|
||||
<StyleTips>
|
||||
{localCache ? (
|
||||
<StyleCheck
|
||||
@@ -43,28 +49,51 @@ export const LogoutModal = ({ open, onClose }: LoginModalProps) => {
|
||||
<UnCheck></UnCheck>
|
||||
</StyleCheck>
|
||||
)}
|
||||
Retain local cached data
|
||||
{t('Retain local cached data')}
|
||||
</StyleTips>
|
||||
<div>
|
||||
<Button
|
||||
style={{ marginRight: '16px' }}
|
||||
shape="round"
|
||||
onClick={() => {
|
||||
onClose(true);
|
||||
}}
|
||||
>
|
||||
Wait for Sync
|
||||
</Button>
|
||||
<Button
|
||||
type="danger"
|
||||
shape="round"
|
||||
onClick={() => {
|
||||
onClose(false);
|
||||
}}
|
||||
>
|
||||
Force Sign Out
|
||||
</Button>
|
||||
</div>
|
||||
{blobDataSynced ? (
|
||||
<div>
|
||||
<Button
|
||||
type="danger"
|
||||
shape="round"
|
||||
style={{ marginRight: '16px' }}
|
||||
onClick={async () => {
|
||||
onClose(false);
|
||||
}}
|
||||
>
|
||||
{t('Force Sign Out')}
|
||||
</Button>
|
||||
<Button
|
||||
shape="round"
|
||||
onClick={() => {
|
||||
onClose(true);
|
||||
}}
|
||||
>
|
||||
{t('Wait for Sync')}
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<Button
|
||||
type="primary"
|
||||
style={{ marginRight: '16px' }}
|
||||
shape="round"
|
||||
onClick={() => {
|
||||
onClose(true);
|
||||
}}
|
||||
>
|
||||
{t('Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
shape="round"
|
||||
onClick={() => {
|
||||
onClose(false);
|
||||
}}
|
||||
>
|
||||
{t('Sign out')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Content>
|
||||
</ModalWrapper>
|
||||
</Modal>
|
||||
|
||||
@@ -14,7 +14,6 @@ export const PageListEmpty = (props: { listType?: string }) => {
|
||||
{listType === 'all' && <p>{t('emptyAllPages')}</p>}
|
||||
{listType === 'favorite' && <p>{t('emptyFavourite')}</p>}
|
||||
{listType === 'trash' && <p>{t('emptyTrash')}</p>}
|
||||
<p>{t('still designed')}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useConfirm } from '@/providers/ConfirmProvider';
|
||||
import { PageMeta } from '@/providers/app-state-provider';
|
||||
import { Menu, MenuItem } from '@/ui/menu';
|
||||
import { Wrapper } from '@/ui/layout';
|
||||
import { FlexWrapper } from '@/ui/layout';
|
||||
import { IconButton } from '@/ui/button';
|
||||
import {
|
||||
MoreVerticalIcon,
|
||||
@@ -63,13 +63,13 @@ export const OperationCell = ({ pageMeta }: { pageMeta: PageMeta }) => {
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<Wrapper alignItems="center" justifyContent="center">
|
||||
<FlexWrapper alignItems="center" justifyContent="center">
|
||||
<Menu content={OperationMenu} placement="bottom-end" disablePortal={true}>
|
||||
<IconButton darker={true}>
|
||||
<MoreVerticalIcon />
|
||||
</IconButton>
|
||||
</Menu>
|
||||
</Wrapper>
|
||||
</FlexWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -80,7 +80,7 @@ export const TrashOperationCell = ({ pageMeta }: { pageMeta: PageMeta }) => {
|
||||
const { confirm } = useConfirm();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Wrapper>
|
||||
<FlexWrapper>
|
||||
<IconButton
|
||||
darker={true}
|
||||
style={{ marginRight: '12px' }}
|
||||
@@ -108,6 +108,6 @@ export const TrashOperationCell = ({ pageMeta }: { pageMeta: PageMeta }) => {
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</Wrapper>
|
||||
</FlexWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,26 +1,23 @@
|
||||
import React from 'react';
|
||||
import { AddIcon } from '@blocksuite/icons';
|
||||
import { StyledModalFooterContent } from './style';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
import { Command } from 'cmdk';
|
||||
import { usePageHelper } from '@/hooks/use-page-helper';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
export const Footer = (props: { query: string }) => {
|
||||
const { triggerQuickSearchModal } = useModal();
|
||||
export const Footer = (props: { query: string; onClose: () => void }) => {
|
||||
const { openPage, createPage } = usePageHelper();
|
||||
const { t } = useTranslation();
|
||||
const query = props.query;
|
||||
const { query, onClose } = props;
|
||||
|
||||
return (
|
||||
<Command.Item
|
||||
data-testid="quickSearch-addNewPage"
|
||||
onSelect={async () => {
|
||||
onClose();
|
||||
const pageId = await createPage({ title: query });
|
||||
if (pageId) {
|
||||
openPage(pageId);
|
||||
}
|
||||
|
||||
triggerQuickSearchModal();
|
||||
}}
|
||||
>
|
||||
<StyledModalFooterContent>
|
||||
|
||||
@@ -8,32 +8,41 @@ import React, {
|
||||
import { SearchIcon } from '@blocksuite/icons';
|
||||
import { StyledInputContent, StyledLabel } from './style';
|
||||
import { Command } from 'cmdk';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
export const Input = (props: {
|
||||
open: boolean;
|
||||
query: string;
|
||||
setQuery: Dispatch<SetStateAction<string>>;
|
||||
setLoading: Dispatch<SetStateAction<boolean>>;
|
||||
isPublic: boolean;
|
||||
publishWorkspaceName: string | undefined;
|
||||
}) => {
|
||||
const { open, query, setQuery, setLoading, isPublic, publishWorkspaceName } =
|
||||
props;
|
||||
const [isComposition, setIsComposition] = useState(false);
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const { currentWorkspace } = useAppState();
|
||||
const { t } = useTranslation();
|
||||
useEffect(() => {
|
||||
inputRef.current?.addEventListener(
|
||||
'blur',
|
||||
() => {
|
||||
inputRef.current?.focus();
|
||||
},
|
||||
true
|
||||
);
|
||||
return inputRef.current?.focus();
|
||||
}, [inputRef]);
|
||||
if (open) {
|
||||
const inputElement = inputRef.current;
|
||||
return inputElement?.focus();
|
||||
}
|
||||
}, [open]);
|
||||
useEffect(() => {
|
||||
return setInputValue(props.query);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
const inputElement = inputRef.current;
|
||||
if (!open) {
|
||||
return;
|
||||
}
|
||||
const handleFocus = () => {
|
||||
inputElement?.focus();
|
||||
};
|
||||
inputElement?.addEventListener('blur', handleFocus, true);
|
||||
return () => inputElement?.removeEventListener('blur', handleFocus, true);
|
||||
}, [inputRef, open]);
|
||||
useEffect(() => {
|
||||
setInputValue(query);
|
||||
}, [query]);
|
||||
return (
|
||||
<StyledInputContent>
|
||||
<StyledLabel htmlFor=":r5:">
|
||||
@@ -46,18 +55,18 @@ export const Input = (props: {
|
||||
setIsComposition(true);
|
||||
}}
|
||||
onCompositionEnd={e => {
|
||||
props.setQuery(e.data);
|
||||
setQuery(e.data);
|
||||
setIsComposition(false);
|
||||
if (!props.query) {
|
||||
props.setLoading(true);
|
||||
if (!query) {
|
||||
setLoading(true);
|
||||
}
|
||||
}}
|
||||
onValueChange={str => {
|
||||
setInputValue(str);
|
||||
if (!isComposition) {
|
||||
props.setQuery(str);
|
||||
if (!props.query) {
|
||||
props.setLoading(true);
|
||||
setQuery(str);
|
||||
if (!query) {
|
||||
setLoading(true);
|
||||
}
|
||||
}
|
||||
}}
|
||||
@@ -78,9 +87,9 @@ export const Input = (props: {
|
||||
}
|
||||
}}
|
||||
placeholder={
|
||||
currentWorkspace?.isPublish
|
||||
isPublic
|
||||
? t('Quick search placeholder2', {
|
||||
workspace: currentWorkspace?.blocksuiteWorkspace?.meta.name,
|
||||
workspace: publishWorkspaceName,
|
||||
})
|
||||
: t('Quick search placeholder')
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
import { Command } from 'cmdk';
|
||||
import { StyledListItem, StyledNotFound } from './style';
|
||||
import { PaperIcon, EdgelessIcon } from '@blocksuite/icons';
|
||||
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
|
||||
import { useAppState, PageMeta } from '@/providers/app-state-provider';
|
||||
import { useRouter } from 'next/router';
|
||||
import { NoResultSVG } from './NoResultSVG';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import usePageHelper from '@/hooks/use-page-helper';
|
||||
import { Workspace } from '@blocksuite/store';
|
||||
|
||||
export const PublishedResults = (props: {
|
||||
query: string;
|
||||
loading: boolean;
|
||||
setLoading: Dispatch<SetStateAction<boolean>>;
|
||||
setPublishWorkspaceName: Dispatch<SetStateAction<string>>;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
const [workspace, setWorkspace] = useState<Workspace>();
|
||||
const { query, loading, setLoading, onClose, setPublishWorkspaceName } =
|
||||
props;
|
||||
const { search } = usePageHelper();
|
||||
const [results, setResults] = useState(new Map<string, string | undefined>());
|
||||
const { dataCenter } = useAppState();
|
||||
const router = useRouter();
|
||||
const [pageList, setPageList] = useState<PageMeta[]>([]);
|
||||
useEffect(() => {
|
||||
dataCenter
|
||||
.loadPublicWorkspace(router.query.workspaceId as string)
|
||||
.then(data => {
|
||||
setPageList(data.blocksuiteWorkspace?.meta.pageMetas as PageMeta[]);
|
||||
if (data.blocksuiteWorkspace) {
|
||||
setWorkspace(data.blocksuiteWorkspace);
|
||||
setPublishWorkspaceName(data.blocksuiteWorkspace.meta.name);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
router.push('/404');
|
||||
});
|
||||
}, [router, dataCenter, setPublishWorkspaceName]);
|
||||
const { t } = useTranslation();
|
||||
useEffect(() => {
|
||||
setResults(search(query, workspace));
|
||||
setLoading(false);
|
||||
//Save the Map<BlockId, PageId> obtained from the search as state
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [query, setResults, setLoading]);
|
||||
const pageIds = [...results.values()];
|
||||
const resultsPageMeta = pageList.filter(
|
||||
page => pageIds.indexOf(page.id) > -1 && !page.trash
|
||||
);
|
||||
|
||||
return loading ? null : (
|
||||
<>
|
||||
{query ? (
|
||||
resultsPageMeta.length ? (
|
||||
<Command.Group
|
||||
heading={t('Find results', { number: resultsPageMeta.length })}
|
||||
>
|
||||
{resultsPageMeta.map(result => {
|
||||
return (
|
||||
<Command.Item
|
||||
key={result.id}
|
||||
onSelect={() => {
|
||||
router.push(
|
||||
`/public-workspace/${router.query.workspaceId}/${result.id}`
|
||||
);
|
||||
onClose();
|
||||
}}
|
||||
value={result.id}
|
||||
>
|
||||
<StyledListItem>
|
||||
{result.mode === 'edgeless' ? (
|
||||
<EdgelessIcon />
|
||||
) : (
|
||||
<PaperIcon />
|
||||
)}
|
||||
<span>{result.title}</span>
|
||||
</StyledListItem>
|
||||
</Command.Item>
|
||||
);
|
||||
})}
|
||||
</Command.Group>
|
||||
) : (
|
||||
<StyledNotFound>
|
||||
<span>{t('Find 0 result')}</span>
|
||||
<NoResultSVG />
|
||||
</StyledNotFound>
|
||||
)
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Command } from 'cmdk';
|
||||
import { StyledListItem, StyledNotFound } from './style';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
import { PaperIcon, EdgelessIcon } from '@blocksuite/icons';
|
||||
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
@@ -12,14 +11,11 @@ import usePageHelper from '@/hooks/use-page-helper';
|
||||
export const Results = (props: {
|
||||
query: string;
|
||||
loading: boolean;
|
||||
onClose: () => void;
|
||||
setLoading: Dispatch<SetStateAction<boolean>>;
|
||||
setShowCreatePage: Dispatch<SetStateAction<boolean>>;
|
||||
}) => {
|
||||
const query = props.query;
|
||||
const loading = props.loading;
|
||||
const setLoading = props.setLoading;
|
||||
const setShowCreatePage = props.setShowCreatePage;
|
||||
const { triggerQuickSearchModal } = useModal();
|
||||
const { query, loading, setLoading, setShowCreatePage, onClose } = props;
|
||||
const { openPage } = usePageHelper();
|
||||
const router = useRouter();
|
||||
const { currentWorkspace, pageList } = useAppState();
|
||||
@@ -55,8 +51,8 @@ export const Results = (props: {
|
||||
<Command.Item
|
||||
key={result.id}
|
||||
onSelect={() => {
|
||||
onClose();
|
||||
openPage(result.id);
|
||||
triggerQuickSearchModal();
|
||||
}}
|
||||
value={result.id}
|
||||
>
|
||||
@@ -86,8 +82,8 @@ export const Results = (props: {
|
||||
key={link.title}
|
||||
value={link.title}
|
||||
onSelect={() => {
|
||||
onClose();
|
||||
router.push(link.href);
|
||||
triggerQuickSearchModal();
|
||||
}}
|
||||
>
|
||||
<StyledListItem>
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { FC, SVGProps } from 'react';
|
||||
import { AllPagesIcon, FavouritesIcon, TrashIcon } from '@blocksuite/icons';
|
||||
import {
|
||||
AllPagesIcon,
|
||||
FavouritesIcon,
|
||||
TrashIcon,
|
||||
SettingsIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
|
||||
export const useSwitchToConfig = (
|
||||
@@ -23,6 +28,13 @@ export const useSwitchToConfig = (
|
||||
: '',
|
||||
icon: FavouritesIcon,
|
||||
},
|
||||
{
|
||||
title: t('Settings'),
|
||||
href: currentWorkspaceId
|
||||
? `/workspace/${currentWorkspaceId}/setting`
|
||||
: '',
|
||||
icon: SettingsIcon,
|
||||
},
|
||||
{
|
||||
title: t('Trash'),
|
||||
href: currentWorkspaceId ? `/workspace/${currentWorkspaceId}/trash` : '',
|
||||
|
||||
@@ -13,7 +13,8 @@ import { Command } from 'cmdk';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
import { getUaHelper } from '@/utils';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { useRouter } from 'next/router';
|
||||
import { PublishedResults } from './PublishedResults';
|
||||
type TransitionsModalProps = {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
@@ -22,18 +23,30 @@ const isMac = () => {
|
||||
return getUaHelper().isMacOs;
|
||||
};
|
||||
export const QuickSearch = ({ open, onClose }: TransitionsModalProps) => {
|
||||
const { currentWorkspace } = useAppState();
|
||||
|
||||
const router = useRouter();
|
||||
const [query, setQuery] = useState('');
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [isPublic, setIsPublic] = useState(false);
|
||||
const [publishWorkspaceName, setPublishWorkspaceName] = useState('');
|
||||
const [showCreatePage, setShowCreatePage] = useState(true);
|
||||
const { triggerQuickSearchModal } = useModal();
|
||||
|
||||
const isPublicAndNoQuery = () => {
|
||||
return isPublic && query.length === 0;
|
||||
};
|
||||
const handleClose = () => {
|
||||
setQuery('');
|
||||
onClose();
|
||||
};
|
||||
// Add ‘⌘+K’ shortcut keys as switches
|
||||
useEffect(() => {
|
||||
if (router.pathname.startsWith('/404')) {
|
||||
triggerQuickSearchModal(false);
|
||||
return;
|
||||
}
|
||||
const down = (e: KeyboardEvent) => {
|
||||
if ((e.key === 'k' && e.metaKey) || (e.key === 'k' && e.ctrlKey)) {
|
||||
const selection = window.getSelection();
|
||||
setQuery('');
|
||||
if (selection?.toString()) {
|
||||
triggerQuickSearchModal(false);
|
||||
return;
|
||||
@@ -43,18 +56,23 @@ export const QuickSearch = ({ open, onClose }: TransitionsModalProps) => {
|
||||
}
|
||||
}
|
||||
};
|
||||
if (!open) {
|
||||
setQuery('');
|
||||
}
|
||||
document.addEventListener('keydown', down, { capture: true });
|
||||
return () =>
|
||||
document.removeEventListener('keydown', down, { capture: true });
|
||||
}, [open, triggerQuickSearchModal]);
|
||||
}, [open, router.pathname, triggerQuickSearchModal]);
|
||||
|
||||
useEffect(() => {
|
||||
if (router.pathname.startsWith('/public-workspace')) {
|
||||
return setIsPublic(true);
|
||||
} else {
|
||||
return setIsPublic(false);
|
||||
}
|
||||
}, [router]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
onClose={handleClose}
|
||||
wrapperPosition={['top', 'center']}
|
||||
data-testid="quickSearch"
|
||||
>
|
||||
@@ -62,7 +80,7 @@ export const QuickSearch = ({ open, onClose }: TransitionsModalProps) => {
|
||||
width={620}
|
||||
style={{
|
||||
maxHeight: '80vh',
|
||||
minHeight: '350px',
|
||||
minHeight: isPublicAndNoQuery() ? '72px' : '350px',
|
||||
top: '12vh',
|
||||
}}
|
||||
>
|
||||
@@ -81,28 +99,51 @@ export const QuickSearch = ({ open, onClose }: TransitionsModalProps) => {
|
||||
}}
|
||||
>
|
||||
<StyledModalHeader>
|
||||
<Input query={query} setQuery={setQuery} setLoading={setLoading} />
|
||||
<Input
|
||||
open={open}
|
||||
query={query}
|
||||
setQuery={setQuery}
|
||||
setLoading={setLoading}
|
||||
isPublic={isPublic}
|
||||
publishWorkspaceName={publishWorkspaceName}
|
||||
/>
|
||||
<StyledShortcut>{isMac() ? '⌘ + K' : 'Ctrl + K'}</StyledShortcut>
|
||||
</StyledModalHeader>
|
||||
<StyledModalDivider />
|
||||
<StyledModalDivider
|
||||
style={{ display: isPublicAndNoQuery() ? 'none' : '' }}
|
||||
/>
|
||||
<Command.List>
|
||||
<StyledContent>
|
||||
<Results
|
||||
query={query}
|
||||
loading={loading}
|
||||
setLoading={setLoading}
|
||||
setShowCreatePage={setShowCreatePage}
|
||||
/>
|
||||
<StyledContent
|
||||
style={{ display: isPublicAndNoQuery() ? 'none' : '' }}
|
||||
>
|
||||
{!isPublic ? (
|
||||
<Results
|
||||
query={query}
|
||||
loading={loading}
|
||||
setLoading={setLoading}
|
||||
onClose={handleClose}
|
||||
setShowCreatePage={setShowCreatePage}
|
||||
/>
|
||||
) : (
|
||||
<PublishedResults
|
||||
query={query}
|
||||
loading={loading}
|
||||
setLoading={setLoading}
|
||||
onClose={handleClose}
|
||||
setPublishWorkspaceName={setPublishWorkspaceName}
|
||||
data-testid="publishedSearchResults"
|
||||
/>
|
||||
)}
|
||||
</StyledContent>
|
||||
{currentWorkspace?.published ? (
|
||||
<></>
|
||||
) : showCreatePage ? (
|
||||
<>
|
||||
<StyledModalDivider />
|
||||
<StyledModalFooter>
|
||||
<Footer query={query} />
|
||||
</StyledModalFooter>
|
||||
</>
|
||||
{!isPublic ? (
|
||||
showCreatePage ? (
|
||||
<>
|
||||
<StyledModalDivider />
|
||||
<StyledModalFooter>
|
||||
<Footer query={query} onClose={handleClose} />
|
||||
</StyledModalFooter>
|
||||
</>
|
||||
) : null
|
||||
) : null}
|
||||
</Command.List>
|
||||
</Command>
|
||||
|
||||
@@ -9,6 +9,7 @@ export const StyledContent = styled('div')(({ theme }) => {
|
||||
marginBottom: '10px',
|
||||
...displayFlex('center', 'flex-start'),
|
||||
color: theme.colors.popoverColor,
|
||||
transition: 'all 0.15s',
|
||||
letterSpacing: '0.06em',
|
||||
'[cmdk-group-heading]': {
|
||||
margin: '5px 16px',
|
||||
@@ -16,7 +17,6 @@ export const StyledContent = styled('div')(({ theme }) => {
|
||||
fontWeight: '500',
|
||||
},
|
||||
'[aria-selected="true"]': {
|
||||
transition: 'background .15s, color .15s',
|
||||
borderRadius: '5px',
|
||||
color: theme.colors.primaryColor,
|
||||
backgroundColor: theme.colors.hoverBackground,
|
||||
@@ -103,6 +103,7 @@ export const StyledModalDivider = styled('div')(({ theme }) => {
|
||||
margin: '6px 16px 6.5px 16px',
|
||||
position: 'relative',
|
||||
borderTop: `0.5px solid ${theme.colors.placeHolderColor}`,
|
||||
transition: 'all 0.15s',
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
useWindowsKeyboardShortcuts,
|
||||
useWinMarkdownShortcuts,
|
||||
} from '@/components/shortcuts-modal/config';
|
||||
import Slide from '@mui/material/Slide';
|
||||
import { MuiSlide } from '@/ui/mui';
|
||||
import { ModalCloseButton } from '@/ui/modal';
|
||||
import { getUaHelper } from '@/utils';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
@@ -40,7 +40,7 @@ export const ShortcutsModal = ({ open, onClose }: ModalProps) => {
|
||||
: windowsKeyboardShortcuts;
|
||||
|
||||
return createPortal(
|
||||
<Slide direction="left" in={open} mountOnEnter unmountOnExit>
|
||||
<MuiSlide direction="left" in={open} mountOnEnter unmountOnExit>
|
||||
<StyledShortcutsModal data-testid="shortcuts-modal">
|
||||
<>
|
||||
<StyledModalHeader>
|
||||
@@ -81,7 +81,7 @@ export const ShortcutsModal = ({ open, onClose }: ModalProps) => {
|
||||
})}
|
||||
</>
|
||||
</StyledShortcutsModal>
|
||||
</Slide>,
|
||||
</MuiSlide>,
|
||||
document.body
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useState, useEffect } from 'react';
|
||||
import type { WorkspaceUnit } from '@affine/datacenter';
|
||||
import { WorkspaceAvatar as Avatar } from './Avatar';
|
||||
|
||||
const useAvatar = (workspaceUnit?: WorkspaceUnit) => {
|
||||
const useAvatar = (workspaceUnit: WorkspaceUnit | null) => {
|
||||
const [avatarUrl, setAvatarUrl] = useState('');
|
||||
const avatarId =
|
||||
workspaceUnit?.avatar || workspaceUnit?.blocksuiteWorkspace?.meta.avatar;
|
||||
@@ -23,7 +23,7 @@ const useAvatar = (workspaceUnit?: WorkspaceUnit) => {
|
||||
export const WorkspaceUnitAvatar = ({
|
||||
size = 20,
|
||||
name,
|
||||
workspaceUnit,
|
||||
workspaceUnit = null,
|
||||
style,
|
||||
}: {
|
||||
size?: number;
|
||||
@@ -31,7 +31,7 @@ export const WorkspaceUnitAvatar = ({
|
||||
workspaceUnit?: WorkspaceUnit | null;
|
||||
style?: React.CSSProperties;
|
||||
}) => {
|
||||
const avatarUrl = useAvatar(workspaceUnit || undefined);
|
||||
const avatarUrl = useAvatar(workspaceUnit);
|
||||
return (
|
||||
<Avatar
|
||||
size={size}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { CloudInsyncIcon, LogOutIcon } from '@blocksuite/icons';
|
||||
import { FlexWrapper } from '@/ui/layout';
|
||||
import { WorkspaceAvatar } from '@/components/workspace-avatar';
|
||||
import { IconButton } from '@/ui/button';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { StyledFooter, StyleUserInfo, StyledSignInButton } from './styles';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { Tooltip } from '@/ui/tooltip';
|
||||
export const Footer = ({
|
||||
onLogin,
|
||||
onLogout,
|
||||
}: {
|
||||
onLogin: () => void;
|
||||
onLogout: () => void;
|
||||
}) => {
|
||||
const { user } = useAppState();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<StyledFooter>
|
||||
{user && (
|
||||
<>
|
||||
<FlexWrapper>
|
||||
<WorkspaceAvatar
|
||||
size={40}
|
||||
name={user.name}
|
||||
avatar={user.avatar}
|
||||
></WorkspaceAvatar>
|
||||
<StyleUserInfo>
|
||||
<p>{user.name}</p>
|
||||
<p>{user.email}</p>
|
||||
</StyleUserInfo>
|
||||
</FlexWrapper>
|
||||
<Tooltip content={t('Sign out')} disablePortal={true}>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
onLogout();
|
||||
}}
|
||||
>
|
||||
<LogOutIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
|
||||
{!user && (
|
||||
<StyledSignInButton
|
||||
noBorder
|
||||
bold
|
||||
icon={
|
||||
<div className="circle">
|
||||
<CloudInsyncIcon fontSize={16} />
|
||||
</div>
|
||||
}
|
||||
onClick={async () => {
|
||||
onLogin();
|
||||
}}
|
||||
>
|
||||
{t('Sign in')}
|
||||
</StyledSignInButton>
|
||||
)}
|
||||
</StyledFooter>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,62 @@
|
||||
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 (
|
||||
<ListItem
|
||||
key={option.name}
|
||||
title={option.name}
|
||||
onClick={() => {
|
||||
changeLanguage(option.tag);
|
||||
}}
|
||||
>
|
||||
{option.originalName}
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export const LanguageMenu = () => {
|
||||
const { i18n } = useTranslation();
|
||||
|
||||
const currentLanguage = LOCALES.find(item => item.tag === i18n.language);
|
||||
|
||||
return (
|
||||
<Menu
|
||||
content={<LanguageMenuContent />}
|
||||
placement="bottom"
|
||||
trigger="click"
|
||||
disablePortal={true}
|
||||
>
|
||||
<Button
|
||||
icon={<ArrowDownIcon />}
|
||||
iconPosition="end"
|
||||
noBorder={true}
|
||||
style={{ textTransform: 'capitalize' }}
|
||||
data-testid="language-menu-button"
|
||||
>
|
||||
{currentLanguage?.originalName}
|
||||
</Button>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
const ListItem = styled(MenuItem)(({ theme }) => ({
|
||||
height: '38px',
|
||||
color: theme.colors.popoverColor,
|
||||
fontSize: theme.font.sm,
|
||||
textTransform: 'capitalize',
|
||||
padding: '0 24px',
|
||||
}));
|
||||
@@ -0,0 +1,83 @@
|
||||
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
|
||||
import {
|
||||
CloudIcon,
|
||||
LocalIcon,
|
||||
OfflineIcon,
|
||||
PublishedIcon,
|
||||
} from '@/components/workspace-modal/icons';
|
||||
import { UsersIcon } from '@blocksuite/icons';
|
||||
import { WorkspaceUnit } from '@affine/datacenter';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { StyleWorkspaceInfo, StyleWorkspaceTitle, StyledCard } from './styles';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { FlexWrapper } from '@/ui/layout';
|
||||
|
||||
const WorkspaceType = ({ workspaceData }: { workspaceData: WorkspaceUnit }) => {
|
||||
const { user } = useAppState();
|
||||
const { t } = useTranslation();
|
||||
const isOwner = user?.id === workspaceData.owner?.id;
|
||||
|
||||
if (workspaceData.provider === 'local') {
|
||||
return (
|
||||
<p>
|
||||
<LocalIcon />
|
||||
{t('Local Workspace')}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
return isOwner ? (
|
||||
<p>
|
||||
<CloudIcon />
|
||||
{t('Cloud Workspace')}
|
||||
</p>
|
||||
) : (
|
||||
<p>
|
||||
<UsersIcon fontSize={20} color={'#FF646B'} />
|
||||
{t('Joined Workspace')}
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
||||
export const WorkspaceCard = ({
|
||||
workspaceData,
|
||||
onClick,
|
||||
}: {
|
||||
workspaceData: WorkspaceUnit;
|
||||
onClick: (data: WorkspaceUnit) => void;
|
||||
}) => {
|
||||
const { currentWorkspace } = useAppState();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<StyledCard
|
||||
data-testid="workspace-card"
|
||||
onClick={() => {
|
||||
onClick(workspaceData);
|
||||
}}
|
||||
active={workspaceData.id === currentWorkspace?.id}
|
||||
>
|
||||
<FlexWrapper>
|
||||
<WorkspaceUnitAvatar size={58} workspaceUnit={workspaceData} />
|
||||
</FlexWrapper>
|
||||
|
||||
<StyleWorkspaceInfo>
|
||||
<StyleWorkspaceTitle>
|
||||
{workspaceData.name || 'AFFiNE'}
|
||||
</StyleWorkspaceTitle>
|
||||
<WorkspaceType workspaceData={workspaceData} />
|
||||
{workspaceData.provider === 'local' && (
|
||||
<p>
|
||||
<OfflineIcon />
|
||||
{t('Available Offline')}
|
||||
</p>
|
||||
)}
|
||||
{workspaceData.published && (
|
||||
<p>
|
||||
<PublishedIcon />
|
||||
{t('Published to Web')}
|
||||
</p>
|
||||
)}
|
||||
</StyleWorkspaceInfo>
|
||||
</StyledCard>
|
||||
);
|
||||
};
|
||||
@@ -70,7 +70,7 @@ export const CloudIcon = () => {
|
||||
<path
|
||||
d="M2.5 11.3744C2.5 13.837 4.51472 15.8333 7 15.8333L13.75 15.8333C15.8211 15.8333 17.5 14.1532 17.5 12.0807C17.5 10.5419 16.5744 9.12069 15.25 8.54163C15.1098 6.10205 13.07 4.16663 10.5744 4.16663C8.62616 4.16663 6.95578 5.40527 6.25 7.08329C4 7.44788 2.5 9.33336 2.5 11.3744Z"
|
||||
stroke="#60A5FA"
|
||||
stroke-width="1.25"
|
||||
strokeWidth="1.25"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
|
||||
@@ -1,28 +1,34 @@
|
||||
import { styled } from '@/styles';
|
||||
import { Modal, ModalWrapper } from '@/ui/modal';
|
||||
import { Button, IconButton } from '@/ui/button';
|
||||
import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal';
|
||||
import { FlexWrapper } 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 { 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 './SelectLanguageMenu';
|
||||
|
||||
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';
|
||||
interface WorkspaceModalProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
@@ -30,337 +36,117 @@ interface WorkspaceModalProps {
|
||||
|
||||
export const WorkspaceModal = ({ open, onClose }: WorkspaceModalProps) => {
|
||||
const [createWorkspaceOpen, setCreateWorkspaceOpen] = useState(false);
|
||||
const { workspaceList, currentWorkspace, user, logout, isOwner } =
|
||||
useAppState();
|
||||
const { logout, dataCenter } = useAppState();
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
const [loginOpen, setLoginOpen] = useState(false);
|
||||
const [logoutOpen, setLogoutOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<>
|
||||
<Modal open={open} onClose={onClose}>
|
||||
<ModalWrapper
|
||||
width={720}
|
||||
height={690}
|
||||
style={{
|
||||
padding: '24px 40px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<Header>
|
||||
<ContentTitle>{t('My Workspaces')}</ContentTitle>
|
||||
<HeaderOption>
|
||||
<LanguageMenu />
|
||||
<div
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
border: 'none',
|
||||
margin: '2px 16px',
|
||||
height: '24px',
|
||||
position: 'relative',
|
||||
top: '4px',
|
||||
}}
|
||||
<StyledModalHeader>
|
||||
<StyledModalHeaderLeft>
|
||||
<StyledModalTitle>{t('My Workspaces')}</StyledModalTitle>
|
||||
<Tooltip
|
||||
content={t('Workspace description')}
|
||||
placement="top-start"
|
||||
disablePortal={true}
|
||||
>
|
||||
<LineIcon></LineIcon>
|
||||
</div>
|
||||
<StyledHelperContainer>
|
||||
<HelpCenterIcon />
|
||||
</StyledHelperContainer>
|
||||
</Tooltip>
|
||||
</StyledModalHeaderLeft>
|
||||
|
||||
<Button
|
||||
style={{ border: 'none', padding: 0 }}
|
||||
<StyledOperationWrapper>
|
||||
<LanguageMenu />
|
||||
<StyledSplitLine />
|
||||
<ModalCloseButton
|
||||
onClick={() => {
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
<CloseIcon></CloseIcon>
|
||||
</Button>
|
||||
</HeaderOption>
|
||||
</Header>
|
||||
<Content>
|
||||
<WorkspaceList>
|
||||
{workspaceList.map((item, index) => {
|
||||
return (
|
||||
<WorkspaceItem
|
||||
onClick={() => {
|
||||
router.replace(`/workspace/${item.id}`);
|
||||
onClose();
|
||||
}}
|
||||
active={item.id === currentWorkspace?.id}
|
||||
key={index}
|
||||
>
|
||||
<div>
|
||||
<WorkspaceUnitAvatar size={58} workspaceUnit={item} />
|
||||
</div>
|
||||
absolute={false}
|
||||
/>
|
||||
</StyledOperationWrapper>
|
||||
</StyledModalHeader>
|
||||
|
||||
<StyleWorkspaceInfo>
|
||||
<StyleWorkspaceTitle>
|
||||
{item.name || 'AFFiNE'}
|
||||
</StyleWorkspaceTitle>
|
||||
{isOwner ? (
|
||||
item.provider === 'local' ? (
|
||||
<p>
|
||||
<LocalIcon />
|
||||
Local Workspace
|
||||
</p>
|
||||
) : (
|
||||
<p>
|
||||
<CloudIcon />
|
||||
Cloud Workspace
|
||||
</p>
|
||||
)
|
||||
) : (
|
||||
<p>
|
||||
<UsersIcon fontSize={20} color={'#FF646B'} />
|
||||
Joined Workspace
|
||||
</p>
|
||||
)}
|
||||
{item.provider === 'local' && (
|
||||
<p>
|
||||
<OfflineIcon />
|
||||
All data can be accessed offline
|
||||
</p>
|
||||
)}
|
||||
{item.published && (
|
||||
<p>
|
||||
<PublishIcon fontSize={16} /> Published to Web
|
||||
</p>
|
||||
)}
|
||||
</StyleWorkspaceInfo>
|
||||
</WorkspaceItem>
|
||||
);
|
||||
})}
|
||||
<WorkspaceItem
|
||||
onClick={() => {
|
||||
setCreateWorkspaceOpen(true);
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<StyleWorkspaceAdd className="add-icon">
|
||||
<AddIcon fontSize={18} />
|
||||
</StyleWorkspaceAdd>
|
||||
</div>
|
||||
<StyledModalContent>
|
||||
{dataCenter.workspaces.map((item, index) => {
|
||||
return (
|
||||
<WorkspaceCard
|
||||
workspaceData={item}
|
||||
onClick={workspaceData => {
|
||||
router.replace(`/workspace/${workspaceData.id}`);
|
||||
onClose();
|
||||
}}
|
||||
key={index}
|
||||
></WorkspaceCard>
|
||||
);
|
||||
})}
|
||||
<StyledCard
|
||||
onClick={() => {
|
||||
setCreateWorkspaceOpen(true);
|
||||
}}
|
||||
>
|
||||
<FlexWrapper>
|
||||
<StyleWorkspaceAdd className="add-icon">
|
||||
<AddIcon fontSize={18} />
|
||||
</StyleWorkspaceAdd>
|
||||
</FlexWrapper>
|
||||
|
||||
<StyleWorkspaceInfo>
|
||||
<StyleWorkspaceTitle>New workspace</StyleWorkspaceTitle>
|
||||
<p>Crete or import</p>
|
||||
</StyleWorkspaceInfo>
|
||||
</WorkspaceItem>
|
||||
</WorkspaceList>
|
||||
{/* <p style={{ fontSize: '14px', color: '#ccc', margin: '12px 0' }}>
|
||||
{t('Tips')}
|
||||
{t('Workspace description')}
|
||||
</p> */}
|
||||
</Content>
|
||||
<LoginModal
|
||||
open={loginOpen}
|
||||
onClose={() => {
|
||||
setLoginOpen(false);
|
||||
<StyleWorkspaceInfo>
|
||||
<StyleWorkspaceTitle>{t('New Workspace')}</StyleWorkspaceTitle>
|
||||
<p>{t('Create Or Import')}</p>
|
||||
</StyleWorkspaceInfo>
|
||||
</StyledCard>
|
||||
</StyledModalContent>
|
||||
|
||||
<Footer
|
||||
onLogin={() => {
|
||||
setLoginOpen(true);
|
||||
}}
|
||||
></LoginModal>
|
||||
<Footer>
|
||||
{!user ? (
|
||||
<StyleSignIn
|
||||
onClick={async () => {
|
||||
setLoginOpen(true);
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
<CloudInsyncIcon fontSize={16} />
|
||||
</span>
|
||||
Sign in to sync with AFFINE Cloud
|
||||
</StyleSignIn>
|
||||
) : (
|
||||
<div style={{ display: 'flex' }}>
|
||||
<div style={{ paddingTop: '20px' }}>
|
||||
<WorkspaceAvatar
|
||||
size={40}
|
||||
name={user.name}
|
||||
avatar={user.avatar}
|
||||
></WorkspaceAvatar>
|
||||
</div>
|
||||
<StyleUserInfo style={{}}>
|
||||
<p>{user.name}</p>
|
||||
<p>{user.email}</p>
|
||||
</StyleUserInfo>
|
||||
<div style={{ paddingTop: '25px' }}>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setLogoutOpen(true);
|
||||
// confirm({
|
||||
// title: 'Sign out?',
|
||||
// content: `All data has been stored in the cloud. `,
|
||||
// confirmText: 'Sign out',
|
||||
// cancelText: 'Cancel',
|
||||
// }).then(async confirm => {
|
||||
// // if (confirm) {
|
||||
// // if (user) {
|
||||
// // await logout();
|
||||
// // router.replace(`/workspace`);
|
||||
// // toast('Enabled success');
|
||||
// // }
|
||||
// // }
|
||||
// });
|
||||
}}
|
||||
>
|
||||
<LogOutIcon></LogOutIcon>
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Footer>
|
||||
<CreateWorkspaceModal
|
||||
open={createWorkspaceOpen}
|
||||
onClose={() => {
|
||||
setCreateWorkspaceOpen(false);
|
||||
onLogout={() => {
|
||||
setLogoutOpen(true);
|
||||
}}
|
||||
></CreateWorkspaceModal>
|
||||
<LogoutModal
|
||||
open={logoutOpen}
|
||||
onClose={async wait => {
|
||||
if (!wait) {
|
||||
await logout();
|
||||
router.replace(`/workspace`);
|
||||
}
|
||||
setLogoutOpen(false);
|
||||
}}
|
||||
></LogoutModal>
|
||||
/>
|
||||
</ModalWrapper>
|
||||
</Modal>
|
||||
</div>
|
||||
|
||||
<LoginModal
|
||||
open={loginOpen}
|
||||
onClose={() => {
|
||||
setLoginOpen(false);
|
||||
}}
|
||||
/>
|
||||
<LogoutModal
|
||||
open={logoutOpen}
|
||||
onClose={async wait => {
|
||||
if (!wait) {
|
||||
await logout();
|
||||
if (dataCenter.workspaces.length === 0) {
|
||||
router.push(`/workspace`);
|
||||
} else {
|
||||
router.push(`/workspace/${dataCenter.workspaces[0].id}`);
|
||||
}
|
||||
}
|
||||
setLogoutOpen(false);
|
||||
}}
|
||||
/>
|
||||
<CreateWorkspaceModal
|
||||
open={createWorkspaceOpen}
|
||||
onClose={() => {
|
||||
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,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
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';
|
||||
|
||||
export const LanguageMenu = () => {
|
||||
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 (
|
||||
<StyledTooltip
|
||||
title={
|
||||
<>
|
||||
{LOCALES.map(option => {
|
||||
return (
|
||||
<ListItem
|
||||
style={{ border: 'none' }}
|
||||
key={option.name}
|
||||
title={option.name}
|
||||
onClick={() => {
|
||||
changeLanguage(option.tag);
|
||||
setShow(false);
|
||||
setLanguageName(option.originalName);
|
||||
}}
|
||||
>
|
||||
{option.originalName}
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
}
|
||||
open={show}
|
||||
>
|
||||
<StyledTitleButton
|
||||
style={{ border: 'none', padding: '0px' }}
|
||||
onClick={() => {
|
||||
setShow(!show);
|
||||
}}
|
||||
>
|
||||
<StyledContainer>
|
||||
<StyledText>{languageName}</StyledText>
|
||||
<ArrowDownIcon fontSize={18} />
|
||||
</StyledContainer>
|
||||
</StyledTitleButton>
|
||||
</StyledTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
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) => (
|
||||
<Tooltip {...props} classes={{ popper: className }} />
|
||||
))(({ 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%',
|
||||
color: theme.colors.popoverColor,
|
||||
fontSize: theme.font.sm,
|
||||
textTransform: 'capitalize',
|
||||
}));
|
||||
|
||||
const StyledTitleButton = styled(Button)(({ theme }) => ({
|
||||
color: theme.colors.popoverColor,
|
||||
fontSize: theme.font.sm,
|
||||
}));
|
||||
@@ -1,38 +1,159 @@
|
||||
import { displayFlex, styled } from '@/styles';
|
||||
import { displayFlex, displayInlineFlex, styled, textEllipsis } from '@/styles';
|
||||
import { Button } from '@/ui/button';
|
||||
|
||||
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',
|
||||
maxWidth: '200px',
|
||||
...textEllipsis(1),
|
||||
};
|
||||
});
|
||||
|
||||
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'),
|
||||
marginBottom: '24px',
|
||||
':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 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', 'flex-start'),
|
||||
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'),
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledSignInButton = styled(Button)(({ theme }) => {
|
||||
return {
|
||||
fontWeight: 700,
|
||||
paddingLeft: 0,
|
||||
'.circle': {
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
borderRadius: '20px',
|
||||
backgroundColor: theme.colors.innerHoverBackground,
|
||||
flexShrink: 0,
|
||||
marginRight: '16px',
|
||||
...displayInlineFlex('center', 'center'),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,24 +1,16 @@
|
||||
import { styled } from '@/styles';
|
||||
import { WorkspaceUnit } from '@affine/datacenter';
|
||||
import { Trans } from '@affine/i18n';
|
||||
export const ExportPageTitleContainer = styled('div')(() => {
|
||||
return {
|
||||
display: 'flex',
|
||||
|
||||
fontWeight: '500',
|
||||
flex: 1,
|
||||
};
|
||||
});
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { Wrapper } from '@/ui/layout';
|
||||
import { Button } from '@/ui/button';
|
||||
export const ExportPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||
const { t } = useTranslation();
|
||||
console.log(workspace);
|
||||
return (
|
||||
<ExportPageTitleContainer>
|
||||
<Trans i18nKey="Export Workspace">
|
||||
Export Workspace
|
||||
<code style={{ margin: '0 10px' }}>
|
||||
{{ workspace: workspace.name }}
|
||||
</code>
|
||||
Is Comming
|
||||
</Trans>
|
||||
</ExportPageTitleContainer>
|
||||
<>
|
||||
<Wrapper marginBottom="32px"> {t('Export Description')}</Wrapper>
|
||||
<Button type="light" shape="circle" disabled>
|
||||
{t('Export AFFINE backup file')}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,20 +1,12 @@
|
||||
import {
|
||||
StyledCopyButtonContainer,
|
||||
StyledPublishContent,
|
||||
StyledPublishCopyContainer,
|
||||
StyledPublishExplanation,
|
||||
StyledSettingH2,
|
||||
StyledStopPublishContainer,
|
||||
} from './style';
|
||||
import { useState } from 'react';
|
||||
import { Button } from '@/ui/button';
|
||||
import Input from '@/ui/input';
|
||||
import { toast } from '@/ui/toast';
|
||||
// import { useAppState } from '@/providers/app-state-provider3';
|
||||
import { WorkspaceUnit } from '@affine/datacenter';
|
||||
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { EnableWorkspaceButton } from '../enable-workspace';
|
||||
import { Wrapper, Content, FlexWrapper } from '@/ui/layout';
|
||||
export const PublishPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||
const shareUrl = window.location.host + '/public-workspace/' + workspace.id;
|
||||
const { publishWorkspace } = useWorkspaceHelper();
|
||||
@@ -23,95 +15,75 @@ export const PublishPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||
const togglePublic = async (flag: boolean) => {
|
||||
try {
|
||||
await publishWorkspace(workspace.id.toString(), flag);
|
||||
setLoaded(false);
|
||||
} catch (e) {
|
||||
toast('Failed to publish workspace');
|
||||
toast(t('Failed to publish workspace'));
|
||||
}
|
||||
};
|
||||
|
||||
const copyUrl = () => {
|
||||
navigator.clipboard.writeText(shareUrl);
|
||||
toast('Copied url to clipboard');
|
||||
toast(t('Copied link to clipboard'));
|
||||
};
|
||||
|
||||
if (workspace.provider === 'affine') {
|
||||
if (workspace.published) {
|
||||
return (
|
||||
<>
|
||||
<Wrapper marginBottom="32px">{t('Published Description')}</Wrapper>
|
||||
|
||||
<Wrapper marginBottom="12px">
|
||||
<Content weight="500">{t('Share with link')}</Content>
|
||||
</Wrapper>
|
||||
<FlexWrapper>
|
||||
<Input width={582} value={shareUrl} disabled={true}></Input>
|
||||
<Button
|
||||
onClick={copyUrl}
|
||||
type="light"
|
||||
shape="circle"
|
||||
style={{ marginLeft: '24px' }}
|
||||
>
|
||||
{t('Copy Link')}
|
||||
</Button>
|
||||
</FlexWrapper>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
setLoaded(true);
|
||||
await togglePublic(false);
|
||||
}}
|
||||
loading={false}
|
||||
type="danger"
|
||||
shape="circle"
|
||||
style={{ marginTop: '38px' }}
|
||||
>
|
||||
{t('Stop publishing')}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Wrapper marginBottom="32px">{t('Publishing Description')}</Wrapper>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
setLoaded(true);
|
||||
await togglePublic(true);
|
||||
}}
|
||||
loading={loaded}
|
||||
type="light"
|
||||
shape="circle"
|
||||
>
|
||||
{t('Publish to web')}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{workspace.provider === 'affine' ? (
|
||||
<div
|
||||
style={{ height: '100%', display: 'flex', flexDirection: 'column' }}
|
||||
>
|
||||
<StyledPublishContent>
|
||||
{workspace.published ? (
|
||||
<>
|
||||
<StyledPublishExplanation>
|
||||
The current workspace has been published to the web, everyone
|
||||
can view the contents of this workspace through the link.
|
||||
</StyledPublishExplanation>
|
||||
|
||||
<StyledPublishCopyContainer>
|
||||
<StyledSettingH2 marginBottom={16}>
|
||||
{t('Share with link')}
|
||||
</StyledSettingH2>
|
||||
<Input width={500} value={shareUrl} disabled={true}></Input>
|
||||
<StyledCopyButtonContainer>
|
||||
<Button onClick={copyUrl} type="primary" shape="circle">
|
||||
{t('Copy Link')}
|
||||
</Button>
|
||||
</StyledCopyButtonContainer>
|
||||
</StyledPublishCopyContainer>
|
||||
</>
|
||||
) : (
|
||||
<StyledPublishExplanation>
|
||||
{t('Publishing Description')}
|
||||
<div style={{ marginTop: '64px' }}>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
setLoaded(true);
|
||||
await togglePublic(true);
|
||||
setLoaded(false);
|
||||
}}
|
||||
loading={loaded}
|
||||
type="primary"
|
||||
shape="circle"
|
||||
>
|
||||
{t('Publish to web')}
|
||||
</Button>
|
||||
</div>
|
||||
</StyledPublishExplanation>
|
||||
)}
|
||||
</StyledPublishContent>
|
||||
|
||||
{workspace.published ? (
|
||||
<StyledStopPublishContainer>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
setLoaded(true);
|
||||
await togglePublic(false);
|
||||
setLoaded(true);
|
||||
}}
|
||||
loading={false}
|
||||
type="danger"
|
||||
shape="circle"
|
||||
>
|
||||
{t('Stop publishing')}
|
||||
</Button>
|
||||
</StyledStopPublishContainer>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<StyledPublishContent>
|
||||
<>
|
||||
<StyledPublishExplanation>
|
||||
Publishing to web requires AFFiNE Cloud service.
|
||||
</StyledPublishExplanation>
|
||||
|
||||
<div style={{ marginTop: '72px' }}>
|
||||
<EnableWorkspaceButton></EnableWorkspaceButton>
|
||||
</div>
|
||||
</>
|
||||
</StyledPublishContent>
|
||||
)}
|
||||
<Wrapper marginBottom="32px">{t('Publishing')}</Wrapper>
|
||||
<EnableWorkspaceButton />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,84 +1,106 @@
|
||||
import {
|
||||
StyleAsync,
|
||||
StyledPublishContent,
|
||||
StyledPublishExplanation,
|
||||
StyledWorkspaceName,
|
||||
StyledWorkspaceType,
|
||||
StyledEmail,
|
||||
// StyledDownloadCard,
|
||||
// StyledDownloadCardDes,
|
||||
} from './style';
|
||||
import { DownloadIcon } from '@blocksuite/icons';
|
||||
import { Button } from '@/ui/button';
|
||||
import { Menu, MenuItem } from '@/ui/menu';
|
||||
import { WorkspaceUnit } from '@affine/datacenter';
|
||||
import { Trans, useTranslation } from '@affine/i18n';
|
||||
import { useTranslation, Trans } from '@affine/i18n';
|
||||
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
|
||||
import { EnableWorkspaceButton } from '../enable-workspace';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { FlexWrapper, Content, Wrapper } from '@/ui/layout';
|
||||
|
||||
// // FIXME: Temporary solution, since the @blocksuite/icons is broken
|
||||
// const ActiveIcon = () => {
|
||||
// return (
|
||||
// <svg
|
||||
// width="24"
|
||||
// height="24"
|
||||
// viewBox="0 0 24 24"
|
||||
// fill="none"
|
||||
// xmlns="http://www.w3.org/2000/svg"
|
||||
// >
|
||||
// <path
|
||||
// fillRule="evenodd"
|
||||
// clipRule="evenodd"
|
||||
// d="M2.25 12C2.25 6.61522 6.61522 2.25 12 2.25C17.3848 2.25 21.75 6.61522 21.75 12C21.75 17.3848 17.3848 21.75 12 21.75C6.61522 21.75 2.25 17.3848 2.25 12Z"
|
||||
// fill="#6880FF"
|
||||
// />
|
||||
// <path
|
||||
// fillRule="evenodd"
|
||||
// clipRule="evenodd"
|
||||
// d="M16.5068 8.44714C16.8121 8.72703 16.8328 9.20146 16.5529 9.5068L11.0529 15.5068C10.9146 15.6576 10.7208 15.7454 10.5163 15.7498C10.3118 15.7543 10.1143 15.675 9.96967 15.5303L7.46967 13.0303C7.17678 12.7374 7.17678 12.2626 7.46967 11.9697C7.76256 11.6768 8.23744 11.6768 8.53033 11.9697L10.4764 13.9158L15.4471 8.49321C15.727 8.18787 16.2015 8.16724 16.5068 8.44714Z"
|
||||
// fill="white"
|
||||
// />
|
||||
// </svg>
|
||||
// );
|
||||
// };
|
||||
|
||||
export const SyncPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||
const { t } = useTranslation();
|
||||
const { user } = useAppState();
|
||||
if (workspace.provider === 'local') {
|
||||
return (
|
||||
<>
|
||||
<FlexWrapper alignItems="center" style={{ marginBottom: '12px' }}>
|
||||
<WorkspaceUnitAvatar
|
||||
size={32}
|
||||
name={workspace.name}
|
||||
workspaceUnit={workspace}
|
||||
style={{ marginRight: '12px' }}
|
||||
/>
|
||||
<StyledWorkspaceName>{workspace.name} </StyledWorkspaceName>
|
||||
<Content weight={500}>{t('is a Local Workspace')}</Content>
|
||||
</FlexWrapper>
|
||||
<p>{t('Local Workspace Description')}</p>
|
||||
<Wrapper marginTop="32px">
|
||||
<EnableWorkspaceButton />
|
||||
</Wrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<StyledPublishContent>
|
||||
{workspace.provider === 'local' ? (
|
||||
<>
|
||||
<StyledPublishExplanation>
|
||||
<WorkspaceUnitAvatar
|
||||
size={32}
|
||||
name={workspace.name}
|
||||
workspaceUnit={workspace}
|
||||
style={{ marginRight: '12px' }}
|
||||
/>
|
||||
<StyledWorkspaceName>{workspace.name};</StyledWorkspaceName>
|
||||
<StyledWorkspaceType>is a Local Workspace.</StyledWorkspaceType>
|
||||
</StyledPublishExplanation>
|
||||
<StyledWorkspaceType>
|
||||
All data is stored on the current device. You can enable AFFiNE
|
||||
Cloud for this workspace to keep data in sync with the cloud.
|
||||
</StyledWorkspaceType>
|
||||
<StyleAsync>
|
||||
<EnableWorkspaceButton></EnableWorkspaceButton>
|
||||
</StyleAsync>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<StyledPublishExplanation>
|
||||
<Trans i18nKey="Sync Description2">
|
||||
<code>{{ workspaceName: workspace.name ?? 'Affine' }}</code>
|
||||
is Cloud Workspace. All data will be synchronised and saved to
|
||||
the AFFiNE
|
||||
</Trans>
|
||||
</StyledPublishExplanation>
|
||||
<StyleAsync>
|
||||
<Menu
|
||||
content={
|
||||
<>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
// deleteMember(workspace.id, 0);
|
||||
}}
|
||||
icon={<DownloadIcon />}
|
||||
>
|
||||
{t('Download data to device', { CoreOrAll: 'core' })}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
// deleteMember(workspace.id, 0);
|
||||
}}
|
||||
icon={<DownloadIcon />}
|
||||
>
|
||||
{t('Download data to device', { CoreOrAll: 'all' })}
|
||||
</MenuItem>
|
||||
</>
|
||||
}
|
||||
placement="bottom-end"
|
||||
disablePortal={true}
|
||||
>
|
||||
<Button type="primary">
|
||||
{t('Download data to device', { CoreOrAll: 'all' })}
|
||||
</Button>
|
||||
</Menu>
|
||||
</StyleAsync>
|
||||
</>
|
||||
)}
|
||||
</StyledPublishContent>
|
||||
</div>
|
||||
<>
|
||||
<FlexWrapper alignItems="center" style={{ marginBottom: '12px' }}>
|
||||
<WorkspaceUnitAvatar
|
||||
size={32}
|
||||
name={workspace.name}
|
||||
workspaceUnit={workspace}
|
||||
style={{ marginRight: '12px' }}
|
||||
/>
|
||||
<StyledWorkspaceName>{workspace.name} </StyledWorkspaceName>
|
||||
<Content weight={500}>{t('is a Cloud Workspace')}</Content>
|
||||
</FlexWrapper>
|
||||
<Trans i18nKey="Cloud Workspace Description">
|
||||
All data will be synchronised and saved to the AFFiNE account
|
||||
<StyledEmail>
|
||||
{{
|
||||
email: '{' + user?.email + '}.',
|
||||
}}
|
||||
</StyledEmail>
|
||||
</Trans>
|
||||
|
||||
{/*<Wrapper marginBottom="12px" marginTop="32px">*/}
|
||||
{/* <Content weight="500">{t('Data sync mode')}</Content>*/}
|
||||
{/*</Wrapper>*/}
|
||||
{/*<FlexWrapper>*/}
|
||||
{/* <StyledDownloadCard>*/}
|
||||
{/* <ActiveIcon />*/}
|
||||
{/* <Wrapper>{t('Download all data')}</Wrapper>*/}
|
||||
{/* <StyledDownloadCardDes>*/}
|
||||
{/* {t('It takes up more space on your device.')}*/}
|
||||
{/* </StyledDownloadCardDes>*/}
|
||||
{/* </StyledDownloadCard>*/}
|
||||
|
||||
{/* <StyledDownloadCard active>*/}
|
||||
{/* <ActiveIcon />*/}
|
||||
{/* <Wrapper>{t('Download core data')}</Wrapper>*/}
|
||||
{/* <StyledDownloadCardDes>*/}
|
||||
{/* {t('It takes up little space on your device.')}*/}
|
||||
{/* </StyledDownloadCardDes>*/}
|
||||
{/* </StyledDownloadCard>*/}
|
||||
{/*</FlexWrapper>*/}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
import {
|
||||
StyledDeleteButtonContainer,
|
||||
// StyledSettingAvatar,
|
||||
StyledSettingAvatarContent,
|
||||
StyledSettingInputContainer,
|
||||
StyledDoneButtonContainer,
|
||||
StyledInput,
|
||||
StyledProviderInfo,
|
||||
StyleGeneral,
|
||||
StyledAvatar,
|
||||
} from './style';
|
||||
import { StyledSettingH2 } from '../style';
|
||||
import { StyledInput, StyledProviderInfo, StyledAvatar } from './style';
|
||||
import { StyledSettingKey, StyledRow } from '../style';
|
||||
import { FlexWrapper, Content } from '@/ui/layout';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Button } from '@/ui/button';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { WorkspaceDelete } from './delete';
|
||||
import { WorkspaceLeave } from './leave';
|
||||
import { DoneIcon, UsersIcon } from '@blocksuite/icons';
|
||||
// import { Upload } from '@/components/file-upload';
|
||||
import { UsersIcon } from '@blocksuite/icons';
|
||||
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
|
||||
import { WorkspaceUnit } from '@affine/datacenter';
|
||||
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
|
||||
@@ -33,9 +23,6 @@ export const GeneralPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||
const { updateWorkspace } = useWorkspaceHelper();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleChangeWorkSpaceName = (newName: string) => {
|
||||
setWorkspaceName(newName);
|
||||
};
|
||||
const handleUpdateWorkspaceName = () => {
|
||||
currentWorkspace &&
|
||||
updateWorkspace({ name: workspaceName }, currentWorkspace);
|
||||
@@ -47,83 +34,105 @@ export const GeneralPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||
(await updateWorkspace({ avatarBlob: blob }, currentWorkspace));
|
||||
};
|
||||
|
||||
return workspace ? (
|
||||
<StyleGeneral>
|
||||
<div style={{ flex: 1, overflow: 'auto' }}>
|
||||
<StyledSettingH2>Workspace Avatar</StyledSettingH2>
|
||||
<StyledSettingAvatarContent>
|
||||
<StyledAvatar>
|
||||
<Upload
|
||||
accept="image/gif,image/jpeg,image/jpg,image/png,image/svg"
|
||||
fileChange={fileChange}
|
||||
>
|
||||
<>
|
||||
<div className="camera-icon">
|
||||
<CameraIcon></CameraIcon>
|
||||
</div>
|
||||
<WorkspaceUnitAvatar
|
||||
size={60}
|
||||
name={workspace.name}
|
||||
workspaceUnit={workspace}
|
||||
/>
|
||||
</>
|
||||
</Upload>
|
||||
</StyledAvatar>
|
||||
{/* TODO: Wait for image sync to complete */}
|
||||
{/* <Upload
|
||||
accept="image/gif,image/jpeg,image/jpg,image/png,image/svg"
|
||||
fileChange={fileChange}
|
||||
>
|
||||
<Button loading={uploading}>{t('Upload')}</Button>
|
||||
</Upload> */}
|
||||
{/* TODO: add upload logic */}
|
||||
</StyledSettingAvatarContent>
|
||||
<StyledSettingH2 marginTop={20}>{t('Workspace Name')}</StyledSettingH2>
|
||||
<StyledSettingInputContainer>
|
||||
if (!workspace) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledRow>
|
||||
<StyledSettingKey>{t('Workspace Avatar')}</StyledSettingKey>
|
||||
<StyledAvatar>
|
||||
<Upload
|
||||
accept="image/gif,image/jpeg,image/jpg,image/png,image/svg"
|
||||
fileChange={fileChange}
|
||||
>
|
||||
<>
|
||||
<div className="camera-icon">
|
||||
<CameraIcon></CameraIcon>
|
||||
</div>
|
||||
<WorkspaceUnitAvatar
|
||||
size={72}
|
||||
name={workspace.name}
|
||||
workspaceUnit={workspace}
|
||||
/>
|
||||
</>
|
||||
</Upload>
|
||||
</StyledAvatar>
|
||||
</StyledRow>
|
||||
|
||||
<StyledRow>
|
||||
<StyledSettingKey>{t('Workspace Name')}</StyledSettingKey>
|
||||
<FlexWrapper>
|
||||
<StyledInput
|
||||
width={284}
|
||||
height={32}
|
||||
height={38}
|
||||
value={workspaceName}
|
||||
placeholder={t('Workspace Name')}
|
||||
maxLength={14}
|
||||
minLength={1}
|
||||
maxLength={15}
|
||||
minLength={0}
|
||||
disabled={!isOwner}
|
||||
onChange={handleChangeWorkSpaceName}
|
||||
onChange={newName => {
|
||||
setWorkspaceName(newName);
|
||||
}}
|
||||
></StyledInput>
|
||||
{isOwner ? (
|
||||
<StyledDoneButtonContainer
|
||||
onClick={() => {
|
||||
handleUpdateWorkspaceName();
|
||||
}}
|
||||
>
|
||||
<DoneIcon />
|
||||
</StyledDoneButtonContainer>
|
||||
) : null}
|
||||
</StyledSettingInputContainer>
|
||||
<StyledSettingH2 marginTop={20}>{t('Workspace Type')}</StyledSettingH2>
|
||||
<StyledSettingInputContainer>
|
||||
{isOwner && (
|
||||
<>
|
||||
<Button
|
||||
type="default"
|
||||
shape="circle"
|
||||
style={{ marginLeft: '24px' }}
|
||||
onClick={() => {
|
||||
setWorkspaceName(workspace.name);
|
||||
}}
|
||||
>
|
||||
{t('Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
type="light"
|
||||
shape="circle"
|
||||
style={{ marginLeft: '24px' }}
|
||||
onClick={() => {
|
||||
handleUpdateWorkspaceName();
|
||||
}}
|
||||
>
|
||||
{t('Confirm')}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</FlexWrapper>
|
||||
</StyledRow>
|
||||
|
||||
<StyledRow>
|
||||
<StyledSettingKey>{t('Workspace Type')}</StyledSettingKey>
|
||||
<FlexWrapper>
|
||||
{isOwner ? (
|
||||
currentWorkspace?.provider === 'local' ? (
|
||||
<StyledProviderInfo>
|
||||
<FlexWrapper alignItems="center">
|
||||
<LocalIcon />
|
||||
Local Workspace
|
||||
</StyledProviderInfo>
|
||||
<Content style={{ marginLeft: '15px' }}>
|
||||
{t('Local Workspace')}
|
||||
</Content>
|
||||
</FlexWrapper>
|
||||
) : (
|
||||
<StyledProviderInfo>
|
||||
<FlexWrapper alignItems="center">
|
||||
<CloudIcon />
|
||||
All data can be accessed offline
|
||||
</StyledProviderInfo>
|
||||
<Content style={{ marginLeft: '15px' }}>
|
||||
{t('Available Offline')}
|
||||
</Content>
|
||||
</FlexWrapper>
|
||||
)
|
||||
) : (
|
||||
<StyledProviderInfo>
|
||||
<UsersIcon fontSize={20} color={'#FF646B'} />
|
||||
Joined Workspace
|
||||
{t('Joined Workspace')}
|
||||
</StyledProviderInfo>
|
||||
)}
|
||||
</StyledSettingInputContainer>
|
||||
</div>
|
||||
</FlexWrapper>
|
||||
</StyledRow>
|
||||
|
||||
<StyledDeleteButtonContainer>
|
||||
<StyledRow>
|
||||
<StyledSettingKey> {t('Delete Workspace')}</StyledSettingKey>
|
||||
{isOwner ? (
|
||||
<>
|
||||
<Button
|
||||
@@ -163,7 +172,7 @@ export const GeneralPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</StyledDeleteButtonContainer>
|
||||
</StyleGeneral>
|
||||
) : null;
|
||||
</StyledRow>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useRouter } from 'next/router';
|
||||
import Modal from '@/ui/modal';
|
||||
import Input from '@/ui/input';
|
||||
import {
|
||||
@@ -11,7 +12,6 @@ import {
|
||||
import { useState } from 'react';
|
||||
import { ModalCloseButton } from '@/ui/modal';
|
||||
import { Button } from '@/ui/button';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
import { WorkspaceUnit } from '@affine/datacenter';
|
||||
import { Trans, useTranslation } from '@affine/i18n';
|
||||
|
||||
@@ -1,40 +1,6 @@
|
||||
import { displayFlex, styled } from '@/styles';
|
||||
import MuiAvatar from '@mui/material/Avatar';
|
||||
import IconButton from '@/ui/button/IconButton';
|
||||
import { styled } from '@/styles';
|
||||
import Input from '@/ui/input';
|
||||
|
||||
export const StyledSettingInputContainer = styled('div')(() => {
|
||||
return {
|
||||
marginTop: '12px',
|
||||
width: '100%',
|
||||
...displayFlex('flex-start', 'center'),
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledDeleteButtonContainer = styled('div')(() => {
|
||||
return {
|
||||
textAlign: 'center',
|
||||
};
|
||||
});
|
||||
export const StyleGeneral = styled('div')(() => {
|
||||
return {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
};
|
||||
});
|
||||
export const StyledDoneButtonContainer = styled(IconButton)(({ theme }) => {
|
||||
return {
|
||||
border: `1px solid ${theme.colors.borderColor}`,
|
||||
borderRadius: '10px',
|
||||
height: '32px',
|
||||
overflow: 'hidden',
|
||||
marginLeft: '20px',
|
||||
':hover': {
|
||||
borderColor: theme.colors.primaryColor,
|
||||
},
|
||||
};
|
||||
});
|
||||
export const StyledInput = styled(Input)(({ theme }) => {
|
||||
return {
|
||||
border: `1px solid ${theme.colors.borderColor}`,
|
||||
@@ -43,24 +9,9 @@ export const StyledInput = styled(Input)(({ theme }) => {
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledSettingAvatarContent = styled('div')(() => {
|
||||
return {
|
||||
marginTop: '12px',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
height: '72px',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledSettingAvatar = styled(MuiAvatar)(() => {
|
||||
return { height: '72px', width: '72px', marginRight: '24px' };
|
||||
});
|
||||
|
||||
export const StyledProviderInfo = styled('p')(({ theme }) => {
|
||||
return {
|
||||
color: theme.colors.iconColor,
|
||||
fontSize: theme.font.sm,
|
||||
svg: {
|
||||
verticalAlign: 'sub',
|
||||
marginRight: '10px',
|
||||
@@ -71,7 +22,6 @@ export const StyledProviderInfo = styled('p')(({ theme }) => {
|
||||
export const StyledAvatar = styled('div')(() => {
|
||||
return {
|
||||
position: 'relative',
|
||||
marginRight: '20px',
|
||||
cursor: 'pointer',
|
||||
':hover': {
|
||||
'.camera-icon': {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal';
|
||||
import { Button } from '@/ui/button';
|
||||
import Input from '@/ui/input';
|
||||
import { useState } from 'react';
|
||||
import { Avatar } from '@mui/material';
|
||||
import { MuiAvatar } from '@/ui/mui';
|
||||
import useMembers from '@/hooks/use-members';
|
||||
import { User } from '@affine/datacenter';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
@@ -76,10 +76,9 @@ export const InviteMemberModal = ({
|
||||
<ModalWrapper width={460} height={236}>
|
||||
<Header>
|
||||
<ModalCloseButton
|
||||
top={6}
|
||||
right={6}
|
||||
onClick={() => {
|
||||
onClose();
|
||||
setEmail('');
|
||||
}}
|
||||
/>
|
||||
</Header>
|
||||
@@ -102,7 +101,7 @@ export const InviteMemberModal = ({
|
||||
) : (
|
||||
<Member>
|
||||
{userData?.avatar ? (
|
||||
<Avatar src={userData?.avatar}></Avatar>
|
||||
<MuiAvatar src={userData?.avatar}></MuiAvatar>
|
||||
) : (
|
||||
<MemberIcon>
|
||||
<EmailIcon></EmailIcon>
|
||||
@@ -125,6 +124,7 @@ export const InviteMemberModal = ({
|
||||
style={{ width: '364px', height: '38px', borderRadius: '40px' }}
|
||||
onClick={async () => {
|
||||
await inviteMember(email);
|
||||
setEmail('');
|
||||
onInviteSuccess();
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -10,10 +10,9 @@ import {
|
||||
StyledMemberRoleContainer,
|
||||
StyledMemberTitleContainer,
|
||||
StyledMoreVerticalButton,
|
||||
StyledPublishExplanation,
|
||||
StyledMemberWarp,
|
||||
StyledMemberContainer,
|
||||
} from './style';
|
||||
import { Wrapper } from '@/ui/layout';
|
||||
import { MoreVerticalIcon, EmailIcon, TrashIcon } from '@blocksuite/icons';
|
||||
import { useState } from 'react';
|
||||
import { Button, IconButton } from '@/ui/button';
|
||||
@@ -25,14 +24,13 @@ 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 { FlexWrapper } from '@/ui/layout';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { EnableWorkspaceButton } from '@/components/enable-workspace';
|
||||
|
||||
export const MembersPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||
const [isInviteModalShow, setIsInviteModalShow] = useState(false);
|
||||
const { members, removeMember, loaded } = useMembers();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { confirm } = useConfirm();
|
||||
|
||||
@@ -41,9 +39,9 @@ export const MembersPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||
<StyledMemberContainer>
|
||||
<StyledMemberListContainer>
|
||||
{!loaded && (
|
||||
<Wrapper justifyContent="center">
|
||||
<FlexWrapper justifyContent="center">
|
||||
<Loading size={25} />
|
||||
</Wrapper>
|
||||
</FlexWrapper>
|
||||
)}
|
||||
{loaded && members.length === 0 && (
|
||||
<Empty width={648} sx={{ marginTop: '60px' }} height={300} />
|
||||
@@ -89,42 +87,50 @@ export const MembersPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||
<StyledMemberRoleContainer>
|
||||
{member.accepted
|
||||
? member.type !== 99
|
||||
? 'Member'
|
||||
: 'Workspace Owner'
|
||||
: 'Pending'}
|
||||
? t('Member')
|
||||
: t('Owner')
|
||||
: t('Pending')}
|
||||
</StyledMemberRoleContainer>
|
||||
<StyledMoreVerticalButton>
|
||||
<Menu
|
||||
content={
|
||||
<>
|
||||
<MenuItem
|
||||
onClick={async () => {
|
||||
const confirmRemove = await confirm({
|
||||
title: 'Delete Member?',
|
||||
content: `will delete member`,
|
||||
confirmText: 'Delete',
|
||||
confirmType: 'danger',
|
||||
});
|
||||
{member.type === 99 ? (
|
||||
<></>
|
||||
) : (
|
||||
<Menu
|
||||
content={
|
||||
<>
|
||||
<MenuItem
|
||||
onClick={async () => {
|
||||
const confirmRemove = await confirm({
|
||||
title: t('Delete Member?'),
|
||||
content: t('will delete member'),
|
||||
confirmText: t('Delete'),
|
||||
confirmType: 'danger',
|
||||
});
|
||||
|
||||
if (!confirmRemove) {
|
||||
return;
|
||||
}
|
||||
await removeMember(member.id);
|
||||
toast(`${user.name} has been removed`);
|
||||
}}
|
||||
icon={<TrashIcon />}
|
||||
>
|
||||
Delete
|
||||
</MenuItem>
|
||||
</>
|
||||
}
|
||||
placement="bottom-end"
|
||||
disablePortal={true}
|
||||
>
|
||||
<IconButton>
|
||||
<MoreVerticalIcon />
|
||||
</IconButton>
|
||||
</Menu>
|
||||
if (!confirmRemove) {
|
||||
return;
|
||||
}
|
||||
await removeMember(member.id);
|
||||
toast(
|
||||
t('Member has been removed', {
|
||||
name: user.name,
|
||||
})
|
||||
);
|
||||
}}
|
||||
icon={<TrashIcon />}
|
||||
>
|
||||
{t('Delete')}
|
||||
</MenuItem>
|
||||
</>
|
||||
}
|
||||
placement="bottom-end"
|
||||
disablePortal={true}
|
||||
>
|
||||
<IconButton>
|
||||
<MoreVerticalIcon />
|
||||
</IconButton>
|
||||
</Menu>
|
||||
)}
|
||||
</StyledMoreVerticalButton>
|
||||
</StyledMemberListItem>
|
||||
);
|
||||
@@ -159,11 +165,14 @@ export const MembersPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledMemberWarp>
|
||||
{t('Collaboration Description')}
|
||||
<StyledPublishExplanation>
|
||||
<EnableWorkspaceButton></EnableWorkspaceButton>
|
||||
</StyledPublishExplanation>
|
||||
</StyledMemberWarp>
|
||||
<Wrapper
|
||||
style={{
|
||||
fontWeight: '500',
|
||||
fontSize: '18px',
|
||||
}}
|
||||
>
|
||||
<Wrapper marginBottom="32px">{t('Collaboration Description')}</Wrapper>
|
||||
<EnableWorkspaceButton />
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { styled } from '@/styles';
|
||||
import MuiAvatar from '@mui/material/Avatar';
|
||||
import { MuiAvatar } from '@/ui/mui';
|
||||
|
||||
export const StyledMemberTitleContainer = styled('li')(() => {
|
||||
return {
|
||||
@@ -97,24 +97,3 @@ export const StyledMoreVerticalButton = styled('button')(() => {
|
||||
paddingRight: '48px',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledPublishExplanation = styled('div')(() => {
|
||||
return {
|
||||
paddingRight: '48px',
|
||||
fontWeight: '500',
|
||||
fontSize: '18px',
|
||||
lineHeight: '26px',
|
||||
flex: 1,
|
||||
marginTop: '64px',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledMemberWarp = styled('div')(() => {
|
||||
return {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: '0 0 48px 0',
|
||||
fontWeight: '500',
|
||||
fontSize: '18px',
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { styled } from '@/styles';
|
||||
|
||||
import { FlexWrapper } from '@/ui/layout';
|
||||
export const StyledSettingContainer = styled('div')(() => {
|
||||
return {
|
||||
display: 'flex',
|
||||
@@ -13,41 +13,19 @@ export const StyledSettingContainer = styled('div')(() => {
|
||||
export const StyledSettingSidebar = styled('div')(() => {
|
||||
{
|
||||
return {
|
||||
// height: '48px',
|
||||
marginTop: '50px',
|
||||
marginTop: '48px',
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
export const StyledSettingContent = styled('div')(() => {
|
||||
return {
|
||||
overflow: 'hidden',
|
||||
overflow: 'auto',
|
||||
flex: 1,
|
||||
paddingTop: '48px',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledSetting = styled('div')(({ theme }) => {
|
||||
{
|
||||
return {
|
||||
width: '236px',
|
||||
background: theme.mode === 'dark' ? '#272727' : '#FBFBFC',
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
export const StyledSettingSidebarHeader = styled('div')(() => {
|
||||
{
|
||||
return {
|
||||
fontWeight: '500',
|
||||
fontSize: '18px',
|
||||
lineHeight: '26px',
|
||||
textAlign: 'left',
|
||||
marginTop: '37px',
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
export const StyledSettingTabContainer = styled('ul')(() => {
|
||||
{
|
||||
return {
|
||||
@@ -65,8 +43,8 @@ export const WorkspaceSettingTagItem = styled('li')<{ isActive?: boolean }>(
|
||||
height: '34px',
|
||||
color: isActive ? theme.colors.primaryColor : theme.colors.textColor,
|
||||
fontWeight: '500',
|
||||
fontSize: theme.font.base,
|
||||
lineHeight: theme.font.lineHeightBase,
|
||||
fontSize: theme.font.h6,
|
||||
lineHeight: theme.font.lineHeight,
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.15s ease',
|
||||
borderBottom: `2px solid ${
|
||||
@@ -78,79 +56,60 @@ export const WorkspaceSettingTagItem = styled('li')<{ isActive?: boolean }>(
|
||||
}
|
||||
);
|
||||
|
||||
export const StyledSettingTagIconContainer = styled('div')(() => {
|
||||
export const StyledSettingKey = styled.div(({ theme }) => {
|
||||
return {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledSettingH2 = styled('h2')<{
|
||||
marginTop?: number;
|
||||
marginBottom?: number;
|
||||
}>(({ marginTop, marginBottom, theme }) => {
|
||||
return {
|
||||
fontWeight: '500',
|
||||
width: '140px',
|
||||
textAlign: 'right',
|
||||
fontSize: theme.font.base,
|
||||
lineHeight: theme.font.lineHeightBase,
|
||||
marginTop: marginTop ? `${marginTop}px` : '0px',
|
||||
marginBottom: marginBottom ? `${marginBottom}px` : '0px',
|
||||
fontWeight: 500,
|
||||
marginRight: '56px',
|
||||
flexShrink: 0,
|
||||
};
|
||||
});
|
||||
export const StyledRow = styled(FlexWrapper)(() => {
|
||||
return {
|
||||
marginBottom: '32px',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledPublishExplanation = styled('div')(() => {
|
||||
return {
|
||||
paddingRight: '48px',
|
||||
fontWeight: '500',
|
||||
fontSize: '18px',
|
||||
lineHeight: '26px',
|
||||
marginBottom: '22px',
|
||||
};
|
||||
});
|
||||
export const StyledWorkspaceName = styled('span')(() => {
|
||||
export const StyledWorkspaceName = styled('span')(({ theme }) => {
|
||||
return {
|
||||
fontWeight: '400',
|
||||
fontSize: '18px',
|
||||
lineHeight: '26px',
|
||||
};
|
||||
});
|
||||
export const StyledWorkspaceType = styled('span')(() => {
|
||||
return {
|
||||
fontWeight: '500',
|
||||
fontSize: '18px',
|
||||
lineHeight: '26px',
|
||||
fontSize: theme.font.h6,
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledPublishCopyContainer = styled('div')(() => {
|
||||
export const StyledEmail = styled('span')(() => {
|
||||
return {
|
||||
marginTop: '12px',
|
||||
marginBottom: '20px',
|
||||
paddingTop: '20px',
|
||||
flex: 1,
|
||||
};
|
||||
});
|
||||
export const StyledStopPublishContainer = styled('div')(() => {
|
||||
return {
|
||||
textAlign: 'center',
|
||||
color: '#E8178A',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledCopyButtonContainer = styled('div')(() => {
|
||||
return {
|
||||
marginTop: '64px',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledPublishContent = styled('div')(() => {
|
||||
return {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyleAsync = styled('div')(() => {
|
||||
return {
|
||||
marginTop: '64px',
|
||||
};
|
||||
});
|
||||
// export const StyledDownloadCard = styled.div<{ active?: boolean }>(
|
||||
// ({ theme, active }) => {
|
||||
// return {
|
||||
// width: '240px',
|
||||
// height: '86px',
|
||||
// border: '1px solid',
|
||||
// borderColor: active
|
||||
// ? theme.colors.primaryColor
|
||||
// : theme.colors.borderColor,
|
||||
// borderRadius: '10px',
|
||||
// padding: '8px 12px',
|
||||
// position: 'relative',
|
||||
// ':not(:last-of-type)': {
|
||||
// marginRight: '24px',
|
||||
// },
|
||||
// svg: {
|
||||
// display: active ? 'block' : 'none',
|
||||
// ...positionAbsolute({ top: '-12px', right: '-12px' }),
|
||||
// },
|
||||
// };
|
||||
// }
|
||||
// );
|
||||
// export const StyledDownloadCardDes = styled.div(({ theme }) => {
|
||||
// return {
|
||||
// fontSize: theme.font.sm,
|
||||
// color: theme.colors.iconColor,
|
||||
// };
|
||||
// });
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import MuiAvatar from '@mui/material/Avatar';
|
||||
import { MuiAvatar } from '@/ui/mui';
|
||||
import { styled } from '@/styles';
|
||||
|
||||
export const WorkspaceItemWrapper = styled('div')(({ theme }) => ({
|
||||
|
||||
+2
-2
@@ -32,13 +32,13 @@ export const WorkspaceSelector = () => {
|
||||
>
|
||||
<WorkspaceUnitAvatar
|
||||
size={28}
|
||||
name={currentWorkspace?.name ?? 'AFFiNE'}
|
||||
name={currentWorkspace?.name ?? 'AFFiNE Test'}
|
||||
workspaceUnit={currentWorkspace}
|
||||
/>
|
||||
</div>
|
||||
</Avatar>
|
||||
<WorkspaceName data-testid="workspace-name">
|
||||
{currentWorkspace?.name ?? 'AFFiNE'}
|
||||
{currentWorkspace?.name ?? 'AFFiNE Test'}
|
||||
</WorkspaceName>
|
||||
</SelectorWrapper>
|
||||
<WorkspaceModal
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import MuiAvatar from '@mui/material/Avatar';
|
||||
import { MuiAvatar } from '@/ui/mui';
|
||||
import { styled } from '@/styles';
|
||||
import { StyledPopperContainer } from '@/ui/shared/Container';
|
||||
|
||||
|
||||
@@ -11,18 +11,17 @@ import {
|
||||
StyledSubListItem,
|
||||
} from './style';
|
||||
import { Arrow } from './icons';
|
||||
import Collapse from '@mui/material/Collapse';
|
||||
import {
|
||||
ArrowDownIcon,
|
||||
SearchIcon,
|
||||
AllPagesIcon,
|
||||
FavouritesIcon,
|
||||
ImportIcon,
|
||||
TrashIcon,
|
||||
AddIcon,
|
||||
SettingsIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import Link from 'next/link';
|
||||
import { MuiCollapse } from '@/ui/mui';
|
||||
import { Tooltip } from '@/ui/tooltip';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
@@ -39,7 +38,7 @@ const FavoriteList = ({ showList }: { showList: boolean }) => {
|
||||
const { t } = useTranslation();
|
||||
const favoriteList = pageList.filter(p => p.favorite && !p.trash);
|
||||
return (
|
||||
<Collapse in={showList}>
|
||||
<MuiCollapse in={showList}>
|
||||
{favoriteList.map((pageMeta, index) => {
|
||||
const active = router.query.pageId === pageMeta.id;
|
||||
return (
|
||||
@@ -61,11 +60,11 @@ const FavoriteList = ({ showList }: { showList: boolean }) => {
|
||||
{favoriteList.length === 0 && (
|
||||
<StyledSubListItem disable={true}>{t('No item')}</StyledSubListItem>
|
||||
)}
|
||||
</Collapse>
|
||||
</MuiCollapse>
|
||||
);
|
||||
};
|
||||
export const WorkSpaceSliderBar = () => {
|
||||
const { triggerQuickSearchModal, triggerImportModal } = useModal();
|
||||
const { triggerQuickSearchModal } = useModal();
|
||||
const [showSubFavorite, setShowSubFavorite] = useState(true);
|
||||
const { currentWorkspace } = useAppState();
|
||||
const { openPage, createPage } = usePageHelper();
|
||||
@@ -161,14 +160,14 @@ export const WorkSpaceSliderBar = () => {
|
||||
setShowWorkspaceSetting(false);
|
||||
}}
|
||||
/> */}
|
||||
|
||||
<StyledListItem
|
||||
{/* TODO: will finish the feature next version */}
|
||||
{/* <StyledListItem
|
||||
onClick={() => {
|
||||
triggerImportModal();
|
||||
}}
|
||||
>
|
||||
<ImportIcon /> {t('Import')}
|
||||
</StyledListItem>
|
||||
</StyledListItem> */}
|
||||
|
||||
<Link href={{ pathname: paths.trash }}>
|
||||
<StyledListItem active={router.asPath === paths.trash}>
|
||||
|
||||
@@ -4,9 +4,9 @@ import Link from 'next/link';
|
||||
export const StyledSliderBar = styled.div<{ show: boolean }>(
|
||||
({ theme, show }) => {
|
||||
return {
|
||||
width: show ? '320px' : '0',
|
||||
width: show ? '256px' : '0',
|
||||
height: '100vh',
|
||||
background: theme.mode === 'dark' ? '#272727' : '#FBFBFC',
|
||||
background: theme.mode === 'dark' ? '#272727' : '#F9F9FB',
|
||||
boxShadow: theme.shadow.modal,
|
||||
transition: 'width .15s, padding .15s',
|
||||
position: 'relative',
|
||||
@@ -58,7 +58,6 @@ export const StyledListItem = styled.div<{
|
||||
width: '296px',
|
||||
height: '32px',
|
||||
marginTop: '12px',
|
||||
fontSize: theme.font.sm,
|
||||
color: active ? theme.colors.primaryColor : theme.colors.popoverColor,
|
||||
paddingLeft: '12px',
|
||||
borderRadius: '5px',
|
||||
@@ -122,9 +121,8 @@ export const StyledSubListItem = styled.button<{
|
||||
width: '296px',
|
||||
height: '32px',
|
||||
marginTop: '4px',
|
||||
fontSize: theme.font.sm,
|
||||
color: disable
|
||||
? theme.colors.iconColor
|
||||
? theme.colors.disableColor
|
||||
: active
|
||||
? theme.colors.primaryColor
|
||||
: theme.colors.popoverColor,
|
||||
|
||||
@@ -38,7 +38,7 @@ export const useEnsureWorkspace = () => {
|
||||
// }
|
||||
const workspaceId =
|
||||
(router.query.workspaceId as string) || workspaceList[0]?.id;
|
||||
loadWorkspace(workspaceId).finally(() => {
|
||||
loadWorkspace.current(workspaceId).finally(() => {
|
||||
setWorkspaceLoaded(true);
|
||||
setActiveWorkspaceId(activeWorkspaceId);
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { uuidv4 } from '@blocksuite/store';
|
||||
import { uuidv4, Workspace } from '@blocksuite/store';
|
||||
import { QueryContent } from '@blocksuite/store/dist/workspace/search';
|
||||
import { PageMeta, useAppState } from '@/providers/app-state-provider';
|
||||
import { EditorContainer } from '@blocksuite/editor';
|
||||
@@ -20,7 +20,10 @@ export type EditorHandlers = {
|
||||
toggleDeletePage: (pageId: string) => Promise<boolean>;
|
||||
toggleFavoritePage: (pageId: string) => Promise<boolean>;
|
||||
permanentlyDeletePage: (pageId: string) => void;
|
||||
search: (query: QueryContent) => Map<string, string | undefined>;
|
||||
search: (
|
||||
query: QueryContent,
|
||||
workspace?: Workspace
|
||||
) => Map<string, string | undefined>;
|
||||
// changeEditorMode: (pageId: string) => void;
|
||||
changePageMode: (
|
||||
pageId: string,
|
||||
@@ -83,9 +86,16 @@ export const usePageHelper = (): EditorHandlers => {
|
||||
});
|
||||
return trash;
|
||||
},
|
||||
search: (query: QueryContent) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return currentWorkspace!.blocksuiteWorkspace!.search(query);
|
||||
search: (query: QueryContent, workspace?: Workspace) => {
|
||||
if (workspace) {
|
||||
return workspace.search(query);
|
||||
}
|
||||
if (currentWorkspace) {
|
||||
if (currentWorkspace.blocksuiteWorkspace) {
|
||||
return currentWorkspace.blocksuiteWorkspace.search(query);
|
||||
}
|
||||
}
|
||||
return new Map();
|
||||
},
|
||||
changePageMode: async (pageId, mode) => {
|
||||
const pageMeta = getPageMeta(currentWorkspace, pageId);
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { useConfirm } from '@/providers/ConfirmProvider';
|
||||
import { toast } from '@/ui/toast';
|
||||
import { WorkspaceUnit } from '@affine/datacenter';
|
||||
import router from 'next/router';
|
||||
|
||||
export const useWorkspaceHelper = () => {
|
||||
const { confirm } = useConfirm();
|
||||
const { dataCenter, currentWorkspace, user, login } = useAppState();
|
||||
const { dataCenter, currentWorkspace } = useAppState();
|
||||
const createWorkspace = async (name: string) => {
|
||||
const workspaceInfo = await dataCenter.createWorkspace({
|
||||
name: name,
|
||||
@@ -31,33 +27,11 @@ export const useWorkspaceHelper = () => {
|
||||
await dataCenter.updateWorkspaceMeta({ name }, workspace);
|
||||
}
|
||||
if (avatarBlob) {
|
||||
// const blobId = await dataCenter.setBlob(workspace, avatarBlob);
|
||||
// await dataCenter.updateWorkspaceMeta({ avatar: blobId }, workspace);
|
||||
const blobId = await dataCenter.setBlob(workspace, avatarBlob);
|
||||
await dataCenter.updateWorkspaceMeta({ avatar: blobId }, workspace);
|
||||
}
|
||||
};
|
||||
|
||||
const enableWorkspace = async () => {
|
||||
confirm({
|
||||
title: 'Enable AFFiNE Cloud?',
|
||||
content: `If enabled, the data in this workspace will be backed up and synchronized via AFFiNE Cloud.`,
|
||||
confirmText: user ? 'Enable' : 'Sign in and Enable',
|
||||
cancelText: 'Skip',
|
||||
confirmType: 'primary',
|
||||
buttonDirection: 'column',
|
||||
}).then(async confirm => {
|
||||
if (confirm && currentWorkspace) {
|
||||
if (!user) {
|
||||
await login();
|
||||
}
|
||||
const workspace = await dataCenter.enableWorkspaceCloud(
|
||||
currentWorkspace
|
||||
);
|
||||
workspace && router.push(`/workspace/${workspace.id}/setting`);
|
||||
toast('Enabled success');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const deleteWorkSpace = async () => {
|
||||
currentWorkspace && (await dataCenter.deleteWorkspace(currentWorkspace.id));
|
||||
};
|
||||
@@ -77,7 +51,6 @@ export const useWorkspaceHelper = () => {
|
||||
createWorkspace,
|
||||
publishWorkspace,
|
||||
updateWorkspace,
|
||||
enableWorkspace,
|
||||
deleteWorkSpace,
|
||||
leaveWorkSpace,
|
||||
acceptInvite,
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
export const AlreadyJoined = () => {
|
||||
return (
|
||||
<div>
|
||||
<p>
|
||||
You are already a member of <code>Workspace name</code>
|
||||
</p>
|
||||
<button>Open Workspace</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,11 +0,0 @@
|
||||
export const Confirm = () => {
|
||||
return (
|
||||
<div>
|
||||
<p>Inviter name</p>
|
||||
<p>
|
||||
invite you to join in <code>Workspace name</code>
|
||||
</p>
|
||||
<button>Join</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,8 +0,0 @@
|
||||
export const LinkExpired = () => {
|
||||
return (
|
||||
<div>
|
||||
<h1>The current invitation link has expired.</h1>
|
||||
<a href={location.origin}>Back to home</a>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,21 +0,0 @@
|
||||
import { useRouter } from 'next/router';
|
||||
import { AlreadyJoined } from './AlreadyJoined';
|
||||
import { Confirm } from './Confirm';
|
||||
import { LinkExpired } from './LinkExpired';
|
||||
|
||||
export const ConfirmInvitation = () => {
|
||||
const router = useRouter();
|
||||
// Temporary code. The code should be returned by request.
|
||||
const { code } = router.query;
|
||||
const Component = {
|
||||
'-1': LinkExpired,
|
||||
0: Confirm,
|
||||
1: AlreadyJoined,
|
||||
}[code as string];
|
||||
return (
|
||||
<div>
|
||||
<h1>Confirm Invitation</h1>
|
||||
{Component ? <Component /> : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -67,15 +67,20 @@ const App = ({ Component, pageProps }: AppPropsWithLayout) => {
|
||||
};
|
||||
|
||||
const AppDefender = ({ children }: PropsWithChildren) => {
|
||||
const { synced } = useAppState();
|
||||
const router = useRouter();
|
||||
const { synced } = useAppState();
|
||||
|
||||
useEffect(() => {
|
||||
if (router.pathname === '/') {
|
||||
if (router.asPath === '/') {
|
||||
router.replace('/workspace');
|
||||
}
|
||||
}, [router]);
|
||||
|
||||
// if you visit /404, you will see the children directly
|
||||
if (router.route === '/404') {
|
||||
return <div>{children}</div>;
|
||||
}
|
||||
|
||||
return <div>{synced ? children : <PageLoading />}</div>;
|
||||
};
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ const Affine = () => {
|
||||
<div>hi</div>
|
||||
</Modal>
|
||||
<Loading />
|
||||
|
||||
<Button
|
||||
icon={<FavouritedIcon />}
|
||||
onClick={() => {
|
||||
@@ -48,13 +47,15 @@ const Affine = () => {
|
||||
<Button icon={<FavouritedIcon />} type={'primary'}>
|
||||
click me!
|
||||
</Button>
|
||||
<Button icon={<FavouritedIcon />} type={'light'}>
|
||||
click me!
|
||||
</Button>
|
||||
<Button icon={<FavouritedIcon />} type={'warning'}>
|
||||
click me!
|
||||
</Button>
|
||||
<Button icon={<FavouritedIcon />} type={'danger'}>
|
||||
click me!
|
||||
</Button>
|
||||
|
||||
<Button icon={<FavouritedIcon />}></Button>
|
||||
<Button icon={<FavouritedIcon />} shape="round"></Button>
|
||||
<Button loading={true}></Button>
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import type { NextPage } from 'next';
|
||||
import { ConfirmInvitation } from '../pages-content/ConfirmInvitationPage';
|
||||
|
||||
const ConfirmInvitationPage: NextPage = () => {
|
||||
return <ConfirmInvitation />;
|
||||
};
|
||||
|
||||
export default ConfirmInvitationPage;
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
|
||||
import { styled } from '@/styles';
|
||||
import { Empty } from '@/ui/empty';
|
||||
// import { Avatar } from '@mui/material';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { PageLoading } from '@/components/loading';
|
||||
|
||||
// const User = ({ name, avatar }: { name: string; avatar?: string }) => {
|
||||
// return (
|
||||
@@ -19,19 +19,29 @@ import { useEffect, useState } from 'react';
|
||||
// };
|
||||
|
||||
export default function DevPage() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const router = useRouter();
|
||||
const [successInvited, setSuccessInvited] = useState<boolean>(false);
|
||||
const { acceptInvite } = useWorkspaceHelper();
|
||||
useEffect(() => {
|
||||
router.query.invite_code &&
|
||||
acceptInvite(router.query.invite_code as string).then(data => {
|
||||
if (data && data.accepted) {
|
||||
setSuccessInvited(true);
|
||||
}
|
||||
});
|
||||
if (router.query.invite_code) {
|
||||
acceptInvite(router.query.invite_code as string)
|
||||
.then(data => {
|
||||
if (data && data.accepted) {
|
||||
setSuccessInvited(true);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [router, acceptInvite]);
|
||||
|
||||
return (
|
||||
return loading ? (
|
||||
<PageLoading />
|
||||
) : (
|
||||
<Invited>
|
||||
<div>
|
||||
<Empty width={310} height={310}></Empty>
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import { ReactElement, useEffect, useState } from 'react';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import type { NextPageWithLayout } from '../..//_app';
|
||||
import { styled } from '@/styles';
|
||||
import { displayFlex, styled } from '@/styles';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useRouter } from 'next/router';
|
||||
import { Page as PageStore, Workspace } from '@blocksuite/store';
|
||||
import { PageLoading } from '@/components/loading';
|
||||
import { Breadcrumbs } from '@/ui/breadcrumbs';
|
||||
import { IconButton } from '@/ui/button';
|
||||
import NextLink from 'next/link';
|
||||
import { PaperIcon, SearchIcon } from '@blocksuite/icons';
|
||||
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
|
||||
const DynamicBlocksuite = dynamic(() => import('@/components/editor'), {
|
||||
ssr: false,
|
||||
@@ -16,12 +22,16 @@ const Page: NextPageWithLayout = () => {
|
||||
const { dataCenter } = useAppState();
|
||||
const router = useRouter();
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
const [workspaceName, setWorkspaceName] = useState('');
|
||||
const [pageTitle, setPageTitle] = useState('');
|
||||
const { triggerQuickSearchModal } = useModal();
|
||||
|
||||
useEffect(() => {
|
||||
dataCenter
|
||||
.loadPublicWorkspace(router.query.workspaceId as string)
|
||||
.then(data => {
|
||||
if (data && data.blocksuiteWorkspace) {
|
||||
setWorkspaceName(data.blocksuiteWorkspace?.meta.name as string);
|
||||
if (data.blocksuiteWorkspace) {
|
||||
setWorkspace(data.blocksuiteWorkspace);
|
||||
if (
|
||||
router.query.pageId &&
|
||||
@@ -32,6 +42,7 @@ const Page: NextPageWithLayout = () => {
|
||||
const page = data.blocksuiteWorkspace.getPage(
|
||||
router.query.pageId as string
|
||||
);
|
||||
page && setPageTitle(page.meta.title);
|
||||
page && setPage(page);
|
||||
} else {
|
||||
router.push('/404');
|
||||
@@ -46,6 +57,30 @@ const Page: NextPageWithLayout = () => {
|
||||
<>
|
||||
{!loaded && <PageLoading />}
|
||||
<PageContainer>
|
||||
<NavContainer>
|
||||
<Breadcrumbs>
|
||||
<StyledBreadcrumbs
|
||||
href={`/public-workspace/${router.query.workspaceId}`}
|
||||
>
|
||||
<WorkspaceUnitAvatar size={24} name={workspaceName} />
|
||||
<span>{workspaceName}</span>
|
||||
</StyledBreadcrumbs>
|
||||
<StyledBreadcrumbs
|
||||
href={`/public-workspace/${router.query.workspaceId}/${router.query.pageId}`}
|
||||
>
|
||||
<PaperIcon fontSize={24} />
|
||||
<span>{pageTitle ? pageTitle : 'Untitled'}</span>
|
||||
</StyledBreadcrumbs>
|
||||
</Breadcrumbs>
|
||||
<SearchButton
|
||||
onClick={() => {
|
||||
triggerQuickSearchModal();
|
||||
}}
|
||||
>
|
||||
<SearchIcon />
|
||||
</SearchButton>
|
||||
</NavContainer>
|
||||
|
||||
{workspace && page && (
|
||||
<DynamicBlocksuite
|
||||
page={page}
|
||||
@@ -69,9 +104,43 @@ export default Page;
|
||||
|
||||
export const PageContainer = styled.div(({ theme }) => {
|
||||
return {
|
||||
height: 'calc(100vh)',
|
||||
padding: '78px 72px',
|
||||
height: '100vh',
|
||||
overflowY: 'auto',
|
||||
backgroundColor: theme.colors.pageBackground,
|
||||
};
|
||||
});
|
||||
export const NavContainer = styled.div(({ theme }) => {
|
||||
return {
|
||||
width: '100vw',
|
||||
padding: '0 12px',
|
||||
height: '60px',
|
||||
...displayFlex('start', 'center'),
|
||||
backgroundColor: theme.colors.pageBackground,
|
||||
};
|
||||
});
|
||||
export const StyledBreadcrumbs = styled(NextLink)(({ theme }) => {
|
||||
return {
|
||||
flex: 1,
|
||||
...displayFlex('center', 'center'),
|
||||
paddingLeft: '12px',
|
||||
span: {
|
||||
padding: '0 12px',
|
||||
fontSize: theme.font.base,
|
||||
lineHeight: theme.font.lineHeight,
|
||||
},
|
||||
':hover': { color: theme.colors.primaryColor },
|
||||
transition: 'all .15s',
|
||||
':visited': {
|
||||
color: theme.colors.popoverColor,
|
||||
':hover': { color: theme.colors.primaryColor },
|
||||
},
|
||||
};
|
||||
});
|
||||
export const SearchButton = styled(IconButton)(({ theme }) => {
|
||||
return {
|
||||
color: theme.colors.iconColor,
|
||||
fontSize: '24px',
|
||||
marginLeft: 'auto',
|
||||
padding: '0 24px',
|
||||
};
|
||||
});
|
||||
|
||||
@@ -2,16 +2,28 @@ import { PageList } from '@/components/page-list';
|
||||
import { ReactElement, useEffect, useState } from 'react';
|
||||
import { PageMeta, useAppState } from '@/providers/app-state-provider';
|
||||
import { useRouter } from 'next/router';
|
||||
import { PageContainer } from './[pageId]';
|
||||
import {
|
||||
PageContainer,
|
||||
NavContainer,
|
||||
StyledBreadcrumbs,
|
||||
SearchButton,
|
||||
} from './[pageId]';
|
||||
import { Breadcrumbs } from '@/ui/breadcrumbs';
|
||||
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
|
||||
import { SearchIcon } from '@blocksuite/icons';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
const All = () => {
|
||||
const { dataCenter } = useAppState();
|
||||
const router = useRouter();
|
||||
const [pageList, setPageList] = useState<PageMeta[]>([]);
|
||||
const [workspaceName, setWorkspaceName] = useState('');
|
||||
const { triggerQuickSearchModal } = useModal();
|
||||
useEffect(() => {
|
||||
dataCenter
|
||||
.loadPublicWorkspace(router.query.workspaceId as string)
|
||||
.then(data => {
|
||||
setPageList(data.blocksuiteWorkspace?.meta.pageMetas as PageMeta[]);
|
||||
setWorkspaceName(data.blocksuiteWorkspace?.meta.name as string);
|
||||
})
|
||||
.catch(() => {
|
||||
router.push('/404');
|
||||
@@ -20,6 +32,23 @@ const All = () => {
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<NavContainer>
|
||||
<Breadcrumbs>
|
||||
<StyledBreadcrumbs
|
||||
href={`/public-workspace/${router.query.workspaceId}`}
|
||||
>
|
||||
<WorkspaceUnitAvatar size={24} name={workspaceName} />
|
||||
<span>{workspaceName}</span>
|
||||
</StyledBreadcrumbs>
|
||||
</Breadcrumbs>
|
||||
<SearchButton
|
||||
onClick={() => {
|
||||
triggerQuickSearchModal();
|
||||
}}
|
||||
>
|
||||
<SearchIcon />
|
||||
</SearchButton>
|
||||
</NavContainer>
|
||||
<PageList
|
||||
pageList={pageList.filter(p => !p.trash)}
|
||||
showFavoriteTag={false}
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
debug-menu {
|
||||
display: none !important;
|
||||
}
|
||||
.affine-block-children-container.edgeless {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.affine-default-page-block-title-container {
|
||||
margin-top: 78px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
.affine-editor-container {
|
||||
background: transparent !important;
|
||||
|
||||
.affine-default-page-block-container {
|
||||
width: 686px !important;
|
||||
}
|
||||
|
||||
@@ -4,9 +4,11 @@ import { PageListHeader } from '@/components/header';
|
||||
import { ReactElement } from 'react';
|
||||
import WorkspaceLayout from '@/components/workspace-layout';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { PageMeta, useAppState } from '@/providers/app-state-provider';
|
||||
const All = () => {
|
||||
const { pageList } = useAppState();
|
||||
const { currentWorkspace } = useAppState();
|
||||
const pageList = (currentWorkspace?.blocksuiteWorkspace?.meta.pageMetas ||
|
||||
[]) as PageMeta[];
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -10,8 +10,8 @@ import {
|
||||
GeneralPage,
|
||||
MembersPage,
|
||||
PublishPage,
|
||||
ExportPage,
|
||||
SyncPage,
|
||||
ExportPage,
|
||||
} from '@/components/workspace-setting';
|
||||
import { SettingsIcon } from '@blocksuite/icons';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
@@ -20,41 +20,37 @@ import { WorkspaceUnit } from '@affine/datacenter';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { PageListHeader } from '@/components/header';
|
||||
|
||||
type TabNames = 'General' | 'Sync' | 'Collaboration' | 'Publish' | 'Export';
|
||||
|
||||
const tabMap: {
|
||||
name: TabNames;
|
||||
panelRender: (workspace: WorkspaceUnit) => ReactNode;
|
||||
}[] = [
|
||||
{
|
||||
name: 'General',
|
||||
panelRender: workspace => <GeneralPage workspace={workspace} />,
|
||||
},
|
||||
{
|
||||
name: 'Sync',
|
||||
panelRender: workspace => <SyncPage workspace={workspace} />,
|
||||
},
|
||||
{
|
||||
name: 'Collaboration',
|
||||
panelRender: workspace => <MembersPage workspace={workspace} />,
|
||||
},
|
||||
{
|
||||
name: 'Publish',
|
||||
panelRender: workspace => <PublishPage workspace={workspace} />,
|
||||
},
|
||||
|
||||
{
|
||||
name: 'Export',
|
||||
panelRender: workspace => <ExportPage workspace={workspace} />,
|
||||
},
|
||||
];
|
||||
|
||||
const WorkspaceSetting = () => {
|
||||
const useTabMap = () => {
|
||||
const { t } = useTranslation();
|
||||
const { currentWorkspace, isOwner } = useAppState();
|
||||
|
||||
const [activeTab, setActiveTab] = useState<TabNames>(tabMap[0].name);
|
||||
const handleTabChange = (tab: TabNames) => {
|
||||
const { isOwner } = useAppState();
|
||||
const tabMap: {
|
||||
name: string;
|
||||
panelRender: (workspace: WorkspaceUnit) => ReactNode;
|
||||
}[] = [
|
||||
{
|
||||
name: t('General'),
|
||||
panelRender: workspace => <GeneralPage workspace={workspace} />,
|
||||
},
|
||||
{
|
||||
name: t('Sync'),
|
||||
panelRender: workspace => <SyncPage workspace={workspace} />,
|
||||
},
|
||||
{
|
||||
name: t('Collaboration'),
|
||||
panelRender: workspace => <MembersPage workspace={workspace} />,
|
||||
},
|
||||
{
|
||||
name: t('Publish'),
|
||||
panelRender: workspace => <PublishPage workspace={workspace} />,
|
||||
},
|
||||
// TODO: next version will finish this feature
|
||||
{
|
||||
name: t('Export'),
|
||||
panelRender: workspace => <ExportPage workspace={workspace} />,
|
||||
},
|
||||
];
|
||||
const [activeTab, setActiveTab] = useState<string>(tabMap[0].name);
|
||||
const handleTabChange = (tab: string) => {
|
||||
setActiveTab(tab);
|
||||
};
|
||||
|
||||
@@ -62,7 +58,7 @@ const WorkspaceSetting = () => {
|
||||
tab => tab.name === activeTab
|
||||
)?.panelRender;
|
||||
let tableArr: {
|
||||
name: TabNames;
|
||||
name: string;
|
||||
panelRender: (workspace: WorkspaceUnit) => ReactNode;
|
||||
}[] = tabMap;
|
||||
if (!isOwner) {
|
||||
@@ -73,6 +69,14 @@ const WorkspaceSetting = () => {
|
||||
},
|
||||
];
|
||||
}
|
||||
return { activeTabPanelRender, tableArr, handleTabChange, activeTab };
|
||||
};
|
||||
|
||||
const WorkspaceSetting = () => {
|
||||
const { t } = useTranslation();
|
||||
const { currentWorkspace } = useAppState();
|
||||
const { activeTabPanelRender, tableArr, handleTabChange, activeTab } =
|
||||
useTabMap();
|
||||
return (
|
||||
<>
|
||||
<StyledSettingContainer>
|
||||
|
||||
@@ -10,16 +10,21 @@ import {
|
||||
import { createDefaultWorkspace } from './utils';
|
||||
import { User } from '@affine/datacenter';
|
||||
|
||||
export interface Disposable {
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
type AppStateContextProps = PropsWithChildren<Record<string, unknown>>;
|
||||
|
||||
export const AppState = createContext<AppStateContext>({} as AppStateContext);
|
||||
|
||||
export const useAppState = () => useContext(AppState);
|
||||
|
||||
export const AppStateProvider = ({
|
||||
children,
|
||||
}: PropsWithChildren<AppStateContextProps>) => {
|
||||
const [appState, setAppState] = useState<AppStateValue>({} as AppStateValue);
|
||||
const [blobState, setBlobState] = useState(false);
|
||||
const [userInfo, setUser] = useState<User | null>({} as User);
|
||||
useEffect(() => {
|
||||
const initState = async () => {
|
||||
const dataCenter = await getDataCenter();
|
||||
@@ -27,10 +32,9 @@ export const AppStateProvider = ({
|
||||
if (dataCenter.workspaces.length === 0) {
|
||||
await createDefaultWorkspace(dataCenter);
|
||||
}
|
||||
|
||||
setUser((await dataCenter.getUserInfo()) || null);
|
||||
setAppState({
|
||||
dataCenter,
|
||||
user: (await dataCenter.getUserInfo()) || null,
|
||||
workspaceList: dataCenter.workspaces,
|
||||
currentWorkspace: null,
|
||||
pageList: [],
|
||||
@@ -90,15 +94,17 @@ export const AppStateProvider = ({
|
||||
});
|
||||
};
|
||||
|
||||
const loadWorkspace = useRef<AppStateFunction['loadWorkspace']>();
|
||||
const loadWorkspace: AppStateFunction['loadWorkspace'] =
|
||||
useRef() as AppStateFunction['loadWorkspace'];
|
||||
loadWorkspace.current = async (workspaceId: string) => {
|
||||
const { dataCenter, workspaceList, currentWorkspace, user } = appState;
|
||||
const { dataCenter, workspaceList, currentWorkspace } = appState;
|
||||
if (!workspaceList.find(v => v.id.toString() === workspaceId)) {
|
||||
return null;
|
||||
}
|
||||
if (workspaceId === currentWorkspace?.id) {
|
||||
return currentWorkspace;
|
||||
}
|
||||
|
||||
const workspace = (await dataCenter.loadWorkspace(workspaceId)) ?? null;
|
||||
let isOwner;
|
||||
if (workspace?.provider === 'local') {
|
||||
@@ -106,8 +112,9 @@ export const AppStateProvider = ({
|
||||
isOwner = true;
|
||||
} else {
|
||||
// We must ensure workspace.owner exists, then ensure id same.
|
||||
isOwner = workspace?.owner && user?.id === workspace.owner.id;
|
||||
isOwner = workspace?.owner && userInfo?.id === workspace.owner.id;
|
||||
}
|
||||
|
||||
const pageList =
|
||||
(workspace?.blocksuiteWorkspace?.meta.pageMetas as PageMeta[]) ?? [];
|
||||
setAppState({
|
||||
@@ -122,6 +129,26 @@ export const AppStateProvider = ({
|
||||
return workspace;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let syncChangeDisposable: Disposable | undefined;
|
||||
const currentWorkspace = appState.currentWorkspace;
|
||||
if (!currentWorkspace) {
|
||||
return;
|
||||
}
|
||||
const getBlobStorage = async () => {
|
||||
const blobStorage = await currentWorkspace?.blocksuiteWorkspace?.blobs;
|
||||
syncChangeDisposable = blobStorage?.signals.onBlobSyncStateChange.on(
|
||||
() => {
|
||||
setBlobState(blobStorage?.uploading);
|
||||
}
|
||||
);
|
||||
};
|
||||
getBlobStorage();
|
||||
return () => {
|
||||
syncChangeDisposable?.dispose();
|
||||
};
|
||||
}, [appState.currentWorkspace]);
|
||||
|
||||
const setEditor: AppStateFunction['setEditor'] =
|
||||
useRef() as AppStateFunction['setEditor'];
|
||||
setEditor.current = editor => {
|
||||
@@ -139,20 +166,13 @@ export const AppStateProvider = ({
|
||||
if (!user) {
|
||||
throw new Error('User info not found');
|
||||
}
|
||||
setAppState({
|
||||
...appState,
|
||||
user,
|
||||
});
|
||||
setUser(user);
|
||||
return user;
|
||||
};
|
||||
|
||||
const logout = async () => {
|
||||
const { dataCenter } = appState;
|
||||
await dataCenter.logout();
|
||||
setAppState({
|
||||
...appState,
|
||||
user: null,
|
||||
});
|
||||
setUser(null);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -161,9 +181,11 @@ export const AppStateProvider = ({
|
||||
...appState,
|
||||
setEditor,
|
||||
loadPage: loadPage.current,
|
||||
loadWorkspace: loadWorkspace.current,
|
||||
loadWorkspace: loadWorkspace,
|
||||
login,
|
||||
logout,
|
||||
blobDataSynced: blobState,
|
||||
user: userInfo,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -16,7 +16,7 @@ export interface PageMeta extends StorePageMeta {
|
||||
|
||||
export type AppStateValue = {
|
||||
dataCenter: DataCenter;
|
||||
user: User | null;
|
||||
user?: User | null;
|
||||
workspaceList: WorkspaceUnit[];
|
||||
currentWorkspace: WorkspaceUnit | null;
|
||||
pageList: PageMeta[];
|
||||
@@ -24,12 +24,15 @@ export type AppStateValue = {
|
||||
editor?: EditorContainer | null;
|
||||
synced: boolean;
|
||||
isOwner?: boolean;
|
||||
blobDataSynced?: boolean;
|
||||
};
|
||||
|
||||
export type AppStateFunction = {
|
||||
setEditor: MutableRefObject<(page: EditorContainer) => void>;
|
||||
|
||||
loadWorkspace: (workspaceId: string) => Promise<WorkspaceUnit | null>;
|
||||
loadWorkspace: MutableRefObject<
|
||||
(workspaceId: string) => Promise<WorkspaceUnit | null>
|
||||
>;
|
||||
loadPage: (pageId: string) => void;
|
||||
|
||||
login: () => Promise<User>;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { DataCenter } from '@affine/datacenter';
|
||||
|
||||
const DEFAULT_WORKSPACE_NAME = 'affine';
|
||||
const DEFAULT_WORKSPACE_NAME = 'AFFiNE Test';
|
||||
|
||||
export const createDefaultWorkspace = async (dataCenter: DataCenter) => {
|
||||
return dataCenter.createWorkspace({
|
||||
|
||||
@@ -2,12 +2,12 @@ import type { CSSProperties } from 'react';
|
||||
|
||||
export const displayFlex = (
|
||||
justifyContent: CSSProperties['justifyContent'] = 'unset',
|
||||
alignItems: CSSProperties['alignContent'] = 'unset',
|
||||
alignItems: CSSProperties['alignItems'] = 'unset',
|
||||
alignContent: CSSProperties['alignContent'] = 'unset'
|
||||
): {
|
||||
display: CSSProperties['display'];
|
||||
justifyContent: CSSProperties['justifyContent'];
|
||||
alignItems: CSSProperties['alignContent'];
|
||||
alignItems: CSSProperties['alignItems'];
|
||||
alignContent: CSSProperties['alignContent'];
|
||||
} => {
|
||||
return {
|
||||
@@ -117,3 +117,29 @@ export const textEllipsis = (lineNum = 1): CSSProperties => {
|
||||
whiteSpace: 'nowrap',
|
||||
};
|
||||
};
|
||||
|
||||
export const positionAbsolute = ({
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
}: {
|
||||
left?: CSSProperties['left'];
|
||||
top?: CSSProperties['top'];
|
||||
right?: CSSProperties['right'];
|
||||
bottom?: CSSProperties['bottom'];
|
||||
}): {
|
||||
position: CSSProperties['position'];
|
||||
left: CSSProperties['left'];
|
||||
top: CSSProperties['top'];
|
||||
right: CSSProperties['right'];
|
||||
bottom: CSSProperties['bottom'];
|
||||
} => {
|
||||
return {
|
||||
position: 'absolute',
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -16,17 +16,20 @@ export const getLightTheme = (
|
||||
|
||||
pageBackground: '#fff',
|
||||
hoverBackground: '#F1F3FF',
|
||||
innerHoverBackground: '#E0E6FF',
|
||||
innerHoverBackground: '#E9E9EC',
|
||||
popoverBackground: '#fff',
|
||||
tooltipBackground: '#6880FF',
|
||||
codeBackground: '#f2f5f9',
|
||||
codeBlockBackground: '#fafbfd',
|
||||
codeBlockBackground: '#F9F9FB',
|
||||
blockHubBackground: '#fbfbfc',
|
||||
blockHubHoverBackground: '#f8f9ff',
|
||||
warningBackground: '#FFF9C7',
|
||||
errorBackground: '#FFDED8',
|
||||
|
||||
textColor: '#3A4C5C',
|
||||
textColor: '#28293D',
|
||||
edgelessTextColor: '#3A4C5C',
|
||||
iconColor: '#888a9e',
|
||||
iconColor: '#555770',
|
||||
handleColor: '#c7c3d9',
|
||||
linkColor: '#6880FF',
|
||||
linkColor2: '#6880FF',
|
||||
linkVisitedColor: '#ABB8FE',
|
||||
@@ -35,28 +38,30 @@ export const getLightTheme = (
|
||||
tooltipColor: '#fff',
|
||||
codeColor: '#517ea6',
|
||||
quoteColor: '#4C6275',
|
||||
placeHolderColor: '#C7C7C7',
|
||||
placeHolderColor: '#888A9E',
|
||||
selectedColor: 'rgba(104, 128, 255, 0.1)',
|
||||
borderColor: '#D0D7E3',
|
||||
disableColor: '#C0C0C0',
|
||||
borderColor: '#E3E2E4',
|
||||
disableColor: '#555770',
|
||||
warningColor: '#906616',
|
||||
errorColor: '#EB4335',
|
||||
lineNumberColor: '#888A9E',
|
||||
lineNumberColor: '#555770',
|
||||
},
|
||||
font: {
|
||||
xs: '12px',
|
||||
sm: '16px',
|
||||
base: '18px',
|
||||
h1: '30px',
|
||||
h2: '28px',
|
||||
h3: '26px',
|
||||
h4: '24px',
|
||||
h5: '22px',
|
||||
h6: '20px',
|
||||
title: '36px',
|
||||
h1: '28px',
|
||||
h2: '26px',
|
||||
h3: '24px',
|
||||
h4: '22px',
|
||||
h5: '20px',
|
||||
h6: '18px',
|
||||
base: '16px',
|
||||
xs: '14px',
|
||||
sm: '12px',
|
||||
|
||||
family: `Avenir Next, Poppins, ${basicFontFamily}`,
|
||||
mono: `Space Mono, ${basicFontFamily}`,
|
||||
lineHeightBase: '26px',
|
||||
numberFamily: `Roboto Mono, ${basicFontFamily}`,
|
||||
codeFamily: `Space Mono, Consolas, Menlo, Monaco, Courier, monospace, ${basicFontFamily}`,
|
||||
lineHeight: 'calc(1em + 8px)',
|
||||
},
|
||||
zIndex: {
|
||||
modal: 1000,
|
||||
@@ -99,12 +104,15 @@ export const getDarkTheme = (
|
||||
? lightTheme.colors.codeBackground
|
||||
: '#505662',
|
||||
codeBlockBackground: '#36383D',
|
||||
blockHubBackground: '#272727',
|
||||
blockHubHoverBackground: '#363636',
|
||||
warningBackground: '#FFF9C7',
|
||||
errorBackground: '#FFDED8',
|
||||
|
||||
textColor: '#fff',
|
||||
edgelessTextColor: '#3A4C5C',
|
||||
iconColor: '#888a9e',
|
||||
handleColor: '#c7c3d9',
|
||||
linkColor: '#7D91FF',
|
||||
linkColor2: '#6880FF',
|
||||
linkVisitedColor: '#505FAB',
|
||||
@@ -148,6 +156,9 @@ export const globalThemeVariables: (
|
||||
'--affine-code-background': theme.colors.codeBackground,
|
||||
'--affine-tooltip-background': theme.colors.tooltipBackground,
|
||||
|
||||
'--affine-block-hub-background': theme.colors.blockHubBackground,
|
||||
'--affine-block-hub-hover-background': theme.colors.blockHubHoverBackground,
|
||||
|
||||
'--affine-text-color': theme.colors.textColor,
|
||||
'--affine-edgeless-text-color': theme.colors.edgelessTextColor,
|
||||
'--affine-link-color': theme.colors.linkColor,
|
||||
@@ -155,6 +166,7 @@ export const globalThemeVariables: (
|
||||
'--affine-link-color2': theme.colors.linkColor2,
|
||||
'--affine-link-visited-color': theme.colors.linkVisitedColor,
|
||||
'--affine-icon-color': theme.colors.iconColor,
|
||||
'--affine-block-handle-color': theme.colors.handleColor,
|
||||
'--affine-popover-color': theme.colors.popoverColor,
|
||||
'--affine-input-color': theme.colors.inputColor,
|
||||
'--affine-code-color': theme.colors.codeColor,
|
||||
@@ -171,16 +183,24 @@ export const globalThemeVariables: (
|
||||
'--affine-popover-shadow': theme.shadow.popover,
|
||||
'--affine-tooltip-shadow': theme.shadow.tooltip,
|
||||
|
||||
'--affine-font-xs': theme.font.xs, // tiny
|
||||
'--affine-font-sm': theme.font.sm, // small
|
||||
'--affine-font-h1': theme.font.h1,
|
||||
'--affine-font-h2': theme.font.h2,
|
||||
'--affine-font-h3': theme.font.h3,
|
||||
'--affine-font-h4': theme.font.h4,
|
||||
'--affine-font-h5': theme.font.h5,
|
||||
'--affine-font-h6': theme.font.h6,
|
||||
'--affine-font-base': theme.font.base,
|
||||
'--affine-line-height-base': theme.font.lineHeightBase,
|
||||
'--affine-font-sm': theme.font.sm, // small
|
||||
'--affine-font-xs': theme.font.xs, // tiny
|
||||
|
||||
'--affine-line-height': theme.font.lineHeight,
|
||||
|
||||
'--affine-z-index-modal': theme.zIndex.modal,
|
||||
'--affine-z-index-popover': theme.zIndex.popover,
|
||||
|
||||
'--affine-font-family': theme.font.family,
|
||||
'--affine-font-mono': theme.font.mono,
|
||||
'--affine-font-number-family': theme.font.numberFamily,
|
||||
'--affine-font-code-family': theme.font.codeFamily,
|
||||
|
||||
'--affine-paragraph-space': theme.space.paragraph,
|
||||
'--affine-popover-radius': theme.radius.popover,
|
||||
|
||||
@@ -26,6 +26,8 @@ export interface AffineTheme {
|
||||
innerHoverBackground: string;
|
||||
codeBackground: string;
|
||||
codeBlockBackground: string;
|
||||
blockHubBackground: string;
|
||||
blockHubHoverBackground: string;
|
||||
warningBackground: string;
|
||||
errorBackground: string;
|
||||
// Use for the page`s text
|
||||
@@ -37,6 +39,7 @@ export interface AffineTheme {
|
||||
linkColor2: string;
|
||||
linkVisitedColor: string;
|
||||
iconColor: string;
|
||||
handleColor: string;
|
||||
popoverColor: string;
|
||||
inputColor: string;
|
||||
tooltipColor: string;
|
||||
@@ -51,20 +54,22 @@ export interface AffineTheme {
|
||||
lineNumberColor: string;
|
||||
};
|
||||
font: {
|
||||
xs: string; // tiny
|
||||
sm: string; // small
|
||||
base: string;
|
||||
title: string;
|
||||
h1: string;
|
||||
h2: string;
|
||||
h3: string;
|
||||
h4: string;
|
||||
h5: string;
|
||||
h6: string;
|
||||
base: string;
|
||||
sm: string; // small
|
||||
xs: string; // tiny
|
||||
|
||||
family: string;
|
||||
mono: string;
|
||||
numberFamily: string;
|
||||
codeFamily: string;
|
||||
|
||||
lineHeightBase: string | number;
|
||||
lineHeight: string | number;
|
||||
};
|
||||
zIndex: {
|
||||
modal: number;
|
||||
@@ -116,17 +121,23 @@ export interface AffineThemeCSSVariables {
|
||||
'--affine-modal-shadow': AffineTheme['shadow']['modal'];
|
||||
'--affine-popover-shadow': AffineTheme['shadow']['popover'];
|
||||
'--affine-tooltip-shadow': AffineTheme['shadow']['tooltip'];
|
||||
|
||||
'--affine-font-xs': AffineTheme['font']['xs']; // tiny
|
||||
'--affine-font-sm': AffineTheme['font']['sm']; // small
|
||||
'--affine-font-h1': AffineTheme['font']['h1'];
|
||||
'--affine-font-h2': AffineTheme['font']['h2'];
|
||||
'--affine-font-h3': AffineTheme['font']['h3'];
|
||||
'--affine-font-h4': AffineTheme['font']['h4'];
|
||||
'--affine-font-h5': AffineTheme['font']['h5'];
|
||||
'--affine-font-h6': AffineTheme['font']['h6'];
|
||||
'--affine-font-base': AffineTheme['font']['base'];
|
||||
'--affine-line-height-base': AffineTheme['font']['lineHeightBase'];
|
||||
'--affine-font-sm': AffineTheme['font']['sm']; // small
|
||||
'--affine-font-xs': AffineTheme['font']['xs']; // tiny
|
||||
'--affine-line-height': AffineTheme['font']['lineHeight'];
|
||||
|
||||
'--affine-z-index-modal': AffineTheme['zIndex']['modal'];
|
||||
'--affine-z-index-popover': AffineTheme['zIndex']['popover'];
|
||||
|
||||
'--affine-font-family': AffineTheme['font']['family'];
|
||||
'--affine-font-mono': AffineTheme['font']['mono'];
|
||||
'--affine-font-number-family': AffineTheme['font']['numberFamily'];
|
||||
'--affine-font-code-family': AffineTheme['font']['codeFamily'];
|
||||
|
||||
'--affine-paragraph-space': AffineTheme['space']['paragraph'];
|
||||
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
We've been working hard on improving the user experience and have made several new features live for you to use.
|
||||
|
||||
If you are looking for **support**, would like to **suggest** your ideas, and keep on top of all the **latest updates**. We suggest you visit the [AFFiNE Community](https://community.affine.pro/home) site.
|
||||
|
||||
Let us know what you think of this latest version.
|
||||
|
||||
**AFFiNE Alpha Downhill brings:**
|
||||
|
||||
We are mainly working on features for pathfinder.
|
||||
|
||||
- Support SlashMenu #629
|
||||
- Support BlockHub + DragHandle #630
|
||||
- Support Whiteboard with basic usability #631
|
||||
- Support bracket autocomplete #632 @lawvs
|
||||
- Support Database #633
|
||||
- Support Account(Google Login) #608 @alt1o @darkskygit
|
||||
- Support AFFiNE Collaboration by AFFiNE Cloud #609 @darkskygit @zuoxiaodong0815
|
||||
- Support Local First Multi Workspace #610 @QiShaoXuan @darkskygit
|
||||
- Support AFFiNE Cloud Sync #611 @alt1o @SaikaSakura @darkskygit
|
||||
- Support Download Workspace by AFFiNE Cloud #612 @darkskygit @alt1o @SaikaSakura
|
||||
- Support Workspace Sharing by AFFiNE Cloud #613 @alt1o @SaikaSakura
|
||||
- Import Markdown #615 @QiShaoXuan @thorseraq
|
||||
- [improvement]: i18n integration #567 @JimmFly
|
||||
- Support DataCEnter @SaikaSakura @alt1o #708
|
||||
@@ -0,0 +1,24 @@
|
||||
We've been working hard on improving the user experience and have made several new features live for you to use.
|
||||
|
||||
If you are looking for **support**, would like to **suggest** your ideas, and keep on top of all the **latest updates**. We suggest you visit the [AFFiNE Community](https://community.affine.pro/home) site.
|
||||
|
||||
Let us know what you think of this latest version.
|
||||
|
||||
**AFFiNE Alpha Downhills brings:**
|
||||
|
||||
We are mainly working on features for pathfinder.
|
||||
|
||||
- [feat] SlashMenu #629
|
||||
- [feat] BlockHub + DragHandle #630
|
||||
- [feat] edgeless with basic usability #631
|
||||
- [feat] bracket autocomplete #632 @lawvs
|
||||
- [feat] database #633
|
||||
- [feat] login with Google #608 @alt1o @darkskygit
|
||||
- [feat] collaboration by AFFiNE Cloud #609 @darkskygit @zuoxiaodong0815
|
||||
- [feat] local first multi workspace #610 @QiShaoXuan @darkskygit
|
||||
- [feat] AFFiNE Cloud sync #611 @alt1o @SaikaSakura @darkskygit
|
||||
- [feat] download workspace by AFFiNE Cloud #612 @darkskygit @alt1o @SaikaSakura
|
||||
- [feat] workspace sharing with AFFiNE Cloud #613 @alt1o @SaikaSakura
|
||||
- [feat] import markdown #615 @QiShaoXuan @thorseraq
|
||||
- [feat] i18n integration #567 @JimmFly
|
||||
- [feat] implement cloud provider for DataCenter @SaikaSakura @alt1o #708
|
||||
@@ -0,0 +1,8 @@
|
||||
import MuiBreadcrumbs from '@mui/material/Breadcrumbs';
|
||||
import { styled } from '@/styles';
|
||||
|
||||
export const Breadcrumbs = styled(MuiBreadcrumbs)(({ theme }) => {
|
||||
return {
|
||||
color: theme.colors.popoverColor,
|
||||
};
|
||||
});
|
||||
@@ -15,16 +15,26 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
hoverStyle,
|
||||
shape = 'default',
|
||||
icon,
|
||||
iconPosition = 'start',
|
||||
type = 'default',
|
||||
children,
|
||||
bold = false,
|
||||
loading = false,
|
||||
noBorder = false,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const { iconSize } = getSize(size);
|
||||
|
||||
const iconElement =
|
||||
icon &&
|
||||
cloneElement(Children.only(icon), {
|
||||
width: iconSize,
|
||||
height: iconSize,
|
||||
className: `affine-button-icon ${icon.props.className ?? ''}`,
|
||||
});
|
||||
|
||||
return (
|
||||
<StyledButton
|
||||
ref={ref}
|
||||
@@ -38,19 +48,16 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
// @ts-ignore
|
||||
type={type}
|
||||
bold={bold}
|
||||
noBorder={noBorder}
|
||||
{...props}
|
||||
>
|
||||
{loading ? (
|
||||
<Loading type={type}></Loading>
|
||||
) : (
|
||||
<>
|
||||
{icon &&
|
||||
cloneElement(Children.only(icon), {
|
||||
width: iconSize,
|
||||
height: iconSize,
|
||||
className: `affine-button-icon ${icon.props.className ?? ''}`,
|
||||
})}
|
||||
{iconPosition === 'start' && iconElement}
|
||||
{children && <span>{children}</span>}
|
||||
{iconPosition === 'end' && iconElement}
|
||||
</>
|
||||
)}
|
||||
</StyledButton>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { getButtonColors } from './utils';
|
||||
|
||||
export const LoadingContainer = styled('div')<Pick<ButtonProps, 'type'>>(
|
||||
({ theme, type = 'default' }) => {
|
||||
const { color } = getButtonColors(theme, type);
|
||||
const { color } = getButtonColors(theme, type, false);
|
||||
return `
|
||||
margin: 0px auto;
|
||||
width: 38px;
|
||||
|
||||
@@ -17,8 +17,10 @@ export type ButtonProps = PropsWithChildren &
|
||||
hoverColor?: CSSProperties['color'];
|
||||
hoverStyle?: CSSProperties;
|
||||
icon?: ReactElement;
|
||||
iconPosition?: 'start' | 'end';
|
||||
shape?: 'default' | 'round' | 'circle';
|
||||
type?: 'primary' | 'warning' | 'danger' | 'default';
|
||||
type?: 'primary' | 'light' | 'warning' | 'danger' | 'default';
|
||||
bold?: boolean;
|
||||
loading?: boolean;
|
||||
noBorder?: boolean;
|
||||
};
|
||||
|
||||
@@ -145,10 +145,12 @@ export const StyledButton = styled('button', {
|
||||
shouldForwardProp: prop => {
|
||||
return ![
|
||||
'hoverBackground',
|
||||
'shape',
|
||||
'hoverColor',
|
||||
'hoverStyle',
|
||||
'type',
|
||||
'bold',
|
||||
'noBorder',
|
||||
].includes(prop);
|
||||
},
|
||||
})<
|
||||
@@ -162,6 +164,7 @@ export const StyledButton = styled('button', {
|
||||
| 'shape'
|
||||
| 'type'
|
||||
| 'bold'
|
||||
| 'noBorder'
|
||||
>
|
||||
>(
|
||||
({
|
||||
@@ -174,6 +177,7 @@ export const StyledButton = styled('button', {
|
||||
bold = false,
|
||||
shape = 'default',
|
||||
type = 'default',
|
||||
noBorder = false,
|
||||
}) => {
|
||||
const { fontSize, borderRadius, padding, height } = getSize(size);
|
||||
|
||||
@@ -181,7 +185,7 @@ export const StyledButton = styled('button', {
|
||||
height,
|
||||
paddingLeft: padding,
|
||||
paddingRight: padding,
|
||||
border: '1px solid',
|
||||
border: noBorder ? 'none' : '1px solid',
|
||||
...displayInlineFlex('center', 'center'),
|
||||
position: 'relative',
|
||||
// TODO: disabled color is not decided
|
||||
@@ -200,13 +204,16 @@ export const StyledButton = styled('button', {
|
||||
'.affine-button-icon': {
|
||||
color: theme.colors.iconColor,
|
||||
},
|
||||
'.affine-button-icon__fixed': {
|
||||
color: theme.colors.iconColor,
|
||||
},
|
||||
'>span': {
|
||||
marginLeft: '5px',
|
||||
width: '100%',
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
...getButtonColors(theme, type, {
|
||||
...getButtonColors(theme, type, disabled, {
|
||||
hoverBackground,
|
||||
hoverColor,
|
||||
hoverStyle,
|
||||
|
||||
@@ -40,6 +40,7 @@ export const getSize = (
|
||||
export const getButtonColors = (
|
||||
theme: AffineTheme,
|
||||
type: ButtonProps['type'],
|
||||
disabled: boolean,
|
||||
extend?: {
|
||||
hoverBackground: ButtonProps['hoverBackground'];
|
||||
hoverColor: ButtonProps['hoverColor'];
|
||||
@@ -56,6 +57,18 @@ export const getButtonColors = (
|
||||
color: '#fff',
|
||||
},
|
||||
};
|
||||
case 'light':
|
||||
return {
|
||||
background: theme.colors.hoverBackground,
|
||||
color: disabled ? theme.colors.disableColor : theme.colors.primaryColor,
|
||||
borderColor: theme.colors.hoverBackground,
|
||||
'.affine-button-icon': {
|
||||
borderColor: theme.colors.primaryColor,
|
||||
},
|
||||
':hover': {
|
||||
borderColor: theme.colors.primaryColor,
|
||||
},
|
||||
};
|
||||
case 'warning':
|
||||
return {
|
||||
background: theme.colors.warningBackground,
|
||||
|
||||
@@ -38,8 +38,9 @@ export const Input = (props: inputProps) => {
|
||||
if (
|
||||
(maxLength && e.target.value.length > maxLength) ||
|
||||
(minLength && e.target.value.length < minLength)
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
setValue(e.target.value);
|
||||
onChange && onChange(e.target.value);
|
||||
};
|
||||
|
||||
@@ -14,8 +14,21 @@ export type ContentProps = {
|
||||
lineNum?: number;
|
||||
children: string;
|
||||
};
|
||||
|
||||
export const Content = styled.div<ContentProps>(
|
||||
export const Content = styled('div', {
|
||||
shouldForwardProp: prop => {
|
||||
return ![
|
||||
'color',
|
||||
'fontSize',
|
||||
'weight',
|
||||
'lineHeight',
|
||||
'ellipsis',
|
||||
'lineNum',
|
||||
'width',
|
||||
'maxWidth',
|
||||
'align',
|
||||
].includes(prop);
|
||||
},
|
||||
})<ContentProps>(
|
||||
({
|
||||
theme,
|
||||
color,
|
||||
|
||||
@@ -2,6 +2,22 @@ import type { CSSProperties } from 'react';
|
||||
import { styled } from '@/styles';
|
||||
|
||||
export type WrapperProps = {
|
||||
display?: CSSProperties['display'];
|
||||
width?: CSSProperties['width'];
|
||||
height?: CSSProperties['height'];
|
||||
padding?: CSSProperties['padding'];
|
||||
paddingTop?: CSSProperties['paddingTop'];
|
||||
paddingRight?: CSSProperties['paddingRight'];
|
||||
paddingLeft?: CSSProperties['paddingLeft'];
|
||||
paddingBottom?: CSSProperties['paddingBottom'];
|
||||
margin?: CSSProperties['margin'];
|
||||
marginTop?: CSSProperties['marginTop'];
|
||||
marginLeft?: CSSProperties['marginLeft'];
|
||||
marginRight?: CSSProperties['marginRight'];
|
||||
marginBottom?: CSSProperties['marginBottom'];
|
||||
};
|
||||
|
||||
export type FlexWrapperProps = {
|
||||
display?: CSSProperties['display'];
|
||||
flexDirection?: CSSProperties['flexDirection'];
|
||||
justifyContent?: CSSProperties['justifyContent'];
|
||||
@@ -16,6 +32,57 @@ export const Wrapper = styled('div', {
|
||||
shouldForwardProp: prop => {
|
||||
return ![
|
||||
'display',
|
||||
'width',
|
||||
'height',
|
||||
'padding',
|
||||
'margin',
|
||||
'paddingTop',
|
||||
'paddingRight',
|
||||
'paddingLeft',
|
||||
'paddingBottom',
|
||||
'marginTop',
|
||||
'marginLeft',
|
||||
'marginRight',
|
||||
'marginBottom',
|
||||
].includes(prop);
|
||||
},
|
||||
})<WrapperProps>(
|
||||
({
|
||||
display,
|
||||
width,
|
||||
height,
|
||||
padding,
|
||||
margin,
|
||||
paddingTop,
|
||||
paddingRight,
|
||||
paddingLeft,
|
||||
paddingBottom,
|
||||
marginTop,
|
||||
marginLeft,
|
||||
marginRight,
|
||||
marginBottom,
|
||||
}) => {
|
||||
return {
|
||||
display,
|
||||
width,
|
||||
height,
|
||||
padding,
|
||||
margin,
|
||||
paddingTop,
|
||||
paddingRight,
|
||||
paddingLeft,
|
||||
paddingBottom,
|
||||
marginTop,
|
||||
marginLeft,
|
||||
marginRight,
|
||||
marginBottom,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
export const FlexWrapper = styled(Wrapper, {
|
||||
shouldForwardProp: prop => {
|
||||
return ![
|
||||
'justifyContent',
|
||||
'alignItems',
|
||||
'wrap',
|
||||
@@ -24,18 +91,17 @@ export const Wrapper = styled('div', {
|
||||
'flexGrow',
|
||||
].includes(prop);
|
||||
},
|
||||
})<WrapperProps>(
|
||||
})<FlexWrapperProps>(
|
||||
({
|
||||
display = 'flex',
|
||||
justifyContent = 'flex-start',
|
||||
alignItems = 'center',
|
||||
justifyContent,
|
||||
alignItems,
|
||||
wrap = false,
|
||||
flexDirection = 'row',
|
||||
flexShrink = '0',
|
||||
flexGrow = '0',
|
||||
flexDirection,
|
||||
flexShrink,
|
||||
flexGrow,
|
||||
}) => {
|
||||
return {
|
||||
display,
|
||||
display: 'flex',
|
||||
justifyContent,
|
||||
alignItems,
|
||||
flexWrap: wrap ? 'wrap' : 'nowrap',
|
||||
@@ -46,4 +112,15 @@ export const Wrapper = styled('div', {
|
||||
}
|
||||
);
|
||||
|
||||
// TODO: Complete me
|
||||
export const GridWrapper = styled(Wrapper, {
|
||||
shouldForwardProp: prop => {
|
||||
return ![''].includes(prop);
|
||||
},
|
||||
})<WrapperProps>(() => {
|
||||
return {
|
||||
display: 'grid',
|
||||
};
|
||||
});
|
||||
|
||||
export default Wrapper;
|
||||
|
||||
@@ -5,6 +5,7 @@ import { styled } from '@/styles';
|
||||
export type ModalCloseButtonProps = {
|
||||
top?: number;
|
||||
right?: number;
|
||||
absolute?: boolean;
|
||||
} & Omit<IconButtonProps, 'children'> &
|
||||
HTMLAttributes<HTMLButtonElement>;
|
||||
|
||||
@@ -13,16 +14,23 @@ const StyledIconButton = styled(IconButton)<
|
||||
>(({ top, right }) => {
|
||||
return {
|
||||
position: 'absolute',
|
||||
top: top ?? 6,
|
||||
right: right ?? 6,
|
||||
top: top ?? 24,
|
||||
right: right ?? 40,
|
||||
};
|
||||
});
|
||||
|
||||
export const ModalCloseButton = ({ ...props }: ModalCloseButtonProps) => {
|
||||
return (
|
||||
export const ModalCloseButton = ({
|
||||
absolute = true,
|
||||
...props
|
||||
}: ModalCloseButtonProps) => {
|
||||
return absolute ? (
|
||||
<StyledIconButton {...props}>
|
||||
<CloseIcon />
|
||||
</StyledIconButton>
|
||||
) : (
|
||||
<IconButton {...props}>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ export const ModalWrapper = styled.div<{
|
||||
height,
|
||||
minHeight,
|
||||
backgroundColor: theme.colors.popoverBackground,
|
||||
borderRadius: '12px',
|
||||
borderRadius: '24px',
|
||||
position: 'relative',
|
||||
};
|
||||
});
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import MuiBreadcrumbs from '@mui/material/Breadcrumbs';
|
||||
import MuiCollapse from '@mui/material/Collapse';
|
||||
import MuiSlide from '@mui/material/Slide';
|
||||
import MuiAvatar from '@mui/material/Avatar';
|
||||
import MuiGrow from '@mui/material/Grow';
|
||||
|
||||
export { MuiBreadcrumbs, MuiCollapse, MuiSlide, MuiAvatar, MuiGrow };
|
||||
@@ -152,6 +152,11 @@ export const Popper = ({
|
||||
onPointerLeave={onPointerLeaveHandler}
|
||||
style={popoverStyle}
|
||||
className={popoverClassName}
|
||||
onClick={() => {
|
||||
if (hasClickTrigger && !visibleControlledByParent) {
|
||||
setVisible(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{showArrow && (
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { TooltipProps } from '@mui/material';
|
||||
|
||||
const StyledTooltip = styled(StyledPopperContainer)(({ theme }) => {
|
||||
return {
|
||||
maxWidth: '320px',
|
||||
boxShadow: theme.shadow.tooltip,
|
||||
padding: '4px 12px',
|
||||
backgroundColor: theme.colors.tooltipBackground,
|
||||
|
||||
Reference in New Issue
Block a user