Fix Layout

This commit is contained in:
ChatGPT 2026-06-04 22:52:08 +02:00
parent db75a89401
commit 4a03a992d0
5 changed files with 228 additions and 76 deletions

View File

@ -86,5 +86,9 @@
"CONFIGURABLE_REACTIONS.Builder.DropSpellOnTrigger": "Zauber auf diesen Auslöser ziehen, um nach Zaubername zu filtern.", "CONFIGURABLE_REACTIONS.Builder.DropSpellOnTrigger": "Zauber auf diesen Auslöser ziehen, um nach Zaubername zu filtern.",
"CONFIGURABLE_REACTIONS.Builder.DropSpellOnAction": "Zauber hier ablegen, um ihn für diese Aktion zu speichern.", "CONFIGURABLE_REACTIONS.Builder.DropSpellOnAction": "Zauber hier ablegen, um ihn für diese Aktion zu speichern.",
"CONFIGURABLE_REACTIONS.Builder.DropSpellCreatesAction": "Ein Zauber kann direkt in den Effekt-Bereich gezogen werden und erzeugt eine passende Zauber-Aktion.", "CONFIGURABLE_REACTIONS.Builder.DropSpellCreatesAction": "Ein Zauber kann direkt in den Effekt-Bereich gezogen werden und erzeugt eine passende Zauber-Aktion.",
"CONFIGURABLE_REACTIONS.Triggers.SpellFilter": "Zauberfilter: {name}" "CONFIGURABLE_REACTIONS.Triggers.SpellFilter": "Zauberfilter: {name}",
"CONFIGURABLE_REACTIONS.Conditions.Damage.SelectedTypes": "Gewählte Schadenstypen",
"CONFIGURABLE_REACTIONS.Conditions.Damage.NoTypes": "Keine Schadenstypen ausgewählt",
"CONFIGURABLE_REACTIONS.Conditions.Damage.SelectPlaceholder": "Schadenstyp auswählen",
"CONFIGURABLE_REACTIONS.Conditions.Damage.RemoveType": "Schadenstyp entfernen: {type}"
} }

View File

@ -86,5 +86,9 @@
"CONFIGURABLE_REACTIONS.Builder.DropSpellOnTrigger": "Drop a spell on this trigger to filter by spell name.", "CONFIGURABLE_REACTIONS.Builder.DropSpellOnTrigger": "Drop a spell on this trigger to filter by spell name.",
"CONFIGURABLE_REACTIONS.Builder.DropSpellOnAction": "Drop a spell here to store it for this action.", "CONFIGURABLE_REACTIONS.Builder.DropSpellOnAction": "Drop a spell here to store it for this action.",
"CONFIGURABLE_REACTIONS.Builder.DropSpellCreatesAction": "A spell can be dropped directly into the effect area to create a configured spell action.", "CONFIGURABLE_REACTIONS.Builder.DropSpellCreatesAction": "A spell can be dropped directly into the effect area to create a configured spell action.",
"CONFIGURABLE_REACTIONS.Triggers.SpellFilter": "Spell filter: {name}" "CONFIGURABLE_REACTIONS.Triggers.SpellFilter": "Spell filter: {name}",
"CONFIGURABLE_REACTIONS.Conditions.Damage.SelectedTypes": "Selected damage types",
"CONFIGURABLE_REACTIONS.Conditions.Damage.NoTypes": "No damage types selected",
"CONFIGURABLE_REACTIONS.Conditions.Damage.SelectPlaceholder": "Select damage type",
"CONFIGURABLE_REACTIONS.Conditions.Damage.RemoveType": "Remove damage type: {type}"
} }

View File

