314 lines
7.1 KiB
TypeScript
314 lines
7.1 KiB
TypeScript
"use strict";
|
|
// License: MIT
|
|
|
|
interface ModalButton {
|
|
title: string;
|
|
value: string;
|
|
default?: boolean;
|
|
dismiss?: boolean;
|
|
}
|
|
|
|
export default class ModalDialog {
|
|
private _showing: any;
|
|
|
|
private _dismiss: HTMLButtonElement | null;
|
|
|
|
private _default: HTMLButtonElement | null;
|
|
|
|
protected el: HTMLDivElement;
|
|
|
|
constructor() {
|
|
this._showing = null;
|
|
this._dismiss = null;
|
|
this._default = null;
|
|
}
|
|
|
|
_makeEl() {
|
|
this._dismiss = null;
|
|
this._default = null;
|
|
|
|
const el = document.createElement("div");
|
|
el.classList.add("modal-container");
|
|
|
|
const cont = document.createElement("section");
|
|
cont.classList.add("modal-dialog");
|
|
|
|
const body = document.createElement("article");
|
|
body.classList.add("modal-body");
|
|
body.appendChild(this.content);
|
|
cont.appendChild(body);
|
|
|
|
const footer = document.createElement("footer");
|
|
footer.classList.add("modal-footer");
|
|
for (const b of this.buttons) {
|
|
const button = document.createElement("button");
|
|
button.classList.add("modal-button");
|
|
if (b.default) {
|
|
if (this._default) {
|
|
throw new Error("Default already declared");
|
|
}
|
|
this._default = button;
|
|
button.classList.add("modal-default");
|
|
}
|
|
if (b.dismiss) {
|
|
if (this._dismiss) {
|
|
throw new Error("dismiss already declared");
|
|
}
|
|
this._dismiss = button;
|
|
button.classList.add("modal-dismiss");
|
|
}
|
|
button.textContent = b.title;
|
|
button.value = b.value;
|
|
button.addEventListener("click", () => {
|
|
this.done(b);
|
|
});
|
|
footer.appendChild(button);
|
|
}
|
|
const nix = !navigator.platform.startsWith("Win");
|
|
if (this._default && nix) {
|
|
footer.appendChild(this._default);
|
|
}
|
|
if (this._dismiss && nix) {
|
|
footer.insertBefore(this._dismiss, footer.firstChild);
|
|
}
|
|
cont.appendChild(footer);
|
|
|
|
el.appendChild(cont);
|
|
|
|
el.addEventListener("click", e => {
|
|
if (e.target !== el) {
|
|
return;
|
|
}
|
|
e.stopPropagation();
|
|
e.stopImmediatePropagation();
|
|
e.preventDefault();
|
|
this.dismiss();
|
|
});
|
|
return el;
|
|
}
|
|
|
|
get content(): DocumentFragment | HTMLElement {
|
|
throw new Error("Not implemented");
|
|
}
|
|
|
|
get buttons(): ModalButton[] {
|
|
return [
|
|
{
|
|
title: "Accept",
|
|
value: "ok",
|
|
default: true,
|
|
dismiss: false
|
|
},
|
|
{
|
|
title: "Cancel",
|
|
value: "cancel",
|
|
default: false,
|
|
dismiss: true
|
|
}
|
|
];
|
|
}
|
|
|
|
done(button: any) {
|
|
const value = this.convertValue(button.value);
|
|
if (button.dismiss) {
|
|
this._showing.reject(new Error(value));
|
|
}
|
|
else {
|
|
this._showing.resolve(value);
|
|
}
|
|
}
|
|
|
|
shown() {
|
|
// ignored
|
|
}
|
|
|
|
focusDefault() {
|
|
this._default && this._default.focus();
|
|
}
|
|
|
|
convertValue(value: string): any {
|
|
return value;
|
|
}
|
|
|
|
|
|
async show() {
|
|
if (this._showing) {
|
|
throw new Error("Double show");
|
|
}
|
|
const escapeHandler = (e: KeyboardEvent) => {
|
|
if (e.key !== "Escape") {
|
|
return;
|
|
}
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
e.stopImmediatePropagation();
|
|
this.dismiss();
|
|
return;
|
|
};
|
|
const enterHandler = (e: KeyboardEvent) => {
|
|
if (e.key !== "Enter") {
|
|
return;
|
|
}
|
|
const {localName} = e.target as HTMLElement;
|
|
if (localName === "textarea" && !e.metaKey) {
|
|
return;
|
|
}
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
e.stopImmediatePropagation();
|
|
this.accept();
|
|
return;
|
|
};
|
|
|
|
document.body.appendChild(this.el = this._makeEl());
|
|
this.shown();
|
|
addEventListener("keydown", escapeHandler);
|
|
addEventListener("keydown", enterHandler);
|
|
try {
|
|
return await new Promise((resolve, reject) => {
|
|
this._showing = {resolve, reject};
|
|
});
|
|
}
|
|
finally {
|
|
removeEventListener("keydown", escapeHandler);
|
|
removeEventListener("keydown", enterHandler);
|
|
document.body.removeChild(this.el);
|
|
this._showing = null;
|
|
}
|
|
}
|
|
|
|
dismiss() {
|
|
if (!this._dismiss) {
|
|
throw new Error("No dismiss button");
|
|
}
|
|
if (!this._showing) {
|
|
throw new Error("Dialog not open");
|
|
}
|
|
this._dismiss.click();
|
|
}
|
|
|
|
accept() {
|
|
if (!this._default) {
|
|
throw new Error("No accept button");
|
|
}
|
|
if (!this._showing) {
|
|
throw new Error("Dialog not open");
|
|
}
|
|
this._default.click();
|
|
}
|
|
|
|
/**
|
|
* Show some informaton in a dialog
|
|
* @param {string} title dialog title
|
|
* @param {string} text info
|
|
* @param {string} oktext button text
|
|
*/
|
|
static async inform(title: string, text: string, oktext: string) {
|
|
const dialog = new class extends ModalDialog {
|
|
get content() {
|
|
const rv = document.createDocumentFragment();
|
|
const h = document.createElement("h1");
|
|
h.textContent = title || "Information";
|
|
rv.appendChild(h);
|
|
const t = document.createElement("p");
|
|
t.textContent = text || "";
|
|
rv.appendChild(t);
|
|
return rv;
|
|
}
|
|
|
|
get buttons() {
|
|
return [
|
|
{
|
|
title: oktext || "OK",
|
|
value: "ok",
|
|
default: true,
|
|
dismiss: false
|
|
},
|
|
];
|
|
}
|
|
|
|
shown() {
|
|
this.focusDefault();
|
|
}
|
|
}();
|
|
try {
|
|
await dialog.show();
|
|
}
|
|
catch (ex) {
|
|
// ignored
|
|
}
|
|
}
|
|
|
|
static async confirm(title: string, text: string) {
|
|
const dialog = new class extends ModalDialog {
|
|
get content() {
|
|
const rv = document.createDocumentFragment();
|
|
const h = document.createElement("h1");
|
|
h.textContent = title || "Confirm";
|
|
rv.appendChild(h);
|
|
const t = document.createElement("p");
|
|
t.textContent = text || "";
|
|
rv.appendChild(t);
|
|
return rv;
|
|
}
|
|
|
|
get buttons() {
|
|
return [
|
|
{
|
|
title: "Yes",
|
|
value: "ok",
|
|
default: true,
|
|
dismiss: false,
|
|
},
|
|
{
|
|
title: "No",
|
|
value: "cancel",
|
|
default: true,
|
|
dismiss: true
|
|
}
|
|
];
|
|
}
|
|
|
|
shown() {
|
|
this.focusDefault();
|
|
}
|
|
}();
|
|
return await dialog.show();
|
|
}
|
|
|
|
static async prompt(title: string, text: string, defaultValue: string) {
|
|
const dialog = new class extends ModalDialog {
|
|
_input: HTMLInputElement;
|
|
|
|
get content() {
|
|
const rv = document.createDocumentFragment();
|
|
const h = document.createElement("h1");
|
|
h.textContent = title || "Confirm";
|
|
rv.appendChild(h);
|
|
const t = document.createElement("p");
|
|
t.textContent = text || "";
|
|
rv.appendChild(t);
|
|
const i = document.createElement("input");
|
|
i.setAttribute("type", text);
|
|
i.value = defaultValue || "";
|
|
rv.appendChild(i);
|
|
i.style.minWidth = "80%";
|
|
this._input = i;
|
|
return rv;
|
|
}
|
|
|
|
shown() {
|
|
this._input.focus();
|
|
}
|
|
|
|
convertValue(v: string) {
|
|
if (v === "ok") {
|
|
v = this._input.value;
|
|
}
|
|
return v;
|
|
}
|
|
}();
|
|
return await dialog.show();
|
|
}
|
|
}
|