From 2e7aac793ace0e938e45cb54ff601afa5d872214 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 13 Sep 2016 02:24:40 +0200 Subject: [PATCH] Adding sense of self to the UI, cleaning up routing, adding third (detail) column --- .../components/actions/accounts.jsx | 48 +++++++++++++++++++ .../components/actions/statuses.jsx | 6 +++ .../components/components/column.jsx | 13 ++++- .../components/components/columns_area.jsx | 2 +- .../components/components/display_name.jsx | 3 ++ .../components/components/frontend.jsx | 14 ++++-- .../components/components/navigation_bar.jsx | 30 ++++++++++++ .../components/components/status.jsx | 12 +++-- .../components/components/upload_button.jsx | 2 +- .../containers/navigation_container.jsx | 8 ++++ .../components/containers/root.jsx | 22 +++++---- .../components/reducers/timelines.jsx | 9 +++- .../components/routes/account_route.jsx | 13 ----- .../components/routes/status_route.jsx | 13 ----- app/helpers/home_helper.rb | 12 +++++ app/views/home/index.html.haml | 2 +- 16 files changed, 160 insertions(+), 49 deletions(-) create mode 100644 app/assets/javascripts/components/actions/accounts.jsx create mode 100644 app/assets/javascripts/components/actions/statuses.jsx create mode 100644 app/assets/javascripts/components/components/navigation_bar.jsx create mode 100644 app/assets/javascripts/components/containers/navigation_container.jsx delete mode 100644 app/assets/javascripts/components/routes/account_route.jsx delete mode 100644 app/assets/javascripts/components/routes/status_route.jsx diff --git a/app/assets/javascripts/components/actions/accounts.jsx b/app/assets/javascripts/components/actions/accounts.jsx new file mode 100644 index 0000000000..a334e3c20e --- /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 0000000000..faf33ea1da --- /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 7f9a4665a0..b3b13ba9e4 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 = ; } + 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 ( -
+
{header} {this.props.children}
diff --git a/app/assets/javascripts/components/components/columns_area.jsx b/app/assets/javascripts/components/components/columns_area.jsx index 7708b32730..e45a6466d4 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 ( -
+
{this.props.children}
); diff --git a/app/assets/javascripts/components/components/display_name.jsx b/app/assets/javascripts/components/components/display_name.jsx index f8d821bce6..8bffca5515 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 9d5166a081..aab3f55dcc 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({
+
@@ -32,6 +34,10 @@ const Frontend = React.createClass({ + + + {this.props.children} + 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 0000000000..3470cd52f4 --- /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 ( +
+ + +
+ {this.props.account.get('acct')} + Settings +
+
+ ); + } + +}); + +export default NavigationBar; diff --git a/app/assets/javascripts/components/components/status.jsx b/app/assets/javascripts/components/components/status.jsx index b72198b09a..d8a2a77e0a 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 ( -
+ ); } @@ -53,7 +59,7 @@ const Status = React.createClass({ } return ( -
+
diff --git a/app/assets/javascripts/components/components/upload_button.jsx b/app/assets/javascripts/components/components/upload_button.jsx index 295c3b8550..a77aacc10f 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({ Add images - +
); } 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 0000000000..4aeea4c37d --- /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 e2baefa240..1ded95d3c4 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 ( - - - - + + + + + + diff --git a/app/assets/javascripts/components/reducers/timelines.jsx b/app/assets/javascripts/components/reducers/timelines.jsx index b6ecdfb1fa..24adeccf8c 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 830621ed84..0000000000 --- a/app/assets/javascripts/components/routes/account_route.jsx +++ /dev/null @@ -1,13 +0,0 @@ -const AccountRoute = React.createClass({ - - render() { - return ( -
- {this.props.params.account_id} -
- ) - } - -}); - -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 358157f1e4..0000000000 --- a/app/assets/javascripts/components/routes/status_route.jsx +++ /dev/null @@ -1,13 +0,0 @@ -const StatusRoute = React.createClass({ - - render() { - return ( -
- {this.props.params.status_id} -
- ) - } - -}); - -export default StatusRoute; diff --git a/app/helpers/home_helper.rb b/app/helpers/home_helper.rb index 23de56ac60..d08264e1e2 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 9279ae9ae8..34028462c9 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