mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-11 20:08:37 +00:00
feat: support remove user & workspace avatar (#4302)
This commit is contained in:
@@ -2,11 +2,12 @@ import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { RootWorkspaceMetadata } from '@affine/workspace/atom';
|
||||
import { SettingsIcon } from '@blocksuite/icons';
|
||||
import { Avatar } from '@toeverything/components/avatar';
|
||||
import { useBlockSuiteWorkspaceAvatarUrl } from '@toeverything/hooks/use-block-suite-workspace-avatar-url';
|
||||
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
||||
import { useStaticBlockSuiteWorkspace } from '@toeverything/infra/__internal__/react';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { WorkspaceAvatar } from '../../workspace-avatar';
|
||||
import {
|
||||
StyledCard,
|
||||
StyledSettingLink,
|
||||
@@ -68,6 +69,7 @@ export const WorkspaceCard = ({
|
||||
// const t = useAFFiNEI18N();
|
||||
const workspace = useStaticBlockSuiteWorkspace(meta.id);
|
||||
const [name] = useBlockSuiteWorkspaceName(workspace);
|
||||
const [workspaceAvatar] = useBlockSuiteWorkspaceAvatarUrl(workspace);
|
||||
|
||||
return (
|
||||
<StyledCard
|
||||
@@ -77,8 +79,7 @@ export const WorkspaceCard = ({
|
||||
}, [onClick, meta.id])}
|
||||
active={workspace.id === currentWorkspaceId}
|
||||
>
|
||||
<WorkspaceAvatar size={28} workspace={workspace} />
|
||||
|
||||
<Avatar size={28} url={workspaceAvatar} name={name} colorfulFallback />
|
||||
<StyledWorkspaceInfo>
|
||||
<StyledWorkspaceTitleArea style={{ display: 'flex' }}>
|
||||
<StyledWorkspaceTitle>{name}</StyledWorkspaceTitle>
|
||||
|
||||
@@ -23,12 +23,6 @@ export const AcceptInvitePage = ({
|
||||
url={inviteInfo.user.avatarUrl || ''}
|
||||
name={inviteInfo.user.name}
|
||||
size={20}
|
||||
// FIXME: fix it in @toeverything/components/avatar
|
||||
imageProps={{
|
||||
style: {
|
||||
objectFit: 'cover',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<span className={styles.inviteName}>{inviteInfo.user.name}</span>
|
||||
{t['invited you to join']()}
|
||||
@@ -37,7 +31,7 @@ export const AcceptInvitePage = ({
|
||||
name={inviteInfo.workspace.name}
|
||||
size={20}
|
||||
style={{ marginLeft: 4 }}
|
||||
colorfulFallback={true}
|
||||
colorfulFallback
|
||||
/>
|
||||
<span className={styles.inviteName}>{inviteInfo.workspace.name}</span>
|
||||
</FlexWrapper>
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import * as Avatar from '@radix-ui/react-avatar';
|
||||
import clsx from 'clsx';
|
||||
import type { CSSProperties } from 'react';
|
||||
|
||||
import * as style from './style.css';
|
||||
|
||||
export interface UserAvatar {
|
||||
size?: number;
|
||||
url?: string;
|
||||
name?: string;
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
|
||||
export const UserAvatar = ({
|
||||
size = 20,
|
||||
style: propsStyles = {},
|
||||
url,
|
||||
name,
|
||||
className,
|
||||
}: UserAvatar) => {
|
||||
return (
|
||||
<Avatar.Root
|
||||
style={{
|
||||
width: size,
|
||||
height: size,
|
||||
...propsStyles,
|
||||
}}
|
||||
className={clsx(style.avatarRoot, className)}
|
||||
>
|
||||
<Avatar.Image className={style.avatarImage} src={url} alt={name} />
|
||||
<Avatar.Fallback className={style.avatarFallback} delayMs={600}>
|
||||
{name?.slice(0, 1) || 'A'}
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
);
|
||||
};
|
||||
@@ -1,31 +0,0 @@
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const avatarRoot = style({
|
||||
display: 'inline-flex',
|
||||
flexShrink: 0,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
verticalAlign: 'middle',
|
||||
overflow: 'hidden',
|
||||
userSelect: 'none',
|
||||
borderRadius: '100%',
|
||||
});
|
||||
|
||||
export const avatarImage = style({
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'cover',
|
||||
borderRadius: 'inherit',
|
||||
});
|
||||
export const avatarFallback = style({
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: 'var(--affine-primary-color)',
|
||||
color: 'var(--affine-white)',
|
||||
fontSize: 'var(--affine-font-base)',
|
||||
lineHeight: '1',
|
||||
fontWeight: '500',
|
||||
});
|
||||
@@ -1,69 +0,0 @@
|
||||
import clsx from 'clsx';
|
||||
import { useMemo, useRef, useState } from 'react';
|
||||
|
||||
import {
|
||||
DefaultAvatarBottomItemStyle,
|
||||
DefaultAvatarBottomItemWithAnimationStyle,
|
||||
DefaultAvatarContainerStyle,
|
||||
DefaultAvatarMiddleItemStyle,
|
||||
DefaultAvatarMiddleItemWithAnimationStyle,
|
||||
DefaultAvatarTopItemStyle,
|
||||
} from './index.css';
|
||||
|
||||
const colorsSchema = [
|
||||
['#FF0000', '#FF00E5', '#FFAE73'],
|
||||
['#FF5C00', '#FFC700', '#FFE073'],
|
||||
['#FFDA16', '#FFFBA6', '#FFBE73'],
|
||||
['#8CD317', '#FCFF5C', '#67CAE9'],
|
||||
['#28E19F', '#89FFC6', '#39A880'],
|
||||
['#35B7E0', '#77FFCE', '#5076FF'],
|
||||
['#3D39FF', '#77BEFF', '#3502FF'],
|
||||
['#BD08EB', '#755FFF', '#6967E4'],
|
||||
];
|
||||
|
||||
export const DefaultAvatar = ({ name: propsName }: { name: string }) => {
|
||||
// Sometimes name is ' '
|
||||
const name = propsName || 'A';
|
||||
const colors = useMemo(() => {
|
||||
const index = name[0].toUpperCase().charCodeAt(0);
|
||||
return colorsSchema[index % colorsSchema.length];
|
||||
}, [name]);
|
||||
|
||||
const timer = useRef<number>();
|
||||
|
||||
const [topColor, middleColor, bottomColor] = colors;
|
||||
const [isHover, setIsHover] = useState(false);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={DefaultAvatarContainerStyle}
|
||||
onMouseEnter={() => {
|
||||
timer.current = window.setTimeout(() => {
|
||||
setIsHover(true);
|
||||
}, 300);
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
clearTimeout(timer.current);
|
||||
setIsHover(false);
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={DefaultAvatarTopItemStyle}
|
||||
style={{ background: bottomColor }}
|
||||
></div>
|
||||
<div
|
||||
className={clsx(DefaultAvatarMiddleItemStyle, {
|
||||
[DefaultAvatarMiddleItemWithAnimationStyle]: isHover,
|
||||
})}
|
||||
style={{ background: middleColor }}
|
||||
></div>
|
||||
<div
|
||||
className={clsx(DefaultAvatarBottomItemStyle, {
|
||||
[DefaultAvatarBottomItemWithAnimationStyle]: isHover,
|
||||
})}
|
||||
style={{ background: topColor }}
|
||||
></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default DefaultAvatar;
|
||||
@@ -1,146 +0,0 @@
|
||||
import { keyframes, style } from '@vanilla-extract/css';
|
||||
export const avatarStyle = style({
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
color: '#fff',
|
||||
borderRadius: '50%',
|
||||
overflow: 'hidden',
|
||||
display: 'inline-block',
|
||||
verticalAlign: 'middle',
|
||||
});
|
||||
|
||||
export const avatarImageStyle = style({
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'cover',
|
||||
objectPosition: 'center',
|
||||
display: 'block',
|
||||
});
|
||||
|
||||
const bottomAnimation = keyframes({
|
||||
'0%': {
|
||||
top: '-44%',
|
||||
left: '-11%',
|
||||
transform: 'matrix(-0.29, -0.96, 0.94, -0.35, 0, 0)',
|
||||
},
|
||||
'16%': {
|
||||
left: '-18%',
|
||||
top: '-51%',
|
||||
transform: 'matrix(-0.73, -0.69, 0.64, -0.77, 0, 0)',
|
||||
},
|
||||
'32%': {
|
||||
left: '-7%',
|
||||
top: '-40%',
|
||||
transform: 'matrix(-0.97, -0.23, 0.16, -0.99, 0, 0)',
|
||||
},
|
||||
'48%': {
|
||||
left: '-15%',
|
||||
top: '-39%',
|
||||
transform: 'matrix(-0.88, 0.48, -0.6, -0.8, 0, 0)',
|
||||
},
|
||||
'64%': {
|
||||
left: '-7%',
|
||||
top: '-40%',
|
||||
transform: 'matrix(-0.97, -0.23, 0.16, -0.99, 0, 0)',
|
||||
},
|
||||
'80%': {
|
||||
left: '-18%',
|
||||
top: '-51%',
|
||||
transform: 'matrix(-0.73, -0.69, 0.64, -0.77, 0, 0)',
|
||||
},
|
||||
'100%': {
|
||||
top: '-44%',
|
||||
left: '-11%',
|
||||
transform: 'matrix(-0.29, -0.96, 0.94, -0.35, 0, 0)',
|
||||
},
|
||||
});
|
||||
const middleAnimation = keyframes({
|
||||
'0%': {
|
||||
left: '-30px',
|
||||
top: '-30px',
|
||||
transform: 'matrix(-0.48, -0.88, 0.8, -0.6, 0, 0)',
|
||||
},
|
||||
'16%': {
|
||||
left: '-37px',
|
||||
top: '-37px',
|
||||
transform: 'matrix(-0.86, -0.52, 0.39, -0.92, 0, 0)',
|
||||
},
|
||||
'32%': {
|
||||
left: '-20px',
|
||||
top: '-10px',
|
||||
transform: 'matrix(-1, -0.02, -0.12, -0.99, 0, 0)',
|
||||
},
|
||||
'48%': {
|
||||
left: '-27px',
|
||||
top: '-2px',
|
||||
transform: 'matrix(-0.88, 0.48, -0.6, -0.8, 0, 0)',
|
||||
},
|
||||
'64%': {
|
||||
left: '-20px',
|
||||
top: '-10px',
|
||||
transform: 'matrix(-1, -0.02, -0.12, -0.99, 0, 0)',
|
||||
},
|
||||
'80%': {
|
||||
left: '-37px',
|
||||
top: '-37px',
|
||||
transform: 'matrix(-0.86, -0.52, 0.39, -0.92, 0, 0)',
|
||||
},
|
||||
'100%': {
|
||||
left: '-30px',
|
||||
top: '-30px',
|
||||
transform: 'matrix(-0.48, -0.88, 0.8, -0.6, 0, 0)',
|
||||
},
|
||||
});
|
||||
|
||||
export const DefaultAvatarContainerStyle = style({
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
position: 'relative',
|
||||
borderRadius: '50%',
|
||||
overflow: 'hidden',
|
||||
});
|
||||
|
||||
export const DefaultAvatarMiddleItemStyle = style({
|
||||
width: '83%',
|
||||
height: '81%',
|
||||
position: 'absolute',
|
||||
left: '-30%',
|
||||
top: '-30%',
|
||||
transform: 'matrix(-0.48, -0.88, 0.8, -0.6, 0, 0)',
|
||||
opacity: '0.8',
|
||||
filter: 'blur(12px)',
|
||||
transformOrigin: 'center center',
|
||||
animation: `${middleAnimation} 3s ease-in-out forwards infinite`,
|
||||
animationPlayState: 'paused',
|
||||
});
|
||||
export const DefaultAvatarMiddleItemWithAnimationStyle = style({
|
||||
animationPlayState: 'running',
|
||||
});
|
||||
export const DefaultAvatarBottomItemStyle = style({
|
||||
width: '98%',
|
||||
height: '97%',
|
||||
position: 'absolute',
|
||||
top: '-44%',
|
||||
left: '-11%',
|
||||
transform: 'matrix(-0.29, -0.96, 0.94, -0.35, 0, 0)',
|
||||
opacity: '0.8',
|
||||
filter: 'blur(12px)',
|
||||
transformOrigin: 'center center',
|
||||
willChange: 'left, top, transform',
|
||||
animation: `${bottomAnimation} 3s ease-in-out forwards infinite`,
|
||||
animationPlayState: 'paused',
|
||||
});
|
||||
export const DefaultAvatarBottomItemWithAnimationStyle = style({
|
||||
animationPlayState: 'running',
|
||||
});
|
||||
export const DefaultAvatarTopItemStyle = style({
|
||||
width: '104%',
|
||||
height: '94%',
|
||||
position: 'absolute',
|
||||
right: '-30%',
|
||||
top: '-30%',
|
||||
opacity: '0.8',
|
||||
filter: 'blur(12px)',
|
||||
transform: 'matrix(-0.28, -0.96, 0.93, -0.37, 0, 0)',
|
||||
transformOrigin: 'center center',
|
||||
});
|
||||
@@ -1,70 +0,0 @@
|
||||
import type { Workspace } from '@blocksuite/store';
|
||||
import * as RadixAvatar from '@radix-ui/react-avatar';
|
||||
import { useBlockSuiteWorkspaceAvatarUrl } from '@toeverything/hooks/use-block-suite-workspace-avatar-url';
|
||||
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import { DefaultAvatar } from './default-avatar';
|
||||
import { avatarImageStyle, avatarStyle } from './index.css';
|
||||
|
||||
export interface WorkspaceAvatarProps {
|
||||
size?: number;
|
||||
workspace: Workspace | null;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export interface BlockSuiteWorkspaceAvatar
|
||||
extends Omit<WorkspaceAvatarProps, 'workspace'> {
|
||||
workspace: Workspace;
|
||||
}
|
||||
|
||||
export const BlockSuiteWorkspaceAvatar = ({
|
||||
size,
|
||||
workspace,
|
||||
...props
|
||||
}: BlockSuiteWorkspaceAvatar) => {
|
||||
const [avatar] = useBlockSuiteWorkspaceAvatarUrl(workspace);
|
||||
const [name] = useBlockSuiteWorkspaceName(workspace);
|
||||
|
||||
return (
|
||||
<RadixAvatar.Root
|
||||
{...props}
|
||||
className={clsx(avatarStyle, props.className)}
|
||||
style={{
|
||||
height: size,
|
||||
width: size,
|
||||
}}
|
||||
>
|
||||
<RadixAvatar.Image className={avatarImageStyle} src={avatar} alt={name} />
|
||||
<RadixAvatar.Fallback>
|
||||
<DefaultAvatar name={name}></DefaultAvatar>
|
||||
</RadixAvatar.Fallback>
|
||||
</RadixAvatar.Root>
|
||||
);
|
||||
};
|
||||
|
||||
export const WorkspaceAvatar = ({
|
||||
size = 20,
|
||||
workspace,
|
||||
...props
|
||||
}: WorkspaceAvatarProps) => {
|
||||
if (workspace) {
|
||||
return (
|
||||
<BlockSuiteWorkspaceAvatar {...props} size={size} workspace={workspace} />
|
||||
);
|
||||
}
|
||||
return (
|
||||
<RadixAvatar.Root
|
||||
{...props}
|
||||
className={clsx(avatarStyle, props.className)}
|
||||
style={{
|
||||
height: size,
|
||||
width: size,
|
||||
}}
|
||||
>
|
||||
<RadixAvatar.Fallback>
|
||||
<DefaultAvatar name="A"></DefaultAvatar>
|
||||
</RadixAvatar.Fallback>
|
||||
</RadixAvatar.Root>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user