migrates CreatureSheet to ActorSheetV2

feature/applicationv2
macniel 2025-10-15 22:29:48 +02:00
parent 6bfd509c2c
commit 9984db4ca6
9 changed files with 401 additions and 356 deletions

View File

@ -1,32 +1,114 @@
export class CreatureSheet extends foundry.appv1.sheets.ActorSheet { const {HandlebarsApplicationMixin} = foundry.applications.api
/**@override */ const {ActorSheetV2} = foundry.applications.sheets
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, { export class CreatureSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
classes: ['dsa41', 'sheet', 'actor', 'creature'],
width: 520, /** @inheritDoc */
height: 480, static DEFAULT_OPTIONS = {
position: {width: 520, height: 480},
classes: ['dsa41', 'sheet', 'actor', 'creature'],
tag: 'form',
form: {
submitOnChange: true,
closeOnSubmit: false,
handler: CreatureSheet.#onSubmitForm
},
actions: {
removeAttack: CreatureSheet.#removeAttack,
addAttack: CreatureSheet.#addAttack,
roll: CreatureSheet.#dieRoll,
editImage: ActorSheetV2.DEFAULT_OPTIONS.actions.editImage,
}
}
static TABS = {
sheet: {
tabs: [ tabs: [
{ {id: 'meta', group: 'sheet', label: 'Meta'},
navSelector: '.sheet-tabs', {id: 'attacks', group: 'sheet', label: 'Attacken'},
contentSelector: '.sheet-body', {id: 'description', group: 'sheet', label: 'Beschreibung'},
initial: 'meta',
},
], ],
initial: 'meta'
}
}
/** @inheritDoc */
static PARTS = {
form: {
template: `systems/DSA_4-1/templates/actor/creature/main-sheet.hbs`
},
meta: {
template: `systems/DSA_4-1/templates/actor/creature/tab-meta.hbs`
},
attacks: {
template: `systems/DSA_4-1/templates/actor/creature/tab-attacks.hbs`
},
description: {
template: `systems/DSA_4-1/templates/actor/creature/tab-description.hbs`
}
}
/**
* Handle form submission
* @this {AdvantageSheet}
* @param {SubmitEvent} event
* @param {HTMLFormElement} form
* @param {FormDataExtended} formData
*/
static async #onSubmitForm(event, form, formData) {
event.preventDefault()
await this.document.update(formData.object) // Note: formData.object
}
static async #removeAttack(evt) {
const {index} = evt.srcElement.dataset;
let sans = Array.from(this.document.system.attacks);
sans.splice(index, 1);
await this.document.update({'system.attacks': sans})
}
static async #dieRoll(evt) {
const {rollType, rollName, roll} = evt.srcElement.dataset;
let r = new Roll(roll, this.document.getRollData());
const label = `${rollType} (${rollName})`
await r.toMessage({
speaker: ChatMessage.getSpeaker({actor: this.document}),
flavor: label,
rollMode: game.settings.get('core', 'rollMode'),
}); });
} }
/** @override */ static async #addAttack() {
get template() { const name = this.element.querySelector('#attack_name').value
return `systems/DSA_4-1/templates/actor/actor-creature-sheet.hbs`; const at = this.element.querySelector('#attack_at').value
const pa = this.element.querySelector('#attack_pa').value
const tp = this.element.querySelector('#attack_tp').value
const newAttack = {
name,
at,
pa,
tp
}
await this.document.update({'system.attacks': [...this.document.system.attacks, newAttack]})
this.element.querySelector('#attack_name').value = ""
this.element.querySelector('#attack_at').value = ""
this.element.querySelector('#attack_pa').value = ""
this.element.querySelector('#attack_tp').value = ""
} }
/** @override */ /** @override */
getData() { async _prepareContext(options) {
const context = super.getData(); const context = await super._prepareContext(options);
const actorData = context.data; const actorData = context.document;
context.attacks = []; context.attacks = [];
context.actor = actorData;
actorData.system.attacks.forEach((attack, index) => { actorData.system.attacks.forEach((attack, index) => {
context.attacks.push({ context.attacks.push({
@ -43,55 +125,7 @@ export class CreatureSheet extends foundry.appv1.sheets.ActorSheet {
}) })
}) })
return context; return context;
} }
activateListeners(html) {
super.activateListeners(html);
// Everything below here is only needed if the sheet is editable
if (!this.isEditable) return;
html.on('click', '.remove-attack', async (evt) => {
const {index} = evt.target.dataset;
let sans = Array.from(this.object.system.attacks);
sans.splice(index, 1);
await this.object.update({'system.attacks': sans})
})
html.on('click', '.attacks-die.die', async (evt) => {
const {rollType, rollName, roll} = evt.currentTarget.dataset;
let r = new Roll(roll, this.actor.getRollData());
const label = `${rollType} (${rollName})`
await r.toMessage({
speaker: ChatMessage.getSpeaker({actor: this.object}),
flavor: label,
rollMode: game.settings.get('core', 'rollMode'),
});
})
html.on('click', '.editor .add-attack', async (evt) => {
const name = html[0].querySelector('#attack_name').value
const at = html[0].querySelector('#attack_at').value
const pa = html[0].querySelector('#attack_pa').value
const tp = html[0].querySelector('#attack_tp').value
const newAttack = {
name,
at,
pa,
tp
}
await this.object.update({'system.attacks': [...this.object.system.attacks, newAttack]})
evt.target.parentElement.querySelector('#attack_name').value = ""
evt.target.parentElement.querySelector('#attack_at').value = ""
evt.target.parentElement.querySelector('#attack_pa').value = ""
evt.target.parentElement.querySelector('#attack_tp').value = ""
})
}
} }

