Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
Merge pull request #2724 from matrix-org/travis/stacked-dialogs
Browse files Browse the repository at this point in the history
Support stacking dialogs to prevent unmounting
  • Loading branch information
turt2live authored Mar 1, 2019
2 parents 5a4676a + 0978ab3 commit 3ce2c3a
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 17 deletions.
28 changes: 27 additions & 1 deletion res/css/_common.scss
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,17 @@ textarea {
color: $roomsublist-label-bg-color;
}

/* Expected z-indexes for dialogs:
4000 - Default wrapper index
4009 - Static dialog background
4010 - Static dialog itself
4011 - Standard dialog background
4012 - Standard dialog itself
These are set up such that the static dialog always appears
underneath the standard dialogs.
*/

.mx_Dialog_wrapper {
position: fixed;
z-index: 4000;
Expand All @@ -252,7 +263,7 @@ textarea {
.mx_Dialog {
background-color: $primary-bg-color;
color: $light-fg-color;
z-index: 4010;
z-index: 4012;
font-weight: 300;
font-size: 15px;
position: relative;
Expand All @@ -264,6 +275,10 @@ textarea {
overflow-y: auto;
}

.mx_Dialog_staticWrapper .mx_Dialog {
z-index: 4010;
}

.mx_Dialog_background {
position: fixed;
top: 0;
Expand All @@ -272,6 +287,17 @@ textarea {
height: 100%;
background-color: $dialog-backdrop-color;
opacity: 0.8;
z-index: 4011;
}

.mx_Dialog_background.mx_Dialog_staticBackground {
z-index: 4009;
}

.mx_Dialog_wrapperWithStaticUnder .mx_Dialog_background {
// Roughly half of what it would normally be - we don't want to black out
// the app, just make it clear that the dialogs are stacked.
opacity: 0.4;
}

.mx_Dialog_lightbox .mx_Dialog_background {
Expand Down
96 changes: 82 additions & 14 deletions src/Modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import dis from './dispatcher';
import { _t } from './languageHandler';

const DIALOG_CONTAINER_ID = "mx_Dialog_Container";
const STATIC_DIALOG_CONTAINER_ID = "mx_Dialog_StaticContainer";

/**
* Wrap an asynchronous loader function with a react component which shows a
Expand Down Expand Up @@ -106,7 +107,12 @@ class ModalManager {
// this modal. Remove all other modals from the stack when this modal
// is closed.
this._priorityModal = null;
// The modal to keep open underneath other modals if possible. Useful
// for cases like Settings where the modal should remain open while the
// user is prompted for more information/errors.
this._staticModal = null;
// A list of the modals we have stacked up, with the most recent at [0]
// Neither the static nor priority modal will be in this list.
this._modals = [
/* {
elem: React component for this dialog
Expand All @@ -130,6 +136,18 @@ class ModalManager {
return container;
}

getOrCreateStaticContainer() {
let container = document.getElementById(STATIC_DIALOG_CONTAINER_ID);

if (!container) {
container = document.createElement("div");
container.id = STATIC_DIALOG_CONTAINER_ID;
document.body.appendChild(container);
}

return container;
}

createTrackedDialog(analyticsAction, analyticsInfo, ...rest) {
Analytics.trackEvent('Modal', analyticsAction, analyticsInfo);
return this.createDialog(...rest);
Expand Down Expand Up @@ -166,8 +184,13 @@ class ModalManager {
* of other modals that are currently in the stack.
* Also, when closed, all modals will be removed
* from the stack.
* @param {boolean} isStaticModal if true, this modal will be displayed under other
* modals in the stack. When closed, all modals will
* also be removed from the stack. This is not compatible
* with being a priority modal. Only one modal can be
* static at a time.
*/
createDialogAsync(prom, props, className, isPriorityModal) {
createDialogAsync(prom, props, className, isPriorityModal, isStaticModal) {
const self = this;
const modal = {};

Expand All @@ -188,6 +211,13 @@ class ModalManager {
self._modals = [];
}

if (self._staticModal === modal) {
self._staticModal = null;

// XXX: This is destructive
self._modals = [];
}

self._reRender();
};

Expand All @@ -207,6 +237,9 @@ class ModalManager {
if (isPriorityModal) {
// XXX: This is destructive
this._priorityModal = modal;
} else if (isStaticModal) {
// This is intentionally destructive
this._staticModal = modal;
} else {
this._modals.unshift(modal);
}
Expand All @@ -216,12 +249,18 @@ class ModalManager {
}

closeAll() {
const modals = this._modals;
const modalsToClose = [...this._modals, this._priorityModal];
this._modals = [];
this._priorityModal = null;

if (this._staticModal && modalsToClose.length === 0) {
modalsToClose.push(this._staticModal);
this._staticModal = null;
}

for (let i = 0; i < modals.length; i++) {
const m = modals[i];
if (m.onFinished) {
for (let i = 0; i < modalsToClose.length; i++) {
const m = modalsToClose[i];
if (m && m.onFinished) {
m.onFinished(false);
}
}
Expand All @@ -230,13 +269,14 @@ class ModalManager {
}

_reRender() {
if (this._modals.length == 0 && !this._priorityModal) {
if (this._modals.length === 0 && !this._priorityModal && !this._staticModal) {
// If there is no modal to render, make all of Riot available
// to screen reader users again
dis.dispatch({
action: 'aria_unhide_main_app',
});
ReactDOM.unmountComponentAtNode(this.getOrCreateContainer());
ReactDOM.unmountComponentAtNode(this.getOrCreateStaticContainer());
return;
}

Expand All @@ -247,17 +287,45 @@ class ModalManager {
action: 'aria_hide_main_app',
});

if (this._staticModal) {
const classes = "mx_Dialog_wrapper mx_Dialog_staticWrapper "
+ (this._staticModal.className ? this._staticModal.className : '');

const staticDialog = (
<div className={classes}>
<div className="mx_Dialog">
{ this._staticModal.elem }
</div>
<div className="mx_Dialog_background mx_Dialog_staticBackground" onClick={this.closeAll}></div>
</div>
);

ReactDOM.render(staticDialog, this.getOrCreateStaticContainer());
} else {
// This is safe to call repeatedly if we happen to do that
ReactDOM.unmountComponentAtNode(this.getOrCreateStaticContainer());
}

const modal = this._priorityModal ? this._priorityModal : this._modals[0];
const dialog = (
<div className={"mx_Dialog_wrapper " + (modal.className ? modal.className : '')}>
<div className="mx_Dialog">
{ modal.elem }
if (modal) {
const classes = "mx_Dialog_wrapper "
+ (this._staticModal ? "mx_Dialog_wrapperWithStaticUnder " : '')
+ (modal.className ? modal.className : '');

const dialog = (
<div className={classes}>
<div className="mx_Dialog">
{modal.elem}
</div>
<div className="mx_Dialog_background" onClick={this.closeAll}></div>
</div>
<div className="mx_Dialog_background" onClick={this.closeAll}></div>
</div>
);
);

ReactDOM.render(dialog, this.getOrCreateContainer());
ReactDOM.render(dialog, this.getOrCreateContainer());
} else {
// This is safe to call repeatedly if we happen to do that
ReactDOM.unmountComponentAtNode(this.getOrCreateContainer());
}
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/components/structures/MatrixChat.js
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,8 @@ export default React.createClass({
break;
case 'view_user_settings': {
const UserSettingsDialog = sdk.getComponent("dialogs.UserSettingsDialog");
Modal.createTrackedDialog('User settings', '', UserSettingsDialog, {}, 'mx_SettingsDialog');
Modal.createTrackedDialog('User settings', '', UserSettingsDialog, {}, 'mx_SettingsDialog',
/*isPriority=*/false, /*isStatic=*/true);

// View the welcome or home page if we need something to look at
this._viewSomethingBehindModal();
Expand Down
2 changes: 1 addition & 1 deletion src/stores/RoomViewStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ class RoomViewStore extends Store {
const RoomSettingsDialog = sdk.getComponent("dialogs.RoomSettingsDialog");
Modal.createTrackedDialog('Room settings', '', RoomSettingsDialog, {
roomId: payload.room_id || this._state.roomId,
}, 'mx_SettingsDialog');
}, 'mx_SettingsDialog', /*isPriority=*/false, /*isStatic=*/true);
break;
}
}
Expand Down

0 comments on commit 3ce2c3a

Please sign in to comment.