Update the privacy handling for the rows to be more dynamic and support more than just private or public rolls

This commit is contained in:
Oliver-Akins 2025-05-18 20:52:32 -06:00
parent f66510c811
commit 6dbc0a817f
9 changed files with 103 additions and 57 deletions

View file

@ -2,6 +2,7 @@ import { Chart } from "chart.js";
import { diceSizeSorter } from "../utils/sorters/diceSize.mjs"; import { diceSizeSorter } from "../utils/sorters/diceSize.mjs";
import { filePath } from "../consts.mjs"; import { filePath } from "../consts.mjs";
import { Logger } from "../utils/Logger.mjs"; import { Logger } from "../utils/Logger.mjs";
import { PrivacyMode } from "../utils/privacy.mjs";
import { smallToLarge } from "../utils/sorters/smallToLarge.mjs"; import { smallToLarge } from "../utils/sorters/smallToLarge.mjs";
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
@ -48,12 +49,30 @@ export class StatsViewer extends HandlebarsApplicationMixin(ApplicationV2) {
}; };
// #endregion // #endregion
constructor({ users, ...opts } = {}) { // #region Instance Data
constructor({ users, privacy, ...opts } = {}) {
super(opts); super(opts);
if (users != null) { if (users != null) {
this._selectedUsers = users; this._selectedUsers = users;
}; };
if (privacy != null && Array.isArray(privacy)) {
this._privacySetting = privacy;
};
};
_selectedUsers = [game.user.id];
_graphData = null;
_privacySetting = [PrivacyMode.PUBLIC, PrivacyMode.PRIVATE];
#_selectedTable = ``;
_selectedSubtable = ``;
get _selectedTable() {
return this.#_selectedTable;
};
set _selectedTable(val) {
this.#_selectedTable = val;
this._selectedSubtable = ``;
}; };
get activeTableID() { get activeTableID() {
@ -62,7 +81,9 @@ export class StatsViewer extends HandlebarsApplicationMixin(ApplicationV2) {
} }
return this._selectedTable; return this._selectedTable;
}; };
// #endregion Instance Data
// #region Lifecycle
async render({ userUpdated, ...opts } = {}) { async render({ userUpdated, ...opts } = {}) {
if (userUpdated && !this._selectedUsers.includes(userUpdated)) { if (userUpdated && !this._selectedUsers.includes(userUpdated)) {
return; return;
@ -84,14 +105,19 @@ export class StatsViewer extends HandlebarsApplicationMixin(ApplicationV2) {
input.addEventListener(`change`, this.#bindListener.bind(this)); input.addEventListener(`change`, this.#bindListener.bind(this));
}; };
if (options.parts.includes(`graph`)) { if (options.parts.includes(`graph`) && this._graphData) {
const canvas = this.element.querySelector(`canvas`); const canvas = this.element.querySelector(`canvas`);
new Chart( canvas, this._graphData ); new Chart( canvas, this._graphData );
}; };
}; };
// #endregion Lifecycle
// #region Data Prep
async _preparePartContext(partId) { async _preparePartContext(partId) {
const ctx = {}; const ctx = {
table: this._selectedTable,
subtable: this._selectedSubtable,
};
ctx.meta = { ctx.meta = {
idp: this.id, idp: this.id,
}; };
@ -117,16 +143,6 @@ export class StatsViewer extends HandlebarsApplicationMixin(ApplicationV2) {
return ctx; return ctx;
}; };
#_selectedTable = ``;
_selectedSubtable = ``;
get _selectedTable() {
return this.#_selectedTable;
};
set _selectedTable(val) {
this.#_selectedTable = val;
this._selectedSubtable = ``;
};
async #prepareTableSelectContext(ctx) { async #prepareTableSelectContext(ctx) {
const tables = new Set(); const tables = new Set();
const subtables = {}; const subtables = {};
@ -141,8 +157,6 @@ export class StatsViewer extends HandlebarsApplicationMixin(ApplicationV2) {
}; };
const tableList = Array.from(tables); const tableList = Array.from(tables);
this._selectedTable ??= tableList[0];
ctx.table = this._selectedTable; ctx.table = this._selectedTable;
ctx.tables = tableList; ctx.tables = tableList;
@ -153,18 +167,12 @@ export class StatsViewer extends HandlebarsApplicationMixin(ApplicationV2) {
subtableList?.sort(diceSizeSorter); subtableList?.sort(diceSizeSorter);
} else { } else {
subtableList?.sort(smallToLarge); subtableList?.sort(smallToLarge);
}
if (!subtableList) {
this._selectedSubtable = undefined;
} else if (!subtableList.includes(this._selectedSubtable)) {
this._selectedSubtable = subtableList?.[0];
}; };
ctx.subtable = this._selectedSubtable; ctx.subtable = this._selectedSubtable;
ctx.subtables = subtableList; ctx.subtables = subtableList;
}; };
_selectedUsers = [game.user.id];
async #prepareDataFiltersContext(ctx) { async #prepareDataFiltersContext(ctx) {
ctx.users = []; ctx.users = [];
ctx.selectedUsers = this._selectedUsers; ctx.selectedUsers = this._selectedUsers;
@ -177,26 +185,36 @@ export class StatsViewer extends HandlebarsApplicationMixin(ApplicationV2) {
ctx.privacySetting = this._privacySetting; ctx.privacySetting = this._privacySetting;
ctx.privacyOptions = [ ctx.privacyOptions = [
{ label: `Only Public Data`, value: `none` }, { label: `Self`, value: PrivacyMode.SELF },
{ label: `Only Your Private Data`, value: `my` }, { label: `Private`, value: PrivacyMode.PRIVATE },
{ label: `Public`, value: PrivacyMode.PUBLIC },
]; ];
if (game.user.isGM) { if (game.user.isGM) {
ctx.privacyOptions.push( ctx.privacyOptions.push(
{ label: `All Private Data`, value: `all` }, { label: `Blind`, value: PrivacyMode.GM },
); );
}; };
}; };
_graphData = {}; async #prepareGraphContext(ctx) {
_privacySetting = `my`;
async #prepareGraphContext(_ctx) {
const table = await CONFIG.stats.db.getTable(this.activeTableID); const table = await CONFIG.stats.db.getTable(this.activeTableID);
if (!table) {
this._graphData = null;
ctx.showGraph = false;
ctx.classes = `alert-box warning`;
return;
};
ctx.classes = ``;
ctx.showGraph = true;
const userData = await CONFIG.stats.db.getRows( const userData = await CONFIG.stats.db.getRows(
this.activeTableID, this.activeTableID,
this._selectedUsers, this._selectedUsers,
this._privacySetting, this._privacySetting,
); );
Logger.log(userData);
const data = {}; const data = {};
const allBuckets = new Set(); const allBuckets = new Set();
@ -265,7 +283,9 @@ export class StatsViewer extends HandlebarsApplicationMixin(ApplicationV2) {
}; };
console.log(`graphData`, this._graphData); console.log(`graphData`, this._graphData);
}; };
// #endregion Data Prep
// #region Actions
/** /**
* @param {Event} event * @param {Event} event
*/ */
@ -283,4 +303,5 @@ export class StatsViewer extends HandlebarsApplicationMixin(ApplicationV2) {
/** @this {StatsViewer} */ /** @this {StatsViewer} */
static async #addAllUsers() {}; static async #addAllUsers() {};
// #endregion Actions
}; };

