[Glitch] Update `Avatar`, `AvatarComposite`, and `AvatarOverlay` components (#2508)

Various ports including 8dfe5179ee,
d1de7fb7fa and
9f8d34620b.

Co-authored-by: Eugen Rochko <eugen@zeonfederated.com>
Co-authored-by: fusagiko / takayamaki <24884114+takayamaki@users.noreply.github.com>
This commit is contained in:
Claire 2023-12-09 18:33:42 +01:00 committed by GitHub
parent c0e562916c
commit b2647bc3f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 93 additions and 94 deletions

View File

@ -5,49 +5,44 @@ import { autoPlayGif } from '../initial_state';
import type { Account } from '../types/resources'; import type { Account } from '../types/resources';
interface Props { interface Props {
account: Account | undefined; account: Account | undefined; // FIXME: remove `undefined` once we know for sure its always there
className?: string;
size: number; size: number;
style?: React.CSSProperties; style?: React.CSSProperties;
inline?: boolean; inline?: boolean;
animate?: boolean;
} }
export const Avatar: React.FC<Props> = ({ export const Avatar: React.FC<Props> = ({
account, account,
className, animate = autoPlayGif,
size = 20, size = 20,
inline = false, inline = false,
style: styleFromParent, style: styleFromParent,
}) => { }) => {
const { hovering, handleMouseEnter, handleMouseLeave } = const { hovering, handleMouseEnter, handleMouseLeave } = useHovering(animate);
useHovering(autoPlayGif);
const style = { const style = {
...styleFromParent, ...styleFromParent,
width: `${size}px`, width: `${size}px`,
height: `${size}px`, height: `${size}px`,
backgroundSize: `${size}px ${size}px`,
}; };
if (account) { const src =
style.backgroundImage = `url(${account.get( hovering || animate
hovering ? 'avatar' : 'avatar_static', ? account?.get('avatar')
)})`; : account?.get('avatar_static');
}
return ( return (
<div <div
className={classNames( className={classNames('account__avatar', {
'account__avatar', 'account__avatar-inline': inline,
{ 'account__avatar-inline': inline }, })}
className,
)}
onMouseEnter={handleMouseEnter} onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave} onMouseLeave={handleMouseLeave}
style={style} style={style}
data-avatar-of={account && `@${account.get('acct')}`} data-avatar-of={account && `@${account.get('acct')}`}
role='img' >
aria-label={account?.get('acct')} {src && <img src={src} alt={account?.get('acct')} />}
/> </div>
); );
}; };

View File

