Add a generic helper app to display settings menus similar to how Foundry does by default, but without the category selector
This commit is contained in:
parent
523d9c1da2
commit
28d0105397
3 changed files with 200 additions and 0 deletions
185
module/apps/OFTSettingsMenu.mjs
Normal file
185
module/apps/OFTSettingsMenu.mjs
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
import { filePath } from "../consts.mjs";
|
||||
|
||||
const { HandlebarsApplicationMixin: HAM, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
export class OFTSettingsMenu extends HAM(ApplicationV2) {
|
||||
|
||||
// #region Options
|
||||
static DEFAULT_OPTIONS = {
|
||||
tag: `form`,
|
||||
window: {
|
||||
icon: `fa-solid fa-gears`,
|
||||
resizable: true,
|
||||
contentClasses: [
|
||||
`standard-form`,
|
||||
],
|
||||
controls: [
|
||||
{
|
||||
icon: `fa-solid fa-arrow-rotate-left`,
|
||||
label: `PACKAGECONFIG.Reset`,
|
||||
visible: true,
|
||||
action: `resetDefaults`,
|
||||
},
|
||||
],
|
||||
},
|
||||
form: {
|
||||
handler: this.#handleSubmit,
|
||||
closeOnSubmit: true,
|
||||
submitOnChange: false,
|
||||
},
|
||||
position: {
|
||||
width: 544,
|
||||
},
|
||||
actions: {
|
||||
resetDefaults: this.#resetDefaults,
|
||||
},
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
settingsList: {
|
||||
template: filePath(`templates/OFTSettingsMenu/settingsList.hbs`),
|
||||
scrollable: [``],
|
||||
},
|
||||
footer: {
|
||||
template: filePath(`templates/OFTSettingsMenu/footer.hbs`),
|
||||
},
|
||||
};
|
||||
|
||||
static _SETTINGS = [];
|
||||
// #endregion Options
|
||||
|
||||
// #region Data Prep
|
||||
async _prepareContext() {
|
||||
return {
|
||||
meta: {
|
||||
idp: this.id,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
#partContextPrep = {
|
||||
settingsList: this._prepareSettingsListContext,
|
||||
};
|
||||
|
||||
async _preparePartContext(partID, ctx) {
|
||||
ctx = foundry.utils.deepClone(ctx);
|
||||
|
||||
const prepper = this.#partContextPrep[partID];
|
||||
if (prepper && typeof prepper === `function`) {
|
||||
await prepper.call(this, ctx);
|
||||
};
|
||||
|
||||
return ctx;
|
||||
};
|
||||
|
||||
async _prepareSettingsListContext(ctx) {
|
||||
const canConfigure = game.user.can(`SETTINGS_MODIFY`);
|
||||
ctx.settings = [];
|
||||
|
||||
const settingIDs = this.constructor._SETTINGS;
|
||||
for ( const settingID of settingIDs ) {
|
||||
const setting = game.settings.settings.get(settingID);
|
||||
if ( !canConfigure && setting.scope === CONST.SETTING_SCOPES.WORLD ) { continue }
|
||||
const data = {
|
||||
label: setting.value,
|
||||
value: game.settings.get(setting.namespace, setting.key),
|
||||
menu: false,
|
||||
};
|
||||
|
||||
// Define a DataField for each setting not originally defined with one
|
||||
const fields = foundry.data.fields;
|
||||
if ( setting.type instanceof fields.DataField ) {
|
||||
data.field = setting.type;
|
||||
}
|
||||
else if ( setting.type === Boolean ) {
|
||||
data.field = new fields.BooleanField({initial: setting.default ?? false});
|
||||
}
|
||||
else if ( setting.type === Number ) {
|
||||
const {min, max, step} = setting.range ?? {};
|
||||
data.field = new fields.NumberField({
|
||||
required: true,
|
||||
choices: setting.choices,
|
||||
initial: setting.default,
|
||||
min,
|
||||
max,
|
||||
step,
|
||||
});
|
||||
}
|
||||
else if ( setting.filePicker ) {
|
||||
const categories = {
|
||||
audio: [`AUDIO`],
|
||||
folder: [],
|
||||
font: [`FONT`],
|
||||
graphics: [`GRAPHICS`],
|
||||
image: [`IMAGE`],
|
||||
imagevideo: [`IMAGE`, `VIDEO`],
|
||||
text: [`TEXT`],
|
||||
video: [`VIDEO`],
|
||||
}[setting.filePicker] ?? Object.keys(CONST.FILE_CATEGORIES).filter(c => c !== `HTML`);
|
||||
if ( categories.length ) {
|
||||
data.field = new fields.FilePathField({required: true, blank: true, categories});
|
||||
}
|
||||
else {
|
||||
data.field = new fields.StringField({required: true}); // Folder paths cannot be FilePathFields
|
||||
data.folderPicker = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
data.field = new fields.StringField({required: true, choices: setting.choices});
|
||||
}
|
||||
data.field.name = `${setting.namespace}.${setting.key}`;
|
||||
data.field.label ||= game.i18n.localize(setting.name ?? ``);
|
||||
data.field.hint ||= game.i18n.localize(setting.hint ?? ``);
|
||||
|
||||
// Categorize setting
|
||||
ctx.settings.push(data);
|
||||
};
|
||||
};
|
||||
// #endregion Data Prep
|
||||
|
||||
// #region Actions
|
||||
/** @this {OFTSettingsMenu} */
|
||||
static async #handleSubmit(_event, _form, formData) {
|
||||
let requiresClientReload = false;
|
||||
let requiresWorldReload = false;
|
||||
for ( const [key, value] of Object.entries(formData.object) ) {
|
||||
const setting = game.settings.settings.get(key);
|
||||
if ( !setting ) { continue };
|
||||
const priorValue = game.settings.get(setting.namespace, setting.key, {document: true})?._source.value;
|
||||
let newSetting;
|
||||
try {
|
||||
newSetting = await game.settings.set(setting.namespace, setting.key, value, {document: true});
|
||||
} catch(error) {
|
||||
ui.notifications.error(error);
|
||||
}
|
||||
if ( priorValue === newSetting?._source.value ) { continue }; // Compare JSON strings
|
||||
requiresClientReload ||= (setting.scope !== CONST.SETTING_SCOPES.WORLD) && setting.requiresReload;
|
||||
requiresWorldReload ||= (setting.scope === CONST.SETTING_SCOPES.WORLD) && setting.requiresReload;
|
||||
}
|
||||
if ( requiresClientReload || requiresWorldReload ) {
|
||||
await foundry.applications.settings.SettingsConfig.reloadConfirm({world: requiresWorldReload});
|
||||
}
|
||||
};
|
||||
|
||||
/** @this {OFTSettingsMenu} */
|
||||
static async #resetDefaults() {
|
||||
const form = this.form;
|
||||
for ( const settingID of this.constructor._SETTINGS ) {
|
||||
const setting = game.settings.settings.get(settingID);
|
||||
console.log({ settingID, setting });
|
||||
const input = form[settingID];
|
||||
if ( !input || !setting ) { continue };
|
||||
|
||||
if ( input.type === `checkbox` ) {
|
||||
input.checked = setting.default;
|
||||
}
|
||||
else {
|
||||
input.value = setting.default;
|
||||
};
|
||||
|
||||
input.dispatchEvent(new Event(`change`));
|
||||
}
|
||||
ui.notifications.info(`SETTINGS.ResetInfo`, {localize: true});
|
||||
};
|
||||
// #endregion Actions
|
||||
};
|
||||
8
templates/OFTSettingsMenu/footer.hbs
Normal file
8
templates/OFTSettingsMenu/footer.hbs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<footer class="form-footer">
|
||||
<button
|
||||
type="submit"
|
||||
>
|
||||
<i class="fa-solid fa-floppy-disk" inert aria-hidden="true"></i>
|
||||
{{localize "SETTINGS.Save"}}
|
||||
</button>
|
||||
</footer>
|
||||
7
templates/OFTSettingsMenu/settingsList.hbs
Normal file
7
templates/OFTSettingsMenu/settingsList.hbs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<section class="setting-list scrollable">
|
||||
{{#each settings as |setting|}}
|
||||
{{formGroup setting.field value=setting.value rootId=@root.meta.idp localize=true }}
|
||||
{{else}}
|
||||
{{localize "OFT.apps.no-settings-to-display"}}
|
||||
{{/each}}
|
||||
</section>
|
||||
Loading…
Add table
Add a link
Reference in a new issue