commit: 98c3a5e9c38b3bc653002dafab0504fdee3d2c44
parent: 6d26bfd14706872f6e4d1c3a3e45b00d81cf86cb
Author: Eugen Rochko <eugen@zeonfederated.com>
Date: Fri, 4 Nov 2016 12:48:53 +0100
Optimize how statuses are re-rendered and relative time intervals
Diffstat:
6 files changed, 89 insertions(+), 31 deletions(-)
diff --git a/app/assets/javascripts/components/actions/interactions.jsx b/app/assets/javascripts/components/actions/interactions.jsx
@@ -20,6 +20,10 @@ export const REBLOGS_FETCH_REQUEST = 'REBLOGS_FETCH_REQUEST';
export const REBLOGS_FETCH_SUCCESS = 'REBLOGS_FETCH_SUCCESS';
export const REBLOGS_FETCH_FAIL = 'REBLOGS_FETCH_FAIL';
+export const FAVOURITES_FETCH_REQUEST = 'FAVOURITES_FETCH_REQUEST';
+export const FAVOURITES_FETCH_SUCCESS = 'FAVOURITES_FETCH_SUCCESS';
+export const FAVOURITES_FETCH_FAIL = 'FAVOURITES_FETCH_FAIL';
+
export function reblog(status) {
return function (dispatch, getState) {
dispatch(reblogRequest(status));
diff --git a/app/assets/javascripts/components/components/relative_timestamp.jsx b/app/assets/javascripts/components/components/relative_timestamp.jsx
@@ -21,35 +21,28 @@ moment.updateLocale('en', {
const RelativeTimestamp = React.createClass({
- getInitialState () {
- return {
- text: ''
- };
- },
-
propTypes: {
- timestamp: React.PropTypes.string.isRequired
+ timestamp: React.PropTypes.string.isRequired,
+ now: React.PropTypes.any
},
mixins: [PureRenderMixin],
- componentWillMount () {
- this._updateMomentText();
- this.interval = setInterval(this._updateMomentText, 60000);
- },
+ render () {
+ const timestamp = moment(this.props.timestamp);
+ const now = this.props.now;
- componentWillUnmount () {
- clearInterval(this.interval);
- },
+ let string = '';
- _updateMomentText () {
- this.setState({ text: moment(this.props.timestamp).fromNow() });
- },
+ if (timestamp.isAfter(now)) {
+ string = 'Just now';
+ } else {
+ string = timestamp.from(now);
+ }
- render () {
return (
<span>
- {this.state.text}
+ {string}
</span>
);
}
diff --git a/app/assets/javascripts/components/components/status.jsx b/app/assets/javascripts/components/components/status.jsx
@@ -22,7 +22,8 @@ const Status = React.createClass({
onReblog: React.PropTypes.func,
onDelete: React.PropTypes.func,
onOpenMedia: React.PropTypes.func,
- me: React.PropTypes.number
+ me: React.PropTypes.number,
+ now: React.PropTypes.any
},
mixins: [PureRenderMixin],
@@ -43,7 +44,7 @@ const Status = React.createClass({
render () {
let media = '';
- let { status, ...other } = this.props;
+ const { status, now, ...other } = this.props;
if (status === null) {
return <div />;
@@ -80,7 +81,7 @@ const Status = React.createClass({
<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' }} target='_blank' rel='noopener'><RelativeTimestamp timestamp={status.get('created_at')} /></a>
+ <a href={status.get('url')} className='status__relative-time' style={{ color: '#616b86' }} target='_blank' rel='noopener'><RelativeTimestamp timestamp={status.get('created_at')} now={now} /></a>
</div>
<a onClick={this.handleAccountClick.bind(this, status.getIn(['account', 'id']))} href={status.getIn(['account', 'url'])} className='status__display-name' style={{ display: 'block', maxWidth: '100%', paddingRight: '25px', color: '#616b86' }}>
diff --git a/app/assets/javascripts/components/components/status_list.jsx b/app/assets/javascripts/components/components/status_list.jsx
@@ -3,6 +3,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import PureRenderMixin from 'react-addons-pure-render-mixin';
import { ScrollContainer } from 'react-router-scroll';
import StatusContainer from '../containers/status_container';
+import moment from 'moment';
const StatusList = React.createClass({
@@ -18,8 +19,22 @@ const StatusList = React.createClass({
};
},
+ getInitialState () {
+ return {
+ now: moment()
+ };
+ },
+
mixins: [PureRenderMixin],
+ componentDidMount () {
+ this._interval = setInterval(() => this.setState({ now: moment() }), 60000);
+ },
+
+ componentWillUnmount () {
+ clearInterval(this._interval);
+ },
+
handleScroll (e) {
const { scrollTop, scrollHeight, clientHeight } = e.target;
@@ -35,7 +50,7 @@ const StatusList = React.createClass({
<div style={{ overflowY: 'scroll', flex: '1 1 auto', overflowX: 'hidden' }} className='scrollable' onScroll={this.handleScroll}>
<div>
{statusIds.map((statusId) => {
- return <StatusContainer key={statusId} id={statusId} />;
+ return <StatusContainer key={statusId} id={statusId} now={this.state.now} />;
})}
</div>
</div>
diff --git a/app/assets/javascripts/components/containers/status_container.jsx b/app/assets/javascripts/components/containers/status_container.jsx
@@ -13,13 +13,47 @@ import {
} from '../actions/interactions';
import { deleteStatus } from '../actions/statuses';
import { openMedia } from '../actions/modal';
+import { createSelector } from 'reselect'
-const makeMapStateToProps = () => {
- const getStatus = makeGetStatus();
+const mapStateToProps = (state, props) => ({
+ statusBase: state.getIn(['statuses', props.id]),
+ me: state.getIn(['meta', 'me'])
+});
+
+const makeMapStateToPropsInner = () => {
+ const getStatus = (() => {
+ return createSelector(
+ [
+ (_, base) => base,
+ (state, base) => (base ? state.getIn(['accounts', base.get('account')]) : null),
+ (state, base) => (base ? state.getIn(['statuses', base.get('reblog')], null) : null)
+ ],
+
+ (base, account, reblog) => (base ? base.set('account', account).set('reblog', reblog) : null)
+ );
+ })();
+
+ const mapStateToProps = (state, { statusBase }) => ({
+ status: getStatus(state, statusBase)
+ });
+
+ return mapStateToProps;
+};
+
+const makeMapStateToPropsLast = () => {
+ const getStatus = (() => {
+ return createSelector(
+ [
+ (_, status) => status,
+ (state, status) => (status ? state.getIn(['accounts', status.getIn(['reblog', 'account'])], null) : null)
+ ],
+
+ (status, reblogAccount) => (status && status.get('reblog') ? status.setIn(['reblog', 'account'], reblogAccount) : status)
+ );
+ })();
- const mapStateToProps = (state, props) => ({
- status: getStatus(state, props.id),
- me: state.getIn(['meta', 'me'])
+ const mapStateToProps = (state, { status }) => ({
+ status: getStatus(state, status)
});
return mapStateToProps;
@@ -61,4 +95,8 @@ const mapDispatchToProps = (dispatch) => ({
});
-export default connect(makeMapStateToProps, mapDispatchToProps)(Status);
+export default connect(mapStateToProps, mapDispatchToProps)(
+ connect(makeMapStateToPropsInner)(
+ connect(makeMapStateToPropsLast)(Status)
+ )
+);
diff --git a/app/assets/javascripts/components/reducers/user_lists.jsx b/app/assets/javascripts/components/reducers/user_lists.jsx
@@ -3,13 +3,18 @@ import {
FOLLOWING_FETCH_SUCCESS
} from '../actions/accounts';
import { SUGGESTIONS_FETCH_SUCCESS } from '../actions/suggestions';
-import { REBLOGS_FETCH_SUCCESS } from '../actions/interactions';
+import {
+ REBLOGS_FETCH_SUCCESS,
+ FAVOURITES_FETCH_SUCCESS
+} from '../actions/interactions';
import Immutable from 'immutable';
const initialState = Immutable.Map({
followers: Immutable.Map(),
following: Immutable.Map(),
- suggestions: Immutable.List()
+ suggestions: Immutable.List(),
+ reblogged_by: Immutable.Map(),
+ favourited_by: Immutable.Map()
});
export default function userLists(state = initialState, action) {
@@ -22,6 +27,8 @@ export default function userLists(state = initialState, action) {
return state.set('suggestions', Immutable.List(action.accounts.map(item => item.id)));
case REBLOGS_FETCH_SUCCESS:
return state.setIn(['reblogged_by', action.id], Immutable.List(action.accounts.map(item => item.id)));
+ case FAVOURITES_FETCH_SUCCESS:
+ return state.setIn(['favourited_by', action.id], Immutable.List(action.accounts.map(item => item.id)));
default:
return state;
}