Compare commits
26 commits
main
...
feature/tr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3aecc1ce8 | ||
|
|
1d13f38f81 | ||
|
|
8ed6f49c8d | ||
|
|
d70c5113b1 | ||
|
|
0d1c2ddbb3 | ||
|
|
cd69228e68 | ||
|
|
28345bdef0 | ||
|
|
798e7441b4 | ||
|
|
d81d86ef47 | ||
|
|
e7ac049ae3 | ||
|
|
4a8ce9b099 | ||
|
|
28989c2d35 | ||
|
|
f237bce4d9 | ||
|
|
7935a85188 | ||
|
|
163423ea5b | ||
|
|
0626279fbe | ||
|
|
ca185ba42a | ||
|
|
e8baec0bc4 | ||
|
|
165c24f32c | ||
|
|
d6beb4ba63 | ||
|
|
7de3f1ca87 | ||
|
|
4d0f29d7f0 | ||
|
|
0e0f9d3831 | ||
|
|
803c1673e2 | ||
|
|
8de63e91c7 | ||
|
|
507c9b0341 |
44 changed files with 931 additions and 123 deletions
23
dev/hooks/getHeaderControlsActorSheetV2.mjs
Normal file
23
dev/hooks/getHeaderControlsActorSheetV2.mjs
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
This hook exists to be able to change all of the actor-sheets to allow
|
||||||
|
them to have dev-mode controls in their header that are useful for
|
||||||
|
testing purposes. (This is particularly useful for testing embedded
|
||||||
|
items that are only allowed to exist on specific actor types)
|
||||||
|
*/
|
||||||
|
Hooks.on(`getHeaderControlsActorSheetV2`, (app, controls) => {
|
||||||
|
if (!game.settings.get(`ripcrypt`, `devMode`)) { return }
|
||||||
|
|
||||||
|
controls.push(
|
||||||
|
{
|
||||||
|
icon: `fa-solid fa-terminal`,
|
||||||
|
label: `Embed New Item (DEV)`,
|
||||||
|
action: `createItem`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: `Make Global Reference`,
|
||||||
|
onClick: () => {
|
||||||
|
globalThis._doc = app.actor;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Logger } from "../utils/Logger.mjs";
|
import { Logger } from "../../module/utils/Logger.mjs";
|
||||||
|
|
||||||
const loaders = {
|
const loaders = {
|
||||||
svg(data) {
|
svg(data) {
|
||||||
3
dev/main.mjs
Normal file
3
dev/main.mjs
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
// Hooks
|
||||||
|
import "./hooks/hotReload.mjs";
|
||||||
|
import "./hooks/getHeaderControlsActorSheetV2.mjs";
|
||||||
|
|
@ -74,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`,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
"@stylistic/brace-style": [`warn`, `stroustrup`, { "allowSingleLine": true }],
|
"@stylistic/brace-style": [`warn`, `stroustrup`, { "allowSingleLine": true }],
|
||||||
"@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` }],
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"module/**/*",
|
"module/**/*",
|
||||||
|
"dev/**/*",
|
||||||
"foundry/client/client.mjs",
|
"foundry/client/client.mjs",
|
||||||
"foundry/client/global.d.mts",
|
"foundry/client/global.d.mts",
|
||||||
"foundry/common/primitives/global.d.mts"
|
"foundry/common/primitives/global.d.mts"
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
"good": "Good",
|
"good": "Good",
|
||||||
"shield": "Shield",
|
"shield": "Shield",
|
||||||
"skill": "Skill",
|
"skill": "Skill",
|
||||||
|
"trait": "Trait",
|
||||||
"weapon": "Weapon"
|
"weapon": "Weapon"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -173,6 +174,7 @@
|
||||||
"guts-value-readonly": "The current amount of guts the character has",
|
"guts-value-readonly": "The current amount of guts the character has",
|
||||||
"guts-max-readonly": "The maximum amount of guts the character can have"
|
"guts-max-readonly": "The maximum amount of guts the character can have"
|
||||||
},
|
},
|
||||||
|
"edit-description": "Edit Description",
|
||||||
"traits-placeholder": "New Trait...",
|
"traits-placeholder": "New Trait...",
|
||||||
"short-range": "Short @RipCrypt.common.range",
|
"short-range": "Short @RipCrypt.common.range",
|
||||||
"long-range": "Long @RipCrypt.common.range",
|
"long-range": "Long @RipCrypt.common.range",
|
||||||
|
|
@ -201,7 +203,8 @@
|
||||||
"invalid-socket": "Invalid socket data received, this means a module or system bug is present.",
|
"invalid-socket": "Invalid socket data received, this means a module or system bug is present.",
|
||||||
"unknown-socket-event": "An unknown socket event was received: {event}",
|
"unknown-socket-event": "An unknown socket event was received: {event}",
|
||||||
"no-active-gm": "No active @USER.GM is logged in, you must wait for a @USER.GM to be active before you can do that.",
|
"no-active-gm": "No active @USER.GM is logged in, you must wait for a @USER.GM to be active before you can do that.",
|
||||||
"malformed-socket-payload": "Socket event \"{event}\" received with malformed payload. Details: {details}"
|
"malformed-socket-payload": "Socket event \"{event}\" received with malformed payload. Details: {details}",
|
||||||
|
"invalid-parent-document": "Cannot create an item with type \"{itemType}\" on a parent document of type \"{parentType}\""
|
||||||
},
|
},
|
||||||
"warn": {
|
"warn": {
|
||||||
"cannot-go-negative": "\"{name}\" is unable to be a negative number."
|
"cannot-go-negative": "\"{name}\" is unable to be a negative number."
|
||||||
|
|
|
||||||
217
module/Apps/ActorSheets/BookGeistSheet.mjs
Normal file
217
module/Apps/ActorSheets/BookGeistSheet.mjs
Normal file
|
|
@ -0,0 +1,217 @@
|
||||||
|
import { filePath } from "../../consts.mjs";
|
||||||
|
import { GenericAppMixin } from "../mixins/GenericApp.mjs";
|
||||||
|
import { LaidOutAppMixin } from "../mixins/LaidOutAppMixin.mjs";
|
||||||
|
import { localizer } from "../../utils/Localizer.mjs";
|
||||||
|
import { editItemFromElement, deleteItemFromElement } from "../utils.mjs";
|
||||||
|
import { gameTerms } from "../../gameTerms.mjs";
|
||||||
|
|
||||||
|
const { HandlebarsApplicationMixin } = foundry.applications.api;
|
||||||
|
const { ActorSheetV2 } = foundry.applications.sheets;
|
||||||
|
const { ContextMenu, TextEditor } = foundry.applications.ux;
|
||||||
|
|
||||||
|
export class BookGeistSheet extends
|
||||||
|
LaidOutAppMixin(
|
||||||
|
GenericAppMixin(
|
||||||
|
HandlebarsApplicationMixin(
|
||||||
|
ActorSheetV2,
|
||||||
|
))) {
|
||||||
|
// #region Options
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: [
|
||||||
|
`ripcrypt--actor`,
|
||||||
|
`BookGeistSheet`,
|
||||||
|
],
|
||||||
|
position: {
|
||||||
|
width: `auto`,
|
||||||
|
height: `auto`,
|
||||||
|
},
|
||||||
|
window: {
|
||||||
|
resizable: true,
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
submitOnChange: true,
|
||||||
|
closeOnSubmite: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static PARTS = {
|
||||||
|
layout: {
|
||||||
|
root: true,
|
||||||
|
template: filePath(`templates/Apps/BookGeistSheet/layout.hbs`),
|
||||||
|
},
|
||||||
|
image: { template: filePath(`templates/Apps/BookGeistSheet/image.hbs`) },
|
||||||
|
header: { template: filePath(`templates/Apps/BookGeistSheet/header.hbs`) },
|
||||||
|
stats: { template: filePath(`templates/Apps/BookGeistSheet/stats.hbs`) },
|
||||||
|
items: { template: filePath(`templates/Apps/BookGeistSheet/items.hbs`) },
|
||||||
|
};
|
||||||
|
// #endregion Options
|
||||||
|
|
||||||
|
// #region Lifecycle
|
||||||
|
async _onRender() {
|
||||||
|
await super._onRender();
|
||||||
|
|
||||||
|
new ContextMenu.implementation(
|
||||||
|
this.element,
|
||||||
|
`[data-ctx-menu="item"]`,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: localizer(`RipCrypt.common.edit`),
|
||||||
|
condition: (el) => {
|
||||||
|
const itemId = el.dataset.itemId;
|
||||||
|
return itemId !== ``;
|
||||||
|
},
|
||||||
|
callback: editItemFromElement,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: localizer(`RipCrypt.common.delete`),
|
||||||
|
condition: (el) => {
|
||||||
|
const itemId = el.dataset.itemId;
|
||||||
|
return itemId !== ``;
|
||||||
|
},
|
||||||
|
callback: deleteItemFromElement,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
{ jQuery: false, fixed: true },
|
||||||
|
);
|
||||||
|
};
|
||||||
|
// #endregion Lifecycle
|
||||||
|
|
||||||
|
// #region Data Prep
|
||||||
|
async _preparePartContext(partID, ctx) {
|
||||||
|
|
||||||
|
switch (partID) {
|
||||||
|
case `layout`: await this._prepareLayoutContext(ctx); break;
|
||||||
|
case `image`: await this._prepareImageContext(ctx); break;
|
||||||
|
case `header`: await this._prepareHeaderContext(ctx); break;
|
||||||
|
case `stats`: await this._prepareStatsContext(ctx); break;
|
||||||
|
case `items`: await this._prepareItemsContext(ctx); break;
|
||||||
|
};
|
||||||
|
|
||||||
|
return ctx;
|
||||||
|
};
|
||||||
|
|
||||||
|
async _prepareLayoutContext(ctx) {
|
||||||
|
ctx.imageVisible = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
async _prepareImageContext(ctx) {
|
||||||
|
ctx.url = this.actor.img;
|
||||||
|
};
|
||||||
|
|
||||||
|
async _prepareHeaderContext(ctx) {
|
||||||
|
ctx.name = this.actor.name;
|
||||||
|
ctx.rank = this.actor.system.level.rank;
|
||||||
|
ctx.ranks = Object.values(gameTerms.Rank)
|
||||||
|
.map((value, index) => ({
|
||||||
|
value,
|
||||||
|
label: index
|
||||||
|
}));
|
||||||
|
ctx.description = await TextEditor.implementation.enrichHTML(this.actor.system.description);
|
||||||
|
};
|
||||||
|
|
||||||
|
async _prepareStatsContext(ctx) {
|
||||||
|
const system = this.actor.system;
|
||||||
|
|
||||||
|
const fate = system.fate;
|
||||||
|
if (fate) {
|
||||||
|
ctx.path = {
|
||||||
|
full: localizer(`RipCrypt.common.ordinals.${fate}.full`),
|
||||||
|
abbv: localizer(`RipCrypt.common.ordinals.${fate}.abbv`),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ctx.path = {
|
||||||
|
full: null,
|
||||||
|
abbv: localizer(`RipCrypt.common.empty`),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.assign(ctx, system.ability);
|
||||||
|
ctx.guts = system.guts;
|
||||||
|
ctx.speed = system.speed;
|
||||||
|
};
|
||||||
|
|
||||||
|
async _prepareItemsContext(ctx) {
|
||||||
|
const armours = this.actor.system.equippedArmour;
|
||||||
|
const shield = this.actor.system.equippedShield;
|
||||||
|
|
||||||
|
let defenses = [];
|
||||||
|
for (const [location, armour] of Object.entries(armours)) {
|
||||||
|
if (!armour) { continue }
|
||||||
|
const defense = {
|
||||||
|
name: localizer(`RipCrypt.common.anatomy.${location}`),
|
||||||
|
tooltip: null,
|
||||||
|
protection: 0,
|
||||||
|
shielded: false,
|
||||||
|
};
|
||||||
|
if (armour) {
|
||||||
|
defense.armourUUID = armour.uuid;
|
||||||
|
defense.tooltip = armour.name,
|
||||||
|
defense.protection = armour.system.protection;
|
||||||
|
}
|
||||||
|
if (shield?.system.location.has(location)) {
|
||||||
|
defense.shieldUUID = shield.uuid;
|
||||||
|
defense.shielded = true;
|
||||||
|
defense.protection += shield.system.protection;
|
||||||
|
};
|
||||||
|
if (defense.protection > 0) {
|
||||||
|
defenses.push(defense);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.defenses = defenses
|
||||||
|
ctx.traits = []; // Array<{name: string}>
|
||||||
|
|
||||||
|
for (const item of this.actor.items) {
|
||||||
|
switch (item.type) {
|
||||||
|
case `weapon`: {
|
||||||
|
if (!item.system.equipped) { continue };
|
||||||
|
ctx.attacks ??= [];
|
||||||
|
ctx.attacks.push(this._prepareWeapon(item));
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
case `craft`: {
|
||||||
|
ctx.crafts ??= [];
|
||||||
|
ctx.crafts.push(this._prepareCraft(item));
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
case `trait`: {
|
||||||
|
ctx.traits.push(this._prepareTrait(item));
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
_prepareWeapon(weapon) {
|
||||||
|
const hasShortRange = weapon.system.range.short != null;
|
||||||
|
const hasLongRange = weapon.system.range.long != null;
|
||||||
|
const isRanged = hasShortRange || hasLongRange;
|
||||||
|
return {
|
||||||
|
uuid: weapon.uuid,
|
||||||
|
name: weapon.name,
|
||||||
|
damage: weapon.system.damage,
|
||||||
|
isRanged,
|
||||||
|
range: weapon.system.range,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
_prepareCraft(craft) {
|
||||||
|
return {
|
||||||
|
uuid: craft.uuid,
|
||||||
|
name: craft.name,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
_prepareTrait(trait) {
|
||||||
|
return {
|
||||||
|
uuid: trait.uuid,
|
||||||
|
name: trait.name,
|
||||||
|
description: trait.system.description,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
// #endregion Data Prep
|
||||||
|
|
||||||
|
// #region Actions
|
||||||
|
// #endregion Actions
|
||||||
|
};
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { CraftCardV1 } from "./CraftCardV1.mjs";
|
import { CraftCardV1 } from "./CraftCardV1.mjs";
|
||||||
import { filePath } from "../../consts.mjs";
|
import { filePath } from "../../consts.mjs";
|
||||||
import { GenericAppMixin } from "../GenericApp.mjs";
|
import { GenericAppMixin } from "../mixins/GenericApp.mjs";
|
||||||
import { SkillsCardV1 } from "./SkillsCardV1.mjs";
|
import { SkillsCardV1 } from "./SkillsCardV1.mjs";
|
||||||
import { StatsCardV1 } from "./StatsCardV1.mjs";
|
import { StatsCardV1 } from "./StatsCardV1.mjs";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { deleteItemFromElement, editItemFromElement } from "../utils.mjs";
|
import { deleteItemFromElement, editItemFromElement } from "../utils.mjs";
|
||||||
import { documentSorter, filePath } from "../../consts.mjs";
|
import { documentSorter, filePath } from "../../consts.mjs";
|
||||||
import { gameTerms } from "../../gameTerms.mjs";
|
import { gameTerms } from "../../gameTerms.mjs";
|
||||||
import { GenericAppMixin } from "../GenericApp.mjs";
|
import { GenericAppMixin } from "../mixins/GenericApp.mjs";
|
||||||
import { localizer } from "../../utils/Localizer.mjs";
|
import { localizer } from "../../utils/Localizer.mjs";
|
||||||
import { Logger } from "../../utils/Logger.mjs";
|
import { Logger } from "../../utils/Logger.mjs";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { deleteItemFromElement, editItemFromElement } from "../utils.mjs";
|
||||||
import { documentSorter, filePath } from "../../consts.mjs";
|
import { documentSorter, filePath } from "../../consts.mjs";
|
||||||
import { AmmoTracker } from "../popovers/AmmoTracker.mjs";
|
import { AmmoTracker } from "../popovers/AmmoTracker.mjs";
|
||||||
import { gameTerms } from "../../gameTerms.mjs";
|
import { gameTerms } from "../../gameTerms.mjs";
|
||||||
import { GenericAppMixin } from "../GenericApp.mjs";
|
import { GenericAppMixin } from "../mixins/GenericApp.mjs";
|
||||||
import { ItemFlags } from "../../flags/item.mjs";
|
import { ItemFlags } from "../../flags/item.mjs";
|
||||||
import { localizer } from "../../utils/Localizer.mjs";
|
import { localizer } from "../../utils/Localizer.mjs";
|
||||||
import { Logger } from "../../utils/Logger.mjs";
|
import { Logger } from "../../utils/Logger.mjs";
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { deleteItemFromElement, editItemFromElement } from "../utils.mjs";
|
||||||
import { DelveDiceHUD } from "../DelveDiceHUD.mjs";
|
import { DelveDiceHUD } from "../DelveDiceHUD.mjs";
|
||||||
import { filePath } from "../../consts.mjs";
|
import { filePath } from "../../consts.mjs";
|
||||||
import { gameTerms } from "../../gameTerms.mjs";
|
import { gameTerms } from "../../gameTerms.mjs";
|
||||||
import { GenericAppMixin } from "../GenericApp.mjs";
|
import { GenericAppMixin } from "../mixins/GenericApp.mjs";
|
||||||
import { localizer } from "../../utils/Localizer.mjs";
|
import { localizer } from "../../utils/Localizer.mjs";
|
||||||
import { Logger } from "../../utils/Logger.mjs";
|
import { Logger } from "../../utils/Logger.mjs";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -143,7 +143,6 @@ export class DelveDiceHUD extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
Logger.log(`${partId} Context`, ctx);
|
|
||||||
return ctx;
|
return ctx;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { filePath } from "../consts.mjs";
|
import { filePath } from "../consts.mjs";
|
||||||
import { GenericAppMixin } from "./GenericApp.mjs";
|
import { GenericAppMixin } from "./mixins/GenericApp.mjs";
|
||||||
import { localizer } from "../utils/Localizer.mjs";
|
import { localizer } from "../utils/Localizer.mjs";
|
||||||
import { Logger } from "../utils/Logger.mjs";
|
import { Logger } from "../utils/Logger.mjs";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { filePath } from "../../consts.mjs";
|
import { filePath } from "../../consts.mjs";
|
||||||
import { GenericAppMixin } from "../GenericApp.mjs";
|
import { GenericAppMixin } from "../mixins/GenericApp.mjs";
|
||||||
import { Logger } from "../../utils/Logger.mjs";
|
import { Logger } from "../../utils/Logger.mjs";
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin } = foundry.applications.api;
|
const { HandlebarsApplicationMixin } = foundry.applications.api;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { filePath } from "../../consts.mjs";
|
import { filePath } from "../../consts.mjs";
|
||||||
import { gameTerms } from "../../gameTerms.mjs";
|
import { gameTerms } from "../../gameTerms.mjs";
|
||||||
import { GenericAppMixin } from "../GenericApp.mjs";
|
import { GenericAppMixin } from "../mixins/GenericApp.mjs";
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin } = foundry.applications.api;
|
const { HandlebarsApplicationMixin } = foundry.applications.api;
|
||||||
const { ItemSheetV2 } = foundry.applications.sheets;
|
const { ItemSheetV2 } = foundry.applications.sheets;
|
||||||
|
|
|
||||||
53
module/Apps/ItemSheets/TraitSheet.mjs
Normal file
53
module/Apps/ItemSheets/TraitSheet.mjs
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { filePath } from "../../consts.mjs";
|
||||||
|
import { GenericAppMixin } from "../mixins/GenericApp.mjs";
|
||||||
|
|
||||||
|
const { HandlebarsApplicationMixin } = foundry.applications.api;
|
||||||
|
const { ItemSheetV2 } = foundry.applications.sheets;
|
||||||
|
|
||||||
|
export class TraitSheet extends GenericAppMixin(HandlebarsApplicationMixin(ItemSheetV2)) {
|
||||||
|
// #region Options
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: [
|
||||||
|
`ripcrypt--item`,
|
||||||
|
`TraitSheet`,
|
||||||
|
],
|
||||||
|
position: {
|
||||||
|
width: `auto`,
|
||||||
|
height: `auto`,
|
||||||
|
},
|
||||||
|
window: {
|
||||||
|
resizable: true,
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
submitOnChange: true,
|
||||||
|
closeOnSubmit: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static PARTS = {
|
||||||
|
content: {
|
||||||
|
template: filePath(`templates/Apps/TraitSheet/content.hbs`),
|
||||||
|
root: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// #endregion Options
|
||||||
|
|
||||||
|
// #region Data Prep
|
||||||
|
async _prepareContext() {
|
||||||
|
const TextEditor = foundry.applications.ux.TextEditor.implementation;
|
||||||
|
const ctx = {
|
||||||
|
meta: {
|
||||||
|
idp: this.id,
|
||||||
|
},
|
||||||
|
item: this.document,
|
||||||
|
enriched: {
|
||||||
|
system: {
|
||||||
|
description: await TextEditor.enrichHTML(this.document.system.description),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return ctx;
|
||||||
|
};
|
||||||
|
// #endregion Data Prep
|
||||||
|
};
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { createItemFromElement, deleteItemFromElement, editItemFromElement, updateForeignDocumentFromEvent } from "./utils.mjs";
|
import { createItemFromElement, deleteItemFromElement, editItemFromElement, updateForeignDocumentFromEvent } from "../utils.mjs";
|
||||||
import { DicePool } from "./DicePool.mjs";
|
import { DicePool } from "../DicePool.mjs";
|
||||||
import { RichEditor } from "./RichEditor.mjs";
|
import { RichEditor } from "../RichEditor.mjs";
|
||||||
import { toBoolean } from "../consts.mjs";
|
import { toBoolean } from "../../consts.mjs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A mixin that takes the class from HandlebarsApplicationMixin and combines it
|
* A mixin that takes the class from HandlebarsApplicationMixin and combines it
|
||||||
|
|
@ -37,6 +37,8 @@ export function GenericAppMixin(HandlebarsApp) {
|
||||||
_popoverManagers = new Map();
|
_popoverManagers = new Map();
|
||||||
/** @type {Map<number, string>} */
|
/** @type {Map<number, string>} */
|
||||||
_hookIDs = new Map();
|
_hookIDs = new Map();
|
||||||
|
/** @type {string | null} */
|
||||||
|
#focus = null;
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
// #region Lifecycle
|
// #region Lifecycle
|
||||||
|
|
@ -53,6 +55,26 @@ export function GenericAppMixin(HandlebarsApp) {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @override
|
||||||
|
* This method overrides Foundry's default behaviour for caching the focused
|
||||||
|
* element so that it actually works when the application has a root partial
|
||||||
|
*/
|
||||||
|
async _preRender(...args) {
|
||||||
|
if (this.rendered) {
|
||||||
|
const target = this.element.querySelector(`:focus`);
|
||||||
|
if (target) {
|
||||||
|
if (target.id) {
|
||||||
|
this.#focus = `#${CSS.escape(target.id)}`;
|
||||||
|
}
|
||||||
|
else if (target.name) {
|
||||||
|
this.#focus = `${target.tagName}[name="${target.name}"]`;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
return super._preRender(...args);
|
||||||
|
};
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
async _onRender(...args) {
|
async _onRender(...args) {
|
||||||
await super._onRender(...args);
|
await super._onRender(...args);
|
||||||
|
|
@ -83,19 +105,30 @@ export function GenericAppMixin(HandlebarsApp) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
async _preparePartContext(partId, ctx, opts) {
|
/**
|
||||||
ctx = await super._preparePartContext(partId, ctx, opts);
|
* @override
|
||||||
delete ctx.document;
|
* This method overrides Foundry's default behaviour for caching the focused
|
||||||
delete ctx.fields;
|
* element so that it actually works when the application has a root partial
|
||||||
|
*/
|
||||||
|
async _postRender(...args) {
|
||||||
|
if (this.rendered) {
|
||||||
|
const target = this.element.querySelector(this.#focus);
|
||||||
|
target?.focus();
|
||||||
|
};
|
||||||
|
this.#focus = null;
|
||||||
|
return super._postRender(...args);
|
||||||
|
};
|
||||||
|
|
||||||
|
async _prepareContext(_options) {
|
||||||
|
const ctx = {};
|
||||||
|
|
||||||
ctx.meta ??= {};
|
ctx.meta ??= {};
|
||||||
ctx.meta.idp = this.document?.uuid ?? this.id;
|
ctx.meta.idp = this.id;
|
||||||
if (this.document) {
|
if (this.document) {
|
||||||
ctx.meta.limited = this.document.limited;
|
ctx.meta.limited = this.document.limited;
|
||||||
ctx.meta.editable = this.isEditable || game.user.isGM;
|
ctx.meta.editable = this.isEditable || game.user.isGM;
|
||||||
ctx.meta.embedded = this.document.isEmbedded;
|
ctx.meta.embedded = this.document.isEmbedded;
|
||||||
};
|
};
|
||||||
delete ctx.editable;
|
|
||||||
|
|
||||||
return ctx;
|
return ctx;
|
||||||
};
|
};
|
||||||
56
module/Apps/mixins/LaidOutAppMixin.mjs
Normal file
56
module/Apps/mixins/LaidOutAppMixin.mjs
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
/**
|
||||||
|
* This mixin makes it so that we can provide a specific layout template without
|
||||||
|
* needing to reference each of the inner areas via a partial embedded in the root,
|
||||||
|
* enabling partial re-renders for parts of the sheet without losing advanced
|
||||||
|
* layout capabilities.
|
||||||
|
*
|
||||||
|
* @param {ReturnType<HandlebarsApp>} HandlebarsApp The mixin'd class from HAM to further mix
|
||||||
|
*/
|
||||||
|
export function LaidOutAppMixin(HandlebarsApp) {
|
||||||
|
class LaidOutApp extends HandlebarsApp {
|
||||||
|
#partDescriptors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This caches the part descriptors into this class of the heirarchy, because
|
||||||
|
* Foundry doesn't expose the partDescriptors from the HAM directly, so we
|
||||||
|
* inject a heirarchy call so that we can nab the pointer that Foundry has
|
||||||
|
* in the HAM so that we can also read/write it from this class.
|
||||||
|
*/
|
||||||
|
_configureRenderParts(options) {
|
||||||
|
const parts = super._configureRenderParts(options);
|
||||||
|
this.#partDescriptors = parts;
|
||||||
|
return parts;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @override
|
||||||
|
* This is essentially Foundry's HandlebarsApplicationMixin implementation,
|
||||||
|
* however if an existing part for non-root elements don't get concatenated
|
||||||
|
* into the DOM.
|
||||||
|
*/
|
||||||
|
_replaceHTML(result, content, options) {
|
||||||
|
const partInfo = this.#partDescriptors;
|
||||||
|
for ( const [partId, htmlElement] of Object.entries(result) ) {
|
||||||
|
const part = partInfo[partId];
|
||||||
|
const priorElement = part.root ? content : content.querySelector(`[data-application-part="${partId}"]`);
|
||||||
|
const state = {};
|
||||||
|
if ( priorElement ) {
|
||||||
|
this._preSyncPartState(partId, htmlElement, priorElement, state);
|
||||||
|
if ( part.root ) {
|
||||||
|
priorElement.replaceChildren(...htmlElement.children);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
priorElement.replaceWith(htmlElement);
|
||||||
|
}
|
||||||
|
this._syncPartState(partId, htmlElement, priorElement, state);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
this._attachPartListeners(partId, htmlElement, options);
|
||||||
|
this.parts[partId] = htmlElement;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
return LaidOutApp;
|
||||||
|
};
|
||||||
|
|
@ -1,3 +1,18 @@
|
||||||
import { EntityData } from "./Entity.mjs";
|
import { EntityData } from "./Entity.mjs";
|
||||||
|
|
||||||
export class GeistData extends EntityData {};
|
const { fields } = foundry.data;
|
||||||
|
|
||||||
|
export class GeistData extends EntityData {
|
||||||
|
static defineSchema() {
|
||||||
|
const schema = super.defineSchema();
|
||||||
|
|
||||||
|
schema.description = new fields.HTMLField({
|
||||||
|
blank: true,
|
||||||
|
nullable: true,
|
||||||
|
trim: true,
|
||||||
|
initial: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
return schema;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,19 +2,6 @@ import { CommonItemData } from "./Common.mjs";
|
||||||
import { gameTerms } from "../../gameTerms.mjs";
|
import { gameTerms } from "../../gameTerms.mjs";
|
||||||
|
|
||||||
export class AmmoData extends CommonItemData {
|
export class AmmoData extends CommonItemData {
|
||||||
// MARK: Base Data
|
|
||||||
prepareBaseData() {
|
|
||||||
super.prepareBaseData();
|
|
||||||
};
|
|
||||||
|
|
||||||
// MARK: Derived Data
|
|
||||||
prepareDerivedData() {
|
|
||||||
super.prepareDerivedData();
|
|
||||||
};
|
|
||||||
|
|
||||||
// #region Getters
|
|
||||||
// #endregion
|
|
||||||
|
|
||||||
// #region Sheet Data
|
// #region Sheet Data
|
||||||
getFormFields(_ctx) {
|
getFormFields(_ctx) {
|
||||||
const fields = [
|
const fields = [
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { gameTerms } from "../../gameTerms.mjs";
|
||||||
const { fields } = foundry.data;
|
const { fields } = foundry.data;
|
||||||
|
|
||||||
export class CommonItemData extends foundry.abstract.TypeDataModel {
|
export class CommonItemData extends foundry.abstract.TypeDataModel {
|
||||||
// MARK: Schema
|
// #region Schema
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
return {
|
return {
|
||||||
quantity: requiredInteger({ min: 0, initial: 1 }),
|
quantity: requiredInteger({ min: 0, initial: 1 }),
|
||||||
|
|
@ -21,14 +21,5 @@ export class CommonItemData extends foundry.abstract.TypeDataModel {
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
// #endregion Schema
|
||||||
// MARK: Base Data
|
|
||||||
prepareBaseData() {
|
|
||||||
super.prepareBaseData();
|
|
||||||
};
|
|
||||||
|
|
||||||
// MARK: Derived Data
|
|
||||||
prepareDerivedData() {
|
|
||||||
super.prepareDerivedData();
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -21,19 +21,6 @@ export class CraftData extends SkillData {
|
||||||
return schema;
|
return schema;
|
||||||
};
|
};
|
||||||
|
|
||||||
// MARK: Base Data
|
|
||||||
prepareBaseData() {
|
|
||||||
super.prepareBaseData();
|
|
||||||
};
|
|
||||||
|
|
||||||
// MARK: Derived Data
|
|
||||||
prepareDerivedData() {
|
|
||||||
super.prepareDerivedData();
|
|
||||||
};
|
|
||||||
|
|
||||||
// #region Getters
|
|
||||||
// #endregion
|
|
||||||
|
|
||||||
// #region Sheet Data
|
// #region Sheet Data
|
||||||
async getFormFields(_ctx) {
|
async getFormFields(_ctx) {
|
||||||
const fields = [
|
const fields = [
|
||||||
|
|
|
||||||
|
|
@ -17,19 +17,6 @@ export class GoodData extends CommonItemData {
|
||||||
return schema;
|
return schema;
|
||||||
};
|
};
|
||||||
|
|
||||||
// MARK: Base Data
|
|
||||||
prepareBaseData() {
|
|
||||||
super.prepareBaseData();
|
|
||||||
};
|
|
||||||
|
|
||||||
// MARK: Derived Data
|
|
||||||
prepareDerivedData() {
|
|
||||||
super.prepareDerivedData();
|
|
||||||
};
|
|
||||||
|
|
||||||
// #region Getters
|
|
||||||
// #endregion
|
|
||||||
|
|
||||||
// #region Sheet Data
|
// #region Sheet Data
|
||||||
async getFormFields(_ctx) {
|
async getFormFields(_ctx) {
|
||||||
const fields = [
|
const fields = [
|
||||||
|
|
|
||||||
|
|
@ -34,19 +34,6 @@ export class SkillData extends foundry.abstract.TypeDataModel {
|
||||||
return schema;
|
return schema;
|
||||||
};
|
};
|
||||||
|
|
||||||
// MARK: Base Data
|
|
||||||
prepareBaseData() {
|
|
||||||
super.prepareBaseData();
|
|
||||||
};
|
|
||||||
|
|
||||||
// MARK: Derived Data
|
|
||||||
prepareDerivedData() {
|
|
||||||
super.prepareDerivedData();
|
|
||||||
};
|
|
||||||
|
|
||||||
// #region Getters
|
|
||||||
// #endregion
|
|
||||||
|
|
||||||
// #region Sheet Data
|
// #region Sheet Data
|
||||||
async getFormFields(_ctx) {
|
async getFormFields(_ctx) {
|
||||||
const fields = [
|
const fields = [
|
||||||
|
|
|
||||||
25
module/data/Item/Trait.mjs
Normal file
25
module/data/Item/Trait.mjs
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { localizer } from "../../utils/Localizer.mjs";
|
||||||
|
|
||||||
|
const { fields } = foundry.data;
|
||||||
|
|
||||||
|
export class TraitData extends foundry.abstract.TypeDataModel {
|
||||||
|
// #region Schema
|
||||||
|
static defineSchema() {
|
||||||
|
return {
|
||||||
|
description: new fields.HTMLField({ blank: true, nullable: false, trim: true }),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
// #endregion Schema
|
||||||
|
|
||||||
|
// #region Lifecycle
|
||||||
|
async _preCreate() {
|
||||||
|
if (this.parent.isEmbedded && this.parent.parent.type !== `geist`) {
|
||||||
|
ui.notifications.error(localizer(
|
||||||
|
`RipCrypt.notifs.error.invalid-parent-document`,
|
||||||
|
{ itemType: `trait`, parentType: this.parent.parent.type },
|
||||||
|
));
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
// #endregion
|
||||||
|
};
|
||||||
|
|
@ -49,10 +49,13 @@ export class WeaponData extends CommonItemData {
|
||||||
async _preCreate(item, options) {
|
async _preCreate(item, options) {
|
||||||
const showEquipPrompt = options.showEquipPrompt ?? true;
|
const showEquipPrompt = options.showEquipPrompt ?? true;
|
||||||
if (showEquipPrompt && this.parent.isEmbedded && this._canEquip()) {
|
if (showEquipPrompt && this.parent.isEmbedded && this._canEquip()) {
|
||||||
const shouldEquip = await DialogV2.confirm({
|
let shouldEquip = this.parent.parent.type === `geist`;
|
||||||
|
|
||||||
|
shouldEquip ||= await DialogV2.confirm({
|
||||||
window: { title: `Equip Item?` },
|
window: { title: `Equip Item?` },
|
||||||
content: `Do you want to equip ${item.name}?`,
|
content: `Do you want to equip ${item.name}?`,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (shouldEquip) {
|
if (shouldEquip) {
|
||||||
this.updateSource({ "equipped": true });
|
this.updateSource({ "equipped": true });
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -9,5 +9,6 @@ export default {
|
||||||
"rc-options": options,
|
"rc-options": options,
|
||||||
|
|
||||||
// #region Simple
|
// #region Simple
|
||||||
|
"rc-ifOut": (v) => (v || ``),
|
||||||
"rc-empty-state": (v) => v ?? localizer(`RipCrypt.common.empty`),
|
"rc-empty-state": (v) => v ?? localizer(`RipCrypt.common.empty`),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Applications
|
// Applications
|
||||||
import { AllItemSheetV1 } from "../Apps/ItemSheets/AllItemSheetV1.mjs";
|
import { AllItemSheetV1 } from "../Apps/ItemSheets/AllItemSheetV1.mjs";
|
||||||
import { ArmourSheet } from "../Apps/ItemSheets/ArmourSheet.mjs";
|
import { ArmourSheet } from "../Apps/ItemSheets/ArmourSheet.mjs";
|
||||||
|
import { BookGeistSheet } from "../Apps/ActorSheets/BookGeistSheet.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";
|
||||||
|
|
@ -17,6 +18,7 @@ import { GoodData } from "../data/Item/Good.mjs";
|
||||||
import { HeroData } from "../data/Actor/Hero.mjs";
|
import { HeroData } from "../data/Actor/Hero.mjs";
|
||||||
import { ShieldData } from "../data/Item/Shield.mjs";
|
import { ShieldData } from "../data/Item/Shield.mjs";
|
||||||
import { SkillData } from "../data/Item/Skill.mjs";
|
import { SkillData } from "../data/Item/Skill.mjs";
|
||||||
|
import { TraitData } from "../data/Item/Trait.mjs";
|
||||||
import { WeaponData } from "../data/Item/Weapon.mjs";
|
import { WeaponData } from "../data/Item/Weapon.mjs";
|
||||||
|
|
||||||
// Class Overrides
|
// Class Overrides
|
||||||
|
|
@ -37,6 +39,7 @@ import { registerMetaSettings } from "../settings/metaSettings.mjs";
|
||||||
import { registerSockets } from "../sockets/_index.mjs";
|
import { registerSockets } from "../sockets/_index.mjs";
|
||||||
import { registerUserSettings } from "../settings/userSettings.mjs";
|
import { registerUserSettings } from "../settings/userSettings.mjs";
|
||||||
import { registerWorldSettings } from "../settings/worldSettings.mjs";
|
import { registerWorldSettings } from "../settings/worldSettings.mjs";
|
||||||
|
import { TraitSheet } from "../Apps/ItemSheets/TraitSheet.mjs";
|
||||||
|
|
||||||
const { Items, Actors } = foundry.documents.collections;
|
const { Items, Actors } = foundry.documents.collections;
|
||||||
|
|
||||||
|
|
@ -62,6 +65,7 @@ Hooks.once(`init`, () => {
|
||||||
CONFIG.Item.dataModels.good = GoodData;
|
CONFIG.Item.dataModels.good = GoodData;
|
||||||
CONFIG.Item.dataModels.shield = ShieldData;
|
CONFIG.Item.dataModels.shield = ShieldData;
|
||||||
CONFIG.Item.dataModels.skill = SkillData;
|
CONFIG.Item.dataModels.skill = SkillData;
|
||||||
|
CONFIG.Item.dataModels.trait = TraitData;
|
||||||
CONFIG.Item.dataModels.weapon = WeaponData;
|
CONFIG.Item.dataModels.weapon = WeaponData;
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
|
|
@ -87,22 +91,21 @@ Hooks.once(`init`, () => {
|
||||||
label: `RipCrypt.sheet-names.StatsCardV1`,
|
label: `RipCrypt.sheet-names.StatsCardV1`,
|
||||||
themes: StatsCardV1.themes,
|
themes: StatsCardV1.themes,
|
||||||
});
|
});
|
||||||
Actors.registerSheet(game.system.id, StatsCardV1, {
|
|
||||||
makeDefault: true,
|
|
||||||
types: [`geist`],
|
|
||||||
label: `RipCrypt.sheet-names.StatsCardV1`,
|
|
||||||
themes: StatsCardV1.themes,
|
|
||||||
});
|
|
||||||
Actors.registerSheet(game.system.id, SkillsCardV1, {
|
Actors.registerSheet(game.system.id, SkillsCardV1, {
|
||||||
types: [`hero`, `geist`],
|
types: [`hero`],
|
||||||
label: `RipCrypt.sheet-names.SkillsCardV1`,
|
label: `RipCrypt.sheet-names.SkillsCardV1`,
|
||||||
themes: SkillsCardV1.themes,
|
themes: SkillsCardV1.themes,
|
||||||
});
|
});
|
||||||
Actors.registerSheet(game.system.id, CraftCardV1, {
|
Actors.registerSheet(game.system.id, CraftCardV1, {
|
||||||
types: [`hero`, `geist`],
|
types: [`hero`],
|
||||||
label: `RipCrypt.sheet-names.CraftCardV1`,
|
label: `RipCrypt.sheet-names.CraftCardV1`,
|
||||||
themes: CraftCardV1.themes,
|
themes: CraftCardV1.themes,
|
||||||
});
|
});
|
||||||
|
Actors.registerSheet(game.system.id, BookGeistSheet, {
|
||||||
|
types: [`geist`],
|
||||||
|
label: `RipCrypt.sheet-names.BookGeistSheet`,
|
||||||
|
themes: BookGeistSheet.themes,
|
||||||
|
});
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
// #region Items
|
// #region Items
|
||||||
|
|
@ -118,8 +121,15 @@ Hooks.once(`init`, () => {
|
||||||
label: `RipCrypt.sheet-names.ArmourSheet`,
|
label: `RipCrypt.sheet-names.ArmourSheet`,
|
||||||
themes: ArmourSheet.themes,
|
themes: ArmourSheet.themes,
|
||||||
});
|
});
|
||||||
|
Items.registerSheet(game.system.id, TraitSheet, {
|
||||||
|
makeDefault: true,
|
||||||
|
types: [`trait`],
|
||||||
|
label: `RipCrypt.sheet-names.TraitSheet`,
|
||||||
|
themes: TraitSheet.themes,
|
||||||
|
});
|
||||||
|
|
||||||
Items.unregisterSheet(game.system.id, AllItemSheetV1, {
|
Items.unregisterSheet(game.system.id, AllItemSheetV1, {
|
||||||
types: [`armour`, `shield`],
|
types: [`armour`, `shield`, `trait`],
|
||||||
});
|
});
|
||||||
// #endregion
|
// #endregion
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
// Hooks
|
// Hooks
|
||||||
import "./hooks/init.mjs";
|
import "./hooks/init.mjs";
|
||||||
import "./hooks/ready.mjs";
|
import "./hooks/ready.mjs";
|
||||||
import "./hooks/hotReload.mjs";
|
|
||||||
|
|
||||||
// Global API
|
// Global API
|
||||||
import "./api.mjs";
|
import "./api.mjs";
|
||||||
|
|
|
||||||
34
scripts/prepareManifest.mjs
Normal file
34
scripts/prepareManifest.mjs
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
The intent of this script is to do all of the modifications of the
|
||||||
|
manifest file that we need to do in order to release the system. This
|
||||||
|
can include removing dev-only fields/attributes that end users will
|
||||||
|
never, and should never, care about nor need.
|
||||||
|
*/
|
||||||
|
import { readFile, writeFile } from "fs/promises";
|
||||||
|
|
||||||
|
const MANIFEST_PATH = `system.json`;
|
||||||
|
|
||||||
|
let manifest;
|
||||||
|
try {
|
||||||
|
manifest = JSON.parse(await readFile(MANIFEST_PATH, `utf-8`));
|
||||||
|
} catch {
|
||||||
|
console.error(`Failed to parse manifest file.`);
|
||||||
|
process.exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Filter out dev-only resources
|
||||||
|
if (manifest.esmodules) {
|
||||||
|
manifest.esmodules = manifest.esmodules.filter(
|
||||||
|
filepath => !filepath.startsWith(`dev/`)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Remove dev flags
|
||||||
|
delete manifest.flags?.hotReload;
|
||||||
|
|
||||||
|
if (Object.keys(manifest.flags).length === 0) {
|
||||||
|
delete manifest.flags;
|
||||||
|
};
|
||||||
|
|
||||||
|
await writeFile(MANIFEST_PATH, JSON.stringify(manifest, undefined, `\t`));
|
||||||
22
system.json
22
system.json
|
|
@ -12,7 +12,8 @@
|
||||||
{ "name": "Oliver" }
|
{ "name": "Oliver" }
|
||||||
],
|
],
|
||||||
"esmodules": [
|
"esmodules": [
|
||||||
"module/main.mjs"
|
"module/main.mjs",
|
||||||
|
"dev/main.mjs"
|
||||||
],
|
],
|
||||||
"styles": [
|
"styles": [
|
||||||
{
|
{
|
||||||
|
|
@ -36,7 +37,7 @@
|
||||||
"flags": {
|
"flags": {
|
||||||
"hotReload": {
|
"hotReload": {
|
||||||
"extensions": ["css", "hbs", "json", "mjs", "svg"],
|
"extensions": ["css", "hbs", "json", "mjs", "svg"],
|
||||||
"paths": ["assets", "templates", "langs", "module"]
|
"paths": ["assets", "templates", "langs", "module", "dev"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"documentTypes": {
|
"documentTypes": {
|
||||||
|
|
@ -48,9 +49,22 @@
|
||||||
"ammo": {},
|
"ammo": {},
|
||||||
"armour": {},
|
"armour": {},
|
||||||
"craft": {},
|
"craft": {},
|
||||||
"good": {},
|
"good": {
|
||||||
|
"htmlFields": [
|
||||||
|
"description"
|
||||||
|
]
|
||||||
|
},
|
||||||
"shield": {},
|
"shield": {},
|
||||||
"skill": {},
|
"skill": {
|
||||||
|
"htmlFields": [
|
||||||
|
"description"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"trait": {
|
||||||
|
"htmlFields": [
|
||||||
|
"description"
|
||||||
|
]
|
||||||
|
},
|
||||||
"weapon": {}
|
"weapon": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
24
templates/Apps/BookGeistSheet/header.hbs
Normal file
24
templates/Apps/BookGeistSheet/header.hbs
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
<div>
|
||||||
|
<div class="overview">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="name"
|
||||||
|
value="{{name}}"
|
||||||
|
>
|
||||||
|
<div class="grow"></div>
|
||||||
|
<label for="{{meta-idp}}-rank">
|
||||||
|
Rank
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="{{meta.idp}}-rank"
|
||||||
|
name="system.level.rank"
|
||||||
|
>
|
||||||
|
{{rc-options rank ranks}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
{{#if description}}
|
||||||
|
<div class="description">
|
||||||
|
{{{description}}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
8
templates/Apps/BookGeistSheet/image.hbs
Normal file
8
templates/Apps/BookGeistSheet/image.hbs
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
<div class="img-wrapper">
|
||||||
|
<img
|
||||||
|
src="{{url}}"
|
||||||
|
alt=""
|
||||||
|
data-action="editImage"
|
||||||
|
data-edit="img"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
78
templates/Apps/BookGeistSheet/items.hbs
Normal file
78
templates/Apps/BookGeistSheet/items.hbs
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
<div class="items">
|
||||||
|
{{#if attacks}}
|
||||||
|
<div>Attacks</div>
|
||||||
|
<div>
|
||||||
|
{{#each attacks as |attack|}}
|
||||||
|
<div
|
||||||
|
class="attack"
|
||||||
|
data-ctx-menu="item"
|
||||||
|
data-item-id="{{attack.uuid}}"
|
||||||
|
>
|
||||||
|
{{attack.name}}
|
||||||
|
{{attack.damage}}
|
||||||
|
{{#if attack.isRanged}}
|
||||||
|
<span class="range">
|
||||||
|
({{attack.range.short}} / {{attack.range.long}})
|
||||||
|
</span>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
{{#if crafts}}
|
||||||
|
<div>Craft</div>
|
||||||
|
<div>
|
||||||
|
{{#each crafts as |craft|}}
|
||||||
|
<div
|
||||||
|
class="craft"
|
||||||
|
data-ctx-menu="item"
|
||||||
|
data-item-id="{{craft.uuid}}"
|
||||||
|
>
|
||||||
|
{{craft.name}}
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
<div>Defense</div>
|
||||||
|
<ul>
|
||||||
|
{{#each defenses as |defense|}}
|
||||||
|
<li
|
||||||
|
class="defense"
|
||||||
|
data-tooltip="{{defense.tooltip}}"
|
||||||
|
data-ctx-menu="item"
|
||||||
|
data-item-id="{{defense.armourUUID}}"
|
||||||
|
>
|
||||||
|
{{defense.name}} ({{defense.protection}}{{#if defense.shielded}},
|
||||||
|
<rc-icon
|
||||||
|
name="icons/shield/solid.v1"
|
||||||
|
var:size="14px"
|
||||||
|
var:fill="currentColor"
|
||||||
|
data-ctx-menu="item"
|
||||||
|
data-item-id="{{defense.shieldUUID}}"
|
||||||
|
/>
|
||||||
|
{{/if}})
|
||||||
|
</li>
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
<div>Traits</div>
|
||||||
|
<ul class="traits">
|
||||||
|
{{#each traits as |trait|}}
|
||||||
|
<li
|
||||||
|
class="trait"
|
||||||
|
data-ctx-menu="item"
|
||||||
|
data-item-id="{{trait.uuid}}"
|
||||||
|
>
|
||||||
|
{{trait.name}}
|
||||||
|
{{#if trait.description}}
|
||||||
|
<rc-icon
|
||||||
|
var:fill="currentColor"
|
||||||
|
name="icons/info-circle"
|
||||||
|
data-tooltip="{{trait.description}}"
|
||||||
|
/>
|
||||||
|
{{/if}}
|
||||||
|
</li>
|
||||||
|
{{else}}
|
||||||
|
None
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
10
templates/Apps/BookGeistSheet/layout.hbs
Normal file
10
templates/Apps/BookGeistSheet/layout.hbs
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<div>
|
||||||
|
{{#if imageVisible}}
|
||||||
|
<div data-application-part="image"></div>
|
||||||
|
{{/if}}
|
||||||
|
<div class="info">
|
||||||
|
<div data-application-part="header"></div>
|
||||||
|
<div data-application-part="stats"></div>
|
||||||
|
<div data-application-part="items"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
97
templates/Apps/BookGeistSheet/stats.hbs
Normal file
97
templates/Apps/BookGeistSheet/stats.hbs
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
<div>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td class="alt">Path</td>
|
||||||
|
<td>Grit</td>
|
||||||
|
<td>Gait</td>
|
||||||
|
<td>Grip</td>
|
||||||
|
<td>Glim</td>
|
||||||
|
<td class="alt">Guts</td>
|
||||||
|
<td class="alt">Move</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
class="alt"
|
||||||
|
data-tooltip="{{path.full}}"
|
||||||
|
>
|
||||||
|
{{path.abbv}}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{#if meta.editable}}
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="{{meta.idp}}-grit"
|
||||||
|
value="{{grit}}"
|
||||||
|
name="system.ability.grit"
|
||||||
|
>
|
||||||
|
{{else if meta.limited}}
|
||||||
|
???
|
||||||
|
{{else}}
|
||||||
|
{{grit}}
|
||||||
|
{{/if}}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{#if meta.editable}}
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="{{meta.idp}}-gait"
|
||||||
|
value="{{gait}}"
|
||||||
|
name="system.ability.gait"
|
||||||
|
>
|
||||||
|
{{else if meta.limited}}
|
||||||
|
???
|
||||||
|
{{else}}
|
||||||
|
{{gait}}
|
||||||
|
{{/if}}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{#if meta.editable}}
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="{{meta.idp}}-grip"
|
||||||
|
value="{{grip}}"
|
||||||
|
name="system.ability.grip"
|
||||||
|
>
|
||||||
|
{{else if meta.limited}}
|
||||||
|
???
|
||||||
|
{{else}}
|
||||||
|
{{grip}}
|
||||||
|
{{/if}}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{#if meta.editable}}
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="{{meta.idp}}-glim"
|
||||||
|
value="{{glim}}"
|
||||||
|
name="system.ability.glim"
|
||||||
|
>
|
||||||
|
{{else if meta.limited}}
|
||||||
|
???
|
||||||
|
{{else}}
|
||||||
|
{{glim}}
|
||||||
|
{{/if}}
|
||||||
|
</td>
|
||||||
|
<td class="alt">
|
||||||
|
{{#if meta.editable}}
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="{{meta.idp}}-guts-value"
|
||||||
|
value="{{guts.value}}"
|
||||||
|
name="system.guts.value"
|
||||||
|
>
|
||||||
|
/ {{guts.max}}
|
||||||
|
{{else if meta.limited}}
|
||||||
|
??/??
|
||||||
|
{{else}}
|
||||||
|
{{guts.value}}/{{guts.max}}
|
||||||
|
{{/if}}
|
||||||
|
</td>
|
||||||
|
<td class="alt">{{speed.move}} / {{speed.run}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
101
templates/Apps/BookGeistSheet/style.css
Normal file
101
templates/Apps/BookGeistSheet/style.css
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
.BookGeistSheet {
|
||||||
|
> .window-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 8px;
|
||||||
|
color: var(--base-text);
|
||||||
|
background: var(--base-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.overview {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
td {
|
||||||
|
border: 1px solid var(--accent-1);
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 30px;
|
||||||
|
background: unset;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
thead td {
|
||||||
|
font-weight: bold;
|
||||||
|
border-top-width: 0;
|
||||||
|
|
||||||
|
&:first-of-type, &:last-of-type {
|
||||||
|
border-left-width: 0;
|
||||||
|
border-right-width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody tr {
|
||||||
|
td:first-of-type, td:last-of-type {
|
||||||
|
border-left-width: 0;
|
||||||
|
border-right-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-of-type td {
|
||||||
|
border-bottom-width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.alt {
|
||||||
|
background-color: var(--alt-row-background);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.items {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) minmax(0, 4fr);
|
||||||
|
grid-template-rows: repeat(3, auto);
|
||||||
|
gap: 2px;
|
||||||
|
|
||||||
|
ul {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 4px;
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
background-color: var(--accent-2);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 2px 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
templates/Apps/TraitSheet/content.hbs
Normal file
21
templates/Apps/TraitSheet/content.hbs
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="name"
|
||||||
|
aria-label="{{ rc-i18n "Name" }}"
|
||||||
|
name="name"
|
||||||
|
value="{{item.name}}"
|
||||||
|
{{disabled meta.limited}}
|
||||||
|
autocomplete="off"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
data-action="openRichEditor"
|
||||||
|
data-path="system.description"
|
||||||
|
data-uuid="{{item.uuid}}"
|
||||||
|
data-collaborative="true"
|
||||||
|
>
|
||||||
|
{{ rc-i18n "RipCrypt.Apps.edit-description" }}
|
||||||
|
</button>
|
||||||
|
<div class="value">{{{ enriched.system.description }}}</div>
|
||||||
|
</div>
|
||||||
35
templates/Apps/TraitSheet/style.css
Normal file
35
templates/Apps/TraitSheet/style.css
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
.ripcrypt.TraitSheet {
|
||||||
|
--input-underline: none;
|
||||||
|
max-width: 300px;
|
||||||
|
|
||||||
|
> .window-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 4px;
|
||||||
|
color: var(--base-text);
|
||||||
|
background: var(--base-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 2px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
background: var(--input-background);
|
||||||
|
color: var(--input-text);
|
||||||
|
padding: 4px;
|
||||||
|
|
||||||
|
> :first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
> :last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
@import url("./common.css");
|
||||||
@import url("./AllItemSheetV1/style.css");
|
@import url("./AllItemSheetV1/style.css");
|
||||||
@import url("./CombinedHeroSheet/style.css");
|
@import url("./CombinedHeroSheet/style.css");
|
||||||
@import url("./DelveDiceHUD/style.css");
|
@import url("./DelveDiceHUD/style.css");
|
||||||
|
|
@ -7,24 +8,8 @@
|
||||||
@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("./ArmourSheet/style.css");
|
||||||
|
@import url("./TraitSheet/style.css");
|
||||||
|
@import url("./BookGeistSheet/style.css");
|
||||||
|
|
||||||
@import url("./popover.css");
|
@import url("./popover.css");
|
||||||
@import url("./popovers/AmmoTracker/style.css");
|
@import url("./popovers/AmmoTracker/style.css");
|
||||||
|
|
||||||
.ripcrypt {
|
|
||||||
.window-content {
|
|
||||||
flex: initial;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.StatsCardV1,
|
|
||||||
.SkillsCardV1,
|
|
||||||
.CraftCardV1 {
|
|
||||||
padding: 8px;
|
|
||||||
/* height: 270px; */
|
|
||||||
width: 680px;
|
|
||||||
--col-gap: 2px;
|
|
||||||
--row-gap: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
17
templates/Apps/common.css
Normal file
17
templates/Apps/common.css
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
.ripcrypt {
|
||||||
|
.window-content {
|
||||||
|
flex: initial;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.StatsCardV1,
|
||||||
|
.SkillsCardV1,
|
||||||
|
.CraftCardV1 {
|
||||||
|
padding: 8px;
|
||||||
|
/* height: 270px; */
|
||||||
|
width: 680px;
|
||||||
|
--col-gap: 2px;
|
||||||
|
--row-gap: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
Required parameters:
|
Required parameters:
|
||||||
"name" : the name of the item
|
"name" : the name of the item
|
||||||
"system.quantity" : the quantity of the item
|
"system.quantity" : the quantity of the item
|
||||||
"meta.idp" : the ID Prefix for the application
|
|
||||||
--}}
|
--}}
|
||||||
<header class="item-header">
|
<header class="item-header">
|
||||||
<div class="name-row">
|
<div class="name-row">
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
.ripcrypt prose-mirror * {
|
|
||||||
all: revert-layer;
|
|
||||||
}
|
|
||||||
|
|
@ -26,4 +26,3 @@
|
||||||
@import url("../Apps/apps.css") layer(apps);
|
@import url("../Apps/apps.css") layer(apps);
|
||||||
|
|
||||||
/* Exceptions */
|
/* Exceptions */
|
||||||
@import url("./elements/prose-mirror.css") layer(exceptions);
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue