Database editor (almost) completed

Liveries editor still to add, but no one in his right mind would change them manually since we have scripts for that
This commit is contained in:
Pax1601 2023-09-29 15:24:08 +02:00
parent 94901849e6
commit 968ff61979
32 changed files with 86698 additions and 8547 deletions

1
.gitignore vendored
View File

@ -13,3 +13,4 @@ node_modules
/client/public/plugins
/client/plugins/controltips/index.js
hgt
/client/public/databases/units/old

View File

@ -604,10 +604,12 @@ declare module "unit/databases/unitdatabase" {
import { LatLng } from "leaflet";
import { UnitBlueprint } from "interfaces";
export class UnitDatabase {
#private;
blueprints: {
[key: string]: UnitBlueprint;
};
constructor(url?: string);
load(callback: CallableFunction): void;
getCategory(): string;
getByName(name: string): UnitBlueprint | null;
getByLabel(label: string): UnitBlueprint | null;
@ -1305,6 +1307,9 @@ declare module "popups/popup" {
setText(text: string): void;
}
}
declare module "map/touchboxselect" {
export var TouchBoxSelect: (new (...args: any[]) => any) & typeof import("leaflet").Class;
}
declare module "map/map" {
import * as L from "leaflet";
import { MapContextMenu } from "contextmenus/mapcontextmenu";

View File

@ -8,12 +8,12 @@ var bodyParser = require('body-parser');
var atcRouter = require('./routes/api/atc');
var airbasesRouter = require('./routes/api/airbases');
var elevationRouter = require('./routes/api/elevation');
var databasesRouter = require('./routes/api/databases');
var indexRouter = require('./routes/index');
var uikitRouter = require('./routes/uikit');
var usersRouter = require('./routes/users');
var resourcesRouter = require('./routes/resources');
var pluginsRouter = require('./routes/plugins');
var databasesRouter = require('./routes/databases');
var app = express();

View File

@ -7,7 +7,7 @@
"scripts": {
"emit-declarations": "tsc --project tsconfig.json --declaration --emitDeclarationOnly --outfile ./@types/olympus/index.d.ts",
"copy": "copy.bat",
"start": "npm run copy & concurrently --kill-others \"npm run watch\" \"nodemon ./bin/www\"",
"start": "npm run copy & concurrently --kill-others \"npm run watch\" \"nodemon --ignore ./public/databases/ ./bin/www\"",
"watch": "watchify .\\src\\index.ts --debug -o .\\public\\javascripts\\bundle.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js'] -p [ tsify --noImplicitAny ]"
},
"dependencies": {

View File

@ -3,53 +3,66 @@ import { UnitEditor } from "./uniteditor";
import { LoadoutEditor } from "./loadouteditor";
import { addDropdownInput, addLoadoutsScroll, addNewElementInput, addStringInput } from "./utils";
/** Database editor for Air Units, both Aircraft and Helicopter since they are identical in terms of datbase entries.
*
*/
export class AirUnitEditor extends UnitEditor {
#blueprint: UnitBlueprint | null = null;
#loadoutEditor: LoadoutEditor | null = null;
constructor(contentDiv1: HTMLElement, contentDiv2: HTMLElement, contentDiv3: HTMLElement) {
super(contentDiv1, contentDiv2, contentDiv3);
/* The loadout editor allows to edit the loadout (who could have thought eh?) */
this.#loadoutEditor = new LoadoutEditor(this.contentDiv3);
this.contentDiv2.addEventListener("refresh", () => {
if (this.visible) {
if (this.#blueprint !== null)
this.setBlueprint(this.#blueprint);
}
});
this.contentDiv3.addEventListener("refresh", () => {
if (this.visible) {
console.log("refresh")
if (this.#blueprint !== null)
this.setBlueprint(this.#blueprint);
/* Refresh the loadout editor if needed */
this.contentDiv3.addEventListener("refresh", () => {
if (this.visible)
this.#loadoutEditor?.show();
}
});
}
/** Sets a unit blueprint as the currently active one
*
* @param blueprint The blueprint to edit
*/
setBlueprint(blueprint: UnitBlueprint) {
this.#blueprint = blueprint;
this.blueprint = blueprint;
if (this.#blueprint !== null) {
if (this.blueprint !== null) {
this.contentDiv2.replaceChildren();
addStringInput(this.contentDiv2, "Name", blueprint.name, "text", (value: string) => {blueprint.name = value; }, true);
addStringInput(this.contentDiv2, "Label", blueprint.label, "text", (value: string) => {blueprint.label = value; });
addStringInput(this.contentDiv2, "Short label", blueprint.shortLabel, "text", (value: string) => {blueprint.shortLabel = value; });
var title = document.createElement("label");
title.innerText = "Unit properties";
this.contentDiv2.appendChild(title);
addStringInput(this.contentDiv2, "Name", blueprint.name, "text", (value: string) => { blueprint.name = value; }, true);
addStringInput(this.contentDiv2, "Label", blueprint.label, "text", (value: string) => { blueprint.label = value; });
addStringInput(this.contentDiv2, "Short label", blueprint.shortLabel, "text", (value: string) => { blueprint.shortLabel = value; });
addDropdownInput(this.contentDiv2, "Coalition", blueprint.coalition, ["", "blue", "red"],);
addDropdownInput(this.contentDiv2, "Era", blueprint.era, ["WW2", "Early Cold War", "Mid Cold War", "Late Cold War", "Modern"]);
addStringInput(this.contentDiv2, "Filename", blueprint.filename?? "", "text", (value: string) => {blueprint.filename = value; });
addStringInput(this.contentDiv2, "Cost", String(blueprint.cost)?? "", "number", (value: string) => {blueprint.cost = parseFloat(value); });
addLoadoutsScroll(this.contentDiv2, blueprint.loadouts?? [], (loadout: LoadoutBlueprint) => {
addStringInput(this.contentDiv2, "Filename", blueprint.filename ?? "", "text", (value: string) => { blueprint.filename = value; });
addStringInput(this.contentDiv2, "Cost", String(blueprint.cost) ?? "", "number", (value: string) => { blueprint.cost = parseFloat(value); });
/* Add a scrollable list of loadouts that the user can edit */
var title = document.createElement("label");
title.innerText = "Loadouts";
this.contentDiv2.appendChild(title);
addLoadoutsScroll(this.contentDiv2, blueprint.loadouts ?? [], (loadout: LoadoutBlueprint) => {
this.#loadoutEditor?.setLoadout(loadout);
this.#loadoutEditor?.show();
});
addNewElementInput(this.contentDiv2, (ev: MouseEvent, input: HTMLInputElement) => { this.addLoadout(input.value); });
this.#loadoutEditor?.hide();
}
}
/** Add a new empty blueprint
*
* @param key Blueprint key
*/
addBlueprint(key: string) {
if (this.database != null) {
this.database.blueprints[key] = {
@ -65,19 +78,26 @@ export class AirUnitEditor extends UnitEditor {
}
}
/** Add a new empty loadout to the currently active blueprint
*
* @param loadoutName The name of the new loadout
*/
addLoadout(loadoutName: string) {
if (loadoutName && this.#blueprint !== null) {
this.#blueprint.loadouts?.push({
if (loadoutName && this.blueprint !== null) {
this.blueprint.loadouts?.push({
name: loadoutName,
code: "",
fuel: 1,
items: [],
roles: []
})
this.setBlueprint(this.#blueprint);
}
this.setBlueprint(this.blueprint);
}
}
/** Hide the editor
*
*/
hide() {
super.hide();
this.#loadoutEditor?.hide();

View File

@ -5,143 +5,166 @@ import { GroundUnitEditor } from "./grounduniteditor";
import { PrimaryToolbar } from "toolbars/primarytoolbar";
import { NavyUnitEditor } from "./navyuniteditor";
/** Database Manager
*
* This database provides a user interface to allow easier and convenient unit databases manipulation. It allows to edit all the fields of the units databases, save them
* on the server, and restore the defaults.
*
* TODO:
* Add ability to manage liveries
*
*/
export class DatabaseManagerPlugin implements OlympusPlugin {
#app: OlympusApp | null = null;
#element: HTMLElement;
#container: HTMLElement;
#mainContentContainer: HTMLElement;
#contentDiv1: HTMLElement;
#contentDiv2: HTMLElement;
#contentDiv3: HTMLElement;
/* Upper tab buttons */
#button1: HTMLButtonElement;
#button2: HTMLButtonElement;
#button3: HTMLButtonElement;
#button4: HTMLButtonElement;
/* Lower operation buttons */
#button5: HTMLButtonElement;
#button6: HTMLButtonElement;
#button7: HTMLButtonElement;
#button8: HTMLButtonElement;
#button9: HTMLButtonElement;
/* Database editors */
#aircraftEditor: AirUnitEditor;
#helicopterEditor: AirUnitEditor;
#groundUnitEditor: GroundUnitEditor;
#navyUnitEditor: NavyUnitEditor;
constructor() {
/* Create main HTML element */
this.#element = document.createElement("div");
this.#element.id = "database-manager-panel";
this.#element.oncontextmenu = () => { return false; }
this.#element.classList.add("ol-dialog");
document.body.appendChild(this.#element);
/* Start hidden */
this.toggle(false);
/* Create the top tab buttons container and buttons */
let topButtonContainer = document.createElement("div");
this.#button1 = document.createElement("button");
this.#button1.classList.add("tab-button");
this.#button1.textContent = "Aircraft database";
this.#button1.onclick = () => { this.hideAll(); this.#aircraftEditor.show(); this.#button1.classList.add("selected"); };
this.#button1.onclick = () => { this.#hideAll(); this.#aircraftEditor.show(); this.#button1.classList.add("selected"); };
topButtonContainer.appendChild(this.#button1);
this.#button2 = document.createElement("button");
this.#button2.classList.add("tab-button");
this.#button2.textContent = "Helicopter database";
this.#button2.onclick = () => { this.hideAll(); this.#helicopterEditor.show(); this.#button2.classList.add("selected"); };
this.#button2.onclick = () => { this.#hideAll(); this.#helicopterEditor.show(); this.#button2.classList.add("selected"); };
topButtonContainer.appendChild(this.#button2);
this.#button3 = document.createElement("button");
this.#button3.classList.add("tab-button");
this.#button3.textContent = "Ground Unit database";
this.#button3.onclick = () => { this.hideAll(); this.#groundUnitEditor.show(); this.#button3.classList.add("selected"); };
this.#button3.onclick = () => { this.#hideAll(); this.#groundUnitEditor.show(); this.#button3.classList.add("selected"); };
topButtonContainer.appendChild(this.#button3);
this.#button4 = document.createElement("button");
this.#button4.classList.add("tab-button");
this.#button4.textContent = "Navy Unit database";
this.#button4.onclick = () => { this.hideAll(); this.#navyUnitEditor.show(); this.#button4.classList.add("selected"); };
this.#button4.onclick = () => { this.#hideAll(); this.#navyUnitEditor.show(); this.#button4.classList.add("selected"); };
topButtonContainer.appendChild(this.#button4);
this.#element.appendChild(topButtonContainer);
this.#container = document.createElement("div");
this.#container.classList.add("dm-container");
this.#element.appendChild(this.#container)
/* Create the container for the database editor elements and the elements themselves */
this.#mainContentContainer = document.createElement("div");
this.#mainContentContainer.classList.add("dm-container");
this.#element.appendChild(this.#mainContentContainer)
this.#contentDiv1 = document.createElement("div");
this.#contentDiv1.classList.add("dm-content-container");
this.#container.appendChild(this.#contentDiv1);
this.#mainContentContainer.appendChild(this.#contentDiv1);
this.#contentDiv2 = document.createElement("div");
this.#contentDiv2.classList.add("dm-content-container");
this.#container.appendChild(this.#contentDiv2);
this.#mainContentContainer.appendChild(this.#contentDiv2);
this.#contentDiv3 = document.createElement("div");
this.#contentDiv3.classList.add("dm-content-container");
this.#container.appendChild(this.#contentDiv3);
this.#mainContentContainer.appendChild(this.#contentDiv3);
/* Create the database editors, which use the three divs created before */
this.#aircraftEditor = new AirUnitEditor(this.#contentDiv1, this.#contentDiv2, this.#contentDiv3);
this.#helicopterEditor = new AirUnitEditor(this.#contentDiv1, this.#contentDiv2, this.#contentDiv3);
this.#groundUnitEditor = new GroundUnitEditor(this.#contentDiv1, this.#contentDiv2, this.#contentDiv3);
this.#navyUnitEditor = new NavyUnitEditor(this.#contentDiv1, this.#contentDiv2, this.#contentDiv3);
/* Create the bottom buttons container. These buttons allow to save, restore, reset, and discard the changes */
let bottomButtonContainer = document.createElement("div");
this.#button5 = document.createElement("button");
this.#button5.textContent = "Save";
this.#button5.title = "Save the changes on the server"
this.#button5.onclick = () => {
var aircraftDatabase = this.#aircraftEditor.getDatabase();
if (aircraftDatabase)
this.uploadDatabase(aircraftDatabase, "aircraftdatabase");
var helicopterDatabase = this.#helicopterEditor.getDatabase();
if (helicopterDatabase)
this.uploadDatabase(helicopterDatabase, "helicopterDatabase");
var groundUnitDatabase = this.#groundUnitEditor.getDatabase();
if (groundUnitDatabase)
this.uploadDatabase(groundUnitDatabase, "groundUnitDatabase");
var navyUnitDatabase = this.#navyUnitEditor.getDatabase();
if (navyUnitDatabase)
this.uploadDatabase(navyUnitDatabase, "navyUnitDatabase");
};
this.#button5.onclick = () => { this.#saveDatabases();};
bottomButtonContainer.appendChild(this.#button5);
this.#button6 = document.createElement("button");
this.#button6.textContent = "Discard";
this.#button6.title = "Discard all changes and reload the database from the server"
this.#button6.onclick = () => { this.loadDatabases(); };
this.#button6.title = "Discard all changes and reload the database from the server";
this.#button6.onclick = () => { this.#loadDatabases(); };
bottomButtonContainer.appendChild(this.#button6);
this.#button7 = document.createElement("button");
this.#button7.textContent = "Reload";
this.#button7.onclick = () => { };
//bottomButtonContainer.appendChild(this.#button7);
this.#button7.textContent = "Reset defaults";
this.#button7.onclick = () => { this.#resetToDefaultDatabases(); };
this.#button7.title = "Reset the databases to the default values";
bottomButtonContainer.appendChild(this.#button7);
this.#button8 = document.createElement("button");
this.#button8.textContent = "Close";
this.#button8.title = "Close the Database Manager"
this.#button8.onclick = () => { this.toggle(false); };
this.#button8.textContent = "Restore previous";
this.#button8.onclick = () => { this.#restoreToPreviousDatabases(); };
this.#button8.title = "Restore the previously saved databases. Use this if you saved a database by mistake.";
bottomButtonContainer.appendChild(this.#button8);
this.#button9 = document.createElement("button");
this.#button9.textContent = "Close";
this.#button9.title = "Close the Database Manager"
this.#button9.onclick = () => { this.toggle(false); };
bottomButtonContainer.appendChild(this.#button9);
this.#element.appendChild(bottomButtonContainer);
}
/**
*
* @returns The name of the plugin
*/
getName() {
return "Database Control Plugin"
}
/** Initialize the plugin
*
* @param app The OlympusApp singleton
* @returns True if successfull
*/
initialize(app: any) {
this.#app = app;
this.loadDatabases();
/* Load the databases and initialize the editors */
this.#loadDatabases();
/* Add a button to the main Olympus App to allow the users to open the dialog */
var mainButtonDiv = document.createElement("div");
var mainButton = document.createElement("button");
mainButton.textContent = "Database Manager";
mainButton.textContent = "Database manager";
mainButtonDiv.appendChild(mainButton);
var toolbar: PrimaryToolbar = this.#app?.getToolbarsManager().get("primaryToolbar") as PrimaryToolbar;
var elements = toolbar.getMainDropdown().getOptionElements();
@ -149,14 +172,51 @@ export class DatabaseManagerPlugin implements OlympusPlugin {
arr.splice(arr.length - 1, 0, mainButtonDiv);
toolbar.getMainDropdown().setOptionsElements(arr);
mainButton.onclick = () => {
toolbar.getMainDropdown().close();
this.toggle();
toolbar.getMainDropdown().close();
this.toggle();
}
return true;
}
loadDatabases() {
/**
*
* @returns The main container element
*/
getElement() {
return this.#element;
}
/** Toggles the visibility of the dialog
*
* @param bool Force a specific visibility state
*/
toggle(bool?: boolean) {
if (bool)
this.getElement().classList.toggle("hide", !bool);
else
this.getElement().classList.toggle("hide");
}
/** Hide all the editors
*
*/
#hideAll() {
this.#aircraftEditor.hide();
this.#helicopterEditor.hide();
this.#groundUnitEditor.hide();
this.#navyUnitEditor.hide();
this.#button1.classList.remove("selected");
this.#button2.classList.remove("selected");
this.#button3.classList.remove("selected");
this.#button4.classList.remove("selected");
}
/** Load the databases from the app to the editor. Note, this does not reload the databases from the server to the app
*
*/
#loadDatabases() {
var aircraftDatabase = this.#app?.getAircraftDatabase();
if (aircraftDatabase != null)
this.#aircraftEditor.setDatabase(aircraftDatabase);
@ -173,44 +233,167 @@ export class DatabaseManagerPlugin implements OlympusPlugin {
if (navyUnitDatabase != null)
this.#navyUnitEditor.setDatabase(navyUnitDatabase);
this.hideAll();
this.#hideAll();
this.#aircraftEditor.show();
this.#button1.classList.add("selected");
}
getElement() {
return this.#element;
/** Save the databases on the server and reloads it to apply the changes
*
*/
#saveDatabases() {
var aircraftDatabase = this.#aircraftEditor.getDatabase();
if (aircraftDatabase){
this.#uploadDatabase(aircraftDatabase, "aircraftdatabase", "Aircraft database", () => {
var helicopterDatabase = this.#helicopterEditor.getDatabase();
if (helicopterDatabase) {
this.#uploadDatabase(helicopterDatabase, "helicopterDatabase", "Helicopter database", () => {
var groundUnitDatabase = this.#groundUnitEditor.getDatabase();
if (groundUnitDatabase) {
this.#uploadDatabase(groundUnitDatabase, "groundUnitDatabase", "Ground Unit database", () => {
var navyUnitDatabase = this.#navyUnitEditor.getDatabase();
if (navyUnitDatabase) {
this.#uploadDatabase(navyUnitDatabase, "navyUnitDatabase", "Navy Unit database", () => {
this.#app?.getAircraftDatabase().load(() => {});
this.#app?.getHelicopterDatabase().load(() => {});
this.#app?.getGroundUnitDatabase().load(() => {});
this.#app?.getNavyUnitDatabase().load(() => {});
});
}
});
}
});
}
});
}
}
toggle(bool?: boolean) {
if (bool)
this.getElement().classList.toggle("hide", !bool);
else
this.getElement().classList.toggle("hide");
/** Resets the databases to the default values
*
*/
#resetToDefaultDatabases() {
this.#resetToDefaultDatabase("aircraftdatabase", "Aircraft database", () => {
this.#app?.getAircraftDatabase().load(() => {
this.#resetToDefaultDatabase("helicopterdatabase", "Helicopter database", () => {
this.#app?.getHelicopterDatabase().load(() => {
this.#resetToDefaultDatabase("groundunitdatabase", "Ground Unit database", () => {
this.#app?.getGroundUnitDatabase().load(() => {
this.#resetToDefaultDatabase("navyunitdatabase", "Navy Unit database", () => {
this.#app?.getNavyUnitDatabase().load(() => {
this.#loadDatabases();
this.#hideAll();
this.#aircraftEditor.show();
this.#button1.classList.add("selected");
});
});
});
});
});
});
});
});
}
hideAll() {
this.#aircraftEditor.hide();
this.#helicopterEditor.hide();
this.#groundUnitEditor.hide();
/** Restores the databases to the previous saved values. This is useful if you saved the databases by mistake and want to undo the error.
*
*/
#restoreToPreviousDatabases() {
this.#restoreToPreviousDatabase("aircraftdatabase", "Aircraft database", () => {
this.#app?.getAircraftDatabase().load(() => {
this.#restoreToPreviousDatabase("helicopterdatabase", "Helicopter database", () => {
this.#app?.getHelicopterDatabase().load(() => {
this.#restoreToPreviousDatabase("groundunitdatabase", "Ground Unit database", () => {
this.#app?.getGroundUnitDatabase().load(() => {
this.#restoreToPreviousDatabase("navyunitdatabase", "Navy Unit database", () => {
this.#app?.getNavyUnitDatabase().load(() => {
this.#loadDatabases();
this.#button1.classList.remove("selected");
this.#button2.classList.remove("selected");
this.#button3.classList.remove("selected");
this.#button4.classList.remove("selected");
this.#hideAll();
this.#aircraftEditor.show();
this.#button1.classList.add("selected");
});
});
});
});
});
});
});
});
}
uploadDatabase(database: {blueprints: {[key: string]: UnitBlueprint}}, databaseName: string) {
/** Upload a single database to the server
*
* @param database The database
* @param name The name of the database as it will be saved on the server
* @param label A label used in the info popup
*/
#uploadDatabase(database: { blueprints: { [key: string]: UnitBlueprint } }, name: string, label: string, callback: CallableFunction) {
var xmlHttp = new XMLHttpRequest();
xmlHttp.open("PUT", "/api/databases/units/" + databaseName);
xmlHttp.open("PUT", "/api/databases/save/units/" + name);
xmlHttp.setRequestHeader("Content-Type", "application/json");
xmlHttp.onload = (res: any) => {
this.#app?.getPopupsManager().get("infoPopup")?.setText(databaseName + " saved successfully");
if (xmlHttp.status == 200) {
this.#app?.getPopupsManager().get("infoPopup")?.setText(label + " saved successfully");
callback();
}
else {
this.#app?.getPopupsManager().get("infoPopup")?.setText("An error has occurred while saving the " + label);
}
};
xmlHttp.onerror = (res: any) => {
this.#app?.getPopupsManager().get("infoPopup")?.setText("An error has occurring saving the database: " + databaseName)
console.log(res);
this.#app?.getPopupsManager().get("infoPopup")?.setText("An error has occurred while saving the " + label);
}
xmlHttp.send(JSON.stringify(database));
}
/** Resets a database to its default values on the server. NOTE: this only resets the database on the server, it will not reload it in the app.
*
* @param name The name of the database as it is saved on the server
* @param label A label used in the info popup
* @param callback Called when the operation is completed
*/
#resetToDefaultDatabase(name: string, label: string, callback: CallableFunction) {
var xmlHttp = new XMLHttpRequest();
xmlHttp.open("PUT", "/api/databases/reset/units/" + name);
xmlHttp.setRequestHeader("Content-Type", "application/json");
xmlHttp.onload = (res: any) => {
if (xmlHttp.status == 200) {
this.#app?.getPopupsManager().get("infoPopup")?.setText(label + " reset successfully");
callback();
}
else {
this.#app?.getPopupsManager().get("infoPopup")?.setText("An error has occurred while resetting the " + label);
}
};
xmlHttp.onerror = (res: any) => {
this.#app?.getPopupsManager().get("infoPopup")?.setText("An error has occurred while resetting the " + label)
}
xmlHttp.send("");
}
/** Restores a database to its previously saved values on the server. NOTE: this only restores the database on the server, it will not reload it in the app.
*
* @param name The name of the database as it is saved on the server
* @param label A label used in the info popup
* @param callback Called when the operation is completed
*/
#restoreToPreviousDatabase(name: string, label: string, callback: CallableFunction) {
var xmlHttp = new XMLHttpRequest();
xmlHttp.open("PUT", "/api/databases/restore/units/" + name);
xmlHttp.setRequestHeader("Content-Type", "application/json");
xmlHttp.onload = (res: any) => {
if (xmlHttp.status == 200) {
this.#app?.getPopupsManager().get("infoPopup")?.setText(label + " restored successfully");
callback();
}
else {
this.#app?.getPopupsManager().get("infoPopup")?.setText("An error has occurred while restoring the " + label);
}
};
xmlHttp.onerror = (res: any) => {
this.#app?.getPopupsManager().get("infoPopup")?.setText("An error has occurred while restoring the " + label)
}
xmlHttp.send("");
}
}

