diff --git a/client/demo.js b/client/demo.js
index 5db2660c..5d7dfc7c 100644
--- a/client/demo.js
+++ b/client/demo.js
@@ -53,7 +53,7 @@ class DemoDataGenerator {
}
- /*
+
// UNCOMMENT TO TEST ALL UNITS ****************
var databases = Object.assign({}, aircraftDatabase, helicopterDatabase, groundUnitDatabase, navyUnitDatabase);
@@ -70,6 +70,7 @@ class DemoDataGenerator {
DEMO_UNIT_DATA[idx].groupName = `Group-${idx}`;
DEMO_UNIT_DATA[idx].position.lat += latIdx / 5;
DEMO_UNIT_DATA[idx].position.lng += lngIdx / 5;
+ DEMO_UNIT_DATA[idx].coalition = Math.floor(Math.random() * 3)
latIdx += 1;
if (latIdx === l) {
@@ -89,9 +90,9 @@ class DemoDataGenerator {
idx += 1;
}
}
- */
-
+
+ /*
let idx = 1;
DEMO_UNIT_DATA[idx] = JSON.parse(JSON.stringify(baseData));
DEMO_UNIT_DATA[idx].name = "S_75M_Volhov";
@@ -152,7 +153,7 @@ class DemoDataGenerator {
DEMO_UNIT_DATA[idx].position.lat += idx / 100;
DEMO_UNIT_DATA[idx].category = "Aircraft";
DEMO_UNIT_DATA[idx].isLeader = true;
-
+ */
this.startTime = Date.now();
}
diff --git a/client/public/stylesheets/style/style.css b/client/public/stylesheets/style/style.css
index 135e6f7b..82c47702 100644
--- a/client/public/stylesheets/style/style.css
+++ b/client/public/stylesheets/style/style.css
@@ -1635,4 +1635,40 @@ input[type=number]::-webkit-outer-spin-button {
.file-import-export button.start-transfer {
background-color: var(--secondary-blue-text);
border-color: var(--secondary-blue-text);
+}
+
+.file-import-export .export-filename-container {
+ display: flex;
+ column-gap: 15px;
+ width: 100%;
+ align-items: center;
+ padding: 10px 0px;
+ color: white;
+ font-size: 14px;
+}
+
+.file-import-export .export-filename-container input {
+ width: 100%;
+ background-color: var(--background-grey);
+ appearance: none;
+ -moz-appearance: none;
+ -webkit-appearance: none;
+ border-style: solid;
+ border: 1px solid var(--background-steel);
+ color: white;
+ font-size: 14px;
+ padding: 4px;
+}
+
+.file-import-export .export-filename-container img {
+ height: 16px;
+ width: 16px;
+ filter: invert(100%);
+ margin-left: -31px;
+ transform: translateX(-15px);
+ pointer-events: none;
+}
+
+.file-import-export .ol-dialog-footer button:first-of-type{
+ margin-left: auto;
}
\ No newline at end of file
diff --git a/client/public/themes/olympus/images/icons/keyboard-solid.svg b/client/public/themes/olympus/images/icons/keyboard-solid.svg
new file mode 100644
index 00000000..8838d567
--- /dev/null
+++ b/client/public/themes/olympus/images/icons/keyboard-solid.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/client/src/constants/constants.ts b/client/src/constants/constants.ts
index c131704c..518b9458 100644
--- a/client/src/constants/constants.ts
+++ b/client/src/constants/constants.ts
@@ -214,7 +214,7 @@ export const MAP_MARKER_CONTROLS: MapMarkerVisibilityControl[] = [{
export const IADSTypes = ["AAA", "MANPADS", "SAM Site", "Radar"];
export const IADSDensities: { [key: string]: number } = { "AAA": 0.8, "MANPADS": 0.3, "SAM Site": 0.1, "Radar": 0.05 };
-
+export const GROUND_UNIT_AIR_DEFENCE_REGEX:RegExp = /(\b(AAA|SAM|MANPADS?|[mM]anpads?)|[sS]tinger\b)/;
export const HIDE_GROUP_MEMBERS = "Hide group members when zoomed out";
export const SHOW_UNIT_LABELS = "Show unit labels (L)";
export const SHOW_UNITS_ENGAGEMENT_RINGS = "Show units threat range rings (Q)";
diff --git a/client/src/unit/unitdatafile.ts b/client/src/unit/unitdatafile.ts
index 1994bc6d..5f3aad52 100644
--- a/client/src/unit/unitdatafile.ts
+++ b/client/src/unit/unitdatafile.ts
@@ -3,38 +3,38 @@ import { createCheckboxOption } from "../other/utils";
export abstract class UnitDataFile {
- protected data:any;
- protected dialog!:Dialog;
+ protected data: any;
+ protected dialog!: Dialog;
- constructor() {}
+ constructor() { }
buildCategoryCoalitionTable() {
-
+
const categories = this.#getCategoriesFromData();
const coalitions = ["blue", "neutral", "red"];
- let headersHTML:string = ``;
- let matrixHTML:string = ``;
+ let headersHTML: string = ``;
+ let matrixHTML: string = ``;
- categories.forEach((category:string, index) => {
+ categories.forEach((category: string, index) => {
matrixHTML += `
| ${category} | `;
- coalitions.forEach((coalition:string) => {
+ coalitions.forEach((coalition: string) => {
if (index === 0)
- headersHTML += `${coalition[0].toUpperCase()+coalition.substring(1)} | `;
+ headersHTML += `${coalition[0].toUpperCase() + coalition.substring(1)} | `;
const optionIsValid = this.data[category].hasOwnProperty(coalition);
- let checkboxHTML = createCheckboxOption(`${category}:${coalition}`, category, optionIsValid, () => {}, {
+ let checkboxHTML = createCheckboxOption(`${category}:${coalition}`, category, optionIsValid, () => { }, {
"disabled": !optionIsValid,
"name": "category-coalition-selection",
"readOnly": !optionIsValid
}).outerHTML;
if (optionIsValid)
- checkboxHTML = checkboxHTML.replace( `"checkbox"`, `"checkbox" checked`); // inner and outerHTML screw default checked up
+ checkboxHTML = checkboxHTML.replace(`"checkbox"`, `"checkbox" checked`); // inner and outerHTML screw default checked up
matrixHTML += `${checkboxHTML} | `;
-
+
});
matrixHTML += "
";
});
@@ -43,7 +43,7 @@ export abstract class UnitDataFile {
(table.tHead).innerHTML = `| | ${headersHTML}
`;
(table.querySelector(`tbody`)).innerHTML = matrixHTML;
-
+
}
#getCategoriesFromData() {
diff --git a/client/src/unit/unitdatafileexport.ts b/client/src/unit/unitdatafileexport.ts
index 2085381f..7f90b715 100644
--- a/client/src/unit/unitdatafileexport.ts
+++ b/client/src/unit/unitdatafileexport.ts
@@ -6,16 +6,17 @@ import { UnitDataFile } from "./unitdatafile";
export class UnitDataFileExport extends UnitDataFile {
- protected data!:any;
- protected dialog:Dialog;
- #element!:HTMLElement;
+ protected data!: any;
+ protected dialog: Dialog;
+ #element!: HTMLElement;
+ #filename: string = "export.json";
- constructor( elementId:string ) {
+ constructor(elementId: string) {
super();
this.dialog = new Dialog(elementId);
this.#element = this.dialog.getElement();
- this.#element.querySelector(".start-transfer")?.addEventListener("click", (ev:MouseEventInit) => {
+ this.#element.querySelector(".start-transfer")?.addEventListener("click", (ev: MouseEventInit) => {
this.#doExport();
});
}
@@ -23,12 +24,12 @@ export class UnitDataFileExport extends UnitDataFile {
/**
* Show the form to start the export journey
*/
- showForm(units:Unit[]) {
- const data:any = {};
- const unitCanBeExported = (unit:Unit) => !["Aircraft", "Helicopter"].includes(unit.getCategory());
+ showForm(units: Unit[]) {
+ const data: any = {};
+ const unitCanBeExported = (unit: Unit) => !["Aircraft", "Helicopter"].includes(unit.getCategory());
- units.filter((unit:Unit) => unit.getAlive() && unitCanBeExported(unit)).forEach((unit:Unit) => {
- const category = unit.getCategory();
+ units.filter((unit: Unit) => unit.getAlive() && unitCanBeExported(unit)).forEach((unit: Unit) => {
+ const category = unit.getCategory();
const coalition = unit.getCoalition();
if (!data.hasOwnProperty(category)) {
@@ -44,13 +45,22 @@ export class UnitDataFileExport extends UnitDataFile {
this.data = data;
this.buildCategoryCoalitionTable();
this.dialog.show();
+
+ const date = new Date();
+ this.#filename = `olympus_${getApp().getMissionManager().getTheatre().replace(/[^\w]/gi, "").toLowerCase()}_${date.getFullYear()}${zeroAppend(date.getMonth() + 1, 2)}${zeroAppend(date.getDate(), 2)}_${zeroAppend(date.getHours(), 2)}${zeroAppend(date.getMinutes(), 2)}${zeroAppend(date.getSeconds(), 2)}.json`;
+ var input = this.#element.querySelector("#export-filename") as HTMLInputElement;
+ input.onchange = (ev: Event) => {
+ this.#filename = (ev.currentTarget as HTMLInputElement).value;
+ }
+ if (input)
+ input.value = this.#filename;
}
-
+
#doExport() {
- let selectedUnits:Unit[] = [];
-
- this.#element.querySelectorAll(`input[type="checkbox"][name="category-coalition-selection"]:checked`).forEach((checkbox:HTMLInputElement) => {
+ let selectedUnits: Unit[] = [];
+
+ this.#element.querySelectorAll(`input[type="checkbox"][name="category-coalition-selection"]:checked`).forEach((checkbox: HTMLInputElement) => {
if (checkbox instanceof HTMLInputElement) {
const [category, coalition] = checkbox.value.split(":"); // e.g. "category:coalition"
selectedUnits = selectedUnits.concat(this.data[category][coalition]);
@@ -63,7 +73,7 @@ export class UnitDataFileExport extends UnitDataFile {
}
var unitsToExport: { [key: string]: any } = {};
- selectedUnits.forEach((unit:Unit) => {
+ selectedUnits.forEach((unit: Unit) => {
var data: any = unit.getData();
if (unit.getGroupName() in unitsToExport)
unitsToExport[unit.getGroupName()].push(data);
@@ -71,11 +81,15 @@ export class UnitDataFileExport extends UnitDataFile {
unitsToExport[unit.getGroupName()] = [data];
});
- const date = new Date();
- const a = document.createElement("a");
+
+ const a = document.createElement("a");
const file = new Blob([JSON.stringify(unitsToExport)], { type: 'text/plain' });
- a.href = URL.createObjectURL(file);
- a.download = `olympus_${getApp().getMissionManager().getTheatre().replace( /[^\w]/gi, "" ).toLowerCase()}_${date.getFullYear()}${zeroAppend(date.getMonth()+1, 2)}${zeroAppend(date.getDate(), 2)}_${zeroAppend(date.getHours(), 2)}${zeroAppend(date.getMinutes(), 2)}${zeroAppend(date.getSeconds(), 2)}.json`;
+ a.href = URL.createObjectURL(file);
+
+ var filename = this.#filename;
+ if (!this.#filename.toLowerCase().endsWith(".json"))
+ filename += ".json";
+ a.download = filename;
a.click();
this.dialog.hide();
}
diff --git a/client/src/unit/unitdatafileimport.ts b/client/src/unit/unitdatafileimport.ts
index b883c150..549a5a9e 100644
--- a/client/src/unit/unitdatafileimport.ts
+++ b/client/src/unit/unitdatafileimport.ts
@@ -5,14 +5,14 @@ import { UnitDataFile } from "./unitdatafile";
export class UnitDataFileImport extends UnitDataFile {
- protected data!:any;
- protected dialog:Dialog;
- #fileData!:{[key:string]: UnitData[]};
+ protected data!: any;
+ protected dialog: Dialog;
+ #fileData!: { [key: string]: UnitData[] };
- constructor( elementId:string ) {
+ constructor(elementId: string) {
super();
this.dialog = new Dialog(elementId);
- this.dialog.getElement().querySelector(".start-transfer")?.addEventListener("click", (ev:MouseEventInit) => {
+ this.dialog.getElement().querySelector(".start-transfer")?.addEventListener("click", (ev: MouseEventInit) => {
this.#doImport();
this.dialog.hide();
});
@@ -20,18 +20,18 @@ export class UnitDataFileImport extends UnitDataFile {
#doImport() {
- let selectedCategories:any = {};
+ let selectedCategories: any = {};
const unitsManager = getApp().getUnitsManager();
- this.dialog.getElement().querySelectorAll(`input[type="checkbox"][name="category-coalition-selection"]:checked`).forEach((checkbox:HTMLInputElement) => {
+ this.dialog.getElement().querySelectorAll(`input[type="checkbox"][name="category-coalition-selection"]:checked`).forEach((checkbox: HTMLInputElement) => {
if (checkbox instanceof HTMLInputElement) {
- const [category, coalition] = checkbox.value.split(":"); // e.g. "category:coalition"
- selectedCategories[category] = selectedCategories[category] || {};
+ const [category, coalition] = checkbox.value.split(":"); // e.g. "category:coalition"
+ selectedCategories[category] = selectedCategories[category] || {};
selectedCategories[category][coalition] = true;
}
});
- for (const[groupName, groupData] of Object.entries(this.#fileData)) {
+ for (const [groupName, groupData] of Object.entries(this.#fileData)) {
if (groupName === "" || groupData.length === 0 || !this.#unitGroupDataCanBeImported(groupData))
continue;
@@ -39,10 +39,10 @@ export class UnitDataFileImport extends UnitDataFile {
if (!selectedCategories.hasOwnProperty(category)
|| !selectedCategories[category].hasOwnProperty(coalition)
- || selectedCategories[category][coalition] !== true )
+ || selectedCategories[category][coalition] !== true)
continue;
- let unitsToSpawn = groupData.filter((unitData:UnitData) => this.#unitDataCanBeImported(unitData)).map((unitData:UnitData) => {
+ let unitsToSpawn = groupData.filter((unitData: UnitData) => this.#unitDataCanBeImported(unitData)).map((unitData: UnitData) => {
return { unitType: unitData.name, location: unitData.position, liveryID: "" }
});
@@ -63,13 +63,13 @@ export class UnitDataFileImport extends UnitDataFile {
}
#showForm() {
- const data:any = {};
+ const data: any = {};
- for (const [ group, units ] of Object.entries(this.#fileData) ) {
+ for (const [group, units] of Object.entries(this.#fileData)) {
if (group === "" || units.length === 0)
continue;
- if (units.some((unit:UnitData) => !this.#unitDataCanBeImported(unit)))
+ if (units.some((unit: UnitData) => !this.#unitDataCanBeImported(unit)))
continue;
const category = units[0].category;
@@ -78,7 +78,7 @@ export class UnitDataFileImport extends UnitDataFile {
data[category] = {};
}
- units.forEach((unit:UnitData) => {
+ units.forEach((unit: UnitData) => {
if (!data[category].hasOwnProperty(unit.coalition))
data[category][unit.coalition] = [];
@@ -125,14 +125,14 @@ export class UnitDataFileImport extends UnitDataFile {
input.click();
}
- #unitDataCanBeImported(unitData:UnitData) {
+ #unitDataCanBeImported(unitData: UnitData) {
return unitData.alive && this.#unitGroupDataCanBeImported([unitData]);
}
- #unitGroupDataCanBeImported(unitGroupData:UnitData[]) {
- return unitGroupData.every((unitData:UnitData) => {
+ #unitGroupDataCanBeImported(unitGroupData: UnitData[]) {
+ return unitGroupData.every((unitData: UnitData) => {
return !["Aircraft", "Helicopter"].includes(unitData.category);
- }) && unitGroupData.some((unitData:UnitData) => unitData.alive);
+ }) && unitGroupData.some((unitData: UnitData) => unitData.alive);
}
}
\ No newline at end of file
diff --git a/client/views/other/dialogs.ejs b/client/views/other/dialogs.ejs
index e1e59b2f..4fa33d6b 100644
--- a/client/views/other/dialogs.ejs
+++ b/client/views/other/dialogs.ejs
@@ -5,13 +5,15 @@
"dialogId": "unit-export-dialog",
"submitButtonText": "Export units to file",
"textContent": "Select the unit categories you would like to export. Note: only ground and naval units can be exported at this time.",
- "title": "Export"
+ "title": "Export",
+ "showFilenameInput": true
}) %>
<%- include('dialogs/importexport.ejs', {
"dialogId": "unit-import-dialog",
"submitButtonText": "Import units into mission",
"textContent": "Select the unit categories you would like to import.",
- "title": "Import"
+ "title": "Import",
+ "showFilenameInput": false
}) %>
<%- include('dialogs/slowdelete.ejs') %>
<%- include('dialogs/splash.ejs') %>
\ No newline at end of file
diff --git a/client/views/other/dialogs/importexport.ejs b/client/views/other/dialogs/importexport.ejs
index 6f10f716..3dfecc59 100644
--- a/client/views/other/dialogs/importexport.ejs
+++ b/client/views/other/dialogs/importexport.ejs
@@ -5,6 +5,15 @@
<%= textContent %>
+
+ <% if (showFilenameInput) { %>
+
+
+
+

+
+ <% } %>
+