From 8dc48c10c3aab66fdb2750f4027c5b8ee40e9dbc Mon Sep 17 00:00:00 2001 From: PeekabooSteam Date: Sun, 5 Nov 2023 22:35:00 +0000 Subject: [PATCH 1/8] Added dialog --- client/public/stylesheets/style/style.css | 16 + client/views/other/dialogs.ejs | 363 +----------------- .../views/other/dialogs/advancedsettings.ejs | 162 ++++++++ .../other/dialogs/commandmodesettings.ejs | 70 ++++ .../views/other/dialogs/customformation.ejs | 48 +++ client/views/other/dialogs/importfromfile.ejs | 43 +++ client/views/other/dialogs/slowdelete.ejs | 21 + client/views/other/dialogs/splash.ejs | 50 +++ 8 files changed, 416 insertions(+), 357 deletions(-) create mode 100644 client/views/other/dialogs/advancedsettings.ejs create mode 100644 client/views/other/dialogs/commandmodesettings.ejs create mode 100644 client/views/other/dialogs/customformation.ejs create mode 100644 client/views/other/dialogs/importfromfile.ejs create mode 100644 client/views/other/dialogs/slowdelete.ejs create mode 100644 client/views/other/dialogs/splash.ejs diff --git a/client/public/stylesheets/style/style.css b/client/public/stylesheets/style/style.css index eb16edde..be988fe1 100644 --- a/client/public/stylesheets/style/style.css +++ b/client/public/stylesheets/style/style.css @@ -740,6 +740,11 @@ nav.ol-panel> :last-child { } /****************************************************************************************/ +#import-from-file-dialog td { + text-align: center; +} + + #splash-screen { border-radius: var(--border-radius-md); overflow: hidden; @@ -1222,6 +1227,17 @@ dl.ol-data-grid dd { margin: 4px 0; } +.ol-dialog-content table th { + background-color: var(--background-grey); + color:white; + font-size:14px; + font-weight: normal; +} + +.ol-dialog-content table tbody th { + text-align: left; +} + .ol-dialog-footer { align-content: center; border-top: 1px solid var(--background-grey); diff --git a/client/views/other/dialogs.ejs b/client/views/other/dialogs.ejs index 738bc97d..35b56f92 100644 --- a/client/views/other/dialogs.ejs +++ b/client/views/other/dialogs.ejs @@ -1,357 +1,6 @@ -
-
-
-

DCS Olympus

-

Dynamic Unit Command

-
Version v0.4.5-alpha
-
- -
-
Username
-
Password
- -
- -

- - -
-
- -
-
- -
-

Olympus 1-1

-
- -
- - -
-
-

General settings

-
-
-
-
- -
- -
- -
- -
- -
- -
- -
- -
- -
-
-
- - - - - -
-
-

TACAN options

-
-
-
- -
- -
- - -
-
- -
- -
-
X
-
-
-
- -
- -
-
-
-
- - -
-
-

Radio options

-
-
- -
- - -
-
- -
- -
-
.000
-
-
-
-
-
- -
- - -
-
-
-
-
-
- - - -
- -
-
-
-
-
- - - -
- -
-
- -
-

Custom formation

-
- -
-
-
-
-
-
-
-
-
-
-
-
- -
- - -
-
- -
- -
-
- -
- - -
-
- -
- -
-
-
- - -
- - -
-
- -
-

Command mode settings

-
- -
-
- -
- -
- -
- -
- - -
-
- -
- -
-
- -
- - -
-
Select eras
-
- -
-
-
- -
-

Spawn points

-
-
- -
- -
- -
-
- -
- -
- -
-
- -
- - -
- - -
-
-

Confirm deletion method

-
- -
-

You are trying to delete a large amount of units (), which can cause the server to lag for players.

-

You may: -

    -
  • delete in batches (less lag but Olympus cannot process any additional orders until
    all units have been deleted);
  • -
  • delete immediately (you can continue to give Olympus orders but players may
    experience lag while this happens);
  • -
  • cancel this instruction.
  • -

-
- - -
+<%- include('dialogs/advancedsettings.ejs') %> +<%- include('dialogs/commandmodesettings.ejs') %> +<%- include('dialogs/customformation.ejs') %> +<%- include('dialogs/importfromfile.ejs') %> +<%- include('dialogs/slowdelete.ejs') %> +<%- include('dialogs/splash.ejs') %> \ No newline at end of file diff --git a/client/views/other/dialogs/advancedsettings.ejs b/client/views/other/dialogs/advancedsettings.ejs new file mode 100644 index 00000000..ad3222e1 --- /dev/null +++ b/client/views/other/dialogs/advancedsettings.ejs @@ -0,0 +1,162 @@ +
+
+ +
+

Olympus 1-1

+
+ +
+ + +
+
+

General settings

+
+
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ + + + + +
+
+

TACAN options

+
+
+
+ +
+ +
+ + +
+
+ +
+ +
+
X
+
+
+
+ +
+ +
+
+
+
+ + +
+
+

Radio options

+
+
+ +
+ + +
+
+ +
+ +
+
.000
+
+
+
+
+
+ +
+ + +
+
+
+
+
+
+ + + +
+ +
+
+
+
+
+ + + +
\ No newline at end of file diff --git a/client/views/other/dialogs/commandmodesettings.ejs b/client/views/other/dialogs/commandmodesettings.ejs new file mode 100644 index 00000000..3b278812 --- /dev/null +++ b/client/views/other/dialogs/commandmodesettings.ejs @@ -0,0 +1,70 @@ +
+
+ +
+

Command mode settings

+
+ +
+
+ +
+ +
+ +
+ +
+ + +
+
+ +
+ +
+
+ +
+ + +
+
Select eras
+
+ +
+
+
+ +
+

Spawn points

+
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ + +
\ No newline at end of file diff --git a/client/views/other/dialogs/customformation.ejs b/client/views/other/dialogs/customformation.ejs new file mode 100644 index 00000000..b682121d --- /dev/null +++ b/client/views/other/dialogs/customformation.ejs @@ -0,0 +1,48 @@ +
+
+ +
+

Custom formation

+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ +
+
+
+ + +
\ No newline at end of file diff --git a/client/views/other/dialogs/importfromfile.ejs b/client/views/other/dialogs/importfromfile.ejs new file mode 100644 index 00000000..6074998c --- /dev/null +++ b/client/views/other/dialogs/importfromfile.ejs @@ -0,0 +1,43 @@ +
+
+

Import from file

+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 BLUEFORNEUTRALREDFOR
Aircraft
Helicopter  
Ground units
+
+ + +
\ No newline at end of file diff --git a/client/views/other/dialogs/slowdelete.ejs b/client/views/other/dialogs/slowdelete.ejs new file mode 100644 index 00000000..372eff82 --- /dev/null +++ b/client/views/other/dialogs/slowdelete.ejs @@ -0,0 +1,21 @@ +
+
+

Confirm deletion method

+
+ +
+

You are trying to delete a large amount of units (), which can cause the server to lag for players.

+

You may: +

    +
  • delete in batches (less lag but Olympus cannot process any additional orders until
    all units have been deleted);
  • +
  • delete immediately (you can continue to give Olympus orders but players may
    experience lag while this happens);
  • +
  • cancel this instruction.
  • +

+
+ + +
\ No newline at end of file diff --git a/client/views/other/dialogs/splash.ejs b/client/views/other/dialogs/splash.ejs new file mode 100644 index 00000000..eea7e760 --- /dev/null +++ b/client/views/other/dialogs/splash.ejs @@ -0,0 +1,50 @@ +
+
+
+

DCS Olympus

+

Dynamic Unit Command

+
Version v0.4.5-alpha
+
+ +
+
Username
+
Password
+ +
+ +

