mirror of
https://github.com/Kornstalx/5etools-mirror-2.github.io.git
synced 2025-10-28 20:45:35 -05:00
v1.206.0
This commit is contained in:
@@ -52,7 +52,6 @@ class BackgroundPage extends ListPage {
|
||||
const pageFilter = new PageFilterBackgrounds();
|
||||
super({
|
||||
dataSource: DataUtil.background.loadJSON.bind(DataUtil.background),
|
||||
dataSourceFluff: DataUtil.backgroundFluff.loadJSON.bind(DataUtil.backgroundFluff),
|
||||
|
||||
pFnGetFluff: Renderer.background.pGetFluff.bind(Renderer.background),
|
||||
|
||||
|
||||
@@ -691,7 +691,7 @@ class BestiaryPage extends ListPageMultiSource {
|
||||
) {
|
||||
Renderer.get().setFirstSection(true);
|
||||
|
||||
const $btnScaleCr = !ScaleCreature.isCrInScaleRange(mon) ? null : $(`<button id="btn-scale-cr" title="Scale Creature By CR (Highly Experimental)" class="mon__btn-scale-cr btn btn-xs btn-default ve-popwindow__hidden"><span class="glyphicon glyphicon-signal"></span></button>`)
|
||||
const $btnScaleCr = !ScaleCreature.isCrInScaleRange(mon) ? null : $(`<button id="btn-scale-cr" title="Scale Creature By CR (Highly Experimental)" class="mon__btn-scale-cr btn btn-xs btn-default ve-popwindow__hidden no-print lst-is-exporting-image__hidden"><span class="glyphicon glyphicon-signal"></span></button>`)
|
||||
.click((evt) => {
|
||||
evt.stopPropagation();
|
||||
const win = (evt.view || {}).window;
|
||||
@@ -708,7 +708,7 @@ class BestiaryPage extends ListPageMultiSource {
|
||||
});
|
||||
});
|
||||
|
||||
const $btnResetScaleCr = !ScaleCreature.isCrInScaleRange(mon) ? null : $(`<button id="btn-reset-cr" title="Reset CR Scaling" class="mon__btn-reset-cr btn btn-xs btn-default ve-popwindow__hidden"><span class="glyphicon glyphicon-refresh"></span></button>`)
|
||||
const $btnResetScaleCr = !ScaleCreature.isCrInScaleRange(mon) ? null : $(`<button id="btn-reset-cr" title="Reset CR Scaling" class="mon__btn-reset-cr btn btn-xs btn-default ve-popwindow__hidden no-print lst-is-exporting-image__hidden"><span class="glyphicon glyphicon-refresh"></span></button>`)
|
||||
.click(() => Hist.setSubhash(VeCt.HASH_SCALED, null))
|
||||
.toggle(isScaledCr);
|
||||
|
||||
|
||||
@@ -266,10 +266,10 @@ class BookUtil {
|
||||
BookUtil.curRender.controls.$btnsPrv = BookUtil.curRender.controls.$btnsPrv || [];
|
||||
let $btnPrev;
|
||||
if (BookUtil.referenceId) {
|
||||
$btnPrev = $(`<button class="btn btn-xs btn-default bk__nav-head-foot-item"><span class="glyphicon glyphicon-chevron-left"/>Previous</button>`)
|
||||
$btnPrev = $(`<button class="btn btn-xs btn-default bk__nav-head-foot-item no-print"><span class="glyphicon glyphicon-chevron-left"></span>Previous</button>`)
|
||||
.click(() => this._showBookContent_goToPage({mod: -1, bookId, ixChapter}));
|
||||
} else {
|
||||
$btnPrev = $(`<a href="#${this._showBookContent_goToPage({mod: -1, isGetHref: true, bookId, ixChapter})}" class="btn btn-xs btn-default bk__nav-head-foot-item"><span class="glyphicon glyphicon-chevron-left"/>Previous</a>`)
|
||||
$btnPrev = $(`<a href="#${this._showBookContent_goToPage({mod: -1, isGetHref: true, bookId, ixChapter})}" class="btn btn-xs btn-default bk__nav-head-foot-item no-print"><span class="glyphicon glyphicon-chevron-left"></span>Previous</a>`)
|
||||
.click(() => MiscUtil.scrollPageTop());
|
||||
}
|
||||
$btnPrev
|
||||
@@ -278,7 +278,7 @@ class BookUtil {
|
||||
BookUtil.curRender.controls.$btnsPrv.push($btnPrev);
|
||||
|
||||
(BookUtil.curRender.controls.$divsPrv = BookUtil.curRender.controls.$divsPrv || [])
|
||||
.push($(`<div class="bk__nav-head-foot-item"/>`)
|
||||
.push($(`<div class="bk__nav-head-foot-item no-print"></div>`)
|
||||
.toggle(!showPrev)
|
||||
.appendTo($wrpControls));
|
||||
|
||||
@@ -289,10 +289,10 @@ class BookUtil {
|
||||
BookUtil.curRender.controls.$btnsNxt = BookUtil.curRender.controls.$btnsNxt || [];
|
||||
let $btnNext;
|
||||
if (BookUtil.referenceId) {
|
||||
$btnNext = $(`<button class="btn btn-xs btn-default bk__nav-head-foot-item">Next<span class="glyphicon glyphicon-chevron-right"/></button>`)
|
||||
$btnNext = $(`<button class="btn btn-xs btn-default bk__nav-head-foot-item no-print">Next<span class="glyphicon glyphicon-chevron-right"></span></button>`)
|
||||
.click(() => this._showBookContent_goToPage({mod: 1, bookId, ixChapter}));
|
||||
} else {
|
||||
$btnNext = $(`<a href="#${this._showBookContent_goToPage({mod: 1, isGetHref: true, bookId, ixChapter})}" class="btn btn-xs btn-default bk__nav-head-foot-item">Next<span class="glyphicon glyphicon-chevron-right"/></a>`)
|
||||
$btnNext = $(`<a href="#${this._showBookContent_goToPage({mod: 1, isGetHref: true, bookId, ixChapter})}" class="btn btn-xs btn-default bk__nav-head-foot-item no-print">Next<span class="glyphicon glyphicon-chevron-right"></span></a>`)
|
||||
.click(() => MiscUtil.scrollPageTop());
|
||||
}
|
||||
$btnNext
|
||||
@@ -301,7 +301,7 @@ class BookUtil {
|
||||
BookUtil.curRender.controls.$btnsNxt.push($btnNext);
|
||||
|
||||
(BookUtil.curRender.controls.$divsNxt = BookUtil.curRender.controls.$divsNxt || [])
|
||||
.push($(`<div class="bk__nav-head-foot-item"/>`)
|
||||
.push($(`<div class="bk__nav-head-foot-item no-print"></div>`)
|
||||
.toggle(!showNxt)
|
||||
.appendTo($wrpControls));
|
||||
|
||||
|
||||
@@ -50,7 +50,6 @@ class CharCreationOptionsPage extends ListPage {
|
||||
const pageFilter = new PageFilterCharCreationOptions();
|
||||
super({
|
||||
dataSource: DataUtil.charoption.loadJSON.bind(DataUtil.charoption),
|
||||
dataSourceFluff: DataUtil.charoptionFluff.loadJSON.bind(DataUtil.charoptionFluff),
|
||||
|
||||
pFnGetFluff: Renderer.charoption.pGetFluff.bind(Renderer.charoption),
|
||||
|
||||
|
||||
270
js/classes.js
270
js/classes.js
@@ -65,6 +65,135 @@ class UtilClassesPage {
|
||||
}
|
||||
return "fresh";
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
static _getRenderedClassSubclassFluff (
|
||||
{
|
||||
ent,
|
||||
entFluff,
|
||||
depthArr = null,
|
||||
isRemoveRootName = false,
|
||||
isAddLeadingHr = false,
|
||||
isAddTrailingHr = false,
|
||||
isAddSourceNote = false,
|
||||
},
|
||||
) {
|
||||
entFluff = MiscUtil.copyFast(entFluff);
|
||||
|
||||
const hasEntries = !!entFluff?.entries?.length;
|
||||
const hasImages = !!entFluff?.images?.length;
|
||||
|
||||
let stack = "";
|
||||
Renderer.get().setFirstSection(true);
|
||||
|
||||
if (hasEntries) {
|
||||
const renderer = Renderer.get();
|
||||
|
||||
if (depthArr) renderer.setDepthTracker(depthArr, {additionalPropsInherited: ["_isStandardSource"]});
|
||||
|
||||
entFluff.entries.filter(f => f.source === ent.source).forEach(f => f._isStandardSource = true);
|
||||
|
||||
entFluff.entries.forEach((f, i) => {
|
||||
const cpy = MiscUtil.copyFast(f);
|
||||
|
||||
// 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);
|
||||
});
|
||||
}
|
||||
|
||||
if (hasImages) {
|
||||
if (hasEntries) {
|
||||
stack += `<div class="py-2"></div>`;
|
||||
}
|
||||
|
||||
this._getFluffLayoutImages(entFluff.images)
|
||||
.forEach(ent => stack += Renderer.get().render(ent));
|
||||
}
|
||||
|
||||
if (hasImages || hasEntries) {
|
||||
if (isAddLeadingHr) stack = Renderer.get().render({type: "hr"}) + stack;
|
||||
if (isAddTrailingHr) stack += Renderer.get().render({type: "hr"});
|
||||
}
|
||||
|
||||
return {
|
||||
hasEntries,
|
||||
hasImages,
|
||||
rendered: stack || null,
|
||||
};
|
||||
}
|
||||
|
||||
static getRenderedClassFluff (
|
||||
{
|
||||
cls,
|
||||
clsFluff,
|
||||
depthArr = null,
|
||||
isRemoveRootName = false,
|
||||
isAddTrailingHr = false,
|
||||
},
|
||||
) {
|
||||
return this._getRenderedClassSubclassFluff({
|
||||
ent: cls,
|
||||
entFluff: clsFluff,
|
||||
depthArr,
|
||||
isRemoveRootName,
|
||||
isAddTrailingHr,
|
||||
isAddSourceNote: true,
|
||||
});
|
||||
}
|
||||
|
||||
static getRenderedSubclassFluff (
|
||||
{
|
||||
sc,
|
||||
scFluff,
|
||||
},
|
||||
) {
|
||||
return this._getRenderedClassSubclassFluff({
|
||||
ent: sc,
|
||||
entFluff: scFluff,
|
||||
isAddLeadingHr: true,
|
||||
isAddTrailingHr: true,
|
||||
});
|
||||
}
|
||||
|
||||
static _getFluffLayoutImages (images) {
|
||||
if (images.length === 1) {
|
||||
return [
|
||||
{
|
||||
maxWidth: "98",
|
||||
maxWidthUnits: "%",
|
||||
...images[0],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return {
|
||||
type: "gallery",
|
||||
images: [...images],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class ClassesPage extends MixinComponentGlobalState(MixinBaseComponent(MixinProxyBase(ListPage))) {
|
||||
@@ -228,7 +357,6 @@ class ClassesPage extends MixinComponentGlobalState(MixinBaseComponent(MixinProx
|
||||
|
||||
// Force data on any classes with unusual sources to behave as though they have normal sources
|
||||
if (SourceUtil.isNonstandardSource(cls.source) || PrereleaseUtil.hasSourceJson(cls.source) || BrewUtil2.hasSourceJson(cls.source)) {
|
||||
if (cls.fluff) cls.fluff.filter(f => f.source === cls.source).forEach(f => f._isStandardSource = true);
|
||||
cls.subclasses.filter(sc => sc.source === cls.source).forEach(sc => sc._isStandardSource = true);
|
||||
}
|
||||
|
||||
@@ -668,7 +796,7 @@ class ClassesPage extends MixinComponentGlobalState(MixinBaseComponent(MixinProx
|
||||
this._render_renderClassTable();
|
||||
this._render_renderSidebar();
|
||||
await this._render_pRenderSubclassTabs();
|
||||
this._render_renderClassContent();
|
||||
await this._render_pRenderClassContent();
|
||||
this._render_renderOutline();
|
||||
this._render_renderAltViews();
|
||||
// endregion
|
||||
@@ -694,7 +822,19 @@ class ClassesPage extends MixinComponentGlobalState(MixinBaseComponent(MixinProx
|
||||
this._addHookBase("feature", hkScrollToFeature);
|
||||
hkScrollToFeature();
|
||||
|
||||
const hkDisplayFluff = () => $(`.cls-main__cls-fluff`).toggleVe(!!this._state.isShowFluff);
|
||||
const hkDisplayFluff = () => {
|
||||
$(`.cls-main__cls-fluff`).toggleVe(!!this._state.isShowFluff);
|
||||
|
||||
if (!this._state.isShowFluff) {
|
||||
$(`.cls-main__sc-fluff`).hideVe();
|
||||
} else {
|
||||
$(`.cls-main__sc-fluff`)
|
||||
.each((i, e) => {
|
||||
const $e = $(e);
|
||||
$e.toggleVe(!!this._state[$e.attr("data-subclass-id")]);
|
||||
});
|
||||
}
|
||||
};
|
||||
this._addHookBase("isShowFluff", hkDisplayFluff);
|
||||
MiscUtil.pDefer(hkDisplayFluff);
|
||||
|
||||
@@ -1863,7 +2003,7 @@ class ClassesPage extends MixinComponentGlobalState(MixinBaseComponent(MixinProx
|
||||
// endregion
|
||||
}
|
||||
|
||||
_render_renderClassContent () {
|
||||
async _render_pRenderClassContent () {
|
||||
const $content = $(document.getElementById("pagecontent")).empty();
|
||||
const cls = this.activeClass;
|
||||
this._outlineData = {};
|
||||
@@ -1872,43 +2012,36 @@ class ClassesPage extends MixinComponentGlobalState(MixinBaseComponent(MixinProx
|
||||
|
||||
$content.append(Renderer.utils.getBorderTr());
|
||||
|
||||
if (cls.fluff) {
|
||||
const clsFluff = await Renderer.class.pGetFluff(cls);
|
||||
if (clsFluff) {
|
||||
const depthArr = [];
|
||||
let stack = "";
|
||||
Renderer.get().setFirstSection(true);
|
||||
|
||||
cls.fluff.forEach((f, i) => {
|
||||
const cpy = MiscUtil.copyFast(f);
|
||||
const {hasEntries, rendered} = UtilClassesPage.getRenderedClassFluff({cls, clsFluff, depthArr, isAddTrailingHr: true});
|
||||
|
||||
if (typeof cpy !== "string") {
|
||||
if (f.source && f.source !== cls.source && cpy.entries) cpy.entries.unshift(`{@note The following information is from ${Parser.sourceJsonToFull(f.source)}${Renderer.utils.isDisplayPage(f.page) ? `, page ${f.page}` : ""}.}`);
|
||||
}
|
||||
if (rendered) {
|
||||
const $trFluff = $(`<tr class="cls-main__cls-fluff"><td colspan="6"></td></tr>`).fastSetHtml(rendered).appendTo($content);
|
||||
}
|
||||
|
||||
stack += Renderer.get().setDepthTracker(depthArr, {additionalPropsInherited: ["_isStandardSource"]}).render(cpy);
|
||||
});
|
||||
|
||||
// Add a trailing `<hr>`
|
||||
stack += Renderer.get().render({type: "section"});
|
||||
|
||||
const $trFluff = $(`<tr class="cls-main__cls-fluff"><td colspan="6"/></tr>`).fastSetHtml(stack).appendTo($content);
|
||||
this._trackOutlineFluffData(depthArr);
|
||||
if (hasEntries) this._trackOutlineFluffData(depthArr);
|
||||
}
|
||||
|
||||
const ptrIsFirstSubclassLevel = {_: true};
|
||||
cls.classFeatures.forEach((lvlFeatures, ixLvl) => {
|
||||
const ptrsHasRenderedSubclass = {};
|
||||
await cls.classFeatures.pSerialAwaitMap(async (lvlFeatures, ixLvl) => {
|
||||
const ptrHasHandledSubclassFeatures = {_: false};
|
||||
|
||||
lvlFeatures.forEach((feature, ixFeature) => {
|
||||
await lvlFeatures.pSerialAwaitMap(async (feature, ixFeature) => {
|
||||
if (feature.source === cls.source) {
|
||||
feature = MiscUtil.copyFast(feature);
|
||||
feature._isStandardSource = true;
|
||||
}
|
||||
|
||||
this._render_renderClassContent_renderFeature({
|
||||
await this._render_renderClassContent_pRenderFeature({
|
||||
ixLvl,
|
||||
feature,
|
||||
ixFeature,
|
||||
ptrHasHandledSubclassFeatures,
|
||||
ptrsHasRenderedSubclass,
|
||||
ptrIsFirstSubclassLevel,
|
||||
$content,
|
||||
cls,
|
||||
@@ -1918,10 +2051,11 @@ class ClassesPage extends MixinComponentGlobalState(MixinBaseComponent(MixinProx
|
||||
// If there are out-of-sync subclass features (e.g. Stryxhaven subclasses), add a "fake" feature to compensate
|
||||
if (!ptrHasHandledSubclassFeatures._ && this.constructor._hasSubclassFeaturesAtLevel(cls, ixLvl + 1)) {
|
||||
this.constructor._hasSubclassFeaturesAtLevel(cls, ixLvl + 1);
|
||||
this._render_renderClassContent_renderFeature({
|
||||
await this._render_renderClassContent_pRenderFeature({
|
||||
ixLvl,
|
||||
feature: this.constructor._getFauxGainSubclassFeatureFeature(cls, ixLvl + 1),
|
||||
ixFeature: -1,
|
||||
ptrsHasRenderedSubclass,
|
||||
ptrIsFirstSubclassLevel,
|
||||
$content,
|
||||
cls,
|
||||
@@ -1945,12 +2079,13 @@ class ClassesPage extends MixinComponentGlobalState(MixinBaseComponent(MixinProx
|
||||
.removePlugins("entries_namePrefix");
|
||||
}
|
||||
|
||||
_render_renderClassContent_renderFeature (
|
||||
async _render_renderClassContent_pRenderFeature (
|
||||
{
|
||||
ixLvl,
|
||||
feature,
|
||||
ixFeature,
|
||||
ptrHasHandledSubclassFeatures,
|
||||
ptrsHasRenderedSubclass,
|
||||
ptrIsFirstSubclassLevel,
|
||||
$content,
|
||||
cls,
|
||||
@@ -1987,12 +2122,15 @@ class ClassesPage extends MixinComponentGlobalState(MixinBaseComponent(MixinProx
|
||||
.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.}`]}]}))
|
||||
.appendTo($content);
|
||||
|
||||
cls.subclasses.forEach(sc => {
|
||||
await cls.subclasses.pSerialAwaitMap(async sc => {
|
||||
const stateKey = UrlUtil.getStateKeySubclass(sc);
|
||||
|
||||
const scLvlFeatures = sc.subclassFeatures.find(it => it[0]?.level === ixLvl + 1);
|
||||
if (!scLvlFeatures) return;
|
||||
|
||||
const scFluff = ptrsHasRenderedSubclass[stateKey] ? null : await Renderer.subclass.pGetFluff(sc);
|
||||
ptrsHasRenderedSubclass[stateKey] = true;
|
||||
|
||||
scLvlFeatures.forEach((scFeature, ixScFeature) => {
|
||||
const depthArr = [];
|
||||
|
||||
@@ -2000,7 +2138,8 @@ class ClassesPage extends MixinComponentGlobalState(MixinBaseComponent(MixinProx
|
||||
? Renderer.get().render(`{@note This subclass was published on ${DatetimeUtil.getDateStr({date: new Date(Parser.sourceJsonToDate(sc.source))})}.}`)
|
||||
: "";
|
||||
const ptSources = ptrIsFirstSubclassLevel._ === true && sc.otherSources ? `{@note {@b Subclass source:} ${Renderer.utils.getSourceAndPageHtml(sc)}}` : "";
|
||||
const toRender = (ptDate || ptSources) && scFeature.entries ? MiscUtil.copyFast(scFeature) : scFeature;
|
||||
const toRender = MiscUtil.copyFast(scFeature);
|
||||
|
||||
if (ptDate && toRender.entries) toRender.entries.unshift(ptDate);
|
||||
if (ptSources && toRender.entries) toRender.entries.push(ptSources);
|
||||
|
||||
@@ -2030,7 +2169,7 @@ class ClassesPage extends MixinComponentGlobalState(MixinBaseComponent(MixinProx
|
||||
if (source === sc.source) return {isSkip: true};
|
||||
},
|
||||
fn: () => {
|
||||
const $trSubclassFeature = $(`<tr class="cls-main__sc-feature" data-subclass-id="${UrlUtil.getStateKeySubclass(sc)}"><td colspan="6"/></tr>`)
|
||||
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))
|
||||
.appendTo($content);
|
||||
},
|
||||
@@ -2039,6 +2178,14 @@ class ClassesPage extends MixinComponentGlobalState(MixinBaseComponent(MixinProx
|
||||
Renderer.get().removePlugins("entries_namePrefix");
|
||||
|
||||
this._trackOutlineScData(stateKey, ixLvl + 1, ixScFeature, depthArr);
|
||||
|
||||
const {rendered: rdScFluff} = UtilClassesPage.getRenderedSubclassFluff({sc, scFluff});
|
||||
|
||||
if (!rdScFluff?.length) return;
|
||||
|
||||
$(`<tr class="cls-main__sc-fluff" data-subclass-id="${UrlUtil.getStateKeySubclass(sc)}"><td colspan="6"></td></tr>`)
|
||||
.fastSetHtml(rdScFluff)
|
||||
.appendTo($content);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2369,7 +2516,7 @@ ClassesPage.ClassBookView = class extends BookModeViewBase {
|
||||
const cls = this._classPage.activeClass;
|
||||
|
||||
// Top bar
|
||||
const $pnlMenu = $(`<div class="cls-bkmv__wrp-tabs ve-flex-h-center"/>`).appendTo($wrpContent);
|
||||
const $pnlMenu = $(`<div class="cls-bkmv__wrp-tabs ve-flex-h-center no-print"></div>`).appendTo($wrpContent);
|
||||
|
||||
// Main panel
|
||||
const $tblBook = $(`<table class="w-100 stats stats--book stats--book-large stats--bkmv"></div>`);
|
||||
@@ -2381,23 +2528,18 @@ ClassesPage.ClassBookView = class extends BookModeViewBase {
|
||||
Renderer.get().recursiveRender({type: "section", name: cls.name}, renderStack);
|
||||
renderStack.push(`</td></tr>`);
|
||||
|
||||
renderStack.push(`<tr class="text" data-cls-book-fluff="true"><td colspan="6" class="py-3 px-5">`);
|
||||
const clsFluff = await Renderer.class.pGetFluff(cls);
|
||||
if (clsFluff) {
|
||||
const {hasEntries, rendered} = UtilClassesPage.getRenderedClassFluff({cls, clsFluff, isRemoveRootName: true});
|
||||
|
||||
if (rendered) {
|
||||
renderStack.push(`<tr class="text" data-cls-book-fluff="true"><td colspan="6" class="py-3 px-5">`);
|
||||
renderStack.push(rendered);
|
||||
renderStack.push(`</td></tr>`);
|
||||
}
|
||||
}
|
||||
|
||||
Renderer.get().setFirstSection(true);
|
||||
(cls.fluff || []).forEach((f, i) => {
|
||||
f = MiscUtil.copyFast(f);
|
||||
|
||||
// Remove the name from the first section if it is a copy of the class name
|
||||
if (i === 0 && f.name && f.name.toLowerCase() === cls.name.toLowerCase()) {
|
||||
delete f.name;
|
||||
}
|
||||
|
||||
if (f.source && f.source !== cls.source && f.entries) {
|
||||
f.entries.unshift(`{@note The following information is from ${Parser.sourceJsonToFull(f.source)}${Renderer.utils.isDisplayPage(f.page) ? `, page ${f.page}` : ""}.}`);
|
||||
}
|
||||
|
||||
Renderer.get().recursiveRender(f, renderStack);
|
||||
});
|
||||
renderStack.push(`</td></tr>`);
|
||||
|
||||
renderStack.push(`<tr class="text" data-cls-book-cf="true"><td colspan="6" class="py-3 px-5">`);
|
||||
cls.classFeatures.forEach(lvl => {
|
||||
@@ -2405,14 +2547,26 @@ ClassesPage.ClassBookView = class extends BookModeViewBase {
|
||||
});
|
||||
renderStack.push(`</td></tr>`);
|
||||
|
||||
cls.subclasses
|
||||
await cls.subclasses
|
||||
.filter(sc => !ClassesPage.isSubclassExcluded_(cls, sc))
|
||||
.forEach((sc, ixSubclass) => {
|
||||
renderStack.push(`<tr data-cls-book-sc-ix="${ixSubclass}" class="cls-main__sc-feature"><td colspan="6" class="py-3 px-5">`);
|
||||
sc.subclassFeatures.forEach(lvl => {
|
||||
.pSerialAwaitMap(async (sc, ixSubclass) => {
|
||||
const scFluff = await Renderer.subclass.pGetFluff(sc);
|
||||
|
||||
sc.subclassFeatures.forEach((lvl, ix) => {
|
||||
renderStack.push(`<tr data-cls-book-sc-ix="${ixSubclass}" class="cls-main__sc-feature"><td colspan="6" class="py-3 px-5">`);
|
||||
lvl.forEach(f => Renderer.get().recursiveRender(f, renderStack));
|
||||
renderStack.push(`</td></tr>`);
|
||||
|
||||
if (ix !== 0) return;
|
||||
|
||||
const {rendered: rdScFluff} = UtilClassesPage.getRenderedSubclassFluff({sc, scFluff});
|
||||
|
||||
if (!rdScFluff?.length) return;
|
||||
|
||||
renderStack.push(`<tr data-cls-book-sc-fluff-ix="${ixSubclass}" class="cls-main__sc-fluff"><td colspan="6" class="py-3 px-5">`);
|
||||
renderStack.push(rdScFluff);
|
||||
renderStack.push(`</td></tr>`);
|
||||
});
|
||||
renderStack.push(`</td></tr>`);
|
||||
});
|
||||
renderStack.push(Renderer.utils.getBorderTr());
|
||||
$tblBook.append(renderStack.join(""));
|
||||
@@ -2454,6 +2608,16 @@ ClassesPage.ClassBookView = class extends BookModeViewBase {
|
||||
this._parent.addHook(stateKey, hkShowHide);
|
||||
hkShowHide();
|
||||
|
||||
const hkShowHideFluff = () => {
|
||||
const isActive = !!this._parent.get(stateKey) && !!this._parent.get("isShowFluff");
|
||||
$wrpContent.find(`[data-cls-book-sc-fluff-ix="${i}"]`).toggleVe(!!isActive);
|
||||
};
|
||||
(this._hooks[stateKey] ||= []).push(hkShowHideFluff);
|
||||
this._parent.addHook(stateKey, hkShowHideFluff);
|
||||
(this._hooks["isShowFluff"] ||= []).push(hkShowHideFluff);
|
||||
this._parent.addHook("isShowFluff", hkShowHideFluff);
|
||||
hkShowHideFluff();
|
||||
|
||||
$pnlMenu.append($btnToggleSc);
|
||||
});
|
||||
|
||||
@@ -2463,17 +2627,17 @@ ClassesPage.ClassBookView = class extends BookModeViewBase {
|
||||
$btnToggleCf.toggleClass("cls__btn-cf--active", isActive);
|
||||
$dispFeatures.toggleVe(!!isActive);
|
||||
};
|
||||
(this._hooks["isHideFeatures"] = this._hooks["isHideFeatures"] || []).push(hkFeatures);
|
||||
(this._hooks["isHideFeatures"] ||= []).push(hkFeatures);
|
||||
this._parent.addHook("isHideFeatures", hkFeatures);
|
||||
hkFeatures();
|
||||
|
||||
const hkFluff = () => {
|
||||
const $dispFluff = $wrpContent.find(`[data-cls-book-fluff="true"]`);
|
||||
const isHidden = !this._parent.get("isShowFluff");
|
||||
$btnToggleInfo.toggleVe(!!isHidden);
|
||||
$btnToggleInfo.toggleClass("active", !isHidden);
|
||||
$dispFluff.toggleVe(!isHidden);
|
||||
};
|
||||
(this._hooks["isShowFluff"] = this._hooks["isShowFluff"] || []).push(hkFluff);
|
||||
(this._hooks["isShowFluff"] ||= []).push(hkFluff);
|
||||
this._parent.addHook("isShowFluff", hkFluff);
|
||||
hkFluff();
|
||||
|
||||
|
||||
@@ -198,9 +198,9 @@ class SpellParser extends BaseParser {
|
||||
|
||||
static _addTags (stats, options) {
|
||||
DamageInflictTagger.tryRun(stats, options);
|
||||
DamageResVulnImmuneTagger.tryRun(stats, "damageResist", options);
|
||||
DamageResVulnImmuneTagger.tryRun(stats, "damageImmune", options);
|
||||
DamageResVulnImmuneTagger.tryRun(stats, "damageVulnerable", options);
|
||||
DamageResTagger.tryRun(stats, options);
|
||||
DamageVulnTagger.tryRun(stats, options);
|
||||
DamageImmuneTagger.tryRun(stats, options);
|
||||
ConditionInflictTagger.tryRun(stats, options);
|
||||
SavingThrowTagger.tryRun(stats, options);
|
||||
AbilityCheckTagger.tryRun(stats, options);
|
||||
@@ -405,6 +405,19 @@ class SpellParser extends BaseParser {
|
||||
;
|
||||
}
|
||||
|
||||
static _getComponentCurrencyMult ({mCost}) {
|
||||
const {currency, currencyLong} = mCost.groups;
|
||||
|
||||
if (currency) return Parser.COIN_CONVERSIONS[Parser.COIN_ABVS.indexOf(currency.toLowerCase())];
|
||||
|
||||
switch (currencyLong.toLowerCase()) {
|
||||
case "gold": {
|
||||
return Parser.COIN_CONVERSIONS[Parser.COIN_ABVS.indexOf("gp")];
|
||||
}
|
||||
default: throw new Error("Unimplemented!");
|
||||
}
|
||||
}
|
||||
|
||||
static _setCleanComponents (stats, line, options) {
|
||||
const components = ConvertUtil.getStatblockLineHeaderText({reStartStr: this._RE_START_COMPONENTS, line});
|
||||
const parts = components.split(StrUtil.COMMAS_NOT_IN_PARENTHESES_REGEX);
|
||||
@@ -422,11 +435,11 @@ class SpellParser extends BaseParser {
|
||||
default: {
|
||||
if (lowerPt.startsWith("m ")) {
|
||||
const materialText = pt.replace(/^m\s*\((.*)\)$/i, "$1").trim();
|
||||
const mCost = /(\d*,?\d+)\s?(cp|sp|ep|gp|pp)/gi.exec(materialText);
|
||||
const mCost = /(\d*,?\d+)\s?(?:(?<currency>cp|sp|ep|gp|pp)|(?:(?<currencyLong>gold)(?: pieces)?))/gi.exec(materialText);
|
||||
const isConsumed = pt.toLowerCase().includes("consume");
|
||||
|
||||
if (mCost) {
|
||||
const valueMult = Parser.COIN_CONVERSIONS[Parser.COIN_ABVS.indexOf(mCost[2].toLowerCase())];
|
||||
const valueMult = this._getComponentCurrencyMult({mCost});
|
||||
const valueNum = Number(mCost[1].replace(/,/g, ""));
|
||||
|
||||
stats.components.m = {
|
||||
|
||||
@@ -1,40 +1,67 @@
|
||||
"use strict";
|
||||
|
||||
class DamageTagger {
|
||||
static _addDamageTypeToArray (arr, str, options) {
|
||||
static _addDamageTypeToSet (set, str, options) {
|
||||
str = str.toLowerCase().trim();
|
||||
if (str === "all" || str === "one" || str === "a") arr.push(...Parser.DMG_TYPES);
|
||||
else if (Parser.DMG_TYPES.includes(str)) arr.push(str);
|
||||
if (str === "all" || str === "one" || str === "a") Parser.DMG_TYPES.forEach(it => set.add(it));
|
||||
else if (Parser.DMG_TYPES.includes(str)) set.add(str);
|
||||
else options.cbWarning(`Unknown damage type "${str}"`);
|
||||
}
|
||||
}
|
||||
|
||||
class DamageInflictTagger extends DamageTagger {
|
||||
static tryRun (sp, options) {
|
||||
sp.damageInflict = [];
|
||||
const tags = new Set();
|
||||
|
||||
JSON.stringify([sp.entries, sp.entriesHigherLevel]).replace(/(?:{@damage [^}]+}|\d+) (\w+)((?:, \w+)*)(,? or \w+)? damage/ig, (...m) => {
|
||||
if (m[1]) this._addDamageTypeToArray(sp.damageInflict, m[1], options);
|
||||
if (m[2]) m[2].split(",").map(it => it.trim()).filter(Boolean).forEach(str => this._addDamageTypeToArray(sp.damageInflict, str, options));
|
||||
if (m[3]) this._addDamageTypeToArray(sp.damageInflict, m[3].split(" ").last(), options);
|
||||
if (m[1]) this._addDamageTypeToSet(tags, m[1], options);
|
||||
if (m[2]) m[2].split(",").map(it => it.trim()).filter(Boolean).forEach(str => this._addDamageTypeToSet(tags, str, options));
|
||||
if (m[3]) this._addDamageTypeToSet(tags, m[3].split(" ").last(), options);
|
||||
});
|
||||
if (!sp.damageInflict.length) delete sp.damageInflict;
|
||||
else sp.damageInflict = [...new Set(sp.damageInflict)].sort(SortUtil.ascSort);
|
||||
|
||||
if (!tags.size) return;
|
||||
sp.damageInflict = [...tags].sort(SortUtil.ascSort);
|
||||
}
|
||||
}
|
||||
|
||||
class DamageResVulnImmuneTagger extends DamageTagger {
|
||||
static tryRun (sp, prop, options) {
|
||||
sp[prop] = [];
|
||||
JSON.stringify([sp.entries, sp.entriesHigherLevel]).replace(/resistance to (\w+)((?:, \w+)*)(,? or \w+)? damage/ig, (...m) => {
|
||||
if (m[1]) this._addDamageTypeToArray(sp[prop], m[1], options);
|
||||
if (m[2]) m[2].split(",").map(it => it.trim()).filter(Boolean).forEach(str => this._addDamageTypeToArray(sp[prop], str, options));
|
||||
if (m[3]) this._addDamageTypeToArray(sp[prop], m[3].split(" ").last(), options);
|
||||
static get _RE () {
|
||||
return (this.__RE ||= new RegExp(`${this._TYPE} to (?<ptBase>\\w+)(?<ptList>(?:, \\w+)*)(?<ptConj>,? or \\w+)? damage`, "gi"));
|
||||
}
|
||||
|
||||
static tryRun (sp, options) {
|
||||
const tags = new Set();
|
||||
|
||||
JSON.stringify([sp.entries, sp.entriesHigherLevel]).replace(this._RE, (...m) => {
|
||||
const {ptBase, ptList, ptConj} = m.last();
|
||||
if (ptBase) this._addDamageTypeToSet(tags, ptBase, options);
|
||||
if (ptList) ptList.split(",").map(it => it.trim()).filter(Boolean).forEach(str => this._addDamageTypeToSet(tags, str, options));
|
||||
if (ptConj) this._addDamageTypeToSet(tags, ptConj.split(" ").last(), options);
|
||||
});
|
||||
if (!sp[prop].length) delete sp[prop];
|
||||
else sp[prop] = [...new Set(sp[prop])].sort(SortUtil.ascSort);
|
||||
|
||||
if (!tags.size) return;
|
||||
sp[this._PROP] = [...tags].sort(SortUtil.ascSort);
|
||||
}
|
||||
}
|
||||
|
||||
class DamageResTagger extends DamageResVulnImmuneTagger {
|
||||
static _TYPE = "resistance";
|
||||
static _PROP = "damageResist";
|
||||
}
|
||||
globalThis.DamageResTagger = DamageResTagger;
|
||||
|
||||
class DamageVulnTagger extends DamageResVulnImmuneTagger {
|
||||
static _TYPE = "vulnerability";
|
||||
static _PROP = "damageVulnerable";
|
||||
}
|
||||
globalThis.DamageVulnTagger = DamageVulnTagger;
|
||||
|
||||
class DamageImmuneTagger extends DamageResVulnImmuneTagger {
|
||||
static _TYPE = "immunity";
|
||||
static _PROP = "damageImmune";
|
||||
}
|
||||
globalThis.DamageImmuneTagger = DamageImmuneTagger;
|
||||
|
||||
class ConditionInflictTagger {
|
||||
static tryRun (sp, options) {
|
||||
sp.conditionInflict = [];
|
||||
@@ -438,7 +465,6 @@ class AffectedCreatureTypeTagger {
|
||||
AffectedCreatureTypeTagger._RE_TYPES = new RegExp(`\\b(${[...Parser.MON_TYPES, ...Object.values(Parser.MON_TYPE_TO_PLURAL)].map(it => it.escapeRegexp()).join("|")})\\b`, "gi");
|
||||
|
||||
globalThis.DamageInflictTagger = DamageInflictTagger;
|
||||
globalThis.DamageResVulnImmuneTagger = DamageResVulnImmuneTagger;
|
||||
globalThis.ConditionInflictTagger = ConditionInflictTagger;
|
||||
globalThis.SavingThrowTagger = SavingThrowTagger;
|
||||
globalThis.AbilityCheckTagger = AbilityCheckTagger;
|
||||
|
||||
@@ -55,7 +55,6 @@ class FeatsPage extends ListPage {
|
||||
const pageFilter = new PageFilterFeats();
|
||||
super({
|
||||
dataSource: DataUtil.feat.loadJSON.bind(DataUtil.feat),
|
||||
dataSourceFluff: DataUtil.featFluff.loadJSON.bind(DataUtil.featFluff),
|
||||
|
||||
pFnGetFluff: Renderer.feat.pGetFluff.bind(Renderer.feat),
|
||||
|
||||
|
||||
@@ -93,9 +93,11 @@ class PageFilterClassesBase extends PageFilter {
|
||||
opts = opts || {};
|
||||
const subclassExclusions = opts.subclassExclusions || {};
|
||||
|
||||
// region Sources
|
||||
// Note that we assume that, for fluff from a given source, a class/subclass will exist from that source.
|
||||
// This allows us to skip loading the class/subclass fluff in order to track the fluff's sources.
|
||||
this._sourceFilter.addItem(cls.source);
|
||||
|
||||
if (cls.fluff) cls.fluff.forEach(it => this._addEntrySourcesToFilter(it));
|
||||
cls.classFeatures.forEach(lvlFeatures => lvlFeatures.forEach(feature => this._addEntrySourcesToFilter(feature)));
|
||||
|
||||
cls.subclasses.forEach(sc => {
|
||||
@@ -105,6 +107,7 @@ class PageFilterClassesBase extends PageFilter {
|
||||
sc.subclassFeatures.forEach(lvlFeatures => lvlFeatures.forEach(feature => this._addEntrySourcesToFilter(feature)));
|
||||
}
|
||||
});
|
||||
// endregion
|
||||
}
|
||||
|
||||
async _pPopulateBoxOptions (opts) {
|
||||
|
||||
110
js/listpage.js
110
js/listpage.js
@@ -978,11 +978,6 @@ class ListPage {
|
||||
* @param [opts.prereleaseDataSource] Function to fetch prerelease data.
|
||||
* @param [opts.brewDataSource] Function to fetch brew data.
|
||||
* @param [opts.pFnGetFluff] Function to fetch fluff for a given entity.
|
||||
* @param [opts.dataSourceFluff] Fluff JSON data url or function to fetch fluff data.
|
||||
* @param [opts.filters] Array of filters to use in the filter box. (Either `filters` and `filterSource` or
|
||||
* `pageFilter` must be specified.)
|
||||
* @param [opts.filterSource] Source filter. (Either `filters` and `filterSource` or
|
||||
* `pageFilter` must be specified.)
|
||||
* @param [opts.pageFilter] PageFilter implementation for this page. (Either `filters` and `filterSource` or
|
||||
* `pageFilter` must be specified.)
|
||||
* @param opts.listOptions Other list options.
|
||||
@@ -1010,9 +1005,6 @@ class ListPage {
|
||||
this._prereleaseDataSource = opts.prereleaseDataSource;
|
||||
this._brewDataSource = opts.brewDataSource;
|
||||
this._pFnGetFluff = opts.pFnGetFluff;
|
||||
this._dataSourcefluff = opts.dataSourceFluff;
|
||||
this._filters = opts.filters;
|
||||
this._filterSource = opts.filterSource;
|
||||
this._pageFilter = opts.pageFilter;
|
||||
this._listOptions = opts.listOptions || {};
|
||||
this._dataProps = opts.dataProps;
|
||||
@@ -1034,7 +1026,6 @@ class ListPage {
|
||||
this._dataList = [];
|
||||
this._ixData = 0;
|
||||
this._bookView = null;
|
||||
this._bookViewToShow = null;
|
||||
this._sublistManager = null;
|
||||
this._btnsTabs = {};
|
||||
this._lastRender = {};
|
||||
@@ -1764,6 +1755,11 @@ class ListPage {
|
||||
evt => this._sublistManager.pHandleClick_save(evt),
|
||||
),
|
||||
null,
|
||||
new ContextUtil.Action(
|
||||
"Export as Image (SHIFT to Copy Image)",
|
||||
evt => this._pHandleClick_exportAsImage({evt, isFast: evt.shiftKey, $eleCopyEffect: $btnOptions}),
|
||||
),
|
||||
null,
|
||||
new ContextUtil.Action(
|
||||
"Download Pinned List (SHIFT to Copy Link)",
|
||||
evt => this._sublistManager.pHandleClick_download({isUrl: evt.shiftKey, $eleCopyEffect: $btnOptions}),
|
||||
@@ -1817,8 +1813,15 @@ class ListPage {
|
||||
|
||||
const menu = ContextUtil.getMenu(contextOptions);
|
||||
$btnOptions
|
||||
.off("mousedown")
|
||||
.on("mousedown", evt => {
|
||||
evt.preventDefault();
|
||||
})
|
||||
.off("click")
|
||||
.on("click", evt => ContextUtil.pOpenMenu(evt, menu));
|
||||
.on("click", async evt => {
|
||||
evt.preventDefault();
|
||||
await ContextUtil.pOpenMenu(evt, menu);
|
||||
});
|
||||
}
|
||||
|
||||
async _handleGenericContextMenuClick_pDoMassPopout (evt, ele, selection) {
|
||||
@@ -2011,6 +2014,93 @@ class ListPage {
|
||||
|
||||
/** @abstract */
|
||||
_renderStats_doBuildStatsTab ({ent}) { throw new Error("Unimplemented!"); }
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
static _OFFSET_WINDOW_EXPORT_AS_IMAGE = 17;
|
||||
|
||||
async _pHandleClick_exportAsImage ({evt, isFast, $eleCopyEffect}) {
|
||||
if (typeof domtoimage === "undefined") await import("../lib/dom-to-image-more.min.js");
|
||||
|
||||
const ent = this._dataList[Hist.lastLoadedId];
|
||||
|
||||
const optsDomToImage = {
|
||||
// FIXME(Future) doesn't seem to have the desired effect; `lst__is-exporting-image` bodge used instead
|
||||
adjustClonedNode: (node, clone, isAfter) => {
|
||||
if (node.classList && node.classList.contains("stats-source") && !isAfter) {
|
||||
clone.style.paddingRight = "0px";
|
||||
}
|
||||
return clone;
|
||||
},
|
||||
};
|
||||
|
||||
if (isFast) {
|
||||
let blob;
|
||||
try {
|
||||
this._$pgContent.addClass("lst__is-exporting-image");
|
||||
blob = await domtoimage.toBlob(this._$pgContent[0], optsDomToImage);
|
||||
} finally {
|
||||
this._$pgContent.removeClass("lst__is-exporting-image");
|
||||
}
|
||||
|
||||
const isCopy = await MiscUtil.pCopyBlobToClipboard(blob);
|
||||
if (isCopy) JqueryUtil.showCopiedEffect($eleCopyEffect, "Copied!");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const html = this._$pgContent[0].outerHTML;
|
||||
const page = UrlUtil.getCurrentPage();
|
||||
|
||||
const $cpy = $(html)
|
||||
.addClass("lst__is-exporting-image");
|
||||
$cpy.find();
|
||||
|
||||
const $btnCpy = $(`<button class="btn btn-default btn-xs" title="SHIFT to Copy and Close">Copy</button>`)
|
||||
.on("click", async evt => {
|
||||
const blob = await domtoimage.toBlob($cpy[0], optsDomToImage);
|
||||
const isCopy = await MiscUtil.pCopyBlobToClipboard(blob);
|
||||
if (isCopy) JqueryUtil.showCopiedEffect($btnCpy, "Copied!");
|
||||
|
||||
if (isCopy && evt.shiftKey) hoverWindow.doClose();
|
||||
});
|
||||
|
||||
const $btnSave = $(`<button class="btn btn-default btn-xs" title="SHIFT to Save and Close">Save</button>`)
|
||||
.on("click", async evt => {
|
||||
const dataUrl = await domtoimage.toPng($cpy[0], optsDomToImage);
|
||||
DataUtil.userDownloadDataUrl(`${ent.name}.png`, dataUrl);
|
||||
|
||||
if (evt.shiftKey) hoverWindow.doClose();
|
||||
});
|
||||
|
||||
const width = this._$pgContent[0].getBoundingClientRect().width;
|
||||
const posBtn = $eleCopyEffect[0].getBoundingClientRect().toJSON();
|
||||
const hoverWindow = Renderer.hover.getShowWindow(
|
||||
$$`<div class="ve-flex-col">
|
||||
<div class="split-v-center mb-2 px-2 mt-2">
|
||||
<i class="mr-2">Optionally resize the width of the window, then Copy or Save.</i>
|
||||
<div class="btn-group">
|
||||
${$btnCpy}
|
||||
${$btnSave}
|
||||
</div>
|
||||
</div>
|
||||
${$cpy}
|
||||
</div>`,
|
||||
Renderer.hover.getWindowPositionExact(
|
||||
posBtn.left - width + posBtn.width - this.constructor._OFFSET_WINDOW_EXPORT_AS_IMAGE,
|
||||
posBtn.top + posBtn.height + this.constructor._OFFSET_WINDOW_EXPORT_AS_IMAGE,
|
||||
evt,
|
||||
),
|
||||
{
|
||||
title: `Image Export - ${ent.name}`,
|
||||
isPermanent: true,
|
||||
isBookContent: page === UrlUtil.PG_RECIPES,
|
||||
isResizeOnlyWidth: true,
|
||||
isHideBottomBorder: true,
|
||||
width,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ListPageBookView extends BookModeViewBase {
|
||||
|
||||
@@ -52,7 +52,6 @@ class ObjectsPage extends ListPage {
|
||||
|
||||
super({
|
||||
dataSource: DataUtil.object.loadJSON.bind(DataUtil.object),
|
||||
dataSourceFluff: DataUtil.objectFluff.loadJSON.bind(DataUtil.objectFluff),
|
||||
|
||||
pFnGetFluff,
|
||||
|
||||
|
||||
@@ -647,7 +647,7 @@ class Omnisearch {
|
||||
const $btnToTop = $(`<button class="btn btn-sm btn-default" title="To Top"><span class="glyphicon glyphicon-arrow-up"/></button>`)
|
||||
.click(() => MiscUtil.scrollPageTop());
|
||||
|
||||
const $wrpTop = $$`<div class="bk__to-top">
|
||||
const $wrpTop = $$`<div class="bk__to-top no-print">
|
||||
${$btnToTop}
|
||||
</div>`.appendTo(document.body);
|
||||
|
||||
|
||||
@@ -77,7 +77,6 @@ class OptionalFeaturesPage extends ListPage {
|
||||
|
||||
super({
|
||||
dataSource: DataUtil.optionalfeature.loadJSON.bind(DataUtil.optionalfeature),
|
||||
dataSourceFluff: DataUtil.featFluff.loadJSON.bind(DataUtil.featFluff),
|
||||
|
||||
pFnGetFluff: Renderer.optionalfeature.pGetFluff.bind(Renderer.optionalfeature),
|
||||
|
||||
|
||||
@@ -1453,6 +1453,7 @@ Parser.SP_MISC_TAG_TO_FULL = {
|
||||
AAD: "Additional Attack Damage",
|
||||
OBJ: "Affects Objects",
|
||||
ADV: "Grants Advantage",
|
||||
PIR: "Permanent If Repeated",
|
||||
};
|
||||
Parser.spMiscTagToFull = function (type) {
|
||||
return Parser._parse_aToB(Parser.SP_MISC_TAG_TO_FULL, type);
|
||||
|
||||
@@ -59,7 +59,6 @@ class RacesPage extends ListPage {
|
||||
const pageFilter = new PageFilterRaces();
|
||||
super({
|
||||
dataSource: DataUtil.race.loadJSON.bind(DataUtil.race, {isAddBaseRaces: true}),
|
||||
dataSourceFluff: DataUtil.raceFluff.loadJSON.bind(DataUtil.raceFluff),
|
||||
prereleaseDataSource: DataUtil.race.loadPrerelease.bind(DataUtil.race),
|
||||
brewDataSource: DataUtil.race.loadBrew.bind(DataUtil.race),
|
||||
|
||||
|
||||
160
js/render.js
160
js/render.js
@@ -934,11 +934,11 @@ globalThis.Renderer = function () {
|
||||
};
|
||||
|
||||
this._getPtExpandCollapse = function () {
|
||||
return `<span class="rd__h-toggle ml-2 clickable no-select" data-rd-h-toggle-button="true" title="Toggle Visibility (CTRL to Toggle All)">[\u2013]</span>`;
|
||||
return `<span class="rd__h-toggle ml-2 clickable no-select no-print lst-is-exporting-image__hidden" data-rd-h-toggle-button="true" title="Toggle Visibility (CTRL to Toggle All)">[\u2013]</span>`;
|
||||
};
|
||||
|
||||
this._getPtExpandCollapseSpecial = function () {
|
||||
return `<span class="rd__h-toggle ml-2 clickable no-select" data-rd-h-special-toggle-button="true" title="Toggle Visibility (CTRL to Toggle All)">[\u2013]</span>`;
|
||||
return `<span class="rd__h-toggle ml-2 clickable no-select no-print lst-is-exporting-image__hidden" data-rd-h-special-toggle-button="true" title="Toggle Visibility (CTRL to Toggle All)">[\u2013]</span>`;
|
||||
};
|
||||
|
||||
this._renderInset = function (entry, textStack, meta, options) {
|
||||
@@ -1887,6 +1887,20 @@ globalThis.Renderer = function () {
|
||||
|
||||
break;
|
||||
}
|
||||
case "@5etoolsImg": {
|
||||
const [displayText, page] = Renderer.splitTagByPipe(text);
|
||||
const fauxEntry = {
|
||||
type: "link",
|
||||
href: {
|
||||
type: "external",
|
||||
url: UrlUtil.link(this.getMediaUrl("img", page)),
|
||||
},
|
||||
text: displayText,
|
||||
};
|
||||
this._recursiveRender(fauxEntry, textStack, meta);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// OTHER HOVERABLES ////////////////////////////////////////////////////////////////////////////////
|
||||
case "@footnote": {
|
||||
@@ -2817,7 +2831,7 @@ Renderer.utils = class {
|
||||
let pageLinkPart;
|
||||
if (opts.page) {
|
||||
const hash = UrlUtil.URL_TO_HASH_BUILDER[opts.page](it);
|
||||
dataPart = `data-page="${opts.page}" data-source="${it.source.escapeQuotes()}" data-hash="${hash.escapeQuotes()}" ${opts.extensionData != null ? `data-extension='${JSON.stringify(opts.extensionData).escapeQuotes()}` : ""}'`;
|
||||
dataPart = `data-page="${opts.page}" data-source="${it.source.escapeQuotes()}" data-hash="${hash.escapeQuotes()}" ${opts.extensionData != null ? `data-extension='${JSON.stringify(opts.extensionData).escapeQuotes()}'` : ""}`;
|
||||
pageLinkPart = SourceUtil.getAdventureBookSourceHref(it.source, it.page);
|
||||
|
||||
// Enable Rivet import for entities embedded in entries
|
||||
@@ -3122,28 +3136,31 @@ Renderer.utils = class {
|
||||
return fluff;
|
||||
}
|
||||
|
||||
static async pGetFluff ({entity, pFnPostProcess, fnGetFluffData, fluffUrl, fluffBaseUrl, fluffProp} = {}) {
|
||||
let predefinedFluff = await Renderer.utils.pGetPredefinedFluff(entity, fluffProp);
|
||||
static async _pGetFluff ({entity, fluffProp} = {}) {
|
||||
const fluffEntity = await DataLoader.pCacheAndGet(fluffProp, entity.source, UrlUtil.URL_TO_HASH_BUILDER[fluffProp](entity));
|
||||
if (fluffEntity) return fluffEntity;
|
||||
|
||||
if (entity._versionBase_name && entity._versionBase_source) {
|
||||
return DataLoader.pCacheAndGet(fluffProp, entity.source, UrlUtil.URL_TO_HASH_BUILDER[fluffProp]({
|
||||
name: entity._versionBase_name,
|
||||
source: entity._versionBase_source,
|
||||
}));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static async pGetFluff ({entity, pFnPostProcess, fluffProp} = {}) {
|
||||
const predefinedFluff = await Renderer.utils.pGetPredefinedFluff(entity, fluffProp);
|
||||
if (predefinedFluff) {
|
||||
if (pFnPostProcess) predefinedFluff = await pFnPostProcess(predefinedFluff);
|
||||
if (pFnPostProcess) return pFnPostProcess(predefinedFluff);
|
||||
return predefinedFluff;
|
||||
}
|
||||
if (!fnGetFluffData && !fluffBaseUrl && !fluffUrl) return null;
|
||||
|
||||
const fluffIndex = fluffBaseUrl ? await DataUtil.loadJSON(`${Renderer.get().baseUrl}${fluffBaseUrl}fluff-index.json`) : null;
|
||||
if (fluffIndex && !fluffIndex[entity.source]) return null;
|
||||
|
||||
const data = fnGetFluffData ? await fnGetFluffData() : fluffIndex && fluffIndex[entity.source]
|
||||
? await DataUtil.loadJSON(`${Renderer.get().baseUrl}${fluffBaseUrl}${fluffIndex[entity.source]}`)
|
||||
: await DataUtil.loadJSON(`${Renderer.get().baseUrl}${fluffUrl}`);
|
||||
if (!data) return null;
|
||||
|
||||
let fluff = (data[fluffProp] || []).find(it => it.name === entity.name && it.source === entity.source);
|
||||
if (!fluff && entity._versionBase_name && entity._versionBase_source) fluff = (data[fluffProp] || []).find(it => it.name === entity._versionBase_name && it.source === entity._versionBase_source);
|
||||
const fluff = await Renderer.utils._pGetFluff({entity, fluffProp});
|
||||
if (!fluff) return null;
|
||||
|
||||
// Avoid modifying the original object
|
||||
if (pFnPostProcess) fluff = await pFnPostProcess(fluff);
|
||||
if (pFnPostProcess) return pFnPostProcess(fluff);
|
||||
return fluff;
|
||||
}
|
||||
|
||||
@@ -4612,6 +4629,10 @@ Renderer.tag = class {
|
||||
tagName = "5etools";
|
||||
};
|
||||
|
||||
static Tag5etoolsImg = class extends this._TagPipedNoDisplayText {
|
||||
tagName = "5etoolsImg";
|
||||
};
|
||||
|
||||
static TagAdventure = class extends this._TagPipedNoDisplayText {
|
||||
tagName = "adventure";
|
||||
};
|
||||
@@ -5010,6 +5031,7 @@ Renderer.tag = class {
|
||||
new this.TagCoinflip(),
|
||||
|
||||
new this.Tag5etools(),
|
||||
new this.Tag5etoolsImg(),
|
||||
new this.TagAdventure(),
|
||||
new this.TagBook(),
|
||||
new this.TagFilter(),
|
||||
@@ -5361,7 +5383,6 @@ Renderer.feat = class {
|
||||
static pGetFluff (feat) {
|
||||
return Renderer.utils.pGetFluff({
|
||||
entity: feat,
|
||||
fnGetFluffData: DataUtil.featFluff.loadJSON.bind(DataUtil.featFluff),
|
||||
fluffProp: "featFluff",
|
||||
});
|
||||
}
|
||||
@@ -5508,20 +5529,51 @@ Renderer.class = class {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
static pGetFluff (cls) {
|
||||
// Handle legacy/deprecated class fluff
|
||||
// TODO(Future) remove this after ~July 2024
|
||||
if (cls.fluff instanceof Array) {
|
||||
cls = {...cls};
|
||||
cls.fluff = {entries: cls.fluff};
|
||||
}
|
||||
|
||||
return Renderer.utils.pGetFluff({
|
||||
entity: cls,
|
||||
fluffProp: "classFluff",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Renderer.subclass = class {
|
||||
static getCompactRenderedString (sc) {
|
||||
const entries = MiscUtil.copyFast((sc.subclassFeatures || []).flat());
|
||||
if (entries[0]?.name === sc.name) delete entries[0].name;
|
||||
|
||||
const scEntry = {
|
||||
type: "section",
|
||||
name: sc.name,
|
||||
source: sc.source,
|
||||
page: sc.page,
|
||||
entries: MiscUtil.copyFast((sc.subclassFeatures || []).flat()),
|
||||
entries,
|
||||
};
|
||||
|
||||
return Renderer.hover.getGenericCompactRenderedString(scEntry);
|
||||
}
|
||||
|
||||
static pGetFluff (sc) {
|
||||
return Renderer.utils.pGetFluff({
|
||||
entity: sc,
|
||||
fluffProp: "subclassFluff",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Renderer.classSubclass = class {
|
||||
static pGetFluff (clsOrSc) {
|
||||
if (clsOrSc.__prop === "subclass") return Renderer.subclass.pGetFluff(clsOrSc);
|
||||
return Renderer.class.pGetFluff(clsOrSc);
|
||||
}
|
||||
};
|
||||
|
||||
Renderer.spell = class {
|
||||
@@ -6083,7 +6135,6 @@ Renderer.spell = class {
|
||||
static pGetFluff (sp) {
|
||||
return Renderer.utils.pGetFluff({
|
||||
entity: sp,
|
||||
fluffBaseUrl: `data/spells/`,
|
||||
fluffProp: "spellFluff",
|
||||
});
|
||||
}
|
||||
@@ -6111,7 +6162,6 @@ Renderer.condition = class {
|
||||
static pGetFluff (it) {
|
||||
return Renderer.utils.pGetFluff({
|
||||
entity: it,
|
||||
fnGetFluffData: it.__prop === "condition" ? DataUtil.conditionFluff.loadJSON.bind(DataUtil.conditionFluff) : null,
|
||||
fluffProp: it.__prop === "condition" ? "conditionFluff" : "diseaseFluff",
|
||||
});
|
||||
}
|
||||
@@ -6131,7 +6181,6 @@ Renderer.background = class {
|
||||
static pGetFluff (bg) {
|
||||
return Renderer.utils.pGetFluff({
|
||||
entity: bg,
|
||||
fnGetFluffData: DataUtil.backgroundFluff.loadJSON.bind(DataUtil.backgroundFluff),
|
||||
fluffProp: "backgroundFluff",
|
||||
});
|
||||
}
|
||||
@@ -6219,7 +6268,6 @@ Renderer.optionalfeature = class {
|
||||
static pGetFluff (ent) {
|
||||
return Renderer.utils.pGetFluff({
|
||||
entity: ent,
|
||||
fnGetFluffData: DataUtil.optionalfeatureFluff.loadJSON.bind(DataUtil.optionalfeatureFluff),
|
||||
fluffProp: "optionalfeatureFluff",
|
||||
});
|
||||
}
|
||||
@@ -6259,7 +6307,6 @@ Renderer.reward = class {
|
||||
static pGetFluff (ent) {
|
||||
return Renderer.utils.pGetFluff({
|
||||
entity: ent,
|
||||
fnGetFluffData: DataUtil.rewardFluff.loadJSON.bind(DataUtil.rewardFluff),
|
||||
fluffProp: "rewardFluff",
|
||||
});
|
||||
}
|
||||
@@ -6766,7 +6813,6 @@ Renderer.race = class {
|
||||
static pGetFluff (race) {
|
||||
return Renderer.utils.pGetFluff({
|
||||
entity: race,
|
||||
fnGetFluffData: DataUtil.raceFluff.loadJSON.bind(DataUtil.raceFluff),
|
||||
fluffProp: "raceFluff",
|
||||
});
|
||||
}
|
||||
@@ -6952,7 +6998,6 @@ Renderer.object = class {
|
||||
static pGetFluff (obj) {
|
||||
return Renderer.utils.pGetFluff({
|
||||
entity: obj,
|
||||
fnGetFluffData: DataUtil.objectFluff.loadJSON.bind(DataUtil.objectFluff),
|
||||
fluffProp: "objectFluff",
|
||||
});
|
||||
}
|
||||
@@ -7087,7 +7132,6 @@ Renderer.traphazard = class {
|
||||
static pGetFluff (ent) {
|
||||
return Renderer.utils.pGetFluff({
|
||||
entity: ent,
|
||||
fnGetFluffData: ent.__prop === "trap" ? DataUtil.trapFluff.loadJSON.bind(DataUtil.trapFluff) : DataUtil.hazardFluff.loadJSON.bind(DataUtil.hazardFluff),
|
||||
fluffProp: ent.__prop === "trap" ? "trapFluff" : "hazardFluff",
|
||||
});
|
||||
}
|
||||
@@ -7529,7 +7573,7 @@ Renderer.monster = class {
|
||||
|
||||
return e_({
|
||||
tag: "select",
|
||||
clazz: "input-xs form-control form-control--minimal w-initial inline-block ve-popwindow__hidden",
|
||||
clazz: "input-xs form-control form-control--minimal w-initial inline-block ve-popwindow__hidden no-print lst-is-exporting-image__hidden",
|
||||
name: "mon__sel-summon-spell-level",
|
||||
children: [
|
||||
e_({tag: "option", val: "-1", text: "\u2014"}),
|
||||
@@ -7547,7 +7591,7 @@ Renderer.monster = class {
|
||||
|
||||
return e_({
|
||||
tag: "select",
|
||||
clazz: "input-xs form-control form-control--minimal w-initial inline-block ve-popwindow__hidden",
|
||||
clazz: "input-xs form-control form-control--minimal w-initial inline-block ve-popwindow__hidden no-print lst-is-exporting-image__hidden",
|
||||
name: "mon__sel-summon-class-level",
|
||||
children: [
|
||||
e_({tag: "option", val: "-1", text: "\u2014"}),
|
||||
@@ -7659,12 +7703,12 @@ Renderer.monster = class {
|
||||
ptCrSpellLevel = `<td colspan="2">
|
||||
${Parser.monCrToFull(mon.cr, {isMythic: !!mon.mythic})}
|
||||
${opts.isShowScalers && !opts.isScaledCr && Parser.isValidCr(mon.cr ? (mon.cr.cr || mon.cr) : null) ? `
|
||||
<button title="Scale Creature By CR (Highly Experimental)" class="mon__btn-scale-cr btn btn-xs btn-default">
|
||||
<button title="Scale Creature By CR (Highly Experimental)" class="mon__btn-scale-cr btn btn-xs btn-default no-print">
|
||||
<span class="glyphicon glyphicon-signal"></span>
|
||||
</button>
|
||||
` : ""}
|
||||
${opts.isScaledCr ? `
|
||||
<button title="Reset CR Scaling" class="mon__btn-reset-cr btn btn-xs btn-default">
|
||||
<button title="Reset CR Scaling" class="mon__btn-reset-cr btn btn-xs btn-default no-print">
|
||||
<span class="glyphicon glyphicon-refresh"></span>
|
||||
</button>
|
||||
` : ""}
|
||||
@@ -7978,8 +8022,7 @@ Renderer.monster = class {
|
||||
static pGetFluff (mon) {
|
||||
return Renderer.utils.pGetFluff({
|
||||
entity: mon,
|
||||
pFnPostProcess: Renderer.monster.postProcessFluff.bind(null, mon),
|
||||
fluffBaseUrl: `data/bestiary/`,
|
||||
pFnPostProcess: Renderer.monster.postProcessFluff.bind(Renderer.monster, mon),
|
||||
fluffProp: "monsterFluff",
|
||||
});
|
||||
}
|
||||
@@ -9413,7 +9456,6 @@ Renderer.item = class {
|
||||
static pGetFluff (item) {
|
||||
return Renderer.utils.pGetFluff({
|
||||
entity: item,
|
||||
fnGetFluffData: DataUtil.itemFluff.loadJSON.bind(DataUtil.itemFluff),
|
||||
fluffProp: "itemFluff",
|
||||
});
|
||||
}
|
||||
@@ -10140,7 +10182,6 @@ Renderer.vehicle = class {
|
||||
static pGetFluff (veh) {
|
||||
return Renderer.utils.pGetFluff({
|
||||
entity: veh,
|
||||
fnGetFluffData: DataUtil.vehicleFluff.loadJSON.bind(DataUtil.vehicleFluff),
|
||||
fluffProp: "vehicleFluff",
|
||||
});
|
||||
}
|
||||
@@ -10226,7 +10267,6 @@ Renderer.language = class {
|
||||
static pGetFluff (it) {
|
||||
return Renderer.utils.pGetFluff({
|
||||
entity: it,
|
||||
fnGetFluffData: DataUtil.languageFluff.loadJSON.bind(DataUtil.languageFluff),
|
||||
fluffProp: "languageFluff",
|
||||
});
|
||||
}
|
||||
@@ -10370,7 +10410,6 @@ Renderer.charoption = class {
|
||||
static pGetFluff (it) {
|
||||
return Renderer.utils.pGetFluff({
|
||||
entity: it,
|
||||
fnGetFluffData: DataUtil.charoptionFluff.loadJSON.bind(DataUtil.charoptionFluff),
|
||||
fluffProp: "charoptionFluff",
|
||||
});
|
||||
}
|
||||
@@ -10490,7 +10529,6 @@ Renderer.recipe = class {
|
||||
static pGetFluff (it) {
|
||||
return Renderer.utils.pGetFluff({
|
||||
entity: it,
|
||||
fnGetFluffData: DataUtil.recipeFluff.loadJSON.bind(DataUtil.recipeFluff),
|
||||
fluffProp: "recipeFluff",
|
||||
});
|
||||
}
|
||||
@@ -11651,9 +11689,14 @@ Renderer.hover = class {
|
||||
* @param [opts.isPopout] If the window should be immediately popped out.
|
||||
* @param [opts.compactReferenceData] Reference (e.g. page/source/hash/others) which can be used to load the contents into the DM screen.
|
||||
* @param [opts.sourceData] Source JSON (as raw as possible) used to construct this popout.
|
||||
* @param [opts.isResizeOnlyWidth]
|
||||
* @param [opts.isHideBottomBorder]
|
||||
*/
|
||||
static getShowWindow ($content, position, opts) {
|
||||
opts = opts || {};
|
||||
const {isHideBottomBorder, isResizeOnlyWidth} = opts;
|
||||
|
||||
if (isHideBottomBorder && !isResizeOnlyWidth) throw new Error(`"isHideBottomBorder" option requires "isResizeOnlyWidth"!`);
|
||||
|
||||
Renderer.hover._doInit();
|
||||
|
||||
@@ -11680,31 +11723,40 @@ Renderer.hover = class {
|
||||
const drag = {};
|
||||
|
||||
const $brdrTopRightResize = $(`<div class="hoverborder__resize-ne"></div>`)
|
||||
.on("mousedown touchstart", (evt) => Renderer.hover._getShowWindow_handleDragMousedown({hoverWindow, hoverId, $hov, drag, $wrpContent}, {evt, type: 1}));
|
||||
.on("mousedown touchstart", (evt) => Renderer.hover._getShowWindow_handleDragMousedown({hoverWindow, hoverId, $hov, drag, $wrpContent}, {evt, type: 1, isResizeOnlyWidth}));
|
||||
if (isResizeOnlyWidth) $brdrTopRightResize.hideVe();
|
||||
|
||||
const $brdrRightResize = $(`<div class="hoverborder__resize-e"></div>`)
|
||||
.on("mousedown touchstart", (evt) => Renderer.hover._getShowWindow_handleDragMousedown({hoverWindow, hoverId, $hov, drag, $wrpContent}, {evt, type: 2}));
|
||||
.on("mousedown touchstart", (evt) => Renderer.hover._getShowWindow_handleDragMousedown({hoverWindow, hoverId, $hov, drag, $wrpContent}, {evt, type: 2, isResizeOnlyWidth}));
|
||||
|
||||
const $brdrBottomRightResize = $(`<div class="hoverborder__resize-se"></div>`)
|
||||
.on("mousedown touchstart", (evt) => Renderer.hover._getShowWindow_handleDragMousedown({hoverWindow, hoverId, $hov, drag, $wrpContent}, {evt, type: 3}));
|
||||
.on("mousedown touchstart", (evt) => Renderer.hover._getShowWindow_handleDragMousedown({hoverWindow, hoverId, $hov, drag, $wrpContent}, {evt, type: 3, isResizeOnlyWidth}));
|
||||
if (isResizeOnlyWidth) $brdrBottomRightResize.hideVe();
|
||||
|
||||
const $brdrBtm = $(`<div class="hoverborder hoverborder--btm ${opts.isBookContent ? "hoverborder-book" : ""}"><div class="hoverborder__resize-s"></div></div>`)
|
||||
.on("mousedown touchstart", (evt) => Renderer.hover._getShowWindow_handleDragMousedown({hoverWindow, hoverId, $hov, drag, $wrpContent}, {evt, type: 4}));
|
||||
const $brdrBtm = $(`<div class="hoverborder hoverborder--btm ${opts.isBookContent ? "hoverborder-book" : ""}"></div>`);
|
||||
const $brdrBtmResize = $(`<div class="hoverborder__resize-s"></div>`)
|
||||
.on("mousedown touchstart", (evt) => Renderer.hover._getShowWindow_handleDragMousedown({hoverWindow, hoverId, $hov, drag, $wrpContent}, {evt, type: 4, isResizeOnlyWidth}))
|
||||
.appendTo($brdrBtm);
|
||||
if (isResizeOnlyWidth) $brdrBtmResize.hideVe();
|
||||
if (isHideBottomBorder) $brdrBtm.hideVe();
|
||||
|
||||
const $brdrBtmLeftResize = $(`<div class="hoverborder__resize-sw"></div>`)
|
||||
.on("mousedown touchstart", (evt) => Renderer.hover._getShowWindow_handleDragMousedown({hoverWindow, hoverId, $hov, drag, $wrpContent}, {evt, type: 5}));
|
||||
.on("mousedown touchstart", (evt) => Renderer.hover._getShowWindow_handleDragMousedown({hoverWindow, hoverId, $hov, drag, $wrpContent}, {evt, type: 5, isResizeOnlyWidth}));
|
||||
if (isResizeOnlyWidth) $brdrBtmLeftResize.hideVe();
|
||||
|
||||
const $brdrLeftResize = $(`<div class="hoverborder__resize-w"></div>`)
|
||||
.on("mousedown touchstart", (evt) => Renderer.hover._getShowWindow_handleDragMousedown({hoverWindow, hoverId, $hov, drag, $wrpContent}, {evt, type: 6}));
|
||||
.on("mousedown touchstart", (evt) => Renderer.hover._getShowWindow_handleDragMousedown({hoverWindow, hoverId, $hov, drag, $wrpContent}, {evt, type: 6, isResizeOnlyWidth}));
|
||||
|
||||
const $brdrTopLeftResize = $(`<div class="hoverborder__resize-nw"></div>`)
|
||||
.on("mousedown touchstart", (evt) => Renderer.hover._getShowWindow_handleDragMousedown({hoverWindow, hoverId, $hov, drag, $wrpContent}, {evt, type: 7}));
|
||||
.on("mousedown touchstart", (evt) => Renderer.hover._getShowWindow_handleDragMousedown({hoverWindow, hoverId, $hov, drag, $wrpContent}, {evt, type: 7, isResizeOnlyWidth}));
|
||||
if (isResizeOnlyWidth) $brdrTopLeftResize.hideVe();
|
||||
|
||||
const $brdrTopResize = $(`<div class="hoverborder__resize-n"></div>`)
|
||||
.on("mousedown touchstart", (evt) => Renderer.hover._getShowWindow_handleDragMousedown({hoverWindow, hoverId, $hov, drag, $wrpContent}, {evt, type: 8}));
|
||||
.on("mousedown touchstart", (evt) => Renderer.hover._getShowWindow_handleDragMousedown({hoverWindow, hoverId, $hov, drag, $wrpContent}, {evt, type: 8, isResizeOnlyWidth}));
|
||||
if (isResizeOnlyWidth) $brdrTopResize.hideVe();
|
||||
|
||||
const $brdrTop = $(`<div class="hoverborder hoverborder--top ${opts.isBookContent ? "hoverborder-book" : ""}" ${opts.isPermanent ? `data-perm="true"` : ""}></div>`)
|
||||
.on("mousedown touchstart", (evt) => Renderer.hover._getShowWindow_handleDragMousedown({hoverWindow, hoverId, $hov, drag, $wrpContent}, {evt, type: 9}))
|
||||
.on("mousedown touchstart", (evt) => Renderer.hover._getShowWindow_handleDragMousedown({hoverWindow, hoverId, $hov, drag, $wrpContent}, {evt, type: 9, isResizeOnlyWidth}))
|
||||
.on("contextmenu", (evt) => {
|
||||
Renderer.hover._contextMenuLastClicked = {
|
||||
hoverId,
|
||||
@@ -11883,7 +11935,7 @@ Renderer.hover = class {
|
||||
if (opts.cbClose) opts.cbClose(hoverWindow);
|
||||
}
|
||||
|
||||
static _getShowWindow_handleDragMousedown ({hoverWindow, hoverId, $hov, drag, $wrpContent}, {evt, type}) {
|
||||
static _getShowWindow_handleDragMousedown ({hoverWindow, hoverId, $hov, drag, $wrpContent}, {evt, type, isResizeOnlyWidth}) {
|
||||
if (evt.which === 0 || evt.which === 1) evt.preventDefault();
|
||||
hoverWindow.zIndex = Renderer.hover._getNextZIndex(hoverId);
|
||||
$hov.css({
|
||||
@@ -11895,11 +11947,11 @@ Renderer.hover = class {
|
||||
drag.startY = EventUtil.getClientY(evt);
|
||||
drag.baseTop = parseFloat($hov.css("top"));
|
||||
drag.baseLeft = parseFloat($hov.css("left"));
|
||||
drag.baseHeight = $wrpContent.height();
|
||||
if (!isResizeOnlyWidth) drag.baseHeight = $wrpContent.height();
|
||||
drag.baseWidth = parseFloat($hov.css("width"));
|
||||
if (type < 9) {
|
||||
$wrpContent.css({
|
||||
"height": drag.baseHeight,
|
||||
...(isResizeOnlyWidth ? {} : {"height": drag.baseHeight}),
|
||||
"max-height": "initial",
|
||||
});
|
||||
$hov.css("max-width", "initial");
|
||||
@@ -12372,6 +12424,8 @@ Renderer.hover = class {
|
||||
case UrlUtil.PG_VEHICLES: return Renderer.vehicle.pGetFluff;
|
||||
case UrlUtil.PG_CHAR_CREATION_OPTIONS: return Renderer.charoption.pGetFluff;
|
||||
case UrlUtil.PG_RECIPES: return Renderer.recipe.pGetFluff;
|
||||
case UrlUtil.PG_TRAPS_HAZARDS: return Renderer.traphazard.pGetFluff;
|
||||
case UrlUtil.PG_CLASSES: return Renderer.classSubclass.pGetFluff;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,6 @@ class RewardsPage extends ListPage {
|
||||
const pageFilter = new PageFilterRewards();
|
||||
super({
|
||||
dataSource: DataUtil.reward.loadJSON.bind(DataUtil.reward),
|
||||
dataSourceFluff: DataUtil.rewardFluff.loadJSON.bind(DataUtil.rewardFluff),
|
||||
|
||||
pFnGetFluff: Renderer.reward.pGetFluff.bind(Renderer.feat),
|
||||
|
||||
|
||||
@@ -1062,6 +1062,28 @@ class _DataTypeLoaderCustomSpellFluff extends _DataTypeLoaderMultiSource {
|
||||
_prop = "spellFluff";
|
||||
}
|
||||
|
||||
class _DataTypeLoaderClassSubclassFluff extends _DataTypeLoaderMultiSource {
|
||||
static PROPS = ["classFluff", "subclassFluff"];
|
||||
static PAGE = UrlUtil.PG_CLASSES;
|
||||
static IS_FLUFF = true;
|
||||
|
||||
_getSiteIdent ({pageClean, sourceClean}) {
|
||||
// use `.toString()` in case `sourceClean` is a `Symbol`
|
||||
return `${this.constructor.PROPS.join("__")}__${sourceClean.toString()}`;
|
||||
}
|
||||
|
||||
async _pGetSiteData ({pageClean, sourceClean}) {
|
||||
return this._pGetSiteDataAll();
|
||||
}
|
||||
|
||||
async _pGetSiteDataAll () {
|
||||
const jsons = await this.constructor.PROPS.pMap(prop => DataUtil[prop].loadJSON());
|
||||
const out = {};
|
||||
jsons.forEach(json => Object.assign(out, {...json}));
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
/** @abstract */
|
||||
class _DataTypeLoaderCustomRawable extends _DataTypeLoader {
|
||||
static _PROPS_RAWABLE;
|
||||
@@ -1670,6 +1692,7 @@ class DataLoader {
|
||||
_DataTypeLoaderCustomMonsterFluff.register({fnRegister});
|
||||
_DataTypeLoaderCustomSpell.register({fnRegister});
|
||||
_DataTypeLoaderCustomSpellFluff.register({fnRegister});
|
||||
_DataTypeLoaderClassSubclassFluff.register({fnRegister});
|
||||
// endregion
|
||||
|
||||
// region Predefined
|
||||
|
||||
@@ -722,6 +722,9 @@ PropOrder._CLASS = [
|
||||
|
||||
"subclassTitle",
|
||||
|
||||
"hasFluff",
|
||||
"hasFluffImages",
|
||||
|
||||
"fluff",
|
||||
|
||||
"foundrySystem",
|
||||
@@ -787,6 +790,11 @@ PropOrder._SUBCLASS = [
|
||||
"subclassTableGroups",
|
||||
"subclassFeatures",
|
||||
|
||||
"hasFluff",
|
||||
"hasFluffImages",
|
||||
|
||||
"fluff",
|
||||
|
||||
"foundrySystem",
|
||||
"foundryFlags",
|
||||
"foundryAdvancement",
|
||||
@@ -799,6 +807,7 @@ PropOrder._SUBCLASS__COPY_MOD = [
|
||||
];
|
||||
PropOrder._SUBCLASS_FLUFF = [
|
||||
"name",
|
||||
"shortName",
|
||||
"source",
|
||||
"className",
|
||||
"classSource",
|
||||
|
||||
104
js/utils.js
104
js/utils.js
@@ -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.205.0"/* 5ETOOLS_VERSION__CLOSE */;
|
||||
globalThis.VERSION_NUMBER = /* 5ETOOLS_VERSION__OPEN */"1.206.0"/* 5ETOOLS_VERSION__CLOSE */;
|
||||
globalThis.DEPLOYED_IMG_ROOT = undefined;
|
||||
// for the roll20 script to set
|
||||
globalThis.IS_VTT = false;
|
||||
@@ -1342,6 +1342,34 @@ globalThis.MiscUtil = class {
|
||||
} else doCompatibilityCopy();
|
||||
}
|
||||
|
||||
static async pCopyBlobToClipboard (blob) {
|
||||
if (!navigator?.permissions) {
|
||||
JqueryUtil.doToast({type: "danger", content: `Could not access clipboard!`});
|
||||
return false;
|
||||
}
|
||||
|
||||
const access = await navigator.permissions.query({name: "clipboard-write"});
|
||||
if (!["granted", "prompt"].includes(access.state)) {
|
||||
JqueryUtil.doToast({type: "danger", content: `Could not access clipboard!`});
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await navigator.clipboard.write([
|
||||
new ClipboardItem({[blob.type]: blob}),
|
||||
]);
|
||||
return true;
|
||||
} catch (e) {
|
||||
if (e.message.includes("Document is not focused")) {
|
||||
JqueryUtil.doToast({type: "danger", content: `Please focus the window first!`});
|
||||
return false;
|
||||
}
|
||||
|
||||
JqueryUtil.doToast({type: "danger", content: `Failed to copy! ${VeCt.STR_SEE_CONSOLE}`});
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
static checkProperty (object, ...path) {
|
||||
for (let i = 0; i < path.length; ++i) {
|
||||
object = object[path[i]];
|
||||
@@ -2428,6 +2456,9 @@ globalThis.ContextUtil = {
|
||||
const result = await this.fnAction(evt, {userData: menu.userData});
|
||||
if (menu.resolveResult_) menu.resolveResult_(result);
|
||||
})
|
||||
.on("mousedown", evt => {
|
||||
evt.preventDefault();
|
||||
})
|
||||
.keydown(evt => {
|
||||
if (evt.key !== "Enter") return;
|
||||
$btnAction.click();
|
||||
@@ -2451,6 +2482,9 @@ globalThis.ContextUtil = {
|
||||
|
||||
const result = await this.fnActionAlt(evt, {userData: menu.userData});
|
||||
if (menu.resolveResult_) menu.resolveResult_(result);
|
||||
})
|
||||
.on("mousedown", evt => {
|
||||
evt.preventDefault();
|
||||
});
|
||||
if (this.titleAlt) $btnActionAlt.title(this.titleAlt);
|
||||
|
||||
@@ -2585,6 +2619,9 @@ globalThis.ContextUtil = {
|
||||
);
|
||||
|
||||
menu.close();
|
||||
})
|
||||
.on("mousedown", evt => {
|
||||
evt.preventDefault();
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -3414,6 +3451,26 @@ globalThis.SortUtil = {
|
||||
},
|
||||
};
|
||||
|
||||
globalThis.MultiSourceUtil = class {
|
||||
static getIndexKey (prop, ent) {
|
||||
switch (prop) {
|
||||
case "class":
|
||||
case "classFluff":
|
||||
return (ent.name || "").toLowerCase().split(" ").at(-1);
|
||||
case "subclass":
|
||||
case "subclassFluff":
|
||||
return (ent.className || "").toLowerCase().split(" ").at(-1);
|
||||
default:
|
||||
return ent.source;
|
||||
}
|
||||
}
|
||||
|
||||
static isEntityIndexKeyMatch (indexKey, prop, ent) {
|
||||
if (indexKey == null) return true;
|
||||
return indexKey === MultiSourceUtil.getIndexKey(prop, ent);
|
||||
}
|
||||
};
|
||||
|
||||
// JSON LOADING ========================================================================================================
|
||||
class _DataUtilPropConfig {
|
||||
static _MERGE_REQUIRES_PRESERVE = {};
|
||||
@@ -3452,7 +3509,7 @@ class _DataUtilPropConfigMultiSource extends _DataUtilPropConfig {
|
||||
|
||||
static async pLoadAll () {
|
||||
const json = await this.loadJSON();
|
||||
return json[this._PROP];
|
||||
return json[this._PROP] || [];
|
||||
}
|
||||
|
||||
static async loadJSON () { return this._loadJSON({isUnmerged: false}); }
|
||||
@@ -3462,7 +3519,7 @@ class _DataUtilPropConfigMultiSource extends _DataUtilPropConfig {
|
||||
const index = await this.pLoadIndex();
|
||||
|
||||
const allData = await Object.entries(index)
|
||||
.pMap(async ([source, file]) => this._pLoadSourceEntities({source, isUnmerged, file}));
|
||||
.pMap(async ([indexKey, file]) => this._pLoadSourceEntities({indexKey, isUnmerged, file}));
|
||||
|
||||
return {[this._PROP]: allData.flat()};
|
||||
}
|
||||
@@ -3473,16 +3530,16 @@ class _DataUtilPropConfigMultiSource extends _DataUtilPropConfig {
|
||||
const file = index[source];
|
||||
if (!file) return null;
|
||||
|
||||
return {[this._PROP]: await this._pLoadSourceEntities({source, file})};
|
||||
return {[this._PROP]: await this._pLoadSourceEntities({indexKey: source, file})};
|
||||
}
|
||||
|
||||
static async _pLoadSourceEntities ({source, isUnmerged = false, file}) {
|
||||
static async _pLoadSourceEntities ({indexKey = null, isUnmerged = false, file}) {
|
||||
await this._pInitPreData();
|
||||
|
||||
const fnLoad = isUnmerged ? DataUtil.loadRawJSON.bind(DataUtil) : DataUtil.loadJSON.bind(DataUtil);
|
||||
|
||||
let data = await fnLoad(`${Renderer.get().baseUrl}data/${this._DIR}/${file}`);
|
||||
data = data[this._PROP].filter(it => it.source === source);
|
||||
data = (data[this._PROP] || []).filter(MultiSourceUtil.isEntityIndexKeyMatch.bind(this, indexKey, this._PROP));
|
||||
|
||||
if (!this._IS_MUT_ENTITIES) return data;
|
||||
|
||||
@@ -3783,12 +3840,17 @@ globalThis.DataUtil = {
|
||||
},
|
||||
|
||||
_userDownload (filename, data, mimeType) {
|
||||
const a = document.createElement("a");
|
||||
const t = new Blob([data], {type: mimeType});
|
||||
a.href = window.URL.createObjectURL(t);
|
||||
const dataUrl = window.URL.createObjectURL(t);
|
||||
DataUtil.userDownloadDataUrl(filename, dataUrl);
|
||||
setTimeout(() => window.URL.revokeObjectURL(dataUrl), 100);
|
||||
},
|
||||
|
||||
userDownloadDataUrl (filename, dataUrl) {
|
||||
const a = document.createElement("a");
|
||||
a.href = dataUrl;
|
||||
a.download = filename;
|
||||
a.dispatchEvent(new MouseEvent("click", {bubbles: true, cancelable: true, view: window}));
|
||||
setTimeout(() => window.URL.revokeObjectURL(a.href), 100);
|
||||
},
|
||||
|
||||
/** Always returns an array of files, even in "single" mode. */
|
||||
@@ -3901,7 +3963,9 @@ globalThis.DataUtil = {
|
||||
"spell": "spells",
|
||||
"spellFluff": "spells",
|
||||
"class": "class",
|
||||
"classFluff": "class",
|
||||
"subclass": "class",
|
||||
"subclassFluff": "class",
|
||||
"classFeature": "class",
|
||||
"subclassFeature": "class",
|
||||
},
|
||||
@@ -3929,7 +3993,9 @@ globalThis.DataUtil = {
|
||||
|
||||
// region Multi-source
|
||||
case "class":
|
||||
case "classFluff":
|
||||
case "subclass":
|
||||
case "subclassFluff":
|
||||
case "classFeature":
|
||||
case "subclassFeature": {
|
||||
const baseUrlPart = `${Renderer.get().baseUrl}data/${DataUtil._MULTI_SOURCE_PROP_TO_DIR[prop]}`;
|
||||
@@ -5623,7 +5689,6 @@ globalThis.DataUtil = {
|
||||
for (const r of recipes) {
|
||||
const fluff = await Renderer.utils.pGetFluff({
|
||||
entity: r,
|
||||
fnGetFluffData: DataUtil.recipeFluff.loadJSON.bind(DataUtil.recipeFluff),
|
||||
fluffProp: "recipeFluff",
|
||||
});
|
||||
|
||||
@@ -5839,8 +5904,25 @@ globalThis.DataUtil = {
|
||||
// endregion
|
||||
},
|
||||
|
||||
subclass: class extends _DataUtilPropConfig {
|
||||
classFluff: class extends _DataUtilPropConfigMultiSource {
|
||||
static _PAGE = UrlUtil.PG_CLASSES;
|
||||
static _DIR = "class";
|
||||
static _PROP = "classFluff";
|
||||
},
|
||||
|
||||
subclass: class extends _DataUtilPropConfigCustom {
|
||||
static _PAGE = "subclass";
|
||||
static _PROP = "subclassFluff";
|
||||
|
||||
static async loadJSON () {
|
||||
return DataUtil.class.loadJSON();
|
||||
}
|
||||
},
|
||||
|
||||
subclassFluff: class extends _DataUtilPropConfigMultiSource {
|
||||
static _PAGE = "subclassFluff";
|
||||
static _DIR = "class";
|
||||
static _PROP = "subclassFluff";
|
||||
},
|
||||
|
||||
deity: class extends _DataUtilPropConfigSingleSource {
|
||||
|
||||
@@ -54,7 +54,6 @@ class VehiclesPage extends ListPage {
|
||||
|
||||
super({
|
||||
dataSource: "data/vehicles.json",
|
||||
dataSourceFluff: "data/fluff-vehicles.json",
|
||||
|
||||
pFnGetFluff,
|
||||
|
||||
|
||||
Reference in New Issue
Block a user