discourse/app/assets/javascripts/dialog-holder/addon/services/dialog.js
David Taylor ed2dae6d1a
FIX: Ensure dialogs are still rendered if triggered during boot (#22511)
When the loading slider is enabled, the rendering of `application.hbs` is slightly delayed compared to the old 'spinner' strategy. This means that if a route tried to render a dialog during its `model()` hook, the dialog wrapper element would not be present and an error would occur.

This commit detects that situation and delays rendering the error until the next runloop iteration. If the element is still not found, we print a useful error to the console.

In the long term, we should ideally convert the dialog service to use a pure-ember rendering strategy instead of leaning on a11y-dialog. But for now, this workaround should resolve the problems identified by the chat system specs.
2023-07-10 11:29:04 +01:00

201 lines
3.9 KiB
JavaScript

import Service from "@ember/service";
import A11yDialog from "a11y-dialog";
import { bind } from "discourse-common/utils/decorators";
import { next } from "@ember/runloop";
export default Service.extend({
dialogInstance: null,
message: null,
title: null,
titleElementId: null,
type: null,
bodyComponent: null,
bodyComponentModel: null,
confirmButtonIcon: null,
confirmButtonLabel: null,
confirmButtonClass: null,
confirmButtonDisabled: false,
cancelButtonLabel: null,
cancelButtonClass: null,
shouldDisplayCancel: null,
didConfirm: null,
didCancel: null,
buttons: null,
class: null,
_confirming: false,
async dialog(params) {
const {
message,
bodyComponent,
bodyComponentModel,
type,
title,
confirmButtonClass = "btn-primary",
confirmButtonIcon,
confirmButtonLabel = "ok_value",
confirmButtonDisabled = false,
cancelButtonClass = "btn-default",
cancelButtonLabel = "cancel_value",
shouldDisplayCancel,
didConfirm,
didCancel,
buttons,
} = params;
let element = document.getElementById("dialog-holder");
if (!element) {
await new Promise((resolve) => next(resolve));
element = document.getElementById("dialog-holder");
}
if (!element) {
const msg =
"dialog-holder wrapper element not found. Unable to render dialog";
// eslint-disable-next-line no-console
console.error(msg, params);
throw new Error(msg);
}
this.setProperties({
message,
bodyComponent,
bodyComponentModel,
type,
dialogInstance: new A11yDialog(element),
title,
titleElementId: title !== null ? "dialog-title" : null,
confirmButtonClass,
confirmButtonDisabled,
confirmButtonIcon,
confirmButtonLabel,
cancelButtonClass,
cancelButtonLabel,
shouldDisplayCancel,
didConfirm,
didCancel,
buttons,
class: params.class,
});
this.dialogInstance.show();
this.dialogInstance.on("hide", () => {
if (!this._confirming && this.didCancel) {
this.didCancel();
}
this.reset();
});
},
alert(params) {
// support string param for easier porting of bootbox.alert
if (typeof params === "string") {
return this.dialog({
message: params,
type: "alert",
});
}
return this.dialog({
...params,
type: "alert",
});
},
confirm(params) {
return this.dialog({
...params,
shouldDisplayCancel: true,
buttons: null,
type: "confirm",
});
},
notice(message) {
return this.dialog({
message,
type: "notice",
});
},
yesNoConfirm(params) {
return this.confirm({
...params,
confirmButtonLabel: "yes_value",
cancelButtonLabel: "no_value",
});
},
deleteConfirm(params) {
return this.confirm({
...params,
confirmButtonClass: "btn-danger",
confirmButtonLabel: params.confirmButtonLabel || "delete",
});
},
reset() {
this.setProperties({
message: null,
bodyComponent: null,
bodyComponentModel: null,
type: null,
dialogInstance: null,
title: null,
titleElementId: null,
confirmButtonDisabled: false,
confirmButtonIcon: null,
confirmButtonLabel: null,
cancelButtonClass: null,
cancelButtonLabel: null,
shouldDisplayCancel: null,
didConfirm: null,
didCancel: null,
buttons: null,
class: null,
_confirming: false,
});
},
willDestroy() {
this.dialogInstance?.destroy();
this.reset();
},
@bind
didConfirmWrapped() {
if (this.didConfirm) {
this.didConfirm();
}
this._confirming = true;
this.dialogInstance.hide();
},
@bind
cancel() {
this.dialogInstance.hide();
},
@bind
enableConfirmButton() {
this.set("confirmButtonDisabled", false);
},
});