Compare commits
276 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8e8e27658c | |||
| 9b6ace394a | |||
| 6cfeaa91bf | |||
| 08b208aeaa | |||
| a089b5275b | |||
| 8425cd4371 | |||
| dbdc517b87 | |||
| e271358a27 | |||
| 66262e3fcd | |||
| 5b2b6bdea2 | |||
| c8587de6d9 | |||
| 1f376085f2 | |||
| f28600a7fa | |||
| d59ead87e6 | |||
| 34d91bc800 | |||
| cf9991d8a0 | |||
| 4ffb79d62b | |||
| d9f5edb4fe | |||
| 1b2112430a | |||
| 0a972a23ef | |||
| 6d52724d06 | |||
| 99415c35d3 | |||
| c3f687d439 | |||
| 266edfd6e6 | |||
| d32d5ad91b | |||
| c3481470cb | |||
| 57e88b9abc | |||
| 44ebf53798 | |||
| 145dc72b6b | |||
| b7f3761940 | |||
| ea7c49dfb3 | |||
| 5d6785813a | |||
| a0054aed14 | |||
| 471e6f5216 | |||
| a617eef00e | |||
| 38e700ecd6 | |||
| da1be0e10a | |||
| f49bb12948 | |||
| 02673a3d70 | |||
| 4ddc7345df | |||
| 5d822684c0 | |||
| 69fa15c70a | |||
| 9192067388 | |||
| 2b41e5b857 | |||
| 674680ff14 | |||
| 475bd2ff10 | |||
| 074d1eb155 | |||
| 378393de89 | |||
| 03e61083a7 | |||
| 0b746fce8c | |||
| 5883e710be | |||
| 3d0695a17b | |||
| 4adb76eeb0 | |||
| 4c41076519 | |||
| 77f61d17fd | |||
| 032a84cbcf | |||
| de9851ebcc | |||
| ff78eba927 | |||
| e4c5f6a322 | |||
| 0a78761928 | |||
| 4843043c29 | |||
| 9e1df1fb61 | |||
| 021474cfb0 | |||
| 7d0e68862c | |||
| 653d45dfc6 | |||
| 4a4e1240a4 | |||
| 14ca48a90d | |||
| a02f9804b1 | |||
| c5efc6fbac | |||
| cacd0a1e4e | |||
| 50375099ab | |||
| 6af67ec741 | |||
| c64112c86a | |||
| 0434702d3b | |||
| dd7d1b0bb0 | |||
| 68934a89a4 | |||
| b88a240ec1 | |||
| eaa34f3359 | |||
| febb22e1bc | |||
| 500f3c1223 | |||
| 221e0c7e82 | |||
| ca19f7e397 | |||
| a71ccd3e1a | |||
| d496c70fa8 | |||
| b9868ba52e | |||
| 59bd3fa2ef | |||
| e14d328ed9 | |||
| 36d71292bc | |||
| 2f6e2e7402 | |||
| e959eac6c5 | |||
| 8bedc4dfc6 | |||
| 73563d5db7 | |||
| 3f818069cd | |||
| cdf0b2a426 | |||
| c29ddeb78c | |||
| b81d98cab7 | |||
| 0e45bca5da | |||
| 715e66879f | |||
| 1747e15dc8 | |||
| 6a923a8e1d | |||
| 25a248e95e | |||
| aa7a6b220e | |||
| deb7b68985 | |||
| 633f05f690 | |||
| 73828867da | |||
| 75685c1e0e | |||
| 496ff95236 | |||
| 7e25327832 | |||
| 272c9f50f8 | |||
| 255e07372e | |||
| 279bbfa4db | |||
| a5c829faf5 | |||
| 5fdfaeac5e | |||
| 9beb30e3d5 | |||
| 48582e2eea | |||
| 2e721fb8bf | |||
| f93c1fbfec | |||
| c06e7b6468 | |||
| 22a6d266cb | |||
| 5f8a16401b | |||
| c9be01aa29 | |||
| 4ec058b33c | |||
| 27a5d2a406 | |||
| 58169e26f6 | |||
| fee1f4bbd5 | |||
| 240817acc3 | |||
| db3be87dd8 | |||
| 1665134d6f | |||
| df289ab734 | |||
| f74440ba6f | |||
| a25dbe9fd5 | |||
| 4fff136d6b | |||
| d06f2d5d2e | |||
| 9f68d628d0 | |||
| d64b906dac | |||
| f3e193e68a | |||
| 5640ce9f2b | |||
| 50100eb2f9 | |||
| e478c510b2 | |||
| 7ea558642f | |||
| 493145f7f2 | |||
| 4f72535365 | |||
| 8e3bf80715 | |||
| 6da586d08a | |||
| be53b9c7fb | |||
| 94ed1160a1 | |||
| 859d8d2631 | |||
| 5f3abd73c5 | |||
| d71c8bb6f9 | |||
| a3db13d79c | |||
| 8cb3da66f2 | |||
| 6e07897ac0 | |||
| 726b859f5c | |||
| 651c60707a | |||
| d4fee84603 | |||
| 86539cdf23 | |||
| 69772460b8 | |||
| 6988a83355 | |||
| b6425564c8 | |||
| caf0a9b4c5 | |||
| bd5f433d6e | |||
| 8d9cc721d6 | |||
| cceeffe49d | |||
| fcb8357489 | |||
| ceffd8e675 | |||
| 5aa53521f7 | |||
| d8b559694b | |||
| c82ac04a91 | |||
| 284bdcbf2a | |||
| 7f4df98349 | |||
| aecbcf62df | |||
| 2a817365a6 | |||
| ecf3a00cab | |||
| beb2f3c0d4 | |||
| a65b09981b | |||
| 4ec544e8a9 | |||
| 7e108c5183 | |||
| 72361b157e | |||
| 1cc4d76e4d | |||
| 846d4d0547 | |||
| 751cadd218 | |||
| 3b44adff44 | |||
| 29a02dd98f | |||
| c5e68c4d03 | |||
| 95f7c2a4d9 | |||
| 84412822ff | |||
| 42bb056e66 | |||
| 053e9d2b6a | |||
| c729d75541 | |||
| a9b72d0b7a | |||
| 95a027f284 | |||
| dbba83b28b | |||
| 65abe550f5 | |||
| 531108c25a | |||
| bcf750d451 | |||
| 0a8bfe4c52 | |||
| 0f19bc767a | |||
| 8eb0266874 | |||
| ae5b5ff965 | |||
| 3a0c4bc151 | |||
| 4f5fcb3189 | |||
| 3a2978bbe3 | |||
| a81cc4559b | |||
| 065143c359 | |||
| 1661881127 | |||
| 93aa889414 | |||
| 2a131ab1fb | |||
| 387f42985a | |||
| 6c83ce90bf | |||
| 3d519e874f | |||
| 99cdb3442a | |||
| a6c0fe86c8 | |||
| 828421efe0 | |||
| 21bacb1c95 | |||
| 0d9a3f4b4f | |||
| 76b8534d63 | |||
| 0821fd708e | |||
| b0a9939446 | |||
| 2a64b8f10d | |||
| dc1c457ea4 | |||
| 1f95bc5230 | |||
| 5a06920e5b | |||
| ee0d9e7ed4 | |||
| 30ad3ad4f2 | |||
| c2bd5a8a1b | |||
| 7101d0972d | |||
| 82bbfcdf70 | |||
| b90ac6c09e | |||
| 76d0ff88f1 | |||
| 951afcedeb | |||
| 96de3f0dcc | |||
| 03950eef66 | |||
| 6dd8cb2dad | |||
| aae51d4139 | |||
| 9994a541b1 | |||
| bc3a603ba2 | |||
| 7857377cab | |||
| 0034f43be4 | |||
| c09fc81886 | |||
| 30f72d518d | |||
| 9b86e07152 | |||
| 4e0fb5dc01 | |||
| a392991030 | |||
| ff7ca27fe6 | |||
| 94cd7aaa60 | |||
| 843289d8d7 | |||
| 66cae68e19 | |||
| b38e50396a | |||
| 7888e59117 | |||
| 46e700583d | |||
| fc56c38c45 | |||
| 9594e2c21a | |||
| a8bab5283b | |||
| 1cc799706c | |||
| 5ee8f72652 | |||
| 942f883b91 | |||
| 54b47d44e5 | |||
| f9aaf21f7a | |||
| d79705ca5c | |||
| 55c64d5f0a | |||
| 4ca1c7bb9c | |||
| abc8fda98e | |||
| aaec23d210 | |||
| 0af7b68138 | |||
| ae24eaf8e4 | |||
| f73a07daee | |||
| 89f5bf2e95 | |||
| 8137c1ff95 | |||
| 4b0d9b887e | |||
| 506064cf9a | |||
| 825e25434a | |||
| 32c601d50a | |||
| b88a96237e | |||
| 6c724440d7 | |||
| 71016e63bb | |||
| fb85352ce3 |
@@ -1,3 +1,6 @@
|
|||||||
|
# This file was auto-generated by the Firebase CLI
|
||||||
|
# https://github.com/firebase/firebase-tools
|
||||||
|
|
||||||
name: Deploy to Firebase Hosting on PR
|
name: Deploy to Firebase Hosting on PR
|
||||||
'on': pull_request
|
'on': pull_request
|
||||||
jobs:
|
jobs:
|
||||||
@@ -11,4 +14,4 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
repoToken: '${{ secrets.GITHUB_TOKEN }}'
|
repoToken: '${{ secrets.GITHUB_TOKEN }}'
|
||||||
firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_STACJOWNIK_TD2 }}'
|
firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_STACJOWNIK_TD2 }}'
|
||||||
projectId: stacjownik-td2
|
projectId: stacjownik-td2
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
node_modules
|
node_modules
|
||||||
|
/dev-dist
|
||||||
/dist
|
/dist
|
||||||
|
|
||||||
# local env files
|
# local env files
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
presets: [
|
|
||||||
'@vue/cli-plugin-babel/preset'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
{
|
{
|
||||||
"hosting": {
|
"hosting": {
|
||||||
"public": "dist",
|
"public": "dist",
|
||||||
"ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
|
"ignore": [
|
||||||
|
"firebase.json",
|
||||||
|
"**/.*",
|
||||||
|
"**/node_modules/**"
|
||||||
|
],
|
||||||
"rewrites": [
|
"rewrites": [
|
||||||
{
|
{
|
||||||
"source": "**",
|
"source": "**",
|
||||||
@@ -10,4 +14,3 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,54 +1,35 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="pl">
|
<html lang="pl">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||||
|
|
||||||
<meta name="keywords" content="Stacjownik, TD2, Train Driver 2, stacjownik-td2" />
|
<meta name="keywords" content="Stacjownik, TD2, Train Driver 2, stacjownik-td2, stacjownik, td2.info.pl" />
|
||||||
<meta name="description" content="Automatycznie odświeżana strona wyświetlająca stacje w Train Driver 2!" />
|
<meta name="description" content="Pomocnik maszynisty i dyżurnego symulatora Train Driver 2" />
|
||||||
|
|
||||||
<title>Stacjownik</title>
|
<title>Stacjownik</title>
|
||||||
|
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
||||||
<link rel="manifest" href="/site.webmanifest" />
|
<link rel="manifest" href="/site.webmanifest" />
|
||||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
|
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
|
||||||
<meta name="msapplication-TileColor" content="#da532c" />
|
<meta name="msapplication-TileColor" content="#da532c" />
|
||||||
<meta name="theme-color" content="#ffffff" />
|
<meta name="theme-color" content="#ffffff" />
|
||||||
|
|
||||||
<link rel="icon" href="favicon-64.png" sizes="64x64" type="image/png" />
|
<link rel="icon" href="favicon-64.png" sizes="64x64" type="image/png" />
|
||||||
<link rel="icon" href="favicon-32.png" sizes="32x32" type="image/png" />
|
<link rel="icon" href="favicon-32.png" sizes="32x32" type="image/png" />
|
||||||
<link rel="icon" href="favicon-62.png" sizes="62x62" type="image/png" />
|
<link rel="icon" href="favicon-62.png" sizes="62x62" type="image/png" />
|
||||||
<link rel="icon" href="favicon-16.png" sizes="16x16" type="image/png" />
|
<link rel="icon" href="favicon-16.png" sizes="16x16" type="image/png" />
|
||||||
<link rel="icon" href="favicon.ico" />
|
<link rel="icon" href="favicon.ico" />
|
||||||
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Quicksand:wght@400;500;700&display=swap" rel="stylesheet" />
|
<link href="https://fonts.googleapis.com/css2?family=Quicksand:wght@500;700&display=swap" rel="stylesheet" />
|
||||||
|
</head>
|
||||||
<script src="https://www.gstatic.com/firebasejs/8.1.1/firebase-app.js"></script>
|
|
||||||
|
<body>
|
||||||
<script>
|
<div id="app"></div>
|
||||||
const firebaseConfig = {
|
<script type="module" src="/src/main.ts"></script>
|
||||||
apiKey: 'AIzaSyBI36X2-p7vU1flxoJdCEc0noByyTe1mpw',
|
</body>
|
||||||
authDomain: 'stacjownik-td2.firebaseapp.com',
|
</html>
|
||||||
databaseURL: 'https://stacjownik-td2.firebaseio.com',
|
|
||||||
projectId: 'stacjownik-td2',
|
|
||||||
storageBucket: 'stacjownik-td2.appspot.com',
|
|
||||||
};
|
|
||||||
|
|
||||||
firebase.initializeApp(firebaseConfig);
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<noscript>
|
|
||||||
<strong
|
|
||||||
>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please
|
|
||||||
enable it to continue.</strong
|
|
||||||
>
|
|
||||||
</noscript>
|
|
||||||
<div id="app"></div>
|
|
||||||
<!-- built files will be auto injected -->
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,37 +1,33 @@
|
|||||||
{
|
{
|
||||||
"name": "stacjownik",
|
"name": "stacjownik",
|
||||||
"version": "1.9.9",
|
"version": "1.16.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve",
|
"dev": "vite",
|
||||||
"build": "vue-cli-service build",
|
"build": "vue-tsc --noEmit && vite build",
|
||||||
"deploy-prod": "npm run build && firebase deploy --only hosting:prod",
|
"deploy": "yarn build && firebase deploy --only hosting",
|
||||||
"deploy-dev": "npm run build && firebase deploy --only hosting:dev"
|
"preview": "yarn build && vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"core-js": "^3.12.1",
|
"core-js": "^3.12.1",
|
||||||
"dotenv": "^8.6.0",
|
"dotenv": "^16.0.3",
|
||||||
"firebase": "^9.8.1",
|
"firebase": "^9.8.1",
|
||||||
"howler": "^2.2.1",
|
"howler": "^2.2.1",
|
||||||
"pinia": "^2.0.14",
|
"pinia": "^2.0.14",
|
||||||
|
"sass": "^1.53.0",
|
||||||
"socket.io-client": "^4.4.1",
|
"socket.io-client": "^4.4.1",
|
||||||
"vue": "^3.2.34",
|
"vue": "^3.2.37",
|
||||||
"vue-i18n": "^9.1.6",
|
"vue-i18n": "^9.1.6",
|
||||||
"vue-router": "^4.0.0-0",
|
"vue-router": "^4.0.0-0"
|
||||||
"vuex": "^4.0.2"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^17.0.35",
|
"@types/node": "^18.11.17",
|
||||||
"@vue/cli-plugin-babel": "^5.0.4",
|
"@vitejs/plugin-vue": "^4.0.0",
|
||||||
"@vue/cli-plugin-router": "^5.0.4",
|
"axios": "^1.2.1",
|
||||||
"@vue/cli-plugin-typescript": "^5.0.4",
|
"typescript": "^4.9.4",
|
||||||
"@vue/cli-plugin-vuex": "^5.0.4",
|
"vite": "^4.0.3",
|
||||||
"@vue/cli-service": "^5.0.4",
|
"vite-plugin-pwa": "^0.14.0",
|
||||||
"@vue/compiler-sfc": "^3.1.0",
|
"vue-tsc": "^1.0.18"
|
||||||
"axios": "^0.21.1",
|
|
||||||
"sass": "^1.32.13",
|
|
||||||
"sass-loader": "^8.0.2",
|
|
||||||
"typescript": "^4.7.3"
|
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"> 1%",
|
"> 1%",
|
||||||
|
|||||||
|
After Width: | Height: | Size: 951 B |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 799 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M8.00251 14.9297L0 1.07422H6.14651L8.00251 4.27503L9.84583 1.07422H16L8.00251 14.9297Z" fill="black"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 215 B |
@@ -0,0 +1,2 @@
|
|||||||
|
User-agent: *
|
||||||
|
Disallow:
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "",
|
"name": "Stacjownik TD2",
|
||||||
"short_name": "",
|
"short_name": "Stacjownik",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "/android-chrome-192x192.png",
|
"src": "/android-chrome-192x192.png",
|
||||||
@@ -13,7 +13,8 @@
|
|||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"theme_color": "#ffffff",
|
"theme_color": "#ffc014",
|
||||||
"background_color": "#ffffff",
|
"background_color": "#4d4d4d",
|
||||||
"display": "standalone"
|
"display": "standalone",
|
||||||
|
"start_url": "."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,22 +17,40 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-anim {
|
||||||
|
&-enter-active,
|
||||||
|
&-leave-active {
|
||||||
|
transition: all $animDuration $animType;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-enter-from,
|
||||||
|
&-leave-to {
|
||||||
|
transform: translateY(-25%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.route {
|
.route {
|
||||||
margin: 0 0.2em;
|
margin: 0 0.2em;
|
||||||
|
|
||||||
&-active {
|
&-active,
|
||||||
|
&[data-active='true'] {
|
||||||
color: $accentCol;
|
color: $accentCol;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// APP
|
// APP
|
||||||
.app {
|
#app {
|
||||||
color: white;
|
color: white;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
|
|
||||||
@include smallScreen() {
|
@include smallScreen() {
|
||||||
font-size: calc(0.4rem + 1.4vw);
|
font-size: calc(0.55rem + 1.1vw);
|
||||||
|
}
|
||||||
|
|
||||||
|
@include screenLandscape() {
|
||||||
|
font-size: calc(0.45rem + 0.8vw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,8 +58,8 @@
|
|||||||
.app_container {
|
.app_container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: column;
|
flex-flow: column;
|
||||||
height: 100vh;
|
|
||||||
min-height: 800px;
|
min-height: 100vh;
|
||||||
|
|
||||||
header {
|
header {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
@@ -68,168 +86,16 @@
|
|||||||
border-radius: 0 0 1em 1em;
|
border-radius: 0 0 1em 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error icon
|
|
||||||
.wip-alert {
|
|
||||||
padding: 0 0.5em;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-error {
|
|
||||||
width: 13em;
|
|
||||||
margin: 0.5em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// HEADER
|
|
||||||
.app_header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
position: relative;
|
|
||||||
background-color: $primaryCol;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
&_body {
|
|
||||||
max-width: 21em;
|
|
||||||
}
|
|
||||||
|
|
||||||
&_container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
width: 1350px;
|
|
||||||
padding: 0.5em 0.3em 0 0.3em;
|
|
||||||
border-radius: 0 0 1em 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
&_brand {
|
|
||||||
img {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&_info {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
|
||||||
max-width: 100%;
|
|
||||||
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
&_links {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
border-radius: 0.7em;
|
|
||||||
|
|
||||||
font-size: 1.25em;
|
|
||||||
padding: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
&_icons {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: flex-end;
|
|
||||||
align-items: flex-end;
|
|
||||||
padding: 0.5em 0.5em;
|
|
||||||
|
|
||||||
@include smallScreen() {
|
|
||||||
right: auto;
|
|
||||||
left: 0.75em;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ICONS
|
|
||||||
.icons {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&-top {
|
|
||||||
img {
|
|
||||||
width: 2.5em;
|
|
||||||
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
margin-bottom: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-bottom {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
a {
|
|
||||||
margin-left: 0.6em;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 1.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include smallScreen() {
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
a {
|
|
||||||
margin: 0.25em 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// COUNTER
|
|
||||||
.info_counter {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
span {
|
|
||||||
margin: 0 0.15em;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 1.35em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// REGION SELECTION
|
|
||||||
.info_region {
|
|
||||||
color: white;
|
|
||||||
font-weight: bold;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
|
|
||||||
.select-box_content button {
|
|
||||||
background-color: transparent;
|
|
||||||
font-weight: bold;
|
|
||||||
padding: 0.1em 0.5em;
|
|
||||||
color: paleturquoise;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.options {
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.arrow {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FOOTER
|
// FOOTER
|
||||||
footer.app_footer {
|
footer.app_footer {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 1.1em;
|
||||||
|
vertical-align: text-bottom;
|
||||||
|
}
|
||||||
|
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
|
|
||||||
background: #111;
|
background: #111;
|
||||||
|
|||||||
@@ -1,110 +1,74 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="app">
|
<div class="app_container">
|
||||||
<div class="app_container">
|
<transition name="modal-anim">
|
||||||
<!-- <div class="wip-alert">
|
<keep-alive>
|
||||||
<img class="icon-error" :src="iconError" alt="error" />
|
<TrainModal v-if="store.chosenModalTrainId" />
|
||||||
<h2>Stacjownik tymczasowo nieaktywny!</h2>
|
</keep-alive>
|
||||||
<p>Absolutny zakaz wjazdu!</p>
|
</transition>
|
||||||
</div> -->
|
|
||||||
<header class="app_header">
|
|
||||||
<div class="header_container">
|
|
||||||
<div class="header_icons">
|
|
||||||
<span class="icons-top">
|
|
||||||
<img :src="icons.pl" alt="icon-pl" @click="changeLang('en')" v-if="currentLang == 'pl'" />
|
|
||||||
<img :src="icons.en" alt="icon-en" @click="changeLang('pl')" v-else />
|
|
||||||
</span>
|
|
||||||
<span class="icons-bottom">
|
|
||||||
<a href="https://www.paypal.com/paypalme/spythere" target="_blank">
|
|
||||||
<img :src="icons.dollar" alt="icon paypal" />
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="https://discord.gg/x2mpNN3svk" target="_blank">
|
<UpdatePrompt />
|
||||||
<img :src="icons.discord" alt="icon discord" />
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="header_body">
|
<AppHeader :current-lang="currentLang" @change-lang="changeLang" />
|
||||||
<status-indicator />
|
|
||||||
<span class="header_brand">
|
|
||||||
<img :src="brand_logo" alt="Stacjownik" />
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="header_info">
|
<main class="app_main">
|
||||||
<Clock />
|
<router-view v-slot="{ Component }">
|
||||||
|
<keep-alive exclude="JournalView">
|
||||||
|
<component :is="Component" :key="$route.name" />
|
||||||
|
</keep-alive>
|
||||||
|
</router-view>
|
||||||
|
</main>
|
||||||
|
|
||||||
<div class="info_counter">
|
<footer class="app_footer">
|
||||||
<img src="@/assets/icon-dispatcher.svg" alt="icon dispatcher" />
|
©
|
||||||
<span class="text--primary">{{ onlineDispatchers.length }}</span>
|
<a href="https://td2.info.pl/profile/?u=20777" target="_blank">Spythere</a>
|
||||||
<span class="text--grayed"> / </span>
|
{{ new Date().getUTCFullYear() }} |
|
||||||
<span class="text--primary">{{ trainList.length }}</span>
|
<a :href="releaseURL" target="_blank">v{{ VERSION }}{{ isOnProductionHost ? '' : 'dev' }}</a>
|
||||||
<img src="@/assets/icon-train.svg" alt="icon train" />
|
<br />
|
||||||
</div>
|
<a href="https://discord.gg/x2mpNN3svk"><img :src="getIcon('discord', 'png')" alt=""> <b>{{ $t('footer.discord') }}</b></a>
|
||||||
|
|
||||||
<span class="info_region">
|
<div style="display: none">∫ ukryta taktyczna całka do programowania w HTMLu</div>
|
||||||
<SelectBox :itemList="computedRegions" :defaultItemIndex="0" @selected="changeRegion" />
|
</footer>
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="header_links">
|
|
||||||
<router-link class="route" active-class="route-active" to="/" exact>
|
|
||||||
{{ $t('app.sceneries') }}
|
|
||||||
</router-link>
|
|
||||||
/
|
|
||||||
<router-link class="route" active-class="route-active" to="/trains">{{ $t('app.trains') }}</router-link>
|
|
||||||
/
|
|
||||||
<router-link class="route" active-class="route-active" to="/journal">
|
|
||||||
{{ $t('app.journal') }}
|
|
||||||
</router-link>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main class="app_main">
|
|
||||||
<router-view v-slot="{ Component }">
|
|
||||||
<!-- <transition name="view-anim" mode="out-in"> -->
|
|
||||||
<keep-alive>
|
|
||||||
<component :is="Component" :key="$route.path" />
|
|
||||||
</keep-alive>
|
|
||||||
</router-view>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer class="app_footer">
|
|
||||||
©
|
|
||||||
<a href="https://td2.info.pl/profile/?u=20777" target="_blank">Spythere</a>
|
|
||||||
{{ new Date().getUTCFullYear() }} | v{{ VERSION }}
|
|
||||||
|
|
||||||
<div style="display: none">∫ ukryta taktyczna całka do programowania w HTMLu</div>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, provide, ref } from 'vue';
|
import { computed, defineComponent, KeepAlive, provide, ref, watch } from 'vue';
|
||||||
|
|
||||||
import Clock from '@/components/App/Clock.vue';
|
import Clock from './components/App/Clock.vue';
|
||||||
import StorageManager from '@/scripts/managers/storageManager';
|
|
||||||
|
|
||||||
import packageInfo from '.././package.json';
|
import packageInfo from '.././package.json';
|
||||||
import options from '@/data/options.json';
|
|
||||||
|
|
||||||
import StatusIndicator from '@/components/App/StatusIndicator.vue';
|
import StatusIndicator from './components/App/StatusIndicator.vue';
|
||||||
import SelectBox from '@/components/Global/SelectBox.vue';
|
import SelectBox from './components/Global/SelectBox.vue';
|
||||||
import { useStore } from './store/store';
|
import { useStore } from './store/store';
|
||||||
|
import TrainModal from './components/Global/TrainModal.vue';
|
||||||
|
import StorageManager from './scripts/managers/storageManager';
|
||||||
|
import imageMixin from './mixins/imageMixin';
|
||||||
|
import AppHeader from './components/App/AppHeader.vue';
|
||||||
|
import axios from 'axios';
|
||||||
|
import UpdatePrompt from './components/App/UpdatePrompt.vue';
|
||||||
|
import { VERSION } from 'vue-i18n';
|
||||||
|
import { RouterView } from 'vue-router';
|
||||||
|
import useCustomSW from './mixins/useCustomSW';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
Clock,
|
Clock,
|
||||||
StatusIndicator,
|
StatusIndicator,
|
||||||
SelectBox,
|
SelectBox,
|
||||||
|
TrainModal,
|
||||||
|
AppHeader,
|
||||||
|
UpdatePrompt,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
mixins: [imageMixin],
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
store.connectToAPI();
|
store.connectToAPI();
|
||||||
|
|
||||||
|
const { offlineReady } = useCustomSW();
|
||||||
|
|
||||||
const isFilterCardVisible = ref(false);
|
const isFilterCardVisible = ref(false);
|
||||||
|
|
||||||
provide('isFilterCardVisible', isFilterCardVisible);
|
provide('isFilterCardVisible', isFilterCardVisible);
|
||||||
@@ -120,73 +84,54 @@ export default defineComponent({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
|
||||||
trainList() {
|
|
||||||
return this.store.trainList.filter((train) => train.online);
|
|
||||||
},
|
|
||||||
|
|
||||||
computedRegions() {
|
|
||||||
return this.options.regions.map((region) => {
|
|
||||||
const regionStationCount =
|
|
||||||
this.store.apiData.stations?.filter((station) => station.region == region.id && station.isOnline).length || 0;
|
|
||||||
const regionTrainCount = this.store.apiData.trains?.filter((train) => train.region == region.id && train.online).length || 0;
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: region.id,
|
|
||||||
value: `${region.value} <div class='text--grayed'>${regionStationCount} / ${regionTrainCount}</div>`,
|
|
||||||
selectedValue: region.value,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
VERSION: packageInfo.version,
|
VERSION: packageInfo.version,
|
||||||
updateModalVisible: false,
|
|
||||||
hasReleaseNotes: false,
|
|
||||||
options,
|
|
||||||
|
|
||||||
currentLang: 'pl',
|
currentLang: 'pl',
|
||||||
|
releaseURL: '',
|
||||||
brand_logo: require('@/assets/stacjownik-header-logo.svg'),
|
isOnProductionHost: location.hostname == 'stacjownik-td2.web.app',
|
||||||
|
|
||||||
icons: {
|
|
||||||
en: require('@/assets/icon-en.jpg'),
|
|
||||||
pl: require('@/assets/icon-pl.svg'),
|
|
||||||
error: require('@/assets/icon-error.svg'),
|
|
||||||
dollar: require('@/assets/icon-dollar.svg'),
|
|
||||||
dispatcher: require('@/assets/icon-dispatcher.svg'),
|
|
||||||
train: require('@/assets/icon-train.svg'),
|
|
||||||
discord: require('@/assets/icon-discord.png'),
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
this.loadLang();
|
this.loadLang();
|
||||||
|
|
||||||
|
this.store.isOffline = !window.navigator.onLine;
|
||||||
|
|
||||||
|
window.addEventListener('offline', () => {
|
||||||
|
this.store.isOffline = true;
|
||||||
|
|
||||||
|
this.store.apiData = {
|
||||||
|
stations: [],
|
||||||
|
dispatchers: [],
|
||||||
|
trains: [],
|
||||||
|
connectedSocketCount: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.store.setOnlineData();
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('online', () => {
|
||||||
|
this.store.isOffline = false;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
async mounted() {
|
async mounted() {
|
||||||
if (StorageManager.getStringValue('version') != this.VERSION) {
|
this.setReleaseURL();
|
||||||
StorageManager.setStringValue('version', this.VERSION);
|
|
||||||
|
|
||||||
if (this.hasReleaseNotes) StorageManager.setBooleanValue('version_notes_read', false);
|
watch(
|
||||||
}
|
() => this.store.blockScroll,
|
||||||
|
(value) => {
|
||||||
|
if (value) {
|
||||||
|
document.body.classList.add('no-scroll');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.updateModalVisible = this.hasReleaseNotes && !StorageManager.getBooleanValue('version_notes_read');
|
document.body.classList.remove('no-scroll');
|
||||||
|
}
|
||||||
this.updateToNewestVersion();
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
toggleUpdateModal() {
|
|
||||||
this.updateModalVisible = !this.updateModalVisible;
|
|
||||||
StorageManager.setBooleanValue('version_notes_read', true);
|
|
||||||
},
|
|
||||||
|
|
||||||
changeRegion(region: { id: string; value: string }) {
|
|
||||||
this.store.changeRegion(region);
|
|
||||||
},
|
|
||||||
|
|
||||||
changeLang(lang: string) {
|
changeLang(lang: string) {
|
||||||
this.$i18n.locale = lang;
|
this.$i18n.locale = lang;
|
||||||
this.currentLang = lang;
|
this.currentLang = lang;
|
||||||
@@ -194,12 +139,18 @@ export default defineComponent({
|
|||||||
StorageManager.setStringValue('lang', lang);
|
StorageManager.setStringValue('lang', lang);
|
||||||
},
|
},
|
||||||
|
|
||||||
updateToNewestVersion() {
|
async setReleaseURL() {
|
||||||
if (!StorageManager.isRegistered('unavailable-status')) {
|
try {
|
||||||
StorageManager.setBooleanValue('unavailable-status', true);
|
const releaseData = await (
|
||||||
StorageManager.setBooleanValue('ending-status', true);
|
await axios.get('https://api.github.com/repos/Spythere/stacjownik/releases/latest')
|
||||||
StorageManager.setBooleanValue('no-space-status', true);
|
).data;
|
||||||
StorageManager.setBooleanValue('afk-status', true);
|
|
||||||
|
if (!releaseData) return;
|
||||||
|
|
||||||
|
this.releaseURL = releaseData.html_url;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Wystąpił błąd podczas pobierania danych z API GitHuba: ${error}`);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<rect width="60" height="60" fill="#898989"/>
|
<rect y="-0.00012207" width="60" height="60" fill="#898989"/>
|
||||||
<path d="M30.5 6.04878H35.2195" stroke="#BFBFBF"/>
|
<path d="M29.0126 32.4897V10.2511V9.52028H30.4337V10.2511V57.234H29.0126V32.4897Z" fill="#BFBFBF"/>
|
||||||
<path d="M27.9024 4.00303C25.2115 4.10008 24.2403 6.24494 24 7.41767H32.0488C31.8486 6.16406 30.5934 3.90598 27.9024 4.00303Z" fill="black"/>
|
<path d="M26.955 29.3992V32.9949L29.7672 36.9105" stroke="black" stroke-width="0.61183"/>
|
||||||
<path d="M33.0244 29.6688V5.47793V4.68292H34.4878V5.47793V56.5854H33.0244V32.5H27.5V28.5V28.0163L28.5 28V31.5L31.9268 31.5447H33.0244V29.6688Z" fill="#BFBFBF"/>
|
<rect x="29.0051" y="34.0686" width="1.42857" height="22.8196" fill="white"/>
|
||||||
<path d="M28.1463 29.2683C30.8373 29.1712 31.8085 27.0264 32.0488 25.8537H24C24.2002 27.1073 25.4554 29.3654 28.1463 29.2683Z" fill="black"/>
|
<rect x="29.0051" y="34.0686" width="1.42857" height="5.18627" fill="#FF0000"/>
|
||||||
<path d="M32.0488 25.8537V7.86993V7.41464H24V25.8537H32.0488Z" fill="black"/>
|
<rect x="29.0051" y="54.8137" width="1.42857" height="5.18627" fill="#FF0000"/>
|
||||||
<path d="M25 26V29.5L33.8781 44.9756" stroke="black"/>
|
<rect x="29.0051" y="44.4412" width="1.42857" height="5.18627" fill="#FF0000"/>
|
||||||
<rect x="33.0244" y="31.5447" width="1.46341" height="25.0407" fill="white"/>
|
<rect x="27.8749" y="31.8649" width="3.75" height="2.17823" fill="white"/>
|
||||||
<rect x="33.0244" y="31.5447" width="1.46341" height="5.69106" fill="#FF0000"/>
|
<path d="M33.5 28.5111V8.61545V8.11176H26V28.5111H33.5Z" fill="black"/>
|
||||||
<rect x="33.0244" y="42.9268" width="1.46341" height="5.69106" fill="#FF0000"/>
|
<path d="M29.6364 5.00276C27.1289 5.09112 26.2239 7.044 26 8.11176H33.5C33.3134 6.97036 32.1438 4.91439 29.6364 5.00276Z" fill="black"/>
|
||||||
<rect x="33.0244" y="54.3089" width="1.46341" height="5.69106" fill="#FF0000"/>
|
<path d="M29.8636 31.6201C32.3711 31.5317 33.2761 29.5789 33.5 28.5111H26C26.1865 29.6525 27.3561 31.7085 29.8636 31.6201Z" fill="black"/>
|
||||||
<ellipse cx="27.9024" cy="7.40022" rx="1.46341" ry="1.40022" fill="#212121"/>
|
<ellipse cx="29.887" cy="11.8168" rx="1.38696" ry="1.28474" fill="#212121"/>
|
||||||
<ellipse cx="27.9024" cy="11.8343" rx="1.46341" ry="1.40022" fill="#212121"/>
|
<ellipse cx="29.887" cy="8.0135" rx="1.38696" ry="1.28474" fill="#212121"/>
|
||||||
<ellipse cx="27.9024" cy="16.2683" rx="1.46341" ry="1.40022" fill="#FF0000"/>
|
<ellipse cx="29.887" cy="15.6151" rx="1.38696" ry="1.28474" fill="#212121"/>
|
||||||
<ellipse cx="27.9024" cy="20.7023" rx="1.46341" ry="1.40022" fill="#212121"/>
|
<ellipse cx="29.887" cy="19.6834" rx="1.38696" ry="1.28474" fill="#212121"/>
|
||||||
<ellipse cx="27.9024" cy="25.1364" rx="1.46341" ry="1.40022" fill="#212121"/>
|
<ellipse cx="29.887" cy="23.7518" rx="1.38696" ry="1.28474" fill="#212121"/>
|
||||||
</svg>
|
<ellipse cx="29.887" cy="27.8201" rx="1.38696" ry="1.28474" fill="#00FF0A"/>
|
||||||
|
<ellipse cx="29.887" cy="19.769" rx="1.38696" ry="1.28474" fill="#00FF0A"/>
|
||||||
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
@@ -0,0 +1,18 @@
|
|||||||
|
<svg width="144" height="147" viewBox="0 0 144 147" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g filter="url(#filter0_d_1343_19)">
|
||||||
|
<path d="M115.039 101.247C116.397 98.6665 115.405 95.4739 112.824 94.1167C110.243 92.7594 107.05 93.7514 105.693 96.3323L115.039 101.247ZM89.4447 44.0402L94.1179 46.4977L99.0329 37.1513L94.3597 34.6938L89.4447 44.0402ZM105.693 96.3323C95.7398 115.259 72.3278 122.534 53.4008 112.581L48.4858 121.927C72.5746 134.595 102.372 125.336 115.039 101.247L105.693 96.3323ZM53.4008 112.581C34.4739 102.627 27.1993 79.2155 37.1525 60.2885L27.8061 55.3735C15.1383 79.4623 24.397 109.259 48.4858 121.927L53.4008 112.581ZM37.1525 60.2885C47.1057 41.3616 70.5177 34.087 89.4447 44.0402L94.3597 34.6938C70.2709 22.026 40.4738 31.2846 27.8061 55.3735L37.1525 60.2885Z" fill="white"/>
|
||||||
|
<path d="M91.2258 38.7627L101.056 20.0698L116.15 51.8695L81.3956 57.4555L91.2258 38.7627Z" fill="white"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<filter id="filter0_d_1343_19" x="18.1328" y="20.0698" width="102.017" height="115.531" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||||
|
<feOffset dy="4"/>
|
||||||
|
<feGaussianBlur stdDeviation="2"/>
|
||||||
|
<feComposite in2="hardAlpha" operator="out"/>
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
|
||||||
|
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1343_19"/>
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1343_19" result="shape"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" ?><svg enable-background="new 0 0 32 32" id="Glyph" version="1.1" viewBox="0 0 32 32" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M27.414,24.586l-5.077-5.077C23.386,17.928,24,16.035,24,14c0-5.514-4.486-10-10-10S4,8.486,4,14 s4.486,10,10,10c2.035,0,3.928-0.614,5.509-1.663l5.077,5.077c0.78,0.781,2.048,0.781,2.828,0 C28.195,26.633,28.195,25.367,27.414,24.586z M7,14c0-3.86,3.14-7,7-7s7,3.14,7,7s-3.14,7-7,7S7,17.86,7,14z" id="XMLID_223_" fill="white" /></svg>
|
||||||
|
After Width: | Height: | Size: 546 B |
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M23.75 3.75H22.5V1.25H20V3.75H10V1.25H7.5V3.75H6.25C4.875 3.75 3.75 4.875 3.75 6.25V23.75C3.75 25.125 4.875 26.25 6.25 26.25H23.75C25.125 26.25 26.25 25.125 26.25 23.75V6.25C26.25 4.875 25.125 3.75 23.75 3.75ZM23.75 23.75H6.25V11.25H23.75V23.75ZM6.25 8.75V6.25H23.75V8.75H6.25ZM8.75 13.75H21.25V16.25H8.75V13.75ZM8.75 18.75H17.5V21.25H8.75V18.75Z" fill="#F2E147"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 477 B |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M23.75 3.75H22.5V1.25H20V3.75H10V1.25H7.5V3.75H6.25C4.875 3.75 3.75 4.875 3.75 6.25V23.75C3.75 25.125 4.875 26.25 6.25 26.25H23.75C25.125 26.25 26.25 25.125 26.25 23.75V6.25C26.25 4.875 25.125 3.75 23.75 3.75ZM23.75 23.75H6.25V11.25H23.75V23.75ZM6.25 8.75V6.25H23.75V8.75H6.25ZM8.75 13.75H21.25V16.25H8.75V13.75ZM8.75 18.75H17.5V21.25H8.75V18.75Z" fill="#66FF6C"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 477 B |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M23.75 3.75H22.5V1.25H20V3.75H10V1.25H7.5V3.75H6.25C4.875 3.75 3.75 4.875 3.75 6.25V23.75C3.75 25.125 4.875 26.25 6.25 26.25H23.75C25.125 26.25 26.25 25.125 26.25 23.75V6.25C26.25 4.875 25.125 3.75 23.75 3.75ZM23.75 23.75H6.25V11.25H23.75V23.75ZM6.25 8.75V6.25H23.75V8.75H6.25ZM8.75 13.75H21.25V16.25H8.75V13.75ZM8.75 18.75H17.5V21.25H8.75V18.75Z" fill="#898989"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 477 B |
@@ -0,0 +1,237 @@
|
|||||||
|
<template>
|
||||||
|
<header class="app_header">
|
||||||
|
<div class="header_container">
|
||||||
|
<div class="header_icons">
|
||||||
|
<span class="icons-top">
|
||||||
|
<img :src="getIcon('pl')" alt="icon-pl" @click="changeLang('en')" v-if="currentLang == 'pl'" />
|
||||||
|
<img :src="getIcon('en', 'jpg')" alt="icon-en" @click="changeLang('pl')" v-else />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="header_body">
|
||||||
|
<StatusIndicator />
|
||||||
|
|
||||||
|
<span class="header_brand">
|
||||||
|
<router-link to="/">
|
||||||
|
<img :src="getImage('stacjownik-header-logo.svg')" alt="Stacjownik" />
|
||||||
|
</router-link>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="header_info">
|
||||||
|
<Clock />
|
||||||
|
|
||||||
|
<div class="info_counter">
|
||||||
|
<img :src="getIcon('dispatcher')" alt="icon dispatcher" />
|
||||||
|
<span class="text--primary">{{ onlineDispatchersCount }}</span>
|
||||||
|
|
||||||
|
<!-- <span class="g-tooltip">
|
||||||
|
<b class="text--primary">{{ factorU }}U</b>
|
||||||
|
<div class="content">Test</div>
|
||||||
|
</span> -->
|
||||||
|
|
||||||
|
<span class="text--grayed"> / </span>
|
||||||
|
<span class="text--primary">{{ onlineTrainsCount }}</span>
|
||||||
|
<img :src="getIcon('train')" alt="icon train" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span class="info_region">
|
||||||
|
<SelectBox :itemList="computedRegions" :defaultItemIndex="0" @selected="changeRegion" />
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="header_links">
|
||||||
|
<router-link class="route" active-class="route-active" to="/" exact>
|
||||||
|
{{ $t('app.sceneries') }}
|
||||||
|
</router-link>
|
||||||
|
/
|
||||||
|
<router-link class="route" active-class="route-active" to="/trains">{{ $t('app.trains') }}</router-link>
|
||||||
|
/
|
||||||
|
<router-link
|
||||||
|
class="route"
|
||||||
|
active-class="route-active"
|
||||||
|
:data-active="$route.path.startsWith('/journal')"
|
||||||
|
to="/journal"
|
||||||
|
>
|
||||||
|
{{ $t('app.journal') }}
|
||||||
|
</router-link>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import { useStore } from '../../store/store';
|
||||||
|
import options from '../../data/options.json';
|
||||||
|
import imageMixin from '../../mixins/imageMixin';
|
||||||
|
import SelectBox from '../Global/SelectBox.vue';
|
||||||
|
import StatusIndicator from './StatusIndicator.vue';
|
||||||
|
import Clock from './Clock.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
emits: ['changeLang'],
|
||||||
|
mixins: [imageMixin],
|
||||||
|
props: {
|
||||||
|
currentLang: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
store: useStore(),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
changeRegion(region: { id: string; value: string }) {
|
||||||
|
this.store.changeRegion(region);
|
||||||
|
},
|
||||||
|
changeLang(lang: string) {
|
||||||
|
this.$emit('changeLang', lang);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
onlineTrainsCount() {
|
||||||
|
return this.store.trainList.filter((train) => train.online).length;
|
||||||
|
},
|
||||||
|
|
||||||
|
onlineDispatchersCount() {
|
||||||
|
return this.store.stationList.filter(
|
||||||
|
(station) => station.onlineInfo && station.onlineInfo.region == this.store.region.id
|
||||||
|
).length;
|
||||||
|
},
|
||||||
|
|
||||||
|
factorU() {
|
||||||
|
return this.onlineDispatchersCount == 0 ? '-' : (this.onlineTrainsCount / this.onlineDispatchersCount).toFixed(2);
|
||||||
|
},
|
||||||
|
|
||||||
|
computedRegions() {
|
||||||
|
return options.regions.map((region) => {
|
||||||
|
const regionStationCount =
|
||||||
|
this.store.apiData.stations?.filter((station) => station.region == region.id && station.isOnline).length || 0;
|
||||||
|
const regionTrainCount =
|
||||||
|
this.store.apiData.trains?.filter((train) => train.region == region.id && train.online).length || 0;
|
||||||
|
return {
|
||||||
|
id: region.id,
|
||||||
|
value: `${region.value} <div class='text--grayed'>${regionStationCount} / ${regionTrainCount}</div>`,
|
||||||
|
selectedValue: region.value,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: { SelectBox, StatusIndicator, Clock },
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '../../styles/variables.scss';
|
||||||
|
@import '../../styles/responsive.scss';
|
||||||
|
|
||||||
|
// HEADER
|
||||||
|
.app_header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
background-color: $primaryCol;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
&_body {
|
||||||
|
position: relative;
|
||||||
|
max-width: 20em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&_container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
border-radius: 0 0 1em 1em;
|
||||||
|
|
||||||
|
@include smallScreen {
|
||||||
|
position: relative;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&_brand {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&_info {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
font-size: 1.15em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&_links {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
border-radius: 0.7em;
|
||||||
|
|
||||||
|
font-size: 1.25em;
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&_icons {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
|
||||||
|
padding: 0.5em;
|
||||||
|
|
||||||
|
@include smallScreen {
|
||||||
|
transform: translateX(85%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ICONS
|
||||||
|
.icons-top {
|
||||||
|
img {
|
||||||
|
width: 2.5em;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// COUNTER
|
||||||
|
.info_counter {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
span {
|
||||||
|
margin: 0 0.15em;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 1.35em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// REGION SELECTION
|
||||||
|
.info_region {
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
.select-box_content button {
|
||||||
|
background-color: transparent;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 0.1em 0.5em;
|
||||||
|
color: paleturquoise;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options {
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="loading">{{message}}</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent } from "@vue/runtime-core";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
props: ["message"],
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.loading {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
min-height: 100%;
|
|
||||||
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
|
|
||||||
font-size: calc(0.75rem + 1vw);
|
|
||||||
|
|
||||||
color: #fdc62f;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -161,20 +161,17 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { DataStatus } from '@/scripts/enums/DataStatus';
|
import { defineComponent } from 'vue';
|
||||||
import { StoreData } from '@/scripts/interfaces/StoreData';
|
import { DataStatus } from '../../scripts/enums/DataStatus';
|
||||||
import { useStore } from '@/store/store';
|
import { useStore } from '../../store/store';
|
||||||
import { StoreState } from '@/store/storeTypes';
|
import { StoreState } from '../../scripts/interfaces/store/storeTypes';
|
||||||
import { computed, defineComponent, watch } from 'vue';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
icons: {
|
|
||||||
statusIndicator: require('@/assets/signal-status-indicator.svg'),
|
|
||||||
},
|
|
||||||
tooltipActive: false,
|
tooltipActive: false,
|
||||||
indicator: {
|
indicator: {
|
||||||
|
offline: false,
|
||||||
status: DataStatus.Loading,
|
status: DataStatus.Loading,
|
||||||
message: 'data-status.S3',
|
message: 'data-status.S3',
|
||||||
},
|
},
|
||||||
@@ -196,6 +193,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
dataStatus: store.dataStatuses,
|
dataStatus: store.dataStatuses,
|
||||||
|
store,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -209,6 +207,13 @@ export default defineComponent({
|
|||||||
const trainsDataStatus = statuses.trains;
|
const trainsDataStatus = statuses.trains;
|
||||||
const dispatcherDataStatus = statuses.dispatchers;
|
const dispatcherDataStatus = statuses.dispatchers;
|
||||||
|
|
||||||
|
if (this.store.isOffline) {
|
||||||
|
this.setSignalStatus(DataStatus.Initialized);
|
||||||
|
this.indicator.status = DataStatus.Initialized;
|
||||||
|
this.indicator.message = 'data-status.S1-offline';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (connectionStatus == DataStatus.Error) {
|
if (connectionStatus == DataStatus.Error) {
|
||||||
this.setSignalStatus(connectionStatus);
|
this.setSignalStatus(connectionStatus);
|
||||||
this.indicator.status = connectionStatus;
|
this.indicator.status = connectionStatus;
|
||||||
@@ -255,6 +260,10 @@ export default defineComponent({
|
|||||||
this.orangeLight = false;
|
this.orangeLight = false;
|
||||||
this.redBottomLight = false;
|
this.redBottomLight = false;
|
||||||
|
|
||||||
|
if (status == DataStatus.Initialized) {
|
||||||
|
this.redTopLight = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (status == DataStatus.Loaded) {
|
if (status == DataStatus.Loaded) {
|
||||||
this.greenLight = true;
|
this.greenLight = true;
|
||||||
}
|
}
|
||||||
@@ -294,10 +303,11 @@ export default defineComponent({
|
|||||||
|
|
||||||
.status-indicator {
|
.status-indicator {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
transform: translateX(12em);
|
right: 0;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
|
||||||
|
transform: translateX(1.5em);
|
||||||
}
|
}
|
||||||
|
|
||||||
.indicator {
|
.indicator {
|
||||||
@@ -322,7 +332,7 @@ export default defineComponent({
|
|||||||
background-color: #171717;
|
background-color: #171717;
|
||||||
border-radius: 0.75em;
|
border-radius: 0.75em;
|
||||||
|
|
||||||
min-width: 13em;
|
width: 13em;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
overflow: none;
|
overflow: none;
|
||||||
|
|
||||||
@@ -346,22 +356,16 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
@include midScreen() {
|
@include midScreen() {
|
||||||
left: 50%;
|
left: auto;
|
||||||
top: 100%;
|
right: 200%;
|
||||||
|
|
||||||
transform: translate(-50%, 0);
|
|
||||||
margin-left: 0;
|
|
||||||
margin-top: 0.75em;
|
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
border-left: 10px solid transparent;
|
|
||||||
border-right: 10px solid transparent;
|
border-right: 10px solid transparent;
|
||||||
border-bottom: 10px solid #171717;
|
border-left: 12px solid #171717;
|
||||||
|
right: 0;
|
||||||
|
left: auto;
|
||||||
|
|
||||||
top: 0;
|
transform: translate(100%, -50%);
|
||||||
left: 50%;
|
|
||||||
|
|
||||||
transform: translate(-50%, -100%);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,3 +375,4 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,168 @@
|
|||||||
|
<template>
|
||||||
|
<transition name="modal-anim">
|
||||||
|
<section class="update-modal card" v-if="releaseData && modalOpen">
|
||||||
|
<h2 class="modal_header text--primary">
|
||||||
|
<img :src="getImage('stacjownik-header-logo.svg')" alt="stacjownik logo" />
|
||||||
|
|
||||||
|
{{ releaseData.tag_name }}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div class="horizontal"></div>
|
||||||
|
|
||||||
|
<div class="modal_content">
|
||||||
|
<h3>{{ $t('update.title') }}</h3>
|
||||||
|
<a :href="releaseData.html_url" target="_blank">{{ $t('update.release-link') }}</a>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<p>{{ $t('update.paragraph1') }}</p>
|
||||||
|
|
||||||
|
<!-- <div class="modal_changelog" v-html="markdownReleaseBody"></div> -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal_actions">
|
||||||
|
<button class="btn btn--option" @click="modalOpen = false">{{ $t('update.confirm-button') }}</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import axios from 'axios';
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import packageInfo from '../../../package.json';
|
||||||
|
import imageMixin from '../../mixins/imageMixin';
|
||||||
|
import { ReleaseAPIData } from '../../scripts/interfaces/github_api/ReleaseAPIData';
|
||||||
|
import StorageManager from '../../scripts/managers/storageManager';
|
||||||
|
import { useStore } from '../../store/store';
|
||||||
|
|
||||||
|
|
||||||
|
const GH_LASTEST_RELEASE_URL = 'https://api.github.com/repos/Spythere/stacjownik/releases/latest';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
mixins: [imageMixin],
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.fetchReleases();
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
modalOpen: false,
|
||||||
|
|
||||||
|
releaseData: null as ReleaseAPIData | null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
store: useStore()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
async fetchReleases() {
|
||||||
|
const storedVersion = StorageManager.getStringValue('appVersion');
|
||||||
|
const appVersion = packageInfo.version;
|
||||||
|
|
||||||
|
// Zmiana
|
||||||
|
if (appVersion != storedVersion) {
|
||||||
|
StorageManager.setStringValue('appVersion', appVersion);
|
||||||
|
|
||||||
|
// Znajdź changelog na GitHubie, jeśli jest pokaż modal
|
||||||
|
try {
|
||||||
|
const releaseData: ReleaseAPIData = await (await axios.get(GH_LASTEST_RELEASE_URL)).data;
|
||||||
|
if (!releaseData) return;
|
||||||
|
|
||||||
|
const lastReleaseVersion = releaseData.tag_name.slice(1);
|
||||||
|
|
||||||
|
if (lastReleaseVersion == appVersion) {
|
||||||
|
this.releaseData = releaseData;
|
||||||
|
this.modalOpen = true;
|
||||||
|
|
||||||
|
StorageManager.setStringValue('releaseURL', releaseData.html_url);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Wystąpił błąd podczas pobierania danych z API GitHuba: ${error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '../../styles/card.scss';
|
||||||
|
@import '../../styles/responsive.scss';
|
||||||
|
|
||||||
|
|
||||||
|
.modal-anim {
|
||||||
|
&-enter-active,
|
||||||
|
&-leave-active {
|
||||||
|
transition: all $animDuration $animType;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-enter-from,
|
||||||
|
&-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translate(-50%, -50%) scale(0.45);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-modal {
|
||||||
|
text-align: center;
|
||||||
|
background-color: var(--clr-secondary);
|
||||||
|
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.horizontal {
|
||||||
|
margin: 1em 0;
|
||||||
|
|
||||||
|
height: 2px;
|
||||||
|
width: 100%;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal_header {
|
||||||
|
font-size: 1.6em;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 50%;
|
||||||
|
vertical-align: text-top;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal_content {
|
||||||
|
font-size: 1.1em;
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal_actions {
|
||||||
|
margin-top: 2em;
|
||||||
|
|
||||||
|
button {
|
||||||
|
color: white;
|
||||||
|
padding: 0.5em;
|
||||||
|
font-size: 1.2em;
|
||||||
|
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal_changelog {
|
||||||
|
font-size: 0.8em;
|
||||||
|
margin-top: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include smallScreen {
|
||||||
|
.update-modal {
|
||||||
|
height: auto;
|
||||||
|
max-width: 95%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
<template>
|
||||||
|
<div class="update-prompt">
|
||||||
|
<transition name="prompt-anim">
|
||||||
|
<div class="prompt_content" v-if="!hidePrompt && needRefresh">
|
||||||
|
<div>{{ $t('update.title') }}</div>
|
||||||
|
|
||||||
|
<div class="prompt_actions">
|
||||||
|
<button class="btn btn--filled" @click="updateServiceWorker(true)">{{ $t('update.confirm-button') }}</button>
|
||||||
|
<button class="btn btn--filled" @click="hidePrompt = true">{{ $t('update.later-button') }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import useCustomSW from '../../mixins/useCustomSW';
|
||||||
|
|
||||||
|
const hidePrompt = ref(false);
|
||||||
|
const { needRefresh, updateServiceWorker } = useCustomSW();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '../../styles/variables.scss';
|
||||||
|
|
||||||
|
.update-prompt {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prompt_content {
|
||||||
|
margin: 1em;
|
||||||
|
padding: 1em;
|
||||||
|
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: black;
|
||||||
|
|
||||||
|
box-shadow: 0 0 10px 1px $accentCol;
|
||||||
|
border-radius: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prompt_actions {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 1em;
|
||||||
|
gap: 0.5em;
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animation
|
||||||
|
.prompt-anim {
|
||||||
|
&-enter-active,
|
||||||
|
&-leave-active {
|
||||||
|
transition: all 120ms ease-in;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-enter-from,
|
||||||
|
&-leave-to {
|
||||||
|
transform: translateY(100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
<template>
|
|
||||||
<section
|
|
||||||
class="updates card"
|
|
||||||
v-if="cardOpen"
|
|
||||||
>
|
|
||||||
<h2>Ostatnie aktualizacje w Stacjowniku</h2>
|
|
||||||
<p>Tutaj będą pojawiać się informacje o kolejnych nowościach na stronie :)</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li
|
|
||||||
v-for="(update, i) in updates"
|
|
||||||
:key="i"
|
|
||||||
>
|
|
||||||
<div>{{update.date}}</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<span
|
|
||||||
v-for="(line, l) in content"
|
|
||||||
:key="l"
|
|
||||||
>{{line}}</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { defineComponent } from "@vue/runtime-core";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
updates: {
|
|
||||||
date: "08/08/20",
|
|
||||||
content: [
|
|
||||||
"Lekko odświeżono wygląd strony, dodano nowy widok z pociągami online",
|
|
||||||
"Dodano animacje zmieniania widoków (zakładek)",
|
|
||||||
"Dodano przycisk zamykający kartę z filtrami",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
</style>
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<button class="action-btn">
|
<button class="action-btn btn--filled">
|
||||||
<div class="button_content">
|
<div class="button_content">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
@@ -16,47 +16,6 @@ export default defineComponent({});
|
|||||||
@import "../../styles/variables";
|
@import "../../styles/variables";
|
||||||
@import "../../styles/responsive";
|
@import "../../styles/responsive";
|
||||||
|
|
||||||
.action-btn {
|
|
||||||
background: #333;
|
|
||||||
border: none;
|
|
||||||
|
|
||||||
color: #bdbdbd;
|
|
||||||
|
|
||||||
font-size: 1em;
|
|
||||||
font-weight: 500;
|
|
||||||
|
|
||||||
padding: 0.35em 0.65em;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
transition: all 0.3s;
|
|
||||||
|
|
||||||
&.outlined {
|
|
||||||
border: 1px solid white;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 1.25em;
|
|
||||||
vertical-align: middle;
|
|
||||||
margin-right: 0.35em;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: 1em;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.open {
|
|
||||||
color: $accentCol;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:focus {
|
|
||||||
color: $accentCol;
|
|
||||||
background: #5c5c5c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.button_content {
|
.button_content {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export default defineComponent({
|
|||||||
.loading {
|
.loading {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translate(-50%, -50%);
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
<img
|
<img
|
||||||
class="search-exit"
|
class="search-exit"
|
||||||
:src="exitIcon"
|
:src="getIcon('exit')"
|
||||||
alt="exit-icon"
|
alt="exit-icon"
|
||||||
@click="clearValue"
|
@click="clearValue"
|
||||||
/>
|
/>
|
||||||
@@ -18,11 +18,11 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, ref, watch } from "vue";
|
import { defineComponent, ref, watch } from "vue";
|
||||||
|
import imageMixin from "../../mixins/imageMixin";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
data: () => ({
|
mixins: [imageMixin],
|
||||||
exitIcon: require("@/assets/icon-exit.svg"),
|
|
||||||
}),
|
|
||||||
emits: ["update:searchedValue", "clearValue"],
|
emits: ["update:searchedValue", "clearValue"],
|
||||||
props: {
|
props: {
|
||||||
searchedValue: {
|
searchedValue: {
|
||||||
@@ -59,7 +59,7 @@ export default defineComponent({
|
|||||||
emit("clearValue");
|
emit("clearValue");
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateValue = (e) => {
|
const updateValue = (e: any) => {
|
||||||
if (!props.updateOnInput && e.keyCode == 13)
|
if (!props.updateOnInput && e.keyCode == 13)
|
||||||
emit("update:searchedValue", compSearchedValue.value);
|
emit("update:searchedValue", compSearchedValue.value);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="select-box" >
|
<div class="select-box">
|
||||||
<div class="select-box_content">
|
<div class="select-box_content">
|
||||||
<button class="selected" @click="toggleBox">
|
<button class="selected" @click="toggleBox">
|
||||||
<span class="text--primary">{{ prefix }}</span>
|
|
||||||
<span>{{ computedSelectedItem.selectedValue || computedSelectedItem.value }}</span>
|
<span>{{ computedSelectedItem.selectedValue || computedSelectedItem.value }}</span>
|
||||||
|
|
||||||
|
<div class="arrow">
|
||||||
|
<img :src="listOpen ? getIcon('arrow-asc') : getIcon('arrow-desc')" alt="arrow-icon" />
|
||||||
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<ul class="options" :ref="(el) => (listRef = el as Element)">
|
<ul class="options" :ref="(el) => (listRef = el as Element)">
|
||||||
@@ -22,15 +25,12 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="arrow">
|
|
||||||
<img :src="listOpen ? ascIcon : descIcon" alt="arrow-icon" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, Ref, ref } from '@vue/runtime-core';
|
import { defineComponent, Ref, ref, computed } from 'vue';
|
||||||
|
import imageMixin from '../../mixins/imageMixin';
|
||||||
|
|
||||||
interface Item {
|
interface Item {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -40,6 +40,7 @@ interface Item {
|
|||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
emits: ['selected'],
|
emits: ['selected'],
|
||||||
|
mixins: [imageMixin],
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
itemList: {
|
itemList: {
|
||||||
@@ -58,11 +59,6 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data: () => ({
|
|
||||||
ascIcon: require('@/assets/icon-arrow-asc.svg'),
|
|
||||||
descIcon: require('@/assets/icon-arrow-desc.svg'),
|
|
||||||
}),
|
|
||||||
|
|
||||||
setup(props) {
|
setup(props) {
|
||||||
let listRef: Ref<Element | null> = ref(null);
|
let listRef: Ref<Element | null> = ref(null);
|
||||||
let buttonRef: Ref<HTMLButtonElement | null> = ref(null);
|
let buttonRef: Ref<HTMLButtonElement | null> = ref(null);
|
||||||
@@ -133,44 +129,25 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
.select-box {
|
.select-box {
|
||||||
position: relative;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.arrow {
|
.arrow {
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
right: 0;
|
|
||||||
padding: 0.5em;
|
|
||||||
|
|
||||||
img {
|
img {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
width: 1.35em;
|
width: 1.35em;
|
||||||
}
|
}
|
||||||
|
|
||||||
transform: translateY(-50%);
|
|
||||||
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button.selected {
|
button.selected {
|
||||||
background: #333;
|
color: paleturquoise;
|
||||||
color: white;
|
|
||||||
|
|
||||||
font-size: 1em;
|
font-weight: bold;
|
||||||
|
padding: 0.1em 0.5em;
|
||||||
padding: 0.35em 0.5em;
|
|
||||||
margin-right: 1.4em;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
|
|
||||||
text-align: left;
|
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
background: #555;
|
background-color: #262626;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,8 +168,9 @@ ul.options {
|
|||||||
height: auto;
|
height: auto;
|
||||||
|
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
|
|
||||||
li.option {
|
li.option {
|
||||||
@@ -206,6 +184,7 @@ li.option {
|
|||||||
appearance: none;
|
appearance: none;
|
||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
background: none;
|
||||||
|
|
||||||
&:focus + span {
|
&:focus + span {
|
||||||
color: $accentCol;
|
color: $accentCol;
|
||||||
@@ -221,11 +200,11 @@ li.option {
|
|||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background-color: hsla(0, 0%, 15%, 0.95);
|
background-color: #262626f2;
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:focus {
|
&:focus {
|
||||||
background-color: hsla(0, 0%, 20%, 0.95);
|
background-color: #333333f2;
|
||||||
}
|
}
|
||||||
|
|
||||||
padding: 0.5em 0;
|
padding: 0.5em 0;
|
||||||
|
|||||||
@@ -47,9 +47,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import dateMixin from '@/mixins/dateMixin';
|
|
||||||
import TrainStop from '@/scripts/interfaces/TrainStop';
|
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
|
import dateMixin from '../../mixins/dateMixin';
|
||||||
|
import TrainStop from '../../scripts/interfaces/TrainStop';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
mixins: [dateMixin],
|
mixins: [dateMixin],
|
||||||
|
|||||||
@@ -0,0 +1,152 @@
|
|||||||
|
<template>
|
||||||
|
<div class="train-modal" v-if="chosenTrain" @keydown.esc="closeModal">
|
||||||
|
<div class="modal_background" @click="closeModal"></div>
|
||||||
|
<div class="modal_content" ref="content" tabindex="0">
|
||||||
|
<button class="btn exit" @click="closeModal">
|
||||||
|
<img :src="getIcon('exit')" alt="close card" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<TrainInfo :train="chosenTrain" :extended="false" ref="trainInfo" />
|
||||||
|
<TrainSchedule :train="chosenTrain" tabindex="0" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import imageMixin from '../../mixins/imageMixin';
|
||||||
|
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
||||||
|
import trainInfoMixin from '../../mixins/trainInfoMixin';
|
||||||
|
import { useStore } from '../../store/store';
|
||||||
|
import TrainInfo from '../TrainsView/TrainInfo.vue';
|
||||||
|
import TrainSchedule from '../TrainsView/TrainSchedule.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: { TrainInfo, TrainSchedule },
|
||||||
|
mixins: [trainInfoMixin, modalTrainMixin, imageMixin],
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isTopBarVisible: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
const store = useStore();
|
||||||
|
|
||||||
|
return {
|
||||||
|
store,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
activated() {
|
||||||
|
const contentEl = this.$refs['content'] as HTMLElement;
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
contentEl.focus();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
handleContentScroll(e: Event) {
|
||||||
|
const trainInfoCompHeight: number = (this.$refs['trainInfo'] as any).$el.getBoundingClientRect().height;
|
||||||
|
|
||||||
|
const posTop = (e.target as HTMLElement).scrollTop;
|
||||||
|
this.isTopBarVisible = posTop > trainInfoCompHeight;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '../../styles/responsive.scss';
|
||||||
|
@import '../../styles/card.scss';
|
||||||
|
|
||||||
|
.top-info-bar-anim {
|
||||||
|
&-enter-active,
|
||||||
|
&-leave-active {
|
||||||
|
transition: all 150ms ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-enter-from,
|
||||||
|
&-leave-to {
|
||||||
|
transform: translate(-50%, -50%) scale(0.8);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.exit {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
|
||||||
|
margin: 0.5em 1em;
|
||||||
|
|
||||||
|
padding: 0.25em;
|
||||||
|
|
||||||
|
z-index: 201;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 1.5rem;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.train-modal {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
color: white;
|
||||||
|
z-index: 200;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal_background {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
background-color: rgba(0, 0, 0, 0.55);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal_content {
|
||||||
|
position: relative;
|
||||||
|
overflow-y: scroll;
|
||||||
|
|
||||||
|
margin-top: 1em;
|
||||||
|
|
||||||
|
width: 95vw;
|
||||||
|
max-height: 96vh;
|
||||||
|
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
box-shadow: 0 0 15px 10px #0e0e0e;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include midScreen {
|
||||||
|
.exit {
|
||||||
|
margin: 0.5em;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 1.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include smallScreen {
|
||||||
|
|
||||||
|
.modal_content {
|
||||||
|
max-height: 85vh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,250 @@
|
|||||||
|
<template>
|
||||||
|
<section class="daily-stats">
|
||||||
|
<span :data-active="statsStatus">
|
||||||
|
<b v-if="statsStatus == DataStatus.Loading">
|
||||||
|
{{ $t('app.loading') }}
|
||||||
|
</b>
|
||||||
|
|
||||||
|
<b v-else-if="stats.distanceSum == null">
|
||||||
|
{{ $t('journal.daily-stats-info') }}
|
||||||
|
</b>
|
||||||
|
|
||||||
|
<span class="stats-list" v-else>
|
||||||
|
<h3>
|
||||||
|
{{ $t('journal.daily-stats-title') }}
|
||||||
|
<b class="text--primary">{{ new Date().toLocaleDateString($i18n.locale) }}</b>
|
||||||
|
</h3>
|
||||||
|
<hr style="margin-bottom: 0.5em" />
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<template #distance>
|
||||||
|
<b class="text--primary"> {{ stats.distanceSum?.toFixed(2) }} km</b>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="stats.timetableId">
|
||||||
|
•
|
||||||
|
<i18n-t keypath="journal.timetable-stats-longest">
|
||||||
|
<template #id>
|
||||||
|
<router-link :to="`/journal/timetables?timetableId=${stats.timetableId}`">
|
||||||
|
<b>{{ stats.timetableId }}</b>
|
||||||
|
</router-link>
|
||||||
|
</template>
|
||||||
|
<template #author>
|
||||||
|
<router-link :to="`/journal/dispatchers?dispatcherName=${stats.timetableAuthor}`">
|
||||||
|
<b>{{ stats.timetableAuthor }}</b>
|
||||||
|
</router-link>
|
||||||
|
</template>
|
||||||
|
<template #driver>
|
||||||
|
<b class="text--primary">{{ stats.timetableDriver }}</b>
|
||||||
|
</template>
|
||||||
|
<template #distance>
|
||||||
|
<b class="text--primary">{{ stats.timetableRouteDistance }} km</b>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="firstPlaceDispatchers.length == 1">
|
||||||
|
•
|
||||||
|
<i18n-t keypath="journal.timetable-stats-most-active-dr">
|
||||||
|
<template #dispatcher>
|
||||||
|
<router-link :to="`/journal/dispatchers?dispatcherName=${firstPlaceDispatchers[0].name}`">
|
||||||
|
<b>{{ firstPlaceDispatchers[0].name }}</b>
|
||||||
|
</router-link>
|
||||||
|
</template>
|
||||||
|
<template #count>
|
||||||
|
<b class="text--primary">
|
||||||
|
{{ firstPlaceDispatchers[0].count }}
|
||||||
|
{{ $t('journal.timetable-count', firstPlaceDispatchers[0].count) }}
|
||||||
|
</b>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="firstPlaceDispatchers.length > 1">
|
||||||
|
•
|
||||||
|
<i18n-t keypath="journal.timetable-stats-most-active-dr-many">
|
||||||
|
<template #dispatchers>
|
||||||
|
<span v-for="(disp, i) in firstPlaceDispatchers">
|
||||||
|
<span v-if="i == firstPlaceDispatchers.length - 1"> {{ $t('general.and') }} </span>
|
||||||
|
|
||||||
|
<router-link :to="`/journal/dispatchers?dispatcherName=${disp.name}`">
|
||||||
|
<b>{{ disp.name }}</b>
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
<span v-if="i < firstPlaceDispatchers.length - 2">, </span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #count>
|
||||||
|
<b class="text--primary">
|
||||||
|
{{ firstPlaceDispatchers[0].count }}
|
||||||
|
{{ $t('journal.timetable-count', firstPlaceDispatchers[0].count) }}
|
||||||
|
</b>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<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.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 }} km</b>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import axios from 'axios';
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import dateMixin from '../../mixins/dateMixin';
|
||||||
|
import { DataStatus } from '../../scripts/enums/DataStatus';
|
||||||
|
import { ITimetablesDailyStats, ITimetablesDailyStatsResponse } from '../../scripts/interfaces/api/StatsAPIData';
|
||||||
|
import { URLs } from '../../scripts/utils/apiURLs';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
mixins: [dateMixin],
|
||||||
|
emits: ['toggleStatsOpen'],
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
DataStatus,
|
||||||
|
statsStatus: DataStatus.Loading,
|
||||||
|
intervalId: -1,
|
||||||
|
|
||||||
|
stats: {
|
||||||
|
totalTimetables: 0,
|
||||||
|
distanceSum: 0,
|
||||||
|
distanceAvg: 0,
|
||||||
|
timetableAuthor: '',
|
||||||
|
timetableDriver: '',
|
||||||
|
timetableId: 0,
|
||||||
|
timetableRouteDistance: 0,
|
||||||
|
longestDuties: [],
|
||||||
|
mostActiveDrivers: [],
|
||||||
|
mostActiveDispatchers: [],
|
||||||
|
} as ITimetablesDailyStats,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
activated() {
|
||||||
|
this.startFetchingDailyStats();
|
||||||
|
this.$emit('toggleStatsOpen', true);
|
||||||
|
},
|
||||||
|
|
||||||
|
deactivated() {
|
||||||
|
this.stopFetchingDailyStats();
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
firstPlaceDispatchers() {
|
||||||
|
if (this.stats.mostActiveDispatchers.length == 0) return [];
|
||||||
|
const maxCount = this.stats.mostActiveDispatchers[0].count;
|
||||||
|
|
||||||
|
return this.stats.mostActiveDispatchers.filter((disp) => disp.count === maxCount);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
async fetchDailyTimetableStats() {
|
||||||
|
try {
|
||||||
|
const res: ITimetablesDailyStatsResponse = await (
|
||||||
|
await axios.get(`${URLs.stacjownikAPI}/api/getDailyTimetableStats`)
|
||||||
|
).data;
|
||||||
|
|
||||||
|
this.stats = {
|
||||||
|
totalTimetables: res.totalTimetables,
|
||||||
|
distanceSum: res.distanceSum,
|
||||||
|
distanceAvg: res.distanceAvg,
|
||||||
|
timetableAuthor: res.maxTimetable?.authorName || '',
|
||||||
|
timetableDriver: res.maxTimetable?.driverName || '',
|
||||||
|
timetableId: res.maxTimetable?.id || 0,
|
||||||
|
timetableRouteDistance: res.maxTimetable?.routeDistance || 0,
|
||||||
|
|
||||||
|
mostActiveDispatchers: res.mostActiveDispatchers,
|
||||||
|
mostActiveDrivers: res.mostActiveDrivers,
|
||||||
|
longestDuties: res.longestDuties,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.statsStatus = DataStatus.Loaded;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ups! Wystąpił błąd podczas pobierania statystyk rozkładów jazdy...');
|
||||||
|
this.statsStatus = DataStatus.Error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
startFetchingDailyStats() {
|
||||||
|
this.fetchDailyTimetableStats();
|
||||||
|
|
||||||
|
if (this.intervalId != -1) return;
|
||||||
|
|
||||||
|
this.intervalId = setInterval(this.fetchDailyTimetableStats, 60000);
|
||||||
|
},
|
||||||
|
|
||||||
|
stopFetchingDailyStats() {
|
||||||
|
clearInterval(this.intervalId);
|
||||||
|
this.intervalId = -1;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '../../styles/responsive.scss';
|
||||||
|
|
||||||
|
.daily-stats {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.daily-stats > span[data-active='0'] {
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-list a {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include smallScreen {
|
||||||
|
.daily-stats {
|
||||||
|
text-align: justify;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="stats_container" v-click-outside="() => (cardVisible = false)">
|
<div class="stats_container" v-click-outside="() => (cardVisible = false)">
|
||||||
<button class="stats_button btn btn--option" @click="toggleCard">
|
<button class="stats_button" @click="toggleCard">
|
||||||
Statystyki dyżurnego {{ store.dispatcherStatsName }}
|
Statystyki dyżurnego {{ store.dispatcherStatsName }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<h3>STATYSTYKI WYSTAWIONYCH ROZKŁADÓW</h3>
|
<h3>STATYSTYKI WYSTAWIONYCH ROZKŁADÓW</h3>
|
||||||
|
|
||||||
<div class="info-stats" v-if="store.dispatcherStatsData._count._all">
|
<div class="info-stats" v-if="store.dispatcherStatsData._count._all">
|
||||||
<span class="stat-badge">
|
<span class="stat-badge">
|
||||||
<span>LICZBA</span>
|
<span>LICZBA</span>
|
||||||
@@ -48,12 +49,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { DispatcherStatsAPIData } from '@/scripts/interfaces/api/DispatcherStatsAPIData';
|
|
||||||
import { TimetableHistory } from '@/scripts/interfaces/api/TimetablesAPIData';
|
|
||||||
import { URLs } from '@/scripts/utils/apiURLs';
|
|
||||||
import { useStore } from '@/store/store';
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { computed, defineComponent } from 'vue';
|
import { computed, defineComponent } from 'vue';
|
||||||
|
import { DispatcherStatsAPIData } from '../../scripts/interfaces/api/DispatcherStatsAPIData';
|
||||||
|
import { TimetableHistory } from '../../scripts/interfaces/api/TimetablesAPIData';
|
||||||
|
import { URLs } from '../../scripts/utils/apiURLs';
|
||||||
|
import { useStore } from '../../store/store';
|
||||||
import Loading from '../Global/Loading.vue';
|
import Loading from '../Global/Loading.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
@@ -161,42 +163,11 @@ h3 {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-stats {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
margin-top: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.last-timetables {
|
.last-timetables {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-badge {
|
|
||||||
margin-right: 0.5em;
|
|
||||||
padding-bottom: 1em;
|
|
||||||
|
|
||||||
span {
|
|
||||||
padding: 0.25em 0.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
span:first-child {
|
|
||||||
background-color: #4d4d4d;
|
|
||||||
}
|
|
||||||
|
|
||||||
span:last-child {
|
|
||||||
background-color: $accentCol;
|
|
||||||
color: black;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include smallScreen() {
|
|
||||||
.stats_card {
|
|
||||||
text-align: center;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
border-radius: 0 0 1em 1em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,140 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="card-dimmer" @click="closeCard"></div>
|
|
||||||
|
|
||||||
<div class="stats-card card">
|
|
||||||
<div>
|
|
||||||
<h2 class="card-title">
|
|
||||||
STATYSTYKI MASZYNISTY <span class="text--primary">{{ store.driverStatsName.toUpperCase() }}</span>
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<div class="loading" v-if="!store.driverStatsData">Ładowanie...</div>
|
|
||||||
|
|
||||||
<div v-else>
|
|
||||||
<div class="info-stats" v-if="store.driverStatsData._sum.routeDistance != null">
|
|
||||||
<span class="stat-badge">
|
|
||||||
<span>PRZEBYTO</span>
|
|
||||||
<span>{{ store.driverStatsData._sum.routeDistance.toFixed(2) }}km</span>
|
|
||||||
</span>
|
|
||||||
<span class="stat-badge">
|
|
||||||
<span>PORZUCONO</span>
|
|
||||||
<span>
|
|
||||||
{{ (store.driverStatsData._sum.routeDistance - store.driverStatsData._sum.currentDistance).toFixed(2) }}km
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="stat-badge">
|
|
||||||
<span>WYPEŁNIONO</span>
|
|
||||||
<span>{{ store.driverStatsData._count.fulfilled }} RJ</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="stat-badge">
|
|
||||||
<span>PORZUCONO</span>
|
|
||||||
<span>{{ store.driverStatsData._count._all - store.driverStatsData._count.fulfilled }} RJ</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="stat-badge">
|
|
||||||
<span>ZATWIERDZONO</span>
|
|
||||||
<span>{{ store.driverStatsData._sum.confirmedStopsCount }} stacji</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="stat-badge">
|
|
||||||
<span>PORZUCONO</span>
|
|
||||||
<span>
|
|
||||||
{{ store.driverStatsData._sum.allStopsCount - store.driverStatsData._sum.confirmedStopsCount }}
|
|
||||||
stacji
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { DriverStatsAPIData } from '@/scripts/interfaces/api/DriverStatsAPIData';
|
|
||||||
import { TimetableHistory } from '@/scripts/interfaces/api/TimetablesAPIData';
|
|
||||||
import { URLs } from '@/scripts/utils/apiURLs';
|
|
||||||
import { useStore } from '@/store/store';
|
|
||||||
import axios from 'axios';
|
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
emits: ['closeCard'],
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
const store = useStore();
|
|
||||||
return {
|
|
||||||
store,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
test: Math.random(),
|
|
||||||
lastDispatcherName: '',
|
|
||||||
|
|
||||||
lastTimetables: [] as TimetableHistory[],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
activated() {
|
|
||||||
this.fetchDispatcherStats();
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
async fetchDispatcherStats() {
|
|
||||||
this.store.driverStatsData = undefined;
|
|
||||||
|
|
||||||
const statsData: DriverStatsAPIData = await (
|
|
||||||
await axios.get(`${URLs.stacjownikAPI}/api/getDriverInfo?name=${this.store.driverStatsName}`)
|
|
||||||
).data;
|
|
||||||
|
|
||||||
const recentTimetablesData: TimetableHistory[] = await (
|
|
||||||
await axios.get(`${URLs.stacjownikAPI}/api/getTimetables?driverName=${this.store.driverStatsName}`)
|
|
||||||
).data;
|
|
||||||
|
|
||||||
this.store.driverStatsData = statsData;
|
|
||||||
this.lastTimetables = recentTimetablesData || [];
|
|
||||||
},
|
|
||||||
|
|
||||||
closeCard() {
|
|
||||||
this.$emit('closeCard');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@import '../../styles/responsive.scss';
|
|
||||||
|
|
||||||
.timetable-row {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 4fr 1fr 1fr 2fr 2fr;
|
|
||||||
gap: 0.2em;
|
|
||||||
margin: 0.5em 0;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
span {
|
|
||||||
min-width: 100px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
|
|
||||||
background-color: #4d4d4d;
|
|
||||||
padding: 0.5em 0.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include smallScreen() {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
span {
|
|
||||||
padding: 0.2em 0.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
background-color: #4d4d4d;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,425 +0,0 @@
|
|||||||
<template>
|
|
||||||
<section class="journal-timetables">
|
|
||||||
<div class="journal-wrapper">
|
|
||||||
<div class="journal_top-bar">
|
|
||||||
<JournalOptions
|
|
||||||
@on-filter-change="search"
|
|
||||||
@on-input-change="search"
|
|
||||||
@on-sorter-change="search"
|
|
||||||
:sorter-option-ids="['timestampFrom', 'duration']"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- <DispatcherStats /> -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="journal-list">
|
|
||||||
<div class="list-wrapper" ref="scrollElement">
|
|
||||||
<transition name="warning" mode="out-in">
|
|
||||||
<div :key="historyDataStatus.status">
|
|
||||||
<Loading v-if="isDataLoading || isDataInit" />
|
|
||||||
|
|
||||||
<div v-else-if="isDataError" class="journal_warning error">
|
|
||||||
{{ $t('app.error') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="journal_warning" v-else-if="historyList.length == 0">
|
|
||||||
{{ $t('app.no-result') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul v-else>
|
|
||||||
<transition-group name="journal-list-anim">
|
|
||||||
<li v-for="(doc, i) in computedHistoryList" :key="doc.id">
|
|
||||||
<div class="journal_day" v-if="isAnotherDay(i - 1, i)">
|
|
||||||
<span>{{ new Date(doc.timestampFrom).toLocaleDateString('pl-PL') }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="journal_item"
|
|
||||||
:class="{ online: doc.isOnline }"
|
|
||||||
@click="navigateToScenery(doc.stationName, doc.isOnline)"
|
|
||||||
@keydown.enter="navigateToScenery(doc.stationName, doc.isOnline)"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
<b class="text--primary">{{ doc.dispatcherName }}</b> • <b>{{ doc.stationName }}</b>
|
|
||||||
<span class="text--grayed"> #{{ doc.stationHash }} </span>
|
|
||||||
<span class="region-badge" :class="doc.region">PL1</span>
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
<span :data-status="doc.isOnline">
|
|
||||||
{{ doc.isOnline ? $t('journal.online-since') : 'OFFLINE' }}
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
{{ new Date(doc.timestampFrom).toLocaleTimeString('pl-PL', { timeStyle: 'short' }) }}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span v-if="doc.currentDuration && doc.isOnline">
|
|
||||||
({{ calculateDuration(doc.currentDuration) }})
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span v-if="doc.timestampTo">
|
|
||||||
>
|
|
||||||
{{ new Date(doc.timestampTo).toLocaleTimeString('pl-PL', { timeStyle: 'short' }) }}
|
|
||||||
({{ $t('journal.duty-lasted') }} {{ calculateDuration(doc.currentDuration!) }})
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</transition-group>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</div>
|
|
||||||
</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>
|
|
||||||
</section>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { computed, defineComponent, JournalFilter, JournalSearcher, provide, reactive, Ref, ref, watch } from 'vue';
|
|
||||||
import axios from 'axios';
|
|
||||||
|
|
||||||
import SearchBox from '@/components/Global/SearchBox.vue';
|
|
||||||
import dateMixin from '@/mixins/dateMixin';
|
|
||||||
import { DataStatus } from '@/scripts/enums/DataStatus';
|
|
||||||
|
|
||||||
import ActionButton from '@/components/Global/ActionButton.vue';
|
|
||||||
import JournalOptions from '@/components/JournalView/JournalOptions.vue';
|
|
||||||
import DispatcherStats from '@/components/JournalView/DispatcherStats.vue';
|
|
||||||
|
|
||||||
import { URLs } from '@/scripts/utils/apiURLs';
|
|
||||||
import { useStore } from '@/store/store';
|
|
||||||
import { DispatcherStatsAPIData } from '@/scripts/interfaces/api/DispatcherStatsAPIData';
|
|
||||||
import Loading from '../Global/Loading.vue';
|
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
|
||||||
|
|
||||||
const PROD_MODE = process.env.VUE_APP_JORUNAL_DISPATCHERS_DEV != '1' || process.env.NODE_ENV === 'production';
|
|
||||||
|
|
||||||
const DISPATCHERS_API_URL = (PROD_MODE ? `${URLs.stacjownikAPI}/api` : 'http://localhost:3001/api') + '/getDispatchers';
|
|
||||||
|
|
||||||
interface DispatcherHistoryItem {
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
stationName: string;
|
|
||||||
stationHash: string;
|
|
||||||
region: string;
|
|
||||||
|
|
||||||
dispatcherName: string;
|
|
||||||
dispatcherId: number;
|
|
||||||
|
|
||||||
timestampFrom: number;
|
|
||||||
timestampTo?: number;
|
|
||||||
currentDuration?: number;
|
|
||||||
|
|
||||||
lastOnlineTimestamp: number;
|
|
||||||
|
|
||||||
isOnline: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: { SearchBox, ActionButton, JournalOptions, DispatcherStats, Loading },
|
|
||||||
mixins: [dateMixin],
|
|
||||||
name: 'JournalDispatchers',
|
|
||||||
|
|
||||||
props: {
|
|
||||||
sceneryName: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
dispatcherName: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
data: () => ({
|
|
||||||
icons: {
|
|
||||||
arrow: require('@/assets/icon-arrow-asc.svg'),
|
|
||||||
},
|
|
||||||
|
|
||||||
currentQuery: '',
|
|
||||||
scrollDataLoaded: true,
|
|
||||||
scrollNoMoreData: false,
|
|
||||||
|
|
||||||
showReturnButton: false,
|
|
||||||
statsCardOpen: false,
|
|
||||||
}),
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
const historyDataStatus: Ref<{ status: DataStatus; error: string | null }> = ref({
|
|
||||||
status: DataStatus.Loading,
|
|
||||||
error: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
const sorterActive = ref({ id: 'timestampFrom', dir: -1 });
|
|
||||||
const journalFilterActive = ref({});
|
|
||||||
const searchersValues = reactive([
|
|
||||||
{ id: 'search-dispatcher', value: '' },
|
|
||||||
{ id: 'search-station', value: '' },
|
|
||||||
]);
|
|
||||||
|
|
||||||
const countFromIndex = ref(0);
|
|
||||||
const countLimit = 15;
|
|
||||||
|
|
||||||
provide('sorterActive', sorterActive);
|
|
||||||
provide('journalFilterActive', journalFilterActive);
|
|
||||||
provide('searchersValues', searchersValues);
|
|
||||||
|
|
||||||
const scrollElement: Ref<HTMLElement | null> = ref(null);
|
|
||||||
|
|
||||||
return {
|
|
||||||
store: useStore(),
|
|
||||||
|
|
||||||
historyList: ref([]) as Ref<DispatcherHistoryItem[]>,
|
|
||||||
historyDataStatus,
|
|
||||||
|
|
||||||
isDataLoading: computed(() => historyDataStatus.value.status === DataStatus.Loading),
|
|
||||||
isDataError: computed(() => historyDataStatus.value.status === DataStatus.Error),
|
|
||||||
isDataInit: computed(() => historyDataStatus.value.status === DataStatus.Initialized),
|
|
||||||
|
|
||||||
sorterActive,
|
|
||||||
searchersValues,
|
|
||||||
|
|
||||||
countFromIndex,
|
|
||||||
countLimit,
|
|
||||||
|
|
||||||
scrollElement,
|
|
||||||
maxCount: ref(15),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
computedHistoryList() {
|
|
||||||
return this.historyList.filter(
|
|
||||||
(doc) => doc.isOnline || (doc.currentDuration && doc.currentDuration > 10 * 60000)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
activated() {
|
|
||||||
if (this.sceneryName || this.dispatcherName) {
|
|
||||||
this.searchersValues[1].value = this.sceneryName?.toString() || '';
|
|
||||||
this.searchersValues[0].value = this.dispatcherName?.toString() || '';
|
|
||||||
this.search();
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('scroll', this.handleScroll);
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted() {
|
|
||||||
if (!this.sceneryName && !this.dispatcherName) {
|
|
||||||
this.search();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
deactivated() {
|
|
||||||
window.removeEventListener('scroll', this.handleScroll);
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
closeDispatcherStatsCard() {
|
|
||||||
this.statsCardOpen = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
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.computedHistoryList[prevIndex].timestampFrom).getDate() !=
|
|
||||||
new Date(this.computedHistoryList[currIndex].timestampFrom).getDate()
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleScroll() {
|
|
||||||
this.showReturnButton = window.scrollY > window.innerHeight;
|
|
||||||
|
|
||||||
const element = this.$refs.scrollElement as HTMLElement;
|
|
||||||
|
|
||||||
if (
|
|
||||||
element.getBoundingClientRect().bottom * 0.85 < window.innerHeight &&
|
|
||||||
this.scrollDataLoaded &&
|
|
||||||
!this.scrollNoMoreData &&
|
|
||||||
this.historyDataStatus.status == DataStatus.Loaded
|
|
||||||
)
|
|
||||||
this.addHistoryData();
|
|
||||||
},
|
|
||||||
|
|
||||||
scrollToTop() {
|
|
||||||
window.scrollTo({ top: 0 });
|
|
||||||
},
|
|
||||||
|
|
||||||
search() {
|
|
||||||
this.fetchHistoryData({
|
|
||||||
searchers: this.searchersValues,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.scrollNoMoreData = false;
|
|
||||||
this.scrollDataLoaded = true;
|
|
||||||
},
|
|
||||||
|
|
||||||
async addHistoryData() {
|
|
||||||
this.scrollDataLoaded = false;
|
|
||||||
|
|
||||||
const countFrom = this.historyList.length;
|
|
||||||
|
|
||||||
const responseData: DispatcherHistoryItem[] = await (
|
|
||||||
await axios.get(`${DISPATCHERS_API_URL}?${this.currentQuery}&countFrom=${countFrom}`)
|
|
||||||
).data;
|
|
||||||
|
|
||||||
if (!responseData) return;
|
|
||||||
|
|
||||||
if (responseData.length == 0) {
|
|
||||||
this.scrollNoMoreData = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.historyList.push(...responseData);
|
|
||||||
this.scrollDataLoaded = true;
|
|
||||||
},
|
|
||||||
|
|
||||||
async fetchHistoryData(
|
|
||||||
props: {
|
|
||||||
searchers?: JournalSearcher[];
|
|
||||||
filter?: JournalFilter;
|
|
||||||
} = {}
|
|
||||||
) {
|
|
||||||
this.historyDataStatus.status = DataStatus.Loading;
|
|
||||||
|
|
||||||
const queries: string[] = [];
|
|
||||||
|
|
||||||
const dispatcher = props.searchers?.find((s) => s.id == 'search-dispatcher')?.value.trim();
|
|
||||||
const station = props.searchers?.find((s) => s.id == 'search-station')?.value.trim();
|
|
||||||
|
|
||||||
if (dispatcher) queries.push(`dispatcherName=${dispatcher}`);
|
|
||||||
if (station) queries.push(`stationName=${station}`);
|
|
||||||
|
|
||||||
// Z API: const SORT_TYPES = ['allStopsCount', 'endDate', 'beginDate', 'routeDistance'];
|
|
||||||
if (this.sorterActive.id == 'timestampFrom') queries.push('sortBy=timestampFrom');
|
|
||||||
else if (this.sorterActive.id == 'duration') queries.push('sortBy=currentDuration');
|
|
||||||
else queries.push('sortBy=timestampFrom');
|
|
||||||
|
|
||||||
queries.push('countLimit=15');
|
|
||||||
|
|
||||||
this.currentQuery = queries.join('&');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const responseData: DispatcherHistoryItem[] = await (
|
|
||||||
await axios.get(`${DISPATCHERS_API_URL}?${this.currentQuery}`)
|
|
||||||
).data;
|
|
||||||
|
|
||||||
if (!responseData) {
|
|
||||||
this.historyDataStatus.status = DataStatus.Error;
|
|
||||||
this.historyDataStatus.error = 'Brak danych!';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!responseData) return;
|
|
||||||
|
|
||||||
// Response data exists
|
|
||||||
this.historyList = responseData;
|
|
||||||
|
|
||||||
// Stats display
|
|
||||||
this.store.dispatcherStatsName =
|
|
||||||
this.historyList.length > 0 && this.searchersValues[0].value.trim() ? this.historyList[0].dispatcherName : '';
|
|
||||||
|
|
||||||
this.historyDataStatus.status = DataStatus.Loaded;
|
|
||||||
} catch (error) {
|
|
||||||
this.historyDataStatus.status = DataStatus.Error;
|
|
||||||
this.historyDataStatus.error = 'Ups! Coś poszło nie tak!';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@import '../../styles/JournalSection.scss';
|
|
||||||
@import '../../styles/responsive.scss';
|
|
||||||
|
|
||||||
.region-badge {
|
|
||||||
padding: 0.1em 0.5em;
|
|
||||||
border-radius: 0.5em;
|
|
||||||
font-weight: bold;
|
|
||||||
|
|
||||||
&.eu {
|
|
||||||
background-color: forestgreen;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-wrapper {
|
|
||||||
margin-top: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.journal_item {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
flex-wrap: wrap;
|
|
||||||
|
|
||||||
&.online {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
span[data-status='true'] {
|
|
||||||
color: springgreen;
|
|
||||||
}
|
|
||||||
|
|
||||||
span[data-status='false'] {
|
|
||||||
color: salmon;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.journal_day {
|
|
||||||
position: relative;
|
|
||||||
text-align: center;
|
|
||||||
background-color: #4d4d4d;
|
|
||||||
|
|
||||||
span {
|
|
||||||
position: relative;
|
|
||||||
background-color: #4d4d4d;
|
|
||||||
z-index: 10;
|
|
||||||
|
|
||||||
padding: 0 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
position: absolute;
|
|
||||||
content: '';
|
|
||||||
|
|
||||||
z-index: 0;
|
|
||||||
|
|
||||||
left: 50%;
|
|
||||||
top: 50%;
|
|
||||||
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
|
|
||||||
height: 3px;
|
|
||||||
width: 60%;
|
|
||||||
min-width: 200px;
|
|
||||||
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include smallScreen() {
|
|
||||||
.journal_item {
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
span {
|
|
||||||
margin-top: 0.25em;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
<template>
|
||||||
|
<transition-group class="journal-list" tag="ul" name="list-anim">
|
||||||
|
<li
|
||||||
|
v-for="item in computedDispatcherHistory"
|
||||||
|
:key="typeof item === 'string' ? item : item.timestampFrom + item.dispatcherId"
|
||||||
|
:class="{ sticky: typeof item == 'string' }"
|
||||||
|
>
|
||||||
|
<div v-if="typeof item == 'string'" class="journal_day">
|
||||||
|
{{ item }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="journal_item"
|
||||||
|
:class="{ online: item.isOnline }"
|
||||||
|
@click="navigateToScenery(item.stationName, item.isOnline)"
|
||||||
|
@keydown.enter="navigateToScenery(item.stationName, item.isOnline)"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<span class="item-general">
|
||||||
|
<b
|
||||||
|
v-if="item.dispatcherLevel !== null"
|
||||||
|
class="level-badge dispatcher"
|
||||||
|
:style="calculateExpStyle(item.dispatcherLevel, item.dispatcherIsSupporter)"
|
||||||
|
>
|
||||||
|
{{ item.dispatcherLevel >= 2 ? item.dispatcherLevel : 'L' }}
|
||||||
|
</b>
|
||||||
|
|
||||||
|
<b class="text--primary">{{ item.dispatcherName }}</b> • <b>{{ item.stationName }}</b>
|
||||||
|
<span class="text--grayed"> #{{ item.stationHash }} </span>
|
||||||
|
<span class="region-badge" :class="item.region">PL1</span>
|
||||||
|
<span class="like-count" v-if="item.dispatcherRate">
|
||||||
|
<img :src="getIcon('like')" alt="like icon" />
|
||||||
|
{{ item.dispatcherRate }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="item-time">
|
||||||
|
<span :data-status="item.isOnline"> {{ item.isOnline ? $t('journal.online-since') : 'OFFLINE' }} </span>
|
||||||
|
<span>
|
||||||
|
{{ new Date(item.timestampFrom).toLocaleTimeString('pl-PL', { timeStyle: 'short' }) }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span v-if="item.currentDuration && item.isOnline"> ({{ calculateDuration(item.currentDuration) }}) </span>
|
||||||
|
|
||||||
|
<span v-if="item.timestampTo">
|
||||||
|
>
|
||||||
|
{{ new Date(item.timestampTo).toLocaleTimeString('pl-PL', { timeStyle: 'short' }) }}
|
||||||
|
({{ $t('journal.duty-lasted') }} {{ calculateDuration(item.currentDuration!) }})
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</transition-group>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, PropType } from 'vue';
|
||||||
|
import dateMixin from '../../mixins/dateMixin';
|
||||||
|
import { DispatcherHistory } from '../../scripts/interfaces/api/DispatchersAPIData';
|
||||||
|
import styleMixin from '../../mixins/styleMixin';
|
||||||
|
import imageMixin from '../../mixins/imageMixin';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
dispatcherHistory: {
|
||||||
|
type: Array as PropType<DispatcherHistory[]>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
mixins: [dateMixin, styleMixin, imageMixin],
|
||||||
|
|
||||||
|
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 (DispatcherHistory | 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/JournalSection.scss';
|
||||||
|
@import '../../styles/variables.scss';
|
||||||
|
|
||||||
|
li.sticky {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.journal_item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
gap: 0.5em 1em;
|
||||||
|
|
||||||
|
line-height: 1.7em;
|
||||||
|
padding: 0.75em;
|
||||||
|
|
||||||
|
&.online {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
span[data-status='true'] {
|
||||||
|
color: springgreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
span[data-status='false'] {
|
||||||
|
color: salmon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-general {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25em;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.level-badge {
|
||||||
|
margin-right: 0.25em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.journal_day {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
padding: 0.5em;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
background-color: #333;
|
||||||
|
|
||||||
|
span {
|
||||||
|
position: relative;
|
||||||
|
background-color: inherit;
|
||||||
|
z-index: 10;
|
||||||
|
padding-right: 1em;
|
||||||
|
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.like-count {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25em;
|
||||||
|
font-size: 1.2em;
|
||||||
|
color: $accentCol;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include smallScreen {
|
||||||
|
.journal_item {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
<template>
|
||||||
|
<div class="journal-stats">
|
||||||
|
<span v-if="store.driverStatsData">
|
||||||
|
<h3>
|
||||||
|
{{ $t('journal.stats-title') }} <span class="text--primary">{{ store.driverStatsName.toUpperCase() }}</span>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div class="info-stats">
|
||||||
|
<span class="stat-badge">
|
||||||
|
<span>{{ $t('journal.stats-timetables') }}</span>
|
||||||
|
<span>{{ store.driverStatsData._count.fulfilled }} / {{ store.driverStatsData._count._all }}</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="stat-badge">
|
||||||
|
<span>{{ $t('journal.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> {{ store.driverStatsData._avg.routeDistance.toFixed(2) }}km </span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="stat-badge">
|
||||||
|
<span>{{ $t('journal.stats-distance') }}</span>
|
||||||
|
<span>
|
||||||
|
{{ store.driverStatsData._sum.currentDistance.toFixed(2) }} /
|
||||||
|
{{ store.driverStatsData._sum.routeDistance.toFixed(2) }}km
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="stat-badge">
|
||||||
|
<span>{{ $t('journal.stats-stations') }}</span>
|
||||||
|
<span>
|
||||||
|
{{ store.driverStatsData._sum.confirmedStopsCount }} /
|
||||||
|
{{ store.driverStatsData._sum.allStopsCount }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<b v-else-if="store.driverStatsStatus == DataStatus.Loading">{{ $t('journal.stats-loading') }}</b>
|
||||||
|
<b v-else-if="store.driverStatsStatus == DataStatus.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 { DataStatus } from '../../scripts/enums/DataStatus';
|
||||||
|
import { useStore } from '../../store/store';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
store: useStore(),
|
||||||
|
DataStatus,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '../../styles/JournalStats.scss';
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<template>
|
||||||
|
<section class="journal-header">
|
||||||
|
<div class="journal-type-options">
|
||||||
|
<router-link class="router-link" active-class="route-active" to="/journal/timetables" exact>
|
||||||
|
{{ $t('journal.section-timetables') }}
|
||||||
|
</router-link>
|
||||||
|
•
|
||||||
|
<router-link class="router-link" active-class="route-active" to="/journal/dispatchers">
|
||||||
|
{{ $t('journal.section-dispatchers') }}
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.journal-type-options {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
background-color: #2c2c2c;
|
||||||
|
max-width: 18em;
|
||||||
|
|
||||||
|
font-size: 1.2em;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
border-radius: 0 0 0.5em 0.5em;
|
||||||
|
padding: 0.1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.journal-section > section {
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.router-link.active {
|
||||||
|
color: gold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,260 +1,300 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="journal-options">
|
<div class="filters-options" @keydown.esc="showOptions = false">
|
||||||
<div class="options_wrapper">
|
<div class="bg" v-if="showOptions" @click="showOptions = false"></div>
|
||||||
<div class="options_content">
|
|
||||||
<div class="content_select">
|
<div class="actions-bar">
|
||||||
<select-box
|
<button class="filter-button btn--filled btn--image" @click="showOptions = !showOptions" ref="button">
|
||||||
:itemList="translatedSorterOptions"
|
<img :src="getIcon('filter2')" alt="Open filters" />
|
||||||
:defaultItemIndex="0"
|
{{ $t('options.filters') }} [F]
|
||||||
@selected="onSorterChange"
|
<span class="active-indicator" v-if="currentOptionsActive"></span>
|
||||||
:prefix="$t('journal.sort-prefix')"
|
</button>
|
||||||
/>
|
|
||||||
</div>
|
<button class="filter-button btn--filled btn--image" @click="refreshData">
|
||||||
|
<img :src="getIcon('refresh')" alt="Refresh data" />
|
||||||
<div class="content_search">
|
{{ $t('general.refresh') }}
|
||||||
<div class="search-box" v-for="search in searchersValues" :key="search.id">
|
</button>
|
||||||
<input
|
</div>
|
||||||
class="search-input"
|
|
||||||
:placeholder="$t(`journal.${search.id}`)"
|
<datalist id="search-driver">
|
||||||
v-model="search.value"
|
<option v-for="sugg in driverSuggestions" :value="sugg"></option>
|
||||||
@keydown.enter="onInputSearch"
|
</datalist>
|
||||||
/>
|
|
||||||
|
<datalist id="search-dispatcher">
|
||||||
<img class="search-exit" :src="exitIcon" alt="exit-icon" @click="onInputClear(search.id)" />
|
<option v-for="sugg in dispatcherSuggestions" :value="sugg"></option>
|
||||||
</div>
|
</datalist>
|
||||||
<!-- <div class="search-box">
|
|
||||||
<input
|
<transition name="options-anim">
|
||||||
class="search-input"
|
<div class="options_wrapper" v-if="showOptions">
|
||||||
v-model="searchedTrain"
|
<div class="options_content">
|
||||||
:placeholder="$t('journal.search-train')"
|
<h1 class="option-title">{{ $t('options.search-title') }}</h1>
|
||||||
@keydown.enter="search"
|
<div class="search_content">
|
||||||
/>
|
<div class="search" v-for="(_, propName) in searchersValues" :key="propName">
|
||||||
|
<label v-if="propName == 'search-date'" for="date">{{ $t(`options.search-${optionsType}-date`) }}</label>
|
||||||
<img class="search-exit" :src="exitIcon" alt="exit-icon" @click="clearTrain" />
|
|
||||||
</div>
|
<div class="search-box">
|
||||||
|
<input
|
||||||
<div class="search-box">
|
class="search-input"
|
||||||
<input
|
v-model="searchersValues[propName]"
|
||||||
class="search-input"
|
@keydown.enter="onSearchConfirm"
|
||||||
v-model="searchedDriver"
|
@focus="preventKeyDown = true"
|
||||||
:placeholder="$t('journal.search-driver')"
|
@blur="preventKeyDown = false"
|
||||||
@keydown.enter="search"
|
:placeholder="$t(`options.${propName}`)"
|
||||||
/>
|
:type="propName == 'search-date' ? 'date' : 'text'"
|
||||||
|
:min="propName == 'search-date' ? '2022-02-01' : undefined"
|
||||||
<img class="search-exit" :src="exitIcon" alt="exit-icon" @click="clearDriver" />
|
:list="propName.toString()"
|
||||||
</div> -->
|
/>
|
||||||
|
|
||||||
<action-button class="search-button" @click="onInputSearch">
|
<button class="search-exit" v-if="propName != 'search-date'">
|
||||||
{{ $t('journal.search') }}
|
<img :src="getIcon('exit')" alt="exit-icon" @click="onInputClear(propName)" />
|
||||||
</action-button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="options_filters">
|
|
||||||
<button
|
<h1 class="option-title">{{ $t('options.sort-title') }}</h1>
|
||||||
v-for="filter in filters"
|
<div class="options_sorters">
|
||||||
class="journal-filter-option btn--option"
|
<div v-for="opt in translatedSorterOptions">
|
||||||
:class="{ checked: journalFilterActive.id === filter.id }"
|
<button
|
||||||
:id="filter.id"
|
class="sort-option btn--option"
|
||||||
@click="onFilterChange(filter)"
|
:data-selected="opt.id == sorterActive.id"
|
||||||
>
|
@click="onSorterChange(opt)"
|
||||||
{{ $t(`journal.filter-${filter.id}`) }}
|
>
|
||||||
</button>
|
{{ opt.value.toUpperCase() }}
|
||||||
</div>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
|
||||||
|
<h1 class="option-title" v-if="filters.length != 0">{{ $t('options.filter-title') }}</h1>
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, inject, JournalFilter, PropType } from 'vue';
|
<div class="options_filter-sections" v-if="filters.length != 0 && filterList">
|
||||||
import ActionButton from '../Global/ActionButton.vue';
|
<section class="filter-section" v-for="section in JournalFilterSection">
|
||||||
import SelectBox from '../Global/SelectBox.vue';
|
<p>{{ $t(`options.filter-section-${section}`) }}</p>
|
||||||
|
|
||||||
export default defineComponent({
|
<div class="options_filters">
|
||||||
components: { SelectBox, ActionButton },
|
<button
|
||||||
emits: ['onSorterChange', 'onInputChange', 'onFilterChange'],
|
v-for="filter in filterList.filter((f) => f.filterSection == section)"
|
||||||
props: {
|
class="filter-option btn--option"
|
||||||
sorterOptionIds: {
|
:class="{ checked: filter.isActive }"
|
||||||
type: Array as PropType<Array<string>>,
|
:id="filter.id"
|
||||||
required: true,
|
@click="onFilterChange(filter)"
|
||||||
},
|
>
|
||||||
|
{{ $t(`options.filter-${filter.id}`) }}
|
||||||
filters: {
|
</button>
|
||||||
type: Array as PropType<JournalFilter[]>,
|
</div>
|
||||||
default: [],
|
</section>
|
||||||
},
|
</div>
|
||||||
},
|
|
||||||
|
<div class="options_actions">
|
||||||
data: () => ({
|
<button class="btn--action" @click="onResetButtonClick">
|
||||||
exitIcon: require('@/assets/icon-exit.svg'),
|
{{ $t('options.reset-button') }}
|
||||||
}),
|
</button>
|
||||||
|
<button class="btn--action" @click="onSearchButtonConfirm">
|
||||||
setup() {
|
{{ $t('options.search-button') }}
|
||||||
return {
|
</button>
|
||||||
searchersValues: inject('searchersValues') as {id: string; value: string}[],
|
</div>
|
||||||
sorterActive: inject('sorterActive') as { id: string | number; dir: number },
|
</div>
|
||||||
journalFilterActive: inject('journalFilterActive') as JournalFilter,
|
</div>
|
||||||
};
|
</transition>
|
||||||
},
|
</div>
|
||||||
|
</template>
|
||||||
computed: {
|
|
||||||
translatedSorterOptions() {
|
<script lang="ts">
|
||||||
return this.$props.sorterOptionIds.map((id) => ({
|
import axios from 'axios';
|
||||||
id,
|
import { defineComponent, inject, PropType } from 'vue';
|
||||||
value: this.$t(`journal.option-${id}`),
|
import imageMixin from '../../mixins/imageMixin';
|
||||||
}));
|
import keyMixin from '../../mixins/keyMixin';
|
||||||
},
|
import { DataStatus } from '../../scripts/enums/DataStatus';
|
||||||
},
|
import { DriverStatsAPIData } from '../../scripts/interfaces/api/DriverStatsAPIData';
|
||||||
|
import { URLs } from '../../scripts/utils/apiURLs';
|
||||||
methods: {
|
import { useStore } from '../../store/store';
|
||||||
onSorterChange(item: { id: string | number; value: string }) {
|
import ActionButton from '../Global/ActionButton.vue';
|
||||||
this.sorterActive.id = item.id;
|
import SelectBox from '../Global/SelectBox.vue';
|
||||||
this.sorterActive.dir = -1;
|
import { JournalFilterSection } from '../../scripts/enums/JournalFilterType';
|
||||||
|
import { JournalFilter } from '../../scripts/types/JournalTimetablesTypes';
|
||||||
this.$emit('onSorterChange');
|
|
||||||
},
|
export default defineComponent({
|
||||||
|
components: { SelectBox, ActionButton },
|
||||||
onFilterChange(filter: JournalFilter) {
|
emits: ['onSearchConfirm', 'onOptionsReset', 'onRefreshData'],
|
||||||
this.journalFilterActive = filter;
|
mixins: [imageMixin, keyMixin],
|
||||||
this.$emit('onFilterChange');
|
|
||||||
},
|
props: {
|
||||||
|
sorterOptionIds: {
|
||||||
onInputSearch() {
|
type: Array as PropType<Array<string>>,
|
||||||
this.$emit('onInputChange');
|
required: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
onInputClear(id: string) {
|
filters: {
|
||||||
this.searchersValues.find(s => s.id == id)!.value = "";
|
type: Array as PropType<JournalFilter[]>,
|
||||||
this.onInputSearch();
|
default: [],
|
||||||
},
|
},
|
||||||
},
|
|
||||||
});
|
dataStatus: {
|
||||||
</script>
|
type: Number as PropType<DataStatus>,
|
||||||
|
default: DataStatus.Initialized,
|
||||||
<style lang="scss" scoped>
|
},
|
||||||
@import '../../styles/responsive';
|
|
||||||
@import '../../styles/option.scss';
|
currentOptionsActive: {
|
||||||
|
type: Boolean,
|
||||||
.options {
|
default: false,
|
||||||
&_wrapper {
|
},
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
optionsType: {
|
||||||
}
|
type: String,
|
||||||
|
required: true,
|
||||||
&_content {
|
},
|
||||||
display: flex;
|
},
|
||||||
flex-wrap: wrap;
|
|
||||||
|
data() {
|
||||||
.content_search,
|
return {
|
||||||
.content_select {
|
showOptions: false,
|
||||||
display: flex;
|
JournalFilterSection,
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
driverSuggestions: [] as string[],
|
||||||
|
dispatcherSuggestions: [] as string[],
|
||||||
padding: 0.25em 0.25em 0 0;
|
|
||||||
}
|
searchTimeout: 0,
|
||||||
}
|
store: useStore(),
|
||||||
|
|
||||||
&_filters {
|
DataStatus,
|
||||||
display: flex;
|
};
|
||||||
flex-wrap: wrap;
|
},
|
||||||
margin: 0.5em 0 0 0;
|
|
||||||
|
setup() {
|
||||||
.journal-filter-option {
|
return {
|
||||||
margin: 0 0.25em 0 0;
|
searchersValues: inject('searchersValues') as { [key: string]: string },
|
||||||
|
sorterActive: inject('sorterActive') as { id: string | number; dir: number },
|
||||||
&#abandoned {
|
// journalFilterActive: inject('journalFilterActive') as JournalFilter,
|
||||||
color: salmon;
|
filterList: inject('filterList') as JournalFilter[] | undefined,
|
||||||
}
|
};
|
||||||
|
},
|
||||||
&#fulfilled {
|
|
||||||
color: lightgreen;
|
computed: {
|
||||||
}
|
driverStatsName() {
|
||||||
|
return this.store.driverStatsName;
|
||||||
&#active {
|
},
|
||||||
color: lightblue;
|
|
||||||
}
|
translatedSorterOptions() {
|
||||||
}
|
return this.$props.sorterOptionIds.map((id) => ({
|
||||||
}
|
id,
|
||||||
}
|
value: this.$t(`options.sort-${id}`),
|
||||||
|
}));
|
||||||
.search {
|
},
|
||||||
&-box {
|
},
|
||||||
position: relative;
|
|
||||||
|
watch: {
|
||||||
background: #333;
|
async driverStatsName(value: string) {
|
||||||
border-radius: 0.5em;
|
await this.fetchDriverStats();
|
||||||
min-width: 200px;
|
|
||||||
margin-right: 0.25em;
|
// if (value) this.store.currentStatsTab = 'driver';
|
||||||
}
|
},
|
||||||
|
|
||||||
&-input {
|
async 'searchersValues.search-driver'(value: string | undefined) {
|
||||||
border: none;
|
clearTimeout(this.searchTimeout);
|
||||||
|
|
||||||
min-width: 100%;
|
if (!value || value == '') return;
|
||||||
padding: 0.35em 0.5em;
|
if (value.length < 3) return;
|
||||||
}
|
|
||||||
|
this.startSearchTimeout('driver', value);
|
||||||
&-exit {
|
},
|
||||||
position: absolute;
|
|
||||||
cursor: pointer;
|
async 'searchersValues.search-dispatcher'(value: string | undefined) {
|
||||||
|
if (!value || value == '') return;
|
||||||
top: 50%;
|
if (value.length < 3) return;
|
||||||
right: 10px;
|
|
||||||
transform: translateY(-50%);
|
this.startSearchTimeout('dispatcher', value);
|
||||||
|
},
|
||||||
width: 1em;
|
},
|
||||||
}
|
|
||||||
}
|
methods: {
|
||||||
|
async fetchDriverStats() {
|
||||||
@include smallScreen() {
|
this.store.driverStatsData = undefined;
|
||||||
.journal-options {
|
|
||||||
width: 100%;
|
if (!this.store.driverStatsName) {
|
||||||
}
|
this.store.driverStatsStatus = DataStatus.Initialized;
|
||||||
.options {
|
return;
|
||||||
&_wrapper {
|
}
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
try {
|
||||||
}
|
this.store.driverStatsStatus = DataStatus.Loading;
|
||||||
|
|
||||||
&_content {
|
const statsData: DriverStatsAPIData = await (
|
||||||
padding: 0 1em;
|
await axios.get(`${URLs.stacjownikAPI}/api/getDriverInfo?name=${this.store.driverStatsName}`)
|
||||||
|
).data;
|
||||||
flex-direction: column;
|
|
||||||
|
this.store.driverStatsData = statsData;
|
||||||
.content_select {
|
this.store.driverStatsStatus = DataStatus.Loaded;
|
||||||
margin: 0 auto;
|
} catch (error) {
|
||||||
padding: 0;
|
this.store.driverStatsStatus = DataStatus.Error;
|
||||||
}
|
console.error('Ups! Wystąpił błąd przy próbie pobrania statystyk maszynisty! :/');
|
||||||
|
}
|
||||||
.content_search {
|
},
|
||||||
justify-content: center;
|
|
||||||
}
|
refreshData() {
|
||||||
}
|
this.$emit('onRefreshData');
|
||||||
|
},
|
||||||
&_filters {
|
|
||||||
justify-content: center;
|
startSearchTimeout(type: 'driver' | 'dispatcher', value: string) {
|
||||||
|
if (this[`${type}Suggestions`].includes(value)) return;
|
||||||
.journal-filter-option {
|
|
||||||
margin: 0.25em 0.25em;
|
window.clearTimeout(this.searchTimeout);
|
||||||
}
|
|
||||||
}
|
this.searchTimeout = setTimeout(async () => {
|
||||||
}
|
try {
|
||||||
|
const suggestions: string[] = await (
|
||||||
.search {
|
await axios.get(`${URLs.stacjownikAPI}/api/get${type}Suggestions?name=${value}`)
|
||||||
&-box,
|
).data;
|
||||||
&-button {
|
|
||||||
margin: 0.5em 0 0 0;
|
this[`${type}Suggestions`] = suggestions;
|
||||||
}
|
} catch (error) {
|
||||||
|
this[`${type}Suggestions`] = [];
|
||||||
&-box {
|
}
|
||||||
width: 100%;
|
}, 450);
|
||||||
}
|
},
|
||||||
|
|
||||||
&-button {
|
// Override keyMixin function
|
||||||
width: 80%;
|
onKeyDownFunction() {
|
||||||
max-width: 300px;
|
this.showOptions = !this.showOptions;
|
||||||
}
|
|
||||||
}
|
this.$nextTick(() => {
|
||||||
}
|
if (this.showOptions) (this.$refs['button'] as HTMLButtonElement)?.focus();
|
||||||
</style>
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onSorterChange(item: { id: string | number; value: string }) {
|
||||||
|
this.sorterActive.id = item.id;
|
||||||
|
this.sorterActive.dir = -1;
|
||||||
|
this.$emit('onSearchConfirm');
|
||||||
|
},
|
||||||
|
|
||||||
|
onFilterChange(filter: JournalFilter) {
|
||||||
|
// this.journalFilterActive = filter;
|
||||||
|
this.filterList?.filter((f) => f.filterSection === filter.filterSection).forEach((f) => (f.isActive = false));
|
||||||
|
filter.isActive = true;
|
||||||
|
|
||||||
|
this.$emit('onSearchConfirm');
|
||||||
|
},
|
||||||
|
|
||||||
|
onInputClear(id: any) {
|
||||||
|
this.searchersValues[id] = '';
|
||||||
|
this.$emit('onSearchConfirm');
|
||||||
|
},
|
||||||
|
|
||||||
|
onSearchConfirm() {
|
||||||
|
this.$emit('onSearchConfirm');
|
||||||
|
},
|
||||||
|
|
||||||
|
onSearchButtonConfirm() {
|
||||||
|
this.showOptions = false;
|
||||||
|
this.$emit('onSearchConfirm');
|
||||||
|
},
|
||||||
|
|
||||||
|
onResetButtonClick() {
|
||||||
|
this.$emit('onOptionsReset');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '../../styles/filters_options.scss';
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
<template>
|
||||||
|
<div class="journal-stats" v-if="!store.isOffline">
|
||||||
|
<div class="tabs">
|
||||||
|
<button
|
||||||
|
v-for="tab in data.tabs"
|
||||||
|
class="btn--filled"
|
||||||
|
:data-selected="tab.name == store.currentStatsTab && areStatsOpen"
|
||||||
|
:data-inactive="tab.inactive"
|
||||||
|
:data-disabled="tab.inactive"
|
||||||
|
:disabled="tab.inactive"
|
||||||
|
@click="onTabButtonClick(tab.name)"
|
||||||
|
>
|
||||||
|
{{ $t(tab.titlePath) }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stats-tab" v-show="areStatsOpen">
|
||||||
|
<keep-alive>
|
||||||
|
<JournalDailyStats v-if="store.currentStatsTab == 'daily'" @toggleStatsOpen="toggleStatsOpen" />
|
||||||
|
<JournalDriverStats v-else-if="store.currentStatsTab == 'driver'" />
|
||||||
|
</keep-alive>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, KeepAlive, onMounted, reactive, Ref, ref, watch } from 'vue';
|
||||||
|
import { useStore } from '../../store/store';
|
||||||
|
import JournalDailyStats from './DailyStats.vue';
|
||||||
|
import JournalDriverStats from './JournalDriverStats.vue';
|
||||||
|
import StorageManager from '../../scripts/managers/storageManager';
|
||||||
|
|
||||||
|
// Types
|
||||||
|
type TStatTab = 'daily' | 'driver';
|
||||||
|
|
||||||
|
// Variables
|
||||||
|
const store = useStore();
|
||||||
|
|
||||||
|
const lastDailyStatsOpen = ref(false);
|
||||||
|
const areStatsOpen = ref(false);
|
||||||
|
const lastClickedTab: Ref<'daily' | 'driver' | null> = ref(null);
|
||||||
|
|
||||||
|
let data = reactive({
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
name: 'daily',
|
||||||
|
titlePath: 'journal.daily-stats-title',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'driver',
|
||||||
|
titlePath: 'journal.driver-stats-title',
|
||||||
|
inactive: true,
|
||||||
|
},
|
||||||
|
] as { name: TStatTab; titlePath: string; inactive?: boolean }[],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
function onTabButtonClick(tab: TStatTab) {
|
||||||
|
if (lastClickedTab.value == tab || !areStatsOpen.value) areStatsOpen.value = !areStatsOpen.value;
|
||||||
|
|
||||||
|
if (tab == 'daily') {
|
||||||
|
StorageManager.setBooleanValue('dailyStatsOpen', areStatsOpen.value);
|
||||||
|
lastDailyStatsOpen.value = areStatsOpen.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
store.currentStatsTab = tab;
|
||||||
|
lastClickedTab.value = tab;
|
||||||
|
|
||||||
|
if (areStatsOpen.value == false) store.currentStatsTab = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleStatsOpen(open: boolean) {
|
||||||
|
areStatsOpen.value = open;
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
computed(() => store.driverStatsData),
|
||||||
|
(statsData) => {
|
||||||
|
store.currentStatsTab = statsData ? 'driver' : lastClickedTab.value;
|
||||||
|
areStatsOpen.value = statsData ? true : lastClickedTab.value !== null;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (StorageManager.getBooleanValue('dailyStatsOpen')) {
|
||||||
|
areStatsOpen.value = true;
|
||||||
|
store.currentStatsTab = 'daily';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '../../styles/JournalStats.scss';
|
||||||
|
@import '../../styles/variables.scss';
|
||||||
|
|
||||||
|
.tabs {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -1,451 +0,0 @@
|
|||||||
<template>
|
|
||||||
<section class="journal-timetables">
|
|
||||||
<keep-alive>
|
|
||||||
<DriverStats v-if="statsCardOpen" @close-card="closeCard" />
|
|
||||||
</keep-alive>
|
|
||||||
|
|
||||||
<div class="journal-wrapper">
|
|
||||||
<div class="journal_top-bar">
|
|
||||||
<JournalOptions
|
|
||||||
@on-input-change="search"
|
|
||||||
@on-filter-change="search"
|
|
||||||
@on-sorter-change="search"
|
|
||||||
:sorter-option-ids="['timetableId', 'beginDate', 'distance', 'total-stops']"
|
|
||||||
:filters="journalTimetableFilters"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- <button
|
|
||||||
class="btn btn--option"
|
|
||||||
:disabled="store.driverStatsName == ''"
|
|
||||||
@click="() => (statsCardOpen = !statsCardOpen)"
|
|
||||||
>
|
|
||||||
<span v-if="store.driverStatsName">
|
|
||||||
Statystyki maszynisty <b>{{ store.driverStatsName }}</b>
|
|
||||||
</span>
|
|
||||||
<span v-else>Statystyki maszynisty niedostępne</span>
|
|
||||||
</button> -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="journal-list">
|
|
||||||
<div class="list-wrapper" ref="scrollElement">
|
|
||||||
<transition name="warning" mode="out-in">
|
|
||||||
<div :key="historyDataStatus.status">
|
|
||||||
<Loading v-if="isDataLoading || isDataInit" />
|
|
||||||
|
|
||||||
<div v-else-if="isDataError" class="journal_warning error">
|
|
||||||
{{ $t('app.error') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="journal_warning" v-else-if="historyList.length == 0">
|
|
||||||
{{ $t('app.no-result') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul v-else>
|
|
||||||
<transition-group name="journal-list-anim">
|
|
||||||
<li v-for="(item, i) in historyList" class="journal_item" :key="item.timetableId">
|
|
||||||
<div class="journal_item-top">
|
|
||||||
<span>
|
|
||||||
<span
|
|
||||||
tabindex="0"
|
|
||||||
@click="navigateToTimetable(item)"
|
|
||||||
@keydown.enter="navigateToTimetable(item)"
|
|
||||||
style="cursor: pointer"
|
|
||||||
>
|
|
||||||
<b class="text--primary">{{ item.trainCategoryCode }} </b>
|
|
||||||
<b>{{ item.trainNo }}</b>
|
|
||||||
| <span>{{ item.driverName }}</span> |
|
|
||||||
<span class="text--grayed">#{{ item.timetableId }}</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<b>{{ item.route.replace('|', ' - ') }}</b>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr style="margin: 0.25em 0" />
|
|
||||||
|
|
||||||
<div class="scenery-list">
|
|
||||||
<span
|
|
||||||
v-for="(scenery, i) in getSceneryList(item)"
|
|
||||||
:key="scenery.name"
|
|
||||||
:class="{ confirmed: scenery.confirmed }"
|
|
||||||
>
|
|
||||||
{{ i > 0 ? ' > ' : '' }} {{ scenery.name }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="schedule-dates">
|
|
||||||
<!-- Data odjazdu ze stacji początkowej -->
|
|
||||||
<b>{{ item.route.split('|')[0] }}:</b>
|
|
||||||
<s v-if="item.beginDate != item.scheduledBeginDate" class="text--grayed">
|
|
||||||
{{ localeTime(item.beginDate, $i18n.locale) }}
|
|
||||||
</s>
|
|
||||||
<span>{{ localeTime(item.scheduledBeginDate, $i18n.locale) }} </span>•
|
|
||||||
|
|
||||||
<!-- Data przyjazdu na stację końcową / porzucenia -->
|
|
||||||
<b v-if="(item.fulfilled && item.terminated) || !item.terminated">
|
|
||||||
{{ item.route.split('|').slice(-1)[0] }}:
|
|
||||||
</b>
|
|
||||||
<i v-else>{{ $t('journal.timetable-abandoned') }} </i>
|
|
||||||
|
|
||||||
<s v-if="item.endDate != item.scheduledEndDate && item.terminated" class="text--grayed">
|
|
||||||
{{ localeTime(item.fulfilled ? item.endDate : item.scheduledEndDate, $i18n.locale) }}
|
|
||||||
</s>
|
|
||||||
<span
|
|
||||||
>{{ localeTime(item.fulfilled ? item.scheduledEndDate : item.endDate, $i18n.locale) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<b
|
|
||||||
class="journal_item-status"
|
|
||||||
:class="{
|
|
||||||
fulfilled: item.fulfilled || item.currentDistance >= item.routeDistance * 0.9,
|
|
||||||
terminated: item.terminated && !item.fulfilled,
|
|
||||||
active: !item.terminated,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
{{
|
|
||||||
!item.terminated
|
|
||||||
? $t('journal.timetable-active')
|
|
||||||
: item.fulfilled || item.currentDistance >= item.routeDistance * 0.9
|
|
||||||
? $t('journal.timetable-fulfilled')
|
|
||||||
: $t('journal.timetable-abandoned')
|
|
||||||
}}
|
|
||||||
</b>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="margin-top: 1em">
|
|
||||||
<div>
|
|
||||||
{{ $t('journal.timetable-day') }} <b>{{ localeDay(item.beginDate, $i18n.locale) }}</b>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Nick dyżurnego -->
|
|
||||||
<div v-if="item.authorName">
|
|
||||||
<b class="text--grayed">{{ $t('journal.dispatcher-name') }} </b>
|
|
||||||
<router-link
|
|
||||||
class="dispatcher-link"
|
|
||||||
:to="`/journal/dispatchers?dispatcherName=${item.authorName}`"
|
|
||||||
>{{ item.authorName }}</router-link
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="margin-top: 1em">
|
|
||||||
<div>
|
|
||||||
<b>{{ $t('journal.route-length') }}</b>
|
|
||||||
{{ !item.fulfilled ? item.currentDistance + ' /' : '' }}
|
|
||||||
{{ item.routeDistance }} km
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<b>{{ $t('journal.station-count') }}</b>
|
|
||||||
{{ item.confirmedStopsCount }} /
|
|
||||||
{{ item.allStopsCount }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</transition-group>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</div>
|
|
||||||
</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>
|
|
||||||
</section>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { computed, defineComponent, JournalFilter, JournalSearcher, provide, reactive, Ref, ref } from 'vue';
|
|
||||||
import axios from 'axios';
|
|
||||||
|
|
||||||
import SearchBox from '@/components/Global/SearchBox.vue';
|
|
||||||
import dateMixin from '@/mixins/dateMixin';
|
|
||||||
import { DataStatus } from '@/scripts/enums/DataStatus';
|
|
||||||
|
|
||||||
import ActionButton from '@/components/Global/ActionButton.vue';
|
|
||||||
import JournalOptions from '@/components/JournalView/JournalOptions.vue';
|
|
||||||
|
|
||||||
import { URLs } from '@/scripts/utils/apiURLs';
|
|
||||||
import { journalTimetableFilters } from '@/data/journalFilters';
|
|
||||||
import { JournalFilterType } from '@/scripts/enums/JournalFilterType';
|
|
||||||
import routerMixin from '@/mixins/routerMixin';
|
|
||||||
import { useStore } from '@/store/store';
|
|
||||||
import DriverStats from './DriverStats.vue';
|
|
||||||
import { TimetableHistory } from '@/scripts/interfaces/api/TimetablesAPIData';
|
|
||||||
import Loading from '../Global/Loading.vue';
|
|
||||||
|
|
||||||
const PROD_MODE = process.env.VUE_APP_JOURNAL_TIMETABLES_DEV != '1' || process.env.NODE_ENV === 'production';
|
|
||||||
|
|
||||||
const TIMETABLES_API_URL = PROD_MODE
|
|
||||||
? `${URLs.stacjownikAPI}/api/getTimetables`
|
|
||||||
: 'http://localhost:3001/api/getTimetables';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: { SearchBox, ActionButton, JournalOptions, DriverStats, Loading },
|
|
||||||
mixins: [dateMixin, routerMixin],
|
|
||||||
|
|
||||||
name: 'JournalTimetables',
|
|
||||||
|
|
||||||
data: () => ({
|
|
||||||
icons: {
|
|
||||||
arrow: require('@/assets/icon-arrow-asc.svg'),
|
|
||||||
},
|
|
||||||
|
|
||||||
currentQuery: '',
|
|
||||||
scrollDataLoaded: true,
|
|
||||||
scrollNoMoreData: false,
|
|
||||||
|
|
||||||
showReturnButton: false,
|
|
||||||
statsCardOpen: false,
|
|
||||||
|
|
||||||
journalTimetableFilters,
|
|
||||||
}),
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
const historyDataStatus: Ref<{ status: DataStatus; error: string | null }> = ref({
|
|
||||||
status: DataStatus.Loading,
|
|
||||||
error: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
const sorterActive = ref({ id: 'timetableId', dir: -1 });
|
|
||||||
const journalFilterActive = ref(journalTimetableFilters[0]);
|
|
||||||
|
|
||||||
const searchersValues = reactive([
|
|
||||||
{ id: 'search-train', value: '' },
|
|
||||||
{ id: 'search-driver', value: '' },
|
|
||||||
]);
|
|
||||||
const countFromIndex = ref(0);
|
|
||||||
const countLimit = 15;
|
|
||||||
|
|
||||||
provide('searchersValues', searchersValues);
|
|
||||||
provide('sorterActive', sorterActive);
|
|
||||||
provide('journalFilterActive', journalFilterActive);
|
|
||||||
|
|
||||||
const scrollElement: Ref<HTMLElement | null> = ref(null);
|
|
||||||
|
|
||||||
return {
|
|
||||||
historyList: ref([]) as Ref<TimetableHistory[]>,
|
|
||||||
historyDataStatus,
|
|
||||||
|
|
||||||
isDataLoading: computed(() => historyDataStatus.value.status === DataStatus.Loading),
|
|
||||||
isDataError: computed(() => historyDataStatus.value.status === DataStatus.Error),
|
|
||||||
isDataInit: computed(() => historyDataStatus.value.status === DataStatus.Initialized),
|
|
||||||
|
|
||||||
sorterActive,
|
|
||||||
journalFilterActive,
|
|
||||||
searchersValues,
|
|
||||||
|
|
||||||
countFromIndex,
|
|
||||||
countLimit,
|
|
||||||
|
|
||||||
scrollElement,
|
|
||||||
maxCount: ref(15),
|
|
||||||
store: useStore(),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
activated() {
|
|
||||||
window.addEventListener('scroll', this.handleScroll);
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted() {
|
|
||||||
this.search();
|
|
||||||
},
|
|
||||||
|
|
||||||
deactivated() {
|
|
||||||
window.removeEventListener('scroll', this.handleScroll);
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
navigateToTimetable(historyItem: TimetableHistory) {
|
|
||||||
if (historyItem.terminated) return;
|
|
||||||
|
|
||||||
this.navigateTo('/trains', {
|
|
||||||
trainNo: historyItem.trainNo,
|
|
||||||
driverName: historyItem.driverName,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
closeCard() {
|
|
||||||
this.statsCardOpen = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
getSceneryList(historyItem: TimetableHistory) {
|
|
||||||
return historyItem.sceneriesString
|
|
||||||
.split('%')
|
|
||||||
.map((name, i) => ({ name, confirmed: i < historyItem.confirmedStopsCount }));
|
|
||||||
},
|
|
||||||
|
|
||||||
handleScroll() {
|
|
||||||
this.showReturnButton = window.scrollY > window.innerHeight;
|
|
||||||
|
|
||||||
const element = this.$refs.scrollElement as HTMLElement;
|
|
||||||
|
|
||||||
if (
|
|
||||||
element.getBoundingClientRect().bottom * 0.85 < window.innerHeight &&
|
|
||||||
this.scrollDataLoaded &&
|
|
||||||
!this.scrollNoMoreData &&
|
|
||||||
this.historyDataStatus.status == DataStatus.Loaded
|
|
||||||
)
|
|
||||||
this.addHistoryData();
|
|
||||||
},
|
|
||||||
|
|
||||||
scrollToTop() {
|
|
||||||
window.scrollTo({ top: 0 });
|
|
||||||
},
|
|
||||||
|
|
||||||
search() {
|
|
||||||
this.fetchHistoryData({
|
|
||||||
searchers: this.searchersValues,
|
|
||||||
filter: this.journalFilterActive,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.scrollNoMoreData = false;
|
|
||||||
this.scrollDataLoaded = true;
|
|
||||||
},
|
|
||||||
|
|
||||||
async addHistoryData() {
|
|
||||||
this.scrollDataLoaded = false;
|
|
||||||
|
|
||||||
const countFrom = this.historyList.length;
|
|
||||||
|
|
||||||
const responseData: TimetableHistory[] = await (
|
|
||||||
await axios.get(`${TIMETABLES_API_URL}?${this.currentQuery}&countFrom=${countFrom}`)
|
|
||||||
).data;
|
|
||||||
|
|
||||||
if (!responseData) return;
|
|
||||||
|
|
||||||
if (responseData.length == 0) {
|
|
||||||
this.scrollNoMoreData = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.historyList.push(...responseData);
|
|
||||||
this.scrollDataLoaded = true;
|
|
||||||
},
|
|
||||||
|
|
||||||
async fetchHistoryData(
|
|
||||||
props: {
|
|
||||||
searchers?: JournalSearcher[];
|
|
||||||
filter?: JournalFilter;
|
|
||||||
} = {}
|
|
||||||
) {
|
|
||||||
this.historyDataStatus.status = DataStatus.Loading;
|
|
||||||
|
|
||||||
const queries: string[] = [];
|
|
||||||
|
|
||||||
const driver = props.searchers?.find((s) => s.id == 'search-driver')?.value.trim();
|
|
||||||
const train = props.searchers?.find((s) => s.id == 'search-train')?.value.trim();
|
|
||||||
|
|
||||||
if (driver) queries.push(`driverName=${driver}`);
|
|
||||||
if (train) queries.push(`trainNo=${train}`);
|
|
||||||
|
|
||||||
// Z API: const SORT_TYPES = ['allStopsCount', 'endDate', 'beginDate', 'routeDistance'];
|
|
||||||
if (this.sorterActive.id == 'distance') queries.push('sortBy=routeDistance');
|
|
||||||
else if (this.sorterActive.id == 'total-stops') queries.push('sortBy=allStopsCount');
|
|
||||||
else if (this.sorterActive.id == 'beginDate') queries.push('sortBy=beginDate');
|
|
||||||
else queries.push('sortBy=timetableId');
|
|
||||||
|
|
||||||
queries.push('countLimit=15');
|
|
||||||
|
|
||||||
switch (props.filter?.id) {
|
|
||||||
case JournalFilterType.abandoned:
|
|
||||||
queries.push('fulfilled=0', 'terminated=1');
|
|
||||||
break;
|
|
||||||
|
|
||||||
case JournalFilterType.active:
|
|
||||||
queries.push('terminated=0');
|
|
||||||
break;
|
|
||||||
|
|
||||||
case JournalFilterType.fulfilled:
|
|
||||||
queries.push('fulfilled=1');
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.currentQuery = queries.join('&');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const responseData: TimetableHistory[] = await (
|
|
||||||
await axios.get(`${TIMETABLES_API_URL}?${this.currentQuery}`)
|
|
||||||
).data;
|
|
||||||
|
|
||||||
if (!responseData) {
|
|
||||||
this.historyDataStatus.status = DataStatus.Error;
|
|
||||||
this.historyDataStatus.error = 'Brak danych!';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (responseData) {
|
|
||||||
// this.historyDataStatus.status = DataStatus.Error;
|
|
||||||
// this.historyDataStatus.error = responseData;
|
|
||||||
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (!responseData) return;
|
|
||||||
|
|
||||||
// Response data exists
|
|
||||||
this.historyList = responseData;
|
|
||||||
|
|
||||||
// Stats display
|
|
||||||
this.store.driverStatsName =
|
|
||||||
this.historyList.length > 0 && this.searchersValues[1].value.trim() ? this.historyList[0].driverName : '';
|
|
||||||
|
|
||||||
this.historyDataStatus.status = DataStatus.Loaded;
|
|
||||||
} catch (error) {
|
|
||||||
this.historyDataStatus.status = DataStatus.Error;
|
|
||||||
this.historyDataStatus.error = 'Ups! Coś poszło nie tak!';
|
|
||||||
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@import '../../styles/JournalSection.scss';
|
|
||||||
|
|
||||||
.journal_item {
|
|
||||||
&-top {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
padding: 0.2em 0;
|
|
||||||
|
|
||||||
.scenery-list {
|
|
||||||
span {
|
|
||||||
color: #adadad;
|
|
||||||
|
|
||||||
&.confirmed {
|
|
||||||
color: #a3eba3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-status {
|
|
||||||
&.terminated {
|
|
||||||
color: salmon;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.fulfilled {
|
|
||||||
color: lightgreen;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
color: lightblue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dispatcher-link {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -0,0 +1,468 @@
|
|||||||
|
<template>
|
||||||
|
<transition-group class="journal-list" tag="ul" name="list-anim">
|
||||||
|
<li
|
||||||
|
v-for="{ timetable, sceneryList, stockHistoryComp, ...item } in computedTimetableHistory"
|
||||||
|
class="journal_item"
|
||||||
|
:key="timetable.id"
|
||||||
|
@click="item.showExtra.value = !item.showExtra.value"
|
||||||
|
>
|
||||||
|
<div class="journal_item-info">
|
||||||
|
<div class="info-general">
|
||||||
|
<span
|
||||||
|
class="general-train"
|
||||||
|
tabindex="0"
|
||||||
|
@click.stop="showTimetable(timetable)"
|
||||||
|
@keydown.enter="showTimetable(timetable)"
|
||||||
|
style="cursor: pointer"
|
||||||
|
>
|
||||||
|
<span class="text--grayed">#{{ timetable.id }}</span>
|
||||||
|
|
||||||
|
<span class="badges" v-if="timetable.skr || timetable.twr">
|
||||||
|
<span class="train-badge twr" v-if="timetable.twr" :title="$t('general.TWR')">TWR</span>
|
||||||
|
<span class="train-badge skr" v-if="timetable.skr" :title="$t('general.SKR')">SKR</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
<strong class="text--primary">
|
||||||
|
{{ timetable.trainCategoryCode }}
|
||||||
|
</strong>
|
||||||
|
<strong> {{ timetable.trainNo }}</strong>
|
||||||
|
</span>
|
||||||
|
•
|
||||||
|
<strong
|
||||||
|
v-if="timetable.driverLevel !== null"
|
||||||
|
class="level-badge driver"
|
||||||
|
:style="calculateExpStyle(timetable.driverLevel, timetable.driverIsSupporter)"
|
||||||
|
>
|
||||||
|
{{ timetable.driverLevel < 2 ? 'L' : `${timetable.driverLevel}` }}
|
||||||
|
</strong>
|
||||||
|
|
||||||
|
<strong>{{ timetable.driverName }}</strong>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="general-time">
|
||||||
|
<b class="info-date">{{ localeDay(timetable.beginDate, $i18n.locale) }}</b>
|
||||||
|
<b
|
||||||
|
class="info-status"
|
||||||
|
:class="{
|
||||||
|
fulfilled: timetable.fulfilled,
|
||||||
|
terminated: timetable.terminated && !timetable.fulfilled,
|
||||||
|
active: !timetable.terminated,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
!timetable.terminated
|
||||||
|
? $t('journal.timetable-active')
|
||||||
|
: timetable.fulfilled
|
||||||
|
? $t('journal.timetable-fulfilled')
|
||||||
|
: `${$t('journal.timetable-abandoned')} ${localeTime(timetable.endDate, $i18n.locale)}`
|
||||||
|
}}
|
||||||
|
</b>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-route">
|
||||||
|
<b>{{ timetable.route.replace('|', ' - ') }}</b>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<div class="scenery-list">
|
||||||
|
<span
|
||||||
|
v-for="(scenery, i) in sceneryList.filter((_, i) =>
|
||||||
|
!item.showExtra.value ? i == 0 || i == sceneryList.length - 1 : true
|
||||||
|
)"
|
||||||
|
:key="scenery.name"
|
||||||
|
:class="{ confirmed: scenery.confirmed }"
|
||||||
|
>
|
||||||
|
<span v-if="i > 0">
|
||||||
|
>
|
||||||
|
<span v-if="!item.showExtra.value && i == 1 && sceneryList.length > 2">
|
||||||
|
... (+{{ sceneryList.length - 2 }}) >
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
{{ scenery.name }}
|
||||||
|
|
||||||
|
<!-- Data odjazdu ze stacji początkowej -->
|
||||||
|
<span v-if="i == 0" v-html="scenery.beginDateHTML"></span>
|
||||||
|
|
||||||
|
<!-- Data przyjazdu do stacji końcowej -->
|
||||||
|
<span
|
||||||
|
v-else-if="i == sceneryList.length - 1 || (i == 1 && !item.showExtra.value)"
|
||||||
|
v-html="scenery.endDateHTML"
|
||||||
|
></span>
|
||||||
|
|
||||||
|
<!-- Data przyjazdu i odjazdu ze stacji pośredniej -->
|
||||||
|
<span v-if="item.showExtra.value && i > 0 && i < sceneryList.length - 1">
|
||||||
|
<span v-if="timetable.checkpointArrivals && i < timetable.checkpointArrivals.length">
|
||||||
|
(p. {{ localeTime(timetable.checkpointArrivals[i], $i18n.locale)
|
||||||
|
}}<span v-if="timetable.checkpointDepartures && i < timetable.checkpointDepartures.length">
|
||||||
|
/ o. {{ localeTime(timetable.checkpointDepartures[i], $i18n.locale) }}</span
|
||||||
|
>)
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Status RJ -->
|
||||||
|
<div style="margin: 0.5em 0">
|
||||||
|
<span>
|
||||||
|
<b>{{ $t('journal.route-length') }}</b>
|
||||||
|
{{ !timetable.fulfilled ? timetable.currentDistance + ' /' : '' }}
|
||||||
|
{{ timetable.routeDistance }} km
|
||||||
|
</span>
|
||||||
|
•
|
||||||
|
<span>
|
||||||
|
<b>{{ $t('journal.station-count') }}</b>
|
||||||
|
{{ timetable.confirmedStopsCount }} /
|
||||||
|
{{ timetable.allStopsCount }}
|
||||||
|
</span>
|
||||||
|
<span class="text--grayed" v-if="!timetable.fulfilled && timetable.currentSceneryName">
|
||||||
|
•
|
||||||
|
<b>
|
||||||
|
{{ $t(`journal.${timetable.terminated ? 'last-seen-at' : 'currently-at'}`) }}
|
||||||
|
{{ timetable.currentSceneryName.replace(/.[a-zA-Z0-9]+.sc/, '') }}
|
||||||
|
</b>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Nick dyżurnego -->
|
||||||
|
<div v-if="timetable.authorName">
|
||||||
|
<b class="text--grayed">{{ $t('journal.dispatcher-name') }} </b>
|
||||||
|
<router-link class="dispatcher-link" :to="`/journal/dispatchers?dispatcherName=${timetable.authorName}`">
|
||||||
|
<b>{{ timetable.authorName }}</b>
|
||||||
|
</router-link>
|
||||||
|
<span class="text--grayed">
|
||||||
|
({{
|
||||||
|
(new Date(timetable.createdAt).getTime() - new Date(timetable.beginDate).getTime() < 0
|
||||||
|
? new Date(timetable.createdAt)
|
||||||
|
: new Date(timetable.beginDate)
|
||||||
|
).toLocaleString($i18n.locale, { timeStyle: 'short', dateStyle: 'full' })
|
||||||
|
}})
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="btn--option btn--show">
|
||||||
|
{{ $t('journal.stock-info') }}
|
||||||
|
<img :src="getIcon(`arrow-${item.showExtra.value ? 'asc' : 'desc'}`)" alt="Arrow" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Dodatkowe informacje -->
|
||||||
|
<div class="info-extended" v-if="timetable.stockString && timetable.stockMass && item.showExtra.value">
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<div class="stock-specs">
|
||||||
|
<span class="badge specs-badge">
|
||||||
|
<span>{{ $t('journal.stock-max-speed') }}</span>
|
||||||
|
<span>{{ timetable.maxSpeed }}km/h</span>
|
||||||
|
</span>
|
||||||
|
<span class="badge specs-badge">
|
||||||
|
<span>{{ $t('journal.stock-length') }}</span>
|
||||||
|
<span>
|
||||||
|
{{
|
||||||
|
item.currentHistoryIndex.value == 0
|
||||||
|
? timetable.stockLength
|
||||||
|
: stockHistoryComp[item.currentHistoryIndex.value].stockLength || timetable.stockLength
|
||||||
|
}}m
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="badge specs-badge">
|
||||||
|
<span>{{ $t('journal.stock-mass') }}</span>
|
||||||
|
<span>
|
||||||
|
{{
|
||||||
|
Math.floor(
|
||||||
|
(item.currentHistoryIndex.value == 0
|
||||||
|
? timetable.stockMass!
|
||||||
|
: stockHistoryComp[item.currentHistoryIndex.value].stockMass || timetable.stockMass) / 1000
|
||||||
|
)
|
||||||
|
}}t
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stock-history" v-if="stockHistoryComp.length > 1">
|
||||||
|
<button
|
||||||
|
class="btn--action"
|
||||||
|
v-for="(sh, i) in stockHistoryComp"
|
||||||
|
:data-checked="i == item.currentHistoryIndex.value"
|
||||||
|
@click.stop="item.currentHistoryIndex.value = i"
|
||||||
|
>
|
||||||
|
{{ sh.updatedAt }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="stock-list">
|
||||||
|
<li
|
||||||
|
v-for="(car, i) in (item.currentHistoryIndex.value == 0
|
||||||
|
? timetable.stockString
|
||||||
|
: stockHistoryComp[item.currentHistoryIndex.value].stockString
|
||||||
|
).split(';')"
|
||||||
|
:key="i"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
@error="onImageError"
|
||||||
|
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${car.split(':')[0]}.png`"
|
||||||
|
:alt="car"
|
||||||
|
/>
|
||||||
|
<div>{{ car.replace(/_/g, ' ').split(':')[0] }}</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</transition-group>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, PropType, ref } from 'vue';
|
||||||
|
import dateMixin from '../../mixins/dateMixin';
|
||||||
|
import imageMixin from '../../mixins/imageMixin';
|
||||||
|
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
||||||
|
import styleMixin from '../../mixins/styleMixin';
|
||||||
|
import { TimetableHistory } from '../../scripts/interfaces/api/TimetablesAPIData';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
timetableHistory: {
|
||||||
|
type: Array as PropType<TimetableHistory[]>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
mixins: [dateMixin, imageMixin, modalTrainMixin, styleMixin],
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
computedTimetableHistory() {
|
||||||
|
return this.timetableHistory.map((timetable) => ({
|
||||||
|
timetable,
|
||||||
|
sceneryList: this.getSceneryList(timetable),
|
||||||
|
stockHistoryComp: timetable.stockHistory
|
||||||
|
.slice()
|
||||||
|
.reverse()
|
||||||
|
.map((h) => {
|
||||||
|
const historyData = h.split('@');
|
||||||
|
|
||||||
|
return {
|
||||||
|
updatedAt: new Date(Number(historyData[0])).toLocaleTimeString(this.$i18n.locale, {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
}),
|
||||||
|
stockString: historyData[1],
|
||||||
|
stockMass: Number(historyData[2]) || undefined,
|
||||||
|
stockLength: Number(historyData[3]) || undefined,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
showExtra: ref(false),
|
||||||
|
currentHistoryIndex: ref(0),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
getSceneryList(timetable: TimetableHistory) {
|
||||||
|
return timetable.sceneriesString.split('%').map((name, i) => {
|
||||||
|
const beginDateHTML =
|
||||||
|
' (o. ' +
|
||||||
|
(timetable.beginDate != timetable.scheduledBeginDate
|
||||||
|
? `<s class='text--grayed'>${this.localeTime(timetable.beginDate, this.$i18n.locale)}</s> `
|
||||||
|
: '') +
|
||||||
|
`<span>${this.localeTime(timetable.scheduledBeginDate, this.$i18n.locale)}</span>)`;
|
||||||
|
|
||||||
|
const endDateHTML =
|
||||||
|
' (p. ' +
|
||||||
|
(timetable.endDate != timetable.scheduledEndDate && timetable.fulfilled
|
||||||
|
? `<s class='text--grayed'>${this.localeTime(
|
||||||
|
timetable.fulfilled ? timetable.endDate : timetable.scheduledEndDate,
|
||||||
|
this.$i18n.locale
|
||||||
|
)}</s> `
|
||||||
|
: '') +
|
||||||
|
`<span>${this.localeTime(
|
||||||
|
timetable.fulfilled || (timetable.terminated && !timetable.fulfilled)
|
||||||
|
? timetable.scheduledEndDate
|
||||||
|
: timetable.endDate,
|
||||||
|
this.$i18n.locale
|
||||||
|
)}</span>)`;
|
||||||
|
|
||||||
|
return { name, confirmed: i < timetable.confirmedStopsCount, beginDateHTML, endDateHTML };
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
showTimetable(timetable: TimetableHistory) {
|
||||||
|
if (!timetable) return;
|
||||||
|
if (timetable.terminated) return;
|
||||||
|
|
||||||
|
this.selectModalTrain(timetable.driverName + timetable.trainNo.toString());
|
||||||
|
},
|
||||||
|
|
||||||
|
onImageError(e: Event) {
|
||||||
|
const imageEl = e.target as HTMLImageElement;
|
||||||
|
imageEl.src = this.getImage('unknown.png');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '../../styles/animations.scss';
|
||||||
|
@import '../../styles/variables.scss';
|
||||||
|
@import '../../styles/responsive.scss';
|
||||||
|
@import '../../styles/badge.scss';
|
||||||
|
@import '../../styles/JournalSection.scss';
|
||||||
|
|
||||||
|
.journal_item {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
margin: 0.25em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
&-date {
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-status {
|
||||||
|
padding: 0.05em 0.35em;
|
||||||
|
color: black;
|
||||||
|
|
||||||
|
&.terminated {
|
||||||
|
background-color: salmon;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.fulfilled {
|
||||||
|
background-color: lightgreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: lightblue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-general {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
gap: 0.5em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-route {
|
||||||
|
margin: 0.25em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-extended {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.general-train {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.stock-list {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
overflow: auto;
|
||||||
|
padding-bottom: 0.5em;
|
||||||
|
margin-top: 1em;
|
||||||
|
|
||||||
|
li > div {
|
||||||
|
text-align: center;
|
||||||
|
color: #aaa;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
li > img {
|
||||||
|
vertical-align: text-bottom;
|
||||||
|
max-height: 60px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-specs {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5em;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
|
||||||
|
.specs-badge {
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
span:last-child {
|
||||||
|
color: black;
|
||||||
|
background-color: $accentCol;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.badges {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.25em;
|
||||||
|
|
||||||
|
// badge.scss
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-history {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5em;
|
||||||
|
margin-top: 1em;
|
||||||
|
|
||||||
|
button[data-checked='true'] {
|
||||||
|
color: $accentCol;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scenery-list {
|
||||||
|
color: #adadad;
|
||||||
|
span.confirmed {
|
||||||
|
color: #a3eba3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn--show {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 1em;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 0.2em 0.45em;
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 1.3em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include smallScreen {
|
||||||
|
.info-general {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.info-extended {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.general-train {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-route {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn--show {
|
||||||
|
margin: 1em auto 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-specs {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-history {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,112 +1,126 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="scenery-dispatchers-history scenery-section">
|
<section class="scenery-dispatchers-history scenery-section">
|
||||||
<Loading v-if="dataStatus != 2" />
|
<Loading v-if="dataStatus != 2" />
|
||||||
|
|
||||||
<div class="list-warning" v-else-if="dispatcherHistoryList.length == 0">{{ $t('scenery.history-list-empty') }}</div>
|
<div class="list-warning" v-else-if="dispatcherHistoryList.length == 0">{{ $t('scenery.history-list-empty') }}</div>
|
||||||
|
|
||||||
<ul class="history-list" v-else>
|
<ul class="history-list" v-else>
|
||||||
<li class="list-item" v-for="historyItem in dispatcherHistoryList">
|
<li class="list-item" v-for="item in dispatcherHistoryList">
|
||||||
<div>
|
<router-link class="item-general" :to="`/journal/dispatchers?dispatcherName=${item.dispatcherName}`">
|
||||||
<span class="text--grayed">#{{ historyItem.stationHash }} </span>
|
<span class="text--grayed">#{{ item.stationHash }} </span>
|
||||||
<b class="text--primary">{{ historyItem.dispatcherName }}</b>
|
<b
|
||||||
</div>
|
v-if="item.dispatcherLevel !== null"
|
||||||
|
class="level-badge dispatcher"
|
||||||
<div v-if="historyItem.timestampTo">
|
:style="calculateExpStyle(item.dispatcherLevel, item.dispatcherIsSupporter)"
|
||||||
<b>{{ $d(historyItem.timestampFrom) }}</b>
|
>
|
||||||
|
{{ item.dispatcherLevel >= 2 ? item.dispatcherLevel : 'L' }}
|
||||||
{{ timestampToString(historyItem.timestampFrom) }}
|
</b>
|
||||||
- {{ timestampToString(historyItem.timestampTo) }} ({{ calculateDuration(historyItem.currentDuration) }})
|
|
||||||
</div>
|
<b>{{ item.dispatcherName }}</b>
|
||||||
|
</router-link>
|
||||||
<div class="dispatcher-online" v-else>
|
|
||||||
{{ $t('journal.online-since') }}
|
<div v-if="item.timestampTo">
|
||||||
<b>{{ timestampToString(historyItem.timestampFrom) }}</b>
|
<b>{{ $d(item.timestampFrom) }}</b>
|
||||||
({{ calculateDuration(historyItem.currentDuration) }})
|
|
||||||
<span></span>
|
{{ timestampToString(item.timestampFrom) }}
|
||||||
</div>
|
- {{ timestampToString(item.timestampTo) }} ({{ calculateDuration(item.currentDuration) }})
|
||||||
</li>
|
</div>
|
||||||
</ul>
|
|
||||||
</section>
|
<div class="dispatcher-online" v-else>
|
||||||
</template>
|
{{ $t('journal.online-since') }}
|
||||||
|
<b>{{ timestampToString(item.timestampFrom) }}</b>
|
||||||
<script lang="ts">
|
({{ calculateDuration(item.currentDuration) }})
|
||||||
import dateMixin from '@/mixins/dateMixin';
|
</div>
|
||||||
import { DataStatus } from '@/scripts/enums/DataStatus';
|
</li>
|
||||||
import { DispatcherHistory } from '@/scripts/interfaces/api/DispatchersAPIData';
|
</ul>
|
||||||
import Station from '@/scripts/interfaces/Station';
|
</section>
|
||||||
import { URLs } from '@/scripts/utils/apiURLs';
|
</template>
|
||||||
import axios from 'axios';
|
|
||||||
import { defineComponent, PropType } from 'vue';
|
<script lang="ts">
|
||||||
import Loading from '../Global/Loading.vue';
|
import axios from 'axios';
|
||||||
|
import { defineComponent, PropType } from 'vue';
|
||||||
export default defineComponent({
|
import dateMixin from '../../mixins/dateMixin';
|
||||||
name: 'SceneryDispatchersHistory',
|
import { DataStatus } from '../../scripts/enums/DataStatus';
|
||||||
mixins: [dateMixin],
|
import { DispatcherHistory } from '../../scripts/interfaces/api/DispatchersAPIData';
|
||||||
props: {
|
import Station from '../../scripts/interfaces/Station';
|
||||||
station: {
|
import { URLs } from '../../scripts/utils/apiURLs';
|
||||||
type: Object as PropType<Station>,
|
import Loading from '../Global/Loading.vue';
|
||||||
required: true,
|
import styleMixin from '../../mixins/styleMixin';
|
||||||
},
|
|
||||||
},
|
export default defineComponent({
|
||||||
data() {
|
name: 'SceneryDispatchersHistory',
|
||||||
return {
|
mixins: [dateMixin, styleMixin],
|
||||||
dispatcherHistoryList: [] as DispatcherHistory[],
|
props: {
|
||||||
dataStatus: DataStatus.Loading,
|
station: {
|
||||||
};
|
type: Object as PropType<Station>,
|
||||||
},
|
required: true,
|
||||||
mounted() {
|
},
|
||||||
this.fetchAPIData();
|
},
|
||||||
},
|
data() {
|
||||||
methods: {
|
return {
|
||||||
async fetchAPIData(countFrom = 0, countLimit = 30) {
|
dispatcherHistoryList: [] as DispatcherHistory[],
|
||||||
try {
|
dataStatus: DataStatus.Loading,
|
||||||
const requestString = `${URLs.stacjownikAPI}/api/getDispatchers?stationName=${this.station.name}&countFrom=${countFrom}&countLimit=${countLimit}`;
|
};
|
||||||
const historyAPIData: DispatcherHistory[] = await (await axios.get(requestString)).data;
|
},
|
||||||
|
activated() {
|
||||||
this.dispatcherHistoryList = historyAPIData;
|
this.fetchAPIData();
|
||||||
this.dataStatus = DataStatus.Loaded;
|
},
|
||||||
|
methods: {
|
||||||
console.log(this.dispatcherHistoryList);
|
async fetchAPIData(countFrom = 0, countLimit = 30) {
|
||||||
} catch (error) {
|
try {
|
||||||
console.error(error);
|
const requestString = `${URLs.stacjownikAPI}/api/getDispatchers?stationName=${this.station.name}&countFrom=${countFrom}&countLimit=${countLimit}`;
|
||||||
}
|
const historyAPIData: DispatcherHistory[] = await (await axios.get(requestString)).data;
|
||||||
},
|
|
||||||
},
|
this.dispatcherHistoryList = historyAPIData;
|
||||||
components: { Loading },
|
this.dataStatus = DataStatus.Loaded;
|
||||||
});
|
} catch (error) {
|
||||||
</script>
|
console.error(error);
|
||||||
|
}
|
||||||
<style lang="scss" scoped>
|
},
|
||||||
@import '../../styles/responsive.scss';
|
},
|
||||||
@import '../../styles/SceneryView/styles.scss';
|
components: { Loading },
|
||||||
|
});
|
||||||
|
</script>
|
||||||
.history-list {
|
|
||||||
padding: 0 0.5em;
|
<style lang="scss" scoped>
|
||||||
}
|
@import '../../styles/responsive.scss';
|
||||||
|
@import '../../styles/SceneryView/styles.scss';
|
||||||
.list-item {
|
|
||||||
display: flex;
|
.history-list {
|
||||||
flex-wrap: wrap;
|
padding: 0 0.5em;
|
||||||
justify-content: space-between;
|
}
|
||||||
|
|
||||||
text-align: left;
|
.list-item {
|
||||||
background-color: #353535;
|
display: flex;
|
||||||
padding: 0.5em;
|
flex-wrap: wrap;
|
||||||
margin: 0.5em 0;
|
justify-content: space-between;
|
||||||
|
|
||||||
line-height: 1.5em;
|
text-align: left;
|
||||||
}
|
background-color: #353535;
|
||||||
|
padding: 0.5em;
|
||||||
.dispatcher-online {
|
margin: 0.5em 0;
|
||||||
color: springgreen;
|
|
||||||
}
|
line-height: 1.5em;
|
||||||
|
}
|
||||||
@include smallScreen {
|
|
||||||
.list-item {
|
.item-general {
|
||||||
align-items: center;
|
display: flex;
|
||||||
flex-direction: column;
|
align-items: center;
|
||||||
}
|
flex-wrap: wrap;
|
||||||
}
|
gap: 0.25em;
|
||||||
</style>
|
}
|
||||||
|
|
||||||
|
.dispatcher-online {
|
||||||
|
color: springgreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include smallScreen {
|
||||||
|
.history-list {
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
.list-item {
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="info-header">
|
<section class="info-header">
|
||||||
<div class="scenery-name">
|
<a class="scenery-name" :href="station.generalInfo?.url" target="_blank">
|
||||||
<a v-if="station.generalInfo?.url" :href="station.generalInfo.url" target="_blank" rel="noopener noreferrer">
|
{{ station.name }}
|
||||||
{{ station.name }}
|
</a>
|
||||||
</a>
|
|
||||||
|
|
||||||
<span v-else>{{ station.name }}</span>
|
<div class="scenery-abbrev">{{ $t('scenery.abbrev') }} <b>{{ station.generalInfo?.abbr }}</b></div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="scenery-hash" v-if="station.onlineInfo?.hash">#{{ station.onlineInfo.hash }}</div>
|
<div class="scenery-hash" v-if="station.onlineInfo?.hash">#{{ station.onlineInfo.hash }}</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -14,8 +12,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
|
import Station from '../../scripts/interfaces/Station';
|
||||||
import Station from '@/scripts/interfaces/Station';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
@@ -33,22 +30,20 @@ export default defineComponent({
|
|||||||
|
|
||||||
.scenery-name {
|
.scenery-name {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $accentCol;
|
font-size: 3em;
|
||||||
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
font-size: 3.5em;
|
|
||||||
padding: 0 0.5em;
|
|
||||||
|
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
@include smallScreen() {
|
.scenery-abbrev {
|
||||||
font-size: 2.75em;
|
font-size: 1.3em;
|
||||||
}
|
color: #aaa;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scenery-hash {
|
.scenery-hash {
|
||||||
|
margin-top: 0.5em;
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="scenery-info">
|
<div class="scenery-info">
|
||||||
<section v-if="!timetableOnly">
|
<section v-if="!timetableOnly">
|
||||||
<div class="info-general" v-if="station.generalInfo">
|
<div class="scenery-info-general" v-if="station.generalInfo">
|
||||||
<scenery-info-icons :station="station" />
|
<scenery-info-icons :station="station" />
|
||||||
|
|
||||||
<div class="general-list">
|
<div class="scenery-general-list">
|
||||||
<span>
|
<span>
|
||||||
<b>{{ $t('availability.title') }}:</b> {{ $t(`availability.${station.generalInfo.availability}`) }}
|
<b>{{ $t('availability.title') }}:</b> {{ $t(`availability.${station.generalInfo.availability}`) }}
|
||||||
|
|
||||||
<span v-if="station.generalInfo.reqLevel > -1">
|
<span v-if="station.generalInfo.reqLevel > -1">
|
||||||
- {{ $tc('scenery.req-level', station.generalInfo.reqLevel, { lvl: station.generalInfo.reqLevel }) }}
|
- {{ $t('scenery.req-level', { lvl: station.generalInfo.reqLevel }, station.generalInfo.reqLevel) }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<!-- <span v-if="station.generalInfo.reqLevel > 0">
|
|
||||||
- minimum {{ station.generalInfo.reqLevel }} poziom dyżurnego
|
|
||||||
</span>
|
|
||||||
<span v-else-if="station.generalInfo.reqLevel == 0">- dla wszystkich poziomów</span> -->
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
@@ -31,22 +26,34 @@
|
|||||||
</span>
|
</span>
|
||||||
<span v-if="station.generalInfo.project">
|
<span v-if="station.generalInfo.project">
|
||||||
• <b>{{ $t('scenery.project-title') }}: </b>
|
• <b>{{ $t('scenery.project-title') }}: </b>
|
||||||
<b style="color: salmon">{{ station.generalInfo.project }}</b>
|
<a
|
||||||
|
style="color: salmon; text-decoration: underline; font-weight: bold"
|
||||||
|
:href="station.generalInfo.projectUrl"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{{ station.generalInfo.project }}
|
||||||
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<scenery-info-routes :station="station" />
|
<scenery-info-routes :station="station" />
|
||||||
|
|
||||||
<div class="scenery-authors" v-if="station.generalInfo.authors && station.generalInfo.authors.length > 0">
|
<div class="scenery-authors" v-if="station.generalInfo.authors && station.generalInfo.authors.length > 0">
|
||||||
<b> {{ $tc('scenery.authors-title', station.generalInfo.authors.length) }}: </b>
|
<b>
|
||||||
|
{{
|
||||||
|
$t(
|
||||||
|
'scenery.authors-title',
|
||||||
|
{ authors: station.generalInfo.authors.length },
|
||||||
|
station.generalInfo.authors.length
|
||||||
|
)
|
||||||
|
}}:
|
||||||
|
</b>
|
||||||
{{ station.generalInfo.authors.join(', ') }}
|
{{ station.generalInfo.authors.join(', ') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin: 2em 0; height: 2px; background-color: white" />
|
<div style="margin: 2em 0; height: 2px; background-color: white"></div>
|
||||||
|
|
||||||
<!-- info stats -->
|
|
||||||
<!-- <scenery-info-stats :station="station" /> -->
|
|
||||||
<!-- info dispatcher -->
|
<!-- info dispatcher -->
|
||||||
<scenery-info-dispatcher :station="station" :onlineFrom="onlineFrom" />
|
<scenery-info-dispatcher :station="station" :onlineFrom="onlineFrom" />
|
||||||
|
|
||||||
@@ -57,10 +64,6 @@
|
|||||||
<!-- spawn list -->
|
<!-- spawn list -->
|
||||||
<scenery-info-spawn-list :station="station" />
|
<scenery-info-spawn-list :station="station" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- info icons -->
|
|
||||||
|
|
||||||
<!-- info routes -->
|
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -74,8 +77,7 @@ import SceneryInfoStats from './SceneryInfo/SceneryInfoStats.vue';
|
|||||||
import SceneryInfoUserList from './SceneryInfo/SceneryInfoUserList.vue';
|
import SceneryInfoUserList from './SceneryInfo/SceneryInfoUserList.vue';
|
||||||
import SceneryInfoSpawnList from './SceneryInfo/SceneryInfoSpawnList.vue';
|
import SceneryInfoSpawnList from './SceneryInfo/SceneryInfoSpawnList.vue';
|
||||||
import SceneryInfoRoutes from './SceneryInfo/SceneryInfoRoutes.vue';
|
import SceneryInfoRoutes from './SceneryInfo/SceneryInfoRoutes.vue';
|
||||||
|
import Station from '../../scripts/interfaces/Station';
|
||||||
import Station from '@/scripts/interfaces/Station';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
@@ -103,6 +105,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../styles/responsive.scss';
|
@import '../../styles/responsive.scss';
|
||||||
|
@import '../../styles/badge.scss';
|
||||||
|
|
||||||
h3.section-header {
|
h3.section-header {
|
||||||
margin: 0.5em 0;
|
margin: 0.5em 0;
|
||||||
@@ -112,7 +115,7 @@ h3.section-header {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
font-size: 1.5em;
|
font-size: 1.2em;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 1.1em;
|
width: 1.1em;
|
||||||
@@ -128,12 +131,11 @@ h3.section-header {
|
|||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-general {
|
.scenery-info-general {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
font-size: 1.1em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.general-list {
|
.scenery-general-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -143,32 +145,7 @@ h3.section-header {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.badge {
|
.scenery-topic a {
|
||||||
font-weight: 600;
|
font-weight: bold;
|
||||||
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
background: #585858;
|
|
||||||
|
|
||||||
margin: 0.25em;
|
|
||||||
|
|
||||||
span {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0.2em 0.4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-none {
|
|
||||||
font-weight: 600;
|
|
||||||
|
|
||||||
padding: 0.2em 0.4em;
|
|
||||||
background: firebrick;
|
|
||||||
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
@include smallScreen() {
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="info-dispatcher">
|
<section class="info-dispatcher">
|
||||||
<div class="dispatcher" v-if="station.onlineInfo">
|
<div class="dispatcher" v-if="station.onlineInfo">
|
||||||
<span class="dispatcher_level" :style="calculateExpStyle(station.onlineInfo.dispatcherExp)">
|
<span
|
||||||
|
class="dispatcher_level"
|
||||||
|
:style="calculateExpStyle(station.onlineInfo.dispatcherExp, station.onlineInfo.dispatcherIsSupporter)"
|
||||||
|
>
|
||||||
{{ station.onlineInfo.dispatcherExp > 1 ? station.onlineInfo.dispatcherExp : 'L' }}
|
{{ station.onlineInfo.dispatcherExp > 1 ? station.onlineInfo.dispatcherExp : 'L' }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@@ -13,7 +16,7 @@
|
|||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<span class="dispatcher_likes text--primary">
|
<span class="dispatcher_likes text--primary">
|
||||||
<img :src="icons.like" alt="icon-like" />
|
<img :src="getIcon('like')" alt="icon-like" />
|
||||||
<span>{{ station.onlineInfo?.dispatcherRate || '0' }}</span>
|
<span>{{ station.onlineInfo?.dispatcherRate || '0' }}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -35,14 +38,14 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
|
import dateMixin from '../../../mixins/dateMixin';
|
||||||
import styleMixin from '@/mixins/styleMixin';
|
import imageMixin from '../../../mixins/imageMixin';
|
||||||
import Station from '@/scripts/interfaces/Station';
|
import routerMixin from '../../../mixins/routerMixin';
|
||||||
import dateMixin from '@/mixins/dateMixin';
|
import styleMixin from '../../../mixins/styleMixin';
|
||||||
import routerMixin from '@/mixins/routerMixin';
|
import Station from '../../../scripts/interfaces/Station';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
mixins: [styleMixin, dateMixin, routerMixin],
|
mixins: [styleMixin, dateMixin, routerMixin, imageMixin],
|
||||||
props: {
|
props: {
|
||||||
station: {
|
station: {
|
||||||
type: Object as () => Station,
|
type: Object as () => Station,
|
||||||
@@ -54,13 +57,6 @@ export default defineComponent({
|
|||||||
default: -1,
|
default: -1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data: () => ({
|
|
||||||
icons: {
|
|
||||||
spawn: require('@/assets/icon-spawn.svg'),
|
|
||||||
like: require('@/assets/icon-like.svg'),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -71,6 +67,7 @@ export default defineComponent({
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5em;
|
||||||
|
|
||||||
.dispatcher {
|
.dispatcher {
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
@@ -89,17 +86,15 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
&_name {
|
&_name {
|
||||||
margin-right: 0.4em;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
margin-right: 0.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
&_likes {
|
&_likes {
|
||||||
img {
|
img {
|
||||||
height: 0.7em;
|
height: 0.7em;
|
||||||
margin-right: 0.25em;
|
margin: 0 0.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
margin-right: 1.5em;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<img
|
<img
|
||||||
v-if="station.generalInfo?.SUP"
|
v-if="station.generalInfo?.SUP"
|
||||||
class="icon-info"
|
class="icon-info"
|
||||||
:src="require(`@/assets/icon-SUP.svg`)"
|
:src="getIcon('SUP')"
|
||||||
alt="SUP (RASP-UZK)"
|
alt="SUP (RASP-UZK)"
|
||||||
:title="$t('desc.SUP')"
|
:title="$t('desc.SUP')"
|
||||||
/>
|
/>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
<img
|
<img
|
||||||
v-if="station.generalInfo?.signalType"
|
v-if="station.generalInfo?.signalType"
|
||||||
class="icon-info"
|
class="icon-info"
|
||||||
:src="require(`@/assets/icon-${station.generalInfo.signalType}.svg`)"
|
:src="getIcon(station.generalInfo.signalType)"
|
||||||
:alt="station.generalInfo.signalType"
|
:alt="station.generalInfo.signalType"
|
||||||
:title="$t('desc.signals-type') + $t(`signals.${station.generalInfo.signalType}`)"
|
:title="$t('desc.signals-type') + $t(`signals.${station.generalInfo.signalType}`)"
|
||||||
/>
|
/>
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
<img
|
<img
|
||||||
v-if="station.generalInfo?.availability == 'nonPublic'"
|
v-if="station.generalInfo?.availability == 'nonPublic'"
|
||||||
class="icon-info"
|
class="icon-info"
|
||||||
:src="icons.lock"
|
:src="getIcon('lock')"
|
||||||
alt="Non-public scenery"
|
alt="Non-public scenery"
|
||||||
:title="$t('desc.non-public')"
|
:title="$t('desc.non-public')"
|
||||||
/>
|
/>
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
<img
|
<img
|
||||||
v-if="station.generalInfo?.availability == 'unavailable'"
|
v-if="station.generalInfo?.availability == 'unavailable'"
|
||||||
class="icon-info"
|
class="icon-info"
|
||||||
:src="icons.unavailable"
|
:src="getIcon('unavailable')"
|
||||||
alt="Unavailable scenery"
|
alt="Unavailable scenery"
|
||||||
:title="$t('desc.unavailable')"
|
:title="$t('desc.unavailable')"
|
||||||
/>
|
/>
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
<img
|
<img
|
||||||
v-if="station.generalInfo?.availability == 'abandoned'"
|
v-if="station.generalInfo?.availability == 'abandoned'"
|
||||||
class="icon-info"
|
class="icon-info"
|
||||||
:src="icons.abandoned"
|
:src="getIcon('abandoned')"
|
||||||
alt="Abandoned scenery"
|
alt="Abandoned scenery"
|
||||||
:title="$t('desc.abandoned')"
|
:title="$t('desc.abandoned')"
|
||||||
/>
|
/>
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
<img
|
<img
|
||||||
v-if="station.generalInfo?.lines"
|
v-if="station.generalInfo?.lines"
|
||||||
class="icon-info"
|
class="icon-info"
|
||||||
:src="icons.real"
|
:src="getIcon('real')"
|
||||||
alt="real scenery"
|
alt="real scenery"
|
||||||
:title="`${$t('desc.real')} ${station.generalInfo.lines}`"
|
:title="`${$t('desc.real')} ${station.generalInfo.lines}`"
|
||||||
/>
|
/>
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
<img
|
<img
|
||||||
v-if="!station.generalInfo"
|
v-if="!station.generalInfo"
|
||||||
class="icon-info"
|
class="icon-info"
|
||||||
:src="icons.unknown"
|
:src="getIcon('unknown')"
|
||||||
alt="icon-unknown"
|
alt="icon-unknown"
|
||||||
:title="$t('desc.unknown')"
|
:title="$t('desc.unknown')"
|
||||||
/>
|
/>
|
||||||
@@ -77,31 +77,19 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import stationInfoMixin from '@/mixins/stationInfoMixin';
|
import imageMixin from '../../../mixins/imageMixin';
|
||||||
|
import stationInfoMixin from '../../../mixins/stationInfoMixin';
|
||||||
import Station from '@/scripts/interfaces/Station';
|
import styleMixin from '../../../mixins/styleMixin';
|
||||||
import styleMixin from '@/mixins/styleMixin';
|
import Station from '../../../scripts/interfaces/Station';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
mixins: [stationInfoMixin, styleMixin],
|
mixins: [stationInfoMixin, styleMixin, imageMixin],
|
||||||
props: {
|
props: {
|
||||||
station: {
|
station: {
|
||||||
type: Object as () => Station,
|
type: Object as () => Station,
|
||||||
default: {},
|
default: {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data: () => ({
|
|
||||||
icons: {
|
|
||||||
td2: require('@/assets/icon-td2.svg'),
|
|
||||||
lock: require('@/assets/icon-lock.svg'),
|
|
||||||
unavailable: require('@/assets/icon-unavailable.svg'),
|
|
||||||
unknown: require('@/assets/icon-unknown.svg'),
|
|
||||||
abandoned: require('@/assets/icon-abandoned.svg'),
|
|
||||||
|
|
||||||
real: require('@/assets/icon-real.svg'),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -130,3 +118,4 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -1,114 +1,129 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="info-routes" v-if="station.generalInfo">
|
<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="station.generalInfo.routes.oneWay.length > 0">
|
||||||
<b>{{ $t('scenery.one-way-routes') }}</b>
|
<b>{{ $t('scenery.one-way-routes') }}</b>
|
||||||
|
|
||||||
<ul class="routes-list">
|
<ul class="routes-list">
|
||||||
<li
|
<li v-for="route in station.generalInfo.routes.oneWay" @click="setActiveShowLength(route.name)">
|
||||||
v-for="route in station.generalInfo.routes.oneWay"
|
<span :class="{ 'no-catenary': !route.catenary, internal: route.isInternal }"> {{ route.name }}</span>
|
||||||
:class="{ 'no-catenary': !route.catenary, internal: route.isInternal }"
|
<span v-if="route.speed" class="speed">
|
||||||
>
|
{{ activeShowLength.includes(route.name) ? route.length + 'm' : route.speed }}
|
||||||
{{ route.name }}
|
</span>
|
||||||
<b v-if="route.SBL">SBL</b>
|
<span v-if="route.SBL" class="sbl">SBL</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="routes two-way" v-if="station.generalInfo.routes.twoWay.length > 0">
|
<div class="routes two-way" v-if="station.generalInfo.routes.twoWay.length > 0">
|
||||||
<b>{{ $t('scenery.two-way-routes') }}</b>
|
<b>{{ $t('scenery.two-way-routes') }}</b>
|
||||||
|
|
||||||
<ul class="routes-list">
|
<ul class="routes-list">
|
||||||
<li
|
<li v-for="(route, i) in station.generalInfo.routes.twoWay" @click="setActiveShowLength(route.name)">
|
||||||
v-for="route in station.generalInfo.routes.twoWay"
|
<span :class="{ 'no-catenary': !route.catenary, internal: route.isInternal }">{{ route.name }}</span>
|
||||||
:class="{ 'no-catenary': !route.catenary, internal: route.isInternal }"
|
<span v-if="route.speed" class="speed">
|
||||||
>
|
{{ activeShowLength.includes(route.name) ? route.length + 'm' : route.speed }}
|
||||||
{{ route.name }} <b v-if="route.SBL">SBL</b>
|
</span>
|
||||||
</li>
|
<span v-if="route.SBL" class="sbl">SBL</span>
|
||||||
</ul>
|
</li>
|
||||||
</div>
|
</ul>
|
||||||
|
</div>
|
||||||
<!-- <div
|
</section>
|
||||||
class="route-info"
|
</template>
|
||||||
:class="{ 'no-catenary': !route.catenary, internal: route.isInternal }"
|
|
||||||
v-for="route in [...station.generalInfo.routes.oneWay, ...station.generalInfo.routes.twoWay].filter(
|
<script lang="ts">
|
||||||
(route) => route.name != '-'
|
import { defineComponent } from 'vue';
|
||||||
)"
|
import Station from '../../../scripts/interfaces/Station';
|
||||||
:key="route.name"
|
|
||||||
:title="`Szlak ${route.name}: ${route.isInternal ? 'wewnętrzny' : 'zewnętrzny'}, ${
|
export default defineComponent({
|
||||||
route.tracks == 2 ? 'dwutorowy' : 'jednotorowy'
|
props: {
|
||||||
}, ${route.catenary ? 'zelektryfikowany' : 'niezelektryfikowany'} z ${route.SBL ? 'SBL' : 'PBL'} ${
|
station: {
|
||||||
route.TWB ? 'i blokadą dwukierunkową' : ''
|
type: Object as () => Station,
|
||||||
}`"
|
default: {},
|
||||||
> -->
|
},
|
||||||
<!-- <span class="track-name">
|
},
|
||||||
<b>{{ route.name }}</b>
|
|
||||||
</span> -->
|
methods: {
|
||||||
<!--
|
setActiveShowLength(name: string) {
|
||||||
<span class="track-specs">
|
if (this.activeShowLength.includes(name)) this.activeShowLength.splice(this.activeShowLength.indexOf(name), 1);
|
||||||
{{ route.tracks }}tor
|
else this.activeShowLength.push(name);
|
||||||
<img v-if="route.catenary" :src="icons.trackCatenary" alt="icon track catenary" />
|
},
|
||||||
<img v-else :src="icons.trackNoCatenary" alt="icon track no catenary" />
|
},
|
||||||
|
|
||||||
<img v-if="route.TWB" :src="icons.trackTWB" alt="icon track twb" />
|
data() {
|
||||||
<img v-if="route.SBL" :src="icons.trackSBL" alt="icon track sbl" />
|
return {
|
||||||
</span> -->
|
activeShowLength: [] as string[],
|
||||||
<!-- </div> -->
|
};
|
||||||
</section>
|
},
|
||||||
</template>
|
});
|
||||||
|
</script>
|
||||||
<script lang="ts">
|
|
||||||
import Station from '@/scripts/interfaces/Station';
|
<style lang="scss" scoped>
|
||||||
import { defineComponent } from 'vue';
|
.info-routes {
|
||||||
|
display: flex;
|
||||||
export default defineComponent({
|
justify-content: center;
|
||||||
props: {
|
flex-wrap: wrap;
|
||||||
station: {
|
|
||||||
type: Object as () => Station,
|
margin: 1em 0;
|
||||||
default: {},
|
}
|
||||||
},
|
|
||||||
},
|
.routes {
|
||||||
});
|
display: flex;
|
||||||
</script>
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
<style lang="scss" scoped>
|
flex-wrap: wrap;
|
||||||
.info-routes {
|
|
||||||
display: flex;
|
padding: 0.25em;
|
||||||
justify-content: center;
|
}
|
||||||
flex-wrap: wrap;
|
|
||||||
|
ul.routes-list {
|
||||||
margin: 1em 0;
|
margin: 0.45em 0.25em;
|
||||||
}
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
.routes {
|
flex-wrap: wrap;
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
li {
|
||||||
align-items: center;
|
margin: 0.5em 0.25em;
|
||||||
flex-wrap: wrap;
|
cursor: pointer;
|
||||||
|
|
||||||
padding: 0.25em;
|
user-select: none;
|
||||||
}
|
-moz-user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
ul.routes-list {
|
|
||||||
margin: 0.45em 0.25em;
|
span {
|
||||||
display: flex;
|
padding: 0.2em 0.25em;
|
||||||
|
background-color: #007599;
|
||||||
li {
|
font-weight: bold;
|
||||||
background-color: #007599;
|
|
||||||
|
&.no-catenary {
|
||||||
padding: 0.2em 0.25em;
|
background-color: #686868;
|
||||||
margin-left: 0.25em;
|
}
|
||||||
|
|
||||||
&.no-catenary {
|
&.internal {
|
||||||
background-color: #686868;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.internal {
|
&.speed {
|
||||||
text-decoration: underline;
|
background-color: #404040;
|
||||||
}
|
color: #cfcfcf;
|
||||||
|
}
|
||||||
b {
|
|
||||||
color: var(--clr-primary);
|
&.sbl {
|
||||||
}
|
color: var(--clr-primary);
|
||||||
}
|
background-color: #404040;
|
||||||
}
|
}
|
||||||
</style>
|
|
||||||
|
&:last-child {
|
||||||
|
border-radius: 0 0.5em 0.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
border-radius: 0.5em 0 0 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:only-child {
|
||||||
|
border-radius: 0.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,55 +1,65 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="info-spawn-list">
|
<section class="info-spawn-list">
|
||||||
<h3 class="spawn-header section-header">
|
<h3 class="spawn-header section-header">
|
||||||
<img :src="icons.spawn" alt="icon-spawn" />
|
<img :src="getIcon('spawn')" alt="icon-spawn" />
|
||||||
{{ $t('scenery.spawns') }}
|
{{ $t('scenery.spawns') }}
|
||||||
<span class="text--primary">{{ station.onlineInfo?.spawns.length || '0' }}</span>
|
<span class="text--primary">{{ station.onlineInfo?.spawns.length || '0' }}</span>
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<span v-if="station.onlineInfo">
|
<span v-if="station.onlineInfo">
|
||||||
<span
|
<span
|
||||||
class="badge spawn"
|
class="badge spawn"
|
||||||
v-for="(spawn, i) in station.onlineInfo.spawns"
|
v-for="(spawn, i) in sortedSpawns"
|
||||||
:key="spawn.spawnName + station.onlineInfo?.dispatcherName + i"
|
:key="spawn.spawnName + station.onlineInfo?.dispatcherName + i"
|
||||||
>
|
:data-electrified="spawn.isElectrified"
|
||||||
<span class="spawn_name">{{ spawn.spawnName }}</span>
|
>
|
||||||
<span class="spawn_length">{{ spawn.spawnLength }}m</span>
|
<span class="spawn_name">{{ spawn.spawnName }}</span>
|
||||||
</span>
|
<span class="spawn_length">{{ spawn.spawnLength }}m</span>
|
||||||
</span>
|
</span>
|
||||||
|
</span>
|
||||||
<span class="badge spawn badge-none" v-if="!station.onlineInfo || station.onlineInfo.spawns.length == 0"
|
|
||||||
>{{ $t('scenery.no-spawns') }}
|
<span class="badge spawn badge-none" v-if="!station.onlineInfo || station.onlineInfo.spawns.length == 0"
|
||||||
</span>
|
>{{ $t('scenery.no-spawns') }}
|
||||||
</section>
|
</span>
|
||||||
</template>
|
</section>
|
||||||
|
</template>
|
||||||
<script lang="ts">
|
|
||||||
import Station from '@/scripts/interfaces/Station';
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
|
import imageMixin from '../../../mixins/imageMixin';
|
||||||
export default defineComponent({
|
import Station from '../../../scripts/interfaces/Station';
|
||||||
props: {
|
|
||||||
station: {
|
export default defineComponent({
|
||||||
type: Object as () => Station,
|
mixins: [imageMixin],
|
||||||
default: {},
|
|
||||||
},
|
props: {
|
||||||
},
|
station: {
|
||||||
|
type: Object as () => Station,
|
||||||
data: () => ({
|
default: {},
|
||||||
icons: {
|
},
|
||||||
spawn: require('@/assets/icon-spawn.svg'),
|
},
|
||||||
},
|
|
||||||
}),
|
computed: {
|
||||||
});
|
sortedSpawns() {
|
||||||
</script>
|
return this.station.onlineInfo?.spawns.sort((s1, s2) => (s1.spawnLength < s2.spawnLength ? 1 : -1));
|
||||||
|
},
|
||||||
<style lang="scss" scoped>
|
},
|
||||||
@import '../../../styles/variables.scss';
|
});
|
||||||
|
</script>
|
||||||
.spawn {
|
|
||||||
&_length {
|
<style lang="scss" scoped>
|
||||||
background: $accentCol;
|
@import '../../../styles/variables.scss';
|
||||||
color: black;
|
|
||||||
}
|
.spawn {
|
||||||
}
|
color: white;
|
||||||
</style>
|
|
||||||
|
&_length {
|
||||||
|
background-color: #404040;
|
||||||
|
color: #cfcfcf;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-electrified='true'] > &_name {
|
||||||
|
background-color: #007599;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="info-stats" :class="!station.onlineInfo ? 'no-stats' : ''">
|
<section class="info-stats" :class="!station.onlineInfo ? 'no-stats' : ''">
|
||||||
<span class="likes">
|
<span class="likes">
|
||||||
<img :src="icons.like" alt="icon-like" />
|
<img :src="getIcon('like')" alt="icon-like" />
|
||||||
<span>{{ station.onlineInfo?.dispatcherRate || '0' }}</span>
|
<span>{{ station.onlineInfo?.dispatcherRate || '0' }}</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="users">
|
<span class="users">
|
||||||
<img :src="icons.user" alt="icon-user" />
|
<img :src="getIcon('user')" alt="icon-user" />
|
||||||
<span>{{ station.onlineInfo?.currentUsers || '0' }}</span>
|
<span>{{ station.onlineInfo?.currentUsers || '0' }}</span>
|
||||||
/
|
/
|
||||||
<span>{{ station.onlineInfo?.maxUsers || '0' }}</span>
|
<span>{{ station.onlineInfo?.maxUsers || '0' }}</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="spawns">
|
<span class="spawns">
|
||||||
<img :src="icons.spawn" alt="icon-spawn" />
|
<img :src="getIcon('spawn')" alt="icon-spawn" />
|
||||||
<span>{{ station.onlineInfo?.spawns.length || '0' }}</span>
|
<span>{{ station.onlineInfo?.spawns.length || '0' }}</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="schedules">
|
<span class="schedules">
|
||||||
<img :src="icons.timetable" alt="icon-timetable" />
|
<img :src="getIcon('timetable')" alt="icon-timetable" />
|
||||||
<span>
|
<span>
|
||||||
<span style="color: #eee">{{ station.onlineInfo?.scheduledTrains?.length || '0' }}</span>
|
<span style="color: #eee">{{ station.onlineInfo?.scheduledTrains?.length || '0' }}</span>
|
||||||
/
|
/
|
||||||
@@ -32,25 +32,17 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
|
import imageMixin from '../../../mixins/imageMixin';
|
||||||
import Station from '@/scripts/interfaces/Station';
|
import Station from '../../../scripts/interfaces/Station';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
mixins: [imageMixin],
|
||||||
props: {
|
props: {
|
||||||
station: {
|
station: {
|
||||||
type: Object as () => Station,
|
type: Object as () => Station,
|
||||||
default: {},
|
default: {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data: () => ({
|
|
||||||
icons: {
|
|
||||||
like: require('@/assets/icon-like.svg'),
|
|
||||||
timetable: require('@/assets/icon-timetable.svg'),
|
|
||||||
user: require('@/assets/icon-user.svg'),
|
|
||||||
spawn: require('@/assets/icon-spawn.svg'),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -83,7 +75,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
span > img {
|
span > img {
|
||||||
width: 1.2em;
|
width: 1.2em;
|
||||||
margin-right: 0.5em;
|
margin-right: 0.5em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="info-user-list">
|
<section class="info-user-list">
|
||||||
<h3 class="user-header section-header">
|
<h3 class="user-header section-header">
|
||||||
<img :src="icons.user" alt="icon-user" />
|
<img :src="getIcon('user')" alt="icon-user" />
|
||||||
{{ $t('scenery.users') }}
|
{{ $t('scenery.users') }}
|
||||||
<span class="text--primary">{{ station.onlineInfo?.currentUsers || '0' }}</span
|
<span class="text--primary">{{ station.onlineInfo?.currentUsers || '0' }}</span
|
||||||
> / <span class="text--primary">{{ station.onlineInfo?.maxUsers || '0' }}</span>
|
> / <span class="text--primary">{{ station.onlineInfo?.maxUsers || '0' }}</span>
|
||||||
@@ -11,10 +11,10 @@
|
|||||||
v-for="(train, i) in computedStationTrains"
|
v-for="(train, i) in computedStationTrains"
|
||||||
class="badge user"
|
class="badge user"
|
||||||
:class="train.stopStatus"
|
:class="train.stopStatus"
|
||||||
:key="train.trainNo + i"
|
:key="train.trainId"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@click="navigateTo('/trains', { trainNo: train.trainNo, driverName: train.driverName })"
|
@click="selectModalTrain(train.trainId)"
|
||||||
@keydown.enter="navigateTo('/trains', { trainNo: train.trainNo, driverName: train.driverName })"
|
@keydown.enter="selectModalTrain(train.trainId)"
|
||||||
>
|
>
|
||||||
<span class="user_train">{{ train.trainNo }}</span>
|
<span class="user_train">{{ train.trainNo }}</span>
|
||||||
<span class="user_name">{{ train.driverName }}</span>
|
<span class="user_name">{{ train.driverName }}</span>
|
||||||
@@ -27,12 +27,16 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import routerMixin from '@/mixins/routerMixin';
|
|
||||||
import Station from '@/scripts/interfaces/Station';
|
|
||||||
import { computed, defineComponent } from 'vue';
|
import { computed, defineComponent } from 'vue';
|
||||||
|
import imageMixin from '../../../mixins/imageMixin';
|
||||||
|
import modalTrainMixin from '../../../mixins/modalTrainMixin';
|
||||||
|
import routerMixin from '../../../mixins/routerMixin';
|
||||||
|
import Station from '../../../scripts/interfaces/Station';
|
||||||
|
import { useStore } from '../../../store/store';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
mixins: [routerMixin],
|
mixins: [routerMixin, imageMixin, modalTrainMixin],
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
station: {
|
station: {
|
||||||
@@ -42,6 +46,8 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
setup(props) {
|
setup(props) {
|
||||||
|
const store = useStore();
|
||||||
|
|
||||||
const computedStationTrains = computed(() => {
|
const computedStationTrains = computed(() => {
|
||||||
if (!props.station) return [];
|
if (!props.station) return [];
|
||||||
|
|
||||||
@@ -59,14 +65,8 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return { computedStationTrains };
|
return { computedStationTrains, store };
|
||||||
},
|
},
|
||||||
|
|
||||||
data: () => ({
|
|
||||||
icons: {
|
|
||||||
user: require('@/assets/icon-user.svg'),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -130,3 +130,4 @@ $disconnected: slategray;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -2,192 +2,213 @@
|
|||||||
<section class="scenery-timetable">
|
<section class="scenery-timetable">
|
||||||
<div class="timetable-header">
|
<div class="timetable-header">
|
||||||
<h3>
|
<h3>
|
||||||
<img :src="icons.timetable" alt="icon-timetable" />
|
<img :src="getIcon('timetable')" alt="icon-timetable" />
|
||||||
<span>{{ $t('scenery.timetables') }}</span>
|
<span>{{ $t('scenery.timetables') }}</span>
|
||||||
|
|
||||||
<span class="text--primary">{{ station.onlineInfo?.scheduledTrains?.length || '0' }}</span>
|
<span>
|
||||||
<span> / </span>
|
<span class="text--primary">{{ station.onlineInfo?.scheduledTrains?.length || '0' }}</span>
|
||||||
<span class="text--grayed">
|
<span> / </span>
|
||||||
{{ station.onlineInfo?.scheduledTrains?.filter((train) => train.stopInfo.confirmed).length || '0' }}
|
<span class="text--grayed">
|
||||||
|
{{ station.onlineInfo?.scheduledTrains?.filter((train) => train.stopInfo.confirmed).length || '0' }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="header_links">
|
||||||
|
<a
|
||||||
|
:href="`https://pragotron-td2.web.app/board?name=${station.name}`"
|
||||||
|
target="_blank"
|
||||||
|
:title="$t('scenery.pragotron-link')"
|
||||||
|
>
|
||||||
|
<img :src="getIcon('pragotron')" alt="icon-pragotron" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a
|
||||||
|
:href="`https://tablice-td2.web.app/?station=${station.name}`"
|
||||||
|
target="_blank"
|
||||||
|
:title="$t('scenery.tablice-link')"
|
||||||
|
>
|
||||||
|
<img :src="getIcon('tablice', 'ico')" alt="icon-tablice" />
|
||||||
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div class="timetable-checkpoints" v-if="station && station.generalInfo?.checkpoints">
|
<div class="timetable-checkpoints" v-if="station?.generalInfo?.checkpoints">
|
||||||
<button
|
<span v-for="(cp, i) in station.generalInfo.checkpoints" :key="i">
|
||||||
v-for="cp in station.generalInfo.checkpoints"
|
{{ (i > 0 && '•') || '' }}
|
||||||
:key="cp.checkpointName"
|
|
||||||
class="checkpoint_item btn btn--text"
|
<button
|
||||||
:class="{ current: selectedCheckpoint === cp.checkpointName }"
|
:key="cp.checkpointName"
|
||||||
@click="selectCheckpoint(cp)"
|
class="checkpoint_item"
|
||||||
>
|
:class="{ current: selectedCheckpoint === cp.checkpointName }"
|
||||||
{{ cp.checkpointName }}
|
@click="selectCheckpoint(cp)"
|
||||||
</button>
|
>
|
||||||
|
{{ cp.checkpointName }}
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="timetable-list">
|
<div class="timetable-list">
|
||||||
<!-- <transition name="scenery-timetable-list-anim" mode="out-in"> -->
|
|
||||||
<!-- <div :key="store.dataStatuses.trains + selectedCheckpoint" class="scenery-timetable-list"> -->
|
|
||||||
<div style="padding-bottom: 5em" v-if="store.dataStatuses.trains == 0 && computedScheduledTrains.length == 0">
|
<div style="padding-bottom: 5em" v-if="store.dataStatuses.trains == 0 && computedScheduledTrains.length == 0">
|
||||||
<Loading />
|
<Loading />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<span class="timetable-item empty" v-else-if="computedScheduledTrains.length == 0 && !station.onlineInfo">
|
||||||
|
{{ $t('scenery.offline') }}
|
||||||
|
</span>
|
||||||
|
|
||||||
<span class="timetable-item empty" v-else-if="computedScheduledTrains.length == 0">
|
<span class="timetable-item empty" v-else-if="computedScheduledTrains.length == 0">
|
||||||
{{ $t('scenery.no-timetables') }}
|
{{ $t('scenery.no-timetables') }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div
|
<transition-group name="list-anim">
|
||||||
class="timetable-item"
|
<div
|
||||||
v-for="(scheduledTrain, i) in computedScheduledTrains"
|
class="timetable-item"
|
||||||
:key="i + 1"
|
v-for="(scheduledTrain, i) in computedScheduledTrains"
|
||||||
tabindex="0"
|
:key="scheduledTrain.trainId"
|
||||||
@click="navigateTo('/trains', { trainNo: scheduledTrain.trainNo, driverName: scheduledTrain.driverName })"
|
tabindex="0"
|
||||||
@keydown.enter="
|
@click.prevent.stop="selectModalTrain(scheduledTrain.trainId)"
|
||||||
navigateTo('/trains', {
|
@keydown.enter.prevent="selectModalTrain(scheduledTrain.trainId)"
|
||||||
trainNo: scheduledTrain.trainNo,
|
>
|
||||||
driverName: scheduledTrain.driverName,
|
<span class="timetable-general">
|
||||||
})
|
<span class="general-info">
|
||||||
"
|
<span class="info-number">
|
||||||
>
|
<strong>{{ scheduledTrain.category }}</strong>
|
||||||
<span class="timetable-general">
|
{{ scheduledTrain.trainNo }}
|
||||||
<span class="general-info">
|
|
||||||
<span class="info-number">
|
|
||||||
<strong>{{ scheduledTrain.category }}</strong>
|
|
||||||
{{ scheduledTrain.trainNo }}
|
|
||||||
|
|
||||||
<span class="g-tooltip" v-if="scheduledTrain.stopInfo.comments">
|
<span class="g-tooltip" v-if="scheduledTrain.stopInfo.comments">
|
||||||
<img :src="icons.warning" />
|
<img :src="getIcon('warning')" />
|
||||||
<span class="content" v-html="scheduledTrain.stopInfo.comments"> </span>
|
<span class="content" v-html="scheduledTrain.stopInfo.comments"> </span>
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
|
|
|
||||||
<span style="color: white">
|
|
||||||
{{ scheduledTrain.driverName }}
|
|
||||||
</span>
|
|
||||||
|
|
|
||||||
<span class="general-status">
|
|
||||||
<span :class="scheduledTrain.stopStatus">
|
|
||||||
{{ $t(`timetables.${scheduledTrain.stopStatus}`) }}
|
|
||||||
<span v-if="scheduledTrain.stopStatus == 'arriving'"> {{ scheduledTrain.prevStationName }}</span>
|
|
||||||
<span v-if="scheduledTrain.stopStatus.startsWith('departed')">{{
|
|
||||||
scheduledTrain.nextStationName
|
|
||||||
}}</span>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<div class="info-route">
|
|
||||||
<strong>{{ scheduledTrain.beginsAt }} - {{ scheduledTrain.terminatesAt }}</strong>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="timetable-schedule">
|
|
||||||
<span class="schedule-arrival">
|
|
||||||
<span class="arrival-time begins" v-if="scheduledTrain.stopInfo.beginsHere">
|
|
||||||
{{ $t('timetables.begins') }}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="arrival-time" v-else>
|
|
||||||
<div v-if="scheduledTrain.stopInfo.arrivalDelay == 0">
|
|
||||||
<span>{{ timestampToString(scheduledTrain.stopInfo.arrivalTimestamp) }}</span>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<div>
|
|
||||||
<s style="margin-right: 0.2em" class="text--grayed">{{
|
|
||||||
timestampToString(scheduledTrain.stopInfo.arrivalTimestamp)
|
|
||||||
}}</s>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<span>
|
|
||||||
{{ timestampToString(scheduledTrain.stopInfo.arrivalRealTimestamp) }}
|
|
||||||
({{ scheduledTrain.stopInfo.arrivalDelay > 0 ? '+' : '' }}{{ scheduledTrain.stopInfo.arrivalDelay }})
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</span>
|
||||||
</span>
|
|
|
||||||
</span>
|
<span>
|
||||||
|
{{ scheduledTrain.driverName }}
|
||||||
<span class="schedule-stop">
|
|
||||||
<span class="stop-time">
|
|
||||||
<span v-if="scheduledTrain.stopInfo.stopTime">
|
|
||||||
{{ scheduledTrain.stopInfo.stopTime }}
|
|
||||||
{{ scheduledTrain.stopInfo.stopType || 'pt' }}
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span v-else> </span>
|
<div class="info-route">
|
||||||
</span>
|
<strong>{{ scheduledTrain.beginsAt }} - {{ scheduledTrain.terminatesAt }}</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
<span class="arrow"></span>
|
<ScheduledTrainStatus :scheduledTrain="scheduledTrain" />
|
||||||
|
|
||||||
<span class="stop-line">
|
|
||||||
{{ scheduledTrain.arrivingLine }}
|
|
||||||
{{ scheduledTrain.arrivingLine && scheduledTrain.departureLine && '>' }}
|
|
||||||
{{ scheduledTrain.departureLine }}
|
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="schedule-departure">
|
<span class="timetable-schedule">
|
||||||
<span class="departure-time terminates" v-if="scheduledTrain.stopInfo.terminatesHere">
|
<span class="schedule-arrival">
|
||||||
{{ $t('timetables.terminates') }}
|
<span class="arrival-time begins" v-if="scheduledTrain.stopInfo.beginsHere">
|
||||||
</span>
|
{{ $t('timetables.begins') }}
|
||||||
|
</span>
|
||||||
|
|
||||||
<span class="departure-time" v-else>
|
<span class="arrival-time" v-else>
|
||||||
<div v-if="scheduledTrain.stopInfo.departureDelay == 0">
|
<div v-if="scheduledTrain.stopInfo.arrivalDelay == 0">
|
||||||
<span>{{ timestampToString(scheduledTrain.stopInfo.departureTimestamp) }}</span>
|
<span>{{ timestampToString(scheduledTrain.stopInfo.arrivalTimestamp) }}</span>
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<div>
|
|
||||||
<s style="margin-right: 0.2em" class="text--grayed">{{
|
|
||||||
timestampToString(scheduledTrain.stopInfo.departureTimestamp)
|
|
||||||
}}</s>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<div>
|
||||||
|
<s style="margin-right: 0.2em" class="text--grayed">{{
|
||||||
|
timestampToString(scheduledTrain.stopInfo.arrivalTimestamp)
|
||||||
|
}}</s>
|
||||||
|
</div>
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
{{ timestampToString(scheduledTrain.stopInfo.departureRealTimestamp) }}
|
{{ timestampToString(scheduledTrain.stopInfo.arrivalRealTimestamp) }}
|
||||||
({{ scheduledTrain.stopInfo.departureDelay > 0 ? '+' : ''
|
({{ scheduledTrain.stopInfo.arrivalDelay > 0 ? '+' : ''
|
||||||
}}{{ scheduledTrain.stopInfo.departureDelay }})
|
}}{{ scheduledTrain.stopInfo.arrivalDelay }})
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="schedule-stop">
|
||||||
|
<span class="stop-time">
|
||||||
|
<span v-if="scheduledTrain.stopInfo.stopTime">
|
||||||
|
{{ scheduledTrain.stopInfo.stopTime }}
|
||||||
|
{{ scheduledTrain.stopInfo.stopType || 'pt' }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
|
||||||
|
<span v-else> </span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="arrow"></span>
|
||||||
|
|
||||||
|
<span class="stop-line">
|
||||||
|
<span>
|
||||||
|
{{ scheduledTrain.arrivingLine }}
|
||||||
|
</span>
|
||||||
|
<span></span>
|
||||||
|
<span>
|
||||||
|
{{ scheduledTrain.departureLine }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="schedule-departure">
|
||||||
|
<span class="departure-time terminates" v-if="scheduledTrain.stopInfo.terminatesHere">
|
||||||
|
{{ $t('timetables.terminates') }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="departure-time" v-else>
|
||||||
|
<div v-if="scheduledTrain.stopInfo.departureDelay == 0">
|
||||||
|
<span>{{ timestampToString(scheduledTrain.stopInfo.departureTimestamp) }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<div>
|
||||||
|
<s style="margin-right: 0.2em" class="text--grayed">{{
|
||||||
|
timestampToString(scheduledTrain.stopInfo.departureTimestamp)
|
||||||
|
}}</s>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
{{ timestampToString(scheduledTrain.stopInfo.departureRealTimestamp) }}
|
||||||
|
({{ scheduledTrain.stopInfo.departureDelay > 0 ? '+' : ''
|
||||||
|
}}{{ scheduledTrain.stopInfo.departureDelay }})
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</div>
|
||||||
</div>
|
</transition-group>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- </transition> -->
|
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Station from '@/scripts/interfaces/Station';
|
|
||||||
import SelectBox from '../Global/SelectBox.vue';
|
import SelectBox from '../Global/SelectBox.vue';
|
||||||
import { computed, defineComponent, PropType, ref } from '@vue/runtime-core';
|
import { computed, defineComponent, PropType, ref } from '@vue/runtime-core';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import dateMixin from '@/mixins/dateMixin';
|
|
||||||
import routerMixin from '@/mixins/routerMixin';
|
|
||||||
import { useStore } from '@/store/store';
|
|
||||||
import Loading from '../Global/Loading.vue';
|
import Loading from '../Global/Loading.vue';
|
||||||
|
import TrainModal from '../Global/TrainModal.vue';
|
||||||
|
import dateMixin from '../../mixins/dateMixin';
|
||||||
|
import routerMixin from '../../mixins/routerMixin';
|
||||||
|
import Station from '../../scripts/interfaces/Station';
|
||||||
|
import { useStore } from '../../store/store';
|
||||||
|
import imageMixin from '../../mixins/imageMixin';
|
||||||
|
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
||||||
|
import ScheduledTrainStatus from './ScheduledTrainStatus.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'SceneryTimetable',
|
name: 'SceneryTimetable',
|
||||||
|
|
||||||
components: { SelectBox, Loading },
|
components: { SelectBox, Loading, TrainModal, ScheduledTrainStatus },
|
||||||
|
|
||||||
mixins: [dateMixin, routerMixin],
|
mixins: [dateMixin, routerMixin, imageMixin, modalTrainMixin],
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
station: {
|
station: {
|
||||||
type: Object as PropType<Station>,
|
type: Object as PropType<Station>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
timetableOnly: {
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
viewIcon: require('@/assets/icon-view.svg'),
|
|
||||||
listOpen: false,
|
listOpen: false,
|
||||||
icons: {
|
|
||||||
warning: require('@/assets/icon-warning.svg'),
|
|
||||||
timetable: require('@/assets/icon-timetable.svg'),
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
setup(props) {
|
setup(props) {
|
||||||
@@ -251,6 +272,10 @@ export default defineComponent({
|
|||||||
selectCheckpoint(cp: { checkpointName: string }) {
|
selectCheckpoint(cp: { checkpointName: string }) {
|
||||||
this.selectedCheckpoint = cp.checkpointName;
|
this.selectedCheckpoint = cp.checkpointName;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
showTimetableOnlyView() {
|
||||||
|
this.$router.push(`${this.$route.fullPath}&timetableOnly=1`);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
@@ -266,12 +291,7 @@ export default defineComponent({
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../styles/responsive.scss';
|
@import '../../styles/responsive.scss';
|
||||||
@import '../../styles/variables.scss';
|
@import '../../styles/variables.scss';
|
||||||
|
@import '../../styles/animations.scss';
|
||||||
// .scenery-timetable {
|
|
||||||
// height: 85vh;
|
|
||||||
// max-height: 900px;
|
|
||||||
// min-height: 450px;
|
|
||||||
// }
|
|
||||||
|
|
||||||
.scenery-timetable {
|
.scenery-timetable {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -280,24 +300,36 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
.timetable-header {
|
.timetable-header {
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 99;
|
z-index: 99;
|
||||||
|
|
||||||
background-color: #181818;
|
background-color: #181818;
|
||||||
|
|
||||||
|
padding: 0.5em;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 25px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: 1.4em;
|
|
||||||
|
gap: 0.5em;
|
||||||
|
font-size: 1.3em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header_links {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5em;
|
||||||
|
margin-left: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
.timetable {
|
.timetable {
|
||||||
&-count {
|
&-count {
|
||||||
margin-left: 0.5em;
|
margin-left: 0.5em;
|
||||||
@@ -305,12 +337,14 @@ export default defineComponent({
|
|||||||
|
|
||||||
&-item {
|
&-item {
|
||||||
margin: 0.5em auto;
|
margin: 0.5em auto;
|
||||||
padding: 0 0.5em;
|
padding: 0.5em;
|
||||||
max-width: 1100px;
|
max-width: 1100px;
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
||||||
gap: 0 0.5em;
|
gap: 2em 0.5em;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
background: #353535;
|
background: #353535;
|
||||||
|
|
||||||
@@ -325,9 +359,6 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
&-general {
|
&-general {
|
||||||
padding: 0.5rem 0;
|
|
||||||
border-radius: 10px;
|
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -338,6 +369,10 @@ export default defineComponent({
|
|||||||
&-schedule {
|
&-schedule {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(30px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(30px, 1fr));
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,18 +386,17 @@ export default defineComponent({
|
|||||||
|
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
padding: 0.75em 0;
|
|
||||||
.checkpoint_item {
|
|
||||||
&.current {
|
|
||||||
font-weight: bold;
|
|
||||||
color: $accentCol;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(:last-child)::after {
|
margin-top: 0.5em;
|
||||||
margin: 0 0.5em;
|
|
||||||
content: '•';
|
button.checkpoint_item {
|
||||||
color: white;
|
color: #aaa;
|
||||||
}
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkpoint_item.current {
|
||||||
|
font-weight: bold;
|
||||||
|
color: $accentCol;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,7 +436,6 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
.info-route {
|
.info-route {
|
||||||
margin-top: 0.5em;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -418,38 +451,6 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.general-status {
|
|
||||||
text-align: right;
|
|
||||||
|
|
||||||
span.arriving {
|
|
||||||
color: #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.departed {
|
|
||||||
color: lime;
|
|
||||||
font-weight: bold;
|
|
||||||
|
|
||||||
&-away {
|
|
||||||
font-weight: bold;
|
|
||||||
color: #5ecc5e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
span.stopped {
|
|
||||||
color: #ffa600;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.online {
|
|
||||||
color: gold;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.terminated {
|
|
||||||
color: salmon;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.schedule {
|
.schedule {
|
||||||
&-arrival,
|
&-arrival,
|
||||||
&-stop,
|
&-stop,
|
||||||
@@ -459,23 +460,40 @@ export default defineComponent({
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
margin: 0 0.3rem;
|
margin: 0 0.3rem;
|
||||||
font-size: 1.1em;
|
font-size: 1.15em;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-stop {
|
&-stop {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
font-size: 0.85em;
|
font-size: 0.9em;
|
||||||
|
|
||||||
padding: 0.3em 0;
|
padding: 0.3em 0;
|
||||||
|
|
||||||
.stop-line {
|
.stop-line {
|
||||||
margin-top: 0.25em;
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
span {
|
||||||
|
width: 65px;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
span:first-child {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
span:last-child {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.stop-time {
|
.stop-time {
|
||||||
transform: translateY(-0.25em);
|
position: absolute;
|
||||||
|
transform: translateY(-15px);
|
||||||
|
|
||||||
|
color: $accentCol;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -485,38 +503,9 @@ export default defineComponent({
|
|||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scenery-timetable-list-anim {
|
@include smallScreen {
|
||||||
&-enter-from,
|
.timetable-item {
|
||||||
&-leave-to {
|
grid-template-columns: 1fr;
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-enter-active {
|
|
||||||
transition: all 100ms ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-leave-active {
|
|
||||||
transition: all 100ms ease-out 100ms;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include smallScreen() {
|
|
||||||
.timetable {
|
|
||||||
&-item {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
font-size: 1.05em;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-general {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-schedule {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,110 +1,135 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="scenery-timetables-history scenery-section">
|
<section class="scenery-timetables-history scenery-section">
|
||||||
<Loading v-if="dataStatus != 2" />
|
<Loading v-if="dataStatus != 2" />
|
||||||
|
|
||||||
<div class="list-warning" v-else-if="sceneryHistoryList.length == 0">{{ $t('scenery.history-list-empty') }}</div>
|
<table v-else-if="sceneryHistoryList.length">
|
||||||
<ul class="history-list" v-else>
|
<thead>
|
||||||
<li class="list-item" v-for="historyItem in sceneryHistoryList">
|
<th>{{ $t('scenery.timetables-history-id') }}</th>
|
||||||
<div>
|
<th>{{ $t('scenery.timetables-history-number') }}</th>
|
||||||
<b>{{ localeDay(historyItem.beginDate, $i18n.locale) }}</b>
|
<th>{{ $t('scenery.timetables-history-route') }}</th>
|
||||||
{{ localeTime(historyItem.beginDate, $i18n.locale) }}
|
<th>{{ $t('scenery.timetables-history-driver') }}</th>
|
||||||
</div>
|
<th>{{ $t('scenery.timetables-history-author') }}</th>
|
||||||
<div>
|
<th>{{ $t('scenery.timetables-history-date') }}</th>
|
||||||
<span class="text--grayed"> #{{ historyItem.timetableId }} </span>
|
</thead>
|
||||||
<b class="text--primary"> {{ historyItem.trainCategoryCode }} {{ historyItem.trainNo }}</b>
|
|
||||||
<div>{{ historyItem.driverName }}</div>
|
<tbody>
|
||||||
</div>
|
<tr v-for="historyItem in sceneryHistoryList">
|
||||||
|
<td>
|
||||||
<div>{{ historyItem.route.replace('|', ' -> ') }}</div>
|
<router-link :to="`/journal/timetables?timetableId=${historyItem.id}`">#{{ historyItem.id }}</router-link>
|
||||||
<!-- <div>{{ historyItem.routeDistance }} km</div> -->
|
</td>
|
||||||
<div>
|
<td>
|
||||||
{{ $t('scenery.timetable-author-title') }}:
|
<b class="text--primary">{{ historyItem.trainCategoryCode }}</b> <br />
|
||||||
<b v-if="historyItem.authorName">{{ historyItem.authorName }}</b>
|
{{ historyItem.trainNo }}
|
||||||
<i v-else>{{ $t('scenery.timetable-author-unknown') }}</i>
|
</td>
|
||||||
</div>
|
<td>{{ historyItem.route.replace('|', ' -> ') }}</td>
|
||||||
|
<td>{{ historyItem.driverName }}</td>
|
||||||
<!-- <div v-if="historyItem.authorId">{{ historyItem.authorName }}</div> -->
|
<td>
|
||||||
</li>
|
<router-link
|
||||||
</ul>
|
v-if="historyItem.authorName"
|
||||||
</section>
|
:to="`/journal/dispatchers?dispatcherName=${historyItem.authorName}`"
|
||||||
</template>
|
>{{ historyItem.authorName }}
|
||||||
|
</router-link>
|
||||||
<script lang="ts">
|
<i v-else>{{ $t('scenery.timetable-author-unknown') }}</i>
|
||||||
import dateMixin from '@/mixins/dateMixin';
|
</td>
|
||||||
import { DataStatus } from '@/scripts/enums/DataStatus';
|
<td>
|
||||||
import { SceneryTimetableHistory, TimetableHistory } from '@/scripts/interfaces/api/TimetablesAPIData';
|
<b>{{ localeDay(historyItem.beginDate, $i18n.locale) }}</b>
|
||||||
import Station from '@/scripts/interfaces/Station';
|
{{ localeTime(historyItem.beginDate, $i18n.locale) }}
|
||||||
import { URLs } from '@/scripts/utils/apiURLs';
|
</td>
|
||||||
import axios from 'axios';
|
</tr>
|
||||||
import { defineComponent, PropType } from 'vue';
|
</tbody>
|
||||||
import Loading from '../Global/Loading.vue';
|
</table>
|
||||||
|
|
||||||
export default defineComponent({
|
<div class="list-warning" v-else>{{ $t('scenery.history-list-empty') }}</div>
|
||||||
name: 'SceneryTimetablesHistory',
|
</section>
|
||||||
mixins: [dateMixin],
|
</template>
|
||||||
props: {
|
|
||||||
station: {
|
<script lang="ts">
|
||||||
type: Object as PropType<Station>,
|
import axios from 'axios';
|
||||||
required: true,
|
import { defineComponent, PropType } from 'vue';
|
||||||
},
|
import dateMixin from '../../mixins/dateMixin';
|
||||||
},
|
import { DataStatus } from '../../scripts/enums/DataStatus';
|
||||||
data() {
|
import { TimetableHistory, SceneryTimetableHistory } from '../../scripts/interfaces/api/TimetablesAPIData';
|
||||||
return {
|
import Station from '../../scripts/interfaces/Station';
|
||||||
sceneryHistoryList: [] as TimetableHistory[],
|
import { URLs } from '../../scripts/utils/apiURLs';
|
||||||
dataStatus: DataStatus.Loading,
|
import Loading from '../Global/Loading.vue';
|
||||||
};
|
|
||||||
},
|
export default defineComponent({
|
||||||
mounted() {
|
name: 'SceneryTimetablesHistory',
|
||||||
this.fetchAPIData();
|
mixins: [dateMixin],
|
||||||
},
|
props: {
|
||||||
methods: {
|
station: {
|
||||||
async fetchAPIData(countFrom = 0, countLimit = 15) {
|
type: Object as PropType<Station>,
|
||||||
try {
|
required: true,
|
||||||
const requestString = `${URLs.stacjownikAPI}/api/getSceneryTimetables?name=${this.station.name}&countFrom=${countFrom}&countLimit=${countLimit}`;
|
},
|
||||||
const historyAPIData: SceneryTimetableHistory = await (await axios.get(requestString)).data;
|
},
|
||||||
|
data() {
|
||||||
this.sceneryHistoryList = historyAPIData.sceneryTimetables;
|
return {
|
||||||
this.dataStatus = DataStatus.Loaded;
|
sceneryHistoryList: [] as TimetableHistory[],
|
||||||
} catch (error) {
|
dataStatus: DataStatus.Loading,
|
||||||
console.error(error);
|
};
|
||||||
}
|
},
|
||||||
},
|
activated() {
|
||||||
},
|
this.fetchAPIData();
|
||||||
components: { Loading },
|
},
|
||||||
});
|
methods: {
|
||||||
</script>
|
async fetchAPIData(countFrom = 0, countLimit = 15) {
|
||||||
|
try {
|
||||||
<style lang="scss" scoped>
|
const requestString = `${URLs.stacjownikAPI}/api/getIssuedTimetables?name=${this.station.name}&countFrom=${countFrom}&countLimit=${countLimit}`;
|
||||||
@import '../../styles/responsive.scss';
|
const historyAPIData: SceneryTimetableHistory = await (await axios.get(requestString)).data;
|
||||||
@import '../../styles/SceneryView/styles.scss';
|
|
||||||
|
this.sceneryHistoryList = historyAPIData.timetables;
|
||||||
.list-warning {
|
this.dataStatus = DataStatus.Loaded;
|
||||||
padding: 1em 0.5em;
|
} catch (error) {
|
||||||
background-color: #444;
|
console.error(error);
|
||||||
font-size: 1.2em;
|
}
|
||||||
}
|
},
|
||||||
|
},
|
||||||
.history-list {
|
components: { Loading },
|
||||||
padding: 0 0.5em;
|
});
|
||||||
}
|
</script>
|
||||||
|
|
||||||
.list-item {
|
<style lang="scss" scoped>
|
||||||
display: grid;
|
@import '../../styles/responsive.scss';
|
||||||
grid-template-columns: 1fr 2fr 2fr 1fr;
|
@import '../../styles/SceneryView/styles.scss';
|
||||||
gap: 1em;
|
|
||||||
align-items: center;
|
.list-warning {
|
||||||
|
padding: 1em 0.5em;
|
||||||
background-color: #353535;
|
background-color: #444;
|
||||||
padding: 0.5em;
|
font-size: 1.2em;
|
||||||
margin: 0.5em 0;
|
}
|
||||||
|
|
||||||
line-height: 1.5em;
|
.history-list {
|
||||||
}
|
padding: 0 0.5em;
|
||||||
|
}
|
||||||
@include smallScreen {
|
|
||||||
.list-item {
|
table {
|
||||||
grid-template-columns: 1fr 1fr;
|
width: 100%;
|
||||||
font-size: 1.05em;
|
border-collapse: collapse;
|
||||||
}
|
|
||||||
}
|
thead {
|
||||||
</style>
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
background-color: #222222;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr {
|
||||||
|
background-color: #353535;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: 0.75em;
|
||||||
|
border-bottom: solid 5px #111;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include smallScreen {
|
||||||
|
.list-item {
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
<template>
|
||||||
|
<div class="general-status">
|
||||||
|
<span :class="computedScheduledTrain.stopStatus" :title="computedScheduledTrain.stopStatusDescription">
|
||||||
|
{{ computedScheduledTrain.stopStatusIndicator }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, PropType } from 'vue';
|
||||||
|
import { ScheduledTrain, StopStatus } from '../../scripts/interfaces/ScheduledTrain';
|
||||||
|
|
||||||
|
interface ScheduledTrainComp extends ScheduledTrain {
|
||||||
|
stopStatusIndicator: string;
|
||||||
|
stopStatusDescription: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
scheduledTrain: {
|
||||||
|
type: Object as PropType<ScheduledTrain>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
computedScheduledTrain(): ScheduledTrainComp {
|
||||||
|
const { prevDepartureLine, prevStationName, stopStatus, nextArrivalLine, nextStationName } = this.scheduledTrain;
|
||||||
|
|
||||||
|
const prevDepartureIndicator = prevDepartureLine ? `(${prevDepartureLine}) ${prevStationName}` : '---';
|
||||||
|
const nextArrivalIndicator = nextArrivalLine ? `(${nextArrivalLine}) ${nextStationName}` : '---';
|
||||||
|
|
||||||
|
let stopStatusDescription = '',
|
||||||
|
stopStatusIndicator = '';
|
||||||
|
|
||||||
|
switch (stopStatus) {
|
||||||
|
case StopStatus.arriving:
|
||||||
|
stopStatusIndicator = `${this.$t('timetables.from')}: ${prevDepartureIndicator}`;
|
||||||
|
stopStatusDescription = this.$t('timetables.desc-arriving', { prevStationName, prevDepartureLine });
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StopStatus.online:
|
||||||
|
case StopStatus.stopped:
|
||||||
|
stopStatusIndicator = nextArrivalLine
|
||||||
|
? `${this.$t('timetables.to')}: ${nextArrivalIndicator}`
|
||||||
|
: `${this.$t('timetables.desc-end')}`;
|
||||||
|
stopStatusDescription = nextArrivalLine
|
||||||
|
? this.$t(`timetables.desc-${stopStatus}`, { nextStationName, nextArrivalLine })
|
||||||
|
: '';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StopStatus.departed:
|
||||||
|
stopStatusIndicator = `${this.$t('timetables.to')}: ${nextArrivalIndicator}`;
|
||||||
|
stopStatusDescription = this.$t('timetables.desc-departed', { nextStationName, nextArrivalLine });
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StopStatus['departed-away']:
|
||||||
|
stopStatusIndicator = `${this.$t('timetables.to')}: ${nextArrivalIndicator}`;
|
||||||
|
stopStatusDescription = this.$t('timetables.desc-departed-away', { nextStationName, nextArrivalLine });
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StopStatus.terminated:
|
||||||
|
stopStatusIndicator = `X ${this.$t('timetables.desc-terminated')}`;
|
||||||
|
stopStatusDescription = this.$t('timetables.desc-terminated');
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...this.scheduledTrain,
|
||||||
|
stopStatusDescription,
|
||||||
|
stopStatusIndicator,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.general-status {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
|
||||||
|
span.arriving {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.departed {
|
||||||
|
color: lime;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
&-away {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #5ecc5e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
span.stopped {
|
||||||
|
color: #ffa600;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.online {
|
||||||
|
color: gold;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.terminated {
|
||||||
|
color: salmon;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -1,23 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="filter-option option">
|
<button
|
||||||
<label>
|
class="btn--action"
|
||||||
<input
|
:class="option.section"
|
||||||
type="checkbox"
|
:data-selected="option.value"
|
||||||
:name="option.name"
|
@click="handleLeftClick"
|
||||||
:defaultValue="option.defaultValue"
|
@dblclick="handleDbClick"
|
||||||
:id="option.id"
|
>
|
||||||
v-model="option.value"
|
{{ $t(`filters.${option.id}`) }}
|
||||||
@change="handleChange"
|
</button>
|
||||||
/>
|
|
||||||
<span v-if="option.id != 'troll'" :class="option.section + (option.value ? ' checked' : '')"
|
|
||||||
>{{ option.id != 'troll' ? $t(`filters.${option.id}`) : 'ARKADIA ZDRÓJ' }}
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
|
import { useStationFiltersStore } from '../../store/stationFiltersStore';
|
||||||
|
|
||||||
interface FilterOption {
|
interface FilterOption {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -34,29 +29,54 @@ export default defineComponent({
|
|||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
emits: ['optionChange'],
|
|
||||||
methods: {
|
|
||||||
handleChange() {
|
|
||||||
if (this.option.name == 'troll') {
|
|
||||||
location.href = 'https://www.youtube.com/watch?v=HIcSWuKMwOw';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$emit('optionChange', {
|
setup() {
|
||||||
|
return {
|
||||||
|
filterStore: useStationFiltersStore(),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
handleLeftClick() {
|
||||||
|
this.option.value = !this.option.value;
|
||||||
|
this.filterStore.lastClickedFilterId = '';
|
||||||
|
|
||||||
|
this.filterStore.changeFilterValue({
|
||||||
name: this.option.name,
|
name: this.option.name,
|
||||||
value: this.option.value,
|
value: !this.option.value,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
|
||||||
setup() {
|
handleDbClick(e: Event) {
|
||||||
return {};
|
e.preventDefault();
|
||||||
|
|
||||||
|
this.filterStore.lastClickedFilterId = this.option.id;
|
||||||
|
this.option.value = true;
|
||||||
|
|
||||||
|
this.filterStore.changeFilterValue({
|
||||||
|
name: this.option.name,
|
||||||
|
value: !this.option.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.filterStore.inputs.options
|
||||||
|
.filter((option) => {
|
||||||
|
return option.section == this.option.section && option.id != this.option.id;
|
||||||
|
})
|
||||||
|
.forEach((option) => {
|
||||||
|
this.filterStore.changeFilterValue({
|
||||||
|
name: option.name,
|
||||||
|
value: this.option.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
option.value = !this.option.value;
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../styles/option.scss';
|
$realityCol: #e03b07;
|
||||||
|
|
||||||
$accessCol: #e03b07;
|
$accessCol: #e03b07;
|
||||||
$controlCol: #0085ff;
|
$controlCol: #0085ff;
|
||||||
$signalCol: #bf7c00;
|
$signalCol: #bf7c00;
|
||||||
@@ -64,83 +84,18 @@ $statusCol: #349b32;
|
|||||||
$saveCol: #28a826;
|
$saveCol: #28a826;
|
||||||
$routesCol: #9049c0;
|
$routesCol: #9049c0;
|
||||||
|
|
||||||
.option span {
|
button {
|
||||||
font-size: 0.9em;
|
padding: 0.25em;
|
||||||
&.checked {
|
border-radius: 0;
|
||||||
&.access {
|
|
||||||
background-color: $accessCol;
|
|
||||||
|
|
||||||
&::before {
|
&:focus-visible {
|
||||||
box-shadow: 0 0 6px 1px $accessCol;
|
outline: 1px solid white;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
&.control {
|
&[data-selected='true'] {
|
||||||
background-color: $controlCol;
|
background-color: forestgreen;
|
||||||
|
font-weight: bold;
|
||||||
&::before {
|
|
||||||
box-shadow: 0 0 6px 1px $controlCol;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.signals {
|
|
||||||
background-color: $signalCol;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
box-shadow: 0 0 6px 1px $signalCol;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.routes {
|
|
||||||
background-color: $routesCol;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
box-shadow: 0 0 6px 1px $routesCol;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.status {
|
|
||||||
background-color: $statusCol;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
box-shadow: 0 0 6px 1px $statusCol;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.save {
|
|
||||||
background-color: $saveCol;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
box-shadow: 0 0 6px 1px $saveCol;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.troll {
|
|
||||||
background-color: firebrick;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
box-shadow: 0 0 6px 1px firebrick;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.mode {
|
|
||||||
background-color: lightgreen;
|
|
||||||
color: black;
|
|
||||||
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
position: absolute;
|
|
||||||
content: '';
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
border-radius: 0.5em;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +1,57 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="filter-card" v-click-outside="closeCard">
|
<section class="filter-card" v-click-outside="closeCard" @keydown.esc="closeCard">
|
||||||
<div class="card_btn">
|
<div class="card_controls">
|
||||||
<button class="btn btn--option" @click="toggleCard">
|
<button class="btn--filled btn--image" @click="toggleCard">
|
||||||
<img class="button_icon" :src="filterIcon" alt="icon-filter" />
|
<img class="button_icon" :src="getIcon('filter2')" alt="filter icon" />
|
||||||
{{ $t('options.filters') }}
|
{{ $t('options.filters') }} [F]
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<label for="scenery-search">
|
||||||
|
<input
|
||||||
|
id="scenery-search"
|
||||||
|
list="sceneries"
|
||||||
|
:placeholder="$t('sceneries.scenery-search')"
|
||||||
|
@focus="preventKeyDown = true"
|
||||||
|
@blur="preventKeyDown = false"
|
||||||
|
v-model="chosenSearchScenery"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<datalist id="sceneries">
|
||||||
|
<option v-for="scenery in sortedStationList" :value="scenery.name"></option>
|
||||||
|
</datalist>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<transition name="card-anim">
|
<transition name="card-anim">
|
||||||
<div class="card" v-if="isVisible">
|
<div class="card" v-if="isVisible" tabindex="0" ref="cardEl">
|
||||||
<div class="card_content">
|
<div class="card_content">
|
||||||
<div class="card_title flex">{{ $t('filters.title') }}</div>
|
<div class="card_title flex">{{ $t('filters.title') }}</div>
|
||||||
|
<p class="card_info" v-html="$t('filters.desc')"></p>
|
||||||
|
|
||||||
<section class="card_options">
|
<section class="card_options">
|
||||||
<filter-option
|
<div class="option-section" v-for="section in filterStore.inputs.optionSections">
|
||||||
v-for="(option, i) in inputs.options"
|
<h3 class="text--primary">
|
||||||
:option="option"
|
{{ $t(`filters.sections.${section}`) }}
|
||||||
:key="i"
|
|
||||||
@optionChange="handleChange"
|
<button @click="filterStore.resetSectionOptions(section)">RESET</button>
|
||||||
/>
|
</h3>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<div class="section-inputs">
|
||||||
|
<filter-option
|
||||||
|
v-for="(option, i) in filterStore.inputs.options.filter((o) => o.section == section)"
|
||||||
|
:option="option"
|
||||||
|
:key="i"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="card_timestamp" style="text-align: center">
|
<section class="card_timestamp" style="text-align: center">
|
||||||
<div>{{ $t('filters.minimum-hours-title') }}</div>
|
<div>{{ $t('filters.minimum-hours-title') }}</div>
|
||||||
<span class="clock">
|
<span class="clock">
|
||||||
<button @click="subHour">-</button>
|
<button class="btn--action" @click="subHour">-</button>
|
||||||
<span>{{
|
<span>{{
|
||||||
minimumHours == 0
|
minimumHours == 0
|
||||||
? $t('filters.now')
|
? $t('filters.now')
|
||||||
@@ -31,7 +59,7 @@
|
|||||||
? minimumHours + $t('filters.hour')
|
? minimumHours + $t('filters.hour')
|
||||||
: $t('filters.no-limit')
|
: $t('filters.no-limit')
|
||||||
}}</span>
|
}}</span>
|
||||||
<button @click="addHour">+</button>
|
<button class="btn--action" @click="addHour">+</button>
|
||||||
</span>
|
</span>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -42,11 +70,13 @@
|
|||||||
name="authors"
|
name="authors"
|
||||||
v-model="authorsInputValue"
|
v-model="authorsInputValue"
|
||||||
@input="handleAuthorsInput"
|
@input="handleAuthorsInput"
|
||||||
|
@focus="preventKeyDown = true"
|
||||||
|
@blur="preventKeyDown = false"
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="card_sliders">
|
<section class="card_sliders">
|
||||||
<div class="slider" v-for="(slider, i) in inputs.sliders" :key="i">
|
<div class="slider" v-for="(slider, i) in filterStore.inputs.sliders" :key="i">
|
||||||
<input
|
<input
|
||||||
class="slider-input"
|
class="slider-input"
|
||||||
type="range"
|
type="range"
|
||||||
@@ -63,51 +93,47 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="card_actions">
|
|
||||||
<div>
|
|
||||||
<filter-option
|
|
||||||
@optionChange="saveFilters"
|
|
||||||
:option="{
|
|
||||||
id: 'save',
|
|
||||||
name: 'save',
|
|
||||||
section: 'mode',
|
|
||||||
value: saveOptions,
|
|
||||||
defaultValue: true,
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<action-button class="outlined" @click="resetFilters">
|
|
||||||
{{ $t('filters.reset') }}
|
|
||||||
</action-button>
|
|
||||||
<action-button class="outlined" @click="closeCard">{{ $t('filters.close') }}</action-button>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<section class="card_actions">
|
||||||
|
<div class="action-buttons">
|
||||||
|
<button class="btn--action" style="width: 100%" @click="saveFilters" :data-selected="saveOptions">
|
||||||
|
{{ $t('filters.save') }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="btn--action"
|
||||||
|
@click="resetFilters"
|
||||||
|
:disabled="filterStore.areFiltersAtDefault"
|
||||||
|
:data-disabled="filterStore.areFiltersAtDefault"
|
||||||
|
>
|
||||||
|
{{ $t('filters.reset') }}
|
||||||
|
</button>
|
||||||
|
<button class="btn--action" @click="closeCard">{{ $t('filters.close') }}</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, inject } from '@vue/runtime-core';
|
import { defineComponent, inject } from 'vue';
|
||||||
|
import imageMixin from '../../mixins/imageMixin';
|
||||||
|
import keyMixin from '../../mixins/keyMixin';
|
||||||
|
import routerMixin from '../../mixins/routerMixin';
|
||||||
|
import StorageManager from '../../scripts/managers/storageManager';
|
||||||
|
import { useStationFiltersStore } from '../../store/stationFiltersStore';
|
||||||
|
import { useStore } from '../../store/store';
|
||||||
|
|
||||||
import inputData from '@/data/options.json';
|
|
||||||
|
|
||||||
import StorageManager from '@/scripts/managers/storageManager';
|
|
||||||
import ActionButton from '../Global/ActionButton.vue';
|
import ActionButton from '../Global/ActionButton.vue';
|
||||||
import FilterOption from './FilterOption.vue';
|
import FilterOption from './FilterOption.vue';
|
||||||
import { useStore } from '@/store/store';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { ActionButton, FilterOption },
|
components: { ActionButton, FilterOption },
|
||||||
emits: ['changeFilterValue', 'invertFilters', 'resetFilters'],
|
mixins: [imageMixin, keyMixin, routerMixin],
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
filterIcon: require('@/assets/icon-filter2.svg'),
|
|
||||||
|
|
||||||
inputs: { ...inputData },
|
|
||||||
saveOptions: false,
|
saveOptions: false,
|
||||||
STORAGE_KEY: 'options_saved',
|
STORAGE_KEY: 'options_saved',
|
||||||
|
|
||||||
@@ -117,15 +143,18 @@ export default defineComponent({
|
|||||||
currentRegion: { id: '', value: '' },
|
currentRegion: { id: '', value: '' },
|
||||||
|
|
||||||
delayInputTimer: -1,
|
delayInputTimer: -1,
|
||||||
|
chosenSearchScenery: '',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
const isVisible = inject('isFilterCardVisible');
|
const isVisible = inject('isFilterCardVisible');
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
|
const filterStore = useStationFiltersStore();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isVisible,
|
isVisible,
|
||||||
store,
|
store,
|
||||||
|
filterStore,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -141,20 +170,41 @@ export default defineComponent({
|
|||||||
this.currentRegion = this.store.region;
|
this.currentRegion = this.store.region;
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
computed: {
|
||||||
handleChange(change: { name: string; value: boolean }) {
|
sortedStationList() {
|
||||||
this.$emit('changeFilterValue', {
|
return this.store.stationList
|
||||||
name: change.name,
|
.filter((s) => s.name.toLocaleLowerCase().includes(this.chosenSearchScenery.toLocaleLowerCase()))
|
||||||
value: !change.value,
|
.sort((s1, s2) => (s1.name > s2.name ? 1 : -1));
|
||||||
});
|
},
|
||||||
|
},
|
||||||
|
|
||||||
if (this.saveOptions) StorageManager.setBooleanValue(change.name, change.value);
|
watch: {
|
||||||
|
chosenSearchScenery(value: string) {
|
||||||
|
const chosenStation = this.store.stationList.find(({ name }) => name == value);
|
||||||
|
|
||||||
|
if (chosenStation) {
|
||||||
|
this.$router.push(`/scenery?station=${chosenStation.name.replace(/ /g, '_')}`);
|
||||||
|
this.chosenSearchScenery = '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
isVisible(value: boolean) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (value) (this.$refs['cardEl'] as HTMLDivElement).focus();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
// Override keyMixin function
|
||||||
|
onKeyDownFunction() {
|
||||||
|
this.isVisible = !this.isVisible;
|
||||||
},
|
},
|
||||||
|
|
||||||
handleInput(e: Event) {
|
handleInput(e: Event) {
|
||||||
const target = e.target as HTMLInputElement;
|
const target = e.target as HTMLInputElement;
|
||||||
|
|
||||||
this.$emit('changeFilterValue', {
|
this.filterStore.changeFilterValue({
|
||||||
name: target.name,
|
name: target.name,
|
||||||
value: target.value,
|
value: target.value,
|
||||||
});
|
});
|
||||||
@@ -165,13 +215,13 @@ export default defineComponent({
|
|||||||
handleAuthorsInput(e: Event) {
|
handleAuthorsInput(e: Event) {
|
||||||
clearTimeout(this.delayInputTimer);
|
clearTimeout(this.delayInputTimer);
|
||||||
|
|
||||||
this.delayInputTimer = setTimeout(() => {
|
this.delayInputTimer = window.setTimeout(() => {
|
||||||
this.handleInput(e);
|
this.handleInput(e);
|
||||||
}, 400);
|
}, 400);
|
||||||
},
|
},
|
||||||
|
|
||||||
changeNumericFilterValue(name: string, value: number, saveToStorage = false) {
|
changeNumericFilterValue(name: string, value: number, saveToStorage = false) {
|
||||||
this.$emit('changeFilterValue', {
|
this.filterStore.changeFilterValue({
|
||||||
name,
|
name,
|
||||||
value,
|
value,
|
||||||
});
|
});
|
||||||
@@ -191,17 +241,8 @@ export default defineComponent({
|
|||||||
this.changeNumericFilterValue('onlineFromHours', this.minimumHours, true);
|
this.changeNumericFilterValue('onlineFromHours', this.minimumHours, true);
|
||||||
},
|
},
|
||||||
|
|
||||||
invertFilters() {
|
saveFilters() {
|
||||||
this.inputs.options.forEach((option) => {
|
this.saveOptions = !this.saveOptions;
|
||||||
option.value = !option.value;
|
|
||||||
StorageManager.setBooleanValue(option.name, option.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$emit('invertFilters');
|
|
||||||
},
|
|
||||||
|
|
||||||
saveFilters(change: { value }) {
|
|
||||||
this.saveOptions = change.value;
|
|
||||||
|
|
||||||
if (!this.saveOptions) {
|
if (!this.saveOptions) {
|
||||||
StorageManager.unregisterStorage(this.STORAGE_KEY);
|
StorageManager.unregisterStorage(this.STORAGE_KEY);
|
||||||
@@ -210,28 +251,16 @@ export default defineComponent({
|
|||||||
|
|
||||||
StorageManager.registerStorage(this.STORAGE_KEY);
|
StorageManager.registerStorage(this.STORAGE_KEY);
|
||||||
|
|
||||||
this.inputs.options.forEach((option) => StorageManager.setBooleanValue(option.name, option.value));
|
this.filterStore.inputs.options.forEach((option) => StorageManager.setBooleanValue(option.name, !option.value));
|
||||||
|
this.filterStore.inputs.sliders.forEach((slider) => StorageManager.setNumericValue(slider.name, slider.value));
|
||||||
this.inputs.sliders.forEach((slider) => StorageManager.setNumericValue(slider.name, slider.value));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
resetFilters() {
|
resetFilters() {
|
||||||
this.inputs.options.forEach((option) => {
|
|
||||||
option.value = option.defaultValue;
|
|
||||||
StorageManager.setBooleanValue(option.name, option.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.inputs.sliders.forEach((slider) => {
|
|
||||||
slider.value = slider.defaultValue;
|
|
||||||
StorageManager.setNumericValue(slider.name, slider.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.authorsInputValue = '';
|
this.authorsInputValue = '';
|
||||||
|
|
||||||
this.minimumHours = 0;
|
this.minimumHours = 0;
|
||||||
this.changeNumericFilterValue('onlineFromHours', this.minimumHours, true);
|
this.changeNumericFilterValue('onlineFromHours', this.minimumHours, true);
|
||||||
|
this.filterStore.resetFilters();
|
||||||
this.$emit('resetFilters');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
closeCard() {
|
closeCard() {
|
||||||
@@ -257,34 +286,38 @@ export default defineComponent({
|
|||||||
|
|
||||||
&-enter-from,
|
&-enter-from,
|
||||||
&-leave-to {
|
&-leave-to {
|
||||||
transform: translate(-50%, -50%) scale(0.8);
|
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
transform: translate(-50%, -50%) scale(0.45);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
&_btn {
|
display: grid;
|
||||||
button {
|
grid-template-rows: 1fr auto;
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
padding: 0.5em 1em;
|
&_info {
|
||||||
border-radius: 0.75em 0.75em 0 0;
|
background-color: #111;
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
font-weight: bold;
|
&_controls {
|
||||||
}
|
display: flex;
|
||||||
|
gap: 0.5em;
|
||||||
|
|
||||||
img {
|
input {
|
||||||
width: 1.3em;
|
border-radius: 0.5em 0.5em 0 0;
|
||||||
margin-right: 0.25em;
|
height: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&_content {
|
&_content {
|
||||||
display: grid;
|
padding: 1em 0.5em;
|
||||||
grid-template-rows: 70px 1fr 100px 50px auto;
|
|
||||||
min-height: 0;
|
display: flex;
|
||||||
max-height: 100vh;
|
flex-direction: column;
|
||||||
|
|
||||||
|
gap: 1em;
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
&_title {
|
&_title {
|
||||||
@@ -292,23 +325,9 @@ export default defineComponent({
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: $accentCol;
|
color: $accentCol;
|
||||||
|
|
||||||
margin: 0.5em 0;
|
|
||||||
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
&_options {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
||||||
grid-template-rows: repeat(4, 1fr);
|
|
||||||
gap: 0.5em;
|
|
||||||
|
|
||||||
@include smallScreen() {
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(8em, 1fr));
|
|
||||||
grid-template-rows: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&_regions {
|
&_regions {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -341,32 +360,18 @@ export default defineComponent({
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
font-size: 1.15em;
|
font-size: 1.2em;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
|
||||||
color: $accentCol;
|
span {
|
||||||
font-weight: bold;
|
min-width: 120px;
|
||||||
}
|
font-weight: bold;
|
||||||
|
|
||||||
span {
|
|
||||||
min-width: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
background: none;
|
|
||||||
padding: 0 0.45em;
|
|
||||||
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
color: white;
|
|
||||||
|
|
||||||
font-size: 1.35em;
|
|
||||||
|
|
||||||
&:focus,
|
|
||||||
&:hover {
|
|
||||||
color: $accentCol;
|
color: $accentCol;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 0.2em 0.6em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,23 +393,65 @@ export default defineComponent({
|
|||||||
input {
|
input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
|
border: 1px solid white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&_actions {
|
&_actions {
|
||||||
margin-top: 1em;
|
width: 100%;
|
||||||
|
padding: 0.5em;
|
||||||
|
|
||||||
|
.filter-option {
|
||||||
|
max-width: 50%;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card_options {
|
||||||
|
.option-section h3 {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
margin-bottom: 0.25em;
|
||||||
|
|
||||||
|
gap: 0.5em;
|
||||||
|
|
||||||
button {
|
button {
|
||||||
margin: 1em 0.25em;
|
padding: 0.15em;
|
||||||
|
color: coral;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.option {
|
.section-inputs {
|
||||||
font-size: 1.1em;
|
display: grid;
|
||||||
}
|
// flex-wrap: wrap;
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
// grid-template-rows: repeat(3, 1fr);
|
||||||
|
gap: 0.5em;
|
||||||
|
margin: 1em 0;
|
||||||
|
|
||||||
|
// @include smallScreen() {
|
||||||
|
// grid-template-columns: repeat(auto-fit, minmax(8em, 1fr));
|
||||||
|
// grid-template-rows: auto;
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -434,8 +481,13 @@ export default defineComponent({
|
|||||||
min-width: 25%;
|
min-width: 25%;
|
||||||
max-width: 120px;
|
max-width: 120px;
|
||||||
|
|
||||||
|
&:focus-visible ~ * {
|
||||||
|
color: gold;
|
||||||
|
}
|
||||||
|
|
||||||
&::-webkit-slider-thumb {
|
&::-webkit-slider-thumb {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
|
||||||
height: 20px;
|
height: 20px;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
|
|||||||
@@ -8,27 +8,37 @@
|
|||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th v-for="(id, i) in headIds" :key="id" @click="() => changeSorter(i)">
|
<th
|
||||||
|
v-for="(headerName, i) in headIds"
|
||||||
|
:key="headerName"
|
||||||
|
@click="changeSorter(headerName)"
|
||||||
|
class="header-text"
|
||||||
|
>
|
||||||
<span class="header_wrapper">
|
<span class="header_wrapper">
|
||||||
<div v-html="$t(`sceneries.${id}`)"></div>
|
<div v-html="$t(`sceneries.${headerName}`)"></div>
|
||||||
|
|
||||||
<img
|
<img
|
||||||
class="sort-icon"
|
class="sort-icon"
|
||||||
v-if="sorterActive.index == i"
|
v-if="sorterActive.headerName == headerName"
|
||||||
:src="sorterActive.dir == 1 ? ascIcon : descIcon"
|
:src="sorterActive.dir == 1 ? getIcon('arrow-asc') : getIcon('arrow-desc')"
|
||||||
alt="sort icon"
|
alt="sort icon"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</th>
|
</th>
|
||||||
|
|
||||||
<th v-for="(id, i) in headIconsIds" :key="id" @click="() => changeSorter(i + 7)">
|
<th
|
||||||
|
v-for="(headerName, i) in headIconsIds"
|
||||||
|
:key="headerName"
|
||||||
|
@click="changeSorter(headerName)"
|
||||||
|
class="header-image"
|
||||||
|
>
|
||||||
<span class="header_wrapper">
|
<span class="header_wrapper">
|
||||||
<img :src="require(`@/assets/icon-${id}.svg`)" :alt="id" :title="$t(`sceneries.${id}s`)" />
|
<img :src="getIcon(headerName)" :alt="headerName" :title="$t(`sceneries.${headerName}`)" />
|
||||||
|
|
||||||
<img
|
<img
|
||||||
class="sort-icon"
|
class="sort-icon"
|
||||||
v-if="sorterActive.index == i + 7"
|
v-if="sorterActive.headerName == headerName"
|
||||||
:src="sorterActive.dir == 1 ? ascIcon : descIcon"
|
:src="sorterActive.dir == 1 ? getIcon('arrow-asc') : getIcon('arrow-desc')"
|
||||||
alt="sort icon"
|
alt="sort icon"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
@@ -67,15 +77,15 @@
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span v-else-if="station.generalInfo.availability == 'abandoned'">
|
<span v-else-if="station.generalInfo.availability == 'abandoned'">
|
||||||
<img :src="abandonedIcon" alt="non-public" :title="$t('desc.abandoned')" />
|
<img :src="getIcon('abandoned')" alt="non-public" :title="$t('desc.abandoned')" />
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span v-else-if="station.generalInfo.availability == 'nonPublic'">
|
<span v-else-if="station.generalInfo.availability == 'nonPublic'">
|
||||||
<img :src="lockIcon" alt="non-public" :title="$t('desc.non-public')" />
|
<img :src="getIcon('lock')" alt="non-public" :title="$t('desc.non-public')" />
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span v-else>
|
<span v-else>
|
||||||
<img :src="unavailableIcon" alt="unavailable" :title="$t('desc.unavailable')" />
|
<img :src="getIcon('unavailable')" alt="unavailable" :title="$t('desc.unavailable')" />
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@@ -100,7 +110,10 @@
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="station_dispatcher-exp">
|
<td class="station_dispatcher-exp">
|
||||||
<span v-if="station.onlineInfo" :style="calculateExpStyle(station.onlineInfo.dispatcherExp)">
|
<span
|
||||||
|
v-if="station.onlineInfo"
|
||||||
|
:style="calculateExpStyle(station.onlineInfo.dispatcherExp, station.onlineInfo.dispatcherIsSupporter)"
|
||||||
|
>
|
||||||
{{ 2 > station.onlineInfo.dispatcherExp ? 'L' : station.onlineInfo.dispatcherExp }}
|
{{ 2 > station.onlineInfo.dispatcherExp ? 'L' : station.onlineInfo.dispatcherExp }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
@@ -154,7 +167,7 @@
|
|||||||
<img
|
<img
|
||||||
class="icon-info"
|
class="icon-info"
|
||||||
v-if="station.generalInfo.SUP"
|
v-if="station.generalInfo.SUP"
|
||||||
:src="require(`@/assets/icon-SUP.svg`)"
|
:src="getIcon('SUP')"
|
||||||
alt="SUP (RASP-UZK)"
|
alt="SUP (RASP-UZK)"
|
||||||
:title="$t('desc.SUP')"
|
:title="$t('desc.SUP')"
|
||||||
/>
|
/>
|
||||||
@@ -164,7 +177,7 @@
|
|||||||
<img
|
<img
|
||||||
class="icon-info"
|
class="icon-info"
|
||||||
v-if="station.generalInfo.signalType"
|
v-if="station.generalInfo.signalType"
|
||||||
:src="require(`@/assets/icon-${station.generalInfo.signalType}.svg`)"
|
:src="getIcon(station.generalInfo.signalType)"
|
||||||
:alt="station.generalInfo.signalType"
|
:alt="station.generalInfo.signalType"
|
||||||
:title="$t('desc.signals-type') + $t(`signals.${station.generalInfo.signalType}`)"
|
:title="$t('desc.signals-type') + $t(`signals.${station.generalInfo.signalType}`)"
|
||||||
/>
|
/>
|
||||||
@@ -174,7 +187,7 @@
|
|||||||
<img
|
<img
|
||||||
class="icon-info"
|
class="icon-info"
|
||||||
v-if="station.generalInfo && station.generalInfo.routes.sblRouteNames.length > 0"
|
v-if="station.generalInfo && station.generalInfo.routes.sblRouteNames.length > 0"
|
||||||
:src="SBLIcon"
|
:src="getIcon('SBL')"
|
||||||
alt="SBL"
|
alt="SBL"
|
||||||
:title="$t('desc.SBL') + `${station.generalInfo.routes.sblRouteNames.join(',')}`"
|
:title="$t('desc.SBL') + `${station.generalInfo.routes.sblRouteNames.join(',')}`"
|
||||||
/>
|
/>
|
||||||
@@ -182,30 +195,36 @@
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="station_info" v-else>
|
<td class="station_info" v-else>
|
||||||
<img class="icon-info" :src="unknownIcon" alt="icon-unknown" :title="$t('desc.unknown')" />
|
<img class="icon-info" :src="getIcon('unknown')" alt="icon-unknown" :title="$t('desc.unknown')" />
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="station_users" :class="{ inactive: !station.onlineInfo }">
|
<td class="station_users" :class="{ inactive: !station.onlineInfo }">
|
||||||
<span>
|
<span>
|
||||||
<span class="highlight">{{ station.onlineInfo?.currentUsers || '0' }}</span>
|
<span class="highlight">{{ station.onlineInfo?.currentUsers || 0 }}</span>
|
||||||
/
|
/
|
||||||
<span>{{ station.onlineInfo?.maxUsers || '0' }}</span>
|
<span class="highlight">{{ station.onlineInfo?.maxUsers || 0 }}</span>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="station_spawns" :class="{ inactive: !station.onlineInfo }">
|
<td class="station_spawns" :class="{ inactive: !station.onlineInfo }">
|
||||||
<span class="highlight">{{ station.onlineInfo?.spawns.length || '0' }}</span>
|
<span>{{ station.onlineInfo?.spawns.length || 0 }}</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="station_schedules" :class="{ inactive: !station.onlineInfo }">
|
<td class="station_schedules" style="width: 30px" :class="{ inactive: !station.onlineInfo }">
|
||||||
<span>
|
<span class="highlight">
|
||||||
<span class="highlight">
|
{{ station.onlineInfo?.scheduledTrains?.length || 0 }}
|
||||||
{{ station.onlineInfo?.scheduledTrains?.length || '0' }}
|
</span>
|
||||||
</span>
|
</td>
|
||||||
/
|
|
||||||
<span style="color: #bbb">
|
<td class="station_schedules" style="width: 30px" :class="{ inactive: !station.onlineInfo }">
|
||||||
{{ station.onlineInfo?.scheduledTrains?.filter((train) => train.stopInfo.confirmed).length || '0' }}
|
<span style="color: #ccc">
|
||||||
</span>
|
{{ station.onlineInfo?.scheduledTrains?.filter((train) => !train.stopInfo.confirmed).length || 0 }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="station_schedules" style="width: 30px" :class="{ inactive: !station.onlineInfo }">
|
||||||
|
<span style="color: #66ff6c">
|
||||||
|
{{ station.onlineInfo?.scheduledTrains?.filter((train) => train.stopInfo.confirmed).length || 0 }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -222,17 +241,18 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import styleMixin from '@/mixins/styleMixin';
|
import { defineComponent, computed } from 'vue';
|
||||||
import dateMixin from '@/mixins/dateMixin';
|
import dateMixin from '../../mixins/dateMixin';
|
||||||
import stationInfoMixin from '@/mixins/stationInfoMixin';
|
import imageMixin from '../../mixins/imageMixin';
|
||||||
import returnBtnMixin from '@/mixins/returnBtnMixin';
|
import returnBtnMixin from '../../mixins/returnBtnMixin';
|
||||||
|
import stationInfoMixin from '../../mixins/stationInfoMixin';
|
||||||
import { DataStatus } from '@/scripts/enums/DataStatus';
|
import styleMixin from '../../mixins/styleMixin';
|
||||||
import { computed, ComputedRef, defineComponent } from '@vue/runtime-core';
|
import { DataStatus } from '../../scripts/enums/DataStatus';
|
||||||
import Station from '@/scripts/interfaces/Station';
|
import Station from '../../scripts/interfaces/Station';
|
||||||
import { StoreData } from '@/scripts/interfaces/StoreData';
|
import { useStationFiltersStore } from '../../store/stationFiltersStore';
|
||||||
import { useStore } from '@/store/store';
|
import { useStore } from '../../store/store';
|
||||||
import Loading from '../Global/Loading.vue';
|
import Loading from '../Global/Loading.vue';
|
||||||
|
import { HeadIdsTypes, headIconsIds, headIds } from '../../scripts/data/stationHeaderNames';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
@@ -240,61 +260,60 @@ export default defineComponent({
|
|||||||
type: Array as () => Station[],
|
type: Array as () => Station[],
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
sorterActive: {
|
|
||||||
type: Object as () => {
|
|
||||||
index: number;
|
|
||||||
dir: number;
|
|
||||||
},
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
setFocusedStation: { type: Function, required: true },
|
|
||||||
changeSorter: { type: Function, required: true },
|
|
||||||
},
|
},
|
||||||
mixins: [styleMixin, dateMixin, stationInfoMixin, returnBtnMixin],
|
|
||||||
|
components: { Loading },
|
||||||
|
mixins: [styleMixin, dateMixin, stationInfoMixin, returnBtnMixin, imageMixin],
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
likeIcon: require('@/assets/icon-like.svg'),
|
headIconsIds,
|
||||||
spawnIcon: require('@/assets/icon-spawn.svg'),
|
headIds,
|
||||||
timetableIcon: require('@/assets/icon-timetable.svg'),
|
|
||||||
userIcon: require('@/assets/icon-user.svg'),
|
|
||||||
trainIcon: require('@/assets/icon-train.svg'),
|
|
||||||
SBLIcon: require('@/assets/icon-SBL.svg'),
|
|
||||||
SUPIcon: require('@/assets/icon-SUP.svg'),
|
|
||||||
lockIcon: require('@/assets/icon-lock.svg'),
|
|
||||||
unavailableIcon: require('@/assets/icon-unavailable.svg'),
|
|
||||||
unknownIcon: require('@/assets/icon-unknown.svg'),
|
|
||||||
abandonedIcon: require('@/assets/icon-abandoned.svg'),
|
|
||||||
ascIcon: require('@/assets/icon-arrow-asc.svg'),
|
|
||||||
descIcon: require('@/assets/icon-arrow-desc.svg'),
|
|
||||||
headIds: ['station', 'min-lvl', 'status', 'dispatcher', 'dispatcher-lvl', 'routes', 'general'],
|
|
||||||
headIconsIds: ['user', 'spawn', 'timetable'],
|
|
||||||
lastSelectedStationName: '',
|
lastSelectedStationName: '',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
sorterActive() {
|
||||||
|
return this.stationFiltersStore.sorterActive;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
|
const stationFiltersStore = useStationFiltersStore();
|
||||||
|
|
||||||
const isDataLoaded = computed(() => {
|
const isDataLoaded = computed(() => {
|
||||||
return store.dataStatuses.sceneries != DataStatus.Loading;
|
return store.dataStatuses.sceneries != DataStatus.Loading;
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
isDataLoaded,
|
isDataLoaded,
|
||||||
|
stationFiltersStore,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
setScenery(name: string) {
|
setScenery(name: string) {
|
||||||
const station = this.stations.find((station) => station.name === name);
|
const station = this.stations.find((station) => station.name === name);
|
||||||
if (!station) return;
|
if (!station) return;
|
||||||
|
|
||||||
this.lastSelectedStationName = station.name;
|
this.lastSelectedStationName = station.name;
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
name: 'SceneryView',
|
name: 'SceneryView',
|
||||||
query: { station: station.name.replaceAll(' ', '_') },
|
query: { station: station.name.replaceAll(' ', '_') },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
openForumSite(e: Event, url: string | undefined) {
|
openForumSite(e: Event, url: string | undefined) {
|
||||||
if (!url) return;
|
if (!url) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
window.open(url, '_blank');
|
window.open(url, '_blank');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
changeSorter(headerName: HeadIdsTypes) {
|
||||||
|
if (headerName == 'general' || headerName == 'routes') return;
|
||||||
|
|
||||||
|
this.stationFiltersStore.changeSorter(headerName);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
components: { Loading },
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -303,7 +322,7 @@ export default defineComponent({
|
|||||||
@import '../../styles/variables.scss';
|
@import '../../styles/variables.scss';
|
||||||
@import '../../styles/icons.scss';
|
@import '../../styles/icons.scss';
|
||||||
|
|
||||||
$rowCol: #4b4b4b;
|
$rowCol: #424242;
|
||||||
|
|
||||||
.change-anim {
|
.change-anim {
|
||||||
&-enter-active,
|
&-enter-active,
|
||||||
@@ -342,17 +361,23 @@ table {
|
|||||||
}
|
}
|
||||||
|
|
||||||
thead tr {
|
thead tr {
|
||||||
background-color: $primaryCol;
|
background-color: $bgCol;
|
||||||
}
|
}
|
||||||
|
|
||||||
thead th {
|
thead th {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
||||||
min-width: 75px;
|
&.header-text {
|
||||||
|
min-width: 140px;
|
||||||
|
}
|
||||||
|
|
||||||
padding: 0.5em;
|
&.header-image {
|
||||||
background-color: $primaryCol;
|
min-width: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
padding: 0.5em 0.25em;
|
||||||
|
background-color: $bgCol;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|||||||
@@ -1,290 +1,289 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="train-info simple" tabindex="0">
|
<div class="train-info" tabindex="0">
|
||||||
<section>
|
<section class="train-route">
|
||||||
<span>
|
<div class="train_general">
|
||||||
<div>
|
<b class="warning-timeout" v-if="train.isTimeout" :title="$t('trains.timeout')">?</b>
|
||||||
<span>
|
<span class="timetable-id" v-if="train.timetableData">#{{ train.timetableData.timetableId }}</span>
|
||||||
<!-- <router-link
|
|
||||||
v-if="train.timetableData"
|
<span class="timetable_warnings" v-if="train.timetableData?.TWR || train.timetableData?.SKR">
|
||||||
:to="`/journal/timetables?timetableId=${train.timetableData.timetableId}`"
|
<span class="train-badge twr" v-if="train.timetableData?.TWR" :title="$t('general.TWR')">TWR</span>
|
||||||
style="color: #ddd; margin-right: 0.3em"
|
<span class="train-badge skr" v-if="train.timetableData?.SKR" :title="$t('general.SKR')">SKR</span>
|
||||||
>
|
</span>
|
||||||
#{{ train.timetableData.timetableId }}
|
|
||||||
</router-link> -->
|
<strong>
|
||||||
|
<span v-if="train.timetableData" class="text--primary">{{ train.timetableData.category }} </span>
|
||||||
<span class="timetable-id" v-if="train.timetableData">#{{ train.timetableData.timetableId }}</span>
|
<span class="train-number">{{ train.trainNo }}</span>
|
||||||
|
</strong>
|
||||||
<span class="timetable_warnings">
|
<span>•</span>
|
||||||
<span class="warning twr" v-if="train.timetableData?.TWR">TWR</span>
|
<b class="level-badge driver" :style="calculateExpStyle(train.driverLevel, train.isSupporter)">
|
||||||
<span class="warning skr" v-if="train.timetableData?.SKR">SKR</span>
|
{{ train.driverLevel < 2 ? 'L' : `${train.driverLevel}` }}
|
||||||
</span>
|
</b>
|
||||||
<strong v-if="train.timetableData">{{ train.timetableData.category }} </strong>
|
<span>{{ train.driverName }}</span>
|
||||||
<strong>{{ train.trainNo }}</strong>
|
</div>
|
||||||
<span> | {{ train.driverName }} </span>
|
|
||||||
</span>
|
<div class="timetable_route" v-if="train.timetableData">
|
||||||
|
<strong>{{ train.timetableData.route.replace('|', ' - ') }}</strong>
|
||||||
<img
|
<img
|
||||||
class="image-offline"
|
v-if="getSceneriesWithComments(train.timetableData).length > 0"
|
||||||
style="height: 1em"
|
class="image-warning"
|
||||||
v-if="!train.currentStationHash"
|
:src="getIcon('warning')"
|
||||||
:src="icons.offline"
|
:title="`${$t('trains.timetable-comments')} (${getSceneriesWithComments(train.timetableData)})`"
|
||||||
alt="offline"
|
/>
|
||||||
:title="$t('trains.offline')"
|
</div>
|
||||||
/>
|
|
||||||
</div>
|
<hr style="margin: 0.25em 0" />
|
||||||
|
|
||||||
<div class="timetable_route" v-if="train.timetableData">
|
<div class="timetable_stops" v-if="train.timetableData">
|
||||||
<strong>{{ train.timetableData.route.replace('|', ' - ') }}</strong>
|
<span v-if="train.timetableData.followingStops.length > 2">
|
||||||
<img
|
{{ $t('trains.via-title') }}
|
||||||
v-if="getSceneriesWithComments(train.timetableData).length > 0"
|
<span v-html="displayStopList(train.timetableData.followingStops)"></span>
|
||||||
class="image-warning"
|
</span>
|
||||||
:src="icons.warning"
|
</div>
|
||||||
:title="`${$t('trains.timetable-comments')} (${getSceneriesWithComments(train.timetableData)})`"
|
|
||||||
/>
|
<div class="timetable_progress" style="margin-top: 0.5em" v-if="train.timetableData">
|
||||||
</div>
|
<span class="timetable_progress-bar">
|
||||||
|
<span class="bar-bg"></span>
|
||||||
<hr style="margin: 0.25em 0" />
|
<span
|
||||||
|
class="bar-fg"
|
||||||
<div class="timetable_stops" v-if="train.timetableData">
|
:style="{ width: `${Math.floor(confirmedPercentage(train.timetableData.followingStops))}%` }"
|
||||||
<span v-if="train.timetableData.followingStops.length > 2">
|
></span>
|
||||||
{{ $t('trains.via-title') }}
|
</span>
|
||||||
<span v-html="displayStopList(train.timetableData.followingStops)"></span>
|
|
||||||
</span>
|
<span class="timetable_progress-distance">
|
||||||
</div>
|
{{ currentDistance(train.timetableData.followingStops) }} km /
|
||||||
|
<span class="text--primary"> {{ train.timetableData.routeDistance }} km </span>
|
||||||
<div class="timetable_progress" style="margin-top: 0.5em" v-if="train.timetableData">
|
|
|
||||||
<!-- <span> </span> -->
|
<span v-html="currentDelay(train.timetableData.followingStops)"></span>
|
||||||
<span class="timetable_progress-bar">
|
</span>
|
||||||
<!-- {{ confirmedPercentage(train.timetableData.followingStops) }}% -->
|
|
||||||
<span class="bar-bg"></span>
|
<div class="train-status-badges">
|
||||||
<span
|
<div v-if="!train.currentStationHash" class="train-badge offline">{{ $t('trains.scenery-offline') }}</div>
|
||||||
class="bar-fg"
|
<div v-if="!train.online" class="train-badge offline">Offline {{ lastSeenMessage(train.lastSeen) }}</div>
|
||||||
:style="{ width: `${Math.floor(confirmedPercentage(train.timetableData.followingStops))}%` }"
|
</div>
|
||||||
></span>
|
</div>
|
||||||
</span>
|
|
||||||
|
<div class="driver_position text--grayed" style="margin-top: 0.25em">
|
||||||
<span>
|
{{ displayTrainPosition(train) }}
|
||||||
{{ currentDistance(train.timetableData.followingStops) }} km /
|
</div>
|
||||||
<span class="text--primary"> {{ train.timetableData.routeDistance }} km </span>
|
</section>
|
||||||
|
|
|
||||||
<span v-html="currentDelay(train.timetableData.followingStops)"></span>
|
<section class="train-stats">
|
||||||
</span>
|
<div>
|
||||||
</div>
|
<img :src="train.locoURL" loading="lazy" alt="Loco image not found" @error="onImageError" />
|
||||||
|
</div>
|
||||||
<div v-if="!train.online" style="color: salmon">Offline - {{ lastSeenMessage(train.lastSeen) }}</div>
|
|
||||||
|
<div class="text--grayed">
|
||||||
<div class="driver_position text--grayed" style="margin-top: 0.25em">
|
{{ train.locoType }}
|
||||||
<span v-if="train.currentStationHash">
|
<span v-if="train.cars.length > 0">
|
||||||
{{ $t('trains.current-scenery') }} <span>{{ train['currentStationName'] }} </span>
|
• {{ $t('trains.cars') }}:
|
||||||
</span>
|
<span class="count">{{ train.cars.length }}</span>
|
||||||
|
</span>
|
||||||
<span v-else>
|
</div>
|
||||||
{{ $t('trains.current-scenery') }}
|
|
||||||
<span>{{ train['currentStationName'].replace(/.[a-zA-Z0-9]+.sc/, '') }} (offline) </span>
|
<div>
|
||||||
</span>
|
<span v-for="(stat, i) in STATS.main" :key="stat.name">
|
||||||
|
<span v-if="i > 0"> • </span>
|
||||||
<span v-if="train.signal">
|
<span>{{ `${~~((train as any)[stat.name] * (stat.multiplier || 1))}${stat.unit}` }} </span>
|
||||||
{{ $t('trains.current-signal') }} <span>{{ train['signal'] }} </span>
|
</span>
|
||||||
</span>
|
</div>
|
||||||
|
</section>
|
||||||
<span v-if="train.connectedTrack">
|
</div>
|
||||||
{{ $t('trains.current-track') }} <span>{{ train['connectedTrack'] }} </span>
|
</template>
|
||||||
</span>
|
|
||||||
|
<script lang="ts">
|
||||||
<span v-if="train.distance">({{ displayDistance(train.distance) }})</span>
|
import { defineComponent } from 'vue';
|
||||||
</div>
|
import imageMixin from '../../mixins/imageMixin';
|
||||||
</span>
|
import styleMixin from '../../mixins/styleMixin';
|
||||||
</section>
|
import trainInfoMixin from '../../mixins/trainInfoMixin';
|
||||||
|
import Train from '../../scripts/interfaces/Train';
|
||||||
<section class="train-image" style="display: flex; justify-content: center; align-items: center">
|
|
||||||
<img :src="train.locoURL" loading="lazy" alt="Loco image not found" @error="onImageError" />
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
<div class="text--grayed">
|
train: {
|
||||||
{{ train.locoType }}
|
type: Object as () => Train,
|
||||||
<span v-if="train.cars.length > 0">
|
required: true,
|
||||||
• {{ $t('trains.cars') }}:
|
},
|
||||||
<span class="count">{{ train.cars.length }}</span>
|
|
||||||
</span>
|
extended: {
|
||||||
</div>
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
<div>
|
},
|
||||||
<div>
|
},
|
||||||
<span v-for="(stat, i) in STATS.main" :key="stat.name">
|
|
||||||
<span v-if="i > 0"> • </span>
|
mixins: [trainInfoMixin, imageMixin, styleMixin],
|
||||||
<span>{{ `${~~(train[stat.name] * (stat.multiplier || 1))}${stat.unit}` }} </span>
|
});
|
||||||
</span>
|
</script>
|
||||||
</div>
|
|
||||||
</div>
|
<style lang="scss" scoped>
|
||||||
</section>
|
@import '../../styles/responsive.scss';
|
||||||
</div>
|
@import '../../styles/badge.scss';
|
||||||
</template>
|
|
||||||
|
.image-warning {
|
||||||
<script lang="ts">
|
height: 1em;
|
||||||
import trainInfoMixin from '@/mixins/trainInfoMixin';
|
|
||||||
import Train from '@/scripts/interfaces/Train';
|
margin-left: 0.5em;
|
||||||
import { defineComponent } from 'vue';
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
.train-stats {
|
||||||
props: {
|
display: flex;
|
||||||
train: {
|
justify-content: center;
|
||||||
type: Object as () => Train,
|
align-content: center;
|
||||||
required: true,
|
|
||||||
},
|
flex-direction: column;
|
||||||
},
|
text-align: center;
|
||||||
|
|
||||||
mixins: [trainInfoMixin],
|
img {
|
||||||
|
margin: 0.5em 0;
|
||||||
data: () => ({
|
width: 12em;
|
||||||
icons: {
|
}
|
||||||
warning: require('@/assets/icon-warning.svg'),
|
}
|
||||||
offline: require('@/assets/icon-offline.svg'),
|
|
||||||
},
|
.train-info {
|
||||||
}),
|
display: grid;
|
||||||
});
|
grid-template-columns: 2fr 1fr;
|
||||||
</script>
|
grid-template-rows: 1fr;
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
padding: 1em;
|
||||||
@import '../../styles/responsive.scss';
|
|
||||||
|
background-color: #1a1a1a;
|
||||||
.image-warning,
|
gap: 0.5em;
|
||||||
.image-offline {
|
}
|
||||||
height: 1em;
|
|
||||||
|
.timetable-id {
|
||||||
margin-left: 0.5em;
|
color: #d2d2d2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.train-image {
|
.warning-timeout {
|
||||||
display: flex;
|
background-color: #be3728;
|
||||||
flex-direction: column;
|
|
||||||
|
display: inline-block;
|
||||||
img {
|
text-align: center;
|
||||||
margin: 0.5em 0;
|
|
||||||
width: 12em;
|
padding: 0 0.25em;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
.timetable_stops {
|
||||||
.simple {
|
font-size: 0.75em;
|
||||||
display: grid;
|
}
|
||||||
grid-template-columns: 2fr 1fr;
|
|
||||||
grid-template-rows: 1fr;
|
.train_general {
|
||||||
|
display: flex;
|
||||||
padding: 1em;
|
align-items: center;
|
||||||
background-color: #202020;
|
flex-wrap: wrap;
|
||||||
gap: 0.5em;
|
|
||||||
}
|
gap: 0.25em;
|
||||||
|
margin-right: 1.5em;
|
||||||
.driver_position:first-letter {
|
}
|
||||||
text-transform: capitalize;
|
.train-status-badges {
|
||||||
}
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
.timetable-id {
|
|
||||||
margin-right: 0.3em;
|
gap: 0.25em;
|
||||||
color: #d2d2d2;
|
}
|
||||||
}
|
|
||||||
|
.train-driver {
|
||||||
.timetable_stops {
|
&.supporter {
|
||||||
font-size: 0.75em;
|
color: orange;
|
||||||
}
|
text-shadow: orange 0 0 5px;
|
||||||
|
}
|
||||||
.timetable_route {
|
}
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
.timetable_route {
|
||||||
|
display: flex;
|
||||||
margin-top: 0.5em;
|
align-items: center;
|
||||||
}
|
|
||||||
|
margin-top: 0.5em;
|
||||||
.timetable_warnings {
|
}
|
||||||
color: black;
|
|
||||||
|
.timetable_warnings {
|
||||||
.warning {
|
display: flex;
|
||||||
padding: 0.1em 0.3em;
|
gap: 0.25em;
|
||||||
margin-right: 0.3em;
|
}
|
||||||
border-radius: 1em;
|
|
||||||
|
.timetable_progress {
|
||||||
font-weight: bold;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
&.twr {
|
flex-wrap: wrap;
|
||||||
background: var(--clr-twr);
|
}
|
||||||
}
|
|
||||||
|
.timetable_progress-bar {
|
||||||
&.skr {
|
position: relative;
|
||||||
background: var(--clr-skr);
|
|
||||||
}
|
width: 6em;
|
||||||
}
|
height: 1em;
|
||||||
}
|
margin: 0.5em 0;
|
||||||
|
|
||||||
.timetable_progress {
|
.bar-fg,
|
||||||
display: flex;
|
.bar-bg {
|
||||||
align-items: center;
|
position: absolute;
|
||||||
flex-wrap: wrap;
|
height: 1em;
|
||||||
}
|
width: 100%;
|
||||||
|
|
||||||
.timetable_progress-bar {
|
left: 0;
|
||||||
position: relative;
|
}
|
||||||
|
|
||||||
width: 6em;
|
.bar-fg {
|
||||||
height: 1em;
|
background-color: springgreen;
|
||||||
margin: 0.5em 0;
|
}
|
||||||
|
|
||||||
.bar-fg,
|
.bar-bg {
|
||||||
.bar-bg {
|
background-color: #5b5b5b;
|
||||||
position: absolute;
|
}
|
||||||
height: 1em;
|
}
|
||||||
width: 100%;
|
|
||||||
|
.timetable_progress-distance {
|
||||||
left: 0;
|
margin-right: 0.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bar-fg {
|
.comments {
|
||||||
background-color: springgreen;
|
display: flex;
|
||||||
}
|
align-items: center;
|
||||||
|
|
||||||
.bar-bg {
|
font-size: 0.9em;
|
||||||
background-color: #5b5b5b;
|
|
||||||
}
|
margin-top: 1em;
|
||||||
}
|
|
||||||
|
img {
|
||||||
.comments {
|
margin-right: 0.5em;
|
||||||
display: flex;
|
}
|
||||||
align-items: center;
|
}
|
||||||
|
|
||||||
font-size: 0.9em;
|
@include smallScreen() {
|
||||||
|
.train-info {
|
||||||
margin-top: 1em;
|
grid-template-columns: 1fr;
|
||||||
|
gap: 1em 0;
|
||||||
img {
|
text-align: center;
|
||||||
margin-right: 0.5em;
|
|
||||||
}
|
font-size: 1.15em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@include smallScreen() {
|
.train-stats {
|
||||||
.simple {
|
font-size: 1.1em;
|
||||||
grid-template-columns: 1fr;
|
}
|
||||||
gap: 1em 0;
|
|
||||||
text-align: center;
|
.train_general {
|
||||||
|
justify-content: center;
|
||||||
font-size: 1.25em;
|
}
|
||||||
}
|
|
||||||
|
.train-status-badges {
|
||||||
.info-stats {
|
justify-content: center;
|
||||||
text-align: center;
|
}
|
||||||
}
|
|
||||||
|
.timetable_route {
|
||||||
.timetable_route {
|
justify-content: center;
|
||||||
justify-content: center;
|
}
|
||||||
}
|
|
||||||
|
.timetable_progress {
|
||||||
.timetable_progress {
|
justify-content: center;
|
||||||
justify-content: center;
|
}
|
||||||
}
|
|
||||||
|
.comments {
|
||||||
.comments {
|
flex-direction: column;
|
||||||
flex-direction: column;
|
justify-content: center;
|
||||||
justify-content: center;
|
|
||||||
|
img {
|
||||||
img {
|
margin: 0 0 0.5em 0;
|
||||||
margin: 0 0 0.5em 0;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
</style>
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,269 +1,226 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="train-options">
|
<div class="filters-options" @keydown.esc="showOptions = false">
|
||||||
<div class="options_wrapper">
|
<div class="bg" v-if="showOptions" @click="showOptions = false"></div>
|
||||||
<div class="options_content">
|
|
||||||
<div class="content_select">
|
|
||||||
<select-box
|
|
||||||
:itemList="translatedSorterOptions"
|
|
||||||
:defaultItemIndex="0"
|
|
||||||
@selected="changeSorter"
|
|
||||||
:prefix="$t('trains.sorter-prefix')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="content_search">
|
<button class="filter-button btn--filled btn--image" @click="toggleShowOptions" ref="button">
|
||||||
<div class="search-box">
|
<img :src="getIcon('filter2')" alt="Open filters" />
|
||||||
<input class="search-input" v-model="searchedTrain" :placeholder="$t('trains.search-train')" />
|
{{ $t('options.filters') }} [F]
|
||||||
|
<span class="active-indicator" v-if="currentOptionsActive"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
<img class="search-exit" :src="exitIcon" alt="exit-icon" @click="() => (searchedTrain = '')" />
|
<transition name="options-anim">
|
||||||
|
<div class="options_wrapper" v-if="showOptions">
|
||||||
|
<div class="options_content">
|
||||||
|
<h1 class="option-title">{{ $t('options.search-title') }}</h1>
|
||||||
|
<div class="search_content">
|
||||||
|
<div class="search-box">
|
||||||
|
<input
|
||||||
|
class="search-input"
|
||||||
|
ref="initFocusedElement"
|
||||||
|
@focus="preventKeyDown = true"
|
||||||
|
@blur="preventKeyDown = false"
|
||||||
|
:placeholder="$t(`options.search-train`)"
|
||||||
|
v-model="searchedTrain"
|
||||||
|
/>
|
||||||
|
<button class="search-exit">
|
||||||
|
<img :src="getIcon('exit')" alt="exit-icon" @click="onInputClear('train')" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="search-box">
|
||||||
|
<input
|
||||||
|
class="search-input"
|
||||||
|
@focus="preventKeyDown = true"
|
||||||
|
@blur="preventKeyDown = false"
|
||||||
|
:placeholder="$t(`options.search-driver`)"
|
||||||
|
v-model="searchedDriver"
|
||||||
|
/>
|
||||||
|
<button class="search-exit">
|
||||||
|
<img :src="getIcon('exit')" alt="exit-icon" @click="onInputClear('driver')" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="search-box">
|
<h1 class="option-title">{{ $t('options.sort-title') }}</h1>
|
||||||
<input class="search-input" v-model="searchedDriver" :placeholder="$t('trains.search-driver')" />
|
<div class="options_sorters">
|
||||||
|
<button
|
||||||
|
v-for="opt in translatedSorterOptions"
|
||||||
|
class="sort-option btn--option"
|
||||||
|
:data-selected="opt.id == sorterActive.id"
|
||||||
|
@click="onSorterChange(opt)"
|
||||||
|
>
|
||||||
|
{{ opt.value.toUpperCase() }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<img class="search-exit" :src="exitIcon" alt="exit-icon" @click="() => (searchedDriver = '')" />
|
<h1 class="option-title" v-if="trainFilterList.length != 0">{{ $t('options.filter-title') }}</h1>
|
||||||
|
|
||||||
|
<div class="options_filters">
|
||||||
|
<div v-for="section in Object.keys(TrainFilterSection)">
|
||||||
|
<button
|
||||||
|
class="btn--option"
|
||||||
|
v-for="filter in trainFilterList.filter((f) => f.section == section)"
|
||||||
|
:data-inactive="!filter.isActive"
|
||||||
|
@click="onFilterChange(filter)"
|
||||||
|
>
|
||||||
|
{{ $t(`options.filter-${filter.id}`) }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter-actions">
|
||||||
|
<div></div>
|
||||||
|
<button class="btn--action" @click="resetAllFilters">{{ $t('options.filter-reset') }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</transition>
|
||||||
|
|
||||||
<div class="filters">
|
|
||||||
<span
|
|
||||||
:class="{ active: filter.isActive }"
|
|
||||||
class="filter"
|
|
||||||
v-for="filter in filterList"
|
|
||||||
:key="filter.id"
|
|
||||||
tabindex="0"
|
|
||||||
@contextmenu="
|
|
||||||
(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
"
|
|
||||||
@click.left="toggleFilter(filter)"
|
|
||||||
@keydown.enter="toggleFilter(filter)"
|
|
||||||
@click.right="setFilterOnly(filter)"
|
|
||||||
@keydown.space="setFilterOnly(filter)"
|
|
||||||
>
|
|
||||||
{{ $t(`trains.filter-${filter.id}`) }}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="filter reset-btn" @click="resetFilters" tabindex="0">
|
|
||||||
{{ $t('trains.filter-reset') }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, inject, TrainFilter } from 'vue';
|
import { defineComponent, inject, PropType } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import imageMixin from '../../mixins/imageMixin';
|
||||||
|
import keyMixin from '../../mixins/keyMixin';
|
||||||
|
import ActionButton from '../Global/ActionButton.vue';
|
||||||
import SelectBox from '../Global/SelectBox.vue';
|
import SelectBox from '../Global/SelectBox.vue';
|
||||||
|
import { TrainFilterSection } from '../../scripts/enums/TrainFilterType';
|
||||||
|
import { TrainFilter } from '../../scripts/interfaces/Trains/TrainFilter';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { SelectBox },
|
components: { SelectBox, ActionButton },
|
||||||
emits: ['changeSearchedTrain', 'changeSearchedDriver', 'changeSorter'],
|
mixins: [imageMixin, keyMixin],
|
||||||
|
|
||||||
data: () => ({
|
props: {
|
||||||
exitIcon: require('@/assets/icon-exit.svg'),
|
sorterOptionIds: {
|
||||||
}),
|
type: Array as PropType<Array<string>>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
|
||||||
setup() {
|
currentOptionsActive: {
|
||||||
const { t } = useI18n();
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
const sorterOptions = [
|
},
|
||||||
{
|
},
|
||||||
id: 'distance',
|
|
||||||
value: 'kilometraż',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'progress',
|
|
||||||
value: 'przebyta trasa',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'delay',
|
|
||||||
value: 'opóźnienie',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'mass',
|
|
||||||
value: 'masa',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'speed',
|
|
||||||
value: 'prędkość',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'length',
|
|
||||||
value: 'długość',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
let filterList = inject('filterList') as TrainFilter[];
|
|
||||||
|
|
||||||
const translatedSorterOptions = computed(() =>
|
|
||||||
sorterOptions.map(({ id }) => ({
|
|
||||||
id,
|
|
||||||
value: t(`trains.option-${id}`),
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
|
data() {
|
||||||
return {
|
return {
|
||||||
translatedSorterOptions,
|
showOptions: false,
|
||||||
searchedTrain: inject('searchedTrain') as string,
|
lastSelectedFilter: null as TrainFilter | null,
|
||||||
searchedDriver: inject('searchedDriver') as string,
|
TrainFilterSection,
|
||||||
sorterActive: inject('sorterActive') as { id: string | number; dir: number },
|
|
||||||
filterList,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
searchedTrain: inject('searchedTrain') as string,
|
||||||
|
searchedDriver: inject('searchedDriver') as string,
|
||||||
|
|
||||||
|
sorterActive: inject('sorterActive') as { id: string | number; dir: number },
|
||||||
|
trainFilterList: inject('filterList') as TrainFilter[],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
translatedSorterOptions() {
|
||||||
|
return this.$props.sorterOptionIds.map((id) => ({
|
||||||
|
id,
|
||||||
|
value: this.$t(`options.sort-${id}`),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
changeSorter(item: { id: string | number; value: string }) {
|
// Override keyMixin function
|
||||||
|
onKeyDownFunction() {
|
||||||
|
this.toggleShowOptions();
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleShowOptions() {
|
||||||
|
this.showOptions = !this.showOptions;
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (this.showOptions) (this.$refs['button'] as HTMLButtonElement)?.focus();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onSorterChange(item: { id: string | number; value: string }) {
|
||||||
this.sorterActive.id = item.id;
|
this.sorterActive.id = item.id;
|
||||||
this.sorterActive.dir = -1;
|
this.sorterActive.dir = -1;
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleFilter(filter: TrainFilter) {
|
onFilterChange(filter: TrainFilter) {
|
||||||
|
// if (this.lastSelectedFilter?.id === filter.id)
|
||||||
|
// this.trainFilterList.forEach((tf) => (tf.isActive = filter.id === tf.id));
|
||||||
|
|
||||||
filter.isActive = !filter.isActive;
|
filter.isActive = !filter.isActive;
|
||||||
|
this.lastSelectedFilter = filter;
|
||||||
},
|
},
|
||||||
|
|
||||||
setFilterOnly(filter: TrainFilter) {
|
clearAllFilters() {
|
||||||
this.filterList.forEach((f) => (f.isActive = f.id == filter.id));
|
this.trainFilterList.forEach((filter) => {
|
||||||
|
filter.isActive = false;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
resetFilters() {
|
resetAllFilters() {
|
||||||
this.filterList.forEach((f) => (f.isActive = true));
|
this.trainFilterList.forEach((filter) => {
|
||||||
this.searchedDriver = "";
|
filter.isActive = true;
|
||||||
this.searchedTrain = "";
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onInputClear(id: 'driver' | 'train') {
|
||||||
|
if (id == 'driver') this.searchedDriver = '';
|
||||||
|
if (id == 'train') this.searchedTrain = '';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../styles/responsive';
|
@import '../../styles/filters_options.scss';
|
||||||
|
|
||||||
.train-options {
|
.search_content > div {
|
||||||
@include smallScreen() {
|
margin: 0.5em auto;
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.options {
|
.search_content > button {
|
||||||
&_wrapper {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
&_content {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
|
|
||||||
.content_search,
|
|
||||||
.content_select {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
|
|
||||||
padding: 0.25em 0.25em 0 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.search {
|
|
||||||
&-box {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
background: #333;
|
|
||||||
border-radius: 0.5em;
|
|
||||||
min-width: 200px;
|
|
||||||
margin-right: 0.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-input {
|
|
||||||
border: none;
|
|
||||||
|
|
||||||
min-width: 100%;
|
|
||||||
padding: 0.35em 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-exit {
|
|
||||||
position: absolute;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
top: 50%;
|
|
||||||
right: 10px;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
|
|
||||||
width: 1em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.filters {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
justify-content: center;
|
||||||
|
margin: 0 auto;
|
||||||
margin-top: 0.5em;
|
|
||||||
|
|
||||||
@include smallScreen() {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter {
|
.options_sorters {
|
||||||
background: #333;
|
display: flex;
|
||||||
padding: 0.2em 0.25em;
|
grid-template-columns: repeat(3, 1fr);
|
||||||
margin: 0.25em 0.25em 0 0;
|
|
||||||
font-weight: bold;
|
|
||||||
|
|
||||||
cursor: pointer;
|
|
||||||
color: gray;
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
color: var(--clr-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.reset-btn {
|
|
||||||
color: salmon;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@include smallScreen() {
|
.options_filters > div {
|
||||||
.journal-options {
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
gap: 0.5em;
|
||||||
|
|
||||||
|
button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
color: springgreen;
|
||||||
.options {
|
font-weight: bold;
|
||||||
&_wrapper {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
&_content {
|
&[data-inactive='true'] {
|
||||||
padding: 0 1em;
|
color: #aaa;
|
||||||
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.content_select {
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content_search {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.search {
|
.filter-actions {
|
||||||
&-box,
|
display: flex;
|
||||||
&-button {
|
gap: 0.5em;
|
||||||
margin: 0.5em 0 0 0;
|
width: 100%;
|
||||||
}
|
|
||||||
|
|
||||||
&-box {
|
margin-top: 1em;
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-button {
|
> * {
|
||||||
width: 80%;
|
width: 100%;
|
||||||
max-width: 300px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,159 +0,0 @@
|
|||||||
<template>
|
|
||||||
<section class="filter-card" v-click-outside="closeCard">
|
|
||||||
<div class="card_btn">
|
|
||||||
<action-button @click="toggleCard">
|
|
||||||
<img class="button_icon" :src="filterIcon" alt="icon-filter" />
|
|
||||||
<p>{{ $t('options.filters') }}</p>
|
|
||||||
</action-button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<transition name="card-anim">
|
|
||||||
<div class="card_content card" v-if="isVisible">
|
|
||||||
<div class="card_exit" @click="closeCard"></div>
|
|
||||||
|
|
||||||
<div class="options_wrapper">
|
|
||||||
<div class="options_content">
|
|
||||||
<div class="content_select">
|
|
||||||
<select-box
|
|
||||||
:itemList="translatedSorterOptions"
|
|
||||||
:defaultItemIndex="0"
|
|
||||||
@selected="changeSorter"
|
|
||||||
:prefix="$t('trains.sorter-prefix')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="content_search">
|
|
||||||
<div class="search-box">
|
|
||||||
<input class="search-input" v-model="searchedTrain" :placeholder="$t('trains.search-train')" />
|
|
||||||
|
|
||||||
<img class="search-exit" :src="exitIcon" alt="exit-icon" @click="() => (searchedTrain = '')" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="search-box">
|
|
||||||
<input class="search-input" v-model="searchedDriver" :placeholder="$t('trains.search-driver')" />
|
|
||||||
|
|
||||||
<img class="search-exit" :src="exitIcon" alt="exit-icon" @click="() => (searchedDriver = '')" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<section class="card_actions flex">
|
|
||||||
<action-button class="outlined">
|
|
||||||
{{ $t('filters.reset') }}
|
|
||||||
</action-button>
|
|
||||||
<action-button class="outlined" @click="closeCard">{{ $t('filters.close') }}</action-button>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</section>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, inject } from '@vue/runtime-core';
|
|
||||||
|
|
||||||
import inputData from '@/data/options.json';
|
|
||||||
|
|
||||||
import ActionButton from '@/components/Global/ActionButton.vue';
|
|
||||||
import { sorterOptions } from '@/data/trainOptions';
|
|
||||||
import { TrainFilter, computed } from 'vue';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
import SelectBox from '../Global/SelectBox.vue';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: { ActionButton, SelectBox },
|
|
||||||
emits: ['changeFilterValue', 'invertFilters', 'resetFilters'],
|
|
||||||
|
|
||||||
data: () => ({
|
|
||||||
filterIcon: require('@/assets/icon-filter2.svg'),
|
|
||||||
exitIcon: require('@/assets/icon-exit.svg'),
|
|
||||||
|
|
||||||
inputs: { ...inputData },
|
|
||||||
}),
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
const isVisible = inject('isTrainOptionsCardVisible');
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
|
||||||
|
|
||||||
let filterList = inject('filterList') as TrainFilter[];
|
|
||||||
|
|
||||||
const translatedSorterOptions = computed(() =>
|
|
||||||
sorterOptions.map(({ id }) => ({
|
|
||||||
id,
|
|
||||||
value: t(`trains.option-${id}`),
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
translatedSorterOptions,
|
|
||||||
searchedTrain: inject('searchedTrain') as string,
|
|
||||||
searchedDriver: inject('searchedDriver') as string,
|
|
||||||
sorterActive: inject('sorterActive') as { id: string | number; dir: number },
|
|
||||||
filterList,
|
|
||||||
isVisible,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
closeCard() {
|
|
||||||
this.isVisible = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleCard() {
|
|
||||||
this.isVisible = !this.isVisible;
|
|
||||||
},
|
|
||||||
|
|
||||||
changeSorter(item: { id: string | number; value: string }) {
|
|
||||||
this.sorterActive.id = item.id;
|
|
||||||
this.sorterActive.dir = -1;
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleFilter(filter: TrainFilter) {
|
|
||||||
filter.isActive = !filter.isActive;
|
|
||||||
},
|
|
||||||
|
|
||||||
setFilterOnly(filter: TrainFilter) {
|
|
||||||
this.filterList.forEach((f) => (f.isActive = f.id == filter.id));
|
|
||||||
},
|
|
||||||
|
|
||||||
resetFilters() {
|
|
||||||
this.filterList.forEach((f) => (f.isActive = true));
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@import '../../styles/responsive';
|
|
||||||
@import '../../styles/card';
|
|
||||||
|
|
||||||
.card-anim {
|
|
||||||
&-enter-active,
|
|
||||||
&-leave-active {
|
|
||||||
transition: all $animDuration $animType;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-enter-from,
|
|
||||||
&-leave-to {
|
|
||||||
transform: translate(-50%, -50%) scale(0.85);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
section {
|
|
||||||
margin: 0.5em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&_title {
|
|
||||||
font-size: 2em;
|
|
||||||
font-weight: 700;
|
|
||||||
color: $accentCol;
|
|
||||||
|
|
||||||
margin: 0.5em 0;
|
|
||||||
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -60,11 +60,13 @@
|
|||||||
<b>{{ stop.stopNameRAW }} </b>: <span v-html="stop.comments"></span>
|
<b>{{ stop.stopNameRAW }} </b>: <span v-html="stop.comments"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span v-if="stop.departureLine == train.timetableData!.followingStops[i + 1].arrivalLine">
|
<span
|
||||||
|
v-if="stop.departureLine == train.timetableData!.followingStops[i + 1].arrivalLine && !/sbl/gi.test(stop.departureLine!)"
|
||||||
|
>
|
||||||
{{ stop.departureLine }}
|
{{ stop.departureLine }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span v-else>
|
<span v-else-if="!/sbl/gi.test(stop.departureLine!)">
|
||||||
{{ stop.departureLine }} /
|
{{ stop.departureLine }} /
|
||||||
{{ train.timetableData!.followingStops[i + 1].arrivalLine }}
|
{{ train.timetableData!.followingStops[i + 1].arrivalLine }}
|
||||||
</span>
|
</span>
|
||||||
@@ -83,10 +85,12 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, PropType } from '@vue/runtime-core';
|
import { computed, defineComponent, PropType } from '@vue/runtime-core';
|
||||||
import dateMixin from '@/mixins/dateMixin';
|
import dateMixin from '../../mixins/dateMixin';
|
||||||
import TrainStop from '@/scripts/interfaces/TrainStop';
|
import imageMixin from '../../mixins/imageMixin';
|
||||||
|
import Train from '../../scripts/interfaces/Train';
|
||||||
|
import TrainStop from '../../scripts/interfaces/TrainStop';
|
||||||
|
import { useStore } from '../../store/store';
|
||||||
import StopDate from '../Global/StopDate.vue';
|
import StopDate from '../Global/StopDate.vue';
|
||||||
import Train from '@/scripts/interfaces/Train';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { StopDate },
|
components: { StopDate },
|
||||||
@@ -97,18 +101,14 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [dateMixin],
|
mixins: [dateMixin, imageMixin],
|
||||||
|
|
||||||
emits: ['click'],
|
emits: ['click'],
|
||||||
|
|
||||||
data: () => ({
|
|
||||||
icons: {
|
|
||||||
warning: require('@/assets/icon-warning.svg'),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
|
|
||||||
setup(props) {
|
setup(props) {
|
||||||
return {
|
return {
|
||||||
|
store: useStore(),
|
||||||
|
|
||||||
lastConfirmed: computed(() => {
|
lastConfirmed: computed(() => {
|
||||||
return props.train.timetableData!.followingStops.findIndex(
|
return props.train.timetableData!.followingStops.findIndex(
|
||||||
(stop, i, stops) => stop.confirmed && !stops[i + 1]?.confirmed && !stops[i + 1]?.stopped
|
(stop, i, stops) => stop.confirmed && !stops[i + 1]?.confirmed && !stops[i + 1]?.stopped
|
||||||
@@ -154,7 +154,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
onImageError(e: Event) {
|
onImageError(e: Event) {
|
||||||
const imageEl = e.target as HTMLImageElement;
|
const imageEl = e.target as HTMLImageElement;
|
||||||
imageEl.src = require('@/assets/unknown.png');
|
imageEl.src = this.getImage('unknown.png');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -179,12 +179,7 @@ $stopNameClr: #22a8d1;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.train-schedule {
|
.train-schedule {
|
||||||
background-color: #202020;
|
|
||||||
padding: 0 0.25em;
|
padding: 0 0.25em;
|
||||||
|
|
||||||
@include smallScreen() {
|
|
||||||
font-size: 1.1em;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.train-stock {
|
.train-stock {
|
||||||
@@ -192,10 +187,11 @@ $stopNameClr: #22a8d1;
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul.stock-list {
|
ul.stock-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
overflow-x: auto;
|
overflow: auto;
|
||||||
padding-bottom: 1em;
|
padding-bottom: 1em;
|
||||||
|
|
||||||
li > div {
|
li > div {
|
||||||
@@ -203,11 +199,14 @@ ul.stock-list {
|
|||||||
color: #aaa;
|
color: #aaa;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-height: 60px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.schedule-wrapper {
|
.schedule-wrapper {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
max-height: 500px;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
|
|
||||||
@@ -278,13 +277,14 @@ ul.stop_list > li.stop {
|
|||||||
padding: 0 0.5em;
|
padding: 0 0.5em;
|
||||||
|
|
||||||
&.sbl {
|
&.sbl {
|
||||||
.stop-name,
|
|
||||||
.stop-date {
|
.stop-date {
|
||||||
opacity: 0.7;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stop-name {
|
.stop-name {
|
||||||
background-color: #333;
|
background: none;
|
||||||
|
color: #aaa;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -381,8 +381,6 @@ ul.stop_list > li.stop {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
||||||
padding: 0.15em 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.stop-bar {
|
.stop-bar {
|
||||||
@@ -428,3 +426,4 @@ ul.stop_list > li.stop {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="train-stats" v-click-outside="closeStats">
|
<div class="train-stats" v-click-outside="closeStats">
|
||||||
<action-button class="stats_button" @click="toggleStatsOpen">
|
<action-button class="stats_button" @click="toggleStatsOpen">
|
||||||
<img :src="statsIcon" :alt="$t('trains.stats')" />
|
<img :src="getIcon('stats')" :alt="$t('trains.stats')" />
|
||||||
<p>{{ $t("trains.stats") }}</p>
|
<p>{{ $t('trains.stats') }}</p>
|
||||||
</action-button>
|
</action-button>
|
||||||
|
|
||||||
<transition name="stats-anim" class="stats_wrapper" tag="div">
|
<transition name="stats-anim" class="stats_wrapper" tag="div">
|
||||||
<div class="stats-body" v-if="trainStatsOpen">
|
<div class="stats-body" v-if="trainStatsOpen">
|
||||||
<h2 class="stats-header">
|
<h2 class="stats-header">
|
||||||
<img :src="statsIcon" :alt="$t('trains.stats')" />
|
<img :src="getIcon('stats')" :alt="$t('trains.stats')" />
|
||||||
{{ $t("trains.stats") }}
|
{{ $t('trains.stats') }}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div class="stats-speed">
|
<div class="stats-speed">
|
||||||
<div class="title stats-title">
|
<div class="title stats-title">
|
||||||
{{ $t("trains.stats-speed") }}
|
{{ $t('trains.stats-speed') }}
|
||||||
</div>
|
|
||||||
<div class="stats-content">
|
|
||||||
{{ speedStats.min }} | {{ speedStats.avg }} | {{ speedStats.max }}
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="stats-content">{{ speedStats.min }} | {{ speedStats.avg }} | {{ speedStats.max }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stats-length">
|
<div class="stats-length">
|
||||||
<div class="title stats-title">
|
<div class="title stats-title">
|
||||||
{{ $t("trains.stats-length") }}
|
{{ $t('trains.stats-length') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="stats-content">
|
<div class="stats-content">
|
||||||
{{ timetableStats.min }} | {{ timetableStats.avg }} |
|
{{ timetableStats.min }} | {{ timetableStats.avg }} |
|
||||||
@@ -33,15 +31,11 @@
|
|||||||
|
|
||||||
<div class="stats-categories">
|
<div class="stats-categories">
|
||||||
<div class="title stats-title">
|
<div class="title stats-title">
|
||||||
{{ $t("trains.stats-categories") }}
|
{{ $t('trains.stats-categories') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="category-list">
|
<div class="category-list">
|
||||||
<span
|
<span class="category" v-for="[key, value] of categoryList" :key="key">
|
||||||
class="category"
|
|
||||||
v-for="[key, value] of categoryList"
|
|
||||||
:key="key"
|
|
||||||
>
|
|
||||||
<span class="category-type">{{ key }}</span>
|
<span class="category-type">{{ key }}</span>
|
||||||
<span class="category-count">{{ value }}</span>
|
<span class="category-count">{{ value }}</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -49,28 +43,22 @@
|
|||||||
|
|
||||||
<div class="special-list">
|
<div class="special-list">
|
||||||
<span class="special twr">
|
<span class="special twr">
|
||||||
<span class="special-type">{{
|
<span class="special-type">{{ $t('trains.stats-special-twr') }}</span>
|
||||||
$t("trains.stats-special-twr")
|
|
||||||
}}</span>
|
|
||||||
<span class="special-count">{{ specialTrainCount[0] }}</span>
|
<span class="special-count">{{ specialTrainCount[0] }}</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="special skr">
|
<span class="special skr">
|
||||||
<span class="special-type">{{
|
<span class="special-type">{{ $t('trains.stats-special-skr') }}</span>
|
||||||
$t("trains.stats-special-skr")
|
|
||||||
}}</span>
|
|
||||||
<span class="special-count">{{ specialTrainCount[1] }}</span>
|
<span class="special-count">{{ specialTrainCount[1] }}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stats-locos">
|
<div class="stats-locos">
|
||||||
<div class="title stats-title">{{ $t("trains.stats-locos") }}</div>
|
<div class="title stats-title">{{ $t('trains.stats-locos') }}</div>
|
||||||
|
|
||||||
<div class="loco-list stats-content">
|
<div class="loco-list stats-content">
|
||||||
<div class="loco-item" v-for="(loco, i) in locoList" :key="i">
|
<div class="loco-item" v-for="(loco, i) in locoList" :key="i">{{ loco[0] }} | {{ loco[1] }}</div>
|
||||||
{{ loco[0] }} | {{ loco[1] }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -79,13 +67,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import ActionButton from "@/components/Global/ActionButton.vue";
|
import { defineComponent, computed, inject } from 'vue';
|
||||||
|
import imageMixin from '../../mixins/imageMixin';
|
||||||
import Train from "@/scripts/interfaces/Train";
|
import Train from '../../scripts/interfaces/Train';
|
||||||
import { computed, defineComponent, inject } from "@vue/runtime-core";
|
import ActionButton from '../Global/ActionButton.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { ActionButton },
|
components: { ActionButton },
|
||||||
|
mixins: [imageMixin],
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
trains: {
|
trains: {
|
||||||
type: Array as () => Train[],
|
type: Array as () => Train[],
|
||||||
@@ -95,7 +85,6 @@ export default defineComponent({
|
|||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
trainStatsOpen: false,
|
trainStatsOpen: false,
|
||||||
statsIcon: require("@/assets/icon-stats.svg"),
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
@@ -110,14 +99,11 @@ export default defineComponent({
|
|||||||
|
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const speedStats = computed(() => {
|
const speedStats = computed(() => {
|
||||||
if (props.trains.length == 0) return { avg: "0", min: "0", max: "0" };
|
if (props.trains.length == 0) return { avg: '0', min: '0', max: '0' };
|
||||||
|
|
||||||
const trainList = props.trains.filter((train) => train.timetableData);
|
const trainList = props.trains.filter((train) => train.timetableData);
|
||||||
|
|
||||||
const avg = (
|
const avg = (trainList.reduce((acc, train) => acc + train.speed, 0) / trainList.length).toFixed(2);
|
||||||
trainList.reduce((acc, train) => acc + train.speed, 0) /
|
|
||||||
trainList.length
|
|
||||||
).toFixed(2);
|
|
||||||
|
|
||||||
const minMaxSpeed = trainList.reduce((acc, train) => {
|
const minMaxSpeed = trainList.reduce((acc, train) => {
|
||||||
if (!train.timetableData) return acc;
|
if (!train.timetableData) return acc;
|
||||||
@@ -136,32 +122,21 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const timetableStats = computed(() => {
|
const timetableStats = computed(() => {
|
||||||
if (props.trains.length == 0) return { avg: "0", min: "0", max: "0" };
|
if (props.trains.length == 0) return { avg: '0', min: '0', max: '0' };
|
||||||
|
|
||||||
const activeTrainsLength = props.trains.filter(
|
const activeTrainsLength = props.trains.filter((train) => train.timetableData).length;
|
||||||
(train) => train.timetableData
|
|
||||||
).length;
|
|
||||||
|
|
||||||
const avg = (
|
const avg = (
|
||||||
props.trains.reduce(
|
props.trains.reduce((acc, train) => (train.timetableData ? acc + train.timetableData.routeDistance : acc), 0) /
|
||||||
(acc, train) =>
|
activeTrainsLength
|
||||||
train.timetableData ? acc + train.timetableData.routeDistance : acc,
|
|
||||||
0
|
|
||||||
) / activeTrainsLength
|
|
||||||
).toFixed(2);
|
).toFixed(2);
|
||||||
|
|
||||||
const minMaxDistance = props.trains.reduce((acc, train) => {
|
const minMaxDistance = props.trains.reduce((acc, train) => {
|
||||||
if (!train.timetableData) return acc;
|
if (!train.timetableData) return acc;
|
||||||
|
|
||||||
acc[0] =
|
acc[0] = !acc[0] || train.timetableData.routeDistance < acc[0] ? train.timetableData.routeDistance : acc[0];
|
||||||
!acc[0] || train.timetableData.routeDistance < acc[0]
|
|
||||||
? train.timetableData.routeDistance
|
|
||||||
: acc[0];
|
|
||||||
|
|
||||||
acc[1] =
|
acc[1] = !acc[1] || train.timetableData.routeDistance > acc[1] ? train.timetableData.routeDistance : acc[1];
|
||||||
!acc[1] || train.timetableData.routeDistance > acc[1]
|
|
||||||
? train.timetableData.routeDistance
|
|
||||||
: acc[1];
|
|
||||||
return acc;
|
return acc;
|
||||||
}, [] as any);
|
}, [] as any);
|
||||||
|
|
||||||
@@ -178,9 +153,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
acc.set(
|
acc.set(
|
||||||
train.timetableData.category,
|
train.timetableData.category,
|
||||||
acc.get(train.timetableData.category)
|
acc.get(train.timetableData.category) ? acc.get(train.timetableData.category) + 1 : 1
|
||||||
? acc.get(train.timetableData.category) + 1
|
|
||||||
: 1
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
@@ -193,35 +166,26 @@ export default defineComponent({
|
|||||||
const map: Map<string, number> = props.trains.reduce((acc, train) => {
|
const map: Map<string, number> = props.trains.reduce((acc, train) => {
|
||||||
if (!train.timetableData || !train.locoType) return acc;
|
if (!train.timetableData || !train.locoType) return acc;
|
||||||
|
|
||||||
acc.set(
|
acc.set(train.locoType, acc.get(train.locoType) ? acc.get(train.locoType) + 1 : 1);
|
||||||
train.locoType,
|
|
||||||
acc.get(train.locoType) ? acc.get(train.locoType) + 1 : 1
|
|
||||||
);
|
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, new Map());
|
}, new Map());
|
||||||
|
|
||||||
const sorted = [...map.entries()]
|
const sorted = [...map.entries()].sort((a, b) => b[1] - a[1]).filter((v, i) => i < 3);
|
||||||
.sort((a, b) => b[1] - a[1])
|
|
||||||
.filter((v, i) => i < 3);
|
|
||||||
|
|
||||||
return sorted;
|
return sorted;
|
||||||
});
|
});
|
||||||
|
|
||||||
const specialTrainCount = computed(() => {
|
const specialTrainCount = computed(() => {
|
||||||
const twrList = props.trains.filter(
|
const twrList = props.trains.filter((train) => train.timetableData && train.timetableData.TWR);
|
||||||
(train) => train.timetableData && train.timetableData.TWR
|
|
||||||
);
|
|
||||||
|
|
||||||
const skrList = props.trains.filter(
|
const skrList = props.trains.filter((train) => train.timetableData && train.timetableData.SKR);
|
||||||
(train) => train.timetableData && train.timetableData.SKR
|
|
||||||
);
|
|
||||||
|
|
||||||
return [twrList.length, skrList.length];
|
return [twrList.length, skrList.length];
|
||||||
});
|
});
|
||||||
|
|
||||||
/* Inject list from TrainsView for category filter */
|
/* Inject list from TrainsView for category filter */
|
||||||
const chosenTrainCategories = inject("chosenTrainCategories") as string[];
|
const chosenTrainCategories = inject('chosenTrainCategories') as string[];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
speedStats,
|
speedStats,
|
||||||
@@ -229,14 +193,14 @@ export default defineComponent({
|
|||||||
categoryList,
|
categoryList,
|
||||||
locoList,
|
locoList,
|
||||||
specialTrainCount,
|
specialTrainCount,
|
||||||
chosenTrainCategories
|
chosenTrainCategories,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "../../styles/responsive";
|
@import '../../styles/responsive';
|
||||||
|
|
||||||
.stats-anim {
|
.stats-anim {
|
||||||
&-enter-active,
|
&-enter-active,
|
||||||
@@ -370,4 +334,4 @@ export default defineComponent({
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,83 +1,58 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="train-table" @keydown.esc="closeTimetable">
|
<div class="train-table">
|
||||||
<button class="return-btn" @click="scrollToTop" v-if="showReturnButton">
|
|
||||||
<img :src="icons.arrowAsc" alt="return arrow" />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<transition name="anim" mode="out-in">
|
<transition name="anim" mode="out-in">
|
||||||
<div :key="store.dataStatuses.trains">
|
<div :key="store.dataStatuses.trains">
|
||||||
<Loading v-if="trains.length == 0 && store.dataStatuses.trains == 0" />
|
<div class="table-info" v-if="store.isOffline">
|
||||||
|
{{ $t('app.offline') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="table-info no-trains" v-if="trains.length == 0 && store.dataStatuses.trains != 0">
|
<Loading v-else-if="trains.length == 0 && store.dataStatuses.trains == 0" />
|
||||||
|
|
||||||
|
<div class="table-info no-trains" v-else-if="trains.length == 0 && store.dataStatuses.trains != 0">
|
||||||
{{ $t('trains.no-trains') }}
|
{{ $t('trains.no-trains') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="train-list">
|
<transition-group name="list-anim" tag="ul" class="train-list" v-else>
|
||||||
<li
|
<li
|
||||||
class="train-row"
|
class="train-row"
|
||||||
v-for="train in currentTrains"
|
v-for="train in currentTrains"
|
||||||
:key="train.trainNo + train.driverId"
|
:key="train.trainId"
|
||||||
@click="toggleTimetable(train)"
|
@click.stop="selectModalTrain(train.trainId)"
|
||||||
@keydown.enter="toggleTimetable(train)"
|
@keydown.enter="selectModalTrain(train.trainId)"
|
||||||
>
|
>
|
||||||
<TrainInfo :train="train" />
|
<TrainInfo :train="train" />
|
||||||
|
|
||||||
<TrainSchedule v-if="chosenTrainId == getTrainId(train)" :train="train" ref="card-inner" tabindex="0" />
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</transition-group>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, inject, Ref } from '@vue/runtime-core';
|
import { computed, defineComponent, inject, PropType, Ref } from 'vue';
|
||||||
|
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
||||||
import defaultVehicleIconsJSON from '@/data/defaultVehicleIcons.json';
|
import returnBtnMixin from '../../mixins/returnBtnMixin';
|
||||||
|
import Train from '../../scripts/interfaces/Train';
|
||||||
import Train from '@/scripts/interfaces/Train';
|
import { useStore } from '../../store/store';
|
||||||
|
|
||||||
import TrainSchedule from '@/components/TrainsView/TrainSchedule.vue';
|
|
||||||
import TrainInfo from '@/components/TrainsView/TrainInfo.vue';
|
|
||||||
|
|
||||||
import returnBtnMixin from '@/mixins/returnBtnMixin';
|
|
||||||
import { useStore } from '@/store/store';
|
|
||||||
import Loading from '../Global/Loading.vue';
|
import Loading from '../Global/Loading.vue';
|
||||||
|
import TrainInfo from './TrainInfo.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: { Loading, TrainInfo },
|
||||||
TrainSchedule,
|
|
||||||
TrainInfo,
|
|
||||||
Loading,
|
|
||||||
},
|
|
||||||
|
|
||||||
mixins: [returnBtnMixin],
|
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
trains: {
|
trains: {
|
||||||
type: Array as () => Train[],
|
type: Array as PropType<Train[]>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data: () => ({
|
mixins: [returnBtnMixin, modalTrainMixin],
|
||||||
defaultLocoImage: require('@/assets/unknown.png'),
|
|
||||||
|
|
||||||
icons: {
|
|
||||||
arrowAsc: require('@/assets/icon-arrow-asc.svg'),
|
|
||||||
arrowDesc: require('@/assets/icon-arrow-desc.svg'),
|
|
||||||
},
|
|
||||||
|
|
||||||
defaultVehicleIcons: defaultVehicleIconsJSON,
|
|
||||||
chosenTrainId: null as string | null,
|
|
||||||
}),
|
|
||||||
|
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
|
|
||||||
const searchedTrain = inject('searchedTrain') as Ref<string>;
|
const searchedTrain = inject('searchedTrain') as Ref<string>;
|
||||||
const searchedDriver = inject('searchedDriver') as Ref<string>;
|
const searchedDriver = inject('searchedDriver') as Ref<string>;
|
||||||
|
|
||||||
const currentTrains = computed(() => {
|
const currentTrains = computed(() => {
|
||||||
return props.trains;
|
return props.trains;
|
||||||
});
|
});
|
||||||
@@ -87,80 +62,29 @@ export default defineComponent({
|
|||||||
searchedDriver,
|
searchedDriver,
|
||||||
currentTrains,
|
currentTrains,
|
||||||
store,
|
store,
|
||||||
|
sorterActive: inject('sorterActive') as {
|
||||||
sorterActive: inject('sorterActive') as { id: string | number; dir: number },
|
id: string | number;
|
||||||
distanceLimitExceeded: computed(
|
dir: number;
|
||||||
() => props.trains.findIndex(({ timetableData }) => timetableData && timetableData.routeDistance > 200) != -1
|
},
|
||||||
),
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
activated() {
|
activated() {
|
||||||
const query = this.$route.query;
|
const query = this.$route.query;
|
||||||
|
|
||||||
if (query.trainNo && query.driverName) {
|
if (query.trainNo && query.driverName) {
|
||||||
this.searchedDriver = query.driverName.toString();
|
this.searchedDriver = query.driverName.toString();
|
||||||
this.searchedTrain = query.trainNo.toString();
|
this.searchedTrain = query.trainNo.toString();
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.chosenTrainId = query.driverName + <string>query.trainNo;
|
this.selectModalTrain(query.driverName! + query.trainNo!.toString());
|
||||||
}, 20);
|
}, 20);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
deactivated() {
|
|
||||||
this.chosenTrainId = null;
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
enter(el: HTMLElement) {
|
|
||||||
const maxHeight = getComputedStyle(el).height;
|
|
||||||
|
|
||||||
el.style.height = '0px';
|
|
||||||
|
|
||||||
getComputedStyle(el);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
el.style.height = maxHeight;
|
|
||||||
}, 10);
|
|
||||||
},
|
|
||||||
|
|
||||||
afterEnter(el: HTMLElement) {
|
|
||||||
el.style.height = 'auto';
|
|
||||||
},
|
|
||||||
|
|
||||||
leave(el: HTMLElement) {
|
|
||||||
el.style.height = getComputedStyle(el).height;
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
el.style.height = '0px';
|
|
||||||
}, 10);
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleTimetable(train: Train, state?: boolean) {
|
|
||||||
const id = this.getTrainId(train);
|
|
||||||
|
|
||||||
if (state !== undefined) {
|
|
||||||
this.chosenTrainId = state ? id : null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.chosenTrainId = this.chosenTrainId && this.chosenTrainId == id ? null : id;
|
|
||||||
},
|
|
||||||
|
|
||||||
closeTimetable() {
|
|
||||||
this.chosenTrainId = null;
|
|
||||||
},
|
|
||||||
|
|
||||||
getTrainId(train: Train) {
|
|
||||||
return train.driverName + train.trainNo.toString();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../styles/responsive.scss';
|
@import '../../styles/responsive.scss';
|
||||||
|
@import '../../styles/animations.scss';
|
||||||
|
|
||||||
.anim {
|
.anim {
|
||||||
&-enter-from,
|
&-enter-from,
|
||||||
@@ -181,11 +105,10 @@ export default defineComponent({
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
padding: 1em 0;
|
padding: 1em 0;
|
||||||
margin: 1em 0;
|
|
||||||
|
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
|
|
||||||
background: #333;
|
background: #1a1a1a;
|
||||||
}
|
}
|
||||||
|
|
||||||
img.train-image {
|
img.train-image {
|
||||||
@@ -198,11 +121,31 @@ img.train-image {
|
|||||||
background: var(--clr-warning);
|
background: var(--clr-warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.timeouts-warning {
|
||||||
|
background-color: #333;
|
||||||
|
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.05em;
|
||||||
|
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-timeout {
|
||||||
|
background-color: #be3728;
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
width: 1.25em;
|
||||||
|
height: 1.25em;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
.train {
|
.train {
|
||||||
&-list {
|
&-list {
|
||||||
overflow: auto;
|
position: relative;
|
||||||
|
|
||||||
margin-top: 1em;
|
|
||||||
|
|
||||||
@include smallScreen() {
|
@include smallScreen() {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { JournalFilterSection, JournalFilterType } from '../../scripts/enums/JournalFilterType';
|
||||||
|
import { JournalFilter } from '../../scripts/types/JournalTimetablesTypes';
|
||||||
|
|
||||||
|
export const journalTimetableFilters: JournalFilter[] = [
|
||||||
|
{
|
||||||
|
id: JournalFilterType.ALL,
|
||||||
|
filterSection: JournalFilterSection.TIMETABLE_STATUS,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: JournalFilterType.ACTIVE,
|
||||||
|
filterSection: JournalFilterSection.TIMETABLE_STATUS,
|
||||||
|
isActive: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: JournalFilterType.FULFILLED,
|
||||||
|
filterSection: JournalFilterSection.TIMETABLE_STATUS,
|
||||||
|
isActive: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: JournalFilterType.ABANDONED,
|
||||||
|
filterSection: JournalFilterSection.TIMETABLE_STATUS,
|
||||||
|
isActive: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: JournalFilterType.TWR_SKR,
|
||||||
|
filterSection: JournalFilterSection.TWRSKR,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: JournalFilterType.TWR,
|
||||||
|
filterSection: JournalFilterSection.TWRSKR,
|
||||||
|
isActive: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: JournalFilterType.SKR,
|
||||||
|
filterSection: JournalFilterSection.TWRSKR,
|
||||||
|
isActive: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import { TrainFilterSection, TrainFilterType } from '../../scripts/enums/TrainFilterType';
|
||||||
|
import { TrainFilter } from '../../scripts/interfaces/Trains/TrainFilter';
|
||||||
|
|
||||||
|
export const trainFilters: TrainFilter[] = [
|
||||||
|
{
|
||||||
|
id: TrainFilterType.twr,
|
||||||
|
section: TrainFilterSection.TRAIN_TYPE,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: TrainFilterType.skr,
|
||||||
|
section: TrainFilterSection.TRAIN_TYPE,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: TrainFilterType.common,
|
||||||
|
section: TrainFilterSection.TRAIN_TYPE,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: TrainFilterType.passenger,
|
||||||
|
section: TrainFilterSection.TIMETABLE_TYPE,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: TrainFilterType.freight,
|
||||||
|
section: TrainFilterSection.TIMETABLE_TYPE,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: TrainFilterType.other,
|
||||||
|
section: TrainFilterSection.TIMETABLE_TYPE,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: TrainFilterType.withComments,
|
||||||
|
section: TrainFilterSection.COMMENTS,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: TrainFilterType.noComments,
|
||||||
|
section: TrainFilterSection.COMMENTS,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: TrainFilterType.withTimetable,
|
||||||
|
section: TrainFilterSection.TIMETABLE,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: TrainFilterType.noTimetable,
|
||||||
|
section: TrainFilterSection.TIMETABLE,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const sorterOptions = [
|
||||||
|
{
|
||||||
|
id: 'distance',
|
||||||
|
value: 'kilometraż',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'id',
|
||||||
|
value: 'id rozkładu',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'progress',
|
||||||
|
value: 'przebyta trasa',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'delay',
|
||||||
|
value: 'opóźnienie',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mass',
|
||||||
|
value: 'masa',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'speed',
|
||||||
|
value: 'prędkość',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'length',
|
||||||
|
value: 'długość',
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import { JournalFilterType } from "@/scripts/enums/JournalFilterType";
|
|
||||||
import { JournalFilter } from "vue";
|
|
||||||
|
|
||||||
export const journalTimetableFilters: JournalFilter[] = [
|
|
||||||
{
|
|
||||||
id: JournalFilterType.all,
|
|
||||||
filterSection: "timetable-status",
|
|
||||||
isActive: true
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
id: JournalFilterType.active,
|
|
||||||
filterSection: "timetable-status",
|
|
||||||
isActive: false
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
id: JournalFilterType.fulfilled,
|
|
||||||
filterSection: "timetable-status",
|
|
||||||
isActive: false
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
id: JournalFilterType.abandoned,
|
|
||||||
filterSection: "timetable-status",
|
|
||||||
isActive: false
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
export const journalDispatcherFilters: JournalFilter[] = []
|
|
||||||
@@ -1,41 +1,38 @@
|
|||||||
{
|
{
|
||||||
|
"optionSections": ["reality", "package-access", "access", "control", "addons", "blockades", "signals", "status"],
|
||||||
|
|
||||||
"options": [
|
"options": [
|
||||||
{
|
|
||||||
"id": "default",
|
|
||||||
"name": "default",
|
|
||||||
"iconName": "td2",
|
|
||||||
"section": "access",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "not-default",
|
|
||||||
"name": "notDefault",
|
|
||||||
"iconName": "",
|
|
||||||
"section": "access",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "real",
|
"id": "real",
|
||||||
"name": "real",
|
"name": "real",
|
||||||
"iconName": "lock",
|
"section": "reality",
|
||||||
"section": "access",
|
|
||||||
"value": true,
|
"value": true,
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "fictional",
|
"id": "fictional",
|
||||||
"name": "fictional",
|
"name": "fictional",
|
||||||
"iconName": "user",
|
"section": "reality",
|
||||||
"section": "access",
|
"value": true,
|
||||||
|
"defaultValue": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "default",
|
||||||
|
"name": "default",
|
||||||
|
"section": "package-access",
|
||||||
|
"value": true,
|
||||||
|
"defaultValue": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "not-default",
|
||||||
|
"name": "notDefault",
|
||||||
|
"section": "package-access",
|
||||||
"value": true,
|
"value": true,
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "non-public",
|
"id": "non-public",
|
||||||
"name": "nonPublic",
|
"name": "nonPublic",
|
||||||
"iconName": "user",
|
|
||||||
"section": "access",
|
"section": "access",
|
||||||
"value": true,
|
"value": true,
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
@@ -43,7 +40,6 @@
|
|||||||
{
|
{
|
||||||
"id": "unavailable",
|
"id": "unavailable",
|
||||||
"name": "unavailable",
|
"name": "unavailable",
|
||||||
"iconName": "user",
|
|
||||||
"section": "access",
|
"section": "access",
|
||||||
"value": false,
|
"value": false,
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
@@ -51,7 +47,6 @@
|
|||||||
{
|
{
|
||||||
"id": "abandoned",
|
"id": "abandoned",
|
||||||
"name": "abandoned",
|
"name": "abandoned",
|
||||||
"iconName": "user",
|
|
||||||
"section": "access",
|
"section": "access",
|
||||||
"value": false,
|
"value": false,
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
@@ -59,7 +54,6 @@
|
|||||||
{
|
{
|
||||||
"id": "SPK",
|
"id": "SPK",
|
||||||
"name": "SPK",
|
"name": "SPK",
|
||||||
"iconName": "SPK",
|
|
||||||
"section": "control",
|
"section": "control",
|
||||||
"value": true,
|
"value": true,
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
@@ -67,7 +61,6 @@
|
|||||||
{
|
{
|
||||||
"id": "SCS",
|
"id": "SCS",
|
||||||
"name": "SCS",
|
"name": "SCS",
|
||||||
"iconName": "SCS",
|
|
||||||
"section": "control",
|
"section": "control",
|
||||||
"value": true,
|
"value": true,
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
@@ -75,15 +68,21 @@
|
|||||||
{
|
{
|
||||||
"id": "SPE",
|
"id": "SPE",
|
||||||
"name": "SPE",
|
"name": "SPE",
|
||||||
"iconName": "SPE",
|
"section": "control",
|
||||||
|
"value": true,
|
||||||
|
"defaultValue": true
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": "SPK-M",
|
||||||
|
"name": "mechaniczne+SPK",
|
||||||
"section": "control",
|
"section": "control",
|
||||||
"value": true,
|
"value": true,
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "manual",
|
"id": "SCS-M",
|
||||||
"name": "ręczne",
|
"name": "mechaniczne+SCS",
|
||||||
"iconName": "ręczne",
|
|
||||||
"section": "control",
|
"section": "control",
|
||||||
"value": true,
|
"value": true,
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
@@ -91,7 +90,27 @@
|
|||||||
{
|
{
|
||||||
"id": "mechanical",
|
"id": "mechanical",
|
||||||
"name": "mechaniczne",
|
"name": "mechaniczne",
|
||||||
"iconName": "mechaniczne",
|
"section": "control",
|
||||||
|
"value": true,
|
||||||
|
"defaultValue": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "SPK-R",
|
||||||
|
"name": "ręczne+SPK",
|
||||||
|
"section": "control",
|
||||||
|
"value": true,
|
||||||
|
"defaultValue": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "SCS-R",
|
||||||
|
"name": "ręczne+SCS",
|
||||||
|
"section": "control",
|
||||||
|
"value": true,
|
||||||
|
"defaultValue": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "manual",
|
||||||
|
"name": "ręczne",
|
||||||
"section": "control",
|
"section": "control",
|
||||||
"value": true,
|
"value": true,
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
@@ -99,23 +118,34 @@
|
|||||||
{
|
{
|
||||||
"id": "SUP",
|
"id": "SUP",
|
||||||
"name": "SUP",
|
"name": "SUP",
|
||||||
"iconName": "SUP",
|
"section": "addons",
|
||||||
"section": "control",
|
"value": true,
|
||||||
|
"defaultValue": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "noSUP",
|
||||||
|
"name": "noSUP",
|
||||||
|
"section": "addons",
|
||||||
"value": true,
|
"value": true,
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "SBL",
|
"id": "SBL",
|
||||||
"name": "SBL",
|
"name": "SBL",
|
||||||
"iconName": "SBL",
|
"section": "blockades",
|
||||||
"section": "routes",
|
"value": true,
|
||||||
|
"defaultValue": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "PBL",
|
||||||
|
"name": "PBL",
|
||||||
|
"section": "blockades",
|
||||||
"value": true,
|
"value": true,
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "modern",
|
"id": "modern",
|
||||||
"name": "współczesna",
|
"name": "współczesna",
|
||||||
"iconName": "współczesna",
|
|
||||||
"section": "signals",
|
"section": "signals",
|
||||||
"value": true,
|
"value": true,
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
@@ -123,7 +153,6 @@
|
|||||||
{
|
{
|
||||||
"id": "semaphores",
|
"id": "semaphores",
|
||||||
"name": "kształtowa",
|
"name": "kształtowa",
|
||||||
"iconName": "kształtowa",
|
|
||||||
"section": "signals",
|
"section": "signals",
|
||||||
"value": true,
|
"value": true,
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
@@ -131,7 +160,6 @@
|
|||||||
{
|
{
|
||||||
"id": "mixed",
|
"id": "mixed",
|
||||||
"name": "mieszana",
|
"name": "mieszana",
|
||||||
"iconName": "mieszana",
|
|
||||||
"section": "signals",
|
"section": "signals",
|
||||||
"value": true,
|
"value": true,
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
@@ -139,7 +167,6 @@
|
|||||||
{
|
{
|
||||||
"id": "historical",
|
"id": "historical",
|
||||||
"name": "historyczna",
|
"name": "historyczna",
|
||||||
"iconName": "historyczna",
|
|
||||||
"section": "signals",
|
"section": "signals",
|
||||||
"value": true,
|
"value": true,
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
@@ -148,7 +175,6 @@
|
|||||||
{
|
{
|
||||||
"id": "free",
|
"id": "free",
|
||||||
"name": "free",
|
"name": "free",
|
||||||
"iconName": "",
|
|
||||||
|
|
||||||
"section": "status",
|
"section": "status",
|
||||||
"value": false,
|
"value": false,
|
||||||
@@ -157,7 +183,6 @@
|
|||||||
{
|
{
|
||||||
"id": "occupied",
|
"id": "occupied",
|
||||||
"name": "occupied",
|
"name": "occupied",
|
||||||
"iconName": "",
|
|
||||||
|
|
||||||
"section": "status",
|
"section": "status",
|
||||||
"value": true,
|
"value": true,
|
||||||
@@ -166,7 +191,6 @@
|
|||||||
{
|
{
|
||||||
"id": "endingStatus",
|
"id": "endingStatus",
|
||||||
"name": "endingStatus",
|
"name": "endingStatus",
|
||||||
"iconName": "",
|
|
||||||
|
|
||||||
"section": "status",
|
"section": "status",
|
||||||
"value": true,
|
"value": true,
|
||||||
@@ -175,7 +199,6 @@
|
|||||||
{
|
{
|
||||||
"id": "afkStatus",
|
"id": "afkStatus",
|
||||||
"name": "afkStatus",
|
"name": "afkStatus",
|
||||||
"iconName": "",
|
|
||||||
|
|
||||||
"section": "status",
|
"section": "status",
|
||||||
"value": true,
|
"value": true,
|
||||||
@@ -184,7 +207,6 @@
|
|||||||
{
|
{
|
||||||
"id": "noSpaceStatus",
|
"id": "noSpaceStatus",
|
||||||
"name": "noSpaceStatus",
|
"name": "noSpaceStatus",
|
||||||
"iconName": "",
|
|
||||||
|
|
||||||
"section": "status",
|
"section": "status",
|
||||||
"value": true,
|
"value": true,
|
||||||
@@ -193,20 +215,10 @@
|
|||||||
{
|
{
|
||||||
"id": "unavailableStatus",
|
"id": "unavailableStatus",
|
||||||
"name": "unavailableStatus",
|
"name": "unavailableStatus",
|
||||||
"iconName": "",
|
|
||||||
|
|
||||||
"section": "status",
|
"section": "status",
|
||||||
"value": true,
|
"value": true,
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "troll",
|
|
||||||
"name": "troll",
|
|
||||||
"iconName": "",
|
|
||||||
|
|
||||||
"section": "troll",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"sliders": [
|
"sliders": [
|
||||||
@@ -263,7 +275,6 @@
|
|||||||
{
|
{
|
||||||
"id": "include-selected",
|
"id": "include-selected",
|
||||||
"name": "include-selected",
|
"name": "include-selected",
|
||||||
"iconName": "",
|
|
||||||
"section": "mode",
|
"section": "mode",
|
||||||
"value": true,
|
"value": true,
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
@@ -271,7 +282,6 @@
|
|||||||
{
|
{
|
||||||
"id": "save",
|
"id": "save",
|
||||||
"name": "save",
|
"name": "save",
|
||||||
"iconName": "",
|
|
||||||
"section": "mode",
|
"section": "mode",
|
||||||
"value": true,
|
"value": true,
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
import { TrainFilterType } from "@/scripts/enums/TrainFilterType";
|
|
||||||
import { TrainFilter } from "vue";
|
|
||||||
|
|
||||||
export const trainFilters: TrainFilter[] = [
|
|
||||||
{
|
|
||||||
id: TrainFilterType.twr,
|
|
||||||
isActive: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: TrainFilterType.skr,
|
|
||||||
isActive: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: TrainFilterType.passenger,
|
|
||||||
isActive: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: TrainFilterType.freight,
|
|
||||||
isActive: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: TrainFilterType.other,
|
|
||||||
isActive: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: TrainFilterType.comments,
|
|
||||||
isActive: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: TrainFilterType.noTimetable,
|
|
||||||
isActive: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const sorterOptions = [
|
|
||||||
{
|
|
||||||
id: 'distance',
|
|
||||||
value: 'kilometraż',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'progress',
|
|
||||||
value: 'przebyta trasa',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'delay',
|
|
||||||
value: 'opóźnienie',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'mass',
|
|
||||||
value: 'masa',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'speed',
|
|
||||||
value: 'prędkość',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'length',
|
|
||||||
value: 'długość',
|
|
||||||
}
|
|
||||||
];
|
|
||||||
@@ -1,4 +1,10 @@
|
|||||||
{
|
{
|
||||||
|
"general": {
|
||||||
|
"and": " and ",
|
||||||
|
"refresh": "REFRESH",
|
||||||
|
"TWR": "High risk freight train",
|
||||||
|
"SKR": "Train with exceeded gauge"
|
||||||
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"sceneries": "SCENERIES",
|
"sceneries": "SCENERIES",
|
||||||
"trains": "TRAINS",
|
"trains": "TRAINS",
|
||||||
@@ -8,9 +14,21 @@
|
|||||||
"error": "An error occured while loading data!",
|
"error": "An error occured while loading data!",
|
||||||
"no-result": "No results for current search!",
|
"no-result": "No results for current search!",
|
||||||
"migration-warning": "Stacjownik services will be unavailable 2/06/2022 between 1-3am (CEST time) due to the migration of API hostings!",
|
"migration-warning": "Stacjownik services will be unavailable 2/06/2022 between 1-3am (CEST time) due to the migration of API hostings!",
|
||||||
"migration-confirm": "Roger that!"
|
"migration-confirm": "Roger that!",
|
||||||
|
"offline": "App is in the offline mode!"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"discord": "Stacjownik Discord server"
|
||||||
|
},
|
||||||
|
"update": {
|
||||||
|
"title": "New version of the app is available!",
|
||||||
|
"paragraph1": "Enjoy the application and may the green signal be with you!",
|
||||||
|
"release-link": "Click here to browse version changelog (GitHub)",
|
||||||
|
"confirm-button": "UPDATE NOW",
|
||||||
|
"later-button": "LATER"
|
||||||
},
|
},
|
||||||
"data-status": {
|
"data-status": {
|
||||||
|
"S1-offline": "<b>S1 signal</b> <br> The app is working in offline mode!",
|
||||||
"S1a-connection": "<b>S1a signal</b> <br> Cannot connect with Stacjownik API service!",
|
"S1a-connection": "<b>S1a signal</b> <br> Cannot connect with Stacjownik API service!",
|
||||||
"S1a-sceneries": "<b>S1a signal</b> <br> Cannot load online stations data!",
|
"S1a-sceneries": "<b>S1a signal</b> <br> Cannot load online stations data!",
|
||||||
"S2": "<b>S2 signal</b> <br> All data loaded successfully!",
|
"S2": "<b>S2 signal</b> <br> All data loaded successfully!",
|
||||||
@@ -22,7 +40,7 @@
|
|||||||
"desc": {
|
"desc": {
|
||||||
"control-type": "Control type: ",
|
"control-type": "Control type: ",
|
||||||
"signals-type": "Signals type: ",
|
"signals-type": "Signals type: ",
|
||||||
"SBL": "This scenery has automatic line blockade system on following routes: ",
|
"SBL": "This scenery has automatic block signalling (ABS/SBL) system on following routes: ",
|
||||||
"SUP": "Requires the SUP application (level crossing remote control simulator)",
|
"SUP": "Requires the SUP application (level crossing remote control simulator)",
|
||||||
"TWB-all": "This scenery has two-way route blockade on all routes",
|
"TWB-all": "This scenery has two-way route blockade on all routes",
|
||||||
"TWB-routes": "This scenery has two-way route blockade on following routes: ",
|
"TWB-routes": "This scenery has two-way route blockade on following routes: ",
|
||||||
@@ -66,17 +84,86 @@
|
|||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"filters": "FILTERS",
|
"filters": "FILTERS",
|
||||||
"donate": "DONATE"
|
"donate": "DONATE",
|
||||||
|
|
||||||
|
"search-button": "Search",
|
||||||
|
"reset-button": "Reset",
|
||||||
|
|
||||||
|
"sort-title": "SORT BY:",
|
||||||
|
"filter-title": "FILTER BY:",
|
||||||
|
"search-title": "SEARCH:",
|
||||||
|
|
||||||
|
"search-train-no": "Train no. / #",
|
||||||
|
"search-train": "Train no.",
|
||||||
|
"search-driver": "Driver name",
|
||||||
|
"search-dispatcher": "Dispatcher name",
|
||||||
|
"search-station": "Scenery name",
|
||||||
|
"search-author": "Timetable author name",
|
||||||
|
"search-issuedFrom": "Origin scenery name",
|
||||||
|
"search-timetables-date": "Timetable date (UTC+2 / CEST)",
|
||||||
|
"search-dispatchers-date": "Service date (UTC+2 / CEST)",
|
||||||
|
"search-date": "Date (UTC+2 / CEST)",
|
||||||
|
|
||||||
|
"sort-mass": "mass",
|
||||||
|
"sort-speed": "speed",
|
||||||
|
"sort-length": "length",
|
||||||
|
"sort-routeDistance": "route distance",
|
||||||
|
"sort-timetable": "train no.",
|
||||||
|
"sort-progress": "route progress",
|
||||||
|
"sort-delay": "current delay",
|
||||||
|
"sort-id": "timetable id",
|
||||||
|
|
||||||
|
"sort-allStopsCount": "total stops",
|
||||||
|
"sort-beginDate": "date",
|
||||||
|
"sort-timetableId": "timetable ID",
|
||||||
|
"sort-timestampFrom": "date",
|
||||||
|
"sort-duration": "duration",
|
||||||
|
|
||||||
|
"filter-noComments": "NO COMMENTS",
|
||||||
|
"filter-withComments": "COMMENTS",
|
||||||
|
"filter-twr": "HIGH RISK CARGO",
|
||||||
|
"filter-skr": "EXCEEDED GAUGE",
|
||||||
|
"filter-twr-skr": "ALL TYPES",
|
||||||
|
"filter-common": "NO WARNINGS",
|
||||||
|
"filter-passenger": "PASSENGER",
|
||||||
|
"filter-freight": "FREIGHT",
|
||||||
|
"filter-other": "OTHER",
|
||||||
|
"filter-noTimetable": "NO TIMETABLE",
|
||||||
|
"filter-withTimetable": "TIMETABLE",
|
||||||
|
|
||||||
|
"filter-reset": "RESET FILTERS",
|
||||||
|
"filter-clear": "CLEAR FILTERS",
|
||||||
|
|
||||||
|
"filter-section-timetable-status": "TIMETABLE STATUS",
|
||||||
|
"filter-section-twrskr": "WARNINGS",
|
||||||
|
|
||||||
|
"filter-all": "ALL ENTRIES",
|
||||||
|
"filter-abandoned": "ABANDONED",
|
||||||
|
"filter-fulfilled": "FULFILLED",
|
||||||
|
"filter-active": "ACTIVE"
|
||||||
},
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
|
"desc": " • Left mouse click: select / unselect chosen filter <br /> • Double left click: unselect all filters but chosen from a <b class='text--primary'>group</b> <br /> • <span style='color: coral'>RESET</span>: reset all filters from a <b class='text--primary'>group</b>",
|
||||||
|
|
||||||
|
"sections": {
|
||||||
|
"reality": "SCENERY REALITY",
|
||||||
|
"package-access": "IN-GAME AVAILABILITY",
|
||||||
|
"access": "GENERAL AVAILABILITY",
|
||||||
|
"control": "CONTROLS",
|
||||||
|
"signals": "SIGNALLING",
|
||||||
|
"addons": "ADDITIONAL PROGRAMS",
|
||||||
|
"blockades": "BLOCK SIGNALLING",
|
||||||
|
"status": "ONLINE STATUS"
|
||||||
|
},
|
||||||
|
|
||||||
"endingStatus": "ENDS SOON",
|
"endingStatus": "ENDS SOON",
|
||||||
"afkStatus": "AFK",
|
"afkStatus": "AFK",
|
||||||
"noSpaceStatus": "NO SPACE",
|
"noSpaceStatus": "NO SPACE",
|
||||||
"unavailableStatus": "UNAVAILABLE",
|
"unavailableStatus": "UNAVAILABLE",
|
||||||
|
|
||||||
"title": "STATION FILTER",
|
"title": "STATION FILTERS",
|
||||||
"default": "DEFAULT",
|
"default": "IN-GAME",
|
||||||
"not-default": "OTHER",
|
"not-default": "ADDITIONAL",
|
||||||
"real": "REAL",
|
"real": "REAL",
|
||||||
"fictional": "FICTIONAL",
|
"fictional": "FICTIONAL",
|
||||||
"unavailable": "UNSUPPORTED",
|
"unavailable": "UNSUPPORTED",
|
||||||
@@ -84,12 +171,22 @@
|
|||||||
"abandoned": "ABANDONED",
|
"abandoned": "ABANDONED",
|
||||||
|
|
||||||
"SPK": "SPK",
|
"SPK": "SPK",
|
||||||
|
"SPK-R": "SPK + MANUAL",
|
||||||
|
"SPK-M": "SPK + MECH.",
|
||||||
"SCS": "SCS",
|
"SCS": "SCS",
|
||||||
|
"SCS-R": "SCS + MANUAL",
|
||||||
|
"SCS-M": "SCS + MECH.",
|
||||||
"SPE": "SPE",
|
"SPE": "SPE",
|
||||||
|
|
||||||
"manual": "MANUAL",
|
"manual": "MANUAL",
|
||||||
"mechanical": "MECHANICAL",
|
"mechanical": "MECHANICAL",
|
||||||
"SUP": "SUP",
|
|
||||||
"SBL": "SBL",
|
"SUP": "SUP (RASP-UZK)",
|
||||||
|
"noSUP": "WITHOUT SUP",
|
||||||
|
|
||||||
|
"SBL": "AUTOMATIC (SBL)",
|
||||||
|
"PBL": "SEMIAUTOMATIC (PBL)",
|
||||||
|
|
||||||
"modern": "MODERN",
|
"modern": "MODERN",
|
||||||
"semaphores": "SEMAPHORES",
|
"semaphores": "SEMAPHORES",
|
||||||
"mixed": "MIXED",
|
"mixed": "MIXED",
|
||||||
@@ -110,7 +207,7 @@
|
|||||||
"hour": "h",
|
"hour": "h",
|
||||||
"no-limit": "NO LIMIT",
|
"no-limit": "NO LIMIT",
|
||||||
"include-selected": "INCLUDE SELECTED",
|
"include-selected": "INCLUDE SELECTED",
|
||||||
"save": "↵ SAVE FILTERS",
|
"save": "REMEMBER FILTERS",
|
||||||
"reset": "RESET FILTERS",
|
"reset": "RESET FILTERS",
|
||||||
"close": "CLOSE FILTERS"
|
"close": "CLOSE FILTERS"
|
||||||
},
|
},
|
||||||
@@ -122,10 +219,13 @@
|
|||||||
"dispatcher-lvl": "Dispatcher\nlevel",
|
"dispatcher-lvl": "Dispatcher\nlevel",
|
||||||
"routes": "Routes\ndouble / single",
|
"routes": "Routes\ndouble / single",
|
||||||
"general": "General info",
|
"general": "General info",
|
||||||
"users": "Drivers online",
|
"user": "Drivers online",
|
||||||
"spawns": "Spawns online",
|
"spawn": "Spawns online",
|
||||||
"timetables": "Active timetables",
|
"timetableAll": "Active timetables",
|
||||||
"no-stations": "No stations to show here!"
|
"timetableConfirmed": "Confirmed timetables",
|
||||||
|
"timetableUnconfirmed": "Unconfirmed timetables",
|
||||||
|
"no-stations": "No stations to show here!",
|
||||||
|
"scenery-search": "Search for scenery..."
|
||||||
},
|
},
|
||||||
"trains": {
|
"trains": {
|
||||||
"no-trains": "No trains to show here!",
|
"no-trains": "No trains to show here!",
|
||||||
@@ -144,28 +244,6 @@
|
|||||||
"current-signal": "at signal",
|
"current-signal": "at signal",
|
||||||
"current-track": "on track",
|
"current-track": "on track",
|
||||||
|
|
||||||
"option-mass": "mass",
|
|
||||||
"option-speed": "speed",
|
|
||||||
"option-length": "length",
|
|
||||||
"option-distance": "distance",
|
|
||||||
"option-timetable": "train no.",
|
|
||||||
"option-progress": "route progress",
|
|
||||||
"option-delay": "current delay",
|
|
||||||
"option-comments": "comments",
|
|
||||||
|
|
||||||
"filter-comments": "comments",
|
|
||||||
"filter-twr": "TWR",
|
|
||||||
"filter-skr": "SKR",
|
|
||||||
"filter-passenger": "passenger",
|
|
||||||
"filter-freight": "freight",
|
|
||||||
"filter-other": "other",
|
|
||||||
"filter-noTimetable": "no timetable",
|
|
||||||
"filter-reset": "X RESET",
|
|
||||||
|
|
||||||
"sorter-prefix": "Sort: ",
|
|
||||||
"search-train": "Train no.",
|
|
||||||
"search-driver": "Driver name",
|
|
||||||
|
|
||||||
"delayed": "Delayed: ",
|
"delayed": "Delayed: ",
|
||||||
"preponed": "Ahead of schedule: ",
|
"preponed": "Ahead of schedule: ",
|
||||||
"on-time": "On time",
|
"on-time": "On time",
|
||||||
@@ -185,9 +263,12 @@
|
|||||||
"comment": "Exploitation comments for: ",
|
"comment": "Exploitation comments for: ",
|
||||||
"table-limit": "For performance reasons there's a limit of 10 trains shown at the same time.",
|
"table-limit": "For performance reasons there's a limit of 10 trains shown at the same time.",
|
||||||
|
|
||||||
"last-seen-now": "last seen: just now",
|
"last-seen-now": "since now",
|
||||||
"last-seen-min": "last seen: one minute ago",
|
"last-seen-min": "since one minute",
|
||||||
"last-seen-ago": "last seen: {minutes} mins ago"
|
"last-seen-ago": "since {minutes} minutes",
|
||||||
|
|
||||||
|
"scenery-offline": "Offline ride",
|
||||||
|
"timeout": "An error occured while trying to refresh SWDR timetable data!"
|
||||||
},
|
},
|
||||||
"journal": {
|
"journal": {
|
||||||
"title": "DISPATCHER HISTORY",
|
"title": "DISPATCHER HISTORY",
|
||||||
@@ -197,26 +278,6 @@
|
|||||||
"section-timetables": "TIMETABLES",
|
"section-timetables": "TIMETABLES",
|
||||||
"section-dispatchers": "DISPATCHERS",
|
"section-dispatchers": "DISPATCHERS",
|
||||||
|
|
||||||
"search": "Search",
|
|
||||||
"search-train": "Train no.",
|
|
||||||
"search-driver": "Driver name",
|
|
||||||
"search-dispatcher": "Dispatcher name",
|
|
||||||
"search-station": "Scenery name",
|
|
||||||
|
|
||||||
"sort-prefix": "Sort: ",
|
|
||||||
|
|
||||||
"option-distance": "distance",
|
|
||||||
"option-total-stops": "total stops",
|
|
||||||
"option-beginDate": "date",
|
|
||||||
"option-timetableId": "timetable ID",
|
|
||||||
"option-timestampFrom": "date",
|
|
||||||
"option-duration": "duration",
|
|
||||||
|
|
||||||
"filter-all": "ALL ENTRIES",
|
|
||||||
"filter-abandoned": "ABANDONED",
|
|
||||||
"filter-fulfilled": "FULFILLED",
|
|
||||||
"filter-active": "ACTIVE",
|
|
||||||
|
|
||||||
"no-further-data": "No further data for current parameters",
|
"no-further-data": "No further data for current parameters",
|
||||||
"loading-further-data": "Loading...",
|
"loading-further-data": "Loading...",
|
||||||
|
|
||||||
@@ -231,13 +292,51 @@
|
|||||||
"online-since": "ONLINE SINCE",
|
"online-since": "ONLINE SINCE",
|
||||||
"duty-lasted": "The duty lasted",
|
"duty-lasted": "The duty lasted",
|
||||||
"minutes": "{minutes} mins",
|
"minutes": "{minutes} mins",
|
||||||
"hours": "{hours}h {minutes} mins"
|
"hours": "{hours}h {minutes} mins",
|
||||||
|
|
||||||
|
"stock-info": "EXTRA INFO",
|
||||||
|
"stock-length": "Length",
|
||||||
|
"stock-mass": "Mass",
|
||||||
|
"stock-max-speed": "Max. speed",
|
||||||
|
|
||||||
|
"load-data": "Load further data...",
|
||||||
|
|
||||||
|
"last-seen-at": "Last seen at",
|
||||||
|
"currently-at": "Currently at",
|
||||||
|
|
||||||
|
"stats-title": "DRIVING STATISTICS OF",
|
||||||
|
|
||||||
|
"stats-timetables": "TIMETABLES",
|
||||||
|
"stats-longest-timetable": "LONGEST TIMETABLE",
|
||||||
|
"stats-avg-timetable": "AVERAGE TIMETABLE LENGTH",
|
||||||
|
"stats-distance": "DISTANCE",
|
||||||
|
"stats-stations": "STATIONS",
|
||||||
|
|
||||||
|
"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})",
|
||||||
|
|
||||||
|
"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!",
|
||||||
|
|
||||||
|
"stats-loading": "Fetching statistics...",
|
||||||
|
"stats-error": "Oops! An unexpected error occurred while trying to fetch statistics! :/"
|
||||||
},
|
},
|
||||||
"scenery": {
|
"scenery": {
|
||||||
"users": "PLAYERS ONLINE",
|
"users": "PLAYERS ONLINE",
|
||||||
"spawns": "OPEN SPAWNS",
|
"spawns": "OPEN SPAWNS",
|
||||||
"timetables": "ACTIVE TIMETABLES",
|
"timetables": "ACTIVE TIMETABLES",
|
||||||
"no-timetables": "No active timetables!",
|
"no-timetables": "No active timetables!",
|
||||||
|
"offline": "Scenery is offline",
|
||||||
"no-users": "NO ACTIVE PLAYERS",
|
"no-users": "NO ACTIVE PLAYERS",
|
||||||
"no-spawns": "NO OPEN SPAWNS",
|
"no-spawns": "NO OPEN SPAWNS",
|
||||||
"no-scenery": "Oops! This scenery doesn't exist!",
|
"no-scenery": "Oops! This scenery doesn't exist!",
|
||||||
@@ -245,39 +344,60 @@
|
|||||||
"history-btn": "View the dispatcher history",
|
"history-btn": "View the dispatcher history",
|
||||||
"info-btn": "Return to the scenery view",
|
"info-btn": "Return to the scenery view",
|
||||||
"authors-title": "Scenery author | Scenery authors",
|
"authors-title": "Scenery author | Scenery authors",
|
||||||
|
"abbrev": "Station symbol:",
|
||||||
"lines-title": "Real lines",
|
"lines-title": "Real lines",
|
||||||
"project-title": "Project name",
|
"project-title": "Project name",
|
||||||
"one-way-routes": "One way routes",
|
"one-way-routes": "One way routes",
|
||||||
"two-way-routes": "Two way routes",
|
"two-way-routes": "Two way routes",
|
||||||
|
|
||||||
"option-active-timetables": "Active timetables",
|
"option-active-timetables": "Active timetables",
|
||||||
"option-timetables-history": "Scenery timetables history",
|
"option-timetables-history": "Timetables history",
|
||||||
"option-dispatchers-history": "Scenery dispatchers history",
|
"option-dispatchers-history": "Dispatchers history",
|
||||||
|
|
||||||
"timetable-author-title": "Issued by",
|
"timetable-author-title": "Issued by",
|
||||||
"timetable-author-unknown": "Author unknown",
|
"timetable-author-unknown": "Author unknown",
|
||||||
|
|
||||||
|
"timetables-history-id": "ID",
|
||||||
|
"timetables-history-number": "Number",
|
||||||
|
"timetables-history-route": "Route",
|
||||||
|
"timetables-history-driver": "Driver",
|
||||||
|
"timetables-history-author": "TT author",
|
||||||
|
"timetables-history-date": "Date",
|
||||||
|
|
||||||
"req-level": "all dispatcher levels | dispatcher level {lvl} required | dispatcher level {lvl} required",
|
"req-level": "all dispatcher levels | dispatcher level {lvl} required | dispatcher level {lvl} required",
|
||||||
"history-list-empty": "No recorded scenery history!"
|
"history-list-empty": "No recorded scenery history!",
|
||||||
|
|
||||||
|
"forum-topic": "Official {name} forum topic",
|
||||||
|
|
||||||
|
"pragotron-link": "Timetable pallet board (beta)",
|
||||||
|
"tablice-link": "Timetable summary board (by Thundo)"
|
||||||
},
|
},
|
||||||
"availability": {
|
"availability": {
|
||||||
"title": "Availability",
|
"title": "Availability",
|
||||||
"default": "in-game",
|
"default": "in-game",
|
||||||
"nonDefault": "downloadable",
|
"nonDefault": "additional",
|
||||||
"unavailable": "unavailable",
|
"unavailable": "unavailable",
|
||||||
"nonPublic": "private",
|
"nonPublic": "private",
|
||||||
"abandoned": "abandoned"
|
"abandoned": "abandoned"
|
||||||
},
|
},
|
||||||
"timetables": {
|
"timetables": {
|
||||||
"timetable-only": "Switch to timetable-only view",
|
"timetable-only": "Switch to timetable-only view",
|
||||||
"online": "At station",
|
"end": "Timetable terminates here",
|
||||||
"departed": "Dispatched to:",
|
"terminated": "Timetable terminated",
|
||||||
"departed-away": "Departed to:",
|
|
||||||
"arriving": "Arriving from:",
|
|
||||||
"stopped": "Stopped",
|
|
||||||
"terminated": "Terminated",
|
|
||||||
"begins": "BEGINS HERE",
|
"begins": "BEGINS HERE",
|
||||||
"terminates": "TERMINATES\nHERE"
|
"terminates": "TERMINATES\nHERE",
|
||||||
|
|
||||||
|
"from": "FROM",
|
||||||
|
"to": "TO",
|
||||||
|
|
||||||
|
"desc-arriving": "The train is not here yet. It's going to come from: {prevStationName} (szlak {prevDepartureLine})",
|
||||||
|
"desc-online": "The train is at the station. It's going to leave to: {nextStationName} (szlak {nextArrivalLine})",
|
||||||
|
"desc-stopped": "The train is at the station and is stopped. It's going to leave towards: {nextStationName} (szlak {nextArrivalLine})",
|
||||||
|
"desc-next-arrival": "Leaves towards: {nextStationName} (szlak {nextArrivalLine})",
|
||||||
|
"desc-departed": "The train is at the station and it's been departed. Leaves towards: {nextStationName} (szlak {nextArrivalLine})",
|
||||||
|
"desc-departed-away": "The train has been departed to: {nextStationName} (szlak {nextArrivalLine})",
|
||||||
|
"desc-end": "The train terminates here",
|
||||||
|
"desc-terminated": "The train has been terminated"
|
||||||
},
|
},
|
||||||
"history": {
|
"history": {
|
||||||
"title": "TIMETABLE JOURNAL",
|
"title": "TIMETABLE JOURNAL",
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
{
|
{
|
||||||
|
"general": {
|
||||||
|
"and": " oraz ",
|
||||||
|
"refresh": "ODŚWIEŻ",
|
||||||
|
"TWR": "Towar niebezpieczny wysokiego ryzyka",
|
||||||
|
"SKR": "Przekroczona skrajnia"
|
||||||
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"sceneries": "SCENERIE",
|
"sceneries": "SCENERIE",
|
||||||
"trains": "POCIĄGI",
|
"trains": "POCIĄGI",
|
||||||
@@ -8,10 +14,21 @@
|
|||||||
"error": "Wystąpił problem z załadowaniem danych!",
|
"error": "Wystąpił problem z załadowaniem danych!",
|
||||||
"no-result": "Brak wyników o podanych kryteriach!",
|
"no-result": "Brak wyników o podanych kryteriach!",
|
||||||
"migration-warning": "Usługi Stacjownika będą niedostępne w godzinach 1:00-3:00 2 czerwca 2022r. z powodu migracji hostingów API!",
|
"migration-warning": "Usługi Stacjownika będą niedostępne w godzinach 1:00-3:00 2 czerwca 2022r. z powodu migracji hostingów API!",
|
||||||
"migration-confirm": "Przyjąłem!"
|
"migration-confirm": "Przyjąłem!",
|
||||||
|
"offline": "Aplikacja w trybie offline!"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"discord": "Serwer Discord Stacjownika"
|
||||||
|
},
|
||||||
|
"update": {
|
||||||
|
"title": "Nowa wersja Stacjownika jest dostępna!",
|
||||||
|
"paragraph1": "Miłego korzystania z aplikacji i niech S2 będzie z wami!",
|
||||||
|
"release-link": "Kliknij, aby przejrzeć listę zmian (GitHub)",
|
||||||
|
"confirm-button": "ZAKTUALIZUJ",
|
||||||
|
"later-button": "PÓŹNIEJ"
|
||||||
},
|
},
|
||||||
|
|
||||||
"data-status": {
|
"data-status": {
|
||||||
|
"S1-offline": "<b>Sygnał S1</b> <br> Aplikacja działa w trybie offline!",
|
||||||
"S1a-connection": "<b>Sygnał S1a</b> <br> Błąd podczas próby połączenia się z API Stacjownika!",
|
"S1a-connection": "<b>Sygnał S1a</b> <br> Błąd podczas próby połączenia się z API Stacjownika!",
|
||||||
"S1a-sceneries": "<b>Sygnał S1a</b> <br> Błąd podczas pobierania danych o sceneriach online!",
|
"S1a-sceneries": "<b>Sygnał S1a</b> <br> Błąd podczas pobierania danych o sceneriach online!",
|
||||||
"S2": "<b>Sygnał S2</b> <br> Pomyślnie załadowano dane!",
|
"S2": "<b>Sygnał S2</b> <br> Pomyślnie załadowano dane!",
|
||||||
@@ -67,9 +84,79 @@
|
|||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"filters": "FILTRY",
|
"filters": "FILTRY",
|
||||||
"donate": "WESPRZYJ"
|
"donate": "WESPRZYJ",
|
||||||
|
|
||||||
|
"search-button": "Szukaj",
|
||||||
|
"reset-button": "Zresetuj",
|
||||||
|
|
||||||
|
"sort-title": "SORTUJ WG:",
|
||||||
|
"filter-title": "FILTRUJ WG:",
|
||||||
|
"search-title": "SZUKAJ:",
|
||||||
|
|
||||||
|
"search-train-no": "Nr pociągu",
|
||||||
|
"search-train": "Nr pociągu / #",
|
||||||
|
"search-driver": "Nick maszynisty",
|
||||||
|
"search-dispatcher": "Nick dyżurnego",
|
||||||
|
"search-station": "Nazwa scenerii",
|
||||||
|
"search-author": "Nick autora rozkładu jazdy",
|
||||||
|
"search-issuedFrom": "Sceneria początkowa",
|
||||||
|
"search-timetables-date": "Data rozkładu jazdy (UTC+2 / CEST)",
|
||||||
|
"search-dispatchers-date": "Data służby (UTC+2 / CEST)",
|
||||||
|
"search-date": "Data (UTC+2 / CEST)",
|
||||||
|
|
||||||
|
"sort-routeDistance": "kilometraż",
|
||||||
|
"sort-allStopsCount": "stacje",
|
||||||
|
"sort-beginDate": "data",
|
||||||
|
"sort-timetableId": "ID rozkładu",
|
||||||
|
"sort-timestampFrom": "data",
|
||||||
|
"sort-duration": "czas dyżuru",
|
||||||
|
"sort-id": "id rozkładu",
|
||||||
|
|
||||||
|
"sort-mass": "masa",
|
||||||
|
"sort-speed": "prędkość",
|
||||||
|
"sort-length": "długość",
|
||||||
|
"sort-timetable": "nr pociągu",
|
||||||
|
"sort-progress": "przebyta trasa",
|
||||||
|
"sort-delay": "opóźnienie",
|
||||||
|
"sort-comments": "uwagi ekspl.",
|
||||||
|
|
||||||
|
"filter-withComments": "UWAGI EKSPLOATACYJNE",
|
||||||
|
"filter-noComments": "BEZ UWAG",
|
||||||
|
"filter-twr": "WYS. RYZYKA",
|
||||||
|
"filter-skr": "SKRAJNIA",
|
||||||
|
"filter-twr-skr": "WSZYSTKIE",
|
||||||
|
"filter-common": "ZWYKŁE",
|
||||||
|
"filter-passenger": "PASAŻERSKIE",
|
||||||
|
"filter-freight": "TOWAROWE",
|
||||||
|
"filter-other": "INNE",
|
||||||
|
"filter-noTimetable": "BEZ RJ",
|
||||||
|
"filter-withTimetable": "ROZKŁAD JAZDY",
|
||||||
|
|
||||||
|
"filter-reset": "ZRESETUJ FILTRY",
|
||||||
|
"filter-clear": "WYŁĄCZ FILTRY",
|
||||||
|
|
||||||
|
"filter-section-timetable-status": "STATUS ROZKŁADU JAZDY",
|
||||||
|
"filter-section-twrskr": "UWAGI",
|
||||||
|
|
||||||
|
"filter-all": "WSZYSTKIE",
|
||||||
|
"filter-abandoned": "PORZUCONE",
|
||||||
|
"filter-fulfilled": "WYPEŁNIONE",
|
||||||
|
"filter-active": "AKTYWNE"
|
||||||
},
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
|
"desc": " • Kliknięcie: zaznaczenie / odznaczenie filtru <br /> • Podwójne kliknięcie: odznaczenie reszty filtrów z <b class='text--primary'>grupy</b> <br /> • <span style='color: coral'>RESET</span>: zresetowanie filtrów z <b class='text--primary'>grupy</b>",
|
||||||
|
|
||||||
|
"sections": {
|
||||||
|
"reality": "FIKCYJNOŚĆ SCENERII",
|
||||||
|
"package-access": "DOSTĘPNOŚĆ W PACZCE",
|
||||||
|
"access": "DOSTĘPNOŚĆ OGÓLNA",
|
||||||
|
"control": "TYP STEROWANIA",
|
||||||
|
"signals": "TYP SYGNALIZACJI",
|
||||||
|
"addons": "DODATKOWE PROGRAMY",
|
||||||
|
"blockades": "BLOKADY LINIOWE",
|
||||||
|
"status": "STATUS ONLINE"
|
||||||
|
},
|
||||||
|
|
||||||
"endingStatus": "KOŃCZY",
|
"endingStatus": "KOŃCZY",
|
||||||
"afkStatus": "Z/W",
|
"afkStatus": "Z/W",
|
||||||
"noSpaceStatus": "BRAK MIEJSCA",
|
"noSpaceStatus": "BRAK MIEJSCA",
|
||||||
@@ -85,18 +172,29 @@
|
|||||||
"abandoned": "WYCOFANA",
|
"abandoned": "WYCOFANA",
|
||||||
|
|
||||||
"SPK": "SPK",
|
"SPK": "SPK",
|
||||||
|
"SPK-R": "SPK + RĘCZNE",
|
||||||
|
"SPK-M": "SPK + MECH.",
|
||||||
"SCS": "SCS",
|
"SCS": "SCS",
|
||||||
|
"SCS-R": "SCS + RĘCZNE",
|
||||||
|
"SCS-M": "SCS + MECH.",
|
||||||
"SPE": "SPE",
|
"SPE": "SPE",
|
||||||
"manual": "RĘCZNE",
|
"manual": "RĘCZNE",
|
||||||
"SUP": "SUP",
|
|
||||||
"SBL": "SBL",
|
"SUP": "SUP (RASP-UZK)",
|
||||||
|
"noSUP": "BEZ SUP",
|
||||||
|
|
||||||
|
"SBL": "SAMOCZYNNA",
|
||||||
|
"PBL": "PÓŁSAMOCZYNNA",
|
||||||
|
|
||||||
"mechanical": "MECHANICZNE",
|
"mechanical": "MECHANICZNE",
|
||||||
"modern": "WSPÓŁCZESNA",
|
"modern": "WSPÓŁCZESNA",
|
||||||
"semaphores": "KSZTAŁTOWA",
|
"semaphores": "KSZTAŁTOWA",
|
||||||
"mixed": "MIESZANA",
|
"mixed": "MIESZANA",
|
||||||
"historical": "HISTORYCZNA",
|
"historical": "HISTORYCZNA",
|
||||||
|
|
||||||
"free": "WOLNA",
|
"free": "WOLNA",
|
||||||
"occupied": "ZAJĘTA",
|
"occupied": "ZAJĘTA",
|
||||||
|
|
||||||
"sliders": {
|
"sliders": {
|
||||||
"min-lvl": "MIN. WYMAGANY POZIOM DYŻURNEGO",
|
"min-lvl": "MIN. WYMAGANY POZIOM DYŻURNEGO",
|
||||||
"max-lvl": "MAKS. WYMAGANY POZIOM DYŻURNEGO",
|
"max-lvl": "MAKS. WYMAGANY POZIOM DYŻURNEGO",
|
||||||
@@ -105,28 +203,33 @@
|
|||||||
"routes-2t-cat": "SZLAKI DWUTOROWE ZELEKTR. (MINIMUM)",
|
"routes-2t-cat": "SZLAKI DWUTOROWE ZELEKTR. (MINIMUM)",
|
||||||
"routes-2t-other": "SZLAKI DWUTOROWE NIEZELEKTR. (MINIMUM)"
|
"routes-2t-other": "SZLAKI DWUTOROWE NIEZELEKTR. (MINIMUM)"
|
||||||
},
|
},
|
||||||
|
|
||||||
"authors-search": "Szukaj autora (uwzględnia inne filtry)",
|
"authors-search": "Szukaj autora (uwzględnia inne filtry)",
|
||||||
"minimum-hours-title": "POKAŻ TYLKO SCENERIE DOSTĘPNE MINIMUM DO:",
|
"minimum-hours-title": "POKAŻ TYLKO SCENERIE DOSTĘPNE MINIMUM DO:",
|
||||||
"now": "TERAZ",
|
"now": "TERAZ",
|
||||||
"hour": " godz.",
|
"hour": " godz.",
|
||||||
"no-limit": "BEZ LIMITU",
|
"no-limit": "BEZ LIMITU",
|
||||||
"include-selected": "POKAŻ ZAZNACZONE",
|
"include-selected": "POKAŻ ZAZNACZONE",
|
||||||
"save": "↵ ZAPISZ FILTRY",
|
"save": "ZAPAMIĘTAJ FILTRY",
|
||||||
"reset": "RESETUJ FILTRY",
|
"reset": "RESETUJ FILTRY",
|
||||||
"close": "ZAMKNIJ FILTRY"
|
"close": "ZAMKNIJ FILTRY"
|
||||||
},
|
},
|
||||||
"sceneries": {
|
"sceneries": {
|
||||||
"station": "Stacja",
|
"station": "Stacja",
|
||||||
|
"abbr": "Skrót\nposterunku",
|
||||||
"min-lvl": "Min. poziom\ndyżurnego",
|
"min-lvl": "Min. poziom\ndyżurnego",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"dispatcher": "Dyżurny",
|
"dispatcher": "Dyżurny",
|
||||||
"dispatcher-lvl": "Poziom\ndyżurnego",
|
"dispatcher-lvl": "Poziom\ndyżurnego",
|
||||||
"routes": "Szlaki\n2tor / 1tor",
|
"routes": "Szlaki\n2tor / 1tor",
|
||||||
"general": "Informacje\nogólne",
|
"general": "Informacje\nogólne",
|
||||||
"users": "Maszyniści online",
|
"user": "Maszyniści online",
|
||||||
"spawns": "Otwarte spawny",
|
"spawn": "Otwarte spawny",
|
||||||
"timetables": "Aktywne rozkłady jazdy",
|
"timetableAll": "Aktywne rozkłady jazdy",
|
||||||
"no-stations": "Brak stacji do wyświetlenia!"
|
"timetableConfirmed": "Zatwierdzone rozkłady jazdy",
|
||||||
|
"timetableUnconfirmed": "Niezatwierdzone rozkłady jazdy",
|
||||||
|
"no-stations": "Brak stacji do wyświetlenia!",
|
||||||
|
"scenery-search": "Wyszukaj scenerię..."
|
||||||
},
|
},
|
||||||
"trains": {
|
"trains": {
|
||||||
"no-trains": "Brak pociągów do wyświetlenia!",
|
"no-trains": "Brak pociągów do wyświetlenia!",
|
||||||
@@ -145,28 +248,6 @@
|
|||||||
"current-signal": "przy semaforze",
|
"current-signal": "przy semaforze",
|
||||||
"current-track": "na szlaku",
|
"current-track": "na szlaku",
|
||||||
|
|
||||||
"option-mass": "masa",
|
|
||||||
"option-speed": "prędkość",
|
|
||||||
"option-length": "długość",
|
|
||||||
"option-distance": "kilometraż",
|
|
||||||
"option-timetable": "nr pociągu",
|
|
||||||
"option-progress": "przebyta trasa",
|
|
||||||
"option-delay": "opóźnienie",
|
|
||||||
"option-comments": "uwagi ekspl.",
|
|
||||||
|
|
||||||
"filter-comments": "uwagi ekspl.",
|
|
||||||
"filter-twr": "TWR",
|
|
||||||
"filter-skr": "SKR",
|
|
||||||
"filter-passenger": "pasażerskie",
|
|
||||||
"filter-freight": "towarowe",
|
|
||||||
"filter-other": "inne",
|
|
||||||
"filter-noTimetable": "bez RJ",
|
|
||||||
"filter-reset": "X RESETUJ",
|
|
||||||
|
|
||||||
"sorter-prefix": "Sortuj: ",
|
|
||||||
"search-train": "Numer pociągu",
|
|
||||||
"search-driver": "Nick maszynisty",
|
|
||||||
|
|
||||||
"delayed": "Opóźniony: ",
|
"delayed": "Opóźniony: ",
|
||||||
"preponed": "Przed czasem: ",
|
"preponed": "Przed czasem: ",
|
||||||
"on-time": "Planowo",
|
"on-time": "Planowo",
|
||||||
@@ -186,9 +267,13 @@
|
|||||||
"comment": "Uwagi eksploatacyjne dla: ",
|
"comment": "Uwagi eksploatacyjne dla: ",
|
||||||
"table-limit": "Dla płynności działania strony pokazanych jest tylko 10 pociągów zgodnie z wybranymi filtrami.",
|
"table-limit": "Dla płynności działania strony pokazanych jest tylko 10 pociągów zgodnie z wybranymi filtrami.",
|
||||||
|
|
||||||
"last-seen-now": "ostatnio widziany: przed chwilą",
|
"last-seen-now": "od niedawna",
|
||||||
"last-seen-min": "ostatnio widziany: minutę temu",
|
"last-seen-min": "od minuty",
|
||||||
"last-seen-ago": "ostatnio widziany: {minutes} min. temu"
|
"last-seen-ago": "od {minutes} minut",
|
||||||
|
|
||||||
|
"scenery-offline": "Przejazd offline",
|
||||||
|
|
||||||
|
"timeout": "Wystąpił problem z aktualizacją rozkładów jazdy z SWDR"
|
||||||
},
|
},
|
||||||
"journal": {
|
"journal": {
|
||||||
"title": "HISTORIA DYŻURÓW",
|
"title": "HISTORIA DYŻURÓW",
|
||||||
@@ -198,26 +283,6 @@
|
|||||||
"section-timetables": "ROZKŁADY JAZDY",
|
"section-timetables": "ROZKŁADY JAZDY",
|
||||||
"section-dispatchers": "DYŻURNI",
|
"section-dispatchers": "DYŻURNI",
|
||||||
|
|
||||||
"search": "Szukaj",
|
|
||||||
"search-train": "Numer pociągu",
|
|
||||||
"search-driver": "Nick maszynisty",
|
|
||||||
"search-dispatcher": "Nick dyżurnego",
|
|
||||||
"search-station": "Nazwa scenerii",
|
|
||||||
|
|
||||||
"sort-prefix": "Sortuj: ",
|
|
||||||
|
|
||||||
"option-distance": "kilometraż",
|
|
||||||
"option-total-stops": "stacje",
|
|
||||||
"option-beginDate": "data",
|
|
||||||
"option-timetableId": "ID rozkładu",
|
|
||||||
"option-timestampFrom": "data",
|
|
||||||
"option-duration": "czas dyżuru",
|
|
||||||
|
|
||||||
"filter-all": "WSZYSTKIE",
|
|
||||||
"filter-abandoned": "PORZUCONE",
|
|
||||||
"filter-fulfilled": "WYPEŁNIONE",
|
|
||||||
"filter-active": "AKTYWNE",
|
|
||||||
|
|
||||||
"no-further-data": "Brak dalszych wyników dla podanych parametrów",
|
"no-further-data": "Brak dalszych wyników dla podanych parametrów",
|
||||||
"loading-further-data": "Ładowanie...",
|
"loading-further-data": "Ładowanie...",
|
||||||
|
|
||||||
@@ -232,34 +297,84 @@
|
|||||||
"timetable-day": "Rozkład z dnia",
|
"timetable-day": "Rozkład z dnia",
|
||||||
"timetable-active": "AKTYWNY",
|
"timetable-active": "AKTYWNY",
|
||||||
"timetable-fulfilled": "WYPEŁNIONY",
|
"timetable-fulfilled": "WYPEŁNIONY",
|
||||||
"timetable-abandoned": "PORZUCONY"
|
"timetable-abandoned": "PORZUCONY",
|
||||||
|
|
||||||
|
"stock-info": "DODATKOWE INFORMACJE",
|
||||||
|
"stock-length": "Długość",
|
||||||
|
"stock-mass": "Masa",
|
||||||
|
"stock-max-speed": "Prędkość maks.",
|
||||||
|
|
||||||
|
"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",
|
||||||
|
|
||||||
|
"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})",
|
||||||
|
|
||||||
|
"timetable-count": "rozkład jazdy | rozkładów jazdy",
|
||||||
|
|
||||||
|
"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!",
|
||||||
|
|
||||||
|
"stats-loading": "Pobieranie statystyk...",
|
||||||
|
"stats-error": "Ups! Wystąpił błąd podczas próby pobrania statystyk! :/"
|
||||||
},
|
},
|
||||||
"scenery": {
|
"scenery": {
|
||||||
"users": "GRACZE ONLINE",
|
"users": "GRACZE ONLINE",
|
||||||
"spawns": "OTWARTE SPAWNY",
|
"spawns": "OTWARTE SPAWNY",
|
||||||
"timetables": "AKTYWNE ROZKŁADY JAZDY",
|
"timetables": "AKTYWNE ROZKŁADY JAZDY",
|
||||||
"no-timetables": "Brak aktywnych rozkładów!",
|
"no-timetables": "Brak aktywnych rozkładów!",
|
||||||
|
"offline": "Sceneria jest offline",
|
||||||
"no-users": "BRAK AKTYWNYCH GRACZY",
|
"no-users": "BRAK AKTYWNYCH GRACZY",
|
||||||
"no-spawns": "BRAK OTWARTYCH SPAWNÓW",
|
"no-spawns": "BRAK OTWARTYCH SPAWNÓW",
|
||||||
"no-scenery": "Ups! Ta sceneria nie istnieje!",
|
"no-scenery": "Ups! Ta sceneria nie istnieje!",
|
||||||
"return-btn": "Wróć na stronę główną",
|
"return-btn": "Wróć na stronę główną",
|
||||||
"history-btn": "Przejdź do widoku historii dyżurnych ruchu",
|
"history-btn": "Przejdź do widoku historii dyżurnych ruchu",
|
||||||
"info-btn": "Wróc do widoku scenerii",
|
"info-btn": "Wróć do widoku scenerii",
|
||||||
"authors-title": "Autor scenerii | Autorzy scenerii",
|
"authors-title": "Autor scenerii | Autorzy scenerii",
|
||||||
|
"abbrev": "Skrót posterunku:",
|
||||||
"lines-title": "Rzeczywiste linie",
|
"lines-title": "Rzeczywiste linie",
|
||||||
"project-title": "Projekt",
|
"project-title": "Projekt",
|
||||||
"one-way-routes": "Szlaki jednotorowe",
|
"one-way-routes": "Szlaki jednotorowe",
|
||||||
"two-way-routes": "Szlaki dwutorowe",
|
"two-way-routes": "Szlaki dwutorowe",
|
||||||
|
|
||||||
"option-active-timetables": "Aktywne rozkłady jazdy",
|
"option-active-timetables": "Aktywne rozkłady jazdy",
|
||||||
"option-timetables-history": "Historia rozkładów scenerii",
|
"option-timetables-history": "Historia rozkładów",
|
||||||
"option-dispatchers-history": "Historia dyżurów scenerii",
|
"option-dispatchers-history": "Historia dyżurów",
|
||||||
|
|
||||||
"timetable-author-title": "Wydany przez",
|
"timetable-author-title": "Wydany przez",
|
||||||
"timetable-author-unknown": "Autor nieznany",
|
"timetable-author-unknown": "Autor nieznany",
|
||||||
|
|
||||||
|
"timetables-history-id": "ID",
|
||||||
|
"timetables-history-number": "Numer",
|
||||||
|
"timetables-history-route": "Trasa",
|
||||||
|
"timetables-history-driver": "Maszynista",
|
||||||
|
"timetables-history-author": "Autor RJ",
|
||||||
|
"timetables-history-date": "Data",
|
||||||
|
|
||||||
"req-level": "ogólnodostępna | minimum {lvl} poziom dyżurnego | minimum {lvl} poziom dyżurnego",
|
"req-level": "ogólnodostępna | minimum {lvl} poziom dyżurnego | minimum {lvl} poziom dyżurnego",
|
||||||
"history-list-empty": "Brak historii dla tej scenerii!"
|
"history-list-empty": "Brak historii dla tej scenerii!",
|
||||||
|
|
||||||
|
"forum-topic": "Oficjalny wątek scenerii {name}",
|
||||||
|
|
||||||
|
"pragotron-link": "Paletowa tablica informacyjna (beta)",
|
||||||
|
"tablice-link": "Tablica informacyjna zbiorcza (autorstwa Thundo)"
|
||||||
},
|
},
|
||||||
"availability": {
|
"availability": {
|
||||||
"title": "Dostępność",
|
"title": "Dostępność",
|
||||||
@@ -271,14 +386,22 @@
|
|||||||
},
|
},
|
||||||
"timetables": {
|
"timetables": {
|
||||||
"timetable-only": "Wyodrębnij rozkłady jazdy",
|
"timetable-only": "Wyodrębnij rozkłady jazdy",
|
||||||
"online": "Na stacji",
|
"end": "Koniec rozkładu jazdy",
|
||||||
"departed": "Odprawiony do:",
|
"terminated": "Rozkład jazdy zakończony",
|
||||||
"departed-away": "Odjechał do:",
|
|
||||||
"arriving": "W drodze z:",
|
|
||||||
"stopped": "Postój",
|
|
||||||
"terminated": "Skończył bieg",
|
|
||||||
"begins": "ROZPOCZYNA\nBIEG",
|
"begins": "ROZPOCZYNA\nBIEG",
|
||||||
"terminates": "KOŃCZY BIEG"
|
"terminates": "KOŃCZY BIEG",
|
||||||
|
|
||||||
|
"from": "Z",
|
||||||
|
"to": "DO",
|
||||||
|
|
||||||
|
"desc-arriving": "Pociągu nie ma jeszcze na tej scenerii. Przyjedzie z: {prevStationName} (szlak {prevDepartureLine})",
|
||||||
|
"desc-online": "Pociąg jest na tej scenerii. Odjedzie do: {nextStationName} (szlak {nextArrivalLine})",
|
||||||
|
"desc-stopped": "Pociąg jest na tej scenerii i odbywa postój. Odjedzie do: {nextStationName} (szlak {nextArrivalLine})",
|
||||||
|
"desc-next-arrival": "Odjeżdża do: {nextStationName} (szlak {nextArrivalLine})",
|
||||||
|
"desc-departed": "Pociąg jest na tej scenerii i został odprawiony. Odjeżdża do: {nextStationName} (szlak {nextArrivalLine})",
|
||||||
|
"desc-departed-away": "Pociąg został odprawiony i odjechał do: {nextStationName} (szlak {nextArrivalLine})",
|
||||||
|
"desc-end": "Pociąg kończy bieg",
|
||||||
|
"desc-terminated": "Pociąg skończył bieg"
|
||||||
},
|
},
|
||||||
"history": {
|
"history": {
|
||||||
"title": "DZIENNIK ROZKŁADÓW JAZDY"
|
"title": "DZIENNIK ROZKŁADÓW JAZDY"
|
||||||
|
|||||||
@@ -2,14 +2,16 @@ import { createApp, Directive, ref } from 'vue';
|
|||||||
import App from './App.vue';
|
import App from './App.vue';
|
||||||
import router from './router';
|
import router from './router';
|
||||||
|
|
||||||
import enLang from '@/locales/en.json';
|
import enLang from './locales/en.json';
|
||||||
import plLang from '@/locales/pl.json';
|
import plLang from './locales/pl.json';
|
||||||
|
|
||||||
import { createI18n } from 'vue-i18n';
|
import { createI18n } from 'vue-i18n';
|
||||||
import { createPinia } from 'pinia';
|
import { createPinia } from 'pinia';
|
||||||
|
|
||||||
const i18n = createI18n({
|
const i18n = createI18n({
|
||||||
locale: 'pl',
|
locale: 'pl',
|
||||||
|
legacy: false,
|
||||||
|
warnHtmlMessage: false,
|
||||||
fallbackLocale: 'pl',
|
fallbackLocale: 'pl',
|
||||||
messages: {
|
messages: {
|
||||||
en: enLang,
|
en: enLang,
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
methods: {
|
||||||
|
getIcon(name: string, ext = 'svg') {
|
||||||
|
return new URL(`../assets/icon-${name}.${ext}`, import.meta.url).href;
|
||||||
|
},
|
||||||
|
|
||||||
|
getImage(name: string) {
|
||||||
|
return new URL(`../assets/${name}`, import.meta.url).href;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
preventKeyDown: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
activated() {
|
||||||
|
window.addEventListener('keydown', this.handleKeyDown);
|
||||||
|
},
|
||||||
|
|
||||||
|
deactivated() {
|
||||||
|
window.removeEventListener('keydown', this.handleKeyDown);
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
onKeyDownFunction() {},
|
||||||
|
|
||||||
|
handleKeyDown(e: KeyboardEvent) {
|
||||||
|
if (!e.key) return;
|
||||||
|
if (e.key.toLowerCase() == 'f' && !this.preventKeyDown && !e.ctrlKey && !e.altKey) this.onKeyDownFunction();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import { useStore } from '../store/store';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
store: useStore(),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
chosenTrain() {
|
||||||
|
return this.store.trainList.find((train) => train.trainId == this.store.chosenModalTrainId);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
selectModalTrain(trainId: string) {
|
||||||
|
this.store.chosenModalTrainId = trainId;
|
||||||
|
document.body.classList.add('no-scroll');
|
||||||
|
},
|
||||||
|
|
||||||
|
closeModal() {
|
||||||
|
this.store.chosenModalTrainId = undefined;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
document.body.classList.remove('no-scroll');
|
||||||
|
}, 150);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -1,31 +1,34 @@
|
|||||||
import { defineComponent, h } from "vue";
|
import { defineComponent, h } from 'vue';
|
||||||
|
import imageMixin from './imageMixin';
|
||||||
export default defineComponent({
|
|
||||||
data() {
|
export default defineComponent({
|
||||||
return {
|
mixins: [imageMixin],
|
||||||
icons: {
|
|
||||||
arrow: require('@/assets/icon-arrow-asc.svg'),
|
data() {
|
||||||
},
|
return {
|
||||||
|
icons: {
|
||||||
showReturnButton: false
|
arrow: this.getIcon('arrow-asc'),
|
||||||
}
|
},
|
||||||
},
|
|
||||||
|
showReturnButton: false,
|
||||||
methods: {
|
};
|
||||||
scrollToTop() {
|
},
|
||||||
window.scrollTo({ top: 0 });
|
|
||||||
},
|
methods: {
|
||||||
|
scrollToTop() {
|
||||||
handleScroll() {
|
window.scrollTo({ top: 0 });
|
||||||
this.showReturnButton = window.scrollY > window.innerHeight * 0.35;
|
},
|
||||||
}
|
|
||||||
},
|
handleScroll() {
|
||||||
|
this.showReturnButton = window.scrollY > window.innerHeight * 0.35;
|
||||||
activated() {
|
},
|
||||||
window.addEventListener('scroll', this.handleScroll);
|
},
|
||||||
},
|
|
||||||
|
activated() {
|
||||||
deactivated() {
|
window.addEventListener('wheel', this.handleScroll);
|
||||||
window.removeEventListener('scroll', this.handleScroll);
|
},
|
||||||
},
|
|
||||||
})
|
deactivated() {
|
||||||
|
window.removeEventListener('wheel', this.handleScroll);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -4,11 +4,17 @@ export default defineComponent({
|
|||||||
methods: {
|
methods: {
|
||||||
calculateExpStyle(exp: number, isSupporter = false): string {
|
calculateExpStyle(exp: number, isSupporter = false): string {
|
||||||
const bgColor = exp > -1 ? (exp < 2 ? '#26B0D9' : `hsl(${-exp * 5 + 100}, 85%, 50%)`) : '#666';
|
const bgColor = exp > -1 ? (exp < 2 ? '#26B0D9' : `hsl(${-exp * 5 + 100}, 85%, 50%)`) : '#666';
|
||||||
|
|
||||||
const fontColor = exp > 15 || exp == -1 ? 'white' : 'black';
|
const fontColor = exp > 14 || exp == -1 ? 'white' : 'black';
|
||||||
const boxShadow = isSupporter ? `box-shadow: 0 0 10px 2px ${bgColor};` : '';
|
const boxShadow = isSupporter ? `box-shadow: 0 0 6px 2px ${bgColor};` : '';
|
||||||
|
|
||||||
return `background-color: ${bgColor}; color: ${fontColor}; ${boxShadow}`;
|
return `background-color: ${bgColor}; color: ${fontColor}; ${boxShadow};`;
|
||||||
|
},
|
||||||
|
|
||||||
|
calculateTextExpStyle(exp: number, isSupporter = false): string {
|
||||||
|
const textColor = exp > -1 ? (exp < 2 ? '#26B0D9' : `hsl(${-exp * 5 + 100}, 75%, 50%)`) : '#666';
|
||||||
|
|
||||||
|
return `color: ${textColor}; ${isSupporter ? 'text-shadow: 0 0 6px ' + textColor : ''};`;
|
||||||
},
|
},
|
||||||
|
|
||||||
statusClasses(occupiedTo: string) {
|
statusClasses(occupiedTo: string) {
|
||||||
@@ -41,6 +47,6 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return className;
|
return className;
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import Train from '@/scripts/interfaces/Train';
|
|
||||||
import TrainStop from '@/scripts/interfaces/TrainStop';
|
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
|
import Train from '../scripts/interfaces/Train';
|
||||||
|
import TrainStop from '../scripts/interfaces/TrainStop';
|
||||||
|
import imageMixin from './imageMixin';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
mixins: [imageMixin],
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
STATS: {
|
STATS: {
|
||||||
main: [
|
main: [
|
||||||
@@ -55,6 +58,23 @@ export default defineComponent({
|
|||||||
: this.$t('trains.last-seen-ago', { minutes: diffMins });
|
: this.$t('trains.last-seen-ago', { minutes: diffMins });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
displayTrainPosition(train: Train) {
|
||||||
|
let positionString = '';
|
||||||
|
|
||||||
|
positionString += this.$t('trains.current-scenery') + ' ';
|
||||||
|
|
||||||
|
if (train.currentStationHash) positionString += train.currentStationName + ' ';
|
||||||
|
else positionString += train['currentStationName'].replace(/.[a-zA-Z0-9]+.sc/, '') + ' (offline) ';
|
||||||
|
|
||||||
|
if (train.signal) positionString += this.$t('trains.current-signal') + ' ' + train.signal + ' ';
|
||||||
|
|
||||||
|
if (train.connectedTrack) positionString += this.$t('trains.current-track') + ' ' + train.connectedTrack + ' ';
|
||||||
|
|
||||||
|
if (train.distance) positionString += `(${this.displayDistance(train.distance)})`;
|
||||||
|
|
||||||
|
return positionString.charAt(0).toUpperCase() + positionString.slice(1);
|
||||||
|
},
|
||||||
|
|
||||||
displayStopList(stops: TrainStop[]): string | undefined {
|
displayStopList(stops: TrainStop[]): string | undefined {
|
||||||
if (!stops) return '';
|
if (!stops) return '';
|
||||||
|
|
||||||
@@ -62,11 +82,7 @@ export default defineComponent({
|
|||||||
.reduce((acc: string[], stop: TrainStop, i: number) => {
|
.reduce((acc: string[], stop: TrainStop, i: number) => {
|
||||||
if (stop.stopType.includes('ph') && !stop.stopNameRAW.includes('po.'))
|
if (stop.stopType.includes('ph') && !stop.stopNameRAW.includes('po.'))
|
||||||
acc.push(`<strong style='color:${stop.confirmed ? 'springgreen' : 'white'}'>${stop.stopName}</strong>`);
|
acc.push(`<strong style='color:${stop.confirmed ? 'springgreen' : 'white'}'>${stop.stopName}</strong>`);
|
||||||
else if (
|
else if (i > 0 && i < stops.length - 1 && !/po\.|sbl/gi.test(stop.stopNameRAW))
|
||||||
i > 0 &&
|
|
||||||
i < stops.length - 1 &&
|
|
||||||
!/po\.|sbl/gi.test(stop.stopNameRAW)
|
|
||||||
)
|
|
||||||
acc.push(`<span style='color:${stop.confirmed ? 'springgreen' : 'lightgray'}'>${stop.stopName}</span>`);
|
acc.push(`<span style='color:${stop.confirmed ? 'springgreen' : 'lightgray'}'>${stop.stopName}</span>`);
|
||||||
return acc;
|
return acc;
|
||||||
}, [])
|
}, [])
|
||||||
@@ -121,7 +137,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
onImageError(e: Event) {
|
onImageError(e: Event) {
|
||||||
const imageEl = e.target as HTMLImageElement;
|
const imageEl = e.target as HTMLImageElement;
|
||||||
imageEl.src = require('@/assets/unknown.png');
|
imageEl.src = this.getImage('unknown.png');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { useRegisterSW } from 'virtual:pwa-register/vue';
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const { needRefresh, updateServiceWorker, offlineReady } = useRegisterSW({
|
||||||
|
immediate: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
needRefresh,
|
||||||
|
updateServiceWorker,
|
||||||
|
offlineReady,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,50 +1,43 @@
|
|||||||
import JournalDispatchersVue from '@/components/JournalView/JournalDispatchers.vue';
|
|
||||||
import JournalTimetablesVue from '@/components/JournalView/JournalTimetables.vue';
|
|
||||||
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
|
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
|
||||||
|
import JournalDispatchersVue from '../views/JournalDispatchers.vue';
|
||||||
|
import JournalTimetablesVue from '../views/JournalTimetables.vue';
|
||||||
|
|
||||||
const routes: Array<RouteRecordRaw> = [
|
const routes: Array<RouteRecordRaw> = [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
name: 'StationsView',
|
name: 'StationsView',
|
||||||
component: () => import('@/views/StationsView.vue'),
|
component: () => import('../views/StationsView.vue'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/trains',
|
path: '/trains',
|
||||||
name: 'TrainsView',
|
name: 'TrainsView',
|
||||||
component: () => import('@/views/TrainsView.vue'),
|
component: () => import('../views/TrainsView.vue'),
|
||||||
props: (route) => ({ train: route.query.train, driver: route.query.driver }),
|
props: (route) => ({ train: route.query.train, driver: route.query.driver, trainId: route.query.trainId }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/scenery',
|
path: '/scenery',
|
||||||
name: 'SceneryView',
|
name: 'SceneryView',
|
||||||
component: () => import('@/views/SceneryView.vue'),
|
component: () => import('../views/SceneryView.vue'),
|
||||||
props: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/journal',
|
path: '/journal',
|
||||||
name: 'JournalView',
|
redirect: '/journal/timetables'
|
||||||
component: () => import('@/views/JournalView.vue'),
|
},
|
||||||
children: [
|
{
|
||||||
{
|
path: '/journal/timetables',
|
||||||
path: '',
|
name: 'JournalTimetables',
|
||||||
redirect: '/journal/timetables',
|
component: JournalTimetablesVue,
|
||||||
component: JournalTimetablesVue,
|
props: (route) => ({
|
||||||
},
|
trainNo: route.query.trainNo,
|
||||||
{
|
driverName: route.query.driverName,
|
||||||
path: 'dispatchers',
|
timetableId: route.query.timetableId,
|
||||||
component: JournalDispatchersVue,
|
}),
|
||||||
props: (route) => ({ sceneryName: route.query.sceneryName, dispatcherName: route.query.dispatcherName }),
|
},
|
||||||
},
|
{
|
||||||
{
|
path: '/journal/dispatchers',
|
||||||
path: 'timetables',
|
name: 'JournalDispatchers',
|
||||||
component: JournalTimetablesVue,
|
component: JournalDispatchersVue,
|
||||||
props: (route) => ({
|
props: (route) => ({ sceneryName: route.query.sceneryName, dispatcherName: route.query.dispatcherName }),
|
||||||
trainNo: route.query.trainNo,
|
|
||||||
driverName: route.query.driverName,
|
|
||||||
timetableId: route.query.timetableId,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/:catchAll(.*)',
|
path: '/:catchAll(.*)',
|
||||||
@@ -56,7 +49,7 @@ const router = createRouter({
|
|||||||
scrollBehavior(to, from) {
|
scrollBehavior(to, from) {
|
||||||
if (to.name == 'SceneryView' && from.name) return { el: `.app_main` };
|
if (to.name == 'SceneryView' && from.name) return { el: `.app_main` };
|
||||||
|
|
||||||
if (from.name == 'SceneryView' && to.name == 'StationsView') return { el: `.last-selected`, top: 20 };
|
// if (from.name == 'SceneryView' && to.name == 'StationsView') return { el: `.last-selected`, top: 20 };
|
||||||
},
|
},
|
||||||
history: createWebHistory(),
|
history: createWebHistory(),
|
||||||
routes,
|
routes,
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import Filter from "../../interfaces/Filter";
|
||||||
|
|
||||||
|
export const filterInitStates: Filter = {
|
||||||
|
default: false,
|
||||||
|
notDefault: false,
|
||||||
|
real: false,
|
||||||
|
fictional: false,
|
||||||
|
SPK: false,
|
||||||
|
SCS: false,
|
||||||
|
SPE: false,
|
||||||
|
SUP: false,
|
||||||
|
noSUP: false,
|
||||||
|
ręczne: false,
|
||||||
|
'ręczne+SPK': false,
|
||||||
|
'ręczne+SCS': false,
|
||||||
|
mechaniczne: false,
|
||||||
|
'mechaniczne+SPK': false,
|
||||||
|
'mechaniczne+SCS': false,
|
||||||
|
współczesna: false,
|
||||||
|
kształtowa: false,
|
||||||
|
historyczna: false,
|
||||||
|
mieszana: false,
|
||||||
|
SBL: false,
|
||||||
|
PBL: false,
|
||||||
|
minLevel: 0,
|
||||||
|
maxLevel: 20,
|
||||||
|
minOneWayCatenary: 0,
|
||||||
|
minOneWay: 0,
|
||||||
|
minTwoWayCatenary: 0,
|
||||||
|
minTwoWay: 0,
|
||||||
|
'include-selected': false,
|
||||||
|
'no-1track': false,
|
||||||
|
'no-2track': false,
|
||||||
|
free: true,
|
||||||
|
occupied: false,
|
||||||
|
ending: false,
|
||||||
|
nonPublic: false,
|
||||||
|
unavailable: true,
|
||||||
|
abandoned: true,
|
||||||
|
afkStatus: false,
|
||||||
|
endingStatus: false,
|
||||||
|
noSpaceStatus: false,
|
||||||
|
unavailableStatus: false,
|
||||||
|
unsignedStatus: false,
|
||||||
|
|
||||||
|
authors: '',
|
||||||
|
|
||||||
|
onlineFromHours: 0,
|
||||||
|
};
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export const headIds = ['station', 'min-lvl', 'status', 'dispatcher', 'dispatcher-lvl', 'routes', 'general'] as const;
|
||||||
|
|
||||||
|
export const headIconsIds = ['user', 'spawn', 'timetableAll', 'timetableUnconfirmed', 'timetableConfirmed'] as const;
|
||||||
|
|
||||||
|
export type HeadIdsTypes = typeof headIds[number] | typeof headIconsIds[number];
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
export const enum DataStatus {
|
export enum DataStatus {
|
||||||
Initialized = -1,
|
Initialized = -1,
|
||||||
Loading = 0,
|
Loading = 0,
|
||||||
Error = 1,
|
Error = 1,
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
export const enum JournalFilterType {
|
export const enum JournalFilterType {
|
||||||
active = "active",
|
ACTIVE = 'active',
|
||||||
fulfilled = "fulfilled",
|
FULFILLED = 'fulfilled',
|
||||||
abandoned = "abandoned",
|
ABANDONED = 'abandoned',
|
||||||
all = "all"
|
ALL = 'all',
|
||||||
}
|
TWR = 'twr',
|
||||||
|
SKR = 'skr',
|
||||||
|
TWR_SKR = 'twr-skr',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum JournalFilterSection {
|
||||||
|
TIMETABLE_STATUS = 'timetable-status',
|
||||||
|
TWRSKR = 'twrskr',
|
||||||
|
}
|
||||||
|
|||||||