From de14f6c738f4a533a6c8ac1d737b3de3449d2cce Mon Sep 17 00:00:00 2001 From: PeekabooSteam Date: Fri, 22 Dec 2023 15:11:59 +0000 Subject: [PATCH 1/3] Airbase data being validated --- client/package.json | 1 + .../public/databases/airbases/sinaimap.json | 4 +- client/public/stylesheets/leaflet/leaflet.css | 7 +- client/src/olympusapp.ts | 30 +++++++++ client/src/schemas/airbases.schema.json | 67 +++++++++++++++++++ client/src/schemas/schema.ts | 51 ++++++++++++++ 6 files changed, 152 insertions(+), 8 deletions(-) create mode 100644 client/src/schemas/airbases.schema.json create mode 100644 client/src/schemas/schema.ts diff --git a/client/package.json b/client/package.json index 4e1de078..47eac41c 100644 --- a/client/package.json +++ b/client/package.json @@ -47,6 +47,7 @@ "@types/node": "^18.16.1", "@types/sortablejs": "^1.15.0", "@types/svg-injector": "^0.0.29", + "ajv": "^8.12.0", "babelify": "^10.0.0", "browserify": "^17.0.0", "concurrently": "^7.6.0", diff --git a/client/public/databases/airbases/sinaimap.json b/client/public/databases/airbases/sinaimap.json index 87f5bae6..25850239 100644 --- a/client/public/databases/airbases/sinaimap.json +++ b/client/public/databases/airbases/sinaimap.json @@ -1037,7 +1037,7 @@ "01R": { "magHeading": "11", "Heading": "16", - "ILS": "109.7" + "ILS": "109.70" } }, { @@ -1366,7 +1366,7 @@ "length": "9536" } ], - "TACAN": "0X", + "TACAN": "", "ICAO": "HECW", "elevation": "439" } diff --git a/client/public/stylesheets/leaflet/leaflet.css b/client/public/stylesheets/leaflet/leaflet.css index 9ade8dc4..1981009f 100644 --- a/client/public/stylesheets/leaflet/leaflet.css +++ b/client/public/stylesheets/leaflet/leaflet.css @@ -60,11 +60,6 @@ padding: 0; } -.leaflet-container img.leaflet-tile { - /* See: https://bugs.chromium.org/p/chromium/issues/detail?id=600120 */ - mix-blend-mode: plus-lighter; -} - .leaflet-container.leaflet-touch-zoom { -ms-touch-action: pan-x pan-y; touch-action: pan-x pan-y; @@ -651,7 +646,7 @@ svg.leaflet-image-layer.leaflet-interactive path { } /* Printing */ - + @media print { /* Prevent printers from removing background-images of controls. */ .leaflet-control { diff --git a/client/src/olympusapp.ts b/client/src/olympusapp.ts index aa1d9d7e..0f38ebfb 100644 --- a/client/src/olympusapp.ts +++ b/client/src/olympusapp.ts @@ -18,6 +18,7 @@ import { Manager } from "./other/manager"; import { SVGInjector } from "@tanem/svg-injector"; import { ServerManager } from "./server/servermanager"; import { sha256 } from 'js-sha256'; +import Ajv from "ajv" import { BLUE_COMMANDER, FILL_SELECTED_RING, GAME_MASTER, HIDE_UNITS_SHORT_RANGE_RINGS, RED_COMMANDER, SHOW_UNITS_ACQUISITION_RINGS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNIT_LABELS } from "./constants/constants"; import { aircraftDatabase } from "./unit/databases/aircraftdatabase"; @@ -27,6 +28,8 @@ import { navyUnitDatabase } from "./unit/databases/navyunitdatabase"; import { UnitListPanel } from "./panels/unitlistpanel"; import { ContextManager } from "./context/contextmanager"; import { Context } from "./context/context"; +import { AirDefenceUnitSpawnMenu } from "./controls/unitspawnmenu"; +import { AirbasesJSONSchemaValidator } from "./schemas/schema"; var VERSION = "{{OLYMPUS_VERSION_NUMBER}}"; @@ -193,6 +196,9 @@ export class OlympusApp { this.#unitsManager = new UnitsManager(); this.#weaponsManager = new WeaponsManager(); + /* Validate data */ + this.#validateData(); + // Toolbars this.getToolbarsManager().add("primaryToolbar", new PrimaryToolbar("primary-toolbar")) .add("commandModeToolbar", new CommandModeToolbar("command-mode-toolbar")); @@ -447,4 +453,28 @@ export class OlympusApp { img.addEventListener("load", () => { SVGInjector(img); }); }) } + + #validateData() { + + const airbasesValidator = new AirbasesJSONSchemaValidator(); + + /* + const validator = new Ajv(); + const schema = { + type: "object", + properties: { + foo: {type: "integer"}, + bar: {type: "string"}, + }, + required: ["foo"], + additionalProperties: false, + } + + const data = this.#getRunwayData(); + + const validate = validator.compile(schema); + const valid = validate(data); + if (!valid) console.log(validate.errors); + //*/ + } } \ No newline at end of file diff --git a/client/src/schemas/airbases.schema.json b/client/src/schemas/airbases.schema.json new file mode 100644 index 00000000..2a47a001 --- /dev/null +++ b/client/src/schemas/airbases.schema.json @@ -0,0 +1,67 @@ +{ + "type": "object", + "additionalProperties": false, + "properties": { + "airfields": { + "type": "object", + "minProperties": 1, + "patternProperties": { + ".*": { + "type": "object", + "properties": { + "elevation": { + "type": "string", + "pattern": "^(0|([1-9][0-9]*))?$" + }, + "ICAO": { + "type": "string" + }, + "runways": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "headings": { + "type": "array", + "items": { + "type": "object", + "patternProperties": { + ".*": { + "type": "object", + "properties": { + "ILS": { + "type": "string", + "pattern": "^(1[0-9]{1,2}\\.[0-9][05])?$" + }, + "magHeading": { + "type": "string", + "pattern": "^([0-2][0-9]{2})|(3[0-6][0-9])?$" + } + }, + "required": ["magHeading"] + } + } + }, + "minItems": 1 + }, + "length": { + "type": "string", + "pattern": "^[1-9][0-9]{3,4}$" + } + }, + "required": [ "headings", "length" ] + } + }, + "TACAN": { + "type": "string", + "pattern": "^([1-9][0-9]{1,2}X)?$" + } + }, + "required": [ "elevation", "runways" ] + } + } + } + }, + "required": ["airfields"] +} \ No newline at end of file diff --git a/client/src/schemas/schema.ts b/client/src/schemas/schema.ts new file mode 100644 index 00000000..8f4a33b6 --- /dev/null +++ b/client/src/schemas/schema.ts @@ -0,0 +1,51 @@ +import Ajv, { JSONSchemaType } from "ajv"; +import { AnySchemaObject, Schema } from "ajv/dist/core"; + + +// For future extension +abstract class JSONSchemaValidator { + + #schema!:AnySchemaObject; + + constructor( schema:AnySchemaObject ) { + this.#schema = schema; + } + + getSchema() { + return this.#schema; + } + +} + + +export class AirbasesJSONSchemaValidator extends JSONSchemaValidator { + + constructor( ) { + + const schema = require("../schemas/airbases.schema.json"); + + super( schema ); + + const ajv = new Ajv({ + "allErrors": true + }); + + [ + require( "../../public/databases/airbases/caucasus.json" ), + require( "../../public/databases/airbases/falklands.json" ), + require( "../../public/databases/airbases/marianas.json" ), + require( "../../public/databases/airbases/nevada.json" ), + require( "../../public/databases/airbases/normandy.json" ), + require( "../../public/databases/airbases/persiangulf.json" ), + require( "../../public/databases/airbases/sinaimap.json" ), + require( "../../public/databases/airbases/syria.json" ), + require( "../../public/databases/airbases/thechannel.json" ) + ].forEach( data => { + const validate = ajv.compile(this.getSchema()); + const valid = validate(data); + + if (!valid) console.error(validate.errors); + }); + } + +} \ No newline at end of file From 955057183d9247ecaec8e387af99a95fea748445 Mon Sep 17 00:00:00 2001 From: PeekabooSteam Date: Wed, 27 Dec 2023 16:26:31 +0000 Subject: [PATCH 2/3] Added JSON validation for imports. --- .../public/databases/airbases/sinaimap.json | 2 +- client/src/schemas/airbases.schema.json | 2 +- client/src/schemas/importdata.schema.json | 377 ++++++++++++++++++ client/src/schemas/schema.ts | 47 ++- .../unit/importexport/unitdatafileexport.ts | 4 + .../unit/importexport/unitdatafileimport.ts | 92 +++-- client/views/other/dialogs/importexport.ejs | 50 ++- 7 files changed, 503 insertions(+), 71 deletions(-) create mode 100644 client/src/schemas/importdata.schema.json diff --git a/client/public/databases/airbases/sinaimap.json b/client/public/databases/airbases/sinaimap.json index 25850239..c1f87765 100644 --- a/client/public/databases/airbases/sinaimap.json +++ b/client/public/databases/airbases/sinaimap.json @@ -1366,7 +1366,7 @@ "length": "9536" } ], - "TACAN": "", + "TACAN": "0X", "ICAO": "HECW", "elevation": "439" } diff --git a/client/src/schemas/airbases.schema.json b/client/src/schemas/airbases.schema.json index 2a47a001..c43f57d3 100644 --- a/client/src/schemas/airbases.schema.json +++ b/client/src/schemas/airbases.schema.json @@ -36,7 +36,7 @@ }, "magHeading": { "type": "string", - "pattern": "^([0-2][0-9]{2})|(3[0-6][0-9])?$" + "pattern": "^([0-2][0-9]{2})|(3(([0-5][0-9])|(60)))?$" } }, "required": ["magHeading"] diff --git a/client/src/schemas/importdata.schema.json b/client/src/schemas/importdata.schema.json new file mode 100644 index 00000000..7790f3b2 --- /dev/null +++ b/client/src/schemas/importdata.schema.json @@ -0,0 +1,377 @@ +{ + "$defs": { + "coalitionName": { + "enum": [ + "blue", + "neutral", + "red" + ], + "type": "string" + }, + "lat": { + "maximum": 90, + "minimum": -90, + "type": "number" + }, + "lng": { + "maximum": 180, + "minimum": -180, + "type": "number" + }, + "vec2": { + "additionalProperties": false, + "properties": { + "lat": { + "$ref": "#/$defs/lat" + }, + "lng": { + "$ref": "#/$defs/lng" + } + }, + "required": [ + "lat", + "lng" + ], + "type": "object" + }, + "vec3": { + "additionalProperties": false, + "properties": { + "alt": { + "type": "number" + }, + "lat": { + "$ref": "#/$defs/lat" + }, + "lng": { + "$ref": "#/$defs/lng" + } + }, + "required": [ + "alt", + "lat", + "lng" + ], + "type": "object" + } + }, + "patternProperties": { + ".*": { + "items": { + "additionalProperties": false, + "properties": { + "activePath": { + "items": { + "$ref": "#/$defs/vec3" + }, + "type": "array" + }, + "alive": { + "type": "boolean" + }, + "ammo": { + "items": { + "additionalProperties": false, + "properties": { + "category": { + "minimum": 0, + "type": "number" + }, + "guidance": { + "minimum": 0, + "type": "number" + }, + "missileCategory": { + "minimum": 0, + "type": "number" + }, + "name": { + "minLength": 3, + "type": "string" + }, + "quantity": { + "minimum": 0, + "type": "number" + } + }, + "required": [ + "quantity", + "name", + "guidance", + "category", + "missileCategory" + ], + "type": "object" + }, + "type": "array" + }, + "category": { + "type": "string" + }, + "categoryDisplayName": { + "type": "string" + }, + "coalition": { + "$ref": "#/$defs/coalitionName" + }, + "contacts": { + "type": "array" + }, + "controlled": { + "type": "boolean" + }, + "country": { + "type": "number" + }, + "desiredAltitude": { + "minimum": 0, + "type": "number" + }, + "desiredAltitudeType": { + "enum": [ + "AGL", + "ASL" + ], + "type": "string" + }, + "desiredSpeed": { + "minimum": 0, + "type": "number" + }, + "desiredSpeedType": { + "enum": [ + "CAS", + "GS" + ], + "type": "string" + }, + "emissionsCountermeasures": { + "enum": [ + "attac", + "defend", + "free", + "silent" + ], + "type": "string" + }, + "followRoads": { + "type": "boolean" + }, + "formationOffset": { + "additionalProperties": false, + "properties": { + "x": { + "minimum": 0, + "type": "number" + }, + "y": { + "minimum": 0, + "type": "number" + }, + "z": { + "minimum": 0, + "type": "number" + } + }, + "required": [ + "x", + "y", + "z" + ], + "type": "object" + }, + "fuel": { + "maximum": 100, + "minimum": 0, + "type": "number" + }, + "generalSettings": { + "additionalProperties": false, + "properties": { + "prohibitAA": { + "type": "boolean" + }, + "prohibitAfterburner": { + "type": "boolean" + }, + "prohibitAG": { + "type": "boolean" + }, + "prohibitAirWpn": { + "type": "boolean" + }, + "prohibitJettison": { + "type": "boolean" + } + }, + "required": [ + "prohibitAA", + "prohibitAfterburner", + "prohibitAG", + "prohibitAirWpn", + "prohibitJettison" + ], + "type": "object" + }, + "groupName": { + "type": "string" + }, + "hasTask": { + "type": "boolean" + }, + "heading": { + "type": "number" + }, + "health": { + "maximum": 100, + "minimum": 0, + "type": "number" + }, + "horizontalVelocity": { + "minimum": 0, + "type": "number" + }, + "human": { + "type": "boolean" + }, + "ID": { + "type": "number" + }, + "isActiveAWACS": { + "type": "boolean" + }, + "isActiveTanker": { + "type": "boolean" + }, + "isLeader": { + "type": "boolean" + }, + "leaderID": { + "minimum": 0, + "type": "number" + }, + "name": { + "type": "string" + }, + "onOff": { + "type": "boolean" + }, + "operateAs": { + "$ref": "#/$defs/coalitionName" + }, + "position": { + "$ref": "#/$defs/vec3" + }, + "radio": { + "additionalProperties": false, + "properties": { + "callsign": { + "type": "number" + }, + "callsignNumber": { + "type": "number" + }, + "frequency": { + "type": "number" + } + }, + "required": [ + "frequency", + "callsign", + "callsignNumber" + ], + "type": "object" + }, + "reactionToThreat": { + "enum": [ + "evade", + "maneouvre", + "none", + "passive" + ], + "type": "string" + }, + "ROE": { + "enum": [ + "designated", + "free", + "hold", + "return" + ], + "type": "string" + }, + "shotsIntensity": { + "maximum": 3, + "minimum": 1, + "type": "number" + }, + "shotsScatter": { + "maximum": 3, + "minimum": 1, + "type": "number" + }, + "speed": { + "minimum": 0, + "type": "number" + }, + "state": { + "type": "string" + }, + "TACAN": { + "properties": { + "callsign": { + "type": "string" + }, + "channel": { + "minimum": 0, + "type": "number" + }, + "isOn": { + "type": "boolean" + }, + "XY": { + "enum": [ + "X", + "Y" + ], + "type": "string" + } + }, + "required": [ + "callsign", + "channel", + "isOn", + "XY" + ], + "type": "object" + }, + "targetID": { + "minimum": 0, + "type": "number" + }, + "targetPosition": { + "$ref": "#/$defs/vec2" + }, + "task": { + "type": "string" + }, + "track": { + "type": "number" + }, + "unitName": { + "type": "string" + }, + "verticalVelocity": { + "minimum": 0, + "type": "number" + } + }, + "type": "object", + "required": ["activePath","alive","ammo","category","categoryDisplayName","coalition","contacts","controlled","country","desiredAltitude","desiredAltitudeType","desiredSpeed","desiredSpeedType","emissionsCountermeasures","followRoads","formationOffset","fuel","generalSettings","groupName","hasTask","heading","health","horizontalVelocity","human","ID","isActiveAWACS","isActiveTanker","isLeader","leaderID","name","onOff","operateAs","position","radio","reactionToThreat","ROE","shotsIntensity","shotsScatter","speed","state","TACAN","targetID","targetPosition","task","track","unitName","verticalVelocity"] + }, + "minItems": 1, + "type": "array" + } + }, + "type": "object" +} \ No newline at end of file diff --git a/client/src/schemas/schema.ts b/client/src/schemas/schema.ts index 8f4a33b6..5ad4c527 100644 --- a/client/src/schemas/schema.ts +++ b/client/src/schemas/schema.ts @@ -1,34 +1,55 @@ -import Ajv, { JSONSchemaType } from "ajv"; -import { AnySchemaObject, Schema } from "ajv/dist/core"; +import Ajv from "ajv"; +import { AnySchemaObject } from "ajv/dist/core"; // For future extension abstract class JSONSchemaValidator { + #ajv:Ajv; + #compiledValidator:any; #schema!:AnySchemaObject; constructor( schema:AnySchemaObject ) { this.#schema = schema; + + this.#ajv = new Ajv({ + "allErrors": true + }); + + this.#compiledValidator = this.getAjv().compile(this.getSchema()); + + } + + getAjv() { + return this.#ajv; + } + + getCompiledValidator() { + return this.#compiledValidator; + } + + getErrors() { + return this.getCompiledValidator().errors; } getSchema() { return this.#schema; } + validate(data:any) { + return (this.getCompiledValidator())(data); + } + } export class AirbasesJSONSchemaValidator extends JSONSchemaValidator { - constructor( ) { + constructor() { const schema = require("../schemas/airbases.schema.json"); super( schema ); - - const ajv = new Ajv({ - "allErrors": true - }); [ require( "../../public/databases/airbases/caucasus.json" ), @@ -41,11 +62,21 @@ export class AirbasesJSONSchemaValidator extends JSONSchemaValidator { require( "../../public/databases/airbases/syria.json" ), require( "../../public/databases/airbases/thechannel.json" ) ].forEach( data => { - const validate = ajv.compile(this.getSchema()); + const validate = this.getAjv().compile(this.getSchema()); const valid = validate(data); if (!valid) console.error(validate.errors); }); } +} + + +export class ImportFileJSONSchemaValidator extends JSONSchemaValidator { + + constructor() { + const schema = require("../schemas/importdata.schema.json"); + super( schema ); + } + } \ No newline at end of file diff --git a/client/src/unit/importexport/unitdatafileexport.ts b/client/src/unit/importexport/unitdatafileexport.ts index 1bdb0d22..2a4f7a82 100644 --- a/client/src/unit/importexport/unitdatafileexport.ts +++ b/client/src/unit/importexport/unitdatafileexport.ts @@ -25,6 +25,10 @@ export class UnitDataFileExport extends UnitDataFile { * Show the form to start the export journey */ showForm(units: Unit[]) { + this.dialog.getElement().querySelectorAll("[data-on-error]").forEach((el:Element) => { + el.classList.toggle("hide", el.getAttribute("data-on-error") === "show"); + }); + const data: any = {}; const unitCanBeExported = (unit: Unit) => !["Aircraft", "Helicopter"].includes(unit.getCategory()); diff --git a/client/src/unit/importexport/unitdatafileimport.ts b/client/src/unit/importexport/unitdatafileimport.ts index 5a0e2221..9907f6f2 100644 --- a/client/src/unit/importexport/unitdatafileimport.ts +++ b/client/src/unit/importexport/unitdatafileimport.ts @@ -1,6 +1,7 @@ import { getApp } from "../.."; import { Dialog } from "../../dialog/dialog"; import { UnitData } from "../../interfaces"; +import { ImportFileJSONSchemaValidator } from "../../schemas/schema"; import { UnitDataFile } from "./unitdatafile"; export class UnitDataFileImport extends UnitDataFile { @@ -48,21 +49,59 @@ export class UnitDataFileImport extends UnitDataFile { unitsManager.spawnUnits(category, unitsToSpawn, coalition, false); } + } - /* - 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); + selectFile() { + var input = document.createElement("input"); + input.type = "file"; + input.addEventListener("change", (e: any) => { + var file = e.target.files[0]; + if (!file) { + return; } - } - //*/ + var reader = new FileReader(); + reader.onload = (e: any) => { + + try { + this.#fileData = JSON.parse(e.target.result); + + const validator = new ImportFileJSONSchemaValidator(); + if (!validator.validate(this.#fileData)) { + const errors = validator.getErrors().reduce((acc:any, error:any) => { + acc.push(error.instancePath + ": " + error.message) + return acc; + }, [] as string[]); + this.#showFileDataErrors(errors); + } else { + this.#showForm(); + } + } catch(e:any) { + this.#showFileDataErrors([e]); + } + }; + reader.readAsText(file); + }) + input.click(); + } + + #showFileDataErrors( reasons:string[]) { + + this.dialog.getElement().querySelectorAll("[data-on-error]").forEach((el:Element) => { + el.classList.toggle("hide", el.getAttribute("data-on-error") === "hide"); + }); + + const reasonsList = this.dialog.getElement().querySelector(".import-error-reasons"); + if (reasonsList instanceof HTMLElement) + reasonsList.innerHTML = `
  • ${reasons.join("
  • ")}
  • `; + + this.dialog.show(); } #showForm() { + this.dialog.getElement().querySelectorAll("[data-on-error]").forEach((el:Element) => { + el.classList.toggle("hide", el.getAttribute("data-on-error") === "show"); + }); + const data: any = {}; for (const [group, units] of Object.entries(this.#fileData)) { @@ -87,44 +126,11 @@ export class UnitDataFileImport extends UnitDataFile { } - /* - 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]); } diff --git a/client/views/other/dialogs/importexport.ejs b/client/views/other/dialogs/importexport.ejs index 3dfecc59..59e77431 100644 --- a/client/views/other/dialogs/importexport.ejs +++ b/client/views/other/dialogs/importexport.ejs @@ -4,26 +4,40 @@
    -

    <%= textContent %>

    - <% if (showFilenameInput) { %> -
    - - - -
    - <% } %> +
    +

    <%= textContent %>

    + + <% if (showFilenameInput) { %> +
    + + + +
    + <% } %> + + + + + + +
    +
    - - - - - -
    -
    +
    - + + +
    \ No newline at end of file From 82e60cd2cf4017162f2aeaeb60bbce0e66de1feb Mon Sep 17 00:00:00 2001 From: PeekabooSteam Date: Thu, 28 Dec 2023 14:10:51 +0000 Subject: [PATCH 3/3] Added more info for when enums aren't matched --- client/src/schemas/importdata.schema.json | 50 ++++++++++++++++++- .../unit/importexport/unitdatafileimport.ts | 8 ++- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/client/src/schemas/importdata.schema.json b/client/src/schemas/importdata.schema.json index 7790f3b2..bfb1943a 100644 --- a/client/src/schemas/importdata.schema.json +++ b/client/src/schemas/importdata.schema.json @@ -367,7 +367,55 @@ } }, "type": "object", - "required": ["activePath","alive","ammo","category","categoryDisplayName","coalition","contacts","controlled","country","desiredAltitude","desiredAltitudeType","desiredSpeed","desiredSpeedType","emissionsCountermeasures","followRoads","formationOffset","fuel","generalSettings","groupName","hasTask","heading","health","horizontalVelocity","human","ID","isActiveAWACS","isActiveTanker","isLeader","leaderID","name","onOff","operateAs","position","radio","reactionToThreat","ROE","shotsIntensity","shotsScatter","speed","state","TACAN","targetID","targetPosition","task","track","unitName","verticalVelocity"] + "required": [ + "activePath", + "alive", + "ammo", + "category", + "categoryDisplayName", + "coalition", + "contacts", + "controlled", + "country", + "desiredAltitude", + "desiredAltitudeType", + "desiredSpeed", + "desiredSpeedType", + "emissionsCountermeasures", + "followRoads", + "formationOffset", + "fuel", + "generalSettings", + "groupName", + "hasTask", + "heading", + "health", + "horizontalVelocity", + "human", + "ID", + "isActiveAWACS", + "isActiveTanker", + "isLeader", + "leaderID", + "name", + "onOff", + "operateAs", + "position", + "radio", + "reactionToThreat", + "ROE", + "shotsIntensity", + "shotsScatter", + "speed", + "state", + "TACAN", + "targetID", + "targetPosition", + "task", + "track", + "unitName", + "verticalVelocity" + ] }, "minItems": 1, "type": "array" diff --git a/client/src/unit/importexport/unitdatafileimport.ts b/client/src/unit/importexport/unitdatafileimport.ts index 9907f6f2..6cebeef9 100644 --- a/client/src/unit/importexport/unitdatafileimport.ts +++ b/client/src/unit/importexport/unitdatafileimport.ts @@ -68,7 +68,13 @@ export class UnitDataFileImport extends UnitDataFile { const validator = new ImportFileJSONSchemaValidator(); if (!validator.validate(this.#fileData)) { const errors = validator.getErrors().reduce((acc:any, error:any) => { - acc.push(error.instancePath + ": " + error.message) + let errorString = error.instancePath.substring(1) + ": " + error.message; + if (error.params) { + const {allowedValues} = error.params; + if (allowedValues) + errorString += ": " + allowedValues.join(', '); + } + acc.push(errorString); return acc; }, [] as string[]); this.#showFileDataErrors(errors);