View File

@ -0,0 +1,3 @@
svg {
pointer-events: none;
}

View File

@ -6,169 +6,163 @@
.window-content { .window-content {
form { .sheet-header {
height: 64px;
display: grid; display: grid;
grid-template-columns: 1fr; grid-template-columns: 64px 1fr;
grid-template-rows: 74px 30px 1fr; grid-template-rows: 1fr;
gap: 0 8px;
.sheet-header { margin-bottom: 8px;
img {
width: 64px;
height: 64px; height: 64px;
display: grid;
grid-template-columns: 64px max-content;
grid-template-rows: 1fr;
gap: 0 8px;
img {
width: 64px;
height: 64px;
}
input {
line-height: 64px;
height: 64px;
font-size: 2rem;
}
} }
.sheet-body { input {
.tab { line-height: 64px;
margin: 8px; height: 64px;
font-size: 2rem;
height: 100%;
div.input {
height: 32px;
display: grid;
grid-template-columns: 120px 1fr;
&.compound {
grid-template-columns: 120px 140px 1fr;
span {
width: 120px;
}
input {
width: 100px;
}
}
label {
height: 32px;
width: 100%;
span {
width: 120px;
text-align: left;
line-height: 32px;
vertical-align: middle;
display: inline-block;
}
input {
text-align: right;
line-height: 32px;
vertical-align: middle;
padding-left: 120px;
display: inline-block;
}
}
}
table.attacks-table {
td {
padding: 2px 4px;
}
}
.rollable.tablecell {
position: relative;
.attacks-die {
width: 24px;
height: 24px;
position: absolute;
left: 4px;
top: 4px;
bottom: 2px;
z-index: 1;
svg {
stroke-width: 0.5;
$color: #f30;
.border {
fill: #0000;
}
.center {
fill: $color;
stroke: colour.$rollable-die-border-color;
}
.topleft {
fill: color.adjust($color, $lightness: numbers.$lighter_factor);
stroke: colour.$rollable-die-border-color;
}
.bottomleft {
fill: color.adjust($color, $lightness: numbers.$lightest_factor);
stroke: colour.$rollable-die-border-color;
}
.topright {
fill: color.adjust($color, $lightness: numbers.$darken_factor);
stroke: colour.$rollable-die-border-color;
}
.bottomright, .bottom {
fill: color.adjust($color, $lightness: numbers.$darkest_factor);
stroke: colour.$rollable-die-border-color;
}
}
}
input {
position: absolute;
left: 16px;
top: 4px;
bottom: 2px;
right: 4px;
width: unset;
text-indent: 12px;
}
}
.button-inline {
border: unset;
background: unset;
}
.editor {
height: 100%;
}
}
} }
} }
}
div.input {
height: 32px;
display: grid;
grid-template-columns: 120px 1fr;
&.compound {
grid-template-columns: 120px 140px 1fr;
span {
width: 120px;
}
input {
width: 100px;
}
}
label {
height: 32px;
width: 100%;
span {
width: 120px;
text-align: left;
line-height: 32px;
vertical-align: middle;
display: inline-block;
}
input {
text-align: right;
line-height: 32px;
vertical-align: middle;
padding-left: 120px;
display: inline-block;
}
}
}
table.attacks-table {
margin: 0;
td {
padding: 2px 4px;
}
}
.rollable.tablecell {
position: relative;
.attacks-die {
width: 24px;
height: 24px;
position: absolute;
left: 4px;
top: 4px;
bottom: 2px;
z-index: 1;
svg {
stroke-width: 0.5;
$color: #f30;
.border {
fill: #0000;
}
.center {
fill: $color;
stroke: colour.$rollable-die-border-color;
}
.topleft {
fill: color.adjust($color, $lightness: numbers.$lighter_factor);
stroke: colour.$rollable-die-border-color;
}
.bottomleft {
fill: color.adjust($color, $lightness: numbers.$lightest_factor);
stroke: colour.$rollable-die-border-color;
}
.topright {
fill: color.adjust($color, $lightness: numbers.$darken_factor);
stroke: colour.$rollable-die-border-color;
}
.bottomright, .bottom {
fill: color.adjust($color, $lightness: numbers.$darkest_factor);
stroke: colour.$rollable-die-border-color;
}
}
}
input {
position: absolute;
left: 16px;
top: 4px;
bottom: 2px;
right: 4px;
width: unset;
text-indent: 12px;
}
}
.button-inline {
border: unset;
background: unset;
}
.editor {
height: 100%;
}
.tab.active {
padding: 8px;
}
.tab.attacks.active {
padding: 16px;
}
}
} }

