From 70c07c0a1af6884e4f69b52d0eec2edf13fed383 Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Fri, 15 Mar 2024 23:53:05 -0400 Subject: [PATCH] fix mobile item picker rendering --- ui/core/components/gear_picker.tsx | 802 +++++++++++++++++------------ 1 file changed, 481 insertions(+), 321 deletions(-) diff --git a/ui/core/components/gear_picker.tsx b/ui/core/components/gear_picker.tsx index 0019807f59..f4c10925c6 100644 --- a/ui/core/components/gear_picker.tsx +++ b/ui/core/components/gear_picker.tsx @@ -1,4 +1,27 @@ +import { Tooltip } from 'bootstrap'; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { element, fragment, ref } from 'tsx-vanilla'; + +import { setItemQualityCssClass } from '../css_utils'; +import { IndividualSimUI } from '../individual_sim_ui.js'; +import { Player } from '../player'; +import { Class, GemColor, ItemQuality, ItemSlot, ItemSpec, ItemType } from '../proto/common'; +import { + DatabaseFilters, + UIEnchant as Enchant, + UIGem as Gem, + UIItem as Item, +} from '../proto/ui.js'; +import { ActionId } from '../proto_utils/action_id'; +import { getEnchantDescription, getUniqueEnchantString } from '../proto_utils/enchants'; +import { EquippedItem } from '../proto_utils/equipped_item'; +import { gemMatchesSocket, getEmptyGemSocketIconUrl } from '../proto_utils/gems'; import { difficultyNames, professionNames, slotNames } from '../proto_utils/names.js'; +import { Stats } from '../proto_utils/stats'; +import { Sim } from '../sim.js'; +import { SimUI } from '../sim_ui'; +import { EventID, TypedEvent } from '../typed_event'; +import { formatDeltaTextElem } from '../utils'; import { BaseModal } from './base_modal'; import { Component } from './component'; import { FiltersMenu } from './filters_menu'; @@ -9,67 +32,36 @@ import { makeShowEPValuesSelector, makeShowMatchingGemsSelector, } from './other_inputs'; - -import { setItemQualityCssClass } from '../css_utils'; -import { Player } from '../player'; -import { Sim } from '../sim.js'; -import { SimUI } from '../sim_ui'; -import { EventID, TypedEvent } from '../typed_event'; -import { formatDeltaTextElem } from '../utils'; - -import { ActionId } from '../proto_utils/action_id'; -import { getEnchantDescription, getUniqueEnchantString } from '../proto_utils/enchants'; -import { EquippedItem } from '../proto_utils/equipped_item'; -import { getEmptyGemSocketIconUrl, gemMatchesSocket } from '../proto_utils/gems'; -import { Stats } from '../proto_utils/stats'; - -import { - Class, - GemColor, - ItemQuality, - ItemSlot, - ItemSpec, - ItemType, -} from '../proto/common'; -import { - DatabaseFilters, - UIEnchant as Enchant, - UIGem as Gem, - UIItem as Item, -} from '../proto/ui.js'; -import { IndividualSimUI } from '../individual_sim_ui.js'; -import { Tooltip } from 'bootstrap'; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -import { element, fragment, ref } from 'tsx-vanilla'; - import { Clusterize } from './virtual_scroll/clusterize.js'; const EP_TOOLTIP = ` EP (Equivalence Points) is way of comparing items by multiplying the raw stats of an item with your current stat weights. More EP does not necessarily mean more DPS, as EP doesn't take into account stat caps and non-linear stat calculations. -` +`; const createHeroicLabel = () => { - return ([H]); -} + return [H]; +}; -const createGemContainer = (socketColor: GemColor ,gem : Gem|null) => { +const createGemContainer = (socketColor: GemColor, gem: Gem | null) => { const gemIconElem = ref(); - let gemContainer = ( + const gemContainer = (
- +
); if (gem != null) { - ActionId.fromItemId(gem.id).fill().then(filledId => { - gemIconElem.value!.src = filledId.iconUrl; - }); + ActionId.fromItemId(gem.id) + .fill() + .then(filledId => { + gemIconElem.value!.src = filledId.iconUrl; + }); } return gemContainer; -} +}; export class GearPicker extends Component { // ItemSlot is used as the index @@ -121,25 +113,37 @@ export class ItemRenderer extends Component { readonly enchantElem: HTMLAnchorElement; readonly socketsContainerElem: HTMLElement; - constructor(parent: HTMLElement, player: Player) { - super(parent, 'item-picker-root'); + constructor(parent: HTMLElement, root: HTMLElement, player: Player) { + super(parent, 'item-picker-root', root); this.player = player; - let iconElem = ref(); - let nameElem = ref(); - let enchantElem = ref(); - let sce = ref(); + const iconElem = ref(); + const nameElem = ref(); + const enchantElem = ref(); + const sce = ref(); this.rootElem.appendChild( <> - +
- -
- + +
+
- + , ); this.iconElem = iconElem.value!; @@ -175,10 +179,13 @@ export class ItemRenderer extends Component { this.player.setWowheadData(newItem, this.iconElem); this.player.setWowheadData(newItem, this.nameElem); - newItem.asActionId().fill().then(filledId => { - filledId.setBackgroundAndHref(this.iconElem); - filledId.setWowheadHref(this.nameElem); - }); + newItem + .asActionId() + .fill() + .then(filledId => { + filledId.setBackgroundAndHref(this.iconElem); + filledId.setWowheadHref(this.nameElem); + }); if (newItem.enchant) { getEnchantDescription(newItem.enchant).then(description => { @@ -196,9 +203,12 @@ export class ItemRenderer extends Component { } newItem.allSocketColors().forEach((socketColor, gemIdx) => { - let gemContainer = createGemContainer(socketColor, newItem.gems[gemIdx]); + const gemContainer = createGemContainer(socketColor, newItem.gems[gemIdx]); - if (gemIdx == newItem.numPossibleSockets - 1 && [ItemType.ItemTypeWrist, ItemType.ItemTypeHands].includes(newItem.item.type)) { + if ( + gemIdx == newItem.numPossibleSockets - 1 && + [ItemType.ItemTypeWrist, ItemType.ItemTypeHands].includes(newItem.item.type) + ) { const updateProfession = () => { if (this.player.isBlacksmithing()) { gemContainer.classList.remove('hide'); @@ -232,7 +242,7 @@ export class ItemPicker extends Component { this.slot = slot; this.simUI = simUI; this.player = player; - this.itemElem = new ItemRenderer(this.rootElem, player); + this.itemElem = new ItemRenderer(parent, this.rootElem, player); this.item = player.getEquippedItem(slot); player.sim.waitForInit().then(() => { @@ -280,7 +290,9 @@ export class ItemPicker extends Component { if (newItem != null) { this.itemElem.update(newItem); } else { - this.itemElem.iconElem.style.backgroundImage = `url('${getEmptySlotIconUrl(this.slot)}')`; + this.itemElem.iconElem.style.backgroundImage = `url('${getEmptySlotIconUrl( + this.slot, + )}')`; } this._equippedItem = newItem; @@ -293,13 +305,12 @@ export class ItemPicker extends Component { equippedItem: this._equippedItem, eligibleItems: this._items, eligibleEnchants: this._enchants, - gearData: gearData - }) + gearData: gearData, + }); } } export class IconItemSwapPicker extends Component { - private readonly iconAnchor: HTMLAnchorElement; private readonly socketsContainerElem: HTMLElement; private readonly player: Player; @@ -310,7 +321,7 @@ export class IconItemSwapPicker extends Component { private _enchants: Array = []; constructor(parent: HTMLElement, simUI: SimUI, player: Player, slot: ItemSlot) { - super(parent, 'icon-picker-root') + super(parent, 'icon-picker-root'); this.rootElem.classList.add('icon-picker'); this.player = player; this.slot = slot; @@ -320,8 +331,8 @@ export class IconItemSwapPicker extends Component { this.iconAnchor.target = '_blank'; this.rootElem.prepend(this.iconAnchor); - this.socketsContainerElem = document.createElement('div') - this.socketsContainerElem.classList.add('item-picker-sockets-container') + this.socketsContainerElem = document.createElement('div'); + this.socketsContainerElem.classList.add('item-picker-sockets-container'); this.iconAnchor.appendChild(this.socketsContainerElem); player.sim.waitForInit().then(() => { @@ -329,11 +340,11 @@ export class IconItemSwapPicker extends Component { this._enchants = this.player.getEnchants(slot); const gearData = { equipItem: (eventID: EventID, newItem: EquippedItem | null) => { - player.equipItemSwapitem(eventID, this.slot, newItem) + player.equipItemSwapitem(eventID, this.slot, newItem); }, getEquippedItem: () => player.getItemSwapItem(this.slot), changeEvent: player.itemSwapChangeEmitter, - } + }; this.iconAnchor.addEventListener('click', (event: Event) => { event.preventDefault(); @@ -356,29 +367,30 @@ export class IconItemSwapPicker extends Component { update(newItem: EquippedItem | null) { this.iconAnchor.style.backgroundImage = `url('${getEmptySlotIconUrl(this.slot)}')`; this.iconAnchor.removeAttribute('data-wowhead'); - this.iconAnchor.href = "#"; + this.iconAnchor.href = '#'; this.socketsContainerElem.innerText = ''; if (newItem) { - this.iconAnchor.classList.add("active") + this.iconAnchor.classList.add('active'); newItem.asActionId().fillAndSet(this.iconAnchor, true, true); this.player.setWowheadData(newItem, this.iconAnchor); newItem.allSocketColors().forEach((socketColor, gemIdx) => { - this.socketsContainerElem.appendChild(createGemContainer(socketColor, newItem.gems[gemIdx])); + this.socketsContainerElem.appendChild( + createGemContainer(socketColor, newItem.gems[gemIdx]), + ); }); } else { - this.iconAnchor.classList.remove("active") + this.iconAnchor.classList.remove('active'); } } - } export interface GearData { - equipItem: (eventID: EventID, equippedItem: EquippedItem | null) => void, - getEquippedItem: () => EquippedItem | null, - changeEvent: TypedEvent, + equipItem: (eventID: EventID, equippedItem: EquippedItem | null) => void; + getEquippedItem: () => EquippedItem | null; + changeEvent: TypedEvent; } export enum SelectorModalTabs { @@ -390,12 +402,12 @@ export enum SelectorModalTabs { } interface SelectorModalConfig { - selectedTab: SelectorModalTabs - slot: ItemSlot, - equippedItem: EquippedItem | null, - eligibleItems: Array, - eligibleEnchants: Array, - gearData: GearData + selectedTab: SelectorModalTabs; + slot: ItemSlot; + equippedItem: EquippedItem | null; + eligibleItems: Array; + eligibleEnchants: Array; + gearData: GearData; } export class SelectorModal extends BaseModal { @@ -407,7 +419,12 @@ export class SelectorModal extends BaseModal { private readonly tabsElem: HTMLElement; private readonly contentElem: HTMLElement; - constructor(parent: HTMLElement, simUI: SimUI, player: Player, config: SelectorModalConfig) { + constructor( + parent: HTMLElement, + simUI: SimUI, + player: Player, + config: SelectorModalConfig, + ) { super(parent, 'selector-modal'); this.simUI = simUI; @@ -417,12 +434,17 @@ export class SelectorModal extends BaseModal { window.scrollTo({ top: 0 }); - this.header!.insertAdjacentElement('afterbegin',
    ); + this.header!.insertAdjacentElement( + 'afterbegin', +
      , + ); - this.body.appendChild(
      ); + this.body.appendChild(
      ); this.tabsElem = this.rootElem.querySelector('.selector-modal-tabs') as HTMLElement; - this.contentElem = this.rootElem.querySelector('.selector-modal-tab-content') as HTMLElement; + this.contentElem = this.rootElem.querySelector( + '.selector-modal-tab-content', + ) as HTMLElement; this.setData(); @@ -436,21 +458,23 @@ export class SelectorModal extends BaseModal { to open your sim options, then click the "Restore Defaults". - - ) + , + ); } // Could be 'Items' 'Enchants' or 'Gem1'-'Gem3' openTabName(name: string) { - Array.from(this.tabsElem.getElementsByClassName("selector-modal-item-tab")).forEach(elem => { - if (elem.getAttribute("data-content-id") == name + "-tab") { - (elem as HTMLElement).click(); - } - }); + Array.from(this.tabsElem.getElementsByClassName('selector-modal-item-tab')).forEach( + elem => { + if (elem.getAttribute('data-content-id') == name + '-tab') { + (elem as HTMLElement).click(); + } + }, + ); } openTab(idx: number) { - const elems = this.tabsElem.getElementsByClassName("selector-modal-item-tab"); + const elems = this.tabsElem.getElementsByClassName('selector-modal-item-tab'); (elems[idx] as HTMLElement).click(); } @@ -489,7 +513,8 @@ export class SelectorModal extends BaseModal { eventID => { gearData.equipItem(eventID, null); this.removeTabs('Gem'); - }); + }, + ); this.addTab( 'Enchants', @@ -497,7 +522,9 @@ export class SelectorModal extends BaseModal { return { item: enchant, id: enchant.effectId, - actionId: enchant.spellId ? ActionId.fromSpellId(enchant.spellId) : ActionId.fromItemId(enchant.itemId), + actionId: enchant.spellId + ? ActionId.fromSpellId(enchant.spellId) + : ActionId.fromItemId(enchant.itemId), name: enchant.name, quality: enchant.quality, phase: enchant.phase || 1, @@ -516,21 +543,20 @@ export class SelectorModal extends BaseModal { GemColor.GemColorUnknown, eventID => { const equippedItem = gearData.getEquippedItem(); - if (equippedItem) - gearData.equipItem(eventID, equippedItem.withEnchant(null)); - }); + if (equippedItem) gearData.equipItem(eventID, equippedItem.withEnchant(null)); + }, + ); this.addGemTabs(slot, equippedItem, gearData); } protected override onShow(e: Event) { // Only refresh opened tab - let t = e.target! as HTMLElement; - let tab = t.querySelector('.active')!.dataset.contentId!; + const t = e.target! as HTMLElement; + const tab = t.querySelector('.active')!.dataset.contentId!; if (tab.includes('Item')) { this.ilists[0].sizeRefresh(); - } - else if (tab.includes('Enchant')) { + } else if (tab.includes('Enchant')) { this.ilists[1].sizeRefresh(); } } @@ -540,70 +566,80 @@ export class SelectorModal extends BaseModal { return; } - const socketBonusEP = this.player.computeStatsEP(new Stats(equippedItem.item.socketBonus)) / (equippedItem.item.gemSockets.length || 1); - equippedItem.curSocketColors(this.player.isBlacksmithing()).forEach((socketColor, socketIdx) => { - this.addTab( - 'Gem ' + (socketIdx + 1), - this.player.getGems(socketColor).map((gem: Gem) => { - return { - item: gem, - id: gem.id, - actionId: ActionId.fromItemId(gem.id), - name: gem.name, - quality: gem.quality, - phase: gem.phase, - heroic: false, - baseEP: this.player.computeStatsEP(new Stats(gem.stats)), - ignoreEPFilter: true, - onEquip: (eventID, gem: Gem) => { - const equippedItem = gearData.getEquippedItem(); - if (equippedItem) - gearData.equipItem(eventID, equippedItem.withGem(gem, socketIdx)); - }, - }; - }), - gem => { - let gemEP = this.player.computeGemEP(gem); - if (gemMatchesSocket(gem, socketColor)) { - gemEP += socketBonusEP; - } - return gemEP; - }, - equippedItem => equippedItem?.gems[socketIdx], - socketColor, - eventID => { - const equippedItem = gearData.getEquippedItem(); - if (equippedItem) - gearData.equipItem(eventID, equippedItem.withGem(null, socketIdx)); - }, - tabAnchor => { - let gemContainer = createGemContainer(socketColor, null); - tabAnchor.appendChild(gemContainer); - tabAnchor.classList.add('selector-modal-tab-gem'); - - const gemElem = tabAnchor.querySelector('.gem-icon') as HTMLElement; - const emptySocketUrl = getEmptyGemSocketIconUrl(socketColor) - - const updateGemIcon = () => { + const socketBonusEP = + this.player.computeStatsEP(new Stats(equippedItem.item.socketBonus)) / + (equippedItem.item.gemSockets.length || 1); + equippedItem + .curSocketColors(this.player.isBlacksmithing()) + .forEach((socketColor, socketIdx) => { + this.addTab( + 'Gem ' + (socketIdx + 1), + this.player.getGems(socketColor).map((gem: Gem) => { + return { + item: gem, + id: gem.id, + actionId: ActionId.fromItemId(gem.id), + name: gem.name, + quality: gem.quality, + phase: gem.phase, + heroic: false, + baseEP: this.player.computeStatsEP(new Stats(gem.stats)), + ignoreEPFilter: true, + onEquip: (eventID, gem: Gem) => { + const equippedItem = gearData.getEquippedItem(); + if (equippedItem) + gearData.equipItem( + eventID, + equippedItem.withGem(gem, socketIdx), + ); + }, + }; + }), + gem => { + let gemEP = this.player.computeGemEP(gem); + if (gemMatchesSocket(gem, socketColor)) { + gemEP += socketBonusEP; + } + return gemEP; + }, + equippedItem => equippedItem?.gems[socketIdx], + socketColor, + eventID => { const equippedItem = gearData.getEquippedItem(); - const gem = equippedItem?.gems[socketIdx]; + if (equippedItem) + gearData.equipItem(eventID, equippedItem.withGem(null, socketIdx)); + }, + tabAnchor => { + const gemContainer = createGemContainer(socketColor, null); + tabAnchor.appendChild(gemContainer); + tabAnchor.classList.add('selector-modal-tab-gem'); - if (gem) { - gemElem.classList.remove('hide'); - ActionId.fromItemId(gem.id).fill().then(filledId => { - gemElem.setAttribute('src', filledId.iconUrl); - }); - } else { - gemElem.classList.add('hide'); - gemElem.setAttribute('src', emptySocketUrl); - } - }; + const gemElem = tabAnchor.querySelector('.gem-icon') as HTMLElement; + const emptySocketUrl = getEmptyGemSocketIconUrl(socketColor); - gearData.changeEvent.on(updateGemIcon); - this.addOnDisposeCallback(() => gearData.changeEvent.off(updateGemIcon)); - updateGemIcon(); - }); - }); + const updateGemIcon = () => { + const equippedItem = gearData.getEquippedItem(); + const gem = equippedItem?.gems[socketIdx]; + + if (gem) { + gemElem.classList.remove('hide'); + ActionId.fromItemId(gem.id) + .fill() + .then(filledId => { + gemElem.setAttribute('src', filledId.iconUrl); + }); + } else { + gemElem.classList.add('hide'); + gemElem.setAttribute('src', emptySocketUrl); + } + }; + + gearData.changeEvent.on(updateGemIcon); + this.addOnDisposeCallback(() => gearData.changeEvent.off(updateGemIcon)); + updateGemIcon(); + }, + ); + }); } /** @@ -616,10 +652,11 @@ export class SelectorModal extends BaseModal { label: string, itemData: Array>, computeEP: (item: T) => number, - equippedToItemFn: (equippedItem: EquippedItem | null) => (T | null | undefined), + equippedToItemFn: (equippedItem: EquippedItem | null) => T | null | undefined, socketColor: GemColor, onRemove: (eventID: EventID) => void, - setTabContent?: (tabElem: HTMLAnchorElement) => void) { + setTabContent?: (tabElem: HTMLAnchorElement) => void, + ) { if (itemData.length == 0) { return; } @@ -636,17 +673,16 @@ export class SelectorModal extends BaseModal { className={`nav-link selector-modal-item-tab ${selected ? 'active' : ''}`} dataset={{ label: label, - contentId:tabContentId, - bsToggle:'tab', - bsTarget:`#${tabContentId}`, + contentId: tabContentId, + bsToggle: 'tab', + bsTarget: `#${tabContentId}`, }} attributes={{ - role:'tab', - 'aria-selected':selected, + role: 'tab', + 'aria-selected': selected, }} - type="button" - > - + type="button"> + , ); if (setTabContent) { @@ -660,7 +696,7 @@ export class SelectorModal extends BaseModal { return; } - let ilist = new ItemList( + const ilist = new ItemList( this.contentElem, this.simUI, this.config, @@ -681,11 +717,17 @@ export class SelectorModal extends BaseModal { this.addGemTabs(slot, gearData.getEquippedItem(), gearData); } }, - ) + ); - let invokeUpdate = () => { ilist.updateSelected() } - let applyFilter = () => { ilist.applyFilters() } - let hideOrShowEPValues = () => { ilist.hideOrShowEPValues() } + const invokeUpdate = () => { + ilist.updateSelected(); + }; + const applyFilter = () => { + ilist.applyFilters(); + }; + const hideOrShowEPValues = () => { + ilist.hideOrShowEPValues(); + }; // Add event handlers gearData.changeEvent.on(invokeUpdate); @@ -694,22 +736,23 @@ export class SelectorModal extends BaseModal { this.player.sim.showEPValuesChangeEmitter.on(hideOrShowEPValues); this.addOnDisposeCallback(() => { - gearData.changeEvent.off(invokeUpdate) + gearData.changeEvent.off(invokeUpdate); this.player.sim.phaseChangeEmitter.off(applyFilter); this.player.sim.filtersChangeEmitter.off(applyFilter); this.player.sim.showEPValuesChangeEmitter.off(hideOrShowEPValues); ilist.dispose(); }); - tabAnchor.value!.addEventListener('shown.bs.tab', (event) => { - ilist.sizeRefresh() + tabAnchor.value!.addEventListener('shown.bs.tab', event => { + ilist.sizeRefresh(); }); this.ilists.push(ilist); } private removeTabs(labelSubstring: string) { - const tabElems = Array.prototype.slice.call(this.tabsElem.getElementsByClassName('selector-modal-item-tab')) + const tabElems = Array.prototype.slice + .call(this.tabsElem.getElementsByClassName('selector-modal-item-tab')) .filter(tab => tab.dataset.label.includes(labelSubstring)); const contentElems = tabElems @@ -722,21 +765,21 @@ export class SelectorModal extends BaseModal { } export interface ItemData { - item: T, - name: string, - id: number, - actionId: ActionId, - quality: ItemQuality, - phase: number, - baseEP: number, - ignoreEPFilter: boolean, - heroic: boolean, - onEquip: (eventID: EventID, item: T) => void, + item: T; + name: string; + id: number; + actionId: ActionId; + quality: ItemQuality; + phase: number; + baseEP: number; + ignoreEPFilter: boolean; + heroic: boolean; + onEquip: (eventID: EventID, item: T) => void; } interface ItemDataWithIdx { - idx: number, - data: ItemData, + idx: number; + data: ItemData; } const emptySlotIcons: Record = { @@ -773,7 +816,7 @@ export class ItemList { private searchInput: HTMLInputElement; private socketColor: GemColor; private computeEP: (item: T) => number; - private equippedToItemFn: (equippedItem: EquippedItem | null) => (T | null | undefined); + private equippedToItemFn: (equippedItem: EquippedItem | null) => T | null | undefined; private gearData: GearData; private tabContent: Element; private onItemClick: (itemData: ItemData) => void; @@ -787,10 +830,11 @@ export class ItemList { label: string, itemData: Array>, computeEP: (item: T) => number, - equippedToItemFn: (equippedItem: EquippedItem | null) => (T | null | undefined), + equippedToItemFn: (equippedItem: EquippedItem | null) => T | null | undefined, socketColor: GemColor, onRemove: (eventID: EventID) => void, - onItemClick: (itemData: ItemData) => void) { + onItemClick: (itemData: ItemData) => void, + ) { this.label = label; this.player = player; this.itemData = itemData; @@ -811,28 +855,43 @@ export class ItemList { this.tabContent = (
      + className={`selector-modal-tab-pane tab-pane fade ${ + selected ? 'active show' : '' + }`}>
      - - {label == 'Items' && } + + {label == 'Items' && ( + + )}
      - - + +
      - - + + @@ -845,85 +904,148 @@ export class ItemList { parent.appendChild(this.tabContent); new Tooltip(epButton.value!, { - title: EP_TOOLTIP + title: EP_TOOLTIP, }); - const show1hWeaponsSelector = makeShow1hWeaponsSelector(this.tabContent.getElementsByClassName('selector-modal-show-1h-weapons')[0] as HTMLElement, player.sim); - const show2hWeaponsSelector = makeShow2hWeaponsSelector(this.tabContent.getElementsByClassName('selector-modal-show-2h-weapons')[0] as HTMLElement, player.sim); - if (!(label == 'Items' && (slot == ItemSlot.ItemSlotMainHand || (slot == ItemSlot.ItemSlotOffHand && player.getClass() == Class.ClassWarrior)))) { - (this.tabContent.getElementsByClassName('selector-modal-show-1h-weapons')[0] as HTMLElement).style.display = 'none'; - (this.tabContent.getElementsByClassName('selector-modal-show-2h-weapons')[0] as HTMLElement).style.display = 'none'; + const show1hWeaponsSelector = makeShow1hWeaponsSelector( + this.tabContent.getElementsByClassName( + 'selector-modal-show-1h-weapons', + )[0] as HTMLElement, + player.sim, + ); + const show2hWeaponsSelector = makeShow2hWeaponsSelector( + this.tabContent.getElementsByClassName( + 'selector-modal-show-2h-weapons', + )[0] as HTMLElement, + player.sim, + ); + if ( + !( + label == 'Items' && + (slot == ItemSlot.ItemSlotMainHand || + (slot == ItemSlot.ItemSlotOffHand && player.getClass() == Class.ClassWarrior)) + ) + ) { + ( + this.tabContent.getElementsByClassName( + 'selector-modal-show-1h-weapons', + )[0] as HTMLElement + ).style.display = 'none'; + ( + this.tabContent.getElementsByClassName( + 'selector-modal-show-2h-weapons', + )[0] as HTMLElement + ).style.display = 'none'; } - makeShowEPValuesSelector(this.tabContent.getElementsByClassName('selector-modal-show-ep-values')[0] as HTMLElement, player.sim); + makeShowEPValuesSelector( + this.tabContent.getElementsByClassName( + 'selector-modal-show-ep-values', + )[0] as HTMLElement, + player.sim, + ); - const showMatchingGemsSelector = makeShowMatchingGemsSelector(this.tabContent.getElementsByClassName('selector-modal-show-matching-gems')[0] as HTMLElement, player.sim); + const showMatchingGemsSelector = makeShowMatchingGemsSelector( + this.tabContent.getElementsByClassName( + 'selector-modal-show-matching-gems', + )[0] as HTMLElement, + player.sim, + ); if (!label.startsWith('Gem')) { - (this.tabContent.getElementsByClassName('selector-modal-show-matching-gems')[0] as HTMLElement).style.display = 'none'; + ( + this.tabContent.getElementsByClassName( + 'selector-modal-show-matching-gems', + )[0] as HTMLElement + ).style.display = 'none'; } - const phaseSelector = makePhaseSelector(this.tabContent.getElementsByClassName('selector-modal-phase-selector')[0] as HTMLElement, player.sim); + const phaseSelector = makePhaseSelector( + this.tabContent.getElementsByClassName( + 'selector-modal-phase-selector', + )[0] as HTMLElement, + player.sim, + ); if (label == 'Items') { - const filtersButton = this.tabContent.getElementsByClassName('selector-modal-filters-button')[0] as HTMLElement; + const filtersButton = this.tabContent.getElementsByClassName( + 'selector-modal-filters-button', + )[0] as HTMLElement; filtersButton.addEventListener('click', () => new FiltersMenu(parent, player, slot)); } - this.listElem = this.tabContent.getElementsByClassName('selector-modal-list')[0] as HTMLElement; + this.listElem = this.tabContent.getElementsByClassName( + 'selector-modal-list', + )[0] as HTMLElement; this.itemsToDisplay = []; - this.scroller = new Clusterize({ - getNumberOfRows: () => { return this.itemsToDisplay.length }, - generateRows: (startIdx, endIdx) => { - let items = []; - for (let i = startIdx; i < endIdx; ++i) { - if (i >= this.itemsToDisplay.length) - break; - items.push(this.createItemElem({idx:this.itemsToDisplay[i], data:this.itemData[this.itemsToDisplay[i]]})); - } - return items; - } - }, { - rows: [], - scroll_elem: this.listElem, - content_elem: this.listElem, - item_height: 56, - show_no_data_row: false, - no_data_text: '', - tag: 'li', - rows_in_block: 16, - blocks_in_cluster: 2, - }); + this.scroller = new Clusterize( + { + getNumberOfRows: () => { + return this.itemsToDisplay.length; + }, + generateRows: (startIdx, endIdx) => { + const items = []; + for (let i = startIdx; i < endIdx; ++i) { + if (i >= this.itemsToDisplay.length) break; + items.push( + this.createItemElem({ + idx: this.itemsToDisplay[i], + data: this.itemData[this.itemsToDisplay[i]], + }), + ); + } + return items; + }, + }, + { + rows: [], + scroll_elem: this.listElem, + content_elem: this.listElem, + item_height: 56, + show_no_data_row: false, + no_data_text: '', + tag: 'li', + rows_in_block: 16, + blocks_in_cluster: 2, + }, + ); - const removeButton = this.tabContent.getElementsByClassName('selector-modal-remove-button')[0] as HTMLButtonElement; + const removeButton = this.tabContent.getElementsByClassName( + 'selector-modal-remove-button', + )[0] as HTMLButtonElement; removeButton.addEventListener('click', event => { onRemove(TypedEvent.nextEventID()); }); - if (label.startsWith("Enchants")) { + if (label.startsWith('Enchants')) { removeButton.textContent = 'Remove Enchant'; - } else if (label.startsWith("Gem")) { + } else if (label.startsWith('Gem')) { removeButton.textContent = 'Remove Gem'; } this.updateSelected(); - this.searchInput = this.tabContent.getElementsByClassName('selector-modal-search')[0] as HTMLInputElement; + this.searchInput = this.tabContent.getElementsByClassName( + 'selector-modal-search', + )[0] as HTMLInputElement; this.searchInput.addEventListener('input', () => this.applyFilters()); - const simAllButton = this.tabContent.getElementsByClassName('selector-modal-simall-button')[0] as HTMLButtonElement; - if (label == "Items") { - simAllButton.hidden = !player.sim.getShowExperimental() + const simAllButton = this.tabContent.getElementsByClassName( + 'selector-modal-simall-button', + )[0] as HTMLButtonElement; + if (label == 'Items') { + simAllButton.hidden = !player.sim.getShowExperimental(); player.sim.showExperimentalChangeEmitter.on(() => { simAllButton.hidden = !player.sim.getShowExperimental(); }); - simAllButton.addEventListener('click', (event) => { + simAllButton.addEventListener('click', event => { if (simUI instanceof IndividualSimUI) { - let itemSpecs = Array(); - const isRangedOrTrinket = this.slot == ItemSlot.ItemSlotRanged || + const itemSpecs = Array(); + const isRangedOrTrinket = + this.slot == ItemSlot.ItemSlotRanged || this.slot == ItemSlot.ItemSlotTrinket1 || - this.slot == ItemSlot.ItemSlotTrinket2 + this.slot == ItemSlot.ItemSlotTrinket2; const curItem = this.equippedToItemFn(this.player.getEquippedItem(this.slot)); let curEP = 0; @@ -931,9 +1053,9 @@ export class ItemList { curEP = this.computeEP(curItem); } - for(let i of this.itemsToDisplay) { + for (const i of this.itemsToDisplay) { const idata = this.itemData[i]; - if (!isRangedOrTrinket && curEP > 0 && idata.baseEP < (curEP / 2)) { + if (!isRangedOrTrinket && curEP > 0 && idata.baseEP < curEP / 2) { continue; // If we have EPs on current item, dont sim items with less than half the EP. } @@ -941,7 +1063,6 @@ export class ItemList { if (idata.baseEP > 0 || isRangedOrTrinket) { itemSpecs.push(ItemSpec.create({ id: idata.id })); } - } simUI.bt.addItems(itemSpecs); // TODO: should we open the bulk sim UI or should we run in the background showing progress, and then sort the items in the picker? @@ -966,18 +1087,22 @@ export class ItemList { const newEquippedItem = this.gearData.getEquippedItem(); const newItem = this.equippedToItemFn(newEquippedItem); - const newItemId = newItem ? (this.label == 'Enchants' ? (newItem as unknown as Enchant).effectId : (newItem as unknown as Item | Gem).id) : 0; + const newItemId = newItem + ? this.label == 'Enchants' + ? (newItem as unknown as Enchant).effectId + : (newItem as unknown as Item | Gem).id + : 0; const newEP = newItem ? this.computeEP(newItem) : 0; - this.scroller.elementUpdate((item) => { - let idx = (item as HTMLElement).dataset.idx!; + this.scroller.elementUpdate(item => { + const idx = (item as HTMLElement).dataset.idx!; const itemData = this.itemData[parseFloat(idx)]; - if (itemData.id == newItemId) - item.classList.add('active'); - else - item.classList.remove('active'); + if (itemData.id == newItemId) item.classList.add('active'); + else item.classList.remove('active'); - const epDeltaElem = item.getElementsByClassName('selector-modal-list-item-ep-delta')[0] as HTMLSpanElement; + const epDeltaElem = item.getElementsByClassName( + 'selector-modal-list-item-ep-delta', + )[0] as HTMLSpanElement; if (epDeltaElem) { epDeltaElem.textContent = ''; if (itemData.item) { @@ -993,7 +1118,7 @@ export class ItemList { public applyFilters() { this.currentFilters = this.player.sim.getFilters(); let itemIdxs = new Array(this.itemData.length); - for (let i = 0; i < this.itemData.length; ++i) { + for (let i = 0; i < this.itemData.length; ++i) { itemIdxs[i] = i; } @@ -1003,19 +1128,22 @@ export class ItemList { itemIdxs = this.player.filterItemData( itemIdxs, i => this.itemData[i].item as unknown as Item, - this.slot); + this.slot, + ); } else if (this.label == 'Enchants') { itemIdxs = this.player.filterEnchantData( itemIdxs, i => this.itemData[i].item as unknown as Enchant, this.slot, - currentEquippedItem); + currentEquippedItem, + ); } else if (this.label.startsWith('Gem')) { itemIdxs = this.player.filterGemData( itemIdxs, i => this.itemData[i].item as unknown as Gem, this.slot, - this.socketColor); + this.socketColor, + ); } itemIdxs = itemIdxs.filter(i => { @@ -1026,13 +1154,15 @@ export class ItemList { } if (this.searchInput.value.length > 0) { - const searchQuery = this.searchInput.value.toLowerCase().replaceAll(/[^a-zA-Z0-9\s]/g, '').split(" "); + const searchQuery = this.searchInput.value + .toLowerCase() + .replaceAll(/[^a-zA-Z0-9\s]/g, '') + .split(' '); const name = listItemData.name.toLowerCase().replaceAll(/[^a-zA-Z0-9\s]/g, ''); - var include = true; + let include = true; searchQuery.forEach(v => { - if (!name.includes(v)) - include = false; + if (!name.includes(v)) include = false; }); if (!include) { return false; @@ -1045,14 +1175,16 @@ export class ItemList { let sortFn: (itemA: T, itemB: T) => number; if (this.slot == ItemSlot.ItemSlotTrinket1 || this.slot == ItemSlot.ItemSlotTrinket2) { // Trinket EP is weird so just sort by ilvl instead. - sortFn = (itemA, itemB) => (itemB as unknown as Item).ilvl - (itemA as unknown as Item).ilvl; + sortFn = (itemA, itemB) => + (itemB as unknown as Item).ilvl - (itemA as unknown as Item).ilvl; } else { sortFn = (itemA, itemB) => { const diff = this.computeEP(itemB) - this.computeEP(itemA); // if EP is same, sort by ilvl - if (Math.abs(diff) < 0.01) return (itemB as unknown as Item).ilvl - (itemA as unknown as Item).ilvl; + if (Math.abs(diff) < 0.01) + return (itemB as unknown as Item).ilvl - (itemA as unknown as Item).ilvl; return diff; - } + }; } itemIdxs = itemIdxs.sort((dataA, dataB) => { @@ -1071,20 +1203,18 @@ export class ItemList { } public hideOrShowEPValues() { - const labels = this.tabContent.getElementsByClassName("ep-label") - const container = this.tabContent.getElementsByClassName("selector-modal-list") + const labels = this.tabContent.getElementsByClassName('ep-label'); + const container = this.tabContent.getElementsByClassName('selector-modal-list'); const show = this.player.sim.getShowEPValues(); - const display = show ? "" : "none" + const display = show ? '' : 'none'; - for (let label of labels) { + for (const label of labels) { (label as HTMLElement).style.display = display; } - for (let c of container) { - if (show) - c.classList.remove("hide-ep"); - else - c.classList.add("hide-ep"); + for (const c of container) { + if (show) c.classList.remove('hide-ep'); + else c.classList.add('hide-ep'); } } @@ -1093,18 +1223,29 @@ export class ItemList { const itemEP = this.computeEP(itemData.item); const equippedItem = this.equippedToItemFn(this.gearData.getEquippedItem()); - const equippedItemID = equippedItem ? (this.label == 'Enchants' ? (equippedItem as unknown as Enchant).effectId : (equippedItem as unknown as Item).id) : 0; - const equippedItemEP = equippedItem ? this.computeEP(equippedItem) : 0 + const equippedItemID = equippedItem + ? this.label == 'Enchants' + ? (equippedItem as unknown as Enchant).effectId + : (equippedItem as unknown as Item).id + : 0; + const equippedItemEP = equippedItem ? this.computeEP(equippedItem) : 0; const nameElem = ref(); const anchorElem = ref(); const iconElem = ref(); const listItemElem = ( -
    • -
    • +
      + + + @@ -1115,37 +1256,42 @@ export class ItemList { if (this.label == 'Items') { listItemElem.appendChild( -
      +
      {this.getSourceInfo(itemData.item as unknown as Item, this.player.sim)} -
      - ) +
      , + ); } - if (this.slot != ItemSlot.ItemSlotTrinket1 && this.slot != ItemSlot.ItemSlotTrinket2) { listItemElem.appendChild( -
      - - {itemEP < 9.95 ? itemEP.toFixed(1).toString() : Math.round(itemEP).toString()} +
      + + {itemEP < 9.95 + ? itemEP.toFixed(1).toString() + : Math.round(itemEP).toString()} itemData.item && equippedItemEP != itemEP && formatDeltaTextElem(e, equippedItemEP, itemEP, 0)} - > -
      + className="selector-modal-list-item-ep-delta" + ref={e => + itemData.item && + equippedItemEP != itemEP && + formatDeltaTextElem(e, equippedItemEP, itemEP, 0) + }>
      +
      , ); } const favoriteElem = ref(); listItemElem.appendChild(
      - -
      - ) +
      , + ); anchorElem.value!.addEventListener('click', (event: Event) => { event.preventDefault(); @@ -1161,7 +1307,7 @@ export class ItemList { setItemQualityCssClass(nameElem.value!, itemData.quality); new Tooltip(favoriteElem.value!, { - title: 'Add to favorites' + title: 'Add to favorites', }); const setFavorite = (isFavorite: boolean) => { const filters = this.player.sim.getFilters(); @@ -1203,7 +1349,7 @@ export class ItemList { this.player.sim.setFilters(TypedEvent.nextEventID(), filters); }; - let isFavorite = this.isItemFavorited(itemData); + const isFavorite = this.isItemFavorited(itemData); if (isFavorite) { favoriteElem.value!.children[0].classList.add('fas'); @@ -1216,11 +1362,13 @@ export class ItemList { return listItemElem; } - private isItemFavorited(itemData: ItemData) : boolean { + private isItemFavorited(itemData: ItemData): boolean { if (this.label == 'Items') { return this.currentFilters.favoriteItems.includes(itemData.id); } else if (this.label == 'Enchants') { - return this.currentFilters.favoriteEnchants.includes(getUniqueEnchantString(itemData.item as unknown as Enchant)); + return this.currentFilters.favoriteEnchants.includes( + getUniqueEnchantString(itemData.item as unknown as Enchant), + ); } else if (this.label.startsWith('Gem')) { return this.currentFilters.favoriteGems.includes(itemData.id); } @@ -1232,14 +1380,21 @@ export class ItemList { return <>; } - const makeAnchor = (href:string, inner:string) => { - return {inner}; - } + const makeAnchor = (href: string, inner: string) => { + return ( + + {inner} + + ); + }; const source = item.sources[0]; if (source.source.oneofKind == 'crafted') { const src = source.source.crafted; - return makeAnchor( ActionId.makeSpellUrl(src.spellId), professionNames.get(src.profession) ?? 'Unknown'); + return makeAnchor( + ActionId.makeSpellUrl(src.spellId), + professionNames.get(src.profession) ?? 'Unknown', + ); } else if (source.source.oneofKind == 'drop') { const src = source.source.drop; const zone = sim.db.getZone(src.zoneId); @@ -1248,12 +1403,17 @@ export class ItemList { throw new Error('No zone found for item: ' + item); } - let rtnEl = makeAnchor( ActionId.makeZoneUrl(zone.id), `${zone.name} (${difficultyNames.get(src.difficulty) ?? 'Unknown'})`); + const rtnEl = makeAnchor( + ActionId.makeZoneUrl(zone.id), + `${zone.name} (${difficultyNames.get(src.difficulty) ?? 'Unknown'})`, + ); const category = src.category ? ` - ${src.category}` : ''; if (npc) { rtnEl.appendChild(document.createElement('br')); - rtnEl.appendChild(makeAnchor(ActionId.makeNpcUrl(npc.id), `${npc.name + category}`)); + rtnEl.appendChild( + makeAnchor(ActionId.makeNpcUrl(npc.id), `${npc.name + category}`), + ); } else if (src.otherName) { /*innerHTML += `