Replace notification bar by notification toasts (#1265)
* Replace notification bar by notification toasts
- Extract alert colors into variables
- Remove old CSS and Javascript files
* Update E2E tests
* Add updated package-lock.json
* Rework CSS
- Make the toast opaque
- Fix .env file
- Fix E2E tests
- Optimize time by closing notification manually
- Fix some issues created by the new toasts
diff --git a/.env b/.env
index bafbd5d..d18f8e8 100644
--- a/.env
+++ b/.env
@@ -2,4 +2,4 @@
# This file needs to be placed where the docker-compose command is run from.
# Used to provide a CouchDB 2 / 3 build matrix in .travis.yml
-COUCHDB_IMAGE=ibmcom/couchdb3:preview-1569600329
+COUCHDB_IMAGE=ibmcom/couchdb3
diff --git a/.gitignore b/.gitignore
index e8b3d39..047db07 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,3 +42,6 @@
test/dashboard.assets
# test coverage dir
coverage
+
+# Jetbrains IDE
+.idea/
diff --git a/app/addons/components/components/stringeditmodal.js b/app/addons/components/components/stringeditmodal.js
index e0950de..df5877d 100644
--- a/app/addons/components/components/stringeditmodal.js
+++ b/app/addons/components/components/stringeditmodal.js
@@ -61,13 +61,14 @@
this.props.onSave(this.editor.getValue());
};
- render() {
- const { editorInitialized } = this.state;
- const saveBt = editorInitialized ? (
+ getSaveBtn = () => {
+ return this.state.editorInitialized && (
<button id="string-edit-save-btn" onClick={this.save} className="btn btn-primary save">
<i className="fonticon-circle-check"></i> Modify Text
- </button>
- ) : null;
+ </button>);
+ };
+
+ render() {
return (
<Modal className="string-editor-modal" show={this.props.visible} onHide={this.closeModal}>
<Modal.Header closeButton={true}>
@@ -81,7 +82,7 @@
</Modal.Body>
<Modal.Footer>
<a className="cancel-link" onClick={this.closeModal}>Cancel</a>
- { saveBt }
+ { this.getSaveBtn() }
</Modal.Footer>
</Modal>
);
diff --git a/app/addons/databases/tests/nightwatch/checkDatabaseTooltip.js b/app/addons/databases/tests/nightwatch/checkDatabaseTooltip.js
index dbc896f..5da4cb2 100644
--- a/app/addons/databases/tests/nightwatch/checkDatabaseTooltip.js
+++ b/app/addons/databases/tests/nightwatch/checkDatabaseTooltip.js
@@ -34,7 +34,7 @@
.clickWhenVisible('.tableview-checkbox-cell input[type="checkbox"]')
.clickWhenVisible('.bulk-action-component-selector-group button.fonticon-trash', waitTime, false)
.acceptAlert()
- .waitForElementVisible('#global-notifications .alert.alert-info', waitTime, false)
+ .waitForElementVisible('.Toastify__toast-container .Toastify__toast--info', waitTime, false)
.checkForStringPresent(newDatabaseName, '"doc_del_count":1')
diff --git a/app/addons/databases/tests/nightwatch/createsDatabase.js b/app/addons/databases/tests/nightwatch/createsDatabase.js
index 164ad84..2371b69 100644
--- a/app/addons/databases/tests/nightwatch/createsDatabase.js
+++ b/app/addons/databases/tests/nightwatch/createsDatabase.js
@@ -81,7 +81,7 @@
.waitForElementVisible('#js-new-database-name', waitTime, false)
.setValue('#js-new-database-name', [invalidDatabaseName])
.clickWhenVisible('#js-create-database', waitTime, false)
- .waitForElementVisible('.global-notification.alert.alert-error', waitTime, false)
+ .waitForElementVisible('.Toastify__toast-container .Toastify__toast--error', waitTime, false)
.url(baseUrl + '/_all_dbs')
.waitForElementVisible('html', waitTime, false)
.getText('html', function (result) {
diff --git a/app/addons/databases/tests/nightwatch/createsDatabasePartitioned.js b/app/addons/databases/tests/nightwatch/createsDatabasePartitioned.js
index 4da4b31..93c3bf1 100644
--- a/app/addons/databases/tests/nightwatch/createsDatabasePartitioned.js
+++ b/app/addons/databases/tests/nightwatch/createsDatabasePartitioned.js
@@ -83,7 +83,7 @@
.setValue('#js-new-database-name', [invalidDatabaseName])
.clickWhenVisible('#non-partitioned-db', waitTime, false)
.clickWhenVisible('#js-create-database', waitTime, false)
- .waitForElementVisible('.global-notification.alert.alert-error', waitTime, false)
+ .waitForElementVisible('.Toastify__toast-container .Toastify__toast--error', waitTime, false)
.url(baseUrl + '/_all_dbs')
.waitForElementVisible('html', waitTime, false)
.getText('html', function (result) {
diff --git a/app/addons/databases/tests/nightwatch/deletesDatabase.js b/app/addons/databases/tests/nightwatch/deletesDatabase.js
index d82c314..e5723d8 100644
--- a/app/addons/databases/tests/nightwatch/deletesDatabase.js
+++ b/app/addons/databases/tests/nightwatch/deletesDatabase.js
@@ -47,7 +47,7 @@
.waitForElementVisible('.delete-db-modal', waitTime, false)
.clickWhenVisible('.delete-db-modal input[type="text"]', waitTime, false)
.setValue('.delete-db-modal input[type="text"]', [newDatabaseName, client.Keys.ENTER])
- .waitForElementNotPresent('.global-notification .fonticon-cancel', waitTime, false)
+ .closeNotifications()
.waitForElementPresent('.fauxton-table-list', waitTime, false)
.checkForDatabaseDeleted(newDatabaseName, waitTime)
.assert.elementNotPresent('a[href="database/' + newDatabaseName + '/_all_docs"]')
diff --git a/app/addons/databases/tests/nightwatch/deletesDatabaseSpecialChars.js b/app/addons/databases/tests/nightwatch/deletesDatabaseSpecialChars.js
index 37d8b28..3476b89 100644
--- a/app/addons/databases/tests/nightwatch/deletesDatabaseSpecialChars.js
+++ b/app/addons/databases/tests/nightwatch/deletesDatabaseSpecialChars.js
@@ -21,7 +21,6 @@
.createDatabase(newDatabaseName)
.loginToGUI()
.url(baseUrl + '/#/database/' + encodeURIComponent(newDatabaseName) + '/_all_docs')
- .waitForElementNotPresent('.global-notification .fonticon-cancel', waitTime, false)
.clickWhenVisible('.faux-header__doc-header-dropdown-toggle')
.clickWhenVisible('.faux-header__doc-header-dropdown-itemwrapper .fonticon-trash')
.waitForElementVisible('.delete-db-modal', waitTime, false)
@@ -48,7 +47,7 @@
.waitForElementPresent('.delete-db-modal', waitTime, false)
.clickWhenVisible('.delete-db-modal input[type="text"]', waitTime, false)
.setValue('.delete-db-modal input[type="text"]', [newDatabaseName, client.Keys.ENTER])
- .waitForElementNotPresent('.global-notification .fonticon-cancel', waitTime, false)
+ .closeNotifications()
.waitForElementPresent('.fauxton-table-list', waitTime, false)
.checkForDatabaseDeleted(newDatabaseName, waitTime)
.assert.elementNotPresent('a[href="database/' + newDatabaseName + '/_all_docs"]')
diff --git a/app/addons/databases/tests/nightwatch/specialCharListLinks.js b/app/addons/databases/tests/nightwatch/specialCharListLinks.js
index aef1b68..650410f 100644
--- a/app/addons/databases/tests/nightwatch/specialCharListLinks.js
+++ b/app/addons/databases/tests/nightwatch/specialCharListLinks.js
@@ -37,7 +37,6 @@
.url(baseUrl + '#/_all_dbs')
.clickWhenVisible(`.fauxton-table-list a[href="database/${encodeURIComponent(db)}/_all_docs"]`)
- .waitForElementNotPresent('#global-notifications .fonticon-cancel', waitTime, false)
.waitForElementVisible('.faux-header__doc-header-title', waitTime, false)
.waitForElementVisible('.no-results-screen', waitTime, false)
diff --git a/app/addons/documents/doc-editor/components/DocEditorScreen.js b/app/addons/documents/doc-editor/components/DocEditorScreen.js
index f8f661f..242b3e6 100644
--- a/app/addons/documents/doc-editor/components/DocEditorScreen.js
+++ b/app/addons/documents/doc-editor/components/DocEditorScreen.js
@@ -96,7 +96,10 @@
if (this.props.numFilesUploaded !== nextProps.numFilesUploaded ||
this.props.doc && this.props.doc.hasChanged() ||
(this.props.doc && nextProps.doc && this.props.doc.id !== nextProps.doc.id)) {
- this.getEditor().setValue(JSON.stringify(nextProps.doc.attributes, null, ' '));
+ const editor = this.getEditor();
+ if (editor) {
+ this.getEditor().setValue(JSON.stringify(nextProps.doc.attributes, null, ' '));
+ }
this.onSaveComplete();
}
}
diff --git a/app/addons/documents/tests/nightwatch/bulkDelete.js b/app/addons/documents/tests/nightwatch/bulkDelete.js
index c3330ea..9f88268 100644
--- a/app/addons/documents/tests/nightwatch/bulkDelete.js
+++ b/app/addons/documents/tests/nightwatch/bulkDelete.js
@@ -35,7 +35,7 @@
.clickWhenVisible('.bulk-action-component-selector-group input[type="checkbox"]')
.clickWhenVisible('.bulk-action-component-selector-group button.fonticon-trash', waitTime, false)
.acceptAlert()
- .waitForElementVisible('#global-notifications .alert.alert-info', waitTime, false)
+ .waitForElementVisible('.Toastify__toast-container .Toastify__toast--info', waitTime, false)
.waitForElementNotPresent('[data-id="' + newDocumentName1 + '"]', waitTime, false)
.getText('body', function (result) {
var data = result.value,
@@ -69,7 +69,7 @@
.clickWhenVisible('.bulk-action-component-selector-group input[type="checkbox"]')
.clickWhenVisible('.bulk-action-component-selector-group button.fonticon-trash', waitTime, false)
.acceptAlert()
- .waitForElementVisible('#global-notifications .alert.alert-info', waitTime, false)
+ .waitForElementVisible('.Toastify__toast-container .Toastify__toast--info', waitTime, false)
.waitForElementNotPresent('.table-view-docs ', waitTime, false)
.getText('body', function (result) {
var data = result.value,
diff --git a/app/addons/documents/tests/nightwatch/deleteDatabaseModal.js b/app/addons/documents/tests/nightwatch/deleteDatabaseModal.js
index 32fc47b..40a6029 100644
--- a/app/addons/documents/tests/nightwatch/deleteDatabaseModal.js
+++ b/app/addons/documents/tests/nightwatch/deleteDatabaseModal.js
@@ -36,7 +36,6 @@
client
.loginToGUI()
.url(baseUrl + '/#/database/_replicator/_all_docs')
- .waitForElementNotPresent('.global-notification .fonticon-cancel', waitTime, false)
.clickWhenVisible('.faux-header__doc-header-dropdown-toggle')
.clickWhenVisible('.faux-header__doc-header-dropdown-itemwrapper .fonticon-trash')
@@ -55,7 +54,6 @@
client
.loginToGUI()
.url(baseUrl + '/#/database/' + newDatabaseName + '/_all_docs')
- .waitForElementNotPresent('.global-notification .fonticon-cancel', waitTime, false)
.clickWhenVisible('.faux-header__doc-header-dropdown-toggle')
.clickWhenVisible('.faux-header__doc-header-dropdown-itemwrapper .fonticon-trash')
diff --git a/app/addons/documents/tests/nightwatch/deletesDocuments.js b/app/addons/documents/tests/nightwatch/deletesDocuments.js
index 6d0372b..536ca23 100644
--- a/app/addons/documents/tests/nightwatch/deletesDocuments.js
+++ b/app/addons/documents/tests/nightwatch/deletesDocuments.js
@@ -33,7 +33,7 @@
.clickWhenVisible('label[for="checkbox-' + newDocumentName + '"]', waitTime, false)
.clickWhenVisible('.bulk-action-component-selector-group button.fonticon-trash', waitTime, false)
.acceptAlert()
- .waitForElementVisible('.alert.alert-info', waitTime, false)
+ .waitForElementVisible('.Toastify__toast-container .Toastify__toast--info', waitTime, false)
.waitForElementVisible('label[for="checkbox-' + newDocumentName + '2' + '"]', waitTime, false)
.clickWhenVisible('label[for="checkbox-' + newDocumentName + '2' + '"]', waitTime, false)
@@ -74,7 +74,7 @@
.clickWhenVisible('#checkbox-' + newDocumentName, waitTime, false)
.clickWhenVisible('.bulk-action-component-selector-group button.fonticon-trash', waitTime, false)
.acceptAlert()
- .waitForElementVisible('.alert.alert-info', waitTime, false)
+ .waitForElementVisible('.Toastify__toast-container .Toastify__toast--info', waitTime, false)
.clickWhenVisible('#checkbox-' + newDocumentName + '2', waitTime, false)
.clickWhenVisible('.bulk-action-component-selector-group button.fonticon-trash', waitTime, false)
diff --git a/app/addons/documents/tests/nightwatch/navigateNotFoundDB.js b/app/addons/documents/tests/nightwatch/navigateNotFoundDB.js
index 610b493..02c4ab2 100644
--- a/app/addons/documents/tests/nightwatch/navigateNotFoundDB.js
+++ b/app/addons/documents/tests/nightwatch/navigateNotFoundDB.js
@@ -23,8 +23,11 @@
client
.loginToGUI()
.url(baseUrl + '/#/database/does-not-exist/_all_docs')
- .waitForElementPresent('.global-notification', waitTime, false)
- .assert.containsText('.global-notification', 'does not exist')
+ .waitForElementVisible('.Toastify__toast-container .Toastify__toast--error', waitTime, false)
+ // We wait for the first toasts to be cleared
+ .pause(1000)
+ .waitForElementVisible('.Toastify__toast-container .Toastify__toast--error', waitTime, false)
+ .assert.containsText('.Toastify__toast-container .Toastify__toast--error .Toastify__toast-body', 'does not exist')
.verify.urlEquals(baseUrl + '/#');
}
};
diff --git a/app/addons/documents/tests/nightwatch/replicateDatabaseButton.js b/app/addons/documents/tests/nightwatch/replicateDatabaseButton.js
index 711a348..f1b399b 100644
--- a/app/addons/documents/tests/nightwatch/replicateDatabaseButton.js
+++ b/app/addons/documents/tests/nightwatch/replicateDatabaseButton.js
@@ -35,7 +35,6 @@
client
.loginToGUI()
.url(baseUrl + '/#/database/' + testDbName + '/_all_docs')
- .waitForElementNotPresent('.global-notification .fonticon-cancel', waitTime, false)
.clickWhenVisible('.faux-header__doc-header-dropdown-toggle')
.clickWhenVisible('.faux-header__doc-header-dropdown-itemwrapper .fonticon-replicate')
diff --git a/app/addons/documents/tests/nightwatch/revBrowser.js b/app/addons/documents/tests/nightwatch/revBrowser.js
index 7d19a70..716120a 100644
--- a/app/addons/documents/tests/nightwatch/revBrowser.js
+++ b/app/addons/documents/tests/nightwatch/revBrowser.js
@@ -91,7 +91,7 @@
.clickWhenVisible('.modal-footer input[type="checkbox"]')
.clickWhenVisible('.modal-footer button.btn-danger')
.clickWhenVisible('.fonticon-json')
-
+ .waitForElementNotVisible('.Toastify__toast-container .Toastify__toast', waitTime, false)
.clickWhenVisible('[data-id="_design/animals"] a')
.waitForElementVisible('.panel-section', waitTime, false)
diff --git a/app/addons/documents/tests/nightwatch/viewCreate.js b/app/addons/documents/tests/nightwatch/viewCreate.js
index dba31d0..d9d1295 100644
--- a/app/addons/documents/tests/nightwatch/viewCreate.js
+++ b/app/addons/documents/tests/nightwatch/viewCreate.js
@@ -144,7 +144,6 @@
.loginToGUI()
.populateDatabase(newDatabaseName)
.url(baseUrl + '/#/database/' + newDatabaseName + '/_all_docs')
- .waitForElementNotPresent('.global-notification .fonticon-cancel', waitTime, false)
.clickWhenVisible('.faux-header__doc-header-dropdown-toggle')
.clickWhenVisible('.faux-header__doc-header-dropdown-itemwrapper a[href*="new_view"]')
.waitForElementPresent('.index-cancel-link', waitTime, false);
diff --git a/app/addons/documents/tests/nightwatch/viewCreateBadView.js b/app/addons/documents/tests/nightwatch/viewCreateBadView.js
index 7405ed0..384406a 100644
--- a/app/addons/documents/tests/nightwatch/viewCreateBadView.js
+++ b/app/addons/documents/tests/nightwatch/viewCreateBadView.js
@@ -38,7 +38,7 @@
')
.execute('document.querySelector("#save-view").scrollIntoView();')
.clickWhenVisible('#save-view')
- .waitForElementNotPresent('.global-notification .fonticon-cancel', waitTime, false)
+ .closeNotifications()
.clickWhenVisible('.control-toggle-queryoptions', waitTime, false)
.clickWhenVisible('label[for="qoReduce"]', waitTime, false)
.clickWhenVisible('.query-options .btn-secondary', waitTime, false)
diff --git a/app/addons/documents/tests/nightwatch/viewEdit.js b/app/addons/documents/tests/nightwatch/viewEdit.js
index 96cf838..8ff46d4 100644
--- a/app/addons/documents/tests/nightwatch/viewEdit.js
+++ b/app/addons/documents/tests/nightwatch/viewEdit.js
@@ -139,7 +139,7 @@
// create the second view
.url(baseUrl + '/#/database/' + newDatabaseName + '/_all_docs')
- .waitForElementNotPresent('.global-notification .fonticon-cancel', waitTime, false)
+ .closeNotifications()
.clickWhenVisible('.faux-header__doc-header-dropdown-toggle')
.clickWhenVisible('.faux-header__doc-header-dropdown-itemwrapper a[href*="new_view"]')
diff --git a/app/addons/fauxton/appwrapper.js b/app/addons/fauxton/appwrapper.js
index 3df37c8..77cbca3 100644
--- a/app/addons/fauxton/appwrapper.js
+++ b/app/addons/fauxton/appwrapper.js
@@ -12,12 +12,13 @@
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 * as NavbarActions from './navigation/actions';
import classNames from 'classnames';
+import {toast, ToastContainer} from "react-toastify";
+
class ContentWrapper extends React.Component {
constructor(props) {
@@ -31,6 +32,13 @@
}
}
+ onKeydown = e => {
+ const code = e.keyCode || e.which;
+ if (code === 27) {
+ toast.dismiss();
+ }
+ };
+
componentDidMount () {
this.props.router.on('new-component', (routerOptions) => {
this.setState({routerOptions});
@@ -40,6 +48,12 @@
this.props.router.on('trigger-update', () => {
this.forceUpdate();
});
+
+ document.addEventListener('keydown', this.onKeydown);
+ }
+
+ componentWillUnmount() {
+ document.removeEventListener('keydown', this.onKeydown);
}
render () {
@@ -64,9 +78,21 @@
);
return (
<div>
- <PermanentNotificationContainer />
+ <ToastContainer
+ className='toast-container'
+ position="top-right"
+ autoClose={5000}
+ closeButton={false}
+ hideProgressBar
+ newestOnTop={false}
+ closeOnClick
+ rtl={false}
+ pauseOnFocusLoss
+ draggable
+ pauseOnHover
+ />
+ <PermanentNotificationContainer/>
<div id="notifications">
- <GlobalNotificationsContainer />
<NotificationPanelContainer />
</div>
<div id="main" className={mainClass}>
diff --git a/app/addons/fauxton/notifications/__tests__/components.test.js b/app/addons/fauxton/notifications/__tests__/components.test.js
index 19eac52..34c3248 100644
--- a/app/addons/fauxton/notifications/__tests__/components.test.js
+++ b/app/addons/fauxton/notifications/__tests__/components.test.js
@@ -14,81 +14,27 @@
import moment from "moment";
import { mount } from 'enzyme';
import sinon from 'sinon';
-import Notification from '../components/Notification';
import NotificationCenterPanel from '../components/NotificationCenterPanel';
import NotificationPanelRow from '../components/NotificationPanelRow';
-describe('Notification', () => {
- it('startHide is only called after visible time is out', (done) => {
- const spy = sinon.spy();
- const component = mount(<Notification
- notificationId={123}
- isHiding={false}
- key={11}
- msg={'a msg'}
- type={'error'}
- escape={true}
- style={{opacity:1}}
- visibleTime={1000}
- onStartHide={spy}
- onHideComplete={() => {}}
- />);
-
- expect(spy.called).toBeFalsy();
-
- setTimeout(() => {
- component.update();
- expect(spy.called).toBeTruthy();
- done();
- }, 3000);
- });
-
- it('notification text should be escaped by default', () => {
- const wrapper = mount(<Notification
- notificationId={123}
- isHiding={false}
- msg={'<script>window.whatever=1;</script>'}
- type={'error'}
- style={{opacity:1}}
- onStartHide={() => {}}
- onHideComplete={() => {}}
- />);
- expect(/<script>window.whatever=1;<\/script>/.test(wrapper.html())).toBeTruthy();
- });
-
- it('notification text can be rendered unescaped', () => {
- const wrapper = mount(<Notification
- notificationId={123}
- isHiding={false}
- msg={'<script>window.whatever=1;</script>'}
- type={'error'}
- escape={false}
- style={{opacity:1}}
- onStartHide={() => {}}
- onHideComplete={() => {}}
- />);
- expect(/<script>window.whatever=1;<\/script>/.test(wrapper.html())).toBeTruthy();
- });
-});
-
describe('NotificationPanelRow', () => {
const notifications = {
success: {
- notificationId: 1,
+ toastId: 1,
type: 'success',
msg: 'Success!',
cleanMsg: 'Success!',
time: moment()
},
info: {
- notificationId: 2,
+ toastId: 2,
type: 'info',
msg: 'Error!',
cleanMsg: 'Error!',
time: moment()
},
error: {
- notificationId: 3,
+ toastId: 3,
type: 'error',
msg: 'Error!',
cleanMsg: 'Error!',
diff --git a/app/addons/fauxton/notifications/__tests__/reducers.test.js b/app/addons/fauxton/notifications/__tests__/reducers.test.js
index 6304974..66da88b 100644
--- a/app/addons/fauxton/notifications/__tests__/reducers.test.js
+++ b/app/addons/fauxton/notifications/__tests__/reducers.test.js
@@ -74,11 +74,11 @@
newState = reducer(newState, action);
expect(newState.notifications.length).toBe(4);
- const idToRemove = newState.notifications[1].notificationId;
+ const idToRemove = newState.notifications[1].toastId;
const msgToRemove = newState.notifications[1].msg;
newState = reducer(newState, {
type: ActionTypes.CLEAR_SINGLE_NOTIFICATION,
- options: { notificationId: idToRemove }
+ options: { toastId: idToRemove }
});
expect(newState.notifications.length).toBe(3);
const item = newState.notifications.find(el => {
diff --git a/app/addons/fauxton/notifications/actions.js b/app/addons/fauxton/notifications/actions.js
index 0e57faf..8ef9621 100644
--- a/app/addons/fauxton/notifications/actions.js
+++ b/app/addons/fauxton/notifications/actions.js
@@ -33,11 +33,11 @@
dispatch({ type: ActionTypes.CLEAR_ALL_NOTIFICATIONS });
};
-export const clearSingleNotification = (notificationId) => (dispatch) => {
+export const clearSingleNotification = (toastId) => (dispatch) => {
dispatch({
type: ActionTypes.CLEAR_SINGLE_NOTIFICATION,
options: {
- notificationId: notificationId
+ toastId: toastId
}
});
};
@@ -50,25 +50,3 @@
}
});
};
-
-export const startHidingNotification = (notificationId) => (dispatch) => {
- dispatch({
- type: ActionTypes.START_HIDING_NOTIFICATION,
- options: {
- notificationId: notificationId
- }
- });
-};
-
-export const hideNotification = (notificationId) => (dispatch) => {
- dispatch({
- type: ActionTypes.HIDE_NOTIFICATION,
- options: {
- notificationId: notificationId
- }
- });
-};
-
-export const hideAllVisibleNotifications = () => (dispatch) => {
- dispatch({ type: ActionTypes.HIDE_ALL_NOTIFICATIONS });
-};
diff --git a/app/addons/fauxton/notifications/actiontypes.js b/app/addons/fauxton/notifications/actiontypes.js
index cd6d275..654925b 100644
--- a/app/addons/fauxton/notifications/actiontypes.js
+++ b/app/addons/fauxton/notifications/actiontypes.js
@@ -17,9 +17,6 @@
CLEAR_SINGLE_NOTIFICATION: 'CLEAR_SINGLE_NOTIFICATION',
CLEAR_ALL_NOTIFICATIONS: 'CLEAR_ALL_NOTIFICATIONS',
SELECT_NOTIFICATION_FILTER: 'SELECT_NOTIFICATION_FILTER',
- START_HIDING_NOTIFICATION: 'START_HIDING_NOTIFICATION',
- HIDE_NOTIFICATION: 'HIDE_NOTIFICATION',
- HIDE_ALL_NOTIFICATIONS: 'HIDE_ALL_NOTIFICATIONS',
SHOW_PERMANENT_NOTIFICATION: 'SHOW_PERMANENT_NOTIFICATION',
HIDE_PERMANENT_NOTIFICATION: 'HIDE_PERMANENT_NOTIFICATION'
};
diff --git a/app/addons/fauxton/notifications/components/GlobalNotifications.js b/app/addons/fauxton/notifications/components/GlobalNotifications.js
deleted file mode 100644
index 2a3d7ce..0000000
--- a/app/addons/fauxton/notifications/components/GlobalNotifications.js
+++ /dev/null
@@ -1,135 +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 PropTypes from 'prop-types';
-import React from 'react';
-import {TransitionMotion, spring, presets} from 'react-motion';
-import Notification from './Notification';
-
-export default class GlobalNotifications extends React.Component {
- static propTypes = {
- notifications: PropTypes.array.isRequired,
- hideAllVisibleNotifications: PropTypes.func.isRequired,
- startHidingNotification: PropTypes.func.isRequired,
- hideNotification: PropTypes.func.isRequired
- };
-
- componentDidMount() {
- document.addEventListener('keydown', this.onKeyDown);
- }
-
- componentWillUnmount() {
- document.removeEventListener('keydown', this.onKeyDown);
- }
-
- onKeyDown = (e) => {
- const code = e.keyCode || e.which;
- if (code === 27) {
- this.props.hideAllVisibleNotifications();
- }
- };
-
- getNotifications = () => {
- if (!this.props.notifications.length) {
- return null;
- }
-
- return this.props.notifications.map((notification, index) => {
-
- // notifications are completely removed from the DOM once they're
- if (!notification.visible) {
- return;
- }
-
- return (
- <Notification
- notificationId={notification.notificationId}
- isHiding={notification.isHiding}
- key={index}
- msg={notification.msg}
- type={notification.type}
- escape={notification.escape}
- visibleTime={notification.visibleTime}
- onStartHide={this.props.startHidingNotification}
- onHideComplete={this.props.hideNotification} />
- );
- });
- };
-
- getchildren = (items) => {
- const notifications = items.map(({key, data, style}) => {
- const notification = data;
- return (
- <Notification
- key={key}
- style={style}
- notificationId={notification.notificationId}
- isHiding={notification.isHiding}
- msg={notification.msg}
- type={notification.type}
- escape={notification.escape}
- visibleTime={notification.visibleTime}
- onStartHide={this.props.startHidingNotification}
- onHideComplete={this.props.hideNotification} />
- );
- });
-
- return (
- <div>
- {notifications}
- </div>
- );
- };
-
- getStyles = (prevItems) => {
- if (!prevItems) {
- prevItems = [];
- }
-
- return this.props.notifications
- .filter(notification => notification.visible)
- .map(notification => {
- let item = prevItems.find(style => style.key === (notification.notificationId.toString()));
- let style = !item ? {opacity: 0.5, minHeight: 50} : false;
-
- if (!style && !notification.isHiding) {
- style = {
- opacity: spring(1, presets.stiff),
- minHeight: spring(64)
- };
- } else if (!style && notification.isHiding) {
- style = {
- opacity: spring(0, presets.stiff),
- minHeight: spring(0, presets.stiff)
- };
- }
-
- return {
- key: notification.notificationId.toString(),
- style,
- data: notification
- };
- });
- };
-
- render() {
- return (
- <div id="global-notifications" role="alert">
- <TransitionMotion
- styles={this.getStyles}
- >
- {this.getchildren}
- </TransitionMotion>
- </div>
- );
- }
-}
diff --git a/app/addons/fauxton/notifications/components/GlobalNotificationsContainer.js b/app/addons/fauxton/notifications/components/GlobalNotificationsContainer.js
deleted file mode 100644
index 8eb19b2..0000000
--- a/app/addons/fauxton/notifications/components/GlobalNotificationsContainer.js
+++ /dev/null
@@ -1,30 +0,0 @@
-import { connect } from 'react-redux';
-import * as Actions from '../actions';
-import GlobalNotifications from './GlobalNotifications';
-
-const mapStateToProps = ({ notifications }) => {
- return {
- notifications: notifications.notifications
- };
-};
-
-const mapDispatchToProps = (dispatch) => {
- return {
- startHidingNotification: (notificationId) => {
- dispatch(Actions.startHidingNotification(notificationId));
- },
- hideNotification: (notificationId) => {
- dispatch(Actions.hideNotification(notificationId));
- },
- hideAllVisibleNotifications: () => {
- dispatch(Actions.hideAllVisibleNotifications());
- }
- };
-};
-
-const GlobalNotificationsContainer = connect(
- mapStateToProps,
- mapDispatchToProps
-)(GlobalNotifications);
-
-export default GlobalNotificationsContainer;
diff --git a/app/addons/fauxton/notifications/components/Notification.js b/app/addons/fauxton/notifications/components/Notification.js
deleted file mode 100644
index ed7c6ca..0000000
--- a/app/addons/fauxton/notifications/components/Notification.js
+++ /dev/null
@@ -1,91 +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 PropTypes from 'prop-types';
-import React from 'react';
-
-export default class Notification extends React.Component {
- static propTypes = {
- notificationId: PropTypes.number.isRequired,
- msg: PropTypes.string.isRequired,
- onStartHide: PropTypes.func.isRequired,
- onHideComplete: PropTypes.func.isRequired,
- type: PropTypes.oneOf(['error', 'info', 'success']),
- escape: PropTypes.bool,
- isHiding: PropTypes.bool.isRequired,
- visibleTime: PropTypes.number
- };
-
- static defaultProps = {
- type: 'info',
- visibleTime: 8000,
- escape: true
- };
-
- componentWillUnmount() {
- if (this.timeout) {
- window.clearTimeout(this.timeout);
- }
- }
-
- componentDidMount() {
- this.timeout = setTimeout(this.hide, this.props.visibleTime);
- }
-
- hide = (e) => {
- if (e) {
- e.preventDefault();
- }
- this.props.onStartHide(this.props.notificationId);
- };
-
- // many messages contain HTML, hence the need for dangerouslySetInnerHTML
- getMsg = () => {
- var msg = (this.props.escape) ? _.escape(this.props.msg) : this.props.msg;
- return {
- __html: msg
- };
- };
-
- onAnimationComplete = () => {
- if (this.props.isHiding) {
- window.setTimeout(() => this.props.onHideComplete(this.props.notificationId));
- }
- };
-
- render() {
- const {style, notificationId} = this.props;
- const iconMap = {
- error: 'fonticon-attention-circled',
- info: 'fonticon-info-circled',
- success: 'fonticon-ok-circled'
- };
-
- if (style.opacity === 0 && this.props.isHiding) {
- this.onAnimationComplete();
- }
-
- return (
- <div
- key={notificationId.toString()} className="notification-wrapper" style={{opacity: style.opacity, minHeight: style.minHeight + 'px'}}>
- <div
- style={{opacity: style.opacity, minHeight: style.minHeight + 'px'}}
- className={'global-notification alert alert-' + this.props.type}
- ref={node => this.notification = node}>
- <a data-bypass href="#" onClick={this.hide}><i className="pull-right fonticon-cancel" /></a>
- <i className={'notification-icon ' + iconMap[this.props.type]} />
- <span dangerouslySetInnerHTML={this.getMsg()}></span>
- </div>
- </div>
- );
- }
-}
diff --git a/app/addons/fauxton/notifications/components/NotificationCenterPanel.js b/app/addons/fauxton/notifications/components/NotificationCenterPanel.js
index 85ca45a..9657035 100644
--- a/app/addons/fauxton/notifications/components/NotificationCenterPanel.js
+++ b/app/addons/fauxton/notifications/components/NotificationCenterPanel.js
@@ -56,7 +56,7 @@
getStyles = (prevItems = []) => {
const styles = this.props.notifications
.map(notification => {
- let item = prevItems.find(style => style.key === (notification.notificationId.toString()));
+ let item = prevItems.find(style => style.key === (notification.toastId.toString()));
let style = !item ? {opacity: 0, height: 0} : false;
if (!style && (notification.type === this.props.filter || this.props.filter === 'all')) {
@@ -72,7 +72,7 @@
}
return {
- key: notification.notificationId.toString(),
+ key: notification.toastId.toString(),
style,
data: notification
};
diff --git a/app/addons/fauxton/notifications/components/NotificationPanelContainer.js b/app/addons/fauxton/notifications/components/NotificationPanelContainer.js
index ec03cdc..a4d37e2 100644
--- a/app/addons/fauxton/notifications/components/NotificationPanelContainer.js
+++ b/app/addons/fauxton/notifications/components/NotificationPanelContainer.js
@@ -21,8 +21,8 @@
clearAllNotifications: () => {
dispatch(Actions.clearAllNotifications());
},
- clearSingleNotification: (notificationId) => {
- dispatch(Actions.clearSingleNotification(notificationId));
+ clearSingleNotification: (toastId) => {
+ dispatch(Actions.clearSingleNotification(toastId));
}
};
};
diff --git a/app/addons/fauxton/notifications/components/NotificationPanelRow.js b/app/addons/fauxton/notifications/components/NotificationPanelRow.js
index f267c20..8ece5bb 100644
--- a/app/addons/fauxton/notifications/components/NotificationPanelRow.js
+++ b/app/addons/fauxton/notifications/components/NotificationPanelRow.js
@@ -24,8 +24,8 @@
};
clearNotification = () => {
- const {notificationId} = this.props.item;
- this.props.clearSingleNotification(notificationId);
+ const {toastId} = this.props.item;
+ this.props.clearSingleNotification(toastId);
};
render() {
diff --git a/app/addons/fauxton/notifications/reducers.js b/app/addons/fauxton/notifications/reducers.js
index 0f755d1..c2b4f77 100644
--- a/app/addons/fauxton/notifications/reducers.js
+++ b/app/addons/fauxton/notifications/reducers.js
@@ -10,9 +10,11 @@
// License for the specific language governing permissions and limitations under
// the License.
+import React from 'react';
import moment from 'moment';
import app from '../../../app';
import ActionTypes from './actiontypes';
+import {toast} from "react-toastify";
const initialState = {
notifications: [],
@@ -29,32 +31,38 @@
function addNotification ({ notifications }, info) {
const newNotifications = notifications.slice();
info = { ...info };
- info.notificationId = ++counter;
- info.cleanMsg = app.utils.stripHTML(info.msg);
+ info.toastId = ++counter;
info.time = moment();
+ info.cleanMsg = app.utils.stripHTML(info.msg);
+
if (info.escape !== true && info.escape !== false) {
info.escape = true;
}
- // all new notifications are visible by default. They get hidden after their time expires, by the component
- info.visible = true;
- info.isHiding = false;
+ info.htmlMsg = {
+ __html: (info.escape ? _.escape(info.cleanMsg) : info.cleanMsg)
+ };
// clear: true causes all visible messages to be hidden
if (info.clear) {
- newNotifications.forEach((notification) => {
- if (notification.visible) {
- notification.isHiding = true;
- }
- });
+ const idsToClear = _.map(notifications, 'toastId');
+
+ // Add some delay to prevent weird flickering
+ setTimeout(() => {
+ idsToClear.forEach(id => toast.dismiss(id));
+ }, 800);
}
+
newNotifications.unshift(info);
+
+ toast(<span dangerouslySetInnerHTML={info.htmlMsg}/>, info);
+
return newNotifications;
}
-function clearNotification({ notifications }, notificationId) {
+function clearNotification({ notifications }, toastId) {
const idx = notifications.findIndex(el => {
- return el.notificationId === notificationId;
+ return el.toastId === toastId;
});
if (idx === -1) {
// no changes
@@ -63,45 +71,9 @@
const newNotifications = [].concat(notifications);
newNotifications.splice(idx, 1);
- return newNotifications;
-}
-function startHidingNotification({ notifications }, notificationId) {
- const idx = notifications.findIndex(el => {
- return el.notificationId === notificationId;
- });
- if (idx === -1) {
- // no changes
- return notifications;
- }
+ toast.dismiss(toastId);
- const newNotifications = [].concat(notifications);
- newNotifications[idx].isHiding = true;
- return newNotifications;
-}
-
-function hideNotification({ notifications }, notificationId) {
- const idx = notifications.findIndex(el => {
- return el.notificationId === notificationId;
- });
- if (idx === -1) {
- // no changes
- return notifications;
- }
-
- const newNotifications = [].concat(notifications);
- newNotifications[idx].visible = false;
- newNotifications[idx].isHiding = false;
- return newNotifications;
-}
-
-function hideAllNotifications({ notifications }) {
- const newNotifications = [].concat(notifications);
- newNotifications.forEach((notification) => {
- if (notification.visible) {
- notification.isHiding = true;
- }
- });
return newNotifications;
}
@@ -119,6 +91,7 @@
};
case ActionTypes.CLEAR_ALL_NOTIFICATIONS:
+ toast.dismiss();
return {
...state,
notifications: []
@@ -127,25 +100,7 @@
case ActionTypes.CLEAR_SINGLE_NOTIFICATION:
return {
...state,
- notifications: clearNotification(state, options.notificationId)
- };
-
- case ActionTypes.START_HIDING_NOTIFICATION:
- return {
- ...state,
- notifications: startHidingNotification(state, options.notificationId)
- };
-
- case ActionTypes.HIDE_NOTIFICATION:
- return {
- ...state,
- notifications: hideNotification(state, options.notificationId)
- };
-
- case ActionTypes.HIDE_ALL_NOTIFICATIONS:
- return {
- ...state,
- notifications: hideAllNotifications(state)
+ notifications: clearNotification(state, options.toastId)
};
case ActionTypes.SHOW_NOTIFICATION_CENTER:
diff --git a/app/addons/fauxton/notifications/stores.js b/app/addons/fauxton/notifications/stores.js
deleted file mode 100644
index a1304e1..0000000
--- a/app/addons/fauxton/notifications/stores.js
+++ /dev/null
@@ -1,153 +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 app from "../../../app";
-import ActionTypes from "./actiontypes";
-import moment from "moment";
-var Stores = {};
-
-// static var used to assign a unique ID to each notification
-var counter = 0;
-var validNotificationTypes = ['success', 'error', 'info'];
-
-
-/**
- * Notifications are of the form:
- * {
- * notificationId: N,
- * message: "string",
- * type: "success"|etc. see above list
- * clear: true|false,
- * escape: true|false
- * }
- */
-
-Stores.NotificationStore = FauxtonAPI.Store.extend({
-
-
- addNotification (info) {
- if (_.isEmpty(info.type) || !_.includes(validNotificationTypes, info.type)) {
- console.warn('Invalid message type: ', info);
- return;
- }
-
- info.notificationId = ++counter;
- info.cleanMsg = app.utils.stripHTML(info.msg);
- info.time = moment();
-
- // all new notifications are visible by default. They get hidden after their time expires, by the component
- info.visible = true;
- info.isHiding = false;
-
- // clear: true causes all visible messages to be hidden
- if (info.clear) {
- this._notifications.forEach(function (notification) {
- if (notification.visible) {
- notification.isHiding = true;
- }
- });
- }
- this._notifications.unshift(info);
- },
-
- clearNotification (notificationId) {
- this._notifications = _.without(this._notifications, _.find(this._notifications, { notificationId: notificationId }));
- },
-
- hideNotification (notificationId) {
- var notification = _.find(this._notifications, { notificationId: notificationId });
- notification.visible = false;
- notification.isHiding = false;
- },
-
- hideAllNotifications () {
- this._notifications.forEach(function (notification) {
- if (notification.visible) {
- notification.isHiding = true;
- }
- });
- },
-
- startHidingNotification (notificationId) {
- var notification = _.find(this._notifications, { notificationId: notificationId });
- notification.isHiding = true;
- },
-
- setNotificationFilter (filter) {
- if ((_.isEmpty(filter) || !_.includes(validNotificationTypes, filter)) && filter !== 'all') {
- console.warn('Invalid notification filter: ', filter);
- return;
- }
- this._selectedNotificationFilter = filter;
- },
-
- dispatch (action) {
- switch (action.type) {
- case ActionTypes.ADD_NOTIFICATION:
- this.addNotification(action.options.info);
- break;
-
- case ActionTypes.CLEAR_ALL_NOTIFICATIONS:
- this.clearNotifications();
- break;
-
- case ActionTypes.CLEAR_SINGLE_NOTIFICATION:
- this.clearNotification(action.options.notificationId);
- break;
-
- case ActionTypes.START_HIDING_NOTIFICATION:
- this.startHidingNotification(action.options.notificationId);
- break;
-
- case ActionTypes.HIDE_NOTIFICATION:
- this.hideNotification(action.options.notificationId);
- break;
-
- case ActionTypes.HIDE_ALL_NOTIFICATIONS:
- this.hideAllNotifications();
- break;
-
- case ActionTypes.SHOW_NOTIFICATION_CENTER:
- this._notificationCenterVisible = true;
- break;
-
- case ActionTypes.HIDE_NOTIFICATION_CENTER:
- this._notificationCenterVisible = false;
- break;
-
- case ActionTypes.SELECT_NOTIFICATION_FILTER:
- this.setNotificationFilter(action.options.filter);
- break;
-
- case ActionTypes.SHOW_PERMANENT_NOTIFICATION:
- this._permanentNotificationVisible = true;
- this.setPermanentNotificationMessage(action.options.msg);
- break;
-
- case ActionTypes.HIDE_PERMANENT_NOTIFICATION:
- this._permanentNotificationVisible = false;
- break;
-
- default:
- return;
- // do nothing
- }
-
- this.triggerChange();
- }
-});
-
-Stores.notificationStore = new Stores.NotificationStore();
-Stores.notificationStore.dispatchToken = FauxtonAPI.dispatcher.register(Stores.notificationStore.dispatch.bind(Stores.notificationStore));
-
-export default Stores;
diff --git a/app/addons/replication/tests/nightwatch/replication.js b/app/addons/replication/tests/nightwatch/replication.js
index 0d2551f..39f123a 100644
--- a/app/addons/replication/tests/nightwatch/replication.js
+++ b/app/addons/replication/tests/nightwatch/replication.js
@@ -80,7 +80,7 @@
.clickWhenVisible('#replicate')
- .waitForElementNotPresent('.global-notification .fonticon-cancel', waitTime, false)
+ .waitForElementNotPresent('.Toastify__toast-container .Toastify__toast-body', waitTime, true)
.end();
},
@@ -138,7 +138,7 @@
})
.clickWhenVisible('#replicate')
- .waitForElementNotPresent('.global-notification .fonticon-cancel', waitTime, false)
+ .waitForElementNotPresent('.Toastify__toast-container .Toastify__toast-body', waitTime, true)
.end();
},
@@ -208,7 +208,7 @@
.waitForElementVisible('.replication__error-doc-modal .replication__error-continue', waitTime, true)
.clickWhenVisible('.replication__error-doc-modal .replication__error-continue')
- .waitForElementNotPresent('.global-notification .fonticon-cancel', waitTime, false)
+ .waitForElementNotPresent('.Toastify__toast-container .Toastify__toast-body', waitTime, true)
.end();
},
@@ -240,7 +240,7 @@
.clickWhenVisible('#replicate')
- .waitForElementPresent('.global-notification.alert-error', waitTime, true)
+ .waitForElementPresent('.Toastify__toast-container .Toastify__toast--error', waitTime, true)
.end();
},
@@ -280,7 +280,7 @@
.clickWhenVisible('#replicate')
- .waitForElementPresent('.global-notification.alert-error', waitTime, true)
+ .waitForElementPresent('.Toastify__toast-container .Toastify__toast--error', waitTime, true)
.end();
}
};
diff --git a/app/addons/replication/tests/nightwatch/replicationactivity.js b/app/addons/replication/tests/nightwatch/replicationactivity.js
index a29e363..3a6bfa5 100644
--- a/app/addons/replication/tests/nightwatch/replicationactivity.js
+++ b/app/addons/replication/tests/nightwatch/replicationactivity.js
@@ -27,7 +27,6 @@
.createDatabase('_replicator')
.createDocument(replicatorDoc._id, '_replicator', replicatorDoc)
.loginToGUI()
- .waitForElementNotPresent('.global-notification .fonticon-cancel', waitTime, false)
.url(baseUrl + '/#replication')
.waitForElementNotPresent('.load-lines', waitTime, true)
.waitForElementPresent('.replication__filter', waitTime, true)
@@ -51,7 +50,6 @@
.createDatabase('_replicator')
.createDocument(replicatorDoc._id, '_replicator', replicatorDoc)
.loginToGUI()
- .waitForElementNotPresent('.global-notification .fonticon-cancel', waitTime, false)
.url(baseUrl + '/#replication')
.waitForElementNotPresent('.load-lines', waitTime, true)
.waitForElementPresent('.replication__filter', waitTime, true)
@@ -84,7 +82,6 @@
.createDocument(replicatorDoc1._id, '_replicator', replicatorDoc1)
.createDocument(replicatorDoc2._id, '_replicator', replicatorDoc2)
.loginToGUI()
- .waitForElementNotPresent('.global-notification .fonticon-cancel', waitTime, false)
.url(baseUrl + '/#replication')
.waitForElementNotPresent('.load-lines', waitTime, true)
.waitForElementVisible('.replication__filter-input', waitTime, true)
@@ -118,7 +115,6 @@
.createDocument(replicatorDoc1._id, '_replicator', replicatorDoc1)
.createDocument(replicatorDoc2._id, '_replicator', replicatorDoc2)
.loginToGUI()
- .waitForElementNotPresent('.global-notification .fonticon-cancel', waitTime, false)
.url(baseUrl + '/#replication')
.waitForElementNotPresent('.load-lines', waitTime, true)
.waitForElementVisible(firstRowSelector, waitTime, true)
diff --git a/app/addons/search/tests/nightwatch/cloneSearchIndex.js b/app/addons/search/tests/nightwatch/cloneSearchIndex.js
index 8056f78..44e194d 100644
--- a/app/addons/search/tests/nightwatch/cloneSearchIndex.js
+++ b/app/addons/search/tests/nightwatch/cloneSearchIndex.js
@@ -31,7 +31,7 @@
.clearValue('#search-name')
.setValue('#search-name', 'test1-index')
.clickWhenVisible('#save-index')
- .waitForElementVisible('#global-notifications .alert.alert-success', waitTime, false)
+ .waitForElementVisible('.Toastify__toast-container .Toastify__toast--success', waitTime, false)
.clickWhenVisible('.index-list li span', waitTime, true)
.clickWhenVisible('.popover-content .fonticon-files-o', waitTime, true)
diff --git a/app/addons/search/tests/nightwatch/createNewSearch.js b/app/addons/search/tests/nightwatch/createNewSearch.js
index 0015d51..270432c 100644
--- a/app/addons/search/tests/nightwatch/createNewSearch.js
+++ b/app/addons/search/tests/nightwatch/createNewSearch.js
@@ -67,7 +67,7 @@
.clearValue('#search-name')
.setValue('#search-name', 'clean-slate-test')
.clickWhenVisible('#save-index')
- .waitForElementVisible('#global-notifications .alert.alert-success', client.globals.maxWaitTime, false)
+ .waitForElementVisible('.Toastify__toast-container .Toastify__toast--success', client.globals.maxWaitTime, false)
.url(baseUrl + '/#/database/' + newDatabaseName + '/_all_docs')
.waitForElementPresent('.tableview-checkbox-cell', client.globals.maxWaitTime, false)
.waitForElementNotPresent('.loading-lines', client.globals.maxWaitTime, false)
@@ -99,7 +99,7 @@
.clearValue('#search-name')
.setValue('#search-name', 'test1-index')
.clickWhenVisible('#save-index')
- .waitForElementVisible('#global-notifications .alert.alert-success', client.globals.maxWaitTime, false)
+ .waitForElementVisible('.Toastify__toast-container .Toastify__toast--success', client.globals.maxWaitTime, false)
.url(baseUrl + '/#/database/' + newDatabaseName + '/_all_docs')
.waitForElementPresent('.tableview-checkbox-cell', client.globals.maxWaitTime, false)
.waitForElementNotPresent('.loading-lines', client.globals.maxWaitTime, false)
@@ -114,7 +114,7 @@
.setValue('#search-name', 'test2-index')
.clickWhenVisible('#save-index')
- .waitForElementVisible('#global-notifications .alert.alert-success', client.globals.maxWaitTime, false)
+ .waitForElementVisible('.Toastify__toast-container .Toastify__toast--success', client.globals.maxWaitTime, false)
// 3. confirm that the nav bar shows ONLY one search index each:
// _design/test1 has the single _design/test1-index
diff --git a/app/addons/search/tests/nightwatch/deleteSearchIndex.js b/app/addons/search/tests/nightwatch/deleteSearchIndex.js
index b8e4a03..d847c43 100644
--- a/app/addons/search/tests/nightwatch/deleteSearchIndex.js
+++ b/app/addons/search/tests/nightwatch/deleteSearchIndex.js
@@ -30,7 +30,7 @@
.clearValue('#search-name')
.setValue('#search-name', 'test1-index')
.clickWhenVisible('#save-index')
- .waitForElementVisible('#global-notifications .alert.alert-success', waitTime, false)
+ .waitForElementVisible('.Toastify__toast-container .Toastify__toast--success', waitTime, false)
.url(baseUrl + '/#/database/' + newDatabaseName + '/_all_docs')
.waitForElementPresent('.tableview-checkbox-cell', client.globals.maxWaitTime, false)
.waitForElementNotPresent('.loading-lines', waitTime, false)
@@ -64,7 +64,7 @@
.clearValue('#search-name')
.setValue('#search-name', 'search-index1')
.clickWhenVisible('#save-index')
- .waitForElementVisible('#global-notifications .alert.alert-success', waitTime, false)
+ .waitForElementVisible('.Toastify__toast-container .Toastify__toast--success', waitTime, false)
.url(baseUrl + '/#/database/' + newDatabaseName + '/_all_docs')
.waitForElementPresent('.tableview-checkbox-cell', client.globals.maxWaitTime, false)
.waitForElementNotPresent('.loading-lines', waitTime, false)
diff --git a/app/addons/search/tests/nightwatch/searchPageApiBar.js b/app/addons/search/tests/nightwatch/searchPageApiBar.js
index 775de1b..d8e70b9 100644
--- a/app/addons/search/tests/nightwatch/searchPageApiBar.js
+++ b/app/addons/search/tests/nightwatch/searchPageApiBar.js
@@ -39,7 +39,7 @@
.clearValue('#search-name')
.setValue('#search-name', 'api-bar-test')
.clickWhenVisible('#save-index')
- .waitForElementVisible('#global-notifications .alert.alert-success', client.globals.maxWaitTime, false)
+ .waitForElementVisible('.Toastify__toast-container .Toastify__toast--success', client.globals.maxWaitTime, false)
// confirm the API URL field now shows up (we're on the edit search index page now)
.assert.elementPresent('.faux__jsonlink')
@@ -48,7 +48,7 @@
.setValue('#search-index-preview-form input', searchStr)
.clickWhenVisible('#search-index-preview-form button')
- .waitForElementNotVisible('#global-notifications', client.globals.maxWaitTime, false)
+ .waitForElementNotVisible('.Toastify__toast-container .Toastify__toast--success', client.globals.maxWaitTime, false)
.waitForElementNotPresent('.loading-lines', client.globals.maxWaitTime, false)
.assert.attributeContains('.faux__jsonlink-link', 'href', fullURL)
.end();
diff --git a/app/app.js b/app/app.js
index ed6d994..713b8de 100644
--- a/app/app.js
+++ b/app/app.js
@@ -15,6 +15,7 @@
import Helpers from "./helpers";
import Utils from "./core/utils";
import FauxtonAPI from "./core/api";
+import 'react-toastify/dist/ReactToastify.min.css';
import "../assets/less/fauxton.less";
// Make sure we have a console.log
diff --git a/assets/less/fauxton.less b/assets/less/fauxton.less
index c2564ad..5bc847a 100644
--- a/assets/less/fauxton.less
+++ b/assets/less/fauxton.less
@@ -192,53 +192,6 @@
font-size: 16px;
}
-// customize the twitter bootstrap alert
-.global-notification {
- &.alert {
- padding-left: 20px;
- padding-top: 20px;
- min-height: @collapsedNavWidth;
- margin-bottom: auto;
- text-shadow: none;
- .border-radius(0);
- border-bottom: 1px solid @brandDark2;
- a, a:hover {
- color: #cecece;
- text-decoration: none;
- }
- a.js-dismiss {
- color: rgba(255, 255, 255, 1);
- }
- a.js-dismiss :hover {
- color: rgba(255, 255, 255, 0.4);
- }
- }
- &.alert-success {
- background-color: #448c11;
- color: #CBDFBD;
- }
- &.alert-success h4 {
- color: #CBDFBD;
- }
- &.alert-danger,
- &.alert-error {
- background-color: #222;
- color: #ff0c35;
- }
- &.alert-danger h4,
- &.alert-error h4 {
- color: #ff0c35;
- }
- &.alert-info {
- background-color: #329898;
- color: #fff;
- }
- &.alert-info h4 {
- color: #fff;
- }
-}
-
-
.notification-icon {
padding-right: 8px;
}
diff --git a/assets/less/notification-center.less b/assets/less/notification-center.less
index caaa7ec..e5b6f02 100644
--- a/assets/less/notification-center.less
+++ b/assets/less/notification-center.less
@@ -181,13 +181,13 @@
}
.fonticon-ok-circled {
- color: @successAlertColor;
+ color: @successAlertBackground;
}
.fonticon-attention-circled {
- color: @errorAlertColor;
+ color: @globalErrorAlertBackground;
}
.fonticon-info-circled {
- color: @infoAlertColor;
+ color: @infoAlertBackground;
}
.notification-page-mask {
diff --git a/assets/less/templates.less b/assets/less/templates.less
index b2819ac..b5bc864 100644
--- a/assets/less/templates.less
+++ b/assets/less/templates.less
@@ -24,14 +24,6 @@
height: 100%;
}
-#global-notifications {
- position: fixed;
- top: 0;
- display: block;
- z-index: 100000;
- width: 100%;
-}
-
#app-container {
height: 100vh;
position: relative;
@@ -289,3 +281,39 @@
opacity: 0;
height: 0;
}
+
+.toast-container {
+ margin-top: @breadcrumbHeight;
+ padding:0;
+ right: 0;
+}
+
+.Toastify__toast {
+ border: 1px solid #999;
+ background-color: #fff;
+ border-right: 0;
+ color: black;
+}
+
+.Toastify__toast--info {
+ &, .Toastify__close-button--info {
+ border-left: 6px solid @infoAlertBackground;
+ }
+}
+
+.Toastify__toast--success {
+ &, .Toastify__close-button--success {
+ border-left: 6px solid @successAlertBackground;
+ }
+}
+.Toastify__toast--warning {
+ &, .Toastify__close-button--warning {
+ border-left: 6px solid @warningBackground;
+ }
+}
+.Toastify__toast--error {
+ &, .Toastify__close-button--error {
+ border-left: 6px solid @errorAlertColor;
+ }
+}
+
diff --git a/assets/less/variables.less b/assets/less/variables.less
index 57aec02..7a7a8a1 100644
--- a/assets/less/variables.less
+++ b/assets/less/variables.less
@@ -60,6 +60,7 @@
@breadcrumbText: @brandHighlight;
@breadcrumbArrow: #999999;
@breadcrumbBorder: @brandHighlight;
+@breadcrumbHeight: 64px;
/* document-header in doclist */
@docHeaderBG: #3a3a3a;
@@ -111,10 +112,15 @@
/* padding and margins */
@panelPadding: 15px;
-/* alerts */
-@infoAlertColor: #329898;
-@successAlertColor: #448c11;
-@errorAlertColor: #c45b55;
+/* Alerts */
+@globalErrorAlertBackground: #c45b55;
+
+@infoAlertBackground: #329898;
+@infoAlertColor: #fff;
+@successAlertBackground: #448c11;
+@successAlertColor: #CBDFBD;
+@errorAlertBackground: #222;
+@errorAlertColor: #ff0c35;
/* query history */
@queryHistoryBGColor: white;
diff --git a/package-lock.json b/package-lock.json
index 6c26735..be5787f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4945,6 +4945,11 @@
"cssom": "0.3.x"
}
},
+ "csstype": {
+ "version": "2.6.10",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.10.tgz",
+ "integrity": "sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w=="
+ },
"currently-unhandled": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
@@ -5330,6 +5335,15 @@
"utila": "~0.4"
}
},
+ "dom-helpers": {
+ "version": "5.1.4",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.1.4.tgz",
+ "integrity": "sha512-TjMyeVUvNEnOnhzs6uAn9Ya47GmMo3qq7m+Lr/3ON0Rs5kHvb8I+SQYjLUSYn7qhEm0QjW0yrBkvz9yOrwwz1A==",
+ "requires": {
+ "@babel/runtime": "^7.8.7",
+ "csstype": "^2.6.7"
+ }
+ },
"dom-serializer": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz",
@@ -13574,6 +13588,13 @@
"map-age-cleaner": "^0.1.1",
"mimic-fn": "^2.0.0",
"p-is-promise": "^2.0.0"
+ },
+ "dependencies": {
+ "mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
+ }
}
},
"memory-fs": {
@@ -13740,11 +13761,6 @@
"mime-db": "~1.35.0"
}
},
- "mimic-fn": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
- "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
- },
"min-document": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz",
@@ -14060,16 +14076,107 @@
"optional": true
},
"nano": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/nano/-/nano-7.0.0.tgz",
- "integrity": "sha512-zR1jRRfpG/lcFjYnGGxabABLFRtFX1E7YqWIJzvC0dLRJ9NTxodJC4MzVifhriMT9yhulsOf8k2UNOX8fMULAg==",
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/nano/-/nano-8.0.1.tgz",
+ "integrity": "sha512-q9q8894oLNyWHhsUjF4Nk9gAvGorsCrmx0aeA7AG9pYRnM+xtAKVX5GtWfW8c2qx4mJftXHeN1/6RR8Oo9eQaQ==",
"requires": {
"@types/request": "^2.47.1",
- "cloudant-follow": "~0.17.0",
+ "cloudant-follow": "^0.18.0",
"debug": "^2.2.0",
"errs": "^0.3.2",
- "lodash.isempty": "^4.4.0",
"request": "^2.85.0"
+ },
+ "dependencies": {
+ "aws4": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz",
+ "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug=="
+ },
+ "cloudant-follow": {
+ "version": "0.18.2",
+ "resolved": "https://registry.npmjs.org/cloudant-follow/-/cloudant-follow-0.18.2.tgz",
+ "integrity": "sha512-qu/AmKxDqJds+UmT77+0NbM7Yab2K3w0qSeJRzsq5dRWJTEJdWeb+XpG4OpKuTE9RKOa/Awn2gR3TTnvNr3TeA==",
+ "requires": {
+ "browser-request": "~0.3.0",
+ "debug": "^4.0.1",
+ "request": "^2.88.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+ "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "request": {
+ "version": "2.88.2",
+ "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
+ "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
+ "requires": {
+ "aws-sign2": "~0.7.0",
+ "aws4": "^1.8.0",
+ "caseless": "~0.12.0",
+ "combined-stream": "~1.0.6",
+ "extend": "~3.0.2",
+ "forever-agent": "~0.6.1",
+ "form-data": "~2.3.2",
+ "har-validator": "~5.1.3",
+ "http-signature": "~1.2.0",
+ "is-typedarray": "~1.0.0",
+ "isstream": "~0.1.2",
+ "json-stringify-safe": "~5.0.1",
+ "mime-types": "~2.1.19",
+ "oauth-sign": "~0.9.0",
+ "performance-now": "^2.1.0",
+ "qs": "~6.5.2",
+ "safe-buffer": "^5.1.2",
+ "tough-cookie": "~2.5.0",
+ "tunnel-agent": "^0.6.0",
+ "uuid": "^3.3.2"
+ }
+ }
+ }
+ },
+ "har-validator": {
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
+ "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
+ "requires": {
+ "ajv": "^6.5.5",
+ "har-schema": "^2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
+ "oauth-sign": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
+ "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
+ },
+ "safe-buffer": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
+ "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg=="
+ },
+ "tough-cookie": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
+ "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
+ "requires": {
+ "psl": "^1.1.28",
+ "punycode": "^2.1.1"
+ }
+ },
+ "uuid": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
+ "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
+ }
}
},
"nanomatch": {
@@ -14682,6 +14789,13 @@
"integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==",
"requires": {
"mimic-fn": "^2.1.0"
+ },
+ "dependencies": {
+ "mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
+ }
}
},
"opn": {
@@ -15634,6 +15748,11 @@
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
"integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY="
},
+ "psl": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
+ "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ=="
+ },
"public-encrypt": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz",
@@ -16207,6 +16326,83 @@
}
}
},
+ "react-toastify": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-5.5.0.tgz",
+ "integrity": "sha512-jsVme7jALIFGRyQsri/g4YTsRuaaGI70T6/ikjwZMB4mwTZaCWqj5NqxhGrRStKlJc5npXKKvKeqTiRGQl78LQ==",
+ "requires": {
+ "@babel/runtime": "^7.4.2",
+ "classnames": "^2.2.6",
+ "prop-types": "^15.7.2",
+ "react-transition-group": "^4"
+ },
+ "dependencies": {
+ "classnames": {
+ "version": "2.2.6",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz",
+ "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q=="
+ },
+ "loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "requires": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ }
+ },
+ "prop-types": {
+ "version": "15.7.2",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
+ "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
+ "requires": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.8.1"
+ }
+ },
+ "react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+ }
+ }
+ },
+ "react-transition-group": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.3.0.tgz",
+ "integrity": "sha512-1qRV1ZuVSdxPlPf4O8t7inxUGpdyO5zG9IoNfJxSO0ImU2A1YWkEQvFPuIPZmMLkg5hYs7vv5mMOyfgSkvAwvw==",
+ "requires": {
+ "@babel/runtime": "^7.5.5",
+ "dom-helpers": "^5.0.1",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.6.2"
+ },
+ "dependencies": {
+ "loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "requires": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ }
+ },
+ "prop-types": {
+ "version": "15.7.2",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
+ "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
+ "requires": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.8.1"
+ }
+ },
+ "react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+ }
+ }
+ },
"read-pkg": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
@@ -17702,31 +17898,6 @@
"strip-ansi": "^4.0.0"
}
},
- "string-width": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
- "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
- "requires": {
- "emoji-regex": "^7.0.1",
- "is-fullwidth-code-point": "^2.0.0",
- "strip-ansi": "^5.1.0"
- },
- "dependencies": {
- "ansi-regex": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
- "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
- },
- "strip-ansi": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
- "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
- "requires": {
- "ansi-regex": "^4.1.0"
- }
- }
- }
- },
"string.prototype.matchall": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.2.tgz",
@@ -19874,6 +20045,31 @@
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^13.1.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
+ },
+ "string-width": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+ "requires": {
+ "emoji-regex": "^7.0.1",
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^5.1.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "requires": {
+ "ansi-regex": "^4.1.0"
+ }
+ }
}
},
"yargs-parser": {
diff --git a/package.json b/package.json
index e585956..6f78690 100644
--- a/package.json
+++ b/package.json
@@ -80,7 +80,7 @@
"mini-css-extract-plugin": "^0.7.0",
"mkdirp": "^0.5.1",
"moment": "^2.17.1",
- "nano": "~7.0.0",
+ "nano": "~8.0.0",
"optimist": "^0.6.1",
"pouchdb-adapter-http": "^7.1.1",
"pouchdb-core": "^7.1.1",
@@ -93,6 +93,7 @@
"react-range": "0.0.7",
"react-redux": "^5.0.7",
"react-select": "^1.2.0",
+ "react-toastify": "^5.5.0",
"redux": "^4.0.0",
"redux-thunk": "^2.1.0",
"regenerator-runtime": "^0.11.1",
diff --git a/test/nightwatch_tests/custom-commands/clickWhenVisible.js b/test/nightwatch_tests/custom-commands/clickWhenVisible.js
index 052169a..1c604ed 100644
--- a/test/nightwatch_tests/custom-commands/clickWhenVisible.js
+++ b/test/nightwatch_tests/custom-commands/clickWhenVisible.js
@@ -20,6 +20,7 @@
this
.waitForElementPresent(element, waitTime, false)
+ .moveToElement(element, 10, 10)
.waitForElementVisible(element, waitTime, false)
.click(element);
diff --git a/test/nightwatch_tests/custom-commands/closeNotification.js b/test/nightwatch_tests/custom-commands/closeNotification.js
index 6350d54..7b3fc66 100644
--- a/test/nightwatch_tests/custom-commands/closeNotification.js
+++ b/test/nightwatch_tests/custom-commands/closeNotification.js
@@ -14,10 +14,10 @@
exports.command = function () {
var client = this,
- dismissSelector = '#global-notifications .fonticon-cancel';
+ dismissSelector = '.Toastify__toast-container .Toastify__toast-body';
client
- .waitForElementPresent(dismissSelector, helpers.maxWaitTime, false)
+ .waitForElementVisible(dismissSelector, helpers.maxWaitTime, false)
.click(dismissSelector)
.waitForElementNotPresent(dismissSelector, helpers.maxWaitTime, false);
diff --git a/test/nightwatch_tests/custom-commands/closeNotifications.js b/test/nightwatch_tests/custom-commands/closeNotifications.js
new file mode 100644
index 0000000..ba9fd7c
--- /dev/null
+++ b/test/nightwatch_tests/custom-commands/closeNotifications.js
@@ -0,0 +1,25 @@
+// 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.
+
+var helpers = require('../helpers/helpers.js');
+
+exports.command = function () {
+ var client = this,
+ dismissSelector = '.Toastify__toast-container .Toastify__toast-body';
+
+ client
+ .waitForElementVisible(dismissSelector, helpers.maxWaitTime, false)
+ .keys(client.keys.ESCAPE)
+ .waitForElementNotPresent(dismissSelector, helpers.maxWaitTime, false);
+
+ return this;
+};