This commit is contained in:
TheGiddyLimit
2024-07-19 17:04:07 +01:00
parent 1e72254fcb
commit c330614db9
27 changed files with 1495 additions and 137 deletions

View File

@@ -565,6 +565,9 @@ class BookUtil {
if (await this._booksHashChange_pDoLoadPrerelease({bookId, $contents, hashParts, isNewBook})) return;
if (await this._booksHashChange_pDoLoadBrew({bookId, $contents, hashParts, isNewBook})) return;
// if it's prerelease/homebrew but hasn't been loaded
if (await this._booksHashChange_pDoFetchPrereleaseBrew({bookId, $contents, hashParts, isNewBook})) return;
return this._booksHashChange_handleNotFound({$contents, bookId});
}
@@ -595,6 +598,27 @@ class BookUtil {
return true;
}
static async _booksHashChange_pDoFetchPrereleaseBrew ({bookId, $contents, hashParts, isNewBook}) {
const {source} = await UrlUtil.pAutoDecodeHash(bookId);
const loaded = await DataLoader.pCacheAndGetHash(UrlUtil.getCurrentPage(), bookId, {isSilent: true});
if (!loaded) return false;
return [
PrereleaseUtil,
BrewUtil2,
]
.some(brewUtil => {
if (
brewUtil.hasSourceJson(source)
&& brewUtil.isReloadRequired()
) {
brewUtil.doLocationReload({isRetainHash: true});
return true;
}
});
}
static _booksHashChange_getCleanName (fromIndex) {
if (fromIndex.parentSource) {
const fullParentSource = Parser.sourceJsonToFull(fromIndex.parentSource);

View File

@@ -88,41 +88,42 @@ class UtilClassesPage {
Renderer.get().setFirstSection(true);
if (hasEntries) {
const renderer = Renderer.get();
Renderer.get().withDepthTracker(
depthArr || [],
({renderer}) => {
entFluff.entries.filter(f => f.source === ent.source).forEach(f => f._isStandardSource = true);
if (depthArr) renderer.setDepthTracker(depthArr, {additionalPropsInherited: ["_isStandardSource"]});
else renderer.setDepthTracker([]);
entFluff.entries.forEach((f, i) => {
const cpy = MiscUtil.copyFast(f);
entFluff.entries.filter(f => f.source === ent.source).forEach(f => f._isStandardSource = true);
// Remove the name from the first section if it is a copy of the class/subclass name
if (
isRemoveRootName
&& i === 0
&& cpy.name
&& (
cpy.name.toLowerCase() === ent.name.toLowerCase()
|| cpy.name.toLowerCase() === `the ${ent.name.toLowerCase()}`
)
) {
delete cpy.name;
}
entFluff.entries.forEach((f, i) => {
const cpy = MiscUtil.copyFast(f);
if (
isAddSourceNote
&& typeof cpy !== "string"
&& cpy.source
&& cpy.source !== ent.source
&& cpy.entries
) {
cpy.entries.unshift(`{@note The following information is from ${Parser.sourceJsonToFull(cpy.source)}${Renderer.utils.isDisplayPage(cpy.page) ? `, page ${cpy.page}` : ""}.}`);
}
// Remove the name from the first section if it is a copy of the class/subclass name
if (
isRemoveRootName
&& i === 0
&& cpy.name
&& (
cpy.name.toLowerCase() === ent.name.toLowerCase()
|| cpy.name.toLowerCase() === `the ${ent.name.toLowerCase()}`
)
) {
delete cpy.name;
}
if (
isAddSourceNote
&& typeof cpy !== "string"
&& cpy.source
&& cpy.source !== ent.source
&& cpy.entries
) {
cpy.entries.unshift(`{@note The following information is from ${Parser.sourceJsonToFull(cpy.source)}${Renderer.utils.isDisplayPage(cpy.page) ? `, page ${cpy.page}` : ""}.}`);
}
stack += renderer.render(cpy);
});
stack += renderer.render(cpy);
});
},
{additionalPropsInherited: ["_isStandardSource"]},
);
}
if (hasImages) {
@@ -2158,9 +2159,15 @@ class ClassesPage extends MixinComponentGlobalState(MixinBaseComponent(MixinProx
if (source === cls.source) return {isSkip: true};
},
fn: () => {
return $(`<tr data-scroll-id="${ixLvl}-${ixFeature}" data-feature-type="class" class="cls-main__linked-titles"><td colspan="6"></td></tr>`)
.fastSetHtml(Renderer.get().setDepthTracker(depthArr, {additionalProps: ["isReprinted"], additionalPropsInherited: ["_isStandardSource", "isClassFeatureVariant"]}).render(feature))
.appendTo($content);
return Renderer.get().withDepthTracker(
depthArr,
({renderer}) => {
return $(`<tr data-scroll-id="${ixLvl}-${ixFeature}" data-feature-type="class" class="cls-main__linked-titles"><td colspan="6"></td></tr>`)
.fastSetHtml(renderer.render(feature))
.appendTo($content);
},
{additionalProps: ["isReprinted"], additionalPropsInherited: ["_isStandardSource", "isClassFeatureVariant"]},
);
},
});
this._trackOutlineCfData(ixLvl, ixFeature, depthArr);
@@ -2173,7 +2180,7 @@ class ClassesPage extends MixinComponentGlobalState(MixinBaseComponent(MixinProx
// Add a placeholder feature to display when no subclasses are active
const $trSubclassFeature = $(`<tr class="cls-main__sc-feature" data-subclass-none-message="true"><td colspan="6"></td></tr>`)
.fastSetHtml(Renderer.get().setDepthTracker([]).render({type: "entries", entries: [{name: `{@note No Subclass Selected}`, type: "entries", entries: [`{@note <span class="clickable roller" data-jump-select-a-subclass="true">Select a subclass</span> to view its feature(s) here.}`]}]}))
.fastSetHtml(Renderer.get().withDepthTracker([], ({renderer}) => renderer.render({type: "entries", entries: [{name: `{@note No Subclass Selected}`, type: "entries", entries: [`{@note <span class="clickable roller" data-jump-select-a-subclass="true">Select a subclass</span> to view its feature(s) here.}`]}]})))
.appendTo($content);
await cls.subclasses.pSerialAwaitMap(async sc => {
@@ -2224,7 +2231,9 @@ class ClassesPage extends MixinComponentGlobalState(MixinBaseComponent(MixinProx
},
fn: () => {
const $trSubclassFeature = $(`<tr class="cls-main__sc-feature" data-subclass-id="${UrlUtil.getStateKeySubclass(sc)}"><td colspan="6"></td></tr>`)
.fastSetHtml(Renderer.get().setDepthTracker(depthArr, {additionalProps: ["isReprinted"], additionalPropsInherited: ["_isStandardSource", "isClassFeatureVariant"]}).render(toRender))
.fastSetHtml(
Renderer.get().withDepthTracker(depthArr, ({renderer}) => renderer.render(toRender), {additionalProps: ["isReprinted"], additionalPropsInherited: ["_isStandardSource", "isClassFeatureVariant"]}),
)
.appendTo($content);
},
});

View File

@@ -422,11 +422,11 @@ class SublistManager {
await this.pDoSublistRemove({entity, doFinalize: true});
}
getTitleBtnAdd () { return `Add (SHIFT for ${this._shiftCountAddSubtract})`; }
getTitleBtnSubtract () { return `Subtract (SHIFT for ${this._shiftCountAddSubtract})`; }
getTitleBtnAdd () { return `Add (SHIFT for ${this._shiftCountAddSubtract}) (Hotkey: p)`; }
getTitleBtnSubtract () { return `Subtract (SHIFT for ${this._shiftCountAddSubtract}) (Hotkey: P)`; }
async pHandleClick_btnAdd ({evt, entity}) {
const addCount = evt.shiftKey ? this._shiftCountAddSubtract : 1;
async pHandleClick_btnAdd ({entity, isMultiple = false}) {
const addCount = isMultiple ? this._shiftCountAddSubtract : 1;
return this.pDoSublistAdd({
index: Hist.lastLoadedId,
entity,
@@ -435,8 +435,8 @@ class SublistManager {
});
}
async pHandleClick_btnSubtract ({evt, entity}) {
const subtractCount = evt.shiftKey ? this._shiftCountAddSubtract : 1;
async pHandleClick_btnSubtract ({entity, isMultiple = false}) {
const subtractCount = isMultiple ? this._shiftCountAddSubtract : 1;
return this.pDoSublistSubtract({
index: Hist.lastLoadedId,
entity,
@@ -1567,7 +1567,7 @@ class ListPage {
const key = EventUtil.getKeyIgnoreCapsLock(evt);
switch (key) {
// K up; J down
// k up; j down
case "k":
case "j": {
// don't switch if the user is typing somewhere else
@@ -1576,6 +1576,24 @@ class ListPage {
return;
}
// p: toggle pinned/add 1 to sublist
case "p": {
if (EventUtil.isInInput(evt)) return;
if (!this._sublistManager) return;
if (this._sublistManager.isSublistItemsCountable) this._sublistManager.pHandleClick_btnAdd({entity: this._lastRender.entity}).then(null);
else this._sublistManager.pHandleClick_btnPin({entity: this._lastRender.entity}).then(null);
return;
}
// P: toggle pinned/remove 1 from sublist
case "P": {
if (EventUtil.isInInput(evt)) return;
if (!this._sublistManager) return;
if (this._sublistManager.isSublistItemsCountable) this._sublistManager.pHandleClick_btnSubtract({entity: this._lastRender.entity}).then(null);
else this._sublistManager.pHandleClick_btnPin({entity: this._lastRender.entity}).then(null);
return;
}
// m: expand/collapse current selection
case "m": {
if (EventUtil.isInInput(evt)) return;
const it = Hist.getSelectedListElementWithLocation();
@@ -1735,21 +1753,21 @@ class ListPage {
this._getOrTabRightButton(`pin`, `pushpin`)
.off("click")
.on("click", () => this._sublistManager.pHandleClick_btnPin({entity: this._lastRender.entity}))
.title("Pin (Toggle)");
.title("Pin (Toggle) (Hotkey: p/P)");
}
_bindAddButton () {
this._getOrTabRightButton(`sublist-add`, `plus`)
.off("click")
.title(this._sublistManager.getTitleBtnAdd())
.on("click", evt => this._sublistManager.pHandleClick_btnAdd({evt, entity: this._lastRender.entity}));
.on("click", evt => this._sublistManager.pHandleClick_btnAdd({entity: this._lastRender.entity, isMultiple: !!evt.shiftKey}));
}
_bindSubtractButton () {
this._getOrTabRightButton(`sublist-subtract`, `minus`)
.off("click")
.title(this._sublistManager.getTitleBtnSubtract())
.on("click", evt => this._sublistManager.pHandleClick_btnSubtract({evt, entity: this._lastRender.entity}));
.on("click", evt => this._sublistManager.pHandleClick_btnSubtract({entity: this._lastRender.entity, isMultiple: !!evt.shiftKey}));
}
/**

View File

@@ -915,8 +915,7 @@ class IndexableFileQuickReference extends IndexableFile {
static getChapterNameMetas (it, {isRequireQuickrefFlag = true} = {}) {
const trackedNames = [];
const renderer = Renderer.get().setDepthTracker(trackedNames);
renderer.render(it);
Renderer.get().withDepthTracker(trackedNames, ({renderer}) => renderer.render(it));
const nameCounts = {};
trackedNames.forEach(meta => {

View File

@@ -243,6 +243,50 @@ globalThis.Renderer = function () {
});
};
/**
* Specify an array where the renderer will record rendered header depths.
* Items added to the array are of the form: `{name: "Header Name", depth: 1, type: "entries", source: "PHB"}`
* @param arr
* @param additionalProps Additional data props which should be tracked per-entry.
* @param additionalPropsInherited As per additionalProps, but if a parent entry has the prop, it should be passed
* to its children.
*/
this.setDepthTracker = function (arr, {additionalProps, additionalPropsInherited} = {}) {
this._depthTracker = arr;
this._depthTrackerAdditionalProps = additionalProps || [];
this._depthTrackerAdditionalPropsInherited = additionalPropsInherited || [];
return this;
};
this.withDepthTracker = function (arr, fn, {additionalProps, additionalPropsInherited} = {}) {
const depthTrackerPrev = this._depthTracker;
const depthTrackerAdditionalPropsPrev = this._depthTrackerAdditionalProps;
const depthTrackerAdditionalPropsInheritedPrev = this._depthTrackerAdditionalPropsInherited;
let out;
try {
this.setDepthTracker(
arr,
{
additionalProps,
additionalPropsInherited,
},
);
out = fn({renderer: this});
} finally {
this.setDepthTracker(
depthTrackerPrev,
{
additionalProps: depthTrackerAdditionalPropsPrev,
additionalPropsInherited: depthTrackerAdditionalPropsInheritedPrev,
},
);
}
return out;
};
/* -------------------------------------------- */
// region Plugins
this.addPlugin = function (pluginType, fnPlugin) {
MiscUtil.getOrSet(this._plugins, pluginType, []).push(fnPlugin);
@@ -309,21 +353,6 @@ globalThis.Renderer = function () {
};
// endregion
/**
* Specify an array where the renderer will record rendered header depths.
* Items added to the array are of the form: `{name: "Header Name", depth: 1, type: "entries", source: "PHB"}`
* @param arr
* @param additionalProps Additional data props which should be tracked per-entry.
* @param additionalPropsInherited As per additionalProps, but if a parent entry has the prop, it should be passed
* to its children.
*/
this.setDepthTracker = function (arr, {additionalProps, additionalPropsInherited} = {}) {
this._depthTracker = arr;
this._depthTrackerAdditionalProps = additionalProps || [];
this._depthTrackerAdditionalPropsInherited = additionalPropsInherited || [];
return this;
};
this.getLineBreak = function () { return "<br>"; };
/**
@@ -1431,19 +1460,26 @@ globalThis.Renderer = function () {
};
this._renderStatblock = function (entry, textStack, meta, options) {
this._renderPrefix(entry, textStack, meta, options);
const page = entry.prop || Renderer.tag.getPage(entry.tag);
const source = Parser.getTagSource(entry.tag, entry.source);
const hash = entry.hash || (UrlUtil.URL_TO_HASH_BUILDER[page] ? UrlUtil.URL_TO_HASH_BUILDER[page]({...entry, name: entry.name, source}) : null);
const tag = entry.tag || Parser.getPropTag(entry.prop);
const asTag = `{@${entry.tag} ${entry.name}|${source}${entry.displayName ? `|${entry.displayName}` : ""}}`;
const asTag = `{@${tag} ${entry.name}|${source}${entry.displayName ? `|${entry.displayName}` : ""}}`;
const fromPlugins = this._applyPlugins_useFirst(
"statblock_render",
{textStack, meta, options},
{input: {entry, page, source, hash, tag, asTag}},
);
if (fromPlugins) return void (textStack[0] += fromPlugins);
if (!page || !source || !hash) {
this._renderPrefix(entry, textStack, meta, options);
this._renderDataHeader(textStack, entry.name, entry.style);
textStack[0] += `<tr>
<td colspan="6">
<i class="text-danger">Cannot load ${entry.tag ? `&quot;${asTag}&quot;` : entry.displayName || entry.name}! An unknown tag/prop, source, or hash was provided.</i>
<i class="text-danger">Cannot load ${tag ? `&quot;${asTag}&quot;` : entry.displayName || entry.name}! An unknown tag/prop, source, or hash was provided.</i>
</td>
</tr>`;
this._renderDataFooter(textStack);
@@ -1452,10 +1488,11 @@ globalThis.Renderer = function () {
return;
}
this._renderPrefix(entry, textStack, meta, options);
this._renderDataHeader(textStack, entry.displayName || entry.name, entry.style, {isCollapsed: entry.collapsed});
textStack[0] += `<tr>
<td colspan="6" data-rd-tag="${(entry.tag || "").qq()}" data-rd-page="${(page || "").qq()}" data-rd-source="${(source || "").qq()}" data-rd-hash="${(hash || "").qq()}" data-rd-name="${(entry.name || "").qq()}" data-rd-display-name="${(entry.displayName || "").qq()}" data-rd-style="${(entry.style || "").qq()}">
<i>Loading ${entry.tag ? `${Renderer.get().render(asTag)}` : entry.displayName || entry.name}...</i>
<td colspan="6" data-rd-tag="${(tag || "").qq()}" data-rd-page="${(page || "").qq()}" data-rd-source="${(source || "").qq()}" data-rd-hash="${(hash || "").qq()}" data-rd-name="${(entry.name || "").qq()}" data-rd-display-name="${(entry.displayName || "").qq()}" data-rd-style="${(entry.style || "").qq()}">
<i>Loading ${tag ? `${Renderer.get().render(asTag)}` : entry.displayName || entry.name}...</i>
<style onload="Renderer.events.handleLoad_inlineStatblock(this)"></style>
</td>
</tr>`;
@@ -10494,7 +10531,7 @@ Renderer.recipe = class {
? `{@b {@style Makes|small-caps}} ${ent._scaleFactor ? `${ent._scaleFactor}× ` : ""}${ent.makes}`
: null,
entryServes: ent.serves
? `{@b {@style Serves|small-caps}} ${ent.serves.min ?? ent.serves.exact}${ent.serves.min != null ? " to " : ""}${ent.serves.max ?? ""}`
? `{@b {@style Serves|small-caps}} ${ent.serves.min ?? ent.serves.exact}${ent.serves.min != null ? " to " : ""}${ent.serves.max ?? ""}${ent.serves.note ? ` ${ent.serves.note}` : ""}`
: null,
entryMetasTime: Renderer.recipe._getEntryMetasTime(ent),
entryIngredients: {entries: ent._fullIngredients},
@@ -10680,6 +10717,7 @@ Renderer.recipe = class {
"tablespoon",
"teaspoon",
"wedge",
"fist",
];
static _UNITS_SINGLE_TO_PLURAL_ES = [
"dash",

View File

@@ -113,9 +113,11 @@ export class BrewUtil2Base {
isReloadRequired () { return this._isDirty; }
doLocationReload () {
if (typeof Hist !== "undefined") Hist.doPreLocationReload();
else window.location.hash = "";
doLocationReload ({isRetainHash = false} = {}) {
if (!isRetainHash) {
if (typeof Hist !== "undefined") Hist.doPreLocationReload();
else window.location.hash = "";
}
location.reload();
}
@@ -513,6 +515,8 @@ export class BrewUtil2Base {
pLoadPropIndex (urlRoot) { throw new Error("Unimplemented!"); }
/** @abstract */
pLoadMetaIndex (urlRoot) { throw new Error("Unimplemented!"); }
/** @abstract */
pLoadAdventureBookIdsIndex (urlRoot) { throw new Error("Unimplemented!"); }
async pGetCombinedIndexes () {
const urlRoot = await this.pGetCustomUrl();

View File

@@ -89,6 +89,8 @@ export class BrewUtil2_ extends BrewUtil2Base {
pLoadMetaIndex (urlRoot) { return DataUtil.brew.pLoadMetaIndex(urlRoot); }
pLoadAdventureBookIdsIndex (urlRoot) { return DataUtil.brew.pLoadAdventureBookIdsIndex(urlRoot); }
/* -------------------------------------------- */
// region Editable

View File

@@ -41,6 +41,8 @@ export class PrereleaseUtil_ extends BrewUtil2Base {
pLoadMetaIndex (urlRoot) { return DataUtil.prerelease.pLoadMetaIndex(urlRoot); }
pLoadAdventureBookIdsIndex (urlRoot) { return DataUtil.prerelease.pLoadAdventureBookIdsIndex(urlRoot); }
/* -------------------------------------------- */
// region Editable

View File

@@ -1987,7 +1987,7 @@ class DataLoader {
}
static async pCacheAndGetHash (page, hash, opts) {
const {source} = UrlUtil.autoDecodeHash(hash, {page});
const {source} = await UrlUtil.pAutoDecodeHash(hash, {page});
if (!source) {
if (opts.isRequired) throw new Error(`Could not find entity for page "${page}" with hash "${hash}"`);
return null;

View File

@@ -1907,6 +1907,8 @@ PropOrder._VARIANTRULE = [
"type",
"entries",
"foundryImg",
];
PropOrder._RACE_SUBRACE = [
"page",

View File

@@ -965,7 +965,7 @@ class ListUiUtil {
let elePreviewWrp;
if (item.ele.children.length === 1) {
elePreviewWrp = e_({
ag: "div",
tag: "div",
clazz: "ve-hidden ve-flex",
children: [
e_({tag: "div", clazz: "ve-col-0-5"}),

View File

@@ -2,7 +2,7 @@
// in deployment, `IS_DEPLOYED = "<version number>";` should be set below.
globalThis.IS_DEPLOYED = undefined;
globalThis.VERSION_NUMBER = /* 5ETOOLS_VERSION__OPEN */"1.209.1"/* 5ETOOLS_VERSION__CLOSE */;
globalThis.VERSION_NUMBER = /* 5ETOOLS_VERSION__OPEN */"1.209.2"/* 5ETOOLS_VERSION__CLOSE */;
globalThis.DEPLOYED_IMG_ROOT = undefined;
// for the roll20 script to set
globalThis.IS_VTT = false;
@@ -2812,7 +2812,24 @@ globalThis.UrlUtil = {
return hash.split(HASH_LIST_SEP).map(it => decodeURIComponent(it));
},
/* -------------------------------------------- */
/**
* @param hash
* @param {?string} page
*/
async pAutoDecodeHash (hash, {page = null} = {}) {
page ||= UrlUtil.getCurrentPage();
if ([UrlUtil.PG_ADVENTURE, UrlUtil.PG_BOOK].includes(page)) return UrlUtil._pAutoDecodeHashAdventureBookHash(hash, {page});
return UrlUtil.autoDecodeHash(hash, {page});
},
// TODO(Future) expand
/**
* @param hash
* @param {?string} page
*/
autoDecodeHash (hash, {page = null} = {}) {
page ||= UrlUtil.getCurrentPage();
const parts = UrlUtil.decodeHash(hash.toLowerCase().trim());
@@ -2822,10 +2839,57 @@ globalThis.UrlUtil = {
return {name, pantheon, source};
}
// TODO(Future) this is broken for docs where the id != the source
// consider indexing
// + homebrew
if (page === UrlUtil.PG_ADVENTURE || page === UrlUtil.PG_BOOK) {
const [source] = parts;
return {source};
}
const [name, source] = parts;
return {name, source};
},
/**
* @param hash
* @param {?string} page
*/
async _pAutoDecodeHashAdventureBookHash (hash, {page = null} = {}) {
page ||= UrlUtil.getCurrentPage();
const parts = UrlUtil.decodeHash(hash.toLowerCase().trim());
if (![UrlUtil.PG_ADVENTURE, UrlUtil.PG_BOOK].includes(page)) throw new Error(`Unhandled page "${page}"!`);
const [id] = parts;
for (const {prop, contentsUrl} of [
{
prop: "adventure",
contentsUrl: `${Renderer.get().baseUrl}data/adventures.json`,
},
{
prop: "book",
contentsUrl: `${Renderer.get().baseUrl}data/books.json`,
},
]) {
const contents = await DataUtil.loadJSON(contentsUrl);
const ent = contents[prop].find(it => it.id.toLowerCase() === id);
if (ent) return {name: ent.name, source: ent.source, id: ent.id};
}
for (const brewUtil of [PrereleaseUtil, BrewUtil2]) {
const urlRoot = await brewUtil.pGetCustomUrl();
const idsIndex = await brewUtil.pLoadAdventureBookIdsIndex(urlRoot);
if (idsIndex[id]) return idsIndex[id];
}
return {};
},
/* -------------------------------------------- */
getSluggedHash (hash) {
return Parser.stringToSlug(decodeURIComponent(hash)).replace(/_/g, "-");
},
@@ -3758,6 +3822,11 @@ class _DataUtilBrewHelper {
return DataUtil.loadJSON(`${urlRoot}_generated/index-sources.json`);
}
async pLoadAdventureBookIdsIndex (urlRoot) {
urlRoot = this._getCleanUrlRoot(urlRoot);
return DataUtil.loadJSON(`${urlRoot}_generated/index-adventure-book-ids.json`);
}
getFileUrl (path, urlRoot) {
urlRoot = this._getCleanUrlRoot(urlRoot);
return `${urlRoot}${path}`;