Files
5etools-mirror-2.github.io/js/utils-brew/utils-brew-impl-brew.js
TheGiddyLimit c330614db9 v1.209.2
2024-07-19 17:04:07 +01:00

266 lines
8.0 KiB
JavaScript

import {BrewUtil2Base} from "./utils-brew-base.js";
import {BrewDoc} from "./utils-brew-models.js";
export class BrewUtil2_ extends BrewUtil2Base {
_STORAGE_KEY_LEGACY = "HOMEBREW_STORAGE";
_STORAGE_KEY_LEGACY_META = "HOMEBREW_META_STORAGE";
// Keep these distinct from the OG brew key, so users can recover their old brew if required.
_STORAGE_KEY = "HOMEBREW_2_STORAGE";
_STORAGE_KEY_META = "HOMEBREW_2_STORAGE_METAS";
_STORAGE_KEY_CUSTOM_URL = "HOMEBREW_CUSTOM_REPO_URL";
_STORAGE_KEY_MIGRATION_VERSION = "HOMEBREW_2_STORAGE_MIGRATION";
_VERSION = 2;
_PATH_LOCAL_DIR = "homebrew";
_PATH_LOCAL_INDEX = VeCt.JSON_BREW_INDEX;
IS_EDITABLE = true;
PAGE_MANAGE = UrlUtil.PG_MANAGE_BREW;
URL_REPO_DEFAULT = VeCt.URL_BREW;
URL_REPO_ROOT_DEFAULT = VeCt.URL_ROOT_BREW;
DISPLAY_NAME = "homebrew";
DISPLAY_NAME_PLURAL = "homebrews";
DEFAULT_AUTHOR = "";
STYLE_BTN = "btn-info";
IS_PREFER_DATE_ADDED = true;
/* -------------------------------------------- */
_pInit_doBindDragDrop () {
document.body.addEventListener("drop", async evt => {
if (EventUtil.isInInput(evt)) return;
evt.stopPropagation();
evt.preventDefault();
const files = evt.dataTransfer?.files;
if (!files?.length) return;
const pFiles = [...files].map((file, i) => {
if (!/\.json$/i.test(file.name)) return null;
return new Promise(resolve => {
const reader = new FileReader();
reader.onload = () => {
let json;
try {
json = JSON.parse(reader.result);
} catch (ignored) {
return resolve(null);
}
resolve({name: file.name, json});
};
reader.readAsText(files[i]);
});
});
const fileMetas = (await Promise.allSettled(pFiles))
.filter(({status}) => status === "fulfilled")
.map(({value}) => value)
.filter(Boolean);
await this.pAddBrewsFromFiles(fileMetas);
if (this.isReloadRequired()) this.doLocationReload();
});
document.body.addEventListener("dragover", evt => {
if (EventUtil.isInInput(evt)) return;
evt.stopPropagation();
evt.preventDefault();
});
}
/* -------------------------------------------- */
async pGetSourceIndex (urlRoot) { return DataUtil.brew.pLoadSourceIndex(urlRoot); }
getFileUrl (path, urlRoot) { return DataUtil.brew.getFileUrl(path, urlRoot); }
pLoadTimestamps (urlRoot) { return DataUtil.brew.pLoadTimestamps(urlRoot); }
pLoadPropIndex (urlRoot) { return DataUtil.brew.pLoadPropIndex(urlRoot); }
pLoadMetaIndex (urlRoot) { return DataUtil.brew.pLoadMetaIndex(urlRoot); }
pLoadAdventureBookIdsIndex (urlRoot) { return DataUtil.brew.pLoadAdventureBookIdsIndex(urlRoot); }
/* -------------------------------------------- */
// region Editable
async pGetEditableBrewDoc () {
return this._findEditableBrewDoc({brewRaw: await this._pGetBrewRaw()});
}
_findEditableBrewDoc ({brewRaw}) {
return brewRaw.find(it => it.head.isEditable);
}
async pGetOrCreateEditableBrewDoc () {
const existing = await this.pGetEditableBrewDoc();
if (existing) return existing;
const brew = this._getNewEditableBrewDoc();
const brews = [...MiscUtil.copyFast(await this._pGetBrewRaw()), brew];
await this.pSetBrew(brews);
return brew;
}
async pSetEditableBrewDoc (brew) {
if (!brew?.head?.docIdLocal || !brew?.body) throw new Error(`Invalid editable brew document!`); // Sanity check
await this.pUpdateBrew(brew);
}
/**
* @param prop
* @param uniqueId
* @param isDuplicate If the entity should be a duplicate, i.e. have a new `uniqueId`.
*/
async pGetEditableBrewEntity (prop, uniqueId, {isDuplicate = false} = {}) {
if (!uniqueId) throw new Error(`A "uniqueId" must be provided!`);
const brew = await this.pGetOrCreateEditableBrewDoc();
const out = (brew.body?.[prop] || []).find(it => it.uniqueId === uniqueId);
if (!out || !isDuplicate) return out;
if (isDuplicate) out.uniqueId = CryptUtil.uid();
return out;
}
async pPersistEditableBrewEntity (prop, ent) {
if (!ent.uniqueId) throw new Error(`Entity did not have a "uniqueId"!`);
const brew = await this.pGetOrCreateEditableBrewDoc();
const ixExisting = (brew.body?.[prop] || []).findIndex(it => it.uniqueId === ent.uniqueId);
if (!~ixExisting) {
const nxt = MiscUtil.copyFast(brew);
MiscUtil.getOrSet(nxt.body, prop, []).push(ent);
await this.pUpdateBrew(nxt);
return;
}
const nxt = MiscUtil.copyFast(brew);
nxt.body[prop][ixExisting] = ent;
await this.pUpdateBrew(nxt);
}
async pRemoveEditableBrewEntity (prop, uniqueId) {
if (!uniqueId) throw new Error(`A "uniqueId" must be provided!`);
const brew = await this.pGetOrCreateEditableBrewDoc();
if (!brew.body?.[prop]?.length) return;
const nxt = MiscUtil.copyFast(brew);
nxt.body[prop] = nxt.body[prop].filter(it => it.uniqueId !== uniqueId);
if (nxt.body[prop].length === brew.body[prop]) return; // Silently allow no-op deletes
await this.pUpdateBrew(nxt);
}
async pAddSource (sourceObj) {
const existing = await this.pGetEditableBrewDoc();
if (existing) {
const nxt = MiscUtil.copyFast(existing);
const sources = MiscUtil.getOrSet(nxt.body, "_meta", "sources", []);
sources.push(sourceObj);
await this.pUpdateBrew(nxt);
return;
}
const json = {_meta: {sources: [sourceObj]}};
const brew = this._getBrewDoc({json, isEditable: true});
const brews = [...MiscUtil.copyFast(await this._pGetBrewRaw()), brew];
await this.pSetBrew(brews);
}
async pEditSource (sourceObj) {
const existing = await this.pGetEditableBrewDoc();
if (!existing) throw new Error(`Editable brew document does not exist!`);
const nxt = MiscUtil.copyFast(existing);
const sources = MiscUtil.get(nxt.body, "_meta", "sources");
if (!sources) throw new Error(`Source "${sourceObj.json}" does not exist in editable brew document!`);
const existingSourceObj = sources.find(it => it.json === sourceObj.json);
if (!existingSourceObj) throw new Error(`Source "${sourceObj.json}" does not exist in editable brew document!`);
Object.assign(existingSourceObj, sourceObj);
await this.pUpdateBrew(nxt);
}
async pIsEditableSourceJson (sourceJson) {
const brew = await this.pGetEditableBrewDoc();
if (!brew) return false;
const sources = MiscUtil.get(brew.body, "_meta", "sources") || [];
return sources.some(it => it.json === sourceJson);
}
/**
* Move the brews containing a given source to the editable document. If a brew cannot be moved to the editable
* document, copy the source to the editable document instead.
*/
async pMoveOrCopyToEditableBySourceJson (sourceJson) {
if (await this.pIsEditableSourceJson(sourceJson)) return;
// Fetch all candidate brews
const brews = (await this._pGetBrewRaw()).filter(brew => (brew.body._meta?.sources || []).some(src => src.json === sourceJson));
const brewsLocal = (await this._pGetBrew_pGetLocalBrew()).filter(brew => (brew.body._meta?.sources || []).some(src => src.json === sourceJson));
// Arbitrarily select one, preferring non-local
let brew = brews.find(brew => BrewDoc.isOperationPermitted_moveToEditable({brew}));
if (!brew) brew = brewsLocal.find(brew => BrewDoc.isOperationPermitted_moveToEditable({brew, isAllowLocal: true}));
if (!brew) return;
if (brew.head.isLocal) return this.pCopyToEditable({brews: [brew]});
return this.pMoveToEditable({brews: [brew]});
}
async pMoveToEditable ({brews}) {
const out = await this.pCopyToEditable({brews});
await this.pDeleteBrews(brews);
return out;
}
async pCopyToEditable ({brews}) {
const brewEditable = await this.pGetOrCreateEditableBrewDoc();
const cpyBrewEditableDoc = BrewDoc.fromObject(brewEditable, {isCopy: true});
brews.forEach((brew, i) => cpyBrewEditableDoc.mutMerge({json: brew.body, isLazy: i !== brews.length - 1}));
await this.pSetEditableBrewDoc(cpyBrewEditableDoc.toObject());
return cpyBrewEditableDoc;
}
async pHasEditableSourceJson () {
const brewsStored = await this._pGetBrewRaw();
if (!brewsStored?.length) return false;
return brewsStored
.map(brew => BrewDoc.fromObject(brew))
.some(brew => brew.head.isEditable && !brew.isEmpty());
}
// endregion
}