summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoram Wilander <jwawilander@gmail.com>2017-04-25 11:46:02 -0400
committerChristopher Speller <crspeller@gmail.com>2017-04-25 11:46:02 -0400
commit6c4c706313eb765eb00c639f381646be74f27b69 (patch)
tree6068feaa9668dcd74601730ac1a5abfb366402b1
parentcc07c005074348de87854f1c953a549e8772fa03 (diff)
downloadchat-6c4c706313eb765eb00c639f381646be74f27b69.tar.gz
chat-6c4c706313eb765eb00c639f381646be74f27b69.tar.bz2
chat-6c4c706313eb765eb00c639f381646be74f27b69.zip
Start moving webapp to Redux (#6140)
* Start moving webapp to Redux * Fix localforage import * Updates per feedback * Feedback udpates and a few fixes * Minor updates * Fix statuses, config not loading properly, getMe sanitizing too much * Fix preferences * Fix user autocomplete * Fix sessions and audits * Fix error handling for all redux actions * Use new directory structure for components and containers * Refresh immediately on logout instead of after timeout * Add fetch polyfill
-rw-r--r--api4/system.go10
-rw-r--r--api4/user.go6
-rw-r--r--webapp/actions/admin_actions.jsx23
-rw-r--r--webapp/actions/emoji_actions.jsx8
-rw-r--r--webapp/actions/global_actions.jsx94
-rw-r--r--webapp/actions/integration_actions.jsx12
-rw-r--r--webapp/actions/post_actions.jsx8
-rw-r--r--webapp/actions/status_actions.jsx21
-rw-r--r--webapp/actions/team_actions.jsx9
-rw-r--r--webapp/actions/user_actions.jsx459
-rw-r--r--webapp/actions/websocket_actions.jsx1
-rw-r--r--webapp/components/access_history_modal/access_history_modal.jsx (renamed from webapp/components/access_history_modal.jsx)18
-rw-r--r--webapp/components/access_history_modal/index.js24
-rw-r--r--webapp/components/activity_log_modal/activity_log_modal.jsx (renamed from webapp/components/activity_log_modal.jsx)29
-rw-r--r--webapp/components/activity_log_modal/index.js25
-rw-r--r--webapp/components/add_users_to_team/add_users_to_team.jsx (renamed from webapp/components/add_users_to_team.jsx)69
-rw-r--r--webapp/components/add_users_to_team/index.js24
-rw-r--r--webapp/components/admin_console/system_users/system_users.jsx87
-rw-r--r--webapp/components/channel_header.jsx4
-rw-r--r--webapp/components/channel_invite_modal/channel_invite_modal.jsx (renamed from webapp/components/channel_invite_modal.jsx)61
-rw-r--r--webapp/components/channel_invite_modal/index.js24
-rw-r--r--webapp/components/login/login_controller.jsx22
-rw-r--r--webapp/components/member_list_channel.jsx47
-rw-r--r--webapp/components/member_list_team.jsx30
-rw-r--r--webapp/components/more_direct_channels/index.js25
-rw-r--r--webapp/components/more_direct_channels/more_direct_channels.jsx (renamed from webapp/components/more_direct_channels.jsx)77
-rw-r--r--webapp/components/navbar.jsx2
-rw-r--r--webapp/components/popover_list_members/index.js24
-rw-r--r--webapp/components/popover_list_members/popover_list_members.jsx (renamed from webapp/components/popover_list_members.jsx)23
-rw-r--r--webapp/components/root.jsx1
-rw-r--r--webapp/components/sidebar.jsx2
-rw-r--r--webapp/components/sidebar_header_dropdown.jsx2
-rw-r--r--webapp/components/sidebar_right_menu.jsx2
-rw-r--r--webapp/components/signup/components/signup_email.jsx6
-rw-r--r--webapp/components/signup/components/signup_ldap.jsx4
-rw-r--r--webapp/components/signup/signup_controller.jsx3
-rw-r--r--webapp/components/suggestion/at_mention_provider.jsx8
-rw-r--r--webapp/components/suggestion/search_user_provider.jsx2
-rw-r--r--webapp/components/suggestion/switch_channel_provider.jsx4
-rw-r--r--webapp/components/team_members_dropdown/index.js24
-rw-r--r--webapp/components/team_members_dropdown/team_members_dropdown.jsx (renamed from webapp/components/team_members_dropdown.jsx)21
-rw-r--r--webapp/components/user_settings/user_settings.jsx4
-rw-r--r--webapp/components/user_settings/user_settings_general/index.js24
-rw-r--r--webapp/components/user_settings/user_settings_general/user_settings_general.jsx (renamed from webapp/components/user_settings/user_settings_general.jsx)32
-rw-r--r--webapp/components/user_settings/user_settings_security/index.js24
-rw-r--r--webapp/components/user_settings/user_settings_security/user_settings_security.jsx (renamed from webapp/components/user_settings/user_settings_security.jsx)35
-rw-r--r--webapp/package.json13
-rw-r--r--webapp/root.jsx43
-rw-r--r--webapp/store/index.js112
-rw-r--r--webapp/store/utils.js42
-rw-r--r--webapp/stores/preference_store.jsx42
-rw-r--r--webapp/stores/redux_store.jsx19
-rw-r--r--webapp/stores/team_store.jsx82
-rw-r--r--webapp/stores/user_store.jsx543
-rw-r--r--webapp/utils/async_client.jsx226
-rw-r--r--webapp/utils/channel_intro_messages.jsx2
-rw-r--r--webapp/webpack.config.js4
57 files changed, 1143 insertions, 1449 deletions
diff --git a/api4/system.go b/api4/system.go
index 55be559bf..7e860ba76 100644
--- a/api4/system.go
+++ b/api4/system.go
@@ -5,6 +5,7 @@ package api4
import (
"net/http"
+ "strconv"
l4g "github.com/alecthomas/log4go"
"github.com/mattermost/platform/app"
@@ -202,7 +203,14 @@ func getClientConfig(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- w.Write([]byte(model.MapToJson(utils.ClientCfg)))
+ respCfg := map[string]string{}
+ for k, v := range utils.ClientCfg {
+ respCfg[k] = v
+ }
+
+ respCfg["NoAccounts"] = strconv.FormatBool(app.IsFirstUserAccount())
+
+ w.Write([]byte(model.MapToJson(respCfg)))
}
func getClientLicense(c *Context, w http.ResponseWriter, r *http.Request) {
diff --git a/api4/user.go b/api4/user.go
index 1c870f1c1..1d117ce07 100644
--- a/api4/user.go
+++ b/api4/user.go
@@ -111,7 +111,11 @@ func getUser(c *Context, w http.ResponseWriter, r *http.Request) {
if HandleEtag(etag, "Get User", w, r) {
return
} else {
- app.SanitizeProfile(user, c.IsSystemAdmin())
+ if c.Session.UserId == user.Id {
+ user.Sanitize(map[string]bool{})
+ } else {
+ app.SanitizeProfile(user, c.IsSystemAdmin())
+ }
w.Header().Set(model.HEADER_ETAG_SERVER, etag)
w.Write([]byte(user.ToJson()))
return
diff --git a/webapp/actions/admin_actions.jsx b/webapp/actions/admin_actions.jsx
index ee3d6fd8a..9a522caf9 100644
--- a/webapp/actions/admin_actions.jsx
+++ b/webapp/actions/admin_actions.jsx
@@ -5,21 +5,12 @@ import Client from 'client/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import {browserHistory} from 'react-router/es6';
-export function revokeSession(altId, success, error) {
- Client.revokeSession(altId,
- () => {
- AsyncClient.getSessions();
- if (success) {
- success();
- }
- },
- (err) => {
- if (error) {
- error(err);
- }
- }
- );
-}
+// Redux actions
+import store from 'stores/redux_store.jsx';
+const dispatch = store.dispatch;
+const getState = store.getState;
+
+import {getUser} from 'mattermost-redux/actions/users';
export function saveConfig(config, success, error) {
Client.saveConfig(
@@ -57,7 +48,7 @@ export function adminResetMfa(userId, success, error) {
Client.adminResetMfa(
userId,
() => {
- AsyncClient.getUser(userId);
+ getUser(userId)(dispatch, getState);
if (success) {
success();
diff --git a/webapp/actions/emoji_actions.jsx b/webapp/actions/emoji_actions.jsx
index feb6bd76b..ed8bc84f7 100644
--- a/webapp/actions/emoji_actions.jsx
+++ b/webapp/actions/emoji_actions.jsx
@@ -10,6 +10,12 @@ import Client from 'client/web_client.jsx';
import {ActionTypes} from 'utils/constants.jsx';
+// Redux actions
+import store from 'stores/redux_store.jsx';
+const dispatch = store.dispatch;
+const getState = store.getState;
+import {getProfilesByIds} from 'mattermost-redux/actions/users';
+
export function loadEmoji(getProfiles = true) {
Client.listEmoji(
(data) => {
@@ -42,5 +48,5 @@ function loadProfilesForEmoji(emojiList) {
return;
}
- AsyncClient.getProfilesByIds(list);
+ getProfilesByIds(list)(dispatch, getState);
}
diff --git a/webapp/actions/global_actions.jsx b/webapp/actions/global_actions.jsx
index c3cfb00a4..bd9178604 100644
--- a/webapp/actions/global_actions.jsx
+++ b/webapp/actions/global_actions.jsx
@@ -9,7 +9,6 @@ import UserStore from 'stores/user_store.jsx';
import BrowserStore from 'stores/browser_store.jsx';
import ErrorStore from 'stores/error_store.jsx';
import TeamStore from 'stores/team_store.jsx';
-import PreferenceStore from 'stores/preference_store.jsx';
import SearchStore from 'stores/search_store.jsx';
import {handleNewPost, loadPosts, loadPostsBefore, loadPostsAfter} from 'actions/post_actions.jsx';
@@ -32,6 +31,12 @@ import en from 'i18n/en.json';
import * as I18n from 'i18n/i18n.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 {ChannelTypes} from 'mattermost-redux/action_types';
+
export function emitChannelClickEvent(channel) {
function userVisitedFakeChannel(chan, success, fail) {
const otherUserId = Utils.getUserIdFromChannelName(chan);
@@ -77,6 +82,11 @@ export function emitChannelClickEvent(channel) {
channelMember,
prev: oldChannelId
});
+
+ dispatch({
+ type: ChannelTypes.SELECT_CHANNEL,
+ data: chan.id
+ }, getState);
}
if (channel.fake) {
@@ -94,85 +104,6 @@ export function emitChannelClickEvent(channel) {
}
}
-export function emitInitialLoad(callback) {
- Client.getInitialLoad(
- (data) => {
- global.window.mm_config = data.client_cfg;
- global.window.mm_license = data.license_cfg;
-
- if (global.window && global.window.analytics) {
- global.window.analytics.identify(global.window.mm_config.DiagnosticId, {}, {
- context: {
- ip: '0.0.0.0'
- },
- page: {
- path: '',
- referrer: '',
- search: '',
- title: '',
- url: ''
- },
- anonymousId: '00000000000000000000000000'
- });
- }
-
- UserStore.setNoAccounts(data.no_accounts);
-
- if (data.user && data.user.id) {
- global.window.mm_user = data.user;
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_ME,
- me: data.user
- });
- }
-
- if (data.preferences) {
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_PREFERENCES,
- preferences: data.preferences
- });
- }
-
- if (data.teams) {
- var teams = {};
- data.teams.forEach((team) => {
- teams[team.id] = team;
- });
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_ALL_TEAMS,
- teams
- });
- }
-
- if (data.team_members) {
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_MY_TEAM_MEMBERS,
- team_members: data.team_members
- });
- }
-
- if (data.direct_profiles) {
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_DIRECT_PROFILES,
- profiles: data.direct_profiles
- });
- }
-
- if (callback) {
- callback();
- }
- },
- (err) => {
- AsyncClient.dispatchError(err, 'getInitialLoad');
-
- if (callback) {
- callback();
- }
- }
- );
-}
-
export function doFocusPost(channelId, postId, data) {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_FOCUSED_POST,
@@ -536,12 +467,11 @@ export function emitUserLoggedOutEvent(redirectTo = '/', shouldSignalLogout = tr
export function clientLogout(redirectTo = '/') {
BrowserStore.clear();
ErrorStore.clearLastError();
- PreferenceStore.clear();
- UserStore.clear();
TeamStore.clear();
ChannelStore.clear();
stopPeriodicStatusUpdates();
WebsocketActions.close();
+ localStorage.removeItem('currentUserId');
window.location.href = redirectTo;
}
diff --git a/webapp/actions/integration_actions.jsx b/webapp/actions/integration_actions.jsx
index 43a4c75f4..c1bbf3432 100644
--- a/webapp/actions/integration_actions.jsx
+++ b/webapp/actions/integration_actions.jsx
@@ -11,6 +11,12 @@ import Client from 'client/web_client.jsx';
import {ActionTypes} from 'utils/constants.jsx';
+// Redux actions
+import store from 'stores/redux_store.jsx';
+const dispatch = store.dispatch;
+const getState = store.getState;
+import {getProfilesByIds} from 'mattermost-redux/actions/users';
+
export function loadIncomingHooks() {
Client.listIncomingHooks(
(data) => {
@@ -42,7 +48,7 @@ function loadProfilesForIncomingHooks(hooks) {
return;
}
- AsyncClient.getProfilesByIds(list);
+ getProfilesByIds(list)(dispatch, getState);
}
export function loadOutgoingHooks() {
@@ -76,7 +82,7 @@ function loadProfilesForOutgoingHooks(hooks) {
return;
}
- AsyncClient.getProfilesByIds(list);
+ getProfilesByIds(list)(dispatch, getState);
}
export function loadTeamCommands() {
@@ -110,5 +116,5 @@ function loadProfilesForCommands(commands) {
return;
}
- AsyncClient.getProfilesByIds(list);
+ getProfilesByIds(list)(dispatch, getState);
}
diff --git a/webapp/actions/post_actions.jsx b/webapp/actions/post_actions.jsx
index 266370f60..36abfc2be 100644
--- a/webapp/actions/post_actions.jsx
+++ b/webapp/actions/post_actions.jsx
@@ -20,6 +20,12 @@ import Constants from 'utils/constants.jsx';
const ActionTypes = Constants.ActionTypes;
const Preferences = Constants.Preferences;
+// Redux actions
+import store from 'stores/redux_store.jsx';
+const dispatch = store.dispatch;
+const getState = store.getState;
+import {getProfilesByIds} from 'mattermost-redux/actions/users';
+
export function handleNewPost(post, msg) {
let websocketMessageProps = {};
if (msg) {
@@ -310,7 +316,7 @@ export function loadProfilesForPosts(posts) {
return;
}
- AsyncClient.getProfilesByIds(list);
+ getProfilesByIds(list)(dispatch, getState);
}
export function addReaction(channelId, postId, emojiName) {
diff --git a/webapp/actions/status_actions.jsx b/webapp/actions/status_actions.jsx
index 649df835a..066a89254 100644
--- a/webapp/actions/status_actions.jsx
+++ b/webapp/actions/status_actions.jsx
@@ -1,16 +1,19 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
-
import ChannelStore from 'stores/channel_store.jsx';
import PostStore from 'stores/post_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import UserStore from 'stores/user_store.jsx';
-import Client from 'client/web_client.jsx';
+import {Preferences, Constants} from 'utils/constants.jsx';
+
+// Redux actions
+import store from 'stores/redux_store.jsx';
+const dispatch = store.dispatch;
+const getState = store.getState;
-import {ActionTypes, Preferences, Constants} from 'utils/constants.jsx';
+import {getStatusesByIds} from 'mattermost-redux/actions/users';
export function loadStatusesForChannel(channelId = ChannelStore.getCurrentId()) {
const postList = PostStore.getVisiblePosts(channelId);
@@ -108,15 +111,7 @@ export function loadStatusesByIds(userIds) {
return;
}
- Client.getStatusesByIds(
- userIds,
- (data) => {
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_STATUSES,
- statuses: data
- });
- }
- );
+ getStatusesByIds(userIds)(dispatch, getState);
}
let intervalId = '';
diff --git a/webapp/actions/team_actions.jsx b/webapp/actions/team_actions.jsx
index 1dcfecbab..f263108fd 100644
--- a/webapp/actions/team_actions.jsx
+++ b/webapp/actions/team_actions.jsx
@@ -13,6 +13,13 @@ import AppDispatcher from 'dispatcher/app_dispatcher.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 {getUser} from 'mattermost-redux/actions/users';
+
export function checkIfTeamExists(teamName, onSuccess, onError) {
Client.findTeamByName(teamName, onSuccess, onError);
}
@@ -62,7 +69,7 @@ export function removeUserFromTeam(teamId, userId, success, error) {
TeamStore.removeMemberInTeam(teamId, userId);
UserStore.removeProfileFromTeam(teamId, userId);
UserStore.emitInTeamChange();
- AsyncClient.getUser(userId);
+ getUser(userId)(dispatch, getState);
AsyncClient.getTeamStats(teamId);
if (success) {
diff --git a/webapp/actions/user_actions.jsx b/webapp/actions/user_actions.jsx
index 9f9987cdd..8a794bb0a 100644
--- a/webapp/actions/user_actions.jsx
+++ b/webapp/actions/user_actions.jsx
@@ -19,6 +19,79 @@ import Client from 'client/web_client.jsx';
import {Constants, ActionTypes, Preferences} 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 {
+ getProfiles,
+ getProfilesInChannel,
+ getProfilesInTeam,
+ getProfilesWithoutTeam,
+ getProfilesByIds,
+ getMe,
+ searchProfiles,
+ autocompleteUsers as autocompleteRedux,
+ updateMe,
+ updateUserMfa,
+ checkMfa as checkMfaRedux,
+ updateUserPassword,
+ createUser,
+ login,
+ loadMe as loadMeRedux
+} from 'mattermost-redux/actions/users';
+
+import {getClientConfig, getLicenseConfig} from 'mattermost-redux/actions/general';
+
+export function loadMe(callback) {
+ loadMeRedux()(dispatch, getState).then(
+ () => {
+ localStorage.setItem('currentUserId', UserStore.getCurrentId());
+
+ if (callback) {
+ callback();
+ }
+ }
+ );
+}
+
+export function loadMeAndConfig(callback) {
+ loadMe(() => {
+ getClientConfig()(store.dispatch, store.getState).then(
+ (config) => {
+ global.window.mm_config = config;
+
+ if (global.window && global.window.analytics) {
+ global.window.analytics.identify(global.window.mm_config.DiagnosticId, {}, {
+ context: {
+ ip: '0.0.0.0'
+ },
+ page: {
+ path: '',
+ referrer: '',
+ search: '',
+ title: '',
+ url: ''
+ },
+ anonymousId: '00000000000000000000000000'
+ });
+ }
+
+ getLicenseConfig()(store.dispatch, store.getState).then(
+ (license) => { // eslint-disable-line max-nested-callbacks
+ global.window.mm_license = license;
+
+ if (callback) {
+ callback();
+ }
+ }
+ );
+ }
+ );
+ });
+}
+
export function switchFromLdapToEmail(email, password, token, ldapPassword, onSuccess, onError) {
Client.ldapToEmail(
email,
@@ -38,80 +111,30 @@ export function switchFromLdapToEmail(email, password, token, ldapPassword, onSu
);
}
-export function loadProfilesAndTeamMembers(offset, limit, teamId = TeamStore.getCurrentId(), success, error) {
- Client.getProfilesInTeam(
- teamId,
- offset,
- limit,
+export function loadProfilesAndTeamMembers(page, perPage, teamId = TeamStore.getCurrentId(), success) {
+ getProfilesInTeam(teamId, page, perPage)(dispatch, getState).then(
(data) => {
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_PROFILES_IN_TEAM,
- profiles: data,
- team_id: teamId,
- offset,
- count: Object.keys(data).length
- });
-
- loadTeamMembersForProfilesMap(data, teamId, success, error);
- loadStatusesForProfilesMap(data);
- },
- (err) => {
- AsyncClient.dispatchError(err, 'getProfilesInTeam');
+ loadTeamMembersForProfilesList(data, teamId, success);
+ loadStatusesForProfilesList(data);
}
);
}
-export function loadProfilesAndTeamMembersAndChannelMembers(offset, limit, teamId = TeamStore.getCurrentId(), channelId = ChannelStore.getCurrentId(), success, error) {
- Client.getProfilesInChannel(
- channelId,
- offset,
- limit,
+export function loadProfilesAndTeamMembersAndChannelMembers(page, perPage, teamId = TeamStore.getCurrentId(), channelId = ChannelStore.getCurrentId(), success, error) {
+ getProfilesInChannel(channelId, page, perPage)(dispatch, getState).then(
(data) => {
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_PROFILES_IN_CHANNEL,
- profiles: data,
- channel_id: channelId,
- offset,
- count: Object.keys(data).length
- });
-
- loadTeamMembersForProfilesMap(
+ loadTeamMembersForProfilesList(
data,
teamId,
() => {
- loadChannelMembersForProfilesMap(data, channelId, success, error);
- loadStatusesForProfilesMap(data);
- });
- },
- (err) => {
- AsyncClient.dispatchError(err, 'getProfilesInChannel');
+ loadChannelMembersForProfilesList(data, channelId, success, error);
+ loadStatusesForProfilesList(data);
+ }
+ );
}
);
}
-export function loadTeamMembersForProfilesMap(profiles, teamId = TeamStore.getCurrentId(), success, error) {
- const membersToLoad = {};
- for (const pid in profiles) {
- if (!profiles.hasOwnProperty(pid)) {
- continue;
- }
-
- if (!TeamStore.hasActiveMemberInTeam(teamId, pid)) {
- membersToLoad[pid] = true;
- }
- }
-
- const list = Object.keys(membersToLoad);
- if (list.length === 0) {
- if (success) {
- success({});
- }
- return;
- }
-
- loadTeamMembersForProfiles(list, teamId, success, error);
-}
-
export function loadTeamMembersForProfilesList(profiles, teamId = TeamStore.getCurrentId(), success, error) {
const membersToLoad = {};
for (let i = 0; i < profiles.length; i++) {
@@ -133,24 +156,13 @@ export function loadTeamMembersForProfilesList(profiles, teamId = TeamStore.getC
loadTeamMembersForProfiles(list, teamId, success, error);
}
-export function loadProfilesWithoutTeam(page, perPage, success, error) {
- Client.getProfilesWithoutTeam(
- page,
- perPage,
+export function loadProfilesWithoutTeam(page, perPage, success) {
+ getProfilesWithoutTeam(page, perPage)(dispatch, getState).then(
(data) => {
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_PROFILES_WITHOUT_TEAM,
- profiles: data,
- page
- });
-
loadStatusesForProfilesMap(data);
- },
- (err) => {
- AsyncClient.dispatchError(err, 'getProfilesWithoutTeam');
- if (error) {
- error(err);
+ if (success) {
+ success(data);
}
}
);
@@ -248,9 +260,9 @@ function populateDMChannelsWithProfiles(userIds) {
}
}
-function populateChannelWithProfiles(channelId, userIds) {
- for (let i = 0; i < userIds.length; i++) {
- UserStore.saveUserIdInChannel(channelId, userIds[i]);
+function populateChannelWithProfiles(channelId, users) {
+ for (let i = 0; i < users.length; i++) {
+ UserStore.saveUserIdInChannel(channelId, users[i].id);
}
UserStore.emitInChannelChange();
}
@@ -360,17 +372,9 @@ export function loadProfilesForGM() {
});
}
- Client.getProfilesInChannel(
- channel.id,
- 0,
- Constants.MAX_USERS_IN_GM,
+ getProfilesInChannel(channel.id, 0, Constants.MAX_USERS_IN_GM)(dispatch, getState).then(
(data) => {
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_PROFILES,
- profiles: data
- });
-
- populateChannelWithProfiles(channel.id, Object.keys(data));
+ populateChannelWithProfiles(channel.id, data);
}
);
}
@@ -420,20 +424,10 @@ export function loadProfilesForDM() {
}
if (profilesToLoad.length > 0) {
- Client.getProfilesByIds(
- profilesToLoad,
- (data) => {
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_PROFILES,
- profiles: data
- });
-
- // Use membersToLoad so we get all the DM profiles even if they were already loaded
+ getProfilesByIds(profilesToLoad)(dispatch, getState).then(
+ () => {
populateDMChannelsWithProfiles(profileIds);
},
- (err) => {
- AsyncClient.dispatchError(err, 'getProfilesByIds');
- }
);
} else {
populateDMChannelsWithProfiles(profileIds);
@@ -491,119 +485,70 @@ function onThemeSaved(teamId, theme, onSuccess) {
onSuccess();
}
-export function searchUsers(term, teamId = TeamStore.getCurrentId(), options = {}, success, error) {
- Client.searchUsers(
- term,
- teamId,
- options,
+export function searchUsers(term, teamId = TeamStore.getCurrentId(), options = {}, success) {
+ searchProfiles(term, {team_id: teamId, ...options})(dispatch, getState).then(
(data) => {
loadStatusesForProfilesList(data);
if (success) {
success(data);
}
- },
- (err) => {
- AsyncClient.dispatchError(err, 'searchUsers');
-
- if (error) {
- error(err);
- }
}
);
}
-export function searchUsersNotInTeam(term, teamId = TeamStore.getCurrentId(), options = {}, success, error) {
- Client.searchUsersNotInTeam(
- term,
- teamId,
- options,
+export function searchUsersNotInTeam(term, teamId = TeamStore.getCurrentId(), options = {}, success) {
+ searchProfiles(term, {not_in_team_id: teamId, ...options})(dispatch, getState).then(
(data) => {
loadStatusesForProfilesList(data);
if (success) {
success(data);
}
- },
- (err) => {
- AsyncClient.dispatchError(err, 'searchUsersNotInTeam');
-
- if (error) {
- error(err);
- }
}
);
}
-export function autocompleteUsersInChannel(username, channelId, success, error) {
- Client.autocompleteUsersInChannel(
- username,
- channelId,
+export function autocompleteUsersInChannel(username, channelId, success) {
+ const channel = ChannelStore.get(channelId);
+ const teamId = channel ? channel.team_id : TeamStore.getCurrentId();
+ autocompleteRedux(username, teamId, channelId)(dispatch, getState).then(
(data) => {
if (success) {
success(data);
}
- },
- (err) => {
- AsyncClient.dispatchError(err, 'autocompleteUsersInChannel');
-
- if (error) {
- error(err);
- }
}
);
}
-export function autocompleteUsersInTeam(username, success, error) {
- Client.autocompleteUsersInTeam(
- username,
+export function autocompleteUsersInTeam(username, success) {
+ autocompleteRedux(username, TeamStore.getCurrentId())(dispatch, getState).then(
(data) => {
if (success) {
success(data);
}
- },
- (err) => {
- AsyncClient.dispatchError(err, 'autocompleteUsersInTeam');
-
- if (error) {
- error(err);
- }
}
);
}
-export function autocompleteUsers(username, success, error) {
- Client.autocompleteUsers(
- username,
+export function autocompleteUsers(username, success) {
+ autocompleteRedux(username)(dispatch, getState).then(
(data) => {
if (success) {
success(data);
}
- },
- (err) => {
- AsyncClient.dispatchError(err, 'autocompleteUsers');
-
- if (error) {
- error(err);
- }
}
);
}
-export function updateUser(username, type, success, error) {
- Client.updateUser(
- username,
- type,
+export function updateUser(user, type, success, error) {
+ updateMe(user)(dispatch, getState).then(
(data) => {
- if (success) {
+ if (data && success) {
success(data);
- }
- },
- (err) => {
- if (error) {
- error(err);
- } else {
- AsyncClient.dispatchError(err, 'updateUser');
+ } else if (data == null && error) {
+ const serverError = getState().requests.users.updateUser.error;
+ error({id: serverError.server_error_id, ...serverError});
}
}
);
@@ -626,74 +571,55 @@ export function generateMfaSecret(success, error) {
);
}
-export function updateUserNotifyProps(data, success, error) {
- Client.updateUserNotifyProps(
- data,
- () => {
- AsyncClient.getMe();
-
- if (success) {
- success();
- }
- },
- (err) => {
- if (error) {
- error(err);
- }
- }
+export function updateUserNotifyProps(props, success, error) {
+ updateMe({notify_props: props})(dispatch, getState).then(
+ (data) => {
+ if (data && success) {
+ success(data);
+ } else if (data == null && error) {
+ const serverError = getState().requests.users.updateMe.error;
+ error({id: serverError.server_error_id, ...serverError});
+ }
+ }
);
}
export function updateUserRoles(userId, newRoles, success, error) {
- Client.updateUserRoles(
- userId,
- newRoles,
- () => {
- AsyncClient.getUser(
- userId,
- success,
- error
- );
- },
- error
+ updateUserRoles(userId, newRoles)(dispatch, getState).then(
+ (data) => {
+ if (data && success) {
+ success(data);
+ } else if (data == null && error) {
+ const serverError = getState().requests.users.updateUser.error;
+ error({id: serverError.server_error_id, ...serverError});
+ }
+ }
);
}
export function activateMfa(code, success, error) {
- Client.updateMfa(
- code,
- true,
- () => {
- AsyncClient.getMe();
-
- if (success) {
- success();
+ updateUserMfa(UserStore.getCurrentId(), true, code)(dispatch, getState).then(
+ (data) => {
+ if (data && success) {
+ success(data);
+ } else if (data == null && error) {
+ const serverError = getState().requests.users.updateUser.error;
+ error({id: serverError.server_error_id, ...serverError});
}
},
- (err) => {
- if (error) {
- error(err);
- }
- }
);
}
export function deactivateMfa(success, error) {
- Client.updateMfa(
- '',
- false,
- () => {
- AsyncClient.getMe();
-
- if (success) {
- success();
+ updateUserMfa(UserStore.getCurrentId(), false)(dispatch, getState).then(
+ (data) => {
+ if (data && success) {
+ success(data);
+ } else if (data == null && error) {
+ const serverError = getState().requests.users.updateUser.error;
+ error({id: serverError.server_error_id, ...serverError});
}
},
- (err) => {
- if (error) {
- error(err);
- }
- }
);
}
@@ -703,16 +629,13 @@ export function checkMfa(loginId, success, error) {
return;
}
- Client.checkMfa(
- loginId,
+ checkMfaRedux(loginId)(dispatch, getState).then(
(data) => {
- if (success) {
- success(data && data.mfa_required === 'true');
- }
- },
- (err) => {
- if (error) {
- error(err);
+ if (data && success) {
+ success(data);
+ } else if (data == null && error) {
+ const serverError = getState().requests.users.checkMfa.error;
+ error({id: serverError.server_error_id, ...serverError});
}
}
);
@@ -735,15 +658,13 @@ export function updateActive(userId, active, success, error) {
}
export function updatePassword(userId, currentPassword, newPassword, success, error) {
- Client.updatePassword(userId, currentPassword, newPassword,
- () => {
- if (success) {
- success();
- }
- },
- (err) => {
- if (error) {
- error(err);
+ updateUserPassword(userId, currentPassword, newPassword)(dispatch, getState).then(
+ (data) => {
+ if (data && success) {
+ success(data);
+ } else if (data == null && error) {
+ const serverError = getState().requests.users.updateUser.error;
+ error({id: serverError.server_error_id, ...serverError});
}
}
);
@@ -820,37 +741,27 @@ export function loginById(userId, password, mfaToken, success, error) {
}
export function createUserWithInvite(user, data, emailHash, inviteId, success, error) {
- Client.createUserWithInvite(
- user,
- data,
- emailHash,
- inviteId,
- (response) => {
- if (success) {
- success(response);
- }
- },
- (err) => {
- if (error) {
- error(err);
+ createUser(user, data, emailHash, inviteId)(dispatch, getState).then(
+ (resp) => {
+ if (resp && success) {
+ success(resp);
+ } else if (resp == null && error) {
+ const serverError = getState().requests.users.create.error;
+ error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function webLogin(loginId, password, token, success, error) {
- Client.webLogin(
- loginId,
- password,
- token,
- () => {
- if (success) {
+ login(loginId, password, token)(dispatch, getState).then(
+ (ok) => {
+ if (ok && success) {
+ localStorage.setItem('currentUserId', UserStore.getCurrentId());
success();
- }
- },
- (err) => {
- if (error) {
- error(err);
+ } else if (!ok && error) {
+ const serverError = getState().requests.users.login.error;
+ error({id: serverError.server_error_id, ...serverError});
}
}
);
@@ -907,7 +818,7 @@ export function uploadProfileImage(userPicture, success, error) {
Client.uploadProfileImage(
userPicture,
() => {
- AsyncClient.getMe();
+ getMe()(dispatch, getState);
if (success) {
success();
}
@@ -920,38 +831,24 @@ export function uploadProfileImage(userPicture, success, error) {
);
}
-export function loadProfiles(offset = UserStore.getPagingOffset(), limit = Constants.PROFILE_CHUNK_SIZE, success, error) {
- Client.getProfiles(
- offset,
- limit,
+export function loadProfiles(page, perPage, success) {
+ getProfiles(page, perPage)(dispatch, getState).then(
(data) => {
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_PROFILES,
- profiles: data
- });
-
if (success) {
success(data);
}
- },
- (err) => {
- AsyncClient.dispatchError(err, 'getProfiles');
-
- if (error) {
- error(err);
- }
}
);
}
-export function getMissingProfiles(ids, success, error) {
+export function getMissingProfiles(ids) {
const missingIds = ids.filter((id) => !UserStore.hasProfile(id));
if (missingIds.length === 0) {
return;
}
- AsyncClient.getProfilesByIds(missingIds, success, error);
+ getProfilesByIds(missingIds)(dispatch, getState);
}
export function loadMyTeamMembers() {
diff --git a/webapp/actions/websocket_actions.jsx b/webapp/actions/websocket_actions.jsx
index 41e1c8f4b..bd220947a 100644
--- a/webapp/actions/websocket_actions.jsx
+++ b/webapp/actions/websocket_actions.jsx
@@ -338,7 +338,6 @@ function handleUserUpdatedEvent(msg) {
const user = msg.data.user;
if (UserStore.getCurrentId() !== user.id) {
UserStore.saveProfile(user);
- UserStore.emitChange(user.id);
}
}
diff --git a/webapp/components/access_history_modal.jsx b/webapp/components/access_history_modal/access_history_modal.jsx
index 25c7ef380..da03fdb5b 100644
--- a/webapp/components/access_history_modal.jsx
+++ b/webapp/components/access_history_modal/access_history_modal.jsx
@@ -1,12 +1,11 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import LoadingScreen from './loading_screen.jsx';
-import AuditTable from './audit_table.jsx';
+import LoadingScreen from 'components/loading_screen.jsx';
+import AuditTable from 'components/audit_table.jsx';
import UserStore from 'stores/user_store.jsx';
-import * as AsyncClient from 'utils/async_client.jsx';
import * as Utils from 'utils/utils.jsx';
import $ from 'jquery';
@@ -16,6 +15,13 @@ import {Modal} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
export default class AccessHistoryModal extends React.Component {
+ static propTypes = {
+ onHide: React.PropTypes.func.isRequired,
+ actions: React.PropTypes.shape({
+ getUserAudits: React.PropTypes.func.isRequired
+ }).isRequired
+ }
+
constructor(props) {
super(props);
@@ -37,7 +43,7 @@ export default class AccessHistoryModal extends React.Component {
}
onShow() {
- AsyncClient.getAudits();
+ this.props.actions.getUserAudits(UserStore.getCurrentId(), 0, 200);
if (!Utils.isMobile()) {
$('.modal-body').perfectScrollbar();
}
@@ -100,7 +106,3 @@ export default class AccessHistoryModal extends React.Component {
);
}
}
-
-AccessHistoryModal.propTypes = {
- onHide: React.PropTypes.func.isRequired
-};
diff --git a/webapp/components/access_history_modal/index.js b/webapp/components/access_history_modal/index.js
new file mode 100644
index 000000000..4842ca730
--- /dev/null
+++ b/webapp/components/access_history_modal/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 {getUserAudits} from 'mattermost-redux/actions/users';
+
+import AccessHistoryModal from './access_history_modal.jsx';
+
+function mapStateToProps(state, ownProps) {
+ return {
+ ...ownProps
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ actions: bindActionCreators({
+ getUserAudits
+ }, dispatch)
+ };
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(AccessHistoryModal);
diff --git a/webapp/components/activity_log_modal.jsx b/webapp/components/activity_log_modal/activity_log_modal.jsx
index 8890a1d19..c94909754 100644
--- a/webapp/components/activity_log_modal.jsx
+++ b/webapp/components/activity_log_modal/activity_log_modal.jsx
@@ -1,11 +1,10 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import LoadingScreen from './loading_screen.jsx';
+import LoadingScreen from 'components/loading_screen.jsx';
import UserStore from 'stores/user_store.jsx';
-import * as AsyncClient from 'utils/async_client.jsx';
import * as Utils from 'utils/utils.jsx';
import $ from 'jquery';
@@ -13,9 +12,15 @@ import React from 'react';
import {Modal} from 'react-bootstrap';
import {FormattedMessage, FormattedTime, FormattedDate} from 'react-intl';
-import {revokeSession} from 'actions/admin_actions.jsx';
-
export default class ActivityLogModal extends React.Component {
+ static propTypes = {
+ onHide: React.PropTypes.func.isRequired,
+ actions: React.PropTypes.shape({
+ getSessions: React.PropTypes.func.isRequired,
+ revokeSession: React.PropTypes.func.isRequired
+ }).isRequired
+ }
+
constructor(props) {
super(props);
@@ -35,7 +40,6 @@ export default class ActivityLogModal extends React.Component {
getStateFromStores() {
return {
sessions: UserStore.getSessions(),
- serverError: null,
clientError: null
};
}
@@ -47,18 +51,11 @@ export default class ActivityLogModal extends React.Component {
setTimeout(() => {
modalContent.removeClass('animation--highlight');
}, 1500);
- revokeSession(altId,
- null,
- (err) => {
- const state = this.getStateFromStores();
- state.serverError = err;
- this.setState(state);
- }
- );
+ this.props.actions.revokeSession(UserStore.getCurrentId(), altId);
}
onShow() {
- AsyncClient.getSessions();
+ this.props.actions.getSessions(UserStore.getCurrentId());
if (!Utils.isMobile()) {
$('.modal-body').perfectScrollbar();
}
@@ -302,7 +299,3 @@ export default class ActivityLogModal extends React.Component {
);
}
}
-
-ActivityLogModal.propTypes = {
- onHide: React.PropTypes.func.isRequired
-};
diff --git a/webapp/components/activity_log_modal/index.js b/webapp/components/activity_log_modal/index.js
new file mode 100644
index 000000000..1c4890c65
--- /dev/null
+++ b/webapp/components/activity_log_modal/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 {revokeSession, getSessions} from 'mattermost-redux/actions/users';
+
+import ActivityLogModal from './activity_log_modal.jsx';
+
+function mapStateToProps(state, ownProps) {
+ return {
+ ...ownProps
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ actions: bindActionCreators({
+ getSessions,
+ revokeSession
+ }, dispatch)
+ };
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(ActivityLogModal);
diff --git a/webapp/components/add_users_to_team.jsx b/webapp/components/add_users_to_team/add_users_to_team.jsx
index f0936c0d7..ae6fd8c4e 100644
--- a/webapp/components/add_users_to_team.jsx
+++ b/webapp/components/add_users_to_team/add_users_to_team.jsx
@@ -10,7 +10,6 @@ import {searchUsersNotInTeam} from 'actions/user_actions.jsx';
import UserStore from 'stores/user_store.jsx';
import TeamStore from 'stores/team_store.jsx';
-import * as AsyncClient from 'utils/async_client.jsx';
import Constants from 'utils/constants.jsx';
import {displayUsernameForUser} from 'utils/utils.jsx';
import Client from 'client/web_client.jsx';
@@ -20,10 +19,20 @@ import {Modal} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
import {browserHistory} from 'react-router/es6';
+import store from 'stores/redux_store.jsx';
+import {searchProfilesNotInCurrentTeam} from 'mattermost-redux/selectors/entities/users';
+
const USERS_PER_PAGE = 50;
const MAX_SELECTABLE_VALUES = 20;
export default class AddUsersToTeam extends React.Component {
+ static propTypes = {
+ onModalDismissed: React.PropTypes.func,
+ actions: React.PropTypes.shape({
+ getProfilesNotInTeam: React.PropTypes.func.isRequired
+ }).isRequired
+ }
+
constructor(props) {
super(props);
@@ -34,11 +43,12 @@ export default class AddUsersToTeam extends React.Component {
this.onChange = this.onChange.bind(this);
this.search = this.search.bind(this);
this.addValue = this.addValue.bind(this);
+ this.handlePageChange = this.handlePageChange.bind(this);
this.searchTimeoutId = 0;
this.state = {
- users: null,
+ users: Object.assign([], UserStore.getProfileListNotInTeam(TeamStore.getCurrentId(), true)),
values: [],
show: true,
search: false
@@ -50,7 +60,7 @@ export default class AddUsersToTeam extends React.Component {
UserStore.addNotInTeamChangeListener(this.onChange);
UserStore.addStatusesChangeListener(this.onChange);
- AsyncClient.getProfilesNotInTeam(TeamStore.getCurrentId(), 0, USERS_PER_PAGE * 2);
+ this.props.actions.getProfilesNotInTeam(TeamStore.getCurrentId(), 0, USERS_PER_PAGE * 2);
}
componentWillUnmount() {
@@ -97,13 +107,14 @@ export default class AddUsersToTeam extends React.Component {
this.setState({values});
}
- onChange(force) {
- if (this.state.search && !force) {
- return;
+ onChange() {
+ let users;
+ if (this.term) {
+ users = Object.assign([], searchProfilesNotInCurrentTeam(store.getState(), this.term, true));
+ } else {
+ users = Object.assign([], UserStore.getProfileListNotInTeam(TeamStore.getCurrentId(), true));
}
- const users = Object.assign([], UserStore.getProfileListNotInTeam(TeamStore.getCurrentId(), true));
-
for (let i = 0; i < users.length; i++) {
const user = Object.assign({}, users[i]);
user.value = user.id;
@@ -118,53 +129,25 @@ export default class AddUsersToTeam extends React.Component {
handlePageChange(page, prevPage) {
if (page > prevPage) {
- AsyncClient.getProfilesNotInTeam((page + 1) * USERS_PER_PAGE, USERS_PER_PAGE);
+ this.props.actions.getProfilesNotInTeam(TeamStore.getCurrentId(), page + 1, USERS_PER_PAGE);
}
}
search(term) {
clearTimeout(this.searchTimeoutId);
+ this.term = term;
if (term === '') {
- this.onChange(true);
- this.setState({search: false});
- this.searchTimeoutId = '';
+ this.onChange();
return;
}
- const teamId = TeamStore.getCurrentId();
-
- const searchTimeoutId = setTimeout(
+ this.searchTimeoutId = setTimeout(
() => {
- searchUsersNotInTeam(
- term,
- teamId,
- {},
- (users) => {
- if (searchTimeoutId !== this.searchTimeoutId) {
- return;
- }
-
- let indexToDelete = -1;
- for (let i = 0; i < users.length; i++) {
- if (users[i].id === UserStore.getCurrentId()) {
- indexToDelete = i;
- }
- users[i].value = users[i].id;
- users[i].label = '@' + users[i].username;
- }
-
- if (indexToDelete !== -1) {
- users.splice(indexToDelete, 1);
- }
- this.setState({search: true, users});
- }
- );
+ searchUsersNotInTeam(term, TeamStore.getCurrentId(), {});
},
Constants.SEARCH_TIMEOUT_MILLISECONDS
);
-
- this.searchTimeoutId = searchTimeoutId;
}
handleDelete(values) {
@@ -272,7 +255,3 @@ export default class AddUsersToTeam extends React.Component {
);
}
}
-
-AddUsersToTeam.propTypes = {
- onModalDismissed: React.PropTypes.func
-};
diff --git a/webapp/components/add_users_to_team/index.js b/webapp/components/add_users_to_team/index.js
new file mode 100644
index 000000000..d38aeb4e5
--- /dev/null
+++ b/webapp/components/add_users_to_team/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 {getProfilesNotInTeam} from 'mattermost-redux/actions/users';
+
+import AddUsersToTeam from './add_users_to_team.jsx';
+
+function mapStateToProps(state, ownProps) {
+ return {
+ ...ownProps
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ actions: bindActionCreators({
+ getProfilesNotInTeam
+ }, dispatch)
+ };
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(AddUsersToTeam);
diff --git a/webapp/components/admin_console/system_users/system_users.jsx b/webapp/components/admin_console/system_users/system_users.jsx
index 0b967dead..7bc4b81ed 100644
--- a/webapp/components/admin_console/system_users/system_users.jsx
+++ b/webapp/components/admin_console/system_users/system_users.jsx
@@ -23,6 +23,9 @@ import * as Utils from 'utils/utils.jsx';
import SystemUsersList from './system_users_list.jsx';
+import store from 'stores/redux_store.jsx';
+import {searchProfiles, searchProfilesInTeam} from 'mattermost-redux/selectors/entities/users';
+
const ALL_USERS = '';
const NO_TEAM = 'no_team';
@@ -120,25 +123,18 @@ export default class SystemUsers extends React.Component {
updateUsersFromStore(teamId = this.state.teamId, term = this.state.term) {
if (term) {
- if (teamId === this.state.teamId) {
- // Search results aren't in the store, so manually update the users in them
- const users = [...this.state.users];
-
- for (let i = 0; i < users.length; i++) {
- const user = users[i];
-
- if (UserStore.hasProfile(user.id)) {
- users[i] = UserStore.getProfile(user.id);
- }
- }
-
- this.setState({
- users
- });
+ let users;
+ if (teamId) {
+ users = searchProfilesInTeam(store.getState(), teamId, term);
} else {
- this.doSearch(teamId, term, true);
+ users = searchProfiles(store.getState(), term);
}
+ if (users.length === 0 && UserStore.hasProfile(term)) {
+ users = [UserStore.getProfile(term)];
+ }
+
+ this.setState({users});
return;
}
@@ -179,11 +175,11 @@ export default class SystemUsers extends React.Component {
// Paging isn't supported while searching
if (this.state.teamId === ALL_USERS) {
- loadProfiles((page + 1) * USERS_PER_PAGE, USERS_PER_PAGE, this.loadComplete);
+ loadProfiles(page, USERS_PER_PAGE, this.loadComplete);
} else if (this.state.teamId === NO_TEAM) {
loadProfilesWithoutTeam(page + 1, USERS_PER_PAGE, this.loadComplete);
} else {
- loadProfilesAndTeamMembers((page + 1) * USERS_PER_PAGE, USERS_PER_PAGE, this.state.teamId, this.loadComplete);
+ loadProfilesAndTeamMembers(page + 1, USERS_PER_PAGE, this.state.teamId, this.loadComplete);
}
}
@@ -204,11 +200,9 @@ export default class SystemUsers extends React.Component {
doSearch(teamId, term, now = false) {
clearTimeout(this.searchTimeoutId);
+ this.term = term;
- this.setState({
- loading: true,
- users: []
- });
+ this.setState({loading: true});
const options = {
[UserSearchOptions.ALLOW_INACTIVE]: true
@@ -217,74 +211,45 @@ export default class SystemUsers extends React.Component {
options[UserSearchOptions.WITHOUT_TEAM] = true;
}
- const searchTimeoutId = setTimeout(
+ this.searchTimeoutId = setTimeout(
() => {
searchUsers(
term,
teamId,
options,
(users) => {
- if (searchTimeoutId !== this.searchTimeoutId) {
- return;
- }
-
- if (users.length > 0) {
- this.setState({
- loading: false,
- users
- });
- } else if (term.length === USER_ID_LENGTH) {
+ if (users.length === 0 && term.length === USER_ID_LENGTH) {
// This term didn't match any users name, but it does look like it might be a user's ID
- this.getUserById(term, searchTimeoutId);
+ this.getUserById(term);
} else {
- this.setState({
- loading: false
- });
+ this.setState({loading: false});
}
},
() => {
- this.setState({
- loading: false
- });
+ this.setState({loading: false});
}
);
},
now ? 0 : Constants.SEARCH_TIMEOUT_MILLISECONDS
);
-
- this.searchTimeoutId = searchTimeoutId;
}
- getUserById(id, searchTimeoutId) {
+ getUserById(id) {
if (UserStore.hasProfile(id)) {
- this.setState({
- loading: false,
- users: [UserStore.getProfile(id)]
- });
-
+ this.setState({loading: false});
return;
}
getUser(
id,
- (user) => {
- if (searchTimeoutId !== this.searchTimeoutId) {
- return;
- }
-
+ () => {
this.setState({
- loading: false,
- users: [user]
+ loading: false
});
},
() => {
- if (searchTimeoutId !== this.searchTimeoutId) {
- return;
- }
-
this.setState({
- loading: false,
- users: []
+ loading: false
});
}
);
diff --git a/webapp/components/channel_header.jsx b/webapp/components/channel_header.jsx
index 5ebe1b745..82864d48c 100644
--- a/webapp/components/channel_header.jsx
+++ b/webapp/components/channel_header.jsx
@@ -5,11 +5,11 @@ import $ from 'jquery';
import 'bootstrap';
import NavbarSearchBox from './search_bar.jsx';
import MessageWrapper from './message_wrapper.jsx';
-import PopoverListMembers from './popover_list_members.jsx';
+import PopoverListMembers from 'components/popover_list_members';
import EditChannelHeaderModal from './edit_channel_header_modal.jsx';
import EditChannelPurposeModal from './edit_channel_purpose_modal.jsx';
import ChannelInfoModal from './channel_info_modal.jsx';
-import ChannelInviteModal from './channel_invite_modal.jsx';
+import ChannelInviteModal from 'components/channel_invite_modal';
import ChannelMembersModal from './channel_members_modal.jsx';
import ChannelNotificationsModal from './channel_notifications_modal.jsx';
import DeleteChannelModal from './delete_channel_modal.jsx';
diff --git a/webapp/components/channel_invite_modal.jsx b/webapp/components/channel_invite_modal/channel_invite_modal.jsx
index d41948a2b..847af16f6 100644
--- a/webapp/components/channel_invite_modal.jsx
+++ b/webapp/components/channel_invite_modal/channel_invite_modal.jsx
@@ -1,9 +1,9 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import ChannelInviteButton from './channel_invite_button.jsx';
+import ChannelInviteButton from 'components/channel_invite_button.jsx';
import SearchableUserList from 'components/searchable_user_list/searchable_user_list_container.jsx';
-import LoadingScreen from './loading_screen.jsx';
+import LoadingScreen from 'components/loading_screen.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import UserStore from 'stores/user_store.jsx';
@@ -19,9 +19,20 @@ import React from 'react';
import {Modal} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
+import store from 'stores/redux_store.jsx';
+import {searchProfilesNotInCurrentChannel} from 'mattermost-redux/selectors/entities/users';
+
const USERS_PER_PAGE = 50;
export default class ChannelInviteModal extends React.Component {
+ static propTypes = {
+ onHide: React.PropTypes.func.isRequired,
+ channel: React.PropTypes.object.isRequired,
+ actions: React.PropTypes.shape({
+ getProfilesNotInChannel: React.PropTypes.func.isRequired
+ }).isRequired
+ }
+
constructor(props) {
super(props);
@@ -39,10 +50,9 @@ export default class ChannelInviteModal extends React.Component {
const teamStats = TeamStore.getCurrentStats();
this.state = {
- users: null,
+ users: UserStore.getProfileListNotInChannel(props.channel.id, true),
total: teamStats.active_member_count - channelStats.member_count,
show: true,
- search: false,
statusChange: false
};
}
@@ -53,7 +63,7 @@ export default class ChannelInviteModal extends React.Component {
UserStore.addNotInChannelChangeListener(this.onChange);
UserStore.addStatusesChangeListener(this.onStatusChange);
- AsyncClient.getProfilesNotInChannel(this.props.channel.id, 0);
+ this.props.actions.getProfilesNotInChannel(TeamStore.getCurrentId(), this.props.channel.id, 0);
AsyncClient.getTeamStats(TeamStore.getCurrentId());
}
@@ -64,17 +74,19 @@ export default class ChannelInviteModal extends React.Component {
UserStore.removeStatusesChangeListener(this.onStatusChange);
}
- onChange(force) {
- if (this.state.search && !force) {
- this.search(this.term);
- return;
+ onChange() {
+ let users;
+ if (this.term) {
+ users = searchProfilesNotInCurrentChannel(store.getState(), this.term, true);
+ } else {
+ users = UserStore.getProfileListNotInChannel(this.props.channel.id, true);
}
const channelStats = ChannelStore.getStats(this.props.channel.id);
const teamStats = TeamStore.getCurrentStats();
this.setState({
- users: UserStore.getProfileListNotInChannel(this.props.channel.id, true),
+ users,
total: teamStats.active_member_count - channelStats.member_count
});
}
@@ -103,40 +115,24 @@ export default class ChannelInviteModal extends React.Component {
}
nextPage(page) {
- AsyncClient.getProfilesNotInChannel(this.props.channel.id, (page + 1) * USERS_PER_PAGE, USERS_PER_PAGE);
+ this.props.actions.getProfilesNotInChannel(TeamStore.getCurrentId(), this.props.channel.id, (page + 1) * USERS_PER_PAGE, USERS_PER_PAGE);
}
search(term) {
clearTimeout(this.searchTimeoutId);
-
this.term = term;
if (term === '') {
- this.onChange(true);
- this.setState({search: false});
- this.searchTimeoutId = '';
+ this.onChange();
return;
}
- const searchTimeoutId = setTimeout(
+ this.searchTimeoutId = setTimeout(
() => {
- searchUsers(
- term,
- TeamStore.getCurrentId(),
- {not_in_channel_id: this.props.channel.id},
- (users) => {
- if (searchTimeoutId !== this.searchTimeoutId) {
- return;
- }
-
- this.setState({search: true, users});
- }
- );
+ searchUsers(term, TeamStore.getCurrentId(), {not_in_channel_id: this.props.channel.id});
},
Constants.SEARCH_TIMEOUT_MILLISECONDS
);
-
- this.searchTimeoutId = searchTimeoutId;
}
render() {
@@ -190,8 +186,3 @@ export default class ChannelInviteModal extends React.Component {
);
}
}
-
-ChannelInviteModal.propTypes = {
- onHide: React.PropTypes.func.isRequired,
- channel: React.PropTypes.object.isRequired
-};
diff --git a/webapp/components/channel_invite_modal/index.js b/webapp/components/channel_invite_modal/index.js
new file mode 100644
index 000000000..c8bdb54f5
--- /dev/null
+++ b/webapp/components/channel_invite_modal/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 {getProfilesNotInChannel} from 'mattermost-redux/actions/users';
+
+import ChannelInviteModal from './channel_invite_modal.jsx';
+
+function mapStateToProps(state, ownProps) {
+ return {
+ ...ownProps
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ actions: bindActionCreators({
+ getProfilesNotInChannel
+ }, dispatch)
+ };
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(ChannelInviteModal);
diff --git a/webapp/components/login/login_controller.jsx b/webapp/components/login/login_controller.jsx
index 482135a5e..34fdc536c 100644
--- a/webapp/components/login/login_controller.jsx
+++ b/webapp/components/login/login_controller.jsx
@@ -209,19 +209,15 @@ export default class LoginController extends React.Component {
}
finishSignin(team) {
- GlobalActions.emitInitialLoad(
- () => {
- const query = this.props.location.query;
- GlobalActions.loadDefaultLocale();
- if (query.redirect_to) {
- browserHistory.push(query.redirect_to);
- } else if (team) {
- browserHistory.push(`/${team.name}`);
- } else {
- GlobalActions.redirectUserToDefaultTeam();
- }
- }
- );
+ const query = this.props.location.query;
+ GlobalActions.loadDefaultLocale();
+ if (query.redirect_to) {
+ browserHistory.push(query.redirect_to);
+ } else if (team) {
+ browserHistory.push(`/${team.name}`);
+ } else {
+ GlobalActions.redirectUserToDefaultTeam();
+ }
}
handleLoginIdChange(e) {
diff --git a/webapp/components/member_list_channel.jsx b/webapp/components/member_list_channel.jsx
index e9eef9fb8..df000c132 100644
--- a/webapp/components/member_list_channel.jsx
+++ b/webapp/components/member_list_channel.jsx
@@ -17,6 +17,9 @@ 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 {
@@ -29,6 +32,7 @@ export default class MemberListChannel extends React.Component {
this.loadComplete = this.loadComplete.bind(this);
this.searchTimeoutId = 0;
+ this.term = '';
const stats = ChannelStore.getCurrentStats();
@@ -37,8 +41,6 @@ export default class MemberListChannel extends React.Component {
teamMembers: Object.assign({}, TeamStore.getMembersInTeam()),
channelMembers: Object.assign({}, ChannelStore.getMembersInChannel()),
total: stats.member_count,
- search: false,
- term: '',
loading: true
};
}
@@ -66,16 +68,16 @@ export default class MemberListChannel extends React.Component {
this.setState({loading: false});
}
- onChange(force) {
- if (this.state.search && !force) {
- return;
- } else if (this.state.search) {
- this.search(this.state.term);
- return;
+ onChange() {
+ let users;
+ if (this.term) {
+ users = searchProfilesInCurrentChannel(store.getState(), this.term);
+ } else {
+ users = UserStore.getProfileListInChannel();
}
this.setState({
- users: UserStore.getProfileListInChannel(),
+ users,
teamMembers: Object.assign({}, TeamStore.getMembersInTeam()),
channelMembers: Object.assign({}, ChannelStore.getMembersInChannel())
});
@@ -87,43 +89,30 @@ export default class MemberListChannel extends React.Component {
}
nextPage(page) {
- loadProfilesAndTeamMembersAndChannelMembers((page + 1) * USERS_PER_PAGE, USERS_PER_PAGE);
+ loadProfilesAndTeamMembersAndChannelMembers(page + 1, USERS_PER_PAGE);
}
search(term) {
clearTimeout(this.searchTimeoutId);
+ this.term = term;
if (term === '') {
- this.setState({
- search: false,
- term,
- users: UserStore.getProfileListInChannel(),
- teamMembers: Object.assign([], TeamStore.getMembersInTeam()),
- channelMembers: Object.assign([], ChannelStore.getMembersInChannel())
- });
+ this.setState({loading: false});
this.searchTimeoutId = '';
+ this.onChange();
return;
}
const searchTimeoutId = setTimeout(
() => {
- searchUsers(
- term,
- TeamStore.getCurrentId(),
- {},
+ searchUsers(term, TeamStore.getCurrentId(), {},
(users) => {
if (searchTimeoutId !== this.searchTimeoutId) {
return;
}
- this.setState({
- loading: true,
- search: true,
- users,
- term,
- teamMembers: Object.assign([], TeamStore.getMembersInTeam()),
- channelMembers: Object.assign([], ChannelStore.getMembersInChannel())
- });
+ this.setState({loading: true});
+
loadTeamMembersAndChannelMembersForProfilesList(users, TeamStore.getCurrentId(), ChannelStore.getCurrentId(), this.loadComplete);
}
);
diff --git a/webapp/components/member_list_team.jsx b/webapp/components/member_list_team.jsx
index 0aa1e6e57..212536dc8 100644
--- a/webapp/components/member_list_team.jsx
+++ b/webapp/components/member_list_team.jsx
@@ -2,7 +2,7 @@
// See License.txt for license information.
import SearchableUserList from 'components/searchable_user_list/searchable_user_list_container.jsx';
-import TeamMembersDropdown from 'components/team_members_dropdown.jsx';
+import TeamMembersDropdown from 'components/team_members_dropdown';
import UserStore from 'stores/user_store.jsx';
import TeamStore from 'stores/team_store.jsx';
@@ -16,6 +16,9 @@ import * as UserAgent from 'utils/user_agent.jsx';
import React from 'react';
+import store from 'stores/redux_store.jsx';
+import {searchProfilesInCurrentTeam} from 'mattermost-redux/selectors/entities/users';
+
const USERS_PER_PAGE = 50;
export default class MemberListTeam extends React.Component {
@@ -29,6 +32,7 @@ export default class MemberListTeam extends React.Component {
this.loadComplete = this.loadComplete.bind(this);
this.searchTimeoutId = 0;
+ this.term = '';
const stats = TeamStore.getCurrentStats();
@@ -36,8 +40,6 @@ export default class MemberListTeam extends React.Component {
users: UserStore.getProfileListInTeam(),
teamMembers: Object.assign([], TeamStore.getMembersInTeam()),
total: stats.total_member_count,
- search: false,
- term: '',
loading: true
};
}
@@ -67,15 +69,15 @@ export default class MemberListTeam extends React.Component {
this.onChange(true);
}
- onChange(force) {
- if (this.state.search && !force) {
- return;
- } else if (this.state.search) {
- this.search(this.state.term);
- return;
+ onChange() {
+ let users;
+ if (this.term) {
+ users = searchProfilesInCurrentTeam(store.getState(), this.term);
+ } else {
+ users = UserStore.getProfileListInTeam();
}
- this.setState({users: UserStore.getProfileListInTeam(), teamMembers: Object.assign([], TeamStore.getMembersInTeam())});
+ this.setState({users, teamMembers: Object.assign([], TeamStore.getMembersInTeam())});
}
onStatsChange() {
@@ -84,15 +86,17 @@ export default class MemberListTeam extends React.Component {
}
nextPage(page) {
- loadProfilesAndTeamMembers((page + 1) * USERS_PER_PAGE, USERS_PER_PAGE);
+ loadProfilesAndTeamMembers(page, USERS_PER_PAGE);
}
search(term) {
clearTimeout(this.searchTimeoutId);
+ this.term = term;
if (term === '') {
- this.setState({search: false, term, users: UserStore.getProfileListInTeam(), teamMembers: Object.assign([], TeamStore.getMembersInTeam())});
+ this.setState({loading: false});
this.searchTimeoutId = '';
+ this.onChange();
return;
}
@@ -106,7 +110,7 @@ export default class MemberListTeam extends React.Component {
if (searchTimeoutId !== this.searchTimeoutId) {
return;
}
- this.setState({loading: true, search: true, users, term, teamMembers: Object.assign([], TeamStore.getMembersInTeam())});
+ this.setState({loading: true});
loadTeamMembersForProfilesList(users, TeamStore.getCurrentId(), this.loadComplete);
}
);
diff --git a/webapp/components/more_direct_channels/index.js b/webapp/components/more_direct_channels/index.js
new file mode 100644
index 000000000..a56f45886
--- /dev/null
+++ b/webapp/components/more_direct_channels/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 {getProfiles, getProfilesInTeam} from 'mattermost-redux/actions/users';
+
+import MoreDirectChannels from './more_direct_channels.jsx';
+
+function mapStateToProps(state, ownProps) {
+ return {
+ ...ownProps
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ actions: bindActionCreators({
+ getProfiles,
+ getProfilesInTeam
+ }, dispatch)
+ };
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(MoreDirectChannels);
diff --git a/webapp/components/more_direct_channels.jsx b/webapp/components/more_direct_channels/more_direct_channels.jsx
index c8a0d22ee..50e2c4e48 100644
--- a/webapp/components/more_direct_channels.jsx
+++ b/webapp/components/more_direct_channels/more_direct_channels.jsx
@@ -10,7 +10,6 @@ import {openDirectChannelToUser, openGroupChannelToUsers} from 'actions/channel_
import UserStore from 'stores/user_store.jsx';
import TeamStore from 'stores/team_store.jsx';
-import * as AsyncClient from 'utils/async_client.jsx';
import Constants from 'utils/constants.jsx';
import {displayUsernameForUser} from 'utils/utils.jsx';
import Client from 'client/web_client.jsx';
@@ -20,10 +19,22 @@ import {Modal} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
import {browserHistory} from 'react-router/es6';
+import store from 'stores/redux_store.jsx';
+import {searchProfiles, searchProfilesInCurrentTeam} from 'mattermost-redux/selectors/entities/users';
+
const USERS_PER_PAGE = 50;
const MAX_SELECTABLE_VALUES = Constants.MAX_USERS_IN_GM - 1;
export default class MoreDirectChannels extends React.Component {
+ static propTypes = {
+ startingUsers: React.PropTypes.arrayOf(React.PropTypes.object),
+ onModalDismissed: React.PropTypes.func,
+ actions: React.PropTypes.shape({
+ getProfiles: React.PropTypes.func.isRequired,
+ getProfilesInTeam: React.PropTypes.func.isRequired
+ }).isRequired
+ }
+
constructor(props) {
super(props);
@@ -36,6 +47,7 @@ export default class MoreDirectChannels extends React.Component {
this.addValue = this.addValue.bind(this);
this.searchTimeoutId = 0;
+ this.term = '';
this.listType = global.window.mm_config.RestrictDirectMessage;
const values = [];
@@ -63,9 +75,9 @@ export default class MoreDirectChannels extends React.Component {
UserStore.addStatusesChangeListener(this.onChange);
if (this.listType === 'any') {
- AsyncClient.getProfiles(0, USERS_PER_PAGE * 2);
+ this.props.actions.getProfiles(0, USERS_PER_PAGE * 2);
} else {
- AsyncClient.getProfilesInTeam(TeamStore.getCurrentId(), 0, USERS_PER_PAGE * 2);
+ this.props.actions.getProfilesInTeam(TeamStore.getCurrentId(), 0, USERS_PER_PAGE * 2);
}
}
@@ -134,13 +146,15 @@ export default class MoreDirectChannels extends React.Component {
this.setState({values});
}
- onChange(force) {
- if (this.state.search && !force) {
- return;
- }
-
+ onChange() {
let users;
- if (this.listType === 'any') {
+ if (this.term) {
+ if (this.listType === 'any') {
+ users = Object.assign([], searchProfiles(store.getState(), this.term, true));
+ } else {
+ users = Object.assign([], searchProfilesInCurrentTeam(store.getState(), this.term, true));
+ }
+ } else if (this.listType === 'any') {
users = Object.assign([], UserStore.getProfileList(true));
} else {
users = Object.assign([], UserStore.getProfileListInTeam(TeamStore.getCurrentId(), true));
@@ -160,17 +174,20 @@ export default class MoreDirectChannels extends React.Component {
handlePageChange(page, prevPage) {
if (page > prevPage) {
- AsyncClient.getProfiles((page + 1) * USERS_PER_PAGE, USERS_PER_PAGE);
+ if (this.listType === 'any') {
+ this.props.actions.getProfiles(page + 1, USERS_PER_PAGE);
+ } else {
+ this.props.actions.getProfilesInTeam(page + 1, USERS_PER_PAGE);
+ }
}
}
search(term) {
clearTimeout(this.searchTimeoutId);
+ this.term = term;
if (term === '') {
- this.onChange(true);
- this.setState({search: false});
- this.searchTimeoutId = '';
+ this.onChange();
return;
}
@@ -181,37 +198,12 @@ export default class MoreDirectChannels extends React.Component {
teamId = TeamStore.getCurrentId();
}
- const searchTimeoutId = setTimeout(
+ this.searchTimeoutId = setTimeout(
() => {
- searchUsers(
- term,
- teamId,
- {},
- (users) => {
- if (searchTimeoutId !== this.searchTimeoutId) {
- return;
- }
-
- let indexToDelete = -1;
- for (let i = 0; i < users.length; i++) {
- if (users[i].id === UserStore.getCurrentId()) {
- indexToDelete = i;
- }
- users[i].value = users[i].id;
- users[i].label = '@' + users[i].username;
- }
-
- if (indexToDelete !== -1) {
- users.splice(indexToDelete, 1);
- }
- this.setState({search: true, users});
- }
- );
+ searchUsers(term, teamId);
},
Constants.SEARCH_TIMEOUT_MILLISECONDS
);
-
- this.searchTimeoutId = searchTimeoutId;
}
handleDelete(values) {
@@ -334,8 +326,3 @@ export default class MoreDirectChannels extends React.Component {
);
}
}
-
-MoreDirectChannels.propTypes = {
- startingUsers: React.PropTypes.arrayOf(React.PropTypes.object),
- onModalDismissed: React.PropTypes.func
-};
diff --git a/webapp/components/navbar.jsx b/webapp/components/navbar.jsx
index 22d2b8ae4..33df0b423 100644
--- a/webapp/components/navbar.jsx
+++ b/webapp/components/navbar.jsx
@@ -7,7 +7,7 @@ import EditChannelPurposeModal from './edit_channel_purpose_modal.jsx';
import MessageWrapper from './message_wrapper.jsx';
import NotifyCounts from './notify_counts.jsx';
import ChannelInfoModal from './channel_info_modal.jsx';
-import ChannelInviteModal from './channel_invite_modal.jsx';
+import ChannelInviteModal from 'components/channel_invite_modal';
import ChannelMembersModal from './channel_members_modal.jsx';
import ChannelNotificationsModal from './channel_notifications_modal.jsx';
import DeleteChannelModal from './delete_channel_modal.jsx';
diff --git a/webapp/components/popover_list_members/index.js b/webapp/components/popover_list_members/index.js
new file mode 100644
index 000000000..3e9087e0d
--- /dev/null
+++ b/webapp/components/popover_list_members/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 {getProfilesInChannel} from 'mattermost-redux/actions/users';
+
+import PopoverListMembers from './popover_list_members.jsx';
+
+function mapStateToProps(state, ownProps) {
+ return {
+ ...ownProps
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ actions: bindActionCreators({
+ getProfilesInChannel
+ }, dispatch)
+ };
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(PopoverListMembers);
diff --git a/webapp/components/popover_list_members.jsx b/webapp/components/popover_list_members/popover_list_members.jsx
index 3af53cb70..e435126ff 100644
--- a/webapp/components/popover_list_members.jsx
+++ b/webapp/components/popover_list_members/popover_list_members.jsx
@@ -7,13 +7,12 @@ import TeamStore from 'stores/team_store.jsx';
import UserStore from 'stores/user_store.jsx';
import ChannelStore from 'stores/channel_store.jsx';
-import TeamMembersModal from './team_members_modal.jsx';
-import ChannelMembersModal from './channel_members_modal.jsx';
-import ChannelInviteModal from './channel_invite_modal.jsx';
+import TeamMembersModal from 'components/team_members_modal.jsx';
+import ChannelMembersModal from 'components/channel_members_modal.jsx';
+import ChannelInviteModal from 'components/channel_invite_modal';
import {openDirectChannelToUser} from 'actions/channel_actions.jsx';
-import * as AsyncClient from 'utils/async_client.jsx';
import Client from 'client/web_client.jsx';
import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
@@ -26,6 +25,15 @@ import {FormattedMessage} from 'react-intl';
import {browserHistory} from 'react-router/es6';
export default class PopoverListMembers extends React.Component {
+ static propTypes = {
+ channel: React.PropTypes.object.isRequired,
+ members: React.PropTypes.array.isRequired,
+ memberCount: React.PropTypes.number,
+ actions: React.PropTypes.shape({
+ getProfilesInChannel: React.PropTypes.func.isRequired
+ }).isRequired
+ }
+
constructor(props) {
super(props);
@@ -239,7 +247,7 @@ export default class PopoverListMembers extends React.Component {
ref='member_popover_target'
onClick={(e) => {
this.setState({popoverTarget: e.target, showPopover: !this.state.showPopover});
- AsyncClient.getProfilesInChannel(this.props.channel.id, 0);
+ this.props.actions.getProfilesInChannel(this.props.channel.id, 0);
}}
>
{countText}
@@ -272,8 +280,3 @@ export default class PopoverListMembers extends React.Component {
}
}
-PopoverListMembers.propTypes = {
- channel: React.PropTypes.object.isRequired,
- members: React.PropTypes.array.isRequired,
- memberCount: React.PropTypes.number
-};
diff --git a/webapp/components/root.jsx b/webapp/components/root.jsx
index b49b4d509..6907e84e4 100644
--- a/webapp/components/root.jsx
+++ b/webapp/components/root.jsx
@@ -136,6 +136,7 @@ export default class Root extends React.Component {
);
}
}
+
Root.defaultProps = {
};
diff --git a/webapp/components/sidebar.jsx b/webapp/components/sidebar.jsx
index 72dcac992..8667802cc 100644
--- a/webapp/components/sidebar.jsx
+++ b/webapp/components/sidebar.jsx
@@ -4,7 +4,7 @@
import $ from 'jquery';
import ReactDOM from 'react-dom';
import NewChannelFlow from './new_channel_flow.jsx';
-import MoreDirectChannels from './more_direct_channels.jsx';
+import MoreDirectChannels from 'components/more_direct_channels';
import MoreChannels from 'components/more_channels.jsx';
import SidebarHeader from './sidebar_header.jsx';
import UnreadChannelIndicator from './unread_channel_indicator.jsx';
diff --git a/webapp/components/sidebar_header_dropdown.jsx b/webapp/components/sidebar_header_dropdown.jsx
index 728017b27..256019b64 100644
--- a/webapp/components/sidebar_header_dropdown.jsx
+++ b/webapp/components/sidebar_header_dropdown.jsx
@@ -14,7 +14,7 @@ import AboutBuildModal from './about_build_modal.jsx';
import SidebarHeaderDropdownButton from './sidebar_header_dropdown_button.jsx';
import TeamMembersModal from './team_members_modal.jsx';
import UserSettingsModal from './user_settings/user_settings_modal.jsx';
-import AddUsersToTeam from './add_users_to_team.jsx';
+import AddUsersToTeam from 'components/add_users_to_team';
import {Constants, WebrtcActionTypes} from 'utils/constants.jsx';
diff --git a/webapp/components/sidebar_right_menu.jsx b/webapp/components/sidebar_right_menu.jsx
index 784f06eac..aac7d58cc 100644
--- a/webapp/components/sidebar_right_menu.jsx
+++ b/webapp/components/sidebar_right_menu.jsx
@@ -6,7 +6,7 @@ import TeamMembersModal from './team_members_modal.jsx';
import ToggleModalButton from './toggle_modal_button.jsx';
import UserSettingsModal from './user_settings/user_settings_modal.jsx';
import AboutBuildModal from './about_build_modal.jsx';
-import AddUsersToTeam from './add_users_to_team.jsx';
+import AddUsersToTeam from 'components/add_users_to_team';
import UserStore from 'stores/user_store.jsx';
import TeamStore from 'stores/team_store.jsx';
diff --git a/webapp/components/signup/components/signup_email.jsx b/webapp/components/signup/components/signup_email.jsx
index c33fb45e0..976b0648b 100644
--- a/webapp/components/signup/components/signup_email.jsx
+++ b/webapp/components/signup/components/signup_email.jsx
@@ -8,7 +8,7 @@ import {trackEvent} from 'actions/diagnostics_actions.jsx';
import BrowserStore from 'stores/browser_store.jsx';
import {getInviteInfo} from 'actions/team_actions.jsx';
-import {loginById, createUserWithInvite} from 'actions/user_actions.jsx';
+import {loadMe, loginById, createUserWithInvite} from 'actions/user_actions.jsx';
import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
@@ -108,7 +108,7 @@ export default class SignupEmail extends React.Component {
}
finishSignup() {
- GlobalActions.emitInitialLoad(
+ loadMe(
() => {
const query = this.props.location.query;
GlobalActions.loadDefaultLocale();
@@ -132,7 +132,7 @@ export default class SignupEmail extends React.Component {
BrowserStore.setGlobalItem(this.state.hash, JSON.stringify({usedBefore: true}));
}
- GlobalActions.emitInitialLoad(
+ loadMe(
() => {
const query = this.props.location.query;
if (query.redirect_to) {
diff --git a/webapp/components/signup/components/signup_ldap.jsx b/webapp/components/signup/components/signup_ldap.jsx
index 80fac3ecc..a101c248f 100644
--- a/webapp/components/signup/components/signup_ldap.jsx
+++ b/webapp/components/signup/components/signup_ldap.jsx
@@ -5,7 +5,7 @@ import FormError from 'components/form_error.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
import {addUserToTeamFromInvite} from 'actions/team_actions.jsx';
-import {webLoginByLdap} from 'actions/user_actions.jsx';
+import {loadMe, webLoginByLdap} from 'actions/user_actions.jsx';
import {trackEvent} from 'actions/diagnostics_actions.jsx';
import * as Utils from 'utils/utils.jsx';
@@ -97,7 +97,7 @@ export default class SignupLdap extends React.Component {
}
finishSignup() {
- GlobalActions.emitInitialLoad(
+ loadMe(
() => {
const query = this.props.location.query;
GlobalActions.loadDefaultLocale();
diff --git a/webapp/components/signup/signup_controller.jsx b/webapp/components/signup/signup_controller.jsx
index 701fe1d30..0c969e5ed 100644
--- a/webapp/components/signup/signup_controller.jsx
+++ b/webapp/components/signup/signup_controller.jsx
@@ -13,6 +13,7 @@ import * as AsyncClient from 'utils/async_client.jsx';
import Client from 'client/web_client.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
import {addUserToTeamFromInvite, getInviteInfo} from 'actions/team_actions.jsx';
+import {loadMe} from 'actions/user_actions.jsx';
import logoImage from 'images/logo.png';
import ErrorBar from 'components/error_bar.jsx';
@@ -74,7 +75,7 @@ export default class SignupController extends React.Component {
hash,
inviteId,
(team) => {
- GlobalActions.emitInitialLoad(
+ loadMe(
() => {
browserHistory.push('/' + team.name + '/channels/town-square');
}
diff --git a/webapp/components/suggestion/at_mention_provider.jsx b/webapp/components/suggestion/at_mention_provider.jsx
index ddf9d46e7..4d55e9db6 100644
--- a/webapp/components/suggestion/at_mention_provider.jsx
+++ b/webapp/components/suggestion/at_mention_provider.jsx
@@ -127,14 +127,14 @@ export default class AtMentionProvider extends Provider {
return;
}
- const members = data.in_channel;
+ const members = Object.assign([], data.users);
for (const id of Object.keys(members)) {
- members[id].type = Constants.MENTION_MEMBERS;
+ members[id] = {...members[id], type: Constants.MENTION_MEMBERS};
}
- const nonmembers = data.out_of_channel;
+ const nonmembers = data.out_of_channel || [];
for (const id of Object.keys(nonmembers)) {
- nonmembers[id].type = Constants.MENTION_NONMEMBERS;
+ nonmembers[id] = {...nonmembers[id], type: Constants.MENTION_NONMEMBERS};
}
let specialMentions = [];
diff --git a/webapp/components/suggestion/search_user_provider.jsx b/webapp/components/suggestion/search_user_provider.jsx
index 6fcc7f7e9..d55f35c87 100644
--- a/webapp/components/suggestion/search_user_provider.jsx
+++ b/webapp/components/suggestion/search_user_provider.jsx
@@ -72,7 +72,7 @@ export default class SearchUserProvider extends Provider {
return;
}
- const users = data.in_team;
+ const users = Object.assign([], data.users);
const mentions = users.map((user) => user.username);
AppDispatcher.handleServerAction({
diff --git a/webapp/components/suggestion/switch_channel_provider.jsx b/webapp/components/suggestion/switch_channel_provider.jsx
index 3d295951c..67cda61ea 100644
--- a/webapp/components/suggestion/switch_channel_provider.jsx
+++ b/webapp/components/suggestion/switch_channel_provider.jsx
@@ -67,7 +67,9 @@ export default class SwitchChannelProvider extends Provider {
autocompleteUsers(
channelPrefix,
- (users) => {
+ (data) => {
+ const users = Object.assign([], data.users);
+
if (this.shouldCancelDispatch(channelPrefix)) {
return;
}
diff --git a/webapp/components/team_members_dropdown/index.js b/webapp/components/team_members_dropdown/index.js
new file mode 100644
index 000000000..54e002a6e
--- /dev/null
+++ b/webapp/components/team_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 {getUser} from 'mattermost-redux/actions/users';
+
+import TeamMembersDropdown from './team_members_dropdown.jsx';
+
+function mapStateToProps(state, ownProps) {
+ return {
+ ...ownProps
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ actions: bindActionCreators({
+ getUser
+ }, dispatch)
+ };
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(TeamMembersDropdown);
diff --git a/webapp/components/team_members_dropdown.jsx b/webapp/components/team_members_dropdown/team_members_dropdown.jsx
index 7c2b763c3..704a60dae 100644
--- a/webapp/components/team_members_dropdown.jsx
+++ b/webapp/components/team_members_dropdown/team_members_dropdown.jsx
@@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import ConfirmModal from './confirm_modal.jsx';
+import ConfirmModal from 'components/confirm_modal.jsx';
import TeamStore from 'stores/team_store.jsx';
import UserStore from 'stores/user_store.jsx';
@@ -18,6 +18,14 @@ import {FormattedMessage} from 'react-intl';
import {browserHistory} from 'react-router/es6';
export default class TeamMembersDropdown extends React.Component {
+ static propTypes = {
+ user: React.PropTypes.object.isRequired,
+ teamMember: React.PropTypes.object.isRequired,
+ actions: React.PropTypes.shape({
+ getUser: React.PropTypes.func.isRequired
+ }).isRequired
+ }
+
constructor(props) {
super(props);
@@ -48,7 +56,7 @@ export default class TeamMembersDropdown extends React.Component {
this.props.user.id,
'team_user',
() => {
- AsyncClient.getUser(this.props.user.id);
+ this.props.actions.getUser(this.props.user.id);
if (this.props.user.id === me.id) {
loadMyTeamMembers();
@@ -110,7 +118,7 @@ export default class TeamMembersDropdown extends React.Component {
this.props.user.id,
'team_user team_admin',
() => {
- AsyncClient.getUser(this.props.user.id);
+ this.props.actions.getUser(this.props.user.id);
},
(err) => {
this.setState({serverError: err.message});
@@ -145,7 +153,7 @@ export default class TeamMembersDropdown extends React.Component {
this.props.user.id,
this.state.newRole,
() => {
- AsyncClient.getUser(this.props.user.id);
+ this.props.actions.getUser(this.props.user.id);
const teamUrl = TeamStore.getCurrentTeamUrl();
if (teamUrl) {
@@ -385,8 +393,3 @@ export default class TeamMembersDropdown extends React.Component {
);
}
}
-
-TeamMembersDropdown.propTypes = {
- user: React.PropTypes.object.isRequired,
- teamMember: React.PropTypes.object.isRequired
-};
diff --git a/webapp/components/user_settings/user_settings.jsx b/webapp/components/user_settings/user_settings.jsx
index d9d5423fe..b01274b32 100644
--- a/webapp/components/user_settings/user_settings.jsx
+++ b/webapp/components/user_settings/user_settings.jsx
@@ -4,8 +4,8 @@
import UserStore from 'stores/user_store.jsx';
import * as utils from 'utils/utils.jsx';
import NotificationsTab from './user_settings_notifications.jsx';
-import SecurityTab from './user_settings_security.jsx';
-import GeneralTab from './user_settings_general.jsx';
+import SecurityTab from './user_settings_security';
+import GeneralTab from './user_settings_general';
import DisplayTab from './user_settings_display.jsx';
import AdvancedTab from './user_settings_advanced.jsx';
diff --git a/webapp/components/user_settings/user_settings_general/index.js b/webapp/components/user_settings/user_settings_general/index.js
new file mode 100644
index 000000000..90fd58bf2
--- /dev/null
+++ b/webapp/components/user_settings/user_settings_general/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 {getMe} from 'mattermost-redux/actions/users';
+
+import UserSettingsGeneralTab from './user_settings_general.jsx';
+
+function mapStateToProps(state, ownProps) {
+ return {
+ ...ownProps
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ actions: bindActionCreators({
+ getMe
+ }, dispatch)
+ };
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(UserSettingsGeneralTab);
diff --git a/webapp/components/user_settings/user_settings_general.jsx b/webapp/components/user_settings/user_settings_general/user_settings_general.jsx
index ce4349519..79132d929 100644
--- a/webapp/components/user_settings/user_settings_general.jsx
+++ b/webapp/components/user_settings/user_settings_general/user_settings_general.jsx
@@ -2,16 +2,15 @@
// See License.txt for license information.
import $ from 'jquery';
-import SettingItemMin from '../setting_item_min.jsx';
-import SettingItemMax from '../setting_item_max.jsx';
-import SettingPicture from '../setting_picture.jsx';
+import SettingItemMin from 'components/setting_item_min.jsx';
+import SettingItemMax from 'components/setting_item_max.jsx';
+import SettingPicture from 'components/setting_picture.jsx';
import UserStore from 'stores/user_store.jsx';
import ErrorStore from 'stores/error_store.jsx';
import Client from 'client/web_client.jsx';
import Constants from 'utils/constants.jsx';
-import * as AsyncClient from 'utils/async_client.jsx';
import * as Utils from 'utils/utils.jsx';
import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage, FormattedDate} from 'react-intl';
@@ -80,6 +79,19 @@ const holders = defineMessages({
import React from 'react';
class UserSettingsGeneralTab extends React.Component {
+ static propTypes = {
+ intl: intlShape.isRequired,
+ user: React.PropTypes.object.isRequired,
+ updateSection: React.PropTypes.func.isRequired,
+ updateTab: React.PropTypes.func.isRequired,
+ activeSection: React.PropTypes.string.isRequired,
+ closeModal: React.PropTypes.func.isRequired,
+ collapseModal: React.PropTypes.func.isRequired,
+ actions: React.PropTypes.shape({
+ getMe: React.PropTypes.func.isRequired
+ }).isRequired
+ }
+
constructor(props) {
super(props);
this.submitActive = false;
@@ -205,7 +217,7 @@ class UserSettingsGeneralTab extends React.Component {
updateUser(user, type,
() => {
this.updateSection('');
- AsyncClient.getMe();
+ this.props.actions.getMe();
const verificationEnabled = global.window.mm_config.SendEmailNotifications === 'true' && global.window.mm_config.RequireEmailVerification === 'true' && emailUpdated;
if (verificationEnabled) {
@@ -1194,14 +1206,4 @@ class UserSettingsGeneralTab extends React.Component {
}
}
-UserSettingsGeneralTab.propTypes = {
- intl: intlShape.isRequired,
- user: React.PropTypes.object.isRequired,
- updateSection: React.PropTypes.func.isRequired,
- updateTab: React.PropTypes.func.isRequired,
- activeSection: React.PropTypes.string.isRequired,
- closeModal: React.PropTypes.func.isRequired,
- collapseModal: React.PropTypes.func.isRequired
-};
-
export default injectIntl(UserSettingsGeneralTab);
diff --git a/webapp/components/user_settings/user_settings_security/index.js b/webapp/components/user_settings/user_settings_security/index.js
new file mode 100644
index 000000000..cdbabd055
--- /dev/null
+++ b/webapp/components/user_settings/user_settings_security/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 {getMe} from 'mattermost-redux/actions/users';
+
+import SecurityTab from './user_settings_security.jsx';
+
+function mapStateToProps(state, ownProps) {
+ return {
+ ...ownProps
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ actions: bindActionCreators({
+ getMe
+ }, dispatch)
+ };
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(SecurityTab);
diff --git a/webapp/components/user_settings/user_settings_security.jsx b/webapp/components/user_settings/user_settings_security/user_settings_security.jsx
index ead579c19..d4a372454 100644
--- a/webapp/components/user_settings/user_settings_security.jsx
+++ b/webapp/components/user_settings/user_settings_security/user_settings_security.jsx
@@ -1,15 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import SettingItemMin from '../setting_item_min.jsx';
-import SettingItemMax from '../setting_item_max.jsx';
-import AccessHistoryModal from '../access_history_modal.jsx';
-import ActivityLogModal from '../activity_log_modal.jsx';
-import ToggleModalButton from '../toggle_modal_button.jsx';
+import SettingItemMin from 'components/setting_item_min.jsx';
+import SettingItemMax from 'components/setting_item_max.jsx';
+import AccessHistoryModal from 'components/access_history_modal';
+import ActivityLogModal from 'components/activity_log_modal';
+import ToggleModalButton from 'components/toggle_modal_button.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
-import * as AsyncClient from 'utils/async_client.jsx';
import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
@@ -23,6 +22,19 @@ import {browserHistory, Link} from 'react-router/es6';
import icon50 from 'images/icon50x50.png';
export default class SecurityTab extends React.Component {
+ static propTypes = {
+ user: React.PropTypes.object,
+ activeSection: React.PropTypes.string,
+ updateSection: React.PropTypes.func,
+ updateTab: React.PropTypes.func,
+ closeModal: React.PropTypes.func.isRequired,
+ collapseModal: React.PropTypes.func.isRequired,
+ setEnforceFocus: React.PropTypes.func.isRequired,
+ actions: React.PropTypes.shape({
+ getMe: React.PropTypes.func.isRequired
+ }).isRequired
+ }
+
constructor(props) {
super(props);
@@ -98,7 +110,7 @@ export default class SecurityTab extends React.Component {
newPassword,
() => {
this.props.updateSection('');
- AsyncClient.getMe();
+ this.props.actions.getMe();
this.setState(this.getDefaultState());
},
(err) => {
@@ -1022,12 +1034,3 @@ SecurityTab.defaultProps = {
user: {},
activeSection: ''
};
-SecurityTab.propTypes = {
- user: React.PropTypes.object,
- activeSection: React.PropTypes.string,
- updateSection: React.PropTypes.func,
- updateTab: React.PropTypes.func,
- closeModal: React.PropTypes.func.isRequired,
- collapseModal: React.PropTypes.func.isRequired,
- setEnforceFocus: React.PropTypes.func.isRequired
-};
diff --git a/webapp/package.json b/webapp/package.json
index b51eb4958..51f1a37ee 100644
--- a/webapp/package.json
+++ b/webapp/package.json
@@ -19,8 +19,10 @@
"intl": "1.2.5",
"jasny-bootstrap": "3.1.3",
"jquery": "3.1.1",
+ "localforage": "1.5.0",
"marked": "mattermost/marked#8f5902fff9bad793cd6c66e0c44002c9e79e1317",
"match-at": "0.1.0",
+ "mattermost-redux": "mattermost/mattermost-redux#webapp-part2",
"object-assign": "4.1.1",
"pdfjs-dist": "1.7.363",
"perfect-scrollbar": "0.6.16",
@@ -30,19 +32,26 @@
"react-custom-scrollbars": "4.0.2",
"react-dom": "15.4.2",
"react-intl": "2.2.3",
+ "react-redux": "5.0.4",
"react-router": "2.8.1",
"react-select": "1.0.0-rc.3",
+ "redux-batched-actions": "0.1.5",
+ "redux-persist": "4.6.0",
+ "redux-persist-transform-filter": "0.0.9",
"superagent": "3.5.0",
"twemoji": "2.2.5",
"velocity-animate": "1.4.3",
"webrtc-adapter": "3.2.0",
+ "whatwg-fetch": "2.0.3",
"xregexp": "3.1.1"
},
"devDependencies": {
+ "babel-cli": "6.24.1",
"babel-core": "6.24.0",
"babel-eslint": "7.1.1",
"babel-jest": "19.0.0",
"babel-loader": "6.4.0",
+ "babel-plugin-module-resolver": "2.7.0",
"babel-plugin-transform-runtime": "6.23.0",
"babel-polyfill": "6.23.0",
"babel-preset-es2015": "6.24.0",
@@ -73,9 +82,11 @@
"raw-loader": "0.5.1",
"react-addons-test-utils": "15.4.2",
"react-dom": "15.4.2",
+ "react-outside-event": "1.2.4",
+ "remote-redux-devtools": "0.5.7",
+ "remote-redux-devtools-on-debugger": "0.7.0",
"sass-loader": "6.0.3",
"style-loader": "0.13.2",
- "react-outside-event": "1.2.4",
"url-loader": "0.5.8",
"webpack": "2.2.1",
"webpack-node-externals": "1.5.4"
diff --git a/webapp/root.jsx b/webapp/root.jsx
index 177eb1ec4..b2da6a54c 100644
--- a/webapp/root.jsx
+++ b/webapp/root.jsx
@@ -6,22 +6,28 @@ require('perfect-scrollbar/jquery')($);
import React from 'react';
import ReactDOM from 'react-dom';
+import {Provider} from 'react-redux';
import {Router, browserHistory} from 'react-router/es6';
import PDFJS from 'pdfjs-dist';
-import * as GlobalActions from 'actions/global_actions.jsx';
+
import * as Websockets from 'actions/websocket_actions.jsx';
+import {loadMeAndConfig} from 'actions/user_actions.jsx';
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';
import 'google-fonts/google-fonts.css';
import 'sass/styles.scss';
import 'katex/dist/katex.min.css';
+import store from 'stores/redux_store.jsx';
+
// Import the root of our routing tree
import rRoot from 'routes/route_root.jsx';
@@ -51,11 +57,26 @@ function preRenderSetup(callwhendone) {
var d1 = $.Deferred(); //eslint-disable-line new-cap
- GlobalActions.emitInitialLoad(
- () => {
- d1.resolve();
- }
- );
+ setUrl(window.location.origin);
+
+ const currentUserId = localStorage.getItem('currentUserId');
+
+ if (currentUserId) {
+ loadMeAndConfig(() => d1.resolve());
+ } else {
+ getClientConfig()(store.dispatch, store.getState).then(
+ (config) => {
+ global.window.mm_config = config;
+
+ getLicenseConfig()(store.dispatch, store.getState).then(
+ (license) => {
+ global.window.mm_license = license;
+ d1.resolve();
+ }
+ );
+ }
+ );
+ }
// Make sure the websockets close and reset version
$(window).on('beforeunload',
@@ -86,10 +107,12 @@ function preRenderSetup(callwhendone) {
function renderRootComponent() {
ReactDOM.render((
- <Router
- history={browserHistory}
- routes={rRoot}
- />
+ <Provider store={store}>
+ <Router
+ history={browserHistory}
+ routes={rRoot}
+ />
+ </Provider>
),
document.getElementById('root'));
}
diff --git a/webapp/store/index.js b/webapp/store/index.js
new file mode 100644
index 000000000..1af7127e8
--- /dev/null
+++ b/webapp/store/index.js
@@ -0,0 +1,112 @@
+// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {batchActions} from 'redux-batched-actions';
+import configureServiceStore from 'mattermost-redux/store';
+import {General, RequestStatus} from 'mattermost-redux/constants';
+import reduxInitialState from 'mattermost-redux/store/initial_state';
+import {createTransform, persistStore} from 'redux-persist';
+import localForage from 'localforage';
+
+import {transformSet} from './utils';
+
+const usersSetTransform = [
+ 'profilesInChannel',
+ 'profilesNotInChannel',
+ 'profilesInTeam',
+ 'profilesNotInTeam'
+];
+
+const teamSetTransform = [
+ 'membersInTeam'
+];
+
+const setTransforms = [
+ ...usersSetTransform,
+ ...teamSetTransform
+];
+
+export default function configureStore(initialState) {
+ const setTransformer = createTransform(
+ (inboundState, key) => {
+ if (key === 'entities') {
+ const state = {...inboundState};
+ for (const prop in state) {
+ if (state.hasOwnProperty(prop)) {
+ state[prop] = transformSet(state[prop], setTransforms);
+ }
+ }
+
+ return state;
+ }
+
+ return inboundState;
+ },
+ (outboundState, key) => {
+ if (key === 'entities') {
+ const state = {...outboundState};
+ for (const prop in state) {
+ if (state.hasOwnProperty(prop)) {
+ state[prop] = transformSet(state[prop], setTransforms, false);
+ }
+ }
+
+ return state;
+ }
+
+ return outboundState;
+ }
+ );
+
+ const offlineOptions = {
+ persist: (store, options) => {
+ const persistor = persistStore(store, {storage: localForage, ...options}, () => {
+ store.dispatch({
+ type: General.STORE_REHYDRATION_COMPLETE,
+ complete: true
+ });
+ });
+
+ let purging = false;
+
+ // check to see if the logout request was successful
+ store.subscribe(() => {
+ const state = store.getState();
+ if (state.requests.users.logout.status === RequestStatus.SUCCESS && !purging) {
+ purging = true;
+
+ persistor.purge();
+
+ store.dispatch(batchActions([
+ {
+ type: General.OFFLINE_STORE_RESET,
+ data: Object.assign({}, reduxInitialState, initialState)
+ }
+ ]));
+
+ localStorage.removeItem('currentUserId');
+ window.location.href = '/';
+
+ setTimeout(() => {
+ purging = false;
+ }, 500);
+ }
+ });
+
+ return persistor;
+ },
+ persistOptions: {
+ autoRehydrate: {
+ log: false
+ },
+ blacklist: ['errors', 'offline', 'requests', 'entities'],
+ debounce: 500,
+ transforms: [
+ setTransformer
+ ]
+ }
+ };
+
+ return configureServiceStore({}, {}, offlineOptions);
+}
+
diff --git a/webapp/store/utils.js b/webapp/store/utils.js
new file mode 100644
index 000000000..5566f54b8
--- /dev/null
+++ b/webapp/store/utils.js
@@ -0,0 +1,42 @@
+// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+function transformFromSet(incoming) {
+ const state = {...incoming};
+
+ for (const key in state) {
+ if (state.hasOwnProperty(key)) {
+ if (state[key] instanceof Set) {
+ state[key] = Array.from([...state[key]]);
+ }
+ }
+ }
+
+ return state;
+}
+
+function transformToSet(incoming) {
+ const state = {...incoming};
+
+ for (const key in state) {
+ if (state.hasOwnProperty(key)) {
+ state[key] = new Set(state[key]);
+ }
+ }
+
+ return state;
+}
+
+export function transformSet(incoming, setTransforms, toStorage = true) {
+ const state = {...incoming};
+
+ const transformer = toStorage ? transformFromSet : transformToSet;
+
+ for (const key in state) {
+ if (state.hasOwnProperty(key) && setTransforms.includes(key)) {
+ state[key] = transformer(state[key]);
+ }
+ }
+
+ return state;
+}
diff --git a/webapp/stores/preference_store.jsx b/webapp/stores/preference_store.jsx
index 7a286f7a2..f3476d9ea 100644
--- a/webapp/stores/preference_store.jsx
+++ b/webapp/stores/preference_store.jsx
@@ -8,6 +8,10 @@ import EventEmitter from 'events';
const CHANGE_EVENT = 'change';
+import store from 'stores/redux_store.jsx';
+import * as Selectors from 'mattermost-redux/selectors/entities/preferences';
+import {PreferenceTypes} from 'mattermost-redux/action_types';
+
class PreferenceStore extends EventEmitter {
constructor() {
super();
@@ -16,6 +20,23 @@ class PreferenceStore extends EventEmitter {
this.dispatchToken = AppDispatcher.register(this.handleEventPayload);
this.preferences = new Map();
+ this.entities = Selectors.getMyPreferences(store.getState());
+ Object.keys(this.entities).forEach((key) => {
+ this.preferences.set(key, this.entities[key].value);
+ });
+
+ store.subscribe(() => {
+ const newEntities = Selectors.getMyPreferences(store.getState());
+ if (this.entities !== newEntities) {
+ this.preferences = new Map();
+ Object.keys(newEntities).forEach((key) => {
+ this.preferences.set(key, newEntities[key].value);
+ });
+ this.emitChange();
+ }
+
+ this.entities = newEntities;
+ });
this.setMaxListeners(600);
}
@@ -79,21 +100,24 @@ class PreferenceStore extends EventEmitter {
}
setPreference(category, name, value) {
- this.preferences.set(this.getKey(category, name), value);
+ store.dispatch({
+ type: PreferenceTypes.RECEIVED_PREFERENCES,
+ data: [{category, name, value}]
+ });
}
setPreferencesFromServer(newPreferences) {
- for (const preference of newPreferences) {
- this.setPreference(preference.category, preference.name, preference.value);
- }
+ store.dispatch({
+ type: PreferenceTypes.RECEIVED_PREFERENCES,
+ data: newPreferences
+ });
}
deletePreference(preference) {
- this.preferences.delete(this.getKey(preference.category, preference.name));
- }
-
- clear() {
- this.preferences.clear();
+ store.dispatch({
+ type: PreferenceTypes.DELETED_PREFERENCES,
+ data: [preference]
+ });
}
emitChange(category) {
diff --git a/webapp/stores/redux_store.jsx b/webapp/stores/redux_store.jsx
new file mode 100644
index 000000000..de5099d27
--- /dev/null
+++ b/webapp/stores/redux_store.jsx
@@ -0,0 +1,19 @@
+// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+// This is a temporary store while we are transitioning from Flux to Redux. This file exports
+// the configured Redux store for use by actions and selectors.
+
+import configureStore from 'store';
+const store = configureStore();
+
+export function bindActionToRedux(action, ...args) {
+ return async () => {
+ await action(...args)(store.dispatch, store.getState);
+ };
+}
+
+window.store = store;
+
+export default store;
+
diff --git a/webapp/stores/team_store.jsx b/webapp/stores/team_store.jsx
index 85480bdac..1d3d5ff25 100644
--- a/webapp/stores/team_store.jsx
+++ b/webapp/stores/team_store.jsx
@@ -17,17 +17,33 @@ const CHANGE_EVENT = 'change';
const STATS_EVENT = 'stats';
const UNREAD_EVENT = 'unread';
+import store from 'stores/redux_store.jsx';
+import * as Selectors from 'mattermost-redux/selectors/entities/teams';
+import {TeamTypes} from 'mattermost-redux/action_types';
+
var Utils;
class TeamStoreClass extends EventEmitter {
constructor() {
super();
this.clear();
+
+ store.subscribe(() => {
+ const newEntities = store.getState().entities.teams;
+
+ if (newEntities.teams !== this.entities.teams) {
+ this.emitChange();
+ }
+ if (newEntities.myMembers !== this.entities.myMembers) {
+ this.emitChange();
+ }
+
+ this.entities = newEntities;
+ });
}
clear() {
- this.teams = {};
- this.my_team_members = [];
+ this.entities = {};
this.members_in_team = {};
this.members_not_in_team = {};
this.stats = {};
@@ -91,7 +107,7 @@ class TeamStoreClass extends EventEmitter {
}
getAll() {
- return this.teams;
+ return store.getState().entities.teams.teams;
}
getCurrentId() {
@@ -100,10 +116,14 @@ class TeamStoreClass extends EventEmitter {
setCurrentId(id) {
this.currentTeamId = id;
+ store.dispatch({
+ type: TeamTypes.SELECT_TEAM,
+ data: id
+ });
}
getCurrent() {
- const team = this.teams[this.currentTeamId];
+ const team = this.getAll()[this.currentTeamId];
if (team) {
return team;
@@ -165,17 +185,21 @@ class TeamStoreClass extends EventEmitter {
}
saveTeam(team) {
- this.teams[team.id] = team;
+ this.saveTeams([team]);
}
saveTeams(teams) {
- this.teams = teams;
+ store.dispatch({
+ type: TeamTypes.RECEIVED_TEAMS_LIST,
+ data: teams
+ });
}
updateTeam(team) {
const t = JSON.parse(team);
- if (this.teams && this.teams[t.id]) {
- this.teams[t.id] = t;
+ const teams = this.getAll();
+ if (teams && teams[t.id]) {
+ this.saveTeam(t);
}
if (this.teamListings && this.teamListings[t.id]) {
@@ -193,7 +217,7 @@ class TeamStoreClass extends EventEmitter {
saveMyTeam(team) {
this.saveTeam(team);
- this.currentTeamId = team.id;
+ this.setCurrentId(team.id);
}
saveStats(teamId, stats) {
@@ -201,20 +225,26 @@ class TeamStoreClass extends EventEmitter {
}
saveMyTeamMembers(members) {
- this.my_team_members = members;
+ store.dispatch({
+ type: TeamTypes.RECEIVED_MY_TEAM_MEMBERS,
+ data: members
+ });
}
appendMyTeamMember(member) {
- this.my_team_members.push(member);
+ const members = this.getMyTeamMembers();
+ members.push(member);
+ this.saveMyTeamMembers(members);
}
saveMyTeamMembersUnread(members) {
- for (let i = 0; i < this.my_team_members.length; i++) {
- const team = this.my_team_members[i];
+ const myMembers = this.getMyTeamMembers();
+ for (let i = 0; i < myMembers.length; i++) {
+ const team = myMembers[i];
const member = members.filter((m) => m.team_id === team.team_id)[0];
if (member) {
- this.my_team_members[i] = Object.assign({},
+ myMembers[i] = Object.assign({},
team,
{
msg_count: member.msg_count,
@@ -222,19 +252,23 @@ class TeamStoreClass extends EventEmitter {
});
}
}
+
+ this.saveMyTeamMembers(myMembers);
}
removeMyTeamMember(teamId) {
- for (let i = 0; i < this.my_team_members.length; i++) {
- if (this.my_team_members[i].team_id === teamId) {
- this.my_team_members.splice(i, 1);
+ const myMembers = this.getMyTeamMembers();
+ for (let i = 0; i < myMembers.length; i++) {
+ if (myMembers[i].team_id === teamId) {
+ myMembers.splice(i, 1);
}
}
- this.emitChange();
+
+ this.saveMyTeamMembers(myMembers);
}
getMyTeamMembers() {
- return this.my_team_members;
+ return Object.values(Selectors.getTeamMemberships(store.getState()));
}
saveMembersInTeam(teamId = this.getCurrentId(), members) {
@@ -320,19 +354,21 @@ class TeamStoreClass extends EventEmitter {
}
updateUnreadCount(teamId, totalMsgCount, channelMember) {
- const member = this.my_team_members.filter((m) => m.team_id === teamId)[0];
+ let member = this.getMyTeamMembers().filter((m) => m.team_id === teamId)[0];
if (member) {
+ member = Object.assign({}, member);
member.msg_count -= (totalMsgCount - channelMember.msg_count);
member.mention_count -= channelMember.mention_count;
}
}
subtractUnread(teamId, msgs, mentions) {
- const member = this.my_team_members.filter((m) => m.team_id === teamId)[0];
+ let member = this.getMyTeamMembers().filter((m) => m.team_id === teamId)[0];
if (member) {
const msgCount = member.msg_count - msgs;
const mentionCount = member.mention_count - mentions;
+ member = Object.assign({}, member);
member.msg_count = (msgCount > 0) ? msgCount : 0;
member.mention_count = (mentionCount > 0) ? mentionCount : 0;
}
@@ -344,7 +380,7 @@ class TeamStoreClass extends EventEmitter {
return;
}
- const member = this.my_team_members.filter((m) => m.team_id === id)[0];
+ const member = Object.assign({}, this.getMyTeamMembers().filter((m) => m.team_id === id)[0]);
member.msg_count++;
}
@@ -355,7 +391,7 @@ class TeamStoreClass extends EventEmitter {
}
if (mentions.indexOf(UserStore.getCurrentId()) !== -1) {
- const member = this.my_team_members.filter((m) => m.team_id === id)[0];
+ const member = Object.assign({}, this.getMyTeamMembers().filter((m) => m.team_id === id)[0]);
member.mention_count++;
}
}
diff --git a/webapp/stores/user_store.jsx b/webapp/stores/user_store.jsx
index fa077f16b..a99c4b37a 100644
--- a/webapp/stores/user_store.jsx
+++ b/webapp/stores/user_store.jsx
@@ -1,16 +1,12 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
import EventEmitter from 'events';
-import * as GlobalActions from 'actions/global_actions.jsx';
-import LocalizationStore from './localization_store.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import TeamStore from 'stores/team_store.jsx';
import Constants from 'utils/constants.jsx';
-const ActionTypes = Constants.ActionTypes;
const UserStatuses = Constants.UserStatuses;
const CHANGE_EVENT_NOT_IN_CHANNEL = 'change_not_in_channel';
@@ -23,48 +19,52 @@ const CHANGE_EVENT_SESSIONS = 'change_sessions';
const CHANGE_EVENT_AUDITS = 'change_audits';
const CHANGE_EVENT_STATUSES = 'change_statuses';
+import store from 'stores/redux_store.jsx';
+import * as Selectors from 'mattermost-redux/selectors/entities/users';
+import {UserTypes} from 'mattermost-redux/action_types';
+
var Utils;
class UserStoreClass extends EventEmitter {
constructor() {
super();
- this.clear();
- }
-
- clear() {
- // All the profiles, regardless of where they came from
- this.profiles = {};
- this.paging_offset = 0;
- this.paging_count = 0;
-
- // Lists of sorted IDs for users in a team
- this.profiles_not_in_team = {};
- this.not_in_team_offset = 0;
- this.not_in_team_count = 0;
-
- // Lists of sorted IDs for users in a team
- this.profiles_in_team = {};
- this.in_team_offset = 0;
- this.in_team_count = 0;
-
- // Lists of sorted IDs for users in a channel
- this.profiles_in_channel = {};
- this.in_channel_offset = {};
- this.in_channel_count = {};
-
- // Lists of sorted IDs for users not in a channel
- this.profiles_not_in_channel = {};
- this.not_in_channel_offset = {};
- this.not_in_channel_count = {};
-
- // Lists of sorted IDs for users without a team
- this.profiles_without_team = {};
-
- this.statuses = {};
- this.sessions = {};
- this.audits = [];
- this.currentUserId = '';
+
this.noAccounts = false;
+ this.entities = {};
+
+ store.subscribe(() => {
+ const newEntities = store.getState().entities.users;
+
+ if (newEntities.profiles !== this.entities.profiles) {
+ this.emitChange();
+ }
+ if (newEntities.profilesInChannel !== this.entities.profilesInChannel) {
+ this.emitInChannelChange();
+ }
+ if (newEntities.profilesNotInChannel !== this.entities.profilesNotInChannel) {
+ this.emitNotInChannelChange();
+ }
+ if (newEntities.profilesInTeam !== this.entities.profilesInTeam) {
+ this.emitInTeamChange();
+ }
+ if (newEntities.profilesNotInTeam !== this.entities.profilesNotInTeam) {
+ this.emitNotInTeamChange();
+ }
+ if (newEntities.profilesWithoutTeam !== this.entities.profilesWithoutTeam) {
+ this.emitWithoutTeamChange();
+ }
+ if (newEntities.statuses !== this.entities.statuses) {
+ this.emitStatusesChange();
+ }
+ if (newEntities.myAudits !== this.entities.myAudits) {
+ this.emitAuditsChange();
+ }
+ if (newEntities.mySessions !== this.entities.mySessions) {
+ this.emitSessionsChange();
+ }
+
+ this.entities = newEntities;
+ });
}
emitChange(userId) {
@@ -178,49 +178,21 @@ class UserStoreClass extends EventEmitter {
// General
getCurrentUser() {
- return this.getProfiles()[this.currentUserId];
- }
-
- setCurrentUser(user) {
- this.saveProfile(user);
- this.currentUserId = user.id;
- global.window.mm_current_user_id = this.currentUserId;
- if (LocalizationStore.getLocale() !== user.locale) {
- setTimeout(() => GlobalActions.newLocalizationSelected(user.locale), 0);
- }
+ return Selectors.getCurrentUser(store.getState());
}
getCurrentId() {
- var user = this.getCurrentUser();
-
- if (user) {
- return user.id;
- }
-
- return null;
+ return Selectors.getCurrentUserId(store.getState());
}
// System-Wide Profiles
- saveProfiles(profiles) {
- const newProfiles = Object.assign({}, profiles);
- const currentId = this.getCurrentId();
- if (newProfiles[currentId]) {
- Reflect.deleteProperty(newProfiles, currentId);
- }
- this.profiles = Object.assign({}, this.profiles, newProfiles);
- }
-
getProfiles() {
- return this.profiles;
+ return Selectors.getUsers(store.getState());
}
getProfile(userId) {
- if (this.profiles[userId]) {
- return Object.assign({}, this.profiles[userId]);
- }
-
- return null;
+ return Selectors.getUser(store.getState(), userId);
}
getProfileListForIds(userIds, skipCurrent = false, skipInactive = false) {
@@ -257,17 +229,7 @@ class UserStoreClass extends EventEmitter {
}
getProfilesUsernameMap() {
- var profileUsernameMap = {};
-
- var profiles = this.getProfiles();
- for (var key in profiles) {
- if (profiles.hasOwnProperty(key)) {
- var profile = profiles[key];
- profileUsernameMap[profile.username] = profile;
- }
- }
-
- return profileUsernameMap;
+ return Selectors.getUsersByUsername(store.getState());
}
getActiveOnlyProfiles(skipCurrent) {
@@ -310,10 +272,11 @@ class UserStoreClass extends EventEmitter {
getProfileList(skipCurrent = false, allowInactive = false) {
const profiles = [];
const currentId = this.getCurrentId();
+ const profileMap = this.getProfiles();
- for (const id in this.profiles) {
- if (this.profiles.hasOwnProperty(id)) {
- var profile = this.profiles[id];
+ for (const id in profileMap) {
+ if (profileMap.hasOwnProperty(id)) {
+ var profile = profileMap[id];
if (skipCurrent && id === currentId) {
continue;
@@ -339,103 +302,32 @@ class UserStoreClass extends EventEmitter {
}
saveProfile(profile) {
- this.profiles[profile.id] = profile;
+ store.dispatch({
+ type: UserTypes.RECEIVED_PROFILE,
+ data: profile
+ });
}
// Team-Wide Profiles
- saveProfilesInTeam(teamId, profiles) {
- const oldProfileList = this.profiles_in_team[teamId] || [];
- const oldProfileMap = {};
- for (let i = 0; i < oldProfileList.length; i++) {
- oldProfileMap[oldProfileList[i]] = this.getProfile(oldProfileList[i]);
- }
-
- const newProfileMap = Object.assign({}, oldProfileMap, profiles);
- const newProfileList = Object.keys(newProfileMap);
-
- newProfileList.sort((a, b) => {
- const aProfile = newProfileMap[a];
- const bProfile = newProfileMap[b];
-
- if (aProfile.username < bProfile.username) {
- return -1;
- }
- if (aProfile.username > bProfile.username) {
- return 1;
- }
- return 0;
- });
-
- this.profiles_in_team[teamId] = newProfileList;
- this.saveProfiles(profiles);
- }
-
getProfileListInTeam(teamId = TeamStore.getCurrentId(), skipCurrent = false, skipInactive = false) {
- const userIds = this.profiles_in_team[teamId] || [];
+ const userIds = Array.from(Selectors.getUserIdsInTeams(store.getState())[teamId] || []);
return this.getProfileListForIds(userIds, skipCurrent, skipInactive);
}
removeProfileFromTeam(teamId, userId) {
- const userIds = this.profiles_in_team[teamId];
- if (!userIds) {
- return;
- }
-
- const index = userIds.indexOf(userId);
- if (index === -1) {
- return;
- }
-
- userIds.splice(index, 1);
- }
-
- // Not In Team Profiles
-
- saveProfilesNotInTeam(teamId, profiles) {
- const oldProfileList = this.profiles_not_in_team[teamId] || [];
- const oldProfileMap = {};
- for (let i = 0; i < oldProfileList.length; i++) {
- oldProfileMap[oldProfileList[i]] = this.getProfile(oldProfileList[i]);
- }
-
- const newProfileMap = Object.assign({}, oldProfileMap, profiles);
- const newProfileList = Object.keys(newProfileMap);
-
- newProfileList.sort((a, b) => {
- const aProfile = newProfileMap[a];
- const bProfile = newProfileMap[b];
-
- if (aProfile.username < bProfile.username) {
- return -1;
- }
- if (aProfile.username > bProfile.username) {
- return 1;
- }
- return 0;
+ store.dispatch({
+ type: UserTypes.RECEIVED_PROFILE_NOT_IN_TEAM,
+ data: {user_id: userId},
+ id: teamId
});
-
- this.profiles_not_in_team[teamId] = newProfileList;
- this.saveProfiles(profiles);
}
- removeProfileNotInTeam(teamId, userId) {
- const userIds = this.profiles_not_in_team[teamId];
- if (!userIds) {
- return;
- }
-
- const index = userIds.indexOf(userId);
- if (index === -1) {
- return;
- }
-
- userIds.splice(index, 1);
- }
+ // Not In Team Profiles
getProfileListNotInTeam(teamId = TeamStore.getCurrentId(), skipCurrent = false, skipInactive = false) {
- const userIds = this.profiles_not_in_team[teamId] || [];
+ const userIds = Array.from(Selectors.getUserIdsNotInTeams(store.getState())[teamId] || []);
const profiles = [];
const currentId = this.getCurrentId();
@@ -460,178 +352,84 @@ class UserStoreClass extends EventEmitter {
return profiles;
}
- // Channel-Wide Profiles
-
- saveProfilesInChannel(channelId = ChannelStore.getCurrentId(), profiles) {
- const oldProfileList = this.profiles_in_channel[channelId] || [];
- const oldProfileMap = {};
- for (let i = 0; i < oldProfileList.length; i++) {
- oldProfileMap[oldProfileList[i]] = this.getProfile(oldProfileList[i]);
- }
-
- const newProfileMap = Object.assign({}, oldProfileMap, profiles);
- const newProfileList = Object.keys(newProfileMap);
-
- newProfileList.sort((a, b) => {
- const aProfile = newProfileMap[a];
- const bProfile = newProfileMap[b];
-
- if (aProfile.username < bProfile.username) {
- return -1;
- }
- if (aProfile.username > bProfile.username) {
- return 1;
- }
- return 0;
+ removeProfileNotInTeam(teamId, userId) {
+ store.dispatch({
+ type: UserTypes.RECEIVED_PROFILE_IN_TEAM,
+ data: {user_id: userId},
+ id: teamId
});
-
- this.profiles_in_channel[channelId] = newProfileList;
- this.saveProfiles(profiles);
}
+ // Channel-Wide Profiles
+
saveProfileInChannel(channelId = ChannelStore.getCurrentId(), profile) {
- const profileMap = {};
- profileMap[profile.id] = profile;
- this.saveProfilesInChannel(channelId, profileMap);
+ store.dispatch({
+ type: UserTypes.RECEIVED_PROFILE_IN_CHANNEL,
+ data: {user_id: profile.id},
+ id: channelId
+ });
}
saveUserIdInChannel(channelId = ChannelStore.getCurrentId(), userId) {
- const profile = this.getProfile(userId);
-
- // Must have profile or we can't sort the list
- if (!profile) {
- return false;
- }
-
- this.saveProfileInChannel(channelId, profile);
-
- return true;
+ store.dispatch({
+ type: UserTypes.RECEIVED_PROFILE_IN_CHANNEL,
+ data: {user_id: userId},
+ id: channelId
+ });
}
removeProfileInChannel(channelId, userId) {
- const userIds = this.profiles_in_channel[channelId];
- if (!userIds) {
- return;
- }
-
- const index = userIds.indexOf(userId);
- if (index === -1) {
- return;
- }
-
- userIds.splice(index, 1);
+ store.dispatch({
+ type: UserTypes.RECEIVED_PROFILE_NOT_IN_CHANNEL,
+ data: {user_id: userId},
+ id: channelId
+ });
}
getProfileListInChannel(channelId = ChannelStore.getCurrentId(), skipCurrent = false) {
- const userIds = this.profiles_in_channel[channelId] || [];
+ const userIds = Array.from(Selectors.getUserIdsInChannels(store.getState())[channelId] || []);
return this.getProfileListForIds(userIds, skipCurrent, false);
}
- saveProfilesNotInChannel(channelId = ChannelStore.getCurrentId(), profiles) {
- const oldProfileList = this.profiles_not_in_channel[channelId] || [];
- const oldProfileMap = {};
- for (let i = 0; i < oldProfileList.length; i++) {
- oldProfileMap[oldProfileList[i]] = this.getProfile(oldProfileList[i]);
- }
-
- const newProfileMap = Object.assign({}, oldProfileMap, profiles);
- const newProfileList = Object.keys(newProfileMap);
-
- newProfileList.sort((a, b) => {
- const aProfile = newProfileMap[a];
- const bProfile = newProfileMap[b];
-
- if (aProfile.username < bProfile.username) {
- return -1;
- }
- if (aProfile.username > bProfile.username) {
- return 1;
- }
- return 0;
- });
-
- this.profiles_not_in_channel[channelId] = newProfileList;
- this.saveProfiles(profiles);
- }
-
saveProfileNotInChannel(channelId = ChannelStore.getCurrentId(), profile) {
- const profileMap = {};
- profileMap[profile.id] = profile;
- this.saveProfilesNotInChannel(channelId, profileMap);
+ store.dispatch({
+ type: UserTypes.RECEIVED_PROFILE_NOT_IN_CHANNEL,
+ data: {user_id: profile.id},
+ id: channelId
+ });
}
removeProfileNotInChannel(channelId, userId) {
- const userIds = this.profiles_not_in_channel[channelId];
- if (!userIds) {
- return;
- }
-
- const index = userIds.indexOf(userId);
- if (index === -1) {
- return;
- }
-
- userIds.splice(index, 1);
+ store.dispatch({
+ type: UserTypes.RECEIVED_PROFILE_IN_CHANNEL,
+ data: {user_id: userId},
+ id: channelId
+ });
}
getProfileListNotInChannel(channelId = ChannelStore.getCurrentId(), skipInactive = false) {
- const userIds = this.profiles_not_in_channel[channelId] || [];
+ const userIds = Array.from(Selectors.getUserIdsNotInChannels(store.getState())[channelId] || []);
return this.getProfileListForIds(userIds, false, skipInactive);
}
// Profiles without any teams
- saveProfilesWithoutTeam(profiles) {
- const oldProfileList = this.profiles_without_team;
- const oldProfileMap = {};
- for (let i = 0; i < oldProfileList.length; i++) {
- oldProfileMap[oldProfileList[i]] = this.getProfile(oldProfileList[i]);
- }
-
- const newProfileMap = Object.assign({}, oldProfileMap, profiles);
- const newProfileList = Object.keys(newProfileMap);
-
- newProfileList.sort((a, b) => {
- const aProfile = newProfileMap[a];
- const bProfile = newProfileMap[b];
-
- if (aProfile.username < bProfile.username) {
- return -1;
- }
- if (aProfile.username > bProfile.username) {
- return 1;
- }
- return 0;
- });
-
- this.profiles_without_team = newProfileList;
- this.saveProfiles(profiles);
- }
-
getProfileListWithoutTeam(skipCurrent = false, skipInactive = false) {
- const userIds = this.profiles_without_team || [];
+ const userIds = Array.from(Selectors.getUserIdsWithoutTeam(store.getState()) || []);
return this.getProfileListForIds(userIds, skipCurrent, skipInactive);
}
// Other
- setSessions(sessions) {
- this.sessions = sessions;
- }
-
getSessions() {
- return this.sessions;
- }
-
- setAudits(audits) {
- this.audits = audits;
+ return store.getState().entities.users.mySessions;
}
getAudits() {
- return this.audits;
+ return store.getState().entities.users.myAudits;
}
getCurrentMentionKeys() {
@@ -668,17 +466,16 @@ class UserStoreClass extends EventEmitter {
return keys;
}
- setStatuses(statuses) {
- this.statuses = Object.assign(this.statuses, statuses);
- }
-
setStatus(userId, status) {
- this.statuses[userId] = status;
- this.emitStatusesChange();
+ const data = [{user_id: userId, status}];
+ store.dispatch({
+ type: UserTypes.RECEIVED_STATUSES,
+ data
+ });
}
getStatuses() {
- return this.statuses;
+ return store.getState().entities.users.statuses;
}
getStatus(id) {
@@ -686,7 +483,7 @@ class UserStoreClass extends EventEmitter {
}
getNoAccounts() {
- return this.noAccounts;
+ return global.window.mm_config.NoAccounts === 'true';
}
setNoAccounts(noAccounts) {
@@ -706,141 +503,9 @@ class UserStoreClass extends EventEmitter {
return false;
}
-
- setPage(offset, count) {
- this.paging_offset = offset + count;
- this.paging_count = this.paging_count + count;
- }
-
- getPagingOffset() {
- return this.paging_offset;
- }
-
- getPagingCount() {
- return this.paging_count;
- }
-
- setInTeamPage(offset, count) {
- this.in_team_offset = offset + count;
- this.in_team_count = this.in_team_count + count;
- }
-
- getInTeamPagingOffset() {
- return this.in_team_offset;
- }
-
- getInTeamPagingCount() {
- return this.in_team_count;
- }
-
- setNotInTeamPage(offset, count) {
- this.not_in_team_offset = offset + count;
- this.not_in_team_count = this.not_in_team_count + count;
- }
-
- getNotInTeamPagingOffset() {
- return this.not_in_team_offset;
- }
-
- getNotInTeamPagingCount() {
- return this.not_in_team_count;
- }
-
- setInChannelPage(channelId, offset, count) {
- this.in_channel_offset[channelId] = offset + count;
- this.in_channel_count[channelId] = this.dm_paging_count + count;
- }
-
- getInChannelPagingOffset(channelId) {
- return this.in_channel_offset[channelId] | 0;
- }
-
- getInChannelPagingCount(channelId) {
- return this.in_channel_count[channelId] | 0;
- }
-
- setNotInChannelPage(channelId, offset, count) {
- this.not_in_channel_offset[channelId] = offset + count;
- this.not_in_channel_count[channelId] = this.dm_paging_count + count;
- }
-
- getNotInChannelPagingOffset(channelId) {
- return this.not_in_channel_offset[channelId] | 0;
- }
-
- getNotInChannelPagingCount(channelId) {
- return this.not_in_channel_count[channelId] | 0;
- }
}
var UserStore = new UserStoreClass();
UserStore.setMaxListeners(600);
-UserStore.dispatchToken = AppDispatcher.register((payload) => {
- var action = payload.action;
-
- switch (action.type) {
- case ActionTypes.RECEIVED_PROFILES:
- UserStore.saveProfiles(action.profiles);
- if (action.offset != null && action.count != null) {
- UserStore.setPage(action.offset, action.count);
- }
- UserStore.emitChange();
- break;
- case ActionTypes.RECEIVED_PROFILES_IN_TEAM:
- UserStore.saveProfilesInTeam(action.team_id, action.profiles);
- if (action.offset != null && action.count != null) {
- UserStore.setInTeamPage(action.offset, action.count);
- }
- UserStore.emitInTeamChange();
- break;
- case ActionTypes.RECEIVED_PROFILES_NOT_IN_TEAM:
- UserStore.saveProfilesNotInTeam(action.team_id, action.profiles);
- if (action.offset != null && action.count != null) {
- UserStore.setNotInTeamPage(action.offset, action.count);
- }
- UserStore.emitNotInTeamChange();
- break;
- case ActionTypes.RECEIVED_PROFILES_IN_CHANNEL:
- UserStore.saveProfilesInChannel(action.channel_id, action.profiles);
- if (action.offset != null && action.count != null) {
- UserStore.setInChannelPage(action.offset, action.count);
- }
- UserStore.emitInChannelChange();
- break;
- case ActionTypes.RECEIVED_PROFILES_NOT_IN_CHANNEL:
- UserStore.saveProfilesNotInChannel(action.channel_id, action.profiles);
- if (action.offset != null && action.count != null) {
- UserStore.setNotInChannelPage(action.offset, action.count);
- }
- UserStore.emitNotInChannelChange();
- break;
- case ActionTypes.RECEIVED_PROFILES_WITHOUT_TEAM:
- UserStore.saveProfilesWithoutTeam(action.profiles);
- UserStore.emitWithoutTeamChange();
- break;
- case ActionTypes.RECEIVED_PROFILE:
- UserStore.saveProfile(action.profile);
- UserStore.emitChange();
- break;
- case ActionTypes.RECEIVED_ME:
- UserStore.setCurrentUser(action.me);
- UserStore.emitChange(action.me.id);
- break;
- case ActionTypes.RECEIVED_SESSIONS:
- UserStore.setSessions(action.sessions);
- UserStore.emitSessionsChange();
- break;
- case ActionTypes.RECEIVED_AUDITS:
- UserStore.setAudits(action.audits);
- UserStore.emitAuditsChange();
- break;
- case ActionTypes.RECEIVED_STATUSES:
- UserStore.setStatuses(action.statuses);
- UserStore.emitStatusesChange();
- break;
- default:
- }
-});
-
export {UserStore as default};
diff --git a/webapp/utils/async_client.jsx b/webapp/utils/async_client.jsx
index abc1017fa..cb911cb55 100644
--- a/webapp/utils/async_client.jsx
+++ b/webapp/utils/async_client.jsx
@@ -8,7 +8,6 @@ import TeamStore from 'stores/team_store.jsx';
import ErrorStore from 'stores/error_store.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
-import {loadStatusesForProfilesMap} from 'actions/status_actions.jsx';
import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
import Client from 'client/web_client.jsx';
@@ -323,231 +322,6 @@ export function getUser(userId, success, error) {
);
}
-export function getProfiles(offset = UserStore.getPagingOffset(), limit = Constants.PROFILE_CHUNK_SIZE) {
- const callName = `getProfiles${offset}${limit}`;
-
- if (isCallInProgress(callName)) {
- return;
- }
-
- callTracker[callName] = utils.getTimestamp();
- Client.getProfiles(
- offset,
- limit,
- (data) => {
- callTracker[callName] = 0;
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_PROFILES,
- profiles: data
- });
- },
- (err) => {
- callTracker[callName] = 0;
- dispatchError(err, 'getProfiles');
- }
- );
-}
-
-export function getProfilesInTeam(teamId = TeamStore.getCurrentId(), offset = UserStore.getInTeamPagingOffset(), limit = Constants.PROFILE_CHUNK_SIZE) {
- const callName = `getProfilesInTeam${teamId}${offset}${limit}`;
-
- if (isCallInProgress(callName)) {
- return;
- }
-
- callTracker[callName] = utils.getTimestamp();
- Client.getProfilesInTeam(
- teamId,
- offset,
- limit,
- (data) => {
- callTracker[callName] = 0;
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_PROFILES_IN_TEAM,
- profiles: data,
- team_id: teamId,
- offset,
- count: Object.keys(data).length
- });
- },
- (err) => {
- callTracker[callName] = 0;
- dispatchError(err, 'getProfilesInTeam');
- }
- );
-}
-
-export function getProfilesNotInTeam(teamId = TeamStore.getCurrentId(), offset = UserStore.getInTeamPagingOffset(), limit = Constants.PROFILE_CHUNK_SIZE) {
- const callName = `getProfilesNotInTeam${teamId}${offset}${limit}`;
-
- if (isCallInProgress(callName)) {
- return;
- }
-
- callTracker[callName] = utils.getTimestamp();
- Client.getProfilesNotInTeam(
- teamId,
- offset,
- limit,
- (data) => {
- callTracker[callName] = 0;
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_PROFILES_NOT_IN_TEAM,
- profiles: data,
- team_id: teamId,
- offset,
- count: Object.keys(data).length
- });
- },
- (err) => {
- callTracker[callName] = 0;
- dispatchError(err, 'getProfilesNotInTeam');
- }
- );
-}
-
-export function getProfilesInChannel(channelId = ChannelStore.getCurrentId(), offset = UserStore.getInChannelPagingOffset(), limit = Constants.PROFILE_CHUNK_SIZE) {
- const callName = `getProfilesInChannel${channelId}${offset}${limit}`;
-
- if (isCallInProgress()) {
- return;
- }
-
- callTracker[callName] = utils.getTimestamp();
- Client.getProfilesInChannel(
- channelId,
- offset,
- limit,
- (data) => {
- callTracker[callName] = 0;
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_PROFILES_IN_CHANNEL,
- channel_id: channelId,
- profiles: data,
- offset,
- count: Object.keys(data).length
- });
-
- loadStatusesForProfilesMap(data);
- },
- (err) => {
- callTracker[callName] = 0;
- dispatchError(err, 'getProfilesInChannel');
- }
- );
-}
-
-export function getProfilesNotInChannel(channelId = ChannelStore.getCurrentId(), offset = UserStore.getNotInChannelPagingOffset(), limit = Constants.PROFILE_CHUNK_SIZE) {
- const callName = `getProfilesNotInChannel${channelId}${offset}${limit}`;
-
- if (isCallInProgress(callName)) {
- return;
- }
-
- callTracker[callName] = utils.getTimestamp();
- Client.getProfilesNotInChannel(
- channelId,
- offset,
- limit,
- (data) => {
- callTracker[callName] = 0;
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_PROFILES_NOT_IN_CHANNEL,
- channel_id: channelId,
- profiles: data,
- offset,
- count: Object.keys(data).length
- });
-
- loadStatusesForProfilesMap(data);
- },
- (err) => {
- callTracker[callName] = 0;
- dispatchError(err, 'getProfilesNotInChannel');
- }
- );
-}
-
-export function getProfilesByIds(userIds) {
- const callName = 'getProfilesByIds' + JSON.stringify(userIds);
-
- if (isCallInProgress(callName)) {
- return;
- }
-
- if (!userIds || userIds.length === 0) {
- return;
- }
-
- callTracker[callName] = utils.getTimestamp();
- Client.getProfilesByIds(
- userIds,
- (data) => {
- callTracker[callName] = 0;
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_PROFILES,
- profiles: data
- });
- },
- (err) => {
- callTracker[callName] = 0;
- dispatchError(err, 'getProfilesByIds');
- }
- );
-}
-
-export function getSessions() {
- if (isCallInProgress('getSessions')) {
- return;
- }
-
- callTracker.getSessions = utils.getTimestamp();
- Client.getSessions(
- UserStore.getCurrentId(),
- (data) => {
- callTracker.getSessions = 0;
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_SESSIONS,
- sessions: data
- });
- },
- (err) => {
- callTracker.getSessions = 0;
- dispatchError(err, 'getSessions');
- }
- );
-}
-
-export function getAudits() {
- if (isCallInProgress('getAudits')) {
- return;
- }
-
- callTracker.getAudits = utils.getTimestamp();
- Client.getAudits(
- UserStore.getCurrentId(),
- (data) => {
- callTracker.getAudits = 0;
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_AUDITS,
- audits: data
- });
- },
- (err) => {
- callTracker.getAudits = 0;
- dispatchError(err, 'getAudits');
- }
- );
-}
-
export function getLogs() {
if (isCallInProgress('getLogs')) {
return;
diff --git a/webapp/utils/channel_intro_messages.jsx b/webapp/utils/channel_intro_messages.jsx
index c6a3d7547..31ba8708d 100644
--- a/webapp/utils/channel_intro_messages.jsx
+++ b/webapp/utils/channel_intro_messages.jsx
@@ -2,7 +2,7 @@
// See License.txt for license information.
import * as Utils from './utils.jsx';
-import ChannelInviteModal from 'components/channel_invite_modal.jsx';
+import ChannelInviteModal from 'components/channel_invite_modal';
import EditChannelHeaderModal from 'components/edit_channel_header_modal.jsx';
import ToggleModalButton from 'components/toggle_modal_button.jsx';
import UserProfile from 'components/user_profile.jsx';
diff --git a/webapp/webpack.config.js b/webapp/webpack.config.js
index 32c5a322a..40b16139b 100644
--- a/webapp/webpack.config.js
+++ b/webapp/webpack.config.js
@@ -23,7 +23,7 @@ if (NPM_TARGET === 'test') {
}
var config = {
- entry: ['babel-polyfill', './root.jsx', 'root.html'],
+ entry: ['babel-polyfill', 'whatwg-fetch', './root.jsx', 'root.html'],
output: {
path: 'dist',
publicPath: '/static/',
@@ -33,7 +33,7 @@ var config = {
module: {
loaders: [
{
- test: /\.jsx?$/,
+ test: /\.(js|jsx)?$/,
loader: 'babel-loader',
exclude: /(node_modules|non_npm_dependencies)/,
query: {