mirror of
https://github.com/Kornstalx/5etools-mirror-2.github.io.git
synced 2025-10-28 20:45:35 -05:00
262 lines
6.4 KiB
JavaScript
262 lines
6.4 KiB
JavaScript
export class BrewDoc {
|
|
// Things which are stored in "_meta", but are "content metadata" rather than "file metadata."
|
|
static _META_KEYS_CONTENT_METADATA__OBJECT = [
|
|
"skills",
|
|
"senses",
|
|
"spellSchools",
|
|
"spellDistanceUnits",
|
|
"optionalFeatureTypes",
|
|
"psionicTypes",
|
|
"currencyConversions",
|
|
];
|
|
|
|
constructor (opts) {
|
|
opts = opts || {};
|
|
this.head = opts.head;
|
|
this.body = opts.body;
|
|
}
|
|
|
|
toObject () { return MiscUtil.copyFast({...this}); }
|
|
|
|
static fromValues ({head, body}) {
|
|
return new this({
|
|
head: _BrewDocHead.fromValues(head),
|
|
body,
|
|
});
|
|
}
|
|
|
|
static fromObject (obj, opts = {}) {
|
|
const {isCopy = false} = opts;
|
|
return new this({
|
|
head: _BrewDocHead.fromObject(obj.head, opts),
|
|
body: isCopy ? MiscUtil.copyFast(obj.body) : obj.body,
|
|
});
|
|
}
|
|
|
|
mutUpdate ({json}) {
|
|
this.body = json;
|
|
this.head.mutUpdate({json, body: this.body});
|
|
return this;
|
|
}
|
|
|
|
isEmpty () {
|
|
if (
|
|
Object.entries(this.body)
|
|
.some(([k, v]) => {
|
|
if (!(v instanceof Array)) return false;
|
|
if (k === "_meta" || k === "_test") return false;
|
|
return !!v.length;
|
|
})
|
|
) return false;
|
|
|
|
if (!this.body._meta) return false;
|
|
|
|
if (
|
|
this.constructor._META_KEYS_CONTENT_METADATA__OBJECT
|
|
.some(k => !!Object.keys(this.body._meta[k] || {}).length)
|
|
) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
// region Conditions
|
|
static isOperationPermitted_moveToEditable ({brew, isAllowLocal = false} = {}) {
|
|
return !brew.head.isEditable
|
|
&& (isAllowLocal || !brew.head.isLocal);
|
|
}
|
|
// endregion
|
|
|
|
// region Merging
|
|
mutMerge ({json, isLazy = false}) {
|
|
this.body = this.constructor.mergeObjects({isCopy: !isLazy, isMutMakeCompatible: false}, this.body, json);
|
|
this.head.mutMerge({json, body: this.body, isLazy});
|
|
return this;
|
|
}
|
|
|
|
static mergeObjects ({isCopy = true, isMutMakeCompatible = true} = {}, ...jsons) {
|
|
const out = {};
|
|
|
|
jsons.forEach(json => {
|
|
json = isCopy ? MiscUtil.copyFast(json) : json;
|
|
|
|
if (isMutMakeCompatible) this._mergeObjects_mutMakeCompatible(json);
|
|
|
|
Object.entries(json)
|
|
.forEach(([prop, val]) => {
|
|
switch (prop) {
|
|
case "_meta": return this._mergeObjects_key__meta({out, prop, val});
|
|
case "_test": return; // ignore; used for static testing
|
|
default: return this._mergeObjects_default({out, prop, val});
|
|
}
|
|
});
|
|
});
|
|
|
|
return out;
|
|
}
|
|
|
|
static _META_KEYS_MERGEABLE_OBJECTS = [
|
|
...this._META_KEYS_CONTENT_METADATA__OBJECT,
|
|
];
|
|
|
|
static _META_KEYS_MERGEABLE_SPECIAL = {
|
|
"dateAdded": (a, b) => a != null && b != null ? Math.min(a, b) : a ?? b,
|
|
"dateLastModified": (a, b) => a != null && b != null ? Math.max(a, b) : a ?? b,
|
|
|
|
"dependencies": (a, b) => this._metaMerge_dependenciesIncludes(a, b),
|
|
"includes": (a, b) => this._metaMerge_dependenciesIncludes(a, b),
|
|
"internalCopies": (a, b) => [...(a || []), ...(b || [])].unique(),
|
|
|
|
"otherSources": (a, b) => this._metaMerge_otherSources(a, b),
|
|
|
|
"status": (a, b) => this._metaMerge_status(a, b),
|
|
};
|
|
|
|
static _metaMerge_dependenciesIncludes (a, b) {
|
|
if (a != null && b != null) {
|
|
Object.entries(b)
|
|
.forEach(([prop, arr]) => a[prop] = [...(a[prop] || []), ...arr].unique());
|
|
return a;
|
|
}
|
|
|
|
return a ?? b;
|
|
}
|
|
|
|
static _metaMerge_otherSources (a, b) {
|
|
if (a != null && b != null) {
|
|
// Note that this can clobber the values in the mapping, but we don't really care since they're not used.
|
|
Object.entries(b)
|
|
.forEach(([prop, obj]) => a[prop] = Object.assign(a[prop] || {}, obj));
|
|
return a;
|
|
}
|
|
|
|
return a ?? b;
|
|
}
|
|
|
|
static _META_MERGE__STATUS_PRECEDENCE = [
|
|
"invalid",
|
|
"deprecated",
|
|
"wip",
|
|
"ready",
|
|
];
|
|
|
|
static _metaMerge_status (a, b) {
|
|
return [a || "ready", b || "ready"]
|
|
.sort((a, b) => this._META_MERGE__STATUS_PRECEDENCE.indexOf(a) - this._META_MERGE__STATUS_PRECEDENCE.indexOf(b))[0];
|
|
}
|
|
|
|
static _mergeObjects_key__meta ({out, val}) {
|
|
out._meta = out._meta || {};
|
|
|
|
out._meta.sources = [...(out._meta.sources || []), ...(val.sources || [])];
|
|
|
|
Object.entries(val)
|
|
.forEach(([metaProp, metaVal]) => {
|
|
if (this._META_KEYS_MERGEABLE_SPECIAL[metaProp]) {
|
|
out._meta[metaProp] = this._META_KEYS_MERGEABLE_SPECIAL[metaProp](out._meta[metaProp], metaVal);
|
|
return;
|
|
}
|
|
if (!this._META_KEYS_MERGEABLE_OBJECTS.includes(metaProp)) return;
|
|
Object.assign(out._meta[metaProp] = out._meta[metaProp] || {}, metaVal);
|
|
});
|
|
}
|
|
|
|
static _mergeObjects_default ({out, prop, val}) {
|
|
// If we cannot merge a prop, use the first value found for it, as a best-effort fallback
|
|
if (!(val instanceof Array)) return out[prop] === undefined ? out[prop] = val : null;
|
|
|
|
out[prop] = [...out[prop] || [], ...val];
|
|
}
|
|
|
|
static _mergeObjects_mutMakeCompatible (json) {
|
|
// region Item
|
|
if (json.variant) {
|
|
// 2022-07-09
|
|
json.magicvariant = json.variant;
|
|
delete json.variant;
|
|
}
|
|
// endregion
|
|
|
|
// region Race
|
|
if (json.subrace) {
|
|
json.subrace.forEach(sr => {
|
|
if (!sr.race) return;
|
|
sr.raceName = sr.race.name;
|
|
sr.raceSource = sr.race.source || sr.source || Parser.SRC_PHB;
|
|
});
|
|
}
|
|
// endregion
|
|
|
|
// region Creature (monster)
|
|
if (json.monster) {
|
|
json.monster.forEach(mon => {
|
|
// 2022-03-22
|
|
if (typeof mon.size === "string") mon.size = [mon.size];
|
|
|
|
// 2022=05-29
|
|
if (mon.summonedBySpell && !mon.summonedBySpellLevel) mon.summonedBySpellLevel = 1;
|
|
});
|
|
}
|
|
// endregion
|
|
|
|
// region Object
|
|
if (json.object) {
|
|
json.object.forEach(obj => {
|
|
// 2023-10-07
|
|
if (typeof obj.size === "string") obj.size = [obj.size];
|
|
});
|
|
}
|
|
// endregion
|
|
}
|
|
// endregion
|
|
}
|
|
|
|
class _BrewDocHead {
|
|
constructor (opts) {
|
|
opts = opts || {};
|
|
|
|
this.docIdLocal = opts.docIdLocal;
|
|
this.timeAdded = opts.timeAdded;
|
|
this.checksum = opts.checksum;
|
|
this.url = opts.url;
|
|
this.filename = opts.filename;
|
|
this.isLocal = opts.isLocal;
|
|
this.isEditable = opts.isEditable;
|
|
}
|
|
|
|
toObject () { return MiscUtil.copyFast({...this}); }
|
|
|
|
static fromValues (
|
|
{
|
|
json,
|
|
url = null,
|
|
filename = null,
|
|
isLocal = false,
|
|
isEditable = false,
|
|
},
|
|
) {
|
|
return new this({
|
|
docIdLocal: CryptUtil.uid(),
|
|
timeAdded: Date.now(),
|
|
checksum: CryptUtil.md5(JSON.stringify(json)),
|
|
url: url,
|
|
filename: filename,
|
|
isLocal: isLocal,
|
|
isEditable: isEditable,
|
|
});
|
|
}
|
|
|
|
static fromObject (obj, {isCopy = false} = {}) {
|
|
return new this(isCopy ? MiscUtil.copyFast(obj) : obj);
|
|
}
|
|
|
|
mutUpdate ({json}) {
|
|
this.checksum = CryptUtil.md5(JSON.stringify(json));
|
|
return this;
|
|
}
|
|
|
|
mutMerge ({json, body, isLazy}) {
|
|
if (!isLazy) this.checksum = CryptUtil.md5(JSON.stringify(body ?? json));
|
|
return this;
|
|
}
|
|
}
|