mirror of
https://github.com/Spythere/stacjownik.git
synced 2026-05-03 13:28:11 +00:00
Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c4c486643 | |||
| 755c729a9b | |||
| 3ac8d60c5c | |||
| dcff3b088f | |||
| 90b2099955 | |||
| fc0c04ec9d | |||
| 41b335555a | |||
| 60f7b3bbb5 | |||
| eaefe955a7 | |||
| edaa4f2684 | |||
| 30fce3787b | |||
| 4716f1c7a4 | |||
| bb7ccf98fd | |||
| c06d75b981 | |||
| c7da8477fa | |||
| e43f1e0819 | |||
| f130e6900b | |||
| db205915be | |||
| 05c38e10e3 | |||
| a8f683a585 | |||
| 68f6fc8a42 | |||
| 6d3b32cd7d | |||
| fadecc9d2c | |||
| 50602cb6db | |||
| 186ce81819 | |||
| f836a075b0 | |||
| 9acf3c740c | |||
| bc1c1bd3d2 | |||
| 2348277b95 | |||
| cd5f489df7 | |||
| f74962222b | |||
| e7f651d2b9 | |||
| 4862328090 | |||
| 87631d1f74 | |||
| 86bb9fcc2e | |||
| b85e3bfe1d | |||
| dd15072813 | |||
| 2f8376c996 | |||
| 514723cf74 | |||
| 0995ce15bc | |||
| 3b3c3bda31 | |||
| 2027b85450 | |||
| 0c6b55146f | |||
| 3c728e3cfa | |||
| adce339392 | |||
| 00a4a840b0 | |||
| 1e705ea496 | |||
| e8ed36df16 | |||
| f4be32aa39 | |||
| e0d3d2585d |
@@ -50,11 +50,6 @@
|
||||
name="twitter:image"
|
||||
content="https://raw.githubusercontent.com/Spythere/api/main/thumbnails/stacjownik.jpg"
|
||||
/>
|
||||
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Quicksand:wght@500;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
Generated
+148
-20
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "stacjownik",
|
||||
"version": "1.19.4",
|
||||
"version": "1.20.3",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "stacjownik",
|
||||
"version": "1.19.4",
|
||||
"version": "1.20.3",
|
||||
"dependencies": {
|
||||
"core-js": "^3.32.2",
|
||||
"dotenv": "^16.3.1",
|
||||
@@ -14,6 +14,7 @@
|
||||
"howler": "^2.2.4",
|
||||
"pinia": "^2.1.6",
|
||||
"sass": "^1.67.0",
|
||||
"socket.io-client": "^4.7.4",
|
||||
"vue": "^3.3.4",
|
||||
"vue-i18n": "^9.4.1",
|
||||
"vue-router": "^4.2.4"
|
||||
@@ -2840,6 +2841,11 @@
|
||||
"integrity": "sha512-6i/8UoL0P5y4leBIGzvkZdS85RDMG9y1ihZzmTZQ5LdHUYmZ7pKFoj8X0236s3lusPs1Fa5HTQUpwI+UfTcmeA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@socket.io/component-emitter": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
|
||||
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg=="
|
||||
},
|
||||
"node_modules/@surma/rollup-plugin-off-main-thread": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz",
|
||||
@@ -4156,7 +4162,6 @@
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "2.1.2"
|
||||
@@ -4399,6 +4404,26 @@
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io-client": {
|
||||
"version": "6.5.3",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz",
|
||||
"integrity": "sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==",
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.1",
|
||||
"engine.io-parser": "~5.2.1",
|
||||
"ws": "~8.11.0",
|
||||
"xmlhttprequest-ssl": "~2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io-parser": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz",
|
||||
"integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/es-abstract": {
|
||||
"version": "1.20.5",
|
||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.5.tgz",
|
||||
@@ -5061,9 +5086,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
|
||||
"version": "1.15.5",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
|
||||
"integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -5071,7 +5096,6 @@
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
@@ -6312,7 +6336,6 @@
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/muggle-string": {
|
||||
@@ -7538,6 +7561,32 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-client": {
|
||||
"version": "4.7.4",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.4.tgz",
|
||||
"integrity": "sha512-wh+OkeF0rAVCrABWQBaEjLfb7DVPotMbu0cgWgyR0v6eA4EoVnAwcIeIbcdTE3GT/H3kbdLl7OoH2+asoDRIIg==",
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.2",
|
||||
"engine.io-client": "~6.5.2",
|
||||
"socket.io-parser": "~4.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-parser": {
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
|
||||
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.8.0-beta.0",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz",
|
||||
@@ -8152,9 +8201,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "4.5.1",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.1.tgz",
|
||||
"integrity": "sha512-AXXFaAJ8yebyqzoNB9fu2pHoo/nWX+xZlaRwoeYUxEqBO+Zj4msE5G+BhGBll9lYEKv9Hfks52PAF2X7qDYXQA==",
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz",
|
||||
"integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.18.10",
|
||||
@@ -8727,6 +8776,26 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.11.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
|
||||
"integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": "^5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/xml-name-validator": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
|
||||
@@ -8736,6 +8805,14 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/xmlhttprequest-ssl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
|
||||
"integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/y18n": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
@@ -10722,6 +10799,11 @@
|
||||
"integrity": "sha512-6i/8UoL0P5y4leBIGzvkZdS85RDMG9y1ihZzmTZQ5LdHUYmZ7pKFoj8X0236s3lusPs1Fa5HTQUpwI+UfTcmeA==",
|
||||
"dev": true
|
||||
},
|
||||
"@socket.io/component-emitter": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
|
||||
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg=="
|
||||
},
|
||||
"@surma/rollup-plugin-off-main-thread": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz",
|
||||
@@ -11640,7 +11722,6 @@
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
@@ -11801,6 +11882,23 @@
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"engine.io-client": {
|
||||
"version": "6.5.3",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz",
|
||||
"integrity": "sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==",
|
||||
"requires": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.1",
|
||||
"engine.io-parser": "~5.2.1",
|
||||
"ws": "~8.11.0",
|
||||
"xmlhttprequest-ssl": "~2.0.0"
|
||||
}
|
||||
},
|
||||
"engine.io-parser": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz",
|
||||
"integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ=="
|
||||
},
|
||||
"es-abstract": {
|
||||
"version": "1.20.5",
|
||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.5.tgz",
|
||||
@@ -12286,9 +12384,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
|
||||
"version": "1.15.5",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
|
||||
"integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
|
||||
"dev": true
|
||||
},
|
||||
"form-data": {
|
||||
@@ -13127,8 +13225,7 @@
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"muggle-string": {
|
||||
"version": "0.3.1",
|
||||
@@ -13945,6 +14042,26 @@
|
||||
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
|
||||
"dev": true
|
||||
},
|
||||
"socket.io-client": {
|
||||
"version": "4.7.4",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.4.tgz",
|
||||
"integrity": "sha512-wh+OkeF0rAVCrABWQBaEjLfb7DVPotMbu0cgWgyR0v6eA4EoVnAwcIeIbcdTE3GT/H3kbdLl7OoH2+asoDRIIg==",
|
||||
"requires": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.2",
|
||||
"engine.io-client": "~6.5.2",
|
||||
"socket.io-parser": "~4.2.4"
|
||||
}
|
||||
},
|
||||
"socket.io-parser": {
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
|
||||
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
|
||||
"requires": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.1"
|
||||
}
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.8.0-beta.0",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz",
|
||||
@@ -14377,9 +14494,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"vite": {
|
||||
"version": "4.5.1",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.1.tgz",
|
||||
"integrity": "sha512-AXXFaAJ8yebyqzoNB9fu2pHoo/nWX+xZlaRwoeYUxEqBO+Zj4msE5G+BhGBll9lYEKv9Hfks52PAF2X7qDYXQA==",
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz",
|
||||
"integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esbuild": "^0.18.10",
|
||||
@@ -14776,12 +14893,23 @@
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"dev": true
|
||||
},
|
||||
"ws": {
|
||||
"version": "8.11.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
|
||||
"integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
|
||||
"requires": {}
|
||||
},
|
||||
"xml-name-validator": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
|
||||
"integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==",
|
||||
"dev": true
|
||||
},
|
||||
"xmlhttprequest-ssl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
|
||||
"integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A=="
|
||||
},
|
||||
"y18n": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "stacjownik",
|
||||
"version": "1.19.4",
|
||||
"version": "1.21.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -18,6 +18,7 @@
|
||||
"howler": "^2.2.4",
|
||||
"pinia": "^2.1.6",
|
||||
"sass": "^1.67.0",
|
||||
"socket.io-client": "^4.7.4",
|
||||
"vue": "^3.3.4",
|
||||
"vue-i18n": "^9.4.1",
|
||||
"vue-router": "^4.2.4"
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,4 +1,4 @@
|
||||
<svg width="160" height="150" viewBox="0 0 160 150" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.163 139L80 12.4204L149.837 139H80H10.163Z" stroke="white" stroke-width="12"/>
|
||||
<path d="M85.4488 50.3354V80.6619C85.4488 83.8784 85.2898 87.0418 84.9717 90.1522C84.6536 93.2273 84.2294 96.4968 83.6992 99.9606H74.8451C74.315 96.4968 73.8908 93.2273 73.5727 90.1522C73.2546 87.0418 73.0955 83.8784 73.0955 80.6619V50.3354H85.4488ZM71.0808 119.789C71.0808 118.694 71.2752 117.651 71.664 116.661C72.0882 115.672 72.6537 114.823 73.3606 114.117C74.1029 113.41 74.9689 112.844 75.9585 112.42C76.9482 111.996 78.0086 111.784 79.1396 111.784C80.2354 111.784 81.278 111.996 82.2677 112.42C83.2574 112.844 84.1057 113.41 84.8126 114.117C85.5195 114.823 86.085 115.672 86.5092 116.661C86.9333 117.651 87.1454 118.694 87.1454 119.789C87.1454 120.921 86.9333 121.981 86.5092 122.971C86.085 123.925 85.5195 124.756 84.8126 125.462C84.1057 126.169 83.2574 126.717 82.2677 127.106C81.278 127.53 80.2354 127.742 79.1396 127.742C78.0086 127.742 76.9482 127.53 75.9585 127.106C74.9689 126.717 74.1029 126.169 73.3606 125.462C72.6537 124.756 72.0882 123.925 71.664 122.971C71.2752 121.981 71.0808 120.921 71.0808 119.789Z" fill="#FFFBFB"/>
|
||||
<path d="M10.163 139L80 12.4204L149.837 139H80H10.163Z" stroke="salmon" stroke-width="15"/>
|
||||
<path d="M85.4488 50.3354V80.6619C85.4488 83.8784 85.2898 87.0418 84.9717 90.1522C84.6536 93.2273 84.2294 96.4968 83.6992 99.9606H74.8451C74.315 96.4968 73.8908 93.2273 73.5727 90.1522C73.2546 87.0418 73.0955 83.8784 73.0955 80.6619V50.3354H85.4488ZM71.0808 119.789C71.0808 118.694 71.2752 117.651 71.664 116.661C72.0882 115.672 72.6537 114.823 73.3606 114.117C74.1029 113.41 74.9689 112.844 75.9585 112.42C76.9482 111.996 78.0086 111.784 79.1396 111.784C80.2354 111.784 81.278 111.996 82.2677 112.42C83.2574 112.844 84.1057 113.41 84.8126 114.117C85.5195 114.823 86.085 115.672 86.5092 116.661C86.9333 117.651 87.1454 118.694 87.1454 119.789C87.1454 120.921 86.9333 121.981 86.5092 122.971C86.085 123.925 85.5195 124.756 84.8126 125.462C84.1057 126.169 83.2574 126.717 82.2677 127.106C81.278 127.53 80.2354 127.742 79.1396 127.742C78.0086 127.742 76.9482 127.53 75.9585 127.106C74.9689 126.717 74.1029 126.169 73.3606 125.462C72.6537 124.756 72.0882 123.925 71.664 122.971C71.2752 121.981 71.0808 120.921 71.0808 119.789Z" fill="white"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
+40
-41
@@ -10,7 +10,7 @@
|
||||
|
||||
<main class="app_main">
|
||||
<router-view v-slot="{ Component }">
|
||||
<keep-alive exclude="JournalView,SceneryView">
|
||||
<keep-alive exclude="SceneryView">
|
||||
<component :is="Component" :key="$route.name" />
|
||||
</keep-alive>
|
||||
</router-view>
|
||||
@@ -33,26 +33,27 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, watch } from 'vue';
|
||||
import axios from 'axios';
|
||||
import packageInfo from '.././package.json';
|
||||
|
||||
import Clock from './components/App/Clock.vue';
|
||||
|
||||
import packageInfo from '.././package.json';
|
||||
import { regions } from './data/options.json';
|
||||
|
||||
import { useMainStore } from './store/mainStore';
|
||||
|
||||
import StatusIndicator from './components/App/StatusIndicator.vue';
|
||||
import TrainModal from './components/Global/TrainModal.vue';
|
||||
import AppHeader from './components/App/AppHeader.vue';
|
||||
import axios from 'axios';
|
||||
import TrainModal from './components/TrainsView/TrainModal.vue';
|
||||
|
||||
import StorageManager from './managers/storageManager';
|
||||
import { useApiStore } from './store/apiStore';
|
||||
import { Status } from './typings/common';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
Clock,
|
||||
StatusIndicator,
|
||||
TrainModal,
|
||||
AppHeader
|
||||
AppHeader,
|
||||
TrainModal
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
@@ -66,26 +67,10 @@ export default defineComponent({
|
||||
}),
|
||||
|
||||
created() {
|
||||
this.loadLang();
|
||||
this.apiStore.setupAPI();
|
||||
|
||||
this.store.isOffline = !window.navigator.onLine;
|
||||
|
||||
window.addEventListener('offline', () => {
|
||||
this.store.isOffline = true;
|
||||
this.apiStore.activeData = undefined;
|
||||
|
||||
this.apiStore.setDataStatuses();
|
||||
});
|
||||
|
||||
window.addEventListener('online', () => {
|
||||
this.store.isOffline = false;
|
||||
});
|
||||
this.init();
|
||||
},
|
||||
|
||||
async mounted() {
|
||||
this.setReleaseURL();
|
||||
|
||||
watch(
|
||||
() => this.store.blockScroll,
|
||||
(value) => {
|
||||
@@ -95,23 +80,37 @@ export default defineComponent({
|
||||
);
|
||||
},
|
||||
|
||||
watch: {
|
||||
'$route.query.region': {
|
||||
immediate: true,
|
||||
handler(regionQuery: string) {
|
||||
if (regionQuery) {
|
||||
this.store.region.id =
|
||||
regions.find(
|
||||
(reg) =>
|
||||
reg.id == regionQuery.toLocaleLowerCase() ||
|
||||
reg.value.toLocaleLowerCase() == regionQuery.toLocaleLowerCase()
|
||||
)?.id || 'eu';
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
init() {
|
||||
this.loadLang();
|
||||
this.setReleaseURL();
|
||||
this.setupOfflineHandling();
|
||||
|
||||
this.apiStore.setupAPIData();
|
||||
},
|
||||
|
||||
setupOfflineHandling() {
|
||||
this.store.isOffline = !window.navigator.onLine;
|
||||
|
||||
if (this.store.isOffline) this.handleOfflineMode();
|
||||
|
||||
window.addEventListener('offline', this.handleOfflineMode);
|
||||
window.addEventListener('online', this.handleOnlineMode);
|
||||
},
|
||||
|
||||
handleOfflineMode() {
|
||||
this.store.isOffline = true;
|
||||
|
||||
this.apiStore.activeData = undefined;
|
||||
this.apiStore.dataStatuses.connection = Status.Data.Offline;
|
||||
},
|
||||
|
||||
handleOnlineMode() {
|
||||
this.store.isOffline = false;
|
||||
|
||||
this.apiStore.setupAPIData();
|
||||
},
|
||||
|
||||
changeLang(lang: string) {
|
||||
this.$i18n.locale = lang;
|
||||
this.currentLang = lang;
|
||||
|
||||
@@ -100,8 +100,8 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
onlineDispatchersCount() {
|
||||
return this.store.onlineSceneryList.filter(
|
||||
(scenery) => scenery.region == this.store.region.id
|
||||
return this.store.activeSceneryList.filter(
|
||||
(scenery) => scenery.region == this.store.region.id && scenery.dispatcherId != -1
|
||||
).length;
|
||||
},
|
||||
|
||||
|
||||
@@ -36,11 +36,11 @@
|
||||
<circle id="Ellipse 18" cx="15" cy="17" r="7" fill="#393838" />
|
||||
</g>
|
||||
|
||||
<g v-if="greenLight" filter="url(#filter0_d_843_28)">
|
||||
<g v-if="indicator.lights.greenLight" filter="url(#filter0_d_843_28)">
|
||||
<circle cx="15" cy="17" r="7" fill="#00FF0A" />
|
||||
</g>
|
||||
|
||||
<g v-if="greenBlinkLight" filter="url(#filter0_d_843_28)">
|
||||
<g v-if="indicator.lights.greenBlinkLight" filter="url(#filter0_d_843_28)">
|
||||
<circle cx="15" cy="17" r="7" fill="#00FF0A" />
|
||||
|
||||
<animate
|
||||
@@ -52,14 +52,14 @@
|
||||
/>
|
||||
</g>
|
||||
|
||||
<g v-if="redTopLight" filter="url(#filter1_d_843_28)">
|
||||
<g v-if="indicator.lights.redTopLight" filter="url(#filter1_d_843_28)">
|
||||
<circle cx="15" cy="36" r="7" fill="#F40000" />
|
||||
</g>
|
||||
|
||||
<g v-if="orangeLight" filter="url(#filter2_d_843_28)">
|
||||
<g v-if="indicator.lights.orangeLight" filter="url(#filter2_d_843_28)">
|
||||
<circle cx="15" cy="55" r="7" fill="#FFB800" />
|
||||
</g>
|
||||
<g v-if="redBottomLight" filter="url(#filter3_d_843_28)">
|
||||
<g v-if="indicator.lights.redBottomLight" filter="url(#filter3_d_843_28)">
|
||||
<circle cx="15" cy="74" r="7" fill="#F40000" />
|
||||
|
||||
<animate
|
||||
@@ -186,7 +186,11 @@
|
||||
</svg>
|
||||
|
||||
<transition name="tooltip-anim">
|
||||
<div v-html="$t(indicator.message)" class="indicator-tooltip" v-if="tooltipActive"></div>
|
||||
<div
|
||||
v-html="$t('data-status.' + indicator.message)"
|
||||
class="indicator-tooltip"
|
||||
v-if="tooltipActive"
|
||||
></div>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
@@ -194,125 +198,112 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { useMainStore } from '../../store/mainStore';
|
||||
import { Status } from '../../typings/common';
|
||||
import { useApiStore } from '../../store/apiStore';
|
||||
import { APIDataStatus } from '../../typings/api';
|
||||
|
||||
interface Indicator {
|
||||
// status: Status.Data;
|
||||
message: string;
|
||||
|
||||
lights: {
|
||||
greenLight: boolean;
|
||||
greenBlinkLight: boolean;
|
||||
redTopLight: boolean;
|
||||
orangeLight: boolean;
|
||||
redBottomLight: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
tooltipActive: false,
|
||||
indicator: {
|
||||
offline: false,
|
||||
status: Status.Data.Loading,
|
||||
message: 'data-status.S3'
|
||||
},
|
||||
|
||||
greenLight: false,
|
||||
greenBlinkLight: false,
|
||||
redTopLight: false,
|
||||
orangeLight: false,
|
||||
redBottomLight: false
|
||||
apiStore: useApiStore()
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.setSignalStatus(Status.Data.Loading);
|
||||
},
|
||||
|
||||
setup() {
|
||||
const store = useMainStore();
|
||||
const apiStore = useApiStore();
|
||||
|
||||
return {
|
||||
dataStatus: apiStore.dataStatuses,
|
||||
store
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
dataStatus: {
|
||||
deep: true,
|
||||
|
||||
handler(statuses: any) {
|
||||
const connectionStatus = statuses.connection;
|
||||
const sceneryDataStatus = statuses.sceneries;
|
||||
const trainsDataStatus = statuses.trains;
|
||||
const dispatcherDataStatus = statuses.dispatchers;
|
||||
|
||||
if (this.store.isOffline) {
|
||||
this.setSignalStatus(Status.Data.Initialized);
|
||||
this.indicator.status = Status.Data.Initialized;
|
||||
this.indicator.message = 'data-status.S1-offline';
|
||||
return;
|
||||
}
|
||||
|
||||
if (connectionStatus == Status.Data.Error) {
|
||||
this.setSignalStatus(connectionStatus);
|
||||
this.indicator.status = connectionStatus;
|
||||
this.indicator.message = 'data-status.S1a-connection';
|
||||
return;
|
||||
}
|
||||
|
||||
if (sceneryDataStatus == Status.Data.Error) {
|
||||
this.setSignalStatus(sceneryDataStatus);
|
||||
this.indicator.status = sceneryDataStatus;
|
||||
this.indicator.message = 'data-status.S1a-sceneries';
|
||||
return;
|
||||
}
|
||||
|
||||
if (trainsDataStatus == Status.Data.Warning) {
|
||||
this.setSignalStatus(trainsDataStatus);
|
||||
this.indicator.status = trainsDataStatus;
|
||||
this.indicator.message = 'data-status.S5-trains';
|
||||
return;
|
||||
}
|
||||
|
||||
if (dispatcherDataStatus == Status.Data.Warning) {
|
||||
this.setSignalStatus(dispatcherDataStatus);
|
||||
this.indicator.status = dispatcherDataStatus;
|
||||
this.indicator.message = 'data-status.S5-dispatchers';
|
||||
return;
|
||||
}
|
||||
|
||||
if (sceneryDataStatus == Status.Data.Loaded) {
|
||||
this.setSignalStatus(Status.Data.Loaded);
|
||||
|
||||
this.indicator.status = Status.Data.Loaded;
|
||||
this.indicator.message = 'data-status.S2';
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
setSignalStatus(status: Status.Data) {
|
||||
this.greenLight = false;
|
||||
this.greenBlinkLight = false;
|
||||
this.redTopLight = false;
|
||||
this.orangeLight = false;
|
||||
this.redBottomLight = false;
|
||||
setLights(message: string) {
|
||||
let lights = {
|
||||
greenBlinkLight: false,
|
||||
greenLight: false,
|
||||
orangeLight: false,
|
||||
redBottomLight: false,
|
||||
redTopLight: false
|
||||
};
|
||||
|
||||
if (status == Status.Data.Initialized) {
|
||||
this.redTopLight = true;
|
||||
switch (message) {
|
||||
case 'S3':
|
||||
lights.greenBlinkLight = true;
|
||||
break;
|
||||
|
||||
case 'S2':
|
||||
lights.greenLight = true;
|
||||
break;
|
||||
|
||||
case 'S1-offline':
|
||||
lights.redTopLight = true;
|
||||
break;
|
||||
|
||||
case 'S1a-connection':
|
||||
case 'S1a-sceneries':
|
||||
lights.redTopLight = true;
|
||||
lights.redBottomLight = true;
|
||||
break;
|
||||
|
||||
case 'S5-dispatchers':
|
||||
case 'S5-trains':
|
||||
lights.orangeLight = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (status == Status.Data.Loaded) {
|
||||
this.greenLight = true;
|
||||
return lights;
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
indicator(): Indicator {
|
||||
const dataStatuses = this.apiStore.dataStatuses;
|
||||
const swdrStatuses = this.apiStore.activeData?.apiStatuses;
|
||||
|
||||
let message = 'S3';
|
||||
|
||||
switch (dataStatuses.connection) {
|
||||
case Status.Data.Loading:
|
||||
message = 'S3';
|
||||
break;
|
||||
case Status.Data.Loaded:
|
||||
message = 'S2';
|
||||
break;
|
||||
case Status.Data.Offline:
|
||||
message = 'S1-offline';
|
||||
break;
|
||||
case Status.Data.Error:
|
||||
message = 'S1a-connection';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (status == Status.Data.Warning) {
|
||||
this.orangeLight = true;
|
||||
if (swdrStatuses?.dispatchersAPI == APIDataStatus.WARNING) {
|
||||
message = 'S5-dispatchers';
|
||||
}
|
||||
|
||||
if (status == Status.Data.Error) {
|
||||
this.redTopLight = true;
|
||||
this.redBottomLight = true;
|
||||
if (swdrStatuses?.trainsAPI == APIDataStatus.WARNING) {
|
||||
message = 'S5-trains';
|
||||
}
|
||||
|
||||
if (status == Status.Data.Loading) {
|
||||
this.greenBlinkLight = true;
|
||||
if (swdrStatuses?.stationsAPI == APIDataStatus.WARNING) {
|
||||
message = 'S1a-sceneries';
|
||||
}
|
||||
|
||||
return {
|
||||
lights: this.setLights(message),
|
||||
message
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -59,6 +59,21 @@ export default defineComponent({
|
||||
'store.region.id': {
|
||||
handler(regionId) {
|
||||
this.selectedItemIndex = this.regionList.findIndex((reg) => reg.id == regionId);
|
||||
|
||||
console.log('region id', regionId);
|
||||
}
|
||||
},
|
||||
'$route.query.region': {
|
||||
immediate: true,
|
||||
handler(regionQuery: string) {
|
||||
if (regionQuery) {
|
||||
this.store.region.id =
|
||||
regionsJSON.find(
|
||||
(reg) =>
|
||||
reg.id == regionQuery.toLocaleLowerCase() ||
|
||||
reg.value.toLocaleLowerCase() == regionQuery.toLocaleLowerCase()
|
||||
)?.id || 'eu';
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -70,7 +85,7 @@ export default defineComponent({
|
||||
|
||||
regionList() {
|
||||
return regionsJSON.map((region) => {
|
||||
const regionStationCount = this.store.onlineSceneryList.filter(
|
||||
const regionStationCount = this.store.activeSceneryList.filter(
|
||||
(scenery) => scenery.region == region.id
|
||||
).length;
|
||||
|
||||
|
||||
@@ -61,6 +61,9 @@ export default defineComponent({
|
||||
case Status.ActiveDispatcher.UNKNOWN:
|
||||
return 'unknown';
|
||||
|
||||
case Status.ActiveDispatcher.FREE:
|
||||
return 'free';
|
||||
|
||||
default:
|
||||
if (this.dispatcherTimestamp != null && this.dispatcherStatus >= Date.now() + 25500000)
|
||||
return 'no-limit';
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
<template>
|
||||
<span class="stop-date">
|
||||
<span
|
||||
class="date arrival"
|
||||
v-if="!stop.beginsHere"
|
||||
:class="{
|
||||
delayed: stop.arrivalDelay > 0 && (stop.confirmed || stop.stopped),
|
||||
preponed: stop.arrivalDelay < 0 && (stop.confirmed || stop.stopped),
|
||||
'on-time': stop.arrivalDelay == 0 && stop.confirmed
|
||||
}"
|
||||
>
|
||||
<span v-if="stop.arrivalDelay != 0 && (stop.confirmed || stop.stopped)">
|
||||
<s>{{ timestampToString(stop.arrivalTimestamp) }}</s>
|
||||
{{ timestampToString(stop.arrivalRealTimestamp) }}
|
||||
({{ stop.arrivalDelay > 0 ? '+' : '' }}{{ stop.arrivalDelay }})
|
||||
</span>
|
||||
|
||||
<span v-else>
|
||||
{{ timestampToString(stop.arrivalTimestamp) }}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="date stop"
|
||||
v-if="stop.stopTime || stop.stopped"
|
||||
:class="stop.stopType.replace(', ', '-')"
|
||||
>
|
||||
{{ stop.stopTime }} {{ stop.stopType == '' ? 'pt' : stop.stopType }}
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="date departure"
|
||||
v-if="!stop.terminatesHere && (stop.stopTime != 0 || stop.stopped)"
|
||||
:class="{
|
||||
delayed: stop.departureDelay > 0 && stop.confirmed,
|
||||
preponed: stop.departureDelay < 0 && stop.confirmed
|
||||
}"
|
||||
>
|
||||
<span v-if="stop.departureDelay != 0 && stop.confirmed">
|
||||
<s>{{ timestampToString(stop.departureTimestamp) }}</s>
|
||||
{{ timestampToString(stop.departureRealTimestamp) }}
|
||||
|
||||
({{ stop.departureDelay > 0 ? '+' : '' }}{{ stop.departureDelay }})
|
||||
</span>
|
||||
|
||||
<span v-else>
|
||||
{{ timestampToString(stop.departureTimestamp) }}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent } from 'vue';
|
||||
import dateMixin from '../../mixins/dateMixin';
|
||||
import { TrainStop } from '../../store/typings';
|
||||
|
||||
export default defineComponent({
|
||||
mixins: [dateMixin],
|
||||
|
||||
props: {
|
||||
stop: {
|
||||
type: Object as PropType<TrainStop>,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
setup() {
|
||||
return {};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$preponedClr: lime;
|
||||
$delayedClr: salmon;
|
||||
$dateClr: #525151;
|
||||
$stopExchangeClr: #db8e29;
|
||||
$stopDefaultClr: #252525;
|
||||
|
||||
.stop-date {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.date {
|
||||
background: $dateClr;
|
||||
padding: 0.3em 0.5em;
|
||||
}
|
||||
|
||||
.stop {
|
||||
&.ph,
|
||||
&.ph-pm,
|
||||
&.pm {
|
||||
background: $stopExchangeClr;
|
||||
}
|
||||
|
||||
background: $stopDefaultClr;
|
||||
}
|
||||
|
||||
.arrival,
|
||||
.departure {
|
||||
&.delayed {
|
||||
s {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
span {
|
||||
color: $delayedClr;
|
||||
}
|
||||
}
|
||||
|
||||
&.preponed {
|
||||
s {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
span {
|
||||
color: $preponedClr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,160 +0,0 @@
|
||||
<template>
|
||||
<div class="stats_container" v-click-outside="() => (cardVisible = false)">
|
||||
<button class="stats_button" @click="toggleCard">
|
||||
Statystyki dyżurnego {{ store.dispatcherStatsName }}
|
||||
</button>
|
||||
|
||||
<div class="stats_card" v-if="store.dispatcherStatsName && cardVisible">
|
||||
<div>
|
||||
<Loading v-if="!store.dispatcherStatsData" />
|
||||
|
||||
<div class="loading" v-else-if="!store.dispatcherStatsData._count._all">
|
||||
Ten dyżurny nie ma jeszcze szczegółowych statystyk!
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<h3>STATYSTYKI WYSTAWIONYCH ROZKŁADÓW</h3>
|
||||
|
||||
<div class="info-stats" v-if="store.dispatcherStatsData._count._all">
|
||||
<span class="stat-badge">
|
||||
<span>LICZBA</span>
|
||||
<span>{{ store.dispatcherStatsData._count._all }}</span>
|
||||
</span>
|
||||
<span class="stat-badge">
|
||||
<span>SUMA (KM)</span>
|
||||
<span>{{ store.dispatcherStatsData._sum.routeDistance.toFixed(2) }}km</span>
|
||||
</span>
|
||||
<span class="stat-badge">
|
||||
<span>NAJDŁUŻSZY</span>
|
||||
<span>{{ store.dispatcherStatsData._max.routeDistance.toFixed(2) }}km</span>
|
||||
</span>
|
||||
<span class="stat-badge">
|
||||
<span>ŚREDNIO</span>
|
||||
<span>{{ store.dispatcherStatsData._avg.routeDistance.toFixed(2) }}km</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<h3>OSTATNIE WYSTAWIONE ROZKŁADY</h3>
|
||||
<div class="last-timetables">
|
||||
<div class="timetable-row" v-for="timetable in timetables" :key="timetable.id">
|
||||
#{{ timetable.timetableId }} |
|
||||
<b>{{ timetable.trainCategoryCode }} {{ timetable.trainNo }}</b> |
|
||||
{{ timetable.driverName }} ({{ timetable.routeDistance }}km)
|
||||
<div>{{ timetable.route.replace('|', ' > ') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { useMainStore } from '../../store/mainStore';
|
||||
import Loading from '../Global/Loading.vue';
|
||||
import { API } from '../../typings/api';
|
||||
import http from '../../http';
|
||||
|
||||
export default defineComponent({
|
||||
components: { Loading },
|
||||
|
||||
setup() {
|
||||
const store = useMainStore();
|
||||
|
||||
return {
|
||||
store
|
||||
};
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
cardVisible: false,
|
||||
lastDispatcherName: '',
|
||||
timetables: [] as API.TimetableHistory.Response
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggleCard() {
|
||||
if (!this.store.dispatcherStatsName) return;
|
||||
|
||||
this.cardVisible = !this.cardVisible;
|
||||
if (this.cardVisible) this.fetchDispatcherStats();
|
||||
},
|
||||
|
||||
async fetchDispatcherStats() {
|
||||
if (this.lastDispatcherName != this.store.dispatcherStatsName) {
|
||||
this.store.dispatcherStatsData = undefined;
|
||||
}
|
||||
|
||||
const statsData: API.DispatcherStats.Response = await (
|
||||
await http.get('api/getDispatcherInfo?name=${this.store.dispatcherStatsName}')
|
||||
).data;
|
||||
|
||||
const timetables: API.TimetableHistory.Response = await (
|
||||
await http.get('api/getTimetables?authorName=${this.store.dispatcherStatsName}')
|
||||
).data;
|
||||
|
||||
this.timetables = timetables;
|
||||
this.store.dispatcherStatsData = statsData;
|
||||
this.lastDispatcherName = this.store.dispatcherStatsName;
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../styles/responsive.scss';
|
||||
@import '../../styles/variables.scss';
|
||||
|
||||
.stats_container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.stats_card {
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
top: 120%;
|
||||
right: 0;
|
||||
width: 500px;
|
||||
max-width: 97vw;
|
||||
min-height: 100px;
|
||||
overflow: auto;
|
||||
|
||||
border-radius: 1em 0 1em 1em;
|
||||
background-color: #222222f1;
|
||||
box-shadow: 0 3px 10px 5px #131313;
|
||||
padding: 1em 0.5em;
|
||||
}
|
||||
|
||||
.last-timetables {
|
||||
max-height: 400px;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
.timetable-row {
|
||||
width: 95%;
|
||||
margin: 0.5em auto;
|
||||
padding: 0.5em;
|
||||
|
||||
background-color: #4d4d4d;
|
||||
}
|
||||
|
||||
h2.card-title {
|
||||
font-size: 1.8em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
h2,
|
||||
h3 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.last-timetables {
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -1,128 +1,165 @@
|
||||
<template>
|
||||
<section class="daily-stats">
|
||||
<span :data-active="statsStatus">
|
||||
<b v-if="statsStatus == Status.Data.Loading">
|
||||
{{ $t('app.loading') }}
|
||||
</b>
|
||||
|
||||
<span class="stats-list" v-else>
|
||||
<span class="stats-list">
|
||||
<h3>
|
||||
{{ $t('journal.daily-stats-title') }}
|
||||
{{ $t('journal.daily-stats.title') }}
|
||||
<b class="text--primary">{{ new Date().toLocaleDateString($i18n.locale) }}</b>
|
||||
</h3>
|
||||
|
||||
<hr style="margin-bottom: 0.5em" />
|
||||
<hr class="header-separator" />
|
||||
|
||||
<div v-if="stats.totalTimetables">
|
||||
•
|
||||
<i18n-t keypath="journal.timetable-stats-total">
|
||||
<template #count>
|
||||
<b class="text--primary">
|
||||
{{ stats.totalTimetables }}
|
||||
{{ $t('journal.timetable-count', stats.totalTimetables) }}
|
||||
</b>
|
||||
</template>
|
||||
<b v-if="statsStatus == Status.Data.Loading">
|
||||
{{ $t('app.loading') }}
|
||||
</b>
|
||||
|
||||
<template #distance>
|
||||
<b class="text--primary"> {{ stats.distanceSum?.toFixed(2) }} km</b>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
<b class="text--error" v-else-if="statsStatus == Status.Data.Error">
|
||||
{{ $t('journal.stats-error') }}
|
||||
</b>
|
||||
|
||||
<div v-if="stats.maxTimetable">
|
||||
•
|
||||
<i18n-t keypath="journal.timetable-stats-longest">
|
||||
<template #id>
|
||||
<router-link :to="`/journal/timetables?timetableId=${stats.maxTimetable.id}`">
|
||||
<b>{{ stats.maxTimetable.id }}</b>
|
||||
</router-link>
|
||||
</template>
|
||||
<template #author>
|
||||
<router-link
|
||||
:to="`/journal/dispatchers?dispatcherName=${stats.maxTimetable.authorName}`"
|
||||
>
|
||||
<b>{{ stats.maxTimetable.authorName }}</b>
|
||||
</router-link>
|
||||
</template>
|
||||
<template #driver>
|
||||
<b class="text--primary">{{ stats.maxTimetable.driverName }}</b>
|
||||
</template>
|
||||
<template #distance>
|
||||
<b class="text--primary">{{ stats.maxTimetable.routeDistance }} km</b>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
<b v-else-if="topDispatchers.length == 0">
|
||||
{{ $t('journal.daily-stats.info') }}
|
||||
</b>
|
||||
|
||||
<div v-if="topDispatchers.length == 1">
|
||||
•
|
||||
<i18n-t keypath="journal.timetable-stats-most-active-dr">
|
||||
<template #dispatcher>
|
||||
<router-link :to="`/journal/dispatchers?dispatcherName=${topDispatchers[0].name}`">
|
||||
<b>{{ topDispatchers[0].name }}</b>
|
||||
</router-link>
|
||||
</template>
|
||||
<template #count>
|
||||
<b class="text--primary">
|
||||
{{ topDispatchers[0].count }}
|
||||
{{ $t('journal.timetable-count', topDispatchers[0].count) }}
|
||||
</b>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-if="stats.totalTimetables">
|
||||
•
|
||||
<i18n-t keypath="journal.daily-stats.total">
|
||||
<template #count>
|
||||
<b class="text--primary">
|
||||
{{ stats.totalTimetables }}
|
||||
{{ $t('journal.daily-stats.count', stats.totalTimetables) }}
|
||||
</b>
|
||||
</template>
|
||||
|
||||
<div v-if="topDispatchers.length > 1">
|
||||
•
|
||||
<i18n-t keypath="journal.timetable-stats-most-active-dr-many">
|
||||
<template #dispatchers>
|
||||
<span v-for="(disp, i) in topDispatchers" :key="i">
|
||||
<span v-if="i == topDispatchers.length - 1"> {{ $t('general.and') }} </span>
|
||||
<template #distance>
|
||||
<b class="text--primary"> {{ stats.distanceSum?.toFixed(2) }} km</b>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
|
||||
<router-link :to="`/journal/dispatchers?dispatcherName=${disp.name}`">
|
||||
<b>{{ disp.name }}</b>
|
||||
<div v-if="stats.maxTimetable">
|
||||
•
|
||||
<i18n-t keypath="journal.daily-stats.longest">
|
||||
<template #id>
|
||||
<router-link :to="`/journal/timetables?search-train=%23${stats.maxTimetable.id}`">
|
||||
<b>{{ stats.maxTimetable.id }}</b>
|
||||
</router-link>
|
||||
</template>
|
||||
<template #author>
|
||||
<router-link
|
||||
:to="`/journal/timetables?search-dispatcher=${stats.maxTimetable.authorName}`"
|
||||
>
|
||||
<b>{{ stats.maxTimetable.authorName }}</b>
|
||||
</router-link>
|
||||
</template>
|
||||
<template #driver>
|
||||
<b class="text--primary">{{ stats.maxTimetable.driverName }}</b>
|
||||
</template>
|
||||
<template #distance>
|
||||
<b class="text--primary">{{ stats.maxTimetable.routeDistance }} km</b>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
|
||||
<span v-if="i < topDispatchers.length - 2">, </span>
|
||||
</span>
|
||||
</template>
|
||||
<div v-if="topDispatchers.length == 1">
|
||||
•
|
||||
<i18n-t keypath="journal.daily-stats.most-active-dr">
|
||||
<template #dispatcher>
|
||||
<router-link
|
||||
:to="`/journal/dispatchers?search-dispatcher=${topDispatchers[0].name}`"
|
||||
>
|
||||
<b>{{ topDispatchers[0].name }}</b>
|
||||
</router-link>
|
||||
</template>
|
||||
<template #count>
|
||||
<b class="text--primary">
|
||||
{{ topDispatchers[0].count }}
|
||||
{{ $t('journal.daily-stats.count', topDispatchers[0].count) }}
|
||||
</b>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
|
||||
<template #count>
|
||||
<b class="text--primary">
|
||||
{{ topDispatchers[0].count }}
|
||||
{{ $t('journal.timetable-count', topDispatchers[0].count) }}
|
||||
</b>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
<div v-if="topDispatchers.length > 1">
|
||||
•
|
||||
<i18n-t keypath="journal.daily-stats.most-active-dr-many">
|
||||
<template #dispatchers>
|
||||
<span v-for="(disp, i) in topDispatchers" :key="i">
|
||||
<span v-if="i == topDispatchers.length - 1"> {{ $t('general.and') }} </span>
|
||||
|
||||
<div v-if="stats.longestDuties.length > 0">
|
||||
•
|
||||
<i18n-t keypath="journal.timetable-stats-longest-duties">
|
||||
<template #dispatcher>
|
||||
<router-link
|
||||
:to="`/journal/dispatchers?dispatcherName=${stats.longestDuties[0].name}`"
|
||||
>
|
||||
<b>{{ stats.longestDuties[0].name }}</b>
|
||||
</router-link>
|
||||
</template>
|
||||
<router-link :to="`/journal/dispatchers?search-dispatcher=${disp.name}`">
|
||||
<b>{{ disp.name }}</b>
|
||||
</router-link>
|
||||
|
||||
<template #station>{{ stats.longestDuties[0].station }}</template>
|
||||
<span v-if="i < topDispatchers.length - 2">, </span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template #duration>
|
||||
{{ calculateDuration(stats.longestDuties[0].duration) }}
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
<template #count>
|
||||
<b class="text--primary">
|
||||
{{ topDispatchers[0].count }}
|
||||
{{ $t('journal.daily-stats.count', topDispatchers[0].count) }}
|
||||
</b>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
|
||||
<div v-if="stats.mostActiveDrivers.length > 0">
|
||||
•
|
||||
<i18n-t keypath="journal.timetable-stats-most-active-driver">
|
||||
<template #driver>
|
||||
<b class="text--primary">{{ stats.mostActiveDrivers[0].name }}</b>
|
||||
</template>
|
||||
<template #distance>
|
||||
<b class="text--primary">{{ stats.mostActiveDrivers[0].distance.toFixed(2) }} km</b>
|
||||
</template>
|
||||
</i18n-t>
|
||||
<div v-if="stats.longestDuties.length > 0">
|
||||
•
|
||||
<i18n-t keypath="journal.daily-stats.longest-duties">
|
||||
<template #dispatcher>
|
||||
<router-link
|
||||
:to="`/journal/dispatchers?search-dispatcher=${stats.longestDuties[0].name}`"
|
||||
>
|
||||
<b>{{ stats.longestDuties[0].name }}</b>
|
||||
</router-link>
|
||||
</template>
|
||||
|
||||
<template #station>{{ stats.longestDuties[0].station }}</template>
|
||||
|
||||
<template #duration>
|
||||
{{ calculateDuration(stats.longestDuties[0].duration) }}
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
|
||||
<div v-if="stats.mostActiveDrivers.length > 0">
|
||||
•
|
||||
<i18n-t keypath="journal.daily-stats.most-active-driver">
|
||||
<template #driver>
|
||||
<router-link
|
||||
:to="`/journal/timetables?search-driver=${stats.mostActiveDrivers[0].name}`"
|
||||
>
|
||||
<b>{{ stats.mostActiveDrivers[0].name }}</b>
|
||||
</router-link>
|
||||
</template>
|
||||
<template #distance>
|
||||
<b class="text--primary">{{ stats.mostActiveDrivers[0].distance.toFixed(2) }} km</b>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
|
||||
<hr class="section-separator" />
|
||||
|
||||
<div class="stats-badges">
|
||||
<span
|
||||
class="stat-badge"
|
||||
v-for="key in [
|
||||
'rippedSwitches',
|
||||
'derailments',
|
||||
'skippedStopSignals',
|
||||
'radioStops',
|
||||
'kills'
|
||||
]"
|
||||
:key="key"
|
||||
>
|
||||
<span>{{ $t(`journal.daily-stats.${key}`) }}</span>
|
||||
<span>{{
|
||||
Object.entries(stats.globalDiff).find(([k, v]) => k == key)?.[1] || '--'
|
||||
}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
@@ -203,6 +240,8 @@ export default defineComponent({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../styles/responsive.scss';
|
||||
@import '../../styles/JournalStats.scss';
|
||||
@import '../../styles/badge.scss';
|
||||
|
||||
.daily-stats {
|
||||
text-align: left;
|
||||
@@ -215,6 +254,12 @@ export default defineComponent({
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.stats-badges {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5em;
|
||||
}
|
||||
|
||||
@include smallScreen {
|
||||
h3 {
|
||||
text-align: center;
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<div class="journal-stats dispatcher" v-if="dispatcherName && stats">
|
||||
<span class="loading" v-if="!stats.issuedTimetables && !stats.services">
|
||||
{{ $t('journal.dispatcher-stats.empty') }}
|
||||
</span>
|
||||
|
||||
<span v-else>
|
||||
<h3>
|
||||
<i18n-t keypath="journal.dispatcher-stats.title">
|
||||
<template #name>
|
||||
<span class="text--primary">{{ dispatcherName.toUpperCase() }}</span>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</h3>
|
||||
|
||||
<hr class="header-separator" />
|
||||
|
||||
<div class="info-stats">
|
||||
<span class="stat-badge" v-if="stats.services">
|
||||
<span>{{ $t('journal.dispatcher-stats.services-count') }}</span>
|
||||
<span>{{ stats.services.count }}</span>
|
||||
</span>
|
||||
|
||||
<span class="stat-badge" v-if="stats.services">
|
||||
<span>{{ $t('journal.dispatcher-stats.service-max') }}</span>
|
||||
<span>{{ calculateDuration(stats.services.durationMax) }}</span>
|
||||
</span>
|
||||
|
||||
<span class="stat-badge" v-if="stats.services">
|
||||
<span>{{ $t('journal.dispatcher-stats.service-avg') }}</span>
|
||||
<span>{{ calculateDuration(stats.services.durationAvg) }}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<hr class="section-separator" />
|
||||
|
||||
<div class="info-stats">
|
||||
<span class="stat-badge" v-if="stats.issuedTimetables">
|
||||
<span>{{ $t('journal.dispatcher-stats.timetables-count') }}</span>
|
||||
<span>{{ stats.issuedTimetables.count }}</span>
|
||||
</span>
|
||||
|
||||
<span class="stat-badge" v-if="stats.issuedTimetables">
|
||||
<span>{{ $t('journal.dispatcher-stats.timetables-sum') }}</span>
|
||||
<span>{{ stats.issuedTimetables.distanceSum.toFixed(2) }}km</span>
|
||||
</span>
|
||||
|
||||
<span class="stat-badge" v-if="stats.issuedTimetables">
|
||||
<span>{{ $t('journal.dispatcher-stats.timetables-max') }}</span>
|
||||
<span>{{ stats.issuedTimetables.distanceMax.toFixed(2) }}km</span>
|
||||
</span>
|
||||
|
||||
<span class="stat-badge" v-if="stats.issuedTimetables">
|
||||
<span>{{ $t('journal.dispatcher-stats.timetables-avg') }}</span>
|
||||
<span>{{ stats.issuedTimetables.distanceAvg.toFixed(2) }}km</span>
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import dateMixin from '../../../mixins/dateMixin';
|
||||
import { useMainStore } from '../../../store/mainStore';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'journal-dispatcher-stats',
|
||||
|
||||
mixins: [dateMixin],
|
||||
|
||||
setup() {
|
||||
const store = useMainStore();
|
||||
|
||||
return {
|
||||
stats: store.dispatcherStatsData,
|
||||
dispatcherName: store.dispatcherStatsName
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../../styles/JournalStats.scss';
|
||||
</style>
|
||||
@@ -0,0 +1,256 @@
|
||||
<template>
|
||||
<transition name="status-anim" mode="out-in">
|
||||
<div :key="dataStatus">
|
||||
<div class="journal_warning" v-if="store.isOffline">
|
||||
{{ $t('app.offline') }}
|
||||
</div>
|
||||
|
||||
<Loading v-else-if="dataStatus == Status.Data.Loading" />
|
||||
|
||||
<div v-else-if="dataStatus == Status.Data.Error" class="journal_warning error">
|
||||
{{ $t('app.error') }}
|
||||
</div>
|
||||
|
||||
<div class="journal_warning" v-else-if="dispatcherHistory.length == 0">
|
||||
{{ $t('app.no-result') }}
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<table class="dispatchers-table">
|
||||
<thead>
|
||||
<th>{{ $t('journal.history-name') }}</th>
|
||||
<th>{{ $t('journal.history-hash') }}</th>
|
||||
<th>{{ $t('journal.history-dispatcher') }}</th>
|
||||
<th>{{ $t('journal.history-level') }}</th>
|
||||
<th>{{ $t('journal.history-rate') }}</th>
|
||||
<th>{{ $t('journal.history-region') }}</th>
|
||||
<th>{{ $t('journal.history-date') }}</th>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<transition-group name="list-anim">
|
||||
<tr v-for="historyItem in dispatcherHistory" :key="historyItem.id">
|
||||
<td>
|
||||
<router-link
|
||||
:to="`/journal/dispatchers?search-station=${historyItem.stationName}`"
|
||||
>
|
||||
<b>{{ historyItem.stationName }}</b>
|
||||
</router-link>
|
||||
</td>
|
||||
<td>#{{ historyItem.stationHash }}</td>
|
||||
<td>
|
||||
<router-link
|
||||
:to="`/journal/dispatchers?search-dispatcher=${historyItem.dispatcherName}`"
|
||||
>
|
||||
<b
|
||||
v-if="isDonator(historyItem.dispatcherName)"
|
||||
class="text--donator"
|
||||
:title="$t('donations.dispatcher-message')"
|
||||
>
|
||||
{{ historyItem.dispatcherName }}
|
||||
</b>
|
||||
|
||||
<b v-else>
|
||||
{{ historyItem.dispatcherName }}
|
||||
</b>
|
||||
</router-link>
|
||||
</td>
|
||||
<td>
|
||||
<b
|
||||
v-if="historyItem.dispatcherLevel !== null"
|
||||
class="level-badge dispatcher"
|
||||
:style="
|
||||
calculateExpStyle(
|
||||
historyItem.dispatcherLevel,
|
||||
historyItem.dispatcherIsSupporter
|
||||
)
|
||||
"
|
||||
>
|
||||
{{ historyItem.dispatcherLevel >= 2 ? historyItem.dispatcherLevel : 'L' }}
|
||||
</b>
|
||||
</td>
|
||||
<td class="text--primary">
|
||||
<b>{{ historyItem.dispatcherRate }}</b>
|
||||
</td>
|
||||
<td>
|
||||
<b class="region-badge" :aria-describedby="historyItem.region">{{
|
||||
regions.find((r) => r.id == historyItem.region)?.value || '???'
|
||||
}}</b>
|
||||
</td>
|
||||
<td style="min-width: 200px" class="time">
|
||||
<span v-if="historyItem.timestampTo" class="text--offline">
|
||||
<b>{{ $d(historyItem.timestampFrom) }}</b>
|
||||
{{ timestampToString(historyItem.timestampFrom) }}
|
||||
- {{ timestampToString(historyItem.timestampTo) }} ({{
|
||||
calculateDuration(historyItem.currentDuration)
|
||||
}})
|
||||
</span>
|
||||
<span class="dispatcher-online" v-else>
|
||||
<b class="text--online">
|
||||
<router-link :to="`/scenery?station=${historyItem.stationName}`">{{
|
||||
$t('journal.online-since')
|
||||
}}</router-link>
|
||||
{{ timestampToString(historyItem.timestampFrom) }}
|
||||
</b>
|
||||
({{ calculateDuration(historyItem.currentDuration) }})
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</transition-group>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<AddDataButton
|
||||
:list="dispatcherHistory"
|
||||
:scrollDataLoaded="scrollDataLoaded"
|
||||
:scrollNoMoreData="scrollNoMoreData"
|
||||
@addHistoryData="addHistoryData"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="journal_warning" v-if="scrollNoMoreData">
|
||||
{{ $t('journal.no-further-data') }}
|
||||
</div>
|
||||
|
||||
<div class="journal_warning" v-else-if="!scrollDataLoaded">
|
||||
{{ $t('journal.loading-further-data') }}
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
import { regions } from '../../../data/options.json';
|
||||
import { useMainStore } from '../../../store/mainStore';
|
||||
import { API } from '../../../typings/api';
|
||||
import { Status } from '../../../typings/common';
|
||||
import Loading from '../../Global/Loading.vue';
|
||||
import AddDataButton from '../../Global/AddDataButton.vue';
|
||||
import dateMixin from '../../../mixins/dateMixin';
|
||||
import donatorMixin from '../../../mixins/donatorMixin';
|
||||
import styleMixin from '../../../mixins/styleMixin';
|
||||
|
||||
export default defineComponent({
|
||||
components: { Loading, AddDataButton },
|
||||
|
||||
mixins: [dateMixin, styleMixin, donatorMixin],
|
||||
|
||||
props: {
|
||||
dispatcherHistory: {
|
||||
type: Array as PropType<API.DispatcherHistory.Response>,
|
||||
required: true
|
||||
},
|
||||
scrollNoMoreData: {
|
||||
type: Boolean
|
||||
},
|
||||
scrollDataLoaded: {
|
||||
type: Boolean
|
||||
},
|
||||
addHistoryData: {
|
||||
type: Function as PropType<() => void>
|
||||
},
|
||||
dataStatus: {
|
||||
type: Number as PropType<Status.Data>
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
Status,
|
||||
store: useMainStore(),
|
||||
regions
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
computedDispatcherHistory() {
|
||||
return this.dispatcherHistory.reduce(
|
||||
(acc, historyItem, i) => {
|
||||
if (this.isAnotherDay(i - 1, i))
|
||||
acc.push(new Date(historyItem.timestampFrom).toLocaleDateString('pl-PL'));
|
||||
acc.push(historyItem);
|
||||
|
||||
return acc;
|
||||
},
|
||||
[] as (API.DispatcherHistory.Data | string)[]
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
navigateToScenery(name: string, isOnline: boolean) {
|
||||
if (!isOnline) return;
|
||||
|
||||
this.$router.push(`/scenery?station=${name.trim().replace(/ /g, '_')}`);
|
||||
},
|
||||
|
||||
isAnotherDay(prevIndex: number, currIndex: number) {
|
||||
if (currIndex == 0) return true;
|
||||
|
||||
return (
|
||||
new Date(this.dispatcherHistory[prevIndex].timestampFrom).getDate() !=
|
||||
new Date(this.dispatcherHistory[currIndex].timestampFrom).getDate()
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../../styles/animations.scss';
|
||||
@import '../../../styles/responsive.scss';
|
||||
@import '../../../styles/badge.scss';
|
||||
@import '../../../styles/variables.scss';
|
||||
@import '../../../styles/JournalSection.scss';
|
||||
|
||||
table.dispatchers-table {
|
||||
--_bg-table: #111;
|
||||
--_bg-head: #101010;
|
||||
--_bg-row: #2f2f2f;
|
||||
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
|
||||
margin-bottom: 1em;
|
||||
|
||||
thead {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background-color: var(--_bg-head);
|
||||
}
|
||||
|
||||
th {
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
tr {
|
||||
background-color: var(--_bg-row);
|
||||
border-bottom: 2px solid black;
|
||||
|
||||
&:last-child {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 0.75em;
|
||||
|
||||
.level-badge {
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
&--online {
|
||||
color: springgreen;
|
||||
}
|
||||
|
||||
&--offline {
|
||||
color: #ddd;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,260 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<transition name="status-anim" mode="out-in">
|
||||
<div :key="dataStatus">
|
||||
<div class="journal_warning" v-if="store.isOffline">
|
||||
{{ $t('app.offline') }}
|
||||
</div>
|
||||
|
||||
<Loading v-else-if="dataStatus == Status.Data.Loading" />
|
||||
|
||||
<div v-else-if="dataStatus == Status.Data.Error" class="journal_warning error">
|
||||
{{ $t('app.error') }}
|
||||
</div>
|
||||
|
||||
<div class="journal_warning" v-else-if="dispatcherHistory.length == 0">
|
||||
{{ $t('app.no-result') }}
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<table class="scenery-history-table">
|
||||
<thead>
|
||||
<th>{{ $t('journal.history-name') }}</th>
|
||||
<th>{{ $t('journal.history-hash') }}</th>
|
||||
<th>{{ $t('journal.history-dispatcher') }}</th>
|
||||
<th>{{ $t('journal.history-level') }}</th>
|
||||
<th>{{ $t('journal.history-rate') }}</th>
|
||||
<th>{{ $t('journal.history-region') }}</th>
|
||||
<th>{{ $t('journal.history-date') }}</th>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<transition-group name="list-anim">
|
||||
<tr v-for="historyItem in dispatcherHistory" :key="historyItem.id">
|
||||
<td>
|
||||
<router-link
|
||||
:to="`/journal/dispatchers?sceneryName=${historyItem.stationName}`"
|
||||
>
|
||||
<b>{{ historyItem.stationName }}</b>
|
||||
</router-link>
|
||||
</td>
|
||||
<td>#{{ historyItem.stationHash }}</td>
|
||||
<td>
|
||||
<router-link
|
||||
:to="`/journal/dispatchers?dispatcherName=${historyItem.dispatcherName}`"
|
||||
>
|
||||
<b
|
||||
v-if="isDonator(historyItem.dispatcherName)"
|
||||
class="text--donator"
|
||||
:title="$t('donations.dispatcher-message')"
|
||||
>
|
||||
{{ historyItem.dispatcherName }}
|
||||
</b>
|
||||
|
||||
<b v-else>
|
||||
{{ historyItem.dispatcherName }}
|
||||
</b>
|
||||
</router-link>
|
||||
</td>
|
||||
<td>
|
||||
<b
|
||||
v-if="historyItem.dispatcherLevel !== null"
|
||||
class="level-badge dispatcher"
|
||||
:style="
|
||||
calculateExpStyle(
|
||||
historyItem.dispatcherLevel,
|
||||
historyItem.dispatcherIsSupporter
|
||||
)
|
||||
"
|
||||
>
|
||||
{{ historyItem.dispatcherLevel >= 2 ? historyItem.dispatcherLevel : 'L' }}
|
||||
</b>
|
||||
</td>
|
||||
<td class="text--primary">
|
||||
<b>{{ historyItem.dispatcherRate }}</b>
|
||||
</td>
|
||||
<td>
|
||||
<b class="region-badge" :aria-describedby="historyItem.region">{{
|
||||
regions.find((r) => r.id == historyItem.region)?.value || '???'
|
||||
}}</b>
|
||||
</td>
|
||||
<td style="min-width: 200px" class="time">
|
||||
<span v-if="historyItem.timestampTo" class="text--offline">
|
||||
<b>{{ $d(historyItem.timestampFrom) }}</b>
|
||||
{{ timestampToString(historyItem.timestampFrom) }}
|
||||
- {{ timestampToString(historyItem.timestampTo) }} ({{
|
||||
calculateDuration(historyItem.currentDuration)
|
||||
}})
|
||||
</span>
|
||||
<span class="dispatcher-online" v-else>
|
||||
<b class="text--online">
|
||||
<router-link :to="`/scenery?station=${historyItem.stationName}`">{{
|
||||
$t('journal.online-since')
|
||||
}}</router-link>
|
||||
{{ timestampToString(historyItem.timestampFrom) }}
|
||||
</b>
|
||||
({{ calculateDuration(historyItem.currentDuration) }})
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</transition-group>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<AddDataButton
|
||||
:list="dispatcherHistory"
|
||||
:scrollDataLoaded="scrollDataLoaded"
|
||||
:scrollNoMoreData="scrollNoMoreData"
|
||||
@addHistoryData="addHistoryData"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<div class="journal_warning" v-if="scrollNoMoreData">
|
||||
{{ $t('journal.no-further-data') }}
|
||||
</div>
|
||||
|
||||
<div class="journal_warning" v-else-if="!scrollDataLoaded">
|
||||
{{ $t('journal.loading-further-data') }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
import dateMixin from '../../mixins/dateMixin';
|
||||
import styleMixin from '../../mixins/styleMixin';
|
||||
import { useMainStore } from '../../store/mainStore';
|
||||
import Loading from '../Global/Loading.vue';
|
||||
import { regions } from '../../data/options.json';
|
||||
import AddDataButton from '../Global/AddDataButton.vue';
|
||||
import { API } from '../../typings/api';
|
||||
import { Status } from '../../typings/common';
|
||||
import donatorMixin from '../../mixins/donatorMixin';
|
||||
|
||||
export default defineComponent({
|
||||
components: { Loading, AddDataButton },
|
||||
|
||||
mixins: [dateMixin, styleMixin, donatorMixin],
|
||||
|
||||
props: {
|
||||
dispatcherHistory: {
|
||||
type: Array as PropType<API.DispatcherHistory.Response>,
|
||||
required: true
|
||||
},
|
||||
scrollNoMoreData: {
|
||||
type: Boolean
|
||||
},
|
||||
scrollDataLoaded: {
|
||||
type: Boolean
|
||||
},
|
||||
addHistoryData: {
|
||||
type: Function as PropType<() => void>
|
||||
},
|
||||
dataStatus: {
|
||||
type: Number as PropType<Status.Data>
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
Status,
|
||||
store: useMainStore(),
|
||||
regions
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
computedDispatcherHistory() {
|
||||
console.log(this.dispatcherHistory.length);
|
||||
|
||||
return this.dispatcherHistory.reduce(
|
||||
(acc, historyItem, i) => {
|
||||
if (this.isAnotherDay(i - 1, i))
|
||||
acc.push(new Date(historyItem.timestampFrom).toLocaleDateString('pl-PL'));
|
||||
acc.push(historyItem);
|
||||
|
||||
return acc;
|
||||
},
|
||||
[] as (API.DispatcherHistory.Data | string)[]
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
navigateToScenery(name: string, isOnline: boolean) {
|
||||
if (!isOnline) return;
|
||||
|
||||
this.$router.push(`/scenery?station=${name.trim().replace(/ /g, '_')}`);
|
||||
},
|
||||
|
||||
isAnotherDay(prevIndex: number, currIndex: number) {
|
||||
if (currIndex == 0) return true;
|
||||
|
||||
return (
|
||||
new Date(this.dispatcherHistory[prevIndex].timestampFrom).getDate() !=
|
||||
new Date(this.dispatcherHistory[currIndex].timestampFrom).getDate()
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../styles/animations.scss';
|
||||
@import '../../styles/responsive.scss';
|
||||
@import '../../styles/badge.scss';
|
||||
@import '../../styles/variables.scss';
|
||||
@import '../../styles/JournalSection.scss';
|
||||
|
||||
table.scenery-history-table {
|
||||
--_bg-table: #111;
|
||||
--_bg-head: #101010;
|
||||
--_bg-row: #2f2f2f;
|
||||
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
|
||||
margin-bottom: 1em;
|
||||
|
||||
thead {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background-color: var(--_bg-head);
|
||||
}
|
||||
|
||||
th {
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
tr {
|
||||
background-color: var(--_bg-row);
|
||||
border-bottom: 2px solid black;
|
||||
|
||||
&:last-child {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 0.75em;
|
||||
|
||||
.level-badge {
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
&--online {
|
||||
color: springgreen;
|
||||
}
|
||||
|
||||
&--offline {
|
||||
color: #ddd;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -33,7 +33,7 @@
|
||||
<h1 class="option-title">{{ $t('options.search-title') }}</h1>
|
||||
<div class="search_content">
|
||||
<div class="search" v-for="(_, propName) in searchersValues" :key="propName">
|
||||
<label v-if="propName == 'search-date'" for="date">{{
|
||||
<label v-if="propName == 'search-date'" for="search-date">{{
|
||||
$t(`options.search-${optionsType}-date`)
|
||||
}}</label>
|
||||
|
||||
@@ -41,12 +41,13 @@
|
||||
<input
|
||||
class="search-input"
|
||||
v-model="searchersValues[propName]"
|
||||
@keydown.enter="onSearchConfirm"
|
||||
@keydown.enter="searchConfirm"
|
||||
@focus="preventKeyDown = true"
|
||||
@blur="preventKeyDown = false"
|
||||
:placeholder="$t(`options.${propName}`)"
|
||||
:type="propName == 'search-date' ? 'date' : 'text'"
|
||||
:min="propName == 'search-date' ? '2022-02-01' : undefined"
|
||||
:id="`${propName}`"
|
||||
:list="propName.toString()"
|
||||
/>
|
||||
|
||||
@@ -114,7 +115,6 @@ import { defineComponent, inject, PropType } from 'vue';
|
||||
import keyMixin from '../../mixins/keyMixin';
|
||||
import { useMainStore } from '../../store/mainStore';
|
||||
import { Journal } from './typings';
|
||||
import { API } from '../../typings/api';
|
||||
import { Status } from '../../typings/common';
|
||||
import http from '../../http';
|
||||
|
||||
@@ -181,10 +181,6 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
watch: {
|
||||
async 'store.driverStatsName'() {
|
||||
await this.fetchDriverStats();
|
||||
},
|
||||
|
||||
async 'searchersValues.search-driver'(value: string | undefined) {
|
||||
clearTimeout(this.searchTimeout);
|
||||
|
||||
@@ -203,27 +199,34 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
methods: {
|
||||
async fetchDriverStats() {
|
||||
this.store.driverStatsData = undefined;
|
||||
// filters & sorters from URL params
|
||||
handleRouteParams() {
|
||||
this.$router.push({
|
||||
query: {
|
||||
...this.$route.query,
|
||||
'sorter-active':
|
||||
this.sorterOptionIds.indexOf(`${this.sorterActive.id}`) != 0
|
||||
? this.sorterActive.id
|
||||
: undefined,
|
||||
...Object.keys(this.searchersValues).reduce(
|
||||
(acc, k) => {
|
||||
const searchVal = this.searchersValues[k as Journal.TimetableSearchKey];
|
||||
|
||||
if (!this.store.driverStatsName) {
|
||||
this.store.driverStatsStatus = Status.Data.Initialized;
|
||||
return;
|
||||
}
|
||||
acc[k] = searchVal || undefined;
|
||||
|
||||
try {
|
||||
this.store.driverStatsStatus = Status.Data.Loading;
|
||||
|
||||
const statsData: API.DriverStats.Response = await (
|
||||
await http.get(`api/getDriverInfo?name=${this.store.driverStatsName}`)
|
||||
).data;
|
||||
|
||||
this.store.driverStatsData = statsData;
|
||||
this.store.driverStatsStatus = Status.Data.Loaded;
|
||||
} catch (error) {
|
||||
this.store.driverStatsStatus = Status.Data.Error;
|
||||
console.error('Ups! Wystąpił błąd przy próbie pobrania statystyk maszynisty! :/');
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{} as { [k: string]: string | undefined }
|
||||
),
|
||||
...this.filterList?.reduce(
|
||||
(acc, f) => {
|
||||
if (f.isActive) acc[f.filterSection] = f.default ? undefined : f.id;
|
||||
return acc;
|
||||
},
|
||||
{} as { [k: string]: string | undefined }
|
||||
)
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
refreshData() {
|
||||
@@ -245,7 +248,7 @@ export default defineComponent({
|
||||
} catch (error) {
|
||||
this[`${type}Suggestions`] = [];
|
||||
}
|
||||
}, 450);
|
||||
}, 250);
|
||||
},
|
||||
|
||||
// Override keyMixin function
|
||||
@@ -260,7 +263,7 @@ export default defineComponent({
|
||||
onSorterChange(item: { id: string | number; value: string }) {
|
||||
this.sorterActive.id = item.id;
|
||||
this.sorterActive.dir = -1;
|
||||
this.$emit('onSearchConfirm');
|
||||
this.searchConfirm();
|
||||
},
|
||||
|
||||
onFilterChange(filter: Journal.TimetableFilter) {
|
||||
@@ -270,25 +273,27 @@ export default defineComponent({
|
||||
.forEach((f) => (f.isActive = false));
|
||||
filter.isActive = true;
|
||||
|
||||
this.$emit('onSearchConfirm');
|
||||
this.searchConfirm();
|
||||
},
|
||||
|
||||
onInputClear(id: any) {
|
||||
this.searchersValues[id] = '';
|
||||
this.$emit('onSearchConfirm');
|
||||
this.searchConfirm();
|
||||
},
|
||||
|
||||
onSearchConfirm() {
|
||||
searchConfirm() {
|
||||
this.$emit('onSearchConfirm');
|
||||
this.handleRouteParams();
|
||||
},
|
||||
|
||||
onSearchButtonConfirm() {
|
||||
this.showOptions = false;
|
||||
this.$emit('onSearchConfirm');
|
||||
this.searchConfirm();
|
||||
},
|
||||
|
||||
onResetButtonClick() {
|
||||
this.$emit('onOptionsReset');
|
||||
this.handleRouteParams();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
<template>
|
||||
<div class="journal-stats" v-if="!store.isOffline">
|
||||
<div class="stats-buttons">
|
||||
<div
|
||||
class="journal-stats dropdown"
|
||||
v-if="!mainStore.isOffline"
|
||||
@keydown.esc="currentStatsTab = null"
|
||||
>
|
||||
<div
|
||||
class="dropdown_background"
|
||||
v-if="currentStatsTab !== null"
|
||||
@click="currentStatsTab = null"
|
||||
></div>
|
||||
|
||||
<div class="actions-bar">
|
||||
<button
|
||||
v-for="button in data.statsButtons"
|
||||
:key="button.name"
|
||||
v-for="button in statsButtons"
|
||||
:key="button.tab"
|
||||
class="btn--filled btn--image"
|
||||
:data-selected="button.name == currentStatsTab"
|
||||
@click="onTabButtonClick(button.name)"
|
||||
:data-selected="button.tab == currentStatsTab"
|
||||
:data-disabled="button.disabled"
|
||||
:disabled="button.disabled"
|
||||
@click="onTabButtonClick(button.tab)"
|
||||
>
|
||||
<img
|
||||
v-if="button.iconName"
|
||||
@@ -17,87 +29,57 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="stats-tab" v-show="currentStatsTab !== null">
|
||||
<keep-alive>
|
||||
<JournalDailyStats v-if="currentStatsTab == 'journal-daily-stats'" />
|
||||
<JournalDriverStats v-else-if="currentStatsTab == 'journal-driver-stats'" />
|
||||
</keep-alive>
|
||||
</div>
|
||||
<transition name="dropdown-anim">
|
||||
<div class="dropdown_wrapper" v-if="currentStatsTab !== null">
|
||||
<keep-alive>
|
||||
<component :is="currentStatsTab" :key="currentStatsTab"></component>
|
||||
</keep-alive>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, Ref, ref, watch } from 'vue';
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
import { useMainStore } from '../../store/mainStore';
|
||||
import JournalDailyStats from './JournalDailyStats.vue';
|
||||
import JournalDriverStats from './JournalDriverStats.vue';
|
||||
import StorageManager from '../../managers/storageManager';
|
||||
import { Journal } from './typings';
|
||||
import JournalDailyStats from './JournalDailyStats.vue';
|
||||
import JournalDispatcherStats from '../JournalView/JournalDispatchers/JournalDispatcherStats.vue';
|
||||
import JournalDriverStats from '../JournalView/JournalTimetables/JournalDriverStats.vue';
|
||||
|
||||
export type JournalStatsTab = 'journal-driver-stats' | 'journal-daily-stats';
|
||||
|
||||
const store = useMainStore();
|
||||
const currentStatsTab: Ref<JournalStatsTab | null> = ref(null);
|
||||
|
||||
let data = reactive({
|
||||
statsButtons: [
|
||||
{
|
||||
name: 'journal-daily-stats',
|
||||
localeKey: 'journal.daily-stats-title',
|
||||
iconName: 'stats'
|
||||
},
|
||||
{
|
||||
name: 'journal-driver-stats',
|
||||
localeKey: 'journal.driver-stats-title',
|
||||
iconName: 'user'
|
||||
export default defineComponent({
|
||||
components: { JournalDailyStats, JournalDriverStats, JournalDispatcherStats },
|
||||
props: {
|
||||
statsButtons: {
|
||||
type: Array as PropType<Journal.StatsButton[]>,
|
||||
required: true
|
||||
}
|
||||
] as { name: JournalStatsTab; localeKey: string; iconName?: string }[]
|
||||
});
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
Journal,
|
||||
mainStore: useMainStore(),
|
||||
currentStatsTab: null as Journal.StatsTab | null
|
||||
};
|
||||
},
|
||||
|
||||
function onTabButtonClick(tab: JournalStatsTab) {
|
||||
currentStatsTab.value = tab == currentStatsTab.value ? null : tab;
|
||||
StorageManager.setStringValue('journalStatsTab', currentStatsTab.value ?? '');
|
||||
}
|
||||
methods: {
|
||||
onTabButtonClick(tab: Journal.StatsTab) {
|
||||
this.currentStatsTab = tab == this.currentStatsTab ? null : tab;
|
||||
|
||||
watch(
|
||||
computed(() => store.driverStatsData),
|
||||
(newData, prevData) => {
|
||||
currentStatsTab.value =
|
||||
JSON.stringify(prevData) !== JSON.stringify(newData) && newData !== undefined
|
||||
? 'journal-driver-stats'
|
||||
: currentStatsTab.value;
|
||||
StorageManager.setStringValue('journalStatsTab', this.currentStatsTab ?? '');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
const storedTab = StorageManager.getStringValue('journalStatsTab');
|
||||
|
||||
if (storedTab && storedTab !== '') currentStatsTab.value = storedTab as JournalStatsTab;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../styles/JournalStats.scss';
|
||||
@import '../../styles/dropdown.scss';
|
||||
@import '../../styles/dropdown_filters.scss';
|
||||
@import '../../styles/variables.scss';
|
||||
|
||||
.stats-buttons {
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
|
||||
margin-bottom: 0.5em;
|
||||
|
||||
button {
|
||||
font-weight: bold;
|
||||
padding: 0.5em 0.75em;
|
||||
|
||||
&[data-inactive='true'] {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
&[data-selected='true'] {
|
||||
color: $accentCol;
|
||||
}
|
||||
}
|
||||
.dropdown_wrapper {
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
+17
-20
@@ -1,14 +1,19 @@
|
||||
<template>
|
||||
<div class="journal-stats">
|
||||
<span v-if="store.driverStatsData">
|
||||
<div class="journal-stats driver" v-if="store.driverStatsData">
|
||||
<span>
|
||||
<h3>
|
||||
{{ $t('journal.stats-title') }}
|
||||
<span class="text--primary">{{ store.driverStatsName.toUpperCase() }}</span>
|
||||
<i18n-t keypath="journal.driver-stats.title">
|
||||
<template #name>
|
||||
<span class="text--primary">{{ store.driverStatsName.toUpperCase() }}</span>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</h3>
|
||||
|
||||
<hr class="header-separator" />
|
||||
|
||||
<div class="info-stats">
|
||||
<span class="stat-badge">
|
||||
<span>{{ $t('journal.stats-timetables') }}</span>
|
||||
<span>{{ $t('journal.driver-stats.timetables') }}</span>
|
||||
<span
|
||||
>{{ store.driverStatsData._count.fulfilled }} /
|
||||
{{ store.driverStatsData._count._all }}</span
|
||||
@@ -16,17 +21,17 @@
|
||||
</span>
|
||||
|
||||
<span class="stat-badge">
|
||||
<span>{{ $t('journal.stats-longest-timetable') }}</span>
|
||||
<span>{{ $t('journal.driver-stats.longest-timetable') }}</span>
|
||||
<span> {{ store.driverStatsData._max.routeDistance.toFixed(2) }}km </span>
|
||||
</span>
|
||||
|
||||
<span class="stat-badge">
|
||||
<span>{{ $t('journal.stats-avg-timetable') }}</span>
|
||||
<span>{{ $t('journal.driver-stats.avg-timetable') }}</span>
|
||||
<span> {{ store.driverStatsData._avg.routeDistance.toFixed(2) }}km </span>
|
||||
</span>
|
||||
|
||||
<span class="stat-badge">
|
||||
<span>{{ $t('journal.stats-distance') }}</span>
|
||||
<span>{{ $t('journal.driver-stats.distance') }}</span>
|
||||
<span>
|
||||
{{ store.driverStatsData._sum.currentDistance.toFixed(2) }} /
|
||||
{{ store.driverStatsData._sum.routeDistance.toFixed(2) }}km
|
||||
@@ -34,7 +39,7 @@
|
||||
</span>
|
||||
|
||||
<span class="stat-badge">
|
||||
<span>{{ $t('journal.stats-stations') }}</span>
|
||||
<span>{{ $t('journal.driver-stats.stations') }}</span>
|
||||
<span>
|
||||
{{ store.driverStatsData._sum.confirmedStopsCount }} /
|
||||
{{ store.driverStatsData._sum.allStopsCount }}
|
||||
@@ -42,21 +47,13 @@
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
<b v-else-if="store.driverStatsStatus == Status.Data.Loading">{{
|
||||
$t('journal.stats-loading')
|
||||
}}</b>
|
||||
<b v-else-if="store.driverStatsStatus == Status.Data.Error">
|
||||
{{ $t('journal.stats-error ') }}
|
||||
</b>
|
||||
<b v-else>{{ $t('journal.driver-stats-info') }}</b>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { useMainStore } from '../../store/mainStore';
|
||||
import { Status } from '../../typings/common';
|
||||
import { useMainStore } from '../../../store/mainStore';
|
||||
import { Status } from '../../../typings/common';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'journal-driver-stats',
|
||||
@@ -71,5 +68,5 @@ export default defineComponent({
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../styles/JournalStats.scss';
|
||||
@import '../../../styles/JournalStats.scss';
|
||||
</style>
|
||||
@@ -1,12 +1,5 @@
|
||||
export namespace Journal {
|
||||
export type DispatcherSearcher = {
|
||||
[key in 'search-dispatcher' | 'search-station' | 'search-date']: string;
|
||||
};
|
||||
|
||||
export interface DispatcherSorter {
|
||||
id: 'timestampFrom' | 'duration';
|
||||
dir: -1 | 1;
|
||||
}
|
||||
export type DispatcherSearchKey = 'search-dispatcher' | 'search-station' | 'search-date';
|
||||
|
||||
export type TimetableSearchKey =
|
||||
| 'search-driver'
|
||||
@@ -19,11 +12,29 @@ export namespace Journal {
|
||||
[key in TimetableSearchKey]: string;
|
||||
};
|
||||
|
||||
export type DispatcherSearchType = {
|
||||
[key in DispatcherSearchKey]: string;
|
||||
};
|
||||
|
||||
export type TimetableSorterKey = 'timetableId' | 'beginDate' | 'distance' | 'total-stops';
|
||||
export type DispatcherSorterKey = 'timestampFrom' | 'duration';
|
||||
|
||||
export interface DispatcherSorter {
|
||||
id: DispatcherSorterKey;
|
||||
dir: -1 | 1;
|
||||
}
|
||||
|
||||
export interface TimetableSorter {
|
||||
id: TimetableSorterKey;
|
||||
dir: 'asc' | 'desc';
|
||||
}
|
||||
|
||||
export const enum TimetableFilterId {
|
||||
ALL_STATUSES = 'all-statuses',
|
||||
ACTIVE = 'active',
|
||||
FULFILLED = 'fulfilled',
|
||||
ABANDONED = 'abandoned',
|
||||
ALL = 'all',
|
||||
ALL_SPECIALS = 'all-specials',
|
||||
TWR = 'twr',
|
||||
SKR = 'skr',
|
||||
TWR_SKR = 'twr-skr'
|
||||
@@ -31,19 +42,26 @@ export namespace Journal {
|
||||
|
||||
export enum FilterSection {
|
||||
TIMETABLE_STATUS = 'timetable-status',
|
||||
TWRSKR = 'twrskr'
|
||||
SPECIAL = 'special'
|
||||
}
|
||||
|
||||
export interface TimetableFilter {
|
||||
id: TimetableFilterId;
|
||||
filterSection: string;
|
||||
isActive: boolean;
|
||||
default: boolean;
|
||||
}
|
||||
|
||||
export type TimetableSorterKey = 'timetableId' | 'beginDate' | 'distance' | 'total-stops';
|
||||
export enum StatsTab {
|
||||
DRIVER_STATS = 'journal-driver-stats',
|
||||
DISPATCHER_STATS = 'journal-dispatcher-stats',
|
||||
DAILY_STATS = 'journal-daily-stats'
|
||||
}
|
||||
|
||||
export interface TimetableSorter {
|
||||
id: TimetableSorterKey;
|
||||
dir: 'asc' | 'desc';
|
||||
export interface StatsButton {
|
||||
tab: StatsTab;
|
||||
localeKey: string;
|
||||
iconName: string;
|
||||
disabled: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,9 @@
|
||||
<tr v-for="historyItem in historyList" :key="historyItem.id">
|
||||
<td>#{{ historyItem.stationHash }}</td>
|
||||
<td>
|
||||
<router-link :to="`/journal/dispatchers?dispatcherName=${historyItem.dispatcherName}`">
|
||||
<router-link
|
||||
:to="`/journal/dispatchers?search-dispatcher=${historyItem.dispatcherName}`"
|
||||
>
|
||||
<b>{{ historyItem.dispatcherName }}</b>
|
||||
</router-link>
|
||||
</td>
|
||||
@@ -74,7 +76,7 @@ import Station from '../../scripts/interfaces/Station';
|
||||
import Loading from '../Global/Loading.vue';
|
||||
import styleMixin from '../../mixins/styleMixin';
|
||||
import listObserverMixin from '../../mixins/listObserverMixin';
|
||||
import { OnlineScenery } from '../../store/typings';
|
||||
import { ActiveScenery } from '../../store/typings';
|
||||
import { API } from '../../typings/api';
|
||||
import { Status } from '../../typings/common';
|
||||
import http from '../../http';
|
||||
@@ -88,7 +90,7 @@ export default defineComponent({
|
||||
type: Object as PropType<Station>
|
||||
},
|
||||
onlineScenery: {
|
||||
type: Object as PropType<OnlineScenery>
|
||||
type: Object as PropType<ActiveScenery>
|
||||
}
|
||||
},
|
||||
|
||||
@@ -138,7 +140,7 @@ export default defineComponent({
|
||||
},
|
||||
navigateToHistory() {
|
||||
this.$router.push(
|
||||
`/journal/dispatchers?sceneryName=${this.station?.name || this.onlineScenery?.name}`
|
||||
`/journal/dispatchers?search-station=${this.station?.name || this.onlineScenery?.name}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent } from 'vue';
|
||||
import Station from '../../scripts/interfaces/Station';
|
||||
import { OnlineScenery } from '../../store/typings';
|
||||
import { ActiveScenery } from '../../store/typings';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
@@ -29,7 +29,7 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
onlineScenery: {
|
||||
type: Object as PropType<OnlineScenery>
|
||||
type: Object as PropType<ActiveScenery>
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -90,7 +90,7 @@ import SceneryInfoUserList from './SceneryInfo/SceneryInfoUserList.vue';
|
||||
import SceneryInfoSpawnList from './SceneryInfo/SceneryInfoSpawnList.vue';
|
||||
import SceneryInfoRoutes from './SceneryInfo/SceneryInfoRoutes.vue';
|
||||
import Station from '../../scripts/interfaces/Station';
|
||||
import { OnlineScenery } from '../../store/typings';
|
||||
import { ActiveScenery } from '../../store/typings';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@@ -106,7 +106,7 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
onlineScenery: {
|
||||
type: Object as PropType<OnlineScenery>
|
||||
type: Object as PropType<ActiveScenery>
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<section class="info-dispatcher">
|
||||
<div class="dispatcher" v-if="onlineScenery">
|
||||
<div class="dispatcher" v-if="onlineScenery && onlineScenery.dispatcherExp != -1">
|
||||
<span
|
||||
class="dispatcher_level"
|
||||
:style="calculateExpStyle(onlineScenery.dispatcherExp, onlineScenery.dispatcherIsSupporter)"
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
<router-link
|
||||
class="dispatcher_name"
|
||||
:to="`/journal/dispatchers?dispatcherName=${onlineScenery.dispatcherName}`"
|
||||
:to="`/journal/dispatchers?search-dispatcher=${onlineScenery.dispatcherName}`"
|
||||
>
|
||||
<span
|
||||
class="text--donator"
|
||||
@@ -42,14 +42,14 @@ import dateMixin from '../../../mixins/dateMixin';
|
||||
import routerMixin from '../../../mixins/routerMixin';
|
||||
import styleMixin from '../../../mixins/styleMixin';
|
||||
import StationStatusBadge from '../../Global/StationStatusBadge.vue';
|
||||
import { OnlineScenery } from '../../../store/typings';
|
||||
import { ActiveScenery } from '../../../store/typings';
|
||||
import donatorMixin from '../../../mixins/donatorMixin';
|
||||
|
||||
export default defineComponent({
|
||||
mixins: [styleMixin, dateMixin, routerMixin, donatorMixin],
|
||||
props: {
|
||||
onlineScenery: {
|
||||
type: Object as PropType<OnlineScenery>,
|
||||
type: Object as PropType<ActiveScenery>,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,41 +1,49 @@
|
||||
<template>
|
||||
<section class="info-routes" v-if="station.generalInfo">
|
||||
<div class="routes one-way" v-if="station.generalInfo.routes.oneWay.length > 0">
|
||||
<div class="routes one-way" v-if="filteredOneWayRoutes.length > 0">
|
||||
<b>{{ $t('scenery.one-way-routes') }}</b>
|
||||
|
||||
<ul class="routes-list">
|
||||
<li
|
||||
v-for="route in station.generalInfo.routes.oneWay"
|
||||
:key="route.name"
|
||||
@click="setActiveShowLength(route.name)"
|
||||
v-for="route in filteredOneWayRoutes"
|
||||
:key="route.routeName"
|
||||
@click="setActiveShowLength(route.routeName)"
|
||||
>
|
||||
<span :class="{ 'no-catenary': !route.catenary, internal: route.isInternal }">
|
||||
{{ route.name }}</span
|
||||
<span :class="{ 'no-catenary': !route.isElectric, internal: route.isInternal }">
|
||||
{{ route.routeName }}</span
|
||||
>
|
||||
<span v-if="route.speed" class="speed">
|
||||
{{ activeShowLength.includes(route.name) ? route.length + 'm' : route.speed }}
|
||||
<span v-if="route.routeSpeed" class="speed">
|
||||
{{
|
||||
activeShowLength.includes(route.routeName)
|
||||
? route.routeLength + 'm'
|
||||
: route.routeSpeed
|
||||
}}
|
||||
</span>
|
||||
<span v-if="route.SBL" class="sbl">SBL</span>
|
||||
<span v-if="route.isRouteSBL" class="sbl">SBL</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="routes two-way" v-if="station.generalInfo.routes.twoWay.length > 0">
|
||||
<div class="routes two-way" v-if="filteredTwoWayRoutes.length > 0">
|
||||
<b>{{ $t('scenery.two-way-routes') }}</b>
|
||||
|
||||
<ul class="routes-list">
|
||||
<li
|
||||
v-for="route in station.generalInfo.routes.twoWay"
|
||||
:key="route.name"
|
||||
@click="setActiveShowLength(route.name)"
|
||||
v-for="route in filteredTwoWayRoutes"
|
||||
:key="route.routeName"
|
||||
@click="setActiveShowLength(route.routeName)"
|
||||
>
|
||||
<span :class="{ 'no-catenary': !route.catenary, internal: route.isInternal }">{{
|
||||
route.name
|
||||
<span :class="{ 'no-catenary': !route.isElectric, internal: route.isInternal }">{{
|
||||
route.routeName
|
||||
}}</span>
|
||||
<span v-if="route.speed" class="speed">
|
||||
{{ activeShowLength.includes(route.name) ? route.length + 'm' : route.speed }}
|
||||
<span v-if="route.routeSpeed" class="speed">
|
||||
{{
|
||||
activeShowLength.includes(route.routeName)
|
||||
? route.routeLength + 'm'
|
||||
: route.routeSpeed
|
||||
}}
|
||||
</span>
|
||||
<span v-if="route.SBL" class="sbl">SBL</span>
|
||||
<span v-if="route.isRouteSBL" class="sbl">SBL</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -45,6 +53,9 @@
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent } from 'vue';
|
||||
import Station from '../../../scripts/interfaces/Station';
|
||||
import { StationRoutesInfo } from '../../../store/typings';
|
||||
|
||||
const routeFilter = (route: StationRoutesInfo) => !route.hidden;
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
@@ -66,6 +77,16 @@ export default defineComponent({
|
||||
return {
|
||||
activeShowLength: [] as string[]
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
filteredOneWayRoutes() {
|
||||
return this.station.generalInfo?.routes.oneWay.filter(routeFilter) || [];
|
||||
},
|
||||
|
||||
filteredTwoWayRoutes() {
|
||||
return this.station.generalInfo?.routes.twoWay.filter(routeFilter) || [];
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -30,12 +30,12 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent } from 'vue';
|
||||
import { OnlineScenery } from '../../../store/typings';
|
||||
import { ActiveScenery } from '../../../store/typings';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
onlineScenery: {
|
||||
type: Object as PropType<OnlineScenery>,
|
||||
type: Object as PropType<ActiveScenery>,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
|
||||
@@ -32,14 +32,14 @@
|
||||
import { PropType, defineComponent } from 'vue';
|
||||
import modalTrainMixin from '../../../mixins/modalTrainMixin';
|
||||
import routerMixin from '../../../mixins/routerMixin';
|
||||
import { OnlineScenery } from '../../../store/typings';
|
||||
import { ActiveScenery } from '../../../store/typings';
|
||||
|
||||
export default defineComponent({
|
||||
mixins: [routerMixin, modalTrainMixin],
|
||||
|
||||
props: {
|
||||
onlineScenery: {
|
||||
type: Object as PropType<OnlineScenery>,
|
||||
type: Object as PropType<ActiveScenery>,
|
||||
required: false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,21 +6,21 @@
|
||||
<span>{{ $t('scenery.timetables') }}</span>
|
||||
|
||||
<span>
|
||||
<span class="text--primary">{{ onlineScenery?.scheduledTrainCount.all || 0 }}</span>
|
||||
<span class="text--primary">{{ onlineScenery?.scheduledTrainCount.all ?? 0 }}</span>
|
||||
<span> / </span>
|
||||
<span class="text--grayed">
|
||||
{{ onlineScenery?.scheduledTrainCount.confirmed || '0' }}
|
||||
{{ onlineScenery?.scheduledTrainCount.confirmed ?? 0 }}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span class="header_links" v-if="station">
|
||||
<a
|
||||
<!-- <a
|
||||
:href="`https://pragotron-td2.web.app/board?name=${station.name}`"
|
||||
target="_blank"
|
||||
:title="$t('scenery.pragotron-link')"
|
||||
>
|
||||
<img src="/images/icon-pragotron.svg" alt="icon-pragotron" />
|
||||
</a>
|
||||
</a> -->
|
||||
|
||||
<a :href="tabliceZbiorczeHref" target="_blank" :title="$t('scenery.tablice-link')">
|
||||
<img src="/images/icon-tablice.ico" alt="icon-tablice" />
|
||||
@@ -33,12 +33,12 @@
|
||||
{{ (i > 0 && '•') || '' }}
|
||||
|
||||
<button
|
||||
:key="cp.checkpointName"
|
||||
:key="cp"
|
||||
class="checkpoint_item"
|
||||
:class="{ current: chosenCheckpoint === cp.checkpointName }"
|
||||
:class="{ current: chosenCheckpoint === cp }"
|
||||
@click="setCheckpoint(cp)"
|
||||
>
|
||||
{{ cp.checkpointName }}
|
||||
{{ cp }}
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
@@ -48,7 +48,7 @@
|
||||
<transition-group name="list-anim">
|
||||
<div
|
||||
style="padding-bottom: 5em"
|
||||
v-if="apiStore.dataStatuses.trains == 0 && computedScheduledTrains.length == 0"
|
||||
v-if="apiStore.dataStatuses.connection == 0 && computedScheduledTrains.length == 0"
|
||||
key="list-loading"
|
||||
>
|
||||
<Loading />
|
||||
@@ -190,7 +190,7 @@ import Station from '../../scripts/interfaces/Station';
|
||||
import { useMainStore } from '../../store/mainStore';
|
||||
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
||||
import ScheduledTrainStatus from './ScheduledTrainStatus.vue';
|
||||
import { OnlineScenery } from '../../store/typings';
|
||||
import { ActiveScenery } from '../../store/typings';
|
||||
import { useApiStore } from '../../store/apiStore';
|
||||
|
||||
export default defineComponent({
|
||||
@@ -205,7 +205,7 @@ export default defineComponent({
|
||||
type: Object as PropType<Station>
|
||||
},
|
||||
onlineScenery: {
|
||||
type: Object as PropType<OnlineScenery>
|
||||
type: Object as PropType<ActiveScenery>
|
||||
}
|
||||
},
|
||||
|
||||
@@ -231,7 +231,7 @@ export default defineComponent({
|
||||
const chosenCheckpoint = ref(
|
||||
props.station?.generalInfo?.checkpoints?.length == 0
|
||||
? ''
|
||||
: props.station?.generalInfo?.checkpoints[0].checkpointName || null
|
||||
: props.station?.generalInfo?.checkpoints[0] ?? null
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -278,12 +278,11 @@ export default defineComponent({
|
||||
loadSelectedOption() {
|
||||
if (!this.station) return;
|
||||
|
||||
this.chosenCheckpoint =
|
||||
this.station.generalInfo?.checkpoints[0]?.checkpointName || this.station.name;
|
||||
this.chosenCheckpoint = this.station.generalInfo?.checkpoints[0] ?? this.station.name;
|
||||
},
|
||||
|
||||
setCheckpoint(cp: { checkpointName: string }) {
|
||||
this.chosenCheckpoint = cp.checkpointName;
|
||||
setCheckpoint(cp: string) {
|
||||
this.chosenCheckpoint = cp;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<tbody>
|
||||
<tr v-for="historyItem in historyList" :key="historyItem.id">
|
||||
<td>
|
||||
<router-link :to="`/journal/timetables?timetableId=${historyItem.id}`">
|
||||
<router-link :to="`/journal/timetables?search-train=%23${historyItem.id}`">
|
||||
#{{ historyItem.id }}
|
||||
</router-link>
|
||||
</td>
|
||||
@@ -37,11 +37,16 @@
|
||||
{{ historyItem.trainNo }}
|
||||
</td>
|
||||
<td>{{ historyItem.route.replace('|', ' -> ') }}</td>
|
||||
<td>{{ historyItem.driverName }}</td>
|
||||
<td>
|
||||
<router-link :to="`/journal/timetables?search-driver=${historyItem.driverName}`">
|
||||
{{ historyItem.driverName }}
|
||||
</router-link>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<router-link
|
||||
v-if="historyItem.authorName"
|
||||
:to="`/journal/timetables?authorName=${historyItem.authorName}`"
|
||||
:to="`/journal/timetables?search-dispatcher=${historyItem.authorName}`"
|
||||
>{{ historyItem.authorName }}
|
||||
</router-link>
|
||||
<i v-else>{{ $t('scenery.timetable-author-unknown') }}</i>
|
||||
@@ -69,7 +74,7 @@ import dateMixin from '../../mixins/dateMixin';
|
||||
import Station from '../../scripts/interfaces/Station';
|
||||
import Loading from '../Global/Loading.vue';
|
||||
import listObserverMixin from '../../mixins/listObserverMixin';
|
||||
import { OnlineScenery } from '../../store/typings';
|
||||
import { ActiveScenery } from '../../store/typings';
|
||||
import { API } from '../../typings/api';
|
||||
import { Status } from '../../typings/common';
|
||||
import http from '../../http';
|
||||
@@ -82,7 +87,7 @@ export default defineComponent({
|
||||
type: Object as PropType<Station>
|
||||
},
|
||||
onlineScenery: {
|
||||
type: Object as PropType<OnlineScenery>
|
||||
type: Object as PropType<ActiveScenery>
|
||||
}
|
||||
},
|
||||
|
||||
@@ -99,18 +104,20 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
methods: {
|
||||
async fetchAPIData(countFrom = 0, countLimit = 15) {
|
||||
async fetchAPIData() {
|
||||
if (!this.station && !this.onlineScenery) {
|
||||
this.dataStatus = Status.Data.Loaded;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const requestString = `api/getTimetables?issuedFrom=${
|
||||
this.station?.name || this.onlineScenery?.name
|
||||
}&countFrom=${countFrom}&countLimit=${countLimit}`;
|
||||
|
||||
const response: API.TimetableHistory.Response = await (await http.get(requestString)).data;
|
||||
const response: API.TimetableHistory.Response = await (
|
||||
await http.get('api/getTimetables', {
|
||||
params: {
|
||||
issuedFrom: this.station?.name || this.onlineScenery?.name
|
||||
}
|
||||
})
|
||||
).data;
|
||||
|
||||
this.historyList = response;
|
||||
|
||||
@@ -121,9 +128,12 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
navigateToHistory() {
|
||||
this.$router.push(
|
||||
`/journal/timetables?issuedFrom=${this.station?.name || this.onlineScenery?.name}`
|
||||
);
|
||||
this.$router.push({
|
||||
path: '/journal/timetables',
|
||||
query: {
|
||||
'search-issuedFrom': this.station?.name || this.onlineScenery?.name
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
components: { Loading }
|
||||
|
||||
@@ -60,8 +60,9 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card_timestamp" style="text-align: center">
|
||||
<div>{{ $t('filters.minimum-hours-title') }}</div>
|
||||
<section class="card_timestamp">
|
||||
<h3 class="section-header">{{ $t('filters.minimum-hours-title') }}</h3>
|
||||
|
||||
<span class="clock">
|
||||
<button class="btn--action" @click="subHour">-</button>
|
||||
<span>{{
|
||||
@@ -75,16 +76,27 @@
|
||||
</span>
|
||||
</section>
|
||||
|
||||
<datalist id="authors">
|
||||
<option v-for="(author, i) in authors" :key="i" :value="author"></option>
|
||||
</datalist>
|
||||
|
||||
<section class="card_authors-search">
|
||||
<input
|
||||
type="text"
|
||||
:placeholder="$t('filters.authors-search')"
|
||||
name="authors"
|
||||
v-model="authorsInputValue"
|
||||
@input="handleAuthorsInput"
|
||||
@focus="preventKeyDown = true"
|
||||
@blur="preventKeyDown = false"
|
||||
/>
|
||||
<h3 class="section-header">{{ $t('filters.authors-search') }}</h3>
|
||||
|
||||
<form action="javascript:void(0);" @submit="handleAuthorsInput">
|
||||
<input
|
||||
type="text"
|
||||
id="author"
|
||||
list="authors"
|
||||
name="authors"
|
||||
:placeholder="$t('filters.authors-placeholder')"
|
||||
v-model="authorsInputValue"
|
||||
@focus="preventKeyDown = true"
|
||||
@blur="preventKeyDown = false"
|
||||
/>
|
||||
|
||||
<button class="btn--action">{{ $t('filters.authors-button-title') }}</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="card_sliders">
|
||||
@@ -196,6 +208,19 @@ export default defineComponent({
|
||||
|
||||
currentOptionsActive() {
|
||||
return true;
|
||||
},
|
||||
|
||||
authors() {
|
||||
return this.store.stationList
|
||||
.reduce((acc, station) => {
|
||||
station.generalInfo?.authors?.forEach((author) => {
|
||||
if (author.trim() != '' && !acc.includes(author.toLocaleLowerCase()))
|
||||
acc.push(author.toLocaleLowerCase());
|
||||
});
|
||||
|
||||
return acc;
|
||||
}, [] as string[])
|
||||
.sort((a, b) => a.localeCompare(b));
|
||||
}
|
||||
},
|
||||
|
||||
@@ -230,12 +255,12 @@ export default defineComponent({
|
||||
if (this.saveOptions) StorageManager.setStringValue(target.name, target.value);
|
||||
},
|
||||
|
||||
handleAuthorsInput(e: Event) {
|
||||
clearTimeout(this.delayInputTimer);
|
||||
handleAuthorsInput() {
|
||||
console.log(this.authorsInputValue);
|
||||
|
||||
this.delayInputTimer = window.setTimeout(() => {
|
||||
this.handleInput(e);
|
||||
}, 400);
|
||||
this.filterStore.changeFilterValue('authors', this.authorsInputValue);
|
||||
|
||||
if (this.saveOptions) StorageManager.setStringValue('authors', this.authorsInputValue);
|
||||
},
|
||||
|
||||
changeNumericFilterValue(name: string, value: number, saveToStorage = false) {
|
||||
@@ -297,136 +322,139 @@ export default defineComponent({
|
||||
@import '../../styles/card.scss';
|
||||
@import '../../styles/animations.scss';
|
||||
|
||||
h3.section-header {
|
||||
text-align: center;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
.card {
|
||||
display: grid;
|
||||
grid-template-rows: 1fr auto;
|
||||
}
|
||||
|
||||
&_info {
|
||||
background-color: #111;
|
||||
padding: 0.5em;
|
||||
.card_info {
|
||||
background-color: #111;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.card_controls {
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
|
||||
input {
|
||||
border-radius: 0.5em 0.5em 0 0;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.card_content {
|
||||
padding: 1em 0.5em;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
gap: 1em;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.card_title {
|
||||
font-size: 2em;
|
||||
font-weight: 700;
|
||||
color: $accentCol;
|
||||
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.card_regions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
label > input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&_controls {
|
||||
label > span {
|
||||
padding: 0.25em 0.5em;
|
||||
margin: 0 0.25em;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
background-color: gray;
|
||||
|
||||
&.checked {
|
||||
background-color: seagreen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card_timestamp {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
.clock {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
font-size: 1.2em;
|
||||
text-align: center;
|
||||
|
||||
span {
|
||||
min-width: 120px;
|
||||
font-weight: bold;
|
||||
color: $accentCol;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0.2em 0.6em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card_authors-search {
|
||||
margin: 1em 0;
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5em;
|
||||
width: 100%;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 70%;
|
||||
max-width: 400px;
|
||||
padding: 0.5em;
|
||||
outline: 1px solid white;
|
||||
}
|
||||
}
|
||||
|
||||
.card_actions {
|
||||
width: 100%;
|
||||
padding: 0.5em;
|
||||
|
||||
.filter-option {
|
||||
max-width: 50%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
|
||||
input {
|
||||
border-radius: 0.5em 0.5em 0 0;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&_content {
|
||||
padding: 1em 0.5em;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
gap: 1em;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
&_title {
|
||||
font-size: 2em;
|
||||
font-weight: 700;
|
||||
color: $accentCol;
|
||||
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&_regions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
label > input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
label > span {
|
||||
padding: 0.25em 0.5em;
|
||||
margin: 0 0.25em;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
background-color: gray;
|
||||
|
||||
&.checked {
|
||||
background-color: seagreen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&_timestamp {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
.clock {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
font-size: 1.2em;
|
||||
margin-top: 0.5em;
|
||||
|
||||
span {
|
||||
min-width: 120px;
|
||||
font-weight: bold;
|
||||
color: $accentCol;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0.2em 0.6em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&_modes {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
.option {
|
||||
margin: 0 1em;
|
||||
}
|
||||
}
|
||||
|
||||
&_authors-search {
|
||||
display: inline-block;
|
||||
margin: 0 auto;
|
||||
width: 60%;
|
||||
min-width: 240px;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 0.5em;
|
||||
border: 1px solid white;
|
||||
}
|
||||
}
|
||||
|
||||
&_actions {
|
||||
width: 100%;
|
||||
padding: 0.5em;
|
||||
|
||||
.filter-option {
|
||||
max-width: 50%;
|
||||
margin-top: 0.5em;
|
||||
|
||||
button {
|
||||
width: 50%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
padding: 0.5em;
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
width: 100%;
|
||||
|
||||
margin-top: 0.5em;
|
||||
|
||||
button {
|
||||
width: 50%;
|
||||
margin: 0 auto;
|
||||
padding: 0.5em;
|
||||
|
||||
&[data-selected='true'] {
|
||||
background-color: forestgreen;
|
||||
}
|
||||
&[data-selected='true'] {
|
||||
background-color: forestgreen;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -447,7 +475,7 @@ export default defineComponent({
|
||||
|
||||
.section-inputs {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 0.5em;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@
|
||||
|
||||
<td class="station_dispatcher-exp">
|
||||
<span
|
||||
v-if="station.onlineInfo"
|
||||
v-if="station.onlineInfo && station.onlineInfo?.dispatcherExp != -1"
|
||||
:style="
|
||||
calculateExpStyle(
|
||||
station.onlineInfo.dispatcherExp,
|
||||
@@ -279,7 +279,7 @@
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<Loading v-if="!isDataLoaded && stations.length == 0" />
|
||||
<Loading v-if="apiStore.dataStatuses.connection == Status.Loading" />
|
||||
|
||||
<div class="no-stations" v-else-if="stations.length == 0">
|
||||
{{ $t('sceneries.no-stations') }}
|
||||
@@ -288,7 +288,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, PropType } from 'vue';
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
import dateMixin from '../../mixins/dateMixin';
|
||||
import stationInfoMixin from '../../mixins/stationInfoMixin';
|
||||
import styleMixin from '../../mixins/styleMixin';
|
||||
@@ -330,12 +330,8 @@ export default defineComponent({
|
||||
const apiStore = useApiStore();
|
||||
const stationFiltersStore = useStationFiltersStore();
|
||||
|
||||
const isDataLoaded = computed(() => {
|
||||
return apiStore.dataStatuses.sceneries != Status.Data.Loading;
|
||||
});
|
||||
|
||||
return {
|
||||
isDataLoaded,
|
||||
Status: Status.Data,
|
||||
stationFiltersStore,
|
||||
mainStore,
|
||||
apiStore
|
||||
|
||||
@@ -42,14 +42,13 @@ export interface Filter {
|
||||
nonPublic: boolean;
|
||||
unavailable: boolean;
|
||||
abandoned: boolean;
|
||||
|
||||
endingStatus: boolean;
|
||||
afkStatus: boolean;
|
||||
noSpaceStatus: boolean;
|
||||
unavailableStatus: boolean;
|
||||
unsignedStatus: boolean;
|
||||
|
||||
authors: string;
|
||||
|
||||
onlineFromHours: number;
|
||||
withActiveTimetables: boolean;
|
||||
withoutActiveTimetables: boolean;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
<template>
|
||||
<span class="stop-label" :data-sbl="stop.isSBL">
|
||||
<span class="name" v-html="stop.nameHtml"></span>
|
||||
|
||||
<span
|
||||
v-if="stop.position != 'begin'"
|
||||
class="date arrival"
|
||||
:data-status="
|
||||
stop.arrivalDelay > 0 && stop.status != 'unconfirmed'
|
||||
? 'delayed'
|
||||
: stop.arrivalDelay < 0 && stop.status != 'unconfirmed'
|
||||
? 'preponed'
|
||||
: stop.arrivalDelay == 0 && stop.status == 'confirmed'
|
||||
? 'on-time'
|
||||
: ''
|
||||
"
|
||||
>
|
||||
<span v-if="stop.arrivalDelay != 0 && stop.status != 'unconfirmed'">
|
||||
<s>{{ timestampToString(stop.arrivalScheduled) }}</s>
|
||||
{{ timestampToString(stop.arrivalReal) }}
|
||||
({{ stop.arrivalDelay > 0 ? '+' : '' }}{{ stop.arrivalDelay }})
|
||||
</span>
|
||||
|
||||
<span v-else>
|
||||
{{ timestampToString(stop.arrivalScheduled) }}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span
|
||||
v-if="
|
||||
stop.duration ||
|
||||
(stop.status == 'stopped' &&
|
||||
stop.position != 'begin' &&
|
||||
stop.departureDelay != stop.arrivalDelay)
|
||||
"
|
||||
class="date stop"
|
||||
:data-stop-types="stop.type.replace(', ', '-')"
|
||||
:data-stop-status="
|
||||
stop.departureDelay - stop.arrivalDelay > 0 && !stop.duration ? 'delayed' : ''
|
||||
"
|
||||
>
|
||||
{{ stop.duration || stop.departureDelay - stop.arrivalDelay }}
|
||||
{{ stop.type == '' ? 'pt' : stop.type }}
|
||||
</span>
|
||||
|
||||
<span
|
||||
v-if="
|
||||
stop.position != 'end' &&
|
||||
(stop.duration != 0 || stop.status == 'stopped' || stop.departureDelay != stop.arrivalDelay)
|
||||
"
|
||||
class="date departure"
|
||||
:data-status="
|
||||
stop.departureDelay > 0 && stop.status == 'confirmed'
|
||||
? 'delayed'
|
||||
: stop.departureDelay < 0 && stop.status == 'confirmed'
|
||||
? 'preponed'
|
||||
: stop.departureDelay == 0 && stop.status == 'confirmed'
|
||||
? 'on-time'
|
||||
: ''
|
||||
"
|
||||
>
|
||||
<span v-if="stop.departureDelay != 0 && stop.status == 'confirmed'">
|
||||
<s>{{ timestampToString(stop.departureScheduled) }}</s>
|
||||
{{ timestampToString(stop.departureReal) }}
|
||||
|
||||
({{ stop.departureDelay > 0 ? '+' : '' }}{{ stop.departureDelay }})
|
||||
</span>
|
||||
|
||||
<span v-else>
|
||||
{{ timestampToString(stop.departureScheduled) }}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent } from 'vue';
|
||||
import dateMixin from '../../mixins/dateMixin';
|
||||
import { TrainScheduleStop } from './TrainSchedule.vue';
|
||||
|
||||
export default defineComponent({
|
||||
mixins: [dateMixin],
|
||||
|
||||
props: {
|
||||
stop: {
|
||||
type: Object as PropType<TrainScheduleStop>,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$preponedClr: lime;
|
||||
$delayedClr: salmon;
|
||||
$dateClr: #525151;
|
||||
$stopExchangeClr: #db8e29;
|
||||
$stopDefaultClr: #252525;
|
||||
$stopNameClr: #22a8d1;
|
||||
|
||||
.stop-label {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
|
||||
&[data-sbl='true'] {
|
||||
.date {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.name {
|
||||
background: none;
|
||||
color: #aaa;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.name {
|
||||
background: $stopNameClr;
|
||||
padding: 0.3em 0.5em;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&.misc {
|
||||
background: gray;
|
||||
}
|
||||
}
|
||||
|
||||
.date {
|
||||
background: $dateClr;
|
||||
padding: 0.3em 0.5em;
|
||||
}
|
||||
|
||||
.stop {
|
||||
&[data-stop-types='ph'],
|
||||
&[data-stop-types='ph-pm'],
|
||||
&[data-stop-types='pm'] {
|
||||
background: $stopExchangeClr;
|
||||
}
|
||||
|
||||
background: $stopDefaultClr;
|
||||
|
||||
&[data-stop-status='delayed'] {
|
||||
color: $delayedClr;
|
||||
}
|
||||
}
|
||||
|
||||
.arrival,
|
||||
.departure {
|
||||
&[data-status='delayed'] {
|
||||
s {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
span {
|
||||
color: $delayedClr;
|
||||
}
|
||||
}
|
||||
|
||||
&[data-status='preponed'] {
|
||||
s {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
span {
|
||||
color: $preponedClr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -15,19 +15,12 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
||||
import trainInfoMixin from '../../mixins/trainInfoMixin';
|
||||
import TrainInfo from '../TrainsView/TrainInfo.vue';
|
||||
import TrainSchedule from '../TrainsView/TrainSchedule.vue';
|
||||
import TrainInfo from './TrainInfo.vue';
|
||||
import TrainSchedule from './TrainSchedule.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: { TrainInfo, TrainSchedule },
|
||||
mixins: [trainInfoMixin, modalTrainMixin],
|
||||
|
||||
data() {
|
||||
return {
|
||||
isTopBarVisible: false
|
||||
};
|
||||
},
|
||||
mixins: [modalTrainMixin],
|
||||
|
||||
activated() {
|
||||
const contentEl = this.$refs['content'] as HTMLElement;
|
||||
@@ -2,83 +2,126 @@
|
||||
<div class="train-schedule" @click="toggleShowState">
|
||||
<StockList :trainStockList="train.stockList" />
|
||||
|
||||
<!-- <div class="train-stock"> -->
|
||||
<!-- <ul>
|
||||
<li v-for="(stockName, i) in train.stockList" :key="i">
|
||||
<p>{{ stockName.split(':')[0].split('_').splice(0, 2).join(' ') }} {{ stockName.split(':')[1] }}</p>
|
||||
<TrainThumbnail :name="stockName" />
|
||||
</li>
|
||||
</ul> -->
|
||||
<!-- </div> -->
|
||||
|
||||
<div class="schedule-wrapper" v-if="train.timetableData">
|
||||
<ul class="stop_list">
|
||||
<li
|
||||
v-for="(stop, i) in train.timetableData.followingStops"
|
||||
<div class="stops">
|
||||
<div
|
||||
v-for="(stop, i) in scheduleStops"
|
||||
:key="i"
|
||||
class="stop"
|
||||
:class="addClasses(stop, i)"
|
||||
:data-status="stop.status"
|
||||
:data-position="stop.position"
|
||||
:data-delayed="stop.departureDelay > 0"
|
||||
:data-stop-type="stop.type"
|
||||
:data-minor-stop-active="stop.isActive"
|
||||
:data-last-confirmed="stop.isLastConfirmed"
|
||||
x
|
||||
>
|
||||
<span class="stop_info">
|
||||
<div class="indicator"></div>
|
||||
|
||||
<div class="progress-bar"></div>
|
||||
|
||||
<div class="stop-bar"></div>
|
||||
|
||||
<span class="distance" v-if="stop.stopDistance">
|
||||
{{ Math.floor(stop.stopDistance) }}
|
||||
<span class="distance">
|
||||
{{ stop.distance ? stop.distance.toFixed(1) : '' }}
|
||||
</span>
|
||||
|
||||
<span class="stop-name" v-html="stop.stopName"> </span>
|
||||
<div class="progress">
|
||||
<div class="line line_node line_node-top"></div>
|
||||
<div class="node"></div>
|
||||
<div class="line line_node line_node-bottom"></div>
|
||||
</div>
|
||||
|
||||
<StopDate :stop="stop" />
|
||||
<StopLabel :stop="stop" />
|
||||
</span>
|
||||
|
||||
<div class="stop_line" v-if="i < train.timetableData!.followingStops.length - 1">
|
||||
<div class="progress-bar"></div>
|
||||
<div class="stop_line">
|
||||
<!-- Grid placeholder -->
|
||||
<div></div>
|
||||
|
||||
<div v-if="stop.comments" style="color: salmon">
|
||||
<b>{{ stop.stopNameRAW }} </b>: <span v-html="stop.comments"></span>
|
||||
<div class="progress">
|
||||
<div class="line line_connection" v-if="i < scheduleStops.length - 1"></div>
|
||||
</div>
|
||||
|
||||
<span
|
||||
v-if="
|
||||
stop.departureLine == train.timetableData!.followingStops[i + 1].arrivalLine &&
|
||||
!/sbl/gi.test(stop.departureLine!)
|
||||
"
|
||||
>
|
||||
{{ stop.departureLine }}
|
||||
</span>
|
||||
<div class="bottom-line-info">
|
||||
<div class="info-comments" v-if="stop.comments" style="color: salmon">
|
||||
<img src="/images/icon-warning.svg" alt="icon-warning" width="20" />
|
||||
<b v-html="stop.comments"></b>
|
||||
</div>
|
||||
|
||||
<span v-else-if="!/sbl/gi.test(stop.departureLine!)">
|
||||
{{ stop.departureLine }} /
|
||||
{{ train.timetableData!.followingStops[i + 1].arrivalLine }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Routes -->
|
||||
<span
|
||||
v-if="
|
||||
stop.departureLine &&
|
||||
stop.departureLine == scheduleStops[i + 1]?.arrivalLine &&
|
||||
!/sbl/gi.test(stop.departureLine)
|
||||
"
|
||||
>
|
||||
{{ stop.departureLine }}
|
||||
</span>
|
||||
|
||||
<div class="stop_line" v-else>
|
||||
<div v-if="stop.comments" style="color: salmon">
|
||||
<b>{{ stop.stopNameRAW }} </b>: <span v-html="stop.comments"></span>
|
||||
<span v-else-if="stop.departureLine && !/sbl/gi.test(stop.departureLine)">
|
||||
<div>{{ stop.departureLine }}</div>
|
||||
<div
|
||||
class="scenery-change-name"
|
||||
v-if="
|
||||
i < scheduleStops.length - 1 &&
|
||||
stop.sceneryName != scheduleStops[i + 1].sceneryName
|
||||
"
|
||||
>
|
||||
{{ scheduleStops[i + 1].sceneryName }}
|
||||
</div>
|
||||
<div>
|
||||
{{ scheduleStops[i + 1].arrivalLine }}
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, PropType } from 'vue';
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
import dateMixin from '../../mixins/dateMixin';
|
||||
import Train from '../../scripts/interfaces/Train';
|
||||
import { useMainStore } from '../../store/mainStore';
|
||||
import StopDate from '../Global/StopDate.vue';
|
||||
import StopLabel from './StopLabel.vue';
|
||||
import StockList from '../Global/StockList.vue';
|
||||
import { TrainStop } from '../../store/typings';
|
||||
import { useMainStore } from '../../store/mainStore';
|
||||
import { useApiStore } from '../../store/apiStore';
|
||||
|
||||
export interface TrainScheduleStop {
|
||||
nameHtml: string;
|
||||
nameRaw: string;
|
||||
|
||||
status: 'confirmed' | 'unconfirmed' | 'stopped';
|
||||
type: string;
|
||||
position: 'begin' | 'end' | 'en-route';
|
||||
|
||||
arrivalScheduled: number;
|
||||
arrivalReal: number;
|
||||
|
||||
departureScheduled: number;
|
||||
departureReal: number;
|
||||
|
||||
departureDelay: number;
|
||||
arrivalDelay: number;
|
||||
|
||||
duration: number | null;
|
||||
|
||||
isActive: boolean;
|
||||
isLastConfirmed: boolean;
|
||||
isSBL: boolean;
|
||||
|
||||
sceneryName: string | null;
|
||||
sceneryHash: string;
|
||||
distance: number;
|
||||
|
||||
arrivalLine: string | null;
|
||||
departureLine: string | null;
|
||||
|
||||
comments: string | null;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
components: { StopDate, StockList },
|
||||
components: { StopLabel, StockList },
|
||||
props: {
|
||||
train: {
|
||||
type: Object as PropType<Train>,
|
||||
@@ -90,62 +133,95 @@ export default defineComponent({
|
||||
|
||||
emits: ['click'],
|
||||
|
||||
setup(props) {
|
||||
data() {
|
||||
return {
|
||||
store: useMainStore(),
|
||||
|
||||
lastConfirmed: computed(() => {
|
||||
return props.train.timetableData!.followingStops.findIndex(
|
||||
(stop, i, stops) => stop.confirmed && !stops[i + 1]?.confirmed && !stops[i + 1]?.stopped
|
||||
);
|
||||
}),
|
||||
activeMinorStops: computed(() => {
|
||||
const lastMajorConfirmed = props.train.timetableData!.followingStops.findIndex(
|
||||
(stop, i, stops) => stop.confirmed && !stops[i + 1]?.confirmed
|
||||
);
|
||||
|
||||
const activeMinorStopList: number[] = [];
|
||||
if (lastMajorConfirmed + 1 >= props.train.timetableData!.followingStops.length)
|
||||
return activeMinorStopList;
|
||||
|
||||
for (
|
||||
let i = lastMajorConfirmed + 1;
|
||||
i < props.train.timetableData!.followingStops.length;
|
||||
i++
|
||||
) {
|
||||
if (/po\.|sbl/gi.test(props.train.timetableData!.followingStops[i].stopNameRAW))
|
||||
activeMinorStopList.push(i);
|
||||
else break;
|
||||
}
|
||||
|
||||
return activeMinorStopList;
|
||||
})
|
||||
apiStore: useApiStore()
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
scheduleStops(): TrainScheduleStop[] {
|
||||
let currentSceneryIndex = 0;
|
||||
|
||||
return (
|
||||
this.train.timetableData?.followingStops.map((stop, i, arr) => {
|
||||
if (
|
||||
i > 0 &&
|
||||
stop.arrivalLine &&
|
||||
stop.arrivalLine != arr[i - 1].departureLine &&
|
||||
!/sbl/gi.test(stop.arrivalLine)
|
||||
)
|
||||
currentSceneryIndex++;
|
||||
|
||||
return {
|
||||
nameHtml: stop.stopName,
|
||||
nameRaw: stop.stopNameRAW,
|
||||
|
||||
arrivalScheduled: stop.arrivalTimestamp,
|
||||
arrivalReal: stop.arrivalRealTimestamp,
|
||||
|
||||
departureScheduled: stop.departureTimestamp,
|
||||
departureReal: stop.departureRealTimestamp,
|
||||
|
||||
departureDelay: stop.departureDelay,
|
||||
arrivalDelay: stop.arrivalDelay,
|
||||
|
||||
duration: stop.stopTime,
|
||||
|
||||
comments: stop.comments ?? null,
|
||||
|
||||
arrivalLine: stop.arrivalLine,
|
||||
departureLine: stop.departureLine,
|
||||
|
||||
type: stop.stopType,
|
||||
distance: stop.stopDistance,
|
||||
isActive: this.activeMinorStops.includes(i),
|
||||
isLastConfirmed: this.lastConfirmed === i && !stop.terminatesHere,
|
||||
isSBL: /sbl/gi.test(stop.stopName),
|
||||
position: stop.beginsHere ? 'begin' : stop.terminatesHere ? 'end' : 'en-route',
|
||||
sceneryHash: '',
|
||||
sceneryName: this.train.timetableData!.sceneryNames[currentSceneryIndex],
|
||||
status: stop.confirmed ? 'confirmed' : stop.stopped ? 'stopped' : 'unconfirmed'
|
||||
};
|
||||
}) ?? []
|
||||
);
|
||||
},
|
||||
|
||||
lastConfirmed() {
|
||||
return this.train.timetableData?.followingStops.findIndex(
|
||||
(stop, i, stops) => stop.confirmed && !stops[i + 1]?.confirmed && !stops[i + 1]?.stopped
|
||||
);
|
||||
},
|
||||
|
||||
activeMinorStops() {
|
||||
if (!this.train.timetableData) return [];
|
||||
|
||||
const lastMajorConfirmed = this.train.timetableData.followingStops.findIndex(
|
||||
(stop, i, stops) => stop.confirmed && !stops[i + 1]?.confirmed
|
||||
);
|
||||
|
||||
const activeMinorStopList: number[] = [];
|
||||
if (lastMajorConfirmed + 1 >= this.train.timetableData.followingStops.length)
|
||||
return activeMinorStopList;
|
||||
|
||||
for (
|
||||
let i = lastMajorConfirmed + 1;
|
||||
i < this.train.timetableData!.followingStops.length;
|
||||
i++
|
||||
) {
|
||||
if (/po\.|sbl/gi.test(this.train.timetableData!.followingStops[i].stopNameRAW))
|
||||
activeMinorStopList.push(i);
|
||||
else break;
|
||||
}
|
||||
|
||||
return activeMinorStopList;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggleShowState() {
|
||||
this.$emit('click');
|
||||
},
|
||||
|
||||
addClasses(stop: TrainStop, index: number) {
|
||||
return {
|
||||
confirmed: stop.confirmed,
|
||||
stopped: stop.stopped,
|
||||
begin: stop.beginsHere,
|
||||
end: stop.terminatesHere,
|
||||
delayed: stop.departureDelay > 0,
|
||||
sbl: /sbl/gi.test(stop.stopName),
|
||||
[stop.stopType.replaceAll(', ', '-')]:
|
||||
stop.stopType.match(new RegExp('ph|pm|pt')) && !stop.confirmed && !stop.beginsHere,
|
||||
'minor-stop-active': this.activeMinorStops.includes(index),
|
||||
'last-confirmed': index == this.lastConfirmed && !stop.terminatesHere
|
||||
};
|
||||
},
|
||||
|
||||
onImageError(e: Event) {
|
||||
const imageEl = e.target as HTMLImageElement;
|
||||
imageEl.src = '/images/icon-unknown.png';
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -155,17 +231,18 @@ export default defineComponent({
|
||||
@import '../../styles/responsive.scss';
|
||||
|
||||
$barClr: #b1b1b1;
|
||||
$confirmedClr: #18d818;
|
||||
$confirmedClr: #4ae24a;
|
||||
$stoppedClr: #f55f31;
|
||||
$haltClr: #f8bb36;
|
||||
$stopNameClr: #22a8d1;
|
||||
|
||||
$blinkAnim: 0.5s ease-in-out alternate infinite blink;
|
||||
|
||||
@keyframes blink {
|
||||
from {
|
||||
background-color: $barClr;
|
||||
border-color: $barClr;
|
||||
}
|
||||
to {
|
||||
background-color: $confirmedClr;
|
||||
border-color: $confirmedClr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,216 +258,247 @@ $stopNameClr: #22a8d1;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
|
||||
top: -1px;
|
||||
left: -17px;
|
||||
|
||||
height: 100%;
|
||||
width: 3px;
|
||||
|
||||
background-color: $barClr;
|
||||
}
|
||||
|
||||
.stop-name {
|
||||
background: $stopNameClr;
|
||||
padding: 0.3em 0.5em;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&.misc {
|
||||
background: gray;
|
||||
}
|
||||
}
|
||||
|
||||
.stop-comment {
|
||||
background: forestgreen;
|
||||
padding: 0.3em 0.5em;
|
||||
|
||||
max-width: 250px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
|
||||
width: 2em;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 1em;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
}
|
||||
|
||||
ul.stop_list {
|
||||
margin-left: 2.5em;
|
||||
}
|
||||
|
||||
ul.stop_list > li.stop {
|
||||
position: relative;
|
||||
|
||||
.stops {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: hidden;
|
||||
gap: 5px;
|
||||
|
||||
padding: 0 0.5em;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
&.sbl {
|
||||
.stop-date {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.stop-name {
|
||||
background: none;
|
||||
color: #aaa;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&[class*='ph'] > .stop_info > .indicator {
|
||||
border-color: $stopNameClr;
|
||||
}
|
||||
|
||||
&[class*='pt'] > .stop_info > .indicator {
|
||||
border-color: #818181;
|
||||
}
|
||||
|
||||
&.begin {
|
||||
.stop_info > .indicator {
|
||||
.stop {
|
||||
// Begin stop
|
||||
&[data-position='begin'] {
|
||||
.node {
|
||||
border-color: lightgreen;
|
||||
}
|
||||
|
||||
.stop_info > .progress-bar {
|
||||
background: lightgreen;
|
||||
.line_node-top {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.end {
|
||||
.stop_info > .indicator {
|
||||
// End stop
|
||||
&[data-position='end'] {
|
||||
.node {
|
||||
border-color: salmon;
|
||||
}
|
||||
|
||||
.stop_info > .progress-bar {
|
||||
background: salmon;
|
||||
.line_node-bottom {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.minor-stop-active {
|
||||
.stop_info > .progress-bar {
|
||||
animation: 0.5s ease-in-out alternate infinite blink;
|
||||
}
|
||||
|
||||
.stop_line > .progress-bar {
|
||||
animation: 0.5s ease-in-out alternate infinite blink;
|
||||
}
|
||||
// Stop types
|
||||
&[data-stop-type*='pt'] .node {
|
||||
border-color: #818181;
|
||||
}
|
||||
|
||||
&.last-confirmed {
|
||||
.stop_line > .progress-bar {
|
||||
animation: 0.5s ease-in-out alternate infinite blink;
|
||||
}
|
||||
&[data-stop-type*='ph'] .node {
|
||||
border-color: $haltClr;
|
||||
}
|
||||
|
||||
&.confirmed {
|
||||
.stop_info {
|
||||
> .progress-bar {
|
||||
background-color: $confirmedClr;
|
||||
}
|
||||
|
||||
> .indicator {
|
||||
border-color: $confirmedClr;
|
||||
}
|
||||
&[data-minor-stop-active='true'] {
|
||||
.progress > .line {
|
||||
animation: $blinkAnim;
|
||||
}
|
||||
|
||||
.stop_line > .progress-bar {
|
||||
background-color: $confirmedClr;
|
||||
}
|
||||
}
|
||||
|
||||
&.stopped {
|
||||
.stop_info {
|
||||
> .indicator {
|
||||
border-color: $stoppedClr;
|
||||
}
|
||||
|
||||
> .stop-bar {
|
||||
background: $stoppedClr;
|
||||
& + div {
|
||||
.progress > .line_node-top {
|
||||
animation: $blinkAnim;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stop_line {
|
||||
font-size: 0.8em;
|
||||
color: #ccc;
|
||||
// Last confirmed outpost / checkpoint
|
||||
&[data-last-confirmed='true'] {
|
||||
.progress > .line_connection {
|
||||
animation: $blinkAnim;
|
||||
}
|
||||
|
||||
padding: 0.35em 0;
|
||||
.progress > .line_node-bottom {
|
||||
animation: $blinkAnim;
|
||||
}
|
||||
|
||||
position: relative;
|
||||
|
||||
.line-segment {
|
||||
color: $barClr;
|
||||
font-weight: 500;
|
||||
& + div {
|
||||
.progress > .line_node-top {
|
||||
animation: $blinkAnim;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stop_info {
|
||||
display: flex;
|
||||
|
||||
position: relative;
|
||||
text-align: center;
|
||||
|
||||
flex-wrap: wrap;
|
||||
// Confirmed status
|
||||
&[data-status='confirmed'] {
|
||||
.progress > .node {
|
||||
border-color: $confirmedClr;
|
||||
}
|
||||
.progress > .line {
|
||||
border-left: 2px solid $confirmedClr;
|
||||
border-right: 2px solid $confirmedClr;
|
||||
}
|
||||
}
|
||||
|
||||
.stop-bar {
|
||||
// Stopped status
|
||||
&[data-status='stopped'] {
|
||||
.progress > .node {
|
||||
border-color: $stoppedClr;
|
||||
}
|
||||
|
||||
.progress > .line_node {
|
||||
border-color: $stoppedClr;
|
||||
}
|
||||
}
|
||||
|
||||
// Unused so far
|
||||
&[data-track-count-departure='2'] {
|
||||
.progress > .line {
|
||||
width: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
&[data-track-count-arrival='2'] {
|
||||
.progress > .line_node-top {
|
||||
width: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
&[data-track-count-arrival='1'] {
|
||||
.progress > .line_node-top {
|
||||
width: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
&[data-electrified-departure] {
|
||||
.stop_line > .line-speed > .speed-departure {
|
||||
color: #00c1c7;
|
||||
}
|
||||
}
|
||||
|
||||
&[data-electrified-arrival] {
|
||||
.stop_line > .line-speed > .speed-next-arrival {
|
||||
color: #00c1c7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stop_info,
|
||||
.stop_line {
|
||||
display: grid;
|
||||
grid-template-columns: 30px 40px auto 1fr;
|
||||
}
|
||||
|
||||
.line-speed {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #9b9b9b;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.stop_info {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stop_line {
|
||||
font-size: 0.8em;
|
||||
color: #ccc;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.distance {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
.progress {
|
||||
position: relative;
|
||||
|
||||
& > .node {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -17px;
|
||||
|
||||
z-index: 10;
|
||||
|
||||
width: 3px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.distance {
|
||||
position: absolute;
|
||||
|
||||
top: 50%;
|
||||
transform: translate(-100%, -50%);
|
||||
|
||||
margin-left: -1.75rem;
|
||||
|
||||
font-size: 0.75em;
|
||||
color: #d6d6d6;
|
||||
}
|
||||
|
||||
.indicator {
|
||||
position: absolute;
|
||||
z-index: 11;
|
||||
|
||||
top: 50%;
|
||||
left: -1rem;
|
||||
|
||||
transform: translate(-47%, -50%);
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 15;
|
||||
|
||||
text-align: right;
|
||||
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
|
||||
background: var(--clr-secondary);
|
||||
border: 3px solid $barClr;
|
||||
background-color: var(--clr-secondary);
|
||||
border: 4px solid $barClr;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
& > .line {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0);
|
||||
z-index: 10;
|
||||
|
||||
height: 100%;
|
||||
|
||||
// background-color: $barClr;
|
||||
border-left: 2px solid $barClr;
|
||||
border-right: 2px solid $barClr;
|
||||
|
||||
&.line_connection {
|
||||
transform: translate(-50%, -6px);
|
||||
height: calc(100% + 12px);
|
||||
// height: calc(100% + 0.25em);
|
||||
}
|
||||
|
||||
&.line_node-top {
|
||||
top: 0;
|
||||
height: 50%;
|
||||
}
|
||||
|
||||
&.line_node-bottom {
|
||||
top: 50%;
|
||||
height: 50%;
|
||||
}
|
||||
|
||||
&.line_stop {
|
||||
border-color: $stoppedClr;
|
||||
z-index: 11;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.info-comments {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25em;
|
||||
|
||||
margin: 0.25em 0;
|
||||
|
||||
img {
|
||||
height: 1.2em;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-line-info {
|
||||
.scenery-change-name {
|
||||
position: relative;
|
||||
margin: 0.25em 0;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
height: 2px;
|
||||
width: 30px;
|
||||
background-color: #aaa;
|
||||
|
||||
top: 50%;
|
||||
right: calc(100% + 5px);
|
||||
transform: translate(0, -50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<hr style="margin: 0.5em 0" />
|
||||
|
||||
<div v-if="apiStore.dataStatuses.trains == Status.Loaded && regionTrains.length > 0">
|
||||
<div v-if="apiStore.dataStatuses.connection == Status.Loaded && regionTrains.length > 0">
|
||||
<div class="top-list general">
|
||||
<transition-group tag="ul" name="stats-anim">
|
||||
<li class="badge" key="timetable-count">
|
||||
@@ -88,7 +88,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="apiStore.dataStatuses.trains != Status.Loaded">
|
||||
<div v-else-if="apiStore.dataStatuses.connection != Status.Loaded">
|
||||
{{ $t('train-stats.stats-loading') }}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
<template>
|
||||
<transition name="status-anim" mode="out-in" tag="div" class="train-table">
|
||||
<div :key="apiStore.dataStatuses.trains">
|
||||
<div :key="apiStore.dataStatuses.connection">
|
||||
<div class="table-info" key="offline" v-if="store.isOffline">
|
||||
{{ $t('app.offline') }}
|
||||
</div>
|
||||
|
||||
<Loading v-else-if="trains.length == 0 && apiStore.dataStatuses.trains == 0" key="loading" />
|
||||
<Loading v-else-if="apiStore.dataStatuses.connection == Status.Loading" key="loading" />
|
||||
|
||||
<div
|
||||
class="table-info"
|
||||
key="no-trains"
|
||||
v-else-if="trains.length == 0 && apiStore.dataStatuses.trains != 0"
|
||||
>
|
||||
<div class="table-info" key="no-trains" v-else-if="trains.length == 0">
|
||||
{{ $t('trains.no-trains') }}
|
||||
</div>
|
||||
|
||||
@@ -64,6 +60,7 @@ export default defineComponent({
|
||||
searchedDriver,
|
||||
store,
|
||||
apiStore,
|
||||
Status: Status.Data,
|
||||
sorterActive: inject('sorterActive') as {
|
||||
id: string | number;
|
||||
dir: number;
|
||||
@@ -75,7 +72,7 @@ export default defineComponent({
|
||||
dataStatus() {
|
||||
if (this.store.isOffline) return Status.Data.Offline;
|
||||
|
||||
if (this.trains.length == 0 && this.apiStore.dataStatuses.trains == Status.Data.Loading)
|
||||
if (this.trains.length == 0 && this.apiStore.dataStatuses.connection == Status.Data.Loading)
|
||||
return Status.Data.Loading;
|
||||
|
||||
return Status.Data.Loaded;
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { PropType, defineComponent } from 'vue';
|
||||
import dateMixin from '../../mixins/dateMixin';
|
||||
import { TrainScheduleStop } from './TrainSchedule.vue';
|
||||
|
||||
export default defineComponent({
|
||||
mixins: [dateMixin],
|
||||
|
||||
props: {
|
||||
stop: {
|
||||
type: Object as PropType<TrainScheduleStop>,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -8732,7 +8732,7 @@
|
||||
"departureDelay": 0,
|
||||
"beginsHere": true,
|
||||
"terminatesHere": false,
|
||||
"confirmed": 0,
|
||||
"confirmed": 1,
|
||||
"stopped": 0,
|
||||
"stopTime": null
|
||||
},
|
||||
@@ -9380,7 +9380,7 @@
|
||||
"stopType": "",
|
||||
"stopDistance": 123.62,
|
||||
"pointId": "1663532077406",
|
||||
"comments": null,
|
||||
"comments": "test121",
|
||||
"mainStop": true,
|
||||
"arrivalLine": "Sk",
|
||||
"arrivalTimestamp": 1701889320000,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+16
-1
@@ -7,7 +7,8 @@
|
||||
"addons",
|
||||
"blockades",
|
||||
"signals",
|
||||
"status"
|
||||
"status",
|
||||
"timetables"
|
||||
],
|
||||
|
||||
"options": [
|
||||
@@ -228,6 +229,20 @@
|
||||
"section": "status",
|
||||
"value": true,
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"id": "withActiveTimetables",
|
||||
"name": "withActiveTimetables",
|
||||
"section": "timetables",
|
||||
"value": true,
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"id": "withoutActiveTimetables",
|
||||
"name": "withoutActiveTimetables",
|
||||
"section": "timetables",
|
||||
"value": true,
|
||||
"defaultValue": true
|
||||
}
|
||||
],
|
||||
"sliders": [
|
||||
|
||||
+55
-26
@@ -12,7 +12,7 @@
|
||||
"p4": "Every person who decides to contribute at least {b1} (in case of PayPal it must be a payment including additional transaction fees) for the development of Stacjownik, will receive (upon a personal request) {img}{b2} of username in the app and on my Discord server (after verifying the payment author, preferably by providing the username directly with the payment).",
|
||||
"p4-b1": "5 PLN",
|
||||
"p4-b2": "a symbolic highlight",
|
||||
"p5": "Thank you and enjoy the app!<br />~ Spythegre",
|
||||
"p5": "Thank you and enjoy the app!<br />~ Spythere",
|
||||
"action-exit": "Maybe next time...",
|
||||
"action-paypal": "DONATE WITH PAYPAL",
|
||||
"action-buycoffee": "BUY ME A COFFEE!",
|
||||
@@ -144,7 +144,8 @@
|
||||
"filter-withComments": "COMMENTS",
|
||||
"filter-twr": "HIGH RISK CARGO",
|
||||
"filter-skr": "EXCEEDED GAUGE",
|
||||
"filter-twr-skr": "ALL TYPES",
|
||||
"filter-twr-skr": "BOTH TYPES",
|
||||
"filter-all-specials": "ALL",
|
||||
"filter-common": "NO WARNINGS",
|
||||
"filter-passenger": "PASSENGER",
|
||||
"filter-freight": "FREIGHT",
|
||||
@@ -156,9 +157,9 @@
|
||||
"filter-clear": "CLEAR FILTERS",
|
||||
|
||||
"filter-section-timetable-status": "TIMETABLE STATUS",
|
||||
"filter-section-twrskr": "WARNINGS",
|
||||
"filter-section-special": "SPECIAL TYPE",
|
||||
|
||||
"filter-all": "ALL ENTRIES",
|
||||
"filter-all-statuses": "ALL",
|
||||
"filter-abandoned": "ABANDONED",
|
||||
"filter-fulfilled": "FULFILLED",
|
||||
"filter-active": "ACTIVE"
|
||||
@@ -175,7 +176,8 @@
|
||||
"signals": "SIGNALLING",
|
||||
"addons": "ADDITIONAL PROGRAMS",
|
||||
"blockades": "BLOCK SIGNALLING",
|
||||
"status": "ONLINE STATUS"
|
||||
"status": "ONLINE STATUS",
|
||||
"timetables": "ACTIVE TIMETABLES"
|
||||
},
|
||||
|
||||
"all-available": "ALL AVAILABLE",
|
||||
@@ -218,6 +220,10 @@
|
||||
"historical": "HISTORICAL",
|
||||
"free": "FREE",
|
||||
"occupied": "OCCUPIED",
|
||||
|
||||
"withActiveTimetables": "ACTIVE",
|
||||
"withoutActiveTimetables": "NO ACTIVE",
|
||||
|
||||
"sliders": {
|
||||
"min-lvl": "MIN. REQUIRED DISPATCHER LEVEL",
|
||||
"max-lvl": "MAX. REQUIRED DISPATCHER LEVEL",
|
||||
@@ -226,7 +232,10 @@
|
||||
"routes-2t-cat": "MIN. CATENARY DOUBLE TRACK ROUTES",
|
||||
"routes-2t-other": "MIN. OTHER DOUBLE TRACK ROUTES"
|
||||
},
|
||||
"authors-search": "Search by author (other filters apply)",
|
||||
"authors-search": "SEARCH BY AUTHOR NAME (other filters apply):",
|
||||
"authors-placeholder": "Enter the author nickname...",
|
||||
"authors-button-title": "Search",
|
||||
|
||||
"minimum-hours-title": "SHOW ONLY SCENERIES UNTIL:",
|
||||
"now": "NOW",
|
||||
"hour": "h",
|
||||
@@ -347,29 +356,49 @@
|
||||
"last-seen-at": "Last seen at",
|
||||
"currently-at": "Currently at",
|
||||
|
||||
"stats-title": "DRIVING STATISTICS OF",
|
||||
"driver-stats": {
|
||||
"button": "DRIVER STATS",
|
||||
"title": "{name}'s DRIVER STATS",
|
||||
"info": "Enter a proper nickname into filters [F] to see user's driving statistics!",
|
||||
"timetables": "TIMETABLES",
|
||||
"longest-timetable": "LONGEST TIMETABLE",
|
||||
"avg-timetable": "AVERAGE TIMETABLE LENGTH",
|
||||
"distance": "DISTANCE",
|
||||
"stations": "STATIONS"
|
||||
},
|
||||
|
||||
"stats-timetables": "TIMETABLES",
|
||||
"stats-longest-timetable": "LONGEST TIMETABLE",
|
||||
"stats-avg-timetable": "AVERAGE TIMETABLE LENGTH",
|
||||
"stats-distance": "DISTANCE",
|
||||
"stats-stations": "STATIONS",
|
||||
"daily-stats": {
|
||||
"button": "DAILY STATS",
|
||||
"title": "STATS OF THE DAY",
|
||||
"info": "Today's statistics are unavailable yet!",
|
||||
"total": "Issued timetables: {count} (total distance: {distance})",
|
||||
"longest": "The longest timetable: #{id} (made by {author} for {driver}, distance: {distance})",
|
||||
"most-active-dr": "The most active dispatcher: {dispatcher} (created {count})",
|
||||
"most-active-dr-many": "The most active dispatchers: {dispatchers} (created {count} each)",
|
||||
"most-active-driver": "The most active driver: {driver} (total driven distance: {distance})",
|
||||
"longest-duties": "The longest service: {dispatcher} at {station} (duration: {duration})",
|
||||
"count": "timetable | timetables",
|
||||
|
||||
"timetable-stats-title": "Daily stats on {date}",
|
||||
"timetable-stats-total": "Issued timetables: {count} (total distance: {distance})",
|
||||
"timetable-stats-longest": "The longest timetable: #{id} (made by {author} for {driver}, distance: {distance})",
|
||||
"timetable-stats-most-active-dr": "The most active dispatcher: {dispatcher} (created {count})",
|
||||
"timetable-stats-most-active-dr-many": "The most active dispatchers: {dispatchers} (created {count} each)",
|
||||
"timetable-stats-most-active-driver": "The most active driver: {driver} (total driven distance: {distance})",
|
||||
"timetable-stats-longest-duties": "The longest service: {dispatcher} at {station} (duration: {duration})",
|
||||
"rippedSwitches": "RIPPED SWITCHES",
|
||||
"derailments": "DERAILMENTS",
|
||||
"skippedStopSignals": "SKIPPED STOP SIGNALS",
|
||||
"radioStops": "RADIOSTOPS",
|
||||
"kills": "KILLS"
|
||||
},
|
||||
|
||||
"timetable-count": "timetable | timetables",
|
||||
|
||||
"daily-stats-title": "DAILY STATS",
|
||||
"daily-stats-info": "Today's statistics are unavailable yet!",
|
||||
|
||||
"driver-stats-title": "DRIVER STATS",
|
||||
"driver-stats-info": "Enter a proper nickname into filters [F] to see user's driving statistics!",
|
||||
"dispatcher-stats": {
|
||||
"button": "DISPATCHER STATS",
|
||||
"title": "{name}'s DISPATCHER STATS",
|
||||
"empty": "This user has no statistics saved yet!",
|
||||
"info": "Enter a proper nickname into filters [F] to see user's dispatcher statistics!",
|
||||
"services-count": "SERVICES",
|
||||
"service-max": "MAX SERVICE DURATION",
|
||||
"service-avg": "AVG SERVICE DURATION",
|
||||
"timetables-count": "ISSUED TIMETABLES",
|
||||
"timetables-sum": "TIMETABLES DISTANCE SUM",
|
||||
"timetables-max": "LONGEST TIMETABLE",
|
||||
"timetables-avg": "AVG TIMETABLE DISTANCE"
|
||||
},
|
||||
|
||||
"stats-loading": "Fetching statistics...",
|
||||
"stats-error": "Oops! An unexpected error occurred while trying to fetch statistics! :/",
|
||||
|
||||
+51
-24
@@ -133,7 +133,8 @@
|
||||
"filter-noComments": "BEZ UWAG",
|
||||
"filter-twr": "WYS. RYZYKA",
|
||||
"filter-skr": "SKRAJNIA",
|
||||
"filter-twr-skr": "WSZYSTKIE",
|
||||
"filter-twr-skr": "TWR/SKR",
|
||||
"filter-all-statuses": "WSZYSTKIE",
|
||||
"filter-common": "ZWYKŁE",
|
||||
"filter-passenger": "PASAŻERSKIE",
|
||||
"filter-freight": "TOWAROWE",
|
||||
@@ -145,9 +146,9 @@
|
||||
"filter-clear": "WYŁĄCZ FILTRY",
|
||||
|
||||
"filter-section-timetable-status": "STATUS ROZKŁADU JAZDY",
|
||||
"filter-section-twrskr": "UWAGI",
|
||||
"filter-section-special": "TYPY SPECJALNE",
|
||||
|
||||
"filter-all": "WSZYSTKIE",
|
||||
"filter-all-specials": "WSZYSTKIE",
|
||||
"filter-abandoned": "PORZUCONE",
|
||||
"filter-fulfilled": "WYPEŁNIONE",
|
||||
"filter-active": "AKTYWNE"
|
||||
@@ -164,7 +165,8 @@
|
||||
"signals": "TYP SYGNALIZACJI",
|
||||
"addons": "DODATKOWE PROGRAMY",
|
||||
"blockades": "BLOKADY LINIOWE",
|
||||
"status": "STATUS ONLINE"
|
||||
"status": "STATUS ONLINE",
|
||||
"timetables": "AKTYWNE ROZKŁADY JAZDY"
|
||||
},
|
||||
|
||||
"all-available": "WSZYSTKIE DOSTĘPNE",
|
||||
@@ -208,6 +210,9 @@
|
||||
"free": "WOLNA",
|
||||
"occupied": "ZAJĘTA",
|
||||
|
||||
"withActiveTimetables": "AKTYWNE RJ",
|
||||
"withoutActiveTimetables": "BEZ AKTYWNYCH RJ",
|
||||
|
||||
"sliders": {
|
||||
"min-lvl": "MIN. WYMAGANY POZIOM DYŻURNEGO",
|
||||
"max-lvl": "MAKS. WYMAGANY POZIOM DYŻURNEGO",
|
||||
@@ -217,7 +222,9 @@
|
||||
"routes-2t-other": "SZLAKI DWUTOROWE NIEZELEKTR. (MINIMUM)"
|
||||
},
|
||||
|
||||
"authors-search": "Szukaj autora (uwzględnia inne filtry)",
|
||||
"authors-search": "SZUKAJ AUTORA (uwzględnia inne filtry):",
|
||||
"authors-placeholder": "Wpisz nick autora...",
|
||||
"authors-button-title": "Szukaj",
|
||||
"minimum-hours-title": "POKAŻ TYLKO SCENERIE DOSTĘPNE MINIMUM DO:",
|
||||
"now": "TERAZ",
|
||||
"hour": " godz.",
|
||||
@@ -326,31 +333,51 @@
|
||||
|
||||
"load-data": "Pobierz dalszą historię...",
|
||||
|
||||
"stats-title": "STATYSTYKI MASZYNISTY",
|
||||
|
||||
"last-seen-at": "Ostatnio widziany na: ",
|
||||
"currently-at": "Obecnie na scenerii: ",
|
||||
|
||||
"stats-timetables": "ROZKŁADY JAZDY",
|
||||
"stats-longest-timetable": "NAJDŁUŻSZY RJ",
|
||||
"stats-avg-timetable": "ŚREDNIA DŁUGOŚĆ RJ",
|
||||
"stats-distance": "DYSTANS",
|
||||
"stats-stations": "STACJE",
|
||||
"driver-stats": {
|
||||
"button": "STAT. MASZYNISTY",
|
||||
"title": "STATYSTYKI MASZYNISTY {name}",
|
||||
"info": "Wpisz nazwę użytkownika w filtrach [F], aby zobaczyć jego statystyki maszynisty!",
|
||||
"timetables": "ROZKŁADY JAZDY",
|
||||
"longest-timetable": "NAJDŁUŻSZY RJ",
|
||||
"avg-timetable": "ŚREDNIA DŁUGOŚĆ RJ",
|
||||
"distance": "DYSTANS",
|
||||
"stations": "STACJE"
|
||||
},
|
||||
|
||||
"timetable-stats-total": "Stworzone rozkłady jazdy: {count} (łączny dystans: {distance})",
|
||||
"timetable-stats-longest": "Najdłuższy rozkład jazdy: #{id} (stworzony przez dyżurnego {author} dla maszynisty {driver} o dystansie {distance})",
|
||||
"timetable-stats-most-active-dr": "Najaktywniejszy dyżurny: {dispatcher} (stworzył {count})",
|
||||
"timetable-stats-most-active-dr-many": "Najaktywniejsi dyżurni: {dispatchers} (stworzyli po {count})",
|
||||
"timetable-stats-most-active-driver": "Najaktywniejszy maszynista: {driver} (łączny przejechany dystans: {distance})",
|
||||
"timetable-stats-longest-duties": "Najdłuższa służba: {dispatcher} na scenerii {station} (czas trwania: {duration})",
|
||||
"daily-stats": {
|
||||
"button": "STATYSTYKI DNIA",
|
||||
"title": "STATYSTYKI DNIA",
|
||||
"info": "Dzisiejsze statystyki nie są jeszcze dostępne!",
|
||||
"total": "Stworzone rozkłady jazdy: {count} (łączny dystans: {distance})",
|
||||
"longest": "Najdłuższy rozkład jazdy: #{id} (stworzony przez dyżurnego {author} dla maszynisty {driver} o dystansie {distance})",
|
||||
"most-active-dr": "Najaktywniejszy dyżurny: {dispatcher} (stworzył {count})",
|
||||
"most-active-dr-many": "Najaktywniejsi dyżurni: {dispatchers} (stworzyli po {count})",
|
||||
"most-active-driver": "Najaktywniejszy maszynista: {driver} (łączny przejechany dystans: {distance})",
|
||||
"longest-duties": "Najdłuższa służba: {dispatcher} na scenerii {station} (czas trwania: {duration})",
|
||||
"count": "rozkład jazdy | rozkładów jazdy",
|
||||
|
||||
"timetable-count": "rozkład jazdy | rozkładów jazdy",
|
||||
"rippedSwitches": "ROZPRUTE ZWROTNICE",
|
||||
"derailments": "WYKOLEJENIA",
|
||||
"skippedStopSignals": "POMINIĘTE S1",
|
||||
"radioStops": "RADIOSTOPY",
|
||||
"kills": "POTRĄCENIA"
|
||||
},
|
||||
|
||||
"daily-stats-title": "STATYSTYKI DNIA",
|
||||
"daily-stats-info": "Dzisiejsze statystyki nie są jeszcze dostępne!",
|
||||
|
||||
"driver-stats-title": "STATYSTYKI GRACZA",
|
||||
"driver-stats-info": "Wpisz nazwę użytkownika w filtrach [F], aby zobaczyć jego statystyki maszynisty!",
|
||||
"dispatcher-stats": {
|
||||
"button": "STATYSTYKI DYŻURNEGO",
|
||||
"title": "STATYSTYKI DYŻURNEGO {name}",
|
||||
"info": "Wpisz nazwę użytkownika w filtrach [F], aby zobaczyć jego statystyki dyżurnego!",
|
||||
"services-count": "DYŻURY",
|
||||
"service-max": "MAKS. CZAS DYŻURU",
|
||||
"service-avg": "ŚREDNI CZAS DYŻURU",
|
||||
"timetables-count": "WYSTAWIONE RJ",
|
||||
"timetables-sum": "SUMA WYSTAWIONYCH RJ",
|
||||
"timetables-max": "NAJDŁUŻSZY WYSTAWIONY RJ",
|
||||
"timetables-avg": "ŚREDNIA WYSTAWIONYCH RJ"
|
||||
},
|
||||
|
||||
"stats-loading": "Pobieranie statystyk...",
|
||||
"stats-error": "Ups! Wystąpił błąd podczas próby pobrania statystyk!",
|
||||
|
||||
@@ -10,8 +10,6 @@ export default defineComponent({
|
||||
mountObserver(actionFunction: () => void, target: Element) {
|
||||
this.observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
console.log(entries);
|
||||
|
||||
if (entries[0].intersectionRatio > 0.5) actionFunction();
|
||||
},
|
||||
{ threshold: 0.2 }
|
||||
|
||||
+4
-6
@@ -18,7 +18,8 @@ const routes: Array<RouteRecordRaw> = [
|
||||
props: (route) => ({
|
||||
train: route.query.train,
|
||||
driver: route.query.driver,
|
||||
trainId: route.query.trainId
|
||||
trainId: route.query.trainId,
|
||||
region: route.query.region
|
||||
})
|
||||
},
|
||||
{
|
||||
@@ -39,9 +40,7 @@ const routes: Array<RouteRecordRaw> = [
|
||||
name: 'JournalTimetables',
|
||||
component: JournalTimetablesVue,
|
||||
props: (route) => ({
|
||||
trainNo: route.query.trainNo,
|
||||
driverName: route.query.driverName,
|
||||
timetableId: route.query.timetableId
|
||||
region: route.query.region
|
||||
})
|
||||
},
|
||||
{
|
||||
@@ -49,8 +48,7 @@ const routes: Array<RouteRecordRaw> = [
|
||||
name: 'JournalDispatchers',
|
||||
component: JournalDispatchersVue,
|
||||
props: (route) => ({
|
||||
sceneryName: route.query.sceneryName,
|
||||
dispatcherName: route.query.dispatcherName
|
||||
region: route.query.region
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Availability, OnlineScenery, ScheduledTrain } from '../../store/typings';
|
||||
import StationRoutes from './StationRoutes';
|
||||
import { Availability, ActiveScenery, ScheduledTrain } from '../../store/typings';
|
||||
import { StationRoutes } from './StationRoutes';
|
||||
|
||||
export default interface Station {
|
||||
name: string;
|
||||
@@ -8,6 +8,7 @@ export default interface Station {
|
||||
name: string;
|
||||
url: string;
|
||||
abbr: string;
|
||||
hash?: string;
|
||||
|
||||
reqLevel: number;
|
||||
// supportersOnly: boolean;
|
||||
@@ -25,11 +26,8 @@ export default interface Station {
|
||||
availability: Availability;
|
||||
routes: StationRoutes;
|
||||
|
||||
checkpoints: {
|
||||
checkpointName: string;
|
||||
scheduledTrains: ScheduledTrain[];
|
||||
}[];
|
||||
checkpoints: string[];
|
||||
};
|
||||
|
||||
onlineInfo?: OnlineScenery;
|
||||
onlineInfo?: ActiveScenery;
|
||||
}
|
||||
|
||||
@@ -1,25 +1,8 @@
|
||||
export default interface StationRoutes {
|
||||
oneWay: {
|
||||
name: string;
|
||||
catenary: boolean;
|
||||
SBL: boolean;
|
||||
TWB: boolean;
|
||||
isInternal: boolean;
|
||||
tracks: number;
|
||||
speed: number;
|
||||
length: number;
|
||||
}[];
|
||||
import { StationRoutesInfo } from '../../store/typings';
|
||||
|
||||
twoWay: {
|
||||
name: string;
|
||||
catenary: boolean;
|
||||
SBL: boolean;
|
||||
TWB: boolean;
|
||||
isInternal: boolean;
|
||||
tracks: number;
|
||||
speed: number;
|
||||
length: number;
|
||||
}[];
|
||||
export interface StationRoutes {
|
||||
oneWay: StationRoutesInfo[];
|
||||
twoWay: StationRoutesInfo[];
|
||||
|
||||
/* [catenary, noCatenary] */
|
||||
oneWayCatenaryRouteNames: string[];
|
||||
|
||||
@@ -33,5 +33,6 @@ export default interface Train {
|
||||
SKR: boolean;
|
||||
routeDistance: number;
|
||||
sceneries: string[];
|
||||
sceneryNames: string[];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -93,26 +93,41 @@ export const sortStations = (
|
||||
};
|
||||
|
||||
export const filterStations = (station: Station, filters: Filter) => {
|
||||
if (!station.onlineInfo && filters['free']) return false;
|
||||
if (filters['free'] && (!station.onlineInfo || station.onlineInfo.dispatcherId == -1))
|
||||
return false;
|
||||
|
||||
if (station.onlineInfo) {
|
||||
const { dispatcherStatus } = station.onlineInfo;
|
||||
|
||||
const isEnding = dispatcherStatus == Status.ActiveDispatcher.ENDING && filters['endingStatus'];
|
||||
const excludeEnding =
|
||||
dispatcherStatus == Status.ActiveDispatcher.ENDING && filters['endingStatus'];
|
||||
|
||||
const isNotSigned =
|
||||
const excludeNotSigned =
|
||||
(dispatcherStatus == Status.ActiveDispatcher.NOT_LOGGED_IN ||
|
||||
dispatcherStatus == Status.ActiveDispatcher.UNAVAILABLE) &&
|
||||
filters['unavailableStatus'];
|
||||
|
||||
const isAFK = dispatcherStatus == Status.ActiveDispatcher.AFK && filters['afkStatus'];
|
||||
const excludeAFK = dispatcherStatus == Status.ActiveDispatcher.AFK && filters['afkStatus'];
|
||||
|
||||
const isNoSpace =
|
||||
const excludeNoSpace =
|
||||
dispatcherStatus == Status.ActiveDispatcher.NO_SPACE && filters['noSpaceStatus'];
|
||||
|
||||
const isOccupied = station.onlineInfo && filters['occupied'];
|
||||
const excludeOccupied = filters['occupied'] && dispatcherStatus != Status.ActiveDispatcher.FREE;
|
||||
|
||||
if (isEnding || isNotSigned || isAFK || isNoSpace || isOccupied) return false;
|
||||
const excludeActiveTTs =
|
||||
(dispatcherStatus == Status.ActiveDispatcher.FREE ||
|
||||
station.onlineInfo.scheduledTrainCount.all != 0) &&
|
||||
filters['withActiveTimetables'];
|
||||
|
||||
if (
|
||||
excludeEnding ||
|
||||
excludeAFK ||
|
||||
excludeNoSpace ||
|
||||
excludeNotSigned ||
|
||||
excludeOccupied ||
|
||||
excludeActiveTTs
|
||||
)
|
||||
return false;
|
||||
|
||||
if (
|
||||
filters['onlineFromHours'] > 0 &&
|
||||
@@ -121,6 +136,12 @@ export const filterStations = (station: Station, filters: Filter) => {
|
||||
return false;
|
||||
}
|
||||
|
||||
const excludeNoActiveTTs =
|
||||
filters['withoutActiveTimetables'] &&
|
||||
(!station.onlineInfo || station.onlineInfo.scheduledTrainCount.all == 0);
|
||||
|
||||
if (excludeNoActiveTTs) return false;
|
||||
|
||||
if (
|
||||
(station.generalInfo?.availability == 'nonPublic' || !station.generalInfo) &&
|
||||
filters['nonPublic']
|
||||
|
||||
+34
-49
@@ -5,44 +5,60 @@ import axios from 'axios';
|
||||
import { Status } from '../typings/common';
|
||||
import { StationJSONData } from './typings';
|
||||
|
||||
// Update seconds cron for active data scheduler
|
||||
const UPDATE_SECONDS = [3, 23, 43];
|
||||
|
||||
export const useApiStore = defineStore('apiStore', {
|
||||
state: () => ({
|
||||
dataStatuses: {
|
||||
connection: Status.Data.Loading,
|
||||
sceneries: Status.Data.Loading,
|
||||
timetables: Status.Data.Loading,
|
||||
dispatchers: Status.Data.Loading,
|
||||
trains: Status.Data.Loading
|
||||
sceneries: Status.Data.Loading
|
||||
},
|
||||
|
||||
activeData: undefined as API.ActiveData.Response | undefined,
|
||||
rollingStockData: undefined as API.RollingStock.Response | undefined,
|
||||
donatorsData: [] as API.Donators.Response,
|
||||
sceneryData: [] as StationJSONData[]
|
||||
sceneryData: [] as StationJSONData[],
|
||||
|
||||
activeDataScheduler: undefined as number | undefined
|
||||
}),
|
||||
|
||||
actions: {
|
||||
async setupAPI() {
|
||||
async setupAPIData() {
|
||||
// Static data
|
||||
this.fetchStockInfoData();
|
||||
this.fetchDonatorsData();
|
||||
this.fetchStationsGeneralInfo();
|
||||
|
||||
this.scheduleFetchActiveData();
|
||||
// Active data schedueler
|
||||
this.fetchActiveData();
|
||||
this.setupActiveDataFetcher();
|
||||
},
|
||||
|
||||
async setDataStatuses() {
|
||||
if (!this.activeData?.activeSceneries) {
|
||||
this.dataStatuses.sceneries = Status.Data.Error;
|
||||
this.dataStatuses.trains = Status.Data.Error;
|
||||
this.dataStatuses.dispatchers = Status.Data.Error;
|
||||
async setupActiveDataFetcher() {
|
||||
if (this.activeDataScheduler) return;
|
||||
|
||||
return;
|
||||
this.dataStatuses.connection = Status.Data.Loading;
|
||||
|
||||
this.activeDataScheduler = window.setInterval(() => {
|
||||
if (UPDATE_SECONDS.includes(new Date().getSeconds())) {
|
||||
this.fetchActiveData();
|
||||
}
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
async fetchActiveData() {
|
||||
try {
|
||||
const response = await http.get<API.ActiveData.Response>('api/getActiveData');
|
||||
|
||||
this.activeData = response.data;
|
||||
this.dataStatuses.connection = Status.Data.Loaded;
|
||||
|
||||
console.log('Fetching active data at ' + new Date().toLocaleTimeString('pl-PL'));
|
||||
} catch (error) {
|
||||
this.dataStatuses.connection = Status.Data.Error;
|
||||
console.error('Ups! Wystąpił błąd podczas pobierania danych online:', error);
|
||||
}
|
||||
|
||||
this.dataStatuses.sceneries = Status.Data.Loaded;
|
||||
this.dataStatuses.trains = !this.activeData.trains ? Status.Data.Warning : Status.Data.Loaded;
|
||||
this.dataStatuses.dispatchers = Status.Data.Loaded;
|
||||
},
|
||||
|
||||
async fetchDonatorsData() {
|
||||
@@ -67,38 +83,6 @@ export const useApiStore = defineStore('apiStore', {
|
||||
}
|
||||
},
|
||||
|
||||
async scheduleFetchActiveData() {
|
||||
if (import.meta.env.VITE_API_MODE === 'mock') {
|
||||
const mockActiveData = await import('../data/mockActiveData.json');
|
||||
this.dataStatuses.connection = Status.Data.Loaded;
|
||||
this.activeData = mockActiveData;
|
||||
this.setDataStatuses();
|
||||
|
||||
console.warn('Stacjownik działa w trybie mockowania danych z WS');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const data = (await http.get<API.ActiveData.Response>('api/getActiveData')).data;
|
||||
|
||||
this.activeData = data;
|
||||
this.dataStatuses.connection = Status.Data.Loaded;
|
||||
|
||||
this.setDataStatuses();
|
||||
} catch (error) {
|
||||
this.dataStatuses.connection = Status.Data.Error;
|
||||
console.error('Wystąpił błąd podczas pobierania danych online z API!');
|
||||
} finally {
|
||||
setTimeout(
|
||||
() => {
|
||||
this.scheduleFetchActiveData();
|
||||
},
|
||||
~~(1000 * (Math.random() * (25 - 20) + 25))
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
async fetchStationsGeneralInfo() {
|
||||
const sceneryData: StationJSONData[] = (await http.get<StationJSONData[]>('api/getSceneries'))
|
||||
.data;
|
||||
@@ -108,6 +92,7 @@ export const useApiStore = defineStore('apiStore', {
|
||||
return;
|
||||
}
|
||||
|
||||
this.dataStatuses.sceneries = Status.Data.Loaded;
|
||||
this.sceneryData = sceneryData;
|
||||
}
|
||||
}
|
||||
|
||||
+124
-153
@@ -1,14 +1,13 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import StationRoutes from '../scripts/interfaces/StationRoutes';
|
||||
import Train from '../scripts/interfaces/Train';
|
||||
import { parseSpawns, getScheduledTrains, getStationTrains } from './utils';
|
||||
|
||||
import { OnlineScenery, ScheduledTrain, StoreState } from './typings';
|
||||
import { ActiveScenery, ScheduledTrain, StoreState } from './typings';
|
||||
|
||||
import { Status } from '../typings/common';
|
||||
import Station from '../scripts/interfaces/Station';
|
||||
import { useApiStore } from './apiStore';
|
||||
import { API } from '../typings/api';
|
||||
import { StationRoutes } from '../scripts/interfaces/StationRoutes';
|
||||
|
||||
export const useMainStore = defineStore('store', {
|
||||
state: () =>
|
||||
@@ -18,7 +17,7 @@ export const useMainStore = defineStore('store', {
|
||||
isOffline: false,
|
||||
|
||||
dispatcherStatsName: '',
|
||||
dispatcherStatsData: undefined,
|
||||
dispatcherStatsStatus: Status.Data.Initialized,
|
||||
|
||||
driverStatsName: '',
|
||||
driverStatsData: undefined,
|
||||
@@ -42,6 +41,14 @@ export const useMainStore = defineStore('store', {
|
||||
|
||||
const timetable = train.timetable;
|
||||
|
||||
const sceneryNames =
|
||||
train.timetable?.sceneries?.map(
|
||||
(sceneryHash) =>
|
||||
this.activeSceneryList.find((st) => st.hash === sceneryHash)?.name ??
|
||||
apiStore.sceneryData.find((sd) => sd.hash === sceneryHash)?.name ??
|
||||
sceneryHash
|
||||
) ?? [];
|
||||
|
||||
return {
|
||||
trainId: train.driverName + train.trainNo.toString(),
|
||||
|
||||
@@ -77,43 +84,67 @@ export const useMainStore = defineStore('store', {
|
||||
category: timetable.category,
|
||||
followingStops: timetable.stopList,
|
||||
routeDistance: timetable.stopList[timetable.stopList.length - 1].stopDistance,
|
||||
sceneries: timetable.sceneries
|
||||
sceneries: timetable.sceneries,
|
||||
sceneryNames: sceneryNames.reverse()
|
||||
}
|
||||
: undefined
|
||||
} as Train;
|
||||
});
|
||||
},
|
||||
|
||||
onlineSceneryList(state): OnlineScenery[] {
|
||||
activeSceneryList(state): ActiveScenery[] {
|
||||
const apiStore = useApiStore();
|
||||
|
||||
if (state.isOffline) return [];
|
||||
|
||||
if (!apiStore.activeData?.activeSceneries) return [];
|
||||
|
||||
return apiStore.activeData?.activeSceneries.reduce((list, scenery) => {
|
||||
const offlineActiveSceneries = this.trainList.reduce((acc, train) => {
|
||||
if (!train.timetableData) return acc;
|
||||
|
||||
train.timetableData.sceneryNames.forEach((name) => {
|
||||
if (
|
||||
acc.findIndex((v) => v.name == name && v.region == train.region) != -1 ||
|
||||
apiStore.activeData?.activeSceneries?.findIndex(
|
||||
(sc) => sc.stationName === name && sc.region == train.region
|
||||
) != -1
|
||||
)
|
||||
return acc;
|
||||
|
||||
acc.push({
|
||||
name: name,
|
||||
hash: '',
|
||||
region: train.region,
|
||||
maxUsers: 0,
|
||||
currentUsers: 0,
|
||||
spawns: [],
|
||||
dispatcherName: '',
|
||||
dispatcherRate: 0,
|
||||
dispatcherId: -1,
|
||||
dispatcherExp: -1,
|
||||
dispatcherIsSupporter: false,
|
||||
scheduledTrains: [],
|
||||
stationTrains: [],
|
||||
dispatcherStatus: Status.ActiveDispatcher.FREE,
|
||||
dispatcherTimestamp: -1,
|
||||
|
||||
isOnline: false,
|
||||
|
||||
scheduledTrainCount: {
|
||||
all: 0,
|
||||
confirmed: 0,
|
||||
unconfirmed: 0
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return acc;
|
||||
}, [] as ActiveScenery[]);
|
||||
|
||||
const onlineActiveSceneries = apiStore.activeData?.activeSceneries.reduce((list, scenery) => {
|
||||
if (scenery.isOnline !== 1 && Date.now() - scenery.lastSeen > 1000 * 60 * 2) return list;
|
||||
if (scenery.dispatcherStatus == Status.ActiveDispatcher.UNKNOWN) return list;
|
||||
|
||||
const station = this.stationList.find((s) => s.name === scenery.stationName);
|
||||
|
||||
const scheduledTrains = getScheduledTrains(this.trainList, scenery, station?.generalInfo);
|
||||
|
||||
const stationTrains = getStationTrains(
|
||||
this.trainList,
|
||||
scheduledTrains,
|
||||
this.region.id,
|
||||
scenery
|
||||
);
|
||||
|
||||
// Remove checkpoint duplicates
|
||||
const uniqueScheduledTrains = scheduledTrains.reduce(
|
||||
(uniqueList, sTrain) =>
|
||||
uniqueList.find((v) => v.trainId === sTrain.trainId)
|
||||
? uniqueList
|
||||
: [...uniqueList, sTrain],
|
||||
[] as ScheduledTrain[]
|
||||
);
|
||||
|
||||
const dispatcherTimestamp =
|
||||
scenery.dispatcherStatus == Status.ActiveDispatcher.NO_LIMIT
|
||||
? Date.now() + 25500000
|
||||
@@ -133,97 +164,42 @@ export const useMainStore = defineStore('store', {
|
||||
dispatcherId: scenery.dispatcherId,
|
||||
dispatcherExp: scenery.dispatcherExp,
|
||||
dispatcherIsSupporter: scenery.dispatcherIsSupporter,
|
||||
scheduledTrains: scheduledTrains,
|
||||
stationTrains: stationTrains,
|
||||
dispatcherStatus: scenery.dispatcherStatus,
|
||||
dispatcherTimestamp: dispatcherTimestamp,
|
||||
|
||||
isOnline: scenery.isOnline == 1,
|
||||
|
||||
scheduledTrains: [],
|
||||
stationTrains: [],
|
||||
scheduledTrainCount: {
|
||||
all: uniqueScheduledTrains.length,
|
||||
confirmed: uniqueScheduledTrains.filter((train) => train.stopInfo.confirmed).length,
|
||||
unconfirmed: uniqueScheduledTrains.filter((train) => !train.stopInfo.confirmed).length
|
||||
all: 0,
|
||||
confirmed: 0,
|
||||
unconfirmed: 0
|
||||
}
|
||||
});
|
||||
|
||||
return list;
|
||||
}, [] as OnlineScenery[]);
|
||||
},
|
||||
}, [] as ActiveScenery[]);
|
||||
|
||||
stationList(): Station[] {
|
||||
const apiStore = useApiStore();
|
||||
const allActiveSceneries = [...onlineActiveSceneries, ...offlineActiveSceneries];
|
||||
|
||||
return apiStore.sceneryData.map((scenery) => {
|
||||
return {
|
||||
name: scenery.name,
|
||||
for (let i = 0, n = allActiveSceneries.length; i < n; i++) {
|
||||
const scenery = allActiveSceneries[i];
|
||||
|
||||
generalInfo: {
|
||||
...scenery,
|
||||
authors: scenery.authors?.split(',').map((a) => a.trim()),
|
||||
routes:
|
||||
scenery.routesInfo.reduce(
|
||||
(acc, route) => {
|
||||
const propName: keyof StationRoutes = `${
|
||||
route.routeTracks == 2 ? 'twoWay' : 'oneWay'
|
||||
}${route.isElectric ? '' : 'No'}CatenaryRouteNames`;
|
||||
const station = this.stationList.find((s) => s.name === scenery.name);
|
||||
|
||||
acc[route.routeTracks == 2 ? 'twoWay' : 'oneWay'].push({
|
||||
name: route.routeName,
|
||||
SBL: route.isRouteSBL,
|
||||
TWB: false,
|
||||
catenary: route.isElectric,
|
||||
isInternal: route.isInternal,
|
||||
tracks: route.routeTracks,
|
||||
length: route.routeLength,
|
||||
speed: route.routeSpeed
|
||||
});
|
||||
|
||||
if (!route.isInternal) acc[propName].push(route.routeName);
|
||||
|
||||
if (route.isRouteSBL) acc['sblRouteNames'].push(route.routeName);
|
||||
|
||||
return acc;
|
||||
},
|
||||
{
|
||||
oneWay: [],
|
||||
twoWay: [],
|
||||
sblRouteNames: [],
|
||||
oneWayCatenaryRouteNames: [],
|
||||
oneWayNoCatenaryRouteNames: [],
|
||||
twoWayCatenaryRouteNames: [],
|
||||
twoWayNoCatenaryRouteNames: []
|
||||
} as StationRoutes
|
||||
) || {},
|
||||
checkpoints: scenery.checkpoints
|
||||
? scenery.checkpoints
|
||||
.split(';')
|
||||
.map((sub) => ({ checkpointName: sub, scheduledTrains: [] }))
|
||||
: []
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
async processStationsOnlineInfo(activeData: API.ActiveData.Response) {
|
||||
if (!activeData.activeSceneries) return;
|
||||
|
||||
const onlineSceneries = activeData.activeSceneries.reduce((acc, scenery) => {
|
||||
const savedStation = this.stationList.find((st) => scenery.stationName === st.name);
|
||||
|
||||
if (scenery.isOnline !== 1 && Date.now() - scenery.lastSeen > 1000 * 60 * 2) return acc;
|
||||
if (scenery.dispatcherStatus == Status.ActiveDispatcher.UNKNOWN) return acc;
|
||||
|
||||
const station = this.stationList.find((s) => s.name === scenery.stationName);
|
||||
|
||||
const scheduledTrains = getScheduledTrains(this.trainList, scenery, station?.generalInfo);
|
||||
const scheduledTrains = getScheduledTrains(
|
||||
this.trainList,
|
||||
station?.generalInfo,
|
||||
scenery.name,
|
||||
scenery.region
|
||||
);
|
||||
|
||||
const stationTrains = getStationTrains(
|
||||
this.trainList,
|
||||
scheduledTrains,
|
||||
this.region.id,
|
||||
scenery
|
||||
scenery.name
|
||||
);
|
||||
|
||||
// Remove checkpoint duplicates
|
||||
@@ -235,65 +211,60 @@ export const useMainStore = defineStore('store', {
|
||||
[] as ScheduledTrain[]
|
||||
);
|
||||
|
||||
const dispatcherTimestamp =
|
||||
scenery.dispatcherStatus == Status.ActiveDispatcher.NO_LIMIT
|
||||
? Date.now() + 25500000
|
||||
: scenery.dispatcherStatus > 5
|
||||
? scenery.dispatcherStatus
|
||||
: null;
|
||||
scenery.scheduledTrains = scheduledTrains;
|
||||
scenery.stationTrains = stationTrains;
|
||||
|
||||
const onlineInfo = {
|
||||
name: scenery.stationName,
|
||||
hash: scenery.stationHash,
|
||||
region: scenery.region,
|
||||
maxUsers: scenery.maxUsers,
|
||||
currentUsers: scenery.currentUsers,
|
||||
spawns: parseSpawns(scenery.spawnString),
|
||||
dispatcherName: scenery.dispatcherName,
|
||||
dispatcherRate: scenery.dispatcherRate,
|
||||
dispatcherId: scenery.dispatcherId,
|
||||
dispatcherExp: scenery.dispatcherExp,
|
||||
dispatcherIsSupporter: scenery.dispatcherIsSupporter,
|
||||
scheduledTrains: scheduledTrains,
|
||||
stationTrains: stationTrains,
|
||||
dispatcherStatus: scenery.dispatcherStatus,
|
||||
dispatcherTimestamp: dispatcherTimestamp,
|
||||
|
||||
isOnline: scenery.isOnline == 1,
|
||||
|
||||
scheduledTrainCount: {
|
||||
all: uniqueScheduledTrains.length,
|
||||
confirmed: uniqueScheduledTrains.filter((train) => train.stopInfo.confirmed).length,
|
||||
unconfirmed: uniqueScheduledTrains.filter((train) => !train.stopInfo.confirmed).length
|
||||
}
|
||||
scenery.scheduledTrainCount = {
|
||||
all: uniqueScheduledTrains.length,
|
||||
confirmed: uniqueScheduledTrains.filter((train) => train.stopInfo.confirmed).length,
|
||||
unconfirmed: uniqueScheduledTrains.filter((train) => !train.stopInfo.confirmed).length
|
||||
};
|
||||
}
|
||||
|
||||
if (savedStation) savedStation.onlineInfo = onlineInfo;
|
||||
else
|
||||
this.stationList.push({
|
||||
name: onlineInfo.name,
|
||||
onlineInfo: onlineInfo
|
||||
});
|
||||
|
||||
acc.push(onlineInfo);
|
||||
|
||||
return acc;
|
||||
}, [] as OnlineScenery[]);
|
||||
|
||||
// Reset online info of already offline sceneries
|
||||
this.stationList
|
||||
.filter(
|
||||
(station) =>
|
||||
station.onlineInfo &&
|
||||
onlineSceneries.findIndex(
|
||||
(os) => os.region == station.onlineInfo!.region && station.name == os.name
|
||||
) != -1
|
||||
)
|
||||
.forEach((station) => (station.onlineInfo = undefined));
|
||||
return allActiveSceneries;
|
||||
},
|
||||
|
||||
async changeRegion(region: StoreState['region']) {
|
||||
this.region = region;
|
||||
stationList(): Station[] {
|
||||
const apiStore = useApiStore();
|
||||
|
||||
return apiStore.sceneryData.map((scenery) => {
|
||||
const routes = scenery.routesInfo.reduce(
|
||||
(acc, route) => {
|
||||
const tracksKey = route.routeTracks == 2 ? 'twoWay' : 'oneWay';
|
||||
const isElectric = route.isElectric;
|
||||
const routesKey: keyof StationRoutes = `${tracksKey}${
|
||||
!isElectric ? 'No' : ''
|
||||
}CatenaryRouteNames`;
|
||||
|
||||
if (!route.isInternal) acc[routesKey].push(route.routeName);
|
||||
if (route.isRouteSBL) acc['sblRouteNames'].push(route.routeName);
|
||||
|
||||
acc[tracksKey].push(route);
|
||||
|
||||
return acc;
|
||||
},
|
||||
{
|
||||
oneWay: [],
|
||||
oneWayCatenaryRouteNames: [],
|
||||
oneWayNoCatenaryRouteNames: [],
|
||||
twoWay: [],
|
||||
twoWayCatenaryRouteNames: [],
|
||||
twoWayNoCatenaryRouteNames: [],
|
||||
sblRouteNames: []
|
||||
} as StationRoutes
|
||||
);
|
||||
|
||||
return {
|
||||
name: scenery.name,
|
||||
|
||||
generalInfo: {
|
||||
...scenery,
|
||||
authors: scenery.authors?.split(',').map((a) => a.trim()),
|
||||
routes: routes,
|
||||
checkpoints: scenery.checkpoints?.split(';') ?? []
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -48,6 +48,8 @@ const filterInitStates: Filter = {
|
||||
noSpaceStatus: false,
|
||||
unavailableStatus: false,
|
||||
unsignedStatus: false,
|
||||
withActiveTimetables: false,
|
||||
withoutActiveTimetables: false,
|
||||
|
||||
authors: '',
|
||||
|
||||
@@ -73,7 +75,7 @@ export const useStationFiltersStore = defineStore('stationFiltersStore', {
|
||||
const store = useMainStore();
|
||||
const savedStationNames = store.stationList.map((s) => s.name);
|
||||
|
||||
const onlineUnsavedStations = store.onlineSceneryList
|
||||
const onlineUnsavedStations = store.activeSceneryList
|
||||
.filter((os) => !savedStationNames.includes(os.name) && os.region == store.region.id)
|
||||
.map((os) => ({
|
||||
name: os.name,
|
||||
@@ -85,7 +87,8 @@ export const useStationFiltersStore = defineStore('stationFiltersStore', {
|
||||
...onlineUnsavedStations,
|
||||
...store.stationList.map((station) => ({
|
||||
...station,
|
||||
onlineInfo: store.onlineSceneryList.find(
|
||||
// append to 'onlineInfo' object for filtering legacy reasons - to optimize later (hopefully)
|
||||
onlineInfo: store.activeSceneryList.find(
|
||||
(os) => os.name == station.name && os.region == store.region.id
|
||||
)
|
||||
}))
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import Station from '../scripts/interfaces/Station';
|
||||
import { API } from '../typings/api';
|
||||
import { Status } from '../typings/common';
|
||||
|
||||
@@ -36,6 +35,7 @@ export interface StationRoutesInfo {
|
||||
routeLength: number;
|
||||
routeSpeed: number;
|
||||
routeTracks: number;
|
||||
hidden?: boolean;
|
||||
}
|
||||
|
||||
export interface StationJSONData {
|
||||
@@ -45,6 +45,7 @@ export interface StationJSONData {
|
||||
lines: string;
|
||||
project: string;
|
||||
projectUrl: string;
|
||||
hash: string;
|
||||
|
||||
reqLevel: number;
|
||||
|
||||
@@ -62,7 +63,7 @@ export interface StationJSONData {
|
||||
availability: Availability;
|
||||
}
|
||||
|
||||
export interface OnlineScenery {
|
||||
export interface ActiveScenery {
|
||||
name: string;
|
||||
hash: string;
|
||||
region: string;
|
||||
|
||||
+37
-83
@@ -1,6 +1,5 @@
|
||||
import Station from '../scripts/interfaces/Station';
|
||||
import Train from '../scripts/interfaces/Train';
|
||||
import { API } from '../typings/api';
|
||||
import { ScheduledTrain, StationTrain, StopStatus, TrainStop } from './typings';
|
||||
|
||||
export function getLocoURL(locoType: string): string {
|
||||
@@ -102,51 +101,33 @@ export function getCheckpointTrain(
|
||||
let prevStationName = '',
|
||||
nextStationName = '';
|
||||
|
||||
let prevDepartureLine: string | null = null,
|
||||
nextArrivalLine: string | null = null;
|
||||
|
||||
for (let i = trainStopIndex - 1; i >= 0; i--) {
|
||||
if (/strong|podg/g.test(followingStops[i].stopName)) {
|
||||
prevStationName = followingStops[i].stopNameRAW.replace(/,.*/g, '');
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = trainStopIndex + 1; i < followingStops.length; i++) {
|
||||
if (/strong|podg/g.test(followingStops[i].stopName)) {
|
||||
nextStationName = followingStops[i].stopNameRAW.replace(/,.*/g, '');
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let departureLine: string | null = null;
|
||||
let arrivingLine: string | null = null;
|
||||
|
||||
for (let i = trainStopIndex; i < followingStops.length; i++) {
|
||||
const currentStop = followingStops[i];
|
||||
let prevDepartureLine: string | null = null,
|
||||
nextArrivalLine: string | null = null;
|
||||
|
||||
if (currentStop.departureLine == null) continue;
|
||||
for (let i = trainStopIndex; i >= 0; i--) {
|
||||
const stop = followingStops[i];
|
||||
|
||||
if (!/-|_|it|sbl/gi.test(currentStop.departureLine)) {
|
||||
departureLine = currentStop.departureLine;
|
||||
nextArrivalLine = followingStops[i + 1]?.arrivalLine || null;
|
||||
if (/strong|podg\.|pe\./g.test(stop.stopName) && !prevStationName && i <= trainStopIndex - 1)
|
||||
prevStationName = stop.stopNameRAW.replace(/,.*/g, '');
|
||||
|
||||
break;
|
||||
if (stop.arrivalLine != null && !arrivingLine && !/-|_|it|sbl/gi.test(stop.arrivalLine)) {
|
||||
arrivingLine = stop.arrivalLine;
|
||||
prevDepartureLine = followingStops[i - 1]?.departureLine || null;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = trainStopIndex; i >= 0; i--) {
|
||||
const currentStop = followingStops[i];
|
||||
for (let i = trainStopIndex; i < followingStops.length; i++) {
|
||||
const stop = followingStops[i];
|
||||
|
||||
if (currentStop.arrivalLine == null) continue;
|
||||
if (/strong|podg\.|pe\./g.test(stop.stopName) && !nextStationName && i > trainStopIndex)
|
||||
nextStationName = stop.stopNameRAW.replace(/,.*/g, '');
|
||||
|
||||
if (!/-|_|it|sbl/gi.test(currentStop.arrivalLine)) {
|
||||
arrivingLine = currentStop.arrivalLine;
|
||||
prevDepartureLine = followingStops[i - 1]?.departureLine || null;
|
||||
|
||||
break;
|
||||
if (stop.departureLine && !departureLine && !/-|_|it|sbl/gi.test(stop.departureLine)) {
|
||||
departureLine = stop.departureLine;
|
||||
nextArrivalLine = followingStops[i + 1]?.arrivalLine || null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,8 +158,8 @@ export function getCheckpointTrain(
|
||||
|
||||
region: train.region,
|
||||
|
||||
arrivingLine,
|
||||
departureLine,
|
||||
arrivingLine: arrivingLine,
|
||||
departureLine: departureLine,
|
||||
|
||||
nextArrivalLine,
|
||||
prevDepartureLine
|
||||
@@ -187,58 +168,33 @@ export function getCheckpointTrain(
|
||||
|
||||
export function getScheduledTrains(
|
||||
trainList: Train[],
|
||||
sceneryData: API.ActiveSceneries.Data,
|
||||
stationGeneralInfo: Station['generalInfo']
|
||||
stationGeneralInfo: Station['generalInfo'],
|
||||
stationName: string,
|
||||
region: string
|
||||
// sceneryData: API.ActiveSceneries.Data,
|
||||
): ScheduledTrain[] {
|
||||
const stationNameLower = sceneryData.stationName.toLocaleLowerCase();
|
||||
|
||||
stationGeneralInfo?.checkpoints.forEach((cp) => (cp.scheduledTrains.length = 0));
|
||||
// stationGeneralInfo?.checkpoints.forEach((cp) => (cp.scheduledTrains.length = 0));
|
||||
|
||||
return trainList.reduce((acc: ScheduledTrain[], train) => {
|
||||
if (!train.timetableData) return acc;
|
||||
if (train.region != region) return acc;
|
||||
|
||||
const timetable = train.timetableData;
|
||||
if (!timetable.sceneries.includes(sceneryData.stationHash)) return acc;
|
||||
if (!timetable.sceneryNames.includes(stationName)) return acc;
|
||||
|
||||
const stopInfoIndex = timetable.followingStops.findIndex((stop) => {
|
||||
const stopNameLower = stop.stopNameRAW.toLocaleLowerCase();
|
||||
|
||||
return (
|
||||
stationNameLower == stopNameLower ||
|
||||
(!/(po\.|podg\.)/.test(stopNameLower) && stopNameLower.includes(stationNameLower)) ||
|
||||
(!/(po\.|podg\.)/.test(stationNameLower) && stationNameLower.includes(stopNameLower)) ||
|
||||
(stopNameLower.split(', podg.')[0] !== undefined &&
|
||||
stationNameLower.startsWith(stopNameLower.split(', podg.')[0]))
|
||||
);
|
||||
});
|
||||
const checkpoints = [stationName];
|
||||
if (stationGeneralInfo?.checkpoints) checkpoints.push(...stationGeneralInfo.checkpoints);
|
||||
|
||||
const checkpointScheduledTrains: ScheduledTrain[] = [];
|
||||
|
||||
if (stopInfoIndex != -1) {
|
||||
checkpointScheduledTrains.push(
|
||||
getCheckpointTrain(train, stopInfoIndex, sceneryData.stationName)
|
||||
);
|
||||
}
|
||||
|
||||
stationGeneralInfo?.checkpoints?.forEach((checkpoint) => {
|
||||
// if (checkpoint.checkpointName.toLocaleLowerCase() == stationNameLower) return;
|
||||
|
||||
for (let i = 0; i < timetable.followingStops.length; i++) {
|
||||
if (
|
||||
checkpointScheduledTrains.findIndex(
|
||||
(cpTrain) =>
|
||||
cpTrain.checkpointName.toLocaleLowerCase() ==
|
||||
checkpoint.checkpointName.toLocaleLowerCase()
|
||||
) != -1
|
||||
)
|
||||
return;
|
||||
|
||||
const index = timetable.followingStops.findIndex(
|
||||
(stop) => stop.stopNameRAW.toLowerCase() == checkpoint.checkpointName.toLowerCase()
|
||||
);
|
||||
|
||||
if (index > -1)
|
||||
checkpointScheduledTrains.push(getCheckpointTrain(train, index, sceneryData.stationName));
|
||||
});
|
||||
new RegExp(`^(${checkpoints.join('|')})$`, 'i').test(
|
||||
timetable.followingStops[i].stopNameRAW
|
||||
)
|
||||
) {
|
||||
checkpointScheduledTrains.push(getCheckpointTrain(train, i, stationName));
|
||||
}
|
||||
}
|
||||
|
||||
acc.push(...checkpointScheduledTrains);
|
||||
return acc;
|
||||
@@ -249,14 +205,12 @@ export function getStationTrains(
|
||||
trainList: Train[],
|
||||
scheduledTrainList: ScheduledTrain[],
|
||||
region: string,
|
||||
sceneryData: API.ActiveSceneries.Data
|
||||
stationName: string
|
||||
): StationTrain[] {
|
||||
return trainList
|
||||
.filter(
|
||||
(train) =>
|
||||
train?.region === region &&
|
||||
train.online &&
|
||||
train.currentStationName === sceneryData.stationName
|
||||
train?.region === region && train.online && train.currentStationName === stationName
|
||||
)
|
||||
.map((train) => ({
|
||||
driverName: train.driverName,
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
overflow-y: auto;
|
||||
height: 90vh;
|
||||
min-height: 550px;
|
||||
margin-top: 0.5em;
|
||||
|
||||
padding-right: 0.2em;
|
||||
}
|
||||
@@ -24,7 +25,7 @@
|
||||
text-align: end;
|
||||
|
||||
padding: 0.25em;
|
||||
margin: 0.5em 0;
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
.journal_warning {
|
||||
@@ -53,9 +54,7 @@
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 0.5em;
|
||||
|
||||
position: relative;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.btn--load-data {
|
||||
|
||||
@@ -2,24 +2,35 @@
|
||||
@import 'responsive.scss';
|
||||
|
||||
.stats-tab {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
z-index: 99;
|
||||
|
||||
transform: translateY(1em);
|
||||
|
||||
width: 100%;
|
||||
|
||||
background-color: #1a1a1a;
|
||||
box-shadow: 0 0 5px 1px $accentCol;
|
||||
|
||||
padding: 1em;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
margin-bottom: 0.5em;
|
||||
hr.header-separator {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
width: 100%;
|
||||
hr.section-separator {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.info-stats {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 0.5em;
|
||||
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.stat-badge {
|
||||
|
||||
@@ -30,7 +30,8 @@
|
||||
top: calc(100% + 0.5em);
|
||||
|
||||
background-color: $bgCol;
|
||||
box-shadow: 0 5px 10px 2px #0f0f0f;
|
||||
// box-shadow: 0 5px 10px 2px #0f0f0f;
|
||||
box-shadow: 0 0 5px 1px $accentCol;
|
||||
|
||||
width: 100%;
|
||||
max-width: 550px;
|
||||
|
||||
@@ -5,11 +5,6 @@
|
||||
.actions-bar {
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.filters-options {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
h1.option-title {
|
||||
@@ -57,6 +52,7 @@ h1.option-title {
|
||||
|
||||
.sort-option[data-selected='true'] {
|
||||
color: $accentCol;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.filter-option {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
@font-face {
|
||||
font-family: 'Quicksand';
|
||||
src:
|
||||
url('/fonts/Quicksand-Bold.woff2') format('woff2'),
|
||||
url('/fonts/Quicksand-Bold.woff') format('woff');
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Quicksand';
|
||||
src:
|
||||
url('/fonts/Quicksand-SemiBold.woff2') format('woff2'),
|
||||
url('/fonts/Quicksand-SemiBold.woff') format('woff');
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Quicksand';
|
||||
src:
|
||||
url('/fonts/Quicksand-Medium.woff2') format('woff2'),
|
||||
url('/fonts/Quicksand-Medium.woff') format('woff');
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Quicksand';
|
||||
src:
|
||||
url('/fonts/Quicksand-Regular.woff2') format('woff2'),
|
||||
url('/fonts/Quicksand-Regular.woff') format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Quicksand';
|
||||
src:
|
||||
url('/fonts/Quicksand-Light.woff2') format('woff2'),
|
||||
url('/fonts/Quicksand-Light.woff') format('woff');
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
@import 'fonts.scss';
|
||||
|
||||
:root {
|
||||
--clr-primary: #ffc014;
|
||||
--clr-secondary: #2f2f2f;
|
||||
@@ -11,7 +13,7 @@
|
||||
--clr-skr: #ff5100;
|
||||
--clr-twr: #ffbb00;
|
||||
|
||||
--clr-error: #df3e3e;
|
||||
--clr-error: #fa3636;
|
||||
--clr-warning: #c59429;
|
||||
|
||||
--clr-donator: #f7a4ff;
|
||||
@@ -158,6 +160,10 @@ ul {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
&--error {
|
||||
color: var(--clr-error);
|
||||
}
|
||||
|
||||
&--donator {
|
||||
color: var(--clr-donator);
|
||||
text-shadow: var(--clr-donator) 0 0 10px;
|
||||
@@ -183,7 +189,7 @@ a.a-button {
|
||||
&[data-disabled='true'] {
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
opacity: 0.85;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
&.btn--filled {
|
||||
|
||||
+76
-19
@@ -1,10 +1,22 @@
|
||||
import { Status } from './common';
|
||||
|
||||
export enum APIDataStatus {
|
||||
OK = 'OK',
|
||||
WARNING = 'WARNING'
|
||||
}
|
||||
export namespace API {
|
||||
export namespace ActiveData {
|
||||
export interface APIStatuses {
|
||||
stationsAPI: APIDataStatus;
|
||||
trainsAPI: APIDataStatus;
|
||||
dispatchersAPI: APIDataStatus;
|
||||
sceneryRequirementsAPI: APIDataStatus;
|
||||
}
|
||||
|
||||
export interface Response {
|
||||
activeSceneries?: API.ActiveSceneries.Response;
|
||||
trains?: API.ActiveTrains.Response;
|
||||
apiStatuses?: APIStatuses;
|
||||
}
|
||||
}
|
||||
export namespace DispatcherHistory {
|
||||
@@ -31,7 +43,11 @@ export namespace API {
|
||||
|
||||
export namespace DispatcherStats {
|
||||
export interface DistanceStat {
|
||||
routeDistance: number;
|
||||
routeDistance: number | null;
|
||||
}
|
||||
|
||||
export interface DurationStat {
|
||||
currentDuration: number | null;
|
||||
}
|
||||
|
||||
export interface Count {
|
||||
@@ -39,11 +55,18 @@ export namespace API {
|
||||
}
|
||||
|
||||
export interface Response {
|
||||
_sum: DistanceStat;
|
||||
_max: DistanceStat;
|
||||
_min: DistanceStat;
|
||||
_avg: DistanceStat;
|
||||
_count: Count;
|
||||
services: {
|
||||
count: number;
|
||||
durationMax: number;
|
||||
durationAvg: number;
|
||||
} | null;
|
||||
|
||||
issuedTimetables: {
|
||||
count: number;
|
||||
distanceMax: number;
|
||||
distanceAvg: number;
|
||||
distanceSum: number;
|
||||
} | null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,21 +286,47 @@ export namespace API {
|
||||
distanceAvg: number;
|
||||
maxTimetable: API.TimetableHistory.Data | null;
|
||||
|
||||
mostActiveDispatchers: {
|
||||
name: string;
|
||||
count: number;
|
||||
}[];
|
||||
globalDiff: GlobalDiff;
|
||||
globalMax: GlobalMax;
|
||||
|
||||
mostActiveDrivers: {
|
||||
name: string;
|
||||
distance: number;
|
||||
}[];
|
||||
mostActiveDispatchers: MostActiveDispatcher[];
|
||||
mostActiveDrivers: MostActiveDriver[];
|
||||
|
||||
longestDuties: {
|
||||
name: string;
|
||||
duration: number;
|
||||
station: string;
|
||||
}[];
|
||||
longestDuties: LongestDuty[];
|
||||
}
|
||||
|
||||
export interface MostActiveDispatcher {
|
||||
name: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface MostActiveDriver {
|
||||
name: string;
|
||||
distance: number;
|
||||
}
|
||||
|
||||
export interface LongestDuty {
|
||||
name: string;
|
||||
duration: number;
|
||||
station: string;
|
||||
}
|
||||
|
||||
export interface GlobalDiff {
|
||||
rippedSwitches: number;
|
||||
derailments: number;
|
||||
skippedStopSignals: number;
|
||||
radioStops: number;
|
||||
kills: number;
|
||||
drivenKilometers: number;
|
||||
routedTrains: number;
|
||||
}
|
||||
|
||||
export interface GlobalMax {
|
||||
_max: {
|
||||
drivers: number;
|
||||
dispatchers: number;
|
||||
timetables: number;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,3 +380,11 @@ export namespace GithubAPI {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export namespace Websocket {
|
||||
export interface Payload {
|
||||
activeSceneries: API.ActiveSceneries.Response;
|
||||
activeTrains: API.ActiveTrains.Response;
|
||||
connectedSocketCount: number;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export namespace Status {
|
||||
export enum ActiveDispatcher {
|
||||
FREE = -3,
|
||||
INVALID = -2,
|
||||
UNKNOWN = -1,
|
||||
NO_LIMIT = 0,
|
||||
@@ -11,7 +12,7 @@ export namespace Status {
|
||||
}
|
||||
|
||||
export enum Data {
|
||||
Offline = 2,
|
||||
Offline = -2,
|
||||
Initialized = -1,
|
||||
Loading = 0,
|
||||
Error = 1,
|
||||
|
||||
@@ -3,15 +3,19 @@
|
||||
<JournalHeader />
|
||||
|
||||
<div class="journal_wrapper">
|
||||
<JournalOptions
|
||||
@on-search-confirm="fetchHistoryData"
|
||||
@on-options-reset="resetOptions"
|
||||
@on-refresh-data="fetchHistoryData(true)"
|
||||
:sorter-option-ids="['timestampFrom', 'duration']"
|
||||
:data-status="dataStatus"
|
||||
:current-options-active="currentOptionsActive"
|
||||
optionsType="dispatchers"
|
||||
/>
|
||||
<div class="journal_top-bar">
|
||||
<JournalOptions
|
||||
@on-search-confirm="fetchHistoryData"
|
||||
@on-options-reset="resetOptions"
|
||||
@on-refresh-data="fetchHistoryData(true)"
|
||||
:sorter-option-ids="['timestampFrom', 'duration']"
|
||||
:data-status="dataStatus"
|
||||
:current-options-active="currentOptionsActive"
|
||||
optionsType="dispatchers"
|
||||
/>
|
||||
|
||||
<JournalStats :statsButtons="statsButtons" />
|
||||
</div>
|
||||
|
||||
<div class="journal_refreshed-date" v-if="dataRefreshedAt">
|
||||
{{ $t('journal.data-refreshed-at') }}: {{ dataRefreshedAt.toLocaleString($i18n.locale) }}
|
||||
@@ -33,22 +37,33 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, provide, reactive, Ref, ref } from 'vue';
|
||||
|
||||
import JournalOptions from '../components/JournalView/JournalOptions.vue';
|
||||
import http from '../http';
|
||||
import { useMainStore } from '../store/mainStore';
|
||||
import JournalDispatchersList from '../components/JournalView/JournalDispatchersList.vue';
|
||||
|
||||
import JournalHeader from '../components/JournalView/JournalHeader.vue';
|
||||
import { LocationQuery } from 'vue-router';
|
||||
import { Journal } from '../components/JournalView/typings';
|
||||
import { API } from '../typings/api';
|
||||
import { Status } from '../typings/common';
|
||||
import http from '../http';
|
||||
|
||||
import JournalDispatchersList from '../components/JournalView/JournalDispatchers/JournalDispatchersList.vue';
|
||||
import JournalOptions from '../components/JournalView/JournalOptions.vue';
|
||||
import JournalHeader from '../components/JournalView/JournalHeader.vue';
|
||||
import JournalStats from '../components/JournalView/JournalStats.vue';
|
||||
|
||||
const statsButtons: Journal.StatsButton[] = [
|
||||
{
|
||||
tab: Journal.StatsTab.DISPATCHER_STATS,
|
||||
localeKey: 'journal.dispatcher-stats.button',
|
||||
iconName: 'user',
|
||||
disabled: true
|
||||
}
|
||||
];
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
JournalOptions,
|
||||
JournalDispatchersList,
|
||||
JournalHeader
|
||||
JournalHeader,
|
||||
JournalStats,
|
||||
JournalDispatchersList
|
||||
},
|
||||
name: 'JournalDispatchers',
|
||||
|
||||
@@ -65,6 +80,8 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
statsButtons,
|
||||
|
||||
currentQuery: '',
|
||||
currentQueryArray: [] as string[],
|
||||
dataRefreshedAt: null as Date | null,
|
||||
@@ -89,7 +106,7 @@ export default defineComponent({
|
||||
'search-dispatcher': '',
|
||||
'search-station': '',
|
||||
'search-date': ''
|
||||
} as Journal.DispatcherSearcher);
|
||||
} as Journal.DispatcherSearchType);
|
||||
|
||||
const countFromIndex = ref(0);
|
||||
const countLimit = 15;
|
||||
@@ -102,7 +119,7 @@ export default defineComponent({
|
||||
const scrollElement: Ref<HTMLElement | null> = ref(null);
|
||||
|
||||
return {
|
||||
store: useMainStore(),
|
||||
mainStore: useMainStore(),
|
||||
|
||||
sorterActive,
|
||||
searchersValues,
|
||||
@@ -120,6 +137,15 @@ export default defineComponent({
|
||||
this.currentOptionsActive =
|
||||
q.length > 2 ||
|
||||
q.some((qv) => qv.startsWith('sortBy=') && qv.split('=')[1] != 'timestampFrom');
|
||||
},
|
||||
|
||||
'mainStore.dispatcherStatsData'(stats) {
|
||||
this.statsButtons.find((sb) => sb.tab == Journal.StatsTab.DISPATCHER_STATS)!.disabled =
|
||||
stats === undefined;
|
||||
},
|
||||
|
||||
async 'mainStore.dispatcherStatsName'() {
|
||||
this.fetchDispatcherStats();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -142,6 +168,16 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleRouteParams() {
|
||||
this.$router.push({
|
||||
query: {
|
||||
'search-date': this.searchersValues['search-date'] || undefined,
|
||||
'search-station': this.searchersValues['search-station'] || undefined,
|
||||
'search-dispatcher': this.searchersValues['search-dispatcher'] || undefined
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
handleScroll(e: Event) {
|
||||
const listElement = e.target as HTMLElement;
|
||||
const scrollTop = listElement.scrollTop;
|
||||
@@ -154,24 +190,44 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
handleQueries(query: LocationQuery) {
|
||||
const queryKeys = Object.keys(query);
|
||||
|
||||
if (queryKeys.includes('sceneryName')) this.setSearchers('', `${query.sceneryName}`, '');
|
||||
if (queryKeys.includes('dispatcherName'))
|
||||
this.setSearchers('', '', `${query.dispatcherName}`);
|
||||
this.setOptions(query as any);
|
||||
},
|
||||
|
||||
setSearchers(date: string, station: string, dispatcher: string) {
|
||||
this.searchersValues['search-date'] = date;
|
||||
this.searchersValues['search-station'] = station;
|
||||
this.searchersValues['search-dispatcher'] = dispatcher;
|
||||
async fetchDispatcherStats() {
|
||||
if (!this.mainStore.dispatcherStatsName) {
|
||||
this.mainStore.dispatcherStatsData = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const statsData: API.DispatcherStats.Response = await (
|
||||
await http.get('api/getDispatcherStats', {
|
||||
params: {
|
||||
name: this.mainStore.dispatcherStatsName
|
||||
}
|
||||
})
|
||||
).data;
|
||||
|
||||
this.mainStore.dispatcherStatsData = statsData;
|
||||
} catch (error) {
|
||||
this.mainStore.dispatcherStatsData = undefined;
|
||||
|
||||
console.error('Ups! Wystąpił błąd przy próbie pobrania statystyk dyżurnego! :/');
|
||||
}
|
||||
},
|
||||
|
||||
setOptions(options: { [key: string]: string }) {
|
||||
this.searchersValues['search-date'] = options['search-date'] ?? '';
|
||||
this.searchersValues['search-station'] = options['search-station'] ?? '';
|
||||
this.searchersValues['search-dispatcher'] = options['search-dispatcher'] ?? '';
|
||||
|
||||
this.sorterActive.id =
|
||||
(options['sorter-active'] as Journal.DispatcherSorterKey) ?? 'timestampFrom';
|
||||
},
|
||||
|
||||
resetOptions() {
|
||||
this.setSearchers('', '', '');
|
||||
this.setOptions({});
|
||||
this.sorterActive.id = 'timestampFrom';
|
||||
|
||||
this.fetchHistoryData();
|
||||
},
|
||||
|
||||
async addHistoryData() {
|
||||
@@ -241,7 +297,7 @@ export default defineComponent({
|
||||
this.historyList = responseData;
|
||||
|
||||
// Stats display
|
||||
this.store.dispatcherStatsName =
|
||||
this.mainStore.dispatcherStatsName =
|
||||
this.historyList.length > 0 && this.searchersValues['search-dispatcher'].trim()
|
||||
? this.historyList[0].dispatcherName
|
||||
: '';
|
||||
|
||||
+135
-72
@@ -3,18 +3,19 @@
|
||||
<JournalHeader />
|
||||
|
||||
<div class="journal_wrapper">
|
||||
<JournalOptions
|
||||
@on-search-confirm="fetchHistoryData"
|
||||
@on-options-reset="resetOptions"
|
||||
@on-refresh-data="fetchHistoryData"
|
||||
:sorter-option-ids="['timetableId', 'beginDate', 'routeDistance', 'allStopsCount']"
|
||||
:filters="journalTimetableFilters"
|
||||
:currentOptionsActive="currentOptionsActive"
|
||||
:data-status="dataStatus"
|
||||
optionsType="timetables"
|
||||
/>
|
||||
<div class="journal_top-bar">
|
||||
<JournalOptions
|
||||
@onOptionsReset="resetOptions"
|
||||
@onRefreshData="fetchHistoryData"
|
||||
:sorter-option-ids="['timetableId', 'beginDate', 'routeDistance', 'allStopsCount']"
|
||||
:filters="journalTimetableFilters"
|
||||
:currentOptionsActive="currentOptionsActive"
|
||||
:data-status="dataStatus"
|
||||
optionsType="timetables"
|
||||
/>
|
||||
|
||||
<JournalStats />
|
||||
<JournalStats :statsButtons="statsButtons" />
|
||||
</div>
|
||||
|
||||
<div class="journal_refreshed-date" v-if="dataRefreshedAt">
|
||||
{{ $t('journal.data-refreshed-at') }}: {{ dataRefreshedAt.toLocaleString($i18n.locale) }}
|
||||
@@ -56,45 +57,58 @@ import http from '../http';
|
||||
|
||||
export const journalTimetableFilters: Journal.TimetableFilter[] = [
|
||||
{
|
||||
id: Journal.TimetableFilterId.ALL,
|
||||
id: Journal.TimetableFilterId.ALL_STATUSES,
|
||||
filterSection: Journal.FilterSection.TIMETABLE_STATUS,
|
||||
isActive: true
|
||||
isActive: true,
|
||||
default: true
|
||||
},
|
||||
|
||||
{
|
||||
id: Journal.TimetableFilterId.ACTIVE,
|
||||
filterSection: Journal.FilterSection.TIMETABLE_STATUS,
|
||||
isActive: false
|
||||
isActive: false,
|
||||
default: false
|
||||
},
|
||||
|
||||
{
|
||||
id: Journal.TimetableFilterId.FULFILLED,
|
||||
filterSection: Journal.FilterSection.TIMETABLE_STATUS,
|
||||
isActive: false
|
||||
isActive: false,
|
||||
default: false
|
||||
},
|
||||
|
||||
{
|
||||
id: Journal.TimetableFilterId.ABANDONED,
|
||||
filterSection: Journal.FilterSection.TIMETABLE_STATUS,
|
||||
isActive: false
|
||||
isActive: false,
|
||||
default: false
|
||||
},
|
||||
|
||||
{
|
||||
id: Journal.TimetableFilterId.TWR_SKR,
|
||||
filterSection: Journal.FilterSection.TWRSKR,
|
||||
isActive: true
|
||||
id: Journal.TimetableFilterId.ALL_SPECIALS,
|
||||
filterSection: Journal.FilterSection.SPECIAL,
|
||||
isActive: true,
|
||||
default: true
|
||||
},
|
||||
|
||||
{
|
||||
id: Journal.TimetableFilterId.TWR,
|
||||
filterSection: Journal.FilterSection.TWRSKR,
|
||||
isActive: false
|
||||
filterSection: Journal.FilterSection.SPECIAL,
|
||||
isActive: false,
|
||||
default: false
|
||||
},
|
||||
|
||||
{
|
||||
id: Journal.TimetableFilterId.SKR,
|
||||
filterSection: Journal.FilterSection.TWRSKR,
|
||||
isActive: false
|
||||
filterSection: Journal.FilterSection.SPECIAL,
|
||||
isActive: false,
|
||||
default: false
|
||||
},
|
||||
{
|
||||
id: Journal.TimetableFilterId.TWR_SKR,
|
||||
filterSection: Journal.FilterSection.SPECIAL,
|
||||
isActive: false,
|
||||
default: false
|
||||
}
|
||||
];
|
||||
|
||||
@@ -104,8 +118,12 @@ interface TimetablesQueryParams {
|
||||
timetableId?: string;
|
||||
|
||||
authorName?: string;
|
||||
timestampFrom?: number;
|
||||
timestampTo?: number;
|
||||
// timestampFrom?: number;
|
||||
// timestampTo?: number;
|
||||
|
||||
dateFrom?: string;
|
||||
dateTo?: string;
|
||||
|
||||
issuedFrom?: string;
|
||||
|
||||
countFrom?: number;
|
||||
@@ -138,6 +156,24 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
journalTimetableFilters,
|
||||
mainStore: useMainStore(),
|
||||
|
||||
statsButtons: [
|
||||
{
|
||||
tab: Journal.StatsTab.DAILY_STATS,
|
||||
localeKey: 'journal.daily-stats.button',
|
||||
iconName: 'stats',
|
||||
disabled: false
|
||||
},
|
||||
{
|
||||
tab: Journal.StatsTab.DRIVER_STATS,
|
||||
localeKey: 'journal.driver-stats.button',
|
||||
iconName: 'train',
|
||||
disabled: true
|
||||
}
|
||||
],
|
||||
|
||||
currentQueryParams: {} as TimetablesQueryParams,
|
||||
dataRefreshedAt: null as Date | null,
|
||||
|
||||
@@ -149,7 +185,6 @@ export default defineComponent({
|
||||
currentOptionsActive: false,
|
||||
|
||||
timetableHistory: [] as API.TimetableHistory.Response,
|
||||
journalTimetableFilters,
|
||||
|
||||
dataStatus: Status.Data.Loading,
|
||||
dataErrorMessage: ''
|
||||
@@ -157,10 +192,11 @@ export default defineComponent({
|
||||
|
||||
setup() {
|
||||
const sorterActive: Journal.TimetableSorter = reactive({ id: 'timetableId', dir: 'desc' });
|
||||
// const journalFilterActive = ref(journalTimetableFilters[0]);
|
||||
|
||||
const initFilters: readonly Journal.TimetableFilter[] = JSON.parse(
|
||||
JSON.stringify(journalTimetableFilters)
|
||||
);
|
||||
|
||||
const filterList: Journal.TimetableFilter[] = reactive(JSON.parse(JSON.stringify(initFilters)));
|
||||
|
||||
const searchersValues = reactive({
|
||||
@@ -189,15 +225,22 @@ export default defineComponent({
|
||||
countFromIndex,
|
||||
countLimit,
|
||||
|
||||
scrollElement,
|
||||
|
||||
store: useMainStore()
|
||||
scrollElement
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
currentQueryParams(q: TimetablesQueryParams) {
|
||||
this.currentOptionsActive = Object.values(q).some((v) => v !== undefined);
|
||||
},
|
||||
|
||||
'mainStore.driverStatsData'(driverStats) {
|
||||
this.statsButtons.find((sb) => sb.tab == Journal.StatsTab.DRIVER_STATS)!.disabled =
|
||||
driverStats === undefined;
|
||||
},
|
||||
|
||||
async 'mainStore.driverStatsName'() {
|
||||
this.fetchDriverStats();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -225,42 +268,51 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
handleQueries(query: LocationQuery) {
|
||||
const queryKeys = Object.keys(query);
|
||||
|
||||
if (queryKeys.includes('timetableId'))
|
||||
this.setSearchers('', '', `#${query.timetableId}`, '', '');
|
||||
if (queryKeys.includes('issuedFrom'))
|
||||
this.setSearchers('', '', '', '', `${query.issuedFrom}`);
|
||||
if (queryKeys.includes('authorName'))
|
||||
this.setSearchers('', '', '', `${query.authorName}`, '');
|
||||
this.setOptions(query as any);
|
||||
},
|
||||
|
||||
setSearchers(
|
||||
date: string,
|
||||
driver: string,
|
||||
train: string,
|
||||
dispatcher: string,
|
||||
issuedFrom: string
|
||||
) {
|
||||
this.searchersValues['search-date'] = date;
|
||||
this.searchersValues['search-driver'] = driver;
|
||||
this.searchersValues['search-train'] = train;
|
||||
this.searchersValues['search-dispatcher'] = dispatcher;
|
||||
this.searchersValues['search-issuedFrom'] = issuedFrom;
|
||||
async fetchDriverStats() {
|
||||
if (!this.mainStore.driverStatsName) {
|
||||
this.mainStore.driverStatsData = undefined;
|
||||
this.mainStore.driverStatsStatus = Status.Data.Initialized;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.mainStore.driverStatsStatus = Status.Data.Loading;
|
||||
|
||||
const statsData: API.DriverStats.Response = await (
|
||||
await http.get(`api/getDriverInfo?name=${this.mainStore.driverStatsName}`)
|
||||
).data;
|
||||
|
||||
this.mainStore.driverStatsData = statsData;
|
||||
this.mainStore.driverStatsStatus = Status.Data.Loaded;
|
||||
} catch (error) {
|
||||
this.mainStore.driverStatsData = undefined;
|
||||
this.mainStore.driverStatsStatus = Status.Data.Error;
|
||||
console.error('Ups! Wystąpił błąd przy próbie pobrania statystyk maszynisty! :/');
|
||||
}
|
||||
},
|
||||
|
||||
setOptions(options: { [key: string]: string }) {
|
||||
this.searchersValues['search-date'] = options['search-date'] ?? '';
|
||||
this.searchersValues['search-driver'] = options['search-driver'] ?? '';
|
||||
this.searchersValues['search-train'] = options['search-train'] ?? '';
|
||||
this.searchersValues['search-dispatcher'] = options['search-dispatcher'] ?? '';
|
||||
this.searchersValues['search-issuedFrom'] = options['search-issuedFrom'] ?? '';
|
||||
|
||||
this.sorterActive.id =
|
||||
(options['sorter-active'] as Journal.TimetableSorterKey) ?? 'timetableId';
|
||||
|
||||
this.filterList.forEach((f) => {
|
||||
f.isActive =
|
||||
options[f.filterSection] === f.id ||
|
||||
(options[f.filterSection] === undefined && f.default);
|
||||
});
|
||||
},
|
||||
|
||||
resetOptions() {
|
||||
this.setSearchers('', '', '', '', '');
|
||||
|
||||
this.sorterActive.id = 'timetableId';
|
||||
|
||||
this.filterList.forEach(
|
||||
(f) =>
|
||||
(f.isActive =
|
||||
this.initFilters.find((initFilter) => initFilter.id == f.id)?.isActive || false)
|
||||
);
|
||||
|
||||
this.fetchHistoryData();
|
||||
this.setOptions({});
|
||||
},
|
||||
|
||||
async addHistoryData() {
|
||||
@@ -289,13 +341,19 @@ export default defineComponent({
|
||||
const driverName = this.searchersValues['search-driver'].trim() || undefined;
|
||||
const trainNo = this.searchersValues['search-train'].trim() || undefined;
|
||||
const authorName = this.searchersValues['search-dispatcher'].trim() || undefined;
|
||||
const dateString = this.searchersValues['search-date'].trim() || undefined;
|
||||
const dateFrom = this.searchersValues['search-date'].trim() || undefined;
|
||||
const issuedFrom = this.searchersValues['search-issuedFrom'].trim() || undefined;
|
||||
|
||||
const timestampFrom = dateString
|
||||
? Date.parse(new Date(dateString).toISOString()) - 120 * 60 * 1000
|
||||
: undefined;
|
||||
const timestampTo = timestampFrom ? timestampFrom + 86400000 : undefined;
|
||||
let dateTo: string | undefined = undefined;
|
||||
|
||||
if (dateFrom) {
|
||||
const d = new Date(dateFrom);
|
||||
d.setDate(d.getDate() + 1);
|
||||
|
||||
dateTo = d.toISOString().split('T')[0];
|
||||
}
|
||||
// const timestampFrom = dateString ? Date.parse(new Date(dateString).toISOString()) : undefined;
|
||||
// const timestampTo = timestampFrom ? timestampFrom + 86400000 : undefined;
|
||||
|
||||
const queryParams: TimetablesQueryParams = {};
|
||||
|
||||
@@ -318,23 +376,28 @@ export default defineComponent({
|
||||
queryParams['fulfilled'] = 1;
|
||||
break;
|
||||
|
||||
case Journal.TimetableFilterId.ALL:
|
||||
case Journal.TimetableFilterId.ALL_STATUSES:
|
||||
queryParams['terminated'] = undefined;
|
||||
queryParams['fulfilled'] = undefined;
|
||||
break;
|
||||
|
||||
case Journal.TimetableFilterId.TWR_SKR:
|
||||
case Journal.TimetableFilterId.ALL_SPECIALS:
|
||||
queryParams['twr'] = undefined;
|
||||
queryParams['skr'] = undefined;
|
||||
break;
|
||||
|
||||
case Journal.TimetableFilterId.TWR:
|
||||
queryParams['twr'] = 1;
|
||||
queryParams['skr'] = undefined;
|
||||
queryParams['skr'] = 0;
|
||||
break;
|
||||
|
||||
case Journal.TimetableFilterId.SKR:
|
||||
queryParams['twr'] = undefined;
|
||||
queryParams['twr'] = 0;
|
||||
queryParams['skr'] = 1;
|
||||
break;
|
||||
|
||||
case Journal.TimetableFilterId.TWR_SKR:
|
||||
queryParams['twr'] = 1;
|
||||
queryParams['skr'] = 1;
|
||||
break;
|
||||
|
||||
@@ -349,8 +412,8 @@ export default defineComponent({
|
||||
queryParams['countLimit'] = undefined;
|
||||
|
||||
queryParams['authorName'] = authorName;
|
||||
queryParams['timestampFrom'] = timestampFrom;
|
||||
queryParams['timestampTo'] = timestampTo;
|
||||
queryParams['dateFrom'] = dateFrom;
|
||||
queryParams['dateTo'] = dateTo;
|
||||
queryParams['issuedFrom'] = issuedFrom;
|
||||
queryParams['sortBy'] =
|
||||
this.sorterActive.id != 'timetableId' ? this.sorterActive.id : undefined;
|
||||
|
||||
@@ -1,16 +1,5 @@
|
||||
<template>
|
||||
<div class="scenery-view">
|
||||
<!-- <div
|
||||
class="scenery-offline"
|
||||
v-if="!stationInfo && !onlineSceneryInfo && store.dataStatuses.sceneries == 2"
|
||||
>
|
||||
<div>{{ $t('scenery.no-scenery') }}</div>
|
||||
|
||||
<action-button>
|
||||
<router-link to="/">{{ $t('scenery.return-btn') }}</router-link>
|
||||
</action-button>
|
||||
</div> -->
|
||||
|
||||
<div class="scenery-wrapper" ref="card-wrapper">
|
||||
<div class="scenery-left">
|
||||
<div class="scenery-actions">
|
||||
@@ -43,7 +32,7 @@
|
||||
<div
|
||||
v-if="
|
||||
apiStore.dataStatuses.sceneries == Status.Loading ||
|
||||
apiStore.dataStatuses.trains == Status.Loading
|
||||
apiStore.dataStatuses.connection == Status.Loading
|
||||
"
|
||||
></div>
|
||||
|
||||
@@ -158,7 +147,7 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
onlineSceneryInfo() {
|
||||
return this.store.onlineSceneryList.find(
|
||||
return this.store.activeSceneryList.find(
|
||||
(scenery) =>
|
||||
scenery.name === this.station?.toString().replace(/_/g, ' ') &&
|
||||
scenery.region == this.store.region.id
|
||||
@@ -180,11 +169,7 @@ export default defineComponent({
|
||||
loadSelectedCheckpoint() {
|
||||
if (!this.stationInfo?.generalInfo?.checkpoints) return;
|
||||
if (this.stationInfo.generalInfo.checkpoints.length == 0) return;
|
||||
this.selectedCheckpoint = this.stationInfo.generalInfo.checkpoints[0].checkpointName;
|
||||
},
|
||||
|
||||
selectCheckpoint(cp: { checkpointName: string }) {
|
||||
this.selectedCheckpoint = cp.checkpointName;
|
||||
this.selectedCheckpoint = this.stationInfo.generalInfo.checkpoints[0];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
+4
-2
@@ -6,20 +6,22 @@ export default defineConfig({
|
||||
server: {
|
||||
port: 5001
|
||||
},
|
||||
publicDir: 'public',
|
||||
plugins: [
|
||||
vue(),
|
||||
VitePWA({
|
||||
registerType: 'autoUpdate',
|
||||
includeAssets: ['/images/*.png', '/fonts/*.woff', '/fonts/*.woff2'],
|
||||
|
||||
workbox: {
|
||||
disableDevLogs: true,
|
||||
globPatterns: ['**/*.{js,css,html,png,svg,jpg}'],
|
||||
runtimeCaching: [
|
||||
{
|
||||
urlPattern: new RegExp('^https://stacjownik.spythere.pl/api/getSceneries', 'i'),
|
||||
urlPattern: new RegExp('^https://stacjownik.spythere.eu/api/getSceneries', 'i'),
|
||||
handler: 'NetworkFirst',
|
||||
options: {
|
||||
cacheName: 'sceneries-cache',
|
||||
|
||||
cacheableResponse: {
|
||||
statuses: [0, 200]
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.10.tgz"
|
||||
integrity sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg==
|
||||
|
||||
"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.11.1", "@babel/core@^7.12.0", "@babel/core@^7.13.0", "@babel/core@^7.4.0-0":
|
||||
"@babel/core@^7.11.1":
|
||||
version "7.20.7"
|
||||
resolved "https://registry.npmjs.org/@babel/core/-/core-7.20.7.tgz"
|
||||
integrity sha512-t1ZjCluspe5DW24bn2Rr1CDb2v9rn/hROtg9a2tmd0+QYf4bsloYfLQzjG4qHPNMhWtKdGC33R5AxGR2Af2cBw==
|
||||
@@ -929,11 +929,116 @@
|
||||
resolved "https://registry.npmjs.org/@canvas/image-data/-/image-data-1.0.0.tgz"
|
||||
integrity sha512-BxOqI5LgsIQP1odU5KMwV9yoijleOPzHL18/YvNqF9KFSGF2K/DLlYAbDQsWqd/1nbaFuSkYD/191dpMtNh4vw==
|
||||
|
||||
"@esbuild/android-arm64@0.18.20":
|
||||
version "0.18.20"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz#984b4f9c8d0377443cc2dfcef266d02244593622"
|
||||
integrity sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==
|
||||
|
||||
"@esbuild/android-arm@0.18.20":
|
||||
version "0.18.20"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.18.20.tgz#fedb265bc3a589c84cc11f810804f234947c3682"
|
||||
integrity sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==
|
||||
|
||||
"@esbuild/android-x64@0.18.20":
|
||||
version "0.18.20"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.18.20.tgz#35cf419c4cfc8babe8893d296cd990e9e9f756f2"
|
||||
integrity sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==
|
||||
|
||||
"@esbuild/darwin-arm64@0.18.20":
|
||||
version "0.18.20"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz#08172cbeccf95fbc383399a7f39cfbddaeb0d7c1"
|
||||
integrity sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==
|
||||
|
||||
"@esbuild/darwin-x64@0.18.20":
|
||||
version "0.18.20"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz#d70d5790d8bf475556b67d0f8b7c5bdff053d85d"
|
||||
integrity sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==
|
||||
|
||||
"@esbuild/freebsd-arm64@0.18.20":
|
||||
version "0.18.20"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz#98755cd12707f93f210e2494d6a4b51b96977f54"
|
||||
integrity sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==
|
||||
|
||||
"@esbuild/freebsd-x64@0.18.20":
|
||||
version "0.18.20"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz#c1eb2bff03915f87c29cece4c1a7fa1f423b066e"
|
||||
integrity sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==
|
||||
|
||||
"@esbuild/linux-arm64@0.18.20":
|
||||
version "0.18.20"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz#bad4238bd8f4fc25b5a021280c770ab5fc3a02a0"
|
||||
integrity sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==
|
||||
|
||||
"@esbuild/linux-arm@0.18.20":
|
||||
version "0.18.20"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz#3e617c61f33508a27150ee417543c8ab5acc73b0"
|
||||
integrity sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==
|
||||
|
||||
"@esbuild/linux-ia32@0.18.20":
|
||||
version "0.18.20"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz#699391cccba9aee6019b7f9892eb99219f1570a7"
|
||||
integrity sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==
|
||||
|
||||
"@esbuild/linux-loong64@0.18.20":
|
||||
version "0.18.20"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz#e6fccb7aac178dd2ffb9860465ac89d7f23b977d"
|
||||
integrity sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==
|
||||
|
||||
"@esbuild/linux-mips64el@0.18.20":
|
||||
version "0.18.20"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz#eeff3a937de9c2310de30622a957ad1bd9183231"
|
||||
integrity sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==
|
||||
|
||||
"@esbuild/linux-ppc64@0.18.20":
|
||||
version "0.18.20"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz#2f7156bde20b01527993e6881435ad79ba9599fb"
|
||||
integrity sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==
|
||||
|
||||
"@esbuild/linux-riscv64@0.18.20":
|
||||
version "0.18.20"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz#6628389f210123d8b4743045af8caa7d4ddfc7a6"
|
||||
integrity sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==
|
||||
|
||||
"@esbuild/linux-s390x@0.18.20":
|
||||
version "0.18.20"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz#255e81fb289b101026131858ab99fba63dcf0071"
|
||||
integrity sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==
|
||||
|
||||
"@esbuild/linux-x64@0.18.20":
|
||||
version "0.18.20"
|
||||
resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz"
|
||||
integrity sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==
|
||||
|
||||
"@esbuild/netbsd-x64@0.18.20":
|
||||
version "0.18.20"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz#30e8cd8a3dded63975e2df2438ca109601ebe0d1"
|
||||
integrity sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==
|
||||
|
||||
"@esbuild/openbsd-x64@0.18.20":
|
||||
version "0.18.20"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz#7812af31b205055874c8082ea9cf9ab0da6217ae"
|
||||
integrity sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==
|
||||
|
||||
"@esbuild/sunos-x64@0.18.20":
|
||||
version "0.18.20"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz#d5c275c3b4e73c9b0ecd38d1ca62c020f887ab9d"
|
||||
integrity sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==
|
||||
|
||||
"@esbuild/win32-arm64@0.18.20":
|
||||
version "0.18.20"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz#73bc7f5a9f8a77805f357fab97f290d0e4820ac9"
|
||||
integrity sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==
|
||||
|
||||
"@esbuild/win32-ia32@0.18.20":
|
||||
version "0.18.20"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz#ec93cbf0ef1085cc12e71e0d661d20569ff42102"
|
||||
integrity sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==
|
||||
|
||||
"@esbuild/win32-x64@0.18.20":
|
||||
version "0.18.20"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz#786c5f41f043b07afb1af37683d7c33668858f6d"
|
||||
integrity sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==
|
||||
|
||||
"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
|
||||
version "4.4.0"
|
||||
resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz"
|
||||
@@ -1025,7 +1130,7 @@
|
||||
"@firebase/util" "1.9.3"
|
||||
tslib "^2.1.0"
|
||||
|
||||
"@firebase/app-compat@0.2.19", "@firebase/app-compat@0.x":
|
||||
"@firebase/app-compat@0.2.19":
|
||||
version "0.2.19"
|
||||
resolved "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.2.19.tgz"
|
||||
integrity sha512-QkJDqYqjhvs4fTMcRVXQkP9hbo5yfoJXDWkhU4VA5Vzs8Qsp76VPzYbqx5SD5OmBy+bz/Ot1UV8qySPGI4aKuw==
|
||||
@@ -1036,12 +1141,12 @@
|
||||
"@firebase/util" "1.9.3"
|
||||
tslib "^2.1.0"
|
||||
|
||||
"@firebase/app-types@0.9.0", "@firebase/app-types@0.x":
|
||||
"@firebase/app-types@0.9.0":
|
||||
version "0.9.0"
|
||||
resolved "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.0.tgz"
|
||||
integrity sha512-AeweANOIo0Mb8GiYm3xhTEBVCmPwTYAu9Hcd2qSkLuga/6+j9b1Jskl5bpiSQWy9eJ/j5pavxj6eYogmnuzm+Q==
|
||||
|
||||
"@firebase/app@0.9.19", "@firebase/app@0.x":
|
||||
"@firebase/app@0.9.19":
|
||||
version "0.9.19"
|
||||
resolved "https://registry.npmjs.org/@firebase/app/-/app-0.9.19.tgz"
|
||||
integrity sha512-t/SHyZ3xWkR77ZU9VMoobDNFLdDKQ5xqoCAn4o16gTsA1C8sJ6ZOMZ02neMOPxNHuQXVE4tA8ukilnDbnK7uJA==
|
||||
@@ -1326,7 +1431,7 @@
|
||||
node-fetch "2.6.7"
|
||||
tslib "^2.1.0"
|
||||
|
||||
"@firebase/util@1.9.3", "@firebase/util@1.x":
|
||||
"@firebase/util@1.9.3":
|
||||
version "1.9.3"
|
||||
resolved "https://registry.npmjs.org/@firebase/util/-/util-1.9.3.tgz"
|
||||
integrity sha512-DY02CRhOZwpzO36fHpuVysz6JZrscPiBXD0fXp6qSrL9oNOx5KWICKdR95C0lSITzxp0TZosVyHqzatE8JbcjA==
|
||||
@@ -1431,7 +1536,7 @@
|
||||
"@jridgewell/gen-mapping" "^0.3.0"
|
||||
"@jridgewell/trace-mapping" "^0.3.9"
|
||||
|
||||
"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@1.4.14":
|
||||
"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10":
|
||||
version "1.4.14"
|
||||
resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz"
|
||||
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
|
||||
@@ -1457,7 +1562,7 @@
|
||||
"@nodelib/fs.stat" "2.0.5"
|
||||
run-parallel "^1.1.9"
|
||||
|
||||
"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5":
|
||||
"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
|
||||
version "2.0.5"
|
||||
resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
|
||||
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
|
||||
@@ -1577,6 +1682,11 @@
|
||||
resolved "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.5.1.tgz"
|
||||
integrity sha512-6i/8UoL0P5y4leBIGzvkZdS85RDMG9y1ihZzmTZQ5LdHUYmZ7pKFoj8X0236s3lusPs1Fa5HTQUpwI+UfTcmeA==
|
||||
|
||||
"@socket.io/component-emitter@~3.1.0":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz"
|
||||
integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==
|
||||
|
||||
"@surma/rollup-plugin-off-main-thread@^2.2.3":
|
||||
version "2.2.3"
|
||||
resolved "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz"
|
||||
@@ -1597,26 +1707,16 @@
|
||||
resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.14.tgz"
|
||||
integrity sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==
|
||||
|
||||
"@types/node@*":
|
||||
"@types/node@*", "@types/node@>=12.12.47", "@types/node@>=13.7.0":
|
||||
version "18.11.18"
|
||||
resolved "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz"
|
||||
integrity sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==
|
||||
|
||||
"@types/node@^20.6.2", "@types/node@>= 14":
|
||||
"@types/node@^20.6.2":
|
||||
version "20.6.2"
|
||||
resolved "https://registry.npmjs.org/@types/node/-/node-20.6.2.tgz"
|
||||
integrity sha512-Y+/1vGBHV/cYk6OI1Na/LHzwnlNCAfU3ZNGrc1LdRe/LAIbdDPTTv/HU3M7yXN448aTVDq3eKRm2cg7iKLb8gw==
|
||||
|
||||
"@types/node@>=12.12.47":
|
||||
version "18.11.18"
|
||||
resolved "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz"
|
||||
integrity sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==
|
||||
|
||||
"@types/node@>=13.7.0":
|
||||
version "18.11.18"
|
||||
resolved "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz"
|
||||
integrity sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==
|
||||
|
||||
"@types/resolve@1.17.1":
|
||||
version "1.17.1"
|
||||
resolved "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz"
|
||||
@@ -1651,7 +1751,7 @@
|
||||
semver "^7.5.4"
|
||||
ts-api-utils "^1.0.1"
|
||||
|
||||
"@typescript-eslint/parser@^6.0.0 || ^6.0.0-alpha", "@typescript-eslint/parser@^6.7.0":
|
||||
"@typescript-eslint/parser@^6.7.0":
|
||||
version "6.9.1"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.9.1.tgz"
|
||||
integrity sha512-C7AK2wn43GSaCUZ9do6Ksgi2g3mwFkMO3Cis96kzmgudoVaKyt62yNzJOktP0HDLb/iO2O0n2lBOzJgr6Q/cyg==
|
||||
@@ -1741,14 +1841,14 @@
|
||||
resolved "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.3.4.tgz"
|
||||
integrity sha512-ciXNIHKPriERBisHFBvnTbfKa6r9SAesOYXeGDzgegcvy9Q4xdScSHAmKbNT0M3O0S9LKhIf5/G+UYG4NnnzYw==
|
||||
|
||||
"@volar/language-core@~1.10.0", "@volar/language-core@1.10.1":
|
||||
"@volar/language-core@1.10.1", "@volar/language-core@~1.10.0":
|
||||
version "1.10.1"
|
||||
resolved "https://registry.npmjs.org/@volar/language-core/-/language-core-1.10.1.tgz"
|
||||
integrity sha512-JnsM1mIPdfGPxmoOcK1c7HYAsL6YOv0TCJ4aW3AXPZN/Jb4R77epDyMZIVudSGjWMbvv/JfUa+rQ+dGKTmgwBA==
|
||||
dependencies:
|
||||
"@volar/source-map" "1.10.1"
|
||||
|
||||
"@volar/source-map@~1.10.0", "@volar/source-map@1.10.1":
|
||||
"@volar/source-map@1.10.1", "@volar/source-map@~1.10.0":
|
||||
version "1.10.1"
|
||||
resolved "https://registry.npmjs.org/@volar/source-map/-/source-map-1.10.1.tgz"
|
||||
integrity sha512-3/S6KQbqa7pGC8CxPrg69qHLpOvkiPHGJtWPkI/1AXCsktkJ6gIk/5z4hyuMp8Anvs6eS/Kvp/GZa3ut3votKA==
|
||||
@@ -1772,7 +1872,7 @@
|
||||
estree-walker "^2.0.2"
|
||||
source-map-js "^1.0.2"
|
||||
|
||||
"@vue/compiler-dom@^3.3.0", "@vue/compiler-dom@3.3.4":
|
||||
"@vue/compiler-dom@3.3.4", "@vue/compiler-dom@^3.3.0":
|
||||
version "3.3.4"
|
||||
resolved "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.4.tgz"
|
||||
integrity sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==
|
||||
@@ -1851,7 +1951,7 @@
|
||||
estree-walker "^2.0.2"
|
||||
magic-string "^0.30.0"
|
||||
|
||||
"@vue/reactivity@^3.3.0", "@vue/reactivity@3.3.4":
|
||||
"@vue/reactivity@3.3.4", "@vue/reactivity@^3.3.0":
|
||||
version "3.3.4"
|
||||
resolved "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.4.tgz"
|
||||
integrity sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==
|
||||
@@ -1883,7 +1983,7 @@
|
||||
"@vue/compiler-ssr" "3.3.4"
|
||||
"@vue/shared" "3.3.4"
|
||||
|
||||
"@vue/shared@^3.3.0", "@vue/shared@3.3.4":
|
||||
"@vue/shared@3.3.4", "@vue/shared@^3.3.0":
|
||||
version "3.3.4"
|
||||
resolved "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz"
|
||||
integrity sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==
|
||||
@@ -1906,7 +2006,7 @@ acorn-jsx@^5.3.2:
|
||||
resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
|
||||
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
|
||||
|
||||
"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.10.0, acorn@^8.5.0, acorn@^8.9.0:
|
||||
acorn@^8.10.0, acorn@^8.5.0, acorn@^8.9.0:
|
||||
version "8.10.0"
|
||||
resolved "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz"
|
||||
integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==
|
||||
@@ -1921,7 +2021,7 @@ ajv@^6.12.4:
|
||||
json-schema-traverse "^0.4.1"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
ajv@^8.6.0, ajv@>=8:
|
||||
ajv@^8.6.0:
|
||||
version "8.11.2"
|
||||
resolved "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz"
|
||||
integrity sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==
|
||||
@@ -2084,7 +2184,7 @@ braces@^3.0.2, braces@~3.0.2:
|
||||
dependencies:
|
||||
fill-range "^7.0.1"
|
||||
|
||||
browserslist@^4.21.3, browserslist@^4.21.4, "browserslist@>= 4.21.0":
|
||||
browserslist@^4.21.3, browserslist@^4.21.4:
|
||||
version "4.21.4"
|
||||
resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz"
|
||||
integrity sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==
|
||||
@@ -2151,15 +2251,7 @@ chalk@^2.4.2:
|
||||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
chalk@^4.0.0:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz"
|
||||
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
|
||||
dependencies:
|
||||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
chalk@^4.0.2:
|
||||
chalk@^4.0.0, chalk@^4.0.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz"
|
||||
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
|
||||
@@ -2210,16 +2302,16 @@ color-convert@^2.0.1:
|
||||
dependencies:
|
||||
color-name "~1.1.4"
|
||||
|
||||
color-name@^1.0.0, color-name@~1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
|
||||
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||
|
||||
color-name@1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz"
|
||||
integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
|
||||
|
||||
color-name@^1.0.0, color-name@~1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
|
||||
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||
|
||||
color-string@^1.9.0:
|
||||
version "1.9.1"
|
||||
resolved "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz"
|
||||
@@ -2314,7 +2406,7 @@ de-indent@^1.0.2:
|
||||
resolved "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz"
|
||||
integrity sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==
|
||||
|
||||
debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4:
|
||||
debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2:
|
||||
version "4.3.4"
|
||||
resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"
|
||||
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
|
||||
@@ -2449,6 +2541,22 @@ end-of-stream@^1.1.0, end-of-stream@^1.4.1:
|
||||
dependencies:
|
||||
once "^1.4.0"
|
||||
|
||||
engine.io-client@~6.5.2:
|
||||
version "6.5.3"
|
||||
resolved "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz"
|
||||
integrity sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==
|
||||
dependencies:
|
||||
"@socket.io/component-emitter" "~3.1.0"
|
||||
debug "~4.3.1"
|
||||
engine.io-parser "~5.2.1"
|
||||
ws "~8.11.0"
|
||||
xmlhttprequest-ssl "~2.0.0"
|
||||
|
||||
engine.io-parser@~5.2.1:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz"
|
||||
integrity sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==
|
||||
|
||||
es-abstract@^1.19.0, es-abstract@^1.20.4:
|
||||
version "1.20.5"
|
||||
resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.5.tgz"
|
||||
@@ -2545,7 +2653,7 @@ eslint-plugin-prettier@^5.0.0:
|
||||
prettier-linter-helpers "^1.0.0"
|
||||
synckit "^0.8.5"
|
||||
|
||||
eslint-plugin-vue@^9.0.0, eslint-plugin-vue@^9.17.0:
|
||||
eslint-plugin-vue@^9.17.0:
|
||||
version "9.18.1"
|
||||
resolved "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.18.1.tgz"
|
||||
integrity sha512-7hZFlrEgg9NIzuVik2I9xSnJA5RsmOfueYgsUGUokEDLJ1LHtxO0Pl4duje1BriZ/jDWb+44tcIlC3yi0tdlZg==
|
||||
@@ -2571,7 +2679,7 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4
|
||||
resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz"
|
||||
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
|
||||
|
||||
"eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^6.2.0 || ^7.0.0 || ^8.0.0", "eslint@^7.0.0 || ^8.0.0", eslint@^8.49.0, "eslint@>= 8.0.0", eslint@>=6.0.0, eslint@>=7.0.0, eslint@>=8.0.0:
|
||||
eslint@^8.49.0:
|
||||
version "8.52.0"
|
||||
resolved "https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz"
|
||||
integrity sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==
|
||||
@@ -2819,9 +2927,9 @@ flatted@^3.2.9:
|
||||
integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==
|
||||
|
||||
follow-redirects@^1.15.0:
|
||||
version "1.15.2"
|
||||
resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz"
|
||||
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
|
||||
version "1.15.5"
|
||||
resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz"
|
||||
integrity sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==
|
||||
|
||||
form-data@^4.0.0:
|
||||
version "4.0.0"
|
||||
@@ -2852,6 +2960,11 @@ fs.realpath@^1.0.0:
|
||||
resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
|
||||
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
|
||||
|
||||
fsevents@~2.3.2:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
|
||||
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
|
||||
|
||||
function-bind@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz"
|
||||
@@ -3052,7 +3165,7 @@ ico-endec@*:
|
||||
resolved "https://registry.npmjs.org/ico-endec/-/ico-endec-0.1.6.tgz"
|
||||
integrity sha512-ZdLU38ZoED3g1j3iEyzcQj+wAkY2xfWNkymszfJPoxucIUhK7NayQ+/C4Kv0nDFMIsbtbEHldv3V8PU494/ueQ==
|
||||
|
||||
idb@^7.0.1, idb@7.0.1:
|
||||
idb@7.0.1, idb@^7.0.1:
|
||||
version "7.0.1"
|
||||
resolved "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz"
|
||||
integrity sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==
|
||||
@@ -3098,7 +3211,7 @@ inflight@^1.0.4:
|
||||
once "^1.3.0"
|
||||
wrappy "1"
|
||||
|
||||
inherits@^2.0.3, inherits@^2.0.4, inherits@2:
|
||||
inherits@2, inherits@^2.0.3, inherits@^2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
@@ -3826,7 +3939,7 @@ prettier-linter-helpers@^1.0.0:
|
||||
dependencies:
|
||||
fast-diff "^1.1.2"
|
||||
|
||||
prettier@^3.0.3, "prettier@>= 3.0.0", prettier@>=3.0.0:
|
||||
prettier@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz"
|
||||
integrity sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==
|
||||
@@ -4023,7 +4136,7 @@ rollup-plugin-terser@^7.0.0:
|
||||
serialize-javascript "^4.0.0"
|
||||
terser "^5.0.0"
|
||||
|
||||
"rollup@^1.20.0 || ^2.0.0", rollup@^1.20.0||^2.0.0, rollup@^2.0.0, rollup@^2.43.1:
|
||||
rollup@^2.43.1:
|
||||
version "2.79.1"
|
||||
resolved "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz"
|
||||
integrity sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==
|
||||
@@ -4051,7 +4164,7 @@ run-parallel@^1.1.9:
|
||||
dependencies:
|
||||
queue-microtask "^1.2.2"
|
||||
|
||||
safe-buffer@^5.0.1, safe-buffer@>=5.1.0, safe-buffer@~5.2.0:
|
||||
safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@~5.2.0:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz"
|
||||
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||
@@ -4070,7 +4183,7 @@ safe-regex-test@^1.0.0:
|
||||
get-intrinsic "^1.1.3"
|
||||
is-regex "^1.1.4"
|
||||
|
||||
sass@*, sass@^1.67.0:
|
||||
sass@^1.67.0:
|
||||
version "1.67.0"
|
||||
resolved "https://registry.npmjs.org/sass/-/sass-1.67.0.tgz"
|
||||
integrity sha512-SVrO9ZeX/QQyEGtuZYCVxoeAL5vGlYjJ9p4i4HFuekWl8y/LtJ7tJc10Z+ck1c8xOuoBm2MYzcLfTAffD0pl/A==
|
||||
@@ -4084,28 +4197,7 @@ semver@^6.1.1, semver@^6.1.2, semver@^6.3.0:
|
||||
resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz"
|
||||
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
|
||||
|
||||
semver@^7.3.5:
|
||||
version "7.5.4"
|
||||
resolved "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz"
|
||||
integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
|
||||
dependencies:
|
||||
lru-cache "^6.0.0"
|
||||
|
||||
semver@^7.3.6:
|
||||
version "7.5.4"
|
||||
resolved "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz"
|
||||
integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
|
||||
dependencies:
|
||||
lru-cache "^6.0.0"
|
||||
|
||||
semver@^7.3.8:
|
||||
version "7.5.4"
|
||||
resolved "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz"
|
||||
integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
|
||||
dependencies:
|
||||
lru-cache "^6.0.0"
|
||||
|
||||
semver@^7.5.4:
|
||||
semver@^7.3.5, semver@^7.3.6, semver@^7.3.8, semver@^7.5.4:
|
||||
version "7.5.4"
|
||||
resolved "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz"
|
||||
integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
|
||||
@@ -4194,7 +4286,25 @@ slash@^3.0.0:
|
||||
resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz"
|
||||
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
|
||||
|
||||
source-map-js@^1.0.2, "source-map-js@>=0.6.2 <2.0.0":
|
||||
socket.io-client@^4.7.4:
|
||||
version "4.7.4"
|
||||
resolved "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.4.tgz"
|
||||
integrity sha512-wh+OkeF0rAVCrABWQBaEjLfb7DVPotMbu0cgWgyR0v6eA4EoVnAwcIeIbcdTE3GT/H3kbdLl7OoH2+asoDRIIg==
|
||||
dependencies:
|
||||
"@socket.io/component-emitter" "~3.1.0"
|
||||
debug "~4.3.2"
|
||||
engine.io-client "~6.5.2"
|
||||
socket.io-parser "~4.2.4"
|
||||
|
||||
socket.io-parser@~4.2.4:
|
||||
version "4.2.4"
|
||||
resolved "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz"
|
||||
integrity sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==
|
||||
dependencies:
|
||||
"@socket.io/component-emitter" "~3.1.0"
|
||||
debug "~4.3.1"
|
||||
|
||||
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz"
|
||||
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
|
||||
@@ -4232,13 +4342,6 @@ streamx@^2.15.0:
|
||||
fast-fifo "^1.1.0"
|
||||
queue-tick "^1.0.1"
|
||||
|
||||
string_decoder@^1.1.1:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz"
|
||||
integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
|
||||
dependencies:
|
||||
safe-buffer "~5.2.0"
|
||||
|
||||
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
|
||||
@@ -4280,6 +4383,13 @@ string.prototype.trimstart@^1.0.6:
|
||||
define-properties "^1.1.4"
|
||||
es-abstract "^1.20.4"
|
||||
|
||||
string_decoder@^1.1.1:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz"
|
||||
integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
|
||||
dependencies:
|
||||
safe-buffer "~5.2.0"
|
||||
|
||||
stringify-object@^3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz"
|
||||
@@ -4402,7 +4512,7 @@ tempy@^0.6.0:
|
||||
type-fest "^0.16.0"
|
||||
unique-string "^2.0.0"
|
||||
|
||||
terser@^5.0.0, terser@^5.4.0:
|
||||
terser@^5.0.0:
|
||||
version "5.16.1"
|
||||
resolved "https://registry.npmjs.org/terser/-/terser-5.16.1.tgz"
|
||||
integrity sha512-xvQfyfA1ayT0qdK47zskQgRZeWLoOQ8JQ6mIgRGVNwZKdQMU+5FkCBjmv4QjcrTzyZquRw2FVtlJSRUmMKQslw==
|
||||
@@ -4485,7 +4595,7 @@ type-fest@^0.20.2:
|
||||
resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz"
|
||||
integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
|
||||
|
||||
typescript@*, typescript@^5.2.2, typescript@>=4.2.0, typescript@>=4.4.4:
|
||||
typescript@^5.2.2:
|
||||
version "5.2.2"
|
||||
resolved "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz"
|
||||
integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==
|
||||
@@ -4591,10 +4701,10 @@ vite-plugin-pwa@^0.16.5:
|
||||
workbox-build "^7.0.0"
|
||||
workbox-window "^7.0.0"
|
||||
|
||||
"vite@^3.1.0 || ^4.0.0", vite@^4.0.0, vite@^4.4.9:
|
||||
version "4.5.1"
|
||||
resolved "https://registry.npmjs.org/vite/-/vite-4.5.1.tgz"
|
||||
integrity sha512-AXXFaAJ8yebyqzoNB9fu2pHoo/nWX+xZlaRwoeYUxEqBO+Zj4msE5G+BhGBll9lYEKv9Hfks52PAF2X7qDYXQA==
|
||||
vite@^4.4.9:
|
||||
version "4.5.2"
|
||||
resolved "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz"
|
||||
integrity sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==
|
||||
dependencies:
|
||||
esbuild "^0.18.10"
|
||||
postcss "^8.4.27"
|
||||
@@ -4653,7 +4763,7 @@ vue-tsc@^1.8.11:
|
||||
"@vue/typescript" "1.8.11"
|
||||
semver "^7.3.8"
|
||||
|
||||
"vue@^2.6.14 || ^3.3.0", vue@^3.0.0, "vue@^3.0.0-0 || ^2.6.0", vue@^3.2.0, vue@^3.2.25, vue@^3.3.4, vue@3.3.4:
|
||||
vue@^3.3.4:
|
||||
version "3.3.4"
|
||||
resolved "https://registry.npmjs.org/vue/-/vue-3.3.4.tgz"
|
||||
integrity sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==
|
||||
@@ -4873,7 +4983,7 @@ workbox-sw@7.0.0:
|
||||
resolved "https://registry.npmjs.org/workbox-sw/-/workbox-sw-7.0.0.tgz"
|
||||
integrity sha512-SWfEouQfjRiZ7GNABzHUKUyj8pCoe+RwjfOIajcx6J5mtgKkN+t8UToHnpaJL5UVVOf5YhJh+OHhbVNIHe+LVA==
|
||||
|
||||
workbox-window@^7.0.0, workbox-window@7.0.0:
|
||||
workbox-window@7.0.0, workbox-window@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.npmjs.org/workbox-window/-/workbox-window-7.0.0.tgz"
|
||||
integrity sha512-j7P/bsAWE/a7sxqTzXo3P2ALb1reTfZdvVp6OJ/uLr/C2kZAMvjeWGm8V4htQhor7DOvYg0sSbFN2+flT5U0qA==
|
||||
@@ -4895,11 +5005,21 @@ wrappy@1:
|
||||
resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz"
|
||||
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
|
||||
|
||||
ws@~8.11.0:
|
||||
version "8.11.0"
|
||||
resolved "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz"
|
||||
integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==
|
||||
|
||||
xml-name-validator@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz"
|
||||
integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==
|
||||
|
||||
xmlhttprequest-ssl@~2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz"
|
||||
integrity sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==
|
||||
|
||||
y18n@^5.0.5:
|
||||
version "5.0.8"
|
||||
resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz"
|
||||
|
||||
Reference in New Issue
Block a user