Initial commit
This commit is contained in:
187
dialog.js
Normal file
187
dialog.js
Normal file
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* 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
|
||||
};
|
||||
Reference in New Issue
Block a user