@ -18,7 +18,8 @@ export class ConfigurableReactionsConfigApp extends HandlebarsApplicationMixin(A
classes: ["configurable-reactions", "standard-form"], classes: ["configurable-reactions", "standard-form"],
window: { window: {
title: "CONFIGURABLE_REACTIONS.App.Title", title: "CONFIGURABLE_REACTIONS.App.Title",
icon: "fa-solid fa-bolt" icon: "fa-solid fa-bolt",
resizable: true
}, },
position: { position: {
width: 1040, width: 1040,
@ -33,6 +34,7 @@ export class ConfigurableReactionsConfigApp extends HandlebarsApplicationMixin(A
removeAction: ConfigurableReactionsConfigApp.#onRemoveAction, removeAction: ConfigurableReactionsConfigApp.#onRemoveAction,
saveReactionBasics: ConfigurableReactionsConfigApp.#onSaveReactionBasics, saveReactionBasics: ConfigurableReactionsConfigApp.#onSaveReactionBasics,
clearTriggerSpell: ConfigurableReactionsConfigApp.#onClearTriggerSpell, clearTriggerSpell: ConfigurableReactionsConfigApp.#onClearTriggerSpell,
removeDamageType: ConfigurableReactionsConfigApp.#onRemoveDamageType,
clearActionSpell: ConfigurableReactionsConfigApp.#onClearActionSpell, clearActionSpell: ConfigurableReactionsConfigApp.#onClearActionSpell,
removeActionStatus: ConfigurableReactionsConfigApp.#onRemoveActionStatus removeActionStatus: ConfigurableReactionsConfigApp.#onRemoveActionStatus
} }
@ -67,7 +69,12 @@ export class ConfigurableReactionsConfigApp extends HandlebarsApplicationMixin(A
const activeTrigger = triggerPalette.find(t => t.type === selectedReaction?.trigger?.type) ?? null; const activeTrigger = triggerPalette.find(t => t.type === selectedReaction?.trigger?.type) ?? null;
const triggerSpellName = selectedReaction?.trigger?.spell?.itemName || selectedReaction?.trigger?.spell?.itemUuid || ""; const triggerSpellName = selectedReaction?.trigger?.spell?.itemName || selectedReaction?.trigger?.spell?.itemUuid || "";
const activeTriggerAcceptsSpell = isSpellTrigger(selectedReaction?.trigger?.type); const activeTriggerAcceptsSpell = isSpellTrigger(selectedReaction?.trigger?.type);
const activeTriggerIsDamage = selectedReaction?.trigger?.type === TRIGGER_TYPES.DAMAGE_RECEIVED;
const statusEffects = getStatusEffectOptions(); const statusEffects = getStatusEffectOptions();
const damageTypes = getDamageTypeOptions();
const selectedDamageTypeIds = Array.from(new Set(selectedReaction?.conditions?.damage?.types ?? []));
const selectedDamageTypes = selectedDamageTypeIds.map(typeId => resolveDamageType(typeId, damageTypes));
const availableDamageTypes = damageTypes.filter(type => !selectedDamageTypeIds.includes(type.id));
const visualActions = (selectedReaction?.actions ?? []).map((action, index) => { const visualActions = (selectedReaction?.actions ?? []).map((action, index) => {
const paletteEntry = actionPalette.find(p => p.type === action.type); const paletteEntry = actionPalette.find(p => p.type === action.type);
const selectedStatusIds = Array.from(new Set(action.statuses ?? [])); const selectedStatusIds = Array.from(new Set(action.statuses ?? []));
@ -101,9 +108,12 @@ export class ConfigurableReactionsConfigApp extends HandlebarsApplicationMixin(A
actionPalette, actionPalette,
activeTrigger, activeTrigger,
activeTriggerAcceptsSpell, activeTriggerAcceptsSpell,
activeTriggerIsDamage,
triggerSpellName, triggerSpellName,
visualActions, visualActions,
statusEffects statusEffects,
selectedDamageTypes,
availableDamageTypes
}; };
} }
@ -115,7 +125,10 @@ export class ConfigurableReactionsConfigApp extends HandlebarsApplicationMixin(A
this.render({ force: true }); this.render({ force: true });
}); });
this.#activateJsonEditor();
this.#activateBasicsSync();
this.#activateStatusSelectors(); this.#activateStatusSelectors();
this.#activateDamageTypeSelector();
this.#activateDragAndDrop(); this.#activateDragAndDrop();
} }
@ -130,6 +143,41 @@ export class ConfigurableReactionsConfigApp extends HandlebarsApplicationMixin(A
}); });
} }
} }
#activateJsonEditor() {
const textarea = this.element.querySelector("[name='reactionJson']");
if (!textarea) return;
textarea.addEventListener("blur", async () => {
if (document.activeElement === textarea) return;
await this.#saveReactionJsonFromTextarea({ render: true });
});
}
#activateBasicsSync() {
const nameInput = this.element.querySelector("[name='reactionName']");
const enabledInput = this.element.querySelector("[name='reactionEnabled']");
nameInput?.addEventListener("input", () => this.#syncJsonFromDesignerForm());
nameInput?.addEventListener("change", async () => this.#saveBasicsFromDesignerForm());
nameInput?.addEventListener("blur", async () => this.#saveBasicsFromDesignerForm());
enabledInput?.addEventListener("change", async () => {
this.#syncJsonFromDesignerForm();
await this.#saveBasicsFromDesignerForm();
});
}
#activateDamageTypeSelector() {
const select = this.element.querySelector("[data-cr-damage-type-select]");
if (!select) return;
select.addEventListener("change", async event => {
const damageType = event.currentTarget.value;
if (!damageType) return;
await this.#addDamageType(damageType);
});
}
#activateDragAndDrop() { #activateDragAndDrop() {
for (const draggable of this.element.querySelectorAll("[data-cr-drag]")) { for (const draggable of this.element.querySelectorAll("[data-cr-drag]")) {
@ -208,29 +256,7 @@ export class ConfigurableReactionsConfigApp extends HandlebarsApplicationMixin(A
} }
static async #onSaveReactionJson(event, target) { static async #onSaveReactionJson(event, target) {
const textarea = this.element.querySelector("[name='reactionJson']"); await this.#saveReactionJsonFromTextarea({ render: true });
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");
reaction.actions ??= [];
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) { static async #onDeleteReaction(event, target) {
@ -308,16 +334,21 @@ export class ConfigurableReactionsConfigApp extends HandlebarsApplicationMixin(A
}); });
} }
static async #onSaveReactionBasics(event, target) { static async #onRemoveDamageType(event, target) {
const name = this.element.querySelector("[name='reactionName']")?.value?.trim(); const damageType = target.dataset.damageType;
const enabled = this.element.querySelector("[name='reactionEnabled']")?.checked === true; if (!damageType) return;
await this.#mutateSelectedReaction(reaction => { await this.#mutateSelectedReaction(reaction => {
if (name) reaction.name = name; reaction.conditions ??= {};
reaction.enabled = enabled; reaction.conditions.damage ??= {};
reaction.conditions.damage.types = (reaction.conditions.damage.types ?? []).filter(type => type !== damageType);
}); });
} }
static async #onSaveReactionBasics(event, target) {
await this.#saveBasicsFromDesignerForm();
}
async #setTrigger(triggerType) { async #setTrigger(triggerType) {
await this.#mutateSelectedReaction(reaction => { await this.#mutateSelectedReaction(reaction => {
reaction.trigger ??= {}; reaction.trigger ??= {};
@ -361,12 +392,77 @@ export class ConfigurableReactionsConfigApp extends HandlebarsApplicationMixin(A
}); });
} }
async #addDamageType(damageType) {
await this.#mutateSelectedReaction(reaction => {
reaction.conditions ??= {};
reaction.conditions.damage ??= { enabled: true, amountMode: "damageOnly", types: [], typeMode: "any", minAmount: 1 };
reaction.conditions.damage.types ??= [];
if (!reaction.conditions.damage.types.includes(damageType)) reaction.conditions.damage.types.push(damageType);
});
}
async #saveReactionJsonFromTextarea({ render = true } = {}) {
const textarea = this.element.querySelector("[name='reactionJson']");
if (!textarea) return false;
let reaction;
try {
reaction = JSON.parse(textarea.value);
} catch (error) {
ui.notifications.error(game.i18n.localize("CONFIGURABLE_REACTIONS.Errors.InvalidJson"));
return false;
}
if (!reaction.id) reaction.id = foundry.utils.randomID();
if (!reaction.name) reaction.name = game.i18n.localize("CONFIGURABLE_REACTIONS.Reactions.NewReaction");
reaction.actions ??= [];
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;
if (render) this.render({ force: true });
return true;
}
async #saveBasicsFromDesignerForm() {
const name = this.element.querySelector("[name='reactionName']")?.value?.trim();
const enabled = this.element.querySelector("[name='reactionEnabled']")?.checked === true;
await this.#mutateSelectedReaction(reaction => {
if (name) reaction.name = name;
reaction.enabled = enabled;
});
}
#syncJsonFromDesignerForm() {
const reactions = foundry.utils.deepClone(game.settings.get(MODULE_ID, SETTINGS.REACTIONS) ?? []);
const reaction = reactions.find(r => r.id === this._selectedReactionId);
if (!reaction) return;
const name = this.element.querySelector("[name='reactionName']")?.value?.trim();
const enabled = this.element.querySelector("[name='reactionEnabled']")?.checked === true;
if (name) reaction.name = name;
reaction.enabled = enabled;
this.#updateJsonTextarea(reaction);
}
#updateJsonTextarea(reaction) {
const textarea = this.element.querySelector("[name='reactionJson']");
if (!textarea || document.activeElement === textarea) return;
textarea.value = JSON.stringify(reaction, null, 2);
}
async #mutateSelectedReaction(mutator) { async #mutateSelectedReaction(mutator) {
const reactions = foundry.utils.deepClone(game.settings.get(MODULE_ID, SETTINGS.REACTIONS) ?? []); const reactions = foundry.utils.deepClone(game.settings.get(MODULE_ID, SETTINGS.REACTIONS) ?? []);
const reaction = reactions.find(r => r.id === this._selectedReactionId); const reaction = reactions.find(r => r.id === this._selectedReactionId);
if (!reaction) return; if (!reaction) return;
mutator(reaction); mutator(reaction);
this.#updateJsonTextarea(reaction);
await game.settings.set(MODULE_ID, SETTINGS.REACTIONS, reactions); await game.settings.set(MODULE_ID, SETTINGS.REACTIONS, reactions);
this.render({ force: true }); this.render({ force: true });
} }
@ -495,6 +591,25 @@ function resolveStatusEffect(statusId, statusEffects = getStatusEffectOptions())
}; };
} }
function getDamageTypeOptions() {
const damageTypes = CONFIG.DND5E?.damageTypes ?? {};
const entries = damageTypes instanceof Map
? Array.from(damageTypes.entries())
: Object.entries(damageTypes);
return entries.map(([id, value]) => ({
id,
label: game.i18n.localize(value?.label ?? value ?? id)
})).sort((a, b) => a.label.localeCompare(b.label, game.i18n.lang));
}
function resolveDamageType(typeId, damageTypes = getDamageTypeOptions()) {
return damageTypes.find(type => type.id === typeId) ?? {
id: typeId,
label: typeId
};
}
function summarizeAction(action, statusEffects = getStatusEffectOptions()) { function summarizeAction(action, statusEffects = getStatusEffectOptions()) {
switch (action.type) { switch (action.type) {
case ACTION_TYPES.TELEPORT: case ACTION_TYPES.TELEPORT:

View File

@ -15,7 +15,7 @@
.configurable-reactions .cr-grid { .configurable-reactions .cr-grid {
display: grid; display: grid;
grid-template-columns: 310px 1fr; grid-template-columns: 33% 67%;
gap: 1rem; gap: 1rem;
min-height: 0; min-height: 0;
height: 100%; height: 100%;
@ -27,6 +27,12 @@
overflow: auto; overflow: auto;
} }
.configurable-reactions .cr-main {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.configurable-reactions .cr-button-column { .configurable-reactions .cr-button-column {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -86,7 +92,9 @@
display: grid; display: grid;
grid-template-columns: 1fr 1.35fr; grid-template-columns: 1fr 1.35fr;
gap: 0.75rem; gap: 0.75rem;
margin-bottom: 0.75rem; flex: 0 0 40%;
min-height: 220px;
overflow: auto;
} }
.configurable-reactions .cr-card, .configurable-reactions .cr-card,
@ -100,7 +108,7 @@
.configurable-reactions .cr-basics { .configurable-reactions .cr-basics {
display: grid; display: grid;
grid-template-columns: 1fr auto auto; grid-template-columns: 1fr auto;
align-items: end; align-items: end;
gap: 0.75rem; gap: 0.75rem;
margin-bottom: 0.75rem; margin-bottom: 0.75rem;
@ -157,15 +165,17 @@
opacity: 0.8; opacity: 0.8;
} }
.configurable-reactions .cr-main textarea[name="reactionJson"] { .configurable-reactions .cr-json-editor {
width: 100%; flex: 1 1 auto;
min-height: 340px; min-height: 0;
font-family: var(--font-monospace, monospace);
resize: vertical;
} }
.configurable-reactions .cr-reaction-summary { .configurable-reactions .cr-main textarea[name="reactionJson"] {
margin-bottom: 0.75rem; width: 100%;
height: 100%;
min-height: 180px;
font-family: var(--font-monospace, monospace);
resize: none;
} }
.configurable-reactions .form-group.stacked { .configurable-reactions .form-group.stacked {
@ -198,21 +208,23 @@
justify-content: end; justify-content: end;
} }
.configurable-reactions .cr-status-builder { .configurable-reactions .cr-status-builder,
.configurable-reactions .cr-damage-builder {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.35rem; gap: 0.35rem;
margin-top: 0.5rem; margin-top: 0.5rem;
} }
.configurable-reactions .cr-status-label { .configurable-reactions .cr-chip-label {
opacity: 0.85; opacity: 0.85;
font-weight: 600; font-weight: 600;
} }
.configurable-reactions .cr-status-chip-list { .configurable-reactions .cr-chip-list {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: flex-end;
gap: 0.25rem; gap: 0.25rem;
min-height: 1.6rem; min-height: 1.6rem;
padding: 0.25rem; padding: 0.25rem;
@ -221,38 +233,45 @@
background: rgb(0 0 0 / 0.08); background: rgb(0 0 0 / 0.08);
} }
.configurable-reactions .cr-status-chip { .configurable-reactions .cr-chip {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 0.3rem; gap: 0.3rem;
max-width: 100%; max-width: 100%;
padding: 0.15rem 0.25rem 0.15rem 0.35rem; padding: 0.12rem 0.2rem 0.12rem 0.35rem;
border: 1px solid var(--color-border-light-secondary, #777); border: 1px solid var(--color-border-light-secondary, #777);
border-radius: 999px; border-radius: 999px;
background: rgb(0 0 0 / 0.16); background: rgb(0 0 0 / 0.16);
font-size: 0.8rem; font-size: 70%;
} }
.configurable-reactions .cr-status-chip img { .configurable-reactions .cr-chip img {
width: 16px; width: 14px;
height: 16px; height: 14px;
border: none; border: none;
object-fit: contain; object-fit: contain;
} }
.configurable-reactions .cr-status-chip span { .configurable-reactions .cr-chip span {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.configurable-reactions .cr-status-chip button { .configurable-reactions .cr-chip button {
width: 1.35rem; width: 1rem;
min-height: 1.35rem; min-height: 1rem;
height: 1rem;
padding: 0; padding: 0;
line-height: 1; line-height: 1;
border: none;
box-shadow: none;
background: transparent;
font-size: 0.85em;
} }
.configurable-reactions .cr-status-builder select { .configurable-reactions .cr-status-builder select,
.configurable-reactions .cr-damage-builder select {
width: 100%; width: 100%;
} }

View File

@ -78,9 +78,6 @@
<label>{{localize "CONFIGURABLE_REACTIONS.Reactions.Enabled"}}</label> <label>{{localize "CONFIGURABLE_REACTIONS.Reactions.Enabled"}}</label>
<input type="checkbox" name="reactionEnabled" {{#if selectedReaction.enabled}}checked{{/if}}> <input type="checkbox" name="reactionEnabled" {{#if selectedReaction.enabled}}checked{{/if}}>
</div> </div>
<button type="button" data-action="saveReactionBasics">
<i class="fa-solid fa-floppy-disk"></i> {{localize "CONFIGURABLE_REACTIONS.Common.Apply"}}
</button>
</section> </section>
<section class="cr-builder-grid"> <section class="cr-builder-grid">
@ -99,6 +96,32 @@
<small class="cr-inline-drop">{{localize "CONFIGURABLE_REACTIONS.Builder.DropSpellOnTrigger"}}</small> <small class="cr-inline-drop">{{localize "CONFIGURABLE_REACTIONS.Builder.DropSpellOnTrigger"}}</small>
{{/if}} {{/if}}
{{/if}} {{/if}}
{{#if activeTriggerIsDamage}}
<div class="cr-damage-builder">
<small class="cr-chip-label">{{localize "CONFIGURABLE_REACTIONS.Conditions.Damage.SelectedTypes"}}</small>
<div class="cr-chip-list cr-damage-chip-list">
{{#if selectedDamageTypes.length}}
{{#each selectedDamageTypes}}
<span class="cr-chip cr-damage-chip" title="{{this.label}}">
<span>{{this.label}}</span>
<button type="button" data-action="removeDamageType" data-damage-type="{{this.id}}" title="{{localize 'CONFIGURABLE_REACTIONS.Conditions.Damage.RemoveType' type=this.label}}">
<i class="fa-solid fa-xmark"></i>
</button>
</span>
{{/each}}
{{else}}
<small class="cr-inline-drop">{{localize "CONFIGURABLE_REACTIONS.Conditions.Damage.NoTypes"}}</small>
{{/if}}
</div>
<select data-cr-damage-type-select>
<option value="">{{localize "CONFIGURABLE_REACTIONS.Conditions.Damage.SelectPlaceholder"}}</option>
{{#each availableDamageTypes}}
<option value="{{this.id}}">{{this.label}}</option>
{{/each}}
</select>
</div>
{{/if}}
</div> </div>
{{#if triggerSpellName}} {{#if triggerSpellName}}
<button type="button" data-action="clearTriggerSpell" title="{{localize 'CONFIGURABLE_REACTIONS.Common.Remove'}}"> <button type="button" data-action="clearTriggerSpell" title="{{localize 'CONFIGURABLE_REACTIONS.Common.Remove'}}">
@ -132,11 +155,11 @@
{{/if}} {{/if}}
{{#if this.isStatusAction}} {{#if this.isStatusAction}}
<div class="cr-status-builder"> <div class="cr-status-builder">
<small class="cr-status-label">{{localize "CONFIGURABLE_REACTIONS.Actions.ApplyStatus.SelectedStatuses"}}</small> <small class="cr-chip-label">{{localize "CONFIGURABLE_REACTIONS.Actions.ApplyStatus.SelectedStatuses"}}</small>
<div class="cr-status-chip-list"> <div class="cr-chip-list cr-status-chip-list">
{{#if this.selectedStatuses.length}} {{#if this.selectedStatuses.length}}
{{#each this.selectedStatuses}} {{#each this.selectedStatuses}}
<span class="cr-status-chip" title="{{this.label}}"> <span class="cr-chip cr-status-chip" title="{{this.label}}">
<img src="{{this.icon}}" alt=""> <img src="{{this.icon}}" alt="">
<span>{{this.label}}</span> <span>{{this.label}}</span>
<button type="button" data-action="removeActionStatus" data-action-index="{{../index}}" data-status-id="{{this.id}}" title="{{localize 'CONFIGURABLE_REACTIONS.Actions.ApplyStatus.RemoveStatus' status=this.label}}"> <button type="button" data-action="removeActionStatus" data-action-index="{{../index}}" data-status-id="{{this.id}}" title="{{localize 'CONFIGURABLE_REACTIONS.Actions.ApplyStatus.RemoveStatus' status=this.label}}">
@ -176,23 +199,10 @@
</div> </div>
</section> </section>
<div class="cr-reaction-summary"> <div class="form-group stacked cr-json-editor">
<h3>{{localize "CONFIGURABLE_REACTIONS.Reactions.JsonPreview"}}</h3>
<p><strong>ID:</strong> <code>{{selectedReaction.id}}</code></p>
<p><strong>{{localize "CONFIGURABLE_REACTIONS.Trigger.Label"}}:</strong> {{selectedReaction.trigger.type}}</p>
<p><strong>{{localize "CONFIGURABLE_REACTIONS.Actions.Title"}}:</strong> {{selectedReaction.actions.length}}</p>
</div>
<div class="form-group stacked">
<label>{{localize "CONFIGURABLE_REACTIONS.Reactions.JsonEditor"}}</label> <label>{{localize "CONFIGURABLE_REACTIONS.Reactions.JsonEditor"}}</label>
<textarea name="reactionJson" spellcheck="false">{{selectedReactionJson}}</textarea> <textarea name="reactionJson" spellcheck="false">{{selectedReactionJson}}</textarea>
</div> </div>
<footer class="cr-footer">
<button type="button" data-action="saveReactionJson">
<i class="fa-solid fa-floppy-disk"></i> {{localize "CONFIGURABLE_REACTIONS.Common.Save"}}
</button>
</footer>
{{else}} {{else}}
<p>{{localize "CONFIGURABLE_REACTIONS.Reactions.None"}}</p> <p>{{localize "CONFIGURABLE_REACTIONS.Reactions.None"}}</p>
{{/if}} {{/if}}