diff --git a/frontend/react/.eslintrc.cjs b/frontend/react/.eslintrc.cjs new file mode 100644 index 00000000..3e212e1d --- /dev/null +++ b/frontend/react/.eslintrc.cjs @@ -0,0 +1,21 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: [ + 'eslint:recommended', + 'plugin:react/recommended', + 'plugin:react/jsx-runtime', + 'plugin:react-hooks/recommended', + ], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, + settings: { react: { version: '18.2' } }, + plugins: ['react-refresh'], + rules: { + 'react/jsx-no-target-blank': 'off', + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, +} diff --git a/frontend/react/.gitignore b/frontend/react/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/frontend/react/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/frontend/react/README.md b/frontend/react/README.md new file mode 100644 index 00000000..f768e33f --- /dev/null +++ b/frontend/react/README.md @@ -0,0 +1,8 @@ +# React + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh diff --git a/frontend/react/index.html b/frontend/react/index.html new file mode 100644 index 00000000..0f9805e1 --- /dev/null +++ b/frontend/react/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + + +
+ + + diff --git a/frontend/react/package.json b/frontend/react/package.json new file mode 100644 index 00000000..fb83550c --- /dev/null +++ b/frontend/react/package.json @@ -0,0 +1,37 @@ +{ + "name": "react", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --port=8080", + "build": "vite build", + "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "@fortawesome/fontawesome-svg-core": "^6.5.1", + "@fortawesome/free-solid-svg-icons": "^6.5.1", + "@fortawesome/react-fontawesome": "^0.2.0", + "@types/leaflet": "^1.9.8", + "@types/react-leaflet": "^3.0.0", + "leaflet": "^1.9.4", + "leaflet-control-mini-map": "^0.4.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-leaflet": "^4.2.1" + }, + "devDependencies": { + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "@vitejs/plugin-react": "^4.2.1", + "autoprefixer": "^10.4.19", + "eslint": "^8.57.0", + "eslint-plugin-react": "^7.34.1", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.6", + "postcss": "^8.4.38", + "tailwindcss": "^3.4.3", + "vite": "^5.2.0" + } +} diff --git a/frontend/react/postcss.config.js b/frontend/react/postcss.config.js new file mode 100644 index 00000000..2e7af2b7 --- /dev/null +++ b/frontend/react/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/frontend/react/public/vite.svg b/frontend/react/public/vite.svg new file mode 100644 index 00000000..e7b8dfb1 --- /dev/null +++ b/frontend/react/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/react/src/App.css b/frontend/react/src/App.css new file mode 100644 index 00000000..80cfb255 --- /dev/null +++ b/frontend/react/src/App.css @@ -0,0 +1,40 @@ +#root { + padding: 0px; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/frontend/react/src/App.tsx b/frontend/react/src/App.tsx new file mode 100644 index 00000000..c29ca811 --- /dev/null +++ b/frontend/react/src/App.tsx @@ -0,0 +1,28 @@ +import React from 'react' +import { useState } from 'react' +import './App.css' + +import { MapContainer, TileLayer } from 'react-leaflet' + +import { Map } from './map/map' +import { Header } from './ui/header' + +var map: Map; +const position = [51.505, -0.09] + +function App() { + const [count, setCount] = useState(0) + + return ( +
+ + + +
+
+ ) +} + +export default App diff --git a/frontend/react/src/assets/react.svg b/frontend/react/src/assets/react.svg new file mode 100644 index 00000000..6c87de9b --- /dev/null +++ b/frontend/react/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/react/src/constants/constants.ts b/frontend/react/src/constants/constants.ts new file mode 100644 index 00000000..9eae0ea7 --- /dev/null +++ b/frontend/react/src/constants/constants.ts @@ -0,0 +1,324 @@ +import { LatLng, LatLngBounds } from "leaflet"; +//import { MapMarkerVisibilityControl } from "../map/map"; + +export const UNITS_URI = "units"; +export const WEAPONS_URI = "weapons"; +export const LOGS_URI = "logs"; +export const AIRBASES_URI = "airbases"; +export const BULLSEYE_URI = "bullseyes"; +export const MISSION_URI = "mission"; +export const COMMANDS_URI = "commands"; + +export const NONE = "None"; +export const GAME_MASTER = "Game master"; +export const BLUE_COMMANDER = "Blue commander"; +export const RED_COMMANDER = "Red commander"; + +export const VISUAL = 1; +export const OPTIC = 2; +export const RADAR = 4; +export const IRST = 8; +export const RWR = 16; +export const DLINK = 32; + +export const states: string[] = ["none", "idle", "reach-destination", "attack", "follow", "land", "refuel", "AWACS", "tanker", "bomb-point", "carpet-bomb", "bomb-building", "fire-at-area", "simulate-fire-fight", "scenic-aaa", "miss-on-purpose", "land-at-point"]; +export const ROEs: string[] = ["free", "designated", "", "return", "hold"]; +export const reactionsToThreat: string[] = ["none", "manoeuvre", "passive", "evade"]; +export const emissionsCountermeasures: string[] = ["silent", "attack", "defend", "free"]; + +export const ERAS = [{ + "name": "Early Cold War", + "chronologicalOrder": 2 +}, { + "name": "Late Cold War", + "chronologicalOrder": 4 +}, { + "name": "Mid Cold War", + "chronologicalOrder": 3 +}, { + "name": "Modern", + "chronologicalOrder": 5 +}, { + "name": "WW2", + "chronologicalOrder": 1 +}]; + +export const ROEDescriptions: string[] = [ + "Free (Attack anyone)", + "Designated (Attack the designated target only) \nWARNING: Ground and Navy units don't respect this ROE, it will be equivalent to weapons FREE.", + "", + "Return (Only fire if fired upon) \nWARNING: Ground and Navy units don't respect this ROE, it will be equivalent to weapons FREE.", + "Hold (Never fire)" +]; + +export const reactionsToThreatDescriptions: string[] = [ + "None (No reaction)", + "Manoeuvre (no countermeasures)", + "Passive (Countermeasures only, no manoeuvre)", + "Evade (Countermeasures and manoeuvers)" +]; + +export const emissionsCountermeasuresDescriptions: string[] = [ + "Silent (Radar OFF, no ECM)", + "Attack (Radar only for targeting, ECM only if locked)", + "Defend (Radar for searching, ECM if locked)", + "Always on (Radar and ECM always on)" +]; + +export const shotsScatterDescriptions: string[] = [ + "When performing scenic shooting tasks like simulated firefights, will shoot with a large scatter", + "When performing scenic shooting tasks like simulated firefights, will shoot with a medium scatter", + "When performing scenic shooting tasks like simulated firefights, will shoot with a small scatter (Radar guided units will track shots when the enemy unit is close)" +]; + +export const shotsIntensityDescriptions: string[] = [ + "When performing scenic shooting tasks like simulated firefights, will shoot with a low rate of fire", + "When performing scenic shooting tasks like simulated firefights, will shoot with a medium rate of fire", + "When performing scenic shooting tasks like simulated firefights, will shoot with a high rate of fire" +]; + +export const minSpeedValues: { [key: string]: number } = { Aircraft: 100, Helicopter: 0, NavyUnit: 0, GroundUnit: 0 }; +export const maxSpeedValues: { [key: string]: number } = { Aircraft: 800, Helicopter: 300, NavyUnit: 60, GroundUnit: 60 }; +export const speedIncrements: { [key: string]: number } = { Aircraft: 25, Helicopter: 10, NavyUnit: 5, GroundUnit: 5 }; +export const minAltitudeValues: { [key: string]: number } = { Aircraft: 0, Helicopter: 0 }; +export const maxAltitudeValues: { [key: string]: number } = { Aircraft: 50000, Helicopter: 10000 }; +export const altitudeIncrements: { [key: string]: number } = { Aircraft: 500, Helicopter: 100 }; + +export const minimapBoundaries = { + "Nevada": [ // NTTR + new LatLng(39.7982463, -119.985425), + new LatLng(34.4037128, -119.7806729), + new LatLng(34.3483316, -112.4529351), + new LatLng(39.7372411, -112.1130805), + new LatLng(39.7982463, -119.985425) + ], + "Syria": [ // Syria + new LatLng(37.3630556, 29.2686111), + new LatLng(31.8472222, 29.8975), + new LatLng(32.1358333, 42.1502778), + new LatLng(37.7177778, 42.3716667), + new LatLng(37.3630556, 29.2686111) + ], + "Caucasus": [ // Caucasus + new LatLng(39.6170191, 27.634935), + new LatLng(38.8735863, 47.1423108), + new LatLng(47.3907982, 49.3101946), + new LatLng(48.3955879, 26.7753625), + new LatLng(39.6170191, 27.634935) + ], + "PersianGulf": [ // Persian Gulf + new LatLng(32.9355285, 46.5623682), + new LatLng(21.729393, 47.572675), + new LatLng(21.8501348, 63.9734737), + new LatLng(33.131584, 64.7313594), + new LatLng(32.9355285, 46.5623682) + ], + "MarianaIslands": [ // Marianas + new LatLng(22.09, 135.0572222), + new LatLng(10.5777778, 135.7477778), + new LatLng(10.7725, 149.3918333), + new LatLng(22.5127778, 149.5427778), + new LatLng(22.09, 135.0572222) + ], + "Falklands": [ // South Atlantic + new LatLng(-49.097217, -79.418267), + new LatLng(-56.874517, -79.418267), + new LatLng(-56.874517, -43.316433), + new LatLng(-49.097217, -43.316433), + new LatLng(-49.097217, -79.418267) + ], + "Normandy": [ // Normandy + new LatLng(50.44, -3.29), + new LatLng(48.12, -3.29), + new LatLng(48.12, 3.70), + new LatLng(50.44, 3.70), + new LatLng(50.44, -3.29) + ], + "SinaiMap": [ // Sinai + new LatLng(34.312222, 28.523333), + new LatLng(25.946944, 28.523333), + new LatLng(25.946944, 36.897778), + new LatLng(34.312222, 36.897778), + new LatLng(34.312222, 28.523333) + ] +}; + +export const mapBounds = { + "Syria": { bounds: new LatLngBounds([31.8472222, 29.8975], [37.7177778, 42.3716667]), zoom: 5 }, + "MarianaIslands": { bounds: new LatLngBounds([10.5777778, 135.7477778], [22.5127778, 149.5427778]), zoom: 5 }, + "Nevada": { bounds: new LatLngBounds([34.4037128, -119.7806729], [39.7372411, -112.1130805]), zoom: 5 }, + "PersianGulf": { bounds: new LatLngBounds([21.729393, 47.572675], [33.131584, 64.7313594]), zoom: 4 }, + "Caucasus": { bounds: new LatLngBounds([39.6170191, 27.634935], [47.3907982, 49.3101946]), zoom: 4 }, + "Falklands": { bounds: new LatLngBounds([-49.097217, -79.418267], [-56.874517, -43.316433]), zoom: 3 }, + "Normandy": { bounds: new LatLngBounds([50.44, -3.29], [48.12, 3.70]), zoom: 5 }, + "SinaiMap": { bounds: new LatLngBounds([34.312222, 28.523333], [25.946944, 36.897778]), zoom: 4 }, +} + +export const DCSMapsZoomLevelsByTheatre: { [key: string]: { minNativeZoom?: number, maxNativeZoom?: number, minZoom?: number }[] } = { + "Syria": [], + "MarianaIslands": [{ minNativeZoom: 1, maxNativeZoom: 13, }, { minNativeZoom: 14, maxNativeZoom: 18, minZoom: 14 }], + "Nevada": [], + "PersianGulf": [], + "Caucasus": [], + "Falklands": [], + "Normandy": [], + "SinaiMap": [], +} + +export const defaultMapLayers = { + "ArcGIS Satellite": { + urlTemplate: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}", + minZoom: 1, + maxZoom: 19, + attribution: "Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Mapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community" + }, + "OpenStreetMap Mapnik": { + urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', + minZoom: 1, + maxZoom: 20, + attribution: '© OpenStreetMap contributors' + } +} + +/* Map constants */ +export const IDLE = "Idle"; +export const MOVE_UNIT = "Move unit"; +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", +// "toggles": ["human"], +// "tooltip": "Toggle human players' visibility" +//}, { +// "image": "visibility/olympus.svg", +// "isProtected": false, +// "name": "Olympus", +// "protectable": false, +// "toggles": ["olympus"], +// "tooltip": "Toggle Olympus-controlled units' visibility" +//}, { +// "image": "visibility/dcs.svg", +// "isProtected": true, +// "name": "DCS", +// "protectable": true, +// "toggles": ["dcs"], +// "tooltip": "Toggle DCS-controlled units' visibility" +//}, { +// "category": "Aircraft", +// "image": "visibility/aircraft.svg", +// "name": "Aircraft", +// "toggles": ["aircraft"], +// "tooltip": "Toggle aircraft's visibility" +//}, { +// "category": "Helicopter", +// "image": "visibility/helicopter.svg", +// "name": "Helicopter", +// "toggles": ["helicopter"], +// "tooltip": "Toggle helicopters' visibility" +//}, { +// "category": "AirDefence", +// "image": "visibility/groundunit-sam.svg", +// "name": "Air defence", +// "toggles": ["groundunit-sam"], +// "tooltip": "Toggle air defence units' visibility" +//}, { +// "image": "visibility/groundunit.svg", +// "name": "Ground units", +// "toggles": ["groundunit"], +// "tooltip": "Toggle ground units' visibility" +//}, { +// "category": "GroundUnit", +// "image": "visibility/navyunit.svg", +// "name": "Naval", +// "toggles": ["navyunit"], +// "tooltip": "Toggle naval units' visibility" +//}, { +// "image": "visibility/airbase.svg", +// "name": "Airbase", +// "toggles": ["airbase"], +// "tooltip": "Toggle airbase' visibility" +//}]; + +export const IADSTypes = ["AAA", "SAM Site", "Radar (EWR)"]; +export const IADSDensities: { [key: string]: number } = { "AAA": 0.8, "SAM Site": 0.1, "Radar (EWR)": 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)"; +export const HIDE_UNITS_SHORT_RANGE_RINGS = "Hide short range units threat range rings (R)"; +export const SHOW_UNITS_ACQUISITION_RINGS = "Show units detection range rings (E)"; +export const FILL_SELECTED_RING = "Fill the threat range rings of selected units (F)"; +export const SHOW_UNIT_CONTACTS = "Show selected units contact lines"; +export const SHOW_UNIT_PATHS = "Show selected unit paths"; +export const SHOW_UNIT_TARGETS = "Show selected unit targets"; +export const DCS_LINK_PORT = "DCS Camera link port"; +export const DCS_LINK_RATIO = "DCS Camera zoom"; + +export enum DataIndexes { + startOfData = 0, + category, + alive, + human, + controlled, + coalition, + country, + name, + unitName, + groupName, + state, + task, + hasTask, + position, + speed, + horizontalVelocity, + verticalVelocity, + heading, + track, + isActiveTanker, + isActiveAWACS, + onOff, + followRoads, + fuel, + desiredSpeed, + desiredSpeedType, + desiredAltitude, + desiredAltitudeType, + leaderID, + formationOffset, + targetID, + targetPosition, + ROE, + reactionToThreat, + emissionsCountermeasures, + TACAN, + radio, + generalSettings, + ammo, + contacts, + activePath, + isLeader, + operateAs, + shotsScatter, + shotsIntensity, + health, + endOfData = 255 +}; + +export const MGRS_PRECISION_10KM = 2; +export const MGRS_PRECISION_1KM = 3; +export const MGRS_PRECISION_100M = 4; +export const MGRS_PRECISION_10M = 5; +export const MGRS_PRECISION_1M = 6; + +export const DELETE_CYCLE_TIME = 0.05; +export const DELETE_SLOW_THRESHOLD = 50; + +export const GROUPING_ZOOM_TRANSITION = 13; + +export const MAX_SHOTS_SCATTER = 3; +export const MAX_SHOTS_INTENSITY = 3; +export const SHOTS_SCATTER_DEGREES = 10; \ No newline at end of file diff --git a/frontend/react/src/index.css b/frontend/react/src/index.css new file mode 100644 index 00000000..4e1243e4 --- /dev/null +++ b/frontend/react/src/index.css @@ -0,0 +1,78 @@ +@import "./leaflet/leaflet.css"; + +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} + +@tailwind base; +@tailwind components; +@tailwind utilities; + +.z-ui { + z-index: 2000; +} \ No newline at end of file diff --git a/frontend/react/src/interfaces.ts b/frontend/react/src/interfaces.ts new file mode 100644 index 00000000..1784f4f9 --- /dev/null +++ b/frontend/react/src/interfaces.ts @@ -0,0 +1,292 @@ +import { LatLng } from "leaflet"; + +class Airbase { + +} + +export interface ContextMenuOption { + tooltip: string; + src: string; + callback: CallableFunction; +} + +export interface AirbasesData { + airbases: { [key: string]: any }, + sessionHash: string; + time: number; +} + +export interface BullseyesData { + bullseyes: { [key: string]: { latitude: number, longitude: number, coalition: string } }, + sessionHash: string; + time: number; +} + +export interface MissionData { + mission: { + theatre: string, + dateAndTime: DateAndTime; + commandModeOptions: CommandModeOptions; + coalitions: { red: string[], blue: string[] }; + } + time: number; + sessionHash: string; +} + +export interface CommandModeOptions { + commandMode: string; + restrictSpawns: boolean; + restrictToCoalition: boolean; + setupTime: number; + spawnPoints: { + red: number, + blue: number + }, + eras: string[] +} + +export interface DateAndTime { + date: { Year: number, Month: number, Day: number }; + time: { h: number, m: number, s: number }; + elapsedTime: number; + startTime: number; +} + +export interface LogData { + logs: { [key: string]: string }, + sessionHash: string; + time: number; +} + +export interface ServerRequestOptions { + time?: number; + commandHash?: string; +} + +export interface UnitSpawnTable { + unitType: string, + location: LatLng, + altitude?: number, + loadout?: string, + skill: string, + liveryID: string +} + +export interface ObjectIconOptions { + showState: boolean, + showVvi: boolean, + showHealth: boolean, + showHotgroup: boolean, + showUnitIcon: boolean, + showShortLabel: boolean, + showFuel: boolean, + showAmmo: boolean, + showSummary: boolean, + showCallsign: boolean, + rotateToHeading: boolean +} + +export interface GeneralSettings { + prohibitJettison: boolean; + prohibitAA: boolean; + prohibitAG: boolean; + prohibitAfterburner: boolean; + prohibitAirWpn: boolean; +} + +export interface TACAN { + isOn: boolean; + channel: number; + XY: string; + callsign: string; +} + +export interface Radio { + frequency: number; + callsign: number; + callsignNumber: number; +} + +export interface Ammo { + quantity: number, + name: string, + guidance: number, + category: number, + missileCategory: number +} + +export interface Contact { + ID: number, + detectionMethod: number +} + +export interface Offset { + x: number, + y: number, + z: number +} + +export interface UnitData { + category: string, + categoryDisplayName: string, + ID: number; + alive: boolean; + human: boolean; + controlled: boolean; + coalition: string; + country: number; + name: string; + unitName: string; + groupName: string; + state: string; + task: string; + hasTask: boolean; + position: LatLng; + speed: number; + horizontalVelocity: number; + verticalVelocity: number; + heading: number; + track: number; + isActiveTanker: boolean; + isActiveAWACS: boolean; + onOff: boolean; + followRoads: boolean; + fuel: number; + desiredSpeed: number; + desiredSpeedType: string; + desiredAltitude: number; + desiredAltitudeType: string; + leaderID: number; + formationOffset: Offset; + targetID: number; + targetPosition: LatLng; + ROE: string; + reactionToThreat: string; + emissionsCountermeasures: string; + TACAN: TACAN; + radio: Radio; + generalSettings: GeneralSettings; + ammo: Ammo[]; + contacts: Contact[]; + activePath: LatLng[]; + isLeader: boolean; + operateAs: string; + shotsScatter: number; + shotsIntensity: number; + health: number; +} + +export interface LoadoutItemBlueprint { + name: string; + quantity: number; + effectiveAgainst?: string; +} + +export interface LoadoutBlueprint { + fuel: number; + items: LoadoutItemBlueprint[]; + roles: string[]; + code: string; + name: string; + enabled: boolean; +} + +export interface UnitBlueprint { + name: string; + enabled: boolean; + coalition: string; + era: string; + label: string; + shortLabel: string; + type?: string; + loadouts?: LoadoutBlueprint[]; + filename?: string; + liveries?: { [key: string]: { name: string, countries: string[] } }; + cost?: number; + barrelHeight?: number; + muzzleVelocity?: number; + aimTime?: number; + shotsToFire?: number; + shotsBaseInterval?: number; + shotsBaseScatter?: number; + description?: string; + abilities?: string; + tags?: string; + acquisitionRange?: number; + engagementRange?: number; + targetingRange?: number; + aimMethodRange?: number; + alertnessTimeConstant?: number; + canTargetPoint?: boolean; + canRearm?: boolean; + canAAA?: boolean; + indirectFire?: boolean; + markerFile?: string; + unitWhenGrouped?: string; +} + +export interface UnitSpawnOptions { + roleType: string; + name: string; + latlng: LatLng; + coalition: string; + count: number; + country: string; + skill: string; + loadout: LoadoutBlueprint | undefined; + airbase: Airbase | undefined; + liveryID: string | undefined; + altitude: number | undefined; +} + +export interface AirbaseOptions { + name: string, + position: L.LatLng +} + +export interface AirbaseChartData { + elevation: string, + ICAO: string, + TACAN: string, + runways: AirbaseChartRunwayData[] +} + +export interface AirbaseChartRunwayHeadingData { + [index: string]: { + magHeading: string, + ILS: string + } +} + +export interface AirbaseChartRunwayData { + headings: AirbaseChartRunwayHeadingData[], + length: string +} + +export interface Listener { + callback: CallableFunction; + name?: string +} + +export interface ShortcutOptions { + altKey?: boolean; + callback: CallableFunction; + context?: string; + ctrlKey?: boolean; + name?: string; + shiftKey?: boolean; +} + +export interface ShortcutKeyboardOptions extends ShortcutOptions { + code: string; + event?: "keydown" | "keyup"; +} + +export interface ShortcutMouseOptions extends ShortcutOptions { + button: number; + event: "mousedown" | "mouseup"; +} + +export interface Manager { + add: CallableFunction; +} \ No newline at end of file diff --git a/frontend/react/src/main.jsx b/frontend/react/src/main.jsx new file mode 100644 index 00000000..a700d3b7 --- /dev/null +++ b/frontend/react/src/main.jsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App.tsx' +import './index.css' + +ReactDOM.createRoot(document.getElementById('root')).render( + + + , +) diff --git a/frontend/react/src/map/map.ts b/frontend/react/src/map/map.ts new file mode 100644 index 00000000..d36a0d6b --- /dev/null +++ b/frontend/react/src/map/map.ts @@ -0,0 +1,6 @@ + +import * as L from "leaflet" + +export class Map extends L.Map { + +} \ No newline at end of file diff --git a/frontend/react/src/ui/header.tsx b/frontend/react/src/ui/header.tsx new file mode 100644 index 00000000..0ef2b841 --- /dev/null +++ b/frontend/react/src/ui/header.tsx @@ -0,0 +1,29 @@ +import React from 'react' +import { StateButton } from './statebuttons'; +import { faPlus, faGamepad, faRuler, faPencil } from '@fortawesome/free-solid-svg-icons'; +import { library } from '@fortawesome/fontawesome-svg-core' + +library.add(faPlus, faGamepad, faRuler, faPencil) + +type HeaderProps = { + +} + +type HeaderState = { + +} + +export class Header extends React.Component { + render() { + return ( +
+
+ + + + +
+
+ ); + } +} diff --git a/frontend/react/src/ui/statebuttons.tsx b/frontend/react/src/ui/statebuttons.tsx new file mode 100644 index 00000000..0d412d39 --- /dev/null +++ b/frontend/react/src/ui/statebuttons.tsx @@ -0,0 +1,30 @@ +import React from 'react' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { IconProp } from '@fortawesome/fontawesome-svg-core' + +type ButtonProperties = { + icon: string +} + +type ButtonState = { + active: boolean +} + +export class StateButton extends React.Component { + constructor(props) { + super(props); + this.state = { + active: true + } + } + + render() { + var computedClassName = ""; + computedClassName += this.state.active? 'bg-white text-background-steel': 'bg-transparent text-white border-white'; + + return ( + this.setState({active: !this.state.active})}> + + ); + } +} \ No newline at end of file diff --git a/frontend/react/tailwind.config.js b/frontend/react/tailwind.config.js new file mode 100644 index 00000000..2042fe07 --- /dev/null +++ b/frontend/react/tailwind.config.js @@ -0,0 +1,18 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + "./index.html", + "./src/**/*.{js,ts,jsx,tsx}", + ], + theme: { + extend: { + colors: { + background: { + steel: "#202831" + } + } + }, + }, + plugins: [], +} + diff --git a/frontend/react/vite.config.js b/frontend/react/vite.config.js new file mode 100644 index 00000000..5a33944a --- /dev/null +++ b/frontend/react/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/frontend/server/public/javascripts/placeholder b/frontend/server/public/javascripts/placeholder deleted file mode 100644 index e69de29b..00000000 diff --git a/frontend/server/views/contextmenus/airbase.ejs b/frontend/server/views/contextmenus/airbase.ejs deleted file mode 100644 index dcba379a..00000000 --- a/frontend/server/views/contextmenus/airbase.ejs +++ /dev/null @@ -1,22 +0,0 @@ -
-