+ + +
+
\ No newline at end of file From e68683acb7f9214328f8e67b223aa78a9a6b1414 Mon Sep 17 00:00:00 2001 From: PeekabooSteam Date: Tue, 7 Nov 2023 22:04:09 +0000 Subject: [PATCH 2/8] Export matrix reads from data --- client/public/stylesheets/style/style.css | 11 ++- client/src/unit/unitdatafile.ts | 3 + client/src/unit/unitdatafileexport.ts | 73 +++++++++++++++++++ client/src/unit/unitsmanager.ts | 4 + client/views/other/dialogs.ejs | 2 +- client/views/other/dialogs/importexport.ejs | 20 +++++ client/views/other/dialogs/importfromfile.ejs | 43 ----------- 7 files changed, 111 insertions(+), 45 deletions(-) create mode 100644 client/src/unit/unitdatafile.ts create mode 100644 client/src/unit/unitdatafileexport.ts create mode 100644 client/views/other/dialogs/importexport.ejs delete mode 100644 client/views/other/dialogs/importfromfile.ejs diff --git a/client/public/stylesheets/style/style.css b/client/public/stylesheets/style/style.css index be988fe1..4915e307 100644 --- a/client/public/stylesheets/style/style.css +++ b/client/public/stylesheets/style/style.css @@ -740,7 +740,16 @@ nav.ol-panel> :last-child { } /****************************************************************************************/ -#import-from-file-dialog td { +#unit-import-export-dialog th { + padding:4px 8px; +} + +#unit-import-export-dialog tr :first-child { + text-align: left; +} + +#unit-import-export-dialog td { + color:white; text-align: center; } diff --git a/client/src/unit/unitdatafile.ts b/client/src/unit/unitdatafile.ts new file mode 100644 index 00000000..b471ce40 --- /dev/null +++ b/client/src/unit/unitdatafile.ts @@ -0,0 +1,3 @@ +export abstract class unitDataFile { + constructor() {} +} \ No newline at end of file diff --git a/client/src/unit/unitdatafileexport.ts b/client/src/unit/unitdatafileexport.ts new file mode 100644 index 00000000..bef50a07 --- /dev/null +++ b/client/src/unit/unitdatafileexport.ts @@ -0,0 +1,73 @@ +import { Dialog } from "../dialog/dialog"; +import { Unit } from "./unit"; +import { unitDataFile } from "./unitdatafile"; + +export class UnitDataFileExport extends unitDataFile { + + #dialog:Dialog; + #element!:HTMLElement; + #categoryCoalitionHeaders!: HTMLElement; + #categoryCoalitionMatrix!: HTMLElement; + + constructor( elementId:string ) { + super(); + this.#dialog = new Dialog(elementId); + this.#element = this.#dialog.getElement(); + this.#categoryCoalitionMatrix = this.#element.querySelector("tbody"); + this.#categoryCoalitionHeaders = this.#element.querySelector("thead"); + } + + /** + * Show the form to start the export journey + */ + showForm(units:Unit[]) { + this.#element.setAttribute( "data-mode", "export" ); + + const data:any = {}; + + const categories:string[] = []; + const coalitions:string[] = []; + + units.filter((unit:Unit) => unit.getControlled() && unit.getAlive()).forEach((unit:Unit) => { + const category = unit.getCategory(); + const coalition = unit.getCoalition(); + + if (!coalitions.includes(coalition)) + coalitions.push(coalition); + + if (!data.hasOwnProperty(category)) { + data[category] = {}; + categories.push(category); + } + + // Cache unit data + if (!data[category].hasOwnProperty(coalition)) + data[category][coalition] = []; + + data[category][coalition].push(unit); + }); + + categories.sort(); + coalitions.sort(); + + let headersHTML:string = ``; + let matrixHTML:string = ``; + + categories.forEach((category:string, index) => { + matrixHTML += `${category}`; + + coalitions.forEach((coalition:string) => { + if (index === 0) + headersHTML += `${coalition}`; + matrixHTML += `${(data[category].hasOwnProperty(coalition)) ? ``: "-"}`; + }); + + matrixHTML += ""; + }); + + this.#categoryCoalitionHeaders.innerHTML = ` ${headersHTML}`; + this.#categoryCoalitionMatrix.innerHTML = matrixHTML; + this.#dialog.show(); + } + +} \ No newline at end of file diff --git a/client/src/unit/unitsmanager.ts b/client/src/unit/unitsmanager.ts index f50e0d23..80f5b5da 100644 --- a/client/src/unit/unitsmanager.ts +++ b/client/src/unit/unitsmanager.ts @@ -15,6 +15,7 @@ import { Popup } from "../popups/popup"; import { HotgroupPanel } from "../panels/hotgrouppanel"; import { Contact, UnitData, UnitSpawnTable } from "../interfaces"; import { Dialog } from "../dialog/dialog"; +import { UnitDataFileExport } from "./unitdatafileexport"; /** The UnitsManager handles the creation, update, and control of units. Data is strictly updated by the server ONLY. This means that any interaction from the user will always and only * result in a command to the server, executed by means of a REST PUT request. Any subsequent change in data will be reflected only when the new data is sent back by the server. This strategy allows @@ -985,6 +986,9 @@ export class UnitsManager { * TODO: Extend to aircraft and helicopters */ exportToFile() { + const fileExport = new UnitDataFileExport("unit-import-export-dialog"); + fileExport.showForm(Object.values(this.#units)); + return; var unitsToExport: { [key: string]: any } = {}; for (let ID in this.#units) { var unit = this.#units[ID]; diff --git a/client/views/other/dialogs.ejs b/client/views/other/dialogs.ejs index 35b56f92..dc7c769d 100644 --- a/client/views/other/dialogs.ejs +++ b/client/views/other/dialogs.ejs @@ -1,6 +1,6 @@ <%- include('dialogs/advancedsettings.ejs') %> <%- include('dialogs/commandmodesettings.ejs') %> <%- include('dialogs/customformation.ejs') %> -<%- include('dialogs/importfromfile.ejs') %> +<%- include('dialogs/importexport.ejs') %> <%- 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 new file mode 100644 index 00000000..e5e626c2 --- /dev/null +++ b/client/views/other/dialogs/importexport.ejs @@ -0,0 +1,20 @@ +
+
+

Export unit data to file

+
+ +
+

This data will only include Olympus-controlled units.

+ + + + + +
+
+ + +
\ No newline at end of file diff --git a/client/views/other/dialogs/importfromfile.ejs b/client/views/other/dialogs/importfromfile.ejs deleted file mode 100644 index 6074998c..00000000 --- a/client/views/other/dialogs/importfromfile.ejs +++ /dev/null @@ -1,43 +0,0 @@ -
-
-

Import from file

-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 BLUEFORNEUTRALREDFOR
Aircraft
Helicopter  
Ground units
-
- - -
\ No newline at end of file From b43afd4e9c5393246d1df39d3c69f8fd9dd80292 Mon Sep 17 00:00:00 2001 From: PeekabooSteam Date: Wed, 8 Nov 2023 14:09:35 +0000 Subject: [PATCH 3/8] Export customisation working - but ugly. --- client/src/constants/constants.ts | 6 +-- client/src/controls/unitspawnmenu.ts | 6 +-- client/src/map/map.ts | 2 +- client/src/olympusapp.ts | 1 + client/src/unit/unitdatafileexport.ts | 52 +++++++++++++++++++-- client/src/unit/unitsmanager.ts | 19 +------- client/views/other/dialogs/importexport.ejs | 6 +-- 7 files changed, 59 insertions(+), 33 deletions(-) diff --git a/client/src/constants/constants.ts b/client/src/constants/constants.ts index 8eb1663f..c529386b 100644 --- a/client/src/constants/constants.ts +++ b/client/src/constants/constants.ts @@ -111,7 +111,7 @@ export const mapBounds = { "Nevada": { bounds: new LatLngBounds([34.4037128, -119.7806729], [39.7372411, -112.1130805]), zoom: 5 }, "PersianGulf": { bounds: new LatLngBounds([21.729393, 47.572675], [33.131584, 64.7313594]), zoom: 5 }, "Caucasus": { bounds: new LatLngBounds([39.6170191, 27.634935], [47.3907982, 49.3101946]), zoom: 4 }, - // TODO "Falklands" + // TODO "Falklands", "Sinai", "Normandy 2" } export const mapLayers = { @@ -157,9 +157,6 @@ export const mapLayers = { export const IDLE = "Idle"; export const MOVE_UNIT = "Move unit"; export const COALITIONAREA_DRAW_POLYGON = "Draw Coalition Area"; -export const visibilityControls: string[] = ["human", "dcs", "aircraft", "helicopter", "groundunit-sam", "groundunit-other", "navyunit", "airbase"]; -export const visibilityControlsTypes: string[][] = [["human"], ["dcs"], ["aircraft"], ["helicopter"], ["groundunit-sam", "groundunit-sam-radar", "groundunit-sam-launcher"], ["groundunit-other", "groundunit-ewr"], ["navyunit"], ["airbase"]]; -export const visibilityControlsTooltips: string[] = ["Toggle human players visibility", "Toggle DCS controlled units visibility", "Toggle aircrafts visibility", "Toggle helicopter visibility", "Toggle SAM units visibility", "Toggle ground units (not SAM) visibility", "Toggle navy units visibility", "Toggle airbases visibility"]; export const MAP_MARKER_CONTROLS:MapMarkerControl[] = [{ "name":"Human", "image": "visibility/human.svg", @@ -206,6 +203,7 @@ export const MAP_MARKER_CONTROLS:MapMarkerControl[] = [{ 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)"; diff --git a/client/src/controls/unitspawnmenu.ts b/client/src/controls/unitspawnmenu.ts index b0ee3fd4..42b7a2ca 100644 --- a/client/src/controls/unitspawnmenu.ts +++ b/client/src/controls/unitspawnmenu.ts @@ -3,7 +3,7 @@ import { Dropdown } from "./dropdown"; import { Slider } from "./slider"; import { UnitDatabase } from "../unit/databases/unitdatabase"; import { getApp } from ".."; -import { GAME_MASTER } from "../constants/constants"; +import { GAME_MASTER, GROUND_UNIT_AIR_DEFENCE_REGEX } from "../constants/constants"; import { Airbase } from "../mission/airbase"; import { ftToM } from "../other/utils"; import { aircraftDatabase } from "../unit/databases/aircraftdatabase"; @@ -586,7 +586,7 @@ export class HelicopterSpawnMenu extends UnitSpawnMenu { export class GroundUnitSpawnMenu extends UnitSpawnMenu { protected showRangeCircles: boolean = true; - protected unitTypeFilter = (unit:any) => {return !(/\bAAA|SAM\b/.test(unit.type) || /\bmanpad|stinger\b/i.test(unit.type))}; + protected unitTypeFilter = (unit:any) => {return !(GROUND_UNIT_AIR_DEFENCE_REGEX.test(unit.type))}; /** * @@ -627,7 +627,7 @@ export class GroundUnitSpawnMenu extends UnitSpawnMenu { export class AirDefenceUnitSpawnMenu extends GroundUnitSpawnMenu { - protected unitTypeFilter = (unit:any) => {return /\bAAA|SAM\b/.test(unit.type) || /\bmanpad|stinger\b/i.test(unit.type)}; + protected unitTypeFilter = (unit:any) => {return GROUND_UNIT_AIR_DEFENCE_REGEX.test(unit.type)}; /** * diff --git a/client/src/map/map.ts b/client/src/map/map.ts index b82feb05..3563b14f 100644 --- a/client/src/map/map.ts +++ b/client/src/map/map.ts @@ -12,7 +12,7 @@ import { DestinationPreviewMarker } from "./markers/destinationpreviewmarker"; import { TemporaryUnitMarker } from "./markers/temporaryunitmarker"; import { ClickableMiniMap } from "./clickableminimap"; import { SVGInjector } from '@tanem/svg-injector' -import { mapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, visibilityControls, visibilityControlsTooltips, MOVE_UNIT, SHOW_UNIT_CONTACTS, HIDE_GROUP_MEMBERS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, visibilityControlsTypes, SHOW_UNIT_LABELS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNITS_ACQUISITION_RINGS, HIDE_UNITS_SHORT_RANGE_RINGS, FILL_SELECTED_RING, MAP_MARKER_CONTROLS } from "../constants/constants"; +import { mapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, MOVE_UNIT, SHOW_UNIT_CONTACTS, HIDE_GROUP_MEMBERS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, SHOW_UNIT_LABELS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNITS_ACQUISITION_RINGS, HIDE_UNITS_SHORT_RANGE_RINGS, FILL_SELECTED_RING, MAP_MARKER_CONTROLS } from "../constants/constants"; import { TargetMarker } from "./markers/targetmarker"; import { CoalitionArea } from "./coalitionarea/coalitionarea"; import { CoalitionAreaContextMenu } from "../contextmenus/coalitionareacontextmenu"; diff --git a/client/src/olympusapp.ts b/client/src/olympusapp.ts index 22f2ebf8..e6999bb3 100644 --- a/client/src/olympusapp.ts +++ b/client/src/olympusapp.ts @@ -400,6 +400,7 @@ export class OlympusApp { // TODO: move from here in dedicated class document.addEventListener("closeDialog", (ev: CustomEventInit) => { ev.detail._element.closest(".ol-dialog").classList.add("hide"); + document.getElementById("gray-out")?.classList.toggle("hide", true); }); /* Try and connect with the Olympus REST server */ diff --git a/client/src/unit/unitdatafileexport.ts b/client/src/unit/unitdatafileexport.ts index bef50a07..6f88ddda 100644 --- a/client/src/unit/unitdatafileexport.ts +++ b/client/src/unit/unitdatafileexport.ts @@ -1,9 +1,12 @@ +import { GROUND_UNIT_AIR_DEFENCE_REGEX } from "../constants/constants"; import { Dialog } from "../dialog/dialog"; +import { zeroAppend } from "../other/utils"; import { Unit } from "./unit"; import { unitDataFile } from "./unitdatafile"; export class UnitDataFileExport extends unitDataFile { + #data!:any; #dialog:Dialog; #element!:HTMLElement; #categoryCoalitionHeaders!: HTMLElement; @@ -15,6 +18,10 @@ export class UnitDataFileExport extends unitDataFile { this.#element = this.#dialog.getElement(); this.#categoryCoalitionMatrix = this.#element.querySelector("tbody"); this.#categoryCoalitionHeaders = this.#element.querySelector("thead"); + + this.#element.querySelector(".start-transfer")?.addEventListener("click", (ev:MouseEventInit) => { + this.#doExport(); + }); } /** @@ -28,8 +35,8 @@ export class UnitDataFileExport extends unitDataFile { const categories:string[] = []; const coalitions:string[] = []; - units.filter((unit:Unit) => unit.getControlled() && unit.getAlive()).forEach((unit:Unit) => { - const category = unit.getCategory(); + units.filter((unit:Unit) => unit.getAlive()).forEach((unit:Unit) => { + const category = ((GROUND_UNIT_AIR_DEFENCE_REGEX.test(unit.getType())) ? "Air Defence" : unit.getCategory()).replace( /(\w)([A-Z])/g, "$1 $2"); const coalition = unit.getCoalition(); if (!coalitions.includes(coalition)) @@ -47,11 +54,13 @@ export class UnitDataFileExport extends unitDataFile { data[category][coalition].push(unit); }); + this.#data = data; + categories.sort(); coalitions.sort(); let headersHTML:string = ``; - let matrixHTML:string = ``; + let matrixHTML:string = ``; categories.forEach((category:string, index) => { matrixHTML += `${category}`; @@ -59,7 +68,7 @@ export class UnitDataFileExport extends unitDataFile { coalitions.forEach((coalition:string) => { if (index === 0) headersHTML += `${coalition}`; - matrixHTML += `${(data[category].hasOwnProperty(coalition)) ? ``: "-"}`; + matrixHTML += `${(data[category].hasOwnProperty(coalition)) ? ``: "-"}`; }); matrixHTML += ""; @@ -70,4 +79,39 @@ export class UnitDataFileExport extends unitDataFile { this.#dialog.show(); } + + #doExport() { + + 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]); + } + }); + + if (selectedUnits.length === 0) { + alert("Please select at least one option for export."); + return; + } + + var unitsToExport: { [key: string]: any } = {}; + selectedUnits.forEach((unit:Unit) => { + var data: any = unit.getData(); + if (unit.getGroupName() in unitsToExport) + unitsToExport[unit.getGroupName()].push(data); + else + unitsToExport[unit.getGroupName()] = [data]; + }); + + const date = new Date(); + const a = document.createElement("a"); + const file = new Blob([JSON.stringify(unitsToExport)], { type: 'text/plain' }); + a.href = URL.createObjectURL(file); + a.download = `olympus_export_${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.click(); + this.#dialog.hide(); + } + } \ No newline at end of file diff --git a/client/src/unit/unitsmanager.ts b/client/src/unit/unitsmanager.ts index 80f5b5da..f39e4542 100644 --- a/client/src/unit/unitsmanager.ts +++ b/client/src/unit/unitsmanager.ts @@ -988,25 +988,8 @@ export class UnitsManager { exportToFile() { const fileExport = new UnitDataFileExport("unit-import-export-dialog"); fileExport.showForm(Object.values(this.#units)); - return; - var unitsToExport: { [key: string]: any } = {}; - for (let ID in this.#units) { - var unit = this.#units[ID]; - if (!["Aircraft", "Helicopter"].includes(unit.getCategory())) { - var data: any = unit.getData(); - if (unit.getGroupName() in unitsToExport) - unitsToExport[unit.getGroupName()].push(data); - else - unitsToExport[unit.getGroupName()] = [data]; - } - } - var a = document.createElement("a"); - var file = new Blob([JSON.stringify(unitsToExport)], { type: 'text/plain' }); - a.href = URL.createObjectURL(file); - a.download = 'export.json'; - a.click(); } - + /** Import ground and navy units from file * TODO: extend to support aircraft and helicopters */ diff --git a/client/views/other/dialogs/importexport.ejs b/client/views/other/dialogs/importexport.ejs index e5e626c2..ff6e8ffe 100644 --- a/client/views/other/dialogs/importexport.ejs +++ b/client/views/other/dialogs/importexport.ejs @@ -4,7 +4,7 @@
-

This data will only include Olympus-controlled units.

+

This data will include DCS and Olympus-controlled units.

@@ -14,7 +14,7 @@ \ No newline at end of file From 9ef6efa3e0e3444ab70d2245ee0256feb4c4dc65 Mon Sep 17 00:00:00 2001 From: PeekabooSteam Date: Sun, 12 Nov 2023 16:18:31 +0000 Subject: [PATCH 4/8] Mid-way commit --- client/src/interfaces.ts | 1 + client/src/mission/missionmanager.ts | 45 +++++++++++++++ client/src/unit/unit.ts | 11 +++- client/src/unit/unitdatafile.ts | 2 +- client/src/unit/unitdatafileexport.ts | 20 +++---- client/src/unit/unitdatafileimport.ts | 62 +++++++++++++++++++++ client/src/unit/unitsmanager.ts | 36 +++--------- client/views/other/dialogs/importexport.ejs | 7 ++- 8 files changed, 142 insertions(+), 42 deletions(-) create mode 100644 client/src/unit/unitdatafileimport.ts diff --git a/client/src/interfaces.ts b/client/src/interfaces.ts index c13419f5..a165176f 100644 --- a/client/src/interfaces.ts +++ b/client/src/interfaces.ts @@ -138,6 +138,7 @@ export interface Offset { export interface UnitData { category: string, + categoryDisplayName: string, ID: number; alive: boolean; human: boolean; diff --git a/client/src/mission/missionmanager.ts b/client/src/mission/missionmanager.ts index 599ca900..2ff2d713 100644 --- a/client/src/mission/missionmanager.ts +++ b/client/src/mission/missionmanager.ts @@ -35,6 +35,10 @@ export class MissionManager { this.#commandModeErasDropdown = new Dropdown("command-mode-era-options", () => {}); } + /** Update location of bullseyes + * + * @param object + */ updateBullseyes(data: BullseyesData) { const commandMode = getApp().getMissionManager().getCommandModeOptions().commandMode; for (let idx in data.bullseyes) { @@ -56,6 +60,10 @@ export class MissionManager { } } + /** Update airbase information + * + * @param object + */ updateAirbases(data: AirbasesData) { for (let idx in data.airbases) { var airbase = data.airbases[idx] @@ -75,6 +83,10 @@ export class MissionManager { } } + /** Update mission information + * + * @param object + */ updateMission(data: MissionData) { if (data.mission) { @@ -109,30 +121,63 @@ export class MissionManager { } } + /** Get the bullseyes set in this theatre + * + * @returns object + */ getBullseyes() { return this.#bullseyes; } + /** Get the airbases in this theatre + * + * @returns object + */ getAirbases() { return this.#airbases; } + /** Get the options/settings as set in the command mode + * + * @returns object + */ getCommandModeOptions() { return this.#commandModeOptions; } + /** Get the current date and time of the mission (based on local time) + * + * @returns object + */ getDateAndTime() { return this.#dateAndTime; } + /** + * Get the number of seconds left of setup time + * @returns number + */ getRemainingSetupTime() { return this.#remainingSetupTime; } + /** Get an object with the coalitions in it + * + * @returns object + */ getCoalitions() { return this.#coalitions; } + /** Get the current theatre (map) name + * + * @returns string + */ + getTheatre() { + return this.#theatre; + } + + getAvailableSpawnPoints() { if (this.getCommandModeOptions().commandMode === GAME_MASTER) return Infinity; diff --git a/client/src/unit/unit.ts b/client/src/unit/unit.ts index a8a13198..19f66abd 100644 --- a/client/src/unit/unit.ts +++ b/client/src/unit/unit.ts @@ -5,7 +5,7 @@ import { CustomMarker } from '../map/markers/custommarker'; import { SVGInjector } from '@tanem/svg-injector'; import { UnitDatabase } from './databases/unitdatabase'; import { TargetMarker } from '../map/markers/targetmarker'; -import { DLINK, DataIndexes, GAME_MASTER, HIDE_GROUP_MEMBERS, IDLE, IRST, MOVE_UNIT, OPTIC, RADAR, ROEs, RWR, SHOW_UNIT_CONTACTS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, VISUAL, emissionsCountermeasures, reactionsToThreat, states, SHOW_UNITS_ACQUISITION_RINGS, HIDE_UNITS_SHORT_RANGE_RINGS, FILL_SELECTED_RING } from '../constants/constants'; +import { DLINK, DataIndexes, GAME_MASTER, HIDE_GROUP_MEMBERS, IDLE, IRST, MOVE_UNIT, OPTIC, RADAR, ROEs, RWR, SHOW_UNIT_CONTACTS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, VISUAL, emissionsCountermeasures, reactionsToThreat, states, SHOW_UNITS_ACQUISITION_RINGS, HIDE_UNITS_SHORT_RANGE_RINGS, FILL_SELECTED_RING, GROUND_UNIT_AIR_DEFENCE_REGEX } from '../constants/constants'; import { DataExtractor } from '../server/dataextractor'; import { groundUnitDatabase } from './databases/groundunitdatabase'; import { navyUnitDatabase } from './databases/navyunitdatabase'; @@ -211,6 +211,14 @@ export class Unit extends CustomMarker { return ""; } + /** Get the category but for display use - for the user. (i.e. has spaces in it) + * + * @returns string + */ + getCategoryLabel() { + return ((GROUND_UNIT_AIR_DEFENCE_REGEX.test(this.getType())) ? "Air Defence" : this.getCategory()).replace(/([a-z])([A-Z])/g, "$1 $2"); + } + /********************** Unit data *************************/ setData(dataExtractor: DataExtractor) { var updateMarker = !getApp().getMap().hasLayer(this); @@ -291,6 +299,7 @@ export class Unit extends CustomMarker { getData(): UnitData { return { category: this.getCategory(), + categoryDisplayName: this.getCategoryLabel(), ID: this.ID, alive: this.#alive, human: this.#human, diff --git a/client/src/unit/unitdatafile.ts b/client/src/unit/unitdatafile.ts index b471ce40..91baddd1 100644 --- a/client/src/unit/unitdatafile.ts +++ b/client/src/unit/unitdatafile.ts @@ -1,3 +1,3 @@ -export abstract class unitDataFile { +export abstract class UnitDataFile { constructor() {} } \ No newline at end of file diff --git a/client/src/unit/unitdatafileexport.ts b/client/src/unit/unitdatafileexport.ts index 6f88ddda..35986d45 100644 --- a/client/src/unit/unitdatafileexport.ts +++ b/client/src/unit/unitdatafileexport.ts @@ -1,10 +1,11 @@ +import { getApp } from ".."; import { GROUND_UNIT_AIR_DEFENCE_REGEX } from "../constants/constants"; import { Dialog } from "../dialog/dialog"; import { zeroAppend } from "../other/utils"; import { Unit } from "./unit"; -import { unitDataFile } from "./unitdatafile"; +import { UnitDataFile } from "./unitdatafile"; -export class UnitDataFileExport extends unitDataFile { +export class UnitDataFileExport extends UnitDataFile { #data!:any; #dialog:Dialog; @@ -19,7 +20,7 @@ export class UnitDataFileExport extends unitDataFile { this.#categoryCoalitionMatrix = this.#element.querySelector("tbody"); this.#categoryCoalitionHeaders = this.#element.querySelector("thead"); - this.#element.querySelector(".start-transfer")?.addEventListener("click", (ev:MouseEventInit) => { + this.#element.querySelector(".start-transfer.export")?.addEventListener("click", (ev:MouseEventInit) => { this.#doExport(); }); } @@ -30,13 +31,13 @@ export class UnitDataFileExport extends unitDataFile { showForm(units:Unit[]) { this.#element.setAttribute( "data-mode", "export" ); - const data:any = {}; - + const data:any = {}; const categories:string[] = []; const coalitions:string[] = []; + const unitCanBeExported = (unit:Unit) => ["GroundUnit", "NavyUnit"].includes(unit.getCategory()); - units.filter((unit:Unit) => unit.getAlive()).forEach((unit:Unit) => { - const category = ((GROUND_UNIT_AIR_DEFENCE_REGEX.test(unit.getType())) ? "Air Defence" : unit.getCategory()).replace( /(\w)([A-Z])/g, "$1 $2"); + units.filter((unit:Unit) => unit.getAlive() && unitCanBeExported(unit)).forEach((unit:Unit) => { + const category = unit.getCategoryLabel(); const coalition = unit.getCoalition(); if (!coalitions.includes(coalition)) @@ -68,7 +69,7 @@ export class UnitDataFileExport extends unitDataFile { coalitions.forEach((coalition:string) => { if (index === 0) headersHTML += ``; - matrixHTML += ``; + matrixHTML += ``; }); matrixHTML += ""; @@ -78,7 +79,6 @@ export class UnitDataFileExport extends unitDataFile { this.#categoryCoalitionMatrix.innerHTML = matrixHTML; this.#dialog.show(); } - #doExport() { @@ -109,7 +109,7 @@ export class UnitDataFileExport extends unitDataFile { const a = document.createElement("a"); const file = new Blob([JSON.stringify(unitsToExport)], { type: 'text/plain' }); a.href = URL.createObjectURL(file); - a.download = `olympus_export_${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.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.click(); this.#dialog.hide(); } diff --git a/client/src/unit/unitdatafileimport.ts b/client/src/unit/unitdatafileimport.ts new file mode 100644 index 00000000..e91af4d0 --- /dev/null +++ b/client/src/unit/unitdatafileimport.ts @@ -0,0 +1,62 @@ +import { getApp } from ".."; +import { GROUND_UNIT_AIR_DEFENCE_REGEX } from "../constants/constants"; +import { Dialog } from "../dialog/dialog"; +import { zeroAppend } from "../other/utils"; +import { Unit } from "./unit"; +import { UnitDataFile } from "./unitdatafile"; + +export class UnitDataFileImport extends UnitDataFile { + + #data!:any; + #dialog:Dialog; + #element!:HTMLElement; + #categoryCoalitionHeaders!: HTMLElement; + #categoryCoalitionMatrix!: HTMLElement; + + constructor( elementId:string ) { + super(); + this.#dialog = new Dialog(elementId); + this.#element = this.#dialog.getElement(); + this.#categoryCoalitionMatrix = this.#element.querySelector("tbody"); + this.#categoryCoalitionHeaders = this.#element.querySelector("thead"); + + // this.#element.querySelector(".start-transfer.import")?.addEventListener("click", (ev:MouseEventInit) => { + // this.#doImport(); + // }); + } + + #doImport() { + + /* + for (let groupName in groups) { + if (groupName !== "" && groups[groupName].length > 0 && (groups[groupName].every((unit: UnitData) => { return unit.category == "GroundUnit"; }) || groups[groupName].every((unit: any) => { return unit.category == "NavyUnit"; }))) { + var aliveUnits = groups[groupName].filter((unit: UnitData) => { return unit.alive }); + var units = aliveUnits.map((unit: UnitData) => { + return { unitType: unit.name, location: unit.position, liveryID: "" } + }); + getApp().getUnitsManager().spawnUnits(groups[groupName][0].category, units, groups[groupName][0].coalition, true); + } + } + //*/ + } + + selectFile() { + var input = document.createElement("input"); + input.type = "file"; + input.addEventListener("change", (e: any) => { + var file = e.target.files[0]; + if (!file) { + return; + } + var reader = new FileReader(); + reader.onload = function (e: any) { + var contents = e.target.result; + var groups = JSON.parse(contents); + console.log(groups); + }; + reader.readAsText(file); + }) + input.click(); + } + +} \ No newline at end of file diff --git a/client/src/unit/unitsmanager.ts b/client/src/unit/unitsmanager.ts index f39e4542..971a70c1 100644 --- a/client/src/unit/unitsmanager.ts +++ b/client/src/unit/unitsmanager.ts @@ -16,6 +16,7 @@ import { HotgroupPanel } from "../panels/hotgrouppanel"; import { Contact, UnitData, UnitSpawnTable } from "../interfaces"; import { Dialog } from "../dialog/dialog"; import { UnitDataFileExport } from "./unitdatafileexport"; +import { UnitDataFileImport } from "./unitdatafileimport"; /** The UnitsManager handles the creation, update, and control of units. Data is strictly updated by the server ONLY. This means that any interaction from the user will always and only * result in a command to the server, executed by means of a REST PUT request. Any subsequent change in data will be reflected only when the new data is sent back by the server. This strategy allows @@ -28,7 +29,8 @@ export class UnitsManager { #selectionEventDisabled: boolean = false; #slowDeleteDialog!:Dialog; #units: { [ID: number]: Unit }; - + #unitDataExport!:UnitDataFileExport; + #unitDataImport!:UnitDataFileImport; constructor() { this.#copiedUnits = []; @@ -986,38 +988,18 @@ export class UnitsManager { * TODO: Extend to aircraft and helicopters */ exportToFile() { - const fileExport = new UnitDataFileExport("unit-import-export-dialog"); - fileExport.showForm(Object.values(this.#units)); + if (!this.#unitDataExport) + this.#unitDataExport = new UnitDataFileExport("unit-import-export-dialog"); + this.#unitDataExport.showForm(Object.values(this.#units)); } /** Import ground and navy units from file * TODO: extend to support aircraft and helicopters */ importFromFile() { - var input = document.createElement("input"); - input.type = "file"; - input.addEventListener("change", (e: any) => { - var file = e.target.files[0]; - if (!file) { - return; - } - var reader = new FileReader(); - reader.onload = function (e: any) { - var contents = e.target.result; - var groups = JSON.parse(contents); - for (let groupName in groups) { - if (groupName !== "" && groups[groupName].length > 0 && (groups[groupName].every((unit: UnitData) => { return unit.category == "GroundUnit"; }) || groups[groupName].every((unit: any) => { return unit.category == "NavyUnit"; }))) { - var aliveUnits = groups[groupName].filter((unit: UnitData) => { return unit.alive }); - var units = aliveUnits.map((unit: UnitData) => { - return { unitType: unit.name, location: unit.position, liveryID: "" } - }); - getApp().getUnitsManager().spawnUnits(groups[groupName][0].category, units, groups[groupName][0].coalition, true); - } - } - }; - reader.readAsText(file); - }) - input.click(); + if (!this.#unitDataImport) + this.#unitDataImport = new UnitDataFileImport("unit-import-export-dialog"); + this.#unitDataImport.selectFile(); } /** Spawn a new group of units diff --git a/client/views/other/dialogs/importexport.ejs b/client/views/other/dialogs/importexport.ejs index ff6e8ffe..b33a73a3 100644 --- a/client/views/other/dialogs/importexport.ejs +++ b/client/views/other/dialogs/importexport.ejs @@ -1,10 +1,10 @@
-

Export unit data to file

+

Export unit data to file

-

This data will include DCS and Olympus-controlled units.

+

Note: only ground and naval units can be exported at this time. (Air units will be possible soon.)

${coalition}${(data[category].hasOwnProperty(coalition)) ? ``: "-"}
@@ -14,7 +14,8 @@ \ No newline at end of file From 0421f6b8fed1bbd008d2e42b9e7e61f8077e4163 Mon Sep 17 00:00:00 2001 From: PeekabooSteam Date: Thu, 16 Nov 2023 11:16:54 +0000 Subject: [PATCH 5/8] Fixed regex undefined --- client/src/constants/constants.ts | 1 - client/src/unit/unitdatafileexport.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/client/src/constants/constants.ts b/client/src/constants/constants.ts index c529386b..43065df5 100644 --- a/client/src/constants/constants.ts +++ b/client/src/constants/constants.ts @@ -204,7 +204,6 @@ export const MAP_MARKER_CONTROLS:MapMarkerControl[] = [{ 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/unitdatafileexport.ts b/client/src/unit/unitdatafileexport.ts index 35986d45..013d49a5 100644 --- a/client/src/unit/unitdatafileexport.ts +++ b/client/src/unit/unitdatafileexport.ts @@ -34,7 +34,7 @@ export class UnitDataFileExport extends UnitDataFile { const data:any = {}; const categories:string[] = []; const coalitions:string[] = []; - const unitCanBeExported = (unit:Unit) => ["GroundUnit", "NavyUnit"].includes(unit.getCategory()); + const unitCanBeExported = (unit:Unit) => !["Aircraft", "Helicopter"].includes(unit.getCategory()); units.filter((unit:Unit) => unit.getAlive() && unitCanBeExported(unit)).forEach((unit:Unit) => { const category = unit.getCategoryLabel(); From 210c1fbecfb1e94d60cce4779d61da6dab26122b Mon Sep 17 00:00:00 2001 From: PeekabooSteam Date: Thu, 16 Nov 2023 21:44:40 +0000 Subject: [PATCH 6/8] Added a selective interface for import and export --- client/public/stylesheets/style/style.css | 65 +++++++-- client/src/other/utils.ts | 14 +- client/src/unit/unitdatafile.ts | 55 ++++++++ client/src/unit/unitdatafileexport.ts | 58 ++------ client/src/unit/unitdatafileimport.ts | 140 +++++++++++++++----- client/src/unit/unitsmanager.ts | 4 +- client/views/other/dialogs.ejs | 13 +- client/views/other/dialogs/importexport.ejs | 13 +- 8 files changed, 258 insertions(+), 104 deletions(-) diff --git a/client/public/stylesheets/style/style.css b/client/public/stylesheets/style/style.css index f72e558b..2117bd1d 100644 --- a/client/public/stylesheets/style/style.css +++ b/client/public/stylesheets/style/style.css @@ -759,20 +759,6 @@ nav.ol-panel> :last-child { } /****************************************************************************************/ -#unit-import-export-dialog th { - padding:4px 8px; -} - -#unit-import-export-dialog tr :first-child { - text-align: left; -} - -#unit-import-export-dialog td { - color:white; - text-align: center; -} - - #splash-screen { border-radius: var(--border-radius-md); overflow: hidden; @@ -1306,6 +1292,10 @@ dl.ol-data-grid dd { width: 16px; } +.ol-checkbox input[type="checkbox"]:disabled:before { + opacity: 10%; +} + .ol-checkbox input[type="checkbox"]:checked::before { background-image: url("/resources/theme/images/icons/square-check-solid.svg"); } @@ -1516,3 +1506,50 @@ input[type=number]::-webkit-outer-spin-button { .ol-log-entry { border-bottom: 1px solid #FFFFFF44; } + +.file-import-export { + max-width: 500px; +} + +.file-import-export .ol-dialog-content { + display:flex; + flex-direction: column; + justify-content: center; +} + +.file-import-export p { + background-color: var(--background-grey); + border-left:6px solid var(--secondary-blue-text); + padding:12px; +} + +.file-import-export th { + padding:4px 6px; +} + +.file-import-export tr td:first-child { + text-align: left; +} + +.file-import-export td { + color:white; + text-align: center; +} + +.file-import-export .ol-checkbox { + display:flex; + justify-content: center; +} + +.file-import-export .ol-checkbox input::before { + margin-right: 0; +} + +.file-import-export .ol-checkbox span { + display:none; +} + +.file-import-export button.start-transfer { + background-color: var(--secondary-blue-text); + border-color: var(--secondary-blue-text); +} \ No newline at end of file diff --git a/client/src/other/utils.ts b/client/src/other/utils.ts index 64f67089..aca47b3b 100644 --- a/client/src/other/utils.ts +++ b/client/src/other/utils.ts @@ -434,14 +434,24 @@ export function convertDateAndTimeToDate(dateAndTime: DateAndTime) { return new Date(year, month, date.Day, time.h, time.m, time.s); } -export function createCheckboxOption(value: string, text: string, checked: boolean = true, callback: CallableFunction = (ev: any) => {}) { +export function createCheckboxOption(value: string, text: string, checked: boolean = true, callback: CallableFunction = (ev: any) => {}, options?:any) { + options = { + "disabled": false, + "name": "", + "readOnly": false, + ...options + }; var div = document.createElement("div"); div.classList.add("ol-checkbox"); var label = document.createElement("label"); label.title = text; var input = document.createElement("input"); input.type = "checkbox"; - input.checked = checked; + input.checked = checked; + input.name = options.name; + input.disabled = options.disabled; + input.readOnly = options.readOnly; + input.value = value; var span = document.createElement("span"); span.innerText = value; label.appendChild(input); diff --git a/client/src/unit/unitdatafile.ts b/client/src/unit/unitdatafile.ts index 91baddd1..1994bc6d 100644 --- a/client/src/unit/unitdatafile.ts +++ b/client/src/unit/unitdatafile.ts @@ -1,3 +1,58 @@ +import { Dialog } from "../dialog/dialog"; +import { createCheckboxOption } from "../other/utils"; + export abstract class UnitDataFile { + + protected data:any; + protected dialog!:Dialog; + constructor() {} + + buildCategoryCoalitionTable() { + + const categories = this.#getCategoriesFromData(); + const coalitions = ["blue", "neutral", "red"]; + + let headersHTML:string = ``; + let matrixHTML:string = ``; + + categories.forEach((category:string, index) => { + matrixHTML += ``; + + coalitions.forEach((coalition:string) => { + if (index === 0) + headersHTML += ``; + + const optionIsValid = this.data[category].hasOwnProperty(coalition); + 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 + + matrixHTML += ``; + + }); + matrixHTML += ""; + }); + + const table = this.dialog.getElement().querySelector("table.categories-coalitions"); + + (table.tHead).innerHTML = `${headersHTML}`; + (table.querySelector(`tbody`)).innerHTML = matrixHTML; + + } + + #getCategoriesFromData() { + const categories = Object.keys(this.data); + categories.sort(); + return categories; + } + + getData() { + return this.data; + } } \ No newline at end of file diff --git a/client/src/unit/unitdatafileexport.ts b/client/src/unit/unitdatafileexport.ts index 013d49a5..2085381f 100644 --- a/client/src/unit/unitdatafileexport.ts +++ b/client/src/unit/unitdatafileexport.ts @@ -1,5 +1,4 @@ import { getApp } from ".."; -import { GROUND_UNIT_AIR_DEFENCE_REGEX } from "../constants/constants"; import { Dialog } from "../dialog/dialog"; import { zeroAppend } from "../other/utils"; import { Unit } from "./unit"; @@ -7,20 +6,16 @@ import { UnitDataFile } from "./unitdatafile"; export class UnitDataFileExport extends UnitDataFile { - #data!:any; - #dialog:Dialog; + protected data!:any; + protected dialog:Dialog; #element!:HTMLElement; - #categoryCoalitionHeaders!: HTMLElement; - #categoryCoalitionMatrix!: HTMLElement; constructor( elementId:string ) { super(); - this.#dialog = new Dialog(elementId); - this.#element = this.#dialog.getElement(); - this.#categoryCoalitionMatrix = this.#element.querySelector("tbody"); - this.#categoryCoalitionHeaders = this.#element.querySelector("thead"); + this.dialog = new Dialog(elementId); + this.#element = this.dialog.getElement(); - this.#element.querySelector(".start-transfer.export")?.addEventListener("click", (ev:MouseEventInit) => { + this.#element.querySelector(".start-transfer")?.addEventListener("click", (ev:MouseEventInit) => { this.#doExport(); }); } @@ -28,56 +23,27 @@ export class UnitDataFileExport extends UnitDataFile { /** * Show the form to start the export journey */ - showForm(units:Unit[]) { - this.#element.setAttribute( "data-mode", "export" ); - + showForm(units:Unit[]) { const data:any = {}; - const categories:string[] = []; - const coalitions:string[] = []; const unitCanBeExported = (unit:Unit) => !["Aircraft", "Helicopter"].includes(unit.getCategory()); units.filter((unit:Unit) => unit.getAlive() && unitCanBeExported(unit)).forEach((unit:Unit) => { - const category = unit.getCategoryLabel(); + const category = unit.getCategory(); const coalition = unit.getCoalition(); - if (!coalitions.includes(coalition)) - coalitions.push(coalition); - if (!data.hasOwnProperty(category)) { data[category] = {}; - categories.push(category); } - // Cache unit data if (!data[category].hasOwnProperty(coalition)) data[category][coalition] = []; data[category][coalition].push(unit); }); - this.#data = data; - - categories.sort(); - coalitions.sort(); - - let headersHTML:string = ``; - let matrixHTML:string = ``; - - categories.forEach((category:string, index) => { - matrixHTML += ``; - - coalitions.forEach((coalition:string) => { - if (index === 0) - headersHTML += ``; - matrixHTML += ``; - }); - - matrixHTML += ""; - }); - - this.#categoryCoalitionHeaders.innerHTML = `${headersHTML}`; - this.#categoryCoalitionMatrix.innerHTML = matrixHTML; - this.#dialog.show(); + this.data = data; + this.buildCategoryCoalitionTable(); + this.dialog.show(); } #doExport() { @@ -87,7 +53,7 @@ export class UnitDataFileExport extends UnitDataFile { 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]); + selectedUnits = selectedUnits.concat(this.data[category][coalition]); } }); @@ -111,7 +77,7 @@ export class UnitDataFileExport extends UnitDataFile { 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.click(); - this.#dialog.hide(); + this.dialog.hide(); } } \ No newline at end of file diff --git a/client/src/unit/unitdatafileimport.ts b/client/src/unit/unitdatafileimport.ts index e91af4d0..b883c150 100644 --- a/client/src/unit/unitdatafileimport.ts +++ b/client/src/unit/unitdatafileimport.ts @@ -1,43 +1,110 @@ import { getApp } from ".."; -import { GROUND_UNIT_AIR_DEFENCE_REGEX } from "../constants/constants"; import { Dialog } from "../dialog/dialog"; -import { zeroAppend } from "../other/utils"; -import { Unit } from "./unit"; +import { UnitData } from "../interfaces"; import { UnitDataFile } from "./unitdatafile"; export class UnitDataFileImport extends UnitDataFile { - #data!:any; - #dialog:Dialog; - #element!:HTMLElement; - #categoryCoalitionHeaders!: HTMLElement; - #categoryCoalitionMatrix!: HTMLElement; + protected data!:any; + protected dialog:Dialog; + #fileData!:{[key:string]: UnitData[]}; constructor( elementId:string ) { super(); - this.#dialog = new Dialog(elementId); - this.#element = this.#dialog.getElement(); - this.#categoryCoalitionMatrix = this.#element.querySelector("tbody"); - this.#categoryCoalitionHeaders = this.#element.querySelector("thead"); - - // this.#element.querySelector(".start-transfer.import")?.addEventListener("click", (ev:MouseEventInit) => { - // this.#doImport(); - // }); + this.dialog = new Dialog(elementId); + this.dialog.getElement().querySelector(".start-transfer")?.addEventListener("click", (ev:MouseEventInit) => { + this.#doImport(); + this.dialog.hide(); + }); } #doImport() { - - /* - for (let groupName in groups) { - if (groupName !== "" && groups[groupName].length > 0 && (groups[groupName].every((unit: UnitData) => { return unit.category == "GroundUnit"; }) || groups[groupName].every((unit: any) => { return unit.category == "NavyUnit"; }))) { - var aliveUnits = groups[groupName].filter((unit: UnitData) => { return unit.alive }); - var units = aliveUnits.map((unit: UnitData) => { - return { unitType: unit.name, location: unit.position, liveryID: "" } - }); - getApp().getUnitsManager().spawnUnits(groups[groupName][0].category, units, groups[groupName][0].coalition, true); - } - } - //*/ + + let selectedCategories:any = {}; + const unitsManager = getApp().getUnitsManager(); + + 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] || {}; + selectedCategories[category][coalition] = true; + } + }); + + for (const[groupName, groupData] of Object.entries(this.#fileData)) { + if (groupName === "" || groupData.length === 0 || !this.#unitGroupDataCanBeImported(groupData)) + continue; + + let { category, coalition } = groupData[0]; + + if (!selectedCategories.hasOwnProperty(category) + || !selectedCategories[category].hasOwnProperty(coalition) + || selectedCategories[category][coalition] !== true ) + continue; + + let unitsToSpawn = groupData.filter((unitData:UnitData) => this.#unitDataCanBeImported(unitData)).map((unitData:UnitData) => { + return { unitType: unitData.name, location: unitData.position, liveryID: "" } + }); + + unitsManager.spawnUnits(category, unitsToSpawn, coalition, true); + } + + /* + for (let groupName in groups) { + if (groupName !== "" && groups[groupName].length > 0 && (groups[groupName].every((unit: UnitData) => { return unit.category == "GroundUnit"; }) || groups[groupName].every((unit: any) => { return unit.category == "NavyUnit"; }))) { + var aliveUnits = groups[groupName].filter((unit: UnitData) => { return unit.alive }); + var units = aliveUnits.map((unit: UnitData) => { + return { unitType: unit.name, location: unit.position, liveryID: "" } + }); + getApp().getUnitsManager().spawnUnits(groups[groupName][0].category, units, groups[groupName][0].coalition, true); + } + } + //*/ + } + + #showForm() { + const data:any = {}; + + for (const [ group, units ] of Object.entries(this.#fileData) ) { + if (group === "" || units.length === 0) + continue; + + if (units.some((unit:UnitData) => !this.#unitDataCanBeImported(unit))) + continue; + + const category = units[0].category; + + if (!data.hasOwnProperty(category)) { + data[category] = {}; + } + + units.forEach((unit:UnitData) => { + if (!data[category].hasOwnProperty(unit.coalition)) + data[category][unit.coalition] = []; + + data[category][unit.coalition].push(unit); + }); + + } + + /* + groups.filter((unit:Unit) => unitCanBeImported(unit)).forEach((unit:Unit) => { + const category = unit.getCategoryLabel(); + const coalition = unit.getCoalition(); + + if (!data.hasOwnProperty(category)) { + data[category] = {}; + } + + if (!data[category].hasOwnProperty(coalition)) + data[category][coalition] = []; + + data[category][coalition].push(unit); + }); + //*/ + this.data = data; + this.buildCategoryCoalitionTable(); + this.dialog.show(); } selectFile() { @@ -49,14 +116,23 @@ export class UnitDataFileImport extends UnitDataFile { return; } var reader = new FileReader(); - reader.onload = function (e: any) { - var contents = e.target.result; - var groups = JSON.parse(contents); - console.log(groups); + reader.onload = (e: any) => { + this.#fileData = JSON.parse(e.target.result); + this.#showForm(); }; reader.readAsText(file); }) input.click(); } + #unitDataCanBeImported(unitData:UnitData) { + return unitData.alive && this.#unitGroupDataCanBeImported([unitData]); + } + + #unitGroupDataCanBeImported(unitGroupData:UnitData[]) { + return unitGroupData.every((unitData:UnitData) => { + return !["Aircraft", "Helicopter"].includes(unitData.category); + }) && unitGroupData.some((unitData:UnitData) => unitData.alive); + } + } \ No newline at end of file diff --git a/client/src/unit/unitsmanager.ts b/client/src/unit/unitsmanager.ts index 5e18e696..825ffef7 100644 --- a/client/src/unit/unitsmanager.ts +++ b/client/src/unit/unitsmanager.ts @@ -1006,7 +1006,7 @@ export class UnitsManager { */ exportToFile() { if (!this.#unitDataExport) - this.#unitDataExport = new UnitDataFileExport("unit-import-export-dialog"); + this.#unitDataExport = new UnitDataFileExport("unit-export-dialog"); this.#unitDataExport.showForm(Object.values(this.#units)); } @@ -1015,7 +1015,7 @@ export class UnitsManager { */ importFromFile() { if (!this.#unitDataImport) - this.#unitDataImport = new UnitDataFileImport("unit-import-export-dialog"); + this.#unitDataImport = new UnitDataFileImport("unit-import-dialog"); this.#unitDataImport.selectFile(); } diff --git a/client/views/other/dialogs.ejs b/client/views/other/dialogs.ejs index dc7c769d..e1e59b2f 100644 --- a/client/views/other/dialogs.ejs +++ b/client/views/other/dialogs.ejs @@ -1,6 +1,17 @@ <%- include('dialogs/advancedsettings.ejs') %> <%- include('dialogs/commandmodesettings.ejs') %> <%- include('dialogs/customformation.ejs') %> -<%- include('dialogs/importexport.ejs') %> +<%- include('dialogs/importexport.ejs', { + "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" +}) %> +<%- 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" +}) %> <%- 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 b33a73a3..6f10f716 100644 --- a/client/views/other/dialogs/importexport.ejs +++ b/client/views/other/dialogs/importexport.ejs @@ -1,11 +1,11 @@ -
+
-

Export unit data to file

+

<%= title %>

-

Note: only ground and naval units can be exported at this time. (Air units will be possible soon.)

-
${category}${coalition[0].toUpperCase()+coalition.substring(1)}${checkboxHTML}
 
${category}${coalition}
 
+

<%= textContent %>

+
@@ -14,8 +14,7 @@ \ No newline at end of file From 8ef48ad9771cf2e655939e4d078f058394c77261 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Tue, 28 Nov 2023 16:59:23 +0100 Subject: [PATCH 7/8] Implemented filename selector --- client/demo.js | 9 ++-- client/public/stylesheets/style/style.css | 36 +++++++++++++ .../olympus/images/icons/keyboard-solid.svg | 1 + client/src/constants/constants.ts | 2 +- client/src/unit/unitdatafile.ts | 26 +++++----- client/src/unit/unitdatafileexport.ts | 52 ++++++++++++------- client/src/unit/unitdatafileimport.ts | 40 +++++++------- client/views/other/dialogs.ejs | 6 ++- client/views/other/dialogs/importexport.ejs | 9 ++++ 9 files changed, 122 insertions(+), 59 deletions(-) create mode 100644 client/public/themes/olympus/images/icons/keyboard-solid.svg 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 += ``; - coalitions.forEach((coalition:string) => { + coalitions.forEach((coalition: string) => { if (index === 0) - headersHTML += ``; + headersHTML += ``; 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 += ``; - + }); 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) { %> +
+ + + +
+ <% } %> +
${category}${coalition[0].toUpperCase()+coalition.substring(1)}${coalition[0].toUpperCase() + coalition.substring(1)}${checkboxHTML}
 
From de7eeec94af05dfbcd1eb4344a892f44bc7cf2ce Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Tue, 28 Nov 2023 17:30:59 +0100 Subject: [PATCH 8/8] Completed import/export page --- client/src/unit/{ => importexport}/unitdatafile.ts | 13 ++++++++++--- .../unit/{ => importexport}/unitdatafileexport.ts | 8 ++++---- .../unit/{ => importexport}/unitdatafileimport.ts | 6 +++--- client/src/unit/unitsmanager.ts | 4 ++-- client/views/other/dialogs/splash.ejs | 2 +- 5 files changed, 20 insertions(+), 13 deletions(-) rename client/src/unit/{ => importexport}/unitdatafile.ts (84%) rename client/src/unit/{ => importexport}/unitdatafileexport.ts (95%) rename client/src/unit/{ => importexport}/unitdatafileimport.ts (97%) diff --git a/client/src/unit/unitdatafile.ts b/client/src/unit/importexport/unitdatafile.ts similarity index 84% rename from client/src/unit/unitdatafile.ts rename to client/src/unit/importexport/unitdatafile.ts index 5f3aad52..2a0c1611 100644 --- a/client/src/unit/unitdatafile.ts +++ b/client/src/unit/importexport/unitdatafile.ts @@ -1,5 +1,12 @@ -import { Dialog } from "../dialog/dialog"; -import { createCheckboxOption } from "../other/utils"; +import { Dialog } from "../../dialog/dialog"; +import { createCheckboxOption } from "../../other/utils"; + +var categoryMap = { + "Aircraft": "Aircraft", + "Helicopter": "Helicopter", + "GroundUnit": "Ground units", + "NavyUnit": "Naval units" +} export abstract class UnitDataFile { @@ -17,7 +24,7 @@ export abstract class UnitDataFile { let matrixHTML: string = ``; categories.forEach((category: string, index) => { - matrixHTML += ``; + matrixHTML += ``; coalitions.forEach((coalition: string) => { if (index === 0) diff --git a/client/src/unit/unitdatafileexport.ts b/client/src/unit/importexport/unitdatafileexport.ts similarity index 95% rename from client/src/unit/unitdatafileexport.ts rename to client/src/unit/importexport/unitdatafileexport.ts index 7f90b715..1bdb0d22 100644 --- a/client/src/unit/unitdatafileexport.ts +++ b/client/src/unit/importexport/unitdatafileexport.ts @@ -1,7 +1,7 @@ -import { getApp } from ".."; -import { Dialog } from "../dialog/dialog"; -import { zeroAppend } from "../other/utils"; -import { Unit } from "./unit"; +import { getApp } from "../.."; +import { Dialog } from "../../dialog/dialog"; +import { zeroAppend } from "../../other/utils"; +import { Unit } from "../unit"; import { UnitDataFile } from "./unitdatafile"; export class UnitDataFileExport extends UnitDataFile { diff --git a/client/src/unit/unitdatafileimport.ts b/client/src/unit/importexport/unitdatafileimport.ts similarity index 97% rename from client/src/unit/unitdatafileimport.ts rename to client/src/unit/importexport/unitdatafileimport.ts index 549a5a9e..deeea4a1 100644 --- a/client/src/unit/unitdatafileimport.ts +++ b/client/src/unit/importexport/unitdatafileimport.ts @@ -1,6 +1,6 @@ -import { getApp } from ".."; -import { Dialog } from "../dialog/dialog"; -import { UnitData } from "../interfaces"; +import { getApp } from "../.."; +import { Dialog } from "../../dialog/dialog"; +import { UnitData } from "../../interfaces"; import { UnitDataFile } from "./unitdatafile"; export class UnitDataFileImport extends UnitDataFile { diff --git a/client/src/unit/unitsmanager.ts b/client/src/unit/unitsmanager.ts index c046c500..4372bf16 100644 --- a/client/src/unit/unitsmanager.ts +++ b/client/src/unit/unitsmanager.ts @@ -16,8 +16,8 @@ import { HotgroupPanel } from "../panels/hotgrouppanel"; import { Contact, UnitData, UnitSpawnTable } from "../interfaces"; import { Dialog } from "../dialog/dialog"; import { Group } from "./group"; -import { UnitDataFileExport } from "./unitdatafileexport"; -import { UnitDataFileImport } from "./unitdatafileimport"; +import { UnitDataFileExport } from "./importexport/unitdatafileexport"; +import { UnitDataFileImport } from "./importexport/unitdatafileimport"; /** The UnitsManager handles the creation, update, and control of units. Data is strictly updated by the server ONLY. This means that any interaction from the user will always and only * result in a command to the server, executed by means of a REST PUT request. Any subsequent change in data will be reflected only when the new data is sent back by the server. This strategy allows diff --git a/client/views/other/dialogs/splash.ejs b/client/views/other/dialogs/splash.ejs index eea7e760..2a883842 100644 --- a/client/views/other/dialogs/splash.ejs +++ b/client/views/other/dialogs/splash.ejs @@ -3,7 +3,7 @@

DCS Olympus

Dynamic Unit Command

-
Version v0.4.5-alpha
+
Version v0.4.8-alpha
${category}
${categoryMap[category as keyof typeof categoryMap]}