Merge pull request #601 from Pax1601/412-improve-importexport

412 improve importexport
This commit is contained in:
Pax1601 2023-11-28 17:31:34 +01:00 committed by GitHub
commit 76801f773d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 888 additions and 385 deletions

View File

@ -53,7 +53,7 @@ class DemoDataGenerator {
}
/*
// UNCOMMENT TO TEST ALL UNITS ****************
var databases = Object.assign({}, aircraftDatabase, helicopterDatabase, groundUnitDatabase, navyUnitDatabase);
@ -70,6 +70,7 @@ class DemoDataGenerator {
DEMO_UNIT_DATA[idx].groupName = `Group-${idx}`;
DEMO_UNIT_DATA[idx].position.lat += latIdx / 5;
DEMO_UNIT_DATA[idx].position.lng += lngIdx / 5;
DEMO_UNIT_DATA[idx].coalition = Math.floor(Math.random() * 3)
latIdx += 1;
if (latIdx === l) {
@ -89,9 +90,9 @@ class DemoDataGenerator {
idx += 1;
}
}
*/
/*
let idx = 1;
DEMO_UNIT_DATA[idx] = JSON.parse(JSON.stringify(baseData));
DEMO_UNIT_DATA[idx].name = "S_75M_Volhov";
@ -152,7 +153,7 @@ class DemoDataGenerator {
DEMO_UNIT_DATA[idx].position.lat += idx / 100;
DEMO_UNIT_DATA[idx].category = "Aircraft";
DEMO_UNIT_DATA[idx].isLeader = true;
*/
this.startTime = Date.now();
}

View File

