diff options
author | Joram Wilander <jwawilander@gmail.com> | 2017-04-25 11:46:02 -0400 |
---|---|---|
committer | Christopher Speller <crspeller@gmail.com> | 2017-04-25 11:46:02 -0400 |
commit | 6c4c706313eb765eb00c639f381646be74f27b69 (patch) | |
tree | 6068feaa9668dcd74601730ac1a5abfb366402b1 | |
parent | cc07c005074348de87854f1c953a549e8772fa03 (diff) | |
download | chat-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
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: { |