Skip to content

Commit

Permalink
Merge pull request #3345 from wowsims/apl
Browse files Browse the repository at this point in the history
Move shared unit selection UI into new UnitPicker class
  • Loading branch information
jimmyt857 authored Jul 19, 2023
2 parents 028e751 + 760bd6d commit 15ad8b6
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 196 deletions.
2 changes: 1 addition & 1 deletion ui/core/components/detailed_results/result_component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export abstract class ResultComponent extends Component {
private lastSimResult: SimResultData | null;

constructor(config: ResultComponentConfig) {
super(config.parent, config.rootCssClass || '');
super(config.parent, config.rootCssClass || 'result-component');
this.lastSimResult = null;

config.resultsEmitter.on((eventID, resultData) => {
Expand Down
220 changes: 51 additions & 169 deletions ui/core/components/detailed_results/results_filter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { SimResult, SimResultFilter, UnitMetrics } from '../../proto_utils/sim_result.js';
import { SimResult, SimResultFilter } from '../../proto_utils/sim_result.js';
import { EventID, TypedEvent } from '../../typed_event.js';
import { Input } from '../../components/input.js';
import { UnitPicker } from '../../components/unit_picker.js';

import { ResultComponent, ResultComponentConfig, SimResultData } from './result_component.js';

Expand All @@ -11,13 +11,20 @@ interface FilterData {
target: number,
};

interface UnitFilterOption {
iconUrl: string,
text: string,
color: string,
value: number,
};

export class ResultsFilter extends ResultComponent {
private readonly currentFilter: FilterData;

readonly changeEmitter: TypedEvent<void>;

private readonly playerFilter: PlayerFilter;
private readonly targetFilter: TargetFilter;
private readonly playerFilter: UnitPicker<FilterData, number>;
private readonly targetFilter: UnitPicker<FilterData, number>;

constructor(config: ResultComponentConfig) {
config.rootCssClass = 'results-filter-root';
Expand All @@ -28,11 +35,27 @@ export class ResultsFilter extends ResultComponent {
};
this.changeEmitter = new TypedEvent<void>();

this.playerFilter = new PlayerFilter(this.rootElem, this.currentFilter);
this.playerFilter.changeEmitter.on(eventID => this.changeEmitter.emit(eventID));
this.playerFilter = new UnitPicker(this.rootElem, this.currentFilter, {
extraCssClasses: [
'player-filter-root',
],
changedEvent: (filterData: FilterData) => this.changeEmitter,
getValue: (filterData: FilterData) => filterData.player,
setValue: (eventID: EventID, filterData: FilterData, newValue: number) => this.setPlayer(eventID, newValue),
equals: (a, b) => a == b,
values: [],
});

this.targetFilter = new TargetFilter(this.rootElem, this.currentFilter);
this.targetFilter.changeEmitter.on(eventID => this.changeEmitter.emit(eventID));
this.targetFilter = new UnitPicker(this.rootElem, this.currentFilter, {
extraCssClasses: [
'target-filter-root',
],
changedEvent: (filterData: FilterData) => this.changeEmitter,
getValue: (filterData: FilterData) => filterData.target,
setValue: (eventID: EventID, filterData: FilterData, newValue: number) => this.setTarget(eventID, newValue),
equals: (a, b) => a == b,
values: [],
});
}

getFilter(): SimResultFilter {
Expand All @@ -43,191 +66,50 @@ export class ResultsFilter extends ResultComponent {
}

onSimResult(resultData: SimResultData) {
this.playerFilter.setOptions(resultData.eventID, resultData.result);
this.targetFilter.setOptions(resultData.eventID, resultData.result);
this.playerFilter.setOptions(this.getUnitOptions(resultData.eventID, resultData.result, true));
this.targetFilter.setOptions(this.getUnitOptions(resultData.eventID, resultData.result, false));
}

setPlayer(eventID: EventID, newPlayer: number | null) {
this.currentFilter.player = (newPlayer === null) ? ALL_UNITS : newPlayer;
this.playerFilter.changeEmitter.emit(eventID);
this.changeEmitter.emit(eventID);
}

setTarget(eventID: EventID, newTarget: number | null) {
this.currentFilter.target = (newTarget === null) ? ALL_UNITS : newTarget;
this.targetFilter.changeEmitter.emit(eventID);
this.changeEmitter.emit(eventID);
}
}

interface UnitFilterOption {
iconUrl: string,
text: string,
color: string,
value: number,
};

// Dropdown menu for filtering by player.
abstract class UnitGroupFilter extends Input<FilterData, number> {
private readonly filterData: FilterData;
readonly changeEmitter: TypedEvent<void>;

private allUnitsOption: UnitFilterOption;
private currentOptions: Array<UnitFilterOption>;

private readonly buttonElem: HTMLElement;
private readonly dropdownElem: HTMLElement;

constructor(parent: HTMLElement, filterData: FilterData, allUnitsLabel: string) {
const changeEmitter = new TypedEvent<void>();
super(parent, 'unit-filter-root', filterData, {
extraCssClasses: [
'dropdown-root',
],
changedEvent: (filterData: FilterData) => changeEmitter,
getValue: (filterData: FilterData) => this.getFilterDataValue(filterData),
setValue: (eventID: EventID, filterData: FilterData, newValue: number) => this.setFilterDataValue(filterData, newValue),
});
this.filterData = filterData;
this.changeEmitter = changeEmitter;

this.allUnitsOption = {
private getUnitOptions(eventID: EventID, simResult: SimResult, isPlayer: boolean): Array<UnitFilterOption> {
const allUnitsOption = {
iconUrl: '',
text: allUnitsLabel,
text: isPlayer ? 'All Players' : 'All Targets',
color: 'black',
value: ALL_UNITS,
};
this.currentOptions = [this.allUnitsOption];

this.rootElem.innerHTML = `
<div class="dropdown-button unit-filter-button"></div>
<div class="dropdown-panel unit-filter-dropdown"></div>
`;

this.buttonElem = this.rootElem.getElementsByClassName('unit-filter-button')[0] as HTMLElement;
this.dropdownElem = this.rootElem.getElementsByClassName('unit-filter-dropdown')[0] as HTMLElement;

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

this.init();
}

abstract getFilterDataValue(filterData: FilterData): number;
abstract setFilterDataValue(filterData: FilterData, newValue: number): void;
abstract getAllUnits(simResult: SimResult): Array<UnitMetrics>;

setOptions(eventID: EventID, simResult: SimResult) {
this.currentOptions = [this.allUnitsOption].concat(this.getAllUnits(simResult).map(unit => {
const unitOptions = (isPlayer ? simResult.getPlayers() : simResult.getTargets()).map(unit => {
return {
iconUrl: unit.iconUrl || '',
text: unit.label,
color: unit.classColor || 'black',
value: unit.unitIndex,
};
}));

const hasSameOption = this.currentOptions.find(option => option.value == this.getInputValue()) != null;
if (!hasSameOption) {
this.setFilterDataValue(this.filterData, this.allUnitsOption.value);
this.changeEmitter.emit(eventID);
}

this.dropdownElem.innerHTML = '';
this.currentOptions.forEach(option => this.dropdownElem.appendChild(this.makeOption(option)));
}

private makeOption(data: UnitFilterOption): HTMLElement {
const option = this.makeOptionElem(data);

option.addEventListener('click', event => {
event.preventDefault();
this.setFilterDataValue(this.filterData, data.value);
this.changeEmitter.emit(TypedEvent.nextEventID());
});

return option;
}

private makeOptionElem(data: UnitFilterOption): HTMLElement {
const optionContainer = document.createElement('div');
optionContainer.classList.add('dropdown-option-container');

const option = document.createElement('div');
option.classList.add('dropdown-option', 'unit-filter-option');
optionContainer.appendChild(option);

if (data.color) {
option.style.backgroundColor = data.color;
}

if (data.iconUrl) {
const icon = document.createElement('img');
icon.src = data.iconUrl;
icon.classList.add('unit-filter-icon');
option.appendChild(icon);
}

if (data.text) {
const label = document.createElement('span');
label.textContent = data.text;
label.classList.add('unit-filter-label');
option.appendChild(label);
}

return optionContainer;
}

getInputElem(): HTMLElement {
return this.buttonElem;
}

getInputValue(): number {
return this.getFilterDataValue(this.filterData);
}
const options = [allUnitsOption].concat(unitOptions);

setInputValue(newValue: number) {
this.setFilterDataValue(this.filterData, newValue);

const optionData = this.currentOptions.find(optionData => optionData.value == newValue);
if (!optionData) {
return;
const curValue = isPlayer ? this.currentFilter.player : this.currentFilter.target;
const hasSameOption = options.find(option => option.value == curValue) != null;
if (!hasSameOption) {
if (isPlayer) {
this.currentFilter.player = ALL_UNITS;
} else {
this.currentFilter.target = ALL_UNITS;
}
this.changeEmitter.emit(eventID);
}

this.buttonElem.innerHTML = '';
this.buttonElem.appendChild(this.makeOptionElem(optionData));
}
}

class PlayerFilter extends UnitGroupFilter {
constructor(parent: HTMLElement, filterData: FilterData) {
super(parent, filterData, 'All Players');
this.rootElem.classList.add('player-filter-root');
}

getFilterDataValue(filterData: FilterData): number {
return filterData.player;
}
setFilterDataValue(filterData: FilterData, newValue: number): void {
filterData.player = newValue;
}
getAllUnits(simResult: SimResult): Array<UnitMetrics> {
return simResult.getPlayers();
}
}

class TargetFilter extends UnitGroupFilter {
constructor(parent: HTMLElement, filterData: FilterData) {
super(parent, filterData, 'All Targets');
this.rootElem.classList.add('target-filter-root');
}

getFilterDataValue(filterData: FilterData): number {
return filterData.target;
}
setFilterDataValue(filterData: FilterData, newValue: number): void {
filterData.target = newValue;
}
getAllUnits(simResult: SimResult): Array<UnitMetrics> {
return simResult.getTargets();
return options;
}
}
}
4 changes: 2 additions & 2 deletions ui/core/components/individual_sim_ui/apl_helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export class APLActionIDPicker extends DropdownPicker<Player<any>, ActionID, Act
const actionIdSet = actionIdSets[config.actionIdSet];
super(parent, player, {
...config,
sourceToValue: (src: ActionID) => ActionId.fromProto(src),
sourceToValue: (src: ActionID) => src ? ActionId.fromProto(src) : ActionId.fromEmpty(),
valueToSource: (val: ActionId) => val.toProto(),
defaultLabel: actionIdSet.defaultLabel,
equals: (a, b) => ((a == null) == (b == null)) && (!a || a.equals(b!)),
Expand All @@ -131,7 +131,7 @@ export class APLActionIDPicker extends DropdownPicker<Player<any>, ActionID, Act
const textElem = document.createTextNode(actionId.name);
button.appendChild(textElem);
},
createMissingValue: value => ((value instanceof ActionId) ? value : ActionId.fromProto(value as unknown as ActionID)).fill().then(filledId => {
createMissingValue: value => value.fill().then(filledId => {
return {
value: filledId,
};
Expand Down
42 changes: 42 additions & 0 deletions ui/core/components/unit_picker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { DropdownPicker, DropdownPickerConfig, DropdownValueConfig } from './dropdown_picker.js';

export interface UnitValueConfig<T> extends DropdownValueConfig<T> {
text: string,
iconUrl?: string,
color?: string,
}

export interface UnitPickerConfig<ModObject, T> extends Omit<DropdownPickerConfig<ModObject, T>, 'values' | 'setOptionContent' | 'defaultLabel'> {
values: Array<UnitValueConfig<T>>,
}

export class UnitPicker<ModObject, T> extends DropdownPicker<ModObject, T> {
constructor(parent: HTMLElement, modObject: ModObject, config: UnitPickerConfig<ModObject, T>) {
super(parent, modObject, {
...config,
defaultLabel: 'Unit',
setOptionContent: (button: HTMLButtonElement, valueConfig: DropdownValueConfig<T>) => {
const unitConfig = valueConfig as UnitValueConfig<T>;

if (unitConfig.color) {
button.style.backgroundColor = unitConfig.color;
}

if (unitConfig.iconUrl) {
const icon = document.createElement('img');
icon.src = unitConfig.iconUrl;
icon.classList.add('unit-filter-icon');
button.appendChild(icon);
}

if (unitConfig.text) {
const label = document.createElement('span');
label.textContent = unitConfig.text;
label.classList.add('unit-filter-label');
button.appendChild(label);
}
}
});
this.rootElem.classList.add('unit-picker-root');
}
}
22 changes: 22 additions & 0 deletions ui/scss/core/components/_unit_picker.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.unit-picker-root {
border: 1px solid white;
color: white;
text-shadow:
0 0 3px black,
0 0 3px black,
0 0 3px black;
}

.unit-filter-option {
height: 30px;
}

.unit-filter-icon {
margin: 2px;
height: 25px;
}

.unit-filter-label {
font-size: 14px;
margin: 2px;
}
Loading

0 comments on commit 15ad8b6

Please sign in to comment.