@ -1290,6 +1290,17 @@ dl.ol-data-grid dd {
margin: 4px 0;
}
.ol-dialog-content table th {
background-color: var(--background-grey);
color:white;
font-size:14px;
font-weight: normal;
}
.ol-dialog-content table tbody th {
text-align: left;
}
.ol-dialog-footer {
align-content: center;
border-top: 1px solid var(--background-grey);
@ -1330,6 +1341,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");
}
@ -1574,3 +1589,86 @@ 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);
}
.file-import-export .export-filename-container {
display: flex;
column-gap: 15px;
width: 100%;
align-items: center;
padding: 10px 0px;
color: white;
font-size: 14px;
}
.file-import-export .export-filename-container input {
width: 100%;
background-color: var(--background-grey);
appearance: none;
-moz-appearance: none;
-webkit-appearance: none;
border-style: solid;
border: 1px solid var(--background-steel);
color: white;
font-size: 14px;
padding: 4px;
}
.file-import-export .export-filename-container img {
height: 16px;
width: 16px;
filter: invert(100%);
margin-left: -31px;
transform: translateX(-15px);
pointer-events: none;
}
.file-import-export .ol-dialog-footer button:first-of-type{
margin-left: auto;
}

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--! Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M64 64C28.7 64 0 92.7 0 128V384c0 35.3 28.7 64 64 64H512c35.3 0 64-28.7 64-64V128c0-35.3-28.7-64-64-64H64zm16 64h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V144c0-8.8 7.2-16 16-16zM64 240c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V240zm16 80h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V336c0-8.8 7.2-16 16-16zm80-176c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H176c-8.8 0-16-7.2-16-16V144zm16 80h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H176c-8.8 0-16-7.2-16-16V240c0-8.8 7.2-16 16-16zM160 336c0-8.8 7.2-16 16-16H400c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H176c-8.8 0-16-7.2-16-16V336zM272 128h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H272c-8.8 0-16-7.2-16-16V144c0-8.8 7.2-16 16-16zM256 240c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H272c-8.8 0-16-7.2-16-16V240zM368 128h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H368c-8.8 0-16-7.2-16-16V144c0-8.8 7.2-16 16-16zM352 240c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H368c-8.8 0-16-7.2-16-16V240zM464 128h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H464c-8.8 0-16-7.2-16-16V144c0-8.8 7.2-16 16-16zM448 240c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H464c-8.8 0-16-7.2-16-16V240zm16 80h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H464c-8.8 0-16-7.2-16-16V336c0-8.8 7.2-16 16-16z"/></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -167,6 +167,7 @@ export const COALITIONAREA_DRAW_POLYGON = "Draw Coalition Area";
export const visibilityControls: string[] = ["human", "dcs", "aircraft", "helicopter", "groundunit-sam", "groundunit", "navyunit", "airbase"];
export const visibilityControlsTypes: string[][] = [["human"], ["dcs"], ["aircraft"], ["helicopter"], ["groundunit-sam"], ["groundunit"], ["navyunit"], ["airbase"]];
export const visibilityControlsTooltips: string[] = ["Toggle human players visibility", "Toggle DCS controlled units visibility", "Toggle aircrafts visibility", "Toggle helicopter visibility", "Toggle SAM units visibility", "Toggle ground units (not SAM) visibility", "Toggle navy units visibility", "Toggle airbases visibility"];
export const MAP_MARKER_CONTROLS: MapMarkerVisibilityControl[] = [{
"name": "Human",
"image": "visibility/human.svg",
@ -213,7 +214,7 @@ export const MAP_MARKER_CONTROLS: MapMarkerVisibilityControl[] = [{
export const IADSTypes = ["AAA", "MANPADS", "SAM Site", "Radar"];
export const IADSDensities: { [key: string]: number } = { "AAA": 0.8, "MANPADS": 0.3, "SAM Site": 0.1, "Radar": 0.05 };
export const GROUND_UNIT_AIR_DEFENCE_REGEX:RegExp = /(\b(AAA|SAM|MANPADS?|[mM]anpads?)|[sS]tinger\b)/;
export const HIDE_GROUP_MEMBERS = "Hide group members when zoomed out";
export const SHOW_UNIT_LABELS = "Show unit labels (L)";
export const SHOW_UNITS_ENGAGEMENT_RINGS = "Show units threat range rings (Q)";

View File

@ -3,7 +3,7 @@ import { Dropdown } from "./dropdown";
import { Slider } from "./slider";
import { UnitDatabase } from "../unit/databases/unitdatabase";
import { getApp } from "..";
import { GAME_MASTER } from "../constants/constants";
import { GAME_MASTER, GROUND_UNIT_AIR_DEFENCE_REGEX } from "../constants/constants";
import { Airbase } from "../mission/airbase";
import { ftToM } from "../other/utils";
import { aircraftDatabase } from "../unit/databases/aircraftdatabase";
@ -616,7 +616,7 @@ export class HelicopterSpawnMenu extends UnitSpawnMenu {
export class GroundUnitSpawnMenu extends UnitSpawnMenu {
protected showRangeCircles: boolean = true;
protected unitTypeFilter = (unit:any) => {return !(/\bAAA|SAM\b/.test(unit.type) || /\bmanpad|stinger\b/i.test(unit.type))};
protected unitTypeFilter = (unit:any) => {return !(GROUND_UNIT_AIR_DEFENCE_REGEX.test(unit.type))};
/**
*
@ -657,7 +657,7 @@ export class GroundUnitSpawnMenu extends UnitSpawnMenu {
export class AirDefenceUnitSpawnMenu extends GroundUnitSpawnMenu {
protected unitTypeFilter = (unit:any) => {return /\bAAA|SAM\b/.test(unit.type) || /\bmanpad|stinger\b/i.test(unit.type)};
protected unitTypeFilter = (unit:any) => {return GROUND_UNIT_AIR_DEFENCE_REGEX.test(unit.type)};
/**
*

View File

@ -139,6 +139,7 @@ export interface Offset {
export interface UnitData {
category: string,
categoryDisplayName: string,
ID: number;
alive: boolean;
human: boolean;

View File

@ -35,6 +35,10 @@ export class MissionManager {
this.#commandModeErasDropdown = new Dropdown("command-mode-era-options", () => {});
}
/** Update location of bullseyes
*
* @param object <BulleyesData>
*/
updateBullseyes(data: BullseyesData) {
const commandMode = getApp().getMissionManager().getCommandModeOptions().commandMode;
for (let idx in data.bullseyes) {
@ -56,6 +60,10 @@ export class MissionManager {
}
}
/** Update airbase information
*
* @param object <AirbasesData>
*/
updateAirbases(data: AirbasesData) {
for (let idx in data.airbases) {
var airbase = data.airbases[idx]
@ -75,6 +83,10 @@ export class MissionManager {
}
}
/** Update mission information
*
* @param object <MissionData>
*/
updateMission(data: MissionData) {
if (data.mission) {
@ -109,30 +121,63 @@ export class MissionManager {
}
}
/** Get the bullseyes set in this theatre
*
* @returns object
*/
getBullseyes() {
return this.#bullseyes;
}
/** Get the airbases in this theatre
*
* @returns object
*/
getAirbases() {
return this.#airbases;
}
/** Get the options/settings as set in the command mode
*
* @returns object
*/
getCommandModeOptions() {
return this.#commandModeOptions;
}
/** Get the current date and time of the mission (based on local time)
*
* @returns object
*/
getDateAndTime() {
return this.#dateAndTime;
}
/**
* Get the number of seconds left of setup time
* @returns number
*/
getRemainingSetupTime() {
return this.#remainingSetupTime;
}
/** Get an object with the coalitions in it
*
* @returns object
*/
getCoalitions() {
return this.#coalitions;
}
/** Get the current theatre (map) name
*
* @returns string
*/
getTheatre() {
return this.#theatre;
}
getAvailableSpawnPoints() {
if (this.getCommandModeOptions().commandMode === GAME_MASTER)
return Infinity;

View File

@ -411,6 +411,7 @@ export class OlympusApp {
// TODO: move from here in dedicated class
document.addEventListener("closeDialog", (ev: CustomEventInit) => {
ev.detail._element.closest(".ol-dialog").classList.add("hide");
document.getElementById("gray-out")?.classList.toggle("hide", true);
});
/* Try and connect with the Olympus REST server */

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

@ -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>&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

@ -0,0 +1,97 @@
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[]) {
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();
}
}

View File

@ -0,0 +1,138 @@
import { getApp } from "../..";
import { Dialog } from "../../dialog/dialog";
import { UnitData } from "../../interfaces";
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, 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() {
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) => {
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

@ -5,7 +5,7 @@ import { CustomMarker } from '../map/markers/custommarker';
import { SVGInjector } from '@tanem/svg-injector';
import { UnitDatabase } from './databases/unitdatabase';
import { TargetMarker } from '../map/markers/targetmarker';
import { DLINK, DataIndexes, GAME_MASTER, HIDE_GROUP_MEMBERS, IDLE, IRST, MOVE_UNIT, OPTIC, RADAR, ROEs, RWR, SHOW_UNIT_CONTACTS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, VISUAL, emissionsCountermeasures, reactionsToThreat, states, SHOW_UNITS_ACQUISITION_RINGS, HIDE_UNITS_SHORT_RANGE_RINGS, FILL_SELECTED_RING, GROUPING_ZOOM_TRANSITION, MAX_SHOTS_SCATTER, SHOTS_SCATTER_DEGREES } from '../constants/constants';
import { DLINK, DataIndexes, GAME_MASTER, HIDE_GROUP_MEMBERS, IDLE, IRST, MOVE_UNIT, OPTIC, RADAR, ROEs, RWR, SHOW_UNIT_CONTACTS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, VISUAL, emissionsCountermeasures, reactionsToThreat, states, SHOW_UNITS_ACQUISITION_RINGS, HIDE_UNITS_SHORT_RANGE_RINGS, FILL_SELECTED_RING, GROUPING_ZOOM_TRANSITION, MAX_SHOTS_SCATTER, SHOTS_SCATTER_DEGREES, GROUND_UNIT_AIR_DEFENCE_REGEX } from '../constants/constants';
import { DataExtractor } from '../server/dataextractor';
import { groundUnitDatabase } from './databases/groundunitdatabase';
import { navyUnitDatabase } from './databases/navyunitdatabase';
@ -249,6 +249,14 @@ export abstract class Unit extends CustomMarker {
*/
abstract getDefaultMarker(): string;
/** Get the category but for display use - for the user. (i.e. has spaces in it)
*
* @returns string
*/
getCategoryLabel() {
return ((GROUND_UNIT_AIR_DEFENCE_REGEX.test(this.getType())) ? "Air Defence" : this.getCategory()).replace(/([a-z])([A-Z])/g, "$1 $2");
}
/********************** Unit data *************************/
/** This function is called by the units manager to update all the data coming from the backend. It reads the binary raw data using a DataExtractor
*
@ -341,6 +349,7 @@ export abstract class Unit extends CustomMarker {
getData(): UnitData {
return {
category: this.getCategory(),
categoryDisplayName: this.getCategoryLabel(),
ID: this.ID,
alive: this.#alive,
human: this.#human,

View File

@ -16,6 +16,8 @@ import { HotgroupPanel } from "../panels/hotgrouppanel";
import { Contact, UnitData, UnitSpawnTable } from "../interfaces";
import { Dialog } from "../dialog/dialog";
import { Group } from "./group";
import { UnitDataFileExport } from "./importexport/unitdatafileexport";
import { UnitDataFileImport } from "./importexport/unitdatafileimport";
/** The UnitsManager handles the creation, update, and control of units. Data is strictly updated by the server ONLY. This means that any interaction from the user will always and only
* result in a command to the server, executed by means of a REST PUT request. Any subsequent change in data will be reflected only when the new data is sent back by the server. This strategy allows
@ -29,6 +31,8 @@ export class UnitsManager {
#slowDeleteDialog!: Dialog;
#units: { [ID: number]: Unit };
#groups: { [groupName: string]: Group } = {};
#unitDataExport!:UnitDataFileExport;
#unitDataImport!:UnitDataFileImport;
constructor() {
this.#copiedUnits = [];
@ -1134,52 +1138,18 @@ export class UnitsManager {
* TODO: Extend to aircraft and helicopters
*/
exportToFile() {
var unitsToExport: { [key: string]: any } = {};
for (let ID in this.#units) {
var unit = this.#units[ID];
if (!["Aircraft", "Helicopter"].includes(unit.getCategory())) {
var data: any = unit.getData();
if (unit.getGroupName() in unitsToExport)
unitsToExport[unit.getGroupName()].push(data);
else
unitsToExport[unit.getGroupName()] = [data];
}
}
var a = document.createElement("a");
var file = new Blob([JSON.stringify(unitsToExport)], { type: 'text/plain' });
a.href = URL.createObjectURL(file);
a.download = 'export.json';
a.click();
if (!this.#unitDataExport)
this.#unitDataExport = new UnitDataFileExport("unit-export-dialog");
this.#unitDataExport.showForm(Object.values(this.#units));
}
/** Import ground and navy units from file
* TODO: extend to support aircraft and helicopters
*/
importFromFile() {
var input = document.createElement("input");
input.type = "file";
input.addEventListener("change", (e: any) => {
var file = e.target.files[0];
if (!file) {
return;
}
var reader = new FileReader();
reader.onload = function (e: any) {
var contents = e.target.result;
var groups = JSON.parse(contents);
for (let groupName in groups) {
if (groupName !== "" && groups[groupName].length > 0 && (groups[groupName].every((unit: UnitData) => { return unit.category == "GroundUnit"; }) || groups[groupName].every((unit: any) => { return unit.category == "NavyUnit"; }))) {
var aliveUnits = groups[groupName].filter((unit: UnitData) => { return unit.alive });
var units = aliveUnits.map((unit: UnitData) => {
return { unitType: unit.name, location: unit.position, liveryID: "" }
});
getApp().getUnitsManager().spawnUnits(groups[groupName][0].category, units, groups[groupName][0].coalition, true);
}
}
};
reader.readAsText(file);
})
input.click();
if (!this.#unitDataImport)
this.#unitDataImport = new UnitDataFileImport("unit-import-dialog");
this.#unitDataImport.selectFile();
}
/** Spawn a new group of units

View File

@ -1,333 +1,19 @@
<div id="splash-screen" class="ol-dialog" oncontextmenu="return false;">
<div id="splash-content" class="ol-dialog-content">
<div id="app-summary">
<h2>DCS Olympus</h2>
<h4>Dynamic Unit Command</h4>
<div class="app-version">Version <span class="app-version-number">v0.4.8-alpha</span></div>
</div>
<form id="authentication-form">
<div><h5>Name</h5> <input type="text" id="username" name="username" required autocomplete="username" placeholder="Enter name..."></div>
<div><h5>Server password</h5> <input type="password" id="password" name="password" minlength="1" required autocomplete="current-password" placeholder="Enter server password..."></div>
<button type="submit" id="connection-button" class="ol-button-apply">Connect</button>
</form>
<h5 id="login-status"><br></h5>
<div id="legal-stuff">
<h5>DISCLAIMER</h5>
<p>
Copyright (C) 2023 Veltro & Gang
</p>
<p>
DCS Olympus (the "MATERIAL" or "Software") is provided completely free
to users subject to the it under both the terms of version 3 of the GNU
General Public License as published by the Free Software Foundation, and
the additional terms set out below; except where such terms conflict with this
disclaimer, in which case, the terms of this disclaimer shall prevail.
</p>
<p>
The authors and/or copyright holders of the Software have not received any
financial benefit in connection with the Software. In any event, the
Software is provided “as is”, without warranty of any kind, express or
implied, including but not limited to the warranties of merchantability,
fitness for a particular purpose and non-infringement. In no event shall
the authors and/or copyright holders be liable for any claim, damages or
other liability, whether in an action of contract, tort or otherwise,
arising from, out of or in connection with the Software or the use or o
ther dealings in the Software.
</p>
<p>
Any party making use of the Software in any manner agrees to be
bound by the terms set out in this disclaimer, version 3 of the GNU
General Public Licence, and the Additional Terms below.
</p>
<p>
THIS MATERIAL IS NOT MADE OR SUPPORTED BY EAGLE DYNAMICS SA.
</p>
</div>
</div>
</div>
<div id="advanced-settings-dialog" class="ol-panel ol-dialog olympus-dialog-close hide" oncontextmenu="return false;">
<div class="ol-dialog-close" data-on-click="closeDialog"></div>
<div class="ol-dialog-header">
<h3 id="unit-name">Olympus 1-1</h3>
</div>
<div class="ol-dialog-content">
<!-- General settings -->
<div id="general-settings">
<div class="ol-group">
<h4>General settings</h4>
<hr>
</div>
<div id="general-settings-grid">
<div id="prohibit-jettison-checkbox" class="ol-checkbox air-unit-checkbox">
<label title="The unit will not jettison external stores">
<input type="checkbox"/>
Prohibit jettison
</label>
</div>
<div id="prohibit-afterburner-checkbox" class="ol-checkbox air-unit-checkbox">
<label title="The unit will not engage the afterburner">
<input type="checkbox" />
Prohibit afterburner
</label>
</div>
<div id="prohibit-AA-checkbox" class="ol-checkbox air-unit-checkbox">
<label title="The unit will not engage airborne targets">
<input type="checkbox" />
Prohibit A/A
</label>
</div>
<div id="prohibit-AG-checkbox" class="ol-checkbox air-unit-checkbox">
<label title="The unit will not engage ground targets">
<input type="checkbox" />
Prohibit A/G
</label>
</div>
<div id="prohibit-air-wpn-checkbox" class="ol-checkbox">
<label title="The unit will not engage air weapons (e.g. SAM sites will not engage HARMs)">
<input type="checkbox" />
Prohibit air wpn engage
</label>
</div>
</div>
</div>
<!-- TACAN options -->
<div id="TACAN-options">
<div class="ol-group">
<h4>TACAN options</h4>
<hr>
</div>
<div id="TACAN-checkbox" class="ol-checkbox">
<label title="Turn ON the TACAN">
<input type="checkbox" />
Activate TACAN
</label>
</div>
<div class="ol-group">
<label>TACAN: </label>
<div class="ol-group">
<div id="TACAN-channel" class="ol-text-input">
<input type="number" onkeypress='return event.charCode >= 48 && event.charCode <= 57' onkeyup="if (value > 126) value = 126;">
</div>
<div id="TACAN-XY" class="ol-select">
<div class="ol-select-value">X</div>
<div class="ol-select-options">
</div>
</div>
<div id="TACAN-callsign" class="ol-text-input">
<input type="text" maxlength="3" value="TKR" style="width: 50px">
</div>
</div>
</div>
</div>
<!-- Radio options -->
<div id="radio-options">
<div class="ol-group">
<h4>Radio options</h4>
<hr>
</div>
<div class="ol-group">
<label> Radio frequency: </label>
<div class="ol-group">
<div id="radio-mhz" class="ol-text-input">
<input type="number" onkeypress='return event.charCode >= 48 && event.charCode <= 57' onkeyup="if (value > 999) value = 999;">
</div>
<div id="radio-decimals" class="ol-select">
<div class="ol-select-value">.000</div>
<div class="ol-select-options">
</div>
</div>
</div>
</div>
<div class="ol-group">
<label> Radio callsign: </label>
<div class="ol-group">
<div id="radio-callsign" class="ol-select">
<div class="ol-select-value"></div>
<div class="ol-select-options">
</div>
</div>
<label>
-
</label>
<div id="radio-callsign-number" class="ol-text-input">
<input type="number" min="1" max="999" step="1" value="1">
</div>
</div>
</div>
</div>
</div>
<div class="ol-dialog-footer ol-group">
<button class="ol-button-apply" data-on-click="applyAdvancedSettings">Apply</button>
<button class="ol-button-close" data-on-click="closeDialog">Close</button>
</div>
</div>
<div id="custom-formation-dialog" class="ol-panel ol-dialog olympus-dialog-close hide" oncontextmenu="return false;">
<div class="ol-dialog-close" data-on-click="closeDialog"></div>
<div class="ol-dialog-header">
<h3 id="unit-name">Custom formation</h3>
</div>
<div class="ol-dialog-content">
<div class="formation-position-clock">
<div class="clock-hand" style="top: 0px; left: 50px;"><input type="radio" id="formation-1" name="formation-position" value="1"></div>
<div class="clock-hand" style="top: 14px; left: 14px;"><input type="radio" id="formation-2" name="formation-position" value="2"></div>
<div class="clock-hand" style="top: 50px; left: 0px; "><input type="radio" id="formation-3" name="formation-position" value="3"></div>
<div class="clock-hand" style="top: 86px; left: 14px;"><input type="radio" id="formation-4" name="formation-position" value="4"></div>
<div class="clock-hand" style="top: 100px; left: 50px;"><input type="radio" id="formation-5" name="formation-position" value="5"></div>
<div class="clock-hand" style="top: 86px; left: 86px;"><input type="radio" id="formation-6" name="formation-position" value="6" checked></div>
<div class="clock-hand" style="top: 50px; left: 100px"><input type="radio" id="formation-7" name="formation-position" value="7"></div>
<div class="clock-hand" style="top: 14px; left: 86px;"><input type="radio" id="formation-8" name="formation-position" value="8"></div>
<div style="top: 50px; left: 50px;" id="reference-system"></div>
</div>
<div class="ol-group">
<label>Distance: </label>
<div class="ol-group">
<div id="distance" class="ol-text-input">
<input type="number" min="0" max="999999" step="1" value="150">
</div>
<label>ft</label>
</div>
</div>
<div class="ol-group">
<label>Up-down: </label>
<div class="ol-group">
<div id="up-down" class="ol-text-input">
<input type="number" min="-99999" max="99999" step="1" value="30">
</div>
<label>ft</label>
</div>
</div>
</div>
<div class="ol-dialog-footer ol-group">
<button class="ol-button-apply" data-on-click="applyCustomFormation">Apply</button>
<button class="ol-button-close" data-on-click="closeDialog">Close</button>
</div>
</div>
<div id="command-mode-settings-dialog" class="ol-panel ol-dialog olympus-dialog-close hide" oncontextmenu="return false;">
<div class="ol-dialog-close" data-on-click="closeDialog"></div>
<div class="ol-dialog-header">
<h3 id="unit-name">Command mode settings</h3>
</div>
<div class="ol-dialog-content">
<div id="restrict-spawns" class="ol-checkbox">
<label title="If false, no spawn restrictions will be applied">
<input type="checkbox"/>
Restrict spawns
</label>
</div>
<div id="restrict-to-coalition" class="ol-checkbox">
<label title="If true, commanders will be allowed to only spawn units that belong to their coalition. E.g. blue commanders will be able to spawn F/A-18 Hornets, but not MiG-29s.">
<input type="checkbox"/>
Restrict units to coalition
</label>
</div>
<div class="ol-group">
<label>Setup time: </label>
<div class="ol-group">
<div id="setup-time" class="ol-text-input">
<input type="number" min="-99999" max="99999" step="1" value="10">
</div>
<label>minutes</label>
</div>
</div>
<div class="ol-group">
<label>Available eras: </label>
<div id="command-mode-era-options" class="ol-select">
<div class="ol-select-value">Select eras</div>
<div class="ol-select-options">
<!-- This is where all the available era buttons will be shown-->
</div>
</div>
</div>
<div class="ol-group">
<h4>Spawn points</h4>
<hr>
</div>
<div class="ol-group">
<label>Blue spawn points: </label>
<div id="blue-spawn-points" class="ol-text-input">
<input type="number" min="0" max="999999" step="1" value="1000">
</div>
</div>
<div class="ol-group">
<label>Red spawn points: </label>
<div id="red-spawn-points" class="ol-text-input">
<input type="number" min="0" max="999999" step="1" value="1000">
</div>
</div>
</div>
<div class="ol-dialog-footer ol-group">
<button class="ol-button-apply" data-on-click="applycommandModeOptions">Apply</button>
<button class="ol-button-close" data-on-click="closeDialog">Close</button>
</div>
</div>
<div id="slow-delete-dialog" class="ol-panel ol-dialog hide" oncontextmenu="return false;">
<div class="ol-dialog-header">
<h3 id="unit-name">Confirm deletion method</h3>
</div>
<div class="ol-dialog-content">
<p>You are trying to delete a large amount of units (<span class="deletion-count"></span>), which can cause the server to lag for players.</p>
<p>You may:
<ul>
<li>delete in batches (less lag but Olympus cannot process any additional orders until<br /> all units have been deleted);</li>
<li>delete immediately (you can continue to give Olympus orders but players may<br />experience lag while this happens);</li>
<li>cancel this instruction.</li>
</ul></p>
</div>
<div class="ol-dialog-footer ol-group">
<button data-action="delete-slow">Delete in batches (~<span class="deletion-time"></span>s)</button>
<button data-action="delete-immediate">Delete immediately</button>
<button data-action="delete-cancel">Cancel</button>
</div>
</div>
<%- include('dialogs/advancedsettings.ejs') %>
<%- include('dialogs/commandmodesettings.ejs') %>
<%- include('dialogs/customformation.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",
"showFilenameInput": true
}) %>
<%- include('dialogs/importexport.ejs', {
"dialogId": "unit-import-dialog",
"submitButtonText": "Import units into mission",
"textContent": "Select the unit categories you would like to import.",
"title": "Import",
"showFilenameInput": false
}) %>
<%- include('dialogs/slowdelete.ejs') %>
<%- include('dialogs/splash.ejs') %>

View File

@ -0,0 +1,162 @@
<div id="advanced-settings-dialog" class="ol-panel ol-dialog olympus-dialog-close hide" oncontextmenu="return false;">
<div class="ol-dialog-close" data-on-click="closeDialog"></div>
<div class="ol-dialog-header">
<h3 id="unit-name">Olympus 1-1</h3>
</div>
<div class="ol-dialog-content">
<!-- General settings -->
<div id="general-settings">
<div class="ol-group">
<h4>General settings</h4>
<hr>
</div>
<div id="general-settings-grid">
<div id="prohibit-jettison-checkbox" class="ol-checkbox air-unit-checkbox">
<label title="The unit will not jettison external stores">
<input type="checkbox"/>
Prohibit jettison
</label>
</div>
<div id="prohibit-afterburner-checkbox" class="ol-checkbox air-unit-checkbox">
<label title="The unit will not engage the afterburner">
<input type="checkbox" />
Prohibit afterburner
</label>
</div>
<div id="prohibit-AA-checkbox" class="ol-checkbox air-unit-checkbox">
<label title="The unit will not engage airborne targets">
<input type="checkbox" />
Prohibit A/A
</label>
</div>
<div id="prohibit-AG-checkbox" class="ol-checkbox air-unit-checkbox">
<label title="The unit will not engage ground targets">
<input type="checkbox" />
Prohibit A/G
</label>
</div>
<div id="prohibit-air-wpn-checkbox" class="ol-checkbox">
<label title="The unit will not engage air weapons (e.g. SAM sites will not engage HARMs)">
<input type="checkbox" />
Prohibit air wpn engage
</label>
</div>
</div>
</div>
<!-- Tasking -->
<!--
<div id="tasking">
<div class="ol-group">
<h4>Tasking</h4>
<hr>
</div>
<div id="tanker-checkbox" class="ol-checkbox">
<label title="The unit will operate as Air to Air Refuelling tanker for airplanes that have a compatible refuelling system">
<input type="checkbox" />
Operate as AAR tanker
</label>
</div>
<div id="AWACS-checkbox" class="ol-checkbox">
<label title="The unit will operate as AWACS on datalink">
<input type="checkbox" />
Operate as AWACS
</label>
</div>
</div>
-->
<!-- TACAN options -->
<div id="TACAN-options">
<div class="ol-group">
<h4>TACAN options</h4>
<hr>
</div>
<div id="TACAN-checkbox" class="ol-checkbox">
<label title="Turn ON the TACAN">
<input type="checkbox" />
Activate TACAN
</label>
</div>
<div class="ol-group">
<label>TACAN: </label>
<div class="ol-group">
<div id="TACAN-channel" class="ol-text-input">
<input type="number" min="1" max="126" step="1" value="40">
</div>
<div id="TACAN-XY" class="ol-select">
<div class="ol-select-value">X</div>
<div class="ol-select-options">
</div>
</div>
<div id="TACAN-callsign" class="ol-text-input">
<input type="text" maxlength="3" value="TKR" style="width: 50px">
</div>
</div>
</div>
</div>
<!-- Radio options -->
<div id="radio-options">
<div class="ol-group">
<h4>Radio options</h4>
<hr>
</div>
<div class="ol-group">
<label> Radio frequency: </label>
<div class="ol-group">
<div id="radio-mhz" class="ol-text-input">
<input type="number" min="1" max="999" step="1" value="260">
</div>
<div id="radio-decimals" class="ol-select">
<div class="ol-select-value">.000</div>
<div class="ol-select-options">
</div>
</div>
</div>
</div>
<div class="ol-group">
<label> Radio callsign: </label>
<div class="ol-group">
<div id="radio-callsign" class="ol-select">
<div class="ol-select-value"></div>
<div class="ol-select-options">
</div>
</div>
<label>
-
</label>
<div id="radio-callsign-number" class="ol-text-input">
<input type="number" min="1" max="999" step="1" value="1">
</div>
</div>
</div>
</div>
</div>
<div class="ol-dialog-footer ol-group">
<button class="ol-button-apply" data-on-click="applyAdvancedSettings">Apply</button>
<button class="ol-button-close" data-on-click="closeDialog">Close</button>
</div>
</div>

View File

@ -0,0 +1,70 @@
<div id="command-mode-settings-dialog" class="ol-panel ol-dialog olympus-dialog-close hide" oncontextmenu="return false;">
<div class="ol-dialog-close" data-on-click="closeDialog"></div>
<div class="ol-dialog-header">
<h3 id="unit-name">Command mode settings</h3>
</div>
<div class="ol-dialog-content">
<div id="restrict-spawns" class="ol-checkbox">
<label title="If false, no spawn restrictions will be applied">
<input type="checkbox"/>
Restrict spawns
</label>
</div>
<div id="restrict-to-coalition" class="ol-checkbox">
<label title="If true, commanders will be allowed to only spawn units that belong to their coalition. E.g. blue commanders will be able to spawn F/A-18 Hornets, but not MiG-29s.">
<input type="checkbox"/>
Restrict units to coalition
</label>
</div>
<div class="ol-group">
<label>Setup time: </label>
<div class="ol-group">
<div id="setup-time" class="ol-text-input">
<input type="number" min="-99999" max="99999" step="1" value="10">
</div>
<label>minutes</label>
</div>
</div>
<div class="ol-group">
<label>Available eras: </label>
<div id="command-mode-era-options" class="ol-select">
<div class="ol-select-value">Select eras</div>
<div class="ol-select-options">
<!-- This is where all the available era buttons will be shown-->
</div>
</div>
</div>
<div class="ol-group">
<h4>Spawn points</h4>
<hr>
</div>
<div class="ol-group">
<label>Blue spawn points: </label>
<div id="blue-spawn-points" class="ol-text-input">
<input type="number" min="0" max="999999" step="1" value="1000">
</div>
</div>
<div class="ol-group">
<label>Red spawn points: </label>
<div id="red-spawn-points" class="ol-text-input">
<input type="number" min="0" max="999999" step="1" value="1000">
</div>
</div>
</div>
<div class="ol-dialog-footer ol-group">
<button class="ol-button-apply" data-on-click="applycommandModeOptions">Apply</button>
<button class="ol-button-close" data-on-click="closeDialog">Close</button>
</div>
</div>

View File

@ -0,0 +1,48 @@
<div id="custom-formation-dialog" class="ol-panel ol-dialog olympus-dialog-close hide" oncontextmenu="return false;">
<div class="ol-dialog-close" data-on-click="closeDialog"></div>
<div class="ol-dialog-header">
<h3 id="unit-name">Custom formation</h3>
</div>
<div class="ol-dialog-content">
<div class="formation-position-clock">
<div class="clock-hand" style="top: 0px; left: 50px;"><input type="radio" id="formation-1" name="formation-position" value="1"></div>
<div class="clock-hand" style="top: 14px; left: 14px;"><input type="radio" id="formation-2" name="formation-position" value="2"></div>
<div class="clock-hand" style="top: 50px; left: 0px; "><input type="radio" id="formation-3" name="formation-position" value="3"></div>
<div class="clock-hand" style="top: 86px; left: 14px;"><input type="radio" id="formation-4" name="formation-position" value="4"></div>
<div class="clock-hand" style="top: 100px; left: 50px;"><input type="radio" id="formation-5" name="formation-position" value="5"></div>
<div class="clock-hand" style="top: 86px; left: 86px;"><input type="radio" id="formation-6" name="formation-position" value="6" checked></div>
<div class="clock-hand" style="top: 50px; left: 100px"><input type="radio" id="formation-7" name="formation-position" value="7"></div>
<div class="clock-hand" style="top: 14px; left: 86px;"><input type="radio" id="formation-8" name="formation-position" value="8"></div>
<div style="top: 50px; left: 50px;" id="reference-system"></div>
</div>
<div class="ol-group">
<label>Distance: </label>
<div class="ol-group">
<div id="distance" class="ol-text-input">
<input type="number" min="0" max="999999" step="1" value="150">
</div>
<label>ft</label>
</div>
</div>
<div class="ol-group">
<label>Up-down: </label>
<div class="ol-group">
<div id="up-down" class="ol-text-input">
<input type="number" min="-99999" max="99999" step="1" value="30">
</div>
<label>ft</label>
</div>
</div>
</div>
<div class="ol-dialog-footer ol-group">
<button class="ol-button-apply" data-on-click="applyCustomFormation">Apply</button>
<button class="ol-button-close" data-on-click="closeDialog">Close</button>
</div>
</div>

View File

@ -0,0 +1,29 @@
<div id="<%= dialogId %>" class="ol-panel ol-dialog file-import-export hide" oncontextmenu="return false;">
<div class="ol-dialog-header">
<h3><%= title %></h3>
</div>
<div class="ol-dialog-content">
<p><%= textContent %></p>
<% if (showFilenameInput) { %>
<div class="export-filename-container">
<label>Filename:</label>
<input id="export-filename">
<img src="resources/theme/images/icons/keyboard-solid.svg">
</div>
<% } %>
<table class="categories-coalitions">
<thead>
</thead>
<tbody>
</tbody>
</table>
</div>
<div class="ol-dialog-footer ol-group">
<button class="start-transfer"><%= submitButtonText %></button>
<button data-on-click="closeDialog">Close</button>
</div>
</div>

View File

@ -0,0 +1,21 @@
<div id="slow-delete-dialog" class="ol-panel ol-dialog hide" oncontextmenu="return false;">
<div class="ol-dialog-header">
<h3>Confirm deletion method</h3>
</div>
<div class="ol-dialog-content">
<p>You are trying to delete a large amount of units (<span class="deletion-count"></span>), which can cause the server to lag for players.</p>
<p>You may:
<ul>
<li>delete in batches (less lag but Olympus cannot process any additional orders until<br /> all units have been deleted);</li>
<li>delete immediately (you can continue to give Olympus orders but players may<br />experience lag while this happens);</li>
<li>cancel this instruction.</li>
</ul></p>
</div>
<div class="ol-dialog-footer ol-group">
<button data-action="delete-slow">Delete in batches (~<span class="deletion-time"></span>s)</button>
<button data-action="delete-immediate">Delete immediately</button>
<button data-action="delete-cancel">Cancel</button>
</div>
</div>

View File

@ -0,0 +1,50 @@
<div id="splash-screen" class="ol-dialog" oncontextmenu="return false;">
<div id="splash-content" class="ol-dialog-content">
<div id="app-summary">
<h2>DCS Olympus</h2>
<h4>Dynamic Unit Command</h4>
<div class="app-version">Version <span class="app-version-number">v0.4.8-alpha</span></div>
</div>
<form id="authentication-form">
<div><h5>Username</h5> <input type="text" id="username" name="username" required autocomplete="username" placeholder="Enter username..."></div>
<div><h5>Password</h5> <input type="password" id="password" name="password" minlength="8" required autocomplete="current-password" placeholder="Enter password..."></div>
<button type="submit" id="connection-button" class="ol-button-apply">Connect</button>
</form>
<h5 id="login-status"><br></h5>
<div id="legal-stuff">
<h5>DISCLAIMER</h5>
<p>
Copyright (C) 2023 Veltro & Gang
</p>
<p>
DCS Olympus (the "MATERIAL" or "Software") is provided completely free
to users subject to the it under both the terms of version 3 of the GNU
General Public License as published by the Free Software Foundation, and
the additional terms set out below; except where such terms conflict with this
disclaimer, in which case, the terms of this disclaimer shall prevail.
</p>
<p>
The authors and/or copyright holders of the Software have not received any
financial benefit in connection with the Software. In any event, the
Software is provided “as is”, without warranty of any kind, express or
implied, including but not limited to the warranties of merchantability,
fitness for a particular purpose and non-infringement. In no event shall
the authors and/or copyright holders be liable for any claim, damages or
other liability, whether in an action of contract, tort or otherwise,
arising from, out of or in connection with the Software or the use or o
ther dealings in the Software.
</p>
<p>
Any party making use of the Software in any manner agrees to be
bound by the terms set out in this disclaimer, version 3 of the GNU
General Public Licence, and the Additional Terms below.
</p>
<p>
THIS MATERIAL IS NOT MADE OR SUPPORTED BY EAGLE DYNAMICS SA.
</p>
</div>
</div>
</div>