Resolved conflicts
@ -112,6 +112,8 @@ class DemoDataGenerator {
|
||||
app.get('/demo/bullseyes', (req, res) => this.bullseyes(req, res));
|
||||
app.get('/demo/airbases', (req, res) => this.airbases(req, res));
|
||||
app.get('/demo/mission', (req, res) => this.mission(req, res));
|
||||
app.get('/demo/commands', (req, res) => this.command(req, res));
|
||||
app.put('/demo', (req, res) => this.put(req, res));
|
||||
|
||||
app.use('/demo', basicAuth({
|
||||
users: {
|
||||
@ -417,21 +419,24 @@ class DemoDataGenerator {
|
||||
|
||||
ret.mission.coalitions = {
|
||||
red: [
|
||||
'Russia',
|
||||
'China'
|
||||
'RUSSIA',
|
||||
'CHINA'
|
||||
],
|
||||
blue: [
|
||||
'United States',
|
||||
'Great Britain'
|
||||
'UK',
|
||||
'USA'
|
||||
],
|
||||
neutral: [
|
||||
'ITALY'
|
||||
]
|
||||
}
|
||||
|
||||
ret.mission.commandModeOptions = {
|
||||
restrictSpawns: false,
|
||||
restrictSpawns: true,
|
||||
restrictToCoalition: true,
|
||||
setupTime: 0,
|
||||
spawnPoints: {
|
||||
red: 1000,
|
||||
red: 400,
|
||||
blue: 400
|
||||
},
|
||||
eras: ["WW2", "Early Cold War", "Late Cold War", "Modern"]
|
||||
@ -454,7 +459,17 @@ class DemoDataGenerator {
|
||||
}
|
||||
res.send(JSON.stringify(ret));
|
||||
}
|
||||
|
||||
command(req, res) {
|
||||
var ret = {commandExecuted: Math.random() > 0.5};
|
||||
res.send(JSON.stringify(ret));
|
||||
}
|
||||
|
||||
put(req, res) {
|
||||
var ret = {commandHash: Math.random().toString(36).slice(2, 19)}
|
||||
res.send(JSON.stringify(ret));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = DemoDataGenerator;
|
||||
6216
client/package-lock.json
generated
@ -2,12 +2,13 @@
|
||||
"name": "DCSOlympus",
|
||||
"node-main": "./bin/www",
|
||||
"main": "http://localhost:3000",
|
||||
"version": "v0.4.3-alpha",
|
||||
"version": "v0.4.4-alpha",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"copy": "copy.bat",
|
||||
"start": "npm run copy & concurrently --kill-others \"npm run watch\" \"nodemon ./bin/www\"",
|
||||
"watch": "watchify .\\src\\index.ts --debug -o .\\public\\javascripts\\bundle.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js'] -p [ tsify --noImplicitAny ]"
|
||||
"watch": "watchify .\\src\\index.ts --debug -o .\\public\\javascripts\\bundle.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js'] -p [ tsify --noImplicitAny ]",
|
||||
"document": "typedoc --out ../docs src/index.ts src/contextmenus/*.ts src/controls/*.ts src/map/*.ts src/mission/*.ts src/other/*.ts src/panels/*.ts src/popups/*.ts src/server/*.ts src/unit/*.ts src/weapon/*.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"cookie-parser": "~1.4.4",
|
||||
@ -44,6 +45,8 @@
|
||||
"sortablejs": "^1.15.0",
|
||||
"tsify": "^5.0.4",
|
||||
"tslib": "latest",
|
||||
"typedoc": "^0.24.8",
|
||||
"typedoc-umlclass": "^0.7.1",
|
||||
"typescript": "^4.9.4",
|
||||
"watchify": "^4.0.0"
|
||||
}
|
||||
|
||||
57
client/public/images/countries/blue.svg
Normal file
@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.1"
|
||||
width="640"
|
||||
height="480"
|
||||
id="svg10"
|
||||
sodipodi:docname="blue.svg"
|
||||
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<sodipodi:namedview
|
||||
id="namedview10"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.88166667"
|
||||
inkscape:cx="497.3535"
|
||||
inkscape:cy="301.13421"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg10"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0" />
|
||||
<metadata
|
||||
id="metadata16">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs14" />
|
||||
<path
|
||||
fill="#bc0000"
|
||||
fill-opacity="1"
|
||||
d="M 0,0 H 640 V 480 H 0 Z"
|
||||
id="path2"
|
||||
style="fill:#0000cd;fill-opacity:1;stroke-width:0.999996" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@ -1,84 +1,556 @@
|
||||
{
|
||||
"RED": "AGGRESSORS",
|
||||
"INS": "INSURGENTS",
|
||||
"DZ" : "ALGERIA",
|
||||
"AR" : "ARGENTINA",
|
||||
"AU" : "AUSTRALIA",
|
||||
"AT" : "AUSTRIA",
|
||||
"BH" : "BAHRAIN",
|
||||
"BY" : "BELARUS",
|
||||
"BE" : "BELGIUM",
|
||||
"BO" : "BOLIVIA",
|
||||
"BR" : "BRAZIL",
|
||||
"BG" : "BULGARIA",
|
||||
"CA" : "CANADA",
|
||||
"CL" : "CHILE",
|
||||
"CN" : "CHINA",
|
||||
"HR" : "CROATIA",
|
||||
"CU" : "CUBA",
|
||||
"CY" : "CYPRUS",
|
||||
"CZ" : "CHEZH_REPUBLIC",
|
||||
"DK" : "DENMARK",
|
||||
"EG" : "EGYPT",
|
||||
"ET" : "ETHIOPIA",
|
||||
"FI" : "FINLAND",
|
||||
"FR" : "FRANCE",
|
||||
"GE" : "GEORGIA",
|
||||
"DE" : "GERMANY",
|
||||
"GH" : "GHANA",
|
||||
"GI" : "Gibraltar",
|
||||
"GR" : "GREECE",
|
||||
"HN" : "HONDURAS",
|
||||
"HU" : "HUNGARY",
|
||||
"IS" : "Iceland",
|
||||
"IN" : "INDIA",
|
||||
"ID" : "INDONESIA",
|
||||
"IR" : "IRAN",
|
||||
"IQ" : "IRAQ",
|
||||
"IE" : "Ireland",
|
||||
"IM" : "Isle Of Man",
|
||||
"IL" : "ISRAEL",
|
||||
"IT" : "ITALY",
|
||||
"JP" : "JAPAN",
|
||||
"JO" : "JORDAN",
|
||||
"KZ" : "KAZAKHSTAN",
|
||||
"KR" : "SOUTH_KOREA",
|
||||
"KW" : "KUWAIT",
|
||||
"LB" : "LEBANON",
|
||||
"LY" : "LIBYIA",
|
||||
"MY" : "MALAYSIA",
|
||||
"MX" : "MEXICO",
|
||||
"MA" : "MOROCCO",
|
||||
"NL" : "THE_NETHERLANDS",
|
||||
"NG" : "NIGERIA",
|
||||
"NO" : "NORWAY",
|
||||
"OM" : "OMAN",
|
||||
"PK" : "PAKISTAN",
|
||||
"PE" : "PERU",
|
||||
"PH" : "PHILIPPINES",
|
||||
"PL" : "POLAND",
|
||||
"PT" : "PORTUGAL",
|
||||
"QA" : "QATAR",
|
||||
"RO" : "ROMANIA",
|
||||
"RU" : "RUSSIA",
|
||||
"SA" : "SAUDI ARABIA",
|
||||
"RS" : "SERBIA",
|
||||
"SK" : "SLOVAKIA",
|
||||
"SI" : "SLOVENIA",
|
||||
"ZA" : "SOUTH AFRICA",
|
||||
"ES" : "SPAIN",
|
||||
"SD" : "SUDAN",
|
||||
"SE" : "SWEDEN",
|
||||
"CH" : "SWITZERLAND",
|
||||
"SY" : "SYRIA",
|
||||
"TH" : "THAILAND",
|
||||
"TN" : "TUNISIA",
|
||||
"TR" : "TURKEY",
|
||||
"UA" : "UKRAINE",
|
||||
"AE" : "UNITED ARAB EMIRATES",
|
||||
"GB" : "UK",
|
||||
"US" : "USA",
|
||||
"VE" : "VENEZUELA",
|
||||
"VN" : "VIETNAM",
|
||||
"YE" : "YEMEN"
|
||||
"AGGRESSORS": {
|
||||
"flagCode": "RED",
|
||||
"liveryCodes": [
|
||||
"RSO"
|
||||
]
|
||||
},
|
||||
"INSURGENTS": {
|
||||
"flagCode": "UNK",
|
||||
"liveryCodes": [
|
||||
"INS"
|
||||
]
|
||||
},
|
||||
"ALGERIA": {
|
||||
"flagCode": "DZ",
|
||||
"liveryCodes": [
|
||||
"DZA"
|
||||
]
|
||||
},
|
||||
"ARGENTINA": {
|
||||
"flagCode": "AR",
|
||||
"liveryCodes": [
|
||||
"ARG"
|
||||
]
|
||||
},
|
||||
"AUSTRALIA": {
|
||||
"flagCode": "AU",
|
||||
"liveryCodes": [
|
||||
"AUS",
|
||||
"AUSAF"
|
||||
]
|
||||
},
|
||||
"AUSTRIA": {
|
||||
"flagCode": "AT",
|
||||
"liveryCodes": [
|
||||
"AUT"
|
||||
]
|
||||
},
|
||||
"BAHRAIN": {
|
||||
"flagCode": "BH",
|
||||
"liveryCodes": [
|
||||
"BHR"
|
||||
]
|
||||
},
|
||||
"BELARUS": {
|
||||
"flagCode": "BY",
|
||||
"liveryCodes": [
|
||||
"BLR"
|
||||
]
|
||||
},
|
||||
"BELGIUM": {
|
||||
"flagCode": "BE",
|
||||
"liveryCodes": [
|
||||
"BEL"
|
||||
]
|
||||
},
|
||||
"BOLIVIA": {
|
||||
"flagCode": "BO",
|
||||
"liveryCodes": [
|
||||
"BOL"
|
||||
]
|
||||
},
|
||||
"BRAZIL": {
|
||||
"flagCode": "BR",
|
||||
"liveryCodes": [
|
||||
"BRA"
|
||||
]
|
||||
},
|
||||
"BULGARIA": {
|
||||
"flagCode": "BG",
|
||||
"liveryCodes": [
|
||||
"BGR"
|
||||
]
|
||||
},
|
||||
"CANADA": {
|
||||
"flagCode": "CA",
|
||||
"liveryCodes": [
|
||||
"CAN"
|
||||
]
|
||||
},
|
||||
"CHILE": {
|
||||
"flagCode": "CL",
|
||||
"liveryCodes": [
|
||||
"CHL"
|
||||
]
|
||||
},
|
||||
"CHINA": {
|
||||
"flagCode": "CN",
|
||||
"liveryCodes": [
|
||||
"CHN"
|
||||
]
|
||||
},
|
||||
"CROATIA": {
|
||||
"flagCode": "HR",
|
||||
"liveryCodes": [
|
||||
"HRV"
|
||||
]
|
||||
},
|
||||
"CUBA": {
|
||||
"flagCode": "CU",
|
||||
"liveryCodes": [
|
||||
"CUB"
|
||||
]
|
||||
},
|
||||
"CYPRUS": {
|
||||
"flagCode": "CY",
|
||||
"liveryCodes": [
|
||||
"CYP"
|
||||
]
|
||||
},
|
||||
"CHEZH_REPUBLIC": {
|
||||
"displayName": "Czech Republic",
|
||||
"flagCode": "CZ",
|
||||
"liveryCodes": [
|
||||
"CZE"
|
||||
]
|
||||
},
|
||||
"DENMARK": {
|
||||
"flagCode": "DK",
|
||||
"liveryCodes": [
|
||||
"DEN"
|
||||
]
|
||||
},
|
||||
"EGYPT": {
|
||||
"flagCode": "EG",
|
||||
"liveryCodes": [
|
||||
"EGY",
|
||||
"EGP"
|
||||
]
|
||||
},
|
||||
"ETHIOPIA": {
|
||||
"flagCode": "ET",
|
||||
"liveryCodes": [
|
||||
"ETH"
|
||||
]
|
||||
},
|
||||
"FINLAND": {
|
||||
"flagCode": "FI",
|
||||
"liveryCodes": [
|
||||
"FIN"
|
||||
]
|
||||
},
|
||||
"FRANCE": {
|
||||
"flagCode": "FR",
|
||||
"liveryCodes": [
|
||||
"FRA"
|
||||
]
|
||||
},
|
||||
"GEORGIA": {
|
||||
"flagCode": "GE",
|
||||
"liveryCodes": [
|
||||
"GRG"
|
||||
]
|
||||
},
|
||||
"GERMANY": {
|
||||
"flagCode": "DE",
|
||||
"liveryCodes": [
|
||||
"GER"
|
||||
]
|
||||
},
|
||||
"GHANA": {
|
||||
"flagCode": "GH",
|
||||
"liveryCodes": [
|
||||
"GHA"
|
||||
]
|
||||
},
|
||||
"GREECE": {
|
||||
"flagCode": "GR",
|
||||
"liveryCodes": [
|
||||
"GRC"
|
||||
]
|
||||
},
|
||||
"HONDURAS": {
|
||||
"flagCode": "HN",
|
||||
"liveryCodes": [
|
||||
"HND"
|
||||
]
|
||||
},
|
||||
"HUNGARY": {
|
||||
"flagCode": "HU",
|
||||
"liveryCodes": [
|
||||
"HUN"
|
||||
]
|
||||
},
|
||||
"INDIA": {
|
||||
"flagCode": "IN",
|
||||
"liveryCodes": [
|
||||
"IND"
|
||||
]
|
||||
},
|
||||
"INDONESIA": {
|
||||
"flagCode": "ID",
|
||||
"liveryCodes": [
|
||||
"IDN"
|
||||
]
|
||||
},
|
||||
"IRAN": {
|
||||
"flagCode": "IR",
|
||||
"liveryCodes": [
|
||||
"IRN"
|
||||
]
|
||||
},
|
||||
"IRAQ": {
|
||||
"flagCode": "IQ",
|
||||
"liveryCodes": [
|
||||
"IRQ"
|
||||
]
|
||||
},
|
||||
"ISRAEL": {
|
||||
"flagCode": "IL",
|
||||
"liveryCodes": [
|
||||
"ISR"
|
||||
]
|
||||
},
|
||||
"ITALY": {
|
||||
"flagCode": "IT",
|
||||
"liveryCodes": [
|
||||
"ITA"
|
||||
]
|
||||
},
|
||||
"JAPAN": {
|
||||
"flagCode": "JP",
|
||||
"liveryCodes": [
|
||||
"JPN"
|
||||
]
|
||||
},
|
||||
"JORDAN": {
|
||||
"flagCode": "JO",
|
||||
"liveryCodes": [
|
||||
"JOR"
|
||||
]
|
||||
},
|
||||
"KAZAKHSTAN": {
|
||||
"flagCode": "KZ",
|
||||
"liveryCodes": [
|
||||
"KAZ"
|
||||
]
|
||||
},
|
||||
"SOUTH_KOREA": {
|
||||
"displayName": "South Korea",
|
||||
"flagCode": "KR",
|
||||
"liveryCodes": [
|
||||
"KOR"
|
||||
]
|
||||
},
|
||||
"KUWAIT": {
|
||||
"flagCode": "KW",
|
||||
"liveryCodes": [
|
||||
"KWT"
|
||||
]
|
||||
},
|
||||
"LEBANON": {
|
||||
"flagCode": "LB",
|
||||
"liveryCodes": [
|
||||
"LBN"
|
||||
]
|
||||
},
|
||||
"MALAYSIA": {
|
||||
"flagCode": "MY",
|
||||
"liveryCodes": [
|
||||
"MYS"
|
||||
]
|
||||
},
|
||||
"MEXICO": {
|
||||
"flagCode": "MX",
|
||||
"liveryCodes": [
|
||||
"MEX"
|
||||
]
|
||||
},
|
||||
"MOROCCO": {
|
||||
"flagCode": "MA",
|
||||
"liveryCodes": [
|
||||
"MAR"
|
||||
]
|
||||
},
|
||||
"THE_NETHERLANDS": {
|
||||
"displayName": "The Netherlands",
|
||||
"flagCode": "NL",
|
||||
"liveryCodes": [
|
||||
"NETH"
|
||||
]
|
||||
},
|
||||
"NIGERIA": {
|
||||
"flagCode": "NG",
|
||||
"liveryCodes": [
|
||||
"NGA"
|
||||
]
|
||||
},
|
||||
"NORWAY": {
|
||||
"flagCode": "NO",
|
||||
"liveryCodes": [
|
||||
"NOR"
|
||||
]
|
||||
},
|
||||
"OMAN": {
|
||||
"flagCode": "OM",
|
||||
"liveryCodes": [
|
||||
"OMN"
|
||||
]
|
||||
},
|
||||
"PAKISTAN": {
|
||||
"flagCode": "PK",
|
||||
"liveryCodes": [
|
||||
"PAK"
|
||||
]
|
||||
},
|
||||
"PERU": {
|
||||
"flagCode": "PE",
|
||||
"liveryCodes": [
|
||||
"PER"
|
||||
]
|
||||
},
|
||||
"PHILIPPINES": {
|
||||
"flagCode": "PH",
|
||||
"liveryCodes": [
|
||||
"PHL"
|
||||
]
|
||||
},
|
||||
"POLAND": {
|
||||
"flagCode": "PL",
|
||||
"liveryCodes": [
|
||||
"POL"
|
||||
]
|
||||
},
|
||||
"PORTUGAL": {
|
||||
"flagCode": "PT",
|
||||
"liveryCodes": [
|
||||
"PRT"
|
||||
]
|
||||
},
|
||||
"QATAR": {
|
||||
"flagCode": "QA",
|
||||
"liveryCodes": [
|
||||
"QAT"
|
||||
]
|
||||
},
|
||||
"ROMANIA": {
|
||||
"flagCode": "RO",
|
||||
"liveryCodes": [
|
||||
"ROU"
|
||||
]
|
||||
},
|
||||
"RUSSIA": {
|
||||
"flagCode": "RU",
|
||||
"liveryCodes": [
|
||||
"RUS"
|
||||
]
|
||||
},
|
||||
"SAUDI_ARABIA": {
|
||||
"displayName": "Saudi Arabia",
|
||||
"flagCode": "SA",
|
||||
"liveryCodes": [
|
||||
"SAU"
|
||||
]
|
||||
},
|
||||
"SERBIA": {
|
||||
"flagCode": "RS",
|
||||
"liveryCodes": [
|
||||
"SRB"
|
||||
]
|
||||
},
|
||||
"SLOVAKIA": {
|
||||
"flagCode": "SK",
|
||||
"liveryCodes": [
|
||||
"SVK"
|
||||
]
|
||||
},
|
||||
"SLOVENIA": {
|
||||
"flagCode": "SI",
|
||||
"liveryCodes": [
|
||||
"SVN"
|
||||
]
|
||||
},
|
||||
"SOUTH_AFRICA": {
|
||||
"displayName": "South Africa",
|
||||
"flagCode": "ZA",
|
||||
"liveryCodes": []
|
||||
},
|
||||
"SPAIN": {
|
||||
"flagCode": "ES",
|
||||
"liveryCodes": [
|
||||
"SPN",
|
||||
"SPA"
|
||||
]
|
||||
},
|
||||
"SUDAN": {
|
||||
"flagCode": "SD",
|
||||
"liveryCodes": [
|
||||
"SDN",
|
||||
"SUN"
|
||||
]
|
||||
},
|
||||
"SWEDEN": {
|
||||
"flagCode": "SE",
|
||||
"liveryCodes": [
|
||||
"SWE"
|
||||
]
|
||||
},
|
||||
"SWITZERLAND": {
|
||||
"flagCode": "CH",
|
||||
"liveryCodes": [
|
||||
"SUI"
|
||||
]
|
||||
},
|
||||
"SYRIA": {
|
||||
"flagCode": "SY",
|
||||
"liveryCodes": [
|
||||
"SYR"
|
||||
]
|
||||
},
|
||||
"THAILAND": {
|
||||
"flagCode": "TH",
|
||||
"liveryCodes": [
|
||||
"THA"
|
||||
]
|
||||
},
|
||||
"TUNISIA": {
|
||||
"flagCode": "TN",
|
||||
"liveryCodes": [
|
||||
"TUN"
|
||||
]
|
||||
},
|
||||
"TURKEY": {
|
||||
"flagCode": "TR",
|
||||
"liveryCodes": [
|
||||
"TUR"
|
||||
]
|
||||
},
|
||||
"UKRAINE": {
|
||||
"flagCode": "UA",
|
||||
"liveryCodes": [
|
||||
"UKR"
|
||||
]
|
||||
},
|
||||
"UNITED_ARAB_EMIRATES": {
|
||||
"displayName": "United Arab Emirates",
|
||||
"flagCode": "AE",
|
||||
"liveryCodes": [
|
||||
"ARE"
|
||||
]
|
||||
},
|
||||
"UK": {
|
||||
"displayName": "United Kingdom",
|
||||
"flagCode": "GB",
|
||||
"liveryCodes": [
|
||||
"UK"
|
||||
]
|
||||
},
|
||||
"USA": {
|
||||
"displayName": "United States of America",
|
||||
"flagCode": "US",
|
||||
"liveryCodes": [
|
||||
"USA",
|
||||
"USAF"
|
||||
]
|
||||
},
|
||||
"VENEZUELA": {
|
||||
"flagCode": "VE",
|
||||
"liveryCodes": [
|
||||
"VEN"
|
||||
]
|
||||
},
|
||||
"VIETNAM": {
|
||||
"flagCode": "VN",
|
||||
"liveryCodes": [
|
||||
"VNM"
|
||||
]
|
||||
},
|
||||
"YEMEN": {
|
||||
"flagCode": "YE",
|
||||
"liveryCodes": [
|
||||
"YEM"
|
||||
]
|
||||
},
|
||||
"CJTF_BLUE": {
|
||||
"displayName": "Combined Joint Task Force Blue",
|
||||
"flagCode": "BLUE",
|
||||
"liveryCodes": [
|
||||
"BLUE"
|
||||
]
|
||||
},
|
||||
"SOUTH_OSETIA": {
|
||||
"displayName": "South Ossetia",
|
||||
"flagCode": "UNK",
|
||||
"liveryCodes": []
|
||||
},
|
||||
"NORTH_KOREA": {
|
||||
"displayName": "Democratic People's Republic of Korea",
|
||||
"flagCode": "KP",
|
||||
"liveryCodes": [
|
||||
"PRK"
|
||||
]
|
||||
},
|
||||
"CJTF_RED": {
|
||||
"displayName": "Combined Joint Task Force Red",
|
||||
"flagCode": "RED",
|
||||
"liveryCodes": [
|
||||
"RED"
|
||||
]
|
||||
},
|
||||
"ABKHAZIA": {
|
||||
"flagCode": "UNK",
|
||||
"liveryCodes": [
|
||||
"ABH"
|
||||
]
|
||||
},
|
||||
"ITALIAN_SOCIAL_REPUBLIC": {
|
||||
"displayName": "Italian Social Republic",
|
||||
"flagCode": "SOCIAL",
|
||||
"liveryCodes": [
|
||||
"RSI"
|
||||
]
|
||||
},
|
||||
"USSR": {
|
||||
"displayName": "USSR",
|
||||
"flagCode": "USSR",
|
||||
"liveryCodes": []
|
||||
},
|
||||
"ECUADOR": {
|
||||
"flagCode": "EC",
|
||||
"liveryCodes": [
|
||||
"ECU"
|
||||
]
|
||||
},
|
||||
"LIBYA": {
|
||||
"flagCode": "LY",
|
||||
"liveryCodes": [
|
||||
"LBY",
|
||||
"LIB"
|
||||
]
|
||||
},
|
||||
"UN_PEACEKEEPERS": {
|
||||
"displayName": "United Nations",
|
||||
"flagCode": "UNK",
|
||||
"liveryCodes": [
|
||||
"UN"
|
||||
]
|
||||
},
|
||||
"GDR": {
|
||||
"flagCode": "UNK",
|
||||
"liveryCodes": [
|
||||
"GDR"
|
||||
]
|
||||
},
|
||||
"YUGOSLAVIA": {
|
||||
"flagCode": "YUG",
|
||||
"liveryCodes": [
|
||||
"YUG"
|
||||
]
|
||||
},
|
||||
"THIRDREICH": {
|
||||
"displayName": "Third Reich",
|
||||
"flagCode": "THIRD",
|
||||
"liveryCodes": []
|
||||
}
|
||||
}
|
||||
57
client/public/images/countries/red.svg
Normal file
@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.1"
|
||||
width="640"
|
||||
height="480"
|
||||
id="svg10"
|
||||
sodipodi:docname="red.svg"
|
||||
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<sodipodi:namedview
|
||||
id="namedview10"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.88166667"
|
||||
inkscape:cx="497.3535"
|
||||
inkscape:cy="301.13421"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg10"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0" />
|
||||
<metadata
|
||||
id="metadata16">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs14" />
|
||||
<path
|
||||
fill="#bc0000"
|
||||
fill-opacity="1"
|
||||
d="M 0,0 H 640 V 480 H 0 Z"
|
||||
id="path2"
|
||||
style="fill:#cc0000;fill-opacity:1;stroke-width:0.999996" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
289
client/public/images/countries/social.svg
Normal file
|
After Width: | Height: | Size: 50 KiB |
56
client/public/images/countries/third.svg
Normal file
@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.0"
|
||||
width="640"
|
||||
height="480"
|
||||
viewBox="0 0 640 480"
|
||||
id="svg8"
|
||||
sodipodi:docname="third.svg"
|
||||
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs12" />
|
||||
<sodipodi:namedview
|
||||
id="namedview10"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
width="640px"
|
||||
inkscape:zoom="1.058"
|
||||
inkscape:cx="284.97164"
|
||||
inkscape:cy="274.57467"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg8" />
|
||||
<rect
|
||||
width="640"
|
||||
height="480"
|
||||
fill="#dd0000"
|
||||
id="rect2"
|
||||
x="0"
|
||||
y="0"
|
||||
style="stroke-width:0.999997" />
|
||||
<circle
|
||||
cx="320"
|
||||
cy="240"
|
||||
r="180"
|
||||
fill="#ffffff"
|
||||
id="circle4"
|
||||
style="stroke-width:1" />
|
||||
<path
|
||||
d="M 472.73507,256.97056 387.88225,172.11774 252.11775,307.88225 167.26494,223.02943 M 336.97057,87.264932 252.11775,172.11774 387.88225,307.88225 303.02944,392.73506"
|
||||
fill="none"
|
||||
stroke="#000000"
|
||||
stroke-width="48"
|
||||
id="path6" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
68
client/public/images/countries/unk.svg
Normal file
@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.1"
|
||||
width="640"
|
||||
height="480"
|
||||
id="svg10"
|
||||
sodipodi:docname="unk.svg"
|
||||
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<sodipodi:namedview
|
||||
id="namedview10"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.88166667"
|
||||
inkscape:cx="497.3535"
|
||||
inkscape:cy="300"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg10"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0" />
|
||||
<metadata
|
||||
id="metadata16">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs14" />
|
||||
<path
|
||||
fill="#bc0000"
|
||||
fill-opacity="1"
|
||||
d="M 0,0 H 640 V 480 H 0 Z"
|
||||
id="path2"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke-width:0.999996" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:549.6px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1"
|
||||
x="179.23065"
|
||||
y="424.19662"
|
||||
id="text2551"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan2549"
|
||||
x="179.23065"
|
||||
y="424.19662"
|
||||
style="stroke-width:1">?</tspan></text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
74
client/public/images/countries/ussr.svg
Normal file
@ -0,0 +1,74 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.1"
|
||||
width="640"
|
||||
height="480"
|
||||
id="svg10"
|
||||
sodipodi:docname="ussr.svg"
|
||||
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<sodipodi:namedview
|
||||
id="namedview10"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:zoom="0.88166667"
|
||||
inkscape:cx="497.3535"
|
||||
inkscape:cy="300"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg10" />
|
||||
<metadata
|
||||
id="metadata16">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs14" />
|
||||
<path
|
||||
fill="#bc0000"
|
||||
fill-opacity="1"
|
||||
d="M 0,0 H 639.99998 V 480 H 0 Z"
|
||||
id="path2"
|
||||
style="fill:#cc0000;fill-opacity:1;stroke-width:0.999997" />
|
||||
<path
|
||||
id="path11728"
|
||||
d="m 160.0004,30 -6.73546,20.729509 H 131.4688 L 149.10222,63.540898 142.36675,84.2704 160.0004,71.458772 177.63406,84.2704 170.89859,63.540898 188.532,50.729509 h -21.79613 z m 0,10.79999 4.31062,13.266778 h 13.94975 l -11.2856,8.199597 4.31061,13.266777 -11.28538,-8.199363 -11.28538,8.199363 4.31062,-13.266777 -11.28561,-8.199597 h 13.94975 z"
|
||||
style="fill:#ffd700;fill-opacity:1;stroke:none;stroke-width:0.15px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<g
|
||||
style="fill:#ffd700;fill-opacity:1;stroke-width:1.25"
|
||||
id="g2900"
|
||||
transform="matrix(0.79145503,0,0,0.78939049,3.0638126,3.0127518)">
|
||||
<path
|
||||
id="rect4165-6"
|
||||
d="m 137.43744,171.69421 18.86296,18.9937 17.78834,-17.66589 c 27.05847,29.021 55.43807,56.99501 82.28704,86.12782 4.03444,4.06233 10.59815,4.085 14.66056,0.0506 4.06232,-4.03445 4.08499,-10.59815 0.0506,-14.66056 -28.81871,-27.1901 -57.72545,-54.60143 -86.55328,-81.89095 l 23.96499,-23.80003 -33.34026,-4.61605 z"
|
||||
style="fill:#ffd700;fill-opacity:1;stroke:none;stroke-width:0.611489;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
<path
|
||||
id="path4179-3"
|
||||
d="m 198.2887,110.1955 c 15.51743,8.7394 27.29872,21.28122 34.2484,34.3924 7.04394,13.28902 10.13959,27.16218 10.20325,38.25433 0.13054,22.74374 -18.43771,41.18184 -41.18183,41.18184 -12.13597,0 -23.04607,-5.24868 -30.58302,-13.60085 l -4.16863,3.51033 c -0.70999,-0.27231 -1.46387,-0.41221 -2.22429,-0.41276 -1.82948,1.9e-4 -3.56621,0.80531 -4.74859,2.20136 -2.97368,0.38896 -5.46251,2.44529 -6.40534,5.29224 -3.13486,6.28843 -8.63524,11.21997 -15.29104,13.4776 -0.0637,0.0216 -0.11992,0.05 -0.1758,0.0783 -3.07749,1.12758 -6.16259,3.1643 -8.78919,5.80245 -5.19155,5.23656 -7.72858,11.93658 -6.30024,16.63822 -0.14098,0.40857 -0.21361,0.83759 -0.21498,1.26979 1.5e-4,2.17082 1.75991,3.93058 3.93073,3.93073 0.54341,-0.002 1.08053,-0.11639 1.57745,-0.33632 4.69369,1.05881 11.06885,-1.54582 16.05444,-6.55917 2.82624,-2.85072 4.94356,-6.22349 5.98303,-9.53062 2.31696,-6.62278 7.29699,-12.01856 13.62281,-15.05312 0.15105,-0.0725 0.27303,-0.14714 0.38218,-0.22358 2.12082,-1.01408 3.67251,-2.92895 4.225,-5.2139 9.70222,11.44481 24.25255,18.75299 40.51876,19.13577 29.83352,0.70205 52.13299,-21.25802 53.16414,-52.83642 0.51894,-15.89259 -5.62993,-36.3847 -19.6412,-53.19089 -10.70835,-12.84441 -26.40987,-23.50795 -44.18699,-28.20777 z"
|
||||
style="fill:#ffd700;fill-opacity:1;stroke:none;stroke-width:0.625044;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
53
client/public/images/countries/yug.svg
Normal file
@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 640 480"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
sodipodi:docname="yug.svg"
|
||||
width="640"
|
||||
height="480"
|
||||
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs12" />
|
||||
<sodipodi:namedview
|
||||
id="namedview10"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:zoom="0.91840278"
|
||||
inkscape:cx="253.1569"
|
||||
inkscape:cy="286.91115"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg8" />
|
||||
<path
|
||||
fill="#dd0000"
|
||||
d="M 0,320 H 640 V 480 H 0"
|
||||
id="path2"
|
||||
style="stroke-width:0.999996" />
|
||||
<path
|
||||
fill="#ffffff"
|
||||
d="M 0,160 H 640 V 320 H 0"
|
||||
id="path4"
|
||||
style="stroke-width:0.999996" />
|
||||
<path
|
||||
fill="#003893"
|
||||
d="M 0,0 H 640 V 160 H 0"
|
||||
id="path6"
|
||||
style="stroke-width:0.999996" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@ -1,194 +0,0 @@
|
||||
/**************************************/
|
||||
|
||||
.olympus-dialog {
|
||||
align-self: center;
|
||||
background:white;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-self: center;
|
||||
padding:10px;
|
||||
position:absolute;
|
||||
width:fit-content;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.olympus-dialog-close {
|
||||
cursor:pointer;
|
||||
position:absolute;
|
||||
right:10px;
|
||||
top:5px;
|
||||
}
|
||||
|
||||
.olympus-dialog-header {
|
||||
font-weight:bold;
|
||||
}
|
||||
|
||||
|
||||
/**************************************/
|
||||
|
||||
|
||||
/***** AIC *****/
|
||||
|
||||
.aic-panel {
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
#aic-control-panel {
|
||||
bottom:30px;
|
||||
position: absolute;
|
||||
left:30px;
|
||||
}
|
||||
|
||||
|
||||
#aic-control-panel .olympus-button img {
|
||||
max-width: 32px;
|
||||
}
|
||||
|
||||
|
||||
#aic-toolbox, #aic-callsign-panel {
|
||||
align-items: flex-start;
|
||||
align-self: center;
|
||||
flex-direction: column;
|
||||
row-gap: 10px;
|
||||
display:none;
|
||||
position:absolute;
|
||||
}
|
||||
|
||||
.aic-panel {
|
||||
background:#eaeaea;
|
||||
border-bottom-right-radius: 10px;
|
||||
border-top-right-radius: 10px;
|
||||
justify-self: left;
|
||||
padding:5px 10px;
|
||||
}
|
||||
|
||||
.aic-enabled #aic-toolbox, .aic-enabled #aic-callsign-panel {
|
||||
display:flex;
|
||||
}
|
||||
|
||||
.aic-enabled #aic-callsign-panel {
|
||||
align-self: auto;
|
||||
top: 100px;
|
||||
}
|
||||
|
||||
.aic-panel h2 {
|
||||
font-size:90%;
|
||||
margin:0;
|
||||
padding:0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#aic-callsign-display {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#aic-formation-list {
|
||||
display:flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#aic-formation-list > div {
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
display:flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
margin-top:10px;
|
||||
position:relative;
|
||||
}
|
||||
|
||||
#aic-formation-list .aic-formation-image img {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 10px;
|
||||
max-width: 50px;
|
||||
}
|
||||
|
||||
#aic-formation-list .aic-formation-name {
|
||||
font-size:90%;
|
||||
}
|
||||
|
||||
#aic-formation-list .aic-formation-descriptor {
|
||||
background:white;
|
||||
border-radius: 10px;
|
||||
left:100px;
|
||||
padding:5px;
|
||||
position:absolute;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
#aic-teleprompt {
|
||||
background-color: white;
|
||||
border:2px solid black;
|
||||
border-radius: 10px;
|
||||
bottom: 50px;
|
||||
color: black;
|
||||
display: none;
|
||||
justify-content: center;
|
||||
justify-self: center;
|
||||
padding: 10px;
|
||||
position: absolute;
|
||||
width: fit-content;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.aic-enabled #aic-teleprompt {
|
||||
display:flex;
|
||||
}
|
||||
|
||||
#aic-descriptor {
|
||||
display:flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
#aic-descriptor .aic-descriptor-section {
|
||||
display:flex;
|
||||
flex-direction: column;
|
||||
margin:0 10px;
|
||||
}
|
||||
|
||||
#aic-descriptor .aic-descriptor-section-label {
|
||||
background-color:#eaeaea;
|
||||
border-top-left-radius: 10px;
|
||||
border-top-right-radius: 10px;
|
||||
padding:.25em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#aic-descriptor .aic-descriptor-phrase {
|
||||
border-bottom: 1px solid #ccc;
|
||||
display:flex;
|
||||
flex-direction: row;
|
||||
margin-bottom:5px;
|
||||
padding-bottom:2px;
|
||||
}
|
||||
|
||||
#aic-descriptor .aic-descriptor-phrase:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#aic-descriptor .aic-descriptor-components .aic-descriptor-component {
|
||||
margin:0 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#aic-descriptor .aic-descriptor-component-label {
|
||||
display:none;
|
||||
}
|
||||
|
||||
#aic-descriptor .aic-descriptor-component-value:after {
|
||||
content:",";
|
||||
margin-right:5px;
|
||||
}
|
||||
|
||||
#aic-descriptor .aic-descriptor-component:last-of-type .aic-descriptor-component-value:after {
|
||||
content:"; ";
|
||||
}
|
||||
|
||||
#aic-descriptor .aic-descriptor-section:last-of-type .aic-descriptor-component:last-of-type .aic-descriptor-component-value:after {
|
||||
content:".";
|
||||
}
|
||||
|
||||
|
||||
/**************************************/
|
||||
@ -1,205 +0,0 @@
|
||||
.ol-strip-board .ol-dialog-header {
|
||||
align-items: center;
|
||||
display:flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.ol-strip-board-strips {
|
||||
display:flex;
|
||||
flex-direction: column;
|
||||
row-gap: 4px;
|
||||
}
|
||||
|
||||
.ol-strip-board-strip {
|
||||
align-items: center;
|
||||
border-radius: var( --border-radius-sm );
|
||||
column-gap: 4px;
|
||||
display:flex;
|
||||
flex-flow: row nowrap;
|
||||
row-gap:4px;
|
||||
}
|
||||
|
||||
.ol-strip-board-strip[data-flight-status="checkedin"] {
|
||||
background-color: #ffffff2A;
|
||||
}
|
||||
|
||||
.ol-strip-board-strip[data-flight-status="readytotaxi"] {
|
||||
background-color: #ffff0063;
|
||||
}
|
||||
|
||||
.ol-strip-board-strip[data-flight-status="clearedtotaxi"] {
|
||||
background-color: #00ff0030;
|
||||
}
|
||||
|
||||
.ol-strip-board-strip[data-flight-status="halted"] {
|
||||
background-color: #FF000040;
|
||||
}
|
||||
|
||||
.ol-strip-board-strip[data-flight-status="terminated"] {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
.ol-strip-board-headers {
|
||||
column-gap: 4px;
|
||||
display:flex;
|
||||
flex-flow:row nowrap;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ol-strip-board-headers > *, .ol-strip-board-strip > [data-point] {
|
||||
padding: 4px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width:80px;
|
||||
}
|
||||
|
||||
.ol-strip-board-strip input[type="text"] {
|
||||
appearance: none;
|
||||
background-color: transparent;
|
||||
border:1px solid #ffffff30;
|
||||
border-radius: var( --border-radius-sm );
|
||||
color:white;
|
||||
font-size:12px;
|
||||
font-weight:normal;
|
||||
outline:none;
|
||||
padding: 4px 0;
|
||||
text-align: center;
|
||||
width:100%;
|
||||
}
|
||||
|
||||
.ol-strip-board-strip[data-time-warning="level-1"] [data-point="timeToGo"] {
|
||||
border:1px solid #cc0000;
|
||||
}
|
||||
|
||||
.ol-strip-board-headers :nth-child(1) {
|
||||
width:12px;
|
||||
}
|
||||
|
||||
.ol-strip-board-headers :nth-child(2),
|
||||
.ol-strip-board-strip :nth-child(2),
|
||||
[data-board-type="ground"] .ol-strip-board-headers :nth-child(3),
|
||||
[data-board-type="ground"] .ol-strip-board-strip :nth-child(3) {
|
||||
width:130px;
|
||||
}
|
||||
|
||||
[data-board-type="ground"] .ol-strip-board-strip :nth-child(5) {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.ol-strip-board-headers :last-child,
|
||||
.ol-strip-board-strip :last-child {
|
||||
width:20px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[data-board-type="tower"] .ol-strip-board-strip > * {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
[data-board-type="tower"] .ol-strip-board-strip a {
|
||||
color:white;
|
||||
}
|
||||
|
||||
[data-board-type="tower"] .ol-strip-board-strip > :nth-child(2) {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
[data-board-type="tower"] .ol-strip-board-strip :nth-child(3) input,
|
||||
[data-board-type="tower"] .ol-strip-board-strip :nth-child(5) input {
|
||||
width:30px;
|
||||
}
|
||||
|
||||
[data-board-type="tower"] .ol-strip-board-strip :nth-child(3) {
|
||||
font-size:10px;
|
||||
}
|
||||
|
||||
|
||||
[data-altitude-assigned] [data-point="assignedAltitude"] input,
|
||||
[data-speed-assigned] [data-point="assignedSpeed"] input {
|
||||
background-color:#ffffffbb;
|
||||
color: black;
|
||||
font-weight: var( --font-weight-bolder );
|
||||
}
|
||||
|
||||
[data-warning-altitude] [data-point="altitude"],
|
||||
[data-warning-speed] [data-point="speed"] {
|
||||
background:#cc0000;
|
||||
border-radius: var( --border-radius-sm );
|
||||
}
|
||||
|
||||
|
||||
|
||||
.ol-strip-board-strip > [data-point="name"] {
|
||||
text-overflow: ellipsis;
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
.ol-strip-board-strip .ol-select-value {
|
||||
opacity: .85;
|
||||
}
|
||||
|
||||
|
||||
.ol-strip-board-add-flight {
|
||||
display:flex;
|
||||
flex-flow: row nowrap;
|
||||
position:relative;
|
||||
}
|
||||
|
||||
|
||||
.ol-strip-board-add-flight > * {
|
||||
border:none;
|
||||
outline: none;
|
||||
padding:4px 8px;
|
||||
}
|
||||
|
||||
.add-flight-by-click img {
|
||||
filter:invert();
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.ol-strip-board-add-flight input {
|
||||
border-radius: var( --border-radius-sm );
|
||||
}
|
||||
|
||||
.ol-strip-board-add-flight .ol-auto-suggest {
|
||||
background:white;
|
||||
border-radius: var(--border-radius-sm );
|
||||
color:black;
|
||||
display:none;
|
||||
flex-direction: column;
|
||||
left:0;
|
||||
margin:0;
|
||||
position:absolute;
|
||||
translate:0 -100%;
|
||||
top:0;
|
||||
}
|
||||
|
||||
.ol-strip-board-add-flight .ol-auto-suggest[data-has-suggestions] {
|
||||
display:flex;
|
||||
row-gap: 4px;
|
||||
}
|
||||
|
||||
.ol-strip-board-add-flight .ol-auto-suggest[data-has-suggestions] a {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
[data-board-type="ground"] {
|
||||
bottom:20px;
|
||||
}
|
||||
|
||||
[data-board-type="tower"] {
|
||||
right:10px;
|
||||
top:10px;
|
||||
}
|
||||
|
||||
[data-board-type="tower"] .ol-auto-suggest {
|
||||
top:30px;
|
||||
translate:0;
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
#unit-list {
|
||||
display:flex;
|
||||
flex-direction: column;
|
||||
font-size:13px;
|
||||
height: 250px;
|
||||
width:fit-content;
|
||||
}
|
||||
|
||||
#unit-list > div {
|
||||
display:flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
#unit-list > div > div {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width:100px;
|
||||
}
|
||||
|
||||
#unit-list > div:first-of-type {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#unit-list > div > div:nth-of-type( 4 ) {
|
||||
text-align: center;
|
||||
}
|
||||
@ -11,7 +11,7 @@
|
||||
left: 10px;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
z-index: 9999;
|
||||
z-index: 99999;
|
||||
column-gap: 10px;
|
||||
}
|
||||
|
||||
|
||||
@ -30,12 +30,47 @@
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
row-gap: 5px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
#aircraft-spawn-menu,
|
||||
#helicopter-spawn-menu,
|
||||
#groundunit-spawn-menu,
|
||||
#navyunit-spawn-menu {
|
||||
#map-contextmenu>div:nth-child(n+4)>div {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.contextmenu-advanced-options {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
row-gap: 5px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.contextmenu-advanced-options-toggle {
|
||||
display: flex;
|
||||
align-content: center;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
margin: 5px;
|
||||
column-gap: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.contextmenu-advanced-options-toggle:after {
|
||||
content: url(/resources/theme/images/icons/chevron-down.svg);
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.contextmenu-advanced-options-toggle div:first-child {
|
||||
width: fit-content;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.contextmenu-advanced-options>* {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.unit-spawn-menu {
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
@ -58,11 +93,30 @@
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
#aircraft-spawn-menu .ol-select.is-open .ol-select-options,
|
||||
#helicopter-spawn-menu .ol-select.is-open .ol-select-options {
|
||||
.unit-spawn-menu .ol-select.is-open .ol-select-options {
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
.unit-loadout-list {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.unit-loadout-list div {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
background-color: var(--background-steel);
|
||||
padding: 2px 5px 2px 5px;
|
||||
}
|
||||
|
||||
.unit-loadout-list div:hover {
|
||||
overflow: visible;
|
||||
white-space: nowrap;
|
||||
background-color: var(--background-steel);
|
||||
width: fit-content;
|
||||
border-radius: var(--border-radius-sm);
|
||||
}
|
||||
|
||||
.deploy-unit-button {
|
||||
margin-top: 5px;
|
||||
text-align: center;
|
||||
@ -82,7 +136,7 @@
|
||||
margin: 0px 5px;
|
||||
}
|
||||
|
||||
.upper-bar button:nth-child(2) {
|
||||
.upper-bar button:first-of-type {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
@ -137,31 +191,44 @@
|
||||
content: "Create neutral unit";
|
||||
}
|
||||
|
||||
#aircraft-loadout-preview,
|
||||
#helicopter-loadout-preview {
|
||||
.unit-label-count-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
column-gap: 5px;
|
||||
}
|
||||
|
||||
.unit-label-count-container>div:nth-child(1) {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.unit-label-count-container>div:nth-child(2) {
|
||||
font-size: large;
|
||||
}
|
||||
|
||||
.unit-loadout-preview {
|
||||
align-content: space-between;
|
||||
align-items: center;
|
||||
column-gap: 20px;
|
||||
column-gap: 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#aircaft-loadout-list,
|
||||
#helicopter-loadout-list {
|
||||
.unit-loadout-list {
|
||||
align-content: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#aircraft-unit-image,
|
||||
#helicopter-unit-image {
|
||||
.unit-image {
|
||||
filter: invert(100%);
|
||||
height: 100px;
|
||||
width: 25%;
|
||||
margin-bottom: 10px;
|
||||
margin-top: 10px;
|
||||
width: 100px;
|
||||
aspect-ratio: 1/1;
|
||||
}
|
||||
|
||||
#smoke-spawn-menu {
|
||||
@ -264,9 +331,20 @@
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.ol-select>.ol-select-options>div button.country-dropdown-element {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-content: center;
|
||||
column-gap: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.country-dropdown-element img {
|
||||
height: 20px;
|
||||
aspect-ratio: initial;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
|
||||
#center-map::before {
|
||||
content: url("/resources/theme/images/icons/arrows-to-eye-solid.svg");
|
||||
}
|
||||
@ -486,4 +564,36 @@
|
||||
|
||||
#airbase-runways>.runway>.heading:last-of-type {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
/* Airbase spawn menu */
|
||||
#airbase-spawn-contextmenu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: fit-content;
|
||||
position: absolute;
|
||||
row-gap: 5px;
|
||||
width: 300px;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
#airbase-spawn-contextmenu>div:nth-child(2) {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-evenly;
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
#airbase-spawn-contextmenu >div:nth-child(n+3) {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
row-gap: 5px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
#airbase-spawn-contextmenu>div:nth-child(n+3)>div {
|
||||
width: 100%;
|
||||
}
|
||||
@ -136,19 +136,19 @@ body.feature-forceShowUnitControlPanel #unit-control-panel {
|
||||
}
|
||||
|
||||
#altitude-type-switch[data-value="true"]>.ol-switch-fill::before {
|
||||
content: "AGL";
|
||||
}
|
||||
|
||||
#altitude-type-switch[data-value="false"]>.ol-switch-fill::before {
|
||||
content: "ASL";
|
||||
}
|
||||
|
||||
#altitude-type-switch[data-value="false"]>.ol-switch-fill::before {
|
||||
content: "AGL";
|
||||
}
|
||||
|
||||
#speed-type-switch[data-value="true"]>.ol-switch-fill::before {
|
||||
content: "GS";
|
||||
content: "CAS";
|
||||
}
|
||||
|
||||
#speed-type-switch[data-value="false"]>.ol-switch-fill::before {
|
||||
content: "CAS";
|
||||
content: "GS";
|
||||
}
|
||||
|
||||
#unit-control-panel .ol-slider-value {
|
||||
|
||||
1416
client/public/stylesheets/style/style.css
Normal file
5
client/src/@types/dom.d.ts
vendored
@ -31,6 +31,11 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
export interface ConfigParameters {
|
||||
port: number;
|
||||
address: string;
|
||||
}
|
||||
|
||||
export interface ContextMenuOption {
|
||||
tooltip: string;
|
||||
src: string;
|
||||
|
||||
5
client/src/@types/server.d.ts
vendored
@ -44,4 +44,9 @@ interface LogData {
|
||||
logs: {[key: string]: string},
|
||||
sessionHash: string;
|
||||
time: number;
|
||||
}
|
||||
|
||||
interface ServerRequestOptions {
|
||||
time?: number;
|
||||
commandHash?: string;
|
||||
}
|
||||
50
client/src/@types/unit.d.ts
vendored
@ -1,5 +1,13 @@
|
||||
import { LatLng } from "leaflet"
|
||||
|
||||
interface UnitSpawnTable {
|
||||
unitType: string,
|
||||
location: latlng,
|
||||
altitude?: number,
|
||||
loadout?: string,
|
||||
liveryID: string
|
||||
}
|
||||
|
||||
interface ObjectIconOptions {
|
||||
showState: boolean,
|
||||
showVvi: boolean,
|
||||
@ -51,4 +59,46 @@ interface Offset {
|
||||
x: number,
|
||||
y: number,
|
||||
z: number
|
||||
}
|
||||
|
||||
interface UnitData {
|
||||
category: 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;
|
||||
heading: number;
|
||||
isTanker: boolean;
|
||||
isAWACS: 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;
|
||||
}
|
||||
18
client/src/@types/unitdatabase.d.ts
vendored
@ -1,3 +1,6 @@
|
||||
import { LatLng } from "leaflet";
|
||||
import { Airbase } from "../mission/airbase";
|
||||
|
||||
interface LoadoutItemBlueprint {
|
||||
name: string;
|
||||
quantity: number;
|
||||
@ -22,6 +25,19 @@ interface UnitBlueprint {
|
||||
range?: string;
|
||||
loadouts?: LoadoutBlueprint[];
|
||||
filename?: string;
|
||||
liveryID?: string;
|
||||
liveries?: {[key: string]: {name: string, countries: string[]}};
|
||||
cost?: number;
|
||||
}
|
||||
|
||||
interface UnitSpawnOptions {
|
||||
roleType: string;
|
||||
name: string;
|
||||
latlng: LatLng;
|
||||
coalition: string;
|
||||
count: number;
|
||||
country: string;
|
||||
loadout: LoadoutBlueprint | undefined;
|
||||
airbase: Airbase | undefined;
|
||||
liveryID: string | undefined;
|
||||
altitude: number | undefined;
|
||||
}
|
||||
|
||||
@ -1,172 +0,0 @@
|
||||
import { ToggleableFeature } from "../features/toggleablefeature";
|
||||
import { AICFormation_Azimuth } from "./aicformation/azimuth";
|
||||
import { AICFormation_Range } from "./aicformation/range";
|
||||
import { AICFormation_Single } from "./aicformation/single";
|
||||
import { AICFormationDescriptorSection } from "./aicformationdescriptorsection";
|
||||
|
||||
|
||||
export class AIC extends ToggleableFeature {
|
||||
|
||||
#formations = [
|
||||
|
||||
new AICFormation_Single(),
|
||||
new AICFormation_Range(),
|
||||
new AICFormation_Azimuth()
|
||||
|
||||
];
|
||||
|
||||
|
||||
constructor() {
|
||||
|
||||
super( false );
|
||||
|
||||
this.onStatusUpdate();
|
||||
|
||||
// This feels kind of dirty
|
||||
let $aicFormationList = document.getElementById( "aic-formation-list" );
|
||||
|
||||
if ( $aicFormationList ) {
|
||||
|
||||
this.getFormations().forEach( formation => {
|
||||
|
||||
// Image
|
||||
let $imageDiv = document.createElement( "div" );
|
||||
$imageDiv.classList.add( "aic-formation-image" );
|
||||
|
||||
let $img = document.createElement( "img" );
|
||||
$img.src = "images/formations/" + formation.icon;
|
||||
|
||||
$imageDiv.appendChild( $img );
|
||||
|
||||
// Name
|
||||
let $nameDiv = document.createElement( "div" );
|
||||
$nameDiv.classList.add( "aic-formation-name" );
|
||||
$nameDiv.innerText = formation.label;
|
||||
|
||||
// Wrapper
|
||||
let $wrapperDiv = document.createElement( "div" );
|
||||
$wrapperDiv.dataset.formationName = formation.name;
|
||||
$wrapperDiv.appendChild( $imageDiv )
|
||||
$wrapperDiv.appendChild( $nameDiv );
|
||||
$wrapperDiv.addEventListener( "click", ( ev ) => {
|
||||
|
||||
const controlTypeInput = document.querySelector( "input[type='radio'][name='control-type']:checked" );
|
||||
|
||||
let controlTypeValue:any = ( controlTypeInput instanceof HTMLInputElement && [ "broadcast", "tactical" ].indexOf( controlTypeInput.value ) > -1 ) ? controlTypeInput.value : "broadcast";
|
||||
|
||||
// TODO: make this not an "any"
|
||||
const output:any = formation.getDescriptor({
|
||||
"aicCallsign" : "Magic",
|
||||
"bullseyeName" : "Bullseye",
|
||||
"control" : controlTypeValue,
|
||||
"numGroups" : formation.numGroups
|
||||
});
|
||||
|
||||
this.updateTeleprompt( output );
|
||||
|
||||
});
|
||||
|
||||
// Add to DOM
|
||||
$aicFormationList?.appendChild( $wrapperDiv );
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
getFormations() {
|
||||
return this.#formations;
|
||||
}
|
||||
|
||||
|
||||
onStatusUpdate() {
|
||||
|
||||
// Update the DOM
|
||||
document.body.classList.toggle( "aic-enabled", this.getStatus() );
|
||||
|
||||
}
|
||||
|
||||
|
||||
toggleHelp() {
|
||||
document.getElementById( "aic-help" )?.classList.toggle( "hide" );
|
||||
}
|
||||
|
||||
//*
|
||||
updateTeleprompt<T extends AICFormationDescriptorSection>( descriptor:T[] ) {
|
||||
|
||||
let $teleprompt = document.getElementById( "aic-teleprompt" );
|
||||
|
||||
if ( $teleprompt instanceof HTMLElement ) {
|
||||
|
||||
// Clean slate
|
||||
while ( $teleprompt.childNodes.length > 0 ) {
|
||||
$teleprompt.childNodes[0].remove();
|
||||
}
|
||||
|
||||
function newDiv() {
|
||||
return document.createElement( "div" );
|
||||
}
|
||||
|
||||
// Wrapper
|
||||
let $descriptor = newDiv();
|
||||
$descriptor.id = "aic-descriptor";
|
||||
|
||||
for ( const section of descriptor ) {
|
||||
|
||||
if ( section.omitSection ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let $section = newDiv();
|
||||
$section.classList.add( "aic-descriptor-section" );
|
||||
|
||||
let $sectionLabel = newDiv();
|
||||
$sectionLabel.classList.add( "aic-descriptor-section-label" );
|
||||
$sectionLabel.innerText = section.label;
|
||||
$section.appendChild( $sectionLabel );
|
||||
|
||||
|
||||
for ( const phrase of section.getPhrases() ) {
|
||||
|
||||
let $phrase = newDiv();
|
||||
$phrase.classList.add( "aic-descriptor-phrase" );
|
||||
|
||||
for ( const component of phrase.getComponents() ) {
|
||||
|
||||
let $component = newDiv();
|
||||
$component.classList.add( "aic-descriptor-component" );
|
||||
|
||||
let $componentLabel = newDiv();
|
||||
$componentLabel.classList.add( "aic-descriptor-component-label" );
|
||||
$componentLabel.innerText = component.label;
|
||||
|
||||
let $componentValue = newDiv();
|
||||
$componentValue.classList.add( "aic-descriptor-component-value" );
|
||||
$componentValue.innerText = component.value;
|
||||
|
||||
$component.appendChild( $componentLabel );
|
||||
$component.appendChild( $componentValue );
|
||||
|
||||
$phrase.appendChild( $component );
|
||||
|
||||
}
|
||||
|
||||
$section.appendChild( $phrase );
|
||||
|
||||
}
|
||||
|
||||
$descriptor.appendChild( $section );
|
||||
|
||||
}
|
||||
|
||||
$teleprompt.appendChild( $descriptor );
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
//*/
|
||||
|
||||
}
|
||||
@ -1,54 +0,0 @@
|
||||
import { AICFormationContextDataInterface, AICFormationDescriptor } from "./aicformationdescriptor";
|
||||
import { AICFormationDescriptorPhrase } from "./aicformationdescriptorphrase";
|
||||
import { AICFormationDescriptorSection } from "./aicformationdescriptorsection";
|
||||
|
||||
export interface AICFormationInterface {
|
||||
"icon" : string,
|
||||
"label" : string,
|
||||
"name" : string,
|
||||
"numGroups" : number,
|
||||
"summary" : string,
|
||||
"unitBreakdown" : string[]
|
||||
}
|
||||
|
||||
|
||||
|
||||
export abstract class AICFormation {
|
||||
|
||||
"icon" = "";
|
||||
"label" = "";
|
||||
"name" = "";
|
||||
"numGroups" = 1;
|
||||
"summary" = "";
|
||||
"unitBreakdown":string[] = []
|
||||
|
||||
|
||||
constructor() {
|
||||
|
||||
this.unitBreakdown = [];
|
||||
}
|
||||
|
||||
|
||||
addToDescriptorPhrase( section: AICFormationDescriptorSection, phrase: AICFormationDescriptorPhrase, contextData: AICFormationContextDataInterface ) {
|
||||
return phrase;
|
||||
}
|
||||
|
||||
|
||||
getDescriptor( contextData: AICFormationContextDataInterface ) {
|
||||
|
||||
return new AICFormationDescriptor().generate( this, contextData );
|
||||
|
||||
}
|
||||
|
||||
|
||||
hasUnitBreakdown() {
|
||||
return this.unitBreakdown.length > 0;
|
||||
}
|
||||
|
||||
|
||||
showFormationNameInDescriptor() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -1,38 +0,0 @@
|
||||
import { AICFormation, AICFormationInterface } from "../aicformation";
|
||||
import { AICFormationContextDataInterface } from "../aicformationdescriptor";
|
||||
import { AICFormationDescriptorSection } from "../aicformationdescriptorsection";
|
||||
import { AICFormationDescriptorComponent } from "../aicformationdescriptorcomponent";
|
||||
import { AICFormationDescriptorPhrase } from "../aicformationdescriptorphrase";
|
||||
|
||||
export class AICFormation_Azimuth extends AICFormation implements AICFormationInterface {
|
||||
|
||||
"icon" = "azimuth.png";
|
||||
"label" = "Azimuth";
|
||||
"name" = "azimuth";
|
||||
"numGroups" = 2;
|
||||
"summary" = "Two contacts, side-by-side in a line perpedicular to the perspective.";
|
||||
"unitBreakdown" = [ "<compass> group", "<compass> group" ];
|
||||
|
||||
constructor() {
|
||||
|
||||
super();
|
||||
|
||||
}
|
||||
|
||||
addToDescriptorPhrase( section: AICFormationDescriptorSection, phrase: AICFormationDescriptorPhrase, contextData: AICFormationContextDataInterface ) {
|
||||
|
||||
switch ( section.name ) {
|
||||
|
||||
case "formation":
|
||||
|
||||
phrase.addComponent( new AICFormationDescriptorComponent( "<distance>" ) );
|
||||
phrase.addComponent( new AICFormationDescriptorComponent( "track <compass>" ) );
|
||||
|
||||
}
|
||||
|
||||
return phrase;
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,38 +0,0 @@
|
||||
import { AICFormation, AICFormationInterface } from "../aicformation";
|
||||
import { AICFormationContextDataInterface } from "../aicformationdescriptor";
|
||||
import { AICFormationDescriptorSection } from "../aicformationdescriptorsection";
|
||||
import { AICFormationDescriptorComponent } from "../aicformationdescriptorcomponent";
|
||||
import { AICFormationDescriptorPhrase } from "../aicformationdescriptorphrase";
|
||||
|
||||
export class AICFormation_Range extends AICFormation implements AICFormationInterface {
|
||||
|
||||
"icon" = "range.png";
|
||||
"label" = "Range";
|
||||
"name" = "range";
|
||||
"numGroups" = 2;
|
||||
"summary" = "Two contacts, one behind the other";
|
||||
"unitBreakdown" = [ "Lead group", "Trail group" ];
|
||||
|
||||
constructor() {
|
||||
|
||||
super();
|
||||
|
||||
}
|
||||
|
||||
addToDescriptorPhrase( section: AICFormationDescriptorSection, phrase: AICFormationDescriptorPhrase, contextData: AICFormationContextDataInterface ) {
|
||||
|
||||
switch ( section.name ) {
|
||||
|
||||
case "formation":
|
||||
|
||||
phrase.addComponent( new AICFormationDescriptorComponent( "<distance>" ) );
|
||||
phrase.addComponent( new AICFormationDescriptorComponent( "track <compass>" ) );
|
||||
|
||||
|
||||
}
|
||||
|
||||
return phrase;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
import { AICFormation, AICFormationInterface } from "../aicformation";
|
||||
import { AICFormationContextDataInterface, AICFormationDescriptor } from "../aicformationdescriptor";
|
||||
|
||||
export class AICFormation_Single extends AICFormation implements AICFormationInterface {
|
||||
|
||||
"icon" = "single.png";
|
||||
"label" = "Single";
|
||||
"name" = "single";
|
||||
"numGroups" = 1;
|
||||
"summary" = "One contact on its own";
|
||||
"unitBreakdown": string[] = [];
|
||||
|
||||
constructor() {
|
||||
|
||||
super();
|
||||
|
||||
}
|
||||
|
||||
|
||||
showFormationNameInDescriptor() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,55 +0,0 @@
|
||||
import { AICFormation } from "./aicformation";
|
||||
import { AICFormationDescriptorSection } from "./aicformationdescriptorsection";
|
||||
import { AICFormationDescriptorSection_Formation } from "./aicformationdescriptorsection/formation";
|
||||
import { AICFormationDescriptorSection_Unit } from "./aicformationdescriptorsection/unit";
|
||||
import { AICFormationDescriptorSection_NumGroups } from "./aicformationdescriptorsection/numgroups";
|
||||
import { AICFormationDescriptorSection_Who } from "./aicformationdescriptorsection/who";
|
||||
|
||||
|
||||
export interface AICFormationContextDataInterface {
|
||||
"aicCallsign" : string,
|
||||
"bullseyeName" : string,
|
||||
"control" : "broadcast" | "tactical",
|
||||
"numGroups" : number
|
||||
}
|
||||
|
||||
|
||||
export class AICFormationDescriptor {
|
||||
|
||||
#sections:AICFormationDescriptorSection[] = [
|
||||
new AICFormationDescriptorSection_Who(),
|
||||
new AICFormationDescriptorSection_NumGroups(),
|
||||
new AICFormationDescriptorSection_Formation(),
|
||||
new AICFormationDescriptorSection_Unit()
|
||||
]
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
|
||||
addSection( section:AICFormationDescriptorSection ) {
|
||||
this.#sections.push( section );
|
||||
}
|
||||
|
||||
|
||||
getSections() {
|
||||
return this.#sections;
|
||||
}
|
||||
|
||||
|
||||
generate( formation:AICFormation, contextData: AICFormationContextDataInterface ) {
|
||||
|
||||
let output:object[] = [];
|
||||
|
||||
for ( const section of this.#sections ) {
|
||||
output.push(
|
||||
section.generate( formation, contextData )
|
||||
);
|
||||
}
|
||||
|
||||
return output;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
interface ComponentInterface {
|
||||
"label" : string;
|
||||
"value" : string;
|
||||
}
|
||||
|
||||
export class AICFormationDescriptorComponent implements ComponentInterface {
|
||||
|
||||
label = "(not set)";
|
||||
value = "(not set)";
|
||||
|
||||
constructor( value:any, label?:string ) {
|
||||
|
||||
this.label = label || "(not set)";
|
||||
this.value = value;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
import { AICFormationDescriptorComponent } from "../aicformationdescriptorcomponent";
|
||||
|
||||
export abstract class AICFormactionDescriptorComponent_Distance extends AICFormationDescriptorComponent {
|
||||
|
||||
constructor( value:string, label?:string ) {
|
||||
super( value, label );
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
import { AICFormactionDescriptorComponent_Distance } from "../distance";
|
||||
|
||||
export class AICFormationDescriptorComponent_Distance_Range extends AICFormactionDescriptorComponent_Distance {
|
||||
|
||||
constructor( value:string, label?:string ) {
|
||||
super( value, label );
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
import { AICFormation } from "./aicformation";
|
||||
import { AICFormationContextDataInterface } from "./aicformationdescriptor";
|
||||
import { AICFormationDescriptorComponent } from "./aicformationdescriptorcomponent";
|
||||
|
||||
export interface AICFormationDescriptorPhraseInterface {
|
||||
"generate" : CallableFunction,
|
||||
"label" : string,
|
||||
"name" : string
|
||||
}
|
||||
|
||||
export class AICFormationDescriptorPhrase {
|
||||
|
||||
#components : AICFormationDescriptorComponent[] = [];
|
||||
label = "";
|
||||
name = "";
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
|
||||
addComponent( component:AICFormationDescriptorComponent ) {
|
||||
this.#components.push( component );
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
getComponents() {
|
||||
return this.#components;
|
||||
}
|
||||
|
||||
|
||||
generate( formation:AICFormation, contextData: AICFormationContextDataInterface ) {
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
import { AICFormation } from "./aicformation";
|
||||
import { AICFormationContextDataInterface } from "./aicformationdescriptor";
|
||||
import { AICFormationDescriptorPhrase } from "./aicformationdescriptorphrase";
|
||||
|
||||
export interface AICFormationDescriptorSectionInterface {
|
||||
"generate" : CallableFunction,
|
||||
"label" : string,
|
||||
"name" : string,
|
||||
"omitSection" : boolean
|
||||
}
|
||||
|
||||
export abstract class AICFormationDescriptorSection {
|
||||
|
||||
#phrases : AICFormationDescriptorPhrase[] = [];
|
||||
label = "";
|
||||
name = "";
|
||||
omitSection = false;
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
|
||||
addPhrase( phrase:AICFormationDescriptorPhrase ) {
|
||||
this.#phrases.push( phrase );
|
||||
}
|
||||
|
||||
|
||||
generate( formation:AICFormation, contextData: AICFormationContextDataInterface ) {
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
|
||||
getPhrases() {
|
||||
return this.#phrases;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -1,39 +0,0 @@
|
||||
import { AICFormation } from "../aicformation";
|
||||
import { AICFormationContextDataInterface } from "../aicformationdescriptor";
|
||||
import { AICFormationDescriptorSection } from "../aicformationdescriptorsection";
|
||||
import { AICFormationDescriptorComponent } from "../aicformationdescriptorcomponent";
|
||||
import { AICFormationDescriptorPhrase } from "../aicformationdescriptorphrase";
|
||||
|
||||
export class AICFormationDescriptorSection_Formation extends AICFormationDescriptorSection {
|
||||
|
||||
label = "Formation";
|
||||
name = "formation";
|
||||
|
||||
constructor() {
|
||||
|
||||
super();
|
||||
|
||||
}
|
||||
|
||||
|
||||
generate( formation:AICFormation, contextData: AICFormationContextDataInterface ) {
|
||||
|
||||
if ( !formation.showFormationNameInDescriptor() ) {
|
||||
this.omitSection = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
let phrase = new AICFormationDescriptorPhrase();
|
||||
|
||||
phrase.addComponent( new AICFormationDescriptorComponent( formation.label, "Formation" ) );
|
||||
|
||||
phrase = formation.addToDescriptorPhrase( this, phrase, contextData );
|
||||
|
||||
this.addPhrase( phrase );
|
||||
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,35 +0,0 @@
|
||||
import { AICFormation } from "../aicformation";
|
||||
import { AICFormationContextDataInterface } from "../aicformationdescriptor";
|
||||
import { AICFormationDescriptorSection } from "../aicformationdescriptorsection";
|
||||
import { AICFormationDescriptorComponent } from "../aicformationdescriptorcomponent";
|
||||
import { AICFormationDescriptorPhrase } from "../aicformationdescriptorphrase";
|
||||
|
||||
export class AICFormationDescriptorSection_NumGroups extends AICFormationDescriptorSection {
|
||||
|
||||
label = "Groups";
|
||||
name = "numgroups";
|
||||
|
||||
constructor() {
|
||||
|
||||
super();
|
||||
|
||||
}
|
||||
|
||||
|
||||
generate( formation:AICFormation, contextData: AICFormationContextDataInterface ) {
|
||||
|
||||
let value = "Single group";
|
||||
|
||||
if ( contextData.numGroups > 1 ) {
|
||||
value = contextData.numGroups + " groups";
|
||||
}
|
||||
|
||||
let phrase = new AICFormationDescriptorPhrase();
|
||||
phrase.addComponent( new AICFormationDescriptorComponent( value, "Number of groups" ) );
|
||||
this.addPhrase( phrase );
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,83 +0,0 @@
|
||||
import { AICFormation } from "../aicformation";
|
||||
import { AICFormationContextDataInterface } from "../aicformationdescriptor";
|
||||
import { AICFormationDescriptorSection } from "../aicformationdescriptorsection";
|
||||
import { AICFormationDescriptorComponent } from "../aicformationdescriptorcomponent";
|
||||
import { AICFormationDescriptorPhrase } from "../aicformationdescriptorphrase";
|
||||
|
||||
interface addUnitInformationInterface {
|
||||
omitTrack?: boolean
|
||||
}
|
||||
|
||||
export class AICFormationDescriptorSection_Unit extends AICFormationDescriptorSection {
|
||||
|
||||
label = "Unit";
|
||||
name = "unit";
|
||||
|
||||
constructor() {
|
||||
|
||||
super();
|
||||
|
||||
}
|
||||
|
||||
|
||||
addUnitInformation( formation:AICFormation, contextData: AICFormationContextDataInterface, phrase: AICFormationDescriptorPhrase, options?:addUnitInformationInterface ) {
|
||||
|
||||
options = options || {};
|
||||
|
||||
const originPoint = ( contextData.control === "broadcast" ) ? contextData.bullseyeName : "BRAA";
|
||||
|
||||
phrase.addComponent( new AICFormationDescriptorComponent( originPoint, "Bearing origin point" ) );
|
||||
phrase.addComponent( new AICFormationDescriptorComponent( "<bearing>", "Bearing" ) );
|
||||
phrase.addComponent( new AICFormationDescriptorComponent( "<range>", "Range" ) );
|
||||
phrase.addComponent( new AICFormationDescriptorComponent( "<altitude>", "Altitude" ) );
|
||||
|
||||
if ( contextData.control === "broadcast" ) {
|
||||
if ( !options.hasOwnProperty( "omitTrack" ) || options.omitTrack !== true ) {
|
||||
phrase.addComponent( new AICFormationDescriptorComponent( "track <compass>", "Tracking" ) );
|
||||
}
|
||||
} else {
|
||||
phrase.addComponent( new AICFormationDescriptorComponent( "[hot|flanking [left|right]|beam <compass>|cold]", "Azimuth" ) );
|
||||
}
|
||||
|
||||
return phrase;
|
||||
|
||||
}
|
||||
|
||||
|
||||
generate( formation:AICFormation, contextData: AICFormationContextDataInterface ) {
|
||||
|
||||
if ( formation.hasUnitBreakdown() ) {
|
||||
|
||||
for ( const [ i, unitRef ] of formation.unitBreakdown.entries() ) {
|
||||
|
||||
let phrase = new AICFormationDescriptorPhrase();
|
||||
|
||||
phrase.addComponent( new AICFormationDescriptorComponent( unitRef, "Unit reference" ) );
|
||||
|
||||
if ( i === 0 ) {
|
||||
this.addUnitInformation( formation, contextData, phrase, { "omitTrack": true } );
|
||||
} else {
|
||||
phrase.addComponent( new AICFormationDescriptorComponent( "<altitude>" ) );
|
||||
}
|
||||
|
||||
phrase.addComponent( new AICFormationDescriptorComponent( "hostile" ) );
|
||||
|
||||
this.addPhrase( phrase );
|
||||
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
this.addPhrase(
|
||||
this.addUnitInformation( formation, contextData, new AICFormationDescriptorPhrase() )
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -1,35 +0,0 @@
|
||||
import { AICFormation } from "../aicformation";
|
||||
import { AICFormationContextDataInterface } from "../aicformationdescriptor";
|
||||
import { AICFormationDescriptorSection } from "../aicformationdescriptorsection";
|
||||
import { AICFormationDescriptorComponent } from "../aicformationdescriptorcomponent";
|
||||
import { AICFormationDescriptorPhrase } from "../aicformationdescriptorphrase";
|
||||
|
||||
export class AICFormationDescriptorSection_Who extends AICFormationDescriptorSection {
|
||||
|
||||
label = "Who";
|
||||
name = "who";
|
||||
|
||||
constructor() {
|
||||
|
||||
super();
|
||||
|
||||
}
|
||||
|
||||
|
||||
generate( formation:AICFormation, contextData: AICFormationContextDataInterface ) {
|
||||
|
||||
let phrase = new AICFormationDescriptorPhrase();
|
||||
|
||||
if ( contextData.control === "tactical" ) {
|
||||
phrase.addComponent( new AICFormationDescriptorComponent( "<their callsign>", "Their callsign" ) );
|
||||
}
|
||||
|
||||
phrase.addComponent( new AICFormationDescriptorComponent( contextData.aicCallsign, "Your callsign" ) );
|
||||
|
||||
this.addPhrase( phrase );
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,188 +0,0 @@
|
||||
import { getMissionHandler } from "..";
|
||||
import { convertDateAndTimeToDate } from "../other/utils";
|
||||
import { getConnected } from "../server/server";
|
||||
import { ATCBoard } from "./atcboard";
|
||||
import { ATCBoardGround } from "./board/ground";
|
||||
import { ATCBoardTower } from "./board/tower";
|
||||
|
||||
export interface FlightInterface {
|
||||
assignedSpeed: any;
|
||||
assignedAltitude : any;
|
||||
id : string;
|
||||
boardId : string;
|
||||
name : string;
|
||||
order : number;
|
||||
status : "unknown";
|
||||
takeoffTime : number;
|
||||
unitId : number;
|
||||
}
|
||||
|
||||
|
||||
class ATCDataHandler {
|
||||
|
||||
#atc:ATC;
|
||||
#flights:{[key:string]: FlightInterface} = {};
|
||||
|
||||
#updateInterval:number|undefined = undefined;
|
||||
#updateIntervalDelay:number = 2500; // Wait between unit update requests
|
||||
|
||||
|
||||
constructor( atc:ATC ) {
|
||||
|
||||
this.#atc = atc;
|
||||
|
||||
}
|
||||
|
||||
|
||||
getFlights( boardId:string ) {
|
||||
|
||||
return Object.values( this.#flights ).reduce( ( acc:{[key:string]: FlightInterface}, flight ) => {
|
||||
|
||||
if ( flight.boardId === boardId ) {
|
||||
acc[ flight.id ] = flight;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {} );
|
||||
}
|
||||
|
||||
|
||||
startUpdates() {
|
||||
|
||||
this.#updateInterval = window.setInterval( () => {
|
||||
|
||||
if ( !getConnected() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const aBoardIsVisible = this.#atc.getBoards().some( board => board.boardIsVisible() );
|
||||
|
||||
if ( aBoardIsVisible ) {
|
||||
|
||||
fetch( '/api/atc/flight', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': '*/*',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
.then( response => response.json() )
|
||||
.then( data => {
|
||||
this.setFlights( data );
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}, this.#updateIntervalDelay );
|
||||
|
||||
}
|
||||
|
||||
|
||||
setFlights( flights:{[key:string]: any} ) {
|
||||
|
||||
this.#flights = flights;
|
||||
|
||||
}
|
||||
|
||||
|
||||
stopUpdates() {
|
||||
|
||||
clearInterval( this.#updateInterval );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export class ATC {
|
||||
|
||||
#boards:ATCBoard[] = [];
|
||||
#dataHandler:ATCDataHandler;
|
||||
|
||||
#initDate:Date = new Date();
|
||||
|
||||
constructor() {
|
||||
|
||||
this.#dataHandler = new ATCDataHandler( this );
|
||||
|
||||
this.lookForBoards();
|
||||
|
||||
}
|
||||
|
||||
|
||||
addBoard<T extends ATCBoard>( board:T ) {
|
||||
|
||||
board.startUpdates();
|
||||
|
||||
this.#boards.push( board );
|
||||
|
||||
}
|
||||
|
||||
|
||||
getBoards() {
|
||||
return this.#boards;
|
||||
}
|
||||
|
||||
|
||||
getDataHandler() {
|
||||
return this.#dataHandler;
|
||||
}
|
||||
|
||||
|
||||
getMissionElapsedSeconds() : number {
|
||||
return new Date().getTime() - this.#initDate.getTime();
|
||||
}
|
||||
|
||||
|
||||
getMissionStartDateTime() : Date {
|
||||
return new Date( 1990, 3, 1, 18, 0, 0 );
|
||||
}
|
||||
|
||||
|
||||
getMissionDate() : Date {
|
||||
return convertDateAndTimeToDate(getMissionHandler().getDateAndTime());
|
||||
}
|
||||
|
||||
lookForBoards() {
|
||||
|
||||
document.querySelectorAll( ".ol-strip-board" ).forEach( board => {
|
||||
|
||||
if ( board instanceof HTMLElement ) {
|
||||
|
||||
switch ( board.dataset.boardType ) {
|
||||
|
||||
case "ground":
|
||||
this.addBoard( new ATCBoardGround( this, board ) );
|
||||
return;
|
||||
|
||||
case "tower":
|
||||
this.addBoard( new ATCBoardTower( this, board ) );
|
||||
return;
|
||||
|
||||
default:
|
||||
console.warn( "Unknown board type for ATC board, got: " + board.dataset.boardType );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
startUpdates() {
|
||||
|
||||
this.#dataHandler.startUpdates();
|
||||
|
||||
}
|
||||
|
||||
|
||||
stopUpdates() {
|
||||
|
||||
this.#dataHandler.stopUpdates();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,527 +0,0 @@
|
||||
import { Dropdown } from "../controls/dropdown";
|
||||
import { zeroAppend } from "../other/utils";
|
||||
import { ATC } from "./atc";
|
||||
import { Unit } from "../unit/unit";
|
||||
import { getMissionHandler, getUnitsManager } from "..";
|
||||
import Sortable from "sortablejs";
|
||||
import { FlightInterface } from "./atc";
|
||||
import { getConnected } from "../server/server";
|
||||
|
||||
export interface StripBoardStripInterface {
|
||||
"id": string,
|
||||
"element": HTMLElement,
|
||||
"dropdowns": {[key:string]: Dropdown},
|
||||
"isDeleted"?: boolean,
|
||||
"unitId": number
|
||||
}
|
||||
|
||||
export abstract class ATCBoard {
|
||||
|
||||
#atc:ATC;
|
||||
#boardId:string = "";
|
||||
#templates: {[key:string]: string} = {};
|
||||
|
||||
|
||||
// Elements
|
||||
#boardElement:HTMLElement;
|
||||
#clockElement:HTMLElement;
|
||||
#stripBoardElement:HTMLElement;
|
||||
|
||||
// Content
|
||||
#isAddFlightByClickEnabled:boolean = false;
|
||||
#strips:{[key:string]: StripBoardStripInterface} = {};
|
||||
#unitIdsBeingMonitored:number[] = [];
|
||||
|
||||
// Update timing
|
||||
#updateInterval:number|undefined = undefined;
|
||||
#updateIntervalDelay:number = 1000;
|
||||
|
||||
|
||||
constructor( atc:ATC, boardElement:HTMLElement, options?:{[key:string]: any} ) {
|
||||
|
||||
options = options || {};
|
||||
|
||||
this.#atc = atc;
|
||||
this.#boardElement = boardElement;
|
||||
this.#stripBoardElement = <HTMLElement>this.getBoardElement().querySelector( ".ol-strip-board-strips" );
|
||||
this.#clockElement = <HTMLElement>this.getBoardElement().querySelector( ".ol-strip-board-clock" );
|
||||
|
||||
|
||||
new MutationObserver( () => {
|
||||
if ( this.boardIsVisible() ) {
|
||||
this.startUpdates();
|
||||
} else {
|
||||
this.stopUpdates();
|
||||
}
|
||||
}).observe( this.getBoardElement(), {
|
||||
"attributes": true,
|
||||
"childList": false,
|
||||
"subtree": false
|
||||
});
|
||||
|
||||
|
||||
new Sortable( this.getStripBoardElement(), {
|
||||
"handle": ".handle",
|
||||
"onUpdate": ev => {
|
||||
|
||||
const order = [].slice.call( this.getStripBoardElement().children ).map( ( strip:HTMLElement ) => {
|
||||
return strip.dataset.flightId
|
||||
});
|
||||
|
||||
fetch( '/api/atc/flight/order', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': '*/*',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
"body": JSON.stringify({
|
||||
"boardId" : this.getBoardId(),
|
||||
"order" : order
|
||||
})
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
window.setInterval( () => {
|
||||
|
||||
if ( !getConnected() ) {
|
||||
return;
|
||||
}
|
||||
this.updateClock();
|
||||
}, 1000 );
|
||||
|
||||
|
||||
if ( this.#boardElement.classList.contains( "ol-draggable" ) ) {
|
||||
|
||||
let options:any = {};
|
||||
|
||||
let handle = this.#boardElement.querySelector( ".handle" );
|
||||
|
||||
if ( handle instanceof HTMLElement ) {
|
||||
options.handle = handle;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
this.#setupAddFlight();
|
||||
|
||||
// this.#_setupDemoData();
|
||||
|
||||
}
|
||||
|
||||
|
||||
addFlight( unit:Unit ) {
|
||||
|
||||
const baseData = unit.getData();
|
||||
|
||||
const unitCanBeAdded = () => {
|
||||
|
||||
if ( unit.getCategory() !== "Aircraft" ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( baseData.controlled === true ) {
|
||||
// return false;
|
||||
}
|
||||
|
||||
if ( this.#unitIdsBeingMonitored.includes( unit.ID ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( !unitCanBeAdded() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#unitIdsBeingMonitored.push( unit.ID );
|
||||
|
||||
return fetch( '/api/atc/flight/', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': '*/*',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
"body": JSON.stringify({
|
||||
"boardId" : this.getBoardId(),
|
||||
"name" : baseData.unitName,
|
||||
"unitId" : unit.ID
|
||||
})
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
addStrip( strip:StripBoardStripInterface ) {
|
||||
|
||||
this.#strips[ strip.id ] = strip;
|
||||
|
||||
strip.element.querySelectorAll( "button.deleteFlight" ).forEach( btn => {
|
||||
btn.addEventListener( "click", ev => {
|
||||
ev.preventDefault();
|
||||
this.deleteFlight( strip.id );
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
boardIsVisible() {
|
||||
return ( !this.getBoardElement().classList.contains( "hide" ) );
|
||||
}
|
||||
|
||||
|
||||
calculateTimeToGo( fromTimestamp:number, toTimestamp:number ) {
|
||||
|
||||
let timestamp = ( toTimestamp - fromTimestamp ) / 1000;
|
||||
|
||||
const hasElapsed = ( timestamp < 0 ) ? true : false;
|
||||
|
||||
if ( hasElapsed ) {
|
||||
timestamp = -( timestamp );
|
||||
}
|
||||
|
||||
const hours = ( timestamp < 3600 ) ? "00" : zeroAppend( Math.floor( timestamp / 3600 ), 2 );
|
||||
const rMinutes = timestamp % 3600;
|
||||
|
||||
const minutes = ( timestamp < 60 ) ? "00" : zeroAppend( Math.floor( rMinutes / 60 ), 2 );
|
||||
const seconds = zeroAppend( Math.floor( rMinutes % 60 ), 2 );
|
||||
|
||||
return {
|
||||
"elapsedMarker": ( hasElapsed ) ? "+" : "-",
|
||||
"hasElapsed": hasElapsed,
|
||||
"hours": hours,
|
||||
"minutes": minutes,
|
||||
"seconds": seconds,
|
||||
"time": `${hours}:${minutes}:${seconds}`,
|
||||
"totalSeconds": timestamp
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
deleteStrip( flightId:string ) {
|
||||
|
||||
if ( this.#strips.hasOwnProperty( flightId ) ) {
|
||||
|
||||
this.#strips[ flightId ].element.remove();
|
||||
this.#strips[ flightId ].isDeleted = true;
|
||||
|
||||
window.setTimeout( () => {
|
||||
delete this.#strips[ flightId ];
|
||||
}, 10000 );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
deleteFlight( flightId:string ) {
|
||||
|
||||
this.deleteStrip( flightId );
|
||||
|
||||
fetch( '/api/atc/flight/' + flightId, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Accept': '*/*',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
"body": JSON.stringify({
|
||||
"boardId": this.getBoardId()
|
||||
})
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
getATC() {
|
||||
return this.#atc;
|
||||
}
|
||||
|
||||
|
||||
getBoardElement() {
|
||||
return this.#boardElement;
|
||||
}
|
||||
|
||||
|
||||
getBoardId(): string {
|
||||
return this.getBoardElement().id;
|
||||
}
|
||||
|
||||
|
||||
getStripBoardElement() {
|
||||
return this.#stripBoardElement;
|
||||
}
|
||||
|
||||
|
||||
getStrips() {
|
||||
return this.#strips;
|
||||
}
|
||||
|
||||
|
||||
getStrip( id:string ) {
|
||||
return this.#strips[ id ] || false;
|
||||
}
|
||||
|
||||
|
||||
getTemplate( key:string ) {
|
||||
|
||||
return this.#templates[ key ] || false;
|
||||
|
||||
}
|
||||
|
||||
|
||||
getUnitIdsBeingMonitored() {
|
||||
|
||||
return this.#unitIdsBeingMonitored;
|
||||
|
||||
}
|
||||
|
||||
|
||||
setTemplates( templates:{[key:string]: string} ) {
|
||||
this.#templates = templates;
|
||||
}
|
||||
|
||||
|
||||
#setupAddFlight() {
|
||||
|
||||
const toggleIsAddFlightByClickEnabled = () => {
|
||||
this.#isAddFlightByClickEnabled = ( !this.#isAddFlightByClickEnabled );
|
||||
this.getBoardElement().classList.toggle( "add-flight-by-click", this.#isAddFlightByClickEnabled );
|
||||
}
|
||||
|
||||
|
||||
document.addEventListener( "unitSelection", ( ev:CustomEventInit ) => {
|
||||
|
||||
if ( this.#isAddFlightByClickEnabled !== true ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.addFlight( ev.detail );
|
||||
|
||||
toggleIsAddFlightByClickEnabled();
|
||||
|
||||
});
|
||||
|
||||
|
||||
const form = <HTMLElement>this.getBoardElement().querySelector( "form.ol-strip-board-add-flight" );
|
||||
const suggestions = <HTMLElement>form.querySelector( ".ol-auto-suggest" );
|
||||
const unitName = <HTMLInputElement>form.querySelector( "input[name='unitName']" );
|
||||
|
||||
const toggleSuggestions = ( bool:boolean ) => {
|
||||
suggestions.toggleAttribute( "data-has-suggestions", bool );
|
||||
}
|
||||
|
||||
let searchTimeout:number|null;
|
||||
|
||||
unitName.addEventListener( "keyup", ev => {
|
||||
|
||||
if ( searchTimeout ) {
|
||||
clearTimeout( searchTimeout );
|
||||
}
|
||||
|
||||
const resetSuggestions = () => {
|
||||
suggestions.innerHTML = "";
|
||||
toggleSuggestions( false );
|
||||
}
|
||||
|
||||
resetSuggestions();
|
||||
|
||||
searchTimeout = window.setTimeout( () => {
|
||||
|
||||
const searchString = unitName.value.toLowerCase();
|
||||
|
||||
if ( searchString === "" ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const units = getUnitsManager().getSelectableAircraft();
|
||||
const unitIdsBeingMonitored = this.getUnitIdsBeingMonitored();
|
||||
|
||||
const results = Object.keys( units ).reduce( ( acc:Unit[], unitId:any ) => {
|
||||
|
||||
const unit = units[ unitId ];
|
||||
const baseData = unit.getData();
|
||||
|
||||
if ( !unitIdsBeingMonitored.includes( parseInt( unitId ) ) && baseData.unitName.toLowerCase().indexOf( searchString ) > -1 ) {
|
||||
acc.push( unit );
|
||||
}
|
||||
|
||||
return acc;
|
||||
|
||||
}, [] );
|
||||
|
||||
toggleSuggestions( results.length > 0 );
|
||||
|
||||
results.forEach( unit => {
|
||||
|
||||
const baseData = unit.getData();
|
||||
|
||||
const a = document.createElement( "a" );
|
||||
a.innerText = baseData.unitName;
|
||||
|
||||
a.addEventListener( "click", ev => {
|
||||
this.addFlight( unit );
|
||||
resetSuggestions();
|
||||
unitName.value = "";
|
||||
});
|
||||
|
||||
suggestions.appendChild( a );
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
}, 1000 );
|
||||
|
||||
|
||||
});
|
||||
|
||||
form.querySelectorAll( ".add-flight-by-click" ).forEach( el => {
|
||||
el.addEventListener( "click", ev => {
|
||||
ev.preventDefault();
|
||||
toggleIsAddFlightByClickEnabled();
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
sortFlights( flights:FlightInterface[] ) {
|
||||
|
||||
flights.sort( ( a, b ) => {
|
||||
|
||||
const aVal = a.order;
|
||||
const bVal = b.order;
|
||||
|
||||
return ( aVal > bVal ) ? 1 : -1;
|
||||
|
||||
});
|
||||
|
||||
return flights;
|
||||
|
||||
}
|
||||
|
||||
|
||||
startUpdates() {
|
||||
|
||||
if ( !this.boardIsVisible() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#updateInterval = window.setInterval( () => {
|
||||
|
||||
if ( !getConnected() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.update();
|
||||
|
||||
}, this.#updateIntervalDelay );
|
||||
|
||||
}
|
||||
|
||||
|
||||
stopUpdates() {
|
||||
|
||||
clearInterval( this.#updateInterval );
|
||||
|
||||
}
|
||||
|
||||
|
||||
timestampToLocaleTime( timestamp:number ) {
|
||||
|
||||
return ( timestamp === -1 ) ? "-" : new Date( timestamp ).toLocaleTimeString();
|
||||
|
||||
}
|
||||
|
||||
|
||||
timeToGo( timestamp:number ) {
|
||||
|
||||
const timeData = this.calculateTimeToGo( this.getATC().getMissionDate().getTime(), timestamp );
|
||||
|
||||
return ( timestamp === -1 ) ? "-" : timeData.elapsedMarker + timeData.time;
|
||||
|
||||
}
|
||||
|
||||
protected update() {
|
||||
console.warn( "No custom update method defined." );
|
||||
}
|
||||
|
||||
|
||||
updateClock() {
|
||||
|
||||
const missionTime = this.#atc.getMissionDate().getTime();
|
||||
const timeDiff = new Date().getTime() - getMissionHandler().getDateAndTime().elapsedTime;
|
||||
|
||||
const nowDate = new Date( missionTime + timeDiff );
|
||||
|
||||
this.#clockElement.innerText = nowDate.toLocaleTimeString();
|
||||
|
||||
}
|
||||
|
||||
|
||||
updateFlight( flightId:string, reqBody:object ) {
|
||||
|
||||
return fetch( '/api/atc/flight/' + flightId, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Accept': '*/*',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
"body": JSON.stringify( reqBody )
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
#_setupDemoData() {
|
||||
|
||||
fetch( '/api/atc/flight/', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': '*/*',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
"body": JSON.stringify({
|
||||
"boardId" : this.getBoardId(),
|
||||
"name" : this.getBoardId() + " 1",
|
||||
"unitId" : 1
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
// fetch( '/api/atc/flight/', {
|
||||
// method: 'POST',
|
||||
// headers: {
|
||||
// 'Accept': '*/*',
|
||||
// 'Content-Type': 'application/json'
|
||||
// },
|
||||
// "body": JSON.stringify({
|
||||
// "boardId" : this.getBoardId(),
|
||||
// "name" : this.getBoardId() + " 2",
|
||||
// "unitId" : 2
|
||||
// })
|
||||
// });
|
||||
|
||||
// fetch( '/api/atc/flight/', {
|
||||
// method: 'POST',
|
||||
// headers: {
|
||||
// 'Accept': '*/*',
|
||||
// 'Content-Type': 'application/json'
|
||||
// },
|
||||
// "body": JSON.stringify({
|
||||
// "boardId" : this.getBoardId(),
|
||||
// "name" : this.getBoardId() + " 3",
|
||||
// "unitId" : 9
|
||||
// })
|
||||
// });
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -1,200 +0,0 @@
|
||||
import { Dropdown } from "../../controls/dropdown";
|
||||
import { ATC } from "../atc";
|
||||
import { ATCBoard } from "../atcboard";
|
||||
|
||||
|
||||
export class ATCBoardGround extends ATCBoard {
|
||||
|
||||
constructor( atc:ATC, element:HTMLElement ) {
|
||||
|
||||
super( atc, element );
|
||||
|
||||
}
|
||||
|
||||
|
||||
update() {
|
||||
|
||||
const flights = this.sortFlights( Object.values( this.getATC().getDataHandler().getFlights( this.getBoardId() ) ) );
|
||||
const stripBoard = this.getStripBoardElement();
|
||||
|
||||
const missionTime = this.getATC().getMissionDate().getTime();
|
||||
|
||||
for( const strip of stripBoard.children ) {
|
||||
strip.toggleAttribute( "data-updating", true );
|
||||
}
|
||||
|
||||
|
||||
flights.forEach( flight => {
|
||||
|
||||
let strip = this.getStrip( flight.id );
|
||||
|
||||
if ( !strip ) {
|
||||
|
||||
const template = `<div class="ol-strip-board-strip" data-flight-id="${flight.id}" data-flight-status="${flight.status}">
|
||||
<div class="handle"></div>
|
||||
<div data-point="name">${flight.name}</div>
|
||||
|
||||
<div id="flight-status-${flight.id}" class="ol-select narrow" data-point="status">
|
||||
<div class="ol-select-value">${flight.status}</div>
|
||||
<div class="ol-select-options"></div>
|
||||
</div>
|
||||
|
||||
<div data-point="takeoffTime"><input type="text" name="takeoffTime" value="${this.timestampToLocaleTime( flight.takeoffTime )}" /></div>
|
||||
|
||||
<div data-point="timeToGo">${this.timeToGo( flight.takeoffTime )}</div>
|
||||
|
||||
<button class="deleteFlight">×</button>
|
||||
</div>`;
|
||||
|
||||
stripBoard.insertAdjacentHTML( "beforeend", template );
|
||||
|
||||
|
||||
strip = {
|
||||
"id": flight.id,
|
||||
"element": <HTMLElement>stripBoard.lastElementChild,
|
||||
"dropdowns": {},
|
||||
"unitId": -1
|
||||
};
|
||||
|
||||
strip.element.querySelectorAll( ".ol-select" ).forEach( select => {
|
||||
|
||||
switch( select.getAttribute( "data-point" ) ) {
|
||||
|
||||
case "status":
|
||||
|
||||
strip.dropdowns.status = new Dropdown( select.id, ( value:string, ev:MouseEvent ) => {
|
||||
|
||||
fetch( '/api/atc/flight/' + flight.id, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Accept': '*/*',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
"body": JSON.stringify({
|
||||
"status": value
|
||||
})
|
||||
});
|
||||
|
||||
}, [
|
||||
"unknown", "checkedin", "readytotaxi", "clearedtotaxi", "halted", "terminated"
|
||||
]);
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
strip.element.querySelectorAll( `input[type="text"]` ).forEach( input => {
|
||||
|
||||
if ( input instanceof HTMLInputElement ) {
|
||||
|
||||
input.addEventListener( "blur", ( ev ) => {
|
||||
|
||||
const target = ev.target;
|
||||
|
||||
if ( target instanceof HTMLInputElement ) {
|
||||
|
||||
if ( /^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/.test( target.value ) ) {
|
||||
target.value += ":00";
|
||||
}
|
||||
|
||||
const value = target.value;
|
||||
|
||||
if ( value === target.dataset.previousValue ) {
|
||||
return;
|
||||
|
||||
} else if ( value === "" ) {
|
||||
|
||||
this.#updateTakeoffTime( flight.id, -1 );
|
||||
|
||||
} else if ( /^([0-1]?[0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$/.test( value ) ) {
|
||||
|
||||
let [ hours, minutes, seconds ] = value.split( ":" ).map( str => parseInt( str ) );
|
||||
|
||||
const missionStart = this.getATC().getMissionStartDateTime();
|
||||
|
||||
this.#updateTakeoffTime( flight.id, new Date(
|
||||
missionStart.getFullYear(),
|
||||
missionStart.getMonth(),
|
||||
missionStart.getDate(),
|
||||
hours,
|
||||
minutes,
|
||||
seconds
|
||||
).getTime() );
|
||||
|
||||
} else {
|
||||
|
||||
target.value === target.dataset.previousValue
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
this.addStrip( strip );
|
||||
|
||||
} else {
|
||||
|
||||
if ( flight.status !== strip.element.getAttribute( "data-flight-status" ) ) {
|
||||
strip.element.setAttribute( "data-flight-status", flight.status );
|
||||
strip.dropdowns.status.selectText( flight.status );
|
||||
}
|
||||
|
||||
strip.element.querySelectorAll( `input[name="takeoffTime"]:not(:focus)` ).forEach( el => {
|
||||
if ( el instanceof HTMLInputElement ) {
|
||||
el.value = this.timestampToLocaleTime( flight.takeoffTime );
|
||||
el.dataset.previousValue = el.value;
|
||||
}
|
||||
});
|
||||
|
||||
strip.element.querySelectorAll( `[data-point="timeToGo"]` ).forEach( el => {
|
||||
|
||||
if ( flight.takeoffTime > 0 && this.calculateTimeToGo( missionTime, flight.takeoffTime ).totalSeconds <= 120 ) {
|
||||
strip.element.setAttribute( "data-time-warning", "level-1" );
|
||||
}
|
||||
|
||||
if ( el instanceof HTMLElement ) {
|
||||
el.innerText = this.timeToGo( flight.takeoffTime );
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
strip.element.toggleAttribute( "data-updating", false );
|
||||
|
||||
});
|
||||
|
||||
|
||||
stripBoard.querySelectorAll( `[data-updating]` ).forEach( strip => {
|
||||
this.deleteStrip( strip.getAttribute( "data-flight-id" ) || "" );
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#updateTakeoffTime = function( flightId:string, time:number ) {
|
||||
|
||||
fetch( '/api/atc/flight/' + flightId, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Accept': '*/*',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
"body": JSON.stringify({
|
||||
"takeoffTime": time
|
||||
})
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,194 +0,0 @@
|
||||
import { getUnitsManager } from "../..";
|
||||
import { Dropdown } from "../../controls/dropdown";
|
||||
import { mToFt, msToKnots } from "../../other/utils";
|
||||
import { ATC } from "../atc";
|
||||
import { ATCBoard } from "../atcboard";
|
||||
|
||||
|
||||
export class ATCBoardTower extends ATCBoard {
|
||||
|
||||
constructor( atc:ATC, element:HTMLElement ) {
|
||||
|
||||
super( atc, element );
|
||||
|
||||
}
|
||||
|
||||
|
||||
update() {
|
||||
|
||||
const flights = this.sortFlights( Object.values( this.getATC().getDataHandler().getFlights( this.getBoardId() ) ) );
|
||||
const missionTime = this.getATC().getMissionDate().getTime();
|
||||
const selectableUnits = getUnitsManager().getSelectableAircraft();
|
||||
const stripBoard = this.getStripBoardElement();
|
||||
|
||||
for( const strip of stripBoard.children ) {
|
||||
strip.toggleAttribute( "data-updating", true );
|
||||
}
|
||||
|
||||
|
||||
flights.forEach( flight => {
|
||||
|
||||
let strip = this.getStrip( flight.id );
|
||||
|
||||
if ( strip.isDeleted === true ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const flightData = {
|
||||
latitude: -1,
|
||||
longitude: -1,
|
||||
altitude: -1,
|
||||
heading: -1,
|
||||
speed: -1,
|
||||
...( selectableUnits.hasOwnProperty( flight.unitId ) ? selectableUnits[flight.unitId].getData() : {} )
|
||||
};
|
||||
|
||||
if ( !strip ) {
|
||||
|
||||
const template = `<div class="ol-strip-board-strip" data-flight-id="${flight.id}" data-flight-status="${flight.status}">
|
||||
<div class="handle"></div>
|
||||
<div data-point="name"><a href="#" class="select-unit">${flight.name}</a></div>
|
||||
|
||||
<div data-point="assignedAltitude"><input type="text" name="assignedAltitude" value="${flight.assignedAltitude}" size="2" /> 000</div>
|
||||
<div data-point="altitude">-</div>
|
||||
|
||||
<div data-point="assignedSpeed"><input type="text" name="assignedSpeed" value="${flight.assignedSpeed}" size="3" /></div>
|
||||
<div data-point="speed">-</div>
|
||||
|
||||
<button class="deleteFlight">×</button>
|
||||
</div>`;
|
||||
|
||||
stripBoard.insertAdjacentHTML( "beforeend", template );
|
||||
|
||||
|
||||
strip = {
|
||||
"id": flight.id,
|
||||
"element": <HTMLElement>stripBoard.lastElementChild,
|
||||
"dropdowns": {},
|
||||
"unitId": flight.unitId
|
||||
};
|
||||
|
||||
|
||||
strip.element.querySelectorAll( `input[type="text"]` ).forEach( input => {
|
||||
|
||||
if ( input instanceof HTMLInputElement ) {
|
||||
|
||||
switch ( input.name ) {
|
||||
|
||||
case "assignedAltitude":
|
||||
|
||||
input.addEventListener( "change", ( ev ) => {
|
||||
|
||||
let val = parseInt( input.value.replace( /[^\d]/g, "" ) );
|
||||
|
||||
if ( isNaN( val ) || val < 0 || val > 40 ) {
|
||||
val = 0;
|
||||
}
|
||||
|
||||
this.updateFlight( flight.id, {
|
||||
"assignedAltitude": val
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
case "assignedSpeed":
|
||||
|
||||
input.addEventListener( "change", ( ev ) => {
|
||||
|
||||
let val = parseInt( input.value.replace( /[^\d]/g, "" ) );
|
||||
|
||||
if ( isNaN( val ) || val < 0 || val > 750 ) {
|
||||
val = 0;
|
||||
}
|
||||
|
||||
this.updateFlight( flight.id, {
|
||||
"assignedSpeed": val
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
strip.element.querySelectorAll( ".select-unit" ).forEach( el => {
|
||||
|
||||
el.addEventListener( "click", ev => {
|
||||
ev.preventDefault();
|
||||
getUnitsManager().selectUnit( flight.unitId );
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
this.addStrip( strip );
|
||||
|
||||
} else {
|
||||
|
||||
//
|
||||
// Altitude
|
||||
//
|
||||
|
||||
let assignedAltitude = <HTMLInputElement>strip.element.querySelector( `input[name="assignedAltitude"]`);
|
||||
|
||||
if ( !assignedAltitude.matches( ":focus" ) && assignedAltitude.value !== flight.assignedAltitude ) {
|
||||
assignedAltitude.value = flight.assignedAltitude;
|
||||
}
|
||||
|
||||
flightData.altitude = Math.floor( mToFt(flightData.altitude) );
|
||||
|
||||
strip.element.querySelectorAll( `[data-point="altitude"]` ).forEach( el => {
|
||||
if ( el instanceof HTMLElement ) {
|
||||
el.innerText = "" + flightData.altitude;
|
||||
}
|
||||
});
|
||||
|
||||
const altitudeDelta = ( flight.assignedAltitude === 0 ) ? 0 : ( flight.assignedAltitude * 1000 ) - flightData.altitude;
|
||||
|
||||
strip.element.toggleAttribute( "data-altitude-assigned", ( flight.assignedAltitude > 0 ) );
|
||||
strip.element.toggleAttribute( "data-warning-altitude", ( altitudeDelta >= 300 || altitudeDelta <= -300 ) );
|
||||
|
||||
|
||||
//
|
||||
// Speed
|
||||
//
|
||||
|
||||
let assignedSpeed = <HTMLInputElement>strip.element.querySelector( `input[name="assignedSpeed"]`);
|
||||
|
||||
if ( !assignedSpeed.matches( ":focus" ) && assignedSpeed.value !== flight.assignedSpeed ) {
|
||||
assignedSpeed.value = flight.assignedSpeed;
|
||||
}
|
||||
|
||||
flightData.speed = Math.floor( msToKnots(flightData.speed) );
|
||||
|
||||
strip.element.querySelectorAll( `[data-point="speed"]` ).forEach( el => {
|
||||
if ( el instanceof HTMLElement ) {
|
||||
el.innerText = "" + flightData.speed;
|
||||
}
|
||||
});
|
||||
|
||||
const speedDelta = ( flight.assignedSpeed === 0 ) ? 0 : flight.assignedSpeed - flightData.speed;
|
||||
|
||||
strip.element.toggleAttribute( "data-speed-assigned", ( flight.assignedSpeed > 0 ) );
|
||||
strip.element.toggleAttribute( "data-warning-speed", ( speedDelta >= 25 || speedDelta <= -25 ) );
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
strip.element.toggleAttribute( "data-updating", false );
|
||||
|
||||
});
|
||||
|
||||
stripBoard.querySelectorAll( `[data-updating]` ).forEach( strip => {
|
||||
this.deleteStrip( strip.getAttribute( "data-flight-id" ) || "" );
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
import { getUnitsManager } from "..";
|
||||
import { Panel } from "../panels/panel";
|
||||
import { Unit } from "../unit/unit";
|
||||
|
||||
export class UnitDataTable extends Panel {
|
||||
constructor(id: string) {
|
||||
super( id );
|
||||
this.hide();
|
||||
}
|
||||
|
||||
update() {
|
||||
var units = getUnitsManager().getUnits();
|
||||
|
||||
const unitsArray = Object.values(units).sort((a: Unit, b: Unit) => {
|
||||
const aVal = a.getUnitName()?.toLowerCase();
|
||||
const bVal = b.getUnitName()?.toLowerCase();
|
||||
|
||||
if (aVal > bVal) {
|
||||
return 1;
|
||||
} else if (bVal > aVal) {
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function addRow(parentEl: HTMLElement, columns: string[]) {
|
||||
const rowDiv = document.createElement("div");
|
||||
|
||||
for (const item of columns) {
|
||||
|
||||
const div = document.createElement("div");
|
||||
div.innerText = item;
|
||||
rowDiv.appendChild(div);
|
||||
|
||||
}
|
||||
parentEl.appendChild(rowDiv);
|
||||
}
|
||||
|
||||
const el = <HTMLElement> this.getElement().querySelector("#unit-list");
|
||||
|
||||
if (el) {
|
||||
|
||||
el.innerHTML = "";
|
||||
|
||||
addRow(el, ["Callsign", "Name", "Category", "AI/Human"])
|
||||
|
||||
for (const unit of unitsArray) {
|
||||
|
||||
const dataset = [unit.getUnitName(), unit.getName(), unit.getCategory(), (unit.getControlled()) ? "AI" : "Human"];
|
||||
|
||||
addRow(el, dataset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,13 @@
|
||||
import { LatLng, LatLngBounds } from "leaflet";
|
||||
|
||||
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";
|
||||
|
||||
@ -1,16 +1,24 @@
|
||||
import { getMap, getMissionHandler, getUnitsManager, setActiveCoalition } from "..";
|
||||
import { BLUE_COMMANDER, GAME_MASTER, RED_COMMANDER } from "../constants/constants";
|
||||
import { GAME_MASTER } from "../constants/constants";
|
||||
import { Airbase } from "../mission/airbase";
|
||||
import { dataPointMap } from "../other/utils";
|
||||
import { Unit } from "../unit/unit";
|
||||
import { ContextMenu } from "./contextmenu";
|
||||
|
||||
/** This context menu is shown to the user when the airbase marker is right clicked on the map.
|
||||
* It allows the user to inspect information about the airbase, as well as allowing to spawn units from the airbase itself and land units on it. */
|
||||
export class AirbaseContextMenu extends ContextMenu {
|
||||
#airbase: Airbase | null = null;
|
||||
|
||||
constructor(id: string) {
|
||||
super(id);
|
||||
/**
|
||||
*
|
||||
* @param ID - the ID of the HTML element which will contain the context menu
|
||||
*/
|
||||
constructor(ID: string){
|
||||
super(ID);
|
||||
|
||||
document.addEventListener("contextMenuSpawnAirbase", (e: any) => {
|
||||
this.showSpawnMenu();
|
||||
this.#showSpawnMenu();
|
||||
})
|
||||
|
||||
document.addEventListener("contextMenuLandAirbase", (e: any) => {
|
||||
@ -20,27 +28,39 @@ export class AirbaseContextMenu extends ContextMenu {
|
||||
})
|
||||
}
|
||||
|
||||
/** Sets the airbase for which data will be shown in the context menu
|
||||
*
|
||||
* @param airbase The airbase for which data will be shown in the context menu. Note: the airbase must be present in the public/databases/airbases/<theatre>.json database.
|
||||
*/
|
||||
setAirbase(airbase: Airbase) {
|
||||
this.#airbase = airbase;
|
||||
this.setName(this.#airbase.getName());
|
||||
this.setProperties(this.#airbase.getProperties());
|
||||
this.setParkings(this.#airbase.getParkings());
|
||||
this.setCoalition(this.#airbase.getCoalition());
|
||||
this.enableLandButton(getUnitsManager().getSelectedUnitsTypes().length == 1 && ["Aircraft", "Helicopter"].includes(getUnitsManager().getSelectedUnitsTypes()[0]) && (getUnitsManager().getSelectedUnitsCoalition() === this.#airbase.getCoalition() || this.#airbase.getCoalition() === "neutral"))
|
||||
this.enableSpawnButton(getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER || this.#airbase.getCoalition() == getMissionHandler().getCommandedCoalition());
|
||||
|
||||
|
||||
this.#setName(this.#airbase.getName());
|
||||
this.#setProperties(this.#airbase.getProperties());
|
||||
this.#setParkings(this.#airbase.getParkings());
|
||||
this.#setCoalition(this.#airbase.getCoalition());
|
||||
this.#showLandButton(getUnitsManager().getSelectedUnitsCategories().length == 1 && ["Aircraft", "Helicopter"].includes(getUnitsManager().getSelectedUnitsCategories()[0]) && (getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getCoalition()}) === this.#airbase.getCoalition() || this.#airbase.getCoalition() === "neutral"))
|
||||
this.#showSpawnButton(getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER || this.#airbase.getCoalition() == getMissionHandler().getCommandedCoalition());
|
||||
this.#setAirbaseData();
|
||||
|
||||
this.clip();
|
||||
}
|
||||
|
||||
setName(airbaseName: string) {
|
||||
/**
|
||||
*
|
||||
* @param airbaseName The name of the airbase
|
||||
*/
|
||||
#setName(airbaseName: string) {
|
||||
var nameDiv = <HTMLElement>this.getContainer()?.querySelector("#airbase-name");
|
||||
if (nameDiv != null)
|
||||
nameDiv.innerText = airbaseName;
|
||||
}
|
||||
|
||||
setProperties(airbaseProperties: string[]) {
|
||||
/**
|
||||
*
|
||||
* @param airbaseProperties The properties of the airbase
|
||||
*/
|
||||
#setProperties(airbaseProperties: string[]) {
|
||||
this.getContainer()?.querySelector("#airbase-properties")?.replaceChildren(...airbaseProperties.map((property: string) => {
|
||||
var div = document.createElement("div");
|
||||
div.innerText = property;
|
||||
@ -48,7 +68,11 @@ export class AirbaseContextMenu extends ContextMenu {
|
||||
}),);
|
||||
}
|
||||
|
||||
setParkings(airbaseParkings: string[]) {
|
||||
/**
|
||||
*
|
||||
* @param airbaseParkings List of available parkings at the airbase
|
||||
*/
|
||||
#setParkings(airbaseParkings: string[]) {
|
||||
this.getContainer()?.querySelector("#airbase-parking")?.replaceChildren(...airbaseParkings.map((parking: string) => {
|
||||
var div = document.createElement("div");
|
||||
div.innerText = parking;
|
||||
@ -56,31 +80,43 @@ export class AirbaseContextMenu extends ContextMenu {
|
||||
}));
|
||||
}
|
||||
|
||||
setCoalition(coalition: string) {
|
||||
(<HTMLElement>this.getContainer()?.querySelector("#spawn-airbase-aircraft-button")).dataset.coalition = coalition;
|
||||
/**
|
||||
*
|
||||
* @param coalition Coalition to which the airbase belongs
|
||||
*/
|
||||
#setCoalition(coalition: string) {
|
||||
(this.getContainer()?.querySelector("#spawn-airbase-aircraft-button") as HTMLElement).dataset.coalition = coalition;
|
||||
}
|
||||
|
||||
enableSpawnButton(enableSpawnButton: boolean) {
|
||||
this.getContainer()?.querySelector("#spawn-airbase-aircraft-button")?.classList.toggle("hide", !enableSpawnButton);
|
||||
/**
|
||||
*
|
||||
* @param showSpawnButton If true, the spawn button will be visibile
|
||||
*/
|
||||
#showSpawnButton(showSpawnButton: boolean) {
|
||||
this.getContainer()?.querySelector("#spawn-airbase-aircraft-button")?.classList.toggle("hide", !showSpawnButton);
|
||||
}
|
||||
|
||||
enableLandButton(enableLandButton: boolean) {
|
||||
this.getContainer()?.querySelector("#land-here-button")?.classList.toggle("hide", !enableLandButton);
|
||||
/**
|
||||
*
|
||||
* @param showLandButton If true, the land button will be visible
|
||||
*/
|
||||
#showLandButton(showLandButton: boolean) {
|
||||
this.getContainer()?.querySelector("#land-here-button")?.classList.toggle("hide", !showLandButton);
|
||||
}
|
||||
|
||||
showSpawnMenu() {
|
||||
/** Shows the spawn context menu which allows the user to select a unit to ground spawn at the airbase
|
||||
*
|
||||
*/
|
||||
#showSpawnMenu() {
|
||||
if (this.#airbase != null) {
|
||||
setActiveCoalition(this.#airbase.getCoalition());
|
||||
getMap().showMapContextMenu(this.getX(), this.getY(), this.getLatLng());
|
||||
getMap().getMapContextMenu().hideUpperBar();
|
||||
getMap().getMapContextMenu().hideLowerBar();
|
||||
getMap().getMapContextMenu().hideAltitudeSlider();
|
||||
getMap().getMapContextMenu().showSubMenu("aircraft");
|
||||
getMap().getMapContextMenu().setAirbaseName(this.#airbase.getName());
|
||||
getMap().getMapContextMenu().setLatLng(this.#airbase.getLatLng());
|
||||
getMap().showAirbaseSpawnMenu(this.getX(), this.getY(), this.getLatLng(), this.#airbase);
|
||||
}
|
||||
}
|
||||
|
||||
/** @todo needs commenting
|
||||
*
|
||||
*/
|
||||
#setAirbaseData() {
|
||||
if (this.#airbase && this.getContainer()) {
|
||||
dataPointMap(this.getContainer() as HTMLElement, {
|
||||
105
client/src/contextmenus/airbasespawnmenu.ts
Normal file
@ -0,0 +1,105 @@
|
||||
import { LatLng } from "leaflet";
|
||||
import { getActiveCoalition } from "..";
|
||||
import { ContextMenu } from "./contextmenu";
|
||||
import { AircraftSpawnMenu, HelicopterSpawnMenu } from "../controls/unitspawnmenu";
|
||||
import { Airbase } from "../mission/airbase";
|
||||
|
||||
/** This context menu is shown when the user wants to spawn a new aircraft or helicopter from the ground at an airbase.
|
||||
* It is shown by clicking on the "spawn" button of a AirbaseContextMenu. */
|
||||
export class AirbaseSpawnContextMenu extends ContextMenu {
|
||||
#aircraftSpawnMenu: AircraftSpawnMenu;
|
||||
#helicopterSpawnMenu: HelicopterSpawnMenu;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ID - the ID of the HTML element which will contain the context menu
|
||||
*/
|
||||
constructor(ID: string){
|
||||
super(ID);
|
||||
|
||||
/* Create the spawn menus for the different unit types */
|
||||
this.#aircraftSpawnMenu = new AircraftSpawnMenu("airbase-aircraft-spawn-menu");
|
||||
this.#helicopterSpawnMenu = new HelicopterSpawnMenu("airbase-helicopter-spawn-menu");
|
||||
|
||||
this.#aircraftSpawnMenu.getAltitudeSlider().hide();
|
||||
this.#helicopterSpawnMenu.getAltitudeSlider().hide();
|
||||
|
||||
/* Event listeners */
|
||||
document.addEventListener("mapContextMenuShow", (e: any) => {
|
||||
if (this.getVisibleSubMenu() !== e.detail.type)
|
||||
this.#showSubMenu(e.detail.type);
|
||||
else
|
||||
this.#hideSubMenus();
|
||||
});
|
||||
|
||||
this.#aircraftSpawnMenu.getContainer().addEventListener("resize", () => this.clip());
|
||||
this.#helicopterSpawnMenu.getContainer().addEventListener("resize", () => this.clip());
|
||||
|
||||
this.#aircraftSpawnMenu.getContainer().addEventListener("hide", () => this.hide());
|
||||
this.#helicopterSpawnMenu.getContainer().addEventListener("hide", () => this.hide());
|
||||
|
||||
this.hide();
|
||||
}
|
||||
|
||||
/** Show the context menu
|
||||
*
|
||||
* @param x X screen coordinate of the top left corner of the context menu
|
||||
* @param y Y screen coordinate of the top left corner of the context menu
|
||||
*/
|
||||
show(x: number, y: number) {
|
||||
super.show(x, y, new LatLng(0, 0));
|
||||
|
||||
this.#aircraftSpawnMenu.setAirbase(undefined);
|
||||
this.#helicopterSpawnMenu.setAirbase(undefined);
|
||||
this.#aircraftSpawnMenu.setCountries();
|
||||
this.#helicopterSpawnMenu.setCountries();
|
||||
|
||||
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getActiveCoalition()) });
|
||||
}
|
||||
|
||||
/** Sets the airbase at which the new unit will be spawned
|
||||
*
|
||||
* @param airbase The airbase at which the new unit will be spawned. Note: if the airbase has no suitable parking spots, the airplane may be spawned on the runway, or spawning may fail.
|
||||
*/
|
||||
setAirbase(airbase: Airbase) {
|
||||
this.#aircraftSpawnMenu.setAirbase(airbase);
|
||||
this.#helicopterSpawnMenu.setAirbase(airbase);
|
||||
}
|
||||
|
||||
/** Shows the submenu depending on unit selection
|
||||
*
|
||||
* @param type Submenu type, either "aircraft" or "helicopter"
|
||||
*/
|
||||
#showSubMenu(type: string) {
|
||||
this.getContainer()?.querySelector("#airbase-aircraft-spawn-menu")?.classList.toggle("hide", type !== "aircraft");
|
||||
this.getContainer()?.querySelector("#airbase-aircraft-spawn-button")?.classList.toggle("is-open", type === "aircraft");
|
||||
this.getContainer()?.querySelector("#airbase-helicopter-spawn-menu")?.classList.toggle("hide", type !== "helicopter");
|
||||
this.getContainer()?.querySelector("#airbase-helicopter-spawn-button")?.classList.toggle("is-open", type === "helicopter");
|
||||
|
||||
(this.getContainer()?.querySelectorAll(".deploy-unit-button"))?.forEach((element: Node) => { (element as HTMLButtonElement).disabled = true; })
|
||||
|
||||
this.#aircraftSpawnMenu.reset();
|
||||
this.#aircraftSpawnMenu.setCountries();
|
||||
this.#helicopterSpawnMenu.reset();
|
||||
this.#helicopterSpawnMenu.setCountries();
|
||||
|
||||
this.setVisibleSubMenu(type);
|
||||
this.clip();
|
||||
}
|
||||
|
||||
/** Hide all the open submenus
|
||||
*
|
||||
*/
|
||||
#hideSubMenus() {
|
||||
this.getContainer()?.querySelector("#airbase-aircraft-spawn-menu")?.classList.toggle("hide", true);
|
||||
this.getContainer()?.querySelector("#airbase-aircraft-spawn-button")?.classList.toggle("is-open", false);
|
||||
this.getContainer()?.querySelector("#airbase-helicopter-spawn-menu")?.classList.toggle("hide", true);
|
||||
this.getContainer()?.querySelector("#airbase-helicopter-spawn-button")?.classList.toggle("is-open", false);
|
||||
|
||||
this.#aircraftSpawnMenu.reset();
|
||||
this.#helicopterSpawnMenu.reset();
|
||||
|
||||
this.setVisibleSubMenu(null);
|
||||
this.clip();
|
||||
}
|
||||
}
|
||||
@ -1,14 +1,15 @@
|
||||
import { LatLng } from "leaflet";
|
||||
import { getMap, getMissionHandler, getUnitsManager } from "..";
|
||||
import { GAME_MASTER, IADSTypes } from "../constants/constants";
|
||||
import { CoalitionArea } from "../map/coalitionarea";
|
||||
import { CoalitionArea } from "../map/coalitionarea/coalitionarea";
|
||||
import { ContextMenu } from "./contextmenu";
|
||||
import { Dropdown } from "./dropdown";
|
||||
import { Slider } from "./slider";
|
||||
import { Switch } from "./switch";
|
||||
import { groundUnitDatabase } from "../unit/groundunitdatabase";
|
||||
import { Dropdown } from "../controls/dropdown";
|
||||
import { Slider } from "../controls/slider";
|
||||
import { Switch } from "../controls/switch";
|
||||
import { groundUnitDatabase } from "../unit/databases/groundunitdatabase";
|
||||
import { createCheckboxOption, getCheckboxOptions } from "../other/utils";
|
||||
|
||||
/** This context menu allows the user to edit or delete a CoalitionArea. Moreover, it allows the user to create a IADS automatically using the CoalitionArea as bounds. */
|
||||
export class CoalitionAreaContextMenu extends ContextMenu {
|
||||
#coalitionSwitch: Switch;
|
||||
#coalitionArea: CoalitionArea | null = null;
|
||||
@ -18,16 +19,25 @@ export class CoalitionAreaContextMenu extends ContextMenu {
|
||||
#iadsErasDropdown: Dropdown;
|
||||
#iadsRangesDropdown: Dropdown;
|
||||
|
||||
constructor(id: string) {
|
||||
super(id);
|
||||
/**
|
||||
*
|
||||
* @param ID - the ID of the HTML element which will contain the context menu
|
||||
*/
|
||||
constructor(ID: string){
|
||||
super(ID);
|
||||
|
||||
/* Create the coalition switch */
|
||||
this.#coalitionSwitch = new Switch("coalition-area-switch", (value: boolean) => this.#onSwitchClick(value));
|
||||
this.#coalitionSwitch.setValue(false);
|
||||
|
||||
/* Create the controls of the IADS creation submenu */
|
||||
this.#iadsTypesDropdown = new Dropdown("iads-units-type-options", () => { });
|
||||
this.#iadsErasDropdown = new Dropdown("iads-era-options", () => {});
|
||||
this.#iadsRangesDropdown = new Dropdown("iads-range-options", () => {});
|
||||
this.#iadsDensitySlider = new Slider("iads-density-slider", 5, 100, "%", (value: number) => { });
|
||||
this.#iadsDistributionSlider = new Slider("iads-distribution-slider", 5, 100, "%", (value: number) => { });
|
||||
|
||||
/* Set the default parameters of the sliders */
|
||||
this.#iadsDensitySlider.setIncrement(5);
|
||||
this.#iadsDensitySlider.setValue(50);
|
||||
this.#iadsDensitySlider.setActive(true);
|
||||
@ -37,9 +47,9 @@ export class CoalitionAreaContextMenu extends ContextMenu {
|
||||
|
||||
document.addEventListener("coalitionAreaContextMenuShow", (e: any) => {
|
||||
if (this.getVisibleSubMenu() !== e.detail.type)
|
||||
this.showSubMenu(e.detail.type);
|
||||
this.#showSubMenu(e.detail.type);
|
||||
else
|
||||
this.hideSubMenus();
|
||||
this.#hideSubMenus();
|
||||
});
|
||||
|
||||
document.addEventListener("coalitionAreaBringToBack", (e: any) => {
|
||||
@ -62,6 +72,12 @@ export class CoalitionAreaContextMenu extends ContextMenu {
|
||||
this.hide();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param x X screen coordinate of the top left corner of the context menu
|
||||
* @param y Y screen coordinate of the top left corner of the context menu
|
||||
* @param latlng Leaflet latlng object of the mouse click
|
||||
*/
|
||||
show(x: number, y: number, latlng: LatLng) {
|
||||
super.show(x, y, latlng);
|
||||
|
||||
@ -85,26 +101,10 @@ export class CoalitionAreaContextMenu extends ContextMenu {
|
||||
this.#coalitionSwitch.hide()
|
||||
}
|
||||
|
||||
showSubMenu(type: string) {
|
||||
this.getContainer()?.querySelector("#iads-menu")?.classList.toggle("hide", type !== "iads");
|
||||
this.getContainer()?.querySelector("#iads-button")?.classList.toggle("is-open", type === "iads");
|
||||
this.clip();
|
||||
|
||||
this.setVisibleSubMenu(type);
|
||||
}
|
||||
|
||||
hideSubMenus() {
|
||||
this.getContainer()?.querySelector("#iads-menu")?.classList.toggle("hide", true);
|
||||
this.getContainer()?.querySelector("#iads-button")?.classList.toggle("is-open", false);
|
||||
this.clip();
|
||||
|
||||
this.setVisibleSubMenu(null);
|
||||
}
|
||||
|
||||
getCoalitionArea() {
|
||||
return this.#coalitionArea;
|
||||
}
|
||||
|
||||
/** Set the CoalitionArea object the user will be able to edit using this menu
|
||||
*
|
||||
* @param coalitionArea The CoalitionArea object to edit
|
||||
*/
|
||||
setCoalitionArea(coalitionArea: CoalitionArea) {
|
||||
this.#coalitionArea = coalitionArea;
|
||||
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => {
|
||||
@ -113,6 +113,41 @@ export class CoalitionAreaContextMenu extends ContextMenu {
|
||||
this.#coalitionSwitch.setValue(this.getCoalitionArea()?.getCoalition() === "red");
|
||||
}
|
||||
|
||||
/** Get the CoalitionArea object the contextmenu is editing
|
||||
*
|
||||
* @returns The CoalitionArea the contextmenu is editing
|
||||
*/
|
||||
getCoalitionArea() {
|
||||
return this.#coalitionArea;
|
||||
}
|
||||
|
||||
/** Show a submenu of the contextmenu
|
||||
*
|
||||
* @param type The submenu type, currently only "iads"
|
||||
*/
|
||||
#showSubMenu(type: string) {
|
||||
this.getContainer()?.querySelector("#iads-menu")?.classList.toggle("hide", type !== "iads");
|
||||
this.getContainer()?.querySelector("#iads-button")?.classList.toggle("is-open", type === "iads");
|
||||
this.clip();
|
||||
|
||||
this.setVisibleSubMenu(type);
|
||||
}
|
||||
|
||||
/** Hide all submenus
|
||||
*
|
||||
*/
|
||||
#hideSubMenus() {
|
||||
this.getContainer()?.querySelector("#iads-menu")?.classList.toggle("hide", true);
|
||||
this.getContainer()?.querySelector("#iads-button")?.classList.toggle("is-open", false);
|
||||
this.clip();
|
||||
|
||||
this.setVisibleSubMenu(null);
|
||||
}
|
||||
|
||||
/** Callback event called when the coalition switch is clicked to change the coalition of the CoalitionArea
|
||||
*
|
||||
* @param value Switch position (false: blue, true: red)
|
||||
*/
|
||||
#onSwitchClick(value: boolean) {
|
||||
if (getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER) {
|
||||
this.getCoalitionArea()?.setCoalition(value ? "red" : "blue");
|
||||
@ -1,52 +1,78 @@
|
||||
import { LatLng } from "leaflet";
|
||||
|
||||
/** Base class for map contextmenus. By default it is empty and requires to be extended. */
|
||||
export class ContextMenu {
|
||||
#container: HTMLElement | null;
|
||||
#latlng: LatLng = new LatLng(0, 0);
|
||||
#x: number = 0;
|
||||
#y: number = 0;
|
||||
#visibleSubMenu: string | null = null;
|
||||
#hidden: boolean = true;
|
||||
|
||||
constructor(id: string) {
|
||||
this.#container = document.getElementById(id);
|
||||
/**
|
||||
*
|
||||
* @param ID - the ID of the HTML element which will contain the context menu
|
||||
*/
|
||||
constructor(ID: string){
|
||||
this.#container = document.getElementById(ID);
|
||||
this.hide();
|
||||
}
|
||||
|
||||
/** Show the contextmenu on top of the map, usually at the location where the user has clicked on it.
|
||||
*
|
||||
* @param x X screen coordinate of the top left corner of the context menu
|
||||
* @param y Y screen coordinate of the top left corner of the context menu
|
||||
* @param latlng Leaflet latlng object of the mouse click
|
||||
*/
|
||||
show(x: number, y: number, latlng: LatLng) {
|
||||
this.#latlng = latlng;
|
||||
this.#container?.classList.toggle("hide", false);
|
||||
this.#x = x;
|
||||
this.#y = y;
|
||||
this.clip();
|
||||
this.#hidden = false;
|
||||
}
|
||||
|
||||
/** Hide the contextmenu
|
||||
*
|
||||
*/
|
||||
hide() {
|
||||
this.#container?.classList.toggle("hide", true);
|
||||
this.#hidden = true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns The HTMLElement that contains the contextmenu
|
||||
*/
|
||||
getContainer() {
|
||||
return this.#container;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns The Leaflet latlng object associated to the click that caused the contextmenu to be shown
|
||||
*/
|
||||
getLatLng() {
|
||||
return this.#latlng;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns The x coordinate of the top left corner of the menu
|
||||
*/
|
||||
getX() {
|
||||
return this.#x;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns The y coordinate of the top left corner of the menu
|
||||
*/
|
||||
getY() {
|
||||
return this.#y;
|
||||
}
|
||||
|
||||
getHidden() {
|
||||
return this.#hidden;
|
||||
}
|
||||
|
||||
/** Clips the contextmenu, meaning it moves it on the screen to make sure it does not overflow the window.
|
||||
*
|
||||
*/
|
||||
clip() {
|
||||
if (this.#container != null) {
|
||||
if (this.#x + this.#container.offsetWidth < window.innerWidth)
|
||||
@ -61,10 +87,18 @@ export class ContextMenu {
|
||||
}
|
||||
}
|
||||
|
||||
/** Sets the currently visible submenu
|
||||
*
|
||||
* @param menu The name of the currently visibile submenu, or null if no submenu is visible
|
||||
*/
|
||||
setVisibleSubMenu(menu: string | null) {
|
||||
this.#visibleSubMenu = menu;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns The name of the currently visible submenu
|
||||
*/
|
||||
getVisibleSubMenu() {
|
||||
return this.#visibleSubMenu;
|
||||
}
|
||||
222
client/src/contextmenus/mapcontextmenu.ts
Normal file
@ -0,0 +1,222 @@
|
||||
import { LatLng } from "leaflet";
|
||||
import { getActiveCoalition, getMap, getMissionHandler, setActiveCoalition } from "..";
|
||||
import { spawnExplosion, spawnSmoke } from "../server/server";
|
||||
import { ContextMenu } from "./contextmenu";
|
||||
import { Switch } from "../controls/switch";
|
||||
import { GAME_MASTER } from "../constants/constants";
|
||||
import { CoalitionArea } from "../map/coalitionarea/coalitionarea";
|
||||
import { AircraftSpawnMenu, GroundUnitSpawnMenu, HelicopterSpawnMenu, NavyUnitSpawnMenu } from "../controls/unitspawnmenu";
|
||||
import { Airbase } from "../mission/airbase";
|
||||
import { SmokeMarker } from "../map/markers/smokemarker";
|
||||
|
||||
/** The MapContextMenu is the main contextmenu shown to the user whenever it rightclicks on the map. It is the primary interaction method for the user.
|
||||
* It allows to spawn units, create explosions and smoke, and edit CoalitionAreas.
|
||||
*/
|
||||
export class MapContextMenu extends ContextMenu {
|
||||
#coalitionSwitch: Switch;
|
||||
#aircraftSpawnMenu: AircraftSpawnMenu;
|
||||
#helicopterSpawnMenu: HelicopterSpawnMenu;
|
||||
#groundUnitSpawnMenu: GroundUnitSpawnMenu;
|
||||
#navyUnitSpawnMenu: NavyUnitSpawnMenu;
|
||||
#coalitionArea: CoalitionArea | null = null;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ID - the ID of the HTML element which will contain the context menu
|
||||
*/
|
||||
constructor(ID: string){
|
||||
super(ID);
|
||||
|
||||
/* Create the coalition switch */
|
||||
this.#coalitionSwitch = new Switch("coalition-switch", (value: boolean) => this.#onSwitchClick(value));
|
||||
this.#coalitionSwitch.setValue(false);
|
||||
this.#coalitionSwitch.getContainer()?.addEventListener("contextmenu", (e) => this.#onSwitchRightClick());
|
||||
|
||||
/* Create the spawn menus for the different unit types */
|
||||
this.#aircraftSpawnMenu = new AircraftSpawnMenu("aircraft-spawn-menu");
|
||||
this.#helicopterSpawnMenu = new HelicopterSpawnMenu("helicopter-spawn-menu");
|
||||
this.#groundUnitSpawnMenu = new GroundUnitSpawnMenu("groundunit-spawn-menu");
|
||||
this.#navyUnitSpawnMenu = new NavyUnitSpawnMenu("navyunit-spawn-menu");
|
||||
|
||||
/* Event listeners */
|
||||
document.addEventListener("mapContextMenuShow", (e: any) => {
|
||||
if (this.getVisibleSubMenu() !== e.detail.type)
|
||||
this.#showSubMenu(e.detail.type);
|
||||
else
|
||||
this.#hideSubMenus(e.detail.type);
|
||||
});
|
||||
|
||||
document.addEventListener("contextMenuDeploySmoke", (e: any) => {
|
||||
this.hide();
|
||||
spawnSmoke(e.detail.color, this.getLatLng());
|
||||
var marker = new SmokeMarker(this.getLatLng(), e.detail.color);
|
||||
marker.addTo(getMap());
|
||||
});
|
||||
|
||||
document.addEventListener("contextMenuExplosion", (e: any) => {
|
||||
this.hide();
|
||||
spawnExplosion(e.detail.strength, this.getLatLng());
|
||||
});
|
||||
|
||||
document.addEventListener("editCoalitionArea", (e: any) => {
|
||||
this.hide();
|
||||
if (this.#coalitionArea) {
|
||||
getMap().deselectAllCoalitionAreas();
|
||||
this.#coalitionArea.setSelected(true);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("commandModeOptionsChanged", (e: any) => {
|
||||
//this.#refreshOptions();
|
||||
});
|
||||
|
||||
this.#aircraftSpawnMenu.getContainer().addEventListener("resize", () => this.clip());
|
||||
this.#helicopterSpawnMenu.getContainer().addEventListener("resize", () => this.clip());
|
||||
this.#groundUnitSpawnMenu.getContainer().addEventListener("resize", () => this.clip());
|
||||
this.#navyUnitSpawnMenu.getContainer().addEventListener("resize", () => this.clip());
|
||||
|
||||
this.#aircraftSpawnMenu.getContainer().addEventListener("hide", () => this.hide());
|
||||
this.#helicopterSpawnMenu.getContainer().addEventListener("hide", () => this.hide());
|
||||
this.#groundUnitSpawnMenu.getContainer().addEventListener("hide", () => this.hide());
|
||||
this.#navyUnitSpawnMenu.getContainer().addEventListener("hide", () => this.hide());
|
||||
|
||||
this.hide();
|
||||
}
|
||||
|
||||
/** Show the contextmenu on top of the map, usually at the location where the user has clicked on it.
|
||||
*
|
||||
* @param x X screen coordinate of the top left corner of the context menu
|
||||
* @param y Y screen coordinate of the top left corner of the context menu
|
||||
* @param latlng Leaflet latlng object of the mouse click
|
||||
*/
|
||||
show(x: number, y: number, latlng: LatLng) {
|
||||
super.show(x, y, latlng);
|
||||
|
||||
this.#aircraftSpawnMenu.setLatLng(latlng);
|
||||
this.#helicopterSpawnMenu.setLatLng(latlng);
|
||||
this.#groundUnitSpawnMenu.setLatLng(latlng);
|
||||
this.#navyUnitSpawnMenu.setLatLng(latlng);
|
||||
|
||||
this.#aircraftSpawnMenu.setCountries();
|
||||
this.#helicopterSpawnMenu.setCountries();
|
||||
this.#groundUnitSpawnMenu.setCountries();
|
||||
this.#navyUnitSpawnMenu.setCountries();
|
||||
|
||||
/* Only a Game Master can choose the coalition of a new unit */
|
||||
if (getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER)
|
||||
this.#coalitionSwitch.hide()
|
||||
|
||||
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getActiveCoalition()) });
|
||||
if (getActiveCoalition() == "blue")
|
||||
this.#coalitionSwitch.setValue(false);
|
||||
else if (getActiveCoalition() == "red")
|
||||
this.#coalitionSwitch.setValue(true);
|
||||
else
|
||||
this.#coalitionSwitch.setValue(undefined);
|
||||
|
||||
/* Hide the coalition area button. It will be visible if a coalition area is set */
|
||||
this.getContainer()?.querySelector("#coalition-area-button")?.classList.toggle("hide", true);
|
||||
}
|
||||
|
||||
/** If the user rightclicked on a CoalitionArea, it will be given the ability to edit it.
|
||||
*
|
||||
* @param coalitionArea The CoalitionArea the user can edit
|
||||
*/
|
||||
setCoalitionArea(coalitionArea: CoalitionArea) {
|
||||
this.#coalitionArea = coalitionArea;
|
||||
this.getContainer()?.querySelector("#coalition-area-button")?.classList.toggle("hide", false);
|
||||
}
|
||||
|
||||
/** Shows the submenu depending on unit selection
|
||||
*
|
||||
* @param type Submenu type, either "aircraft", "helicopter", "groundunit", "navyunit", "smoke", or "explosion"
|
||||
*/
|
||||
#showSubMenu(type: string) {
|
||||
if (type === "more")
|
||||
this.getContainer()?.querySelector("#more-options-button-bar")?.classList.toggle("hide");
|
||||
else if (["aircraft", "helicopter", "groundunit"].includes(type))
|
||||
this.getContainer()?.querySelector("#more-options-button-bar")?.classList.toggle("hide", true);
|
||||
|
||||
this.getContainer()?.querySelector("#aircraft-spawn-menu")?.classList.toggle("hide", type !== "aircraft");
|
||||
this.getContainer()?.querySelector("#aircraft-spawn-button")?.classList.toggle("is-open", type === "aircraft");
|
||||
this.getContainer()?.querySelector("#helicopter-spawn-menu")?.classList.toggle("hide", type !== "helicopter");
|
||||
this.getContainer()?.querySelector("#helicopter-spawn-button")?.classList.toggle("is-open", type === "helicopter");
|
||||
this.getContainer()?.querySelector("#groundunit-spawn-menu")?.classList.toggle("hide", type !== "groundunit");
|
||||
this.getContainer()?.querySelector("#groundunit-spawn-button")?.classList.toggle("is-open", type === "groundunit");
|
||||
this.getContainer()?.querySelector("#navyunit-spawn-menu")?.classList.toggle("hide", type !== "navyunit");
|
||||
this.getContainer()?.querySelector("#navyunit-spawn-button")?.classList.toggle("is-open", type === "navyunit");
|
||||
this.getContainer()?.querySelector("#smoke-spawn-menu")?.classList.toggle("hide", type !== "smoke");
|
||||
this.getContainer()?.querySelector("#smoke-spawn-button")?.classList.toggle("is-open", type === "smoke");
|
||||
this.getContainer()?.querySelector("#explosion-menu")?.classList.toggle("hide", type !== "explosion");
|
||||
this.getContainer()?.querySelector("#explosion-spawn-button")?.classList.toggle("is-open", type === "explosion");
|
||||
|
||||
(this.getContainer()?.querySelectorAll(".deploy-unit-button"))?.forEach((element: Node) => { (element as HTMLButtonElement).disabled = true; })
|
||||
|
||||
this.#aircraftSpawnMenu.reset();
|
||||
this.#aircraftSpawnMenu.setCountries();
|
||||
this.#helicopterSpawnMenu.reset();
|
||||
this.#helicopterSpawnMenu.setCountries();
|
||||
this.#groundUnitSpawnMenu.reset();
|
||||
this.#groundUnitSpawnMenu.setCountries();
|
||||
this.#navyUnitSpawnMenu.reset();
|
||||
this.#navyUnitSpawnMenu.setCountries();
|
||||
|
||||
this.setVisibleSubMenu(type);
|
||||
this.clip();
|
||||
}
|
||||
|
||||
/** Hide all the submenus
|
||||
*
|
||||
* @param type The type of the currenlt open submenu.
|
||||
*/
|
||||
#hideSubMenus(type: string) {
|
||||
/* Close the lower options bar if the currently open submenu does not required it */
|
||||
this.getContainer()?.querySelector("#more-options-button-bar")?.classList.toggle("hide", ["aircraft", "helicopter", "groundunit"].includes(type));
|
||||
this.getContainer()?.querySelector("#aircraft-spawn-menu")?.classList.toggle("hide", true);
|
||||
this.getContainer()?.querySelector("#aircraft-spawn-button")?.classList.toggle("is-open", false);
|
||||
this.getContainer()?.querySelector("#helicopter-spawn-menu")?.classList.toggle("hide", true);
|
||||
this.getContainer()?.querySelector("#helicopter-spawn-button")?.classList.toggle("is-open", false);
|
||||
this.getContainer()?.querySelector("#groundunit-spawn-menu")?.classList.toggle("hide", true);
|
||||
this.getContainer()?.querySelector("#groundunit-spawn-button")?.classList.toggle("is-open", false);
|
||||
this.getContainer()?.querySelector("#navyunit-spawn-menu")?.classList.toggle("hide", true);
|
||||
this.getContainer()?.querySelector("#navyunit-spawn-button")?.classList.toggle("is-open", false);
|
||||
this.getContainer()?.querySelector("#smoke-spawn-menu")?.classList.toggle("hide", true);
|
||||
this.getContainer()?.querySelector("#smoke-spawn-button")?.classList.toggle("is-open", false);
|
||||
this.getContainer()?.querySelector("#explosion-menu")?.classList.toggle("hide", true);
|
||||
this.getContainer()?.querySelector("#explosion-spawn-button")?.classList.toggle("is-open", false);
|
||||
|
||||
this.#aircraftSpawnMenu.reset();
|
||||
this.#helicopterSpawnMenu.reset();
|
||||
this.#groundUnitSpawnMenu.reset();
|
||||
this.#navyUnitSpawnMenu.reset();
|
||||
|
||||
this.setVisibleSubMenu(null);
|
||||
this.clip();
|
||||
}
|
||||
|
||||
/** Callback called when the user left clicks on the coalition switch
|
||||
*
|
||||
* @param value Switch position (false: "blue", true: "red")
|
||||
*/
|
||||
#onSwitchClick(value: boolean) {
|
||||
value ? setActiveCoalition("red") : setActiveCoalition("blue");
|
||||
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getActiveCoalition()) });
|
||||
this.#aircraftSpawnMenu.setCountries();
|
||||
this.#helicopterSpawnMenu.setCountries();
|
||||
this.#groundUnitSpawnMenu.setCountries();
|
||||
this.#navyUnitSpawnMenu.setCountries();
|
||||
}
|
||||
|
||||
/** Callback called when the user rightclicks on the coalition switch. This will select the "neutral" coalition.
|
||||
*
|
||||
*/
|
||||
#onSwitchRightClick() {
|
||||
this.#coalitionSwitch.setValue(undefined);
|
||||
setActiveCoalition("neutral");
|
||||
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getActiveCoalition()) });
|
||||
this.#aircraftSpawnMenu.setCountries();
|
||||
this.#helicopterSpawnMenu.setCountries();
|
||||
this.#groundUnitSpawnMenu.setCountries();
|
||||
this.#navyUnitSpawnMenu.setCountries();
|
||||
}
|
||||
}
|
||||
32
client/src/contextmenus/unitcontextmenu.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { deg2rad, ftToM } from "../other/utils";
|
||||
import { ContextMenu } from "./contextmenu";
|
||||
|
||||
/** The UnitContextMenu is shown when the user rightclicks on a unit. It dynamically presents the user with possible actions to perform on the unit. */
|
||||
export class UnitContextMenu extends ContextMenu {
|
||||
/**
|
||||
*
|
||||
* @param ID - the ID of the HTML element which will contain the context menu
|
||||
*/
|
||||
constructor(ID: string){
|
||||
super(ID);
|
||||
}
|
||||
|
||||
/** Set the options that will be presented to the user in the contextmenu
|
||||
*
|
||||
* @param options Dictionary element containing the text and tooltip of the options shown in the menu
|
||||
* @param callback Callback that will be called when the user clicks on one of the options
|
||||
*/
|
||||
setOptions(options: { [key: string]: {text: string, tooltip: string }}, callback: CallableFunction) {
|
||||
this.getContainer()?.replaceChildren(...Object.keys(options).map((key: string, idx: number) => {
|
||||
const option = options[key];
|
||||
var button = document.createElement("button");
|
||||
var el = document.createElement("div");
|
||||
el.title = option.tooltip;
|
||||
el.innerText = option.text;
|
||||
el.id = key;
|
||||
button.addEventListener("click", () => callback(key));
|
||||
button.appendChild(el);
|
||||
return (button);
|
||||
}));
|
||||
}
|
||||
}
|
||||
@ -2,8 +2,11 @@ export class Control {
|
||||
#container: HTMLElement | null;
|
||||
expectedValue: any = null;
|
||||
|
||||
constructor(ID: string) {
|
||||
this.#container = document.getElementById(ID);
|
||||
constructor(container: string | null, options?: any) {
|
||||
if (typeof container === "string")
|
||||
this.#container = document.getElementById(container);
|
||||
else
|
||||
this.#container = this.createElement(options);
|
||||
}
|
||||
|
||||
show() {
|
||||
@ -31,4 +34,8 @@ export class Control {
|
||||
checkExpectedValue(value: any) {
|
||||
return this.expectedValue === null || value === this.expectedValue;
|
||||
}
|
||||
|
||||
createElement(options?: any): HTMLElement | null {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -1,16 +1,21 @@
|
||||
export class Dropdown {
|
||||
#element: HTMLElement;
|
||||
#container: HTMLElement;
|
||||
#options: HTMLElement;
|
||||
#value: HTMLElement;
|
||||
#callback: CallableFunction;
|
||||
#defaultValue: string;
|
||||
#optionsList: string[] = [];
|
||||
#index: number = 0;
|
||||
#hidden: boolean = false;
|
||||
|
||||
constructor(ID: string, callback: CallableFunction, options: string[] | null = null) {
|
||||
this.#element = <HTMLElement>document.getElementById(ID);
|
||||
this.#options = <HTMLElement>this.#element.querySelector(".ol-select-options");
|
||||
this.#value = <HTMLElement>this.#element.querySelector(".ol-select-value");
|
||||
constructor(ID: string | null, callback: CallableFunction, options: string[] | null = null, defaultText?: string) {
|
||||
if (ID === null)
|
||||
this.#container = this.#createElement(defaultText);
|
||||
else
|
||||
this.#container = document.getElementById(ID) as HTMLElement;
|
||||
|
||||
this.#options = this.#container.querySelector(".ol-select-options") as HTMLElement;
|
||||
this.#value = this.#container.querySelector(".ol-select-value") as HTMLElement;
|
||||
this.#defaultValue = this.#value.innerText;
|
||||
this.#callback = callback;
|
||||
|
||||
@ -19,14 +24,18 @@ export class Dropdown {
|
||||
this.#value.addEventListener("click", (ev) => { this.#toggle(); });
|
||||
|
||||
document.addEventListener("click", (ev) => {
|
||||
if (!(this.#value.contains(ev.target as Node) || this.#options.contains(ev.target as Node) || this.#element.contains(ev.target as Node))) {
|
||||
this.#close();
|
||||
if (!(this.#value.contains(ev.target as Node) || this.#options.contains(ev.target as Node) || this.#container.contains(ev.target as Node))) {
|
||||
this.close();
|
||||
}
|
||||
});
|
||||
|
||||
this.#options.classList.add("ol-scrollable");
|
||||
}
|
||||
|
||||
getContainer() {
|
||||
return this.#container;
|
||||
}
|
||||
|
||||
setOptions(optionsList: string[], sortAlphabetically: boolean = true) {
|
||||
this.#optionsList = optionsList.sort();
|
||||
if (this.#optionsList.length == 0) {
|
||||
@ -75,7 +84,7 @@ export class Dropdown {
|
||||
this.#value.replaceChildren();
|
||||
this.#value.appendChild(el);
|
||||
this.#index = idx;
|
||||
this.#close();
|
||||
this.close();
|
||||
this.#callback(option);
|
||||
return true;
|
||||
}
|
||||
@ -98,32 +107,66 @@ export class Dropdown {
|
||||
this.selectValue(index);
|
||||
}
|
||||
|
||||
forceValue(value: string) {
|
||||
var el = document.createElement("div");
|
||||
el.classList.add("ol-ellipsed");
|
||||
el.innerText = value;
|
||||
this.#value.replaceChildren();
|
||||
this.#value.appendChild(el);
|
||||
this.close();
|
||||
}
|
||||
|
||||
getIndex() {
|
||||
return this.#index;
|
||||
}
|
||||
|
||||
#clip() {
|
||||
clip() {
|
||||
const options = this.#options;
|
||||
const bounds = options.getBoundingClientRect();
|
||||
this.#element.dataset.position = (bounds.bottom > window.innerHeight) ? "top" : "";
|
||||
this.#container.dataset.position = (bounds.bottom > window.innerHeight) ? "top" : "";
|
||||
}
|
||||
|
||||
#close() {
|
||||
this.#element.classList.remove("is-open");
|
||||
this.#element.dataset.position = "";
|
||||
close() {
|
||||
this.#container.classList.remove("is-open");
|
||||
this.#container.dataset.position = "";
|
||||
}
|
||||
|
||||
#open() {
|
||||
this.#element.classList.add("is-open");
|
||||
open() {
|
||||
this.#container.classList.add("is-open");
|
||||
this.#options.classList.toggle("scrollbar-visible", this.#options.scrollHeight > this.#options.clientHeight);
|
||||
this.#clip();
|
||||
this.clip();
|
||||
}
|
||||
|
||||
show() {
|
||||
this.#container.classList.remove("hide");
|
||||
this.#hidden = false;
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.#container.classList.add("hide");
|
||||
this.#hidden = true;
|
||||
}
|
||||
|
||||
isHidden() {
|
||||
return this.#hidden;
|
||||
}
|
||||
|
||||
#toggle() {
|
||||
if (this.#element.classList.contains("is-open")) {
|
||||
this.#close();
|
||||
} else {
|
||||
this.#open();
|
||||
}
|
||||
this.#container.classList.contains("is-open")? this.close(): this.open();
|
||||
}
|
||||
|
||||
#createElement(defaultText: string | undefined) {
|
||||
var div = document.createElement("div");
|
||||
div.classList.add("ol-select");
|
||||
|
||||
var value = document.createElement("div");
|
||||
value.classList.add("ol-select-value");
|
||||
value.innerText = defaultText? defaultText: "";
|
||||
|
||||
var options = document.createElement("div");
|
||||
options.classList.add("ol-select-options");
|
||||
|
||||
div.append(value, options);
|
||||
return div;
|
||||
}
|
||||
}
|
||||
@ -1,594 +0,0 @@
|
||||
import { LatLng } from "leaflet";
|
||||
import { getActiveCoalition, getMap, getMissionHandler, getUnitsManager, setActiveCoalition } from "..";
|
||||
import { spawnExplosion, spawnSmoke } from "../server/server";
|
||||
import { aircraftDatabase } from "../unit/aircraftdatabase";
|
||||
import { groundUnitDatabase } from "../unit/groundunitdatabase";
|
||||
import { helicopterDatabase } from "../unit/helicopterdatabase";
|
||||
import { ContextMenu } from "./contextmenu";
|
||||
import { Dropdown } from "./dropdown";
|
||||
import { Switch } from "./switch";
|
||||
import { Slider } from "./slider";
|
||||
import { ftToM } from "../other/utils";
|
||||
import { GAME_MASTER } from "../constants/constants";
|
||||
import { navyUnitDatabase } from "../unit/navyunitdatabase";
|
||||
import { CoalitionArea } from "../map/coalitionarea";
|
||||
import { SmokeMarker } from "../map/smokemarker";
|
||||
|
||||
export class MapContextMenu extends ContextMenu {
|
||||
#coalitionSwitch: Switch;
|
||||
#aircraftRoleDropdown: Dropdown;
|
||||
#aircraftLabelDropdown: Dropdown;
|
||||
#aircraftCountDropdown: Dropdown;
|
||||
#aircraftLoadoutDropdown: Dropdown;
|
||||
#aircraftSpawnAltitudeSlider: Slider;
|
||||
#helicopterRoleDropdown: Dropdown;
|
||||
#helicopterLabelDropdown: Dropdown;
|
||||
#helicopterCountDropdown: Dropdown;
|
||||
#helicopterLoadoutDropdown: Dropdown;
|
||||
#helicopterSpawnAltitudeSlider: Slider;
|
||||
#groundUnitTypeDropdown: Dropdown;
|
||||
#groundUnitLabelDropdown: Dropdown;
|
||||
#groundUnitCountDropdown: Dropdown;
|
||||
#navyUnitTypeDropdown: Dropdown;
|
||||
#navyUnitLabelDropdown: Dropdown;
|
||||
#navyUnitCountDropdown: Dropdown;
|
||||
#spawnOptions = { role: "", name: "", latlng: new LatLng(0, 0), coalition: "blue", loadout: "", airbaseName: "", altitude: 0, count: 1 };
|
||||
#coalitionArea: CoalitionArea | null = null;
|
||||
|
||||
constructor(id: string) {
|
||||
super(id);
|
||||
|
||||
this.#coalitionSwitch = new Switch("coalition-switch", (value: boolean) => this.#onSwitchClick(value));
|
||||
this.#coalitionSwitch.setValue(false);
|
||||
this.#coalitionSwitch.getContainer()?.addEventListener("contextmenu", (e) => this.#onSwitchRightClick(e));
|
||||
|
||||
/* Aircraft menu */
|
||||
this.#aircraftRoleDropdown = new Dropdown("aircraft-role-options", (role: string) => this.#setAircraftRole(role));
|
||||
this.#aircraftLabelDropdown = new Dropdown("aircraft-label-options", (type: string) => this.#setAircraftLabel(type));
|
||||
this.#aircraftCountDropdown = new Dropdown("aircraft-count-options", (type: string) => this.#setAircraftCount(type));
|
||||
this.#aircraftCountDropdown.setOptions(["1", "2", "3", "4"]);
|
||||
this.#aircraftCountDropdown.setValue("1");
|
||||
this.#aircraftLoadoutDropdown = new Dropdown("aircraft-loadout-options", (loadout: string) => this.#setAircraftLoadout(loadout));
|
||||
this.#aircraftSpawnAltitudeSlider = new Slider("aircraft-spawn-altitude-slider", 0, 50000, "ft", (value: number) => {this.#spawnOptions.altitude = ftToM(value);});
|
||||
this.#aircraftSpawnAltitudeSlider.setIncrement(500);
|
||||
this.#aircraftSpawnAltitudeSlider.setValue(20000);
|
||||
this.#aircraftSpawnAltitudeSlider.setActive(true);
|
||||
|
||||
/* Helicopter menu */
|
||||
this.#helicopterRoleDropdown = new Dropdown("helicopter-role-options", (role: string) => this.#setHelicopterRole(role));
|
||||
this.#helicopterLabelDropdown = new Dropdown("helicopter-label-options", (type: string) => this.#setHelicopterLabel(type));
|
||||
this.#helicopterCountDropdown = new Dropdown("helicopter-count-options", (type: string) => this.#setHelicopterCount(type));
|
||||
this.#helicopterCountDropdown.setOptions(["1", "2", "3", "4"]);
|
||||
this.#helicopterCountDropdown.setValue("1");
|
||||
this.#helicopterLoadoutDropdown = new Dropdown("helicopter-loadout-options", (loadout: string) => this.#setHelicopterLoadout(loadout));
|
||||
this.#helicopterSpawnAltitudeSlider = new Slider("helicopter-spawn-altitude-slider", 0, 10000, "ft", (value: number) => {this.#spawnOptions.altitude = ftToM(value);});
|
||||
this.#helicopterSpawnAltitudeSlider.setIncrement(50);
|
||||
this.#helicopterSpawnAltitudeSlider.setValue(5000);
|
||||
this.#helicopterSpawnAltitudeSlider.setActive(true);
|
||||
|
||||
var count = [];
|
||||
for (let i = 1; i < 10; i++) count.push(String(i));
|
||||
|
||||
/* Ground unit menu */
|
||||
this.#groundUnitTypeDropdown = new Dropdown("groundunit-type-options", (type: string) => this.#setGroundUnitType(type));
|
||||
this.#groundUnitLabelDropdown = new Dropdown("groundunit-label-options", (name: string) => this.#setGroundUnitLabel(name));
|
||||
this.#groundUnitCountDropdown = new Dropdown("groundunit-count-options", (count: string) => this.#setGroundUnitCount(count));
|
||||
this.#groundUnitCountDropdown.setOptions(count);
|
||||
this.#groundUnitCountDropdown.setValue("1");
|
||||
|
||||
/* Navy unit menu */
|
||||
this.#navyUnitTypeDropdown = new Dropdown("navyunit-type-options", (type: string) => this.#setNavyUnitType(type));
|
||||
this.#navyUnitLabelDropdown = new Dropdown("navyunit-label-options", (name: string) => this.#setNavyUnitLabel(name));
|
||||
this.#navyUnitCountDropdown = new Dropdown("navyunit-count-options", (count: string) => this.#setNavyUnitCount(count));
|
||||
this.#navyUnitCountDropdown.setOptions(count);
|
||||
this.#navyUnitCountDropdown.setValue("1");
|
||||
|
||||
document.addEventListener("mapContextMenuShow", (e: any) => {
|
||||
if (this.getVisibleSubMenu() !== e.detail.type)
|
||||
this.showSubMenu(e.detail.type);
|
||||
else
|
||||
this.hideSubMenus(e.detail.type);
|
||||
});
|
||||
|
||||
document.addEventListener("contextMenuDeployAircrafts", () => {
|
||||
this.#spawnOptions.coalition = getActiveCoalition();
|
||||
if (this.#spawnOptions) {
|
||||
const liveryID = aircraftDatabase.getByName(this.#spawnOptions.name)?.liveryID;
|
||||
var unitTable = {unitType: this.#spawnOptions.name, location: this.#spawnOptions.latlng, altitude: this.#spawnOptions.altitude, loadout: this.#spawnOptions.loadout, liveryID: liveryID? liveryID: ""};
|
||||
var units = [];
|
||||
for (let i = 1; i < parseInt(this.#aircraftCountDropdown.getValue()) + 1; i++) {
|
||||
units.push(unitTable);
|
||||
}
|
||||
if (getUnitsManager().spawnUnits("Aircraft", units, getActiveCoalition(), false, this.#spawnOptions.airbaseName)) {
|
||||
getMap().addTemporaryMarker(this.#spawnOptions.latlng, this.#spawnOptions.name, getActiveCoalition());
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("contextMenuDeployHelicopters", () => {
|
||||
this.#spawnOptions.coalition = getActiveCoalition();
|
||||
if (this.#spawnOptions) {
|
||||
const liveryID = aircraftDatabase.getByName(this.#spawnOptions.name)?.liveryID;
|
||||
var unitTable = {unitType: this.#spawnOptions.name, location: this.#spawnOptions.latlng, altitude: this.#spawnOptions.altitude, loadout: this.#spawnOptions.loadout, liveryID: liveryID? liveryID: ""};
|
||||
var units = [];
|
||||
for (let i = 1; i < parseInt(this.#helicopterCountDropdown.getValue()) + 1; i++) {
|
||||
units.push(unitTable);
|
||||
}
|
||||
if (getUnitsManager().spawnUnits("Helicopter", units, getActiveCoalition(), false, this.#spawnOptions.airbaseName)) {
|
||||
getMap().addTemporaryMarker(this.#spawnOptions.latlng, this.#spawnOptions.name, getActiveCoalition());
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("contextMenuDeployGroundUnits", () => {
|
||||
this.#spawnOptions.coalition = getActiveCoalition();
|
||||
if (this.#spawnOptions) {
|
||||
const liveryID = aircraftDatabase.getByName(this.#spawnOptions.name)?.liveryID;
|
||||
var unitTable = {unitType: this.#spawnOptions.name, location: this.#spawnOptions.latlng, liveryID: liveryID? liveryID: ""};
|
||||
var units = [];
|
||||
for (let i = 1; i < parseInt(this.#groundUnitCountDropdown.getValue()) + 1; i++) {
|
||||
units.push(JSON.parse(JSON.stringify(unitTable)));
|
||||
unitTable.location.lat += 0.0001;
|
||||
}
|
||||
if (getUnitsManager().spawnUnits("GroundUnit", units, getActiveCoalition(), false)) {
|
||||
getMap().addTemporaryMarker(this.#spawnOptions.latlng, this.#spawnOptions.name, getActiveCoalition());
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("contextMenuDeployNavyUnits", () => {
|
||||
this.#spawnOptions.coalition = getActiveCoalition();
|
||||
if (this.#spawnOptions) {
|
||||
const liveryID = aircraftDatabase.getByName(this.#spawnOptions.name)?.liveryID;
|
||||
var unitTable = {unitType: this.#spawnOptions.name, location: this.#spawnOptions.latlng, liveryID: liveryID? liveryID: ""};
|
||||
var units = [];
|
||||
for (let i = 1; i < parseInt(this.#navyUnitCountDropdown.getValue()) + 1; i++) {
|
||||
units.push(JSON.parse(JSON.stringify(unitTable)));
|
||||
unitTable.location.lat += 0.0001;
|
||||
}
|
||||
if (getUnitsManager().spawnUnits("NavyUnit", units, getActiveCoalition(), false)) {
|
||||
getMap().addTemporaryMarker(this.#spawnOptions.latlng, this.#spawnOptions.name, getActiveCoalition());
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("contextMenuDeploySmoke", (e: any) => {
|
||||
this.hide();
|
||||
spawnSmoke(e.detail.color, this.getLatLng());
|
||||
var marker = new SmokeMarker(this.getLatLng(), e.detail.color);
|
||||
marker.addTo(getMap());
|
||||
});
|
||||
|
||||
document.addEventListener("contextMenuExplosion", (e: any) => {
|
||||
this.hide();
|
||||
spawnExplosion(e.detail.strength, this.getLatLng());
|
||||
});
|
||||
|
||||
document.addEventListener("editCoalitionArea", (e: any) => {
|
||||
this.hide();
|
||||
if (this.#coalitionArea) {
|
||||
getMap().deselectAllCoalitionAreas();
|
||||
this.#coalitionArea.setSelected(true);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("commandModeOptionsChanged", (e: any) => {
|
||||
this.#refreshOptions();
|
||||
});
|
||||
|
||||
this.hide();
|
||||
}
|
||||
|
||||
show(x: number, y: number, latlng: LatLng) {
|
||||
super.show(x, y, latlng);
|
||||
this.showUpperBar();
|
||||
|
||||
this.showAltitudeSlider();
|
||||
|
||||
this.#spawnOptions.airbaseName = "";
|
||||
this.#spawnOptions.latlng = latlng;
|
||||
|
||||
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getActiveCoalition()) });
|
||||
if (getActiveCoalition() == "blue")
|
||||
this.#coalitionSwitch.setValue(false);
|
||||
else if (getActiveCoalition() == "red")
|
||||
this.#coalitionSwitch.setValue(true);
|
||||
else
|
||||
this.#coalitionSwitch.setValue(undefined);
|
||||
|
||||
if (getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER)
|
||||
this.#coalitionSwitch.hide()
|
||||
|
||||
this.getContainer()?.querySelector("#coalition-area-button")?.classList.toggle("hide", true);
|
||||
}
|
||||
|
||||
showSubMenu(type: string) {
|
||||
if (type === "more")
|
||||
this.getContainer()?.querySelector("#more-options-button-bar")?.classList.toggle("hide");
|
||||
else if (["aircraft", "groundunit"].includes(type))
|
||||
this.getContainer()?.querySelector("#more-options-button-bar")?.classList.toggle("hide", true);
|
||||
|
||||
this.getContainer()?.querySelector("#aircraft-spawn-menu")?.classList.toggle("hide", type !== "aircraft");
|
||||
this.getContainer()?.querySelector("#aircraft-spawn-button")?.classList.toggle("is-open", type === "aircraft");
|
||||
this.getContainer()?.querySelector("#helicopter-spawn-menu")?.classList.toggle("hide", type !== "helicopter");
|
||||
this.getContainer()?.querySelector("#helicopter-spawn-button")?.classList.toggle("is-open", type === "helicopter");
|
||||
this.getContainer()?.querySelector("#groundunit-spawn-menu")?.classList.toggle("hide", type !== "groundunit");
|
||||
this.getContainer()?.querySelector("#groundunit-spawn-button")?.classList.toggle("is-open", type === "groundunit");
|
||||
this.getContainer()?.querySelector("#navyunit-spawn-menu")?.classList.toggle("hide", type !== "navyunit");
|
||||
this.getContainer()?.querySelector("#navyunit-spawn-button")?.classList.toggle("is-open", type === "navyunit");
|
||||
this.getContainer()?.querySelector("#smoke-spawn-menu")?.classList.toggle("hide", type !== "smoke");
|
||||
this.getContainer()?.querySelector("#smoke-spawn-button")?.classList.toggle("is-open", type === "smoke");
|
||||
this.getContainer()?.querySelector("#explosion-menu")?.classList.toggle("hide", type !== "explosion");
|
||||
this.getContainer()?.querySelector("#explosion-spawn-button")?.classList.toggle("is-open", type === "explosion");
|
||||
|
||||
(this.getContainer()?.querySelectorAll(".deploy-unit-button"))?.forEach((element: Node) => {(element as HTMLButtonElement).disabled = true;})
|
||||
|
||||
this.#resetAircraftRole();
|
||||
this.#resetAircraftLabel();
|
||||
this.#resetHelicopterRole();
|
||||
this.#resetHelicopterLabel();
|
||||
this.#resetGroundUnitType();
|
||||
this.#resetGroundUnitLabel();
|
||||
this.#resetNavyUnitType();
|
||||
this.#resetNavyUnitLabel();
|
||||
this.#aircraftCountDropdown.setValue("1");
|
||||
this.#helicopterCountDropdown.setValue("1");
|
||||
this.#groundUnitCountDropdown.setValue("1");
|
||||
this.clip();
|
||||
|
||||
if (type === "aircraft") {
|
||||
this.#spawnOptions.altitude = ftToM(this.#aircraftSpawnAltitudeSlider.getValue());
|
||||
}
|
||||
else if (type === "helicopter") {
|
||||
this.#spawnOptions.altitude = ftToM(this.#helicopterSpawnAltitudeSlider.getValue());
|
||||
}
|
||||
|
||||
this.setVisibleSubMenu(type);
|
||||
}
|
||||
|
||||
hideSubMenus(type: string) {
|
||||
this.getContainer()?.querySelector("#more-options-button-bar")?.classList.toggle("hide", ["aircraft", "groundunit"].includes(type));
|
||||
this.getContainer()?.querySelector("#aircraft-spawn-menu")?.classList.toggle("hide", true);
|
||||
this.getContainer()?.querySelector("#aircraft-spawn-button")?.classList.toggle("is-open", false);
|
||||
this.getContainer()?.querySelector("#helicopter-spawn-menu")?.classList.toggle("hide", true);
|
||||
this.getContainer()?.querySelector("#helicopter-spawn-button")?.classList.toggle("is-open", false);
|
||||
this.getContainer()?.querySelector("#groundunit-spawn-menu")?.classList.toggle("hide", true);
|
||||
this.getContainer()?.querySelector("#groundunit-spawn-button")?.classList.toggle("is-open", false);
|
||||
this.getContainer()?.querySelector("#navyunit-spawn-menu")?.classList.toggle("hide", true);
|
||||
this.getContainer()?.querySelector("#navyunit-spawn-button")?.classList.toggle("is-open", false);
|
||||
this.getContainer()?.querySelector("#smoke-spawn-menu")?.classList.toggle("hide", true);
|
||||
this.getContainer()?.querySelector("#smoke-spawn-button")?.classList.toggle("is-open", false);
|
||||
this.getContainer()?.querySelector("#explosion-menu")?.classList.toggle("hide", true);
|
||||
this.getContainer()?.querySelector("#explosion-spawn-button")?.classList.toggle("is-open", false);
|
||||
|
||||
this.#resetAircraftRole();
|
||||
this.#resetAircraftLabel();
|
||||
this.#resetHelicopterRole();
|
||||
this.#resetHelicopterLabel();
|
||||
this.#resetHelicopterRole();
|
||||
this.#resetHelicopterLabel();
|
||||
this.#resetGroundUnitType();
|
||||
this.#resetGroundUnitLabel();
|
||||
this.#resetNavyUnitType();
|
||||
this.#resetNavyUnitLabel();
|
||||
this.clip();
|
||||
|
||||
this.setVisibleSubMenu(null);
|
||||
}
|
||||
|
||||
showUpperBar() {
|
||||
this.getContainer()?.querySelector(".upper-bar")?.classList.toggle("hide", false);
|
||||
}
|
||||
|
||||
hideUpperBar() {
|
||||
this.getContainer()?.querySelector(".upper-bar")?.classList.toggle("hide", true);
|
||||
}
|
||||
|
||||
showLowerBar() {
|
||||
this.getContainer()?.querySelector("#more-options-button-bar")?.classList.toggle("hide", false);
|
||||
}
|
||||
|
||||
hideLowerBar() {
|
||||
this.getContainer()?.querySelector("#more-optionsbutton-bar")?.classList.toggle("hide", true);
|
||||
}
|
||||
|
||||
showAltitudeSlider() {
|
||||
this.getContainer()?.querySelector("#aircraft-spawn-altitude-slider")?.classList.toggle("hide", false);
|
||||
}
|
||||
|
||||
hideAltitudeSlider() {
|
||||
this.getContainer()?.querySelector("#aircraft-spawn-altitude-slider")?.classList.toggle("hide", true);
|
||||
}
|
||||
|
||||
setAirbaseName(airbaseName: string) {
|
||||
this.#spawnOptions.airbaseName = airbaseName;
|
||||
}
|
||||
|
||||
setLatLng(latlng: LatLng) {
|
||||
this.#spawnOptions.latlng = latlng;
|
||||
}
|
||||
|
||||
setCoalitionArea(coalitionArea: CoalitionArea) {
|
||||
this.#coalitionArea = coalitionArea;
|
||||
this.getContainer()?.querySelector("#coalition-area-button")?.classList.toggle("hide", false);
|
||||
}
|
||||
|
||||
#onSwitchClick(value: boolean) {
|
||||
value? setActiveCoalition("red"): setActiveCoalition("blue");
|
||||
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getActiveCoalition()) });
|
||||
}
|
||||
|
||||
#onSwitchRightClick(e: any) {
|
||||
this.#coalitionSwitch.setValue(undefined);
|
||||
setActiveCoalition("neutral");
|
||||
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getActiveCoalition()) });
|
||||
}
|
||||
|
||||
#refreshOptions() {
|
||||
if (!aircraftDatabase.getRoles().includes(this.#aircraftRoleDropdown.getValue()))
|
||||
this.#resetAircraftRole();
|
||||
if (!aircraftDatabase.getByRole(this.#aircraftRoleDropdown.getValue()).map((blueprint) => { return blueprint.label }).includes(this.#aircraftLabelDropdown.getValue()))
|
||||
this.#resetAircraftLabel();
|
||||
|
||||
if (!helicopterDatabase.getRoles().includes(this.#helicopterRoleDropdown.getValue()))
|
||||
this.#resetHelicopterRole();
|
||||
if (!helicopterDatabase.getByRole(this.#helicopterRoleDropdown.getValue()).map((blueprint) => { return blueprint.label }).includes(this.#helicopterLabelDropdown.getValue()))
|
||||
this.#resetHelicopterLabel();
|
||||
|
||||
if (!groundUnitDatabase.getRoles().includes(this.#groundUnitTypeDropdown.getValue()))
|
||||
this.#resetGroundUnitType();
|
||||
if (!groundUnitDatabase.getByType(this.#groundUnitTypeDropdown.getValue()).map((blueprint) => { return blueprint.label }).includes(this.#groundUnitLabelDropdown.getValue()))
|
||||
this.#resetGroundUnitLabel();
|
||||
|
||||
if (!navyUnitDatabase.getRoles().includes(this.#navyUnitTypeDropdown.getValue()))
|
||||
this.#resetNavyUnitType();
|
||||
if (!navyUnitDatabase.getByType(this.#navyUnitTypeDropdown.getValue()).map((blueprint) => { return blueprint.label }).includes(this.#aircraftLabelDropdown.getValue()))
|
||||
this.#resetNavyUnitLabel();
|
||||
}
|
||||
|
||||
/********* Aircraft spawn menu *********/
|
||||
#setAircraftRole(role: string) {
|
||||
this.#spawnOptions.role = role;
|
||||
this.#resetAircraftLabel();
|
||||
this.#aircraftLabelDropdown.setOptions(aircraftDatabase.getByRole(role).map((blueprint) => { return blueprint.label }));
|
||||
this.#aircraftLabelDropdown.selectValue(0);
|
||||
this.clip();
|
||||
this.#computeSpawnPoints();
|
||||
}
|
||||
|
||||
#resetAircraftRole() {
|
||||
(<HTMLButtonElement>this.getContainer()?.querySelector("#aircraft-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true;
|
||||
(<HTMLButtonElement>this.getContainer()?.querySelector("#aircraft-loadout-list")).replaceChildren();
|
||||
this.#aircraftRoleDropdown.reset();
|
||||
this.#aircraftLabelDropdown.reset();
|
||||
this.#aircraftRoleDropdown.setOptions(aircraftDatabase.getRoles());
|
||||
this.clip();
|
||||
}
|
||||
|
||||
#setAircraftLabel(label: string) {
|
||||
this.#resetAircraftLabel();
|
||||
var name = aircraftDatabase.getByLabel(label)?.name || null;
|
||||
if (name != null) {
|
||||
this.#spawnOptions.name = name;
|
||||
this.#aircraftLoadoutDropdown.setOptions(aircraftDatabase.getLoadoutNamesByRole(name, this.#spawnOptions.role));
|
||||
this.#aircraftLoadoutDropdown.selectValue(0);
|
||||
var image = (<HTMLImageElement>this.getContainer()?.querySelector("#aircraft-unit-image"));
|
||||
image.src = `images/units/${aircraftDatabase.getByLabel(label)?.filename}`;
|
||||
image.classList.toggle("hide", false);
|
||||
}
|
||||
this.clip();
|
||||
this.#computeSpawnPoints();
|
||||
}
|
||||
|
||||
#resetAircraftLabel() {
|
||||
(<HTMLButtonElement>this.getContainer()?.querySelector("#aircraft-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true;
|
||||
(<HTMLButtonElement>this.getContainer()?.querySelector("#aircraft-loadout-list")).replaceChildren();
|
||||
this.#aircraftLoadoutDropdown.reset();
|
||||
(<HTMLImageElement>this.getContainer()?.querySelector("#aircraft-unit-image")).classList.toggle("hide", true);
|
||||
this.clip();
|
||||
}
|
||||
|
||||
#setAircraftCount(count: string) {
|
||||
this.#spawnOptions.count = parseInt(count);
|
||||
this.clip();
|
||||
this.#computeSpawnPoints();
|
||||
}
|
||||
|
||||
#setAircraftLoadout(loadoutName: string) {
|
||||
var loadout = aircraftDatabase.getLoadoutByName(this.#spawnOptions.name, loadoutName);
|
||||
if (loadout) {
|
||||
this.#spawnOptions.loadout = loadout.code;
|
||||
(<HTMLButtonElement>this.getContainer()?.querySelector("#aircraft-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = false;
|
||||
var items = loadout.items.map((item: any) => { return `${item.quantity}x ${item.name}`; });
|
||||
items.length == 0 ? items.push("Empty loadout") : "";
|
||||
(<HTMLButtonElement>this.getContainer()?.querySelector("#aircraft-loadout-list")).replaceChildren(
|
||||
...items.map((item: any) => {
|
||||
var div = document.createElement('div');
|
||||
div.innerText = item;
|
||||
return div;
|
||||
})
|
||||
)
|
||||
}
|
||||
this.clip();
|
||||
}
|
||||
|
||||
/********* Helicopter spawn menu *********/
|
||||
#setHelicopterRole(role: string) {
|
||||
this.#spawnOptions.role = role;
|
||||
this.#resetHelicopterLabel();
|
||||
this.#helicopterLabelDropdown.setOptions(helicopterDatabase.getByRole(role).map((blueprint) => { return blueprint.label }));
|
||||
this.#helicopterLabelDropdown.selectValue(0);
|
||||
this.clip();
|
||||
this.#computeSpawnPoints();
|
||||
}
|
||||
|
||||
#resetHelicopterRole() {
|
||||
(<HTMLButtonElement>this.getContainer()?.querySelector("#helicopter-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true;
|
||||
(<HTMLButtonElement>this.getContainer()?.querySelector("#helicopter-loadout-list")).replaceChildren();
|
||||
this.#helicopterRoleDropdown.reset();
|
||||
this.#helicopterLabelDropdown.reset();
|
||||
this.#helicopterRoleDropdown.setOptions(helicopterDatabase.getRoles());
|
||||
this.clip();
|
||||
}
|
||||
|
||||
#setHelicopterLabel(label: string) {
|
||||
this.#resetHelicopterLabel();
|
||||
var name = helicopterDatabase.getByLabel(label)?.name || null;
|
||||
if (name != null) {
|
||||
this.#spawnOptions.name = name;
|
||||
this.#helicopterLoadoutDropdown.setOptions(helicopterDatabase.getLoadoutNamesByRole(name, this.#spawnOptions.role));
|
||||
this.#helicopterLoadoutDropdown.selectValue(0);
|
||||
var image = (<HTMLImageElement>this.getContainer()?.querySelector("#helicopter-unit-image"));
|
||||
image.src = `images/units/${helicopterDatabase.getByLabel(label)?.filename}`;
|
||||
image.classList.toggle("hide", false);
|
||||
}
|
||||
this.clip();
|
||||
this.#computeSpawnPoints();
|
||||
}
|
||||
|
||||
#resetHelicopterLabel() {
|
||||
(<HTMLButtonElement>this.getContainer()?.querySelector("#helicopter-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true;
|
||||
(<HTMLButtonElement>this.getContainer()?.querySelector("#helicopter-loadout-list")).replaceChildren();
|
||||
this.#helicopterLoadoutDropdown.reset();
|
||||
(<HTMLImageElement>this.getContainer()?.querySelector("#helicopter-unit-image")).classList.toggle("hide", true);
|
||||
this.clip();
|
||||
}
|
||||
|
||||
#setHelicopterCount(count: string) {
|
||||
this.#spawnOptions.count = parseInt(count);
|
||||
this.clip();
|
||||
this.#computeSpawnPoints();
|
||||
}
|
||||
|
||||
#setHelicopterLoadout(loadoutName: string) {
|
||||
var loadout = helicopterDatabase.getLoadoutByName(this.#spawnOptions.name, loadoutName);
|
||||
if (loadout) {
|
||||
this.#spawnOptions.loadout = loadout.code;
|
||||
(<HTMLButtonElement>this.getContainer()?.querySelector("#helicopter-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = false;
|
||||
var items = loadout.items.map((item: any) => { return `${item.quantity}x ${item.name}`; });
|
||||
items.length == 0 ? items.push("Empty loadout") : "";
|
||||
(<HTMLButtonElement>this.getContainer()?.querySelector("#helicopter-loadout-list")).replaceChildren(
|
||||
...items.map((item: any) => {
|
||||
var div = document.createElement('div');
|
||||
div.innerText = item;
|
||||
return div;
|
||||
})
|
||||
)
|
||||
}
|
||||
this.clip();
|
||||
}
|
||||
|
||||
/********* Groundunit spawn menu *********/
|
||||
#setGroundUnitType(role: string) {
|
||||
this.#resetGroundUnitLabel();
|
||||
|
||||
const types = groundUnitDatabase.getByType(role).map((blueprint) => { return blueprint.label });
|
||||
this.#groundUnitLabelDropdown.setOptions(types);
|
||||
this.#groundUnitLabelDropdown.selectValue(0);
|
||||
this.clip();
|
||||
this.#computeSpawnPoints();
|
||||
}
|
||||
|
||||
#resetGroundUnitType() {
|
||||
(<HTMLButtonElement>this.getContainer()?.querySelector("#groundunit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true;
|
||||
this.#groundUnitTypeDropdown.reset();
|
||||
this.#groundUnitLabelDropdown.reset();
|
||||
|
||||
const types = groundUnitDatabase.getTypes();
|
||||
this.#groundUnitTypeDropdown.setOptions(types);
|
||||
this.clip();
|
||||
}
|
||||
|
||||
#setGroundUnitLabel(label: string) {
|
||||
this.#resetGroundUnitLabel();
|
||||
var type = groundUnitDatabase.getByLabel(label)?.name || null;
|
||||
if (type != null) {
|
||||
this.#spawnOptions.name = type;
|
||||
(<HTMLButtonElement>this.getContainer()?.querySelector("#groundunit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = false;
|
||||
}
|
||||
this.clip();
|
||||
this.#computeSpawnPoints();
|
||||
}
|
||||
|
||||
#resetGroundUnitLabel() {
|
||||
(<HTMLButtonElement>this.getContainer()?.querySelector("#groundunit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true;
|
||||
this.clip();
|
||||
}
|
||||
|
||||
#setGroundUnitCount(count: string) {
|
||||
this.#spawnOptions.count = parseInt(count);
|
||||
this.clip();
|
||||
this.#computeSpawnPoints();
|
||||
}
|
||||
|
||||
/********* Navyunit spawn menu *********/
|
||||
#setNavyUnitType(role: string) {
|
||||
this.#resetNavyUnitLabel();
|
||||
|
||||
const types = navyUnitDatabase.getByType(role).map((blueprint) => { return blueprint.label });
|
||||
this.#navyUnitLabelDropdown.setOptions(types);
|
||||
this.#navyUnitLabelDropdown.selectValue(0);
|
||||
this.clip();
|
||||
this.#computeSpawnPoints();
|
||||
}
|
||||
|
||||
#resetNavyUnitType() {
|
||||
(<HTMLButtonElement>this.getContainer()?.querySelector("#navyunit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true;
|
||||
this.#navyUnitTypeDropdown.reset();
|
||||
this.#navyUnitLabelDropdown.reset();
|
||||
|
||||
const types = navyUnitDatabase.getTypes();
|
||||
this.#navyUnitTypeDropdown.setOptions(types);
|
||||
this.clip();
|
||||
}
|
||||
|
||||
#setNavyUnitLabel(label: string) {
|
||||
this.#resetNavyUnitLabel();
|
||||
var type = navyUnitDatabase.getByLabel(label)?.name || null;
|
||||
if (type != null) {
|
||||
this.#spawnOptions.name = type;
|
||||
(<HTMLButtonElement>this.getContainer()?.querySelector("#navyunit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = false;
|
||||
}
|
||||
this.clip();
|
||||
this.#computeSpawnPoints();
|
||||
}
|
||||
|
||||
#resetNavyUnitLabel() {
|
||||
(<HTMLButtonElement>this.getContainer()?.querySelector("#navyunit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true;
|
||||
this.clip();
|
||||
}
|
||||
|
||||
#setNavyUnitCount(count: string) {
|
||||
this.#spawnOptions.count = parseInt(count);
|
||||
this.clip();
|
||||
this.#computeSpawnPoints();
|
||||
}
|
||||
|
||||
#computeSpawnPoints() {
|
||||
if (getMissionHandler() && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER){
|
||||
var aircraftCount = parseInt(this.#aircraftCountDropdown.getValue());
|
||||
var aircraftSpawnPoints = aircraftCount * aircraftDatabase.getSpawnPointsByLabel(this.#aircraftLabelDropdown.getValue());
|
||||
(<HTMLButtonElement>this.getContainer()?.querySelector("#aircraft-spawn-menu")?.querySelector(".deploy-unit-button")).dataset.points = `${aircraftSpawnPoints}`;
|
||||
(<HTMLButtonElement>this.getContainer()?.querySelector("#aircraft-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = aircraftSpawnPoints >= getMissionHandler().getAvailableSpawnPoints();
|
||||
|
||||
var helicopterCount = parseInt(this.#helicopterCountDropdown.getValue());
|
||||
var helicopterSpawnPoints = helicopterCount * helicopterDatabase.getSpawnPointsByLabel(this.#helicopterLabelDropdown.getValue());
|
||||
(<HTMLButtonElement>this.getContainer()?.querySelector("#helicopter-spawn-menu")?.querySelector(".deploy-unit-button")).dataset.points = `${helicopterSpawnPoints}`;
|
||||
(<HTMLButtonElement>this.getContainer()?.querySelector("#helicopter-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = helicopterSpawnPoints >= getMissionHandler().getAvailableSpawnPoints();
|
||||
|
||||
var groundUnitCount = parseInt(this.#groundUnitCountDropdown.getValue());
|
||||
var groundUnitSpawnPoints = groundUnitCount * groundUnitDatabase.getSpawnPointsByLabel(this.#groundUnitLabelDropdown.getValue());
|
||||
(<HTMLButtonElement>this.getContainer()?.querySelector("#groundunit-spawn-menu")?.querySelector(".deploy-unit-button")).dataset.points = `${groundUnitSpawnPoints}`;
|
||||
(<HTMLButtonElement>this.getContainer()?.querySelector("#groundunit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = groundUnitSpawnPoints >= getMissionHandler().getAvailableSpawnPoints();
|
||||
|
||||
var navyUnitCount = parseInt(this.#navyUnitCountDropdown.getValue());
|
||||
var navyUnitSpawnPoints = navyUnitCount * navyUnitDatabase.getSpawnPointsByLabel(this.#navyUnitLabelDropdown.getValue());
|
||||
(<HTMLButtonElement>this.getContainer()?.querySelector("#navyunit-spawn-menu")?.querySelector(".deploy-unit-button")).dataset.points = `${navyUnitSpawnPoints}`;
|
||||
(<HTMLButtonElement>this.getContainer()?.querySelector("#navyunit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = navyUnitSpawnPoints >= getMissionHandler().getAvailableSpawnPoints();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -13,8 +13,9 @@ export class Slider extends Control {
|
||||
#dragged: boolean = false;
|
||||
#value: number = 0;
|
||||
|
||||
constructor(ID: string, minValue: number, maxValue: number, unitOfMeasure: string, callback: CallableFunction) {
|
||||
super(ID);
|
||||
constructor(ID: string | null, minValue: number, maxValue: number, unitOfMeasure: string, callback: CallableFunction, options?: any) {
|
||||
super(ID, options);
|
||||
|
||||
this.#callback = callback;
|
||||
this.#unitOfMeasure = unitOfMeasure;
|
||||
this.#slider = this.getContainer()?.querySelector("input") as HTMLInputElement;
|
||||
@ -62,7 +63,12 @@ export class Slider extends Control {
|
||||
|
||||
setValue(newValue: number, ignoreExpectedValue: boolean = true) {
|
||||
if (!this.getDragged() && (ignoreExpectedValue || this.checkExpectedValue(newValue))) {
|
||||
this.#value = newValue;
|
||||
if (this.#value !== newValue) {
|
||||
this.#value = newValue;
|
||||
if (this.#callback)
|
||||
this.#callback(this.getValue());
|
||||
}
|
||||
|
||||
if (this.#slider != null)
|
||||
this.#slider.value = String((newValue - this.#minValue) / (this.#maxValue - this.#minValue) * parseFloat(this.#slider.max));
|
||||
this.#update();
|
||||
@ -120,4 +126,31 @@ export class Slider extends Control {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createElement(options?: any): HTMLElement | null {
|
||||
var containerEl = document.createElement("div");
|
||||
containerEl.classList.add("ol-slider-container", "flight-control-ol-slider");
|
||||
|
||||
var dl = document.createElement("dl");
|
||||
dl.classList.add("ol-data-grid");
|
||||
|
||||
var dt = document.createElement("dt");
|
||||
dt.innerText = (options !== undefined && options.title !== undefined)? options.title: "";
|
||||
|
||||
var dd = document.createElement("dd");
|
||||
var sliderEl = document.createElement("div");
|
||||
sliderEl.classList.add("ol-slider-value");
|
||||
dd.append(sliderEl);
|
||||
dl.append(dt, dd);
|
||||
|
||||
var input = document.createElement("input") as HTMLInputElement;
|
||||
input.type = "range";
|
||||
input.min = "0";
|
||||
input.max = "100";
|
||||
input.value = "0"
|
||||
input.classList.add("ol-slider");
|
||||
containerEl.append(dl, input);
|
||||
|
||||
return containerEl;
|
||||
}
|
||||
}
|
||||
@ -4,6 +4,7 @@ export class Switch extends Control {
|
||||
#value: boolean | undefined = false;
|
||||
#callback: CallableFunction | null = null;
|
||||
|
||||
// TODO: allow for null ID so that the element is created automatically
|
||||
constructor(ID: string, callback: CallableFunction, initialValue?: boolean) {
|
||||
super(ID);
|
||||
this.getContainer()?.addEventListener('click', (e) => this.#onToggle());
|
||||
|
||||
@ -1,56 +0,0 @@
|
||||
import { deg2rad, ftToM } from "../other/utils";
|
||||
import { ContextMenu } from "./contextmenu";
|
||||
|
||||
export class UnitContextMenu extends ContextMenu {
|
||||
#customFormationCallback: CallableFunction | null = null;
|
||||
|
||||
constructor(id: string) {
|
||||
super(id);
|
||||
|
||||
document.addEventListener("applyCustomFormation", () => {
|
||||
var dialog = document.getElementById("custom-formation-dialog");
|
||||
if (dialog) {
|
||||
dialog.classList.add("hide");
|
||||
var clock = 1;
|
||||
while (clock < 8) {
|
||||
if ((<HTMLInputElement>dialog.querySelector(`#formation-${clock}`)).checked)
|
||||
break
|
||||
clock++;
|
||||
}
|
||||
var angleDeg = 360 - (clock - 1) * 45;
|
||||
var angleRad = deg2rad(angleDeg);
|
||||
var distance = ftToM(parseInt((<HTMLInputElement>dialog.querySelector(`#distance`)?.querySelector("input")).value));
|
||||
var upDown = ftToM(parseInt((<HTMLInputElement>dialog.querySelector(`#up-down`)?.querySelector("input")).value));
|
||||
|
||||
// X: front-rear, positive front
|
||||
// Y: top-bottom, positive top
|
||||
// Z: left-right, positive right
|
||||
|
||||
var x = distance * Math.cos(angleRad);
|
||||
var y = upDown;
|
||||
var z = distance * Math.sin(angleRad);
|
||||
|
||||
if (this.#customFormationCallback)
|
||||
this.#customFormationCallback({ "x": x, "y": y, "z": z })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
setCustomFormationCallback(callback: CallableFunction) {
|
||||
this.#customFormationCallback = callback;
|
||||
}
|
||||
|
||||
setOptions(options: { [key: string]: {text: string, tooltip: string }}, callback: CallableFunction) {
|
||||
this.getContainer()?.replaceChildren(...Object.keys(options).map((key: string, idx: number) => {
|
||||
const option = options[key];
|
||||
var button = document.createElement("button");
|
||||
var el = document.createElement("div");
|
||||
el.title = option.tooltip;
|
||||
el.innerText = option.text;
|
||||
el.id = key;
|
||||
button.addEventListener("click", () => callback(key));
|
||||
button.appendChild(el);
|
||||
return (button);
|
||||
}));
|
||||
}
|
||||
}
|
||||
539
client/src/controls/unitspawnmenu.ts
Normal file
@ -0,0 +1,539 @@
|
||||
import { LatLng } from "leaflet";
|
||||
import { Dropdown } from "./dropdown";
|
||||
import { Slider } from "./slider";
|
||||
import { UnitDatabase } from "../unit/databases/unitdatabase";
|
||||
import { getActiveCoalition, getMap, getMissionHandler, getUnitsManager } from "..";
|
||||
import { GAME_MASTER } from "../constants/constants";
|
||||
import { UnitSpawnOptions } from "../@types/unitdatabase";
|
||||
import { Airbase } from "../mission/airbase";
|
||||
import { ftToM } from "../other/utils";
|
||||
import { aircraftDatabase } from "../unit/databases/aircraftdatabase";
|
||||
import { helicopterDatabase } from "../unit/databases/helicopterdatabase";
|
||||
import { groundUnitDatabase } from "../unit/databases/groundunitdatabase";
|
||||
import { navyUnitDatabase } from "../unit/databases/navyunitdatabase";
|
||||
import { UnitSpawnTable } from "../@types/unit";
|
||||
|
||||
export class UnitSpawnMenu {
|
||||
#container: HTMLElement;
|
||||
#unitDatabase: UnitDatabase;
|
||||
#countryCodes: any;
|
||||
#orderByRole: boolean;
|
||||
#spawnOptions: UnitSpawnOptions = {
|
||||
roleType: "",
|
||||
name: "",
|
||||
latlng: new LatLng(0, 0),
|
||||
coalition: "blue",
|
||||
count: 1,
|
||||
country: "",
|
||||
loadout: undefined,
|
||||
airbase: undefined,
|
||||
liveryID: undefined,
|
||||
altitude: undefined
|
||||
};
|
||||
|
||||
/* Controls */
|
||||
#unitRoleTypeDropdown: Dropdown;
|
||||
#unitLabelDropdown: Dropdown;
|
||||
#unitCountDropdown: Dropdown;
|
||||
#unitLoadoutDropdown: Dropdown;
|
||||
#unitCountryDropdown: Dropdown;
|
||||
#unitLiveryDropdown: Dropdown;
|
||||
#unitSpawnAltitudeSlider: Slider;
|
||||
|
||||
/* HTML Elements */
|
||||
#deployUnitButtonEl: HTMLButtonElement;
|
||||
#unitLoadoutPreviewEl: HTMLDivElement;
|
||||
#unitImageEl: HTMLImageElement;
|
||||
#unitLoadoutListEl: HTMLDivElement;
|
||||
|
||||
constructor(ID: string, unitDatabase: UnitDatabase, orderByRole: boolean) {
|
||||
this.#container = document.getElementById(ID) as HTMLElement;
|
||||
this.#unitDatabase = unitDatabase;
|
||||
this.#orderByRole = orderByRole;
|
||||
|
||||
/* Create the dropdowns and the altitude slider */
|
||||
this.#unitRoleTypeDropdown = new Dropdown(null, (roleType: string) => this.#setUnitRoleType(roleType), undefined, "Unit type");
|
||||
this.#unitLabelDropdown = new Dropdown(null, (label: string) => this.#setUnitLabel(label), undefined, "Unit label");
|
||||
this.#unitLoadoutDropdown = new Dropdown(null, (loadout: string) => this.#setUnitLoadout(loadout), undefined, "Unit loadout");
|
||||
this.#unitCountDropdown = new Dropdown(null, (count: string) => this.#setUnitCount(count), undefined, "Unit count");
|
||||
this.#unitCountryDropdown = new Dropdown(null, () => { /* Custom button implementation */ }, undefined, "Unit country");
|
||||
this.#unitLiveryDropdown = new Dropdown(null, (livery: string) => this.#setUnitLivery(livery), undefined, "Unit livery");
|
||||
this.#unitSpawnAltitudeSlider = new Slider(null, 0, 1000, "ft", (value: number) => { this.#spawnOptions.altitude = ftToM(value); }, { title: "Spawn altitude" });
|
||||
|
||||
/* The unit label and unit count are in the same "row" for clarity and compactness */
|
||||
var unitLabelCountContainerEl = document.createElement("div");
|
||||
unitLabelCountContainerEl.classList.add("unit-label-count-container");
|
||||
var divider = document.createElement("div");
|
||||
divider.innerText = "x";
|
||||
unitLabelCountContainerEl.append(this.#unitLabelDropdown.getContainer(), divider, this.#unitCountDropdown.getContainer());
|
||||
|
||||
/* Create the unit image and loadout elements */
|
||||
this.#unitLoadoutPreviewEl = document.createElement("div");
|
||||
this.#unitLoadoutPreviewEl.classList.add("unit-loadout-preview");
|
||||
this.#unitImageEl = document.createElement("img");
|
||||
this.#unitImageEl.classList.add("unit-image", "hide");
|
||||
this.#unitLoadoutListEl = document.createElement("div");
|
||||
this.#unitLoadoutListEl.classList.add("unit-loadout-list");
|
||||
this.#unitLoadoutPreviewEl.append(this.#unitImageEl, this.#unitLoadoutListEl);
|
||||
|
||||
/* Create the divider and the advanced options collapsible div */
|
||||
var advancedOptionsDiv = document.createElement("div");
|
||||
advancedOptionsDiv.classList.add("contextmenu-advanced-options", "hide");
|
||||
var advancedOptionsToggle = document.createElement("div");
|
||||
advancedOptionsToggle.classList.add("contextmenu-advanced-options-toggle");
|
||||
var advancedOptionsText = document.createElement("div");
|
||||
advancedOptionsText.innerText = "Advanced options";
|
||||
var advancedOptionsHr = document.createElement("hr");
|
||||
advancedOptionsToggle.append(advancedOptionsText, advancedOptionsHr);
|
||||
advancedOptionsToggle.addEventListener("click", () => {
|
||||
advancedOptionsDiv.classList.toggle("hide");
|
||||
this.#container.dispatchEvent(new Event("resize"));
|
||||
});
|
||||
advancedOptionsDiv.append(this.#unitCountryDropdown.getContainer(), this.#unitLiveryDropdown.getContainer(),
|
||||
this.#unitLoadoutPreviewEl, this.#unitSpawnAltitudeSlider.getContainer() as HTMLElement);
|
||||
|
||||
/* Create the unit deploy button */
|
||||
this.#deployUnitButtonEl = document.createElement("button");
|
||||
this.#deployUnitButtonEl.classList.add("deploy-unit-button");
|
||||
this.#deployUnitButtonEl.disabled = true;
|
||||
this.#deployUnitButtonEl.innerText = "Deploy unit";
|
||||
this.#deployUnitButtonEl.setAttribute("data-coalition", "blue");
|
||||
this.#deployUnitButtonEl.addEventListener("click", () => {
|
||||
this.deployUnits(this.#spawnOptions, parseInt(this.#unitCountDropdown.getValue()));
|
||||
});
|
||||
|
||||
/* Assemble all components */
|
||||
this.#container.append(this.#unitRoleTypeDropdown.getContainer(), unitLabelCountContainerEl, this.#unitLoadoutDropdown.getContainer(),
|
||||
advancedOptionsToggle, advancedOptionsDiv, this.#deployUnitButtonEl);
|
||||
|
||||
/* Load the country codes from the public folder */
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', 'images/countries/codes.json', true);
|
||||
xhr.responseType = 'json';
|
||||
xhr.onload = () => {
|
||||
var status = xhr.status;
|
||||
if (status === 200)
|
||||
this.#countryCodes = xhr.response;
|
||||
else
|
||||
console.error(`Error retrieving country codes`)
|
||||
};
|
||||
xhr.send();
|
||||
|
||||
/* Event listeners */
|
||||
this.#container.addEventListener("unitRoleTypeChanged", () => {
|
||||
this.#deployUnitButtonEl.disabled = true;
|
||||
this.#unitLabelDropdown.reset();
|
||||
this.#unitLoadoutListEl.replaceChildren();
|
||||
this.#unitLoadoutDropdown.reset();
|
||||
this.#unitImageEl.classList.toggle("hide", true);
|
||||
this.#unitLiveryDropdown.reset();
|
||||
|
||||
if (this.#orderByRole)
|
||||
this.#unitLabelDropdown.setOptions(this.#unitDatabase.getByRole(this.#spawnOptions.roleType).map((blueprint) => { return blueprint.label }));
|
||||
else
|
||||
this.#unitLabelDropdown.setOptions(this.#unitDatabase.getByType(this.#spawnOptions.roleType).map((blueprint) => { return blueprint.label }));
|
||||
this.#container.dispatchEvent(new Event("resize"));
|
||||
|
||||
this.#spawnOptions.name = "";
|
||||
this.#spawnOptions.loadout = undefined;
|
||||
this.#spawnOptions.liveryID = undefined;
|
||||
|
||||
this.#computeSpawnPoints();
|
||||
})
|
||||
|
||||
this.#container.addEventListener("unitLabelChanged", () => {
|
||||
this.#deployUnitButtonEl.disabled = false;
|
||||
if (!this.#unitLoadoutDropdown.isHidden()) {
|
||||
this.#unitLoadoutDropdown.setOptions(this.#unitDatabase.getLoadoutNamesByRole(this.#spawnOptions.name, this.#spawnOptions.roleType));
|
||||
this.#unitLoadoutDropdown.selectValue(0);
|
||||
}
|
||||
|
||||
this.#unitImageEl.src = `images/units/${this.#unitDatabase.getByName(this.#spawnOptions.name)?.filename}`;
|
||||
this.#unitImageEl.classList.toggle("hide", false);
|
||||
|
||||
this.#setUnitLiveryOptions();
|
||||
|
||||
this.#container.dispatchEvent(new Event("resize"));
|
||||
this.#computeSpawnPoints();
|
||||
})
|
||||
|
||||
this.#container.addEventListener("unitLoadoutChanged", () => {
|
||||
var items = this.#spawnOptions.loadout?.items.map((item: any) => { return `${item.quantity}x ${item.name}`; });
|
||||
if (items != undefined) {
|
||||
items.length == 0 ? items.push("Empty loadout") : "";
|
||||
this.#unitLoadoutListEl.replaceChildren(
|
||||
...items.map((item: any) => {
|
||||
var div = document.createElement('div');
|
||||
div.innerText = item;
|
||||
return div;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
this.#container.dispatchEvent(new Event("resize"));
|
||||
})
|
||||
|
||||
this.#container.addEventListener("unitCountChanged", () => {
|
||||
this.#computeSpawnPoints();
|
||||
})
|
||||
|
||||
this.#container.addEventListener("unitCountryChanged", () => {
|
||||
this.#setUnitLiveryOptions();
|
||||
})
|
||||
|
||||
this.#container.addEventListener("unitLiveryChanged", () => {
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
getContainer() {
|
||||
return this.#container;
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.#deployUnitButtonEl.disabled = true;
|
||||
this.#unitRoleTypeDropdown.reset();
|
||||
this.#unitLabelDropdown.reset();
|
||||
this.#unitLiveryDropdown.reset();
|
||||
if (this.#orderByRole)
|
||||
this.#unitRoleTypeDropdown.setOptions(this.#unitDatabase.getRoles());
|
||||
else
|
||||
this.#unitRoleTypeDropdown.setOptions(this.#unitDatabase.getTypes());
|
||||
|
||||
this.#unitLoadoutListEl.replaceChildren();
|
||||
this.#unitLoadoutDropdown.reset();
|
||||
this.#unitImageEl.classList.toggle("hide", true);
|
||||
|
||||
this.setCountries();
|
||||
this.#container.dispatchEvent(new Event("resize"));
|
||||
}
|
||||
|
||||
setCountries() {
|
||||
var coalitions = getMissionHandler().getCoalitions();
|
||||
var countries = Object.values(coalitions[getActiveCoalition() as keyof typeof coalitions]);
|
||||
this.#unitCountryDropdown.setOptionsElements(this.#createCountryButtons(this.#unitCountryDropdown, countries, (country: string) => { this.#setUnitCountry(country) }));
|
||||
|
||||
if (countries.length > 0 && !countries.includes(this.#spawnOptions.country)) {
|
||||
this.#unitCountryDropdown.forceValue(this.#getFormattedCountry(countries[0]));
|
||||
this.#setUnitCountry(countries[0]);
|
||||
}
|
||||
}
|
||||
|
||||
refreshOptions() {
|
||||
//if (!this.#unitDatabase.getTypes().includes(this.#unitTypeDropdown.getValue()))
|
||||
// this.reset();
|
||||
//if (!this.#unitDatabase.getByType(this.#unitTypeDropdown.getValue()).map((blueprint) => { return blueprint.label }).includes(this.#unitLabelDropdown.getValue()))
|
||||
// this.resetUnitLabel();
|
||||
}
|
||||
|
||||
setAirbase(airbase: Airbase | undefined) {
|
||||
this.#spawnOptions.airbase = airbase;
|
||||
}
|
||||
|
||||
setLatLng(latlng: LatLng) {
|
||||
this.#spawnOptions.latlng = latlng;
|
||||
}
|
||||
|
||||
setMaxUnitCount(maxUnitCount: number) {
|
||||
/* Create the unit count options */
|
||||
var countOptions: string[] = [];
|
||||
for (let i = 1; i <= maxUnitCount; i++)
|
||||
countOptions.push(i.toString());
|
||||
this.#unitCountDropdown.setOptions(countOptions);
|
||||
this.#unitCountDropdown.selectValue(0);
|
||||
}
|
||||
|
||||
getRoleTypeDrodown() {
|
||||
return this.#unitRoleTypeDropdown;
|
||||
}
|
||||
|
||||
getLabelDropdown() {
|
||||
return this.#unitLabelDropdown;
|
||||
}
|
||||
|
||||
getCountDropdown() {
|
||||
return this.#unitCountDropdown;
|
||||
}
|
||||
|
||||
getLoadoutDropdown() {
|
||||
return this.#unitLoadoutDropdown;
|
||||
}
|
||||
|
||||
getCountryDropdown() {
|
||||
return this.#unitCountDropdown;
|
||||
}
|
||||
|
||||
getLiveryDropdown() {
|
||||
return this.#unitLiveryDropdown;
|
||||
}
|
||||
|
||||
getLoadoutPreview() {
|
||||
return this.#unitLoadoutPreviewEl;
|
||||
}
|
||||
|
||||
getAltitudeSlider() {
|
||||
return this.#unitSpawnAltitudeSlider;
|
||||
}
|
||||
|
||||
#setUnitRoleType(roleType: string) {
|
||||
this.#spawnOptions.roleType = roleType;
|
||||
this.#container.dispatchEvent(new Event("unitRoleTypeChanged"));
|
||||
}
|
||||
|
||||
#setUnitLabel(label: string) {
|
||||
var name = this.#unitDatabase.getByLabel(label)?.name || null;
|
||||
if (name != null)
|
||||
this.#spawnOptions.name = name;
|
||||
this.#container.dispatchEvent(new Event("unitLabelChanged"));
|
||||
}
|
||||
|
||||
#setUnitLoadout(loadoutName: string) {
|
||||
var loadout = this.#unitDatabase.getLoadoutByName(this.#spawnOptions.name, loadoutName);
|
||||
if (loadout)
|
||||
this.#spawnOptions.loadout = loadout;
|
||||
this.#container.dispatchEvent(new Event("unitLoadoutChanged"));
|
||||
}
|
||||
|
||||
#setUnitCount(count: string) {
|
||||
this.#spawnOptions.count = parseInt(count);
|
||||
this.#container.dispatchEvent(new Event("unitCountChanged"));
|
||||
}
|
||||
|
||||
#setUnitCountry(country: string) {
|
||||
this.#spawnOptions.country = country;
|
||||
this.#container.dispatchEvent(new Event("unitCountryChanged"));
|
||||
}
|
||||
|
||||
#setUnitLivery(liveryName: string) {
|
||||
var liveries = this.#unitDatabase.getByName(this.#spawnOptions.name)?.liveries;
|
||||
if (liveryName === "Default") {
|
||||
this.#spawnOptions.liveryID = "";
|
||||
}
|
||||
else {
|
||||
if (liveries !== undefined) {
|
||||
for (let liveryID in liveries)
|
||||
if (liveries[liveryID].name === liveryName)
|
||||
this.#spawnOptions.liveryID = liveryID;
|
||||
}
|
||||
}
|
||||
this.#container.dispatchEvent(new Event("unitLiveryChanged"));
|
||||
}
|
||||
|
||||
#setUnitLiveryOptions() {
|
||||
if (this.#spawnOptions.name !== "" && this.#spawnOptions.country !== "") {
|
||||
var liveries = this.#unitDatabase.getLiveryNamesByName(this.#spawnOptions.name);
|
||||
var countryLiveries: string[] = ["Default"];
|
||||
liveries.forEach((livery: any) => {
|
||||
var nationLiveryCodes = this.#countryCodes[this.#spawnOptions.country].liveryCodes;
|
||||
if (livery.countries === "All" || livery.countries.some((country: string) => { return nationLiveryCodes.includes(country) }))
|
||||
countryLiveries.push(livery.name);
|
||||
});
|
||||
this.#unitLiveryDropdown.setOptions(countryLiveries);
|
||||
this.#unitLiveryDropdown.selectValue(0);
|
||||
}
|
||||
}
|
||||
|
||||
deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number) {
|
||||
/* Virtual function must be overloaded by inheriting classes */
|
||||
}
|
||||
|
||||
#createCountryButtons(parent: Dropdown, countries: string[], callback: CallableFunction) {
|
||||
return Object.values(countries).map((country: string) => {
|
||||
var el = document.createElement("div");
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.classList.add("country-dropdown-element");
|
||||
el.appendChild(button);
|
||||
button.addEventListener("click", () => {
|
||||
callback(country);
|
||||
parent.forceValue(this.#getFormattedCountry(country));
|
||||
parent.close();
|
||||
});
|
||||
if (this.#countryCodes[country] !== undefined) {
|
||||
var code = this.#countryCodes[country].flagCode;
|
||||
if (code !== undefined) {
|
||||
var img = document.createElement("img");
|
||||
img.src = `images/countries/${code.toLowerCase()}.svg`;
|
||||
button.appendChild(img);
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log("Unknown country " + country);
|
||||
}
|
||||
var text = document.createElement("div");
|
||||
text.innerText = this.#getFormattedCountry(country);
|
||||
button.appendChild(text);
|
||||
return el;
|
||||
});
|
||||
}
|
||||
|
||||
#getFormattedCountry(country: string) {
|
||||
var formattedCountry = "";
|
||||
if (this.#countryCodes[country] !== undefined && this.#countryCodes[country].displayName !== undefined)
|
||||
formattedCountry = this.#countryCodes[country].displayName;
|
||||
else
|
||||
formattedCountry = country.charAt(0).toUpperCase() + country.slice(1).toLowerCase();
|
||||
return formattedCountry;
|
||||
}
|
||||
|
||||
#computeSpawnPoints() {
|
||||
if (getMissionHandler() && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER) {
|
||||
var unitCount = parseInt(this.#unitCountDropdown.getValue());
|
||||
var unitSpawnPoints = unitCount * this.#unitDatabase.getSpawnPointsByLabel(this.#unitLabelDropdown.getValue());
|
||||
this.#deployUnitButtonEl.dataset.points = `${unitSpawnPoints}`;
|
||||
this.#deployUnitButtonEl.disabled = unitSpawnPoints >= getMissionHandler().getAvailableSpawnPoints();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class AircraftSpawnMenu extends UnitSpawnMenu {
|
||||
/**
|
||||
*
|
||||
* @param ID - the ID of the HTML element which will contain the context menu
|
||||
*/
|
||||
constructor(ID: string){
|
||||
super(ID, aircraftDatabase, true);
|
||||
this.setMaxUnitCount(4);
|
||||
this.getAltitudeSlider().setMinMax(0, 50000);
|
||||
this.getAltitudeSlider().setIncrement(500);
|
||||
this.getAltitudeSlider().setValue(20000);
|
||||
}
|
||||
|
||||
deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number) {
|
||||
spawnOptions.coalition = getActiveCoalition();
|
||||
if (spawnOptions) {
|
||||
var unitTable: UnitSpawnTable = {
|
||||
unitType: spawnOptions.name,
|
||||
location: spawnOptions.latlng,
|
||||
altitude: spawnOptions.altitude? spawnOptions.altitude: 0,
|
||||
loadout: spawnOptions.loadout? spawnOptions.loadout.name: "",
|
||||
liveryID: spawnOptions.liveryID? spawnOptions.liveryID: ""
|
||||
};
|
||||
var units = [];
|
||||
for (let i = 1; i < unitsCount + 1; i++) {
|
||||
units.push(unitTable);
|
||||
}
|
||||
|
||||
getUnitsManager().spawnUnits("Aircraft", units, getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => {
|
||||
if (res.commandHash !== undefined)
|
||||
getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getActiveCoalition(), res.commandHash);
|
||||
});
|
||||
|
||||
this.getContainer().dispatchEvent(new Event("hide"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class HelicopterSpawnMenu extends UnitSpawnMenu {
|
||||
/**
|
||||
*
|
||||
* @param ID - the ID of the HTML element which will contain the context menu
|
||||
*/
|
||||
constructor(ID: string){
|
||||
super(ID, helicopterDatabase, true);
|
||||
this.setMaxUnitCount(4);
|
||||
this.getAltitudeSlider().setMinMax(0, 10000);
|
||||
this.getAltitudeSlider().setIncrement(100);
|
||||
this.getAltitudeSlider().setValue(5000);
|
||||
}
|
||||
|
||||
deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number) {
|
||||
spawnOptions.coalition = getActiveCoalition();
|
||||
if (spawnOptions) {
|
||||
var unitTable: UnitSpawnTable = {
|
||||
unitType: spawnOptions.name,
|
||||
location: spawnOptions.latlng,
|
||||
altitude: spawnOptions.altitude? spawnOptions.altitude: 0,
|
||||
loadout: spawnOptions.loadout? spawnOptions.loadout.name: "",
|
||||
liveryID: spawnOptions.liveryID? spawnOptions.liveryID: ""
|
||||
};
|
||||
var units = [];
|
||||
for (let i = 1; i < unitsCount + 1; i++) {
|
||||
units.push(unitTable);
|
||||
}
|
||||
|
||||
getUnitsManager().spawnUnits("Helicopter", units, getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => {
|
||||
if (res.commandHash !== undefined)
|
||||
getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getActiveCoalition(), res.commandHash);
|
||||
});
|
||||
|
||||
this.getContainer().dispatchEvent(new Event("hide"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class GroundUnitSpawnMenu extends UnitSpawnMenu {
|
||||
/**
|
||||
*
|
||||
* @param ID - the ID of the HTML element which will contain the context menu
|
||||
*/
|
||||
constructor(ID: string){
|
||||
super(ID, groundUnitDatabase, false);
|
||||
this.setMaxUnitCount(20);
|
||||
this.getAltitudeSlider().hide();
|
||||
this.getLoadoutDropdown().hide();
|
||||
this.getLoadoutPreview().classList.add("hide");
|
||||
}
|
||||
|
||||
deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number) {
|
||||
spawnOptions.coalition = getActiveCoalition();
|
||||
if (spawnOptions) {
|
||||
var unitTable: UnitSpawnTable = {
|
||||
unitType: spawnOptions.name,
|
||||
location: spawnOptions.latlng,
|
||||
liveryID: spawnOptions.liveryID? spawnOptions.liveryID: ""
|
||||
};
|
||||
|
||||
var units = [];
|
||||
for (let i = 0; i < unitsCount; i++) {
|
||||
units.push(JSON.parse(JSON.stringify(unitTable)));
|
||||
unitTable.location.lat += i > 0? 0.0001: 0;
|
||||
}
|
||||
|
||||
getUnitsManager().spawnUnits("GroundUnit", units, getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => {
|
||||
if (res.commandHash !== undefined)
|
||||
getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getActiveCoalition(), res.commandHash);
|
||||
});
|
||||
|
||||
this.getContainer().dispatchEvent(new Event("hide"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class NavyUnitSpawnMenu extends UnitSpawnMenu {
|
||||
/**
|
||||
*
|
||||
* @param ID - the ID of the HTML element which will contain the context menu
|
||||
*/
|
||||
constructor(ID: string){
|
||||
super(ID, navyUnitDatabase, false);
|
||||
this.setMaxUnitCount(4);
|
||||
this.getAltitudeSlider().hide();
|
||||
this.getLoadoutDropdown().hide();
|
||||
this.getLoadoutPreview().classList.add("hide");
|
||||
}
|
||||
|
||||
deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number) {
|
||||
spawnOptions.coalition = getActiveCoalition();
|
||||
if (spawnOptions) {
|
||||
var unitTable: UnitSpawnTable = {
|
||||
unitType: spawnOptions.name,
|
||||
location: spawnOptions.latlng,
|
||||
liveryID: spawnOptions.liveryID? spawnOptions.liveryID: ""
|
||||
};
|
||||
|
||||
var units = [];
|
||||
for (let i = 0; i < unitsCount; i++) {
|
||||
units.push(JSON.parse(JSON.stringify(unitTable)));
|
||||
unitTable.location.lat += i > 0? 0.0001: 0;
|
||||
}
|
||||
|
||||
getUnitsManager().spawnUnits("NavyUnit", units, getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => {
|
||||
if (res.commandHash !== undefined)
|
||||
getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getActiveCoalition(), res.commandHash);
|
||||
});
|
||||
|
||||
this.getContainer().dispatchEvent(new Event("hide"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,36 +2,37 @@ import { Map } from "./map/map"
|
||||
import { UnitsManager } from "./unit/unitsmanager";
|
||||
import { UnitInfoPanel } from "./panels/unitinfopanel";
|
||||
import { ConnectionStatusPanel } from "./panels/connectionstatuspanel";
|
||||
import { MissionHandler } from "./mission/missionhandler";
|
||||
import { MissionManager } from "./mission/missionmanager";
|
||||
import { UnitControlPanel } from "./panels/unitcontrolpanel";
|
||||
import { MouseInfoPanel } from "./panels/mouseinfopanel";
|
||||
import { AIC } from "./aic/aic";
|
||||
import { ATC } from "./atc/atc";
|
||||
import { FeatureSwitches } from "./features/featureswitches";
|
||||
import { LogPanel } from "./panels/logpanel";
|
||||
import { getConfig, getPaused, setAddress, setCredentials, setPaused, startUpdate, toggleDemoEnabled } from "./server/server";
|
||||
import { UnitDataTable } from "./atc/unitdatatable";
|
||||
import { Popup } from "./popups/popup";
|
||||
import { Dropdown } from "./controls/dropdown";
|
||||
import { HotgroupPanel } from "./panels/hotgrouppanel";
|
||||
import { SVGInjector } from "@tanem/svg-injector";
|
||||
import { BLUE_COMMANDER, GAME_MASTER, RED_COMMANDER } from "./constants/constants";
|
||||
import { ServerStatusPanel } from "./panels/serverstatuspanel";
|
||||
import { WeaponsManager } from "./weapon/weaponsmanager";
|
||||
import { ConfigParameters } from "./@types/dom";
|
||||
import { IndexApp } from "./indexapp";
|
||||
import { ShortcutKeyboard } from "./shortcut/shortcut";
|
||||
import { FeatureSwitches } from "./features/featureswitches";
|
||||
import { PrimaryToolbar } from "./toolbars/primarytoolbar";
|
||||
import { CommandModeToolbar } from "./toolbars/commandmodetoolbar";
|
||||
import { OlympusApp } from "./olympusapp";
|
||||
import { ShortcutKeyboard } from "./shortcut/shortcut";
|
||||
|
||||
/* Global data */
|
||||
var activeCoalition: string = "blue";
|
||||
|
||||
/* Main leaflet map, extended by custom methods */
|
||||
var map: Map;
|
||||
|
||||
/* Managers */
|
||||
var unitsManager: UnitsManager;
|
||||
var weaponsManager: WeaponsManager;
|
||||
var missionHandler: MissionHandler;
|
||||
|
||||
var aic: AIC;
|
||||
var atc: ATC;
|
||||
var missionManager: MissionManager;
|
||||
|
||||
/* UI Panels */
|
||||
var unitInfoPanel: UnitInfoPanel;
|
||||
var connectionStatusPanel: ConnectionStatusPanel;
|
||||
var serverStatusPanel: ServerStatusPanel;
|
||||
@ -40,22 +41,21 @@ var mouseInfoPanel: MouseInfoPanel;
|
||||
var logPanel: LogPanel;
|
||||
var hotgroupPanel: HotgroupPanel;
|
||||
|
||||
/* UI Toolbars */
|
||||
var primaryToolbar: PrimaryToolbar;
|
||||
var commandModeToolbar: CommandModeToolbar;
|
||||
|
||||
/* Popups */
|
||||
var infoPopup: Popup;
|
||||
|
||||
var activeCoalition: string = "blue";
|
||||
|
||||
var unitDataTable: UnitDataTable;
|
||||
|
||||
var featureSwitches;
|
||||
|
||||
function setup() {
|
||||
featureSwitches = new FeatureSwitches();
|
||||
|
||||
/* Initialize base functionalitites */
|
||||
map = new Map('map-container');
|
||||
|
||||
unitsManager = new UnitsManager();
|
||||
weaponsManager = new WeaponsManager();
|
||||
map = new Map('map-container');
|
||||
missionHandler = new MissionHandler();
|
||||
missionManager = new MissionManager();
|
||||
|
||||
/* Panels */
|
||||
unitInfoPanel = new UnitInfoPanel("unit-info-panel");
|
||||
@ -66,30 +66,15 @@ function setup() {
|
||||
hotgroupPanel = new HotgroupPanel("hotgroup-panel");
|
||||
logPanel = new LogPanel("log-panel");
|
||||
|
||||
/* Toolbars */
|
||||
primaryToolbar = new PrimaryToolbar("primary-toolbar");
|
||||
commandModeToolbar = new CommandModeToolbar("command-mode-toolbar");
|
||||
|
||||
/* Popups */
|
||||
infoPopup = new Popup("info-popup");
|
||||
|
||||
/* Controls */
|
||||
new Dropdown("app-icon", () => { });
|
||||
|
||||
/* Unit data table */
|
||||
unitDataTable = new UnitDataTable("unit-data-table");
|
||||
|
||||
/* AIC */
|
||||
let aicFeatureSwitch = featureSwitches.getSwitch("aic");
|
||||
if (aicFeatureSwitch?.isEnabled()) {
|
||||
aic = new AIC();
|
||||
}
|
||||
|
||||
/* ATC */
|
||||
let atcFeatureSwitch = featureSwitches.getSwitch("atc");
|
||||
if (atcFeatureSwitch?.isEnabled()) {
|
||||
atc = new ATC();
|
||||
atc.startUpdates();
|
||||
}
|
||||
|
||||
/* Load the config file */
|
||||
getConfig(readConfig);
|
||||
/* Load the config file from the app server*/
|
||||
getConfig((config: ConfigParameters) => readConfig(config));
|
||||
|
||||
/*
|
||||
This is done like this for now as a way to make it work in the new and old world.
|
||||
@ -97,9 +82,8 @@ function setup() {
|
||||
*/
|
||||
|
||||
const indexApp = new IndexApp({
|
||||
"featureSwitches": featureSwitches,
|
||||
"featureSwitches": new FeatureSwitches(),
|
||||
"map": map,
|
||||
"missionHandler": missionHandler,
|
||||
"panels": {
|
||||
"connectionStatus": connectionStatusPanel,
|
||||
"hotgroup": hotgroupPanel,
|
||||
@ -110,7 +94,6 @@ function setup() {
|
||||
"unitControl": unitControlPanel,
|
||||
"unitInfo": unitInfoPanel
|
||||
},
|
||||
"unitDataTable": unitDataTable,
|
||||
"unitsManager": unitsManager
|
||||
});
|
||||
|
||||
@ -121,10 +104,14 @@ function setup() {
|
||||
|
||||
}
|
||||
|
||||
function readConfig(config: any) {
|
||||
if (config && config["address"] != undefined && config["port"] != undefined) {
|
||||
const address = config["address"];
|
||||
const port = config["port"];
|
||||
/** Loads the configuration parameters
|
||||
*
|
||||
* @param config ConfigParameters, defines the address and port of the Olympus REST server
|
||||
*/
|
||||
function readConfig(config: ConfigParameters) {
|
||||
if (config && config.address != undefined && config.port != undefined) {
|
||||
const address = config.address;
|
||||
const port = config.port;
|
||||
if (typeof address === 'string' && typeof port == 'number')
|
||||
setAddress(address == "*" ? window.location.hostname : address, <number>port);
|
||||
}
|
||||
@ -171,13 +158,6 @@ function setupEvents( indexApp:OlympusApp ) {
|
||||
"code": "KeyT"
|
||||
})
|
||||
)
|
||||
.add( "toggleUnitDataTable", new ShortcutKeyboard({
|
||||
"callback": () => {
|
||||
unitDataTable.toggle();
|
||||
},
|
||||
"code": "Quote"
|
||||
})
|
||||
)
|
||||
.add( "togglePause", new ShortcutKeyboard({
|
||||
"altKey": false,
|
||||
"callback": () => {
|
||||
@ -225,20 +205,18 @@ function setupEvents( indexApp:OlympusApp ) {
|
||||
|
||||
|
||||
|
||||
// TODO: move from here in dedicated class
|
||||
document.addEventListener("closeDialog", (ev: CustomEventInit) => {
|
||||
ev.detail._element.closest(".ol-dialog").classList.add("hide");
|
||||
});
|
||||
|
||||
document.addEventListener("toggleElements", (ev: CustomEventInit) => {
|
||||
document.querySelectorAll(ev.detail.selector).forEach(el => {
|
||||
el.classList.toggle("hide");
|
||||
})
|
||||
});
|
||||
|
||||
/* Try and connect with the Olympus REST server */
|
||||
document.addEventListener("tryConnection", () => {
|
||||
const form = document.querySelector("#splash-content")?.querySelector("#authentication-form");
|
||||
const username = (<HTMLInputElement>(form?.querySelector("#username"))).value;
|
||||
const password = (<HTMLInputElement>(form?.querySelector("#password"))).value;
|
||||
const username = (form?.querySelector("#username") as HTMLInputElement).value;
|
||||
const password = (form?.querySelector("#password") as HTMLInputElement).value;
|
||||
|
||||
/* Update the user credentials */
|
||||
setCredentials(username, password);
|
||||
|
||||
/* Start periodically requesting updates */
|
||||
@ -247,10 +225,12 @@ function setupEvents( indexApp:OlympusApp ) {
|
||||
setLoginStatus("connecting");
|
||||
})
|
||||
|
||||
/* Reload the page, used to mimic a restart of the app */
|
||||
document.addEventListener("reloadPage", () => {
|
||||
location.reload();
|
||||
})
|
||||
|
||||
/* Inject the svgs with the corresponding svg code. This allows to dynamically manipulate the svg, like changing colors */
|
||||
document.querySelectorAll("[inject-svg]").forEach((el: Element) => {
|
||||
var img = el as HTMLImageElement;
|
||||
var isLoaded = img.complete;
|
||||
@ -263,14 +243,11 @@ function setupEvents( indexApp:OlympusApp ) {
|
||||
})
|
||||
}
|
||||
|
||||
/* Getters */
|
||||
export function getMap() {
|
||||
return map;
|
||||
}
|
||||
|
||||
export function getUnitDataTable() {
|
||||
return unitDataTable;
|
||||
}
|
||||
|
||||
export function getUnitsManager() {
|
||||
return unitsManager;
|
||||
}
|
||||
@ -279,9 +256,8 @@ export function getWeaponsManager() {
|
||||
return weaponsManager;
|
||||
}
|
||||
|
||||
|
||||
export function getMissionHandler() {
|
||||
return missionHandler;
|
||||
return missionManager;
|
||||
}
|
||||
|
||||
export function getUnitInfoPanel() {
|
||||
@ -312,11 +288,23 @@ export function getHotgroupPanel() {
|
||||
return hotgroupPanel;
|
||||
}
|
||||
|
||||
export function getInfoPopup() {
|
||||
return infoPopup;
|
||||
}
|
||||
|
||||
/** Set the active coalition, i.e. the currently controlled coalition. A game master can change the active coalition, while a commander is bound to his/her coalition
|
||||
*
|
||||
* @param newActiveCoalition
|
||||
*/
|
||||
export function setActiveCoalition(newActiveCoalition: string) {
|
||||
if (getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER)
|
||||
activeCoalition = newActiveCoalition;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns The active coalition
|
||||
*/
|
||||
export function getActiveCoalition() {
|
||||
if (getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER)
|
||||
return activeCoalition;
|
||||
@ -330,15 +318,15 @@ export function getActiveCoalition() {
|
||||
}
|
||||
}
|
||||
|
||||
/** Set a message in the login splash screen
|
||||
*
|
||||
* @param status The message to show in the login splash screen
|
||||
*/
|
||||
export function setLoginStatus(status: string) {
|
||||
const el = document.querySelector("#login-status") as HTMLElement;
|
||||
if (el)
|
||||
el.dataset["status"] = status;
|
||||
}
|
||||
|
||||
export function getInfoPopup() {
|
||||
return infoPopup;
|
||||
}
|
||||
|
||||
window.onload = setup;
|
||||
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { FeatureSwitches } from "./features/featureswitches";
|
||||
import { MissionHandler } from "./mission/missionhandler";
|
||||
import { IOlympusApp, OlympusApp } from "./olympusapp";
|
||||
import { ConnectionStatusPanel } from "./panels/connectionstatuspanel";
|
||||
import { HotgroupPanel } from "./panels/hotgrouppanel";
|
||||
@ -15,7 +14,6 @@ import { UnitsManager } from "./unit/unitsmanager";
|
||||
|
||||
export interface IIndexApp extends IOlympusApp {
|
||||
"featureSwitches": FeatureSwitches,
|
||||
"missionHandler": MissionHandler,
|
||||
"panels": IIndexAppPanels,
|
||||
"unitsManager": UnitsManager
|
||||
}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { DomUtil, LatLng, LatLngExpression, Map, Point, Polygon, PolylineOptions } from "leaflet";
|
||||
import { getMap, getMissionHandler, getUnitsManager } from "..";
|
||||
import { getMap, getMissionHandler, getUnitsManager } from "../..";
|
||||
import { CoalitionAreaHandle } from "./coalitionareahandle";
|
||||
import { CoalitionAreaMiddleHandle } from "./coalitionareamiddlehandle";
|
||||
import { BLUE_COMMANDER, RED_COMMANDER } from "../constants/constants";
|
||||
import { BLUE_COMMANDER, RED_COMMANDER } from "../../constants/constants";
|
||||
|
||||
export class CoalitionArea extends Polygon {
|
||||
#coalition: string = "blue";
|
||||
@ -1,5 +1,5 @@
|
||||
import { DivIcon, LatLng } from "leaflet";
|
||||
import { CustomMarker } from "./custommarker";
|
||||
import { CustomMarker } from "../markers/custommarker";
|
||||
|
||||
export class CoalitionAreaHandle extends CustomMarker {
|
||||
constructor(latlng: LatLng) {
|
||||
@ -1,5 +1,5 @@
|
||||
import { DivIcon, LatLng } from "leaflet";
|
||||
import { CustomMarker } from "./custommarker";
|
||||
import { CustomMarker } from "../markers/custommarker";
|
||||
|
||||
export class CoalitionAreaMiddleHandle extends CustomMarker {
|
||||
constructor(latlng: LatLng) {
|
||||
@ -1,5 +1,5 @@
|
||||
import { DivIcon, LatLng } from "leaflet";
|
||||
import { CustomMarker } from "./custommarker";
|
||||
import { CustomMarker } from "../markers/custommarker";
|
||||
|
||||
export class DrawingCursor extends CustomMarker {
|
||||
constructor() {
|
||||
@ -1,22 +1,23 @@
|
||||
import * as L from "leaflet"
|
||||
import { getInfoPopup, getMissionHandler, getUnitsManager } from "..";
|
||||
import { BoxSelect } from "./boxselect";
|
||||
import { MapContextMenu } from "../controls/mapcontextmenu";
|
||||
import { UnitContextMenu } from "../controls/unitcontextmenu";
|
||||
import { AirbaseContextMenu } from "../controls/airbasecontextmenu";
|
||||
import { MapContextMenu } from "../contextmenus/mapcontextmenu";
|
||||
import { UnitContextMenu } from "../contextmenus/unitcontextmenu";
|
||||
import { AirbaseContextMenu } from "../contextmenus/airbasecontextmenu";
|
||||
import { Dropdown } from "../controls/dropdown";
|
||||
import { Airbase } from "../mission/airbase";
|
||||
import { Unit } from "../unit/unit";
|
||||
import { bearing, createCheckboxOption } from "../other/utils";
|
||||
import { DestinationPreviewMarker } from "./destinationpreviewmarker";
|
||||
import { TemporaryUnitMarker } from "./temporaryunitmarker";
|
||||
import { DestinationPreviewMarker } from "./markers/destinationpreviewmarker";
|
||||
import { TemporaryUnitMarker } from "./markers/temporaryunitmarker";
|
||||
import { ClickableMiniMap } from "./clickableminimap";
|
||||
import { SVGInjector } from '@tanem/svg-injector'
|
||||
import { layers as mapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, visibilityControls, visibilityControlsTooltips, MOVE_UNIT, SHOW_CONTACT_LINES, HIDE_GROUP_MEMBERS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, visibilityControlsTypes, SHOW_UNIT_LABELS, SHOW_CONTROL_TIPS } from "../constants/constants";
|
||||
import { TargetMarker } from "./targetmarker";
|
||||
import { CoalitionArea } from "./coalitionarea";
|
||||
import { CoalitionAreaContextMenu } from "../controls/coalitionareacontextmenu";
|
||||
import { DrawingCursor } from "./drawingcursor";
|
||||
import { TargetMarker } from "./markers/targetmarker";
|
||||
import { CoalitionArea } from "./coalitionarea/coalitionarea";
|
||||
import { CoalitionAreaContextMenu } from "../contextmenus/coalitionareacontextmenu";
|
||||
import { DrawingCursor } from "./coalitionarea/drawingcursor";
|
||||
import { AirbaseSpawnContextMenu } from "../contextmenus/airbasespawnmenu";
|
||||
import { OlympusApp } from "../olympusapp";
|
||||
|
||||
L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect);
|
||||
@ -61,16 +62,22 @@ export class Map extends L.Map {
|
||||
#mapContextMenu: MapContextMenu = new MapContextMenu("map-contextmenu");
|
||||
#unitContextMenu: UnitContextMenu = new UnitContextMenu("unit-contextmenu");
|
||||
#airbaseContextMenu: AirbaseContextMenu = new AirbaseContextMenu("airbase-contextmenu");
|
||||
#airbaseSpawnMenu: AirbaseSpawnContextMenu = new AirbaseSpawnContextMenu("airbase-spawn-contextmenu");
|
||||
#coalitionAreaContextMenu: CoalitionAreaContextMenu = new CoalitionAreaContextMenu("coalition-area-contextmenu");
|
||||
|
||||
#mapSourceDropdown: Dropdown;
|
||||
#mapVisibilityOptionsDropdown: Dropdown;
|
||||
#optionButtons: { [key: string]: HTMLButtonElement[] } = {}
|
||||
#visibilityOptions: { [key: string]: boolean } = {}
|
||||
#hiddenTypes: string[] = [];
|
||||
|
||||
#olympusApp!:OlympusApp;
|
||||
|
||||
constructor(ID: string) {
|
||||
/**
|
||||
*
|
||||
* @param ID - the ID of the HTML element which will contain the context menu
|
||||
*/
|
||||
constructor(ID: string){
|
||||
/* Init the leaflet map */
|
||||
//@ts-ignore Needed because the boxSelect option is non-standard
|
||||
super(ID, { zoomSnap: 0, zoomDelta: 0.25, preferCanvas: true, doubleClickZoom: false, zoomControl: false, boxZoom: false, boxSelect: true, zoomAnimation: true, maxBoundsViscosity: 1.0, minZoom: 7, keyboard: true, keyboardPanDelta: 0 });
|
||||
@ -118,14 +125,14 @@ export class Map extends L.Map {
|
||||
document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => {
|
||||
const el = ev.detail._element;
|
||||
el?.classList.toggle("off");
|
||||
getUnitsManager().setHiddenType(ev.detail.coalition, !el?.classList.contains("off"));
|
||||
this.setHiddenType(ev.detail.coalition, !el?.classList.contains("off"));
|
||||
Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility());
|
||||
});
|
||||
|
||||
document.addEventListener("toggleMarkerVisibility", (ev: CustomEventInit) => {
|
||||
const el = ev.detail._element;
|
||||
el?.classList.toggle("off");
|
||||
ev.detail.types.forEach((type: string) => getUnitsManager().setHiddenType(type, !el?.classList.contains("off")));
|
||||
ev.detail.types.forEach((type: string) => this.setHiddenType(type, !el?.classList.contains("off")));
|
||||
Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility());
|
||||
|
||||
if (ev.detail.types.includes("airbase")) {
|
||||
@ -245,11 +252,27 @@ export class Map extends L.Map {
|
||||
this.removeLayer(coalitionArea);
|
||||
}
|
||||
|
||||
setHiddenType(key: string, value: boolean) {
|
||||
if (value) {
|
||||
if (this.#hiddenTypes.includes(key))
|
||||
delete this.#hiddenTypes[this.#hiddenTypes.indexOf(key)];
|
||||
}
|
||||
else {
|
||||
this.#hiddenTypes.push(key);
|
||||
}
|
||||
Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility());
|
||||
}
|
||||
|
||||
getHiddenTypes() {
|
||||
return this.#hiddenTypes;
|
||||
}
|
||||
|
||||
/* Context Menus */
|
||||
hideAllContextMenus() {
|
||||
this.hideMapContextMenu();
|
||||
this.hideUnitContextMenu();
|
||||
this.hideAirbaseContextMenu();
|
||||
this.hideAirbaseSpawnMenu();
|
||||
this.hideCoalitionAreaContextMenu();
|
||||
}
|
||||
|
||||
@ -295,6 +318,20 @@ export class Map extends L.Map {
|
||||
this.#airbaseContextMenu.hide();
|
||||
}
|
||||
|
||||
showAirbaseSpawnMenu(x: number, y: number, latlng: L.LatLng, airbase: Airbase) {
|
||||
this.hideAllContextMenus();
|
||||
this.#airbaseSpawnMenu.show(x, y);
|
||||
this.#airbaseSpawnMenu.setAirbase(airbase);
|
||||
}
|
||||
|
||||
getAirbaseSpawnMenu() {
|
||||
return this.#airbaseSpawnMenu;
|
||||
}
|
||||
|
||||
hideAirbaseSpawnMenu() {
|
||||
this.#airbaseSpawnMenu.hide();
|
||||
}
|
||||
|
||||
showCoalitionAreaContextMenu(x: number, y: number, latlng: L.LatLng, coalitionArea: CoalitionArea) {
|
||||
this.hideAllContextMenus();
|
||||
this.#coalitionAreaContextMenu.show(x, y, latlng);
|
||||
@ -412,29 +449,11 @@ export class Map extends L.Map {
|
||||
}
|
||||
}
|
||||
|
||||
addTemporaryMarker(latlng: L.LatLng, name: string, coalition: string) {
|
||||
var marker = new TemporaryUnitMarker(latlng, name, coalition);
|
||||
addTemporaryMarker(latlng: L.LatLng, name: string, coalition: string, commandHash?: string) {
|
||||
var marker = new TemporaryUnitMarker(latlng, name, coalition, commandHash);
|
||||
marker.addTo(this);
|
||||
this.#temporaryMarkers.push(marker);
|
||||
}
|
||||
|
||||
removeTemporaryMarker(latlng: L.LatLng) {
|
||||
// TODO something more refined than this
|
||||
var dist: number | null = null;
|
||||
var closest: L.Marker | null = null;
|
||||
var i: number = 0;
|
||||
this.#temporaryMarkers.forEach((marker: L.Marker, idx: number) => {
|
||||
var t = latlng.distanceTo(marker.getLatLng());
|
||||
if (dist == null || t < dist) {
|
||||
dist = t;
|
||||
closest = marker;
|
||||
i = idx;
|
||||
}
|
||||
});
|
||||
if (closest && dist != null && dist < 100) {
|
||||
this.removeLayer(closest);
|
||||
this.#temporaryMarkers.splice(i, 1);
|
||||
}
|
||||
return marker;
|
||||
}
|
||||
|
||||
getSelectedCoalitionArea() {
|
||||
@ -549,7 +568,7 @@ export class Map extends L.Map {
|
||||
}
|
||||
|
||||
this.#longPressTimer = window.setTimeout(() => {
|
||||
if (e.originalEvent.button != 2)
|
||||
if (e.originalEvent.button != 2 || e.originalEvent.ctrlKey)
|
||||
return;
|
||||
|
||||
this.hideMapContextMenu();
|
||||
@ -557,7 +576,7 @@ export class Map extends L.Map {
|
||||
|
||||
var options: { [key: string]: { text: string, tooltip: string } } = {};
|
||||
const selectedUnits = getUnitsManager().getSelectedUnits();
|
||||
const selectedUnitTypes = getUnitsManager().getSelectedUnitsTypes();
|
||||
const selectedUnitTypes = getUnitsManager().getSelectedUnitsCategories();
|
||||
|
||||
if (selectedUnitTypes.length === 1 && ["Aircraft", "Helicopter"].includes(selectedUnitTypes[0])) {
|
||||
if (selectedUnits.every((unit: Unit) => { return unit.canFulfillRole(["CAS", "Strike"]) })) {
|
||||
@ -573,7 +592,7 @@ export class Map extends L.Map {
|
||||
else
|
||||
getInfoPopup().setText(`Selected units can not perform point actions.`);
|
||||
}
|
||||
else {
|
||||
else if(selectedUnitTypes.length > 1) {
|
||||
getInfoPopup().setText(`Multiple unit types selected, no common actions available.`);
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { DivIcon, LatLngExpression, MarkerOptions } from "leaflet";
|
||||
import { CustomMarker } from "./custommarker";
|
||||
import { SVGInjector } from "@tanem/svg-injector";
|
||||
import { getMap } from "..";
|
||||
import { getMap } from "../..";
|
||||
|
||||
export class SmokeMarker extends CustomMarker {
|
||||
#color: string;
|
||||
@ -1,16 +1,38 @@
|
||||
import { CustomMarker } from "./custommarker";
|
||||
import { DivIcon, LatLng } from "leaflet";
|
||||
import { SVGInjector } from "@tanem/svg-injector";
|
||||
import { getMarkerCategoryByName, getUnitDatabaseByCategory } from "../other/utils";
|
||||
import { getMarkerCategoryByName, getUnitDatabaseByCategory } from "../../other/utils";
|
||||
import { isCommandExecuted } from "../../server/server";
|
||||
import { getMap } from "../..";
|
||||
|
||||
export class TemporaryUnitMarker extends CustomMarker {
|
||||
#name: string;
|
||||
#coalition: string;
|
||||
#commandHash: string|undefined = undefined;
|
||||
#timer: number = 0;
|
||||
|
||||
constructor(latlng: LatLng, name: string, coalition: string) {
|
||||
constructor(latlng: LatLng, name: string, coalition: string, commandHash?: string) {
|
||||
super(latlng, {interactive: false});
|
||||
this.#name = name;
|
||||
this.#coalition = coalition;
|
||||
this.#commandHash = commandHash;
|
||||
|
||||
if (commandHash !== undefined)
|
||||
this.setCommandHash(commandHash)
|
||||
}
|
||||
|
||||
setCommandHash(commandHash: string) {
|
||||
this.#commandHash = commandHash;
|
||||
this.#timer = window.setInterval(() => {
|
||||
if (this.#commandHash !== undefined) {
|
||||
isCommandExecuted((res: any) => {
|
||||
if (res.commandExecuted) {
|
||||
this.removeFrom(getMap());
|
||||
window.clearInterval(this.#timer);
|
||||
}
|
||||
}, this.#commandHash)
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
createIcon() {
|
||||
@ -1,5 +1,5 @@
|
||||
import { DivIcon } from 'leaflet';
|
||||
import { CustomMarker } from '../map/custommarker';
|
||||
import { CustomMarker } from '../map/markers/custommarker';
|
||||
import { SVGInjector } from '@tanem/svg-injector';
|
||||
|
||||
export interface AirbaseOptions {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { DivIcon } from "leaflet";
|
||||
import { CustomMarker } from "../map/custommarker";
|
||||
import { CustomMarker } from "../map/markers/custommarker";
|
||||
import { SVGInjector } from "@tanem/svg-injector";
|
||||
|
||||
export class Bullseye extends CustomMarker {
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
import { LatLng } from "leaflet";
|
||||
import { getInfoPopup, getMap } from "..";
|
||||
import { Airbase, AirbaseChartData } from "./airbase";
|
||||
import { Airbase } from "./airbase";
|
||||
import { Bullseye } from "./bullseye";
|
||||
import { BLUE_COMMANDER, GAME_MASTER, NONE, RED_COMMANDER } from "../constants/constants";
|
||||
import { refreshAll, setCommandModeOptions } from "../server/server";
|
||||
import { Dropdown } from "../controls/dropdown";
|
||||
import { groundUnitDatabase } from "../unit/groundunitdatabase";
|
||||
import { groundUnitDatabase } from "../unit/databases/groundunitdatabase";
|
||||
import { createCheckboxOption, getCheckboxOptions } from "../other/utils";
|
||||
import { aircraftDatabase } from "../unit/aircraftdatabase";
|
||||
import { helicopterDatabase } from "../unit/helicopterdatabase";
|
||||
import { navyUnitDatabase } from "../unit/navyunitdatabase";
|
||||
import { aircraftDatabase } from "../unit/databases/aircraftdatabase";
|
||||
import { helicopterDatabase } from "../unit/databases/helicopterdatabase";
|
||||
import { navyUnitDatabase } from "../unit/databases/navyunitdatabase";
|
||||
|
||||
export class MissionHandler {
|
||||
/** The MissionManager */
|
||||
export class MissionManager {
|
||||
#bullseyes: { [name: string]: Bullseye } = {};
|
||||
#airbases: { [name: string]: Airbase } = {};
|
||||
#theatre: string = "";
|
||||
@ -79,7 +80,7 @@ export class MissionHandler {
|
||||
this.#dateAndTime = data.mission.dateAndTime;
|
||||
|
||||
/* Set the coalition countries */
|
||||
//this.#coalitions = data.mission.coalitions;
|
||||
this.#coalitions = data.mission.coalitions;
|
||||
|
||||
/* Set the command mode options */
|
||||
this.#setcommandModeOptions(data.mission.commandModeOptions);
|
||||
@ -1,7 +1,5 @@
|
||||
import { UnitDataTable } from "./atc/unitdatatable";
|
||||
import { FeatureSwitches } from "./features/featureswitches";
|
||||
import { Map } from "./map/map";
|
||||
import { MissionHandler } from "./mission/missionhandler";
|
||||
import { PanelsManager } from "./panels/panelsmanager";
|
||||
import { ControlTips } from "./shortcut/controltips";
|
||||
import { ShortcutManager } from "./shortcut/shortcutmanager";
|
||||
@ -11,8 +9,6 @@ import { UnitsManager } from "./unit/unitsmanager";
|
||||
export interface IOlympusApp {
|
||||
featureSwitches: FeatureSwitches;
|
||||
map: Map,
|
||||
missionHandler: MissionHandler;
|
||||
unitDataTable: UnitDataTable;
|
||||
unitsManager: UnitsManager;
|
||||
}
|
||||
|
||||
@ -21,10 +17,8 @@ export abstract class OlympusApp {
|
||||
#controlTips: ControlTips;
|
||||
#featureSwitches: FeatureSwitches;
|
||||
#map: Map;
|
||||
#missionHandler: MissionHandler;
|
||||
#panelsManager: PanelsManager = new PanelsManager( this );
|
||||
#shortcutManager: ShortcutManager = new ShortcutManager( this );
|
||||
#unitDataTable: UnitDataTable;
|
||||
#unitsManager: UnitsManager;
|
||||
|
||||
constructor( config:IOlympusApp ) {
|
||||
@ -32,8 +26,6 @@ export abstract class OlympusApp {
|
||||
this.#controlTips = new ControlTips( "control-tips-panel", this );
|
||||
this.#featureSwitches = config.featureSwitches;
|
||||
this.#map = config.map;
|
||||
this.#missionHandler = config.missionHandler;
|
||||
this.#unitDataTable = config.unitDataTable;
|
||||
this.#unitsManager = config.unitsManager;
|
||||
|
||||
this.getMap().setOlympusApp( this );
|
||||
@ -52,10 +44,6 @@ export abstract class OlympusApp {
|
||||
return this.#map;
|
||||
}
|
||||
|
||||
getMissionHandler() {
|
||||
return this.#missionHandler;
|
||||
}
|
||||
|
||||
getPanelsManager() {
|
||||
return this.#panelsManager;
|
||||
}
|
||||
@ -64,10 +52,6 @@ export abstract class OlympusApp {
|
||||
return this.#shortcutManager;
|
||||
}
|
||||
|
||||
getUnitDataTable() {
|
||||
return this.#unitDataTable;
|
||||
}
|
||||
|
||||
getUnitsManager() {
|
||||
return this.#unitsManager;
|
||||
}
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
import { LatLng, Point, Polygon } from "leaflet";
|
||||
import * as turf from "@turf/turf";
|
||||
import { UnitDatabase } from "../unit/unitdatabase";
|
||||
import { aircraftDatabase } from "../unit/aircraftdatabase";
|
||||
import { helicopterDatabase } from "../unit/helicopterdatabase";
|
||||
import { groundUnitDatabase } from "../unit/groundunitdatabase";
|
||||
import { UnitDatabase } from "../unit/databases/unitdatabase";
|
||||
import { aircraftDatabase } from "../unit/databases/aircraftdatabase";
|
||||
import { helicopterDatabase } from "../unit/databases/helicopterdatabase";
|
||||
import { groundUnitDatabase } from "../unit/databases/groundunitdatabase";
|
||||
import { Buffer } from "buffer";
|
||||
import { ROEs, emissionsCountermeasures, reactionsToThreat, states } from "../constants/constants";
|
||||
import { Dropdown } from "../controls/dropdown";
|
||||
import { UnitBlueprint } from "../@types/unitdatabase";
|
||||
import { navyUnitDatabase } from "../unit/databases/navyunitdatabase";
|
||||
|
||||
export function bearing(lat1: number, lon1: number, lat2: number, lon2: number) {
|
||||
const φ1 = deg2rad(lat1); // φ, λ in radians
|
||||
@ -296,12 +298,14 @@ export function getMarkerCategoryByName(name: string) {
|
||||
}
|
||||
|
||||
export function getUnitDatabaseByCategory(category: string) {
|
||||
if (category == "aircraft")
|
||||
if (category.toLowerCase() == "aircraft")
|
||||
return aircraftDatabase;
|
||||
else if (category == "helicopter")
|
||||
else if (category.toLowerCase() == "helicopter")
|
||||
return helicopterDatabase;
|
||||
else if (category.includes("groundunit"))
|
||||
else if (category.toLowerCase().includes("groundunit"))
|
||||
return groundUnitDatabase;
|
||||
else if (category.toLowerCase().includes("navyunit"))
|
||||
return navyUnitDatabase;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ export class ConnectionStatusPanel extends Panel {
|
||||
super( ID );
|
||||
}
|
||||
|
||||
|
||||
update(connected: boolean) {
|
||||
this.getElement().toggleAttribute( "data-is-connected", connected );
|
||||
}
|
||||
|
||||
@ -3,8 +3,12 @@ import { Unit } from "../unit/unit";
|
||||
import { Panel } from "./panel";
|
||||
|
||||
export class HotgroupPanel extends Panel {
|
||||
constructor(ID: string) {
|
||||
super( ID );
|
||||
/**
|
||||
*
|
||||
* @param ID - the ID of the HTML element which will contain the context menu
|
||||
*/
|
||||
constructor(ID: string){
|
||||
super(ID);
|
||||
document.addEventListener("unitDeath", () => this.refreshHotgroups());
|
||||
}
|
||||
|
||||
|
||||
@ -7,14 +7,19 @@ export class LogPanel extends Panel {
|
||||
#scrolledDown: boolean = true;
|
||||
#logs: {[key: string]: string} = {};
|
||||
|
||||
constructor(ID: string) {
|
||||
super( ID );
|
||||
/**
|
||||
*
|
||||
* @param ID - the ID of the HTML element which will contain the context menu
|
||||
*/
|
||||
constructor(ID: string){
|
||||
super(ID);
|
||||
|
||||
document.addEventListener("toggleLogPanel", () => {
|
||||
this.getElement().classList.toggle("open");
|
||||
this.#open = !this.#open;
|
||||
this.#queuedMessages = 0;
|
||||
this.#updateHeader();
|
||||
this.#calculateHeight();
|
||||
|
||||
if (this.#scrolledDown)
|
||||
this.#scrollDown();
|
||||
@ -85,6 +90,9 @@ export class LogPanel extends Panel {
|
||||
|
||||
#calculateHeight() {
|
||||
const mouseInfoPanel = getMouseInfoPanel();
|
||||
this.getElement().style.height = `${mouseInfoPanel.getElement().offsetTop - this.getElement().offsetTop - 10}px`;
|
||||
if (this.#open)
|
||||
this.getElement().style.height = `${mouseInfoPanel.getElement().offsetTop - this.getElement().offsetTop - 10}px`;
|
||||
else
|
||||
this.getElement().style.height = "fit-content";
|
||||
}
|
||||
}
|
||||
@ -124,9 +124,9 @@ export class MouseInfoPanel extends Panel {
|
||||
this.#drawMeasureLine();
|
||||
}
|
||||
|
||||
#drawMeasure(imgId: string | null, textId: string, value: LatLng | null, mousePosition: LatLng) {
|
||||
var el = this.getElement().querySelector(`#${textId}`) as HTMLElement;
|
||||
var img = imgId != null ? this.getElement().querySelector(`#${imgId}`) as HTMLElement : null;
|
||||
#drawMeasure(imgID: string | null, textID: string, value: LatLng | null, mousePosition: LatLng) {
|
||||
var el = this.getElement().querySelector(`#${textID}`) as HTMLElement;
|
||||
var img = imgID != null ? this.getElement().querySelector(`#${imgID}`) as HTMLElement : null;
|
||||
if (value) {
|
||||
if (el != null) {
|
||||
el.classList.remove("hide");
|
||||
@ -156,9 +156,9 @@ export class MouseInfoPanel extends Panel {
|
||||
}
|
||||
}
|
||||
|
||||
#drawCoordinates(imgId: string, textId: string, value: string) {
|
||||
const el = this.getElement().querySelector(`#${textId}`) as HTMLElement;
|
||||
const img = this.getElement().querySelector(`#${imgId}`) as HTMLElement;
|
||||
#drawCoordinates(imgID: string, textID: string, value: string) {
|
||||
const el = this.getElement().querySelector(`#${textID}`) as HTMLElement;
|
||||
const img = this.getElement().querySelector(`#${imgID}`) as HTMLElement;
|
||||
if (img && el) {
|
||||
el.dataset.value = value.substring(1);
|
||||
img.dataset.label = value[0];
|
||||
|
||||
@ -2,7 +2,7 @@ import { SVGInjector } from "@tanem/svg-injector";
|
||||
import { getUnitsManager } from "..";
|
||||
import { Dropdown } from "../controls/dropdown";
|
||||
import { Slider } from "../controls/slider";
|
||||
import { aircraftDatabase } from "../unit/aircraftdatabase";
|
||||
import { aircraftDatabase } from "../unit/databases/aircraftdatabase";
|
||||
import { Unit } from "../unit/unit";
|
||||
import { Panel } from "./panel";
|
||||
import { Switch } from "../controls/switch";
|
||||
@ -25,15 +25,19 @@ export class UnitControlPanel extends Panel {
|
||||
#units: Unit[] = [];
|
||||
#selectedUnitsTypes: string[] = [];
|
||||
|
||||
constructor(ID: string) {
|
||||
super( ID );
|
||||
/**
|
||||
*
|
||||
* @param ID - the ID of the HTML element which will contain the context menu
|
||||
*/
|
||||
constructor(ID: string){
|
||||
super(ID);
|
||||
|
||||
/* Unit control sliders */
|
||||
this.#altitudeSlider = new Slider("altitude-slider", 0, 100, "ft", (value: number) => { getUnitsManager().selectedUnitsSetAltitude(ftToM(value)); });
|
||||
this.#altitudeTypeSwitch = new Switch("altitude-type-switch", (value: boolean) => { getUnitsManager().selectedUnitsSetAltitudeType(value? "AGL": "ASL"); });
|
||||
this.#altitudeTypeSwitch = new Switch("altitude-type-switch", (value: boolean) => { getUnitsManager().selectedUnitsSetAltitudeType(value? "ASL": "AGL"); });
|
||||
|
||||
this.#speedSlider = new Slider("speed-slider", 0, 100, "kts", (value: number) => { getUnitsManager().selectedUnitsSetSpeed(knotsToMs(value)); });
|
||||
this.#speedTypeSwitch = new Switch("speed-type-switch", (value: boolean) => { getUnitsManager().selectedUnitsSetSpeedType(value? "GS": "CAS"); });
|
||||
this.#speedTypeSwitch = new Switch("speed-type-switch", (value: boolean) => { getUnitsManager().selectedUnitsSetSpeedType(value? "CAS": "GS"); });
|
||||
|
||||
/* Option buttons */
|
||||
// Reversing the ROEs so that the least "aggressive" option is always on the left
|
||||
@ -105,7 +109,7 @@ export class UnitControlPanel extends Panel {
|
||||
|
||||
addButtons() {
|
||||
this.#units = getUnitsManager().getSelectedUnits();
|
||||
this.#selectedUnitsTypes = getUnitsManager().getSelectedUnitsTypes();
|
||||
this.#selectedUnitsTypes = getUnitsManager().getSelectedUnitsCategories();
|
||||
|
||||
if (this.#units.length < 20) {
|
||||
this.getElement().querySelector("#selected-units-container")?.replaceChildren(...this.#units.map((unit: Unit, index: number) => {
|
||||
@ -164,8 +168,8 @@ export class UnitControlPanel extends Panel {
|
||||
var onOff = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getOnOff()});
|
||||
var followRoads = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getFollowRoads()});
|
||||
|
||||
this.#altitudeTypeSwitch.setValue(desiredAltitudeType != undefined? desiredAltitudeType == "AGL": undefined, false);
|
||||
this.#speedTypeSwitch.setValue(desiredSpeedType != undefined? desiredSpeedType == "GS": undefined, false);
|
||||
this.#altitudeTypeSwitch.setValue(desiredAltitudeType != undefined? desiredAltitudeType == "ASL": undefined, false);
|
||||
this.#speedTypeSwitch.setValue(desiredSpeedType != undefined? desiredSpeedType == "CAS": undefined, false);
|
||||
|
||||
this.#speedSlider.setMinMax(minSpeedValues[this.#selectedUnitsTypes[0]], maxSpeedValues[this.#selectedUnitsTypes[0]]);
|
||||
this.#altitudeSlider.setMinMax(minAltitudeValues[this.#selectedUnitsTypes[0]], maxAltitudeValues[this.#selectedUnitsTypes[0]]);
|
||||
@ -225,7 +229,7 @@ export class UnitControlPanel extends Panel {
|
||||
|
||||
const unit = units[0];
|
||||
const roles = aircraftDatabase.getByName(unit.getName())?.loadouts?.map((loadout) => {return loadout.roles})
|
||||
const tanker = roles != undefined && Array.prototype.concat.apply([], roles)?.includes("Tanker");
|
||||
const tanker = roles != undefined && Array.prototype.concat.apply([], roles)?.includes("Refueling");
|
||||
const AWACS = roles != undefined && Array.prototype.concat.apply([], roles)?.includes("AWACS");
|
||||
const radioMHz = Math.floor(unit.getRadio().frequency / 1000000);
|
||||
const radioDecimals = (unit.getRadio().frequency / 1000000 - radioMHz) * 1000;
|
||||
|
||||
@ -1,19 +1,12 @@
|
||||
import { Ammo } from "../@types/unit";
|
||||
import { aircraftDatabase } from "../unit/aircraftdatabase";
|
||||
import { aircraftDatabase } from "../unit/databases/aircraftdatabase";
|
||||
import { Unit } from "../unit/unit";
|
||||
import { Panel } from "./panel";
|
||||
|
||||
export class UnitInfoPanel extends Panel {
|
||||
#altitude: HTMLElement;
|
||||
#currentTask: HTMLElement;
|
||||
#fuelBar: HTMLElement;
|
||||
#fuelPercentage: HTMLElement;
|
||||
#groundSpeed: HTMLElement;
|
||||
#groupName: HTMLElement;
|
||||
#heading: HTMLElement;
|
||||
#name: HTMLElement;
|
||||
#latitude: HTMLElement;
|
||||
#longitude: HTMLElement;
|
||||
#loadoutContainer: HTMLElement;
|
||||
#silhouette: HTMLImageElement;
|
||||
#unitControl: HTMLElement;
|
||||
@ -23,17 +16,10 @@ export class UnitInfoPanel extends Panel {
|
||||
constructor(ID: string) {
|
||||
super( ID );
|
||||
|
||||
this.#altitude = (this.getElement().querySelector("#altitude")) as HTMLElement;
|
||||
this.#currentTask = (this.getElement().querySelector("#current-task")) as HTMLElement;
|
||||
this.#groundSpeed = (this.getElement().querySelector("#ground-speed")) as HTMLElement;
|
||||
this.#fuelBar = (this.getElement().querySelector("#fuel-bar")) as HTMLElement;
|
||||
this.#fuelPercentage = (this.getElement().querySelector("#fuel-percentage")) as HTMLElement;
|
||||
this.#groupName = (this.getElement().querySelector("#group-name")) as HTMLElement;
|
||||
this.#heading = (this.getElement().querySelector("#heading")) as HTMLElement;
|
||||
this.#name = (this.getElement().querySelector("#name")) as HTMLElement;
|
||||
this.#latitude = (this.getElement().querySelector("#latitude")) as HTMLElement;
|
||||
this.#loadoutContainer = (this.getElement().querySelector("#loadout-container")) as HTMLElement;
|
||||
this.#longitude = (this.getElement().querySelector("#longitude")) as HTMLElement;
|
||||
this.#silhouette = (this.getElement().querySelector("#loadout-silhouette")) as HTMLImageElement;
|
||||
this.#unitControl = (this.getElement().querySelector("#unit-control")) as HTMLElement;
|
||||
this.#unitLabel = (this.getElement().querySelector("#unit-label")) as HTMLElement;
|
||||
@ -50,14 +36,12 @@ export class UnitInfoPanel extends Panel {
|
||||
#onUnitUpdate(unit: Unit) {
|
||||
if (this.getElement() != null && this.getVisible() && unit.getSelected()) {
|
||||
|
||||
const baseData = unit.getData();
|
||||
|
||||
/* Set the unit info */
|
||||
this.#unitLabel.innerText = aircraftDatabase.getByName(baseData.name)?.label || baseData.name;
|
||||
this.#unitName.innerText = baseData.unitName;
|
||||
this.#unitLabel.innerText = aircraftDatabase.getByName(unit.getName())?.label || unit.getName();
|
||||
this.#unitName.innerText = unit.getUnitName();
|
||||
if (unit.getHuman())
|
||||
this.#unitControl.innerText = "Human";
|
||||
else if (baseData.controlled)
|
||||
else if (unit.getControlled())
|
||||
this.#unitControl.innerText = "Olympus controlled";
|
||||
else
|
||||
this.#unitControl.innerText = "DCS Controlled";
|
||||
@ -66,11 +50,11 @@ export class UnitInfoPanel extends Panel {
|
||||
this.#currentTask.dataset.currentTask = unit.getTask() !== "" ? unit.getTask() : "No task";
|
||||
this.#currentTask.dataset.coalition = unit.getCoalition();
|
||||
|
||||
this.#silhouette.src = `/images/units/${unit.getDatabase()?.getByName(baseData.name)?.filename}`;
|
||||
this.#silhouette.classList.toggle("hide", unit.getDatabase()?.getByName(baseData.name)?.filename == undefined || unit.getDatabase()?.getByName(baseData.name)?.filename == '');
|
||||
this.#silhouette.src = `/images/units/${unit.getDatabase()?.getByName(unit.getName())?.filename}`;
|
||||
this.#silhouette.classList.toggle("hide", unit.getDatabase()?.getByName(unit.getName())?.filename == undefined || unit.getDatabase()?.getByName(unit.getName())?.filename == '');
|
||||
|
||||
/* Add the loadout elements */
|
||||
const items = <HTMLElement>this.#loadoutContainer.querySelector("#loadout-items");
|
||||
const items = this.#loadoutContainer.querySelector("#loadout-items") as HTMLElement;
|
||||
|
||||
if (items) {
|
||||
const ammo = Object.values(unit.getAmmo());
|
||||
|
||||
@ -1,19 +1,13 @@
|
||||
import { LatLng } from 'leaflet';
|
||||
import { getConnectionStatusPanel, getInfoPopup, getLogPanel, getMissionHandler, getServerStatusPanel, getUnitsManager, getWeaponsManager, setLoginStatus } from '..';
|
||||
import { GeneralSettings, Radio, TACAN } from '../@types/unit';
|
||||
import { NONE, ROEs, emissionsCountermeasures, reactionsToThreat } from '../constants/constants';
|
||||
import { AIRBASES_URI, BULLSEYE_URI, COMMANDS_URI, LOGS_URI, MISSION_URI, NONE, ROEs, UNITS_URI, WEAPONS_URI, emissionsCountermeasures, reactionsToThreat } from '../constants/constants';
|
||||
|
||||
var connected: boolean = false;
|
||||
var paused: boolean = false;
|
||||
|
||||
var REST_ADDRESS = "http://localhost:30000/olympus";
|
||||
var DEMO_ADDRESS = window.location.href + "demo";
|
||||
const UNITS_URI = "units";
|
||||
const WEAPONS_URI = "weapons";
|
||||
const LOGS_URI = "logs";
|
||||
const AIRBASES_URI = "airbases";
|
||||
const BULLSEYE_URI = "bullseyes";
|
||||
const MISSION_URI = "mission";
|
||||
|
||||
var username = "";
|
||||
var password = "";
|
||||
@ -38,13 +32,15 @@ export function setCredentials(newUsername: string, newPassword: string) {
|
||||
password = newPassword;
|
||||
}
|
||||
|
||||
export function GET(callback: CallableFunction, uri: string, options?: { time?: number }, responseType?: string) {
|
||||
export function GET(callback: CallableFunction, uri: string, options?: ServerRequestOptions, responseType?: string) {
|
||||
var xmlHttp = new XMLHttpRequest();
|
||||
|
||||
/* Assemble the request options string */
|
||||
var optionsString = '';
|
||||
if (options?.time != undefined)
|
||||
optionsString = `time=${options.time}`;
|
||||
if (options?.commandHash != undefined)
|
||||
optionsString = `commandHash=${options.commandHash}`;
|
||||
|
||||
/* On the connection */
|
||||
xmlHttp.open("GET", `${demoEnabled ? DEMO_ADDRESS : REST_ADDRESS}/${uri}${optionsString ? `?${optionsString}` : ''}`, true);
|
||||
@ -92,8 +88,9 @@ export function POST(request: object, callback: CallableFunction) {
|
||||
xmlHttp.setRequestHeader("Content-Type", "application/json");
|
||||
if (username && password)
|
||||
xmlHttp.setRequestHeader("Authorization", "Basic " + btoa(`${username}:${password}`));
|
||||
xmlHttp.onreadystatechange = () => {
|
||||
callback();
|
||||
xmlHttp.onload = (res: any) => {
|
||||
var res = JSON.parse(xmlHttp.responseText);
|
||||
callback(res);
|
||||
};
|
||||
xmlHttp.send(JSON.stringify(request));
|
||||
}
|
||||
@ -140,185 +137,189 @@ export function getWeapons(callback: CallableFunction, refresh: boolean = false)
|
||||
GET(callback, WEAPONS_URI, { time: refresh ? 0 : lastUpdateTimes[WEAPONS_URI] }, 'arraybuffer');
|
||||
}
|
||||
|
||||
export function addDestination(ID: number, path: any) {
|
||||
export function isCommandExecuted(callback: CallableFunction, commandHash: string) {
|
||||
GET(callback, COMMANDS_URI, { commandHash: commandHash});
|
||||
}
|
||||
|
||||
export function addDestination(ID: number, path: any, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "path": path }
|
||||
var data = { "setPath": command }
|
||||
POST(data, () => { });
|
||||
POST(data, callback);
|
||||
}
|
||||
|
||||
export function spawnSmoke(color: string, latlng: LatLng) {
|
||||
export function spawnSmoke(color: string, latlng: LatLng, callback: CallableFunction = () => {}) {
|
||||
var command = { "color": color, "location": latlng };
|
||||
var data = { "smoke": command }
|
||||
POST(data, () => { });
|
||||
POST(data, callback);
|
||||
}
|
||||
|
||||
export function spawnExplosion(intensity: number, latlng: LatLng) {
|
||||
export function spawnExplosion(intensity: number, latlng: LatLng, callback: CallableFunction = () => {}) {
|
||||
var command = { "intensity": intensity, "location": latlng };
|
||||
var data = { "explosion": command }
|
||||
POST(data, () => { });
|
||||
POST(data, callback);
|
||||
}
|
||||
|
||||
export function spawnAircrafts(units: any, coalition: string, airbaseName: string, immediate: boolean, spawnPoints: number) {
|
||||
var command = { "units": units, "coalition": coalition, "airbaseName": airbaseName, "immediate": immediate, "spawnPoints": spawnPoints };
|
||||
export function spawnAircrafts(units: any, coalition: string, airbaseName: string, country: string, immediate: boolean, spawnPoints: number, callback: CallableFunction = () => {}) {
|
||||
var command = { "units": units, "coalition": coalition, "airbaseName": airbaseName, "country": country, "immediate": immediate, "spawnPoints": spawnPoints };
|
||||
var data = { "spawnAircrafts": command }
|
||||
POST(data, () => { });
|
||||
POST(data, callback);
|
||||
}
|
||||
|
||||
export function spawnHelicopters(units: any, coalition: string, airbaseName: string, immediate: boolean, spawnPoints: number) {
|
||||
var command = { "units": units, "coalition": coalition, "airbaseName": airbaseName, "immediate": immediate, "spawnPoints": spawnPoints };
|
||||
export function spawnHelicopters(units: any, coalition: string, airbaseName: string, country: string, immediate: boolean, spawnPoints: number, callback: CallableFunction = () => {}) {
|
||||
var command = { "units": units, "coalition": coalition, "airbaseName": airbaseName, "country": country, "immediate": immediate, "spawnPoints": spawnPoints };
|
||||
var data = { "spawnHelicopters": command }
|
||||
POST(data, () => { });
|
||||
POST(data, callback);
|
||||
}
|
||||
|
||||
export function spawnGroundUnits(units: any, coalition: string, immediate: boolean, spawnPoints: number) {
|
||||
var command = { "units": units, "coalition": coalition, "immediate": immediate, "spawnPoints": spawnPoints };;
|
||||
export function spawnGroundUnits(units: any, coalition: string, country: string, immediate: boolean, spawnPoints: number, callback: CallableFunction = () => {}) {
|
||||
var command = { "units": units, "coalition": coalition, "country": country, "immediate": immediate, "spawnPoints": spawnPoints };;
|
||||
var data = { "spawnGroundUnits": command }
|
||||
POST(data, () => { });
|
||||
POST(data, callback);
|
||||
}
|
||||
|
||||
export function spawnNavyUnits(units: any, coalition: string, immediate: boolean, spawnPoints: number) {
|
||||
var command = { "units": units, "coalition": coalition, "immediate": immediate, "spawnPoints": spawnPoints };
|
||||
export function spawnNavyUnits(units: any, coalition: string, country: string, immediate: boolean, spawnPoints: number, callback: CallableFunction = () => {}) {
|
||||
var command = { "units": units, "coalition": coalition, "country": country, "immediate": immediate, "spawnPoints": spawnPoints };
|
||||
var data = { "spawnNavyUnits": command }
|
||||
POST(data, () => { });
|
||||
POST(data, callback);
|
||||
}
|
||||
|
||||
export function attackUnit(ID: number, targetID: number) {
|
||||
export function attackUnit(ID: number, targetID: number, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "targetID": targetID };
|
||||
var data = { "attackUnit": command }
|
||||
POST(data, () => { });
|
||||
POST(data, callback);
|
||||
}
|
||||
|
||||
export function followUnit(ID: number, targetID: number, offset: { "x": number, "y": number, "z": number }) {
|
||||
export function followUnit(ID: number, targetID: number, offset: { "x": number, "y": number, "z": number }, callback: CallableFunction = () => {}) {
|
||||
// X: front-rear, positive front
|
||||
// Y: top-bottom, positive bottom
|
||||
// Z: left-right, positive right
|
||||
|
||||
var command = { "ID": ID, "targetID": targetID, "offsetX": offset["x"], "offsetY": offset["y"], "offsetZ": offset["z"] };
|
||||
var data = { "followUnit": command }
|
||||
POST(data, () => { });
|
||||
POST(data, callback);
|
||||
}
|
||||
|
||||
export function cloneUnit(ID: number, latlng: LatLng) {
|
||||
var command = { "ID": ID, "location": latlng };
|
||||
var data = { "cloneUnit": command }
|
||||
POST(data, () => { });
|
||||
export function cloneUnits(units: {ID: number, location: LatLng}[], deleteOriginal: boolean, spawnPoints: number, callback: CallableFunction = () => {}) {
|
||||
var command = { "units": units, "deleteOriginal": deleteOriginal, "spawnPoints": spawnPoints };
|
||||
var data = { "cloneUnits": command }
|
||||
POST(data, callback);
|
||||
}
|
||||
|
||||
export function deleteUnit(ID: number, explosion: boolean, immediate: boolean) {
|
||||
export function deleteUnit(ID: number, explosion: boolean, immediate: boolean, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "explosion": explosion, "immediate": immediate };
|
||||
var data = { "deleteUnit": command }
|
||||
POST(data, () => { });
|
||||
POST(data, callback);
|
||||
}
|
||||
|
||||
export function landAt(ID: number, latlng: LatLng) {
|
||||
export function landAt(ID: number, latlng: LatLng, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "location": latlng };
|
||||
var data = { "landAt": command }
|
||||
POST(data, () => { });
|
||||
POST(data, callback);
|
||||
}
|
||||
|
||||
export function changeSpeed(ID: number, speedChange: string) {
|
||||
export function changeSpeed(ID: number, speedChange: string, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "change": speedChange }
|
||||
var data = { "changeSpeed": command }
|
||||
POST(data, () => { });
|
||||
POST(data, callback);
|
||||
}
|
||||
|
||||
export function setSpeed(ID: number, speed: number) {
|
||||
export function setSpeed(ID: number, speed: number, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "speed": speed }
|
||||
var data = { "setSpeed": command }
|
||||
POST(data, () => { });
|
||||
POST(data, callback);
|
||||
}
|
||||
|
||||
export function setSpeedType(ID: number, speedType: string) {
|
||||
export function setSpeedType(ID: number, speedType: string, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "speedType": speedType }
|
||||
var data = { "setSpeedType": command }
|
||||
POST(data, () => { });
|
||||
POST(data, callback);
|
||||
}
|
||||
|
||||
export function changeAltitude(ID: number, altitudeChange: string) {
|
||||
export function changeAltitude(ID: number, altitudeChange: string, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "change": altitudeChange }
|
||||
var data = { "changeAltitude": command }
|
||||
POST(data, () => { });
|
||||
POST(data, callback);
|
||||
}
|
||||
|
||||
export function setAltitudeType(ID: number, altitudeType: string) {
|
||||
export function setAltitudeType(ID: number, altitudeType: string, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "altitudeType": altitudeType }
|
||||
var data = { "setAltitudeType": command }
|
||||
POST(data, () => { });
|
||||
POST(data, callback);
|
||||
}
|
||||
|
||||
export function setAltitude(ID: number, altitude: number) {
|
||||
export function setAltitude(ID: number, altitude: number, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "altitude": altitude }
|
||||
var data = { "setAltitude": command }
|
||||
POST(data, () => { });
|
||||
POST(data, callback);
|
||||
}
|
||||
|
||||
export function createFormation(ID: number, isLeader: boolean, wingmenIDs: number[]) {
|
||||
export function createFormation(ID: number, isLeader: boolean, wingmenIDs: number[], callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "wingmenIDs": wingmenIDs, "isLeader": isLeader }
|
||||
var data = { "setLeader": command }
|
||||
POST(data, () => { });
|
||||
POST(data, callback);
|
||||
}
|
||||
|
||||
export function setROE(ID: number, ROE: string) {
|
||||
export function setROE(ID: number, ROE: string, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "ROE": ROEs.indexOf(ROE) }
|
||||
var data = { "setROE": command }
|
||||
POST(data, () => { });
|
||||
POST(data, callback);
|
||||
}
|
||||
|
||||
export function setReactionToThreat(ID: number, reactionToThreat: string) {
|
||||
export function setReactionToThreat(ID: number, reactionToThreat: string, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "reactionToThreat": reactionsToThreat.indexOf(reactionToThreat) }
|
||||
var data = { "setReactionToThreat": command }
|
||||
POST(data, () => { });
|
||||
POST(data, callback);
|
||||
}
|
||||
|
||||
export function setEmissionsCountermeasures(ID: number, emissionCountermeasure: string) {
|
||||
export function setEmissionsCountermeasures(ID: number, emissionCountermeasure: string, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "emissionsCountermeasures": emissionsCountermeasures.indexOf(emissionCountermeasure) }
|
||||
var data = { "setEmissionsCountermeasures": command }
|
||||
POST(data, () => { });
|
||||
POST(data, callback);
|
||||
}
|
||||
|
||||
export function setOnOff(ID: number, onOff: boolean) {
|
||||
export function setOnOff(ID: number, onOff: boolean, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "onOff": onOff }
|
||||
var data = { "setOnOff": command }
|
||||
POST(data, () => { });
|
||||
POST(data, callback);
|
||||
}
|
||||
|
||||
export function setFollowRoads(ID: number, followRoads: boolean) {
|
||||
export function setFollowRoads(ID: number, followRoads: boolean, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "followRoads": followRoads }
|
||||
var data = { "setFollowRoads": command }
|
||||
POST(data, () => { });
|
||||
POST(data, callback);
|
||||
}
|
||||
|
||||
export function refuel(ID: number) {
|
||||
export function refuel(ID: number, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID };
|
||||
var data = { "refuel": command }
|
||||
POST(data, () => { });
|
||||
POST(data, callback);
|
||||
}
|
||||
|
||||
export function bombPoint(ID: number, latlng: LatLng) {
|
||||
export function bombPoint(ID: number, latlng: LatLng, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "location": latlng }
|
||||
var data = { "bombPoint": command }
|
||||
POST(data, () => { });
|
||||
POST(data, callback);
|
||||
}
|
||||
|
||||
export function carpetBomb(ID: number, latlng: LatLng) {
|
||||
export function carpetBomb(ID: number, latlng: LatLng, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "location": latlng }
|
||||
var data = { "carpetBomb": command }
|
||||
POST(data, () => { });
|
||||
POST(data, callback);
|
||||
}
|
||||
|
||||
export function bombBuilding(ID: number, latlng: LatLng) {
|
||||
export function bombBuilding(ID: number, latlng: LatLng, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "location": latlng }
|
||||
var data = { "bombBuilding": command }
|
||||
POST(data, () => { });
|
||||
POST(data, callback);
|
||||
}
|
||||
|
||||
export function fireAtArea(ID: number, latlng: LatLng) {
|
||||
export function fireAtArea(ID: number, latlng: LatLng, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID, "location": latlng }
|
||||
var data = { "fireAtArea": command }
|
||||
POST(data, () => { });
|
||||
POST(data, callback);
|
||||
}
|
||||
|
||||
export function setAdvacedOptions(ID: number, isTanker: boolean, isAWACS: boolean, TACAN: TACAN, radio: Radio, generalSettings: GeneralSettings) {
|
||||
export function setAdvacedOptions(ID: number, isTanker: boolean, isAWACS: boolean, TACAN: TACAN, radio: Radio, generalSettings: GeneralSettings, callback: CallableFunction = () => {}) {
|
||||
var command = {
|
||||
"ID": ID,
|
||||
"isTanker": isTanker,
|
||||
@ -329,10 +330,10 @@ export function setAdvacedOptions(ID: number, isTanker: boolean, isAWACS: boolea
|
||||
};
|
||||
|
||||
var data = { "setAdvancedOptions": command };
|
||||
POST(data, () => { });
|
||||
POST(data, callback);
|
||||
}
|
||||
|
||||
export function setCommandModeOptions(restrictSpawns: boolean, restrictToCoalition: boolean, spawnPoints: {blue: number, red: number}, eras: string[], setupTime: number) {
|
||||
export function setCommandModeOptions(restrictSpawns: boolean, restrictToCoalition: boolean, spawnPoints: {blue: number, red: number}, eras: string[], setupTime: number, callback: CallableFunction = () => {}) {
|
||||
var command = {
|
||||
"restrictSpawns": restrictSpawns,
|
||||
"restrictToCoalition": restrictToCoalition,
|
||||
@ -342,7 +343,7 @@ export function setCommandModeOptions(restrictSpawns: boolean, restrictToCoaliti
|
||||
};
|
||||
|
||||
var data = { "setCommandModeOptions": command };
|
||||
POST(data, () => { });
|
||||
POST(data, callback);
|
||||
}
|
||||
|
||||
export function startUpdate() {
|
||||
|
||||
5
client/src/toolbars/commandmodetoolbar.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { Toolbar } from "./toolbar";
|
||||
|
||||
export class CommandModeToolbar extends Toolbar {
|
||||
// TODO move here all code about the command mode toolbar
|
||||
}
|
||||
13
client/src/toolbars/primarytoolbar.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { Dropdown } from "../controls/dropdown";
|
||||
import { Toolbar } from "./toolbar";
|
||||
|
||||
export class PrimaryToolbar extends Toolbar {
|
||||
constructor(ID: string) {
|
||||
super(ID);
|
||||
|
||||
// TODO move here all code about primary toolbar
|
||||
|
||||
/* The content of the dropdown is entirely defined in the .ejs file */
|
||||
new Dropdown("app-icon", () => { });
|
||||
}
|
||||
}
|
||||
38
client/src/toolbars/toolbar.ts
Normal file
@ -0,0 +1,38 @@
|
||||
export class Toolbar {
|
||||
#element: HTMLElement
|
||||
#visible: boolean = true;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ID - the ID of the HTML element which will contain the context menu
|
||||
*/
|
||||
constructor(ID: string){
|
||||
this.#element = document.getElementById(ID) as HTMLElement;
|
||||
}
|
||||
|
||||
show() {
|
||||
this.#element.classList.toggle("hide", false);
|
||||
this.#visible = true;
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.#element.classList.toggle("hide", true);
|
||||
this.#visible = false;
|
||||
}
|
||||
|
||||
toggle() {
|
||||
// Simple way to track if currently visible
|
||||
if (this.#visible)
|
||||
this.hide();
|
||||
else
|
||||
this.show();
|
||||
}
|
||||
|
||||
getElement() {
|
||||
return this.#element;
|
||||
}
|
||||
|
||||
getVisible(){
|
||||
return this.#visible;
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import { getMissionHandler } from "..";
|
||||
import { GAME_MASTER } from "../constants/constants";
|
||||
import { getMissionHandler } from "../..";
|
||||
import { GAME_MASTER } from "../../constants/constants";
|
||||
import { UnitDatabase } from "./unitdatabase"
|
||||
|
||||
export class AircraftDatabase extends UnitDatabase {
|
||||
@ -1,5 +1,5 @@
|
||||
import { getMissionHandler} from "..";
|
||||
import { GAME_MASTER } from "../constants/constants";
|
||||
import { getMissionHandler } from "../..";
|
||||
import { GAME_MASTER } from "../../constants/constants";
|
||||
import { UnitDatabase } from "./unitdatabase"
|
||||
|
||||
export class GroundUnitDatabase extends UnitDatabase {
|
||||
@ -1,5 +1,5 @@
|
||||
import { getMissionHandler } from "..";
|
||||
import { GAME_MASTER } from "../constants/constants";
|
||||
import { getMissionHandler } from "../..";
|
||||
import { GAME_MASTER } from "../../constants/constants";
|
||||
import { UnitDatabase } from "./unitdatabase"
|
||||
|
||||
export class HelicopterDatabase extends UnitDatabase {
|
||||
@ -1,5 +1,5 @@
|
||||
import { getMissionHandler } from "..";
|
||||
import { GAME_MASTER } from "../constants/constants";
|
||||
import { getMissionHandler } from "../..";
|
||||
import { GAME_MASTER } from "../../constants/constants";
|
||||
import { UnitDatabase } from "./unitdatabase"
|
||||
|
||||
export class NavyUnitDatabase extends UnitDatabase {
|
||||
@ -1,6 +1,7 @@
|
||||
import { LatLng } from "leaflet";
|
||||
import { getMissionHandler, getUnitsManager } from "..";
|
||||
import { GAME_MASTER } from "../constants/constants";
|
||||
import { getMissionHandler, getUnitsManager } from "../..";
|
||||
import { GAME_MASTER } from "../../constants/constants";
|
||||
import { UnitBlueprint } from "../../@types/unitdatabase";
|
||||
|
||||
export class UnitDatabase {
|
||||
blueprints: { [key: string]: UnitBlueprint } = {};
|
||||
@ -170,6 +171,15 @@ export class UnitDatabase {
|
||||
return loadoutsByRole;
|
||||
}
|
||||
|
||||
/* Get the livery names for a specific unit */
|
||||
getLiveryNamesByName(name: string) {
|
||||
var liveries = this.blueprints[name].liveries;
|
||||
if (liveries !== undefined)
|
||||
return Object.values(liveries);
|
||||
else
|
||||
return [];
|
||||
}
|
||||
|
||||
/* Get the loadout content from the unit name and loadout name */
|
||||
getLoadoutByName(name: string, loadoutName: string) {
|
||||
var loadouts = this.blueprints[name].loadouts;
|
||||
@ -191,7 +201,7 @@ export class UnitDatabase {
|
||||
var row = Math.floor(idx / gridSize);
|
||||
var col = idx - row * gridSize;
|
||||
var location = new LatLng(initialPosition.lat + col * step, initialPosition.lng + row * step)
|
||||
getUnitsManager().spawnUnits(this.getCategory(), [{unitType: unitBlueprint.name, location: location, altitude: 1000, loadout: ""}]);
|
||||
getUnitsManager().spawnUnits(this.getCategory(), [{unitType: unitBlueprint.name, location: location, altitude: 1000, loadout: "", liveryID: ""}]);
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
import { Marker, LatLng, Polyline, Icon, DivIcon, CircleMarker, Map, Point } from 'leaflet';
|
||||
import { getMap, getMissionHandler, getUnitsManager, getWeaponsManager } from '..';
|
||||
import { enumToCoalition, enumToEmissioNCountermeasure, getMarkerCategoryByName, enumToROE, enumToReactionToThreat, enumToState, getUnitDatabaseByCategory, mToFt, msToKnots, rad2deg, bearing, deg2rad } from '../other/utils';
|
||||
import { enumToCoalition, enumToEmissioNCountermeasure, getMarkerCategoryByName, enumToROE, enumToReactionToThreat, enumToState, getUnitDatabaseByCategory, mToFt, msToKnots, rad2deg, bearing, deg2rad, ftToM } from '../other/utils';
|
||||
import { addDestination, attackUnit, changeAltitude, changeSpeed, createFormation as setLeader, deleteUnit, landAt, setAltitude, setReactionToThreat, setROE, setSpeed, refuel, setAdvacedOptions, followUnit, setEmissionsCountermeasures, setSpeedType, setAltitudeType, setOnOff, setFollowRoads, bombPoint, carpetBomb, bombBuilding, fireAtArea } from '../server/server';
|
||||
import { CustomMarker } from '../map/custommarker';
|
||||
import { CustomMarker } from '../map/markers/custommarker';
|
||||
import { SVGInjector } from '@tanem/svg-injector';
|
||||
import { UnitDatabase } from './unitdatabase';
|
||||
import { TargetMarker } from '../map/targetmarker';
|
||||
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_CONTACT_LINES, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, VISUAL, emissionsCountermeasures, reactionsToThreat, states } from '../constants/constants';
|
||||
import { Ammo, Contact, GeneralSettings, Offset, Radio, TACAN, ObjectIconOptions } from '../@types/unit';
|
||||
import { Ammo, Contact, GeneralSettings, Offset, Radio, TACAN, ObjectIconOptions, UnitData } from '../@types/unit';
|
||||
import { DataExtractor } from '../server/dataextractor';
|
||||
import { groundUnitDatabase } from './groundunitdatabase';
|
||||
import { navyUnitDatabase } from './navyunitdatabase';
|
||||
import { groundUnitDatabase } from './databases/groundunitdatabase';
|
||||
import { navyUnitDatabase } from './databases/navyunitdatabase';
|
||||
import { Weapon } from '../weapon/weapon';
|
||||
import { LoadoutBlueprint } from '../@types/unitdatabase';
|
||||
|
||||
var pathIcon = new Icon({
|
||||
iconUrl: '/resources/theme/images/markers/marker-icon.png',
|
||||
@ -42,9 +43,9 @@ export class Unit extends CustomMarker {
|
||||
#followRoads: boolean = false;
|
||||
#fuel: number = 0;
|
||||
#desiredSpeed: number = 0;
|
||||
#desiredSpeedType: string = "GS";
|
||||
#desiredSpeedType: string = "CAS";
|
||||
#desiredAltitude: number = 0;
|
||||
#desiredAltitudeType: string = "AGL";
|
||||
#desiredAltitudeType: string = "ASL";
|
||||
#leaderID: number = 0;
|
||||
#formationOffset: Offset = {
|
||||
x: 0,
|
||||
@ -252,7 +253,7 @@ export class Unit extends CustomMarker {
|
||||
this.#drawTarget();
|
||||
}
|
||||
|
||||
getData() {
|
||||
getData(): UnitData {
|
||||
return {
|
||||
category: this.getCategory(),
|
||||
ID: this.ID,
|
||||
@ -320,11 +321,6 @@ export class Unit extends CustomMarker {
|
||||
}
|
||||
}
|
||||
|
||||
getLiveryID(): string {
|
||||
const liveryID = this.getDatabase()?.getByName(this.getName())?.liveryID;
|
||||
return liveryID ? liveryID : "";
|
||||
}
|
||||
|
||||
setAlive(newAlive: boolean) {
|
||||
if (newAlive != this.#alive)
|
||||
document.dispatchEvent(new CustomEvent("unitDeath", { detail: this }));
|
||||
@ -410,6 +406,11 @@ export class Unit extends CustomMarker {
|
||||
return "";
|
||||
}
|
||||
|
||||
getSpawnPoints() {
|
||||
return this.getDatabase()?.getSpawnPointsByName(this.getName());
|
||||
}
|
||||
|
||||
|
||||
/********************** Icon *************************/
|
||||
createIcon(): void {
|
||||
/* Set the icon */
|
||||
@ -522,7 +523,7 @@ export class Unit extends CustomMarker {
|
||||
|
||||
/********************** Visibility *************************/
|
||||
updateVisibility() {
|
||||
const hiddenUnits = getUnitsManager().getHiddenTypes();
|
||||
const hiddenUnits = getMap().getHiddenTypes();
|
||||
var hidden = ((this.#human && hiddenUnits.includes("human")) ||
|
||||
(this.#controlled == false && hiddenUnits.includes("dcs")) ||
|
||||
(hiddenUnits.includes(this.getMarkerCategory())) ||
|
||||
@ -732,8 +733,6 @@ export class Unit extends CustomMarker {
|
||||
/***********************************************/
|
||||
onAdd(map: Map): this {
|
||||
super.onAdd(map);
|
||||
/* If this is the first time adding this unit to the map, remove the temporary marker */
|
||||
getMap().removeTemporaryMarker(new LatLng(this.#position.lat, this.#position.lng));
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -770,13 +769,13 @@ export class Unit extends CustomMarker {
|
||||
#onContextMenu(e: any) {
|
||||
var options: { [key: string]: { text: string, tooltip: string } } = {};
|
||||
const selectedUnits = getUnitsManager().getSelectedUnits();
|
||||
const selectedUnitTypes = getUnitsManager().getSelectedUnitsTypes();
|
||||
const selectedUnitTypes = getUnitsManager().getSelectedUnitsCategories();
|
||||
|
||||
options["center-map"] = { text: "Center map", tooltip: "Center the map on the unit and follow it" };
|
||||
|
||||
if (selectedUnits.length > 0 && !(selectedUnits.length == 1 && (selectedUnits.includes(this)))) {
|
||||
options["attack"] = { text: "Attack", tooltip: "Attack the unit using A/A or A/G weapons" };
|
||||
if (getUnitsManager().getSelectedUnitsTypes().length == 1 && getUnitsManager().getSelectedUnitsTypes()[0] === "Aircraft")
|
||||
if (getUnitsManager().getSelectedUnitsCategories().length == 1 && getUnitsManager().getSelectedUnitsCategories()[0] === "Aircraft")
|
||||
options["follow"] = { text: "Follow", tooltip: "Follow the unit at a user defined distance and position" };;
|
||||
}
|
||||
else if ((selectedUnits.length > 0 && (selectedUnits.includes(this))) || selectedUnits.length == 0) {
|
||||
@ -835,9 +834,31 @@ export class Unit extends CustomMarker {
|
||||
#applyFollowOptions(action: string) {
|
||||
if (action === "custom") {
|
||||
document.getElementById("custom-formation-dialog")?.classList.remove("hide");
|
||||
getMap().getUnitContextMenu().setCustomFormationCallback((offset: { x: number, y: number, z: number }) => {
|
||||
getUnitsManager().selectedUnitsFollowUnit(this.ID, offset);
|
||||
})
|
||||
document.addEventListener("applyCustomFormation", () => {
|
||||
var dialog = document.getElementById("custom-formation-dialog");
|
||||
if (dialog) {
|
||||
dialog.classList.add("hide");
|
||||
var clock = 1;
|
||||
while (clock < 8) {
|
||||
if ((<HTMLInputElement>dialog.querySelector(`#formation-${clock}`)).checked)
|
||||
break
|
||||
clock++;
|
||||
}
|
||||
var angleDeg = 360 - (clock - 1) * 45;
|
||||
var angleRad = deg2rad(angleDeg);
|
||||
var distance = ftToM(parseInt((<HTMLInputElement>dialog.querySelector(`#distance`)?.querySelector("input")).value));
|
||||
var upDown = ftToM(parseInt((<HTMLInputElement>dialog.querySelector(`#up-down`)?.querySelector("input")).value));
|
||||
|
||||
// X: front-rear, positive front
|
||||
// Y: top-bottom, positive top
|
||||
// Z: left-right, positive right
|
||||
var x = distance * Math.cos(angleRad);
|
||||
var y = upDown;
|
||||
var z = distance * Math.sin(angleRad);
|
||||
|
||||
getUnitsManager().selectedUnitsFollowUnit(this.ID, { "x": x, "y": y, "z": z });
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
getUnitsManager().selectedUnitsFollowUnit(this.ID, undefined, action);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { LatLng, DivIcon, Map } from 'leaflet';
|
||||
import { getMap, getMissionHandler, getUnitsManager } from '..';
|
||||
import { enumToCoalition, mToFt, msToKnots, rad2deg } from '../other/utils';
|
||||
import { CustomMarker } from '../map/custommarker';
|
||||
import { CustomMarker } from '../map/markers/custommarker';
|
||||
import { SVGInjector } from '@tanem/svg-injector';
|
||||
import { DLINK, DataIndexes, GAME_MASTER, IRST, OPTIC, RADAR, VISUAL } from '../constants/constants';
|
||||
import { ObjectIconOptions } from '../@types/unit';
|
||||
@ -166,7 +166,7 @@ export class Weapon extends CustomMarker {
|
||||
|
||||
/********************** Visibility *************************/
|
||||
updateVisibility() {
|
||||
const hiddenUnits = getUnitsManager().getHiddenTypes();
|
||||
const hiddenUnits = getMap().getHiddenTypes();
|
||||
var hidden = (hiddenUnits.includes(this.getMarkerCategory())) ||
|
||||
(hiddenUnits.includes(this.#coalition)) ||
|
||||
(!this.belongsToCommandedCoalition() && this.#detectionMethods.length == 0);
|
||||
@ -214,8 +214,6 @@ export class Weapon extends CustomMarker {
|
||||
/***********************************************/
|
||||
onAdd(map: Map): this {
|
||||
super.onAdd(map);
|
||||
/* If this is the first time adding this unit to the map, remove the temporary marker */
|
||||
getMap().removeTemporaryMarker(new LatLng(this.#position.lat, this.#position.lng));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import { DataIndexes, GAME_MASTER } from "../constants/constants";
|
||||
import { DataExtractor } from "../server/dataextractor";
|
||||
import { Contact } from "../@types/unit";
|
||||
|
||||
/** The WeaponsManager handles the creation and update of weapons. Data is strictly updated by the server ONLY. */
|
||||
export class WeaponsManager {
|
||||
#weapons: { [ID: number]: Weapon };
|
||||
|
||||
@ -13,10 +14,19 @@ export class WeaponsManager {
|
||||
document.addEventListener("commandModeOptionsChanged", () => {Object.values(this.#weapons).forEach((weapon: Weapon) => weapon.updateVisibility())});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns All the existing weapons, both active and destroyed
|
||||
*/
|
||||
getWeapons() {
|
||||
return this.#weapons;
|
||||
}
|
||||
|
||||
/** Get a weapon by ID
|
||||
*
|
||||
* @param ID ID of the weapon
|
||||
* @returns Weapon object, or null if input ID does not exist
|
||||
*/
|
||||
getWeaponByID(ID: number) {
|
||||
if (ID in this.#weapons)
|
||||
return this.#weapons[ID];
|
||||
@ -24,6 +34,11 @@ export class WeaponsManager {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Add a new weapon to the manager
|
||||
*
|
||||
* @param ID ID of the new weapon
|
||||
* @param category Either "Missile" or "Bomb". Determines what class will be used to create the new unit accordingly.
|
||||
*/
|
||||
addWeapon(ID: number, category: string) {
|
||||
if (category){
|
||||
/* The name of the weapon category is exactly the same as the constructor name */
|
||||
@ -34,11 +49,25 @@ export class WeaponsManager {
|
||||
}
|
||||
}
|
||||
|
||||
/** Update the data of all the weapons. The data is directly decoded from the binary buffer received from the REST Server. This is necessary for performance and bandwidth reasons.
|
||||
*
|
||||
* @param buffer The arraybuffer, encoded according to the ICD defined in: TODO Add reference to ICD
|
||||
* @returns The decoded updateTime of the data update.
|
||||
*/
|
||||
update(buffer: ArrayBuffer) {
|
||||
/* Extract the data from the arraybuffer. Since data is encoded dynamically (not all data is always present, but rather only the data that was actually updated since the last request).
|
||||
No a prori casting can be performed. On the contrary, the array is decoded incrementally, depending on the DataIndexes of the data. The actual data decoding is performed by the Weapon class directly.
|
||||
Every time a piece of data is decoded the decoder seeker is incremented. */
|
||||
var dataExtractor = new DataExtractor(buffer);
|
||||
|
||||
var updateTime = Number(dataExtractor.extractUInt64());
|
||||
|
||||
/* Run until all data is extracted or an error occurs */
|
||||
while (dataExtractor.getSeekPosition() < buffer.byteLength) {
|
||||
/* Extract the weapon ID */
|
||||
const ID = dataExtractor.extractUInt32();
|
||||
|
||||
/* If the ID of the weapon does not yet exist, create the weapon, if the category is known. If it isn't, some data must have been lost and we need to wait for another update */
|
||||
if (!(ID in this.#weapons)) {
|
||||
const datumIndex = dataExtractor.extractUInt8();
|
||||
if (datumIndex == DataIndexes.category) {
|
||||
@ -50,11 +79,18 @@ export class WeaponsManager {
|
||||
return updateTime;
|
||||
}
|
||||
}
|
||||
/* Update the data of the weapon */
|
||||
this.#weapons[ID]?.setData(dataExtractor);
|
||||
}
|
||||
return updateTime;
|
||||
}
|
||||
|
||||
/** For a given weapon, it returns if and how it is being detected by other units. NOTE: this function will return how a weapon is being detected, i.e. how other units are detecting it. It will not return
|
||||
* what the weapon is detecting (mostly because weapons can't detect units).
|
||||
*
|
||||
* @param weapon The unit of which to retrieve the "detected" methods.
|
||||
* @returns Array of detection methods
|
||||
*/
|
||||
getWeaponDetectedMethods(weapon: Weapon) {
|
||||
var detectionMethods: number[] = [];
|
||||
var units = getUnitsManager().getUnits();
|
||||
|
||||
22
client/views/contextmenus/airbase.ejs
Normal file
@ -0,0 +1,22 @@
|
||||
<div id="airbase-contextmenu" class="ol-panel" oncontextmenu="return false;">
|
||||
<h3 id="airbase-name"></h3>
|
||||
|
||||
<dl id="airbase-chart-data" class="ol-data-grid">
|
||||
<dt>ICAO</dt>
|
||||
<dd data-point="ICAO"></dd>
|
||||
<dt>Coalition</dt>
|
||||
<dd data-point="coalition"></dd>
|
||||
<dt>Elevation</dt>
|
||||
<dd><span data-point="elevation"></span>ft</dd>
|
||||
<dt>TACAN</dt>
|
||||
<dd data-point="TACAN"></dd>
|
||||
</dl>
|
||||
|
||||
<h4>Runways</h4>
|
||||
<div id="airbase-runways">
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
<button id="spawn-airbase-aircraft-button" data-coalition="blue" title="Spawn aircraft" data-on-click="contextMenuSpawnAirbase" class="deploy-unit-button">Spawn</button>
|
||||
<button id="land-here-button" title="Land here" data-on-click="contextMenuLandAirbase" class="hide">Land here</button>
|
||||
</div>
|
||||
15
client/views/contextmenus/airbasespawn.ejs
Normal file
@ -0,0 +1,15 @@
|
||||
<div id="airbase-spawn-contextmenu" class="ol-context-menu" oncontextmenu="return false;">
|
||||
<div id="airbase-active-coalition-label" data-coalition="blue"></div>
|
||||
<div class="upper-bar ol-panel">
|
||||
<button data-coalition="blue" id="airbase-aircraft-spawn-button" title="Spawn aircraft" data-on-click="mapContextMenuShow"
|
||||
data-on-click-params='{ "type": "aircraft" }' class="ol-contexmenu-button"><img src="/resources/theme/images/buttons/spawn/aircraft.svg" inject-svg></button>
|
||||
<button data-coalition="blue" id="airbase-helicopter-spawn-button" title="Spawn helicopter" data-on-click="mapContextMenuShow"
|
||||
data-on-click-params='{ "type": "helicopter" }' class="ol-contexmenu-button"><img src="/resources/theme/images/buttons/spawn/helicopter.svg" inject-svg></button>
|
||||
</div>
|
||||
<div id="airbase-aircraft-spawn-menu" class="ol-contexmenu-panel ol-panel hide">
|
||||
<!-- Here the aircraft spawn menu will be shown -->
|
||||
</div>
|
||||
<div id="airbase-helicopter-spawn-menu" class="ol-contexmenu-panel ol-panel hide">
|
||||
<!-- Here the helicopter spawn menu will be shown -->
|
||||
</div>
|
||||
</div>
|
||||
61
client/views/contextmenus/coalitionarea.ejs
Normal file
@ -0,0 +1,61 @@
|
||||
<div id="coalition-area-contextmenu" class="ol-context-menu" oncontextmenu="return false;">
|
||||
<div id="area-coalition-label" data-coalition="blue"></div>
|
||||
<div class="upper-bar ol-panel">
|
||||
<div id="coalition-area-switch" class="ol-switch ol-coalition-switch"></div>
|
||||
<button data-coalition="blue" id="iads-button" title="Create Integrated Air Defense System" data-on-click="coalitionAreaContextMenuShow"
|
||||
data-on-click-params='{ "type": "iads" }' class="ol-contexmenu-button"><img src="/resources/theme/images/buttons/spawn/sam.svg" inject-svg></button>
|
||||
<!--<button data-coalition="blue" id="cap-button" title="Create Combat Air Patrols" data-on-click="coalitionAreaContextMenuShow"
|
||||
data-on-click-params='{ "type": "cap" }' class="ol-contexmenu-button"></button>-->
|
||||
<button data-coalition="blue" id="coalitionarea-back-button" title="Bring area to back" data-on-click="coalitionAreaBringToBack"
|
||||
class="ol-contexmenu-button"><img src="/resources/theme/images/buttons/other/back.svg" inject-svg></button>
|
||||
<button data-coalition="blue" id="coalitionarea-delete-button" title="Delete area" data-on-click="coalitionAreaDelete"
|
||||
class="ol-contexmenu-button"><img src="/resources/theme/images/buttons/other/delete.svg" inject-svg></button>
|
||||
</div>
|
||||
<div id="iads-menu" class="ol-panel ol-contexmenu-panel hide">
|
||||
<div id="iads-units-type-options" class="ol-select">
|
||||
<div class="ol-select-value">Unit types</div>
|
||||
<div class="ol-select-options">
|
||||
<!-- This is where all the iads unit types checkboxes will be shown-->
|
||||
</div>
|
||||
</div>
|
||||
<div class="ol-select-container">
|
||||
<div id="iads-era-options" class="ol-select">
|
||||
<div class="ol-select-value">Units eras</div>
|
||||
<div class="ol-select-options">
|
||||
<!-- This is where all the iads unit era buttons will be shown-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ol-select-container">
|
||||
<div id="iads-range-options" class="ol-select">
|
||||
<div class="ol-select-value">Units ranges</div>
|
||||
<div class="ol-select-options">
|
||||
<!-- This is where all the iads unit range buttons will be shown-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
<div id="coalition-units-checkbox" class="ol-checkbox">
|
||||
<label title="Use coalition specific units only (e.g. Patriot sites for blue coalition, SA-2s for red coalition)">
|
||||
<input type="checkbox"/>
|
||||
Coalition specific units
|
||||
</label>
|
||||
</div>
|
||||
-->
|
||||
|
||||
<div id="iads-density-slider" class="ol-slider-container">
|
||||
<dl class="ol-data-grid">
|
||||
<dt> IADS density </dt> <dd> <div class="ol-slider-value"></div> </dd>
|
||||
</dl>
|
||||
<input title="An high density value will cause more units to be deployed" type="range" min="0" max="100" value="0" class="ol-slider">
|
||||
</div>
|
||||
<div id="iads-distribution-slider" class="ol-slider-container">
|
||||
<dl class="ol-data-grid">
|
||||
<dt> IADS distribution </dt> <dd> <div class="ol-slider-value"></div> </dd>
|
||||
</dl>
|
||||
<input title="If distrubution is low units will be concentrated around towns, otherwise they will spread around the map more evenly" type="range" min="0" max="100" value="0" class="ol-slider">
|
||||
</div>
|
||||
<button class="create-iads-button" title="" data-coalition="blue" data-on-click="contextMenuCreateIads">Add units to IADS</button>
|
||||
</div>
|
||||
</div>
|
||||