mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Added state icons for scenic functions
And fixed dropdowns in case of duplicate labels
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
|
||||
Reference in New Issue
Block a user