View file

@ -5,8 +5,8 @@ import { TableManager } from "./Apps/TableManager.mjs";
import { TestApp } from "./Apps/TestApp.mjs"; import { TestApp } from "./Apps/TestApp.mjs";
// Utils // Utils
import { filterPrivateRows, PrivacyMode } from "./utils/privacy.mjs";
import { validateBucketConfig, validateValue } from "./utils/buckets.mjs"; import { validateBucketConfig, validateValue } from "./utils/buckets.mjs";
import { filterPrivateRows } from "./utils/privacy.mjs";
const { deepFreeze } = foundry.utils; const { deepFreeze } = foundry.utils;
@ -26,6 +26,9 @@ Object.defineProperty(
validateValue, validateValue,
validateBucketConfig, validateBucketConfig,
}, },
enums: {
PrivacyMode,
},
}), }),
writable: false, writable: false,
}, },

View file

@ -9,13 +9,13 @@ Hooks.on(`preCreateChatMessage`, (_message, context, options, author) => {
/** An object of dice denomination to rows to add */ /** An object of dice denomination to rows to add */
const rows = {}; const rows = {};
const mode = determinePrivacyFromRollMode(options.rollMode); const privacy = determinePrivacyFromRollMode(options.rollMode);
for (const roll of context.rolls) { for (const roll of context.rolls) {
for (const die of roll.dice) { for (const die of roll.dice) {
const size = die.denomination; const size = die.denomination;
rows[size] ??= []; rows[size] ??= [];
for (const result of die.results) { for (const result of die.results) {
rows[size].push({ mode, value: result.result }); rows[size].push({ privacy, value: result.result });
}; };
}; };
}; };

View file

@ -1,6 +1,4 @@
/* eslint-disable no-unused-vars */ /* eslint-disable no-unused-vars */
import { Table } from "./model.mjs";
const tablesFlag = `tables`; const tablesFlag = `tables`;
export class UserFlagDatabase { export class UserFlagDatabase {
@ -22,8 +20,6 @@ export class UserFlagDatabase {
datasets[user.id] = null; datasets[user.id] = null;
continue; continue;
}; };
const table = new Table(tables[tableId]);
} }
return datasets; return datasets;
}; };

