mirror of
https://github.com/Kornstalx/5etools-mirror-2.github.io.git
synced 2025-10-28 20:45:35 -05:00
v1.199.0
This commit is contained in:
@@ -821,8 +821,7 @@ class BestiaryPage extends ListPageMultiSource {
|
||||
|
||||
const $floatToken = this._$dispToken.empty();
|
||||
|
||||
const hasToken = mon.tokenUrl || mon.hasToken;
|
||||
if (!hasToken) return;
|
||||
if (!Renderer.monster.hasToken(mon)) return;
|
||||
|
||||
const imgLink = Renderer.monster.getTokenUrl(mon);
|
||||
const $img = $(`<img src="${imgLink}" class="mon__token" alt="Token Image: ${(mon.name || "").qq()}" ${mon.tokenCredit ? `title="Credit: ${mon.tokenCredit.qq()}"` : ""} loading="lazy">`);
|
||||
@@ -844,8 +843,9 @@ class BestiaryPage extends ListPageMultiSource {
|
||||
|
||||
const buildEle = (meta) => {
|
||||
if (!meta.$ele) {
|
||||
const imgLink = Renderer.monster.getTokenUrl({name: meta.name, source: meta.source, tokenUrl: meta.tokenUrl});
|
||||
const $img = $(`<img src="${imgLink}" class="mon__token" alt="Token Image: ${(meta.displayName || meta.name || "").qq()}" ${meta.tokenCredit ? `title="Credit: ${meta.tokenCredit.qq()}"` : ""} loading="lazy">`)
|
||||
const imgLink = Renderer.monster.getTokenUrl(meta);
|
||||
const displayName = Renderer.monster.getAltArtDisplayName(meta);
|
||||
const $img = $(`<img src="${imgLink}" class="mon__token" alt="Token Image${displayName ? `: ${displayName.qq()}` : ""}}" ${meta.tokenCredit ? `title="Credit: ${meta.tokenCredit.qq()}"` : ""} loading="lazy">`)
|
||||
.on("error", () => {
|
||||
$img.attr(
|
||||
"src",
|
||||
@@ -897,8 +897,7 @@ class BestiaryPage extends ListPageMultiSource {
|
||||
meta.$ele.show();
|
||||
setTimeout(() => meta.$ele.css("max-width", ""), 10); // hack to clear the earlier 100% width
|
||||
|
||||
if (meta.name && meta.source) $footer.html(Renderer.monster.getRenderedAltArtEntry(meta));
|
||||
else $footer.html("");
|
||||
$footer.html(Renderer.monster.getRenderedAltArtEntry(meta));
|
||||
|
||||
$wrpFooter.detach().appendTo(meta.$ele);
|
||||
$btnLeft.detach().appendTo(meta.$ele);
|
||||
|
||||
@@ -205,8 +205,7 @@ export class EncounterBuilderUiBestiary extends EncounterBuilderUi {
|
||||
}
|
||||
|
||||
static getTokenHoverMeta (mon) {
|
||||
const hasToken = mon.tokenUrl || mon.hasToken;
|
||||
if (!hasToken) return null;
|
||||
if (!Renderer.monster.hasToken(mon)) return null;
|
||||
|
||||
return Renderer.hover.getMakePredefinedHover(
|
||||
{
|
||||
|
||||
@@ -1635,7 +1635,7 @@ class CreatureParser extends BaseParser {
|
||||
spl.forEach(it => {
|
||||
const m = /^(?<abil>\w+)\s*(?<sign>[-+])\s*(?<save>\d+)(?<plusPb>(?:\s+plus\s+|\s*\+\s*)PB)?$/i.exec(it);
|
||||
if (m) {
|
||||
out[m.groups.abil] = `${m.groups.sign}${m.groups.save}${m.groups.plusPb ? m.groups.plusPb.replace(/\bpb\b/gi, "PB") : ""}`;
|
||||
out[m.groups.abil.slice(0, 3)] = `${m.groups.sign}${m.groups.save}${m.groups.plusPb ? m.groups.plusPb.replace(/\bpb\b/gi, "PB") : ""}`;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,15 @@ class SpellParser extends BaseParser {
|
||||
static _RE_START_DURATION = "Duration";
|
||||
static _RE_START_CLASS = "Class(?:es)?";
|
||||
|
||||
static _REQUIRED_PROPS = [
|
||||
"level",
|
||||
"school",
|
||||
"time",
|
||||
"range",
|
||||
"duration",
|
||||
"entries",
|
||||
];
|
||||
|
||||
/**
|
||||
* Parses spells from raw text pastes
|
||||
* @param inText Input text.
|
||||
@@ -117,6 +126,9 @@ class SpellParser extends BaseParser {
|
||||
|
||||
const statsOut = this._getFinalState(spell, options);
|
||||
|
||||
const missingProps = this._REQUIRED_PROPS.filter(prop => !statsOut[prop]);
|
||||
if (missingProps.length) options.cbWarning(`${statsOut.name ? `(${statsOut.name}) ` : ""}Missing properties: ${missingProps.join(", ")}`);
|
||||
|
||||
options.cbOutput(statsOut, options.isAppend);
|
||||
}
|
||||
|
||||
@@ -445,12 +457,12 @@ class SpellParser extends BaseParser {
|
||||
if (dur.toLowerCase() === "special") return stats.duration = [{type: "special"}];
|
||||
if (dur.toLowerCase() === "permanent") return stats.duration = [{type: "permanent"}];
|
||||
|
||||
const mConcOrUpTo = /^(concentration, )?up to (\d+|an?) (hour|minute|turn|round|week|month|day|year)(?:s)?$/i.exec(dur);
|
||||
const mConcOrUpTo = /^(?<conc>concentration, )?up to (?<amount>\d+|an?) (?<unit>hour|minute|turn|round|week|month|day|year)(?:s)?$/i.exec(dur);
|
||||
if (mConcOrUpTo) {
|
||||
const amount = mConcOrUpTo[2].toLowerCase().startsWith("a") ? 1 : Number(mConcOrUpTo[2]);
|
||||
const out = {type: "timed", duration: {type: this._getCleanTimeUnit(mConcOrUpTo[3], true, options), amount}, concentration: true};
|
||||
if (mConcOrUpTo[1]) out.concentration = true;
|
||||
else out.upTo = true;
|
||||
const amount = mConcOrUpTo.groups.amount.toLowerCase().startsWith("a") ? 1 : Number(mConcOrUpTo.groups.amount);
|
||||
const out = {type: "timed", duration: {type: this._getCleanTimeUnit(mConcOrUpTo.groups.unit, true, options), amount}};
|
||||
if (mConcOrUpTo.groups.conc) out.concentration = true;
|
||||
else out.duration.upTo = true;
|
||||
return stats.duration = [out];
|
||||
}
|
||||
|
||||
|
||||
102
js/converter.js
102
js/converter.js
@@ -938,6 +938,9 @@ class ConverterUi extends BaseComponent {
|
||||
this.saveSettingsDebounced = MiscUtil.debounce(() => StorageUtil.pSetForPage(ConverterUi.STORAGE_STATE, this.getBaseSaveableState()), 50);
|
||||
|
||||
this._addHookAll("state", () => this.saveSettingsDebounced());
|
||||
|
||||
this.__meta = this._getDefaultMetaState();
|
||||
this._meta = this._getProxy("meta", this.__meta);
|
||||
}
|
||||
|
||||
set converters (converters) { this._converters = converters; }
|
||||
@@ -996,6 +999,7 @@ class ConverterUi extends BaseComponent {
|
||||
JqueryUtil.doToast({type: "warning", content: "Enabled editing. Note that edits will be overwritten as you parse new stat blocks."});
|
||||
});
|
||||
|
||||
let hovWindowPreview = null;
|
||||
$(`#preview`)
|
||||
.on("click", async evt => {
|
||||
const metaCurr = this._getCurrentEntities();
|
||||
@@ -1018,15 +1022,25 @@ class ConverterUi extends BaseComponent {
|
||||
};
|
||||
});
|
||||
|
||||
Renderer.hover.getShowWindow(
|
||||
Renderer.hover.$getHoverContent_generic({
|
||||
type: "entries",
|
||||
entries,
|
||||
}),
|
||||
const $content = Renderer.hover.$getHoverContent_generic({
|
||||
type: "entries",
|
||||
entries,
|
||||
});
|
||||
|
||||
if (hovWindowPreview) {
|
||||
hovWindowPreview.$setContent($content);
|
||||
return;
|
||||
}
|
||||
|
||||
hovWindowPreview = Renderer.hover.getShowWindow(
|
||||
$content,
|
||||
Renderer.hover.getWindowPositionFromEvent(evt),
|
||||
{
|
||||
title: "Preview",
|
||||
isPermanent: true,
|
||||
cbClose: () => {
|
||||
hovWindowPreview = null;
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -1172,16 +1186,12 @@ class ConverterUi extends BaseComponent {
|
||||
*/
|
||||
const catchErrors = async (pToRun) => {
|
||||
try {
|
||||
$(`#lastWarnings`).hide().html("");
|
||||
$(`#lastError`).hide().html("");
|
||||
this._editorOut.resize();
|
||||
this._proxyAssignSimple("meta", this._getDefaultMetaState());
|
||||
await pToRun();
|
||||
} catch (x) {
|
||||
const splitStack = x.stack.split("\n");
|
||||
const atPos = splitStack.length > 1 ? splitStack[1].trim() : "(Unknown location)";
|
||||
const message = `[Error] ${x.message} ${atPos}`;
|
||||
$(`#lastError`).show().html(message);
|
||||
this._editorOut.resize();
|
||||
this._meta.errors = [...this._meta.errors, `${x.message} ${atPos}`];
|
||||
setTimeout(() => { throw x; });
|
||||
}
|
||||
};
|
||||
@@ -1197,7 +1207,10 @@ class ConverterUi extends BaseComponent {
|
||||
const chunks = (this._state.inputSeparator
|
||||
? this.inText.split(this._state.inputSeparator)
|
||||
: [this.inText]).map(it => it.trim()).filter(Boolean);
|
||||
if (!chunks.length) return this.showWarning("No input!");
|
||||
if (!chunks.length) {
|
||||
this._meta.warnings = [...this._meta.warnings, "No input!"];
|
||||
return;
|
||||
}
|
||||
|
||||
chunks
|
||||
.reverse() // reverse as the append is actually a prepend
|
||||
@@ -1205,7 +1218,7 @@ class ConverterUi extends BaseComponent {
|
||||
this.activeConverter.handleParse(
|
||||
chunk,
|
||||
this.doCleanAndOutput.bind(this),
|
||||
this.showWarning.bind(this),
|
||||
(warning) => this._meta.warnings = [...this._meta.warnings, warning],
|
||||
isAppend || i !== 0, // always clear the output for the first non-append chunk, then append
|
||||
);
|
||||
});
|
||||
@@ -1217,9 +1230,60 @@ class ConverterUi extends BaseComponent {
|
||||
|
||||
this.initSideMenu();
|
||||
|
||||
this._pInit_dispErrorsWarnings();
|
||||
|
||||
window.dispatchEvent(new Event("toolsLoaded"));
|
||||
}
|
||||
|
||||
_pInit_dispErrorsWarnings () {
|
||||
const $stgErrors = $(`#lastError`);
|
||||
const $stgWarnings = $(`#lastWarnings`);
|
||||
|
||||
const getRow = ({prefix, text, prop}) => {
|
||||
const $btnClose = $(`<button class="btn btn-danger btn-xs w-24p" title="Dismiss ${prefix} (SHIFT to Dismiss All)">×</button>`)
|
||||
.on("click", evt => {
|
||||
if (evt.shiftKey) {
|
||||
this._meta[prop] = [];
|
||||
return;
|
||||
}
|
||||
|
||||
const ix = this._meta[prop].indexOf(text);
|
||||
if (!~ix) return;
|
||||
this._meta[prop].splice(ix, 1);
|
||||
this._meta[prop] = [...this._meta[prop]];
|
||||
});
|
||||
|
||||
return $$`<div class="split-v-center py-1">
|
||||
<div>[${prefix}] ${text}</div>
|
||||
${$btnClose}
|
||||
</div>`;
|
||||
};
|
||||
|
||||
this._addHook("meta", "errors", () => {
|
||||
$stgErrors.toggleVe(this._meta.errors.length);
|
||||
$stgErrors.empty();
|
||||
this._meta.errors
|
||||
.forEach(it => {
|
||||
getRow({prefix: "Error", text: it, prop: "errors"})
|
||||
.appendTo($stgErrors);
|
||||
});
|
||||
})();
|
||||
|
||||
this._addHook("meta", "warnings", () => {
|
||||
$stgWarnings.toggleVe(this._meta.warnings.length);
|
||||
$stgWarnings.empty();
|
||||
this._meta.warnings
|
||||
.forEach(it => {
|
||||
getRow({prefix: "Warning", text: it, prop: "warnings"})
|
||||
.appendTo($stgWarnings);
|
||||
});
|
||||
})();
|
||||
|
||||
const hkResize = () => this._editorOut.resize();
|
||||
this._addHook("meta", "errors", hkResize);
|
||||
this._addHook("meta", "warnings", hkResize);
|
||||
}
|
||||
|
||||
_getCurrentEntities () {
|
||||
const output = this._outText;
|
||||
|
||||
@@ -1285,11 +1349,6 @@ class ConverterUi extends BaseComponent {
|
||||
hkMode();
|
||||
}
|
||||
|
||||
showWarning (text) {
|
||||
$(`#lastWarnings`).show().append(`<div>[Warning] ${text}</div>`);
|
||||
this._editorOut.resize();
|
||||
}
|
||||
|
||||
doCleanAndOutput (obj, append) {
|
||||
const asCleanString = CleanUtil.getCleanJson(obj, {isFast: false});
|
||||
if (append) {
|
||||
@@ -1312,6 +1371,13 @@ class ConverterUi extends BaseComponent {
|
||||
set inText (text) { this._editorIn.setValue(text, -1); }
|
||||
|
||||
_getDefaultState () { return MiscUtil.copy(ConverterUi._DEFAULT_STATE); }
|
||||
|
||||
_getDefaultMetaState () {
|
||||
return {
|
||||
errors: [],
|
||||
warnings: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
ConverterUi.STORAGE_INPUT = "converterInput";
|
||||
ConverterUi.STORAGE_STATE = "converterState";
|
||||
|
||||
@@ -1697,7 +1697,24 @@ globalThis.SpeedConvert = SpeedConvert;
|
||||
|
||||
class DetectNamedCreature {
|
||||
static tryRun (mon) {
|
||||
if (this._tryRun_nickname(mon)) return;
|
||||
this._tryRun_heuristic(mon);
|
||||
}
|
||||
|
||||
static _tryRun_nickname (mon) {
|
||||
if (
|
||||
/^[^"]+ "[^"]+" [^"]+/.test(mon.name)
|
||||
|| /^[^']+ '[^']+' [^']+/.test(mon.name)
|
||||
) {
|
||||
mon.isNamedCreature = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static _tryRun_heuristic (mon) {
|
||||
const totals = {yes: 0, no: 0};
|
||||
|
||||
this._doCheckProp(mon, totals, "trait");
|
||||
this._doCheckProp(mon, totals, "spellcasting");
|
||||
this._doCheckProp(mon, totals, "action");
|
||||
@@ -1707,6 +1724,8 @@ class DetectNamedCreature {
|
||||
this._doCheckProp(mon, totals, "mythic");
|
||||
|
||||
if (totals.yes && totals.yes > totals.no) mon.isNamedCreature = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static _doCheckProp (mon, totals, prop) {
|
||||
|
||||
@@ -136,7 +136,15 @@ class DmMapperRoot extends BaseComponent {
|
||||
|
||||
$parent.append(`<div class="ve-flex-vh-center w-100 h-100"><i class="dnd-font ve-muted">Loading...</i></div>`);
|
||||
|
||||
RenderMap.$pGetRendered(this._state)
|
||||
RenderMap.$pGetRendered(
|
||||
this._state,
|
||||
{
|
||||
fnGetContainerDimensions: () => {
|
||||
const bcr = $parent[0].getBoundingClientRect();
|
||||
return {w: bcr.width, h: bcr.height};
|
||||
},
|
||||
},
|
||||
)
|
||||
.then($ele => $parent.empty().append($ele));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ import {InitiativeTrackerStatColumnFactory} from "./dmscreen-initiativetracker-s
|
||||
|
||||
class _ConvertedEncounter {
|
||||
constructor () {
|
||||
this.isOverwriteStatsCols = false;
|
||||
|
||||
this.isStatsAddColumns = false;
|
||||
|
||||
this.statsCols = [];
|
||||
@@ -52,7 +54,10 @@ export class InitiativeTrackerEncounterConverter {
|
||||
|
||||
const colNameIndex = {};
|
||||
encounterInfo.colsExtraAdvanced = encounterInfo.colsExtraAdvanced || [];
|
||||
if (encounterInfo.colsExtraAdvanced.length) out.isStatsAddColumns = true;
|
||||
if (encounterInfo.colsExtraAdvanced.length) {
|
||||
out.isOverwriteStatsCols = true;
|
||||
out.isStatsAddColumns = true;
|
||||
}
|
||||
|
||||
encounterInfo.colsExtraAdvanced.forEach((col, i) => colNameIndex[i] = (col?.name || "").toLowerCase());
|
||||
|
||||
|
||||
@@ -56,7 +56,8 @@ export class InitiativeTracker extends BaseComponent {
|
||||
|
||||
this._render_bindSortDirHooks();
|
||||
|
||||
const $wrpTracker = $(`<div class="dm-init dm__panel-bg dm__data-anchor"></div>`);
|
||||
const $wrpTracker = $(`<div class="dm-init dm__panel-bg dm__data-anchor"></div>`)
|
||||
.on("drop", evt => this._pDoHandleImportDrop(evt.originalEvent));
|
||||
|
||||
const sendStateToClientsDebounced = MiscUtil.debounce(
|
||||
() => {
|
||||
@@ -450,12 +451,6 @@ export class InitiativeTracker extends BaseComponent {
|
||||
.filter(({id}) => !idsDefaultParty.has(id));
|
||||
|
||||
const stateNxt = {
|
||||
// region TODO(DMS) not ideal--merge columns instead? Note that this also clobbers default party info
|
||||
isStatsAddColumns: nxtState.isStatsAddColumns,
|
||||
statsCols: nxtState.statsCols
|
||||
.map(it => it.getAsStateData()),
|
||||
// endregion
|
||||
|
||||
rows: this._state.importIsAppend
|
||||
? [
|
||||
...rowsPrevNonDefaultParty,
|
||||
@@ -468,6 +463,54 @@ export class InitiativeTracker extends BaseComponent {
|
||||
],
|
||||
};
|
||||
|
||||
if (nxtState.isOverwriteStatsCols) {
|
||||
const userVal = await InputUiUtil.pGetUserGenericButton({
|
||||
title: "Overwrite Additional Columns",
|
||||
buttons: [
|
||||
new InputUiUtil.GenericButtonInfo({
|
||||
text: "Yes",
|
||||
clazzIcon: "glyphicon glyphicon-ok",
|
||||
value: "yes",
|
||||
}),
|
||||
new InputUiUtil.GenericButtonInfo({
|
||||
text: "No",
|
||||
clazzIcon: "glyphicon glyphicon-remove",
|
||||
isPrimary: true,
|
||||
value: "no",
|
||||
}),
|
||||
new InputUiUtil.GenericButtonInfo({
|
||||
text: "Cancel",
|
||||
clazzIcon: "glyphicon glyphicon-stop",
|
||||
isSmall: true,
|
||||
value: "cancel",
|
||||
}),
|
||||
],
|
||||
htmlDescription: `<p>The encounter you are trying to load contains additional column data from the Encounter Builder's "Advanced" mode.<br>Do you want to overwrite your existing additional columns with columns from the encounter?</p>`,
|
||||
});
|
||||
|
||||
switch (userVal) {
|
||||
case null:
|
||||
case "cancel": {
|
||||
this._state.rows = rowsPrev;
|
||||
return;
|
||||
}
|
||||
|
||||
case "yes": {
|
||||
stateNxt.isStatsAddColumns = nxtState.isStatsAddColumns;
|
||||
stateNxt.statsCols = nxtState.statsCols
|
||||
.map(it => it.getAsStateData());
|
||||
break;
|
||||
}
|
||||
|
||||
case "no": {
|
||||
// No-op
|
||||
break;
|
||||
}
|
||||
|
||||
default: throw new Error(`Unexpected value "${userVal}"`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this._state.importIsAppend) {
|
||||
const defaultState = this._getDefaultState();
|
||||
["round", "sort", "dir"]
|
||||
@@ -479,6 +522,34 @@ export class InitiativeTracker extends BaseComponent {
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
async _pDoHandleImportDrop (evt) {
|
||||
const data = EventUtil.getDropJson(evt);
|
||||
if (!data) return;
|
||||
|
||||
if (data.type !== VeCt.DRAG_TYPE_IMPORT) return;
|
||||
|
||||
evt.stopPropagation();
|
||||
evt.preventDefault();
|
||||
|
||||
const {page, source, hash} = data;
|
||||
if (page !== UrlUtil.PG_BESTIARY) return;
|
||||
|
||||
const ent = await DataLoader.pCacheAndGet(page, source, hash, {isRequired: true});
|
||||
|
||||
const rowsNxt = [...this._state.rows];
|
||||
const rowToAdd = await this._rowStateBuilderActive.pGetNewRowState({
|
||||
name: ent.name,
|
||||
source: ent.source,
|
||||
initiative: null,
|
||||
rows: rowsNxt,
|
||||
});
|
||||
if (!rowToAdd) return;
|
||||
rowsNxt.push(rowToAdd);
|
||||
this._state.rows = rowsNxt;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
doConnectCreatureViewer ({creatureViewer}) {
|
||||
if (this._creatureViewers.includes(creatureViewer)) return this;
|
||||
this._creatureViewers.push(creatureViewer);
|
||||
|
||||
@@ -274,19 +274,11 @@ class PageFilterBestiary extends PageFilter {
|
||||
static mutateForFilters (mon) {
|
||||
Renderer.monster.initParsed(mon);
|
||||
|
||||
if (typeof mon.speed === "number" && mon.speed > 0) {
|
||||
mon._fSpeedType = ["walk"];
|
||||
mon._fSpeed = mon.speed;
|
||||
} else {
|
||||
mon._fSpeedType = Object.keys(mon.speed).filter(k => mon.speed[k]);
|
||||
if (mon._fSpeedType.length) mon._fSpeed = mon._fSpeedType.map(k => mon.speed[k].number || mon.speed[k]).filter(it => !isNaN(it)).sort((a, b) => SortUtil.ascSort(b, a))[0];
|
||||
else mon._fSpeed = 0;
|
||||
if (mon.speed.canHover) mon._fSpeedType.push("hover");
|
||||
}
|
||||
this._mutateForFilters_speed(mon);
|
||||
|
||||
mon._fAc = mon.ac.map(it => it.special ? null : (it.ac || it)).filter(it => it !== null);
|
||||
mon._fAc = (mon.ac || []).map(it => it.special ? null : (it.ac || it)).filter(it => it !== null);
|
||||
if (!mon._fAc.length) mon._fAc = null;
|
||||
mon._fHp = mon.hp.average;
|
||||
mon._fHp = mon.hp?.average ?? null;
|
||||
if (mon.alignment) {
|
||||
const tempAlign = typeof mon.alignment[0] === "object"
|
||||
? Array.prototype.concat.apply([], mon.alignment.map(a => a.alignment))
|
||||
@@ -348,7 +340,7 @@ class PageFilterBestiary extends PageFilter {
|
||||
if (mon.srd) mon._fMisc.push("SRD");
|
||||
if (mon.basicRules) mon._fMisc.push("Basic Rules");
|
||||
if (SourceUtil.isLegacySourceWotc(mon.source)) mon._fMisc.push("Legacy");
|
||||
if (mon.tokenUrl || mon.hasToken) mon._fMisc.push("Has Token");
|
||||
if (Renderer.monster.hasToken(mon)) mon._fMisc.push("Has Token");
|
||||
if (mon.mythic) mon._fMisc.push("Mythic");
|
||||
if (mon.hasFluff || mon.fluff?.entries) mon._fMisc.push("Has Info");
|
||||
if (mon.hasFluffImages || mon.fluff?.images) mon._fMisc.push("Has Images");
|
||||
@@ -370,6 +362,25 @@ class PageFilterBestiary extends PageFilter {
|
||||
mon._fEquipment = this._getEquipmentList(mon);
|
||||
}
|
||||
|
||||
static _mutateForFilters_speed (mon) {
|
||||
if (mon.speed == null) {
|
||||
mon._fSpeedType = [];
|
||||
mon._fSpeed = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof mon.speed === "number" && mon.speed > 0) {
|
||||
mon._fSpeedType = ["walk"];
|
||||
mon._fSpeed = mon.speed;
|
||||
return;
|
||||
}
|
||||
|
||||
mon._fSpeedType = Object.keys(mon.speed).filter(k => mon.speed[k]);
|
||||
if (mon._fSpeedType.length) mon._fSpeed = mon._fSpeedType.map(k => mon.speed[k].number || mon.speed[k]).filter(it => !isNaN(it)).sort((a, b) => SortUtil.ascSort(b, a))[0];
|
||||
else mon._fSpeed = 0;
|
||||
if (mon.speed.canHover) mon._fSpeedType.push("hover");
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
static _getInitWalker () {
|
||||
@@ -473,8 +484,8 @@ class PageFilterBestiary extends PageFilter {
|
||||
this._wisdomFilter.addItem(mon._fWis);
|
||||
this._charismaFilter.addItem(mon._fCha);
|
||||
this._speedFilter.addItem(mon._fSpeed);
|
||||
mon.ac.forEach(it => this._acFilter.addItem(it.ac || it));
|
||||
if (mon.hp.average) this._averageHpFilter.addItem(mon.hp.average);
|
||||
(mon.ac || []).forEach(it => this._acFilter.addItem(it.ac || it));
|
||||
if (mon.hp?.average) this._averageHpFilter.addItem(mon.hp.average);
|
||||
this._tagFilter.addItem(mon._pTypes.tags);
|
||||
this._sidekickTypeFilter.addItem(mon._pTypes.typeSidekick);
|
||||
this._sidekickTagFilter.addItem(mon._pTypes.tagsSidekick);
|
||||
|
||||
@@ -10,7 +10,7 @@ class PageFilterObjects extends PageFilter {
|
||||
static mutateForFilters (obj) {
|
||||
obj._fMisc = obj.srd ? ["SRD"] : [];
|
||||
if (SourceUtil.isLegacySourceWotc(obj.source)) obj._fMisc.push("Legacy");
|
||||
if (obj.tokenUrl || obj.hasToken) obj._fMisc.push("Has Token");
|
||||
if (Renderer.object.hasToken(obj)) obj._fMisc.push("Has Token");
|
||||
if (obj.hasFluff || obj.fluff?.entries) obj._fMisc.push("Has Info");
|
||||
if (obj.hasFluffImages || obj.fluff?.images) obj._fMisc.push("Has Images");
|
||||
}
|
||||
|
||||
@@ -25,44 +25,44 @@ class PageFilterVehicles extends PageFilter {
|
||||
this._miscFilter = new Filter({header: "Miscellaneous", items: ["SRD", "Legacy", "Has Images", "Has Info", "Has Token"], isMiscFilter: true});
|
||||
}
|
||||
|
||||
static mutateForFilters (it) {
|
||||
it._fSpeed = 0;
|
||||
if (typeof it.speed === "number" && it.speed > 0) {
|
||||
it._fSpeed = it.speed;
|
||||
} else if (it.speed) {
|
||||
const maxSpeed = Math.max(...Object.values(it.speed));
|
||||
if (maxSpeed > 0) it._fSpeed = maxSpeed;
|
||||
} else if (it.pace && typeof it.pace === "number") {
|
||||
it._fSpeed = it.pace * 10; // Based on "Special Travel Pace," DMG p242
|
||||
static mutateForFilters (ent) {
|
||||
ent._fSpeed = 0;
|
||||
if (typeof ent.speed === "number" && ent.speed > 0) {
|
||||
ent._fSpeed = ent.speed;
|
||||
} else if (ent.speed) {
|
||||
const maxSpeed = Math.max(...Object.values(ent.speed));
|
||||
if (maxSpeed > 0) ent._fSpeed = maxSpeed;
|
||||
} else if (ent.pace && typeof ent.pace === "number") {
|
||||
ent._fSpeed = ent.pace * 10; // Based on "Special Travel Pace," DMG p242
|
||||
}
|
||||
|
||||
it._fHp = 0;
|
||||
if (it.hp && it.hp.hp != null) {
|
||||
it._fHp = it.hp.hp;
|
||||
} else if (it.hull && it.hull.hp != null) {
|
||||
it._fHp = it.hull.hp;
|
||||
} else if (it.hp && it.hp.average != null) {
|
||||
it._fHp = it.hp.average;
|
||||
ent._fHp = 0;
|
||||
if (ent.hp && ent.hp.hp != null) {
|
||||
ent._fHp = ent.hp.hp;
|
||||
} else if (ent.hull && ent.hull.hp != null) {
|
||||
ent._fHp = ent.hull.hp;
|
||||
} else if (ent.hp && ent.hp.average != null) {
|
||||
ent._fHp = ent.hp.average;
|
||||
}
|
||||
|
||||
it._fAc = 0;
|
||||
if (it.hull && it.hull.ac != null) {
|
||||
it._fAc = it.hull.ac;
|
||||
} else if (it.vehicleType === "INFWAR") {
|
||||
it._fAc = 19 + Parser.getAbilityModNumber(it.dex == null ? 10 : it.dex);
|
||||
} else if (it.ac instanceof Array) {
|
||||
it._fAc = it.ac.map(it => it.special ? null : (it.ac || it)).filter(it => it !== null);
|
||||
} else if (it.ac) {
|
||||
it._fAc = it.ac;
|
||||
ent._fAc = 0;
|
||||
if (ent.hull && ent.hull.ac != null) {
|
||||
ent._fAc = ent.hull.ac;
|
||||
} else if (ent.vehicleType === "INFWAR") {
|
||||
ent._fAc = 19 + Parser.getAbilityModNumber(ent.dex == null ? 10 : ent.dex);
|
||||
} else if (ent.ac instanceof Array) {
|
||||
ent._fAc = ent.ac.map(it => it.special ? null : (it.ac || it)).filter(it => it !== null);
|
||||
} else if (ent.ac) {
|
||||
ent._fAc = ent.ac;
|
||||
}
|
||||
|
||||
it._fCreatureCapacity = (it.capCrew || 0) + (it.capPassenger || 0) + (it.capCreature || 0);
|
||||
ent._fCreatureCapacity = (ent.capCrew || 0) + (ent.capPassenger || 0) + (ent.capCreature || 0);
|
||||
|
||||
it._fMisc = it.srd ? ["SRD"] : [];
|
||||
if (SourceUtil.isLegacySourceWotc(it.source)) it._fMisc.push("Legacy");
|
||||
if (it.tokenUrl || it.hasToken) it._fMisc.push("Has Token");
|
||||
if (it.hasFluff || it.fluff?.entries) it._fMisc.push("Has Info");
|
||||
if (it.hasFluffImages || it.fluff?.images) it._fMisc.push("Has Images");
|
||||
ent._fMisc = ent.srd ? ["SRD"] : [];
|
||||
if (SourceUtil.isLegacySourceWotc(ent.source)) ent._fMisc.push("Legacy");
|
||||
if (Renderer.vehicle.hasToken(ent)) ent._fMisc.push("Has Token");
|
||||
if (ent.hasFluff || ent.fluff?.entries) ent._fMisc.push("Has Info");
|
||||
if (ent.hasFluffImages || ent.fluff?.images) ent._fMisc.push("Has Images");
|
||||
}
|
||||
|
||||
addToFilters (it, isExcluded) {
|
||||
|
||||
64
js/filter.js
64
js/filter.js
@@ -1469,6 +1469,7 @@ class Filter extends FilterBase {
|
||||
* Defaults to ascending alphabetical sort.
|
||||
* @param [opts.itemSortFnMini] Function which should be used to sort the `items` array when rendering mini-pills.
|
||||
* @param [opts.groupFn] Function which takes an item and assigns it to a group.
|
||||
* @param [opts.groupNameFn] Function which takes a group and returns a group name;
|
||||
* @param [opts.minimalUi] True if the filter should render with a reduced UI, false otherwise.
|
||||
* @param [opts.umbrellaItems] Items which should, when set active, show everything in the filter. E.g. "All".
|
||||
* @param [opts.umbrellaExcludes] Items which should ignore the state of any `umbrellaItems`
|
||||
@@ -1489,6 +1490,7 @@ class Filter extends FilterBase {
|
||||
this._itemSortFn = opts.itemSortFn === undefined ? SortUtil.ascSort : opts.itemSortFn;
|
||||
this._itemSortFnMini = opts.itemSortFnMini;
|
||||
this._groupFn = opts.groupFn;
|
||||
this._groupNameFn = opts.groupNameFn;
|
||||
this._minimalUi = opts.minimalUi;
|
||||
this._umbrellaItems = Filter._getAsFilterItems(opts.umbrellaItems);
|
||||
this._umbrellaExcludes = Filter._getAsFilterItems(opts.umbrellaExcludes);
|
||||
@@ -2137,14 +2139,14 @@ class Filter extends FilterBase {
|
||||
_doRenderPills_doRenderWrpGroup (group) {
|
||||
const existingMeta = this._pillGroupsMeta[group];
|
||||
if (existingMeta && !existingMeta.isAttached) {
|
||||
existingMeta.hrDivider.appendTo(this.__wrpPills);
|
||||
existingMeta.wrpDivider.appendTo(this.__wrpPills);
|
||||
existingMeta.wrpPills.appendTo(this.__wrpPills);
|
||||
existingMeta.isAttached = true;
|
||||
}
|
||||
if (existingMeta) return;
|
||||
|
||||
this._pillGroupsMeta[group] = {
|
||||
hrDivider: this._doRenderPills_doRenderWrpGroup_getHrDivider(group).appendTo(this.__wrpPills),
|
||||
wrpDivider: this._doRenderPills_doRenderWrpGroup_getDivider(group).appendTo(this.__wrpPills),
|
||||
wrpPills: this._doRenderPills_doRenderWrpGroup_getWrpPillsSub(group).appendTo(this.__wrpPills),
|
||||
isAttached: true,
|
||||
};
|
||||
@@ -2152,14 +2154,14 @@ class Filter extends FilterBase {
|
||||
Object.entries(this._pillGroupsMeta)
|
||||
.sort((a, b) => SortUtil.ascSortLower(a[0], b[0]))
|
||||
.forEach(([groupKey, groupMeta], i) => {
|
||||
groupMeta.hrDivider.appendTo(this.__wrpPills);
|
||||
groupMeta.hrDivider.toggleVe(!this._isGroupDividerHidden(groupKey, i));
|
||||
groupMeta.wrpDivider.appendTo(this.__wrpPills);
|
||||
groupMeta.wrpDivider.toggleVe(!this._isGroupDividerHidden(groupKey, i));
|
||||
groupMeta.wrpPills.appendTo(this.__wrpPills);
|
||||
});
|
||||
|
||||
if (this._nests) {
|
||||
this._pillGroupsMeta[group].toggleDividerFromNestVisibility = () => {
|
||||
this._pillGroupsMeta[group].hrDivider.toggleVe(!this._isGroupDividerHidden(group));
|
||||
this._pillGroupsMeta[group].wrpDivider.toggleVe(!this._isGroupDividerHidden(group));
|
||||
};
|
||||
|
||||
// bind group dividers to show/hide depending on nest visibility state
|
||||
@@ -2184,7 +2186,34 @@ class Filter extends FilterBase {
|
||||
return groupItems.length === hiddenGroupItems.length;
|
||||
}
|
||||
|
||||
_doRenderPills_doRenderWrpGroup_getHrDivider () { return e_({tag: "hr", clazz: `fltr__dropdown-divider--sub hr-2 mx-3`}); }
|
||||
_doRenderPills_doRenderWrpGroup_getDivider (group) {
|
||||
const eleHeader = this._doRenderPills_doRenderWrpGroup_getDividerHeader(group);
|
||||
const eleHr = this._doRenderPills_doRenderWrpGroup_getDividerHr(group);
|
||||
|
||||
return e_({
|
||||
tag: "div",
|
||||
clazz: "ve-flex-col w-100",
|
||||
children: [
|
||||
eleHr,
|
||||
eleHeader,
|
||||
]
|
||||
.filter(Boolean),
|
||||
});
|
||||
}
|
||||
|
||||
_doRenderPills_doRenderWrpGroup_getDividerHr (group) { return e_({tag: "hr", clazz: `fltr__dropdown-divider--sub hr-2 mx-3`}); }
|
||||
|
||||
_doRenderPills_doRenderWrpGroup_getDividerHeader (group) {
|
||||
const groupName = this._groupNameFn?.(group);
|
||||
if (!groupName) return null;
|
||||
|
||||
return e_({
|
||||
tag: "div",
|
||||
clazz: `fltr__divider-header ve-muted italic ve-small`,
|
||||
text: groupName,
|
||||
});
|
||||
}
|
||||
|
||||
_doRenderPills_doRenderWrpGroup_getWrpPillsSub () { return e_({tag: "div", clazz: `fltr__wrp-pills--sub fltr__container-pills`}); }
|
||||
|
||||
_doRenderMiniPills () {
|
||||
@@ -2461,7 +2490,7 @@ class Filter extends FilterBase {
|
||||
|
||||
Object.values(this._pillGroupsMeta || {})
|
||||
.forEach(it => {
|
||||
it.hrDivider.detach();
|
||||
it.wrpDivider.detach();
|
||||
it.wrpPills.detach();
|
||||
it.isAttached = false;
|
||||
});
|
||||
@@ -2816,6 +2845,7 @@ class SourceFilter extends Filter {
|
||||
opts.itemSortFnMini = opts.itemSortFnMini === undefined ? SourceFilter._SORT_ITEMS_MINI.bind(SourceFilter) : opts.itemSortFnMini;
|
||||
opts.itemSortFn = opts.itemSortFn === undefined ? (a, b) => SortUtil.ascSortLower(Parser.sourceJsonToFull(a.item), Parser.sourceJsonToFull(b.item)) : opts.itemSortFn;
|
||||
opts.groupFn = opts.groupFn === undefined ? SourceUtil.getFilterGroup : opts.groupFn;
|
||||
opts.groupNameFn = opts.groupNameFn === undefined ? SourceUtil.getFilterGroupName : opts.groupNameFn;
|
||||
opts.selFn = opts.selFn === undefined ? PageFilter.defaultSourceSelFn : opts.selFn;
|
||||
|
||||
super(opts);
|
||||
@@ -3071,15 +3101,15 @@ class SourceFilter extends Filter {
|
||||
return [ent.source].concat(otherSourcesFilt.map(src => new SourceFilterItem({item: src.source, isIgnoreRed: true, isOtherSource: true})));
|
||||
}
|
||||
|
||||
_doRenderPills_doRenderWrpGroup_getHrDivider (group) {
|
||||
_doRenderPills_doRenderWrpGroup_getDividerHeader (group) {
|
||||
switch (group) {
|
||||
case SourceUtil.FILTER_GROUP_NON_STANDARD: return this._doRenderPills_doRenderWrpGroup_getHrDivider_groupNonStandard(group);
|
||||
case SourceUtil.FILTER_GROUP_HOMEBREW: return this._doRenderPills_doRenderWrpGroup_getHrDivider_groupBrew(group);
|
||||
default: return super._doRenderPills_doRenderWrpGroup_getHrDivider(group);
|
||||
case SourceUtil.FILTER_GROUP_NON_STANDARD: return this._doRenderPills_doRenderWrpGroup_getDividerHeader_groupNonStandard(group);
|
||||
case SourceUtil.FILTER_GROUP_HOMEBREW: return this._doRenderPills_doRenderWrpGroup_getDividerHeader_groupBrew(group);
|
||||
default: return super._doRenderPills_doRenderWrpGroup_getDividerHeader(group);
|
||||
}
|
||||
}
|
||||
|
||||
_doRenderPills_doRenderWrpGroup_getHrDivider_groupNonStandard (group) {
|
||||
_doRenderPills_doRenderWrpGroup_getDividerHeader_groupNonStandard (group) {
|
||||
let dates = [];
|
||||
const comp = BaseComponent.fromObject({
|
||||
min: 0,
|
||||
@@ -3203,9 +3233,9 @@ class SourceFilter extends Filter {
|
||||
|
||||
return e_({
|
||||
tag: "div",
|
||||
clazz: `ve-flex-col w-100`,
|
||||
clazz: `split-v-center w-100`,
|
||||
children: [
|
||||
super._doRenderPills_doRenderWrpGroup_getHrDivider(),
|
||||
super._doRenderPills_doRenderWrpGroup_getDividerHeader(group) || e_({clazz: "div"}),
|
||||
e_({
|
||||
tag: "div",
|
||||
clazz: `mb-1 ve-flex-h-right`,
|
||||
@@ -3219,7 +3249,7 @@ class SourceFilter extends Filter {
|
||||
});
|
||||
}
|
||||
|
||||
_doRenderPills_doRenderWrpGroup_getHrDivider_groupBrew (group) {
|
||||
_doRenderPills_doRenderWrpGroup_getDividerHeader_groupBrew (group) {
|
||||
const btnClear = e_({
|
||||
tag: "button",
|
||||
clazz: `btn btn-xxs btn-default px-1`,
|
||||
@@ -3235,9 +3265,9 @@ class SourceFilter extends Filter {
|
||||
|
||||
return e_({
|
||||
tag: "div",
|
||||
clazz: `ve-flex-col w-100`,
|
||||
clazz: `split-v-center w-100`,
|
||||
children: [
|
||||
super._doRenderPills_doRenderWrpGroup_getHrDivider(),
|
||||
super._doRenderPills_doRenderWrpGroup_getDividerHeader(group) || e_({clazz: "div"}),
|
||||
e_({
|
||||
tag: "div",
|
||||
clazz: `mb-1 ve-flex-h-right`,
|
||||
|
||||
@@ -58,24 +58,13 @@ class CreatureBuilder extends Builder {
|
||||
async pHandleSidebarLoadExistingData (creature, opts) {
|
||||
opts = opts || [];
|
||||
|
||||
function pFetchToken (mon) {
|
||||
return new Promise(resolve => {
|
||||
const img = new Image();
|
||||
const url = Renderer.monster.getTokenUrl(mon);
|
||||
img.onload = resolve(url);
|
||||
img.onerror = resolve(null);
|
||||
img.src = url;
|
||||
});
|
||||
}
|
||||
|
||||
const cleanOrigin = window.location.origin.replace(/\/+$/, "");
|
||||
|
||||
// Get the token based on the original source
|
||||
if (creature.tokenUrl || creature.hasToken) {
|
||||
const rawTokenUrl = await pFetchToken(creature);
|
||||
if (rawTokenUrl) {
|
||||
creature.tokenUrl = /^[a-zA-Z\d]+:\/\//.test(rawTokenUrl) ? rawTokenUrl : `${cleanOrigin}/${rawTokenUrl}`;
|
||||
}
|
||||
if (creature.hasToken) {
|
||||
creature.token = {
|
||||
name: creature.name,
|
||||
source: creature.source,
|
||||
};
|
||||
}
|
||||
|
||||
// Get the fluff based on the original source
|
||||
@@ -3224,23 +3213,51 @@ class CreatureBuilder extends Builder {
|
||||
}
|
||||
|
||||
__$getTokenInput (cb) {
|
||||
const [$row, $rowInner] = BuilderUi.getLabelledRowTuple("Token Image URL");
|
||||
const [$row, $rowInner] = BuilderUi.getLabelledRowTuple("Token Image");
|
||||
|
||||
const $iptUrl = $(`<input class="form-control form-control--minimal input-xs mr-2">`)
|
||||
.change(() => doUpdateState())
|
||||
.val(this._state.tokenUrl || "");
|
||||
const doUpdateState = () => {
|
||||
delete this._state.token;
|
||||
delete this._state.tokenUrl;
|
||||
delete this._state.tokenHref;
|
||||
|
||||
const $btnPreview = $(`<button class="btn btn-xs btn-default mr-2" title="Preview Token"><span class="glyphicon glyphicon-fullscreen"/></button>`)
|
||||
switch ($selMode.val()) {
|
||||
case "0": {
|
||||
this._state.token = {name: $iptExistingName.val().trim(), source: $iptExistingSource.val().trim()};
|
||||
break;
|
||||
}
|
||||
|
||||
case "1": {
|
||||
this._state.tokenHref = {
|
||||
type: "external",
|
||||
url: $iptExternalUrl.val(),
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
case "2": {
|
||||
this._state.tokenHref = {
|
||||
type: "internal",
|
||||
path: $iptInternalPath.val(),
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
default: throw new Error("Unimplemented!");
|
||||
}
|
||||
|
||||
cb();
|
||||
};
|
||||
|
||||
const $btnPreview = $(`<button class="btn btn-xs btn-default" title="Preview Token"><span class="glyphicon glyphicon-fullscreen"></span></button>`)
|
||||
.click((evt) => {
|
||||
const val = $iptUrl.val().trim();
|
||||
if (!val) return JqueryUtil.doToast({content: "Please enter an image URL", type: "warning"});
|
||||
if (!Renderer.monster.hasToken(this._state)) return JqueryUtil.doToast({content: "Please set a token first!", type: "warning"});
|
||||
|
||||
const $content = Renderer.hover.$getHoverContent_generic(
|
||||
{
|
||||
type: "image",
|
||||
href: {
|
||||
type: "external",
|
||||
url: val,
|
||||
url: Renderer.monster.getTokenUrl(this._state),
|
||||
},
|
||||
},
|
||||
{isBookContent: true},
|
||||
@@ -3256,15 +3273,81 @@ class CreatureBuilder extends Builder {
|
||||
);
|
||||
});
|
||||
|
||||
const doUpdateState = () => {
|
||||
const val = $iptUrl.val().trim();
|
||||
if (val) this._state.tokenUrl = val;
|
||||
else delete this._state.tokenUrl;
|
||||
const initialMode = this._state.token ? "0" : this._state.tokenHref?.type === "internal" ? "2" : "1";
|
||||
|
||||
cb();
|
||||
};
|
||||
const $selMode = $(`<select class="form-control input-xs mr-2">
|
||||
<option value="0">Existing Creature</option>
|
||||
<option value="1">External URL</option>
|
||||
<option value="2">Internal URL</option>
|
||||
</select>`)
|
||||
.val(initialMode)
|
||||
.change(() => {
|
||||
switch ($selMode.val()) {
|
||||
case "0": {
|
||||
$stgExistingCreature.showVe(); $stgExternalUrl.hideVe(); $stgInternalUrl.hideVe();
|
||||
doUpdateState();
|
||||
break;
|
||||
}
|
||||
case "1": {
|
||||
$stgExistingCreature.hideVe(); $stgExternalUrl.showVe(); $stgInternalUrl.hideVe();
|
||||
doUpdateState();
|
||||
break;
|
||||
}
|
||||
case "2": {
|
||||
$stgExistingCreature.hideVe(); $stgExternalUrl.hideVe(); $stgInternalUrl.showVe();
|
||||
doUpdateState();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$$`<div class="ve-flex">${$iptUrl}${$btnPreview}</div>`.appendTo($rowInner);
|
||||
// region Existing creature
|
||||
const $iptExistingName = $(`<input class="form-control input-xs form-control--minimal">`)
|
||||
.val(this._state.token?.name || "")
|
||||
.on("change", () => doUpdateState());
|
||||
const $iptExistingSource = $(`<input class="form-control input-xs form-control--minimal">`)
|
||||
.val(this._state.token?.source || "")
|
||||
.on("change", () => doUpdateState());
|
||||
|
||||
const $stgExistingCreature = $$`<div class="ve-flex-col mb-2">
|
||||
<div class="ve-flex-v-center mb-2"><span class="mr-2 mkbru__sub-name--25">Name</span>${$iptExistingName}</div>
|
||||
<div class="ve-flex-v-center"><span class="mr-2 mkbru__sub-name--25">Source</span>${$iptExistingSource}</div>
|
||||
</div>`
|
||||
.toggleVe(initialMode === "0");
|
||||
// endregion
|
||||
|
||||
// region External URL
|
||||
const $iptExternalUrl = $(`<input class="form-control form-control--minimal input-xs code">`)
|
||||
.change(() => doUpdateState())
|
||||
.val(
|
||||
this._state.tokenHref?.url
|
||||
|| this._state.tokenUrl // TODO(Future) legacy; remove
|
||||
|| "",
|
||||
);
|
||||
|
||||
const $stgExternalUrl = $$`<div class="ve-flex-col mb-2">
|
||||
<div class="ve-flex-v-center"><span class="mr-2 mkbru__sub-name--25">URL</span>${$iptExternalUrl}</div>
|
||||
</div>`
|
||||
.toggleVe(initialMode === "1");
|
||||
// endregion
|
||||
|
||||
// region Internal URL
|
||||
const $iptInternalPath = $(`<input class="form-control form-control--minimal input-xs code">`)
|
||||
.change(() => doUpdateState())
|
||||
.val(this._state.tokenHref?.path || "");
|
||||
|
||||
const $stgInternalUrl = $$`<div class="ve-flex-col mb-2">
|
||||
<div class="ve-flex-v-center"><span class="mr-2 mkbru__sub-name--25">Path</span>${$iptInternalPath}</div>
|
||||
</div>`
|
||||
.toggleVe(initialMode === "2");
|
||||
// endregion
|
||||
|
||||
$$`<div class="ve-flex-col">
|
||||
<div class="ve-flex mb-2">${$selMode}${$btnPreview}</div>
|
||||
${$stgExistingCreature}
|
||||
${$stgExternalUrl}
|
||||
${$stgInternalUrl}
|
||||
</div>`.appendTo($rowInner);
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
@@ -124,11 +124,10 @@ class ObjectsPage extends ListPage {
|
||||
|
||||
(this._$dispToken = this._$dispToken || $(`#float-token`)).empty();
|
||||
|
||||
const hasToken = ent.tokenUrl || ent.hasToken;
|
||||
if (hasToken) {
|
||||
const imgLink = Renderer.object.getTokenUrl(ent);
|
||||
this._$dispToken.append(`<a href="${imgLink}" target="_blank" rel="noopener noreferrer"><img src="${imgLink}" id="token_image" class="token" alt="Token Image: ${(ent.name || "").qq()}" ${ent.tokenCredit ? `title="Credit: ${ent.tokenCredit.qq()}"` : ""} loading="lazy"></a>`);
|
||||
}
|
||||
if (!Renderer.object.hasToken(ent)) return;
|
||||
|
||||
const imgLink = Renderer.object.getTokenUrl(ent);
|
||||
this._$dispToken.append(`<a href="${imgLink}" target="_blank" rel="noopener noreferrer"><img src="${imgLink}" id="token_image" class="token" alt="Token Image: ${(ent.name || "").qq()}" ${ent.tokenCredit ? `title="Credit: ${ent.tokenCredit.qq()}"` : ""} loading="lazy"></a>`);
|
||||
}
|
||||
|
||||
_renderStats_onTabChangeStats () {
|
||||
|
||||
@@ -246,7 +246,7 @@ class IndexableDirectoryBestiary extends IndexableDirectory {
|
||||
baseUrl: "bestiary.html",
|
||||
isHover: true,
|
||||
fnGetToken: (ent) => {
|
||||
if (!ent.tokenUrl && !ent.hasToken) return null;
|
||||
if (!Renderer.monster.hasToken(ent)) return null;
|
||||
return Renderer.monster.getTokenUrl(ent);
|
||||
},
|
||||
});
|
||||
@@ -977,7 +977,7 @@ class IndexableFileObjects extends IndexableFile {
|
||||
baseUrl: "objects.html",
|
||||
isHover: true,
|
||||
fnGetToken: (ent) => {
|
||||
if (!ent.tokenUrl && !ent.hasToken) return null;
|
||||
if (!Renderer.object.hasToken(ent)) return null;
|
||||
return Renderer.object.getTokenUrl(ent);
|
||||
},
|
||||
});
|
||||
@@ -1080,7 +1080,7 @@ class IndexableFileVehicles extends IndexableFile {
|
||||
baseUrl: "vehicles.html",
|
||||
isHover: true,
|
||||
fnGetToken: (ent) => {
|
||||
if (!ent.tokenUrl && !ent.hasToken) return null;
|
||||
if (!Renderer.vehicle.hasToken(ent)) return null;
|
||||
return Renderer.vehicle.getTokenUrl(ent);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -317,7 +317,7 @@ class Omnisearch {
|
||||
}
|
||||
|
||||
static _renderLink_getHoverString (category, url, src, {isFauxPage = false} = {}) {
|
||||
return `onmouseover="Renderer.hover.pHandleLinkMouseOver(event, this)" onmouseleave="Renderer.hover.handleLinkMouseLeave(event, this)" onmousemove="Renderer.hover.handleLinkMouseMove(event, this)" data-vet-page="${UrlUtil.categoryToHoverPage(category).qq()}" data-vet-source="${src.qq()}" data-vet-hash="${url.qq()}" ${isFauxPage ? `data-vet-is-faux-page="true"` : ""} ${Renderer.hover.getPreventTouchString()}`;
|
||||
return `onmouseover="Renderer.hover.pHandleLinkMouseOver(event, this)" onmouseleave="Renderer.hover.handleLinkMouseLeave(event, this)" onmousemove="Renderer.hover.handleLinkMouseMove(event, this)" ondragstart="Renderer.hover.handleLinkDragStart(event, this)" data-vet-page="${UrlUtil.categoryToHoverPage(category).qq()}" data-vet-source="${src.qq()}" data-vet-hash="${url.qq()}" ${isFauxPage ? `data-vet-is-faux-page="true"` : ""} ${Renderer.hover.getPreventTouchString()}`;
|
||||
}
|
||||
|
||||
static $getResultLink (r) {
|
||||
|
||||
@@ -21,10 +21,6 @@ class PlutoniumPage {
|
||||
{path: "plutonium/compact-chat.webp", id: "wrp-img-compact-chat"},
|
||||
];
|
||||
|
||||
static _VIDEOS = [
|
||||
{path: "plutonium/artbrowser.webm", id: "video-artbrowser"},
|
||||
];
|
||||
|
||||
static _pOnLoad_initElements () {
|
||||
this._IMAGES
|
||||
.forEach(({path, id}) => {
|
||||
@@ -34,14 +30,6 @@ class PlutoniumPage {
|
||||
.html(`<img class="big-help-gif" src="${url}" loading="lazy">`)
|
||||
.attr("href", url);
|
||||
});
|
||||
|
||||
this._VIDEOS
|
||||
.forEach(({path, id}) => {
|
||||
const url = Renderer.get().getMediaUrl("img", path);
|
||||
|
||||
$(`#${id}`)
|
||||
.html(`<source src="${url}" type="video/webm">`);
|
||||
});
|
||||
}
|
||||
|
||||
static async pOnLoad () {
|
||||
|
||||
@@ -34,7 +34,7 @@ class RenderBestiary {
|
||||
|
||||
const htmlSourceAndEnvironment = this._$getRenderedCreature_getHtmlSourceAndEnvironment(mon, legGroup);
|
||||
|
||||
const hasToken = mon.tokenUrl || mon.hasToken;
|
||||
const hasToken = Renderer.monster.hasToken(mon);
|
||||
const extraThClasses = hasToken ? ["mon__name--token"] : null;
|
||||
|
||||
const ptsResource = mon.resource?.length
|
||||
@@ -51,8 +51,8 @@ class RenderBestiary {
|
||||
</td></tr>
|
||||
<tr><td class="divider" colspan="6"><div></div></td></tr>
|
||||
|
||||
<tr><td colspan="6"><div ${hasToken ? `class="mon__wrp-avoid-token"` : ""}><strong>Armor Class</strong> ${Parser.acToFull(mon.ac)}</div></td></tr>
|
||||
<tr><td colspan="6"><div ${hasToken ? `class="mon__wrp-avoid-token"` : ""}><strong>Hit Points</strong> ${Renderer.monster.getRenderedHp(mon.hp)}</div></td></tr>
|
||||
<tr><td colspan="6"><div ${hasToken ? `class="mon__wrp-avoid-token"` : ""}><strong>Armor Class</strong> ${mon.ac == null ? "\u2014" : Parser.acToFull(mon.ac)}</div></td></tr>
|
||||
<tr><td colspan="6"><div ${hasToken ? `class="mon__wrp-avoid-token"` : ""}><strong>Hit Points</strong> ${mon.hp == null ? "\u2014" : Renderer.monster.getRenderedHp(mon.hp)}</div></td></tr>
|
||||
${ptsResource.join("")}
|
||||
<tr><td colspan="6"><strong>Speed</strong> ${Parser.getSpeedString(mon)}</td></tr>
|
||||
<tr><td class="divider" colspan="6"><div></div></td></tr>
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
"use strict";
|
||||
|
||||
class RenderMap {
|
||||
static _getZoom (mapData) {
|
||||
return this._ZOOM_LEVELS[mapData.ixZoom];
|
||||
static _ZOOM_LEVELS = [0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.25, 1.5, 1.75, 2.0, 2.5, 3.0, 4.0, 5.0];
|
||||
static _AREA_CACHE = {};
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
static _getZoom (mapData, {ixZoom} = {}) {
|
||||
return this._ZOOM_LEVELS[ixZoom ?? mapData.ixZoom];
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
static async pShowViewer (evt, ele) {
|
||||
const mapData = JSON.parse(ele.dataset.rdPackedMap);
|
||||
|
||||
@@ -16,10 +23,10 @@ class RenderMap {
|
||||
|
||||
if (!mapData.loadedImage) return;
|
||||
|
||||
const $content = this._$getWindowContent(mapData);
|
||||
const {$wrp, setZoom} = this._$getWindowContent(mapData);
|
||||
|
||||
Renderer.hover.getShowWindow(
|
||||
$content,
|
||||
const hoverWindow = Renderer.hover.getShowWindow(
|
||||
$wrp,
|
||||
// Open in the top-right corner of the screen
|
||||
Renderer.hover.getWindowPositionExact(document.body.clientWidth, 7, evt),
|
||||
{
|
||||
@@ -28,7 +35,7 @@ class RenderMap {
|
||||
isBookContent: true,
|
||||
width: Math.min(Math.floor(document.body.clientWidth / 2), mapData.width),
|
||||
height: mapData.height + 32,
|
||||
$pFnGetPopoutContent: this._$getWindowContent.bind(this, mapData),
|
||||
$pFnGetPopoutContent: () => this._$getWindowContent(mapData).$wrp,
|
||||
fnGetPopoutSize: () => {
|
||||
return {
|
||||
width: Math.min(window.innerWidth, Math.round(this._getZoom(mapData) * mapData.width)),
|
||||
@@ -38,23 +45,67 @@ class RenderMap {
|
||||
isPopout: !!evt.shiftKey,
|
||||
},
|
||||
);
|
||||
|
||||
this._mutInitialZoom({
|
||||
fnGetContainerDimensions: () => {
|
||||
const {wWrpContent, hWrapContent} = hoverWindow.getPosition();
|
||||
return {
|
||||
w: wWrpContent,
|
||||
h: hWrapContent,
|
||||
};
|
||||
},
|
||||
mapData,
|
||||
setZoom,
|
||||
});
|
||||
}
|
||||
|
||||
static async $pGetRendered (mapData) {
|
||||
/* -------------------------------------------- */
|
||||
|
||||
static async $pGetRendered (mapData, {fnGetContainerDimensions = null} = {}) {
|
||||
await RenderMap._pMutMapData(mapData);
|
||||
if (!mapData.loadedImage) return;
|
||||
return this._$getWindowContent(mapData);
|
||||
const {$wrp, setZoom} = this._$getWindowContent(mapData);
|
||||
this._mutInitialZoom({
|
||||
fnGetContainerDimensions,
|
||||
mapData,
|
||||
setZoom,
|
||||
});
|
||||
return $wrp;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** Treat container as slightly larger than it actually is, as many maps have borders/"dead" regions around the edges */
|
||||
static _INITIAL_ZOOM_PAD_MULTIPLIER = 1.1;
|
||||
|
||||
static _mutInitialZoom ({fnGetContainerDimensions, mapData, setZoom}) {
|
||||
if (!fnGetContainerDimensions) return;
|
||||
|
||||
const {w, h} = fnGetContainerDimensions();
|
||||
|
||||
// Attempt to zoom out until one full edge of the image is in-view
|
||||
let ixZoomDesired = this._ZOOM_LEVELS.length - 1;
|
||||
while (ixZoomDesired > 0) {
|
||||
const isInW = Math.round(this._getZoom(mapData, {ixZoom: ixZoomDesired}) * mapData.width) <= (w * this._INITIAL_ZOOM_PAD_MULTIPLIER);
|
||||
const isInH = Math.round(this._getZoom(mapData, {ixZoom: ixZoomDesired}) * mapData.height) <= (h * this._INITIAL_ZOOM_PAD_MULTIPLIER);
|
||||
if (isInW || isInH) break;
|
||||
ixZoomDesired--;
|
||||
}
|
||||
if (ixZoomDesired !== mapData.ixZoom) setZoom(ixZoomDesired);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
static async _pMutMapData (mapData) {
|
||||
// Store some additional data on this mapData state object
|
||||
mapData.ixZoom = RenderMap._ZOOM_LEVELS.indexOf(1.0);
|
||||
mapData.activeWindows = {};
|
||||
mapData.loadedImage = await RenderMap._pLoadImage(mapData);
|
||||
if (mapData.loadedImage) {
|
||||
mapData.width = mapData.width || mapData.loadedImage.naturalWidth;
|
||||
mapData.height = mapData.height || mapData.loadedImage.naturalHeight;
|
||||
}
|
||||
|
||||
if (!mapData.loadedImage) return;
|
||||
|
||||
mapData.width = mapData.width || mapData.loadedImage.naturalWidth;
|
||||
mapData.height = mapData.height || mapData.loadedImage.naturalHeight;
|
||||
}
|
||||
|
||||
static async _pLoadImage (mapData) {
|
||||
@@ -75,6 +126,8 @@ class RenderMap {
|
||||
return out;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
static _$getWindowContent (mapData) {
|
||||
const X = 0;
|
||||
const Y = 1;
|
||||
@@ -101,6 +154,10 @@ class RenderMap {
|
||||
if (lastIxZoom === mapData.ixZoom) return;
|
||||
}
|
||||
|
||||
onZoomChange();
|
||||
};
|
||||
|
||||
const onZoomChange = () => {
|
||||
const zoom = this._getZoom(mapData);
|
||||
|
||||
const nxtWidth = Math.round(mapData.width * zoom);
|
||||
@@ -346,7 +403,17 @@ class RenderMap {
|
||||
|
||||
zoomChange();
|
||||
|
||||
return $out;
|
||||
return {
|
||||
$wrp: $out,
|
||||
setZoom: ixZoom => {
|
||||
if (mapData.ixZoom === ixZoom) return;
|
||||
if (!Array.from({length: this._ZOOM_LEVELS.length}, (_, i) => i).includes(ixZoom)) throw new Error(`Unhandled zoom index "${ixZoom}"`);
|
||||
|
||||
mapData.ixZoom = ixZoom;
|
||||
|
||||
onZoomChange();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static async _pGetArea (areaId, mapData) {
|
||||
@@ -403,5 +470,3 @@ class RenderMap {
|
||||
}
|
||||
}
|
||||
}
|
||||
RenderMap._ZOOM_LEVELS = [0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.25, 1.5, 1.75, 2.0, 2.5, 3.0, 4.0, 5.0];
|
||||
RenderMap._AREA_CACHE = {};
|
||||
|
||||
@@ -866,7 +866,7 @@ RendererMarkdown.utils = class {
|
||||
static getRenderedAbilityScores (ent, {prefix = ""} = "") {
|
||||
return `${prefix}|${Parser.ABIL_ABVS.map(it => `${it.toUpperCase()}|`).join("")}
|
||||
${prefix}|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
${prefix}|${Parser.ABIL_ABVS.map(ab => `${ent[ab]} (${Parser.getAbilityModifier(ent[ab])})|`).join("")}`;
|
||||
${prefix}|${Parser.ABIL_ABVS.map(ab => ent[ab] == null ? `\u2014|` : `${ent[ab]} (${Parser.getAbilityModifier(ent[ab])})|`).join("")}`;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -899,7 +899,7 @@ RendererMarkdown.monster = class {
|
||||
|
||||
const monTypes = Parser.monTypeToFullObj(mon.type);
|
||||
RendererMarkdown.get().isSkipStylingItemLinks = true;
|
||||
const acPart = Parser.acToFull(mon.ac, RendererMarkdown.get());
|
||||
const acPart = mon.ac == null ? "\u2014" : Parser.acToFull(mon.ac, RendererMarkdown.get());
|
||||
RendererMarkdown.get().isSkipStylingItemLinks = false;
|
||||
const resourcePart = mon.resource?.length
|
||||
? mon.resource
|
||||
@@ -947,7 +947,7 @@ RendererMarkdown.monster = class {
|
||||
>*${mon.level ? `${Parser.getOrdinalForm(mon.level)}-level ` : ""}${Renderer.utils.getRenderedSize(mon.size)} ${monTypes.asText}${mon.alignment ? `, ${mon.alignmentPrefix ? RendererMarkdown.get().render(mon.alignmentPrefix) : ""}${Parser.alignmentListToFull(mon.alignment)}` : ""}*
|
||||
>___
|
||||
>- **Armor Class** ${acPart}
|
||||
>- **Hit Points** ${Renderer.monster.getRenderedHp(mon.hp, true)}${resourcePart}
|
||||
>- **Hit Points** ${mon.hp == null ? "\u2014" : Renderer.monster.getRenderedHp(mon.hp, true)}${resourcePart}
|
||||
>- **Speed** ${Parser.getSpeedString(mon)}
|
||||
>___
|
||||
${abilityScorePart}
|
||||
|
||||
402
js/render.js
402
js/render.js
@@ -495,9 +495,11 @@ globalThis.Renderer = function () {
|
||||
|
||||
textStack[0] += `<div class="rd__image-title">`;
|
||||
|
||||
if (entry.title && !entry.mapRegions) textStack[0] += `<div class="rd__image-title-inner">${this.render(entry.title)}</div>`;
|
||||
const isDynamicViewer = entry.mapRegions && !IS_VTT;
|
||||
|
||||
if (entry.mapRegions && !IS_VTT) {
|
||||
if (entry.title && !isDynamicViewer) textStack[0] += `<div class="rd__image-title-inner">${this.render(entry.title)}</div>`;
|
||||
|
||||
if (isDynamicViewer) {
|
||||
textStack[0] += `<button class="btn btn-xs btn-default rd__image-btn-viewer" onclick="RenderMap.pShowViewer(event, this)" data-rd-packed-map="${this._renderImage_getMapRegionData(entry)}" ${ptAdventureBookMeta} title="Open Dynamic Viewer (SHIFT to Open in New Window)"><span class="glyphicon glyphicon-picture"></span> ${Renderer.stripTags(entry.title) || "Dynamic Viewer"}</button>`;
|
||||
}
|
||||
|
||||
@@ -2039,7 +2041,7 @@ globalThis.Renderer = function () {
|
||||
const replacementAttributes = pluginData.map(it => it.attributesHoverReplace).filter(Boolean);
|
||||
if (replacementAttributes.length) return replacementAttributes.join(" ");
|
||||
|
||||
return `onmouseover="Renderer.hover.pHandleLinkMouseOver(event, this)" onmouseleave="Renderer.hover.handleLinkMouseLeave(event, this)" onmousemove="Renderer.hover.handleLinkMouseMove(event, this)" data-vet-page="${entry.href.hover.page.qq()}" data-vet-source="${entry.href.hover.source.qq()}" data-vet-hash="${procHash.qq()}" ${entry.href.hover.preloadId != null ? `data-vet-preload-id="${`${entry.href.hover.preloadId}`.qq()}"` : ""} ${entry.href.hover.isFauxPage ? `data-vet-is-faux-page="true"` : ""} ${Renderer.hover.getPreventTouchString()}`;
|
||||
return `onmouseover="Renderer.hover.pHandleLinkMouseOver(event, this)" onmouseleave="Renderer.hover.handleLinkMouseLeave(event, this)" onmousemove="Renderer.hover.handleLinkMouseMove(event, this)" ondragstart="Renderer.hover.handleLinkDragStart(event, this)" data-vet-page="${entry.href.hover.page.qq()}" data-vet-source="${entry.href.hover.source.qq()}" data-vet-hash="${procHash.qq()}" ${entry.href.hover.preloadId != null ? `data-vet-preload-id="${`${entry.href.hover.preloadId}`.qq()}"` : ""} ${entry.href.hover.isFauxPage ? `data-vet-is-faux-page="true"` : ""} ${Renderer.hover.getPreventTouchString()}`;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -6785,7 +6787,7 @@ Renderer.object = class {
|
||||
|
||||
const renderer = Renderer.get().setFirstSection(true);
|
||||
|
||||
const hasToken = ent.tokenUrl || ent.hasToken;
|
||||
const hasToken = Renderer.object.hasToken(ent);
|
||||
const extraThClasses = !opts.isCompact && hasToken ? ["objs__name--token"] : null;
|
||||
|
||||
const entriesMeta = Renderer.object.getObjectRenderableEntriesMeta(ent);
|
||||
@@ -6807,9 +6809,12 @@ Renderer.object = class {
|
||||
`;
|
||||
}
|
||||
|
||||
static hasToken (obj) {
|
||||
return Renderer.generic.hasToken(obj);
|
||||
}
|
||||
|
||||
static getTokenUrl (obj) {
|
||||
if (obj.tokenUrl) return obj.tokenUrl;
|
||||
return Renderer.get().getMediaUrl("img", `objects/tokens/${Parser.sourceJsonToAbv(obj.source)}/${Parser.nameToTokenName(obj.name)}.webp`);
|
||||
return Renderer.generic.getTokenUrl(obj, "objects/tokens");
|
||||
}
|
||||
|
||||
static pGetFluff (obj) {
|
||||
@@ -7499,7 +7504,7 @@ Renderer.monster = class {
|
||||
|
||||
const renderStack = [];
|
||||
const legGroup = DataUtil.monster.getMetaGroup(mon);
|
||||
const hasToken = mon.tokenUrl || mon.hasToken;
|
||||
const hasToken = Renderer.monster.hasToken(mon);
|
||||
const extraThClasses = !opts.isCompact && hasToken ? ["mon__name--token"] : null;
|
||||
|
||||
const isShowCrScaler = ScaleCreature.isCrInScaleRange(mon);
|
||||
@@ -7550,8 +7555,8 @@ Renderer.monster = class {
|
||||
${hasToken && !opts.isCompact ? `<th colspan="1"></th>` : ""}
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">${Parser.acToFull(mon.ac)}</td>
|
||||
<td colspan="2">${Renderer.monster.getRenderedHp(mon.hp)}</td>
|
||||
<td colspan="2">${mon.ac == null ? "\u2014" : Parser.acToFull(mon.ac)}</td>
|
||||
<td colspan="2">${mon.hp == null ? "\u2014" : Renderer.monster.getRenderedHp(mon.hp)}</td>
|
||||
<td colspan="2">${Parser.getSpeedString(mon)}</td>
|
||||
${ptCrSpellLevel}
|
||||
${mon.pbNote || Parser.crToNumber(mon.cr) < VeCt.CR_CUSTOM ? `<td colspan="1">${mon.pbNote ?? UiUtil.intToBonus(Parser.crToPb(mon.cr), {isPretty: true})}</td>` : ""}
|
||||
@@ -7747,9 +7752,12 @@ Renderer.monster = class {
|
||||
return skills;
|
||||
}
|
||||
|
||||
static hasToken (mon) {
|
||||
return Renderer.generic.hasToken(mon);
|
||||
}
|
||||
|
||||
static getTokenUrl (mon) {
|
||||
if (mon.tokenUrl) return mon.tokenUrl;
|
||||
return Renderer.get().getMediaUrl("img", `bestiary/tokens/${Parser.sourceJsonToAbv(mon.source)}/${Parser.nameToTokenName(mon.name)}.webp`);
|
||||
return Renderer.generic.getTokenUrl(mon, "bestiary/tokens");
|
||||
}
|
||||
|
||||
static postProcessFluff (mon, fluff) {
|
||||
@@ -7823,8 +7831,16 @@ Renderer.monster = class {
|
||||
|
||||
static getRenderedEnvironment (envs) { return (envs || []).sort(SortUtil.ascSortLower).map(it => it.toTitleCase()).join(", "); }
|
||||
|
||||
static getAltArtDisplayName (meta) { return meta.displayName || meta.name || meta.token?.name; }
|
||||
static getAltArtSource (meta) { return meta.source || meta.token?.source; }
|
||||
|
||||
static getRenderedAltArtEntry (meta, {isPlainText = false} = {}) {
|
||||
return `${isPlainText ? "" : `<div>`}${meta.displayName || meta.name}; ${isPlainText ? "" : `<span title="${Parser.sourceJsonToFull(meta.source)}">`}${Parser.sourceJsonToAbv(meta.source)}${Renderer.utils.isDisplayPage(meta.page) ? ` p${meta.page}` : ""}${isPlainText ? "" : `</span></div>`}`;
|
||||
const displayName = Renderer.monster.getAltArtDisplayName(meta);
|
||||
const source = Renderer.monster.getAltArtSource(meta);
|
||||
|
||||
if (!displayName || !source) return "";
|
||||
|
||||
return `${isPlainText ? "" : `<div>`}${displayName}; ${isPlainText ? "" : `<span title="${Parser.sourceJsonToFull(source)}">`}${Parser.sourceJsonToAbv(source)}${Renderer.utils.isDisplayPage(meta.page) ? ` p${meta.page}` : ""}${isPlainText ? "" : `</span></div>`}`;
|
||||
}
|
||||
|
||||
static pGetFluff (mon) {
|
||||
@@ -8904,7 +8920,6 @@ Renderer.item = class {
|
||||
static _INHERITED_PROPS_BLOCKLIST = new Set([
|
||||
// region Specific merge strategy
|
||||
"entries",
|
||||
"rarity",
|
||||
// endregion
|
||||
|
||||
// region Meaningless on merged item
|
||||
@@ -8932,11 +8947,6 @@ Renderer.item = class {
|
||||
genericVariant.entries = MiscUtil.copyFast(Renderer.applyAllProperties(genericVariant.inherits.entries, genericVariant.inherits));
|
||||
}
|
||||
|
||||
if (genericVariant.inherits.rarity == null) delete genericVariant.rarity;
|
||||
else if (genericVariant.inherits.rarity === "varies") {
|
||||
/* No-op, i.e., use current rarity */
|
||||
} else genericVariant.rarity = genericVariant.inherits.rarity;
|
||||
|
||||
if (genericVariant.requires.armor) genericVariant.armor = genericVariant.requires.armor;
|
||||
}
|
||||
|
||||
@@ -9874,7 +9884,7 @@ Renderer.vehicle = class {
|
||||
const entriesMeta = Renderer.vehicle.getVehicleRenderableEntriesMeta(ent);
|
||||
const entriesMetaShip = Renderer.vehicle.ship.getVehicleShipRenderableEntriesMeta(ent);
|
||||
|
||||
const hasToken = ent.tokenUrl || ent.hasToken;
|
||||
const hasToken = Renderer.vehicle.hasToken(ent);
|
||||
const extraThClasses = !opts.isCompact && hasToken ? ["veh__name--token"] : null;
|
||||
|
||||
return `
|
||||
@@ -9913,7 +9923,7 @@ Renderer.vehicle = class {
|
||||
static _getRenderedString_spelljammer (veh, opts) {
|
||||
const renderer = Renderer.get();
|
||||
|
||||
const hasToken = veh.tokenUrl || veh.hasToken;
|
||||
const hasToken = Renderer.vehicle.hasToken(veh);
|
||||
const extraThClasses = !opts.isCompact && hasToken ? ["veh__name--token"] : null;
|
||||
|
||||
return `
|
||||
@@ -9954,7 +9964,7 @@ Renderer.vehicle = class {
|
||||
const entriesMeta = Renderer.vehicle.getVehicleRenderableEntriesMeta(ent);
|
||||
const entriesMetaInfwar = Renderer.vehicle.infwar.getVehicleInfwarRenderableEntriesMeta(ent);
|
||||
|
||||
const hasToken = ent.tokenUrl || ent.hasToken;
|
||||
const hasToken = Renderer.vehicle.hasToken(ent);
|
||||
const extraThClasses = !opts.isCompact && hasToken ? ["veh__name--token"] : null;
|
||||
|
||||
return `
|
||||
@@ -9985,9 +9995,12 @@ Renderer.vehicle = class {
|
||||
});
|
||||
}
|
||||
|
||||
static hasToken (veh) {
|
||||
return Renderer.generic.hasToken(veh);
|
||||
}
|
||||
|
||||
static getTokenUrl (veh) {
|
||||
if (veh.tokenUrl) return veh.tokenUrl;
|
||||
return Renderer.get().getMediaUrl("img", `vehicles/tokens/${Parser.sourceJsonToAbv(veh.source)}/${Parser.nameToTokenName(veh.name)}.webp`);
|
||||
return Renderer.generic.getTokenUrl(veh, "vehicles/tokens");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -10150,8 +10163,12 @@ Renderer.adventureBook = class {
|
||||
|
||||
static _isAltMissingCoverUsed = false;
|
||||
static getCoverUrl (contents) {
|
||||
if (contents.cover) {
|
||||
return UrlUtil.link(Renderer.utils.getEntryMediaUrl(contents, "cover", "img"));
|
||||
}
|
||||
|
||||
// TODO(Future) remove as deprecated; remove from schema; remove from proporder
|
||||
if (contents.coverUrl) {
|
||||
// FIXME(future) migrate to e.g. a `coverImage` "image"-type entry to avoid this hack
|
||||
if (/^https?:\/\//.test(contents.coverUrl)) return contents.coverUrl;
|
||||
return UrlUtil.link(Renderer.get().getMediaUrl("img", contents.coverUrl.replace(/^img\//, "")));
|
||||
}
|
||||
@@ -10896,32 +10913,51 @@ Renderer.generic = class {
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
static hasToken (ent) {
|
||||
return ent.tokenUrl // TODO(Future) legacy; remove
|
||||
|| ent.hasToken // An implicit token
|
||||
|| ent.token // An explicit token
|
||||
|| ent.tokenHref // An explicit token URL (local or external)
|
||||
;
|
||||
}
|
||||
|
||||
static getTokenUrl (ent, mediaDir) {
|
||||
if (ent.tokenUrl) return ent.tokenUrl; // TODO(Future) legacy; remove
|
||||
if (ent.token) return Renderer.get().getMediaUrl("img", `${mediaDir}/${Parser.sourceJsonToAbv(ent.token.source)}/${Parser.nameToTokenName(ent.token.name)}.webp`);
|
||||
if (ent.tokenHref) return Renderer.utils.getEntryMediaUrl(ent, "tokenHref", "img");
|
||||
return Renderer.get().getMediaUrl("img", `${mediaDir}/${Parser.sourceJsonToAbv(ent.source)}/${Parser.nameToTokenName(ent.name)}.webp`);
|
||||
}
|
||||
};
|
||||
|
||||
Renderer.hover = {
|
||||
LinkMeta: function () {
|
||||
this.isHovered = false;
|
||||
this.isLoading = false;
|
||||
this.isPermanent = false;
|
||||
this.windowMeta = null;
|
||||
},
|
||||
Renderer.hover = class {
|
||||
static LinkMeta = class {
|
||||
constructor () {
|
||||
this.isHovered = false;
|
||||
this.isLoading = false;
|
||||
this.isPermanent = false;
|
||||
this.windowMeta = null;
|
||||
}
|
||||
};
|
||||
|
||||
_BAR_HEIGHT: 16,
|
||||
static _BAR_HEIGHT = 16;
|
||||
|
||||
_linkCache: {},
|
||||
_eleCache: new Map(),
|
||||
_entryCache: {},
|
||||
_isInit: false,
|
||||
_dmScreen: null,
|
||||
_lastId: 0,
|
||||
_contextMenu: null,
|
||||
_contextMenuLastClicked: null,
|
||||
static _linkCache = {};
|
||||
static _eleCache = new Map();
|
||||
static _entryCache = {};
|
||||
static _isInit = false;
|
||||
static _dmScreen = null;
|
||||
static _lastId = 0;
|
||||
static _contextMenu = null;
|
||||
static _contextMenuLastClicked = null;
|
||||
|
||||
bindDmScreen (screen) { this._dmScreen = screen; },
|
||||
static bindDmScreen (screen) { this._dmScreen = screen; }
|
||||
|
||||
_getNextId () { return ++Renderer.hover._lastId; },
|
||||
static _getNextId () { return ++Renderer.hover._lastId; }
|
||||
|
||||
_doInit () {
|
||||
static _doInit () {
|
||||
if (!Renderer.hover._isInit) {
|
||||
Renderer.hover._isInit = true;
|
||||
|
||||
@@ -10956,9 +10992,9 @@ Renderer.hover = {
|
||||
),
|
||||
]);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
cleanTempWindows () {
|
||||
static cleanTempWindows () {
|
||||
for (const [key, meta] of Renderer.hover._eleCache.entries()) {
|
||||
// If this is an element-less "permanent" show which has been closed
|
||||
if (!meta.isPermanent && meta.windowMeta && typeof key === "number") {
|
||||
@@ -10983,20 +11019,20 @@ Renderer.hover = {
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
_doCloseAllWindows ({hoverIdBlocklist = null} = {}) {
|
||||
static _doCloseAllWindows ({hoverIdBlocklist = null} = {}) {
|
||||
Object.entries(Renderer.hover._WINDOW_METAS)
|
||||
.filter(([hoverId, meta]) => hoverIdBlocklist == null || !hoverIdBlocklist.has(Number(hoverId)))
|
||||
.forEach(([, meta]) => meta.doClose());
|
||||
},
|
||||
}
|
||||
|
||||
_getSetMeta (ele) {
|
||||
static _getSetMeta (ele) {
|
||||
if (!Renderer.hover._eleCache.has(ele)) Renderer.hover._eleCache.set(ele, new Renderer.hover.LinkMeta());
|
||||
return Renderer.hover._eleCache.get(ele);
|
||||
},
|
||||
}
|
||||
|
||||
_handleGenericMouseOverStart ({evt, ele}) {
|
||||
static _handleGenericMouseOverStart ({evt, ele}) {
|
||||
// Don't open on small screens unless forced
|
||||
if (Renderer.hover.isSmallScreen(evt) && !evt.shiftKey) return;
|
||||
|
||||
@@ -11013,9 +11049,9 @@ Renderer.hover = {
|
||||
meta.isPermanent = evt.shiftKey;
|
||||
|
||||
return meta;
|
||||
},
|
||||
}
|
||||
|
||||
_doPredefinedShowStart ({entryId}) {
|
||||
static _doPredefinedShowStart ({entryId}) {
|
||||
Renderer.hover.cleanTempWindows();
|
||||
|
||||
const meta = Renderer.hover._getSetMeta(entryId);
|
||||
@@ -11023,10 +11059,20 @@ Renderer.hover = {
|
||||
meta.isPermanent = true;
|
||||
|
||||
return meta;
|
||||
},
|
||||
}
|
||||
|
||||
static getLinkElementData (ele) {
|
||||
return {
|
||||
page: ele.dataset.vetPage,
|
||||
source: ele.dataset.vetSource,
|
||||
hash: ele.dataset.vetHash,
|
||||
preloadId: ele.dataset.vetPreloadId,
|
||||
isFauxPage: ele.dataset.vetIsFauxPage,
|
||||
};
|
||||
}
|
||||
|
||||
// (Baked into render strings)
|
||||
async pHandleLinkMouseOver (evt, ele, opts) {
|
||||
static async pHandleLinkMouseOver (evt, ele, opts) {
|
||||
Renderer.hover._doInit();
|
||||
|
||||
let page, source, hash, preloadId, customHashId, isFauxPage;
|
||||
@@ -11038,11 +11084,13 @@ Renderer.hover = {
|
||||
customHashId = opts.customHashId;
|
||||
isFauxPage = !!opts.isFauxPage;
|
||||
} else {
|
||||
page = ele.dataset.vetPage;
|
||||
source = ele.dataset.vetSource;
|
||||
hash = ele.dataset.vetHash;
|
||||
preloadId = ele.dataset.vetPreloadId;
|
||||
isFauxPage = ele.dataset.vetIsFauxPage;
|
||||
({
|
||||
page,
|
||||
source,
|
||||
hash,
|
||||
preloadId,
|
||||
isFauxPage,
|
||||
} = Renderer.hover.getLinkElementData(ele));
|
||||
}
|
||||
|
||||
let meta = Renderer.hover._handleGenericMouseOverStart({evt, ele});
|
||||
@@ -11130,10 +11178,10 @@ Renderer.hover = {
|
||||
const fnBind = Renderer.hover.getFnBindListenersCompact(page);
|
||||
if (fnBind) fnBind(toRender, $content);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// (Baked into render strings)
|
||||
handleInlineMouseOver (evt, ele, entry, opts) {
|
||||
static handleInlineMouseOver (evt, ele, entry, opts) {
|
||||
Renderer.hover._doInit();
|
||||
|
||||
entry = entry || JSON.parse(ele.dataset.vetEntry);
|
||||
@@ -11173,9 +11221,9 @@ Renderer.hover = {
|
||||
sourceData: entry,
|
||||
},
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
async pGetHoverableFluff (page, source, hash, opts) {
|
||||
static async pGetHoverableFluff (page, source, hash, opts) {
|
||||
// Try to fetch the fluff directly
|
||||
let toRender = await DataLoader.pCacheAndGet(`${page}Fluff`, source, hash, opts);
|
||||
|
||||
@@ -11200,10 +11248,10 @@ Renderer.hover = {
|
||||
}
|
||||
|
||||
return toRender;
|
||||
},
|
||||
}
|
||||
|
||||
// (Baked into render strings)
|
||||
handleLinkMouseLeave (evt, ele) {
|
||||
static handleLinkMouseLeave (evt, ele) {
|
||||
const meta = Renderer.hover._eleCache.get(ele);
|
||||
ele.style.cursor = "";
|
||||
|
||||
@@ -11220,10 +11268,10 @@ Renderer.hover = {
|
||||
meta.windowMeta.doClose();
|
||||
meta.windowMeta = null;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// (Baked into render strings)
|
||||
handleLinkMouseMove (evt, ele) {
|
||||
static handleLinkMouseMove (evt, ele) {
|
||||
const meta = Renderer.hover._eleCache.get(ele);
|
||||
if (!meta || meta.isPermanent) return;
|
||||
|
||||
@@ -11241,7 +11289,7 @@ Renderer.hover = {
|
||||
meta.isPermanent = true;
|
||||
meta.windowMeta.setIsPermanent(true);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* (Baked into render strings)
|
||||
@@ -11252,7 +11300,7 @@ Renderer.hover = {
|
||||
* @param [opts.isBookContent]
|
||||
* @param [opts.isLargeBookContent]
|
||||
*/
|
||||
handlePredefinedMouseOver (evt, ele, entryId, opts) {
|
||||
static handlePredefinedMouseOver (evt, ele, entryId, opts) {
|
||||
opts = opts || {};
|
||||
|
||||
const meta = Renderer.hover._handleGenericMouseOverStart({evt, ele});
|
||||
@@ -11280,9 +11328,24 @@ Renderer.hover = {
|
||||
|
||||
// Reset cursor
|
||||
ele.style.cursor = "";
|
||||
},
|
||||
}
|
||||
|
||||
doPredefinedShow (entryId, opts) {
|
||||
// (Baked into render strings)
|
||||
static handleLinkDragStart (evt, ele) {
|
||||
// Close the window
|
||||
Renderer.hover.handleLinkMouseLeave(evt, ele);
|
||||
|
||||
const {page, source, hash} = Renderer.hover.getLinkElementData(ele);
|
||||
const meta = {
|
||||
type: VeCt.DRAG_TYPE_IMPORT,
|
||||
page,
|
||||
source,
|
||||
hash,
|
||||
};
|
||||
evt.dataTransfer.setData("application/json", JSON.stringify(meta));
|
||||
}
|
||||
|
||||
static doPredefinedShow (entryId, opts) {
|
||||
opts = opts || {};
|
||||
|
||||
const meta = Renderer.hover._doPredefinedShowStart({entryId});
|
||||
@@ -11303,24 +11366,24 @@ Renderer.hover = {
|
||||
sourceData: toRender,
|
||||
},
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
// (Baked into render strings)
|
||||
handlePredefinedMouseLeave (evt, ele) { return Renderer.hover.handleLinkMouseLeave(evt, ele); },
|
||||
static handlePredefinedMouseLeave (evt, ele) { return Renderer.hover.handleLinkMouseLeave(evt, ele); }
|
||||
|
||||
// (Baked into render strings)
|
||||
handlePredefinedMouseMove (evt, ele) { return Renderer.hover.handleLinkMouseMove(evt, ele); },
|
||||
static handlePredefinedMouseMove (evt, ele) { return Renderer.hover.handleLinkMouseMove(evt, ele); }
|
||||
|
||||
_WINDOW_POSITION_PROPS_FROM_EVENT: [
|
||||
static _WINDOW_POSITION_PROPS_FROM_EVENT = [
|
||||
"isFromBottom",
|
||||
"isFromRight",
|
||||
"clientX",
|
||||
"window",
|
||||
"isPreventFlicker",
|
||||
"bcr",
|
||||
],
|
||||
];
|
||||
|
||||
getWindowPositionFromEvent (evt, {isPreventFlicker = false} = {}) {
|
||||
static getWindowPositionFromEvent (evt, {isPreventFlicker = false} = {}) {
|
||||
const ele = evt.target;
|
||||
const win = evt?.view?.window || window;
|
||||
|
||||
@@ -11338,37 +11401,37 @@ Renderer.hover = {
|
||||
isPreventFlicker,
|
||||
bcr,
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
getWindowPositionExact (x, y, evt = null) {
|
||||
static getWindowPositionExact (x, y, evt = null) {
|
||||
return {
|
||||
window: evt?.view?.window || window,
|
||||
mode: "exact",
|
||||
x,
|
||||
y,
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
getWindowPositionExactVisibleBottom (x, y, evt = null) {
|
||||
static getWindowPositionExactVisibleBottom (x, y, evt = null) {
|
||||
return {
|
||||
...Renderer.hover.getWindowPositionExact(x, y, evt),
|
||||
mode: "exactVisibleBottom",
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
_WINDOW_METAS: {},
|
||||
MIN_Z_INDEX: 200,
|
||||
_MAX_Z_INDEX: 300,
|
||||
_DEFAULT_WIDTH_PX: 600,
|
||||
_BODY_SCROLLER_WIDTH_PX: 15,
|
||||
static _WINDOW_METAS = {};
|
||||
static MIN_Z_INDEX = 200;
|
||||
static _MAX_Z_INDEX = 300;
|
||||
static _DEFAULT_WIDTH_PX = 600;
|
||||
static _BODY_SCROLLER_WIDTH_PX = 15;
|
||||
|
||||
_getZIndex () {
|
||||
static _getZIndex () {
|
||||
const zIndices = Object.values(Renderer.hover._WINDOW_METAS).map(it => it.zIndex);
|
||||
if (!zIndices.length) return Renderer.hover.MIN_Z_INDEX;
|
||||
return Math.max(...zIndices);
|
||||
},
|
||||
}
|
||||
|
||||
_getNextZIndex (hoverId) {
|
||||
static _getNextZIndex (hoverId) {
|
||||
const cur = Renderer.hover._getZIndex();
|
||||
// If we're already the highest index, continue to use this index
|
||||
if (hoverId != null && Renderer.hover._WINDOW_METAS[hoverId].zIndex === cur) return cur;
|
||||
@@ -11394,14 +11457,14 @@ Renderer.hover = {
|
||||
|
||||
return Renderer.hover._getNextZIndex(hoverId);
|
||||
} else return out;
|
||||
},
|
||||
}
|
||||
|
||||
_isIntersectRect (r1, r2) {
|
||||
static _isIntersectRect (r1, r2) {
|
||||
return r1.left <= r2.right
|
||||
&& r2.left <= r1.right
|
||||
&& r1.top <= r2.bottom
|
||||
&& r2.top <= r1.bottom;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $content Content to append to the window.
|
||||
@@ -11421,7 +11484,7 @@ Renderer.hover = {
|
||||
* @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.
|
||||
*/
|
||||
getShowWindow ($content, position, opts) {
|
||||
static getShowWindow ($content, position, opts) {
|
||||
opts = opts || {};
|
||||
|
||||
Renderer.hover._doInit();
|
||||
@@ -11632,12 +11695,16 @@ Renderer.hover = {
|
||||
hoverWindow.doMaximize = Renderer.hover._getShowWindow_doMaximize.bind(this, {$brdrTop, $hov});
|
||||
hoverWindow.doZIndexToFront = Renderer.hover._getShowWindow_doZIndexToFront.bind(this, {$hov, hoverWindow, hoverId});
|
||||
|
||||
hoverWindow.getPosition = Renderer.hover._getShowWindow_getPosition.bind(this, {$hov, $wrpContent, position});
|
||||
|
||||
hoverWindow.$setContent = ($contentNxt) => $wrpContent.empty().append($contentNxt);
|
||||
|
||||
if (opts.isPopout) Renderer.hover._getShowWindow_pDoPopout({$hov, position, mouseUpId, mouseMoveId, resizeId, hoverId, opts, hoverWindow, $content});
|
||||
|
||||
return hoverWindow;
|
||||
},
|
||||
}
|
||||
|
||||
_getShowWindow_doClose ({$hov, position, mouseUpId, mouseMoveId, resizeId, hoverId, opts, hoverWindow}) {
|
||||
static _getShowWindow_doClose ({$hov, position, mouseUpId, mouseMoveId, resizeId, hoverId, opts, hoverWindow}) {
|
||||
$hov.remove();
|
||||
$(position.window.document).off(mouseUpId);
|
||||
$(position.window.document).off(mouseMoveId);
|
||||
@@ -11646,9 +11713,9 @@ Renderer.hover = {
|
||||
delete Renderer.hover._WINDOW_METAS[hoverId];
|
||||
|
||||
if (opts.cbClose) opts.cbClose(hoverWindow);
|
||||
},
|
||||
}
|
||||
|
||||
_getShowWindow_handleDragMousedown ({hoverWindow, hoverId, $hov, drag, $wrpContent}, {evt, type}) {
|
||||
static _getShowWindow_handleDragMousedown ({hoverWindow, hoverId, $hov, drag, $wrpContent}, {evt, type}) {
|
||||
if (evt.which === 0 || evt.which === 1) evt.preventDefault();
|
||||
hoverWindow.zIndex = Renderer.hover._getNextZIndex(hoverId);
|
||||
$hov.css({
|
||||
@@ -11669,61 +11736,61 @@ Renderer.hover = {
|
||||
});
|
||||
$hov.css("max-width", "initial");
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
_getShowWindow_isOverHoverTarget ({evt, target}) {
|
||||
static _getShowWindow_isOverHoverTarget ({evt, target}) {
|
||||
return EventUtil.getClientX(evt) >= target.left
|
||||
&& EventUtil.getClientX(evt) <= target.left + target.width
|
||||
&& EventUtil.getClientY(evt) >= target.top
|
||||
&& EventUtil.getClientY(evt) <= target.top + target.height;
|
||||
},
|
||||
}
|
||||
|
||||
_getShowWindow_handleNorthDrag ({$wrpContent, $hov, drag, evt}) {
|
||||
static _getShowWindow_handleNorthDrag ({$wrpContent, $hov, drag, evt}) {
|
||||
const diffY = Math.max(drag.startY - EventUtil.getClientY(evt), 80 - drag.baseHeight); // prevent <80 height, as this will cause the box to move downwards
|
||||
$wrpContent.css("height", drag.baseHeight + diffY);
|
||||
$hov.css("top", drag.baseTop - diffY);
|
||||
drag.startY = EventUtil.getClientY(evt);
|
||||
drag.baseHeight = $wrpContent.height();
|
||||
drag.baseTop = parseFloat($hov.css("top"));
|
||||
},
|
||||
}
|
||||
|
||||
_getShowWindow_handleEastDrag ({$wrpContent, $hov, drag, evt}) {
|
||||
static _getShowWindow_handleEastDrag ({$wrpContent, $hov, drag, evt}) {
|
||||
const diffX = drag.startX - EventUtil.getClientX(evt);
|
||||
$hov.css("width", drag.baseWidth - diffX);
|
||||
drag.startX = EventUtil.getClientX(evt);
|
||||
drag.baseWidth = parseFloat($hov.css("width"));
|
||||
},
|
||||
}
|
||||
|
||||
_getShowWindow_handleSouthDrag ({$wrpContent, $hov, drag, evt}) {
|
||||
static _getShowWindow_handleSouthDrag ({$wrpContent, $hov, drag, evt}) {
|
||||
const diffY = drag.startY - EventUtil.getClientY(evt);
|
||||
$wrpContent.css("height", drag.baseHeight - diffY);
|
||||
drag.startY = EventUtil.getClientY(evt);
|
||||
drag.baseHeight = $wrpContent.height();
|
||||
},
|
||||
}
|
||||
|
||||
_getShowWindow_handleWestDrag ({$wrpContent, $hov, drag, evt}) {
|
||||
static _getShowWindow_handleWestDrag ({$wrpContent, $hov, drag, evt}) {
|
||||
const diffX = Math.max(drag.startX - EventUtil.getClientX(evt), 150 - drag.baseWidth);
|
||||
$hov.css("width", drag.baseWidth + diffX)
|
||||
.css("left", drag.baseLeft - diffX);
|
||||
drag.startX = EventUtil.getClientX(evt);
|
||||
drag.baseWidth = parseFloat($hov.css("width"));
|
||||
drag.baseLeft = parseFloat($hov.css("left"));
|
||||
},
|
||||
}
|
||||
|
||||
_getShowWindow_doToggleMinimizedMaximized ({$brdrTop, $hov}) {
|
||||
static _getShowWindow_doToggleMinimizedMaximized ({$brdrTop, $hov}) {
|
||||
const curState = $brdrTop.attr("data-display-title");
|
||||
const isNextMinified = curState === "false";
|
||||
$brdrTop.attr("data-display-title", isNextMinified);
|
||||
$brdrTop.attr("data-perm", true);
|
||||
$hov.toggleClass("hwin--minified", isNextMinified);
|
||||
},
|
||||
}
|
||||
|
||||
_getShowWindow_doMaximize ({$brdrTop, $hov}) {
|
||||
static _getShowWindow_doMaximize ({$brdrTop, $hov}) {
|
||||
$brdrTop.attr("data-display-title", false);
|
||||
$hov.toggleClass("hwin--minified", false);
|
||||
},
|
||||
}
|
||||
|
||||
async _getShowWindow_pDoPopout ({$hov, position, mouseUpId, mouseMoveId, resizeId, hoverId, opts, hoverWindow, $content}, {evt} = {}) {
|
||||
static async _getShowWindow_pDoPopout ({$hov, position, mouseUpId, mouseMoveId, resizeId, hoverId, opts, hoverWindow, $content}, {evt} = {}) {
|
||||
const dimensions = opts.fnGetPopoutSize ? opts.fnGetPopoutSize() : {width: 600, height: $content.height()};
|
||||
const win = window.open(
|
||||
"",
|
||||
@@ -11802,9 +11869,9 @@ Renderer.hover = {
|
||||
$cpyContent.appendTo(win.$wrpHoverContent.empty());
|
||||
|
||||
Renderer.hover._getShowWindow_doClose({$hov, position, mouseUpId, mouseMoveId, resizeId, hoverId, opts, hoverWindow});
|
||||
},
|
||||
}
|
||||
|
||||
_getShowWindow_setPosition ({$hov, $wrpContent, position}, positionNxt) {
|
||||
static _getShowWindow_setPosition ({$hov, $wrpContent, position}, positionNxt) {
|
||||
switch (positionNxt.mode) {
|
||||
case "autoFromElement": {
|
||||
const bcr = $hov[0].getBoundingClientRect();
|
||||
@@ -11858,9 +11925,9 @@ Renderer.hover = {
|
||||
}
|
||||
|
||||
Renderer.hover._getShowWindow_adjustPosition({$hov, $wrpContent, position});
|
||||
},
|
||||
}
|
||||
|
||||
_getShowWindow_adjustPosition ({$hov, $wrpContent, position}) {
|
||||
static _getShowWindow_adjustPosition ({$hov, $wrpContent, position}) {
|
||||
const eleHov = $hov[0];
|
||||
const wrpContent = $wrpContent[0];
|
||||
|
||||
@@ -11904,22 +11971,29 @@ Renderer.hover = {
|
||||
wrpContent.style.height = `${bcr.height}px`;
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
_getShowWindow_setIsPermanent ({opts, $brdrTop}, isPermanent) {
|
||||
static _getShowWindow_getPosition ({$hov, $wrpContent}) {
|
||||
return {
|
||||
wWrpContent: $wrpContent.width(),
|
||||
hWrapContent: $wrpContent.height(),
|
||||
};
|
||||
}
|
||||
|
||||
static _getShowWindow_setIsPermanent ({opts, $brdrTop}, isPermanent) {
|
||||
opts.isPermanent = isPermanent;
|
||||
$brdrTop.attr("data-perm", isPermanent);
|
||||
},
|
||||
}
|
||||
|
||||
_getShowWindow_setZIndex ({$hov, hoverWindow}, zIndex) {
|
||||
static _getShowWindow_setZIndex ({$hov, hoverWindow}, zIndex) {
|
||||
$hov.css("z-index", zIndex);
|
||||
hoverWindow.zIndex = zIndex;
|
||||
},
|
||||
}
|
||||
|
||||
_getShowWindow_doZIndexToFront ({$hov, hoverWindow, hoverId}) {
|
||||
static _getShowWindow_doZIndexToFront ({$hov, hoverWindow, hoverId}) {
|
||||
const nxtZIndex = Renderer.hover._getNextZIndex(hoverId);
|
||||
Renderer.hover._getShowWindow_setZIndex({$hov, hoverWindow}, nxtZIndex);
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* @param entry
|
||||
@@ -11929,7 +12003,7 @@ Renderer.hover = {
|
||||
* @param [opts.depth]
|
||||
* @param [opts.id]
|
||||
*/
|
||||
getMakePredefinedHover (entry, opts) {
|
||||
static getMakePredefinedHover (entry, opts) {
|
||||
opts = opts || {};
|
||||
|
||||
const id = opts.id ?? Renderer.hover._getNextId();
|
||||
@@ -11943,24 +12017,24 @@ Renderer.hover = {
|
||||
touchStart: (evt, ele) => Renderer.hover.handleTouchStart(evt, ele),
|
||||
show: () => Renderer.hover.doPredefinedShow(id, opts),
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
updatePredefinedHover (id, entry) {
|
||||
static updatePredefinedHover (id, entry) {
|
||||
Renderer.hover._entryCache[id] = entry;
|
||||
},
|
||||
}
|
||||
|
||||
getInlineHover (entry, opts) {
|
||||
static getInlineHover (entry, opts) {
|
||||
return {
|
||||
// Re-use link handlers, as the inline version is a simplified version
|
||||
html: `onmouseover="Renderer.hover.handleInlineMouseOver(event, this)" onmouseleave="Renderer.hover.handleLinkMouseLeave(event, this)" onmousemove="Renderer.hover.handleLinkMouseMove(event, this)" data-vet-entry="${JSON.stringify(entry).qq()}" ${opts ? `data-vet-opts="${JSON.stringify(opts).qq()}"` : ""} ${Renderer.hover.getPreventTouchString()}`,
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
getPreventTouchString () {
|
||||
static getPreventTouchString () {
|
||||
return `ontouchstart="Renderer.hover.handleTouchStart(event, this)"`;
|
||||
},
|
||||
}
|
||||
|
||||
handleTouchStart (evt, ele) {
|
||||
static handleTouchStart (evt, ele) {
|
||||
// on large touchscreen devices only (e.g. iPads)
|
||||
if (!Renderer.hover.isSmallScreen(evt)) {
|
||||
// cache the link location and redirect it to void
|
||||
@@ -11976,10 +12050,10 @@ Renderer.hover = {
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// region entry fetching
|
||||
getEntityLink (
|
||||
static getEntityLink (
|
||||
ent,
|
||||
{
|
||||
displayText = null,
|
||||
@@ -12026,9 +12100,9 @@ Renderer.hover = {
|
||||
while (parts.length && !parts.last()?.length) parts.pop();
|
||||
|
||||
return Renderer.get().render(`{@${Parser.getPropTag(prop || ent.__prop)} ${parts.join("|")}}`);
|
||||
},
|
||||
}
|
||||
|
||||
getRefMetaFromTag (str) {
|
||||
static getRefMetaFromTag (str) {
|
||||
// convert e.g. `"{#itemEntry Ring of Resistance|DMG}"`
|
||||
// to `{type: "refItemEntry", "itemEntry": "Ring of Resistance|DMG"}`
|
||||
str = str.slice(2, -1);
|
||||
@@ -12036,11 +12110,11 @@ Renderer.hover = {
|
||||
const ref = refParts.join(" ");
|
||||
const type = `ref${tag.uppercaseFirst()}`;
|
||||
return {type, [tag]: ref};
|
||||
},
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Apply custom hash IDs
|
||||
async pApplyCustomHashId (page, ent, customHashId) {
|
||||
static async pApplyCustomHashId (page, ent, customHashId) {
|
||||
switch (page) {
|
||||
case UrlUtil.PG_BESTIARY: {
|
||||
const out = await Renderer.monster.pGetModifiedCreature(ent, customHashId);
|
||||
@@ -12052,18 +12126,18 @@ Renderer.hover = {
|
||||
|
||||
default: return ent;
|
||||
}
|
||||
},
|
||||
}
|
||||
// endregion
|
||||
|
||||
getGenericCompactRenderedString (entry, depth = 0) {
|
||||
static getGenericCompactRenderedString (entry, depth = 0) {
|
||||
return `
|
||||
<tr class="text homebrew-hover"><td colspan="6">
|
||||
${Renderer.get().setFirstSection(true).render(entry, depth)}
|
||||
</td></tr>
|
||||
`;
|
||||
},
|
||||
}
|
||||
|
||||
getFnRenderCompact (page, {isStatic = false} = {}) {
|
||||
static getFnRenderCompact (page, {isStatic = false} = {}) {
|
||||
switch (page) {
|
||||
case "generic":
|
||||
case "hover": return Renderer.hover.getGenericCompactRenderedString;
|
||||
@@ -12106,17 +12180,17 @@ Renderer.hover = {
|
||||
if (Renderer[page]?.getCompactRenderedString) return Renderer[page].getCompactRenderedString;
|
||||
return null;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
getFnBindListenersCompact (page) {
|
||||
static getFnBindListenersCompact (page) {
|
||||
switch (page) {
|
||||
case UrlUtil.PG_BESTIARY: return Renderer.monster.bindListenersCompact;
|
||||
case UrlUtil.PG_RACES: return Renderer.race.bindListenersCompact;
|
||||
default: return null;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
_pageToFluffFn (page) {
|
||||
static _pageToFluffFn (page) {
|
||||
switch (page) {
|
||||
case UrlUtil.PG_BESTIARY: return Renderer.monster.pGetFluff;
|
||||
case UrlUtil.PG_ITEMS: return Renderer.item.pGetFluff;
|
||||
@@ -12131,15 +12205,15 @@ Renderer.hover = {
|
||||
case UrlUtil.PG_RECIPES: return Renderer.recipe.pGetFluff;
|
||||
default: return null;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
isSmallScreen (evt) {
|
||||
static isSmallScreen (evt) {
|
||||
if (typeof window === "undefined") return false;
|
||||
|
||||
evt = evt || {};
|
||||
const win = (evt.view || {}).window || window;
|
||||
return win.innerWidth <= 768;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* @param page
|
||||
@@ -12150,7 +12224,7 @@ Renderer.hover = {
|
||||
* @param [opts.fnRender]
|
||||
* @param [renderFnOpts]
|
||||
*/
|
||||
$getHoverContent_stats (page, toRender, opts, renderFnOpts) {
|
||||
static $getHoverContent_stats (page, toRender, opts, renderFnOpts) {
|
||||
opts = opts || {};
|
||||
if (page === UrlUtil.PG_RECIPES) opts = {...MiscUtil.copyFast(opts), isBookContent: true};
|
||||
|
||||
@@ -12163,7 +12237,7 @@ Renderer.hover = {
|
||||
}
|
||||
|
||||
return $out;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* @param page
|
||||
@@ -12172,7 +12246,7 @@ Renderer.hover = {
|
||||
* @param [opts.isBookContent]
|
||||
* @param [renderFnOpts]
|
||||
*/
|
||||
$getHoverContent_fluff (page, toRender, opts, renderFnOpts) {
|
||||
static $getHoverContent_fluff (page, toRender, opts, renderFnOpts) {
|
||||
opts = opts || {};
|
||||
if (page === UrlUtil.PG_RECIPES) opts = {...MiscUtil.copyFast(opts), isBookContent: true};
|
||||
|
||||
@@ -12202,24 +12276,24 @@ Renderer.hover = {
|
||||
}
|
||||
|
||||
return $$`<table class="w-100 stats ${opts.isBookContent ? `stats--book` : ""}">${Renderer.generic.getCompactRenderedString(toRender, renderFnOpts)}</table>`;
|
||||
},
|
||||
}
|
||||
|
||||
$getHoverContent_statsCode (toRender, {isSkipClean = false, title = null} = {}) {
|
||||
static $getHoverContent_statsCode (toRender, {isSkipClean = false, title = null} = {}) {
|
||||
const cleanCopy = isSkipClean ? toRender : DataUtil.cleanJson(MiscUtil.copyFast(toRender));
|
||||
return Renderer.hover.$getHoverContent_miscCode(
|
||||
title || [cleanCopy.name, "Source Data"].filter(Boolean).join(" \u2014 "),
|
||||
JSON.stringify(cleanCopy, null, "\t"),
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
$getHoverContent_miscCode (name, code) {
|
||||
static $getHoverContent_miscCode (name, code) {
|
||||
const toRenderCode = {
|
||||
type: "code",
|
||||
name,
|
||||
preformatted: code,
|
||||
};
|
||||
return $$`<table class="w-100 stats stats--book">${Renderer.get().render(toRenderCode)}</table>`;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* @param toRender
|
||||
@@ -12228,17 +12302,17 @@ Renderer.hover = {
|
||||
* @param [opts.isLargeBookContent]
|
||||
* @param [opts.depth]
|
||||
*/
|
||||
$getHoverContent_generic (toRender, opts) {
|
||||
static $getHoverContent_generic (toRender, opts) {
|
||||
opts = opts || {};
|
||||
|
||||
return $$`<table class="w-100 stats ${opts.isBookContent || opts.isLargeBookContent ? "stats--book" : ""} ${opts.isLargeBookContent ? "stats--book-large" : ""}">${Renderer.hover.getGenericCompactRenderedString(toRender, opts.depth || 0)}</table>`;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* @param evt
|
||||
* @param entity
|
||||
*/
|
||||
doPopoutCurPage (evt, entity) {
|
||||
static doPopoutCurPage (evt, entity) {
|
||||
const page = UrlUtil.getCurrentPage();
|
||||
const $content = Renderer.hover.$getHoverContent_stats(page, entity);
|
||||
Renderer.hover.getShowWindow(
|
||||
@@ -12252,7 +12326,7 @@ Renderer.hover = {
|
||||
sourceData: entity,
|
||||
},
|
||||
);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
53
js/search.js
53
js/search.js
@@ -243,18 +243,49 @@ class SearchPage {
|
||||
// region Render tokens, where available
|
||||
let isImagePopulated = false;
|
||||
|
||||
switch (category) {
|
||||
case Parser.CAT_ID_CREATURE:
|
||||
case Parser.CAT_ID_VEHICLE:
|
||||
case Parser.CAT_ID_OBJECT: {
|
||||
const hasToken = ent.tokenUrl || ent.hasToken;
|
||||
if (hasToken) {
|
||||
const fnGetTokenUrl = category === Parser.CAT_ID_CREATURE ? Renderer.monster.getTokenUrl : category === Parser.CAT_ID_VEHICLE ? Renderer.vehicle.getTokenUrl : Renderer.object.getTokenUrl;
|
||||
const displayTokenImage = (
|
||||
{
|
||||
fnHasToken,
|
||||
fnGetTokenUrl,
|
||||
},
|
||||
ent,
|
||||
) => {
|
||||
if (!fnHasToken(ent)) return;
|
||||
|
||||
isImagePopulated = true;
|
||||
const tokenUrl = fnGetTokenUrl(ent);
|
||||
$dispImage.html(`<img src="${tokenUrl}" class="w-100 h-100" alt="Token Image: ${(ent.name || "").qq()}" ${ent.tokenCredit ? `title="Credit: ${ent.tokenCredit.qq()}"` : ""} loading="lazy">`);
|
||||
}
|
||||
isImagePopulated = true;
|
||||
const tokenUrl = fnGetTokenUrl(ent);
|
||||
$dispImage.html(`<img src="${tokenUrl}" class="w-100 h-100" alt="Token Image: ${(ent.name || "").qq()}" ${ent.tokenCredit ? `title="Credit: ${ent.tokenCredit.qq()}"` : ""} loading="lazy">`);
|
||||
};
|
||||
|
||||
switch (category) {
|
||||
case Parser.CAT_ID_CREATURE: {
|
||||
displayTokenImage(
|
||||
{
|
||||
fnHasToken: Renderer.monster.hasToken,
|
||||
fnGetTokenUrl: Renderer.monster.getTokenUrl,
|
||||
},
|
||||
ent,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case Parser.CAT_ID_VEHICLE: {
|
||||
displayTokenImage(
|
||||
{
|
||||
fnHasToken: Renderer.vehicle.hasToken,
|
||||
fnGetTokenUrl: Renderer.vehicle.getTokenUrl,
|
||||
},
|
||||
ent,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case Parser.CAT_ID_OBJECT: {
|
||||
displayTokenImage(
|
||||
{
|
||||
fnHasToken: Renderer.object.hasToken,
|
||||
fnGetTokenUrl: Renderer.object.getTokenUrl,
|
||||
},
|
||||
ent,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -270,6 +270,7 @@ class _BrewUtil2Base {
|
||||
IS_EDITABLE;
|
||||
PAGE_MANAGE;
|
||||
URL_REPO_DEFAULT;
|
||||
URL_REPO_ROOT_DEFAULT;
|
||||
DISPLAY_NAME;
|
||||
DISPLAY_NAME_PLURAL;
|
||||
DEFAULT_AUTHOR;
|
||||
@@ -351,9 +352,11 @@ class _BrewUtil2Base {
|
||||
async pGetCustomUrl () { return this._storage.pGet(this._STORAGE_KEY_CUSTOM_URL); }
|
||||
|
||||
async pSetCustomUrl (val) {
|
||||
return !val
|
||||
await (!val
|
||||
? this._storage.pRemove(this._STORAGE_KEY_CUSTOM_URL)
|
||||
: this._storage.pSet(this._STORAGE_KEY_CUSTOM_URL, val);
|
||||
: this._storage.pSet(this._STORAGE_KEY_CUSTOM_URL, val));
|
||||
|
||||
location.reload();
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
@@ -1352,6 +1355,7 @@ class _PrereleaseUtil extends _BrewUtil2Base {
|
||||
IS_EDITABLE = false;
|
||||
PAGE_MANAGE = UrlUtil.PG_MANAGE_PRERELEASE;
|
||||
URL_REPO_DEFAULT = VeCt.URL_PRERELEASE;
|
||||
URL_REPO_ROOT_DEFAULT = VeCt.URL_ROOT_PRERELEASE;
|
||||
DISPLAY_NAME = "prerelease content";
|
||||
DISPLAY_NAME_PLURAL = "prereleases";
|
||||
DEFAULT_AUTHOR = "Wizards of the Coast";
|
||||
@@ -1368,11 +1372,11 @@ class _PrereleaseUtil extends _BrewUtil2Base {
|
||||
|
||||
getFileUrl (path, urlRoot) { return DataUtil.prerelease.getFileUrl(path, urlRoot); }
|
||||
|
||||
pLoadTimestamps (brewIndex, src, urlRoot) { return DataUtil.prerelease.pLoadTimestamps(urlRoot); }
|
||||
pLoadTimestamps (urlRoot) { return DataUtil.prerelease.pLoadTimestamps(urlRoot); }
|
||||
|
||||
pLoadPropIndex (brewIndex, src, urlRoot) { return DataUtil.prerelease.pLoadPropIndex(urlRoot); }
|
||||
pLoadPropIndex (urlRoot) { return DataUtil.prerelease.pLoadPropIndex(urlRoot); }
|
||||
|
||||
pLoadMetaIndex (brewIndex, src, urlRoot) { return DataUtil.prerelease.pLoadMetaIndex(urlRoot); }
|
||||
pLoadMetaIndex (urlRoot) { return DataUtil.prerelease.pLoadMetaIndex(urlRoot); }
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
@@ -1413,6 +1417,7 @@ class _BrewUtil2 extends _BrewUtil2Base {
|
||||
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 = "";
|
||||
@@ -1475,11 +1480,11 @@ class _BrewUtil2 extends _BrewUtil2Base {
|
||||
|
||||
getFileUrl (path, urlRoot) { return DataUtil.brew.getFileUrl(path, urlRoot); }
|
||||
|
||||
pLoadTimestamps (brewIndex, src, urlRoot) { return DataUtil.brew.pLoadTimestamps(urlRoot); }
|
||||
pLoadTimestamps (urlRoot) { return DataUtil.brew.pLoadTimestamps(urlRoot); }
|
||||
|
||||
pLoadPropIndex (brewIndex, src, urlRoot) { return DataUtil.brew.pLoadPropIndex(urlRoot); }
|
||||
pLoadPropIndex (urlRoot) { return DataUtil.brew.pLoadPropIndex(urlRoot); }
|
||||
|
||||
pLoadMetaIndex (brewIndex, src, urlRoot) { return DataUtil.brew.pLoadMetaIndex(urlRoot); }
|
||||
pLoadMetaIndex (urlRoot) { return DataUtil.brew.pLoadMetaIndex(urlRoot); }
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
@@ -1862,7 +1867,7 @@ class ManageBrewUi {
|
||||
title: `${this._brewUtil.DISPLAY_NAME.toTitleCase()} Repository URL`,
|
||||
$elePre: $(`<div>
|
||||
<p>Leave blank to use the <a href="${this._brewUtil.URL_REPO_DEFAULT}" rel="noopener noreferrer" target="_blank">default ${this._brewUtil.DISPLAY_NAME} repo</a>.</p>
|
||||
<div>Note that for GitHub URLs, the <code>raw.</code> URL must be used. For example, <code>${this._brewUtil.URL_REPO_DEFAULT.replace(/TheGiddyLimit/g, "YourUsernameHere")}</code></div>
|
||||
<div>Note that for GitHub URLs, the <code>raw.</code> URL must be used. For example, <code>${this._brewUtil.URL_REPO_ROOT_DEFAULT.replace(/TheGiddyLimit/g, "YourUsernameHere")}</code></div>
|
||||
<hr class="hr-3">
|
||||
</div>`),
|
||||
default: customBrewUtl,
|
||||
|
||||
@@ -1998,7 +1998,7 @@ class DataLoader {
|
||||
|
||||
static _isPossibleSource ({parent, sourceClean}) { return parent._isPrereleaseSource({sourceClean}) && !Parser.SOURCE_JSON_TO_FULL[Parser.sourceJsonToJson(sourceClean)]; }
|
||||
static _getBrewUtil () { return typeof PrereleaseUtil !== "undefined" ? PrereleaseUtil : null; }
|
||||
static _pGetSourceIndex () { return DataUtil.prerelease.pLoadSourceIndex(); }
|
||||
static _pGetSourceIndex () { return DataUtil.prerelease.pLoadSourceIndex(PrereleaseUtil.pGetCustomUrl()); }
|
||||
};
|
||||
|
||||
static _BrewPreloader = class extends this._PrereleaseBrewPreloader {
|
||||
@@ -2009,7 +2009,7 @@ class DataLoader {
|
||||
|
||||
static _isPossibleSource ({parent, sourceClean}) { return !parent._isSiteSource({sourceClean}) && !parent._isPrereleaseSource({sourceClean}); }
|
||||
static _getBrewUtil () { return typeof BrewUtil2 !== "undefined" ? BrewUtil2 : null; }
|
||||
static _pGetSourceIndex () { return DataUtil.brew.pLoadSourceIndex(); }
|
||||
static _pGetSourceIndex () { return DataUtil.brew.pLoadSourceIndex(BrewUtil2.pGetCustomUrl()); }
|
||||
};
|
||||
|
||||
static async _pCacheAndGet_getCacheMeta ({pageClean, sourceClean, dataLoader}) {
|
||||
|
||||
@@ -220,6 +220,8 @@ PropOrder._MONSTER = [
|
||||
"dragonAge",
|
||||
|
||||
"tokenUrl",
|
||||
"token",
|
||||
"tokenHref",
|
||||
"tokenCredit",
|
||||
"soundClip",
|
||||
"foundryImg",
|
||||
@@ -515,6 +517,7 @@ PropOrder._ADVENTURE = [
|
||||
|
||||
"group",
|
||||
|
||||
"cover",
|
||||
"coverUrl",
|
||||
"published",
|
||||
"publishedOrder",
|
||||
@@ -544,6 +547,7 @@ PropOrder._BOOK = [
|
||||
|
||||
"group",
|
||||
|
||||
"cover",
|
||||
"coverUrl",
|
||||
"published",
|
||||
"author",
|
||||
@@ -1197,6 +1201,8 @@ PropOrder._VEHICLE = [
|
||||
"reaction",
|
||||
|
||||
"tokenUrl",
|
||||
"token",
|
||||
"tokenHref",
|
||||
"tokenCredit",
|
||||
|
||||
"hasToken",
|
||||
@@ -1484,6 +1490,8 @@ PropOrder._OBJECT = [
|
||||
"actionEntries",
|
||||
|
||||
"tokenUrl",
|
||||
"token",
|
||||
"tokenHref",
|
||||
"tokenCredit",
|
||||
"hasToken",
|
||||
"hasFluff",
|
||||
|
||||
264
js/utils-ui.js
264
js/utils-ui.js
@@ -2384,6 +2384,191 @@ class InputUiUtil {
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
static GenericButtonInfo = class {
|
||||
constructor (
|
||||
{
|
||||
text,
|
||||
clazzIcon,
|
||||
isPrimary,
|
||||
isSmall,
|
||||
isRemember,
|
||||
value,
|
||||
},
|
||||
) {
|
||||
this._text = text;
|
||||
this._clazzIcon = clazzIcon;
|
||||
this._isPrimary = isPrimary;
|
||||
this._isSmall = isSmall;
|
||||
this._isRemember = isRemember;
|
||||
this._value = value;
|
||||
}
|
||||
|
||||
get isPrimary () { return !!this._isPrimary; }
|
||||
|
||||
$getBtn ({doClose, fnRemember, isGlobal, storageKey}) {
|
||||
if (this._isRemember && !storageKey) throw new Error(`No "storageKey" provided for button with saveable value!`);
|
||||
|
||||
return $(`<button class="btn ${this._isPrimary ? "btn-primary" : "btn-default"} ${this._isSmall ? "btn-sm" : ""} ve-flex-v-center mr-3">
|
||||
<span class="${this._clazzIcon} mr-2"></span><span>${this._text}</span>
|
||||
</button>`)
|
||||
.on("click", evt => {
|
||||
evt.stopPropagation();
|
||||
doClose(true, this._value);
|
||||
|
||||
if (!this._isRemember) return;
|
||||
|
||||
if (fnRemember) {
|
||||
fnRemember(this._value);
|
||||
} else {
|
||||
isGlobal
|
||||
? StorageUtil.pSet(storageKey, true)
|
||||
: StorageUtil.pSetForPage(storageKey, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
static async pGetUserGenericButton (
|
||||
{
|
||||
title,
|
||||
buttons,
|
||||
textSkip,
|
||||
htmlDescription,
|
||||
$eleDescription,
|
||||
storageKey,
|
||||
isGlobal,
|
||||
fnRemember,
|
||||
isSkippable,
|
||||
isIgnoreRemembered,
|
||||
},
|
||||
) {
|
||||
if (storageKey && !isIgnoreRemembered) {
|
||||
const prev = await (isGlobal ? StorageUtil.pGet(storageKey) : StorageUtil.pGetForPage(storageKey));
|
||||
if (prev != null) return prev;
|
||||
}
|
||||
|
||||
const {$modalInner, doClose, pGetResolved, doAutoResize: doAutoResizeModal} = await InputUiUtil._pGetShowModal({
|
||||
title: title || "Choose",
|
||||
isMinHeight0: true,
|
||||
});
|
||||
|
||||
const $btns = buttons.map(btnInfo => btnInfo.$getBtn({doClose, fnRemember, isGlobal, storageKey}));
|
||||
|
||||
const $btnSkip = !isSkippable ? null : $(`<button class="btn btn-default btn-sm ml-3"><span class="glyphicon glyphicon-forward"></span><span>${textSkip || "Skip"}</span></button>`)
|
||||
.click(evt => {
|
||||
evt.stopPropagation();
|
||||
doClose(VeCt.SYM_UI_SKIP);
|
||||
});
|
||||
|
||||
if ($eleDescription?.length) $$`<div class="ve-flex w-100 mb-1">${$eleDescription}</div>`.appendTo($modalInner);
|
||||
else if (htmlDescription && htmlDescription.trim()) $$`<div class="ve-flex w-100 mb-1">${htmlDescription}</div>`.appendTo($modalInner);
|
||||
$$`<div class="ve-flex-v-center ve-flex-h-right py-1 px-1">${$btns}${$btnSkip}</div>`.appendTo($modalInner);
|
||||
|
||||
if (doAutoResizeModal) doAutoResizeModal();
|
||||
|
||||
const ixPrimary = buttons.findIndex(btn => btn.isPrimary);
|
||||
if (~ixPrimary) {
|
||||
$btns[ixPrimary].focus();
|
||||
$btns[ixPrimary].select();
|
||||
}
|
||||
|
||||
// region Output
|
||||
const [isDataEntered, out] = await pGetResolved();
|
||||
|
||||
if (typeof isDataEntered === "symbol") return isDataEntered;
|
||||
|
||||
if (!isDataEntered) return null;
|
||||
if (out == null) throw new Error(`Callback must receive a value!`); // sense check
|
||||
return out;
|
||||
// endregion
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [title] Prompt title.
|
||||
* @param [textYesRemember] Text for "yes, and remember" button.
|
||||
* @param [textYes] Text for "yes" button.
|
||||
* @param [textNo] Text for "no" button.
|
||||
* @param [textSkip] Text for "skip" button.
|
||||
* @param [htmlDescription] Description HTML for the modal.
|
||||
* @param [$eleDescription] Description element for the modal.
|
||||
* @param [storageKey] Storage key to use when "remember" options are passed.
|
||||
* @param [isGlobal] If the stored setting is global when "remember" options are passed.
|
||||
* @param [fnRemember] Custom function to run when saving the "yes and remember" option.
|
||||
* @param [isSkippable] If the prompt is skippable.
|
||||
* @param [isAlert] If this prompt is just a notification/alert.
|
||||
* @param [isIgnoreRemembered] If the remembered value should be ignored, in favour of re-prompting the user.
|
||||
* @return {Promise} A promise which resolves to true/false if the user chose, or null otherwise.
|
||||
*/
|
||||
static async pGetUserBoolean (
|
||||
{
|
||||
title,
|
||||
textYesRemember,
|
||||
textYes,
|
||||
textNo,
|
||||
textSkip,
|
||||
htmlDescription,
|
||||
$eleDescription,
|
||||
storageKey,
|
||||
isGlobal,
|
||||
fnRemember,
|
||||
isSkippable,
|
||||
isAlert,
|
||||
isIgnoreRemembered,
|
||||
},
|
||||
) {
|
||||
const buttons = [];
|
||||
|
||||
if (textYesRemember) {
|
||||
buttons.push(
|
||||
new this.GenericButtonInfo({
|
||||
text: textYesRemember,
|
||||
clazzIcon: "glyphicon glyphicon-ok",
|
||||
isRemember: true,
|
||||
isPrimary: true,
|
||||
value: true,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
buttons.push(
|
||||
new this.GenericButtonInfo({
|
||||
text: textYes || "OK",
|
||||
clazzIcon: "glyphicon glyphicon-ok",
|
||||
isPrimary: true,
|
||||
value: true,
|
||||
}),
|
||||
);
|
||||
|
||||
// TODO(Future) migrate usages to `pGetUserGenericButton` (or helper method)
|
||||
if (!isAlert) {
|
||||
buttons.push(
|
||||
new this.GenericButtonInfo({
|
||||
text: textNo || "Cancel",
|
||||
clazzIcon: "glyphicon glyphicon-remove",
|
||||
isSmall: true,
|
||||
value: false,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return this.pGetUserGenericButton({
|
||||
title,
|
||||
buttons,
|
||||
textSkip,
|
||||
htmlDescription,
|
||||
$eleDescription,
|
||||
storageKey,
|
||||
isGlobal,
|
||||
fnRemember,
|
||||
isSkippable,
|
||||
isIgnoreRemembered,
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* @param opts Options.
|
||||
* @param opts.min Minimum value.
|
||||
@@ -2462,85 +2647,6 @@ class InputUiUtil {
|
||||
// endregion
|
||||
}
|
||||
|
||||
/**
|
||||
* @param [opts] Options.
|
||||
* @param [opts.title] Prompt title.
|
||||
* @param [opts.textYesRemember] Text for "yes, and remember" button.
|
||||
* @param [opts.textYes] Text for "yes" button.
|
||||
* @param [opts.textNo] Text for "no" button.
|
||||
* @param [opts.textSkip] Text for "skip" button.
|
||||
* @param [opts.htmlDescription] Description HTML for the modal.
|
||||
* @param [opts.$eleDescription] Description element for the modal.
|
||||
* @param [opts.storageKey] Storage key to use when "remember" options are passed.
|
||||
* @param [opts.isGlobal] If the stored setting is global when "remember" options are passed.
|
||||
* @param [opts.fnRemember] Custom function to run when saving the "yes and remember" option.
|
||||
* @param [opts.isSkippable] If the prompt is skippable.
|
||||
* @param [opts.isAlert] If this prompt is just a notification/alert.
|
||||
* @return {Promise} A promise which resolves to true/false if the user chose, or null otherwise.
|
||||
*/
|
||||
static async pGetUserBoolean (opts) {
|
||||
opts = opts || {};
|
||||
|
||||
if (opts.storageKey) {
|
||||
const prev = await (opts.isGlobal ? StorageUtil.pGet(opts.storageKey) : StorageUtil.pGetForPage(opts.storageKey));
|
||||
if (prev != null) return prev;
|
||||
}
|
||||
|
||||
const $btnTrueRemember = opts.textYesRemember ? $(`<button class="btn btn-primary ve-flex-v-center mr-2"><span class="glyphicon glyphicon-ok mr-2"></span><span>${opts.textYesRemember}</span></button>`)
|
||||
.click(() => {
|
||||
doClose(true, true);
|
||||
if (opts.fnRemember) {
|
||||
opts.fnRemember(true);
|
||||
} else {
|
||||
opts.isGlobal
|
||||
? StorageUtil.pSet(opts.storageKey, true)
|
||||
: StorageUtil.pSetForPage(opts.storageKey, true);
|
||||
}
|
||||
}) : null;
|
||||
|
||||
const $btnTrue = $(`<button class="btn btn-primary ve-flex-v-center mr-3"><span class="glyphicon glyphicon-ok mr-2"></span><span>${opts.textYes || "OK"}</span></button>`)
|
||||
.click(evt => {
|
||||
evt.stopPropagation();
|
||||
doClose(true, true);
|
||||
});
|
||||
|
||||
const $btnFalse = opts.isAlert ? null : $(`<button class="btn btn-default btn-sm ve-flex-v-center"><span class="glyphicon glyphicon-remove mr-2"></span><span>${opts.textNo || "Cancel"}</span></button>`)
|
||||
.click(evt => {
|
||||
evt.stopPropagation();
|
||||
doClose(true, false);
|
||||
});
|
||||
|
||||
const $btnSkip = !opts.isSkippable ? null : $(`<button class="btn btn-default btn-sm ml-3"><span class="glyphicon glyphicon-forward"></span><span>${opts.textSkip || "Skip"}</span></button>`)
|
||||
.click(evt => {
|
||||
evt.stopPropagation();
|
||||
doClose(VeCt.SYM_UI_SKIP);
|
||||
});
|
||||
|
||||
const {$modalInner, doClose, pGetResolved, doAutoResize: doAutoResizeModal} = await InputUiUtil._pGetShowModal({
|
||||
title: opts.title || "Choose",
|
||||
isMinHeight0: true,
|
||||
});
|
||||
|
||||
if (opts.$eleDescription?.length) $$`<div class="ve-flex w-100 mb-1">${opts.$eleDescription}</div>`.appendTo($modalInner);
|
||||
else if (opts.htmlDescription && opts.htmlDescription.trim()) $$`<div class="ve-flex w-100 mb-1">${opts.htmlDescription}</div>`.appendTo($modalInner);
|
||||
$$`<div class="ve-flex-v-center ve-flex-h-right py-1 px-1">${$btnTrueRemember}${$btnTrue}${$btnFalse}${$btnSkip}</div>`.appendTo($modalInner);
|
||||
|
||||
if (doAutoResizeModal) doAutoResizeModal();
|
||||
|
||||
$btnTrue.focus();
|
||||
$btnTrue.select();
|
||||
|
||||
// region Output
|
||||
const [isDataEntered, out] = await pGetResolved();
|
||||
|
||||
if (typeof isDataEntered === "symbol") return isDataEntered;
|
||||
|
||||
if (!isDataEntered) return null;
|
||||
if (out == null) throw new Error(`Callback must receive a value!`); // sanity check
|
||||
return out;
|
||||
// endregion
|
||||
}
|
||||
|
||||
/**
|
||||
* @param opts Options.
|
||||
* @param opts.values Array of values.
|
||||
|
||||
202
js/utils.js
202
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.197.4"/* 5ETOOLS_VERSION__CLOSE */;
|
||||
globalThis.VERSION_NUMBER = /* 5ETOOLS_VERSION__OPEN */"1.199.0"/* 5ETOOLS_VERSION__CLOSE */;
|
||||
globalThis.DEPLOYED_IMG_ROOT = undefined;
|
||||
// for the roll20 script to set
|
||||
globalThis.IS_VTT = false;
|
||||
@@ -300,8 +300,8 @@ globalThis.StrUtil = {
|
||||
// Certain minor words should be left lowercase unless they are the first or last words in the string
|
||||
TITLE_LOWER_WORDS: ["a", "an", "the", "and", "but", "or", "for", "nor", "as", "at", "by", "for", "from", "in", "into", "near", "of", "on", "onto", "to", "with", "over", "von"],
|
||||
// Certain words such as initialisms or acronyms should be left uppercase
|
||||
TITLE_UPPER_WORDS: ["Id", "Tv", "Dm", "Ok", "Npc", "Pc", "Tpk", "Wip", "Dc"],
|
||||
TITLE_UPPER_WORDS_PLURAL: ["Ids", "Tvs", "Dms", "Oks", "Npcs", "Pcs", "Tpks", "Wips", "Dcs"], // (Manually pluralize, to avoid infinite loop)
|
||||
TITLE_UPPER_WORDS: ["Id", "Tv", "Dm", "Ok", "Npc", "Pc", "Tpk", "Wip", "Dc", "D&d"],
|
||||
TITLE_UPPER_WORDS_PLURAL: ["Ids", "Tvs", "Dms", "Oks", "Npcs", "Pcs", "Tpks", "Wips", "Dcs", "D&d"], // (Manually pluralize, to avoid infinite loop)
|
||||
|
||||
IRREGULAR_PLURAL_WORDS: {
|
||||
"cactus": "cacti",
|
||||
@@ -542,6 +542,16 @@ globalThis.SourceUtil = class {
|
||||
return SourceUtil.FILTER_GROUP_STANDARD;
|
||||
}
|
||||
|
||||
static getFilterGroupName (group) {
|
||||
switch (group) {
|
||||
case SourceUtil.FILTER_GROUP_NON_STANDARD: return "Other/Prerelease";
|
||||
case SourceUtil.FILTER_GROUP_HOMEBREW: return "Homebrew";
|
||||
case SourceUtil.FILTER_GROUP_PARTNERED: return "Partnered";
|
||||
case SourceUtil.FILTER_GROUP_STANDARD: return null;
|
||||
default: throw new Error(`Unhandled source filter group "${group}"`);
|
||||
}
|
||||
}
|
||||
|
||||
static getAdventureBookSourceHref (source, page) {
|
||||
if (!source) return null;
|
||||
source = source.toLowerCase();
|
||||
@@ -2052,13 +2062,13 @@ globalThis.MiscUtil = {
|
||||
};
|
||||
|
||||
// EVENT HANDLERS ======================================================================================================
|
||||
globalThis.EventUtil = {
|
||||
_mouseX: 0,
|
||||
_mouseY: 0,
|
||||
_isUsingTouch: false,
|
||||
_isSetCssVars: false,
|
||||
globalThis.EventUtil = class {
|
||||
static _mouseX = 0;
|
||||
static _mouseY = 0;
|
||||
static _isUsingTouch = false;
|
||||
static _isSetCssVars = false;
|
||||
|
||||
init () {
|
||||
static init () {
|
||||
document.addEventListener("mousemove", evt => {
|
||||
EventUtil._mouseX = evt.clientX;
|
||||
EventUtil._mouseY = evt.clientY;
|
||||
@@ -2067,46 +2077,50 @@ globalThis.EventUtil = {
|
||||
document.addEventListener("touchstart", () => {
|
||||
EventUtil._isUsingTouch = true;
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
_eleDocRoot: null,
|
||||
_onMouseMove_setCssVars () {
|
||||
static _eleDocRoot = null;
|
||||
static _onMouseMove_setCssVars () {
|
||||
if (!EventUtil._isSetCssVars) return;
|
||||
|
||||
EventUtil._eleDocRoot = EventUtil._eleDocRoot || document.querySelector(":root");
|
||||
|
||||
EventUtil._eleDocRoot.style.setProperty("--mouse-position-x", EventUtil._mouseX);
|
||||
EventUtil._eleDocRoot.style.setProperty("--mouse-position-y", EventUtil._mouseY);
|
||||
},
|
||||
}
|
||||
|
||||
getClientX (evt) { return evt.touches && evt.touches.length ? evt.touches[0].clientX : evt.clientX; },
|
||||
getClientY (evt) { return evt.touches && evt.touches.length ? evt.touches[0].clientY : evt.clientY; },
|
||||
/* -------------------------------------------- */
|
||||
|
||||
getOffsetY (evt) {
|
||||
static getClientX (evt) { return evt.touches && evt.touches.length ? evt.touches[0].clientX : evt.clientX; }
|
||||
static getClientY (evt) { return evt.touches && evt.touches.length ? evt.touches[0].clientY : evt.clientY; }
|
||||
|
||||
static getOffsetY (evt) {
|
||||
if (!evt.touches?.length) return evt.offsetY;
|
||||
|
||||
const bounds = evt.target.getBoundingClientRect();
|
||||
return evt.targetTouches[0].clientY - bounds.y;
|
||||
},
|
||||
}
|
||||
|
||||
getMousePos () {
|
||||
static getMousePos () {
|
||||
return {x: EventUtil._mouseX, y: EventUtil._mouseY};
|
||||
},
|
||||
}
|
||||
|
||||
isUsingTouch () { return !!EventUtil._isUsingTouch; },
|
||||
/* -------------------------------------------- */
|
||||
|
||||
isInInput (evt) {
|
||||
static isUsingTouch () { return !!EventUtil._isUsingTouch; }
|
||||
|
||||
static isInInput (evt) {
|
||||
return evt.target.nodeName === "INPUT" || evt.target.nodeName === "TEXTAREA"
|
||||
|| evt.target.getAttribute("contenteditable") === "true";
|
||||
},
|
||||
}
|
||||
|
||||
isCtrlMetaKey (evt) {
|
||||
static isCtrlMetaKey (evt) {
|
||||
return evt.ctrlKey || evt.metaKey;
|
||||
},
|
||||
}
|
||||
|
||||
noModifierKeys (evt) { return !evt.ctrlKey && !evt.altKey && !evt.metaKey; },
|
||||
static noModifierKeys (evt) { return !evt.ctrlKey && !evt.altKey && !evt.metaKey; }
|
||||
|
||||
getKeyIgnoreCapsLock (evt) {
|
||||
static getKeyIgnoreCapsLock (evt) {
|
||||
if (!evt.key) return null;
|
||||
if (evt.key.length !== 1) return evt.key;
|
||||
const isCaps = (evt.originalEvent || evt).getModifierState("CapsLock");
|
||||
@@ -2116,7 +2130,29 @@ globalThis.EventUtil = {
|
||||
const isLowerCase = asciiCode >= 97 && asciiCode <= 122;
|
||||
if (!isUpperCase && !isLowerCase) return evt.key;
|
||||
return isUpperCase ? evt.key.toLowerCase() : evt.key.toUpperCase();
|
||||
},
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
// In order of preference/priority.
|
||||
// Note: `"application/json"`, as e.g. Founrdy's TinyMCE blocks drops which are not plain text.
|
||||
static _MIME_TYPES_DROP_JSON = ["application/json", "text/plain"];
|
||||
|
||||
static getDropJson (evt) {
|
||||
let data;
|
||||
for (const mimeType of EventUtil._MIME_TYPES_DROP_JSON) {
|
||||
if (!evt.dataTransfer.types.includes(mimeType)) continue;
|
||||
|
||||
try {
|
||||
const rawJson = evt.dataTransfer.getData(mimeType);
|
||||
if (!rawJson) return;
|
||||
data = JSON.parse(rawJson);
|
||||
} catch (e) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof window !== "undefined") window.addEventListener("load", EventUtil.init);
|
||||
@@ -4639,59 +4675,65 @@ globalThis.DataUtil = {
|
||||
|
||||
static _getCleanMathExpression (str) { return str.replace(/[^-+/*0-9.,]+/g, ""); }
|
||||
|
||||
static _WALKER = null;
|
||||
static resolve ({obj, ent, msgPtFailed = null}) {
|
||||
return JSON.parse(
|
||||
JSON.stringify(obj)
|
||||
.replace(/<\$(?<variable>[^$]+)\$>/g, (...m) => {
|
||||
const [mode, detail] = m.last().variable.split("__");
|
||||
DataUtil.generic.variableResolver._WALKER ||= MiscUtil.getWalker();
|
||||
|
||||
switch (mode) {
|
||||
case "name": return ent.name;
|
||||
case "short_name":
|
||||
case "title_short_name": {
|
||||
return Renderer.monster.getShortName(ent, {isTitleCase: mode === "title_short_name"});
|
||||
return DataUtil.generic.variableResolver._WALKER
|
||||
.walk(
|
||||
obj,
|
||||
{
|
||||
string: str => str.replace(/<\$(?<variable>[^$]+)\$>/g, (...m) => {
|
||||
const [mode, detail] = m.last().variable.split("__");
|
||||
|
||||
switch (mode) {
|
||||
case "name": return ent.name;
|
||||
case "short_name":
|
||||
case "title_short_name": {
|
||||
return Renderer.monster.getShortName(ent, {isTitleCase: mode === "title_short_name"});
|
||||
}
|
||||
|
||||
case "dc":
|
||||
case "spell_dc": {
|
||||
if (!Parser.ABIL_ABVS.includes(detail)) throw new Error(`${msgPtFailed ? `${msgPtFailed} ` : ""} Unknown ability score "${detail}"`);
|
||||
return 8 + Parser.getAbilityModNumber(Number(ent[detail])) + Parser.crToPb(ent.cr);
|
||||
}
|
||||
|
||||
case "to_hit": {
|
||||
if (!Parser.ABIL_ABVS.includes(detail)) throw new Error(`${msgPtFailed ? `${msgPtFailed} ` : ""} Unknown ability score "${detail}"`);
|
||||
const total = Parser.crToPb(ent.cr) + Parser.getAbilityModNumber(Number(ent[detail]));
|
||||
return total >= 0 ? `+${total}` : total;
|
||||
}
|
||||
|
||||
case "damage_mod": {
|
||||
if (!Parser.ABIL_ABVS.includes(detail)) throw new Error(`${msgPtFailed ? `${msgPtFailed} ` : ""} Unknown ability score "${detail}"`);
|
||||
const total = Parser.getAbilityModNumber(Number(ent[detail]));
|
||||
return total === 0 ? "" : total > 0 ? ` + ${total}` : ` - ${Math.abs(total)}`;
|
||||
}
|
||||
|
||||
case "damage_avg": {
|
||||
const replaced = detail
|
||||
.replace(/\b(?<abil>str|dex|con|int|wis|cha)\b/gi, (...m) => Parser.getAbilityModNumber(Number(ent[m.last().abil])))
|
||||
.replace(/\bsize_mult\b/g, () => this._getSizeMult(this._getSize({ent})));
|
||||
|
||||
// eslint-disable-next-line no-eval
|
||||
return Math.floor(eval(this._getCleanMathExpression(replaced)));
|
||||
}
|
||||
|
||||
case "size_mult": {
|
||||
const mult = this._getSizeMult(this._getSize({ent}));
|
||||
|
||||
if (!detail) return mult;
|
||||
|
||||
// eslint-disable-next-line no-eval
|
||||
return Math.floor(eval(`${mult} * ${this._getCleanMathExpression(detail)}`));
|
||||
}
|
||||
|
||||
default: return m[0];
|
||||
}
|
||||
|
||||
case "dc":
|
||||
case "spell_dc": {
|
||||
if (!Parser.ABIL_ABVS.includes(detail)) throw new Error(`${msgPtFailed ? `${msgPtFailed} ` : ""} Unknown ability score "${detail}"`);
|
||||
return 8 + Parser.getAbilityModNumber(Number(ent[detail])) + Parser.crToPb(ent.cr);
|
||||
}
|
||||
|
||||
case "to_hit": {
|
||||
if (!Parser.ABIL_ABVS.includes(detail)) throw new Error(`${msgPtFailed ? `${msgPtFailed} ` : ""} Unknown ability score "${detail}"`);
|
||||
const total = Parser.crToPb(ent.cr) + Parser.getAbilityModNumber(Number(ent[detail]));
|
||||
return total >= 0 ? `+${total}` : total;
|
||||
}
|
||||
|
||||
case "damage_mod": {
|
||||
if (!Parser.ABIL_ABVS.includes(detail)) throw new Error(`${msgPtFailed ? `${msgPtFailed} ` : ""} Unknown ability score "${detail}"`);
|
||||
const total = Parser.getAbilityModNumber(Number(ent[detail]));
|
||||
return total === 0 ? "" : total > 0 ? ` + ${total}` : ` - ${Math.abs(total)}`;
|
||||
}
|
||||
|
||||
case "damage_avg": {
|
||||
const replaced = detail
|
||||
.replace(/\b(?<abil>str|dex|con|int|wis|cha)\b/gi, (...m) => Parser.getAbilityModNumber(Number(ent[m.last().abil])))
|
||||
.replace(/\bsize_mult\b/g, () => this._getSizeMult(this._getSize({ent})));
|
||||
|
||||
// eslint-disable-next-line no-eval
|
||||
return Math.floor(eval(this._getCleanMathExpression(replaced)));
|
||||
}
|
||||
|
||||
case "size_mult": {
|
||||
const mult = this._getSizeMult(this._getSize({ent}));
|
||||
|
||||
if (!detail) return mult;
|
||||
|
||||
// eslint-disable-next-line no-eval
|
||||
return Math.floor(eval(`${mult} * ${this._getCleanMathExpression(detail)}`));
|
||||
}
|
||||
|
||||
default: return m[0];
|
||||
}
|
||||
}),
|
||||
);
|
||||
}),
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -5929,7 +5971,11 @@ globalThis.RollerUtil = {
|
||||
|
||||
getColRollType (colLabel) {
|
||||
if (typeof colLabel !== "string") return false;
|
||||
colLabel = Renderer.stripTags(colLabel);
|
||||
|
||||
colLabel = colLabel.trim();
|
||||
const mDice = /^{@dice (?<exp>[^}|]+)([^}]+)?}$/.exec(colLabel);
|
||||
|
||||
colLabel = mDice ? mDice.groups.exp : Renderer.stripTags(colLabel);
|
||||
|
||||
if (Renderer.dice.lang.getTree3(colLabel)) return RollerUtil.ROLL_COL_STANDARD;
|
||||
|
||||
|
||||
@@ -118,8 +118,7 @@ class VehiclesPage extends ListPage {
|
||||
(this._$dispToken = this._$dispToken || $(`#float-token`)).empty();
|
||||
|
||||
if (ent.vehicleType) {
|
||||
const hasToken = ent.tokenUrl || ent.hasToken;
|
||||
if (hasToken) {
|
||||
if (Renderer.vehicle.hasToken(ent)) {
|
||||
const imgLink = Renderer.vehicle.getTokenUrl(ent);
|
||||
this._$dispToken.append(`<a href="${imgLink}" target="_blank" rel="noopener noreferrer"><img src="${imgLink}" id="token_image" class="token" alt="Token Image: ${(ent.name || "").qq()}" ${ent.tokenCredit ? `title="Credit: ${ent.tokenCredit.qq()}"` : ""} loading="lazy"></a>`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user