diff --git a/app/actions/views/channel.js b/app/actions/views/channel.js index c78eb2391..daf759179 100644 --- a/app/actions/views/channel.js +++ b/app/actions/views/channel.js @@ -1,13 +1,16 @@ // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. +import {batchActions} from 'redux-batched-actions'; + +import {ViewTypes} from 'app/constants'; + import {fetchMyChannelsAndMembers, getMyChannelMembers, selectChannel} from 'service/actions/channels'; import {getPosts} from 'service/actions/posts'; import {getTeamMembersByIds} from 'service/actions/teams'; import {Constants, UsersTypes} from 'service/constants'; import {getChannelByName, getDirectChannelName} from 'service/utils/channel_utils'; import {getPreferencesByCategory} from 'service/utils/preference_utils'; -import {batchActions} from 'redux-batched-actions'; export function loadChannelsIfNecessary(teamId) { return async (dispatch, getState) => { @@ -101,3 +104,12 @@ export function selectInitialChannel(teamId) { } }; } + +export function handlePostDraftChanged(postDraft) { + return async (dispatch, getState) => { + dispatch({ + type: ViewTypes.POST_DRAFT_CHANGED, + postDraft + }, getState); + }; +} diff --git a/app/components/channel_drawer/channel_item.js b/app/components/channel_drawer/channel_item.js index 5ef8b0596..ef5a739b4 100644 --- a/app/components/channel_drawer/channel_item.js +++ b/app/components/channel_drawer/channel_item.js @@ -2,12 +2,16 @@ // See License.txt for license information. import React from 'react'; -import Icon from 'react-native-vector-icons/FontAwesome'; -import Badge from './badge'; import {TouchableHighlight, Text, View} from 'react-native'; +import Icon from 'react-native-vector-icons/FontAwesome'; + import {OnlineStatus, AwayStatus, OfflineStatus} from 'app/components/status_icons'; +import {changeOpacity} from 'app/utils/colors'; + import {Constants} from 'service/constants'; +import Badge from './badge'; + export default class ChannelItem extends React.Component { static propTypes = { channel: React.PropTypes.object.isRequired, @@ -18,28 +22,6 @@ export default class ChannelItem extends React.Component { theme: React.PropTypes.object.isRequired }; - changeOpacity = (oldColor, opacity) => { - let color = oldColor; - if (color[0] === '#') { - color = color.slice(1); - } - - if (color.length === 3) { - const tempColor = color; - color = ''; - - color += tempColor[0] + tempColor[0]; - color += tempColor[1] + tempColor[1]; - color += tempColor[2] + tempColor[2]; - } - - const r = parseInt(color.substring(0, 2), 16); - const g = parseInt(color.substring(2, 4), 16); - const b = parseInt(color.substring(4, 6), 16); - - return 'rgba(' + r + ',' + g + ',' + b + ',' + opacity + ')'; - }; - render() { const { channel, @@ -49,7 +31,7 @@ export default class ChannelItem extends React.Component { isActive } = this.props; - let iconColor = this.changeOpacity(theme.centerChannelColor, 0.7); + let iconColor = changeOpacity(theme.centerChannelColor, 0.7); let icon; let activeBorder; let badge; @@ -112,7 +94,7 @@ export default class ChannelItem extends React.Component { iconColor = theme.sidebarTextActiveColor; style.color = theme.sidebarTextActiveColor; style.opacity = 1; - itemStyle.backgroundColor = this.changeOpacity(theme.sidebarTextActiveColor, 0.1); + itemStyle.backgroundColor = changeOpacity(theme.sidebarTextActiveColor, 0.1); activeBorder = ( @@ -160,7 +142,7 @@ export default class ChannelItem extends React.Component { ); break; @@ -169,7 +151,7 @@ export default class ChannelItem extends React.Component { return ( this.props.onSelectChannel(channel)} > diff --git a/app/components/post/post.js b/app/components/post/post.js index 93af77ef6..96791ad31 100644 --- a/app/components/post/post.js +++ b/app/components/post/post.js @@ -11,6 +11,7 @@ import {isSystemMessage} from 'service/utils/post_utils.js'; export default class Post extends React.Component { static propTypes = { + style: React.PropTypes.object, post: React.PropTypes.object.isRequired, user: React.PropTypes.object, theme: React.PropTypes.object.isRequired @@ -32,7 +33,7 @@ export default class Post extends React.Component { } return ( - + {'['} diff --git a/app/components/post_list.js b/app/components/post_list.js index 3a930082f..f92fe5673 100644 --- a/app/components/post_list.js +++ b/app/components/post_list.js @@ -28,12 +28,18 @@ export default class PostList extends React.Component { } renderPost(post) { - return ; + return ( + + ); } render() { return ( { + if (this.root) { + this.root.setNativeProps(nativeProps); + } + } + + render() { + return ( + + + + + + ); + } +} diff --git a/app/components/post_textbox/index.js b/app/components/post_textbox/index.js new file mode 100644 index 000000000..eead7d57a --- /dev/null +++ b/app/components/post_textbox/index.js @@ -0,0 +1,6 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import PostTextboxContainer from './post_textbox_container'; + +export default PostTextboxContainer; diff --git a/app/components/post_textbox/post_textbox.js b/app/components/post_textbox/post_textbox.js new file mode 100644 index 000000000..ce1c4430f --- /dev/null +++ b/app/components/post_textbox/post_textbox.js @@ -0,0 +1,145 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; +import {TouchableHighlight, View} from 'react-native'; +import Icon from 'react-native-vector-icons/FontAwesome'; + +import TextInputWithLocalizedPlaceholder from 'app/components/text_input_with_localized_placeholder'; +import {changeOpacity} from 'app/utils/colors'; + +// import PaperClipIcon from './components/paper_clip_icon.js'; + +const MAX_CONTENT_HEIGHT = 100; + +export default class PostTextbox extends React.PureComponent { + static propTypes = { + currentUserId: React.PropTypes.string.isRequired, + teamId: React.PropTypes.string.isRequired, + channelId: React.PropTypes.string.isRequired, + rootId: React.PropTypes.string, + value: React.PropTypes.string.isRequired, + onChangeText: React.PropTypes.func.isRequired, + theme: React.PropTypes.object.isRequired, + actions: React.PropTypes.shape({ + createPost: React.PropTypes.func.isRequired + }).isRequired + }; + + static defaultProps = { + rootId: '' + }; + + constructor(props) { + super(props); + + this.state = { + contentHeight: 0 + }; + } + + blur = () => { + this.refs.input.getWrappedInstance().blur(); + } + + handleContentSizeChange = (e) => { + this.setState({ + contentHeight: e.nativeEvent.contentSize.height + }); + } + + sendMessage = () => { + if (this.props.value.trim().length === 0) { + return; + } + + const post = { + user_id: this.props.currentUserId, + channel_id: this.props.channelId, + root_id: this.props.rootId, + parent_id: this.props.rootId, + message: this.props.value + }; + + this.props.actions.createPost(this.props.teamId, post); + this.props.onChangeText(''); + } + + render() { + const theme = this.props.theme; + + let placeholder; + if (this.props.rootId) { + placeholder = {id: 'create_comment.addComment', defaultMessage: 'Add a comment...'}; + } else { + placeholder = {id: 'create_post.write', defaultMessage: 'Write a message...'}; + } + + return ( + + + {/* + + + */} + + + + + + + + ); + } +} diff --git a/app/components/post_textbox/post_textbox_container.js b/app/components/post_textbox/post_textbox_container.js new file mode 100644 index 000000000..3c4680150 --- /dev/null +++ b/app/components/post_textbox/post_textbox_container.js @@ -0,0 +1,29 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import {bindActionCreators} from 'redux'; +import {connect} from 'react-redux'; + +import {createPost} from 'service/actions/posts'; +import {getTheme} from 'service/selectors/entities/preferences'; +import {getCurrentUserId} from 'service/selectors/entities/users'; + +import PostTextbox from './post_textbox'; + +function mapStateToProps(state, ownProps) { + return { + ...ownProps, + currentUserId: getCurrentUserId(state), + theme: getTheme(state) + }; +} + +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators({ + createPost + }, dispatch) + }; +} + +export default connect(mapStateToProps, mapDispatchToProps, null, {withRef: true})(PostTextbox); diff --git a/app/components/text_input_with_localized_placeholder.js b/app/components/text_input_with_localized_placeholder.js new file mode 100644 index 000000000..a0688d9e7 --- /dev/null +++ b/app/components/text_input_with_localized_placeholder.js @@ -0,0 +1,32 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; +import {injectIntl, intlShape} from 'react-intl'; +import {TextInput} from 'react-native'; + +class TextInputWithLocalizedPlaceholder extends React.PureComponent { + static propTypes = { + ...TextInput.propTypes, + placeholder: React.PropTypes.object.isRequired, + intl: intlShape.isRequired + }; + + blur = () => { + this.refs.input.blur(); + } + + render() { + const {intl, placeholder, ...otherProps} = this.props; + + return ( + + ); + } +} + +export default injectIntl(TextInputWithLocalizedPlaceholder, {withRef: true}); diff --git a/app/constants/view.js b/app/constants/view.js index c2508f939..fd5962dcb 100644 --- a/app/constants/view.js +++ b/app/constants/view.js @@ -9,7 +9,8 @@ const ViewTypes = keyMirror({ LOGIN_ID_CHANGED: null, PASSWORD_CHANGED: null, - TOGGLE_CHANNEL_DRAWER: null + TOGGLE_CHANNEL_DRAWER: null, + POST_DRAFT_CHANGED: null }); export default ViewTypes; diff --git a/app/reducers/views/channel.js b/app/reducers/views/channel.js new file mode 100644 index 000000000..4347c8afd --- /dev/null +++ b/app/reducers/views/channel.js @@ -0,0 +1,23 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import {combineReducers} from 'redux'; + +import {ViewTypes} from 'app/constants'; + +import {ChannelTypes} from 'service/constants'; + +function postDraft(state = '', action) { + switch (action.type) { + case ViewTypes.POST_DRAFT_CHANGED: + return action.postDraft; + case ChannelTypes.SELECT_CHANNEL: + return ''; + default: + return state; + } +} + +export default combineReducers({ + postDraft +}); diff --git a/app/reducers/views/index.js b/app/reducers/views/index.js index cd4369b4f..29acc036e 100644 --- a/app/reducers/views/index.js +++ b/app/reducers/views/index.js @@ -3,14 +3,16 @@ import {combineReducers} from 'redux'; +import channel from './channel'; +import drawer from './drawer'; +import i18n from './i18n'; import login from './login'; import selectServer from './select_server'; -import i18n from './i18n'; -import drawer from './drawer'; export default combineReducers({ + channel, + drawer, i18n, login, - selectServer, - drawer + selectServer }); diff --git a/app/scenes/channel/channel.js b/app/scenes/channel/channel.js index 999a0b407..abe2dca42 100644 --- a/app/scenes/channel/channel.js +++ b/app/scenes/channel/channel.js @@ -2,34 +2,38 @@ // See License.txt for license information. import React from 'react'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; -import {StatusBar, Text, TouchableHighlight, View} from 'react-native'; -import Icon from 'react-native-vector-icons/FontAwesome'; +import { + KeyboardAvoidingView, + StatusBar, + Text +} from 'react-native'; import Drawer from 'react-native-drawer'; import ChannelDrawer from 'app/components/channel_drawer'; +import PostTextbox from 'app/components/post_textbox'; import RightSidebarMenu from 'app/components/right_sidebar_menu'; -import ChannelPostList from './components/channel_post_list'; +import ChannelHeader from './channel_header'; +import ChannelPostList from './channel_post_list'; -export default class Channel extends React.Component { +export default class Channel extends React.PureComponent { static propTypes = { actions: React.PropTypes.shape({ loadChannelsIfNecessary: React.PropTypes.func.isRequired, loadProfilesAndTeamMembersForDMSidebar: React.PropTypes.func.isRequired, selectInitialChannel: React.PropTypes.func.isRequired, - openChannelDrawer: React.PropTypes.func.isRequired + openChannelDrawer: React.PropTypes.func.isRequired, + handlePostDraftChanged: React.PropTypes.func.isRequired }).isRequired, currentTeam: React.PropTypes.object, currentChannel: React.PropTypes.object, + postDraft: React.PropTypes.string.isRequired, theme: React.PropTypes.object.isRequired }; constructor(props) { super(props); - this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this); - this.state = { leftSidebarOpen: false, rightSidebarOpen: false @@ -56,6 +60,7 @@ export default class Channel extends React.Component { }; openRightSidebar = () => { + this.refs.postTextbox.getWrappedInstance().blur(); this.setState({rightSidebarOpen: true}); }; @@ -63,6 +68,11 @@ export default class Channel extends React.Component { this.setState({rightSidebarOpen: false}); }; + openChannelDrawer = () => { + this.refs.postTextbox.getWrappedInstance().blur(); + this.props.actions.openChannelDrawer(); + } + render() { const { currentTeam, @@ -77,7 +87,10 @@ export default class Channel extends React.Component { } return ( - + - - - - - - - - - {currentChannel.display_name} - - - - {'>'} - - + + + - + ); } } diff --git a/app/scenes/channel/channel_container.js b/app/scenes/channel/channel_container.js index 65ba6bbb1..51f30ac86 100644 --- a/app/scenes/channel/channel_container.js +++ b/app/scenes/channel/channel_container.js @@ -7,7 +7,8 @@ import {connect} from 'react-redux'; import { loadChannelsIfNecessary, loadProfilesAndTeamMembersForDMSidebar, - selectInitialChannel + selectInitialChannel, + handlePostDraftChanged } from 'app/actions/views/channel'; import {openChannelDrawer} from 'app/actions/views/drawer'; @@ -15,27 +16,14 @@ import {getCurrentChannel} from 'service/selectors/entities/channels'; import {getTheme} from 'service/selectors/entities/preferences'; import {getCurrentTeam} from 'service/selectors/entities/teams'; -import {Constants} from 'service/constants'; -import {displayUsername} from 'service/utils/user_utils'; -import {getUserIdFromChannelName} from 'service/utils/channel_utils'; - -import Channel from './channel.js'; +import Channel from './channel'; function mapStateToProps(state, ownProps) { - const channel = getCurrentChannel(state); - const currentChannel = {...channel}; - const {currentId, profiles} = state.entities.users; - const {myPreferences} = state.entities.preferences; - - if (channel && channel.type === Constants.DM_CHANNEL) { - const otherUserId = getUserIdFromChannelName(currentId, currentChannel); - currentChannel.display_name = displayUsername(profiles[otherUserId], myPreferences); - } - return { ...ownProps, + ...state.views.channel, currentTeam: getCurrentTeam(state), - currentChannel, + currentChannel: getCurrentChannel(state), theme: getTheme(state) }; } @@ -46,7 +34,8 @@ function mapDispatchToProps(dispatch) { loadChannelsIfNecessary, loadProfilesAndTeamMembersForDMSidebar, selectInitialChannel, - openChannelDrawer + openChannelDrawer, + handlePostDraftChanged }, dispatch) }; } diff --git a/app/scenes/channel/channel_header/channel_header.js b/app/scenes/channel/channel_header/channel_header.js new file mode 100644 index 000000000..fe6ed2701 --- /dev/null +++ b/app/scenes/channel/channel_header/channel_header.js @@ -0,0 +1,54 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; +import { + Text, + TouchableHighlight, + View +} from 'react-native'; +import Icon from 'react-native-vector-icons/FontAwesome'; + +export default class ChannelHeader extends React.PureComponent { + static propTypes = { + displayName: React.PropTypes.string.isRequired, + theme: React.PropTypes.object.isRequired, + openLeftDrawer: React.PropTypes.func.isRequired, + openRightDrawer: React.PropTypes.func.isRequired + } + + render() { + const { + displayName, + theme + } = this.props; + + return ( + + + + + + + + + {displayName} + + + + {'>'} + + + ); + } +} diff --git a/app/scenes/channel/channel_header/channel_header_container.js b/app/scenes/channel/channel_header/channel_header_container.js new file mode 100644 index 000000000..8ff067ddc --- /dev/null +++ b/app/scenes/channel/channel_header/channel_header_container.js @@ -0,0 +1,37 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import {connect} from 'react-redux'; + +import {Constants} from 'service/constants'; +import {getMyPreferences, getTheme} from 'service/selectors/entities/preferences'; +import {getCurrentUserId, getUser} from 'service/selectors/entities/users'; +import {getUserIdFromChannelName} from 'service/utils/channel_utils'; +import {displayUsername} from 'service/utils/user_utils'; + +import ChannelHeader from './channel_header'; + +function mapStateToProps(state, ownProps) { + const currentChannel = ownProps.currentChannel; + + let displayName = ''; + if (currentChannel) { + if (currentChannel.type === Constants.DM_CHANNEL) { + const otherUser = getUser(state, getUserIdFromChannelName(getCurrentUserId(state), currentChannel)); + + if (otherUser) { + displayName = displayUsername(otherUser, getMyPreferences(state)); + } + } else { + displayName = currentChannel.display_name; + } + } + + return { + ...ownProps, + displayName, + theme: getTheme(state) + }; +} + +export default connect(mapStateToProps)(ChannelHeader); diff --git a/app/scenes/channel/channel_header/index.js b/app/scenes/channel/channel_header/index.js new file mode 100644 index 000000000..337137922 --- /dev/null +++ b/app/scenes/channel/channel_header/index.js @@ -0,0 +1,6 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import ChannelHeaderContainer from './channel_header_container'; + +export default ChannelHeaderContainer; diff --git a/app/scenes/channel/components/channel_post_list/channel_post_list.js b/app/scenes/channel/channel_post_list/channel_post_list.js similarity index 100% rename from app/scenes/channel/components/channel_post_list/channel_post_list.js rename to app/scenes/channel/channel_post_list/channel_post_list.js diff --git a/app/scenes/channel/components/channel_post_list/channel_post_list_container.js b/app/scenes/channel/channel_post_list/channel_post_list_container.js similarity index 100% rename from app/scenes/channel/components/channel_post_list/channel_post_list_container.js rename to app/scenes/channel/channel_post_list/channel_post_list_container.js diff --git a/app/scenes/channel/components/channel_post_list/index.js b/app/scenes/channel/channel_post_list/index.js similarity index 100% rename from app/scenes/channel/components/channel_post_list/index.js rename to app/scenes/channel/channel_post_list/index.js diff --git a/app/scenes/channel/index.js b/app/scenes/channel/index.js new file mode 100644 index 000000000..b2cb8a03c --- /dev/null +++ b/app/scenes/channel/index.js @@ -0,0 +1,6 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import ChannelContainer from './channel_container'; + +export default ChannelContainer; diff --git a/app/scenes/index.js b/app/scenes/index.js index b55d185af..065861fbe 100644 --- a/app/scenes/index.js +++ b/app/scenes/index.js @@ -1,7 +1,7 @@ // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import Channel from './channel/channel_container.js'; +import Channel from './channel'; import Login from './login/login_container.js'; import Root from './root/root_container.js'; import Search from './search/search_container.js'; diff --git a/app/scenes/login/login_container.js b/app/scenes/login/login_container.js index d22434e85..73e5be109 100644 --- a/app/scenes/login/login_container.js +++ b/app/scenes/login/login_container.js @@ -5,7 +5,7 @@ import {bindActionCreators} from 'redux'; import {connect} from 'react-redux'; import {getClientConfig, getLicenseConfig} from 'service/actions/general'; -import * as LoginActions from 'app/actions/views/login'; +import LoginActions from 'app/actions/views/login'; import * as StorageActions from 'app/actions/storage'; import {goToSelectTeam} from 'app/actions/navigation'; import {login} from 'service/actions/users'; diff --git a/app/utils/colors.js b/app/utils/colors.js new file mode 100644 index 000000000..0d84e9849 --- /dev/null +++ b/app/utils/colors.js @@ -0,0 +1,24 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +export function changeOpacity(oldColor, opacity) { + let color = oldColor; + if (color[0] === '#') { + color = color.slice(1); + } + + if (color.length === 3) { + const tempColor = color; + color = ''; + + color += tempColor[0] + tempColor[0]; + color += tempColor[1] + tempColor[1]; + color += tempColor[2] + tempColor[2]; + } + + const r = parseInt(color.substring(0, 2), 16); + const g = parseInt(color.substring(2, 4), 16); + const b = parseInt(color.substring(4, 6), 16); + + return 'rgba(' + r + ',' + g + ',' + b + ',' + opacity + ')'; +} diff --git a/scripts/make-dist-assets.js b/scripts/make-dist-assets.js index 7dda77b5e..6e8473f9f 100644 --- a/scripts/make-dist-assets.js +++ b/scripts/make-dist-assets.js @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. /* eslint-disable no-console */ diff --git a/service/selectors/entities/preferences.js b/service/selectors/entities/preferences.js index 2a35a6394..d4a1b88e4 100644 --- a/service/selectors/entities/preferences.js +++ b/service/selectors/entities/preferences.js @@ -1,43 +1,52 @@ // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. +import {createSelector} from 'reselect'; + import Config from 'assets/config.json'; import Themes from 'assets/themes.json'; import {Preferences} from 'service/constants'; -export function getTheme(state) { - const myPreferences = state.entities.preferences.myPreferences; - const currentTeamId = state.entities.teams.currentId; +import {getCurrentTeamId} from './teams'; - // Prefer the user's current team-specific theme over the user's current global theme over the default theme - let themePreference; - if (currentTeamId) { - themePreference = myPreferences[`${Preferences.CATEGORY_THEME}--${currentTeamId}`]; - } - - if (!themePreference) { - themePreference = myPreferences[`${Preferences.CATEGORY_THEME}--`]; - } - - let theme; - if (themePreference) { - theme = themePreference.value; - } else { - theme = Config.DefaultTheme; - } - - if (typeof theme === 'string') { - try { - // A custom theme will be a JSON-serialized object stored in a preference - theme = JSON.parse(theme); - } catch (e) { - // But if it's not a JSON object, it must be the name of a default theme - theme = Themes[theme]; - } - } - - // At this point, the theme should be a plain object - - return theme; +export function getMyPreferences(state) { + return state.entities.preferences.myPreferences; } + +export const getTheme = createSelector( + getMyPreferences, + getCurrentTeamId, + (myPreferences, currentTeamId) => { + // Prefer the user's current team-specific theme over the user's current global theme over the default theme + let themePreference; + if (currentTeamId) { + themePreference = myPreferences[`${Preferences.CATEGORY_THEME}--${currentTeamId}`]; + } + + if (!themePreference) { + themePreference = myPreferences[`${Preferences.CATEGORY_THEME}--`]; + } + + let theme; + if (themePreference) { + theme = themePreference.value; + } else { + theme = Config.DefaultTheme; + } + + if (typeof theme === 'string') { + try { + // A custom theme will be a JSON-serialized object stored in a preference + theme = JSON.parse(theme); + } catch (e) { + // But if it's not a JSON object, it must be the name of a default theme + theme = Themes[theme]; + } + } + + // At this point, the theme should be a plain object + + return theme; + } +); diff --git a/service/selectors/entities/users.js b/service/selectors/entities/users.js index 861ee7f3d..29989eb23 100644 --- a/service/selectors/entities/users.js +++ b/service/selectors/entities/users.js @@ -1,6 +1,24 @@ // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. +import {createSelector} from 'reselect'; + +export function getCurrentUserId(state) { + return state.entities.users.currentId; +} + +export function getUsers(state) { + return state.entities.users.profiles; +} + +export const getCurrentUser = createSelector( + getUsers, + getCurrentUserId, + (profiles, currentUserId) => { + return profiles[currentUserId]; + } +); + export function getUser(state, id) { return state.entities.users.profiles[id]; }