Compare commits
52 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 86ff3c9b79 | |||
| 1f213a890a | |||
| 3ca1de9645 | |||
| 1eebde246e | |||
| 0e966f4b6a | |||
| 4bbca4e786 | |||
| 57b902f986 | |||
| 48760343ce | |||
| e128f2a828 | |||
| 76f5a4f27a | |||
| 687440c0da | |||
| 9955b76956 | |||
| fc24caf08a | |||
| 380ed3fe15 | |||
| 6f3139edf1 | |||
| f666fd2fa9 | |||
| 13619b6a09 | |||
| f05311d7c8 | |||
| e4e1f30fcb | |||
| cfa352e5e0 | |||
| 4c05171e04 | |||
| 441930a5e5 | |||
| 66d2452d1d | |||
| c9ad0d8a4e | |||
| 2d6db98530 | |||
| 9d49a45f0d | |||
| b20240699b | |||
| e4b6a5c635 | |||
| 25840a65e9 | |||
| c65e960bd7 | |||
| 0f1ba90161 | |||
| 1892a02794 | |||
| 9f19072426 | |||
| f5c7e1c4bc | |||
| 44372d0a17 | |||
| 6e2dfa1cf1 | |||
| 92553cb1f1 | |||
| 2b0fbdfa8b | |||
| 704ff4672a | |||
| 96eccc62f2 | |||
| 761f0b6563 | |||
| 23a402f11a | |||
| 6b03d62234 | |||
| 40f0e1ea2c | |||
| 76d8f3675c | |||
| f2420a6848 | |||
| ce81212bbe | |||
| c6ec60b5bf | |||
| d9d66abf27 | |||
| 94b3ec923b | |||
| 2518c7cf05 | |||
| f91c3d2419 |
50 changed files with 1590 additions and 121 deletions
34
.vscode/components.html-data.json
vendored
34
.vscode/components.html-data.json
vendored
|
|
@ -2,24 +2,36 @@
|
||||||
"version": 1.1,
|
"version": 1.1,
|
||||||
"tags": [
|
"tags": [
|
||||||
{
|
{
|
||||||
"name": "dd-incrementer",
|
"name": "taf-icon",
|
||||||
"description": "A number input that allows more flexible increase/decrease buttons",
|
"description": "Loads an icon asynchronously, caching the result for future uses",
|
||||||
"attributes": [
|
"attributes": [
|
||||||
{ "name": "value", "description": "The initial value to put in the input" },
|
{ "name": "name", "description": "The name of the icon, this is relative to the assets folder of the system" },
|
||||||
{ "name": "name", "description": "The form name to use when this input is used to submit data" },
|
{ "name": "path", "description": "The full path of the icon, this will only be used if `name` isn't provided or fails to fetch." }
|
||||||
{ "name": "min", "description": "The minimum value that this input can contain" },
|
|
||||||
{ "name": "max", "description": "The maximum value that this input can contain" },
|
|
||||||
{ "name": "smallStep", "description": "The value that the input is changed by when clicking a delta button or using the up/down arrow key" },
|
|
||||||
{ "name": "largeStep", "description": "The value that the input is changed by when clicking a delta button with control held or using the page up/ page down arrow key" }
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "dd-icon",
|
"name": "taf-svg",
|
||||||
"description": "Loads an icon asynchronously, caching the result for future uses",
|
"description": "Loads an SVG file asynchronously, caching the result for future uses",
|
||||||
"attributes": [
|
"attributes": [
|
||||||
{ "name": "name", "description": "The name of the icon, this is relative to the assets folder of the dotdungeon system" },
|
{ "name": "name", "description": "The name of the icon, this is relative to the assets folder of the system" },
|
||||||
{ "name": "path", "description": "The full path of the icon, this will only be used if `name` isn't provided or fails to fetch." }
|
{ "name": "path", "description": "The full path of the icon, this will only be used if `name` isn't provided or fails to fetch." }
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "taf-toggle",
|
||||||
|
"description": "A conveniency component for a toggle switch",
|
||||||
|
"attributes": [
|
||||||
|
{
|
||||||
|
"name": "type",
|
||||||
|
"description": "The type of toggle that this should be",
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"name": "round",
|
||||||
|
"description": "The slider is a full circle"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"globalAttributes": [],
|
"globalAttributes": [],
|
||||||
|
|
|
||||||
1
assets/icons/chevron.svg
Normal file
1
assets/icons/chevron.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path d="M7.637 25.637a8.997 8.997 0 0 1 12.727 0L50 55.274l29.637-29.637c3.515-3.516 9.21-3.516 12.727 0s3.515 9.21 0 12.727l-36 36a8.997 8.997 0 0 1-12.727 0l-36-36a8.997 8.997 0 0 1 0-12.727" fill-rule="evenodd" fill="var(--colour, currentcolor)"/></svg>
|
||||||
|
After Width: | Height: | Size: 319 B |
1
assets/icons/plus.svg
Normal file
1
assets/icons/plus.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path d="M81.801 41.801H58.199V18.199c0-4.5-3.7-8.2-8.2-8.2s-8.198 3.7-8.198 8.2v23.602H18.199c-4.5 0-8.2 3.7-8.2 8.2s3.7 8.198 8.2 8.198H41.8v23.602c0 4.5 3.699 8.2 8.199 8.2s8.199-3.7 8.199-8.2V58.2H81.8c4.5 0 8.2-3.699 8.2-8.199s-3.7-8.199-8.2-8.199"/></svg>
|
||||||
|
After Width: | Height: | Size: 323 B |
|
|
@ -22,6 +22,8 @@ export default [
|
||||||
Hooks: `readonly`,
|
Hooks: `readonly`,
|
||||||
ui: `readonly`,
|
ui: `readonly`,
|
||||||
foundry: `readonly`,
|
foundry: `readonly`,
|
||||||
|
Actor: `readonly`,
|
||||||
|
Item: `readonly`,
|
||||||
ChatMessage: `readonly`,
|
ChatMessage: `readonly`,
|
||||||
ActiveEffect: `readonly`,
|
ActiveEffect: `readonly`,
|
||||||
fromUuid: `readonly`,
|
fromUuid: `readonly`,
|
||||||
|
|
@ -29,7 +31,7 @@ export default [
|
||||||
|
|
||||||
// v14 Additions:
|
// v14 Additions:
|
||||||
_loc: `readonly`,
|
_loc: `readonly`,
|
||||||
_del: `reaonly`,
|
_del: `readonly`,
|
||||||
_replace: `readonly`,
|
_replace: `readonly`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -72,7 +74,16 @@ export default [
|
||||||
"@stylistic/space-infix-ops": `warn`,
|
"@stylistic/space-infix-ops": `warn`,
|
||||||
"@stylistic/eol-last": `warn`,
|
"@stylistic/eol-last": `warn`,
|
||||||
"@stylistic/operator-linebreak": [`warn`, `before`],
|
"@stylistic/operator-linebreak": [`warn`, `before`],
|
||||||
"@stylistic/indent": [`warn`, `tab`],
|
"@stylistic/indent": [
|
||||||
|
`warn`,
|
||||||
|
`tab`,
|
||||||
|
{
|
||||||
|
SwitchCase: 1,
|
||||||
|
ignoredNodes: [
|
||||||
|
`.superClass CallExpression`,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
"@stylistic/brace-style": [`off`],
|
"@stylistic/brace-style": [`off`],
|
||||||
"@stylistic/quotes": [`warn`, `backtick`, { "avoidEscape": true }],
|
"@stylistic/quotes": [`warn`, `backtick`, { "avoidEscape": true }],
|
||||||
"@stylistic/comma-dangle": [`warn`, { arrays: `always-multiline`, objects: `always-multiline`, imports: `always-multiline`, exports: `always-multiline`, functions: `always-multiline` }],
|
"@stylistic/comma-dangle": [`warn`, { arrays: `always-multiline`, objects: `always-multiline`, imports: `always-multiline`, exports: `always-multiline`, functions: `always-multiline` }],
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,18 @@
|
||||||
"TYPES": {
|
"TYPES": {
|
||||||
"Actor": {
|
"Actor": {
|
||||||
"player": "Player"
|
"player": "Player"
|
||||||
|
},
|
||||||
|
"Item": {
|
||||||
|
"generic": "Generic Item"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"taf": {
|
"taf": {
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"actorDefaultAttributes": {
|
||||||
|
"name": "Remove Default Attributes",
|
||||||
|
"hint": "This removes the default attributes that are applied when a new actor is created, making it so that no attributes get created alongside the actor.",
|
||||||
|
"label": "Remove Attributes"
|
||||||
|
},
|
||||||
"canPlayersManageAttributes": {
|
"canPlayersManageAttributes": {
|
||||||
"name": "Players Can Manage Attributes",
|
"name": "Players Can Manage Attributes",
|
||||||
"hint": "This allows players who have edit access to a document to be able to edit what attributes those characters have via the attribute editor"
|
"hint": "This allows players who have edit access to a document to be able to edit what attributes those characters have via the attribute editor"
|
||||||
|
|
@ -31,16 +39,16 @@
|
||||||
"true": "Resizable"
|
"true": "Resizable"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"actorDefaultAttributes": {
|
"weightUnit": {
|
||||||
"name": "Remove Default Attributes",
|
"name": "Weight Unit",
|
||||||
"hint": "This removes the default attributes that are applied when a new actor is created, making it so that no attributes get created alongside the actor.",
|
"hint": "This unit is used to display the units for the weights of items and carrying capacity of actors. This does NOTHING beyond adding the unit into the displays, it will not automatically convert between any units."
|
||||||
"label": "Remove Attributes"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sheet-names": {
|
"sheet-names": {
|
||||||
"PlayerSheet": "Player Sheet",
|
"PlayerSheet": "Player Sheet",
|
||||||
"SingleModePlayerSheet": "Player Sheet (Always Editing)",
|
"SingleModePlayerSheet": "Player Sheet (Always Editing)",
|
||||||
"AttributeOnlyPlayerSheet": "Player Sheet (Attributes Only)"
|
"AttributeOnlyPlayerSheet": "Player Sheet (Attributes Only)",
|
||||||
|
"GenericItemSheet": "System Item Sheet"
|
||||||
},
|
},
|
||||||
"misc": {
|
"misc": {
|
||||||
"Key": "Key",
|
"Key": "Key",
|
||||||
|
|
@ -51,8 +59,15 @@
|
||||||
"confirm-and-close": "Confirm and Close",
|
"confirm-and-close": "Confirm and Close",
|
||||||
"save-and-close": "Save and Close",
|
"save-and-close": "Save and Close",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
|
"edit": "Edit",
|
||||||
"resizable": "Resizable",
|
"resizable": "Resizable",
|
||||||
"not-resizable": "Not Resizable"
|
"not-resizable": "Not Resizable",
|
||||||
|
"item": {
|
||||||
|
"weight": "Weight",
|
||||||
|
"quantity": "Quantity",
|
||||||
|
"equipped": "Equipped",
|
||||||
|
"group": "Group"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"Apps": {
|
"Apps": {
|
||||||
"Ask": {
|
"Ask": {
|
||||||
|
|
@ -69,8 +84,21 @@
|
||||||
},
|
},
|
||||||
"PlayerSheet": {
|
"PlayerSheet": {
|
||||||
"manage-attributes": "Manage Attributes",
|
"manage-attributes": "Manage Attributes",
|
||||||
|
"create-item": "Create Embedded Item",
|
||||||
"current-value": "Current value",
|
"current-value": "Current value",
|
||||||
"max-value": "Maximum value"
|
"max-value": "Maximum value",
|
||||||
|
"carry-capacity-used": "({percent}% Used)",
|
||||||
|
"carrying-capacity": {
|
||||||
|
"placeholder": "Unlimited",
|
||||||
|
"title": "Carrying Capacity:",
|
||||||
|
"label": "Maximum carrying weight"
|
||||||
|
},
|
||||||
|
"total-weight": "Total weight",
|
||||||
|
"toggle-item-description": "Show/Hide Item Description",
|
||||||
|
"tab-names": {
|
||||||
|
"content": "Content",
|
||||||
|
"items": "Items"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"QueryStatus": {
|
"QueryStatus": {
|
||||||
"title": "Information Request Status",
|
"title": "Information Request Status",
|
||||||
|
|
|
||||||
|
|
@ -13,24 +13,18 @@ import { toID } from "./utils/toID.mjs";
|
||||||
|
|
||||||
const { deepFreeze } = foundry.utils;
|
const { deepFreeze } = foundry.utils;
|
||||||
|
|
||||||
Object.defineProperty(
|
export const api = deepFreeze({
|
||||||
globalThis,
|
DialogManager,
|
||||||
`taf`,
|
QueryManager,
|
||||||
{
|
Apps: {
|
||||||
value: deepFreeze({
|
Ask,
|
||||||
DialogManager,
|
AttributeManager,
|
||||||
QueryManager,
|
PlayerSheet,
|
||||||
Apps: {
|
QueryStatus,
|
||||||
Ask,
|
|
||||||
AttributeManager,
|
|
||||||
PlayerSheet,
|
|
||||||
QueryStatus,
|
|
||||||
},
|
|
||||||
utils: {
|
|
||||||
attributeSorter,
|
|
||||||
localizer,
|
|
||||||
toID,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
);
|
utils: {
|
||||||
|
attributeSorter,
|
||||||
|
localizer,
|
||||||
|
toID,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { __ID__, filePath } from "../consts.mjs";
|
import { __ID__, filePath } from "../consts.mjs";
|
||||||
import { attributeSorter } from "../utils/attributeSort.mjs";
|
|
||||||
import { ask } from "../utils/DialogManager.mjs";
|
import { ask } from "../utils/DialogManager.mjs";
|
||||||
|
import { attributeSorter } from "../utils/attributeSort.mjs";
|
||||||
import { localizer } from "../utils/localizer.mjs";
|
import { localizer } from "../utils/localizer.mjs";
|
||||||
import { toID } from "../utils/toID.mjs";
|
import { toID } from "../utils/toID.mjs";
|
||||||
|
|
||||||
|
|
@ -29,7 +29,7 @@ export class AttributeManager extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
label: `Save As Defaults`,
|
label: `Save As Defaults`,
|
||||||
visible: () => game.user.isGM,
|
visible: () => game.user.isGM,
|
||||||
action: `saveAsDefault`,
|
action: `saveAsDefault`,
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
|
|
@ -137,7 +137,8 @@ export class AttributeManager extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
const attrs = [];
|
const attrs = [];
|
||||||
for (const [id, data] of Object.entries(this.#attributes)) {
|
for (const [id, data] of Object.entries(this.#attributes)) {
|
||||||
if (data == null) { continue };
|
if (data == null) { continue };
|
||||||
if (game.release.generation >= 14 && data == _del) continue;
|
// Remove with issue: Foundry/taf#54
|
||||||
|
if (game.release.generation >= 14 && data == _del) {continue}
|
||||||
attrs.push({
|
attrs.push({
|
||||||
id,
|
id,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
|
|
@ -150,7 +151,7 @@ export class AttributeManager extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
};
|
};
|
||||||
// #endregion Data Prep
|
// #endregion Data Prep
|
||||||
|
|
||||||
// #region Actions
|
// #region Actions
|
||||||
/**
|
/**
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
*/
|
*/
|
||||||
|
|
@ -186,6 +187,7 @@ export class AttributeManager extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
static async #remove($e, element) {
|
static async #remove($e, element) {
|
||||||
const attribute = element.closest(`[data-attribute]`)?.dataset.attribute;
|
const attribute = element.closest(`[data-attribute]`)?.dataset.attribute;
|
||||||
if (!attribute) { return };
|
if (!attribute) { return };
|
||||||
|
// Remove with issue: Foundry/taf#54
|
||||||
if (game.release.generation < 14) {
|
if (game.release.generation < 14) {
|
||||||
delete this.#attributes[attribute];
|
delete this.#attributes[attribute];
|
||||||
this.#attributes[`-=${attribute}`] = null;
|
this.#attributes[`-=${attribute}`] = null;
|
||||||
|
|
@ -227,7 +229,7 @@ export class AttributeManager extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{ type: `divider` }
|
{ type: `divider` },
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
@ -254,6 +256,7 @@ export class AttributeManager extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
switch (response.state) {
|
switch (response.state) {
|
||||||
case `errored`:
|
case `errored`:
|
||||||
ui.notifications.error(response.error);
|
ui.notifications.error(response.error);
|
||||||
|
// eslint-disable-next-line no-fallthrough
|
||||||
case `fronted`:
|
case `fronted`:
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ import { PlayerSheet } from "./PlayerSheet.mjs";
|
||||||
|
|
||||||
const { deepClone } = foundry.utils;
|
const { deepClone } = foundry.utils;
|
||||||
|
|
||||||
|
const removedParts = new Set([`content`]);
|
||||||
|
|
||||||
export class AttributeOnlyPlayerSheet extends PlayerSheet {
|
export class AttributeOnlyPlayerSheet extends PlayerSheet {
|
||||||
// #region Options
|
// #region Options
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
|
|
@ -15,14 +17,45 @@ export class AttributeOnlyPlayerSheet extends PlayerSheet {
|
||||||
delete parts.content;
|
delete parts.content;
|
||||||
return parts;
|
return parts;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static get TABS() {
|
||||||
|
const tabs = deepClone(super.TABS);
|
||||||
|
tabs.primary.tabs = tabs.primary.tabs
|
||||||
|
.filter(tab => tab.id !== `content`);
|
||||||
|
tabs.primary.initial = tabs.primary.tabs.at(0).id;
|
||||||
|
return tabs;
|
||||||
|
};
|
||||||
// #endregion Options
|
// #endregion Options
|
||||||
|
|
||||||
|
// #region Instance Data
|
||||||
|
/**
|
||||||
|
* This method is used in order to ensure that when we hide specific
|
||||||
|
* tabs due to programmatic logic (e.g. having no items), that the tab
|
||||||
|
* doesn't stay selected in the app if the logic for it being visible
|
||||||
|
* no longer holds true.
|
||||||
|
*/
|
||||||
|
_assertSelectedTabs() {
|
||||||
|
// Intentional No-Op Function
|
||||||
|
};
|
||||||
|
|
||||||
|
get hasContentTab() {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
// #endregion Instance Data
|
||||||
|
|
||||||
// #region Lifecycle
|
// #region Lifecycle
|
||||||
_configureRenderOptions(options) {
|
_configureRenderOptions(options) {
|
||||||
super._configureRenderOptions(options);
|
super._configureRenderOptions(options);
|
||||||
|
|
||||||
// don't attempt to rerender the content
|
// don't attempt to rerender the parts that get removed
|
||||||
options.parts = options.parts?.filter(partID => partID !== `content`);
|
options.parts = options.parts?.filter(partID => !removedParts.has(partID));
|
||||||
};
|
};
|
||||||
// #endregion Lifecycle
|
// #endregion Lifecycle
|
||||||
|
|
||||||
|
// #region Data Prep
|
||||||
|
async _prepareItems(ctx) {
|
||||||
|
await super._prepareItems(ctx);
|
||||||
|
ctx.tabActive &&= this.hasItemsTab;
|
||||||
|
};
|
||||||
|
// #endregion Data Prep
|
||||||
};
|
};
|
||||||
|
|
|
||||||
90
module/apps/GenericItemSheet.mjs
Normal file
90
module/apps/GenericItemSheet.mjs
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
import { __ID__, filePath } from "../consts.mjs";
|
||||||
|
import { TAFDocumentSheetMixin } from "./mixins/TAFDocumentSheetMixin.mjs";
|
||||||
|
|
||||||
|
const { HandlebarsApplicationMixin } = foundry.applications.api;
|
||||||
|
const { ItemSheetV2 } = foundry.applications.sheets;
|
||||||
|
const { setProperty } = foundry.utils;
|
||||||
|
|
||||||
|
export class GenericItemSheet extends
|
||||||
|
TAFDocumentSheetMixin(
|
||||||
|
HandlebarsApplicationMixin(
|
||||||
|
ItemSheetV2,
|
||||||
|
)) {
|
||||||
|
// #region Options
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: [
|
||||||
|
__ID__,
|
||||||
|
`GenericItemSheet`,
|
||||||
|
],
|
||||||
|
position: {
|
||||||
|
width: 400,
|
||||||
|
height: 450,
|
||||||
|
},
|
||||||
|
window: {
|
||||||
|
resizable: true,
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
submitOnChange: true,
|
||||||
|
closeOnSubmit: false,
|
||||||
|
},
|
||||||
|
actions: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
static PARTS = {
|
||||||
|
header: { template: filePath(`templates/GenericItemSheet/header.hbs`) },
|
||||||
|
content: { template: filePath(`templates/GenericItemSheet/content.hbs`) },
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This tells the Application's TAFDocumentSheetMixin how to rerender this app
|
||||||
|
* when specific properties get changed on the actor, so that it doesn't need
|
||||||
|
* to full-app rendering if we can do a partial rerender instead.
|
||||||
|
*/
|
||||||
|
static PROPERTY_TO_PARTIAL = {
|
||||||
|
"name": [`header`],
|
||||||
|
"img": [`header`],
|
||||||
|
"system": [`content`],
|
||||||
|
};
|
||||||
|
// #endregion Options
|
||||||
|
|
||||||
|
// #region Instance Data
|
||||||
|
// #endregion Instance Data
|
||||||
|
|
||||||
|
// #region Lifecycle
|
||||||
|
async _prepareContext() {
|
||||||
|
return {
|
||||||
|
meta: {
|
||||||
|
idp: this.id,
|
||||||
|
editable: this.isEditable,
|
||||||
|
limited: this.isLimited,
|
||||||
|
},
|
||||||
|
item: this.item,
|
||||||
|
system: this.item.system,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
async _preparePartContext(partID, ctx) {
|
||||||
|
switch (partID) {
|
||||||
|
case `content`: {
|
||||||
|
await this._prepareContentContext(ctx);
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return ctx;
|
||||||
|
};
|
||||||
|
|
||||||
|
async _prepareContentContext(ctx) {
|
||||||
|
const TextEditor = foundry.applications.ux.TextEditor.implementation;
|
||||||
|
|
||||||
|
setProperty(
|
||||||
|
ctx,
|
||||||
|
`enriched.system.description`,
|
||||||
|
await TextEditor.enrichHTML(this.item.system.description),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
// #endregion Lifecycle
|
||||||
|
|
||||||
|
// #region Actions
|
||||||
|
// #endregion Actions
|
||||||
|
};
|
||||||
|
|
@ -1,22 +1,22 @@
|
||||||
import { __ID__, filePath } from "../consts.mjs";
|
import { __ID__, filePath } from "../consts.mjs";
|
||||||
|
import { deleteItemFromElement, editItemFromElement } from "./utils.mjs";
|
||||||
import { AttributeManager } from "./AttributeManager.mjs";
|
import { AttributeManager } from "./AttributeManager.mjs";
|
||||||
import { attributeSorter } from "../utils/attributeSort.mjs";
|
import { attributeSorter } from "../utils/attributeSort.mjs";
|
||||||
|
import { config } from "../config.mjs";
|
||||||
|
import { Logger } from "../utils/Logger.mjs";
|
||||||
import { TAFDocumentSheetConfig } from "./TAFDocumentSheetConfig.mjs";
|
import { TAFDocumentSheetConfig } from "./TAFDocumentSheetConfig.mjs";
|
||||||
|
import { TAFDocumentSheetMixin } from "./mixins/TAFDocumentSheetMixin.mjs";
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin } = foundry.applications.api;
|
const { HandlebarsApplicationMixin } = foundry.applications.api;
|
||||||
const { ActorSheetV2 } = foundry.applications.sheets;
|
const { ActorSheetV2 } = foundry.applications.sheets;
|
||||||
const { getProperty, hasProperty } = foundry.utils;
|
const { getProperty } = foundry.utils;
|
||||||
|
const { ContextMenu, TextEditor } = foundry.applications.ux;
|
||||||
|
|
||||||
const propertyToParts = {
|
export class PlayerSheet extends
|
||||||
"name": [`header`],
|
TAFDocumentSheetMixin(
|
||||||
"img": [`header`],
|
HandlebarsApplicationMixin(
|
||||||
"system.attr": [`attributes`],
|
ActorSheetV2,
|
||||||
"system.attr.value": [`attributes`, `content`],
|
)) {
|
||||||
"system.attr.max": [`attributes`, `content`],
|
|
||||||
"system.content": [`content`],
|
|
||||||
};
|
|
||||||
|
|
||||||
export class PlayerSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
|
|
||||||
|
|
||||||
// #region Options
|
// #region Options
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
|
|
@ -36,18 +36,101 @@ export class PlayerSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
|
||||||
closeOnSubmit: false,
|
closeOnSubmit: false,
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
|
createEmbeddedItem: this.#createEmbeddedItem,
|
||||||
manageAttributes: this.#manageAttributes,
|
manageAttributes: this.#manageAttributes,
|
||||||
configureSheet: this.#configureSheet,
|
configureSheet: this.#configureSheet,
|
||||||
|
toggleExpand: this.#toggleExpand,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
static PARTS = {
|
static PARTS = {
|
||||||
header: { template: filePath(`templates/PlayerSheet/header.hbs`) },
|
header: { template: filePath(`templates/PlayerSheet/header.hbs`) },
|
||||||
attributes: { template: filePath(`templates/PlayerSheet/attributes.hbs`) },
|
attributes: { template: filePath(`templates/PlayerSheet/attributes.hbs`) },
|
||||||
|
tabs: { template: filePath(`templates/generic/tabs.hbs`) },
|
||||||
content: { template: filePath(`templates/PlayerSheet/content.hbs`) },
|
content: { template: filePath(`templates/PlayerSheet/content.hbs`) },
|
||||||
|
items: {
|
||||||
|
template: filePath(`templates/PlayerSheet/item-lists.hbs`),
|
||||||
|
scrollable: [``],
|
||||||
|
templates: [
|
||||||
|
filePath(`templates/PlayerSheet/item.hbs`),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This tells the Application's TAFDocumentSheetMixin how to rerender this app
|
||||||
|
* when specific properties get changed on the actor, so that it doesn't need
|
||||||
|
* to full-app rendering if we can do a partial rerender instead.
|
||||||
|
*/
|
||||||
|
static PROPERTY_TO_PARTIAL = {
|
||||||
|
"name": [`header`],
|
||||||
|
"img": [`header`],
|
||||||
|
"system.attr": [`attributes`],
|
||||||
|
"system.attr.value": [`attributes`, `content`],
|
||||||
|
"system.attr.max": [`attributes`, `content`],
|
||||||
|
"system.content": [`content`],
|
||||||
|
"system.carryCapacity": [`items`],
|
||||||
|
};
|
||||||
|
|
||||||
|
static TABS = {
|
||||||
|
primary: {
|
||||||
|
initial: `content`,
|
||||||
|
labelPrefix: `taf.Apps.PlayerSheet.tab-names`,
|
||||||
|
tabs: [
|
||||||
|
{ id: `content` },
|
||||||
|
{ id: `items` },
|
||||||
|
],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
// #endregion Options
|
// #endregion Options
|
||||||
|
|
||||||
|
// #region Instance Data
|
||||||
|
/**
|
||||||
|
* This Set is used to keep track of which items have had their full
|
||||||
|
* details expanded so that it can be persisted across rerenders as
|
||||||
|
* they occur.
|
||||||
|
*/
|
||||||
|
#expandedItems = new Set();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is used in order to ensure that when we hide specific
|
||||||
|
* tabs due to programmatic logic (e.g. having no items), that the tab
|
||||||
|
* doesn't stay selected in the app if the logic for it being visible
|
||||||
|
* no longer holds true.
|
||||||
|
*/
|
||||||
|
_assertSelectedTabs() {
|
||||||
|
const initial = this.constructor.TABS.primary.initial;
|
||||||
|
if (this.tabGroups.primary === `items` && !this.hasItemsTab) {
|
||||||
|
Logger.debug(`Asserting app "${this.id}" from tab "items" to "${initial}"`);
|
||||||
|
this.tabGroups.primary = initial;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper method that allows a shortcut to determine if a tab is visible
|
||||||
|
* solely based on it's ID. This usually redirects to the relevant getter in
|
||||||
|
* the class, but if the tab ID doesn't exist it always returns false.
|
||||||
|
*
|
||||||
|
* @param {string} tabID The ID of the relevant tab
|
||||||
|
* @returns Whether or not the tab is visible
|
||||||
|
*/
|
||||||
|
hasTab(tabID) {
|
||||||
|
switch (tabID) {
|
||||||
|
case `content`: return this.hasContentTab;
|
||||||
|
case `items`: return this.hasItemsTab;
|
||||||
|
};
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
get hasContentTab() {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
get hasItemsTab() {
|
||||||
|
return this.actor.items.size > 0;
|
||||||
|
};
|
||||||
|
// #endregion Instance Data
|
||||||
|
|
||||||
// #region Lifecycle
|
// #region Lifecycle
|
||||||
_initializeApplicationOptions(options) {
|
_initializeApplicationOptions(options) {
|
||||||
const sizing = getProperty(options.document, `flags.${__ID__}.PlayerSheet.size`) ?? {};
|
const sizing = getProperty(options.document, `flags.${__ID__}.PlayerSheet.size`) ?? {};
|
||||||
|
|
@ -89,34 +172,64 @@ export class PlayerSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
|
||||||
_getHeaderControls() {
|
_getHeaderControls() {
|
||||||
const controls = super._getHeaderControls();
|
const controls = super._getHeaderControls();
|
||||||
|
|
||||||
controls.push({
|
controls.push(
|
||||||
icon: `fa-solid fa-at`,
|
{
|
||||||
label: `taf.Apps.PlayerSheet.manage-attributes`,
|
icon: `fa-solid fa-at`,
|
||||||
action: `manageAttributes`,
|
label: `taf.Apps.PlayerSheet.manage-attributes`,
|
||||||
visible: () => {
|
action: `manageAttributes`,
|
||||||
const isGM = game.user.isGM;
|
visible: () => {
|
||||||
const allowPlayerEdits = game.settings.get(__ID__, `canPlayersManageAttributes`);
|
const isGM = game.user.isGM;
|
||||||
const editable = this.isEditable;
|
const allowPlayerEdits = game.settings.get(__ID__, `canPlayersManageAttributes`);
|
||||||
return isGM || (allowPlayerEdits && editable);
|
const editable = this.isEditable;
|
||||||
|
return isGM || (allowPlayerEdits && editable);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
{
|
||||||
|
icon: `fa-solid fa-suitcase`,
|
||||||
|
label: `taf.Apps.PlayerSheet.create-item`,
|
||||||
|
action: `createEmbeddedItem`,
|
||||||
|
visible: () => {
|
||||||
|
return this.isEditable;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
return controls;
|
return controls;
|
||||||
};
|
};
|
||||||
|
|
||||||
_configureRenderOptions(options) {
|
async _preRender(ctx, options) {
|
||||||
// Only rerender the parts of the app that got changed
|
this._assertSelectedTabs();
|
||||||
if (options.renderContext === `updateActor`) {
|
return super._preRender(ctx, options);
|
||||||
const parts = new Set();
|
};
|
||||||
for (const property in propertyToParts) {
|
|
||||||
if (hasProperty(options.renderData, property)) {
|
|
||||||
propertyToParts[property].forEach(partID => parts.add(partID));
|
|
||||||
};
|
|
||||||
};
|
|
||||||
options.parts = options.parts?.filter(part => !parts.has(part)) ?? Array.from(parts);
|
|
||||||
};
|
|
||||||
|
|
||||||
super._configureRenderOptions(options);
|
async _onRender(ctx, options) {
|
||||||
|
await super._onRender(ctx, options);
|
||||||
|
|
||||||
|
new ContextMenu.implementation(
|
||||||
|
this.element,
|
||||||
|
`li.item`,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
label: _loc(`taf.misc.edit`),
|
||||||
|
condition: (el) => {
|
||||||
|
const itemUuid = el.dataset.itemUuid;
|
||||||
|
const itemExists = itemUuid != null && itemUuid !== ``;
|
||||||
|
return this.isEditable && itemExists;
|
||||||
|
},
|
||||||
|
onClick: editItemFromElement,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: _loc(`taf.misc.delete`),
|
||||||
|
condition: (el) => {
|
||||||
|
const itemUuid = el.dataset.itemUuid;
|
||||||
|
const itemExists = itemUuid != null && itemUuid !== ``;
|
||||||
|
return this.isEditable && itemExists;
|
||||||
|
},
|
||||||
|
onClick: deleteItemFromElement,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
{ jQuery: false, fixed: true },
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
async close() {
|
async close() {
|
||||||
|
|
@ -127,22 +240,36 @@ export class PlayerSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
|
||||||
// #endregion Lifecycle
|
// #endregion Lifecycle
|
||||||
|
|
||||||
// #region Data Prep
|
// #region Data Prep
|
||||||
async _preparePartContext(partID) {
|
async _prepareContext() {
|
||||||
let ctx = {
|
return {
|
||||||
|
meta: {
|
||||||
|
idp: this.id,
|
||||||
|
editable: this.isEditable,
|
||||||
|
},
|
||||||
actor: this.actor,
|
actor: this.actor,
|
||||||
system: this.actor.system,
|
system: this.actor.system,
|
||||||
editable: this.isEditable,
|
editable: this.isEditable,
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
async _preparePartContext(partID, ctx) {
|
||||||
switch (partID) {
|
switch (partID) {
|
||||||
case `attributes`: {
|
case `attributes`: {
|
||||||
await this._prepareAttributes(ctx);
|
await this._prepareAttributes(ctx);
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
|
case `tabs`: {
|
||||||
|
await this._prepareTabList(ctx);
|
||||||
|
break;
|
||||||
|
};
|
||||||
case `content`: {
|
case `content`: {
|
||||||
await this._prepareContent(ctx);
|
await this._prepareContent(ctx);
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
|
case `items`: {
|
||||||
|
await this._prepareItems(ctx);
|
||||||
|
break;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
return ctx;
|
return ctx;
|
||||||
|
|
@ -162,22 +289,91 @@ export class PlayerSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
|
||||||
ctx.attrs = attrs.toSorted(attributeSorter);
|
ctx.attrs = attrs.toSorted(attributeSorter);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async _prepareTabList(ctx) {
|
||||||
|
ctx.tabs = await this._prepareTabs(`primary`);
|
||||||
|
|
||||||
|
let amountVisible = 0;
|
||||||
|
for (const tabID in ctx.tabs) {
|
||||||
|
const visible = this.hasTab(tabID);
|
||||||
|
ctx.tabs[tabID].visible = visible;
|
||||||
|
if (visible) { amountVisible++ };
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.hideTabs = amountVisible <= 1;
|
||||||
|
};
|
||||||
|
|
||||||
async _prepareContent(ctx) {
|
async _prepareContent(ctx) {
|
||||||
// Whether or not the prose-mirror is toggled or always-edit
|
// Whether or not the prose-mirror is toggled or always-edit
|
||||||
ctx.toggled = true;
|
ctx.toggled = true;
|
||||||
|
ctx.tabActive = this.tabGroups.primary === `content`;
|
||||||
|
|
||||||
const TextEditor = foundry.applications.ux.TextEditor.implementation;
|
|
||||||
ctx.enriched = {
|
ctx.enriched = {
|
||||||
system: {
|
system: {
|
||||||
content: await TextEditor.enrichHTML(this.actor.system.content),
|
content: await TextEditor.implementation.enrichHTML(this.actor.system.content),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async _prepareItems(ctx) {
|
||||||
|
ctx.tabActive = this.tabGroups.primary === `items`;
|
||||||
|
|
||||||
|
let totalWeight = 0;
|
||||||
|
|
||||||
|
ctx.itemGroups = [];
|
||||||
|
for (const [groupName, items] of Object.entries(this.actor.itemTypes)) {
|
||||||
|
const preparedItems = [];
|
||||||
|
|
||||||
|
let summedWeight = 0;
|
||||||
|
for (const item of items) {
|
||||||
|
summedWeight += item.system.quantifiedWeight;
|
||||||
|
preparedItems.push(await this._prepareItem(item));
|
||||||
|
};
|
||||||
|
totalWeight += summedWeight;
|
||||||
|
|
||||||
|
ctx.itemGroups.push({
|
||||||
|
name: groupName.titleCase(),
|
||||||
|
items: preparedItems,
|
||||||
|
weight: config.weightFormatter(totalWeight),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.totalWeight = config.weightFormatter(totalWeight);
|
||||||
|
ctx.hasCarryingCapacity = this.actor.system.carryCapacity != null;
|
||||||
|
ctx.carryCapacityPercent = Math.round(totalWeight / this.actor.system.carryCapacity * 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
async _prepareItem(item) {
|
||||||
|
const ctx = {
|
||||||
|
uuid: item.uuid,
|
||||||
|
img: item.img,
|
||||||
|
name: item.name,
|
||||||
|
equipped: item.system.equipped,
|
||||||
|
quantity: item.system.quantity,
|
||||||
|
weight: config.weightFormatter(item.system.quantifiedWeight),
|
||||||
|
isExpanded: this.#expandedItems.has(item.uuid),
|
||||||
|
canExpand: item.system.description.length > 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.description = ``;
|
||||||
|
if (item.system.description.length > 0) {
|
||||||
|
ctx.description = await TextEditor.implementation.enrichHTML(item.system.description);
|
||||||
|
};
|
||||||
|
|
||||||
|
return ctx;
|
||||||
|
};
|
||||||
// #endregion Data Prep
|
// #endregion Data Prep
|
||||||
|
|
||||||
// #region Actions
|
// #region Actions
|
||||||
#attributeManager = null;
|
#attributeManager = null;
|
||||||
/** @this {PlayerSheet} */
|
|
||||||
|
/**
|
||||||
|
* This action opens an instance of the AttributeManager application
|
||||||
|
* so that the user can edit and update all of the attributes for the
|
||||||
|
* actor. This persists the application instance for the duration of
|
||||||
|
* the ActorSheet's lifespan.
|
||||||
|
*
|
||||||
|
* @this {PlayerSheet}
|
||||||
|
*/
|
||||||
static async #manageAttributes() {
|
static async #manageAttributes() {
|
||||||
this.#attributeManager ??= new AttributeManager({ document: this.actor });
|
this.#attributeManager ??= new AttributeManager({ document: this.actor });
|
||||||
if (this.#attributeManager.rendered) {
|
if (this.#attributeManager.rendered) {
|
||||||
|
|
@ -190,6 +386,13 @@ export class PlayerSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This action overrides the default Foundry action in order to tell
|
||||||
|
* it to open my custom DocumentSheetConfig application instead of
|
||||||
|
* opening the non-customized sheet config app.
|
||||||
|
*
|
||||||
|
* @this {PlayerSheet}
|
||||||
|
*/
|
||||||
static async #configureSheet(event) {
|
static async #configureSheet(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
if ( event.detail > 1 ) { return }
|
if ( event.detail > 1 ) { return }
|
||||||
|
|
@ -205,5 +408,52 @@ export class PlayerSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
|
||||||
window: { windowId: this.window.windowId },
|
window: { windowId: this.window.windowId },
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This action is used by the item lists in order to expand/collapse
|
||||||
|
* the descriptions while maintaining that state across renders.
|
||||||
|
*
|
||||||
|
* @this {PlayerSheet}
|
||||||
|
*/
|
||||||
|
static async #toggleExpand(event, target) {
|
||||||
|
const element = target.closest(`[data-item-uuid]`);
|
||||||
|
const { itemUuid } = element?.dataset ?? {};
|
||||||
|
if (!itemUuid) { return };
|
||||||
|
|
||||||
|
const expanded = this.#expandedItems.has(itemUuid);
|
||||||
|
const newExpanded = !expanded;
|
||||||
|
|
||||||
|
this.#expandedItems[newExpanded ? `add` : `delete`]?.(itemUuid);
|
||||||
|
target.dataset.expanded = newExpanded;
|
||||||
|
const collapses = element.querySelectorAll(`[data-expanded]`);
|
||||||
|
collapses.forEach(el => {
|
||||||
|
el.dataset.expanded = newExpanded;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by the sheet in order to create embedded items without needing to have
|
||||||
|
* equivalent World Items or Compendiums initially.
|
||||||
|
*
|
||||||
|
* @this {PlayerSheet}
|
||||||
|
*/
|
||||||
|
static async #createEmbeddedItem(event, target) {
|
||||||
|
let { itemGroup } = target.dataset ?? {};
|
||||||
|
if (itemGroup === `Items`) { itemGroup = undefined };
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
name: Item.defaultName({
|
||||||
|
type: `generic`,
|
||||||
|
parent: this.actor,
|
||||||
|
}),
|
||||||
|
type: `generic`,
|
||||||
|
system: {
|
||||||
|
group: itemGroup,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const item = await Item.create(data, { parent: this.actor });
|
||||||
|
item?.sheet?.render({ force: true });
|
||||||
|
};
|
||||||
// #endregion Actions
|
// #endregion Actions
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,13 @@ export function StyledShadowElement(Base) {
|
||||||
/** @type {ShadowRoot} */
|
/** @type {ShadowRoot} */
|
||||||
_shadow;
|
_shadow;
|
||||||
|
|
||||||
constructor() {
|
constructor({focusable = false} = {}) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this._shadow = this.attachShadow({ mode: `open` });
|
this._shadow = this.attachShadow({
|
||||||
|
mode: `open`,
|
||||||
|
delegatesFocus: focusable,
|
||||||
|
});
|
||||||
this._style = document.createElement(`style`);
|
this._style = document.createElement(`style`);
|
||||||
this._shadow.appendChild(this._style);
|
this._shadow.appendChild(this._style);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
117
module/apps/elements/Toggle.mjs
Normal file
117
module/apps/elements/Toggle.mjs
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
import { StyledShadowElement } from "./StyledShadowElement.mjs";
|
||||||
|
|
||||||
|
const { debounce } = foundry.utils;
|
||||||
|
|
||||||
|
export class TafToggle extends StyledShadowElement(HTMLElement) {
|
||||||
|
static elementName = `taf-toggle`;
|
||||||
|
static formAssociated = true;
|
||||||
|
|
||||||
|
static _stylePath = `toggle.css`;
|
||||||
|
|
||||||
|
_mounted;
|
||||||
|
_internals;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super({ focusable: true });
|
||||||
|
|
||||||
|
this._internals = this.attachInternals();
|
||||||
|
this._internals.role = `checkbox`;
|
||||||
|
};
|
||||||
|
|
||||||
|
get type() {
|
||||||
|
return `checkbox`;
|
||||||
|
};
|
||||||
|
|
||||||
|
get name() {
|
||||||
|
return this.getAttribute(`name`);
|
||||||
|
};
|
||||||
|
set name(newName) {
|
||||||
|
this.setAttribute(`name`, newName);
|
||||||
|
};
|
||||||
|
|
||||||
|
get value() {
|
||||||
|
return this._input.value;
|
||||||
|
};
|
||||||
|
set value(newValue) {
|
||||||
|
this._input.value = newValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
get checked() {
|
||||||
|
return this._input.checked ?? false;
|
||||||
|
};
|
||||||
|
set checked(newValue) {
|
||||||
|
if (typeof newValue !== `boolean`) { return };
|
||||||
|
this._input.checked = newValue;
|
||||||
|
this.#emitEvents();
|
||||||
|
};
|
||||||
|
|
||||||
|
get disabled() {
|
||||||
|
return this.matches(`:disabled`);
|
||||||
|
};
|
||||||
|
set disabled(value) {
|
||||||
|
this.toggleAttribute(`disabled`, value);
|
||||||
|
};
|
||||||
|
|
||||||
|
get editable() {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
if (this._mounted) { return };
|
||||||
|
|
||||||
|
this._internals.checked = this.hasAttribute(`checked`);
|
||||||
|
|
||||||
|
/*
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const container = document.createElement(`div`);
|
||||||
|
container.classList = `toggle`;
|
||||||
|
container.dataset.type = `round`;
|
||||||
|
|
||||||
|
const input = this._input = document.createElement(`input`);
|
||||||
|
input.type = `checkbox`;
|
||||||
|
input.toggleAttribute(`switch`, true);
|
||||||
|
input.checked = this.hasAttribute(`checked`);
|
||||||
|
input.addEventListener(`change`, () => {
|
||||||
|
this.#emitEvents();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.addEventListener(`click`, () => {
|
||||||
|
input.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
container.appendChild(input);
|
||||||
|
|
||||||
|
const slider = document.createElement(`div`);
|
||||||
|
slider.classList = `slider`;
|
||||||
|
container.appendChild(slider);
|
||||||
|
|
||||||
|
this._shadow.appendChild(container);
|
||||||
|
|
||||||
|
this._mounted = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
if (!this._mounted) { return };
|
||||||
|
this._mounted = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
#emitEvents = debounce(
|
||||||
|
() => {
|
||||||
|
this.dispatchEvent(new Event(`input`, {bubbles: true, cancelable: false}));
|
||||||
|
this.dispatchEvent(new Event(`change`, {bubbles: true, cancelable: false}));
|
||||||
|
},
|
||||||
|
150,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
import { Logger } from "../../utils/Logger.mjs";
|
import { Logger } from "../../utils/Logger.mjs";
|
||||||
import { TafIcon } from "./Icon.mjs";
|
import { TafIcon } from "./Icon.mjs";
|
||||||
import { TafSVGLoader } from "./svgLoader.mjs";
|
import { TafSVGLoader } from "./svgLoader.mjs";
|
||||||
|
import { TafToggle } from "./Toggle.mjs";
|
||||||
|
|
||||||
const components = [
|
const components = [
|
||||||
TafSVGLoader,
|
TafSVGLoader,
|
||||||
TafIcon,
|
TafIcon,
|
||||||
|
TafToggle,
|
||||||
];
|
];
|
||||||
|
|
||||||
export function registerCustomComponents() {
|
export function registerCustomComponents() {
|
||||||
(CONFIG.CACHE ??= {}).componentListeners ??= [];
|
|
||||||
for (const component of components) {
|
for (const component of components) {
|
||||||
if (!window.customElements.get(component.elementName)) {
|
if (!window.customElements.get(component.elementName)) {
|
||||||
Logger.debug(`Registering component "${component.elementName}"`);
|
Logger.debug(`Registering component "${component.elementName}"`);
|
||||||
|
|
@ -16,9 +17,6 @@ export function registerCustomComponents() {
|
||||||
component.elementName,
|
component.elementName,
|
||||||
component,
|
component,
|
||||||
);
|
);
|
||||||
if (component.formAssociated) {
|
|
||||||
CONFIG.CACHE.componentListeners.push(component.elementName);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
53
module/apps/mixins/TAFDocumentSheetMixin.mjs
Normal file
53
module/apps/mixins/TAFDocumentSheetMixin.mjs
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { updateForeignDocumentFromEvent } from "../utils.mjs";
|
||||||
|
|
||||||
|
const { hasProperty } = foundry.utils;
|
||||||
|
|
||||||
|
export function TAFDocumentSheetMixin(HandlebarsApplication) {
|
||||||
|
class TAFDocumentSheet extends HandlebarsApplication {
|
||||||
|
/** @type {Record<string, string[]> | null} */
|
||||||
|
static PROPERTY_TO_PARTIAL = null;
|
||||||
|
|
||||||
|
// #region Lifecycle
|
||||||
|
/**
|
||||||
|
* This override is used by the mixin in order to allow for partial
|
||||||
|
* re-rendering of applications based on what properties changed.
|
||||||
|
* It requires that a static PROPERTY_TO_PARTIAL to be defined as
|
||||||
|
* an object of path keys to arrays of part IDs in order to work.
|
||||||
|
* This will not interfere with renders that are not started as
|
||||||
|
* part of the actor update lifecycle.
|
||||||
|
*/
|
||||||
|
_configureRenderOptions(options) {
|
||||||
|
|
||||||
|
if (options.renderContext === `updateActor`) {
|
||||||
|
const propertyToParts = this.constructor.PROPERTY_TO_PARTIAL;
|
||||||
|
if (propertyToParts) {
|
||||||
|
const parts = new Set();
|
||||||
|
for (const property in propertyToParts) {
|
||||||
|
if (hasProperty(options.renderData, property)) {
|
||||||
|
propertyToParts[property].forEach(partID => parts.add(partID));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
options.parts = options.parts?.filter(part => !parts.has(part)) ?? Array.from(parts);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
super._configureRenderOptions(options);
|
||||||
|
};
|
||||||
|
|
||||||
|
async _onRender(...args) {
|
||||||
|
await super._onRender(...args);
|
||||||
|
this._attachEmbeddedChangeListeners();
|
||||||
|
};
|
||||||
|
|
||||||
|
_attachEmbeddedChangeListeners() {
|
||||||
|
/** @type {HTMLElement[]} */
|
||||||
|
const elements = this.element.querySelectorAll(`[data-foreign-name]`);
|
||||||
|
for (const el of elements) {
|
||||||
|
el.addEventListener(`change`, updateForeignDocumentFromEvent);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
// #endregion Lifecycle
|
||||||
|
};
|
||||||
|
|
||||||
|
return TAFDocumentSheet;
|
||||||
|
};
|
||||||
58
module/apps/utils.mjs
Normal file
58
module/apps/utils.mjs
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
This file contains utility methods used by Applications in order to be
|
||||||
|
DRYer
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Event} _event The click event
|
||||||
|
* @param {HTMLElement} target The element to operate on
|
||||||
|
*/
|
||||||
|
export async function editItemFromElement(_event, target) {
|
||||||
|
const itemEl = target.closest(`[data-item-uuid]`);
|
||||||
|
if (!itemEl) { return };
|
||||||
|
const uuid = itemEl.dataset.itemUuid;
|
||||||
|
if (!uuid) { return };
|
||||||
|
const item = await fromUuid(uuid);
|
||||||
|
item.sheet.render({ force: true, orBringToFront: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Event} _event The click event
|
||||||
|
* @param {HTMLElement} target The element to operate on
|
||||||
|
*/
|
||||||
|
export async function deleteItemFromElement(_event, target) {
|
||||||
|
const itemEl = target.closest(`[data-item-uuid]`);
|
||||||
|
if (!itemEl) { return };
|
||||||
|
const uuid = itemEl.dataset.itemUuid;
|
||||||
|
if (!uuid) { return };
|
||||||
|
const item = await fromUuid(uuid);
|
||||||
|
item.deleteDialog();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a document using the UUID, this is most useful for editing
|
||||||
|
* documents from a sheet of another document (e.g. an Item embedded
|
||||||
|
* in an Actor). This requires the dataset of the element to have a
|
||||||
|
* "data-foreign-name" which is the data path of the property being
|
||||||
|
* edited. As well as the input, or a parent of it, to have the
|
||||||
|
* "data-foreign-uuid" attribute, representing the UUID of the document
|
||||||
|
* to edit.
|
||||||
|
*/
|
||||||
|
export async function updateForeignDocumentFromEvent(event) {
|
||||||
|
const target = event.currentTarget;
|
||||||
|
const name = target.dataset.foreignName;
|
||||||
|
let uuid = target.dataset.foreignUuid;
|
||||||
|
uuid ??= target.closest(`[data-foreign-uuid]`)?.dataset.foreignUuid;
|
||||||
|
|
||||||
|
if (!name || !uuid) {
|
||||||
|
throw `Cannot edit foreign document with the name and UUID`;
|
||||||
|
};
|
||||||
|
|
||||||
|
let value = target.value;
|
||||||
|
switch (target.type) {
|
||||||
|
case `checkbox`: value = target.checked; break;
|
||||||
|
};
|
||||||
|
|
||||||
|
let doc = await fromUuid(uuid);
|
||||||
|
await doc?.update({ [name]: value });
|
||||||
|
};
|
||||||
7
module/config.mjs
Normal file
7
module/config.mjs
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { formatWeight } from "./utils/formatWeight.mjs";
|
||||||
|
|
||||||
|
const { deepSeal } = foundry.utils;
|
||||||
|
|
||||||
|
export const config = deepSeal({
|
||||||
|
weightFormatter: formatWeight,
|
||||||
|
});
|
||||||
|
|
@ -7,6 +7,11 @@ export class PlayerData extends foundry.abstract.TypeDataModel {
|
||||||
trim: true,
|
trim: true,
|
||||||
initial: ``,
|
initial: ``,
|
||||||
}),
|
}),
|
||||||
|
carryCapacity: new fields.NumberField({
|
||||||
|
min: 0,
|
||||||
|
nullable: true,
|
||||||
|
initial: null,
|
||||||
|
}),
|
||||||
attr: new fields.TypedObjectField(
|
attr: new fields.TypedObjectField(
|
||||||
new fields.SchemaField({
|
new fields.SchemaField({
|
||||||
name: new fields.StringField({ blank: false, trim: true }),
|
name: new fields.StringField({ blank: false, trim: true }),
|
||||||
41
module/data/Item/generic.mjs
Normal file
41
module/data/Item/generic.mjs
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { toPrecision } from "../../utils/roundToPrecision.mjs";
|
||||||
|
|
||||||
|
export class GenericItemData extends foundry.abstract.TypeDataModel {
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields;
|
||||||
|
return {
|
||||||
|
group: new fields.StringField({
|
||||||
|
blank: false,
|
||||||
|
trim: true,
|
||||||
|
initial: null,
|
||||||
|
nullable: true,
|
||||||
|
}),
|
||||||
|
weight: new fields.NumberField({
|
||||||
|
min: 0,
|
||||||
|
initial: 0,
|
||||||
|
nullable: false,
|
||||||
|
}),
|
||||||
|
quantity: new fields.NumberField({
|
||||||
|
integer: true,
|
||||||
|
initial: 1,
|
||||||
|
}),
|
||||||
|
equipped: new fields.BooleanField({
|
||||||
|
initial: true,
|
||||||
|
}),
|
||||||
|
description: new fields.HTMLField({
|
||||||
|
blank: true,
|
||||||
|
trim: true,
|
||||||
|
initial: ``,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the total weight of the item based on the quantity of it, this
|
||||||
|
* rounds the number to the nearest 2 decimal places.
|
||||||
|
*/
|
||||||
|
get quantifiedWeight() {
|
||||||
|
const value = this.weight * this.quantity;
|
||||||
|
return toPrecision(Math.max(value, 0), 2);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -6,19 +6,36 @@ const { hasProperty } = foundry.utils;
|
||||||
export class TAFActor extends Actor {
|
export class TAFActor extends Actor {
|
||||||
|
|
||||||
// #region Lifecycle
|
// #region Lifecycle
|
||||||
|
/**
|
||||||
|
* This makes sure that the actor gets created with the global attributes if
|
||||||
|
* they exist, while still allowing programmatic creation through the API with
|
||||||
|
* specific attributes.
|
||||||
|
*/
|
||||||
async _preCreate(data, options, user) {
|
async _preCreate(data, options, user) {
|
||||||
|
|
||||||
// Assign the defaults from the world setting if they exist
|
// Assign the defaults from the world setting if they exist
|
||||||
const defaults = game.settings.get(__ID__, `actorDefaultAttributes`) ?? {};
|
const defaults = game.settings.get(__ID__, `actorDefaultAttributes`) ?? {};
|
||||||
if (!hasProperty(data, `system.attr`)) {
|
if (!hasProperty(data, `system.attr`)) {
|
||||||
|
// Remove with issue: Foundry/taf#55
|
||||||
const value = game.release.generation > 13 ? _replace(defaults) : defaults;
|
const value = game.release.generation > 13 ? _replace(defaults) : defaults;
|
||||||
this.updateSource({ "system.==attr": value });
|
this.updateSource({ "system.==attr": value });
|
||||||
};
|
};
|
||||||
|
|
||||||
return super._preCreate(data, options, user);
|
return super._preCreate(data, options, user);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This resets the cache of the item groupings whenever a descedant document
|
||||||
|
* gets changed (created, updated, deleted) so that we keep the cache as close
|
||||||
|
* to accurate as can be possible.
|
||||||
|
*/
|
||||||
|
_onEmbeddedDocumentChange(...args) {
|
||||||
|
super._onEmbeddedDocumentChange(...args);
|
||||||
|
this.#sortedTypes = null;
|
||||||
|
};
|
||||||
// #endregion Lifecycle
|
// #endregion Lifecycle
|
||||||
|
|
||||||
|
// #region Token Attributes
|
||||||
async modifyTokenAttribute(attribute, value, isDelta = false, isBar = true) {
|
async modifyTokenAttribute(attribute, value, isDelta = false, isBar = true) {
|
||||||
const attr = foundry.utils.getProperty(this.system, attribute);
|
const attr = foundry.utils.getProperty(this.system, attribute);
|
||||||
const current = isBar ? attr.value : attr;
|
const current = isBar ? attr.value : attr;
|
||||||
|
|
@ -40,9 +57,18 @@ export class TAFActor extends Actor {
|
||||||
|
|
||||||
return allowed !== false ? this.update(updates) : this;
|
return allowed !== false ? this.update(updates) : this;
|
||||||
};
|
};
|
||||||
|
// #endregion Token Attributes
|
||||||
|
|
||||||
|
// #region Roll Data
|
||||||
getRollData() {
|
getRollData() {
|
||||||
const data = {};
|
/*
|
||||||
|
All properties assigned during this phase of the roll data prep can potentially
|
||||||
|
be overridden by users creating attributes of the same key, if users shouldn't
|
||||||
|
be able to override, assign the property before the return of this function.
|
||||||
|
*/
|
||||||
|
const data = {
|
||||||
|
carryCapacity: this.system.carryCapacity ?? null,
|
||||||
|
};
|
||||||
|
|
||||||
if (`attr` in this.system) {
|
if (`attr` in this.system) {
|
||||||
for (const attrID in this.system.attr) {
|
for (const attrID in this.system.attr) {
|
||||||
|
|
@ -60,4 +86,24 @@ export class TAFActor extends Actor {
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
// #endregion Roll Data
|
||||||
|
|
||||||
|
// #region Getters
|
||||||
|
#sortedTypes = null;
|
||||||
|
get itemTypes() {
|
||||||
|
if (this.#sortedTypes) { return this.#sortedTypes };
|
||||||
|
const types = {};
|
||||||
|
for (const item of this.items) {
|
||||||
|
if (item.type !== `generic`) {
|
||||||
|
types[item.type] ??= [];
|
||||||
|
types[item.type].push(item);
|
||||||
|
} else {
|
||||||
|
const group = item.system.group?.toLowerCase() ?? `items`;
|
||||||
|
types[group] ??= [];
|
||||||
|
types[group].push(item);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
return this.#sortedTypes = types;
|
||||||
|
};
|
||||||
|
// #endregion Getters
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
const { Item } = foundry.documents;
|
|
||||||
|
|
||||||
export class TAFItem extends Item {
|
|
||||||
async _preCreate() {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
// Apps
|
// Apps
|
||||||
import { AttributeOnlyPlayerSheet } from "../apps/AttributeOnlyPlayerSheet.mjs";
|
import { AttributeOnlyPlayerSheet } from "../apps/AttributeOnlyPlayerSheet.mjs";
|
||||||
|
import { GenericItemSheet } from "../apps/GenericItemSheet.mjs";
|
||||||
import { PlayerSheet } from "../apps/PlayerSheet.mjs";
|
import { PlayerSheet } from "../apps/PlayerSheet.mjs";
|
||||||
import { SingleModePlayerSheet } from "../apps/SingleModePlayerSheet.mjs";
|
import { SingleModePlayerSheet } from "../apps/SingleModePlayerSheet.mjs";
|
||||||
|
|
||||||
// Data Models
|
// Data Models
|
||||||
import { PlayerData } from "../data/Player.mjs";
|
import { GenericItemData } from "../data/Item/generic.mjs";
|
||||||
|
import { PlayerData } from "../data/Actor/player.mjs";
|
||||||
|
|
||||||
// Documents
|
// Documents
|
||||||
import { TAFActor } from "../documents/Actor.mjs";
|
import { TAFActor } from "../documents/Actor.mjs";
|
||||||
import { TAFCombatant } from "../documents/Combatant.mjs";
|
import { TAFCombatant } from "../documents/Combatant.mjs";
|
||||||
import { TAFItem } from "../documents/Item.mjs";
|
|
||||||
import { TAFTokenDocument } from "../documents/Token.mjs";
|
import { TAFTokenDocument } from "../documents/Token.mjs";
|
||||||
|
|
||||||
// Settings
|
// Settings
|
||||||
|
|
@ -25,16 +26,18 @@ import { registerSockets } from "../sockets/_index.mjs";
|
||||||
Hooks.on(`init`, () => {
|
Hooks.on(`init`, () => {
|
||||||
Logger.debug(`Initializing`);
|
Logger.debug(`Initializing`);
|
||||||
|
|
||||||
|
// #region Documents
|
||||||
CONFIG.Token.documentClass = TAFTokenDocument;
|
CONFIG.Token.documentClass = TAFTokenDocument;
|
||||||
CONFIG.Actor.documentClass = TAFActor;
|
CONFIG.Actor.documentClass = TAFActor;
|
||||||
CONFIG.Combatant.documentClass = TAFCombatant;
|
CONFIG.Combatant.documentClass = TAFCombatant;
|
||||||
|
// #endregion Documents
|
||||||
|
|
||||||
|
// #region Data Models
|
||||||
CONFIG.Actor.dataModels.player = PlayerData;
|
CONFIG.Actor.dataModels.player = PlayerData;
|
||||||
|
CONFIG.Item.dataModels.generic = GenericItemData;
|
||||||
|
// #endregion Data Models
|
||||||
|
|
||||||
// We disable items in the system for now
|
// #region Sheets
|
||||||
CONFIG.Item.documentClass = TAFItem;
|
|
||||||
delete CONFIG.ui.sidebar.TABS.items;
|
|
||||||
|
|
||||||
foundry.documents.collections.Actors.registerSheet(
|
foundry.documents.collections.Actors.registerSheet(
|
||||||
__ID__,
|
__ID__,
|
||||||
PlayerSheet,
|
PlayerSheet,
|
||||||
|
|
@ -54,6 +57,16 @@ Hooks.on(`init`, () => {
|
||||||
{ label: `taf.sheet-names.AttributeOnlyPlayerSheet` },
|
{ label: `taf.sheet-names.AttributeOnlyPlayerSheet` },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
foundry.documents.collections.Items.registerSheet(
|
||||||
|
__ID__,
|
||||||
|
GenericItemSheet,
|
||||||
|
{
|
||||||
|
makeDefault: true,
|
||||||
|
label: `taf.sheet-names.GenericItemSheet`,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
// #endregion Sheets
|
||||||
|
|
||||||
registerWorldSettings();
|
registerWorldSettings();
|
||||||
|
|
||||||
registerSockets();
|
registerSockets();
|
||||||
|
|
|
||||||
6
module/hooks/ready.mjs
Normal file
6
module/hooks/ready.mjs
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
Hooks.on(`ready`, () => {
|
||||||
|
// Remove with issue: Foundry/taf#52
|
||||||
|
if (game.release.generation < 14 && globalThis._loc == null) {
|
||||||
|
globalThis._loc = game.i18n.format.bind(game.i18n);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { __ID__ } from "../consts.mjs";
|
import { __ID__ } from "../consts.mjs";
|
||||||
|
|
||||||
Hooks.on(`renderSettingsConfig`, (app, html, context, options) => {
|
Hooks.on(`renderSettingsConfig`, (app, html) => {
|
||||||
/*
|
/*
|
||||||
This section is used to insert a button into the settings config that unsets
|
This section is used to insert a button into the settings config that unsets
|
||||||
a world setting when it exists but doesn't allow any other form of editing it.
|
a world setting when it exists but doesn't allow any other form of editing it.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,20 @@
|
||||||
import "./api.mjs";
|
|
||||||
import "./hooks/init.mjs";
|
import "./hooks/init.mjs";
|
||||||
|
import "./hooks/ready.mjs";
|
||||||
import "./hooks/userConnected.mjs";
|
import "./hooks/userConnected.mjs";
|
||||||
import "./hooks/renderSettingsConfig.mjs";
|
import "./hooks/renderSettingsConfig.mjs";
|
||||||
|
|
||||||
|
import { api } from "./api.mjs";
|
||||||
|
import { config } from "./config.mjs";
|
||||||
|
|
||||||
|
Object.defineProperty(
|
||||||
|
globalThis,
|
||||||
|
`taf`,
|
||||||
|
{
|
||||||
|
value: Object.seal({
|
||||||
|
api,
|
||||||
|
config,
|
||||||
|
}),
|
||||||
|
writable: false,
|
||||||
|
enumerable: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,15 @@ export function registerWorldSettings() {
|
||||||
scope: `world`,
|
scope: `world`,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
game.settings.register(__ID__, `weightUnit`, {
|
||||||
|
name: `taf.settings.weightUnit.name`,
|
||||||
|
hint: `taf.settings.weightUnit.hint`,
|
||||||
|
config: true,
|
||||||
|
type: String,
|
||||||
|
default: ``,
|
||||||
|
scope: `world`,
|
||||||
|
});
|
||||||
|
|
||||||
game.settings.register(__ID__, `canPlayersManageAttributes`, {
|
game.settings.register(__ID__, `canPlayersManageAttributes`, {
|
||||||
name: `taf.settings.canPlayersManageAttributes.name`,
|
name: `taf.settings.canPlayersManageAttributes.name`,
|
||||||
hint: `taf.settings.canPlayersManageAttributes.hint`,
|
hint: `taf.settings.canPlayersManageAttributes.hint`,
|
||||||
|
|
|
||||||
12
module/utils/formatWeight.mjs
Normal file
12
module/utils/formatWeight.mjs
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { __ID__ } from "../consts.mjs";
|
||||||
|
import { toPrecision } from "./roundToPrecision.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats a numerical value as a weight.
|
||||||
|
*
|
||||||
|
* @param {number} weight The numerical weight to format
|
||||||
|
*/
|
||||||
|
export function formatWeight(weight) {
|
||||||
|
const unit = game.settings.get(__ID__, `weightUnit`);
|
||||||
|
return toPrecision(weight, 2) + unit;
|
||||||
|
};
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
const config = Object.preventExtensions({
|
const config = Object.seal({
|
||||||
subKeyPattern: /@(?<key>[a-zA-Z.]+)/gm,
|
subKeyPattern: /@(?<key>[a-zA-Z.]+)/gm,
|
||||||
maxDepth: 10,
|
maxDepth: 10,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
20
module/utils/roundToPrecision.mjs
Normal file
20
module/utils/roundToPrecision.mjs
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
/**
|
||||||
|
* Takes a possibly-decimal value and rounds after a certain precision, keeping
|
||||||
|
* only the specified amount of decimals.
|
||||||
|
*
|
||||||
|
* @param {number} value The value that is to be rounded.
|
||||||
|
* @param {number} precision The number of decimal places to round to. Must be a
|
||||||
|
* positive integer.
|
||||||
|
* @returns The rounded number
|
||||||
|
*/
|
||||||
|
export function toPrecision(value, precision = 1) {
|
||||||
|
if (!Number.isInteger(precision)) {
|
||||||
|
throw `Precision must be an integer`;
|
||||||
|
};
|
||||||
|
if (precision < 0) {
|
||||||
|
throw `Precision must be greater than or equal to 0`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const m = 10 ** precision;
|
||||||
|
return Math.round(value * m) / m;
|
||||||
|
};
|
||||||
78
styles/Apps/GenericItemSheet.css
Normal file
78
styles/Apps/GenericItemSheet.css
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
.taf.GenericItemSheet {
|
||||||
|
> .window-content {
|
||||||
|
padding: 0;
|
||||||
|
color: var(--item-sheet-colour);
|
||||||
|
background: var(--item-sheet-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sheet-header {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 1fr min-content 75px;
|
||||||
|
gap: 4px;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-bottom: 1px solid var(--item-sheet-divider-colour);
|
||||||
|
|
||||||
|
img {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
gap: inherit;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.property {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 100px;
|
||||||
|
align-items: center;
|
||||||
|
justify-items: left;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
taf-toggle {
|
||||||
|
justify-self: end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
flex-grow: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 8px;
|
||||||
|
--table-row-color-odd: var(--table-header-bg-color);
|
||||||
|
|
||||||
|
&:not(:has(> prose-mirror)) {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prose-mirror {
|
||||||
|
height: 100%;
|
||||||
|
background: var(--item-sheet-description-content-background);
|
||||||
|
--divider-colour: currentColor;
|
||||||
|
|
||||||
|
menu {
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
background: var(--item-sheet-description-menu-background);
|
||||||
|
|
||||||
|
button {
|
||||||
|
color: var(--item-sheet-description-menu-colour);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
color: var(--item-sheet-input-colour);
|
||||||
|
background: var(--item-sheet-input-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
taf-toggle {
|
||||||
|
--toggle-background: var(--item-sheet-input-background);
|
||||||
|
--slider-checked-colour: var(--item-sheet-toggle-slider-enabled-colour);
|
||||||
|
--slider-unchecked-colour: var(--item-sheet-toggle-slider-disabled-colour)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -37,6 +37,141 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.items-tab.active {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inventory-summary {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 4px;
|
||||||
|
align-items: center;
|
||||||
|
background: var(--inventory-summary-background);
|
||||||
|
color: var(--inventory-summary-colour);
|
||||||
|
padding: 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 75px;
|
||||||
|
text-align: center;
|
||||||
|
background: var(--inventory-input-background);
|
||||||
|
color: var(--inventory-input-colour);
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
color: var(--inventory-input-disabled-colour);
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-list-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
border-radius: 6px 6px 0 0;
|
||||||
|
padding: 6px 6px 4px;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
background: var(--item-list-header-background);
|
||||||
|
color: var(--item-list-header-colour);
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 2px;
|
||||||
|
border: none;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
height: unset;
|
||||||
|
min-height: unset;
|
||||||
|
background: var(--item-list-header-input-background);
|
||||||
|
color: var(--item-list-header-input-colour);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
background: var(--item-card-background);
|
||||||
|
color: var(--item-card-colour);
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 0;
|
||||||
|
|
||||||
|
.summary {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: min-content auto 1fr 50px auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
background: var(--item-card-header-background);
|
||||||
|
color: var(--item-card-header-colour);
|
||||||
|
padding: 4px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
--size: 35px;
|
||||||
|
width: var(--size);
|
||||||
|
height: var(--size);
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
.subtitle {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
opacity: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input, button {
|
||||||
|
background: var(--item-card-header-input-background);
|
||||||
|
color: var(--item-card-header-input-colour);
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
color: var(--item-card-header-disabled-input-colour);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-button {
|
||||||
|
border: none;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
filter: brightness(150%);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-expanded="true"] {
|
||||||
|
rotate: 180deg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-details {
|
||||||
|
padding: 4px;
|
||||||
|
|
||||||
|
&[data-expanded="false"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-radius: 0 0 6px 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,26 @@
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> .window-content nav.system-tabs {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: left;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
border: none;
|
||||||
|
text-shadow: none;
|
||||||
|
box-shadow: none;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
outline: 1px solid var(--tab-button-active-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--tab-button-hover-bg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
55
styles/components/toggle.css
Normal file
55
styles/components/toggle.css
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider {
|
||||||
|
width: var(--size, 16px);
|
||||||
|
height: var(--size, 16px);
|
||||||
|
background: var(
|
||||||
|
--slider-colour,
|
||||||
|
var(--toggle-slider-unchecked-colour)
|
||||||
|
);
|
||||||
|
transition: all 150ms ease-in-out;
|
||||||
|
border-radius: 9999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle {
|
||||||
|
display: flex;
|
||||||
|
padding: var(--padding, 4px);
|
||||||
|
height: calc(var(--size, 16px) + (var(--padding, 4px) * 2));
|
||||||
|
width: calc((var(--size, 16px) * 2) + (var(--padding, 4px) * 2));
|
||||||
|
border-radius: 9999px;
|
||||||
|
background: var(
|
||||||
|
--toggle-background,
|
||||||
|
var(--toggle-background-colour)
|
||||||
|
);
|
||||||
|
box-sizing: border-box;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
/* Non-checked, clicking */
|
||||||
|
&:is(&:active, &.active) .slider {
|
||||||
|
width: calc(var(--size, 16px) * 1.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* checked, non-clicking */
|
||||||
|
& > :checked + .slider {
|
||||||
|
transform: translateX(var(--size, 16px));
|
||||||
|
background: var(
|
||||||
|
--slider-checked-colour,
|
||||||
|
var(--toggle-slider-checked-colour)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* checked, clicking */
|
||||||
|
&:is(&:active, &.active) > :checked + .slider {
|
||||||
|
width: calc(var(--size, 16px) * 1.5);
|
||||||
|
transform: translateX(calc(var(--size, 16px) * 0.5));
|
||||||
|
}
|
||||||
|
}
|
||||||
5
styles/elements/button.css
Normal file
5
styles/elements/button.css
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
.taf > .window-content button {
|
||||||
|
&:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
.taf > .window-content div {
|
.taf > .window-content div {
|
||||||
&.chip {
|
&.chip {
|
||||||
display: inline flex;
|
display: inline flex;
|
||||||
color: var(--chip-color);
|
color: var(--chip-colour);
|
||||||
background: var(--chip-background);
|
background: var(--chip-background);
|
||||||
border: 1px solid var(--chip-border-color);
|
border: 1px solid var(--chip-border-colour);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
||||||
.key {
|
.key {
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
.value {
|
.value {
|
||||||
padding: 2px 4px;
|
padding: 2px 4px;
|
||||||
border-radius: 0 4px 4px 0;
|
border-radius: 0 4px 4px 0;
|
||||||
color: var(--chip-value-color);
|
color: var(--chip-value-colour);
|
||||||
background: var(--chip-value-background);
|
background: var(--chip-value-background);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,14 @@
|
||||||
.taf > .window-content {
|
.taf > .window-content {
|
||||||
h1, h2, h3, h4, h5, h6 {
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
font-family: var(--font-body);
|
||||||
|
color: currentColor;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h1 { font-size: 1.25rem; }
|
||||||
|
h2 { font-size: 1.20rem; }
|
||||||
|
h3 { font-size: 1.15rem; }
|
||||||
|
h4 { font-size: 1.1rem; }
|
||||||
|
h5 { font-size: 1.1rem; }
|
||||||
|
h6 { font-size: 1.1rem; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
.taf > .window-content hr {
|
.taf > .window-content hr {
|
||||||
|
color: inherit;
|
||||||
display: block;
|
display: block;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
background: rebeccapurple;
|
background: var(--divider-colour, rebeccapurple);
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,10 @@
|
||||||
.taf > .window-content input {
|
.taf > .window-content input {
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: color-mix(in srgb, currentColor 40%, transparent 60%);
|
||||||
|
}
|
||||||
|
|
||||||
&.large {
|
&.large {
|
||||||
--input-height: 2.5rem;
|
--input-height: 2.5rem;
|
||||||
font-size: 1.75rem;
|
font-size: 1.75rem;
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,19 @@
|
||||||
@layer resets, themes, elements, components, partials, apps, exceptions;
|
@layer resets, themes, elements, components, partials, apps, exceptions;
|
||||||
|
|
||||||
/* Resets */
|
/* Resets */
|
||||||
|
@import url("./resets/button.css") layer(resets);
|
||||||
@import url("./resets/hr.css") layer(resets);
|
@import url("./resets/hr.css") layer(resets);
|
||||||
@import url("./resets/inputs.css") layer(resets);
|
@import url("./resets/inputs.css") layer(resets);
|
||||||
@import url("./resets/button.css") layer(resets);
|
@import url("./resets/tabs.css") layer(resets);
|
||||||
|
|
||||||
/* Themes */
|
/* Themes */
|
||||||
|
@import url("./palettes/forgejo.css") layer(themes);
|
||||||
@import url("./themes/dark.css") layer(themes);
|
@import url("./themes/dark.css") layer(themes);
|
||||||
@import url("./themes/light.css") layer(themes);
|
@import url("./themes/light.css") layer(themes);
|
||||||
|
|
||||||
/* Elements */
|
/* Elements */
|
||||||
@import url("./elements/utils.css") layer(elements);
|
@import url("./elements/utils.css") layer(elements);
|
||||||
|
@import url("./elements/button.css") layer(elements);
|
||||||
@import url("./elements/div.css") layer(elements);
|
@import url("./elements/div.css") layer(elements);
|
||||||
@import url("./elements/headers.css") layer(elements);
|
@import url("./elements/headers.css") layer(elements);
|
||||||
@import url("./elements/hr.css") layer(elements);
|
@import url("./elements/hr.css") layer(elements);
|
||||||
|
|
@ -24,6 +27,7 @@
|
||||||
@import url("./Apps/common.css") layer(apps);
|
@import url("./Apps/common.css") layer(apps);
|
||||||
@import url("./Apps/Ask.css") layer(apps);
|
@import url("./Apps/Ask.css") layer(apps);
|
||||||
@import url("./Apps/AttributeManager.css") layer(apps);
|
@import url("./Apps/AttributeManager.css") layer(apps);
|
||||||
|
@import url("./Apps/GenericItemSheet.css") layer(apps);
|
||||||
@import url("./Apps/PlayerSheet.css") layer(apps);
|
@import url("./Apps/PlayerSheet.css") layer(apps);
|
||||||
@import url("./Apps/QueryStatus.css") layer(apps);
|
@import url("./Apps/QueryStatus.css") layer(apps);
|
||||||
@import url("./Apps/TAFDocumentSheetConfig.css") layer(apps);
|
@import url("./Apps/TAFDocumentSheetConfig.css") layer(apps);
|
||||||
|
|
|
||||||
49
styles/palettes/forgejo.css
Normal file
49
styles/palettes/forgejo.css
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
Most of this file comes from a combination of Forgejo's themes
|
||||||
|
|
||||||
|
- https://codeberg.org/forgejo/forgejo/src/commit/b68caa311fe5e3b7118130c2894c5b396b319681/web_src/css/themes/theme-forgejo-dark.css
|
||||||
|
- https://codeberg.org/forgejo/forgejo/src/commit/b68caa311fe5e3b7118130c2894c5b396b319681/web_src/css/themes/theme-forgejo-light.css
|
||||||
|
|
||||||
|
Licensed under the GNU GPL v3
|
||||||
|
*/
|
||||||
|
|
||||||
|
:root {
|
||||||
|
/* Steel */
|
||||||
|
--steel-900: #10161d;
|
||||||
|
--steel-850: #131a21;
|
||||||
|
--steel-800: #171e26;
|
||||||
|
--steel-750: #1d262f;
|
||||||
|
--steel-700: #242d38;
|
||||||
|
--steel-650: #2b3642;
|
||||||
|
--steel-600: #374351;
|
||||||
|
--steel-550: #445161;
|
||||||
|
--steel-500: #515f70;
|
||||||
|
--steel-450: #5f6e80;
|
||||||
|
--steel-400: #6d7d8f;
|
||||||
|
--steel-350: #7c8c9f;
|
||||||
|
--steel-300: #8c9caf;
|
||||||
|
--steel-250: #9dadc0;
|
||||||
|
--steel-200: #aebed0;
|
||||||
|
--steel-150: #c0cfe0;
|
||||||
|
--steel-100: #d2e0f0;
|
||||||
|
|
||||||
|
/* Zinc */
|
||||||
|
--zinc-50: #fafafa;
|
||||||
|
--zinc-100: #f4f4f5;
|
||||||
|
--zinc-150: #ececee;
|
||||||
|
--zinc-200: #e4e4e7;
|
||||||
|
--zinc-250: #dcdce0;
|
||||||
|
--zinc-300: #d4d4d8;
|
||||||
|
--zinc-350: #babac1;
|
||||||
|
--zinc-400: #a1a1aa;
|
||||||
|
--zinc-450: #898992;
|
||||||
|
--zinc-500: #71717a;
|
||||||
|
--zinc-550: #61616a;
|
||||||
|
--zinc-600: #52525b;
|
||||||
|
--zinc-650: #484850;
|
||||||
|
--zinc-700: #3f3f46;
|
||||||
|
--zinc-750: #333338;
|
||||||
|
--zinc-800: #27272a;
|
||||||
|
--zinc-850: #1f1f23;
|
||||||
|
--zinc-900: #18181b;
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,8 @@
|
||||||
.taf > .window-content button {
|
.taf > .window-content button {
|
||||||
height: initial;
|
height: initial;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
10
styles/resets/tabs.css
Normal file
10
styles/resets/tabs.css
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
.taf > .window-content {
|
||||||
|
nav.tabs.system-tabs {
|
||||||
|
all: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.sheet-tabs.top-tabs {
|
||||||
|
margin-inline: 0;
|
||||||
|
margin-top: calc(var(--spacer-8) * -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,10 +4,50 @@
|
||||||
--spinner-outer-colour: white;
|
--spinner-outer-colour: white;
|
||||||
--spinner-inner-colour: #FF3D00;
|
--spinner-inner-colour: #FF3D00;
|
||||||
|
|
||||||
|
--toggle-background-colour: #171e26;
|
||||||
|
--toggle-slider-unchecked-colour: maroon;
|
||||||
|
--toggle-slider-checked-colour: green;
|
||||||
|
|
||||||
|
--tab-button-active-border: rebeccapurple;
|
||||||
|
--tab-button-hover-bg: var(--color-cool-3);
|
||||||
|
|
||||||
|
/* Actor Sheet Variables */
|
||||||
|
/* Use --steel-850 as the main sheet background */
|
||||||
|
--inventory-summary-background: var(--steel-800);
|
||||||
|
--inventory-summary-colour: var(--steel-100);
|
||||||
|
--inventory-input-background: var(--steel-650);
|
||||||
|
--inventory-input-colour: var(--steel-100);
|
||||||
|
--inventory-input-disabled-colour: var(--steel-350);
|
||||||
|
|
||||||
|
--item-list-header-background: var(--steel-800);
|
||||||
|
--item-list-header-colour: var(--steel-100);
|
||||||
|
--item-list-header-input-background: var(--steel-650);
|
||||||
|
--item-list-header-input-colour: var(--steel-100);
|
||||||
|
|
||||||
|
--item-card-background: #1d262f;
|
||||||
|
--item-card-colour: var(--steel-100);
|
||||||
|
--item-card-header-background: var(--steel-700);
|
||||||
|
--item-card-header-colour: var(--steel-100);
|
||||||
|
--item-card-header-input-background: var(--steel-650);
|
||||||
|
--item-card-header-input-colour: var(--steel-100);
|
||||||
|
--item-card-header-disabled-input-colour: var(--steel-350);
|
||||||
|
|
||||||
|
/* Item Sheet Variables */
|
||||||
|
--item-sheet-colour: var(--steel-100);
|
||||||
|
--item-sheet-background: var(--steel-800);
|
||||||
|
--item-sheet-divider-colour: var(--steel-700);
|
||||||
|
--item-sheet-input-colour: var(--steel-100);
|
||||||
|
--item-sheet-input-background: var(--steel-650);
|
||||||
|
--item-sheet-toggle-slider-enabled-colour: green;
|
||||||
|
--item-sheet-toggle-slider-disabled-colour: maroon;
|
||||||
|
--item-sheet-description-menu-colour: var(--steel-100);
|
||||||
|
--item-sheet-description-menu-background: var(--steel-700);
|
||||||
|
--item-sheet-description-content-background: var(--steel-650);
|
||||||
|
|
||||||
/* Chip Variables */
|
/* Chip Variables */
|
||||||
--chip-color: #fff7ed;
|
--chip-colour: #fff7ed;
|
||||||
--chip-background: #2b3642;
|
--chip-background: #2b3642;
|
||||||
--chip-value-color: #fff7ed;
|
--chip-value-colour: #fff7ed;
|
||||||
--chip-value-background: #10161d;
|
--chip-value-background: #10161d;
|
||||||
--chip-border-color: var(--chip-value-background);
|
--chip-border-colour: var(--chip-value-background);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,13 @@
|
||||||
--spinner-outer-colour: black;
|
--spinner-outer-colour: black;
|
||||||
--spinner-inner-colour: #FF3D00;
|
--spinner-inner-colour: #FF3D00;
|
||||||
|
|
||||||
|
--tab-button-active: rebeccapurple;
|
||||||
|
--tab-button-hover-bg: var(--color-light-3);
|
||||||
|
|
||||||
/* Chip Variables */
|
/* Chip Variables */
|
||||||
--chip-color: #18181b;
|
--chip-colour: #18181b;
|
||||||
--chip-background: #fafafa;
|
--chip-background: #fafafa;
|
||||||
--chip-value-color: #18181b;
|
--chip-value-colour: #18181b;
|
||||||
--chip-value-background: #d4d4d8aa;
|
--chip-value-background: #d4d4d8aa;
|
||||||
--chip-border-color: var(--chip-value-background);
|
--chip-border-colour: var(--chip-value-background);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,14 @@
|
||||||
"filePathFields": {}
|
"filePathFields": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Item": {}
|
"Item": {
|
||||||
|
"generic": {
|
||||||
|
"htmlFields": [
|
||||||
|
"description"
|
||||||
|
],
|
||||||
|
"filePathFields": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"socket": true,
|
"socket": true,
|
||||||
"flags": {
|
"flags": {
|
||||||
|
|
|
||||||
50
templates/GenericItemSheet/content.hbs
Normal file
50
templates/GenericItemSheet/content.hbs
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
<div class="content">
|
||||||
|
<div class="property">
|
||||||
|
<label for="{{meta.idp}}-weight">
|
||||||
|
{{localize "taf.misc.item.weight"}}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="{{meta.idp}}-weight"
|
||||||
|
name="system.weight"
|
||||||
|
value="{{system.weight}}"
|
||||||
|
{{disabled (not meta.editable)}}
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="property">
|
||||||
|
<label for="{{meta.idp}}-group">
|
||||||
|
{{localize "taf.misc.item.group"}}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="{{meta.idp}}-group"
|
||||||
|
name="system.group"
|
||||||
|
value="{{system.group}}"
|
||||||
|
{{disabled (not meta.editable)}}
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="property">
|
||||||
|
<label for="{{meta.idp}}-equipped">
|
||||||
|
{{localize "taf.misc.item.equipped"}}
|
||||||
|
</label>
|
||||||
|
<taf-toggle
|
||||||
|
id="{{meta.idp}}-equipped"
|
||||||
|
name="system.equipped"
|
||||||
|
{{checked system.equipped}}
|
||||||
|
></taf-toggle>
|
||||||
|
</div>
|
||||||
|
<div class="bordered description">
|
||||||
|
{{#if meta.editable}}
|
||||||
|
<prose-mirror
|
||||||
|
name="system.description"
|
||||||
|
value="{{system.description}}"
|
||||||
|
collaborate="true"
|
||||||
|
data-document-uuid="{{item.uuid}}"
|
||||||
|
>
|
||||||
|
{{{ enriched.system.description }}}
|
||||||
|
</prose-mirror>
|
||||||
|
{{else}}
|
||||||
|
{{{ enriched.system.description }}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
28
templates/GenericItemSheet/header.hbs
Normal file
28
templates/GenericItemSheet/header.hbs
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<header class="sheet-header bordered">
|
||||||
|
<img
|
||||||
|
src="{{item.img}}"
|
||||||
|
data-action="editImage"
|
||||||
|
data-edit="img"
|
||||||
|
title="{{item.name}}"
|
||||||
|
height="48"
|
||||||
|
width="48"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="large"
|
||||||
|
name="name"
|
||||||
|
value="{{item.name}}"
|
||||||
|
title="{{item.name}}"
|
||||||
|
{{disabled (not meta.editable)}}
|
||||||
|
placeholder="{{localize "Name"}}"
|
||||||
|
>
|
||||||
|
<span aria-hidden="true">x</span>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
name="system.quantity"
|
||||||
|
value="{{system.quantity}}"
|
||||||
|
{{disabled (not meta.editable)}}
|
||||||
|
data-tooltip
|
||||||
|
aria-label="{{localize "taf.misc.item.quantity"}}"
|
||||||
|
>
|
||||||
|
</header>
|
||||||
|
|
@ -1,4 +1,8 @@
|
||||||
<div class="content">
|
<div
|
||||||
|
class="content tab {{ifThen tabActive "active" ""}}"
|
||||||
|
data-group="primary"
|
||||||
|
data-tab="content"
|
||||||
|
>
|
||||||
{{#if editable}}
|
{{#if editable}}
|
||||||
<prose-mirror
|
<prose-mirror
|
||||||
class="actor-text"
|
class="actor-text"
|
||||||
|
|
|
||||||
69
templates/PlayerSheet/item-lists.hbs
Normal file
69
templates/PlayerSheet/item-lists.hbs
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
<div
|
||||||
|
class="tab items-tab {{ifThen tabActive "active" ""}}"
|
||||||
|
data-group="primary"
|
||||||
|
data-tab="items"
|
||||||
|
>
|
||||||
|
<section class="inventory-summary">
|
||||||
|
<h3 class="grow">
|
||||||
|
{{localize "taf.Apps.PlayerSheet.carrying-capacity.title"}}
|
||||||
|
</h3>
|
||||||
|
{{#if hasCarryingCapacity}}
|
||||||
|
<div>
|
||||||
|
{{localize
|
||||||
|
"taf.Apps.PlayerSheet.carry-capacity-used"
|
||||||
|
percent=carryCapacityPercent
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value="{{totalWeight}}"
|
||||||
|
disabled
|
||||||
|
aria-label="{{localize "taf.Apps.PlayerSheet.total-weight"}}"
|
||||||
|
data-tooltip
|
||||||
|
data-tooltip-direction="UP"
|
||||||
|
>
|
||||||
|
<div aria-hidden="true">
|
||||||
|
/
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
name="system.carryCapacity"
|
||||||
|
value="{{system.carryCapacity}}"
|
||||||
|
placeholder="{{localize "taf.Apps.PlayerSheet.carrying-capacity.placeholder"}}"
|
||||||
|
aria-label="{{localize "taf.Apps.PlayerSheet.carrying-capacity.label"}}"
|
||||||
|
data-tooltip
|
||||||
|
data-tooltip-direction="UP"
|
||||||
|
>
|
||||||
|
</section>
|
||||||
|
{{#each itemGroups as | group |}}
|
||||||
|
<section>
|
||||||
|
<div class="item-list-header">
|
||||||
|
{{#if @root.meta.editable}}
|
||||||
|
<button
|
||||||
|
data-action="createEmbeddedItem"
|
||||||
|
data-item-group="{{ group.name }}"
|
||||||
|
aria-label="{{localize "taf.Apps.PlayerSheet.create-item"}}"
|
||||||
|
data-tooltip
|
||||||
|
>
|
||||||
|
<taf-icon
|
||||||
|
name="icons/plus"
|
||||||
|
var:fill="currentColor"
|
||||||
|
></taf-icon>
|
||||||
|
</button>
|
||||||
|
{{/if}}
|
||||||
|
<h3 class="grow">
|
||||||
|
{{ group.name }}
|
||||||
|
</h3>
|
||||||
|
<span class="weight">
|
||||||
|
{{ group.weight }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<ul class="item-list">
|
||||||
|
{{#each group.items as |item|}}
|
||||||
|
{{> (systemFilePath "templates/PlayerSheet/item.hbs") item }}
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
50
templates/PlayerSheet/item.hbs
Normal file
50
templates/PlayerSheet/item.hbs
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
<li
|
||||||
|
class="item"
|
||||||
|
data-item-uuid="{{uuid}}"
|
||||||
|
>
|
||||||
|
<div class="summary">
|
||||||
|
<taf-toggle
|
||||||
|
data-foreign-name="system.equipped"
|
||||||
|
data-foreign-uuid="{{uuid}}"
|
||||||
|
var:size="8px"
|
||||||
|
var:padding="2px"
|
||||||
|
{{checked equipped}}
|
||||||
|
></taf-toggle>
|
||||||
|
<img
|
||||||
|
src="{{img}}"
|
||||||
|
alt=""
|
||||||
|
>
|
||||||
|
<div class="title">
|
||||||
|
<span class="name">{{ name }}</span>
|
||||||
|
<span class="subtitle">{{ weight }}</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
id="{{uuid}}-quantity"
|
||||||
|
type="number"
|
||||||
|
value="{{quantity}}"
|
||||||
|
data-foreign-name="system.quantity"
|
||||||
|
data-foreign-uuid="{{uuid}}"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="expand-button"
|
||||||
|
data-action="toggleExpand"
|
||||||
|
data-expanded="{{isExpanded}}"
|
||||||
|
{{disabled (not canExpand)}}
|
||||||
|
aria-label="{{localize "taf.Apps.PlayerSheet.toggle-item-description"}}"
|
||||||
|
>
|
||||||
|
<taf-icon
|
||||||
|
name="icons/chevron"
|
||||||
|
var:colour="var(--item-card-header-input-color)"
|
||||||
|
></taf-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{{#if canExpand}}
|
||||||
|
<div
|
||||||
|
class="full-details"
|
||||||
|
data-expanded="{{isExpanded}}"
|
||||||
|
>
|
||||||
|
{{{ description }}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</li>
|
||||||
19
templates/generic/tabs.hbs
Normal file
19
templates/generic/tabs.hbs
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
{{#if hideTabs}}
|
||||||
|
<template></template>
|
||||||
|
{{else}}
|
||||||
|
<nav class="tabs system-tabs">
|
||||||
|
{{#each tabs as |tab|}}
|
||||||
|
{{#if tab.visible}}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="{{tab.cssClass}}"
|
||||||
|
data-action="tab"
|
||||||
|
data-group="{{tab.group}}"
|
||||||
|
data-tab="{{tab.id}}"
|
||||||
|
>
|
||||||
|
{{localize tab.label}}
|
||||||
|
</button>
|
||||||
|
{{/if}}
|
||||||
|
{{/each}}
|
||||||
|
</nav>
|
||||||
|
{{/if}}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue