/** * FrontLayerViewManager.tsx * * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT license. * * Manages the layering of the main view, modals and popups. */ import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { Types } from '../common/Interfaces'; import Timers from '../common/utils/Timers'; import MouseResponder from './utils/MouseResponder'; import { PopupDescriptor, RootView } from './RootView'; const MAX_CACHED_POPUPS = 4; export class FrontLayerViewManager { private _mainView: React.ReactElement | undefined; private _modalStack: { modal: React.ReactElement; id: string }[] = []; private _activePopupOptions: Types.PopupOptions | undefined; private _activePopupId: string | undefined; private _activePopupAutoDismiss = false; private _activePopupAutoDismissDelay = 0; private _activePopupShowDelay = 0; private _popupShowDelayTimer: number | undefined; private _cachedPopups: PopupDescriptor[] = []; // We need to be careful accessing document because it may not be defined // in some environments like Electron. private _isRtlDefault = typeof document !== 'undefined' && typeof document.documentElement !== 'undefined' && document.documentElement.dir === 'rtl'; private _isRtlAllowed = true; private _isRtlForced = false; setMainView(element: React.ReactElement): void { this._mainView = element; this._renderRootView(); } isModalDisplayed(modalId?: string): boolean { if (modalId) { return this._modalStack.some(d => d.id === modalId); } else { return this._modalStack.length > 0; } } showModal(modal: React.ReactElement, modalId: string, options?: Types.ModalOptions) { // Dismiss any active popups. if (this._activePopupOptions) { this.dismissPopup(this._activePopupId!); } this._modalStack.push({ modal: modal, id: modalId }); this._renderRootView(); } dismissModal(modalId: string) { this._modalStack = this._modalStack.filter(d => d.id !== modalId); this._renderRootView(); } dismissAllModals() { if (this._modalStack.length > 0) { this._modalStack = []; this._renderRootView(); } } private _shouldPopupBeDismissed = (options: Types.PopupOptions): boolean => { return !!this._activePopupOptions && this._activePopupOptions.getAnchor() === options.getAnchor(); } private _updateModalDisplayedState() { MouseResponder.setModalIsDisplayed(this.isModalDisplayed()); } showPopup(options: Types.PopupOptions, popupId: string, showDelay?: number): boolean { // If options.dismissIfShown is true, calling this method will behave like a toggle. // On one call, it will open the popup. If it is called when pop up is seen, it will // dismiss the popup. If options.dismissIfShown is false, we will simply show the // popup always. if (options.dismissIfShown) { if (this._shouldPopupBeDismissed(options)) { this.dismissPopup(popupId); return false; } } this._showPopup(options, popupId, showDelay); return true; } private _showPopup(options: Types.PopupOptions, popupId: string, showDelay?: number) : void { // New popup is transitioning from maybe cached to active. this._cachedPopups = this._cachedPopups.filter(popup => popup.popupId !== popupId); if (this._activePopupOptions && this._activePopupOptions.cacheable && this._activePopupId !== popupId) { // Old popup is transitioning from active to cached. this._cachedPopups.push({ popupOptions: this._activePopupOptions, popupId: this._activePopupId! }); this._cachedPopups = this._cachedPopups.slice(-MAX_CACHED_POPUPS); } // Update fields before calling onDismiss to guard against reentry. const oldPopupOptions = this._activePopupOptions; this._activePopupOptions = options; this._activePopupId = popupId; this._activePopupAutoDismiss = false; this._activePopupAutoDismissDelay = 0; this._activePopupShowDelay = showDelay || 0; if (this._popupShowDelayTimer) { Timers.clearTimeout(this._popupShowDelayTimer); this._popupShowDelayTimer = undefined; } if (this._activePopupShowDelay > 0) { this._popupShowDelayTimer = Timers.setTimeout(() => { this._activePopupShowDelay = 0; this._popupShowDelayTimer = undefined; this._renderRootView(); }, this._activePopupShowDelay); } if (oldPopupOptions) { if (oldPopupOptions.onDismiss) { oldPopupOptions.onDismiss(); } } this._renderRootView(); } autoDismissPopup(popupId: string, dismissDelay?: number): void { if (popupId === this._activePopupId && this._activePopupOptions) { if (this._popupShowDelayTimer) { Timers.clearTimeout(this._popupShowDelayTimer); this._popupShowDelayTimer = undefined; } this._activePopupAutoDismiss = true; this._activePopupAutoDismissDelay = dismissDelay || 0; this._renderRootView(); } } dismissPopup(popupId: string): void { if (popupId === this._activePopupId && this._activePopupOptions) { if (this._popupShowDelayTimer) { Timers.clearTimeout(this._popupShowDelayTimer); this._popupShowDelayTimer = undefined; } if (this._activePopupOptions.cacheable) { // The popup is transitioning from active to cached. this._cachedPopups.push({ popupOptions: this._activePopupOptions, popupId: this._activePopupId }); this._cachedPopups = this._cachedPopups.slice(-MAX_CACHED_POPUPS); } // Reset fields before calling onDismiss to guard against reentry. const activePopupOptions = this._activePopupOptions; this._activePopupOptions = undefined; this._activePopupId = undefined; if (activePopupOptions.onDismiss) { activePopupOptions.onDismiss(); } this._renderRootView(); } } dismissAllPopups() { if (this._activePopupId) { this.dismissPopup(this._activePopupId); } } private _renderRootView() { const topModal = this._modalStack.length > 0 ? this._modalStack[this._modalStack.length - 1].modal : undefined; const activePopup = (!this._activePopupOptions || this._activePopupShowDelay > 0) ? undefined : { popupOptions: this._activePopupOptions, popupId: this._activePopupId! }; this._updateModalDisplayedState(); const rootView = ( this.dismissPopup(this._activePopupId!) } writingDirection={ this._isRtlForced ? 'rtl' : (this._isRtlAllowed ? 'auto' : 'ltr') } /> ); const container = document.getElementsByClassName('app-container')[0]; ReactDOM.render(rootView, container); } isPopupDisplayed(popupId?: string): boolean { if (popupId) { return popupId === this._activePopupId; } else { return !!this._activePopupId; } } allowRTL(allow: boolean) { if (this._isRtlAllowed !== allow) { this._isRtlAllowed = allow; this._renderRootView(); } } forceRTL(force: boolean) { if (this._isRtlForced !== force) { this._isRtlForced = force; this._renderRootView(); } } isRTL(): boolean { return this._isRtlForced || (this._isRtlDefault && this._isRtlAllowed); } } export default new FrontLayerViewManager();