Improved how in-UI profiles look

This commit is contained in:
Eugen Rochko 2016-10-09 22:19:15 +02:00
parent 29e79f770f
commit 0634e8dee5
7 changed files with 94 additions and 64 deletions

View File

@ -0,0 +1,25 @@
import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown';
const DropdownMenu = ({ icon, items, size }) => {
return (
<Dropdown>
<DropdownTrigger className='icon-button' style={{ fontSize: `${size}px`, width: `${size}px`, lineHeight: `${size}px` }}>
<i className={`fa fa-fw fa-${icon}`} style={{ verticalAlign: 'middle' }} />
</DropdownTrigger>
<DropdownContent style={{ lineHeight: '18px' }}>
<ul>
{items.map(({ text, action }, i) => <li key={i}><a href='#' onClick={e => { e.preventDefault(); action(); }}>{text}</a></li>)}
</ul>
</DropdownContent>
</Dropdown>
);
};
DropdownMenu.propTypes = {
icon: React.PropTypes.string.isRequired,
items: React.PropTypes.array.isRequired,
size: React.PropTypes.number.isRequired
};
export default DropdownMenu;

View File

@ -1,7 +1,7 @@
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import PureRenderMixin from 'react-addons-pure-render-mixin'; import PureRenderMixin from 'react-addons-pure-render-mixin';
import IconButton from './icon_button'; import IconButton from './icon_button';
import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown'; import DropdownMenu from './dropdown_menu';
const StatusActionBar = React.createClass({ const StatusActionBar = React.createClass({
propTypes: { propTypes: {
@ -26,23 +26,16 @@ const StatusActionBar = React.createClass({
this.props.onReblog(this.props.status); this.props.onReblog(this.props.status);
}, },
handleDeleteClick(e) { handleDeleteClick () {
e.preventDefault();
this.props.onDelete(this.props.status); this.props.onDelete(this.props.status);
}, },
render () { render () {
const { status, me } = this.props; const { status, me } = this.props;
let menu = ''; let menu = [];
if (status.getIn(['account', 'id']) === me) { if (status.getIn(['account', 'id']) === me) {
menu = ( menu.push({ text: 'Delete', action: this.handleDeleteClick });
<ul>
<li><a href='#' onClick={this.handleDeleteClick}>Delete</a></li>
</ul>
);
} else {
menu = <ul />;
} }
return ( return (
@ -52,13 +45,7 @@ const StatusActionBar = React.createClass({
<div style={{ float: 'left', marginRight: '18px'}}><IconButton active={status.get('favourited')} title='Favourite' icon='star' onClick={this.handleFavouriteClick} /></div> <div style={{ float: 'left', marginRight: '18px'}}><IconButton active={status.get('favourited')} title='Favourite' icon='star' onClick={this.handleFavouriteClick} /></div>
<div onClick={e => e.stopPropagation()} style={{ width: '18px', height: '18px', float: 'left' }}> <div onClick={e => e.stopPropagation()} style={{ width: '18px', height: '18px', float: 'left' }}>
<Dropdown> <DropdownMenu items={menu} icon='ellipsis-h' size={18} />
<DropdownTrigger className='icon-button' style={{ fontSize: '18px', lineHeight: '18px', width: '18px', height: '18px' }}>
<i className='fa fa-fw fa-ellipsis-h' />
</DropdownTrigger>
<DropdownContent>{menu}</DropdownContent>
</Dropdown>
</div> </div>
</div> </div>
); );

View File

@ -1,6 +1,6 @@
import PureRenderMixin from 'react-addons-pure-render-mixin'; import PureRenderMixin from 'react-addons-pure-render-mixin';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import Button from '../../../components/button'; import DropdownMenu from '../../../components/dropdown_menu';
const ActionBar = React.createClass({ const ActionBar = React.createClass({
@ -16,47 +16,42 @@ const ActionBar = React.createClass({
render () { render () {
const { account, me } = this.props; const { account, me } = this.props;
let infoText = ''; let menu = [];
let follow = '';
let buttonText = '';
let block = '';
let disabled = false;
if (account.get('id') === me) { if (account.get('id') === me) {
buttonText = 'This is you!';
disabled = true; } else if (account.getIn(['relationship', 'blocking'])) {
menu.push({ text: 'Unblock', action: this.props.onBlock });
} else if (account.getIn(['relationship', 'following'])) {
menu.push({ text: 'Unfollow', action: this.props.onFollow });
menu.push({ text: 'Block', action: this.props.onBlock });
} else { } else {
let blockText = ''; menu.push({ text: 'Follow', action: this.props.onFollow });
menu.push({ text: 'Block', action: this.props.onBlock });
if (account.getIn(['relationship', 'blocking'])) {
buttonText = 'Blocked';
disabled = true;
blockText = 'Unblock';
} else {
if (account.getIn(['relationship', 'following'])) {
buttonText = 'Unfollow';
} else {
buttonText = 'Follow';
}
if (account.getIn(['relationship', 'followed_by'])) {
infoText = 'Follows you!';
}
blockText = 'Block';
}
block = <Button text={blockText} onClick={this.props.onBlock} />;
}
if (!account.getIn(['relationship', 'blocking'])) {
follow = <Button text={buttonText} onClick={this.props.onFollow} disabled={disabled} />;
} }
return ( return (
<div style={{ borderTop: '1px solid #363c4b', borderBottom: '1px solid #363c4b', padding: '10px', lineHeight: '36px', overflow: 'hidden', flex: '0 0 auto' }}> <div style={{ borderTop: '1px solid #363c4b', borderBottom: '1px solid #363c4b', lineHeight: '36px', overflow: 'hidden', flex: '0 0 auto', display: 'flex' }}>
{follow} {block} <div style={{ flex: '1 1 auto', display: 'flex', lineHeight: '18px' }}>
<span style={{ color: '#616b86', fontWeight: '500', textTransform: 'uppercase', float: 'right', display: 'block' }}>{infoText}</span> <div style={{ overflow: 'hidden', width: '80px', borderRight: '1px solid #363c4b', padding: '10px', paddingRight: '5px' }}>
<span style={{ display: 'block', textTransform: 'uppercase', fontSize: '11px', color: '#616b86' }}>Posts</span>
<span style={{ display: 'block', fontSize: '15px', fontWeight: '500', color: '#fff' }}>{account.get('statuses_count')}</span>
</div>
<div style={{ overflow: 'hidden', width: '80px', borderRight: '1px solid #363c4b', padding: '10px 5px' }}>
<span style={{ display: 'block', textTransform: 'uppercase', fontSize: '11px', color: '#616b86' }}>Follows</span>
<span style={{ display: 'block', fontSize: '15px', fontWeight: '500', color: '#fff' }}>{account.get('following_count')}</span>
</div>
<div style={{ overflow: 'hidden', width: '80px', padding: '10px 5px', borderRight: '1px solid #363c4b' }}>
<span style={{ display: 'block', textTransform: 'uppercase', fontSize: '11px', color: '#616b86' }}>Followers</span>
<span style={{ display: 'block', fontSize: '15px', fontWeight: '500', color: '#fff' }}>{account.get('followers_count')}</span>
</div>
</div>
<div style={{ padding: '10px', flex: '1 1 auto' }}>
<DropdownMenu items={menu} icon='bars' size={24} />
</div>
</div> </div>
); );
}, },

View File

@ -4,33 +4,41 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
const Header = React.createClass({ const Header = React.createClass({
propTypes: { propTypes: {
account: ImmutablePropTypes.map.isRequired account: ImmutablePropTypes.map.isRequired,
me: React.PropTypes.number.isRequired
}, },
mixins: [PureRenderMixin], mixins: [PureRenderMixin],
render () { render () {
const { account } = this.props; const { account, me } = this.props;
let displayName = account.get('display_name'); let displayName = account.get('display_name');
let info = '';
if (displayName.length === 0) { if (displayName.length === 0) {
displayName = account.get('username'); displayName = account.get('username');
} }
if (me !== account.get('id') && account.getIn(['relationship', 'followed_by'])) {
info = <span style={{ position: 'absolute', top: '10px', right: '10px', opacity: '0.7', display: 'inline-block', verticalAlign: 'top', background: 'rgba(0, 0, 0, 0.4)', color: '#fff', textTransform: 'uppercase', fontSize: '11px', fontWeight: '500', padding: '4px', borderRadius: '4px' }}>Follows you</span>
}
return ( return (
<div style={{ flex: '0 0 auto', background: '#2f3441', textAlign: 'center', backgroundImage: `url(${account.get('header')})`, backgroundSize: 'cover' }}> <div style={{ flex: '0 0 auto', background: '#2f3441', textAlign: 'center', backgroundImage: `url(${account.get('header')})`, backgroundSize: 'cover', position: 'relative' }}>
<div style={{ background: 'rgba(47, 52, 65, 0.8)', padding: '30px 10px' }}> <div style={{ background: 'rgba(47, 52, 65, 0.8)', padding: '30px 10px' }}>
<a href={account.get('url')} target='_blank' rel='noopener' style={{ display: 'block', color: 'inherit', textDecoration: 'none' }}> <a href={account.get('url')} target='_blank' rel='noopener' style={{ display: 'block', color: 'inherit', textDecoration: 'none' }}>
<div style={{ width: '90px', margin: '0 auto', marginBottom: '15px' }}> <div style={{ width: '90px', margin: '0 auto', marginBottom: '15px' }}>
<img src={account.get('avatar')} alt='' style={{ display: 'block', width: '90px', height: '90px', borderRadius: '90px' }} /> <img src={account.get('avatar')} alt='' style={{ display: 'block', width: '90px', height: '90px', borderRadius: '90px' }} />
</div> </div>
<span style={{ color: '#fff', fontSize: '20px', lineHeight: '27px', fontWeight: '500', display: 'block' }}>{displayName}</span> <span style={{ display: 'inline-block', color: '#fff', fontSize: '20px', lineHeight: '27px', fontWeight: '500' }}>{displayName}</span>
</a> </a>
<span style={{ fontSize: '14px', fontWeight: '400', display: 'block', color: '#2b90d9', marginBottom: '15px' }}>@{account.get('acct')}</span> <span style={{ fontSize: '14px', fontWeight: '400', display: 'block', color: '#2b90d9', marginBottom: '15px' }}>@{account.get('acct')}</span>
<p style={{ color: '#616b86', fontSize: '14px' }}>{account.get('note')}</p> <p style={{ color: '#616b86', fontSize: '14px' }}>{account.get('note')}</p>
{info}
</div> </div>
</div> </div>
); );

View File

@ -75,7 +75,7 @@ const Account = React.createClass({
return ( return (
<Column> <Column>
<div style={{ display: 'flex', flexDirection: 'column', 'flex': '0 0 auto', height: '100%' }}> <div style={{ display: 'flex', flexDirection: 'column', 'flex': '0 0 auto', height: '100%' }}>
<Header account={account} /> <Header account={account} me={me} />
<ActionBar account={account} me={me} onFollow={this.handleFollow} onBlock={this.handleBlock} /> <ActionBar account={account} me={me} onFollow={this.handleFollow} onBlock={this.handleBlock} />

View File

@ -1,26 +1,36 @@
import PureRenderMixin from 'react-addons-pure-render-mixin'; import PureRenderMixin from 'react-addons-pure-render-mixin';
import IconButton from '../../../components/icon_button'; import IconButton from '../../../components/icon_button';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import DropdownMenu from '../../../components/dropdown_menu';
const ActionBar = React.createClass({ const ActionBar = React.createClass({
propTypes: { propTypes: {
status: ImmutablePropTypes.map.isRequired, status: ImmutablePropTypes.map.isRequired,
onReply: React.PropTypes.func.isRequired, onReply: React.PropTypes.func.isRequired,
onReblog: React.PropTypes.func.isRequired, onReblog: React.PropTypes.func.isRequired,
onFavourite: React.PropTypes.func.isRequired onFavourite: React.PropTypes.func.isRequired,
onDelete: React.PropTypes.func.isRequired,
me: React.PropTypes.number.isRequired
}, },
mixins: [PureRenderMixin], mixins: [PureRenderMixin],
render () { render () {
const { status } = this.props; const { status, me } = this.props;
let menu = [];
if (me === status.getIn(['account', 'id'])) {
menu.push({ text: 'Delete', action: () => this.props.onDelete(status) });
}
return ( return (
<div style={{ background: '#2f3441', display: 'flex', flexDirection: 'row', borderTop: '1px solid #363c4b', borderBottom: '1px solid #363c4b', padding: '10px 0' }}> <div style={{ background: '#2f3441', display: 'flex', flexDirection: 'row', borderTop: '1px solid #363c4b', borderBottom: '1px solid #363c4b', padding: '10px 0' }}>
<div style={{ flex: '1 1 auto', textAlign: 'center' }}><IconButton title='Reply' icon='reply' onClick={() => this.props.onReply(status)} /></div> <div style={{ flex: '1 1 auto', textAlign: 'center' }}><IconButton title='Reply' icon='reply' onClick={() => this.props.onReply(status)} /></div>
<div style={{ flex: '1 1 auto', textAlign: 'center' }}><IconButton active={status.get('reblogged')} title='Reblog' icon='retweet' onClick={() => this.props.onReblog(status)} /></div> <div style={{ flex: '1 1 auto', textAlign: 'center' }}><IconButton active={status.get('reblogged')} title='Reblog' icon='retweet' onClick={() => this.props.onReblog(status)} /></div>
<div style={{ flex: '1 1 auto', textAlign: 'center' }}><IconButton active={status.get('favourited')} title='Favourite' icon='star' onClick={() => this.props.onFavourite(status)} /></div> <div style={{ flex: '1 1 auto', textAlign: 'center' }}><IconButton active={status.get('favourited')} title='Favourite' icon='star' onClick={() => this.props.onFavourite(status)} /></div>
<div style={{ flex: '1 1 auto', textAlign: 'center' }}><DropdownMenu size={18} icon='ellipsis-h' items={menu} /></div>
</div> </div>
); );
} }

View File

@ -10,6 +10,7 @@ import ActionBar from './components/action_bar';
import Column from '../ui/components/column'; import Column from '../ui/components/column';
import { favourite, reblog } from '../../actions/interactions'; import { favourite, reblog } from '../../actions/interactions';
import { replyCompose } from '../../actions/compose'; import { replyCompose } from '../../actions/compose';
import { deleteStatus } from '../../actions/statuses';
import { import {
getStatus, getStatus,
getStatusAncestors, getStatusAncestors,
@ -57,8 +58,12 @@ const Status = React.createClass({
this.props.dispatch(reblog(status)); this.props.dispatch(reblog(status));
}, },
handleDeleteClick (status) {
this.props.dispatch(deleteStatus(status.get('id')));
},
renderChildren (list) { renderChildren (list) {
return list.map(s => <EmbeddedStatus status={s} me={this.props.me} key={s.get('id')} onReply={this.handleReplyClick} onFavourite={this.handleFavouriteClick} onReblog={this.handleReblogClick} />); return list.map(s => <EmbeddedStatus status={s} me={this.props.me} key={s.get('id')} onReply={this.handleReplyClick} onFavourite={this.handleFavouriteClick} onReblog={this.handleReblogClick} onDelete={this.handleDeleteClick} />);
}, },
render () { render () {
@ -80,7 +85,7 @@ const Status = React.createClass({
<div>{this.renderChildren(ancestors)}</div> <div>{this.renderChildren(ancestors)}</div>
<DetailedStatus status={status} me={me} /> <DetailedStatus status={status} me={me} />
<ActionBar status={status} onReply={this.handleReplyClick} onFavourite={this.handleFavouriteClick} onReblog={this.handleReblogClick} /> <ActionBar status={status} me={me} onReply={this.handleReplyClick} onFavourite={this.handleFavouriteClick} onReblog={this.handleReblogClick} onDelete={this.handleDeleteClick} />
<div>{this.renderChildren(descendants)}</div> <div>{this.renderChildren(descendants)}</div>
</div> </div>