forked from cybrespace/mastodon
Improved how in-UI profiles look
This commit is contained in:
parent
29e79f770f
commit
0634e8dee5
|
@ -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;
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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} />
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue