Compare commits

..

No commits in common. "main" and "v1.0.2" have entirely different histories.
main ... v1.0.2

33 changed files with 126 additions and 275 deletions

View file

@ -1,2 +0,0 @@
# The absolute path to the Foundry installation to create symlinks to
FOUNDRY_ROOT=""

View file

@ -50,13 +50,7 @@ jobs:
tag: "v${{ steps.version.outputs.version }}" tag: "v${{ steps.version.outputs.version }}"
commit: ${{ github.ref }} commit: ${{ github.ref }}
draft: true draft: true
body: > body: "This version can be installed using this manifest URL: https://github.com/Oliver-Akins/Foundry-Stat-Tracker/releases/download/v${{ steps.version.outputs.version }}/module.json"
| <img aria-hidden="true" src="https://img.shields.io/github/downloads/Oliver-Akins/Foundry-Stat-Tracker/v${{ steps.version.outputs.version }}/release.zip?style=flat-square&color=%2300aa00">
|
| ### Changes:
| -
|
| This version can be installed using this manifest URL: https://github.com/Oliver-Akins/Foundry-Stat-Tracker/releases/download/v${{ steps.version.outputs.version }}/module.json
generateReleaseNotes: true generateReleaseNotes: true
artifacts: "prod.dist/release.zip,prod.dist/module.json" artifacts: "prod.dist/release.zip,prod.dist/module.json"
artifactsErrorsFailBuild: true artifactsErrorsFailBuild: true

2
.gitignore vendored
View file

@ -17,8 +17,6 @@ lerna-debug.log*
node_modules node_modules
/*.dist /*.dist
*.local *.local
.env
/foundry
# Editor directories and files # Editor directories and files
.vscode/* .vscode/*

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

View file

@ -1,30 +1,2 @@
# Stats Tracker # Foundry-Stat-Tracker
This FoundryVTT module aims to provide a clean way of keeping track of any data A Foundry module that allows tracking arbitrary stats.
points you want within Foundry, whether that be dice rolls, or other things like
how many natural 1s to natural 20s you get.
I was inspired by the dicestats module, however it only allows tracking dice
statistics, which is something I found myself needing to work around and struggle
against, so I decided to make this module to fill that gap while improving upon
the graph rendering.
For more information on how to use this module, check out the "Documentation"
compendium within your world!
## Installation
<div style="display: flex; gap: 8px;">
<img aria-hidden="true" src="https://img.shields.io/github/v/release/Oliver-Akins/Foundry-Stat-Tracker?sort=semver&style=flat-square&label=Latest%20Stable%20Release&color=%23070">
<img aria-hidden="true" src="https://img.shields.io/github/v/release/Oliver-Akins/Foundry-Stat-Tracker?include_prereleases&sort=semver&style=flat-square&color=%23800&label=Latest%20Prerelease">
</div>
You can find a history of all releases on the [Foundry package listing](https://foundryvtt.com/packages/stat-tracker)
or in the [GitHub releases tab](https://github.com/Oliver-Akins/Foundry-Stat-Tracker/releases).
Prereleases will only be released to the GitHub page, so if you want to check
out those releases, you'll need to use those manifest links directly.
## Bugs or Feature Requests
Bugs and Feature Requests can be submitted via the [GitHub issues](https://github.com/Oliver-Akins/Foundry-Stat-Tracker/issues/new/choose).
Planned features can also be seen in the GitHub issues list.
## Contribution
Contribution guidelines / requirements coming soon.

14
augments.d.ts vendored
View file

@ -1,14 +0,0 @@
declare global {
class Hooks extends foundry.helpers.Hooks {};
const fromUuid = foundry.utils.fromUuid;
}
interface Actor {
/** The system-specific data */
system: any;
};
interface Item {
/** The system-specific data */
system: any;
};

View file