View file

@ -1,7 +1,11 @@
export const PrivacyMode = Object.freeze({ export const PrivacyMode = Object.freeze({
/** Only the GM is able to the see the result of the row */
GM: `gm`, GM: `gm`,
/** Both the GM and the logged in user are able to see the row */
PRIVATE: `private`, PRIVATE: `private`,
/** Only the logged in user is able to see the row */
SELF: `self`, SELF: `self`,
/** Everyone is able to see the row */
PUBLIC: `public`, PUBLIC: `public`,
}); });
@ -23,22 +27,37 @@ export function determinePrivacyFromRollMode(rollMode) {
* *
* @param {Array<any>} rows The rows to filter * @param {Array<any>} rows The rows to filter
* @param {string} userID The user's ID who the rows belong to * @param {string} userID The user's ID who the rows belong to
* @param {"all"|"me"|"none"} privacy The privacy level we're filtering for * @param {(PrivacyMode[keyof PrivacyMode])[]} privacies The privacy level we're filtering for
* @returns The filtered rows * @returns The filtered rows
*/ */
export function filterPrivateRows(rows, userID, privacy) { export function filterPrivateRows(rows, userID, privacies) {
console.log(rows, userID, privacy); console.log({rows, userID, privacies});
const filtered = []; const filtered = [];
const isMe = userID === game.user.id; const isMe = userID === game.user.id;
// TODO: make this use a permission rather than just isGM const isGM = game.user.isGM;
const canSeeAll = game.user.isGM;
for (const row of rows) { for (const row of rows) {
let allowed = privacies.includes(row.privacy);
let allowed = !row.isPrivate; /*
allowed ||= privacy === `all` && canSeeAll; Assert that the user is actually allowed to see the privacy level, even if
allowed ||= privacy === `my` && isMe; they provide through the param that they want that privacy level.
*/
switch (row.privacy) {
case PrivacyMode.SELF: {
allowed &&= isMe;
break;
};
case PrivacyMode.GM: {
allowed &&= isGM;
break;
};
case PrivacyMode.PRIVATE: {
allowed &&= (isMe || isGM);
break;
};
};
if (allowed) { if (allowed) {
filtered.push(row); filtered.push(row);

View file

@ -3,7 +3,8 @@
"common": { "common": {
"Table": "Table", "Table": "Table",
"Subtable": "Subtable", "Subtable": "Subtable",
"Users": "Users" "Users": "Users",
"DataVisibility": "Data Visibility"
} }
} }
} }

View file

@ -1,4 +1,7 @@
stats-tracker-multi-select { stats-tracker-multi-select {
/*
TODO: Improve styling when this isn't the tallest element in a row
*/
display: grid; display: grid;
grid-template-columns: auto minmax(0, 1fr); grid-template-columns: auto minmax(0, 1fr);
gap: 2px; gap: 2px;

View file

@ -1,15 +1,10 @@
<div> <div>
<div class="control-group"> <stats-tracker-multi-select
<label for="{{meta.idp}}-private-data"> data-bind="_privacySetting"
Private Data Visibility placeholder="STAT_TRACKER.common.DataVisibility"
</label> >
<select {{ st-options privacySetting privacyOptions }}
id="{{meta.idp}}-private-data" </stats-tracker-multi-select>
data-bind="_privacySetting"
>
{{ st-options privacySetting privacyOptions }}
</select>
</div>
<stats-tracker-multi-select <stats-tracker-multi-select
data-bind="_selectedUsers" data-bind="_selectedUsers"
placeholder="STAT_TRACKER.common.Users" placeholder="STAT_TRACKER.common.Users"

View file

@ -1,3 +1,11 @@
<div> <div class="{{classes}} center">
<canvas></canvas> {{#if showGraph}}
<canvas></canvas>
{{else}}
{{#if table}}
<span class="large">Select a Subtable</span>
{{else}}
<span class="large">Select a Table</span>
{{/if}}
{{/if}}
</div> </div>