233 lines
7.9 KiB
JavaScript
233 lines
7.9 KiB
JavaScript
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
|
|
|
let registeredKeys = new Set();
|
|
|
|
/**
|
|
* Apply the custom attribution sources to the system CONFIG object.
|
|
*/
|
|
export function applyAttributionSources() {
|
|
if (!CONFIG.DH?.GENERAL?.attributionSources) return;
|
|
|
|
// Remove previously registered custom sources
|
|
for (const key of registeredKeys) {
|
|
delete CONFIG.DH.GENERAL.attributionSources[key];
|
|
}
|
|
registeredKeys.clear();
|
|
|
|
// Get current custom sources from setting
|
|
const customSources = game.settings.get("dh-attribution-sources", "customSources") || [];
|
|
|
|
// Inject custom sources
|
|
for (const source of customSources) {
|
|
if (!source.id) continue;
|
|
const key = `custom_${source.id}`;
|
|
|
|
CONFIG.DH.GENERAL.attributionSources[key] = {
|
|
label: source.label || source.id,
|
|
values: (source.values || []).map(val => ({
|
|
label: val.label || "",
|
|
hint: val.hint || ""
|
|
}))
|
|
};
|
|
registeredKeys.add(key);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Custom application sheet to add, update, and remove custom attribution sources.
|
|
*/
|
|
export class CustomAttributionSourcesForm extends HandlebarsApplicationMixin(ApplicationV2) {
|
|
constructor(options = {}) {
|
|
super(options);
|
|
const saved = game.settings.get("dh-attribution-sources", "customSources") || [];
|
|
this.sources = foundry.utils.deepClone(saved);
|
|
}
|
|
|
|
static DEFAULT_OPTIONS = {
|
|
tag: 'form',
|
|
id: 'dh-attribution-sources-settings',
|
|
classes: ['dh-attribution-sources', 'dialog', 'settings-app'],
|
|
position: { width: 620, height: 650 },
|
|
window: {
|
|
title: "Custom Attribution Sources",
|
|
icon: 'fa-solid fa-signature',
|
|
resizable: true
|
|
},
|
|
actions: {
|
|
addSource: CustomAttributionSourcesForm.#onAddSource,
|
|
removeSource: CustomAttributionSourcesForm.#onRemoveSource,
|
|
addValue: CustomAttributionSourcesForm.#onAddValue,
|
|
removeValue: CustomAttributionSourcesForm.#onRemoveValue,
|
|
reset: CustomAttributionSourcesForm.#onReset,
|
|
save: CustomAttributionSourcesForm.#onSave
|
|
},
|
|
form: {
|
|
handler: CustomAttributionSourcesForm.#onSubmit,
|
|
submitOnChange: false,
|
|
closeOnSubmit: false
|
|
}
|
|
};
|
|
|
|
static PARTS = {
|
|
main: {
|
|
template: 'modules/dh-attribution-sources/templates/settings.hbs',
|
|
scrollable: ['.sources-list']
|
|
},
|
|
footer: {
|
|
template: 'modules/dh-attribution-sources/templates/footer.hbs'
|
|
}
|
|
};
|
|
|
|
async _prepareContext(options) {
|
|
const context = await super._prepareContext(options);
|
|
context.sources = this.sources;
|
|
return context;
|
|
}
|
|
|
|
static #serializeForm(target) {
|
|
const form = target.closest('form');
|
|
const formData = new foundry.applications.ux.FormDataExtended(form);
|
|
const data = foundry.utils.expandObject(formData.object);
|
|
|
|
// Convert dot-notation structures back to nested arrays
|
|
const sources = Object.values(data.sources || {}).map(src => {
|
|
const values = Object.values(src.values || {}).map(val => ({
|
|
label: val.label || "",
|
|
hint: val.hint || ""
|
|
}));
|
|
return {
|
|
id: src.id || "",
|
|
label: src.label || "",
|
|
values: values
|
|
};
|
|
});
|
|
|
|
return sources;
|
|
}
|
|
|
|
static async #onAddSource(event, target) {
|
|
const sources = CustomAttributionSourcesForm.#serializeForm(target);
|
|
sources.push({
|
|
id: `group-${foundry.utils.randomID(4)}`,
|
|
label: "New Source Group",
|
|
values: [{ label: "", hint: "" }]
|
|
});
|
|
this.sources = sources;
|
|
this.render();
|
|
}
|
|
|
|
static async #onRemoveSource(event, target) {
|
|
const sources = CustomAttributionSourcesForm.#serializeForm(target);
|
|
const index = parseInt(target.dataset.sourceIndex);
|
|
if (!isNaN(index)) {
|
|
sources.splice(index, 1);
|
|
}
|
|
this.sources = sources;
|
|
this.render();
|
|
}
|
|
|
|
static async #onAddValue(event, target) {
|
|
const sources = CustomAttributionSourcesForm.#serializeForm(target);
|
|
const index = parseInt(target.dataset.sourceIndex);
|
|
if (!isNaN(index)) {
|
|
sources[index].values.push({ label: "", hint: "" });
|
|
}
|
|
this.sources = sources;
|
|
this.render();
|
|
}
|
|
|
|
static async #onRemoveValue(event, target) {
|
|
const sources = CustomAttributionSourcesForm.#serializeForm(target);
|
|
const sourceIndex = parseInt(target.dataset.sourceIndex);
|
|
const valueIndex = parseInt(target.dataset.valueIndex);
|
|
if (!isNaN(sourceIndex) && !isNaN(valueIndex)) {
|
|
sources[sourceIndex].values.splice(valueIndex, 1);
|
|
}
|
|
this.sources = sources;
|
|
this.render();
|
|
}
|
|
|
|
static async #onReset(event, target) {
|
|
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
|
window: {
|
|
title: "Reset Custom Attribution Sources"
|
|
},
|
|
content: "Are you sure you want to discard your unsaved changes and reload settings?"
|
|
});
|
|
if (!confirmed) return;
|
|
|
|
const saved = game.settings.get("dh-attribution-sources", "customSources") || [];
|
|
this.sources = foundry.utils.deepClone(saved);
|
|
this.render();
|
|
}
|
|
|
|
static async #onSave(event, target) {
|
|
const sources = CustomAttributionSourcesForm.#serializeForm(target);
|
|
|
|
// Validation
|
|
const ids = new Set();
|
|
for (const src of sources) {
|
|
const cleanId = src.id.trim();
|
|
if (!cleanId) {
|
|
ui.notifications.error("All attribution source groups must have a non-empty ID.");
|
|
return;
|
|
}
|
|
if (ids.has(cleanId)) {
|
|
ui.notifications.error(`Duplicate ID found: "${cleanId}". Each attribution source group must have a unique ID.`);
|
|
return;
|
|
}
|
|
ids.add(cleanId);
|
|
|
|
src.id = cleanId;
|
|
src.label = src.label.trim() || cleanId;
|
|
|
|
if (src.values.length === 0) {
|
|
ui.notifications.error(`Group "${src.label}" must contain at least one attribution value.`);
|
|
return;
|
|
}
|
|
|
|
for (const val of src.values) {
|
|
val.label = val.label.trim();
|
|
val.hint = val.hint.trim();
|
|
if (!val.label) {
|
|
ui.notifications.error(`Group "${src.label}" has an empty attribution label.`);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
await game.settings.set("dh-attribution-sources", "customSources", sources);
|
|
applyAttributionSources();
|
|
ui.notifications.info("Custom attribution sources saved successfully.");
|
|
this.close();
|
|
}
|
|
|
|
static async #onSubmit(event, form, formData) {
|
|
// Form submit falls through; handled by onSave button click
|
|
}
|
|
}
|
|
|
|
// Hook into initialization to register the settings and configuration panel
|
|
Hooks.once('init', () => {
|
|
game.settings.register("dh-attribution-sources", "customSources", {
|
|
name: "Custom Attribution Sources List",
|
|
scope: "world",
|
|
config: false,
|
|
type: Array,
|
|
default: []
|
|
});
|
|
|
|
game.settings.registerMenu("dh-attribution-sources", "manageSources", {
|
|
name: "Custom Attribution Sources",
|
|
label: "Manage Custom Sources",
|
|
hint: "Configure your custom item attribution sources.",
|
|
icon: "fas fa-signature",
|
|
type: CustomAttributionSourcesForm,
|
|
restricted: true
|
|
});
|
|
});
|
|
|
|
// Setup hook to run once the system has registered its configuration
|
|
Hooks.on('setup', () => {
|
|
applyAttributionSources();
|
|
});
|