Skip to content

Commit dba4be1

Browse files
authored
Change appearance of account cards in web UI (#17689)
* Change appearance of account cards in web UI * Various fixes and improvements * Various fixes and improvements
1 parent 292c75a commit dba4be1

10 files changed

Lines changed: 178 additions & 347 deletions

File tree

app/javascript/mastodon/features/directory/components/account_card.js

Lines changed: 80 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,28 @@ import { makeGetAccount } from 'mastodon/selectors';
77
import Avatar from 'mastodon/components/avatar';
88
import DisplayName from 'mastodon/components/display_name';
99
import Permalink from 'mastodon/components/permalink';
10-
import RelativeTimestamp from 'mastodon/components/relative_timestamp';
11-
import IconButton from 'mastodon/components/icon_button';
10+
import Button from 'mastodon/components/button';
1211
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
1312
import { autoPlayGif, me, unfollowModal } from 'mastodon/initial_state';
1413
import ShortNumber from 'mastodon/components/short_number';
1514
import {
1615
followAccount,
1716
unfollowAccount,
18-
blockAccount,
1917
unblockAccount,
2018
unmuteAccount,
2119
} from 'mastodon/actions/accounts';
2220
import { openModal } from 'mastodon/actions/modal';
23-
import { initMuteModal } from 'mastodon/actions/mutes';
21+
import classNames from 'classnames';
2422

2523
const messages = defineMessages({
26-
follow: { id: 'account.follow', defaultMessage: 'Follow' },
2724
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
28-
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' },
29-
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
30-
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
31-
unfollowConfirm: {
32-
id: 'confirmations.unfollow.confirm',
33-
defaultMessage: 'Unfollow',
34-
},
25+
follow: { id: 'account.follow', defaultMessage: 'Follow' },
26+
cancel_follow_request: { id: 'account.cancel_follow_request', defaultMessage: 'Cancel follow request' },
27+
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' },
28+
unblock: { id: 'account.unblock_short', defaultMessage: 'Unblock' },
29+
unmute: { id: 'account.unmute_short', defaultMessage: 'Unmute' },
30+
unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' },
31+
edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
3532
});
3633

3734
const makeMapStateToProps = () => {
@@ -75,18 +72,15 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
7572
onBlock(account) {
7673
if (account.getIn(['relationship', 'blocking'])) {
7774
dispatch(unblockAccount(account.get('id')));
78-
} else {
79-
dispatch(blockAccount(account.get('id')));
8075
}
8176
},
8277

8378
onMute(account) {
8479
if (account.getIn(['relationship', 'muting'])) {
8580
dispatch(unmuteAccount(account.get('id')));
86-
} else {
87-
dispatch(initMuteModal(account));
8881
}
8982
},
83+
9084
});
9185

9286
export default
@@ -138,130 +132,92 @@ class AccountCard extends ImmutablePureComponent {
138132

139133
handleMute = () => {
140134
this.props.onMute(this.props.account);
141-
};
135+
}
136+
137+
handleEditProfile = () => {
138+
window.open('/settings/profile', '_blank');
139+
}
142140

143141
render() {
144142
const { account, intl } = this.props;
145143

146-
let buttons;
147-
148-
if (
149-
account.get('id') !== me &&
150-
account.get('relationship', null) !== null
151-
) {
152-
const following = account.getIn(['relationship', 'following']);
153-
const requested = account.getIn(['relationship', 'requested']);
154-
const blocking = account.getIn(['relationship', 'blocking']);
155-
const muting = account.getIn(['relationship', 'muting']);
156-
157-
if (requested) {
158-
buttons = (
159-
<IconButton
160-
disabled
161-
icon='hourglass'
162-
title={intl.formatMessage(messages.requested)}
163-
/>
164-
);
165-
} else if (blocking) {
166-
buttons = (
167-
<IconButton
168-
active
169-
icon='unlock'
170-
title={intl.formatMessage(messages.unblock, {
171-
name: account.get('username'),
172-
})}
173-
onClick={this.handleBlock}
174-
/>
175-
);
176-
} else if (muting) {
177-
buttons = (
178-
<IconButton
179-
active
180-
icon='volume-up'
181-
title={intl.formatMessage(messages.unmute, {
182-
name: account.get('username'),
183-
})}
184-
onClick={this.handleMute}
185-
/>
186-
);
187-
} else if (!account.get('moved') || following) {
188-
buttons = (
189-
<IconButton
190-
icon={following ? 'user-times' : 'user-plus'}
191-
title={intl.formatMessage(
192-
following ? messages.unfollow : messages.follow,
193-
)}
194-
onClick={this.handleFollow}
195-
active={following}
196-
/>
197-
);
144+
let actionBtn;
145+
146+
if (me !== account.get('id')) {
147+
if (!account.get('relationship')) { // Wait until the relationship is loaded
148+
actionBtn = '';
149+
} else if (account.getIn(['relationship', 'requested'])) {
150+
actionBtn = <Button className={classNames('logo-button')} text={intl.formatMessage(messages.cancel_follow_request)} title={intl.formatMessage(messages.requested)} onClick={this.handleFollow} />;
151+
} else if (account.getIn(['relationship', 'muting'])) {
152+
actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.unmute)} onClick={this.handleMute} />;
153+
} else if (!account.getIn(['relationship', 'blocking'])) {
154+
actionBtn = <Button disabled={account.getIn(['relationship', 'blocked_by'])} className={classNames('logo-button', { 'button--destructive': account.getIn(['relationship', 'following']) })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.handleFollow} />;
155+
} else if (account.getIn(['relationship', 'blocking'])) {
156+
actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.unblock)} onClick={this.handleBlock} />;
198157
}
158+
} else {
159+
actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.edit_profile)} onClick={this.handleEditProfile} />;
199160
}
200161

