diff options
author | Eugen Rochko <eugen@zeonfederated.com> | 2016-09-19 23:25:59 +0200 |
---|---|---|
committer | Eugen Rochko <eugen@zeonfederated.com> | 2016-09-19 23:26:21 +0200 |
commit | 337462aa5e68014aa15788e4513e190b2e434d7e (patch) | |
tree | a760e80e2bc9b1fef55f118ddeb8603b20155a61 /app/assets/javascripts/components/features | |
parent | f820edb463109e313e836d8e2f210927a0eba7d1 (diff) |
Re-organizing components to be more modular, adding loading bars
Diffstat (limited to 'app/assets/javascripts/components/features')
20 files changed, 700 insertions, 2 deletions
diff --git a/app/assets/javascripts/components/features/status/index.jsx b/app/assets/javascripts/components/features/status/index.jsx index 72ff6a944..7a810d474 100644 --- a/app/assets/javascripts/components/features/status/index.jsx +++ b/app/assets/javascripts/components/features/status/index.jsx @@ -31,12 +31,12 @@ const Status = React.createClass({ mixins: [PureRenderMixin], componentWillMount () { - this.props.dispatch(fetchStatus(this.props.params.statusId)); + this.props.dispatch(fetchStatus(Number(this.props.params.statusId))); }, componentWillReceiveProps (nextProps) { if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) { - this.props.dispatch(fetchStatus(nextProps.params.statusId)); + this.props.dispatch(fetchStatus(Number(nextProps.params.statusId))); } }, diff --git a/app/assets/javascripts/components/features/ui/components/character_counter.jsx b/app/assets/javascripts/components/features/ui/components/character_counter.jsx new file mode 100644 index 000000000..dd9218844 --- /dev/null +++ b/app/assets/javascripts/components/features/ui/components/character_counter.jsx @@ -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; diff --git a/app/assets/javascripts/components/features/ui/components/column.jsx b/app/assets/javascripts/components/features/ui/components/column.jsx new file mode 100644 index 000000000..7109bcc19 --- /dev/null +++ b/app/assets/javascripts/components/features/ui/components/column.jsx @@ -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; diff --git a/app/assets/javascripts/components/features/ui/components/column_header.jsx b/app/assets/javascripts/components/features/ui/components/column_header.jsx new file mode 100644 index 000000000..21def69c7 --- /dev/null +++ b/app/assets/javascripts/components/features/ui/components/column_header.jsx @@ -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; diff --git a/app/assets/javascripts/components/features/ui/components/columns_area.jsx b/app/assets/javascripts/components/features/ui/components/columns_area.jsx new file mode 100644 index 000000000..e45a6466d --- /dev/null +++ b/app/assets/javascripts/components/features/ui/components/columns_area.jsx @@ -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; diff --git a/app/assets/javascripts/components/features/ui/components/compose_form.jsx b/app/assets/javascripts/components/features/ui/components/compose_form.jsx new file mode 100644 index 000000000..1aa0b447f --- /dev/null +++ b/app/assets/javascripts/components/features/ui/components/compose_form.jsx @@ -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; diff --git a/app/assets/javascripts/components/features/ui/components/drawer.jsx b/app/assets/javascripts/components/features/ui/components/drawer.jsx new file mode 100644 index 000000000..dfba11ad2 --- /dev/null +++ b/app/assets/javascripts/components/features/ui/components/drawer.jsx @@ -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; diff --git a/app/assets/javascripts/components/features/ui/components/follow_form.jsx b/app/assets/javascripts/components/features/ui/components/follow_form.jsx new file mode 100644 index 000000000..a9d73a9a1 --- /dev/null +++ b/app/assets/javascripts/components/features/ui/components/follow_form.jsx @@ -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; diff --git a/app/assets/javascripts/components/features/ui/components/media_gallery.jsx b/app/assets/javascripts/components/features/ui/components/media_gallery.jsx new file mode 100644 index 000000000..20f9a3d87 --- /dev/null +++ b/app/assets/javascripts/components/features/ui/components/media_gallery.jsx @@ -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; diff --git a/app/assets/javascripts/components/features/ui/components/navigation_bar.jsx b/app/assets/javascripts/components/features/ui/components/navigation_bar.jsx new file mode 100644 index 000000000..b5d374a88 --- /dev/null +++ b/app/assets/javascripts/components/features/ui/components/navigation_bar.jsx @@ -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; diff --git a/app/assets/javascripts/components/features/ui/components/reply_indicator.jsx b/app/assets/javascripts/components/features/ui/components/reply_indicator.jsx new file mode 100644 index 000000000..316a23b54 --- /dev/null +++ b/app/assets/javascripts/components/features/ui/components/reply_indicator.jsx @@ -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; diff --git a/app/assets/javascripts/components/features/ui/components/upload_button.jsx b/app/assets/javascripts/components/features/ui/components/upload_button.jsx new file mode 100644 index 000000000..d1b093242 --- /dev/null +++ b/app/assets/javascripts/components/features/ui/components/upload_button.jsx @@ -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; diff --git a/app/assets/javascripts/components/features/ui/components/upload_form.jsx b/app/assets/javascripts/components/features/ui/components/upload_form.jsx new file mode 100644 index 000000000..d584e9ab7 --- /dev/null +++ b/app/assets/javascripts/components/features/ui/components/upload_form.jsx @@ -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; diff --git a/app/assets/javascripts/components/features/ui/containers/compose_form_container.jsx b/app/assets/javascripts/components/features/ui/containers/compose_form_container.jsx new file mode 100644 index 000000000..a092a1e8e --- /dev/null +++ b/app/assets/javascripts/components/features/ui/containers/compose_form_container.jsx @@ -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); diff --git a/app/assets/javascripts/components/features/ui/containers/follow_form_container.jsx b/app/assets/javascripts/components/features/ui/containers/follow_form_container.jsx new file mode 100644 index 000000000..a21c1291b --- /dev/null +++ b/app/assets/javascripts/components/features/ui/containers/follow_form_container.jsx @@ -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); diff --git a/app/assets/javascripts/components/features/ui/containers/navigation_container.jsx b/app/assets/javascripts/components/features/ui/containers/navigation_container.jsx new file mode 100644 index 000000000..4aeea4c37 --- /dev/null +++ b/app/assets/javascripts/components/features/ui/containers/navigation_container.jsx @@ -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); diff --git a/app/assets/javascripts/components/features/ui/containers/notifications_container.jsx b/app/assets/javascripts/components/features/ui/containers/notifications_container.jsx new file mode 100644 index 000000000..2db1603fc --- /dev/null +++ b/app/assets/javascripts/components/features/ui/containers/notifications_container.jsx @@ -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); diff --git a/app/assets/javascripts/components/features/ui/containers/status_list_container.jsx b/app/assets/javascripts/components/features/ui/containers/status_list_container.jsx new file mode 100644 index 000000000..4ea599fc0 --- /dev/null +++ b/app/assets/javascripts/components/features/ui/containers/status_list_container.jsx @@ -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); diff --git a/app/assets/javascripts/components/features/ui/containers/upload_form_container.jsx b/app/assets/javascripts/components/features/ui/containers/upload_form_container.jsx new file mode 100644 index 000000000..6554f944f --- /dev/null +++ b/app/assets/javascripts/components/features/ui/containers/upload_form_container.jsx @@ -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); diff --git a/app/assets/javascripts/components/features/ui/index.jsx b/app/assets/javascripts/components/features/ui/index.jsx new file mode 100644 index 000000000..fbfa361f2 --- /dev/null +++ b/app/assets/javascripts/components/features/ui/index.jsx @@ -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; |