Added a selective interface for import and export

This commit is contained in:
PeekabooSteam 2023-11-16 21:44:40 +00:00
parent b89c5142b6
commit 210c1fbecf
8 changed files with 258 additions and 104 deletions

View File

@ -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);
}

View File

@ -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);

View File

@ -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 += `<tr><td>${category}</td>`;
coalitions.forEach((coalition:string) => {
if (index === 0)
headersHTML += `<th data-coalition="${coalition}">${coalition[0].toUpperCase()+coalition.substring(1)}</th>`;
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 += `<td data-coalition="${coalition}">${checkboxHTML}</td>`;
});
matrixHTML += "</tr>";
});
const table = <HTMLTableElement>this.dialog.getElement().querySelector("table.categories-coalitions");
(<HTMLElement>table.tHead).innerHTML = `<tr><td>&nbsp;</td>${headersHTML}</tr>`;
(<HTMLElement>table.querySelector(`tbody`)).innerHTML = matrixHTML;
}
#getCategoriesFromData() {
const categories = Object.keys(this.data);
categories.sort();
return categories;
}
getData() {
return this.data;
}
}

View File

@ -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 = <HTMLElement>this.#element.querySelector("tbody");
this.#categoryCoalitionHeaders = <HTMLElement>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 += `<tr><td>${category}</td>`;
coalitions.forEach((coalition:string) => {
if (index === 0)
headersHTML += `<th data-coalition="${coalition}">${coalition}</th>`;
matrixHTML += `<td data-coalition="${coalition}"><input type="checkbox" name="category-coalition-selection" value="${category}:${coalition}" ${(data[category].hasOwnProperty(coalition)) ? "checked": "disabled readonly"} /></td>`;
});
matrixHTML += "</tr>";
});
this.#categoryCoalitionHeaders.innerHTML = `<tr><td>&nbsp;</td>${headersHTML}</tr>`;
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(<HTMLInputElement>(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();
}
}

View File

@ -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 = <HTMLElement>this.#element.querySelector("tbody");
this.#categoryCoalitionHeaders = <HTMLElement>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(<HTMLInputElement>(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);
}
}

View File

@ -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();
}

View File

@ -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') %>

View File

@ -1,11 +1,11 @@
<div id="unit-import-export-dialog" class="ol-panel ol-dialog hide" oncontextmenu="return false;">
<div id="<%= dialogId %>" class="ol-panel ol-dialog file-import-export hide" oncontextmenu="return false;">
<div class="ol-dialog-header">
<h3 class="export">Export unit data to file</h3>
<h3><%= title %></h3>
</div>
<div class="ol-dialog-content">
<p>Note: only ground and naval units can be exported at this time. (Air units will be possible <em>soon</em>.)</p>
<table>
<p><%= textContent %></p>
<table class="categories-coalitions">
<thead>
</thead>
<tbody>
@ -14,8 +14,7 @@
</div>
<div class="ol-dialog-footer ol-group">
<button class="start-transfer import">Import units into mission</button>
<button class="start-transfer export">Export units to file</button>
<button data-on-click="closeDialog">Cancel</button>
<button class="start-transfer"><%= submitButtonText %></button>
<button data-on-click="closeDialog">Close</button>
</div>
</div>