123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304 |
- // @flow
-
- import { FlagGroupContext } from '@atlaskit/flag/flag-group';
- import { AtlasKitThemeProvider } from '@atlaskit/theme';
- import { withStyles } from '@material-ui/styles';
- import React, { Component } from 'react';
- import { CSSTransition, TransitionGroup } from 'react-transition-group';
-
- import { translate } from '../../../base/i18n';
- import { connect } from '../../../base/redux';
- import { hideNotification } from '../../actions';
- import { areThereNotifications } from '../../functions';
-
- import Notification from './Notification';
-
- declare var interfaceConfig: Object;
-
- type Props = {
-
- /**
- * Whether we are a SIP gateway or not.
- */
- _iAmSipGateway: boolean,
-
- /**
- * Whether or not the chat is open.
- */
- _isChatOpen: boolean,
-
- /**
- * The notifications to be displayed, with the first index being the
- * notification at the top and the rest shown below it in order.
- */
- _notifications: Array<Object>,
-
- /**
- * The length, in milliseconds, to use as a default timeout for all
- * dismissible timeouts that do not have a timeout specified.
- */
- autoDismissTimeout: number,
-
- /**
- * JSS classes object.
- */
- classes: Object,
-
- /**
- * Invoked to update the redux store in order to remove notifications.
- */
- dispatch: Function,
-
- /**
- * Whether or not the notifications are displayed in a portal.
- */
- portal?: boolean,
-
- /**
- * Invoked to obtain translated strings.
- */
- t: Function
- };
-
- const useStyles = theme => {
- return {
- container: {
- position: 'absolute',
- left: '16px',
- bottom: '90px',
- width: '400px',
- maxWidth: '100%',
- zIndex: 600
- },
-
- containerPortal: {
- maxWidth: 'calc(100% - 32px)'
- },
-
- containerChatOpen: {
- left: '331px'
- },
-
- transitionGroup: {
- '& > *': {
- marginBottom: '20px',
- borderRadius: '6px!important', // !important used to overwrite atlaskit style
- position: 'relative'
- },
-
- '& div > span > svg > path': {
- fill: 'inherit'
- },
-
- '& div > span, & div > p': {
- color: theme.palette.field01
- },
-
- '& .ribbon': {
- width: '4px',
- height: 'calc(100% - 16px)',
- position: 'absolute',
- left: 0,
- top: '8px',
- borderRadius: '4px',
-
- '&.normal': {
- backgroundColor: theme.palette.link01Active
- },
-
- '&.error': {
- backgroundColor: theme.palette.iconError
- },
-
- '&.warning': {
- backgroundColor: theme.palette.warning01
- }
- }
- }
- };
- };
-
- /**
- * Implements a React {@link Component} which displays notifications and handles
- * automatic dismissal after a notification is shown for a defined timeout
- * period.
- *
- * @extends {Component}
- */
- class NotificationsContainer extends Component<Props> {
- _api: Object;
- _timeouts: Map<string, TimeoutID>;
-
- /**
- * Initializes a new {@code NotificationsContainer} instance.
- *
- * @inheritdoc
- */
- constructor(props: Props) {
- super(props);
-
- this._timeouts = new Map();
-
- // Bind event handlers so they are only bound once for every instance.
- this._onDismissed = this._onDismissed.bind(this);
-
- // HACK ALERT! We are rendering AtlasKit Flag elements outside of a FlagGroup container.
- // In order to hook-up the dismiss action we'll a fake context provider,
- // just like FlagGroup does.
- this._api = {
- onDismissed: this._onDismissed,
- dismissAllowed: () => true
- };
- }
-
- /**
- * Sets a timeout for each notification, where applicable.
- *
- * @inheritdoc
- */
- componentDidMount() {
- this._updateTimeouts();
- }
-
- /**
- * Sets a timeout for each notification, where applicable.
- *
- * @inheritdoc
- */
- componentDidUpdate() {
- this._updateTimeouts();
- }
-
- /**
- * Implements React's {@link Component#render()}.
- *
- * @inheritdoc
- * @returns {ReactElement}
- */
- render() {
- if (this.props._iAmSipGateway) {
- return null;
- }
-
- return (
- <AtlasKitThemeProvider mode = 'light'>
- <FlagGroupContext.Provider value = { this._api }>
- <div
- className = { `${this.props.classes.container} ${this.props.portal
- ? this.props.classes.containerPortal
- : this.props._isChatOpen
- ? this.props.classes.containerChatOpen
- : ''}`
- }
- id = 'notifications-container'>
- <TransitionGroup className = { this.props.classes.transitionGroup }>
- {this._renderFlags()}
- </TransitionGroup>
- </div>
- </FlagGroupContext.Provider>
- </AtlasKitThemeProvider>
- );
- }
-
- _onDismissed: number => void;
-
- /**
- * Emits an action to remove the notification from the redux store so it
- * stops displaying.
- *
- * @param {number} uid - The id of the notification to be removed.
- * @private
- * @returns {void}
- */
- _onDismissed(uid) {
- const timeout = this._timeouts.get(uid);
-
- if (timeout) {
- clearTimeout(timeout);
- this._timeouts.delete(uid);
- }
-
- this.props.dispatch(hideNotification(uid));
- }
-
- /**
- * Renders notifications to display as ReactElements. An empty array will
- * be returned if notifications are disabled.
- *
- * @private
- * @returns {ReactElement[]}
- */
- _renderFlags() {
- const { _notifications } = this.props;
-
- return _notifications.map(notification => {
- const { props, uid } = notification;
-
- // The id attribute is necessary as {@code FlagGroup} looks for
- // either id or key to set a key on notifications, but accessing
- // props.key will cause React to print an error.
- return (
- <CSSTransition
- appear = { true }
- classNames = 'notification'
- in = { true }
- key = { uid }
- timeout = { 200 }>
- <Notification
- { ...props }
- id = { uid }
- onDismissed = { this._onDismissed }
- uid = { uid } />
- </CSSTransition>
- );
- });
- }
-
- /**
- * Updates the timeouts for every notification.
- *
- * @returns {void}
- */
- _updateTimeouts() {
- const { _notifications, autoDismissTimeout } = this.props;
-
- for (const notification of _notifications) {
- if ((notification.timeout || typeof autoDismissTimeout === 'number')
- && notification.props.isDismissAllowed !== false
- && !this._timeouts.has(notification.uid)) {
- const {
- timeout = autoDismissTimeout,
- uid
- } = notification;
- const timerID = setTimeout(() => {
- this._onDismissed(uid);
- }, timeout);
-
- this._timeouts.set(uid, timerID);
- }
- }
- }
- }
-
- /**
- * Maps (parts of) the Redux state to the associated props for this component.
- *
- * @param {Object} state - The Redux state.
- * @private
- * @returns {Props}
- */
- function _mapStateToProps(state) {
- const { notifications } = state['features/notifications'];
- const { iAmSipGateway } = state['features/base/config'];
- const { isOpen: isChatOpen } = state['features/chat'];
- const _visible = areThereNotifications(state);
-
- return {
- _iAmSipGateway: Boolean(iAmSipGateway),
- _isChatOpen: isChatOpen,
- _notifications: _visible ? notifications : [],
- autoDismissTimeout: interfaceConfig.ENFORCE_NOTIFICATION_AUTO_DISMISS_TIMEOUT
- };
- }
-
- export default translate(connect(_mapStateToProps)(withStyles(useStyles)(NotificationsContainer)));
|