From 7d03c24b44a2f4eba86adf86954280fa73e726e4 Mon Sep 17 00:00:00 2001 From: hmhealey Date: Fri, 2 Oct 2015 09:50:34 -0400 Subject: Changed direct channels in the sidebar to be displayed based on user preferences --- web/react/components/sidebar.jsx | 166 ++++++++++++++++++++++++++-------- web/react/stores/preference_store.jsx | 135 +++++++++++++++++++++++++++ web/react/utils/async_client.jsx | 29 ++++++ web/react/utils/client.jsx | 14 +++ web/react/utils/constants.jsx | 1 + 5 files changed, 308 insertions(+), 37 deletions(-) create mode 100644 web/react/stores/preference_store.jsx (limited to 'web/react') diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx index 4ac1fd4a0..115e9c9c6 100644 --- a/web/react/components/sidebar.jsx +++ b/web/react/components/sidebar.jsx @@ -1,19 +1,20 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -var ChannelStore = require('../stores/channel_store.jsx'); -var Client = require('../utils/client.jsx'); -var AsyncClient = require('../utils/async_client.jsx'); -var SocketStore = require('../stores/socket_store.jsx'); -var UserStore = require('../stores/user_store.jsx'); -var TeamStore = require('../stores/team_store.jsx'); -var BrowserStore = require('../stores/browser_store.jsx'); -var Utils = require('../utils/utils.jsx'); -var SidebarHeader = require('./sidebar_header.jsx'); -var SearchBox = require('./search_bar.jsx'); -var Constants = require('../utils/constants.jsx'); -var NewChannelFlow = require('./new_channel_flow.jsx'); -var UnreadChannelIndicator = require('./unread_channel_indicator.jsx'); +const AsyncClient = require('../utils/async_client.jsx'); +const BrowserStore = require('../stores/browser_store.jsx'); +const ChannelStore = require('../stores/channel_store.jsx'); +const Client = require('../utils/client.jsx'); +const Constants = require('../utils/constants.jsx'); +const PreferenceStore = require('../stores/preference_store.jsx'); +const NewChannelFlow = require('./new_channel_flow.jsx'); +const SearchBox = require('./search_bar.jsx'); +const SidebarHeader = require('./sidebar_header.jsx'); +const SocketStore = require('../stores/socket_store.jsx'); +const TeamStore = require('../stores/team_store.jsx'); +const UnreadChannelIndicator = require('./unread_channel_indicator.jsx'); +const UserStore = require('../stores/user_store.jsx'); +const Utils = require('../utils/utils.jsx'); export default class Sidebar extends React.Component { constructor(props) { @@ -23,6 +24,9 @@ export default class Sidebar extends React.Component { this.firstUnreadChannel = null; this.lastUnreadChannel = null; + this.getStateFromStores = this.getStateFromStores.bind(this); + //this.getDirectChannelsFromStores = this.getDirectChannelsFromStores.bind(this); + this.onChange = this.onChange.bind(this); this.onScroll = this.onScroll.bind(this); this.onResize = this.onResize.bind(this); @@ -36,7 +40,7 @@ export default class Sidebar extends React.Component { this.state = state; } getStateFromStores() { - var members = ChannelStore.getAllMembers(); + const members = ChannelStore.getAllMembers(); var teamMemberMap = UserStore.getActiveOnlyProfiles(); var currentId = ChannelStore.getCurrentId(); @@ -48,11 +52,13 @@ export default class Sidebar extends React.Component { teammates.push(teamMemberMap[id]); } + const preferences = PreferenceStore.getPreferences('direct_channels', 'show_hide'); + // Create lists of all read and unread direct channels - var showDirectChannels = []; - var readDirectChannels = []; + var visibleDirectChannels = []; + var hiddenDirectChannels = []; for (var i = 0; i < teammates.length; i++) { - var teammate = teammates[i]; + const teammate = teammates[i]; if (teammate.id === UserStore.getCurrentId()) { continue; @@ -65,7 +71,7 @@ export default class Sidebar extends React.Component { channelName = teammate.id + '__' + UserStore.getCurrentId(); } - var channel = ChannelStore.getByName(channelName); + let channel = ChannelStore.getByName(channelName); if (channel == null) { var tempChannel = {}; @@ -84,21 +90,44 @@ export default class Sidebar extends React.Component { channel.status = UserStore.getStatus(teammate.id); - var channelMember = members[channel.id]; + /*var channelMember = members[channel.id]; var msgCount = channel.total_msg_count - channelMember.msg_count; if (msgCount > 0) { - showDirectChannels.push(channel); + visibleDirectChannels.push(channel); } else if (currentId === channel.id) { - showDirectChannels.push(channel); + visibleDirectChannels.push(channel); } else { - readDirectChannels.push(channel); - } + hiddenDirectChannels.push(channel); + }*/ + } else { + channel = {}; + channel.fake = true; + channel.name = channelName; + channel.display_name = teammate.username; + channel.teammate_username = teammate.username; + channel.status = UserStore.getStatus(teammate.id); + channel.last_post_at = 0; + channel.total_msg_count = 0; + channel.type = 'D'; + } + + if (preferences.some((preference) => (preference.alt_id === teammate.id && preference.value !== 'false'))) { + visibleDirectChannels.push(channel); + } else { + hiddenDirectChannels.push(channel); } } - // If we don't have MAX_DMS unread channels, sort the read list by last_post_at - if (showDirectChannels.length < Constants.MAX_DMS) { - readDirectChannels.sort(function sortByLastPost(a, b) { + function sortByDisplayName(a, b) { + return a.display_name.localeCompare(b.display_name); + } + + visibleDirectChannels.sort(sortByDisplayName); + hiddenDirectChannels.sort(sortByDisplayName); + + /*// If we don't have MAX_DMS unread channels, sort the read list by last_post_at + if (visibleDirectChannels.length < Constants.MAX_DMS) { + hiddenDirectChannels.sort(function sortByLastPost(a, b) { // sort by last_post_at first if (a.last_post_at > b.last_post_at) { return -1; @@ -118,13 +147,13 @@ export default class Sidebar extends React.Component { }); var index = 0; - while (showDirectChannels.length < Constants.MAX_DMS && index < readDirectChannels.length) { - showDirectChannels.push(readDirectChannels[index]); + while (visibleDirectChannels.length < Constants.MAX_DMS && index < hiddenDirectChannels.length) { + visibleDirectChannels.push(hiddenDirectChannels[index]); index++; } - readDirectChannels = readDirectChannels.slice(index); + hiddenDirectChannels = hiddenDirectChannels.slice(index); - showDirectChannels.sort(function directSort(a, b) { + visibleDirectChannels.sort(function directSort(a, b) { if (a.display_name < b.display_name) { return -1; } @@ -133,22 +162,84 @@ export default class Sidebar extends React.Component { } return 0; }); - } + }*/ return { activeId: currentId, channels: ChannelStore.getAll(), members: members, - showDirectChannels: showDirectChannels, - hideDirectChannels: readDirectChannels + visibleDirectChannels: visibleDirectChannels, + hiddenDirectChannels: hiddenDirectChannels }; } + + /*getDirectChannelsFromStores() { + const id = UserStore.getCurrentId(); + + const channels = []; + const preferences = PreferenceStore.getPreferences('direct_channels', 'show_hide'); + for (const preference of preferences) { + if (preference.value !== 'true') { + continue; + } + + const otherId = preference.alt_id; + + if (otherId === id) { + continue; + } + + const teammate = UserStore.getProfile(otherId); + + if (!teammate) { + continue; + } + + let channelName = ''; + if (otherId > id) { + channelName = `${id}__${otherId}`; + } else { + channelName = `${otherId}__${id}`; + } + + const channel = ChannelStore.getByName(channelName); + + if (channel != null) { + channel.display_name = teammate.username; + channel.teammate_username = teammate.username; + + channel.status = UserStore.getStatus(otherId); + + channels.push(channel); + } else { + const tempChannel = {}; + tempChannel.fake = true; + tempChannel.name = channelName; + tempChannel.display_name = teammate.username; + tempChannel.teammate_username = teammate.username; + tempChannel.status = UserStore.getStatus(teammate.id); + tempChannel.last_post_at = 0; + tempChannel.total_msg_count = 0; + tempChannel.type = 'D'; + channels.push(tempChannel); + } + } + + channels.sort((a, b) => a.display_name.localeCompare(b)); + + return channels; + }*/ + componentDidMount() { ChannelStore.addChangeListener(this.onChange); UserStore.addChangeListener(this.onChange); UserStore.addStatusesChangeListener(this.onChange); TeamStore.addChangeListener(this.onChange); SocketStore.addChangeListener(this.onSocketChange); + PreferenceStore.addChangeListener(this.onChange); + + AsyncClient.getDirectChannels(); + $('.nav-pills__container').perfectScrollbar(); this.updateTitle(); @@ -178,6 +269,7 @@ export default class Sidebar extends React.Component { UserStore.removeStatusesChangeListener(this.onChange); TeamStore.removeChangeListener(this.onChange); SocketStore.removeChangeListener(this.onSocketChange); + PreferenceStore.removeChangeListener(this.onChange); } onChange() { var newState = this.getStateFromStores(); @@ -464,7 +556,7 @@ export default class Sidebar extends React.Component { const privateChannels = this.state.channels.filter((channel) => channel.type === 'P'); const privateChannelItems = privateChannels.map(this.createChannelElement); - const directMessageItems = this.state.showDirectChannels.map(this.createChannelElement); + const directMessageItems = this.state.visibleDirectChannels.map(this.createChannelElement); // update the favicon to show if there are any notifications var link = document.createElement('link'); @@ -484,7 +576,7 @@ export default class Sidebar extends React.Component { head.appendChild(link); var directMessageMore = null; - if (this.state.hideDirectChannels.length > 0) { + if (this.state.hiddenDirectChannels.length > 0) { directMessageMore = (
  • - {'More (' + this.state.hideDirectChannels.length + ')'} + {'More (' + this.state.hiddenDirectChannels.length + ')'}
  • ); diff --git a/web/react/stores/preference_store.jsx b/web/react/stores/preference_store.jsx new file mode 100644 index 000000000..c13c61e1d --- /dev/null +++ b/web/react/stores/preference_store.jsx @@ -0,0 +1,135 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +const ActionTypes = require('../utils/constants.jsx').ActionTypes; +const AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); +const BrowserStore = require('./browser_store.jsx'); +const EventEmitter = require('events').EventEmitter; +const UserStore = require('../stores/user_store.jsx'); + +const CHANGE_EVENT = 'change'; + +class PreferenceStoreClass extends EventEmitter { + constructor() { + super(); + + this.getAllPreferences = this.getAllPreferences.bind(this); + this.getPreference = this.getPreference.bind(this); + this.getPreferenceWithAltId = this.getPreferenceWithAltId.bind(this); + this.getPreferences = this.getPreferences.bind(this); + this.getPreferencesWhere = this.getPreferencesWhere.bind(this); + this.setAllPreferences = this.setAllPreferences.bind(this); + this.setPreference = this.setPreference.bind(this); + this.setPreferenceWithAltId = this.setPreferenceWithAltId.bind(this); + + this.emitChange = this.emitChange.bind(this); + this.addChangeListener = this.addChangeListener.bind(this); + this.removeChangeListener = this.removeChangeListener.bind(this); + + this.handleEventPayload = this.handleEventPayload.bind(this); + this.dispatchToken = AppDispatcher.register(this.handleEventPayload); + } + + getKey(category, name, altId = '') { + return `${category}-${name}-${altId}`; + } + + getKeyForModel(preference) { + return `${preference.category}-${preference.name}-${preference.alt_id}`; + } + + getAllPreferences() { + console.log('getting preferences'); // eslint-disable-line no-console + return new Map(BrowserStore.getItem('preferences', [])); + } + + getPreference(category, name, defaultValue = '') { + return this.getAllPreferences().get(this.getKey(category, name)) || defaultValue; + } + + getPreferenceWithAltId(category, name, altId, defaultValue = '') { + return this.getAllPreferences().get(this.getKey(category, name, altId)) || defaultValue; + } + + getPreferences(category, name) { + return this.getPreferencesWhere((preference) => (preference.category === category && preference.name === name)); + } + + getPreferencesWhere(pred) { + const all = this.getAllPreferences(); + const preferences = []; + + for (const [, preference] of all) { + if (pred(preference)) { + preferences.push(preference); + } + } + + return preferences; + } + + setAllPreferences(preferences) { + // note that we store the preferences as an array of key-value pairs so that we can deserialize + // it as a proper Map instead of an object + BrowserStore.setItem('preferences', [...preferences]); + } + + setPreference(category, name, value) { + this.setPreferenceWithAltId(category, name, '', value); + } + + setPreferenceWithAltId(category, name, altId, value) { + const preferences = this.getAllPreferences(); + + const key = this.getKey(category, name); + let preference = preferences.get(key); + + if (!preference) { + preference = { + user_id: UserStore.getCurrentId(), + category, + name, + alt_id: altId + }; + } + preference.value = value; + + preferences.set(key, preference); + + this.setAllPreferences(preferences); + } + + emitChange(preferences) { + this.emit(CHANGE_EVENT, preferences); + } + + addChangeListener(callback) { + this.on(CHANGE_EVENT, callback); + } + + removeChangeListener(callback) { + this.removeListener(CHANGE_EVENT, callback); + } + + handleEventPayload(payload) { + const action = payload.action; + + switch (action.type) { + case ActionTypes.RECIEVED_PREFERENCES: + const preferences = this.getAllPreferences(); + + for (const preference of action.preferences) { + preferences.set(this.getKeyForModel(preference), preference); + } + + this.setAllPreferences(preferences); + this.emitChange(preferences); + } + } +} + +const PreferenceStore = new PreferenceStoreClass(); +export default PreferenceStore; + +// TODO remove me +global.PreferenceStore = PreferenceStore; diff --git a/web/react/utils/async_client.jsx b/web/react/utils/async_client.jsx index a903f055b..3f084578a 100644 --- a/web/react/utils/async_client.jsx +++ b/web/react/utils/async_client.jsx @@ -637,3 +637,32 @@ export function getMyTeam() { } ); } + +export function getDirectChannels() { + if (isCallInProgress('getDirectChannels')) { + return; + } + + callTracker.getDirectChannels = utils.getTimestamp(); + client.getPreferencesByName( + 'direct_channels', + 'show_hide', + (data, textStatus, xhr) => { + callTracker.getDirectChannels = 0; + + if (xhr.status === 304 || !data) { + return; + } + + AppDispatcher.handleServerAction({ + type: ActionTypes.RECIEVED_PREFERENCES, + preferences: data + }); + }, + (err) => { + callTracker.getDirectChannels = 0; + dispatchError(err, 'getDirectChannels'); + } + ); +} + diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx index 6dccfcdeb..f1827f296 100644 --- a/web/react/utils/client.jsx +++ b/web/react/utils/client.jsx @@ -1141,3 +1141,17 @@ export function listIncomingHooks(success, error) { } }); } + +export function getPreferencesByName(category, name, success, error) { + $.ajax({ + url: `/api/v1/preferences/${category}/${name}`, + dataType: 'json', + type: 'GET', + success, + error: (xhr, status, err) => { + var e = handleError('getPreferencesByName', xhr, status, err); + error(e); + } + }); +} + diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index e3cbfccde..56c47a244 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -28,6 +28,7 @@ module.exports = { RECIEVED_AUDITS: null, RECIEVED_TEAMS: null, RECIEVED_STATUSES: null, + RECIEVED_PREFERENCES: null, RECIEVED_MSG: null, -- cgit v1.2.3-1-g7c22