View File

@ -1,3 +1,4 @@
@use "atoms/svg";
@use "molecules/rollable"; @use "molecules/rollable";
@use "molecules/lists"; @use "molecules/lists";
@use "molecules/attributes"; @use "molecules/attributes";

View File

@ -1,137 +0,0 @@
<form class="{{cssClass}} {{actor.type}}" autocomplete="off">
{{!-- Sheet Header --}}
<header class="sheet-header">
{{!-- Header stuff goes here --}}
<img src="{{actor.img}}" data-edit="img" title="{{actor.name}}"/>
<input type="text" name="name" value="{{actor.name}}" placeholder="Name"/>
</header>
{{!-- Sheet Tab Navigation --}}
<nav class="sheet-tabs tabs" style="flex: 0" data-group="primary">
<a class="item" data-tab="meta">Kopfdaten</a>
<a class="item" data-tab="attacks">Attacken</a>
<a class="item" data-tab="description">Beschreibung</a>
</nav>
{{!-- Sheet Body --}}
<section class="sheet-body" style="flex: 1">
<div class="tab meta" data-group="primary" data-tab="meta">
<div>
<div class="input">
<label for="system.lep.aktuell">Lebenspunkte</label>
<input type="text" name="system.lep.aktuell"
value="{{actor.system.lep.aktuell}}"/>
</div>
<div class="input">
<label for="system.rs">Rüstungsschutz</label>
<input type="text" name="system.rs"
value="{{actor.system.rs}}"/>
</div>
<div class="input">
<label for="system.mr">Magieresistenz</label>
<input type="text" name="system.mr"
value="{{actor.system.mr}}"/>
</div>
<div class="input compound">
<label>Initiative</label>
<div class="inline-label">
<input type="number" name="system.ini.wuerfel"
value="{{actor.system.ini.wuerfel}}"/>
<span class="inline">w6 + </span>
</div>
<input type="number" name="system.ini.aktuell"
value="{{actor.system.ini.aktuell}}"/>
</div>
</div>
</div>
<div class="tab attacks" data-group="primary" data-tab="attacks">
<table class="attacks-table">
<thead>
<tr>
<th>Name</th>
<th>Attacke</th>
<th>Parade</th>
<th>Schaden</th>
<th></th>
</tr>
</thead>
<tbody>
{{#each this.attacks}}
<tr>
<td>
<input type="text" name="{{this.namepath}}"
value="{{this.name}}"/>
</td>
<td class="rollable tablecell">
<div class="attacks-die die rollable attack" data-actor="{{actor.name}}"
data-roll-type="attack" data-roll-name="{{this.name}}" data-roll="1d20<{{this.at}}cs">
{{> 'systems/DSA_4-1/templates/ui/partial-die.hbs' }}
</div>
<input type="text" name="{{this.atpath}}"
value="{{this.at}}"/>
</td>
<td class="rollable tablecell">
<div class="attacks-die die" data-roll-type="parry" data-roll-name="{{this.name}}"
data-roll="1d20 + {{this.pa}}">
{{> 'systems/DSA_4-1/templates/ui/partial-die.hbs' }}
</div>
<input type="text" name="{{this.papath}}"
value="{{this.pa}}"/>
</td>
<td class="rollable tablecell">
<div class="attacks-die die" data-roll-type="damage" data-roll-name="{{this.name}}"
data-roll="{{this.tp}}">
{{> 'systems/DSA_4-1/templates/ui/partial-die.hbs' }}
</div>
<input type="text" name="{{this.tppath}}"
value="{{this.tp}}"/>
</td>
<td>
<button class="button-inline remove-attack" data-index="{{this.index}}"><i
class="fa-solid fa-trash"></i></button>
</td>
</tr>
{{/each}}
</tbody>
<tfoot class="editor">
<tr>
<th colspan="5">Attacke hinzufügen</th>
</tr>
<tr>
<td>
<input type="text" id="attack_name"/>
</td>
<td>
<input type="text" id="attack_at" placeholder="8"/>
</td>
<td>
<input type="text" id="attack_pa" placeholer="12"/>
</td>
<td>
<input type="text" id="attack_tp" placeholder="1d6+3"/>
</td>
<td>
<button class="button-inline add-attack"><i class="fa-solid fa-plus"></i></button>
</td>
</tr>
</tfoot>
</table>
</div>
<div class="tab description" data-group="primary" data-tab="description">
{{editor system.description target="system.description" button=true owner=owner editable=editable}}
</div>
</section>
</form>

View File

@ -0,0 +1,22 @@
<div>
<header class="sheet-header">
{{!-- Header stuff goes here --}}
<img class="img" src="{{actor.img}}" data-action="editImage" data-edit="img" alt="{{actor.name}}"
title="{{actor.name}}"/>
<input type="text" name="name" value="{{actor.name}}" placeholder="Name"/>
</header>
<nav class="sheet-tabs tabs{{#if verticalTabs}} vertical{{/if}}"
aria-roledescription="{{localize "SHEETS.FormNavLabel"}}">
{{#each tabs as |tab|}}
<a data-action="tab" data-group="{{tab.group}}" data-tab="{{tab.id}}"
{{#if tab.cssClass}}class="{{tab.cssClass}}"{{/if}}
{{#if tab.tooltip}}data-tooltip="{{tab.tooltip}}"{{/if}}>
{{#if tab.icon}}<i class="{{tab.icon}}" inert></i>{{/if}}
{{#if tab.label}}<span>{{localize tab.label}}</span>{{/if}}
</a>
{{/each}}
</nav>
</div>

View File

@ -0,0 +1,83 @@
<section class="tab {{tabs.attacks.id}} {{tabs.attacks.cssClass}}"
data-tab="{{tabs.attacks.id}}"
data-group="{{tabs.attacks.group}}">
<table class="attacks-table">
<thead>
<tr>
<th>Name</th>
<th>Attacke</th>
<th>Parade</th>
<th>Schaden</th>
<th></th>
</tr>
</thead>
<tbody>
{{#each this.attacks}}
<tr>
<td>
<input type="text" name="{{this.namepath}}"
value="{{this.name}}"/>
</td>
<td class="rollable tablecell">
<div class="attacks-die die rollable attack" data-action="roll" data-actor="{{actor.name}}"
data-roll-type="attack" data-roll-name="{{this.name}}" data-roll="1d20cs<{{this.at}}">
{{> 'systems/DSA_4-1/templates/ui/partial-die.hbs' }}
</div>
<input type="text" name="{{this.atpath}}"
value="{{this.at}}"/>
</td>
<td class="rollable tablecell">
<div class="attacks-die die" data-action="roll" data-roll-type="parry"
data-roll-name="{{this.name}}"
data-roll="1d20cs<{{this.pa}}">
{{> 'systems/DSA_4-1/templates/ui/partial-die.hbs' }}
</div>
<input type="text" name="{{this.papath}}"
value="{{this.pa}}"/>
</td>
<td class="rollable tablecell">
<div class="attacks-die die" data-action="roll" data-roll-type="damage"
data-roll-name="{{this.name}}"
data-roll="{{this.tp}}">
{{> 'systems/DSA_4-1/templates/ui/partial-die.hbs' }}
</div>
<input type="text" name="{{this.tppath}}"
value="{{this.tp}}"/>
</td>
<td>
<button class="button-inline remove-attack" data-action="removeAttack" data-index="{{this.index}}">
<i
class="fa-solid fa-trash"></i></button>
</td>
</tr>
{{/each}}
</tbody>
<tfoot class="editor">
<tr>
<th colspan="5">Attacke hinzufügen</th>
</tr>
<tr>
<td>
<input type="text" id="attack_name"/>
</td>
<td>
<input type="text" id="attack_at" placeholder="8"/>
</td>
<td>
<input type="text" id="attack_pa" placeholer="12"/>
</td>
<td>
<input type="text" id="attack_tp" placeholder="1d6+3"/>
</td>
<td>
<button data-action="addAttack" class="button-inline add-attack"><i class="fa-solid fa-plus"></i>
</button>
</td>
</tr>
</tfoot>
</table>
</section>

View File

@ -0,0 +1,12 @@
<section class="tab {{tabs.description.id}} {{tabs.description.cssClass}}"
data-tab="{{tabs.description.id}}"
data-group="{{tabs.description.group}}">
<prose-mirror
name="system.description"
button="false"
editable="{{editable}}"
toggled="true"
value="{{system.description}}">
{{{system.description}}}
</prose-mirror>
</section>

View File

@ -0,0 +1,33 @@
<section class="tab {{tabs.meta.id}} {{tabs.meta.cssClass}}"
data-tab="{{tabs.meta.id}}"
data-group="{{tabs.meta.group}}">
<div>
<div class="input">
<label for="system.lep.aktuell">Lebenspunkte</label>
<input type="text" name="system.lep.aktuell"
value="{{actor.system.lep.aktuell}}"/>
</div>
<div class="input">
<label for="system.rs">Rüstungsschutz</label>
<input type="text" name="system.rs"
value="{{actor.system.rs}}"/>
</div>
<div class="input">
<label for="system.mr">Magieresistenz</label>
<input type="text" name="system.mr"
value="{{actor.system.mr}}"/>
</div>
<div class="input compound">
<label>Initiative</label>
<div class="inline-label">
<input type="number" name="system.ini.wuerfel"
value="{{actor.system.ini.wuerfel}}"/>
<span class="inline">w6 + </span>
</div>
<input type="number" name="system.ini.aktuell"
value="{{actor.system.ini.aktuell}}"/>
</div>
</div>
</section>