Make a unique ArmourSheet so that it can have a better UX for indicating protection locations
This commit is contained in:
parent
2b88bcc2ef
commit
94942c8eab
9 changed files with 407 additions and 6 deletions
115
module/Apps/ItemSheets/ArmourSheet.mjs
Normal file
115
module/Apps/ItemSheets/ArmourSheet.mjs
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
import { filePath } from "../../consts.mjs";
|
||||
import { gameTerms } from "../../gameTerms.mjs";
|
||||
import { GenericAppMixin } from "../GenericApp.mjs";
|
||||
import { Logger } from "../../utils/Logger.mjs";
|
||||
|
||||
const { HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
const { ItemSheetV2 } = foundry.applications.sheets;
|
||||
|
||||
export class ArmourSheet extends GenericAppMixin(HandlebarsApplicationMixin(ItemSheetV2)) {
|
||||
|
||||
// #region Options
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: [
|
||||
`ripcrypt--item`,
|
||||
`ArmourSheet`,
|
||||
],
|
||||
position: {
|
||||
width: `auto`,
|
||||
height: `auto`,
|
||||
},
|
||||
window: {
|
||||
resizable: false,
|
||||
},
|
||||
form: {
|
||||
submitOnChange: true,
|
||||
closeOnSubmit: false,
|
||||
},
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
header: {
|
||||
template: filePath(`templates/Apps/partials/item-header.hbs`),
|
||||
},
|
||||
content: {
|
||||
template: filePath(`templates/Apps/ArmourSheet/content.hbs`),
|
||||
},
|
||||
};
|
||||
// #endregion
|
||||
|
||||
// #region Lifecycle
|
||||
async _onRender() {
|
||||
// remove the flag if it exists when we render the sheet
|
||||
delete this.document?.system?.forceRerender;
|
||||
};
|
||||
|
||||
/**
|
||||
* Used to make it so that items that don't get updated because of the
|
||||
* _preUpdate hook removing/changing the data submitted, can still get
|
||||
* re-rendered when the diff is empty. If the document does get updated,
|
||||
* this rerendering does not happen.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
async _processSubmitData(...args) {
|
||||
await super._processSubmitData(...args);
|
||||
|
||||
if (this.document.system.forceRerender) {
|
||||
await this.render(false);
|
||||
};
|
||||
};
|
||||
// #endregion
|
||||
|
||||
// #region Data Prep
|
||||
async _preparePartContext(partId, _, opts) {
|
||||
const ctx = await super._preparePartContext(partId, {}, opts);
|
||||
|
||||
ctx.item = this.document;
|
||||
ctx.system = this.document.system;
|
||||
|
||||
switch (partId) {
|
||||
case `content`: {
|
||||
this._prepareContentContext(ctx, opts);
|
||||
break;
|
||||
};
|
||||
};
|
||||
|
||||
Logger.debug(`Context:`, ctx);
|
||||
return ctx;
|
||||
};
|
||||
|
||||
async _prepareContentContext(ctx) {
|
||||
ctx.weights = [
|
||||
{
|
||||
label: `RipCrypt.common.empty`,
|
||||
value: null,
|
||||
},
|
||||
...Object.values(gameTerms.WeightRatings).map(opt => ({
|
||||
label: `RipCrypt.common.weightRatings.${opt}`,
|
||||
value: opt,
|
||||
})),
|
||||
];
|
||||
|
||||
ctx.accesses = [
|
||||
{
|
||||
label: `RipCrypt.common.empty`,
|
||||
value: ``,
|
||||
},
|
||||
...gameTerms.Access.map(opt => ({
|
||||
label: `RipCrypt.common.accessLevels.${opt}`,
|
||||
value: opt,
|
||||
})),
|
||||
];
|
||||
|
||||
ctx.protects = {
|
||||
head: this.document.system.location.has(gameTerms.Anatomy.HEAD),
|
||||
body: this.document.system.location.has(gameTerms.Anatomy.BODY),
|
||||
arms: this.document.system.location.has(gameTerms.Anatomy.ARMS),
|
||||
legs: this.document.system.location.has(gameTerms.Anatomy.LEGS),
|
||||
};
|
||||
};
|
||||
// #endregion
|
||||
|
||||
// #region Actions
|
||||
// #endregion
|
||||
};
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
// Applications
|
||||
import { AllItemSheetV1 } from "../Apps/ItemSheets/AllItemSheetV1.mjs";
|
||||
import { ArmourSheet } from "../Apps/ItemSheets/ArmourSheet.mjs";
|
||||
import { CombinedHeroSheet } from "../Apps/ActorSheets/CombinedHeroSheet.mjs";
|
||||
import { CraftCardV1 } from "../Apps/ActorSheets/CraftCardV1.mjs";
|
||||
import { DelveDiceHUD } from "../Apps/DelveDiceHUD.mjs";
|
||||
|
|
@ -37,7 +38,6 @@ import { registerUserSettings } from "../settings/userSettings.mjs";
|
|||
import { registerWorldSettings } from "../settings/worldSettings.mjs";
|
||||
|
||||
const { Items, Actors } = foundry.documents.collections;
|
||||
const { ItemSheet, ActorSheet } = foundry.appv1.sheets;
|
||||
|
||||
Hooks.once(`init`, () => {
|
||||
Logger.log(`Initializing`);
|
||||
|
|
@ -74,10 +74,6 @@ Hooks.once(`init`, () => {
|
|||
// #endregion
|
||||
|
||||
// #region Sheets
|
||||
// Unregister core sheets
|
||||
Items.unregisterSheet(`core`, ItemSheet);
|
||||
Actors.unregisterSheet(`core`, ActorSheet);
|
||||
|
||||
// #region Actors
|
||||
Actors.registerSheet(game.system.id, CombinedHeroSheet, {
|
||||
makeDefault: true,
|
||||
|
|
@ -114,6 +110,13 @@ Hooks.once(`init`, () => {
|
|||
label: `RipCrypt.sheet-names.AllItemsSheetV1`,
|
||||
themes: AllItemSheetV1.themes,
|
||||
});
|
||||
|
||||
Items.registerSheet(game.system.id, ArmourSheet, {
|
||||
makeDefault: true,
|
||||
types: [`armour`],
|
||||
label: `RipCrypt.sheet-names.ArmourSheet`,
|
||||
themes: ArmourSheet.themes,
|
||||
});
|
||||
// #endregion
|
||||
// #endregion
|
||||
|
||||
|
|
|
|||
109
templates/Apps/ArmourSheet/content.hbs
Normal file
109
templates/Apps/ArmourSheet/content.hbs
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
<div class="contents">
|
||||
<div class="contents__left">
|
||||
<label for="">
|
||||
{{ rc-i18n "RipCrypt.common.access" }}
|
||||
</label>
|
||||
<select name="system.access" id="">
|
||||
{{ rc-options system.access accesses localize=true }}
|
||||
</select>
|
||||
<label for="">
|
||||
{{ rc-i18n "RipCrypt.common.weightRating" }}
|
||||
</label>
|
||||
<select name="system.weight" id="">
|
||||
{{ rc-options system.weight weights localize=true }}
|
||||
</select>
|
||||
<rc-border
|
||||
var:border-color="var(--accent-1)"
|
||||
>
|
||||
<span slot="title">Cost</span>
|
||||
<div slot="content" class="content">
|
||||
<label
|
||||
for="{{ meta.idp }}"
|
||||
>
|
||||
Gold
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
id="{{ meta.idp }}"
|
||||
name="system.cost.gold"
|
||||
value="{{ system.cost.gold }}"
|
||||
>
|
||||
<label
|
||||
for="{{ meta.idp }}"
|
||||
>
|
||||
Silver
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
id="{{ meta.idp }}"
|
||||
name="system.cost.silver"
|
||||
value="{{ system.cost.silver }}"
|
||||
>
|
||||
<label
|
||||
for="{{ meta.idp }}"
|
||||
>
|
||||
Copper
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
id="{{ meta.idp }}"
|
||||
name="system.cost.copper"
|
||||
value="{{ system.cost.copper }}"
|
||||
>
|
||||
</div>
|
||||
</rc-border>
|
||||
</div>
|
||||
<hr class="vertical">
|
||||
<div class="contents__right">
|
||||
<span class="section-pill">Location</span>
|
||||
<armour-summary
|
||||
var:row-gap="8px"
|
||||
>
|
||||
<div slot="head">
|
||||
<input
|
||||
type="checkbox"
|
||||
aria-label=""
|
||||
value="head"
|
||||
{{ checked protects.head }}
|
||||
name="system.location"
|
||||
>
|
||||
</div>
|
||||
<div slot="body">
|
||||
<input
|
||||
type="checkbox"
|
||||
aria-label=""
|
||||
value="body"
|
||||
{{ checked protects.body }}
|
||||
name="system.location"
|
||||
>
|
||||
</div>
|
||||
<div slot="arms">
|
||||
<input
|
||||
type="checkbox"
|
||||
aria-label=""
|
||||
value="arms"
|
||||
{{ checked protects.arms }}
|
||||
name="system.location"
|
||||
>
|
||||
</div>
|
||||
<div slot="legs">
|
||||
<input
|
||||
type="checkbox"
|
||||
aria-label=""
|
||||
value="legs"
|
||||
{{ checked protects.legs }}
|
||||
name="system.location"
|
||||
>
|
||||
</div>
|
||||
</armour-summary>
|
||||
<input
|
||||
type="number"
|
||||
class="center"
|
||||
aria-label="Damage reduction"
|
||||
data-tooltip="Damage reduction"
|
||||
min="0"
|
||||
name="system.protection"
|
||||
value="{{ system.protection }}"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
131
templates/Apps/ArmourSheet/style.css
Normal file
131
templates/Apps/ArmourSheet/style.css
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
.ripcrypt.ArmourSheet > .window-content {
|
||||
--input-height: 1rem;
|
||||
--input-underline: none;
|
||||
--col-gap: 8px;
|
||||
--row-gap: 8px;
|
||||
|
||||
--string-tags-tag-text: var(--header-text);
|
||||
--string-tags-tag-background: var(--header-background);
|
||||
--string-tags-add-text: white;
|
||||
--string-tags-add-background: var(--accent-1);
|
||||
--string-tags-input-text: white;
|
||||
--string-tags-input-background: var(--accent-2);
|
||||
|
||||
--input-text: white;
|
||||
--input-background: var(--accent-2);
|
||||
--button-text: white;
|
||||
--button-background: var(--accent-2);
|
||||
|
||||
--pill-width: 100%;
|
||||
--pill-border-radius: 4px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
padding: 8px;
|
||||
background: var(--base-background);
|
||||
color: var(--base-text);
|
||||
|
||||
hr {
|
||||
background: var(--accent-1);
|
||||
grid-column: 1 / -1;
|
||||
height: 1px;
|
||||
width: 90%;
|
||||
margin: 0 auto;
|
||||
|
||||
&.vertical {
|
||||
grid-column: unset;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
label, .label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
|
||||
padding: 2px 4px;
|
||||
text-transform: uppercase;
|
||||
font-size: var(--font-size-14);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
button, input, select, .value {
|
||||
border-radius: 4px;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
.value {
|
||||
border: 2px solid var(--accent-2);
|
||||
}
|
||||
|
||||
.contents {
|
||||
display: grid;
|
||||
grid-template-columns: 300px 1px 118px;
|
||||
gap: 8px;
|
||||
|
||||
> .contents__left {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
|
||||
column-gap: var(--col-gap);
|
||||
row-gap: var(--row-gap);
|
||||
}
|
||||
|
||||
> .contents__right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.section-pill {
|
||||
background: var(--section-header-background);
|
||||
color: var(--section-header-text);
|
||||
padding: 0 4px;
|
||||
border-radius: 999px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
rc-border {
|
||||
grid-column: 1 / -1;
|
||||
|
||||
> .content {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
|
||||
column-gap: var(--col-gap);
|
||||
row-gap: var(--row-gap);
|
||||
}
|
||||
|
||||
.label {
|
||||
background: purple;
|
||||
}
|
||||
}
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.compass {
|
||||
--size: 35px;
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: 2px solid var(--accent-1);
|
||||
border-radius: 50%;
|
||||
font-size: 1.1rem;
|
||||
position: relative;
|
||||
background: var(--base-background);
|
||||
|
||||
> .value {
|
||||
background: none;
|
||||
width: 70%;
|
||||
text-align: center;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
@import url("./StatsCardV1/style.css");
|
||||
@import url("./SkillsCardV1/style.css");
|
||||
@import url("./RichEditor/style.css");
|
||||
@import url("./ArmourSheet/style.css");
|
||||
|
||||
@import url("./popover.css");
|
||||
@import url("./popovers/AmmoTracker/style.css");
|
||||
|
|
|
|||
27
templates/Apps/partials/item-header.hbs
Normal file
27
templates/Apps/partials/item-header.hbs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
{{!--
|
||||
Required parameters:
|
||||
"name" : the name of the item
|
||||
"system.quantity" : the quantity of the item
|
||||
"meta.idp" : the ID Prefix for the application
|
||||
--}}
|
||||
<header class="item-header">
|
||||
<div class="name-row">
|
||||
<input
|
||||
type="text"
|
||||
class="name"
|
||||
aria-label="{{ rc-i18n "Name" }}"
|
||||
name="name"
|
||||
value="{{item.name}}"
|
||||
{{disabled limited}}
|
||||
>
|
||||
<span aria-hidden="true">x</span>
|
||||
<input
|
||||
type="number"
|
||||
class="quantity"
|
||||
aria-label="{{ rc-i18n "RipCrypt.common.quantity" }}"
|
||||
data-tooltip="{{ rc-i18n "RipCrypt.common.quantity" }}"
|
||||
name="system.quantity"
|
||||
value="{{system.quantity}}"
|
||||
>
|
||||
</div>
|
||||
</header>
|
||||
|
|
@ -21,6 +21,7 @@
|
|||
all: revert-layer;
|
||||
--checkbox-checked-color: var(--accent-3);
|
||||
--checkbox-background-color: var(--accent-2);
|
||||
--checkbox-checkmark-color: black;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@layer resets, themes, elements, components, apps, exceptions;
|
||||
@layer resets, themes, elements, partials, apps, exceptions;
|
||||
|
||||
/* Resets */
|
||||
@import url("./resets/inputs.css") layer(resets);
|
||||
|
|
@ -19,6 +19,9 @@
|
|||
@import url("./elements/string-tags.css") layer(elements);
|
||||
@import url("./elements/table.css") layer(elements);
|
||||
|
||||
/* Partials */
|
||||
@import url("./partials/item-header.css") layer(partials);
|
||||
|
||||
/* Applications */
|
||||
@import url("../Apps/apps.css") layer(apps);
|
||||
|
||||
|
|
|
|||
11
templates/css/partials/item-header.css
Normal file
11
templates/css/partials/item-header.css
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
.item-header {
|
||||
.name-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) min-content 50px;
|
||||
gap: 4px;
|
||||
|
||||
.quantity {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue