diff --git a/ui/core/components/sim_title_dropdown.ts b/ui/core/components/sim_title_dropdown.ts index de16cc84a1..a1d1c88bdf 100644 --- a/ui/core/components/sim_title_dropdown.ts +++ b/ui/core/components/sim_title_dropdown.ts @@ -1,42 +1,42 @@ -import { Component } from './component.js'; import { getLaunchedSimsForClass, LaunchStatus, raidSimStatus, - simLaunchStatuses + simLaunchStatuses, } from '../launched_sims.js'; import { Class, Spec } from '../proto/common.js'; import { classNames, getSpecSiteUrl, naturalClassOrder, - raidSimSiteUrl, raidSimIcon, raidSimLabel, + raidSimSiteUrl, specNames, specToClass, textCssClassForClass, textCssClassForSpec, titleIcons, } from '../proto_utils/utils.js'; +import { Component } from './component.js'; interface ClassOptions { - type: 'Class', - index: Class + type: 'Class'; + index: Class; } interface SpecOptions { - type: 'Spec' - index: Spec + type: 'Spec'; + index: Spec; } interface RaidOptions { - type: 'Raid' + type: 'Raid'; } type SimTitleDropdownConfig = { - noDropdown?: boolean, -} + noDropdown?: boolean; +}; // Dropdown menu for selecting a player. export class SimTitleDropdown extends Component { @@ -64,52 +64,59 @@ export class SimTitleDropdown extends Component { [Spec.SpecProtectionWarrior]: 'Protection', [Spec.SpecDeathknight]: 'DPS', [Spec.SpecTankDeathknight]: 'Tank', - } + }; - constructor(parent: HTMLElement, currentSpecIndex: Spec | null, config: SimTitleDropdownConfig = {}) { + constructor( + parent: HTMLElement, + currentSpecIndex: Spec | null, + config: SimTitleDropdownConfig = {}, + ) { super(parent, 'sim-title-dropdown-root'); - let rootLinkArgs: SpecOptions | RaidOptions = currentSpecIndex === null ? { type: 'Raid' } : { type: 'Spec', index: currentSpecIndex } - let rootLink = this.buildRootSimLink(rootLinkArgs); + const rootLinkArgs: SpecOptions | RaidOptions = + currentSpecIndex === null + ? { type: 'Raid' } + : { type: 'Spec', index: currentSpecIndex }; + const rootLink = this.buildRootSimLink(rootLinkArgs); if (config.noDropdown) { this.rootElem.innerHTML = rootLink.outerHTML; - return + return; } this.rootElem.innerHTML = ` - - `; + + `; this.dropdownMenu = this.rootElem.getElementsByClassName('dropdown-menu')[0] as HTMLElement; this.buildDropdown(); // Prevent Bootstrap from closing the menu instead of opening class menus - this.dropdownMenu.addEventListener('click', (event) => { - let target = event.target as HTMLElement; - let link = target.closest('a:not([href="javascript:void(0)"]'); + this.dropdownMenu.addEventListener('click', event => { + const target = event.target as HTMLElement; + const link = target.closest('a:not([href="javascript:void(0)"]'); if (!link) { event.stopPropagation(); event.preventDefault(); } - }) + }); } private buildDropdown() { if (raidSimStatus >= LaunchStatus.Alpha) { // Add the raid sim to the top of the dropdown - let raidListItem = document.createElement('li'); + const raidListItem = document.createElement('li'); raidListItem.appendChild(this.buildRaidLink()); this.dropdownMenu?.appendChild(raidListItem); } naturalClassOrder.forEach(classIndex => { - let listItem = document.createElement('li'); - let sims = getLaunchedSimsForClass(classIndex); + const listItem = document.createElement('li'); + const sims = getLaunchedSimsForClass(classIndex); if (sims.length == 1) { // The class only has one listed sim so make a direct link to the sim @@ -124,132 +131,134 @@ export class SimTitleDropdown extends Component { } private buildClassDropdown(classIndex: Class) { - let sims = getLaunchedSimsForClass(classIndex); - let dropdownFragment = document.createElement('fragment'); - let dropdownMenu = document.createElement('ul'); + const sims = getLaunchedSimsForClass(classIndex); + const dropdownFragment = document.createElement('fragment'); + const dropdownMenu = document.createElement('ul'); dropdownMenu.classList.add('dropdown-menu'); // Generate the class link to act as a dropdown toggle for the spec dropdown - let classLink = this.buildClassLink(classIndex); + const classLink = this.buildClassLink(classIndex); // Generate links for a class's specs - sims.forEach((specIndex) => { - let listItem = document.createElement('li'); - let link = this.buildSpecLink(specIndex); + sims.forEach(specIndex => { + const listItem = document.createElement('li'); + const link = this.buildSpecLink(specIndex); listItem.appendChild(link); dropdownMenu.appendChild(listItem); }); dropdownFragment.innerHTML = ` - - `; + + `; return dropdownFragment.children[0] as HTMLElement; } private buildRootSimLink(data: SpecOptions | RaidOptions): HTMLElement { - let iconPath = this.getSimIconPath(data);; - let textKlass = this.getContextualKlass(data); + const iconPath = this.getSimIconPath(data); + const textKlass = this.getContextualKlass(data); let label; - if (data.type == 'Raid') - label = raidSimLabel; + if (data.type == 'Raid') label = raidSimLabel; else { - let classIndex = specToClass[data.index]; + const classIndex = specToClass[data.index]; if (getLaunchedSimsForClass(classIndex).length > 1) // If the class has multiple sims, use the spec name label = specNames[data.index]; - else - // If the class has only 1 sim, use the class name - label = classNames[classIndex]; + // If the class has only 1 sim, use the class name + else label = classNames[classIndex]; } - let fragment = document.createElement('fragment'); + const fragment = document.createElement('fragment'); fragment.innerHTML = ` - - `; + + `; return fragment.children[0] as HTMLElement; } private buildRaidLink(): HTMLElement { - let href = raidSimSiteUrl; - let textKlass = this.getContextualKlass({ type: 'Raid' }); - let iconPath = this.getSimIconPath({ type: 'Raid' }); - let label = raidSimLabel; + const href = raidSimSiteUrl; + const textKlass = this.getContextualKlass({ type: 'Raid' }); + const iconPath = this.getSimIconPath({ type: 'Raid' }); + const label = raidSimLabel; - let fragment = document.createElement('fragment'); + const fragment = document.createElement('fragment'); fragment.innerHTML = ` - - - - `; + + + + `; return fragment.children[0] as HTMLElement; } private buildClassLink(classIndex: Class): HTMLElement { - let specIndexes = getLaunchedSimsForClass(classIndex); - let href = specIndexes.length > 1 ? 'javascript:void(0)' : getSpecSiteUrl(specIndexes[0]); - let textKlass = this.getContextualKlass({ type: 'Class', index: classIndex }); - let iconPath = this.getSimIconPath({ type: 'Class', index: classIndex }); - let label = classNames[classIndex]; + const specIndexes = getLaunchedSimsForClass(classIndex); + const href = specIndexes.length > 1 ? 'javascript:void(0)' : getSpecSiteUrl(specIndexes[0]); + const textKlass = this.getContextualKlass({ type: 'Class', index: classIndex }); + const iconPath = this.getSimIconPath({ type: 'Class', index: classIndex }); + const label = classNames[classIndex]; - let fragment = document.createElement('fragment'); + const fragment = document.createElement('fragment'); fragment.innerHTML = ` - 1 ? 'role="button" data-bs-toggle="dropdown" aria-expanded="false"' : ''}> - - - `; + 1 + ? 'role="button" data-bs-toggle="dropdown" aria-expanded="false"' + : '' + }> + + + `; return fragment.children[0] as HTMLElement; } private buildSpecLink(specIndex: Spec): HTMLElement { - let href = getSpecSiteUrl(specIndex); - let textKlass = this.getContextualKlass({ type: 'Spec', index: specIndex }); - let iconPath = this.getSimIconPath({ type: 'Spec', index: specIndex }); - let className = classNames[specToClass[specIndex]]; - let specLabel = this.specLabels[specIndex]; + const href = getSpecSiteUrl(specIndex); + const textKlass = this.getContextualKlass({ type: 'Spec', index: specIndex }); + const iconPath = this.getSimIconPath({ type: 'Spec', index: specIndex }); + const className = classNames[specToClass[specIndex]]; + const specLabel = this.specLabels[specIndex]; - let fragment = document.createElement('fragment'); + const fragment = document.createElement('fragment'); fragment.innerHTML = ` - - - - `; + + + + `; return fragment.children[0] as HTMLElement; } @@ -258,10 +267,14 @@ export class SimTitleDropdown extends Component { if ( (data.type == 'Raid' && raidSimStatus == LaunchStatus.Launched) || (data.type == 'Spec' && simLaunchStatuses[data.index] == LaunchStatus.Launched) - ) return ""; - - let label = data.type == 'Raid' ? LaunchStatus[raidSimStatus] : LaunchStatus[simLaunchStatuses[data.index]]; - let elem = document.createElement('span'); + ) + return ''; + + const label = + data.type == 'Raid' + ? LaunchStatus[raidSimStatus] + : LaunchStatus[simLaunchStatuses[data.index]]; + const elem = document.createElement('span'); elem.classList.add('launch-status-label', 'text-brand'); elem.textContent = label; @@ -274,8 +287,8 @@ export class SimTitleDropdown extends Component { if (data.type == 'Raid') { iconPath = raidSimIcon; } else if (data.type == 'Class') { - let className = classNames[data.index]; - iconPath = `/wotlk/assets/img/${className.toLowerCase().replace(/\s/g, '_')}_icon.png` + const className = classNames[data.index]; + iconPath = `/wotlk/assets/img/${className.toLowerCase().replace(/\s/g, '_')}_icon.png`; } else { iconPath = titleIcons[data.index]; } @@ -290,7 +303,6 @@ export class SimTitleDropdown extends Component { else if (data.type == 'Class') // Class links return textCssClassForClass(data.index); - else - return textCssClassForSpec(data.index); + else return textCssClassForSpec(data.index); } } diff --git a/ui/shared/bootstrap_overrides.ts b/ui/shared/bootstrap_overrides.ts index 7769be606b..e3a60143af 100644 --- a/ui/shared/bootstrap_overrides.ts +++ b/ui/shared/bootstrap_overrides.ts @@ -1,90 +1,115 @@ import { Dropdown, Popover, Tooltip } from 'bootstrap'; + import { isDescendant } from './utils'; Dropdown.Default.offset = [0, -1]; //Dropdown.Default.display = "static"; -Tooltip.Default.trigger = "hover"; +Tooltip.Default.trigger = 'hover'; -let body = document.querySelector('body') as HTMLElement; +const body = document.querySelector('body') as HTMLElement; function hasTouch() { - return ('ontouchstart' in window) || (navigator.maxTouchPoints > 0); + return 'ontouchstart' in window || navigator.maxTouchPoints > 0; } function hasHover() { - return window.matchMedia("(any-hover: hover)").matches; + return window.matchMedia('(any-hover: hover)').matches; } // Disable 'mouseover' to avoid needed to double click on mobile // Leaving 'mouseleave', however still allows dropdown to close when clicking new box if (!hasTouch() || hasHover()) { // Custom dropdown event handlers for mouseover dropdowns - body.addEventListener('mouseover', event => { - let target = event.target as HTMLElement; - let toggle = target.closest('[data-bs-toggle=dropdown]'); - if (toggle && !toggle.classList.contains('open-on-click')) { - let dropdown = Dropdown.getOrCreateInstance(toggle); - dropdown.show(); - } - }, true); + body.addEventListener( + 'mouseover', + event => { + const target = event.target as HTMLElement; + const toggle = target.closest('[data-bs-toggle=dropdown]:not([data-bs-trigger=click])'); + if (toggle && !toggle.classList.contains('open-on-click')) { + const dropdown = Dropdown.getOrCreateInstance(toggle); + dropdown.show(); + } + }, + true, + ); } -body.addEventListener('mouseleave', event => { - let e = event as MouseEvent; - let target = event.target as HTMLElement; - let toggle = target.closest('[data-bs-toggle=dropdown]') as HTMLElement | null; - // Hide dropdowns when hovering off of the toggle, so long as the new target is not part of the dropdown as well - if (toggle) { - let dropdown = Dropdown.getOrCreateInstance(toggle); - let dropdownMenu = toggle.nextElementSibling as HTMLElement; - let relatedTarget = e.relatedTarget as HTMLElement; - if (relatedTarget == null || (!isDescendant(relatedTarget, dropdownMenu) && !isDescendant(relatedTarget, toggle))) - dropdown.hide(); - } +body.addEventListener( + 'mouseleave', + event => { + const e = event as MouseEvent; + const target = event.target as HTMLElement; + const toggle = target.closest( + '[data-bs-toggle=dropdown]:not([data-bs-trigger=click])', + ) as HTMLElement | null; + // Hide dropdowns when hovering off of the toggle, so long as the new target is not part of the dropdown as well + if (toggle) { + const dropdown = Dropdown.getOrCreateInstance(toggle); + const dropdownMenu = toggle.nextElementSibling as HTMLElement; + const relatedTarget = e.relatedTarget as HTMLElement; + if ( + relatedTarget == null || + (!isDescendant(relatedTarget, dropdownMenu) && !isDescendant(relatedTarget, toggle)) + ) + dropdown.hide(); + } - let dropdownMenu = target.closest('.dropdown-menu') as HTMLElement; - // Hide dropdowns when hovering off of the menu, so long as the new target is not part of the dropdown as well - if (dropdownMenu) { - let toggle = dropdownMenu.previousElementSibling as HTMLElement; - let dropdown = Dropdown.getOrCreateInstance(toggle); - let relatedTarget = e.relatedTarget as HTMLElement; - if (relatedTarget == null || (!isDescendant(relatedTarget, dropdownMenu) && e.relatedTarget != toggle)) - dropdown.hide(); - } -}, true); + const dropdownMenu = target.closest('.dropdown-menu') as HTMLElement; + // Hide dropdowns when hovering off of the menu, so long as the new target is not part of the dropdown as well + if (dropdownMenu) { + const toggle = dropdownMenu.previousElementSibling as HTMLElement; + const dropdown = Dropdown.getOrCreateInstance(toggle); + const relatedTarget = e.relatedTarget as HTMLElement; + if ( + relatedTarget == null || + (!isDescendant(relatedTarget, dropdownMenu) && e.relatedTarget != toggle) + ) + dropdown.hide(); + } + }, + true, +); -let closePopovers = () => { +const closePopovers = () => { document.querySelectorAll('[data-bs-toggle="popover"][aria-describedby]').forEach(e => { - let p = Popover.getOrCreateInstance(e); + const p = Popover.getOrCreateInstance(e); p.hide(); }); -} - -body.addEventListener('show.bs.popover', (event) => { - closePopovers(); +}; - document.querySelectorAll('[data-bs-toggle="tooltip"][aria-describedby]').forEach(e => { - let t = Tooltip.getOrCreateInstance(e); - t.hide(); - }); - - document.querySelectorAll('.tooltip').forEach(e => e.remove()); -}, true); - -body.addEventListener('show.bs.tooltip', (event) => { - document.querySelectorAll('[data-bs-toggle="tooltip"][aria-describedby]').forEach(e => { - let t = Tooltip.getOrCreateInstance(e); - t.hide(); - }); - - document.querySelectorAll('.tooltip').forEach(e => e.remove()); -}, true); +body.addEventListener( + 'show.bs.popover', + event => { + closePopovers(); -document.onkeydown = (event) => { + document.querySelectorAll('[data-bs-toggle="tooltip"][aria-describedby]').forEach(e => { + const t = Tooltip.getOrCreateInstance(e); + t.hide(); + }); + + document.querySelectorAll('.tooltip').forEach(e => e.remove()); + }, + true, +); + +body.addEventListener( + 'show.bs.tooltip', + event => { + document.querySelectorAll('[data-bs-toggle="tooltip"][aria-describedby]').forEach(e => { + const t = Tooltip.getOrCreateInstance(e); + t.hide(); + }); + + document.querySelectorAll('.tooltip').forEach(e => e.remove()); + }, + true, +); + +document.onkeydown = event => { event = event || window.event; if (event.key == 'Escape') { closePopovers(); } -} +};