Create the initial version of the TableManager class for configuring settings
This commit is contained in:
parent
4bfce858ef
commit
8a2d946b63
21 changed files with 718 additions and 115 deletions
123
module/utils/buckets.mjs
Normal file
123
module/utils/buckets.mjs
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
import { Logger } from "./Logger.mjs";
|
||||
|
||||
const { deepClone } = foundry.utils;
|
||||
const { StringField, NumberField } = foundry.data.fields;
|
||||
|
||||
export const BucketTypes = {
|
||||
STRING: `string`,
|
||||
NUMBER: `number`,
|
||||
RANGE: `range`,
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {unknown} value The value to validate
|
||||
* @param {BucketConfig} options The bucket config for the table
|
||||
* @returns Whether or not the value is valid for the table
|
||||
*/
|
||||
export function validateValue(value, options) {
|
||||
/** @type {BucketConfig} */
|
||||
let opts = deepClone(options);
|
||||
|
||||
const validator = validators[opts.type];
|
||||
if (validator == null) {
|
||||
Logger.error(`Failed to find type validator for: ${opts.type}`);
|
||||
return false;
|
||||
};
|
||||
|
||||
// Disallow function choices if present
|
||||
if (typeof opts.choices === `function`) {
|
||||
delete opts.choices;
|
||||
};
|
||||
|
||||
// Get ride of properties that aren't part of the data fields
|
||||
delete opts.type;
|
||||
delete opts.locked;
|
||||
|
||||
validator.transformOptions(opts);
|
||||
|
||||
const field = new validator.field(opts);
|
||||
const error = field.validate(value);
|
||||
|
||||
// DataFields return a class instance on error, or void when valid.
|
||||
return !error;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {BucketConfig} config The bucket config for the table
|
||||
* @returns {BucketConfig} The transformed bucket config
|
||||
*/
|
||||
export function validateBucketConfig(config) {
|
||||
/** @type {BucketConfig} */
|
||||
let conf = deepClone(config);
|
||||
|
||||
const validator = validators[conf.type];
|
||||
if (validator == null) {
|
||||
Logger.error(`Failed to find type validator for: ${conf.type}`);
|
||||
return false;
|
||||
};
|
||||
|
||||
// Disallow function choices if present
|
||||
if (typeof conf.choices === `function`) {
|
||||
Logger.error(`Choices cannot be a function in a table's buckets configuraion`);
|
||||
delete conf.choices;
|
||||
};
|
||||
|
||||
validator.validateConfig(conf);
|
||||
|
||||
return conf;
|
||||
};
|
||||
|
||||
const validators = {
|
||||
[BucketTypes.STRING]: {
|
||||
field: StringField,
|
||||
transformOptions: (opts) => {
|
||||
opts.nullable = false;
|
||||
opts.trim = true;
|
||||
opts.blank = false;
|
||||
},
|
||||
validateConfig: (config) => {
|
||||
if (config.choices.length === 0) {
|
||||
delete config.choices;
|
||||
config[`-=choices`] = null;
|
||||
};
|
||||
},
|
||||
},
|
||||
[BucketTypes.NUMBER]: {
|
||||
field: NumberField,
|
||||
transformOptions: transformNumberFieldOptions,
|
||||
validateConfig: (config) => {
|
||||
if (config.step != null && config.min == null) {
|
||||
delete config.step;
|
||||
config[`-=step`] = null;
|
||||
};
|
||||
if (
|
||||
config.min != null
|
||||
&& config.max != null
|
||||
&& config.min > config.max
|
||||
) {
|
||||
throw new Error(`"min" must be less than "max"`);
|
||||
}
|
||||
},
|
||||
},
|
||||
[BucketTypes.RANGE]: {
|
||||
field: NumberField,
|
||||
transformOptions: transformNumberFieldOptions,
|
||||
validateConfig: (config) => {
|
||||
if (config.min == null) {
|
||||
throw new Error(`"min" must be defined for range buckets`);
|
||||
};
|
||||
if (config.max == null) {
|
||||
throw new Error(`"max" must be defined for range buckets`);
|
||||
};
|
||||
if (config.min > config.max) {
|
||||
throw new Error(`"min" must be less than "max"`);
|
||||
}
|
||||
config.step ??= 1;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function transformNumberFieldOptions(opts) {
|
||||
opts.nullable = false;
|
||||
opts.integer = true;
|
||||
};
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
import { Database } from "./Database.mjs";
|
||||
import { filterPrivateRows } from "../privacy.mjs";
|
||||
import { Logger } from "../Logger.mjs";
|
||||
import { validateBucketConfig } from "../buckets.mjs";
|
||||
|
||||
const { randomID, mergeObject } = foundry.utils;
|
||||
const { deleteProperty, diffObject, expandObject, mergeObject, randomID } = foundry.utils;
|
||||
|
||||
export class MemoryDatabase extends Database {
|
||||
static #tables = {
|
||||
|
|
@ -10,6 +11,7 @@ export class MemoryDatabase extends Database {
|
|||
name: `Dice/d10`,
|
||||
buckets: {
|
||||
type: `range`,
|
||||
locked: true,
|
||||
min: 1,
|
||||
max: 10,
|
||||
step: 1,
|
||||
|
|
@ -23,6 +25,7 @@ export class MemoryDatabase extends Database {
|
|||
name: `Dice/d20`,
|
||||
buckets: {
|
||||
type: `range`,
|
||||
locked: true,
|
||||
min: 1,
|
||||
max: 20,
|
||||
step: 1,
|
||||
|
|
@ -36,6 +39,7 @@ export class MemoryDatabase extends Database {
|
|||
name: `Dice/d100`,
|
||||
buckets: {
|
||||
type: `range`,
|
||||
locked: true,
|
||||
min: 1,
|
||||
max: 100,
|
||||
step: 1,
|
||||
|
|
@ -45,11 +49,23 @@ export class MemoryDatabase extends Database {
|
|||
stacked: true,
|
||||
},
|
||||
},
|
||||
"Count of Successes": {
|
||||
name: `Count of Successes`,
|
||||
"Successes Number": {
|
||||
name: `Successes Number`,
|
||||
buckets: {
|
||||
type: `number`,
|
||||
min: 0,
|
||||
},
|
||||
graph: {
|
||||
type: `bar`,
|
||||
stacked: true,
|
||||
},
|
||||
},
|
||||
"Successes Range": {
|
||||
name: `Successes Range`,
|
||||
buckets: {
|
||||
type: `range`,
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
},
|
||||
graph: {
|
||||
|
|
@ -61,9 +77,6 @@ export class MemoryDatabase extends Database {
|
|||
name: `Type of Result`,
|
||||
buckets: {
|
||||
type: `string`,
|
||||
trim: true, // forced true
|
||||
blank: false, // forced false
|
||||
textSearch: false, // forced false
|
||||
choices: [`Normal`, `Popped Off`, `Downed`],
|
||||
},
|
||||
graph: {
|
||||
|
|
@ -81,6 +94,26 @@ export class MemoryDatabase extends Database {
|
|||
return true;
|
||||
};
|
||||
|
||||
static getTableNames() {
|
||||
const tables = new Set();
|
||||
for (const tableID of Object.keys(this.#tables)) {
|
||||
const [ targetTable ] = tableID.split(`/`, 2);
|
||||
tables.add(targetTable);
|
||||
};
|
||||
return Array.from(tables);
|
||||
};
|
||||
|
||||
static getSubtableNames(table) {
|
||||
const subtables = new Set();
|
||||
for (const tableID of Object.keys(this.#tables)) {
|
||||
const [ targetTable, targetSubtable ] = tableID.split(`/`, 2);
|
||||
if (targetTable === table) {
|
||||
subtables.add(targetSubtable);
|
||||
}
|
||||
};
|
||||
return Array.from(subtables);
|
||||
};
|
||||
|
||||
/** @returns {Array<Table>} */
|
||||
static getTables() {
|
||||
return Object.values(this.#tables);
|
||||
|
|
@ -90,6 +123,39 @@ export class MemoryDatabase extends Database {
|
|||
return this.#tables[tableID];
|
||||
};
|
||||
|
||||
static async updateTable(tableID, changes) {
|
||||
Logger.debug({tableID, changes});
|
||||
const table = this.getTable(tableID);
|
||||
if (!table) { return false };
|
||||
|
||||
// Bucket coercion in case called via the API
|
||||
deleteProperty(changes, `name`);
|
||||
deleteProperty(changes, `buckets.type`);
|
||||
|
||||
const diff = diffObject(
|
||||
table,
|
||||
expandObject(changes),
|
||||
{ inner: true, deletionKeys: true },
|
||||
);
|
||||
if (Object.keys(diff).length === 0) { return false };
|
||||
|
||||
const updated = mergeObject(
|
||||
table,
|
||||
diff,
|
||||
{ inplace: false, performDeletions: true },
|
||||
);
|
||||
|
||||
try {
|
||||
updated.buckets = validateBucketConfig(updated.buckets);
|
||||
} catch (e) {
|
||||
ui.notifications.error(e);
|
||||
return false;
|
||||
};
|
||||
|
||||
this.#tables[tableID] = updated;
|
||||
return true;
|
||||
};
|
||||
|
||||
static createRow(table, userID, row, { rerender = true } = {}) {
|
||||
if (!this.#tables[table]) { return };
|
||||
this.#rows[userID] ??= {};
|
||||
|
|
|
|||
|
|
@ -1,73 +0,0 @@
|
|||
import { Logger } from "./Logger.mjs";
|
||||
|
||||
const { deepClone } = foundry.utils;
|
||||
const { StringField, NumberField } = foundry.data.fields;
|
||||
|
||||
/**
|
||||
* @param {unknown} value The value to validate
|
||||
* @param {BucketConfig} options The bucket config for the table
|
||||
* @returns Whether or not the value is valid for the table
|
||||
*/
|
||||
export function validateValue(value, options) {
|
||||
/** @type {BucketConfig} */
|
||||
let opts = deepClone(options);
|
||||
if (validatorTypes[opts.type] == null) {
|
||||
Logger.error(`Failed to find type validator for: ${opts.type}`);
|
||||
return false;
|
||||
};
|
||||
|
||||
const validator = validatorTypes[opts.type];
|
||||
validator.transformOptions(opts);
|
||||
|
||||
const field = new validator.field(opts);
|
||||
const error = field.validate(value);
|
||||
|
||||
// DataFields return a class instance on error, or void when valid.
|
||||
return !error;
|
||||
};
|
||||
|
||||
export const BucketTypes = {
|
||||
STRING: `string`,
|
||||
NUMBER: `number`,
|
||||
RANGE: `range`,
|
||||
};
|
||||
|
||||
const validatorTypes = {
|
||||
[BucketTypes.STRING]: {
|
||||
field: StringField,
|
||||
transformOptions: (opts) => {
|
||||
delete opts.type;
|
||||
opts.nullable = false;
|
||||
opts.trim = true;
|
||||
opts.blank = false;
|
||||
if (typeof opts.choices === `function`) {
|
||||
Logger.error(`Choices cannot be a function in a table's buckets configuraion`);
|
||||
delete opts.choices;
|
||||
};
|
||||
},
|
||||
},
|
||||
[BucketTypes.NUMBER]: {
|
||||
field: NumberField,
|
||||
transformOptions: (opts) => {
|
||||
delete opts.type;
|
||||
opts.nullable = false;
|
||||
opts.integer = true;
|
||||
if (typeof opts.choices === `function`) {
|
||||
Logger.error(`Choices cannot be a function in a table's buckets configuraion`);
|
||||
delete opts.choices;
|
||||
};
|
||||
},
|
||||
},
|
||||
[BucketTypes.RANGE]: {
|
||||
field: NumberField,
|
||||
transformOptions: (opts) => {
|
||||
delete opts.type;
|
||||
opts.nullable = false;
|
||||
opts.integer = true;
|
||||
if (typeof opts.choices === `function`) {
|
||||
Logger.error(`Choices cannot be a function in a table's buckets configuraion`);
|
||||
delete opts.choices;
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue