This commit is contained in:
TheGiddyLimit
2024-01-01 19:34:49 +00:00
parent 332769043f
commit 8117ebddc5
1748 changed files with 2544409 additions and 1 deletions

View File

@@ -0,0 +1,39 @@
import {EncounterBuilderCacheBase} from "../encounterbuilder/encounterbuilder-cache.js";
export class EncounterBuilderCacheBestiaryPage extends EncounterBuilderCacheBase {
_cache = null;
constructor ({bestiaryPage}) {
super();
this._bestiaryPage = bestiaryPage;
}
_build () {
if (this._cache != null) return;
// create a map of {XP: [monster list]}
this._cache = this._getBuiltCache();
}
_getBuiltCache () {
const out = {};
this._bestiaryPage.list_.visibleItems
.map(li => this._bestiaryPage.dataList_[li.ix])
.filter(mon => !this._isUnwantedCreature(mon))
.forEach(mon => {
(out[Parser.crToXpNumber(mon.cr)] ||= []).push(mon);
});
return out;
}
reset () { this._cache = null; }
getCreaturesByXp (xp) {
this._build();
return this._cache[xp] || [];
}
getXpKeys () {
this._build();
return Object.keys(this._cache).map(it => Number(it));
}
}

View File

@@ -0,0 +1,37 @@
import {EncounterBuilderComponent} from "../encounterbuilder/encounterbuilder-component.js";
export class EncounterBuilderComponentBestiary extends EncounterBuilderComponent {
getSublistPluginState () {
return {
// region Special handling for `creatureMetas`
items: this._state.creatureMetas
.map(creatureMeta => ({
h: creatureMeta.getHash(),
c: creatureMeta.count,
customHashId: creatureMeta.customHashId || undefined,
l: creatureMeta.isLocked,
})),
sources: this._state.creatureMetas
.map(creatureMeta => creatureMeta.creature.source)
.unique(),
// endregion
...Object.fromEntries(
Object.entries(this._state)
.filter(([k]) => k !== "creatureMetas")
.map(([k, v]) => [k, MiscUtil.copyFast(v)]),
),
};
}
/** Get a generic representation of the encounter, which can be used elsewhere. */
static getStateFromExportedSublist ({exportedSublist}) {
exportedSublist = MiscUtil.copyFast(exportedSublist);
const out = this._getDefaultState();
Object.keys(out)
.filter(k => exportedSublist[k] != null)
.forEach(k => out[k] = exportedSublist[k]);
return out;
}
}

View File

