188 lines
5.7 KiB
JavaScript
188 lines
5.7 KiB
JavaScript
/*
|
|
* This file is part of NoccyLabs JavaScript Standard Library (JSL)
|
|
* Copyright (c) 2025, NoccyLabs. Licensed under GNU GPL v2 or later
|
|
*
|
|
*
|
|
*/
|
|
|
|
import { dom, el } from './dom.js';
|
|
|
|
/**
|
|
* A friendly user dialog class
|
|
*
|
|
*/
|
|
class UserDialog {
|
|
|
|
#dialog;
|
|
#resolver;
|
|
|
|
#style = "msgbox";
|
|
#title = "MessageBox";
|
|
#message;
|
|
#actions = { "ok":"Ok" };
|
|
|
|
/**
|
|
* Dialog constructor
|
|
*
|
|
* @param {string} style The dialog style
|
|
* @param {string} title The dialog title
|
|
* @param {string} message The dialog message
|
|
* @param {object} actions The dialog actions
|
|
*/
|
|
constructor(style, title, message, actions) {
|
|
if (style) this.#style = style;
|
|
if (title) this.#title = title;
|
|
if (message) this.#message = message;
|
|
if (actions && typeof actions == 'object') this.#actions = actions;
|
|
}
|
|
|
|
/**
|
|
* Build and show the dialog. Promise will resolve with the selected action key,
|
|
* or false if closed via the X.
|
|
*
|
|
* @returns Promise
|
|
*/
|
|
show() {
|
|
let promise = new Promise((resolve) => {
|
|
this.#resolver = resolve;
|
|
});
|
|
this.#buildDialog();
|
|
this.#showDialog();
|
|
return promise;
|
|
}
|
|
|
|
#buildDialog() {
|
|
if (this.#dialog)
|
|
throw new Error("#buildDialog should not be called with an active #dialog");
|
|
|
|
const dialog = el.dialog({ class: 'jsl-dialog' });
|
|
dialog.addEventListener('close', event => {
|
|
if (this.#resolver) {
|
|
this.#resolver(false);
|
|
this.#resolver = null;
|
|
}
|
|
this.#destroyDialog();
|
|
});
|
|
dialog.className += ' -style-' + this.#style;
|
|
|
|
let cbtn;
|
|
dom.append(dialog, el.div({ class: '-header' }, [
|
|
el.div({ class: '-title' }, this.#title),
|
|
// cbtn = el.button({ class: '-close-btn' }, '✕')
|
|
]));
|
|
dom.append(dialog, el.div({ class: '-content' }, this.#message));
|
|
// cbtn.addEventListener('click', () => { this.#handleButton(false); });
|
|
|
|
const buttons = el.div({ class: '-buttons' });
|
|
let tabindex = 1;
|
|
Object.keys(this.#actions).forEach(action => {
|
|
let btn;
|
|
dom.append(buttons, btn = el.button({ class: 'action', tabIndex: tabindex++ }, this.#actions[action]));
|
|
// if (action.match(/\*$/)) {
|
|
// btn.style.outline = 'solid 1px #222';
|
|
// btn.class += ' default-action';
|
|
// }
|
|
btn.addEventListener('click', () => { this.#handleButton(action); });
|
|
});
|
|
dom.append(dialog, buttons);
|
|
|
|
this.#dialog = dialog;
|
|
}
|
|
|
|
#destroyDialog() {
|
|
document.body.removeChild(this.#dialog);
|
|
this.#dialog = null;
|
|
}
|
|
|
|
#showDialog() {
|
|
if (!this.#dialog)
|
|
throw new Error("#showDialog should be called after #buildDialog with an active #dialog");
|
|
|
|
document.body.appendChild(this.#dialog);
|
|
this.#dialog.showModal();
|
|
setTimeout(() => {
|
|
let act = this.#dialog.querySelector('button.default-action');
|
|
if (act) { act.focus(); return; }
|
|
act = this.#dialog.querySelector('button.action');
|
|
if (!act) { console.warning("No buttons found? What to focus?"); return; }
|
|
act.focus();
|
|
}, 200);
|
|
}
|
|
|
|
#handleButton(id) {
|
|
this.#resolver(id);
|
|
this.#resolver = null;
|
|
this.#destroyDialog();
|
|
}
|
|
|
|
}
|
|
|
|
class UserDialogFactory {
|
|
|
|
/**
|
|
* Create a dialog, and return a promise that will resolve with the key of the
|
|
* selected button, or false if closed via the X.
|
|
*
|
|
* @param {string} type The dialog type, ex msgbox, information, error
|
|
* @param {string} title The dialog title
|
|
* @param {string} message The message to display
|
|
* @param {object} actions The buttons to present at the bottom of the dialog
|
|
* @returns Promise
|
|
*/
|
|
showDialog(type, title, message, actions) {
|
|
let dialog;
|
|
switch (type) {
|
|
case 'msgbox':
|
|
title = title ?? "MessageBox";
|
|
actions = actions ?? { 'ok': 'Ok' };
|
|
dialog = new UserDialog('msgbox', title, message, actions);
|
|
break;
|
|
case 'information':
|
|
case 'info':
|
|
title = title ?? "Information";
|
|
actions = actions ?? { 'ok': 'Ok' };
|
|
dialog = new UserDialog('info', title, message, actions);
|
|
break;
|
|
case 'error':
|
|
title = title ?? "Error";
|
|
actions = actions ?? { 'ok': 'Ok' };
|
|
dialog = new UserDialog('error', title, message, actions);
|
|
break;
|
|
case 'warning':
|
|
title = title ?? "Warning";
|
|
actions = actions ?? { 'ok': 'Ok' };
|
|
dialog = new UserDialog('warning', title, message, actions);
|
|
break;
|
|
case 'confirm':
|
|
title = title ?? "Confirmation";
|
|
actions = actions ?? { 'ok': 'Ok', 'cancel': 'Cancel' };
|
|
dialog = new UserDialog('confirm', title, message, actions);
|
|
break;
|
|
default:
|
|
title = title ?? "Dialog";
|
|
actions = actions ?? { 'ok': 'Ok' };
|
|
dialog = new UserDialog(type, title, message, actions);
|
|
}
|
|
|
|
return dialog.show();
|
|
}
|
|
}
|
|
|
|
// The dialog factory
|
|
const factory = new UserDialogFactory();
|
|
|
|
// A proxy to invoke various types via dialog.
|
|
const dialog = new Proxy(factory, {
|
|
get(target, name) {
|
|
return name in target
|
|
? target[name]
|
|
: (message, title = null, actions = null) => target.showDialog(name, title, message, actions);
|
|
}
|
|
});
|
|
|
|
export {
|
|
factory,
|
|
dialog,
|
|
UserDialog
|
|
};
|