diff --git a/.env.template b/.env.template new file mode 100644 index 0000000..180dbd6 --- /dev/null +++ b/.env.template @@ -0,0 +1,2 @@ +# The absolute path to the Foundry installation to create symlinks to +FOUNDRY_ROOT="" diff --git a/.github/workflows/draft-release.yaml b/.github/workflows/draft-release.yaml index de3b830..d38d6ab 100644 --- a/.github/workflows/draft-release.yaml +++ b/.github/workflows/draft-release.yaml @@ -50,5 +50,13 @@ jobs: tag: "v${{ steps.version.outputs.version }}" commit: ${{ github.ref }} draft: true + body: > + | + | + | ### 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 artifacts: "prod.dist/release.zip,prod.dist/module.json" + artifactsErrorsFailBuild: true diff --git a/.gitignore b/.gitignore index 8878028..ff8974d 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,8 @@ lerna-debug.log* node_modules /*.dist *.local +.env +/foundry # Editor directories and files .vscode/* diff --git a/.promo/imgs/example-dice-stat.png b/.promo/imgs/example-dice-stat.png new file mode 100644 index 0000000..63cb502 Binary files /dev/null and b/.promo/imgs/example-dice-stat.png differ diff --git a/.promo/imgs/example-string-stat.png b/.promo/imgs/example-string-stat.png new file mode 100644 index 0000000..fa884dc Binary files /dev/null and b/.promo/imgs/example-string-stat.png differ diff --git a/.promo/imgs/example-table-config.png b/.promo/imgs/example-table-config.png new file mode 100644 index 0000000..a86cdf5 Binary files /dev/null and b/.promo/imgs/example-table-config.png differ diff --git a/README.md b/README.md index ccf8f72..61e924e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,30 @@ -# Foundry-Stat-Tracker -A Foundry module that allows tracking arbitrary stats. +# Stats Tracker +This FoundryVTT module aims to provide a clean way of keeping track of any data +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 +
+ + +
+ +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. diff --git a/augments.d.ts b/augments.d.ts new file mode 100644 index 0000000..a08bb60 --- /dev/null +++ b/augments.d.ts @@ -0,0 +1,14 @@ +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; +}; diff --git a/jsconfig.json b/jsconfig.json index 8b97154..27f6e7c 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -1,10 +1,22 @@ { "compilerOptions": { "module": "ES2020", - "target": "ES2020" + "target": "ES2020", + "types": [ + "./augments.d.ts" + ], + "paths": { + "@client/*": ["./foundry/client/*"], + "@common/*": ["./foundry/common/*"], + } }, "exclude": ["node_modules", "**/node_modules/*"], - "include": ["module/**/*"], + "include": [ + "module/**/*", + "foundry/client/client.mjs", + "foundry/client/global.d.mts", + "foundry/common/primitives/global.d.mts" + ], "typeAcquisition": { "include": ["joi"] } diff --git a/module/Apps/TableCreator.mjs b/module/Apps/TableCreator.mjs index 9619af2..1422fbf 100644 --- a/module/Apps/TableCreator.mjs +++ b/module/Apps/TableCreator.mjs @@ -75,7 +75,7 @@ export class TableCreator extends HandlebarsApplicationMixin(ApplicationV2) { if (this._name.startsWith(`Dice`)) { ctx.createButtonDisabled = !this._name.match(diceNamespacePattern); ctx.typeDisabled = true; - ctx.type = BucketTypes.RANGE; + ctx.type = BucketTypes.NUMBER; 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.`, { permanent: true }, diff --git a/module/__tests__/registration.mjs b/module/__tests__/registration.mjs index 83e025c..0d45207 100644 --- a/module/__tests__/registration.mjs +++ b/module/__tests__/registration.mjs @@ -1,8 +1,8 @@ -import { barGraphTests } from "./schemas/barGraph.mjs"; -import { numberBucketTests } from "./schemas/numberBucket.mjs"; -import { rowTests } from "./schemas/row.mjs"; -import { stringBucketTests } from "./schemas/stringBucket.mjs"; -import { tableTests } from "./schemas/table.mjs"; +import { barGraphTests } from "./schemas/barGraph.test.mjs"; +import { numberBucketTests } from "./schemas/numberBucket.test.mjs"; +import { rowTests } from "./schemas/row.test.mjs"; +import { stringBucketTests } from "./schemas/stringBucket.test.mjs"; +import { tableTests } from "./schemas/table.test.mjs"; Hooks.on(`quenchReady`, (quench) => { numberBucketTests(quench); diff --git a/module/__tests__/schemas/barGraph.mjs b/module/__tests__/schemas/barGraph.test.mjs similarity index 100% rename from module/__tests__/schemas/barGraph.mjs rename to module/__tests__/schemas/barGraph.test.mjs diff --git a/module/__tests__/schemas/numberBucket.mjs b/module/__tests__/schemas/numberBucket.test.mjs similarity index 100% rename from module/__tests__/schemas/numberBucket.mjs rename to module/__tests__/schemas/numberBucket.test.mjs diff --git a/module/__tests__/schemas/row.mjs b/module/__tests__/schemas/row.test.mjs similarity index 100% rename from module/__tests__/schemas/row.mjs rename to module/__tests__/schemas/row.test.mjs diff --git a/module/__tests__/schemas/stringBucket.mjs b/module/__tests__/schemas/stringBucket.test.mjs similarity index 100% rename from module/__tests__/schemas/stringBucket.mjs rename to module/__tests__/schemas/stringBucket.test.mjs diff --git a/module/__tests__/schemas/table.mjs b/module/__tests__/schemas/table.test.mjs similarity index 100% rename from module/__tests__/schemas/table.mjs rename to module/__tests__/schemas/table.test.mjs diff --git a/module/api.mjs b/module/api.mjs index 539e15a..274f255 100644 --- a/module/api.mjs +++ b/module/api.mjs @@ -11,8 +11,9 @@ import { UserFlagDatabase } from "./utils/databases/UserFlag.mjs"; // Utils import { barGraphSchema, numberBucketSchema, rowSchema, stringBucketSchema, tableSchema } from "./utils/databases/model.mjs"; -import { filterPrivateRows, PrivacyMode } from "./utils/privacy.mjs"; +import { determinePrivacyFromRollMode, filterPrivateRows, PrivacyMode } from "./utils/privacy.mjs"; import { validateBucketConfig, validateValue } from "./utils/buckets.mjs"; +import { inferRollMode } from "./utils/inferRollMode.mjs"; const { deepFreeze } = foundry.utils; @@ -24,6 +25,8 @@ export const api = deepFreeze({ TableManager, }, utils: { + determinePrivacyFromRollMode, + inferRollMode, filterPrivateRows, validateValue, validateBucketConfig, diff --git a/module/hooks/createChatMessage.mjs b/module/hooks/createChatMessage.mjs new file mode 100644 index 0000000..e686c70 --- /dev/null +++ b/module/hooks/createChatMessage.mjs @@ -0,0 +1,40 @@ +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 }); +}); diff --git a/module/hooks/preCreateChatMessage.mjs b/module/hooks/preCreateChatMessage.mjs deleted file mode 100644 index 05a4d41..0000000 --- a/module/hooks/preCreateChatMessage.mjs +++ /dev/null @@ -1,30 +0,0 @@ -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], - ); - }; -}); diff --git a/module/hooks/ready.mjs b/module/hooks/ready.mjs index 33fe483..025ed1b 100644 --- a/module/hooks/ready.mjs +++ b/module/hooks/ready.mjs @@ -29,10 +29,10 @@ Hooks.on(`ready`, () => { ); // Fire and forget - CONFIG.stats.db.migrateData(notif) + CONFIG.stats.db.migrateData(lastVersion, notif) .then(() => { game.settings.set(__ID__, `lastVersion`, __VERSION__); - setTimeout(() => ui.notifications.remove(notif), 500); + setTimeout(() => ui.notifications.remove(notif), 5_000); }); } else { ui.notifications.error( diff --git a/module/main.mjs b/module/main.mjs index 12d5422..4a90d3f 100644 --- a/module/main.mjs +++ b/module/main.mjs @@ -5,7 +5,7 @@ import "./hooks/init.mjs"; import "./hooks/ready.mjs"; // Document Hooks -import "./hooks/preCreateChatMessage.mjs"; +import "./hooks/createChatMessage.mjs"; // Dev Only imports if (import.meta.env.DEV) { diff --git a/module/utils/buckets.mjs b/module/utils/buckets.mjs index 7082df1..53c78f6 100644 --- a/module/utils/buckets.mjs +++ b/module/utils/buckets.mjs @@ -6,7 +6,6 @@ const { StringField, NumberField } = foundry.data.fields; export const BucketTypes = { STRING: `string`, NUMBER: `number`, - RANGE: `range`, }; /** diff --git a/module/utils/databases/Database.mjs b/module/utils/databases/Database.mjs index acca180..9cd4be1 100644 --- a/module/utils/databases/Database.mjs +++ b/module/utils/databases/Database.mjs @@ -28,7 +28,7 @@ Default Subtables: tables that are parents to other tables. */ -const { deleteProperty, diffObject, expandObject, mergeObject } = foundry.utils; +const { deleteProperty, diffObject, expandObject, isNewerVersion, mergeObject } = foundry.utils; /** * The generic Database implementation, any subclasses should implement all of @@ -275,7 +275,7 @@ export class Database { * @returns {boolean} */ static requiresMigrationFrom(lastVersion) { - return foundry.utils.isNewerVersion(__VERSION__, lastVersion); + return isNewerVersion(__VERSION__, lastVersion); }; /** @@ -289,7 +289,27 @@ export class Database { * @param {Notification} notif The progress bar notification used for * 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 */ diff --git a/module/utils/databases/UserFlag.mjs b/module/utils/databases/UserFlag.mjs index 41ed866..c5292cd 100644 --- a/module/utils/databases/UserFlag.mjs +++ b/module/utils/databases/UserFlag.mjs @@ -2,6 +2,7 @@ import { filterPrivateRows, PrivacyMode } from "../privacy.mjs"; import { Database } from "./Database.mjs"; import { Logger } from "../Logger.mjs"; import { rowSchema } from "./model.mjs"; +import { validateValue } from "../buckets.mjs"; const { hasProperty, mergeObject, randomID } = foundry.utils; @@ -46,6 +47,9 @@ export class UserFlagDatabase extends Database { const userData = user.getFlag(__ID__, dataFlag) ?? {}; userData[tableID] ??= []; + let valueErrorPosted = false; + let validationErrorPosted = false; + for (const row of rows) { row._id = randomID(); row.timestamp = new Date().toISOString(); @@ -55,12 +59,31 @@ export class UserFlagDatabase extends Database { { abortEarly: false, convert: true, dateFormat: `iso`, render: false }, ); if (error) { - ui.notifications.error(`A row being created did not conform to required schema, see console for more information.`, { console: false }); + if (!validationErrorPosted) { + ui.notifications.error( + `One or more rows being created did not conform to required schema, skipping row and see console for more information.`, + { console: false }, + ); + validationErrorPosted = true; + }; Logger.error(`Failing row:`, row); Logger.error(error); continue; }; + const validValue = validateValue(corrected.value, table.buckets); + if (!validValue) { + if (!valueErrorPosted) { + ui.notifications.warn( + `One or more rows being created did not contain a valid value, skipping row and see console for more information.`, + { console: false }, + ); + valueErrorPosted = true; + }; + Logger.warn(`Row with invalid value:`, row); + continue; + }; + userData[tableID].push(corrected); }; @@ -100,22 +123,36 @@ export class UserFlagDatabase extends Database { const table = await this.getTable(tableID); if (!table) { Logger.error(`Cannot find the table with ID "${tableID}"`); - return; + return false; }; const user = game.users.get(userID); if (!user) { Logger.error(`Can't find the user with ID "${tableID}"`); - return; + return false; }; const userData = user.getFlag(__ID__, dataFlag) ?? {}; let row = userData[tableID]?.find(row => row._id === rowID); - if (!row) { return }; + if (!row) { return false }; + + if (hasProperty(changes, `value`)) { + const validValue = validateValue(changes.value, table.buckets); + if (!validValue) { + ui.notifications.warn( + `One or more rows being created did not contain a valid value, skipping row and see console for more information.`, + { console: false }, + ); + Logger.warn(`Row with invalid value:`, row); + return false; + }; + }; + mergeObject(row, changes); await user.setFlag(__ID__, dataFlag, userData); this.render({ userUpdated: userID }); this.triggerListeners(); + return true; }; static async deleteRow(tableID, userID, rowID) { diff --git a/module/utils/inferRollMode.mjs b/module/utils/inferRollMode.mjs new file mode 100644 index 0000000..4880c21 --- /dev/null +++ b/module/utils/inferRollMode.mjs @@ -0,0 +1,36 @@ +/** + * 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; +}; diff --git a/module/utils/privacy.mjs b/module/utils/privacy.mjs index f04a885..b437880 100644 --- a/module/utils/privacy.mjs +++ b/module/utils/privacy.mjs @@ -11,6 +11,8 @@ export const PrivacyMode = Object.freeze({ export function determinePrivacyFromRollMode(rollMode) { switch (rollMode) { + case CONST.DICE_ROLL_MODES.PUBLIC: + return PrivacyMode.PUBLIC; case CONST.DICE_ROLL_MODES.BLIND: return PrivacyMode.GM; case CONST.DICE_ROLL_MODES.PRIVATE: @@ -18,7 +20,7 @@ export function determinePrivacyFromRollMode(rollMode) { case CONST.DICE_ROLL_MODES.SELF: return PrivacyMode.SELF; } - return PrivacyMode.PUBLIC; + return PrivacyMode.SELF; }; /** diff --git a/package-lock.json b/package-lock.json index 7664dfa..6594a26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,10 +13,11 @@ "devDependencies": { "@foundryvtt/foundryvtt-cli": "^1.1.0", "@stylistic/eslint-plugin": "^4.2.0", + "dotenv": "^17.2.2", "eslint": "^9.25.0", "glob": "^11.0.1", "terser": "^5.39.0", - "vite": "^6.3.1" + "vite": "^6.3.4" } }, "node_modules/@esbuild/aix-ppc64": { @@ -1680,6 +1681,19 @@ "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": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -2031,10 +2045,14 @@ } }, "node_modules/fdir": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", - "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, "peerDependencies": { "picomatch": "^3 || ^4" }, @@ -2952,10 +2970,11 @@ "dev": true }, "node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -3374,13 +3393,14 @@ } }, "node_modules/tinyglobby": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz", - "integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==", + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, + "license": "MIT", "dependencies": { - "fdir": "^6.4.3", - "picomatch": "^4.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { "node": ">=12.0.0" @@ -3462,17 +3482,18 @@ } }, "node_modules/vite": { - "version": "6.3.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.2.tgz", - "integrity": "sha512-ZSvGOXKGceizRQIZSz7TGJ0pS3QLlVY/9hwxVh17W3re67je1RKYzFHivZ/t0tubU78Vkyb9WnHPENSBCzbckg==", + "version": "6.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz", + "integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==", "dev": true, + "license": "MIT", "dependencies": { "esbuild": "^0.25.0", - "fdir": "^6.4.3", + "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", - "tinyglobby": "^0.2.12" + "tinyglobby": "^0.2.13" }, "bin": { "vite": "bin/vite.js" diff --git a/package.json b/package.json index a88df2c..ad4b381 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "devDependencies": { "@foundryvtt/foundryvtt-cli": "^1.1.0", "@stylistic/eslint-plugin": "^4.2.0", + "dotenv": "^17.2.2", "eslint": "^9.25.0", "glob": "^11.0.1", "terser": "^5.39.0", diff --git a/packs/docs/_source/English_pBOyeBDuTeowuDOE.json b/packs/docs/_source/English_pBOyeBDuTeowuDOE.json index 91393f7..f6e18d1 100644 --- a/packs/docs/_source/English_pBOyeBDuTeowuDOE.json +++ b/packs/docs/_source/English_pBOyeBDuTeowuDOE.json @@ -250,7 +250,7 @@ "image": {}, "text": { "format": 1, - "content": "

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.

filterPrivateRows

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.

Available under <api>.utils.filterPrivateRows.

validateValue

Available under <api>.utils.validateValue.

validateBucketConfig

Available under <api>.utils.validateBucketConfig.

" + "content": "

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.

filterPrivateRows

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.

Available under <api>.utils.filterPrivateRows.

inferRollMode

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.

Available under <api>.utils.inferRollMode

validateValue

Available under <api>.utils.validateValue.

validateBucketConfig

Available under <api>.utils.validateBucketConfig.

" }, "video": { "controls": true, @@ -266,11 +266,11 @@ "compendiumSource": null, "duplicateSource": null, "exportSource": null, - "coreVersion": "13.344", + "coreVersion": "13.345", "systemId": "empty-system", "systemVersion": "0.0.0", "createdTime": 1748330904988, - "modifiedTime": 1748394635911, + "modifiedTime": 1749864406851, "lastModifiedBy": "t2sWGWEYSMFrfBu3" }, "_key": "!journal.pages!pBOyeBDuTeowuDOE.TQzWrVTEz4oQhLPD" diff --git a/public/module.json b/public/module.json index be69283..431b68d 100644 --- a/public/module.json +++ b/public/module.json @@ -1,7 +1,8 @@ { "id": "stat-tracker", "title": "Stats Tracker", - "version": "1.0.1", + "description": "

A user-first approach to stat tracking. Designed from the ground up with the intent of being able to track whatever statistics you want.

", + "version": "1.0.3", "compatibility": { "maximum": 13, "verified": 13, diff --git a/public/templates/Apps/TableManager/buckets/number.hbs b/public/templates/Apps/TableManager/buckets/number.hbs index 91ce2ec..cebbfaa 100644 --- a/public/templates/Apps/TableManager/buckets/number.hbs +++ b/public/templates/Apps/TableManager/buckets/number.hbs @@ -1,13 +1,4 @@ -
- {{#if buckets.locked}} -

- This bucket configuration has been locked, preventing editing - of the settings. -

- {{/if}} +