@@ -0,0 +1,305 @@
import {EncounterBuilderHelpers} from "../utils-list-bestiary.js";
import {EncounterBuilderCreatureMeta} from "../encounterbuilder/encounterbuilder-models.js";
import {EncounterBuilderComponentBestiary} from "./bestiary-encounterbuilder-component.js";
/**
* Serialize/deserialize state from the encounter builder.
*/
export class EncounterBuilderSublistPlugin extends SublistPlugin {
constructor ({sublistManager, encounterBuilder, encounterBuilderComp}) {
super();
this._sublistManager = sublistManager;
this._encounterBuilder = encounterBuilder;
this._encounterBuilderComp = encounterBuilderComp;
}
/* -------------------------------------------- */
async pLoadData ({exportedSublist, isMemoryOnly}) {
const nxt = {};
// Allow URLified versions of keys
const keyLookup = this._encounterBuilderComp.getDefaultStateKeys()
.mergeMap(k => ({[k]: k, [k.toUrlified()]: k}));
if (exportedSublist) {
Object.entries(exportedSublist)
.filter(([, v]) => v != null)
.map(([k, v]) => {
// Only add specific keys, as we do not want to track e.g. sublist state
k = keyLookup[k];
if (!k) return null;
return [k, v];
})
.filter(Boolean)
// Always process `colsExtraAdvanced` first (if available), as used in `playersAdvanced`
.sort(([kA], [kB]) => kA === "colsExtraAdvanced" ? -1 : kB === "colsExtraAdvanced" ? 1 : 0)
.forEach(([k, v]) => {
if (isMemoryOnly) return nxt[k] = MiscUtil.copyFast(v);
// When loading from non-memory sources, expand the data
switch (k) {
case "playersSimple": return nxt[k] = v.map(it => EncounterBuilderComponentBestiary.getDefaultPlayerRow_simple(it));
case "colsExtraAdvanced": return nxt[k] = v.map(it => EncounterBuilderComponentBestiary.getDefaultColExtraAdvanced(it));
case "playersAdvanced": return nxt[k] = v.map(it => EncounterBuilderComponentBestiary.getDefaultPlayerRow_advanced({
...it,
extras: it.extras.map(x => EncounterBuilderComponentBestiary.getDefaultPlayerAdvancedExtra(x)),
colsExtraAdvanced: nxt.colsExtraAdvanced || this._encounterBuilderComp.colsExtraAdvanced,
}));
default: return nxt[k] = v;
}
});
if (nxt.playersSimple) {
nxt.playersSimple
.forEach(wrapped => {
wrapped.entity.count = wrapped.entity.count || 1;
wrapped.entity.level = wrapped.entity.level || 1;
});
}
if (nxt.playersAdvanced) {
nxt.playersAdvanced
.forEach(wrapped => {
wrapped.entity.name = wrapped.entity.name || "";
wrapped.entity.level = wrapped.entity.level || 1;
wrapped.entity.extraCols = wrapped.entity.extraCols
|| (nxt.colsExtraAdvanced || this._encounterBuilderComp.colsExtraAdvanced.map(() => ""));
});
}
}
// Note that we do not set `creatureMetas` here, as `onSublistUpdate` handles this
this._encounterBuilderComp.setStateFromLoaded(nxt);
}
async _pLoadData_getCreatureMetas ({exportedSublist}) {
if (!exportedSublist.items?.length) return [];
return exportedSublist.items
.pSerialAwaitMap(async serialItem => {
const {
entity,
entityBase,
count,
isLocked,
customHashId,
} = await SublistManager.pDeserializeExportedSublistItem(serialItem);
return new EncounterBuilderCreatureMeta({
creature: entity,
count,
isLocked,
customHashId: customHashId,
baseCreature: entityBase,
});
});
}
static async pMutLegacyData ({exportedSublist, isMemoryOnly}) {
if (!exportedSublist) return;
// region Legacy Bestiary Encounter Builder format
if (exportedSublist.p) {
exportedSublist.playersSimple = exportedSublist.p.map(it => EncounterBuilderComponentBestiary.getDefaultPlayerRow_simple(it));
if (!isMemoryOnly) this._mutExternalize({obj: exportedSublist, k: "playersSimple"});
delete exportedSublist.p;
}
if (exportedSublist.l) {
Object.assign(exportedSublist, exportedSublist.l);
delete exportedSublist.l;
}
if (exportedSublist.a != null) {
exportedSublist.isAdvanced = !!exportedSublist.a;
delete exportedSublist.a;
}
if (exportedSublist.c) {
exportedSublist.colsExtraAdvanced = exportedSublist.c.map(name => EncounterBuilderComponentBestiary.getDefaultColExtraAdvanced({name}));
if (!isMemoryOnly) this._mutExternalize({obj: exportedSublist, k: "colsExtraAdvanced"});
delete exportedSublist.c;
}
if (exportedSublist.d) {
exportedSublist.playersAdvanced = exportedSublist.d.map(({n, l, x}) => EncounterBuilderComponentBestiary.getDefaultPlayerRow_advanced({
name: n,
level: l,
extras: x.map(value => EncounterBuilderComponentBestiary.getDefaultPlayerAdvancedExtra({value})),
colsExtraAdvanced: exportedSublist.colsExtraAdvanced,
}));
if (!isMemoryOnly) this._mutExternalize({obj: exportedSublist, k: "playersAdvanced"});
delete exportedSublist.d;
}
// endregion
// region Legacy "reference" format
// These are general save manager properties, but we set them here, as encounter data was the only thing to make
// use of this system.
if (exportedSublist.bestiaryId) {
exportedSublist.saveId = exportedSublist.bestiaryId;
delete exportedSublist.bestiaryId;
}
if (exportedSublist.isRef) {
exportedSublist.managerClient_isReferencable = true;
exportedSublist.managerClient_isLoadAsCopy = false;
}
delete exportedSublist.isRef;
// endregion
}
async pMutLegacyData ({exportedSublist, isMemoryOnly}) {
await this.constructor.pMutLegacyData({exportedSublist, isMemoryOnly});
}
/* -------------------------------------------- */
async pMutSaveableData ({exportedSublist, isForce = false, isMemoryOnly = false}) {
if (!isForce && !this._encounterBuilder.isActive()) return;
[
"playersSimple",
"isAdvanced",
"colsExtraAdvanced",
"playersAdvanced",
].forEach(k => {
exportedSublist[k] = MiscUtil.copyFast(this._encounterBuilderComp[k]);
if (isMemoryOnly) return;
this.constructor._mutExternalize({obj: exportedSublist, k});
});
}
static _WALKER_EXTERNALIZE = null;
static _HANDLERS_EXTERNALIZE = {
array: (arr) => {
if (arr.some(it => !it.id || !it.entity)) return arr;
return arr.map(({entity}) => entity);
},
};
static _mutExternalize ({obj, k}) {
this._WALKER_EXTERNALIZE = this._WALKER_EXTERNALIZE || MiscUtil.getWalker();
obj[k] = this._WALKER_EXTERNALIZE.walk(
obj[k],
this._HANDLERS_EXTERNALIZE,
);
}
/* -------------------------------------------- */
async pDoInitNewState ({prevExportableSublist, evt}) {
// If SHIFT pressed, reset players
const nxt = {
playersSimple: evt.shiftKey ? [] : MiscUtil.copyFast(prevExportableSublist.playersSimple),
playersAdvanced: evt.shiftKey ? [] : MiscUtil.copyFast(prevExportableSublist.playersAdvanced),
};
this._encounterBuilderComp.setPartialStateFromLoaded(nxt);
}
/* -------------------------------------------- */
getDownloadName () {
if (!this._encounterBuilder.isActive()) return null;
return "encounter";
}
getDownloadFileType () {
if (!this._encounterBuilder.isActive()) return null;
return "encounter";
}
getUploadFileTypes ({downloadFileTypeBase}) {
if (!this._encounterBuilder.isActive()) return null;
return [this.getDownloadFileType(), downloadFileTypeBase];
}
/* -------------------------------------------- */
onSublistUpdate () {
this._encounterBuilder.withSublistSyncSuppressed(() => {
// Note that we only update `creatureMetas` here, as this only triggers on direct updates to the underlying sublist.
// For everything else, the `pLoadData` path is used.
this._encounterBuilderComp.creatureMetas = this._sublistManager.sublistItems
.map(sublistItem => EncounterBuilderHelpers.getSublistedCreatureMeta({sublistItem}));
});
}
}
class EncounterBuilderLegacyStorageMigration {
static _VERSION = 2;
static _STORAGE_KEY_LEGACY_SAVED_ENCOUNTERS = "ENCOUNTER_SAVED_STORAGE";
static _STORAGE_KEY_LEGACY_ENCOUNTER = "ENCOUNTER_STORAGE";
static _STORAGE_KEY_LEGACY_ENCOUNTER_MIGRATION_VERSION = "ENCOUNTER_STORAGE_MIGRATION_VERSION";
static _STORAGE_KEY_LEGACY_SAVED_ENCOUNTER_MIGRATION_VERSION = "ENCOUNTER_SAVED_STORAGE_MIGRATION_VERSION";
static register () {
SublistPersistor._LEGACY_MIGRATOR.registerLegacyMigration(this._pMigrateSublist.bind(this));
SaveManager._LEGACY_MIGRATOR.registerLegacyMigration(this._pMigrateSaves.bind(this));
}
static async _pMigrateSublist (stored) {
let version = await StorageUtil.pGet(this._STORAGE_KEY_LEGACY_ENCOUNTER_MIGRATION_VERSION);
if (version && version >= 2) return false;
if (!version) version = 1;
const encounter = await StorageUtil.pGet(this._STORAGE_KEY_LEGACY_ENCOUNTER);
if (!encounter) return false;
Object.entries(encounter)
.forEach(([k, v]) => {
if (stored[k] != null) return;
stored[k] = v;
});
await EncounterBuilderSublistPlugin.pMutLegacyData({exportedSublist: stored});
await StorageUtil.pSet(this._STORAGE_KEY_LEGACY_ENCOUNTER_MIGRATION_VERSION, this._VERSION);
JqueryUtil.doToast(`Migrated active Bestiary encounter from version ${version} to version ${this._VERSION}!`);
return true;
}
static async _pMigrateSaves (stored) {
let version = await StorageUtil.pGet(this._STORAGE_KEY_LEGACY_SAVED_ENCOUNTER_MIGRATION_VERSION);
if (version && version >= 2) return false;
if (!version) version = 1;
const encounters = await StorageUtil.pGet(this._STORAGE_KEY_LEGACY_SAVED_ENCOUNTERS);
if (!encounters) return false;
await Object.entries(encounters.savedEncounters || {})
.pSerialAwaitMap(async ([id, enc]) => {
const legacyData = MiscUtil.copyFast(enc.data || {});
legacyData.name = enc.name || "(Unnamed encounter)";
legacyData.saveId = id;
legacyData.manager_isSaved = true;
await EncounterBuilderSublistPlugin.pMutLegacyData({exportedSublist: legacyData});
const tgt = MiscUtil.getOrSet(stored, "state", "saves", []);
tgt.push({
id: CryptUtil.uid(),
entity: legacyData,
});
});
await StorageUtil.pSet(this._STORAGE_KEY_LEGACY_SAVED_ENCOUNTER_MIGRATION_VERSION, this._VERSION);
JqueryUtil.doToast(`Migrated saved Bestiary encounters from version ${version} to version ${this._VERSION}!`);
return true;
}
}
EncounterBuilderLegacyStorageMigration.register();

