Added state icons for scenic functions

And fixed dropdowns in case of duplicate labels
This commit is contained in:
Pax1601
2023-11-24 17:52:48 +01:00
parent 915020ddc3
commit 7415e0cb97
10 changed files with 5236 additions and 66 deletions

View File

@@ -5,9 +5,10 @@ export class Dropdown {
#callback: CallableFunction;
#defaultValue: string;
#optionsList: string[] = [];
#labelsList: string[] | undefined = [];
#index: number = 0;
#hidden: boolean = false;
#text!:HTMLElement;
#text!: HTMLElement;
constructor(ID: string | null, callback: CallableFunction, options: string[] | null = null, defaultText?: string) {
if (ID === null)
@@ -18,7 +19,7 @@ export class Dropdown {
this.#options = this.#container.querySelector(".ol-select-options") as HTMLElement;
const text = this.#container.querySelector(".ol-select-value-text");
this.#value = ( text instanceof HTMLElement ) ? text : this.#container.querySelector(".ol-select-value") as HTMLElement;
this.#value = (text instanceof HTMLElement) ? text : this.#container.querySelector(".ol-select-value") as HTMLElement;
this.#defaultValue = this.#value.innerText;
this.#callback = callback;
@@ -40,48 +41,37 @@ export class Dropdown {
return this.#container;
}
setOptions(optionsList: string[], sort: "" | "string" | "number" | "string+number" = "string") {
if (sort === "number") {
this.#optionsList = optionsList.sort((optionA: string, optionB: string) => {
const a = parseInt(optionA);
const b = parseInt(optionB);
if (a > b)
return 1;
else
return (b > a) ? -1 : 0;
});
} else if (sort === "string+number") {
this.#optionsList = optionsList.sort((optionA: string, optionB: string) => {
var regex = /\d+/g;
var matchesA = optionA.match(regex);
var matchesB = optionB.match(regex);
if ((matchesA != null && matchesA?.length > 0) && (matchesB != null && matchesB?.length > 0) && optionA[0] == optionB[0]) {
const a = parseInt(matchesA[0] ?? 0);
const b = parseInt(matchesB[0] ?? 0);
if (a > b)
return 1;
else
return (b > a) ? -1 : 0;
} else {
if (optionA > optionB)
return 1;
else
return (optionB > optionA) ? -1 : 0;
}
});
} else if (sort === "string") {
this.#optionsList = optionsList.sort();
}
/** Set the dropdown options strings
*
* @param optionsList List of options. These are the keys that will always be returned on selection
* @param sort Sort method. "string" performs js default sort. "number" sorts purely by numeric value.
* "string+number" sorts by string, unless two elements are lexicographically identical up to a numeric value (e.g. "SA-2" and "SA-3"), in which case it sorts by number.
* @param labelsList (Optional) List of labels to be shown instead of the keys directly. If provided, the options will be sorted by label.
*/
setOptions(optionsList: string[], sort: "" | "string" | "number" | "string+number" = "string", labelsList: string[] | undefined = undefined) {
/* If labels are provided, sort by labels, else by options */
if (labelsList && labelsList.length == optionsList.length)
this.#sortByLabels(optionsList, sort, labelsList);
else
this.#sortByOptions(optionsList, sort);
/* If no options are provided, return */
if (this.#optionsList.length == 0) {
optionsList = ["No options available"]
this.#value.innerText = "No options available";
return;
}
this.#options.replaceChildren(...optionsList.map((option: string, idx: number) => {
/* Create the buttons containing the options or the labels */
this.#options.replaceChildren(...this.#optionsList.map((option: string, idx: number) => {
var div = document.createElement("div");
var button = document.createElement("button");
button.textContent = option;
/* If the labels are provided use them for the options */
if (this.#labelsList && this.#labelsList.length === optionsList.length)
button.textContent = this.#labelsList[idx];
else
button.textContent = option;
div.appendChild(button);
if (option === this.#defaultValue)
@@ -95,8 +85,21 @@ export class Dropdown {
}));
}
getOptionsList() {
return this.#optionsList;
}
getLabelsList() {
return this.#labelsList;
}
/** Manually set the HTMLElements of the dropdown values. Handling of the selection must be performed externally.
*
* @param optionsElements List of elements to be added to the dropdown
*/
setOptionsElements(optionsElements: HTMLElement[]) {
this.#optionsList = [];
this.#labelsList = [];
this.#options.replaceChildren(...optionsElements);
}
@@ -108,19 +111,22 @@ export class Dropdown {
this.#options.appendChild(optionElement);
}
selectText(text: string) {
const index = [].slice.call(this.#options.children).findIndex((opt: Element) => opt.querySelector("button")?.innerText === text);
if (index > -1) {
this.selectValue(index);
}
}
/** Select the active value of the dropdown
*
* @param idx The index of the element to select
* @returns True if the index is valid, false otherwise
*/
selectValue(idx: number) {
if (idx < this.#optionsList.length) {
var option = this.#optionsList[idx];
var el = document.createElement("div");
el.classList.add("ol-ellipsed");
el.innerText = option;
if (this.#labelsList && this.#labelsList.length == this.#optionsList.length)
el.innerText = this.#labelsList[idx];
else
el.innerText = option;
this.#value.replaceChildren();
this.#value.appendChild(el);
this.#index = idx;
@@ -137,16 +143,24 @@ export class Dropdown {
this.#value.innerText = this.#defaultValue;
}
getValue() {
return this.#value.innerText;
}
/** Manually set the selected value of the dropdown
*
* @param value The value to select. Must be one of the valid options
*/
setValue(value: string) {
var index = this.#optionsList.findIndex((option) => { return option === value });
if (index > -1)
this.selectValue(index);
}
getValue() {
return this.#value.innerText;
}
/** Force the selected value of the dropdown.
*
* @param value Any string. Will be shown as selected value even if not one of the options.
*/
forceValue(value: string) {
var el = document.createElement("div");
el.classList.add("ol-ellipsed");
@@ -209,4 +223,110 @@ export class Dropdown {
div.append(value, options);
return div;
}
/** Sort the elements by their option keys
*
* @param optionsList The unsorted list of options
* @param sort The sorting method
*/
#sortByOptions(optionsList: string[], sort: string) {
if (sort === "number") {
this.#optionsList = JSON.parse(JSON.stringify(this.#numberSort(optionsList)));
} else if (sort === "string+number") {
this.#optionsList = JSON.parse(JSON.stringify(this.#stringNumberSort(optionsList)));
} else if (sort === "string") {
this.#optionsList = JSON.parse(JSON.stringify(this.#stringSort(optionsList)));
}
}
/** Sort the elements by their labels
*
* @param optionsList The unsorted list of options
* @param sort The sorting method
* @param labelsList The unsorted list of labels. The elements will be sorted according to these values
*/
#sortByLabels(optionsList: string[], sort: string, labelsList: string[]) {
/* Create a temporary deepcopied list. This is necessary because unlike options, labels can be repeated.
Once matched, labels are removed from the temporary array to avoid repeating the same key multiple times */
var tempLabelsList: (string | undefined)[] = JSON.parse(JSON.stringify(labelsList));
if (sort === "number") {
this.#labelsList = JSON.parse(JSON.stringify(this.#numberSort(labelsList)));
} else if (sort === "string+number") {
this.#labelsList = JSON.parse(JSON.stringify(this.#stringNumberSort(labelsList)));
} else if (sort === "string") {
this.#labelsList = JSON.parse(JSON.stringify(this.#stringSort(labelsList)));
}
/* Remap the options list to match their labels */
this.#optionsList = optionsList?.map((option: string, idx: number) => {
let originalIdx = tempLabelsList.indexOf(this.#labelsList? this.#labelsList[idx]: "");
/* After a match has been completed, set the label to undefined so it won't be matched again. This allows to have repeated labels */
tempLabelsList[originalIdx] = undefined;
return optionsList[originalIdx];
})
}
/** Sort elements by number. All elements must be parsable as numbers.
*
* @param elements List of strings
* @returns Sorted list
*/
#numberSort(elements: string[]) {
return elements.sort((elementA: string, elementB: string) => {
const a = parseFloat(elementA);
const b = parseFloat(elementB);
if (a > b)
return 1;
else
return (b > a) ? -1 : 0;
});
}
/** Sort elements by string, unless two elements are lexicographically identical up to a numeric value (e.g. "SA-2" and "SA-3"), in which case sort by number
*
* @param elements List of strings
* @returns Sorted list
*/
#stringNumberSort(elements: string[]) {
return elements.sort((elementA: string, elementB: string) => {
/* Check if there is a number in both strings */
var regex = /\d+/g;
var matchesA = elementA.match(regex);
var matchesB = elementB.match(regex);
/* Get the position of the number in the string */
var indexA = -1;
var indexB = -1;
if (matchesA != null && matchesA?.length > 0)
indexA = elementA.search(matchesA[0]);
if (matchesB != null && matchesB?.length > 0)
indexB = elementB.search(matchesB[0]);
/* If the two strings are the same up to the number, sort them using the number value, else sort them according to the string */
if ((matchesA != null && matchesA?.length > 0) && (matchesB != null && matchesB?.length > 0) && elementA.substring(0, indexA) === elementB.substring(0, indexB)) {
const a = parseInt(matchesA[0] ?? 0);
const b = parseInt(matchesB[0] ?? 0);
if (a > b)
return 1;
else
return (b > a) ? -1 : 0;
} else {
if (elementA > elementB)
return 1;
else
return (elementB > elementA) ? -1 : 0;
}
});
}
/** Sort by string. Just a wrapper for consistency.
*
* @param elements List of strings
* @returns Sorted list
*/
#stringSort(elements: string[]) {
return elements.sort();
}
}