- -
-
ICAO
-
-
Coalition
-
-
Elevation
-
ft
-
TACAN
-
-
- -

Runways

-
-
- -
- - -
\ No newline at end of file diff --git a/frontend/server/views/contextmenus/airbasespawn.ejs b/frontend/server/views/contextmenus/airbasespawn.ejs deleted file mode 100644 index d5863dfc..00000000 --- a/frontend/server/views/contextmenus/airbasespawn.ejs +++ /dev/null @@ -1,15 +0,0 @@ -
-
-
- - -
-
- -
-
- -
-
\ No newline at end of file diff --git a/frontend/server/views/contextmenus/coalitionarea.ejs b/frontend/server/views/contextmenus/coalitionarea.ejs deleted file mode 100644 index cb1f6973..00000000 --- a/frontend/server/views/contextmenus/coalitionarea.ejs +++ /dev/null @@ -1,67 +0,0 @@ -
-
-
-
- - - - -
-
-
-
Unit types
-
- -
-
-
-
-
Units eras
-
- -
-
-
-
-
-
Units ranges
-
- -
-
-
- - - -
-
-
IADS density
-
- -
-
-
-
IADS distribution
-
- -
-
- -
- -
-
\ No newline at end of file diff --git a/frontend/server/views/contextmenus/map.ejs b/frontend/server/views/contextmenus/map.ejs deleted file mode 100644 index 4f375786..00000000 --- a/frontend/server/views/contextmenus/map.ejs +++ /dev/null @@ -1,71 +0,0 @@ -
-
-
- - -
-
-

