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 += `${category}`; + + coalitions.forEach((coalition:string) => { + if (index === 0) + headersHTML += `${coalition[0].toUpperCase()+coalition.substring(1)}`; + + 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 += `${checkboxHTML}`; + + }); + 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 += `${category}`; - - coalitions.forEach((coalition:string) => { - if (index === 0) - headersHTML += `${coalition}`; - 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.)

- +

<%= textContent %>

+
@@ -14,8 +14,7 @@ \ No newline at end of file