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: `

${game.i18n.localize("CONFIGURABLE_REACTIONS.Reactions.DeleteConfirm")}

` }); 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 }); } }