Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | 17x 17x 17x 17x 17x 17x 17x 17x 88x 88x 88x 88x 88x 17x 252x 25x 25x 25x 75x 17x 75x 17x 17x 142x 142x 17x 82x 82x 82x 9x 9x 8x 8x 3x 3x 3x 8x 8x 8x 8x 3x 3x 8x 5x 5x 8x 6x 6x 6x 6x 6x 6x 8x 2x 2x 8x 8x 8x 8x 8x 8x 8x 8x 8x 1x 1x | import * as bootstrap from "bootstrap";
import { noteFeature } from "./analytics.js";
/**
* The dialogs the page raises itself, and the rule that a dialog pauses the
* emulator: the first one up stops it, and the last one down starts it again
* if it was running before.
*/
export class Modals {
constructor({ loop }) {
this.errorDialog = document.getElementById("error-dialog");
this.errorModal = new bootstrap.Modal(this.errorDialog);
this.aysEl = document.getElementById("are-you-sure");
this.aysModal = new bootstrap.Modal(this.aysEl);
this.dropUnaskedQuestion = null;
// Bootstrap raises a modal's hidden event only once its backdrop has faded, and that late event clears
// body.modal-open (and, here, the modal's hold) whatever has opened since; so a show waits for it.
const closing = new Set();
const waiting = new Set();
const closed = (modal) => {
closing.delete(modal);
Iif (closing.size !== 0) return;
const shows = [...waiting];
waiting.clear();
for (const waited of shows) bootstrap.Modal.getOrCreateInstance(waited).show();
};
document.addEventListener(
"show.bs.modal",
(event) => {
if (closing.size === 0) return;
event.preventDefault();
event.stopImmediatePropagation();
waiting.add(event.target);
},
{ capture: true },
);
document.addEventListener("hide.bs.modal", (event) => closing.add(event.target), { capture: true });
document.addEventListener("hide.bs.modal", (event) => {
if (event.defaultPrevented) closed(event.target);
});
const holds = new WeakMap();
document.addEventListener("show.bs.modal", (event) => {
noteFeature(event.target, "open");
if (!holds.has(event.target)) holds.set(event.target, loop.hold(`the ${event.target.id} dialog`));
});
document.addEventListener("hidden.bs.modal", (event) => {
holds.get(event.target)?.();
holds.delete(event.target);
closed(event.target);
});
}
show(id) {
const el = document.getElementById(id);
if (el) bootstrap.Modal.getOrCreateInstance(el).show();
}
hide(id) {
const el = document.getElementById(id);
if (el) bootstrap.Modal.getInstance(el)?.hide();
}
showError(context, error) {
this.errorDialog.querySelector(".context").textContent = context;
this.errorDialog.querySelector(".error").textContent = error;
this.errorModal.show();
}
/** @returns {Promise<boolean>} true for the yes button; false for any other way out of the dialog */
confirm(message, yesText, noText) {
const yesButton = this.aysEl.querySelector(".ays-yes");
return new Promise((resolve) => {
let confirmed = false;
const onYes = () => {
confirmed = true;
this.aysModal.hide();
};
// The "no" button, Escape and a click outside raise no event of their own: they only hide the modal.
const onHidden = () => {
yesButton.removeEventListener("click", onYes);
resolve(confirmed);
};
// Asked from the show, not now, so a previous opening's late hidden cannot answer this question.
const ask = () => {
this.dropUnaskedQuestion = null;
this.aysEl.querySelector(".context").textContent = message;
this.aysEl.querySelector(".ays-no").textContent = noText;
yesButton.textContent = yesText;
yesButton.addEventListener("click", onYes, { once: true });
this.aysEl.addEventListener("hidden.bs.modal", onHidden, { once: true });
};
const unask = () => {
this.aysEl.removeEventListener("show.bs.modal", ask);
resolve(false);
};
this.aysEl.addEventListener("show.bs.modal", ask, { once: true });
// A question still held back while a modal closes gives way to this one.
this.dropUnaskedQuestion?.();
this.dropUnaskedQuestion = unask;
// Bootstrap raises no show at all while the dialog is up or in its own fade; that question goes unasked.
let raised = false;
const onRaised = (event) => (raised ||= event.target === this.aysEl);
window.addEventListener("show.bs.modal", onRaised, { capture: true });
this.aysModal.show();
window.removeEventListener("show.bs.modal", onRaised, { capture: true });
if (!raised) {
this.dropUnaskedQuestion = null;
unask();
}
});
}
}
|