View File

@@ -0,0 +1,363 @@
import {EncounterBuilderUi} from "../encounterbuilder/encounterbuilder-ui.js";
import {EncounterBuilderCreatureMeta} from "../encounterbuilder/encounterbuilder-models.js";
import {EncounterBuilderHelpers} from "../utils-list-bestiary.js";
export class EncounterBuilderUiBestiary extends EncounterBuilderUi {
static _HASH_KEY = "encounterbuilder";
_isSuspendSyncToSublist = false;
constructor ({cache, comp, bestiaryPage, sublistManager}) {
super({cache, comp});
this._bestiaryPage = bestiaryPage;
this._sublistManager = sublistManager;
this._lock = new VeLock();
this._cachedTitle = null;
}
initUi () {
document.getElementById("stat-tabs").classList.add("best-ecgen__hidden");
document.getElementById("float-token").classList.add("best-ecgen__hidden");
document.getElementById("wrp-pagecontent").classList.add("best-ecgen__hidden");
$(`#btn-encounterbuild`).click(() => Hist.setSubhash(this.constructor._HASH_KEY, true));
}
render () {
super.render({
$parentRandomAndAdjust: $("#wrp-encounterbuild-random-and-adjust"),
$parentGroupAndDifficulty: $("#wrp-encounterbuild-group-and-difficulty"),
});
this._render_saveLoad();
}
_render_saveLoad () {
const $btnSave = $(`<button class="btn btn-default btn-xs">Save Encounter</button>`)
.click(evt => this._sublistManager.pHandleClick_save(evt));
const $btnLoad = $(`<button class="btn btn-default btn-xs">Load Encounter</button>`)
.click(evt => this._sublistManager.pHandleClick_load(evt));
$$(document.getElementById("best-ecgen__wrp-save-controls"))`<div class="ve-flex-col">
<div class="ve-flex-h-right btn-group">
${$btnSave}
${$btnLoad}
</div>
</div>`;
}
_handleClickCopyAsText (evt) {
let xpTotal = 0;
const ptsCreature = this._sublistManager.sublistItems
.sort((a, b) => SortUtil.ascSortLower(a.name, b.name))
.map(it => {
xpTotal += Parser.crToXpNumber(it.values.cr) * it.data.count;
return `${it.data.count}× ${it.name}`;
});
const ptXp = `${xpTotal.toLocaleString()} XP`;
if (evt.shiftKey) {
MiscUtil.pCopyTextToClipboard([...ptsCreature, ptXp].join("\n")).then(null);
} else {
MiscUtil.pCopyTextToClipboard(`${ptsCreature.join(", ")} (${ptXp})`).then(null);
}
JqueryUtil.showCopiedEffect(evt.currentTarget);
}
_handleClickBackToStatblocks () {
Hist.setSubhash(this.constructor._HASH_KEY, null);
}
_render_groupAndDifficulty ({rdState, $parentGroupAndDifficulty}) {
super._render_groupAndDifficulty({rdState, $parentGroupAndDifficulty});
const $btnSaveToUrl = $(`<button class="btn btn-default btn-xs mr-2">Save to URL</button>`)
.click(() => this._sublistManager.pHandleClick_download({isUrl: true, $eleCopyEffect: $btnSaveToUrl}));
const $btnSaveToFile = $(`<button class="btn btn-default btn-xs">Save to File</button>`)
.click(() => this._sublistManager.pHandleClick_download());
const $btnLoadFromFile = $(`<button class="btn btn-default btn-xs">Load from File</button>`)
.click(evt => this._sublistManager.pHandleClick_upload({isAdditive: evt.shiftKey}));
const $btnCopyAsText = $(`<button class="btn btn-default btn-xs mr-2" title="SHIFT for Multi-Line Format">Copy as Text</button>`).click((evt) => this._handleClickCopyAsText(evt));
const $btnReset = $(`<button class="btn btn-danger btn-xs" title="SHIFT to Reset Players">Reset</button>`)
.click((evt) => this._sublistManager.pHandleClick_new(evt));
const $btnBackToStatblocks = $(`<button class="btn btn-success btn-xs">Back to Stat Blocks</button>`).click((evt) => this._handleClickBackToStatblocks(evt));
$$`<div class="ve-flex-col w-100">
<hr class="hr-1">
<div class="ve-flex-v-center mb-2">
${$btnSaveToUrl}
<div class="btn-group ve-flex-v-center mr-2">
${$btnSaveToFile}
${$btnLoadFromFile}
</div>
${$btnCopyAsText}
${$btnReset}
</div>
<div class="ve-flex">
${$btnBackToStatblocks}
</div>
</div>`
.appendTo($parentGroupAndDifficulty);
}
/* -------------------------------------------- */
withSublistSyncSuppressed (fn) {
try {
this._isSuspendSyncToSublist = true;
fn();
} finally {
this._isSuspendSyncToSublist = false;
}
}
/**
* On encounter builder state change, save to the sublist
*/
_render_hk_doUpdateExternalStates () {
if (this._isSuspendSyncToSublist) return;
this._render_hk_pDoUpdateExternalStates().then(null);
}
async _render_hk_pDoUpdateExternalStates () {
try {
await this._lock.pLock();
await this._render_hk_pDoUpdateExternalStates_();
} finally {
this._lock.unlock();
}
}
async _render_hk_pDoUpdateExternalStates_ () {
const nxtState = await this._sublistManager.pGetExportableSublist({isMemoryOnly: true});
Object.assign(nxtState, this._comp.getSublistPluginState());
await this._sublistManager.pDoLoadExportedSublist(nxtState, {isMemoryOnly: true});
}
/* -------------------------------------------- */
onSublistChange ({$dispCrTotal}) {
const encounterXpInfo = EncounterBuilderCreatureMeta.getEncounterXpInfo(this._comp.creatureMetas, this._getPartyMeta());
const monCount = this._sublistManager.sublistItems.map(it => it.data.count).sum();
$dispCrTotal.html(`${monCount} creature${monCount === 1 ? "" : "s"}; ${encounterXpInfo.baseXp.toLocaleString()} XP (<span class="help" title="Adjusted Encounter XP">Enc</span>: ${(encounterXpInfo.adjustedXp).toLocaleString()} XP)`);
}
/* -------------------------------------------- */
resetCache () { this._cache.reset(); }
isActive () {
return Hist.getSubHash(this.constructor._HASH_KEY) === "true";
}
_showBuilder () {
this._cachedTitle = this._cachedTitle || document.title;
document.title = "Encounter Builder - 5etools";
$(document.body).addClass("best__ecgen-active");
this._bestiaryPage.doDeselectAll();
this._sublistManager.doSublistDeselectAll();
}
_hideBuilder () {
if (this._cachedTitle) {
document.title = this._cachedTitle;
this._cachedTitle = null;
}
$(document.body).removeClass("best__ecgen-active");
}
_handleClick ({evt, mode, entity}) {
if (mode === "add") {
return this._sublistManager.pDoSublistAdd({entity, doFinalize: true, addCount: evt.shiftKey ? 5 : 1});
}
return this._sublistManager.pDoSublistSubtract({entity, subtractCount: evt.shiftKey ? 5 : 1});
}
async _pHandleShuffleClick ({evt, sublistItem}) {
const creatureMeta = EncounterBuilderHelpers.getSublistedCreatureMeta({sublistItem});
this._doShuffle({creatureMeta});
}
handleSubhash () {
if (Hist.getSubHash(this.constructor._HASH_KEY) === "true") this._showBuilder();
else this._hideBuilder();
}
async doStatblockMouseOver ({evt, ele, source, hash, customHashId}) {
return Renderer.hover.pHandleLinkMouseOver(
evt,
ele,
{
page: UrlUtil.PG_BESTIARY,
source,
hash,
customHashId,
},
);
}
static getTokenHoverMeta (mon) {
const hasToken = mon.tokenUrl || mon.hasToken;
if (!hasToken) return null;
return Renderer.hover.getMakePredefinedHover(
{
type: "image",
href: {
type: "external",
url: Renderer.monster.getTokenUrl(mon),
},
data: {
hoverTitle: `Token \u2014 ${mon.name}`,
},
},
{isBookContent: true},
);
}
static _getFauxMon (name, source, scaledTo) {
return {name, source, _isScaledCr: scaledTo != null, _scaledCr: scaledTo};
}
async pDoCrChange ($iptCr, monScaled, scaledTo) {
if (!$iptCr) return; // Should never occur, but if the creature has a non-adjustable CR, this field will not exist
try {
await this._lock.pLock();
await this._pDoCrChange({$iptCr, monScaled, scaledTo});
} finally {
this._lock.unlock();
}
}
async _pDoCrChange ({$iptCr, monScaled, scaledTo}) {
// Fetch original
const mon = await DataLoader.pCacheAndGetHash(
UrlUtil.PG_BESTIARY,
UrlUtil.autoEncodeHash(monScaled),
);
const baseCr = mon.cr.cr || mon.cr;
if (baseCr == null) return;
const baseCrNum = Parser.crToNumber(baseCr);
const targetCr = $iptCr.val();
if (!Parser.isValidCr(targetCr)) {
JqueryUtil.doToast({
content: `"${$iptCr.val()}" is not a valid Challenge Rating! Please enter a valid CR (0-30). For fractions, "1/X" should be used.`,
type: "danger",
});
$iptCr.val(Parser.numberToCr(scaledTo || baseCr));
return;
}
const targetCrNum = Parser.crToNumber(targetCr);
if (targetCrNum === scaledTo) return;
const state = await this._sublistManager.pGetExportableSublist({isForceIncludePlugins: true, isMemoryOnly: true});
const toFindHash = UrlUtil.autoEncodeHash(mon);
const toFindUid = !(scaledTo == null || baseCrNum === scaledTo) ? Renderer.monster.getCustomHashId(this.constructor._getFauxMon(mon.name, mon.source, scaledTo)) : null;
const ixCurrItem = state.items.findIndex(it => {
if (scaledTo == null || scaledTo === baseCrNum) return !it.customHashId && it.h === toFindHash;
else return it.customHashId === toFindUid;
});
if (!~ixCurrItem) throw new Error(`Could not find previously sublisted item!`);
const toFindNxtUid = baseCrNum !== targetCrNum ? Renderer.monster.getCustomHashId(this.constructor._getFauxMon(mon.name, mon.source, targetCrNum)) : null;
const nextItem = state.items.find(it => {
if (targetCrNum === baseCrNum) return !it.customHashId && it.h === toFindHash;
else return it.customHashId === toFindNxtUid;
});
// if there's an existing item with a matching UID (or lack of), merge into it
if (nextItem) {
const curr = state.items[ixCurrItem];
nextItem.c = `${Number(nextItem.c || 1) + Number(curr.c || 1)}`;
state.items.splice(ixCurrItem, 1);
} else {
// if we're returning to the original CR, wipe the existing UID. Otherwise, adjust it
if (targetCrNum === baseCrNum) delete state.items[ixCurrItem].customHashId;
else state.items[ixCurrItem].customHashId = Renderer.monster.getCustomHashId(this.constructor._getFauxMon(mon.name, mon.source, targetCrNum));
}
await this._sublistManager.pDoLoadExportedSublist(state, {isMemoryOnly: true});
}
getButtons (monId) {
return e_({
tag: "span",
clazz: `best-ecgen__visible col-1 no-wrap pl-0 btn-group`,
click: evt => {
evt.preventDefault();
evt.stopPropagation();
},
children: [
e_({
tag: "button",
title: `Add (SHIFT for 5)`,
clazz: `btn btn-success btn-xs best-ecgen__btn-list`,
click: evt => this._handleClick({evt, entity: this._bestiaryPage.dataList_[monId], mode: "add"}),
children: [
e_({
tag: "span",
clazz: `glyphicon glyphicon-plus`,
}),
],
}),
e_({
tag: "button",
title: `Subtract (SHIFT for 5)`,
clazz: `btn btn-danger btn-xs best-ecgen__btn-list`,
click: evt => this._handleClick({evt, entity: this._bestiaryPage.dataList_[monId], mode: "subtract"}),
children: [
e_({
tag: "span",
clazz: `glyphicon glyphicon-minus`,
}),
],
}),
],
});
}
getSublistButtonsMeta (sublistItem) {
const $btnAdd = $(`<button title="Add (SHIFT for 5)" class="btn btn-success btn-xs best-ecgen__btn-list"><span class="glyphicon glyphicon-plus"></span></button>`)
.click(evt => this._handleClick({evt, entity: sublistItem.data.entity, mode: "add"}));
const $btnSub = $(`<button title="Subtract (SHIFT for 5)" class="btn btn-danger btn-xs best-ecgen__btn-list"><span class="glyphicon glyphicon-minus"></span></button>`)
.click(evt => this._handleClick({evt, entity: sublistItem.data.entity, mode: "subtract"}));
const $btnRandomize = $(`<button title="Randomize Monster" class="btn btn-default btn-xs best-ecgen__btn-list"><span class="glyphicon glyphicon-random"></span></button>`)
.click(evt => this._pHandleShuffleClick({evt, sublistItem}));
const $btnLock = $(`<button title="Lock Monster against Randomizing/Adjusting" class="btn btn-default btn-xs best-ecgen__btn-list"><span class="glyphicon glyphicon-lock"></span></button>`)
.click(() => this._sublistManager.pSetDataEntry({sublistItem, key: "isLocked", value: !sublistItem.data.isLocked}))
.toggleClass("active", sublistItem.data.isLocked);
const $wrp = $$`<span class="best-ecgen__visible col-1-5 no-wrap pl-0 btn-group">
${$btnAdd}
${$btnSub}
${$btnRandomize}
${$btnLock}
</span>`
.click(evt => {
evt.preventDefault();
evt.stopPropagation();
});
return {
$wrp,
fnUpdate: () => $btnLock.toggleClass("active", sublistItem.data.isLocked),
};
}
}