View File

@@ -10,7 +10,7 @@ import { aircraftDatabase } from "../unit/databases/aircraftdatabase";
import { helicopterDatabase } from "../unit/databases/helicopterdatabase";
import { groundUnitDatabase } from "../unit/databases/groundunitdatabase";
import { navyUnitDatabase } from "../unit/databases/navyunitdatabase";
import { UnitSpawnOptions, UnitSpawnTable } from "../interfaces";
import { UnitBlueprint, UnitSpawnOptions, UnitSpawnTable } from "../interfaces";
export class UnitSpawnMenu {
protected showRangeCircles: boolean = false;
@@ -61,7 +61,7 @@ export class UnitSpawnMenu {
/* Create the dropdowns and the altitude slider */
this.#unitRoleTypeDropdown = new Dropdown(null, (roleType: string) => this.#setUnitRoleType(roleType), undefined, "Unit type");
this.#unitLabelDropdown = new Dropdown(null, (label: string) => this.#setUnitLabel(label), undefined, "Unit label");
this.#unitLabelDropdown = new Dropdown(null, (name: string) => this.#setUnitName(name), undefined, "Unit label");
this.#unitLoadoutDropdown = new Dropdown(null, (loadout: string) => this.#setUnitLoadout(loadout), undefined, "Unit loadout");
this.#unitCountDropdown = new Dropdown(null, (count: string) => this.#setUnitCount(count), undefined, "Unit count");
this.#unitCountryDropdown = new Dropdown(null, () => { /* Custom button implementation */ }, undefined, "Unit country");
@@ -153,16 +153,28 @@ export class UnitSpawnMenu {
this.#unitImageEl.classList.toggle("hide", true);
this.#unitLiveryDropdown.reset();
var blueprints: UnitBlueprint[] = [];
if (this.#orderByRole)
this.#unitLabelDropdown.setOptions(this.#unitDatabase.getByRole(this.spawnOptions.roleType).map((blueprint) => { return blueprint.label }), "string+number");
blueprints = this.#unitDatabase.getByRole(this.spawnOptions.roleType);
else
this.#unitLabelDropdown.setOptions(this.#unitDatabase.getByType(this.spawnOptions.roleType).map((blueprint) => { return blueprint.label }), "string+number");
blueprints = this.#unitDatabase.getByType(this.spawnOptions.roleType);
/* Presort the elements by name in case any have equal labels */
blueprints = blueprints.sort((blueprintA: UnitBlueprint, blueprintB: UnitBlueprint) => {
if (blueprintA.name > blueprintA.name)
return 1;
else
return (blueprintB.name > blueprintA.name) ? -1 : 0;
});
this.#unitLabelDropdown.setOptions(blueprints.map((blueprint) => { return blueprint.name }), "string+number", blueprints.map((blueprint) => { return blueprint.label }));
/* Add the tags to the options */
var elements: HTMLElement[] = [];
for (let idx = 0; idx < this.#unitLabelDropdown.getOptionElements().length; idx++) {
let name = this.#unitLabelDropdown.getOptionsList()[idx];
let element = this.#unitLabelDropdown.getOptionElements()[idx] as HTMLElement;
let entry = this.#unitDatabase.getByLabel(element.textContent ?? "");
let entry = this.#unitDatabase.getByName(name);
if (entry) {
element.querySelectorAll("button")[0]?.append(...(entry.tags?.split(",").map((tag: string) => {
tag = tag.trim();
@@ -418,8 +430,7 @@ export class UnitSpawnMenu {
this.#container.dispatchEvent(new Event("unitRoleTypeChanged"));
}
#setUnitLabel(label: string) {
var name = this.#unitDatabase.getByLabel(label)?.name || null;
#setUnitName(name: string) {
if (name != null)
this.spawnOptions.name = name;
this.#container.dispatchEvent(new Event("unitLabelChanged"));