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 { StatsViewer } from "./Apps/StatsViewer.mjs";
|
||||||
import { TestApp } from "./Apps/TestApp.mjs";
|
import { TestApp } from "./Apps/TestApp.mjs";
|
||||||
|
|
||||||
|
// Utils
|
||||||
|
import { filterPrivateRows } from "./utils/filterPrivateRows.mjs";
|
||||||
|
import { validateValue } from "./utils/validateValue.mjs";
|
||||||
|
|
||||||
const { deepFreeze } = foundry.utils;
|
const { deepFreeze } = foundry.utils;
|
||||||
|
|
||||||
Object.defineProperty(
|
Object.defineProperty(
|
||||||
|
|
@ -12,6 +17,10 @@ Object.defineProperty(
|
||||||
TestApp,
|
TestApp,
|
||||||
StatsViewer,
|
StatsViewer,
|
||||||
},
|
},
|
||||||
|
utils: {
|
||||||
|
filterPrivateRows,
|
||||||
|
validateValue,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
writable: false,
|
writable: false,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,82 +1,150 @@
|
||||||
/* eslint-disable no-unused-vars */
|
/* eslint-disable no-unused-vars */
|
||||||
import { Table } from "./model.mjs";
|
import { filterPrivateRows } from "../filterPrivateRows.mjs";
|
||||||
|
|
||||||
const { randomID } = foundry.utils;
|
const { randomID, mergeObject } = 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);
|
|
||||||
};
|
|
||||||
|
|
||||||
export class MemoryDatabase {
|
export class MemoryDatabase {
|
||||||
static getTables() {
|
static #tables = {
|
||||||
/** @type {Array<{ name: string; }>} */
|
"Dice/d10": {
|
||||||
return [
|
name: `Dice/d10`,
|
||||||
{ name: `Dice/d4` },
|
graphType: `bar`,
|
||||||
{ name: `Dice/d6` },
|
buckets: {
|
||||||
{ name: `Dice/d8` },
|
type: `range`,
|
||||||
{ name: `Dice/d10` },
|
min: 1,
|
||||||
{ name: `Dice/d12` },
|
max: 10,
|
||||||
{ name: `Dice/d20` },
|
step: 1,
|
||||||
{ name: `Dice/d100` },
|
},
|
||||||
{ name: `Count of Successes` },
|
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) {
|
static getTable(tableID) {
|
||||||
if (users.length === 0) {
|
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 {};
|
return {};
|
||||||
};
|
};
|
||||||
|
|
||||||
const datasets = {};
|
const datasets = {};
|
||||||
|
|
||||||
for (const user of users) {
|
for (const userID of userIDs) {
|
||||||
if (this.#cache[user]?.[tableID]) {
|
if (this.#rows[userID]?.[tableID]) {
|
||||||
datasets[user] = this.#cache[user][tableID];
|
datasets[userID] = filterPrivateRows(
|
||||||
} else {
|
this.#rows[userID][tableID] ?? [],
|
||||||
|
userID,
|
||||||
const [table, subtable] = tableID.split(`/`);
|
privacy,
|
||||||
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);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.#cache[user] ??= {};
|
|
||||||
datasets[user] = this.#cache[user][tableID] = rows;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return datasets;
|
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 */
|
/* 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