diff options
author | Eugen Rochko <eugen@zeonfederated.com> | 2016-09-13 02:24:40 +0200 |
---|---|---|
committer | Eugen Rochko <eugen@zeonfederated.com> | 2016-09-13 02:24:40 +0200 |
commit | 2e7aac793ace0e938e45cb54ff601afa5d872214 (patch) | |
tree | 5c45c25a21ef9b1068604b8c459e1855ba91f239 | |
parent | d6a64f45fd4530cfee4f7721f0c6e7ca28fe677f (diff) |
Adding sense of self to the UI, cleaning up routing, adding third (detail) column
16 files changed, 160 insertions, 49 deletions
diff --git a/app/assets/javascripts/components/actions/accounts.jsx b/app/assets/javascripts/components/actions/accounts.jsx new file mode 100644 index 000000000..a334e3c20 --- /dev/null +++ b/app/assets/javascripts/components/actions/accounts.jsx @@ -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 + }; +}; diff --git a/app/assets/javascripts/components/actions/statuses.jsx b/app/assets/javascripts/components/actions/statuses.jsx new file mode 100644 index 000000000..faf33ea1d --- /dev/null +++ b/app/assets/javascripts/components/actions/statuses.jsx @@ -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'; diff --git a/app/assets/javascripts/components/components/column.jsx b/app/assets/javascripts/components/components/column.jsx index 7f9a4665a..b3b13ba9e 100644 --- a/app/assets/javascripts/components/components/column.jsx +++ b/app/assets/javascripts/components/components/column.jsx @@ -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> diff --git a/app/assets/javascripts/components/components/columns_area.jsx b/app/assets/javascripts/components/components/columns_area.jsx index 7708b3273..e45a6466d 100644 --- a/app/assets/javascripts/components/components/columns_area.jsx +++ b/app/assets/javascripts/components/components/columns_area.jsx @@ -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> ); diff --git a/app/assets/javascripts/components/components/display_name.jsx b/app/assets/javascripts/components/components/display_name.jsx index f8d821bce..8bffca551 100644 --- a/app/assets/javascripts/components/components/display_name.jsx +++ b/app/assets/javascripts/components/components/display_name.jsx @@ -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'); diff --git a/app/assets/javascripts/components/components/frontend.jsx b/app/assets/javascripts/components/components/frontend.jsx index 9d5166a08..aab3f55dc 100644 --- a/app/assets/javascripts/components/components/frontend.jsx +++ b/app/assets/javascripts/components/components/frontend.jsx @@ -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 /> diff --git a/app/assets/javascripts/components/components/navigation_bar.jsx b/app/assets/javascripts/components/components/navigation_bar.jsx new file mode 100644 index 000000000..3470cd52f --- /dev/null +++ b/app/assets/javascripts/components/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 './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; diff --git a/app/assets/javascripts/components/components/status.jsx b/app/assets/javascripts/components/components/status.jsx index b72198b09..d8a2a77e0 100644 --- a/app/assets/javascripts/components/components/status.jsx +++ b/app/assets/javascripts/components/components/status.jsx @@ -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> diff --git a/app/assets/javascripts/components/components/upload_button.jsx b/app/assets/javascripts/components/components/upload_button.jsx index 295c3b855..a77aacc10 100644 --- a/app/assets/javascripts/components/components/upload_button.jsx +++ b/app/assets/javascripts/components/components/upload_button.jsx @@ -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> ); } diff --git a/app/assets/javascripts/components/containers/navigation_container.jsx b/app/assets/javascripts/components/containers/navigation_container.jsx new file mode 100644 index 000000000..4aeea4c37 --- /dev/null +++ b/app/assets/javascripts/components/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/containers/root.jsx b/app/assets/javascripts/components/containers/root.jsx index e2baefa24..1ded95d3c 100644 --- a/app/assets/javascripts/components/containers/root.jsx +++ b/app/assets/javascripts/components/containers/root.jsx @@ -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> diff --git a/app/assets/javascripts/components/reducers/timelines.jsx b/app/assets/javascripts/components/reducers/timelines.jsx index b6ecdfb1f..24adeccf8 100644 --- a/app/assets/javascripts/components/reducers/timelines.jsx +++ b/app/assets/javascripts/components/reducers/timelines.jsx @@ -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; } diff --git a/app/assets/javascripts/components/routes/account_route.jsx b/app/assets/javascripts/components/routes/account_route.jsx deleted file mode 100644 index 830621ed8..000000000 --- a/app/assets/javascripts/components/routes/account_route.jsx +++ /dev/null @@ -1,13 +0,0 @@ -const AccountRoute = React.createClass({ - - render() { - return ( - <div> - {this.props.params.account_id} - </div> - ) - } - -}); - -export default AccountRoute; diff --git a/app/assets/javascripts/components/routes/status_route.jsx b/app/assets/javascripts/components/routes/status_route.jsx deleted file mode 100644 index 358157f1e..000000000 --- a/app/assets/javascripts/components/routes/status_route.jsx +++ /dev/null @@ -1,13 +0,0 @@ -const StatusRoute = React.createClass({ - - render() { - return ( - <div> - {this.props.params.status_id} - </div> - ) - } - -}); - -export default StatusRoute; diff --git a/app/helpers/home_helper.rb b/app/helpers/home_helper.rb index 23de56ac6..d08264e1e 100644 --- a/app/helpers/home_helper.rb +++ b/app/helpers/home_helper.rb @@ -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 diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index 9279ae9ae..34028462c 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -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 |