Spawn history

-
-

You do not have any units to show.

-
-
-
-
-
- - - - - - - -
-
- - - - -
-
- -
-
- -
-
- -
-
- -
- -
- - - - - -
-
- - - - - - -
-
-
\ No newline at end of file diff --git a/frontend/server/views/contextmenus/unit.ejs b/frontend/server/views/contextmenus/unit.ejs deleted file mode 100644 index 540bd66a..00000000 --- a/frontend/server/views/contextmenus/unit.ejs +++ /dev/null @@ -1,3 +0,0 @@ -
- -
\ No newline at end of file diff --git a/frontend/server/views/other/dialogs.ejs b/frontend/server/views/other/dialogs.ejs deleted file mode 100644 index 4fa33d6b..00000000 --- a/frontend/server/views/other/dialogs.ejs +++ /dev/null @@ -1,19 +0,0 @@ -<%- 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') %> \ No newline at end of file diff --git a/frontend/server/views/other/dialogs/advancedsettings.ejs b/frontend/server/views/other/dialogs/advancedsettings.ejs deleted file mode 100644 index ad3222e1..00000000 --- a/frontend/server/views/other/dialogs/advancedsettings.ejs +++ /dev/null @@ -1,162 +0,0 @@ -
-
- -
-

Olympus 1-1

