Adding sense of self to the UI, cleaning up routing, adding third (detail) column
This commit is contained in:
		
							
								
								
									
										48
									
								
								app/assets/javascripts/components/actions/accounts.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								app/assets/javascripts/components/actions/accounts.jsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| import api from '../api' | ||||
|  | ||||
| export const ACCOUNT_SET_SELF      = 'ACCOUNT_SET_SELF'; | ||||
| export const ACCOUNT_FETCH         = 'ACCOUNT_FETCH'; | ||||
| export const ACCOUNT_FETCH_REQUEST = 'ACCOUNT_FETCH_REQUEST'; | ||||
| export const ACCOUNT_FETCH_SUCCESS = 'ACCOUNT_FETCH_SUCCESS'; | ||||
| export const ACCOUNT_FETCH_FAIL    = 'ACCOUNT_FETCH_FAIL'; | ||||
|  | ||||
| export function setAccountSelf(account) { | ||||
|   return { | ||||
|     type: ACCOUNT_SET_SELF, | ||||
|     account: account | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| export function fetchAccount(id) { | ||||
|   return (dispatch, getState) => { | ||||
|     dispatch(fetchAccountRequest(id)); | ||||
|  | ||||
|     api(getState).get(`/api/accounts/${id}`).then(response => { | ||||
|       dispatch(fetchAccountSuccess(response.data)); | ||||
|     }).catch(error => { | ||||
|       dispatch(fetchAccountFail(id, error)); | ||||
|     }); | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| export function fetchAccountRequest(id) { | ||||
|   return { | ||||
|     type: ACCOUNT_FETCH_REQUEST, | ||||
|     id: id | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| export function fetchAccountSuccess(account) { | ||||
|   return { | ||||
|     type: ACCOUNT_FETCH_SUCCESS, | ||||
|     account: account | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| export function fetchAccountFail(id, error) { | ||||
|   return { | ||||
|     type: ACCOUNT_FETCH_FAIL, | ||||
|     id: id, | ||||
|     error: error | ||||
|   }; | ||||
| }; | ||||
							
								
								
									
										6
									
								
								app/assets/javascripts/components/actions/statuses.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								app/assets/javascripts/components/actions/statuses.jsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| import api from '../api'; | ||||
|  | ||||
| export const STATUS_FETCH         = 'STATUS_FETCH'; | ||||
| export const STATUS_FETCH_REQUEST = 'STATUS_FETCH_REQUEST'; | ||||
| export const STATUS_FETCH_SUCCESS = 'STATUS_FETCH_SUCCESS'; | ||||
| export const STATUS_FETCH_FAIL    = 'STATUS_FETCH_FAIL'; | ||||
| @@ -5,7 +5,8 @@ const Column = React.createClass({ | ||||
|  | ||||
|   propTypes: { | ||||
|     heading: React.PropTypes.string, | ||||
|     icon: React.PropTypes.string | ||||
|     icon: React.PropTypes.string, | ||||
|     fluid: React.PropTypes.bool | ||||
|   }, | ||||
|  | ||||
|   mixins: [PureRenderMixin], | ||||
| @@ -22,8 +23,16 @@ const Column = React.createClass({ | ||||
|       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' }; | ||||
|  | ||||
|     if (this.props.fluid) { | ||||
|       style.width      = 'auto'; | ||||
|       style.flex       = '1 1 auto'; | ||||
|       style.background = '#21242d'; | ||||
|     } | ||||
|  | ||||
|     return ( | ||||
|       <div style={{ width: '380px', flex: '0 0 auto', background: '#282c37', margin: '10px', marginRight: '0', display: 'flex', flexDirection: 'column' }}> | ||||
|       <div style={style}> | ||||
|         {header} | ||||
|         {this.props.children} | ||||
|       </div> | ||||
|   | ||||
| @@ -6,7 +6,7 @@ const ColumnsArea = React.createClass({ | ||||
|  | ||||
|   render () { | ||||
|     return ( | ||||
|       <div style={{ display: 'flex', flexDirection: 'row', flex: '1' }}> | ||||
|       <div style={{ display: 'flex', flexDirection: 'row', flex: '1', marginRight: '10px' }}> | ||||
|         {this.props.children} | ||||
|       </div> | ||||
|     ); | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
| import PureRenderMixin    from 'react-addons-pure-render-mixin'; | ||||
|  | ||||
| const DisplayName = React.createClass({ | ||||
|  | ||||
| @@ -6,6 +7,8 @@ const DisplayName = React.createClass({ | ||||
|     account: ImmutablePropTypes.map.isRequired | ||||
|   }, | ||||
|  | ||||
|   mixins: [PureRenderMixin], | ||||
|  | ||||
|   render () { | ||||
|     let displayName = this.props.account.get('display_name'); | ||||
|  | ||||
|   | ||||
| @@ -1,12 +1,13 @@ | ||||
| import ColumnsArea          from './columns_area'; | ||||
| import Column               from './column'; | ||||
| import Drawer               from './drawer'; | ||||
| import ColumnsArea            from './columns_area'; | ||||
| import Column                 from './column'; | ||||
| import Drawer                 from './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 PureRenderMixin        from 'react-addons-pure-render-mixin'; | ||||
| import NavigationContainer    from '../containers/navigation_container'; | ||||
| import PureRenderMixin from 'react-addons-pure-render-mixin'; | ||||
|  | ||||
| const Frontend = React.createClass({ | ||||
|  | ||||
| @@ -17,6 +18,7 @@ const Frontend = React.createClass({ | ||||
|       <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> | ||||
| @@ -32,6 +34,10 @@ const Frontend = React.createClass({ | ||||
|           <Column icon='at' heading='Mentions'> | ||||
|             <StatusListContainer type='mentions' /> | ||||
|           </Column> | ||||
|  | ||||
|           <Column fluid={true}> | ||||
|             {this.props.children} | ||||
|           </Column> | ||||
|         </ColumnsArea> | ||||
|  | ||||
|         <NotificationsContainer /> | ||||
|   | ||||
| @@ -0,0 +1,30 @@ | ||||
| import PureRenderMixin    from 'react-addons-pure-render-mixin'; | ||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
| import Avatar             from './avatar'; | ||||
| import IconButton         from './icon_button'; | ||||
| import DisplayName        from './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' }}> | ||||
|         <Avatar src={this.props.account.get('avatar')} size={40} /> | ||||
|  | ||||
|         <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; | ||||
| @@ -5,11 +5,13 @@ import PureRenderMixin    from 'react-addons-pure-render-mixin'; | ||||
| import IconButton         from './icon_button'; | ||||
| import DisplayName        from './display_name'; | ||||
| import MediaGallery       from './media_gallery'; | ||||
| import { hashHistory }    from 'react-router'; | ||||
|  | ||||
| const Status = React.createClass({ | ||||
|  | ||||
|   propTypes: { | ||||
|     status: ImmutablePropTypes.map.isRequired, | ||||
|     wrapped: React.PropTypes.bool, | ||||
|     onReply: React.PropTypes.func, | ||||
|     onFavourite: React.PropTypes.func, | ||||
|     onReblog: React.PropTypes.func | ||||
| @@ -29,6 +31,10 @@ const Status = React.createClass({ | ||||
|     this.props.onReblog(this.props.status); | ||||
|   }, | ||||
|  | ||||
|   handleClick () { | ||||
|     hashHistory.push(`/statuses/${this.props.status.get('id')}`); | ||||
|   }, | ||||
|  | ||||
|   render () { | ||||
|     var content = { __html: this.props.status.get('content') }; | ||||
|     var media   = ''; | ||||
| @@ -37,13 +43,13 @@ const Status = React.createClass({ | ||||
|  | ||||
|     if (status.get('reblog') !== null) { | ||||
|       return ( | ||||
|         <div style={{ cursor: 'pointer' }}> | ||||
|         <div style={{ cursor: 'pointer' }} onClick={this.handleClick}> | ||||
|           <div style={{ marginLeft: '68px', color: '#616b86', padding: '8px 0', paddingBottom: '2px', fontSize: '14px', position: 'relative' }}> | ||||
|             <div style={{ position: 'absolute', 'left': '-26px'}}><i className='fa fa-fw fa-retweet'></i></div> | ||||
|             <a href={status.getIn(['account', 'url'])} className='status__display-name'><strong style={{ color: '#616b86'}}>{status.getIn(['account', 'display_name'])}</strong></a> reblogged | ||||
|           </div> | ||||
|  | ||||
|           <Status {...other} status={status.get('reblog')} /> | ||||
|           <Status {...other} wrapped={true} status={status.get('reblog')} /> | ||||
|         </div> | ||||
|       ); | ||||
|     } | ||||
| @@ -53,7 +59,7 @@ const Status = React.createClass({ | ||||
|     } | ||||
|  | ||||
|     return ( | ||||
|       <div style={{ padding: '8px 10px', paddingLeft: '68px', position: 'relative', minHeight: '48px', borderBottom: '1px solid #363c4b', cursor: 'pointer' }}> | ||||
|       <div style={{ padding: '8px 10px', paddingLeft: '68px', position: 'relative', minHeight: '48px', borderBottom: '1px solid #363c4b', cursor: 'pointer' }} onClick={this.handleClick}> | ||||
|         <div style={{ fontSize: '15px' }}> | ||||
|           <div style={{ float: 'right', fontSize: '14px' }}> | ||||
|             <a href={status.get('url')} className='status__relative-time' style={{ color: '#616b86' }}><RelativeTimestamp timestamp={status.get('created_at')} /></a> | ||||
|   | ||||
| @@ -27,7 +27,7 @@ const UploadButton = React.createClass({ | ||||
|           <i className='fa fa-fw fa-photo' /> Add images | ||||
|         </Button> | ||||
|  | ||||
|         <input ref='fileElement' type='file' onChange={this.handleChange} disabled={this.props.disabled} style={{ display: 'none' }} /> | ||||
|         <input ref='fileElement' type='file' multiple={false} onChange={this.handleChange} disabled={this.props.disabled} style={{ display: 'none' }} /> | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
|   | ||||
| @@ -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); | ||||
| @@ -3,25 +3,25 @@ import configureStore                                                        fro | ||||
| import Frontend                                                              from '../components/frontend'; | ||||
| import { setTimeline, updateTimeline, deleteFromTimelines, refreshTimeline } from '../actions/timelines'; | ||||
| import { setAccessToken }                                                    from '../actions/meta'; | ||||
| import { setAccountSelf }                                                    from '../actions/accounts'; | ||||
| import PureRenderMixin                                                       from 'react-addons-pure-render-mixin'; | ||||
| import { Router, Route, createMemoryHistory }                                from 'react-router'; | ||||
| import AccountRoute                                                          from '../routes/account_route'; | ||||
| import StatusRoute                                                           from '../routes/status_route'; | ||||
| import { Router, Route, hashHistory }                                        from 'react-router'; | ||||
|  | ||||
| const store   = configureStore(); | ||||
| const history = createMemoryHistory(); | ||||
| const store = configureStore(); | ||||
|  | ||||
| const Root = React.createClass({ | ||||
|  | ||||
|   propTypes: { | ||||
|     token: React.PropTypes.string.isRequired, | ||||
|     timelines: React.PropTypes.object | ||||
|     timelines: React.PropTypes.object, | ||||
|     account: React.PropTypes.string | ||||
|   }, | ||||
|  | ||||
|   mixins: [PureRenderMixin], | ||||
|  | ||||
|   componentWillMount() { | ||||
|     store.dispatch(setAccessToken(this.props.token)); | ||||
|     store.dispatch(setAccountSelf(JSON.parse(this.props.account))); | ||||
|  | ||||
|     for (var timelineType in this.props.timelines) { | ||||
|       if (this.props.timelines.hasOwnProperty(timelineType)) { | ||||
| @@ -53,10 +53,12 @@ const Root = React.createClass({ | ||||
|   render () { | ||||
|     return ( | ||||
|       <Provider store={store}> | ||||
|         <Router history={history}> | ||||
|           <Route path="/" component={Frontend}> | ||||
|             <Route path="/accounts/:account_id" component={AccountRoute} /> | ||||
|             <Route path="/statuses/:status_id" component={StatusRoute} /> | ||||
|         <Router history={hashHistory}> | ||||
|           <Route path='/' component={Frontend}> | ||||
|             <Route path='/settings' component={null} /> | ||||
|             <Route path='/subscriptions' component={null} /> | ||||
|             <Route path='/statuses/:statusId' component={null} /> | ||||
|             <Route path='/accounts/:accountId' component={null} /> | ||||
|           </Route> | ||||
|         </Router> | ||||
|       </Provider> | ||||
|   | ||||
| @@ -1,12 +1,14 @@ | ||||
| import { TIMELINE_SET, TIMELINE_UPDATE, TIMELINE_DELETE } from '../actions/timelines'; | ||||
| import { REBLOG_SUCCESS, FAVOURITE_SUCCESS }              from '../actions/interactions'; | ||||
| import { ACCOUNT_SET_SELF }                               from '../actions/accounts'; | ||||
| import Immutable                                          from 'immutable'; | ||||
|  | ||||
| const initialState = Immutable.Map({ | ||||
|   home: Immutable.List([]), | ||||
|   mentions: Immutable.List([]), | ||||
|   statuses: Immutable.Map(), | ||||
|   accounts: Immutable.Map() | ||||
|   accounts: Immutable.Map(), | ||||
|   me: null | ||||
| }); | ||||
|  | ||||
| function statusToMaps(state, status) { | ||||
| @@ -63,6 +65,11 @@ export default function timelines(state = initialState, action) { | ||||
|     case REBLOG_SUCCESS: | ||||
|     case FAVOURITE_SUCCESS: | ||||
|       return statusToMaps(state, Immutable.fromJS(action.response)); | ||||
|     case ACCOUNT_SET_SELF: | ||||
|       return state.withMutations(map => { | ||||
|         map.setIn(['accounts', action.account.id], Immutable.fromJS(action.account)); | ||||
|         map.set('me', action.account.id); | ||||
|       }); | ||||
|     default: | ||||
|       return state; | ||||
|   } | ||||
|   | ||||
| @@ -1,13 +0,0 @@ | ||||
| const AccountRoute = React.createClass({ | ||||
|  | ||||
|   render() { | ||||
|     return ( | ||||
|       <div> | ||||
|         {this.props.params.account_id} | ||||
|       </div> | ||||
|     ) | ||||
|   } | ||||
|  | ||||
| }); | ||||
|  | ||||
| export default AccountRoute; | ||||
| @@ -1,13 +0,0 @@ | ||||
| const StatusRoute = React.createClass({ | ||||
|  | ||||
|   render() { | ||||
|     return ( | ||||
|       <div> | ||||
|         {this.props.params.status_id} | ||||
|       </div> | ||||
|     ) | ||||
|   } | ||||
|  | ||||
| }); | ||||
|  | ||||
| export default StatusRoute; | ||||
| @@ -1,2 +1,14 @@ | ||||
| module HomeHelper | ||||
|   def default_props | ||||
|     { | ||||
|       token: @token, | ||||
|  | ||||
|       account: render(file: 'api/accounts/show', locals: { account: current_user.account }, formats: :json), | ||||
|  | ||||
|       timelines: { | ||||
|         home: render(file: 'api/statuses/home', locals: { statuses: @home }, formats: :json), | ||||
|         mentions: render(file: 'api/statuses/mentions', locals: { statuses: @mentions }, formats: :json) | ||||
|       } | ||||
|     } | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -1 +1 @@ | ||||
| = react_component 'Root', { token: @token, timelines: { home: render(file: 'api/statuses/home', locals: { statuses: @home }, formats: :json), mentions: render(file: 'api/statuses/mentions', locals: { statuses: @mentions }, formats: :json) }}, class: 'app-holder', prerender: false | ||||
| = react_component 'Root', default_props, class: 'app-holder', prerender: false | ||||
|   | ||||
		Reference in New Issue
	
	Block a user