View File

@ -2,31 +2,29 @@ import { UnitBlueprint } from "interfaces";
import { UnitEditor } from "./uniteditor";
import { addDropdownInput, addStringInput } from "./utils";
/** Database editor for ground units
*
*/
export class GroundUnitEditor extends UnitEditor {
#blueprint: UnitBlueprint | null = null;
constructor(contentDiv1: HTMLElement, contentDiv2: HTMLElement, contentDiv3: HTMLElement) {
super(contentDiv1, contentDiv2, contentDiv3);
this.contentDiv2.addEventListener("refresh", () => {
if (this.visible) {
if (this.#blueprint !== null)
this.setBlueprint(this.#blueprint);
}
});
this.contentDiv3.addEventListener("refresh", () => {
if (this.visible) {
if (this.#blueprint !== null)
this.setBlueprint(this.#blueprint);
}
});
}
/** Sets a unit blueprint as the currently active one
*
* @param blueprint The blueprint to edit
*/
setBlueprint(blueprint: UnitBlueprint) {
this.#blueprint = blueprint;
if (this.#blueprint !== null) {
this.contentDiv2.replaceChildren();
var title = document.createElement("label");
title.innerText = "Unit properties";
this.contentDiv2.appendChild(title);
addStringInput(this.contentDiv2, "Name", blueprint.name, "text", (value: string) => {blueprint.name = value; }, true);
addStringInput(this.contentDiv2, "Label", blueprint.label, "text", (value: string) => {blueprint.label = value; });
@ -41,6 +39,10 @@ export class GroundUnitEditor extends UnitEditor {
}
}
/** Add a new empty blueprint
*
* @param key Blueprint key
*/
addBlueprint(key: string) {
if (this.database != null) {
this.database.blueprints[key] = {

View File

@ -1,13 +1,15 @@
import { LoadoutBlueprint, LoadoutItemBlueprint } from "interfaces";
import { LoadoutBlueprint } from "interfaces";
import { addLoadoutItemsEditor, addStringInput } from "./utils";
/** The LoadoutEditor allows the user to edit a loadout
*
*/
export class LoadoutEditor {
#contentDiv: HTMLElement;
#loadout: LoadoutBlueprint | null = null;
#visible: boolean = false;
constructor(contentDiv: HTMLElement) {
this.#contentDiv = contentDiv;
this.#contentDiv.addEventListener("refresh", () => {
if (this.#visible)
@ -15,14 +17,25 @@ export class LoadoutEditor {
})
}
/** Set the loadout to edit
*
* @param loadout The loadout to edit
*/
setLoadout(loadout: LoadoutBlueprint) {
this.#loadout = loadout;
}
/** Show the editor
*
*/
show() {
this.#visible = true;
this.#contentDiv.replaceChildren();
var title = document.createElement("label");
title.innerText = "Loadout properties";
this.#contentDiv.appendChild(title);
if (this.#loadout) {
var laodout = this.#loadout;
addStringInput(this.#contentDiv, "Name", laodout.name, "text", (value: string) => {laodout.name = value; this.#contentDiv.dispatchEvent(new Event("refresh"));});
@ -31,6 +44,9 @@ export class LoadoutEditor {
}
}
/** Hide the editor
*
*/
hide() {
this.#visible = false;
this.#contentDiv.replaceChildren();

View File

@ -2,31 +2,29 @@ import { UnitBlueprint } from "interfaces";
import { UnitEditor } from "./uniteditor";
import { addDropdownInput, addStringInput } from "./utils";
/** Database editor for navy units
*
*/
export class NavyUnitEditor extends UnitEditor {
#blueprint: UnitBlueprint | null = null;
constructor(contentDiv1: HTMLElement, contentDiv2: HTMLElement, contentDiv3: HTMLElement) {
super(contentDiv1, contentDiv2, contentDiv3);
this.contentDiv2.addEventListener("refresh", () => {
if (this.visible) {
if (this.#blueprint !== null)
this.setBlueprint(this.#blueprint);
}
});
this.contentDiv3.addEventListener("refresh", () => {
if (this.visible) {
if (this.#blueprint !== null)
this.setBlueprint(this.#blueprint);
}
});
}
/** Sets a unit blueprint as the currently active one
*
* @param blueprint The blueprint to edit
*/
setBlueprint(blueprint: UnitBlueprint) {
this.#blueprint = blueprint;
if (this.#blueprint !== null) {
this.contentDiv2.replaceChildren();
var title = document.createElement("label");
title.innerText = "Unit properties";
this.contentDiv2.appendChild(title);
addStringInput(this.contentDiv2, "Name", blueprint.name, "text", (value: string) => {blueprint.name = value; }, true);
addStringInput(this.contentDiv2, "Label", blueprint.label, "text", (value: string) => {blueprint.label = value; });
@ -36,9 +34,15 @@ export class NavyUnitEditor extends UnitEditor {
addDropdownInput(this.contentDiv2, "Era", blueprint.era, ["WW2", "Early Cold War", "Mid Cold War", "Late Cold War", "Modern"]);
//addStringInput(this.contentDiv2, "Filename", blueprint.filename?? "", "text", (value: string) => {blueprint.filename = value; });
addStringInput(this.contentDiv2, "Cost", String(blueprint.cost)?? "", "number", (value: string) => {blueprint.cost = parseFloat(value); });
addStringInput(this.contentDiv2, "Barrel height [m]", String(blueprint.barrelHeight)?? "", "number", (value: string) => {blueprint.barrelHeight = parseFloat(value); });
addStringInput(this.contentDiv2, "Muzzle velocity [m/s]", String(blueprint.muzzleVelocity)?? "", "number", (value: string) => {blueprint.muzzleVelocity = parseFloat(value); });
}
}
/** Add a new empty blueprint
*
* @param key Blueprint key
*/
addBlueprint(key: string) {
if (this.database != null) {
this.database.blueprints[key] = {

View File

@ -1,8 +1,12 @@
import { LoadoutBlueprint, UnitBlueprint } from "interfaces";
import { UnitBlueprint } from "interfaces";
import { UnitDatabase } from "unit/databases/unitdatabase";
import { addBlueprintsScroll, addNewElementInput } from "./utils";
/** Base abstract class of Unit database editors
*
*/
export abstract class UnitEditor {
blueprint: UnitBlueprint | null = null;
database: {blueprints: {[key: string]: UnitBlueprint}} | null = null;
visible: boolean = false;
contentDiv1: HTMLElement;
@ -13,27 +17,57 @@ export abstract class UnitEditor {
this.contentDiv1 = contentDiv1;
this.contentDiv2 = contentDiv2;
this.contentDiv3 = contentDiv3;
/* Refresh the list of units if it changes */
this.contentDiv1.addEventListener("refresh", () => {
if (this.visible)
this.show();
})
/* If the unit properties or loadout are edited, reload the editor */
this.contentDiv2.addEventListener("refresh", () => {
if (this.visible) {
if (this.blueprint !== null)
this.setBlueprint(this.blueprint);
}
});
this.contentDiv3.addEventListener("refresh", () => {
if (this.visible) {
if (this.blueprint !== null)
this.setBlueprint(this.blueprint);
}
});
}
/**
*
* @param database The database that the editor will operate on
*/
setDatabase(database: UnitDatabase) {
this.database = JSON.parse(JSON.stringify(database));
}
/** Show the editor
*
*/
show() {
this.visible = true;
this.contentDiv1.replaceChildren();
this.contentDiv2.replaceChildren();
this.contentDiv3.replaceChildren();
/* Create the list of units. Each unit is clickable to activate the editor on it */
if (this.database != null) {
var title = document.createElement("label");
title.innerText = "Units list";
this.contentDiv1.appendChild(title);
addBlueprintsScroll(this.contentDiv1, this.database, (key: string) => {
if (this.database != null)
this.setBlueprint(this.database.blueprints[key])
})
addNewElementInput(this.contentDiv1, (ev: MouseEvent, input: HTMLInputElement) => {
if (input.value != "")
this.addBlueprint((input).value);
@ -41,6 +75,9 @@ export abstract class UnitEditor {
}
}
/** Hid the editor
*
*/
hide() {
this.visible = false;
this.contentDiv1.replaceChildren();
@ -48,10 +85,15 @@ export abstract class UnitEditor {
this.contentDiv3.replaceChildren();
}
/**
*
* @returns The edited database
*/
getDatabase() {
return this.database;
}
/* Abstract methods which will depend on the specific type of units */
abstract setBlueprint(blueprint: UnitBlueprint): void;
abstract addBlueprint(key: string): void;
}

View File

@ -1,5 +1,18 @@
import { LoadoutBlueprint, LoadoutItemBlueprint, UnitBlueprint } from "interfaces";
/** This file contains a set of utility functions that are reused in the various editors and allows to declutter the classes
*
*/
/** Add a string input in the form of String: [ value ]
*
* @param div The HTMLElement that will contain the input
* @param key The key of the input, which will be used as label
* @param value The initial value of the input
* @param type The type of the input, e.g. "Text" or "Number" as per html standard
* @param callback Callback called when the user enters a new value
* @param disabled If true, the input will be disabled and read only
*/
export function addStringInput(div: HTMLElement, key: string, value: string, type: string, callback: CallableFunction, disabled?: boolean) {
var row = document.createElement("div");
var dt = document.createElement("dt");
@ -18,6 +31,13 @@ export function addStringInput(div: HTMLElement, key: string, value: string, typ
div.appendChild(row);
}
/** Add a dropdown (select) input
*
* @param div The HTMLElement that will contain the input
* @param key The key of the input, which will be used as label
* @param value The initial value of the input
* @param options The dropdown options
*/
export function addDropdownInput(div: HTMLElement, key: string, value: string, options: string[]) {
var row = document.createElement("div");
var dt = document.createElement("dt");
@ -38,9 +58,16 @@ export function addDropdownInput(div: HTMLElement, key: string, value: string, o
div.appendChild(row);
}
/** Create a loadout items editor. This editor allows to add or remove loadout items, as well as changing their name and quantity
*
* @param div The HTMLElement that will contain the editor
* @param loadout The loadout to edit
*/
export function addLoadoutItemsEditor(div: HTMLElement, loadout: LoadoutBlueprint) {
var itemsEl = document.createElement("div");
itemsEl.classList.add("dm-scroll-container", "dm-items-container");
/* Create a row for each loadout item to allow and change the name and quantity of the item itself */
loadout.items.forEach((item: LoadoutItemBlueprint, index: number) => {
var rowDiv = document.createElement("div");
@ -66,6 +93,7 @@ export function addLoadoutItemsEditor(div: HTMLElement, loadout: LoadoutBlueprin
quantityInput.step = "1";
quantityInput.onchange = () => { loadout.items[index].quantity = parseInt(quantityInput.value); }
/* This button allows to remove the item */
var button = document.createElement("button");
button.innerText = "X";
button.onclick = () => {
@ -78,6 +106,7 @@ export function addLoadoutItemsEditor(div: HTMLElement, loadout: LoadoutBlueprin
})
div.appendChild(itemsEl);
/* Button to add a new item to the loadout */
var inputDiv = document.createElement("div");
inputDiv.classList.add("dm-new-item-input");
var button = document.createElement("button");
@ -94,11 +123,18 @@ export function addLoadoutItemsEditor(div: HTMLElement, loadout: LoadoutBlueprin
});
}
/** Add a input and button to create a new element in a list. It uses a generic callback to actually add the element.
*
* @param div The HTMLElement that will contain the input and button
* @param callback Callback called when the user clicks on "Add"
*/
export function addNewElementInput(div: HTMLElement, callback: CallableFunction) {
var inputDiv = document.createElement("div");
inputDiv.classList.add("dm-new-element-input");
var input = document.createElement("input");
inputDiv.appendChild(input);
var button = document.createElement("button");
button.innerText = "Add";
button.addEventListener("click", (ev: MouseEvent) => callback(ev, input));
@ -106,6 +142,12 @@ export function addNewElementInput(div: HTMLElement, callback: CallableFunction)
div.appendChild(inputDiv);
}
/** Add a scrollable list of blueprints
*
* @param div The HTMLElement that will contain the list
* @param database The database that will be used to fill the list of blueprints
* @param callback Callback called when the user clicks on one of the elements
*/
export function addBlueprintsScroll(div: HTMLElement, database: {blueprints: {[key: string]: UnitBlueprint}}, callback: CallableFunction) {
var scrollDiv = document.createElement("div");
scrollDiv.classList.add("dm-scroll-container");
@ -121,6 +163,7 @@ export function addBlueprintsScroll(div: HTMLElement, database: {blueprints: {[k
text.onclick = () => callback(key);
rowDiv.appendChild(text);
/* This button allows to remove an element from the list. It requires a refresh. */
var button = document.createElement("button");
button.innerText = "X";
button.onclick = () => {
@ -133,6 +176,12 @@ export function addBlueprintsScroll(div: HTMLElement, database: {blueprints: {[k
div.appendChild(scrollDiv);
}
/** Add a scrollable list of loadouts
*
* @param div The HTMLElement that will contain the list
* @param loadouts The loadouts that will be used to fill the list
* @param callback Callback called when the user clicks on one of the elements
*/
export function addLoadoutsScroll(div: HTMLElement, loadouts: LoadoutBlueprint[], callback: CallableFunction) {
var loadoutsEl = document.createElement("div");
loadoutsEl.classList.add("dm-scroll-container", "dm-loadout-container")
@ -146,7 +195,9 @@ export function addLoadoutsScroll(div: HTMLElement, loadouts: LoadoutBlueprint[]
text.onclick = () => { callback(loadout) };
rowDiv.appendChild(text);
/* The "Empty loadout" can not be removed */
if (loadout.name !== "Empty loadout") {
/* This button allows to remove an element from the list. It requires a refresh. */
var button = document.createElement("button");
button.innerText = "X";
button.onclick = () => {

View File

@ -41,6 +41,10 @@
margin-top: 5px;
}
#database-manager-panel>div:last-child>button {
border: 1px solid white;
}
.dm-container {
background-color: var(--background-grey);
border: 2px solid #777777;
@ -52,6 +56,10 @@
border-radius: 0px 5px 5px 5px;
}
.dm-container>div:nth-child(2) {
width: 500px;
}
.dm-container>div:nth-child(3) {
flex: 1;
}
@ -79,6 +87,11 @@
}
}
.dm-content-container>label {
font-size: 18px !important;
font-weight: bold;
}
.dm-scroll-container {
display: flex;
flex-direction: column;
@ -98,8 +111,10 @@
.dm-scroll-container>div *:nth-child(1) {
height: 100%;
width: 100%;
width: calc(100% - 25px);
padding: 2px;
text-wrap: wrap;
word-wrap: break-word;
}
.dm-scroll-container>div *:nth-child(1):hover {
@ -141,6 +156,7 @@
.dm-loadout-container {
max-height: 100%;
max-width: 500px;
width: 100%;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,767 +0,0 @@
{
"An-26B": {
"name": "An-26B",
"coalition": "red",
"label": "An-26B Curl",
"era": "Mid Cold War",
"shortLabel": "26",
"loadouts": [
{
"fuel": 1,
"items": [],
"roles": [
"Transport"
],
"code": "",
"name": "Empty Loadout"
}
],
"filename": "an-26.png",
"cost": 0
},
"An-30M": {
"name": "An-30M",
"coalition": "red",
"label": "An-30M Clank",
"era": "Mid Cold War",
"shortLabel": "30",
"loadouts": [
{
"fuel": 1,
"items": [],
"roles": [
"Transport"
],
"code": "",
"name": "Empty Loadout"
}
],
"filename": "a-50.png",
"cost": 0
},
"C-130": {
"name": "C-130",
"label": "C-130 Hercules",
"era": "Early Cold War",
"shortLabel": "130",
"loadouts": [
{
"fuel": 1,
"items": [],
"roles": [
"Transport"
],
"code": "",
"name": "Empty Loadout"
}
],
"filename": "c-130.png",
"cost": 0
},
"F-14A-135-GR": {
"name": "F-14A-135-GR",
"coalition": "blue",
"label": "F-14A-135-GR Tomcat",
"era": "Mid Cold War",
"shortLabel": "14A",
"loadouts": [
{
"fuel": 1,
"items": [
{
"name": "fuel",
"quantity": 2
},
{
"name": "AIM-54A",
"quantity": 2
},
{
"name": "AIM-7F",
"quantity": 1
},
{
"name": "AIM-9L",
"quantity": 4
}
],
"roles": [
"CAP"
],
"code": "AIM-54A-MK47*2, AIM-7F*1, AIM-9L*4, XT*2",
"name": "Heavy / Fox 3 / Long Range"
},
{
"fuel": 1,
"items": [
{
"name": "fuel",
"quantity": 2
},
{
"name": "AIM-7F",
"quantity": 4
},
{
"name": "AIM-9L",
"quantity": 4
}
],
"roles": [
"CAP"
],
"code": "AIM-7F*4, AIM-9L*4, XT*2",
"name": "Heavy / Fox 1 / Long Range"
},
{
"fuel": 1,
"items": [
{
"name": "fuel",
"quantity": 2
},
{
"name": "AIM-7M",
"quantity": 1
},
{
"name": "AIM-9M",
"quantity": 2
},
{
"name": "GBU-12",
"quantity": 2
},
{
"name": "LANTIRN",
"quantity": 1
}
],
"roles": [
"Strike"
],
"code": "AIM-7M*1, AIM-9M*2, XT*2, GBU-12*2, LANTIRN",
"name": "Heavy / Fox 3, GBU-12 / Long Range"
},
{
"fuel": 1,
"items": [],
"roles": [
""
],
"code": "",
"name": "Empty Loadout"
}
],
"filename": "f-14.png",
"cost": 300,
"liveryID": "IRIAF Asia Minor"
},
"F-4E": {
"name": "F-4E",
"coalition": "blue",
"label": "F-4E Phantom II",
"era": "Mid Cold War",
"shortLabel": "4",
"loadouts": [
{
"fuel": 1,
"items": [
{
"name": "fuel",
"quantity": 2
},
{
"name": "AIM-7M",
"quantity": 4
},
{
"name": "AIM-9M",
"quantity": 4
}
],
"roles": [
"CAP"
],
"code": "AIM-9*4,AIM-7*4,Fuel*2",
"name": "Heavy / Fox 1 / Long Range"
},
{
"fuel": 1,
"items": [
{
"name": "ECM",
"quantity": 1
},
{
"name": "AIM-7M",
"quantity": 2
},
{
"name": "Mk-82",
"quantity": 18
}
],
"roles": [
"Strike"
],
"code": "Mk-82*18,AIM-7*2,ECM",
"name": "Heavy / Fox 1, Mk-82 / Short Range"
},
{
"fuel": 1,
"items": [],
"roles": [
""
],
"code": "",
"name": "Empty Loadout"
}
],
"filename": "f-4.png",
"cost": 100,
"liveryID": "IRIAF Asia Minor"
},
"F-5E-3": {
"name": "F-5E-3",
"coalition": "blue",
"label": "F-5E Tiger",
"era": "Mid Cold War",
"shortLabel": "5",
"loadouts": [
{
"fuel": 1,
"items": [
{
"name": "Fuel 275",
"quantity": 3
},
{
"name": "AIM-9P5",
"quantity": 2
}
],
"roles": [
"CAP"
],
"code": "AIM-9P5*2, Fuel 275*3",
"name": "Heavy / Fox 2 / Long Range"
},
{
"fuel": 1,
"items": [
{
"name": "Mk-82",
"quantity": 4
},
{
"name": "AIM-9P5",
"quantity": 2
},
{
"name": "Fuel 275",
"quantity": 1
}
],
"roles": [
"Strike"
],
"code": "Mk-82LD*4,AIM-9P*2,Fuel 275",
"name": "Heavy / Fox 2 / Short Range"
},
{
"fuel": 1,
"items": [],
"roles": [
""
],
"code": "",
"name": "Empty Loadout"
}
],
"liveryID": "ir iriaf 43rd tfs",
"filename": "f-5.png",
"cost": 80
},
"F-86F Sabre": {
"name": "F-86F Sabre",
"coalition": "blue",
"label": "F-86F Sabre",
"era": "Early Cold War",
"shortLabel": "86",
"loadouts": [
{
"fuel": 1,
"items": [
{
"name": "120gal Fuel",
"quantity": 2
}
],
"roles": [
"CAP"
],
"code": "120gal Fuel*2",
"name": "Light / Guns / Short Range"
},
{
"fuel": 1,
"items": [
{
"name": "HVAR",
"quantity": 16
}
],
"roles": [
"CAS"
],
"code": "HVAR*16",
"name": "Light / HVAR / Short Range"
},
{
"fuel": 1,
"items": [
{
"name": "AN-M64",
"quantity": 2
}
],
"roles": [
"Strike"
],
"code": "AN-M64*2",
"name": "Light / AN-M64 / Short Range"
},
{
"fuel": 1,
"items": [],
"roles": [
""
],
"code": "",
"name": "Light / Guns / Short Range"
}
],
"filename": "f-86.png",
"cost": 40,
"liveryID": "iiaf bare metall"
},
"IL-76MD": {
"name": "IL-76MD",
"label": "IL-76MD Candid",
"era": "Mid Cold War",
"shortLabel": "76",
"loadouts": [
{
"fuel": 1,
"items": [],
"roles": [
"Transport"
],
"code": "",
"name": "Default Transport"
}
],
"filename": "il-76.png",
"cost": 0
},
"MiG-15bis": {
"name": "MiG-15bis",
"coalition": "red",
"label": "MiG-15 Fagot",
"era": "Early Cold War",
"shortLabel": "M15",
"loadouts": [
{
"fuel": 1,
"items": [
{
"name": "300L Fuel Tanks",
"quantity": 2
}
],
"roles": [
"CAP"
],
"code": "2*300L",
"name": "Medium / Guns / Medium Range"
},
{
"fuel": 1,
"items": [
{
"name": "FAB-100M",
"quantity": 2
}
],
"roles": [
"Strike"
],
"code": "2*FAB-100M",
"name": "Medium / FAB-100M / Short Range"
},
{
"fuel": 1,
"items": [],
"roles": [
"CAP"
],
"code": "",
"name": "Light / Guns / Short Range"
}
],
"filename": "mig-15.png",
"cost": 30,
"liveryID": "Iraqi_Camo"
},
"MiG-21Bis": {
"name": "MiG-21Bis",
"coalition": "red",
"label": "MiG-21 Fishbed",
"era": "Mid Cold War",
"shortLabel": "21",
"loadouts": [
{
"fuel": 1,
"items": [
{
"name": "R-3 Atoll",
"quantity": 2
},
{
"name": "R-60 Aphid",
"quantity": 2
},
{
"name": "130 gal tanks",
"quantity": 1
},
{
"name": "ASO-2 Countermeasures",
"quantity": 1
}
],
"roles": [
"CAP"
],
"code": "Patrol, short range",
"name": "Light / Fox-2 / Short range"
},
{
"fuel": 1,
"items": [
{
"name": "R-3 Atoll",
"quantity": 2
},
{
"name": "R-60 Aphid",
"quantity": 2
},
{
"name": "210 gal tanks",
"quantity": 1
},
{
"name": "ASO-2 Countermeasures",
"quantity": 1
}
],
"roles": [
"CAP"
],
"code": "Patrol, medium range",
"name": "Medium / Fox-2 / Medium range"
},
{
"fuel": 1,
"items": [
{
"name": "R-3R Atoll",
"quantity": 2
},
{
"name": "R-3S Atoll",
"quantity": 2
},
{
"name": "210 gal tanks",
"quantity": 1
},
{
"name": "ASO-2 Countermeasures",
"quantity": 1
}
],
"roles": [
"CAP"
],
"code": "Patrol, long range",
"name": "Medium / Fox-1, Fox-2 / Medium range"
},
{
"fuel": 1,
"items": [
{
"name": "GROM",
"quantity": 2
},
{
"name": "FAB-250",
"quantity": 2
},
{
"name": "210 gal tanks",
"quantity": 1
},
{
"name": "ASO-2 Countermeasures",
"quantity": 1
}
],
"roles": [
"Strike"
],
"code": "Few big targets, GROM + BOMBS",
"name": "Heavy / GROM, FAB250 / Medium range"
},
{
"fuel": 1,
"items": [],
"roles": [
"CAP"
],
"code": "",
"name": "Empty Loadout"
}
],
"filename": "mig-21.png",
"cost": 100,
"liveryID": "iran - standard"
},
"MiG-23MLD": {
"name": "MiG-23MLD",
"coalition": "red",
"label": "MiG-23 Flogger",
"era": "Mid Cold War",
"shortLabel": "23",
"loadouts": [
{
"fuel": 1,
"items": [
{
"name": "Fuel-800",
"quantity": 1
},
{
"name": "R-60M",
"quantity": 4
},
{
"name": "R-24R",
"quantity": 2
}
],
"roles": [
"CAP"
],
"code": "R-24R*2,R-60M*4,Fuel-800",
"name": "Heavy / Fox 1 / Long Range"
},
{
"fuel": 1,
"items": [
{
"name": "Fuel-800",
"quantity": 1
},
{
"name": "FAB-500",
"quantity": 2
},
{
"name": "R-60M",
"quantity": 2
}
],
"roles": [
"Strike"
],
"code": "FAB-500*2,R-60M*2,Fuel-800",
"name": "Heavy / FAB-500 / Long Range"
},
{
"fuel": 1,
"items": [],
"roles": [
""
],
"code": "",
"name": "Empty Loadout"
}
],
"filename": "mig-23.png",
"cost": 200
},
"Mirage-F1EE": {
"name": "Mirage-F1EE",
"coalition": "red",
"label": "Mirage-F1EE",
"era": "Mid Cold War",
"shortLabel": "F1EE",
"loadouts": [
{
"fuel": 1,
"items": [
{
"name": "AIM-9JULI",
"quantity": 2
},
{
"name": "R530EM",
"quantity": 2
},
{
"name": "1137L Fuel Tank",
"quantity": 1
}
],
"roles": [
"CAP"
],
"code": "2*AIM9-JULI, 2*R530EM, 1*Fuel Tank",
"name": "Medium / Fox 1 / Medium Range"
},
{
"fuel": 1,
"items": [
{
"name": "AIM-9JULI",
"quantity": 2
},
{
"name": "SAMP 400 LD",
"quantity": 8
}
],
"roles": [
"Strike"
],
"code": "2*AIM-9JULI, 8*SAMP 400 LD",
"name": "Heavy / SAMP400 / Short Range"
},
{
"fuel": 1,
"items": [],
"roles": [
""
],
"code": "",
"name": "Empty Loadout"
}
],
"filename": "mig-25.png",
"cost": 150,
"liveryID": "iriaf 3-6215 _ 1990-2010s desert (eq variant)"
},
"Mirage-F1CE": {
"name": "Mirage-F1CE",
"coalition": "red",
"label": "Mirage-F1CE",
"era": "Mid Cold War",
"shortLabel": "F1CE",
"loadouts": [
{
"fuel": 1,
"items": [
{
"name": "AIM-9JULI",
"quantity": 2
},
{
"name": "R530IR",
"quantity": 2
},
{
"name": "1137L Fuel Tank",
"quantity": 1
}
],
"roles": [
"CAP"
],
"code": "2*AIM9-JULI, 2*R530IR, 1*Fuel Tank",
"name": "Medium / Fox 2 / Medium Range"
},
{
"fuel": 1,
"items": [
{
"name": "AIM-9JULI",
"quantity": 2
},
{
"name": "SAMP 400 LD",
"quantity": 8
}
],
"roles": [
"Strike"
],
"code": "2*AIM-9JULI, 8*SAMP 400 LD",
"name": "Heavy / SAMP400 / Short Range"
},
{
"fuel": 1,
"items": [],
"roles": [
""
],
"code": "",
"name": "Empty Loadout"
}
],
"filename": "mig-25.png",
"cost": 150,
"liveryID": "iraq air force (fictional eq version)"
},
"Su-24M": {
"name": "Su-24M",
"coalition": "red",
"label": "Su-24M Fencer",
"era": "Mid Cold War",
"shortLabel": "24",
"loadouts": [
{
"fuel": 1,
"items": [
{
"name": "R-60M",
"quantity": 2
},
{
"name": "FAB-1500",
"quantity": 2
}
],
"roles": [
"Strike"
],
"code": "FAB-1500*2,R-60M*2",
"name": "Heavy / FAB-500 / Short Range"
},
{
"fuel": 1,
"items": [],
"roles": [
""
],
"code": "",
"name": "Empty Loadout"
}
],
"filename": "su-24.png",
"cost": 250,
"liveryID": "iran air force"
}
}

View File

@ -1,153 +0,0 @@
{
"BMP-1": {
"name": "BMP-1",
"coalition": "red",
"era": "Mid Cold War",
"label": "BMP-1",
"shortLabel": "BMP-1",
"filename": "",
"type": "IFV",
"cost": 10,
"liveryID": "desert"
},
"Hawk SAM Battery": {
"name": "Hawk SAM Battery",
"coalition": "blue",
"era": "Early Cold War",
"label": "Hawk SAM Battery",
"shortLabel": "Hawk SAM Battery",
"range": "Medium",
"filename": "",
"type": "SAM Site",
"cost": 500
},
"Infantry AK": {
"name": "Infantry AK",
"era": "Mid Cold War",
"label": "Infantry AK",
"shortLabel": "Infantry AK",
"filename": "",
"type": "Infantry",
"cost": 1
},
"M-113": {
"name": "M-113",
"coalition": "blue",
"era": "Early Cold War",
"label": "M-113",
"shortLabel": "M-113",
"filename": "",
"type": "APC",
"cost": 5
},
"M-60": {
"name": "M-60",
"coalition": "blue",
"era": "Early Cold War",
"label": "M-60",
"shortLabel": "M-60",
"filename": "",
"type": "Tank",
"cost": 10
},
"SA-2 SAM Battery": {
"name": "SA-2 SAM Battery",
"coalition": "red",
"era": "Early Cold War",
"label": "SA-2 SAM Battery",
"shortLabel": "SA-2 SAM Battery",
"range": "Long",
"filename": "",
"type": "SAM Site",
"cost": 500
},
"T-55": {
"name": "T-55",
"coalition": "red",
"era": "Early Cold War",
"label": "T-55",
"shortLabel": "T-55",
"filename": "",
"type": "Tank",
"cost": 10,
"liveryID": "desert"
},
"T-72B": {
"name": "T-72B",
"coalition": "red",
"era": "Mid Cold War",
"label": "T-72B",
"shortLabel": "T-72B",
"filename": "",
"type": "Tank",
"cost": 20,
"liveryID": "desert"
},
"ZSU-23-4 Shilka": {
"name": "ZSU-23-4 Shilka",
"era": "Late Cold War",
"label": "ZSU-23-4 Shilka",
"shortLabel": "ZSU-23-4 Shilka",
"filename": "",
"type": "AAA",
"cost": 100
},
"Ural-375 ZU-23": {
"name": "Ural-375 ZU-23",
"era": "Early Cold War",
"label": "Ural-375 ZU-23",
"shortLabel": "Ural-375 ZU-23",
"filename": "",
"type": "AAA",
"cost": 50
},
"SAU Gvozdika": {
"name": "SAU Gvozdika",
"coalition": "red",
"era": "Early Cold War",
"label": "SAU Gvozdika",
"shortLabel": "SAU Gvozdika",
"filename": "",
"type": "Gun Artillery",
"cost": 10,
"liveryID": "desert"
},
"Grad-URAL": {
"name": "Grad-URAL",
"coalition": "blue",
"era": "Early Cold War",
"label": "BM-21",
"shortLabel": "BM-21",
"filename": "",
"type": "Rocket Artillery",
"cost": 10
},
"Chieftain_mk3": {
"name": "Chieftain_mk3",
"coalition": "blue",
"era": "Early Cold War",
"label": "Chieftain Mk3",
"shortLabel": "Chieftain Mk3",
"filename": "",
"type": "Tank",
"cost": 20
},
"Scud_B": {
"name": "Scud_B",
"era": "Early Cold War",
"label": "SCUD",
"shortLabel": "SCUD",
"filename": "",
"type": "Rocket Artillery",
"cost": 10
},
"tt_ZU-23": {
"name": "tt_ZU-23",
"era": "Early Cold War",
"label": "Technical ZSU-23",
"shortLabel": "Technical ZSU-23",
"filename": "",
"type": "AAA",
"cost": 25
}
}

View File

@ -1,141 +0,0 @@
{
"Mi-24P": {
"name": "Mi-24P",
"coalition": "red",
"era": "Mid Cold War",
"label": "Mi-24P Hind",
"shortLabel": "Mi24",
"loadouts": [
{
"fuel": 1,
"items": [
{
"name": "S-8KOM",
"quantity": 40
},
{
"name": "9M114 ATGM",
"quantity": 8
}
],
"roles": [
"CAS"
],
"code": "2xB8V20 (S-8KOM)+8xATGM 9M114",
"name": "Gun / ATGM / Rockets"
},
{
"fuel": 1,
"items": [
{
"name": "S-24B",
"quantity": 4
},
{
"name": "9M114 ATGM",
"quantity": 4
}
],
"roles": [
"Strike"
],
"code": "4xS-24B+4xATGM 9M114",
"name": "Gun / ATGM / Rockets"
},
{
"fuel": 1,
"items": [
{
"name": "GUV-1 Grenade Launcher",
"quantity": 4
},
{
"name": "9M114 ATGM",
"quantity": 4
}
],
"roles": [
"CAS"
],
"code": "4xGUV-1 AP30+4xATGM 9M114",
"name": "Gun / ATGM / Grenade Launcher"
},
{
"fuel": 1,
"items": [],
"roles": [
""
],
"code": "",
"name": "Empty Loadout"
}
],
"filename": "mi-24.png",
"cost": 150,
"liveryID": "IQAF"
},
"Mi-8MT": {
"name": "Mi-8MT",
"coalition": "blue",
"era": "Mid Cold War",
"label": "Mi-8MT Hip",
"shortLabel": "Mi8",
"loadouts": [
{
"fuel": 1,
"items": [
{
"name": "UPK",
"quantity": 2
},
{
"name": "B8",
"quantity": 2
}
],
"roles": [
"CAS"
],
"code": "2 x UPK +2 x B8",
"name": "Rockets / Gunpods"
},
{
"fuel": 1,
"items": [],
"roles": [
"Transport"
],
"code": "",
"name": "Empty Loadout"
}
],
"filename": "mi-8.png",
"cost": 100,
"liveryID": "IR Iranian Special Police Forces"
},
"SA342M": {
"name": "SA342M",
"coalition": "blue",
"era": "Mid Cold War",
"label": "SA342M Gazelle",
"shortLabel": "342",
"loadouts": [
{
"fuel": 1,
"items": [
{
"name": "HOT3",
"quantity": 4
}
],
"roles": [
"CAS"
],
"code": "4x HOT3, IR Deflector, Sand Filter",
"name": "ATGM"
}
],
"filename": "sa-342.png",
"cost": 80
}
}

File diff suppressed because it is too large Load Diff

View File

@ -140,7 +140,7 @@
top: 100px;
left: 50%;
translate: -50% 0%;
z-index: 999999;
z-index: 9999999999;
display: flex;
align-items: center;
}

View File

@ -0,0 +1,47 @@
@-webkit-keyframes leaflet-gestures-fadein {
0% {
opacity: 0; }
100% {
opacity: 1; } }
@keyframes leaflet-gestures-fadein {
0% {
opacity: 0; }
100% {
opacity: 1; } }
.leaflet-container:after {
-webkit-animation: leaflet-gestures-fadein 0.8s backwards;
animation: leaflet-gestures-fadein 0.8s backwards;
color: #fff;
font-family: "Roboto", Arial, sans-serif;
font-size: 22px;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
padding: 15px;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 461;
pointer-events: none; }
.leaflet-gesture-handling-touch-warning:after,
.leaflet-gesture-handling-scroll-warning:after {
-webkit-animation: leaflet-gestures-fadein 0.8s forwards;
animation: leaflet-gestures-fadein 0.8s forwards; }
.leaflet-gesture-handling-touch-warning:after {
content: attr(data-gesture-handling-touch-content); }
.leaflet-gesture-handling-scroll-warning:after {
content: attr(data-gesture-handling-scroll-content); }

View File

@ -0,0 +1,58 @@
const express = require('express');
const router = express.Router();
const fs = require("fs");
const path = require("path");
router.get('/:type/:name', function (req, res) {
console.log(req.params.database)
});
router.put('/save/:type/:name', function (req, res) {
var dir = path.join("./public/databases", req.params.type, "old");
if (!fs.existsSync(dir)){
fs.mkdirSync(dir);
}
var filepath = path.join("./public/databases", req.params.type, req.params.name + ".json");
if (fs.existsSync(filepath)) {
var newFilepath = path.join("./public/databases/", req.params.type, "old", req.params.name + ".json");
fs.copyFileSync(filepath, newFilepath);
if (fs.existsSync(newFilepath)) {
try {
var json = JSON.stringify(req.body.blueprints, null, "\t" );
fs.writeFileSync(filepath, json, 'utf8');
res.send("OK");
} catch {
res.status(422).send("Error");
}
} else {
res.status(422).send("Error");
}
} else {
res.status(404).send('Not found');
}
});
router.put('/reset/:type/:name', function (req, res) {
var filepath = path.join("./public/databases", req.params.type, "default", req.params.name + ".json");
if (fs.existsSync(filepath)) {
var newFilepath = path.join("./public/databases", req.params.type, req.params.name + ".json");
fs.copyFileSync(filepath, newFilepath);
res.send("OK");
} else {
res.status(404).send('Not found');
}
});
router.put('/restore/:type/:name', function (req, res) {
var filepath = path.join("./public/databases", req.params.type, "old", req.params.name + ".json");
if (fs.existsSync(filepath)) {
var newFilepath = path.join("./public/databases", req.params.type, req.params.name + ".json");
fs.copyFileSync(filepath, newFilepath);
res.send("OK");
} else {
res.status(404).send('Not found');
}
});
module.exports = router;

View File

@ -1,31 +0,0 @@
const express = require('express');
const router = express.Router();
const fs = require("fs");
const path = require("path");
router.get('/:type/:name', function (req, res) {
console.log(req.params.database)
});
router.put('/:type/:name', function (req, res) {
var filepath = path.join("./public/databases", req.params.type, req.params.name + ".json");
if (fs.existsSync(filepath)) {
var newFilepath = filepath + ".old";
fs.copyFileSync(filepath, newFilepath);
if (fs.existsSync(newFilepath)) {
try {
var json = JSON.stringify(req.body.blueprints);
fs.writeFileSync(filepath, json, 'utf8');
res.send('OK')
} catch {
res.status(422);
}
} else {
res.status(422);
}
} else {
res.status(404);
}
});
module.exports = router;

View File

@ -5,18 +5,26 @@ import { UnitBlueprint } from "../../interfaces";
export class UnitDatabase {
blueprints: { [key: string]: UnitBlueprint } = {};
#url: string;
constructor(url: string = "") {
if (url !== "") {
this.#url = url;
this.load(() => {});
}
load(callback: CallableFunction) {
if (this.#url !== "") {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
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 ${url}`)
console.error(`Error retrieving database from ${this.#url}`)
}
};
xhr.send();