@ -5,6 +5,8 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import { autoPlayGif } from '../initial_state'; import { autoPlayGif } from '../initial_state';
import { Avatar } from './avatar';
export default class AvatarComposite extends PureComponent { export default class AvatarComposite extends PureComponent {
static propTypes = { static propTypes = {
@ -76,12 +78,12 @@ export default class AvatarComposite extends PureComponent {
bottom: bottom, bottom: bottom,
width: `${width}%`, width: `${width}%`,
height: `${height}%`, height: `${height}%`,
backgroundSize: 'cover',
backgroundImage: `url(${account.get(animate ? 'avatar' : 'avatar_static')})`,
}; };
return ( return (
<div key={account.get('id')} style={style} data-avatar-of={`@${account.get('acct')}`} /> <div key={account.get('id')} style={style}>
<Avatar account={account} animate={animate} />
</div>
); );
} }

View File

@ -1,39 +0,0 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { autoPlayGif } from 'flavours/glitch/initial_state';
export default class AvatarOverlay extends PureComponent {
static propTypes = {
account: ImmutablePropTypes.map.isRequired,
friend: ImmutablePropTypes.map.isRequired,
animate: PropTypes.bool,
};
static defaultProps = {
animate: autoPlayGif,
};
render() {
const { account, friend, animate } = this.props;
const baseStyle = {
backgroundImage: `url(${account.get(animate ? 'avatar' : 'avatar_static')})`,
};
const overlayStyle = {
backgroundImage: `url(${friend.get(animate ? 'avatar' : 'avatar_static')})`,
};
return (
<div className='account__avatar-overlay'>
<div className='account__avatar-overlay-base' style={baseStyle} data-avatar-of={`@${account.get('acct')}`} />
<div className='account__avatar-overlay-overlay' style={overlayStyle} data-avatar-of={`@${friend.get('acct')}`} />
</div>
);
}
}

View File

@ -0,0 +1,56 @@
import { useHovering } from '../hooks/useHovering';
import { autoPlayGif } from '../initial_state';
import type { Account } from '../types/resources';
interface Props {
account: Account | undefined; // FIXME: remove `undefined` once we know for sure its always there
friend: Account | undefined; // FIXME: remove `undefined` once we know for sure its always there
size?: number;
baseSize?: number;
overlaySize?: number;
}
export const AvatarOverlay: React.FC<Props> = ({
account,
friend,
size = 46,
baseSize = 36,
overlaySize = 24,
}) => {
const { hovering, handleMouseEnter, handleMouseLeave } =
useHovering(autoPlayGif);
const accountSrc = hovering
? account?.get('avatar')
: account?.get('avatar_static');
const friendSrc = hovering
? friend?.get('avatar')
: friend?.get('avatar_static');
return (
<div
className='account__avatar-overlay'
style={{ width: size, height: size }}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<div className='account__avatar-overlay-base'>
<div
className='account__avatar'
style={{ width: `${baseSize}px`, height: `${baseSize}px` }}
data-avatar-of={`@${account?.get('acct')}`}
>
{accountSrc && <img src={accountSrc} alt={account?.get('acct')} />}
</div>
</div>
<div className='account__avatar-overlay-overlay'>
<div
className='account__avatar'
style={{ width: `${overlaySize}px`, height: `${overlaySize}px` }}
data-avatar-of={`@${friend?.get('acct')}`}
>
{friendSrc && <img src={friendSrc} alt={friend?.get('acct')} />}
</div>
</div>
</div>
);
};

View File

@ -6,7 +6,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
// Mastodon imports. // Mastodon imports.
import { Avatar } from './avatar'; import { Avatar } from './avatar';
import AvatarOverlay from './avatar_overlay'; import { AvatarOverlay } from './avatar_overlay';
import { DisplayName } from './display_name'; import { DisplayName } from './display_name';
export default class StatusHeader extends PureComponent { export default class StatusHeader extends PureComponent {
@ -39,7 +39,7 @@ export default class StatusHeader extends PureComponent {
let statusAvatar; let statusAvatar;
if (friend === undefined || friend === null) { if (friend === undefined || friend === null) {
statusAvatar = <Avatar account={account} size={48} />; statusAvatar = <Avatar account={account} size={46} />;
} else { } else {
statusAvatar = <AvatarOverlay account={account} friend={friend} />; statusAvatar = <AvatarOverlay account={account} friend={friend} />;
} }

View File

@ -8,7 +8,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
import AvatarOverlay from '../../../components/avatar_overlay'; import { AvatarOverlay } from '../../../components/avatar_overlay';
import { DisplayName } from '../../../components/display_name'; import { DisplayName } from '../../../components/display_name';
class MovedNote extends ImmutablePureComponent { class MovedNote extends ImmutablePureComponent {

View File

@ -5,7 +5,7 @@ import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import AvatarOverlay from 'flavours/glitch/components/avatar_overlay'; import { AvatarOverlay } from 'flavours/glitch/components/avatar_overlay';
import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp'; import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp';
// This needs to be kept in sync with app/models/report.rb // This needs to be kept in sync with app/models/report.rb

View File

@ -85,10 +85,14 @@
display: block; display: block;
position: relative; position: relative;
cursor: pointer; overflow: hidden;
width: 36px;
height: 36px; img {
background-size: 36px 36px; display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
&-inline { &-inline {
display: inline-block; display: inline-block;
@ -102,7 +106,7 @@
overflow: hidden; overflow: hidden;
position: relative; position: relative;
& div { & > div {
@include avatar-radius; @include avatar-radius;
float: left; float: left;
@ -110,6 +114,11 @@
box-sizing: border-box; box-sizing: border-box;
} }
.account__avatar {
width: 100% !important;
height: 100% !important;
}
&__label { &__label {
display: block; display: block;
position: absolute; position: absolute;
@ -125,37 +134,13 @@
} }
.account__avatar-overlay { .account__avatar-overlay {
@include avatar-size(48px);
position: relative; position: relative;
&-base {
@include avatar-radius;
@include avatar-size(36px);
img {
@include avatar-radius;
width: 100%;
height: 100%;
}
}
&-overlay { &-overlay {
@include avatar-radius;
@include avatar-size(24px);
position: absolute; position: absolute;
bottom: 0; bottom: 0;
inset-inline-end: 0; inset-inline-end: 0;
z-index: 1; z-index: 1;
img {
@include avatar-radius;
width: 100%;
height: 100%;
}
} }
} }