mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Split client into frontend website and server
This commit is contained in:
60
frontend/website/src/unit/contextaction.ts
Normal file
60
frontend/website/src/unit/contextaction.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { Unit } from "./unit";
|
||||
|
||||
export interface ContextActionOptions {
|
||||
isScenic?: boolean
|
||||
}
|
||||
|
||||
export class ContextAction {
|
||||
#id: string = "";
|
||||
#label: string = "";
|
||||
#description: string = "";
|
||||
#callback: CallableFunction | null = null;
|
||||
#units: Unit[] = [];
|
||||
#hideContextAfterExecution: boolean = true
|
||||
#options: ContextActionOptions;
|
||||
|
||||
constructor(id: string, label: string, description: string, callback: CallableFunction, hideContextAfterExecution: boolean = true, options: ContextActionOptions) {
|
||||
this.#id = id;
|
||||
this.#label = label;
|
||||
this.#description = description;
|
||||
this.#callback = callback;
|
||||
this.#hideContextAfterExecution = hideContextAfterExecution;
|
||||
this.#options = {
|
||||
"isScenic": false,
|
||||
...options
|
||||
}
|
||||
}
|
||||
|
||||
addUnit(unit: Unit) {
|
||||
this.#units.push(unit);
|
||||
}
|
||||
|
||||
getId() {
|
||||
return this.#id;
|
||||
}
|
||||
|
||||
getLabel() {
|
||||
return this.#label;
|
||||
}
|
||||
|
||||
getOptions() {
|
||||
return this.#options;
|
||||
}
|
||||
|
||||
getDescription() {
|
||||
return this.#description;
|
||||
}
|
||||
|
||||
getCallback() {
|
||||
return this.#callback;
|
||||
}
|
||||
|
||||
executeCallback() {
|
||||
if (this.#callback)
|
||||
this.#callback(this.#units);
|
||||
}
|
||||
|
||||
getHideContextAfterExecution() {
|
||||
return this.#hideContextAfterExecution;
|
||||
}
|
||||
}
|
||||
25
frontend/website/src/unit/contextactionset.ts
Normal file
25
frontend/website/src/unit/contextactionset.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { ContextAction, ContextActionOptions } from "./contextaction";
|
||||
import { Unit } from "./unit";
|
||||
|
||||
export class ContextActionSet {
|
||||
#contextActions: {[key: string]: ContextAction} = {};
|
||||
|
||||
constructor() {
|
||||
|
||||
}
|
||||
|
||||
addContextAction(unit: Unit, id: string, label: string, description: string, callback: CallableFunction, hideContextAfterExecution: boolean = true, options?:ContextActionOptions) {
|
||||
options = options || {};
|
||||
|
||||
if (!(id in this.#contextActions)) {
|
||||
this.#contextActions[id] = new ContextAction(id, label, description, callback, hideContextAfterExecution, options);
|
||||
}
|
||||
this.#contextActions[id].addUnit(unit);
|
||||
}
|
||||
|
||||
getContextActions() {
|
||||
return this.#contextActions;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
37
frontend/website/src/unit/databases/aircraftdatabase.ts
Normal file
37
frontend/website/src/unit/databases/aircraftdatabase.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { getApp } from "../..";
|
||||
import { GAME_MASTER } from "../../constants/constants";
|
||||
import { UnitDatabase } from "./unitdatabase"
|
||||
|
||||
export class AircraftDatabase extends UnitDatabase {
|
||||
constructor() {
|
||||
super('api/databases/units/aircraftdatabase');
|
||||
}
|
||||
|
||||
getCategory() {
|
||||
return "Aircraft";
|
||||
}
|
||||
|
||||
getSpawnPointsByName(name: string) {
|
||||
if (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER || !getApp().getMissionManager().getCommandModeOptions().restrictSpawns)
|
||||
return 0;
|
||||
|
||||
const blueprint = this.getByName(name);
|
||||
if (blueprint?.cost != undefined)
|
||||
return blueprint?.cost;
|
||||
|
||||
if (blueprint?.era == "WW2")
|
||||
return 20;
|
||||
else if (blueprint?.era == "Early Cold War")
|
||||
return 50;
|
||||
else if (blueprint?.era == "Mid Cold War")
|
||||
return 100;
|
||||
else if (blueprint?.era == "Late Cold War")
|
||||
return 200;
|
||||
else if (blueprint?.era == "Modern")
|
||||
return 400;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
export var aircraftDatabase = new AircraftDatabase();
|
||||
|
||||
7137
frontend/website/src/unit/databases/citiesdatabase.ts
Normal file
7137
frontend/website/src/unit/databases/citiesdatabase.ts
Normal file
File diff suppressed because it is too large
Load Diff
36
frontend/website/src/unit/databases/groundunitdatabase.ts
Normal file
36
frontend/website/src/unit/databases/groundunitdatabase.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { getApp } from "../..";
|
||||
import { GAME_MASTER } from "../../constants/constants";
|
||||
import { UnitDatabase } from "./unitdatabase"
|
||||
|
||||
export class GroundUnitDatabase extends UnitDatabase {
|
||||
constructor() {
|
||||
super('api/databases/units/groundunitdatabase');
|
||||
}
|
||||
|
||||
getSpawnPointsByName(name: string) {
|
||||
if (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER || !getApp().getMissionManager().getCommandModeOptions().restrictSpawns)
|
||||
return 0;
|
||||
|
||||
const blueprint = this.getByName(name);
|
||||
if (blueprint?.cost != undefined)
|
||||
return blueprint?.cost;
|
||||
|
||||
if (blueprint?.era == "WW2")
|
||||
return 20;
|
||||
else if (blueprint?.era == "Early Cold War")
|
||||
return 50;
|
||||
else if (blueprint?.era == "Mid Cold War")
|
||||
return 100;
|
||||
else if (blueprint?.era == "Late Cold War")
|
||||
return 200;
|
||||
else if (blueprint?.era == "Modern")
|
||||
return 400;
|
||||
return 0;
|
||||
}
|
||||
|
||||
getCategory() {
|
||||
return "GroundUnit";
|
||||
}
|
||||
}
|
||||
|
||||
export var groundUnitDatabase = new GroundUnitDatabase();
|
||||
37
frontend/website/src/unit/databases/helicopterdatabase.ts
Normal file
37
frontend/website/src/unit/databases/helicopterdatabase.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { getApp } from "../..";
|
||||
import { GAME_MASTER } from "../../constants/constants";
|
||||
import { UnitDatabase } from "./unitdatabase"
|
||||
|
||||
export class HelicopterDatabase extends UnitDatabase {
|
||||
constructor() {
|
||||
super('api/databases/units/helicopterdatabase');
|
||||
}
|
||||
|
||||
getSpawnPointsByName(name: string) {
|
||||
if (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER || !getApp().getMissionManager().getCommandModeOptions().restrictSpawns)
|
||||
return 0;
|
||||
|
||||
const blueprint = this.getByName(name);
|
||||
if (blueprint?.cost != undefined)
|
||||
return blueprint?.cost;
|
||||
|
||||
if (blueprint?.era == "WW2")
|
||||
return 20;
|
||||
else if (blueprint?.era == "Early Cold War")
|
||||
return 50;
|
||||
else if (blueprint?.era == "Mid Cold War")
|
||||
return 100;
|
||||
else if (blueprint?.era == "Late Cold War")
|
||||
return 200;
|
||||
else if (blueprint?.era == "Modern")
|
||||
return 400;
|
||||
return 0;
|
||||
}
|
||||
|
||||
getCategory() {
|
||||
return "Helicopter";
|
||||
}
|
||||
}
|
||||
|
||||
export var helicopterDatabase = new HelicopterDatabase();
|
||||
|
||||
36
frontend/website/src/unit/databases/navyunitdatabase.ts
Normal file
36
frontend/website/src/unit/databases/navyunitdatabase.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { getApp } from "../..";
|
||||
import { GAME_MASTER } from "../../constants/constants";
|
||||
import { UnitDatabase } from "./unitdatabase"
|
||||
|
||||
export class NavyUnitDatabase extends UnitDatabase {
|
||||
constructor() {
|
||||
super('api/databases/units/navyunitdatabase');
|
||||
}
|
||||
|
||||
getSpawnPointsByName(name: string) {
|
||||
if (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER || !getApp().getMissionManager().getCommandModeOptions().restrictSpawns)
|
||||
return 0;
|
||||
|
||||
const blueprint = this.getByName(name);
|
||||
if (blueprint?.cost != undefined)
|
||||
return blueprint?.cost;
|
||||
|
||||
if (blueprint?.era == "WW2")
|
||||
return 20;
|
||||
else if (blueprint?.era == "Early Cold War")
|
||||
return 50;
|
||||
else if (blueprint?.era == "Mid Cold War")
|
||||
return 100;
|
||||
else if (blueprint?.era == "Late Cold War")
|
||||
return 200;
|
||||
else if (blueprint?.era == "Modern")
|
||||
return 400;
|
||||
return 0;
|
||||
}
|
||||
|
||||
getCategory() {
|
||||
return "NavyUnit";
|
||||
}
|
||||
}
|
||||
|
||||
export var navyUnitDatabase = new NavyUnitDatabase();
|
||||
252
frontend/website/src/unit/databases/unitdatabase.ts
Normal file
252
frontend/website/src/unit/databases/unitdatabase.ts
Normal file
@@ -0,0 +1,252 @@
|
||||
import { LatLng } from "leaflet";
|
||||
import { getApp } from "../..";
|
||||
import { GAME_MASTER } from "../../constants/constants";
|
||||
import { UnitBlueprint } from "../../interfaces";
|
||||
|
||||
export abstract class UnitDatabase {
|
||||
blueprints: { [key: string]: UnitBlueprint } = {};
|
||||
#url: string;
|
||||
|
||||
constructor(url: string = "") {
|
||||
this.#url = url;
|
||||
this.load(() => {});
|
||||
}
|
||||
|
||||
load(callback: CallableFunction) {
|
||||
if (this.#url !== "") {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', this.#url, true);
|
||||
xhr.setRequestHeader("Cache-Control", "no-cache, no-store, max-age=0");
|
||||
xhr.responseType = 'json';
|
||||
xhr.onload = () => {
|
||||
var status = xhr.status;
|
||||
if (status === 200) {
|
||||
this.blueprints = xhr.response;
|
||||
callback();
|
||||
} else {
|
||||
console.error(`Error retrieving database from ${this.#url}`)
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
}
|
||||
|
||||
abstract getCategory(): string;
|
||||
|
||||
/* Gets a specific blueprint by name */
|
||||
getByName(name: string) {
|
||||
if (name in this.blueprints)
|
||||
return this.blueprints[name];
|
||||
return null;
|
||||
}
|
||||
|
||||
/* Gets a specific blueprint by label */
|
||||
getByLabel(label: string) {
|
||||
for (let unit in this.blueprints) {
|
||||
if (this.blueprints[unit].label === label)
|
||||
return this.blueprints[unit];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getBlueprints(includeDisabled: boolean = false) {
|
||||
if (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER || !getApp().getMissionManager().getCommandModeOptions().restrictSpawns) {
|
||||
var filteredBlueprints: { [key: string]: UnitBlueprint } = {};
|
||||
for (let unit in this.blueprints) {
|
||||
const blueprint = this.blueprints[unit];
|
||||
if (blueprint.enabled || includeDisabled)
|
||||
filteredBlueprints[unit] = blueprint;
|
||||
}
|
||||
return filteredBlueprints;
|
||||
}
|
||||
else {
|
||||
var filteredBlueprints: { [key: string]: UnitBlueprint } = {};
|
||||
for (let unit in this.blueprints) {
|
||||
const blueprint = this.blueprints[unit];
|
||||
if ((blueprint.enabled || includeDisabled) && this.getSpawnPointsByName(blueprint.name) <= getApp().getMissionManager().getAvailableSpawnPoints() &&
|
||||
getApp().getMissionManager().getCommandModeOptions().eras.includes(blueprint.era) &&
|
||||
(!getApp().getMissionManager().getCommandModeOptions().restrictToCoalition || blueprint.coalition === getApp().getMissionManager().getCommandedCoalition() || blueprint.coalition === undefined)) {
|
||||
filteredBlueprints[unit] = blueprint;
|
||||
}
|
||||
}
|
||||
return filteredBlueprints;
|
||||
}
|
||||
}
|
||||
|
||||
/* Returns a list of all possible roles in a database */
|
||||
getRoles() {
|
||||
var roles: string[] = [];
|
||||
var filteredBlueprints = this.getBlueprints();
|
||||
for (let unit in filteredBlueprints) {
|
||||
var loadouts = filteredBlueprints[unit].loadouts;
|
||||
if (loadouts) {
|
||||
for (let loadout of loadouts) {
|
||||
for (let role of loadout.roles) {
|
||||
if (role !== "" && !roles.includes(role))
|
||||
roles.push(role);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return roles;
|
||||
}
|
||||
|
||||
/* Returns a list of all possible types in a database */
|
||||
getTypes(unitFilter?:CallableFunction) {
|
||||
var filteredBlueprints = this.getBlueprints();
|
||||
var types: string[] = [];
|
||||
for (let unit in filteredBlueprints) {
|
||||
if ( typeof unitFilter === "function" && !unitFilter(filteredBlueprints[unit]))
|
||||
continue;
|
||||
var type = filteredBlueprints[unit].type;
|
||||
if (type && type !== "" && !types.includes(type))
|
||||
types.push(type);
|
||||
}
|
||||
return types;
|
||||
}
|
||||
|
||||
/* Returns a list of all possible periods in a database */
|
||||
getEras() {
|
||||
var filteredBlueprints = this.getBlueprints();
|
||||
var eras: string[] = [];
|
||||
for (let unit in filteredBlueprints) {
|
||||
var era = filteredBlueprints[unit].era;
|
||||
if (era && era !== "" && !eras.includes(era))
|
||||
eras.push(era);
|
||||
}
|
||||
return eras;
|
||||
}
|
||||
|
||||
/* Get all blueprints by range */
|
||||
getByRange(range: string) {
|
||||
var filteredBlueprints = this.getBlueprints();
|
||||
var unitswithrange = [];
|
||||
var minRange = 0;
|
||||
var maxRange = 0;
|
||||
|
||||
if (range === "Short range") {
|
||||
minRange = 0;
|
||||
maxRange = 10000;
|
||||
}
|
||||
else if (range === "Medium range") {
|
||||
minRange = 10000;
|
||||
maxRange = 100000;
|
||||
}
|
||||
else {
|
||||
minRange = 100000;
|
||||
maxRange = 999999;
|
||||
}
|
||||
|
||||
for (let unit in filteredBlueprints) {
|
||||
var engagementRange = filteredBlueprints[unit].engagementRange;
|
||||
if (engagementRange !== undefined) {
|
||||
if (engagementRange >= minRange && engagementRange < maxRange) {
|
||||
unitswithrange.push(filteredBlueprints[unit]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return unitswithrange;
|
||||
}
|
||||
|
||||
/* Get all blueprints by type */
|
||||
getByType(type: string) {
|
||||
var filteredBlueprints = this.getBlueprints();
|
||||
var units = [];
|
||||
for (let unit in filteredBlueprints) {
|
||||
if (filteredBlueprints[unit].type === type) {
|
||||
units.push(filteredBlueprints[unit]);
|
||||
}
|
||||
}
|
||||
return units;
|
||||
}
|
||||
|
||||
/* Get all blueprints by role */
|
||||
getByRole(role: string) {
|
||||
var filteredBlueprints = this.getBlueprints();
|
||||
var units = [];
|
||||
for (let unit in filteredBlueprints) {
|
||||
var loadouts = filteredBlueprints[unit].loadouts;
|
||||
if (loadouts) {
|
||||
for (let loadout of loadouts) {
|
||||
if (loadout.roles.includes(role) || loadout.roles.includes(role.toLowerCase())) {
|
||||
units.push(filteredBlueprints[unit])
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return units;
|
||||
}
|
||||
|
||||
/* Get the names of all the loadouts for a specific unit and for a specific role */
|
||||
getLoadoutNamesByRole(name: string, role: string) {
|
||||
var filteredBlueprints = this.getBlueprints();
|
||||
var loadoutsByRole = [];
|
||||
var loadouts = filteredBlueprints[name].loadouts;
|
||||
if (loadouts) {
|
||||
for (let loadout of loadouts) {
|
||||
if (loadout.roles.includes(role) || loadout.roles.includes("")) {
|
||||
loadoutsByRole.push(loadout.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return loadoutsByRole;
|
||||
}
|
||||
|
||||
/* Get the livery names for a specific unit */
|
||||
getLiveryNamesByName(name: string) {
|
||||
var liveries = this.blueprints[name].liveries;
|
||||
if (liveries !== undefined)
|
||||
return Object.values(liveries);
|
||||
else
|
||||
return [];
|
||||
}
|
||||
|
||||
/* Get the loadout content from the unit name and loadout name */
|
||||
getLoadoutByName(name: string, loadoutName: string) {
|
||||
var loadouts = this.blueprints[name].loadouts;
|
||||
if (loadouts) {
|
||||
for (let loadout of loadouts) {
|
||||
if (loadout.name === loadoutName)
|
||||
return loadout;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
generateTestGrid(initialPosition: LatLng) {
|
||||
var filteredBlueprints = this.getBlueprints();
|
||||
const step = 0.01;
|
||||
var nUnits = Object.values(filteredBlueprints).length;
|
||||
var gridSize = Math.ceil(Math.sqrt(nUnits));
|
||||
Object.values(filteredBlueprints).forEach((unitBlueprint: UnitBlueprint, idx: number) => {
|
||||
var row = Math.floor(idx / gridSize);
|
||||
var col = idx - row * gridSize;
|
||||
var location = new LatLng(initialPosition.lat + col * step, initialPosition.lng + row * step)
|
||||
getApp().getUnitsManager().spawnUnits(this.getCategory(), [{unitType: unitBlueprint.name, location: location, altitude: 1000, loadout: "", liveryID: ""}]);
|
||||
})
|
||||
}
|
||||
|
||||
getSpawnPointsByLabel(label: string) {
|
||||
var blueprint = this.getByLabel(label);
|
||||
if (blueprint)
|
||||
return this.getSpawnPointsByName(blueprint.name);
|
||||
else
|
||||
return Infinity;
|
||||
}
|
||||
|
||||
getSpawnPointsByName(name: string) {
|
||||
return Infinity;
|
||||
}
|
||||
|
||||
getUnkownUnit(name: string): UnitBlueprint {
|
||||
return {
|
||||
name: name,
|
||||
enabled: true,
|
||||
coalition: 'neutral',
|
||||
era: 'N/A',
|
||||
label: name,
|
||||
shortLabel: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
45
frontend/website/src/unit/group.ts
Normal file
45
frontend/website/src/unit/group.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { Unit } from "./unit";
|
||||
|
||||
export class Group {
|
||||
#members: Unit[] = [];
|
||||
#name: string;
|
||||
|
||||
constructor(name: string) {
|
||||
this.#name = name;
|
||||
|
||||
document.addEventListener("unitDeath", (e: any) => {
|
||||
if (this.#members.includes(e.detail))
|
||||
this.getLeader()?.onGroupChanged(e.detail);
|
||||
});
|
||||
}
|
||||
|
||||
getName() {
|
||||
return this.#name;
|
||||
}
|
||||
|
||||
addMember(member: Unit) {
|
||||
if (!this.#members.includes(member)) {
|
||||
this.#members.push(member);
|
||||
member.setGroup(this);
|
||||
|
||||
this.getLeader()?.onGroupChanged(member);
|
||||
}
|
||||
}
|
||||
|
||||
removeMember(member: Unit) {
|
||||
if (this.#members.includes(member)) {
|
||||
delete this.#members[this.#members.indexOf(member)];
|
||||
member.setGroup(null);
|
||||
|
||||
this.getLeader()?.onGroupChanged(member);
|
||||
}
|
||||
}
|
||||
|
||||
getMembers() {
|
||||
return this.#members;
|
||||
}
|
||||
|
||||
getLeader() {
|
||||
return this.#members.find((unit: Unit) => { return (unit.getIsLeader() && unit.getAlive())})
|
||||
}
|
||||
}
|
||||
65
frontend/website/src/unit/importexport/unitdatafile.ts
Normal file
65
frontend/website/src/unit/importexport/unitdatafile.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
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 {
|
||||
|
||||
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>${categoryMap[category as keyof typeof categoryMap]}</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> </td>${headersHTML}</tr>`;
|
||||
(<HTMLElement>table.querySelector(`tbody`)).innerHTML = matrixHTML;
|
||||
|
||||
}
|
||||
|
||||
#getCategoriesFromData() {
|
||||
const categories = Object.keys(this.data);
|
||||
categories.sort();
|
||||
return categories;
|
||||
}
|
||||
|
||||
getData() {
|
||||
return this.data;
|
||||
}
|
||||
}
|
||||
101
frontend/website/src/unit/importexport/unitdatafileexport.ts
Normal file
101
frontend/website/src/unit/importexport/unitdatafileexport.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
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 {
|
||||
|
||||
protected data!: any;
|
||||
protected dialog: Dialog;
|
||||
#element!: HTMLElement;
|
||||
#filename: string = "export.json";
|
||||
|
||||
constructor(elementId: string) {
|
||||
super();
|
||||
this.dialog = new Dialog(elementId);
|
||||
this.#element = this.dialog.getElement();
|
||||
|
||||
this.#element.querySelector(".start-transfer")?.addEventListener("click", (ev: MouseEventInit) => {
|
||||
this.#doExport();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form to start the export journey
|
||||
*/
|
||||
showForm(units: Unit[]) {
|
||||
this.dialog.getElement().querySelectorAll("[data-on-error]").forEach((el:Element) => {
|
||||
el.classList.toggle("hide", el.getAttribute("data-on-error") === "show");
|
||||
});
|
||||
|
||||
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();
|
||||
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();
|
||||
|
||||
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(<HTMLInputElement>(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 a = document.createElement("a");
|
||||
const file = new Blob([JSON.stringify(unitsToExport)], { type: 'text/plain' });
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
150
frontend/website/src/unit/importexport/unitdatafileimport.ts
Normal file
150
frontend/website/src/unit/importexport/unitdatafileimport.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import { getApp } from "../..";
|
||||
import { Dialog } from "../../dialog/dialog";
|
||||
import { UnitData } from "../../interfaces";
|
||||
import { ImportFileJSONSchemaValidator } from "../../schemas/schema";
|
||||
import { UnitDataFile } from "./unitdatafile";
|
||||
|
||||
export class UnitDataFileImport extends UnitDataFile {
|
||||
|
||||
protected data!: any;
|
||||
protected dialog: Dialog;
|
||||
#fileData!: { [key: string]: UnitData[] };
|
||||
|
||||
constructor(elementId: string) {
|
||||
super();
|
||||
this.dialog = new Dialog(elementId);
|
||||
this.dialog.getElement().querySelector(".start-transfer")?.addEventListener("click", (ev: MouseEventInit) => {
|
||||
this.#doImport();
|
||||
this.dialog.hide();
|
||||
});
|
||||
}
|
||||
|
||||
#doImport() {
|
||||
|
||||
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, false);
|
||||
}
|
||||
}
|
||||
|
||||
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 = (e: any) => {
|
||||
|
||||
try {
|
||||
this.#fileData = JSON.parse(e.target.result);
|
||||
|
||||
const validator = new ImportFileJSONSchemaValidator();
|
||||
if (!validator.validate(this.#fileData)) {
|
||||
const errors = validator.getErrors().reduce((acc:any, error:any) => {
|
||||
let errorString = error.instancePath.substring(1) + ": " + error.message;
|
||||
if (error.params) {
|
||||
const {allowedValues} = error.params;
|
||||
if (allowedValues)
|
||||
errorString += ": " + allowedValues.join(', ');
|
||||
}
|
||||
acc.push(errorString);
|
||||
return acc;
|
||||
}, [] as string[]);
|
||||
this.#showFileDataErrors(errors);
|
||||
} else {
|
||||
this.#showForm();
|
||||
}
|
||||
} catch(e:any) {
|
||||
this.#showFileDataErrors([e]);
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
})
|
||||
input.click();
|
||||
}
|
||||
|
||||
#showFileDataErrors( reasons:string[]) {
|
||||
|
||||
this.dialog.getElement().querySelectorAll("[data-on-error]").forEach((el:Element) => {
|
||||
el.classList.toggle("hide", el.getAttribute("data-on-error") === "hide");
|
||||
});
|
||||
|
||||
const reasonsList = this.dialog.getElement().querySelector(".import-error-reasons");
|
||||
if (reasonsList instanceof HTMLElement)
|
||||
reasonsList.innerHTML = `<li>${reasons.join("</li><li>")}</li>`;
|
||||
|
||||
this.dialog.show();
|
||||
}
|
||||
|
||||
#showForm() {
|
||||
this.dialog.getElement().querySelectorAll("[data-on-error]").forEach((el:Element) => {
|
||||
el.classList.toggle("hide", el.getAttribute("data-on-error") === "show");
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
this.data = data;
|
||||
this.buildCategoryCoalitionTable();
|
||||
this.dialog.show();
|
||||
}
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
}
|
||||
1729
frontend/website/src/unit/unit.ts
Normal file
1729
frontend/website/src/unit/unit.ts
Normal file
File diff suppressed because it is too large
Load Diff
1544
frontend/website/src/unit/unitsmanager.ts
Normal file
1544
frontend/website/src/unit/unitsmanager.ts
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user