Initial Foundry module scaffold
This commit is contained in:
commit
927ea528b7
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
node_modules/
|
||||
dist/
|
||||
*.log
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Florian Zumpe
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
65
README.md
Normal file
65
README.md
Normal file
@ -0,0 +1,65 @@
|
||||
# Configurable Reactions
|
||||
|
||||
**Configurable Reactions** is a Foundry VTT 14 module for configurable automatic reactions assigned to actors or concrete token instances.
|
||||
|
||||
The module is designed as a generic reaction engine:
|
||||
|
||||
```text
|
||||
Trigger -> Conditions -> Consumption -> Actions
|
||||
```
|
||||
|
||||
## Current scope
|
||||
|
||||
This repository is an initial module skeleton. It includes:
|
||||
|
||||
- World-level reaction storage
|
||||
- World-level assignment storage
|
||||
- A GM configuration app
|
||||
- Assignment of a configured reaction to selected tokens
|
||||
- Linked-token handling: linked tokens assign the reaction to the Actor
|
||||
- Unlinked-token handling: unlinked tokens assign the reaction to the TokenDocument
|
||||
- Managed flags and optional managed ActiveEffects
|
||||
- A reaction engine skeleton
|
||||
- A damage-received trigger skeleton for dnd5e
|
||||
- Action handlers for:
|
||||
- applying statuses
|
||||
- teleporting a token
|
||||
- opening/using an inventory item placeholder
|
||||
- casting a spell placeholder
|
||||
|
||||
## Important design rules
|
||||
|
||||
For teleport actions:
|
||||
|
||||
- `askOwner === true`: the owner should choose the target, then the GM validates it.
|
||||
- `askOwner === false`: the GM chooses a random valid target.
|
||||
- The target must always be reachable.
|
||||
- The target must never be blocked by movement walls.
|
||||
- The target must never be occupied by another token.
|
||||
- The target must remain inside scene bounds.
|
||||
- If no valid random target is found within `maxAttempts`, the teleport fails.
|
||||
- `consumeOnFailure` is configurable.
|
||||
|
||||
## Installation during development
|
||||
|
||||
Clone or copy this repository into Foundry's `Data/modules` directory:
|
||||
|
||||
```bash
|
||||
cd /path/to/FoundryVTT/Data/modules
|
||||
git clone <your-remote-url> configurable-reactions
|
||||
```
|
||||
|
||||
Restart Foundry and enable **Configurable Reactions** in the world.
|
||||
|
||||
## Development workflow
|
||||
|
||||
This repository is initialized as a Git repository. You can set a remote URL afterwards:
|
||||
|
||||
```bash
|
||||
git remote add origin <your-remote-url>
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
## Status
|
||||
|
||||
Initial development scaffold. Not yet a production-ready automation module.
|
||||
41
lang/de.json
Normal file
41
lang/de.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"CONFIGURABLE_REACTIONS.App.Title": "Configurable Reactions",
|
||||
"CONFIGURABLE_REACTIONS.Controls.OpenConfig": "Configurable Reactions öffnen",
|
||||
"CONFIGURABLE_REACTIONS.Settings.Reactions.Name": "Reaktionen",
|
||||
"CONFIGURABLE_REACTIONS.Settings.Reactions.Hint": "Konfigurierte automatische Reaktionen.",
|
||||
"CONFIGURABLE_REACTIONS.Settings.Assignments.Name": "Zuweisungen",
|
||||
"CONFIGURABLE_REACTIONS.Settings.Assignments.Hint": "Zuweisungen von Reaktionen an Actors oder Tokens.",
|
||||
"CONFIGURABLE_REACTIONS.Settings.ShowFailedTeleportMessages.Name": "Fehlgeschlagene Teleports im Chat anzeigen",
|
||||
"CONFIGURABLE_REACTIONS.Settings.ShowFailedTeleportMessages.Hint": "Erzeugt eine Chat-Nachricht, wenn kein gültiges Teleportziel gefunden wurde.",
|
||||
"CONFIGURABLE_REACTIONS.Settings.CreateManagedEffects.Name": "Verwaltete ActiveEffects bei Zuweisung erzeugen",
|
||||
"CONFIGURABLE_REACTIONS.Settings.CreateManagedEffects.Hint": "Erzeugt auf verknüpften Actors einen sichtbaren ActiveEffect, wenn eine Reaktion zugewiesen wird.",
|
||||
"CONFIGURABLE_REACTIONS.Reactions.Create": "Reaktion erstellen",
|
||||
"CONFIGURABLE_REACTIONS.Reactions.NewReaction": "Neue Reaktion",
|
||||
"CONFIGURABLE_REACTIONS.Reactions.Selected": "Ausgewählte Reaktion",
|
||||
"CONFIGURABLE_REACTIONS.Reactions.JsonEditor": "Reaktion als JSON bearbeiten",
|
||||
"CONFIGURABLE_REACTIONS.Reactions.Delete": "Reaktion löschen",
|
||||
"CONFIGURABLE_REACTIONS.Reactions.DeleteConfirm": "Diese Reaktion und ihre Zuweisungen wirklich löschen?",
|
||||
"CONFIGURABLE_REACTIONS.Reactions.None": "Noch keine Reaktion vorhanden.",
|
||||
"CONFIGURABLE_REACTIONS.Assignments.Title": "Zuweisungen",
|
||||
"CONFIGURABLE_REACTIONS.Assignments.AssignSelectedTokens": "Ausgewählten Tokens zuweisen",
|
||||
"CONFIGURABLE_REACTIONS.Assignments.AssignedResult": "Reaktion zugewiesen: {assigned}. Übersprungen: {skipped}.",
|
||||
"CONFIGURABLE_REACTIONS.Actions.Title": "Aktionen",
|
||||
"CONFIGURABLE_REACTIONS.Actions.AddStatus": "Status-Aktion hinzufügen",
|
||||
"CONFIGURABLE_REACTIONS.Actions.AddTeleport": "Teleport-Aktion hinzufügen",
|
||||
"CONFIGURABLE_REACTIONS.Trigger.Label": "Auslöser",
|
||||
"CONFIGURABLE_REACTIONS.Common.Save": "Speichern",
|
||||
"CONFIGURABLE_REACTIONS.Common.Remove": "Entfernen",
|
||||
"CONFIGURABLE_REACTIONS.Common.Choose": "Wählen",
|
||||
"CONFIGURABLE_REACTIONS.Common.Cancel": "Abbrechen",
|
||||
"CONFIGURABLE_REACTIONS.Errors.GmOnly": "Nur der GM kann diese Aktion ausführen.",
|
||||
"CONFIGURABLE_REACTIONS.Errors.NoReactionSelected": "Keine Reaktion ausgewählt.",
|
||||
"CONFIGURABLE_REACTIONS.Errors.NoTokensSelected": "Bitte wähle zuerst mindestens einen Token aus.",
|
||||
"CONFIGURABLE_REACTIONS.Errors.ReactionNotFound": "Die ausgewählte Reaktion wurde nicht gefunden.",
|
||||
"CONFIGURABLE_REACTIONS.Errors.InvalidJson": "Das JSON ist ungültig.",
|
||||
"CONFIGURABLE_REACTIONS.Effects.ManagedReaction": "Automatische Reaktion: {name}",
|
||||
"CONFIGURABLE_REACTIONS.Teleport.Title": "Reaktiver Teleport",
|
||||
"CONFIGURABLE_REACTIONS.Teleport.ChooseTarget": "Wähle ein Ziel innerhalb von {radius} ft.",
|
||||
"CONFIGURABLE_REACTIONS.Teleport.SwitchScene": "Bitte wechsle zur betroffenen Szene.",
|
||||
"CONFIGURABLE_REACTIONS.Teleport.InvalidTarget": "Das gewählte Teleportziel ist ungültig.",
|
||||
"CONFIGURABLE_REACTIONS.Teleport.NoValidTarget": "Kein gültiges Teleportziel für {name} innerhalb von {radius} ft gefunden."
|
||||
}
|
||||
41
lang/en.json
Normal file
41
lang/en.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"CONFIGURABLE_REACTIONS.App.Title": "Configurable Reactions",
|
||||
"CONFIGURABLE_REACTIONS.Controls.OpenConfig": "Open Configurable Reactions",
|
||||
"CONFIGURABLE_REACTIONS.Settings.Reactions.Name": "Reactions",
|
||||
"CONFIGURABLE_REACTIONS.Settings.Reactions.Hint": "Configured automatic reactions.",
|
||||
"CONFIGURABLE_REACTIONS.Settings.Assignments.Name": "Assignments",
|
||||
"CONFIGURABLE_REACTIONS.Settings.Assignments.Hint": "Assignments of reactions to actors or tokens.",
|
||||
"CONFIGURABLE_REACTIONS.Settings.ShowFailedTeleportMessages.Name": "Show failed teleports in chat",
|
||||
"CONFIGURABLE_REACTIONS.Settings.ShowFailedTeleportMessages.Hint": "Creates a chat message when no valid teleport target was found.",
|
||||
"CONFIGURABLE_REACTIONS.Settings.CreateManagedEffects.Name": "Create managed ActiveEffects on assignment",
|
||||
"CONFIGURABLE_REACTIONS.Settings.CreateManagedEffects.Hint": "Creates a visible ActiveEffect on linked actors when a reaction is assigned.",
|
||||
"CONFIGURABLE_REACTIONS.Reactions.Create": "Create reaction",
|
||||
"CONFIGURABLE_REACTIONS.Reactions.NewReaction": "New reaction",
|
||||
"CONFIGURABLE_REACTIONS.Reactions.Selected": "Selected reaction",
|
||||
"CONFIGURABLE_REACTIONS.Reactions.JsonEditor": "Edit reaction as JSON",
|
||||
"CONFIGURABLE_REACTIONS.Reactions.Delete": "Delete reaction",
|
||||
"CONFIGURABLE_REACTIONS.Reactions.DeleteConfirm": "Really delete this reaction and its assignments?",
|
||||
"CONFIGURABLE_REACTIONS.Reactions.None": "No reaction exists yet.",
|
||||
"CONFIGURABLE_REACTIONS.Assignments.Title": "Assignments",
|
||||
"CONFIGURABLE_REACTIONS.Assignments.AssignSelectedTokens": "Assign selected tokens",
|
||||
"CONFIGURABLE_REACTIONS.Assignments.AssignedResult": "Reaction assigned: {assigned}. Skipped: {skipped}.",
|
||||
"CONFIGURABLE_REACTIONS.Actions.Title": "Actions",
|
||||
"CONFIGURABLE_REACTIONS.Actions.AddStatus": "Add status action",
|
||||
"CONFIGURABLE_REACTIONS.Actions.AddTeleport": "Add teleport action",
|
||||
"CONFIGURABLE_REACTIONS.Trigger.Label": "Trigger",
|
||||
"CONFIGURABLE_REACTIONS.Common.Save": "Save",
|
||||
"CONFIGURABLE_REACTIONS.Common.Remove": "Remove",
|
||||
"CONFIGURABLE_REACTIONS.Common.Choose": "Choose",
|
||||
"CONFIGURABLE_REACTIONS.Common.Cancel": "Cancel",
|
||||
"CONFIGURABLE_REACTIONS.Errors.GmOnly": "Only the GM can perform this action.",
|
||||
"CONFIGURABLE_REACTIONS.Errors.NoReactionSelected": "No reaction selected.",
|
||||
"CONFIGURABLE_REACTIONS.Errors.NoTokensSelected": "Please select at least one token first.",
|
||||
"CONFIGURABLE_REACTIONS.Errors.ReactionNotFound": "The selected reaction was not found.",
|
||||
"CONFIGURABLE_REACTIONS.Errors.InvalidJson": "The JSON is invalid.",
|
||||
"CONFIGURABLE_REACTIONS.Effects.ManagedReaction": "Automatic Reaction: {name}",
|
||||
"CONFIGURABLE_REACTIONS.Teleport.Title": "Reactive Teleport",
|
||||
"CONFIGURABLE_REACTIONS.Teleport.ChooseTarget": "Choose a target within {radius} ft.",
|
||||
"CONFIGURABLE_REACTIONS.Teleport.SwitchScene": "Please switch to the affected scene.",
|
||||
"CONFIGURABLE_REACTIONS.Teleport.InvalidTarget": "The selected teleport target is invalid.",
|
||||
"CONFIGURABLE_REACTIONS.Teleport.NoValidTarget": "No valid teleport target for {name} within {radius} ft found."
|
||||
}
|
||||
38
module.json
Normal file
38
module.json
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"id": "configurable-reactions",
|
||||
"title": "Configurable Reactions",
|
||||
"description": "A Foundry VTT 14 module for configurable token and actor reactions triggered by damage, targeting, spell usage, feature usage and HP thresholds.",
|
||||
"version": "0.1.0",
|
||||
"compatibility": {
|
||||
"minimum": "14",
|
||||
"verified": "14"
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "Florian Zumpe"
|
||||
}
|
||||
],
|
||||
"esmodules": [
|
||||
"scripts/main.js"
|
||||
],
|
||||
"styles": [
|
||||
"styles/configurable-reactions.css"
|
||||
],
|
||||
"languages": [
|
||||
{
|
||||
"lang": "de",
|
||||
"name": "Deutsch",
|
||||
"path": "lang/de.json"
|
||||
},
|
||||
{
|
||||
"lang": "en",
|
||||
"name": "English",
|
||||
"path": "lang/en.json"
|
||||
}
|
||||
],
|
||||
"socket": true,
|
||||
"url": "",
|
||||
"manifest": "",
|
||||
"download": "",
|
||||
"license": "MIT"
|
||||
}
|
||||
54
scripts/constants.js
Normal file
54
scripts/constants.js
Normal file
@ -0,0 +1,54 @@
|
||||
export const MODULE_ID = "configurable-reactions";
|
||||
export const MODULE_TITLE = "Configurable Reactions";
|
||||
|
||||
export const SETTINGS = Object.freeze({
|
||||
REACTIONS: "reactions",
|
||||
ASSIGNMENTS: "assignments",
|
||||
SHOW_FAILED_TELEPORT_MESSAGES: "showFailedTeleportMessages",
|
||||
CREATE_MANAGED_EFFECTS_ON_ASSIGNMENT: "createManagedEffectsOnAssignment"
|
||||
});
|
||||
|
||||
export const TRIGGER_TYPES = Object.freeze({
|
||||
DAMAGE_RECEIVED: "damageReceived",
|
||||
TARGET_SELECTED: "targetSelected",
|
||||
SPELL_CAST_START: "spellCastStart",
|
||||
SPELL_CAST_COMPLETE: "spellCastComplete",
|
||||
FEATURE_USED: "featureUsed"
|
||||
});
|
||||
|
||||
export const ACTION_TYPES = Object.freeze({
|
||||
APPLY_STATUS: "applyStatus",
|
||||
TELEPORT: "teleport",
|
||||
CAST_SPELL_FROM_TOKEN: "castSpellFromToken",
|
||||
USE_INVENTORY_ITEM: "useInventoryItem"
|
||||
});
|
||||
|
||||
export const DEFAULT_REACTION = Object.freeze({
|
||||
name: "Neue Reaktion",
|
||||
enabled: true,
|
||||
trigger: {
|
||||
type: TRIGGER_TYPES.DAMAGE_RECEIVED
|
||||
},
|
||||
conditions: {
|
||||
damage: {
|
||||
enabled: true,
|
||||
amountMode: "damageOnly",
|
||||
types: [],
|
||||
typeMode: "any",
|
||||
minAmount: 1
|
||||
},
|
||||
hpAfterDamage: {
|
||||
enabled: false,
|
||||
operator: "lte",
|
||||
mode: "percent",
|
||||
value: 50
|
||||
}
|
||||
},
|
||||
consumption: {
|
||||
enabled: false,
|
||||
mode: "none",
|
||||
maxUses: 1,
|
||||
consumeOnFailure: false
|
||||
},
|
||||
actions: []
|
||||
});
|
||||
36
scripts/main.js
Normal file
36
scripts/main.js
Normal file
@ -0,0 +1,36 @@
|
||||
import { MODULE_ID } from "./constants.js";
|
||||
import { registerSettings } from "./settings.js";
|
||||
import { registerSocket } from "./sockets.js";
|
||||
import { ConfigurableReactionsConfigApp } from "./apps/reaction-config-app.js";
|
||||
import { registerDnd5eDamageTrigger } from "./triggers/damage-received.js";
|
||||
import { registerTargetSelectedTrigger } from "./triggers/target-selected.js";
|
||||
|
||||
Hooks.once("init", () => {
|
||||
registerSettings();
|
||||
console.log(`${MODULE_ID} | Settings registered`);
|
||||
});
|
||||
|
||||
Hooks.once("ready", () => {
|
||||
registerSocket();
|
||||
registerDnd5eDamageTrigger();
|
||||
registerTargetSelectedTrigger();
|
||||
console.log(`${MODULE_ID} | Ready`);
|
||||
});
|
||||
|
||||
Hooks.on("getSceneControlButtons", controls => {
|
||||
if (!game.user.isGM) return;
|
||||
|
||||
const tokenControls = controls.tokens ?? controls.find?.(c => c.name === "token");
|
||||
if (!tokenControls) return;
|
||||
|
||||
const tool = {
|
||||
name: "configurable-reactions",
|
||||
title: game.i18n.localize("CONFIGURABLE_REACTIONS.Controls.OpenConfig"),
|
||||
icon: "fa-solid fa-bolt",
|
||||
button: true,
|
||||
onChange: () => new ConfigurableReactionsConfigApp().render(true)
|
||||
};
|
||||
|
||||
if (Array.isArray(tokenControls.tools)) tokenControls.tools.push(tool);
|
||||
else if (tokenControls.tools instanceof Object) tokenControls.tools[tool.name] = tool;
|
||||
});
|
||||
39
scripts/settings.js
Normal file
39
scripts/settings.js
Normal file
@ -0,0 +1,39 @@
|
||||
import { MODULE_ID, SETTINGS } from "./constants.js";
|
||||
|
||||
export function registerSettings() {
|
||||
game.settings.register(MODULE_ID, SETTINGS.REACTIONS, {
|
||||
name: game.i18n.localize("CONFIGURABLE_REACTIONS.Settings.Reactions.Name"),
|
||||
hint: game.i18n.localize("CONFIGURABLE_REACTIONS.Settings.Reactions.Hint"),
|
||||
scope: "world",
|
||||
config: false,
|
||||
type: Array,
|
||||
default: []
|
||||
});
|
||||
|
||||
game.settings.register(MODULE_ID, SETTINGS.ASSIGNMENTS, {
|
||||
name: game.i18n.localize("CONFIGURABLE_REACTIONS.Settings.Assignments.Name"),
|
||||
hint: game.i18n.localize("CONFIGURABLE_REACTIONS.Settings.Assignments.Hint"),
|
||||
scope: "world",
|
||||
config: false,
|
||||
type: Array,
|
||||
default: []
|
||||
});
|
||||
|
||||
game.settings.register(MODULE_ID, SETTINGS.SHOW_FAILED_TELEPORT_MESSAGES, {
|
||||
name: game.i18n.localize("CONFIGURABLE_REACTIONS.Settings.ShowFailedTeleportMessages.Name"),
|
||||
hint: game.i18n.localize("CONFIGURABLE_REACTIONS.Settings.ShowFailedTeleportMessages.Hint"),
|
||||
scope: "world",
|
||||
config: true,
|
||||
type: Boolean,
|
||||
default: true
|
||||
});
|
||||
|
||||
game.settings.register(MODULE_ID, SETTINGS.CREATE_MANAGED_EFFECTS_ON_ASSIGNMENT, {
|
||||
name: game.i18n.localize("CONFIGURABLE_REACTIONS.Settings.CreateManagedEffects.Name"),
|
||||
hint: game.i18n.localize("CONFIGURABLE_REACTIONS.Settings.CreateManagedEffects.Hint"),
|
||||
scope: "world",
|
||||
config: true,
|
||||
type: Boolean,
|
||||
default: true
|
||||
});
|
||||
}
|
||||
23
scripts/sockets.js
Normal file
23
scripts/sockets.js
Normal file
@ -0,0 +1,23 @@
|
||||
import { MODULE_ID } from "./constants.js";
|
||||
import { handleTeleportTargetRequest, handleTeleportTargetChosen } from "./actions/teleport-action.js";
|
||||
|
||||
export function registerSocket() {
|
||||
game.socket.on(`module.${MODULE_ID}`, async message => {
|
||||
if (!message?.type) return;
|
||||
|
||||
switch (message.type) {
|
||||
case "requestTeleportTarget":
|
||||
return handleTeleportTargetRequest(message);
|
||||
|
||||
case "teleportTargetChosen":
|
||||
return handleTeleportTargetChosen(message);
|
||||
|
||||
default:
|
||||
console.warn(`${MODULE_ID} | Unknown socket message`, message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function emitSocket(message) {
|
||||
game.socket.emit(`module.${MODULE_ID}`, message);
|
||||
}
|
||||
79
styles/configurable-reactions.css
Normal file
79
styles/configurable-reactions.css
Normal file
@ -0,0 +1,79 @@
|
||||
.configurable-reactions .cr-config {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.configurable-reactions .cr-header,
|
||||
.configurable-reactions .cr-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.configurable-reactions .cr-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 280px 1fr;
|
||||
gap: 1rem;
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.configurable-reactions .cr-sidebar,
|
||||
.configurable-reactions .cr-main {
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.configurable-reactions .cr-button-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.35rem;
|
||||
margin: 0.75rem 0;
|
||||
}
|
||||
|
||||
.configurable-reactions .cr-assignment-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.35rem;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.configurable-reactions .cr-assignment-list li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
border: 1px solid var(--color-border-light-tertiary, #888);
|
||||
border-radius: 4px;
|
||||
padding: 0.35rem;
|
||||
}
|
||||
|
||||
.configurable-reactions .cr-assignment-list small {
|
||||
display: block;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.configurable-reactions .cr-main textarea[name="reactionJson"] {
|
||||
width: 100%;
|
||||
min-height: 430px;
|
||||
font-family: var(--font-monospace, monospace);
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.configurable-reactions .cr-reaction-summary {
|
||||
border: 1px solid var(--color-border-light-tertiary, #888);
|
||||
border-radius: 4px;
|
||||
padding: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.configurable-reactions .form-group.stacked {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
75
templates/reaction-config.hbs
Normal file
75
templates/reaction-config.hbs
Normal file
@ -0,0 +1,75 @@
|
||||
<div class="cr-config">
|
||||
<header class="cr-header">
|
||||
<h2>{{localize "CONFIGURABLE_REACTIONS.App.Title"}}</h2>
|
||||
<button type="button" data-action="createReaction">
|
||||
<i class="fa-solid fa-plus"></i> {{localize "CONFIGURABLE_REACTIONS.Reactions.Create"}}
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<section class="cr-grid">
|
||||
<aside class="cr-sidebar">
|
||||
<div class="form-group">
|
||||
<label>{{localize "CONFIGURABLE_REACTIONS.Reactions.Selected"}}</label>
|
||||
<select name="selectedReactionId">
|
||||
{{#each reactions}}
|
||||
<option value="{{this.id}}" {{#if (eq this.id ../selectedReaction.id)}}selected{{/if}}>{{this.name}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="cr-button-column">
|
||||
<button type="button" data-action="assignSelectedTokens" {{#unless selectedReaction}}disabled{{/unless}}>
|
||||
<i class="fa-solid fa-crosshairs"></i> {{localize "CONFIGURABLE_REACTIONS.Assignments.AssignSelectedTokens"}}
|
||||
</button>
|
||||
<button type="button" data-action="addStatusAction" {{#unless selectedReaction}}disabled{{/unless}}>
|
||||
<i class="fa-solid fa-circle-plus"></i> {{localize "CONFIGURABLE_REACTIONS.Actions.AddStatus"}}
|
||||
</button>
|
||||
<button type="button" data-action="addTeleportAction" {{#unless selectedReaction}}disabled{{/unless}}>
|
||||
<i class="fa-solid fa-circle-plus"></i> {{localize "CONFIGURABLE_REACTIONS.Actions.AddTeleport"}}
|
||||
</button>
|
||||
<button type="button" data-action="deleteReaction" {{#unless selectedReaction}}disabled{{/unless}}>
|
||||
<i class="fa-solid fa-trash"></i> {{localize "CONFIGURABLE_REACTIONS.Reactions.Delete"}}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<h3>{{localize "CONFIGURABLE_REACTIONS.Assignments.Title"}}</h3>
|
||||
<ol class="cr-assignment-list">
|
||||
{{#each assignments}}
|
||||
<li>
|
||||
<div>
|
||||
<strong>{{this.name}}</strong>
|
||||
<small>{{this.mode}} · {{this.reactionName}}</small>
|
||||
</div>
|
||||
<button type="button" data-action="removeAssignment" data-assignment-id="{{this.id}}" title="{{localize 'CONFIGURABLE_REACTIONS.Common.Remove'}}">
|
||||
<i class="fa-solid fa-xmark"></i>
|
||||
</button>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ol>
|
||||
</aside>
|
||||
|
||||
<main class="cr-main">
|
||||
{{#if selectedReaction}}
|
||||
<div class="cr-reaction-summary">
|
||||
<h3>{{selectedReaction.name}}</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>
|
||||
<textarea name="reactionJson" spellcheck="false">{{selectedReactionJson}}</textarea>
|
||||
</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}}
|
||||
<p>{{localize "CONFIGURABLE_REACTIONS.Reactions.None"}}</p>
|
||||
{{/if}}
|
||||
</main>
|
||||
</section>
|
||||
</div>
|
||||
Loading…
x
Reference in New Issue
Block a user