configurable-reactions/scripts/apps/reaction-config-app.js
2026-06-04 18:21:46 +02:00

178 lines
6.4 KiB
JavaScript
Executable File

import { MODULE_ID, SETTINGS, DEFAULT_REACTION, ACTION_TYPES, TRIGGER_TYPES } from "../constants.js";
import { assignReactionToSelectedTokens, removeAssignment } from "./assignment-manager.js";
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
export class ConfigurableReactionsConfigApp extends HandlebarsApplicationMixin(ApplicationV2) {
static DEFAULT_OPTIONS = {
id: "configurable-reactions-config",
tag: "section",
classes: ["configurable-reactions", "standard-form"],
window: {
title: "CONFIGURABLE_REACTIONS.App.Title",
icon: "fa-solid fa-bolt"
},
position: {
width: 900,
height: 760
},
actions: {
createReaction: ConfigurableReactionsConfigApp.#onCreateReaction,
saveReactionJson: ConfigurableReactionsConfigApp.#onSaveReactionJson,
deleteReaction: ConfigurableReactionsConfigApp.#onDeleteReaction,
assignSelectedTokens: ConfigurableReactionsConfigApp.#onAssignSelectedTokens,
removeAssignment: ConfigurableReactionsConfigApp.#onRemoveAssignment,
addTeleportAction: ConfigurableReactionsConfigApp.#onAddTeleportAction,
addStatusAction: ConfigurableReactionsConfigApp.#onAddStatusAction
}
};
static PARTS = {
main: {
template: `modules/${MODULE_ID}/templates/reaction-config.hbs`
}
};
async _prepareContext(options) {
const reactions = foundry.utils.deepClone(game.settings.get(MODULE_ID, SETTINGS.REACTIONS) ?? []);
const assignments = foundry.utils.deepClone(game.settings.get(MODULE_ID, SETTINGS.ASSIGNMENTS) ?? []);
const selectedReactionId = this._selectedReactionId ?? reactions[0]?.id ?? null;
const selectedReaction = reactions.find(r => r.id === selectedReactionId) ?? reactions[0] ?? null;
this._selectedReactionId = selectedReaction?.id ?? null;
return {
reactions,
assignments: assignments.map(a => ({
...a,
reactionName: reactions.find(r => r.id === a.reactionId)?.name ?? a.reactionId
})),
selectedReaction,
selectedReactionJson: selectedReaction ? JSON.stringify(selectedReaction, null, 2) : "",
triggerTypes: TRIGGER_TYPES,
actionTypes: ACTION_TYPES,
statusEffects: CONFIG.statusEffects.map(s => ({ id: s.id, name: game.i18n.localize(s.name ?? s.id) }))
};
}
_onRender(context, options) {
super._onRender(context, options);
this.element.querySelector("[name='selectedReactionId']")?.addEventListener("change", event => {
this._selectedReactionId = event.currentTarget.value;
this.render({ force: true });
});
}
static async #onCreateReaction(event, target) {
const reactions = foundry.utils.deepClone(game.settings.get(MODULE_ID, SETTINGS.REACTIONS) ?? []);
const reaction = foundry.utils.deepClone(DEFAULT_REACTION);
reaction.id = foundry.utils.randomID();
reaction.name = game.i18n.localize("CONFIGURABLE_REACTIONS.Reactions.NewReaction");
reactions.push(reaction);
await game.settings.set(MODULE_ID, SETTINGS.REACTIONS, reactions);
this._selectedReactionId = reaction.id;
this.render({ force: true });
}
static async #onSaveReactionJson(event, target) {
const textarea = this.element.querySelector("[name='reactionJson']");
if (!textarea) return;
let reaction;
try {
reaction = JSON.parse(textarea.value);
} catch (error) {
ui.notifications.error(game.i18n.localize("CONFIGURABLE_REACTIONS.Errors.InvalidJson"));
return;
}
if (!reaction.id) reaction.id = foundry.utils.randomID();
if (!reaction.name) reaction.name = game.i18n.localize("CONFIGURABLE_REACTIONS.Reactions.NewReaction");
const reactions = foundry.utils.deepClone(game.settings.get(MODULE_ID, SETTINGS.REACTIONS) ?? []);
const index = reactions.findIndex(r => r.id === reaction.id);
if (index >= 0) reactions[index] = reaction;
else reactions.push(reaction);
await game.settings.set(MODULE_ID, SETTINGS.REACTIONS, reactions);
this._selectedReactionId = reaction.id;
this.render({ force: true });
}
static async #onDeleteReaction(event, target) {
const reactionId = this.element.querySelector("[name='selectedReactionId']")?.value;
if (!reactionId) return;
const confirmed = await foundry.applications.api.DialogV2.confirm({
window: { title: game.i18n.localize("CONFIGURABLE_REACTIONS.Reactions.Delete") },
content: `<p>${game.i18n.localize("CONFIGURABLE_REACTIONS.Reactions.DeleteConfirm")}</p>`
});
if (!confirmed) return;
const reactions = foundry.utils.deepClone(game.settings.get(MODULE_ID, SETTINGS.REACTIONS) ?? [])
.filter(r => r.id !== reactionId);
const assignments = foundry.utils.deepClone(game.settings.get(MODULE_ID, SETTINGS.ASSIGNMENTS) ?? [])
.filter(a => a.reactionId !== reactionId);
await game.settings.set(MODULE_ID, SETTINGS.REACTIONS, reactions);
await game.settings.set(MODULE_ID, SETTINGS.ASSIGNMENTS, assignments);
this._selectedReactionId = reactions[0]?.id ?? null;
this.render({ force: true });
}
static async #onAssignSelectedTokens(event, target) {
const reactionId = this.element.querySelector("[name='selectedReactionId']")?.value;
await assignReactionToSelectedTokens(reactionId);
this.render({ force: true });
}
static async #onRemoveAssignment(event, target) {
const assignmentId = target.dataset.assignmentId;
await removeAssignment(assignmentId);
this.render({ force: true });
}
static async #onAddTeleportAction(event, target) {
await this.#addAction({
id: foundry.utils.randomID(),
type: ACTION_TYPES.TELEPORT,
enabled: true,
radius: 30,
askOwner: false,
snapToGrid: true,
maxAttempts: 80,
consumeOnFailure: false
});
}
static async #onAddStatusAction(event, target) {
await this.#addAction({
id: foundry.utils.randomID(),
type: ACTION_TYPES.APPLY_STATUS,
enabled: true,
statuses: [],
duration: {
type: "rounds",
value: 1
}
});
}
async #addAction(action) {
const reactions = foundry.utils.deepClone(game.settings.get(MODULE_ID, SETTINGS.REACTIONS) ?? []);
const reaction = reactions.find(r => r.id === this._selectedReactionId);
if (!reaction) return;
reaction.actions ??= [];
reaction.actions.push(action);
await game.settings.set(MODULE_ID, SETTINGS.REACTIONS, reactions);
this.render({ force: true });
}
}