mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Committing so I don't lose work.
This commit is contained in:
parent
853af46483
commit
093ab75e8f
26
client/package-lock.json
generated
26
client/package-lock.json
generated
@ -21,9 +21,11 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/gtag.js": "^0.0.12",
|
||||
"@types/sortablejs": "^1.15.0",
|
||||
"browserify": "^17.0.0",
|
||||
"concurrently": "^7.6.0",
|
||||
"nodemon": "^2.0.20",
|
||||
"sortablejs": "^1.15.0",
|
||||
"tsify": "^5.0.4",
|
||||
"typescript": "^4.9.4",
|
||||
"watchify": "^4.0.0"
|
||||
@ -48,6 +50,12 @@
|
||||
"@types/geojson": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/sortablejs": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/sortablejs/-/sortablejs-1.15.0.tgz",
|
||||
"integrity": "sha512-qrhtM7M41EhH4tZQTNw2/RJkxllBx3reiJpTbgWCM2Dx0U1sZ6LwKp9lfNln9uqE26ZMKUaPEYaD4rzvOWYtZw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/abbrev": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||
@ -2621,6 +2629,12 @@
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/sortablejs": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.0.tgz",
|
||||
"integrity": "sha512-bv9qgVMjUMf89wAvM6AxVvS/4MX3sPeN0+agqShejLU5z5GX4C75ow1O2e5k4L6XItUyAK3gH6AxSbXrOM5e8w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||
@ -3248,6 +3262,12 @@
|
||||
"@types/geojson": "*"
|
||||
}
|
||||
},
|
||||
"@types/sortablejs": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/sortablejs/-/sortablejs-1.15.0.tgz",
|
||||
"integrity": "sha512-qrhtM7M41EhH4tZQTNw2/RJkxllBx3reiJpTbgWCM2Dx0U1sZ6LwKp9lfNln9uqE26ZMKUaPEYaD4rzvOWYtZw==",
|
||||
"dev": true
|
||||
},
|
||||
"abbrev": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||
@ -5329,6 +5349,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sortablejs": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.0.tgz",
|
||||
"integrity": "sha512-bv9qgVMjUMf89wAvM6AxVvS/4MX3sPeN0+agqShejLU5z5GX4C75ow1O2e5k4L6XItUyAK3gH6AxSbXrOM5e8w==",
|
||||
"dev": true
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||
|
||||
@ -23,9 +23,11 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/gtag.js": "^0.0.12",
|
||||
"@types/sortablejs": "^1.15.0",
|
||||
"browserify": "^17.0.0",
|
||||
"concurrently": "^7.6.0",
|
||||
"nodemon": "^2.0.20",
|
||||
"sortablejs": "^1.15.0",
|
||||
"tsify": "^5.0.4",
|
||||
"typescript": "^4.9.4",
|
||||
"watchify": "^4.0.0"
|
||||
|
||||
27
client/public/images/buttons/atc.svg
Normal file
27
client/public/images/buttons/atc.svg
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="64.000000pt" height="64.000000pt" viewBox="0 0 64.000000 64.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
|
||||
<g transform="translate(0.000000,64.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M285 590 c-11 -12 -31 -20 -51 -20 -23 0 -37 -6 -44 -20 -6 -11 -17
|
||||
-20 -25 -20 -7 0 -16 -4 -20 -10 -10 -16 14 -174 28 -185 7 -5 16 -18 20 -27
|
||||
3 -10 16 -18 27 -18 13 0 20 -7 20 -20 0 -11 5 -20 10 -20 6 0 10 -43 10 -110
|
||||
l0 -110 60 0 60 0 0 110 c0 67 4 110 10 110 6 0 10 9 10 20 0 13 7 20 20 20
|
||||
11 0 24 8 27 18 4 9 13 22 20 27 14 11 38 169 28 185 -4 6 -13 10 -20 10 -8 0
|
||||
-19 9 -25 20 -7 14 -21 20 -44 20 -20 0 -40 8 -51 20 -10 11 -26 20 -35 20 -9
|
||||
0 -25 -9 -35 -20z m60 -10 c4 -6 -7 -10 -25 -10 -18 0 -29 4 -25 10 3 6 15 10
|
||||
25 10 10 0 22 -4 25 -10z m90 -40 c4 -6 -37 -10 -115 -10 -78 0 -119 4 -115
|
||||
10 4 6 53 10 115 10 62 0 111 -4 115 -10z m-205 -110 c0 -78 -1 -80 -24 -80
|
||||
-22 0 -25 5 -31 48 -3 26 -8 62 -11 80 -5 31 -4 32 31 32 l35 0 0 -80z m80 0
|
||||
l0 -80 -30 0 -30 0 0 80 0 80 30 0 30 0 0 -80z m80 0 l0 -80 -30 0 -30 0 0 80
|
||||
0 80 30 0 30 0 0 -80z m86 48 c-3 -18 -8 -54 -11 -80 -6 -43 -9 -48 -31 -48
|
||||
-23 0 -24 2 -24 80 l0 80 35 0 c35 0 36 -1 31 -32z m-46 -158 c0 -6 -43 -10
|
||||
-110 -10 -67 0 -110 4 -110 10 0 6 43 10 110 10 67 0 110 -4 110 -10z m-50
|
||||
-40 c0 -6 -27 -10 -60 -10 -33 0 -60 4 -60 10 0 6 27 10 60 10 33 0 60 -4 60
|
||||
-10z m-20 -130 l0 -100 -40 0 -40 0 0 100 0 100 40 0 40 0 0 -100z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
21
client/public/images/buttons/reorder.svg
Normal file
21
client/public/images/buttons/reorder.svg
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="96.000000pt" height="96.000000pt" viewBox="0 0 96.000000 96.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
|
||||
<g transform="translate(0.000000,96.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M411 846 c-77 -78 -70 -86 69 -86 139 0 146 8 69 86 -29 30 -60 54
|
||||
-69 54 -9 0 -40 -24 -69 -54z"/>
|
||||
<path d="M132 668 c-16 -16 -15 -43 2 -57 9 -8 110 -11 351 -9 309 3 339 4
|
||||
349 21 8 12 8 22 0 35 -10 16 -40 17 -350 20 -256 2 -343 -1 -352 -10z"/>
|
||||
<path d="M132 508 c-16 -16 -15 -43 2 -57 9 -8 110 -11 351 -9 309 3 339 4
|
||||
349 21 8 12 8 22 0 35 -10 16 -40 17 -350 20 -256 2 -343 -1 -352 -10z"/>
|
||||
<path d="M132 348 c-16 -16 -15 -43 2 -57 9 -8 110 -11 351 -9 309 3 339 4
|
||||
349 21 8 12 8 22 0 35 -10 16 -40 17 -350 20 -256 2 -343 -1 -352 -10z"/>
|
||||
<path d="M363 184 c-7 -18 92 -124 117 -124 25 0 124 106 117 124 -9 24 -225
|
||||
24 -234 0z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
211
client/public/stylesheets/atc.css
Normal file
211
client/public/stylesheets/atc.css
Normal file
@ -0,0 +1,211 @@
|
||||
/*** Control panel ***/
|
||||
|
||||
#atc-control-panel {
|
||||
align-self: flex-end;
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
display:flex;
|
||||
margin: 0 0 50px 100px;
|
||||
padding:5px;
|
||||
position: absolute;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.atc-tool {
|
||||
align-self: center;
|
||||
border-radius: 10px;
|
||||
display:none;
|
||||
justify-self: center;
|
||||
padding: 10px;
|
||||
position: absolute;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
|
||||
.atc-enabled .atc-tool {
|
||||
display:flex;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#atc-flight-list {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#atc-flight-list table {
|
||||
color:white;
|
||||
}
|
||||
|
||||
#atc-flight-list table td {
|
||||
padding:0 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#atc-flight-list table td:first-of-type {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#atc-flight-list table tr[data-status='checkedIn'] td {
|
||||
background-color:goldenrod;
|
||||
}
|
||||
|
||||
#atc-flight-list table tr[data-status='readyToTaxi'] td {
|
||||
background-color:darkgreen;
|
||||
}
|
||||
|
||||
#atc-flight-list table button {
|
||||
background-color: #666;
|
||||
border:1px solid white;
|
||||
color:white;
|
||||
font-weight: bold;
|
||||
margin:2px 0;
|
||||
}
|
||||
|
||||
|
||||
.atc-strip-board {
|
||||
align-self: center;
|
||||
display:flex;
|
||||
justify-self: center;
|
||||
position: absolute;
|
||||
z-index: 9999 ;
|
||||
}
|
||||
|
||||
.atc-strip-board-header {
|
||||
display:none;
|
||||
}
|
||||
|
||||
.atc-strip-board-strips {
|
||||
display:flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.atc-strip-board-strip {
|
||||
display:flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
.atc-strip-board-header {
|
||||
background:black;
|
||||
color:white;
|
||||
display:none;
|
||||
justify-content: right;
|
||||
}
|
||||
|
||||
|
||||
.atc-strip-board {
|
||||
display:flex;
|
||||
flex-direction: column;
|
||||
row-gap: 5px;
|
||||
}
|
||||
|
||||
.atc-strip-board-strips {
|
||||
display:flex;
|
||||
flex-direction: column;
|
||||
padding:10px;
|
||||
row-gap: 5px;
|
||||
}
|
||||
|
||||
.atc-strip-board-strips > div {
|
||||
align-items: center;
|
||||
color:white;
|
||||
column-gap: 2px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.atc-strip-board-header > div, .atc-strip-board-strips > div > div {
|
||||
text-align: center;
|
||||
width: 75px;
|
||||
}
|
||||
|
||||
.atc-strip-board-header > .name {
|
||||
width:150px;
|
||||
}
|
||||
|
||||
.atc-strip-board-header > div, .atc-strip-board-strips > div > div {
|
||||
text-align: center;
|
||||
width: 75px;
|
||||
}
|
||||
|
||||
.atc-strip-board-strips > div > .name {
|
||||
text-align: left;
|
||||
width:150px;
|
||||
}
|
||||
|
||||
.atc-strip-board-strips > div {
|
||||
align-items: center;
|
||||
column-gap: 5px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-size:12px;
|
||||
font-weight: 600;
|
||||
padding: 5px;
|
||||
row-gap: 5px;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
|
||||
.atc-strip-board-header, .atc-strip-board-strips > div {
|
||||
align-items: center;
|
||||
background:#FFF3;
|
||||
color:white;
|
||||
column-gap: 5px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-size:12px;
|
||||
font-weight: 600;
|
||||
padding: 5px;
|
||||
row-gap: 5px;
|
||||
}
|
||||
|
||||
.atc-strip-board-header {
|
||||
background:black;
|
||||
color:white;
|
||||
display:none;
|
||||
justify-content: right;
|
||||
}
|
||||
|
||||
.atc-strip-board-strips > div {
|
||||
border-bottom:1px solid black;
|
||||
}
|
||||
|
||||
.atc-strip-board-header > div, .atc-strip-board-strips > div > div {
|
||||
text-align: center;
|
||||
width: 75px;
|
||||
}
|
||||
|
||||
.atc-strip-board-header > .name {
|
||||
width:150px;
|
||||
}
|
||||
|
||||
.atc-strip-board-strips > div > .handle {
|
||||
background: black;
|
||||
border-radius: 50%;
|
||||
cursor:grab;
|
||||
height:10px;
|
||||
width:10px;
|
||||
}
|
||||
|
||||
.atc-strip-board-strips > div > .name {
|
||||
text-align: left;
|
||||
width:150px;
|
||||
}
|
||||
|
||||
.atc-strip-board-strips > div > .warning {
|
||||
background:red;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.atc-strip-board-strips > div > .link-warning {
|
||||
border: 1px solid red;
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
|
||||
}
|
||||
*/
|
||||
106
client/public/stylesheets/olympus.css
Normal file
106
client/public/stylesheets/olympus.css
Normal file
@ -0,0 +1,106 @@
|
||||
/* Variables definitions */
|
||||
:root {
|
||||
--active-coalition-color: var(--blue-coalition-color);
|
||||
--background-color-dark: #202831;
|
||||
--background-color-light: #AAA;
|
||||
--blue-coalition-color: #247be2;
|
||||
--highlight-color: #FFF5;
|
||||
--neutral-coalition-color: whitesmoke;
|
||||
--neutral-coalition-text: #202831;
|
||||
--red-coalition-color: #f32121;
|
||||
--text-color: white;
|
||||
--title-color: #d3e9ff;
|
||||
}
|
||||
|
||||
|
||||
* {
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
||||
html {
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.ol-panel {
|
||||
background-color: var(--background-color-dark);
|
||||
border-radius: 15px;
|
||||
box-shadow: 0px 2px 5px #000A;
|
||||
color:white;
|
||||
font-size: 12px;
|
||||
height:fit-content;
|
||||
padding:10px;
|
||||
width:fit-content;
|
||||
}
|
||||
|
||||
|
||||
.ol-panel-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: fit-content;
|
||||
row-gap: 5px;
|
||||
text-align: center;
|
||||
width: fit-content;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.ol-panel-list .list-item {
|
||||
border-radius: 10px;
|
||||
display:flex;
|
||||
justify-content: space-between;
|
||||
padding: 6px 10px;
|
||||
}
|
||||
|
||||
.ol-panel-list.sortable > .sortable-item {
|
||||
align-items: center;
|
||||
column-gap: 5px;
|
||||
display:flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.ol-panel-list.sortable > .sortable-item > .handle {
|
||||
cursor:grab;
|
||||
filter:invert(100);
|
||||
}
|
||||
|
||||
.ol-panel-list.sortable > .sortable-item > .handle img {
|
||||
max-width: 16px;
|
||||
}
|
||||
|
||||
|
||||
.ol-panel-info {
|
||||
display:flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
.ol-panel-info > .panel-section {
|
||||
border-right: 1px solid #555;
|
||||
padding:10px;
|
||||
}
|
||||
|
||||
.ol-panel-info > .panel-section:last-of-type {
|
||||
border-right-width: 0;
|
||||
}
|
||||
|
||||
|
||||
.highlight-primary {
|
||||
background-color: var(--highlight-color);
|
||||
}
|
||||
|
||||
.highlight-bluefor {
|
||||
background-color: var(--blue-coalition-color);
|
||||
}
|
||||
|
||||
.highlight-redfor {
|
||||
background-color: var(--red-coalition-color);
|
||||
}
|
||||
|
||||
.highlight-neutral {
|
||||
background-color: var(--neutral-coalition-color);
|
||||
color: var(--neutral-coalition-text)
|
||||
}
|
||||
@ -16,6 +16,7 @@
|
||||
@import url("mouseinfopanel.css");
|
||||
|
||||
@import url( "aic.css" );
|
||||
@import url( "atc.css" );
|
||||
|
||||
@import url("layout.css");
|
||||
|
||||
@ -36,7 +37,7 @@
|
||||
* {
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
|
||||
54
client/public/stylesheets/uikit.css
Normal file
54
client/public/stylesheets/uikit.css
Normal file
@ -0,0 +1,54 @@
|
||||
body {
|
||||
background-color:#eaeaea;
|
||||
}
|
||||
|
||||
#content-wrapper {
|
||||
row-gap: 5px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
height:100%;
|
||||
width:100%;
|
||||
}
|
||||
|
||||
section {
|
||||
column-gap: 20px;
|
||||
display:flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
font-size:125%;
|
||||
font-weight: bold;
|
||||
margin-bottom: 1vh;
|
||||
}
|
||||
|
||||
.content {
|
||||
background:white;
|
||||
border-radius: 10px;
|
||||
height:fit-content;
|
||||
margin-bottom:4vh;
|
||||
padding:20px;
|
||||
width:fit-content;
|
||||
}
|
||||
|
||||
.content-header {
|
||||
color:#666;
|
||||
letter-spacing:1px;
|
||||
margin-bottom: 1vh;
|
||||
}
|
||||
|
||||
.content-body {
|
||||
column-gap: 20px;
|
||||
display:flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.caption {
|
||||
margin:2vh 0 1vh 0;
|
||||
}
|
||||
|
||||
|
||||
#paragraph {
|
||||
max-width: 750px;
|
||||
}
|
||||
181
client/public/uikit.html
Normal file
181
client/public/uikit.html
Normal file
@ -0,0 +1,181 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Olympus UI Kit</title>
|
||||
<link rel="stylesheet" type="text/css" href="stylesheets/olympus.css" />
|
||||
<link rel="stylesheet" type="text/css" href="stylesheets/uikit.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="content-wrapper">
|
||||
|
||||
<h1>Olympus UI Kit</h1>
|
||||
|
||||
<div class="section-header">Typeography</div>
|
||||
<section>
|
||||
|
||||
<div class="content">
|
||||
|
||||
<div class="content-header">Headings</div>
|
||||
<div class="content-body">
|
||||
|
||||
<div class="example">
|
||||
<h1>h1 | open sans | 32px</h1>
|
||||
<h2>h2 | open sans | 24px</h2>
|
||||
<h3>h3 | open sans | 18.72px</h3>
|
||||
<h4>h4 | open sans | 16px</h4>
|
||||
<h5>h5 | open sans | 13.28px</h5>
|
||||
<h6>h6 | open sans | 10.72px</h6>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="paragraph" class="content">
|
||||
|
||||
<div class="content-header">Paragraph</div>
|
||||
<div class="content-body">
|
||||
|
||||
<div class="example">
|
||||
<div class="caption">Plain</div>
|
||||
<p>Nullam iaculis nisi sed mi tincidunt pretium blandit tempus urna. Vestibulum non ex vitae massa tristique auctor. Praesent orci justo, porttitor pellentesque convallis non, commodo at augue.</p>
|
||||
</div>
|
||||
|
||||
<div class="example">
|
||||
<div class="caption">In a panel</div>
|
||||
<div class="ol-panel">
|
||||
<p>Donec nibh est, fringilla sed pharetra eu, varius vel sem. Aliquam ac libero leo. Sed consectetur enim aliquam dui pellentesque luctus. Pellentesque vel iaculis quam.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
<div class="section-header">.ol-panel</div>
|
||||
<section>
|
||||
|
||||
|
||||
<div class="content">
|
||||
|
||||
<div class="content-header">Plain panel</div>
|
||||
|
||||
<div class="content-body">
|
||||
|
||||
<div class="example">
|
||||
|
||||
<div class="ol-panel">
|
||||
Disconnected
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
|
||||
<div class="content-header">Panel list</div>
|
||||
|
||||
<div class="content-body">
|
||||
|
||||
<div class="example">
|
||||
|
||||
<div class="caption">Basic list</div>
|
||||
|
||||
<div class="ol-panel">
|
||||
<div class="ol-panel-list">
|
||||
<div class="list-item">List item 1</div>
|
||||
<div class="list-item">List item 2</div>
|
||||
<div class="list-item">List item 3</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="example">
|
||||
|
||||
<div class="caption">List with .highlight-primary</div>
|
||||
|
||||
<div class="ol-panel">
|
||||
<div class="ol-panel-list">
|
||||
<div class="list-item highlight-primary">List item with highlight-primary</div>
|
||||
<div class="list-item highlight-bluefor">List item with highlight-bluefor</div>
|
||||
<div class="list-item highlight-redfor">List item with highlight-redfor</div>
|
||||
<div class="list-item highlight-neutral">List item with highlight-neutral</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="example">
|
||||
|
||||
<div class="caption">Sortable list</div>
|
||||
|
||||
<div class="ol-panel">
|
||||
<div class="ol-panel-list sortable">
|
||||
<div class="sortable-item">
|
||||
<div class="handle"><img src="images/buttons/reorder.svg" /></div>
|
||||
<div class="list-item">List item 1</div>
|
||||
</div>
|
||||
<div class="sortable-item">
|
||||
<div class="handle"><img src="images/buttons/reorder.svg" /></div>
|
||||
<div class="list-item">List item 2</div>
|
||||
</div>
|
||||
<div class="sortable-item">
|
||||
<div class="handle"><img src="images/buttons/reorder.svg" /></div>
|
||||
<div class="list-item">List item 3</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- JavaScript: new Sortable( element:HTMLElement, [ options:object ]) -->
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
|
||||
<div class="content-header">.ol-panel > .ol-panel-info</div>
|
||||
|
||||
<div class="content-body">
|
||||
|
||||
<div class="example">
|
||||
|
||||
<div class="ol-panel">
|
||||
<div class="ol-panel-info">
|
||||
<div class="panel-section">
|
||||
Info panel number 1
|
||||
</div>
|
||||
<div class="panel-section">
|
||||
Info panel number 2
|
||||
</div>
|
||||
<div class="panel-section">
|
||||
Info panel number 3
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
77
client/src/FeatureSwitches.ts
Normal file
77
client/src/FeatureSwitches.ts
Normal file
@ -0,0 +1,77 @@
|
||||
export interface FeatureSwitchInterface {
|
||||
"enabled": boolean,
|
||||
"label": string,
|
||||
"name": string,
|
||||
"options"?: object,
|
||||
"removeArtifactsIfDisabled"?: boolean
|
||||
}
|
||||
|
||||
|
||||
class FeatureSwitch {
|
||||
enabled;
|
||||
label;
|
||||
name;
|
||||
removeArtifactsIfDisabled = true;
|
||||
|
||||
constructor( config:FeatureSwitchInterface ) {
|
||||
|
||||
this.enabled = config.enabled;
|
||||
this.label = config.label;
|
||||
this.name = config.name;
|
||||
|
||||
}
|
||||
|
||||
|
||||
isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class FeatureSwitches {
|
||||
|
||||
#featureSwitches:FeatureSwitch[] = [
|
||||
|
||||
new FeatureSwitch({
|
||||
"enabled": false,
|
||||
"label": "AIC",
|
||||
"name": "aic"
|
||||
}),
|
||||
|
||||
new FeatureSwitch({
|
||||
"enabled": false,
|
||||
"label": "ATC",
|
||||
"name": "atc",
|
||||
"options": {
|
||||
"key": "value"
|
||||
}
|
||||
})
|
||||
|
||||
];
|
||||
|
||||
|
||||
constructor() {
|
||||
|
||||
this.#removeArtifacts();
|
||||
|
||||
}
|
||||
|
||||
|
||||
getSwitch( switchName:string ) {
|
||||
|
||||
return this.#featureSwitches.find( featureSwitch => featureSwitch.name === switchName );
|
||||
|
||||
}
|
||||
|
||||
|
||||
#removeArtifacts() {
|
||||
|
||||
for ( const featureSwitch of this.#featureSwitches ) {
|
||||
if ( !featureSwitch.isEnabled() && featureSwitch.removeArtifactsIfDisabled !== false ) {
|
||||
document.querySelectorAll( "[data-feature-switch='" + featureSwitch.name + "']" ).forEach( el => el.remove() );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
35
client/src/ToggleableFeature.ts
Normal file
35
client/src/ToggleableFeature.ts
Normal file
@ -0,0 +1,35 @@
|
||||
export abstract class ToggleableFeature {
|
||||
|
||||
#status:boolean = false;
|
||||
|
||||
|
||||
constructor( defaultStatus:boolean ) {
|
||||
|
||||
this.#status = defaultStatus;
|
||||
|
||||
this.onStatusUpdate();
|
||||
|
||||
}
|
||||
|
||||
|
||||
getStatus() : boolean {
|
||||
return this.#status;
|
||||
}
|
||||
|
||||
|
||||
protected onStatusUpdate() {}
|
||||
|
||||
|
||||
toggleStatus( force?:boolean ) : void {
|
||||
|
||||
if ( force ) {
|
||||
this.#status = force;
|
||||
} else {
|
||||
this.#status = !this.#status;
|
||||
}
|
||||
|
||||
this.onStatusUpdate();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,12 +1,11 @@
|
||||
import { ToggleableFeature } from "../ToggleableFeature";
|
||||
import { AICFormation_Azimuth } from "./AICFormation/Azimuth";
|
||||
import { AICFormation_Range } from "./AICFormation/Range";
|
||||
import { AICFormation_Single } from "./AICFormation/Single";
|
||||
import { AICFormationDescriptorSection } from "./AICFormationDescriptorSection";
|
||||
|
||||
|
||||
export class AIC {
|
||||
|
||||
#status:boolean = true;
|
||||
export class AIC extends ToggleableFeature {
|
||||
|
||||
#formations = [
|
||||
|
||||
@ -18,8 +17,10 @@ export class AIC {
|
||||
|
||||
|
||||
constructor() {
|
||||
|
||||
super( false );
|
||||
|
||||
this.#onStatusUpdate();
|
||||
this.onStatusUpdate();
|
||||
|
||||
// This feels kind of dirty
|
||||
let $aicFormationList = document.getElementById( "aic-formation-list" );
|
||||
@ -80,30 +81,10 @@ export class AIC {
|
||||
}
|
||||
|
||||
|
||||
getStatus() {
|
||||
return this.#status;
|
||||
}
|
||||
|
||||
|
||||
#onStatusUpdate() {
|
||||
|
||||
const status:boolean = this.getStatus();
|
||||
onStatusUpdate() {
|
||||
|
||||
// Update the DOM
|
||||
document.body.classList.toggle( "aic-enabled", status );
|
||||
|
||||
}
|
||||
|
||||
|
||||
toggleStatus(force?:boolean) {
|
||||
|
||||
if ( force ) {
|
||||
this.#status = force;
|
||||
} else {
|
||||
this.#status = !this.#status;
|
||||
}
|
||||
|
||||
this.#onStatusUpdate();
|
||||
document.body.classList.toggle( "aic-enabled", this.getStatus() );
|
||||
|
||||
}
|
||||
|
||||
|
||||
87
client/src/atc/ATC.ts
Normal file
87
client/src/atc/ATC.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import { ToggleableFeature } from "../ToggleableFeature";
|
||||
import Sortable from 'sortablejs';
|
||||
import { ATCFLightList } from "./FlightList";
|
||||
|
||||
export class ATC extends ToggleableFeature {
|
||||
|
||||
constructor() {
|
||||
|
||||
super( true );
|
||||
|
||||
//this.#generateFlightList();
|
||||
|
||||
let $list = document.getElementById( "atc-strip-board-arrivals" );
|
||||
|
||||
if ( $list instanceof HTMLElement ) {
|
||||
Sortable.create( $list, {
|
||||
"handle": ".handle"
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
#generateFlightList() {
|
||||
|
||||
const flightList = new ATCFLightList();
|
||||
const flights:any = flightList.getFlights( true );
|
||||
|
||||
const $tbody = document.getElementById( "atc-flight-list-table-body" );
|
||||
|
||||
if ( $tbody instanceof HTMLElement ) {
|
||||
|
||||
if ( flights.length > 0 ) {
|
||||
|
||||
let flight:any = {};
|
||||
|
||||
let $button, i;
|
||||
|
||||
for ( [ i, flight ] of flights.entries() ) {
|
||||
|
||||
const $row = document.createElement( "tr" );
|
||||
$row.dataset.status = flight.status
|
||||
|
||||
let $td = document.createElement( "td" );
|
||||
$td.innerText = flight.name;
|
||||
$row.appendChild( $td );
|
||||
|
||||
$td = document.createElement( "td" );
|
||||
$td.innerText = flight.takeOffTime;
|
||||
$row.appendChild( $td );
|
||||
|
||||
$td = document.createElement( "td" );
|
||||
$td.innerText = "00:0" + ( 5 + i );
|
||||
$row.appendChild( $td );
|
||||
|
||||
$td = document.createElement( "td" );
|
||||
$td.innerText = flight.status;
|
||||
$row.appendChild( $td );
|
||||
|
||||
|
||||
$td = document.createElement( "td" );
|
||||
$button = document.createElement( "button" );
|
||||
$button.innerText = "...";
|
||||
|
||||
$td.appendChild( $button );
|
||||
|
||||
$row.appendChild( $td );
|
||||
|
||||
|
||||
$tbody.appendChild( $row );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected onStatusUpdate(): void {
|
||||
|
||||
document.body.classList.toggle( "atc-enabled", this.getStatus() );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
|
||||
export interface ATCAPIInterface {
|
||||
get: CallableFunction
|
||||
}
|
||||
|
||||
export abstract class ATCAPI {
|
||||
|
||||
|
||||
|
||||
constructor() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
7
client/src/atc/ATCMockAPI.ts
Normal file
7
client/src/atc/ATCMockAPI.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export abstract class ATCMockAPI {
|
||||
|
||||
constructor() {}
|
||||
|
||||
generateMockData() {}
|
||||
|
||||
}
|
||||
40
client/src/atc/ATCMockAPI/Flights.ts
Normal file
40
client/src/atc/ATCMockAPI/Flights.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { ATCMockAPI } from "../ATCMockAPI";
|
||||
|
||||
export class ATCMockAPI_Flights extends ATCMockAPI {
|
||||
|
||||
|
||||
generateMockData() {
|
||||
|
||||
let data = [];
|
||||
const statuses = [ "unknown", "checkedIn", "readyToTaxi" ]
|
||||
|
||||
for ( const [ i, flightName ] of [ "Shark", "Whale", "Dolphin" ].entries() ) {
|
||||
|
||||
data.push({
|
||||
"name": flightName,
|
||||
"status": statuses[ i ],
|
||||
"takeOffTime": "18:0" + i
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
localStorage.setItem( "flightList", JSON.stringify( data ) );
|
||||
|
||||
}
|
||||
|
||||
|
||||
get( generateMockDataIfEmpty?:boolean ) : object {
|
||||
|
||||
generateMockDataIfEmpty = generateMockDataIfEmpty || false;
|
||||
|
||||
let data = localStorage.getItem( "flightList" ) || "[]";
|
||||
|
||||
if ( data === "[]" && generateMockDataIfEmpty ) {
|
||||
this.generateMockData();
|
||||
}
|
||||
|
||||
return JSON.parse( data );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
18
client/src/atc/FlightList.ts
Normal file
18
client/src/atc/FlightList.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { ATCMockAPI_Flights } from "./ATCMockAPI/Flights";
|
||||
|
||||
export class ATCFLightList {
|
||||
|
||||
|
||||
constructor() {
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
getFlights( generateMockDataIfEmpty?:boolean ) {
|
||||
let api = new ATCMockAPI_Flights();
|
||||
return api.get( generateMockDataIfEmpty );
|
||||
}
|
||||
|
||||
}
|
||||
@ -14,6 +14,8 @@ import { Slider } from "./controls/slider";
|
||||
import { AIC } from "./aic/AIC";
|
||||
|
||||
import { VisibilityControlPanel } from "./panels/visibilitycontrolpanel";
|
||||
import { ATC } from "./atc/ATC";
|
||||
import { FeatureSwitches } from "./FeatureSwitches";
|
||||
|
||||
/* TODO: should this be a class? */
|
||||
var map: Map;
|
||||
@ -41,13 +43,22 @@ var aic: AIC;
|
||||
var aicToggleButton: Button;
|
||||
var aicHelpButton: Button;
|
||||
|
||||
|
||||
var atc: ATC;
|
||||
var atcToggleButton: Button;
|
||||
|
||||
var altitudeSlider: Slider;
|
||||
var airspeedSlider: Slider;
|
||||
|
||||
var connected: boolean;
|
||||
var activeCoalition: string;
|
||||
|
||||
var featureSwitches;
|
||||
|
||||
function setup() {
|
||||
|
||||
featureSwitches = new FeatureSwitches();
|
||||
|
||||
/* Initialize */
|
||||
map = new Map('map-container');
|
||||
unitsManager = new UnitsManager();
|
||||
@ -63,9 +74,6 @@ function setup() {
|
||||
mouseInfoPanel = new MouseInfoPanel("mouse-info-panel");
|
||||
visibilityControlPanel = new VisibilityControlPanel("visibility-control-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));
|
||||
|
||||
missionData = new MissionData();
|
||||
|
||||
/* Unit control buttons */
|
||||
@ -79,15 +87,20 @@ function setup() {
|
||||
airspeedSlider = new Slider("airspeed-slider", 0, 100, "kts", (value: number) => getUnitsManager().selectedUnitsSetSpeed(value / 1.94384));
|
||||
|
||||
/* AIC */
|
||||
aic = new AIC();
|
||||
|
||||
aicToggleButton = new Button( "toggle-aic-button", ["images/buttons/radar.svg"], () => {
|
||||
aic.toggleStatus();
|
||||
});
|
||||
let aicFeatureSwitch = featureSwitches.getSwitch( "aic" );
|
||||
|
||||
aicHelpButton = new Button( "aic-help-button", [ "images/buttons/question-mark.svg" ], () => {
|
||||
aic.toggleHelp();
|
||||
});
|
||||
if ( aicFeatureSwitch?.isEnabled() ) {
|
||||
aic = new AIC();
|
||||
|
||||
aicToggleButton = new Button( "toggle-aic-button", ["images/buttons/radar.svg"], () => {
|
||||
aic.toggleStatus();
|
||||
});
|
||||
|
||||
aicHelpButton = new Button( "aic-help-button", [ "images/buttons/question-mark.svg" ], () => {
|
||||
aic.toggleHelp();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/* Generic clicks */
|
||||
@ -104,6 +117,22 @@ function setup() {
|
||||
|
||||
});
|
||||
|
||||
|
||||
/*** ATC ***/
|
||||
|
||||
let atcFeatureSwitch = featureSwitches.getSwitch( "atc" );
|
||||
|
||||
if ( atcFeatureSwitch?.isEnabled() ) {
|
||||
|
||||
atc = new ATC();
|
||||
|
||||
atcToggleButton = new Button( "atc-toggle-button", [ "images/buttons/atc.svg" ], () => {
|
||||
atc.toggleStatus();
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Default values */
|
||||
activeCoalition = "blue";
|
||||
connected = false;
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
<div class="ol-panel aic-panel" id="aic-control-panel">
|
||||
<div class="ol-panel aic-panel" id="aic-control-panel" data-feature-switch="aic">
|
||||
<div class="olympus-button" id="toggle-aic-button"></div>
|
||||
<div class="olympus-button" id="aic-help-button"></div>
|
||||
</div>
|
||||
|
||||
<div id="aic-help" class="olympus-dialog hide">
|
||||
<div id="aic-help" class="olympus-dialog hide" data-feature-switch="aic">
|
||||
<div class="olympus-dialog-close">×</div>
|
||||
<div class="olympus-dialog-header">AIC Help</div>
|
||||
<div class="olympus-dialog-content">
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<div id="aic-callsign-panel" class="aic-panel">
|
||||
<div id="aic-callsign-panel" class="aic-panel" data-feature-switch="aic">
|
||||
|
||||
<div class="aic-panel">
|
||||
<h2>My callsign</h2>
|
||||
@ -8,7 +8,7 @@
|
||||
</div>
|
||||
|
||||
|
||||
<div id="aic-toolbox" class="aic-panel">
|
||||
<div id="aic-toolbox" class="aic-panel" data-feature-switch="aic">
|
||||
|
||||
<div id="aic-control-type" class="aic-toolbox-panel">
|
||||
<h2>Control</h2>
|
||||
|
||||
105
client/views/atc.ejs
Normal file
105
client/views/atc.ejs
Normal file
@ -0,0 +1,105 @@
|
||||
<div id="atc-control-panel" data-feature-switch="atc">
|
||||
<div class="ol-button" id="atc-toggle-button"></div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="atc-flight-list" class="atc-tool hide" data-feature-switch="atc">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Flight</th>
|
||||
<th>T/O</th>
|
||||
<th>TTG</th>
|
||||
<th>Status</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="atc-flight-list-table-body"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="atc-strip-board" data-feature-switch="atc">
|
||||
|
||||
<div class="atc-strip-board-header">
|
||||
<div class="name">Name</div>
|
||||
<div class="bearing-range">BR</div>
|
||||
<div class="target-altitude">t. Alt</div>
|
||||
<div class="current-altitude">Alt</div>
|
||||
<div class="target-speed">t. Spd</div>
|
||||
<div class="current-speed">Speed</div>
|
||||
<div class="runway">RWY</div>
|
||||
<div class="line">Line</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="atc-strip-board-arrivals" class="atc-strip-board-strips ol-panel">
|
||||
<div class="atc-strip-board-strip">
|
||||
<div class="handle"></div>
|
||||
<div class="rectangular-container">
|
||||
<div class="name">Shark 3</div>
|
||||
<div class="bearing-range">250 / 28</div>
|
||||
<div class="target-altitude">-</div>
|
||||
<div class="current-altitude">10000</div>
|
||||
<div class="target-speed">-</div>
|
||||
<div class="current-speed">421</div>
|
||||
<div class="runway">-</div>
|
||||
<div class="line">-</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="atc-strip-board-strip">
|
||||
<div class="handle"></div>
|
||||
<div class="rectangular-container">
|
||||
<div class="name">Shark 2</div>
|
||||
<div class="bearing-range">250 / 24</div>
|
||||
<div class="target-altitude">6000</div>
|
||||
<div class="current-altitude">6000</div>
|
||||
<div class="target-speed">-</div>
|
||||
<div class="current-speed">400</div>
|
||||
<div class="runway">-</div>
|
||||
<div class="line">-</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="atc-strip-board-strip">
|
||||
<div class="handle"></div>
|
||||
<div class="rectangular-container">
|
||||
<div class="name">Shark 1</div>
|
||||
<div class="bearing-range link-warning">262 / 12</div>
|
||||
<div class="target-altitude">5000</div>
|
||||
<div class="current-altitude">5100</div>
|
||||
<div class="target-speed">-</div>
|
||||
<div class="current-speed">367</div>
|
||||
<div class="runway warning">-</div>
|
||||
<div class="line">-</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="atc-strip-board-strip">
|
||||
<div class="handle"></div>
|
||||
<div class="rectangular-container">
|
||||
<div class="name">Dolphin 1</div>
|
||||
<div class="bearing-range">250 / 4</div>
|
||||
<div class="target-altitude link-warning">3000</div>
|
||||
<div class="current-altitude warning">4100</div>
|
||||
<div class="target-speed">-</div>
|
||||
<div class="current-speed">511</div>
|
||||
<div class="runway">25L</div>
|
||||
<div class="line">2nd</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="atc-strip-board-strip">
|
||||
<div class="handle"></div>
|
||||
<div class="rectangular-container">
|
||||
<div class="name">Whale 1</div>
|
||||
<div class="bearing-range">070 / 2</div>
|
||||
<div class="target-altitude">1500</div>
|
||||
<div class="current-altitude">1650</div>
|
||||
<div class="target-speed link-warning">350</div>
|
||||
<div class="current-speed warning">312</div>
|
||||
<div class="runway">25L</div>
|
||||
<div class="line">1st</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
@ -31,9 +31,12 @@
|
||||
<%- include('visibilitycontrolpanel.ejs') %>
|
||||
<%- include('connectionstatuspanel.ejs') %>
|
||||
<%- include('mouseinfopanel.ejs') %>
|
||||
|
||||
<%- include('aiccontrolpanel.ejs') %>
|
||||
<%- include('aicformationpanel.ejs') %>
|
||||
|
||||
<%- include( 'atc.ejs' ) %>
|
||||
|
||||
<script src="javascripts/bundle.js"></script>
|
||||
</body>
|
||||
|
||||
|
||||
9
package-lock.json
generated
9
package-lock.json
generated
@ -6,7 +6,8 @@
|
||||
"": {
|
||||
"devDependencies": {
|
||||
"chai": "^4.3.7",
|
||||
"mocha": "^10.2.0"
|
||||
"mocha": "^10.2.0",
|
||||
"sortablejs": "^1.15.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-colors": {
|
||||
@ -827,6 +828,12 @@
|
||||
"randombytes": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sortablejs": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.0.tgz",
|
||||
"integrity": "sha512-bv9qgVMjUMf89wAvM6AxVvS/4MX3sPeN0+agqShejLU5z5GX4C75ow1O2e5k4L6XItUyAK3gH6AxSbXrOM5e8w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"chai": "^4.3.7",
|
||||
"mocha": "^10.2.0"
|
||||
"mocha": "^10.2.0",
|
||||
"sortablejs": "^1.15.0"
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user