mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-11 20:08:37 +00:00
feat: migrate workspace setting with new design to setting modal (#2900)
Co-authored-by: Alex Yang <himself65@outlook.com>
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
import { Button, Input, Modal, ModalCloseButton } from '@affine/component';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { Trans } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import type { AffineOfficialWorkspace } from '../../../../../shared';
|
||||
import { toast } from '../../../../../utils';
|
||||
import {
|
||||
StyledButtonContent,
|
||||
StyledInputContent,
|
||||
StyledModalHeader,
|
||||
StyledModalWrapper,
|
||||
StyledTextContent,
|
||||
StyledWorkspaceName,
|
||||
} from './style';
|
||||
|
||||
interface WorkspaceDeleteProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
workspace: AffineOfficialWorkspace;
|
||||
onDeleteWorkspace: () => Promise<void>;
|
||||
}
|
||||
|
||||
export const WorkspaceDeleteModal = ({
|
||||
open,
|
||||
onClose,
|
||||
workspace,
|
||||
onDeleteWorkspace,
|
||||
}: WorkspaceDeleteProps) => {
|
||||
const [workspaceName] = useBlockSuiteWorkspaceName(
|
||||
workspace.blockSuiteWorkspace ?? null
|
||||
);
|
||||
const [deleteStr, setDeleteStr] = useState<string>('');
|
||||
const allowDelete = deleteStr === workspaceName;
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
const handleDelete = useCallback(() => {
|
||||
onDeleteWorkspace()
|
||||
.then(() => {
|
||||
toast(t['Successfully deleted'](), {
|
||||
portal: document.body,
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
// ignore error
|
||||
});
|
||||
}, [onDeleteWorkspace, t]);
|
||||
|
||||
return (
|
||||
<Modal open={open} onClose={onClose}>
|
||||
<StyledModalWrapper>
|
||||
<ModalCloseButton onClick={onClose} />
|
||||
<StyledModalHeader>{t['Delete Workspace']()}?</StyledModalHeader>
|
||||
{workspace.flavour === WorkspaceFlavour.LOCAL ? (
|
||||
<StyledTextContent>
|
||||
<Trans i18nKey="Delete Workspace Description">
|
||||
Deleting (
|
||||
<StyledWorkspaceName>
|
||||
{{ workspace: workspaceName } as any}
|
||||
</StyledWorkspaceName>
|
||||
) cannot be undone, please proceed with caution. All contents will
|
||||
be lost.
|
||||
</Trans>
|
||||
</StyledTextContent>
|
||||
) : (
|
||||
<StyledTextContent>
|
||||
<Trans i18nKey="Delete Workspace Description2">
|
||||
Deleting (
|
||||
<StyledWorkspaceName>
|
||||
{{ workspace: workspaceName } as any}
|
||||
</StyledWorkspaceName>
|
||||
) will delete both local and cloud data, this operation cannot be
|
||||
undone, please proceed with caution.
|
||||
</Trans>
|
||||
</StyledTextContent>
|
||||
)}
|
||||
<StyledInputContent>
|
||||
<Input
|
||||
ref={ref => {
|
||||
if (ref) {
|
||||
setTimeout(() => ref.focus(), 0);
|
||||
}
|
||||
}}
|
||||
onChange={setDeleteStr}
|
||||
data-testid="delete-workspace-input"
|
||||
placeholder={t['Placeholder of delete workspace']()}
|
||||
value={deleteStr}
|
||||
width={315}
|
||||
height={42}
|
||||
/>
|
||||
</StyledInputContent>
|
||||
<StyledButtonContent>
|
||||
<Button shape="circle" onClick={onClose}>
|
||||
{t['Cancel']()}
|
||||
</Button>
|
||||
<Button
|
||||
data-testid="delete-workspace-confirm-button"
|
||||
disabled={!allowDelete}
|
||||
onClick={handleDelete}
|
||||
type="danger"
|
||||
shape="circle"
|
||||
style={{ marginLeft: '24px' }}
|
||||
>
|
||||
{t['Delete']()}
|
||||
</Button>
|
||||
</StyledButtonContent>
|
||||
</StyledModalWrapper>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,76 @@
|
||||
import { styled } from '@affine/component';
|
||||
|
||||
export const StyledModalWrapper = styled('div')(() => {
|
||||
return {
|
||||
position: 'relative',
|
||||
padding: '0px',
|
||||
width: '560px',
|
||||
background: 'var(--affine-white)',
|
||||
borderRadius: '12px',
|
||||
// height: '312px',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledModalHeader = styled('div')(() => {
|
||||
return {
|
||||
margin: '44px 0px 12px 0px',
|
||||
width: '560px',
|
||||
fontWeight: '600',
|
||||
fontSize: '20px;',
|
||||
textAlign: 'center',
|
||||
};
|
||||
});
|
||||
|
||||
// export const StyledModalContent = styled('div')(({ theme }) => {});
|
||||
|
||||
export const StyledTextContent = styled('div')(() => {
|
||||
return {
|
||||
margin: 'auto',
|
||||
width: '425px',
|
||||
fontFamily: 'Avenir Next',
|
||||
fontStyle: 'normal',
|
||||
fontWeight: '400',
|
||||
fontSize: '18px',
|
||||
lineHeight: '26px',
|
||||
textAlign: 'left',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledInputContent = styled('div')(() => {
|
||||
return {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
margin: '24px 0',
|
||||
fontSize: 'var(--affine-font-base)',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledButtonContent = styled('div')(() => {
|
||||
return {
|
||||
marginBottom: '42px',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledWorkspaceName = styled('span')(() => {
|
||||
return {
|
||||
fontWeight: '600',
|
||||
};
|
||||
});
|
||||
|
||||
// export const StyledCancelButton = styled(Button)(({ theme }) => {
|
||||
// return {
|
||||
// width: '100px',
|
||||
// justifyContent: 'center',
|
||||
// };
|
||||
// });
|
||||
|
||||
// export const StyledDeleteButton = styled(Button)(({ theme }) => {
|
||||
// return {
|
||||
// width: '100px',
|
||||
// justifyContent: 'center',
|
||||
// };
|
||||
// });
|
||||
@@ -0,0 +1,58 @@
|
||||
import {
|
||||
SettingRow,
|
||||
} from '@affine/component/setting-components';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { ArrowRightSmallIcon } from '@blocksuite/icons';
|
||||
import { type FC, useState } from 'react';
|
||||
|
||||
import { useIsWorkspaceOwner } from '../../../../hooks/affine/use-is-workspace-owner';
|
||||
import type { AffineOfficialWorkspace } from '../../../../shared';
|
||||
import type { WorkspaceSettingDetailProps } from '../index';
|
||||
import { WorkspaceDeleteModal } from './delete';
|
||||
import { WorkspaceLeave } from './leave';
|
||||
|
||||
export const DeleteLeaveWorkspace: FC<{
|
||||
workspace: AffineOfficialWorkspace;
|
||||
onDeleteWorkspace: WorkspaceSettingDetailProps['onDeleteWorkspace'];
|
||||
}> = ({ workspace, onDeleteWorkspace }) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const isOwner = useIsWorkspaceOwner(workspace);
|
||||
|
||||
const [showDelete, setShowDelete] = useState(false);
|
||||
const [showLeave, setShowLeave] = useState(false);
|
||||
return (
|
||||
<>
|
||||
<SettingRow
|
||||
name={
|
||||
<span style={{ color: 'var(--affine-error-color)' }}>
|
||||
{isOwner ? t['Delete Workspace']() : t['Leave Workspace']()}
|
||||
</span>
|
||||
}
|
||||
desc={t['None yet']()}
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
setShowDelete(true);
|
||||
}}
|
||||
>
|
||||
<ArrowRightSmallIcon />
|
||||
</SettingRow>
|
||||
{isOwner ? (
|
||||
<WorkspaceDeleteModal
|
||||
onDeleteWorkspace={onDeleteWorkspace}
|
||||
open={showDelete}
|
||||
onClose={() => {
|
||||
setShowDelete(false);
|
||||
}}
|
||||
workspace={workspace}
|
||||
/>
|
||||
) : (
|
||||
<WorkspaceLeave
|
||||
open={showLeave}
|
||||
onClose={() => {
|
||||
setShowLeave(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Modal } from '@affine/component';
|
||||
import { ModalCloseButton } from '@affine/component';
|
||||
import { Button } from '@affine/component';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
|
||||
import {
|
||||
StyledButtonContent,
|
||||
StyledModalHeader,
|
||||
StyledModalWrapper,
|
||||
StyledTextContent,
|
||||
} from './style';
|
||||
|
||||
interface WorkspaceDeleteProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const WorkspaceLeave = ({ open, onClose }: WorkspaceDeleteProps) => {
|
||||
// const { leaveWorkSpace } = useWorkspaceHelper();
|
||||
const t = useAFFiNEI18N();
|
||||
const handleLeave = async () => {
|
||||
// await leaveWorkSpace();
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal open={open} onClose={onClose}>
|
||||
<StyledModalWrapper>
|
||||
<ModalCloseButton onClick={onClose} />
|
||||
<StyledModalHeader>{t['Leave Workspace']()}</StyledModalHeader>
|
||||
<StyledTextContent>
|
||||
{t['Leave Workspace Description']()}
|
||||
</StyledTextContent>
|
||||
<StyledButtonContent>
|
||||
<Button shape="circle" onClick={onClose}>
|
||||
{t['Cancel']()}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleLeave}
|
||||
type="danger"
|
||||
shape="circle"
|
||||
style={{ marginLeft: '24px' }}
|
||||
>
|
||||
{t['Leave']()}
|
||||
</Button>
|
||||
</StyledButtonContent>
|
||||
</StyledModalWrapper>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
import { styled } from '@affine/component';
|
||||
|
||||
export const StyledModalWrapper = styled('div')(() => {
|
||||
return {
|
||||
position: 'relative',
|
||||
padding: '0px',
|
||||
width: '460px',
|
||||
background: 'var(--affine-white)',
|
||||
borderRadius: '12px',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledModalHeader = styled('div')(() => {
|
||||
return {
|
||||
margin: '44px 0px 12px 0px',
|
||||
width: '460px',
|
||||
fontWeight: '600',
|
||||
fontSize: '20px;',
|
||||
textAlign: 'center',
|
||||
};
|
||||
});
|
||||
|
||||
// export const StyledModalContent = styled('div')(({ theme }) => {});
|
||||
|
||||
export const StyledTextContent = styled('div')(() => {
|
||||
return {
|
||||
margin: 'auto',
|
||||
width: '425px',
|
||||
fontFamily: 'Avenir Next',
|
||||
fontStyle: 'normal',
|
||||
fontWeight: '400',
|
||||
fontSize: '18px',
|
||||
lineHeight: '26px',
|
||||
textAlign: 'center',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledButtonContent = styled('div')(() => {
|
||||
return {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
margin: '0px 0 32px 0',
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Button, toast } from '@affine/component';
|
||||
import { SettingRow } from '@affine/component/setting-components';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { FC } from 'react';
|
||||
|
||||
import type { AffineOfficialWorkspace } from '../../../shared';
|
||||
|
||||
export const ExportPanel: FC<{
|
||||
workspace: AffineOfficialWorkspace;
|
||||
}> = ({ workspace }) => {
|
||||
const workspaceId = workspace.id;
|
||||
const t = useAFFiNEI18N();
|
||||
return (
|
||||
<>
|
||||
<SettingRow name={t['Export']()} desc={t['Export Description']()}>
|
||||
<Button
|
||||
size="small"
|
||||
data-testid="export-affine-backup"
|
||||
onClick={async () => {
|
||||
const result = await window.apis?.dialog.saveDBFileAs(workspaceId);
|
||||
if (result?.error) {
|
||||
// @ts-expect-error: result.error is dynamic
|
||||
toast(t[result.error]());
|
||||
} else if (!result?.canceled) {
|
||||
toast(t['Export success']());
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t['Export']()}
|
||||
</Button>
|
||||
</SettingRow>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,11 +1,23 @@
|
||||
import {
|
||||
SettingHeader,
|
||||
SettingRow,
|
||||
SettingWrapper,
|
||||
} from '@affine/component/setting-components';
|
||||
import type {
|
||||
WorkspaceFlavour,
|
||||
WorkspaceRegistry,
|
||||
} from '@affine/env/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
||||
import type { FC } from 'react';
|
||||
|
||||
import type { AffineOfficialWorkspace } from '../../../shared';
|
||||
import { DeleteLeaveWorkspace } from './delete-leave-workspace';
|
||||
import { ExportPanel } from './export';
|
||||
import { MembersPanel } from './members';
|
||||
import { ProfilePanel } from './profile';
|
||||
import { PublishPanel } from './publish';
|
||||
import { StoragePanel } from './storage';
|
||||
|
||||
export type WorkspaceSettingDetailProps = {
|
||||
workspace: AffineOfficialWorkspace;
|
||||
@@ -22,15 +34,54 @@ export type WorkspaceSettingDetailProps = {
|
||||
|
||||
export const WorkspaceSettingDetail: FC<WorkspaceSettingDetailProps> = ({
|
||||
workspace,
|
||||
onDeleteWorkspace,
|
||||
...props
|
||||
}) => {
|
||||
const [workspaceName] = useBlockSuiteWorkspaceName(
|
||||
workspace.blockSuiteWorkspace ?? null
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
<h2>New Workspace Setting Coming Soon!</h2>
|
||||
const t = useAFFiNEI18N();
|
||||
const [name] = useBlockSuiteWorkspaceName(workspace.blockSuiteWorkspace);
|
||||
|
||||
{workspaceName}
|
||||
</div>
|
||||
return (
|
||||
<>
|
||||
<SettingHeader
|
||||
title={t[`Workspace Settings with name`]({ name })}
|
||||
subtitle={t['You can customize your workspace here.']()}
|
||||
/>
|
||||
<SettingWrapper title={t['Info']()}>
|
||||
<SettingRow
|
||||
name={t['Workspace Profile']()}
|
||||
desc={t[
|
||||
'Only an owner can edit the the Workspace avatar and name.Changes will be shown for everyone.'
|
||||
]()}
|
||||
spreadCol={false}
|
||||
>
|
||||
<ProfilePanel workspace={workspace} />
|
||||
</SettingRow>
|
||||
</SettingWrapper>
|
||||
<SettingWrapper title={t['AFFiNE Cloud']()}>
|
||||
<PublishPanel
|
||||
workspace={workspace}
|
||||
onDeleteWorkspace={onDeleteWorkspace}
|
||||
{...props}
|
||||
/>
|
||||
<MembersPanel
|
||||
workspace={workspace}
|
||||
onDeleteWorkspace={onDeleteWorkspace}
|
||||
{...props}
|
||||
/>
|
||||
</SettingWrapper>
|
||||
{environment.isDesktop ? (
|
||||
<SettingWrapper title={t['Storage and Export']()}>
|
||||
<StoragePanel workspace={workspace} />
|
||||
<ExportPanel workspace={workspace} />
|
||||
</SettingWrapper>
|
||||
) : null}
|
||||
|
||||
<SettingWrapper>
|
||||
<DeleteLeaveWorkspace
|
||||
workspace={workspace}
|
||||
onDeleteWorkspace={onDeleteWorkspace}
|
||||
/>
|
||||
</SettingWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
import { Button, IconButton, Menu, MenuItem } from '@affine/component';
|
||||
import { SettingRow } from '@affine/component/setting-components';
|
||||
import { UserAvatar } from '@affine/component/user-avatar';
|
||||
import { Unreachable } from '@affine/env/constant';
|
||||
import type { AffineLegacyCloudWorkspace } from '@affine/env/workspace';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { PermissionType } from '@affine/env/workspace/legacy-cloud';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { DeleteTemporarilyIcon, MoreVerticalIcon } from '@blocksuite/icons';
|
||||
import type { FC } from 'react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import { useMembers } from '../../../../hooks/affine/use-members';
|
||||
import type { AffineOfficialWorkspace } from '../../../../shared';
|
||||
import { toast } from '../../../../utils';
|
||||
import type { WorkspaceSettingDetailProps } from '../index';
|
||||
import { fakeWrapper } from '../style.css';
|
||||
import { InviteMemberModal } from './invite-member-modal';
|
||||
import * as style from './style.css';
|
||||
|
||||
export type AffineRemoteMembersProps = WorkspaceSettingDetailProps & {
|
||||
workspace: AffineLegacyCloudWorkspace;
|
||||
};
|
||||
export type MemberPanelProps = WorkspaceSettingDetailProps & {
|
||||
workspace: AffineOfficialWorkspace;
|
||||
};
|
||||
|
||||
const MemberList: FC<{
|
||||
workspace: AffineLegacyCloudWorkspace;
|
||||
}> = ({ workspace }) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const { members, removeMember } = useMembers(workspace.id);
|
||||
|
||||
if (members.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ul className={style.memberList}>
|
||||
{members
|
||||
.sort((b, a) => a.type - b.type)
|
||||
.map(member => {
|
||||
const { id, name, email, avatar_url } = {
|
||||
name: '',
|
||||
email: '',
|
||||
avatar_url: '',
|
||||
...member,
|
||||
};
|
||||
return (
|
||||
<li className="member-list-item" key={id}>
|
||||
<div className="left-col">
|
||||
<UserAvatar size={36} name={name} url={avatar_url} />
|
||||
<div className="user-info-wrapper">
|
||||
<p className="user-name">{name}</p>
|
||||
<p className="email">{email}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="right-col">
|
||||
<div className="user-identity">
|
||||
{member.accepted
|
||||
? member.type !== PermissionType.Owner
|
||||
? t['Member']()
|
||||
: t['Owner']()
|
||||
: t['Pending']()}
|
||||
</div>
|
||||
<Menu
|
||||
content={
|
||||
<>
|
||||
<MenuItem
|
||||
onClick={async () => {
|
||||
await removeMember(Number(id));
|
||||
toast(
|
||||
t['Member has been removed']({
|
||||
name,
|
||||
})
|
||||
);
|
||||
}}
|
||||
icon={<DeleteTemporarilyIcon />}
|
||||
>
|
||||
{t['Remove from workspace']()}
|
||||
</MenuItem>
|
||||
</>
|
||||
}
|
||||
placement="bottom"
|
||||
disablePortal={true}
|
||||
trigger="click"
|
||||
>
|
||||
<IconButton>
|
||||
<MoreVerticalIcon />
|
||||
</IconButton>
|
||||
</Menu>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
export const AffineRemoteMembers: FC<AffineRemoteMembersProps> = ({
|
||||
workspace,
|
||||
}) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const { members } = useMembers(workspace.id);
|
||||
|
||||
const [isInviteModalShow, setIsInviteModalShow] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingRow
|
||||
name={`${t['Members']()} (${members.length})`}
|
||||
desc={t['Members hint']()}
|
||||
style={{ marginTop: '25px' }}
|
||||
>
|
||||
<Button
|
||||
size="middle"
|
||||
onClick={() => {
|
||||
setIsInviteModalShow(true);
|
||||
}}
|
||||
>
|
||||
{t['Invite']()}
|
||||
</Button>
|
||||
</SettingRow>
|
||||
<MemberList workspace={workspace} />
|
||||
<InviteMemberModal
|
||||
onClose={useCallback(() => {
|
||||
setIsInviteModalShow(false);
|
||||
}, [])}
|
||||
onInviteSuccess={useCallback(() => {
|
||||
setIsInviteModalShow(false);
|
||||
}, [])}
|
||||
workspaceId={workspace.id}
|
||||
open={isInviteModalShow}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export const FakeMembers: FC = () => {
|
||||
const t = useAFFiNEI18N();
|
||||
return (
|
||||
<div className={fakeWrapper} style={{ marginTop: '25px' }}>
|
||||
<SettingRow name={`${t['Members']()} (0)`} desc={t['Members hint']()}>
|
||||
<Button size="middle">{t['Invite']()}</Button>
|
||||
</SettingRow>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const MembersPanel: FC<MemberPanelProps> = props => {
|
||||
switch (props.workspace.flavour) {
|
||||
case WorkspaceFlavour.AFFINE: {
|
||||
const workspace = props.workspace as AffineLegacyCloudWorkspace;
|
||||
return <AffineRemoteMembers {...props} workspace={workspace} />;
|
||||
}
|
||||
case WorkspaceFlavour.LOCAL: {
|
||||
return <FakeMembers />;
|
||||
}
|
||||
}
|
||||
throw new Unreachable();
|
||||
};
|
||||
@@ -0,0 +1,222 @@
|
||||
import {
|
||||
Button,
|
||||
Input,
|
||||
Modal,
|
||||
ModalCloseButton,
|
||||
ModalWrapper,
|
||||
MuiAvatar,
|
||||
styled,
|
||||
} from '@affine/component';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { EmailIcon } from '@blocksuite/icons';
|
||||
import type React from 'react';
|
||||
import { Suspense, useCallback, useState } from 'react';
|
||||
|
||||
import { useMembers } from '../../../../../hooks/affine/use-members';
|
||||
import { useUsersByEmail } from '../../../../../hooks/affine/use-users-by-email';
|
||||
|
||||
interface LoginModalProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
workspaceId: string;
|
||||
onInviteSuccess: () => void;
|
||||
}
|
||||
|
||||
const gmailReg =
|
||||
/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@(gmail|example)\.(com|org)$/;
|
||||
|
||||
const Result: React.FC<{
|
||||
workspaceId: string;
|
||||
queryEmail: string;
|
||||
}> = ({ workspaceId, queryEmail }) => {
|
||||
const users = useUsersByEmail(workspaceId, queryEmail);
|
||||
const firstUser = users?.at(0) ?? null;
|
||||
if (!firstUser || !firstUser.email) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Members>
|
||||
<Member>
|
||||
{firstUser.avatar_url ? (
|
||||
<MuiAvatar src={firstUser.avatar_url}></MuiAvatar>
|
||||
) : (
|
||||
<MemberIcon>
|
||||
<EmailIcon></EmailIcon>
|
||||
</MemberIcon>
|
||||
)}
|
||||
<Email>{firstUser.email}</Email>
|
||||
{/* <div>invited</div> */}
|
||||
</Member>
|
||||
</Members>
|
||||
);
|
||||
};
|
||||
|
||||
export const InviteMemberModal = ({
|
||||
open,
|
||||
onClose,
|
||||
onInviteSuccess,
|
||||
workspaceId,
|
||||
}: LoginModalProps) => {
|
||||
const { inviteMember } = useMembers(workspaceId);
|
||||
const [email, setEmail] = useState<string>('');
|
||||
const [showMemberPreview, setShowMemberPreview] = useState(false);
|
||||
const t = useAFFiNEI18N();
|
||||
const inputChange = useCallback((value: string) => {
|
||||
setEmail(value);
|
||||
}, []);
|
||||
return (
|
||||
<div>
|
||||
<Modal open={open} onClose={onClose}>
|
||||
<ModalWrapper width={460} height={236}>
|
||||
<Header>
|
||||
<ModalCloseButton
|
||||
onClick={() => {
|
||||
onClose();
|
||||
setEmail('');
|
||||
}}
|
||||
/>
|
||||
</Header>
|
||||
<Content>
|
||||
<ContentTitle>{t['Invite Members']()}</ContentTitle>
|
||||
<InviteBox>
|
||||
<Input
|
||||
data-testid="invite-member-input"
|
||||
width={360}
|
||||
value={email}
|
||||
onChange={inputChange}
|
||||
onFocus={useCallback(() => {
|
||||
setShowMemberPreview(true);
|
||||
}, [])}
|
||||
onBlur={useCallback(() => {
|
||||
setShowMemberPreview(false);
|
||||
}, [])}
|
||||
placeholder={t['Invite placeholder']()}
|
||||
/>
|
||||
{showMemberPreview && gmailReg.test(email) && (
|
||||
<Suspense fallback="loading...">
|
||||
<Result workspaceId={workspaceId} queryEmail={email} />
|
||||
</Suspense>
|
||||
)}
|
||||
</InviteBox>
|
||||
</Content>
|
||||
<Footer>
|
||||
<Button
|
||||
data-testid="invite-member-button"
|
||||
disabled={!gmailReg.test(email)}
|
||||
shape="circle"
|
||||
type="primary"
|
||||
style={{
|
||||
width: '364px',
|
||||
height: '38px',
|
||||
borderRadius: '40px',
|
||||
}}
|
||||
onClick={async () => {
|
||||
await inviteMember(email);
|
||||
setEmail('');
|
||||
onInviteSuccess();
|
||||
}}
|
||||
>
|
||||
{t['Invite']()}
|
||||
</Button>
|
||||
</Footer>
|
||||
</ModalWrapper>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Header = styled('div')({
|
||||
position: 'relative',
|
||||
height: '44px',
|
||||
});
|
||||
|
||||
const Content = styled('div')({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
const ContentTitle = styled('h1')({
|
||||
fontSize: '20px',
|
||||
lineHeight: '28px',
|
||||
fontWeight: 600,
|
||||
textAlign: 'center',
|
||||
paddingBottom: '16px',
|
||||
});
|
||||
|
||||
const Footer = styled('div')({
|
||||
height: '102px',
|
||||
margin: '32px 0',
|
||||
textAlign: 'center',
|
||||
});
|
||||
|
||||
const InviteBox = styled('div')({
|
||||
position: 'relative',
|
||||
});
|
||||
|
||||
const Members = styled('div')(() => {
|
||||
return {
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
background: 'var(--affine-background-primary-color)',
|
||||
textAlign: 'left',
|
||||
zIndex: 1,
|
||||
borderRadius: '0px 10px 10px 10px',
|
||||
height: '56px',
|
||||
padding: '8px 12px',
|
||||
input: {
|
||||
'&::placeholder': {
|
||||
color: 'var(--affine-placeholder-color)',
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
// const NoFind = styled('div')(({ theme }) => {
|
||||
// return {
|
||||
// color: 'var(--affine-icon-color)',
|
||||
// fontSize: 'var(--affine-font-sm)',
|
||||
// lineHeight: '40px',
|
||||
// userSelect: 'none',
|
||||
// width: '100%',
|
||||
// };
|
||||
// });
|
||||
|
||||
const Member = styled('div')(() => {
|
||||
return {
|
||||
color: 'var(--affine-icon-color)',
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
lineHeight: '40px',
|
||||
userSelect: 'none',
|
||||
display: 'flex',
|
||||
};
|
||||
});
|
||||
|
||||
const MemberIcon = styled('div')(() => {
|
||||
return {
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
borderRadius: '50%',
|
||||
color: 'var(--affine-primary-color)',
|
||||
background: '#F5F5F5',
|
||||
textAlign: 'center',
|
||||
lineHeight: '45px',
|
||||
// icon size
|
||||
fontSize: '20px',
|
||||
overflow: 'hidden',
|
||||
img: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const Email = styled('div')(() => {
|
||||
return {
|
||||
flex: '1',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
marginLeft: '8px',
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
import { globalStyle, style } from '@vanilla-extract/css';
|
||||
|
||||
export const memberList = style({
|
||||
marginTop: '12px',
|
||||
});
|
||||
|
||||
globalStyle(`${memberList} .member-list-item`, {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
});
|
||||
globalStyle(`${memberList} .member-list-item:not(:last-of-type)`, {
|
||||
marginBottom: '8px',
|
||||
});
|
||||
|
||||
globalStyle(`${memberList} .left-col`, {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
width: '60%',
|
||||
});
|
||||
globalStyle(`${memberList} .right-col`, {
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
width: '35%',
|
||||
});
|
||||
globalStyle(`${memberList} .user-info-wrapper`, {
|
||||
flexGrow: 1,
|
||||
marginLeft: '12px',
|
||||
overflow: 'hidden',
|
||||
});
|
||||
|
||||
globalStyle(`${memberList} .user-info-wrapper p`, {
|
||||
width: '100%',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
});
|
||||
|
||||
globalStyle(`${memberList} .user-name`, {
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
});
|
||||
globalStyle(`${memberList} .email`, {
|
||||
fontSize: 'var(--affine-font-xs)',
|
||||
color: 'var(--affine-text-secondary-color)',
|
||||
});
|
||||
globalStyle(`${memberList} .user-identity`, {
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
marginRight: '15px',
|
||||
flexGrow: '1',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
});
|
||||
@@ -0,0 +1,87 @@
|
||||
import { IconButton, Input, toast } from '@affine/component';
|
||||
import { WorkspaceAvatar } from '@affine/component/workspace-avatar';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { DoneIcon } from '@blocksuite/icons';
|
||||
import { useBlockSuiteWorkspaceAvatarUrl } from '@toeverything/hooks/use-block-suite-workspace-avatar-url';
|
||||
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
||||
import { type FC, useCallback, useState } from 'react';
|
||||
|
||||
import { useIsWorkspaceOwner } from '../../../hooks/affine/use-is-workspace-owner';
|
||||
import type { AffineOfficialWorkspace } from '../../../shared';
|
||||
import { Upload } from '../../pure/file-upload';
|
||||
import { CameraIcon } from '../workspace-setting-detail/panel/general/icons';
|
||||
import * as style from './style.css';
|
||||
|
||||
export const ProfilePanel: FC<{
|
||||
workspace: AffineOfficialWorkspace;
|
||||
}> = ({ workspace }) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const isOwner = useIsWorkspaceOwner(workspace);
|
||||
|
||||
const [, update] = useBlockSuiteWorkspaceAvatarUrl(
|
||||
workspace.blockSuiteWorkspace
|
||||
);
|
||||
const [name, setName] = useBlockSuiteWorkspaceName(
|
||||
workspace.blockSuiteWorkspace
|
||||
);
|
||||
|
||||
const [input, setInput] = useState<string>(name);
|
||||
|
||||
const handleUpdateWorkspaceName = useCallback(
|
||||
(name: string) => {
|
||||
setName(name);
|
||||
toast(t['Update workspace name success']());
|
||||
},
|
||||
[setName, t]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={style.profileWrapper}>
|
||||
<div className={style.avatarWrapper}>
|
||||
{isOwner ? (
|
||||
<Upload
|
||||
accept="image/gif,image/jpeg,image/jpg,image/png,image/svg"
|
||||
fileChange={update}
|
||||
data-testid="upload-avatar"
|
||||
>
|
||||
<>
|
||||
<div className="camera-icon-wrapper">
|
||||
<CameraIcon />
|
||||
</div>
|
||||
<WorkspaceAvatar size={56} workspace={workspace} />
|
||||
</>
|
||||
</Upload>
|
||||
) : (
|
||||
<WorkspaceAvatar size={56} workspace={workspace} />
|
||||
)}
|
||||
</div>
|
||||
<div className={style.profileHandlerWrapper}>
|
||||
<Input
|
||||
width={280}
|
||||
height={32}
|
||||
value={input}
|
||||
data-testid="workspace-name-input"
|
||||
placeholder={t['Workspace Name']()}
|
||||
maxLength={64}
|
||||
minLength={0}
|
||||
onChange={setInput}
|
||||
/>
|
||||
{input === workspace.blockSuiteWorkspace.meta.name ? null : (
|
||||
<IconButton
|
||||
size="middle"
|
||||
data-testid="save-workspace-name"
|
||||
onClick={() => {
|
||||
handleUpdateWorkspaceName(input);
|
||||
}}
|
||||
style={{
|
||||
color: 'var(--affine-primary-color)',
|
||||
marginLeft: '12px',
|
||||
}}
|
||||
>
|
||||
<DoneIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,180 @@
|
||||
import { Button, FlexWrapper, Switch } from '@affine/component';
|
||||
import { SettingRow } from '@affine/component/setting-components';
|
||||
import { Unreachable } from '@affine/env/constant';
|
||||
import type {
|
||||
AffineLegacyCloudWorkspace,
|
||||
LocalWorkspace,
|
||||
} from '@affine/env/workspace';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
||||
import type { FC } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { useToggleWorkspacePublish } from '../../../hooks/affine/use-toggle-workspace-publish';
|
||||
import type { AffineOfficialWorkspace } from '../../../shared';
|
||||
import { toast } from '../../../utils';
|
||||
import { EnableAffineCloudModal } from '../enable-affine-cloud-modal';
|
||||
import { TmpDisableAffineCloudModal } from '../tmp-disable-affine-cloud-modal';
|
||||
import type { WorkspaceSettingDetailProps } from './index';
|
||||
import * as style from './style.css';
|
||||
|
||||
export type PublishPanelProps = WorkspaceSettingDetailProps & {
|
||||
workspace: AffineOfficialWorkspace;
|
||||
};
|
||||
export type PublishPanelLocalProps = WorkspaceSettingDetailProps & {
|
||||
workspace: LocalWorkspace;
|
||||
};
|
||||
export type PublishPanelAffineProps = WorkspaceSettingDetailProps & {
|
||||
workspace: AffineLegacyCloudWorkspace;
|
||||
};
|
||||
|
||||
const PublishPanelAffine: FC<PublishPanelAffineProps> = props => {
|
||||
const { workspace } = props;
|
||||
const t = useAFFiNEI18N();
|
||||
const toggleWorkspacePublish = useToggleWorkspacePublish(workspace);
|
||||
|
||||
const [origin, setOrigin] = useState('');
|
||||
const shareUrl = origin + '/public-workspace/' + workspace.id;
|
||||
|
||||
useEffect(() => {
|
||||
setOrigin(
|
||||
typeof window !== 'undefined' && window.location.origin
|
||||
? window.location.origin
|
||||
: ''
|
||||
);
|
||||
}, []);
|
||||
|
||||
const copyUrl = useCallback(async () => {
|
||||
await navigator.clipboard.writeText(shareUrl);
|
||||
toast(t['Copied link to clipboard']());
|
||||
}, [shareUrl, t]);
|
||||
return (
|
||||
<>
|
||||
<SettingRow
|
||||
name={t['Publish']()}
|
||||
desc={
|
||||
workspace.public ? t['Unpublished hint']() : t['Published hint']()
|
||||
}
|
||||
>
|
||||
<Switch
|
||||
checked={workspace.public}
|
||||
onChange={checked => toggleWorkspacePublish(checked)}
|
||||
/>
|
||||
</SettingRow>
|
||||
<FlexWrapper justifyContent="space-between">
|
||||
<Button
|
||||
className={style.urlButton}
|
||||
size="middle"
|
||||
onClick={useCallback(() => {
|
||||
window.open(shareUrl, '_blank');
|
||||
}, [shareUrl])}
|
||||
title={shareUrl}
|
||||
>
|
||||
{shareUrl}
|
||||
</Button>
|
||||
<Button size="middle" onClick={copyUrl}>
|
||||
{t['Copy']()}
|
||||
</Button>
|
||||
</FlexWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const FakePublishPanelAffine: FC<{
|
||||
workspace: AffineOfficialWorkspace;
|
||||
}> = ({ workspace }) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const [origin, setOrigin] = useState('');
|
||||
const shareUrl = origin + '/public-workspace/' + workspace.id;
|
||||
|
||||
useEffect(() => {
|
||||
setOrigin(
|
||||
typeof window !== 'undefined' && window.location.origin
|
||||
? window.location.origin
|
||||
: ''
|
||||
);
|
||||
}, []);
|
||||
return (
|
||||
<div className={style.fakeWrapper}>
|
||||
<SettingRow name={t['Publish']()} desc={t['Unpublished hint']()}>
|
||||
<Switch checked={false} />
|
||||
</SettingRow>
|
||||
<FlexWrapper justifyContent="space-between">
|
||||
<Button className={style.urlButton} size="middle" title={shareUrl}>
|
||||
{shareUrl}
|
||||
</Button>
|
||||
<Button size="middle">{t['Copy']()}</Button>
|
||||
</FlexWrapper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const PublishPanelLocal: FC<PublishPanelLocalProps> = ({
|
||||
workspace,
|
||||
onTransferWorkspace,
|
||||
}) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const [name] = useBlockSuiteWorkspaceName(workspace.blockSuiteWorkspace);
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingRow
|
||||
name={t['Workspace saved locally']({ name })}
|
||||
desc={t['Enable cloud hint']()}
|
||||
spreadCol={false}
|
||||
style={{
|
||||
padding: '10px',
|
||||
background: 'var(--affine-background-secondary-color)',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
data-testid="publish-enable-affine-cloud-button"
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
setOpen(true);
|
||||
}}
|
||||
style={{ marginTop: '12px' }}
|
||||
>
|
||||
{runtimeConfig.enableLegacyCloud
|
||||
? t['Enable AFFiNE Cloud']()
|
||||
: 'Disable AFFiNE Cloud'}
|
||||
</Button>
|
||||
</SettingRow>
|
||||
<FakePublishPanelAffine workspace={workspace} />
|
||||
{runtimeConfig.enableLegacyCloud ? (
|
||||
<EnableAffineCloudModal
|
||||
open={open}
|
||||
onClose={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
onConfirm={() => {
|
||||
onTransferWorkspace(
|
||||
WorkspaceFlavour.LOCAL,
|
||||
WorkspaceFlavour.AFFINE,
|
||||
workspace
|
||||
);
|
||||
setOpen(false);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<TmpDisableAffineCloudModal
|
||||
open={open}
|
||||
onClose={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const PublishPanel: FC<PublishPanelProps> = props => {
|
||||
if (props.workspace.flavour === WorkspaceFlavour.AFFINE) {
|
||||
return <PublishPanelAffine {...props} workspace={props.workspace} />;
|
||||
} else if (props.workspace.flavour === WorkspaceFlavour.LOCAL) {
|
||||
return <PublishPanelLocal {...props} workspace={props.workspace} />;
|
||||
}
|
||||
throw new Unreachable();
|
||||
};
|
||||
@@ -0,0 +1,87 @@
|
||||
import { Button, toast } from '@affine/component';
|
||||
import { SettingRow } from '@affine/component/setting-components';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { type FC, useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import type { AffineOfficialWorkspace } from '../../../shared';
|
||||
|
||||
const useShowOpenDBFile = (workspaceId: string) => {
|
||||
const [show, setShow] = useState(false);
|
||||
useEffect(() => {
|
||||
if (window.apis && window.events && environment.isDesktop) {
|
||||
window.apis?.workspace
|
||||
.getMeta(workspaceId)
|
||||
.then(meta => {
|
||||
setShow(!!meta.secondaryDBPath);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
return window.events.workspace.onMetaChange((newMeta: any) => {
|
||||
if (newMeta.workspaceId === workspaceId) {
|
||||
const meta = newMeta.meta;
|
||||
setShow(!!meta.secondaryDBPath);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [workspaceId]);
|
||||
return show;
|
||||
};
|
||||
|
||||
export const StoragePanel: FC<{
|
||||
workspace: AffineOfficialWorkspace;
|
||||
}> = ({ workspace }) => {
|
||||
const workspaceId = workspace.id;
|
||||
const t = useAFFiNEI18N();
|
||||
const showOpenFolder = useShowOpenDBFile(workspaceId);
|
||||
|
||||
const [moveToInProgress, setMoveToInProgress] = useState<boolean>(false);
|
||||
const onRevealDBFile = useCallback(() => {
|
||||
window.apis?.dialog.revealDBFile(workspaceId).catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}, [workspaceId]);
|
||||
|
||||
const handleMoveTo = useCallback(() => {
|
||||
if (moveToInProgress) {
|
||||
return;
|
||||
}
|
||||
setMoveToInProgress(true);
|
||||
window.apis?.dialog
|
||||
.moveDBFile(workspaceId)
|
||||
.then(result => {
|
||||
if (!result?.error && !result?.canceled) {
|
||||
toast(t['Move folder success']());
|
||||
} else if (result?.error) {
|
||||
// @ts-expect-error: result.error is dynamic
|
||||
toast(t[result.error]());
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
toast(t['UNKNOWN_ERROR']());
|
||||
})
|
||||
.finally(() => {
|
||||
setMoveToInProgress(false);
|
||||
});
|
||||
}, [moveToInProgress, t, workspaceId]);
|
||||
|
||||
if (!showOpenFolder) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<SettingRow
|
||||
name={t['Storage']()}
|
||||
desc={t['Storage Folder Hint']()}
|
||||
spreadCol={false}
|
||||
>
|
||||
<Button
|
||||
data-testid="move-folder"
|
||||
data-disabled={moveToInProgress}
|
||||
onClick={handleMoveTo}
|
||||
>
|
||||
{t['Move folder']()}
|
||||
</Button>
|
||||
<Button onClick={onRevealDBFile}>{t['Open folder']()}</Button>
|
||||
</SettingRow>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
import { globalStyle, style } from '@vanilla-extract/css';
|
||||
|
||||
export const profileWrapper = style({
|
||||
display: 'flex',
|
||||
alignItems: 'flex-end',
|
||||
marginTop: '12px',
|
||||
});
|
||||
export const profileHandlerWrapper = style({
|
||||
flexGrow: '1',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginLeft: '20px',
|
||||
});
|
||||
|
||||
export const avatarWrapper = style({
|
||||
width: '56px',
|
||||
height: '56px',
|
||||
borderRadius: '50%',
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
cursor: 'pointer',
|
||||
flexShrink: '0',
|
||||
selectors: {
|
||||
'&.disable': {
|
||||
cursor: 'default',
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
},
|
||||
});
|
||||
globalStyle(`${avatarWrapper}:hover .camera-icon-wrapper`, {
|
||||
display: 'flex',
|
||||
});
|
||||
globalStyle(`${avatarWrapper} .camera-icon-wrapper`, {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
position: 'absolute',
|
||||
display: 'none',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: 'rgba(60, 61, 63, 0.5)',
|
||||
zIndex: '1',
|
||||
});
|
||||
|
||||
export const urlButton = style({
|
||||
width: 'calc(100% - 64px - 15px)',
|
||||
});
|
||||
globalStyle(`${urlButton} span`, {
|
||||
width: '100%',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
});
|
||||
|
||||
export const fakeWrapper = style({
|
||||
position: 'relative',
|
||||
selectors: {
|
||||
'&::after': {
|
||||
content: '""',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0,
|
||||
background: 'var(--affine-white-60)',
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,14 +0,0 @@
|
||||
import type { FC } from 'react';
|
||||
|
||||
import { settingHeader } from './share.css';
|
||||
export const SettingHeader: FC<{ title: string; subtitle?: string }> = ({
|
||||
title,
|
||||
subtitle,
|
||||
}) => {
|
||||
return (
|
||||
<div className={settingHeader}>
|
||||
<div className="title">{title}</div>
|
||||
<div className="subtitle">{subtitle}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,22 +0,0 @@
|
||||
import type { CSSProperties, FC, PropsWithChildren, ReactElement } from 'react';
|
||||
|
||||
import { settingRow } from './share.css';
|
||||
|
||||
export const SettingRow: FC<
|
||||
PropsWithChildren<{
|
||||
name: string;
|
||||
desc: string | ReactElement;
|
||||
style?: CSSProperties;
|
||||
onClick?: () => void;
|
||||
}>
|
||||
> = ({ name, desc, children, onClick, style }) => {
|
||||
return (
|
||||
<div className={settingRow} style={style} onClick={onClick}>
|
||||
<div className="left-col">
|
||||
<div className="name">{name}</div>
|
||||
<div className="desc">{desc}</div>
|
||||
</div>
|
||||
<div className="right-col">{children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,69 +0,0 @@
|
||||
import { globalStyle, style } from '@vanilla-extract/css';
|
||||
|
||||
export const settingHeader = style({
|
||||
height: '68px',
|
||||
borderBottom: '1px solid var(--affine-border-color)',
|
||||
marginBottom: '24px',
|
||||
});
|
||||
|
||||
globalStyle(`${settingHeader} .title`, {
|
||||
fontSize: 'var(--affine-font-base)',
|
||||
fontWeight: 600,
|
||||
lineHeight: '24px',
|
||||
marginBottom: '4px',
|
||||
});
|
||||
|
||||
globalStyle(`${settingHeader} .subtitle`, {
|
||||
fontSize: 'var(--affine-font-xs)',
|
||||
lineHeight: '16px',
|
||||
color: 'var(--affine-text-secondary-color)',
|
||||
});
|
||||
|
||||
export const wrapper = style({
|
||||
borderBottom: '1px solid var(--affine-border-color)',
|
||||
paddingBottom: '24px',
|
||||
marginBottom: '24px',
|
||||
selectors: {
|
||||
'&:last-of-type': {
|
||||
borderBottom: 'none',
|
||||
paddingBottom: '0',
|
||||
marginBottom: '0',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
globalStyle(`${wrapper} .title`, {
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
fontWeight: 600,
|
||||
lineHeight: '18px',
|
||||
color: 'var(--affine-text-secondary-color)',
|
||||
marginBottom: '16px',
|
||||
});
|
||||
|
||||
export const settingRow = style({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: '25px',
|
||||
color: 'var(--affine-text-primary-color)',
|
||||
});
|
||||
|
||||
globalStyle(`${settingRow} .left-col`, {
|
||||
flexShrink: 0,
|
||||
maxWidth: '80%',
|
||||
});
|
||||
globalStyle(`${settingRow} .name`, {
|
||||
marginBottom: '2px',
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
fontWeight: 600,
|
||||
});
|
||||
globalStyle(`${settingRow} .desc`, {
|
||||
fontSize: 'var(--affine-font-xs)',
|
||||
color: 'var(--affine-text-secondary-color)',
|
||||
});
|
||||
globalStyle(`${settingRow} .right-col`, {
|
||||
flexGrow: 1,
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
paddingLeft: '15px',
|
||||
});
|
||||
@@ -1,14 +0,0 @@
|
||||
import type { FC, PropsWithChildren } from 'react';
|
||||
|
||||
import { wrapper } from './share.css';
|
||||
export const Wrapper: FC<PropsWithChildren<{ title?: string }>> = ({
|
||||
title,
|
||||
children,
|
||||
}) => {
|
||||
return (
|
||||
<div className={wrapper}>
|
||||
{title ? <div className="title">{title}</div> : null}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,2 +1,2 @@
|
||||
// Some settings are not implemented yet, but need to show in the setting modal when boss is watching.
|
||||
export const IS_EXHIBITION = false;
|
||||
export const IS_EXHIBITION = true;
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { Switch } from '@affine/component';
|
||||
import { relatedLinks } from '@affine/component/contact-modal';
|
||||
import { isDesktop } from '@affine/env/constant';
|
||||
import { SettingHeader } from '@affine/component/setting-components';
|
||||
import { SettingRow } from '@affine/component/setting-components';
|
||||
import { SettingWrapper } from '@affine/component/setting-components';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { ArrowRightSmallIcon, OpenInNewIcon } from '@blocksuite/icons';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { type AppSetting, useAppSetting } from '../../../../../atoms/settings';
|
||||
import { SettingHeader } from '../../common/setting-header';
|
||||
import { SettingRow } from '../../common/setting-row';
|
||||
import { Wrapper } from '../../common/wrapper';
|
||||
import { IS_EXHIBITION } from '../../config';
|
||||
import { communityItem, communityWrapper, link } from './style.css';
|
||||
|
||||
@@ -24,8 +23,8 @@ export const AboutAffine = () => {
|
||||
return (
|
||||
<>
|
||||
<SettingHeader title={t['About AFFiNE']()} subtitle={t['None yet']()} />
|
||||
{IS_EXHIBITION && isDesktop ? (
|
||||
<Wrapper title={t['Version']()}>
|
||||
{IS_EXHIBITION && environment.isDesktop ? (
|
||||
<SettingWrapper title={t['Version']()}>
|
||||
<SettingRow
|
||||
name={t['Check for updates']()}
|
||||
desc={t['New version is ready']()}
|
||||
@@ -65,9 +64,9 @@ export const AboutAffine = () => {
|
||||
>
|
||||
<ArrowRightSmallIcon />
|
||||
</SettingRow>
|
||||
</Wrapper>
|
||||
</SettingWrapper>
|
||||
) : null}
|
||||
<Wrapper title={t['Contact with us']()}>
|
||||
<SettingWrapper title={t['Contact with us']()}>
|
||||
<a className={link} href="https://affine.pro" target="_blank">
|
||||
{t['Official Website']()}
|
||||
<OpenInNewIcon className="icon" />
|
||||
@@ -76,8 +75,8 @@ export const AboutAffine = () => {
|
||||
{t['AFFiNE Community']()}
|
||||
<OpenInNewIcon className="icon" />
|
||||
</a>
|
||||
</Wrapper>
|
||||
<Wrapper title={t['Communities']()}>
|
||||
</SettingWrapper>
|
||||
<SettingWrapper title={t['Communities']()}>
|
||||
<div className={communityWrapper}>
|
||||
{relatedLinks.map(({ icon, title, link }) => {
|
||||
return (
|
||||
@@ -94,8 +93,8 @@ export const AboutAffine = () => {
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Wrapper>
|
||||
<Wrapper title={t['Info of legal']()}>
|
||||
</SettingWrapper>
|
||||
<SettingWrapper title={t['Info of legal']()}>
|
||||
<a className={link} href="https://affine.pro/privacy" target="_blank">
|
||||
{t['Privacy']()}
|
||||
<OpenInNewIcon className="icon" />
|
||||
@@ -104,7 +103,7 @@ export const AboutAffine = () => {
|
||||
{t['Terms of Use']()}
|
||||
<OpenInNewIcon className="icon" />
|
||||
</a>
|
||||
</Wrapper>
|
||||
</SettingWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { RadioButton, RadioButtonGroup, Switch } from '@affine/component';
|
||||
import { isDesktop } from '@affine/env/constant';
|
||||
import { SettingHeader } from '@affine/component/setting-components';
|
||||
import { SettingRow } from '@affine/component/setting-components';
|
||||
import { SettingWrapper } from '@affine/component/setting-components';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { useTheme } from 'next-themes';
|
||||
import { useCallback } from 'react';
|
||||
@@ -10,9 +12,6 @@ import {
|
||||
windowFrameStyleOptions,
|
||||
} from '../../../../../atoms/settings';
|
||||
import { LanguageMenu } from '../../../language-menu';
|
||||
import { SettingHeader } from '../../common/setting-header';
|
||||
import { SettingRow } from '../../common/setting-row';
|
||||
import { Wrapper } from '../../common/wrapper';
|
||||
import { IS_EXHIBITION } from '../../config';
|
||||
import { DateFormatSetting } from './date-format-setting';
|
||||
import { settingWrapper } from './style.css';
|
||||
@@ -56,7 +55,7 @@ export const AppearanceSettings = () => {
|
||||
subtitle={t['Customize your AFFiNE Appearance']()}
|
||||
/>
|
||||
|
||||
<Wrapper title={t['Theme']()}>
|
||||
<SettingWrapper title={t['Theme']()}>
|
||||
<SettingRow
|
||||
name={t['Color Scheme']()}
|
||||
desc={t['Choose your color scheme']()}
|
||||
@@ -71,7 +70,7 @@ export const AppearanceSettings = () => {
|
||||
<LanguageMenu />
|
||||
</div>
|
||||
</SettingRow>
|
||||
{IS_EXHIBITION && isDesktop ? (
|
||||
{IS_EXHIBITION && environment.isDesktop ? (
|
||||
<SettingRow
|
||||
name={t['Client Border Style']()}
|
||||
desc={t['Customize the appearance of the client.']()}
|
||||
@@ -92,7 +91,7 @@ export const AppearanceSettings = () => {
|
||||
onChange={checked => changeSwitch('fullWidthLayout', checked)}
|
||||
/>
|
||||
</SettingRow>
|
||||
{IS_EXHIBITION && isDesktop ? (
|
||||
{IS_EXHIBITION && environment.isDesktop ? (
|
||||
<SettingRow
|
||||
name={t['Window frame style']()}
|
||||
desc={t['Customize appearance of Windows Client.']()}
|
||||
@@ -114,9 +113,9 @@ export const AppearanceSettings = () => {
|
||||
</RadioButtonGroup>
|
||||
</SettingRow>
|
||||
) : null}
|
||||
</Wrapper>
|
||||
</SettingWrapper>
|
||||
{IS_EXHIBITION ? (
|
||||
<Wrapper title={t['Date']()}>
|
||||
<SettingWrapper title={t['Date']()}>
|
||||
<SettingRow
|
||||
name={t['Date Format']()}
|
||||
desc={t['Customize your date style.']()}
|
||||
@@ -134,11 +133,11 @@ export const AppearanceSettings = () => {
|
||||
onChange={checked => changeSwitch('startWeekOnMonday', checked)}
|
||||
/>
|
||||
</SettingRow>
|
||||
</Wrapper>
|
||||
</SettingWrapper>
|
||||
) : null}
|
||||
|
||||
{isDesktop ? (
|
||||
<Wrapper title={t['Sidebar']()}>
|
||||
{environment.isDesktop ? (
|
||||
<SettingWrapper title={t['Sidebar']()}>
|
||||
<SettingRow
|
||||
name={t['Disable the noise background on the sidebar']()}
|
||||
desc={t['None yet']()}
|
||||
@@ -161,7 +160,7 @@ export const AppearanceSettings = () => {
|
||||
}
|
||||
/>
|
||||
</SettingRow>
|
||||
</Wrapper>
|
||||
</SettingWrapper>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -3,7 +3,7 @@ import { style } from '@vanilla-extract/css';
|
||||
export const settingWrapper = style({
|
||||
flexGrow: 1,
|
||||
display: 'flex',
|
||||
width: '50%',
|
||||
justifyContent: 'flex-end',
|
||||
minWidth: '150px',
|
||||
maxWidth: '250px',
|
||||
});
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { SettingHeader } from '@affine/component/setting-components';
|
||||
import { SettingWrapper } from '@affine/component/setting-components';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
|
||||
import {
|
||||
@@ -6,8 +8,6 @@ import {
|
||||
useMarkdownShortcuts,
|
||||
usePageShortcuts,
|
||||
} from '../../../../../hooks/affine/use-shortcuts';
|
||||
import { SettingHeader } from '../../common/setting-header';
|
||||
import { Wrapper } from '../../common/wrapper';
|
||||
import { shortcutRow } from './style.css';
|
||||
|
||||
export const Shortcuts = () => {
|
||||
@@ -24,7 +24,7 @@ export const Shortcuts = () => {
|
||||
title={t['Keyboard Shortcuts']()}
|
||||
subtitle={t['Check Keyboard Shortcuts quickly']()}
|
||||
/>
|
||||
<Wrapper title={t['General']()}>
|
||||
<SettingWrapper title={t['General']()}>
|
||||
{Object.entries(generalShortcuts).map(([title, shortcuts]) => {
|
||||
return (
|
||||
<div key={title} className={shortcutRow}>
|
||||
@@ -33,8 +33,8 @@ export const Shortcuts = () => {
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</Wrapper>
|
||||
<Wrapper title={t['Page']()}>
|
||||
</SettingWrapper>
|
||||
<SettingWrapper title={t['Page']()}>
|
||||
{Object.entries(pageShortcuts).map(([title, shortcuts]) => {
|
||||
return (
|
||||
<div key={title} className={shortcutRow}>
|
||||
@@ -43,8 +43,8 @@ export const Shortcuts = () => {
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</Wrapper>
|
||||
<Wrapper title={t['Edgeless']()}>
|
||||
</SettingWrapper>
|
||||
<SettingWrapper title={t['Edgeless']()}>
|
||||
{Object.entries(edgelessShortcuts).map(([title, shortcuts]) => {
|
||||
return (
|
||||
<div key={title} className={shortcutRow}>
|
||||
@@ -53,8 +53,8 @@ export const Shortcuts = () => {
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</Wrapper>
|
||||
<Wrapper title={t['Markdown Syntax']()}>
|
||||
</SettingWrapper>
|
||||
<SettingWrapper title={t['Markdown Syntax']()}>
|
||||
{Object.entries(markdownShortcuts).map(([title, shortcuts]) => {
|
||||
return (
|
||||
<div key={title} className={shortcutRow}>
|
||||
@@ -63,7 +63,7 @@ export const Shortcuts = () => {
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</Wrapper>
|
||||
</SettingWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { Modal, ModalCloseButton, ModalWrapper } from '@affine/component';
|
||||
import {
|
||||
SettingModal as SettingModalBase,
|
||||
type SettingModalProps,
|
||||
} from '@affine/component/setting-components';
|
||||
import type {
|
||||
AffineLegacyCloudWorkspace,
|
||||
LocalWorkspace,
|
||||
@@ -6,13 +9,11 @@ import type {
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { ContactWithUsIcon } from '@blocksuite/icons';
|
||||
import type { NextRouter } from 'next/router';
|
||||
import type React from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
|
||||
import { useWorkspaces } from '../../../hooks/use-workspaces';
|
||||
import type { BlockSuiteWorkspace } from '../../../shared';
|
||||
import { AccountSetting } from './account-setting';
|
||||
import {
|
||||
GeneralSetting,
|
||||
@@ -24,15 +25,7 @@ import { settingContent } from './style.css';
|
||||
import type { Workspace } from './type';
|
||||
import { WorkSpaceSetting } from './workspace-setting';
|
||||
|
||||
export type QuickSearchModalProps = {
|
||||
currentWorkspace?: BlockSuiteWorkspace;
|
||||
workspaceList?: BlockSuiteWorkspace[];
|
||||
open: boolean;
|
||||
setOpen: (value: boolean) => void;
|
||||
router: NextRouter;
|
||||
};
|
||||
|
||||
export const SettingModal: React.FC<QuickSearchModalProps> = ({
|
||||
export const SettingModal: React.FC<SettingModalProps> = ({
|
||||
open,
|
||||
setOpen,
|
||||
}) => {
|
||||
@@ -55,9 +48,6 @@ export const SettingModal: React.FC<QuickSearchModalProps> = ({
|
||||
generalKey: generalSettingList[0].key,
|
||||
isAccount: false,
|
||||
});
|
||||
const handleClose = useCallback(() => {
|
||||
setOpen(false);
|
||||
}, [setOpen]);
|
||||
|
||||
const onGeneralSettingClick = useCallback((key: GeneralSettingKeys) => {
|
||||
setCurrentRef({
|
||||
@@ -82,59 +72,41 @@ export const SettingModal: React.FC<QuickSearchModalProps> = ({
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
wrapperPosition={['center', 'center']}
|
||||
data-testid="setting-modal"
|
||||
>
|
||||
<ModalWrapper
|
||||
width={1080}
|
||||
height={760}
|
||||
style={{
|
||||
maxHeight: '85vh',
|
||||
maxWidth: '70vw',
|
||||
overflow: 'hidden',
|
||||
display: 'flex',
|
||||
}}
|
||||
>
|
||||
<ModalCloseButton top={16} right={20} onClick={handleClose} />
|
||||
<SettingModalBase open={open} setOpen={setOpen}>
|
||||
<SettingSidebar
|
||||
generalSettingList={generalSettingList}
|
||||
onGeneralSettingClick={onGeneralSettingClick}
|
||||
currentWorkspace={
|
||||
currentWorkspace as AffineLegacyCloudWorkspace | LocalWorkspace
|
||||
}
|
||||
workspaceList={workspaceList}
|
||||
onWorkspaceSettingClick={onWorkspaceSettingClick}
|
||||
selectedGeneralKey={currentRef.generalKey}
|
||||
selectedWorkspace={currentRef.workspace}
|
||||
onAccountSettingClick={onAccountSettingClick}
|
||||
/>
|
||||
|
||||
<SettingSidebar
|
||||
generalSettingList={generalSettingList}
|
||||
onGeneralSettingClick={onGeneralSettingClick}
|
||||
currentWorkspace={
|
||||
currentWorkspace as AffineLegacyCloudWorkspace | LocalWorkspace
|
||||
}
|
||||
workspaceList={workspaceList}
|
||||
onWorkspaceSettingClick={onWorkspaceSettingClick}
|
||||
selectedGeneralKey={currentRef.generalKey}
|
||||
selectedWorkspace={currentRef.workspace}
|
||||
onAccountSettingClick={onAccountSettingClick}
|
||||
/>
|
||||
|
||||
<div className={settingContent}>
|
||||
<div className="wrapper">
|
||||
<div className="content">
|
||||
{currentRef.workspace ? (
|
||||
<WorkSpaceSetting workspace={currentRef.workspace} />
|
||||
) : null}
|
||||
{currentRef.generalKey ? (
|
||||
<GeneralSetting generalKey={currentRef.generalKey} />
|
||||
) : null}
|
||||
{currentRef.isAccount ? <AccountSetting /> : null}
|
||||
</div>
|
||||
<div className="footer">
|
||||
<ContactWithUsIcon />
|
||||
<a href="https://community.affine.pro/home" target="_blank">
|
||||
{t[
|
||||
'Need more customization options? You can suggest them to us in the community.'
|
||||
]()}
|
||||
</a>
|
||||
</div>
|
||||
<div className={settingContent}>
|
||||
<div className="wrapper">
|
||||
<div className="content">
|
||||
{currentRef.workspace ? (
|
||||
<WorkSpaceSetting workspace={currentRef.workspace} />
|
||||
) : null}
|
||||
{currentRef.generalKey ? (
|
||||
<GeneralSetting generalKey={currentRef.generalKey} />
|
||||
) : null}
|
||||
{currentRef.isAccount ? <AccountSetting /> : null}
|
||||
</div>
|
||||
<div className="footer">
|
||||
<ContactWithUsIcon />
|
||||
<a href="https://community.affine.pro/home" target="_blank">
|
||||
{t[
|
||||
'Need more customization options? You can suggest them to us in the community.'
|
||||
]()}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</ModalWrapper>
|
||||
</Modal>
|
||||
</div>
|
||||
</SettingModalBase>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { UserAvatar } from '@affine/component/user-avatar';
|
||||
import { WorkspaceAvatar } from '@affine/component/workspace-avatar';
|
||||
import type {
|
||||
AffineLegacyCloudWorkspace,
|
||||
@@ -5,6 +6,7 @@ import type {
|
||||
} from '@affine/env/workspace';
|
||||
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
|
||||
import type {
|
||||
GeneralSettingKeys,
|
||||
@@ -84,7 +86,13 @@ export const SettingSidebar = ({
|
||||
</div>
|
||||
|
||||
<div className={accountButton} onClick={onAccountSettingClick}>
|
||||
<div className="avatar"></div>
|
||||
<UserAvatar
|
||||
size={28}
|
||||
name="Account NameAccount Name"
|
||||
url={''}
|
||||
className="avatar"
|
||||
/>
|
||||
|
||||
<div className="content">
|
||||
<div className="name" title="xxx">
|
||||
Account NameAccount Name
|
||||
|
||||
@@ -3,9 +3,7 @@ import { globalStyle, style } from '@vanilla-extract/css';
|
||||
export const settingSlideBar = style({
|
||||
width: '25%',
|
||||
maxWidth: '242px',
|
||||
// TODO: use color variable
|
||||
// background: 'var(--affine-background-secondary-color)',
|
||||
backgroundColor: '#F4F4F5',
|
||||
background: 'var(--affine-background-secondary-color)',
|
||||
padding: '20px 16px',
|
||||
height: '100%',
|
||||
flexShrink: 0,
|
||||
@@ -107,14 +105,9 @@ export const accountButton = style({
|
||||
});
|
||||
|
||||
globalStyle(`${accountButton} .avatar`, {
|
||||
width: '28px',
|
||||
height: '28px',
|
||||
border: '1px solid',
|
||||
borderColor: 'var(--affine-white)',
|
||||
borderRadius: '14px',
|
||||
flexShrink: '0',
|
||||
marginRight: '10px',
|
||||
background: 'red',
|
||||
});
|
||||
globalStyle(`${accountButton} .content`, {
|
||||
flexGrow: '1',
|
||||
|
||||
@@ -140,11 +140,7 @@ export const Setting: FC = () => {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<SettingModal
|
||||
open={openSettingModal}
|
||||
setOpen={setOpenSettingModalAtom}
|
||||
router={router}
|
||||
/>
|
||||
<SettingModal open={openSettingModal} setOpen={setOpenSettingModalAtom} />
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user