Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ui/core/components/detailed_results.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ export abstract class DetailedResults extends Component {
this.simUI?.sim.settingsChangeEmitter.on(async () => await this.updateSettings());

// Allow styling the sticky toolbar
const toolbar = document.querySelector('.dr-toolbar') as HTMLElement;
const toolbar = document.querySelector<HTMLElement>('.dr-toolbar')!;
new IntersectionObserver(
([e]) => {
e.target.classList.toggle('stuck', e.intersectionRatio < 1);
Expand Down
28 changes: 15 additions & 13 deletions ui/core/components/detailed_results/metrics_table/metrics_table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import { ActionId } from '../../../proto_utils/action_id';
import { ActionMetrics, AuraMetrics, ResourceMetrics, UnitMetrics } from '../../../proto_utils/sim_result';
import { TypedEvent } from '../../../typed_event';
import { ResultComponent, ResultComponentConfig, SimResultData } from '../result_component';

declare let $: any;
import { TableSorter } from './table_sorter';

export enum ColumnSortType {
None,
Expand All @@ -33,7 +32,8 @@ export abstract class MetricsTable<T extends ActionMetrics | AuraMetrics | UnitM
private readonly columnConfigs: Array<MetricsColumnConfig<T>>;

protected readonly tableElem: HTMLElement;
protected readonly bodyElem: HTMLElement;
protected readonly bodyElem: HTMLTableSectionElement;
private readonly sorter: TableSorter;

readonly onUpdate = new TypedEvent<void>('MetricsTableUpdate');

Expand All @@ -50,10 +50,10 @@ export abstract class MetricsTable<T extends ActionMetrics | AuraMetrics | UnitM
</table>,
);

this.tableElem = this.rootElem.getElementsByClassName('metrics-table')[0] as HTMLTableSectionElement;
this.bodyElem = this.rootElem.getElementsByClassName('metrics-table-body')[0] as HTMLElement;
this.tableElem = this.rootElem.querySelector<HTMLTableElement>('.metrics-table')!;
this.bodyElem = this.rootElem.querySelector<HTMLTableSectionElement>('.metrics-table-body')!;

const headerRowElem = this.rootElem.getElementsByClassName('metrics-table-header-row')[0] as HTMLElement;
const headerRowElem = this.rootElem.querySelector<HTMLTableRowElement>('.metrics-table-header-row')!;
this.columnConfigs.forEach(columnConfig => {
const headerCell = document.createElement('th');
const tooltip = columnConfig.tooltip || TOOLTIP_METRIC_LABELS[columnConfig.name as keyof typeof TOOLTIP_METRIC_LABELS];
Expand All @@ -74,13 +74,15 @@ export abstract class MetricsTable<T extends ActionMetrics | AuraMetrics | UnitM
headerRowElem.appendChild(headerCell);
});

const sortList = this.columnConfigs
.map((config, i) => [i, config.sort == ColumnSortType.Ascending ? 0 : 1])
.filter(sortData => this.columnConfigs[sortData[0]].sort);
const sortCol = this.columnConfigs.findIndex(v => !!v.sort);

$(this.tableElem).tablesorter({
sortList: sortList,
cssChildRow: 'child-metric',
this.sorter = new TableSorter({
tableHead: headerRowElem,
tableBody: this.bodyElem,
dataSetKey: 'text',
childRowClass: 'child-metric',
defaultSortCol: sortCol !== -1 ? sortCol : 0,
defaultSortDesc: sortCol !== -1 && this.columnConfigs[sortCol].sort == ColumnSortType.Descending,
});
}

Expand Down Expand Up @@ -168,7 +170,7 @@ export abstract class MetricsTable<T extends ActionMetrics | AuraMetrics | UnitM
this.rootElem.classList.remove('hide');
}
groupedMetrics.forEach(group => this.addGroup(group));
$(this.tableElem).trigger('update');
this.sorter.update();
this.onUpdate.emit(resultData.eventID);
}

Expand Down
104 changes: 104 additions & 0 deletions ui/core/components/detailed_results/metrics_table/table_sorter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
type TableSorterRowData = {
readonly values: ReadonlyArray<string | number>;
readonly rowElement: HTMLTableRowElement;
};

type TableSorterConfig = {
tableHead: HTMLTableRowElement;
tableBody: HTMLTableSectionElement;
dataSetKey: string;
childRowClass: string;
defaultSortCol: number;
defaultSortDesc: boolean;
};

export class TableSorter {
private readonly cfg: Readonly<TableSorterConfig>;
private readonly rowData: Array<TableSorterRowData & { children?: Array<TableSorterRowData> }> = [];
private sortCol = -1;
private sortDesc: Array<boolean>;

constructor(config: TableSorterConfig) {
if (config.tableHead.cells[config.defaultSortCol] === undefined) throw new Error('Default sort column must be a valid header cell index!');

this.cfg = config;

this.sortCol = this.cfg.defaultSortCol;
this.sortDesc = Array(config.tableHead.cells.length).fill(true);
this.sortDesc[config.defaultSortCol] = config.defaultSortDesc;

Array.from(config.tableHead.cells).forEach((cell, i) => {
cell.addEventListener('click', () => this.setSort(i));
});
}

private sortFunc = (a: TableSorterRowData, b: TableSorterRowData) => {
const aValue = a.values[this.sortCol];
const bValue = b.values[this.sortCol];
const asc = !this.sortDesc[this.sortCol];
if (typeof aValue === 'number' && typeof bValue === 'number') {
return asc ? aValue - bValue : bValue - aValue;
} else {
return asc ? aValue.toString().localeCompare(bValue.toString()) : bValue.toString().localeCompare(aValue.toString());
}
};

private sort() {
if (!this.rowData.length || !(this.sortCol in this.rowData[0].values)) return;

const sortedRowElems: Array<HTMLTableRowElement> = [];

this.rowData.sort(this.sortFunc);
for (const row of this.rowData) {
sortedRowElems.push(row.rowElement);
if (row.children) {
row.children.sort(this.sortFunc);
sortedRowElems.push(...row.children.map(v => v.rowElement));
}
}

this.cfg.tableBody.replaceChildren(...sortedRowElems);
}

/**
* Set column to sort by. If set to the current sort column the order will be reversed.
* @param column If omitted use default column.
*/
setSort(column = -1) {
if (this.sortDesc[column] === undefined) column = this.cfg.defaultSortCol;
this.sortDesc[column] = !this.sortDesc[column];
this.sortCol = column;
this.sort();
}

private parseRowValues(rowElement: HTMLTableRowElement): Array<number | string> {
const values: Array<string | number> = [];
for (const cell of rowElement.cells) {
const val = cell.dataset[this.cfg.dataSetKey] ?? cell.innerText;
const numVal = parseFloat(val);
values.push(!isNaN(numVal) ? numVal : val);
}
return values;
}

/**
* Update internal data structure for changed table data.
*/
update() {
this.rowData.length = 0;

for (const rowElement of this.cfg.tableBody.rows) {
const values = this.parseRowValues(rowElement);
if (!rowElement.classList.contains(this.cfg.childRowClass)) {
this.rowData.push({ values, rowElement });
} else {
const parentData = this.rowData[this.rowData.length - 1];
if (!parentData) throw new Error('Child row has no parent!');
if (!parentData.children) parentData.children = [];
parentData.children.push({ values, rowElement });
}
}

this.sort();
}
}
6 changes: 3 additions & 3 deletions ui/core/components/icon_enum_picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@ export class IconEnumPicker<ModObject, T> extends Input<ModObject, T> {
</>,
);

this.buttonElem = this.rootElem.querySelector('.icon-picker-button') as HTMLAnchorElement;
this.buttonText = this.rootElem.querySelector('label') as HTMLElement;
this.dropdownMenu = this.rootElem.querySelector('.dropdown-menu') as HTMLElement;
this.buttonElem = this.rootElem.querySelector<HTMLAnchorElement>('.icon-picker-button')!;
this.buttonText = this.rootElem.querySelector<HTMLElement>('label')!;
this.dropdownMenu = this.rootElem.querySelector<HTMLElement>('.dropdown-menu')!;

if (this.config.numColumns) {
this.dropdownMenu.style.gridTemplateColumns = `repeat(${this.config.numColumns}, 1fr)`;
Expand Down
22 changes: 11 additions & 11 deletions ui/core/components/individual_sim_ui/consumes_picker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export class ConsumesPicker extends Component {
`;

const row = this.rootElem.appendChild(fragment.children[0] as HTMLElement);
const potionsElem = this.rootElem.querySelector('.consumes-potions') as HTMLElement;
const potionsElem = this.rootElem.querySelector<HTMLElement>('.consumes-potions')!;

const potionsOptions = ConsumablesInputs.makePotionsInput(relevantStatOptions(ConsumablesInputs.POTIONS_CONFIG, this.simUI), 'Potions');
const conjuredOptions = ConsumablesInputs.makeConjuredInput(relevantStatOptions(ConsumablesInputs.CONJURED_CONFIG, this.simUI));
Expand All @@ -67,7 +67,7 @@ export class ConsumesPicker extends Component {
`;

const row = this.rootElem.appendChild(fragment.children[0] as HTMLElement);
const flasksElem = this.rootElem.querySelector('.consumes-flasks') as HTMLElement;
const flasksElem = this.rootElem.querySelector<HTMLElement>('.consumes-flasks')!;

const flasksOptions = ConsumablesInputs.makeFlasksInput(relevantStatOptions(ConsumablesInputs.FLASKS_CONFIG, this.simUI));

Expand All @@ -87,7 +87,7 @@ export class ConsumesPicker extends Component {
`;

const row = this.rootElem.appendChild(fragment.children[0] as HTMLElement);
const imbuesElem = this.rootElem.querySelector('.consumes-weapon-imbues') as HTMLElement;
const imbuesElem = this.rootElem.querySelector<HTMLElement>('.consumes-weapon-imbues')!;

const mhImbueOptions = ConsumablesInputs.makeMainHandImbuesInput(
relevantStatOptions(ConsumablesInputs.WEAPON_IMBUES_MH_CONFIG, this.simUI),
Expand All @@ -111,7 +111,7 @@ export class ConsumesPicker extends Component {
`;

const row = this.rootElem.appendChild(fragment.children[0] as HTMLElement);
const foodsElem = this.rootElem.querySelector('.consumes-food') as HTMLElement;
const foodsElem = this.rootElem.querySelector<HTMLElement>('.consumes-food')!;

const foodOptions = ConsumablesInputs.makeFoodInput(relevantStatOptions(ConsumablesInputs.FOOD_CONFIG, this.simUI));
const alcoholOptions = ConsumablesInputs.makeAlcoholInput(relevantStatOptions(ConsumablesInputs.ALCOHOL_CONFIG, this.simUI));
Expand All @@ -136,7 +136,7 @@ export class ConsumesPicker extends Component {
`;

const row = this.rootElem.appendChild(fragment.children[0] as HTMLElement);
const physicalConsumesElem = this.rootElem.querySelector('.consumes-physical') as HTMLElement;
const physicalConsumesElem = this.rootElem.querySelector<HTMLElement>('.consumes-physical')!;

const apBuffOptions = ConsumablesInputs.makeAttackPowerConsumeInput(
relevantStatOptions(ConsumablesInputs.ATTACK_POWER_CONSUMES_CONFIG, this.simUI),
Expand Down Expand Up @@ -168,7 +168,7 @@ export class ConsumesPicker extends Component {
`;

const row = this.rootElem.appendChild(fragment.children[0] as HTMLElement);
const defensiveConsumesElem = this.rootElem.querySelector('.consumes-defensive') as HTMLElement;
const defensiveConsumesElem = this.rootElem.querySelector<HTMLElement>('.consumes-defensive')!;

const healthBuffOptions = ConsumablesInputs.makeHealthConsumeInput(relevantStatOptions(ConsumablesInputs.HEALTH_CONSUMES_CONFIG, this.simUI), 'Health');

Expand All @@ -193,7 +193,7 @@ export class ConsumesPicker extends Component {
`;

const row = this.rootElem.appendChild(fragment.children[0] as HTMLElement);
const spellsCnsumesElem = this.rootElem.querySelector('.consumes-spells') as HTMLElement;
const spellsCnsumesElem = this.rootElem.querySelector<HTMLElement>('.consumes-spells')!;

const spBuffOptions = ConsumablesInputs.makeSpellPowerConsumeInput(
relevantStatOptions(ConsumablesInputs.SPELL_POWER_CONFIG, this.simUI),
Expand Down Expand Up @@ -235,7 +235,7 @@ export class ConsumesPicker extends Component {
`;

const row = this.rootElem.appendChild(fragment.children[0] as HTMLElement);
const miscConsumesElem = this.rootElem.querySelector('.consumes-misc') as HTMLElement;
const miscConsumesElem = this.rootElem.querySelector<HTMLElement>('.consumes-misc')!;

const sealOfTheDawnOptions = ConsumablesInputs.makeSealOfTheDawnConsumesInput(
relevantStatOptions(ConsumablesInputs.SEAL_OF_THE_DAWN_CONSUMES_CONFIG, this.simUI),
Expand Down Expand Up @@ -272,7 +272,7 @@ export class ConsumesPicker extends Component {
`;

const row = this.rootElem.appendChild(fragment.children[0] as HTMLElement);
const engiConsumesElem = this.rootElem.querySelector('.consumes-engi') as HTMLElement;
const engiConsumesElem = this.rootElem.querySelector<HTMLElement>('.consumes-engi')!;

const explosiveOptions = ConsumablesInputs.makeExplosivesInput(relevantStatOptions(ConsumablesInputs.EXPLOSIVES_CONFIG, this.simUI), 'Explosives');
const sapperOptions = ConsumablesInputs.makeSappersInput(relevantStatOptions(ConsumablesInputs.SAPPER_CONFIG, this.simUI), 'Sappers');
Expand All @@ -297,7 +297,7 @@ export class ConsumesPicker extends Component {
`;

const row = this.rootElem.appendChild(fragment.children[0] as HTMLElement);
const enchConsumesElem = this.rootElem.querySelector('.consumes-ench') as HTMLElement;
const enchConsumesElem = this.rootElem.querySelector<HTMLElement>('.consumes-ench')!;

const enchantedSigilOptions = ConsumablesInputs.makeEncanthedSigilInput(
relevantStatOptions(ConsumablesInputs.ENCHANTED_SIGIL_CONFIG, this.simUI),
Expand All @@ -322,7 +322,7 @@ export class ConsumesPicker extends Component {
`;

const row = this.rootElem.appendChild(fragment.children[0] as HTMLElement);
const petConsumesElem = this.rootElem.querySelector('.consumes-pet') as HTMLElement;
const petConsumesElem = this.rootElem.querySelector<HTMLElement>('.consumes-pet')!;

// const miscPetConsumesOptions = relevantStatOptions(ConsumablesInputs.MISC_PET_CONSUMES, this.simUI);

Expand Down
2 changes: 1 addition & 1 deletion ui/core/components/list_picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export class ListPicker<ModObject, ItemType> extends Input<ModObject, Array<Item
}

if (this.config.titleTooltip) {
const titleTooltip = tippy(this.rootElem.querySelector('.list-picker-title') as HTMLElement, {
const titleTooltip = tippy(this.rootElem.querySelector<HTMLElement>('.list-picker-title')!, {
content: this.config.titleTooltip,
});
this.addOnDisposeCallback(() => titleTooltip?.destroy());
Expand Down
6 changes: 3 additions & 3 deletions ui/core/components/multi_icon_picker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,15 @@ export class MultiIconPicker<ModObject> extends Component {
this.addOnDisposeCallback(() => tooltip.destroy());
}

const labelElem = this.rootElem.querySelector('.multi-icon-picker-label') as HTMLElement;
const labelElem = this.rootElem.querySelector<HTMLElement>('.multi-icon-picker-label')!;
if (config.label) {
labelElem.textContent = config.label;
} else {
labelElem.remove();
}

this.buttonElem = this.rootElem.querySelector('.icon-picker-button') as HTMLAnchorElement;
this.dropdownMenu = this.rootElem.querySelector('.dropdown-menu') as HTMLElement;
this.buttonElem = this.rootElem.querySelector<HTMLAnchorElement>('.icon-picker-button')!;
this.dropdownMenu = this.rootElem.querySelector<HTMLElement>('.dropdown-menu')!;

if (this.config.direction == IconPickerDirection.Horizontal) {
this.dropdownMenu.style.gridAutoFlow = 'column';
Expand Down
2 changes: 1 addition & 1 deletion ui/core/components/raid_sim_action.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ export class RaidSimResultsManager {
lowerIsBetter?: boolean,
preNormalizedErrors?: boolean,
) {
const elem = this.simUI.resultsViewer.contentElem.querySelector(querySelector) as HTMLSpanElement;
const elem = this.simUI.resultsViewer.contentElem.querySelector<HTMLSpanElement>(querySelector)!;
if (!elem) {
return;
}
Expand Down
4 changes: 2 additions & 2 deletions ui/core/components/raid_target_picker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ export class UnitReferencePicker<ModObject> extends Input<ModObject, UnitReferen
<div class="dropdown-menu"></div>
`;

this.buttonElem = this.rootElem.querySelector('.raid-target-picker-button') as HTMLElement;
this.dropdownElem = this.rootElem.querySelector('.dropdown-menu') as HTMLElement;
this.buttonElem = this.rootElem.querySelector<HTMLElement>('.raid-target-picker-button')!;
this.dropdownElem = this.rootElem.querySelector<HTMLElement>('.dropdown-menu')!;

this.buttonElem.addEventListener('click', event => event.preventDefault());

Expand Down
4 changes: 2 additions & 2 deletions ui/core/components/sim_header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ export class SimHeader extends Component {
constructor(parentElem: HTMLElement, simUI: SimUI) {
super(parentElem, 'sim-header');
this.simUI = simUI;
this.simTabsContainer = this.rootElem.querySelector('.sim-tabs') as HTMLElement;
this.simToolbar = this.rootElem.querySelector('.sim-toolbar') as HTMLElement;
this.simTabsContainer = this.rootElem.querySelector<HTMLElement>('.sim-tabs')!;
this.simToolbar = this.rootElem.querySelector<HTMLElement>('.sim-toolbar')!;

this.knownIssuesContent = (<ul className="text-start ps-3 mb-0"></ul>) as HTMLUListElement;
this.knownIssuesLink = this.addKnownIssuesLink();
Expand Down
14 changes: 7 additions & 7 deletions ui/core/components/stat_weights_action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,11 +330,11 @@ class EpWeightsMenu extends BaseModal {
</button>
`;

this.container = this.rootElem.querySelector('.results-ep-table-container') as HTMLElement;
this.table = this.rootElem.querySelector('.results-ep-table') as HTMLElement;
this.tableBody = this.rootElem.querySelector('.results-ep-table tbody') as HTMLElement;
this.container = this.rootElem.querySelector<HTMLElement>('.results-ep-table-container')!;
this.table = this.rootElem.querySelector<HTMLElement>('.results-ep-table')!;
this.tableBody = this.rootElem.querySelector<HTMLElement>('.results-ep-table tbody')!;

const resultsElem = this.rootElem.querySelector('.results-pending-overlay') as HTMLElement;
const resultsElem = this.rootElem.querySelector<HTMLElement>('.results-pending-overlay')!;
this.resultsViewer = new ResultsViewer(resultsElem);

const updateType = () => {
Expand Down Expand Up @@ -726,7 +726,7 @@ class EpWeightsMenu extends BaseModal {
`;

if (this.isEpStat(stat)) {
const includeToggleCell = row.querySelector('.swcalc-include-toggle') as HTMLElement;
const includeToggleCell = row.querySelector<HTMLElement>('.swcalc-include-toggle')!;
new BooleanPicker(includeToggleCell, this, {
id: 'sw-stat-toggle-' + stat.getName(this.simUI.player.getClass()),
getValue: epWeightsModal => !epWeightsModal.settings.isUnitStatExcludedFromCalc(stat),
Expand All @@ -736,7 +736,7 @@ class EpWeightsMenu extends BaseModal {
});
}

const currentEpCell = row.querySelector('.current-ep') as HTMLElement;
const currentEpCell = row.querySelector<HTMLElement>('.current-ep')!;
new NumberPicker(currentEpCell, this.simUI.player, {
id: `ep-weight-stat-${stat}`,
float: true,
Expand Down Expand Up @@ -787,7 +787,7 @@ class EpWeightsMenu extends BaseModal {
const epCurrent = this.simUI.player.getEpWeights().getUnitStat(stat);
const epDelta = epTotal - epCurrent;

const epAvgElem = template.content.querySelector('.type-ep .results-avg') as HTMLElement;
const epAvgElem = template.content.querySelector<HTMLElement>('.type-ep .results-avg')!;
if (epDelta.toFixed(2) == '0.00') epAvgElem; // no-op
else if (epDelta > 0) epAvgElem.classList.add('positive');
else if (epDelta < 0) epAvgElem.classList.add('negative');
Expand Down
Loading
Loading