From b8261123d04c8470e20564600ee9cbda543e3fa5 Mon Sep 17 00:00:00 2001 From: Oswaldo Acauan Date: Thu, 12 Oct 2017 15:53:33 -0300 Subject: [PATCH 1/2] Add toast notifications --- .../client/stylesheets/toastify.css | 120 ++++++++++++ .../imports/ui/components/app/component.jsx | 2 + .../imports/ui/components/toast/component.jsx | 38 ++++ .../imports/ui/components/toast/container.jsx | 21 +++ .../imports/ui/components/toast/service.jsx | 21 +++ .../imports/ui/components/toast/styles.scss | 178 ++++++++++++++++++ .../imports/ui/services/api/index.js | 8 +- .../imports/ui/services/notification/index.js | 28 --- .../notification/notificationService.js | 41 ---- .../ui/stylesheets/variables/palette.scss | 1 + bigbluebutton-html5/package.json | 1 + 11 files changed, 386 insertions(+), 73 deletions(-) create mode 100755 bigbluebutton-html5/client/stylesheets/toastify.css mode change 100644 => 100755 bigbluebutton-html5/imports/ui/components/app/component.jsx create mode 100755 bigbluebutton-html5/imports/ui/components/toast/component.jsx create mode 100755 bigbluebutton-html5/imports/ui/components/toast/container.jsx create mode 100755 bigbluebutton-html5/imports/ui/components/toast/service.jsx create mode 100755 bigbluebutton-html5/imports/ui/components/toast/styles.scss mode change 100644 => 100755 bigbluebutton-html5/imports/ui/services/api/index.js delete mode 100644 bigbluebutton-html5/imports/ui/services/notification/index.js delete mode 100644 bigbluebutton-html5/imports/ui/services/notification/notificationService.js mode change 100644 => 100755 bigbluebutton-html5/imports/ui/stylesheets/variables/palette.scss diff --git a/bigbluebutton-html5/client/stylesheets/toastify.css b/bigbluebutton-html5/client/stylesheets/toastify.css new file mode 100755 index 0000000000..05ee77479e --- /dev/null +++ b/bigbluebutton-html5/client/stylesheets/toastify.css @@ -0,0 +1,120 @@ +/* TODO: We can remove this file after we update react animation package */ +@keyframes toastify-bounceInRight { + from, 60%, 75%, 90%, to { + animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); } + from { + opacity: 0; + transform: translate3d(3000px, 0, 0); } + 60% { + opacity: 1; + transform: translate3d(-25px, 0, 0); } + 75% { + transform: translate3d(10px, 0, 0); } + 90% { + transform: translate3d(-5px, 0, 0); } + to { + transform: none; } } +.toastify-bounceInRight, .toast-enter--top-right, .toast-enter--bottom-right { + animation-name: toastify-bounceInRight; } + +@keyframes toastify-bounceOutRight { + 20% { + opacity: 1; + transform: translate3d(-20px, 0, 0); } + to { + opacity: 0; + transform: translate3d(2000px, 0, 0); } } +.toastify-bounceOutRight, .toast-exit--top-right, .toast-exit--bottom-right { + animation-name: toastify-bounceOutRight; } + +@keyframes toastify-bounceInLeft { + from, 60%, 75%, 90%, to { + animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); } + 0% { + opacity: 0; + transform: translate3d(-3000px, 0, 0); } + 60% { + opacity: 1; + transform: translate3d(25px, 0, 0); } + 75% { + transform: translate3d(-10px, 0, 0); } + 90% { + transform: translate3d(5px, 0, 0); } + to { + transform: none; } } +.toastify-bounceInLeft, .toast-enter--top-left, .toast-enter--bottom-left { + animation-name: toastify-bounceInLeft; } + +@keyframes toastify-bounceOutLeft { + 20% { + opacity: 1; + transform: translate3d(20px, 0, 0); } + to { + opacity: 0; + transform: translate3d(-2000px, 0, 0); } } +.toastify-bounceOutLeft, .toast-exit--top-left, .toast-exit--bottom-left { + animation-name: toastify-bounceOutLeft; } + +@keyframes toastify-bounceInUp { + from, 60%, 75%, 90%, to { + animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); } + from { + opacity: 0; + transform: translate3d(0, 3000px, 0); } + 60% { + opacity: 1; + transform: translate3d(0, -20px, 0); } + 75% { + transform: translate3d(0, 10px, 0); } + 90% { + transform: translate3d(0, -5px, 0); } + to { + transform: translate3d(0, 0, 0); } } +.toastify-bounceInUp, .toast-enter--bottom-center { + animation-name: toastify-bounceInUp; } + +@keyframes toastify-bounceOutUp { + 20% { + transform: translate3d(0, -10px, 0); } + 40%, 45% { + opacity: 1; + transform: translate3d(0, 20px, 0); } + to { + opacity: 0; + transform: translate3d(0, -2000px, 0); } } +.toastify-bounceOutUp, .toast-exit--top-center { + animation-name: toastify-bounceOutUp; } + +@keyframes toastify-bounceInDown { + from, 60%, 75%, 90%, to { + animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); } + 0% { + opacity: 0; + transform: translate3d(0, -3000px, 0); } + 60% { + opacity: 1; + transform: translate3d(0, 25px, 0); } + 75% { + transform: translate3d(0, -10px, 0); } + 90% { + transform: translate3d(0, 5px, 0); } + to { + transform: none; } } +.toastify-bounceInDown, .toast-enter--top-center { + animation-name: toastify-bounceInDown; } + +@keyframes toastify-bounceOutDown { + 20% { + transform: translate3d(0, 10px, 0); } + 40%, 45% { + opacity: 1; + transform: translate3d(0, -20px, 0); } + to { + opacity: 0; + transform: translate3d(0, 2000px, 0); } } +.toastify-bounceOutDown, .toast-exit--bottom-center { + animation-name: toastify-bounceOutDown; } + +.toastify-animated { + animation-duration: 0.75s; + animation-fill-mode: both; } diff --git a/bigbluebutton-html5/imports/ui/components/app/component.jsx b/bigbluebutton-html5/imports/ui/components/app/component.jsx old mode 100644 new mode 100755 index ffb24acebd..f3c0b2440a --- a/bigbluebutton-html5/imports/ui/components/app/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/app/component.jsx @@ -4,6 +4,7 @@ import { defineMessages, injectIntl } from 'react-intl'; import Modal from 'react-modal'; import cx from 'classnames'; +import ToastContainer from '../toast/container'; import ModalContainer from '../modal/container'; import NotificationsBarContainer from '../notifications-bar/container'; import AudioNotificationContainer from '../audio/audio-notification/container'; @@ -189,6 +190,7 @@ class App extends Component { + ); diff --git a/bigbluebutton-html5/imports/ui/components/toast/component.jsx b/bigbluebutton-html5/imports/ui/components/toast/component.jsx new file mode 100755 index 0000000000..63ad166aad --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/toast/component.jsx @@ -0,0 +1,38 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { toast } from 'react-toastify'; + +import Icon from '../icon/component'; +import styles from './styles'; + +const propTypes = { + icon: PropTypes.string, + message: PropTypes.string.isRequired, + type: PropTypes.oneOf(Object.values(toast.TYPE)).isRequired, +}; + +const defaultProps = { + icon: null, +}; + +const defaultIcons = { + [toast.TYPE.INFO]: 'help', + [toast.TYPE.SUCCESS]: 'checkmark', + [toast.TYPE.WARNING]: 'warning', + [toast.TYPE.ERROR]: 'close', + [toast.TYPE.DEFAULT]: 'about', +}; + +const Toast = ({ icon, type, message }) => ( +
+
+
+ {message} +
+
+); + +export default Toast; + +Toast.propTypes = propTypes; +Toast.defaultProps = defaultProps; diff --git a/bigbluebutton-html5/imports/ui/components/toast/container.jsx b/bigbluebutton-html5/imports/ui/components/toast/container.jsx new file mode 100755 index 0000000000..67a38c4c0e --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/toast/container.jsx @@ -0,0 +1,21 @@ +import React from 'react'; + +import { ToastContainer } from 'react-toastify'; + +import Icon from '../icon/component'; +import styles from './styles'; + +export default () => ( + } + autoClose={5000} + className={styles.container} + toastClassName={styles.toast} + bodyClassName={styles.body} + progressClassName={styles.progress} + newestOnTop={false} + hideProgressBar={false} + closeOnClick + pauseOnHover + /> +); diff --git a/bigbluebutton-html5/imports/ui/components/toast/service.jsx b/bigbluebutton-html5/imports/ui/components/toast/service.jsx new file mode 100755 index 0000000000..10dbaa4e0a --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/toast/service.jsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { toast } from 'react-toastify'; + +import Toast from './component'; + +let lastToastId = null; +const notify = (message, type = 'default', icon, options) => { + const settings = { + type, + ...options, + }; + + if (!toast.isActive(lastToastId)) { + lastToastId = toast(, settings); + } +}; + +export default notify; + +export const withToast = ComponentToWrap => + props => (); diff --git a/bigbluebutton-html5/imports/ui/components/toast/styles.scss b/bigbluebutton-html5/imports/ui/components/toast/styles.scss new file mode 100755 index 0000000000..7cb2fca832 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/toast/styles.scss @@ -0,0 +1,178 @@ +@import "../../stylesheets/variables/_all"; + +$toast-default-color: $color-white; +$toast-default-bg: $color-gray; + +$toast-info-color: $color-white; +$toast-info-bg: $color-primary; + +$toast-success-color: $color-white; +$toast-success-bg: $color-success; + +$toast-error-color: $color-white; +$toast-error-bg: $color-danger; + +$toast-warning-color: $color-white; +$toast-warning-bg: $color-warning; + +$background: $color-white; +$background-active: darken($color-white, 5%); + +@mixin notification-variant($icon-color, $icon-bg) { + display: flex; + + > .icon { + color: $icon-color; + background-color: $icon-bg; + } +} + +.icon { + align-self: flex-start; + margin-bottom: auto; + margin-right: $sm-padding-x; + width: 2rem; + border-radius: 50%; + position: relative; + height: 2rem; + flex-shrink: 0; + + > i { + line-height: 0; + color: inherit; + position: absolute; + top: 50%; + width: 100%; + } +} + +.message { + margin-top: auto; + margin-bottom: auto; + font-size: $font-size-small; + max-height: 15vh; + overflow: auto; +} + +.default { + @include notification-variant($toast-default-color, $toast-default-bg); +} + +.info { + @include notification-variant($toast-info-color, $toast-info-bg); +} + +.success { + @include notification-variant($toast-success-color, $toast-success-bg); +} + +.error { + @include notification-variant($toast-error-color, $toast-error-bg); +} + +.warning { + @include notification-variant($toast-warning-color, $toast-warning-bg); +} + +.container { + z-index: 9999; + position: fixed; + padding: $sm-padding-y; + width: 15vw; + min-width: 320px; + box-sizing: border-box; + top: $md-padding-y; + right: $md-padding-y; + max-height: 75vh; + overflow: hidden; + + @include mq($small-only) { + left: $sm-padding-y; + width: auto; + } +} + +.toast { + position: relative; + margin-bottom: $sm-padding-x; + padding: $md-padding-x; + border-radius: $border-radius; + box-shadow: 0 1px 10px 0 rgba(0, 0, 0, 0.1), 0 2px 15px 0 rgba(0, 0, 0, 0.05); + display: flex; + justify-content: space-between; + cursor: pointer; + color: $color-text; + background-color: $background; + animation-duration: 0.75s; + animation-fill-mode: both; + + &:hover, + &:focus { + background-color: $background-active; + } +} + +.body { + margin: auto 0; + flex: 1; +} + +.close { + background: transparent; + outline: none; + border: none; + cursor: pointer; + opacity: .3; + transition: .3s ease; + font-size: .35rem; + color: $color-gray-dark; + border: 1px solid; + border-radius: 50%; + padding: .4rem; + line-height: 0; + position: absolute; + top: $md-padding-y; + right: $md-padding-y; + + &:before { + margin-left: -.2rem; + } + + &:hover, + &:focus { + opacity: 1; + } + + @include mq($small-only) { + position: relative; + top: auto; + right: auto; + align-self: flex-start; + margin-top: auto; + margin-bottom: auto; + margin-left: $sm-padding-x; + font-size: 1rem; + line-height: 1; + border: none; + } +} + +@keyframes track-progress { + 0% { + width: 100%; + } + 100% { + width: 0; + } +} + +.progress { + position: absolute; + bottom: 0; + left: 0; + width: 0; + height: 5px; + z-index: 9999; + animation: track-progress linear 1; + background-color: $color-gray-lighter; +} diff --git a/bigbluebutton-html5/imports/ui/services/api/index.js b/bigbluebutton-html5/imports/ui/services/api/index.js old mode 100644 new mode 100755 index e515180c21..ecfed10bb2 --- a/bigbluebutton-html5/imports/ui/services/api/index.js +++ b/bigbluebutton-html5/imports/ui/services/api/index.js @@ -1,6 +1,6 @@ import Auth from '/imports/ui/services/auth'; import { check } from 'meteor/check'; -import NotificationService from '/imports/ui/services/notification/notificationService'; +import notify from '/imports/ui/components/toast/service'; /** * Send the request to the server via Meteor.call and don't treat errors. @@ -36,7 +36,7 @@ function makeCall(name, ...args) { */ function call(name, ...args) { return makeCall(name, ...args).catch((e) => { - NotificationService.add({ notification: `Error while executing ${name}` }); + notify(`Ops! Error while executing ${name}`, 'error'); throw e; }); } @@ -47,9 +47,9 @@ function call(name, ...args) { * @example * @code{ logClient({error:"Error caused by blabla"}) } */ -function logClient() { +function logClient(...arggs) { const credentials = Auth.credentials; - const args = Array.prototype.slice.call(arguments, 0); + const args = Array.prototype.slice.call(arggs, 0); const userInfo = window.navigator; args.push({ diff --git a/bigbluebutton-html5/imports/ui/services/notification/index.js b/bigbluebutton-html5/imports/ui/services/notification/index.js deleted file mode 100644 index e2fc226ce4..0000000000 --- a/bigbluebutton-html5/imports/ui/services/notification/index.js +++ /dev/null @@ -1,28 +0,0 @@ -import { check } from 'meteor/check'; - -const collection = new Mongo.Collection(null); - -function findById(notificationId) { - check(notificationId, String); - collection.find({ notificationId }); -} - -function add(notification) { - check(notification.notification, String); - collection.insert(notification); -} - -function remove(notificationId) { - check(notificationId, String); - collection.remove({ notificationId }); -} - -const NotificationCollection = { findById, add, remove }; - -export default NotificationCollection; - -export { - findById, - add, - remove, -}; diff --git a/bigbluebutton-html5/imports/ui/services/notification/notificationService.js b/bigbluebutton-html5/imports/ui/services/notification/notificationService.js deleted file mode 100644 index 85b8c2243e..0000000000 --- a/bigbluebutton-html5/imports/ui/services/notification/notificationService.js +++ /dev/null @@ -1,41 +0,0 @@ -import db from '/imports/ui/services/notification/index.js'; - -class NotificationService { - - /** - * Database to be transacted - * @param {Object} database - */ - constructor(database) { - this.database = database; - } - - /** - * @param {string} notificationID - */ - get(notificationID) { - this.database.findById(notificationID); - } - - /** - * @param {Object} notification - */ - add(notification) { - this.database.add(notification); - } - - /** - * @param {string} notificationID - */ - remove(notificationID) { - this.database.remove(notificationID); - } -} - -const NotificationServiceSingleton = new NotificationService(db); - -export { - NotificationService, -}; - -export default NotificationServiceSingleton; diff --git a/bigbluebutton-html5/imports/ui/stylesheets/variables/palette.scss b/bigbluebutton-html5/imports/ui/stylesheets/variables/palette.scss old mode 100644 new mode 100755 index cce3142913..7943da981f --- a/bigbluebutton-html5/imports/ui/stylesheets/variables/palette.scss +++ b/bigbluebutton-html5/imports/ui/stylesheets/variables/palette.scss @@ -8,6 +8,7 @@ $color-gray-lighter: lighten($color-gray-light, 25%) !default; $color-primary: #0F70D7 !default; $color-success: #008081 !default; $color-danger: #DF2721 !default; +$color-warning: purple !default; $color-background: $color-gray-dark !default; diff --git a/bigbluebutton-html5/package.json b/bigbluebutton-html5/package.json index 11230dc9e4..b4577f8564 100644 --- a/bigbluebutton-html5/package.json +++ b/bigbluebutton-html5/package.json @@ -39,6 +39,7 @@ "react-modal": "~1.7.7", "react-router": "~3.0.2", "react-tabs": "~1.0.0", + "react-toastify": "^2.1.0", "react-toggle": "~4.0.1", "react-transition-group": "~1.1.3", "redis": "^2.6.2", From c1ee189d57fbf1e50d8552bd8ca90cfaaac8c076 Mon Sep 17 00:00:00 2001 From: Oswaldo Acauan Date: Thu, 12 Oct 2017 16:03:48 -0300 Subject: [PATCH 2/2] Fix message propType --- bigbluebutton-html5/imports/ui/components/toast/component.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigbluebutton-html5/imports/ui/components/toast/component.jsx b/bigbluebutton-html5/imports/ui/components/toast/component.jsx index 63ad166aad..3929ec7c08 100755 --- a/bigbluebutton-html5/imports/ui/components/toast/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/toast/component.jsx @@ -7,7 +7,7 @@ import styles from './styles'; const propTypes = { icon: PropTypes.string, - message: PropTypes.string.isRequired, + message: PropTypes.node.isRequired, type: PropTypes.oneOf(Object.values(toast.TYPE)).isRequired, };