@ -1,22 +1,10 @@
{ {
"compilerOptions": { "compilerOptions": {
"module": "ES2020", "module": "ES2020",
"target": "ES2020", "target": "ES2020"
"types": [
"./augments.d.ts"
],
"paths": {
"@client/*": ["./foundry/client/*"],
"@common/*": ["./foundry/common/*"],
}
}, },
"exclude": ["node_modules", "**/node_modules/*"], "exclude": ["node_modules", "**/node_modules/*"],
"include": [ "include": ["module/**/*"],
"module/**/*",
"foundry/client/client.mjs",
"foundry/client/global.d.mts",
"foundry/common/primitives/global.d.mts"
],
"typeAcquisition": { "typeAcquisition": {
"include": ["joi"] "include": ["joi"]
} }

View file

@ -75,7 +75,7 @@ export class TableCreator extends HandlebarsApplicationMixin(ApplicationV2) {
if (this._name.startsWith(`Dice`)) { if (this._name.startsWith(`Dice`)) {
ctx.createButtonDisabled = !this._name.match(diceNamespacePattern); ctx.createButtonDisabled = !this._name.match(diceNamespacePattern);
ctx.typeDisabled = true; ctx.typeDisabled = true;
ctx.type = BucketTypes.NUMBER; ctx.type = BucketTypes.RANGE;
this.#diceNamespaceAlert ??= ui.notifications.info( this.#diceNamespaceAlert ??= ui.notifications.info(
`Tables in the "Dice" namespace must be formatted as "Dice/dX" where X is the number of sides on the die and are restricted to be ranges 1 to X.`, `Tables in the "Dice" namespace must be formatted as "Dice/dX" where X is the number of sides on the die and are restricted to be ranges 1 to X.`,
{ permanent: true }, { permanent: true },

View file

@ -1,8 +1,8 @@
import { barGraphTests } from "./schemas/barGraph.test.mjs"; import { barGraphTests } from "./schemas/barGraph.mjs";
import { numberBucketTests } from "./schemas/numberBucket.test.mjs"; import { numberBucketTests } from "./schemas/numberBucket.mjs";
import { rowTests } from "./schemas/row.test.mjs"; import { rowTests } from "./schemas/row.mjs";
import { stringBucketTests } from "./schemas/stringBucket.test.mjs"; import { stringBucketTests } from "./schemas/stringBucket.mjs";
import { tableTests } from "./schemas/table.test.mjs"; import { tableTests } from "./schemas/table.mjs";
Hooks.on(`quenchReady`, (quench) => { Hooks.on(`quenchReady`, (quench) => {
numberBucketTests(quench); numberBucketTests(quench);

View file

@ -13,7 +13,6 @@ import { UserFlagDatabase } from "./utils/databases/UserFlag.mjs";
import { barGraphSchema, numberBucketSchema, rowSchema, stringBucketSchema, tableSchema } from "./utils/databases/model.mjs"; import { barGraphSchema, numberBucketSchema, rowSchema, stringBucketSchema, tableSchema } from "./utils/databases/model.mjs";
import { determinePrivacyFromRollMode, filterPrivateRows, PrivacyMode } from "./utils/privacy.mjs"; import { determinePrivacyFromRollMode, filterPrivateRows, PrivacyMode } from "./utils/privacy.mjs";
import { validateBucketConfig, validateValue } from "./utils/buckets.mjs"; import { validateBucketConfig, validateValue } from "./utils/buckets.mjs";
import { inferRollMode } from "./utils/inferRollMode.mjs";
const { deepFreeze } = foundry.utils; const { deepFreeze } = foundry.utils;
@ -26,7 +25,6 @@ export const api = deepFreeze({
}, },
utils: { utils: {
determinePrivacyFromRollMode, determinePrivacyFromRollMode,
inferRollMode,
filterPrivateRows, filterPrivateRows,
validateValue, validateValue,
validateBucketConfig, validateBucketConfig,

View file

@ -1,40 +0,0 @@
import { determinePrivacyFromRollMode } from "../utils/privacy.mjs";
import { inferRollMode } from "../utils/inferRollMode.mjs";
Hooks.on(`createChatMessage`, (message, options, author) => {
const isSelf = author === game.user.id;
const isNew = options.action === `create`;
const hasRolls = message.rolls?.length > 0;
const autoTracking = game.settings.get(__ID__, `autoTrackRolls`);
if (!isSelf || !isNew || !hasRolls || !autoTracking) { return };
/** An object of dice denomination to database rows */
const rows = {};
const privacy = determinePrivacyFromRollMode(options.rollMode ?? inferRollMode(message));
/*
Goes through all of the dice within the message and grabs their result in order
to batch-save them all to the database handler.
*/
for (const roll of message.rolls) {
for (const die of roll.dice) {
const size = die.denomination;
rows[size] ??= [];
for (const result of die.results) {
rows[size].push({ privacy, value: result.result });
};
};
};
// save all the rows, then rerender once we're properly done
for (const denomination in rows) {
CONFIG.stats.db.createRows(
`Dice/${denomination}`,
author,
rows[denomination],
{ rerender: false },
);
};
CONFIG.stats.db.render({ userUpdated: author });
});

View file

@ -0,0 +1,30 @@
import { determinePrivacyFromRollMode } from "../utils/privacy.mjs";
Hooks.on(`preCreateChatMessage`, (_message, context, options, author) => {
const isNew = options.action === `create`;
const hasRolls = context.rolls?.length > 0;
const autoTracking = game.settings.get(__ID__, `autoTrackRolls`);
if (!isNew || !hasRolls || !autoTracking) { return };
/** An object of dice denomination to rows to add */
const rows = {};
const privacy = determinePrivacyFromRollMode(options.rollMode);
for (const roll of context.rolls) {
for (const die of roll.dice) {
const size = die.denomination;
rows[size] ??= [];
for (const result of die.results) {
rows[size].push({ privacy, value: result.result });
};
};
};
for (const denomination in rows) {
CONFIG.stats.db.createRows(
`Dice/${denomination}`,
author,
rows[denomination],
);
};
});

View file

@ -29,10 +29,10 @@ Hooks.on(`ready`, () => {
); );
// Fire and forget // Fire and forget
CONFIG.stats.db.migrateData(lastVersion, notif) CONFIG.stats.db.migrateData(notif)
.then(() => { .then(() => {
game.settings.set(__ID__, `lastVersion`, __VERSION__); game.settings.set(__ID__, `lastVersion`, __VERSION__);
setTimeout(() => ui.notifications.remove(notif), 5_000); setTimeout(() => ui.notifications.remove(notif), 500);
}); });
} else { } else {
ui.notifications.error( ui.notifications.error(

View file

@ -5,7 +5,7 @@ import "./hooks/init.mjs";
import "./hooks/ready.mjs"; import "./hooks/ready.mjs";
// Document Hooks // Document Hooks
import "./hooks/createChatMessage.mjs"; import "./hooks/preCreateChatMessage.mjs";
// Dev Only imports // Dev Only imports
if (import.meta.env.DEV) { if (import.meta.env.DEV) {

View file

@ -6,6 +6,7 @@ const { StringField, NumberField } = foundry.data.fields;
export const BucketTypes = { export const BucketTypes = {
STRING: `string`, STRING: `string`,
NUMBER: `number`, NUMBER: `number`,
RANGE: `range`,
}; };
/** /**

View file

@ -28,7 +28,7 @@ Default Subtables:
tables that are parents to other tables. tables that are parents to other tables.
*/ */
const { deleteProperty, diffObject, expandObject, isNewerVersion, mergeObject } = foundry.utils; const { deleteProperty, diffObject, expandObject, mergeObject } = foundry.utils;
/** /**
* The generic Database implementation, any subclasses should implement all of * The generic Database implementation, any subclasses should implement all of
@ -275,7 +275,7 @@ export class Database {
* @returns {boolean} * @returns {boolean}
*/ */
static requiresMigrationFrom(lastVersion) { static requiresMigrationFrom(lastVersion) {
return isNewerVersion(__VERSION__, lastVersion); return foundry.utils.isNewerVersion(__VERSION__, lastVersion);
}; };
/** /**
@ -289,27 +289,7 @@ export class Database {
* @param {Notification} notif The progress bar notification used for * @param {Notification} notif The progress bar notification used for
* user feedback while performing migrations. * user feedback while performing migrations.
*/ */
static async migrateData(lastVersion, notif) { static async migrateData(lastVersion, notif) {};
const totalSteps = 1;
/*
This migration is for going up to 1.0.3, getting rid of any tables that have
a bucket type of range, since those were not supported within the initial
release, but could still accidentally be created by users.
*/
if (isNewerVersion(`1.0.3`, lastVersion)) {
Logger.log(`Migrating up to the v1.0.3 data structure`);
const tables = game.settings.get(__ID__, `tables`);
for (const table of Object.values(tables)) {
if (table.buckets.type !== `range`) { continue };
table.buckets.type = BucketTypes.NUMBER;
table.buckets.showEmptyBuckets = true;
};
await game.settings.set(__ID__, `tables`, tables);
notif.update({ pct: notif.pct + (1 / totalSteps) });
};
};
}; };
/* eslint-enable no-unused-vars */ /* eslint-enable no-unused-vars */

View file

@ -1,36 +0,0 @@
/**
* A helper function to try and infer what roll mode was used when creating a
* chat message in case the roll mode was not provided during the createChatMessage
* hook for whatever reason.
*
* **Disclaimer**: This inference is not totally correct. Particularly when inferring
* a GM's message, as it won't be able to distinguish between a self-roll and a
* private GM roll when it's
*
* @param {ChatMessage} message The ChatMessage document to infer from
* @returns The Foundry-specified roll mode
*/
export function inferRollMode(message) {
const whisperCount = message.whisper.length;
if (whisperCount === 0) {
return CONST.DICE_ROLL_MODES.PUBLIC;
};
if (whisperCount === 1 && message.whisper[0] === game.user.id) {
return CONST.DICE_ROLL_MODES.SELF;
};
let allGMs = true;
for (const userID of message.whisper) {
const user = game.users.get(userID);
if (!user) { continue };
allGMs &&= user.isGM;
};
if (!allGMs) {
return CONST.DICE_ROLL_MODES.PUBLIC;
};
return message.blind
? CONST.DICE_ROLL_MODES.BLIND
: CONST.DICE_ROLL_MODES.PRIVATE;
};

View file

@ -11,8 +11,6 @@ export const PrivacyMode = Object.freeze({
export function determinePrivacyFromRollMode(rollMode) { export function determinePrivacyFromRollMode(rollMode) {
switch (rollMode) { switch (rollMode) {
case CONST.DICE_ROLL_MODES.PUBLIC:
return PrivacyMode.PUBLIC;
case CONST.DICE_ROLL_MODES.BLIND: case CONST.DICE_ROLL_MODES.BLIND:
return PrivacyMode.GM; return PrivacyMode.GM;
case CONST.DICE_ROLL_MODES.PRIVATE: case CONST.DICE_ROLL_MODES.PRIVATE:
@ -20,7 +18,7 @@ export function determinePrivacyFromRollMode(rollMode) {
case CONST.DICE_ROLL_MODES.SELF: case CONST.DICE_ROLL_MODES.SELF:
return PrivacyMode.SELF; return PrivacyMode.SELF;
} }
return PrivacyMode.SELF; return PrivacyMode.PUBLIC;
}; };
/** /**

55
package-lock.json generated
View file

@ -13,11 +13,10 @@
"devDependencies": { "devDependencies": {
"@foundryvtt/foundryvtt-cli": "^1.1.0", "@foundryvtt/foundryvtt-cli": "^1.1.0",
"@stylistic/eslint-plugin": "^4.2.0", "@stylistic/eslint-plugin": "^4.2.0",
"dotenv": "^17.2.2",
"eslint": "^9.25.0", "eslint": "^9.25.0",
"glob": "^11.0.1", "glob": "^11.0.1",
"terser": "^5.39.0", "terser": "^5.39.0",
"vite": "^6.3.4" "vite": "^6.3.1"
} }
}, },
"node_modules/@esbuild/aix-ppc64": { "node_modules/@esbuild/aix-ppc64": {
@ -1681,19 +1680,6 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/dotenv": {
"version": "17.2.2",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.2.tgz",
"integrity": "sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/dunder-proto": { "node_modules/dunder-proto": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@ -2045,14 +2031,10 @@
} }
}, },
"node_modules/fdir": { "node_modules/fdir": {
"version": "6.5.0", "version": "6.4.3",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==",
"dev": true, "dev": true,
"license": "MIT",
"engines": {
"node": ">=12.0.0"
},
"peerDependencies": { "peerDependencies": {
"picomatch": "^3 || ^4" "picomatch": "^3 || ^4"
}, },
@ -2970,11 +2952,10 @@
"dev": true "dev": true
}, },
"node_modules/picomatch": { "node_modules/picomatch": {
"version": "4.0.3", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"dev": true, "dev": true,
"license": "MIT",
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@ -3393,14 +3374,13 @@
} }
}, },
"node_modules/tinyglobby": { "node_modules/tinyglobby": {
"version": "0.2.15", "version": "0.2.12",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz",
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"fdir": "^6.5.0", "fdir": "^6.4.3",
"picomatch": "^4.0.3" "picomatch": "^4.0.2"
}, },
"engines": { "engines": {
"node": ">=12.0.0" "node": ">=12.0.0"
@ -3482,18 +3462,17 @@
} }
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "6.3.6", "version": "6.3.2",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.2.tgz",
"integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==", "integrity": "sha512-ZSvGOXKGceizRQIZSz7TGJ0pS3QLlVY/9hwxVh17W3re67je1RKYzFHivZ/t0tubU78Vkyb9WnHPENSBCzbckg==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"esbuild": "^0.25.0", "esbuild": "^0.25.0",
"fdir": "^6.4.4", "fdir": "^6.4.3",
"picomatch": "^4.0.2", "picomatch": "^4.0.2",
"postcss": "^8.5.3", "postcss": "^8.5.3",
"rollup": "^4.34.9", "rollup": "^4.34.9",
"tinyglobby": "^0.2.13" "tinyglobby": "^0.2.12"
}, },
"bin": { "bin": {
"vite": "bin/vite.js" "vite": "bin/vite.js"

View file

@ -13,7 +13,6 @@
"devDependencies": { "devDependencies": {
"@foundryvtt/foundryvtt-cli": "^1.1.0", "@foundryvtt/foundryvtt-cli": "^1.1.0",
"@stylistic/eslint-plugin": "^4.2.0", "@stylistic/eslint-plugin": "^4.2.0",
"dotenv": "^17.2.2",
"eslint": "^9.25.0", "eslint": "^9.25.0",
"glob": "^11.0.1", "glob": "^11.0.1",
"terser": "^5.39.0", "terser": "^5.39.0",

View file

@ -250,7 +250,7 @@
"image": {}, "image": {},
"text": { "text": {
"format": 1, "format": 1,
"content": "<p>The module provides a multitude of utility functions through it's API for usage however desired. This will go over them and describe their purpose.</p><p></p><h2>filterPrivateRows</h2><p>This method is intended to take @UUID[Compendium.stat-tracker.docs.JournalEntry.pBOyeBDuTeowuDOE.JournalEntryPage.S7Z6mZ0JablJVQJu]{rows} provided by the database and filter out any that the user would not be able to see normally. This is usually called by the database adapters so there's unlikely to be any reason to use it externally.</p><p>Available under <code>&lt;api&gt;.utils.filterPrivateRows</code>.</p><p></p><h2>inferRollMode</h2><p>This utility is intended to try and determine what roll mode was used to create a chat message. The inference is not entirely accurate because it struggles to differentiate between a GM rolling with a Private GM Roll and a Self Roll when there is only one GM present in the world.</p><p>Available under <code>&lt;api&gt;.utils.inferRollMode</code></p><p></p><h2>validateValue</h2><p>Available under <code>&lt;api&gt;.utils.validateValue</code>.</p><p></p><h2>validateBucketConfig</h2><p>Available under <code>&lt;api&gt;.utils.validateBucketConfig</code>.</p>" "content": "<p>The module provides a multitude of utility functions through it's API for usage however desired. This will go over them and describe their purpose.</p><p></p><h2>filterPrivateRows</h2><p>This method is intended to take @UUID[Compendium.stat-tracker.docs.JournalEntry.pBOyeBDuTeowuDOE.JournalEntryPage.S7Z6mZ0JablJVQJu]{rows} provided by the database and filter out any that the user would not be able to see normally. This is usually called by the database adapters so there's unlikely to be any reason to use it externally.</p><p>Available under <code>&lt;api&gt;.utils.filterPrivateRows</code>.</p><p></p><h2>validateValue</h2><p>Available under <code>&lt;api&gt;.utils.validateValue</code>.</p><p></p><h2>validateBucketConfig</h2><p>Available under <code>&lt;api&gt;.utils.validateBucketConfig</code>.</p>"
}, },
"video": { "video": {
"controls": true, "controls": true,
@ -266,11 +266,11 @@
"compendiumSource": null, "compendiumSource": null,
"duplicateSource": null, "duplicateSource": null,
"exportSource": null, "exportSource": null,
"coreVersion": "13.345", "coreVersion": "13.344",
"systemId": "empty-system", "systemId": "empty-system",
"systemVersion": "0.0.0", "systemVersion": "0.0.0",
"createdTime": 1748330904988, "createdTime": 1748330904988,
"modifiedTime": 1749864406851, "modifiedTime": 1748394635911,
"lastModifiedBy": "t2sWGWEYSMFrfBu3" "lastModifiedBy": "t2sWGWEYSMFrfBu3"
}, },
"_key": "!journal.pages!pBOyeBDuTeowuDOE.TQzWrVTEz4oQhLPD" "_key": "!journal.pages!pBOyeBDuTeowuDOE.TQzWrVTEz4oQhLPD"

View file

@ -1,8 +1,7 @@
{ {
"id": "stat-tracker", "id": "stat-tracker",
"title": "Stats Tracker", "title": "Stats Tracker",
"description": "<p>A user-first approach to stat tracking. Designed from the ground up with the intent of being able to track whatever statistics you want.</p>", "version": "1.0.2",
"version": "1.0.3",
"compatibility": { "compatibility": {
"maximum": 13, "maximum": 13,
"verified": 13, "verified": 13,

View file

@ -1,4 +1,13 @@
<div data-bucket-type="number"> <div
class="{{#if buckets.locked}}alert-box locked{{/if}}"
data-bucket-type="number"
>
{{#if buckets.locked}}
<p class="center">
This bucket configuration has been locked, preventing editing
of the settings.
</p>
{{/if}}
<div class="input-group"> <div class="input-group">
<label for="{{meta.idp}}-min"> <label for="{{meta.idp}}-min">
Minimum Minimum

View file

@ -0,0 +1,42 @@
<div class="input-group">
<label for="{{meta.idp}}-min">
Minimum
</label>
<input
id="{{meta.idp}}-min"
type="number"
name="buckets.min"
value="{{ buckets.min }}"
required
{{disabled buckets.locked}}
>
</div>
<div class="input-group">
<label for="{{meta.idp}}-max">
Maximum
</label>
<input
id="{{meta.idp}}-max"
type="number"
name="buckets.max"
value="{{ buckets.max }}"
required
{{disabled buckets.locked}}
>
</div>
<div class="input-group">
<label for="{{meta.idp}}-step">
Step
</label>
<input
id="{{meta.idp}}-step"
type="number"
name="buckets.step"
value="{{ buckets.step }}"
required
{{disabled buckets.locked}}
>
<p class="hint">
The size of the step between values within the range.
</p>
</div>

View file

@ -1,4 +1,7 @@
<div data-bucket-type="string"> <div
class="{{#if buckets.locked}}alert-box locked{{/if}}"
data-bucket-type="string"
>
<div class="input-group"> <div class="input-group">
<label for=""> <label for="">
Valid Options Valid Options

View file

@ -1,47 +0,0 @@
import { existsSync } from "fs";
import { symlink, unlink } from "fs/promises";
import { join } from "path";
import { config } from "dotenv";
config({ quiet: true });
const root = process.env.FOUNDRY_ROOT;
// Early exit
if (!root) {
console.error(`Must provide a FOUNDRY_ROOT environment variable`);
process.exit(1);
};
// Assert Foundry exists
if (!existsSync(root)) {
console.error(`Foundry root not found.`);
process.exit(1);
};
// Removing existing symlink
if (existsSync(`foundry`)) {
console.log(`Attempting to unlink foundry instance`);
try {
await unlink(`foundry`);
} catch {
console.error(`Failed to unlink foundry folder.`);
process.exit(1);
};
};
// Account for if the root is pointing at an Electron install
let targetRoot = root;
if (existsSync(join(root, `resources`, `app`))) {
console.log(`Switching to use the "${root}/resources/app" directory`);
targetRoot = join(root, `resources`, `app`);
};
// Create symlink
console.log(`Linking foundry source into folder`)
try {
await symlink(targetRoot, `foundry`);
} catch (e) {
console.error(e);
process.exit(1);
};