Merge pull request #52 from Eldritch-Oliver/feature/armour-sheet-improvements
Armour/Shield Sheet Improvements
This commit is contained in:
commit
1228b32823
18 changed files with 614 additions and 104 deletions
|
|
@ -17,6 +17,7 @@
|
||||||
"RipCrypt": {
|
"RipCrypt": {
|
||||||
"sheet-names": {
|
"sheet-names": {
|
||||||
"AllItemsSheetV1": "RipCrypt Item Sheet",
|
"AllItemsSheetV1": "RipCrypt Item Sheet",
|
||||||
|
"ArmourSheet": "Armour Sheet",
|
||||||
"CombinedHeroSheet": "Hero Sheet",
|
"CombinedHeroSheet": "Hero Sheet",
|
||||||
"StatsCardV1": "Hero Stat Card",
|
"StatsCardV1": "Hero Stat Card",
|
||||||
"CraftCardV1": "Hero Craft Card",
|
"CraftCardV1": "Hero Craft Card",
|
||||||
|
|
@ -157,6 +158,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Apps": {
|
"Apps": {
|
||||||
|
"damage-reduction": "@RipCrypt.common.damage reduction",
|
||||||
"traits-range": "@RipCrypt.common.traits & @RipCrypt.common.range",
|
"traits-range": "@RipCrypt.common.traits & @RipCrypt.common.range",
|
||||||
"grit-skills": "@RipCrypt.common.abilities.grit Skills",
|
"grit-skills": "@RipCrypt.common.abilities.grit Skills",
|
||||||
"gait-skills": "@RipCrypt.common.abilities.gait Skills",
|
"gait-skills": "@RipCrypt.common.abilities.gait Skills",
|
||||||
|
|
@ -184,7 +186,8 @@
|
||||||
"star-button-tooltip": "Add Star",
|
"star-button-tooltip": "Add Star",
|
||||||
"unstar-button": "Remove {name} as a starred ammo",
|
"unstar-button": "Remove {name} as a starred ammo",
|
||||||
"unstar-button-tooltip": "Remove Star"
|
"unstar-button-tooltip": "Remove Star"
|
||||||
}
|
},
|
||||||
|
"protects-the-location": "Protects your {part}"
|
||||||
},
|
},
|
||||||
"notifs": {
|
"notifs": {
|
||||||
"error": {
|
"error": {
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,6 @@ export class CombinedHeroSheet extends GenericAppMixin(HandlebarsApplicationMixi
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
Logger.debug(`Context keys:`, Object.keys(ctx));
|
|
||||||
return ctx;
|
return ctx;
|
||||||
};
|
};
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ import { RichEditor } from "./RichEditor.mjs";
|
||||||
import { toBoolean } from "../consts.mjs";
|
import { toBoolean } from "../consts.mjs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A mixin that takes the class from HandlebarsApplicationMixin and
|
* A mixin that takes the class from HandlebarsApplicationMixin and combines it
|
||||||
|
* with utility functions / data that is used across all RipCrypt applications
|
||||||
*/
|
*/
|
||||||
export function GenericAppMixin(HandlebarsApp) {
|
export function GenericAppMixin(HandlebarsApp) {
|
||||||
class GenericRipCryptApp extends HandlebarsApp {
|
class GenericRipCryptApp extends HandlebarsApp {
|
||||||
|
|
@ -91,8 +92,9 @@ export function GenericAppMixin(HandlebarsApp) {
|
||||||
ctx.meta.idp = this.document?.uuid ?? this.id;
|
ctx.meta.idp = this.document?.uuid ?? this.id;
|
||||||
if (this.document) {
|
if (this.document) {
|
||||||
ctx.meta.limited = this.document.limited;
|
ctx.meta.limited = this.document.limited;
|
||||||
ctx.meta.editable = ctx.editable;
|
ctx.meta.editable = this.isEditable || game.user.isGM;
|
||||||
}
|
ctx.meta.embedded = this.document.isEmbedded;
|
||||||
|
};
|
||||||
delete ctx.editable;
|
delete ctx.editable;
|
||||||
|
|
||||||
return ctx;
|
return ctx;
|
||||||
|
|
|
||||||
135
module/Apps/ItemSheets/ArmourSheet.mjs
Normal file
135
module/Apps/ItemSheets/ArmourSheet.mjs
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
import { filePath } from "../../consts.mjs";
|
||||||
|
import { gameTerms } from "../../gameTerms.mjs";
|
||||||
|
import { GenericAppMixin } from "../GenericApp.mjs";
|
||||||
|
|
||||||
|
const { HandlebarsApplicationMixin } = foundry.applications.api;
|
||||||
|
const { ItemSheetV2 } = foundry.applications.sheets;
|
||||||
|
const { getProperty, hasProperty, setProperty } = foundry.utils;
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Customize how form data is extracted into an expanded object.
|
||||||
|
* @param {SubmitEvent|null} event The originating form submission event
|
||||||
|
* @param {HTMLFormElement} form The form element that was submitted
|
||||||
|
* @param {FormDataExtended} formData Processed data for the submitted form
|
||||||
|
* @returns {object} An expanded object of processed form data
|
||||||
|
* @throws {Error} Subclasses may throw validation errors here to prevent form submission
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
_processFormData(event, form, formData) {
|
||||||
|
const data = super._processFormData(event, form, formData);
|
||||||
|
|
||||||
|
if (hasProperty(data, `system.location`)) {
|
||||||
|
let locations = getProperty(data, `system.location`);
|
||||||
|
locations = locations.filter(value => value != null);
|
||||||
|
setProperty(data, `system.location`, locations);
|
||||||
|
};
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
// #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;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
56
module/Apps/components/ArmourSummary.mjs
Normal file
56
module/Apps/components/ArmourSummary.mjs
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
import { filePath } from "../../consts.mjs";
|
||||||
|
import { StyledShadowElement } from "./mixins/StyledShadowElement.mjs";
|
||||||
|
|
||||||
|
const { renderTemplate } = foundry.applications.handlebars;
|
||||||
|
|
||||||
|
export class ArmourSummary extends StyledShadowElement(HTMLElement) {
|
||||||
|
static elementName = `armour-summary`;
|
||||||
|
static formAssociated = false;
|
||||||
|
|
||||||
|
/* Stuff for the mixin to use */
|
||||||
|
static _stylePath = `css/components/armour-summary.css`;
|
||||||
|
#container;
|
||||||
|
|
||||||
|
get type() {
|
||||||
|
return this.getAttribute(`type`) ?? `hero`;
|
||||||
|
};
|
||||||
|
|
||||||
|
set type(newValue) {
|
||||||
|
this.setAttribute(`type`, newValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
_mounted = false;
|
||||||
|
async connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
if (this._mounted) { return };
|
||||||
|
|
||||||
|
/*
|
||||||
|
This converts all of the double-dash prefixed properties on the element to
|
||||||
|
CSS variables so that they don't all need to be provided by doing style=""
|
||||||
|
*/
|
||||||
|
for (const attrVar of this.attributes) {
|
||||||
|
if (attrVar.name?.startsWith(`var:`)) {
|
||||||
|
const prop = attrVar.name.replace(`var:`, ``);
|
||||||
|
this.style.setProperty(`--` + prop, attrVar.value);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
this.#container = document.createElement(`div`);
|
||||||
|
this.#container.classList = `person`;
|
||||||
|
|
||||||
|
this.#container.innerHTML = await renderTemplate(
|
||||||
|
filePath(`templates/components/armour-summary.hbs`),
|
||||||
|
{ type: this.type },
|
||||||
|
);
|
||||||
|
|
||||||
|
this._shadow.appendChild(this.#container);
|
||||||
|
|
||||||
|
this._mounted = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
if (!this._mounted) { return };
|
||||||
|
this._mounted = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
|
import { ArmourSummary } from "./ArmourSummary.mjs";
|
||||||
import { Logger } from "../../utils/Logger.mjs";
|
import { Logger } from "../../utils/Logger.mjs";
|
||||||
import { RipCryptBorder } from "./RipCryptBorder.mjs";
|
import { RipCryptBorder } from "./RipCryptBorder.mjs";
|
||||||
import { RipCryptIcon } from "./Icon.mjs";
|
import { RipCryptIcon } from "./Icon.mjs";
|
||||||
import { RipCryptSVGLoader } from "./svgLoader.mjs";
|
import { RipCryptSVGLoader } from "./svgLoader.mjs";
|
||||||
|
|
||||||
const components = [
|
const components = [
|
||||||
|
ArmourSummary,
|
||||||
RipCryptIcon,
|
RipCryptIcon,
|
||||||
RipCryptSVGLoader,
|
RipCryptSVGLoader,
|
||||||
RipCryptBorder,
|
RipCryptBorder,
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { Logger } from "../../utils/Logger.mjs";
|
||||||
import { requiredInteger } from "../helpers.mjs";
|
import { requiredInteger } from "../helpers.mjs";
|
||||||
|
|
||||||
const { fields } = foundry.data;
|
const { fields } = foundry.data;
|
||||||
const { hasProperty, diffObject, mergeObject } = foundry.utils;
|
const { getProperty, diffObject, mergeObject } = foundry.utils;
|
||||||
|
|
||||||
/** Used for Armour and Shields */
|
/** Used for Armour and Shields */
|
||||||
export class ArmourData extends CommonItemData {
|
export class ArmourData extends CommonItemData {
|
||||||
|
|
@ -19,16 +19,16 @@ export class ArmourData extends CommonItemData {
|
||||||
blank: false,
|
blank: false,
|
||||||
trim: true,
|
trim: true,
|
||||||
nullable: false,
|
nullable: false,
|
||||||
|
required: true,
|
||||||
options: Object.values(gameTerms.Anatomy),
|
options: Object.values(gameTerms.Anatomy),
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
nullable: false,
|
nullable: false,
|
||||||
required: true,
|
initial: [],
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
equipped: new fields.BooleanField({
|
equipped: new fields.BooleanField({
|
||||||
initial: false,
|
initial: false,
|
||||||
required: true,
|
|
||||||
nullable: false,
|
nullable: false,
|
||||||
}),
|
}),
|
||||||
weight: new fields.StringField({
|
weight: new fields.StringField({
|
||||||
|
|
@ -59,7 +59,7 @@ export class ArmourData extends CommonItemData {
|
||||||
const diff = diffObject(this.parent._source, changes);
|
const diff = diffObject(this.parent._source, changes);
|
||||||
let valid = await super._preUpdate(changes, options, user);
|
let valid = await super._preUpdate(changes, options, user);
|
||||||
|
|
||||||
if (hasProperty(diff, `system.equipped`) && !this._canEquip()) {
|
if (getProperty(diff, `system.equipped`) && !this._canEquip()) {
|
||||||
ui.notifications.error(
|
ui.notifications.error(
|
||||||
localizer(
|
localizer(
|
||||||
`RipCrypt.notifs.error.cannot-equip`,
|
`RipCrypt.notifs.error.cannot-equip`,
|
||||||
|
|
@ -80,7 +80,10 @@ export class ArmourData extends CommonItemData {
|
||||||
return valid;
|
return valid;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Used to tell the preUpdate logic whether or not to prevent the */
|
/**
|
||||||
|
* Used to tell the preUpdate logic whether or not to prevent the item from
|
||||||
|
* being equipped or not.
|
||||||
|
*/
|
||||||
_canEquip() {
|
_canEquip() {
|
||||||
const parent = this.parent;
|
const parent = this.parent;
|
||||||
if (!parent.isEmbedded || !(parent.parent instanceof Actor)) {
|
if (!parent.isEmbedded || !(parent.parent instanceof Actor)) {
|
||||||
|
|
@ -94,7 +97,7 @@ export class ArmourData extends CommonItemData {
|
||||||
};
|
};
|
||||||
|
|
||||||
const slots = parent.parent.system.equippedArmour ?? {};
|
const slots = parent.parent.system.equippedArmour ?? {};
|
||||||
Logger.debug(`slots`, slots);
|
|
||||||
for (const locationTag of this.location) {
|
for (const locationTag of this.location) {
|
||||||
if (slots[locationTag.toLowerCase()] != null) {
|
if (slots[locationTag.toLowerCase()] != null) {
|
||||||
Logger.error(`Unable to equip multiple items in the same slot`);
|
Logger.error(`Unable to equip multiple items in the same slot`);
|
||||||
|
|
@ -110,90 +113,4 @@ export class ArmourData extends CommonItemData {
|
||||||
return [...this.location].join(`, `);
|
return [...this.location].join(`, `);
|
||||||
};
|
};
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
// #region Sheet Data
|
|
||||||
getFormFields(_ctx) {
|
|
||||||
const fields = [
|
|
||||||
{
|
|
||||||
id: `quantity`,
|
|
||||||
type: `integer`,
|
|
||||||
label: `RipCrypt.common.quantity`,
|
|
||||||
path: `system.quantity`,
|
|
||||||
value: this.quantity,
|
|
||||||
min: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: `access`,
|
|
||||||
type: `dropdown`,
|
|
||||||
label: `RipCrypt.common.access`,
|
|
||||||
path: `system.access`,
|
|
||||||
value: this.access,
|
|
||||||
limited: false,
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
label: `RipCrypt.common.empty`,
|
|
||||||
value: ``,
|
|
||||||
},
|
|
||||||
...gameTerms.Access.map(opt => ({
|
|
||||||
label: `RipCrypt.common.accessLevels.${opt}`,
|
|
||||||
value: opt,
|
|
||||||
})),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: `cost`,
|
|
||||||
type: `cost`,
|
|
||||||
label: `RipCrypt.common.cost`,
|
|
||||||
gold: this.cost.gold,
|
|
||||||
silver: this.cost.silver,
|
|
||||||
copper: this.cost.copper,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: `weight`,
|
|
||||||
type: `dropdown`,
|
|
||||||
label: `RipCrypt.common.weightRating`,
|
|
||||||
path: `system.weight`,
|
|
||||||
value: this.weight,
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
label: `RipCrypt.common.empty`,
|
|
||||||
value: null,
|
|
||||||
},
|
|
||||||
...Object.values(gameTerms.WeightRatings).map(opt => ({
|
|
||||||
label: `RipCrypt.common.weightRatings.${opt}`,
|
|
||||||
value: opt,
|
|
||||||
})),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: `location`,
|
|
||||||
type: `string-set`,
|
|
||||||
label: `RipCrypt.common.location`,
|
|
||||||
placeholder: `RipCrypt.Apps.location-placeholder`,
|
|
||||||
path: `system.location`,
|
|
||||||
value: this.locationString,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: `protection`,
|
|
||||||
type: `integer`,
|
|
||||||
label: `RipCrypt.common.protection`,
|
|
||||||
value: this.protection,
|
|
||||||
path: `system.protection`,
|
|
||||||
min: 0,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
if (this.parent.isEmbedded) {
|
|
||||||
fields.push({
|
|
||||||
id: `equipped`,
|
|
||||||
type: `boolean`,
|
|
||||||
label: `RipCrypt.common.equipped`,
|
|
||||||
value: this.equipped,
|
|
||||||
path: `system.equipped`,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return fields;
|
|
||||||
};
|
|
||||||
// #endregion
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
// Applications
|
// Applications
|
||||||
import { AllItemSheetV1 } from "../Apps/ItemSheets/AllItemSheetV1.mjs";
|
import { AllItemSheetV1 } from "../Apps/ItemSheets/AllItemSheetV1.mjs";
|
||||||
|
import { ArmourSheet } from "../Apps/ItemSheets/ArmourSheet.mjs";
|
||||||
import { CombinedHeroSheet } from "../Apps/ActorSheets/CombinedHeroSheet.mjs";
|
import { CombinedHeroSheet } from "../Apps/ActorSheets/CombinedHeroSheet.mjs";
|
||||||
import { CraftCardV1 } from "../Apps/ActorSheets/CraftCardV1.mjs";
|
import { CraftCardV1 } from "../Apps/ActorSheets/CraftCardV1.mjs";
|
||||||
import { DelveDiceHUD } from "../Apps/DelveDiceHUD.mjs";
|
import { DelveDiceHUD } from "../Apps/DelveDiceHUD.mjs";
|
||||||
|
|
@ -37,7 +38,6 @@ import { registerUserSettings } from "../settings/userSettings.mjs";
|
||||||
import { registerWorldSettings } from "../settings/worldSettings.mjs";
|
import { registerWorldSettings } from "../settings/worldSettings.mjs";
|
||||||
|
|
||||||
const { Items, Actors } = foundry.documents.collections;
|
const { Items, Actors } = foundry.documents.collections;
|
||||||
const { ItemSheet, ActorSheet } = foundry.appv1.sheets;
|
|
||||||
|
|
||||||
Hooks.once(`init`, () => {
|
Hooks.once(`init`, () => {
|
||||||
Logger.log(`Initializing`);
|
Logger.log(`Initializing`);
|
||||||
|
|
@ -74,10 +74,6 @@ Hooks.once(`init`, () => {
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
// #region Sheets
|
// #region Sheets
|
||||||
// Unregister core sheets
|
|
||||||
Items.unregisterSheet(`core`, ItemSheet);
|
|
||||||
Actors.unregisterSheet(`core`, ActorSheet);
|
|
||||||
|
|
||||||
// #region Actors
|
// #region Actors
|
||||||
Actors.registerSheet(game.system.id, CombinedHeroSheet, {
|
Actors.registerSheet(game.system.id, CombinedHeroSheet, {
|
||||||
makeDefault: true,
|
makeDefault: true,
|
||||||
|
|
@ -114,6 +110,16 @@ Hooks.once(`init`, () => {
|
||||||
label: `RipCrypt.sheet-names.AllItemsSheetV1`,
|
label: `RipCrypt.sheet-names.AllItemsSheetV1`,
|
||||||
themes: AllItemSheetV1.themes,
|
themes: AllItemSheetV1.themes,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Items.registerSheet(game.system.id, ArmourSheet, {
|
||||||
|
makeDefault: true,
|
||||||
|
types: [`armour`, `shield`],
|
||||||
|
label: `RipCrypt.sheet-names.ArmourSheet`,
|
||||||
|
themes: ArmourSheet.themes,
|
||||||
|
});
|
||||||
|
Items.unregisterSheet(game.system.id, AllItemSheetV1, {
|
||||||
|
types: [`armour`, `shield`],
|
||||||
|
});
|
||||||
// #endregion
|
// #endregion
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<div class="AllItemSheetV1">
|
<div class="AllItemSheetV1">
|
||||||
{{#if meta.editable}}
|
{{#if meta.editable}}
|
||||||
<label for="{{meta.idp}}-name">Name</label>
|
<label for="{{meta.idp}}-name">{{ rc-i18n "Name" }}</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="{{meta.idp}}-name"
|
id="{{meta.idp}}-name"
|
||||||
|
|
|
||||||
140
templates/Apps/ArmourSheet/content.hbs
Normal file
140
templates/Apps/ArmourSheet/content.hbs
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
<div class="contents">
|
||||||
|
<div class="contents__left">
|
||||||
|
<label for="{{ meta.idp }}-access">
|
||||||
|
{{ rc-i18n "RipCrypt.common.access" }}
|
||||||
|
</label>
|
||||||
|
<select name="system.access" id="{{ meta.idp }}-access">
|
||||||
|
{{ rc-options system.access accesses localize=true }}
|
||||||
|
</select>
|
||||||
|
<label for="{{ meta.idp }}-weight">
|
||||||
|
{{ rc-i18n "RipCrypt.common.weightRating" }}
|
||||||
|
</label>
|
||||||
|
<select name="system.weight" id="{{ meta.idp }}-weight">
|
||||||
|
{{ rc-options system.weight weights localize=true }}
|
||||||
|
</select>
|
||||||
|
{{#if meta.embedded}}
|
||||||
|
<label for="{{meta.idp}}-equipped">
|
||||||
|
{{ rc-i18n "RipCrypt.common.equipped" }}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="{{meta.idp}}-equipped"
|
||||||
|
name="system.equipped"
|
||||||
|
{{ checked system.equipped }}
|
||||||
|
>
|
||||||
|
{{/if}}
|
||||||
|
<rc-border
|
||||||
|
var:border-color="var(--accent-1)"
|
||||||
|
>
|
||||||
|
<span slot="title">
|
||||||
|
{{ rc-i18n "RipCrypt.common.cost" }}
|
||||||
|
</span>
|
||||||
|
<div slot="content" class="content">
|
||||||
|
<label
|
||||||
|
for="{{ meta.idp }}-gold"
|
||||||
|
>
|
||||||
|
{{ rc-i18n "RipCrypt.common.currency.gold" }}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="{{ meta.idp }}-gold"
|
||||||
|
name="system.cost.gold"
|
||||||
|
value="{{ system.cost.gold }}"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
for="{{ meta.idp }}-silver"
|
||||||
|
>
|
||||||
|
{{ rc-i18n "RipCrypt.common.currency.silver" }}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="{{ meta.idp }}-silver"
|
||||||
|
name="system.cost.silver"
|
||||||
|
value="{{ system.cost.silver }}"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
for="{{ meta.idp }}-copper"
|
||||||
|
>
|
||||||
|
{{ rc-i18n "RipCrypt.common.currency.copper" }}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="{{ meta.idp }}-copper"
|
||||||
|
name="system.cost.copper"
|
||||||
|
value="{{ system.cost.copper }}"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</rc-border>
|
||||||
|
</div>
|
||||||
|
<hr class="vertical">
|
||||||
|
<div class="contents__right">
|
||||||
|
<span class="section-pill">
|
||||||
|
{{ rc-i18n "RipCrypt.common.location" }}
|
||||||
|
</span>
|
||||||
|
<armour-summary
|
||||||
|
var:row-gap="8px"
|
||||||
|
>
|
||||||
|
<div slot="head">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
aria-label="{{
|
||||||
|
rc-i18n
|
||||||
|
"RipCrypt.Apps.protects-the-location"
|
||||||
|
part="@RipCrypt.common.anatomy.head"
|
||||||
|
}}"
|
||||||
|
value="head"
|
||||||
|
{{ checked protects.head }}
|
||||||
|
name="system.location"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div slot="body">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
aria-label="{{
|
||||||
|
rc-i18n
|
||||||
|
"RipCrypt.Apps.protects-the-location"
|
||||||
|
part="@RipCrypt.common.anatomy.body"
|
||||||
|
}}"
|
||||||
|
value="body"
|
||||||
|
{{ checked protects.body }}
|
||||||
|
name="system.location"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div slot="arms">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
aria-label="{{
|
||||||
|
rc-i18n
|
||||||
|
"RipCrypt.Apps.protects-the-location"
|
||||||
|
part="@RipCrypt.common.anatomy.arms"
|
||||||
|
}}"
|
||||||
|
value="arms"
|
||||||
|
{{ checked protects.arms }}
|
||||||
|
name="system.location"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div slot="legs">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
aria-label="{{
|
||||||
|
rc-i18n
|
||||||
|
"RipCrypt.Apps.protects-the-location"
|
||||||
|
part="@RipCrypt.common.anatomy.legs"
|
||||||
|
}}"
|
||||||
|
value="legs"
|
||||||
|
{{ checked protects.legs }}
|
||||||
|
name="system.location"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</armour-summary>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
class="center"
|
||||||
|
aria-label="{{ rc-i18n "RipCrypt.Apps.damage-reduction" }}"
|
||||||
|
data-tooltip="{{ rc-i18n "RipCrypt.Apps.damage-reduction" }}"
|
||||||
|
min="0"
|
||||||
|
name="system.protection"
|
||||||
|
value="{{ system.protection }}"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
135
templates/Apps/ArmourSheet/style.css
Normal file
135
templates/Apps/ArmourSheet/style.css
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
input[type="checkbox"] {
|
||||||
|
justify-self: end;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.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("./StatsCardV1/style.css");
|
||||||
@import url("./SkillsCardV1/style.css");
|
@import url("./SkillsCardV1/style.css");
|
||||||
@import url("./RichEditor/style.css");
|
@import url("./RichEditor/style.css");
|
||||||
|
@import url("./ArmourSheet/style.css");
|
||||||
|
|
||||||
@import url("./popover.css");
|
@import url("./popover.css");
|
||||||
@import url("./popovers/AmmoTracker/style.css");
|
@import url("./popovers/AmmoTracker/style.css");
|
||||||
|
|
|
||||||
28
templates/Apps/partials/item-header.hbs
Normal file
28
templates/Apps/partials/item-header.hbs
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
{{!--
|
||||||
|
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 meta.limited}}
|
||||||
|
autocomplete="off"
|
||||||
|
>
|
||||||
|
<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>
|
||||||
35
templates/components/armour-summary.hbs
Normal file
35
templates/components/armour-summary.hbs
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
{{#if (eq type "hero")}}
|
||||||
|
<rc-svg
|
||||||
|
var:fill="var(--section-header-background)"
|
||||||
|
var:stroke="var(--base-background)"
|
||||||
|
var:stroke-width="2"
|
||||||
|
var:stroke-linejoin="rounded"
|
||||||
|
class="silhouette"
|
||||||
|
name="hero-silhouette"
|
||||||
|
></rc-svg>
|
||||||
|
{{else}}
|
||||||
|
<rc-svg
|
||||||
|
var:fill="var(--section-header-background)"
|
||||||
|
var:stroke="var(--base-background)"
|
||||||
|
var:stroke-width="2"
|
||||||
|
var:stroke-linejoin="rounded"
|
||||||
|
class="silhouette"
|
||||||
|
name="geist-silhouette.v2"
|
||||||
|
></rc-svg>
|
||||||
|
{{/if}}
|
||||||
|
<div class="head">
|
||||||
|
<slot name="head"></slot>
|
||||||
|
<span class="label" aria-hidden="true">{{ rc-i18n "RipCrypt.common.anatomy.head" }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="body">
|
||||||
|
<slot name="body"></slot>
|
||||||
|
<span class="label" aria-hidden="true">{{ rc-i18n "RipCrypt.common.anatomy.body" }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="arms">
|
||||||
|
<slot name="arms"></slot>
|
||||||
|
<span class="label" aria-hidden="true">{{ rc-i18n "RipCrypt.common.anatomy.arms" }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="legs">
|
||||||
|
<slot name="legs"></slot>
|
||||||
|
<span class="label" aria-hidden="true">{{ rc-i18n "RipCrypt.common.anatomy.legs" }}</span>
|
||||||
|
</div>
|
||||||
34
templates/css/components/armour-summary.css
Normal file
34
templates/css/components/armour-summary.css
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
:host {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.person {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
grid-template-rows: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1.2fr);
|
||||||
|
justify-items: center;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
row-gap: var(--row-gap);
|
||||||
|
|
||||||
|
> div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
> rc-svg {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 58%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.head, .body, .legs { grid-column: 1; }
|
||||||
|
.arms { grid-column: 2; }
|
||||||
|
.head { grid-row: 1; }
|
||||||
|
.body, .arms { grid-row: 2; }
|
||||||
|
.legs { grid-row: 3; }
|
||||||
|
}
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
background: var(--input-background);
|
background: var(--input-background);
|
||||||
color: var(--input-text);
|
color: var(--input-text);
|
||||||
padding: 0px 4px;
|
padding: 0px 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
&[type="text"],
|
&[type="text"],
|
||||||
&[type="number"] {
|
&[type="number"] {
|
||||||
|
|
@ -19,8 +20,10 @@
|
||||||
|
|
||||||
&[type="checkbox"] {
|
&[type="checkbox"] {
|
||||||
all: revert-layer;
|
all: revert-layer;
|
||||||
|
cursor: pointer;
|
||||||
--checkbox-checked-color: var(--accent-3);
|
--checkbox-checked-color: var(--accent-3);
|
||||||
--checkbox-background-color: var(--accent-2);
|
--checkbox-background-color: var(--accent-2);
|
||||||
|
--checkbox-checkmark-color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
@layer resets, themes, elements, components, apps, exceptions;
|
@layer resets, themes, elements, partials, apps, exceptions;
|
||||||
|
|
||||||
/* Resets */
|
/* Resets */
|
||||||
@import url("./resets/inputs.css") layer(resets);
|
@import url("./resets/inputs.css") layer(resets);
|
||||||
|
|
@ -19,6 +19,9 @@
|
||||||
@import url("./elements/string-tags.css") layer(elements);
|
@import url("./elements/string-tags.css") layer(elements);
|
||||||
@import url("./elements/table.css") layer(elements);
|
@import url("./elements/table.css") layer(elements);
|
||||||
|
|
||||||
|
/* Partials */
|
||||||
|
@import url("./partials/item-header.css") layer(partials);
|
||||||
|
|
||||||
/* Applications */
|
/* Applications */
|
||||||
@import url("../Apps/apps.css") layer(apps);
|
@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