-
- -
- - -
-
-

General settings

-
-
-
-
- -
- -
- -
- -
- -
- -
- -
- -
- -
-
-
- - - - - -
-
-

TACAN options

-
-
-
- -
- -
- - -
-
- -
- -
-
X
-
-
-
- -
- -
-
-
-
- - -
-
-

Radio options

-
-
- -
- - -
-
- -
- -
-
.000
-
-
-
-
-
- -
- - -
-
-
-
-
-
- - - -
- -
-
-
-
-
- - - -
\ No newline at end of file diff --git a/frontend/server/views/other/dialogs/commandmodesettings.ejs b/frontend/server/views/other/dialogs/commandmodesettings.ejs deleted file mode 100644 index 5d783473..00000000 --- a/frontend/server/views/other/dialogs/commandmodesettings.ejs +++ /dev/null @@ -1,72 +0,0 @@ -
-
- -
-

Command mode settings

-
- -
-
- -
- -
- -
- -
- -
- - -
-
- -
- -
-
- -
- - -
-
Select eras
-
- -
-
-
- -
-

Spawn points

-
-
- -
- -
- -
-
- -
- -
- -
-
- -
- - -
\ No newline at end of file diff --git a/frontend/server/views/other/dialogs/customformation.ejs b/frontend/server/views/other/dialogs/customformation.ejs deleted file mode 100644 index b682121d..00000000 --- a/frontend/server/views/other/dialogs/customformation.ejs +++ /dev/null @@ -1,48 +0,0 @@ -
-
- -
-