201162
return (
202-
<div className='directory__card'>
203-
<div className='directory__card__img'>
204-
<img
205-
src={
206-
autoPlayGif ? account.get('header') : account.get('header_static')
207-
}
208-
alt=''
209-
/>
210-
</div>
163+
<div className='account-card'>
164+
<Permalink href={account.get('url')} to={`/@${account.get('acct')}`} className='account-card__permalink'>
165+
<div className='account-card__header'>
166+
<img
167+
src={
168+
autoPlayGif ? account.get('header') : account.get('header_static')
169+
}
170+
alt=''
171+
/>
172+
</div>
211173

212-
<div className='directory__card__bar'>
213-
<Permalink
214-
className='directory__card__bar__name'
215-
href={account.get('url')}
216-
to={`/@${account.get('acct')}`}
217-
>
218-
<Avatar account={account} size={48} />
174+
<div className='account-card__title'>
175+
<div className='account-card__title__avatar'><Avatar account={account} size={56} /></div>
219176
<DisplayName account={account} />
220-
</Permalink>
221-
222-
<div className='directory__card__bar__relationship account__relationship'>
223-
{buttons}
224177
</div>
225-
</div>
178+
</Permalink>
226179

227-
<div className='directory__card__extra' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
180+
{account.get('note').length > 0 && (
228181
<div
229-
className='account__header__content translate'
182+
className='account-card__bio translate'
183+
onMouseEnter={this.handleMouseEnter}
184+
onMouseLeave={this.handleMouseLeave}
230185
dangerouslySetInnerHTML={{ __html: account.get('note_emojified') }}
231186
/>
232-
</div>
233-
234-
<div className='directory__card__extra'>
235-
<div className='accounts-table__count'>
236-
<ShortNumber value={account.get('statuses_count')} />
237-
<small>
238-
<FormattedMessage id='account.posts' defaultMessage='Toots' />
239-
</small>
187+
)}
188+
189+
<div className='account-card__actions'>
190+
<div className='account-card__counters'>
191+
<div className='account-card__counters__item'>
192+
<ShortNumber value={account.get('statuses_count')} />
193+
<small>
194+
<FormattedMessage id='account.posts' defaultMessage='Toots' />
195+
</small>
196+
</div>
197+
198+
<div className='account-card__counters__item'>
199+
<ShortNumber value={account.get('followers_count')} />{' '}
200+
<small>
201+
<FormattedMessage
202+
id='account.followers'
203+
defaultMessage='Followers'
204+
/>
205+
</small>
206+
</div>
207+
208+
<div className='account-card__counters__item'>
209+
<ShortNumber value={account.get('following_count')} />{' '}
210+
<small>
211+
<FormattedMessage
212+
id='account.following'
213+
defaultMessage='Following'
214+
/>
215+
</small>
216+
</div>
240217
</div>
241-
<div className='accounts-table__count'>
242-
<ShortNumber value={account.get('followers_count')} />{' '}
243-
<small>
244-
<FormattedMessage
245-
id='account.followers'
246-
defaultMessage='Followers'
247-
/>
248-
</small>
249-
</div>
250-
<div className='accounts-table__count'>
251-
{account.get('last_status_at') === null ? (
252-
<FormattedMessage
253-
id='account.never_active'
254-
defaultMessage='Never'
255-
/>
256-
) : (
257-
<RelativeTimestamp timestamp={account.get('last_status_at')} />
258-
)}{' '}
259-
<small>
260-
<FormattedMessage
261-
id='account.last_status'
262-
defaultMessage='Last active'
263-
/>
264-
</small>
218+
219+
<div className='account-card__actions__button'>
220+
{actionBtn}
265221
</div>
266222
</div>
267223
</div>

app/javascript/mastodon/features/directory/index.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ import { fetchDirectory, expandDirectory } from 'mastodon/actions/directory';
1010
import { List as ImmutableList } from 'immutable';
1111
import AccountCard from './components/account_card';
1212
import RadioButton from 'mastodon/components/radio_button';
13-
import classNames from 'classnames';
1413
import LoadMore from 'mastodon/components/load_more';
1514
import ScrollContainer from 'mastodon/containers/scroll_container';
15+
import LoadingIndicator from 'mastodon/components/loading_indicator';
1616

1717
const messages = defineMessages({
1818
title: { id: 'column.directory', defaultMessage: 'Browse profiles' },
@@ -129,7 +129,7 @@ class Directory extends React.PureComponent {
129129
const pinned = !!columnId;
130130

131131
const scrollableArea = (
132-
<div className='scrollable' style={{ background: 'transparent' }}>
132+
<div className='scrollable'>
133133
<div className='filter-form'>
134134
<div className='filter-form__column' role='group'>
135135
<RadioButton name='order' value='active' label={intl.formatMessage(messages.recentlyActive)} checked={order === 'active'} onChange={this.handleChangeOrder} />
@@ -142,8 +142,10 @@ class Directory extends React.PureComponent {
142142
</div>
143143
</div>
144144

145-
<div className={classNames('directory__list', { loading: isLoading })}>
146-
{accountIds.map(accountId => <AccountCard id={accountId} key={accountId} />)}
145+
<div className='directory__list'>
146+
{isLoading ? <LoadingIndicator /> : accountIds.map(accountId => (
147+
<AccountCard id={accountId} key={accountId} />
148+
))}
147149
</div>
148150

149151
<LoadMore onClick={this.handleLoadMore} visible={!isLoading} />

app/javascript/mastodon/features/explore/suggestions.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22
import PropTypes from 'prop-types';
33
import ImmutablePropTypes from 'react-immutable-proptypes';
4-
import Account from 'mastodon/containers/account_container';
4+
import AccountCard from 'mastodon/features/directory/components/account_card';
55
import LoadingIndicator from 'mastodon/components/loading_indicator';
66
import { connect } from 'react-redux';
77
import { fetchSuggestions } from 'mastodon/actions/suggestions';
@@ -29,9 +29,9 @@ class Suggestions extends React.PureComponent {
2929
const { isLoading, suggestions } = this.props;
3030

3131
return (
32-
<div className='explore__links'>
33-
{isLoading ? (<LoadingIndicator />) : suggestions.map(suggestion => (
34-
<Account key={suggestion.get('account')} id={suggestion.get('account')} />
32+
<div className='explore__suggestions'>
33+
{isLoading ? <LoadingIndicator /> : suggestions.map(suggestion => (
34+
<AccountCard key={suggestion.get('account')} id={suggestion.get('account')} />
3535
))}
3636
</div>
3737
);

app/javascript/styles/mastodon-light/diff.scss

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,19 +40,11 @@ html {
4040
background: lighten($ui-base-color, 12%);
4141
}
4242

43-
.filter-form,
44-
.directory__card__bar {
43+
.filter-form {
4544
background: $white;
4645
border-bottom: 1px solid lighten($ui-base-color, 8%);
4746
}
4847

49-
.scrollable .directory__list {
50-
width: calc(100% + 2px);
51-
margin-left: -1px;
52-
margin-right: -1px;
53-
}
54-
55-
.directory__card,
5648
.table-of-contents {
5749
border: 1px solid lighten($ui-base-color, 8%);
5850
}
@@ -75,8 +67,7 @@ html {
7567
.column-header__back-button,
7668
.column-header__button,
7769
.column-header__button.active,
78-
.account__header__bar,
79-
.directory__card__extra {
70+
.account__header__bar {
8071
background: $white;
8172
}
8273

0 commit comments

Comments
 (0)