diff --git a/app/addons/auth/__tests__/base.test.js b/app/addons/auth/__tests__/base.test.js new file mode 100644 index 000000000..ae56b1668 --- /dev/null +++ b/app/addons/auth/__tests__/base.test.js @@ -0,0 +1,83 @@ +// 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 Auth from "../../../core/auth"; +import Base from "../base"; +import sinon from "sinon"; + + +describe("Auth", function () { + FauxtonAPI.auth = new Auth(); + Base.initialize(); + + describe("failed login", function () { + + it("redirects with replace: true set", function () { + const navigateSpy = sinon.spy(FauxtonAPI, 'navigate'); + FauxtonAPI.router.trigger = () => {}; + FauxtonAPI.session.isLoggedIn = function () { return false; }; + FauxtonAPI.auth.authDeniedCb(); + expect(navigateSpy.withArgs('/login?urlback=', {replace: true}).calledOnce).toBeTruthy(); + FauxtonAPI.navigate.restore(); + }); + }); + + describe('auth session change', function () { + + afterEach(function () { + FauxtonAPI.addHeaderLink.restore && FauxtonAPI.addHeaderLink.restore(); + FauxtonAPI.session.isLoggedIn.restore && FauxtonAPI.session.isLoggedIn.restore(); + FauxtonAPI.session.isAdminParty.restore(); + }); + + it('for admin party changes title to admin party', function () { + const spy = sinon.spy(FauxtonAPI, 'addHeaderLink'); + sinon.stub(FauxtonAPI.session, 'isAdminParty').returns(true); + FauxtonAPI.session.trigger('change'); + + expect(spy.calledOnce).toBeTruthy(); + const args = spy.getCall(0).args[0]; + expect(args.title).toMatch("Admin Party!"); + }); + + it('for login changes title to Your Account', function () { + var spy = sinon.spy(FauxtonAPI, 'addHeaderLink'); + sinon.stub(FauxtonAPI.session, 'isAdminParty').returns(false); + sinon.stub(FauxtonAPI.session, 'isLoggedIn').returns(true); + FauxtonAPI.session.trigger('change'); + + expect(spy.calledOnce).toBeTruthy(); + var args = spy.getCall(0).args[0]; + expect(args.title).toMatch("Your Account"); + }); + + it('for login adds logout link', function () { + var spy = sinon.spy(FauxtonAPI, 'showLogout'); + sinon.stub(FauxtonAPI.session, 'isAdminParty').returns(false); + sinon.stub(FauxtonAPI.session, 'isLoggedIn').returns(true); + FauxtonAPI.session.trigger('change'); + + expect(spy.calledOnce).toBeTruthy(); + FauxtonAPI.showLogout.restore(); + }); + + it('for logout, removes logout link', function () { + var spy = sinon.spy(FauxtonAPI, 'showLogin'); + sinon.stub(FauxtonAPI.session, 'isAdminParty').returns(false); + sinon.stub(FauxtonAPI.session, 'isLoggedIn').returns(false); + FauxtonAPI.session.trigger('change'); + + expect(spy.calledOnce).toBeTruthy(); + FauxtonAPI.showLogin.restore(); + }); + }); +}); diff --git a/app/addons/auth/base.js b/app/addons/auth/base.js index 237a45065..c7c32c7ca 100644 --- a/app/addons/auth/base.js +++ b/app/addons/auth/base.js @@ -19,19 +19,16 @@ Auth.session = new Auth.Session(); FauxtonAPI.setSession(Auth.session); app.session = Auth.session; -Auth.initialize = function () { - FauxtonAPI.addHeaderLink({ - id: 'auth', - title: 'Login', - href: '#/login', - icon: 'fonticon-user', - bottomNav: true - }); +function cleanupAuthSection () { + FauxtonAPI.removeHeaderLink({ id: 'auth', bottomNav: true }); +} + +Auth.initialize = function () { Auth.session.on('change', function () { - var session = Auth.session; - var link = {}; + const session = Auth.session; + let link; if (session.isAdminParty()) { link = { @@ -41,36 +38,28 @@ Auth.initialize = function () { icon: 'fonticon-user', bottomNav: true }; + + cleanupAuthSection(); + FauxtonAPI.addHeaderLink(link); + FauxtonAPI.hideLogin(); + } else if (session.isLoggedIn()) { link = { id: 'auth', - title: session.user().name, + title: 'Your Account', href: '#/changePassword', icon: 'fonticon-user', bottomNav: true }; - // ensure the footer link is removed before adding it - FauxtonAPI.removeHeaderLink({ id: 'logout', footerNav: true }); - FauxtonAPI.addHeaderLink({ - id: 'logout', - footerNav: true, - href: '#logout', - title: 'Logout', - icon: '', - className: 'logout' - }); + cleanupAuthSection(); + FauxtonAPI.addHeaderLink(link); + FauxtonAPI.showLogout(); } else { - link = { - id: 'auth', - title: 'Login', - href: '#/login', - icon: 'fonticon-user', - bottomNav: true - }; - FauxtonAPI.removeHeaderLink({ id: 'logout', footerNav: true }); + cleanupAuthSection(); + FauxtonAPI.showLogin(); } - FauxtonAPI.updateHeaderLink(link); + }); Auth.session.fetchUser().then(function () { diff --git a/app/addons/auth/routes.js b/app/addons/auth/routes.js index b4a0e6837..12256197e 100644 --- a/app/addons/auth/routes.js +++ b/app/addons/auth/routes.js @@ -65,6 +65,7 @@ var AuthRouteObject = FauxtonAPI.RouteObject.extend({ var UserRouteObject = FauxtonAPI.RouteObject.extend({ hideNotificationCenter: true, hideApiBar: true, + selectedHeader: 'Your Account', routes: { 'changePassword': { @@ -93,10 +94,6 @@ var UserRouteObject = FauxtonAPI.RouteObject.extend({ ClusterActions.navigateToNodeBasedOnNodeCount('/addAdmin/'); }, - selectedHeader: function () { - return FauxtonAPI.session.user().name; - }, - changePassword: function () { ClusterActions.fetchNodes(); AuthActions.selectPage('changePassword'); diff --git a/app/addons/auth/test/baseSpec.js b/app/addons/auth/test/baseSpec.js deleted file mode 100644 index c56628775..000000000 --- a/app/addons/auth/test/baseSpec.js +++ /dev/null @@ -1,100 +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 Base from "../base"; -import Auth from "../../../core/auth"; -import testUtils from "../../../../test/mocha/testUtils"; -import sinon from "sinon"; -var assert = testUtils.assert; - -describe("Auth: Login", function () { - - describe("failed login", function () { - - it("redirects with replace: true set", function () { - var navigateSpy = sinon.spy(FauxtonAPI, 'navigate'); - FauxtonAPI.auth = new Auth(); - FauxtonAPI.session.isLoggedIn = function () { return false; }; - Base.initialize(); - FauxtonAPI.auth.authDeniedCb(); - assert.ok(navigateSpy.withArgs('/login?urlback=', {replace: true}).calledOnce); - }); - }); -}); - -describe('auth session change', function () { - - afterEach(function () { - FauxtonAPI.updateHeaderLink.restore && FauxtonAPI.updateHeaderLink.restore(); - FauxtonAPI.session.isAdminParty.restore && FauxtonAPI.session.isAdminParty.restore(); - }); - - it('for admin party changes title to admin party', function () { - var spy = sinon.spy(FauxtonAPI, 'updateHeaderLink'); - var stub = sinon.stub(FauxtonAPI.session, 'isAdminParty').returns(true); - FauxtonAPI.session.trigger('change'); - - assert.ok(spy.calledOnce); - var args = spy.getCall(0).args[0]; - - assert.ok(args.title.match(/Admin Party/)); - FauxtonAPI.session.isAdminParty.restore(); - }); - - it('for login changes title to login', function () { - var spy = sinon.spy(FauxtonAPI, 'updateHeaderLink'); - var stub = sinon.stub(FauxtonAPI.session, 'isAdminParty').returns(false); - sinon.stub(FauxtonAPI.session, 'user').returns({name: 'test-user'}); - sinon.stub(FauxtonAPI.session, 'isLoggedIn').returns(true); - FauxtonAPI.session.trigger('change'); - - assert.ok(spy.calledOnce); - var args = spy.getCall(0).args[0]; - - assert.equal(args.title, 'test-user'); - FauxtonAPI.session.isLoggedIn.restore(); - FauxtonAPI.session.user.restore(); - FauxtonAPI.session.isAdminParty.restore(); - }); - - it('for login adds logout link', function () { - var spy = sinon.spy(FauxtonAPI, 'addHeaderLink'); - var stub = sinon.stub(FauxtonAPI.session, 'isAdminParty').returns(false); - sinon.stub(FauxtonAPI.session, 'user').returns({name: 'test-user'}); - sinon.stub(FauxtonAPI.session, 'isLoggedIn').returns(true); - FauxtonAPI.session.trigger('change'); - - assert.ok(spy.calledOnce); - var args = spy.getCall(0).args[0]; - - assert.equal(args.title, 'Logout'); - FauxtonAPI.session.isLoggedIn.restore(); - FauxtonAPI.session.user.restore(); - FauxtonAPI.session.isAdminParty.restore(); - }); - - it('for logout, removes logout link', function () { - var spy = sinon.spy(FauxtonAPI, 'removeHeaderLink'); - var stub = sinon.stub(FauxtonAPI.session, 'isAdminParty').returns(false); - sinon.stub(FauxtonAPI.session, 'isLoggedIn').returns(false); - FauxtonAPI.session.trigger('change'); - - assert.ok(spy.calledOnce); - var args = spy.getCall(0).args[0]; - - assert.equal(args.id, 'logout'); - FauxtonAPI.session.isLoggedIn.restore(); - FauxtonAPI.session.isAdminParty.restore(); - }); - - -}); diff --git a/app/addons/cluster/routes.js b/app/addons/cluster/routes.js index 591541f51..c4b924d56 100644 --- a/app/addons/cluster/routes.js +++ b/app/addons/cluster/routes.js @@ -19,6 +19,8 @@ import {OnePaneSimpleLayout} from '../components/layouts'; var ConfigDisabledRouteObject = FauxtonAPI.RouteObject.extend({ + selectedHeader: 'Configuration', + routes: { 'cluster/disabled': 'showDisabledFeatureScreen' }, diff --git a/app/addons/config/routes.js b/app/addons/config/routes.js index e1dadb769..370d126fc 100644 --- a/app/addons/config/routes.js +++ b/app/addons/config/routes.js @@ -20,6 +20,7 @@ import Layout from './layout'; var ConfigDisabledRouteObject = FauxtonAPI.RouteObject.extend({ + selectedHeader: 'Configuration', routes: { '_config': 'checkNodes', diff --git a/app/addons/databases/tests/nightwatch/checkDatabaseTooltip.js b/app/addons/databases/tests/nightwatch/checkDatabaseTooltip.js index d2c6606a7..e5b0353d5 100644 --- a/app/addons/databases/tests/nightwatch/checkDatabaseTooltip.js +++ b/app/addons/databases/tests/nightwatch/checkDatabaseTooltip.js @@ -38,7 +38,7 @@ module.exports = { .checkForStringPresent(newDatabaseName, '"doc_del_count":1') - .clickWhenVisible('#nav-links a[href="#/_all_dbs"]') + .clickWhenVisible('#primary-navbar a[href="#/_all_dbs"]') // now let's look at the actual UI to confirm the tooltip appears .waitForElementPresent('.js-db-graveyard', waitTime, false) .moveToElement('.js-db-graveyard', 1, 1) diff --git a/app/addons/documents/tests/nightwatch/selectDocViaTypeahead.js b/app/addons/documents/tests/nightwatch/selectDocViaTypeahead.js index b96940e42..0a083f206 100644 --- a/app/addons/documents/tests/nightwatch/selectDocViaTypeahead.js +++ b/app/addons/documents/tests/nightwatch/selectDocViaTypeahead.js @@ -27,7 +27,6 @@ module.exports = { .keys(['\uE00C']) .waitForElementPresent('.prettyprint', waitTime, false) .waitForElementPresent('.documents-pagination', waitTime, false) - .click('.burger') // we need to explicitly show the doc field because it's hidden on Travis due to screen width .execute("$('.searchbox-wrapper').show();") .setValue('.jump-to-doc .Select-input input', ['_des']) @@ -50,7 +49,6 @@ module.exports = { .keys(['\uE00C']) .waitForElementPresent('.prettyprint', waitTime, false) .waitForElementPresent('.documents-pagination', waitTime, false) - .click('.burger') // we need to explicitly show the doc field because it's hidden on Travis due to screen width .execute("$('.searchbox-wrapper').show();") .setValue('.jump-to-doc .Select-input input', ['MY_CAP']) diff --git a/app/addons/fauxton/appwrapper.js b/app/addons/fauxton/appwrapper.js index f377bbc62..573521baf 100644 --- a/app/addons/fauxton/appwrapper.js +++ b/app/addons/fauxton/appwrapper.js @@ -12,8 +12,12 @@ import React from 'react'; import {NotificationController, PermanentNotification} from "./notifications/notifications.react"; -import {NavBar} from './navigation/components.react'; +import NavBar from './navigation/container/NavBar'; import NavbarActions from './navigation/actions'; +import Stores from './navigation/stores'; +import classNames from 'classnames'; + +const navBarStore = Stores.navBarStore; class ContentWrapper extends React.Component { constructor(props) { @@ -49,27 +53,52 @@ class ContentWrapper extends React.Component { } } -const App = ({router}) => { - return ( -
- -
- -
-
-
-
-
- -
-
- +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} + ); + + return ( +
+ +
+ +
+
+
+
+
+ +
+
+ +
-
- ); + ); + } }; export default App; diff --git a/app/addons/fauxton/assets/less/fauxton.less b/app/addons/fauxton/assets/less/fauxton.less index 255589d52..c248f69c9 100644 --- a/app/addons/fauxton/assets/less/fauxton.less +++ b/app/addons/fauxton/assets/less/fauxton.less @@ -1 +1,2 @@ @import "components.less"; +@import "navigation.less"; diff --git a/app/addons/fauxton/assets/less/navigation.less b/app/addons/fauxton/assets/less/navigation.less new file mode 100644 index 000000000..1493c6272 --- /dev/null +++ b/app/addons/fauxton/assets/less/navigation.less @@ -0,0 +1,202 @@ +.faux-navbar { + margin-top: 64px; + background-color: @brandDark2; + position: absolute; + left: 0; + top: 0; + bottom: 0; + z-index: 5; + overflow: scroll; +} + +.faux-navbar nav { + height: 100%; +} + +.faux-navbar__linkcontainer { + display: flex; + flex-direction: column; + justify-content: space-between; + height: 100%; +} + +.faux-navbar__itemarea { + .box-sizing(border-box); + border-bottom: 1px solid @brandDark2; + height: 48px; + padding: 10px 20px; + line-height: 24px; +} + +.faux-navbar__version-footer { + color: @buttonText; + font-size: 10px; + text-align: center; + background-color: @brandDark2; + padding-bottom: 10px; +} + +.faux-navbar__burger { + background-color: @brandDark2; + padding: 19px 0 18px 18px; + position: fixed; + z-index: 100; + top: 0; +} + +.faux-navbar--narrow { + width: @collapsedNavWidth; +} + +.faux-navbar--wide { + width: @navWidth; +} + +.faux-navbar__burger:hover .faux-navbar__burger__icon { + color: @navIconActive; +} + +.faux-navbar__burger__icon { + color: @navIconColor; + font-size: 27px; +} + +.faux-navbar__burger__icon--flipped:before{ + -moz-transform: scale(-1, 1); + -webkit-transform: scale(-1, 1); + -o-transform: scale(-1, 1); + -ms-transform: scale(-1, 1); + transform: scale(-1, 1); +} + +.faux-navbar__link, .faux-logout__link, .faux-login__link { + display: block; + text-decoration: none; + cursor: pointer; +} + +.faux-navbar__link--active { + background-color: @brandHighlight; + text-decoration: none; +} + +.faux-navbar__link--inactive { + background-color: @brandDark1; +} + +.faux-navbar__link:hover, .faux-logout__link:hover, .faux-login__link:hover { + background-color: @hoverHighlight; + text-decoration: none; +} + +.faux-navbar__link:active { + text-decoration: none; +} + +.faux-navbar__link:hover .faux-navbar__icon:before { + color: @navIconActive; +} + +.faux-navbar__link--active .faux-navbar__icon:before { + color: @navIconActive; +} + +.faux-navbar__icon { + margin-right: 14px; + color: @navIconColor; + font-size: 24px; + vertical-align: middle; +} + +.faux-navbar__text { + margin: 0; + color: @buttonText; + vertical-align: middle; + font-size: 16px; + font-weight: normal; + font-family: Helvetica,sans-serif; + font-weight: 400; +} + +.faux-navbar__logout__text { + font-size: 12px; + color: @buttonText; +} + +.faux-navbar__logout__textcontainer { + text-align: center; + color: @buttonText; +} + +.faux-navbar__logout__textcontainer--narrow { + padding-bottom: 4px; + padding: 15px 0; +} + +.faux-navbar__logout__textcontainer--wide { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + padding: 15px; +} + +.faux-navbar__login__textcontainer { + text-align: center; + color: @buttonText; +} + +.faux-navbar__login__textcontainer--narrow { + padding-bottom: 4px; + padding: 15px 0; +} + +.faux-navbar__login__textcontainer--wide { + padding: 15px; +} + +.faux-navbar__brand { + margin: 20px 0 20px 0; + height: 50px; + padding: 10px 10px 10px 10px; + float: none; + background: @brandDark2; +} + +.faux-navbar__brand-logo { + display: block; + height: 100%; + margin-top: 10px; +} + +.faux-navbar__brand-logo--wide { + background: url(@largeLogoPath) no-repeat 23px 0px; + background-size: 150px; + width: 200px; +} + +.faux-navbar__brand-logo--narrow { + width: 43px; + height: 40px; + background: url(@smallLogoPath) no-repeat 3px 0; + background-size: 40px; +} + +.faux-navbar__footer { + display: flex; + flex-direction: column; + justify-content: flex-end; + height: 100%; + min-height: 200px; + width: 100%; +} + +.faux-navbar__logout__username { + color: @buttonText; + overflow: hidden; + text-overflow: ellipsis; + max-width: 150px; + white-space: nowrap; + padding-right: 5px; + padding-left: 10px; + font-size: 12px; +} diff --git a/app/addons/fauxton/base.js b/app/addons/fauxton/base.js index fa83b9791..17812d886 100644 --- a/app/addons/fauxton/base.js +++ b/app/addons/fauxton/base.js @@ -19,7 +19,6 @@ import "./assets/less/fauxton.less"; const Fauxton = FauxtonAPI.addon(); Fauxton.initialize = () => { - const versionInfo = new Fauxton.VersionInfo(); versionInfo.fetch().then(function () { NavigationActions.setNavbarVersionInfo(versionInfo.get("version")); diff --git a/app/addons/fauxton/navigation/__tests__/burger-test.js b/app/addons/fauxton/navigation/__tests__/burger-test.js new file mode 100644 index 000000000..5f9640ff6 --- /dev/null +++ b/app/addons/fauxton/navigation/__tests__/burger-test.js @@ -0,0 +1,29 @@ +// 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 Burger from "../components/Burger"; +import React from "react"; +import ReactDOM from "react-dom"; +import sinon from "sinon"; +import {mount} from 'enzyme'; + +describe('Navigation Bar', () => { + + describe('Burger', () => { + it('dispatch TOGGLE_NAVBAR_MENU on click', () => { + const toggleMenu = sinon.spy(); + const burgerEl = mount(); + burgerEl.simulate('click'); + expect(toggleMenu.calledOnce).toBeTruthy(); + }); + + }); +}); diff --git a/app/addons/fauxton/navigation/__tests__/login-logout-test.js b/app/addons/fauxton/navigation/__tests__/login-logout-test.js new file mode 100644 index 000000000..097e4952f --- /dev/null +++ b/app/addons/fauxton/navigation/__tests__/login-logout-test.js @@ -0,0 +1,80 @@ +// 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 React from 'react'; +import { mount } from 'enzyme'; + +import NavBar from '../components/NavBar'; + +describe('Navigation Bar', () => { + + 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(); + + const button = navBar.find('[href="#/login"]'); + expect(button.text()).toContain('Login'); + }); + + 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(); + + const button = navBar.find('[href="#/logout"]'); + expect(button.text()).toContain('Log Out'); + }); + + 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(); + + 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 new file mode 100644 index 000000000..7a6331ebb --- /dev/null +++ b/app/addons/fauxton/navigation/__tests__/navbar-test.js @@ -0,0 +1,43 @@ +// 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 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'; + +describe('Navigation Bar', () => { + FauxtonAPI.session = { + user: () => {} + }; + + it('is displayed by default', () => { + const NavBar = mount(); + expect(NavBar.find('.faux-navbar').length).toBe(1); + }); + + it('is dynamically displayed by isNavBarVisible', () => { + const NavBar = mount(); + + FauxtonAPI.dispatch({ + type: ActionTypes.NAVBAR_HIDE + }); + expect(NavBar.find('.faux-navbar').length).toBe(0); + + FauxtonAPI.dispatch({ + type: ActionTypes.NAVBAR_SHOW + }); + expect(NavBar.find('.faux-navbar').length).toBe(1); + }); + +}); diff --git a/app/addons/fauxton/navigation/tests/storeSpec.js b/app/addons/fauxton/navigation/__tests__/navigation-store-test.js similarity index 52% rename from app/addons/fauxton/navigation/tests/storeSpec.js rename to app/addons/fauxton/navigation/__tests__/navigation-store-test.js index 778dac149..c664dceec 100644 --- a/app/addons/fauxton/navigation/tests/storeSpec.js +++ b/app/addons/fauxton/navigation/__tests__/navigation-store-test.js @@ -9,24 +9,21 @@ // 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 app from "../../../../app"; -import testUtils from "../../../../../test/mocha/testUtils"; import FauxtonAPI from "../../../../core/api"; import Stores from "../stores"; -var assert = testUtils.assert; -var navBarStore = Stores.navBarStore; +const navBarStore = Stores.navBarStore; -describe('NavBarStore', function () { - beforeEach(function () { +describe('NavBarStore', () => { + beforeEach(() => { FauxtonAPI.dispatch({ type: 'CLEAR_NAVBAR_LINK', }); }); - describe('add links', function () { + describe('add links', () => { - it('to nav links', function () { + it('to nav links', () => { var link = { id: 'mylink' }; @@ -35,10 +32,10 @@ describe('NavBarStore', function () { link: link }); - assert.equal(navBarStore.getNavLinks()[0].id, link.id); + expect(navBarStore.getNavLinks()[0].id).toMatch(link.id); }); - it('to top nav links', function () { + it('to top nav links', () => { var link1 = { id: 'mylink1' }; @@ -58,10 +55,10 @@ describe('NavBarStore', function () { link: link2 }); - assert.equal(navBarStore.getNavLinks()[0].id, link2.id); + expect(navBarStore.getNavLinks()[0].id).toMatch(link2.id); }); - it('to bottom nav', function () { + it('to bottom nav', () => { var link = { id: 'bottomNav', bottomNav: true @@ -71,10 +68,10 @@ describe('NavBarStore', function () { link: link }); - assert.equal(navBarStore.getBottomNavLinks()[0].id, link.id); + expect(navBarStore.getBottomNavLinks()[0].id).toMatch(link.id); }); - it('to top of bottom nav', function () { + it('to top of bottom nav', () => { var link = { id: 'bottomNav', bottomNav: true, @@ -85,10 +82,10 @@ describe('NavBarStore', function () { link: link }); - assert.equal(navBarStore.getBottomNavLinks()[0].id, link.id); + expect(navBarStore.getBottomNavLinks()[0].id).toMatch(link.id); }); - it('to footer nav', function () { + it('to footer nav', () => { var link = { id: 'footerNav', footerNav: true @@ -98,12 +95,12 @@ describe('NavBarStore', function () { link: link }); - assert.equal(navBarStore.getFooterNavLinks()[0].id, link.id); + expect(navBarStore.getFooterNavLinks()[0].id).toMatch(link.id); }); }); - describe('remove link', function () { - it('from nav links', function () { + describe('remove link', () => { + it('from nav links', () => { var link = { id: 'remove_link', }; @@ -117,11 +114,11 @@ describe('NavBarStore', function () { link: link }); - assert.equal(navBarStore.getNavLinks().length, 0); + expect(navBarStore.getNavLinks().length).toBe(0); }); - it('remove link from list', function () { - function addLink (id) { + it('remove link from list', () => { + const addLink = (id) => { FauxtonAPI.dispatch({ type: 'ADD_NAVBAR_LINK', link: { @@ -129,8 +126,8 @@ describe('NavBarStore', function () { footerNav: true } }); - } - function removeLink () { + }; + const removeLink = () => { FauxtonAPI.dispatch({ type: 'REMOVE_NAVBAR_LINK', link: { @@ -138,7 +135,7 @@ describe('NavBarStore', function () { footerNav: true } }); - } + }; addLink('remove_link1'); addLink('remove_link2'); addLink('remove_link3'); @@ -147,10 +144,10 @@ describe('NavBarStore', function () { removeLink(); removeLink(); - assert.equal(navBarStore.getFooterNavLinks().length, 2); + expect(navBarStore.getFooterNavLinks().length).toBe(2); }); - it('from bottom nav links', function () { + it('from bottom nav links', () => { var link = { id: 'remove_link', bottomNav: true @@ -165,10 +162,10 @@ describe('NavBarStore', function () { link: link }); - assert.equal(navBarStore.getBottomNavLinks().length, 0); + expect(navBarStore.getBottomNavLinks().length).toBe(0); }); - it('from footer nav links', function () { + it('from footer nav links', () => { var link = { id: 'remove_link', footerNav: true @@ -183,12 +180,12 @@ describe('NavBarStore', function () { link: link }); - assert.equal(navBarStore.getFooterNavLinks().length, 0); + expect(navBarStore.getFooterNavLinks().length).toBe(0); }); }); - describe('update link', function () { - it('for nav links', function () { + describe('update link', () => { + it('for nav links', () => { var link = { id: 'update-link', title: 'first' @@ -205,59 +202,19 @@ describe('NavBarStore', function () { link: link }); - assert.equal(navBarStore.getNavLinks()[0].title, 'second'); + expect(navBarStore.getNavLinks()[0].title).toMatch('second'); }); }); - describe('set version', function () { - it('stores version number', function () { + describe('set version', () => { + it('stores version number', () => { FauxtonAPI.dispatch({ type: 'NAVBAR_SET_VERSION_INFO', version: 1234 }); - assert.equal(navBarStore.getVersion(), 1234); - }); - - }); - - describe('is Minimized', function () { - - it('returns true if localstorage is true', function () { - app.utils.localStorageSet(FauxtonAPI.constants.LOCAL_STORAGE.SIDEBAR_MINIMIZED, true); - assert.ok(navBarStore.isMinimized()); - }); - - it('returns false if localstorage is false', function () { - app.utils.localStorageSet(FauxtonAPI.constants.LOCAL_STORAGE.SIDEBAR_MINIMIZED, false); - assert.notOk(navBarStore.isMinimized(), false); - }); - - it('returns false if localstorage is undefined', function () { - window.localStorage.removeItem(FauxtonAPI.constants.LOCAL_STORAGE.SIDEBAR_MINIMIZED); - assert.notOk(navBarStore.isMinimized(), false); - }); - }); - - describe('toggleMenu', function () { - - it('that is minimized changes to false', function () { - app.utils.localStorageSet(FauxtonAPI.constants.LOCAL_STORAGE.SIDEBAR_MINIMIZED, true); - navBarStore.toggleMenu(); - assert.notOk(navBarStore.isMinimized()); - }); - - it('that is not minimized changes to true', function () { - app.utils.localStorageSet(FauxtonAPI.constants.LOCAL_STORAGE.SIDEBAR_MINIMIZED, false); - navBarStore.toggleMenu(); - assert.ok(navBarStore.isMinimized()); - }); - - it('that is undefined changes to true', function () { - window.localStorage.removeItem(FauxtonAPI.constants.LOCAL_STORAGE.SIDEBAR_MINIMIZED); - navBarStore.toggleMenu(); - assert.ok(navBarStore.isMinimized()); + expect(navBarStore.getVersion()).toBe(1234); }); }); diff --git a/app/addons/fauxton/navigation/__tests__/navlink-test.js b/app/addons/fauxton/navigation/__tests__/navlink-test.js new file mode 100644 index 000000000..8201f4e4a --- /dev/null +++ b/app/addons/fauxton/navigation/__tests__/navlink-test.js @@ -0,0 +1,48 @@ +// 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 NavLink from "../components/NavLink"; +import React from "react"; +import ReactDOM from "react-dom"; +import {mount} from 'enzyme'; + +describe('Navigation Bar', () => { + const dbLink = { + href: "#/_all_dbs", + title: "Databases", + icon: "fonticon-database", + className: 'databases' + }; + + describe('Active Link', () => { + it('matching title sets active css class', () => { + const linkEl = mount(); + expect(linkEl.find('a.faux-navbar__link--active').length).toBe(1); + }); + + it('different title sets inactive css class', () => { + const linkEl = mount(); + expect(linkEl.find('a.faux-navbar__link--inactive').length).toBe(1); + }); + }); + + describe('Minimized Link', () => { + it('shows title when not minimized', () => { + const linkEl = mount(); + expect(linkEl.text()).toMatch("Databases"); + }); + + it('does not show title when minimized', () => { + const linkEl = mount(); + expect(linkEl.find('span.faux-navbar__text').length).toBe(0); + }); + }); +}); diff --git a/app/addons/fauxton/navigation/actiontypes.js b/app/addons/fauxton/navigation/actiontypes.js index b5515bdcd..e5f685f33 100644 --- a/app/addons/fauxton/navigation/actiontypes.js +++ b/app/addons/fauxton/navigation/actiontypes.js @@ -19,5 +19,8 @@ export default { NAVBAR_SET_VERSION_INFO: 'NAVBAR_SET_VERSION_INFO', NAVBAR_ACTIVE_LINK: 'NAVBAR_ACTIVE_LINK', NAVBAR_HIDE: 'NAVBAR_HIDE', - NAVBAR_SHOW: 'NAVBAR_SHOW' + NAVBAR_SHOW: 'NAVBAR_SHOW', + NAVBAR_SHOW_HIDE_LOGIN_LOGOUT_SECTION: 'NAVBAR_SHOW_HIDE_LOGIN_LOGOUT_SECTION', + NAVBAR_SHOW_LOGIN_BUTTON: 'NAVBAR_SHOW_LOGIN_BUTTON', + NAVBAR_SHOW_LOGOUT_BUTTON: 'NAVBAR_SHOW_LOGOUT_BUTTON' }; diff --git a/app/addons/fauxton/navigation/components.react.jsx b/app/addons/fauxton/navigation/components.react.jsx deleted file mode 100644 index a269726d4..000000000 --- a/app/addons/fauxton/navigation/components.react.jsx +++ /dev/null @@ -1,155 +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 React from "react"; -import ReactDOM from "react-dom"; -import Stores from "./stores"; -import Actions from "./actions"; -const navBarStore = Stores.navBarStore; - -const Footer = React.createClass({ - render () { - const version = this.props.version; - - if (!version) { return null; } - return ( -
- Fauxton on {" "} - Apache CouchDB - {" "} v. {version} -
- ); - } -}); - -const Burger = React.createClass({ - render () { - return ( -
-
-
-
-
- ); - } -}); - -const NavLink = React.createClass({ - render () { - const link = this.props.link; - const liClassName = this.props.active === link.title ? 'active' : ''; - - return ( -
  • - - - - -
  • - ); - } -}); - -export const NavBar = React.createClass({ - getStoreState () { - return { - navLinks: navBarStore.getNavLinks(), - bottomNavLinks: navBarStore.getBottomNavLinks(), - footerNavLinks: navBarStore.getFooterNavLinks(), - activeLink: navBarStore.getActiveLink(), - version: navBarStore.getVersion(), - isMinimized: navBarStore.isMinimized(), - isNavBarVisible: navBarStore.isNavBarVisible() - }; - }, - - getInitialState () { - return this.getStoreState(); - }, - - createLinks (links) { - return _.map(links, function (link, i) { - return ; - }, this); - }, - - onChange () { - this.setState(this.getStoreState()); - }, - - setMenuState () { - $('body').toggleClass('closeMenu', this.state.isMinimized); - FauxtonAPI.Events.trigger(FauxtonAPI.constants.EVENTS.NAVBAR_SIZE_CHANGED, this.state.isMinimized); - }, - - componentDidMount () { - navBarStore.on('change', this.onChange, this); - this.setMenuState(); - }, - - componentDidUpdate () { - this.setMenuState(); - }, - - componentWillUnmount () { - navBarStore.off('change', this.onChange); - }, - - toggleMenu () { - Actions.toggleNavbarMenu(); - }, - - render () { - //YUCK!! but we can only really fix this once we have removed all backbone - if (!this.state.isNavBarVisible) { - $('#primary-navbar').hide(); - return null; - } - - $('#primary-navbar').show(); - - const navLinks = this.createLinks(this.state.navLinks); - const bottomNavLinks = this.createLinks(this.state.bottomNavLinks); - const footerNavLinks = this.createLinks(this.state.footerNavLinks); - - return ( -
    - - -
    - -
    -
    -
    Apache Fauxton
    -
    - -
    - -
    -
    - ); - } -}); - -export default { - NavBar: NavBar, - Burger: Burger -}; diff --git a/app/addons/fauxton/navigation/components/Brand.js b/app/addons/fauxton/navigation/components/Brand.js new file mode 100644 index 000000000..e8751b08d --- /dev/null +++ b/app/addons/fauxton/navigation/components/Brand.js @@ -0,0 +1,36 @@ +// 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 React from 'react'; + +import classNames from 'classnames'; + +const Brand = ({isMinimized}) => { + + const burgerClasses = classNames( + 'faux-navbar__brand-logo', + {'faux-navbar__brand-logo--wide': !isMinimized}, + {'faux-navbar__brand-logo--narrow': isMinimized} + ); + + return ( +
    +
    +
    + ); +}; + +Brand.propTypes = { + isMinimized: React.PropTypes.bool.isRequired +}; + +export default Brand; diff --git a/app/addons/fauxton/navigation/components/Burger.js b/app/addons/fauxton/navigation/components/Burger.js new file mode 100644 index 000000000..fc9c35792 --- /dev/null +++ b/app/addons/fauxton/navigation/components/Burger.js @@ -0,0 +1,41 @@ +// 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 React from 'react'; + +import classNames from 'classnames'; + +const Burger = ({toggleMenu, isMinimized}) => { + + const burgerClasses = classNames( + 'faux-navbar__burger', + {'faux-navbar--wide': !isMinimized}, + {'faux-navbar--narrow': isMinimized} + ); + + const icon = isMinimized ? + 'icon-resize-horizontal' : + 'icon-signin faux-navbar__burger__icon--flipped'; + + return ( +
    + +
    + ); +}; + +Burger.propTypes = { + toggleMenu: React.PropTypes.func.isRequired, + isMinimized: React.PropTypes.bool.isRequired +}; + +export default Burger; diff --git a/app/addons/fauxton/navigation/components/Footer.js b/app/addons/fauxton/navigation/components/Footer.js new file mode 100644 index 000000000..72ddd012c --- /dev/null +++ b/app/addons/fauxton/navigation/components/Footer.js @@ -0,0 +1,33 @@ +// 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 React from 'react'; + +const Footer = ({version}) => { + + if (!version) { return null; } + + return ( +
    + Fauxton on   + Apache CouchDB +
    v. {version}
    +
    + ); + +}; + +Footer.propTypes = { + version: React.PropTypes.string +}; + +export default Footer; diff --git a/app/addons/fauxton/navigation/components/LoginButton.js b/app/addons/fauxton/navigation/components/LoginButton.js new file mode 100644 index 000000000..64939012c --- /dev/null +++ b/app/addons/fauxton/navigation/components/LoginButton.js @@ -0,0 +1,42 @@ +// 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 React from 'react'; + +import classNames from 'classnames'; + +const LoginButton = ({isMinimized}) => { + + const containerClasses = classNames( + 'faux-navbar__login__textcontainer', + {'faux-navbar__login__textcontainer--narrow': isMinimized}, + {'faux-navbar__login__textcontainer--wide': !isMinimized} + ); + + return ( + + +
    + + Login + +
    + +
    + ); +}; + +LoginButton.propTypes = { + isMinimized: React.PropTypes.bool.isRequired +}; + +export default LoginButton; diff --git a/app/addons/fauxton/navigation/components/LogoutButton.js b/app/addons/fauxton/navigation/components/LogoutButton.js new file mode 100644 index 000000000..037f172be --- /dev/null +++ b/app/addons/fauxton/navigation/components/LogoutButton.js @@ -0,0 +1,47 @@ +// 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 React from 'react'; + +import classNames from 'classnames'; +import _ from 'lodash'; + +const LogoutButton = ({username, isMinimized}) => { + + const containerClasses = classNames( + 'faux-navbar__logout__textcontainer', + {'faux-navbar__logout__textcontainer--narrow': isMinimized}, + {'faux-navbar__logout__textcontainer--wide': !isMinimized} + ); + + return ( + +
    + + Log Out + +   + {isMinimized ? + null : + {_.escape(username)} + } +
    +
    + ); +}; + +LogoutButton.propTypes = { + username: React.PropTypes.string, + isMinimized: React.PropTypes.bool.isRequired +}; + +export default LogoutButton; diff --git a/app/addons/fauxton/navigation/components/NavBar.js b/app/addons/fauxton/navigation/components/NavBar.js new file mode 100644 index 000000000..3a83a1158 --- /dev/null +++ b/app/addons/fauxton/navigation/components/NavBar.js @@ -0,0 +1,115 @@ +// 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 React, { Component } from 'react'; + +import Footer from './Footer'; +import Burger from './Burger'; +import NavLink from './NavLink'; +import Brand from './Brand'; +import LogoutButton from './LogoutButton'; +import LoginButton from './LoginButton'; + +import Actions from "../actions"; + +import classNames from 'classnames'; + +class NavBar extends Component { + + createLinks (links) { + const { activeLink, isMinimized } = this.props; + + return links.map((link, i) => { + return ; + }); + } + + toggleMenu () { + Actions.toggleNavbarMenu(); + } + + render () { + const { + isMinimized, + version, + isLoginSectionVisible, + isLoginVisibleInsteadOfLogout, + activeLink, + username, + isNavBarVisible + } = this.props; + + if (!isNavBarVisible) { + return null; + } + + const navLinks = this.createLinks(this.props.navLinks); + const bottomNavLinks = this.createLinks(this.props.bottomNavLinks); + const footerNavLinks = this.createLinks(this.props.footerNavLinks); + + const navClasses = classNames( + 'faux-navbar', + {'faux-navbar--wide': !isMinimized}, + {'faux-navbar--narrow': isMinimized} + ); + + const loginSection = isLoginVisibleInsteadOfLogout ? + : + ; + + return ( +
    + +
    +
    + ); + } +} + +NavBar.propTypes = { + activeLink: React.PropTypes.string, + isMinimized: React.PropTypes.bool.isRequired, + version: React.PropTypes.string, + username: React.PropTypes.string, + navLinks: React.PropTypes.array, + bottomNavLinks: React.PropTypes.array, + footerNavLinks: React.PropTypes.array, + isNavBarVisible: React.PropTypes.bool, + isLoginSectionVisible: React.PropTypes.bool.isRequired, + isLoginVisibleInsteadOfLogout: React.PropTypes.bool.isRequired +}; + +export default NavBar; diff --git a/app/addons/fauxton/navigation/components/NavLink.js b/app/addons/fauxton/navigation/components/NavLink.js new file mode 100644 index 000000000..1cb6b7b78 --- /dev/null +++ b/app/addons/fauxton/navigation/components/NavLink.js @@ -0,0 +1,51 @@ +// 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 React from 'react'; +import classNames from 'classnames'; + + +const NavLink = ({link, active, isMinimized}) => { + + const linkClass = classNames( + 'faux-navbar__link', + {'faux-navbar__link--active': active === link.title}, + {'faux-navbar__link--inactive': active !== link.title}, + {'faux-navbar--wide': !isMinimized}, + {'faux-navbar--narrow': isMinimized} + ); + + return ( + +
    + + {!!link.icon ? + : + null + } + {isMinimized ? + null : + {link.title} + } +
    +
    + ); +}; + +NavLink.propTypes = { + link: React.PropTypes.object.isRequired, + active: React.PropTypes.string, + isMinimized: React.PropTypes.bool.isRequired, +}; + + +export default NavLink; diff --git a/app/addons/fauxton/navigation/container/NavBar.js b/app/addons/fauxton/navigation/container/NavBar.js new file mode 100644 index 000000000..537f0da1a --- /dev/null +++ b/app/addons/fauxton/navigation/container/NavBar.js @@ -0,0 +1,68 @@ +// 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 React from "react"; + +import ReactDOM from "react-dom"; +import Stores from "../stores"; + +import NavBar from '../components/NavBar'; + +const navBarStore = Stores.navBarStore; + +const NavBarContainer = React.createClass({ + + 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() + }; + }, + + getInitialState () { + return this.getStoreState(); + }, + + onChange () { + this.setState(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 : ''; + return ( + + ); + } + +}); + + +export default NavBarContainer; diff --git a/app/addons/fauxton/navigation/stores.js b/app/addons/fauxton/navigation/stores.js index 5e781a8f6..777f09796 100644 --- a/app/addons/fauxton/navigation/stores.js +++ b/app/addons/fauxton/navigation/stores.js @@ -10,11 +10,10 @@ // License for the specific language governing permissions and limitations under // the License. -import app from "../../../app"; import FauxtonAPI from "../../../core/api"; import ActionTypes from "./actiontypes"; -const Stores = {}; +const Stores = {}; Stores.NavBarStore = FauxtonAPI.Store.extend({ initialize () { @@ -22,12 +21,24 @@ Stores.NavBarStore = FauxtonAPI.Store.extend({ }, 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 () { @@ -67,7 +78,7 @@ Stores.NavBarStore = FauxtonAPI.Store.extend({ const links = this.getLinkSection(removeLink); let indexOf = 0; - const res = _.filter(links, function (link) { + const res = links.filter((link) => { if (link.id === removeLink.id) { return true; } @@ -94,8 +105,7 @@ Stores.NavBarStore = FauxtonAPI.Store.extend({ }, toggleMenu () { - app.utils.localStorageSet(FauxtonAPI.constants.LOCAL_STORAGE.SIDEBAR_MINIMIZED, - !this.isMinimized()); + this._isMinimized = !this._isMinimized; }, getLinkSection (link) { @@ -143,8 +153,7 @@ Stores.NavBarStore = FauxtonAPI.Store.extend({ }, isMinimized () { - const isMinimized = app.utils.localStorageGet(FauxtonAPI.constants.LOCAL_STORAGE.SIDEBAR_MINIMIZED); - return (_.isUndefined(isMinimized)) ? false : isMinimized; + return this._isMinimized; }, dispatch (action) { @@ -185,6 +194,20 @@ Stores.NavBarStore = FauxtonAPI.Store.extend({ 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 diff --git a/app/addons/fauxton/navigation/tests/componentsSpec.react.jsx b/app/addons/fauxton/navigation/tests/componentsSpec.react.jsx deleted file mode 100644 index ba373644a..000000000 --- a/app/addons/fauxton/navigation/tests/componentsSpec.react.jsx +++ /dev/null @@ -1,61 +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 Views from "../components.react"; -import Auth from "../../../../core/auth"; -import BaseAuth from "../../../auth/base"; -import utils from "../../../../../test/mocha/testUtils"; -import React from "react"; -import ReactDOM from "react-dom"; -import sinon from "sinon"; -import {mount} from 'enzyme'; - -var assert = utils.assert; - -describe('NavBar', function () { - - describe('burger', function () { - it('dispatch TOGGLE_NAVBAR_MENU on click', function () { - const toggleMenu = sinon.spy(); - const burgerEl = mount(); - burgerEl.simulate('click'); - assert.ok(toggleMenu.calledOnce); - }); - - }); - - it('logout link only ever appears once', function () { - FauxtonAPI.auth = new Auth(); - sinon.stub(FauxtonAPI.session, 'isLoggedIn').returns(true); - sinon.stub(FauxtonAPI.session, 'isAdminParty').returns(false); - sinon.stub(FauxtonAPI.session, 'user').returns({ name: 'test-user' }); - BaseAuth.initialize(); - - const el = mount(); - - FauxtonAPI.session.trigger('change'); - - // confirm the logout link is present - let matches = el.text().match(/Logout/); - assert.equal(matches.length, 1); - - // now confirm there's still only a single logout link after publishing multiple - FauxtonAPI.session.trigger('change'); - FauxtonAPI.session.trigger('change'); - matches = el.text().match(/Logout/); - assert.equal(matches.length, 1); - - FauxtonAPI.session.isLoggedIn.restore(); - FauxtonAPI.session.user.restore(); - FauxtonAPI.session.isAdminParty.restore(); - }); -}); diff --git a/app/addons/fauxton/tests/nightwatch/highlightsidebar.js b/app/addons/fauxton/tests/nightwatch/highlightsidebar.js index def0e2327..daa4c5900 100644 --- a/app/addons/fauxton/tests/nightwatch/highlightsidebar.js +++ b/app/addons/fauxton/tests/nightwatch/highlightsidebar.js @@ -24,7 +24,7 @@ module.exports = { .click('a[href="#/replication"]') .pause(1000) .waitForElementVisible('.replication__activity_header-btn', waitTime, false) - .assert.cssClassPresent('li[data-nav-name="Replication"]', 'active') + .assert.cssClassPresent('a[href="#/replication"]', 'faux-navbar__link--active') .end(); } }; diff --git a/app/addons/fauxton/tests/nightwatch/notificationCenter.js b/app/addons/fauxton/tests/nightwatch/notificationCenter.js index b81d86064..0c192bd0f 100644 --- a/app/addons/fauxton/tests/nightwatch/notificationCenter.js +++ b/app/addons/fauxton/tests/nightwatch/notificationCenter.js @@ -29,6 +29,7 @@ module.exports = { .assert.cssClassNotPresent('.notification-center-panel', 'visible') .clickWhenVisible('#notification-center-btn', waitTime, false) .waitForElementPresent('.notification-center-panel.visible', waitTime, false) + .waitForElementPresent('.notification-list div.flex-layout', waitTime, false) .getText('.notification-center-panel', function (result) { var content = result.value; diff --git a/app/app.js b/app/app.js index 4551a07da..cf9be2114 100644 --- a/app/app.js +++ b/app/app.js @@ -72,6 +72,25 @@ FauxtonAPI.config({ type: 'REMOVE_NAVBAR_LINK', link: link }); + }, + + hideLogin: function () { + FauxtonAPI.dispatch({ + type: 'NAVBAR_SHOW_HIDE_LOGIN_LOGOUT_SECTION', + visible: false + }); + }, + + showLogout: function () { + FauxtonAPI.dispatch({ + type: 'NAVBAR_SHOW_LOGOUT_BUTTON' + }); + }, + + showLogin: function () { + FauxtonAPI.dispatch({ + type: 'NAVBAR_SHOW_LOGIN_BUTTON' + }); } }); diff --git a/app/constants.js b/app/constants.js index a967db4c9..e93709446 100644 --- a/app/constants.js +++ b/app/constants.js @@ -47,9 +47,5 @@ export default { MANGO_INDEX: '/_utils/docs/intro/api.html#documents', MANGO_SEARCH: '/_utils/docs/intro/api.html#documents', CHANGES: '/_utils/docs/api/database/changes.html?highlight=changes#post--db-_changes' - }, - - LOCAL_STORAGE: { - SIDEBAR_MINIMIZED: 'sidebar-minimized' } }; diff --git a/assets/less/fauxton.less b/assets/less/fauxton.less index 679857b73..31145f0b6 100644 --- a/assets/less/fauxton.less +++ b/assets/less/fauxton.less @@ -60,10 +60,6 @@ body { h2,h3,h4 {font-weight: 600;} -a { - .transition(all .25s linear); -} - // remove blue borders from clicked elements button:focus, a:focus { outline: 0; @@ -77,6 +73,7 @@ a, a:visited, a:active { color: @linkColor; + text-decoration: none; } a:hover { @@ -557,7 +554,7 @@ footer.pagination-footer { // left navigationbar is closed @media (max-width: 875px) { - body:not(.closeMenu) { + #main:not(.closeMenu) { .one-pane { .faux-header__searchboxwrapper { display: none; @@ -567,7 +564,7 @@ footer.pagination-footer { } @media (max-width: 1285px) { - body:not(.closeMenu) { + #main:not(.closeMenu) { .with-sidebar { .faux-header__searchboxwrapper { display: none; @@ -584,7 +581,7 @@ body .control-toggle-include-docs span { } @media (max-width: 1177px) { - body.closeMenu { + #main.closeMenu { .with-sidebar { .two-panel-header { .control-toggle-include-docs span::before { @@ -600,7 +597,7 @@ body .control-toggle-include-docs span { @media (max-width: 1120px) { - body.closeMenu { + #main.closeMenu { .with-sidebar { .two-panel-header { .control-toggle-include-docs span::before { @@ -627,7 +624,7 @@ body .control-toggle-include-docs span { } @media (max-width: 1339px) { - body:not(.closeMenu) { + #main:not(.closeMenu) { .with-sidebar { .two-panel-header { .control-toggle-include-docs span::before { @@ -642,7 +639,7 @@ body .control-toggle-include-docs span { } @media (max-width: 1090px) { - body:not(.closeMenu) { + #main:not(.closeMenu) { .with-sidebar { .two-panel-header { .control-toggle-include-docs span::before { diff --git a/assets/less/templates.less b/assets/less/templates.less index d7a7b49a2..f42de0078 100644 --- a/assets/less/templates.less +++ b/assets/less/templates.less @@ -50,234 +50,6 @@ } } -/* Fixed side navigation */ -#primary-navbar { - z-index: 5; - height: 100%; - left: 0; - top: 0; - bottom: 0; - position: absolute; - width: @navWidth; - .closeMenu & { - width: @collapsedNavWidth; - overflow-x: hidden; - } - background-color: @brandDark2; - .version-footer { - color: #fff; - font-size: 10px; - text-align: center; - background-color: @brandDark2; - } - .closeMenu & { - .version-footer { - display: none; - } - } - #user-create-admin { - font-size: 12px - } - .navbar { - .closeMenu & { - width: @collapsedNavWidth; - } - .burger { - padding: 22px 0 22px 18px; - position: absolute; - z-index: 100; - top: 0; - background-color: @brandDark2; - width: @navWidth; - .closeMenu & { - width: @collapsedNavWidth; - } - div { - height: 4px; - width: 24px; - .border-radius(2); - background-color: @navIconColor; - margin: 2px 0px; - } - &:hover div { - background-color: @hoverHighlight; - } - } - .bottom-container { - position: fixed; - bottom: 10px; - .brand { - .box-sizing(content-box); - .hide-text; - margin: 20px 0 0px 0; - width: 199px; - height: 50px; - padding: 10px 10px 10px 10px; - float: none; - background: @brandDark2; - .icon { - .box-sizing(content-box); - background: url(../img/CouchDB-negative-logo.png) no-repeat 23px 0px; - background-size: 150px; - display: block; - height: 100%; - width: 100%; - margin-top: 10px; - } - .closeMenu & { - width: 43px; - - .icon { - background: url(../img/couchdb-logo.png) no-repeat 3px 0; - background-size: 40px; - - } - } - } - - #footer-nav-links { - width: 100%; - margin: 0; - background: @brandDark2; - - li { - width: 100%; - text-align: center; - - a { - font-size: 10px; - color: @navIconColor; - padding: 0px; - text-shadow: none; - width: 100%; - } - - &.active, &:hover { - a { - text-decoration: underline; - color: @hoverHighlight; - } - } - } - } - } - nav { - margin-top: 64px; - .nav { - margin: 0; - li { - padding: 0; - font-size: 16px; - letter-spacing: 1px; - line-height: 24px; - width: @navWidth; - .closeMenu & { - width: @collapsedNavWidth; - } - font-weight: normal; - font-family: helvetica; - .box-sizing(border-box); - background-color: @brandDark1; - border-bottom: 1px solid @brandDark2; - height: 48px; - min-height: 48px; - position: relative; - - &.active { - a { - .box-shadow(none); - } - background-color: @brandHighlight; - //pointer-events: none; - } - &:hover { - a { - .box-shadow(none); - } - background-color: @hoverHighlight; - } - &:hover .fonticon:before { - color: @white; - } - &.active .fonticon:before, - &:hover .fonticon:before, - { - text-shadow: @boxShadow; - color: @navIconActive; - } - a { - padding: 12px 25px 12px 60px; - background-color: transparent; - color: #fff; - text-shadow: @textShadowOff; - height: 48px; - display: flex; - - .fonticon { - position: relative; - - &:before { - position: absolute; - left: -41px; - font-size: 24px; - color: @navIconColor; - text-shadow: @boxShadowOff; - } - } - .closeMenu & { - color: transparent; - span { - display: none; - } - } - } - } - } - ul#footer-nav-links { - li { - background-color: @brandDark2; - border-top: 1px solid @linkColor; - border-bottom: none; - &.active, &:hover { - background-color: @hoverHighlight; - border-top: 1px solid @linkColor; - a { - color: white; - } - } - a { - color: @linkColor; - } - } - - } - ul#bottom-nav-links { - margin-top: 0; - padding-bottom: 136px; - li { - background-color: @brandDark1; - &.active { - background-color:@brandHighlight; - } - &:hover { - background-color: @hoverHighlight; - } - a { - &.fonticon { - position: relative; - &:before { - top: -5px; - left: -40px; - font-size: 22px; - } - } - } - } - } - } - } -} - .bottom-shadow-border { border-bottom: 1px solid #999; .box-shadow(0px 6px 0 0 rgba(0, 0, 0, 0.1)); diff --git a/assets/less/variables.less b/assets/less/variables.less index a33f43f19..47d9b76a7 100644 --- a/assets/less/variables.less +++ b/assets/less/variables.less @@ -115,3 +115,10 @@ @infoAlertColor: #329898; @successAlertColor: #448c11; @errorAlertColor: #c45b55; + +/* + -- logo image paths -- + These are set during webpack bundle through your settings.json file. +*/ +@largeLogoPath: ""; +@smallLogoPath: ""; diff --git a/package.json b/package.json index 1bad8841b..3d4d985f3 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "fetch-mock": "^5.9.3", "jest": "^18.1.0", "less": "^2.7.2", - "less-loader": "^2.2.3", + "less-loader": "^4.0.3", "mocha": "~3.1.2", "mocha-loader": "^1.1.0", "mocha-phantomjs": "git+https://github.com/garrensmith/mocha-phantomjs.git", @@ -50,6 +50,7 @@ "bluebird": "^3.4.6", "brace": "^0.7.0", "chai": "^3.5.0", + "classnames": "^2.2.5", "clean-css": "^4.0.5", "clipboard": "^1.5.16", "couchapp": "~0.11.0", @@ -77,8 +78,8 @@ "imports-loader": "^0.7.0", "jquery": "^2.2.0", "jsondiffpatch": "^0.1.41", - "less": "^2.3.1", - "less-loader": "^2.2.3", + "less": "^2.7.2", + "less-loader": "^4.0.3", "lodash": "^3.10.1", "mkdirp": "^0.5.1", "moment": "^2.17.1", diff --git a/settings.json.default.json b/settings.json.default.json index fde5dada0..d474c1b5b 100644 --- a/settings.json.default.json +++ b/settings.json.default.json @@ -21,7 +21,9 @@ "dest": "dist/debug/index.html", "variables": { "title": "Project Fauxton", - "generationLabel": "Fauxton Dev" + "generationLabel": "Fauxton Dev", + "largeLogoPath": "../../../../../assets/img/CouchDB-negative-logo.png", + "smallLogoPath": "../../../../../assets/img/couchdb-logo.png" }, "app": { "root": "/", @@ -34,7 +36,9 @@ "dest": "dist/debug/index.html", "variables": { "title": "Project Fauxton", - "generationLabel": "Fauxton Release" + "generationLabel": "Fauxton Release", + "largeLogoPath": "../../../../../assets/img/CouchDB-negative-logo.png", + "smallLogoPath": "../../../../../assets/img/couchdb-logo.png" }, "app": { "root": "/", @@ -47,7 +51,9 @@ "dest": "dist/debug/index.html", "variables": { "title": "Project Fauxton", - "generationLabel": "Fauxton Couchapp" + "generationLabel": "Fauxton Couchapp", + "largeLogoPath": "../../../../../assets/img/CouchDB-negative-logo.png", + "smallLogoPath": "../../../../../assets/img/couchdb-logo.png" }, "app": { "root": "/", diff --git a/webpack.config.dev.js b/webpack.config.dev.js index 89d3dbd55..54f09f407 100644 --- a/webpack.config.dev.js +++ b/webpack.config.dev.js @@ -74,7 +74,15 @@ module.exports = { use: [ "style-loader", "css-loader", - "less-loader" + { + loader: "less-loader", + options: { + modifyVars: { + largeLogoPath: "\'" + settings.variables.largeLogoPath + "\'", + smallLogoPath: "\'" + settings.variables.smallLogoPath + "\'" + } + } + } ] }, { diff --git a/webpack.config.release.js b/webpack.config.release.js index 99316c93a..522095281 100644 --- a/webpack.config.release.js +++ b/webpack.config.release.js @@ -109,7 +109,15 @@ module.exports = { fallback: "style-loader", use: [ "css-loader", - "less-loader" + { + loader: "less-loader", + options: { + modifyVars: { + largeLogoPath: "\'" + settings.variables.largeLogoPath + "\'", + smallLogoPath: "\'" + settings.variables.smallLogoPath + "\'" + } + } + } ], publicPath: '../../' }),