* Implement server changes for group messaging

* Majority of client-side implementation

* Some server updates

* Added new React multiselect component

* Fix style issues

* Add custom renderer for options

* Fix model test

* Update ENTER functionality for multiselect control

* Remove buttons from multiselect UI control

* Updating group messaging UI (#5524)

* Move filter controls up a component level

* Scroll with arrow keys

* Updating mobile layout for multiselect (#5534)

* Fix race condition when backspacing quickly

* Hidden or new GMs show up for regular messages

* Add overriding of number remaining text

* Add UI filtering for team if config setting set

* Add icon to channel switcher and class prop to status icon

* Minor updates per feedback

* Improving group messaging UI (#5563)

* UX changes per feedback

* Update email for group messages

* UI fixes for group messaging (#5587)

* Fix missing localization string

* Add maximum users message when adding members to GM

* Fix input clearing on Android

* Updating group messaging UI (#5603)

* Updating UI for group messaging (#5604)
Этот коммит содержится в:
Joram Wilander
2017-03-02 17:48:56 -05:00
коммит произвёл GitHub
родитель 8c5cee9521
Коммит 3a91d4e5e4
48 изменённых файлов: 1596 добавлений и 293 удалений

Просмотреть файл

@@ -9,7 +9,7 @@ import ChannelStore from 'stores/channel_store.jsx';
import * as ChannelUtils from 'utils/channel_utils.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import {loadProfilesAndTeamMembersForDMSidebar} from 'actions/user_actions.jsx';
import {loadProfilesForSidebar} from 'actions/user_actions.jsx';
import {trackEvent} from 'actions/diagnostics_actions.jsx';
import Client from 'client/web_client.jsx';
@@ -22,8 +22,12 @@ import {browserHistory} from 'react-router/es6';
export function goToChannel(channel) {
if (channel.fake) {
const user = UserStore.getProfileByUsername(channel.display_name);
if (!user) {
return;
}
openDirectChannelToUser(
UserStore.getProfileByUsername(channel.display_name),
user.id,
() => {
browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/channels/' + channel.name);
},
@@ -167,18 +171,18 @@ export function makeUserChannelMember(channelId, userId, success, error) {
);
}
export function openDirectChannelToUser(user, success, error) {
const channelName = Utils.getDirectChannelName(UserStore.getCurrentId(), user.id);
export function openDirectChannelToUser(userId, success, error) {
const channelName = Utils.getDirectChannelName(UserStore.getCurrentId(), userId);
const channel = ChannelStore.getByName(channelName);
if (channel) {
trackEvent('api', 'api_channels_join_direct');
PreferenceStore.setPreference(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, user.id, 'true');
loadProfilesAndTeamMembersForDMSidebar();
PreferenceStore.setPreference(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, userId, 'true');
loadProfilesForSidebar();
AsyncClient.savePreference(
Preferences.CATEGORY_DIRECT_CHANNEL_SHOW,
user.id,
userId,
'true'
);
@@ -190,7 +194,7 @@ export function openDirectChannelToUser(user, success, error) {
}
Client.createDirectChannel(
user.id,
userId,
(data) => {
Client.getChannel(
data.id,
@@ -201,12 +205,12 @@ export function openDirectChannelToUser(user, success, error) {
member: data2.member
});
PreferenceStore.setPreference(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, user.id, 'true');
loadProfilesAndTeamMembersForDMSidebar();
PreferenceStore.setPreference(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, userId, 'true');
loadProfilesForSidebar();
AsyncClient.savePreference(
Preferences.CATEGORY_DIRECT_CHANNEL_SHOW,
user.id,
userId,
'true'
);
@@ -225,6 +229,43 @@ export function openDirectChannelToUser(user, success, error) {
);
}
export function openGroupChannelToUsers(userIds, success, error) {
Client.createGroupChannel(
userIds,
(data) => {
Client.getChannelMember(
data.id,
UserStore.getCurrentId(),
(data2) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_CHANNEL,
channel: data,
member: data2
});
PreferenceStore.setPreference(Preferences.CATEGORY_GROUP_CHANNEL_SHOW, data.id, 'true');
loadProfilesForSidebar();
AsyncClient.savePreference(
Preferences.CATEGORY_GROUP_CHANNEL_SHOW,
data.id,
'true'
);
if (success) {
success(data);
}
}
);
},
() => {
if (error) {
error();
}
}
);
}
export function markFavorite(channelId) {
trackEvent('api', 'api_channels_favorited');
AsyncClient.savePreference(Preferences.CATEGORY_FAVORITE_CHANNEL, channelId, 'true');

Просмотреть файл

@@ -13,7 +13,7 @@ import PreferenceStore from 'stores/preference_store.jsx';
import SearchStore from 'stores/search_store.jsx';
import {handleNewPost, loadPosts, loadPostsBefore, loadPostsAfter} from 'actions/post_actions.jsx';
import {loadProfilesAndTeamMembersForDMSidebar} from 'actions/user_actions.jsx';
import {loadProfilesForSidebar} from 'actions/user_actions.jsx';
import {loadChannelsForCurrentUser} from 'actions/channel_actions.jsx';
import {stopPeriodicStatusUpdates} from 'actions/status_actions.jsx';
import * as WebsocketActions from 'actions/websocket_actions.jsx';
@@ -387,7 +387,7 @@ export function emitPreferenceChangedEvent(preference) {
});
if (preference.category === Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW) {
loadProfilesAndTeamMembersForDMSidebar();
loadProfilesForSidebar();
}
}

Просмотреть файл

@@ -8,7 +8,7 @@ import PostStore from 'stores/post_store.jsx';
import UserStore from 'stores/user_store.jsx';
import {loadStatusesForChannel} from 'actions/status_actions.jsx';
import {loadNewDMIfNeeded} from 'actions/user_actions.jsx';
import {loadNewDMIfNeeded, loadNewGMIfNeeded} from 'actions/user_actions.jsx';
import {trackEvent} from 'actions/diagnostics_actions.jsx';
import Client from 'client/web_client.jsx';
@@ -24,8 +24,12 @@ export function handleNewPost(post, msg) {
websocketMessageProps = msg.data;
}
if (msg && msg.data && msg.data.channel_type === Constants.DM_CHANNEL) {
loadNewDMIfNeeded(post.user_id);
if (msg && msg.data) {
if (msg.data.channel_type === Constants.DM_CHANNEL) {
loadNewDMIfNeeded(post.user_id);
} else if (msg.data.channel_type === Constants.GM_CHANNEL) {
loadNewGMIfNeeded(post.channel_id, post.user_id);
}
}
if (post.root_id && PostStore.getPost(post.channel_id, post.root_id) == null) {

Просмотреть файл

@@ -225,63 +225,150 @@ function populateDMChannelsWithProfiles(userIds) {
}
}
function populateChannelWithProfiles(channelId, userIds) {
for (let i = 0; i < userIds.length; i++) {
UserStore.saveUserIdInChannel(channelId, userIds[i]);
}
UserStore.emitInChannelChange();
}
export function loadNewDMIfNeeded(userId) {
if (userId === UserStore.getCurrentId()) {
return;
}
const pref = PreferenceStore.get(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, userId, 'false');
if (pref === 'false') {
const pref = PreferenceStore.getBool(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, userId, false);
if (pref === false) {
PreferenceStore.setPreference(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, userId, 'true');
AsyncClient.savePreference(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, userId, 'true');
loadProfilesAndTeamMembersForDMSidebar();
loadProfilesForDM();
}
}
export function loadProfilesAndTeamMembersForDMSidebar() {
const dmPrefs = PreferenceStore.getCategory(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW);
const teamId = TeamStore.getCurrentId();
const profilesToLoad = [];
const membersToLoad = [];
export function loadNewGMIfNeeded(channelId, userId) {
if (userId === UserStore.getCurrentId()) {
return;
}
for (const [key, value] of dmPrefs) {
if (value === 'true') {
if (!UserStore.hasProfile(key)) {
profilesToLoad.push(key);
}
membersToLoad.push(key);
function checkPreference() {
const pref = PreferenceStore.getBool(Preferences.CATEGORY_GROUP_CHANNEL_SHOW, channelId, false);
if (pref === false) {
PreferenceStore.setPreference(Preferences.CATEGORY_GROUP_CHANNEL_SHOW, channelId, 'true');
AsyncClient.savePreference(Preferences.CATEGORY_GROUP_CHANNEL_SHOW, channelId, 'true');
loadProfilesForGM();
}
}
const channelMembers = ChannelStore.getMyMembers();
const channel = ChannelStore.get(channelId);
if (channel) {
checkPreference();
} else {
Client.getChannel(
channelId,
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_CHANNEL,
channel: data.channel,
member: data.member
});
checkPreference();
},
(err) => {
AsyncClient.dispatchError(err, 'getChannel');
}
);
}
}
export function loadProfilesForSidebar() {
loadProfilesForDM();
loadProfilesForGM();
}
export function loadProfilesForGM() {
const channels = ChannelStore.getChannels();
const newPreferences = [];
for (let i = 0; i < channels.length; i++) {
const channel = channels[i];
if (channel.type !== Constants.GM_CHANNEL) {
continue;
}
if (UserStore.getProfileListInChannel(channel.id).length >= Constants.MIN_USERS_IN_GM) {
continue;
}
const isVisible = PreferenceStore.getBool(Preferences.CATEGORY_GROUP_CHANNEL_SHOW, channel.id);
if (!isVisible) {
const member = ChannelStore.getMyMember(channel.id);
if (!member || (member.mention_count === 0 && member.msg_count < member.total_msg_count)) {
continue;
}
newPreferences.push({
user_id: UserStore.getCurrentId(),
category: Preferences.CATEGORY_GROUP_CHANNEL_SHOW,
name: channel.id,
value: 'true'
});
}
Client.getProfilesInChannel(
channel.id,
0,
Constants.MAX_USERS_IN_GM,
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_PROFILES,
profiles: data
});
populateChannelWithProfiles(channel.id, Object.keys(data));
}
);
}
if (newPreferences.length > 0) {
AsyncClient.savePreferences(newPreferences);
}
}
export function loadProfilesForDM() {
const channels = ChannelStore.getChannels();
const newPreferences = [];
const profilesToLoad = [];
const profileIds = [];
for (let i = 0; i < channels.length; i++) {
const channel = channels[i];
if (channel.type !== Constants.DM_CHANNEL) {
continue;
}
const member = channelMembers[channel.id];
if (!member) {
continue;
}
const teammateId = channel.name.replace(UserStore.getCurrentId(), '').replace('__', '');
const isVisible = PreferenceStore.getBool(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, teammateId);
const teammateId = channel.name.replace(member.user_id, '').replace('__', '');
if (!isVisible) {
const member = ChannelStore.getMyMember(channel.id);
if (!member || member.mention_count === 0) {
continue;
}
if (member.mention_count > 0 && membersToLoad.indexOf(teammateId) === -1) {
membersToLoad.push(teammateId);
newPreferences.push({
user_id: UserStore.getCurrentId(),
category: Preferences.CATEGORY_DIRECT_CHANNEL_SHOW,
name: teammateId,
value: 'true'
});
if (!UserStore.hasProfile(teammateId)) {
profilesToLoad.push(teammateId);
}
}
if (!UserStore.hasProfile(teammateId)) {
profilesToLoad.push(teammateId);
}
profileIds.push(teammateId);
}
if (newPreferences.length > 0) {
@@ -298,44 +385,14 @@ export function loadProfilesAndTeamMembersForDMSidebar() {
});
// Use membersToLoad so we get all the DM profiles even if they were already loaded
populateDMChannelsWithProfiles(membersToLoad);
populateDMChannelsWithProfiles(profileIds);
},
(err) => {
AsyncClient.dispatchError(err, 'getProfilesByIds');
}
);
} else {
populateDMChannelsWithProfiles(membersToLoad);
}
if (membersToLoad.length > 0) {
Client.getTeamMembersByIds(
teamId,
membersToLoad,
(data) => {
const memberMap = {};
for (let i = 0; i < data.length; i++) {
memberMap[data[i].user_id] = data[i];
}
const nonMembersMap = {};
for (let i = 0; i < membersToLoad.length; i++) {
if (!memberMap[membersToLoad[i]]) {
nonMembersMap[membersToLoad[i]] = true;
}
}
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_MEMBERS_IN_TEAM,
team_id: teamId,
team_members: memberMap,
non_team_members: nonMembersMap
});
},
(err) => {
AsyncClient.dispatchError(err, 'getTeamMembersByIds');
}
);
populateDMChannelsWithProfiles(profileIds);
}
}
@@ -801,3 +858,27 @@ export function uploadProfileImage(userPicture, success, error) {
}
);
}
export function loadProfiles(offset = UserStore.getPagingOffset(), limit = Constants.PROFILE_CHUNK_SIZE, success, error) {
Client.getProfiles(
offset,
limit,
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_PROFILES,
profiles: data
});
if (success) {
success(data);
}
},
(err) => {
AsyncClient.dispatchError(err, 'getProfiles');
if (error) {
error(err);
}
}
);
}

Просмотреть файл

@@ -22,7 +22,7 @@ import {getSiteURL} from 'utils/url.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
import {handleNewPost, loadPosts, loadProfilesForPosts} from 'actions/post_actions.jsx';
import {loadProfilesAndTeamMembersForDMSidebar} from 'actions/user_actions.jsx';
import {loadProfilesForSidebar} from 'actions/user_actions.jsx';
import {loadChannelsForCurrentUser} from 'actions/channel_actions.jsx';
import * as StatusActions from 'actions/status_actions.jsx';
@@ -244,7 +244,7 @@ function handleUpdateTeamEvent(msg) {
function handleDirectAddedEvent(msg) {
AsyncClient.getChannel(msg.broadcast.channel_id);
PreferenceStore.setPreference(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, msg.data.teammate_id, 'true');
loadProfilesAndTeamMembersForDMSidebar();
loadProfilesForSidebar();
}
function handleUserAddedEvent(msg) {