summaryrefslogtreecommitdiffstats
path: root/webapp/components
diff options
context:
space:
mode:
authorJoram Wilander <jwawilander@gmail.com>2016-04-29 08:40:06 -0400
committerChristopher Speller <crspeller@gmail.com>2016-04-29 08:40:06 -0400
commit1f4974dc02c786b65c802d4497fd736cca79d01c (patch)
tree1007e452c4a9345dee8aff113f28f235432bf323 /webapp/components
parent9961ccca7d39bdfabbafce423d3f7fe4b6ed2f29 (diff)
downloadchat-1f4974dc02c786b65c802d4497fd736cca79d01c.tar.gz
chat-1f4974dc02c786b65c802d4497fd736cca79d01c.tar.bz2
chat-1f4974dc02c786b65c802d4497fd736cca79d01c.zip
General react performance improvements (#2796)
* General React performance improvements * Cleaned up unused props/state in PermaLinkView and PostFocusView
Diffstat (limited to 'webapp/components')
-rw-r--r--webapp/components/channel_info_modal.jsx13
-rw-r--r--webapp/components/channel_view.jsx18
-rw-r--r--webapp/components/floating_timestamp.jsx54
-rw-r--r--webapp/components/get_post_link_modal.jsx33
-rw-r--r--webapp/components/get_team_invite_link_modal.jsx41
-rw-r--r--webapp/components/more_channels.jsx17
-rw-r--r--webapp/components/more_direct_channels.jsx21
-rw-r--r--webapp/components/needs_team.jsx7
-rw-r--r--webapp/components/permalink_view.jsx8
-rw-r--r--webapp/components/post_body.jsx47
-rw-r--r--webapp/components/post_focus_view.jsx21
-rw-r--r--webapp/components/posts_view.jsx98
-rw-r--r--webapp/components/posts_view_container.jsx22
-rw-r--r--webapp/components/rhs_comment.jsx39
-rw-r--r--webapp/components/sidebar.jsx5
-rw-r--r--webapp/components/sidebar_right_menu.jsx3
-rw-r--r--webapp/components/time_since.jsx6
-rw-r--r--webapp/components/user_profile.jsx19
18 files changed, 250 insertions, 222 deletions
diff --git a/webapp/components/channel_info_modal.jsx b/webapp/components/channel_info_modal.jsx
index 4c16dda90..d44df4056 100644
--- a/webapp/components/channel_info_modal.jsx
+++ b/webapp/components/channel_info_modal.jsx
@@ -9,6 +9,17 @@ import {Modal} from 'react-bootstrap';
import React from 'react';
export default class ChannelInfoModal extends React.Component {
+ shouldComponentUpdate(nextProps) {
+ if (nextProps.show !== this.props.show) {
+ return true;
+ }
+
+ if (!Utils.areObjectsEqual(nextProps.channel, this.props.channel)) {
+ return true;
+ }
+
+ return false;
+ }
render() {
let channel = this.props.channel;
if (!channel) {
@@ -93,4 +104,4 @@ ChannelInfoModal.propTypes = {
show: React.PropTypes.bool.isRequired,
onHide: React.PropTypes.func.isRequired,
channel: React.PropTypes.object.isRequired
-}; \ No newline at end of file
+};
diff --git a/webapp/components/channel_view.jsx b/webapp/components/channel_view.jsx
index 45d0f2393..6511d960a 100644
--- a/webapp/components/channel_view.jsx
+++ b/webapp/components/channel_view.jsx
@@ -11,6 +11,8 @@ import PostsViewContainer from 'components/posts_view_container.jsx';
import ChannelStore from 'stores/channel_store.jsx';
+import * as Utils from 'utils/utils.jsx';
+
export default class ChannelView extends React.Component {
constructor(props) {
super(props);
@@ -47,6 +49,17 @@ export default class ChannelView extends React.Component {
componentWillReceiveProps(nextProps) {
this.setState(this.getStateFromStores(nextProps));
}
+ shouldComponentUpdate(nextProps, nextState) {
+ if (!Utils.areObjectsEqual(nextProps.params, this.props.params)) {
+ return true;
+ }
+
+ if (nextState.channelId !== this.state.channelId) {
+ return true;
+ }
+
+ return false;
+ }
render() {
return (
<div
@@ -57,7 +70,7 @@ export default class ChannelView extends React.Component {
<ChannelHeader
channelId={this.state.channelId}
/>
- <PostsViewContainer profiles={this.props.profiles}/>
+ <PostsViewContainer/>
<div
className='post-create__container'
id='post-create'
@@ -72,6 +85,5 @@ ChannelView.defaultProps = {
};
ChannelView.propTypes = {
- params: React.PropTypes.object.isRequired,
- profiles: React.PropTypes.object
+ params: React.PropTypes.object.isRequired
};
diff --git a/webapp/components/floating_timestamp.jsx b/webapp/components/floating_timestamp.jsx
new file mode 100644
index 000000000..8974c62c5
--- /dev/null
+++ b/webapp/components/floating_timestamp.jsx
@@ -0,0 +1,54 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {FormattedDate} from 'react-intl';
+
+import React from 'react';
+import PureRenderMixin from 'react-addons-pure-render-mixin';
+
+export default class FloatingTimestamp extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
+ }
+
+ render() {
+ if (!this.props.isMobile) {
+ return <noscript/>;
+ }
+
+ if (this.props.createAt === 0) {
+ return <noscript/>;
+ }
+
+ const dateString = (
+ <FormattedDate
+ value={this.props.createAt}
+ weekday='short'
+ day='2-digit'
+ month='short'
+ year='numeric'
+ />
+ );
+
+ let className = 'post-list__timestamp';
+ if (this.props.isScrolling) {
+ className += ' scrolling';
+ }
+
+ return (
+ <div className={className}>
+ <div>
+ <span>{dateString}</span>
+ </div>
+ </div>
+ );
+ }
+}
+
+FloatingTimestamp.propTypes = {
+ isScrolling: React.PropTypes.bool.isRequired,
+ isMobile: React.PropTypes.bool,
+ createAt: React.PropTypes.number
+};
diff --git a/webapp/components/get_post_link_modal.jsx b/webapp/components/get_post_link_modal.jsx
index 4c56d4d64..cc01d1124 100644
--- a/webapp/components/get_post_link_modal.jsx
+++ b/webapp/components/get_post_link_modal.jsx
@@ -1,34 +1,25 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import Constants from 'utils/constants.jsx';
import GetLinkModal from './get_link_modal.jsx';
import ModalStore from 'stores/modal_store.jsx';
import TeamStore from 'stores/team_store.jsx';
-import {intlShape, injectIntl, defineMessages} from 'react-intl';
-
-const holders = defineMessages({
- title: {
- id: 'get_post_link_modal.title',
- defaultMessage: 'Copy Permalink'
- },
- help: {
- id: 'get_post_link_modal.help',
- defaultMessage: 'The link below allows authorized users to see your post.'
- }
-});
+import * as Utils from 'utils/utils.jsx';
+import Constants from 'utils/constants.jsx';
import React from 'react';
+import PureRenderMixin from 'react-addons-pure-render-mixin';
-class GetPostLinkModal extends React.Component {
+export default class GetPostLinkModal extends React.Component {
constructor(props) {
super(props);
this.handleToggle = this.handleToggle.bind(this);
-
this.hide = this.hide.bind(this);
+ this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
+
this.state = {
show: false,
post: {}
@@ -57,22 +48,14 @@ class GetPostLinkModal extends React.Component {
}
render() {
- const {formatMessage} = this.props.intl;
-
return (
<GetLinkModal
show={this.state.show}
onHide={this.hide}
- title={formatMessage(holders.title)}
- helpText={formatMessage(holders.help)}
+ title={Utils.localizeMessage('get_post_link_modal.title', 'Copy Permalink')}
+ helpText={Utils.localizeMessage('get_post_link_modal.help', 'The link below allows authorized users to see your post.')}
link={TeamStore.getCurrentTeamUrl() + '/pl/' + this.state.post.id}
/>
);
}
}
-
-GetPostLinkModal.propTypes = {
- intl: intlShape.isRequired
-};
-
-export default injectIntl(GetPostLinkModal);
diff --git a/webapp/components/get_team_invite_link_modal.jsx b/webapp/components/get_team_invite_link_modal.jsx
index 33cc065d1..109cb2120 100644
--- a/webapp/components/get_team_invite_link_modal.jsx
+++ b/webapp/components/get_team_invite_link_modal.jsx
@@ -1,36 +1,24 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import Constants from 'utils/constants.jsx';
import GetLinkModal from './get_link_modal.jsx';
import ModalStore from 'stores/modal_store.jsx';
import TeamStore from 'stores/team_store.jsx';
-import {intlShape, injectIntl, defineMessages} from 'react-intl';
-
-const holders = defineMessages({
- title: {
- id: 'get_team_invite_link_modal.title',
- defaultMessage: 'Team Invite Link'
- },
- help: {
- id: 'get_team_invite_link_modal.help',
- defaultMessage: 'Send teammates the link below for them to sign-up to this team site. The Team Invite Link can be shared with multiple teammates as it does not change unless it\'s regenerated in Team Settings by a Team Admin.'
- },
- helpDisabled: {
- id: 'get_team_invite_link_modal.helpDisabled',
- defaultMessage: 'User creation has been disabled for your team. Please ask your team administrator for details.'
- }
-});
+import * as Utils from 'utils/utils.jsx';
+import Constants from 'utils/constants.jsx';
import React from 'react';
+import PureRenderMixin from 'react-addons-pure-render-mixin';
-class GetTeamInviteLinkModal extends React.Component {
+export default class GetTeamInviteLinkModal extends React.Component {
constructor(props) {
super(props);
this.handleToggle = this.handleToggle.bind(this);
+ this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
+
this.state = {
show: false
};
@@ -51,28 +39,21 @@ class GetTeamInviteLinkModal extends React.Component {
}
render() {
- const {formatMessage} = this.props.intl;
-
- let helpText = formatMessage(holders.helpDisabled);
-
+ let helpText;
if (global.window.mm_config.EnableUserCreation === 'true') {
- helpText = formatMessage(holders.help);
+ helpText = Utils.localizeMessage('get_team_invite_link_modal.help', 'Send teammates the link below for them to sign-up to this team site. The Team Invite Link can be shared with multiple teammates as it does not change unless it\'s regenerated in Team Settings by a Team Admin.');
+ } else {
+ helpText = Utils.localizeMessage('get_team_invite_link_modal.helpDisabled', 'User creation has been disabled for your team. Please ask your team administrator for details.');
}
return (
<GetLinkModal
show={this.state.show}
onHide={() => this.setState({show: false})}
- title={formatMessage(holders.title)}
+ title={Utils.localizeMessage('get_team_invite_link_modal.title', 'Team Invite Link')}
helpText={helpText}
link={TeamStore.getCurrentInviteLink()}
/>
);
}
}
-
-GetTeamInviteLinkModal.propTypes = {
- intl: intlShape.isRequired
-};
-
-export default injectIntl(GetTeamInviteLinkModal);
diff --git a/webapp/components/more_channels.jsx b/webapp/components/more_channels.jsx
index 04c613ce5..087db68e6 100644
--- a/webapp/components/more_channels.jsx
+++ b/webapp/components/more_channels.jsx
@@ -2,17 +2,22 @@
// See License.txt for license information.
import $ from 'jquery';
-import ReactDOM from 'react-dom';
+import LoadingScreen from './loading_screen.jsx';
+import NewChannelFlow from './new_channel_flow.jsx';
+
+import ChannelStore from 'stores/channel_store.jsx';
+
import * as Utils from 'utils/utils.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import * as GlobalActions from 'action_creators/global_actions.jsx';
-import ChannelStore from 'stores/channel_store.jsx';
-import LoadingScreen from './loading_screen.jsx';
-import NewChannelFlow from './new_channel_flow.jsx';
import {FormattedMessage} from 'react-intl';
import {browserHistory} from 'react-router';
+import React from 'react';
+import ReactDOM from 'react-dom';
+import PureRenderMixin from 'react-addons-pure-render-mixin';
+
import loadingGif from 'images/load.gif';
function getStateFromStores() {
@@ -22,8 +27,6 @@ function getStateFromStores() {
};
}
-import React from 'react';
-
export default class MoreChannels extends React.Component {
constructor(props) {
super(props);
@@ -33,6 +36,8 @@ export default class MoreChannels extends React.Component {
this.handleNewChannel = this.handleNewChannel.bind(this);
this.createChannelRow = this.createChannelRow.bind(this);
+ this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
+
var initState = getStateFromStores();
initState.channelType = '';
initState.joiningChannel = -1;
diff --git a/webapp/components/more_direct_channels.jsx b/webapp/components/more_direct_channels.jsx
index a83816c40..a7fb2b6cd 100644
--- a/webapp/components/more_direct_channels.jsx
+++ b/webapp/components/more_direct_channels.jsx
@@ -19,7 +19,6 @@ export default class MoreDirectChannels extends React.Component {
this.handleHide = this.handleHide.bind(this);
this.handleShowDirectChannel = this.handleShowDirectChannel.bind(this);
this.handleUserChange = this.handleUserChange.bind(this);
-
this.createJoinDirectChannelButton = this.createJoinDirectChannelButton.bind(this);
this.state = {
@@ -52,6 +51,26 @@ export default class MoreDirectChannels extends React.Component {
UserStore.removeChangeListener(this.handleUserChange);
}
+ shouldComponentUpdate(nextProps, nextState) {
+ if (nextProps.show !== this.props.show) {
+ return true;
+ }
+
+ if (nextProps.onModalDismissed.toString() !== this.props.onModalDismissed.toString()) {
+ return true;
+ }
+
+ if (nextState.loadingDMChannel !== this.state.loadingDMChannel) {
+ return true;
+ }
+
+ if (!Utils.areObjectsEqual(nextState.users, this.state.users)) {
+ return true;
+ }
+
+ return false;
+ }
+
handleHide() {
if (this.props.onModalDismissed) {
this.props.onModalDismissed();
diff --git a/webapp/components/needs_team.jsx b/webapp/components/needs_team.jsx
index 4aea8fe46..92c6fc0ce 100644
--- a/webapp/components/needs_team.jsx
+++ b/webapp/components/needs_team.jsx
@@ -42,20 +42,18 @@ export default class NeedsTeam extends React.Component {
this.onChanged = this.onChanged.bind(this);
this.state = {
- profiles: UserStore.getProfiles(),
team: TeamStore.getCurrent()
};
}
onChanged() {
this.setState({
- profiles: UserStore.getProfiles(),
team: TeamStore.getCurrent()
});
}
componentWillMount() {
- // Go to tutorial if we are first arrivign
+ // Go to tutorial if we are first arriving
const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 999);
if (tutorialStep <= TutorialSteps.INTRO_SCREENS) {
browserHistory.push(Utils.getTeamURLNoOriginFromAddressBar() + '/tutorial');
@@ -63,7 +61,6 @@ export default class NeedsTeam extends React.Component {
}
componentDidMount() {
- UserStore.addChangeListener(this.onChanged);
TeamStore.addChangeListener(this.onChanged);
// Emit view action
@@ -84,7 +81,6 @@ export default class NeedsTeam extends React.Component {
}
componentWillUnmount() {
- UserStore.removeChangeListener(this.onChanged);
TeamStore.removeChangeListener(this.onChanged);
$(window).off('focus');
$(window).off('blur');
@@ -114,7 +110,6 @@ export default class NeedsTeam extends React.Component {
<div className='row main'>
{React.cloneElement(this.props.center, {
user: this.props.user,
- profiles: this.state.profiles,
team: this.state.team
})}
</div>
diff --git a/webapp/components/permalink_view.jsx b/webapp/components/permalink_view.jsx
index 07f826d57..c2019cb49 100644
--- a/webapp/components/permalink_view.jsx
+++ b/webapp/components/permalink_view.jsx
@@ -70,7 +70,7 @@ export default class PermalinkView extends React.Component {
<ChannelHeader
channelId={this.state.channelId}
/>
- <PostFocusView profiles={this.props.profiles}/>
+ <PostFocusView/>
<div
id='archive-link-home'
>
@@ -89,10 +89,6 @@ export default class PermalinkView extends React.Component {
}
}
-PermalinkView.defaultProps = {
-};
-
PermalinkView.propTypes = {
- params: React.PropTypes.object.isRequired,
- profiles: React.PropTypes.object
+ params: React.PropTypes.object.isRequired
};
diff --git a/webapp/components/post_body.jsx b/webapp/components/post_body.jsx
index 884dbbbbb..a159dcbb1 100644
--- a/webapp/components/post_body.jsx
+++ b/webapp/components/post_body.jsx
@@ -10,24 +10,13 @@ import * as TextFormatting from 'utils/text_formatting.jsx';
import twemoji from 'twemoji';
import PostBodyAdditionalContent from './post_body_additional_content.jsx';
-import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl';
+import {FormattedMessage} from 'react-intl';
import loadingGif from 'images/load.gif';
-const holders = defineMessages({
- plusOne: {
- id: 'post_body.plusOne',
- defaultMessage: ' plus 1 other file'
- },
- plusMore: {
- id: 'post_body.plusMore',
- defaultMessage: ' plus {count} other files'
- }
-});
-
import React from 'react';
-class PostBody extends React.Component {
+export default class PostBody extends React.Component {
constructor(props) {
super(props);
@@ -61,8 +50,27 @@ class PostBody extends React.Component {
this.parseEmojis();
}
+ shouldComponentUpdate(nextProps) {
+ if (!Utils.areObjectsEqual(nextProps.post, this.props.post)) {
+ return true;
+ }
+
+ if (!Utils.areObjectsEqual(nextProps.parentPost, this.props.parentPost)) {
+ return true;
+ }
+
+ if (nextProps.retryPost.toString() !== this.props.retryPost.toString()) {
+ return true;
+ }
+
+ if (nextProps.handleCommentClick.toString() !== this.props.handleCommentClick.toString()) {
+ return true;
+ }
+
+ return false;
+ }
+
render() {
- const {formatMessage} = this.props.intl;
const post = this.props.post;
const filenames = this.props.post.filenames;
const parentPost = this.props.parentPost;
@@ -106,9 +114,9 @@ class PostBody extends React.Component {
message = parentPost.filenames[0].split('/').pop();
if (parentPost.filenames.length === 2) {
- message += formatMessage(holders.plusOne);
+ message += Utils.localizeMessage('post_body.plusOne', ' plus 1 other file');
} else if (parentPost.filenames.length > 2) {
- message += formatMessage(holders.plusMore, {count: (parentPost.filenames.length - 1)});
+ message += Utils.localizeMessage('post_body.plusMore', ' plus {count} other files').replace('{count}', (parentPost.filenames.length - 1).toString());
}
}
@@ -119,8 +127,8 @@ class PostBody extends React.Component {
id='post_body.commentedOn'
defaultMessage='Commented on {name}{apostrophe} message: '
values={{
- name: (name),
- apostrophe: apostrophe
+ name,
+ apostrophe
}}
/>
<a
@@ -213,11 +221,8 @@ class PostBody extends React.Component {
}
PostBody.propTypes = {
- intl: intlShape.isRequired,
post: React.PropTypes.object.isRequired,
parentPost: React.PropTypes.object,
retryPost: React.PropTypes.func.isRequired,
handleCommentClick: React.PropTypes.func.isRequired
};
-
-export default injectIntl(PostBody);
diff --git a/webapp/components/post_focus_view.jsx b/webapp/components/post_focus_view.jsx
index f3dfb46c6..d2fbb4532 100644
--- a/webapp/components/post_focus_view.jsx
+++ b/webapp/components/post_focus_view.jsx
@@ -5,7 +5,6 @@ import PostsView from './posts_view.jsx';
import PostStore from 'stores/post_store.jsx';
import ChannelStore from 'stores/channel_store.jsx';
-import UserStore from 'stores/user_store.jsx';
import * as GlobalActions from 'action_creators/global_actions.jsx';
import {FormattedMessage} from 'react-intl';
@@ -18,7 +17,6 @@ export default class PostFocusView extends React.Component {
this.onChannelChange = this.onChannelChange.bind(this);
this.onPostsChange = this.onPostsChange.bind(this);
- this.onUserChange = this.onUserChange.bind(this);
this.handlePostsViewScroll = this.handlePostsViewScroll.bind(this);
this.loadMorePostsTop = this.loadMorePostsTop.bind(this);
this.loadMorePostsBottom = this.loadMorePostsBottom.bind(this);
@@ -31,21 +29,18 @@ export default class PostFocusView extends React.Component {
scrollPostId: focusedPostId,
postList: PostStore.getVisiblePosts(focusedPostId),
atTop: PostStore.getVisibilityAtTop(focusedPostId),
- atBottom: PostStore.getVisibilityAtBottom(focusedPostId),
- currentUser: UserStore.getCurrentUser()
+ atBottom: PostStore.getVisibilityAtBottom(focusedPostId)
};
}
componentDidMount() {
ChannelStore.addChangeListener(this.onChannelChange);
PostStore.addChangeListener(this.onPostsChange);
- UserStore.addChangeListener(this.onUserChange);
}
componentWillUnmount() {
ChannelStore.removeChangeListener(this.onChannelChange);
PostStore.removeChangeListener(this.onPostsChange);
- UserStore.removeChangeListener(this.onUserChange);
}
onChannelChange() {
@@ -58,10 +53,6 @@ export default class PostFocusView extends React.Component {
}
}
- onUserChange() {
- this.setState({currentUser: UserStore.getCurrentUser()});
- }
-
onPostsChange() {
const focusedPostId = PostStore.getFocusedPostId();
if (focusedPostId == null) {
@@ -105,7 +96,7 @@ export default class PostFocusView extends React.Component {
const postsToHighlight = {};
postsToHighlight[this.state.scrollPostId] = true;
- if (!this.state.currentUser || !this.state.postList) {
+ if (!this.state.postList) {
return null;
}
@@ -125,16 +116,8 @@ export default class PostFocusView extends React.Component {
introText={this.getIntroMessage()}
messageSeparatorTime={0}
postsToHighlight={postsToHighlight}
- profiles={this.props.profiles}
- currentUser={this.state.currentUser}
/>
</div>
);
}
}
-PostFocusView.defaultProps = {
-};
-
-PostFocusView.propTypes = {
- profiles: React.PropTypes.object
-};
diff --git a/webapp/components/posts_view.jsx b/webapp/components/posts_view.jsx
index be098086f..327756723 100644
--- a/webapp/components/posts_view.jsx
+++ b/webapp/components/posts_view.jsx
@@ -2,19 +2,25 @@
// See License.txt for license information.
import $ from 'jquery';
-import ReactDOM from 'react-dom';
-import PreferenceStore from 'stores/preference_store.jsx';
+
+import Post from './post.jsx';
+import FloatingTimestamp from './floating_timestamp.jsx';
+
import * as GlobalActions from 'action_creators/global_actions.jsx';
+
+import PreferenceStore from 'stores/preference_store.jsx';
+import UserStore from 'stores/user_store.jsx';
+
import * as Utils from 'utils/utils.jsx';
-import Post from './post.jsx';
-import Constants from 'utils/constants.jsx';
import DelayedAction from 'utils/delayed_action.jsx';
-import {FormattedDate, FormattedMessage} from 'react-intl';
-
+import Constants from 'utils/constants.jsx';
const Preferences = Constants.Preferences;
+import {FormattedDate, FormattedMessage} from 'react-intl';
+
import React from 'react';
+import ReactDOM from 'react-dom';
export default class PostsView extends React.Component {
constructor(props) {
@@ -31,6 +37,7 @@ export default class PostsView extends React.Component {
this.handleResize = this.handleResize.bind(this);
this.scrollToBottom = this.scrollToBottom.bind(this);
this.scrollToBottomAnimated = this.scrollToBottomAnimated.bind(this);
+ this.onUserChange = this.onUserChange.bind(this);
this.jumpToPostNode = null;
this.wasAtBottom = true;
@@ -43,7 +50,9 @@ export default class PostsView extends React.Component {
centerPosts: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT) === Preferences.CHANNEL_DISPLAY_MODE_CENTERED,
isScrolling: false,
topPostId: null,
- showUnreadMessageAlert: false
+ showUnreadMessageAlert: false,
+ currentUser: UserStore.getCurrentUser(),
+ profiles: UserStore.getProfiles()
};
}
static get SCROLL_TYPE_FREE() {
@@ -67,6 +76,9 @@ export default class PostsView extends React.Component {
centerPosts: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT) === Preferences.CHANNEL_DISPLAY_MODE_CENTERED
});
}
+ onUserChange() {
+ this.setState({currentUser: UserStore.getCurrentUser(), profiles: UserStore.getProfiles()});
+ }
isAtBottom() {
// consider the view to be at the bottom if it's within this many pixels of the bottom
const atBottomMargin = 10;
@@ -152,8 +164,8 @@ export default class PostsView extends React.Component {
createPosts(posts, order) {
const postCtls = [];
let previousPostDay = new Date(0);
- const userId = this.props.currentUser.id;
- const profiles = this.props.profiles || {};
+ const userId = this.state.currentUser.id;
+ const profiles = this.state.profiles || {};
let renderedLastViewed = false;
@@ -229,8 +241,8 @@ export default class PostsView extends React.Component {
const shouldHighlight = this.props.postsToHighlight && this.props.postsToHighlight.hasOwnProperty(post.id);
let profile;
- if (this.props.currentUser.id === post.user_id) {
- profile = this.props.currentUser;
+ if (userId === post.user_id) {
+ profile = this.state.currentUser;
} else {
profile = profiles[post.user_id];
}
@@ -250,7 +262,7 @@ export default class PostsView extends React.Component {
onClick={() => GlobalActions.emitPostFocusEvent(post.id)} //eslint-disable-line no-loop-func
displayNameType={this.state.displayNameType}
user={profile}
- currentUser={this.props.currentUser}
+ currentUser={this.state.currentUser}
center={this.state.centerPosts}
/>
);
@@ -377,13 +389,19 @@ export default class PostsView extends React.Component {
if (this.props.postList != null) {
this.updateScrolling();
}
+
+ if (this.props.isActive) {
+ PreferenceStore.addChangeListener(this.updateState);
+ UserStore.addChangeListener(this.onUserChange);
+ }
+
window.addEventListener('resize', this.handleResize);
- PreferenceStore.addChangeListener(this.updateState);
}
componentWillUnmount() {
window.removeEventListener('resize', this.handleResize);
this.scrollStopAction.cancel();
PreferenceStore.removeChangeListener(this.updateState);
+ UserStore.removeChangeListener(this.onUserChange);
}
componentDidUpdate() {
if (this.props.postList != null) {
@@ -401,8 +419,10 @@ export default class PostsView extends React.Component {
if (!this.props.isActive && nextProps.isActive) {
this.updateState();
PreferenceStore.addChangeListener(this.updateState);
+ UserStore.addChangeListener(this.onUserChange);
} else if (this.props.isActive && !nextProps.isActive) {
PreferenceStore.removeChangeListener(this.updateState);
+ UserStore.removeChangeListener(this.onUserChange);
}
}
shouldComponentUpdate(nextProps, nextState) {
@@ -436,7 +456,7 @@ export default class PostsView extends React.Component {
if (this.state.centerPosts !== nextState.centerPosts) {
return true;
}
- if (!Utils.areObjectsEqual(this.props.profiles, nextProps.profiles)) {
+ if (!Utils.areObjectsEqual(this.state.profiles, nextState.profiles)) {
return true;
}
@@ -497,16 +517,17 @@ export default class PostsView extends React.Component {
}
}
- let topPost = null;
+ let topPostCreateAt = 0;
if (this.state.topPostId) {
- topPost = this.props.postList.posts[this.state.topPostId];
+ topPostCreateAt = this.props.postList.posts[this.state.topPostId].create_at;
}
return (
<div className={activeClass}>
<FloatingTimestamp
isScrolling={this.state.isScrolling}
- post={topPost}
+ isMobile={$(window).width() > 768}
+ createAt={topPostCreateAt}
/>
<ScrollToBottomArrows
isScrolling={this.state.isScrolling}
@@ -551,7 +572,6 @@ PostsView.defaultProps = {
PostsView.propTypes = {
isActive: React.PropTypes.bool,
postList: React.PropTypes.object,
- profiles: React.PropTypes.object.isRequired,
scrollPostId: React.PropTypes.string,
scrollType: React.PropTypes.number,
postViewScrolled: React.PropTypes.func.isRequired,
@@ -561,47 +581,7 @@ PostsView.propTypes = {
showMoreMessagesBottom: React.PropTypes.bool,
introText: React.PropTypes.element,
messageSeparatorTime: React.PropTypes.number,
- postsToHighlight: React.PropTypes.object,
- currentUser: React.PropTypes.object.isRequired
-};
-
-function FloatingTimestamp({isScrolling, post}) {
- // only show on mobile
- if ($(window).width() > 768) {
- return <noscript/>;
- }
-
- if (!post) {
- return <noscript/>;
- }
-
- const dateString = (
- <FormattedDate
- value={post.create_at}
- weekday='short'
- day='2-digit'
- month='short'
- year='numeric'
- />
- );
-
- let className = 'post-list__timestamp';
- if (isScrolling) {
- className += ' scrolling';
- }
-
- return (
- <div className={className}>
- <div>
- <span>{dateString}</span>
- </div>
- </div>
- );
-}
-
-FloatingTimestamp.propTypes = {
- isScrolling: React.PropTypes.bool.isRequired,
- post: React.PropTypes.object
+ postsToHighlight: React.PropTypes.object
};
function ScrollToBottomArrows({isScrolling, atBottom, onClick}) {
diff --git a/webapp/components/posts_view_container.jsx b/webapp/components/posts_view_container.jsx
index edfa314f8..32b0aa578 100644
--- a/webapp/components/posts_view_container.jsx
+++ b/webapp/components/posts_view_container.jsx
@@ -1,12 +1,13 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+import $ from 'jquery';
+
import PostsView from './posts_view.jsx';
import LoadingScreen from './loading_screen.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import PostStore from 'stores/post_store.jsx';
-import UserStore from 'stores/user_store.jsx';
import * as GlobalActions from 'action_creators/global_actions.jsx';
@@ -25,7 +26,6 @@ export default class PostsViewContainer extends React.Component {
this.onChannelChange = this.onChannelChange.bind(this);
this.onChannelLeave = this.onChannelLeave.bind(this);
this.onPostsChange = this.onPostsChange.bind(this);
- this.onUserChange = this.onUserChange.bind(this);
this.handlePostsViewScroll = this.handlePostsViewScroll.bind(this);
this.loadMorePostsTop = this.loadMorePostsTop.bind(this);
this.handlePostsViewJumpRequest = this.handlePostsViewJumpRequest.bind(this);
@@ -33,8 +33,7 @@ export default class PostsViewContainer extends React.Component {
const currentChannelId = ChannelStore.getCurrentId();
const state = {
scrollType: PostsView.SCROLL_TYPE_BOTTOM,
- scrollPost: null,
- currentUser: UserStore.getCurrentUser()
+ scrollPost: null
};
if (currentChannelId) {
Object.assign(state, {
@@ -60,17 +59,14 @@ export default class PostsViewContainer extends React.Component {
ChannelStore.addLeaveListener(this.onChannelLeave);
PostStore.addChangeListener(this.onPostsChange);
PostStore.addPostsViewJumpListener(this.handlePostsViewJumpRequest);
- UserStore.addChangeListener(this.onUserChange);
+ $('body').addClass('app__body');
}
componentWillUnmount() {
ChannelStore.removeChangeListener(this.onChannelChange);
ChannelStore.removeLeaveListener(this.onChannelLeave);
PostStore.removeChangeListener(this.onPostsChange);
PostStore.removePostsViewJumpListener(this.handlePostsViewJumpRequest);
- UserStore.removeChangeListener(this.onUserChange);
- }
- onUserChange() {
- this.setState({currentUser: UserStore.getCurrentUser()});
+ $('body').removeClass('app__body');
}
handlePostsViewJumpRequest(type, post) {
switch (type) {
@@ -171,7 +167,7 @@ export default class PostsViewContainer extends React.Component {
const currentChannelId = channels[this.state.currentChannelIndex];
const channel = ChannelStore.get(currentChannelId);
- if (!this.state.currentUser || !channel) {
+ if (!channel) {
return null;
}
@@ -194,8 +190,6 @@ export default class PostsViewContainer extends React.Component {
showMoreMessagesBottom={false}
introText={channel ? createChannelIntroMessage(channel) : null}
messageSeparatorTime={this.state.currentLastViewed}
- profiles={this.props.profiles}
- currentUser={this.state.currentUser}
/>
);
if (!postLists[i] && isActive) {
@@ -215,7 +209,3 @@ export default class PostsViewContainer extends React.Component {
);
}
}
-
-PostsViewContainer.propTypes = {
- profiles: React.PropTypes.object
-};
diff --git a/webapp/components/rhs_comment.jsx b/webapp/components/rhs_comment.jsx
index 709865dc1..290eb64c6 100644
--- a/webapp/components/rhs_comment.jsx
+++ b/webapp/components/rhs_comment.jsx
@@ -1,35 +1,32 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import ReactDOM from 'react-dom';
+import UserProfile from './user_profile.jsx';
+import FileAttachmentList from './file_attachment_list.jsx';
+
import PostStore from 'stores/post_store.jsx';
import ChannelStore from 'stores/channel_store.jsx';
-import UserProfile from './user_profile.jsx';
+
+import * as GlobalActions from 'action_creators/global_actions.jsx';
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
+
+import * as TextFormatting from 'utils/text_formatting.jsx';
import * as Utils from 'utils/utils.jsx';
-import Constants from 'utils/constants.jsx';
-import FileAttachmentList from './file_attachment_list.jsx';
import Client from 'utils/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
-var ActionTypes = Constants.ActionTypes;
-import * as TextFormatting from 'utils/text_formatting.jsx';
-import twemoji from 'twemoji';
-import * as GlobalActions from 'action_creators/global_actions.jsx';
-import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedDate} from 'react-intl';
+import Constants from 'utils/constants.jsx';
+const ActionTypes = Constants.ActionTypes;
-import loadingGif from 'images/load.gif';
+import {FormattedMessage, FormattedDate} from 'react-intl';
-const holders = defineMessages({
- comment: {
- id: 'rhs_comment.comment',
- defaultMessage: 'Comment'
- }
-});
+import loadingGif from 'images/load.gif';
import React from 'react';
+import ReactDOM from 'react-dom';
+import twemoji from 'twemoji';
-class RhsComment extends React.Component {
+export default class RhsComment extends React.Component {
constructor(props) {
super(props);
@@ -136,7 +133,7 @@ class RhsComment extends React.Component {
data-toggle='modal'
data-target='#edit_post'
data-refocusid='#reply_textbox'
- data-title={this.props.intl.formatMessage(holders.comment)}
+ data-title={Utils.localizeMessage('rhs_comment.comment', 'Comment')}
data-message={post.message}
data-postid={post.id}
data-channelid={post.channel_id}
@@ -302,14 +299,8 @@ class RhsComment extends React.Component {
}
}
-RhsComment.defaultProps = {
- post: null
-};
RhsComment.propTypes = {
- intl: intlShape.isRequired,
post: React.PropTypes.object,
user: React.PropTypes.object.isRequired,
currentUser: React.PropTypes.object.isRequired
};
-
-export default injectIntl(RhsComment);
diff --git a/webapp/components/sidebar.jsx b/webapp/components/sidebar.jsx
index 2ccf01740..1f73e743f 100644
--- a/webapp/components/sidebar.jsx
+++ b/webapp/components/sidebar.jsx
@@ -698,8 +698,3 @@ export default class Sidebar extends React.Component {
);
}
}
-
-Sidebar.defaultProps = {
-};
-Sidebar.propTypes = {
-};
diff --git a/webapp/components/sidebar_right_menu.jsx b/webapp/components/sidebar_right_menu.jsx
index 42bc7ce53..b368f9fe1 100644
--- a/webapp/components/sidebar_right_menu.jsx
+++ b/webapp/components/sidebar_right_menu.jsx
@@ -20,6 +20,7 @@ import {Link} from 'react-router';
import {createMenuTip} from 'components/tutorial/tutorial_tip.jsx';
import React from 'react';
+import PureRenderMixin from 'react-addons-pure-render-mixin';
export default class SidebarRightMenu extends React.Component {
constructor(props) {
@@ -30,6 +31,8 @@ export default class SidebarRightMenu extends React.Component {
const state = this.getStateFromStores();
state.showUserSettingsModal = false;
+ this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
+
this.state = state;
}
diff --git a/webapp/components/time_since.jsx b/webapp/components/time_since.jsx
index 02b0174ae..9f7a93ceb 100644
--- a/webapp/components/time_since.jsx
+++ b/webapp/components/time_since.jsx
@@ -9,8 +9,14 @@ import {FormattedRelative, FormattedDate} from 'react-intl';
import {Tooltip, OverlayTrigger} from 'react-bootstrap';
import React from 'react';
+import PureRenderMixin from 'react-addons-pure-render-mixin';
export default class TimeSince extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
+ }
componentDidMount() {
this.intervalId = setInterval(() => {
this.forceUpdate();
diff --git a/webapp/components/user_profile.jsx b/webapp/components/user_profile.jsx
index 04b4f99a4..673422d3f 100644
--- a/webapp/components/user_profile.jsx
+++ b/webapp/components/user_profile.jsx
@@ -22,6 +22,25 @@ export default class UserProfile extends React.Component {
super(props);
this.uniqueId = nextId();
}
+ shouldComponentUpdate(nextProps) {
+ if (!Utils.areObjectsEqual(nextProps.user, this.props.user)) {
+ return true;
+ }
+
+ if (nextProps.overwriteName !== this.props.overwriteName) {
+ return true;
+ }
+
+ if (nextProps.overwriteImage !== this.props.overwriteImage) {
+ return true;
+ }
+
+ if (nextProps.disablePopover !== this.props.disablePopover) {
+ return true;
+ }
+
+ return false;
+ }
render() {
let name = '...';
let email = '';