From 96906482cecb0df21c8e1a40a2ba00c13c0182a7 Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Fri, 28 Apr 2017 13:16:03 -0400 Subject: PLT-6214 Move channel store and actions over to redux (#6235) * Move channel store and actions over to redux * Fix style errors * Fix unit test * Various fixes * More fixes * Revert config changes --- webapp/actions/channel_actions.jsx | 403 +++++++-------------- webapp/actions/global_actions.jsx | 15 +- webapp/actions/post_actions.jsx | 3 +- webapp/actions/team_actions.jsx | 5 +- webapp/actions/websocket_actions.jsx | 20 +- webapp/components/channel_members_dropdown.jsx | 264 -------------- .../channel_members_dropdown.jsx | 266 ++++++++++++++ .../components/channel_members_dropdown/index.js | 24 ++ webapp/components/channel_members_modal.jsx | 2 +- webapp/components/channel_view.jsx | 2 +- webapp/components/member_list_channel.jsx | 169 --------- webapp/components/member_list_channel/index.js | 24 ++ .../member_list_channel/member_list_channel.jsx | 171 +++++++++ webapp/components/more_channels.jsx | 201 ---------- webapp/components/more_channels/index.js | 24 ++ webapp/components/more_channels/more_channels.jsx | 203 +++++++++++ webapp/components/needs_team.jsx | 231 ------------ webapp/components/needs_team/index.js | 25 ++ webapp/components/needs_team/needs_team.jsx | 234 ++++++++++++ webapp/components/new_channel_flow.jsx | 6 +- webapp/components/post_view/index.js | 24 ++ webapp/components/post_view/post_view_cache.jsx | 9 +- webapp/components/rename_channel_modal.jsx | 5 +- webapp/components/sidebar.jsx | 2 +- webapp/components/team_members_dropdown/index.js | 4 +- .../team_members_dropdown.jsx | 8 +- webapp/package.json | 2 +- webapp/root.jsx | 11 +- webapp/routes/route_team.jsx | 52 ++- webapp/stores/channel_store.jsx | 171 +++++---- webapp/utils/async_client.jsx | 229 ------------ 31 files changed, 1307 insertions(+), 1502 deletions(-) delete mode 100644 webapp/components/channel_members_dropdown.jsx create mode 100644 webapp/components/channel_members_dropdown/channel_members_dropdown.jsx create mode 100644 webapp/components/channel_members_dropdown/index.js delete mode 100644 webapp/components/member_list_channel.jsx create mode 100644 webapp/components/member_list_channel/index.js create mode 100644 webapp/components/member_list_channel/member_list_channel.jsx delete mode 100644 webapp/components/more_channels.jsx create mode 100644 webapp/components/more_channels/index.js create mode 100644 webapp/components/more_channels/more_channels.jsx delete mode 100644 webapp/components/needs_team.jsx create mode 100644 webapp/components/needs_team/index.js create mode 100644 webapp/components/needs_team/needs_team.jsx create mode 100644 webapp/components/post_view/index.js (limited to 'webapp') diff --git a/webapp/actions/channel_actions.jsx b/webapp/actions/channel_actions.jsx index d441a0e94..79dfe3212 100644 --- a/webapp/actions/channel_actions.jsx +++ b/webapp/actions/channel_actions.jsx @@ -20,6 +20,29 @@ import {Constants, Preferences, ActionTypes} from 'utils/constants.jsx'; import {browserHistory} from 'react-router/es6'; +// Redux actions +import store from 'stores/redux_store.jsx'; +const dispatch = store.dispatch; +const getState = store.getState; + +import { + viewChannel, + addChannelMember, + removeChannelMember, + updateChannelMemberRoles, + createDirectChannel, + fetchMyChannelsAndMembers, + joinChannel as joinChannelRedux, + leaveChannel as leaveChannelRedux, + updateChannel as updateChannelRedux, + searchChannels, + updateChannelNotifyProps as updateChannelNotifyPropsRedux, + createChannel as createChannelRedux, + patchChannel, + getChannelMembersByIds, + deleteChannel as deleteChannelRedux +} from 'mattermost-redux/actions/channels'; + export function goToChannel(channel) { if (channel.fake) { const user = UserStore.getProfileByUsername(channel.display_name); @@ -66,7 +89,7 @@ export function executeCommand(message, args, success, error) { export function setChannelAsRead(channelIdParam) { const channelId = channelIdParam || ChannelStore.getCurrentId(); - AsyncClient.viewChannel(); + viewChannel(channelId)(dispatch, getState); ChannelStore.resetCounts(channelId); ChannelStore.emitChange(); if (channelId === ChannelStore.getCurrentId()) { @@ -75,97 +98,52 @@ export function setChannelAsRead(channelIdParam) { } export function addUserToChannel(channelId, userId, success, error) { - Client.addChannelMember( - channelId, - userId, + addChannelMember(channelId, userId)(dispatch, getState).then( (data) => { - UserStore.removeProfileNotInChannel(channelId, userId); - const profile = UserStore.getProfile(userId); - if (profile) { - UserStore.saveProfileInChannel(channelId, profile); - UserStore.emitInChannelChange(); - } - UserStore.emitNotInChannelChange(); - - if (success) { + if (data && success) { success(data); - } - }, - (err) => { - AsyncClient.dispatchError(err, 'addChannelMember'); - - if (error) { - error(err); + } else if (data == null && error) { + const serverError = getState().requests.channels.addChannelMember.error; + error({id: serverError.server_error_id, ...serverError}); } } ); } export function removeUserFromChannel(channelId, userId, success, error) { - Client.removeChannelMember( - channelId, - userId, + removeChannelMember(channelId, userId)(dispatch, getState).then( (data) => { - UserStore.removeProfileInChannel(channelId, userId); - const profile = UserStore.getProfile(userId); - if (profile) { - UserStore.saveProfileNotInChannel(channelId, profile); - UserStore.emitNotInChannelChange(); - } - UserStore.emitInChannelChange(); - - ChannelStore.removeMemberInChannel(channelId, userId); - ChannelStore.emitChange(); - - if (success) { + if (data && success) { success(data); - } - }, - (err) => { - AsyncClient.dispatchError(err, 'removeChannelMember'); - - if (error) { - error(err); + } else if (data == null && error) { + const serverError = getState().requests.channels.removeChannelMember.error; + error({id: serverError.server_error_id, ...serverError}); } } ); } export function makeUserChannelAdmin(channelId, userId, success, error) { - Client.updateChannelMemberRoles( - channelId, - userId, - 'channel_user channel_admin', - () => { - getChannelMembersForUserIds(channelId, [userId]); - - if (success) { - success(); - } - }, - (err) => { - if (error) { - error(err); + updateChannelMemberRoles(channelId, userId, 'channel_user channel_admin')(dispatch, getState).then( + (data) => { + if (data && success) { + success(data); + } else if (data == null && error) { + const serverError = getState().requests.channels.updateChannelMember.error; + error({id: serverError.server_error_id, ...serverError}); } } ); } export function makeUserChannelMember(channelId, userId, success, error) { - Client.updateChannelMemberRoles( - channelId, - userId, - 'channel_user', - () => { - getChannelMembersForUserIds(channelId, [userId]); - - if (success) { - success(); - } - }, - (err) => { - if (error) { - error(err); + updateChannelMemberRoles(channelId, userId, 'channel_user')(dispatch, getState).then( + (data) => { + if (data && success) { + success(data); + } else if (data == null && error) { + const serverError = getState().requests.channels.updateChannelMember.error; + error({id: serverError.server_error_id, ...serverError}); } } ); @@ -193,37 +171,15 @@ export function openDirectChannelToUser(userId, success, error) { return; } - Client.createDirectChannel( - userId, + createDirectChannel(UserStore.getCurrentId(), userId)(dispatch, getState).then( (data) => { - Client.getChannel( - data.id, - (data2) => { - AppDispatcher.handleServerAction({ - type: ActionTypes.RECEIVED_CHANNEL, - channel: data2.channel, - member: data2.member - }); - - PreferenceStore.setPreference(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, userId, 'true'); - loadProfilesForSidebar(); - - AsyncClient.savePreference( - Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, - userId, - 'true' - ); - - if (success) { - success(data2.channel, false); - } - } - ); - }, - () => { - browserHistory.push(TeamStore.getCurrentTeamUrl() + '/channels/' + channelName); - if (error) { - error(); + loadProfilesForSidebar(); + if (data && success) { + success(data, false); + } else if (data == null && error) { + browserHistory.push(TeamStore.getCurrentTeamUrl() + '/channels/' + channelName); + const serverError = getState().requests.channels.createChannel.error; + error({id: serverError.server_error_id, ...serverError}); } } ); @@ -283,11 +239,11 @@ export function unmarkFavorite(channelId) { } export function loadChannelsForCurrentUser() { - AsyncClient.getChannels().then(() => { - AsyncClient.getMyChannelMembers().then(() => { + fetchMyChannelsAndMembers(TeamStore.getCurrentId())(dispatch, getState).then( + () => { loadDMsAndGMsForUnreads(); - }); - }); + } + ); } export function loadDMsAndGMsForUnreads() { @@ -309,214 +265,125 @@ export function loadDMsAndGMsForUnreads() { } export function joinChannel(channel, success, error) { - Client.joinChannel( - channel.id, - () => { - ChannelStore.removeMoreChannel(channel.id); - ChannelStore.storeChannel(channel); - - if (success) { - success(); - } - }, - () => { - if (error) { - error(); + joinChannelRedux(UserStore.getCurrentId(), null, channel.id)(dispatch, getState).then( + (data) => { + if (data && success) { + success(data); + } else if (data == null && error) { + const serverError = getState().requests.channels.joinChannel.error; + error({id: serverError.server_error_id, ...serverError}); } } ); } export function updateChannel(channel, success, error) { - Client.updateChannel( - channel, - () => { - AsyncClient.getChannel(channel.id); - - if (success) { - success(); - } - }, - (err) => { - if (error) { - error(err); + updateChannelRedux(channel)(dispatch, getState).then( + (data) => { + if (data && success) { + success(data); + } else if (data == null && error) { + const serverError = getState().requests.channels.updateChannel.error; + error({id: serverError.server_error_id, ...serverError}); } } ); } export function searchMoreChannels(term, success, error) { - Client.searchMoreChannels( - term, + searchChannels(TeamStore.getCurrentId(), term)(dispatch, getState).then( (data) => { - if (success) { + if (data && success) { success(data); - } - }, - (err) => { - if (error) { - error(err); + } else if (data == null && error) { + const serverError = getState().requests.channels.getChannels.error; + error({id: serverError.server_error_id, ...serverError}); } } ); } export function autocompleteChannels(term, success, error) { - Client.autocompleteChannels( - term, + searchChannels(TeamStore.getCurrentId(), term)(dispatch, getState).then( (data) => { - if (success) { + if (data && success) { success(data); - } - }, - (err) => { - AsyncClient.dispatchError(err, 'autocompleteChannels'); - - if (error) { - error(err); + } else if (data == null && error) { + const serverError = getState().requests.channels.getChannels.error; + error({id: serverError.server_error_id, ...serverError}); } } ); } export function updateChannelNotifyProps(data, options, success, error) { - Client.updateChannelNotifyProps(Object.assign({}, data, options), - () => { - const member = ChannelStore.getMyMember(data.channel_id); - member.notify_props = Object.assign(member.notify_props, options); - ChannelStore.storeMyChannelMember(member); - - if (success) { - success(); - } - }, - (err) => { - if (error) { - error(err); + updateChannelNotifyPropsRedux(data.user_id, data.channel_id, Object.assign({}, data, options))(dispatch, getState).then( + (result) => { + if (result && success) { + success(result); + } else if (result == null && error) { + const serverError = getState().requests.channels.updateChannelNotifyProps.error; + error({id: serverError.server_error_id, ...serverError}); } } ); } export function createChannel(channel, success, error) { - Client.createChannel( - channel, + createChannelRedux(channel)(dispatch, getState).then( (data) => { - const existing = ChannelStore.getChannelById(data.id); - if (existing) { - if (success) { - success({channel: existing}); - } - } else { - Client.getChannel( - data.id, - (data2) => { - AppDispatcher.handleServerAction({ - type: ActionTypes.RECEIVED_CHANNEL, - channel: data2.channel, - member: data2.channel - }); - - if (success) { - success(data2); - } - }, - (err) => { - AsyncClient.dispatchError(err, 'getChannel'); - - if (error) { - error(err); - } - } - ); - } - }, - (err) => { - if (error) { - error(err); - } else { - AsyncClient.dispatchError(err, 'createChannel'); + if (data && success) { + success(data); + } else if (data == null && error) { + const serverError = getState().requests.channels.createChannel.error; + error({id: serverError.server_error_id, ...serverError}); } } ); } -export function updateChannelPurpose(channelId, purposeValue, success, error) { - Client.updateChannelPurpose( - channelId, - purposeValue, - () => { - AsyncClient.getChannel(channelId); - - if (success) { - success(); - } - }, - (err) => { - if (error) { - error(err); +export function updateChannelPurpose(channelId, purpose, success, error) { + patchChannel(channelId, {purpose})(dispatch, getState).then( + (data) => { + if (data && success) { + success(data); + } else if (data == null && error) { + const serverError = getState().requests.channels.updateChannel.error; + error({id: serverError.server_error_id, ...serverError}); } } ); } export function updateChannelHeader(channelId, header, success, error) { - Client.updateChannelHeader( - channelId, - header, - (channelData) => { - AppDispatcher.handleServerAction({ - type: ActionTypes.RECEIVED_CHANNEL, - channel: channelData - }); - - if (success) { - success(channelData); - } - }, - (err) => { - if (error) { - error(err); + patchChannel(channelId, {header})(dispatch, getState).then( + (data) => { + if (data && success) { + success(data); + } else if (data == null && error) { + const serverError = getState().requests.channels.updateChannel.error; + error({id: serverError.server_error_id, ...serverError}); } } ); } export function getChannelMembersForUserIds(channelId, userIds, success, error) { - Client.getChannelMembersByIds( - channelId, - userIds, + getChannelMembersByIds(channelId, userIds)(dispatch, getState).then( (data) => { - const memberMap = {}; - for (let i = 0; i < data.length; i++) { - memberMap[data[i].user_id] = data[i]; - } - - AppDispatcher.handleServerAction({ - type: ActionTypes.RECEIVED_MEMBERS_IN_CHANNEL, - channel_id: channelId, - channel_members: memberMap - }); - - if (success) { + if (data && success) { success(data); - } - }, - (err) => { - AsyncClient.dispatchError(err, 'getChannelMembersByIds'); - - if (error) { - error(err); + } else if (data == null && error) { + const serverError = getState().requests.channels.members.error; + error({id: serverError.server_error_id, ...serverError}); } } ); } -export function leaveChannel(channelId, success, error) { - Client.leaveChannel(channelId, +export function leaveChannel(channelId, success) { + leaveChannelRedux(channelId)(dispatch, getState).then( () => { - loadChannelsForCurrentUser(); - if (ChannelUtils.isFavoriteChannelId(channelId)) { unmarkFavorite(channelId); } @@ -527,33 +394,19 @@ export function leaveChannel(channelId, success, error) { if (success) { success(); } - }, - (err) => { - AsyncClient.dispatchError(err, 'handleLeave'); - - if (error) { - error(err); - } } ); } export function deleteChannel(channelId, success, error) { - Client.deleteChannel( - channelId, - () => { - loadChannelsForCurrentUser(); - - if (success) { - success(); - } - }, - (err) => { - AsyncClient.dispatchError(err, 'handleDelete'); - - if (error) { - error(err); - } + deleteChannelRedux(channelId)(dispatch, getState).then( + (data) => { + if (data && success) { + success(data); + } else if (data == null && error) { + const serverError = getState().requests.channels.members.error; + error({id: serverError.server_error_id, ...serverError}); } - ); + } + ); } diff --git a/webapp/actions/global_actions.jsx b/webapp/actions/global_actions.jsx index 9709f5f80..1dd5d6952 100644 --- a/webapp/actions/global_actions.jsx +++ b/webapp/actions/global_actions.jsx @@ -35,8 +35,8 @@ import {browserHistory} from 'react-router/es6'; import store from 'stores/redux_store.jsx'; const dispatch = store.dispatch; const getState = store.getState; -import {ChannelTypes} from 'mattermost-redux/action_types'; import {removeUserFromTeam} from 'mattermost-redux/actions/teams'; +import {viewChannel, getChannelStats, getChannelMember} from 'mattermost-redux/actions/channels'; export function emitChannelClickEvent(channel) { function userVisitedFakeChannel(chan, success, fail) { @@ -53,12 +53,12 @@ export function emitChannelClickEvent(channel) { } function switchToChannel(chan) { const channelMember = ChannelStore.getMyMember(chan.id); - const getMyChannelMemberPromise = AsyncClient.getChannelMember(chan.id, UserStore.getCurrentId()); + const getMyChannelMemberPromise = getChannelMember(chan.id, UserStore.getCurrentId())(dispatch, getState); const oldChannelId = ChannelStore.getCurrentId(); getMyChannelMemberPromise.then(() => { - AsyncClient.getChannelStats(chan.id, true); - AsyncClient.viewChannel(chan.id, oldChannelId); + getChannelStats(chan.id)(dispatch, getState); + viewChannel(chan.id)(dispatch, getState); loadPosts(chan.id); }); @@ -83,11 +83,6 @@ export function emitChannelClickEvent(channel) { channelMember, prev: oldChannelId }); - - dispatch({ - type: ChannelTypes.SELECT_CHANNEL, - data: chan.id - }, getState); } if (channel.fake) { @@ -113,7 +108,7 @@ export function doFocusPost(channelId, postId, data) { post_list: data }); loadChannelsForCurrentUser(); - AsyncClient.getChannelStats(channelId); + getChannelStats(channelId)(dispatch, getState); loadPostsBefore(postId, 0, Constants.POST_FOCUS_CONTEXT_RADIUS, true); loadPostsAfter(postId, 0, Constants.POST_FOCUS_CONTEXT_RADIUS, true); } diff --git a/webapp/actions/post_actions.jsx b/webapp/actions/post_actions.jsx index 36abfc2be..4b7ade862 100644 --- a/webapp/actions/post_actions.jsx +++ b/webapp/actions/post_actions.jsx @@ -25,6 +25,7 @@ import store from 'stores/redux_store.jsx'; const dispatch = store.dispatch; const getState = store.getState; import {getProfilesByIds} from 'mattermost-redux/actions/users'; +import {getChannelMember} from 'mattermost-redux/actions/channels'; export function handleNewPost(post, msg) { let websocketMessageProps = {}; @@ -40,7 +41,7 @@ export function handleNewPost(post, msg) { Client.setTeamId(msg.data.team_id); } - AsyncClient.getChannelMember(post.channel_id, UserStore.getCurrentId()).then(() => completePostReceive(post, websocketMessageProps)); + getChannelMember(post.channel_id, UserStore.getCurrentId())(dispatch, getState).then(() => completePostReceive(post, websocketMessageProps)); } if (msg && msg.data) { diff --git a/webapp/actions/team_actions.jsx b/webapp/actions/team_actions.jsx index 83fbfa0a9..44b554ded 100644 --- a/webapp/actions/team_actions.jsx +++ b/webapp/actions/team_actions.jsx @@ -2,8 +2,8 @@ // See License.txt for license information. import TeamStore from 'stores/team_store.jsx'; +import ChannelStore from 'stores/channel_store.jsx'; -import * as AsyncClient from 'utils/async_client.jsx'; import Client from 'client/web_client.jsx'; import {browserHistory} from 'react-router/es6'; @@ -14,6 +14,7 @@ const dispatch = store.dispatch; const getState = store.getState; import {getUser} from 'mattermost-redux/actions/users'; +import {viewChannel} from 'mattermost-redux/actions/channels'; import { createTeam as createTeamRedux, updateTeam as updateTeamRedux, @@ -165,7 +166,7 @@ export function inviteMembers(data, success, error) { } export function switchTeams(url) { - AsyncClient.viewChannel(); + viewChannel(ChannelStore.getCurrentId())(dispatch, getState); browserHistory.push(url); } diff --git a/webapp/actions/websocket_actions.jsx b/webapp/actions/websocket_actions.jsx index 57f78f95f..c6de42647 100644 --- a/webapp/actions/websocket_actions.jsx +++ b/webapp/actions/websocket_actions.jsx @@ -30,6 +30,13 @@ import {ActionTypes, Constants, Preferences, SocketEvents, UserStatuses} from 'u import {browserHistory} from 'react-router/es6'; +// Redux actions +import store from 'stores/redux_store.jsx'; +const dispatch = store.dispatch; +const getState = store.getState; +import {viewChannel, getChannelAndMyMember, getChannelStats} from 'mattermost-redux/actions/channels'; +import {ChannelTypes} from 'mattermost-redux/action_types'; + const MAX_WEBSOCKET_FAILS = 7; export function initialize() { @@ -241,7 +248,7 @@ function handlePostEditEvent(msg) { // Update channel state if (ChannelStore.getCurrentId() === msg.broadcast.channel_id) { if (window.isActive) { - AsyncClient.viewChannel(); + viewChannel(ChannelStore.getCurrentId())(dispatch, getState); } } } @@ -297,18 +304,18 @@ function handleUpdateTeamEvent(msg) { } function handleDirectAddedEvent(msg) { - AsyncClient.getChannel(msg.broadcast.channel_id); + getChannelAndMyMember(msg.broadcast.channel_id)(dispatch, getState); PreferenceStore.setPreference(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, msg.data.teammate_id, 'true'); loadProfilesForSidebar(); } function handleUserAddedEvent(msg) { if (ChannelStore.getCurrentId() === msg.broadcast.channel_id) { - AsyncClient.getChannelStats(); + getChannelStats(ChannelStore.getCurrentId())(dispatch, getState); } if (TeamStore.getCurrentId() === msg.data.team_id && UserStore.getCurrentId() === msg.data.user_id) { - AsyncClient.getChannel(msg.broadcast.channel_id); + getChannelAndMyMember(msg.broadcast.channel_id)(dispatch, getState); } } @@ -327,7 +334,7 @@ function handleUserRemovedEvent(msg) { $('#removed_from_channel').modal('show'); } } else if (ChannelStore.getCurrentId() === msg.broadcast.channel_id) { - AsyncClient.getChannelStats(); + getChannelStats(ChannelStore.getCurrentId())(dispatch, getState); } } @@ -343,7 +350,7 @@ function handleChannelCreatedEvent(msg) { const teamId = msg.data.team_id; if (TeamStore.getCurrentId() === teamId && !ChannelStore.getChannelById(channelId)) { - AsyncClient.getChannel(channelId); + getChannelAndMyMember(channelId)(dispatch, getState); } } @@ -352,6 +359,7 @@ function handleChannelDeletedEvent(msg) { const teamUrl = TeamStore.getCurrentTeamRelativeUrl(); browserHistory.push(teamUrl + '/channels/' + Constants.DEFAULT_CHANNEL); } + dispatch({type: ChannelTypes.RECEIVED_CHANNEL_DELETED, data: {id: msg.data.channel_id, team_id: msg.broadcast.team_id}}, getState); loadChannelsForCurrentUser(); } diff --git a/webapp/components/channel_members_dropdown.jsx b/webapp/components/channel_members_dropdown.jsx deleted file mode 100644 index e44108f3c..000000000 --- a/webapp/components/channel_members_dropdown.jsx +++ /dev/null @@ -1,264 +0,0 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import ChannelStore from 'stores/channel_store.jsx'; -import TeamStore from 'stores/team_store.jsx'; -import UserStore from 'stores/user_store.jsx'; - -import {removeUserFromChannel, makeUserChannelAdmin, makeUserChannelMember} from 'actions/channel_actions.jsx'; - -import * as AsyncClient from 'utils/async_client.jsx'; -import * as Utils from 'utils/utils.jsx'; -import {canManageMembers} from 'utils/channel_utils.jsx'; -import {Constants} from 'utils/constants.jsx'; - -import React from 'react'; -import {FormattedMessage} from 'react-intl'; - -export default class ChannelMembersDropdown extends React.Component { - constructor(props) { - super(props); - - this.handleRemoveFromChannel = this.handleRemoveFromChannel.bind(this); - this.handleMakeChannelMember = this.handleMakeChannelMember.bind(this); - this.handleMakeChannelAdmin = this.handleMakeChannelAdmin.bind(this); - - this.state = { - serverError: null, - user: null, - role: null - }; - } - - handleRemoveFromChannel() { - removeUserFromChannel( - this.props.channel.id, - this.props.user.id, - () => { - AsyncClient.getChannelStats(this.props.channel.id); - }, - (err) => { - this.setState({serverError: err.message}); - } - ); - } - - handleMakeChannelMember() { - makeUserChannelMember( - this.props.channel.id, - this.props.user.id, - () => { - AsyncClient.getChannelStats(this.props.channel.id); - }, - (err) => { - this.setState({serverError: err.message}); - } - ); - } - - handleMakeChannelAdmin() { - makeUserChannelAdmin( - this.props.channel.id, - this.props.user.id, - () => { - AsyncClient.getChannelStats(this.props.channel.id); - }, - (err) => { - this.setState({serverError: err.message}); - } - ); - } - - // Checks if the user this menu is for is a channel admin or not. - isChannelAdmin() { - if (Utils.isChannelAdmin(this.props.channelMember.roles)) { - return true; - } - - return false; - } - - // Checks if the current user has the power to change the roles of this member. - canChangeMemberRoles() { - if (UserStore.isSystemAdminForCurrentUser()) { - return true; - } else if (TeamStore.isTeamAdminForCurrentTeam()) { - return true; - } else if (ChannelStore.isChannelAdminForCurrentChannel()) { - return true; - } - - return false; - } - - // Checks if the current user has the power to remove this member from the channel. - canRemoveMember() { - return canManageMembers(this.props.channel, UserStore.isSystemAdminForCurrentUser(), TeamStore.isTeamAdminForCurrentTeam(), ChannelStore.isChannelAdminForCurrentChannel()); - } - - render() { - let serverError = null; - if (this.state.serverError) { - serverError = ( -
- -
- ); - } - - if (this.props.user.id === UserStore.getCurrentId()) { - return null; - } - - if (this.canChangeMemberRoles()) { - let role = ( - - ); - - if (this.isChannelAdmin()) { - role = ( - - ); - } - - let removeFromChannel = null; - if (this.canRemoveMember() && this.props.channel.name !== Constants.DEFAULT_CHANNEL) { - removeFromChannel = ( -
  • - - - -
  • - ); - } - - let makeChannelMember = null; - if (this.isChannelAdmin()) { - makeChannelMember = ( -
  • - - - -
  • - ); - } - - let makeChannelAdmin = null; - if (!this.isChannelAdmin()) { - makeChannelAdmin = ( -
  • - - - -
  • - ); - } - - return ( -
    - - {role} - - -
      - {makeChannelMember} - {makeChannelAdmin} - {removeFromChannel} -
    - {serverError} -
    - ); - } else if (this.canRemoveMember() && this.props.channel.name !== Constants.DEFAULT_CHANNEL) { - return ( - - ); - } else if (this.isChannelAdmin()) { - if (this.props.channel.name === Constants.DEFAULT_CHANNEL) { - return ( -
    - ); - } - - return ( -
    - -
    - ); - } - - if (this.props.channel.name === Constants.DEFAULT_CHANNEL) { - return ( -
    - ); - } - - return ( -
    - -
    - ); - } -} - -ChannelMembersDropdown.propTypes = { - channel: React.PropTypes.object.isRequired, - user: React.PropTypes.object.isRequired, - teamMember: React.PropTypes.object.isRequired, - channelMember: React.PropTypes.object.isRequired -}; diff --git a/webapp/components/channel_members_dropdown/channel_members_dropdown.jsx b/webapp/components/channel_members_dropdown/channel_members_dropdown.jsx new file mode 100644 index 000000000..f7d42ce9b --- /dev/null +++ b/webapp/components/channel_members_dropdown/channel_members_dropdown.jsx @@ -0,0 +1,266 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import ChannelStore from 'stores/channel_store.jsx'; +import TeamStore from 'stores/team_store.jsx'; +import UserStore from 'stores/user_store.jsx'; + +import {removeUserFromChannel, makeUserChannelAdmin, makeUserChannelMember} from 'actions/channel_actions.jsx'; + +import * as Utils from 'utils/utils.jsx'; +import {canManageMembers} from 'utils/channel_utils.jsx'; +import {Constants} from 'utils/constants.jsx'; + +import React from 'react'; +import {FormattedMessage} from 'react-intl'; + +export default class ChannelMembersDropdown extends React.Component { + static propTypes = { + channel: React.PropTypes.object.isRequired, + user: React.PropTypes.object.isRequired, + teamMember: React.PropTypes.object.isRequired, + channelMember: React.PropTypes.object.isRequired, + actions: React.PropTypes.shape({ + getChannelStats: React.PropTypes.func.isRequired + }).isRequired + } + + constructor(props) { + super(props); + + this.handleRemoveFromChannel = this.handleRemoveFromChannel.bind(this); + this.handleMakeChannelMember = this.handleMakeChannelMember.bind(this); + this.handleMakeChannelAdmin = this.handleMakeChannelAdmin.bind(this); + + this.state = { + serverError: null, + user: null, + role: null + }; + } + + handleRemoveFromChannel() { + removeUserFromChannel( + this.props.channel.id, + this.props.user.id, + () => { + this.props.actions.getChannelStats(this.props.channel.id); + }, + (err) => { + this.setState({serverError: err.message}); + } + ); + } + + handleMakeChannelMember() { + makeUserChannelMember( + this.props.channel.id, + this.props.user.id, + () => { + this.props.actions.getChannelStats(this.props.channel.id); + }, + (err) => { + this.setState({serverError: err.message}); + } + ); + } + + handleMakeChannelAdmin() { + makeUserChannelAdmin( + this.props.channel.id, + this.props.user.id, + () => { + this.props.actions.getChannelStats(this.props.channel.id); + }, + (err) => { + this.setState({serverError: err.message}); + } + ); + } + + // Checks if the user this menu is for is a channel admin or not. + isChannelAdmin() { + if (Utils.isChannelAdmin(this.props.channelMember.roles)) { + return true; + } + + return false; + } + + // Checks if the current user has the power to change the roles of this member. + canChangeMemberRoles() { + if (UserStore.isSystemAdminForCurrentUser()) { + return true; + } else if (TeamStore.isTeamAdminForCurrentTeam()) { + return true; + } else if (ChannelStore.isChannelAdminForCurrentChannel()) { + return true; + } + + return false; + } + + // Checks if the current user has the power to remove this member from the channel. + canRemoveMember() { + return canManageMembers(this.props.channel, UserStore.isSystemAdminForCurrentUser(), TeamStore.isTeamAdminForCurrentTeam(), ChannelStore.isChannelAdminForCurrentChannel()); + } + + render() { + let serverError = null; + if (this.state.serverError) { + serverError = ( +
    + +
    + ); + } + + if (this.props.user.id === UserStore.getCurrentId()) { + return null; + } + + if (this.canChangeMemberRoles()) { + let role = ( + + ); + + if (this.isChannelAdmin()) { + role = ( + + ); + } + + let removeFromChannel = null; + if (this.canRemoveMember() && this.props.channel.name !== Constants.DEFAULT_CHANNEL) { + removeFromChannel = ( +
  • + + + +
  • + ); + } + + let makeChannelMember = null; + if (this.isChannelAdmin()) { + makeChannelMember = ( +
  • + + + +
  • + ); + } + + let makeChannelAdmin = null; + if (!this.isChannelAdmin()) { + makeChannelAdmin = ( +
  • + + + +
  • + ); + } + + return ( +
    + + {role} + + +
      + {makeChannelMember} + {makeChannelAdmin} + {removeFromChannel} +
    + {serverError} +
    + ); + } else if (this.canRemoveMember() && this.props.channel.name !== Constants.DEFAULT_CHANNEL) { + return ( + + ); + } else if (this.isChannelAdmin()) { + if (this.props.channel.name === Constants.DEFAULT_CHANNEL) { + return ( +
    + ); + } + + return ( +
    + +
    + ); + } + + if (this.props.channel.name === Constants.DEFAULT_CHANNEL) { + return ( +
    + ); + } + + return ( +
    + +
    + ); + } +} diff --git a/webapp/components/channel_members_dropdown/index.js b/webapp/components/channel_members_dropdown/index.js new file mode 100644 index 000000000..11a626e46 --- /dev/null +++ b/webapp/components/channel_members_dropdown/index.js @@ -0,0 +1,24 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import {connect} from 'react-redux'; +import {bindActionCreators} from 'redux'; +import {getChannelStats} from 'mattermost-redux/actions/channels'; + +import ChannelMembersDropdown from './channel_members_dropdown.jsx'; + +function mapStateToProps(state, ownProps) { + return { + ...ownProps + }; +} + +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators({ + getChannelStats + }, dispatch) + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(ChannelMembersDropdown); diff --git a/webapp/components/channel_members_modal.jsx b/webapp/components/channel_members_modal.jsx index dab51a1bd..492bc8809 100644 --- a/webapp/components/channel_members_modal.jsx +++ b/webapp/components/channel_members_modal.jsx @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import MemberListChannel from './member_list_channel.jsx'; +import MemberListChannel from 'components/member_list_channel'; import TeamStore from 'stores/team_store.jsx'; import UserStore from 'stores/user_store.jsx'; diff --git a/webapp/components/channel_view.jsx b/webapp/components/channel_view.jsx index d6a931e0b..e9fb4cf94 100644 --- a/webapp/components/channel_view.jsx +++ b/webapp/components/channel_view.jsx @@ -8,7 +8,7 @@ import * as UserAgent from 'utils/user_agent.jsx'; import ChannelHeader from 'components/channel_header.jsx'; import FileUploadOverlay from 'components/file_upload_overlay.jsx'; import CreatePost from 'components/create_post.jsx'; -import PostViewCache from 'components/post_view/post_view_cache.jsx'; +import PostViewCache from 'components/post_view'; import ChannelStore from 'stores/channel_store.jsx'; diff --git a/webapp/components/member_list_channel.jsx b/webapp/components/member_list_channel.jsx deleted file mode 100644 index df000c132..000000000 --- a/webapp/components/member_list_channel.jsx +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import ChannelMembersDropdown from 'components/channel_members_dropdown.jsx'; -import SearchableUserList from 'components/searchable_user_list/searchable_user_list_container.jsx'; - -import ChannelStore from 'stores/channel_store.jsx'; -import UserStore from 'stores/user_store.jsx'; -import TeamStore from 'stores/team_store.jsx'; - -import {searchUsers, loadProfilesAndTeamMembersAndChannelMembers, loadTeamMembersAndChannelMembersForProfilesList} from 'actions/user_actions.jsx'; -import {getChannelStats} from 'utils/async_client.jsx'; - -import Constants from 'utils/constants.jsx'; - -import * as UserAgent from 'utils/user_agent.jsx'; - -import React from 'react'; - -import store from 'stores/redux_store.jsx'; -import {searchProfilesInCurrentChannel} from 'mattermost-redux/selectors/entities/users'; - -const USERS_PER_PAGE = 50; - -export default class MemberListChannel extends React.Component { - constructor(props) { - super(props); - - this.onChange = this.onChange.bind(this); - this.onStatsChange = this.onStatsChange.bind(this); - this.search = this.search.bind(this); - this.loadComplete = this.loadComplete.bind(this); - - this.searchTimeoutId = 0; - this.term = ''; - - const stats = ChannelStore.getCurrentStats(); - - this.state = { - users: UserStore.getProfileListInChannel(), - teamMembers: Object.assign({}, TeamStore.getMembersInTeam()), - channelMembers: Object.assign({}, ChannelStore.getMembersInChannel()), - total: stats.member_count, - loading: true - }; - } - - componentDidMount() { - UserStore.addInTeamChangeListener(this.onChange); - UserStore.addStatusesChangeListener(this.onChange); - TeamStore.addChangeListener(this.onChange); - ChannelStore.addChangeListener(this.onChange); - ChannelStore.addStatsChangeListener(this.onStatsChange); - - loadProfilesAndTeamMembersAndChannelMembers(0, Constants.PROFILE_CHUNK_SIZE, TeamStore.getCurrentId(), ChannelStore.getCurrentId(), this.loadComplete); - getChannelStats(ChannelStore.getCurrentId()); - } - - componentWillUnmount() { - UserStore.removeInTeamChangeListener(this.onChange); - UserStore.removeStatusesChangeListener(this.onChange); - TeamStore.removeChangeListener(this.onChange); - ChannelStore.removeChangeListener(this.onChange); - ChannelStore.removeStatsChangeListener(this.onStatsChange); - } - - loadComplete() { - this.setState({loading: false}); - } - - onChange() { - let users; - if (this.term) { - users = searchProfilesInCurrentChannel(store.getState(), this.term); - } else { - users = UserStore.getProfileListInChannel(); - } - - this.setState({ - users, - teamMembers: Object.assign({}, TeamStore.getMembersInTeam()), - channelMembers: Object.assign({}, ChannelStore.getMembersInChannel()) - }); - } - - onStatsChange() { - const stats = ChannelStore.getCurrentStats(); - this.setState({total: stats.member_count}); - } - - nextPage(page) { - loadProfilesAndTeamMembersAndChannelMembers(page + 1, USERS_PER_PAGE); - } - - search(term) { - clearTimeout(this.searchTimeoutId); - this.term = term; - - if (term === '') { - this.setState({loading: false}); - this.searchTimeoutId = ''; - this.onChange(); - return; - } - - const searchTimeoutId = setTimeout( - () => { - searchUsers(term, TeamStore.getCurrentId(), {}, - (users) => { - if (searchTimeoutId !== this.searchTimeoutId) { - return; - } - - this.setState({loading: true}); - - loadTeamMembersAndChannelMembersForProfilesList(users, TeamStore.getCurrentId(), ChannelStore.getCurrentId(), this.loadComplete); - } - ); - }, - Constants.SEARCH_TIMEOUT_MILLISECONDS - ); - - this.searchTimeoutId = searchTimeoutId; - } - - render() { - const teamMembers = this.state.teamMembers; - const channelMembers = this.state.channelMembers; - const users = this.state.users; - const actionUserProps = {}; - - let usersToDisplay; - if (this.state.loading) { - usersToDisplay = null; - } else { - usersToDisplay = []; - - for (let i = 0; i < users.length; i++) { - const user = users[i]; - - if (teamMembers[user.id] && channelMembers[user.id]) { - usersToDisplay.push(user); - actionUserProps[user.id] = { - channel: this.props.channel, - teamMember: teamMembers[user.id], - channelMember: channelMembers[user.id] - }; - } - } - } - - return ( - - ); - } -} - -MemberListChannel.propTypes = { - channel: React.PropTypes.object.isRequired -}; diff --git a/webapp/components/member_list_channel/index.js b/webapp/components/member_list_channel/index.js new file mode 100644 index 000000000..c0f70709e --- /dev/null +++ b/webapp/components/member_list_channel/index.js @@ -0,0 +1,24 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import {connect} from 'react-redux'; +import {bindActionCreators} from 'redux'; +import {getChannelStats} from 'mattermost-redux/actions/channels'; + +import MemberListChannel from './member_list_channel.jsx'; + +function mapStateToProps(state, ownProps) { + return { + ...ownProps + }; +} + +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators({ + getChannelStats + }, dispatch) + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(MemberListChannel); diff --git a/webapp/components/member_list_channel/member_list_channel.jsx b/webapp/components/member_list_channel/member_list_channel.jsx new file mode 100644 index 000000000..af2304433 --- /dev/null +++ b/webapp/components/member_list_channel/member_list_channel.jsx @@ -0,0 +1,171 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import ChannelMembersDropdown from 'components/channel_members_dropdown'; +import SearchableUserList from 'components/searchable_user_list/searchable_user_list_container.jsx'; + +import ChannelStore from 'stores/channel_store.jsx'; +import UserStore from 'stores/user_store.jsx'; +import TeamStore from 'stores/team_store.jsx'; + +import {searchUsers, loadProfilesAndTeamMembersAndChannelMembers, loadTeamMembersAndChannelMembersForProfilesList} from 'actions/user_actions.jsx'; + +import Constants from 'utils/constants.jsx'; + +import * as UserAgent from 'utils/user_agent.jsx'; + +import React from 'react'; + +import store from 'stores/redux_store.jsx'; +import {searchProfilesInCurrentChannel} from 'mattermost-redux/selectors/entities/users'; + +const USERS_PER_PAGE = 50; + +export default class MemberListChannel extends React.Component { + static propTypes = { + channel: React.PropTypes.object.isRequired, + actions: React.PropTypes.shape({ + getChannelStats: React.PropTypes.func.isRequired + }).isRequired + } + + constructor(props) { + super(props); + + this.onChange = this.onChange.bind(this); + this.onStatsChange = this.onStatsChange.bind(this); + this.search = this.search.bind(this); + this.loadComplete = this.loadComplete.bind(this); + + this.searchTimeoutId = 0; + this.term = ''; + + const stats = ChannelStore.getCurrentStats(); + + this.state = { + users: UserStore.getProfileListInChannel(), + teamMembers: Object.assign({}, TeamStore.getMembersInTeam()), + channelMembers: Object.assign({}, ChannelStore.getMembersInChannel()), + total: stats.member_count, + loading: true + }; + } + + componentDidMount() { + UserStore.addInTeamChangeListener(this.onChange); + UserStore.addStatusesChangeListener(this.onChange); + TeamStore.addChangeListener(this.onChange); + ChannelStore.addChangeListener(this.onChange); + ChannelStore.addStatsChangeListener(this.onStatsChange); + + loadProfilesAndTeamMembersAndChannelMembers(0, Constants.PROFILE_CHUNK_SIZE, TeamStore.getCurrentId(), ChannelStore.getCurrentId(), this.loadComplete); + this.props.actions.getChannelStats(ChannelStore.getCurrentId()); + } + + componentWillUnmount() { + UserStore.removeInTeamChangeListener(this.onChange); + UserStore.removeStatusesChangeListener(this.onChange); + TeamStore.removeChangeListener(this.onChange); + ChannelStore.removeChangeListener(this.onChange); + ChannelStore.removeStatsChangeListener(this.onStatsChange); + } + + loadComplete() { + this.setState({loading: false}); + } + + onChange() { + let users; + if (this.term) { + users = searchProfilesInCurrentChannel(store.getState(), this.term); + } else { + users = UserStore.getProfileListInChannel(); + } + + this.setState({ + users, + teamMembers: Object.assign({}, TeamStore.getMembersInTeam()), + channelMembers: Object.assign({}, ChannelStore.getMembersInChannel()) + }); + } + + onStatsChange() { + const stats = ChannelStore.getCurrentStats(); + this.setState({total: stats.member_count}); + } + + nextPage(page) { + loadProfilesAndTeamMembersAndChannelMembers(page + 1, USERS_PER_PAGE); + } + + search(term) { + clearTimeout(this.searchTimeoutId); + this.term = term; + + if (term === '') { + this.setState({loading: false}); + this.searchTimeoutId = ''; + this.onChange(); + return; + } + + const searchTimeoutId = setTimeout( + () => { + searchUsers(term, TeamStore.getCurrentId(), {}, + (users) => { + if (searchTimeoutId !== this.searchTimeoutId) { + return; + } + + this.setState({loading: true}); + + loadTeamMembersAndChannelMembersForProfilesList(users, TeamStore.getCurrentId(), ChannelStore.getCurrentId(), this.loadComplete); + } + ); + }, + Constants.SEARCH_TIMEOUT_MILLISECONDS + ); + + this.searchTimeoutId = searchTimeoutId; + } + + render() { + const teamMembers = this.state.teamMembers; + const channelMembers = this.state.channelMembers; + const users = this.state.users; + const actionUserProps = {}; + + let usersToDisplay; + if (this.state.loading) { + usersToDisplay = null; + } else { + usersToDisplay = []; + + for (let i = 0; i < users.length; i++) { + const user = users[i]; + + if (teamMembers[user.id] && channelMembers[user.id]) { + usersToDisplay.push(user); + actionUserProps[user.id] = { + channel: this.props.channel, + teamMember: teamMembers[user.id], + channelMember: channelMembers[user.id] + }; + } + } + } + + return ( + + ); + } +} diff --git a/webapp/components/more_channels.jsx b/webapp/components/more_channels.jsx deleted file mode 100644 index 02a0628ba..000000000 --- a/webapp/components/more_channels.jsx +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import SearchableChannelList from './searchable_channel_list.jsx'; - -import ChannelStore from 'stores/channel_store.jsx'; -import UserStore from 'stores/user_store.jsx'; -import TeamStore from 'stores/team_store.jsx'; - -import Constants from 'utils/constants.jsx'; -import * as AsyncClient from 'utils/async_client.jsx'; -import {joinChannel, searchMoreChannels} from 'actions/channel_actions.jsx'; -import {showCreateOption} from 'utils/channel_utils.jsx'; - -import React from 'react'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; -import {Modal} from 'react-bootstrap'; -import {FormattedMessage} from 'react-intl'; -import {browserHistory} from 'react-router/es6'; - -const CHANNELS_CHUNK_SIZE = 50; -const CHANNELS_PER_PAGE = 50; -const SEARCH_TIMEOUT_MILLISECONDS = 100; - -export default class MoreChannels extends React.Component { - constructor(props) { - super(props); - - this.onChange = this.onChange.bind(this); - this.handleJoin = this.handleJoin.bind(this); - this.handleHide = this.handleHide.bind(this); - this.handleExit = this.handleExit.bind(this); - this.nextPage = this.nextPage.bind(this); - this.search = this.search.bind(this); - - this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this); - - this.searchTimeoutId = 0; - - this.state = { - show: true, - search: false, - channels: null, - serverError: null - }; - } - - componentDidMount() { - ChannelStore.addChangeListener(this.onChange); - AsyncClient.getMoreChannelsPage(0, CHANNELS_CHUNK_SIZE * 2); - } - - componentWillUnmount() { - ChannelStore.removeChangeListener(this.onChange); - } - - handleHide() { - this.setState({show: false}); - } - - handleExit() { - if (this.props.onModalDismissed) { - this.props.onModalDismissed(); - } - } - - onChange(force) { - if (this.state.search && !force) { - return; - } - - this.setState({ - channels: ChannelStore.getMoreChannelsList(), - serverError: null - }); - } - - nextPage(page) { - AsyncClient.getMoreChannelsPage((page + 1) * CHANNELS_PER_PAGE, CHANNELS_PER_PAGE); - } - - handleJoin(channel, done) { - joinChannel( - channel, - () => { - browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/channels/' + channel.name); - if (done) { - done(); - } - - this.handleHide(); - }, - (err) => { - this.setState({serverError: err.message}); - if (done) { - done(); - } - } - ); - } - - search(term) { - clearTimeout(this.searchTimeoutId); - - if (term === '') { - this.onChange(true); - this.setState({search: false}); - this.searchTimeoutId = ''; - return; - } - - const searchTimeoutId = setTimeout( - () => { - searchMoreChannels( - term, - (channels) => { - if (searchTimeoutId !== this.searchTimeoutId) { - return; - } - this.setState({search: true, channels}); - } - ); - }, - SEARCH_TIMEOUT_MILLISECONDS - ); - - this.searchTimeoutId = searchTimeoutId; - } - - render() { - let serverError; - if (this.state.serverError) { - serverError =
    ; - } - - let createNewChannelButton = ( - - ); - - let createChannelHelpText = ( -

    - -

    - ); - - const isAdmin = TeamStore.isTeamAdminForCurrentTeam() || UserStore.isSystemAdminForCurrentUser(); - const isSystemAdmin = UserStore.isSystemAdminForCurrentUser(); - - if (!showCreateOption(Constants.OPEN_CHANNEL, isAdmin, isSystemAdmin)) { - createNewChannelButton = null; - createChannelHelpText = null; - } - - return ( - - - - - - {createNewChannelButton} - - - - {serverError} - - - ); - } -} - -MoreChannels.propTypes = { - onModalDismissed: React.PropTypes.func, - handleNewChannel: React.PropTypes.func -}; diff --git a/webapp/components/more_channels/index.js b/webapp/components/more_channels/index.js new file mode 100644 index 000000000..b3ce839ef --- /dev/null +++ b/webapp/components/more_channels/index.js @@ -0,0 +1,24 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import {connect} from 'react-redux'; +import {bindActionCreators} from 'redux'; +import {getChannels} from 'mattermost-redux/actions/channels'; + +import MoreChannels from './more_channels.jsx'; + +function mapStateToProps(state, ownProps) { + return { + ...ownProps + }; +} + +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators({ + getChannels + }, dispatch) + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(MoreChannels); diff --git a/webapp/components/more_channels/more_channels.jsx b/webapp/components/more_channels/more_channels.jsx new file mode 100644 index 000000000..3643d916b --- /dev/null +++ b/webapp/components/more_channels/more_channels.jsx @@ -0,0 +1,203 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import SearchableChannelList from 'components/searchable_channel_list.jsx'; + +import ChannelStore from 'stores/channel_store.jsx'; +import UserStore from 'stores/user_store.jsx'; +import TeamStore from 'stores/team_store.jsx'; + +import Constants from 'utils/constants.jsx'; +import {joinChannel, searchMoreChannels} from 'actions/channel_actions.jsx'; +import {showCreateOption} from 'utils/channel_utils.jsx'; + +import React from 'react'; +import PureRenderMixin from 'react-addons-pure-render-mixin'; +import {Modal} from 'react-bootstrap'; +import {FormattedMessage} from 'react-intl'; +import {browserHistory} from 'react-router/es6'; + +const CHANNELS_CHUNK_SIZE = 50; +const CHANNELS_PER_PAGE = 50; +const SEARCH_TIMEOUT_MILLISECONDS = 100; + +export default class MoreChannels extends React.Component { + static propTypes = { + onModalDismissed: React.PropTypes.func, + handleNewChannel: React.PropTypes.func, + actions: React.PropTypes.shape({ + getChannels: React.PropTypes.func.isRequired + }).isRequired + } + + constructor(props) { + super(props); + + this.onChange = this.onChange.bind(this); + this.handleJoin = this.handleJoin.bind(this); + this.handleHide = this.handleHide.bind(this); + this.handleExit = this.handleExit.bind(this); + this.nextPage = this.nextPage.bind(this); + this.search = this.search.bind(this); + + this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this); + + this.searchTimeoutId = 0; + + this.state = { + show: true, + search: false, + channels: null, + serverError: null + }; + } + + componentDidMount() { + ChannelStore.addChangeListener(this.onChange); + this.props.actions.getChannels(TeamStore.getCurrentId(), 0, CHANNELS_CHUNK_SIZE * 2); + } + + componentWillUnmount() { + ChannelStore.removeChangeListener(this.onChange); + } + + handleHide() { + this.setState({show: false}); + } + + handleExit() { + if (this.props.onModalDismissed) { + this.props.onModalDismissed(); + } + } + + onChange(force) { + if (this.state.search && !force) { + return; + } + + this.setState({ + channels: ChannelStore.getMoreChannelsList(), + serverError: null + }); + } + + nextPage(page) { + this.props.actions.getChannels(TeamStore.getCurrentId(), (page + 1) * CHANNELS_PER_PAGE, CHANNELS_PER_PAGE); + } + + handleJoin(channel, done) { + joinChannel( + channel, + () => { + browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/channels/' + channel.name); + if (done) { + done(); + } + + this.handleHide(); + }, + (err) => { + this.setState({serverError: err.message}); + if (done) { + done(); + } + } + ); + } + + search(term) { + clearTimeout(this.searchTimeoutId); + + if (term === '') { + this.onChange(true); + this.setState({search: false}); + this.searchTimeoutId = ''; + return; + } + + const searchTimeoutId = setTimeout( + () => { + searchMoreChannels( + term, + (channels) => { + if (searchTimeoutId !== this.searchTimeoutId) { + return; + } + this.setState({search: true, channels}); + } + ); + }, + SEARCH_TIMEOUT_MILLISECONDS + ); + + this.searchTimeoutId = searchTimeoutId; + } + + render() { + let serverError; + if (this.state.serverError) { + serverError =
    ; + } + + let createNewChannelButton = ( + + ); + + let createChannelHelpText = ( +

    + +

    + ); + + const isAdmin = TeamStore.isTeamAdminForCurrentTeam() || UserStore.isSystemAdminForCurrentUser(); + const isSystemAdmin = UserStore.isSystemAdminForCurrentUser(); + + if (!showCreateOption(Constants.OPEN_CHANNEL, isAdmin, isSystemAdmin)) { + createNewChannelButton = null; + createChannelHelpText = null; + } + + return ( + + + + + + {createNewChannelButton} + + + + {serverError} + + + ); + } +} diff --git a/webapp/components/needs_team.jsx b/webapp/components/needs_team.jsx deleted file mode 100644 index 42f05c1a9..000000000 --- a/webapp/components/needs_team.jsx +++ /dev/null @@ -1,231 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import React from 'react'; - -import $ from 'jquery'; - -import {browserHistory} from 'react-router/es6'; -import * as Utils from 'utils/utils.jsx'; -import * as AsyncClient from 'utils/async_client.jsx'; -import TeamStore from 'stores/team_store.jsx'; -import UserStore from 'stores/user_store.jsx'; -import PreferenceStore from 'stores/preference_store.jsx'; -import ChannelStore from 'stores/channel_store.jsx'; -import PostStore from 'stores/post_store.jsx'; -import * as GlobalActions from 'actions/global_actions.jsx'; -import {startPeriodicStatusUpdates, stopPeriodicStatusUpdates} from 'actions/status_actions.jsx'; -import {startPeriodicSync, stopPeriodicSync} from 'actions/websocket_actions.jsx'; -import {loadProfilesForSidebar} from 'actions/user_actions.jsx'; - -import Constants from 'utils/constants.jsx'; -const TutorialSteps = Constants.TutorialSteps; -const Preferences = Constants.Preferences; - -import ErrorBar from 'components/error_bar.jsx'; -import SidebarRight from 'components/sidebar_right.jsx'; -import SidebarRightMenu from 'components/sidebar_right_menu.jsx'; -import Navbar from 'components/navbar.jsx'; -import WebrtcSidebar from './webrtc/components/webrtc_sidebar.jsx'; - -import WebrtcNotification from './webrtc/components/webrtc_notification.jsx'; - -// Modals -import GetPostLinkModal from 'components/get_post_link_modal.jsx'; -import GetPublicLinkModal from 'components/get_public_link_modal.jsx'; -import GetTeamInviteLinkModal from 'components/get_team_invite_link_modal.jsx'; -import EditPostModal from 'components/edit_post_modal.jsx'; -import DeletePostModal from 'components/delete_post_modal.jsx'; -import TeamSettingsModal from 'components/team_settings_modal.jsx'; -import RemovedFromChannelModal from 'components/removed_from_channel_modal.jsx'; -import ImportThemeModal from 'components/user_settings/import_theme_modal.jsx'; -import InviteMemberModal from 'components/invite_member_modal.jsx'; -import LeaveTeamModal from 'components/leave_team_modal.jsx'; - -import iNoBounce from 'inobounce'; -import * as UserAgent from 'utils/user_agent.jsx'; - -const UNREAD_CHECK_TIME_MILLISECONDS = 10000; - -export default class NeedsTeam extends React.Component { - constructor(params) { - super(params); - - this.onTeamChanged = this.onTeamChanged.bind(this); - this.onPreferencesChanged = this.onPreferencesChanged.bind(this); - - this.blurTime = new Date().getTime(); - - const team = TeamStore.getCurrent(); - - this.state = { - team, - theme: PreferenceStore.getTheme(team.id) - }; - } - - onTeamChanged() { - const team = TeamStore.getCurrent(); - - this.setState({ - team, - theme: PreferenceStore.getTheme(team.id) - }); - } - - onPreferencesChanged(category) { - if (!category || category === Preferences.CATEGORY_THEME) { - this.setState({ - theme: PreferenceStore.getTheme(this.state.team.id) - }); - } - } - - componentWillMount() { - // 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(TeamStore.getCurrentTeamRelativeUrl() + '/tutorial'); - } - } - - componentDidMount() { - TeamStore.addChangeListener(this.onTeamChanged); - PreferenceStore.addChangeListener(this.onPreferencesChanged); - - // Emit view action - GlobalActions.viewLoggedIn(); - - startPeriodicStatusUpdates(); - startPeriodicSync(); - - // Set up tracking for whether the window is active - window.isActive = true; - $(window).on('focus', () => { - AsyncClient.viewChannel(); - ChannelStore.resetCounts(ChannelStore.getCurrentId()); - ChannelStore.emitChange(); - - window.isActive = true; - if (new Date().getTime() - this.blurTime > UNREAD_CHECK_TIME_MILLISECONDS) { - AsyncClient.getMyChannelMembers().then(loadProfilesForSidebar); - } - }); - - $(window).on('blur', () => { - window.isActive = false; - this.blurTime = new Date().getTime(); - if (UserStore.getCurrentUser()) { - AsyncClient.viewChannel(''); - } - }); - - Utils.applyTheme(this.state.theme); - - if (UserAgent.isIosSafari()) { - // Use iNoBounce to prevent scrolling past the boundaries of the page - iNoBounce.enable(); - } - } - - componentDidUpdate(prevProps, prevState) { - if (!Utils.areObjectsEqual(prevState.theme, this.state.theme)) { - Utils.applyTheme(this.state.theme); - } - } - - componentWillUnmount() { - TeamStore.removeChangeListener(this.onTeamChanged); - PreferenceStore.removeChangeListener(this.onPreferencesChanged); - $(window).off('focus'); - $(window).off('blur'); - - if (UserAgent.isIosSafari()) { - iNoBounce.disable(); - } - stopPeriodicStatusUpdates(); - stopPeriodicSync(); - } - - render() { - let content = []; - if (this.props.children) { - content = this.props.children; - } else { - content.push( - this.props.navbar - ); - content.push(this.props.team_sidebar); - content.push( - this.props.sidebar - ); - content.push( -
    -
    - -
    -
    - {React.cloneElement(this.props.center, { - user: this.props.user, - team: this.state.team - })} -
    -
    - ); - } - - let channel = ChannelStore.getByName(this.props.params.channel); - if (channel == null) { - // the permalink view is not really tied to a particular channel but still needs it - const postId = PostStore.getFocusedPostId(); - const post = PostStore.getEarliestPostFromPage(postId); - - // the post take some time before being available on page load - if (post != null) { - channel = ChannelStore.get(post.channel_id); - } - } - - return ( -
    - - -
    - - - - {content} - - - - - - - - - - - -
    -
    - ); - } -} - -NeedsTeam.propTypes = { - children: React.PropTypes.oneOfType([ - React.PropTypes.arrayOf(React.PropTypes.element), - React.PropTypes.element - ]), - navbar: React.PropTypes.element, - sidebar: React.PropTypes.element, - team_sidebar: React.PropTypes.element, - center: React.PropTypes.element, - params: React.PropTypes.object, - user: React.PropTypes.object -}; diff --git a/webapp/components/needs_team/index.js b/webapp/components/needs_team/index.js new file mode 100644 index 000000000..ba809fd10 --- /dev/null +++ b/webapp/components/needs_team/index.js @@ -0,0 +1,25 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import {connect} from 'react-redux'; +import {bindActionCreators} from 'redux'; +import {viewChannel, getMyChannelMembers} from 'mattermost-redux/actions/channels'; + +import NeedsTeam from './needs_team.jsx'; + +function mapStateToProps(state, ownProps) { + return { + ...ownProps + }; +} + +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators({ + viewChannel, + getMyChannelMembers + }, dispatch) + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(NeedsTeam); diff --git a/webapp/components/needs_team/needs_team.jsx b/webapp/components/needs_team/needs_team.jsx new file mode 100644 index 000000000..2ae3cbeec --- /dev/null +++ b/webapp/components/needs_team/needs_team.jsx @@ -0,0 +1,234 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; + +import $ from 'jquery'; + +import {browserHistory} from 'react-router/es6'; +import * as Utils from 'utils/utils.jsx'; +import TeamStore from 'stores/team_store.jsx'; +import UserStore from 'stores/user_store.jsx'; +import PreferenceStore from 'stores/preference_store.jsx'; +import ChannelStore from 'stores/channel_store.jsx'; +import PostStore from 'stores/post_store.jsx'; +import * as GlobalActions from 'actions/global_actions.jsx'; +import {startPeriodicStatusUpdates, stopPeriodicStatusUpdates} from 'actions/status_actions.jsx'; +import {startPeriodicSync, stopPeriodicSync} from 'actions/websocket_actions.jsx'; +import {loadProfilesForSidebar} from 'actions/user_actions.jsx'; + +import Constants from 'utils/constants.jsx'; +const TutorialSteps = Constants.TutorialSteps; +const Preferences = Constants.Preferences; + +import ErrorBar from 'components/error_bar.jsx'; +import SidebarRight from 'components/sidebar_right.jsx'; +import SidebarRightMenu from 'components/sidebar_right_menu.jsx'; +import Navbar from 'components/navbar.jsx'; +import WebrtcSidebar from 'components/webrtc/components/webrtc_sidebar.jsx'; + +import WebrtcNotification from 'components/webrtc/components/webrtc_notification.jsx'; + +// Modals +import GetPostLinkModal from 'components/get_post_link_modal.jsx'; +import GetPublicLinkModal from 'components/get_public_link_modal.jsx'; +import GetTeamInviteLinkModal from 'components/get_team_invite_link_modal.jsx'; +import EditPostModal from 'components/edit_post_modal.jsx'; +import DeletePostModal from 'components/delete_post_modal.jsx'; +import TeamSettingsModal from 'components/team_settings_modal.jsx'; +import RemovedFromChannelModal from 'components/removed_from_channel_modal.jsx'; +import ImportThemeModal from 'components/user_settings/import_theme_modal.jsx'; +import InviteMemberModal from 'components/invite_member_modal.jsx'; +import LeaveTeamModal from 'components/leave_team_modal.jsx'; + +import iNoBounce from 'inobounce'; +import * as UserAgent from 'utils/user_agent.jsx'; + +const UNREAD_CHECK_TIME_MILLISECONDS = 10000; + +export default class NeedsTeam extends React.Component { + static propTypes = { + children: React.PropTypes.oneOfType([ + React.PropTypes.arrayOf(React.PropTypes.element), + React.PropTypes.element + ]), + navbar: React.PropTypes.element, + sidebar: React.PropTypes.element, + team_sidebar: React.PropTypes.element, + center: React.PropTypes.element, + params: React.PropTypes.object, + user: React.PropTypes.object, + actions: React.PropTypes.shape({ + viewChannel: React.PropTypes.func.isRequired, + getMyChannelMembers: React.PropTypes.func.isRequired + }).isRequired + } + + constructor(params) { + super(params); + + this.onTeamChanged = this.onTeamChanged.bind(this); + this.onPreferencesChanged = this.onPreferencesChanged.bind(this); + + this.blurTime = new Date().getTime(); + + const team = TeamStore.getCurrent(); + + this.state = { + team, + theme: PreferenceStore.getTheme(team.id) + }; + } + + onTeamChanged() { + const team = TeamStore.getCurrent(); + + this.setState({ + team, + theme: PreferenceStore.getTheme(team.id) + }); + } + + onPreferencesChanged(category) { + if (!category || category === Preferences.CATEGORY_THEME) { + this.setState({ + theme: PreferenceStore.getTheme(this.state.team.id) + }); + } + } + + componentWillMount() { + // 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(TeamStore.getCurrentTeamRelativeUrl() + '/tutorial'); + } + } + + componentDidMount() { + TeamStore.addChangeListener(this.onTeamChanged); + PreferenceStore.addChangeListener(this.onPreferencesChanged); + + // Emit view action + GlobalActions.viewLoggedIn(); + + startPeriodicStatusUpdates(); + startPeriodicSync(); + + // Set up tracking for whether the window is active + window.isActive = true; + $(window).on('focus', () => { + this.props.actions.viewChannel(ChannelStore.getCurrentId()); + ChannelStore.resetCounts(ChannelStore.getCurrentId()); + ChannelStore.emitChange(); + + window.isActive = true; + if (new Date().getTime() - this.blurTime > UNREAD_CHECK_TIME_MILLISECONDS) { + this.props.actions.getMyChannelMembers(TeamStore.getCurrentId()).then(loadProfilesForSidebar); + } + }); + + $(window).on('blur', () => { + window.isActive = false; + this.blurTime = new Date().getTime(); + if (UserStore.getCurrentUser()) { + this.props.actions.viewChannel(''); + } + }); + + Utils.applyTheme(this.state.theme); + + if (UserAgent.isIosSafari()) { + // Use iNoBounce to prevent scrolling past the boundaries of the page + iNoBounce.enable(); + } + } + + componentDidUpdate(prevProps, prevState) { + if (!Utils.areObjectsEqual(prevState.theme, this.state.theme)) { + Utils.applyTheme(this.state.theme); + } + } + + componentWillUnmount() { + TeamStore.removeChangeListener(this.onTeamChanged); + PreferenceStore.removeChangeListener(this.onPreferencesChanged); + $(window).off('focus'); + $(window).off('blur'); + + if (UserAgent.isIosSafari()) { + iNoBounce.disable(); + } + stopPeriodicStatusUpdates(); + stopPeriodicSync(); + } + + render() { + let content = []; + if (this.props.children) { + content = this.props.children; + } else { + content.push( + this.props.navbar + ); + content.push(this.props.team_sidebar); + content.push( + this.props.sidebar + ); + content.push( +
    +
    + +
    +
    + {React.cloneElement(this.props.center, { + user: this.props.user, + team: this.state.team + })} +
    +
    + ); + } + + let channel = ChannelStore.getByName(this.props.params.channel); + if (channel == null) { + // the permalink view is not really tied to a particular channel but still needs it + const postId = PostStore.getFocusedPostId(); + const post = PostStore.getEarliestPostFromPage(postId); + + // the post take some time before being available on page load + if (post != null) { + channel = ChannelStore.get(post.channel_id); + } + } + + return ( +
    + + +
    + + + + {content} + + + + + + + + + + + +
    +
    + ); + } +} diff --git a/webapp/components/new_channel_flow.jsx b/webapp/components/new_channel_flow.jsx index 0ca504534..91dd04c0c 100644 --- a/webapp/components/new_channel_flow.jsx +++ b/webapp/components/new_channel_flow.jsx @@ -3,7 +3,6 @@ import * as Utils from 'utils/utils.jsx'; import TeamStore from 'stores/team_store.jsx'; -import UserStore from 'stores/user_store.jsx'; import {cleanUpUrlable} from 'utils/url.jsx'; import NewChannelModal from './new_channel_modal.jsx'; @@ -68,9 +67,8 @@ export default class NewChannelFlow extends React.Component { return; } - const cu = UserStore.getCurrentUser(); const channel = { - team_id: cu.team_id, + team_id: TeamStore.getCurrentId(), name: this.state.channelName, display_name: this.state.channelDisplayName, purpose: this.state.channelPurpose, @@ -82,7 +80,7 @@ export default class NewChannelFlow extends React.Component { channel, (data) => { this.doOnModalExited = () => { - browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/channels/' + data.channel.name); + browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/channels/' + data.name); }; this.props.onModalDismissed(); diff --git a/webapp/components/post_view/index.js b/webapp/components/post_view/index.js new file mode 100644 index 000000000..b42b486ab --- /dev/null +++ b/webapp/components/post_view/index.js @@ -0,0 +1,24 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import {connect} from 'react-redux'; +import {bindActionCreators} from 'redux'; +import {viewChannel} from 'mattermost-redux/actions/channels'; + +import PostViewCache from './post_view_cache.jsx'; + +function mapStateToProps(state, ownProps) { + return { + ...ownProps + }; +} + +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators({ + viewChannel + }, dispatch) + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(PostViewCache); diff --git a/webapp/components/post_view/post_view_cache.jsx b/webapp/components/post_view/post_view_cache.jsx index d7cb360d1..beb20360a 100644 --- a/webapp/components/post_view/post_view_cache.jsx +++ b/webapp/components/post_view/post_view_cache.jsx @@ -5,13 +5,18 @@ import PostViewController from './post_view_controller.jsx'; import ChannelStore from 'stores/channel_store.jsx'; import UserStore from 'stores/user_store.jsx'; -import * as AsyncClient from 'utils/async_client.jsx'; import React from 'react'; const MAXIMUM_CACHED_VIEWS = 5; export default class PostViewCache extends React.Component { + static propTypes = { + actions: React.PropTypes.shape({ + viewChannel: React.PropTypes.func.isRequired + }).isRequired + } + constructor(props) { super(props); @@ -32,7 +37,7 @@ export default class PostViewCache extends React.Component { componentWillUnmount() { if (UserStore.getCurrentUser()) { - AsyncClient.viewChannel('', this.state.currentChannelId || ''); + this.props.actions.viewChannel('', this.state.currentChannelId || ''); } ChannelStore.removeChangeListener(this.onChannelChange); } diff --git a/webapp/components/rename_channel_modal.jsx b/webapp/components/rename_channel_modal.jsx index f7d8fad28..96897eb52 100644 --- a/webapp/components/rename_channel_modal.jsx +++ b/webapp/components/rename_channel_modal.jsx @@ -2,6 +2,7 @@ // See License.txt for license information. import ReactDOM from 'react-dom'; +import {browserHistory} from 'react-router/es6'; import * as Utils from 'utils/utils.jsx'; import Constants from 'utils/constants.jsx'; import {cleanUpUrlable, getShortenedURL} from 'utils/url.jsx'; @@ -164,8 +165,10 @@ export class RenameChannelModal extends React.Component { } updateChannel(channel, - () => { + (data) => { this.handleHide(); + const team = TeamStore.get(data.team_id); + browserHistory.push('/' + team.name + '/channels/' + data.name); }, (err) => { this.setState({ diff --git a/webapp/components/sidebar.jsx b/webapp/components/sidebar.jsx index 8667802cc..71559de02 100644 --- a/webapp/components/sidebar.jsx +++ b/webapp/components/sidebar.jsx @@ -5,7 +5,7 @@ import $ from 'jquery'; import ReactDOM from 'react-dom'; import NewChannelFlow from './new_channel_flow.jsx'; import MoreDirectChannels from 'components/more_direct_channels'; -import MoreChannels from 'components/more_channels.jsx'; +import MoreChannels from 'components/more_channels'; import SidebarHeader from './sidebar_header.jsx'; import UnreadChannelIndicator from './unread_channel_indicator.jsx'; import TutorialTip from './tutorial/tutorial_tip.jsx'; diff --git a/webapp/components/team_members_dropdown/index.js b/webapp/components/team_members_dropdown/index.js index 9486c89fa..e7b5910a2 100644 --- a/webapp/components/team_members_dropdown/index.js +++ b/webapp/components/team_members_dropdown/index.js @@ -5,6 +5,7 @@ import {connect} from 'react-redux'; import {bindActionCreators} from 'redux'; import {getUser} from 'mattermost-redux/actions/users'; import {getTeamStats} from 'mattermost-redux/actions/teams'; +import {getChannelStats} from 'mattermost-redux/actions/channels'; import TeamMembersDropdown from './team_members_dropdown.jsx'; @@ -18,7 +19,8 @@ function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ getUser, - getTeamStats + getTeamStats, + getChannelStats }, dispatch) }; } diff --git a/webapp/components/team_members_dropdown/team_members_dropdown.jsx b/webapp/components/team_members_dropdown/team_members_dropdown.jsx index 00441ba37..f01997d12 100644 --- a/webapp/components/team_members_dropdown/team_members_dropdown.jsx +++ b/webapp/components/team_members_dropdown/team_members_dropdown.jsx @@ -10,7 +10,6 @@ import ChannelStore from 'stores/channel_store.jsx'; import {removeUserFromTeam, updateTeamMemberRoles} from 'actions/team_actions.jsx'; import {loadMyTeamMembers, updateActive} from 'actions/user_actions.jsx'; -import * as AsyncClient from 'utils/async_client.jsx'; import * as Utils from 'utils/utils.jsx'; import React from 'react'; @@ -23,7 +22,8 @@ export default class TeamMembersDropdown extends React.Component { teamMember: React.PropTypes.object.isRequired, actions: React.PropTypes.shape({ getUser: React.PropTypes.func.isRequired, - getTeamStats: React.PropTypes.func.isRequired + getTeamStats: React.PropTypes.func.isRequired, + getChannelStats: React.PropTypes.func.isRequired }).isRequired } @@ -88,7 +88,7 @@ export default class TeamMembersDropdown extends React.Component { handleMakeActive() { updateActive(this.props.user.id, true, () => { - AsyncClient.getChannelStats(ChannelStore.getCurrentId()); + this.props.actions.getChannelStats(ChannelStore.getCurrentId()); this.props.actions.getTeamStats(this.props.teamMember.team_id); }, (err) => { @@ -100,7 +100,7 @@ export default class TeamMembersDropdown extends React.Component { handleMakeNotActive() { updateActive(this.props.user.id, false, () => { - AsyncClient.getChannelStats(ChannelStore.getCurrentId()); + this.props.actions.getChannelStats(ChannelStore.getCurrentId()); this.props.actions.getTeamStats(this.props.teamMember.team_id); }, (err) => { diff --git a/webapp/package.json b/webapp/package.json index 0067e2fb9..e7203f0d6 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -22,7 +22,7 @@ "localforage": "1.5.0", "marked": "mattermost/marked#8f5902fff9bad793cd6c66e0c44002c9e79e1317", "match-at": "0.1.0", - "mattermost-redux": "mattermost/mattermost-redux#webapp-part3", + "mattermost-redux": "mattermost/mattermost-redux#webapp-part4", "object-assign": "4.1.1", "pdfjs-dist": "1.7.363", "perfect-scrollbar": "0.6.16", diff --git a/webapp/root.jsx b/webapp/root.jsx index b2da6a54c..6a63e6dad 100644 --- a/webapp/root.jsx +++ b/webapp/root.jsx @@ -16,9 +16,6 @@ import BrowserStore from 'stores/browser_store.jsx'; import ChannelStore from 'stores/channel_store.jsx'; import UserStore from 'stores/user_store.jsx'; import * as I18n from 'i18n/i18n.jsx'; -import * as AsyncClient from 'utils/async_client.jsx'; - -import {getClientConfig, getLicenseConfig, setUrl} from 'mattermost-redux/actions/general'; // Import our styles import 'bootstrap-colorpicker/dist/css/bootstrap-colorpicker.css'; @@ -26,7 +23,13 @@ import 'google-fonts/google-fonts.css'; import 'sass/styles.scss'; import 'katex/dist/katex.min.css'; +// Redux actions import store from 'stores/redux_store.jsx'; +const dispatch = store.dispatch; +const getState = store.getState; + +import {viewChannel} from 'mattermost-redux/actions/channels'; +import {getClientConfig, getLicenseConfig, setUrl} from 'mattermost-redux/actions/general'; // Import the root of our routing tree import rRoot from 'routes/route_root.jsx'; @@ -85,7 +88,7 @@ function preRenderSetup(callwhendone) { $(window).off('beforeunload'); BrowserStore.setLastServerVersion(''); if (UserStore.getCurrentUser()) { - AsyncClient.viewChannel('', ChannelStore.getCurrentId() || ''); + viewChannel('', ChannelStore.getCurrentId() || '')(dispatch, getState); } Websockets.close(); } diff --git a/webapp/routes/route_team.jsx b/webapp/routes/route_team.jsx index 0a3c0a59f..45494dca5 100644 --- a/webapp/routes/route_team.jsx +++ b/webapp/routes/route_team.jsx @@ -6,14 +6,12 @@ import * as RouteUtils from 'routes/route_utils.jsx'; import {browserHistory} from 'react-router/es6'; import TeamStore from 'stores/team_store.jsx'; +import UserStore from 'stores/user_store.jsx'; import * as GlobalActions from 'actions/global_actions.jsx'; import {loadStatusesForChannelAndSidebar} from 'actions/status_actions.jsx'; import {reconnect} from 'actions/websocket_actions.jsx'; -import AppDispatcher from 'dispatcher/app_dispatcher.jsx'; import Constants from 'utils/constants.jsx'; -const ActionTypes = Constants.ActionTypes; import * as AsyncClient from 'utils/async_client.jsx'; -import Client from 'client/web_client.jsx'; import ChannelStore from 'stores/channel_store.jsx'; import BrowserStore from 'stores/browser_store.jsx'; @@ -22,6 +20,13 @@ import integrationsRoute from 'routes/route_integrations.jsx'; import {loadNewDMIfNeeded, loadNewGMIfNeeded, loadProfilesForSidebar} from 'actions/user_actions.jsx'; +// Redux actions +import store from 'stores/redux_store.jsx'; +const dispatch = store.dispatch; +const getState = store.getState; + +import {fetchMyChannelsAndMembers, joinChannel} from 'mattermost-redux/actions/channels'; + function onChannelEnter(nextState, replace, callback) { doChannelChange(nextState, replace, callback); } @@ -40,22 +45,16 @@ function doChannelChange(state, replace, callback) { } if (!channel) { - Client.joinChannelByName( - state.params.channel, + joinChannel(UserStore.getCurrentId(), TeamStore.getCurrentId(), null, state.params.channel)(dispatch, getState).then( (data) => { - AppDispatcher.handleServerAction({ - type: ActionTypes.RECEIVED_CHANNEL, - channel: data - }); - - GlobalActions.emitChannelClickEvent(data); - callback(); - }, - () => { - if (state.params.team) { - replace('/' + state.params.team + '/channels/town-square'); - } else { - replace('/'); + if (data) { + GlobalActions.emitChannelClickEvent(data.channel); + } else if (data == null) { + if (state.params.team) { + replace('/' + state.params.team + '/channels/town-square'); + } else { + replace('/'); + } } callback(); } @@ -107,25 +106,16 @@ function preNeedsTeam(nextState, replace, callback) { if (nextState.location.pathname.indexOf('/channels/') > -1 || nextState.location.pathname.indexOf('/pl/') > -1) { AsyncClient.getMyTeamsUnread(); - AsyncClient.getMyChannelMembersForTeam(team.id); + fetchMyChannelsAndMembers(team.id)(dispatch, getState); } const d1 = $.Deferred(); //eslint-disable-line new-cap - Client.getChannels( - (data) => { - AppDispatcher.handleServerAction({ - type: ActionTypes.RECEIVED_CHANNELS, - channels: data - }); - + fetchMyChannelsAndMembers(team.id)(dispatch, getState).then( + () => { loadStatusesForChannelAndSidebar(); loadProfilesForSidebar(); - d1.resolve(); - }, - (err) => { - AsyncClient.dispatchError(err, 'getChannels'); d1.resolve(); } ); @@ -166,7 +156,7 @@ export default { emojiRoute, { getComponents: (location, callback) => { - System.import('components/needs_team.jsx').then(RouteUtils.importComponentSuccess(callback)); + System.import('components/needs_team').then(RouteUtils.importComponentSuccess(callback)); }, childRoutes: [ { diff --git a/webapp/stores/channel_store.jsx b/webapp/stores/channel_store.jsx index 8530d0620..d33f3ac9a 100644 --- a/webapp/stores/channel_store.jsx +++ b/webapp/stores/channel_store.jsx @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import AppDispatcher from '../dispatcher/app_dispatcher.jsx'; +import AppDispatcher from 'dispatcher/app_dispatcher.jsx'; import EventEmitter from 'events'; import TeamStore from 'stores/team_store.jsx'; @@ -17,21 +17,44 @@ const CHANGE_EVENT = 'change'; const STATS_EVENT = 'stats'; const LAST_VIEVED_EVENT = 'last_viewed'; +import store from 'stores/redux_store.jsx'; +import * as Selectors from 'mattermost-redux/selectors/entities/channels'; +import {ChannelTypes, UserTypes} from 'mattermost-redux/action_types'; + class ChannelStoreClass extends EventEmitter { constructor(props) { super(props); this.setMaxListeners(600); this.clear(); + + this.entities = store.getState().entities.channels; + + store.subscribe(() => { + const newEntities = store.getState().entities.channels; + + if (newEntities.currentTeamId !== this.entities.currentChannelId) { + this.emitChange(); + } + if (newEntities.channels !== this.entities.channels) { + this.emitChange(); + } + if (newEntities.myMembers !== this.entities.myMembers) { + this.setUnreadCountsByMembers(Object.values(newEntities.myMembers)); + this.emitChange(); + } + if (newEntities.membersInChannel !== this.entities.membersInChannel) { + this.emitChange(); + } + if (newEntities.stats !== this.entities.stats) { + this.emitStatsChange(); + } + + this.entities = newEntities; + }); } clear() { - this.currentId = null; this.postMode = this.POST_MODE_CHANNEL; - this.channels = []; - this.members_in_channel = {}; - this.myChannelMembers = {}; - this.moreChannels = {}; - this.stats = {}; this.unreadCounts = {}; } @@ -126,17 +149,25 @@ class ChannelStoreClass extends EventEmitter { } setCurrentId(id) { - this.currentId = id; + store.dispatch({ + type: ChannelTypes.SELECT_CHANNEL, + data: id + }); } resetCounts(id) { - const cm = this.myChannelMembers; - for (const cmid in cm) { - if (cm[cmid].channel_id === id) { + const members = Object.assign({}, this.getMyMembers()); + for (const cmid in members) { + if (!members.hasOwnProperty(cmid)) { + continue; + } + const member = {...members[cmid]}; + if (member.channel_id === id) { const channel = this.get(id); if (channel) { - cm[cmid].msg_count = channel.total_msg_count; - cm[cmid].mention_count = 0; + member.msg_count = channel.total_msg_count; + member.mention_count = 0; + this.storeMyChannelMember(member); this.setUnreadCountByChannel(id); } break; @@ -145,7 +176,7 @@ class ChannelStoreClass extends EventEmitter { } getCurrentId() { - return this.currentId; + return Selectors.getCurrentChannelId(store.getState()); } getCurrent() { @@ -176,7 +207,7 @@ class ChannelStoreClass extends EventEmitter { let stats; if (channelId) { - stats = this.stats[channelId]; + stats = Selectors.getAllChannelStats(store.getState())[channelId]; } if (stats) { @@ -214,54 +245,67 @@ class ChannelStoreClass extends EventEmitter { } storeChannels(channels) { - this.channels = channels; + store.dispatch({ + type: ChannelTypes.RECEIVED_CHANNELS, + data: channels, + teamId: channels[0].team_id + }); } getChannels() { - return this.channels; + return Selectors.getMyChannels(store.getState()); } getChannelById(id) { - return this.channels.filter((c) => c.id === id)[0]; + return Selectors.getChannelsInCurrentTeam(store.getState())[id]; } storeMyChannelMember(channelMember) { - const members = Object.assign({}, this.getMyMembers()); - members[channelMember.channel_id] = channelMember; - this.storeMyChannelMembers(members); + store.dispatch({ + type: ChannelTypes.RECEIVED_MY_CHANNEL_MEMBER, + data: channelMember + }); } storeMyChannelMembers(channelMembers) { - this.myChannelMembers = channelMembers; + store.dispatch({ + type: ChannelTypes.RECEIVED_MY_CHANNEL_MEMBERS, + data: Object.values(channelMembers) + }); } storeMyChannelMembersList(channelMembers) { - channelMembers.forEach((m) => { - this.myChannelMembers[m.channel_id] = m; + store.dispatch({ + type: ChannelTypes.RECEIVED_MY_CHANNEL_MEMBERS, + data: channelMembers }); } getMyMembers() { - return this.myChannelMembers; + return Selectors.getMyChannelMemberships(store.getState()); } saveMembersInChannel(channelId = this.getCurrentId(), members) { - const oldMembers = this.members_in_channel[channelId] || {}; - this.members_in_channel[channelId] = Object.assign({}, oldMembers, members); + store.dispatch({ + type: ChannelTypes.RECEIVED_CHANNEL_MEMBERS, + data: Object.values(members) + }); } removeMemberInChannel(channelId = this.getCurrentId(), userId) { - if (this.members_in_channel[channelId]) { - Reflect.deleteProperty(this.members_in_channel[channelId], userId); - } + store.dispatch({ + type: UserTypes.RECEIVED_PROFILE_NOT_IN_CHANNEL, + data: {id: channelId, user_id: userId} + }); } getMembersInChannel(channelId = this.getCurrentId()) { - return Object.assign({}, this.members_in_channel[channelId]) || {}; + return Selectors.getChannelMembersInChannels(store.getState())[channelId] || {}; } hasActiveMemberInChannel(channelId = this.getCurrentId(), userId) { - if (this.members_in_channel[channelId] && this.members_in_channel[channelId][userId]) { + const members = this.getMembersInChannel(channelId); + if (members && members[userId]) { return true; } @@ -269,33 +313,24 @@ class ChannelStoreClass extends EventEmitter { } storeMoreChannels(channels, teamId = TeamStore.getCurrentId()) { - const newChannels = {}; - for (let i = 0; i < channels.length; i++) { - newChannels[channels[i].id] = channels[i]; - } - this.moreChannels[teamId] = Object.assign({}, this.moreChannels[teamId], newChannels); - } - - removeMoreChannel(channelId, teamId = TeamStore.getCurrentId()) { - Reflect.deleteProperty(this.moreChannels[teamId], channelId); - } - - getMoreChannels(teamId = TeamStore.getCurrentId()) { - return Object.assign({}, this.moreChannels[teamId]); + store.dispatch({ + type: ChannelTypes.RECEIVED_CHANNELS, + data: channels, + teamId + }); } - getMoreChannelsList(teamId = TeamStore.getCurrentId()) { - const teamChannels = this.moreChannels[teamId] || {}; - - if (!ChannelUtils) { - ChannelUtils = require('utils/channel_utils.jsx'); //eslint-disable-line global-require - } - - return Object.keys(teamChannels).map((cid) => teamChannels[cid]).sort(ChannelUtils.sortChannelsByDisplayName); + getMoreChannels() { + const channels = Selectors.getOtherChannels(store.getState()); + const channelMap = {}; + channels.forEach((c) => { + channelMap[c.id] = c; + }); + return channelMap; } - storeStats(stats) { - this.stats = stats; + getMoreChannelsList() { + return Selectors.getOtherChannels(store.getState()); } isDefault(channel) { @@ -317,8 +352,8 @@ class ChannelStoreClass extends EventEmitter { } setUnreadCountsByCurrentMembers() { - Object.keys(this.myChannelMembers).forEach((key) => { - this.setUnreadCountByChannel(this.myChannelMembers[key].channel_id); + Object.keys(this.getMyMembers()).forEach((key) => { + this.setUnreadCountByChannel(this.getMyMember(key).channel_id); }); } @@ -415,7 +450,12 @@ class ChannelStoreClass extends EventEmitter { return; } - this.get(id).total_msg_count++; + const channel = {...this.get(id)}; + channel.total_msg_count++; + store.dispatch({ + type: ChannelTypes.RECEIVED_CHANNEL, + data: channel + }); if (markRead) { this.resetCounts(id); @@ -436,7 +476,12 @@ class ChannelStoreClass extends EventEmitter { if (mentions.indexOf(UserStore.getCurrentId()) !== -1) { this.unreadCounts[id].mentions++; - this.getMyMember(id).mention_count++; + const member = {...this.getMyMember(id)}; + member.mention_count++; + store.dispatch({ + type: ChannelTypes.RECEIVED_MY_CHANNEL_MEMBER, + data: member + }); } } } @@ -510,10 +555,10 @@ ChannelStore.dispatchToken = AppDispatcher.register((payload) => { ChannelStore.emitChange(); break; case ActionTypes.RECEIVED_CHANNEL_STATS: - var stats = Object.assign({}, ChannelStore.getStats()); - stats[action.stats.channel_id] = action.stats; - ChannelStore.storeStats(stats); - ChannelStore.emitStatsChange(); + store.dispatch({ + type: ChannelTypes.RECEIVED_CHANNEL_STATS, + data: action.stats + }); break; case ActionTypes.RECEIVED_POST: diff --git a/webapp/utils/async_client.jsx b/webapp/utils/async_client.jsx index 47327c5e5..712d447e8 100644 --- a/webapp/utils/async_client.jsx +++ b/webapp/utils/async_client.jsx @@ -2,10 +2,8 @@ // See License.txt for license information. import BrowserStore from 'stores/browser_store.jsx'; -import ChannelStore from 'stores/channel_store.jsx'; import UserStore from 'stores/user_store.jsx'; import TeamStore from 'stores/team_store.jsx'; -import ErrorStore from 'stores/error_store.jsx'; import * as GlobalActions from 'actions/global_actions.jsx'; @@ -62,233 +60,6 @@ export function checkVersion() { } } -export function getChannels() { - return new Promise((resolve, reject) => { - if (isCallInProgress('getChannels')) { - resolve(); - return; - } - - callTracker.getChannels = utils.getTimestamp(); - - Client.getChannels( - (data) => { - callTracker.getChannels = 0; - - AppDispatcher.handleServerAction({ - type: ActionTypes.RECEIVED_CHANNELS, - channels: data - }); - resolve(); - }, - (err) => { - callTracker.getChannels = 0; - dispatchError(err, 'getChannels'); - reject(new Error('Unable to getChannels')); - } - ); - }); -} - -export function getChannel(id) { - if (isCallInProgress('getChannel' + id)) { - return; - } - - callTracker['getChannel' + id] = utils.getTimestamp(); - - Client.getChannel(id, - (data) => { - callTracker['getChannel' + id] = 0; - - AppDispatcher.handleServerAction({ - type: ActionTypes.RECEIVED_CHANNEL, - channel: data.channel, - member: data.member - }); - }, - (err) => { - callTracker['getChannel' + id] = 0; - dispatchError(err, 'getChannel'); - } - ); -} - -export function getMyChannelMembers() { - return new Promise((resolve, reject) => { - if (isCallInProgress('getMyChannelMembers')) { - resolve(); - return; - } - - callTracker.getMyChannelMembers = utils.getTimestamp(); - - Client.getMyChannelMembers( - (data) => { - callTracker.getMyChannelMembers = 0; - - AppDispatcher.handleServerAction({ - type: ActionTypes.RECEIVED_MY_CHANNEL_MEMBERS, - members: data - }); - resolve(); - }, - (err) => { - callTracker.getMyChannelMembers = 0; - dispatchError(err, 'getMyChannelMembers'); - reject(new Error('Unable to getMyChannelMembers')); - } - ); - }); -} - -export function getMyChannelMembersForTeam(teamId) { - return new Promise((resolve, reject) => { - if (isCallInProgress(`getMyChannelMembers${teamId}`)) { - resolve(); - return; - } - - callTracker[`getMyChannelMembers${teamId}`] = utils.getTimestamp(); - - Client.getMyChannelMembersForTeam( - teamId, - (data) => { - callTracker[`getMyChannelMembers${teamId}`] = 0; - - AppDispatcher.handleServerAction({ - type: ActionTypes.RECEIVED_MY_CHANNEL_MEMBERS, - members: data - }); - resolve(); - }, - (err) => { - callTracker[`getMyChannelMembers${teamId}`] = 0; - dispatchError(err, 'getMyChannelMembersForTeam'); - reject(new Error('Unable to getMyChannelMembersForTeam')); - } - ); - }); -} - -export function viewChannel(channelId = ChannelStore.getCurrentId(), prevChannelId = '', time = 0) { - if (channelId == null || !Client.teamId) { - return; - } - - if (isCallInProgress(`viewChannel${channelId}`)) { - return; - } - - callTracker[`viewChannel${channelId}`] = utils.getTimestamp(); - Client.viewChannel( - channelId, - prevChannelId, - time, - () => { - AppDispatcher.handleServerAction({ - type: ActionTypes.RECEIVED_PREFERENCE, - preference: { - category: 'last', - name: TeamStore.getCurrentId(), - value: channelId - } - }); - - callTracker[`viewChannel${channelId}`] = 0; - ErrorStore.clearLastError(); - }, - (err) => { - callTracker[`viewChannel${channelId}`] = 0; - const count = ErrorStore.getConnectionErrorCount(); - ErrorStore.setConnectionErrorCount(count + 1); - dispatchError(err, 'viewChannel'); - } - ); -} - -export function getMoreChannelsPage(offset, limit) { - if (isCallInProgress('getMoreChannelsPage')) { - return; - } - - callTracker.getMoreChannelsPage = utils.getTimestamp(); - Client.getMoreChannelsPage( - offset, - limit, - (data) => { - callTracker.getMoreChannelsPage = 0; - - AppDispatcher.handleServerAction({ - type: ActionTypes.RECEIVED_MORE_CHANNELS, - channels: data - }); - }, - (err) => { - callTracker.getMoreChannelsPage = 0; - dispatchError(err, 'getMoreChannelsPage'); - } - ); -} - -export function getChannelStats(channelId = ChannelStore.getCurrentId(), doVersionCheck = false) { - if (isCallInProgress('getChannelStats' + channelId) || channelId == null) { - return; - } - - callTracker['getChannelStats' + channelId] = utils.getTimestamp(); - - Client.getChannelStats( - channelId, - (data) => { - callTracker['getChannelStats' + channelId] = 0; - - if (doVersionCheck) { - checkVersion(); - } - - AppDispatcher.handleServerAction({ - type: ActionTypes.RECEIVED_CHANNEL_STATS, - stats: data - }); - }, - (err) => { - callTracker['getChannelStats' + channelId] = 0; - dispatchError(err, 'getChannelStats'); - } - ); -} - -export function getChannelMember(channelId, userId) { - return new Promise((resolve, reject) => { - if (isCallInProgress(`getChannelMember${channelId}${userId}`)) { - resolve(); - return; - } - - callTracker[`getChannelMember${channelId}${userId}`] = utils.getTimestamp(); - - Client.getChannelMember( - channelId, - userId, - (data) => { - callTracker[`getChannelMember${channelId}${userId}`] = 0; - - AppDispatcher.handleServerAction({ - type: ActionTypes.RECEIVED_CHANNEL_MEMBER, - member: data - }); - resolve(); - }, - (err) => { - callTracker[`getChannelMember${channelId}${userId}`] = 0; - dispatchError(err, 'getChannelMember'); - reject(new Error('Unable to getChannelMeber')); - } - ); - }); -} - export function getUser(userId, success, error) { const callName = `getUser${userId}`; -- cgit v1.2.3-1-g7c22