Re-organizing components to be more modular, adding loading bars
This commit is contained in:
parent
f820edb463
commit
337462aa5e
31 changed files with 155 additions and 126 deletions
|
@ -0,0 +1,21 @@
|
|||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
|
||||
const CharacterCounter = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
text: React.PropTypes.string.isRequired
|
||||
},
|
||||
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
render () {
|
||||
return (
|
||||
<span style={{ fontSize: '16px', cursor: 'default' }}>
|
||||
{this.props.text.length}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default CharacterCounter;
|
|
@ -0,0 +1,76 @@
|
|||
import ColumnHeader from './column_header';
|
||||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
|
||||
const easingOutQuint = (x, t, b, c, d) => c*((t=t/d-1)*t*t*t*t + 1) + b;
|
||||
|
||||
const scrollTop = (node) => {
|
||||
const startTime = Date.now();
|
||||
const offset = node.scrollTop;
|
||||
const targetY = -offset;
|
||||
const duration = 1000;
|
||||
let interrupt = false;
|
||||
|
||||
const step = () => {
|
||||
const elapsed = Date.now() - startTime;
|
||||
const percentage = elapsed / duration;
|
||||
|
||||
if (percentage > 1 || interrupt) {
|
||||
return;
|
||||
}
|
||||
|
||||
node.scrollTo(0, easingOutQuint(0, elapsed, offset, targetY, duration));
|
||||
requestAnimationFrame(step);
|
||||
};
|
||||
|
||||
step();
|
||||
|
||||
return () => {
|
||||
interrupt = true;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
const Column = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
heading: React.PropTypes.string,
|
||||
icon: React.PropTypes.string
|
||||
},
|
||||
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
handleHeaderClick () {
|
||||
let node = ReactDOM.findDOMNode(this);
|
||||
this._interruptScrollAnimation = scrollTop(node.querySelector('.scrollable'));
|
||||
},
|
||||
|
||||
handleWheel () {
|
||||
if (typeof this._interruptScrollAnimation !== 'undefined') {
|
||||
this._interruptScrollAnimation();
|
||||
}
|
||||
},
|
||||
|
||||
handleScroll () {
|
||||
// todo
|
||||
},
|
||||
|
||||
render () {
|
||||
let header = '';
|
||||
|
||||
if (this.props.heading) {
|
||||
header = <ColumnHeader icon={this.props.icon} type={this.props.heading} onClick={this.handleHeaderClick} />;
|
||||
}
|
||||
|
||||
const style = { width: '350px', flex: '0 0 auto', background: '#282c37', margin: '10px', marginRight: '0', display: 'flex', flexDirection: 'column' };
|
||||
|
||||
return (
|
||||
<div style={style} onWheel={this.handleWheel} onScroll={this.handleScroll}>
|
||||
{header}
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default Column;
|
|
@ -0,0 +1,34 @@
|
|||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
|
||||
const ColumnHeader = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
icon: React.PropTypes.string,
|
||||
type: React.PropTypes.string,
|
||||
onClick: React.PropTypes.func
|
||||
},
|
||||
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
handleClick () {
|
||||
this.props.onClick();
|
||||
},
|
||||
|
||||
render () {
|
||||
let icon = '';
|
||||
|
||||
if (this.props.icon) {
|
||||
icon = <i className={`fa fa-fw fa-${this.props.icon}`} style={{ display: 'inline-block', marginRight: '5px' }} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div onClick={this.handleClick} style={{ padding: '15px', fontSize: '16px', background: '#2f3441', flex: '0 0 auto', cursor: 'pointer' }}>
|
||||
{icon}
|
||||
{this.props.type}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default ColumnHeader;
|
|
@ -0,0 +1,17 @@
|
|||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
|
||||
const ColumnsArea = React.createClass({
|
||||
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'row', flex: '1', marginRight: '10px' }}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default ColumnsArea;
|
|
@ -0,0 +1,58 @@
|
|||
import CharacterCounter from './character_counter';
|
||||
import Button from '../../../components/button';
|
||||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ReplyIndicator from './reply_indicator';
|
||||
import UploadButton from './upload_button';
|
||||
|
||||
const ComposeForm = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
text: React.PropTypes.string.isRequired,
|
||||
is_submitting: React.PropTypes.bool,
|
||||
in_reply_to: ImmutablePropTypes.map,
|
||||
onChange: React.PropTypes.func.isRequired,
|
||||
onSubmit: React.PropTypes.func.isRequired,
|
||||
onCancelReply: React.PropTypes.func.isRequired
|
||||
},
|
||||
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
handleChange (e) {
|
||||
this.props.onChange(e.target.value);
|
||||
},
|
||||
|
||||
handleKeyUp (e) {
|
||||
if (e.keyCode === 13 && e.ctrlKey) {
|
||||
this.props.onSubmit();
|
||||
}
|
||||
},
|
||||
|
||||
handleSubmit () {
|
||||
this.props.onSubmit();
|
||||
},
|
||||
|
||||
render () {
|
||||
let replyArea = '';
|
||||
|
||||
if (this.props.in_reply_to) {
|
||||
replyArea = <ReplyIndicator status={this.props.in_reply_to} onCancel={this.props.onCancelReply} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ padding: '10px' }}>
|
||||
{replyArea}
|
||||
|
||||
<textarea disabled={this.props.is_submitting} placeholder='What is on your mind?' value={this.props.text} onKeyUp={this.handleKeyUp} onChange={this.handleChange} className='compose-form__textarea' style={{ display: 'block', boxSizing: 'border-box', width: '100%', height: '100px', resize: 'none', border: 'none', color: '#282c37', padding: '10px', fontFamily: 'Roboto', fontSize: '14px', margin: '0' }} />
|
||||
|
||||
<div style={{ marginTop: '10px', overflow: 'hidden' }}>
|
||||
<div style={{ float: 'right' }}><Button text='Publish' onClick={this.handleSubmit} disabled={this.props.is_submitting} /></div>
|
||||
<div style={{ float: 'right', marginRight: '16px', lineHeight: '36px' }}><CharacterCounter text={this.props.text} /></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default ComposeForm;
|
|
@ -0,0 +1,17 @@
|
|||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
|
||||
const Drawer = React.createClass({
|
||||
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div style={{ width: '280px', flex: '0 0 auto', boxSizing: 'border-box', background: '#454b5e', margin: '10px', marginRight: '0', padding: '0', display: 'flex', flexDirection: 'column' }}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default Drawer;
|
|
@ -0,0 +1,40 @@
|
|||
import IconButton from '../../../components/icon_button';
|
||||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
|
||||
const FollowForm = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
text: React.PropTypes.string.isRequired,
|
||||
is_submitting: React.PropTypes.bool,
|
||||
onChange: React.PropTypes.func.isRequired,
|
||||
onSubmit: React.PropTypes.func.isRequired
|
||||
},
|
||||
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
handleChange (e) {
|
||||
this.props.onChange(e.target.value);
|
||||
},
|
||||
|
||||
handleKeyUp (e) {
|
||||
if (e.keyCode === 13) {
|
||||
this.props.onSubmit();
|
||||
}
|
||||
},
|
||||
|
||||
handleSubmit () {
|
||||
this.props.onSubmit();
|
||||
},
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div style={{ display: 'flex', lineHeight: '20px', padding: '10px', background: '#373b4a' }}>
|
||||
<input type='text' disabled={this.props.is_submitting} placeholder='username@domain' value={this.props.text} onKeyUp={this.handleKeyUp} onChange={this.handleChange} className='follow-form__input' style={{ flex: '1 1 auto', boxSizing: 'border-box', display: 'block', border: 'none', padding: '10px', fontFamily: 'Roboto', color: '#282c37', fontSize: '14px', margin: '0' }} />
|
||||
<div style={{ padding: '10px', paddingRight: '0' }}><IconButton title='Follow' size={20} icon='user-plus' onClick={this.handleSubmit} disabled={this.props.is_submitting} /></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default FollowForm;
|
|
@ -0,0 +1,75 @@
|
|||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
|
||||
const MediaGallery = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
media: ImmutablePropTypes.list.isRequired,
|
||||
height: React.PropTypes.number.isRequired
|
||||
},
|
||||
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
render () {
|
||||
var children = this.props.media.take(4);
|
||||
var size = children.size;
|
||||
|
||||
children = children.map((attachment, i) => {
|
||||
let width = 50;
|
||||
let height = 100;
|
||||
let top = 'auto';
|
||||
let left = 'auto';
|
||||
let bottom = 'auto';
|
||||
let right = 'auto';
|
||||
|
||||
if (size === 4 || (size === 3 && i > 0)) {
|
||||
height = 50;
|
||||
}
|
||||
|
||||
if (size === 2) {
|
||||
if (i === 0) {
|
||||
right = '2px';
|
||||
} else {
|
||||
left = '2px';
|
||||
}
|
||||
} else if (size === 3) {
|
||||
if (i === 0) {
|
||||
right = '2px';
|
||||
} else if (i > 0) {
|
||||
left = '2px';
|
||||
}
|
||||
|
||||
if (i === 1) {
|
||||
bottom = '2px';
|
||||
} else if (i > 1) {
|
||||
top = '2px';
|
||||
}
|
||||
} else if (size === 4) {
|
||||
if (i === 0 || i === 2) {
|
||||
right = '2px';
|
||||
}
|
||||
|
||||
if (i === 1 || i === 3) {
|
||||
left = '2px';
|
||||
}
|
||||
|
||||
if (i < 2) {
|
||||
bottom = '2px';
|
||||
} else {
|
||||
top = '2px';
|
||||
}
|
||||
}
|
||||
|
||||
return <a key={attachment.get('id')} href={attachment.get('url')} target='_blank' style={{ boxSizing: 'border-box', position: 'relative', left: left, top: top, right: right, bottom: bottom, float: 'left', textDecoration: 'none', border: 'none', display: 'block', width: `${width}%`, height: `${height}%`, background: `url(${attachment.get('preview_url')}) no-repeat center`, backgroundSize: 'cover', cursor: 'zoom-in' }} />;
|
||||
});
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: '8px', overflow: 'hidden', width: '100%', height: `${this.props.height}px`, boxSizing: 'border-box' }}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default MediaGallery;
|
|
@ -0,0 +1,30 @@
|
|||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import Avatar from '../../../components/avatar';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
import DisplayName from '../../../components/display_name';
|
||||
import { Link } from 'react-router';
|
||||
|
||||
const NavigationBar = React.createClass({
|
||||
propTypes: {
|
||||
account: ImmutablePropTypes.map.isRequired
|
||||
},
|
||||
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div style={{ padding: '10px', display: 'flex', cursor: 'default' }}>
|
||||
<Link to={`/accounts/${this.props.account.get('id')}`} style={{ textDecoration: 'none' }}><Avatar src={this.props.account.get('avatar')} size={40} /></Link>
|
||||
|
||||
<div style={{ flex: '1 1 auto', marginLeft: '8px' }}>
|
||||
<strong style={{ fontWeight: '500', display: 'block' }}>{this.props.account.get('acct')}</strong>
|
||||
<Link to='/settings' style={{ color: '#9baec8', textDecoration: 'none' }}>Settings <i className='fa fa fa-cog' /></Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default NavigationBar;
|
|
@ -0,0 +1,41 @@
|
|||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import Avatar from '../../../components/avatar';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
import DisplayName from '../../../components/display_name';
|
||||
|
||||
const ReplyIndicator = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
status: ImmutablePropTypes.map.isRequired,
|
||||
onCancel: React.PropTypes.func.isRequired
|
||||
},
|
||||
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
handleClick () {
|
||||
this.props.onCancel();
|
||||
},
|
||||
|
||||
render () {
|
||||
let content = { __html: this.props.status.get('content') };
|
||||
|
||||
return (
|
||||
<div style={{ background: '#9baec8', padding: '10px' }}>
|
||||
<div style={{ overflow: 'hidden', marginBottom: '5px' }}>
|
||||
<div style={{ float: 'right', lineHeight: '24px' }}><IconButton title='Cancel' icon='times' onClick={this.handleClick} /></div>
|
||||
|
||||
<a href={this.props.status.getIn(['account', 'url'])} className='reply-indicator__display-name' style={{ display: 'block', maxWidth: '100%', paddingRight: '25px', color: '#282c37', textDecoration: 'none', overflow: 'hidden', lineHeight: '24px' }}>
|
||||
<div style={{ float: 'left', marginRight: '5px' }}><Avatar size={24} src={this.props.status.getIn(['account', 'avatar'])} /></div>
|
||||
<DisplayName account={this.props.status.get('account')} />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className='reply-indicator__content' dangerouslySetInnerHTML={content} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default ReplyIndicator;
|
|
@ -0,0 +1,37 @@
|
|||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
import Button from '../../../components/button';
|
||||
|
||||
const UploadButton = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
disabled: React.PropTypes.bool,
|
||||
onSelectFile: React.PropTypes.func.isRequired
|
||||
},
|
||||
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
handleChange (e) {
|
||||
if (e.target.files.length > 0) {
|
||||
this.props.onSelectFile(e.target.files);
|
||||
}
|
||||
},
|
||||
|
||||
handleClick () {
|
||||
this.refs.fileElement.click();
|
||||
},
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div>
|
||||
<Button disabled={this.props.disabled} onClick={this.handleClick} block={true}>
|
||||
<i className='fa fa-fw fa-photo' /> Add images
|
||||
</Button>
|
||||
|
||||
<input ref='fileElement' type='file' multiple={false} onChange={this.handleChange} disabled={this.props.disabled} style={{ display: 'none' }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default UploadButton;
|
|
@ -0,0 +1,43 @@
|
|||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import UploadButton from './upload_button';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
|
||||
const UploadForm = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
media: ImmutablePropTypes.list.isRequired,
|
||||
is_uploading: React.PropTypes.bool,
|
||||
onSelectFile: React.PropTypes.func.isRequired,
|
||||
onRemoveFile: React.PropTypes.func.isRequired
|
||||
},
|
||||
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
render () {
|
||||
let uploads = this.props.media.map(function (attachment) {
|
||||
return (
|
||||
<div key={attachment.get('id')} style={{ borderRadius: '4px', marginBottom: '10px' }} className='transparent-background'>
|
||||
<div style={{ width: '100%', height: '100px', borderRadius: '4px', background: `url(${attachment.get('preview_url')}) no-repeat center`, backgroundSize: 'cover' }}>
|
||||
<IconButton icon='times' title='Undo' size={36} onClick={() => this.props.onRemoveFile(attachment.get('id'))} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}.bind(this));
|
||||
|
||||
const noMoreAllowed = (this.props.media.some(m => m.get('type') === 'video')) || (this.props.media.size > 3);
|
||||
|
||||
return (
|
||||
<div style={{ marginBottom: '20px', padding: '10px', paddingTop: '0' }}>
|
||||
<UploadButton onSelectFile={this.props.onSelectFile} disabled={this.props.is_uploading || noMoreAllowed } />
|
||||
|
||||
<div style={{ marginTop: '10px', overflow: 'hidden' }}>
|
||||
{uploads}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default UploadForm;
|
|
@ -0,0 +1,42 @@
|
|||
import { connect } from 'react-redux';
|
||||
import ComposeForm from '../components/compose_form';
|
||||
import { changeCompose, submitCompose, cancelReplyCompose } from '../../../actions/compose';
|
||||
|
||||
function selectStatus(state) {
|
||||
let statusId = state.getIn(['compose', 'in_reply_to'], null);
|
||||
|
||||
if (statusId === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let status = state.getIn(['timelines', 'statuses', statusId]);
|
||||
status = status.set('account', state.getIn(['timelines', 'accounts', status.get('account')]));
|
||||
|
||||
return status;
|
||||
};
|
||||
|
||||
const mapStateToProps = function (state, props) {
|
||||
return {
|
||||
text: state.getIn(['compose', 'text']),
|
||||
is_submitting: state.getIn(['compose', 'is_submitting']),
|
||||
in_reply_to: selectStatus(state)
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = function (dispatch) {
|
||||
return {
|
||||
onChange: function (text) {
|
||||
dispatch(changeCompose(text));
|
||||
},
|
||||
|
||||
onSubmit: function () {
|
||||
dispatch(submitCompose());
|
||||
},
|
||||
|
||||
onCancelReply: function () {
|
||||
dispatch(cancelReplyCompose());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ComposeForm);
|
|
@ -0,0 +1,24 @@
|
|||
import { connect } from 'react-redux';
|
||||
import FollowForm from '../components/follow_form';
|
||||
import { changeFollow, submitFollow } from '../../../actions/follow';
|
||||
|
||||
const mapStateToProps = function (state, props) {
|
||||
return {
|
||||
text: state.getIn(['follow', 'text']),
|
||||
is_submitting: state.getIn(['follow', 'is_submitting'])
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = function (dispatch) {
|
||||
return {
|
||||
onChange: function (text) {
|
||||
dispatch(changeFollow(text));
|
||||
},
|
||||
|
||||
onSubmit: function () {
|
||||
dispatch(submitFollow());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(FollowForm);
|
|
@ -0,0 +1,8 @@
|
|||
import { connect } from 'react-redux';
|
||||
import NavigationBar from '../components/navigation_bar';
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
account: state.getIn(['timelines', 'accounts', state.getIn(['timelines', 'me'])])
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(NavigationBar);
|
|
@ -0,0 +1,25 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { NotificationStack } from 'react-notification';
|
||||
import { dismissNotification } from '../../../actions/notifications';
|
||||
|
||||
const mapStateToProps = (state, props) => {
|
||||
return {
|
||||
notifications: state.get('notifications').map((item, i) => ({
|
||||
message: item.get('message'),
|
||||
title: item.get('title'),
|
||||
key: i,
|
||||
action: 'Dismiss',
|
||||
dismissAfter: 5000
|
||||
})).toJS()
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
onDismiss: notifiction => {
|
||||
dispatch(dismissNotification(notifiction));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(NotificationStack);
|
|
@ -0,0 +1,29 @@
|
|||
import { connect } from 'react-redux';
|
||||
import StatusList from '../../../components/status_list';
|
||||
import { replyCompose } from '../../../actions/compose';
|
||||
import { reblog, favourite } from '../../../actions/interactions';
|
||||
import { selectStatus } from '../../../reducers/timelines';
|
||||
|
||||
const mapStateToProps = function (state, props) {
|
||||
return {
|
||||
statuses: state.getIn(['timelines', props.type]).map(id => selectStatus(state, id))
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = function (dispatch) {
|
||||
return {
|
||||
onReply: function (status) {
|
||||
dispatch(replyCompose(status));
|
||||
},
|
||||
|
||||
onFavourite: function (status) {
|
||||
dispatch(favourite(status));
|
||||
},
|
||||
|
||||
onReblog: function (status) {
|
||||
dispatch(reblog(status));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(StatusList);
|
|
@ -0,0 +1,25 @@
|
|||
import { connect } from 'react-redux';
|
||||
import UploadForm from '../components/upload_form';
|
||||
import { uploadCompose, undoUploadCompose } from '../../../actions/compose';
|
||||
|
||||
const mapStateToProps = function (state, props) {
|
||||
return {
|
||||
media: state.getIn(['compose', 'media_attachments']),
|
||||
progress: state.getIn(['compose', 'progress']),
|
||||
is_uploading: state.getIn(['compose', 'is_uploading'])
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = function (dispatch) {
|
||||
return {
|
||||
onSelectFile: function (files) {
|
||||
dispatch(uploadCompose(files));
|
||||
},
|
||||
|
||||
onRemoveFile: function (media_id) {
|
||||
dispatch(undoUploadCompose(media_id));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(UploadForm);
|
56
app/assets/javascripts/components/features/ui/index.jsx
Normal file
56
app/assets/javascripts/components/features/ui/index.jsx
Normal file
|
@ -0,0 +1,56 @@
|
|||
import ColumnsArea from './components/columns_area';
|
||||
import Column from './components/column';
|
||||
import Drawer from './components/drawer';
|
||||
import ComposeFormContainer from './containers/compose_form_container';
|
||||
import FollowFormContainer from './containers/follow_form_container';
|
||||
import UploadFormContainer from './containers/upload_form_container';
|
||||
import StatusListContainer from './containers/status_list_container';
|
||||
import NotificationsContainer from './containers/notifications_container';
|
||||
import NavigationContainer from './containers/navigation_container';
|
||||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
import LoadingBar from 'react-redux-loading-bar';
|
||||
|
||||
const UI = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
router: React.PropTypes.object
|
||||
},
|
||||
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div style={{ flex: '0 0 auto', display: 'flex', width: '100%', height: '100%', background: '#1a1c23' }}>
|
||||
<Drawer>
|
||||
<div style={{ flex: '1 1 auto' }}>
|
||||
<NavigationContainer />
|
||||
<ComposeFormContainer />
|
||||
<UploadFormContainer />
|
||||
</div>
|
||||
|
||||
<FollowFormContainer />
|
||||
</Drawer>
|
||||
|
||||
<ColumnsArea>
|
||||
<Column icon='home' heading='Home'>
|
||||
<StatusListContainer type='home' />
|
||||
</Column>
|
||||
|
||||
<Column icon='at' heading='Mentions'>
|
||||
<StatusListContainer type='mentions' />
|
||||
</Column>
|
||||
|
||||
<Column>
|
||||
{this.props.children}
|
||||
</Column>
|
||||
</ColumnsArea>
|
||||
|
||||
<NotificationsContainer />
|
||||
<LoadingBar style={{ backgroundColor: '#2b90d9', left: '0', top: '0' }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default UI;
|
Loading…
Add table
Add a link
Reference in a new issue