Merge remote-tracking branch 'oswaldo/toast-notifications' into audio-refactor-notifications
This commit is contained in:
commit
2ae020ba9a
120
bigbluebutton-html5/client/stylesheets/toastify.css
Executable file
120
bigbluebutton-html5/client/stylesheets/toastify.css
Executable file
@ -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; }
|
2
bigbluebutton-html5/imports/ui/components/app/component.jsx
Normal file → Executable file
2
bigbluebutton-html5/imports/ui/components/app/component.jsx
Normal file → Executable file
@ -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 {
|
||||
</section>
|
||||
<ModalContainer />
|
||||
<AudioContainer />
|
||||
<ToastContainer />
|
||||
<ChatNotificationContainer currentChatID={params.chatID} />
|
||||
</main>
|
||||
);
|
||||
|
38
bigbluebutton-html5/imports/ui/components/toast/component.jsx
Executable file
38
bigbluebutton-html5/imports/ui/components/toast/component.jsx
Executable file
@ -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.node.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 }) => (
|
||||
<div className={styles[type]}>
|
||||
<div className={styles.icon}><Icon iconName={icon || defaultIcons[type]} /></div>
|
||||
<div className={styles.message}>
|
||||
<span>{message}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default Toast;
|
||||
|
||||
Toast.propTypes = propTypes;
|
||||
Toast.defaultProps = defaultProps;
|
21
bigbluebutton-html5/imports/ui/components/toast/container.jsx
Executable file
21
bigbluebutton-html5/imports/ui/components/toast/container.jsx
Executable file
@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
|
||||
import Icon from '../icon/component';
|
||||
import styles from './styles';
|
||||
|
||||
export default () => (
|
||||
<ToastContainer
|
||||
closeButton={<Icon className={styles.close} iconName="close" />}
|
||||
autoClose={5000}
|
||||
className={styles.container}
|
||||
toastClassName={styles.toast}
|
||||
bodyClassName={styles.body}
|
||||
progressClassName={styles.progress}
|
||||
newestOnTop={false}
|
||||
hideProgressBar={false}
|
||||
closeOnClick
|
||||
pauseOnHover
|
||||
/>
|
||||
);
|
21
bigbluebutton-html5/imports/ui/components/toast/service.jsx
Executable file
21
bigbluebutton-html5/imports/ui/components/toast/service.jsx
Executable file
@ -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(<Toast {...{ type, icon, message }} />, settings);
|
||||
}
|
||||
};
|
||||
|
||||
export default notify;
|
||||
|
||||
export const withToast = ComponentToWrap =>
|
||||
props => (<ComponentToWrap {...props} toastNotify={notify} />);
|
178
bigbluebutton-html5/imports/ui/components/toast/styles.scss
Executable file
178
bigbluebutton-html5/imports/ui/components/toast/styles.scss
Executable file
@ -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;
|
||||
}
|
8
bigbluebutton-html5/imports/ui/services/api/index.js
Normal file → Executable file
8
bigbluebutton-html5/imports/ui/services/api/index.js
Normal file → Executable file
@ -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({
|
||||
|
@ -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,
|
||||
};
|
@ -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;
|
1
bigbluebutton-html5/imports/ui/stylesheets/variables/palette.scss
Normal file → Executable file
1
bigbluebutton-html5/imports/ui/stylesheets/variables/palette.scss
Normal file → Executable file
@ -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;
|
||||
|
||||
|
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user