This is an automated email from the ASF dual-hosted git repository. amaranhao pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/couchdb-fauxton.git
The following commit(s) were added to refs/heads/master by this push: new add43fc Update fauxton/navigation to use redux (#1142) add43fc is described below commit add43fc566196a854e7e3021e0fd084f83b37317 Author: Antonio Maranhao <30349380+antonio-maran...@users.noreply.github.com> AuthorDate: Mon Oct 15 21:46:04 2018 -0400 Update fauxton/navigation to use redux (#1142) * Use redux * Update tests --- app/addons/fauxton/appwrapper.js | 44 ++-- app/addons/fauxton/base.js | 4 +- .../navigation/__tests__/login-logout-test.js | 47 ++-- .../fauxton/navigation/__tests__/navbar-test.js | 54 ++--- .../navigation/__tests__/navigation-reducers.js | 185 +++++++++++++++ .../navigation/__tests__/navigation-store-test.js | 221 ------------------ app/addons/fauxton/navigation/actions.js | 96 ++++---- app/addons/fauxton/navigation/actiontypes.js | 1 - app/addons/fauxton/navigation/components/NavBar.js | 23 +- app/addons/fauxton/navigation/container/NavBar.js | 73 +++--- app/addons/fauxton/navigation/reducers.js | 209 +++++++++++++++++ app/addons/fauxton/navigation/stores.js | 251 --------------------- app/app.js | 12 +- app/core/base.js | 6 +- 14 files changed, 553 insertions(+), 673 deletions(-) diff --git a/app/addons/fauxton/appwrapper.js b/app/addons/fauxton/appwrapper.js index fca8a81..ea81a67 100644 --- a/app/addons/fauxton/appwrapper.js +++ b/app/addons/fauxton/appwrapper.js @@ -11,16 +11,14 @@ // the License. import React from 'react'; +import { connect } from 'react-redux'; import GlobalNotificationsContainer from './notifications/components/GlobalNotificationsContainer'; import NotificationPanelContainer from './notifications/components/NotificationPanelContainer'; import PermanentNotificationContainer from './notifications/components/PermanentNotificationContainer'; import NavBar from './navigation/container/NavBar'; -import NavbarActions from './navigation/actions'; -import Stores from './navigation/stores'; +import * as NavbarActions from './navigation/actions'; import classNames from 'classnames'; -const navBarStore = Stores.navBarStore; - class ContentWrapper extends React.Component { constructor(props) { super(props); @@ -29,14 +27,14 @@ class ContentWrapper extends React.Component { }; if (props.router.currentRouteOptions && props.router.currentRouteOptions.selectedHeader) { - NavbarActions.setNavbarActiveLink(this.state.routerOptions.selectedHeader); + this.props.setNavbarActiveLink(this.state.routerOptions.selectedHeader); } } componentDidMount () { this.props.router.on('new-component', (routerOptions) => { this.setState({routerOptions}); - NavbarActions.setNavbarActiveLink(this.state.routerOptions.selectedHeader); + this.props.setNavbarActiveLink(this.state.routerOptions.selectedHeader); }); this.props.router.on('trigger-update', () => { @@ -55,31 +53,15 @@ class ContentWrapper extends React.Component { } } -export default class App extends React.Component { +class App extends React.Component { constructor (props) { super(props); - this.state = this.getStoreState(); - } - - getStoreState () { - return { - isPrimaryNavMinimized: navBarStore.isMinimized() - }; - } - - componentDidMount () { - navBarStore.on('change', this.onChange, this); - } - - onChange () { - this.setState(this.getStoreState()); } render () { const mainClass = classNames( - {'closeMenu': this.state.isPrimaryNavMinimized} + {'closeMenu': this.props.isPrimaryNavMinimized} ); - return ( <div> <PermanentNotificationContainer /> @@ -91,7 +73,7 @@ export default class App extends React.Component { <div id="app-container"> <div className="wrapper"> <div className="pusher"> - <ContentWrapper router={this.props.router} /> + <ContentWrapper router={this.props.router} setNavbarActiveLink={this.props.setNavbarActiveLink}/> </div> <div id="primary-navbar"> <NavBar/> @@ -103,3 +85,15 @@ export default class App extends React.Component { ); } } + +export default connect( + ({ navigation }) => { + return { + isPrimaryNavMinimized: navigation.isMinimized}; + }, + (dispatch) => { + return { + setNavbarActiveLink: (link) => { dispatch(NavbarActions.setNavbarActiveLink(link)); } + }; + } +)(App); diff --git a/app/addons/fauxton/base.js b/app/addons/fauxton/base.js index 8a3f417..a1bb98e 100644 --- a/app/addons/fauxton/base.js +++ b/app/addons/fauxton/base.js @@ -12,7 +12,8 @@ import app from '../../app'; import FauxtonAPI from '../../core/api'; -import NavigationActions from './navigation/actions'; +import * as NavigationActions from './navigation/actions'; +import navigationReducers from './navigation/reducers'; import notificationsReducer from './notifications/reducers'; import './assets/less/fauxton.less'; @@ -32,6 +33,7 @@ Fauxton.VersionInfo = Backbone.Model.extend({ }); FauxtonAPI.addReducers({ + navigation: navigationReducers, notifications: notificationsReducer }); diff --git a/app/addons/fauxton/navigation/__tests__/login-logout-test.js b/app/addons/fauxton/navigation/__tests__/login-logout-test.js index 097e495..e84c2af 100644 --- a/app/addons/fauxton/navigation/__tests__/login-logout-test.js +++ b/app/addons/fauxton/navigation/__tests__/login-logout-test.js @@ -13,25 +13,23 @@ import React from 'react'; import { mount } from 'enzyme'; - import NavBar from '../components/NavBar'; describe('Navigation Bar', () => { - + const defaultProps = { + activeLink: '', + isMinimized: false, + version: '42', + navLinks: [], + bottomNavLinks: [], + footerNavLinks: [], + isNavBarVisible: true, + isLoginSectionVisible: true, + isLoginVisibleInsteadOfLogout: true, + toggleNavbarMenu: () => {} + }; it('renders with login button when logged out', () => { - const props = { - activeLink: '', - isMinimized: false, - version: '42', - navLinks: [], - bottomNavLinks: [], - footerNavLinks: [], - isNavBarVisible: true, - isLoginSectionVisible: true, - isLoginVisibleInsteadOfLogout: true - }; - - const navBar = mount(<NavBar {...props} />); + const navBar = mount(<NavBar {...defaultProps} />); const button = navBar.find('[href="#/login"]'); expect(button.text()).toContain('Login'); @@ -39,19 +37,11 @@ describe('Navigation Bar', () => { it('renders with logout button when logged in', () => { const props = { - activeLink: '', - isMinimized: false, - version: '42', - navLinks: [], - bottomNavLinks: [], - footerNavLinks: [], username: 'Rocko', - isNavBarVisible: true, - isLoginSectionVisible: true, isLoginVisibleInsteadOfLogout: false }; - const navBar = mount(<NavBar {...props} />); + const navBar = mount(<NavBar {...defaultProps} {...props} />); const button = navBar.find('[href="#/logout"]'); expect(button.text()).toContain('Log Out'); @@ -59,19 +49,12 @@ describe('Navigation Bar', () => { it('Admin Party has no Logout button and no Login button', () => { const props = { - activeLink: '', - isMinimized: false, - version: '42', - navLinks: [], - bottomNavLinks: [], - footerNavLinks: [], username: 'Rocko', - isNavBarVisible: true, isLoginSectionVisible: false, isLoginVisibleInsteadOfLogout: false }; - const navBar = mount(<NavBar {...props} />); + const navBar = mount(<NavBar {...defaultProps} {...props} />); expect(navBar.text()).not.toMatch(/Login/); expect(navBar.text()).not.toMatch(/Log Out/); diff --git a/app/addons/fauxton/navigation/__tests__/navbar-test.js b/app/addons/fauxton/navigation/__tests__/navbar-test.js index 769b945..ba52483 100644 --- a/app/addons/fauxton/navigation/__tests__/navbar-test.js +++ b/app/addons/fauxton/navigation/__tests__/navbar-test.js @@ -9,58 +9,60 @@ // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. -import NavBarContainer from "../container/NavBar"; -import FauxtonAPI from "../../../../core/api"; -import ActionTypes from "../actiontypes"; -import React from "react"; -import ReactDOM from "react-dom"; + import {mount} from 'enzyme'; +import React from 'react'; +import NavBar from '../components/NavBar'; describe('Navigation Bar', () => { - FauxtonAPI.session = { - user: () => {} + const defaultProps = { + isMinimized: true, + version: '', + username: '', + navLinks: [], + bottomNavLinks: [], + footerNavLinks: [], + isNavBarVisible: true, + isLoginSectionVisible: false, + isLoginVisibleInsteadOfLogout: true, + toggleNavbarMenu: () => {} }; it('is displayed by default', () => { - const navbar = mount(<NavBarContainer />); + const navbar = mount(<NavBar {...defaultProps}/>); expect(navbar.find('.faux-navbar').length).toBe(1); }); it('is dynamically displayed by isNavBarVisible', () => { - const navbar = mount(<NavBarContainer />); + const navbar = mount(<NavBar + {...defaultProps} + isNavBarVisible={false}/>); - FauxtonAPI.dispatch({ - type: ActionTypes.NAVBAR_HIDE - }); - navbar.update(); expect(navbar.find('.faux-navbar').length).toBe(0); - FauxtonAPI.dispatch({ - type: ActionTypes.NAVBAR_SHOW - }); + navbar.setProps({ isNavBarVisible: true }); navbar.update(); expect(navbar.find('.faux-navbar').length).toBe(1); }); it('can display items with icon badge', () => { - FauxtonAPI.dispatch({ - type: ActionTypes.ADD_NAVBAR_LINK, - link: { + const navLinks = [ + { href: "#/_with_badge", title: "WithBadge", icon: "fonticon-database", badge: true - } - }); - FauxtonAPI.dispatch({ - type: ActionTypes.ADD_NAVBAR_LINK, - link: { + }, + { href: "#/_without_badge", title: "WithoutBadge", icon: "fonticon-database" } - }); - const navbar = mount(<NavBarContainer />); + ]; + + const navbar = mount(<NavBar + {...defaultProps} + navLinks={navLinks}/>); expect(navbar.find('div[data-nav-name="WithoutBadge"] i.faux-navbar__icon-badge').length, 0); expect(navbar.find('div[data-nav-name="WithBadge"] i.faux-navbar__icon-badge').length, 1); }); diff --git a/app/addons/fauxton/navigation/__tests__/navigation-reducers.js b/app/addons/fauxton/navigation/__tests__/navigation-reducers.js new file mode 100644 index 0000000..f2f9f12 --- /dev/null +++ b/app/addons/fauxton/navigation/__tests__/navigation-reducers.js @@ -0,0 +1,185 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +import ActionTypes from '../actiontypes'; +import reducer from '../reducers'; + +describe('NavBar Reducer', () => { + + describe('add links', () => { + + it('to nav links', () => { + const action = { + type: ActionTypes.ADD_NAVBAR_LINK, + link: { + id: 'mylink' + } + }; + const newState = reducer(undefined, action); + expect(newState.navLinks[0].id).toMatch(action.link.id); + }); + + it('to top nav links', () => { + const action1 = { + type: ActionTypes.ADD_NAVBAR_LINK, + link: { id: 'mylink1' } + }; + const action2 = { + type: ActionTypes.ADD_NAVBAR_LINK, + link: { id: 'mylink2', top: true } + }; + let newState = reducer(undefined, action1); + newState = reducer(newState, action2); + + expect(newState.navLinks[0].id).toMatch(action2.link.id); + }); + + it('to bottom nav', () => { + const action = { + type: ActionTypes.ADD_NAVBAR_LINK, + link: { + id: 'bottomNav', + bottomNav: true + } + }; + const newState = reducer(undefined, action); + expect(newState.bottomNavLinks[0].id).toMatch(action.link.id); + }); + + it('to top of bottom nav', () => { + const action = { + type: ActionTypes.ADD_NAVBAR_LINK, + link: { + id: 'bottomNav', + bottomNav: true, + top: true + } + }; + const newState = reducer(undefined, action); + expect(newState.bottomNavLinks[0].id).toMatch(action.link.id); + }); + + it('to footer nav', () => { + const action = { + type: ActionTypes.ADD_NAVBAR_LINK, + link: { + id: 'footerNav', + footerNav: true + } + }; + const newState = reducer(undefined, action); + expect(newState.footerNavLinks[0].id).toMatch(action.link.id); + }); + }); + + describe('remove link', () => { + it('from nav links', () => { + const action = { + type: ActionTypes.ADD_NAVBAR_LINK, + link: { + id: 'remove_link' + } + }; + let newState = reducer(undefined, action); + action.type = ActionTypes.REMOVE_NAVBAR_LINK; + newState = reducer(newState, action); + + expect(newState.navLinks.length).toBe(0); + }); + + it('remove link from list only when it exists', () => { + const addAction = { + type: ActionTypes.ADD_NAVBAR_LINK, + link: { + id: 'remove_link1', + footerNav: true + } + }; + let newState = reducer(undefined, addAction); + addAction.link = { id: 'remove_link2', footerNav: true }; + newState = reducer(newState, addAction); + addAction.link = { id: 'remove_link3', footerNav: true }; + newState = reducer(newState, addAction); + expect(newState.footerNavLinks.length).toBe(3); + + const removeAction = { + type: ActionTypes.REMOVE_NAVBAR_LINK, + link: addAction.link + }; + newState = reducer(newState, removeAction); + newState = reducer(newState, removeAction); + newState = reducer(newState, removeAction); + expect(newState.footerNavLinks.length).toBe(2); + }); + + it('from bottom nav links', () => { + const action = { + type: ActionTypes.ADD_NAVBAR_LINK, + link: { + id: 'remove_link', + bottomNav: true + } + }; + let newState = reducer(undefined, action); + action.type = ActionTypes.REMOVE_NAVBAR_LINK; + newState = reducer(newState, action); + + expect(newState.bottomNavLinks.length).toBe(0); + }); + + it('from footer nav links', () => { + const action = { + type: ActionTypes.ADD_NAVBAR_LINK, + link: { + id: 'remove_link', + footerNav: true + } + }; + let newState = reducer(undefined, action); + action.type = ActionTypes.REMOVE_NAVBAR_LINK; + newState = reducer(newState, action); + + expect(newState.footerNavLinks.length).toBe(0); + }); + }); + + describe('update link', () => { + it('for nav links', () => { + const action = { + type: ActionTypes.ADD_NAVBAR_LINK, + link: { + id: 'update-link', + title: 'first' + } + }; + let newState = reducer(undefined, action); + action.type = ActionTypes.UPDATE_NAVBAR_LINK; + action.link.title = 'second'; + newState = reducer(newState, action); + + expect(newState.navLinks[0].title).toMatch('second'); + }); + + }); + + describe('set version', () => { + it('stores version number', () => { + const action = { + type: ActionTypes.NAVBAR_SET_VERSION_INFO, + version: 1234 + }; + const newState = reducer(undefined, action); + expect(newState.version).toBe(1234); + }); + + }); +}); diff --git a/app/addons/fauxton/navigation/__tests__/navigation-store-test.js b/app/addons/fauxton/navigation/__tests__/navigation-store-test.js deleted file mode 100644 index c664dce..0000000 --- a/app/addons/fauxton/navigation/__tests__/navigation-store-test.js +++ /dev/null @@ -1,221 +0,0 @@ -// Licensed under the Apache License, Version 2.0 (the "License"); you may not -// use this file except in compliance with the License. You may obtain a copy of -// the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations under -// the License. -import FauxtonAPI from "../../../../core/api"; -import Stores from "../stores"; -const navBarStore = Stores.navBarStore; - -describe('NavBarStore', () => { - beforeEach(() => { - FauxtonAPI.dispatch({ - type: 'CLEAR_NAVBAR_LINK', - }); - - }); - - describe('add links', () => { - - it('to nav links', () => { - var link = { - id: 'mylink' - }; - FauxtonAPI.dispatch({ - type: 'ADD_NAVBAR_LINK', - link: link - }); - - expect(navBarStore.getNavLinks()[0].id).toMatch(link.id); - }); - - it('to top nav links', () => { - var link1 = { - id: 'mylink1' - }; - - var link2 = { - id: 'mylink2', - top: true - }; - - FauxtonAPI.dispatch({ - type: 'ADD_NAVBAR_LINK', - link: link1 - }); - - FauxtonAPI.dispatch({ - type: 'ADD_NAVBAR_LINK', - link: link2 - }); - - expect(navBarStore.getNavLinks()[0].id).toMatch(link2.id); - }); - - it('to bottom nav', () => { - var link = { - id: 'bottomNav', - bottomNav: true - }; - FauxtonAPI.dispatch({ - type: 'ADD_NAVBAR_LINK', - link: link - }); - - expect(navBarStore.getBottomNavLinks()[0].id).toMatch(link.id); - }); - - it('to top of bottom nav', () => { - var link = { - id: 'bottomNav', - bottomNav: true, - top: true - }; - FauxtonAPI.dispatch({ - type: 'ADD_NAVBAR_LINK', - link: link - }); - - expect(navBarStore.getBottomNavLinks()[0].id).toMatch(link.id); - }); - - it('to footer nav', () => { - var link = { - id: 'footerNav', - footerNav: true - }; - FauxtonAPI.dispatch({ - type: 'ADD_NAVBAR_LINK', - link: link - }); - - expect(navBarStore.getFooterNavLinks()[0].id).toMatch(link.id); - }); - }); - - describe('remove link', () => { - it('from nav links', () => { - var link = { - id: 'remove_link', - }; - FauxtonAPI.dispatch({ - type: 'ADD_NAVBAR_LINK', - link: link - }); - - FauxtonAPI.dispatch({ - type: 'REMOVE_NAVBAR_LINK', - link: link - }); - - expect(navBarStore.getNavLinks().length).toBe(0); - }); - - it('remove link from list', () => { - const addLink = (id) => { - FauxtonAPI.dispatch({ - type: 'ADD_NAVBAR_LINK', - link: { - id: id, - footerNav: true - } - }); - }; - const removeLink = () => { - FauxtonAPI.dispatch({ - type: 'REMOVE_NAVBAR_LINK', - link: { - id: 'remove_link3', - footerNav: true - } - }); - }; - addLink('remove_link1'); - addLink('remove_link2'); - addLink('remove_link3'); - - removeLink(); - removeLink(); - removeLink(); - - expect(navBarStore.getFooterNavLinks().length).toBe(2); - }); - - it('from bottom nav links', () => { - var link = { - id: 'remove_link', - bottomNav: true - }; - FauxtonAPI.dispatch({ - type: 'ADD_NAVBAR_LINK', - link: link - }); - - FauxtonAPI.dispatch({ - type: 'REMOVE_NAVBAR_LINK', - link: link - }); - - expect(navBarStore.getBottomNavLinks().length).toBe(0); - }); - - it('from footer nav links', () => { - var link = { - id: 'remove_link', - footerNav: true - }; - FauxtonAPI.dispatch({ - type: 'ADD_NAVBAR_LINK', - link: link - }); - - FauxtonAPI.dispatch({ - type: 'REMOVE_NAVBAR_LINK', - link: link - }); - - expect(navBarStore.getFooterNavLinks().length).toBe(0); - }); - }); - - describe('update link', () => { - it('for nav links', () => { - var link = { - id: 'update-link', - title: 'first' - }; - FauxtonAPI.dispatch({ - type: 'ADD_NAVBAR_LINK', - link: link - }); - - link.title = 'second'; - - FauxtonAPI.dispatch({ - type: 'UPDATE_NAVBAR_LINK', - link: link - }); - - expect(navBarStore.getNavLinks()[0].title).toMatch('second'); - }); - - }); - - describe('set version', () => { - it('stores version number', () => { - FauxtonAPI.dispatch({ - type: 'NAVBAR_SET_VERSION_INFO', - version: 1234 - }); - - expect(navBarStore.getVersion()).toBe(1234); - }); - - }); -}); diff --git a/app/addons/fauxton/navigation/actions.js b/app/addons/fauxton/navigation/actions.js index 3548157..ac26091 100644 --- a/app/addons/fauxton/navigation/actions.js +++ b/app/addons/fauxton/navigation/actions.js @@ -10,53 +10,51 @@ // License for the specific language governing permissions and limitations under // the License. -import FauxtonAPI from "../../../core/api"; -import ActionTypes from "./actiontypes"; - -export default { - toggleNavbarMenu () { - FauxtonAPI.dispatch({ - type: ActionTypes.TOGGLE_NAVBAR_MENU - }); - }, - - addHeaderLink (link) { - FauxtonAPI.dispatch({ - type: ActionTypes.ADD_NAVBAR_LINK, - link: link - }); - }, - - removeHeaderLink (link) { - FauxtonAPI.dispatch({ - type: ActionTypes.REMOVE_NAVBAR_LINK, - link: link - }); - }, - - setNavbarVersionInfo (versionInfo) { - FauxtonAPI.dispatch({ - type: ActionTypes.NAVBAR_SET_VERSION_INFO, - version: versionInfo - }); - }, - - setNavbarActiveLink (header) { - FauxtonAPI.dispatch({ - type: ActionTypes.NAVBAR_ACTIVE_LINK, - name: header - }); - }, - - showNavBar () { - FauxtonAPI.dispatch({ - type: ActionTypes.NAVBAR_SHOW - }); - }, - - hideNavBar () { - FauxtonAPI.dispatch({ - type: ActionTypes.NAVBAR_HIDE - }); - } +import FauxtonAPI from '../../../core/api'; +import ActionTypes from './actiontypes'; + +export const toggleNavbarMenu = () => (dispatch) => { + dispatch({ + type: ActionTypes.TOGGLE_NAVBAR_MENU + }); +}; + +export const addHeaderLink = (link) => (dispatch) => { + dispatch({ + type: ActionTypes.ADD_NAVBAR_LINK, + link: link + }); +}; + +export const removeHeaderLink = (link) => (dispatch) => { + dispatch({ + type: ActionTypes.REMOVE_NAVBAR_LINK, + link: link + }); +}; + +export const setNavbarVersionInfo = (versionInfo) => { + FauxtonAPI.reduxDispatch({ + type: ActionTypes.NAVBAR_SET_VERSION_INFO, + version: versionInfo + }); +}; + +export const setNavbarActiveLink = (header) => (dispatch) => { + dispatch({ + type: ActionTypes.NAVBAR_ACTIVE_LINK, + name: header + }); +}; + +export const showNavBar = () => (dispatch) => { + dispatch({ + type: ActionTypes.NAVBAR_SHOW + }); +}; + +export const hideNavBar = () => (dispatch) => { + dispatch({ + type: ActionTypes.NAVBAR_HIDE + }); }; diff --git a/app/addons/fauxton/navigation/actiontypes.js b/app/addons/fauxton/navigation/actiontypes.js index 2a5d72f..e007062 100644 --- a/app/addons/fauxton/navigation/actiontypes.js +++ b/app/addons/fauxton/navigation/actiontypes.js @@ -14,7 +14,6 @@ export default { ADD_NAVBAR_LINK: 'ADD_NAVBAR_LINK', TOGGLE_NAVBAR_MENU: 'TOGGLE_NAVBAR_MENU', UPDATE_NAVBAR_LINK: 'UPDATE_NAVBAR_LINK', - CLEAR_NAVBAR_LINK: 'CLEAR_NAVBAR_LINK', REMOVE_NAVBAR_LINK: 'REMOVE_NAVBAR_LINK', HIDE_NAVBAR_LINK_BADGE: 'HIDE_NAVBAR_LINK_BADGE', SHOW_NAVBAR_LINK_BADGE: 'SHOW_NAVBAR_LINK_BADGE', diff --git a/app/addons/fauxton/navigation/components/NavBar.js b/app/addons/fauxton/navigation/components/NavBar.js index 77cb3ab..89d0c48 100644 --- a/app/addons/fauxton/navigation/components/NavBar.js +++ b/app/addons/fauxton/navigation/components/NavBar.js @@ -10,10 +10,9 @@ // License for the specific language governing permissions and limitations under // the License. +import classNames from 'classnames'; import PropTypes from 'prop-types'; - import React, { Component } from 'react'; - import Footer from './Footer'; import Burger from './Burger'; import NavLink from './NavLink'; @@ -21,12 +20,12 @@ import Brand from './Brand'; import LogoutButton from './LogoutButton'; import LoginButton from './LoginButton'; -import Actions from "../actions"; - -import classNames from 'classnames'; - class NavBar extends Component { + constructor(props) { + super(props); + } + createLinks (links) { const { activeLink, isMinimized } = this.props; @@ -39,10 +38,6 @@ class NavBar extends Component { }); } - toggleMenu () { - Actions.toggleNavbarMenu(); - } - render () { const { isMinimized, @@ -51,7 +46,8 @@ class NavBar extends Component { isLoginVisibleInsteadOfLogout, activeLink, username, - isNavBarVisible + isNavBarVisible, + toggleNavbarMenu } = this.props; if (!isNavBarVisible) { @@ -76,7 +72,7 @@ class NavBar extends Component { <div className={navClasses}> <nav> <div className="faux-navbar__linkcontainer"> - <Burger isMinimized={isMinimized} toggleMenu={this.toggleMenu}/> + <Burger isMinimized={isMinimized} toggleMenu={toggleNavbarMenu}/> <div className="faux-navbar__links"> {navLinks} {bottomNavLinks} @@ -110,7 +106,8 @@ NavBar.propTypes = { footerNavLinks: PropTypes.array, isNavBarVisible: PropTypes.bool, isLoginSectionVisible: PropTypes.bool.isRequired, - isLoginVisibleInsteadOfLogout: PropTypes.bool.isRequired + isLoginVisibleInsteadOfLogout: PropTypes.bool.isRequired, + toggleNavbarMenu: PropTypes.func.isRequired }; export default NavBar; diff --git a/app/addons/fauxton/navigation/container/NavBar.js b/app/addons/fauxton/navigation/container/NavBar.js index d5efb13..2703b79 100644 --- a/app/addons/fauxton/navigation/container/NavBar.js +++ b/app/addons/fauxton/navigation/container/NavBar.js @@ -10,55 +10,38 @@ // License for the specific language governing permissions and limitations under // the License. -import FauxtonAPI from "../../../../core/api"; -import React from "react"; - -import ReactDOM from "react-dom"; -import Stores from "../stores"; - +import { connect } from 'react-redux'; +import FauxtonAPI from '../../../../core/api'; import NavBar from '../components/NavBar'; - -const navBarStore = Stores.navBarStore; - -class NavBarContainer extends React.Component { - getStoreState = () => { - return { - navLinks: navBarStore.getNavLinks(), - bottomNavLinks: navBarStore.getBottomNavLinks(), - footerNavLinks: navBarStore.getFooterNavLinks(), - activeLink: navBarStore.getActiveLink(), - version: navBarStore.getVersion(), - isMinimized: navBarStore.isMinimized(), - isNavBarVisible: navBarStore.isNavBarVisible(), - - isLoginSectionVisible: navBarStore.getIsLoginSectionVisible(), - isLoginVisibleInsteadOfLogout: navBarStore.getIsLoginVisibleInsteadOfLogout() - }; +import * as Actions from '../actions'; + +const mapStateToProps = ({ navigation }) => { + const user = FauxtonAPI.session.user(); + return { + navLinks: navigation.navLinks, + bottomNavLinks: navigation.bottomNavLinks, + footerNavLinks: navigation.footerNavLinks, + activeLink: navigation.activeLink, + version: navigation.version, + isMinimized: navigation.isMinimized, + isNavBarVisible: navigation.navBarVisible, + isLoginSectionVisible: navigation.loginSectionVisible, + isLoginVisibleInsteadOfLogout: navigation.loginVisibleInsteadOfLogout, + username: (user && user.name) ? user.name : '' }; +}; - onChange = () => { - this.setState(this.getStoreState()); +const mapDispatchToProps = (dispatch) => { + return { + toggleNavbarMenu: () => { + dispatch(Actions.toggleNavbarMenu()); + } }; +}; - state = this.getStoreState(); - - componentDidMount() { - navBarStore.on('change', this.onChange, this); - } - - componentWillUnmount() { - navBarStore.off('change', this.onChange); - } - - render() { - const user = FauxtonAPI.session.user(); - - const username = (user && user.name) ? user.name : ''; - return ( - <NavBar {...this.state} username={username} /> - ); - } -} - +const NavBarContainer = connect( + mapStateToProps, + mapDispatchToProps +)(NavBar); export default NavBarContainer; diff --git a/app/addons/fauxton/navigation/reducers.js b/app/addons/fauxton/navigation/reducers.js new file mode 100644 index 0000000..6af5d2c --- /dev/null +++ b/app/addons/fauxton/navigation/reducers.js @@ -0,0 +1,209 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +import ActionTypes from './actiontypes'; + +const initialState = { + isMinimized: true, + activeLink: null, + version: null, + navLinks: [], + footerNavLinks: [], + bottomNavLinks: [], + navBarVisible: true, + loginSectionVisible: false, + loginVisibleInsteadOfLogout: true +}; + +function addLink(state, link) { + const newState = { ...state }; + + if (link.top && !link.bottomNav) { + newState.navLinks = [].concat(newState.navLinks); + newState.navLinks.unshift(link); + return newState; + } + if (link.top && link.bottomNav) { + newState.bottomNavLinks = [].concat(newState.bottomNavLinks); + newState.bottomNavLinks.unshift(link); + return newState; + } + if (link.bottomNav) { + newState.bottomNavLinks = [].concat(newState.bottomNavLinks); + newState.bottomNavLinks.push(link); + return newState; + } + if (link.footerNav) { + newState.footerNavLinks = [].concat(newState.footerNavLinks); + newState.footerNavLinks.push(link); + return newState; + } + + newState.navLinks = [].concat(newState.navLinks); + newState.navLinks.push(link); + return newState; +} + +function removeLink (state, removeLink) { + const {links, sectionName} = getLinkSection(state, removeLink); + + // create new array without the link to remove + const newLinks = links.filter(link => link.id !== removeLink.id); + + if (newLinks.length === links.length) { + return state; + } + + const newState = { ...state }; + newState[sectionName] = newLinks; + return newState; +} + +function updateLink (state, link) { + const {links, sectionName} = getLinkSection(state, link); + + // create new array and updates the link when found + let found = false; + const newLinks = links.map(el => { + if (el.id === link.id) { + found = true; + return { + ...el, + title: link.title, + href: link.href + }; + } + return el; + }); + + if (!found) { + return state; + } + + const newState = { ...state }; + newState[sectionName] = newLinks; + return newState; +} + +function setLinkBadgeVisible (state, link, visible) { + const {links, sectionName} = getLinkSection(state, link); + + let found = false; + const newLinks = links.map(el => { + if (el.title === link.title) { + found = true; + return { + ...el, + badge: visible + }; + } + return el; + }); + + if (!found) { + return state; + } + + const newState = { ...state }; + newState[sectionName] = newLinks; + return newState; +} + +function getLinkSection (state, link) { + let links = state.navLinks; + let sectionName = 'navLinks'; + + if (link.bottomNav) { + links = state.bottomNavLinks; + sectionName = 'bottomNavLinks'; + } + + if (link.footerNav) { + links = state.footerNavLinks; + sectionName = 'footerNavLinks'; + } + + return { links, sectionName }; +} + +export default function navigation(state = initialState, action) { + switch (action.type) { + + case ActionTypes.ADD_NAVBAR_LINK: + return addLink(state, action.link); + + case ActionTypes.TOGGLE_NAVBAR_MENU: + return { + ...state, + isMinimized: !state.isMinimized + }; + + case ActionTypes.UPDATE_NAVBAR_LINK: + return updateLink(state, action.link); + + case ActionTypes.REMOVE_NAVBAR_LINK: + return removeLink(state, action.link); + + case ActionTypes.SHOW_NAVBAR_LINK_BADGE: + return setLinkBadgeVisible(state, action.link, true); + + case ActionTypes.HIDE_NAVBAR_LINK_BADGE: + return setLinkBadgeVisible(state, action.link, false); + + case ActionTypes.NAVBAR_SET_VERSION_INFO: + return { + ...state, + version: action.version + }; + + case ActionTypes.NAVBAR_ACTIVE_LINK: + return { + ...state, + activeLink: action.name + }; + + case ActionTypes.NAVBAR_HIDE: + return { + ...state, + navBarVisible: false + }; + + case ActionTypes.NAVBAR_SHOW: + return { + ...state, + navBarVisible: true + }; + + case ActionTypes.NAVBAR_SHOW_HIDE_LOGIN_LOGOUT_SECTION: + return { + ...state, + loginSectionVisible: action.visible + }; + + case ActionTypes.NAVBAR_SHOW_LOGIN_BUTTON: + return { + ...state, + loginSectionVisible: true, + loginVisibleInsteadOfLogout: true + }; + + case ActionTypes.NAVBAR_SHOW_LOGOUT_BUTTON: + return { + ...state, + loginSectionVisible: true, + loginVisibleInsteadOfLogout: false + }; + + default: + return state; + } +} diff --git a/app/addons/fauxton/navigation/stores.js b/app/addons/fauxton/navigation/stores.js deleted file mode 100644 index d08f243..0000000 --- a/app/addons/fauxton/navigation/stores.js +++ /dev/null @@ -1,251 +0,0 @@ -// Licensed under the Apache License, Version 2.0 (the "License"); you may not -// use this file except in compliance with the License. You may obtain a copy of -// the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations under -// the License. - -import FauxtonAPI from "../../../core/api"; -import ActionTypes from "./actiontypes"; -import {findIndex} from 'lodash'; - -const Stores = {}; - -Stores.NavBarStore = FauxtonAPI.Store.extend({ - initialize () { - this.reset(); - }, - - reset () { - this._isMinimized = true; - this._activeLink = null; - this._version = null; - this._navLinks = []; - this._footerNavLinks = []; - this._bottomNavLinks = []; - this._navBarVisible = true; - - this._loginSectionVisible = false; - this._loginVisibleInsteadOfLogout = true; - }, - - getIsLoginSectionVisible () { - return this._loginSectionVisible; - }, - - getIsLoginVisibleInsteadOfLogout () { - return this._loginVisibleInsteadOfLogout; - }, - - isNavBarVisible () { - return this._navBarVisible; - }, - - showNavBar () { - this._navBarVisible = true; - }, - - hideNavBar () { - this._navBarVisible = false; - }, - - addLink (link) { - if (link.top && !link.bottomNav) { - this._navLinks.unshift(link); - return; - } - if (link.top && link.bottomNav) { - this._bottomNavLinks.unshift(link); - return; - } - if (link.bottomNav) { - this._bottomNavLinks.push(link); - return; - } - if (link.footerNav) { - this._footerNavLinks.push(link); - return; - } - - this._navLinks.push(link); - }, - - removeLink (removeLink) { - const links = this.getLinkSection(removeLink); - - const indexOf = findIndex(links, link => { - if (link.id === removeLink.id) { - return true; - } - - return false; - }); - - if (indexOf === -1) { return; } - - links.splice(indexOf, 1); - }, - - getNavLinks () { - return this._navLinks; - }, - - getBottomNavLinks () { - return this._bottomNavLinks; - }, - - getFooterNavLinks () { - return this._footerNavLinks; - }, - - toggleMenu () { - this._isMinimized = !this._isMinimized; - }, - - getLinkSection (link) { - let links = this._navLinks; - - if (link.bottomNav) { - links = this._bottomNavLinks; - } - - if (link.footerNav) { - links = this._footerNavLinks; - } - - return links; - }, - - updateLink (link) { - let oldLink; - const links = this.getLinkSection(link); - - oldLink = _.find(links, function (oldLink) { - return oldLink.id === link.id; - }); - - if (!oldLink) { return; } - - oldLink.title = link.title; - oldLink.href = link.href; - }, - - showLinkBadge (link) { - const links = this.getLinkSection(link); - const selectedLink = links.find(function (oldLink) { - return oldLink.title === link.title; - }); - if (selectedLink) { - selectedLink.badge = true; - } - }, - - hideLinkBadge (link) { - const links = this.getLinkSection(link); - const selectedLink = links.find(function (oldLink) { - return oldLink.title === link.title; - }); - - if (selectedLink) { - selectedLink.badge = false; - } - }, - - getVersion () { - return this._version; - }, - - setVersion (version) { - this._version = version; - }, - - getActiveLink () { - return this._activeLink; - }, - - setActiveLink (activeLink) { - this._activeLink = activeLink; - }, - - isMinimized () { - return this._isMinimized; - }, - - dispatch (action) { - switch (action.type) { - case ActionTypes.ADD_NAVBAR_LINK: - this.addLink(action.link); - break; - - case ActionTypes.TOGGLE_NAVBAR_MENU: - this.toggleMenu(); - break; - - case ActionTypes.UPDATE_NAVBAR_LINK: - this.updateLink(action.link); - break; - - case ActionTypes.CLEAR_NAVBAR_LINK: - this.reset(); - break; - - case ActionTypes.REMOVE_NAVBAR_LINK: - this.removeLink(action.link); - break; - - case ActionTypes.SHOW_NAVBAR_LINK_BADGE: - this.showLinkBadge(action.link); - break; - - case ActionTypes.HIDE_NAVBAR_LINK_BADGE: - this.hideLinkBadge(action.link); - break; - - case ActionTypes.NAVBAR_SET_VERSION_INFO: - this.setVersion(action.version); - break; - - case ActionTypes.NAVBAR_ACTIVE_LINK: - this.setActiveLink(action.name); - break; - - case ActionTypes.NAVBAR_HIDE: - this.hideNavBar(); - break; - - case ActionTypes.NAVBAR_SHOW: - this.showNavBar(); - break; - - case ActionTypes.NAVBAR_SHOW_HIDE_LOGIN_LOGOUT_SECTION: - this._loginSectionVisible = action.visible; - break; - - case ActionTypes.NAVBAR_SHOW_LOGIN_BUTTON: - this._loginSectionVisible = true; - this._loginVisibleInsteadOfLogout = true; - break; - - case ActionTypes.NAVBAR_SHOW_LOGOUT_BUTTON: - this._loginSectionVisible = true; - this._loginVisibleInsteadOfLogout = false; - break; - - default: - return; - // do nothing - } - - this.triggerChange(); - } -}); - -Stores.navBarStore = new Stores.NavBarStore(); -Stores.navBarStore.dispatchToken = FauxtonAPI.dispatcher.register(Stores.navBarStore.dispatch.bind(Stores.navBarStore)); - -export default Stores; diff --git a/app/app.js b/app/app.js index 6c39160..ed6d994 100644 --- a/app/app.js +++ b/app/app.js @@ -38,41 +38,41 @@ FauxtonAPI.config({ // I haven't wrapped these dispatch methods in a action // because I don't want to require fauxton/actions in this method. addHeaderLink: function (link) { - FauxtonAPI.dispatch({ + FauxtonAPI.reduxDispatch({ type: 'ADD_NAVBAR_LINK', link: link }); }, updateHeaderLink: function (link) { - FauxtonAPI.dispatch({ + FauxtonAPI.reduxDispatch({ type: 'UPDATE_NAVBAR_LINK', link: link }); }, removeHeaderLink: function (link) { - FauxtonAPI.dispatch({ + FauxtonAPI.reduxDispatch({ type: 'REMOVE_NAVBAR_LINK', link: link }); }, hideLogin: function () { - FauxtonAPI.dispatch({ + FauxtonAPI.reduxDispatch({ type: 'NAVBAR_SHOW_HIDE_LOGIN_LOGOUT_SECTION', visible: false }); }, showLogout: function () { - FauxtonAPI.dispatch({ + FauxtonAPI.reduxDispatch({ type: 'NAVBAR_SHOW_LOGOUT_BUTTON' }); }, showLogin: function () { - FauxtonAPI.dispatch({ + FauxtonAPI.reduxDispatch({ type: 'NAVBAR_SHOW_LOGIN_BUTTON' }); } diff --git a/app/core/base.js b/app/core/base.js index 0762f7a..2278d84 100644 --- a/app/core/base.js +++ b/app/core/base.js @@ -24,19 +24,19 @@ var FauxtonAPI = { // I haven't wrapped these dispatch methods in a action // because I don't want to require fauxton/actions in this method. addHeaderLink: function (link) { - FauxtonAPI.dispatch({ + FauxtonAPI.reduxDispatch({ type: 'ADD_NAVBAR_LINK', link: link }); }, showHeaderLinkBadge: function (link) { - FauxtonAPI.dispatch({ + FauxtonAPI.reduxDispatch({ type: 'SHOW_NAVBAR_LINK_BADGE', link: link }); }, hideHeaderLinkBadge: function (link) { - FauxtonAPI.dispatch({ + FauxtonAPI.reduxDispatch({ type: 'HIDE_NAVBAR_LINK_BADGE', link: link });