6
client/.vscode/launch.json
vendored
@ -9,7 +9,11 @@
|
||||
"request": "launch",
|
||||
"name": "Launch Chrome against localhost",
|
||||
"url": "http://localhost:3000",
|
||||
"webRoot": "${workspaceFolder}\\.."
|
||||
"webRoot": "${workspaceFolder}/public/",
|
||||
"sourceMapPathOverrides": {
|
||||
"src/*": "${workspaceFolder}/src/*"
|
||||
},
|
||||
"preLaunchTask": "server"
|
||||
}
|
||||
]
|
||||
}
|
||||
52
client/.vscode/tasks.json
vendored
@ -1,41 +1,13 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "start",
|
||||
"isBackground": true,
|
||||
"presentation": {
|
||||
"focus": true,
|
||||
"panel": "dedicated"
|
||||
},
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"problemMatcher": {
|
||||
"owner": "typescript",
|
||||
"source": "ts",
|
||||
"applyTo": "closedDocuments",
|
||||
"fileLocation": [
|
||||
"relative",
|
||||
"${cwd}"
|
||||
],
|
||||
"pattern": "$tsc",
|
||||
"background": {
|
||||
"activeOnStart": true,
|
||||
"beginsPattern": {
|
||||
"regexp": "(.*?)"
|
||||
},
|
||||
"endsPattern": {
|
||||
"regexp": "Compiled |Failed to compile."
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "server",
|
||||
"type": "shell",
|
||||
"command": "npm run start",
|
||||
"isBackground": true
|
||||
}
|
||||
]
|
||||
}
|
||||
11
client/TODO.txt
Normal file
@ -0,0 +1,11 @@
|
||||
Change cursor when in moving mode
|
||||
Show airfields and enable airfield spawn
|
||||
RTB
|
||||
tanker
|
||||
scenario dropdown
|
||||
explosion
|
||||
wrong name for ground units
|
||||
ground units don't move
|
||||
improve map zIndex
|
||||
fuel is wrong (either 0 or 1, its is casting it to int somewhere)
|
||||
weapons should not be selected
|
||||
@ -17,4 +17,6 @@ app.use(express.static(path.join(__dirname, 'public')));
|
||||
app.use('/', indexRouter);
|
||||
app.use('/users', usersRouter);
|
||||
|
||||
app.set('view engine', 'ejs');
|
||||
|
||||
module.exports = app;
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
start cmd /k "npm run start"
|
||||
start cmd /k "watchify .\index.ts --debug -p [ tsify --noImplicitAny ] -o .\public\javascripts\bundle.js"
|
||||
start cmd /k "watchify .\src\index.ts --debug -p [ tsify --noImplicitAny ] -o .\public\javascripts\bundle.js"
|
||||
|
||||
@ -1 +0,0 @@
|
||||
console.log("Hellorld!!!")
|
||||
821
client/package-lock.json
generated
@ -1,23 +1,31 @@
|
||||
{
|
||||
"name": "client",
|
||||
"name": "DCS Olympus",
|
||||
"node-main": "./bin/www",
|
||||
"main": "http://localhost:3000",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "node ./bin/www"
|
||||
"start": "npm run copy & concurrently --kill-others \"npm run watch\" \"nodemon ./bin/www\"",
|
||||
"copy": "copy .\\node_modules\\leaflet\\dist\\leaflet.css .\\public\\stylesheets\\leaflet.css",
|
||||
"watch": "watchify .\\src\\index.ts --debug -p [ tsify --noImplicitAny ] -o .\\public\\javascripts\\bundle.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/geojson": "^7946.0.10",
|
||||
"@types/leaflet": "^1.9.0",
|
||||
"cookie-parser": "~1.4.4",
|
||||
"debug": "~2.6.9",
|
||||
"ejs": "^3.1.8",
|
||||
"express": "~4.16.1",
|
||||
"leaflet": "^1.9.3",
|
||||
"morgan": "~1.9.1"
|
||||
"milsymbol": "^2.0.0",
|
||||
"morgan": "~1.9.1",
|
||||
"save": "^2.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"browserify": "^17.0.0",
|
||||
"concurrently": "^7.6.0",
|
||||
"tsify": "^5.0.4",
|
||||
"typescript": "^4.9.4",
|
||||
"watchify": "^4.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.5 KiB |
55
client/public/images/buttons/ai-full.svg
Normal file
@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 448 512"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="ai-full.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="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.1490485"
|
||||
inkscape:cx="233.6716"
|
||||
inkscape:cy="300.68357"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
|
||||
<rect
|
||||
style="stroke-width:2.5;paint-order:stroke fill markers"
|
||||
id="rect851"
|
||||
width="398.59067"
|
||||
height="44.384548"
|
||||
x="25.146276"
|
||||
y="37.924564"
|
||||
rx="20"
|
||||
ry="20" />
|
||||
<rect
|
||||
style="stroke-width:2.5;paint-order:stroke fill markers"
|
||||
id="rect851-9"
|
||||
width="398.59067"
|
||||
height="44.384548"
|
||||
x="26.978836"
|
||||
y="439.85379"
|
||||
rx="20"
|
||||
ry="20" />
|
||||
<path
|
||||
d="m 262.05206,159.27038 v 132.1849 H 73.216485 V 159.27038 Z M 73.216485,121.50326 c -20.83092,0 -37.76711,16.93619 -37.76711,37.76712 v 132.1849 c 0,20.83093 16.93619,37.76712 37.76711,37.76712 h 69.220035 l -6.31419,18.88355 H 92.100045 c -10.44497,0 -18.88356,8.43859 -18.88356,18.88356 0,10.44497 8.43859,18.88356 18.88356,18.88356 H 243.1685 c 10.44497,0 18.88356,-8.43859 18.88356,-18.88356 0,-10.44497 -8.43859,-18.88356 -18.88356,-18.88356 h -44.0813 l -6.31419,-18.88355 h 69.27905 c 20.83092,0 37.76711,-16.93619 37.76711,-37.76712 v -132.1849 c 0,-20.83093 -16.93619,-37.76712 -37.76711,-37.76712 z m 273.811585,0 c -15.63795,0 -28.32534,12.68739 -28.32534,28.32534 v 207.71913 c 0,15.63795 12.68739,28.32534 28.32534,28.32534 h 37.76711 c 15.63795,0 28.32534,-12.68739 28.32534,-28.32534 V 149.8286 c 0,-15.63795 -12.68739,-28.32534 -28.32534,-28.32534 z m 9.44178,37.76712 h 18.88355 c 5.19298,0 9.44178,4.2488 9.44178,9.44178 0,5.19298 -4.2488,9.44178 -9.44178,9.44178 h -18.88355 c -5.19298,0 -9.44178,-4.2488 -9.44178,-9.44178 0,-5.19298 4.2488,-9.44178 9.44178,-9.44178 z m -9.44178,47.20889 c 0,-5.19298 4.2488,-9.44178 9.44178,-9.44178 h 18.88355 c 5.19298,0 9.44178,4.2488 9.44178,9.44178 0,5.19298 -4.2488,9.44178 -9.44178,9.44178 h -18.88355 c -5.19298,0 -9.44178,-4.2488 -9.44178,-9.44178 z m 18.88356,132.18491 c -10.44497,0 -18.88356,-8.43859 -18.88356,-18.88356 0,-10.44497 8.43859,-18.88356 18.88356,-18.88356 10.44496,0 18.88355,8.43859 18.88355,18.88356 0,10.44497 -8.43859,18.88356 -18.88355,18.88356 z"
|
||||
id="path2"
|
||||
style="stroke-width:0.590111" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
47
client/public/images/buttons/ai-hidden.svg
Normal file
@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 448 512"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="ai-hidden.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="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.1490485"
|
||||
inkscape:cx="233.6716"
|
||||
inkscape:cy="300.68357"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
|
||||
<path
|
||||
d="m 262.05206,159.27038 v 132.1849 H 73.216485 V 159.27038 Z M 73.216485,121.50326 c -20.83092,0 -37.76711,16.93619 -37.76711,37.76712 v 132.1849 c 0,20.83093 16.93619,37.76712 37.76711,37.76712 h 69.220035 l -6.31419,18.88355 H 92.100045 c -10.44497,0 -18.88356,8.43859 -18.88356,18.88356 0,10.44497 8.43859,18.88356 18.88356,18.88356 H 243.1685 c 10.44497,0 18.88356,-8.43859 18.88356,-18.88356 0,-10.44497 -8.43859,-18.88356 -18.88356,-18.88356 h -44.0813 l -6.31419,-18.88355 h 69.27905 c 20.83092,0 37.76711,-16.93619 37.76711,-37.76712 v -132.1849 c 0,-20.83093 -16.93619,-37.76712 -37.76711,-37.76712 z m 273.811585,0 c -15.63795,0 -28.32534,12.68739 -28.32534,28.32534 v 207.71913 c 0,15.63795 12.68739,28.32534 28.32534,28.32534 h 37.76711 c 15.63795,0 28.32534,-12.68739 28.32534,-28.32534 V 149.8286 c 0,-15.63795 -12.68739,-28.32534 -28.32534,-28.32534 z m 9.44178,37.76712 h 18.88355 c 5.19298,0 9.44178,4.2488 9.44178,9.44178 0,5.19298 -4.2488,9.44178 -9.44178,9.44178 h -18.88355 c -5.19298,0 -9.44178,-4.2488 -9.44178,-9.44178 0,-5.19298 4.2488,-9.44178 9.44178,-9.44178 z m -9.44178,47.20889 c 0,-5.19298 4.2488,-9.44178 9.44178,-9.44178 h 18.88355 c 5.19298,0 9.44178,4.2488 9.44178,9.44178 0,5.19298 -4.2488,9.44178 -9.44178,9.44178 h -18.88355 c -5.19298,0 -9.44178,-4.2488 -9.44178,-9.44178 z m 18.88356,132.18491 c -10.44497,0 -18.88356,-8.43859 -18.88356,-18.88356 0,-10.44497 8.43859,-18.88356 18.88356,-18.88356 10.44496,0 18.88355,8.43859 18.88355,18.88356 0,10.44497 -8.43859,18.88356 -18.88355,18.88356 z"
|
||||
id="path2"
|
||||
style="stroke-width:0.590111" />
|
||||
<rect
|
||||
style="stroke:#fffffc;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="rect851-9"
|
||||
width="398.59067"
|
||||
height="44.384548"
|
||||
x="-224.52609"
|
||||
y="315.65387"
|
||||
rx="20"
|
||||
ry="20"
|
||||
transform="rotate(-45)" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
37
client/public/images/buttons/ai-none.svg
Normal file
@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 448 512"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="ai-none.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="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.1490485"
|
||||
inkscape:cx="233.6716"
|
||||
inkscape:cy="300.68357"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
|
||||
<path
|
||||
d="m 262.05206,159.27038 v 132.1849 H 73.216485 V 159.27038 Z M 73.216485,121.50326 c -20.83092,0 -37.76711,16.93619 -37.76711,37.76712 v 132.1849 c 0,20.83093 16.93619,37.76712 37.76711,37.76712 h 69.220035 l -6.31419,18.88355 H 92.100045 c -10.44497,0 -18.88356,8.43859 -18.88356,18.88356 0,10.44497 8.43859,18.88356 18.88356,18.88356 H 243.1685 c 10.44497,0 18.88356,-8.43859 18.88356,-18.88356 0,-10.44497 -8.43859,-18.88356 -18.88356,-18.88356 h -44.0813 l -6.31419,-18.88355 h 69.27905 c 20.83092,0 37.76711,-16.93619 37.76711,-37.76712 v -132.1849 c 0,-20.83093 -16.93619,-37.76712 -37.76711,-37.76712 z m 273.811585,0 c -15.63795,0 -28.32534,12.68739 -28.32534,28.32534 v 207.71913 c 0,15.63795 12.68739,28.32534 28.32534,28.32534 h 37.76711 c 15.63795,0 28.32534,-12.68739 28.32534,-28.32534 V 149.8286 c 0,-15.63795 -12.68739,-28.32534 -28.32534,-28.32534 z m 9.44178,37.76712 h 18.88355 c 5.19298,0 9.44178,4.2488 9.44178,9.44178 0,5.19298 -4.2488,9.44178 -9.44178,9.44178 h -18.88355 c -5.19298,0 -9.44178,-4.2488 -9.44178,-9.44178 0,-5.19298 4.2488,-9.44178 9.44178,-9.44178 z m -9.44178,47.20889 c 0,-5.19298 4.2488,-9.44178 9.44178,-9.44178 h 18.88355 c 5.19298,0 9.44178,4.2488 9.44178,9.44178 0,5.19298 -4.2488,9.44178 -9.44178,9.44178 h -18.88355 c -5.19298,0 -9.44178,-4.2488 -9.44178,-9.44178 z m 18.88356,132.18491 c -10.44497,0 -18.88356,-8.43859 -18.88356,-18.88356 0,-10.44497 8.43859,-18.88356 18.88356,-18.88356 10.44496,0 18.88355,8.43859 18.88355,18.88356 0,10.44497 -8.43859,18.88356 -18.88355,18.88356 z"
|
||||
id="path2"
|
||||
style="stroke-width:0.590111" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
46
client/public/images/buttons/ai-partial.svg
Normal file
@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 448 512"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="a-ipartial.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="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.1490485"
|
||||
inkscape:cx="233.6716"
|
||||
inkscape:cy="300.68357"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
|
||||
<rect
|
||||
style="stroke-width:2.5;paint-order:stroke fill markers"
|
||||
id="rect851-9"
|
||||
width="398.59067"
|
||||
height="44.384548"
|
||||
x="26.978836"
|
||||
y="439.85379"
|
||||
rx="20"
|
||||
ry="20" />
|
||||
<path
|
||||
d="m 262.05206,159.27038 v 132.1849 H 73.216485 V 159.27038 Z M 73.216485,121.50326 c -20.83092,0 -37.76711,16.93619 -37.76711,37.76712 v 132.1849 c 0,20.83093 16.93619,37.76712 37.76711,37.76712 h 69.220035 l -6.31419,18.88355 H 92.100045 c -10.44497,0 -18.88356,8.43859 -18.88356,18.88356 0,10.44497 8.43859,18.88356 18.88356,18.88356 H 243.1685 c 10.44497,0 18.88356,-8.43859 18.88356,-18.88356 0,-10.44497 -8.43859,-18.88356 -18.88356,-18.88356 h -44.0813 l -6.31419,-18.88355 h 69.27905 c 20.83092,0 37.76711,-16.93619 37.76711,-37.76712 v -132.1849 c 0,-20.83093 -16.93619,-37.76712 -37.76711,-37.76712 z m 273.811585,0 c -15.63795,0 -28.32534,12.68739 -28.32534,28.32534 v 207.71913 c 0,15.63795 12.68739,28.32534 28.32534,28.32534 h 37.76711 c 15.63795,0 28.32534,-12.68739 28.32534,-28.32534 V 149.8286 c 0,-15.63795 -12.68739,-28.32534 -28.32534,-28.32534 z m 9.44178,37.76712 h 18.88355 c 5.19298,0 9.44178,4.2488 9.44178,9.44178 0,5.19298 -4.2488,9.44178 -9.44178,9.44178 h -18.88355 c -5.19298,0 -9.44178,-4.2488 -9.44178,-9.44178 0,-5.19298 4.2488,-9.44178 9.44178,-9.44178 z m -9.44178,47.20889 c 0,-5.19298 4.2488,-9.44178 9.44178,-9.44178 h 18.88355 c 5.19298,0 9.44178,4.2488 9.44178,9.44178 0,5.19298 -4.2488,9.44178 -9.44178,9.44178 h -18.88355 c -5.19298,0 -9.44178,-4.2488 -9.44178,-9.44178 z m 18.88356,132.18491 c -10.44497,0 -18.88356,-8.43859 -18.88356,-18.88356 0,-10.44497 8.43859,-18.88356 18.88356,-18.88356 10.44496,0 18.88355,8.43859 18.88355,18.88356 0,10.44497 -8.43859,18.88356 -18.88355,18.88356 z"
|
||||
id="path2"
|
||||
style="stroke-width:0.590111" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
1
client/public/images/buttons/ai.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M384 96V320H64L64 96H384zM64 32C28.7 32 0 60.7 0 96V320c0 35.3 28.7 64 64 64H181.3l-10.7 32H96c-17.7 0-32 14.3-32 32s14.3 32 32 32H352c17.7 0 32-14.3 32-32s-14.3-32-32-32H277.3l-10.7-32H384c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H64zm464 0c-26.5 0-48 21.5-48 48V432c0 26.5 21.5 48 48 48h64c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48H528zm16 64h32c8.8 0 16 7.2 16 16s-7.2 16-16 16H544c-8.8 0-16-7.2-16-16s7.2-16 16-16zm-16 80c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16s-7.2 16-16 16H544c-8.8 0-16-7.2-16-16zm32 224c-17.7 0-32-14.3-32-32s14.3-32 32-32s32 14.3 32 32s-14.3 32-32 32z"/></svg>
|
||||
|
After Width: | Height: | Size: 832 B |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
36
client/public/images/buttons/climb.svg
Normal file
@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 640 512"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="climb.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="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.8125"
|
||||
inkscape:cx="465.84615"
|
||||
inkscape:cy="22.769231"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) -->
|
||||
<path
|
||||
d="m 80.55,407.27 c 6.28,6.84 15.1,10.72 24.33,10.71 l 130.54,-0.18 a 65.62,65.62 0 0 0 29.64,-7.12 L 556.02,263.03 c 26.74,-13.57 50.71,-32.94 67.02,-58.31 18.31,-28.48 20.3,-49.09 13.07,-63.65 -7.21,-14.57 -24.74,-25.27 -58.25,-27.45 -29.85,-1.94 -59.54,5.92 -86.28,19.48 l -98.51,49.99 -218.7,-82.06 a 17.799,17.799 0 0 0 -18,-1.11 l -65.75,33.37 c -10.67,5.41 -13.25,19.65 -5.17,28.53 l 156.22,98.1 -103.21,52.38 -72.35,-36.47 a 17.804,17.804 0 0 0 -16.07,0.02 L 9.91,296.22 c -10.44,5.3 -13.19,19.12 -5.57,28.08 z"
|
||||
id="path846" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
47
client/public/images/buttons/dead-hidden.svg
Normal file
@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 512 512"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="dead-hidden.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="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.8125"
|
||||
inkscape:cx="101.53846"
|
||||
inkscape:cy="209.84615"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
|
||||
<path
|
||||
d="m 336,333.63317 c 29.25,-22.32836 48,-56.55432 48,-95.01779 0,-67.2024 -57.3,-121.6923 -128,-121.6923 -70.7,0 -128,54.4899 -128,121.6923 0,38.40914 18.75,72.68943 48,95.01779 0,0.21731 0,0.38029 0,0.5976 V 369 c 0,14.39663 10.75,26.07692 24,26.07692 h 24 V 369 c 0,-4.78077 3.6,-8.69231 8,-8.69231 4.4,0 8,3.91154 8,8.69231 v 26.07692 h 32 V 369 c 0,-4.78077 3.6,-8.69231 8,-8.69231 4.4,0 8,3.91154 8,8.69231 v 26.07692 h 24 c 13.25,0 24,-11.68029 24,-26.07692 v -34.76923 c 0,-0.21731 0,-0.38029 0,-0.5976 z M 240,256 c 0,19.1774 -14.35,34.76923 -32,34.76923 -17.65,0 -32,-15.59183 -32,-34.76923 0,-19.1774 14.35,-34.76923 32,-34.76923 17.65,0 32,15.59183 32,34.76923 z m 64,34.76923 c -17.65,0 -32,-15.59183 -32,-34.76923 0,-19.1774 14.35,-34.76923 32,-34.76923 17.65,0 32,15.59183 32,34.76923 0,19.1774 -14.35,34.76923 -32,34.76923 z"
|
||||
id="path2"
|
||||
style="stroke-width:0.521186" />
|
||||
<rect
|
||||
style="stroke:#fffffc;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="rect851-9"
|
||||
width="398.59067"
|
||||
height="44.384548"
|
||||
x="-197.55475"
|
||||
y="339.84641"
|
||||
rx="20"
|
||||
ry="20"
|
||||
transform="rotate(-45)" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
37
client/public/images/buttons/dead.svg
Normal file
@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 512 512"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="dead.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="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.8125"
|
||||
inkscape:cx="101.53846"
|
||||
inkscape:cy="209.84615"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
|
||||
<path
|
||||
d="m 336,333.63317 c 29.25,-22.32836 48,-56.55432 48,-95.01779 0,-67.2024 -57.3,-121.6923 -128,-121.6923 -70.7,0 -128,54.4899 -128,121.6923 0,38.40914 18.75,72.68943 48,95.01779 0,0.21731 0,0.38029 0,0.5976 V 369 c 0,14.39663 10.75,26.07692 24,26.07692 h 24 V 369 c 0,-4.78077 3.6,-8.69231 8,-8.69231 4.4,0 8,3.91154 8,8.69231 v 26.07692 h 32 V 369 c 0,-4.78077 3.6,-8.69231 8,-8.69231 4.4,0 8,3.91154 8,8.69231 v 26.07692 h 24 c 13.25,0 24,-11.68029 24,-26.07692 v -34.76923 c 0,-0.21731 0,-0.38029 0,-0.5976 z M 240,256 c 0,19.1774 -14.35,34.76923 -32,34.76923 -17.65,0 -32,-15.59183 -32,-34.76923 0,-19.1774 14.35,-34.76923 32,-34.76923 17.65,0 32,15.59183 32,34.76923 z m 64,34.76923 c -17.65,0 -32,-15.59183 -32,-34.76923 0,-19.1774 14.35,-34.76923 32,-34.76923 17.65,0 32,15.59183 32,34.76923 0,19.1774 -14.35,34.76923 -32,34.76923 z"
|
||||
id="path2"
|
||||
style="stroke-width:0.521186" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
36
client/public/images/buttons/descend.svg
Normal file
@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 640 512"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="descend.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="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.8125"
|
||||
inkscape:cx="313.23077"
|
||||
inkscape:cy="312"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) -->
|
||||
<path
|
||||
d="m 52.81,265.66 88.74,80 a 62.607,62.607 0 0 0 25.47,13.93 l 287.6,78.35 c 26.48,7.21 54.56,8.72 81,1.36 29.67,-8.27 43.44,-21.21 47.25,-35.71 3.83,-14.5 -1.73,-32.71 -23.37,-54.96 -19.28,-19.82 -44.35,-32.79 -70.83,-40 L 391.16,282.07 290.8,90.22 C 289.29,84.41 284.85,79.87 279.14,78.31 L 214.05,60.58 c -10.56,-2.88 -20.9,5.32 -20.71,16.44 l 47.92,164.21 -102.2,-27.84 -27.59,-67.88 c -1.93,-4.89 -6.01,-8.57 -11.02,-9.93 L 60.72,124.75 c -10.34,-2.82 -20.53,5 -20.72,15.88 l 0.23,101.78 c 0.19,8.91 6.03,17.34 12.58,23.25 z"
|
||||
id="path827" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
1
client/public/images/buttons/fast.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M224.3 273l-136 136c-9.4 9.4-24.6 9.4-33.9 0l-22.6-22.6c-9.4-9.4-9.4-24.6 0-33.9l96.4-96.4-96.4-96.4c-9.4-9.4-9.4-24.6 0-33.9L54.3 103c9.4-9.4 24.6-9.4 33.9 0l136 136c9.5 9.4 9.5 24.6.1 34zm192-34l-136-136c-9.4-9.4-24.6-9.4-33.9 0l-22.6 22.6c-9.4 9.4-9.4 24.6 0 33.9l96.4 96.4-96.4 96.4c-9.4 9.4-9.4 24.6 0 33.9l22.6 22.6c9.4 9.4 24.6 9.4 33.9 0l136-136c9.4-9.2 9.4-24.4 0-33.8z"/></svg>
|
||||
|
After Width: | Height: | Size: 595 B |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
1
client/public/images/buttons/slow.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M224.3 273l-136 136c-9.4 9.4-24.6 9.4-33.9 0l-22.6-22.6c-9.4-9.4-9.4-24.6 0-33.9l96.4-96.4-96.4-96.4c-9.4-9.4-9.4-24.6 0-33.9L54.3 103c9.4-9.4 24.6-9.4 33.9 0l136 136c9.5 9.4 9.5 24.6.1 34z"/></svg>
|
||||
|
After Width: | Height: | Size: 406 B |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 7.0 KiB |
55
client/public/images/buttons/user-full.svg
Normal file
@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 448 512"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="user-full.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="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.1490485"
|
||||
inkscape:cx="233.67159"
|
||||
inkscape:cy="300.68356"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
|
||||
<path
|
||||
d="m 223.51472,258.30254 c 41.41184,0 74.97476,-33.56293 74.97476,-74.97477 0,-41.41184 -33.56292,-74.97476 -74.97476,-74.97476 -41.41185,0 -74.97476,33.56292 -74.97476,74.97476 0,41.41184 33.56291,74.97477 74.97476,74.97477 z m -26.76834,28.11553 c -57.69542,0 -104.437495,46.74208 -104.437495,104.43749 0,9.60615 7.790345,17.39649 17.396485,17.39649 h 227.61869 c 9.60614,0 17.39649,-7.79034 17.39649,-17.39649 0,-57.69541 -46.74208,-104.43749 -104.4375,-104.43749 z"
|
||||
id="path2"
|
||||
style="stroke-width:0.58574" />
|
||||
<rect
|
||||
style="stroke-width:2.5;paint-order:stroke fill markers"
|
||||
id="rect851"
|
||||
width="398.59067"
|
||||
height="44.384548"
|
||||
x="25.146276"
|
||||
y="37.924564"
|
||||
rx="20"
|
||||
ry="20" />
|
||||
<rect
|
||||
style="stroke-width:2.5;paint-order:stroke fill markers"
|
||||
id="rect851-9"
|
||||
width="398.59067"
|
||||
height="44.384548"
|
||||
x="26.978836"
|
||||
y="439.85379"
|
||||
rx="20"
|
||||
ry="20" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
47
client/public/images/buttons/user-hidden.svg
Normal file
@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 448 512"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="user-hidden.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="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.1490485"
|
||||
inkscape:cx="233.67159"
|
||||
inkscape:cy="300.68356"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
|
||||
<path
|
||||
d="m 223.51472,258.30254 c 41.41184,0 74.97476,-33.56293 74.97476,-74.97477 0,-41.41184 -33.56292,-74.97476 -74.97476,-74.97476 -41.41185,0 -74.97476,33.56292 -74.97476,74.97476 0,41.41184 33.56291,74.97477 74.97476,74.97477 z m -26.76834,28.11553 c -57.69542,0 -104.437495,46.74208 -104.437495,104.43749 0,9.60615 7.790345,17.39649 17.396485,17.39649 h 227.61869 c 9.60614,0 17.39649,-7.79034 17.39649,-17.39649 0,-57.69541 -46.74208,-104.43749 -104.4375,-104.43749 z"
|
||||
id="path2"
|
||||
style="stroke-width:0.58574" />
|
||||
<rect
|
||||
style="stroke-width:20;paint-order:stroke fill markers;stroke:#fffffc;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
id="rect851-9"
|
||||
width="398.59067"
|
||||
height="44.384548"
|
||||
x="-237.39584"
|
||||
y="307.60056"
|
||||
rx="20"
|
||||
ry="20"
|
||||
transform="rotate(-45)" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
37
client/public/images/buttons/user-none.svg
Normal file
@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 448 512"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="user-none.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="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.1490485"
|
||||
inkscape:cx="233.67159"
|
||||
inkscape:cy="300.68356"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
|
||||
<path
|
||||
d="m 223.51472,258.30254 c 41.41184,0 74.97476,-33.56293 74.97476,-74.97477 0,-41.41184 -33.56292,-74.97476 -74.97476,-74.97476 -41.41185,0 -74.97476,33.56292 -74.97476,74.97476 0,41.41184 33.56291,74.97477 74.97476,74.97477 z m -26.76834,28.11553 c -57.69542,0 -104.437495,46.74208 -104.437495,104.43749 0,9.60615 7.790345,17.39649 17.396485,17.39649 h 227.61869 c 9.60614,0 17.39649,-7.79034 17.39649,-17.39649 0,-57.69541 -46.74208,-104.43749 -104.4375,-104.43749 z"
|
||||
id="path2"
|
||||
style="stroke-width:0.58574" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
46
client/public/images/buttons/user-partial.svg
Normal file
@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 448 512"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="user-partial.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="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.1490485"
|
||||
inkscape:cx="233.67159"
|
||||
inkscape:cy="300.68356"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
|
||||
<path
|
||||
d="m 223.51472,258.30254 c 41.41184,0 74.97476,-33.56293 74.97476,-74.97477 0,-41.41184 -33.56292,-74.97476 -74.97476,-74.97476 -41.41185,0 -74.97476,33.56292 -74.97476,74.97476 0,41.41184 33.56291,74.97477 74.97476,74.97477 z m -26.76834,28.11553 c -57.69542,0 -104.437495,46.74208 -104.437495,104.43749 0,9.60615 7.790345,17.39649 17.396485,17.39649 h 227.61869 c 9.60614,0 17.39649,-7.79034 17.39649,-17.39649 0,-57.69541 -46.74208,-104.43749 -104.4375,-104.43749 z"
|
||||
id="path2"
|
||||
style="stroke-width:0.58574" />
|
||||
<rect
|
||||
style="stroke-width:2.5;paint-order:stroke fill markers"
|
||||
id="rect851-9"
|
||||
width="398.59067"
|
||||
height="44.384548"
|
||||
x="26.978836"
|
||||
y="439.85379"
|
||||
rx="20"
|
||||
ry="20" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
59
client/public/images/buttons/weapon-hidden.svg
Normal file
@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 448 512"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="weapon-hidden.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="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.1490485"
|
||||
inkscape:cx="140.55107"
|
||||
inkscape:cy="295.46185"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
|
||||
<ellipse
|
||||
style="stroke:#fffffc;stroke-width:20;paint-order:stroke fill markers"
|
||||
id="path1385"
|
||||
cx="341.43585"
|
||||
cy="18.914345"
|
||||
rx="181.45448"
|
||||
ry="29.154556"
|
||||
transform="rotate(45)" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.704307px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 100.09292,126.05632 28.85702,-28.857023 25.34012,25.340123 4.35147,41.90701 z"
|
||||
id="path1519" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.704307px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 100.09292,126.05632 -28.857017,28.85702 25.340121,25.34012 41.907006,4.35147 z"
|
||||
id="path1519-5" />
|
||||
<rect
|
||||
style="stroke:#fffffc;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="rect851-9"
|
||||
width="398.59067"
|
||||
height="44.384548"
|
||||
x="-218.37225"
|
||||
y="300.88464"
|
||||
rx="20"
|
||||
ry="20"
|
||||
transform="rotate(-45)" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
49
client/public/images/buttons/weapon-none.svg
Normal file
@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 448 512"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="weapon-none.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="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.1490485"
|
||||
inkscape:cx="140.55107"
|
||||
inkscape:cy="295.46185"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
|
||||
<ellipse
|
||||
style="stroke:#fffffc;stroke-width:20;paint-order:stroke fill markers"
|
||||
id="path1385"
|
||||
cx="341.43585"
|
||||
cy="18.914345"
|
||||
rx="181.45448"
|
||||
ry="29.154556"
|
||||
transform="rotate(45)" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.704307px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 100.09292,126.05632 28.85702,-28.857023 25.34012,25.340123 4.35147,41.90701 z"
|
||||
id="path1519" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.704307px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 100.09292,126.05632 -28.857017,28.85702 25.340121,25.34012 41.907006,4.35147 z"
|
||||
id="path1519-5" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
58
client/public/images/buttons/weapon-partial.svg
Normal file
@ -0,0 +1,58 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 448 512"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="weapon-partial.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="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.1490485"
|
||||
inkscape:cx="140.55107"
|
||||
inkscape:cy="295.46185"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
|
||||
<rect
|
||||
style="stroke-width:2.5;paint-order:stroke fill markers"
|
||||
id="rect851-9"
|
||||
width="398.59067"
|
||||
height="44.384548"
|
||||
x="26.978836"
|
||||
y="439.85379"
|
||||
rx="20"
|
||||
ry="20" />
|
||||
<ellipse
|
||||
style="stroke:#fffffc;stroke-width:20;paint-order:stroke fill markers"
|
||||
id="path1385"
|
||||
cx="341.43585"
|
||||
cy="18.914345"
|
||||
rx="181.45448"
|
||||
ry="29.154556"
|
||||
transform="rotate(45)" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.704307px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 100.09292,126.05632 28.85702,-28.857023 25.34012,25.340123 4.35147,41.90701 z"
|
||||
id="path1519" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.704307px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 100.09292,126.05632 -28.857017,28.85702 25.340121,25.34012 41.907006,4.35147 z"
|
||||
id="path1519-5" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
56
client/public/images/icons/altitude.svg
Normal file
@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
|
||||
sodipodi:docname="altitude.svg"
|
||||
id="svg4"
|
||||
version="1.1"
|
||||
viewBox="0 0 640 512">
|
||||
<metadata
|
||||
id="metadata10">
|
||||
<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="defs8" />
|
||||
<sodipodi:namedview
|
||||
inkscape:current-layer="svg4"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:cy="268.97402"
|
||||
inkscape:cx="234.71843"
|
||||
inkscape:zoom="0.81953125"
|
||||
showgrid="false"
|
||||
id="namedview6"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0"
|
||||
guidetolerance="10"
|
||||
gridtolerance="10"
|
||||
objecttolerance="10"
|
||||
borderopacity="1"
|
||||
bordercolor="#666666"
|
||||
pagecolor="#ffffff" />
|
||||
<!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) -->
|
||||
<path
|
||||
style="stroke-width:0.671054"
|
||||
id="path837"
|
||||
d="m 132.70945,183.88092 c 1.69844,5.99526 5.81278,10.98349 11.34732,13.76423 l 78.2866,39.30502 a 44.034591,44.034591 0 0 0 19.91285,4.68197 l 218.9503,-0.63901 c 20.12229,-0.0591 40.33567,-4.43039 57.76997,-14.71021 19.57187,-11.5398 26.98708,-23.29049 27.05015,-34.19915 0.0781,-10.90862 -7.19705,-22.61379 -26.62131,-34.03766 -17.30329,-10.17501 -37.46953,-14.4286 -57.5888,-14.37553 L 387.68668,143.88712 281.39609,28.67846 a 11.944098,11.944098 0 0 0 -10.45221,-6.09982 l -49.47892,0.14718 c -8.02791,0.0207 -13.87346,7.77572 -11.71221,15.53701 l 64.00366,105.95731 -77.66813,0.22982 -32.34806,-43.70043 a 11.947453,11.947453 0 0 0 -9.63675,-4.8399 l -30.19994,0.0916 c -7.85685,0.0242 -13.67748,7.47621 -11.81606,15.14656 z" />
|
||||
<path
|
||||
id="path2"
|
||||
d="M 624,448 H 16 c -8.84,0 -16,7.16 -16,16 v 32 c 0,8.84 7.16,16 16,16 h 608 c 8.84,0 16,-7.16 16,-16 v -32 c 0,-8.84 -7.16,-16 -16,-16 z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
1
client/public/images/icons/fuel.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M336 448H16c-8.8 0-16 7.2-16 16v32c0 8.8 7.2 16 16 16h320c8.8 0 16-7.2 16-16v-32c0-8.8-7.2-16-16-16zm157.2-340.7l-81-81c-6.2-6.2-16.4-6.2-22.6 0l-11.3 11.3c-6.2 6.2-6.2 16.4 0 22.6L416 97.9V160c0 28.1 20.9 51.3 48 55.2V376c0 13.2-10.8 24-24 24s-24-10.8-24-24v-32c0-48.6-39.4-88-88-88h-8V64c0-35.3-28.7-64-64-64H96C60.7 0 32 28.7 32 64v352h288V304h8c22.1 0 40 17.9 40 40v27.8c0 37.7 27 72 64.5 75.9 43 4.3 79.5-29.5 79.5-71.7V152.6c0-17-6.8-33.3-18.8-45.3zM256 192H96V64h160v128z"/></svg>
|
||||
|
After Width: | Height: | Size: 695 B |
1
client/public/images/icons/heading.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M347.94 129.86L203.6 195.83a31.938 31.938 0 0 0-15.77 15.77l-65.97 144.34c-7.61 16.65 9.54 33.81 26.2 26.2l144.34-65.97a31.938 31.938 0 0 0 15.77-15.77l65.97-144.34c7.61-16.66-9.54-33.81-26.2-26.2zm-77.36 148.72c-12.47 12.47-32.69 12.47-45.16 0-12.47-12.47-12.47-32.69 0-45.16 12.47-12.47 32.69-12.47 45.16 0 12.47 12.47 12.47 32.69 0 45.16zM248 8C111.03 8 0 119.03 0 256s111.03 248 248 248 248-111.03 248-248S384.97 8 248 8zm0 448c-110.28 0-200-89.72-200-200S137.72 56 248 56s200 89.72 200 200-89.72 200-200 200z"/></svg>
|
||||
|
After Width: | Height: | Size: 730 B |
1
client/public/images/icons/speed.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M288 32C128.94 32 0 160.94 0 320c0 52.8 14.25 102.26 39.06 144.8 5.61 9.62 16.3 15.2 27.44 15.2h443c11.14 0 21.83-5.58 27.44-15.2C561.75 422.26 576 372.8 576 320c0-159.06-128.94-288-288-288zm0 64c14.71 0 26.58 10.13 30.32 23.65-1.11 2.26-2.64 4.23-3.45 6.67l-9.22 27.67c-5.13 3.49-10.97 6.01-17.64 6.01-17.67 0-32-14.33-32-32S270.33 96 288 96zM96 384c-17.67 0-32-14.33-32-32s14.33-32 32-32 32 14.33 32 32-14.33 32-32 32zm48-160c-17.67 0-32-14.33-32-32s14.33-32 32-32 32 14.33 32 32-14.33 32-32 32zm246.77-72.41l-61.33 184C343.13 347.33 352 364.54 352 384c0 11.72-3.38 22.55-8.88 32H232.88c-5.5-9.45-8.88-20.28-8.88-32 0-33.94 26.5-61.43 59.9-63.59l61.34-184.01c4.17-12.56 17.73-19.45 30.36-15.17 12.57 4.19 19.35 17.79 15.17 30.36zm14.66 57.2l15.52-46.55c3.47-1.29 7.13-2.23 11.05-2.23 17.67 0 32 14.33 32 32s-14.33 32-32 32c-11.38-.01-20.89-6.28-26.57-15.22zM480 384c-17.67 0-32-14.33-32-32s14.33-32 32-32 32 14.33 32 32-14.33 32-32 32z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
BIN
client/public/images/marker-icon.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
client/public/images/marker-shadow.png
Normal file
|
After Width: | Height: | Size: 618 B |
|
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
@ -1,14 +0,0 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Express</title>
|
||||
<link rel="stylesheet" href="/stylesheets/style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Express</h1>
|
||||
<p>Welcome to Express</p>
|
||||
<script src="javascripts/bundle.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
36
client/public/stylesheets/airbasemarker.css
Normal file
@ -0,0 +1,36 @@
|
||||
.airbasemarker-container {
|
||||
height: 60px;
|
||||
width: 60px;
|
||||
left: -30px;
|
||||
top: -30px;
|
||||
border: 1px transparent solid;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.airbasemarker-icon {
|
||||
height: 60px;
|
||||
width: 60px;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
display: block;
|
||||
position: absolute;
|
||||
filter: drop-shadow(1px 1px 0 white) drop-shadow(1px -1px 0 white) drop-shadow(-1px 1px 0 white) drop-shadow(-1px -1px 0 white);
|
||||
}
|
||||
|
||||
.airbasemarker-icon-blue {
|
||||
filter: invert(69%) sepia(84%) saturate(264%) hue-rotate(162deg) brightness(100%) contrast(102%) drop-shadow(0px 1px 0 #000A) drop-shadow(1px 0px 0 #000A) drop-shadow(-1px 0px 0 #000A) drop-shadow(0px -1px 0 #000A);
|
||||
}
|
||||
|
||||
.airbasemarker-icon-red {
|
||||
filter: invert(68%) sepia(85%) saturate(2385%) hue-rotate(313deg) brightness(108%) contrast(102%) drop-shadow(0px 1px 0 #000A) drop-shadow(1px 0px 0 #000A) drop-shadow(-1px 0px 0 #000A) drop-shadow(0px -1px 0 #000A);
|
||||
}
|
||||
|
||||
.airbasemarker-name {
|
||||
bottom: -20px;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
font: 800 14px Arial;
|
||||
white-space: nowrap;
|
||||
-webkit-text-fill-color: white;
|
||||
-webkit-text-stroke: 1px;
|
||||
}
|
||||
17
client/public/stylesheets/button.css
Normal file
@ -0,0 +1,17 @@
|
||||
.olympus-button {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.olympus-button img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.olympus-button:hover {}
|
||||
|
||||
.olympus-button:active {}
|
||||
33
client/public/stylesheets/connectionstatuspanel.css
Normal file
@ -0,0 +1,33 @@
|
||||
#connection-status-panel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 15px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
#status-string {
|
||||
font-size: 14px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.olympus-status-disconnected::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
border-radius: 50%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
.olympus-status-connected::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
border-radius: 50%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-color: 00FF00;
|
||||
}
|
||||
90
client/public/stylesheets/dropdown.css
Normal file
@ -0,0 +1,90 @@
|
||||
.olympus-dropdown {
|
||||
width: 100%;
|
||||
min-width: 100px;
|
||||
height: 30px;
|
||||
position: relative;
|
||||
background-color: #DDDD;
|
||||
z-index: 1000;
|
||||
border-radius: 15px;
|
||||
font-family: Verdana, Geneva, Tahoma, sans-serif;
|
||||
color: var(--background-color-dark);
|
||||
padding-left: 5px;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
display: flex;
|
||||
text-shadow: none;
|
||||
box-shadow: 0px 2px 5px #000A;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.olympus-dropdown::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
background-color: var(--background-color-dark);
|
||||
z-index: 1000;
|
||||
border-top-right-radius: 15px;
|
||||
border-bottom-right-radius: 15px;
|
||||
}
|
||||
|
||||
.olympus-dropdown-open {
|
||||
border-bottom-left-radius: 0px;
|
||||
}
|
||||
|
||||
.olympus-dropdown-open::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 13px;
|
||||
right: 11px;
|
||||
height: 1px;
|
||||
width: 1px;
|
||||
border: solid white;
|
||||
border-width: 0 3px 3px 0;
|
||||
padding: 3px;
|
||||
z-index: 1000;
|
||||
transform: rotate(-135deg);
|
||||
-webkit-transform: rotate(-135deg);
|
||||
}
|
||||
|
||||
.olympus-dropdown-closed::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 9px;
|
||||
right: 11px;
|
||||
height: 1px;
|
||||
width: 1px;
|
||||
border: solid white;
|
||||
border-width: 0 3px 3px 0;
|
||||
padding: 3px;
|
||||
z-index: 1000;
|
||||
transform: rotate(45deg);
|
||||
-webkit-transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.olympus-dropdown-content {
|
||||
position: fixed;
|
||||
/*overflow: visible;
|
||||
overflow-y: scroll;*/
|
||||
background-color: #DDDD;
|
||||
z-index: 2000;
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
|
||||
.olympus-dropdown-element {
|
||||
margin: 2px;
|
||||
font-family: Verdana, Geneva, Tahoma, sans-serif;
|
||||
color: var(--background-color-dark);
|
||||
cursor: pointer;
|
||||
opacity: 1;
|
||||
font-size: 13px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.olympus-dropdown-element:hover {
|
||||
background-color: var(--highlight-color);
|
||||
}
|
||||
14
client/public/stylesheets/elements.css
Normal file
@ -0,0 +1,14 @@
|
||||
.olympus-element-1 {
|
||||
background-color: #247be2;
|
||||
height: 28;
|
||||
border-radius: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.bottom-separator {
|
||||
border-bottom: 1px solid gray;
|
||||
}
|
||||
94
client/public/stylesheets/layout.css
Normal file
@ -0,0 +1,94 @@
|
||||
|
||||
/* Page style */
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#map-container {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
min-width: 820px;
|
||||
}
|
||||
|
||||
#unit-info-panel {
|
||||
position: fixed;
|
||||
height: 160px;
|
||||
width: 800px;
|
||||
left: 10px;
|
||||
bottom: 10px;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#map-source-dropdown {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: 10px;
|
||||
width: 200px;
|
||||
color: black;
|
||||
}
|
||||
|
||||
#scenario-dropdown {
|
||||
position: absolute;
|
||||
left: 220px;
|
||||
top: 10px;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
#visibility-control-panel {
|
||||
position: absolute;
|
||||
left: 430px;
|
||||
height: 30px;
|
||||
width: 110;
|
||||
top: 10px;
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
#unit-control-buttons {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
height: fit-content;
|
||||
width: fit-content;
|
||||
right: 10px;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#unit-control-panel {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
height: fit-content;
|
||||
width: 250px;
|
||||
right: 10px;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#connection-status-panel {
|
||||
position: absolute;
|
||||
height: 30px;
|
||||
width: 140px;
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1000px) {
|
||||
#unit-control-buttons {
|
||||
top: 50px;
|
||||
}
|
||||
|
||||
#unit-control-panel {
|
||||
top: 50px;
|
||||
}
|
||||
}
|
||||
656
client/public/stylesheets/leaflet.css
Normal file
@ -0,0 +1,656 @@
|
||||
/* required styles */
|
||||
|
||||
.leaflet-pane,
|
||||
.leaflet-tile,
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow,
|
||||
.leaflet-tile-container,
|
||||
.leaflet-pane > svg,
|
||||
.leaflet-pane > canvas,
|
||||
.leaflet-zoom-box,
|
||||
.leaflet-image-layer,
|
||||
.leaflet-layer {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.leaflet-container {
|
||||
overflow: hidden;
|
||||
}
|
||||
.leaflet-tile,
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
}
|
||||
/* Prevents IE11 from highlighting tiles in blue */
|
||||
.leaflet-tile::selection {
|
||||
background: transparent;
|
||||
}
|
||||
/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
|
||||
.leaflet-safari .leaflet-tile {
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
}
|
||||
/* hack that prevents hw layers "stretching" when loading new tiles */
|
||||
.leaflet-safari .leaflet-tile-container {
|
||||
width: 1600px;
|
||||
height: 1600px;
|
||||
-webkit-transform-origin: 0 0;
|
||||
}
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow {
|
||||
display: block;
|
||||
}
|
||||
/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
|
||||
/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
|
||||
.leaflet-container .leaflet-overlay-pane svg {
|
||||
max-width: none !important;
|
||||
max-height: none !important;
|
||||
}
|
||||
.leaflet-container .leaflet-marker-pane img,
|
||||
.leaflet-container .leaflet-shadow-pane img,
|
||||
.leaflet-container .leaflet-tile-pane img,
|
||||
.leaflet-container img.leaflet-image-layer,
|
||||
.leaflet-container .leaflet-tile {
|
||||
max-width: none !important;
|
||||
max-height: none !important;
|
||||
width: auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.leaflet-container.leaflet-touch-zoom {
|
||||
-ms-touch-action: pan-x pan-y;
|
||||
touch-action: pan-x pan-y;
|
||||
}
|
||||
.leaflet-container.leaflet-touch-drag {
|
||||
-ms-touch-action: pinch-zoom;
|
||||
/* Fallback for FF which doesn't support pinch-zoom */
|
||||
touch-action: none;
|
||||
touch-action: pinch-zoom;
|
||||
}
|
||||
.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
|
||||
-ms-touch-action: none;
|
||||
touch-action: none;
|
||||
}
|
||||
.leaflet-container {
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
.leaflet-container a {
|
||||
-webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
|
||||
}
|
||||
.leaflet-tile {
|
||||
filter: inherit;
|
||||
visibility: hidden;
|
||||
}
|
||||
.leaflet-tile-loaded {
|
||||
visibility: inherit;
|
||||
}
|
||||
.leaflet-zoom-box {
|
||||
width: 0;
|
||||
height: 0;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
z-index: 800;
|
||||
}
|
||||
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
|
||||
.leaflet-overlay-pane svg {
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
.leaflet-pane { z-index: 400; }
|
||||
|
||||
.leaflet-tile-pane { z-index: 200; }
|
||||
.leaflet-overlay-pane { z-index: 400; }
|
||||
.leaflet-shadow-pane { z-index: 500; }
|
||||
.leaflet-marker-pane { z-index: 600; }
|
||||
.leaflet-tooltip-pane { z-index: 650; }
|
||||
.leaflet-popup-pane { z-index: 700; }
|
||||
|
||||
.leaflet-map-pane canvas { z-index: 100; }
|
||||
.leaflet-map-pane svg { z-index: 200; }
|
||||
|
||||
.leaflet-vml-shape {
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
}
|
||||
.lvml {
|
||||
behavior: url(#default#VML);
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
|
||||
/* control positioning */
|
||||
|
||||
.leaflet-control {
|
||||
position: relative;
|
||||
z-index: 800;
|
||||
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
|
||||
pointer-events: auto;
|
||||
}
|
||||
.leaflet-top,
|
||||
.leaflet-bottom {
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
pointer-events: none;
|
||||
}
|
||||
.leaflet-top {
|
||||
top: 0;
|
||||
}
|
||||
.leaflet-right {
|
||||
right: 0;
|
||||
}
|
||||
.leaflet-bottom {
|
||||
bottom: 0;
|
||||
}
|
||||
.leaflet-left {
|
||||
left: 0;
|
||||
}
|
||||
.leaflet-control {
|
||||
float: left;
|
||||
clear: both;
|
||||
}
|
||||
.leaflet-right .leaflet-control {
|
||||
float: right;
|
||||
}
|
||||
.leaflet-top .leaflet-control {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.leaflet-bottom .leaflet-control {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.leaflet-left .leaflet-control {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.leaflet-right .leaflet-control {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
|
||||
/* zoom and fade animations */
|
||||
|
||||
.leaflet-fade-anim .leaflet-popup {
|
||||
opacity: 0;
|
||||
-webkit-transition: opacity 0.2s linear;
|
||||
-moz-transition: opacity 0.2s linear;
|
||||
transition: opacity 0.2s linear;
|
||||
}
|
||||
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
|
||||
opacity: 1;
|
||||
}
|
||||
.leaflet-zoom-animated {
|
||||
-webkit-transform-origin: 0 0;
|
||||
-ms-transform-origin: 0 0;
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
svg.leaflet-zoom-animated {
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.leaflet-zoom-anim .leaflet-zoom-animated {
|
||||
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
}
|
||||
.leaflet-zoom-anim .leaflet-tile,
|
||||
.leaflet-pan-anim .leaflet-tile {
|
||||
-webkit-transition: none;
|
||||
-moz-transition: none;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.leaflet-zoom-anim .leaflet-zoom-hide {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
|
||||
/* cursors */
|
||||
|
||||
.leaflet-interactive {
|
||||
cursor: pointer;
|
||||
}
|
||||
.leaflet-grab {
|
||||
cursor: -webkit-grab;
|
||||
cursor: -moz-grab;
|
||||
cursor: grab;
|
||||
}
|
||||
.leaflet-crosshair,
|
||||
.leaflet-crosshair .leaflet-interactive {
|
||||
cursor: crosshair;
|
||||
}
|
||||
.leaflet-popup-pane,
|
||||
.leaflet-control {
|
||||
cursor: auto;
|
||||
}
|
||||
.leaflet-dragging .leaflet-grab,
|
||||
.leaflet-dragging .leaflet-grab .leaflet-interactive,
|
||||
.leaflet-dragging .leaflet-marker-draggable {
|
||||
cursor: move;
|
||||
cursor: -webkit-grabbing;
|
||||
cursor: -moz-grabbing;
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
/* marker & overlays interactivity */
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow,
|
||||
.leaflet-image-layer,
|
||||
.leaflet-pane > svg path,
|
||||
.leaflet-tile-container {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.leaflet-marker-icon.leaflet-interactive,
|
||||
.leaflet-image-layer.leaflet-interactive,
|
||||
.leaflet-pane > svg path.leaflet-interactive,
|
||||
svg.leaflet-image-layer.leaflet-interactive path {
|
||||
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
/* visual tweaks */
|
||||
|
||||
.leaflet-container {
|
||||
background: #ddd;
|
||||
outline-offset: 1px;
|
||||
}
|
||||
.leaflet-container a {
|
||||
color: #0078A8;
|
||||
}
|
||||
.leaflet-zoom-box {
|
||||
border: 2px dotted #38f;
|
||||
background: rgba(255,255,255,0.5);
|
||||
}
|
||||
|
||||
|
||||
/* general typography */
|
||||
.leaflet-container {
|
||||
font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||
font-size: 12px;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
|
||||
/* general toolbar styles */
|
||||
|
||||
.leaflet-bar {
|
||||
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.leaflet-bar a {
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #ccc;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
display: block;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
.leaflet-bar a,
|
||||
.leaflet-control-layers-toggle {
|
||||
background-position: 50% 50%;
|
||||
background-repeat: no-repeat;
|
||||
display: block;
|
||||
}
|
||||
.leaflet-bar a:hover,
|
||||
.leaflet-bar a:focus {
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
.leaflet-bar a:first-child {
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
}
|
||||
.leaflet-bar a:last-child {
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
border-bottom: none;
|
||||
}
|
||||
.leaflet-bar a.leaflet-disabled {
|
||||
cursor: default;
|
||||
background-color: #f4f4f4;
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.leaflet-touch .leaflet-bar a {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
}
|
||||
.leaflet-touch .leaflet-bar a:first-child {
|
||||
border-top-left-radius: 2px;
|
||||
border-top-right-radius: 2px;
|
||||
}
|
||||
.leaflet-touch .leaflet-bar a:last-child {
|
||||
border-bottom-left-radius: 2px;
|
||||
border-bottom-right-radius: 2px;
|
||||
}
|
||||
|
||||
/* zoom control */
|
||||
|
||||
.leaflet-control-zoom-in,
|
||||
.leaflet-control-zoom-out {
|
||||
font: bold 18px 'Lucida Console', Monaco, monospace;
|
||||
text-indent: 1px;
|
||||
}
|
||||
|
||||
.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
|
||||
/* layers control */
|
||||
|
||||
.leaflet-control-layers {
|
||||
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
|
||||
background: #fff;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.leaflet-control-layers-toggle {
|
||||
background-image: url(images/layers.png);
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
.leaflet-retina .leaflet-control-layers-toggle {
|
||||
background-image: url(images/layers-2x.png);
|
||||
background-size: 26px 26px;
|
||||
}
|
||||
.leaflet-touch .leaflet-control-layers-toggle {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
.leaflet-control-layers .leaflet-control-layers-list,
|
||||
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
|
||||
display: none;
|
||||
}
|
||||
.leaflet-control-layers-expanded .leaflet-control-layers-list {
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
.leaflet-control-layers-expanded {
|
||||
padding: 6px 10px 6px 6px;
|
||||
color: #333;
|
||||
background: #fff;
|
||||
}
|
||||
.leaflet-control-layers-scrollbar {
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
padding-right: 5px;
|
||||
}
|
||||
.leaflet-control-layers-selector {
|
||||
margin-top: 2px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
.leaflet-control-layers label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
font-size: 1.08333em;
|
||||
}
|
||||
.leaflet-control-layers-separator {
|
||||
height: 0;
|
||||
border-top: 1px solid #ddd;
|
||||
margin: 5px -10px 5px -6px;
|
||||
}
|
||||
|
||||
/* Default icon URLs */
|
||||
.leaflet-default-icon-path { /* used only in path-guessing heuristic, see L.Icon.Default */
|
||||
background-image: url(images/marker-icon.png);
|
||||
}
|
||||
|
||||
|
||||
/* attribution and scale controls */
|
||||
|
||||
.leaflet-container .leaflet-control-attribution {
|
||||
background: #fff;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
margin: 0;
|
||||
}
|
||||
.leaflet-control-attribution,
|
||||
.leaflet-control-scale-line {
|
||||
padding: 0 5px;
|
||||
color: #333;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.leaflet-control-attribution a {
|
||||
text-decoration: none;
|
||||
}
|
||||
.leaflet-control-attribution a:hover,
|
||||
.leaflet-control-attribution a:focus {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.leaflet-attribution-flag {
|
||||
display: inline !important;
|
||||
vertical-align: baseline !important;
|
||||
width: 1em;
|
||||
height: 0.6669em;
|
||||
}
|
||||
.leaflet-left .leaflet-control-scale {
|
||||
margin-left: 5px;
|
||||
}
|
||||
.leaflet-bottom .leaflet-control-scale {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.leaflet-control-scale-line {
|
||||
border: 2px solid #777;
|
||||
border-top: none;
|
||||
line-height: 1.1;
|
||||
padding: 2px 5px 1px;
|
||||
white-space: nowrap;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
text-shadow: 1px 1px #fff;
|
||||
}
|
||||
.leaflet-control-scale-line:not(:first-child) {
|
||||
border-top: 2px solid #777;
|
||||
border-bottom: none;
|
||||
margin-top: -2px;
|
||||
}
|
||||
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
|
||||
border-bottom: 2px solid #777;
|
||||
}
|
||||
|
||||
.leaflet-touch .leaflet-control-attribution,
|
||||
.leaflet-touch .leaflet-control-layers,
|
||||
.leaflet-touch .leaflet-bar {
|
||||
box-shadow: none;
|
||||
}
|
||||
.leaflet-touch .leaflet-control-layers,
|
||||
.leaflet-touch .leaflet-bar {
|
||||
border: 2px solid rgba(0,0,0,0.2);
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
|
||||
/* popup */
|
||||
|
||||
.leaflet-popup {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.leaflet-popup-content-wrapper {
|
||||
padding: 1px;
|
||||
text-align: left;
|
||||
border-radius: 12px;
|
||||
}
|
||||
.leaflet-popup-content {
|
||||
margin: 13px 24px 13px 20px;
|
||||
line-height: 1.3;
|
||||
font-size: 13px;
|
||||
font-size: 1.08333em;
|
||||
min-height: 1px;
|
||||
}
|
||||
.leaflet-popup-content p {
|
||||
margin: 17px 0;
|
||||
margin: 1.3em 0;
|
||||
}
|
||||
.leaflet-popup-tip-container {
|
||||
width: 40px;
|
||||
height: 20px;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
margin-top: -1px;
|
||||
margin-left: -20px;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
.leaflet-popup-tip {
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
padding: 1px;
|
||||
|
||||
margin: -10px auto 0;
|
||||
pointer-events: auto;
|
||||
|
||||
-webkit-transform: rotate(45deg);
|
||||
-moz-transform: rotate(45deg);
|
||||
-ms-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
.leaflet-popup-content-wrapper,
|
||||
.leaflet-popup-tip {
|
||||
background: white;
|
||||
color: #333;
|
||||
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
|
||||
}
|
||||
.leaflet-container a.leaflet-popup-close-button {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
border: none;
|
||||
text-align: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
font: 16px/24px Tahoma, Verdana, sans-serif;
|
||||
color: #757575;
|
||||
text-decoration: none;
|
||||
background: transparent;
|
||||
}
|
||||
.leaflet-container a.leaflet-popup-close-button:hover,
|
||||
.leaflet-container a.leaflet-popup-close-button:focus {
|
||||
color: #585858;
|
||||
}
|
||||
.leaflet-popup-scrolled {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.leaflet-oldie .leaflet-popup-content-wrapper {
|
||||
-ms-zoom: 1;
|
||||
}
|
||||
.leaflet-oldie .leaflet-popup-tip {
|
||||
width: 24px;
|
||||
margin: 0 auto;
|
||||
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
|
||||
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
|
||||
}
|
||||
|
||||
.leaflet-oldie .leaflet-control-zoom,
|
||||
.leaflet-oldie .leaflet-control-layers,
|
||||
.leaflet-oldie .leaflet-popup-content-wrapper,
|
||||
.leaflet-oldie .leaflet-popup-tip {
|
||||
border: 1px solid #999;
|
||||
}
|
||||
|
||||
|
||||
/* div icon */
|
||||
|
||||
.leaflet-div-icon {
|
||||
background: #fff;
|
||||
border: 1px solid #666;
|
||||
}
|
||||
|
||||
|
||||
/* Tooltip */
|
||||
/* Base styles for the element that has a tooltip */
|
||||
.leaflet-tooltip {
|
||||
position: absolute;
|
||||
padding: 6px;
|
||||
background-color: #fff;
|
||||
border: 1px solid #fff;
|
||||
border-radius: 3px;
|
||||
color: #222;
|
||||
white-space: nowrap;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
|
||||
}
|
||||
.leaflet-tooltip.leaflet-interactive {
|
||||
cursor: pointer;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.leaflet-tooltip-top:before,
|
||||
.leaflet-tooltip-bottom:before,
|
||||
.leaflet-tooltip-left:before,
|
||||
.leaflet-tooltip-right:before {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
border: 6px solid transparent;
|
||||
background: transparent;
|
||||
content: "";
|
||||
}
|
||||
|
||||
/* Directions */
|
||||
|
||||
.leaflet-tooltip-bottom {
|
||||
margin-top: 6px;
|
||||
}
|
||||
.leaflet-tooltip-top {
|
||||
margin-top: -6px;
|
||||
}
|
||||
.leaflet-tooltip-bottom:before,
|
||||
.leaflet-tooltip-top:before {
|
||||
left: 50%;
|
||||
margin-left: -6px;
|
||||
}
|
||||
.leaflet-tooltip-top:before {
|
||||
bottom: 0;
|
||||
margin-bottom: -12px;
|
||||
border-top-color: #fff;
|
||||
}
|
||||
.leaflet-tooltip-bottom:before {
|
||||
top: 0;
|
||||
margin-top: -12px;
|
||||
margin-left: -6px;
|
||||
border-bottom-color: #fff;
|
||||
}
|
||||
.leaflet-tooltip-left {
|
||||
margin-left: -6px;
|
||||
}
|
||||
.leaflet-tooltip-right {
|
||||
margin-left: 6px;
|
||||
}
|
||||
.leaflet-tooltip-left:before,
|
||||
.leaflet-tooltip-right:before {
|
||||
top: 50%;
|
||||
margin-top: -6px;
|
||||
}
|
||||
.leaflet-tooltip-left:before {
|
||||
right: 0;
|
||||
margin-right: -12px;
|
||||
border-left-color: #fff;
|
||||
}
|
||||
.leaflet-tooltip-right:before {
|
||||
left: 0;
|
||||
margin-left: -12px;
|
||||
border-right-color: #fff;
|
||||
}
|
||||
|
||||
/* Printing */
|
||||
|
||||
@media print {
|
||||
/* Prevent printers from removing background-images of controls. */
|
||||
.leaflet-control {
|
||||
-webkit-print-color-adjust: exact;
|
||||
print-color-adjust: exact;
|
||||
}
|
||||
}
|
||||
8
client/public/stylesheets/panels.css
Normal file
@ -0,0 +1,8 @@
|
||||
/* Panels style */
|
||||
.olympus-panel {
|
||||
background-color: var(--background-color-dark);
|
||||
font-size: 12px;
|
||||
transition: bottom 0.2s;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0px 2px 5px #000A;
|
||||
}
|
||||
91
client/public/stylesheets/selectionscroll.css
Normal file
@ -0,0 +1,91 @@
|
||||
.olympus-selection-scroll-container {
|
||||
position: fixed;
|
||||
background-color: var(--background-color-dark);
|
||||
font-size: 12px;
|
||||
transition: bottom 0.2s;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0px 2px 5px #000A;
|
||||
width: 180px;
|
||||
height: fit-content;
|
||||
z-index: 1000;
|
||||
max-height: 400px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.olympus-selection-scroll {
|
||||
overflow-y: auto;
|
||||
max-height: calc(400px - 60px);
|
||||
}
|
||||
|
||||
.olympus-selection-scroll::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
.olympus-selection-scroll::-webkit-scrollbar-track {
|
||||
background-color: transparent;
|
||||
border-radius: 100px;
|
||||
}
|
||||
|
||||
.olympus-selection-scroll::-webkit-scrollbar-thumb {
|
||||
background-color: white;
|
||||
border-radius: 100px;
|
||||
opacity: 0.8;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.olympus-selection-scroll-element {
|
||||
margin: 2px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
opacity: 1;
|
||||
border-radius: 5px;
|
||||
margin: 5px;
|
||||
padding: 5px;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.olympus-selection-scroll-element:hover {
|
||||
background-color: var(--highlight-color);
|
||||
}
|
||||
|
||||
.olympus-selection-scroll-container label {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.olympus-selection-scroll-container input {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.olympus-selection-scroll-switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
left: calc(50% - 10px);
|
||||
width: 60px;
|
||||
height: 34px;
|
||||
background-color: var(--active-coalition-color);
|
||||
border-radius: 999px;
|
||||
cursor: pointer;
|
||||
margin-left: -30px;
|
||||
}
|
||||
|
||||
.olympus-selection-scroll-switch:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
background-color: white;
|
||||
-webkit-transition: 0.2s;
|
||||
transition: 0.2s;
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
input:checked+.olympus-selection-scroll-switch:before {
|
||||
-webkit-transform: translateX(26px);
|
||||
-ms-transform: translateX(26px);
|
||||
transform: translateX(26px);
|
||||
}
|
||||
102
client/public/stylesheets/selectionwheel.css
Normal file
@ -0,0 +1,102 @@
|
||||
.olympus-selection-wheel {
|
||||
margin: 0;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
width: 220px;
|
||||
height: 220px;
|
||||
overflow: visible;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.olympus-wheel {
|
||||
width: 100%;
|
||||
border-radius: 50%;
|
||||
background-color: var(--background-color-dark);
|
||||
-webkit-mask: radial-gradient(transparent 30%, #000 31%);
|
||||
mask: radial-gradient(transparent 30%, #000 31%);
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.olympus-wheel:before {
|
||||
content: "";
|
||||
display: block;
|
||||
padding-top: 100%;
|
||||
}
|
||||
|
||||
.selection-wheel-button {
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
opacity: 0;
|
||||
/*transition: opacity var(--animation_duration), left var(--animation_duration), top var(--animation_duration);*/
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.selection-wheel-image {
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
/*filter: invert(100%);*/
|
||||
transition: width var(--animation_duration), height var(--animation_duration);
|
||||
filter: invert(100%);
|
||||
}
|
||||
|
||||
.selection-wheel-button:hover {
|
||||
|
||||
}
|
||||
|
||||
.selection-wheel-button:hover .selection-wheel-image {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
/*filter: invert(21%) sepia(23%) saturate(775%) hue-rotate(170deg) brightness(92%) contrast(90%);*/
|
||||
}
|
||||
|
||||
.olympus-selection-wheel label {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.olympus-selection-wheel input {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.olympus-selection-wheel-switch {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
display: inline-block;
|
||||
width: 60px;
|
||||
height: 34px;
|
||||
background-color: var(--active-coalition-color);
|
||||
border-radius: 17px;
|
||||
margin-left: -30px;
|
||||
margin-top: -17px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.olympus-selection-wheel-switch:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
background-color: white;
|
||||
-webkit-transition: 0.2s;
|
||||
transition: 0.2s;
|
||||
border-radius: 50%;
|
||||
|
||||
}
|
||||
|
||||
input:checked+.olympus-selection-wheel-switch:before {
|
||||
-webkit-transform: translateX(26px);
|
||||
-ms-transform: translateX(26px);
|
||||
transform: translateX(26px);
|
||||
}
|
||||
@ -1,8 +1,87 @@
|
||||
body {
|
||||
padding: 50px;
|
||||
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
|
||||
@import url("button.css");
|
||||
@import url("dropdown.css");
|
||||
|
||||
@import url("selectionwheel.css");
|
||||
@import url("selectionscroll.css");
|
||||
|
||||
@import url("unitmarker.css");
|
||||
@import url("airbasemarker.css");
|
||||
|
||||
@import url("panels.css");
|
||||
@import url("connectionstatuspanel.css");
|
||||
@import url("unitcontrolpanel.css");
|
||||
@import url("visibilitycontrolpanel.css");
|
||||
@import url("unitinfopanel.css");
|
||||
|
||||
@import url("layout.css");
|
||||
|
||||
|
||||
/* Variables definitions */
|
||||
:root {
|
||||
--background-color-dark: #202831;
|
||||
--background-color-light: #aaaaaa;
|
||||
--title-color: #d3e9ff;
|
||||
--text-color: white;
|
||||
--blue-coalition-color: #2196F3;
|
||||
--red-coalition-color: #f32121;
|
||||
--neutral-coalition-color: #AAAAAA;
|
||||
--active-coalition-color: var(--blue-coalition-color);
|
||||
--highlight-color: #FFFFFFAA;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #00B7FF;
|
||||
* {
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
}
|
||||
|
||||
.leaflet-container.crosshair-cursor-enabled {
|
||||
cursor:crosshair;
|
||||
}
|
||||
|
||||
.rectangular-container {
|
||||
padding: 0.5em;
|
||||
background-color: gray;
|
||||
border-radius: 5px;
|
||||
width: fit-content;
|
||||
height: fit-content;
|
||||
text-align: center;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.rectangular-container-dark {
|
||||
padding-left: 0.5em;
|
||||
padding-right: 0.5em;
|
||||
padding-top: 0.2em;
|
||||
padding-bottom: 0.2em;
|
||||
background-color: #151b20;
|
||||
border-radius: 5px;
|
||||
width: fit-content;
|
||||
height: fit-content;
|
||||
text-align: center;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.rounded-container {
|
||||
padding: 0.5em;
|
||||
background-color: #247be2;
|
||||
border-radius: 10px;
|
||||
width: fit-content;
|
||||
height: fit-content;
|
||||
text-align: center;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
.vl {
|
||||
border-left: 1px solid #555;
|
||||
width: 1px !important;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
41
client/public/stylesheets/unitcontrolpanel.css
Normal file
@ -0,0 +1,41 @@
|
||||
#unit-control-buttons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 5px;
|
||||
padding: 10px;
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
#unit-control-buttons>div {
|
||||
filter: invert(100%);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
#unit-control-panel {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
align-content: flex-start;
|
||||
row-gap: 5px;
|
||||
padding-left: 30px;
|
||||
padding-right: 30px;
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
/* Common */
|
||||
#unit-info-panel>div {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#unit-control-panel .rounded-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#unit-control-panel #title-label {
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
font-weight: 600;
|
||||
}
|
||||
121
client/public/stylesheets/unitinfopanel.css
Normal file
@ -0,0 +1,121 @@
|
||||
|
||||
/* Panel properties */
|
||||
#unit-info-panel {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding-left: 30px;
|
||||
padding-right: 30px;
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
/* Common */
|
||||
#unit-info-panel>div {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#unit-info-panel>.vl {
|
||||
margin-left: 30px;
|
||||
margin-right: 30px;
|
||||
}
|
||||
|
||||
/* Sections */
|
||||
#unit-info-panel #general {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
column-gap: 5px;
|
||||
row-gap: 5px;
|
||||
align-content: flex-start;
|
||||
}
|
||||
|
||||
#unit-info-panel #flight-data {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
align-content: space-between;
|
||||
}
|
||||
|
||||
#unit-info-panel #loadout-data {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
flex-wrap: wrap;
|
||||
align-content: flex-start;
|
||||
justify-content: flex-start;
|
||||
row-gap: 5px;
|
||||
}
|
||||
|
||||
/* General section */
|
||||
#general #unit-name {
|
||||
color: white;
|
||||
font-size: 18px;
|
||||
width: 100%;
|
||||
padding-bottom: 5px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
#general #task {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Flight data section */
|
||||
#flight-data #flight-data-label {
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
#flight-data #latitude {
|
||||
width: 50%;
|
||||
color: white;
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
color: #8bff63;
|
||||
}
|
||||
|
||||
#flight-data #longitude {
|
||||
width: 50%;
|
||||
color: white;
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
color: #5ca7ff;
|
||||
}
|
||||
|
||||
.flight-data-label, .flight-data-value {
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.flight-data-icon {
|
||||
width: 10%;
|
||||
padding: 2px;
|
||||
filter: invert(100%);
|
||||
}
|
||||
|
||||
.flight-data-label {
|
||||
width: 58%;
|
||||
}
|
||||
|
||||
.flight-data-value {
|
||||
width: 30%;
|
||||
font-weight: 600;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* Loadout section */
|
||||
#loadout-data #loadout-label {
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
108
client/public/stylesheets/unitmarker.css
Normal file
@ -0,0 +1,108 @@
|
||||
.unit-marker-container {
|
||||
height: 60px;
|
||||
width: 60px;
|
||||
left: -30px;
|
||||
top: -30px;
|
||||
border: 0px black solid;
|
||||
position: absolute;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.unit-marker-icon {
|
||||
height: 60px;
|
||||
width: 60px;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
display: flex;
|
||||
position: absolute;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.unit-marker-selected {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
#ring {
|
||||
top: 0px;
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
-webkit-mask: radial-gradient(transparent 60%, #000 61%);
|
||||
mask: radial-gradient(transparent 60%, #000 61%);
|
||||
}
|
||||
|
||||
#background {
|
||||
top: 0px;
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.blue.unit-marker-selected {
|
||||
background-color: var(--blue-coalition-color);
|
||||
}
|
||||
|
||||
.red.unit-marker-selected {
|
||||
background-color: var(--red-coalition-color);
|
||||
}
|
||||
|
||||
|
||||
.unit-marker-hovered {
|
||||
filter: brightness(130%);
|
||||
}
|
||||
|
||||
.unit-marker-dead {
|
||||
filter: brightness(50%);
|
||||
}
|
||||
|
||||
.unit-marker-unitName {
|
||||
top: -20px;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
font: 800 16px Arial;
|
||||
white-space: nowrap;
|
||||
-webkit-text-fill-color: white;
|
||||
-webkit-text-stroke: 1px;
|
||||
}
|
||||
|
||||
.unit-marker-name {
|
||||
bottom: -20px;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
font: 800 14px Arial;
|
||||
white-space: nowrap;
|
||||
-webkit-text-fill-color: white;
|
||||
-webkit-text-stroke: 1px;
|
||||
}
|
||||
|
||||
.unit-marker-altitude {
|
||||
width: 100%;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
position: absolute;
|
||||
text-align: right;
|
||||
font: 800 12px Arial;
|
||||
white-space: nowrap;
|
||||
-webkit-text-fill-color: white;
|
||||
-webkit-text-stroke: 1px;
|
||||
}
|
||||
|
||||
.unit-marker-speed {
|
||||
width: 100%;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
position: absolute;
|
||||
text-align: left;
|
||||
font: 800 12px Arial;
|
||||
white-space: nowrap;
|
||||
-webkit-text-fill-color: white;
|
||||
-webkit-text-stroke: 1px;
|
||||
}
|
||||
|
||||
.unit-marker-container-table-dead .unit-marker-name {
|
||||
opacity: 0;
|
||||
}
|
||||
4
client/public/stylesheets/visibilitycontrolpanel.css
Normal file
@ -0,0 +1,4 @@
|
||||
#visibility-control-panel .olympus-button {
|
||||
filter: invert(100%);
|
||||
opacity: 0.8;
|
||||
}
|
||||
@ -2,7 +2,7 @@ var express = require('express');
|
||||
var router = express.Router();
|
||||
|
||||
/* GET home page. */
|
||||
router.get('/', function(req, res, next) {
|
||||
router.get('/', function (req, res, next) {
|
||||
res.render('index', { title: 'Express' });
|
||||
});
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ var express = require('express');
|
||||
var router = express.Router();
|
||||
|
||||
/* GET users listing. */
|
||||
router.get('/', function(req, res, next) {
|
||||
router.get('/', function (req, res, next) {
|
||||
res.send('respond with a resource');
|
||||
});
|
||||
|
||||
|
||||
38
client/src/controls/button.ts
Normal file
@ -0,0 +1,38 @@
|
||||
export class Button {
|
||||
#container: HTMLElement | null;
|
||||
#srcs: string[];
|
||||
#callback: CallableFunction;
|
||||
#img: any;
|
||||
#state: number = 0;
|
||||
|
||||
constructor(ID: string, srcs: string[], callback: CallableFunction) {
|
||||
this.#container = document.getElementById(ID);
|
||||
this.#srcs = srcs;
|
||||
this.#callback = callback;
|
||||
if (this.#container != null) {
|
||||
this.#img = document.createElement("img");
|
||||
this.#img.src = this.#srcs[this.#state];
|
||||
this.#container.appendChild(this.#img);
|
||||
this.#container.addEventListener("click", () => this.#onClick());
|
||||
}
|
||||
}
|
||||
|
||||
setState(state: number) {
|
||||
if (state < this.#srcs.length) {
|
||||
this.#state = state;
|
||||
this.#img.src = this.#srcs[this.#state];
|
||||
}
|
||||
}
|
||||
|
||||
getState() {
|
||||
return this.#state;
|
||||
}
|
||||
|
||||
#onClick() {
|
||||
if (this.#img != null) {
|
||||
this.setState(this.#state < this.#srcs.length - 1 ? this.#state + 1 : 0);
|
||||
if (this.#callback)
|
||||
this.#callback(this.#state);
|
||||
}
|
||||
}
|
||||
}
|
||||
62
client/src/controls/dropdown.ts
Normal file
@ -0,0 +1,62 @@
|
||||
export class Dropdown {
|
||||
#container: HTMLElement | null;
|
||||
#options: string[];
|
||||
#open?: boolean;
|
||||
#content?: HTMLElement;
|
||||
#callback?: CallableFunction;
|
||||
|
||||
constructor(ID: string, options: string[], callback: CallableFunction) {
|
||||
this.#container = document.getElementById(ID);
|
||||
this.#options = options;
|
||||
this.#callback = callback;
|
||||
this.close()
|
||||
this.#container?.addEventListener("click", () => {
|
||||
this.#open ? this.close() : this.open();
|
||||
})
|
||||
if (this.#container != null && this.#options.length > 0)
|
||||
this.#container.innerHTML = this.#options[0];
|
||||
}
|
||||
|
||||
open() {
|
||||
if (this.#container != null) {
|
||||
this.#open = true;
|
||||
this.#container.classList.add("olympus-dropdown-open");
|
||||
this.#container.classList.remove("olympus-dropdown-closed");
|
||||
this.#content = document.createElement("div");
|
||||
this.#content.classList.add("olympus-dropdown-content");
|
||||
this.#content.style.width = (this.#container.offsetWidth - this.#container.offsetHeight) + "px";
|
||||
|
||||
this.#content.style.left = this.#container.offsetLeft + "px";
|
||||
this.#content.style.top = this.#container.offsetTop + this.#container.offsetHeight + "px";
|
||||
console.log(this.#container);
|
||||
document.body.appendChild(this.#content);
|
||||
|
||||
var height = 2;
|
||||
for (let optionID in this.#options) {
|
||||
var node = document.createElement("div");
|
||||
node.classList.add("olympus-dropdown-element");
|
||||
node.appendChild(document.createTextNode(this.#options[optionID]));
|
||||
this.#content.appendChild(node);
|
||||
height += node.offsetHeight + 2;
|
||||
node.addEventListener('click', () => {
|
||||
this.close();
|
||||
if (this.#container != null)
|
||||
this.#container.innerHTML = this.#options[optionID];
|
||||
if (this.#callback != null)
|
||||
this.#callback(this.#options[optionID])
|
||||
})
|
||||
}
|
||||
this.#content.style.height = height + "px";
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
if (this.#container != null) {
|
||||
this.#open = false;
|
||||
this.#container?.classList.remove("olympus-dropdown-open");
|
||||
this.#container?.classList.add("olympus-dropdown-closed");
|
||||
if (this.#content != null)
|
||||
document.body.removeChild(this.#content);
|
||||
}
|
||||
}
|
||||
}
|
||||
72
client/src/controls/selectionscroll.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import { LatLng } from "leaflet";
|
||||
import { setActiveCoalition } from "..";
|
||||
|
||||
export class SelectionScroll {
|
||||
#container: HTMLElement | null;
|
||||
#display: string;
|
||||
|
||||
constructor(id: string,) {
|
||||
this.#container = document.getElementById(id);
|
||||
this.#display = '';
|
||||
if (this.#container != null) {
|
||||
this.#container.querySelector("#coalition-switch")?.addEventListener('change', (e) => this.#onSwitch(e))
|
||||
this.#display = this.#container.style.display;
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
|
||||
show(x: number, y: number, options: any, callback: CallableFunction, showCoalition: boolean) {
|
||||
/* Hide to remove buttons, if present */
|
||||
this.hide();
|
||||
|
||||
if (this.#container != null && options.length >= 1) {
|
||||
this.#container.style.display = this.#display;
|
||||
this.#container.style.left = x - 110 + "px";
|
||||
this.#container.style.top = y - 110 + "px";
|
||||
var scroll = this.#container.querySelector(".olympus-selection-scroll");
|
||||
if (scroll != null)
|
||||
{
|
||||
for (let optionID in options) {
|
||||
var node = document.createElement("div");
|
||||
node.classList.add("olympus-selection-scroll-element");
|
||||
if (typeof options[optionID] === 'string' || options[optionID] instanceof String){
|
||||
node.appendChild(document.createTextNode(options[optionID]));
|
||||
node.addEventListener('click', () => callback(options[optionID]));
|
||||
}
|
||||
else {
|
||||
node.appendChild(document.createTextNode(options[optionID].tooltip));
|
||||
node.addEventListener('click', () => options[optionID].callback());
|
||||
}
|
||||
scroll.appendChild(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hide() {
|
||||
if (this.#container != null) {
|
||||
this.#container.style.display = "none";
|
||||
var buttons = this.#container.querySelectorAll(".olympus-selection-scroll-element");
|
||||
var scroll = this.#container.querySelector(".olympus-selection-scroll");
|
||||
if (scroll != null)
|
||||
{
|
||||
for (let child of buttons) {
|
||||
scroll.removeChild(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#onSwitch(e: any) {
|
||||
if (this.#container != null) {
|
||||
if (e.currentTarget.checked) {
|
||||
document.documentElement.style.setProperty('--active-coalition-color', getComputedStyle(this.#container).getPropertyValue("--red-coalition-color"));
|
||||
setActiveCoalition("red");
|
||||
}
|
||||
else {
|
||||
document.documentElement.style.setProperty('--active-coalition-color', getComputedStyle(this.#container).getPropertyValue("--blue-coalition-color"));
|
||||
setActiveCoalition("blue");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
92
client/src/controls/selectionwheel.ts
Normal file
@ -0,0 +1,92 @@
|
||||
import { getActiveCoalition, setActiveCoalition } from "..";
|
||||
import { deg2rad } from "../other/utils";
|
||||
|
||||
export class SelectionWheel {
|
||||
#container: HTMLElement | null;
|
||||
#display: string;
|
||||
|
||||
constructor(id: string) {
|
||||
this.#container = document.getElementById(id);
|
||||
this.#display = '';
|
||||
if (this.#container != null) {
|
||||
this.#container.querySelector("#coalition-switch")?.addEventListener('change', (e) => this.#onSwitch(e))
|
||||
this.#display = this.#container.style.display;
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
|
||||
show(x: number, y: number, options: any, showCoalition: boolean) {
|
||||
/* Hide to remove buttons, if present */
|
||||
this.hide();
|
||||
|
||||
if (this.#container != null) {
|
||||
this.#container.style.display = this.#display;
|
||||
this.#container.style.left = x - 110 + "px";
|
||||
this.#container.style.top = y - 110 + "px";
|
||||
|
||||
var angularSize = 360 / options.length;
|
||||
var r = 80;
|
||||
|
||||
/* Create the buttons */
|
||||
for (let id in options) {
|
||||
var button = document.createElement("div");
|
||||
button.classList.add("selection-wheel-button");
|
||||
button.style.left = x - 25 + "px";
|
||||
button.style.top = y - 25 + "px";
|
||||
button.addEventListener('click', (e) => options[id].callback(e));
|
||||
this.#container.appendChild(button);
|
||||
var angle = parseInt(id) * angularSize;
|
||||
button.style.opacity = "1";
|
||||
button.style.left = x + r * Math.sin(deg2rad(angle)) - 25 + "px";
|
||||
button.style.top = y - r * Math.cos(deg2rad(angle)) - 25 + "px";
|
||||
|
||||
var image = document.createElement("img");
|
||||
image.classList.add("selection-wheel-image");
|
||||
image.src = `images/buttons/${options[id].src}`
|
||||
image.title = options[id].tooltip;
|
||||
if ('tint' in options[id]) {
|
||||
button.style.setProperty('background-color', options[id].tint);
|
||||
image.style.opacity = "0";
|
||||
}
|
||||
button.appendChild(image);
|
||||
}
|
||||
|
||||
/* Hide the coalition switch if required */
|
||||
var switchContainer = <HTMLElement>this.#container.querySelector("#coalition-switch-container");
|
||||
if (showCoalition == false) {
|
||||
switchContainer.style.display = "none";
|
||||
document.documentElement.style.setProperty('--active-coalition-color', getComputedStyle(this.#container).getPropertyValue("--neutral-coalition-color"));
|
||||
}
|
||||
else {
|
||||
switchContainer.style.display = "block";
|
||||
if (getActiveCoalition() == "blue")
|
||||
document.documentElement.style.setProperty('--active-coalition-color', getComputedStyle(this.#container).getPropertyValue("--blue-coalition-color"));
|
||||
else
|
||||
document.documentElement.style.setProperty('--active-coalition-color', getComputedStyle(this.#container).getPropertyValue("--red-coalition-color"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hide() {
|
||||
if (this.#container != null) {
|
||||
this.#container.style.display = "none";
|
||||
var buttons = this.#container.querySelectorAll(".selection-wheel-button");
|
||||
for (let child of buttons) {
|
||||
this.#container.removeChild(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#onSwitch(e: any) {
|
||||
if (this.#container != null) {
|
||||
if (e.currentTarget.checked) {
|
||||
document.documentElement.style.setProperty('--active-coalition-color', getComputedStyle(this.#container).getPropertyValue("--red-coalition-color"));
|
||||
setActiveCoalition("red");
|
||||
}
|
||||
else {
|
||||
document.documentElement.style.setProperty('--active-coalition-color', getComputedStyle(this.#container).getPropertyValue("--blue-coalition-color"));
|
||||
setActiveCoalition("blue");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
148
client/src/dcs/dcs.ts
Normal file
@ -0,0 +1,148 @@
|
||||
import * as L from 'leaflet'
|
||||
import { getUnitsManager, setConnected } from '..';
|
||||
import { ConvertDDToDMS } from '../other/utils';
|
||||
|
||||
/* Edit here to change server address */
|
||||
var RESTaddress = "http://localhost:30000/restdemo";
|
||||
|
||||
export function getDataFromDCS(callback: CallableFunction) {
|
||||
/* Request the updated unit data from the server */
|
||||
var xmlHttp = new XMLHttpRequest();
|
||||
xmlHttp.open("GET", RESTaddress, true);
|
||||
|
||||
xmlHttp.onload = function (e) {
|
||||
var data = JSON.parse(xmlHttp.responseText);
|
||||
callback(data);
|
||||
setConnected(true);
|
||||
};
|
||||
|
||||
xmlHttp.onerror = function () {
|
||||
console.error("An error occurred during the XMLHttpRequest");
|
||||
setConnected(false);
|
||||
};
|
||||
xmlHttp.send(null);
|
||||
}
|
||||
|
||||
export function addDestination(ID: number, path: any) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("PUT", RESTaddress);
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
xhr.onreadystatechange = () => { };
|
||||
|
||||
var command = { "ID": ID, "path": path }
|
||||
var data = { "setPath": command }
|
||||
|
||||
xhr.send(JSON.stringify(data));
|
||||
}
|
||||
|
||||
export function spawnSmoke(color: string, latlng: L.LatLng) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("PUT", RESTaddress);
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState === 4) {
|
||||
console.log("Added " + color + " smoke at " + ConvertDDToDMS(latlng.lat, false) + " " + ConvertDDToDMS(latlng.lng, true));
|
||||
}
|
||||
};
|
||||
|
||||
var command = { "color": color, "location": latlng };
|
||||
var data = { "smoke": command }
|
||||
|
||||
xhr.send(JSON.stringify(data));
|
||||
}
|
||||
|
||||
export function spawnGroundUnit(type: string, latlng: L.LatLng, coalition: string) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("PUT", RESTaddress);
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState === 4) {
|
||||
console.log("Added " + coalition + " " + type + " at " + ConvertDDToDMS(latlng.lat, false) + " " + ConvertDDToDMS(latlng.lng, true));
|
||||
}
|
||||
};
|
||||
|
||||
var command = { "type": type, "location": latlng, "coalition": coalition };
|
||||
var data = { "spawnGround": command }
|
||||
|
||||
xhr.send(JSON.stringify(data));
|
||||
}
|
||||
|
||||
export function spawnAircraft(type: string, latlng: L.LatLng, coalition: string, payloadName: string | null = null, airbaseName: string | null = null) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("PUT", RESTaddress);
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState === 4) {
|
||||
console.log("Added " + coalition + " " + type + " at " + ConvertDDToDMS(latlng.lat, false) + " " + ConvertDDToDMS(latlng.lng, true));
|
||||
}
|
||||
};
|
||||
|
||||
var command = { "type": type, "location": latlng, "coalition": coalition, "payloadName": payloadName != null? payloadName: "", "airbaseName": airbaseName != null? airbaseName: ""};
|
||||
var data = { "spawnAir": command }
|
||||
|
||||
xhr.send(JSON.stringify(data));
|
||||
}
|
||||
|
||||
export function attackUnit(ID: number, targetID: number) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("PUT", RESTaddress);
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState === 4) {
|
||||
console.log("Unit " + getUnitsManager().getUnitByID(ID).unitName + " attack " + getUnitsManager().getUnitByID(targetID).unitName);
|
||||
}
|
||||
};
|
||||
|
||||
var command = { "ID": ID, "targetID": targetID };
|
||||
var data = { "attackUnit": command }
|
||||
|
||||
xhr.send(JSON.stringify(data));
|
||||
}
|
||||
|
||||
export function cloneUnit(ID: number) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("PUT", RESTaddress);
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState === 4) {
|
||||
console.log("Unit " + getUnitsManager().getUnitByID(ID).unitName + " cloned");
|
||||
}
|
||||
};
|
||||
|
||||
var command = { "ID": ID };
|
||||
var data = { "cloneUnit": command }
|
||||
|
||||
xhr.send(JSON.stringify(data));
|
||||
}
|
||||
|
||||
export function changeSpeed(ID: number, speedChange: string) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("PUT", RESTaddress);
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState === 4) {
|
||||
console.log(getUnitsManager().getUnitByID(ID).unitName + " speed change request: " + speedChange);
|
||||
}
|
||||
};
|
||||
|
||||
var command = {"ID": ID, "change": speedChange}
|
||||
var data = {"changeSpeed": command}
|
||||
|
||||
xhr.send(JSON.stringify(data));
|
||||
}
|
||||
|
||||
export function changeAltitude(ID: number, altitudeChange: string) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("PUT", RESTaddress);
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState === 4) {
|
||||
console.log(getUnitsManager().getUnitByID(ID).unitName + " altitude change request: " + altitudeChange);
|
||||
}
|
||||
};
|
||||
|
||||
var command = {"ID": ID, "change": altitudeChange}
|
||||
var data = {"changeAltitude": command}
|
||||
|
||||
xhr.send(JSON.stringify(data));
|
||||
}
|
||||
165
client/src/index.ts
Normal file
@ -0,0 +1,165 @@
|
||||
import { Map } from "./map/map"
|
||||
import { getDataFromDCS } from "./dcs/dcs"
|
||||
import { SelectionWheel } from "./controls/selectionwheel";
|
||||
import { UnitsManager } from "./units/unitsmanager";
|
||||
import { UnitInfoPanel } from "./panels/unitinfopanel";
|
||||
import { SelectionScroll } from "./controls/selectionscroll";
|
||||
import { Dropdown } from "./controls/dropdown";
|
||||
import { ConnectionStatusPanel } from "./panels/connectionstatuspanel";
|
||||
import { Button } from "./controls/button";
|
||||
import { MissionData } from "./missiondata/missiondata";
|
||||
|
||||
/* TODO: should this be a class? */
|
||||
var map: Map;
|
||||
var selectionWheel: SelectionWheel;
|
||||
var selectionScroll: SelectionScroll;
|
||||
var unitsManager: UnitsManager;
|
||||
var unitInfoPanel: UnitInfoPanel;
|
||||
var activeCoalition: string;
|
||||
var scenarioDropdown: Dropdown;
|
||||
var mapSourceDropdown: Dropdown;
|
||||
var connected: boolean;
|
||||
var connectionStatusPanel: ConnectionStatusPanel;
|
||||
var missionData: MissionData;
|
||||
|
||||
var slowButton: Button;
|
||||
var fastButton: Button;
|
||||
var climbButton: Button;
|
||||
var descendButton: Button;
|
||||
var userVisibilityButton: Button;
|
||||
var aiVisibilityButton: Button;
|
||||
var weaponVisibilityButton: Button;
|
||||
var deadVisibilityButton: Button;
|
||||
|
||||
function setup() {
|
||||
/* Initialize */
|
||||
map = new Map('map-container');
|
||||
selectionWheel = new SelectionWheel("selection-wheel");
|
||||
selectionScroll = new SelectionScroll("selection-scroll");
|
||||
unitsManager = new UnitsManager();
|
||||
unitInfoPanel = new UnitInfoPanel("unit-info-panel");
|
||||
scenarioDropdown = new Dropdown("scenario-dropdown", ["Caucasus", "Syria", "Marianas", "Nevada", "South Atlantic", "The channel"], () => { });
|
||||
mapSourceDropdown = new Dropdown("map-source-dropdown", map.getLayers(), (option: string) => map.setLayer(option));
|
||||
connectionStatusPanel = new ConnectionStatusPanel("connection-status-panel");
|
||||
missionData = new MissionData();
|
||||
|
||||
/* Unit control buttons */
|
||||
slowButton = new Button("slow-button", ["images/buttons/slow.svg"], () => { getUnitsManager().selectedUnitsChangeSpeed("slow"); });
|
||||
fastButton = new Button("fast-button", ["images/buttons/fast.svg"], () => { getUnitsManager().selectedUnitsChangeSpeed("fast"); });
|
||||
climbButton = new Button("climb-button", ["images/buttons/climb.svg"], () => { getUnitsManager().selectedUnitsChangeAltitude("climb"); });
|
||||
descendButton = new Button("descend-button", ["images/buttons/descend.svg"], () => { getUnitsManager().selectedUnitsChangeAltitude("descend"); });
|
||||
|
||||
/* Visibility buttons */
|
||||
userVisibilityButton = new Button("user-visibility-button", ["images/buttons/user-full.svg", "images/buttons/user-partial.svg", "images/buttons/user-none.svg", "images/buttons/user-hidden.svg"], () => { });
|
||||
aiVisibilityButton = new Button("ai-visibility-button", ["images/buttons/ai-full.svg", "images/buttons/ai-partial.svg", "images/buttons/ai-none.svg", "images/buttons/ai-hidden.svg"], () => { });
|
||||
weaponVisibilityButton = new Button("weapon-visibility-button", ["images/buttons/weapon-partial.svg", "images/buttons/weapon-none.svg", "images/buttons/weapon-hidden.svg"], () => { });
|
||||
deadVisibilityButton = new Button("dead-visibility-button", ["images/buttons/dead.svg", "images/buttons/dead-hidden.svg"], () => { });
|
||||
|
||||
aiVisibilityButton.setState(1);
|
||||
weaponVisibilityButton.setState(1);
|
||||
deadVisibilityButton.setState(1);
|
||||
|
||||
/* Default values */
|
||||
activeCoalition = "blue";
|
||||
connected = false;
|
||||
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
function requestUpdate() {
|
||||
getDataFromDCS(update);
|
||||
/* Main update rate = 250ms is minimum time, equal to server update time. */
|
||||
setTimeout(() => requestUpdate(), getConnected() ? 250 : 1000);
|
||||
connectionStatusPanel.update(getConnected());
|
||||
}
|
||||
|
||||
export function update(data: JSON) {
|
||||
unitsManager.update(data);
|
||||
missionData.update(data);
|
||||
}
|
||||
|
||||
export function getMap() {
|
||||
return map;
|
||||
}
|
||||
|
||||
export function getSelectionWheel() {
|
||||
return selectionWheel;
|
||||
}
|
||||
|
||||
export function getSelectionScroll() {
|
||||
return selectionScroll;
|
||||
}
|
||||
|
||||
export function getUnitsManager() {
|
||||
return unitsManager;
|
||||
}
|
||||
|
||||
export function getUnitInfoPanel() {
|
||||
return unitInfoPanel;
|
||||
}
|
||||
|
||||
export function setActiveCoalition(newActiveCoalition: string) {
|
||||
activeCoalition = newActiveCoalition;
|
||||
}
|
||||
|
||||
export function getActiveCoalition() {
|
||||
return activeCoalition;
|
||||
}
|
||||
|
||||
export function setConnected(newConnected: boolean) {
|
||||
connected = newConnected
|
||||
}
|
||||
|
||||
export function getConnected() {
|
||||
return connected;
|
||||
}
|
||||
|
||||
export function getVisibilitySettings() {
|
||||
var visibility = {
|
||||
user: "",
|
||||
ai: "",
|
||||
weapon: "",
|
||||
dead: ""
|
||||
};
|
||||
|
||||
switch (userVisibilityButton.getState()) {
|
||||
case 0:
|
||||
visibility.user = "full"; break;
|
||||
case 1:
|
||||
visibility.user = "partial"; break;
|
||||
case 2:
|
||||
visibility.user = "none"; break;
|
||||
case 3:
|
||||
visibility.user = "hidden"; break;
|
||||
}
|
||||
|
||||
switch (aiVisibilityButton.getState()) {
|
||||
case 0:
|
||||
visibility.ai = "full"; break;
|
||||
case 1:
|
||||
visibility.ai = "partial"; break;
|
||||
case 2:
|
||||
visibility.ai = "none"; break;
|
||||
case 3:
|
||||
visibility.ai = "hidden"; break;
|
||||
}
|
||||
|
||||
switch (weaponVisibilityButton.getState()) {
|
||||
case 0:
|
||||
visibility.weapon = "partial"; break;
|
||||
case 1:
|
||||
visibility.weapon = "none"; break;
|
||||
case 2:
|
||||
visibility.weapon = "hidden"; break;
|
||||
}
|
||||
|
||||
switch (deadVisibilityButton.getState()) {
|
||||
case 0:
|
||||
visibility.dead = "none"; break;
|
||||
case 1:
|
||||
visibility.dead = "hidden"; break;
|
||||
}
|
||||
return visibility;
|
||||
}
|
||||
|
||||
window.onload = setup;
|
||||
133
client/src/map/boxselect.ts
Normal file
@ -0,0 +1,133 @@
|
||||
import { Map } from 'leaflet';
|
||||
import { Handler} from 'leaflet';
|
||||
import { Util } from 'leaflet';
|
||||
import { DomUtil } from 'leaflet';
|
||||
import { DomEvent } from 'leaflet';
|
||||
import { LatLngBounds } from 'leaflet';
|
||||
import { Bounds } from 'leaflet';
|
||||
|
||||
export var BoxSelect = Handler.extend({
|
||||
initialize: function (map: Map) {
|
||||
this._map = map;
|
||||
this._container = map.getContainer();
|
||||
this._pane = map.getPanes().overlayPane;
|
||||
this._resetStateTimeout = 0;
|
||||
map.on('unload', this._destroy, this);
|
||||
},
|
||||
|
||||
addHooks: function () {
|
||||
DomEvent.on(this._container, 'mousedown', this._onMouseDown, this);
|
||||
},
|
||||
|
||||
removeHooks: function () {
|
||||
DomEvent.off(this._container, 'mousedown', this._onMouseDown, this);
|
||||
},
|
||||
|
||||
moved: function () {
|
||||
return this._moved;
|
||||
},
|
||||
|
||||
_destroy: function () {
|
||||
DomUtil.remove(this._pane);
|
||||
delete this._pane;
|
||||
},
|
||||
|
||||
_resetState: function () {
|
||||
this._resetStateTimeout = 0;
|
||||
this._moved = false;
|
||||
},
|
||||
|
||||
_clearDeferredResetState: function () {
|
||||
if (this._resetStateTimeout !== 0) {
|
||||
clearTimeout(this._resetStateTimeout);
|
||||
this._resetStateTimeout = 0;
|
||||
}
|
||||
},
|
||||
|
||||
_onMouseDown: function (e: any) {
|
||||
if (((e.which !== 3) && (e.button !== 2))) { return false; }
|
||||
|
||||
// Clear the deferred resetState if it hasn't executed yet, otherwise it
|
||||
// will interrupt the interaction and orphan a box element in the container.
|
||||
this._clearDeferredResetState();
|
||||
this._resetState();
|
||||
|
||||
DomUtil.disableTextSelection();
|
||||
DomUtil.disableImageDrag();
|
||||
|
||||
this._startPoint = this._map.mouseEventToContainerPoint(e);
|
||||
|
||||
//@ts-ignore
|
||||
DomEvent.on(document, {
|
||||
contextmenu: DomEvent.stop,
|
||||
mousemove: this._onMouseMove,
|
||||
mouseup: this._onMouseUp,
|
||||
keydown: this._onKeyDown
|
||||
}, this);
|
||||
},
|
||||
|
||||
_onMouseMove: function (e: any) {
|
||||
if (!this._moved) {
|
||||
this._moved = true;
|
||||
|
||||
this._box = DomUtil.create('div', 'leaflet-zoom-box', this._container);
|
||||
DomUtil.addClass(this._container, 'leaflet-crosshair');
|
||||
|
||||
this._map.fire('boxzoomstart');
|
||||
}
|
||||
|
||||
this._point = this._map.mouseEventToContainerPoint(e);
|
||||
|
||||
var bounds = new Bounds(this._point, this._startPoint),
|
||||
size = bounds.getSize();
|
||||
|
||||
if (bounds.min != undefined)
|
||||
DomUtil.setPosition(this._box, bounds.min);
|
||||
|
||||
this._box.style.width = size.x + 'px';
|
||||
this._box.style.height = size.y + 'px';
|
||||
},
|
||||
|
||||
_finish: function () {
|
||||
if (this._moved) {
|
||||
DomUtil.remove(this._box);
|
||||
DomUtil.removeClass(this._container, 'leaflet-crosshair');
|
||||
}
|
||||
|
||||
DomUtil.enableTextSelection();
|
||||
DomUtil.enableImageDrag();
|
||||
|
||||
//@ts-ignore
|
||||
DomEvent.off(document, {
|
||||
contextmenu: DomEvent.stop,
|
||||
mousemove: this._onMouseMove,
|
||||
mouseup: this._onMouseUp,
|
||||
keydown: this._onKeyDown
|
||||
}, this);
|
||||
},
|
||||
|
||||
_onMouseUp: function (e: any) {
|
||||
if ((e.which !== 3) && (e.button !== 2)) { return; }
|
||||
|
||||
this._finish();
|
||||
|
||||
if (!this._moved) { return; }
|
||||
// Postpone to next JS tick so internal click event handling
|
||||
// still see it as "moved".
|
||||
setTimeout(Util.bind(this._resetState, this), 0);
|
||||
var bounds = new LatLngBounds(
|
||||
this._map.containerPointToLatLng(this._startPoint),
|
||||
this._map.containerPointToLatLng(this._point));
|
||||
|
||||
this._map.fire('selectionend', {selectionBounds: bounds});
|
||||
},
|
||||
|
||||
_onKeyDown: function (e: any) {
|
||||
if (e.keyCode === 27) {
|
||||
this._finish();
|
||||
this._clearDeferredResetState();
|
||||
this._resetState();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
282
client/src/map/map.ts
Normal file
@ -0,0 +1,282 @@
|
||||
import * as L from "leaflet"
|
||||
import { getSelectionWheel, getSelectionScroll, getUnitsManager, getActiveCoalition } from "..";
|
||||
import { spawnAircraft, spawnGroundUnit, spawnSmoke } from "../dcs/dcs";
|
||||
import { payloadNames } from "../units/payloadNames";
|
||||
import { unitTypes } from "../units/unitTypes";
|
||||
import { BoxSelect } from "./boxselect";
|
||||
|
||||
L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect);
|
||||
|
||||
export interface ClickEvent {
|
||||
x: number;
|
||||
y: number;
|
||||
latlng: L.LatLng;
|
||||
}
|
||||
|
||||
export interface SpawnEvent extends ClickEvent{
|
||||
airbaseName: string | null;
|
||||
coalitionID: number | null;
|
||||
}
|
||||
|
||||
export class Map extends L.Map {
|
||||
#state: string;
|
||||
#layer?: L.TileLayer;
|
||||
#preventRightClick: boolean = false;
|
||||
#rightClickTimer: number = 0;
|
||||
|
||||
constructor(ID: string) {
|
||||
/* Init the leaflet map */
|
||||
//@ts-ignore
|
||||
super(ID, { doubleClickZoom: false, zoomControl: false, boxZoom: false, boxSelect: true });
|
||||
this.setView([37.23, -115.8], 12);
|
||||
|
||||
this.setLayer("ArcGIS Satellite");
|
||||
|
||||
/* Init the state machine */
|
||||
this.#state = "IDLE";
|
||||
|
||||
/* Register event handles */
|
||||
this.on("click", (e: any) => this.#onClick(e));
|
||||
this.on("dblclick", (e: any) => this.#onDoubleClick(e));
|
||||
this.on("contextmenu", (e: any) => this.#onContextMenu(e));
|
||||
this.on('selectionend', (e: any) => this.#onSelectionEnd(e));
|
||||
}
|
||||
|
||||
setLayer(layerName: string) {
|
||||
if (this.#layer != null) {
|
||||
this.removeLayer(this.#layer)
|
||||
}
|
||||
|
||||
if (layerName == "ArcGIS Satellite") {
|
||||
this.#layer = L.tileLayer("https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}", {
|
||||
attribution: "Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community"
|
||||
});
|
||||
}
|
||||
else if (layerName == "USGS Topo") {
|
||||
this.#layer = L.tileLayer('https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}', {
|
||||
maxZoom: 20,
|
||||
attribution: 'Tiles courtesy of the <a href="https://usgs.gov/">U.S. Geological Survey</a>'
|
||||
});
|
||||
}
|
||||
else if (layerName == "OpenStreetMap Mapnik") {
|
||||
this.#layer = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
maxZoom: 19,
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
});
|
||||
}
|
||||
else if (layerName == "OPENVKarte") {
|
||||
this.#layer = L.tileLayer('https://tileserver.memomaps.de/tilegen/{z}/{x}/{y}.png', {
|
||||
maxZoom: 18,
|
||||
attribution: 'Map <a href="https://memomaps.de/">memomaps.de</a> <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, map data © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
});
|
||||
}
|
||||
else if (layerName == "Esri.DeLorme") {
|
||||
this.#layer = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/Specialty/DeLorme_World_Base_Map/MapServer/tile/{z}/{y}/{x}', {
|
||||
attribution: 'Tiles © Esri — Copyright: ©2012 DeLorme',
|
||||
minZoom: 1,
|
||||
maxZoom: 11
|
||||
});
|
||||
}
|
||||
else if (layerName == "CyclOSM") {
|
||||
this.#layer = L.tileLayer('https://{s}.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png', {
|
||||
maxZoom: 20,
|
||||
attribution: '<a href="https://github.com/cyclosm/cyclosm-cartocss-style/releases" title="CyclOSM - Open Bicycle render">CyclOSM</a> | Map data: © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
});
|
||||
}
|
||||
this.#layer?.addTo(this);
|
||||
}
|
||||
|
||||
getLayers() {
|
||||
return ["ArcGIS Satellite", "USGS Topo", "OpenStreetMap Mapnik", "OPENVKarte", "Esri.DeLorme", "CyclOSM"]
|
||||
}
|
||||
|
||||
/* State machine */
|
||||
setState(state: string) {
|
||||
this.#state = state;
|
||||
|
||||
if (this.#state === "IDLE") {
|
||||
L.DomUtil.removeClass(this.getContainer(),'crosshair-cursor-enabled');
|
||||
}
|
||||
else if (this.#state === "MOVE_UNIT") {
|
||||
L.DomUtil.addClass(this.getContainer(),'crosshair-cursor-enabled');
|
||||
}
|
||||
else if (this.#state === "ATTACK") {
|
||||
|
||||
}
|
||||
else if (this.#state === "FORMATION") {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
getState() {
|
||||
return this.#state;
|
||||
}
|
||||
|
||||
/* Selection wheel */
|
||||
showSelectionWheel(e: ClickEvent | SpawnEvent, options: any, showCoalition: boolean) {
|
||||
var x = e.x;
|
||||
var y = e.y;
|
||||
getSelectionWheel().show(x, y, options, showCoalition);
|
||||
}
|
||||
|
||||
hideSelectionWheel() {
|
||||
getSelectionWheel().hide();
|
||||
}
|
||||
|
||||
/* Selection scroll */
|
||||
showSelectionScroll(e: ClickEvent | SpawnEvent, options: any, callback: CallableFunction, showCoalition: boolean = false) {
|
||||
var x = e.x;
|
||||
var y = e.y;
|
||||
getSelectionScroll().show(x, y, options, callback, showCoalition);
|
||||
}
|
||||
|
||||
hideSelectionScroll() {
|
||||
getSelectionScroll().hide();
|
||||
}
|
||||
|
||||
/* Event handlers */
|
||||
#onClick(e: any) {
|
||||
this.hideSelectionWheel();
|
||||
this.hideSelectionScroll();
|
||||
if (this.#state === "IDLE") {
|
||||
|
||||
}
|
||||
else if (this.#state === "MOVE_UNIT") {
|
||||
if (!e.originalEvent.ctrlKey) {
|
||||
getUnitsManager().clearDestinations();
|
||||
}
|
||||
getUnitsManager().addDestination(e.latlng)
|
||||
}
|
||||
}
|
||||
|
||||
#onDoubleClick(e: any) {
|
||||
var spawnEvent: SpawnEvent = {x: e.originalEvent.x, y: e.originalEvent.y, latlng: e.latlng, airbaseName: null, coalitionID: null};
|
||||
if (this.#state == "IDLE") {
|
||||
var options = [
|
||||
{ "tooltip": "Spawn air unit", "src": "spawnAir.png", "callback": () => this.#aircraftSpawnMenu(spawnEvent) },
|
||||
{ "tooltip": "Spawn ground unit", "src": "spawnGround.png", "callback": () => this.#groundUnitSpawnMenu(spawnEvent) },
|
||||
{ "tooltip": "Smoke", "src": "spawnSmoke.png", "callback": () => this.#smokeSpawnMenu(spawnEvent) },
|
||||
//{ "tooltip": "Explosion", "src": "spawnExplosion.png", "callback": () => this.#explosionSpawnMenu(e) }
|
||||
]
|
||||
this.showSelectionScroll(spawnEvent, options, () => {}, true);
|
||||
}
|
||||
}
|
||||
|
||||
#onContextMenu(e: any) {
|
||||
this.#rightClickTimer = setTimeout(() => {
|
||||
if (!this.#preventRightClick) {
|
||||
this.setState("IDLE");
|
||||
getUnitsManager().deselectAllUnits();
|
||||
this.hideSelectionWheel();
|
||||
this.hideSelectionScroll();
|
||||
}
|
||||
this.#preventRightClick = false;
|
||||
}, 200);
|
||||
}
|
||||
|
||||
#onSelectionEnd(e: any)
|
||||
{
|
||||
clearTimeout(this.#rightClickTimer);
|
||||
this.#preventRightClick = true;
|
||||
getUnitsManager().selectFromBounds(e.selectionBounds);
|
||||
}
|
||||
|
||||
/* Spawn from air base */
|
||||
spawnFromAirbase(e: SpawnEvent)
|
||||
{
|
||||
this.#aircraftSpawnMenu(e);
|
||||
}
|
||||
|
||||
/* Spawning menus */
|
||||
#groundUnitSpawnMenu(e: SpawnEvent) {
|
||||
var options = [
|
||||
{'coalition': true, 'tooltip': 'Howitzer', 'src': 'spawnHowitzer.png', 'callback': () => this.#selectGroundUnit(e, "Howitzers")},
|
||||
{'coalition': true, 'tooltip': 'SAM', 'src': 'spawnSAM.png', 'callback': () => this.#selectGroundUnit(e, "SAM")},
|
||||
{'coalition': true, 'tooltip': 'IFV', 'src': 'spawnIFV.png', 'callback': () => this.#selectGroundUnit(e, "IFV")},
|
||||
{'coalition': true, 'tooltip': 'Tank', 'src': 'spawnTank.png', 'callback': () => this.#selectGroundUnit(e, "Tanks")},
|
||||
{'coalition': true, 'tooltip': 'MLRS', 'src': 'spawnMLRS.png', 'callback': () => this.#selectGroundUnit(e, "MLRS")},
|
||||
{'coalition': true, 'tooltip': 'Radar', 'src': 'spawnRadar.png', 'callback': () => this.#selectGroundUnit(e, "Radar")},
|
||||
{'coalition': true, 'tooltip': 'Unarmed', 'src': 'spawnUnarmed.png', 'callback': () => this.#selectGroundUnit(e, "Unarmed")}
|
||||
]
|
||||
this.showSelectionScroll(e, options, () => {}, true);
|
||||
}
|
||||
|
||||
#smokeSpawnMenu(e: SpawnEvent) {
|
||||
this.hideSelectionWheel();
|
||||
this.hideSelectionScroll();
|
||||
var options = [
|
||||
{'tooltip': 'Red smoke', 'src': 'spawnSmoke.png', 'callback': () => {this.hideSelectionWheel(); this.hideSelectionScroll(); spawnSmoke('red', e.latlng)}, 'tint': 'red'},
|
||||
{'tooltip': 'White smoke', 'src': 'spawnSmoke.png', 'callback': () => {this.hideSelectionWheel(); this.hideSelectionScroll(); spawnSmoke('white', e.latlng)}, 'tint': 'white'},
|
||||
{'tooltip': 'Blue smoke', 'src': 'spawnSmoke.png', 'callback': () => {this.hideSelectionWheel(); this.hideSelectionScroll(); spawnSmoke('blue', e.latlng)}, 'tint': 'blue'},
|
||||
{'tooltip': 'Green smoke', 'src': 'spawnSmoke.png', 'callback': () => {this.hideSelectionWheel(); this.hideSelectionScroll(); spawnSmoke('green', e.latlng)}, 'tint': 'green'},
|
||||
{'tooltip': 'Orange smoke', 'src': 'spawnSmoke.png', 'callback': () => {this.hideSelectionWheel(); this.hideSelectionScroll(); spawnSmoke('orange', e.latlng)}, 'tint': 'orange'},
|
||||
]
|
||||
this.showSelectionScroll(e, options, () => {}, true);
|
||||
}
|
||||
|
||||
#explosionSpawnMenu(e: SpawnEvent) {
|
||||
|
||||
}
|
||||
|
||||
#aircraftSpawnMenu(e: SpawnEvent) {
|
||||
var options = [
|
||||
{ 'coalition': true, 'tooltip': 'CAP', 'src': 'spawnCAP.png', 'callback': () => this.#selectAircraft(e, "CAP") },
|
||||
{ 'coalition': true, 'tooltip': 'CAS', 'src': 'spawnCAS.png', 'callback': () => this.#selectAircraft(e, "CAS") },
|
||||
{ 'coalition': true, 'tooltip': 'Tanker', 'src': 'spawnTanker.png', 'callback': () => this.#selectAircraft(e, "tanker") },
|
||||
{ 'coalition': true, 'tooltip': 'AWACS', 'src': 'spawnAWACS.png', 'callback': () => this.#selectAircraft(e, "awacs") },
|
||||
{ 'coalition': true, 'tooltip': 'Strike', 'src': 'spawnStrike.png', 'callback': () => this.#selectAircraft(e, "strike") },
|
||||
{ 'coalition': true, 'tooltip': 'Drone', 'src': 'spawnDrone.png', 'callback': () => this.#selectAircraft(e, "drone") },
|
||||
{ 'coalition': true, 'tooltip': 'Transport', 'src': 'spawnTransport.png', 'callback': () => this.#selectAircraft(e, "transport") },
|
||||
]
|
||||
this.showSelectionScroll(e, options, () => {}, true);
|
||||
}
|
||||
|
||||
/* Show unit selection for air units */
|
||||
#selectAircraft(e: SpawnEvent, group: string) {
|
||||
this.hideSelectionWheel();
|
||||
this.hideSelectionScroll();
|
||||
var options = unitTypes.air[group];
|
||||
if (options != undefined)
|
||||
options.sort();
|
||||
else
|
||||
options = [];
|
||||
this.showSelectionScroll(e, options, (unitType: string) => {
|
||||
this.hideSelectionWheel();
|
||||
this.hideSelectionScroll();
|
||||
this.#unitSelectPayload(e, unitType);
|
||||
});
|
||||
}
|
||||
|
||||
/* Show weapon selection for air units */
|
||||
#unitSelectPayload(e: SpawnEvent, unitType: string) {
|
||||
this.hideSelectionWheel();
|
||||
this.hideSelectionScroll();
|
||||
var options = [];
|
||||
options = payloadNames[unitType]
|
||||
if (options != undefined && options.length > 0) {
|
||||
options.sort();
|
||||
this.showSelectionScroll({x: e.x, y: e.y, latlng: e.latlng}, options, (payloadName: string) => {
|
||||
this.hideSelectionWheel();
|
||||
this.hideSelectionScroll();
|
||||
spawnAircraft(unitType, e.latlng, getActiveCoalition(), payloadName, e.airbaseName);
|
||||
});
|
||||
}
|
||||
else {
|
||||
spawnAircraft(unitType, e.latlng, getActiveCoalition());
|
||||
}
|
||||
}
|
||||
|
||||
/* Show unit selection for ground units */
|
||||
#selectGroundUnit(e: any, group: string)
|
||||
{
|
||||
this.hideSelectionWheel();
|
||||
this.hideSelectionScroll();
|
||||
var options = unitTypes.vehicles[group];
|
||||
options.sort();
|
||||
this.showSelectionScroll(e, options, (unitType: string) => {
|
||||
this.hideSelectionWheel();
|
||||
this.hideSelectionScroll();
|
||||
spawnGroundUnit(unitType, e.latlng, getActiveCoalition());
|
||||
});
|
||||
}
|
||||
}
|
||||
66
client/src/missiondata/airbasemarker.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import * as L from 'leaflet'
|
||||
|
||||
export interface AirbaseOptions
|
||||
{
|
||||
name: string,
|
||||
position: L.LatLng,
|
||||
src: string
|
||||
}
|
||||
|
||||
export class AirbaseMarker extends L.Marker
|
||||
{
|
||||
#name: string = "";
|
||||
#coalitionID: number = -1;
|
||||
|
||||
constructor(options: AirbaseOptions)
|
||||
{
|
||||
super(options.position, { riseOnHover: true });
|
||||
|
||||
this.#name = options.name;
|
||||
|
||||
var icon = new L.DivIcon({
|
||||
html: `<table class="airbasemarker-container" id="container">
|
||||
<tr>
|
||||
<td>
|
||||
<img class="airbasemarker-icon" id="icon" src="${options.src}">
|
||||
<div class="airbasemarker-name" id="name">${options.name}</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>`,
|
||||
className: 'airbase-marker'}); // Set the marker, className must be set to avoid white square
|
||||
this.setIcon(icon);
|
||||
}
|
||||
|
||||
setCoalitionID(coalitionID: number)
|
||||
{
|
||||
this.#coalitionID = coalitionID;
|
||||
var element = this.getElement();
|
||||
if (element != null)
|
||||
{
|
||||
var img = element.querySelector("#icon");
|
||||
if (img != null)
|
||||
{
|
||||
img.classList.remove("airbasemarker-icon-blue");
|
||||
img.classList.remove("airbasemarker-icon-red");
|
||||
if (this.#coalitionID == 2)
|
||||
{
|
||||
img.classList.add("airbasemarker-icon-blue");
|
||||
}
|
||||
else if (this.#coalitionID == 1)
|
||||
{
|
||||
img.classList.add("airbasemarker-icon-red");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getName()
|
||||
{
|
||||
return this.#name;
|
||||
}
|
||||
|
||||
getCoalitionID()
|
||||
{
|
||||
return this.#coalitionID;
|
||||
}
|
||||
}
|
||||
65
client/src/missiondata/missiondata.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { Marker, LatLng } from "leaflet";
|
||||
import { getMap } from "..";
|
||||
import { SpawnEvent } from "../map/map";
|
||||
import { AirbaseMarker } from "./airbasemarker";
|
||||
|
||||
export class MissionData
|
||||
{
|
||||
//#bullseye : any; //TODO declare interface
|
||||
//#bullseyeMarker : Marker;
|
||||
#airbases : any; //TODO declare interface
|
||||
#airbasesMarkers: {[name: string]: AirbaseMarker};
|
||||
|
||||
constructor()
|
||||
{
|
||||
//this.#bullseye = undefined;
|
||||
//this.#bullseyeMarker = undefined;
|
||||
this.#airbasesMarkers = {};
|
||||
}
|
||||
|
||||
update(data: any)
|
||||
{
|
||||
//this.#bullseye = data.missionData.bullseye;
|
||||
this.#airbases = data.airbases;
|
||||
//this.#drawBullseye();
|
||||
this.#drawAirbases();
|
||||
}
|
||||
|
||||
//#drawBullseye()
|
||||
//{
|
||||
// if (this.#bullseyeMarker === undefined)
|
||||
// {
|
||||
// this.#bullseyeMarker = new Marker([this.#bullseye.lat, this.#bullseye.lng]).addTo(map.getMap());
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// this.#bullseyeMarker.setLatLng(new LatLng(this.#bullseye.lat, this.#bullseye.lng));
|
||||
// }
|
||||
//}
|
||||
|
||||
#drawAirbases()
|
||||
{
|
||||
for (let idx in this.#airbases)
|
||||
{
|
||||
var airbase = this.#airbases[idx]
|
||||
if (this.#airbasesMarkers[idx] === undefined)
|
||||
{
|
||||
this.#airbasesMarkers[idx] = new AirbaseMarker({
|
||||
position: new LatLng(airbase.lat, airbase.lng),
|
||||
name: airbase.callsign,
|
||||
src: "images/airbase.png"}).addTo(getMap());
|
||||
this.#airbasesMarkers[idx].on('click', (e) => this.#onAirbaseClick(e));
|
||||
}
|
||||
else
|
||||
{
|
||||
this.#airbasesMarkers[idx].setCoalitionID(airbase.coalition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#onAirbaseClick(e: any)
|
||||
{
|
||||
var spawnEvent: SpawnEvent = {x: e.originalEvent.x, y: e.originalEvent.y, latlng: e.latlng, airbaseName: e.sourceTarget.getName(), coalitionID: e.sourceTarget.getCoalitionID()};
|
||||
getMap().spawnFromAirbase(spawnEvent);
|
||||
}
|
||||
}
|
||||
@ -1,37 +1,40 @@
|
||||
export function distance(lat1, lon1, lat2, lon2)
|
||||
{
|
||||
export function distance(lat1: number, lon1: number, lat2: number, lon2: number) {
|
||||
const R = 6371e3; // metres
|
||||
const φ1 = deg2rad(lat1); // φ, λ in radians
|
||||
const φ2 = deg2rad(lat2);
|
||||
const Δφ = deg2rad(lat2-lat1);
|
||||
const Δλ = deg2rad(lon2-lon1);
|
||||
const Δφ = deg2rad(lat2 - lat1);
|
||||
const Δλ = deg2rad(lon2 - lon1);
|
||||
|
||||
const a = Math.sin(Δφ/2) * Math.sin(Δφ/2) + Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ/2) * Math.sin(Δλ/2);
|
||||
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
|
||||
const a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) + Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2);
|
||||
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||
|
||||
const d = R * c; // in metres
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
export function bearing(lat1, lon1, lat2, lon2)
|
||||
{
|
||||
export function bearing(lat1: number, lon1: number, lat2: number, lon2: number) {
|
||||
const φ1 = deg2rad(lat1); // φ, λ in radians
|
||||
const φ2 = deg2rad(lat2);
|
||||
const λ1 = deg2rad(lon1); // φ, λ in radians
|
||||
const λ2 = deg2rad(lon2);
|
||||
const y = Math.sin(λ2-λ1) * Math.cos(φ2);
|
||||
const x = Math.cos(φ1)*Math.sin(φ2) - Math.sin(φ1)*Math.cos(φ2)*Math.cos(λ2-λ1);
|
||||
const y = Math.sin(λ2 - λ1) * Math.cos(φ2);
|
||||
const x = Math.cos(φ1) * Math.sin(φ2) - Math.sin(φ1) * Math.cos(φ2) * Math.cos(λ2 - λ1);
|
||||
const θ = Math.atan2(y, x);
|
||||
const brng = (rad2deg(θ) + 360) % 360; // in degrees
|
||||
|
||||
return brng;
|
||||
}
|
||||
|
||||
const zeroPad = (num, places) => String(num).padStart(places, '0')
|
||||
const zeroPad = function (num: number, places: number) {
|
||||
var string = String(num);
|
||||
while (string.length < places) {
|
||||
string += "0";
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
export function ConvertDDToDMS(D, lng)
|
||||
{
|
||||
export function ConvertDDToDMS(D: number, lng: boolean) {
|
||||
var dir = D < 0 ? (lng ? "W" : "S") : lng ? "E" : "N";
|
||||
var deg = 0 | (D < 0 ? (D = -D) : D);
|
||||
var min = 0 | (((D += 1e-9) % 1) * 60);
|
||||
@ -44,14 +47,12 @@ export function ConvertDDToDMS(D, lng)
|
||||
return dir + zeroPad(deg, 2) + "°" + zeroPad(min, 2) + "'" + zeroPad(sec, 2) + "." + zeroPad(dec, 2) + "\"";
|
||||
}
|
||||
|
||||
export function deg2rad(deg)
|
||||
{
|
||||
export function deg2rad(deg: number) {
|
||||
var pi = Math.PI;
|
||||
return deg * (pi/180);
|
||||
return deg * (pi / 180);
|
||||
}
|
||||
|
||||
export function rad2deg(rad)
|
||||
{
|
||||
export function rad2deg(rad: number) {
|
||||
var pi = Math.PI;
|
||||
return rad / (pi/180);
|
||||
return rad / (pi / 180);
|
||||
}
|
||||
25
client/src/panels/connectionstatuspanel.ts
Normal file
@ -0,0 +1,25 @@
|
||||
export class ConnectionStatusPanel {
|
||||
#element: HTMLElement
|
||||
|
||||
constructor(ID: string) {
|
||||
this.#element = <HTMLElement>document.getElementById(ID);
|
||||
}
|
||||
|
||||
update(connected: boolean) {
|
||||
if (this.#element != null) {
|
||||
var div = this.#element.querySelector("#status-string");
|
||||
if (div != null) {
|
||||
if (connected) {
|
||||
div.innerHTML = "Connected";
|
||||
div.classList.add("olympus-status-connected");
|
||||
div.classList.remove("olympus-status-disconnected");
|
||||
}
|
||||
else {
|
||||
div.innerHTML = "Disconnected";
|
||||
div.classList.add("olympus-status-disconnected");
|
||||
div.classList.remove("olympus-status-connected");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
53
client/src/panels/unitinfopanel.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { ConvertDDToDMS, rad2deg } from "../other/utils";
|
||||
import { Unit } from "../units/unit";
|
||||
|
||||
export class UnitInfoPanel {
|
||||
#element: HTMLElement
|
||||
#display: string;
|
||||
|
||||
constructor(ID: string) {
|
||||
this.#element = <HTMLElement>document.getElementById(ID);
|
||||
this.#display = '';
|
||||
if (this.#element != null) {
|
||||
this.#display = this.#element.style.display;
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
|
||||
show() {
|
||||
this.#element.style.display = this.#display;
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.#element.style.display = "none";
|
||||
}
|
||||
|
||||
update(unit: Unit) {
|
||||
if (this.#element != null) {
|
||||
var els = this.#element.getElementsByClassName("js-loadout-element");
|
||||
while (els.length > 0)
|
||||
this.#element.querySelector("#loadout-data")?.removeChild(els[0]);
|
||||
|
||||
for (let index in unit.ammo) {
|
||||
var ammo = unit.ammo[index];
|
||||
var displayName = ammo.desc.displayName;
|
||||
var amount = ammo.count;
|
||||
var el = document.createElement("div")
|
||||
el.classList.add("js-loadout-element", "rectangular-container-dark")
|
||||
el.innerHTML = amount + "x" + displayName;
|
||||
this.#element.querySelector("#loadout-data")?.appendChild(el);
|
||||
}
|
||||
|
||||
this.#element.querySelector("#unit-name")!.innerHTML = unit.unitName;
|
||||
this.#element.querySelector("#group-name")!.innerHTML = unit.groupName;
|
||||
this.#element.querySelector("#name")!.innerHTML = unit.name;
|
||||
this.#element.querySelector("#heading")!.innerHTML = String(Math.floor(rad2deg(unit.heading)) + " °");
|
||||
this.#element.querySelector("#altitude")!.innerHTML = String(Math.floor(unit.altitude / 0.3048) + " ft");
|
||||
this.#element.querySelector("#ground-speed")!.innerHTML = String(Math.floor(unit.speed * 1.94384) + " kts");
|
||||
this.#element.querySelector("#fuel")!.innerHTML = String(unit.fuel + "%");
|
||||
this.#element.querySelector("#latitude")!.innerHTML = ConvertDDToDMS(unit.latitude, false);
|
||||
this.#element.querySelector("#longitude")!.innerHTML = ConvertDDToDMS(unit.longitude, true);
|
||||
this.#element.querySelector("#task")!.innerHTML = unit.currentTask !== ""? unit.currentTask: "Not controlled";
|
||||
}
|
||||
}
|
||||
}
|
||||
1
client/src/units/payloadNames.ts
Normal file
442
client/src/units/unit.ts
Normal file
@ -0,0 +1,442 @@
|
||||
import { Marker, LatLng, Polyline, Icon } from 'leaflet';
|
||||
import { ConvertDDToDMS } from '../other/utils';
|
||||
import { getMap, getUnitsManager, getVisibilitySettings } from '..';
|
||||
import { UnitMarker, MarkerOptions, AircraftMarker, HelicopterMarker, GroundUnitMarker, NavyUnitMarker, WeaponMarker } from './unitmarker';
|
||||
import { addDestination, attackUnit, changeAltitude, changeSpeed } from '../dcs/dcs';
|
||||
|
||||
var pathIcon = new Icon({
|
||||
iconUrl: 'images/marker-icon.png',
|
||||
shadowUrl: 'images/marker-shadow.png',
|
||||
iconAnchor: [13, 41]
|
||||
});
|
||||
|
||||
export class Unit {
|
||||
ID: number = -1;
|
||||
leader: boolean = false;
|
||||
wingman: boolean = false;
|
||||
wingmen: Unit[] = [];
|
||||
formation: string = "";
|
||||
name: string = "";
|
||||
unitName: string = "";
|
||||
groupName: string = "";
|
||||
latitude: number = 0;
|
||||
longitude: number = 0;
|
||||
altitude: number = 0;
|
||||
heading: number = 0;
|
||||
speed: number = 0;
|
||||
coalitionID: number = -1;
|
||||
alive: boolean = true;
|
||||
currentTask: string = "";
|
||||
fuel: number = 0;
|
||||
type: any = null;
|
||||
flags: any = null;
|
||||
activePath: any = null;
|
||||
ammo: any = null;
|
||||
targets: any = null;
|
||||
|
||||
#selectable: boolean;
|
||||
#selected: boolean = false;
|
||||
#preventClick: boolean = false;
|
||||
#pathMarkers: Marker[] = [];
|
||||
#pathPolyline: Polyline;
|
||||
#targetsPolylines: Polyline[];
|
||||
#marker: UnitMarker;
|
||||
#timer: number = 0;
|
||||
|
||||
static getConstructor(name: string) {
|
||||
if (name === "GroundUnit") return GroundUnit;
|
||||
if (name === "Aircraft") return Aircraft;
|
||||
if (name === "Helicopter") return Helicopter;
|
||||
if (name === "Missile") return Missile;
|
||||
if (name === "Bomb") return Bomb;
|
||||
if (name === "NavyUnit") return NavyUnit;
|
||||
}
|
||||
|
||||
constructor(ID: number, marker: UnitMarker) {
|
||||
this.ID = ID;
|
||||
|
||||
this.#selectable = true;
|
||||
|
||||
/* The marker is set by the inherited class */
|
||||
this.#marker = marker;
|
||||
this.#marker.on('click', (e) => this.#onClick(e));
|
||||
this.#marker.on('dblclick', (e) => this.#onDoubleClick(e));
|
||||
|
||||
this.#pathPolyline = new Polyline([], { color: '#2d3e50', weight: 3, opacity: 0.5, smoothFactor: 1 });
|
||||
this.#pathPolyline.addTo(getMap());
|
||||
this.#targetsPolylines = [];
|
||||
}
|
||||
|
||||
update(response: JSON) {
|
||||
for (let entry in response) {
|
||||
// @ts-ignore
|
||||
this[entry] = response[entry];
|
||||
}
|
||||
|
||||
/* Dead units can't be selected */
|
||||
this.setSelected(this.getSelected() && this.alive)
|
||||
|
||||
this.#updateMarker();
|
||||
|
||||
this.#clearTargets();
|
||||
if (this.getSelected())
|
||||
{
|
||||
this.#drawPath();
|
||||
this.#drawTargets();
|
||||
}
|
||||
else
|
||||
this.#clearPath();
|
||||
|
||||
/*
|
||||
this.wingmen = [];
|
||||
if (response["wingmenIDs"] != null)
|
||||
{
|
||||
for (let ID of response["wingmenIDs"])
|
||||
{
|
||||
this.wingmen.push(unitsManager.getUnitByID(ID));
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
setSelected(selected: boolean) {
|
||||
/* Only alive units can be selected. Some units are not selectable (weapons) */
|
||||
if ((this.alive || !selected) && this.#selectable && this.#selected != selected) {
|
||||
this.#selected = selected;
|
||||
this.#marker.setSelected(selected);
|
||||
getUnitsManager().onUnitSelection();
|
||||
}
|
||||
}
|
||||
|
||||
getSelected() {
|
||||
return this.#selected;
|
||||
}
|
||||
|
||||
setSelectable(selectable: boolean) {
|
||||
this.#selectable = selectable;
|
||||
}
|
||||
|
||||
getSelectable() {
|
||||
return this.#selectable;
|
||||
}
|
||||
|
||||
addDestination(latlng: L.LatLng) {
|
||||
var path: any = {};
|
||||
if (this.activePath != null) {
|
||||
path = this.activePath;
|
||||
path[(Object.keys(path).length + 1).toString()] = latlng;
|
||||
}
|
||||
else {
|
||||
path = { "1": latlng };
|
||||
}
|
||||
addDestination(this.ID, path);
|
||||
}
|
||||
|
||||
clearDestinations() {
|
||||
this.activePath = null;
|
||||
}
|
||||
|
||||
getHidden() {
|
||||
return false;
|
||||
}
|
||||
|
||||
#onClick(e: any) {
|
||||
this.#timer = setTimeout(() => {
|
||||
if (!this.#preventClick) {
|
||||
if (getMap().getState() === 'IDLE' || getMap().getState() === 'MOVE_UNIT' || e.originalEvent.ctrlKey) {
|
||||
if (!e.originalEvent.ctrlKey) {
|
||||
getUnitsManager().deselectAllUnits();
|
||||
}
|
||||
this.setSelected(true);
|
||||
}
|
||||
}
|
||||
this.#preventClick = false;
|
||||
}, 200);
|
||||
}
|
||||
|
||||
#onDoubleClick(e: any) {
|
||||
clearTimeout(this.#timer);
|
||||
this.#preventClick = true;
|
||||
|
||||
var options = [
|
||||
'Attack',
|
||||
]
|
||||
|
||||
//if (!this.leader && !this.wingman) {
|
||||
// options.push({ 'tooltip': 'Create formation', 'src': 'formation.png', 'callback': () => { getMap().hideSelectionWheel(); /*unitsManager.createFormation(this.ID);*/ } });
|
||||
//}
|
||||
|
||||
getMap().showSelectionScroll(e.originalEvent, options, (action: string) => this.#executeAction(action));
|
||||
}
|
||||
|
||||
#executeAction(action: string) {
|
||||
getMap().hideSelectionScroll();
|
||||
if (action === "Attack")
|
||||
getUnitsManager().attackUnit(this.ID);
|
||||
}
|
||||
|
||||
#updateMarker() {
|
||||
/* Add the marker if not present */
|
||||
if (!getMap().hasLayer(this.#marker) && !this.getHidden()) {
|
||||
this.#marker.addTo(getMap());
|
||||
}
|
||||
|
||||
/* Hide the marker if necessary*/
|
||||
if (getMap().hasLayer(this.#marker) && this.getHidden()) {
|
||||
getMap().removeLayer(this.#marker);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.#marker.setLatLng(new LatLng(this.latitude, this.longitude));
|
||||
this.#marker.draw({
|
||||
heading: this.heading,
|
||||
speed: this.speed,
|
||||
altitude: this.altitude,
|
||||
alive: this.alive
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#drawPath() {
|
||||
if (this.activePath != null) {
|
||||
var _points = [];
|
||||
_points.push(new LatLng(this.latitude, this.longitude));
|
||||
|
||||
/* Add markers if missing */
|
||||
while (this.#pathMarkers.length < Object.keys(this.activePath).length) {
|
||||
var marker = new Marker([0, 0], { icon: pathIcon }).addTo(getMap());
|
||||
this.#pathMarkers.push(marker);
|
||||
}
|
||||
|
||||
/* Remove markers if too many */
|
||||
while (this.#pathMarkers.length > Object.keys(this.activePath).length) {
|
||||
getMap().removeLayer(this.#pathMarkers[this.#pathMarkers.length - 1]);
|
||||
this.#pathMarkers.splice(this.#pathMarkers.length - 1, 1)
|
||||
}
|
||||
|
||||
/* Update the position of the existing markers (to avoid creating markers uselessly) */
|
||||
for (let WP in this.activePath) {
|
||||
var destination = this.activePath[WP];
|
||||
this.#pathMarkers[parseInt(WP) - 1].setLatLng([destination.lat, destination.lng]);
|
||||
_points.push(new LatLng(destination.lat, destination.lng));
|
||||
this.#pathPolyline.setLatLngs(_points);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#clearPath() {
|
||||
for (let WP in this.#pathMarkers) {
|
||||
getMap().removeLayer(this.#pathMarkers[WP]);
|
||||
}
|
||||
this.#pathMarkers = [];
|
||||
this.#pathPolyline.setLatLngs([]);
|
||||
}
|
||||
|
||||
|
||||
#drawTargets()
|
||||
{
|
||||
for (let typeIndex in this.targets)
|
||||
{
|
||||
for (let index in this.targets[typeIndex])
|
||||
{
|
||||
var targetData = this.targets[typeIndex][index];
|
||||
var target = getUnitsManager().getUnitByID(targetData.object["id_"])
|
||||
if (target != null){
|
||||
var startLatLng = new LatLng(this.latitude, this.longitude)
|
||||
var endLatLng = new LatLng(target.latitude, target.longitude)
|
||||
|
||||
var color;
|
||||
if (typeIndex === "radar")
|
||||
{
|
||||
color = "#FFFF00";
|
||||
}
|
||||
else if (typeIndex === "visual")
|
||||
{
|
||||
color = "#FF00FF";
|
||||
}
|
||||
else if (typeIndex === "rwr")
|
||||
{
|
||||
color = "#00FF00";
|
||||
}
|
||||
else
|
||||
{
|
||||
color = "#FFFFFF";
|
||||
}
|
||||
var targetPolyline = new Polyline([startLatLng, endLatLng], {color: color, weight: 3, opacity: 1, smoothFactor: 1});
|
||||
targetPolyline.addTo(getMap());
|
||||
this.#targetsPolylines.push(targetPolyline)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#clearTargets()
|
||||
{
|
||||
for (let index in this.#targetsPolylines)
|
||||
{
|
||||
getMap().removeLayer(this.#targetsPolylines[index])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
attackUnit(targetID: number) {
|
||||
/* Call DCS attackUnit function */
|
||||
if (this.ID != targetID) {
|
||||
attackUnit(this.ID, targetID);
|
||||
}
|
||||
else {
|
||||
// TODO: show a message
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
changeSpeed(speedChange: string)
|
||||
{
|
||||
changeSpeed(this.ID, speedChange);
|
||||
}
|
||||
|
||||
changeAltitude(altitudeChange: string)
|
||||
{
|
||||
changeAltitude(this.ID, altitudeChange);
|
||||
}
|
||||
|
||||
/*
|
||||
setformation(formation)
|
||||
{
|
||||
// TODO move in dedicated file
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("PUT", RESTaddress);
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState === 4) {
|
||||
console.log(this.unitName + " formation change: " + formation);
|
||||
}
|
||||
};
|
||||
|
||||
var command = {"ID": this.ID, "formation": formation}
|
||||
var data = {"setFormation": command}
|
||||
|
||||
xhr.send(JSON.stringify(data));
|
||||
}
|
||||
|
||||
setLeader(wingmenIDs)
|
||||
{
|
||||
// TODO move in dedicated file
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("PUT", RESTaddress);
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState === 4) {
|
||||
console.log(this.unitName + " created formation with: " + wingmenIDs);
|
||||
}
|
||||
};
|
||||
|
||||
var command = {"ID": this.ID, "wingmenIDs": wingmenIDs}
|
||||
var data = {"setLeader": command}
|
||||
|
||||
xhr.send(JSON.stringify(data));
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
export class AirUnit extends Unit {
|
||||
getHidden() {
|
||||
if (this.alive)
|
||||
{
|
||||
if (this.flags.user && getVisibilitySettings().user === "hidden")
|
||||
return true
|
||||
else if (!this.flags.user && getVisibilitySettings().ai === "hidden")
|
||||
return true
|
||||
}
|
||||
else
|
||||
return getVisibilitySettings().dead === "hidden"
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class Aircraft extends AirUnit {
|
||||
constructor(ID: number, options: MarkerOptions) {
|
||||
var marker = new AircraftMarker(options);
|
||||
super(ID, marker);
|
||||
}
|
||||
}
|
||||
|
||||
export class Helicopter extends AirUnit {
|
||||
constructor(ID: number, options: MarkerOptions) {
|
||||
var marker = new HelicopterMarker(options);
|
||||
super(ID, marker);
|
||||
}
|
||||
}
|
||||
|
||||
export class GroundUnit extends Unit {
|
||||
constructor(ID: number, options: MarkerOptions) {
|
||||
var marker = new GroundUnitMarker(options);
|
||||
super(ID, marker);
|
||||
}
|
||||
|
||||
getHidden() {
|
||||
if (this.alive)
|
||||
{
|
||||
if (this.flags.user && getVisibilitySettings().user === "hidden")
|
||||
return true
|
||||
else if (!this.flags.user && getVisibilitySettings().ai === "hidden")
|
||||
return true
|
||||
}
|
||||
else
|
||||
return getVisibilitySettings().dead === "hidden"
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class NavyUnit extends Unit {
|
||||
constructor(ID: number, options: MarkerOptions) {
|
||||
var marker = new NavyUnitMarker(options);
|
||||
super(ID, marker);
|
||||
}
|
||||
|
||||
getHidden() {
|
||||
if (this.alive)
|
||||
{
|
||||
if (this.flags.user && getVisibilitySettings().user === "hidden")
|
||||
return true
|
||||
else if (!this.flags.user && getVisibilitySettings().ai === "hidden")
|
||||
return true
|
||||
}
|
||||
else
|
||||
return getVisibilitySettings().dead === "hidden"
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class Weapon extends Unit {
|
||||
constructor(ID: number, marker: UnitMarker)
|
||||
{
|
||||
super(ID, marker);
|
||||
this.setSelectable(false);
|
||||
}
|
||||
|
||||
getHidden() {
|
||||
if (this.alive)
|
||||
{
|
||||
if (!this.flags.user && getVisibilitySettings().weapon === "hidden")
|
||||
return true
|
||||
}
|
||||
else
|
||||
return getVisibilitySettings().dead === "hidden"
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class Missile extends Weapon {
|
||||
constructor(ID: number, options: MarkerOptions) {
|
||||
var marker = new WeaponMarker(options);
|
||||
super(ID, marker);
|
||||
}
|
||||
}
|
||||
|
||||
export class Bomb extends Weapon {
|
||||
constructor(ID: number, options: MarkerOptions) {
|
||||
var marker = new WeaponMarker(options);
|
||||
super(ID, marker);
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
|
||||
|
||||
var unitTypes: any = {};
|
||||
export var unitTypes: any = {};
|
||||
/* NAVY */
|
||||
unitTypes.navy = {};
|
||||
unitTypes.navy.blue = [
|
||||
@ -250,7 +250,7 @@ unitTypes.air.strike = [
|
||||
"Tu-142",
|
||||
]
|
||||
|
||||
unitTypes.air.tank = [
|
||||
unitTypes.air.tanker = [
|
||||
"S-3B Tanker",
|
||||
"KC-135",
|
||||
"IL-78M",
|
||||
276
client/src/units/unitmarker.ts
Normal file
@ -0,0 +1,276 @@
|
||||
import * as L from 'leaflet'
|
||||
import { Symbol } from 'milsymbol'
|
||||
import { getVisibilitySettings } from '..'
|
||||
|
||||
export interface MarkerOptions {
|
||||
unitName: string
|
||||
name: string
|
||||
human: boolean
|
||||
coalitionID: number
|
||||
type: any
|
||||
}
|
||||
|
||||
export interface MarkerData {
|
||||
heading: number
|
||||
speed: number
|
||||
altitude: number
|
||||
alive: boolean
|
||||
}
|
||||
|
||||
export class UnitMarker extends L.Marker {
|
||||
#unitName: string
|
||||
#name: string
|
||||
#human: boolean
|
||||
#alive: boolean = true
|
||||
#selected: boolean = false
|
||||
|
||||
constructor(options: MarkerOptions) {
|
||||
super(new L.LatLng(0, 0), { riseOnHover: true });
|
||||
this.#unitName = options.unitName;
|
||||
this.#name = options.name;
|
||||
this.#human = options.human;
|
||||
|
||||
var symbol = new Symbol(this.#computeMarkerCode(options), { size: 25 });
|
||||
var img = symbol.asCanvas().toDataURL('image/png');
|
||||
|
||||
var coalition = "";
|
||||
if (options.coalitionID == 1)
|
||||
coalition = "red"
|
||||
else if (options.coalitionID == 2)
|
||||
coalition = "blue"
|
||||
else
|
||||
coalition = "neutral"
|
||||
|
||||
var icon = new L.DivIcon({
|
||||
html: `<table class="unit-marker-container" id="container">
|
||||
<tr>
|
||||
<td>
|
||||
<div class="${coalition}" id="background"></div>
|
||||
<div class="${coalition}" id="ring"></div>
|
||||
<div class="unit-marker-icon" id="icon"><img src="${img}"></div>
|
||||
<div class="unit-marker-unitName" id="unitName">${this.#unitName}</div>
|
||||
<div class="unit-marker-altitude" id="altitude"></div>
|
||||
<div class="unit-marker-speed" id="speed"></div>
|
||||
<div class="unit-marker-name" id="name">${this.#name}</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>`,
|
||||
className: 'unit-marker'
|
||||
});
|
||||
this.setIcon(icon);
|
||||
}
|
||||
|
||||
onAdd(map: L.Map): this {
|
||||
super.onAdd(map);
|
||||
this.addEventListener('mouseover', function (e: any) { e.target?.setHovered(true); });
|
||||
this.addEventListener('mouseout', function (e: any) { e.target?.setHovered(false); });
|
||||
return this
|
||||
}
|
||||
|
||||
draw(data: MarkerData) {
|
||||
this.#alive = data.alive;
|
||||
var element = this.getElement();
|
||||
if (element != null) {
|
||||
var nameDiv = <HTMLElement>element.querySelector("#name");
|
||||
var unitNameDiv = <HTMLElement>element.querySelector("#unitName");
|
||||
var container = <HTMLElement>element.querySelector("#container");
|
||||
var icon = <HTMLElement>element.querySelector("#icon");
|
||||
var altitudeDiv = <HTMLElement>element.querySelector("#altitude");
|
||||
var speedDiv = <HTMLElement>element.querySelector("#speed");
|
||||
|
||||
/* If visibility is full show all labels */
|
||||
nameDiv.style.display = '';
|
||||
unitNameDiv.style.display = '';
|
||||
altitudeDiv.style.display = '';
|
||||
speedDiv.style.display = '';
|
||||
|
||||
/* If visibility is partial shown only icon and unit name. If none, shown only icon. */
|
||||
if (this.getVisibility() === "partial" || this.getVisibility() === "none")
|
||||
{
|
||||
unitNameDiv.style.display = 'none';
|
||||
altitudeDiv.style.display = 'none';
|
||||
speedDiv.style.display = 'none';
|
||||
}
|
||||
if (this.getVisibility() === "none")
|
||||
nameDiv.style.display = 'none';
|
||||
|
||||
nameDiv.style.left = (-(nameDiv.offsetWidth - container.offsetWidth) / 2) + "px";
|
||||
unitNameDiv.style.left = (-(unitNameDiv.offsetWidth - container.offsetWidth) / 2) + "px";
|
||||
|
||||
icon.style.transform = "rotate(" + data.heading + "rad)";
|
||||
altitudeDiv.innerHTML = String(Math.round(data.altitude / 0.3048 / 100) / 10);
|
||||
speedDiv.innerHTML = String(Math.round(data.speed * 1.94384));
|
||||
|
||||
if (!this.#alive)
|
||||
{
|
||||
this.getElement()?.querySelector("#icon")?.classList.add("unit-marker-dead");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setSelected(selected: boolean) {
|
||||
this.#selected = selected;
|
||||
this.getElement()?.querySelector("#icon")?.classList.remove("unit-marker-hovered");
|
||||
this.getElement()?.querySelector("#ring")?.classList.toggle("unit-marker-selected", selected);
|
||||
this.getElement()?.querySelector("#background")?.classList.toggle("unit-marker-selected", selected);
|
||||
}
|
||||
|
||||
getSelected() {
|
||||
return this.#selected;
|
||||
}
|
||||
|
||||
setHovered(hovered: boolean) {
|
||||
this.getElement()?.querySelector("#icon")?.classList.toggle("unit-marker-hovered", hovered && this.#alive);
|
||||
}
|
||||
|
||||
getHuman() {
|
||||
return this.#human;
|
||||
}
|
||||
|
||||
getAlive() {
|
||||
return this.#alive;
|
||||
}
|
||||
|
||||
getVisibility() {
|
||||
return "full";
|
||||
}
|
||||
|
||||
#computeMarkerCode(options: MarkerOptions) {
|
||||
var identity = "00";
|
||||
var set = "00";
|
||||
var entity = "00";
|
||||
var entityType = "00";
|
||||
var entitySubtype = "00";
|
||||
var sectorOneModifier = "00";
|
||||
var sectorTwoModifier = "00";
|
||||
|
||||
/* Identity */
|
||||
if (options.coalitionID == 1)
|
||||
identity = "06" /* Hostile */
|
||||
else if (options.coalitionID == 2)
|
||||
identity = "03" /* Friendly */
|
||||
else
|
||||
identity = "04" /* Neutral */
|
||||
|
||||
/* Air */
|
||||
if (options.type.level1 == 1) {
|
||||
set = "01"
|
||||
entity = "11"
|
||||
if (options.type.level2 == 1)
|
||||
entityType = "01"
|
||||
else if (options.type.level2 == 1)
|
||||
entityType = "02"
|
||||
|
||||
if (options.type.level3 == 1)
|
||||
entitySubtype = "04";
|
||||
else if (options.type.level3 == 2)
|
||||
entitySubtype = "05";
|
||||
else if (options.type.level3 == 3)
|
||||
entitySubtype = "04";
|
||||
else if (options.type.level3 == 4)
|
||||
entitySubtype = "02";
|
||||
else if (options.type.level3 == 5)
|
||||
entitySubtype = "00";
|
||||
else if (options.type.level3 == 6)
|
||||
entitySubtype = "00";
|
||||
}
|
||||
|
||||
/* Ground */
|
||||
else if (options.type.level1 == 2)
|
||||
{
|
||||
set = "10"
|
||||
entity = "12"
|
||||
entityType = "05"
|
||||
}
|
||||
/* Navy */
|
||||
else if (options.type.level1 == 3)
|
||||
set = "30"
|
||||
/* Weapon */
|
||||
else if (options.type.level1 == 4)
|
||||
{
|
||||
set = "02"
|
||||
entity = "11"
|
||||
if (options.type.level3 == 7)
|
||||
{
|
||||
sectorOneModifier = "01"
|
||||
sectorTwoModifier = "01"
|
||||
}
|
||||
else if (options.type.level3 == 8)
|
||||
{
|
||||
sectorOneModifier = "01"
|
||||
sectorTwoModifier = "02"
|
||||
}
|
||||
else if (options.type.level3 == 34)
|
||||
{
|
||||
sectorOneModifier = "02"
|
||||
sectorTwoModifier = "01"
|
||||
}
|
||||
else if (options.type.level3 == 11)
|
||||
{
|
||||
sectorOneModifier = "02"
|
||||
sectorTwoModifier = "02"
|
||||
}
|
||||
}
|
||||
|
||||
return `10${identity}${set}0000${entity}${entityType}${entitySubtype}${sectorOneModifier}${sectorTwoModifier}`
|
||||
}
|
||||
}
|
||||
|
||||
export class AirUnitMarker extends UnitMarker {
|
||||
getVisibility() {
|
||||
if (this.getSelected())
|
||||
return "full";
|
||||
|
||||
if (this.getHuman())
|
||||
return getVisibilitySettings().user;
|
||||
else if (!this.getAlive())
|
||||
return "none";
|
||||
else
|
||||
return getVisibilitySettings().ai;
|
||||
}
|
||||
}
|
||||
|
||||
export class AircraftMarker extends AirUnitMarker {
|
||||
}
|
||||
|
||||
export class HelicopterMarker extends AirUnitMarker {
|
||||
}
|
||||
|
||||
export class GroundUnitMarker extends UnitMarker {
|
||||
/* Are user driven units recognized as human? */
|
||||
getVisibility() {
|
||||
if (this.getSelected())
|
||||
return "full";
|
||||
|
||||
if (this.getHuman())
|
||||
return getVisibilitySettings().user;
|
||||
else if (!this.getAlive())
|
||||
return "none";
|
||||
else
|
||||
return getVisibilitySettings().ai;
|
||||
}
|
||||
}
|
||||
|
||||
export class NavyUnitMarker extends UnitMarker {
|
||||
getVisibility() {
|
||||
if (this.getSelected())
|
||||
return "full";
|
||||
|
||||
if (!this.getAlive())
|
||||
return "none";
|
||||
else
|
||||
return getVisibilitySettings().ai;
|
||||
}
|
||||
}
|
||||
|
||||
export class WeaponMarker extends UnitMarker {
|
||||
getVisibility() {
|
||||
if (this.getSelected())
|
||||
return "full";
|
||||
|
||||
if (!this.getAlive())
|
||||
return "none";
|
||||
else
|
||||
return getVisibilitySettings().weapon;
|
||||
}
|
||||
}
|
||||
231
client/src/units/unitsmanager.ts
Normal file
@ -0,0 +1,231 @@
|
||||
import { LatLng, LatLngBounds } from "leaflet";
|
||||
import { getMap, getUnitInfoPanel } from "..";
|
||||
import { Unit, GroundUnit } from "./unit";
|
||||
|
||||
export class UnitsManager {
|
||||
#units: { [ID: number]: Unit };
|
||||
#copiedUnits: Unit[];
|
||||
|
||||
constructor() {
|
||||
this.#units = {};
|
||||
this.#copiedUnits = [];
|
||||
}
|
||||
|
||||
addUnit(ID: number, data: any) {
|
||||
/* The name of the unit category is exactly the same as the constructor name */
|
||||
var constructor = Unit.getConstructor(data.category);
|
||||
if (constructor != undefined) {
|
||||
var options = {
|
||||
unitName: data.unitName,
|
||||
name: data.name,
|
||||
human: data.human,
|
||||
coalitionID: data.coalitionID,
|
||||
type: data.type
|
||||
}
|
||||
this.#units[ID] = new constructor(ID, options);
|
||||
}
|
||||
}
|
||||
|
||||
getUnitByID(ID: number) {
|
||||
return this.#units[ID];
|
||||
}
|
||||
|
||||
removeUnit(ID: number) {
|
||||
|
||||
}
|
||||
|
||||
deselectAllUnits() {
|
||||
for (let ID in this.#units) {
|
||||
this.#units[ID].setSelected(false);
|
||||
}
|
||||
}
|
||||
|
||||
update(data: any) {
|
||||
for (let ID in data["units"]) {
|
||||
/* Create the unit if missing from the local array, then update the data. Drawing is handled by leaflet. */
|
||||
if (!(ID in this.#units)) {
|
||||
this.addUnit(parseInt(ID), data["units"][ID]);
|
||||
}
|
||||
this.#units[parseInt(ID)].update(data["units"][ID]);
|
||||
}
|
||||
|
||||
if (this.getSelectedUnits().length == 1) {
|
||||
getUnitInfoPanel().show();
|
||||
getUnitInfoPanel().update(this.getSelectedUnits()[0]);
|
||||
}
|
||||
else {
|
||||
getUnitInfoPanel().hide();
|
||||
}
|
||||
}
|
||||
|
||||
onUnitSelection() {
|
||||
if (this.getSelectedUnits().length > 0) {
|
||||
getMap().setState("MOVE_UNIT");
|
||||
//unitControlPanel.setEnabled(true);
|
||||
}
|
||||
else {
|
||||
getMap().setState("IDLE");
|
||||
//unitControlPanel.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
selectFromBounds(bounds: LatLngBounds)
|
||||
{
|
||||
this.deselectAllUnits();
|
||||
for (let ID in this.#units)
|
||||
{
|
||||
var latlng = new LatLng(this.#units[ID].latitude, this.#units[ID].longitude);
|
||||
if (bounds.contains(latlng))
|
||||
{
|
||||
this.#units[ID].setSelected(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getSelectedUnits() {
|
||||
var selectedUnits = [];
|
||||
for (let ID in this.#units) {
|
||||
if (this.#units[ID].getSelected()) {
|
||||
selectedUnits.push(this.#units[ID]);
|
||||
}
|
||||
}
|
||||
return selectedUnits;
|
||||
}
|
||||
|
||||
addDestination(latlng: L.LatLng) {
|
||||
var selectedUnits = this.getSelectedUnits();
|
||||
for (let idx in selectedUnits) {
|
||||
var commandedUnit = selectedUnits[idx];
|
||||
//if (selectedUnits[idx].wingman)
|
||||
//{
|
||||
// commandedUnit = this.getLeader(selectedUnits[idx].ID);
|
||||
//}
|
||||
commandedUnit.addDestination(latlng);
|
||||
}
|
||||
}
|
||||
|
||||
clearDestinations() {
|
||||
var selectedUnits = this.getSelectedUnits();
|
||||
for (let idx in selectedUnits) {
|
||||
var commandedUnit = selectedUnits[idx];
|
||||
//if (selectedUnits[idx].wingman)
|
||||
//{
|
||||
// commandedUnit = this.getLeader(selectedUnits[idx].ID);
|
||||
//}
|
||||
commandedUnit.clearDestinations();
|
||||
}
|
||||
}
|
||||
|
||||
selectedUnitsChangeSpeed(speedChange: string)
|
||||
{
|
||||
var selectedUnits = this.getSelectedUnits();
|
||||
for (let idx in selectedUnits)
|
||||
{
|
||||
selectedUnits[idx].changeSpeed(speedChange);
|
||||
}
|
||||
}
|
||||
|
||||
selectedUnitsChangeAltitude(altitudeChange: string)
|
||||
{
|
||||
var selectedUnits = this.getSelectedUnits();
|
||||
for (let idx in selectedUnits)
|
||||
{
|
||||
selectedUnits[idx].changeAltitude(altitudeChange);
|
||||
}
|
||||
}
|
||||
|
||||
// handleKeyEvent(e)
|
||||
// {
|
||||
// if (e.originalEvent.code === 'KeyC' && e.originalEvent.ctrlKey)
|
||||
// {
|
||||
// this.copyUnits();
|
||||
// }
|
||||
// else if (e.originalEvent.code === 'KeyV' && e.originalEvent.ctrlKey)
|
||||
// {
|
||||
// this.pasteUnits();
|
||||
// }
|
||||
// }
|
||||
|
||||
// copyUnits()
|
||||
// {
|
||||
// this.#copiedUnits = this.getSelectedUnits();
|
||||
// }
|
||||
|
||||
// pasteUnits()
|
||||
// {
|
||||
// for (let idx in this.#copiedUnits)
|
||||
// {
|
||||
// var unit = this.#copiedUnits[idx];
|
||||
// cloneUnit(unit.ID);
|
||||
// }
|
||||
// }
|
||||
|
||||
attackUnit(ID: number) {
|
||||
var selectedUnits = this.getSelectedUnits();
|
||||
for (let idx in selectedUnits) {
|
||||
/* If a unit is a wingman, send the command to its leader */
|
||||
var commandedUnit = selectedUnits[idx];
|
||||
//if (selectedUnits[idx].wingman)
|
||||
//{
|
||||
// commandedUnit = this.getLeader(selectedUnits[idx].ID);
|
||||
//}
|
||||
commandedUnit.attackUnit(ID);
|
||||
}
|
||||
}
|
||||
|
||||
// createFormation(ID)
|
||||
// {
|
||||
// var selectedUnits = this.getSelectedUnits();
|
||||
// var wingmenIDs = [];
|
||||
// for (let idx in selectedUnits)
|
||||
// {
|
||||
// if (selectedUnits[idx].wingman)
|
||||
// {
|
||||
// showMessage(selectedUnits[idx].unitName + " is already in a formation.");
|
||||
// return;
|
||||
// }
|
||||
// else if (selectedUnits[idx].leader)
|
||||
// {
|
||||
// showMessage(selectedUnits[idx].unitName + " is already in a formation.");
|
||||
// return;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// /* TODO
|
||||
// if (selectedUnits[idx].category !== this.getUnitByID(ID).category)
|
||||
// {
|
||||
// showMessage("All units must be of the same category to create a formation.");
|
||||
// }
|
||||
// */
|
||||
// if (selectedUnits[idx].ID != ID)
|
||||
// {
|
||||
// wingmenIDs.push(selectedUnits[idx].ID);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if (wingmenIDs.length > 0)
|
||||
// {
|
||||
// this.getUnitByID(ID).setLeader(wingmenIDs);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// showMessage("At least 2 units must be selected to create a formation.");
|
||||
// }
|
||||
// }
|
||||
|
||||
// getLeader(ID)
|
||||
// {
|
||||
// for (let idx in this.#units)
|
||||
// {
|
||||
// var unit = this.#units[idx];
|
||||
// if (unit.leader)
|
||||
// {
|
||||
// if (unit.wingmen.includes(this.getUnitByID(ID)))
|
||||
// {
|
||||
// return unit;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// showMessage("Error: no leader found for this unit")
|
||||
// }
|
||||
}
|
||||
103
client/tsconfig.json
Normal file
@ -0,0 +1,103 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
/* Language and Environment */
|
||||
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
/* Modules */
|
||||
"module": "commonjs", /* Specify what module code is generated. */
|
||||
"rootDir": "./src", /* Specify the root folder within your source files. */
|
||||
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
"types": [
|
||||
"leaflet",
|
||||
"geojson"
|
||||
], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
// "outDir": "./", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
/* Type Checking */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
]
|
||||
}
|
||||