/** MIT (c) 2021 DnD5e Helpers */
|
|
|
|
/** @typedef {import('./api.js').NoticeConfig} NoticeConfig */
|
|
|
|
import {
|
|
logger
|
|
} from './logger.js';
|
|
|
|
const NAME = "warpgate";
|
|
const PATH = `/modules/${NAME}`;
|
|
|
|
export class MODULE {
|
|
static data = {
|
|
name: NAME,
|
|
path: PATH,
|
|
title: "Warp Gate"
|
|
};
|
|
|
|
static get isV10() {
|
|
// @ts-ignore
|
|
return game.release?.generation >= 10;
|
|
}
|
|
|
|
static async register() {
|
|
logger.info("Initializing Module");
|
|
MODULE.settings();
|
|
}
|
|
|
|
static async build() {
|
|
|
|
logger.info("Module Data Built");
|
|
}
|
|
|
|
static setting(key) {
|
|
return game.settings.get(MODULE.data.name, key);
|
|
}
|
|
|
|
/**
|
|
* Returns the localized string for a given warpgate scoped i18n key
|
|
*
|
|
* @ignore
|
|
* @static
|
|
* @param {*} key
|
|
* @returns {string}
|
|
* @memberof MODULE
|
|
*/
|
|
static localize(key) {
|
|
return game.i18n.localize(`warpgate.${key}`);
|
|
}
|
|
|
|
static format(key, data) {
|
|
return game.i18n.format(`warpgate.${key}`, data);
|
|
}
|
|
|
|
static canSpawn(user) {
|
|
const reqs = [
|
|
'TOKEN_CREATE',
|
|
'TOKEN_CONFIGURE',
|
|
'FILES_BROWSE',
|
|
]
|
|
|
|
return MODULE.canUser(user, reqs);
|
|
}
|
|
|
|
static canMutate(user) {
|
|
const reqs = [
|
|
'TOKEN_CONFIGURE',
|
|
'FILES_BROWSE',
|
|
]
|
|
|
|
return MODULE.canUser(user, reqs);
|
|
}
|
|
|
|
/**
|
|
* Handles notice request from spawns and mutations
|
|
*
|
|
* @static
|
|
* @param {{x: Number, y: Number}} location
|
|
* @param {string} sceneId
|
|
* @param {NoticeConfig} config
|
|
* @memberof MODULE
|
|
*/
|
|
static async handleNotice({x, y}, sceneId, config) {
|
|
|
|
/* can only operate if the user is on the scene requesting notice */
|
|
if( canvas.ready &&
|
|
!!sceneId && !!config &&
|
|
config.receivers.includes(game.userId) &&
|
|
canvas.scene?.id === sceneId ) {
|
|
|
|
const panSettings = {};
|
|
const hasLoc = x !== undefined && y !== undefined;
|
|
const doPan = !!config.pan;
|
|
const doZoom = !!config.zoom;
|
|
const doPing = !!config.ping;
|
|
|
|
if(hasLoc) {
|
|
panSettings.x = x;
|
|
panSettings.y = y;
|
|
}
|
|
|
|
if(doPan) {
|
|
panSettings.duration = Number.isNumeric(config.pan) && config.pan !== true ? Number(config.pan) : CONFIG.Canvas.pings.pullSpeed;
|
|
}
|
|
|
|
if (doZoom) {
|
|
panSettings.scale = Math.min(CONFIG.Canvas.maxZoom, config.zoom);
|
|
}
|
|
|
|
if (doPan) {
|
|
await canvas.animatePan(panSettings);
|
|
}
|
|
|
|
if (doPing && hasLoc) {
|
|
const user = game.users.get(config.sender);
|
|
const location = {x: panSettings.x, y: panSettings.y};
|
|
|
|
/* draw the ping, either onscreen or offscreen */
|
|
canvas.isOffscreen(location) ?
|
|
canvas.controls.drawOffscreenPing(location, {scene: sceneId, style: CONFIG.Canvas.pings.types.ARROW, user}) :
|
|
canvas.controls.drawPing(location, {scene: sceneId, style: config.ping, user});
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return {Array<String>} missing permissions for this operation
|
|
*/
|
|
static canUser(user, requiredPermissions) {
|
|
if(MODULE.setting('disablePermCheck')) return [];
|
|
const {role} = user;
|
|
const permissions = game.settings.get('core','permissions');
|
|
return requiredPermissions.filter( req => !permissions[req].includes(role) ).map(missing => game.i18n.localize(CONST.USER_PERMISSIONS[missing].label));
|
|
}
|
|
|
|
/**
|
|
* A helper functions that returns the first active GM level user.
|
|
* @returns {User|undefined} First active GM User
|
|
*/
|
|
static firstGM() {
|
|
return game.users?.find(u => u.isGM && u.active);
|
|
}
|
|
|
|
/**
|
|
* Checks whether the user calling this function is the user returned
|
|
* by {@link warpgate.util.firstGM}. Returns true if they are, false if they are not.
|
|
* @returns {boolean} Is the current user the first active GM user?
|
|
*/
|
|
static isFirstGM() {
|
|
return game.user?.id === MODULE.firstGM()?.id;
|
|
}
|
|
|
|
static emptyObject(obj){
|
|
// @ts-ignore
|
|
return foundry.utils.isEmpty(obj);
|
|
}
|
|
|
|
static removeEmptyObjects(obj) {
|
|
let result = foundry.utils.flattenObject(obj);
|
|
Object.keys(result).forEach( key => {
|
|
if(typeof result[key] == 'object' && MODULE.emptyObject(result[key])) {
|
|
delete result[key];
|
|
}
|
|
});
|
|
|
|
return foundry.utils.expandObject(result);
|
|
}
|
|
|
|
/**
|
|
* Duplicates a compatible object (non-complex).
|
|
*
|
|
* @returns {Object}
|
|
*/
|
|
static copy(source, errorString = 'error.unknown') {
|
|
try {
|
|
return foundry.utils.deepClone(source, {strict:true});
|
|
} catch (err) {
|
|
logger.catchThrow(err, MODULE.localize(errorString));
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Removes top level empty objects from the provided object.
|
|
*
|
|
* @static
|
|
* @param {object} obj
|
|
* @memberof MODULE
|
|
*/
|
|
static stripEmpty(obj, inplace = true) {
|
|
const result = inplace ? obj : MODULE.copy(obj);
|
|
|
|
Object.keys(result).forEach( key => {
|
|
if(typeof result[key] == 'object' && MODULE.emptyObject(result[key])) {
|
|
delete result[key];
|
|
}
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
static ownerSublist(docList) {
|
|
|
|
/* break token list into sublists by first owner */
|
|
const subLists = docList.reduce( (lists, doc) => {
|
|
if(!doc) return lists;
|
|
const owner = MODULE.firstOwner(doc)?.id ?? 'none';
|
|
lists[owner] ??= [];
|
|
lists[owner].push(doc);
|
|
return lists;
|
|
},{});
|
|
|
|
return subLists;
|
|
}
|
|
|
|
/**
|
|
* Returns the first active user with owner permissions for the given document,
|
|
* falling back to the firstGM should there not be any. Returns false if the
|
|
* document is falsey. In the case of token documents it checks the permissions
|
|
* for the token's actor as tokens themselves do not have a permission object.
|
|
*
|
|
* @param {{ actor: Actor } | { document: { actor: Actor } } | Actor} doc
|
|
*
|
|
* @returns {User|undefined}
|
|
*/
|
|
static firstOwner(doc) {
|
|
/* null docs could mean an empty lookup, null docs are not owned by anyone */
|
|
if (!doc) return undefined;
|
|
|
|
/* while conceptually correct, tokens derive permissions from their
|
|
* (synthetic) actor data.
|
|
*/
|
|
const corrected = doc instanceof TokenDocument ? doc.actor :
|
|
// @ts-ignore 2589
|
|
doc instanceof Token ? doc.document.actor : doc;
|
|
|
|
const ownershipPath = MODULE.isV10 ? 'ownership' : 'data.permission';
|
|
|
|
const permissionObject = getProperty(corrected ?? {}, ownershipPath) ?? {};
|
|
|
|
const playerOwners = Object.entries(permissionObject)
|
|
.filter(([id, level]) => (!game.users.get(id)?.isGM && game.users.get(id)?.active) && level === 3)
|
|
.map(([id, ]) => id);
|
|
|
|
if (playerOwners.length > 0) {
|
|
return game.users.get(playerOwners[0]);
|
|
}
|
|
|
|
/* if no online player owns this actor, fall back to first GM */
|
|
return MODULE.firstGM();
|
|
}
|
|
|
|
/**
|
|
* Checks whether the user calling this function is the user returned by
|
|
* {@link warpgate.util.firstOwner} when the function is passed the
|
|
* given document. Returns true if they are the same, false if they are not.
|
|
*
|
|
* As `firstOwner`, biases towards players first.
|
|
*
|
|
* @returns {boolean} the current user is the first player owner. If no owning player, first GM.
|
|
*/
|
|
static isFirstOwner(doc) {
|
|
return game.user.id === MODULE.firstOwner(doc).id;
|
|
}
|
|
|
|
/**
|
|
* Helper function. Waits for a specified amount of time in milliseconds (be sure to await!).
|
|
* Useful for timings with animations in the pre/post callbacks.
|
|
*
|
|
* @param {Number} ms Time to delay, in milliseconds
|
|
* @returns Promise
|
|
*/
|
|
static async wait(ms) {
|
|
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
}
|
|
|
|
static async waitFor(fn, maxIter = 600, iterWaitTime = 100, i = 0) {
|
|
const continueWait = (current, max) => {
|
|
|
|
/* negative max iter means wait forever */
|
|
if (maxIter < 0) return true;
|
|
|
|
return current < max;
|
|
}
|
|
|
|
while (!fn(i, ((i * iterWaitTime) / 100)) && continueWait(i, maxIter)) {
|
|
i++;
|
|
await MODULE.wait(iterWaitTime);
|
|
}
|
|
return i === maxIter ? false : true;
|
|
}
|
|
|
|
static settings() {
|
|
const data = {
|
|
disablePermCheck: {
|
|
config: true, scope: 'world', type: Boolean, default: false,
|
|
}
|
|
}
|
|
|
|
MODULE.applySettings(data);
|
|
}
|
|
|
|
static applySettings(settingsData) {
|
|
Object.entries(settingsData).forEach(([key, data]) => {
|
|
game.settings.register(
|
|
MODULE.data.name, key, {
|
|
name: MODULE.localize(`setting.${key}.name`),
|
|
hint: MODULE.localize(`setting.${key}.hint`),
|
|
...data
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param {string|Actor} actorNameDoc
|
|
* @param {object} tokenUpdates
|
|
*
|
|
* @returns {Promise<TokenDocument|false>}
|
|
*/
|
|
static async getTokenData(actorNameDoc, tokenUpdates) {
|
|
|
|
let sourceActor = actorNameDoc;
|
|
if(typeof actorNameDoc == 'string') {
|
|
/* lookup by actor name */
|
|
sourceActor = game.actors.getName(actorNameDoc);
|
|
}
|
|
|
|
//get source actor
|
|
if (!sourceActor) {
|
|
logger.error(`Could not find world actor named "${actorNameDoc}" or no souce actor document provided.`);
|
|
return false;
|
|
}
|
|
|
|
//get prototoken data -- need to prepare potential wild cards for the template preview
|
|
let protoData = await sourceActor.getTokenDocument(tokenUpdates);
|
|
if (!protoData) {
|
|
logger.error(`Could not find proto token data for ${sourceActor.name}`);
|
|
return false;
|
|
}
|
|
|
|
await loadTexture(protoData.texture.src);
|
|
|
|
return protoData;
|
|
}
|
|
|
|
static async updateProtoToken(protoToken, changes) {
|
|
protoToken.updateSource(changes);
|
|
const img = getProperty(changes, 'texture.src');
|
|
if (img) await loadTexture(img);
|
|
}
|
|
|
|
static getMouseStagePos() {
|
|
const mouse = canvas.app.renderer.plugins.interaction.mouse;
|
|
return mouse.getLocalPosition(canvas.app.stage);
|
|
}
|
|
|
|
/**
|
|
* @returns {undefined} provided updates object modified in-place
|
|
*/
|
|
static shimUpdate(updates) {
|
|
|
|
updates.token = MODULE.shimClassData(TokenDocument.implementation, updates.token);
|
|
updates.actor = MODULE.shimClassData(Actor.implementation, updates.actor);
|
|
|
|
Object.keys(updates.embedded ?? {}).forEach( (embeddedName) => {
|
|
const cls = CONFIG[embeddedName].documentClass;
|
|
|
|
Object.entries(updates.embedded[embeddedName]).forEach( ([shortId, data]) => {
|
|
updates.embedded[embeddedName][shortId] = (typeof data == 'string') ? data : MODULE.shimClassData(cls, data);
|
|
});
|
|
});
|
|
|
|
}
|
|
|
|
static shimClassData(cls, change) {
|
|
|
|
if(!change) return change;
|
|
|
|
if(!!change && !foundry.utils.isEmpty(change)) {
|
|
/* shim data if needed */
|
|
return cls.migrateData(foundry.utils.expandObject(change));
|
|
}
|
|
|
|
return foundry.utils.expandObject(change);
|
|
}
|
|
|
|
static getFeedbackSettings({alwaysAccept = false, suppressToast = false} = {}) {
|
|
const acceptSetting = MODULE.setting('alwaysAcceptLocal') == 0 ?
|
|
MODULE.setting('alwaysAccept') :
|
|
{1: true, 2: false}[MODULE.setting('alwaysAcceptLocal')];
|
|
|
|
const accepted = !!alwaysAccept ? true : acceptSetting;
|
|
|
|
const suppressSetting = MODULE.setting('suppressToastLocal') == 0 ?
|
|
MODULE.setting('suppressToast') :
|
|
{1: true, 2: false}[MODULE.setting('suppressToastLocal')];
|
|
|
|
const suppress = !!suppressToast ? true : suppressSetting;
|
|
|
|
return {alwaysAccept: accepted, suppressToast: suppress};
|
|
|
|
}
|
|
|
|
/**
|
|
* Collects the changes in 'other' compared to 'base'.
|
|
* Also includes "delete update" keys for elements in 'base' that do NOT
|
|
* exist in 'other'.
|
|
*/
|
|
static strictUpdateDiff(base, other) {
|
|
/* get the changed fields */
|
|
const diff = foundry.utils.flattenObject(foundry.utils.diffObject(base, other, {inner: true}));
|
|
|
|
/* get any newly added fields */
|
|
const additions = MODULE.unique(flattenObject(base), flattenObject(other))
|
|
|
|
/* set their data to null */
|
|
Object.keys(additions).forEach( key => {
|
|
if( typeof additions[key] != 'object' ) diff[key] = null
|
|
});
|
|
|
|
return foundry.utils.expandObject(diff);
|
|
}
|
|
|
|
static unique(object, remove) {
|
|
// Validate input
|
|
const ts = getType(object);
|
|
const tt = getType(remove);
|
|
if ((ts !== "Object") || (tt !== "Object")) throw new Error("One of source or template are not Objects!");
|
|
|
|
// Define recursive filtering function
|
|
const _filter = function (s, t, filtered) {
|
|
for (let [k, v] of Object.entries(s)) {
|
|
let has = t.hasOwnProperty(k);
|
|
let x = t[k];
|
|
|
|
// Case 1 - inner object
|
|
if (has && (getType(v) === "Object") && (getType(x) === "Object")) {
|
|
filtered[k] = _filter(v, x, {});
|
|
}
|
|
|
|
// Case 2 - inner key
|
|
else if (!has) {
|
|
filtered[k] = v;
|
|
}
|
|
}
|
|
return filtered;
|
|
};
|
|
|
|
// Begin filtering at the outer-most layer
|
|
return _filter(object, remove, {});
|
|
}
|
|
|
|
/**
|
|
* Helper function for quickly creating a simple dialog with labeled buttons and associated data.
|
|
* Useful for allowing a choice of actors to spawn prior to `warpgate.spawn`.
|
|
*
|
|
* @param {Object} data
|
|
* @param {Array<{label: string, value:*}>} data.buttons
|
|
* @param {string} [data.title]
|
|
* @param {string} [data.content]
|
|
* @param {Object} [data.options]
|
|
*
|
|
* @param {string} [direction = 'row'] 'column' or 'row' accepted. Controls layout direction of dialog.
|
|
*/
|
|
static async buttonDialog(data, direction = 'row') {
|
|
return await new Promise(async (resolve) => {
|
|
/** @type Object<string, object> */
|
|
let buttons = {},
|
|
dialog;
|
|
|
|
data.buttons.forEach((button) => {
|
|
buttons[button.label] = {
|
|
label: button.label,
|
|
callback: () => resolve(button.value)
|
|
}
|
|
});
|
|
|
|
dialog = new Dialog({
|
|
title: data.title ?? '',
|
|
content: data.content ?? '',
|
|
buttons,
|
|
close: () => resolve(false)
|
|
}, {
|
|
/*width: '100%',*/
|
|
height: '100%',
|
|
...data.options
|
|
});
|
|
|
|
await dialog._render(true);
|
|
dialog.element.find('.dialog-buttons').css({
|
|
'flex-direction': direction
|
|
});
|
|
});
|
|
}
|
|
|
|
static dialogInputs = (data) => {
|
|
|
|
/* correct legacy input data */
|
|
data.forEach(inputData => {
|
|
if (inputData.type === 'select') {
|
|
inputData.options.forEach((e, i) => {
|
|
switch (typeof e) {
|
|
case 'string':
|
|
/* if we are handed legacy string values, convert them to objects */
|
|
inputData.options[i] = {value: e, html: e};
|
|
/* fallthrough to tweak missing values from object */
|
|
|
|
case 'object':
|
|
/* if no HMTL provided, use value */
|
|
inputData.options[i].html ??= inputData.options[i].value;
|
|
|
|
/* sanity check */
|
|
if(!!inputData.options[i].html && inputData.options[i].value != undefined) {
|
|
break;
|
|
}
|
|
|
|
/* fallthrough to throw error if all else fails */
|
|
|
|
default: {
|
|
const emsg = MODULE.format('error.badSelectOpts', {fnName: 'menu'});
|
|
logger.error(emsg);
|
|
throw new Error(emsg);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
const mapped = data.map(({type, label, value, options}, i) => {
|
|
type = type.toLowerCase();
|
|
switch (type) {
|
|
case 'header': return `<tr><td colspan = "2"><h2>${label}</h2></td></tr>`;
|
|
case 'button': return '';
|
|
case 'info': return `<tr><td colspan="2">${label}</td></tr>`;
|
|
case 'select': {
|
|
|
|
const optionString = options.map((e, i) => {
|
|
return `<option value="${i}">${e.html}</option>`
|
|
}).join('');
|
|
|
|
return `<tr><th style="width:50%"><label>${label}</label></th><td style="width:50%"><select id="${i}qd">${optionString}</select></td></tr>`;
|
|
}
|
|
case 'radio': return `<tr><th style="width:50%"><label>${label}</label></th><td style="width:50%"><input type="${type}" id="${i}qd" ${(options instanceof Array ? options[1] : false ?? false) ? 'checked' : ''} value="${value ?? label}" name="${options instanceof Array ? options[0] : options ?? 'radio'}"/></td></tr>`;
|
|
case 'checkbox': return `<tr><th style="width:50%"><label>${label}</label></th><td style="width:50%"><input type="${type}" id="${i}qd" ${(options instanceof Array ? options[0] : options ?? false) ? 'checked' : ''} value="${value ?? label}"/></td></tr>`;
|
|
default: return `<tr><th style="width:50%"><label>${label}</label></th><td style="width:50%"><input type="${type}" id="${i}qd" value="${options instanceof Array ? options[0] : options}"/></td></tr>`;
|
|
}
|
|
}).join(``)
|
|
|
|
|
|
const content = `
|
|
<table style="width:100%">
|
|
${mapped}
|
|
</table>`;
|
|
|
|
return content;
|
|
}
|
|
|
|
static async dialog(data = {}, title = 'Prompt', submitLabel = 'Ok') {
|
|
logger.warn(`'warpgate.dialog' is deprecated and will be removed in version 1.17.0. See 'warpgate.menu' as a replacement.`);
|
|
data = data instanceof Array ? data : [data];
|
|
|
|
const results = await warpgate.menu({inputs: data}, {title, defaultButton: submitLabel});
|
|
if(results.buttons === false) return false;
|
|
return results.inputs;
|
|
}
|
|
|
|
/**
|
|
* Advanced dialog helper providing multiple input type options as well as user defined buttons.
|
|
*
|
|
* | `type` | `options` | Return Value | Notes |
|
|
* |--|--|--|--|
|
|
* | header | none | undefined | Shortcut for `info | <h2>text</h2>`. |
|
|
* | info | none | undefined | Inserts a line of text for display/informational purposes. |
|
|
* | text | default value | {String} final value of text field | |
|
|
* | password | (as `text`) | (as `text`) | Characters are obscured for security. |
|
|
* | radio | [group name, default state (`false`)] {Array of String/Bool} | selected: {Class<Primitive>} `value`. un-selected: {Boolean} `false` | For a given group name, only one radio button can be selected. |
|
|
* | checkbox | default state (`false`) {Boolean} | {Boolean} `value`/`false` checked/unchecked | `label` is used for the HTML element's `name` property |
|
|
* | number | (as `text`) | {Number} final value of text field converted to a number |
|
|
* | select | array of option labels or objects {value, html} | `value` property of selected option. If values not provided, numeric index of option in original list | |
|
|
* @static
|
|
* @param {object} [prompts]
|
|
* @param {Array<{label: string, type: string, options: any|Array<any>} >} [prompts.inputs=[]] follow the same structure as dialog
|
|
* @param {Array<{label: string, value: any, callback: Function }>} [prompts.buttons=[]] as buttonDialog
|
|
* @param {object} [config]
|
|
* @param {string} [config.title='Prompt'] Title of dialog
|
|
* @param {string} [config.defaultButton='Ok'] default button label if no buttons provided
|
|
* @param {function(HTMLElement) : void} [config.render=undefined]
|
|
* @param {Function} [config.close = (resolve) => resolve({buttons: false})]
|
|
* @param {object} [config.options = {}] Options passed to the Dialog constructor
|
|
*
|
|
* @return {Promise<{ inputs: Array<any>, buttons: any}>} Object with `inputs` containing the chosen values for each provided input,
|
|
* in order, and the provided `value` of the pressed button, or `false` if closed.
|
|
*
|
|
* @example
|
|
* await warpgate.menu({
|
|
* inputs: [{
|
|
* label: 'My Way',
|
|
* type: 'radio',
|
|
* options: 'group1'
|
|
* }, {
|
|
* label: 'The Highway',
|
|
* type: 'radio',
|
|
* options: 'group1'
|
|
* }],
|
|
* buttons: [{
|
|
* label: 'Yes',
|
|
* value: 1
|
|
* }, {
|
|
* label: 'No',
|
|
* value: 2
|
|
* }, {
|
|
* label: 'Maybe',
|
|
* value: 3
|
|
* }, {
|
|
* label: 'Eventually',
|
|
* value: 4
|
|
* }]
|
|
* }, {
|
|
* options: {
|
|
* width: '100px',
|
|
* height: '100%'
|
|
* }
|
|
* })
|
|
*
|
|
*/
|
|
static async menu(prompts = {}, config = {}) {
|
|
|
|
/* apply defaults to optional params */
|
|
const configDefaults = {
|
|
title : 'Prompt',
|
|
defaultButton : 'Ok',
|
|
render:null,
|
|
close : (resolve) => resolve({buttons: false}),
|
|
options : {}
|
|
}
|
|
|
|
const {title, defaultButton, render, close, options} = foundry.utils.mergeObject(configDefaults, config);
|
|
const {inputs, buttons} = foundry.utils.mergeObject({inputs: [], buttons: []}, prompts);
|
|
|
|
return await new Promise((resolve) => {
|
|
let content = MODULE.dialogInputs(inputs);
|
|
/** @type Object<string, object> */
|
|
let buttonData = {}
|
|
|
|
buttons.forEach((button) => {
|
|
buttonData[button.label] = {
|
|
label: button.label,
|
|
callback: async (html) => {
|
|
const results = {
|
|
inputs: MODULE._innerValueParse(inputs, html),
|
|
buttons: button.value
|
|
}
|
|
if(button.callback instanceof Function) await button.callback(results, button, html);
|
|
resolve(results);
|
|
}
|
|
}
|
|
});
|
|
|
|
/* insert standard submit button if none provided */
|
|
if (buttons.length < 1) {
|
|
buttonData = {
|
|
Ok: {
|
|
label: defaultButton,
|
|
callback: (html) => resolve({inputs: MODULE._innerValueParse(inputs, html), buttons: true})
|
|
}
|
|
}
|
|
}
|
|
|
|
new Dialog({
|
|
title,
|
|
content,
|
|
close: (...args) => close(resolve, ...args),
|
|
buttons: buttonData,
|
|
render,
|
|
}, {focus: true, ...options}).render(true);
|
|
});
|
|
}
|
|
|
|
static _innerValueParse(data, html) {
|
|
return Array(data.length).fill().map((e, i) => {
|
|
let {
|
|
type
|
|
} = data[i];
|
|
if (type.toLowerCase() === `select`) {
|
|
return data[i].options[html.find(`select#${i}qd`).val()].value;
|
|
} else {
|
|
switch (type.toLowerCase()) {
|
|
case `text`:
|
|
case `password`:
|
|
return html.find(`input#${i}qd`)[0].value;
|
|
case `radio`:
|
|
case `checkbox`:
|
|
return html.find(`input#${i}qd`)[0].checked ? html.find(`input#${i}qd`)[0].value : false;
|
|
case `number`:
|
|
return html.find(`input#${i}qd`)[0].valueAsNumber;
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|