Custom formation

-
- -
-
-
-
-
-
-
-
-
-
-
-
- -
- - -
-
- -
- -
-
- -
- - -
-
- -
- -
-
-
- - -
\ No newline at end of file diff --git a/frontend/server/views/other/dialogs/importexport.ejs b/frontend/server/views/other/dialogs/importexport.ejs deleted file mode 100644 index 59e77431..00000000 --- a/frontend/server/views/other/dialogs/importexport.ejs +++ /dev/null @@ -1,43 +0,0 @@ -
-
-

<%= title %>

-
- -
- -
-

<%= textContent %>

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

Data could not be imported because:

- -
    - -
    Please correct the error(s) and run the import again.
    - -
    - - - -
    -
    \ No newline at end of file diff --git a/frontend/server/views/other/dialogs/slowdelete.ejs b/frontend/server/views/other/dialogs/slowdelete.ejs deleted file mode 100644 index 372eff82..00000000 --- a/frontend/server/views/other/dialogs/slowdelete.ejs +++ /dev/null @@ -1,21 +0,0 @@ -
    -
    -

    Confirm deletion method

    -
    - -
    -

    You are trying to delete a large amount of units (), which can cause the server to lag for players.

    -

    You may: -

      -
    • delete in batches (less lag but Olympus cannot process any additional orders until
      all units have been deleted);
    • -
    • delete immediately (you can continue to give Olympus orders but players may
      experience lag while this happens);
    • -
    • cancel this instruction.
    • -

    -
    - - -
    \ No newline at end of file diff --git a/frontend/server/views/other/dialogs/splash.ejs b/frontend/server/views/other/dialogs/splash.ejs deleted file mode 100644 index 4d17115a..00000000 --- a/frontend/server/views/other/dialogs/splash.ejs +++ /dev/null @@ -1,51 +0,0 @@ -
    -
    -
    -

    DCS Olympus

    -

    Dynamic Unit Command

    -
    Version {{OLYMPUS_VERSION_NUMBER}}
    -
    Latest version
    -
    - -
    -
    Display Name
    -
    Password
    - -
    - -

    - - -
    -
    \ No newline at end of file diff --git a/frontend/server/views/other/popups.ejs b/frontend/server/views/other/popups.ejs deleted file mode 100644 index aa2e8af4..00000000 --- a/frontend/server/views/other/popups.ejs +++ /dev/null @@ -1,3 +0,0 @@ -
    - -
    \ No newline at end of file diff --git a/frontend/server/views/panels/connectionstatus.ejs b/frontend/server/views/panels/connectionstatus.ejs deleted file mode 100644 index 067f9abd..00000000 --- a/frontend/server/views/panels/connectionstatus.ejs +++ /dev/null @@ -1,7 +0,0 @@ -
    -
    - - -
    -
    -
    \ No newline at end of file diff --git a/frontend/server/views/panels/hotgroup.ejs b/frontend/server/views/panels/hotgroup.ejs deleted file mode 100644 index 0838c635..00000000 --- a/frontend/server/views/panels/hotgroup.ejs +++ /dev/null @@ -1,3 +0,0 @@ -
    - -
    \ No newline at end of file diff --git a/frontend/server/views/panels/logpanel.ejs b/frontend/server/views/panels/logpanel.ejs deleted file mode 100644 index 719dc8a8..00000000 --- a/frontend/server/views/panels/logpanel.ejs +++ /dev/null @@ -1,22 +0,0 @@ -
    -
    -
    Server log
    - -
    -
    -
    -
    FPS:
    -
    -
    -
    -
    Load:
    -
    -
    -
    - -
    - -
    -
    -
    -
    \ No newline at end of file diff --git a/frontend/server/views/panels/mouseinfo.ejs b/frontend/server/views/panels/mouseinfo.ejs deleted file mode 100644 index 990258e7..00000000 --- a/frontend/server/views/panels/mouseinfo.ejs +++ /dev/null @@ -1,63 +0,0 @@ -
    - -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    -
    - -
    \ No newline at end of file diff --git a/frontend/server/views/panels/serverstatus.ejs b/frontend/server/views/panels/serverstatus.ejs deleted file mode 100644 index 6cf7d63b..00000000 --- a/frontend/server/views/panels/serverstatus.ejs +++ /dev/null @@ -1,10 +0,0 @@ -
    -
    -
    Server frame rate:
    -
    -
    -
    -
    Olympus load:
    -
    -
    -
    \ No newline at end of file diff --git a/frontend/server/views/panels/unitcontrol.ejs b/frontend/server/views/panels/unitcontrol.ejs deleted file mode 100644 index 16e2a420..00000000 --- a/frontend/server/views/panels/unitcontrol.ejs +++ /dev/null @@ -1,168 +0,0 @@ -
    -
    - -
    -

    Selected Units

    - -
    - -
    - - -
    -
    - -
    - -
    -
    -
    -
    -
    Speed
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    Altitude -
    -
    -
    -
    -
    -
    - -
    -
    -
    Multiple categories selected
    -
    - -
    - -
    -

    Rules of engagement

    -
    - -
    -
    - -
    -

    Reaction to threat

    -
    - -
    -
    - -
    -

    Radar & ECM

    -
    - -
    -
    - -
    -

    Shots scatter

    -
    - -
    -
    - -
    -

    Shots intensity

    -
    - -
    -
    - -
    -

    Enable tanker -

    -
    -
    - -
    -

    Airborne Early Warning -

    -
    -
    - -
    -

    Operate as

    -
    -
    - -
    -

    Unit active -

    -
    -
    - -
    -

    Follow roads

    -
    -
    -
    - -
    - -
    - -
    -
    - Delete unit -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - - - - - -
    - -
    \ No newline at end of file diff --git a/frontend/server/views/panels/unitinfo.ejs b/frontend/server/views/panels/unitinfo.ejs deleted file mode 100644 index 017e346e..00000000 --- a/frontend/server/views/panels/unitinfo.ejs +++ /dev/null @@ -1,29 +0,0 @@ -
    - - -
    -

    -
    -
    -
    -
    -
    -
    -
    - -
    - -
    - -
    -
    -
    - -
    -
    -
    -
    - -
    - -
    \ No newline at end of file diff --git a/frontend/server/views/panels/unitlist.ejs b/frontend/server/views/panels/unitlist.ejs deleted file mode 100644 index 23514c55..00000000 --- a/frontend/server/views/panels/unitlist.ejs +++ /dev/null @@ -1,11 +0,0 @@ -
    -

    Unit List

    -
    -
    Name
    -
    Vehicle
    -
    Category
    -
    Coalition
    -
    Human/AI
    -
    -
    -
    \ No newline at end of file diff --git a/frontend/server/views/toolbars/commandmode.ejs b/frontend/server/views/toolbars/commandmode.ejs deleted file mode 100644 index 69000053..00000000 --- a/frontend/server/views/toolbars/commandmode.ejs +++ /dev/null @@ -1,8 +0,0 @@ - \ No newline at end of file diff --git a/frontend/server/views/toolbars/primary.ejs b/frontend/server/views/toolbars/primary.ejs deleted file mode 100644 index 69fc0b52..00000000 --- a/frontend/server/views/toolbars/primary.ejs +++ /dev/null @@ -1,110 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/server/views/uikit/uikit.ejs b/frontend/server/views/uikit/uikit.ejs deleted file mode 100644 index 224cc7b3..00000000 --- a/frontend/server/views/uikit/uikit.ejs +++ /dev/null @@ -1,1313 +0,0 @@ - - - - Olympus UI Kit - - - - - - - -
    - -

    Olympus UI Kit

    - -
    -
    Typeography
    -
    Navbar
    -
    Context menu
    -
    Unit control panel
    -
    Mouse info panel
    -
    Buttons
    -
    Ground Units
    -
    Planes
    -
    Weapons
    -
    .ol-panel
    -
    Icons
    -
    - -
    - -
    - -
    Headings
    -
    - -
    -

    h1 | open sans | 32px

    -

    h2 | open sans | 24px

    -

    h3 | open sans | 18.72px

    -

    h4 | open sans | 16px

    -
    h5 | open sans | 13.28px
    -
    h6 | open sans | 10.72px
    -
    - -
    - -
    - -
    - -
    Paragraph
    -
    - -
    -
    Plain
    -

    Nullam iaculis nisi sed mi tincidunt pretium blandit tempus urna. Vestibulum non ex vitae massa tristique auctor. Praesent orci justo, porttitor pellentesque convallis non, commodo at augue.

    -
    - -
    -
    In a panel
    -
    -

    Donec nibh est, fringilla sed pharetra eu, varius vel sem. Aliquam ac libero leo. Sed consectetur enim aliquam dui pellentesque luctus. Pellentesque vel iaculis quam.

    -
    -
    - -
    - -
    - -
    - - -
    - -
    - -
    Primary nav
    -
    - -
    - <%- include('navbar.ejs') %> -
    - -
    - -
    - -
    - -
    - -
    - -
    Context menu
    -
    -
    - <%- include('contextmenus.ejs') %> -
    -
    -
    - -
    - -
    - -
    - -
    Unit Control Panel
    -
    - -
    - <%- include('unitcontrolpanel.ejs') %> -
    - -
    - -
    - -
    - -
    - -
    - -
    Primary nav
    -
    - -
    - <%- include('mouseinfopanel.ejs') %> -
    - -
    - -
    - -
    - - -
    - -
    - -
    Buttons
    -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    - - -
    - -
    - -
    Ground
    -
    - -
    - -
    Neutral
    - -
    -
    -
    -
    Z
    -
    - -
    - -
    - -
    Blue
    - -
    -
    -
    -
    Y
    -
    - -
    - -
    - -
    Red
    - -
    -
    -
    -
    X
    -
    - -
    - -
    - -
    - - -
    - -
    SAM
    -
    - -
    - -
    Neutral
    - -
    -
    -
    -
    Z
    -
    - -
    - -
    - -
    Blue
    - -
    -
    -
    -
    Y
    -
    - -
    - -
    - -
    Red
    - -
    -
    -
    -
    X
    -
    - -
    - -
    - -
    - -
    - -
    navyunit
    -
    - -
    - -
    Neutral
    - -
    -
    -
    -
    Z
    -
    - -
    - -
    - -
    Blue
    - -
    -
    -
    -
    Y
    -
    - -
    - -
    - -
    Red
    - -
    -
    -
    -
    X
    -
    - -
    - -
    - -
    - -
    - -
    Buildings
    -
    - -
    - -
    Neutral
    - -
    -
    -
    -
    J
    -
    - -
    - -
    - -
    Blue
    - -
    -
    -
    -
    K
    -
    - -
    - -
    - -
    Red
    - -
    -
    -
    -
    L
    -
    - -
    - -
    - -
    - -
    - - -
    - -
    - -
    Fuel states (AI only)
    -
    - -
    - -
    0% (empty)
    -
    -
    -
    -
    4
    -
    -
    -
    18
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - -
    - -
    10%
    - -
    -
    -
    -
    4
    -
    -
    -
    18
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - -
    - -
    20%
    -
    -
    -
    -
    4
    -
    -
    -
    18
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - - -
    - -
    50%
    -
    -
    -
    -
    4
    -
    -
    -
    18
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - -
    - -
    75%
    -
    -
    -
    -
    4
    -
    -
    -
    18
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - -
    - -
    100%
    -
    -
    -
    -
    4
    -
    -
    -
    18
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - -
    - -
    - -
    - -
    Status icons
    -
    - -
    - -
    -
    -
    -
    -
    -
    4
    -
    -
    -
    18
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    Springfield 3-1 | Longname
    -
    260
    -
    31
    -
    -
    - -
    - - -
    - -
    -
    -
    -
    -
    -
    4
    -
    -
    -
    18
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    Springfield 3-1 | Longname
    -
    260
    -
    31
    -
    -
    - -
    - -
    - -
    -
    -
    -
    -
    -
    4
    -
    -
    -
    18
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    Springfield 3-1 | Longname
    -
    260
    -
    31
    -
    -
    - -
    - - -
    - -
    -
    -
    -
    -
    -
    4
    -
    -
    -
    18
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    Springfield 3-1 | Longname
    -
    260
    -
    31
    -
    -
    - -
    - - -
    - -
    -
    -
    -
    -
    -
    4
    -
    -
    -
    18
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    Springfield 3-1 | Longname
    -
    260
    -
    31
    -
    -
    - -
    - -
    - -
    -
    -
    -
    -
    -
    4
    -
    -
    -
    18
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    Springfield 3-1 | Longname
    -
    260
    -
    31
    -
    -
    - -
    - - -
    -
    - -
    - -
    Dead
    -
    - -
    - -
    -
    -
    -
    -
    -
    4
    -
    -
    -
    18
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    Springfield 3-1 | Longname
    -
    260
    -
    31
    -
    -
    - -
    - - -
    - -
    -
    -
    -
    -
    -
    4
    -
    -
    -
    18
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    Springfield 3-1 | Longname
    -
    260
    -
    31
    -
    -
    - -
    - -
    - -
    -
    -
    -
    -
    -
    4
    -
    -
    -
    18
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    Springfield 3-1 | Longname
    -
    260
    -
    31
    -
    -
    - -
    - - -
    -
    - -
    - -
    - -
    - -
    Missile
    - -
    - -
    - -
    -
    -
    - -
    - - -
    - -
    -
    -
    - -
    - -
    - -
    -
    -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    Plain panel
    - -
    - -
    - -
    - Disconnected -
    - -
    - -
    - -
    - -
    - -
    Panel list
    - -
    - -
    - -
    Basic list
    - -
    -
    -
    List item 1
    -
    List item 2
    -
    List item 3
    -
    -
    - -
    - -
    - -
    List with .highlight-primary
    - -
    -
    -
    List item with highlight-primary
    -
    List item with highlight-bluefor
    -
    List item with highlight-redfor
    -
    List item with highlight-neutral
    -
    -
    - -
    - -
    - -
    Sortable list
    - -
    -
    -
    -
    -
    List item 1
    -
    -
    -
    -
    List item 2
    -
    -
    -
    -
    List item 3
    -
    -
    - -
    - -
    - - - -
    - -
    - -
    - -
    Panel board
    - -
    - -
    - -
    -
    -
    -

    Unit Callsign

    -
    Airframe
    -
    Group
    -
    -
    -

    Flight data

    -
    -
    -

    Loadout

    -
    -
    -
    - -
    - - -
    - -
    - -
    - -
    Button group
    - -
    - -
    - -
    -
    - - - -
    -
    - -
    - - -
    - -
    - - -
    -
    ol select
    -
    -
    - -
    -
    - -
    -
    -
    - -
    -
    -
    -
    -
    - -
    -
    -
    - -
    - -
    - -
    -
    - The selected value goes here -
    -
    -
    - -
    -
    - -
    -
    -
    - -
    - -
    - -
    -
    - Options go up -
    -
    -
    - -
    -
    - -
    -
    -
    - -
    -
    -
    - - -
    -
    Airfield menu
    -
    -
    - -
    -

    Al Alhambra

    -
    -
    Runway 1
    -
    31 / 13
    -
    Runway 2
    -
    27 / 09
    -
    TCN
    -
    19X
    -
    ILS
    -
    -
    -
    -
    -

    Parking available:

    -
    -
    Shelters
    -
    2
    -
    Open air
    -
    5
    -
    - - -
    - -
    -
    -
    - - -
    -
    Airfield menu
    -
    -
    - -
    - -
    - -
    -

    Olympus 1-1

    -
    -
    Name
    -
    AI Controlled
    -
    -
    -
    - -
    - -
    -
    -
    - Empty loadout -
    -
    - - -
    -
    -
    -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    -

    Olympus 1-1

    -
    -
    Name
    -
    AI Controlled
    -
    -
    -
    - -
    - -
    -
    -
    -
    -
    -
    -
    -
    - - -
    -
    -
    -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    -
    -
    -
    -
    -
    -
    -
    - - -
    -
    -
    -
    - -
    - -
    - -
    - -
    -
    -
    - -
    - -
    - -
    - -
    Icons
    -
    - -
    -
    Actions
    -
    -
    - - icons_actions_gas -
    -
    - - icons_actions_nothing -
    -
    - - icons_actions_rtb -
    -
    - - icons_actions_search -
    -
    -
    - -
    -
    RoE
    -
    -
    - - icons_roe_free -
    -
    - - icons_roe_return -
    -
    - - icons_roe_stop -
    -
    - - icons_roe_target -
    -
    -
    - -
    -
    Threat
    -
    -
    - - icons_threat_protect -
    -
    - - icons_threat_retreat -
    -
    -
    - -
    - -
    - -
    - -
    - - - - - \ No newline at end of file