Compare commits
159 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| af9073ab98 | |||
| 800fc35e63 | |||
| d5649d221b | |||
| 5b35fac512 | |||
| d5bc90f668 | |||
| 6d663886f0 | |||
| 85a1a0216e | |||
| 4ac054e947 | |||
| ba70fa1316 | |||
| 77e6b20d0c | |||
| f60263c923 | |||
| 6aec1a75c9 | |||
| d28d600833 | |||
| a353eb3185 | |||
| c5735a6953 | |||
| 7930f7fc8a | |||
| 68f4d54619 | |||
| c4f9738589 | |||
| dd916afd1d | |||
| ea5c9e0028 | |||
| eb7c2d7132 | |||
| ee7c50f59b | |||
| 439f59fedc | |||
| c47d839ce3 | |||
| f77c13cbcf | |||
| dbbbd33100 | |||
| 14d13360a8 | |||
| dc862252ba | |||
| e5fe727ccd | |||
| e836bbed0c | |||
| d4438fd215 | |||
| 1550849360 | |||
| 9d1dc4ffca | |||
| 0397fa788d | |||
| 6e5696b0a6 | |||
| 4537341a57 | |||
| c35c74bd4a | |||
| 25735c5e6e | |||
| 41e60bc69e | |||
| 933bdecb3c | |||
| 10e183d96b | |||
| 5429d39f5e | |||
| ff31e7f903 | |||
| 91f4c6bc57 | |||
| c133eb060b | |||
| 7ffc169d8a | |||
| 1b85cc5f58 | |||
| 72ff857fff | |||
| 96d64e77fc | |||
| 6ceae3f161 | |||
| 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 |
@@ -31,6 +31,7 @@ node_modules
|
||||
.firebase
|
||||
.firebaserc
|
||||
|
||||
# Env
|
||||
.env
|
||||
|
||||
.fake
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
|
||||
<meta name="keywords" content="Stacjownik, TD2, Train Driver 2, stacjownik-td2" />
|
||||
<meta name="description" content="Automatycznie odświeżana strona wyświetlająca stacje w Train Driver 2!" />
|
||||
<meta name="keywords" content="Stacjownik, TD2, Train Driver 2, stacjownik-td2, stacjownik, td2.info.pl" />
|
||||
<meta name="description" content="Pomocnik maszynisty i dyżurnego symulatora Train Driver 2" />
|
||||
|
||||
<title>Stacjownik</title>
|
||||
|
||||
@@ -16,15 +16,31 @@
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
|
||||
<meta name="msapplication-TileColor" content="#da532c" />
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<meta name="theme-color" content="#222222" />
|
||||
|
||||
<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-62.png" sizes="62x62" type="image/png" />
|
||||
<link rel="icon" href="favicon-32.png" sizes="32x32" type="image/png" />
|
||||
<link rel="icon" href="favicon-16.png" sizes="16x16" type="image/png" />
|
||||
<link rel="icon" href="favicon.ico" />
|
||||
|
||||
<link href="https://fonts.googleapis.com/css2?family=Quicksand:wght@400;500;700&display=swap" rel="stylesheet" />
|
||||
<!-- Static OpenGraph meta -->
|
||||
<meta name="description" content="Pomocnik maszynisty i dyżurnego symulatora Train Driver 2" />
|
||||
<meta property="og:url" content="https://stacjownik-td2.web.app/" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:title" content="Stacjownik" />
|
||||
<meta property="og:description" content="Pomocnik maszynisty i dyżurnego symulatora Train Driver 2" />
|
||||
<meta property="og:image" content="https://raw.githubusercontent.com/Spythere/api/main/thumbnails/stacjownik.jpg" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta property="og:site_name" content="Stacjownik" />
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="Stacjownik" />
|
||||
<meta name="twitter:description" content="Pomocnik maszynisty i dyżurnego symulatora Train Driver 2" />
|
||||
<meta name="twitter:image" content="https://raw.githubusercontent.com/Spythere/api/main/thumbnails/stacjownik.jpg" />
|
||||
|
||||
<link href="https://fonts.googleapis.com/css2?family=Quicksand:wght@500;700&display=swap" rel="stylesheet" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "stacjownik",
|
||||
"version": "1.11.0",
|
||||
"version": "1.17.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -9,25 +9,26 @@
|
||||
"preview": "yarn build && vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": "^3.12.1",
|
||||
"dotenv": "^16.0.3",
|
||||
"firebase": "^9.8.1",
|
||||
"howler": "^2.2.1",
|
||||
"pinia": "^2.0.14",
|
||||
"sass": "^1.53.0",
|
||||
"socket.io-client": "^4.4.1",
|
||||
"vue": "^3.2.37",
|
||||
"vue-i18n": "^9.1.6",
|
||||
"vue-router": "^4.0.0-0"
|
||||
"core-js": "^3.32.2",
|
||||
"dotenv": "^16.3.1",
|
||||
"firebase": "^10.4.0",
|
||||
"howler": "^2.2.4",
|
||||
"pinia": "^2.1.6",
|
||||
"sass": "^1.67.0",
|
||||
"socket.io-client": "^4.7.2",
|
||||
"vue": "^3.3.4",
|
||||
"vue-i18n": "^9.4.1",
|
||||
"vue-router": "^4.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.11.17",
|
||||
"@vitejs/plugin-vue": "^4.0.0",
|
||||
"axios": "^1.2.1",
|
||||
"typescript": "^4.9.4",
|
||||
"vite": "^4.0.3",
|
||||
"vite-plugin-pwa": "^0.14.0",
|
||||
"vue-tsc": "^1.0.18"
|
||||
"@types/node": "^20.6.2",
|
||||
"@vite-pwa/assets-generator": "^0.0.10",
|
||||
"@vitejs/plugin-vue": "^4.3.4",
|
||||
"axios": "^1.5.0",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^4.4.9",
|
||||
"vite-plugin-pwa": "^0.16.5",
|
||||
"vue-tsc": "^1.8.11"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
|
||||
|
After Width: | Height: | Size: 932 B |
|
After Width: | Height: | Size: 953 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1020 B |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 799 B |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
@@ -1,3 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 215 B |
@@ -1,7 +1,6 @@
|
||||
@import './styles/responsive.scss';
|
||||
@import './styles/variables.scss';
|
||||
@import './styles/global.scss';
|
||||
@import './styles/scenery_status.scss';
|
||||
|
||||
// VUE ROUTE CHANGE ANIMATION
|
||||
.view-anim {
|
||||
@@ -46,7 +45,11 @@
|
||||
font-size: 1rem;
|
||||
|
||||
@include smallScreen() {
|
||||
font-size: calc(0.5rem + 1.3vw);
|
||||
font-size: calc(0.55rem + 1.1vw);
|
||||
}
|
||||
|
||||
@include screenLandscape() {
|
||||
font-size: calc(0.45rem + 0.8vw);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +90,11 @@ footer.app_footer {
|
||||
max-width: 100%;
|
||||
padding: 0.5em;
|
||||
|
||||
img {
|
||||
width: 1.1em;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
|
||||
z-index: 10;
|
||||
|
||||
background: #111;
|
||||
|
||||
@@ -21,7 +21,10 @@
|
||||
<footer class="app_footer">
|
||||
©
|
||||
<a href="https://td2.info.pl/profile/?u=20777" target="_blank">Spythere</a>
|
||||
{{ new Date().getUTCFullYear() }} | <a :href="releaseURL" target="_blank">v{{ VERSION }}</a>
|
||||
{{ new Date().getUTCFullYear() }} |
|
||||
<a :href="releaseURL" target="_blank">v{{ VERSION }}{{ isOnProductionHost ? '' : 'dev' }}</a>
|
||||
<br />
|
||||
<a href="https://discord.gg/x2mpNN3svk"><img :src="getIcon('discord', 'png')" alt=""> <b>{{ $t('footer.discord') }}</b></a>
|
||||
|
||||
<div style="display: none">∫ ukryta taktyczna całka do programowania w HTMLu</div>
|
||||
</footer>
|
||||
@@ -86,6 +89,7 @@ export default defineComponent({
|
||||
|
||||
currentLang: 'pl',
|
||||
releaseURL: '',
|
||||
isOnProductionHost: location.hostname == 'stacjownik-td2.web.app',
|
||||
}),
|
||||
|
||||
created() {
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
<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"/>
|
||||
<path d="M30.5 6.04878H35.2195" stroke="#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="M33.0244 29.6688V5.47793V4.68292H34.4878V5.47793V56.5854H33.0244V32.5H27.5V28.5V28.0163L28.5 28V31.5L31.9268 31.5447H33.0244V29.6688Z" fill="#BFBFBF"/>
|
||||
<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"/>
|
||||
<path d="M32.0488 25.8537V7.86993V7.41464H24V25.8537H32.0488Z" fill="black"/>
|
||||
<path d="M25 26V29.5L33.8781 44.9756" stroke="black"/>
|
||||
<rect x="33.0244" y="31.5447" width="1.46341" height="25.0407" fill="white"/>
|
||||
<rect x="33.0244" y="31.5447" width="1.46341" height="5.69106" fill="#FF0000"/>
|
||||
<rect x="33.0244" y="42.9268" width="1.46341" height="5.69106" fill="#FF0000"/>
|
||||
<rect x="33.0244" y="54.3089" width="1.46341" height="5.69106" fill="#FF0000"/>
|
||||
<ellipse cx="27.9024" cy="7.40022" rx="1.46341" ry="1.40022" fill="#212121"/>
|
||||
<ellipse cx="27.9024" cy="11.8343" rx="1.46341" ry="1.40022" fill="#212121"/>
|
||||
<ellipse cx="27.9024" cy="16.2683" rx="1.46341" ry="1.40022" fill="#FF0000"/>
|
||||
<ellipse cx="27.9024" cy="20.7023" rx="1.46341" ry="1.40022" fill="#212121"/>
|
||||
<ellipse cx="27.9024" cy="25.1364" rx="1.46341" ry="1.40022" fill="#212121"/>
|
||||
</svg>
|
||||
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect y="-0.00012207" width="60" height="60" fill="#898989"/>
|
||||
<path d="M29.0126 32.4897V10.2511V9.52028H30.4337V10.2511V57.234H29.0126V32.4897Z" fill="#BFBFBF"/>
|
||||
<path d="M26.955 29.3992V32.9949L29.7672 36.9105" stroke="black" stroke-width="0.61183"/>
|
||||
<rect x="29.0051" y="34.0686" width="1.42857" height="22.8196" fill="white"/>
|
||||
<rect x="29.0051" y="34.0686" width="1.42857" height="5.18627" fill="#FF0000"/>
|
||||
<rect x="29.0051" y="54.8137" width="1.42857" height="5.18627" fill="#FF0000"/>
|
||||
<rect x="29.0051" y="44.4412" width="1.42857" height="5.18627" fill="#FF0000"/>
|
||||
<rect x="27.8749" y="31.8649" width="3.75" height="2.17823" fill="white"/>
|
||||
<path d="M33.5 28.5111V8.61545V8.11176H26V28.5111H33.5Z" fill="black"/>
|
||||
<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"/>
|
||||
<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="29.887" cy="11.8168" rx="1.38696" ry="1.28474" fill="#212121"/>
|
||||
<ellipse cx="29.887" cy="8.0135" rx="1.38696" ry="1.28474" fill="#212121"/>
|
||||
<ellipse cx="29.887" cy="15.6151" rx="1.38696" ry="1.28474" fill="#212121"/>
|
||||
<ellipse cx="29.887" cy="19.6834" rx="1.38696" ry="1.28474" fill="#212121"/>
|
||||
<ellipse cx="29.887" cy="23.7518" rx="1.38696" ry="1.28474" fill="#212121"/>
|
||||
<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 |
|
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 |
|
After Width: | Height: | Size: 2.3 KiB |
@@ -6,16 +6,6 @@
|
||||
<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>
|
||||
|
||||
<span class="icons-bottom">
|
||||
<a href="https://www.paypal.com/paypalme/spythere" target="_blank">
|
||||
<img :src="getIcon('dollar')" alt="icon paypal" />
|
||||
</a>
|
||||
|
||||
<a href="https://discord.gg/x2mpNN3svk" target="_blank">
|
||||
<img :src="getIcon('discord', 'png')" alt="icon discord" />
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="header_body">
|
||||
@@ -33,6 +23,12 @@
|
||||
<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" />
|
||||
@@ -98,11 +94,17 @@ export default defineComponent({
|
||||
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 =
|
||||
@@ -135,22 +137,20 @@ export default defineComponent({
|
||||
|
||||
.header {
|
||||
&_body {
|
||||
max-width: 21em;
|
||||
position: relative;
|
||||
|
||||
@include smallScreen {
|
||||
max-width: 18em;
|
||||
}
|
||||
max-width: 20em;
|
||||
}
|
||||
|
||||
&_container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
|
||||
width: 1350px;
|
||||
padding: 0.5em 0.3em 0 0.3em;
|
||||
border-radius: 0 0 1em 1em;
|
||||
|
||||
@include smallScreen {
|
||||
position: relative;
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
&_brand {
|
||||
@@ -158,6 +158,7 @@ export default defineComponent({
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
@@ -165,9 +166,7 @@ export default defineComponent({
|
||||
&_info {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
max-width: 100%;
|
||||
|
||||
font-size: 1.2em;
|
||||
font-size: 1.15em;
|
||||
}
|
||||
|
||||
&_links {
|
||||
@@ -184,57 +183,20 @@ export default defineComponent({
|
||||
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;
|
||||
padding: 0.5em;
|
||||
|
||||
@include smallScreen() {
|
||||
right: auto;
|
||||
left: 0.75em;
|
||||
padding: 0;
|
||||
|
||||
align-items: center;
|
||||
@include smallScreen {
|
||||
transform: translateX(85%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
.icons-top {
|
||||
img {
|
||||
width: 2.5em;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -164,7 +164,7 @@
|
||||
import { defineComponent } from 'vue';
|
||||
import { DataStatus } from '../../scripts/enums/DataStatus';
|
||||
import { useStore } from '../../store/store';
|
||||
import { StoreState } from '../../store/storeTypes';
|
||||
import { StoreState } from '../../scripts/interfaces/store/storeTypes';
|
||||
|
||||
export default defineComponent({
|
||||
data() {
|
||||
@@ -303,9 +303,11 @@ export default defineComponent({
|
||||
|
||||
.status-indicator {
|
||||
position: absolute;
|
||||
left: 110%;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
|
||||
transform: translateX(1.5em);
|
||||
}
|
||||
|
||||
.indicator {
|
||||
@@ -330,7 +332,7 @@ export default defineComponent({
|
||||
background-color: #171717;
|
||||
border-radius: 0.75em;
|
||||
|
||||
min-width: 13em;
|
||||
width: 13em;
|
||||
text-align: center;
|
||||
overflow: none;
|
||||
|
||||
@@ -354,22 +356,16 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
@include midScreen() {
|
||||
left: 50%;
|
||||
top: 100%;
|
||||
|
||||
transform: translate(-50%, 0);
|
||||
margin-left: 0;
|
||||
margin-top: 0.75em;
|
||||
left: auto;
|
||||
right: 200%;
|
||||
|
||||
&::before {
|
||||
border-left: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
border-bottom: 10px solid #171717;
|
||||
border-left: 12px solid #171717;
|
||||
right: 0;
|
||||
left: auto;
|
||||
|
||||
top: 0;
|
||||
left: 50%;
|
||||
|
||||
transform: translate(-50%, -100%);
|
||||
transform: translate(100%, -50%);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,3 +375,4 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<button
|
||||
class="btn btn--option btn--load-data"
|
||||
v-if="!scrollNoMoreData && scrollDataLoaded && list.length >= 15"
|
||||
@click="addHistoryData"
|
||||
>
|
||||
{{ $t('journal.load-data') }}
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
scrollNoMoreData: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
|
||||
scrollDataLoaded: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
|
||||
list: {
|
||||
type: Array as PropType<any[]>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
emits: ['addHistoryData'],
|
||||
|
||||
methods: {
|
||||
addHistoryData() {
|
||||
this.$emit('addHistoryData');
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<div class="progress-bar">
|
||||
<span class="bar-bg"></span>
|
||||
<span class="bar-fg" :style="{ width: `${~~progressPercent}%`, backgroundColor: bgColor }"></span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
progressPercent: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
progressType: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
bgColor() {
|
||||
switch (this.progressType) {
|
||||
case 'abandoned':
|
||||
return 'salmon';
|
||||
|
||||
default:
|
||||
return 'springgreen';
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.progress-bar {
|
||||
position: relative;
|
||||
|
||||
width: 6em;
|
||||
height: 1em;
|
||||
margin: 0.5em 0;
|
||||
|
||||
.bar-fg,
|
||||
.bar-bg {
|
||||
position: absolute;
|
||||
height: 1em;
|
||||
width: 100%;
|
||||
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.bar-fg {
|
||||
background-color: springgreen;
|
||||
}
|
||||
|
||||
.bar-bg {
|
||||
background-color: #5b5b5b;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -3,6 +3,10 @@
|
||||
<div class="select-box_content">
|
||||
<button class="selected" @click="toggleBox">
|
||||
<span>{{ computedSelectedItem.selectedValue || computedSelectedItem.value }}</span>
|
||||
|
||||
<div class="arrow">
|
||||
<img :src="listOpen ? getIcon('arrow-asc') : getIcon('arrow-desc')" alt="arrow-icon" />
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<ul class="options" :ref="(el) => (listRef = el as Element)">
|
||||
@@ -21,10 +25,6 @@
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="arrow">
|
||||
<img :src="listOpen ? getIcon('arrow-asc') : getIcon('arrow-desc')" alt="arrow-icon" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -129,46 +129,22 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
.select-box {
|
||||
position: relative;
|
||||
width: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
padding: 0;
|
||||
|
||||
img {
|
||||
vertical-align: middle;
|
||||
width: 1.35em;
|
||||
}
|
||||
|
||||
transform: translateY(-50%);
|
||||
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
button.selected {
|
||||
background-color: transparent;
|
||||
color: paleturquoise;
|
||||
|
||||
font-size: 1em;
|
||||
font-weight: bold;
|
||||
|
||||
padding: 0.1em 0.5em;
|
||||
margin-right: 2em;
|
||||
|
||||
display: flex;
|
||||
|
||||
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
|
||||
border: none;
|
||||
outline: none;
|
||||
|
||||
text-align: left;
|
||||
|
||||
&:focus {
|
||||
background-color: #262626;
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<span class="status-badge" :class="statusID" v-if="isOnline">
|
||||
{{ $t(`status.${statusID}`) }}
|
||||
{{ statusID == 'online' ? timestampToString(statusTimestamp!) : '' }}
|
||||
</span>
|
||||
|
||||
<span class="status-badge free" v-else>
|
||||
{{ $t('status.free') }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import dateMixin from '../../mixins/dateMixin';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
statusID: {
|
||||
type: String,
|
||||
},
|
||||
statusTimestamp: {
|
||||
type: Number,
|
||||
},
|
||||
isOnline: {
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
mixins: [dateMixin],
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$free: #8a8a8a;
|
||||
$ending: #e6c300;
|
||||
$no-limit: #117fc9;
|
||||
$unav: #ff3d5d;
|
||||
$brb: #e6a100;
|
||||
$no-space: #222;
|
||||
$online: #09a116;
|
||||
$unknown: rgb(185, 60, 60);
|
||||
|
||||
.status-badge {
|
||||
border-radius: 1rem;
|
||||
font-weight: 500;
|
||||
|
||||
padding: 0.2em 0.55em;
|
||||
|
||||
background-color: $online;
|
||||
|
||||
&.free {
|
||||
background-color: $free;
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
&.ending {
|
||||
background-color: $ending;
|
||||
color: black;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
&.no-limit {
|
||||
background-color: $no-limit;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
&.not-signed,
|
||||
&.unavailable {
|
||||
background-color: $unav;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
&.brb {
|
||||
background-color: $brb;
|
||||
color: black;
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
&.no-space {
|
||||
background-color: $no-space;
|
||||
border: 1px solid white;
|
||||
color: white;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
&.unknown {
|
||||
background-color: $unknown;
|
||||
font-size: 0.95em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<div class="stock-list">
|
||||
<ul>
|
||||
<li v-for="(stockName, i) in trainStockList">
|
||||
<p>{{ stockName.split(':')[0].split('_').splice(0, 2).join(' ') }} {{ stockName.split(':')[1] }}</p>
|
||||
|
||||
<span>
|
||||
<img
|
||||
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}${/^EN/.test(stockName) ? 'rb' : ''}.png`"
|
||||
@error="onImageError($event, stockName)"
|
||||
width="400"
|
||||
height="60"
|
||||
/>
|
||||
|
||||
<img
|
||||
v-if="/^(EN|2EN)/.test(stockName)"
|
||||
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}s.png`"
|
||||
@error="(event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-s.png')"
|
||||
/>
|
||||
|
||||
<img
|
||||
class="train-thumbnail"
|
||||
v-if="/^EN71/.test(stockName)"
|
||||
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}s.png`"
|
||||
@error="(event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-s.png')"
|
||||
/>
|
||||
|
||||
<img
|
||||
class="train-thumbnail"
|
||||
v-if="/^(EN|2EN)/.test(stockName)"
|
||||
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}ra.png`"
|
||||
@error="(event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-ra.png')"
|
||||
/>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent } from 'vue';
|
||||
import { useStore } from '../../store/store';
|
||||
import { RollingStockInfo } from '../../scripts/interfaces/github_api/StockInfoGithubData';
|
||||
import imageMixin from '../../mixins/imageMixin';
|
||||
|
||||
export default defineComponent({
|
||||
mixins: [imageMixin],
|
||||
|
||||
props: {
|
||||
trainStockList: {
|
||||
type: Array as PropType<string[]>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
store: useStore(),
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
onImageError(event: Event, stockName: string) {
|
||||
const fallbackName =
|
||||
Object.keys(this.store.rollingStockData!.info).find((type) => {
|
||||
return this.store.rollingStockData!.info[type as keyof RollingStockInfo].find((v) => v[0] === stockName.split(':')[0]);
|
||||
}) || 'vehicle-unknown';
|
||||
|
||||
(event.target as HTMLImageElement).src = `/images/icon-${fallbackName}.png`;
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.stock-list {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.stock-list ul {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
overflow: auto;
|
||||
margin: 0 auto;
|
||||
padding: 1em 0;
|
||||
}
|
||||
|
||||
ul > li > span {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
img {
|
||||
max-height: 60px;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: center;
|
||||
color: #aaa;
|
||||
font-size: 0.9em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
</style>
|
||||
@@ -1,119 +1,119 @@
|
||||
<template>
|
||||
<span class="stop-date">
|
||||
<span
|
||||
class="date arrival"
|
||||
v-if="!stop.beginsHere"
|
||||
:class="{
|
||||
delayed: stop.arrivalDelay > 0 && stop.confirmed,
|
||||
preponed: stop.arrivalDelay < 0 && stop.confirmed,
|
||||
'on-time': stop.arrivalDelay == 0 && stop.confirmed,
|
||||
}"
|
||||
>
|
||||
<span v-if="stop.arrivalDelay != 0 && stop.confirmed">
|
||||
<s>{{ timestampToString(stop.arrivalTimestamp) }}</s>
|
||||
{{ timestampToString(stop.arrivalRealTimestamp) }}
|
||||
({{ stop.arrivalDelay > 0 ? '+' : '' }}{{ stop.arrivalDelay }})
|
||||
</span>
|
||||
|
||||
<span v-else>
|
||||
{{ timestampToString(stop.arrivalTimestamp) }}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span class="date stop" v-if="stop.stopTime" :class="stop.stopType.replace(', ', '-')">
|
||||
{{ stop.stopTime }} {{ stop.stopType == '' ? 'pt' : stop.stopType }}
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="date departure"
|
||||
v-if="!stop.terminatesHere && stop.stopTime != 0"
|
||||
:class="{
|
||||
delayed: stop.departureDelay > 0 && stop.confirmed,
|
||||
preponed: stop.departureDelay < 0 && stop.confirmed,
|
||||
}"
|
||||
>
|
||||
<span v-if="stop.departureDelay != 0 && stop.confirmed">
|
||||
<s>{{ timestampToString(stop.departureTimestamp) }}</s>
|
||||
{{ timestampToString(stop.departureRealTimestamp) }}
|
||||
|
||||
({{ stop.departureDelay > 0 ? '+' : '' }}{{ stop.departureDelay }})
|
||||
</span>
|
||||
|
||||
<span v-else>
|
||||
{{ timestampToString(stop.departureTimestamp) }}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import dateMixin from '../../mixins/dateMixin';
|
||||
import TrainStop from '../../scripts/interfaces/TrainStop';
|
||||
|
||||
export default defineComponent({
|
||||
mixins: [dateMixin],
|
||||
|
||||
props: {
|
||||
stop: {
|
||||
type: Object as () => TrainStop,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
setup() {
|
||||
return {};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$preponedClr: lime;
|
||||
$delayedClr: salmon;
|
||||
$dateClr: #525151;
|
||||
$stopExchangeClr: #db8e29;
|
||||
$stopDefaultClr: #252525;
|
||||
|
||||
.stop-date {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.date {
|
||||
background: $dateClr;
|
||||
padding: 0.3em 0.5em;
|
||||
}
|
||||
|
||||
.stop {
|
||||
&.ph,
|
||||
&.ph-pm,
|
||||
&.pm {
|
||||
background: $stopExchangeClr;
|
||||
}
|
||||
|
||||
background: $stopDefaultClr;
|
||||
}
|
||||
|
||||
.arrival,
|
||||
.departure {
|
||||
&.delayed {
|
||||
s {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
span {
|
||||
color: $delayedClr;
|
||||
}
|
||||
}
|
||||
|
||||
&.preponed {
|
||||
s {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
span {
|
||||
color: $preponedClr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<span class="stop-date">
|
||||
<span
|
||||
class="date arrival"
|
||||
v-if="!stop.beginsHere"
|
||||
:class="{
|
||||
delayed: stop.arrivalDelay > 0 && (stop.confirmed || stop.stopped),
|
||||
preponed: stop.arrivalDelay < 0 && (stop.confirmed || stop.stopped),
|
||||
'on-time': stop.arrivalDelay == 0 && stop.confirmed,
|
||||
}"
|
||||
>
|
||||
<span v-if="stop.arrivalDelay != 0 && (stop.confirmed || stop.stopped)">
|
||||
<s>{{ timestampToString(stop.arrivalTimestamp) }}</s>
|
||||
{{ timestampToString(stop.arrivalRealTimestamp) }}
|
||||
({{ stop.arrivalDelay > 0 ? '+' : '' }}{{ stop.arrivalDelay }})
|
||||
</span>
|
||||
|
||||
<span v-else>
|
||||
{{ timestampToString(stop.arrivalTimestamp) }}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span class="date stop" v-if="stop.stopTime || stop.stopped" :class="stop.stopType.replace(', ', '-')">
|
||||
{{ stop.stopTime }} {{ stop.stopType == '' ? 'pt' : stop.stopType }}
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="date departure"
|
||||
v-if="!stop.terminatesHere && (stop.stopTime != 0 || stop.stopped)"
|
||||
:class="{
|
||||
delayed: stop.departureDelay > 0 && stop.confirmed,
|
||||
preponed: stop.departureDelay < 0 && stop.confirmed,
|
||||
}"
|
||||
>
|
||||
<span v-if="stop.departureDelay != 0 && stop.confirmed">
|
||||
<s>{{ timestampToString(stop.departureTimestamp) }}</s>
|
||||
{{ timestampToString(stop.departureRealTimestamp) }}
|
||||
|
||||
({{ stop.departureDelay > 0 ? '+' : '' }}{{ stop.departureDelay }})
|
||||
</span>
|
||||
|
||||
<span v-else>
|
||||
{{ timestampToString(stop.departureTimestamp) }}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import dateMixin from '../../mixins/dateMixin';
|
||||
import TrainStop from '../../scripts/interfaces/TrainStop';
|
||||
|
||||
export default defineComponent({
|
||||
mixins: [dateMixin],
|
||||
|
||||
props: {
|
||||
stop: {
|
||||
type: Object as () => TrainStop,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
setup() {
|
||||
return {};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$preponedClr: lime;
|
||||
$delayedClr: salmon;
|
||||
$dateClr: #525151;
|
||||
$stopExchangeClr: #db8e29;
|
||||
$stopDefaultClr: #252525;
|
||||
|
||||
.stop-date {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.date {
|
||||
background: $dateClr;
|
||||
padding: 0.3em 0.5em;
|
||||
}
|
||||
|
||||
.stop {
|
||||
&.ph,
|
||||
&.ph-pm,
|
||||
&.pm {
|
||||
background: $stopExchangeClr;
|
||||
}
|
||||
|
||||
background: $stopDefaultClr;
|
||||
}
|
||||
|
||||
.arrival,
|
||||
.departure {
|
||||
&.delayed {
|
||||
s {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
span {
|
||||
color: $delayedClr;
|
||||
}
|
||||
}
|
||||
|
||||
&.preponed {
|
||||
s {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
span {
|
||||
color: $preponedClr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<img class="train-thumbnail" :src="placeholderUrl" v-if="isNotFound" />
|
||||
|
||||
<img
|
||||
class="train-thumbnail"
|
||||
v-else
|
||||
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${name.split(':')[0]}${stockType == 'loco-ezt' ? 'rb' : ''}.png`"
|
||||
@error="onImageError"
|
||||
@load="onImageLoad"
|
||||
width="220"
|
||||
height="60"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import imageMixin from '../../mixins/imageMixin';
|
||||
import { useStore } from '../../store/store';
|
||||
import { RollingStockInfo } from '../../scripts/interfaces/github_api/StockInfoGithubData';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
|
||||
onlyFirstSegment: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
store: useStore(),
|
||||
isNotFound: false,
|
||||
isLoaded: false,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
url() {
|
||||
return `https://rj.td2.info.pl/dist/img/thumbnails/${this.name.split(':')[0]}.png`;
|
||||
},
|
||||
|
||||
placeholderUrl() {
|
||||
return `/images/icon-${this.stockType}.png`;
|
||||
},
|
||||
|
||||
stockType() {
|
||||
if (!this.store.rollingStockData) return 'vehicle-unknown';
|
||||
|
||||
return (
|
||||
Object.keys(this.store.rollingStockData.info).find((type) => {
|
||||
return this.store.rollingStockData?.info[type as keyof RollingStockInfo].find((v) => v[0] === this.name.split(':')[0]);
|
||||
}) || 'vehicle-unknown'
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
onImageError() {
|
||||
this.isNotFound = true;
|
||||
this.isLoaded = false;
|
||||
},
|
||||
|
||||
onImageLoad() {
|
||||
this.isNotFound = false;
|
||||
this.isLoaded = true;
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.train-thumbnail {
|
||||
width: auto;
|
||||
height: auto;
|
||||
max-height: 60px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,56 +1,62 @@
|
||||
<template>
|
||||
<section class="daily-stats">
|
||||
<span :data-active="data.statsStatus">
|
||||
<b v-if="data.statsStatus == DataStatus.Loading">
|
||||
<span :data-active="statsStatus">
|
||||
<b v-if="statsStatus == DataStatus.Loading">
|
||||
{{ $t('app.loading') }}
|
||||
</b>
|
||||
|
||||
<b v-else-if="data.stats.distanceSum == null">
|
||||
<b v-else-if="stats.distanceSum == null">
|
||||
{{ $t('journal.daily-stats-info') }}
|
||||
</b>
|
||||
|
||||
<span>
|
||||
<div v-if="data.stats.totalTimetables">
|
||||
<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">
|
||||
{{ data.stats.totalTimetables }}
|
||||
{{ $t('journal.timetable-count', data.stats.totalTimetables) }}
|
||||
{{ stats.totalTimetables }}
|
||||
{{ $t('journal.timetable-count', stats.totalTimetables) }}
|
||||
</b>
|
||||
</template>
|
||||
|
||||
<template #distance>
|
||||
<b class="text--primary"> {{ data.stats.distanceSum?.toFixed(2) }} km </b>
|
||||
<b class="text--primary"> {{ stats.distanceSum?.toFixed(2) }} km</b>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
|
||||
<div v-if="data.stats.timetableId">
|
||||
<div v-if="stats.timetableId">
|
||||
•
|
||||
<i18n-t keypath="journal.timetable-stats-longest">
|
||||
<template #id>
|
||||
<router-link :to="`/journal/timetables?timetableId=${data.stats.timetableId}`">
|
||||
<b>{{ data.stats.timetableId }}</b>
|
||||
<router-link :to="`/journal/timetables?timetableId=${stats.timetableId}`">
|
||||
<b>{{ stats.timetableId }}</b>
|
||||
</router-link>
|
||||
</template>
|
||||
<template #author>
|
||||
<router-link :to="`/journal/dispatchers?dispatcherName=${data.stats.timetableAuthor}`">
|
||||
<b>{{ data.stats.timetableAuthor }}</b>
|
||||
<router-link :to="`/journal/dispatchers?dispatcherName=${stats.timetableAuthor}`">
|
||||
<b>{{ stats.timetableAuthor }}</b>
|
||||
</router-link>
|
||||
</template>
|
||||
<template #driver>
|
||||
<b>{{ data.stats.timetableDriver }}</b>
|
||||
<b class="text--primary">{{ stats.timetableDriver }}</b>
|
||||
</template>
|
||||
<template #distance>
|
||||
<b class="text--primary">{{ data.stats.timetableRouteDistance }} km</b>
|
||||
<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">
|
||||
<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>
|
||||
@@ -67,7 +73,7 @@
|
||||
|
||||
<div v-if="firstPlaceDispatchers.length > 1">
|
||||
•
|
||||
<i18n-t keypath="journal.timetable-stats-most-active-many">
|
||||
<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>
|
||||
@@ -88,95 +94,157 @@
|
||||
</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.toFixed(2) }} km</b>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
<script lang="ts">
|
||||
import axios from 'axios';
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
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';
|
||||
|
||||
const intervalId = ref(-1);
|
||||
export default defineComponent({
|
||||
mixins: [dateMixin],
|
||||
emits: ['toggleStatsOpen'],
|
||||
|
||||
const data = reactive({
|
||||
statsStatus: DataStatus.Loading,
|
||||
data() {
|
||||
return {
|
||||
DataStatus,
|
||||
statsStatus: DataStatus.Loading,
|
||||
intervalId: -1,
|
||||
|
||||
stats: {
|
||||
totalTimetables: 0,
|
||||
distanceSum: 0,
|
||||
distanceAvg: 0,
|
||||
timetableAuthor: '',
|
||||
timetableDriver: '',
|
||||
timetableId: 0,
|
||||
timetableRouteDistance: 0,
|
||||
|
||||
mostActiveDispatchers: [],
|
||||
} as ITimetablesDailyStats,
|
||||
});
|
||||
|
||||
const firstPlaceDispatchers = computed(() => {
|
||||
if (data.stats.mostActiveDispatchers.length == 0) return [];
|
||||
const maxCount = data.stats.mostActiveDispatchers[0].count;
|
||||
|
||||
return data.stats.mostActiveDispatchers.filter((disp) => disp.count === maxCount);
|
||||
});
|
||||
|
||||
async function fetchDailyTimetableStats() {
|
||||
try {
|
||||
const {
|
||||
distanceAvg,
|
||||
distanceSum,
|
||||
maxTimetable,
|
||||
totalTimetables,
|
||||
mostActiveDispatchers,
|
||||
}: ITimetablesDailyStatsResponse = await (
|
||||
await axios.get(`${URLs.stacjownikAPI}/api/getDailyTimetableStats`)
|
||||
).data;
|
||||
|
||||
data.stats = {
|
||||
totalTimetables,
|
||||
distanceSum,
|
||||
distanceAvg,
|
||||
timetableAuthor: maxTimetable?.authorName || '',
|
||||
timetableDriver: maxTimetable?.driverName || '',
|
||||
timetableId: maxTimetable?.timetableId || 0,
|
||||
timetableRouteDistance: maxTimetable?.routeDistance || 0,
|
||||
|
||||
mostActiveDispatchers,
|
||||
stats: {
|
||||
totalTimetables: 0,
|
||||
distanceSum: 0,
|
||||
distanceAvg: 0,
|
||||
timetableAuthor: '',
|
||||
timetableDriver: '',
|
||||
timetableId: 0,
|
||||
timetableRouteDistance: 0,
|
||||
longestDuties: [],
|
||||
mostActiveDrivers: [],
|
||||
mostActiveDispatchers: [],
|
||||
} as ITimetablesDailyStats,
|
||||
};
|
||||
},
|
||||
|
||||
data.statsStatus = DataStatus.Loaded;
|
||||
} catch (error) {
|
||||
console.error('Ups! Wystąpił błąd podczas pobierania statystyk rozkładów jazdy...');
|
||||
data.statsStatus = DataStatus.Error;
|
||||
}
|
||||
}
|
||||
activated() {
|
||||
this.startFetchingDailyStats();
|
||||
this.$emit('toggleStatsOpen', true);
|
||||
},
|
||||
|
||||
function startFetchingDailyStats() {
|
||||
fetchDailyTimetableStats();
|
||||
intervalId.value = setInterval(fetchDailyTimetableStats, 60000);
|
||||
}
|
||||
deactivated() {
|
||||
this.stopFetchingDailyStats();
|
||||
},
|
||||
|
||||
function stopFetchingDailyStats() {
|
||||
clearInterval(intervalId.value);
|
||||
}
|
||||
computed: {
|
||||
firstPlaceDispatchers() {
|
||||
if (this.stats.mostActiveDispatchers.length == 0) return [];
|
||||
const maxCount = this.stats.mostActiveDispatchers[0].count;
|
||||
|
||||
defineExpose({
|
||||
startFetchingDailyStats,
|
||||
stopFetchingDailyStats,
|
||||
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,62 +1,155 @@
|
||||
<template>
|
||||
<ul class="journal-list">
|
||||
<!-- <transition-group name="journal-list-anim"> -->
|
||||
<li v-for="item in computedDispatcherHistory" :class="{ sticky: typeof item == 'string' }">
|
||||
<div v-if="typeof item == 'string'" class="journal_day">
|
||||
{{ item }}
|
||||
<div>
|
||||
<transition name="status-anim" mode="out-in">
|
||||
<div :key="dataStatus">
|
||||
<div class="journal_warning" v-if="store.isOffline">
|
||||
{{ $t('app.offline') }}
|
||||
</div>
|
||||
|
||||
<Loading v-else-if="dataStatus == DataStatus.Loading" />
|
||||
|
||||
<div v-else-if="dataStatus == DataStatus.Error" class="journal_warning error">
|
||||
{{ $t('app.error') }}
|
||||
</div>
|
||||
|
||||
<div class="journal_warning" v-else-if="dispatcherHistory.length == 0">
|
||||
{{ $t('app.no-result') }}
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<table class="scenery-history-table">
|
||||
<thead>
|
||||
<th>{{ $t('journal.history-name') }}</th>
|
||||
<th>{{ $t('journal.history-hash') }}</th>
|
||||
<th>{{ $t('journal.history-dispatcher') }}</th>
|
||||
<th>{{ $t('journal.history-level') }}</th>
|
||||
<th>{{ $t('journal.history-rate') }}</th>
|
||||
<th>{{ $t('journal.history-region') }}</th>
|
||||
<th>{{ $t('journal.history-date') }}</th>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<transition-group name="list-anim">
|
||||
<tr v-for="historyItem in dispatcherHistory" :key="historyItem.id">
|
||||
<td>
|
||||
<router-link :to="`/journal/dispatchers?sceneryName=${historyItem.stationName}`">
|
||||
<b>{{ historyItem.stationName }}</b>
|
||||
</router-link>
|
||||
</td>
|
||||
<td>#{{ historyItem.stationHash }}</td>
|
||||
<td>
|
||||
<router-link :to="`/journal/dispatchers?dispatcherName=${historyItem.dispatcherName}`">
|
||||
<b>{{ historyItem.dispatcherName }}</b>
|
||||
</router-link>
|
||||
</td>
|
||||
<td>
|
||||
<b
|
||||
v-if="historyItem.dispatcherLevel !== null"
|
||||
class="level-badge dispatcher"
|
||||
:style="calculateExpStyle(historyItem.dispatcherLevel, historyItem.dispatcherIsSupporter)"
|
||||
>
|
||||
{{ historyItem.dispatcherLevel >= 2 ? historyItem.dispatcherLevel : 'L' }}
|
||||
</b>
|
||||
</td>
|
||||
<td class="text--primary">
|
||||
<b>{{ historyItem.dispatcherRate }}</b>
|
||||
</td>
|
||||
<td>
|
||||
<b class="region-badge" :aria-describedby="historyItem.region">{{
|
||||
regions.find((r) => r.id == historyItem.region)?.value || '???'
|
||||
}}</b>
|
||||
</td>
|
||||
<td style="min-width: 200px" class="time">
|
||||
<span v-if="historyItem.timestampTo" class="text--offline">
|
||||
<b>{{ $d(historyItem.timestampFrom) }}</b>
|
||||
{{ timestampToString(historyItem.timestampFrom) }}
|
||||
- {{ timestampToString(historyItem.timestampTo) }} ({{
|
||||
calculateDuration(historyItem.currentDuration)
|
||||
}})
|
||||
</span>
|
||||
<span class="dispatcher-online" v-else>
|
||||
<b class="text--online">
|
||||
<router-link :to="`/scenery?station=${historyItem.stationName}`">{{
|
||||
$t('journal.online-since')
|
||||
}}</router-link>
|
||||
{{ timestampToString(historyItem.timestampFrom) }}
|
||||
</b>
|
||||
({{ calculateDuration(historyItem.currentDuration) }})
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</transition-group>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<AddDataButton
|
||||
:list="dispatcherHistory"
|
||||
:scrollDataLoaded="scrollDataLoaded"
|
||||
:scrollNoMoreData="scrollNoMoreData"
|
||||
@addHistoryData="addHistoryData"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<div
|
||||
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>
|
||||
<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>
|
||||
<div class="journal_warning" v-if="scrollNoMoreData">
|
||||
{{ $t('journal.no-further-data') }}
|
||||
</div>
|
||||
|
||||
<span>
|
||||
<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> -->
|
||||
</ul>
|
||||
<div class="journal_warning" v-else-if="!scrollDataLoaded">
|
||||
{{ $t('journal.loading-further-data') }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
import dateMixin from '../../mixins/dateMixin';
|
||||
import { DispatcherHistory } from '../../scripts/interfaces/api/DispatchersAPIData';
|
||||
import styleMixin from '../../mixins/styleMixin';
|
||||
import imageMixin from '../../mixins/imageMixin';
|
||||
import { DataStatus } from '../../scripts/enums/DataStatus';
|
||||
import { useStore } from '../../store/store';
|
||||
import Loading from '../Global/Loading.vue';
|
||||
import { regions } from '../../data/options.json';
|
||||
import AddDataButton from '../Global/AddDataButton.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: { Loading, AddDataButton },
|
||||
|
||||
mixins: [dateMixin, styleMixin, imageMixin],
|
||||
|
||||
props: {
|
||||
dispatcherHistory: {
|
||||
type: Array as PropType<DispatcherHistory[]>,
|
||||
required: true,
|
||||
},
|
||||
scrollNoMoreData: {
|
||||
type: Boolean,
|
||||
},
|
||||
scrollDataLoaded: {
|
||||
type: Boolean,
|
||||
},
|
||||
addHistoryData: {
|
||||
type: Function as PropType<() => void>,
|
||||
},
|
||||
dataStatus: {
|
||||
type: Number as PropType<DataStatus>,
|
||||
},
|
||||
},
|
||||
|
||||
mixins: [dateMixin],
|
||||
data() {
|
||||
return {
|
||||
DataStatus,
|
||||
store: useStore(),
|
||||
regions,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
computedDispatcherHistory() {
|
||||
console.log(this.dispatcherHistory.length);
|
||||
|
||||
return this.dispatcherHistory.reduce((acc, historyItem, i) => {
|
||||
if (this.isAnotherDay(i - 1, i)) acc.push(new Date(historyItem.timestampFrom).toLocaleDateString('pl-PL'));
|
||||
acc.push(historyItem);
|
||||
@@ -86,71 +179,63 @@ export default defineComponent({
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../styles/animations.scss';
|
||||
@import '../../styles/responsive.scss';
|
||||
@import '../../styles/badge.scss';
|
||||
@import '../../styles/variables.scss';
|
||||
@import '../../styles/JournalSection.scss';
|
||||
|
||||
.region-badge {
|
||||
padding: 0.1em 0.5em;
|
||||
border-radius: 0.5em;
|
||||
font-weight: bold;
|
||||
table.scenery-history-table {
|
||||
--_bg-table: #111;
|
||||
--_bg-head: #101010;
|
||||
--_bg-row: #2f2f2f;
|
||||
|
||||
&.eu {
|
||||
background-color: forestgreen;
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
|
||||
margin-bottom: 1em;
|
||||
|
||||
thead {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background-color: var(--_bg-head);
|
||||
}
|
||||
|
||||
th {
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
tr {
|
||||
background-color: var(--_bg-row);
|
||||
border-bottom: 2px solid black;
|
||||
|
||||
&:last-child {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 0.75em;
|
||||
|
||||
.level-badge {
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
}
|
||||
|
||||
li.sticky {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.journal_item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
flex-wrap: wrap;
|
||||
|
||||
padding: 0.75em;
|
||||
|
||||
&.online {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
span[data-status='true'] {
|
||||
.text {
|
||||
&--online {
|
||||
color: springgreen;
|
||||
}
|
||||
|
||||
span[data-status='false'] {
|
||||
color: salmon;
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
@include smallScreen() {
|
||||
.journal_item {
|
||||
flex-direction: column;
|
||||
|
||||
span {
|
||||
margin-top: 0.25em;
|
||||
text-align: center;
|
||||
}
|
||||
&--offline {
|
||||
color: #ddd;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,11 +2,18 @@
|
||||
<div class="filters-options" @keydown.esc="showOptions = false">
|
||||
<div class="bg" v-if="showOptions" @click="showOptions = false"></div>
|
||||
|
||||
<button class="filter-button btn--filled btn--image" @click="showOptions = !showOptions" ref="button">
|
||||
<img :src="getIcon('filter2')" alt="Open filters" />
|
||||
{{ $t('options.filters') }} [F]
|
||||
<span class="active-indicator" v-if="currentOptionsActive"></span>
|
||||
</button>
|
||||
<div class="actions-bar">
|
||||
<button class="filter-button btn--filled btn--image" @click="showOptions = !showOptions" ref="button">
|
||||
<img :src="getIcon('filter2')" alt="Open filters" />
|
||||
{{ $t('options.filters') }} [F]
|
||||
<span class="active-indicator" v-if="currentOptionsActive"></span>
|
||||
</button>
|
||||
|
||||
<button class="filter-button btn--filled btn--image" @click="refreshData">
|
||||
<img :src="getIcon('refresh')" alt="Refresh data" />
|
||||
{{ $t('general.refresh') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<datalist id="search-driver">
|
||||
<option v-for="sugg in driverSuggestions" :value="sugg"></option>
|
||||
@@ -22,7 +29,7 @@
|
||||
<h1 class="option-title">{{ $t('options.search-title') }}</h1>
|
||||
<div class="search_content">
|
||||
<div class="search" v-for="(_, propName) in searchersValues" :key="propName">
|
||||
<label v-if="propName == 'search-date'" for="date">{{ $t('options.search-date') }}</label>
|
||||
<label v-if="propName == 'search-date'" for="date">{{ $t(`options.search-${optionsType}-date`) }}</label>
|
||||
|
||||
<div class="search-box">
|
||||
<input
|
||||
@@ -42,15 +49,6 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="search_actions">
|
||||
<button class="btn--action" @click="onResetButtonClick">
|
||||
{{ $t('options.reset-button') }}
|
||||
</button>
|
||||
<button class="btn--action" @click="onSearchButtonConfirm">
|
||||
{{ $t('options.search-button') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1 class="option-title">{{ $t('options.sort-title') }}</h1>
|
||||
@@ -67,15 +65,31 @@
|
||||
</div>
|
||||
|
||||
<h1 class="option-title" v-if="filters.length != 0">{{ $t('options.filter-title') }}</h1>
|
||||
<div class="options_filters">
|
||||
<button
|
||||
v-for="filter in filters"
|
||||
class="filter-option btn--option"
|
||||
:class="{ checked: journalFilterActive.id === filter.id }"
|
||||
:id="filter.id"
|
||||
@click="onFilterChange(filter)"
|
||||
>
|
||||
{{ $t(`options.filter-${filter.id}`) }}
|
||||
|
||||
<div class="options_filter-sections" v-if="filters.length != 0 && filterList">
|
||||
<section class="filter-section" v-for="section in JournalFilterSection">
|
||||
<p>{{ $t(`options.filter-section-${section}`) }}</p>
|
||||
|
||||
<div class="options_filters">
|
||||
<button
|
||||
v-for="filter in filterList.filter((f) => f.filterSection == section)"
|
||||
class="filter-option btn--option"
|
||||
:class="{ checked: filter.isActive }"
|
||||
:id="filter.id"
|
||||
@click="onFilterChange(filter)"
|
||||
>
|
||||
{{ $t(`options.filter-${filter.id}`) }}
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="options_actions">
|
||||
<button class="btn--action" @click="onResetButtonClick">
|
||||
{{ $t('options.reset-button') }}
|
||||
</button>
|
||||
<button class="btn--action" @click="onSearchButtonConfirm">
|
||||
{{ $t('options.search-button') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -93,13 +107,14 @@ import { DataStatus } from '../../scripts/enums/DataStatus';
|
||||
import { DriverStatsAPIData } from '../../scripts/interfaces/api/DriverStatsAPIData';
|
||||
import { URLs } from '../../scripts/utils/apiURLs';
|
||||
import { useStore } from '../../store/store';
|
||||
import { JournalTimetableFilter } from '../../types/Journal/JournalTimetablesTypes';
|
||||
import ActionButton from '../Global/ActionButton.vue';
|
||||
import SelectBox from '../Global/SelectBox.vue';
|
||||
import { JournalFilterSection } from '../../scripts/enums/JournalFilterType';
|
||||
import { JournalFilter } from '../../scripts/types/JournalTimetablesTypes';
|
||||
|
||||
export default defineComponent({
|
||||
components: { SelectBox, ActionButton },
|
||||
emits: ['onSearchConfirm', 'onOptionsReset'],
|
||||
emits: ['onSearchConfirm', 'onOptionsReset', 'onRefreshData'],
|
||||
mixins: [imageMixin, keyMixin],
|
||||
|
||||
props: {
|
||||
@@ -109,7 +124,7 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
filters: {
|
||||
type: Array as PropType<JournalTimetableFilter[]>,
|
||||
type: Array as PropType<JournalFilter[]>,
|
||||
default: [],
|
||||
},
|
||||
|
||||
@@ -122,11 +137,17 @@ export default defineComponent({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
optionsType: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
showOptions: false,
|
||||
JournalFilterSection,
|
||||
|
||||
driverSuggestions: [] as string[],
|
||||
dispatcherSuggestions: [] as string[],
|
||||
@@ -142,7 +163,8 @@ export default defineComponent({
|
||||
return {
|
||||
searchersValues: inject('searchersValues') as { [key: string]: string },
|
||||
sorterActive: inject('sorterActive') as { id: string | number; dir: number },
|
||||
journalFilterActive: inject('journalFilterActive') as JournalTimetableFilter,
|
||||
// journalFilterActive: inject('journalFilterActive') as JournalFilter,
|
||||
filterList: inject('filterList') as JournalFilter[] | undefined,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -162,7 +184,8 @@ export default defineComponent({
|
||||
watch: {
|
||||
async driverStatsName(value: string) {
|
||||
await this.fetchDriverStats();
|
||||
this.store.currentStatsTab = value ? 'driver' : 'daily';
|
||||
|
||||
// if (value) this.store.currentStatsTab = 'driver';
|
||||
},
|
||||
|
||||
async 'searchersValues.search-driver'(value: string | undefined) {
|
||||
@@ -206,6 +229,10 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
|
||||
refreshData() {
|
||||
this.$emit('onRefreshData');
|
||||
},
|
||||
|
||||
startSearchTimeout(type: 'driver' | 'dispatcher', value: string) {
|
||||
if (this[`${type}Suggestions`].includes(value)) return;
|
||||
|
||||
@@ -233,18 +260,17 @@ export default defineComponent({
|
||||
});
|
||||
},
|
||||
|
||||
focusEnd() {
|
||||
console.log('focus end');
|
||||
},
|
||||
|
||||
onSorterChange(item: { id: string | number; value: string }) {
|
||||
this.sorterActive.id = item.id;
|
||||
this.sorterActive.dir = -1;
|
||||
this.$emit('onSearchConfirm');
|
||||
},
|
||||
|
||||
onFilterChange(filter: JournalTimetableFilter) {
|
||||
this.journalFilterActive = filter;
|
||||
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');
|
||||
},
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<template>
|
||||
<div class="journal-stats" v-show="!store.isOffline">
|
||||
<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) }}
|
||||
@@ -14,7 +16,7 @@
|
||||
|
||||
<div class="stats-tab" v-show="areStatsOpen">
|
||||
<keep-alive>
|
||||
<JournalDailyStats v-if="store.currentStatsTab == 'daily'" ref="dailyStatsComp" />
|
||||
<JournalDailyStats v-if="store.currentStatsTab == 'daily'" @toggleStatsOpen="toggleStatsOpen" />
|
||||
<JournalDriverStats v-else-if="store.currentStatsTab == 'driver'" />
|
||||
</keep-alive>
|
||||
</div>
|
||||
@@ -22,21 +24,21 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, KeepAlive, onActivated, onDeactivated, reactive, Ref, ref, watch } from 'vue';
|
||||
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 dailyStatsComp: Ref<InstanceType<typeof JournalDailyStats> | null> = ref(null);
|
||||
|
||||
const areStatsOpen = ref(true);
|
||||
const lastClickedTab = ref('daily');
|
||||
const lastDailyStatsOpen = ref(false);
|
||||
const areStatsOpen = ref(false);
|
||||
const lastClickedTab: Ref<'daily' | 'driver' | null> = ref(null);
|
||||
|
||||
let data = reactive({
|
||||
tabs: [
|
||||
@@ -47,38 +49,44 @@ let data = reactive({
|
||||
{
|
||||
name: 'driver',
|
||||
titlePath: 'journal.driver-stats-title',
|
||||
inactive: true,
|
||||
// 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 (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;
|
||||
}
|
||||
|
||||
onActivated(() => {
|
||||
dailyStatsComp.value?.startFetchingDailyStats();
|
||||
});
|
||||
|
||||
onDeactivated(() => {
|
||||
dailyStatsComp.value?.stopFetchingDailyStats();
|
||||
});
|
||||
function toggleStatsOpen(open: boolean) {
|
||||
areStatsOpen.value = open;
|
||||
}
|
||||
|
||||
watch(
|
||||
computed(() => store.driverStatsData),
|
||||
(statsData) => {
|
||||
data.tabs[1].inactive = statsData ? false : true;
|
||||
|
||||
lastClickedTab.value = statsData ? 'driver' : 'daily';
|
||||
if (statsData) areStatsOpen.value = true;
|
||||
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>
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<div>
|
||||
<transition name="status-anim" mode="out-in">
|
||||
<div :key="dataStatus">
|
||||
<div class="journal_warning" v-if="store.isOffline">
|
||||
{{ $t('app.offline') }}
|
||||
</div>
|
||||
|
||||
<Loading v-else-if="dataStatus == DataStatus.Loading" />
|
||||
|
||||
<div v-else-if="dataStatus == DataStatus.Error" class="journal_warning error">
|
||||
{{ $t('app.error') }}
|
||||
</div>
|
||||
|
||||
<div v-else-if="timetableHistory.length == 0" class="journal_warning">
|
||||
{{ $t('app.no-result') }}
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<TimetableHistoryList :timetableHistory="timetableHistory" />
|
||||
|
||||
<AddDataButton
|
||||
:list="timetableHistory"
|
||||
:scrollDataLoaded="scrollDataLoaded"
|
||||
:scrollNoMoreData="scrollNoMoreData"
|
||||
@addHistoryData="addHistoryData"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<div class="journal_warning" v-if="scrollNoMoreData">{{ $t('journal.no-further-data') }}</div>
|
||||
<div class="journal_warning" v-else-if="!scrollDataLoaded">{{ $t('journal.loading-further-data') }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
import { DataStatus } from '../../../scripts/enums/DataStatus';
|
||||
import { TimetableHistory } from '../../../scripts/interfaces/api/TimetablesAPIData';
|
||||
import { useStore } from '../../../store/store';
|
||||
|
||||
import Loading from '../../Global/Loading.vue';
|
||||
import ProgressBar from '../../Global/ProgressBar.vue';
|
||||
import AddDataButton from '../../Global/AddDataButton.vue';
|
||||
import TimetableHistoryList from './TimetableHistoryList.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: { ProgressBar, Loading, AddDataButton, TimetableHistoryList },
|
||||
|
||||
props: {
|
||||
timetableHistory: {
|
||||
type: Array as PropType<TimetableHistory[]>,
|
||||
required: true,
|
||||
},
|
||||
scrollNoMoreData: {
|
||||
type: Boolean,
|
||||
},
|
||||
scrollDataLoaded: {
|
||||
type: Boolean,
|
||||
},
|
||||
addHistoryData: {
|
||||
type: Function as PropType<() => void>,
|
||||
},
|
||||
dataStatus: {
|
||||
type: Number as PropType<DataStatus>,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
DataStatus,
|
||||
store: useStore(),
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../../styles/JournalSection.scss';
|
||||
@import '../../../styles/animations.scss';
|
||||
</style>
|
||||
@@ -0,0 +1,165 @@
|
||||
<template>
|
||||
<div class="item-extra" v-if="timetable.stockString && timetable.stockMass && showExtraInfo">
|
||||
<hr />
|
||||
|
||||
<div class="stock-specs">
|
||||
<span class="badge">
|
||||
<span>{{ $t('journal.dispatcher-name') }}</span>
|
||||
<span>{{ timetable.authorName }}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="stock-specs">
|
||||
<span class="badge">
|
||||
<span>{{ $t('journal.stock-max-speed') }}</span>
|
||||
<span>{{ timetable.maxSpeed }}km/h</span>
|
||||
</span>
|
||||
|
||||
<span class="badge">
|
||||
<span>{{ $t('journal.stock-length') }}</span>
|
||||
<span>
|
||||
{{ currentHistoryIndex == 0 ? timetable.stockLength : stockHistory[currentHistoryIndex].stockLength || timetable.stockLength }}m
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span class="badge">
|
||||
<span>{{ $t('journal.stock-mass') }}</span>
|
||||
<span>
|
||||
{{
|
||||
Math.floor((currentHistoryIndex == 0 ? timetable.stockMass! : stockHistory[currentHistoryIndex].stockMass || timetable.stockMass) / 1000)
|
||||
}}t
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Historia zmian w składzie -->
|
||||
<div class="stock-history" v-if="stockHistory.length > 1">
|
||||
<button class="btn--action" v-for="(sh, i) in stockHistory" :data-checked="i == currentHistoryIndex" @click.stop="currentHistoryIndex = i">
|
||||
{{ sh.updatedAt }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- <StockList :trainStockList="currentHistoryIndex == 0 ? timetable.stockString : stockHistory[currentHistoryIndex].stockString).split(';')" /> -->
|
||||
<StockList :trainStockList="(currentHistoryIndex == 0 ? timetable.stockString : stockHistory[currentHistoryIndex].stockString).split(';') " />
|
||||
|
||||
<!-- <ul class="stock-list">
|
||||
<li
|
||||
v-for="(stockName, i) in (currentHistoryIndex == 0 ? timetable.stockString : stockHistory[currentHistoryIndex].stockString).split(';')"
|
||||
:key="i"
|
||||
>
|
||||
<div>{{ stockName.split(':')[0].split('_').splice(0, 2).join(' ') }} {{ stockName.split(':')[1] }}</div>
|
||||
<TrainThumbnail :name="stockName" />
|
||||
</li>
|
||||
</ul> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent } from 'vue';
|
||||
import { TimetableHistory } from '../../../scripts/interfaces/api/TimetablesAPIData';
|
||||
import imageMixin from '../../../mixins/imageMixin';
|
||||
import TrainThumbnail from '../../Global/TrainThumbnail.vue';
|
||||
import StockList from '../../Global/StockList.vue';
|
||||
|
||||
export default defineComponent({
|
||||
mixins: [imageMixin],
|
||||
props: {
|
||||
showExtraInfo: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
timetable: {
|
||||
type: Object as PropType<TimetableHistory>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentHistoryIndex: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
stockHistory() {
|
||||
return this.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,
|
||||
};
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onImageError(e: Event) {
|
||||
const imageEl = e.target as HTMLImageElement;
|
||||
imageEl.src = this.getImage('unknown.png');
|
||||
},
|
||||
},
|
||||
components: { TrainThumbnail, StockList },
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../../styles/variables.scss';
|
||||
@import '../../../styles/responsive.scss';
|
||||
@import '../../../styles/badge.scss';
|
||||
|
||||
.item-extra {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
.stock-history {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5em;
|
||||
margin-top: 1em;
|
||||
|
||||
button[data-checked='true'] {
|
||||
color: $accentCol;
|
||||
}
|
||||
}
|
||||
|
||||
.stock-specs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5em;
|
||||
margin-top: 0.5em;
|
||||
|
||||
.badge {
|
||||
margin: 0;
|
||||
|
||||
span:last-child {
|
||||
color: black;
|
||||
background-color: $accentCol;
|
||||
}
|
||||
}
|
||||
|
||||
@include smallScreen() {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
ul.stock-list {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
overflow: auto;
|
||||
|
||||
padding-bottom: 0.5em;
|
||||
|
||||
li > div {
|
||||
margin: 1em 0;
|
||||
|
||||
text-align: center;
|
||||
color: #aaa;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<div class="item-general">
|
||||
<span
|
||||
class="general-train"
|
||||
tabindex="0"
|
||||
@click.stop="showTimetable(timetable, $event.currentTarget)"
|
||||
@keydown.enter="showTimetable(timetable, $event.currentTarget)"
|
||||
>
|
||||
<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"
|
||||
>{{
|
||||
new Date(timetable.createdAt).getTime() - new Date(timetable.beginDate).getTime() < 0
|
||||
? localeDateTime(timetable.createdAt, $i18n.locale)
|
||||
: localeDateTime(timetable.beginDate, $i18n.locale)
|
||||
}}
|
||||
</b>
|
||||
|
||||
<b
|
||||
class="info-badge"
|
||||
: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>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent } from 'vue';
|
||||
import { TimetableHistory } from '../../../scripts/interfaces/api/TimetablesAPIData';
|
||||
|
||||
import dateMixin from '../../../mixins/dateMixin';
|
||||
import modalTrainMixin from '../../../mixins/modalTrainMixin';
|
||||
import styleMixin from '../../../mixins/styleMixin';
|
||||
|
||||
export default defineComponent({
|
||||
mixins: [dateMixin, modalTrainMixin, styleMixin],
|
||||
|
||||
props: {
|
||||
timetable: {
|
||||
type: Object as PropType<TimetableHistory>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
showTimetable(timetable: TimetableHistory, target: EventTarget | null) {
|
||||
if (timetable?.terminated) return;
|
||||
|
||||
this.selectModalTrain(timetable.driverName + timetable.trainNo.toString(), target);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../../styles/responsive.scss';
|
||||
@import '../../../styles/badge.scss';
|
||||
|
||||
.item-general {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
|
||||
gap: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
|
||||
@include smallScreen() {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.info-date {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.info-badge {
|
||||
padding: 0.05em 0.35em;
|
||||
color: black;
|
||||
|
||||
&.terminated {
|
||||
background-color: salmon;
|
||||
}
|
||||
|
||||
&.fulfilled {
|
||||
background-color: lightgreen;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: lightblue;
|
||||
}
|
||||
}
|
||||
|
||||
.general-train {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.25em;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<ul class="journal-list">
|
||||
<transition-group name="list-anim">
|
||||
<li
|
||||
v-for="{ timetable, showExtraInfo } in computedTimetableHistory"
|
||||
class="journal_item"
|
||||
:key="timetable.id"
|
||||
@click="showExtraInfo.value = !showExtraInfo.value"
|
||||
>
|
||||
<div class="journal_item-info">
|
||||
<!-- General -->
|
||||
<TimetableGeneral :timetable="timetable" />
|
||||
<!-- Route -->
|
||||
<span class="item-route">
|
||||
<b>{{ timetable.route.replace('|', ' - ') }}</b>
|
||||
</span>
|
||||
|
||||
<hr />
|
||||
<!-- Stops -->
|
||||
<TimetableStops :timetable="timetable" :showExtraInfo="showExtraInfo.value" />
|
||||
<!-- Status -->
|
||||
<TimetableStatus :timetable="timetable" />
|
||||
|
||||
<button class="btn--option btn--show">
|
||||
{{ $t('journal.stock-info') }}
|
||||
<img :src="getIcon(`arrow-${showExtraInfo.value ? 'asc' : 'desc'}`)" alt="Arrow" />
|
||||
</button>
|
||||
<!-- Extra -->
|
||||
<TimetableExtra :timetable="timetable" :showExtraInfo="showExtraInfo.value" />
|
||||
</div>
|
||||
</li>
|
||||
</transition-group>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent, ref } from 'vue';
|
||||
import imageMixin from '../../../mixins/imageMixin';
|
||||
import { TimetableHistory } from '../../../scripts/interfaces/api/TimetablesAPIData';
|
||||
|
||||
import TimetableGeneral from './TimetableGeneral.vue';
|
||||
import TimetableStops from './TimetableStops.vue';
|
||||
import TimetableStatus from './TimetableStatus.vue';
|
||||
import TimetableExtra from './TimetableExtra.vue';
|
||||
|
||||
export default defineComponent({
|
||||
mixins: [imageMixin],
|
||||
props: {
|
||||
timetableHistory: {
|
||||
type: Array as PropType<TimetableHistory[]>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
computedTimetableHistory() {
|
||||
return this.timetableHistory.map((timetable) => ({
|
||||
timetable,
|
||||
showExtraInfo: ref(false),
|
||||
}));
|
||||
},
|
||||
},
|
||||
methods: {},
|
||||
components: { TimetableGeneral, TimetableStops, TimetableStatus, TimetableExtra },
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../../styles/variables.scss';
|
||||
@import '../../../styles/responsive.scss';
|
||||
@import '../../../styles/JournalSection.scss';
|
||||
|
||||
.btn--show {
|
||||
display: flex;
|
||||
font-weight: bold;
|
||||
padding: 0.2em 0.45em;
|
||||
|
||||
img {
|
||||
height: 1.3em;
|
||||
}
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 0.25em 0;
|
||||
}
|
||||
|
||||
@include smallScreen {
|
||||
.journal_item-info {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.item-route {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.btn--show {
|
||||
margin: 1em auto 0 auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<div class="item-status" style="margin: 0.5em 0">
|
||||
<ProgressBar
|
||||
:progressPercent="~~((timetable.currentDistance / timetable.routeDistance) * 100)"
|
||||
:progressType="!timetable.fulfilled && timetable.terminated ? 'abandoned' : ''"
|
||||
/>
|
||||
|
||||
<span>
|
||||
<span :style="{ color: timetable.fulfilled ? 'lightgreen' : timetable.terminated ? 'salmon' : '' }">
|
||||
{{ timetable.currentDistance + ' km' }}
|
||||
</span>
|
||||
<span> / </span>
|
||||
<span class="text--primary">{{ timetable.routeDistance }} km</span>
|
||||
|
|
||||
<span class="text--grayed">{{ timetable.confirmedStopsCount }}/{{ timetable.allStopsCount }}</span>
|
||||
</span>
|
||||
|
||||
<span class="text--grayed" v-if="timetable.currentSceneryName">
|
||||
<b>
|
||||
{{ $t(`journal.${timetable.terminated ? 'last-seen-at' : 'currently-at'}`) }}
|
||||
{{ timetable.currentSceneryName.replace(/.[a-zA-Z0-9]+.sc/, '') }}
|
||||
|
||||
<span v-if="timetable.currentLocation[0] || timetable.currentLocation[1]">(</span>
|
||||
|
||||
<span v-if="timetable.currentLocation[1]">
|
||||
{{ $t('journal.timetable-location-route') }} {{ timetable.currentLocation[1] }}
|
||||
</span>
|
||||
|
||||
<span v-else-if="timetable.currentLocation[0]">
|
||||
{{ $t('journal.timetable-location-signal') }} {{ timetable.currentLocation[0] }}
|
||||
</span>
|
||||
|
||||
<span v-if="timetable.currentLocation[0] || timetable.currentLocation[1]">)</span>
|
||||
</b>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent } from 'vue';
|
||||
import { TimetableHistory } from '../../../scripts/interfaces/api/TimetablesAPIData';
|
||||
import ProgressBar from '../../Global/ProgressBar.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: { ProgressBar },
|
||||
props: {
|
||||
timetable: {
|
||||
type: Object as PropType<TimetableHistory>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../../styles/responsive.scss';
|
||||
|
||||
.item-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5em;
|
||||
|
||||
@include smallScreen() {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<div class="stop-list" v-if="showExtraInfo == true">
|
||||
<span
|
||||
v-for="(stop, i) in timetableStops.filter((_, i) =>
|
||||
!showExtraInfo ? i == 0 || i == timetableStops.length - 1 : true
|
||||
)"
|
||||
class="stop-list-item"
|
||||
:key="stop.stopName"
|
||||
:data-confirmed="stop.confirmed"
|
||||
>
|
||||
<span v-if="i > 0">
|
||||
>
|
||||
<span v-if="!showExtraInfo && i == 1 && timetableStops.length > 2">
|
||||
... (+{{ timetableStops.length - 2 }}) >
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span class="stop-name">{{ stop.stopName }}</span>
|
||||
<span v-html="stop.html"></span>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent } from 'vue';
|
||||
import dateMixin from '../../../mixins/dateMixin';
|
||||
|
||||
import { TimetableHistory } from '../../../scripts/interfaces/api/TimetablesAPIData';
|
||||
|
||||
export default defineComponent({
|
||||
mixins: [dateMixin],
|
||||
|
||||
props: {
|
||||
showExtraInfo: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
|
||||
timetable: {
|
||||
type: Object as PropType<TimetableHistory>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
timetableStops() {
|
||||
const timetable = this.timetable;
|
||||
|
||||
const stopNames = timetable.sceneriesString.split('%');
|
||||
|
||||
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.endDate, this.$i18n.locale)}</s>`
|
||||
: ''
|
||||
} <span>${this.localeTime(timetable.scheduledEndDate, this.$i18n.locale)}</span>)`;
|
||||
|
||||
return stopNames.map((stopName, i) => {
|
||||
const confirmed = i < timetable.confirmedStopsCount;
|
||||
if (i == 0) return { stopName, html: beginDateHTML, confirmed };
|
||||
if (i == stopNames.length - 1) return { stopName, html: endDateHTML, confirmed };
|
||||
|
||||
const departureDateScheduled = this.stringToDate(timetable.checkpointDeparturesScheduled?.at(i));
|
||||
const departureDateReal = this.stringToDate(timetable.checkpointDepartures?.at(i));
|
||||
const arrivalDateScheduled = this.stringToDate(timetable.checkpointArrivalsScheduled?.at(i));
|
||||
const arrivalDateReal = this.stringToDate(timetable.checkpointArrivals?.at(i));
|
||||
const arrivalHTML =
|
||||
(arrivalDateReal && arrivalDateScheduled && arrivalDateReal?.getTime() != arrivalDateScheduled?.getTime()
|
||||
? `<s class="text--grayed">${this.parseDateToTimeString(arrivalDateScheduled)}</s> `
|
||||
: '') + this.parseDateToTimeString(arrivalDateReal || arrivalDateScheduled);
|
||||
const departureHTML =
|
||||
(departureDateReal &&
|
||||
departureDateScheduled &&
|
||||
departureDateReal?.getTime() != departureDateScheduled?.getTime()
|
||||
? `<s class="text--grayed">${this.parseDateToTimeString(departureDateScheduled)}</s> `
|
||||
: '') + this.parseDateToTimeString(departureDateReal || departureDateScheduled);
|
||||
let html = `${arrivalHTML}${departureHTML ? ` / ${departureHTML}` : ''}`;
|
||||
if (html) html = ` (${html})`;
|
||||
return { stopName, html, confirmed };
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.stop-list {
|
||||
word-wrap: break-word;
|
||||
gap: 0.25em;
|
||||
font-size: 0.95em;
|
||||
|
||||
color: #adadad;
|
||||
|
||||
&-item[data-confirmed='true'] {
|
||||
color: lightgreen;
|
||||
|
||||
.stop-name {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,321 +0,0 @@
|
||||
<template>
|
||||
<ul class="journal-list">
|
||||
<li
|
||||
v-for="{ timetable, sceneryList, ...item } in computedTimetableHistory"
|
||||
class="journal_item"
|
||||
:key="timetable.timetableId"
|
||||
>
|
||||
<div class="journal_item-info">
|
||||
<div class="info-top">
|
||||
<span
|
||||
tabindex="0"
|
||||
@click="showTimetable(timetable)"
|
||||
@keydown.enter="showTimetable(timetable)"
|
||||
style="cursor: pointer"
|
||||
>
|
||||
<b class="text--primary">{{ timetable.trainCategoryCode }} </b>
|
||||
<b>{{ timetable.trainNo }}</b>
|
||||
| <span>{{ timetable.driverName }}</span> |
|
||||
<span class="text--grayed">#{{ timetable.timetableId }}</span>
|
||||
</span>
|
||||
|
||||
<span>
|
||||
<b class="info-date">{{ localeDay(timetable.beginDate, $i18n.locale) }}</b>
|
||||
<b
|
||||
class="info-status"
|
||||
:class="{
|
||||
fulfilled: timetable.fulfilled || timetable.currentDistance >= timetable.routeDistance * 0.9,
|
||||
terminated: timetable.terminated && !timetable.fulfilled,
|
||||
active: !timetable.terminated,
|
||||
}"
|
||||
>
|
||||
{{
|
||||
!timetable.terminated
|
||||
? $t('journal.timetable-active')
|
||||
: timetable.fulfilled || timetable.currentDistance >= timetable.routeDistance * 0.9
|
||||
? $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" :key="scenery.name" :class="{ confirmed: scenery.confirmed }">
|
||||
<span v-if="i > 0"> ></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-if="i == sceneryList.length - 1" v-html="scenery.endDateHTML"> </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>
|
||||
</div>
|
||||
|
||||
<button
|
||||
v-if="timetable.stockString"
|
||||
class="btn--option btn--show"
|
||||
@click="item.showStock.value = !item.showStock.value"
|
||||
>
|
||||
{{ $t('journal.stock-info') }}
|
||||
<img :src="getIcon(`arrow-${item.showStock.value ? 'asc' : 'desc'}`)" alt="Arrow" />
|
||||
</button>
|
||||
|
||||
<div class="info-extended" v-if="timetable.stockString && item.showStock.value">
|
||||
<hr />
|
||||
|
||||
<div>
|
||||
<span class="badge info-badge">
|
||||
<span>{{ $t('journal.stock-max-speed') }}</span>
|
||||
<span>{{ timetable.maxSpeed }}km/h</span>
|
||||
</span>
|
||||
|
||||
<span class="badge info-badge">
|
||||
<span>{{ $t('journal.stock-length') }}</span>
|
||||
<span>{{ timetable.stockLength }}m</span>
|
||||
</span>
|
||||
|
||||
<span class="badge info-badge">
|
||||
<span>{{ $t('journal.stock-mass') }}</span>
|
||||
<span>{{ Math.floor(timetable.stockMass! / 1000) }}t</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<ul class="stock-list">
|
||||
<li v-for="(car, i) in timetable.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>
|
||||
</ul>
|
||||
</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 { TimetableHistory } from '../../scripts/interfaces/api/TimetablesAPIData';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
timetableHistory: {
|
||||
type: Array as PropType<TimetableHistory[]>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
mixins: [dateMixin, imageMixin, modalTrainMixin],
|
||||
|
||||
computed: {
|
||||
computedTimetableHistory() {
|
||||
return this.timetableHistory.map((timetable) => ({
|
||||
timetable,
|
||||
sceneryList: this.getSceneryList(timetable),
|
||||
showStock: ref(false),
|
||||
}));
|
||||
},
|
||||
},
|
||||
|
||||
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>)`;
|
||||
|
||||
const abandonedDateHTML = ` (porz. ${this.localeTime(
|
||||
timetable.fulfilled ? timetable.scheduledEndDate : timetable.endDate,
|
||||
this.$i18n.locale
|
||||
)})`;
|
||||
|
||||
return { name, confirmed: i < timetable.confirmedStopsCount, beginDateHTML, endDateHTML, abandonedDateHTML };
|
||||
});
|
||||
},
|
||||
|
||||
showTimetable(timetable: TimetableHistory) {
|
||||
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/variables.scss';
|
||||
@import '../../styles/responsive.scss';
|
||||
@import '../../styles/badge.scss';
|
||||
@import '../../styles/JournalSection.scss';
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
&-top {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&-route {
|
||||
margin: 0.25em 0;
|
||||
}
|
||||
|
||||
&-extended {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
.info-badge {
|
||||
span:last-child {
|
||||
color: black;
|
||||
background-color: $accentCol;
|
||||
}
|
||||
}
|
||||
|
||||
@include smallScreen {
|
||||
.info-top {
|
||||
flex-direction: column;
|
||||
|
||||
span {
|
||||
margin: 0.1em auto;
|
||||
}
|
||||
}
|
||||
|
||||
.info-extended {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.info-route {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.btn--show {
|
||||
margin: 1em auto 0 auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,33 +1,61 @@
|
||||
<template>
|
||||
<section class="scenery-dispatchers-history scenery-section">
|
||||
<Loading v-if="dataStatus != 2" />
|
||||
<section class="scenery-table-section">
|
||||
<Loading v-if="dataStatus != DataStatus.Loaded && historyList.length == 0" />
|
||||
<div class="no-history" v-else-if="historyList.length == 0">{{ $t('scenery.history-list-empty') }}</div>
|
||||
|
||||
<div class="list-warning" v-else-if="dispatcherHistoryList.length == 0">{{ $t('scenery.history-list-empty') }}</div>
|
||||
<table class="scenery-history-table" v-else="historyList.length">
|
||||
<thead>
|
||||
<th>{{ $t('scenery.dispatchers-history-hash') }}</th>
|
||||
<th>{{ $t('scenery.dispatchers-history-dispatcher') }}</th>
|
||||
<th>{{ $t('scenery.dispatchers-history-level') }}</th>
|
||||
<th>{{ $t('scenery.dispatchers-history-rate') }}</th>
|
||||
<th>{{ $t('scenery.dispatchers-history-date') }}</th>
|
||||
</thead>
|
||||
|
||||
<ul class="history-list" v-else>
|
||||
<li class="list-item" v-for="historyItem in dispatcherHistoryList">
|
||||
<div>
|
||||
<router-link :to="`/journal/dispatchers?dispatcherName=${historyItem.dispatcherName}`">
|
||||
<span class="text--grayed">#{{ historyItem.stationHash }} </span>
|
||||
<b>{{ historyItem.dispatcherName }}</b>
|
||||
</router-link>
|
||||
</div>
|
||||
<tbody>
|
||||
<tr v-for="historyItem in historyList">
|
||||
<td>#{{ historyItem.stationHash }}</td>
|
||||
<td>
|
||||
<router-link :to="`/journal/dispatchers?dispatcherName=${historyItem.dispatcherName}`">
|
||||
<b>{{ historyItem.dispatcherName }}</b>
|
||||
</router-link>
|
||||
</td>
|
||||
<td>
|
||||
<b
|
||||
v-if="historyItem.dispatcherLevel !== null"
|
||||
class="level-badge dispatcher"
|
||||
:style="calculateExpStyle(historyItem.dispatcherLevel, historyItem.dispatcherIsSupporter)"
|
||||
>
|
||||
{{ historyItem.dispatcherLevel >= 2 ? historyItem.dispatcherLevel : 'L' }}
|
||||
</b>
|
||||
</td>
|
||||
<td class="text--primary">
|
||||
<b>{{ historyItem.dispatcherRate }}</b>
|
||||
</td>
|
||||
<td style="min-width: 300px">
|
||||
<div v-if="historyItem.timestampTo">
|
||||
<b>{{ $d(historyItem.timestampFrom) }}</b>
|
||||
|
||||
<div v-if="historyItem.timestampTo">
|
||||
<b>{{ $d(historyItem.timestampFrom) }}</b>
|
||||
{{ timestampToString(historyItem.timestampFrom) }}
|
||||
- {{ timestampToString(historyItem.timestampTo) }} ({{ calculateDuration(historyItem.currentDuration) }})
|
||||
</div>
|
||||
|
||||
{{ timestampToString(historyItem.timestampFrom) }}
|
||||
- {{ timestampToString(historyItem.timestampTo) }} ({{ calculateDuration(historyItem.currentDuration) }})
|
||||
</div>
|
||||
|
||||
<div class="dispatcher-online" v-else>
|
||||
{{ $t('journal.online-since') }}
|
||||
<b>{{ timestampToString(historyItem.timestampFrom) }}</b>
|
||||
({{ calculateDuration(historyItem.currentDuration) }})
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="dispatcher-online" v-else>
|
||||
{{ $t('journal.online-since') }}
|
||||
<b>{{ timestampToString(historyItem.timestampFrom) }}</b>
|
||||
({{ calculateDuration(historyItem.currentDuration) }})
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<div class="bottom-info">
|
||||
<button class="btn btn--option" v-if="historyList.length > 0" @click="navigateToHistory">
|
||||
{{ $t('scenery.bottom-info') }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -39,37 +67,53 @@ import { DispatcherHistory } from '../../scripts/interfaces/api/DispatchersAPIDa
|
||||
import Station from '../../scripts/interfaces/Station';
|
||||
import { URLs } from '../../scripts/utils/apiURLs';
|
||||
import Loading from '../Global/Loading.vue';
|
||||
import styleMixin from '../../mixins/styleMixin';
|
||||
import listObserverMixin from '../../mixins/listObserverMixin';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SceneryDispatchersHistory',
|
||||
mixins: [dateMixin],
|
||||
mixins: [dateMixin, styleMixin, listObserverMixin],
|
||||
props: {
|
||||
station: {
|
||||
type: Object as PropType<Station>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
dispatcherHistoryList: [] as DispatcherHistory[],
|
||||
historyList: [] as DispatcherHistory[],
|
||||
dataStatus: DataStatus.Loading,
|
||||
DataStatus,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.fetchAPIData();
|
||||
|
||||
async activated() {
|
||||
// if (this.historyList.length == 0) {
|
||||
const fetchedHistory = await this.fetchAPIData();
|
||||
if (fetchedHistory) this.historyList = fetchedHistory;
|
||||
// }
|
||||
},
|
||||
|
||||
methods: {
|
||||
async fetchAPIData(countFrom = 0, countLimit = 30) {
|
||||
async fetchAPIData(countFrom = 0, countLimit = 30): Promise<DispatcherHistory[] | null> {
|
||||
try {
|
||||
this.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;
|
||||
|
||||
this.dispatcherHistoryList = historyAPIData;
|
||||
this.dataStatus = DataStatus.Loaded;
|
||||
return historyAPIData;
|
||||
} catch (error) {
|
||||
this.dataStatus = DataStatus.Error;
|
||||
console.error(error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
navigateToHistory() {
|
||||
this.$router.push(`/journal/dispatchers?sceneryName=${this.station.name}`);
|
||||
},
|
||||
},
|
||||
components: { Loading },
|
||||
});
|
||||
@@ -77,23 +121,10 @@ export default defineComponent({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../styles/responsive.scss';
|
||||
@import '../../styles/SceneryView/styles.scss';
|
||||
@import '../../styles/sceneryViewTables.scss';
|
||||
|
||||
.history-list {
|
||||
padding: 0 0.5em;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
|
||||
text-align: left;
|
||||
background-color: #353535;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em 0;
|
||||
|
||||
line-height: 1.5em;
|
||||
.level-badge {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.dispatcher-online {
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
<template>
|
||||
<section class="info-header">
|
||||
<a class="scenery-name" :href="station.generalInfo?.url">
|
||||
<a class="scenery-name" :href="station.generalInfo?.url" target="_blank">
|
||||
{{ station.name }}
|
||||
</a>
|
||||
|
||||
<div class="scenery-abbrev">
|
||||
{{ $t('scenery.abbrev') }} <b>{{ station.generalInfo?.abbr }}</b>
|
||||
</div>
|
||||
|
||||
<div class="scenery-hash" v-if="station.onlineInfo?.hash">#{{ station.onlineInfo.hash }}</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -26,18 +30,26 @@ export default defineComponent({
|
||||
@import '../../styles/variables.scss';
|
||||
@import '../../styles/responsive.scss';
|
||||
|
||||
.info-header {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.scenery-name {
|
||||
font-weight: bold;
|
||||
|
||||
position: relative;
|
||||
|
||||
font-size: 3em;
|
||||
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.scenery-abbrev {
|
||||
font-size: 1.3em;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.scenery-hash {
|
||||
margin-top: 0.5em;
|
||||
color: #aaa;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div class="scenery-info">
|
||||
<section v-if="!timetableOnly">
|
||||
<div class="info-general" v-if="station.generalInfo">
|
||||
<scenery-info-icons :station="station" />
|
||||
<div class="scenery-info-general" v-if="station.generalInfo">
|
||||
<SceneryInfoIcons :station="station" />
|
||||
|
||||
<div class="general-list">
|
||||
<div class="scenery-general-list">
|
||||
<span>
|
||||
<b>{{ $t('availability.title') }}:</b> {{ $t(`availability.${station.generalInfo.availability}`) }}
|
||||
|
||||
@@ -26,36 +26,31 @@
|
||||
</span>
|
||||
<span v-if="station.generalInfo.project">
|
||||
• <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>
|
||||
</div>
|
||||
|
||||
<scenery-info-routes :station="station" />
|
||||
<SceneryInfoRoutes :station="station" />
|
||||
|
||||
<div class="scenery-authors" v-if="station.generalInfo.authors && station.generalInfo.authors.length > 0">
|
||||
<b> {{ $t('scenery.authors-title', { authors: station.generalInfo.authors.length }, station.generalInfo.authors.length) }}: </b>
|
||||
{{ station.generalInfo.authors.join(', ') }}
|
||||
</div>
|
||||
|
||||
<br />
|
||||
<div class="scenery-topic" v-if="station.generalInfo.url">
|
||||
<a :href="station.generalInfo.url" target="_blank">
|
||||
> {{ $t('scenery.forum-topic', { name: station.name }) }} <
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin: 2em 0; height: 2px; background-color: white" />
|
||||
<div style="margin: 2em 0; height: 2px; background-color: white"></div>
|
||||
|
||||
<!-- info dispatcher -->
|
||||
<scenery-info-dispatcher :station="station" :onlineFrom="onlineFrom" />
|
||||
<SceneryInfoDispatcher :station="station" :onlineFrom="onlineFrom" />
|
||||
|
||||
<div class="info-lists">
|
||||
<!-- user list -->
|
||||
<scenery-info-user-list :station="station" />
|
||||
<SceneryInfoUserList :station="station" />
|
||||
|
||||
<!-- spawn list -->
|
||||
<scenery-info-spawn-list :station="station" />
|
||||
<SceneryInfoSpawnList :station="station" />
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@@ -124,11 +119,11 @@ h3.section-header {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.info-general {
|
||||
.scenery-info-general {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.general-list {
|
||||
.scenery-general-list {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@@ -21,18 +21,11 @@
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<span class="status-badge" v-if="station.onlineInfo && onlineFrom > 0">
|
||||
OD {{ new Date(onlineFrom).toLocaleTimeString('pl-PL', { hour: '2-digit', minute: '2-digit' }) }}
|
||||
</span>
|
||||
|
||||
<span class="status-badge" v-if="station.onlineInfo" :class="station.onlineInfo.statusID">
|
||||
{{ $t(`status.${station.onlineInfo.statusID}`) }}
|
||||
{{ station.onlineInfo.statusID == 'online' ? timestampToString(station.onlineInfo.statusTimestamp) : '' }}
|
||||
</span>
|
||||
|
||||
<span class="status-badge free" v-else>
|
||||
{{ $t('status.free') }}
|
||||
</span>
|
||||
<StationStatusBadge
|
||||
:statusID="station.onlineInfo?.statusID"
|
||||
:isOnline="station.onlineInfo ? true : false"
|
||||
:statusTimestamp="station.onlineInfo?.statusTimestamp"
|
||||
/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -43,20 +36,21 @@ import imageMixin from '../../../mixins/imageMixin';
|
||||
import routerMixin from '../../../mixins/routerMixin';
|
||||
import styleMixin from '../../../mixins/styleMixin';
|
||||
import Station from '../../../scripts/interfaces/Station';
|
||||
import StationStatusBadge from '../../Global/StationStatusBadge.vue';
|
||||
|
||||
export default defineComponent({
|
||||
mixins: [styleMixin, dateMixin, routerMixin, imageMixin],
|
||||
props: {
|
||||
station: {
|
||||
type: Object as () => Station,
|
||||
default: {},
|
||||
mixins: [styleMixin, dateMixin, routerMixin, imageMixin],
|
||||
props: {
|
||||
station: {
|
||||
type: Object as () => Station,
|
||||
default: {},
|
||||
},
|
||||
onlineFrom: {
|
||||
type: Number,
|
||||
default: -1,
|
||||
},
|
||||
},
|
||||
|
||||
onlineFrom: {
|
||||
type: Number,
|
||||
default: -1,
|
||||
},
|
||||
},
|
||||
components: { StationStatusBadge }
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -104,3 +98,4 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,114 +1,129 @@
|
||||
<template>
|
||||
<section class="info-routes" v-if="station.generalInfo">
|
||||
<div class="routes one-way" v-if="station.generalInfo.routes.oneWay.length > 0">
|
||||
<b>{{ $t('scenery.one-way-routes') }}</b>
|
||||
|
||||
<ul class="routes-list">
|
||||
<li
|
||||
v-for="route in station.generalInfo.routes.oneWay"
|
||||
:class="{ 'no-catenary': !route.catenary, internal: route.isInternal }"
|
||||
>
|
||||
{{ route.name }}
|
||||
<b v-if="route.SBL">SBL</b>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="routes two-way" v-if="station.generalInfo.routes.twoWay.length > 0">
|
||||
<b>{{ $t('scenery.two-way-routes') }}</b>
|
||||
|
||||
<ul class="routes-list">
|
||||
<li
|
||||
v-for="route in station.generalInfo.routes.twoWay"
|
||||
:class="{ 'no-catenary': !route.catenary, internal: route.isInternal }"
|
||||
>
|
||||
{{ route.name }} <b v-if="route.SBL">SBL</b>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- <div
|
||||
class="route-info"
|
||||
:class="{ 'no-catenary': !route.catenary, internal: route.isInternal }"
|
||||
v-for="route in [...station.generalInfo.routes.oneWay, ...station.generalInfo.routes.twoWay].filter(
|
||||
(route) => route.name != '-'
|
||||
)"
|
||||
:key="route.name"
|
||||
:title="`Szlak ${route.name}: ${route.isInternal ? 'wewnętrzny' : 'zewnętrzny'}, ${
|
||||
route.tracks == 2 ? 'dwutorowy' : 'jednotorowy'
|
||||
}, ${route.catenary ? 'zelektryfikowany' : 'niezelektryfikowany'} z ${route.SBL ? 'SBL' : 'PBL'} ${
|
||||
route.TWB ? 'i blokadą dwukierunkową' : ''
|
||||
}`"
|
||||
> -->
|
||||
<!-- <span class="track-name">
|
||||
<b>{{ route.name }}</b>
|
||||
</span> -->
|
||||
<!--
|
||||
<span class="track-specs">
|
||||
{{ route.tracks }}tor
|
||||
<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" />
|
||||
<img v-if="route.SBL" :src="icons.trackSBL" alt="icon track sbl" />
|
||||
</span> -->
|
||||
<!-- </div> -->
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import Station from '../../../scripts/interfaces/Station';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
station: {
|
||||
type: Object as () => Station,
|
||||
default: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.info-routes {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.routes {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
|
||||
padding: 0.25em;
|
||||
}
|
||||
|
||||
ul.routes-list {
|
||||
margin: 0.45em 0.25em;
|
||||
display: flex;
|
||||
|
||||
li {
|
||||
background-color: #007599;
|
||||
|
||||
padding: 0.2em 0.25em;
|
||||
margin-left: 0.25em;
|
||||
|
||||
&.no-catenary {
|
||||
background-color: #686868;
|
||||
}
|
||||
|
||||
&.internal {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
b {
|
||||
color: var(--clr-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<section class="info-routes" v-if="station.generalInfo">
|
||||
<div class="routes one-way" v-if="station.generalInfo.routes.oneWay.length > 0">
|
||||
<b>{{ $t('scenery.one-way-routes') }}</b>
|
||||
|
||||
<ul class="routes-list">
|
||||
<li v-for="route in station.generalInfo.routes.oneWay" @click="setActiveShowLength(route.name)">
|
||||
<span :class="{ 'no-catenary': !route.catenary, internal: route.isInternal }"> {{ route.name }}</span>
|
||||
<span v-if="route.speed" class="speed">
|
||||
{{ activeShowLength.includes(route.name) ? route.length + 'm' : route.speed }}
|
||||
</span>
|
||||
<span v-if="route.SBL" class="sbl">SBL</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="routes two-way" v-if="station.generalInfo.routes.twoWay.length > 0">
|
||||
<b>{{ $t('scenery.two-way-routes') }}</b>
|
||||
|
||||
<ul class="routes-list">
|
||||
<li v-for="(route, i) in station.generalInfo.routes.twoWay" @click="setActiveShowLength(route.name)">
|
||||
<span :class="{ 'no-catenary': !route.catenary, internal: route.isInternal }">{{ route.name }}</span>
|
||||
<span v-if="route.speed" class="speed">
|
||||
{{ activeShowLength.includes(route.name) ? route.length + 'm' : route.speed }}
|
||||
</span>
|
||||
<span v-if="route.SBL" class="sbl">SBL</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import Station from '../../../scripts/interfaces/Station';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
station: {
|
||||
type: Object as () => Station,
|
||||
default: {},
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
setActiveShowLength(name: string) {
|
||||
if (this.activeShowLength.includes(name)) this.activeShowLength.splice(this.activeShowLength.indexOf(name), 1);
|
||||
else this.activeShowLength.push(name);
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
activeShowLength: [] as string[],
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.info-routes {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.routes {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
|
||||
padding: 0.25em;
|
||||
}
|
||||
|
||||
ul.routes-list {
|
||||
margin: 0.45em 0.25em;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
|
||||
li {
|
||||
margin: 0.5em 0.25em;
|
||||
cursor: pointer;
|
||||
|
||||
user-select: none;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
|
||||
span {
|
||||
padding: 0.2em 0.25em;
|
||||
background-color: #007599;
|
||||
font-weight: bold;
|
||||
|
||||
&.no-catenary {
|
||||
background-color: #686868;
|
||||
}
|
||||
|
||||
&.internal {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
&.speed {
|
||||
background-color: #404040;
|
||||
color: #cfcfcf;
|
||||
}
|
||||
|
||||
&.sbl {
|
||||
color: var(--clr-primary);
|
||||
background-color: #404040;
|
||||
}
|
||||
|
||||
&: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,52 +1,65 @@
|
||||
<template>
|
||||
<section class="info-spawn-list">
|
||||
<h3 class="spawn-header section-header">
|
||||
<img :src="getIcon('spawn')" alt="icon-spawn" />
|
||||
{{ $t('scenery.spawns') }}
|
||||
<span class="text--primary">{{ station.onlineInfo?.spawns.length || '0' }}</span>
|
||||
</h3>
|
||||
|
||||
<span v-if="station.onlineInfo">
|
||||
<span
|
||||
class="badge spawn"
|
||||
v-for="(spawn, i) in station.onlineInfo.spawns"
|
||||
:key="spawn.spawnName + station.onlineInfo?.dispatcherName + i"
|
||||
>
|
||||
<span class="spawn_name">{{ spawn.spawnName }}</span>
|
||||
<span class="spawn_length">{{ spawn.spawnLength }}m</span>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span class="badge spawn badge-none" v-if="!station.onlineInfo || station.onlineInfo.spawns.length == 0"
|
||||
>{{ $t('scenery.no-spawns') }}
|
||||
</span>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import imageMixin from '../../../mixins/imageMixin';
|
||||
import Station from '../../../scripts/interfaces/Station';
|
||||
|
||||
export default defineComponent({
|
||||
mixins: [imageMixin],
|
||||
|
||||
props: {
|
||||
station: {
|
||||
type: Object as () => Station,
|
||||
default: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../../styles/variables.scss';
|
||||
|
||||
.spawn {
|
||||
&_length {
|
||||
background: $accentCol;
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<section class="info-spawn-list">
|
||||
<h3 class="spawn-header section-header">
|
||||
<img :src="getIcon('spawn')" alt="icon-spawn" />
|
||||
{{ $t('scenery.spawns') }}
|
||||
<span class="text--primary">{{ station.onlineInfo?.spawns.length || '0' }}</span>
|
||||
</h3>
|
||||
|
||||
<span v-if="station.onlineInfo">
|
||||
<span
|
||||
class="badge spawn"
|
||||
v-for="(spawn, i) in sortedSpawns"
|
||||
: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>
|
||||
</span>
|
||||
|
||||
<span class="badge spawn badge-none" v-if="!station.onlineInfo || station.onlineInfo.spawns.length == 0"
|
||||
>{{ $t('scenery.no-spawns') }}
|
||||
</span>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import imageMixin from '../../../mixins/imageMixin';
|
||||
import Station from '../../../scripts/interfaces/Station';
|
||||
|
||||
export default defineComponent({
|
||||
mixins: [imageMixin],
|
||||
|
||||
props: {
|
||||
station: {
|
||||
type: Object as () => Station,
|
||||
default: {},
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
sortedSpawns() {
|
||||
return this.station.onlineInfo?.spawns.sort((s1, s2) => (s1.spawnLength < s2.spawnLength ? 1 : -1));
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../../styles/variables.scss';
|
||||
|
||||
.spawn {
|
||||
color: white;
|
||||
|
||||
&_length {
|
||||
background-color: #404040;
|
||||
color: #cfcfcf;
|
||||
}
|
||||
|
||||
&[data-electrified='true'] > &_name {
|
||||
background-color: #007599;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,133 +1,131 @@
|
||||
<template>
|
||||
<section class="info-user-list">
|
||||
<h3 class="user-header section-header">
|
||||
<img :src="getIcon('user')" alt="icon-user" />
|
||||
{{ $t('scenery.users') }}
|
||||
<span class="text--primary">{{ station.onlineInfo?.currentUsers || '0' }}</span
|
||||
> / <span class="text--primary">{{ station.onlineInfo?.maxUsers || '0' }}</span>
|
||||
</h3>
|
||||
|
||||
<div
|
||||
v-for="(train, i) in computedStationTrains"
|
||||
class="badge user"
|
||||
:class="train.stopStatus"
|
||||
:key="train.trainId"
|
||||
tabindex="0"
|
||||
@click="selectModalTrain(train.trainId)"
|
||||
@keydown.enter="selectModalTrain(train.trainId)"
|
||||
>
|
||||
<span class="user_train">{{ train.trainNo }}</span>
|
||||
<span class="user_name">{{ train.driverName }}</span>
|
||||
</div>
|
||||
|
||||
<div class="badge user badge-none" v-if="!computedStationTrains || computedStationTrains.length == 0">
|
||||
{{ $t('scenery.no-users') }}
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
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({
|
||||
mixins: [routerMixin, imageMixin, modalTrainMixin],
|
||||
|
||||
props: {
|
||||
station: {
|
||||
type: Object as () => Station,
|
||||
default: {},
|
||||
},
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
const store = useStore();
|
||||
|
||||
const computedStationTrains = computed(() => {
|
||||
if (!props.station) return [];
|
||||
|
||||
const station = props.station as Station;
|
||||
if (!station.onlineInfo) return [];
|
||||
if (!station.onlineInfo.stationTrains) return [];
|
||||
|
||||
return station.onlineInfo.stationTrains.map((train) => {
|
||||
const scheduledTrainStatus = station.onlineInfo?.scheduledTrains?.find((st) => st.trainNo === train.trainNo);
|
||||
|
||||
return {
|
||||
...train,
|
||||
stopStatus: scheduledTrainStatus?.stopStatus || 'no-timetable',
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
return { computedStationTrains, store };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$no-timetable: #aaa;
|
||||
$departed: springgreen;
|
||||
$stopped: #ffa600;
|
||||
$online: gold;
|
||||
$terminated: salmon;
|
||||
$disconnected: slategray;
|
||||
|
||||
.info-user-list {
|
||||
width: 100%;
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.user {
|
||||
cursor: pointer;
|
||||
|
||||
&_train {
|
||||
color: black;
|
||||
background-color: $no-timetable;
|
||||
|
||||
transition: background-color 200ms;
|
||||
-ms-transition: background-color 200ms;
|
||||
-webkit-transition: background-color 200ms;
|
||||
}
|
||||
|
||||
&.no-timetable .user_train {
|
||||
background-color: $no-timetable;
|
||||
}
|
||||
|
||||
&.departed > &_train {
|
||||
background-color: $departed;
|
||||
}
|
||||
|
||||
&.stopped > &_train {
|
||||
background-color: $stopped;
|
||||
}
|
||||
|
||||
&.online > &_train {
|
||||
background-color: $online;
|
||||
}
|
||||
|
||||
&.terminated > &_train {
|
||||
background-color: $terminated;
|
||||
}
|
||||
|
||||
&.disconnected > &_train {
|
||||
background-color: $disconnected;
|
||||
}
|
||||
|
||||
&.offline {
|
||||
background: firebrick;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<section class="info-user-list">
|
||||
<h3 class="user-header section-header">
|
||||
<img :src="getIcon('user')" alt="icon-user" />
|
||||
{{ $t('scenery.users') }}
|
||||
<span class="text--primary">{{ station.onlineInfo?.currentUsers || '0' }}</span
|
||||
> / <span class="text--primary">{{ station.onlineInfo?.maxUsers || '0' }}</span>
|
||||
</h3>
|
||||
|
||||
<div
|
||||
v-for="(train, i) in computedStationTrains"
|
||||
class="badge user"
|
||||
:class="train.stopStatus"
|
||||
:key="train.trainId"
|
||||
tabindex="0"
|
||||
@click.prevent="selectModalTrain(train.trainId, $event.currentTarget)"
|
||||
@keydown.enter="selectModalTrain(train.trainId, $event.currentTarget)"
|
||||
>
|
||||
<span class="user_train">{{ train.trainNo }}</span>
|
||||
<span class="user_name">{{ train.driverName }}</span>
|
||||
</div>
|
||||
|
||||
<div class="badge user badge-none" v-if="!computedStationTrains || computedStationTrains.length == 0">
|
||||
{{ $t('scenery.no-users') }}
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
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({
|
||||
mixins: [routerMixin, imageMixin, modalTrainMixin],
|
||||
|
||||
props: {
|
||||
station: {
|
||||
type: Object as () => Station,
|
||||
default: {},
|
||||
},
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
const store = useStore();
|
||||
|
||||
const computedStationTrains = computed(() => {
|
||||
if (!props.station) return [];
|
||||
|
||||
const station = props.station as Station;
|
||||
if (!station.onlineInfo) return [];
|
||||
if (!station.onlineInfo.stationTrains) return [];
|
||||
|
||||
return station.onlineInfo.stationTrains.map((train) => {
|
||||
const scheduledTrainStatus = station.onlineInfo?.scheduledTrains?.find((st) => st.trainNo === train.trainNo);
|
||||
|
||||
return {
|
||||
...train,
|
||||
stopStatus: scheduledTrainStatus?.stopStatus || 'no-timetable',
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
return { computedStationTrains, store };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$no-timetable: #aaa;
|
||||
$departed: springgreen;
|
||||
$stopped: #ffa600;
|
||||
$online: gold;
|
||||
$terminated: salmon;
|
||||
$disconnected: slategray;
|
||||
|
||||
.info-user-list {
|
||||
width: 100%;
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.user {
|
||||
cursor: pointer;
|
||||
|
||||
&_train {
|
||||
color: black;
|
||||
background-color: $no-timetable;
|
||||
|
||||
transition: background-color 200ms;
|
||||
-ms-transition: background-color 200ms;
|
||||
-webkit-transition: background-color 200ms;
|
||||
}
|
||||
|
||||
&.no-timetable .user_train {
|
||||
background-color: $no-timetable;
|
||||
}
|
||||
|
||||
&.departed > &_train {
|
||||
background-color: $departed;
|
||||
}
|
||||
|
||||
&.stopped > &_train {
|
||||
background-color: $stopped;
|
||||
}
|
||||
|
||||
&.online > &_train {
|
||||
background-color: $online;
|
||||
}
|
||||
|
||||
&.terminated > &_train {
|
||||
background-color: $terminated;
|
||||
}
|
||||
|
||||
&.disconnected > &_train {
|
||||
background-color: $disconnected;
|
||||
}
|
||||
|
||||
&.offline {
|
||||
background: firebrick;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,25 +2,37 @@
|
||||
<section class="scenery-timetable">
|
||||
<div class="timetable-header">
|
||||
<h3>
|
||||
<img :src="getIcon('timetable')" alt="icon-timetable" />
|
||||
<img :src="getIcon('timetable')" alt="icon-timetable" />
|
||||
<span>{{ $t('scenery.timetables') }}</span>
|
||||
|
||||
<span class="text--primary">{{ station.onlineInfo?.scheduledTrains?.length || '0' }}</span>
|
||||
<span> / </span>
|
||||
<span class="text--grayed">
|
||||
{{ station.onlineInfo?.scheduledTrains?.filter((train) => train.stopInfo.confirmed).length || '0' }}
|
||||
|
||||
<span>
|
||||
<span class="text--primary">{{ station.onlineInfo?.scheduledTrains?.length || '0' }}</span>
|
||||
<span> / </span>
|
||||
<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="tabliceZbiorczeHref" target="_blank" :title="$t('scenery.tablice-link')">
|
||||
<img :src="getIcon('tablice', 'ico')" alt="icon-tablice" />
|
||||
</a>
|
||||
</span>
|
||||
</h3>
|
||||
|
||||
<div class="timetable-checkpoints" v-if="station && station.generalInfo?.checkpoints">
|
||||
<div class="timetable-checkpoints" v-if="station?.generalInfo?.checkpoints">
|
||||
<span v-for="(cp, i) in station.generalInfo.checkpoints" :key="i">
|
||||
{{ (i > 0 && '•') || '' }}
|
||||
|
||||
<button
|
||||
:key="cp.checkpointName"
|
||||
class="checkpoint_item"
|
||||
:class="{ current: selectedCheckpoint === cp.checkpointName }"
|
||||
@click="selectCheckpoint(cp)"
|
||||
:class="{ current: chosenCheckpoint === cp.checkpointName }"
|
||||
@click="setCheckpoint(cp)"
|
||||
>
|
||||
{{ cp.checkpointName }}
|
||||
</button>
|
||||
@@ -41,14 +53,14 @@
|
||||
{{ $t('scenery.no-timetables') }}
|
||||
</span>
|
||||
|
||||
<transition-group name="timetables-anim">
|
||||
<transition-group name="list-anim">
|
||||
<div
|
||||
class="timetable-item"
|
||||
v-for="(scheduledTrain, i) in computedScheduledTrains"
|
||||
:key="scheduledTrain.trainId"
|
||||
tabindex="0"
|
||||
@click.prevent.stop="selectModalTrain(scheduledTrain.trainId)"
|
||||
@keydown.enter.prevent="selectModalTrain(scheduledTrain.trainId)"
|
||||
@click.prevent.stop="selectModalTrain(scheduledTrain.trainId, $event.currentTarget)"
|
||||
@keydown.enter.prevent="selectModalTrain(scheduledTrain.trainId, $event.currentTarget)"
|
||||
>
|
||||
<span class="timetable-general">
|
||||
<span class="general-info">
|
||||
@@ -86,40 +98,29 @@
|
||||
</div>
|
||||
<div v-else>
|
||||
<div>
|
||||
<s style="margin-right: 0.2em" class="text--grayed">{{
|
||||
timestampToString(scheduledTrain.stopInfo.arrivalTimestamp)
|
||||
}}</s>
|
||||
<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 }})
|
||||
({{ scheduledTrain.stopInfo.arrivalDelay > 0 ? '+' : '' }}{{ 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 v-else> </span>
|
||||
<span class="stop-connection">
|
||||
{{ scheduledTrain.arrivingLine }}
|
||||
</span>
|
||||
|
||||
<span class="arrow"></span>
|
||||
<span class="stop-time">
|
||||
{{ scheduledTrain.stopInfo.stopTime || '' }}
|
||||
{{ scheduledTrain.stopInfo.stopTime ? scheduledTrain.stopInfo.stopType || 'pt' : '' }}
|
||||
</span>
|
||||
|
||||
<span class="stop-line">
|
||||
<span>
|
||||
{{ scheduledTrain.arrivingLine }}
|
||||
</span>
|
||||
<span></span>
|
||||
<span>
|
||||
{{ scheduledTrain.departureLine }}
|
||||
</span>
|
||||
<span class="stop-connection">
|
||||
{{ scheduledTrain.departureLine }}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
@@ -134,15 +135,12 @@
|
||||
</div>
|
||||
<div v-else>
|
||||
<div>
|
||||
<s style="margin-right: 0.2em" class="text--grayed">{{
|
||||
timestampToString(scheduledTrain.stopInfo.departureTimestamp)
|
||||
}}</s>
|
||||
<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 }})
|
||||
({{ scheduledTrain.stopInfo.departureDelay > 0 ? '+' : '' }}{{ scheduledTrain.stopInfo.departureDelay }})
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
@@ -168,7 +166,6 @@ import { useStore } from '../../store/store';
|
||||
import imageMixin from '../../mixins/imageMixin';
|
||||
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
||||
import ScheduledTrainStatus from './ScheduledTrainStatus.vue';
|
||||
import ScheduledTrain from '../../scripts/interfaces/ScheduledTrain';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SceneryTimetable',
|
||||
@@ -192,16 +189,22 @@ export default defineComponent({
|
||||
listOpen: false,
|
||||
}),
|
||||
|
||||
mounted() {
|
||||
this.loadSelectedOption();
|
||||
},
|
||||
|
||||
activated() {
|
||||
this.loadSelectedOption();
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
const route = useRoute();
|
||||
const currentURL = computed(() => `${location.origin}${route.fullPath}`);
|
||||
|
||||
const store = useStore();
|
||||
|
||||
const selectedCheckpoint = ref(
|
||||
props.station?.generalInfo?.checkpoints?.length == 0
|
||||
? ''
|
||||
: props.station?.generalInfo?.checkpoints[0].checkpointName || ''
|
||||
const chosenCheckpoint = ref(
|
||||
props.station?.generalInfo?.checkpoints?.length == 0 ? '' : props.station?.generalInfo?.checkpoints[0].checkpointName || null
|
||||
);
|
||||
|
||||
const computedScheduledTrains = computed(() => {
|
||||
@@ -210,8 +213,7 @@ export default defineComponent({
|
||||
const station = props.station as Station;
|
||||
|
||||
let scheduledTrains =
|
||||
station.generalInfo?.checkpoints.find((cp) => cp.checkpointName === selectedCheckpoint.value)
|
||||
?.scheduledTrains ||
|
||||
station.generalInfo?.checkpoints.find((cp) => cp.checkpointName === chosenCheckpoint.value)?.scheduledTrains ||
|
||||
station.onlineInfo?.scheduledTrains ||
|
||||
[];
|
||||
|
||||
@@ -232,12 +234,21 @@ export default defineComponent({
|
||||
|
||||
return {
|
||||
currentURL,
|
||||
selectedCheckpoint,
|
||||
chosenCheckpoint,
|
||||
computedScheduledTrains,
|
||||
store,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
tabliceZbiorczeHref() {
|
||||
let url = `https://tablice-td2.web.app/?station=${this.station.name}`;
|
||||
if (this.chosenCheckpoint) url += `&checkpoint=${this.chosenCheckpoint}`;
|
||||
|
||||
return url;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
loadSelectedOption() {
|
||||
if (!this.station) return;
|
||||
@@ -245,49 +256,26 @@ export default defineComponent({
|
||||
if (!this.station.generalInfo.checkpoints) return;
|
||||
if (this.station.generalInfo.checkpoints.length == 0) return;
|
||||
|
||||
if (this.selectedCheckpoint != '') return;
|
||||
if (this.chosenCheckpoint != '') return;
|
||||
|
||||
this.selectedCheckpoint = this.station.generalInfo.checkpoints[0].checkpointName;
|
||||
this.chosenCheckpoint = this.station.generalInfo.checkpoints[0].checkpointName;
|
||||
},
|
||||
|
||||
selectCheckpoint(cp: { checkpointName: string }) {
|
||||
this.selectedCheckpoint = cp.checkpointName;
|
||||
setCheckpoint(cp: { checkpointName: string }) {
|
||||
this.chosenCheckpoint = cp.checkpointName;
|
||||
},
|
||||
|
||||
showTimetableOnlyView() {
|
||||
this.$router.push(`${this.$route.fullPath}&timetableOnly=1`);
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.loadSelectedOption();
|
||||
},
|
||||
|
||||
activated() {
|
||||
this.loadSelectedOption();
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../styles/responsive.scss';
|
||||
@import '../../styles/variables.scss';
|
||||
|
||||
.timetables-anim-move,
|
||||
.timetables-anim-enter-active,
|
||||
.timetables-anim-leave-active {
|
||||
transition: all 250ms ease;
|
||||
}
|
||||
|
||||
.timetables-anim-enter-from,
|
||||
.timetables-anim-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
|
||||
.timetables-anim-leave-active {
|
||||
position: absolute;
|
||||
}
|
||||
@import '../../styles/animations.scss';
|
||||
|
||||
.scenery-timetable {
|
||||
height: 100%;
|
||||
@@ -296,24 +284,36 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
.timetable-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 99;
|
||||
|
||||
background-color: #181818;
|
||||
|
||||
padding: 0.5em;
|
||||
|
||||
img {
|
||||
width: 25px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
h3 {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
|
||||
gap: 0.5em;
|
||||
font-size: 1.3em;
|
||||
}
|
||||
}
|
||||
|
||||
.header_links {
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
.timetable {
|
||||
&-count {
|
||||
margin-left: 0.5em;
|
||||
@@ -325,8 +325,8 @@ export default defineComponent({
|
||||
max-width: 1100px;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
||||
gap: 2em 0.5em;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 1.2em 0.5em;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
@@ -352,7 +352,9 @@ export default defineComponent({
|
||||
|
||||
&-schedule {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(30px, 1fr));
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 0.2em;
|
||||
align-items: center;
|
||||
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
@@ -370,7 +372,8 @@ export default defineComponent({
|
||||
|
||||
flex-wrap: wrap;
|
||||
font-size: 1.1em;
|
||||
padding: 0.75em 0;
|
||||
|
||||
margin-top: 0.5em;
|
||||
|
||||
button.checkpoint_item {
|
||||
color: #aaa;
|
||||
@@ -383,33 +386,6 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
.arrow {
|
||||
border: solid white;
|
||||
border-width: 0 2px 2px 0;
|
||||
display: inline-block;
|
||||
padding: 2px;
|
||||
margin-left: 50px;
|
||||
|
||||
position: relative;
|
||||
|
||||
transform: rotate(-45deg);
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: 55px;
|
||||
height: 3px;
|
||||
top: 4px;
|
||||
left: 4px;
|
||||
|
||||
transform: translate(-100%, -1px) rotate(45deg);
|
||||
transform-origin: right bottom;
|
||||
|
||||
background: white;
|
||||
}
|
||||
}
|
||||
|
||||
.general-info {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -436,47 +412,34 @@ export default defineComponent({
|
||||
|
||||
.schedule {
|
||||
&-arrival,
|
||||
&-stop,
|
||||
&-departure {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
margin: 0 0.3rem;
|
||||
font-size: 1.15em;
|
||||
}
|
||||
|
||||
&-stop {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 0.9em;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 0.5em;
|
||||
align-items: end;
|
||||
|
||||
padding: 0.3em 0;
|
||||
|
||||
.stop-line {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
|
||||
span {
|
||||
width: 65px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
span:first-child {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
span:last-child {
|
||||
text-align: left;
|
||||
}
|
||||
.stop-connection {
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
.stop-time {
|
||||
position: absolute;
|
||||
transform: translateY(-15px);
|
||||
position: relative;
|
||||
inline-size: max-content;
|
||||
align-self: center;
|
||||
font-size: 0.9em;
|
||||
|
||||
color: $accentCol;
|
||||
|
||||
&::after {
|
||||
content: '\027F6';
|
||||
display: block;
|
||||
font-size: 2.2em;
|
||||
line-height: 0.65em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -486,21 +449,6 @@ export default defineComponent({
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.scenery-timetable-list-anim {
|
||||
&-enter-from,
|
||||
&-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&-enter-active {
|
||||
transition: all 100ms ease-out;
|
||||
}
|
||||
|
||||
&-leave-active {
|
||||
transition: all 100ms ease-out 100ms;
|
||||
}
|
||||
}
|
||||
|
||||
@include smallScreen {
|
||||
.timetable-item {
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
@@ -1,35 +1,51 @@
|
||||
<template>
|
||||
<section class="scenery-timetables-history scenery-section">
|
||||
<Loading v-if="dataStatus != 2" />
|
||||
<section class="scenery-table-section">
|
||||
<Loading v-if="dataStatus != DataStatus.Loaded" />
|
||||
<div class="no-history" v-else-if="historyList.length == 0">{{ $t('scenery.history-list-empty') }}</div>
|
||||
|
||||
<div class="list-warning" v-else-if="sceneryHistoryList.length == 0">{{ $t('scenery.history-list-empty') }}</div>
|
||||
<ul class="history-list" v-else>
|
||||
<li class="list-item" v-for="historyItem in sceneryHistoryList">
|
||||
<div>
|
||||
<b>{{ localeDay(historyItem.beginDate, $i18n.locale) }}</b>
|
||||
{{ localeTime(historyItem.beginDate, $i18n.locale) }}
|
||||
</div>
|
||||
<table class="scenery-history-table" v-else>
|
||||
<thead>
|
||||
<th>{{ $t('scenery.timetables-history-id') }}</th>
|
||||
<th>{{ $t('scenery.timetables-history-number') }}</th>
|
||||
<th>{{ $t('scenery.timetables-history-route') }}</th>
|
||||
<th>{{ $t('scenery.timetables-history-driver') }}</th>
|
||||
<th>{{ $t('scenery.timetables-history-author') }}</th>
|
||||
<th>{{ $t('scenery.timetables-history-date') }}</th>
|
||||
</thead>
|
||||
|
||||
<div>
|
||||
<router-link :to="`/journal/timetables?timetableId=${historyItem.timetableId}`">
|
||||
<span class="text--grayed"> #{{ historyItem.timetableId }} </span>
|
||||
<b class="text--primary"> {{ historyItem.trainCategoryCode }} {{ historyItem.trainNo }}</b>
|
||||
<div>{{ historyItem.driverName }}</div>
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<div>{{ historyItem.route.replace('|', ' -> ') }}</div>
|
||||
<!-- <div>{{ historyItem.routeDistance }} km</div> -->
|
||||
<div>
|
||||
{{ $t('scenery.timetable-author-title') }}:
|
||||
<b v-if="historyItem.authorName">{{ historyItem.authorName }}</b>
|
||||
<i v-else>{{ $t('scenery.timetable-author-unknown') }}</i>
|
||||
</div>
|
||||
|
||||
<!-- <div v-if="historyItem.authorId">{{ historyItem.authorName }}</div> -->
|
||||
</li>
|
||||
</ul>
|
||||
<tbody>
|
||||
<tr v-for="historyItem in historyList">
|
||||
<td>
|
||||
<router-link :to="`/journal/timetables?timetableId=${historyItem.id}`">#{{ historyItem.id }}</router-link>
|
||||
</td>
|
||||
<td>
|
||||
<b class="text--primary">{{ historyItem.trainCategoryCode }}</b> <br />
|
||||
{{ historyItem.trainNo }}
|
||||
</td>
|
||||
<td>{{ historyItem.route.replace('|', ' -> ') }}</td>
|
||||
<td>{{ historyItem.driverName }}</td>
|
||||
<td>
|
||||
<router-link
|
||||
v-if="historyItem.authorName"
|
||||
:to="`/journal/timetables?authorName=${historyItem.authorName}`"
|
||||
>{{ historyItem.authorName }}
|
||||
</router-link>
|
||||
<i v-else>{{ $t('scenery.timetable-author-unknown') }}</i>
|
||||
</td>
|
||||
<td>
|
||||
<b>{{ localeDay(historyItem.beginDate, $i18n.locale) }}</b>
|
||||
{{ localeTime(historyItem.beginDate, $i18n.locale) }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<div class="bottom-info">
|
||||
<button class="btn btn--option" v-if="historyList.length > 0" @click="navigateToHistory()">
|
||||
{{ $t('scenery.bottom-info') }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -41,37 +57,48 @@ import { TimetableHistory, SceneryTimetableHistory } from '../../scripts/interfa
|
||||
import Station from '../../scripts/interfaces/Station';
|
||||
import { URLs } from '../../scripts/utils/apiURLs';
|
||||
import Loading from '../Global/Loading.vue';
|
||||
import listObserverMixin from '../../mixins/listObserverMixin';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SceneryTimetablesHistory',
|
||||
mixins: [dateMixin],
|
||||
mixins: [dateMixin, listObserverMixin],
|
||||
props: {
|
||||
station: {
|
||||
type: Object as PropType<Station>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
sceneryHistoryList: [] as TimetableHistory[],
|
||||
historyList: [] as TimetableHistory[],
|
||||
dataStatus: DataStatus.Loading,
|
||||
DataStatus,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.fetchAPIData();
|
||||
|
||||
async activated() {
|
||||
const fetchedHistory = await this.fetchAPIData();
|
||||
if (fetchedHistory) this.historyList = fetchedHistory.timetables;
|
||||
},
|
||||
|
||||
methods: {
|
||||
async fetchAPIData(countFrom = 0, countLimit = 15) {
|
||||
async fetchAPIData(countFrom = 0, countLimit = 15): Promise<SceneryTimetableHistory | null> {
|
||||
try {
|
||||
const requestString = `${URLs.stacjownikAPI}/api/getSceneryTimetables?name=${this.station.name}&countFrom=${countFrom}&countLimit=${countLimit}`;
|
||||
const requestString = `${URLs.stacjownikAPI}/api/getIssuedTimetables?name=${this.station.name}&countFrom=${countFrom}&countLimit=${countLimit}`;
|
||||
const historyAPIData: SceneryTimetableHistory = await (await axios.get(requestString)).data;
|
||||
|
||||
this.sceneryHistoryList = historyAPIData.sceneryTimetables;
|
||||
this.dataStatus = DataStatus.Loaded;
|
||||
return historyAPIData;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
navigateToHistory() {
|
||||
this.$router.push(`/journal/timetables?issuedFrom=${this.station.name}`);
|
||||
},
|
||||
},
|
||||
components: { Loading },
|
||||
});
|
||||
@@ -79,34 +106,5 @@ export default defineComponent({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../styles/responsive.scss';
|
||||
@import '../../styles/SceneryView/styles.scss';
|
||||
|
||||
.list-warning {
|
||||
padding: 1em 0.5em;
|
||||
background-color: #444;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.history-list {
|
||||
padding: 0 0.5em;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 2fr 2fr 1fr;
|
||||
gap: 1em;
|
||||
align-items: center;
|
||||
|
||||
background-color: #353535;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em 0;
|
||||
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
@include smallScreen {
|
||||
.list-item {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
@import '../../styles/sceneryViewTables.scss';
|
||||
</style>
|
||||
|
||||
@@ -1,47 +1,19 @@
|
||||
<template>
|
||||
<div class="general-status">
|
||||
<span :class="scheduledTrain.stopStatus">
|
||||
<span v-if="scheduledTrain.stopStatus == 'arriving'">
|
||||
<span v-if="scheduledTrain.prevDepartureLine">({{ scheduledTrain.prevDepartureLine }})</span>
|
||||
{{ scheduledTrain.prevStationName }}
|
||||
><span v-if="scheduledTrain.nextArrivalLine"> ({{ scheduledTrain.nextArrivalLine }}) </span>
|
||||
{{ scheduledTrain.nextStationName || '---' }}
|
||||
</span>
|
||||
|
||||
<span v-else-if="scheduledTrain.stopStatus == 'departed'">
|
||||
>> <span v-if="scheduledTrain.nextArrivalLine"> ({{ scheduledTrain.nextArrivalLine }}) </span>
|
||||
{{ scheduledTrain.nextStationName }}
|
||||
</span>
|
||||
|
||||
<span v-else-if="scheduledTrain.stopStatus == 'departed-away'">
|
||||
>>>
|
||||
<span v-if="scheduledTrain.nextArrivalLine"> ({{ scheduledTrain.nextArrivalLine }}) </span>
|
||||
{{ scheduledTrain.nextStationName }}
|
||||
</span>
|
||||
|
||||
<span v-else-if="scheduledTrain.stopStatus == 'online'">
|
||||
>
|
||||
<span v-if="scheduledTrain.nextArrivalLine">
|
||||
({{ scheduledTrain.nextArrivalLine }}) {{ scheduledTrain.nextStationName }}
|
||||
</span>
|
||||
<span v-else-if="!scheduledTrain.nextStationName">{{ $t('timetables.end') }}</span>
|
||||
<span v-else>{{ scheduledTrain.nextStationName }}</span>
|
||||
</span>
|
||||
|
||||
<span v-else-if="scheduledTrain.stopStatus == 'stopped'">
|
||||
>
|
||||
<span v-if="scheduledTrain.nextArrivalLine"> ({{ scheduledTrain.nextArrivalLine }}) </span>
|
||||
{{ scheduledTrain.nextStationName }}
|
||||
</span>
|
||||
|
||||
<span v-else-if="scheduledTrain.stopStatus == 'terminated'">X {{ $t('timetables.terminated') }}</span>
|
||||
<span :class="computedScheduledTrain.stopStatus" :title="computedScheduledTrain.stopStatusDescription">
|
||||
{{ computedScheduledTrain.stopStatusIndicator }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
import ScheduledTrain from '../../scripts/interfaces/ScheduledTrain';
|
||||
import { ScheduledTrain, StopStatus } from '../../scripts/interfaces/ScheduledTrain';
|
||||
|
||||
interface ScheduledTrainComp extends ScheduledTrain {
|
||||
stopStatusIndicator: string;
|
||||
stopStatusDescription: string;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
@@ -50,6 +22,58 @@ export default defineComponent({
|
||||
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>
|
||||
|
||||
@@ -86,3 +110,4 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
<template>
|
||||
<button class="btn--action" :class="option.section" :data-selected="option.value" @click="handleChange">
|
||||
{{ $t(`filters.${option.id}`) }}
|
||||
</button>
|
||||
<label @dblclick="handleDbClick">
|
||||
<input v-model="option.value" type="checkbox" :class="option.section" :name="option.id" />
|
||||
<span>
|
||||
{{ $t(`filters.${option.id}`) }}
|
||||
</span>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -29,78 +32,68 @@ export default defineComponent({
|
||||
filterStore: useStationFiltersStore(),
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleChange() {
|
||||
this.option.value = !this.option.value;
|
||||
|
||||
this.filterStore.changeFilterValue({
|
||||
name: this.option.name,
|
||||
value: !this.option.value,
|
||||
});
|
||||
watch: {
|
||||
'option.value'() {
|
||||
this.filterStore.changeFilterValue(this.option.name, !this.option.value);
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleDbClick(e: Event) {
|
||||
e.preventDefault();
|
||||
|
||||
this.filterStore.lastClickedFilterId = this.option.id;
|
||||
this.option.value = true;
|
||||
|
||||
this.filterStore.inputs.options
|
||||
.filter((option) => {
|
||||
return option.section == this.option.section && option.id != this.option.id;
|
||||
})
|
||||
.forEach((option) => {
|
||||
option.value = !this.option.value;
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$accessCol: #e03b07;
|
||||
$controlCol: #0085ff;
|
||||
$signalCol: #bf7c00;
|
||||
$statusCol: #349b32;
|
||||
$saveCol: #28a826;
|
||||
$routesCol: #9049c0;
|
||||
@import '../../styles/variables.scss';
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
padding: 0.4em;
|
||||
border-radius: 0.4em;
|
||||
label {
|
||||
position: relative;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
|
||||
&:focus-visible {
|
||||
outline: 1px solid white;
|
||||
span {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
padding: 0.25em;
|
||||
background-color: #444;
|
||||
}
|
||||
|
||||
&[data-selected='true'] {
|
||||
&.access {
|
||||
background-color: $accessCol;
|
||||
box-shadow: 0 0 6px 1px $accessCol;
|
||||
span:hover {
|
||||
background-color: #555;
|
||||
}
|
||||
|
||||
input[type='checkbox'] {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
|
||||
&:checked + span {
|
||||
background-color: forestgreen;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&.control {
|
||||
background-color: $controlCol;
|
||||
box-shadow: 0 0 6px 1px $controlCol;
|
||||
}
|
||||
|
||||
&.signals {
|
||||
background-color: $signalCol;
|
||||
box-shadow: 0 0 6px 1px $signalCol;
|
||||
}
|
||||
|
||||
&.routes {
|
||||
background-color: $routesCol;
|
||||
box-shadow: 0 0 6px 1px $routesCol;
|
||||
}
|
||||
|
||||
&.status {
|
||||
background-color: $statusCol;
|
||||
box-shadow: 0 0 6px 1px $statusCol;
|
||||
}
|
||||
|
||||
&.save {
|
||||
background-color: $saveCol;
|
||||
box-shadow: 0 0 6px 1px $saveCol;
|
||||
}
|
||||
|
||||
&.troll {
|
||||
background-color: firebrick;
|
||||
box-shadow: 0 0 6px 1px firebrick;
|
||||
}
|
||||
|
||||
&.mode {
|
||||
background-color: lightgreen;
|
||||
color: black;
|
||||
|
||||
font-weight: 500;
|
||||
&:focus-visible + span {
|
||||
outline: 1px solid $accentCol;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<button class="btn--filled btn--image" @click="toggleCard">
|
||||
<img class="button_icon" :src="getIcon('filter2')" alt="filter icon" />
|
||||
{{ $t('options.filters') }} [F]
|
||||
<span class="active-indicator" v-if="!filterStore.areFiltersAtDefault"></span>
|
||||
</button>
|
||||
|
||||
<label for="scenery-search">
|
||||
@@ -26,15 +27,44 @@
|
||||
<div class="card" v-if="isVisible" tabindex="0" ref="cardEl">
|
||||
<div class="card_content">
|
||||
<div class="card_title flex">{{ $t('filters.title') }}</div>
|
||||
<p class="card_info" v-html="$t('filters.desc')"></p>
|
||||
|
||||
<section class="card_options">
|
||||
<filter-option
|
||||
v-for="(option, i) in filterStore.inputs.options"
|
||||
:option="option"
|
||||
:key="i"
|
||||
@optionChange="handleChange"
|
||||
/>
|
||||
<!-- QUICK ACTIONS (TODO) -->
|
||||
<!-- <div class="quick-actions">
|
||||
<h3 class="text--primary">{{ $t('filters.sections.quick') }}</h3>
|
||||
<hr />
|
||||
|
||||
<div>
|
||||
<button class="btn--action" style="width: 100%" @click="filterStore.handleQuickAction('all-available')">
|
||||
{{ $t('filters.all-available') }}
|
||||
</button>
|
||||
|
||||
<button class="btn--action" style="width: 100%" @click="filterStore.handleQuickAction('all-free')">
|
||||
{{ $t('filters.all-free') }}
|
||||
</button>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<div class="option-section" v-for="section in filterStore.inputs.optionSections">
|
||||
<h3 class="text--primary">
|
||||
{{ $t(`filters.sections.${section}`) }}
|
||||
|
||||
<button @click="filterStore.resetSectionOptions(section)">RESET</button>
|
||||
</h3>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="section-inputs">
|
||||
<FilterOption
|
||||
v-for="(option, i) in filterStore.inputs.options.filter((o) => o.section == section)"
|
||||
:option="option"
|
||||
:key="i"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card_timestamp" style="text-align: center">
|
||||
<div>{{ $t('filters.minimum-hours-title') }}</div>
|
||||
<span class="clock">
|
||||
@@ -80,18 +110,25 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<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">{{ $t('filters.reset') }}</button>
|
||||
<button class="btn--action" @click="closeCard">{{ $t('filters.close') }}</button>
|
||||
</div>
|
||||
</section>
|
||||
</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>
|
||||
</transition>
|
||||
</section>
|
||||
@@ -156,6 +193,10 @@ export default defineComponent({
|
||||
.filter((s) => s.name.toLocaleLowerCase().includes(this.chosenSearchScenery.toLocaleLowerCase()))
|
||||
.sort((s1, s2) => (s1.name > s2.name ? 1 : -1));
|
||||
},
|
||||
|
||||
currentOptionsActive() {
|
||||
return true;
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
@@ -181,22 +222,10 @@ export default defineComponent({
|
||||
this.isVisible = !this.isVisible;
|
||||
},
|
||||
|
||||
handleChange(change: { name: string; value: boolean }) {
|
||||
this.filterStore.changeFilterValue({
|
||||
name: change.name,
|
||||
value: !change.value,
|
||||
});
|
||||
|
||||
if (this.saveOptions) StorageManager.setBooleanValue(change.name, change.value);
|
||||
},
|
||||
|
||||
handleInput(e: Event) {
|
||||
const target = e.target as HTMLInputElement;
|
||||
|
||||
this.filterStore.changeFilterValue({
|
||||
name: target.name,
|
||||
value: target.value,
|
||||
});
|
||||
this.filterStore.changeFilterValue(target.name, target.value);
|
||||
|
||||
if (this.saveOptions) StorageManager.setStringValue(target.name, target.value);
|
||||
},
|
||||
@@ -210,11 +239,7 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
changeNumericFilterValue(name: string, value: number, saveToStorage = false) {
|
||||
this.filterStore.changeFilterValue({
|
||||
name,
|
||||
value,
|
||||
});
|
||||
|
||||
this.filterStore.changeFilterValue(name, value);
|
||||
if (this.saveOptions && saveToStorage) StorageManager.setNumericValue(name, value);
|
||||
},
|
||||
|
||||
@@ -281,6 +306,14 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
.card {
|
||||
display: grid;
|
||||
grid-template-rows: 1fr auto;
|
||||
|
||||
&_info {
|
||||
background-color: #111;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
&_controls {
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
@@ -292,13 +325,13 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
&_content {
|
||||
padding: 1em 0.5em;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
gap: 1em;
|
||||
|
||||
max-height: 90vh;
|
||||
|
||||
padding: 1em;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
&_title {
|
||||
@@ -309,18 +342,6 @@ export default defineComponent({
|
||||
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 {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -391,6 +412,9 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
&_actions {
|
||||
width: 100%;
|
||||
padding: 0.5em;
|
||||
|
||||
.filter-option {
|
||||
max-width: 50%;
|
||||
margin: 0 auto;
|
||||
@@ -409,14 +433,39 @@ export default defineComponent({
|
||||
padding: 0.5em;
|
||||
|
||||
&[data-selected='true'] {
|
||||
background-color: lightgreen;
|
||||
color: black;
|
||||
background-color: forestgreen;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.option-section h3 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 0.25em;
|
||||
|
||||
gap: 0.5em;
|
||||
|
||||
button {
|
||||
padding: 0.15em;
|
||||
color: coral;
|
||||
}
|
||||
}
|
||||
|
||||
.section-inputs {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 0.5em;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.quick-actions div {
|
||||
display: flex;
|
||||
margin: 1em 0;
|
||||
gap: 1em;
|
||||
}
|
||||
|
||||
.slider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,33 +1,39 @@
|
||||
<template>
|
||||
<section class="station_table">
|
||||
<button class="return-btn" @click="scrollToTop" v-if="showReturnButton">
|
||||
<img :src="icons.arrow" alt="return arrow" />
|
||||
</button>
|
||||
|
||||
<div class="table_wrapper">
|
||||
<table>
|
||||
<thead>
|
||||
<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">
|
||||
<div v-html="$t(`sceneries.${id}`)"></div>
|
||||
<div v-html="$t(`sceneries.${headerName}`)"></div>
|
||||
|
||||
<img
|
||||
class="sort-icon"
|
||||
v-if="sorterActive.index == i"
|
||||
v-if="sorterActive.headerName == headerName"
|
||||
:src="sorterActive.dir == 1 ? getIcon('arrow-asc') : getIcon('arrow-desc')"
|
||||
alt="sort icon"
|
||||
/>
|
||||
</span>
|
||||
</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">
|
||||
<img :src="getIcon(id)" :alt="id" :title="$t(`sceneries.${id}s`)" />
|
||||
<img :src="getIcon(headerName)" :alt="headerName" :title="$t(`sceneries.${headerName}`)" />
|
||||
|
||||
<img
|
||||
class="sort-icon"
|
||||
v-if="sorterActive.index == i + 7"
|
||||
v-if="sorterActive.headerName == headerName"
|
||||
:src="sorterActive.dir == 1 ? getIcon('arrow-asc') : getIcon('arrow-desc')"
|
||||
alt="sort icon"
|
||||
/>
|
||||
@@ -83,16 +89,11 @@
|
||||
</td>
|
||||
|
||||
<td class="station_status">
|
||||
<span class="status-badge" :class="station.onlineInfo.statusID" v-if="station.onlineInfo">
|
||||
{{ $t(`status.${station.onlineInfo.statusID}`) }}
|
||||
{{
|
||||
station.onlineInfo.statusID == 'online' ? timestampToString(station.onlineInfo.statusTimestamp) : ''
|
||||
}}
|
||||
</span>
|
||||
|
||||
<span class="status-badge free" v-else>
|
||||
{{ $t('status.free') }}
|
||||
</span>
|
||||
<StationStatusBadge
|
||||
:statusID="station.onlineInfo?.statusID"
|
||||
:isOnline="station.onlineInfo ? true : false"
|
||||
:statusTimestamp="station.onlineInfo?.statusTimestamp"
|
||||
/>
|
||||
</td>
|
||||
|
||||
<td class="station_dispatcher-name">
|
||||
@@ -190,25 +191,31 @@
|
||||
|
||||
<td class="station_users" :class="{ inactive: !station.onlineInfo }">
|
||||
<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>
|
||||
</td>
|
||||
|
||||
<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 class="station_schedules" :class="{ inactive: !station.onlineInfo }">
|
||||
<span>
|
||||
<span class="highlight">
|
||||
{{ station.onlineInfo?.scheduledTrains?.length || '0' }}
|
||||
</span>
|
||||
/
|
||||
<span style="color: #bbb">
|
||||
{{ station.onlineInfo?.scheduledTrains?.filter((train) => train.stopInfo.confirmed).length || '0' }}
|
||||
</span>
|
||||
<td class="station_schedules" style="width: 30px" :class="{ inactive: !station.onlineInfo }">
|
||||
<span class="highlight">
|
||||
{{ station.onlineInfo?.scheduledTrains?.length || 0 }}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<td class="station_schedules" style="width: 30px" :class="{ inactive: !station.onlineInfo }">
|
||||
<span style="color: #ccc">
|
||||
{{ 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>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -236,6 +243,8 @@ import Station from '../../scripts/interfaces/Station';
|
||||
import { useStationFiltersStore } from '../../store/stationFiltersStore';
|
||||
import { useStore } from '../../store/store';
|
||||
import Loading from '../Global/Loading.vue';
|
||||
import { HeadIdsTypes, headIconsIds, headIds } from '../../scripts/data/stationHeaderNames';
|
||||
import StationStatusBadge from '../Global/StationStatusBadge.vue';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
@@ -245,12 +254,12 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
|
||||
components: { Loading },
|
||||
components: { Loading, StationStatusBadge },
|
||||
mixins: [styleMixin, dateMixin, stationInfoMixin, returnBtnMixin, imageMixin],
|
||||
|
||||
data: () => ({
|
||||
headIds: ['station', 'min-lvl', 'status', 'dispatcher', 'dispatcher-lvl', 'routes', 'general'],
|
||||
headIconsIds: ['user', 'spawn', 'timetable'],
|
||||
headIconsIds,
|
||||
headIds,
|
||||
lastSelectedStationName: '',
|
||||
}),
|
||||
|
||||
@@ -291,8 +300,10 @@ export default defineComponent({
|
||||
window.open(url, '_blank');
|
||||
},
|
||||
|
||||
changeSorter(i: number) {
|
||||
this.stationFiltersStore.changeSorter(i);
|
||||
changeSorter(headerName: HeadIdsTypes) {
|
||||
if (headerName == 'general' || headerName == 'routes') return;
|
||||
|
||||
this.stationFiltersStore.changeSorter(headerName);
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -349,9 +360,15 @@ table {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
|
||||
min-width: 75px;
|
||||
&.header-text {
|
||||
min-width: 140px;
|
||||
}
|
||||
|
||||
padding: 0.5em;
|
||||
&.header-image {
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
padding: 0.5em 0.25em;
|
||||
background-color: $bgCol;
|
||||
white-space: pre-wrap;
|
||||
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
<template>
|
||||
<div class="train-info" tabindex="0">
|
||||
<div class="train-info">
|
||||
<section class="train-route">
|
||||
<div class="train_general">
|
||||
<span>
|
||||
<span class="timetable-id" v-if="train.timetableData">#{{ train.timetableData.timetableId }}</span>
|
||||
<b class="warning-timeout" v-if="train.isTimeout" :title="$t('trains.timeout')">?</b>
|
||||
<span class="timetable-id" v-if="train.timetableData">#{{ train.timetableData.timetableId }}</span>
|
||||
|
||||
<span class="timetable_warnings">
|
||||
<span class="train-badge twr" v-if="train.timetableData?.TWR">TWR</span>
|
||||
<span class="train-badge skr" v-if="train.timetableData?.SKR">SKR</span>
|
||||
</span>
|
||||
<strong class="timetable-category" v-if="train.timetableData">
|
||||
{{ train.timetableData.category }}
|
||||
</strong>
|
||||
<strong class="train-number"> {{ train.trainNo }}</strong>
|
||||
|
|
||||
<span class="train-driver" :class="{ supporter: train.isSupporter }">{{ train.driverName }}</span>
|
||||
|
||||
<b class="warning-timeout" v-if="train.isTimeout" :title="$t('trains.timeout')">?</b>
|
||||
<span class="timetable_warnings" v-if="train.timetableData?.TWR || train.timetableData?.SKR">
|
||||
<span class="train-badge twr" v-if="train.timetableData?.TWR" :title="$t('general.TWR')">TWR</span>
|
||||
<span class="train-badge skr" v-if="train.timetableData?.SKR" :title="$t('general.SKR')">SKR</span>
|
||||
</span>
|
||||
|
||||
<strong>
|
||||
<span v-if="train.timetableData" class="text--primary">{{ train.timetableData.category }} </span>
|
||||
<span class="train-number">{{ train.trainNo }}</span>
|
||||
</strong>
|
||||
<span>•</span>
|
||||
<b class="level-badge driver" :style="calculateExpStyle(train.driverLevel, train.isSupporter)">
|
||||
{{ train.driverLevel < 2 ? 'L' : `${train.driverLevel}` }}
|
||||
</b>
|
||||
<span>{{ train.driverName }}</span>
|
||||
</div>
|
||||
|
||||
<div class="timetable_route" v-if="train.timetableData">
|
||||
@@ -40,15 +41,7 @@
|
||||
</div>
|
||||
|
||||
<div class="timetable_progress" style="margin-top: 0.5em" v-if="train.timetableData">
|
||||
<!-- <span> </span> -->
|
||||
<span class="timetable_progress-bar">
|
||||
<!-- {{ confirmedPercentage(train.timetableData.followingStops) }}% -->
|
||||
<span class="bar-bg"></span>
|
||||
<span
|
||||
class="bar-fg"
|
||||
:style="{ width: `${Math.floor(confirmedPercentage(train.timetableData.followingStops))}%` }"
|
||||
></span>
|
||||
</span>
|
||||
<ProgressBar :progressPercent="confirmedPercentage(train.timetableData.followingStops)" />
|
||||
|
||||
<span class="timetable_progress-distance">
|
||||
{{ currentDistance(train.timetableData.followingStops) }} km /
|
||||
@@ -69,15 +62,13 @@
|
||||
</section>
|
||||
|
||||
<section class="train-stats">
|
||||
<div>
|
||||
<img :src="train.locoURL" loading="lazy" alt="Loco image not found" @error="onImageError" />
|
||||
</div>
|
||||
<TrainThumbnail :name="train.locoType" :onlyFirstSegment="true" />
|
||||
|
||||
<div class="text--grayed">
|
||||
{{ train.locoType }}
|
||||
<span v-if="train.cars.length > 0">
|
||||
<span v-if="train.stockList.length > 1">
|
||||
• {{ $t('trains.cars') }}:
|
||||
<span class="count">{{ train.cars.length }}</span>
|
||||
<span class="count">{{ train.stockList.length - 1 }}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -94,8 +85,11 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import imageMixin from '../../mixins/imageMixin';
|
||||
import styleMixin from '../../mixins/styleMixin';
|
||||
import trainInfoMixin from '../../mixins/trainInfoMixin';
|
||||
import Train from '../../scripts/interfaces/Train';
|
||||
import ProgressBar from '../Global/ProgressBar.vue';
|
||||
import TrainThumbnail from '../Global/TrainThumbnail.vue';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
@@ -103,19 +97,26 @@ export default defineComponent({
|
||||
type: Object as () => Train,
|
||||
required: true,
|
||||
},
|
||||
|
||||
extended: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
|
||||
mixins: [trainInfoMixin, imageMixin],
|
||||
mixins: [trainInfoMixin, imageMixin, styleMixin],
|
||||
components: { ProgressBar, TrainThumbnail },
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Global style for TrainThumbnail -->
|
||||
<style lang="scss">
|
||||
.train-stats .train-thumbnail {
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../styles/responsive.scss';
|
||||
@import '../../styles/badge.scss';
|
||||
|
||||
.image-warning {
|
||||
height: 1em;
|
||||
@@ -126,15 +127,12 @@ export default defineComponent({
|
||||
.train-stats {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
|
||||
img {
|
||||
margin: 0.5em 0;
|
||||
width: 12em;
|
||||
}
|
||||
gap: 0.25em;
|
||||
}
|
||||
|
||||
.train-info {
|
||||
@@ -149,7 +147,6 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
.timetable-id {
|
||||
margin-right: 0.3em;
|
||||
color: #d2d2d2;
|
||||
}
|
||||
|
||||
@@ -159,11 +156,7 @@ export default defineComponent({
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
|
||||
width: 1.25em;
|
||||
height: 1.25em;
|
||||
border-radius: 50%;
|
||||
|
||||
margin-left: 0.25em;
|
||||
padding: 0 0.25em;
|
||||
}
|
||||
|
||||
.timetable_stops {
|
||||
@@ -174,31 +167,15 @@ export default defineComponent({
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
|
||||
gap: 0.25em;
|
||||
margin-right: 1.5em;
|
||||
}
|
||||
.train-status-badges {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.train-badge {
|
||||
padding: 0.15em 0.35em;
|
||||
margin-right: 0.3em;
|
||||
|
||||
font-weight: bold;
|
||||
|
||||
font-size: 0.9em;
|
||||
|
||||
&.twr {
|
||||
background-color: var(--clr-twr);
|
||||
}
|
||||
|
||||
&.skr {
|
||||
background-color: var(--clr-skr);
|
||||
}
|
||||
|
||||
&.offline {
|
||||
background-color: #b83b2d;
|
||||
}
|
||||
gap: 0.25em;
|
||||
}
|
||||
|
||||
.train-driver {
|
||||
@@ -216,7 +193,8 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
.timetable_warnings {
|
||||
color: black;
|
||||
display: flex;
|
||||
gap: 0.25em;
|
||||
}
|
||||
|
||||
.timetable_progress {
|
||||
@@ -225,31 +203,6 @@ export default defineComponent({
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.timetable_progress-bar {
|
||||
position: relative;
|
||||
|
||||
width: 6em;
|
||||
height: 1em;
|
||||
margin: 0.5em 0;
|
||||
|
||||
.bar-fg,
|
||||
.bar-bg {
|
||||
position: absolute;
|
||||
height: 1em;
|
||||
width: 100%;
|
||||
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.bar-fg {
|
||||
background-color: springgreen;
|
||||
}
|
||||
|
||||
.bar-bg {
|
||||
background-color: #5b5b5b;
|
||||
}
|
||||
}
|
||||
|
||||
.timetable_progress-distance {
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
|
||||
@@ -43,29 +43,34 @@
|
||||
|
||||
<h1 class="option-title">{{ $t('options.sort-title') }}</h1>
|
||||
<div class="options_sorters">
|
||||
<div v-for="opt in translatedSorterOptions">
|
||||
<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>
|
||||
|
||||
<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="sort-option btn--option"
|
||||
:data-selected="opt.id == sorterActive.id"
|
||||
@click="onSorterChange(opt)"
|
||||
class="btn--option"
|
||||
v-for="filter in trainFilterList.filter((f) => f.section == section)"
|
||||
:data-inactive="!filter.isActive"
|
||||
@click="onFilterChange(filter)"
|
||||
>
|
||||
{{ opt.value.toUpperCase() }}
|
||||
{{ $t(`options.filter-${filter.id}`) }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1 class="option-title" v-if="trainFilterList.length != 0">{{ $t('options.filter-title') }}</h1>
|
||||
<div class="options_filters">
|
||||
<div class="filter-option" v-for="filter in trainFilterList">
|
||||
<button class="btn--option" :data-inactive="!filter.isActive" @click="onFilterChange(filter)">
|
||||
{{ $t(`options.filter-${filter.id}`) }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="filter-actions">
|
||||
<button class="btn--action" @click="clearAllFilters">{{ $t('options.filter-clear') }}</button>
|
||||
<button class="btn--action" @click="resetAllFilters">{{ $t('options.filter-reset') }}</button>
|
||||
</div>
|
||||
<div class="filter-actions">
|
||||
<div></div>
|
||||
<button class="btn--action" @click="resetAllFilters">{{ $t('options.filter-reset') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -77,9 +82,10 @@
|
||||
import { defineComponent, inject, PropType } from 'vue';
|
||||
import imageMixin from '../../mixins/imageMixin';
|
||||
import keyMixin from '../../mixins/keyMixin';
|
||||
import { TrainFilter } from '../../types/Trains/TrainOptionsTypes';
|
||||
import ActionButton from '../Global/ActionButton.vue';
|
||||
import SelectBox from '../Global/SelectBox.vue';
|
||||
import { TrainFilterSection } from '../../scripts/enums/TrainFilterType';
|
||||
import { TrainFilter } from '../../scripts/interfaces/Trains/TrainFilter';
|
||||
|
||||
export default defineComponent({
|
||||
components: { SelectBox, ActionButton },
|
||||
@@ -101,6 +107,7 @@ export default defineComponent({
|
||||
return {
|
||||
showOptions: false,
|
||||
lastSelectedFilter: null as TrainFilter | null,
|
||||
TrainFilterSection,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -183,13 +190,24 @@ export default defineComponent({
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.filter-option {
|
||||
.options_sorters {
|
||||
display: flex;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.options_filters > div {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
|
||||
gap: 0.5em;
|
||||
|
||||
button {
|
||||
color: white;
|
||||
width: 100%;
|
||||
color: springgreen;
|
||||
font-weight: bold;
|
||||
|
||||
&[data-disabled='true'] {
|
||||
color: #888;
|
||||
&[data-inactive='true'] {
|
||||
color: #aaa;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -201,7 +219,7 @@ export default defineComponent({
|
||||
|
||||
margin-top: 1em;
|
||||
|
||||
button {
|
||||
> * {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,15 @@
|
||||
<template>
|
||||
<div class="train-schedule" @click="toggleShowState">
|
||||
<div class="train-stock">
|
||||
<ul class="stock-list">
|
||||
<li>
|
||||
<img class="train-image" :src="train.locoURL" alt="loco" @error="onImageError" />
|
||||
<div>{{ train.locoType }}</div>
|
||||
</li>
|
||||
<StockList :trainStockList="train.stockList" />
|
||||
|
||||
<li v-if="train.locoType.startsWith('EN')">
|
||||
<img :src="train.locoURL.replace('rb', 's')" @error="onImageError" alt="" />
|
||||
<div>{{ train.locoType }}S</div>
|
||||
<!-- <div class="train-stock"> -->
|
||||
<!-- <ul>
|
||||
<li v-for="(stockName, i) in train.stockList" :key="i">
|
||||
<p>{{ stockName.split(':')[0].split('_').splice(0, 2).join(' ') }} {{ stockName.split(':')[1] }}</p>
|
||||
<TrainThumbnail :name="stockName" />
|
||||
</li>
|
||||
|
||||
<li v-if="train.locoType.startsWith('EN71')">
|
||||
<img :src="train.locoURL.replace('rb', 's')" @error="onImageError" alt="" />
|
||||
<div>{{ train.locoType }}S</div>
|
||||
</li>
|
||||
|
||||
<li v-if="train.locoType.startsWith('EN')">
|
||||
<img :src="train.locoURL.replace('rb', 'ra')" @error="onImageError" alt="" />
|
||||
<div>{{ train.locoType }}RA</div>
|
||||
</li>
|
||||
|
||||
<li v-for="(car, i) in train.cars" :key="i">
|
||||
<img
|
||||
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${car.split(':')[0]}.png`"
|
||||
@error="onImageError"
|
||||
alt="car"
|
||||
/>
|
||||
|
||||
<div>{{ car.replace(/_/g, ' ').split(':')[0] }}</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</ul> -->
|
||||
<!-- </div> -->
|
||||
|
||||
<div class="schedule-wrapper" v-if="train.timetableData">
|
||||
<ul class="stop_list">
|
||||
@@ -60,9 +37,7 @@
|
||||
<b>{{ stop.stopNameRAW }} </b>: <span v-html="stop.comments"></span>
|
||||
</div>
|
||||
|
||||
<span
|
||||
v-if="stop.departureLine == train.timetableData!.followingStops[i + 1].arrivalLine && !/sbl/gi.test(stop.departureLine!)"
|
||||
>
|
||||
<span v-if="stop.departureLine == train.timetableData!.followingStops[i + 1].arrivalLine && !/sbl/gi.test(stop.departureLine!)">
|
||||
{{ stop.departureLine }}
|
||||
</span>
|
||||
|
||||
@@ -89,10 +64,13 @@ import dateMixin from '../../mixins/dateMixin';
|
||||
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 TrainThumbnail from '../Global/TrainThumbnail.vue';
|
||||
import StockList from '../Global/StockList.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: { StopDate },
|
||||
components: { StopDate, TrainThumbnail, StockList },
|
||||
props: {
|
||||
train: {
|
||||
type: Object as PropType<Train>,
|
||||
@@ -106,6 +84,8 @@ export default defineComponent({
|
||||
|
||||
setup(props) {
|
||||
return {
|
||||
store: useStore(),
|
||||
|
||||
lastConfirmed: computed(() => {
|
||||
return props.train.timetableData!.followingStops.findIndex(
|
||||
(stop, i, stops) => stop.confirmed && !stops[i + 1]?.confirmed && !stops[i + 1]?.stopped
|
||||
@@ -142,8 +122,7 @@ export default defineComponent({
|
||||
end: stop.terminatesHere,
|
||||
delayed: stop.departureDelay > 0,
|
||||
sbl: /sbl/gi.test(stop.stopName),
|
||||
[stop.stopType.replaceAll(', ', '-')]:
|
||||
stop.stopType.match(new RegExp('ph|pm|pt')) && !stop.confirmed && !stop.beginsHere,
|
||||
[stop.stopType.replaceAll(', ', '-')]: stop.stopType.match(new RegExp('ph|pm|pt')) && !stop.confirmed && !stop.beginsHere,
|
||||
'minor-stop-active': this.activeMinorStops.includes(index),
|
||||
'last-confirmed': index == this.lastConfirmed && !stop.terminatesHere,
|
||||
};
|
||||
@@ -176,31 +155,7 @@ $stopNameClr: #22a8d1;
|
||||
}
|
||||
|
||||
.train-schedule {
|
||||
padding: 0 0.25em;
|
||||
}
|
||||
|
||||
.train-stock {
|
||||
padding: 0.25em 0.5em;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
ul.stock-list {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
overflow: auto;
|
||||
padding-bottom: 1em;
|
||||
|
||||
li > div {
|
||||
text-align: center;
|
||||
color: #aaa;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
img {
|
||||
max-height: 60px;
|
||||
max-width: 320px;
|
||||
}
|
||||
padding: 0 1em;
|
||||
}
|
||||
|
||||
.schedule-wrapper {
|
||||
|
||||
@@ -1,337 +0,0 @@
|
||||
<template>
|
||||
<div class="train-stats" v-click-outside="closeStats">
|
||||
<action-button class="stats_button" @click="toggleStatsOpen">
|
||||
<img :src="getIcon('stats')" :alt="$t('trains.stats')" />
|
||||
<p>{{ $t('trains.stats') }}</p>
|
||||
</action-button>
|
||||
|
||||
<transition name="stats-anim" class="stats_wrapper" tag="div">
|
||||
<div class="stats-body" v-if="trainStatsOpen">
|
||||
<h2 class="stats-header">
|
||||
<img :src="getIcon('stats')" :alt="$t('trains.stats')" />
|
||||
{{ $t('trains.stats') }}
|
||||
</h2>
|
||||
|
||||
<div class="stats-speed">
|
||||
<div class="title stats-title">
|
||||
{{ $t('trains.stats-speed') }}
|
||||
</div>
|
||||
<div class="stats-content">{{ speedStats.min }} | {{ speedStats.avg }} | {{ speedStats.max }}</div>
|
||||
</div>
|
||||
|
||||
<div class="stats-length">
|
||||
<div class="title stats-title">
|
||||
{{ $t('trains.stats-length') }}
|
||||
</div>
|
||||
<div class="stats-content">
|
||||
{{ timetableStats.min }} | {{ timetableStats.avg }} |
|
||||
{{ timetableStats.max }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats-categories">
|
||||
<div class="title stats-title">
|
||||
{{ $t('trains.stats-categories') }}
|
||||
</div>
|
||||
|
||||
<div class="category-list">
|
||||
<span class="category" v-for="[key, value] of categoryList" :key="key">
|
||||
<span class="category-type">{{ key }}</span>
|
||||
<span class="category-count">{{ value }}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="special-list">
|
||||
<span class="special twr">
|
||||
<span class="special-type">{{ $t('trains.stats-special-twr') }}</span>
|
||||
<span class="special-count">{{ specialTrainCount[0] }}</span>
|
||||
</span>
|
||||
|
||||
<span class="special skr">
|
||||
<span class="special-type">{{ $t('trains.stats-special-skr') }}</span>
|
||||
<span class="special-count">{{ specialTrainCount[1] }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats-locos">
|
||||
<div class="title stats-title">{{ $t('trains.stats-locos') }}</div>
|
||||
|
||||
<div class="loco-list stats-content">
|
||||
<div class="loco-item" v-for="(loco, i) in locoList" :key="i">{{ loco[0] }} | {{ loco[1] }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, inject } from 'vue';
|
||||
import imageMixin from '../../mixins/imageMixin';
|
||||
import Train from '../../scripts/interfaces/Train';
|
||||
import ActionButton from '../Global/ActionButton.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: { ActionButton },
|
||||
mixins: [imageMixin],
|
||||
|
||||
props: {
|
||||
trains: {
|
||||
type: Array as () => Train[],
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
trainStatsOpen: false,
|
||||
}),
|
||||
|
||||
methods: {
|
||||
toggleStatsOpen() {
|
||||
this.trainStatsOpen = !this.trainStatsOpen;
|
||||
},
|
||||
|
||||
closeStats() {
|
||||
this.trainStatsOpen = false;
|
||||
},
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
const speedStats = computed(() => {
|
||||
if (props.trains.length == 0) return { avg: '0', min: '0', max: '0' };
|
||||
|
||||
const trainList = props.trains.filter((train) => train.timetableData);
|
||||
|
||||
const avg = (trainList.reduce((acc, train) => acc + train.speed, 0) / trainList.length).toFixed(2);
|
||||
|
||||
const minMaxSpeed = trainList.reduce((acc, train) => {
|
||||
if (!train.timetableData) return acc;
|
||||
|
||||
acc[0] = !acc[0] || train.speed < acc[0] ? train.speed : acc[0];
|
||||
|
||||
acc[1] = !acc[1] || train.speed > acc[1] ? train.speed : acc[1];
|
||||
return acc;
|
||||
}, [] as any);
|
||||
|
||||
return {
|
||||
avg,
|
||||
min: minMaxSpeed[0].toString(),
|
||||
max: minMaxSpeed[1].toString(),
|
||||
};
|
||||
});
|
||||
|
||||
const timetableStats = computed(() => {
|
||||
if (props.trains.length == 0) return { avg: '0', min: '0', max: '0' };
|
||||
|
||||
const activeTrainsLength = props.trains.filter((train) => train.timetableData).length;
|
||||
|
||||
const avg = (
|
||||
props.trains.reduce((acc, train) => (train.timetableData ? acc + train.timetableData.routeDistance : acc), 0) /
|
||||
activeTrainsLength
|
||||
).toFixed(2);
|
||||
|
||||
const minMaxDistance = props.trains.reduce((acc, train) => {
|
||||
if (!train.timetableData) return acc;
|
||||
|
||||
acc[0] = !acc[0] || train.timetableData.routeDistance < acc[0] ? train.timetableData.routeDistance : acc[0];
|
||||
|
||||
acc[1] = !acc[1] || train.timetableData.routeDistance > acc[1] ? train.timetableData.routeDistance : acc[1];
|
||||
return acc;
|
||||
}, [] as any);
|
||||
|
||||
return {
|
||||
avg,
|
||||
min: minMaxDistance[0].toString(),
|
||||
max: minMaxDistance[1].toString(),
|
||||
};
|
||||
});
|
||||
|
||||
const categoryList = computed(() => {
|
||||
const map = props.trains.reduce((acc, train) => {
|
||||
if (!train.timetableData || !train.timetableData.category) return acc;
|
||||
|
||||
acc.set(
|
||||
train.timetableData.category,
|
||||
acc.get(train.timetableData.category) ? acc.get(train.timetableData.category) + 1 : 1
|
||||
);
|
||||
|
||||
return acc;
|
||||
}, new Map());
|
||||
|
||||
return new Map([...map.entries()].sort((a, b) => b[1] - a[1]));
|
||||
});
|
||||
|
||||
const locoList = computed(() => {
|
||||
const map: Map<string, number> = props.trains.reduce((acc, train) => {
|
||||
if (!train.timetableData || !train.locoType) return acc;
|
||||
|
||||
acc.set(train.locoType, acc.get(train.locoType) ? acc.get(train.locoType) + 1 : 1);
|
||||
|
||||
return acc;
|
||||
}, new Map());
|
||||
|
||||
const sorted = [...map.entries()].sort((a, b) => b[1] - a[1]).filter((v, i) => i < 3);
|
||||
|
||||
return sorted;
|
||||
});
|
||||
|
||||
const specialTrainCount = computed(() => {
|
||||
const twrList = props.trains.filter((train) => train.timetableData && train.timetableData.TWR);
|
||||
|
||||
const skrList = props.trains.filter((train) => train.timetableData && train.timetableData.SKR);
|
||||
|
||||
return [twrList.length, skrList.length];
|
||||
});
|
||||
|
||||
/* Inject list from TrainsView for category filter */
|
||||
const chosenTrainCategories = inject('chosenTrainCategories') as string[];
|
||||
|
||||
return {
|
||||
speedStats,
|
||||
timetableStats,
|
||||
categoryList,
|
||||
locoList,
|
||||
specialTrainCount,
|
||||
chosenTrainCategories,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../styles/responsive';
|
||||
|
||||
.stats-anim {
|
||||
&-enter-active,
|
||||
&-leave-active {
|
||||
transition: all 150ms ease-out;
|
||||
}
|
||||
|
||||
&-enter-from,
|
||||
&-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
}
|
||||
|
||||
.train-stats {
|
||||
position: relative;
|
||||
top: 0;
|
||||
z-index: 15;
|
||||
}
|
||||
|
||||
.stats {
|
||||
&_wrapper {
|
||||
margin-bottom: 0.5em;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&-header {
|
||||
display: flex;
|
||||
margin-bottom: 0.85em;
|
||||
|
||||
img {
|
||||
vertical-align: middle;
|
||||
margin-right: 0.35em;
|
||||
}
|
||||
}
|
||||
|
||||
&-body {
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
max-width: 700px;
|
||||
width: 100%;
|
||||
|
||||
top: 100%;
|
||||
left: 0;
|
||||
|
||||
background: #222;
|
||||
border-radius: 0 1em 1em 1em;
|
||||
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
&-content {
|
||||
color: #ddd;
|
||||
}
|
||||
}
|
||||
|
||||
/* .category {
|
||||
cursor: pointer;
|
||||
} */
|
||||
|
||||
.category,
|
||||
.special {
|
||||
&-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
margin-right: 0.4em;
|
||||
margin-bottom: 0.4em;
|
||||
|
||||
&-type,
|
||||
&-count {
|
||||
display: inline-block;
|
||||
padding: 0.2em 0.4em;
|
||||
}
|
||||
|
||||
&-type {
|
||||
background: #585858;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&-count {
|
||||
background: #ffc014;
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
|
||||
.special {
|
||||
&-list {
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
&-count {
|
||||
background: gray;
|
||||
color: white;
|
||||
}
|
||||
|
||||
&.twr > &-type {
|
||||
background-color: var(--clr-twr);
|
||||
|
||||
color: black;
|
||||
}
|
||||
|
||||
&.skr > &-type {
|
||||
background-color: var(--clr-skr);
|
||||
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.warning {
|
||||
display: inline-block;
|
||||
margin-right: 0.4em;
|
||||
padding: 0.2em 0.3em;
|
||||
|
||||
color: black;
|
||||
font-weight: bold;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
@include smallScreen {
|
||||
.stats-body {
|
||||
display: block;
|
||||
width: 100%;
|
||||
|
||||
border-radius: 0 0 1em 1em;
|
||||
}
|
||||
|
||||
.train-stats {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -12,22 +12,18 @@
|
||||
{{ $t('trains.no-trains') }}
|
||||
</div>
|
||||
|
||||
<!-- <div class="timeouts-warning" v-if="trainNumbersWithTimeouts.length == 0">
|
||||
<b class="warning-timeout">?</b>
|
||||
{{ $t('trains.timeout') }}
|
||||
</div> -->
|
||||
|
||||
<ul class="train-list" v-else>
|
||||
<transition-group name="list-anim" tag="ul" class="train-list" v-else>
|
||||
<li
|
||||
class="train-row"
|
||||
v-for="train in currentTrains"
|
||||
:key="train.trainId"
|
||||
@click.stop="selectModalTrain(train.trainId)"
|
||||
@keydown.enter="selectModalTrain(train.trainId)"
|
||||
tabindex="0"
|
||||
@click.stop="selectModalTrain(train.trainId, $event.currentTarget)"
|
||||
@keydown.enter="selectModalTrain(train.trainId, $event.currentTarget)"
|
||||
>
|
||||
<TrainInfo :train="train" />
|
||||
</li>
|
||||
</ul>
|
||||
</transition-group>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
@@ -71,18 +67,9 @@ export default defineComponent({
|
||||
id: string | number;
|
||||
dir: number;
|
||||
},
|
||||
distanceLimitExceeded: computed(
|
||||
() => props.trains.findIndex(({ timetableData }) => timetableData && timetableData.routeDistance > 200) != -1
|
||||
),
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
trainNumbersWithTimeouts() {
|
||||
return this.store.trainList.filter((train) => train.isTimeout).map((train) => train.trainNo);
|
||||
},
|
||||
},
|
||||
|
||||
activated() {
|
||||
const query = this.$route.query;
|
||||
if (query.trainNo && query.driverName) {
|
||||
@@ -98,6 +85,7 @@ export default defineComponent({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../styles/responsive.scss';
|
||||
@import '../../styles/animations.scss';
|
||||
|
||||
.anim {
|
||||
&-enter-from,
|
||||
@@ -158,7 +146,7 @@ img.train-image {
|
||||
|
||||
.train {
|
||||
&-list {
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
|
||||
@include smallScreen() {
|
||||
width: 100%;
|
||||
|
||||
@@ -1,28 +1,46 @@
|
||||
import { JournalFilterType } from "../../scripts/enums/JournalFilterType";
|
||||
import { JournalTimetableFilter } from "../../types/Journal/JournalTimetablesTypes";
|
||||
import { JournalFilterSection, JournalFilterType } from '../../scripts/enums/JournalFilterType';
|
||||
import { JournalFilter } from '../../scripts/types/JournalTimetablesTypes';
|
||||
|
||||
export const journalTimetableFilters: JournalTimetableFilter[] = [
|
||||
export const journalTimetableFilters: JournalFilter[] = [
|
||||
{
|
||||
id: JournalFilterType.all,
|
||||
filterSection: 'timetable-status',
|
||||
id: JournalFilterType.ALL,
|
||||
filterSection: JournalFilterSection.TIMETABLE_STATUS,
|
||||
isActive: true,
|
||||
},
|
||||
|
||||
{
|
||||
id: JournalFilterType.active,
|
||||
filterSection: 'timetable-status',
|
||||
id: JournalFilterType.ACTIVE,
|
||||
filterSection: JournalFilterSection.TIMETABLE_STATUS,
|
||||
isActive: false,
|
||||
},
|
||||
|
||||
{
|
||||
id: JournalFilterType.fulfilled,
|
||||
filterSection: 'timetable-status',
|
||||
id: JournalFilterType.FULFILLED,
|
||||
filterSection: JournalFilterSection.TIMETABLE_STATUS,
|
||||
isActive: false,
|
||||
},
|
||||
|
||||
{
|
||||
id: JournalFilterType.abandoned,
|
||||
filterSection: 'timetable-status',
|
||||
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,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,33 +1,58 @@
|
||||
import { TrainFilterType } from '../../scripts/enums/TrainFilterType';
|
||||
import { TrainFilter } from '../../types/Trains/TrainOptionsTypes';
|
||||
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.comments,
|
||||
id: TrainFilterType.noComments,
|
||||
section: TrainFilterSection.COMMENTS,
|
||||
isActive: true,
|
||||
},
|
||||
|
||||
{
|
||||
id: TrainFilterType.withTimetable,
|
||||
section: TrainFilterSection.TIMETABLE,
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
id: TrainFilterType.noTimetable,
|
||||
section: TrainFilterSection.TIMETABLE,
|
||||
isActive: true,
|
||||
},
|
||||
];
|
||||
@@ -37,6 +62,10 @@ export const sorterOptions = [
|
||||
id: 'distance',
|
||||
value: 'kilometraż',
|
||||
},
|
||||
{
|
||||
id: 'id',
|
||||
value: 'id rozkładu',
|
||||
},
|
||||
{
|
||||
id: 'progress',
|
||||
value: 'przebyta trasa',
|
||||
|
||||
@@ -1,41 +1,38 @@
|
||||
{
|
||||
"optionSections": ["reality", "package-access", "access", "control", "addons", "blockades", "signals", "status"],
|
||||
|
||||
"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",
|
||||
"name": "real",
|
||||
"iconName": "lock",
|
||||
"section": "access",
|
||||
"section": "reality",
|
||||
"value": true,
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"id": "fictional",
|
||||
"name": "fictional",
|
||||
"iconName": "user",
|
||||
"section": "access",
|
||||
"section": "reality",
|
||||
"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,
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"id": "non-public",
|
||||
"name": "nonPublic",
|
||||
"iconName": "user",
|
||||
"section": "access",
|
||||
"value": true,
|
||||
"defaultValue": true
|
||||
@@ -43,7 +40,6 @@
|
||||
{
|
||||
"id": "unavailable",
|
||||
"name": "unavailable",
|
||||
"iconName": "user",
|
||||
"section": "access",
|
||||
"value": false,
|
||||
"defaultValue": false
|
||||
@@ -51,7 +47,6 @@
|
||||
{
|
||||
"id": "abandoned",
|
||||
"name": "abandoned",
|
||||
"iconName": "user",
|
||||
"section": "access",
|
||||
"value": false,
|
||||
"defaultValue": false
|
||||
@@ -59,7 +54,6 @@
|
||||
{
|
||||
"id": "SPK",
|
||||
"name": "SPK",
|
||||
"iconName": "SPK",
|
||||
"section": "control",
|
||||
"value": true,
|
||||
"defaultValue": true
|
||||
@@ -67,7 +61,6 @@
|
||||
{
|
||||
"id": "SCS",
|
||||
"name": "SCS",
|
||||
"iconName": "SCS",
|
||||
"section": "control",
|
||||
"value": true,
|
||||
"defaultValue": true
|
||||
@@ -75,15 +68,21 @@
|
||||
{
|
||||
"id": "SPE",
|
||||
"name": "SPE",
|
||||
"iconName": "SPE",
|
||||
"section": "control",
|
||||
"value": true,
|
||||
"defaultValue": true
|
||||
},
|
||||
|
||||
{
|
||||
"id": "SPK-M",
|
||||
"name": "mechaniczne+SPK",
|
||||
"section": "control",
|
||||
"value": true,
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"id": "manual",
|
||||
"name": "ręczne",
|
||||
"iconName": "ręczne",
|
||||
"id": "SCS-M",
|
||||
"name": "mechaniczne+SCS",
|
||||
"section": "control",
|
||||
"value": true,
|
||||
"defaultValue": true
|
||||
@@ -91,7 +90,27 @@
|
||||
{
|
||||
"id": "mechanical",
|
||||
"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",
|
||||
"value": true,
|
||||
"defaultValue": true
|
||||
@@ -99,23 +118,34 @@
|
||||
{
|
||||
"id": "SUP",
|
||||
"name": "SUP",
|
||||
"iconName": "SUP",
|
||||
"section": "control",
|
||||
"section": "addons",
|
||||
"value": true,
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"id": "noSUP",
|
||||
"name": "noSUP",
|
||||
"section": "addons",
|
||||
"value": true,
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"id": "SBL",
|
||||
"name": "SBL",
|
||||
"iconName": "SBL",
|
||||
"section": "routes",
|
||||
"section": "blockades",
|
||||
"value": true,
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"id": "PBL",
|
||||
"name": "PBL",
|
||||
"section": "blockades",
|
||||
"value": true,
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"id": "modern",
|
||||
"name": "współczesna",
|
||||
"iconName": "współczesna",
|
||||
"section": "signals",
|
||||
"value": true,
|
||||
"defaultValue": true
|
||||
@@ -123,7 +153,6 @@
|
||||
{
|
||||
"id": "semaphores",
|
||||
"name": "kształtowa",
|
||||
"iconName": "kształtowa",
|
||||
"section": "signals",
|
||||
"value": true,
|
||||
"defaultValue": true
|
||||
@@ -131,7 +160,6 @@
|
||||
{
|
||||
"id": "mixed",
|
||||
"name": "mieszana",
|
||||
"iconName": "mieszana",
|
||||
"section": "signals",
|
||||
"value": true,
|
||||
"defaultValue": true
|
||||
@@ -139,7 +167,6 @@
|
||||
{
|
||||
"id": "historical",
|
||||
"name": "historyczna",
|
||||
"iconName": "historyczna",
|
||||
"section": "signals",
|
||||
"value": true,
|
||||
"defaultValue": true
|
||||
@@ -148,7 +175,6 @@
|
||||
{
|
||||
"id": "free",
|
||||
"name": "free",
|
||||
"iconName": "",
|
||||
|
||||
"section": "status",
|
||||
"value": false,
|
||||
@@ -157,7 +183,6 @@
|
||||
{
|
||||
"id": "occupied",
|
||||
"name": "occupied",
|
||||
"iconName": "",
|
||||
|
||||
"section": "status",
|
||||
"value": true,
|
||||
@@ -166,7 +191,6 @@
|
||||
{
|
||||
"id": "endingStatus",
|
||||
"name": "endingStatus",
|
||||
"iconName": "",
|
||||
|
||||
"section": "status",
|
||||
"value": true,
|
||||
@@ -175,7 +199,6 @@
|
||||
{
|
||||
"id": "afkStatus",
|
||||
"name": "afkStatus",
|
||||
"iconName": "",
|
||||
|
||||
"section": "status",
|
||||
"value": true,
|
||||
@@ -184,7 +207,6 @@
|
||||
{
|
||||
"id": "noSpaceStatus",
|
||||
"name": "noSpaceStatus",
|
||||
"iconName": "",
|
||||
|
||||
"section": "status",
|
||||
"value": true,
|
||||
@@ -193,7 +215,6 @@
|
||||
{
|
||||
"id": "unavailableStatus",
|
||||
"name": "unavailableStatus",
|
||||
"iconName": "",
|
||||
|
||||
"section": "status",
|
||||
"value": true,
|
||||
@@ -254,7 +275,6 @@
|
||||
{
|
||||
"id": "include-selected",
|
||||
"name": "include-selected",
|
||||
"iconName": "",
|
||||
"section": "mode",
|
||||
"value": true,
|
||||
"defaultValue": true
|
||||
@@ -262,7 +282,6 @@
|
||||
{
|
||||
"id": "save",
|
||||
"name": "save",
|
||||
"iconName": "",
|
||||
"section": "mode",
|
||||
"value": true,
|
||||
"defaultValue": true
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"general": {
|
||||
"and": " and "
|
||||
"and": " and ",
|
||||
"refresh": "REFRESH",
|
||||
"TWR": "High risk freight train",
|
||||
"SKR": "Train with exceeded gauge"
|
||||
},
|
||||
"app": {
|
||||
"sceneries": "SCENERIES",
|
||||
@@ -14,6 +17,9 @@
|
||||
"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!",
|
||||
@@ -34,7 +40,7 @@
|
||||
"desc": {
|
||||
"control-type": "Control 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)",
|
||||
"TWB-all": "This scenery has two-way route blockade on all routes",
|
||||
"TWB-routes": "This scenery has two-way route blockade on following routes: ",
|
||||
@@ -93,47 +99,75 @@
|
||||
"search-dispatcher": "Dispatcher name",
|
||||
"search-station": "Scenery name",
|
||||
"search-author": "Timetable author name",
|
||||
"search-date": "Timetable date (CEST / GMT+2)",
|
||||
"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-distance": "distance",
|
||||
"sort-routeDistance": "route distance",
|
||||
"sort-timetable": "train no.",
|
||||
"sort-progress": "route progress",
|
||||
"sort-delay": "current delay",
|
||||
"sort-id": "timetable id",
|
||||
|
||||
"sort-total-stops": "total stops",
|
||||
"sort-allStopsCount": "total stops",
|
||||
"sort-beginDate": "date",
|
||||
"sort-timetableId": "timetable ID",
|
||||
"sort-timestampFrom": "date",
|
||||
"sort-duration": "duration",
|
||||
|
||||
"filter-comments": "COMMENTS",
|
||||
"filter-twr": "TWR",
|
||||
"filter-skr": "SKR",
|
||||
"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": {
|
||||
"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": {
|
||||
"quick": "QUICK FILTERS",
|
||||
"reality": "SCENERY REALITY",
|
||||
"package-access": "IN-GAME AVAILABILITY",
|
||||
"access": "GENERAL AVAILABILITY",
|
||||
"control": "CONTROLS",
|
||||
"signals": "SIGNALLING",
|
||||
"addons": "ADDITIONAL PROGRAMS",
|
||||
"blockades": "BLOCK SIGNALLING",
|
||||
"status": "ONLINE STATUS"
|
||||
},
|
||||
|
||||
"all-available": "ALL AVAILABLE",
|
||||
"all-free": "CURRENTLY FREE",
|
||||
|
||||
"endingStatus": "ENDS SOON",
|
||||
"afkStatus": "AFK",
|
||||
"noSpaceStatus": "NO SPACE",
|
||||
"unavailableStatus": "UNAVAILABLE",
|
||||
|
||||
"title": "STATION FILTER",
|
||||
"default": "DEFAULT",
|
||||
"not-default": "OTHER",
|
||||
"title": "STATION FILTERS",
|
||||
"default": "IN-GAME",
|
||||
"not-default": "ADDITIONAL",
|
||||
"real": "REAL",
|
||||
"fictional": "FICTIONAL",
|
||||
"unavailable": "UNSUPPORTED",
|
||||
@@ -141,12 +175,22 @@
|
||||
"abandoned": "ABANDONED",
|
||||
|
||||
"SPK": "SPK",
|
||||
"SPK-R": "SPK + MANUAL",
|
||||
"SPK-M": "SPK + MECH.",
|
||||
"SCS": "SCS",
|
||||
"SCS-R": "SCS + MANUAL",
|
||||
"SCS-M": "SCS + MECH.",
|
||||
"SPE": "SPE",
|
||||
|
||||
"manual": "MANUAL",
|
||||
"mechanical": "MECHANICAL",
|
||||
"SUP": "SUP",
|
||||
"SBL": "SBL",
|
||||
|
||||
"SUP": "SUP (RASP-UZK)",
|
||||
"noSUP": "WITHOUT SUP",
|
||||
|
||||
"SBL": "AUTOMATIC (SBL)",
|
||||
"PBL": "SEMIAUTOMATIC (PBL)",
|
||||
|
||||
"modern": "MODERN",
|
||||
"semaphores": "SEMAPHORES",
|
||||
"mixed": "MIXED",
|
||||
@@ -167,7 +211,7 @@
|
||||
"hour": "h",
|
||||
"no-limit": "NO LIMIT",
|
||||
"include-selected": "INCLUDE SELECTED",
|
||||
"save": "SAVE FILTERS",
|
||||
"save": "REMEMBER FILTERS",
|
||||
"reset": "RESET FILTERS",
|
||||
"close": "CLOSE FILTERS"
|
||||
},
|
||||
@@ -179,9 +223,11 @@
|
||||
"dispatcher-lvl": "Dispatcher\nlevel",
|
||||
"routes": "Routes\ndouble / single",
|
||||
"general": "General info",
|
||||
"users": "Drivers online",
|
||||
"spawns": "Spawns online",
|
||||
"timetables": "Active timetables",
|
||||
"user": "Drivers online",
|
||||
"spawn": "Spawns online",
|
||||
"timetableAll": "Active timetables",
|
||||
"timetableConfirmed": "Confirmed timetables",
|
||||
"timetableUnconfirmed": "Unconfirmed timetables",
|
||||
"no-stations": "No stations to show here!",
|
||||
"scenery-search": "Search for scenery..."
|
||||
},
|
||||
@@ -232,6 +278,7 @@
|
||||
"title": "DISPATCHER HISTORY",
|
||||
"loading": "Loading dispatcher history data...",
|
||||
"no-history": "No dispatcher history found!",
|
||||
"data-refreshed-at": "Data refreshed at",
|
||||
|
||||
"section-timetables": "TIMETABLES",
|
||||
"section-dispatchers": "DISPATCHERS",
|
||||
@@ -241,7 +288,7 @@
|
||||
|
||||
"route-length": "Route length:",
|
||||
"station-count": "Stations:",
|
||||
"dispatcher-name": "Created by",
|
||||
"dispatcher-name": "Author",
|
||||
"timetable-day": "Timetable created at",
|
||||
"timetable-active": "ACTIVE",
|
||||
"timetable-fulfilled": "FULFILLED",
|
||||
@@ -249,13 +296,15 @@
|
||||
|
||||
"online-since": "ONLINE SINCE",
|
||||
"duty-lasted": "The duty lasted",
|
||||
"minutes": "{minutes} mins",
|
||||
"hours": "{hours}h {minutes} mins",
|
||||
|
||||
"stock-info": "STOCK INFO",
|
||||
"hours": "{value} hour | {value} hours",
|
||||
"minutes": "{value} min | {value} mins",
|
||||
"seconds": "{value} s",
|
||||
|
||||
"stock-info": "EXTRA INFO",
|
||||
"stock-length": "Length",
|
||||
"stock-mass": "Mass",
|
||||
"stock-max-speed": "Maximum registered speed",
|
||||
"stock-max-speed": "Max. speed",
|
||||
|
||||
"load-data": "Load further data...",
|
||||
|
||||
@@ -270,10 +319,13 @@
|
||||
"stats-distance": "DISTANCE",
|
||||
"stats-stations": "STATIONS",
|
||||
|
||||
"timetable-stats-total": "Today, dispatchers made so far {count} with total distance of {distance}",
|
||||
"timetable-stats-longest": "The longest timetable today is #{id} made by {author} for {driver} - {distance}",
|
||||
"timetable-stats-most-active": "The most active dispatcher today is {dispatcher} who created {count}",
|
||||
"timetable-stats-most-active-many": "The most active dispatchers today are {dispatchers} who created {count} each",
|
||||
"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",
|
||||
|
||||
@@ -284,7 +336,18 @@
|
||||
"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! :/"
|
||||
"stats-error": "Oops! An unexpected error occurred while trying to fetch statistics! :/",
|
||||
|
||||
"timetable-location-signal": "signal:",
|
||||
"timetable-location-route": "route:",
|
||||
|
||||
"history-name": "Scenery name",
|
||||
"history-hash": "Hash",
|
||||
"history-dispatcher": "Dispatcher",
|
||||
"history-level": "Level",
|
||||
"history-rate": "Rate",
|
||||
"history-region": "Region",
|
||||
"history-date": "Service date"
|
||||
},
|
||||
"scenery": {
|
||||
"users": "PLAYERS ONLINE",
|
||||
@@ -299,22 +362,41 @@
|
||||
"history-btn": "View the dispatcher history",
|
||||
"info-btn": "Return to the scenery view",
|
||||
"authors-title": "Scenery author | Scenery authors",
|
||||
"abbrev": "Station symbol:",
|
||||
"lines-title": "Real lines",
|
||||
"project-title": "Project name",
|
||||
"one-way-routes": "One way routes",
|
||||
"two-way-routes": "Two way routes",
|
||||
|
||||
"option-active-timetables": "Active timetables",
|
||||
"option-timetables-history": "Scenery timetables history",
|
||||
"option-dispatchers-history": "Scenery dispatchers history",
|
||||
"option-timetables-history": "Timetables history",
|
||||
"option-dispatchers-history": "Dispatchers history",
|
||||
|
||||
"timetable-author-title": "Issued by",
|
||||
"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",
|
||||
|
||||
"dispatchers-history-hash": "Hash",
|
||||
"dispatchers-history-dispatcher": "Dispatcher",
|
||||
"dispatchers-history-level": "Level",
|
||||
"dispatchers-history-rate": "Rate",
|
||||
"dispatchers-history-date": "Service date",
|
||||
|
||||
"req-level": "all dispatcher levels | dispatcher level {lvl} required | dispatcher level {lvl} required",
|
||||
"history-list-empty": "No recorded scenery history!",
|
||||
|
||||
"forum-topic": "Official {name} forum topic"
|
||||
"forum-topic": "Official {name} forum topic",
|
||||
|
||||
"pragotron-link": "Timetable pallet board (beta)",
|
||||
"tablice-link": "Timetable summary board (by Thundo)",
|
||||
|
||||
"bottom-info": "Show full history in the Journal tab"
|
||||
},
|
||||
"availability": {
|
||||
"title": "Availability",
|
||||
@@ -329,7 +411,19 @@
|
||||
"end": "Timetable terminates here",
|
||||
"terminated": "Timetable terminated",
|
||||
"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": {
|
||||
"title": "TIMETABLE JOURNAL",
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"general": {
|
||||
"and": " oraz "
|
||||
"and": " oraz ",
|
||||
"refresh": "ODŚWIEŻ",
|
||||
"TWR": "Towar niebezpieczny wysokiego ryzyka",
|
||||
"SKR": "Przekroczona skrajnia"
|
||||
},
|
||||
"app": {
|
||||
"sceneries": "SCENERIE",
|
||||
@@ -14,7 +17,9 @@
|
||||
"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!",
|
||||
@@ -22,7 +27,6 @@
|
||||
"confirm-button": "ZAKTUALIZUJ",
|
||||
"later-button": "PÓŹNIEJ"
|
||||
},
|
||||
|
||||
"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!",
|
||||
@@ -95,14 +99,18 @@
|
||||
"search-dispatcher": "Nick dyżurnego",
|
||||
"search-station": "Nazwa scenerii",
|
||||
"search-author": "Nick autora rozkładu jazdy",
|
||||
"search-date": "Data rozkładu jazdy (czas polski)",
|
||||
"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-distance": "kilometraż",
|
||||
"sort-total-stops": "stacje",
|
||||
"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ść",
|
||||
@@ -112,23 +120,47 @@
|
||||
"sort-delay": "opóźnienie",
|
||||
"sort-comments": "uwagi ekspl.",
|
||||
|
||||
"filter-comments": "UWAGI EKSPLOATACYJNE",
|
||||
"filter-twr": "TWR",
|
||||
"filter-skr": "PRZEKR. SKRAJNIA",
|
||||
"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": {
|
||||
"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": {
|
||||
"quick": "SZYBKIE FILTRY",
|
||||
"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"
|
||||
},
|
||||
|
||||
"all-available": "WSZYSTKIE DOSTĘPNE",
|
||||
"all-free": "WSZYSTKIE WOLNE",
|
||||
|
||||
"endingStatus": "KOŃCZY",
|
||||
"afkStatus": "Z/W",
|
||||
"noSpaceStatus": "BRAK MIEJSCA",
|
||||
@@ -144,18 +176,29 @@
|
||||
"abandoned": "WYCOFANA",
|
||||
|
||||
"SPK": "SPK",
|
||||
"SPK-R": "SPK + RĘCZNE",
|
||||
"SPK-M": "SPK + MECH.",
|
||||
"SCS": "SCS",
|
||||
"SCS-R": "SCS + RĘCZNE",
|
||||
"SCS-M": "SCS + MECH.",
|
||||
"SPE": "SPE",
|
||||
"manual": "RĘCZNE",
|
||||
"SUP": "SUP",
|
||||
"SBL": "SBL",
|
||||
|
||||
"SUP": "SUP (RASP-UZK)",
|
||||
"noSUP": "BEZ SUP",
|
||||
|
||||
"SBL": "SAMOCZYNNA",
|
||||
"PBL": "PÓŁSAMOCZYNNA",
|
||||
|
||||
"mechanical": "MECHANICZNE",
|
||||
"modern": "WSPÓŁCZESNA",
|
||||
"semaphores": "KSZTAŁTOWA",
|
||||
"mixed": "MIESZANA",
|
||||
"historical": "HISTORYCZNA",
|
||||
|
||||
"free": "WOLNA",
|
||||
"occupied": "ZAJĘTA",
|
||||
|
||||
"sliders": {
|
||||
"min-lvl": "MIN. WYMAGANY POZIOM DYŻURNEGO",
|
||||
"max-lvl": "MAKS. WYMAGANY POZIOM DYŻURNEGO",
|
||||
@@ -164,27 +207,31 @@
|
||||
"routes-2t-cat": "SZLAKI DWUTOROWE ZELEKTR. (MINIMUM)",
|
||||
"routes-2t-other": "SZLAKI DWUTOROWE NIEZELEKTR. (MINIMUM)"
|
||||
},
|
||||
|
||||
"authors-search": "Szukaj autora (uwzględnia inne filtry)",
|
||||
"minimum-hours-title": "POKAŻ TYLKO SCENERIE DOSTĘPNE MINIMUM DO:",
|
||||
"now": "TERAZ",
|
||||
"hour": " godz.",
|
||||
"no-limit": "BEZ LIMITU",
|
||||
"include-selected": "POKAŻ ZAZNACZONE",
|
||||
"save": "ZAPISZ FILTRY",
|
||||
"save": "ZAPAMIĘTAJ FILTRY",
|
||||
"reset": "RESETUJ FILTRY",
|
||||
"close": "ZAMKNIJ FILTRY"
|
||||
},
|
||||
"sceneries": {
|
||||
"station": "Stacja",
|
||||
"abbr": "Skrót\nposterunku",
|
||||
"min-lvl": "Min. poziom\ndyżurnego",
|
||||
"status": "Status",
|
||||
"dispatcher": "Dyżurny",
|
||||
"dispatcher-lvl": "Poziom\ndyżurnego",
|
||||
"routes": "Szlaki\n2tor / 1tor",
|
||||
"general": "Informacje\nogólne",
|
||||
"users": "Maszyniści online",
|
||||
"spawns": "Otwarte spawny",
|
||||
"timetables": "Aktywne rozkłady jazdy",
|
||||
"user": "Maszyniści online",
|
||||
"spawn": "Otwarte spawny",
|
||||
"timetableAll": "Aktywne rozkłady jazdy",
|
||||
"timetableConfirmed": "Zatwierdzone rozkłady jazdy",
|
||||
"timetableUnconfirmed": "Niezatwierdzone rozkłady jazdy",
|
||||
"no-stations": "Brak stacji do wyświetlenia!",
|
||||
"scenery-search": "Wyszukaj scenerię..."
|
||||
},
|
||||
@@ -236,6 +283,7 @@
|
||||
"title": "HISTORIA DYŻURÓW",
|
||||
"loading": "Ładowanie historii dyżurów...",
|
||||
"no-history": "Brak historii dyżurów dla tej scenerii!",
|
||||
"data-refreshed-at": "Dane odświeżone o",
|
||||
|
||||
"section-timetables": "ROZKŁADY JAZDY",
|
||||
"section-dispatchers": "DYŻURNI",
|
||||
@@ -245,21 +293,22 @@
|
||||
|
||||
"online-since": "ONLINE OD",
|
||||
"duty-lasted": "Dyżur trwał",
|
||||
"minutes": "{minutes} min.",
|
||||
"hours": "{hours} godz. {minutes} min.",
|
||||
"hours": "{value} godz.",
|
||||
"minutes": "{value} min.",
|
||||
"seconds": "{value} sek.",
|
||||
|
||||
"route-length": "Kilometraż:",
|
||||
"station-count": "Stacje:",
|
||||
"dispatcher-name": "Wystawiony przez dyżurnego",
|
||||
"dispatcher-name": "Autor",
|
||||
"timetable-day": "Rozkład z dnia",
|
||||
"timetable-active": "AKTYWNY",
|
||||
"timetable-fulfilled": "WYPEŁNIONY",
|
||||
"timetable-abandoned": "PORZUCONY",
|
||||
|
||||
"stock-info": "INFORMACJE O SKŁADZIE",
|
||||
"stock-info": "DODATKOWE INFORMACJE",
|
||||
"stock-length": "Długość",
|
||||
"stock-mass": "Masa",
|
||||
"stock-max-speed": "Maks. zarejestrowana prędkość",
|
||||
"stock-max-speed": "Prędkość maks.",
|
||||
|
||||
"load-data": "Pobierz dalszą historię...",
|
||||
|
||||
@@ -274,10 +323,12 @@
|
||||
"stats-distance": "DYSTANS",
|
||||
"stats-stations": "STACJE",
|
||||
|
||||
"timetable-stats-total": "Dyżurni stworzyli dziś {count} o łącznym dystansie {distance}",
|
||||
"timetable-stats-longest": "Najdłuższym rozkładem jazdy jest dzisiaj #{id} stworzony przez dyżurnego {author} dla maszynisty {driver} - {distance}",
|
||||
"timetable-stats-most-active": "Dzisiejszym najaktywniejszym dyżurnym jest {dispatcher}, który stworzył {count}",
|
||||
"timetable-stats-most-active-many": "Dzisiejszymi najaktywniejszymi dyżurnymi są {dispatchers}, którzy stworzyli po {count}",
|
||||
"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",
|
||||
|
||||
@@ -288,7 +339,18 @@
|
||||
"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! :/"
|
||||
"stats-error": "Ups! Wystąpił błąd podczas próby pobrania statystyk! :/",
|
||||
|
||||
"timetable-location-signal": "semafor:",
|
||||
"timetable-location-route": "szlak:",
|
||||
|
||||
"history-name": "Sceneria",
|
||||
"history-hash": "Hash",
|
||||
"history-dispatcher": "Dyżurny",
|
||||
"history-level": "Poziom",
|
||||
"history-rate": "Ocena",
|
||||
"history-region": "Region",
|
||||
"history-date": "Data służby"
|
||||
},
|
||||
"scenery": {
|
||||
"users": "GRACZE ONLINE",
|
||||
@@ -301,24 +363,43 @@
|
||||
"no-scenery": "Ups! Ta sceneria nie istnieje!",
|
||||
"return-btn": "Wróć na stronę główną",
|
||||
"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",
|
||||
"abbrev": "Skrót posterunku:",
|
||||
"lines-title": "Rzeczywiste linie",
|
||||
"project-title": "Projekt",
|
||||
"one-way-routes": "Szlaki jednotorowe",
|
||||
"two-way-routes": "Szlaki dwutorowe",
|
||||
|
||||
"option-active-timetables": "Aktywne rozkłady jazdy",
|
||||
"option-timetables-history": "Historia rozkładów scenerii",
|
||||
"option-dispatchers-history": "Historia dyżurów scenerii",
|
||||
"option-timetables-history": "Historia rozkładów",
|
||||
"option-dispatchers-history": "Historia dyżurów",
|
||||
|
||||
"timetable-author-title": "Wydany przez",
|
||||
"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",
|
||||
|
||||
"dispatchers-history-hash": "Hash",
|
||||
"dispatchers-history-dispatcher": "Dyżurny",
|
||||
"dispatchers-history-level": "Poziom",
|
||||
"dispatchers-history-rate": "Ocena",
|
||||
"dispatchers-history-date": "Data służby",
|
||||
|
||||
"req-level": "ogólnodostępna | minimum {lvl} poziom dyżurnego | minimum {lvl} poziom dyżurnego",
|
||||
"history-list-empty": "Brak historii dla tej scenerii!",
|
||||
|
||||
"forum-topic": "Oficjalny wątek scenerii {name}"
|
||||
"forum-topic": "Oficjalny wątek scenerii {name}",
|
||||
|
||||
"pragotron-link": "Paletowa tablica informacyjna (beta)",
|
||||
"tablice-link": "Tablica informacyjna zbiorcza (autorstwa Thundo)",
|
||||
|
||||
"bottom-info": "Pokaż pełną historię w zakładce Dziennika"
|
||||
},
|
||||
"availability": {
|
||||
"title": "Dostępność",
|
||||
@@ -333,7 +414,19 @@
|
||||
"end": "Koniec rozkładu jazdy",
|
||||
"terminated": "Rozkład jazdy zakończony",
|
||||
"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": {
|
||||
"title": "DZIENNIK ROZKŁADÓW JAZDY"
|
||||
|
||||
@@ -11,6 +11,7 @@ import { createPinia } from 'pinia';
|
||||
const i18n = createI18n({
|
||||
locale: 'pl',
|
||||
legacy: false,
|
||||
warnHtmlMessage: false,
|
||||
fallbackLocale: 'pl',
|
||||
messages: {
|
||||
en: enLang,
|
||||
|
||||
@@ -1,50 +1,77 @@
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
methods: {
|
||||
localeDate(dateString: string, locale: string) {
|
||||
return new Date(dateString).toLocaleDateString(locale == 'pl' ? 'pl-PL' : 'en-GB', {
|
||||
weekday: 'long',
|
||||
day: 'numeric',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
});
|
||||
},
|
||||
|
||||
localeDay(dateString: string, locale: string) {
|
||||
return new Date(dateString).toLocaleDateString(locale == 'pl' ? 'pl-PL' : 'en-GB', {
|
||||
day: 'numeric',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
});
|
||||
},
|
||||
|
||||
localeTime(dateString: string, locale: string) {
|
||||
return new Date(dateString).toLocaleTimeString(locale == 'pl' ? 'pl-PL' : 'en-GB', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
});
|
||||
},
|
||||
|
||||
timestampToString(timestamp: number | null) {
|
||||
return timestamp
|
||||
? new Date(timestamp).toLocaleTimeString('pl-PL', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})
|
||||
: '';
|
||||
},
|
||||
|
||||
calculateDuration(timestampMs: number) {
|
||||
const minsTotal = Math.round(timestampMs / 60000);
|
||||
const hoursTotal = Math.floor(minsTotal / 60);
|
||||
const minsInHour = minsTotal % 60;
|
||||
|
||||
return minsTotal > 60
|
||||
? this.$t('journal.hours', { hours: hoursTotal, minutes: minsInHour })
|
||||
: this.$t('journal.minutes', { minutes: minsTotal });
|
||||
},
|
||||
},
|
||||
});
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
methods: {
|
||||
localeDate(dateString: string, locale: string) {
|
||||
return new Date(dateString).toLocaleDateString(locale == 'pl' ? 'pl-PL' : 'en-GB', {
|
||||
weekday: 'long',
|
||||
day: 'numeric',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
});
|
||||
},
|
||||
|
||||
localeDay(dateString: string, locale: string) {
|
||||
return new Date(dateString).toLocaleDateString(locale == 'pl' ? 'pl-PL' : 'en-GB', {
|
||||
day: 'numeric',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
});
|
||||
},
|
||||
|
||||
localeDateTime(dateString: string, locale: string) {
|
||||
return new Date(dateString).toLocaleString(locale == 'pl' ? 'pl-PL' : 'en-GB', {
|
||||
timeStyle: 'short',
|
||||
dateStyle: 'medium'
|
||||
});
|
||||
},
|
||||
|
||||
localeTime(dateString: string, locale: string) {
|
||||
return new Date(dateString).toLocaleTimeString(locale == 'pl' ? 'pl-PL' : 'en-GB', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
});
|
||||
},
|
||||
|
||||
stringToDate(dateString?: string) {
|
||||
return dateString ? new Date(dateString) : null;
|
||||
},
|
||||
|
||||
parseDateToTimeString(date: Date | null) {
|
||||
return (
|
||||
date?.toLocaleTimeString('pl-PL', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
}) || ''
|
||||
);
|
||||
},
|
||||
|
||||
timestampToString(timestamp: number | null) {
|
||||
return timestamp
|
||||
? new Date(timestamp).toLocaleTimeString('pl-PL', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})
|
||||
: '';
|
||||
},
|
||||
|
||||
calculateDuration(timestampMs: number, showSeconds = false) {
|
||||
const secondsTotal = Math.floor(timestampMs / 1000);
|
||||
const minsTotal = Math.round(timestampMs / 60000);
|
||||
const hoursTotal = Math.floor(minsTotal / 60);
|
||||
const minsInHour = minsTotal % 60;
|
||||
|
||||
return minsTotal >= 60
|
||||
? `${this.$t('journal.hours', { value: hoursTotal }, hoursTotal)} ${this.$t(
|
||||
'journal.minutes',
|
||||
{ value: minsInHour },
|
||||
minsInHour
|
||||
)}`
|
||||
: showSeconds && secondsTotal <= 60
|
||||
? this.$t('journal.seconds', { value: secondsTotal }, secondsTotal)
|
||||
: this.$t('journal.minutes', { value: minsTotal }, minsTotal);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
data: () => ({
|
||||
observer: null as IntersectionObserver | null,
|
||||
observerTarget: null as Element | null,
|
||||
}),
|
||||
|
||||
methods: {
|
||||
mountObserver(actionFunction: () => void, target: Element) {
|
||||
this.observer = new IntersectionObserver((entries) => {
|
||||
console.log(entries);
|
||||
|
||||
if (entries[0].intersectionRatio > 0.5) actionFunction();
|
||||
}, { threshold: 0.2 });
|
||||
|
||||
this.observer.observe(target);
|
||||
},
|
||||
|
||||
unmountObserver() {
|
||||
if (!this.observerTarget) return;
|
||||
|
||||
this.observer?.unobserve(this.observerTarget);
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineComponent } from 'vue';
|
||||
import { Ref, defineComponent } from 'vue';
|
||||
import { useStore } from '../store/store';
|
||||
|
||||
export default defineComponent({
|
||||
@@ -15,15 +15,17 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
methods: {
|
||||
selectModalTrain(trainId: string) {
|
||||
selectModalTrain(trainId: string, target?: EventTarget | null) {
|
||||
this.store.chosenModalTrainId = trainId;
|
||||
document.body.classList.add('no-scroll');
|
||||
if (target) this.store.modalLastClickedTarget = target;
|
||||
},
|
||||
|
||||
closeModal() {
|
||||
this.store.chosenModalTrainId = undefined;
|
||||
|
||||
setTimeout(() => {
|
||||
(this.store.modalLastClickedTarget as any)?.focus();
|
||||
document.body.classList.remove('no-scroll');
|
||||
}, 150);
|
||||
},
|
||||
|
||||
@@ -4,11 +4,17 @@ export default defineComponent({
|
||||
methods: {
|
||||
calculateExpStyle(exp: number, isSupporter = false): string {
|
||||
const bgColor = exp > -1 ? (exp < 2 ? '#26B0D9' : `hsl(${-exp * 5 + 100}, 85%, 50%)`) : '#666';
|
||||
|
||||
|
||||
const fontColor = exp > 14 || exp == -1 ? 'white' : 'black';
|
||||
const boxShadow = isSupporter ? `box-shadow: 0 0 10px 2px ${bgColor};` : '';
|
||||
|
||||
return `background-color: ${bgColor}; color: ${fontColor}; ${boxShadow}`;
|
||||
const boxShadow = isSupporter ? `box-shadow: 0 0 6px 2px ${bgColor};` : '';
|
||||
|
||||
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) {
|
||||
@@ -41,6 +47,6 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
return className;
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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,6 +1,14 @@
|
||||
export const enum JournalFilterType {
|
||||
active = "active",
|
||||
fulfilled = "fulfilled",
|
||||
abandoned = "abandoned",
|
||||
all = "all"
|
||||
}
|
||||
export const enum JournalFilterType {
|
||||
ACTIVE = 'active',
|
||||
FULFILLED = 'fulfilled',
|
||||
ABANDONED = 'abandoned',
|
||||
ALL = 'all',
|
||||
TWR = 'twr',
|
||||
SKR = 'skr',
|
||||
TWR_SKR = 'twr-skr',
|
||||
}
|
||||
|
||||
export enum JournalFilterSection {
|
||||
TIMETABLE_STATUS = 'timetable-status',
|
||||
TWRSKR = 'twrskr',
|
||||
}
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
export const enum TrainFilterType {
|
||||
comments = "comments",
|
||||
twr = "twr",
|
||||
skr = "skr",
|
||||
passenger = "passenger",
|
||||
freight = "freight",
|
||||
other = "other",
|
||||
noTimetable = "noTimetable"
|
||||
export enum TrainFilterSection {
|
||||
TRAIN_TYPE = 'TRAIN_TYPE',
|
||||
TIMETABLE_TYPE = 'TIMETABLE_TYPE',
|
||||
COMMENTS = 'COMMENTS',
|
||||
TIMETABLE = 'TIMETABLE',
|
||||
}
|
||||
|
||||
export const enum TrainFilterType {
|
||||
noComments = 'noComments',
|
||||
withComments = 'withComments',
|
||||
|
||||
twr = 'twr',
|
||||
skr = 'skr',
|
||||
common = 'common',
|
||||
|
||||
passenger = 'passenger',
|
||||
freight = 'freight',
|
||||
other = 'other',
|
||||
noTimetable = 'noTimetable',
|
||||
withTimetable = 'withTimetable',
|
||||
}
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
export default interface Filter {
|
||||
[key: string]: (boolean | number | string),
|
||||
[key: string]: boolean | number | string;
|
||||
default: boolean;
|
||||
notDefault: boolean;
|
||||
real: boolean;
|
||||
fictional: boolean;
|
||||
"SPK": boolean;
|
||||
"SCS": boolean;
|
||||
"SPE": boolean;
|
||||
"SUP": boolean;
|
||||
SPK: boolean;
|
||||
SCS: boolean;
|
||||
SPE: boolean;
|
||||
SUP: boolean;
|
||||
noSUP: boolean;
|
||||
ręczne: boolean;
|
||||
'ręczne+SPK': boolean;
|
||||
'ręczne+SCS': boolean;
|
||||
mechaniczne: boolean;
|
||||
"SBL": boolean;
|
||||
'mechaniczne+SPK': boolean;
|
||||
'mechaniczne+SCS': boolean;
|
||||
SBL: boolean;
|
||||
PBL: boolean;
|
||||
współczesna: boolean;
|
||||
kształtowa: boolean;
|
||||
historyczna: boolean;
|
||||
|
||||
@@ -1,32 +1,41 @@
|
||||
import TrainStop from "./TrainStop";
|
||||
import TrainStop from './TrainStop';
|
||||
|
||||
export default interface ScheduledTrain {
|
||||
trainId: string;
|
||||
trainNo: number;
|
||||
|
||||
driverName: string;
|
||||
driverId: number;
|
||||
currentStationName: string;
|
||||
currentStationHash: string;
|
||||
category: string;
|
||||
stopInfo: TrainStop;
|
||||
export enum StopStatus {
|
||||
'arriving' = 'arriving',
|
||||
'departed' = 'departed',
|
||||
'departed-away' = 'departed-away',
|
||||
'online' = 'online',
|
||||
'stopped' = 'stopped',
|
||||
'terminated' = 'terminated',
|
||||
}
|
||||
|
||||
terminatesAt: string;
|
||||
beginsAt: string;
|
||||
export interface ScheduledTrain {
|
||||
trainId: string;
|
||||
trainNo: number;
|
||||
|
||||
prevStationName: string;
|
||||
nextStationName: string;
|
||||
driverName: string;
|
||||
driverId: number;
|
||||
currentStationName: string;
|
||||
currentStationHash: string;
|
||||
category: string;
|
||||
stopInfo: TrainStop;
|
||||
|
||||
arrivingLine: string | null;
|
||||
departureLine: string | null;
|
||||
terminatesAt: string;
|
||||
beginsAt: string;
|
||||
|
||||
prevDepartureLine: string | null;
|
||||
nextArrivalLine: string | null;
|
||||
prevStationName: string;
|
||||
nextStationName: string;
|
||||
|
||||
signal: string;
|
||||
connectedTrack: string;
|
||||
arrivingLine: string | null;
|
||||
departureLine: string | null;
|
||||
|
||||
stopLabel: string;
|
||||
stopStatus: string;
|
||||
stopStatusID: number;
|
||||
}
|
||||
prevDepartureLine: string | null;
|
||||
nextArrivalLine: string | null;
|
||||
|
||||
signal: string;
|
||||
connectedTrack: string;
|
||||
|
||||
stopLabel: string;
|
||||
stopStatus: StopStatus;
|
||||
stopStatusID: number;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Availability } from '../../store/storeTypes';
|
||||
import ScheduledTrain from './ScheduledTrain';
|
||||
import { Availability } from './store/storeTypes';
|
||||
import {ScheduledTrain} from './ScheduledTrain';
|
||||
import StationRoutes from './StationRoutes';
|
||||
|
||||
export default interface Station {
|
||||
@@ -8,12 +8,15 @@ export default interface Station {
|
||||
generalInfo?: {
|
||||
name: string;
|
||||
url: string;
|
||||
abbr: string;
|
||||
|
||||
reqLevel: number;
|
||||
// supportersOnly: boolean;
|
||||
|
||||
|
||||
lines: string;
|
||||
project: string;
|
||||
projectUrl?: string;
|
||||
|
||||
signalType: string;
|
||||
controlType: string;
|
||||
@@ -38,7 +41,7 @@ export default interface Station {
|
||||
maxUsers: number;
|
||||
currentUsers: number;
|
||||
|
||||
spawns: { spawnName: string; spawnLength: number }[];
|
||||
spawns: { spawnName: string; spawnLength: number; isElectrified: boolean }[];
|
||||
dispatcherRate: number;
|
||||
dispatcherName: string;
|
||||
dispatcherExp: number;
|
||||
|
||||
@@ -1,27 +1,30 @@
|
||||
export default interface StationRoutes {
|
||||
oneWay:
|
||||
{
|
||||
name: string;
|
||||
catenary: boolean;
|
||||
SBL: boolean;
|
||||
TWB: boolean;
|
||||
isInternal: boolean;
|
||||
tracks: number;
|
||||
}[];
|
||||
|
||||
twoWay: {
|
||||
name: string;
|
||||
catenary: boolean;
|
||||
SBL: boolean;
|
||||
TWB: boolean;
|
||||
isInternal: boolean;
|
||||
tracks: number;
|
||||
}[];
|
||||
|
||||
/* [catenary, noCatenary] */
|
||||
oneWayCatenaryRouteNames: string[];
|
||||
oneWayNoCatenaryRouteNames: string[];
|
||||
twoWayCatenaryRouteNames: string[];
|
||||
twoWayNoCatenaryRouteNames: string[];
|
||||
sblRouteNames: string[];
|
||||
}
|
||||
export default interface StationRoutes {
|
||||
oneWay: {
|
||||
name: string;
|
||||
catenary: boolean;
|
||||
SBL: boolean;
|
||||
TWB: boolean;
|
||||
isInternal: boolean;
|
||||
tracks: number;
|
||||
speed: number;
|
||||
length: number;
|
||||
}[];
|
||||
|
||||
twoWay: {
|
||||
name: string;
|
||||
catenary: boolean;
|
||||
SBL: boolean;
|
||||
TWB: boolean;
|
||||
isInternal: boolean;
|
||||
tracks: number;
|
||||
speed: number;
|
||||
length: number;
|
||||
}[];
|
||||
|
||||
/* [catenary, noCatenary] */
|
||||
oneWayCatenaryRouteNames: string[];
|
||||
oneWayNoCatenaryRouteNames: string[];
|
||||
twoWayCatenaryRouteNames: string[];
|
||||
twoWayNoCatenaryRouteNames: string[];
|
||||
sblRouteNames: string[];
|
||||
}
|
||||
|
||||
@@ -12,14 +12,14 @@ export default interface Train {
|
||||
driverId: number;
|
||||
trainNo: number;
|
||||
driverName: string;
|
||||
driverLevel: number;
|
||||
currentStationName: string;
|
||||
currentStationHash: string;
|
||||
locoURL: string;
|
||||
locoType: string;
|
||||
online: boolean;
|
||||
lastSeen: number;
|
||||
region: string;
|
||||
cars: string[];
|
||||
stockList: string[];
|
||||
|
||||
isTimeout: boolean;
|
||||
isSupporter: boolean;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export default interface TrainStop {
|
||||
export default interface TrainStop {
|
||||
stopName: string;
|
||||
stopNameRAW: string;
|
||||
stopType: string;
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { TrainFilterSection, TrainFilterType } from '../../enums/TrainFilterType'
|
||||
|
||||
export interface TrainFilter {
|
||||
id: TrainFilterType;
|
||||
section: TrainFilterSection;
|
||||
isActive: boolean;
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
export interface DispatcherHistory {
|
||||
id: string;
|
||||
|
||||
|
||||
currentDuration: number;
|
||||
dispatcherId: number;
|
||||
dispatcherName: string;
|
||||
dispatcherLevel: number | null;
|
||||
dispatcherRate: number;
|
||||
dispatcherIsSupporter: boolean;
|
||||
dispatcherStatus?: number;
|
||||
isOnline: boolean;
|
||||
lastOnlineTimestamp: number;
|
||||
region: string;
|
||||
@@ -11,4 +15,4 @@ export interface DispatcherHistory {
|
||||
stationName: string;
|
||||
timestampFrom: number;
|
||||
timestampTo?: number;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export default interface StationAPIData {
|
||||
lastSeen: number;
|
||||
dispatcherExp: number;
|
||||
nameFromHeader: string;
|
||||
spawnString: string;
|
||||
spawnString: string | null;
|
||||
networkConnectionString: string;
|
||||
isOnline: number;
|
||||
dispatcherRate: number;
|
||||
|
||||
@@ -14,6 +14,17 @@ export interface ITimetablesDailyStats {
|
||||
name: string;
|
||||
count: number;
|
||||
}[];
|
||||
|
||||
mostActiveDrivers: {
|
||||
name: string;
|
||||
distance: number;
|
||||
}[];
|
||||
|
||||
longestDuties: {
|
||||
name: string;
|
||||
duration: number;
|
||||
station: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface ITimetablesDailyStatsResponse {
|
||||
@@ -26,5 +37,16 @@ export interface ITimetablesDailyStatsResponse {
|
||||
name: string;
|
||||
count: number;
|
||||
}[];
|
||||
|
||||
mostActiveDrivers: {
|
||||
name: string;
|
||||
distance: number;
|
||||
}[];
|
||||
|
||||
longestDuties: {
|
||||
name: string;
|
||||
duration: number;
|
||||
station: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
|
||||