diff --git a/app/actions/navigation/index.js b/app/actions/navigation/index.js index 1e5acb990..8f55f3713 100644 --- a/app/actions/navigation/index.js +++ b/app/actions/navigation/index.js @@ -12,6 +12,14 @@ export function goBack() { }; } +export function closeDrawers() { + return async (dispatch, getState) => { + dispatch({ + type: NavigationTypes.NAVIGATION_CLOSE_DRAWERS + }, getState); + }; +} + export function goToLogin() { return async (dispatch, getState) => { dispatch({ @@ -21,6 +29,15 @@ export function goToLogin() { }; } +export function goToLoadTeam() { + return async (dispatch, getState) => { + dispatch({ + type: NavigationTypes.NAVIGATION_PUSH, + route: Routes.LoadTeam + }, getState); + }; +} + export function goToSelectTeam() { return async (dispatch, getState) => { dispatch({ @@ -30,43 +47,6 @@ export function goToSelectTeam() { }; } -export function goToChannelView() { - return async (dispatch, getState) => { - dispatch({ - type: NavigationTypes.NAVIGATION_PUSH, - route: Routes.ChannelView - }, getState); - }; -} - -export function goToRecentMentions() { - return async (dispatch, getState) => { - dispatch({ - type: NavigationTypes.NAVIGATION_PUSH, - route: { - ...Routes.Search, - props: { - searchType: 'recent_mentions' - } - } - }, getState); - }; -} - -export function goToFlaggedPosts() { - return async (dispatch, getState) => { - dispatch({ - type: NavigationTypes.NAVIGATION_PUSH, - route: { - ...Routes.Search, - props: { - searchType: 'flagged_posts' - } - } - }, getState); - }; -} - export function goToChannelInfo() { return async (dispatch, getState) => { dispatch({ @@ -75,3 +55,21 @@ export function goToChannelInfo() { }, getState); }; } + +export function openChannelDrawer() { + return async (dispatch, getState) => { + dispatch({ + type: NavigationTypes.NAVIGATION_OPEN_LEFT_DRAWER, + route: Routes.ChannelDrawer + }, getState); + }; +} + +export function openRightMenuDrawer() { + return async (dispatch, getState) => { + dispatch({ + type: NavigationTypes.NAVIGATION_OPEN_RIGHT_DRAWER, + route: Routes.RightMenuDrawer + }, getState); + }; +} diff --git a/app/actions/views/drawer.js b/app/actions/views/drawer.js deleted file mode 100644 index 7f7f8f336..000000000 --- a/app/actions/views/drawer.js +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import {ViewTypes} from 'app/constants'; - -export function openChannelDrawer() { - return async (dispatch, getState) => { - dispatch({type: ViewTypes.TOGGLE_CHANNEL_DRAWER, data: true}, getState); - }; -} - -export function closeChannelDrawer() { - return async (dispatch, getState) => { - dispatch({type: ViewTypes.TOGGLE_CHANNEL_DRAWER, data: false}, getState); - }; -} diff --git a/app/actions/views/load_team.js b/app/actions/views/load_team.js new file mode 100644 index 000000000..f22034118 --- /dev/null +++ b/app/actions/views/load_team.js @@ -0,0 +1,14 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import {NavigationTypes} from 'app/constants'; +import Routes from 'app/navigation/routes'; + +export function goToChannelView() { + return async (dispatch, getState) => { + dispatch({ + type: NavigationTypes.NAVIGATION_RESET, + routes: [Routes.ChannelView] + }, getState); + }; +} diff --git a/app/actions/views/right_menu_drawer.js b/app/actions/views/right_menu_drawer.js new file mode 100644 index 000000000..d9e60e868 --- /dev/null +++ b/app/actions/views/right_menu_drawer.js @@ -0,0 +1,50 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import {batchActions} from 'redux-batched-actions'; + +import {NavigationTypes} from 'app/constants'; +import Routes from 'app/navigation/routes'; + +export function goToSelectTeam() { + return async (dispatch, getState) => { + dispatch(batchActions([{ + type: NavigationTypes.NAVIGATION_CLOSE_DRAWERS + }, { + type: NavigationTypes.NAVIGATION_PUSH, + route: Routes.SelectTeam + }]), getState); + }; +} + +export function goToRecentMentions() { + return async (dispatch, getState) => { + dispatch(batchActions([{ + type: NavigationTypes.NAVIGATION_CLOSE_DRAWERS + }, { + type: NavigationTypes.NAVIGATION_PUSH, + route: { + ...Routes.Search, + props: { + searchType: 'recent_mentions' + } + } + }]), getState); + }; +} + +export function goToFlaggedPosts() { + return async (dispatch, getState) => { + dispatch(batchActions([{ + type: NavigationTypes.NAVIGATION_CLOSE_DRAWERS + }, { + type: NavigationTypes.NAVIGATION_PUSH, + route: { + ...Routes.Search, + props: { + searchType: 'flagged_posts' + } + } + }]), getState); + }; +} diff --git a/app/components/channel_drawer/channel_drawer.js b/app/components/channel_drawer/channel_drawer.js deleted file mode 100644 index c467d8864..000000000 --- a/app/components/channel_drawer/channel_drawer.js +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import React from 'react'; -import {Platform, BackAndroid} from 'react-native'; -import deepEqual from 'deep-equal'; - -import Drawer from 'react-native-drawer'; -import ChannelList from './channel_list'; - -export default class ChannelDrawer extends React.Component { - static propTypes = { - children: React.PropTypes.element.isRequired, - actions: React.PropTypes.shape({ - updateStorage: React.PropTypes.func.isRequired, - handleSelectChannel: React.PropTypes.func.isRequired, - viewChannel: React.PropTypes.func.isRequired, - closeDMChannel: React.PropTypes.func.isRequired, - closeChannelDrawer: React.PropTypes.func.isRequired - }).isRequired, - currentTeam: React.PropTypes.object, - currentChannel: React.PropTypes.object, - channels: React.PropTypes.object, - channelMembers: React.PropTypes.object, - theme: React.PropTypes.object.isRequired, - isOpen: React.PropTypes.bool.isRequired - }; - - constructor(props) { - super(props); - - this.handleBackButton = this.handleBackButton.bind(this); - this.selectChannel = this.selectChannel.bind(this); - } - - componentDidMount() { - if (Platform.OS === 'android') { - BackAndroid.addEventListener('hardwareBackPress', this.handleBackButton); - } - } - - componentWillUnmount() { - if (Platform.OS === 'android') { - BackAndroid.removeEventListener('hardwareBackPress', this.handleBackButton); - } - } - shouldComponentUpdate(nextProps) { - return !deepEqual(this.props, nextProps, {strict: true}); - } - - componentWillReceiveProps(nextProps) { - if (!this.props.isOpen && nextProps.isOpen) { - this.drawer.open(); - } else if (this.props.isOpen && !nextProps.isOpen) { - this.drawer.close(); - } - } - - handleBackButton() { - if (this.props.isOpen) { - this.props.actions.closeChannelDrawer(); - return true; - } - return false; - } - - selectChannel(channelId) { - this.props.actions.handleSelectChannel(channelId); - } - - render() { - const { - currentChannel, - currentTeam, - channels, - channelMembers, - theme - } = this.props; - - return ( - { - this.drawer = ref; - }} - onClose={this.props.actions.closeChannelDrawer} - type='displace' - openDrawerOffset={0.2} - closedDrawerOffset={0} - panOpenMask={0.1} - panCloseMask={0.2} - panThreshold={0.2} - acceptPan={true} - tapToClose={true} - content={ - - } - > - {this.props.children} - - ); - } -} diff --git a/app/components/channel_drawer/channel_drawer_container.js b/app/components/channel_drawer/channel_drawer_container.js deleted file mode 100644 index 42c3ccf54..000000000 --- a/app/components/channel_drawer/channel_drawer_container.js +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import {bindActionCreators} from 'redux'; -import {connect} from 'react-redux'; - -import {viewChannel} from 'service/actions/channels'; -import {getChannelsByCategory} from 'service/selectors/entities/channels'; -import {closeDMChannel, handleSelectChannel} from 'app/actions/views/channel'; -import {closeChannelDrawer} from 'app/actions/views/drawer'; -import {updateStorage} from 'app/actions/storage'; -import ChannelDrawer from './channel_drawer'; - -function mapStateToProps(state, ownProps) { - const isOpen = state.views.drawer.channel; - const channelMembers = state.entities.channels.myMembers; - return { - ...ownProps, - channels: getChannelsByCategory(state), - channelMembers, - isOpen - }; -} - -function mapDispatchToProps(dispatch) { - return { - actions: bindActionCreators({ - updateStorage, - handleSelectChannel, - viewChannel, - closeDMChannel, - closeChannelDrawer - }, dispatch) - }; -} - -export default connect(mapStateToProps, mapDispatchToProps)(ChannelDrawer); diff --git a/app/components/channel_drawer/channel_list.js b/app/components/channel_drawer/channel_list.js index 78458aa10..c020ed97b 100644 --- a/app/components/channel_drawer/channel_list.js +++ b/app/components/channel_drawer/channel_list.js @@ -60,8 +60,7 @@ class ChannelList extends React.Component { theme: React.PropTypes.object.isRequired, onSelectChannel: React.PropTypes.func.isRequired, onViewChannel: React.PropTypes.func.isRequired, - handleCloseDM: React.PropTypes.func.isRequired, - closeChannelDrawer: React.PropTypes.func.isRequired + handleCloseDM: React.PropTypes.func.isRequired }; constructor(props) { @@ -141,7 +140,6 @@ class ChannelList extends React.Component { this.props.onSelectChannel(channel.id); this.props.onViewChannel(currentTeam.id, channel.id, currentChannel.id); - this.props.closeChannelDrawer(); }; handleClose = (channel) => { diff --git a/app/components/drawer.js b/app/components/drawer.js index d3e53b316..96894c9cd 100644 --- a/app/components/drawer.js +++ b/app/components/drawer.js @@ -11,14 +11,19 @@ export default class Drawer extends BaseDrawer { onRequestClose: React.PropTypes.func.isRequired }; - close = (type, callback) => { - this.props.onRequestClose(); + processTapGestures = () => { + // Note that we explicitly don't support tap to open or double tap because I didn't copy them over - if (typeof type === 'function') { - // This function can be called with only a callback as its only argument - type(); - } else if (callback) { - callback(); + if (this._activeTween) { // eslint-disable-line no-underscore-dangle + return false; } - } + + if (this.props.tapToClose && this._open) { // eslint-disable-line no-underscore-dangle + this.props.onRequestClose(); + + return true; + } + + return false; + }; } diff --git a/app/components/right_sidebar_menu/index.js b/app/components/right_sidebar_menu/index.js deleted file mode 100644 index be9b8dd2f..000000000 --- a/app/components/right_sidebar_menu/index.js +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import RightSidebarMenuContainer from './right_sidebar_menu_container'; - -export default RightSidebarMenuContainer; diff --git a/app/constants/navigation.js b/app/constants/navigation.js index 0bbc7ec31..90d3ee7e1 100644 --- a/app/constants/navigation.js +++ b/app/constants/navigation.js @@ -6,8 +6,12 @@ import keyMirror from 'service/utils/key_mirror'; const NavigationTypes = keyMirror({ NAVIGATION_PUSH: null, NAVIGATION_POP: null, + NAVIGATION_OPEN_LEFT_DRAWER: null, + NAVIGATION_OPEN_RIGHT_DRAWER: null, + NAVIGATION_CLOSE_DRAWERS: null, NAVIGATION_JUMP: null, NAVIGATION_JUMP_TO_INDEX: null, + NAVIGATION_REPLACE: null, NAVIGATION_RESET: null }); diff --git a/app/initial_state.js b/app/initial_state.js index 6c7ceba5c..bcfde8ba6 100644 --- a/app/initial_state.js +++ b/app/initial_state.js @@ -257,7 +257,11 @@ const state = { { key: 'Root' } - ] + ], + leftDrawerOpen: false, + leftDrawerRoute: null, + rightDrawerOpen: false, + rightDrawerRoute: null }, views: { i18n: { @@ -269,9 +273,6 @@ const state = { }, selectServer: { serverUrl: Config.DefaultServerUrl - }, - drawer: { - channel: false } } }; diff --git a/app/navigation/router.js b/app/navigation/router.js index 52f89029f..bace732c9 100644 --- a/app/navigation/router.js +++ b/app/navigation/router.js @@ -2,22 +2,26 @@ // See License.txt for license information. import React from 'react'; - -import {bindActionCreators} from 'redux'; +import { + Easing, + NavigationExperimental, + View +} from 'react-native'; import {connect} from 'react-redux'; +import {bindActionCreators} from 'redux'; -import {goBack} from 'app/actions/navigation'; - -import {getComponentForScene} from 'app/scenes'; - -import {Easing, NavigationExperimental, View} from 'react-native'; +import {closeDrawers, goBack} from 'app/actions/navigation'; +import Drawer from 'app/components/drawer'; import FormattedText from 'app/components/formatted_text'; +import {RouteTransitions} from 'app/navigation/routes'; +import {getComponentForScene} from 'app/scenes'; class Router extends React.Component { static propTypes = { navigation: React.PropTypes.object, actions: React.PropTypes.shape({ - goBack: React.PropTypes.func + closeDrawers: React.PropTypes.func.isRequired, + goBack: React.PropTypes.func.isRequired }).isRequired }; @@ -39,12 +43,19 @@ class Router extends React.Component { scene }; + let style; + if (scene.route.transition === RouteTransitions.Horizontal) { + style = NavigationExperimental.Card.PagerStyleInterpolator.forHorizontal({ + ...cardProps + }); + } else { + style = {}; + } + return ( @@ -52,11 +63,9 @@ class Router extends React.Component { }); return ( - - - {renderedScenes} - + {title} + {renderedScenes} ); }; @@ -79,11 +88,15 @@ class Router extends React.Component { }; renderScene = ({scene}) => { - const SceneComponent = getComponentForScene(scene.route.key); - - return ; + return this.renderRoute(scene.route); }; + renderRoute = (route) => { + const SceneComponent = getComponentForScene(route.key); + + return ; + } + configureTransition = () => { return { duration: 500, @@ -92,13 +105,49 @@ class Router extends React.Component { }; render = () => { + const { + leftDrawerOpen, + leftDrawerRoute, + rightDrawerOpen, + rightDrawerRoute + } = this.props.navigation; + + let leftDrawerContent; + if (leftDrawerRoute) { + leftDrawerContent = this.renderRoute(leftDrawerRoute); + } + + let rightDrawerContent; + if (rightDrawerRoute) { + rightDrawerContent = this.renderRoute(rightDrawerRoute); + } + return ( - + + + + + ); }; } @@ -112,6 +161,7 @@ function mapStateToProps(state) { function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ + closeDrawers, goBack }, dispatch) }; diff --git a/app/navigation/routes.js b/app/navigation/routes.js index 6372e92e8..d55d717c6 100644 --- a/app/navigation/routes.js +++ b/app/navigation/routes.js @@ -1,30 +1,52 @@ // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -export default { +import keyMirror from 'service/utils/key_mirror'; + +export const RouteTransitions = keyMirror({ + Horizontal: null +}); + +export const Routes = { + ChannelInfo: { + key: 'ChannelInfo', + title: {id: 'mobile.routes.channelInfo', defaultMessage: 'Info'}, + transition: RouteTransitions.Horizontal + }, + ChannelDrawer: { + key: 'ChannelDrawer' + }, + ChannelView: { + key: 'ChannelView', + transition: RouteTransitions.Horizontal + }, + LoadTeam: { + key: 'LoadTeam' + }, + Login: { + key: 'Login', + title: {id: 'mobile.routes.login', defaultMessage: 'Login'}, + transition: RouteTransitions.Horizontal + }, + RightMenuDrawer: { + key: 'RightMenuDrawer' + }, Root: { key: 'Root' }, + Search: { + key: 'Search', + transition: RouteTransitions.Horizontal + }, SelectServer: { key: 'SelectServer', title: {id: 'mobile.routes.enterServerUrl', defaultMessage: 'Enter Server URL'} }, - Login: { - key: 'Login', - title: {id: 'mobile.routes.login', defaultMessage: 'Login'} - }, SelectTeam: { key: 'SelectTeam', - title: {id: 'mobile.routes.selectTeam', defaultMessage: 'Select Team'} - }, - ChannelView: { - key: 'ChannelView' - }, - Search: { - key: 'Search' - }, - ChannelInfo: { - key: 'ChannelInfo', - title: {id: 'mobile.routes.channelInfo', defaultMessage: 'Info'} + title: {id: 'mobile.routes.selectTeam', defaultMessage: 'Select Team'}, + transition: RouteTransitions.Horizontal } }; + +export default Routes; diff --git a/app/reducers/navigation/index.js b/app/reducers/navigation/index.js index 9ed8e2a11..7b14bd20d 100644 --- a/app/reducers/navigation/index.js +++ b/app/reducers/navigation/index.js @@ -12,7 +12,11 @@ const initialState = { index: 0, routes: [ Routes.Root - ] + ], + leftDrawerOpen: false, + leftDrawerRoute: null, + rightDrawerOpen: false, + rightDrawerRoute: null }; export default function(state = initialState, action) { @@ -21,8 +25,37 @@ export default function(state = initialState, action) { return NavigationExperimental.StateUtils.push(state, action.route); case NavigationTypes.NAVIGATION_POP: + if (state.leftDrawerOpen || state.rightDrawerOpen) { + return { + ...state, + leftDrawerOpen: false, + rightDrawerOpen: false + }; + } + return NavigationExperimental.StateUtils.pop(state); + case NavigationTypes.NAVIGATION_OPEN_LEFT_DRAWER: + return { + ...state, + leftDrawerOpen: true, + leftDrawerRoute: action.route + }; + + case NavigationTypes.NAVIGATION_OPEN_RIGHT_DRAWER: + return { + ...state, + rightDrawerOpen: true, + rightDrawerRoute: action.route + }; + + case NavigationTypes.NAVIGATION_CLOSE_DRAWERS: + return { + ...state, + leftDrawerOpen: false, + rightDrawerOpen: false + }; + case NavigationTypes.NAVIGATION_JUMP: return NavigationExperimental.StateUtils.jumpTo(state, action.key); @@ -30,7 +63,15 @@ export default function(state = initialState, action) { return NavigationExperimental.StateUtils.jumpToIndex(state, action.index); case NavigationTypes.NAVIGATION_RESET: - return NavigationExperimental.StateUtils.reset(state, action.routes, action.index); + return { + ...state, + ...NavigationExperimental.StateUtils.reset(state, action.routes, action.index), + leftDrawerOpen: false, + rightDrawerOpen: false + }; + + case NavigationTypes.NAVIGATION_REPLACE: + return NavigationExperimental.StateUtils.replaceAtIndex(state, state.index, action.route); case UsersTypes.LOGOUT_SUCCESS: return NavigationExperimental.StateUtils.reset(state, initialState.routes, initialState.index); diff --git a/app/reducers/views/drawer.js b/app/reducers/views/drawer.js deleted file mode 100644 index c4f1e1c1b..000000000 --- a/app/reducers/views/drawer.js +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import {combineReducers} from 'redux'; -import {ViewTypes} from 'app/constants'; - -function channel(state = false, action) { - switch (action.type) { - case ViewTypes.TOGGLE_CHANNEL_DRAWER: - return action.data; - - default: - return state; - } -} - -export default combineReducers({ - channel -}); - diff --git a/app/reducers/views/index.js b/app/reducers/views/index.js index 29acc036e..bb9fd29c2 100644 --- a/app/reducers/views/index.js +++ b/app/reducers/views/index.js @@ -4,14 +4,12 @@ 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'; export default combineReducers({ channel, - drawer, i18n, login, selectServer diff --git a/app/scenes/channel/channel.js b/app/scenes/channel/channel.js index 4f51f6602..dfac84d13 100644 --- a/app/scenes/channel/channel.js +++ b/app/scenes/channel/channel.js @@ -7,11 +7,8 @@ import { 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 ChannelHeader from './channel_header'; import ChannelPostList from './channel_post_list'; @@ -23,6 +20,7 @@ export default class Channel extends React.PureComponent { loadProfilesAndTeamMembersForDMSidebar: React.PropTypes.func.isRequired, selectInitialChannel: React.PropTypes.func.isRequired, openChannelDrawer: React.PropTypes.func.isRequired, + openRightMenuDrawer: React.PropTypes.func.isRequired, handlePostDraftChanged: React.PropTypes.func.isRequired, goToChannelInfo: React.PropTypes.func.isRequired }).isRequired, @@ -60,20 +58,16 @@ export default class Channel extends React.PureComponent { }); }; - openRightSidebar = () => { - this.refs.postTextbox.getWrappedInstance().blur(); - this.setState({rightSidebarOpen: true}); - }; - - closeRightSidebar = () => { - this.setState({rightSidebarOpen: false}); - }; - openChannelDrawer = () => { this.refs.postTextbox.getWrappedInstance().blur(); this.props.actions.openChannelDrawer(); } + openRightMenuDrawer = () => { + this.refs.postTextbox.getWrappedInstance().blur(); + this.props.actions.openRightMenuDrawer(); + } + render() { const { currentTeam, @@ -93,36 +87,20 @@ export default class Channel extends React.PureComponent { style={{flex: 1, backgroundColor: theme.centerChannelBg}} > - - } - side='right' - tapToClose={true} - onCloseStart={this.closeRightSidebar} - openDrawerOffset={0.2} - > - - - - - + openLeftDrawer={this.openChannelDrawer} + openRightDrawer={this.openRightMenuDrawer} + goToChannelInfo={this.props.actions.goToChannelInfo} + /> + + ); } diff --git a/app/scenes/channel/channel_container.js b/app/scenes/channel/channel_container.js index 2b14d299e..d2a1fd243 100644 --- a/app/scenes/channel/channel_container.js +++ b/app/scenes/channel/channel_container.js @@ -4,18 +4,21 @@ import {bindActionCreators} from 'redux'; import {connect} from 'react-redux'; +import { + goToChannelInfo, + openChannelDrawer, + openRightMenuDrawer +} from 'app/actions/navigation'; import { loadChannelsIfNecessary, loadProfilesAndTeamMembersForDMSidebar, selectInitialChannel, handlePostDraftChanged } from 'app/actions/views/channel'; -import {openChannelDrawer} from 'app/actions/views/drawer'; import {getCurrentChannel} from 'service/selectors/entities/channels'; import {getTheme} from 'service/selectors/entities/preferences'; import {getCurrentTeam} from 'service/selectors/entities/teams'; -import {goToChannelInfo} from 'app/actions/navigation'; import Channel from './channel'; @@ -36,6 +39,7 @@ function mapDispatchToProps(dispatch) { loadProfilesAndTeamMembersForDMSidebar, selectInitialChannel, openChannelDrawer, + openRightMenuDrawer, handlePostDraftChanged, goToChannelInfo }, dispatch) diff --git a/app/scenes/channel_drawer/channel_drawer.js b/app/scenes/channel_drawer/channel_drawer.js new file mode 100644 index 000000000..3bbbdbcb8 --- /dev/null +++ b/app/scenes/channel_drawer/channel_drawer.js @@ -0,0 +1,50 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; + +import ChannelList from 'app/components/channel_drawer/channel_list'; + +export default class ChannelDrawer extends React.PureComponent { + static propTypes = { + actions: React.PropTypes.shape({ + selectChannel: React.PropTypes.func.isRequired, + viewChannel: React.PropTypes.func.isRequired, + closeDMChannel: React.PropTypes.func.isRequired, + closeDrawers: React.PropTypes.func.isRequired + }).isRequired, + currentTeam: React.PropTypes.object, + currentChannel: React.PropTypes.object, + channels: React.PropTypes.object, + channelMembers: React.PropTypes.object, + theme: React.PropTypes.object.isRequired + }; + + selectChannel = (id) => { + this.props.actions.selectChannel(id); + this.props.actions.closeDrawers(); + } + + render() { + const { + currentChannel, + currentTeam, + channels, + channelMembers, + theme + } = this.props; + + return ( + + ); + } +} diff --git a/app/scenes/channel_drawer/channel_drawer_container.js b/app/scenes/channel_drawer/channel_drawer_container.js new file mode 100644 index 000000000..cd60ec371 --- /dev/null +++ b/app/scenes/channel_drawer/channel_drawer_container.js @@ -0,0 +1,38 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import {bindActionCreators} from 'redux'; +import {connect} from 'react-redux'; + +import {closeDrawers} from 'app/actions/navigation'; +import {closeDMChannel} from 'app/actions/views/channel'; + +import {selectChannel, viewChannel} from 'service/actions/channels'; +import {getChannelsByCategory, getCurrentChannel} from 'service/selectors/entities/channels'; +import {getTheme} from 'service/selectors/entities/preferences'; +import {getCurrentTeam} from 'service/selectors/entities/teams'; + +import ChannelDrawer from './channel_drawer.js'; + +function mapStateToProps(state) { + return { + currentTeam: getCurrentTeam(state), + currentChannel: getCurrentChannel(state), + channels: getChannelsByCategory(state), + channelMembers: state.entities.channels.myMembers, + theme: getTheme(state) + }; +} + +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators({ + selectChannel, + viewChannel, + closeDMChannel, + closeDrawers + }, dispatch) + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(ChannelDrawer); diff --git a/app/components/channel_drawer/index.js b/app/scenes/channel_drawer/index.js similarity index 71% rename from app/components/channel_drawer/index.js rename to app/scenes/channel_drawer/index.js index 859043ad5..79e9d4d33 100644 --- a/app/components/channel_drawer/index.js +++ b/app/scenes/channel_drawer/index.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. import ChannelDrawerContainer from './channel_drawer_container'; diff --git a/app/scenes/index.js b/app/scenes/index.js index 96fb53b6d..472ecd989 100644 --- a/app/scenes/index.js +++ b/app/scenes/index.js @@ -2,21 +2,27 @@ // See License.txt for license information. import Channel from './channel'; +import ChannelDrawer from './channel_drawer'; +import ChannelInfo from './channel_info'; +import LoadTeam from './load_team'; import Login from './login/login_container.js'; +import RightMenuDrawer from './right_menu_drawer'; import Root from './root/root_container.js'; import Search from './search/search_container.js'; import SelectServer from './select_server/select_server_container.js'; import SelectTeam from './select_team/select_team_container.js'; -import ChannelInfo from './channel_info'; const scenes = { - Root, - SelectServer, - Login, - SelectTeam, ChannelView: Channel, // Special case the name for this one to avoid ambiguity + ChannelDrawer, + ChannelInfo, + LoadTeam, + Login, + RightMenuDrawer, + Root, Search, - ChannelInfo + SelectServer, + SelectTeam }; export function getComponentForScene(key) { diff --git a/app/scenes/load_team/index.js b/app/scenes/load_team/index.js new file mode 100644 index 000000000..e1315349f --- /dev/null +++ b/app/scenes/load_team/index.js @@ -0,0 +1,6 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import LoadTeamContainer from './load_team_container'; + +export default LoadTeamContainer; diff --git a/app/scenes/load_team/load_team.js b/app/scenes/load_team/load_team.js new file mode 100644 index 000000000..ff4f3da98 --- /dev/null +++ b/app/scenes/load_team/load_team.js @@ -0,0 +1,60 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; + +import Loading from 'app/components/loading'; + +import {RequestStatus} from 'service/constants'; + +export default class LoadTeam extends React.Component { + static propTypes = { + teams: React.PropTypes.object.isRequired, + myMembers: React.PropTypes.object.isRequired, + teamsRequest: React.PropTypes.object.isRequired, + currentTeam: React.PropTypes.object, + actions: React.PropTypes.shape({ + goToChannelView: React.PropTypes.func.isRequired, + handleTeamChange: React.PropTypes.func.isRequired, + initWebsocket: React.PropTypes.func.isRequired + }).isRequired + }; + + componentWillMount() { + this.props.actions.initWebsocket(); + } + + componentDidMount() { + const {currentTeam, myMembers, teams} = this.props; + + if (currentTeam) { + this.onSelectTeam(currentTeam); + } else if (!currentTeam) { + this.selectFirstTeam(teams, myMembers); + } + } + + componentWillReceiveProps(nextProps) { + if (this.props.teamsRequest.status === RequestStatus.STARTED && + nextProps.teamsRequest.status === RequestStatus.SUCCESS) { + this.selectFirstTeam(nextProps.teams, nextProps.myMembers); + } + } + + selectFirstTeam(allTeams, myMembers) { + const teams = Object.keys(myMembers).map((key) => allTeams[key]); + const firstTeam = Object.values(teams).sort((a, b) => a.display_name.localeCompare(b.display_name))[0]; + + if (firstTeam) { + this.onSelectTeam(firstTeam); + } + } + + onSelectTeam(team) { + this.props.actions.handleTeamChange(team).then(this.props.actions.goToChannelView); + } + + render() { + return ; + } +} diff --git a/app/scenes/load_team/load_team_container.js b/app/scenes/load_team/load_team_container.js new file mode 100644 index 000000000..6c5ac5bb6 --- /dev/null +++ b/app/scenes/load_team/load_team_container.js @@ -0,0 +1,35 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import {bindActionCreators} from 'redux'; +import {connect} from 'react-redux'; + +import {goToChannelView} from 'app/actions/views/load_team'; +import {handleTeamChange} from 'app/actions/views/select_team'; + +import {init} from 'service/actions/websocket'; +import {getCurrentTeam} from 'service/selectors/entities/teams'; + +import LoadTeam from './load_team.js'; + +function mapStateToProps(state) { + return { + config: state.entities.general.config, + teamsRequest: state.requests.teams.allTeams, + teams: state.entities.teams.teams, + currentTeam: getCurrentTeam(state), + myMembers: state.entities.teams.myMembers + }; +} + +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators({ + goToChannelView, + handleTeamChange, + initWebsocket: init + }, dispatch) + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(LoadTeam); diff --git a/app/scenes/login/login.js b/app/scenes/login/login.js index 1dab9dcba..91abd37fb 100644 --- a/app/scenes/login/login.js +++ b/app/scenes/login/login.js @@ -35,7 +35,7 @@ class Login extends Component { componentWillReceiveProps(nextProps) { if (this.props.loginRequest.status === RequestStatus.STARTED && nextProps.loginRequest.status === RequestStatus.SUCCESS) { - this.props.actions.handleSuccessfulLogin().then(this.props.actions.goToSelectTeam); + this.props.actions.handleSuccessfulLogin().then(this.props.actions.goToLoadTeam); } } diff --git a/app/scenes/login/login_container.js b/app/scenes/login/login_container.js index 823b5006b..bc348dd02 100644 --- a/app/scenes/login/login_container.js +++ b/app/scenes/login/login_container.js @@ -6,8 +6,7 @@ import {connect} from 'react-redux'; import {getClientConfig, getLicenseConfig} from 'service/actions/general'; import LoginActions from 'app/actions/views/login'; -import {updateRootStorage} from 'app/actions/storage'; -import {goToSelectTeam} from 'app/actions/navigation'; +import {goToLoadTeam} from 'app/actions/navigation'; import {login} from 'service/actions/users'; import Login from './login.js'; @@ -27,11 +26,10 @@ function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ ...LoginActions, - updateRootStorage, login, getClientConfig, getLicenseConfig, - goToSelectTeam + goToLoadTeam }, dispatch) }; } diff --git a/app/scenes/right_menu_drawer/index.js b/app/scenes/right_menu_drawer/index.js new file mode 100644 index 000000000..ef150b1e5 --- /dev/null +++ b/app/scenes/right_menu_drawer/index.js @@ -0,0 +1,6 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import RightMenuDrawerContainer from './right_menu_drawer_container'; + +export default RightMenuDrawerContainer; diff --git a/app/components/right_sidebar_menu/right_sidebar_menu.js b/app/scenes/right_menu_drawer/right_menu_drawer.js similarity index 80% rename from app/components/right_sidebar_menu/right_sidebar_menu.js rename to app/scenes/right_menu_drawer/right_menu_drawer.js index 6d94777c0..fad51b302 100644 --- a/app/components/right_sidebar_menu/right_sidebar_menu.js +++ b/app/scenes/right_menu_drawer/right_menu_drawer.js @@ -2,16 +2,17 @@ // See License.txt for license information. import React from 'react'; - -import FormattedText from 'app/components/formatted_text'; -import Icon from 'react-native-vector-icons/FontAwesome'; -import Item from './components/item'; import { ScrollView, StyleSheet, Text, View } from 'react-native'; +import Icon from 'react-native-vector-icons/FontAwesome'; + +import FormattedText from 'app/components/formatted_text'; + +import RightMenuDrawerItem from './right_menu_drawer_item'; const Styles = StyleSheet.create({ container: { @@ -36,43 +37,28 @@ const Styles = StyleSheet.create({ } }); -export default class RightSidebarMenu extends React.Component { +export default class RightMenuDrawer extends React.Component { static propTypes = { actions: React.PropTypes.shape({ - goBack: React.PropTypes.func.isRequired, goToFlaggedPosts: React.PropTypes.func.isRequired, goToRecentMentions: React.PropTypes.func.isRequired, + goToSelectTeam: React.PropTypes.func.isRequired, logout: React.PropTypes.func.isRequired - }).isRequired, - onClose: React.PropTypes.func.isRequired - } - - goToRecentMentions = () => { - this.props.onClose(); - this.props.actions.goToRecentMentions(); - } - - goToFlaggedPosts = () => { - this.props.onClose(); - this.props.actions.goToFlaggedPosts(); - } - - goToTeamSelection = () => { - this.props.actions.goBack(); + }).isRequired } render() { return ( - + {'@'} - - + + - + - + - - + + - - + + - + - + - - + + - - + + - + - + - - + + - - + + - + - + - + ); } diff --git a/app/components/right_sidebar_menu/right_sidebar_menu_container.js b/app/scenes/right_menu_drawer/right_menu_drawer_container.js similarity index 70% rename from app/components/right_sidebar_menu/right_sidebar_menu_container.js rename to app/scenes/right_menu_drawer/right_menu_drawer_container.js index 0620c7cf3..28c5dd489 100644 --- a/app/components/right_sidebar_menu/right_sidebar_menu_container.js +++ b/app/scenes/right_menu_drawer/right_menu_drawer_container.js @@ -4,10 +4,12 @@ import {bindActionCreators} from 'redux'; import {connect} from 'react-redux'; -import {goBack, goToFlaggedPosts, goToRecentMentions} from 'app/actions/navigation'; +import {goBack} from 'app/actions/navigation'; +import {goToFlaggedPosts, goToRecentMentions, goToSelectTeam} from 'app/actions/views/right_menu_drawer'; + import {logout} from 'service/actions/users'; -import RightSidebarMenu from './right_sidebar_menu'; +import RightMenuDrawer from './right_menu_drawer'; function mapStateToProps(state, ownProps) { return ownProps; @@ -19,9 +21,10 @@ function mapDispatchToProps(dispatch) { goBack, goToFlaggedPosts, goToRecentMentions, + goToSelectTeam, logout }, dispatch) }; } -export default connect(mapStateToProps, mapDispatchToProps)(RightSidebarMenu); +export default connect(mapStateToProps, mapDispatchToProps)(RightMenuDrawer); diff --git a/app/components/right_sidebar_menu/components/item.js b/app/scenes/right_menu_drawer/right_menu_drawer_item.js similarity index 94% rename from app/components/right_sidebar_menu/components/item.js rename to app/scenes/right_menu_drawer/right_menu_drawer_item.js index 0942bb603..3a73e829f 100644 --- a/app/components/right_sidebar_menu/components/item.js +++ b/app/scenes/right_menu_drawer/right_menu_drawer_item.js @@ -16,7 +16,7 @@ const Styles = StyleSheet.create({ } }); -export default class Item extends React.Component { +export default class MainMenuItem extends React.Component { static propTypes = { children: React.PropTypes.node, onPress: React.PropTypes.func, diff --git a/app/scenes/root/root.js b/app/scenes/root/root.js index b77d9fd09..c1b8a6232 100644 --- a/app/scenes/root/root.js +++ b/app/scenes/root/root.js @@ -12,9 +12,8 @@ export default class Root extends React.Component { logoutRequest: React.PropTypes.object, loginRequest: React.PropTypes.object, actions: React.PropTypes.shape({ - goToChannelView: React.PropTypes.func, + goToLoadTeam: React.PropTypes.func, goToSelectServer: React.PropTypes.func, - goToSelectTeam: React.PropTypes.func, loadStorage: React.PropTypes.func, removeStorage: React.PropTypes.func, setStoreFromLocalData: React.PropTypes.func, @@ -24,7 +23,6 @@ export default class Root extends React.Component { componentDidMount() { // Any initialization logic for navigation, setting up the client, etc should go here - this.init(); } @@ -53,7 +51,7 @@ export default class Root extends React.Component { if (this.props.credentials.token && this.props.credentials.url) { this.props.actions.setStoreFromLocalData(this.props.credentials).then(() => { if (this.props.loginRequest.status === RequestStatus.SUCCESS) { - this.props.actions.goToSelectTeam(); + this.props.actions.goToLoadTeam(); } else { this.props.actions.goToSelectServer(); } diff --git a/app/scenes/root/root_container.js b/app/scenes/root/root_container.js index 53fe0d11e..bcbf4bb1a 100644 --- a/app/scenes/root/root_container.js +++ b/app/scenes/root/root_container.js @@ -6,7 +6,7 @@ import {connect} from 'react-redux'; import {loadStorage, removeStorage} from 'app/actions/storage'; import {goToSelectServer, setStoreFromLocalData} from 'app/actions/views/root'; -import {goToSelectTeam, goToChannelView} from 'app/actions/navigation'; +import {goToLoadTeam} from 'app/actions/navigation'; import {resetLogout} from 'service/actions/users'; import Root from './root'; @@ -23,9 +23,8 @@ function mapStateToProps(state, ownProps) { function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ - goToChannelView, + goToLoadTeam, goToSelectServer, - goToSelectTeam, loadStorage, removeStorage, setStoreFromLocalData, diff --git a/app/scenes/select_team/select_team.js b/app/scenes/select_team/select_team.js index 5a8807b35..5eb6ab50c 100644 --- a/app/scenes/select_team/select_team.js +++ b/app/scenes/select_team/select_team.js @@ -1,68 +1,32 @@ // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import React, {Component, PropTypes} from 'react'; +import React from 'react'; import {View, Image, Text} from 'react-native'; import Button from 'react-native-button'; import Icon from 'react-native-vector-icons/MaterialIcons'; -import ErrorText from 'app/components/error_text'; import FormattedText from 'app/components/formatted_text'; -import Loading from 'app/components/loading'; import {GlobalStyles} from 'app/styles'; import logo from 'assets/images/logo.png'; -import {RequestStatus} from 'service/constants'; - -export default class SelectTeam extends Component { +export default class SelectTeam extends React.Component { static propTypes = { - config: PropTypes.object.isRequired, - teams: PropTypes.object.isRequired, - myMembers: PropTypes.object.isRequired, - teamsRequest: PropTypes.object.isRequired, - currentTeam: PropTypes.object, - actions: PropTypes.object.isRequired + config: React.PropTypes.object.isRequired, + teams: React.PropTypes.object.isRequired, + myMembers: React.PropTypes.object.isRequired, + actions: React.PropTypes.shape({ + goBackToChannelView: React.PropTypes.func.isRequired, + handleTeamChange: React.PropTypes.func.isRequired + }).isRequired }; - componentWillMount() { - this.props.actions.websocket(); - } - - componentDidMount() { - const {currentTeam, myMembers, teams} = this.props; - - if (currentTeam) { - this.onSelectTeam(currentTeam); - } else if (!currentTeam) { - this.selectFirstTeam(teams, myMembers); - } - } - - componentWillReceiveProps(nextProps) { - if (this.props.teamsRequest.status === RequestStatus.STARTED && - nextProps.teamsRequest.status === RequestStatus.SUCCESS) { - this.selectFirstTeam(nextProps.teams, nextProps.myMembers); - } - } - - selectFirstTeam(allTeams, myMembers) { - const teams = Object.keys(myMembers).map((key) => allTeams[key]); - const firstTeam = Object.values(teams).sort((a, b) => a.display_name.localeCompare(b.display_name))[0]; - if (firstTeam) { - this.onSelectTeam(firstTeam); - } - } - onSelectTeam(team) { - this.props.actions.handleTeamChange(team).then(this.props.actions.goToChannelView); + this.props.actions.handleTeamChange(team).then(this.props.actions.goBackToChannelView); } render() { - if (this.props.teamsRequest.status === RequestStatus.STARTED) { - return ; - } - const teams = []; for (const id of Object.keys(this.props.teams)) { const team = this.props.teams[id]; @@ -106,7 +70,6 @@ export default class SelectTeam extends Component { defaultMessage='Your teams:' /> {teams} - ); } diff --git a/app/scenes/select_team/select_team_container.js b/app/scenes/select_team/select_team_container.js index fbacab8e2..17323e12e 100644 --- a/app/scenes/select_team/select_team_container.js +++ b/app/scenes/select_team/select_team_container.js @@ -4,13 +4,12 @@ import {bindActionCreators} from 'redux'; import {connect} from 'react-redux'; +import {goBack} from 'app/actions/navigation'; import {handleTeamChange} from 'app/actions/views/select_team'; -import {init as websocket} from 'service/actions/websocket'; -import {updateRootStorage, loadTeamStorage} from 'app/actions/storage'; -import {goToChannelView} from 'app/actions/navigation'; + import {getCurrentTeam} from 'service/selectors/entities/teams'; -import SelectTeamView from './select_team.js'; +import SelectTeam from './select_team.js'; function mapStateToProps(state) { return { @@ -25,13 +24,10 @@ function mapStateToProps(state) { function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ - goToChannelView, - updateRootStorage, - loadTeamStorage, - handleTeamChange, - websocket + goBackToChannelView: goBack, + handleTeamChange }, dispatch) }; } -export default connect(mapStateToProps, mapDispatchToProps)(SelectTeamView); +export default connect(mapStateToProps, mapDispatchToProps)(SelectTeam); diff --git a/test/app/reducers/navigation.test.js b/test/app/reducers/navigation.test.js index 836d4377d..ab5395fc0 100644 --- a/test/app/reducers/navigation.test.js +++ b/test/app/reducers/navigation.test.js @@ -22,7 +22,11 @@ describe('Reducers.Navigation', () => { assert.deepEqual(state, { index: 0, - routes: [Routes.Root] + routes: [Routes.Root], + leftDrawerOpen: false, + leftDrawerRoute: null, + rightDrawerOpen: false, + rightDrawerRoute: null }, 'initial state'); }); @@ -31,44 +35,38 @@ describe('Reducers.Navigation', () => { it('first push', () => { state = reduceAndFreeze(state, { - type: NavigationTypes.PUSH, + type: NavigationTypes.NAVIGATION_PUSH, route: {key: 'fake'} }); - assert.deepEqual(state, { - index: 1, - routes: [Routes.Root, {key: 'fake'}] - }); + assert.equal(state.index, 1); + assert.deepEqual(state.routes, [Routes.Root, {key: 'fake'}]); }); it('second push', () => { state = reduceAndFreeze(state, { - type: NavigationTypes.PUSH, + type: NavigationTypes.NAVIGATION_PUSH, route: {key: 'fake2'} }); - assert.deepEqual(state, { - index: 1, - routes: [Routes.Root, {key: 'fake'}, {key: 'fake2'}] - }); + assert.equal(state.index, 2); + assert.deepEqual(state.routes, [Routes.Root, {key: 'fake2'}]); + }); + + state = reduceAndFreeze(state, { + type: NavigationTypes.NAVIGATION_JUMP, + key: Routes.Root.key }); it('push when not at head of stack', () => { state = reduceAndFreeze(state, { - type: NavigationTypes.JUMP, - key: Routes.key.root - }); - - state = reduceAndFreeze(state, { - type: NavigationTypes.PUSH, + type: NavigationTypes.NAVIGATION_PUSH, route: {key: 'fake3'} }); // The next route on the stack is replaced, but anything past that is left unchanged - assert.deepEqual(state, { - index: 1, - routes: [Routes.Root, {key: 'fake3'}, {key: 'fake2'}] - }); + assert.equal(state.index, 1); + assert.deepEqual(state.routes, [Routes.Root, {key: 'fake3'}, {key: 'fake2'}]); }); }); @@ -76,45 +74,146 @@ describe('Reducers.Navigation', () => { let state = initialState(); state = reduceAndFreeze(state, { - type: NavigationTypes.PUSH, + type: NavigationTypes.NAVIGATION_PUSH, route: {key: 'fake'} }); state = reduceAndFreeze(state, { - type: NavigationTypes.PUSH, + type: NavigationTypes.NAVIGATION_PUSH, route: {key: 'fake2'} }); it('first pop', () => { state = reduceAndFreeze(state, { - type: NavigationTypes.POP + type: NavigationTypes.NAVIGATION_POP }); - assert.deepEqual(state, { - index: 1, - routes: [Routes.Root, {key: 'fake'}] - }); + assert.equal(state.index, 1); + assert.deepEqual(state.routes, [Routes.Root, {key: 'fake'}]); }); it('second pop', () => { state = reduceAndFreeze(state, { - type: NavigationTypes.POP + type: NavigationTypes.NAVIGATION_POP }); - assert.deepEqual(state, { - index: 0, - routes: [Routes.Root] - }); + assert.equal(state.index, 0); + assert.deepEqual(state.routes, [Routes.Root]); }); it('pop last entry on stack', () => { state = reduceAndFreeze(state, { - type: NavigationTypes.POP + type: NavigationTypes.NAVIGATION_POP }); - assert.deepEqual(state, { - index: 0, - routes: [Routes.Root] + // Nothing happens + assert.equal(state.index, 0); + assert.deepEqual(state.routes, [Routes.Root]); + }); + + state = reduceAndFreeze(state, { + type: NavigationTypes.NAVIGATION_PUSH, + route: {key: 'fake3'} + }); + state = reduceAndFreeze(state, { + type: NavigationTypes.NAVIGATION_OPEN_LEFT_DRAWER, + route: {key: 'fake'} + }); + + it('popping to close left drawer', () => { + state.reduceAndFreeze(state, { + type: NavigationTypes.NAVIGATION_POP }); + + // Routes not popped, but drawer closed + assert.equal(state.index, 1); + assert.deepEqual(state.routes, [Routes.Root, {key: 'fake3'}]); + assert.equal(state.leftDrawerOpen, false); + assert.equal(state.rightDrawerOpen, false); + }); + + state = reduceAndFreeze(state, { + type: NavigationTypes.NAVIGATION_OPEN_RIGHT_DRAWER, + route: {key: 'fake'} + }); + + it('popping to close right drawer', () => { + state.reduceAndFreeze(state, { + type: NavigationTypes.NAVIGATION_POP + }); + + // Routes not popped, but drawer closed + assert.equal(state.index, 1); + assert.deepEqual(state.routes, [Routes.Root, {key: 'fake'}]); + assert.equal(state.leftDrawerOpen, false); + assert.equal(state.rightDrawerOpen, false); + }); + }); + + it('NAVIGATION_OPEN_LEFT_DRAWER', () => { + let state = initialState(); + + it('open left drawer', () => { + state = reduceAndFreeze(state, { + type: NavigationTypes.NAVIGATION_OPEN_LEFT_DRAWER, + route: {key: 'fake'} + }); + + assert.equal(state.index, 0); + assert.deepEqual(state.routes, [Routes.Root]); + assert.equal(state.leftDrawerOpen, true); + assert.deepEqual(state.leftDrawerRoute, {key: 'fake'}); + assert.equal(state.rightDrawerOpen, false); + }); + }); + + it('NAVIGATION_OPEN_RIGHT_DRAWER', () => { + let state = initialState(); + + it('open right drawer', () => { + state = reduceAndFreeze(state, { + type: NavigationTypes.NAVIGATION_OPEN_RIGHT_DRAWER, + route: {key: 'fake'} + }); + + assert.equal(state.index, 0); + assert.deepEqual(state.routes, [Routes.Root]); + assert.equal(state.leftDrawerOpen, false); + assert.equal(state.rightDrawerOpen, true); + assert.deepEqual(state.rightDrawerRoute, {key: 'fake'}); + }); + }); + + it('NAVIGATION_CLOSE_DRAWERS', () => { + let state = initialState(); + + state = reduceAndFreeze(state, { + type: NavigationTypes.NAVIGATION_OPEN_LEFT_DRAWER, + route: {key: 'fake'} + }); + + it('close left drawer', () => { + state = reduceAndFreeze(state, { + type: NavigationTypes.NAVIGATION_CLOSE_DRAWERS + }); + + assert.equal(state.index, 0); + assert.deepEqual(state.routes, [Routes.Root]); + assert.equal(state.leftDrawerOpen, false); + }); + + state = reduceAndFreeze(state, { + type: NavigationTypes.NAVIGATION_OPEN_RIGHT_DRAWER, + route: {key: 'fake'} + }); + + it('close right drawer', () => { + state = reduceAndFreeze(state, { + type: NavigationTypes.NAVIGATION_CLOSE_DRAWERS + }); + + assert.equal(state.index, 0); + assert.deepEqual(state.routes, [Routes.Root]); + assert.equal(state.rightDrawerOpen, false); }); }); @@ -122,11 +221,11 @@ describe('Reducers.Navigation', () => { let state = initialState(); state = reduceAndFreeze(state, { - type: NavigationTypes.PUSH, + type: NavigationTypes.NAVIGATION_PUSH, route: {key: 'fake'} }); state = reduceAndFreeze(state, { - type: NavigationTypes.PUSH, + type: NavigationTypes.NAVIGATION_PUSH, route: {key: 'fake2'} }); @@ -137,10 +236,8 @@ describe('Reducers.Navigation', () => { }); // The rest of the stack is preserved - assert.deepEqual(state, { - index: 0, - routes: [Routes.Root, {key: 'fake'}, {key: 'fake2'}] - }); + assert.equal(state.index, 0); + assert.deepEqual(state.routes, [Routes.Root, {key: 'fake'}, {key: 'fake2'}]); }); it('jump forward', () => { @@ -149,10 +246,8 @@ describe('Reducers.Navigation', () => { key: 'fake' }); - assert.deepEqual(state, { - index: 2, - routes: [Routes.Root, {key: 'fake'}, {key: 'fake2'}] - }); + assert.equal(state.index, 2); + assert.deepEqual(state.routes, [Routes.Root, {key: 'fake'}, {key: 'fake2'}]); }); }); @@ -160,11 +255,11 @@ describe('Reducers.Navigation', () => { let state = initialState(); state = reduceAndFreeze(state, { - type: NavigationTypes.PUSH, + type: NavigationTypes.NAVIGATION_PUSH, route: {key: 'fake'} }); state = reduceAndFreeze(state, { - type: NavigationTypes.PUSH, + type: NavigationTypes.NAVIGATION_PUSH, route: {key: 'fake2'} }); @@ -175,10 +270,8 @@ describe('Reducers.Navigation', () => { }); // The rest of the stack is preserved - assert.deepEqual(state, { - index: 0, - routes: [Routes.Root, {key: 'fake'}, {key: 'fake2'}] - }); + assert.equal(state.index, 0); + assert.deepEqual(state.routes, [Routes.Root, {key: 'fake'}, {key: 'fake2'}]); }); it('jump forward', () => { @@ -187,10 +280,8 @@ describe('Reducers.Navigation', () => { index: 2 }); - assert.deepEqual(state, { - index: 2, - routes: [Routes.Root, {key: 'fake'}, {key: 'fake2'}] - }); + assert.equal(state.index, 2); + assert.deepEqual(state.routes, [Routes.Root, {key: 'fake'}, {key: 'fake2'}]); }); }); @@ -198,11 +289,11 @@ describe('Reducers.Navigation', () => { let state = initialState(); state = reduceAndFreeze(state, { - type: NavigationTypes.PUSH, + type: NavigationTypes.NAVIGATION_PUSH, route: {key: 'fake'} }); state = reduceAndFreeze(state, { - type: NavigationTypes.PUSH, + type: NavigationTypes.NAVIGATION_PUSH, route: {key: 'fake2'} }); @@ -215,9 +306,67 @@ describe('Reducers.Navigation', () => { // The navigation state is wiped out and replaced with the new state assert.deepEqual(state, { + index: 1, + routes: [{key: 'fake3'}, {key: 'fake4'}], + leftDrawerOpen: false, + leftDrawerRoute: null, + rightDrawerOpen: false, + rightDrawerRoute: null + }); + }); + + state = initialState(); + state = reduceAndFreeze(state, { + type: NavigationTypes.NAVIGATION_PUSH, + route: {key: 'fake'} + }); + state = reduceAndFreeze(state, { + type: NavigationTypes.NAVIGATION_OPEN_LEFT_DRAWER, + route: {key: 'fake2'} + }); + + it('reset with left drawer open', () => { + state = reduceAndFreeze(state, { + type: NavigationTypes.NAVIGATION_RESET, index: 1, routes: [{key: 'fake3'}, {key: 'fake4'}] }); + + assert.deepEqual(state, { + index: 1, + routes: [{key: 'fake3'}, {key: 'fake4'}], + leftDrawerOpen: false, + leftDrawerRoute: {key: 'fake2'}, // Leave drawer routes in tact so that the close animation still works + rightDrawerOpen: false, + rightDrawerRoute: null + }); + }); + + state = initialState(); + state = reduceAndFreeze(state, { + type: NavigationTypes.NAVIGATION_PUSH, + route: {key: 'fake'} + }); + state = reduceAndFreeze(state, { + type: NavigationTypes.NAVIGATION_OPEN_RIGHT_DRAWER, + route: {key: 'fake2'} + }); + + it('reset with left drawer open', () => { + state = reduceAndFreeze(state, { + type: NavigationTypes.NAVIGATION_RESET, + index: 1, + routes: [{key: 'fake3'}, {key: 'fake4'}] + }); + + assert.deepEqual(state, { + index: 1, + routes: [{key: 'fake3'}, {key: 'fake4'}], + leftDrawerOpen: false, + leftDrawerRoute: null, + rightDrawerOpen: false, + rightDrawerRoute: {key: 'fake2'} // Leave drawer routes in tact so that the close animation still works + }); }); }); });