import { BucketTypes } from "../utils/buckets.mjs"; import { diceSizeSorter } from "../utils/sorters/diceSize.mjs"; import { filePath } from "../consts.mjs"; import { Logger } from "../utils/Logger.mjs"; import { smallToLarge } from "../utils/sorters/smallToLarge.mjs"; const { HandlebarsApplicationMixin, ApplicationV2, DialogV2 } = foundry.applications.api; const { isEmpty } = foundry.utils; export class TableManager extends HandlebarsApplicationMixin(ApplicationV2) { // #region Options static DEFAULT_OPTIONS = { tag: `form`, classes: [ __ID__, `TableManager`, ], window: { title: `Table Manager`, frame: true, positioned: true, resizable: true, minimizable: true, contentClasses: [`st-scrollable`], controls: [ // Add action for deleting the table { icon: `fa-solid fa-trash`, label: `Delete Selected Table`, action: `deleteTable`, visible: () => CONFIG.stats.db.canDeleteTables(), }, ], }, position: { width: 475, height: 440, }, form: { submitOnChange: false, closeOnSubmit: false, handler: this.#submit, }, actions: { deleteTable: this.#deleteTable, }, }; static PARTS = { tableSelect: { template: filePath(`templates/Apps/common/tableSelect.hbs`), }, buckets: { 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: { template: filePath(`templates/Apps/TableManager/submit.hbs`), }, }; // #endregion Options // #region Instance Data #_selectedTable = ``; _selectedSubtable = ``; get _selectedTable() { return this.#_selectedTable; }; set _selectedTable(val) { this.#_selectedTable = val; this._selectedSubtable = ``; }; get activeTableID() { if (this._selectedSubtable) { return `${this._selectedTable}/${this._selectedSubtable}`; } return this._selectedTable; }; // #endregion Instance Data // #region Lifecycle async render({ userUpdated, ...opts } = {}) { if (userUpdated) { return; }; await super.render(opts); }; async _onRender(context, options) { await super._onRender(context, options); const elements = this.element .querySelectorAll(`[data-bind]`); for (const input of elements) { input.addEventListener(`change`, this.#bindListener.bind(this)); }; }; async _onFirstRender(context, options) { await super._onFirstRender(context, options); CONFIG.stats.db.addApp(this); }; _tearDown() { CONFIG.stats.db.removeApp(this); return super._tearDown(); }; // #endregion Lifecycle // #region Data Prep async _preparePartContext(partId) { const ctx = { table: this._selectedTable, subtable: this._selectedSubtable, }; ctx.meta = { idp: this.id, }; switch (partId) { case `tableSelect`: { await this.#prepareTableSelectContext(ctx); break; }; case `buckets`: { await this.#prepareBucketContext(ctx); break; }; }; if (import.meta.env.DEV) { Logger.log(partId, `context`, ctx); }; return ctx; }; async #prepareTableSelectContext(ctx) { const tables = new Set(); const subtables = {}; for (const tableConfig of await CONFIG.stats.db.getTables()) { const [ table, subtable ] = tableConfig.name.split(`/`); tables.add(table); if (subtable?.length > 0) { subtables[table] ??= []; subtables[table].push(subtable); }; }; const tableList = Array.from(tables); ctx.table = this._selectedTable; ctx.tables = tableList; const subtableList = subtables[this._selectedTable]; // Sort the subtables to be sane if (this._selectedTable === `Dice`) { subtableList?.sort(diceSizeSorter); } else { subtableList?.sort(smallToLarge); }; ctx.subtable = this._selectedSubtable; ctx.subtables = subtableList; }; async #prepareBucketContext(ctx) { const table = await CONFIG.stats.db.getTable(this.activeTableID); const type = table?.buckets?.type ?? `empty`; 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); if (!this[`_prepare${capitalizedType}Context`]) { return }; this[`_prepare${capitalizedType}Context`](ctx, table); }; async _prepareNumberContext(ctx, table) { ctx.buckets.min = table.buckets.min; ctx.buckets.max = table.buckets.max; ctx.buckets.step = table.buckets.step; }; async _prepareRangeContext(ctx, table) { ctx.buckets.min = table.buckets.min; ctx.buckets.max = table.buckets.max; ctx.buckets.step = table.buckets.step; }; async _prepareStringContext(ctx, table) { ctx.buckets.choices = [...table.buckets.choices]; }; // #endregion Data Prep // #region Actions /** * @param {Event} event */ async #bindListener(event) { const target = event.target; const data = target.dataset; const binding = data.bind; if (import.meta.env.DEV) { Logger.debug(`updating ${binding} value to ${target.value}`); } Reflect.set(this, binding, target.value); this.render(); }; /** * Process form submission for the sheet. * @this {DocumentSheetV2} The handler is called with the application as its bound scope * @param {SubmitEvent} event The originating form submission event * @param {HTMLFormElement} form The form element that was submitted * @param {FormDataExtended} formData Processed data for the submitted form * @param {object} [options] Additional options provided by a manual submit call. All options except `options.updateData` are forwarded along to _processSubmitData. * @param {object} [options.updateData] Additional data passed in if this form is submitted manually which should be merged with prepared formData. * @returns {Promise} */ static async #submit(_event, _form, formData, _options) { if (isEmpty(formData.object)) { ui.notifications.info(`Nothing to save`); return; } await CONFIG.stats.db.updateTable(this.activeTableID, formData.object); }; /** * @this {TableManager} */ static async #deleteTable() { const table = await CONFIG.stats.db.getTable(this.activeTableID); Logger.debug({ table }); if (!table) { ui.notifications.error( `You must select a table before you can delete it`, { console: false }, ); return; }; const confirmed = await DialogV2.confirm({ window: { title: `Confirm Deletion`, }, content: `

Are you sure you want to delete the table: ${this.activeTableID}

`, }); if (!confirmed) { return }; CONFIG.stats.db.deleteTable(this.activeTableID); this._selectedTable = null; this.render(); }; // #endregion Actions };