Make all of the DB methods async, and update the dependencies in order to make them work with the async DBs

This commit is contained in:
Oliver-Akins 2025-05-11 22:47:23 -06:00
parent 8a2d946b63
commit 606d6e14ea
11 changed files with 179 additions and 165 deletions

View file

@ -35,7 +35,7 @@ export class StatSidebar extends HandlebarsApplicationMixin(AbstractSidebarTab)
const ctx = await super._prepareContext(options); const ctx = await super._prepareContext(options);
const db = CONFIG.stats.db; const db = CONFIG.stats.db;
ctx.tableCount = db.getTables().length; ctx.tableCount = (await db.getTables()).length;
const controls = { const controls = {
openStats: { label: `View Stats`, action: `openStats` }, openStats: { label: `View Stats`, action: `openStats` },

View file

@ -131,7 +131,7 @@ export class StatsViewer extends HandlebarsApplicationMixin(ApplicationV2) {
const tables = new Set(); const tables = new Set();
const subtables = {}; const subtables = {};
for (const tableConfig of CONFIG.stats.db.getTables()) { for (const tableConfig of await CONFIG.stats.db.getTables()) {
const [ table, subtable ] = tableConfig.name.split(`/`); const [ table, subtable ] = tableConfig.name.split(`/`);
tables.add(table); tables.add(table);
if (subtable?.length > 0) { if (subtable?.length > 0) {
@ -190,8 +190,8 @@ export class StatsViewer extends HandlebarsApplicationMixin(ApplicationV2) {
_graphData = {}; _graphData = {};
_privacySetting = `my`; _privacySetting = `my`;
async #prepareGraphContext(_ctx) { async #prepareGraphContext(_ctx) {
const table = CONFIG.stats.db.getTable(this.activeTableID); const table = await CONFIG.stats.db.getTable(this.activeTableID);
const userData = CONFIG.stats.db.getRows( const userData = await CONFIG.stats.db.getRows(
this.activeTableID, this.activeTableID,
this._selectedUsers, this._selectedUsers,
this._privacySetting, this._privacySetting,

View file

@ -116,7 +116,7 @@ export class TableCreator extends HandlebarsApplicationMixin(ApplicationV2) {
ui.notifications.error(`Cannot create a table without a name`); ui.notifications.error(`Cannot create a table without a name`);
}; };
const existing = CONFIG.stats.db.getTable(name); const existing = await CONFIG.stats.db.getTable(name);
if (existing) { if (existing) {
ui.notifications.error(`A table with the name "${name}" already exists`); ui.notifications.error(`A table with the name "${name}" already exists`);
return; return;
@ -128,11 +128,11 @@ export class TableCreator extends HandlebarsApplicationMixin(ApplicationV2) {
return; return;
}; };
const size = Number(name.replace(`Dice/d`, ``)); const size = Number(name.replace(`Dice/d`, ``));
CONFIG.stats.db.createTable(createDiceTable(size)); await CONFIG.stats.db.createTable(createDiceTable(size));
return; return;
}; };
CONFIG.stats.db.createTable({ await CONFIG.stats.db.createTable({
name, name,
buckets: { buckets: {
type: this._type, type: this._type,

View file

@ -1,3 +1,4 @@
import { BucketTypes } from "../utils/buckets.mjs";
import { diceSizeSorter } from "../utils/sorters/diceSize.mjs"; import { diceSizeSorter } from "../utils/sorters/diceSize.mjs";
import { filePath } from "../consts.mjs"; import { filePath } from "../consts.mjs";
import { Logger } from "../utils/Logger.mjs"; import { Logger } from "../utils/Logger.mjs";
@ -42,23 +43,18 @@ export class TableManager extends HandlebarsApplicationMixin(ApplicationV2) {
template: filePath(`templates/Apps/common/tableSelect.hbs`), template: filePath(`templates/Apps/common/tableSelect.hbs`),
}, },
buckets: { buckets: {
template: filePath(`templates/Apps/TableManager/buckets/empty.hbs`), template: filePath(`templates/Apps/TableManager/buckets.hbs`),
templates: [
filePath(`templates/Apps/TableManager/buckets/empty.hbs`),
...Object.values(BucketTypes).map(
(bucketType) => filePath(`templates/Apps/TableManager/buckets/${bucketType}.hbs`),
),
],
}, },
submit: { submit: {
template: filePath(`templates/Apps/TableManager/submit.hbs`), template: filePath(`templates/Apps/TableManager/submit.hbs`),
}, },
}; };
_configureRenderOptions(options) {
const table = CONFIG.stats.db.getTable(this.activeTableID);
let bucketType = table?.buckets?.type ?? `empty`;
this.constructor.PARTS.buckets = {
template: filePath(`templates/Apps/TableManager/buckets/${bucketType}.hbs`),
};
super._configureRenderOptions(options);
};
// #endregion Options // #endregion Options
// #region Selected Table // #region Selected Table
@ -84,7 +80,7 @@ export class TableManager extends HandlebarsApplicationMixin(ApplicationV2) {
async render({ userUpdated, ...opts } = {}) { async render({ userUpdated, ...opts } = {}) {
if (userUpdated) { if (userUpdated) {
return; return;
} };
await super.render(opts); await super.render(opts);
}; };
@ -140,7 +136,7 @@ export class TableManager extends HandlebarsApplicationMixin(ApplicationV2) {
const tables = new Set(); const tables = new Set();
const subtables = {}; const subtables = {};
for (const tableConfig of CONFIG.stats.db.getTables()) { for (const tableConfig of await CONFIG.stats.db.getTables()) {
const [ table, subtable ] = tableConfig.name.split(`/`); const [ table, subtable ] = tableConfig.name.split(`/`);
tables.add(table); tables.add(table);
if (subtable?.length > 0) { if (subtable?.length > 0) {
@ -167,35 +163,46 @@ export class TableManager extends HandlebarsApplicationMixin(ApplicationV2) {
}; };
async #prepareBucketContext(ctx) { async #prepareBucketContext(ctx) {
const table = CONFIG.stats.db.getTable(this.activeTableID); const table = await CONFIG.stats.db.getTable(this.activeTableID);
if (!table) { return }; const type = table?.buckets?.type ?? `empty`;
const type = table.buckets.type;
const template = filePath(`templates/Apps/TableManager/buckets/${type}.hbs`);
ctx.buckets = {
locked: false,
template,
classes: ``,
};
if (!table) {
ctx.buckets.classes = `alert-box warning center`;
return;
};
const locked = this._selectedTable === `Dice` || table.buckets.locked;
ctx.buckets.locked = locked;
if (locked) {
ctx.buckets.classes = `alert-box locked`;
};
const capitalizedType = type[0].toUpperCase() + type.slice(1); const capitalizedType = type[0].toUpperCase() + type.slice(1);
if (!this[`_prepare${capitalizedType}Context`]) { return }; if (!this[`_prepare${capitalizedType}Context`]) { return };
this[`_prepare${capitalizedType}Context`](ctx, table); this[`_prepare${capitalizedType}Context`](ctx, table);
}; };
async _prepareNumberContext(ctx, table) { async _prepareNumberContext(ctx, table) {
ctx.buckets = { ctx.buckets.min = table.buckets.min;
min: table.buckets.min, ctx.buckets.max = table.buckets.max;
max: table.buckets.max, ctx.buckets.step = table.buckets.step;
step: table.buckets.step,
};
}; };
async _prepareRangeContext(ctx, table) { async _prepareRangeContext(ctx, table) {
ctx.buckets = { ctx.buckets.min = table.buckets.min;
locked: this._selectedTable === `Dice` || table.buckets.locked, ctx.buckets.max = table.buckets.max;
min: table.buckets.min, ctx.buckets.step = table.buckets.step;
max: table.buckets.max,
step: table.buckets.step,
};
}; };
async _prepareStringContext(ctx, table) { async _prepareStringContext(ctx, table) {
ctx.buckets = { ctx.buckets.choices = [...table.buckets.choices];
choices: [...table.buckets.choices],
};
}; };
// #endregion Data Prep // #endregion Data Prep
@ -230,7 +237,7 @@ export class TableManager extends HandlebarsApplicationMixin(ApplicationV2) {
ui.notifications.info(`Nothing to save`); ui.notifications.info(`Nothing to save`);
return; return;
} }
CONFIG.stats.db.updateTable(this.activeTableID, formData.object); await CONFIG.stats.db.updateTable(this.activeTableID, formData.object);
}; };
// #endregion Actions // #endregion Actions
}; };

View file

@ -1,4 +1,5 @@
/* eslint-disable no-unused-vars */ /* eslint-disable no-unused-vars */
import { validateBucketConfig } from "../buckets.mjs";
/* /*
NOTE: NOTE:
@ -24,9 +25,11 @@ Default Subtables:
tables that are parents to other tables. tables that are parents to other tables.
*/ */
const { deleteProperty, diffObject, expandObject, mergeObject } = foundry.utils;
export class Database { export class Database {
// MARK: Table Ops // MARK: Table Ops
static createTable(tableConfig) { static async createTable(tableConfig) {
if (!game.user.isGM) { if (!game.user.isGM) {
ui.notifications.error(`You do not have the required permission to create a new table`); ui.notifications.error(`You do not have the required permission to create a new table`);
return false; return false;
@ -52,23 +55,61 @@ export class Database {
tables[name] = tableConfig; tables[name] = tableConfig;
game.settings.set(__ID__, `tables`, tables); game.settings.set(__ID__, `tables`, tables);
this.render(); this.render({ tags: [`table`] });
return true; return true;
}; };
/** @returns {Array<Table>} */ /** @returns {Array<Table>} */
static getTables() { static async getTables() {
const tables = game.settings.get(__ID__, `tables`); const tables = game.settings.get(__ID__, `tables`);
return Object.values(tables) ?? []; return Object.values(tables) ?? [];
}; };
static getTable(tableID) { static async getTable(tableID) {
const tables = game.settings.get(__ID__, `tables`); const tables = game.settings.get(__ID__, `tables`);
if (!tables[tableID]) { return }; if (!tables[tableID]) { return };
return tables[tableID]; return tables[tableID];
}; };
static deleteTable(tableID) { static async updateTable(tableID, changes) {
const table = this.getTable(tableID);
if (!tables[tableID]) {
ui.notifications.error(`Cannot update table that doesn't exist`);
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;
};
const tables = game.settings.get(__ID__, `tables`);
tables[tableID] = updated;
game.settings.set(__ID__, `tables`, tables);
this.render({ tags: [`table`] });
return true;
};
static async deleteTable(tableID) {
if (!game.user.isGM) { if (!game.user.isGM) {
ui.notifications.error(`You do not have the required permission to delete a table`); ui.notifications.error(`You do not have the required permission to delete a table`);
return false; return false;
@ -86,23 +127,23 @@ export class Database {
}; };
// MARK: Row Ops // MARK: Row Ops
static createRow(table, userID, row, opts) { static async createRow(table, userID, row, opts) {
throw new Error(`createRow() must be implemented`); throw new Error(`createRow() must be implemented`);
}; };
static createRows(table, userID, rows, opts) { static async createRows(table, userID, rows, opts) {
throw new Error(`createRows() must be implemented`); throw new Error(`createRows() must be implemented`);
}; };
static getRows(tableID, userIDs, privacy = `none`) { static async getRows(tableID, userIDs, privacy = `none`) {
throw new Error(`getRows() must be implemented`); throw new Error(`getRows() must be implemented`);
}; };
static updateRow(table, userID, rowID, changes) { static async updateRow(table, userID, rowID, changes) {
throw new Error(`updateRow() must be implemented`); throw new Error(`updateRow() must be implemented`);
}; };
static deleteRow(table, userID, rowID) { static async deleteRow(table, userID, rowID) {
throw new Error(`deleteRow() must be implemented`); throw new Error(`deleteRow() must be implemented`);
}; };
@ -137,7 +178,7 @@ export class Database {
* Rerenders all of the applications that are displaying data from * Rerenders all of the applications that are displaying data from
* this database * this database
*/ */
static render(opts) { static async render(opts) {
for (const app of this._apps.values()) { for (const app of this._apps.values()) {
app.render(opts); app.render(opts);
}; };
@ -148,9 +189,9 @@ export class Database {
* Used to listen for changes from other clients and rerender the apps * Used to listen for changes from other clients and rerender the apps
* as required in order to keep the data as up-to-date as possible. * as required in order to keep the data as up-to-date as possible.
*/ */
static registerListeners() {}; static async registerListeners() {};
static unregisterListeners() {}; static async unregisterListeners() {};
}; };
/* eslint-enable no-unused-vars */ /* eslint-enable no-unused-vars */

View file

@ -1,3 +1,4 @@
import { createDiceTable } from "./utils.mjs";
import { Database } from "./Database.mjs"; import { Database } from "./Database.mjs";
import { filterPrivateRows } from "../privacy.mjs"; import { filterPrivateRows } from "../privacy.mjs";
import { Logger } from "../Logger.mjs"; import { Logger } from "../Logger.mjs";
@ -7,48 +8,9 @@ const { deleteProperty, diffObject, expandObject, mergeObject, randomID } = foun
export class MemoryDatabase extends Database { export class MemoryDatabase extends Database {
static #tables = { static #tables = {
"Dice/d10": { "Dice/d10": createDiceTable(10),
name: `Dice/d10`, "Dice/d20": createDiceTable(20),
buckets: { "Dice/d100": createDiceTable(100),
type: `range`,
locked: true,
min: 1,
max: 10,
step: 1,
},
graph: {
type: `bar`,
stacked: true,
},
},
"Dice/d20": {
name: `Dice/d20`,
buckets: {
type: `range`,
locked: true,
min: 1,
max: 20,
step: 1,
},
graph: {
type: `bar`,
stacked: true,
},
},
"Dice/d100": {
name: `Dice/d100`,
buckets: {
type: `range`,
locked: true,
min: 1,
max: 100,
step: 1,
},
graph: {
type: `bar`,
stacked: true,
},
},
"Successes Number": { "Successes Number": {
name: `Successes Number`, name: `Successes Number`,
buckets: { buckets: {
@ -88,13 +50,13 @@ export class MemoryDatabase extends Database {
static #rows = {}; static #rows = {};
static createTable(tableConfig) { static async createTable(tableConfig) {
this.#tables[tableConfig.name] = tableConfig; this.#tables[tableConfig.name] = tableConfig;
this.render(); this.render();
return true; return true;
}; };
static getTableNames() { static async getTableNames() {
const tables = new Set(); const tables = new Set();
for (const tableID of Object.keys(this.#tables)) { for (const tableID of Object.keys(this.#tables)) {
const [ targetTable ] = tableID.split(`/`, 2); const [ targetTable ] = tableID.split(`/`, 2);
@ -103,7 +65,7 @@ export class MemoryDatabase extends Database {
return Array.from(tables); return Array.from(tables);
}; };
static getSubtableNames(table) { static async getSubtableNames(table) {
const subtables = new Set(); const subtables = new Set();
for (const tableID of Object.keys(this.#tables)) { for (const tableID of Object.keys(this.#tables)) {
const [ targetTable, targetSubtable ] = tableID.split(`/`, 2); const [ targetTable, targetSubtable ] = tableID.split(`/`, 2);
@ -115,16 +77,15 @@ export class MemoryDatabase extends Database {
}; };
/** @returns {Array<Table>} */ /** @returns {Array<Table>} */
static getTables() { static async getTables() {
return Object.values(this.#tables); return Object.values(this.#tables);
}; };
static getTable(tableID) { static async getTable(tableID) {
return this.#tables[tableID]; return this.#tables[tableID];
}; };
static async updateTable(tableID, changes) { static async updateTable(tableID, changes) {
Logger.debug({tableID, changes});
const table = this.getTable(tableID); const table = this.getTable(tableID);
if (!table) { return false }; if (!table) { return false };
@ -156,7 +117,7 @@ export class MemoryDatabase extends Database {
return true; return true;
}; };
static createRow(table, userID, row, { rerender = true } = {}) { static async createRow(table, userID, row, { rerender = true } = {}) {
if (!this.#tables[table]) { return }; if (!this.#tables[table]) { return };
this.#rows[userID] ??= {}; this.#rows[userID] ??= {};
this.#rows[userID][table] ??= []; this.#rows[userID][table] ??= [];
@ -172,7 +133,7 @@ export class MemoryDatabase extends Database {
}; };
}; };
static createRows(table, userID, rows, { rerender = true } = {}) { static async createRows(table, userID, rows, { rerender = true } = {}) {
if (!this.#tables[table]) { return }; if (!this.#tables[table]) { return };
this.#rows[userID] ??= {}; this.#rows[userID] ??= {};
this.#rows[userID][table] ??= []; this.#rows[userID][table] ??= [];
@ -187,7 +148,7 @@ export class MemoryDatabase extends Database {
}; };
}; };
static getRows(tableID, userIDs, privacy = `none`) { static async getRows(tableID, userIDs, privacy = `none`) {
if (userIDs.length === 0) { if (userIDs.length === 0) {
return {}; return {};
}; };
@ -207,7 +168,7 @@ export class MemoryDatabase extends Database {
return datasets; return datasets;
}; };
static updateRow(table, userID, rowID, changes) { static async updateRow(table, userID, rowID, changes) {
if (!this.#tables[table] || !this.#rows[userID]?.[table]) { return }; if (!this.#tables[table] || !this.#rows[userID]?.[table]) { return };
let row = this.#rows[userID][table].find(row => row._id === rowID); let row = this.#rows[userID][table].find(row => row._id === rowID);
if (!row) { return }; if (!row) { return };
@ -215,7 +176,7 @@ export class MemoryDatabase extends Database {
this.render({ userUpdated: userID }); this.render({ userUpdated: userID });
}; };
static deleteRow(table, userID, rowID) { static async deleteRow(table, userID, rowID) {
if (!this.#tables[table] || !this.#rows[userID]?.[table]) { return }; if (!this.#tables[table] || !this.#rows[userID]?.[table]) { return };
let rowIndex = this.#rows[userID][table].findIndex(row => row._id === rowID); let rowIndex = this.#rows[userID][table].findIndex(row => row._id === rowID);
if (rowIndex === -1) { return }; if (rowIndex === -1) { return };
@ -227,7 +188,7 @@ export class MemoryDatabase extends Database {
* Used to listen for changes from other clients and refresh the local DB as * Used to listen for changes from other clients and refresh the local DB as
* required, so that the StatsTracker stays up to date. * required, so that the StatsTracker stays up to date.
*/ */
static registerListeners() {}; static async registerListeners() {};
static unregisterListeners() {}; static async unregisterListeners() {};
}; };

View file

@ -0,0 +1,9 @@
<div class="{{buckets.classes}}">
{{#if buckets.locked}}
<p class="center">
This bucket configuration has been locked, preventing editing
of the settings.
</p>
{{/if}}
{{> (lookup buckets "template") }}
</div>

View file

@ -1,7 +1,5 @@
<div class="alert-box warning center"> {{#if table}}
{{#if table}}
<span class="large">Select a Subtable</span> <span class="large">Select a Subtable</span>
{{else}} {{else}}
<span class="large">Select a Table</span> <span class="large">Select a Table</span>
{{/if}} {{/if}}
</div>

View file

@ -1,4 +1,7 @@
<div class="{{#if buckets.locked}}alert-box locked{{/if}}"> <div
class="{{#if buckets.locked}}alert-box locked{{/if}}"
data-bucket-type="number"
>
{{#if buckets.locked}} {{#if buckets.locked}}
<p class="center"> <p class="center">
This bucket configuration has been locked, preventing editing This bucket configuration has been locked, preventing editing

View file

@ -1,11 +1,4 @@
<div class="{{#if buckets.locked}}alert-box locked{{/if}}"> <div class="input-group">
{{#if buckets.locked}}
<p class="center">
This bucket configuration has been locked, preventing editing
of the settings.
</p>
{{/if}}
<div class="input-group">
<label for="{{meta.idp}}-min"> <label for="{{meta.idp}}-min">
Minimum Minimum
</label> </label>
@ -17,8 +10,8 @@
required required
{{disabled buckets.locked}} {{disabled buckets.locked}}
> >
</div> </div>
<div class="input-group"> <div class="input-group">
<label for="{{meta.idp}}-max"> <label for="{{meta.idp}}-max">
Maximum Maximum
</label> </label>
@ -30,8 +23,8 @@
required required
{{disabled buckets.locked}} {{disabled buckets.locked}}
> >
</div> </div>
<div class="input-group"> <div class="input-group">
<label for="{{meta.idp}}-step"> <label for="{{meta.idp}}-step">
Step Step
</label> </label>
@ -46,5 +39,4 @@
<p class="hint"> <p class="hint">
The size of the step between values within the range. The size of the step between values within the range.
</p> </p>
</div>
</div> </div>

View file

@ -1,4 +1,7 @@
<div class="{{#if buckets.locked}}alert-box locked{{/if}}"> <div
class="{{#if buckets.locked}}alert-box locked{{/if}}"
data-bucket-type="string"
>
<div class="input-group"> <div class="input-group">
<label for=""> <label for="">
Valid Options Valid Options