Add some utils and update the memory database to "store" rows
This commit is contained in:
parent
9694d72dd0
commit
6b35c10106
4 changed files with 220 additions and 60 deletions
|
|
@ -1,6 +1,11 @@
|
|||
// Apps
|
||||
import { StatsViewer } from "./Apps/StatsViewer.mjs";
|
||||
import { TestApp } from "./Apps/TestApp.mjs";
|
||||
|
||||
// Utils
|
||||
import { filterPrivateRows } from "./utils/filterPrivateRows.mjs";
|
||||
import { validateValue } from "./utils/validateValue.mjs";
|
||||
|
||||
const { deepFreeze } = foundry.utils;
|
||||
|
||||
Object.defineProperty(
|
||||
|
|
@ -12,6 +17,10 @@ Object.defineProperty(
|
|||
TestApp,
|
||||
StatsViewer,
|
||||
},
|
||||
utils: {
|
||||
filterPrivateRows,
|
||||
validateValue,
|
||||
},
|
||||
}),
|
||||
writable: false,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,82 +1,150 @@
|
|||
/* eslint-disable no-unused-vars */
|
||||
import { Table } from "./model.mjs";
|
||||
import { filterPrivateRows } from "../filterPrivateRows.mjs";
|
||||
|
||||
const { randomID } = foundry.utils;
|
||||
|
||||
function generateRow(value, isPrivate = false) {
|
||||
return {
|
||||
id: randomID(),
|
||||
timestamp: Date.now(),
|
||||
value,
|
||||
isPrivate,
|
||||
};
|
||||
};
|
||||
|
||||
function getNormalDistributionHeight(x, a, b) {
|
||||
const maxHeight = b;
|
||||
return Math.round(Math.exp(-( ((x - a) ** 2) / b )) * maxHeight);
|
||||
};
|
||||
const { randomID, mergeObject } = foundry.utils;
|
||||
|
||||
export class MemoryDatabase {
|
||||
static getTables() {
|
||||
/** @type {Array<{ name: string; }>} */
|
||||
return [
|
||||
{ name: `Dice/d4` },
|
||||
{ name: `Dice/d6` },
|
||||
{ name: `Dice/d8` },
|
||||
{ name: `Dice/d10` },
|
||||
{ name: `Dice/d12` },
|
||||
{ name: `Dice/d20` },
|
||||
{ name: `Dice/d100` },
|
||||
{ name: `Count of Successes` },
|
||||
];
|
||||
static #tables = {
|
||||
"Dice/d10": {
|
||||
name: `Dice/d10`,
|
||||
graphType: `bar`,
|
||||
buckets: {
|
||||
type: `range`,
|
||||
min: 1,
|
||||
max: 10,
|
||||
step: 1,
|
||||
},
|
||||
config: {
|
||||
stacked: true,
|
||||
},
|
||||
},
|
||||
"Dice/d20": {
|
||||
name: `Dice/d20`,
|
||||
graphType: `bar`,
|
||||
buckets: {
|
||||
type: `range`,
|
||||
min: 1,
|
||||
max: 20,
|
||||
step: 1,
|
||||
},
|
||||
config: {
|
||||
stacked: true,
|
||||
},
|
||||
},
|
||||
"Dice/d100": {
|
||||
name: `Dice/d100`,
|
||||
graphType: `bar`,
|
||||
buckets: {
|
||||
type: `range`,
|
||||
min: 1,
|
||||
max: 100,
|
||||
step: 1,
|
||||
},
|
||||
config: {
|
||||
stacked: true,
|
||||
},
|
||||
},
|
||||
"Count of Successes": {
|
||||
name: `Count of Successes`,
|
||||
graphType: `bar`,
|
||||
buckets: {
|
||||
type: `number`,
|
||||
min: 0,
|
||||
step: 1,
|
||||
},
|
||||
config: {
|
||||
stacked: true,
|
||||
},
|
||||
},
|
||||
"Type of Result": {
|
||||
name: `Type of Result`,
|
||||
graphType: `bar`,
|
||||
buckets: {
|
||||
type: `string`,
|
||||
trim: true, // forced true
|
||||
blank: false, // forced false
|
||||
textSearch: false, // forced false
|
||||
choices: [`Normal`, `Popped Off`, `Downed`],
|
||||
},
|
||||
config: {
|
||||
stacked: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static createRow(table, user, row) {};
|
||||
static #rows = {};
|
||||
|
||||
static #cache = {};
|
||||
static getTables() {
|
||||
/** @type {Array<{ name: string; }>} */
|
||||
return Object.values(this.#tables);
|
||||
};
|
||||
|
||||
static getRows(tableID, users) {
|
||||
if (users.length === 0) {
|
||||
static getTable(tableID) {
|
||||
return this.#tables[tableID];
|
||||
};
|
||||
|
||||
static createRow(table, userID, row) {
|
||||
if (!this.#tables[table]) { return };
|
||||
this.#rows[userID] ??= {};
|
||||
this.#rows[userID][table] ??= [];
|
||||
|
||||
// data format assertions
|
||||
row._id ||= randomID();
|
||||
|
||||
this.#rows[userID][table].push(row);
|
||||
this.render();
|
||||
};
|
||||
|
||||
static getRows(tableID, userIDs, privacy = `none`) {
|
||||
if (userIDs.length === 0) {
|
||||
return {};
|
||||
};
|
||||
|
||||
const datasets = {};
|
||||
|
||||
for (const user of users) {
|
||||
if (this.#cache[user]?.[tableID]) {
|
||||
datasets[user] = this.#cache[user][tableID];
|
||||
} else {
|
||||
|
||||
const [table, subtable] = tableID.split(`/`);
|
||||
if (!subtable) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const size = Number.parseInt(subtable.slice(1));
|
||||
const rows = [];
|
||||
|
||||
for (let i = 1; i <= size; i++) {
|
||||
const count = getNormalDistributionHeight(i, size / 2, size);
|
||||
const temp = new Array(count)
|
||||
.fill(null)
|
||||
.map(() => generateRow(
|
||||
game.user.id == user ? i : Math.ceil(Math.random() * size),
|
||||
));
|
||||
rows.push(...temp);
|
||||
for (const userID of userIDs) {
|
||||
if (this.#rows[userID]?.[tableID]) {
|
||||
datasets[userID] = filterPrivateRows(
|
||||
this.#rows[userID][tableID] ?? [],
|
||||
userID,
|
||||
privacy,
|
||||
);
|
||||
};
|
||||
|
||||
this.#cache[user] ??= {};
|
||||
datasets[user] = this.#cache[user][tableID] = rows;
|
||||
}
|
||||
}
|
||||
|
||||
return datasets;
|
||||
};
|
||||
|
||||
static updateRow(table, user, rowId, changes) {};
|
||||
static updateRow(table, userID, rowID, changes) {
|
||||
if (!this.#tables[table] || !this.#rows[userID]?.[table]) { return };
|
||||
let row = this.#rows[userID][table].find(row => row._id === rowID);
|
||||
if (!row) { return };
|
||||
mergeObject(row, changes);
|
||||
this.render();
|
||||
};
|
||||
|
||||
static deleteRow(table, user, rowId) {};
|
||||
static deleteRow(table, userID, rowID) {
|
||||
if (!this.#tables[table] || !this.#rows[userID]?.[table]) { return };
|
||||
let rowIndex = this.#rows[userID][table].findIndex(row => row._id === rowID);
|
||||
if (rowIndex === -1) { return };
|
||||
this.#rows[userID][table].splice(rowIndex, 1);
|
||||
this.render();
|
||||
};
|
||||
|
||||
static apps = {};
|
||||
static render() {
|
||||
for (const app of Object.values(this.apps)) {
|
||||
app.render();
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Used to listen for changes from other clients and refresh the local DB as
|
||||
* required, so that the StatsTracker stays up to date.
|
||||
*/
|
||||
static registerListeners() {};
|
||||
|
||||
static unregisterListeners() {};
|
||||
};
|
||||
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
|
|
|||
30
module/utils/filterPrivateRows.mjs
Normal file
30
module/utils/filterPrivateRows.mjs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* Filters an array of database rows based on if the current user would
|
||||
* be able to see them based on the privacy level.
|
||||
*
|
||||
* @param {Array<any>} rows The rows to filter
|
||||
* @param {string} userID The user's ID who the rows belong to
|
||||
* @param {"all"|"me"|"none"} privacy The privacy level we're filtering for
|
||||
* @returns The filtered rows
|
||||
*/
|
||||
export function filterPrivateRows(rows, userID, privacy) {
|
||||
console.log(rows, userID, privacy);
|
||||
const filtered = [];
|
||||
|
||||
const isMe = userID === game.user.id;
|
||||
// TODO: make this use a permission rather than just isGM
|
||||
const canSeeAll = game.user.isGM;
|
||||
|
||||
for (const row of rows) {
|
||||
|
||||
let allowed = !row.isPrivate;
|
||||
allowed ||= privacy === `all` && canSeeAll;
|
||||
allowed ||= privacy === `my` && isMe;
|
||||
|
||||
if (allowed) {
|
||||
filtered.push(row);
|
||||
};
|
||||
};
|
||||
|
||||
return filtered;
|
||||
};
|
||||
53
module/utils/validateValue.mjs
Normal file
53
module/utils/validateValue.mjs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import { Logger } from "./Logger.mjs";
|
||||
|
||||
const { deepClone } = foundry.utils;
|
||||
const { StringField, NumberField } = foundry.data.fields;
|
||||
|
||||
export function validateValue(value, options) {
|
||||
let opts = deepClone(options);
|
||||
if (validatorTypes[opts.type] == null) {
|
||||
Logger.error(`Failed to find type validator for: ${opts.type}`);
|
||||
return false;
|
||||
};
|
||||
|
||||
const validator = validatorTypes[opts.type];
|
||||
validator.transformOptions(opts);
|
||||
|
||||
const field = new validator.field(opts);
|
||||
const error = field.validate(value);
|
||||
|
||||
// DataFields return a class instance on error, or void when valid.
|
||||
return !error;
|
||||
};
|
||||
|
||||
const validatorTypes = {
|
||||
string: {
|
||||
field: StringField,
|
||||
transformOptions: (opts) => {
|
||||
delete opts.type;
|
||||
opts.nullable = false;
|
||||
opts.trim = true;
|
||||
opts.blank = false;
|
||||
if (typeof opts.choices === `function`) {
|
||||
Logger.error(`Choices cannot be a function in a table's buckets configuraion`);
|
||||
delete opts.choices;
|
||||
};
|
||||
},
|
||||
},
|
||||
number: {
|
||||
field: NumberField,
|
||||
transformOptions: (opts) => {
|
||||
delete opts.type;
|
||||
opts.nullable = false;
|
||||
opts.integer = true;
|
||||
},
|
||||
},
|
||||
range: {
|
||||
field: NumberField,
|
||||
transformOptions: (opts) => {
|
||||
delete opts.type;
|
||||
opts.nullable = false;
|
||||
opts.integer = true;
|
||||
},
|
||||
},
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue