mirror of
https://github.com/Spythere/stacjownik.git
synced 2026-05-03 13:28:11 +00:00
Compare commits
177 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1f457d6389 | |||
| eb5b94c9f6 | |||
| 328e8c0573 | |||
| 9f58ae5428 | |||
| ebd0eeb8c4 | |||
| fa656c2f26 | |||
| 0cc3a12d1d | |||
| 392a6437f8 | |||
| 122532f0ed | |||
| 366ff91f60 | |||
| a0496736dd | |||
| f974120e87 | |||
| abd8b8178b | |||
| f1fcde8459 | |||
| b3289d6aab | |||
| 6481a4a3b0 | |||
| 05dc268526 | |||
| 669acc98d2 | |||
| 3371b661c2 | |||
| 871b2c0221 | |||
| d366a877a4 | |||
| 405aab96bd | |||
| f29c160000 | |||
| a2de0e2030 | |||
| 7dd1c06f3f | |||
| ff041b9aaf | |||
| 4782dba444 | |||
| d6b8d032d6 | |||
| c16616330c | |||
| 57cec8bfe7 | |||
| 6bea340e19 | |||
| c181cf7e64 | |||
| 8e4ae64cd3 | |||
| 5750490f01 | |||
| 3ef27e1d69 | |||
| f53993c717 | |||
| 235c16e30f | |||
| c3533f07ad | |||
| d05579c5ee | |||
| c8f53c2f06 | |||
| b44f88ebcd | |||
| 7805d1350c | |||
| b17bd19433 | |||
| c12a6cbacd | |||
| ba650238db | |||
| d5ec9919e2 | |||
| 20cd393e05 | |||
| 31e65c09d6 | |||
| fb2348e774 | |||
| 1ec75bda70 | |||
| 6b6b837dde | |||
| 66a02d76bd | |||
| c7162dbd14 | |||
| 1cfe073bab | |||
| e3b72c81ea | |||
| 5552995564 | |||
| 623d5dd2ce | |||
| 6992b998a8 | |||
| 669975c68e | |||
| 084823de44 | |||
| f62d6982e5 | |||
| 1c9b54b578 | |||
| 0f4e5ee5f3 | |||
| 29b5e715fa | |||
| 91a18b51a0 | |||
| 241648ec49 | |||
| ed7d93e7fc | |||
| 436e3e63f9 | |||
| 17ebdace82 | |||
| 20826d905d | |||
| 41b1ab398c | |||
| 03465a1487 | |||
| a19fdbc19d | |||
| 032f82acd2 | |||
| b4a9d4ca3b | |||
| 986c7ba95e | |||
| 17f6f9c8ef | |||
| 40bbdbe4fa | |||
| 9f5d882119 | |||
| 6f45663c6c | |||
| a7861b361d | |||
| 5f8d7480d1 | |||
| e222dc63eb | |||
| 9c2f0ac797 | |||
| e33ba4af90 | |||
| 7b868a9f28 | |||
| 2a18ba4368 | |||
| fcbd6d0883 | |||
| 20fc4aba5b | |||
| 76ca0d1786 | |||
| 7e3c150815 | |||
| c8d56ec442 | |||
| 5c4c486643 | |||
| 755c729a9b | |||
| 3ac8d60c5c | |||
| dcff3b088f | |||
| 90b2099955 | |||
| fc0c04ec9d | |||
| 41b335555a | |||
| 60f7b3bbb5 | |||
| eaefe955a7 | |||
| edaa4f2684 | |||
| 30fce3787b | |||
| 4716f1c7a4 | |||
| bb7ccf98fd | |||
| c06d75b981 | |||
| c7da8477fa | |||
| e43f1e0819 | |||
| f130e6900b | |||
| db205915be | |||
| 05c38e10e3 | |||
| a8f683a585 | |||
| 68f6fc8a42 | |||
| 6d3b32cd7d | |||
| fadecc9d2c | |||
| 50602cb6db | |||
| 186ce81819 | |||
| f836a075b0 | |||
| 9acf3c740c | |||
| bc1c1bd3d2 | |||
| 2348277b95 | |||
| cd5f489df7 | |||
| f74962222b | |||
| e7f651d2b9 | |||
| 4862328090 | |||
| 87631d1f74 | |||
| 86bb9fcc2e | |||
| b85e3bfe1d | |||
| dd15072813 | |||
| 2f8376c996 | |||
| 514723cf74 | |||
| 0995ce15bc | |||
| 3b3c3bda31 | |||
| 2027b85450 | |||
| 0c6b55146f | |||
| 3c728e3cfa | |||
| adce339392 | |||
| 00a4a840b0 | |||
| 1e705ea496 | |||
| e8ed36df16 | |||
| f4be32aa39 | |||
| e0d3d2585d | |||
| ebfaf06a44 | |||
| 5a651aedf8 | |||
| b66af014b9 | |||
| 634c9e1514 | |||
| c4132a9be2 | |||
| 82a9a9165f | |||
| fcac03c0a4 | |||
| 39c3cf2329 | |||
| 59f4a0cb66 | |||
| e2b42d16a4 | |||
| e23663ed28 | |||
| dc7846c31e | |||
| d875433d56 | |||
| 71e5044cb4 | |||
| e83aa40f82 | |||
| d1c0e0b898 | |||
| 26a7c69886 | |||
| 0dc2c505db | |||
| 188857d335 | |||
| 3dbbb3b4f9 | |||
| 07a77c463b | |||
| 1a8e2231dd | |||
| f630d272ca | |||
| a381cf806c | |||
| 0023ab8cfd | |||
| a392940d52 | |||
| 74984ad653 | |||
| a8991434bc | |||
| 825610b4c2 | |||
| fef3991206 | |||
| a21cb31a0a | |||
| 23a954a0d1 | |||
| b7bc9625b8 | |||
| bc58196804 | |||
| 6550d6973e |
@@ -0,0 +1,17 @@
|
|||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
github-releases-to-discord:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Github Releases To Discord
|
||||||
|
uses: SethCohen/github-releases-to-discord@v1.13.1
|
||||||
|
with:
|
||||||
|
webhook_url: ${{ secrets.WEBHOOK_URL }}
|
||||||
|
color: "15844367"
|
||||||
|
footer_title: "Changelog - Stacjownik"
|
||||||
|
footer_timestamp: true
|
||||||
@@ -50,11 +50,6 @@
|
|||||||
name="twitter:image"
|
name="twitter:image"
|
||||||
content="https://raw.githubusercontent.com/Spythere/api/main/thumbnails/stacjownik.jpg"
|
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>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
Generated
+2809
-9613
File diff suppressed because it is too large
Load Diff
+11
-12
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "stacjownik",
|
"name": "stacjownik",
|
||||||
"version": "1.18.6",
|
"version": "1.24.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
@@ -14,31 +14,30 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"core-js": "^3.32.2",
|
"core-js": "^3.32.2",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"firebase": "^10.4.0",
|
|
||||||
"howler": "^2.2.4",
|
|
||||||
"pinia": "^2.1.6",
|
"pinia": "^2.1.6",
|
||||||
"sass": "^1.67.0",
|
"sass": "^1.67.0",
|
||||||
"socket.io-client": "^4.7.2",
|
"showdown": "^2.1.0",
|
||||||
"vue": "^3.3.4",
|
"vue": "^3.3.4",
|
||||||
"vue-i18n": "^9.4.1",
|
"vue-i18n": "^9.4.1",
|
||||||
"vue-router": "^4.2.4"
|
"vue-router": "^4.2.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@rushstack/eslint-patch": "^1.3.3",
|
||||||
"@types/node": "^20.6.2",
|
"@types/node": "^20.6.2",
|
||||||
"@vite-pwa/assets-generator": "^0.0.10",
|
"@types/showdown": "^2.0.6",
|
||||||
|
"@vite-pwa/assets-generator": "^0.2.4",
|
||||||
"@vitejs/plugin-vue": "^4.3.4",
|
"@vitejs/plugin-vue": "^4.3.4",
|
||||||
"axios": "^1.5.0",
|
|
||||||
"prettier": "^3.0.3",
|
|
||||||
"typescript": "^5.2.2",
|
|
||||||
"vite": "^4.4.9",
|
|
||||||
"vite-plugin-pwa": "^0.16.5",
|
|
||||||
"vue-tsc": "^1.8.11",
|
|
||||||
"@vue/eslint-config-prettier": "^8.0.0",
|
"@vue/eslint-config-prettier": "^8.0.0",
|
||||||
"@vue/eslint-config-typescript": "^12.0.0",
|
"@vue/eslint-config-typescript": "^12.0.0",
|
||||||
"@vue/tsconfig": "^0.4.0",
|
"@vue/tsconfig": "^0.4.0",
|
||||||
|
"axios": "^1.5.0",
|
||||||
"eslint": "^8.49.0",
|
"eslint": "^8.49.0",
|
||||||
"eslint-plugin-vue": "^9.17.0",
|
"eslint-plugin-vue": "^9.17.0",
|
||||||
"@rushstack/eslint-patch": "^1.3.3"
|
"prettier": "^3.0.3",
|
||||||
|
"typescript": "^5.2.2",
|
||||||
|
"vite": "^4.4.9",
|
||||||
|
"vite-plugin-pwa": "^0.20.0",
|
||||||
|
"vue-tsc": "^1.8.11"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"> 1%",
|
"> 1%",
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,4 @@
|
|||||||
|
<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="M10.5573 31.3793L9.43741 28.0694C9.35445 27.8592 9.26596 27.6131 9.17195 27.3311C9.07793 27.0435 8.98391 26.7338 8.8899 26.402C8.80694 26.7393 8.71845 27.0518 8.62444 27.3394C8.53042 27.6269 8.44193 27.8758 8.35898 28.086L7.24736 31.3793H10.5573ZM15.0121 36H12.8386C12.5953 36 12.3989 35.9447 12.2496 35.8341C12.1003 35.7179 11.9869 35.5714 11.9095 35.3944L11.1961 33.2873H6.6003L5.88688 35.3944C5.82604 35.5493 5.71544 35.6903 5.55505 35.8175C5.4002 35.9392 5.20664 36 4.97436 36H2.78431L7.46305 23.9133H10.3333L15.0121 36ZM22.643 26.3688C22.5601 26.5015 22.4716 26.6011 22.3775 26.6674C22.2891 26.7338 22.1729 26.767 22.0291 26.767C21.9019 26.767 21.7637 26.7283 21.6143 26.6508C21.4706 26.5679 21.3046 26.4766 21.1166 26.3771C20.9341 26.2775 20.724 26.189 20.4861 26.1116C20.2483 26.0287 19.9773 25.9872 19.6732 25.9872C19.1478 25.9872 18.7551 26.1006 18.4952 26.3273C18.2408 26.5485 18.1136 26.8499 18.1136 27.2315C18.1136 27.4749 18.191 27.6767 18.3459 27.8371C18.5007 27.9975 18.7026 28.1357 18.9515 28.2519C19.2059 28.368 19.4934 28.4759 19.8142 28.5754C20.1405 28.6694 20.4723 28.7773 20.8097 28.8989C21.147 29.0151 21.4761 29.1533 21.7969 29.3137C22.1231 29.4741 22.4107 29.6787 22.6596 29.9276C22.914 30.1765 23.1186 30.4806 23.2735 30.8401C23.4283 31.1941 23.5058 31.6227 23.5058 32.1259C23.5058 32.6845 23.409 33.2071 23.2154 33.6938C23.0218 34.1805 22.7398 34.6063 22.3693 34.9713C22.0042 35.3308 21.5508 35.6156 21.0088 35.8258C20.4723 36.0304 19.8612 36.1327 19.1754 36.1327C18.7994 36.1327 18.415 36.094 18.0223 36.0166C17.6352 35.9392 17.2591 35.8313 16.8941 35.6931C16.5291 35.5493 16.1862 35.3806 15.8655 35.187C15.5447 34.9935 15.2654 34.7778 15.0276 34.54L15.8572 33.2293C15.9235 33.1352 16.0093 33.0578 16.1143 32.997C16.225 32.9306 16.3439 32.8974 16.4711 32.8974C16.637 32.8974 16.8029 32.95 16.9688 33.0551C17.1402 33.1601 17.331 33.2763 17.5412 33.4035C17.7569 33.5307 18.003 33.6468 18.2795 33.7519C18.556 33.857 18.8823 33.9095 19.2584 33.9095C19.7672 33.9095 20.1626 33.7989 20.4447 33.5777C20.7267 33.3509 20.8677 32.9942 20.8677 32.5075C20.8677 32.2255 20.7903 31.996 20.6355 31.819C20.4806 31.642 20.276 31.4955 20.0216 31.3793C19.7727 31.2632 19.4879 31.1609 19.1671 31.0724C18.8464 30.9839 18.5173 30.8871 18.18 30.7821C17.8426 30.6714 17.5135 30.5387 17.1928 30.3839C16.872 30.2235 16.5844 30.0161 16.33 29.7617C16.0812 29.5018 15.8793 29.181 15.7245 28.7994C15.5696 28.4123 15.4922 27.9367 15.4922 27.3725C15.4922 26.9191 15.5834 26.4766 15.7659 26.0452C15.9484 25.6139 16.2167 25.2295 16.5706 24.8922C16.9246 24.5548 17.3587 24.2866 17.873 24.0875C18.3874 23.8829 18.9763 23.7805 19.64 23.7805C20.0105 23.7805 20.37 23.811 20.7184 23.8718C21.0724 23.9271 21.407 24.0128 21.7222 24.129C22.0374 24.2396 22.3305 24.3751 22.6015 24.5354C22.8781 24.6903 23.1242 24.8673 23.3398 25.0664L22.643 26.3688ZM36.1186 29.9525C36.1186 30.8263 35.9665 31.6337 35.6623 32.3748C35.3637 33.1104 34.9406 33.7491 34.3931 34.2911C33.8456 34.8276 33.1847 35.2479 32.4105 35.552C31.6417 35.8507 30.7873 36 29.8471 36H25.1518V23.9133H29.8471C30.7873 23.9133 31.6417 24.0654 32.4105 24.3695C33.1847 24.6737 33.8456 25.094 34.3931 25.6305C34.9406 26.1669 35.3637 26.8057 35.6623 27.5468C35.9665 28.2823 36.1186 29.0842 36.1186 29.9525ZM33.2483 29.9525C33.2483 29.3552 33.1709 28.816 33.016 28.3348C32.8612 27.8537 32.6372 27.4472 32.3441 27.1154C32.0565 26.778 31.7026 26.5209 31.2823 26.3439C30.8619 26.1614 30.3836 26.0701 29.8471 26.0701H27.9723V33.8431H29.8471C30.3836 33.8431 30.8619 33.7547 31.2823 33.5777C31.7026 33.3952 32.0565 33.138 32.3441 32.8062C32.6372 32.4688 32.8612 32.0596 33.016 31.5784C33.1709 31.0973 33.2483 30.5553 33.2483 29.9525ZM40.594 26.0701V28.8906H44.3934V30.9646H40.594V33.8431H45.5547V36H37.7735V23.9133H45.5547V26.0701H40.594ZM50.0882 28.8077H50.5361C50.9509 28.8077 51.2496 28.6777 51.4321 28.4178L54.153 24.4691C54.3134 24.2589 54.4849 24.1151 54.6674 24.0377C54.8554 23.9547 55.0877 23.9133 55.3642 23.9133H57.8031L54.2194 28.7994C53.965 29.1368 53.6912 29.3801 53.3981 29.5294C53.6083 29.6068 53.7991 29.7147 53.9705 29.8529C54.142 29.9912 54.3024 30.1709 54.4517 30.3922L58.1018 36H55.5965C55.4361 36 55.2978 35.9889 55.1817 35.9668C55.0711 35.9447 54.9743 35.9115 54.8913 35.8673C54.8084 35.823 54.7365 35.7705 54.6757 35.7097C54.6148 35.6433 54.5568 35.5686 54.5015 35.4857L51.7639 31.2798C51.6643 31.1249 51.5371 31.0171 51.3823 30.9563C51.233 30.8899 51.0284 30.8567 50.7684 30.8567H50.0882V36H47.2843V23.9133H50.0882V28.8077Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.5 KiB |
@@ -0,0 +1,56 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Warstwa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 159 38.8" style="enable-background:new 0 0 159 38.8;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#00A962;}
|
||||||
|
.st1{enable-background:new ;}
|
||||||
|
.st2{fill:#FFFFFF;}
|
||||||
|
.st3{fill:#1E3A33;}
|
||||||
|
.st4{fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;}
|
||||||
|
</style>
|
||||||
|
<path class="st0" d="M139.6,38.7H19.4C8.7,38.7,0,30.1,0,19.3l0,0C0,8.6,8.7-0.1,19.4-0.1h120.2c10.7,0,19.4,8.7,19.4,19.4l0,0
|
||||||
|
C159,30.1,150.3,38.7,139.6,38.7z"/>
|
||||||
|
<g class="st1">
|
||||||
|
<path class="st2" d="M48.9,23.7c0,0.5-0.4,0.9-0.9,0.9c-0.5,0-0.9-0.4-0.9-0.9v-9.9c0-0.6,0.4-1,1-1h3.7c2.5,0,3.6,1.9,3.6,3.7
|
||||||
|
s-1,3.7-3.6,3.7h-2.9V23.7z M48.9,14.5v4.1h2.8c1.3,0,1.9-1,1.9-2c0-1-0.5-2.1-1.8-2.1H48.9z"/>
|
||||||
|
<path class="st2" d="M56.3,20.5c0-2.5,1.9-4.1,4.1-4.1c2.3,0,4.2,1.6,4.2,4.1c0,2.5-1.9,4.2-4.2,4.2C58.2,24.7,56.3,23,56.3,20.5z
|
||||||
|
M62.9,20.5c0-1.5-1.1-2.4-2.4-2.4c-1.3,0-2.4,1-2.4,2.4c0,1.5,1.1,2.5,2.4,2.5C61.8,23,62.9,22,62.9,20.5z"/>
|
||||||
|
<path class="st2" d="M66.6,21.9c0.4-0.2,0.7,0.1,0.9,0.3c0.3,0.6,0.9,1,1.7,1c0.8,0,1.4-0.4,1.4-1c0-0.5-0.5-0.7-1.1-0.9l-1.1-0.3
|
||||||
|
c-1.7-0.5-2.3-1.4-2.2-2.7c0.1-1.2,1.4-2.1,2.8-2.1c1.1,0,2,0.3,2.5,1.2c0.2,0.4,0.1,0.8-0.2,1c-0.3,0.2-0.6,0.2-1-0.1
|
||||||
|
c-0.4-0.4-1-0.5-1.4-0.5c-0.4,0-0.9,0.2-1.1,0.4c-0.1,0.2-0.2,0.4-0.1,0.7c0.1,0.3,0.6,0.5,1,0.6l1.2,0.3c1.7,0.4,2.1,1.5,2.1,2.3
|
||||||
|
c0,1.5-1.3,2.4-3.2,2.4c-1.2,0-2.5-0.6-2.9-1.8C66,22.5,66.2,22.1,66.6,21.9z"/>
|
||||||
|
<path class="st2" d="M74.6,18h-0.8c-0.4,0-0.7-0.3-0.7-0.7c0-0.4,0.3-0.7,0.7-0.7h0.8v-2.2c0-0.5,0.4-0.9,0.9-0.9
|
||||||
|
c0.5,0,0.9,0.4,0.9,0.9v2.2h1c0.4,0,0.7,0.3,0.7,0.7c0,0.4-0.3,0.7-0.7,0.7h-1v5.7c0,0.5-0.4,0.9-0.9,0.9c-0.5,0-0.9-0.4-0.9-0.9
|
||||||
|
V18z"/>
|
||||||
|
<path class="st2" d="M85.7,23.4c-0.5,0.9-1.7,1.3-2.7,1.3c-2.1,0-4-1.6-4-4.1c0-2.5,1.9-4.1,4-4.1c1,0,2.1,0.4,2.7,1.3v-0.3
|
||||||
|
c0-0.5,0.4-0.9,0.9-0.9c0.5,0,0.9,0.4,0.9,0.9v6.3c0,0.5-0.4,0.9-0.9,0.9c-0.5,0-0.9-0.4-0.9-0.9V23.4z M83.2,23
|
||||||
|
c1.3,0,2.4-0.9,2.4-2.5c0-1.6-1.3-2.4-2.4-2.4c-1.3,0-2.4,1-2.4,2.4C80.8,22,81.9,23,83.2,23z"/>
|
||||||
|
<path class="st2" d="M95.1,16.6c0.5,0,0.9,0.3,1,0.8l1.6,4.6l1.7-4.8c0.2-0.5,0.6-0.7,1.1-0.6c0.5,0.1,0.7,0.6,0.6,1.1l-2.4,6.3
|
||||||
|
c-0.2,0.5-0.5,0.7-0.9,0.7h-0.1c-0.4,0-0.7-0.2-0.9-0.7L95.1,19l-1.7,4.9c-0.2,0.5-0.5,0.7-0.9,0.7h-0.1c-0.4,0-0.7-0.2-0.9-0.7
|
||||||
|
l-2.4-6.3c-0.2-0.4,0.1-1,0.6-1.1c0.5-0.1,0.9,0.1,1.1,0.6l1.7,4.8l1.6-4.6C94.2,16.8,94.6,16.6,95.1,16.6L95.1,16.6z"/>
|
||||||
|
<path class="st2" d="M112.9,23.2c0.3,0.3,0.3,0.9,0,1.2c-0.4,0.3-0.9,0.3-1.2,0l-3.2-3.5v2.9c0,0.5-0.4,0.9-0.9,0.9
|
||||||
|
c-0.5,0-0.8-0.4-0.8-0.9V13.1c0-0.5,0.4-0.9,0.8-0.9c0.5,0,0.9,0.4,0.9,0.9v6.1l2.4-2.4c0.3-0.3,0.9-0.3,1.2,0
|
||||||
|
c0.3,0.4,0.3,0.9,0,1.2l-2,2.1L112.9,23.2z"/>
|
||||||
|
<path class="st2" d="M120.8,23.4c-0.5,0.9-1.7,1.3-2.7,1.3c-2.1,0-4-1.6-4-4.1c0-2.5,1.9-4.1,4-4.1c1,0,2.1,0.4,2.7,1.3v-0.3
|
||||||
|
c0-0.5,0.4-0.9,0.8-0.9c0.5,0,0.9,0.4,0.9,0.9v6.3c0,0.5-0.4,0.9-0.9,0.9c-0.5,0-0.8-0.4-0.8-0.9V23.4z M118.4,23
|
||||||
|
c1.3,0,2.4-0.9,2.4-2.5c0-1.6-1.3-2.4-2.4-2.4c-1.3,0-2.4,1-2.4,2.4C115.9,22,117,23,118.4,23z"/>
|
||||||
|
<path class="st2" d="M130.3,16.6c0.5,0,0.9,0.3,1,0.8l1.6,4.6l1.7-4.8c0.2-0.5,0.6-0.7,1.1-0.6c0.5,0.1,0.7,0.6,0.6,1.1l-2.4,6.3
|
||||||
|
c-0.2,0.5-0.5,0.7-0.9,0.7h-0.1c-0.4,0-0.7-0.2-0.9-0.7l-1.7-4.9l-1.7,4.9c-0.2,0.5-0.5,0.7-0.9,0.7h-0.1c-0.4,0-0.7-0.2-0.9-0.7
|
||||||
|
l-2.4-6.3c-0.2-0.4,0.1-1,0.6-1.1c0.5-0.1,0.9,0.1,1.1,0.6l1.7,4.8l1.6-4.6C129.3,16.8,129.7,16.6,130.3,16.6L130.3,16.6z"/>
|
||||||
|
<path class="st2" d="M143.6,26.1c0,0.2,0.2,0.5,0.6,0.5c0.1,0,0.4,0,0.5-0.4c0.1-0.4,0.3-0.5,0.7-0.5c0.4,0.1,0.6,0.4,0.5,0.8
|
||||||
|
c-0.2,0.8-0.9,1.4-1.7,1.4c-1.3,0-1.9-0.8-1.9-1.7c0-0.2,0.1-0.6,0.2-0.8l0.5-1c-0.5,0.2-1,0.3-1.6,0.3c-2.3,0-4.1-1.6-4.1-4.2
|
||||||
|
c0-2.5,1.8-4.1,4.1-4.1c2.2,0,3.9,1.5,4,4c0,0.5-0.4,0.8-0.8,0.8h-5.5c0.2,1.2,1.2,2,2.4,2c0.8,0,1.3-0.3,1.9-0.8
|
||||||
|
c0.3-0.2,0.8-0.4,1.1-0.1c0.3,0.3,0.2,0.7,0,1.1C143.5,25.9,143.6,26.1,143.6,26.1z M143.7,19.9c-0.2-1.2-1.1-2-2.3-2
|
||||||
|
c-1.2,0-2.2,0.7-2.3,2H143.7z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st3" d="M21.6,26.7c-4.9,0-7.4-2.6-8.5-4.9c-1.2-2.4-1.4-5-1.4-6c0-2.2,1.8-3.8,4.3-3.8h7c0.7,0,1.3,0.6,1.3,1.3
|
||||||
|
c0,0.7-0.6,1.3-1.3,1.3h-7c-0.5,0-1,0.1-1.3,0.4c-0.3,0.2-0.4,0.4-0.4,0.8c0,1.9,0.5,8.3,7.3,8.3c5.8,0,7.3-4.6,7.9-8.1
|
||||||
|
c0-0.2,0-0.3,0.1-0.4c0.2-0.9,0.4-1.7,0.8-2.3C31,12.4,31.9,12,33,12c0.5,0,0.9,0.2,1.2,0.5c0.5,0.4,0.9,1.3,1.2,4.1
|
||||||
|
c0.1,1.4,0.1,2.6,0.1,2.7c0,0.7-0.6,1.3-1.3,1.3l0,0c-0.7,0-1.3-0.6-1.3-1.3c0,0,0-1.1-0.1-2.3c-0.1-1-0.2-1.7-0.3-2.1
|
||||||
|
c-0.1,0.3-0.2,0.9-0.3,1.1c0,0.1,0,0.2-0.1,0.4c-0.2,1.2-0.7,3.7-2.1,5.9c-0.8,1.3-1.9,2.3-3.2,3.1C25.4,26.3,23.6,26.7,21.6,26.7z
|
||||||
|
"/>
|
||||||
|
<ellipse class="st4" cx="26.7" cy="13.3" rx="1.3" ry="1.3"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.9 KiB |
@@ -1,4 +1,4 @@
|
|||||||
<svg width="160" height="150" viewBox="0 0 160 150" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="160" height="150" viewBox="0 0 160 150" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M10.163 139L80 12.4204L149.837 139H80H10.163Z" stroke="white" stroke-width="12"/>
|
<path d="M10.163 139L80 12.4204L149.837 139H80H10.163Z" stroke="salmon" stroke-width="15"/>
|
||||||
<path d="M85.4488 50.3354V80.6619C85.4488 83.8784 85.2898 87.0418 84.9717 90.1522C84.6536 93.2273 84.2294 96.4968 83.6992 99.9606H74.8451C74.315 96.4968 73.8908 93.2273 73.5727 90.1522C73.2546 87.0418 73.0955 83.8784 73.0955 80.6619V50.3354H85.4488ZM71.0808 119.789C71.0808 118.694 71.2752 117.651 71.664 116.661C72.0882 115.672 72.6537 114.823 73.3606 114.117C74.1029 113.41 74.9689 112.844 75.9585 112.42C76.9482 111.996 78.0086 111.784 79.1396 111.784C80.2354 111.784 81.278 111.996 82.2677 112.42C83.2574 112.844 84.1057 113.41 84.8126 114.117C85.5195 114.823 86.085 115.672 86.5092 116.661C86.9333 117.651 87.1454 118.694 87.1454 119.789C87.1454 120.921 86.9333 121.981 86.5092 122.971C86.085 123.925 85.5195 124.756 84.8126 125.462C84.1057 126.169 83.2574 126.717 82.2677 127.106C81.278 127.53 80.2354 127.742 79.1396 127.742C78.0086 127.742 76.9482 127.53 75.9585 127.106C74.9689 126.717 74.1029 126.169 73.3606 125.462C72.6537 124.756 72.0882 123.925 71.664 122.971C71.2752 121.981 71.0808 120.921 71.0808 119.789Z" fill="#FFFBFB"/>
|
<path d="M85.4488 50.3354V80.6619C85.4488 83.8784 85.2898 87.0418 84.9717 90.1522C84.6536 93.2273 84.2294 96.4968 83.6992 99.9606H74.8451C74.315 96.4968 73.8908 93.2273 73.5727 90.1522C73.2546 87.0418 73.0955 83.8784 73.0955 80.6619V50.3354H85.4488ZM71.0808 119.789C71.0808 118.694 71.2752 117.651 71.664 116.661C72.0882 115.672 72.6537 114.823 73.3606 114.117C74.1029 113.41 74.9689 112.844 75.9585 112.42C76.9482 111.996 78.0086 111.784 79.1396 111.784C80.2354 111.784 81.278 111.996 82.2677 112.42C83.2574 112.844 84.1057 113.41 84.8126 114.117C85.5195 114.823 86.085 115.672 86.5092 116.661C86.9333 117.651 87.1454 118.694 87.1454 119.789C87.1454 120.921 86.9333 121.981 86.5092 122.971C86.085 123.925 85.5195 124.756 84.8126 125.462C84.1057 126.169 83.2574 126.717 82.2677 127.106C81.278 127.53 80.2354 127.742 79.1396 127.742C78.0086 127.742 76.9482 127.53 75.9585 127.106C74.9689 126.717 74.1029 126.169 73.3606 125.462C72.6537 124.756 72.0882 123.925 71.664 122.971C71.2752 121.981 71.0808 120.921 71.0808 119.789Z" fill="white"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -1,71 +0,0 @@
|
|||||||
@import './styles/responsive.scss';
|
|
||||||
@import './styles/variables.scss';
|
|
||||||
@import './styles/global.scss';
|
|
||||||
@import './styles/animations.scss';
|
|
||||||
|
|
||||||
.route {
|
|
||||||
margin: 0 0.2em;
|
|
||||||
|
|
||||||
&-active,
|
|
||||||
&[data-active='true'] {
|
|
||||||
color: $accentCol;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// APP
|
|
||||||
#app {
|
|
||||||
color: white;
|
|
||||||
font-size: 1rem;
|
|
||||||
overflow-x: hidden;
|
|
||||||
|
|
||||||
@include smallScreen() {
|
|
||||||
font-size: calc(0.65rem + 0.8vw);
|
|
||||||
}
|
|
||||||
|
|
||||||
@include screenLandscape() {
|
|
||||||
font-size: calc(0.45rem + 0.8vw);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CONTAINER
|
|
||||||
.app_container {
|
|
||||||
display: grid;
|
|
||||||
grid-template-rows: auto 1fr auto;
|
|
||||||
grid-template-columns: 100%;
|
|
||||||
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app_main {
|
|
||||||
padding: 0 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.warning {
|
|
||||||
background-color: firebrick;
|
|
||||||
text-align: center;
|
|
||||||
padding: 0.5em 0.4em;
|
|
||||||
max-width: 1100px;
|
|
||||||
margin: 0 auto;
|
|
||||||
|
|
||||||
border-radius: 0 0 1em 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
// FOOTER
|
|
||||||
footer.app_footer {
|
|
||||||
max-width: 100%;
|
|
||||||
padding: 0.5em;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 1.1em;
|
|
||||||
vertical-align: text-bottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
z-index: 10;
|
|
||||||
|
|
||||||
background: #111;
|
|
||||||
color: white;
|
|
||||||
|
|
||||||
text-align: center;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
+176
-67
@@ -1,8 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="app_container">
|
<div class="app_container">
|
||||||
|
<UpdateModal
|
||||||
|
:update-modal-open="isUpdateModalOpen"
|
||||||
|
@toggle-modal="() => (isUpdateModalOpen = false)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Tooltip />
|
||||||
|
|
||||||
<transition name="modal-anim">
|
<transition name="modal-anim">
|
||||||
<keep-alive>
|
<keep-alive>
|
||||||
<TrainModal v-if="store.chosenModalTrainId" />
|
<TrainModal />
|
||||||
</keep-alive>
|
</keep-alive>
|
||||||
</transition>
|
</transition>
|
||||||
|
|
||||||
@@ -10,7 +17,7 @@
|
|||||||
|
|
||||||
<main class="app_main">
|
<main class="app_main">
|
||||||
<router-view v-slot="{ Component }">
|
<router-view v-slot="{ Component }">
|
||||||
<keep-alive exclude="JournalView,SceneryView">
|
<keep-alive exclude="SceneryView">
|
||||||
<component :is="Component" :key="$route.name" />
|
<component :is="Component" :key="$route.name" />
|
||||||
</keep-alive>
|
</keep-alive>
|
||||||
</router-view>
|
</router-view>
|
||||||
@@ -20,7 +27,10 @@
|
|||||||
©
|
©
|
||||||
<a href="https://td2.info.pl/profile/?u=20777" target="_blank">Spythere</a>
|
<a href="https://td2.info.pl/profile/?u=20777" target="_blank">Spythere</a>
|
||||||
{{ new Date().getUTCFullYear() }} |
|
{{ new Date().getUTCFullYear() }} |
|
||||||
<a :href="releaseURL" target="_blank">v{{ VERSION }}{{ isOnProductionHost ? '' : 'dev' }}</a>
|
<button class="btn--text" @click="() => (isUpdateModalOpen = true)">
|
||||||
|
v{{ VERSION }}{{ isOnProductionHost ? '' : 'dev' }}
|
||||||
|
</button>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
<a href="https://discord.gg/x2mpNN3svk">
|
<a href="https://discord.gg/x2mpNN3svk">
|
||||||
<img src="/images/icon-discord.png" alt="" /> <b>{{ $t('footer.discord') }}</b>
|
<img src="/images/icon-discord.png" alt="" /> <b>{{ $t('footer.discord') }}</b>
|
||||||
@@ -32,95 +42,81 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, watch } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
import { version } from '.././package.json';
|
||||||
|
import { Status } from './typings/common';
|
||||||
|
import { useMainStore } from './store/mainStore';
|
||||||
|
import { useApiStore } from './store/apiStore';
|
||||||
|
import { useTooltipStore } from './store/tooltipStore';
|
||||||
|
|
||||||
import Clock from './components/App/Clock.vue';
|
import Clock from './components/App/Clock.vue';
|
||||||
|
|
||||||
import packageInfo from '.././package.json';
|
|
||||||
import { regions } from './data/options.json';
|
|
||||||
|
|
||||||
import { useStore } from './store/mainStore';
|
|
||||||
import StatusIndicator from './components/App/StatusIndicator.vue';
|
import StatusIndicator from './components/App/StatusIndicator.vue';
|
||||||
import TrainModal from './components/Global/TrainModal.vue';
|
|
||||||
import AppHeader from './components/App/AppHeader.vue';
|
import AppHeader from './components/App/AppHeader.vue';
|
||||||
import axios from 'axios';
|
import TrainModal from './components/TrainsView/TrainModal.vue';
|
||||||
|
import Tooltip from './components/Tooltip/Tooltip.vue';
|
||||||
|
import UpdateModal from './components/App/UpdateModal.vue';
|
||||||
|
|
||||||
import StorageManager from './managers/storageManager';
|
import StorageManager from './managers/storageManager';
|
||||||
|
|
||||||
|
const STORAGE_VERSION_KEY = 'app_version';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
Clock,
|
Clock,
|
||||||
StatusIndicator,
|
StatusIndicator,
|
||||||
|
AppHeader,
|
||||||
TrainModal,
|
TrainModal,
|
||||||
AppHeader
|
UpdateModal,
|
||||||
|
Tooltip
|
||||||
},
|
},
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
VERSION: packageInfo.version,
|
VERSION: version,
|
||||||
store: useStore(),
|
store: useMainStore(),
|
||||||
|
apiStore: useApiStore(),
|
||||||
|
tooltipStore: useTooltipStore(),
|
||||||
|
|
||||||
|
isUpdateModalOpen: false,
|
||||||
|
|
||||||
currentLang: 'pl',
|
currentLang: 'pl',
|
||||||
releaseURL: '',
|
isOnProductionHost: location.hostname == 'stacjownik-td2.web.app',
|
||||||
isOnProductionHost: location.hostname == 'stacjownik-td2.web.app'
|
|
||||||
|
nextUpdateTime: 0
|
||||||
}),
|
}),
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
this.loadLang();
|
this.init();
|
||||||
this.store.connectToAPI();
|
|
||||||
|
|
||||||
this.store.isOffline = !window.navigator.onLine;
|
|
||||||
|
|
||||||
window.addEventListener('offline', () => {
|
|
||||||
this.store.isOffline = true;
|
|
||||||
|
|
||||||
this.store.activeData.activeSceneries = [];
|
|
||||||
this.store.activeData.trains = [];
|
|
||||||
this.store.activeData.connectedSocketCount = 0;
|
|
||||||
|
|
||||||
this.store.setStatuses();
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener('online', () => {
|
|
||||||
this.store.isOffline = false;
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async mounted() {
|
async mounted() {
|
||||||
this.setReleaseURL();
|
window.addEventListener('mousemove', (e: MouseEvent) => this.tooltipStore.handle(e));
|
||||||
|
|
||||||
watch(
|
|
||||||
() => this.store.blockScroll,
|
|
||||||
(value) => {
|
|
||||||
if (value) document.body.classList.add('no-scroll');
|
|
||||||
else document.body.classList.remove('no-scroll');
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
watch: {
|
|
||||||
'$route.query.region': {
|
|
||||||
immediate: true,
|
|
||||||
handler(regionQuery: string) {
|
|
||||||
if (regionQuery) {
|
|
||||||
this.store.region.id =
|
|
||||||
regions.find(
|
|
||||||
(reg) =>
|
|
||||||
reg.id == regionQuery.toLocaleLowerCase() ||
|
|
||||||
reg.value.toLocaleLowerCase() == regionQuery.toLocaleLowerCase()
|
|
||||||
)?.id || 'eu';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
changeLang(lang: string) {
|
init() {
|
||||||
this.$i18n.locale = lang;
|
this.loadLang();
|
||||||
this.currentLang = lang;
|
this.setupOfflineHandling();
|
||||||
|
this.checkAppVersion();
|
||||||
|
|
||||||
StorageManager.setStringValue('lang', lang);
|
this.apiStore.setupAPIData();
|
||||||
|
window.requestAnimationFrame(this.update);
|
||||||
|
|
||||||
|
if (!this.isOnProductionHost) document.title = 'Stacjownik Dev';
|
||||||
},
|
},
|
||||||
|
|
||||||
async setReleaseURL() {
|
update(t: number) {
|
||||||
|
if (t >= this.nextUpdateTime) {
|
||||||
|
this.apiStore.fetchActiveData();
|
||||||
|
this.nextUpdateTime = t + 20000;
|
||||||
|
}
|
||||||
|
window.requestAnimationFrame(this.update);
|
||||||
|
},
|
||||||
|
|
||||||
|
async checkAppVersion() {
|
||||||
|
const storageVersion = StorageManager.getStringValue(STORAGE_VERSION_KEY);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const releaseData = await (
|
const releaseData = await (
|
||||||
await axios.get('https://api.github.com/repos/Spythere/stacjownik/releases/latest')
|
await axios.get('https://api.github.com/repos/Spythere/stacjownik/releases/latest')
|
||||||
@@ -128,11 +124,48 @@ export default defineComponent({
|
|||||||
|
|
||||||
if (!releaseData) return;
|
if (!releaseData) return;
|
||||||
|
|
||||||
this.releaseURL = releaseData.html_url;
|
this.store.appUpdate = {
|
||||||
|
version,
|
||||||
|
changelog: releaseData.body,
|
||||||
|
releaseURL: releaseData.html_url
|
||||||
|
};
|
||||||
|
|
||||||
|
this.isUpdateModalOpen =
|
||||||
|
storageVersion != version || import.meta.env.VITE_UPDATE_TEST === 'test';
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Wystąpił błąd podczas pobierania danych z API GitHuba: ${error}`);
|
console.error(`Wystąpił błąd podczas pobierania danych z API GitHuba: ${error}`);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StorageManager.setStringValue(STORAGE_VERSION_KEY, version);
|
||||||
|
},
|
||||||
|
|
||||||
|
setupOfflineHandling() {
|
||||||
|
this.store.isOffline = !window.navigator.onLine;
|
||||||
|
|
||||||
|
if (this.store.isOffline) this.handleOfflineMode();
|
||||||
|
|
||||||
|
window.addEventListener('offline', this.handleOfflineMode);
|
||||||
|
window.addEventListener('online', this.handleOnlineMode);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleOfflineMode() {
|
||||||
|
this.store.isOffline = true;
|
||||||
|
|
||||||
|
this.apiStore.activeData = undefined;
|
||||||
|
this.apiStore.dataStatuses.connection = Status.Data.Offline;
|
||||||
|
},
|
||||||
|
|
||||||
|
handleOnlineMode() {
|
||||||
|
this.store.isOffline = false;
|
||||||
|
|
||||||
|
this.apiStore.connectToAPI();
|
||||||
|
},
|
||||||
|
|
||||||
|
changeLang(lang: string) {
|
||||||
|
this.$i18n.locale = lang;
|
||||||
|
this.currentLang = lang;
|
||||||
|
|
||||||
|
StorageManager.setStringValue('lang', lang);
|
||||||
},
|
},
|
||||||
|
|
||||||
loadLang() {
|
loadLang() {
|
||||||
@@ -156,4 +189,80 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" src="./App.scss"></style>
|
<style lang="scss">
|
||||||
|
@import './styles/global';
|
||||||
|
@import './styles/animations';
|
||||||
|
|
||||||
|
.route {
|
||||||
|
margin: 0 0.2em;
|
||||||
|
|
||||||
|
&-active,
|
||||||
|
&[data-active='true'] {
|
||||||
|
color: $accentCol;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// APP
|
||||||
|
#app {
|
||||||
|
color: white;
|
||||||
|
font-size: 1rem;
|
||||||
|
overflow-x: hidden;
|
||||||
|
|
||||||
|
@include smallScreen() {
|
||||||
|
font-size: calc(0.65rem + 0.8vw);
|
||||||
|
}
|
||||||
|
|
||||||
|
@include screenLandscape() {
|
||||||
|
font-size: calc(0.45rem + 0.8vw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CONTAINER
|
||||||
|
.app_container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto 1fr auto;
|
||||||
|
grid-template-columns: 100%;
|
||||||
|
|
||||||
|
min-height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app_main {
|
||||||
|
padding: 0 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning {
|
||||||
|
background-color: firebrick;
|
||||||
|
text-align: center;
|
||||||
|
padding: 0.5em 0.4em;
|
||||||
|
max-width: 1100px;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
border-radius: 0 0 1em 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FOOTER
|
||||||
|
.app_footer {
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 0.5em;
|
||||||
|
|
||||||
|
button {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 1.1em;
|
||||||
|
vertical-align: text-bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
background: #111;
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -29,11 +29,6 @@
|
|||||||
<img src="/images/icon-dispatcher.svg" alt="icon dispatcher" />
|
<img src="/images/icon-dispatcher.svg" alt="icon dispatcher" />
|
||||||
<span class="text--primary">{{ onlineDispatchersCount }}</span>
|
<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--grayed"> / </span>
|
||||||
<span class="text--primary">{{ onlineTrainsCount }}</span>
|
<span class="text--primary">{{ onlineTrainsCount }}</span>
|
||||||
<img src="/images/icon-train.svg" alt="icon train" />
|
<img src="/images/icon-train.svg" alt="icon train" />
|
||||||
@@ -68,7 +63,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { useStore } from '../../store/mainStore';
|
import { useMainStore } from '../../store/mainStore';
|
||||||
import StatusIndicator from './StatusIndicator.vue';
|
import StatusIndicator from './StatusIndicator.vue';
|
||||||
import Clock from './Clock.vue';
|
import Clock from './Clock.vue';
|
||||||
import RegionDropdown from '../Global/RegionDropdown.vue';
|
import RegionDropdown from '../Global/RegionDropdown.vue';
|
||||||
@@ -84,7 +79,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
return {
|
return {
|
||||||
store: useStore()
|
store: useMainStore()
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -100,15 +95,9 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
onlineDispatchersCount() {
|
onlineDispatchersCount() {
|
||||||
return this.store.onlineSceneryList.filter(
|
return this.store.activeSceneryList.filter(
|
||||||
(scenery) => scenery.region == this.store.region.id
|
(scenery) => scenery.region == this.store.region.id && scenery.dispatcherId != -1
|
||||||
).length;
|
).length;
|
||||||
},
|
|
||||||
|
|
||||||
factorU() {
|
|
||||||
return this.onlineDispatchersCount == 0
|
|
||||||
? '-'
|
|
||||||
: (this.onlineTrainsCount / this.onlineDispatchersCount).toFixed(2);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: { StatusIndicator, Clock, RegionDropdown }
|
components: { StatusIndicator, Clock, RegionDropdown }
|
||||||
|
|||||||
@@ -36,11 +36,11 @@
|
|||||||
<circle id="Ellipse 18" cx="15" cy="17" r="7" fill="#393838" />
|
<circle id="Ellipse 18" cx="15" cy="17" r="7" fill="#393838" />
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<g v-if="greenLight" filter="url(#filter0_d_843_28)">
|
<g v-if="indicator.lights.greenLight" filter="url(#filter0_d_843_28)">
|
||||||
<circle cx="15" cy="17" r="7" fill="#00FF0A" />
|
<circle cx="15" cy="17" r="7" fill="#00FF0A" />
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<g v-if="greenBlinkLight" filter="url(#filter0_d_843_28)">
|
<g v-if="indicator.lights.greenBlinkLight" filter="url(#filter0_d_843_28)">
|
||||||
<circle cx="15" cy="17" r="7" fill="#00FF0A" />
|
<circle cx="15" cy="17" r="7" fill="#00FF0A" />
|
||||||
|
|
||||||
<animate
|
<animate
|
||||||
@@ -52,14 +52,14 @@
|
|||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<g v-if="redTopLight" filter="url(#filter1_d_843_28)">
|
<g v-if="indicator.lights.redTopLight" filter="url(#filter1_d_843_28)">
|
||||||
<circle cx="15" cy="36" r="7" fill="#F40000" />
|
<circle cx="15" cy="36" r="7" fill="#F40000" />
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<g v-if="orangeLight" filter="url(#filter2_d_843_28)">
|
<g v-if="indicator.lights.orangeLight" filter="url(#filter2_d_843_28)">
|
||||||
<circle cx="15" cy="55" r="7" fill="#FFB800" />
|
<circle cx="15" cy="55" r="7" fill="#FFB800" />
|
||||||
</g>
|
</g>
|
||||||
<g v-if="redBottomLight" filter="url(#filter3_d_843_28)">
|
<g v-if="indicator.lights.redBottomLight" filter="url(#filter3_d_843_28)">
|
||||||
<circle cx="15" cy="74" r="7" fill="#F40000" />
|
<circle cx="15" cy="74" r="7" fill="#F40000" />
|
||||||
|
|
||||||
<animate
|
<animate
|
||||||
@@ -186,7 +186,11 @@
|
|||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
<transition name="tooltip-anim">
|
<transition name="tooltip-anim">
|
||||||
<div v-html="$t(indicator.message)" class="indicator-tooltip" v-if="tooltipActive"></div>
|
<div
|
||||||
|
v-html="$t('data-status.' + indicator.message)"
|
||||||
|
class="indicator-tooltip"
|
||||||
|
v-if="tooltipActive"
|
||||||
|
></div>
|
||||||
</transition>
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -194,124 +198,112 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { StoreState } from '../../store/typings';
|
|
||||||
import { useStore } from '../../store/mainStore';
|
|
||||||
import { Status } from '../../typings/common';
|
import { Status } from '../../typings/common';
|
||||||
|
import { useApiStore } from '../../store/apiStore';
|
||||||
|
import { APIDataStatus } from '../../typings/api';
|
||||||
|
|
||||||
|
interface Indicator {
|
||||||
|
// status: Status.Data;
|
||||||
|
message: string;
|
||||||
|
|
||||||
|
lights: {
|
||||||
|
greenLight: boolean;
|
||||||
|
greenBlinkLight: boolean;
|
||||||
|
redTopLight: boolean;
|
||||||
|
orangeLight: boolean;
|
||||||
|
redBottomLight: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
tooltipActive: false,
|
tooltipActive: false,
|
||||||
indicator: {
|
apiStore: useApiStore()
|
||||||
offline: false,
|
|
||||||
status: Status.Data.Loading,
|
|
||||||
message: 'data-status.S3'
|
|
||||||
},
|
|
||||||
|
|
||||||
greenLight: false,
|
|
||||||
greenBlinkLight: false,
|
|
||||||
redTopLight: false,
|
|
||||||
orangeLight: false,
|
|
||||||
redBottomLight: false
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
|
||||||
this.setSignalStatus(Status.Data.Loading);
|
|
||||||
},
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
const store = useStore();
|
|
||||||
|
|
||||||
return {
|
|
||||||
dataStatus: store.dataStatuses,
|
|
||||||
store
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
watch: {
|
|
||||||
dataStatus: {
|
|
||||||
deep: true,
|
|
||||||
|
|
||||||
handler(statuses: StoreState['dataStatuses']) {
|
|
||||||
const connectionStatus = statuses.connection;
|
|
||||||
const sceneryDataStatus = statuses.sceneries;
|
|
||||||
const trainsDataStatus = statuses.trains;
|
|
||||||
const dispatcherDataStatus = statuses.dispatchers;
|
|
||||||
|
|
||||||
if (this.store.isOffline) {
|
|
||||||
this.setSignalStatus(Status.Data.Initialized);
|
|
||||||
this.indicator.status = Status.Data.Initialized;
|
|
||||||
this.indicator.message = 'data-status.S1-offline';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (connectionStatus == Status.Data.Error) {
|
|
||||||
this.setSignalStatus(connectionStatus);
|
|
||||||
this.indicator.status = connectionStatus;
|
|
||||||
this.indicator.message = 'data-status.S1a-connection';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sceneryDataStatus == Status.Data.Error) {
|
|
||||||
this.setSignalStatus(sceneryDataStatus);
|
|
||||||
this.indicator.status = sceneryDataStatus;
|
|
||||||
this.indicator.message = 'data-status.S1a-sceneries';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (trainsDataStatus == Status.Data.Warning) {
|
|
||||||
this.setSignalStatus(trainsDataStatus);
|
|
||||||
this.indicator.status = trainsDataStatus;
|
|
||||||
this.indicator.message = 'data-status.S5-trains';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dispatcherDataStatus == Status.Data.Warning) {
|
|
||||||
this.setSignalStatus(dispatcherDataStatus);
|
|
||||||
this.indicator.status = dispatcherDataStatus;
|
|
||||||
this.indicator.message = 'data-status.S5-dispatchers';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sceneryDataStatus == Status.Data.Loaded) {
|
|
||||||
this.setSignalStatus(Status.Data.Loaded);
|
|
||||||
|
|
||||||
this.indicator.status = Status.Data.Loaded;
|
|
||||||
this.indicator.message = 'data-status.S2';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
setSignalStatus(status: Status.Data) {
|
setLights(message: string) {
|
||||||
this.greenLight = false;
|
let lights = {
|
||||||
this.greenBlinkLight = false;
|
greenBlinkLight: false,
|
||||||
this.redTopLight = false;
|
greenLight: false,
|
||||||
this.orangeLight = false;
|
orangeLight: false,
|
||||||
this.redBottomLight = false;
|
redBottomLight: false,
|
||||||
|
redTopLight: false
|
||||||
|
};
|
||||||
|
|
||||||
if (status == Status.Data.Initialized) {
|
switch (message) {
|
||||||
this.redTopLight = true;
|
case 'S3':
|
||||||
|
lights.greenBlinkLight = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'S2':
|
||||||
|
lights.greenLight = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'S1-offline':
|
||||||
|
lights.redTopLight = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'S1a-connection':
|
||||||
|
case 'S1a-sceneries':
|
||||||
|
lights.redTopLight = true;
|
||||||
|
lights.redBottomLight = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'S5-dispatchers':
|
||||||
|
case 'S5-trains':
|
||||||
|
lights.orangeLight = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status == Status.Data.Loaded) {
|
return lights;
|
||||||
this.greenLight = true;
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
indicator(): Indicator {
|
||||||
|
const dataStatuses = this.apiStore.dataStatuses;
|
||||||
|
const swdrStatuses = this.apiStore.activeData?.apiStatuses;
|
||||||
|
|
||||||
|
let message = 'S3';
|
||||||
|
|
||||||
|
switch (dataStatuses.connection) {
|
||||||
|
case Status.Data.Loading:
|
||||||
|
message = 'S3';
|
||||||
|
break;
|
||||||
|
case Status.Data.Loaded:
|
||||||
|
message = 'S2';
|
||||||
|
break;
|
||||||
|
case Status.Data.Offline:
|
||||||
|
message = 'S1-offline';
|
||||||
|
break;
|
||||||
|
case Status.Data.Error:
|
||||||
|
message = 'S1a-connection';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status == Status.Data.Warning) {
|
if (swdrStatuses?.dispatchersAPI == APIDataStatus.WARNING) {
|
||||||
this.orangeLight = true;
|
message = 'S5-dispatchers';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status == Status.Data.Error) {
|
if (swdrStatuses?.trainsAPI == APIDataStatus.WARNING) {
|
||||||
this.redTopLight = true;
|
message = 'S5-trains';
|
||||||
this.redBottomLight = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status == Status.Data.Loading) {
|
if (swdrStatuses?.stationsAPI == APIDataStatus.WARNING) {
|
||||||
this.greenBlinkLight = true;
|
message = 'S1a-sceneries';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
lights: this.setLights(message),
|
||||||
|
message
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
<template>
|
||||||
|
<AnimatedModal :is-open="updateModalOpen" @toggle-modal="toggleModal(false)">
|
||||||
|
<div class="modal-content">
|
||||||
|
<h1 style="margin-bottom: 0.5em">🚀 {{ $t('update.title') }}</h1>
|
||||||
|
|
||||||
|
<div class="features-body" v-if="htmlChangelog != ''" v-html="htmlChangelog"></div>
|
||||||
|
<div class="no-features" v-else>{{ $t('update.no-data') }}</div>
|
||||||
|
|
||||||
|
<button class="btn btn--action" ref="confirm-btn" @click="toggleModal(false)">
|
||||||
|
{{ $t('update.confirm') }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<p class="bottom-info">
|
||||||
|
{{ $t('update.info-1') }}
|
||||||
|
<br />
|
||||||
|
<span v-html="$t('update.info-2')"></span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</AnimatedModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import { useMainStore } from '../../store/mainStore';
|
||||||
|
import { version } from '../../../package.json';
|
||||||
|
import { Converter } from 'showdown';
|
||||||
|
|
||||||
|
import AnimatedModal from '../Global/AnimatedModal.vue';
|
||||||
|
|
||||||
|
const converter = new Converter();
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: { AnimatedModal },
|
||||||
|
|
||||||
|
props: {
|
||||||
|
updateModalOpen: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
emits: ['toggleModal'],
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
mainStore: useMainStore(),
|
||||||
|
version: version
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
updateModalOpen(val: boolean) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (val) (this.$refs['confirm-btn'] as HTMLElement).focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
htmlChangelog() {
|
||||||
|
if (this.mainStore.appUpdate == null) return '';
|
||||||
|
|
||||||
|
const x = converter.makeHtml(this.mainStore.appUpdate.changelog);
|
||||||
|
console.log(x);
|
||||||
|
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
toggleModal(value: boolean) {
|
||||||
|
this.$emit('toggleModal', value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
::v-deep(h1) {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep(h2) {
|
||||||
|
padding: 0.25em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep(ul) {
|
||||||
|
list-style: inside;
|
||||||
|
padding: 0.5em;
|
||||||
|
line-height: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto 1fr auto;
|
||||||
|
gap: 0.5em;
|
||||||
|
padding: 1em;
|
||||||
|
min-height: 700px;
|
||||||
|
overflow: auto;
|
||||||
|
text-align: justify;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-features {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0.5em 0.75em;
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.bottom-info {
|
||||||
|
text-align: center;
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<transition name="modal-anim" tag="div" class="modal">
|
<transition name="modal-anim" tag="div">
|
||||||
<div class="body" v-if="isOpen">
|
<div class="modal" v-if="isOpen">
|
||||||
<div class="background" @click="toggleModal(false)"></div>
|
<div class="modal-background" @click="toggleModal(false)"></div>
|
||||||
<div class="wrapper" ref="wrapper" tabindex="0">
|
<div class="modal-wrapper" ref="wrapper" tabindex="0">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-exit" ref="exit" tabindex="0" @focus="toggleModal(false)"></div>
|
<div class="tab-exit" ref="exit" tabindex="0" @focus="toggleModal(false)"></div>
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { useStore } from '../../store/mainStore';
|
import { useMainStore } from '../../store/mainStore';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
emits: ['toggleModal'],
|
emits: ['toggleModal'],
|
||||||
@@ -23,15 +23,14 @@ export default defineComponent({
|
|||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
store: useStore()
|
store: useMainStore()
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
isOpen(v) {
|
isOpen(v) {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (v) (this.$refs['wrapper'] as HTMLElement).focus();
|
if (v == false) (this.store.modalLastClickedTarget as HTMLElement)?.focus();
|
||||||
else (this.store.modalLastClickedTarget as HTMLElement)?.focus();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -47,17 +46,17 @@ export default defineComponent({
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../styles/responsive.scss';
|
@import '../../styles/responsive.scss';
|
||||||
|
|
||||||
.body {
|
.modal {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: 200;
|
|
||||||
|
|
||||||
width: 100vw;
|
width: 100%;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
z-index: 200;
|
||||||
}
|
}
|
||||||
|
|
||||||
.background {
|
.modal-background {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@@ -69,33 +68,21 @@ export default defineComponent({
|
|||||||
background-color: rgba(0, 0, 0, 0.55);
|
background-color: rgba(0, 0, 0, 0.55);
|
||||||
}
|
}
|
||||||
|
|
||||||
.wrapper {
|
.modal-wrapper {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 210;
|
||||||
|
overflow: auto;
|
||||||
|
max-height: 95vh;
|
||||||
|
|
||||||
|
& > :slotted(div) {
|
||||||
background-color: #1a1a1a;
|
background-color: #1a1a1a;
|
||||||
box-shadow: 0 0 15px 10px #333333;
|
box-shadow: 0 0 15px 10px #0e0e0e;
|
||||||
|
|
||||||
width: 95%;
|
width: 95vw;
|
||||||
max-width: 800px;
|
max-width: 850px;
|
||||||
max-height: 95vh;
|
|
||||||
|
|
||||||
& > :slotted(div) {
|
|
||||||
max-height: 95vh;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include smallScreen {
|
|
||||||
.wrapper {
|
|
||||||
top: 0;
|
|
||||||
transform: translate(-50%, 1em);
|
|
||||||
max-height: 90vh;
|
|
||||||
|
|
||||||
& > :slotted(div) {
|
|
||||||
max-height: 90vh;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,184 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="donation-modal" @keydown.esc="toggleModal(false)">
|
|
||||||
<button
|
|
||||||
class="modal_button action btn--image"
|
|
||||||
ref="btn"
|
|
||||||
@click="toggleModal(true)"
|
|
||||||
@focus="toggleModal(false)"
|
|
||||||
>
|
|
||||||
<img src="/images/icon-dollar.svg" alt="dollar donation icon" />
|
|
||||||
<span>{{ $t('donations.button-title') }}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<AnimatedModal :isOpen="isModalOpen" @toggleModal="toggleModal">
|
|
||||||
<div class="modal_content">
|
|
||||||
<div class="modal_main">
|
|
||||||
<h1 v-html="$t('donations.header')"></h1>
|
|
||||||
<br />
|
|
||||||
<p v-html="$t('donations.p1')"></p>
|
|
||||||
<br />
|
|
||||||
<i18n-t keypath="donations.p2" tag="p">
|
|
||||||
<template v-slot:b1>
|
|
||||||
<b>{{ $t('donations.p2-b1') }}</b>
|
|
||||||
</template>
|
|
||||||
<template v-slot:b2>
|
|
||||||
<b>{{ $t('donations.p2-b2') }}</b>
|
|
||||||
</template>
|
|
||||||
<template v-slot:b3>
|
|
||||||
<b>{{ $t('donations.p2-b3') }}</b>
|
|
||||||
</template>
|
|
||||||
<template v-slot:link>
|
|
||||||
<a href="https://discord.gg/x2mpNN3svk" target="_blank">
|
|
||||||
{{ $t('donations.p2-a1') }}
|
|
||||||
</a>
|
|
||||||
</template>
|
|
||||||
</i18n-t>
|
|
||||||
<br />
|
|
||||||
<p v-html="$t('donations.p3')"></p>
|
|
||||||
<br />
|
|
||||||
<i18n-t keypath="donations.p4" tag="p">
|
|
||||||
<template v-slot:img>
|
|
||||||
<img src="/images/icon-diamond.svg" alt="donator diamond icon" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-slot:b1>
|
|
||||||
<b>{{ $t('donations.p4-b1') }}</b>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-slot:b2>
|
|
||||||
<b>{{ $t('donations.p4-b2') }}</b>
|
|
||||||
</template>
|
|
||||||
</i18n-t>
|
|
||||||
<br />
|
|
||||||
<i
|
|
||||||
v-html="$t('donations.p5')"
|
|
||||||
style="display: flex; justify-content: flex-end; text-align: right"
|
|
||||||
>
|
|
||||||
</i>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal_actions">
|
|
||||||
<button class="modal_button exit btn--image" @click="toggleModal(false)">
|
|
||||||
<img src="/images/icon-exit.svg" alt="dollar donation icon" />
|
|
||||||
{{ $t('donations.action-exit') }}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<form action="https://www.paypal.com/donate" method="post">
|
|
||||||
<input type="hidden" name="hosted_button_id" value="EDB3SKFAHXFTW" />
|
|
||||||
|
|
||||||
<button class="modal_button action btn--image" @click="toggleModal(false)">
|
|
||||||
<img src="/images/icon-dollar.svg" alt="dollar donation icon" />
|
|
||||||
{{ $t('donations.action-confirm') }}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</AnimatedModal>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
import AnimatedModal from './AnimatedModal.vue';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
isModalOpen: Boolean
|
|
||||||
},
|
|
||||||
emits: ['toggleModal'],
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
toggleModal(value: boolean) {
|
|
||||||
this.$emit('toggleModal', value);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
components: { AnimatedModal }
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@import '../../styles/responsive.scss';
|
|
||||||
|
|
||||||
.modal_button {
|
|
||||||
&.action {
|
|
||||||
$btnColor: #254069;
|
|
||||||
|
|
||||||
background-color: $btnColor;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: lighten($btnColor, 10%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.exit {
|
|
||||||
$btnColor: crimson;
|
|
||||||
|
|
||||||
background-color: $btnColor;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: lighten($btnColor, 10%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include smallScreen {
|
|
||||||
span {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-logo {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 50%;
|
|
||||||
|
|
||||||
width: 6em;
|
|
||||||
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal_content {
|
|
||||||
display: grid;
|
|
||||||
grid-template-rows: 1fr auto;
|
|
||||||
gap: 1em;
|
|
||||||
|
|
||||||
font-size: 1.1em;
|
|
||||||
|
|
||||||
& > div {
|
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 1.95em;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
text-align: justify;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal_main {
|
|
||||||
overflow: auto;
|
|
||||||
|
|
||||||
img {
|
|
||||||
max-height: 20px;
|
|
||||||
margin-right: 5px;
|
|
||||||
vertical-align: text-bottom;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal_actions {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(13em, 1fr));
|
|
||||||
gap: 0.5em;
|
|
||||||
|
|
||||||
form button {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -0,0 +1,261 @@
|
|||||||
|
<template>
|
||||||
|
<AnimatedModal
|
||||||
|
class="donation-modal"
|
||||||
|
:isOpen="isModalOpen"
|
||||||
|
@toggleModal="toggleModal"
|
||||||
|
@keydown.esc="toggleModal(false)"
|
||||||
|
>
|
||||||
|
<div class="modal_content">
|
||||||
|
<div class="modal_main">
|
||||||
|
<h1 v-html="$t('donations.header')"></h1>
|
||||||
|
<div class="donators-slider" v-if="donatorList.length != 0">
|
||||||
|
<span v-html="$t('donations.donator-title', { count: donatorList.length })"></span>
|
||||||
|
|
||||||
|
<transition mode="out-in" name="slider-anim" class="current-name">
|
||||||
|
<span :key="displayingName">
|
||||||
|
<img src="/images/icon-diamond.svg" alt="donator diamond icon" />
|
||||||
|
{{ displayingName }}
|
||||||
|
</span>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<p v-html="$t('donations.p1')"></p>
|
||||||
|
<br />
|
||||||
|
<i18n-t keypath="donations.p2" tag="p">
|
||||||
|
<template v-slot:b1>
|
||||||
|
<b>{{ $t('donations.p2-b1') }}</b>
|
||||||
|
</template>
|
||||||
|
<template v-slot:b2>
|
||||||
|
<b>{{ $t('donations.p2-b2') }}</b>
|
||||||
|
</template>
|
||||||
|
<template v-slot:b3>
|
||||||
|
<b>{{ $t('donations.p2-b3') }}</b>
|
||||||
|
</template>
|
||||||
|
<template v-slot:link>
|
||||||
|
<a class="discord" href="https://discord.gg/x2mpNN3svk" target="_blank">
|
||||||
|
{{ $t('donations.p2-a1') }}
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
<br />
|
||||||
|
<p v-html="$t('donations.p3')"></p>
|
||||||
|
<br />
|
||||||
|
<i18n-t keypath="donations.p4" tag="p">
|
||||||
|
<template v-slot:img>
|
||||||
|
<img src="/images/icon-diamond.svg" alt="donator diamond icon" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:b1>
|
||||||
|
<b>{{ $t('donations.p4-b1') }}</b>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:b2>
|
||||||
|
<b>{{ $t('donations.p4-b2') }}</b>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
<br />
|
||||||
|
<i
|
||||||
|
v-html="$t('donations.p5')"
|
||||||
|
style="display: flex; justify-content: flex-end; text-align: right"
|
||||||
|
>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal_actions">
|
||||||
|
<a
|
||||||
|
class="modal-action a-button btn--image coffee"
|
||||||
|
href="https://buycoffee.to/spythere"
|
||||||
|
target="_blank"
|
||||||
|
ref="action"
|
||||||
|
>
|
||||||
|
<img src="/images/icon-coffee.png" width="20" alt="buycoffee.to donation" />
|
||||||
|
{{ $t('donations.action-buycoffee') }}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a
|
||||||
|
class="modal-action a-button btn--image paypal"
|
||||||
|
href="https://www.paypal.com/donate/?hosted_button_id=EDB3SKFAHXFTW"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<img src="/images/icon-dollar.svg" alt="paypal donation" />
|
||||||
|
{{ $t('donations.action-paypal') }}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<button class="modal-action btn--image exit" @click="toggleModal(false)">
|
||||||
|
<img src="/images/icon-exit.svg" alt="dollar donation icon" />
|
||||||
|
{{ $t('donations.action-exit') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AnimatedModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import AnimatedModal from './AnimatedModal.vue';
|
||||||
|
import { useApiStore } from '../../store/apiStore';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: { AnimatedModal },
|
||||||
|
props: {
|
||||||
|
isModalOpen: Boolean
|
||||||
|
},
|
||||||
|
|
||||||
|
emits: ['toggleModal'],
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
isModalOpen(val: boolean) {
|
||||||
|
this.running = val;
|
||||||
|
this.lastUpdate = Date.now();
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (val) (this.$refs['action'] as HTMLElement).focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.runUpdate();
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
apiStore: useApiStore(),
|
||||||
|
displayingIndex: 0,
|
||||||
|
lastUpdate: 0,
|
||||||
|
running: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
displayingName() {
|
||||||
|
return this.donatorList[this.displayingIndex];
|
||||||
|
},
|
||||||
|
|
||||||
|
donatorList() {
|
||||||
|
return this.apiStore.donatorsData.slice().sort(() => Math.sign(Math.random() * -2 + 1));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
toggleModal(value: boolean) {
|
||||||
|
this.$emit('toggleModal', value);
|
||||||
|
},
|
||||||
|
|
||||||
|
runUpdate() {
|
||||||
|
if (Date.now() >= this.lastUpdate + 2000 && this.running) {
|
||||||
|
this.displayingIndex = (this.displayingIndex + 1) % this.donatorList.length;
|
||||||
|
this.lastUpdate = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.requestAnimationFrame(this.runUpdate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '../../styles/responsive.scss';
|
||||||
|
|
||||||
|
.modal_content {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 1fr auto;
|
||||||
|
gap: 1em;
|
||||||
|
|
||||||
|
font-size: 1.1em;
|
||||||
|
|
||||||
|
& > div {
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 1.95em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
text-align: justify;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.discord {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal_main {
|
||||||
|
overflow: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-height: 20px;
|
||||||
|
margin-right: 5px;
|
||||||
|
vertical-align: text-bottom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal_actions {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||||
|
gap: 0.5em;
|
||||||
|
|
||||||
|
form button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal_actions > .modal-action {
|
||||||
|
&.paypal {
|
||||||
|
$btnColor: #254069;
|
||||||
|
|
||||||
|
background-color: $btnColor;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: lighten($btnColor, 5%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.coffee {
|
||||||
|
$btnColor: #009255;
|
||||||
|
background-color: $btnColor;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: lighten($btnColor, 5%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.exit {
|
||||||
|
$btnColor: #686868;
|
||||||
|
background-color: $btnColor;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: lighten($btnColor, 5%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.donators-slider {
|
||||||
|
text-align: center;
|
||||||
|
line-height: 30px;
|
||||||
|
|
||||||
|
.current-name {
|
||||||
|
backface-visibility: hidden;
|
||||||
|
display: block;
|
||||||
|
font-weight: bold;
|
||||||
|
word-wrap: break-word;
|
||||||
|
color: var(--clr-donator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-anim {
|
||||||
|
&-move,
|
||||||
|
&-enter-active,
|
||||||
|
&-leave-active {
|
||||||
|
transition: all 150ms ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-enter-from,
|
||||||
|
&-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, Ref, ref } from 'vue';
|
import { defineComponent, Ref, ref } from 'vue';
|
||||||
import { regions as regionsJSON } from '../../data/options.json';
|
import { regions as regionsJSON } from '../../data/options.json';
|
||||||
import { useStore } from '../../store/mainStore';
|
import { useMainStore } from '../../store/mainStore';
|
||||||
|
|
||||||
interface Item {
|
interface Item {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -41,7 +41,7 @@ interface Item {
|
|||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
store: useStore(),
|
store: useMainStore(),
|
||||||
selectedItemIndex: 0,
|
selectedItemIndex: 0,
|
||||||
listOpen: false
|
listOpen: false
|
||||||
};
|
};
|
||||||
@@ -60,6 +60,19 @@ export default defineComponent({
|
|||||||
handler(regionId) {
|
handler(regionId) {
|
||||||
this.selectedItemIndex = this.regionList.findIndex((reg) => reg.id == regionId);
|
this.selectedItemIndex = this.regionList.findIndex((reg) => reg.id == regionId);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
'$route.query.region': {
|
||||||
|
immediate: true,
|
||||||
|
handler(regionQuery: string) {
|
||||||
|
if (regionQuery) {
|
||||||
|
this.store.region =
|
||||||
|
regionsJSON.find(
|
||||||
|
(reg) =>
|
||||||
|
reg.id == regionQuery.toLocaleLowerCase() ||
|
||||||
|
reg.value.toLocaleLowerCase() == regionQuery.toLocaleLowerCase()
|
||||||
|
) ?? regionsJSON[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -70,8 +83,8 @@ export default defineComponent({
|
|||||||
|
|
||||||
regionList() {
|
regionList() {
|
||||||
return regionsJSON.map((region) => {
|
return regionsJSON.map((region) => {
|
||||||
const regionStationCount = this.store.onlineSceneryList.filter(
|
const regionStationCount = this.store.activeSceneryList.filter(
|
||||||
(scenery) => scenery.region == region.id
|
(scenery) => scenery.region == region.id && scenery.dispatcherId != -1
|
||||||
).length;
|
).length;
|
||||||
|
|
||||||
const regionTrainCount =
|
const regionTrainCount =
|
||||||
@@ -126,15 +139,10 @@ button.selected-region {
|
|||||||
color: paleturquoise;
|
color: paleturquoise;
|
||||||
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
padding: 0.1em 0.5em;
|
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
background-color: #262626;
|
background-color: #262626;
|
||||||
}
|
}
|
||||||
|
|
||||||
span {
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
@@ -184,6 +192,8 @@ li.option {
|
|||||||
}
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5em 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@@ -194,10 +204,6 @@ li.option {
|
|||||||
background-color: #333333f2;
|
background-color: #333333f2;
|
||||||
}
|
}
|
||||||
|
|
||||||
padding: 0.5em 0;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,9 @@ export default defineComponent({
|
|||||||
case Status.ActiveDispatcher.UNKNOWN:
|
case Status.ActiveDispatcher.UNKNOWN:
|
||||||
return 'unknown';
|
return 'unknown';
|
||||||
|
|
||||||
|
case Status.ActiveDispatcher.FREE:
|
||||||
|
return 'free';
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if (this.dispatcherTimestamp != null && this.dispatcherStatus >= Date.now() + 25500000)
|
if (this.dispatcherTimestamp != null && this.dispatcherStatus >= Date.now() + 25500000)
|
||||||
return 'no-limit';
|
return 'no-limit';
|
||||||
@@ -83,7 +86,7 @@ $online: #09a116;
|
|||||||
$unknown: #b93c3c;
|
$unknown: #b93c3c;
|
||||||
|
|
||||||
.status-badge {
|
.status-badge {
|
||||||
border-radius: 1rem;
|
border-radius: 1em;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|
||||||
padding: 0.2em 0.55em;
|
padding: 0.2em 0.55em;
|
||||||
|
|||||||
@@ -1,7 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="stock-list">
|
<div class="stock-list">
|
||||||
<ul>
|
<div v-if="tractionOnly">
|
||||||
<li v-for="(stockName, i) in trainStockList" :key="i">
|
<p>
|
||||||
|
{{ computedStockList[0].split(':')[0].split('_').splice(0, 2).join(' ') }}
|
||||||
|
{{ computedStockList[0].split(':')[1] }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<img
|
||||||
|
class="traction-only"
|
||||||
|
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${computedStockList[0].split(':')[0]}${
|
||||||
|
/^EN/.test(computedStockList[0]) ? 'rb' : ''
|
||||||
|
}.png`"
|
||||||
|
@error="onImageError($event, computedStockList[0])"
|
||||||
|
width="300"
|
||||||
|
height="60"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul v-else>
|
||||||
|
<li v-for="(stockName, i) in computedStockList" :key="i">
|
||||||
<p>
|
<p>
|
||||||
{{ stockName.split(':')[0].split('_').splice(0, 2).join(' ') }}
|
{{ stockName.split(':')[0].split('_').splice(0, 2).join(' ') }}
|
||||||
{{ stockName.split(':')[1] }}
|
{{ stockName.split(':')[1] }}
|
||||||
@@ -9,39 +26,55 @@
|
|||||||
|
|
||||||
<span>
|
<span>
|
||||||
<img
|
<img
|
||||||
|
:data-mouseover="stockName"
|
||||||
|
data-tooltip-type="VehiclePreviewTooltip"
|
||||||
|
:data-tooltip-content="stockName.split(':')[0]"
|
||||||
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}${
|
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}${
|
||||||
/^EN/.test(stockName) ? 'rb' : ''
|
/^EN/.test(stockName) ? 'rb' : ''
|
||||||
}.png`"
|
}.png`"
|
||||||
@error="onImageError($event, stockName)"
|
@error="onImageError($event, stockName)"
|
||||||
|
@click.stop="() => {}"
|
||||||
width="400"
|
width="400"
|
||||||
height="60"
|
height="60"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- /// Manualne dodawanie miniaturek członów dla kibelków /// -->
|
||||||
<img
|
<img
|
||||||
|
:data-mouseover="stockName"
|
||||||
|
data-tooltip-type="VehiclePreviewTooltip"
|
||||||
|
:data-tooltip-content="stockName.split(':')[0]"
|
||||||
v-if="/^(EN|2EN)/.test(stockName)"
|
v-if="/^(EN|2EN)/.test(stockName)"
|
||||||
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}s.png`"
|
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}s.png`"
|
||||||
@error="
|
@error="
|
||||||
(event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-s.png')
|
(event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-s.png')
|
||||||
"
|
"
|
||||||
|
@click.stop="() => {}"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<img
|
<img
|
||||||
class="train-thumbnail"
|
:data-mouseover="stockName"
|
||||||
|
data-tooltip-type="VehiclePreviewTooltip"
|
||||||
|
:data-tooltip-content="stockName.split(':')[0]"
|
||||||
v-if="/^EN71/.test(stockName)"
|
v-if="/^EN71/.test(stockName)"
|
||||||
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}s.png`"
|
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}s.png`"
|
||||||
@error="
|
@error="
|
||||||
(event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-s.png')
|
(event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-s.png')
|
||||||
"
|
"
|
||||||
|
@click.stop="() => {}"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<img
|
<img
|
||||||
class="train-thumbnail"
|
:data-mouseover="stockName"
|
||||||
|
data-tooltip-type="VehiclePreviewTooltip"
|
||||||
|
:data-tooltip-content="stockName.split(':')[0]"
|
||||||
v-if="/^(EN|2EN)/.test(stockName)"
|
v-if="/^(EN|2EN)/.test(stockName)"
|
||||||
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}ra.png`"
|
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}ra.png`"
|
||||||
@error="
|
@error="
|
||||||
(event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-ra.png')
|
(event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-ra.png')
|
||||||
"
|
"
|
||||||
|
@click.stop="() => {}"
|
||||||
/>
|
/>
|
||||||
|
<!-- /// -->
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -50,31 +83,49 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { PropType, defineComponent } from 'vue';
|
import { PropType, defineComponent } from 'vue';
|
||||||
import { useStore } from '../../store/mainStore';
|
import { useApiStore } from '../../store/apiStore';
|
||||||
import { API } from '../../typings/api';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
trainStockList: {
|
trainStockList: {
|
||||||
type: Array as PropType<string[]>,
|
type: Array as PropType<string[]>,
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
tractionOnly: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
store: useStore()
|
apiStore: useApiStore()
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
computedStockList() {
|
||||||
|
return this.tractionOnly ? this.trainStockList.slice(0, 1) : this.trainStockList;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
onImageError(event: Event, stockName: string) {
|
onImageError(event: Event, stockName: string) {
|
||||||
const fallbackName =
|
let fallbackName = '';
|
||||||
Object.keys(this.store.rollingStockData!.info).find((type) => {
|
|
||||||
return this.store.rollingStockData!.info[type as keyof API.RollingStock.Info].find(
|
const isLoco = /.-\d{3}/.test(stockName);
|
||||||
(v) => v[0] === stockName.split(':')[0]
|
|
||||||
);
|
if (isLoco) {
|
||||||
}) || 'vehicle-unknown';
|
if (/^\d?EN\d{2}/.test(stockName)) fallbackName = 'loco-ezt';
|
||||||
|
else if (/^SN\d{2}/.test(stockName)) fallbackName = 'loco-szt';
|
||||||
|
else if (/^\d{0,}?E/.test(stockName)) fallbackName = 'loco-e';
|
||||||
|
else fallbackName = 'loco-s';
|
||||||
|
} else {
|
||||||
|
const isCarPassenger = /(\d{3}a|(Bau|Gor)\d{2}|304C)_/.test(stockName);
|
||||||
|
|
||||||
|
fallbackName += 'car-';
|
||||||
|
fallbackName += isCarPassenger ? 'passenger' : 'cargo';
|
||||||
|
}
|
||||||
|
|
||||||
(event.target as HTMLImageElement).src = `/images/icon-${fallbackName}.png`;
|
(event.target as HTMLImageElement).src = `/images/icon-${fallbackName}.png`;
|
||||||
}
|
}
|
||||||
@@ -99,6 +150,7 @@ export default defineComponent({
|
|||||||
ul > li > span {
|
ul > li > span {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
|
cursor: crosshair;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
@@ -107,10 +159,14 @@ img {
|
|||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
img.traction-only {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
font-size: 0.9em;
|
font-size: 0.95em;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,123 +0,0 @@
|
|||||||
<template>
|
|
||||||
<span class="stop-date">
|
|
||||||
<span
|
|
||||||
class="date arrival"
|
|
||||||
v-if="!stop.beginsHere"
|
|
||||||
:class="{
|
|
||||||
delayed: stop.arrivalDelay > 0 && (stop.confirmed || stop.stopped),
|
|
||||||
preponed: stop.arrivalDelay < 0 && (stop.confirmed || stop.stopped),
|
|
||||||
'on-time': stop.arrivalDelay == 0 && stop.confirmed
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<span v-if="stop.arrivalDelay != 0 && (stop.confirmed || stop.stopped)">
|
|
||||||
<s>{{ timestampToString(stop.arrivalTimestamp) }}</s>
|
|
||||||
{{ timestampToString(stop.arrivalRealTimestamp) }}
|
|
||||||
({{ stop.arrivalDelay > 0 ? '+' : '' }}{{ stop.arrivalDelay }})
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span v-else>
|
|
||||||
{{ timestampToString(stop.arrivalTimestamp) }}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span
|
|
||||||
class="date stop"
|
|
||||||
v-if="stop.stopTime || stop.stopped"
|
|
||||||
:class="stop.stopType.replace(', ', '-')"
|
|
||||||
>
|
|
||||||
{{ stop.stopTime }} {{ stop.stopType == '' ? 'pt' : stop.stopType }}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span
|
|
||||||
class="date departure"
|
|
||||||
v-if="!stop.terminatesHere && (stop.stopTime != 0 || stop.stopped)"
|
|
||||||
:class="{
|
|
||||||
delayed: stop.departureDelay > 0 && stop.confirmed,
|
|
||||||
preponed: stop.departureDelay < 0 && stop.confirmed
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<span v-if="stop.departureDelay != 0 && stop.confirmed">
|
|
||||||
<s>{{ timestampToString(stop.departureTimestamp) }}</s>
|
|
||||||
{{ timestampToString(stop.departureRealTimestamp) }}
|
|
||||||
|
|
||||||
({{ stop.departureDelay > 0 ? '+' : '' }}{{ stop.departureDelay }})
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span v-else>
|
|
||||||
{{ timestampToString(stop.departureTimestamp) }}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { PropType, defineComponent } from 'vue';
|
|
||||||
import dateMixin from '../../mixins/dateMixin';
|
|
||||||
import { TrainStop } from '../../store/typings';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
mixins: [dateMixin],
|
|
||||||
|
|
||||||
props: {
|
|
||||||
stop: {
|
|
||||||
type: Object as PropType<TrainStop>,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
$preponedClr: lime;
|
|
||||||
$delayedClr: salmon;
|
|
||||||
$dateClr: #525151;
|
|
||||||
$stopExchangeClr: #db8e29;
|
|
||||||
$stopDefaultClr: #252525;
|
|
||||||
|
|
||||||
.stop-date {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.date {
|
|
||||||
background: $dateClr;
|
|
||||||
padding: 0.3em 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stop {
|
|
||||||
&.ph,
|
|
||||||
&.ph-pm,
|
|
||||||
&.pm {
|
|
||||||
background: $stopExchangeClr;
|
|
||||||
}
|
|
||||||
|
|
||||||
background: $stopDefaultClr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.arrival,
|
|
||||||
.departure {
|
|
||||||
&.delayed {
|
|
||||||
s {
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
span {
|
|
||||||
color: $delayedClr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.preponed {
|
|
||||||
s {
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
span {
|
|
||||||
color: $preponedClr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
<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 { useStore } from '../../store/mainStore';
|
|
||||||
import { API } from '../../typings/api';
|
|
||||||
|
|
||||||
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 API.RollingStock.Info].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,241 +0,0 @@
|
|||||||
<template>
|
|
||||||
<section class="daily-stats">
|
|
||||||
<span :data-active="statsStatus">
|
|
||||||
<b v-if="statsStatus == Status.Data.Loading">
|
|
||||||
{{ $t('app.loading') }}
|
|
||||||
</b>
|
|
||||||
|
|
||||||
<b v-else-if="stats.distanceSum == null">
|
|
||||||
{{ $t('journal.daily-stats-info') }}
|
|
||||||
</b>
|
|
||||||
|
|
||||||
<span class="stats-list" v-else>
|
|
||||||
<h3>
|
|
||||||
{{ $t('journal.daily-stats-title') }}
|
|
||||||
<b class="text--primary">{{ new Date().toLocaleDateString($i18n.locale) }}</b>
|
|
||||||
</h3>
|
|
||||||
<hr style="margin-bottom: 0.5em" />
|
|
||||||
|
|
||||||
<div v-if="stats.totalTimetables">
|
|
||||||
•
|
|
||||||
<i18n-t keypath="journal.timetable-stats-total">
|
|
||||||
<template #count>
|
|
||||||
<b class="text--primary">
|
|
||||||
{{ stats.totalTimetables }}
|
|
||||||
{{ $t('journal.timetable-count', stats.totalTimetables) }}
|
|
||||||
</b>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #distance>
|
|
||||||
<b class="text--primary"> {{ stats.distanceSum?.toFixed(2) }} km</b>
|
|
||||||
</template>
|
|
||||||
</i18n-t>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="stats.maxTimetable">
|
|
||||||
•
|
|
||||||
<i18n-t keypath="journal.timetable-stats-longest">
|
|
||||||
<template #id>
|
|
||||||
<router-link :to="`/journal/timetables?timetableId=${stats.maxTimetable.id}`">
|
|
||||||
<b>{{ stats.maxTimetable.id }}</b>
|
|
||||||
</router-link>
|
|
||||||
</template>
|
|
||||||
<template #author>
|
|
||||||
<router-link
|
|
||||||
:to="`/journal/dispatchers?dispatcherName=${stats.maxTimetable.authorName}`"
|
|
||||||
>
|
|
||||||
<b>{{ stats.maxTimetable.authorName }}</b>
|
|
||||||
</router-link>
|
|
||||||
</template>
|
|
||||||
<template #driver>
|
|
||||||
<b class="text--primary">{{ stats.maxTimetable.driverName }}</b>
|
|
||||||
</template>
|
|
||||||
<template #distance>
|
|
||||||
<b class="text--primary">{{ stats.maxTimetable.routeDistance }} km</b>
|
|
||||||
</template>
|
|
||||||
</i18n-t>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="topDispatchers.length == 1">
|
|
||||||
•
|
|
||||||
<i18n-t keypath="journal.timetable-stats-most-active-dr">
|
|
||||||
<template #dispatcher>
|
|
||||||
<router-link :to="`/journal/dispatchers?dispatcherName=${topDispatchers[0].name}`">
|
|
||||||
<b>{{ topDispatchers[0].name }}</b>
|
|
||||||
</router-link>
|
|
||||||
</template>
|
|
||||||
<template #count>
|
|
||||||
<b class="text--primary">
|
|
||||||
{{ topDispatchers[0].count }}
|
|
||||||
{{ $t('journal.timetable-count', topDispatchers[0].count) }}
|
|
||||||
</b>
|
|
||||||
</template>
|
|
||||||
</i18n-t>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="topDispatchers.length > 1">
|
|
||||||
•
|
|
||||||
<i18n-t keypath="journal.timetable-stats-most-active-dr-many">
|
|
||||||
<template #dispatchers>
|
|
||||||
<span v-for="(disp, i) in topDispatchers" :key="i">
|
|
||||||
<span v-if="i == topDispatchers.length - 1"> {{ $t('general.and') }} </span>
|
|
||||||
|
|
||||||
<router-link :to="`/journal/dispatchers?dispatcherName=${disp.name}`">
|
|
||||||
<b>{{ disp.name }}</b>
|
|
||||||
</router-link>
|
|
||||||
|
|
||||||
<span v-if="i < topDispatchers.length - 2">, </span>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #count>
|
|
||||||
<b class="text--primary">
|
|
||||||
{{ topDispatchers[0].count }}
|
|
||||||
{{ $t('journal.timetable-count', topDispatchers[0].count) }}
|
|
||||||
</b>
|
|
||||||
</template>
|
|
||||||
</i18n-t>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="stats.longestDuties.length > 0">
|
|
||||||
•
|
|
||||||
<i18n-t keypath="journal.timetable-stats-longest-duties">
|
|
||||||
<template #dispatcher>
|
|
||||||
<router-link
|
|
||||||
:to="`/journal/dispatchers?dispatcherName=${stats.longestDuties[0].name}`"
|
|
||||||
>
|
|
||||||
<b>{{ stats.longestDuties[0].name }}</b>
|
|
||||||
</router-link>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #station>{{ stats.longestDuties[0].station }}</template>
|
|
||||||
|
|
||||||
<template #duration>
|
|
||||||
{{ calculateDuration(stats.longestDuties[0].duration) }}
|
|
||||||
</template>
|
|
||||||
</i18n-t>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="stats.mostActiveDrivers.length > 0">
|
|
||||||
•
|
|
||||||
<i18n-t keypath="journal.timetable-stats-most-active-driver">
|
|
||||||
<template #driver>
|
|
||||||
<b class="text--primary">{{ stats.mostActiveDrivers[0].name }}</b>
|
|
||||||
</template>
|
|
||||||
<template #distance>
|
|
||||||
<b class="text--primary">{{ stats.mostActiveDrivers[0].distance.toFixed(2) }} km</b>
|
|
||||||
</template>
|
|
||||||
</i18n-t>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</section>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import axios from 'axios';
|
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
import dateMixin from '../../mixins/dateMixin';
|
|
||||||
|
|
||||||
import { URLs } from '../../scripts/utils/apiURLs';
|
|
||||||
import { API } from '../../typings/api';
|
|
||||||
import { Status } from '../../typings/common';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
mixins: [dateMixin],
|
|
||||||
emits: ['toggleStatsOpen'],
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
Status,
|
|
||||||
statsStatus: Status.Data.Loading,
|
|
||||||
intervalId: -1,
|
|
||||||
|
|
||||||
stats: {} as API.DailyStats.Response
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
activated() {
|
|
||||||
this.startFetchingDailyStats();
|
|
||||||
this.$emit('toggleStatsOpen', true);
|
|
||||||
},
|
|
||||||
|
|
||||||
deactivated() {
|
|
||||||
this.stopFetchingDailyStats();
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
topDispatchers() {
|
|
||||||
if (this.stats.mostActiveDispatchers.length == 0) return [];
|
|
||||||
const maxCount = this.stats.mostActiveDispatchers[0].count;
|
|
||||||
|
|
||||||
return this.stats.mostActiveDispatchers.filter((disp) => disp.count === maxCount);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
async fetchDailyTimetableStats() {
|
|
||||||
try {
|
|
||||||
const res: API.DailyStats.Response = 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.stats = res;
|
|
||||||
|
|
||||||
this.statsStatus = Status.Data.Loaded;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Ups! Wystąpił błąd podczas pobierania statystyk rozkładów jazdy...');
|
|
||||||
this.statsStatus = Status.Data.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 {
|
|
||||||
h3 {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="stats_container" v-click-outside="() => (cardVisible = false)">
|
|
||||||
<button class="stats_button" @click="toggleCard">
|
|
||||||
Statystyki dyżurnego {{ store.dispatcherStatsName }}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="stats_card" v-if="store.dispatcherStatsName && cardVisible">
|
|
||||||
<div>
|
|
||||||
<Loading v-if="!store.dispatcherStatsData" />
|
|
||||||
|
|
||||||
<div class="loading" v-else-if="!store.dispatcherStatsData._count._all">
|
|
||||||
Ten dyżurny nie ma jeszcze szczegółowych statystyk!
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else>
|
|
||||||
<h3>STATYSTYKI WYSTAWIONYCH ROZKŁADÓW</h3>
|
|
||||||
|
|
||||||
<div class="info-stats" v-if="store.dispatcherStatsData._count._all">
|
|
||||||
<span class="stat-badge">
|
|
||||||
<span>LICZBA</span>
|
|
||||||
<span>{{ store.dispatcherStatsData._count._all }}</span>
|
|
||||||
</span>
|
|
||||||
<span class="stat-badge">
|
|
||||||
<span>SUMA (KM)</span>
|
|
||||||
<span>{{ store.dispatcherStatsData._sum.routeDistance.toFixed(2) }}km</span>
|
|
||||||
</span>
|
|
||||||
<span class="stat-badge">
|
|
||||||
<span>NAJDŁUŻSZY</span>
|
|
||||||
<span>{{ store.dispatcherStatsData._max.routeDistance.toFixed(2) }}km</span>
|
|
||||||
</span>
|
|
||||||
<span class="stat-badge">
|
|
||||||
<span>ŚREDNIO</span>
|
|
||||||
<span>{{ store.dispatcherStatsData._avg.routeDistance.toFixed(2) }}km</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3>OSTATNIE WYSTAWIONE ROZKŁADY</h3>
|
|
||||||
<div class="last-timetables">
|
|
||||||
<div class="timetable-row" v-for="timetable in timetables" :key="timetable.id">
|
|
||||||
#{{ timetable.timetableId }} |
|
|
||||||
<b>{{ timetable.trainCategoryCode }} {{ timetable.trainNo }}</b> |
|
|
||||||
{{ timetable.driverName }} ({{ timetable.routeDistance }}km)
|
|
||||||
<div>{{ timetable.route.replace('|', ' > ') }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import axios from 'axios';
|
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
import { URLs } from '../../scripts/utils/apiURLs';
|
|
||||||
import { useStore } from '../../store/mainStore';
|
|
||||||
import Loading from '../Global/Loading.vue';
|
|
||||||
import { API } from '../../typings/api';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: { Loading },
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
const store = useStore();
|
|
||||||
|
|
||||||
return {
|
|
||||||
store
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
cardVisible: false,
|
|
||||||
lastDispatcherName: '',
|
|
||||||
timetables: [] as API.TimetableHistory.Response
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
toggleCard() {
|
|
||||||
if (!this.store.dispatcherStatsName) return;
|
|
||||||
|
|
||||||
this.cardVisible = !this.cardVisible;
|
|
||||||
if (this.cardVisible) this.fetchDispatcherStats();
|
|
||||||
},
|
|
||||||
|
|
||||||
async fetchDispatcherStats() {
|
|
||||||
if (this.lastDispatcherName != this.store.dispatcherStatsName) {
|
|
||||||
this.store.dispatcherStatsData = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const statsData: API.DispatcherStats.Response = await (
|
|
||||||
await axios.get(
|
|
||||||
`${URLs.stacjownikAPI}/api/getDispatcherInfo?name=${this.store.dispatcherStatsName}`
|
|
||||||
)
|
|
||||||
).data;
|
|
||||||
|
|
||||||
const timetables: API.TimetableHistory.Response = await (
|
|
||||||
await axios.get(
|
|
||||||
`${URLs.stacjownikAPI}/api/getTimetables?authorName=${this.store.dispatcherStatsName}`
|
|
||||||
)
|
|
||||||
).data;
|
|
||||||
|
|
||||||
this.timetables = timetables;
|
|
||||||
this.store.dispatcherStatsData = statsData;
|
|
||||||
this.lastDispatcherName = this.store.dispatcherStatsName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@import '../../styles/responsive.scss';
|
|
||||||
@import '../../styles/variables.scss';
|
|
||||||
|
|
||||||
.stats_container {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stats_card {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 999;
|
|
||||||
top: 120%;
|
|
||||||
right: 0;
|
|
||||||
width: 500px;
|
|
||||||
max-width: 97vw;
|
|
||||||
min-height: 100px;
|
|
||||||
overflow: auto;
|
|
||||||
|
|
||||||
border-radius: 1em 0 1em 1em;
|
|
||||||
background-color: #222222f1;
|
|
||||||
box-shadow: 0 3px 10px 5px #131313;
|
|
||||||
padding: 1em 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.last-timetables {
|
|
||||||
max-height: 400px;
|
|
||||||
margin: 0.5em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timetable-row {
|
|
||||||
width: 95%;
|
|
||||||
margin: 0.5em auto;
|
|
||||||
padding: 0.5em;
|
|
||||||
|
|
||||||
background-color: #4d4d4d;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2.card-title {
|
|
||||||
font-size: 1.8em;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin-top: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2,
|
|
||||||
h3 {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.last-timetables {
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -0,0 +1,271 @@
|
|||||||
|
<template>
|
||||||
|
<section class="daily-stats">
|
||||||
|
<span :data-active="statsStatus">
|
||||||
|
<span class="stats-list">
|
||||||
|
<h3>
|
||||||
|
{{ $t('journal.daily-stats.title') }}
|
||||||
|
<b class="text--primary">{{ new Date().toLocaleDateString($i18n.locale) }}</b>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<hr class="header-separator" />
|
||||||
|
|
||||||
|
<b v-if="statsStatus == Status.Data.Loading">
|
||||||
|
{{ $t('app.loading') }}
|
||||||
|
</b>
|
||||||
|
|
||||||
|
<b class="text--error" v-else-if="statsStatus == Status.Data.Error">
|
||||||
|
{{ $t('journal.stats-error') }}
|
||||||
|
</b>
|
||||||
|
|
||||||
|
<b v-else-if="topDispatchers.length == 0">
|
||||||
|
{{ $t('journal.daily-stats.info') }}
|
||||||
|
</b>
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
<div v-if="stats.totalTimetables">
|
||||||
|
•
|
||||||
|
<i18n-t keypath="journal.daily-stats.total">
|
||||||
|
<template #count>
|
||||||
|
<b class="text--primary">
|
||||||
|
{{ stats.totalTimetables }}
|
||||||
|
{{ $t('journal.daily-stats.count', stats.totalTimetables) }}
|
||||||
|
</b>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #distance>
|
||||||
|
<b class="text--primary"> {{ stats.distanceSum?.toFixed(2) }} km</b>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="stats.maxTimetable">
|
||||||
|
•
|
||||||
|
<i18n-t keypath="journal.daily-stats.longest">
|
||||||
|
<template #id>
|
||||||
|
<router-link :to="`/journal/timetables?search-train=%23${stats.maxTimetable.id}`">
|
||||||
|
<b>{{ stats.maxTimetable.id }}</b>
|
||||||
|
</router-link>
|
||||||
|
</template>
|
||||||
|
<template #author>
|
||||||
|
<router-link
|
||||||
|
:to="`/journal/timetables?search-dispatcher=${stats.maxTimetable.authorName}`"
|
||||||
|
>
|
||||||
|
<b>{{ stats.maxTimetable.authorName }}</b>
|
||||||
|
</router-link>
|
||||||
|
</template>
|
||||||
|
<template #driver>
|
||||||
|
<b class="text--primary">{{ stats.maxTimetable.driverName }}</b>
|
||||||
|
</template>
|
||||||
|
<template #distance>
|
||||||
|
<b class="text--primary">{{ stats.maxTimetable.routeDistance }} km</b>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="topDispatchers.length == 1">
|
||||||
|
•
|
||||||
|
<i18n-t keypath="journal.daily-stats.most-active-dr">
|
||||||
|
<template #dispatcher>
|
||||||
|
<router-link
|
||||||
|
:to="`/journal/dispatchers?search-dispatcher=${topDispatchers[0].name}`"
|
||||||
|
>
|
||||||
|
<b>{{ topDispatchers[0].name }}</b>
|
||||||
|
</router-link>
|
||||||
|
</template>
|
||||||
|
<template #count>
|
||||||
|
<b class="text--primary">
|
||||||
|
{{ topDispatchers[0].count }}
|
||||||
|
{{ $t('journal.daily-stats.count', topDispatchers[0].count) }}
|
||||||
|
</b>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="topDispatchers.length > 1">
|
||||||
|
•
|
||||||
|
<i18n-t keypath="journal.daily-stats.most-active-dr-many">
|
||||||
|
<template #dispatchers>
|
||||||
|
<span v-for="(disp, i) in topDispatchers" :key="i">
|
||||||
|
<span v-if="i == topDispatchers.length - 1"> {{ $t('general.and') }} </span>
|
||||||
|
|
||||||
|
<router-link :to="`/journal/dispatchers?search-dispatcher=${disp.name}`">
|
||||||
|
<b>{{ disp.name }}</b>
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
<span v-if="i < topDispatchers.length - 2">, </span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #count>
|
||||||
|
<b class="text--primary">
|
||||||
|
{{ topDispatchers[0].count }}
|
||||||
|
{{ $t('journal.daily-stats.count', topDispatchers[0].count) }}
|
||||||
|
</b>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="stats.longestDuties.length > 0">
|
||||||
|
•
|
||||||
|
<i18n-t keypath="journal.daily-stats.longest-duties">
|
||||||
|
<template #dispatcher>
|
||||||
|
<router-link
|
||||||
|
:to="`/journal/dispatchers?search-dispatcher=${stats.longestDuties[0].name}`"
|
||||||
|
>
|
||||||
|
<b>{{ stats.longestDuties[0].name }}</b>
|
||||||
|
</router-link>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #station>{{ stats.longestDuties[0].station }}</template>
|
||||||
|
|
||||||
|
<template #duration>
|
||||||
|
{{ calculateDuration(stats.longestDuties[0].duration) }}
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="stats.mostActiveDrivers.length > 0">
|
||||||
|
•
|
||||||
|
<i18n-t keypath="journal.daily-stats.most-active-driver">
|
||||||
|
<template #driver>
|
||||||
|
<router-link
|
||||||
|
:to="`/journal/timetables?search-driver=${stats.mostActiveDrivers[0].name}`"
|
||||||
|
>
|
||||||
|
<b>{{ stats.mostActiveDrivers[0].name }}</b>
|
||||||
|
</router-link>
|
||||||
|
</template>
|
||||||
|
<template #distance>
|
||||||
|
<b class="text--primary">{{ stats.mostActiveDrivers[0].distance.toFixed(2) }} km</b>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="section-separator" />
|
||||||
|
|
||||||
|
<div class="stats-badges">
|
||||||
|
<span
|
||||||
|
class="stat-badge"
|
||||||
|
v-for="key in [
|
||||||
|
'rippedSwitches',
|
||||||
|
'derailments',
|
||||||
|
'skippedStopSignals',
|
||||||
|
'radioStops',
|
||||||
|
'kills'
|
||||||
|
]"
|
||||||
|
:key="key"
|
||||||
|
>
|
||||||
|
<span>{{ $t(`journal.daily-stats.${key}`) }}</span>
|
||||||
|
<span>{{
|
||||||
|
Object.entries(stats.globalDiff).find(([k, v]) => k == key)?.[1] || '--'
|
||||||
|
}}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import dateMixin from '../../mixins/dateMixin';
|
||||||
|
|
||||||
|
import { API } from '../../typings/api';
|
||||||
|
import { Status } from '../../typings/common';
|
||||||
|
import { useApiStore } from '../../store/apiStore';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'journal-daily-stats',
|
||||||
|
|
||||||
|
mixins: [dateMixin],
|
||||||
|
// emits: ['toggleStatsOpen'],
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
Status,
|
||||||
|
statsStatus: Status.Data.Loading,
|
||||||
|
intervalId: -1,
|
||||||
|
|
||||||
|
stats: {} as API.DailyStats.Response,
|
||||||
|
apiStore: useApiStore()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
activated() {
|
||||||
|
this.startFetchingDailyStats();
|
||||||
|
// this.$emit('toggleStatsOpen', true);
|
||||||
|
},
|
||||||
|
|
||||||
|
deactivated() {
|
||||||
|
this.stopFetchingDailyStats();
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
topDispatchers() {
|
||||||
|
if (this.stats.mostActiveDispatchers.length == 0) return [];
|
||||||
|
const maxCount = this.stats.mostActiveDispatchers[0].count;
|
||||||
|
|
||||||
|
return this.stats.mostActiveDispatchers.filter((disp) => disp.count === maxCount);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
async fetchDailyTimetableStats() {
|
||||||
|
try {
|
||||||
|
const res: API.DailyStats.Response = await (
|
||||||
|
await this.apiStore.client!.get('api/getDailyStats')
|
||||||
|
).data;
|
||||||
|
|
||||||
|
this.stats = res;
|
||||||
|
|
||||||
|
this.statsStatus = Status.Data.Loaded;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ups! Wystąpił błąd podczas pobierania statystyk rozkładów jazdy...');
|
||||||
|
this.statsStatus = Status.Data.Error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
startFetchingDailyStats() {
|
||||||
|
this.fetchDailyTimetableStats();
|
||||||
|
|
||||||
|
if (this.intervalId != -1) return;
|
||||||
|
|
||||||
|
this.intervalId = window.setInterval(this.fetchDailyTimetableStats, 60000);
|
||||||
|
},
|
||||||
|
|
||||||
|
stopFetchingDailyStats() {
|
||||||
|
clearInterval(this.intervalId);
|
||||||
|
this.intervalId = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '../../styles/responsive.scss';
|
||||||
|
@import '../../styles/JournalStats.scss';
|
||||||
|
@import '../../styles/badge.scss';
|
||||||
|
|
||||||
|
.daily-stats {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.daily-stats > span[data-active='0'] {
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-list a {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-badges {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include smallScreen {
|
||||||
|
h3 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
<template>
|
||||||
|
<div class="journal-stats dispatcher" v-if="dispatcherName && stats">
|
||||||
|
<span class="loading" v-if="!stats.issuedTimetables && !stats.services">
|
||||||
|
{{ $t('journal.dispatcher-stats.empty') }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span v-else>
|
||||||
|
<h3>
|
||||||
|
<i18n-t keypath="journal.dispatcher-stats.title">
|
||||||
|
<template #name>
|
||||||
|
<span class="text--primary">{{ dispatcherName.toUpperCase() }}</span>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<hr class="header-separator" />
|
||||||
|
|
||||||
|
<div class="info-stats">
|
||||||
|
<span class="stat-badge" v-if="stats.services">
|
||||||
|
<span>{{ $t('journal.dispatcher-stats.services-count') }}</span>
|
||||||
|
<span>{{ stats.services.count }}</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="stat-badge" v-if="stats.services">
|
||||||
|
<span>{{ $t('journal.dispatcher-stats.service-max') }}</span>
|
||||||
|
<span>{{ calculateDuration(stats.services.durationMax) }}</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="stat-badge" v-if="stats.services">
|
||||||
|
<span>{{ $t('journal.dispatcher-stats.service-avg') }}</span>
|
||||||
|
<span>{{ calculateDuration(stats.services.durationAvg) }}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="section-separator" />
|
||||||
|
|
||||||
|
<div class="info-stats">
|
||||||
|
<span class="stat-badge" v-if="stats.issuedTimetables">
|
||||||
|
<span>{{ $t('journal.dispatcher-stats.timetables-count') }}</span>
|
||||||
|
<span>{{ stats.issuedTimetables.count }}</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="stat-badge" v-if="stats.issuedTimetables">
|
||||||
|
<span>{{ $t('journal.dispatcher-stats.timetables-sum') }}</span>
|
||||||
|
<span>{{ stats.issuedTimetables.distanceSum.toFixed(2) }}km</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="stat-badge" v-if="stats.issuedTimetables">
|
||||||
|
<span>{{ $t('journal.dispatcher-stats.timetables-max') }}</span>
|
||||||
|
<span>{{ stats.issuedTimetables.distanceMax.toFixed(2) }}km</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="stat-badge" v-if="stats.issuedTimetables">
|
||||||
|
<span>{{ $t('journal.dispatcher-stats.timetables-avg') }}</span>
|
||||||
|
<span>{{ stats.issuedTimetables.distanceAvg.toFixed(2) }}km</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import dateMixin from '../../../mixins/dateMixin';
|
||||||
|
import { useMainStore } from '../../../store/mainStore';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'journal-dispatcher-stats',
|
||||||
|
|
||||||
|
mixins: [dateMixin],
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
const store = useMainStore();
|
||||||
|
|
||||||
|
return {
|
||||||
|
stats: store.dispatcherStatsData,
|
||||||
|
dispatcherName: store.dispatcherStatsName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '../../../styles/JournalStats.scss';
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,257 @@
|
|||||||
|
<template>
|
||||||
|
<transition name="status-anim" mode="out-in">
|
||||||
|
<div :key="dataStatus">
|
||||||
|
<div class="journal_warning" v-if="store.isOffline">
|
||||||
|
{{ $t('app.offline') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Loading v-else-if="dataStatus == Status.Data.Loading" />
|
||||||
|
|
||||||
|
<div v-else-if="dataStatus == Status.Data.Error" class="journal_warning error">
|
||||||
|
{{ $t('app.error') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="journal_warning" v-else-if="dispatcherHistory.length == 0">
|
||||||
|
{{ $t('app.no-result') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
<table class="dispatchers-table">
|
||||||
|
<thead>
|
||||||
|
<th>{{ $t('journal.history-name') }}</th>
|
||||||
|
<th>{{ $t('journal.history-hash') }}</th>
|
||||||
|
<th>{{ $t('journal.history-dispatcher') }}</th>
|
||||||
|
<th>{{ $t('journal.history-level') }}</th>
|
||||||
|
<th>{{ $t('journal.history-rate') }}</th>
|
||||||
|
<th>{{ $t('journal.history-region') }}</th>
|
||||||
|
<th>{{ $t('journal.history-date') }}</th>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
<transition-group name="list-anim">
|
||||||
|
<tr v-for="historyItem in dispatcherHistory" :key="historyItem.id">
|
||||||
|
<td>
|
||||||
|
<router-link
|
||||||
|
:to="`/journal/dispatchers?search-station=${historyItem.stationName}`"
|
||||||
|
>
|
||||||
|
<b>{{ historyItem.stationName }}</b>
|
||||||
|
</router-link>
|
||||||
|
</td>
|
||||||
|
<td>#{{ historyItem.stationHash }}</td>
|
||||||
|
<td>
|
||||||
|
<router-link
|
||||||
|
:to="`/journal/dispatchers?search-dispatcher=${historyItem.dispatcherName}`"
|
||||||
|
>
|
||||||
|
<b
|
||||||
|
v-if="apiStore.donatorsData.includes(historyItem.dispatcherName)"
|
||||||
|
class="text--donator"
|
||||||
|
:title="$t('donations.dispatcher-message')"
|
||||||
|
>
|
||||||
|
{{ historyItem.dispatcherName }}
|
||||||
|
</b>
|
||||||
|
|
||||||
|
<b v-else>
|
||||||
|
{{ historyItem.dispatcherName }}
|
||||||
|
</b>
|
||||||
|
</router-link>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<b
|
||||||
|
v-if="historyItem.dispatcherLevel !== null"
|
||||||
|
class="level-badge dispatcher"
|
||||||
|
:style="
|
||||||
|
calculateExpStyle(
|
||||||
|
historyItem.dispatcherLevel,
|
||||||
|
historyItem.dispatcherIsSupporter
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ historyItem.dispatcherLevel >= 2 ? historyItem.dispatcherLevel : 'L' }}
|
||||||
|
</b>
|
||||||
|
</td>
|
||||||
|
<td class="text--primary">
|
||||||
|
<b>{{ historyItem.dispatcherRate }}</b>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<b class="region-badge" :aria-describedby="historyItem.region">{{
|
||||||
|
regions.find((r) => r.id == historyItem.region)?.value || '???'
|
||||||
|
}}</b>
|
||||||
|
</td>
|
||||||
|
<td style="min-width: 200px" class="time">
|
||||||
|
<span v-if="historyItem.timestampTo" class="text--offline">
|
||||||
|
<b>{{ $d(historyItem.timestampFrom) }}</b>
|
||||||
|
{{ timestampToString(historyItem.timestampFrom) }}
|
||||||
|
- {{ timestampToString(historyItem.timestampTo) }} ({{
|
||||||
|
calculateDuration(historyItem.currentDuration)
|
||||||
|
}})
|
||||||
|
</span>
|
||||||
|
<span class="dispatcher-online" v-else>
|
||||||
|
<b class="text--online">
|
||||||
|
<router-link :to="`/scenery?station=${historyItem.stationName}`">{{
|
||||||
|
$t('journal.online-since')
|
||||||
|
}}</router-link>
|
||||||
|
{{ timestampToString(historyItem.timestampFrom) }}
|
||||||
|
</b>
|
||||||
|
({{ calculateDuration(historyItem.currentDuration) }})
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</transition-group>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<AddDataButton
|
||||||
|
:list="dispatcherHistory"
|
||||||
|
:scrollDataLoaded="scrollDataLoaded"
|
||||||
|
:scrollNoMoreData="scrollNoMoreData"
|
||||||
|
@addHistoryData="addHistoryData"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="journal_warning" v-if="scrollNoMoreData">
|
||||||
|
{{ $t('journal.no-further-data') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="journal_warning" v-else-if="!scrollDataLoaded">
|
||||||
|
{{ $t('journal.loading-further-data') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, PropType } from 'vue';
|
||||||
|
import { regions } from '../../../data/options.json';
|
||||||
|
import { useMainStore } from '../../../store/mainStore';
|
||||||
|
import { API } from '../../../typings/api';
|
||||||
|
import { Status } from '../../../typings/common';
|
||||||
|
import Loading from '../../Global/Loading.vue';
|
||||||
|
import AddDataButton from '../../Global/AddDataButton.vue';
|
||||||
|
import dateMixin from '../../../mixins/dateMixin';
|
||||||
|
import styleMixin from '../../../mixins/styleMixin';
|
||||||
|
import { useApiStore } from '../../../store/apiStore';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: { Loading, AddDataButton },
|
||||||
|
|
||||||
|
mixins: [dateMixin, styleMixin],
|
||||||
|
|
||||||
|
props: {
|
||||||
|
dispatcherHistory: {
|
||||||
|
type: Array as PropType<API.DispatcherHistory.Response>,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
scrollNoMoreData: {
|
||||||
|
type: Boolean
|
||||||
|
},
|
||||||
|
scrollDataLoaded: {
|
||||||
|
type: Boolean
|
||||||
|
},
|
||||||
|
addHistoryData: {
|
||||||
|
type: Function as PropType<() => void>
|
||||||
|
},
|
||||||
|
dataStatus: {
|
||||||
|
type: Number as PropType<Status.Data>
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
Status,
|
||||||
|
store: useMainStore(),
|
||||||
|
apiStore: useApiStore(),
|
||||||
|
regions
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
computedDispatcherHistory() {
|
||||||
|
return this.dispatcherHistory.reduce(
|
||||||
|
(acc, historyItem, i) => {
|
||||||
|
if (this.isAnotherDay(i - 1, i))
|
||||||
|
acc.push(new Date(historyItem.timestampFrom).toLocaleDateString('pl-PL'));
|
||||||
|
acc.push(historyItem);
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
[] as (API.DispatcherHistory.Data | string)[]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
navigateToScenery(name: string, isOnline: boolean) {
|
||||||
|
if (!isOnline) return;
|
||||||
|
|
||||||
|
this.$router.push(`/scenery?station=${name.trim().replace(/ /g, '_')}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
isAnotherDay(prevIndex: number, currIndex: number) {
|
||||||
|
if (currIndex == 0) return true;
|
||||||
|
|
||||||
|
return (
|
||||||
|
new Date(this.dispatcherHistory[prevIndex].timestampFrom).getDate() !=
|
||||||
|
new Date(this.dispatcherHistory[currIndex].timestampFrom).getDate()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '../../../styles/animations.scss';
|
||||||
|
@import '../../../styles/responsive.scss';
|
||||||
|
@import '../../../styles/badge.scss';
|
||||||
|
@import '../../../styles/variables.scss';
|
||||||
|
@import '../../../styles/JournalSection.scss';
|
||||||
|
|
||||||
|
table.dispatchers-table {
|
||||||
|
--_bg-table: #111;
|
||||||
|
--_bg-head: #101010;
|
||||||
|
--_bg-row: #2f2f2f;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
margin-bottom: 1em;
|
||||||
|
|
||||||
|
thead {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
background-color: var(--_bg-head);
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr {
|
||||||
|
background-color: var(--_bg-row);
|
||||||
|
border-bottom: 2px solid black;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: 0.75em;
|
||||||
|
|
||||||
|
.level-badge {
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
&--online {
|
||||||
|
color: springgreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--offline {
|
||||||
|
color: #ddd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,249 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<transition name="status-anim" mode="out-in">
|
|
||||||
<div :key="dataStatus">
|
|
||||||
<div class="journal_warning" v-if="store.isOffline">
|
|
||||||
{{ $t('app.offline') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Loading v-else-if="dataStatus == Status.Data.Loading" />
|
|
||||||
|
|
||||||
<div v-else-if="dataStatus == Status.Data.Error" class="journal_warning error">
|
|
||||||
{{ $t('app.error') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="journal_warning" v-else-if="dispatcherHistory.length == 0">
|
|
||||||
{{ $t('app.no-result') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else>
|
|
||||||
<table class="scenery-history-table">
|
|
||||||
<thead>
|
|
||||||
<th>{{ $t('journal.history-name') }}</th>
|
|
||||||
<th>{{ $t('journal.history-hash') }}</th>
|
|
||||||
<th>{{ $t('journal.history-dispatcher') }}</th>
|
|
||||||
<th>{{ $t('journal.history-level') }}</th>
|
|
||||||
<th>{{ $t('journal.history-rate') }}</th>
|
|
||||||
<th>{{ $t('journal.history-region') }}</th>
|
|
||||||
<th>{{ $t('journal.history-date') }}</th>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
<transition-group name="list-anim">
|
|
||||||
<tr v-for="historyItem in dispatcherHistory" :key="historyItem.id">
|
|
||||||
<td>
|
|
||||||
<router-link
|
|
||||||
:to="`/journal/dispatchers?sceneryName=${historyItem.stationName}`"
|
|
||||||
>
|
|
||||||
<b>{{ historyItem.stationName }}</b>
|
|
||||||
</router-link>
|
|
||||||
</td>
|
|
||||||
<td>#{{ historyItem.stationHash }}</td>
|
|
||||||
<td>
|
|
||||||
<router-link
|
|
||||||
:to="`/journal/dispatchers?dispatcherName=${historyItem.dispatcherName}`"
|
|
||||||
>
|
|
||||||
<b>{{ historyItem.dispatcherName }}</b>
|
|
||||||
</router-link>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<b
|
|
||||||
v-if="historyItem.dispatcherLevel !== null"
|
|
||||||
class="level-badge dispatcher"
|
|
||||||
:style="
|
|
||||||
calculateExpStyle(
|
|
||||||
historyItem.dispatcherLevel,
|
|
||||||
historyItem.dispatcherIsSupporter
|
|
||||||
)
|
|
||||||
"
|
|
||||||
>
|
|
||||||
{{ historyItem.dispatcherLevel >= 2 ? historyItem.dispatcherLevel : 'L' }}
|
|
||||||
</b>
|
|
||||||
</td>
|
|
||||||
<td class="text--primary">
|
|
||||||
<b>{{ historyItem.dispatcherRate }}</b>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<b class="region-badge" :aria-describedby="historyItem.region">{{
|
|
||||||
regions.find((r) => r.id == historyItem.region)?.value || '???'
|
|
||||||
}}</b>
|
|
||||||
</td>
|
|
||||||
<td style="min-width: 200px" class="time">
|
|
||||||
<span v-if="historyItem.timestampTo" class="text--offline">
|
|
||||||
<b>{{ $d(historyItem.timestampFrom) }}</b>
|
|
||||||
{{ timestampToString(historyItem.timestampFrom) }}
|
|
||||||
- {{ timestampToString(historyItem.timestampTo) }} ({{
|
|
||||||
calculateDuration(historyItem.currentDuration)
|
|
||||||
}})
|
|
||||||
</span>
|
|
||||||
<span class="dispatcher-online" v-else>
|
|
||||||
<b class="text--online">
|
|
||||||
<router-link :to="`/scenery?station=${historyItem.stationName}`">{{
|
|
||||||
$t('journal.online-since')
|
|
||||||
}}</router-link>
|
|
||||||
{{ timestampToString(historyItem.timestampFrom) }}
|
|
||||||
</b>
|
|
||||||
({{ calculateDuration(historyItem.currentDuration) }})
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</transition-group>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<AddDataButton
|
|
||||||
:list="dispatcherHistory"
|
|
||||||
:scrollDataLoaded="scrollDataLoaded"
|
|
||||||
:scrollNoMoreData="scrollNoMoreData"
|
|
||||||
@addHistoryData="addHistoryData"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
|
|
||||||
<div class="journal_warning" v-if="scrollNoMoreData">
|
|
||||||
{{ $t('journal.no-further-data') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="journal_warning" v-else-if="!scrollDataLoaded">
|
|
||||||
{{ $t('journal.loading-further-data') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, PropType } from 'vue';
|
|
||||||
import dateMixin from '../../mixins/dateMixin';
|
|
||||||
import styleMixin from '../../mixins/styleMixin';
|
|
||||||
import { useStore } from '../../store/mainStore';
|
|
||||||
import Loading from '../Global/Loading.vue';
|
|
||||||
import { regions } from '../../data/options.json';
|
|
||||||
import AddDataButton from '../Global/AddDataButton.vue';
|
|
||||||
import { API } from '../../typings/api';
|
|
||||||
import { Status } from '../../typings/common';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: { Loading, AddDataButton },
|
|
||||||
|
|
||||||
mixins: [dateMixin, styleMixin],
|
|
||||||
|
|
||||||
props: {
|
|
||||||
dispatcherHistory: {
|
|
||||||
type: Array as PropType<API.DispatcherHistory.Response>,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
scrollNoMoreData: {
|
|
||||||
type: Boolean
|
|
||||||
},
|
|
||||||
scrollDataLoaded: {
|
|
||||||
type: Boolean
|
|
||||||
},
|
|
||||||
addHistoryData: {
|
|
||||||
type: Function as PropType<() => void>
|
|
||||||
},
|
|
||||||
dataStatus: {
|
|
||||||
type: Number as PropType<Status.Data>
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
Status,
|
|
||||||
store: 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);
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
[] as (API.DispatcherHistory.Data | string)[]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
navigateToScenery(name: string, isOnline: boolean) {
|
|
||||||
if (!isOnline) return;
|
|
||||||
|
|
||||||
this.$router.push(`/scenery?station=${name.trim().replace(/ /g, '_')}`);
|
|
||||||
},
|
|
||||||
|
|
||||||
isAnotherDay(prevIndex: number, currIndex: number) {
|
|
||||||
if (currIndex == 0) return true;
|
|
||||||
|
|
||||||
return (
|
|
||||||
new Date(this.dispatcherHistory[prevIndex].timestampFrom).getDate() !=
|
|
||||||
new Date(this.dispatcherHistory[currIndex].timestampFrom).getDate()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@import '../../styles/animations.scss';
|
|
||||||
@import '../../styles/responsive.scss';
|
|
||||||
@import '../../styles/badge.scss';
|
|
||||||
@import '../../styles/variables.scss';
|
|
||||||
@import '../../styles/JournalSection.scss';
|
|
||||||
|
|
||||||
table.scenery-history-table {
|
|
||||||
--_bg-table: #111;
|
|
||||||
--_bg-head: #101010;
|
|
||||||
--_bg-row: #2f2f2f;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
position: relative;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
margin-bottom: 1em;
|
|
||||||
|
|
||||||
thead {
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
background-color: var(--_bg-head);
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
padding: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
tr {
|
|
||||||
background-color: var(--_bg-row);
|
|
||||||
border-bottom: 2px solid black;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
padding: 0.75em;
|
|
||||||
|
|
||||||
.level-badge {
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.text {
|
|
||||||
&--online {
|
|
||||||
color: springgreen;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--offline {
|
|
||||||
color: #ddd;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="filters-options" @keydown.esc="showOptions = false">
|
<div class="filters-options dropdown" @keydown.esc="showOptions = false">
|
||||||
<div class="bg" v-if="showOptions" @click="showOptions = false"></div>
|
<div class="dropdown_background" v-if="showOptions" @click="showOptions = false"></div>
|
||||||
|
|
||||||
<div class="actions-bar">
|
<div class="actions-bar">
|
||||||
<button
|
<button
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
ref="button"
|
ref="button"
|
||||||
>
|
>
|
||||||
<img src="/images/icon-filter2.svg" alt="Open filters" />
|
<img src="/images/icon-filter2.svg" alt="Open filters" />
|
||||||
{{ $t('options.filters') }} [F]
|
[F] {{ $t('options.filters') }}
|
||||||
<span class="active-indicator" v-if="currentOptionsActive"></span>
|
<span class="active-indicator" v-if="currentOptionsActive"></span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -27,13 +27,13 @@
|
|||||||
<option v-for="(sugg, i) in dispatcherSuggestions" :key="i" :value="sugg"></option>
|
<option v-for="(sugg, i) in dispatcherSuggestions" :key="i" :value="sugg"></option>
|
||||||
</datalist>
|
</datalist>
|
||||||
|
|
||||||
<transition name="options-anim">
|
<transition name="dropdown-anim">
|
||||||
<div class="options_wrapper" v-if="showOptions">
|
<div class="dropdown_wrapper" v-if="showOptions">
|
||||||
<div class="options_content">
|
<div class="options_content">
|
||||||
<h1 class="option-title">{{ $t('options.search-title') }}</h1>
|
<h1 class="option-title">{{ $t('options.search-title') }}</h1>
|
||||||
<div class="search_content">
|
<div class="search_content">
|
||||||
<div class="search" v-for="(_, propName) in searchersValues" :key="propName">
|
<div class="search" v-for="(_, propName) in searchersValues" :key="propName">
|
||||||
<label v-if="propName == 'search-date'" for="date">{{
|
<label v-if="propName == 'search-date'" for="search-date">{{
|
||||||
$t(`options.search-${optionsType}-date`)
|
$t(`options.search-${optionsType}-date`)
|
||||||
}}</label>
|
}}</label>
|
||||||
|
|
||||||
@@ -41,12 +41,13 @@
|
|||||||
<input
|
<input
|
||||||
class="search-input"
|
class="search-input"
|
||||||
v-model="searchersValues[propName]"
|
v-model="searchersValues[propName]"
|
||||||
@keydown.enter="onSearchConfirm"
|
@keydown.enter="searchConfirm"
|
||||||
@focus="preventKeyDown = true"
|
@focus="preventKeyDown = true"
|
||||||
@blur="preventKeyDown = false"
|
@blur="preventKeyDown = false"
|
||||||
:placeholder="$t(`options.${propName}`)"
|
:placeholder="$t(`options.${propName}`)"
|
||||||
:type="propName == 'search-date' ? 'date' : 'text'"
|
:type="propName == 'search-date' ? 'date' : 'text'"
|
||||||
:min="propName == 'search-date' ? '2022-02-01' : undefined"
|
:min="propName == 'search-date' ? '2022-02-01' : undefined"
|
||||||
|
:id="`${propName}`"
|
||||||
:list="propName.toString()"
|
:list="propName.toString()"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -110,14 +111,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import axios from 'axios';
|
|
||||||
import { defineComponent, inject, PropType } from 'vue';
|
import { defineComponent, inject, PropType } from 'vue';
|
||||||
import keyMixin from '../../mixins/keyMixin';
|
import keyMixin from '../../mixins/keyMixin';
|
||||||
import { URLs } from '../../scripts/utils/apiURLs';
|
import { useMainStore } from '../../store/mainStore';
|
||||||
import { useStore } from '../../store/mainStore';
|
|
||||||
import { Journal } from './typings';
|
import { Journal } from './typings';
|
||||||
import { API } from '../../typings/api';
|
|
||||||
import { Status } from '../../typings/common';
|
import { Status } from '../../typings/common';
|
||||||
|
import { useApiStore } from '../../store/apiStore';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
emits: ['onSearchConfirm', 'onOptionsReset', 'onRefreshData'],
|
emits: ['onSearchConfirm', 'onOptionsReset', 'onRefreshData'],
|
||||||
@@ -158,7 +157,8 @@ export default defineComponent({
|
|||||||
dispatcherSuggestions: [] as string[],
|
dispatcherSuggestions: [] as string[],
|
||||||
|
|
||||||
searchTimeout: 0,
|
searchTimeout: 0,
|
||||||
store: useStore(),
|
store: useMainStore(),
|
||||||
|
apiStore: useApiStore(),
|
||||||
|
|
||||||
JournalFilterSection: Journal.FilterSection
|
JournalFilterSection: Journal.FilterSection
|
||||||
};
|
};
|
||||||
@@ -182,12 +182,6 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
async 'store.driverStatsName'() {
|
|
||||||
await this.fetchDriverStats();
|
|
||||||
|
|
||||||
// if (value) this.store.currentStatsTab = 'driver';
|
|
||||||
},
|
|
||||||
|
|
||||||
async 'searchersValues.search-driver'(value: string | undefined) {
|
async 'searchersValues.search-driver'(value: string | undefined) {
|
||||||
clearTimeout(this.searchTimeout);
|
clearTimeout(this.searchTimeout);
|
||||||
|
|
||||||
@@ -206,29 +200,34 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
async fetchDriverStats() {
|
// filters & sorters from URL params
|
||||||
this.store.driverStatsData = undefined;
|
handleRouteParams() {
|
||||||
|
this.$router.push({
|
||||||
|
query: {
|
||||||
|
...this.$route.query,
|
||||||
|
'sorter-active':
|
||||||
|
this.sorterOptionIds.indexOf(`${this.sorterActive.id}`) != 0
|
||||||
|
? this.sorterActive.id
|
||||||
|
: undefined,
|
||||||
|
...Object.keys(this.searchersValues).reduce(
|
||||||
|
(acc, k) => {
|
||||||
|
const searchVal = this.searchersValues[k as Journal.TimetableSearchKey];
|
||||||
|
|
||||||
if (!this.store.driverStatsName) {
|
acc[k] = searchVal || undefined;
|
||||||
this.store.driverStatsStatus = Status.Data.Initialized;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
return acc;
|
||||||
this.store.driverStatsStatus = Status.Data.Loading;
|
},
|
||||||
|
{} as { [k: string]: string | undefined }
|
||||||
const statsData: API.DriverStats.Response = await (
|
),
|
||||||
await axios.get(
|
...this.filterList?.reduce(
|
||||||
`${URLs.stacjownikAPI}/api/getDriverInfo?name=${this.store.driverStatsName}`
|
(acc, f) => {
|
||||||
|
if (f.isActive) acc[f.filterSection] = f.default ? undefined : f.id;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as { [k: string]: string | undefined }
|
||||||
)
|
)
|
||||||
).data;
|
|
||||||
|
|
||||||
this.store.driverStatsData = statsData;
|
|
||||||
this.store.driverStatsStatus = Status.Data.Loaded;
|
|
||||||
} catch (error) {
|
|
||||||
this.store.driverStatsStatus = Status.Data.Error;
|
|
||||||
console.error('Ups! Wystąpił błąd przy próbie pobrania statystyk maszynisty! :/');
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
refreshData() {
|
refreshData() {
|
||||||
@@ -240,17 +239,17 @@ export default defineComponent({
|
|||||||
|
|
||||||
window.clearTimeout(this.searchTimeout);
|
window.clearTimeout(this.searchTimeout);
|
||||||
|
|
||||||
this.searchTimeout = setTimeout(async () => {
|
this.searchTimeout = window.setTimeout(async () => {
|
||||||
try {
|
try {
|
||||||
const suggestions: string[] = await (
|
const suggestions: string[] = await (
|
||||||
await axios.get(`${URLs.stacjownikAPI}/api/get${type}Suggestions?name=${value}`)
|
await this.apiStore.client!.get(`api/get${type}Suggestions?name=${value}`)
|
||||||
).data;
|
).data;
|
||||||
|
|
||||||
this[`${type}Suggestions`] = suggestions;
|
this[`${type}Suggestions`] = suggestions;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this[`${type}Suggestions`] = [];
|
this[`${type}Suggestions`] = [];
|
||||||
}
|
}
|
||||||
}, 450);
|
}, 250);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Override keyMixin function
|
// Override keyMixin function
|
||||||
@@ -265,7 +264,7 @@ export default defineComponent({
|
|||||||
onSorterChange(item: { id: string | number; value: string }) {
|
onSorterChange(item: { id: string | number; value: string }) {
|
||||||
this.sorterActive.id = item.id;
|
this.sorterActive.id = item.id;
|
||||||
this.sorterActive.dir = -1;
|
this.sorterActive.dir = -1;
|
||||||
this.$emit('onSearchConfirm');
|
this.searchConfirm();
|
||||||
},
|
},
|
||||||
|
|
||||||
onFilterChange(filter: Journal.TimetableFilter) {
|
onFilterChange(filter: Journal.TimetableFilter) {
|
||||||
@@ -275,30 +274,33 @@ export default defineComponent({
|
|||||||
.forEach((f) => (f.isActive = false));
|
.forEach((f) => (f.isActive = false));
|
||||||
filter.isActive = true;
|
filter.isActive = true;
|
||||||
|
|
||||||
this.$emit('onSearchConfirm');
|
this.searchConfirm();
|
||||||
},
|
},
|
||||||
|
|
||||||
onInputClear(id: any) {
|
onInputClear(id: any) {
|
||||||
this.searchersValues[id] = '';
|
this.searchersValues[id] = '';
|
||||||
this.$emit('onSearchConfirm');
|
this.searchConfirm();
|
||||||
},
|
},
|
||||||
|
|
||||||
onSearchConfirm() {
|
searchConfirm() {
|
||||||
this.$emit('onSearchConfirm');
|
this.$emit('onSearchConfirm');
|
||||||
|
this.handleRouteParams();
|
||||||
},
|
},
|
||||||
|
|
||||||
onSearchButtonConfirm() {
|
onSearchButtonConfirm() {
|
||||||
this.showOptions = false;
|
this.showOptions = false;
|
||||||
this.$emit('onSearchConfirm');
|
this.searchConfirm();
|
||||||
},
|
},
|
||||||
|
|
||||||
onResetButtonClick() {
|
onResetButtonClick() {
|
||||||
this.$emit('onOptionsReset');
|
this.$emit('onOptionsReset');
|
||||||
|
this.handleRouteParams();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../styles/filters_options.scss';
|
@import '../../styles/dropdown.scss';
|
||||||
|
@import '../../styles/dropdown_filters.scss';
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,122 +1,85 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="journal-stats" v-if="!store.isOffline">
|
<div
|
||||||
<div class="tabs">
|
class="journal-stats dropdown"
|
||||||
<button
|
v-if="!mainStore.isOffline"
|
||||||
v-for="tab in data.tabs"
|
@keydown.esc="currentStatsTab = null"
|
||||||
:key="tab.name"
|
|
||||||
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) }}
|
<div
|
||||||
|
class="dropdown_background"
|
||||||
|
v-if="currentStatsTab !== null"
|
||||||
|
@click="currentStatsTab = null"
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<div class="actions-bar">
|
||||||
|
<button
|
||||||
|
v-for="button in statsButtons"
|
||||||
|
:key="button.tab"
|
||||||
|
class="btn--filled btn--image"
|
||||||
|
:data-selected="button.tab == currentStatsTab"
|
||||||
|
:data-disabled="button.disabled"
|
||||||
|
:disabled="button.disabled"
|
||||||
|
@click="onTabButtonClick(button.tab)"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
v-if="button.iconName"
|
||||||
|
:src="`/images/icon-${button.iconName}.svg`"
|
||||||
|
:alt="button.iconName"
|
||||||
|
/>
|
||||||
|
{{ $t(button.localeKey) }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stats-tab" v-show="areStatsOpen">
|
<transition name="dropdown-anim">
|
||||||
|
<div class="dropdown_wrapper" v-if="currentStatsTab !== null">
|
||||||
<keep-alive>
|
<keep-alive>
|
||||||
<JournalDailyStats
|
<component :is="currentStatsTab" :key="currentStatsTab"></component>
|
||||||
v-if="store.currentStatsTab == 'daily'"
|
|
||||||
@toggleStatsOpen="toggleStatsOpen"
|
|
||||||
/>
|
|
||||||
<JournalDriverStats v-else-if="store.currentStatsTab == 'driver'" />
|
|
||||||
</keep-alive>
|
</keep-alive>
|
||||||
</div>
|
</div>
|
||||||
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script lang="ts">
|
||||||
import { computed, onMounted, reactive, Ref, ref, watch } from 'vue';
|
import { defineComponent, PropType } from 'vue';
|
||||||
import { useStore } from '../../store/mainStore';
|
import { useMainStore } from '../../store/mainStore';
|
||||||
import JournalDailyStats from './DailyStats.vue';
|
|
||||||
import JournalDriverStats from './JournalDriverStats.vue';
|
|
||||||
import StorageManager from '../../managers/storageManager';
|
import StorageManager from '../../managers/storageManager';
|
||||||
|
import { Journal } from './typings';
|
||||||
|
import JournalDailyStats from './JournalDailyStats.vue';
|
||||||
|
import JournalDispatcherStats from '../JournalView/JournalDispatchers/JournalDispatcherStats.vue';
|
||||||
|
import JournalDriverStats from '../JournalView/JournalTimetables/JournalDriverStats.vue';
|
||||||
|
|
||||||
// Types
|
export default defineComponent({
|
||||||
type TStatTab = 'daily' | 'driver';
|
components: { JournalDailyStats, JournalDriverStats, JournalDispatcherStats },
|
||||||
|
props: {
|
||||||
// Variables
|
statsButtons: {
|
||||||
const store = useStore();
|
type: Array as PropType<Journal.StatsButton[]>,
|
||||||
|
required: true
|
||||||
const lastDailyStatsOpen = ref(false);
|
}
|
||||||
const areStatsOpen = ref(false);
|
|
||||||
const lastClickedTab: Ref<'daily' | 'driver' | null> = ref(null);
|
|
||||||
|
|
||||||
let data = reactive({
|
|
||||||
tabs: [
|
|
||||||
{
|
|
||||||
name: 'daily',
|
|
||||||
titlePath: 'journal.daily-stats-title'
|
|
||||||
},
|
},
|
||||||
{
|
data() {
|
||||||
name: 'driver',
|
return {
|
||||||
titlePath: 'journal.driver-stats-title'
|
Journal,
|
||||||
// inactive: true,
|
mainStore: useMainStore(),
|
||||||
|
currentStatsTab: null as Journal.StatsTab | null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
onTabButtonClick(tab: Journal.StatsTab) {
|
||||||
|
this.currentStatsTab = tab == this.currentStatsTab ? null : tab;
|
||||||
|
|
||||||
|
StorageManager.setStringValue('journalStatsTab', this.currentStatsTab ?? '');
|
||||||
}
|
}
|
||||||
] as { name: TStatTab; titlePath: string; inactive?: boolean }[]
|
|
||||||
});
|
|
||||||
|
|
||||||
// Methods
|
|
||||||
function onTabButtonClick(tab: TStatTab) {
|
|
||||||
if (lastClickedTab.value == tab || !lastClickedTab.value || !areStatsOpen.value)
|
|
||||||
areStatsOpen.value = !areStatsOpen.value;
|
|
||||||
|
|
||||||
if (tab == 'daily') {
|
|
||||||
StorageManager.setBooleanValue('dailyStatsOpen', areStatsOpen.value);
|
|
||||||
lastDailyStatsOpen.value = areStatsOpen.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
store.currentStatsTab = tab;
|
|
||||||
lastClickedTab.value = tab;
|
|
||||||
|
|
||||||
if (areStatsOpen.value == false) store.currentStatsTab = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleStatsOpen(open: boolean) {
|
|
||||||
areStatsOpen.value = open;
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
computed(() => store.driverStatsData),
|
|
||||||
(statsData) => {
|
|
||||||
store.currentStatsTab = statsData ? 'driver' : lastClickedTab.value;
|
|
||||||
areStatsOpen.value = statsData ? true : lastClickedTab.value !== null;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (StorageManager.getBooleanValue('dailyStatsOpen')) {
|
|
||||||
areStatsOpen.value = true;
|
|
||||||
store.currentStatsTab = 'daily';
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../styles/JournalStats.scss';
|
@import '../../styles/dropdown.scss';
|
||||||
|
@import '../../styles/dropdown_filters.scss';
|
||||||
@import '../../styles/variables.scss';
|
@import '../../styles/variables.scss';
|
||||||
|
|
||||||
.tabs {
|
.dropdown_wrapper {
|
||||||
position: relative;
|
max-width: 100%;
|
||||||
|
|
||||||
display: flex;
|
|
||||||
gap: 0.5em;
|
|
||||||
|
|
||||||
margin-bottom: 0.5em;
|
|
||||||
|
|
||||||
button {
|
|
||||||
font-weight: bold;
|
|
||||||
padding: 0.5em 0.75em;
|
|
||||||
|
|
||||||
&[data-inactive='true'] {
|
|
||||||
color: gray;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-selected='true'] {
|
|
||||||
color: $accentCol;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+19
-20
@@ -1,14 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="journal-stats">
|
<div class="journal-stats driver" v-if="store.driverStatsData">
|
||||||
<span v-if="store.driverStatsData">
|
<span>
|
||||||
<h3>
|
<h3>
|
||||||
{{ $t('journal.stats-title') }}
|
<i18n-t keypath="journal.driver-stats.title">
|
||||||
|
<template #name>
|
||||||
<span class="text--primary">{{ store.driverStatsName.toUpperCase() }}</span>
|
<span class="text--primary">{{ store.driverStatsName.toUpperCase() }}</span>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
|
<hr class="header-separator" />
|
||||||
|
|
||||||
<div class="info-stats">
|
<div class="info-stats">
|
||||||
<span class="stat-badge">
|
<span class="stat-badge">
|
||||||
<span>{{ $t('journal.stats-timetables') }}</span>
|
<span>{{ $t('journal.driver-stats.timetables') }}</span>
|
||||||
<span
|
<span
|
||||||
>{{ store.driverStatsData._count.fulfilled }} /
|
>{{ store.driverStatsData._count.fulfilled }} /
|
||||||
{{ store.driverStatsData._count._all }}</span
|
{{ store.driverStatsData._count._all }}</span
|
||||||
@@ -16,17 +21,17 @@
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="stat-badge">
|
<span class="stat-badge">
|
||||||
<span>{{ $t('journal.stats-longest-timetable') }}</span>
|
<span>{{ $t('journal.driver-stats.longest-timetable') }}</span>
|
||||||
<span> {{ store.driverStatsData._max.routeDistance.toFixed(2) }}km </span>
|
<span> {{ store.driverStatsData._max.routeDistance.toFixed(2) }}km </span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="stat-badge">
|
<span class="stat-badge">
|
||||||
<span>{{ $t('journal.stats-avg-timetable') }}</span>
|
<span>{{ $t('journal.driver-stats.avg-timetable') }}</span>
|
||||||
<span> {{ store.driverStatsData._avg.routeDistance.toFixed(2) }}km </span>
|
<span> {{ store.driverStatsData._avg.routeDistance.toFixed(2) }}km </span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="stat-badge">
|
<span class="stat-badge">
|
||||||
<span>{{ $t('journal.stats-distance') }}</span>
|
<span>{{ $t('journal.driver-stats.distance') }}</span>
|
||||||
<span>
|
<span>
|
||||||
{{ store.driverStatsData._sum.currentDistance.toFixed(2) }} /
|
{{ store.driverStatsData._sum.currentDistance.toFixed(2) }} /
|
||||||
{{ store.driverStatsData._sum.routeDistance.toFixed(2) }}km
|
{{ store.driverStatsData._sum.routeDistance.toFixed(2) }}km
|
||||||
@@ -34,7 +39,7 @@
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="stat-badge">
|
<span class="stat-badge">
|
||||||
<span>{{ $t('journal.stats-stations') }}</span>
|
<span>{{ $t('journal.driver-stats.stations') }}</span>
|
||||||
<span>
|
<span>
|
||||||
{{ store.driverStatsData._sum.confirmedStopsCount }} /
|
{{ store.driverStatsData._sum.confirmedStopsCount }} /
|
||||||
{{ store.driverStatsData._sum.allStopsCount }}
|
{{ store.driverStatsData._sum.allStopsCount }}
|
||||||
@@ -42,26 +47,20 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<b v-else-if="store.driverStatsStatus == Status.Data.Loading">{{
|
|
||||||
$t('journal.stats-loading')
|
|
||||||
}}</b>
|
|
||||||
<b v-else-if="store.driverStatsStatus == Status.Data.Error">
|
|
||||||
{{ $t('journal.stats-error ') }}
|
|
||||||
</b>
|
|
||||||
<b v-else>{{ $t('journal.driver-stats-info') }}</b>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { useStore } from '../../store/mainStore';
|
import { useMainStore } from '../../../store/mainStore';
|
||||||
import { Status } from '../../typings/common';
|
import { Status } from '../../../typings/common';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
name: 'journal-driver-stats',
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
store: useStore(),
|
store: useMainStore(),
|
||||||
Status: Status
|
Status: Status
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -69,5 +68,5 @@ export default defineComponent({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../styles/JournalStats.scss';
|
@import '../../../styles/JournalStats.scss';
|
||||||
</style>
|
</style>
|
||||||
@@ -17,7 +17,34 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<TimetableHistoryList :timetableHistory="timetableHistory" />
|
<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" />
|
||||||
|
|
||||||
|
<!-- Extra -->
|
||||||
|
<TimetableDetails :timetable="timetable" :showExtraInfo="showExtraInfo.value" />
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</transition-group>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<AddDataButton
|
<AddDataButton
|
||||||
:list="timetableHistory"
|
:list="timetableHistory"
|
||||||
@@ -37,17 +64,29 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, PropType } from 'vue';
|
import { defineComponent, PropType, ref } from 'vue';
|
||||||
|
|
||||||
import Loading from '../../Global/Loading.vue';
|
import Loading from '../../Global/Loading.vue';
|
||||||
import AddDataButton from '../../Global/AddDataButton.vue';
|
import AddDataButton from '../../Global/AddDataButton.vue';
|
||||||
import TimetableHistoryList from './TimetableHistoryList.vue';
|
|
||||||
import { useStore } from '../../../store/mainStore';
|
import { useMainStore } from '../../../store/mainStore';
|
||||||
import { Status } from '../../../typings/common';
|
import { Status } from '../../../typings/common';
|
||||||
import { API } from '../../../typings/api';
|
import { API } from '../../../typings/api';
|
||||||
|
|
||||||
|
import TimetableGeneral from './TimetableGeneral.vue';
|
||||||
|
import TimetableStops from './TimetableStops.vue';
|
||||||
|
import TimetableStatus from './TimetableStatus.vue';
|
||||||
|
import TimetableDetails from './TimetableDetails.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { Loading, AddDataButton, TimetableHistoryList },
|
components: {
|
||||||
|
Loading,
|
||||||
|
AddDataButton,
|
||||||
|
TimetableDetails,
|
||||||
|
TimetableGeneral,
|
||||||
|
TimetableStatus,
|
||||||
|
TimetableStops
|
||||||
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
timetableHistory: {
|
timetableHistory: {
|
||||||
@@ -71,8 +110,17 @@ export default defineComponent({
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
Status,
|
Status,
|
||||||
store: useStore()
|
store: useMainStore()
|
||||||
};
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
computedTimetableHistory() {
|
||||||
|
return this.timetableHistory.map((timetable) => ({
|
||||||
|
timetable,
|
||||||
|
showExtraInfo: ref(false)
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -80,4 +128,15 @@ export default defineComponent({
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../../styles/JournalSection.scss';
|
@import '../../../styles/JournalSection.scss';
|
||||||
@import '../../../styles/animations.scss';
|
@import '../../../styles/animations.scss';
|
||||||
|
|
||||||
|
@include smallScreen {
|
||||||
|
.journal_item-info {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-route {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,195 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="details-actions">
|
||||||
|
<button class="btn--action">
|
||||||
|
<b>{{ $t('journal.stock-info') }}</b>
|
||||||
|
<img :src="`/images/icon-arrow-${showExtraInfo ? 'asc' : 'desc'}.svg`" alt="Arrow icon" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="details-body" 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
|
||||||
|
v-for="(sh, i) in stockHistory"
|
||||||
|
:key="i"
|
||||||
|
class="btn--action"
|
||||||
|
:data-checked="i == currentHistoryIndex"
|
||||||
|
@click.stop="currentHistoryIndex = i"
|
||||||
|
>
|
||||||
|
{{ sh.updatedAt }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<StockList
|
||||||
|
:trainStockList="
|
||||||
|
(currentHistoryIndex == 0
|
||||||
|
? timetable.stockString
|
||||||
|
: stockHistory[currentHistoryIndex].stockString
|
||||||
|
).split(';')
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { PropType, defineComponent } from 'vue';
|
||||||
|
import StockList from '../../Global/StockList.vue';
|
||||||
|
import { API } from '../../../typings/api';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: { StockList },
|
||||||
|
props: {
|
||||||
|
showExtraInfo: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
timetable: {
|
||||||
|
type: Object as PropType<API.TimetableHistory.Data>,
|
||||||
|
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 = '/images/icon-unknown.png';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '../../../styles/variables.scss';
|
||||||
|
@import '../../../styles/responsive.scss';
|
||||||
|
@import '../../../styles/badge.scss';
|
||||||
|
|
||||||
|
.details-body {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-actions {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
button img {
|
||||||
|
height: 1.25em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include smallScreen() {
|
||||||
|
.stock-specs {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-actions {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
<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
|
|
||||||
v-for="(sh, i) in stockHistory"
|
|
||||||
:key="i"
|
|
||||||
class="btn--action"
|
|
||||||
: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 StockList from '../../Global/StockList.vue';
|
|
||||||
import { API } from '../../../typings/api';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: { StockList },
|
|
||||||
props: {
|
|
||||||
showExtraInfo: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
timetable: {
|
|
||||||
type: Object as PropType<API.TimetableHistory.Data>,
|
|
||||||
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 = '/images/icon-unknown.png';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</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>
|
|
||||||
@@ -1,11 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="item-general">
|
<div class="item-general">
|
||||||
<span
|
<span class="general-train">
|
||||||
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="text--grayed">#{{ timetable.id }}</span>
|
||||||
|
|
||||||
<span class="badges" v-if="timetable.skr || timetable.twr">
|
<span class="badges" v-if="timetable.skr || timetable.twr">
|
||||||
@@ -28,7 +23,17 @@
|
|||||||
{{ timetable.driverLevel < 2 ? 'L' : `${timetable.driverLevel}` }}
|
{{ timetable.driverLevel < 2 ? 'L' : `${timetable.driverLevel}` }}
|
||||||
</strong>
|
</strong>
|
||||||
|
|
||||||
<strong>{{ timetable.driverName }}</strong>
|
<strong
|
||||||
|
v-if="apiStore.donatorsData.includes(timetable.driverName)"
|
||||||
|
class="text--donator"
|
||||||
|
:title="$t('donations.driver-message')"
|
||||||
|
>
|
||||||
|
{{ timetable.driverName }}
|
||||||
|
</strong>
|
||||||
|
|
||||||
|
<strong v-else>
|
||||||
|
{{ timetable.driverName }}
|
||||||
|
</strong>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="general-time">
|
<span class="general-time">
|
||||||
@@ -56,6 +61,15 @@
|
|||||||
: `${$t('journal.timetable-abandoned')} ${localeTime(timetable.endDate, $i18n.locale)}`
|
: `${$t('journal.timetable-abandoned')} ${localeTime(timetable.endDate, $i18n.locale)}`
|
||||||
}}
|
}}
|
||||||
</b>
|
</b>
|
||||||
|
|
||||||
|
<button
|
||||||
|
v-if="timetable.terminated == false"
|
||||||
|
class="btn--action btn-timetable"
|
||||||
|
@click.stop="showTimetable(timetable, $event.currentTarget)"
|
||||||
|
>
|
||||||
|
<img src="/images/icon-train.svg" alt="train icon" />
|
||||||
|
<b>{{ $t('journal.timetable-online-button') }}</b>
|
||||||
|
</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -63,14 +77,21 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { PropType, defineComponent } from 'vue';
|
import { PropType, defineComponent } from 'vue';
|
||||||
|
|
||||||
|
import { API } from '../../../typings/api';
|
||||||
import dateMixin from '../../../mixins/dateMixin';
|
import dateMixin from '../../../mixins/dateMixin';
|
||||||
import modalTrainMixin from '../../../mixins/modalTrainMixin';
|
import modalTrainMixin from '../../../mixins/modalTrainMixin';
|
||||||
import styleMixin from '../../../mixins/styleMixin';
|
import styleMixin from '../../../mixins/styleMixin';
|
||||||
import { API } from '../../../typings/api';
|
import { useApiStore } from '../../../store/apiStore';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
mixins: [dateMixin, modalTrainMixin, styleMixin],
|
mixins: [dateMixin, modalTrainMixin, styleMixin],
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
apiStore: useApiStore()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
timetable: {
|
timetable: {
|
||||||
type: Object as PropType<API.TimetableHistory.Data>,
|
type: Object as PropType<API.TimetableHistory.Data>,
|
||||||
@@ -89,8 +110,8 @@ export default defineComponent({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../../styles/responsive.scss';
|
@import '../../../styles/responsive';
|
||||||
@import '../../../styles/badge.scss';
|
@import '../../../styles/badge';
|
||||||
|
|
||||||
.item-general {
|
.item-general {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -100,14 +121,29 @@ export default defineComponent({
|
|||||||
|
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
|
|
||||||
@include smallScreen() {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-date {
|
.general-train {
|
||||||
margin-right: 0.5em;
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.25em;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
line-height: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.general-time {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
gap: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badges {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-badge {
|
.info-badge {
|
||||||
@@ -127,11 +163,18 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.general-train {
|
.btn-timetable {
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
padding: 0.2em 0.5em;
|
||||||
align-items: center;
|
|
||||||
gap: 0.25em;
|
img {
|
||||||
|
height: 1.25em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include smallScreen {
|
||||||
|
.item-general {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<!-- Status -->
|
<!-- Status -->
|
||||||
<TimetableStatus :timetable="timetable" />
|
<TimetableStatus :timetable="timetable" />
|
||||||
|
|
||||||
<button class="btn--option btn--show">
|
<button class="btn--action btn--show">
|
||||||
{{ $t('journal.stock-info') }}
|
{{ $t('journal.stock-info') }}
|
||||||
<img
|
<img
|
||||||
:src="`/images/icon-arrow-${showExtraInfo.value ? 'asc' : 'desc'}.svg`"
|
:src="`/images/icon-arrow-${showExtraInfo.value ? 'asc' : 'desc'}.svg`"
|
||||||
@@ -46,6 +46,8 @@ import TimetableExtra from './TimetableExtra.vue';
|
|||||||
import { API } from '../../../typings/api';
|
import { API } from '../../../typings/api';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
components: { TimetableGeneral, TimetableStops, TimetableStatus, TimetableExtra },
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
timetableHistory: {
|
timetableHistory: {
|
||||||
type: Array as PropType<API.TimetableHistory.Response>,
|
type: Array as PropType<API.TimetableHistory.Response>,
|
||||||
@@ -59,16 +61,14 @@ export default defineComponent({
|
|||||||
showExtraInfo: ref(false)
|
showExtraInfo: ref(false)
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
methods: {},
|
|
||||||
components: { TimetableGeneral, TimetableStops, TimetableStatus, TimetableExtra }
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../../styles/variables.scss';
|
@import '../../../styles/variables';
|
||||||
@import '../../../styles/responsive.scss';
|
@import '../../../styles/responsive';
|
||||||
@import '../../../styles/JournalSection.scss';
|
@import '../../../styles/JournalSection';
|
||||||
|
|
||||||
.btn--show {
|
.btn--show {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -1,12 +1,5 @@
|
|||||||
export namespace Journal {
|
export namespace Journal {
|
||||||
export type DispatcherSearcher = {
|
export type DispatcherSearchKey = 'search-dispatcher' | 'search-station' | 'search-date';
|
||||||
[key in 'search-dispatcher' | 'search-station' | 'search-date']: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface DispatcherSorter {
|
|
||||||
id: 'timestampFrom' | 'duration';
|
|
||||||
dir: -1 | 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TimetableSearchKey =
|
export type TimetableSearchKey =
|
||||||
| 'search-driver'
|
| 'search-driver'
|
||||||
@@ -19,11 +12,29 @@ export namespace Journal {
|
|||||||
[key in TimetableSearchKey]: string;
|
[key in TimetableSearchKey]: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type DispatcherSearchType = {
|
||||||
|
[key in DispatcherSearchKey]: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TimetableSorterKey = 'timetableId' | 'beginDate' | 'distance' | 'total-stops';
|
||||||
|
export type DispatcherSorterKey = 'timestampFrom' | 'duration';
|
||||||
|
|
||||||
|
export interface DispatcherSorter {
|
||||||
|
id: DispatcherSorterKey;
|
||||||
|
dir: -1 | 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TimetableSorter {
|
||||||
|
id: TimetableSorterKey;
|
||||||
|
dir: 'asc' | 'desc';
|
||||||
|
}
|
||||||
|
|
||||||
export const enum TimetableFilterId {
|
export const enum TimetableFilterId {
|
||||||
|
ALL_STATUSES = 'all-statuses',
|
||||||
ACTIVE = 'active',
|
ACTIVE = 'active',
|
||||||
FULFILLED = 'fulfilled',
|
FULFILLED = 'fulfilled',
|
||||||
ABANDONED = 'abandoned',
|
ABANDONED = 'abandoned',
|
||||||
ALL = 'all',
|
ALL_SPECIALS = 'all-specials',
|
||||||
TWR = 'twr',
|
TWR = 'twr',
|
||||||
SKR = 'skr',
|
SKR = 'skr',
|
||||||
TWR_SKR = 'twr-skr'
|
TWR_SKR = 'twr-skr'
|
||||||
@@ -31,19 +42,26 @@ export namespace Journal {
|
|||||||
|
|
||||||
export enum FilterSection {
|
export enum FilterSection {
|
||||||
TIMETABLE_STATUS = 'timetable-status',
|
TIMETABLE_STATUS = 'timetable-status',
|
||||||
TWRSKR = 'twrskr'
|
SPECIAL = 'special'
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TimetableFilter {
|
export interface TimetableFilter {
|
||||||
id: TimetableFilterId;
|
id: TimetableFilterId;
|
||||||
filterSection: string;
|
filterSection: string;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
|
default: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TimetableSorterKey = 'timetableId' | 'beginDate' | 'distance' | 'total-stops';
|
export enum StatsTab {
|
||||||
|
DRIVER_STATS = 'journal-driver-stats',
|
||||||
|
DISPATCHER_STATS = 'journal-dispatcher-stats',
|
||||||
|
DAILY_STATS = 'journal-daily-stats'
|
||||||
|
}
|
||||||
|
|
||||||
export interface TimetableSorter {
|
export interface StatsButton {
|
||||||
id: TimetableSorterKey;
|
tab: StatsTab;
|
||||||
dir: 'asc' | 'desc';
|
localeKey: string;
|
||||||
|
iconName: string;
|
||||||
|
disabled: boolean;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,9 @@
|
|||||||
<tr v-for="historyItem in historyList" :key="historyItem.id">
|
<tr v-for="historyItem in historyList" :key="historyItem.id">
|
||||||
<td>#{{ historyItem.stationHash }}</td>
|
<td>#{{ historyItem.stationHash }}</td>
|
||||||
<td>
|
<td>
|
||||||
<router-link :to="`/journal/dispatchers?dispatcherName=${historyItem.dispatcherName}`">
|
<router-link
|
||||||
|
:to="`/journal/dispatchers?search-dispatcher=${historyItem.dispatcherName}`"
|
||||||
|
>
|
||||||
<b>{{ historyItem.dispatcherName }}</b>
|
<b>{{ historyItem.dispatcherName }}</b>
|
||||||
</router-link>
|
</router-link>
|
||||||
</td>
|
</td>
|
||||||
@@ -33,6 +35,8 @@
|
|||||||
>
|
>
|
||||||
{{ historyItem.dispatcherLevel >= 2 ? historyItem.dispatcherLevel : 'L' }}
|
{{ historyItem.dispatcherLevel >= 2 ? historyItem.dispatcherLevel : 'L' }}
|
||||||
</b>
|
</b>
|
||||||
|
|
||||||
|
<b v-else>?</b>
|
||||||
</td>
|
</td>
|
||||||
<td class="text--primary">
|
<td class="text--primary">
|
||||||
<b>{{ historyItem.dispatcherRate }}</b>
|
<b>{{ historyItem.dispatcherRate }}</b>
|
||||||
@@ -66,30 +70,24 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import axios from 'axios';
|
|
||||||
import { defineComponent, PropType } from 'vue';
|
import { defineComponent, PropType } from 'vue';
|
||||||
import dateMixin from '../../mixins/dateMixin';
|
import dateMixin from '../../mixins/dateMixin';
|
||||||
import Station from '../../scripts/interfaces/Station';
|
|
||||||
import { URLs } from '../../scripts/utils/apiURLs';
|
|
||||||
import Loading from '../Global/Loading.vue';
|
import Loading from '../Global/Loading.vue';
|
||||||
import styleMixin from '../../mixins/styleMixin';
|
import styleMixin from '../../mixins/styleMixin';
|
||||||
import listObserverMixin from '../../mixins/listObserverMixin';
|
|
||||||
import { OnlineScenery } from '../../store/typings';
|
|
||||||
import { API } from '../../typings/api';
|
import { API } from '../../typings/api';
|
||||||
import { Status } from '../../typings/common';
|
import { ActiveScenery, Station, Status } from '../../typings/common';
|
||||||
|
import { useApiStore } from '../../store/apiStore';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'SceneryDispatchersHistory',
|
name: 'SceneryDispatchersHistory',
|
||||||
mixins: [dateMixin, styleMixin, listObserverMixin],
|
mixins: [dateMixin, styleMixin],
|
||||||
components: { Loading },
|
components: { Loading },
|
||||||
props: {
|
props: {
|
||||||
station: {
|
station: {
|
||||||
type: Object as PropType<Station>,
|
type: Object as PropType<Station>
|
||||||
required: true
|
|
||||||
},
|
},
|
||||||
onlineScenery: {
|
onlineScenery: {
|
||||||
type: Object as PropType<OnlineScenery>,
|
type: Object as PropType<ActiveScenery>
|
||||||
required: false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -97,7 +95,8 @@ export default defineComponent({
|
|||||||
return {
|
return {
|
||||||
historyList: [] as API.DispatcherHistory.Response,
|
historyList: [] as API.DispatcherHistory.Response,
|
||||||
dataStatus: Status.Data.Loading,
|
dataStatus: Status.Data.Loading,
|
||||||
DataStatus: Status.Data
|
DataStatus: Status.Data,
|
||||||
|
apiStore: useApiStore()
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -113,12 +112,20 @@ export default defineComponent({
|
|||||||
countFrom = 0,
|
countFrom = 0,
|
||||||
countLimit = 30
|
countLimit = 30
|
||||||
): Promise<API.DispatcherHistory.Response | null> {
|
): Promise<API.DispatcherHistory.Response | null> {
|
||||||
|
if (!this.station && !this.onlineScenery) {
|
||||||
|
this.dataStatus = Status.Data.Loaded;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.dataStatus = Status.Data.Loading;
|
this.dataStatus = Status.Data.Loading;
|
||||||
|
|
||||||
const requestString = `${URLs.stacjownikAPI}/api/getDispatchers?stationName=${this.station.name}&countFrom=${countFrom}&countLimit=${countLimit}`;
|
const requestString = `api/getDispatchers?stationName=${
|
||||||
|
this.station?.name || this.onlineScenery?.name
|
||||||
|
}&countFrom=${countFrom}&countLimit=${countLimit}`;
|
||||||
|
|
||||||
const historyAPIData: API.DispatcherHistory.Response = await (
|
const historyAPIData: API.DispatcherHistory.Response = await (
|
||||||
await axios.get(requestString)
|
await this.apiStore.client!.get(requestString)
|
||||||
).data;
|
).data;
|
||||||
|
|
||||||
this.dataStatus = Status.Data.Loaded;
|
this.dataStatus = Status.Data.Loaded;
|
||||||
@@ -130,7 +137,9 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
navigateToHistory() {
|
navigateToHistory() {
|
||||||
this.$router.push(`/journal/dispatchers?sceneryName=${this.station.name}`);
|
this.$router.push(
|
||||||
|
`/journal/dispatchers?search-station=${this.station?.name || this.onlineScenery?.name}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="info-header">
|
<section class="info-header">
|
||||||
<a class="scenery-name" :href="station.generalInfo?.url" target="_blank">
|
<a class="scenery-name" :href="station?.generalInfo?.url" target="_blank">
|
||||||
{{ station.name }}
|
{{ stationName.replace(/_/g, ' ') }}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="scenery-abbrev">
|
<div class="scenery-abbrev" v-if="station?.generalInfo?.abbr">
|
||||||
{{ $t('scenery.abbrev') }} <b>{{ station.generalInfo?.abbr }}</b>
|
{{ $t('scenery.abbrev') }} <b>{{ station.generalInfo.abbr }}</b>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="scenery-hash" v-if="onlineScenery?.hash">#{{ onlineScenery.hash }}</div>
|
<div class="scenery-hash" v-if="onlineScenery?.hash">#{{ onlineScenery.hash }}</div>
|
||||||
@@ -14,19 +14,21 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { PropType, defineComponent } from 'vue';
|
import { PropType, defineComponent } from 'vue';
|
||||||
import Station from '../../scripts/interfaces/Station';
|
import { ActiveScenery, Station } from '../../typings/common';
|
||||||
import { OnlineScenery } from '../../store/typings';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
station: {
|
station: {
|
||||||
type: Object as PropType<Station>,
|
type: Object as PropType<Station>
|
||||||
|
},
|
||||||
|
|
||||||
|
stationName: {
|
||||||
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
|
||||||
onlineScenery: {
|
onlineScenery: {
|
||||||
type: Object as PropType<OnlineScenery>,
|
type: Object as PropType<ActiveScenery>
|
||||||
required: false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -58,4 +60,3 @@ export default defineComponent({
|
|||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
../../store/storeTypes
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="scenery-info">
|
<div class="scenery-info">
|
||||||
<section>
|
<section>
|
||||||
<div class="scenery-info-general" v-if="station.generalInfo">
|
<div class="scenery-info-general">
|
||||||
<SceneryInfoIcons :station="station" />
|
<SceneryInfoIcons :station="station" />
|
||||||
|
|
||||||
<div class="scenery-general-list">
|
<div class="scenery-general-list" v-if="station?.generalInfo">
|
||||||
<span>
|
<span>
|
||||||
<b>{{ $t('availability.title') }}:</b>
|
<b>{{ $t('availability.title') }}:</b>
|
||||||
{{ $t(`availability.${station.generalInfo.availability}`) }}
|
{{ $t(`availability.${station.generalInfo.availability}`) }}
|
||||||
@@ -46,11 +46,11 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SceneryInfoRoutes :station="station" />
|
<SceneryInfoRoutes v-if="station" :station="station" />
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="scenery-authors"
|
class="scenery-authors"
|
||||||
v-if="station.generalInfo.authors && station.generalInfo.authors.length > 0"
|
v-if="station?.generalInfo?.authors && station.generalInfo.authors.length > 0"
|
||||||
>
|
>
|
||||||
<b>
|
<b>
|
||||||
{{
|
{{
|
||||||
@@ -89,8 +89,7 @@ import SceneryInfoIcons from './SceneryInfo/SceneryInfoIcons.vue';
|
|||||||
import SceneryInfoUserList from './SceneryInfo/SceneryInfoUserList.vue';
|
import SceneryInfoUserList from './SceneryInfo/SceneryInfoUserList.vue';
|
||||||
import SceneryInfoSpawnList from './SceneryInfo/SceneryInfoSpawnList.vue';
|
import SceneryInfoSpawnList from './SceneryInfo/SceneryInfoSpawnList.vue';
|
||||||
import SceneryInfoRoutes from './SceneryInfo/SceneryInfoRoutes.vue';
|
import SceneryInfoRoutes from './SceneryInfo/SceneryInfoRoutes.vue';
|
||||||
import Station from '../../scripts/interfaces/Station';
|
import { ActiveScenery, Station } from '../../typings/common';
|
||||||
import { OnlineScenery } from '../../store/typings';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
@@ -102,13 +101,11 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
station: {
|
station: {
|
||||||
type: Object as PropType<Station>,
|
type: Object as PropType<Station>
|
||||||
required: true
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onlineScenery: {
|
onlineScenery: {
|
||||||
type: Object as PropType<OnlineScenery>,
|
type: Object as PropType<ActiveScenery>
|
||||||
required: false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,31 +1,45 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="info-dispatcher">
|
<section class="info-dispatcher">
|
||||||
<div class="dispatcher" v-if="onlineScenery">
|
<div class="info-top" v-if="onlineScenery && onlineScenery.dispatcherExp != -1">
|
||||||
<span
|
<span
|
||||||
class="dispatcher_level"
|
class="dispatcher-level"
|
||||||
:style="calculateExpStyle(onlineScenery.dispatcherExp, onlineScenery.dispatcherIsSupporter)"
|
:style="calculateExpStyle(onlineScenery.dispatcherExp, onlineScenery.dispatcherIsSupporter)"
|
||||||
>
|
>
|
||||||
{{ onlineScenery.dispatcherExp > 1 ? onlineScenery.dispatcherExp : 'L' }}
|
{{ onlineScenery.dispatcherExp > 1 ? onlineScenery.dispatcherExp : 'L' }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<router-link
|
<router-link
|
||||||
class="dispatcher_name"
|
class="dispatcher-name"
|
||||||
:to="`/journal/dispatchers?dispatcherName=${onlineScenery.dispatcherName}`"
|
:to="`/journal/dispatchers?search-dispatcher=${onlineScenery.dispatcherName}`"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="text--donator"
|
||||||
|
v-if="apiStore.donatorsData.includes(onlineScenery.dispatcherName)"
|
||||||
|
:title="$t('donations.dispatcher-message')"
|
||||||
>
|
>
|
||||||
{{ onlineScenery.dispatcherName }}
|
{{ onlineScenery.dispatcherName }}
|
||||||
|
</span>
|
||||||
|
<span v-else>{{ onlineScenery.dispatcherName }}</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
</div>
|
||||||
|
|
||||||
<span class="dispatcher_likes text--primary">
|
<div class="info-bottom">
|
||||||
|
<span
|
||||||
|
class="dispatcher-likes text--primary"
|
||||||
|
v-if="onlineScenery && onlineScenery.dispatcherExp != -1"
|
||||||
|
>
|
||||||
<img src="/images/icon-like.svg" alt="Likes count icon" />
|
<img src="/images/icon-like.svg" alt="Likes count icon" />
|
||||||
<span>{{ onlineScenery?.dispatcherRate || '0' }}</span>
|
<span>{{ onlineScenery?.dispatcherRate || '0' }}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<span class="dispatcher-badge">
|
||||||
<StationStatusBadge
|
<StationStatusBadge
|
||||||
:isOnline="onlineScenery ? true : false"
|
:isOnline="onlineScenery ? true : false"
|
||||||
:dispatcherStatus="onlineScenery?.dispatcherStatus"
|
:dispatcherStatus="onlineScenery?.dispatcherStatus"
|
||||||
:dispatcherTimestamp="onlineScenery?.dispatcherTimestamp"
|
:dispatcherTimestamp="onlineScenery?.dispatcherTimestamp"
|
||||||
/>
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -35,13 +49,21 @@ import dateMixin from '../../../mixins/dateMixin';
|
|||||||
import routerMixin from '../../../mixins/routerMixin';
|
import routerMixin from '../../../mixins/routerMixin';
|
||||||
import styleMixin from '../../../mixins/styleMixin';
|
import styleMixin from '../../../mixins/styleMixin';
|
||||||
import StationStatusBadge from '../../Global/StationStatusBadge.vue';
|
import StationStatusBadge from '../../Global/StationStatusBadge.vue';
|
||||||
import { OnlineScenery } from '../../../store/typings';
|
import { ActiveScenery } from '../../../typings/common';
|
||||||
|
import { useApiStore } from '../../../store/apiStore';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
mixins: [styleMixin, dateMixin, routerMixin],
|
mixins: [styleMixin, dateMixin, routerMixin],
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
apiStore: useApiStore()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
onlineScenery: {
|
onlineScenery: {
|
||||||
type: Object as PropType<OnlineScenery>,
|
type: Object as PropType<ActiveScenery>,
|
||||||
required: false
|
required: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -51,19 +73,26 @@ export default defineComponent({
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.info-dispatcher {
|
.info-dispatcher {
|
||||||
display: flex;
|
font-size: 1.8em;
|
||||||
align-items: center;
|
}
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
flex-wrap: wrap;
|
.info-top {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-bottom {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
|
|
||||||
.dispatcher {
|
margin-top: 0.5em;
|
||||||
font-size: 2em;
|
}
|
||||||
|
|
||||||
&_level {
|
.dispatcher-level {
|
||||||
display: inline-block;
|
|
||||||
margin-right: 0.3em;
|
|
||||||
background: firebrick;
|
background: firebrick;
|
||||||
|
|
||||||
border-radius: 0.1em;
|
border-radius: 0.1em;
|
||||||
@@ -72,24 +101,18 @@ export default defineComponent({
|
|||||||
height: 1.5em;
|
height: 1.5em;
|
||||||
line-height: 1.5em;
|
line-height: 1.5em;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
&_name {
|
.dispatcher-likes {
|
||||||
cursor: pointer;
|
display: flex;
|
||||||
margin-right: 0.25em;
|
gap: 0.25em;
|
||||||
}
|
|
||||||
|
|
||||||
&_likes {
|
|
||||||
img {
|
img {
|
||||||
height: 0.7em;
|
width: 1em;
|
||||||
margin: 0 0.25em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-badge {
|
|
||||||
font-size: 1.25em;
|
|
||||||
margin: 0.5em 0.25em;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dispatcher-badge {
|
||||||
|
font-size: 0.7em;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,92 +1,101 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="info-icons">
|
<section class="info-icons">
|
||||||
<span
|
<span v-if="!station || !station.generalInfo">
|
||||||
v-if="station.generalInfo && station.generalInfo.reqLevel >= 0"
|
|
||||||
class="scenery-icon icon-info level"
|
|
||||||
:style="calculateExpStyle(station.generalInfo.reqLevel)"
|
|
||||||
>
|
|
||||||
{{ station.generalInfo.reqLevel >= 2 ? station.generalInfo.reqLevel : 'L' }}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span
|
|
||||||
v-if="station.generalInfo"
|
|
||||||
class="scenery-icon icon-info"
|
|
||||||
:class="station.generalInfo.controlType.replace('+', '-')"
|
|
||||||
:title="$t('desc.control-type') + $t(`controls.${station.generalInfo.controlType}`)"
|
|
||||||
v-html="getControlTypeAbbrev(station.generalInfo.controlType)"
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<img
|
<img
|
||||||
v-if="station.generalInfo?.SUP"
|
|
||||||
class="icon-info"
|
|
||||||
src="/images/icon-SUP.svg"
|
|
||||||
alt="SUP (RASP-UZK)"
|
|
||||||
:title="$t('desc.SUP')"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<img
|
|
||||||
v-if="station.generalInfo?.signalType"
|
|
||||||
class="icon-info"
|
|
||||||
:src="`/images/icon-${station.generalInfo.signalType}.svg`"
|
|
||||||
:alt="station.generalInfo.signalType"
|
|
||||||
:title="$t('desc.signals-type') + $t(`signals.${station.generalInfo.signalType}`)"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<img
|
|
||||||
v-if="station.generalInfo?.availability == 'nonPublic'"
|
|
||||||
class="icon-info"
|
|
||||||
src="/images/icon-lock.svg"
|
|
||||||
alt="Non-public scenery"
|
|
||||||
:title="$t('desc.non-public')"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<img
|
|
||||||
v-if="station.generalInfo?.availability == 'unavailable'"
|
|
||||||
class="icon-info"
|
|
||||||
src="/images/icon-unavailable.svg"
|
|
||||||
alt="Unavailable scenery"
|
|
||||||
:title="$t('desc.unavailable')"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<img
|
|
||||||
v-if="station.generalInfo?.availability == 'abandoned'"
|
|
||||||
class="icon-info"
|
|
||||||
src="/images/icon-abandoned.svg"
|
|
||||||
alt="Abandoned scenery"
|
|
||||||
:title="$t('desc.abandoned')"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<img
|
|
||||||
v-if="station.generalInfo?.lines"
|
|
||||||
class="icon-info"
|
|
||||||
src="/images/icon-real.svg"
|
|
||||||
alt="real scenery"
|
|
||||||
:title="`${$t('desc.real')} ${station.generalInfo.lines}`"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<img
|
|
||||||
v-if="!station.generalInfo"
|
|
||||||
class="icon-info"
|
class="icon-info"
|
||||||
src="/images/icon-unknown.svg"
|
src="/images/icon-unknown.svg"
|
||||||
alt="icon-unknown"
|
alt="icon-unknown"
|
||||||
:title="$t('desc.unknown')"
|
:title="$t('sceneries.info.unknown')"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span
|
||||||
|
v-if="station?.generalInfo && station?.generalInfo.reqLevel >= 0"
|
||||||
|
class="scenery-icon icon-info level"
|
||||||
|
:style="calculateExpStyle(station?.generalInfo.reqLevel)"
|
||||||
|
>
|
||||||
|
{{ station?.generalInfo.reqLevel >= 2 ? station?.generalInfo.reqLevel : 'L' }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span
|
||||||
|
v-if="station?.generalInfo"
|
||||||
|
class="scenery-icon icon-info"
|
||||||
|
:class="station?.generalInfo.controlType.replace('+', '-')"
|
||||||
|
:title="
|
||||||
|
$t('sceneries.info.control-type') + $t(`controls.${station?.generalInfo.controlType}`)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ $t(`controls.abbrevs.${station.generalInfo.controlType}`) }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<img
|
||||||
|
v-if="station?.generalInfo?.signalType"
|
||||||
|
class="icon-info"
|
||||||
|
:src="`/images/icon-${station.generalInfo.signalType}.svg`"
|
||||||
|
:alt="station.generalInfo.signalType"
|
||||||
|
:title="$t('sceneries.info.signals-type') + $t(`signals.${station.generalInfo.signalType}`)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<img
|
||||||
|
v-if="station?.generalInfo?.availability == 'nonPublic'"
|
||||||
|
class="icon-info"
|
||||||
|
src="/images/icon-lock.svg"
|
||||||
|
alt="Non-public scenery"
|
||||||
|
:title="$t('sceneries.info.non-public')"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<img
|
||||||
|
v-if="station?.generalInfo?.availability == 'unavailable'"
|
||||||
|
class="icon-info"
|
||||||
|
src="/images/icon-unavailable.svg"
|
||||||
|
alt="Unavailable scenery"
|
||||||
|
:title="$t('sceneries.info.unavailable')"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<img
|
||||||
|
v-if="station?.generalInfo?.availability == 'abandoned'"
|
||||||
|
class="icon-info"
|
||||||
|
src="/images/icon-abandoned.svg"
|
||||||
|
alt="Abandoned scenery"
|
||||||
|
:title="$t('sceneries.info.abandoned')"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<img
|
||||||
|
v-if="station?.generalInfo?.SUP"
|
||||||
|
class="icon-info"
|
||||||
|
src="/images/icon-SUP.svg"
|
||||||
|
alt="SUP (RASP-UZK)"
|
||||||
|
:title="$t('sceneries.info.SUP')"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<img
|
||||||
|
v-if="station?.generalInfo?.ASDEK"
|
||||||
|
class="icon-info"
|
||||||
|
src="/images/icon-ASDEK.svg"
|
||||||
|
alt="dSAT ASDEK"
|
||||||
|
:title="$t('sceneries.info.ASDEK')"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<img
|
||||||
|
v-if="station?.generalInfo?.lines"
|
||||||
|
class="icon-info"
|
||||||
|
src="/images/icon-real.svg"
|
||||||
|
alt="real scenery"
|
||||||
|
:title="`${$t('sceneries.info.real')} ${station.generalInfo.lines}`"
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { PropType, defineComponent } from 'vue';
|
import { PropType, defineComponent } from 'vue';
|
||||||
import stationInfoMixin from '../../../mixins/stationInfoMixin';
|
|
||||||
import styleMixin from '../../../mixins/styleMixin';
|
import styleMixin from '../../../mixins/styleMixin';
|
||||||
import Station from '../../../scripts/interfaces/Station';
|
import { Station } from '../../../typings/common';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
mixins: [stationInfoMixin, styleMixin],
|
mixins: [styleMixin],
|
||||||
props: {
|
props: {
|
||||||
station: {
|
station: {
|
||||||
type: Object as PropType<Station>,
|
type: Object as PropType<Station>
|
||||||
required: true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -94,6 +103,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../../styles/icons.scss';
|
@import '../../../styles/icons.scss';
|
||||||
|
|
||||||
.info-icons {
|
.info-icons {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -101,6 +111,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
margin: 1em;
|
margin: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-info {
|
.icon-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
@@ -1,41 +1,49 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="info-routes" v-if="station.generalInfo">
|
<section class="info-routes" v-if="station.generalInfo">
|
||||||
<div class="routes one-way" v-if="station.generalInfo.routes.oneWay.length > 0">
|
<div class="routes one-way" v-if="oneWayRoutes.length > 0">
|
||||||
<b>{{ $t('scenery.one-way-routes') }}</b>
|
<b>{{ $t('scenery.one-way-routes') }}</b>
|
||||||
|
|
||||||
<ul class="routes-list">
|
<ul class="routes-list">
|
||||||
<li
|
<li
|
||||||
v-for="route in station.generalInfo.routes.oneWay"
|
v-for="route in oneWayRoutes"
|
||||||
:key="route.name"
|
:key="route.routeName"
|
||||||
@click="setActiveShowLength(route.name)"
|
@click="setActiveShowLength(route.routeName)"
|
||||||
>
|
>
|
||||||
<span :class="{ 'no-catenary': !route.catenary, internal: route.isInternal }">
|
<span :class="{ 'no-catenary': !route.isElectric, internal: route.isInternal }">
|
||||||
{{ route.name }}</span
|
{{ route.routeName }}</span
|
||||||
>
|
>
|
||||||
<span v-if="route.speed" class="speed">
|
<span v-if="route.routeSpeed" class="speed">
|
||||||
{{ activeShowLength.includes(route.name) ? route.length + 'm' : route.speed }}
|
{{
|
||||||
|
activeShowLength.includes(route.routeName)
|
||||||
|
? route.routeLength + 'm'
|
||||||
|
: route.routeSpeed
|
||||||
|
}}
|
||||||
</span>
|
</span>
|
||||||
<span v-if="route.SBL" class="sbl">SBL</span>
|
<span v-if="route.isRouteSBL" class="sbl">SBL</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="routes two-way" v-if="station.generalInfo.routes.twoWay.length > 0">
|
<div class="routes two-way" v-if="twoWayRoutes.length > 0">
|
||||||
<b>{{ $t('scenery.two-way-routes') }}</b>
|
<b>{{ $t('scenery.two-way-routes') }}</b>
|
||||||
|
|
||||||
<ul class="routes-list">
|
<ul class="routes-list">
|
||||||
<li
|
<li
|
||||||
v-for="route in station.generalInfo.routes.twoWay"
|
v-for="route in twoWayRoutes"
|
||||||
:key="route.name"
|
:key="route.routeName"
|
||||||
@click="setActiveShowLength(route.name)"
|
@click="setActiveShowLength(route.routeName)"
|
||||||
>
|
>
|
||||||
<span :class="{ 'no-catenary': !route.catenary, internal: route.isInternal }">{{
|
<span :class="{ 'no-catenary': !route.isElectric, internal: route.isInternal }">{{
|
||||||
route.name
|
route.routeName
|
||||||
}}</span>
|
}}</span>
|
||||||
<span v-if="route.speed" class="speed">
|
<span v-if="route.routeSpeed" class="speed">
|
||||||
{{ activeShowLength.includes(route.name) ? route.length + 'm' : route.speed }}
|
{{
|
||||||
|
activeShowLength.includes(route.routeName)
|
||||||
|
? route.routeLength + 'm'
|
||||||
|
: route.routeSpeed
|
||||||
|
}}
|
||||||
</span>
|
</span>
|
||||||
<span v-if="route.SBL" class="sbl">SBL</span>
|
<span v-if="route.isRouteSBL" class="sbl">SBL</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -44,7 +52,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { PropType, defineComponent } from 'vue';
|
import { PropType, defineComponent } from 'vue';
|
||||||
import Station from '../../../scripts/interfaces/Station';
|
import { Station } from '../../../typings/common';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
@@ -66,6 +74,16 @@ export default defineComponent({
|
|||||||
return {
|
return {
|
||||||
activeShowLength: [] as string[]
|
activeShowLength: [] as string[]
|
||||||
};
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
oneWayRoutes() {
|
||||||
|
return this.station.generalInfo?.routes.single ?? [];
|
||||||
|
},
|
||||||
|
|
||||||
|
twoWayRoutes() {
|
||||||
|
return this.station.generalInfo?.routes.double ?? [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<transition-group name="spawns-anim" tag="ul">
|
<transition-group name="spawns-anim" tag="ul">
|
||||||
<li
|
<li
|
||||||
class="badge spawn badge-none"
|
class="badge badge-none"
|
||||||
v-if="!onlineScenery || onlineScenery.spawns.length == 0"
|
v-if="!onlineScenery || onlineScenery.spawns.length == 0"
|
||||||
key="no-spawns"
|
key="no-spawns"
|
||||||
>
|
>
|
||||||
@@ -16,13 +16,13 @@
|
|||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li
|
<li
|
||||||
class="badge spawn"
|
class="badge spawn-badge"
|
||||||
v-for="(spawn, i) in sortedSpawns"
|
v-for="(spawn, i) in sortedSpawns"
|
||||||
:key="spawn.spawnName + onlineScenery?.dispatcherName + i"
|
:key="spawn.spawnName + onlineScenery?.dispatcherName + i"
|
||||||
:data-electrified="spawn.isElectrified"
|
:data-electrified="spawn.isElectrified"
|
||||||
>
|
>
|
||||||
<span class="spawn_name">{{ spawn.spawnName }}</span>
|
<span class="name">{{ spawn.spawnName }}</span>
|
||||||
<span class="spawn_length">{{ spawn.spawnLength }}m</span>
|
<span class="length">{{ spawn.spawnLength }}m</span>
|
||||||
</li>
|
</li>
|
||||||
</transition-group>
|
</transition-group>
|
||||||
</section>
|
</section>
|
||||||
@@ -30,12 +30,12 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { PropType, defineComponent } from 'vue';
|
import { PropType, defineComponent } from 'vue';
|
||||||
import { OnlineScenery } from '../../../store/typings';
|
import { ActiveScenery } from '../../../typings/common';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
onlineScenery: {
|
onlineScenery: {
|
||||||
type: Object as PropType<OnlineScenery>,
|
type: Object as PropType<ActiveScenery>,
|
||||||
required: false
|
required: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -59,19 +59,6 @@ ul {
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.spawn {
|
|
||||||
color: white;
|
|
||||||
|
|
||||||
&_length {
|
|
||||||
background-color: #404040;
|
|
||||||
color: #cfcfcf;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-electrified='true'] > &_name {
|
|
||||||
background-color: #007599;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.spawns-anim {
|
.spawns-anim {
|
||||||
&-move,
|
&-move,
|
||||||
&-enter-active,
|
&-enter-active,
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
<template>
|
|
||||||
<section class="info-stats" :class="!station.onlineInfo ? 'no-stats' : ''">
|
|
||||||
<span class="likes">
|
|
||||||
<img src="/images/icon-like" alt="Likes count icon" />
|
|
||||||
<span>{{ station.onlineInfo?.dispatcherRate || '0' }}</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="users">
|
|
||||||
<img src="/images/icon-user" alt="Users count icon" />
|
|
||||||
<span>{{ station.onlineInfo?.currentUsers || '0' }}</span>
|
|
||||||
/
|
|
||||||
<span>{{ station.onlineInfo?.maxUsers || '0' }}</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="spawns">
|
|
||||||
<img src="/images/icon-spawn" alt="Spawns count icon" />
|
|
||||||
<span>{{ station.onlineInfo?.spawns.length || '0' }}</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="schedules">
|
|
||||||
<img src="/images/icon-timetable" alt="Timetables count icon" />
|
|
||||||
<span>
|
|
||||||
<span style="color: #eee">{{ station.onlineInfo?.scheduledTrains?.length || '0' }}</span>
|
|
||||||
/
|
|
||||||
<span style="color: #bbb"
|
|
||||||
>{{
|
|
||||||
station.onlineInfo?.scheduledTrains?.filter((train) => train.stopInfo.confirmed)
|
|
||||||
.length || '0'
|
|
||||||
}}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</section>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { PropType, defineComponent } from 'vue';
|
|
||||||
import Station from '../../../scripts/interfaces/Station';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
station: {
|
|
||||||
type: Object as PropType<Station>,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@import '../../../styles/variables.scss';
|
|
||||||
|
|
||||||
.info-stats {
|
|
||||||
padding: 1rem 0;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
font-size: 1.65em;
|
|
||||||
|
|
||||||
&.no-stats {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > span {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
margin: 0.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.likes,
|
|
||||||
.spawns {
|
|
||||||
color: $accentCol;
|
|
||||||
}
|
|
||||||
|
|
||||||
span > img {
|
|
||||||
width: 1.2em;
|
|
||||||
margin-right: 0.5em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -32,14 +32,14 @@
|
|||||||
import { PropType, defineComponent } from 'vue';
|
import { PropType, defineComponent } from 'vue';
|
||||||
import modalTrainMixin from '../../../mixins/modalTrainMixin';
|
import modalTrainMixin from '../../../mixins/modalTrainMixin';
|
||||||
import routerMixin from '../../../mixins/routerMixin';
|
import routerMixin from '../../../mixins/routerMixin';
|
||||||
import { OnlineScenery } from '../../../store/typings';
|
import { ActiveScenery } from '../../../typings/common';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
mixins: [routerMixin, modalTrainMixin],
|
mixins: [routerMixin, modalTrainMixin],
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
onlineScenery: {
|
onlineScenery: {
|
||||||
type: Object as PropType<OnlineScenery>,
|
type: Object as PropType<ActiveScenery>,
|
||||||
required: false
|
required: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,22 +6,14 @@
|
|||||||
<span>{{ $t('scenery.timetables') }}</span>
|
<span>{{ $t('scenery.timetables') }}</span>
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
<span class="text--primary">{{ onlineScenery?.scheduledTrainCount.all || 0 }}</span>
|
<span class="text--primary">{{ onlineScenery?.scheduledTrainCount.all ?? 0 }}</span>
|
||||||
<span> / </span>
|
<span> / </span>
|
||||||
<span class="text--grayed">
|
<span class="text--grayed">
|
||||||
{{ onlineScenery?.scheduledTrainCount.confirmed || '0' }}
|
{{ onlineScenery?.scheduledTrainCount.confirmed ?? 0 }}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="header_links">
|
<span class="header_links" v-if="station">
|
||||||
<a
|
|
||||||
:href="`https://pragotron-td2.web.app/board?name=${station.name}`"
|
|
||||||
target="_blank"
|
|
||||||
:title="$t('scenery.pragotron-link')"
|
|
||||||
>
|
|
||||||
<img src="/images/icon-pragotron.svg" alt="icon-pragotron" />
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a :href="tabliceZbiorczeHref" target="_blank" :title="$t('scenery.tablice-link')">
|
<a :href="tabliceZbiorczeHref" target="_blank" :title="$t('scenery.tablice-link')">
|
||||||
<img src="/images/icon-tablice.ico" alt="icon-tablice" />
|
<img src="/images/icon-tablice.ico" alt="icon-tablice" />
|
||||||
</a>
|
</a>
|
||||||
@@ -33,12 +25,12 @@
|
|||||||
{{ (i > 0 && '•') || '' }}
|
{{ (i > 0 && '•') || '' }}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
:key="cp.checkpointName"
|
:key="cp"
|
||||||
class="checkpoint_item"
|
class="checkpoint_item"
|
||||||
:class="{ current: chosenCheckpoint === cp.checkpointName }"
|
:class="{ current: chosenCheckpoint === cp }"
|
||||||
@click="setCheckpoint(cp)"
|
@click="setCheckpoint(cp)"
|
||||||
>
|
>
|
||||||
{{ cp.checkpointName }}
|
{{ cp }}
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -48,7 +40,7 @@
|
|||||||
<transition-group name="list-anim">
|
<transition-group name="list-anim">
|
||||||
<div
|
<div
|
||||||
style="padding-bottom: 5em"
|
style="padding-bottom: 5em"
|
||||||
v-if="store.dataStatuses.trains == 0 && computedScheduledTrains.length == 0"
|
v-if="apiStore.dataStatuses.connection == 0 && computedScheduledTrains.length == 0"
|
||||||
key="list-loading"
|
key="list-loading"
|
||||||
>
|
>
|
||||||
<Loading />
|
<Loading />
|
||||||
@@ -74,7 +66,7 @@
|
|||||||
class="timetable-item"
|
class="timetable-item"
|
||||||
v-else
|
v-else
|
||||||
v-for="scheduledTrain in computedScheduledTrains"
|
v-for="scheduledTrain in computedScheduledTrains"
|
||||||
:key="scheduledTrain.trainId"
|
:key="scheduledTrain.trainId + scheduledTrain.stopInfo.arrivalTimestamp"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@click.prevent.stop="selectModalTrain(scheduledTrain.trainId, $event.currentTarget)"
|
@click.prevent.stop="selectModalTrain(scheduledTrain.trainId, $event.currentTarget)"
|
||||||
@keydown.enter.prevent="selectModalTrain(scheduledTrain.trainId, $event.currentTarget)"
|
@keydown.enter.prevent="selectModalTrain(scheduledTrain.trainId, $event.currentTarget)"
|
||||||
@@ -186,11 +178,11 @@ import { useRoute } from 'vue-router';
|
|||||||
import Loading from '../Global/Loading.vue';
|
import Loading from '../Global/Loading.vue';
|
||||||
import dateMixin from '../../mixins/dateMixin';
|
import dateMixin from '../../mixins/dateMixin';
|
||||||
import routerMixin from '../../mixins/routerMixin';
|
import routerMixin from '../../mixins/routerMixin';
|
||||||
import Station from '../../scripts/interfaces/Station';
|
import { useMainStore } from '../../store/mainStore';
|
||||||
import { useStore } from '../../store/mainStore';
|
|
||||||
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
||||||
import ScheduledTrainStatus from './ScheduledTrainStatus.vue';
|
import ScheduledTrainStatus from './ScheduledTrainStatus.vue';
|
||||||
import { OnlineScenery } from '../../store/typings';
|
import { useApiStore } from '../../store/apiStore';
|
||||||
|
import { ActiveScenery, Station } from '../../typings/common';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'SceneryTimetable',
|
name: 'SceneryTimetable',
|
||||||
@@ -201,12 +193,10 @@ export default defineComponent({
|
|||||||
|
|
||||||
props: {
|
props: {
|
||||||
station: {
|
station: {
|
||||||
type: Object as PropType<Station>,
|
type: Object as PropType<Station>
|
||||||
required: true
|
|
||||||
},
|
},
|
||||||
onlineScenery: {
|
onlineScenery: {
|
||||||
type: Object as PropType<OnlineScenery>,
|
type: Object as PropType<ActiveScenery>
|
||||||
required: false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -226,36 +216,41 @@ export default defineComponent({
|
|||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const currentURL = computed(() => `${location.origin}${route.fullPath}`);
|
const currentURL = computed(() => `${location.origin}${route.fullPath}`);
|
||||||
|
|
||||||
const store = useStore();
|
const apiStore = useApiStore();
|
||||||
|
const mainStore = useMainStore();
|
||||||
|
|
||||||
const chosenCheckpoint = ref(
|
const chosenCheckpoint = ref(
|
||||||
props.station?.generalInfo?.checkpoints?.length == 0
|
props.station?.generalInfo?.checkpoints?.length == 0
|
||||||
? ''
|
? ''
|
||||||
: props.station?.generalInfo?.checkpoints[0].checkpointName || null
|
: props.station?.generalInfo?.checkpoints[0] ?? null
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currentURL,
|
currentURL,
|
||||||
chosenCheckpoint,
|
chosenCheckpoint,
|
||||||
store
|
apiStore,
|
||||||
|
mainStore
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
tabliceZbiorczeHref() {
|
tabliceZbiorczeHref() {
|
||||||
let url = `https://tablice-td2.web.app/?station=${this.station.name}`;
|
let url = `https://tablice-td2.web.app/?station=${this.station!.name}`;
|
||||||
if (this.chosenCheckpoint) url += `&checkpoint=${this.chosenCheckpoint}`;
|
if (this.chosenCheckpoint) url += `&checkpoint=${this.chosenCheckpoint}`;
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
},
|
},
|
||||||
|
|
||||||
computedScheduledTrains() {
|
computedScheduledTrains() {
|
||||||
|
if (!this.station) return [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
this.onlineScenery?.scheduledTrains
|
this.onlineScenery?.scheduledTrains
|
||||||
?.filter(
|
?.filter(
|
||||||
(train) =>
|
(train) =>
|
||||||
train.checkpointName.toLocaleLowerCase() ==
|
train.checkpointName.toLocaleLowerCase() ==
|
||||||
(this.chosenCheckpoint || this.station.name).toLocaleLowerCase()
|
(this.chosenCheckpoint || this.station!.name).toLocaleLowerCase() &&
|
||||||
|
train.region == this.mainStore.region.id
|
||||||
)
|
)
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
if (a.stopStatusID > b.stopStatusID) return 1;
|
if (a.stopStatusID > b.stopStatusID) return 1;
|
||||||
@@ -272,12 +267,13 @@ export default defineComponent({
|
|||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
loadSelectedOption() {
|
loadSelectedOption() {
|
||||||
this.chosenCheckpoint =
|
if (!this.station) return;
|
||||||
this.station.generalInfo?.checkpoints[0]?.checkpointName || this.station.name;
|
|
||||||
|
this.chosenCheckpoint = this.station.generalInfo?.checkpoints[0] ?? this.station.name;
|
||||||
},
|
},
|
||||||
|
|
||||||
setCheckpoint(cp: { checkpointName: string }) {
|
setCheckpoint(cp: string) {
|
||||||
this.chosenCheckpoint = cp.checkpointName;
|
this.chosenCheckpoint = cp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -409,13 +405,6 @@ export default defineComponent({
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.g-tooltip > .content {
|
|
||||||
z-index: 100;
|
|
||||||
color: white;
|
|
||||||
|
|
||||||
left: 110%;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 1.1em;
|
width: 1.1em;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="historyItem in historyList" :key="historyItem.id">
|
<tr v-for="historyItem in historyList" :key="historyItem.id">
|
||||||
<td>
|
<td>
|
||||||
<router-link :to="`/journal/timetables?timetableId=${historyItem.id}`">
|
<router-link :to="`/journal/timetables?search-train=%23${historyItem.id}`">
|
||||||
#{{ historyItem.id }}
|
#{{ historyItem.id }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</td>
|
</td>
|
||||||
@@ -37,11 +37,16 @@
|
|||||||
{{ historyItem.trainNo }}
|
{{ historyItem.trainNo }}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ historyItem.route.replace('|', ' -> ') }}</td>
|
<td>{{ historyItem.route.replace('|', ' -> ') }}</td>
|
||||||
<td>{{ historyItem.driverName }}</td>
|
<td>
|
||||||
|
<router-link :to="`/journal/timetables?search-driver=${historyItem.driverName}`">
|
||||||
|
{{ historyItem.driverName }}
|
||||||
|
</router-link>
|
||||||
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<router-link
|
<router-link
|
||||||
v-if="historyItem.authorName"
|
v-if="historyItem.authorName"
|
||||||
:to="`/journal/timetables?authorName=${historyItem.authorName}`"
|
:to="`/journal/timetables?search-dispatcher=${historyItem.authorName}`"
|
||||||
>{{ historyItem.authorName }}
|
>{{ historyItem.authorName }}
|
||||||
</router-link>
|
</router-link>
|
||||||
<i v-else>{{ $t('scenery.timetable-author-unknown') }}</i>
|
<i v-else>{{ $t('scenery.timetable-author-unknown') }}</i>
|
||||||
@@ -63,35 +68,30 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import axios from 'axios';
|
|
||||||
import { defineComponent, PropType } from 'vue';
|
import { defineComponent, PropType } from 'vue';
|
||||||
import dateMixin from '../../mixins/dateMixin';
|
import dateMixin from '../../mixins/dateMixin';
|
||||||
|
|
||||||
import Station from '../../scripts/interfaces/Station';
|
|
||||||
import { URLs } from '../../scripts/utils/apiURLs';
|
|
||||||
import Loading from '../Global/Loading.vue';
|
import Loading from '../Global/Loading.vue';
|
||||||
import listObserverMixin from '../../mixins/listObserverMixin';
|
|
||||||
import { OnlineScenery } from '../../store/typings';
|
|
||||||
import { API } from '../../typings/api';
|
import { API } from '../../typings/api';
|
||||||
import { Status } from '../../typings/common';
|
import { ActiveScenery, Station, Status } from '../../typings/common';
|
||||||
|
import { useApiStore } from '../../store/apiStore';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'SceneryTimetablesHistory',
|
name: 'SceneryTimetablesHistory',
|
||||||
mixins: [dateMixin, listObserverMixin],
|
mixins: [dateMixin],
|
||||||
props: {
|
props: {
|
||||||
station: {
|
station: {
|
||||||
type: Object as PropType<Station>,
|
type: Object as PropType<Station>
|
||||||
required: true
|
|
||||||
},
|
},
|
||||||
onlineScenery: {
|
onlineScenery: {
|
||||||
type: Object as PropType<OnlineScenery>,
|
type: Object as PropType<ActiveScenery>
|
||||||
required: false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
historyList: [] as API.TimetableHistory.Response,
|
historyList: [] as API.TimetableHistory.Response,
|
||||||
|
apiStore: useApiStore(),
|
||||||
dataStatus: Status.Data.Loading,
|
dataStatus: Status.Data.Loading,
|
||||||
DataStatus: Status.Data
|
DataStatus: Status.Data
|
||||||
};
|
};
|
||||||
@@ -102,11 +102,20 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
async fetchAPIData(countFrom = 0, countLimit = 15) {
|
async fetchAPIData() {
|
||||||
try {
|
if (!this.station && !this.onlineScenery) {
|
||||||
const requestString = `${URLs.stacjownikAPI}/api/getTimetables?issuedFrom=${this.station.name}&countFrom=${countFrom}&countLimit=${countLimit}`;
|
this.dataStatus = Status.Data.Loaded;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const response: API.TimetableHistory.Response = await (await axios.get(requestString)).data;
|
try {
|
||||||
|
const response: API.TimetableHistory.Response = await (
|
||||||
|
await this.apiStore.client!.get('api/getTimetables', {
|
||||||
|
params: {
|
||||||
|
issuedFrom: this.station?.name || this.onlineScenery?.name
|
||||||
|
}
|
||||||
|
})
|
||||||
|
).data;
|
||||||
|
|
||||||
this.historyList = response;
|
this.historyList = response;
|
||||||
|
|
||||||
@@ -117,7 +126,12 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
navigateToHistory() {
|
navigateToHistory() {
|
||||||
this.$router.push(`/journal/timetables?issuedFrom=${this.station.name}`);
|
this.$router.push({
|
||||||
|
path: '/journal/timetables',
|
||||||
|
query: {
|
||||||
|
'search-issuedFrom': this.station?.name || this.onlineScenery?.name
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: { Loading }
|
components: { Loading }
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, PropType } from 'vue';
|
import { defineComponent, PropType } from 'vue';
|
||||||
import { ScheduledTrain, StopStatus } from '../../store/typings';
|
import { ScheduledTrain, StopStatus } from '../../typings/common';
|
||||||
|
|
||||||
interface ScheduledTrainComp extends ScheduledTrain {
|
interface ScheduledTrainComp extends ScheduledTrain {
|
||||||
stopStatusIndicator: string;
|
stopStatusIndicator: string;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<div class="card_controls">
|
<div class="card_controls">
|
||||||
<button class="btn--filled btn--image" @click="toggleCard">
|
<button class="btn--filled btn--image" @click="toggleCard">
|
||||||
<img class="button_icon" src="/images/icon-filter2.svg" alt="filter icon" />
|
<img class="button_icon" src="/images/icon-filter2.svg" alt="filter icon" />
|
||||||
{{ $t('options.filters') }} [F]
|
[F] {{ $t('options.filters') }}
|
||||||
<span class="active-indicator" v-if="!filterStore.areFiltersAtDefault"></span>
|
<span class="active-indicator" v-if="!filterStore.areFiltersAtDefault"></span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -28,8 +28,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<transition name="card-anim">
|
<transition name="card-anim">
|
||||||
<div class="card" v-if="isVisible" tabindex="0" ref="cardEl">
|
<div class="card" v-if="isVisible" tabindex="0" ref="cardRef" @keydown.r="resetFilters">
|
||||||
<div class="card_content">
|
<div class="card_content" @scroll="onScroll" ref="cardContentRef">
|
||||||
<div class="card_title flex">{{ $t('filters.title') }}</div>
|
<div class="card_title flex">{{ $t('filters.title') }}</div>
|
||||||
<p class="card_info" v-html="$t('filters.desc')"></p>
|
<p class="card_info" v-html="$t('filters.desc')"></p>
|
||||||
|
|
||||||
@@ -60,8 +60,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="card_timestamp" style="text-align: center">
|
<section class="card_timestamp">
|
||||||
<div>{{ $t('filters.minimum-hours-title') }}</div>
|
<h3 class="section-header">{{ $t('filters.minimum-hours-title') }}</h3>
|
||||||
|
|
||||||
<span class="clock">
|
<span class="clock">
|
||||||
<button class="btn--action" @click="subHour">-</button>
|
<button class="btn--action" @click="subHour">-</button>
|
||||||
<span>{{
|
<span>{{
|
||||||
@@ -75,16 +76,27 @@
|
|||||||
</span>
|
</span>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<datalist id="authors">
|
||||||
|
<option v-for="(author, i) in authors" :key="i" :value="author"></option>
|
||||||
|
</datalist>
|
||||||
|
|
||||||
<section class="card_authors-search">
|
<section class="card_authors-search">
|
||||||
|
<h3 class="section-header">{{ $t('filters.authors-search') }}</h3>
|
||||||
|
|
||||||
|
<form action="javascript:void(0);" @submit="handleAuthorsInput">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
:placeholder="$t('filters.authors-search')"
|
id="author"
|
||||||
|
list="authors"
|
||||||
name="authors"
|
name="authors"
|
||||||
|
:placeholder="$t('filters.authors-placeholder')"
|
||||||
v-model="authorsInputValue"
|
v-model="authorsInputValue"
|
||||||
@input="handleAuthorsInput"
|
|
||||||
@focus="preventKeyDown = true"
|
@focus="preventKeyDown = true"
|
||||||
@blur="preventKeyDown = false"
|
@blur="preventKeyDown = false"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<button class="btn--action">{{ $t('filters.authors-button-title') }}</button>
|
||||||
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="card_sliders">
|
<section class="card_sliders">
|
||||||
@@ -96,6 +108,7 @@
|
|||||||
:id="slider.id"
|
:id="slider.id"
|
||||||
:min="slider.minRange"
|
:min="slider.minRange"
|
||||||
:max="slider.maxRange"
|
:max="slider.maxRange"
|
||||||
|
:step="slider.step"
|
||||||
v-model="slider.value"
|
v-model="slider.value"
|
||||||
@change="handleInput"
|
@change="handleInput"
|
||||||
/>
|
/>
|
||||||
@@ -124,7 +137,7 @@
|
|||||||
:disabled="filterStore.areFiltersAtDefault"
|
:disabled="filterStore.areFiltersAtDefault"
|
||||||
:data-disabled="filterStore.areFiltersAtDefault"
|
:data-disabled="filterStore.areFiltersAtDefault"
|
||||||
>
|
>
|
||||||
{{ $t('filters.reset') }}
|
[R] {{ $t('filters.reset') }}
|
||||||
</button>
|
</button>
|
||||||
<button class="btn--action" @click="closeCard">{{ $t('filters.close') }}</button>
|
<button class="btn--action" @click="closeCard">{{ $t('filters.close') }}</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -139,7 +152,7 @@ import { defineComponent, inject } from 'vue';
|
|||||||
import keyMixin from '../../mixins/keyMixin';
|
import keyMixin from '../../mixins/keyMixin';
|
||||||
import routerMixin from '../../mixins/routerMixin';
|
import routerMixin from '../../mixins/routerMixin';
|
||||||
import { useStationFiltersStore } from '../../store/stationFiltersStore';
|
import { useStationFiltersStore } from '../../store/stationFiltersStore';
|
||||||
import { useStore } from '../../store/mainStore';
|
import { useMainStore } from '../../store/mainStore';
|
||||||
|
|
||||||
import FilterOption from './FilterOption.vue';
|
import FilterOption from './FilterOption.vue';
|
||||||
import StorageManager from '../../managers/storageManager';
|
import StorageManager from '../../managers/storageManager';
|
||||||
@@ -158,12 +171,15 @@ export default defineComponent({
|
|||||||
currentRegion: { id: '', value: '' },
|
currentRegion: { id: '', value: '' },
|
||||||
|
|
||||||
delayInputTimer: -1,
|
delayInputTimer: -1,
|
||||||
chosenSearchScenery: ''
|
chosenSearchScenery: '',
|
||||||
|
|
||||||
|
scrollTop: 0,
|
||||||
|
lastFocusedEl: null as HTMLElement | null
|
||||||
}),
|
}),
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
const isVisible = inject('isFilterCardVisible');
|
const isVisible = inject('isFilterCardVisible');
|
||||||
const store = useStore();
|
const store = useMainStore();
|
||||||
const filterStore = useStationFiltersStore();
|
const filterStore = useStationFiltersStore();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -196,6 +212,19 @@ export default defineComponent({
|
|||||||
|
|
||||||
currentOptionsActive() {
|
currentOptionsActive() {
|
||||||
return true;
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
authors() {
|
||||||
|
return this.store.stationList
|
||||||
|
.reduce((acc, station) => {
|
||||||
|
station.generalInfo?.authors?.forEach((author) => {
|
||||||
|
if (author.trim() != '' && !acc.includes(author.toLocaleLowerCase()))
|
||||||
|
acc.push(author.toLocaleLowerCase());
|
||||||
|
});
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, [] as string[])
|
||||||
|
.sort((a, b) => a.localeCompare(b));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -211,7 +240,10 @@ export default defineComponent({
|
|||||||
|
|
||||||
isVisible(value: boolean) {
|
isVisible(value: boolean) {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (value) (this.$refs['cardEl'] as HTMLDivElement).focus();
|
if (value) {
|
||||||
|
(this.$refs['cardRef'] as HTMLDivElement).focus();
|
||||||
|
(this.$refs['cardContentRef'] as HTMLDivElement).scrollTop = this.scrollTop;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -222,6 +254,10 @@ export default defineComponent({
|
|||||||
this.isVisible = !this.isVisible;
|
this.isVisible = !this.isVisible;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onScroll(e: Event) {
|
||||||
|
this.scrollTop = (e.target as HTMLElement).scrollTop;
|
||||||
|
},
|
||||||
|
|
||||||
handleInput(e: Event) {
|
handleInput(e: Event) {
|
||||||
const target = e.target as HTMLInputElement;
|
const target = e.target as HTMLInputElement;
|
||||||
|
|
||||||
@@ -230,12 +266,10 @@ export default defineComponent({
|
|||||||
if (this.saveOptions) StorageManager.setStringValue(target.name, target.value);
|
if (this.saveOptions) StorageManager.setStringValue(target.name, target.value);
|
||||||
},
|
},
|
||||||
|
|
||||||
handleAuthorsInput(e: Event) {
|
handleAuthorsInput() {
|
||||||
clearTimeout(this.delayInputTimer);
|
this.filterStore.changeFilterValue('authors', this.authorsInputValue);
|
||||||
|
|
||||||
this.delayInputTimer = window.setTimeout(() => {
|
if (this.saveOptions) StorageManager.setStringValue('authors', this.authorsInputValue);
|
||||||
this.handleInput(e);
|
|
||||||
}, 400);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
changeNumericFilterValue(name: string, value: number, saveToStorage = false) {
|
changeNumericFilterValue(name: string, value: number, saveToStorage = false) {
|
||||||
@@ -297,16 +331,22 @@ export default defineComponent({
|
|||||||
@import '../../styles/card.scss';
|
@import '../../styles/card.scss';
|
||||||
@import '../../styles/animations.scss';
|
@import '../../styles/animations.scss';
|
||||||
|
|
||||||
|
h3.section-header {
|
||||||
|
text-align: center;
|
||||||
|
margin: 0.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: 1fr auto;
|
grid-template-rows: 1fr auto;
|
||||||
|
}
|
||||||
|
|
||||||
&_info {
|
.card_info {
|
||||||
background-color: #111;
|
background-color: #111;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
&_controls {
|
.card_controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
|
|
||||||
@@ -314,9 +354,9 @@ export default defineComponent({
|
|||||||
border-radius: 0.5em 0.5em 0 0;
|
border-radius: 0.5em 0.5em 0 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&_content {
|
.card_content {
|
||||||
padding: 1em 0.5em;
|
padding: 1em 0.5em;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -324,17 +364,17 @@ export default defineComponent({
|
|||||||
|
|
||||||
gap: 1em;
|
gap: 1em;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
&_title {
|
.card_title {
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: $accentCol;
|
color: $accentCol;
|
||||||
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
&_regions {
|
.card_regions {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
@@ -354,9 +394,9 @@ export default defineComponent({
|
|||||||
background-color: seagreen;
|
background-color: seagreen;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&_timestamp {
|
.card_timestamp {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -367,7 +407,7 @@ export default defineComponent({
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
margin-top: 0.5em;
|
text-align: center;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
min-width: 120px;
|
min-width: 120px;
|
||||||
@@ -379,48 +419,39 @@ export default defineComponent({
|
|||||||
padding: 0.2em 0.6em;
|
padding: 0.2em 0.6em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&_modes {
|
.card_authors-search {
|
||||||
|
margin: 1em 0;
|
||||||
|
|
||||||
|
form {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
.option {
|
gap: 0.5em;
|
||||||
margin: 0 1em;
|
width: 100%;
|
||||||
|
margin-top: 1em;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
&_authors-search {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0 auto;
|
|
||||||
width: 60%;
|
|
||||||
min-width: 240px;
|
|
||||||
|
|
||||||
input {
|
input {
|
||||||
width: 100%;
|
width: 70%;
|
||||||
|
max-width: 400px;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
border: 1px solid white;
|
outline: 1px solid white;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&_actions {
|
.card_actions {
|
||||||
width: 100%;
|
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
|
|
||||||
.filter-option {
|
|
||||||
max-width: 50%;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-buttons {
|
.action-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
margin-top: 0.5em;
|
margin-top: 0.5em;
|
||||||
|
|
||||||
button {
|
button {
|
||||||
width: 50%;
|
width: 100%;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
|
|
||||||
@@ -429,7 +460,6 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.option-section h3 {
|
.option-section h3 {
|
||||||
@@ -447,7 +477,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
.section-inputs {
|
.section-inputs {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
grid-template-columns: repeat(3, 1fr);
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
margin: 1em 0;
|
margin: 1em 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
<template>
|
||||||
|
<div class="station-stats">
|
||||||
|
<div class="separator" />
|
||||||
|
<div>
|
||||||
|
{{ $t('station-stats.u-factor') }}
|
||||||
|
<a
|
||||||
|
href="https://td2.info.pl/dyskusje/wspolczynnik-ugla-czy-to-ma-sens/msg81011/#msg81011"
|
||||||
|
target="_blank"
|
||||||
|
:data-tooltip="$t('station-stats.u-factor-tooltip')"
|
||||||
|
>(?)</a
|
||||||
|
>:
|
||||||
|
<b :style="calculateFactorStyle()">
|
||||||
|
{{ uFactor.toFixed(2) }}
|
||||||
|
</b>
|
||||||
|
| {{ $t('station-stats.avg-timetable-count') }}
|
||||||
|
<b>{{ avgTimetableCount.toFixed(2) }}</b>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ $t('station-stats.single-track-count') }}
|
||||||
|
<b>{{ trackCount.oneWayElectric }}</b> {{ $t('station-stats.electrified') }} /
|
||||||
|
<b>{{ trackCount.oneWayOther }}</b> {{ $t('station-stats.not-electrified') }} |
|
||||||
|
{{ $t('station-stats.double-track-count') }} <b>{{ trackCount.twoWayElectric }}</b>
|
||||||
|
{{ $t('station-stats.electrified') }} / <b>{{ trackCount.twoWayOther }}</b>
|
||||||
|
{{ $t('station-stats.not-electrified') }} | {{ $t('station-stats.open-spawns') }}
|
||||||
|
<b>{{ spawnCount.passenger }}</b> - PAS / <b>{{ spawnCount.freight }}</b> - TOW /
|
||||||
|
<b>{{ spawnCount.loco }}</b> - LUZ / <b>{{ spawnCount.all }}</b> - ALL
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import { useMainStore } from '../../store/mainStore';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
mainStore: useMainStore()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
calculateFactorStyle() {
|
||||||
|
if (this.uFactor == 0) return '';
|
||||||
|
|
||||||
|
const norm = this.uFactor == 0 ? 1 : Math.max(Math.min(this.uFactor / 2, 1), 0);
|
||||||
|
const lerp = 120 * norm;
|
||||||
|
|
||||||
|
return `color: hsl(${lerp}, 100%, 60%)`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
uFactor() {
|
||||||
|
const activeDispatchers = this.mainStore.activeSceneryList.filter(
|
||||||
|
(scenery) => scenery.region == this.mainStore.region.id && scenery.dispatcherId != -1
|
||||||
|
);
|
||||||
|
|
||||||
|
const activeTrains = this.mainStore.trainList.filter(
|
||||||
|
(train) => train.region == this.mainStore.region.id
|
||||||
|
);
|
||||||
|
|
||||||
|
return activeDispatchers.length != 0 ? activeTrains.length / activeDispatchers.length : 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
avgTimetableCount() {
|
||||||
|
const scheduledTrainsTotal = this.mainStore.activeSceneryList.reduce<number>((acc, sc) => {
|
||||||
|
if (sc.region != this.mainStore.region.id) return acc;
|
||||||
|
|
||||||
|
acc += sc.scheduledTrainCount.all;
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
return this.mainStore.activeSceneryList.length != 0
|
||||||
|
? scheduledTrainsTotal / this.mainStore.activeSceneryList.length
|
||||||
|
: 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
trackCount() {
|
||||||
|
return this.mainStore.allStationInfo
|
||||||
|
.filter(
|
||||||
|
(st) =>
|
||||||
|
st.onlineInfo?.dispatcherId != -1 &&
|
||||||
|
st.onlineInfo?.region == this.mainStore.region.id &&
|
||||||
|
st.generalInfo?.routes
|
||||||
|
)
|
||||||
|
.reduce(
|
||||||
|
(acc, st) => {
|
||||||
|
[...st.generalInfo!.routes.single, ...st.generalInfo!.routes.double].forEach((r) => {
|
||||||
|
if (r.isInternal) return;
|
||||||
|
|
||||||
|
const keyName: keyof typeof acc = `${r.routeTracks == 2 ? 'twoWay' : 'oneWay'}${r.isElectric ? 'Electric' : 'Other'}`;
|
||||||
|
|
||||||
|
acc[keyName] += 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{ oneWayElectric: 0, oneWayOther: 0, twoWayElectric: 0, twoWayOther: 0 }
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
spawnCount() {
|
||||||
|
return this.mainStore.activeSceneryList.reduce(
|
||||||
|
(acc, scenery) => {
|
||||||
|
scenery.spawns.forEach((spawn) => {
|
||||||
|
if (/EZT|POS|OSOB/i.test(spawn.spawnName)) acc['passenger'] += 1;
|
||||||
|
if (/TOW/i.test(spawn.spawnName)) acc['freight'] += 1;
|
||||||
|
if (/LUZ|SM/i.test(spawn.spawnName)) acc['loco'] += 1;
|
||||||
|
if (/ALL/i.test(spawn.spawnName)) acc['all'] += 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{ passenger: 0, freight: 0, loco: 0, all: 0 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.separator {
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
margin: 0.5em 0;
|
||||||
|
background-color: #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.station-stats {
|
||||||
|
text-align: center;
|
||||||
|
color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-factor-low='true'] {
|
||||||
|
color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-factor-mediocre='true'] {
|
||||||
|
color: lightgreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-factor-high='true'] {
|
||||||
|
color: greenyellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-factor-highest='true'] {
|
||||||
|
color: rgb(22, 245, 22);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="station_table">
|
<section class="station_table">
|
||||||
<div class="table_wrapper">
|
<Loading
|
||||||
|
v-if="apiStore.dataStatuses.connection == Status.Loading && displayedStations.length == 0"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="table_wrapper" v-else-if="displayedStations.length > 0">
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -9,9 +13,10 @@
|
|||||||
:key="headerName"
|
:key="headerName"
|
||||||
@click="changeSorter(headerName)"
|
@click="changeSorter(headerName)"
|
||||||
class="header-text"
|
class="header-text"
|
||||||
|
:class="headerName"
|
||||||
>
|
>
|
||||||
<span class="header_wrapper">
|
<span class="header_wrapper">
|
||||||
<div v-html="$t(`sceneries.${headerName}`)"></div>
|
<div v-html="$t(`sceneries.headers.${headerName}`)"></div>
|
||||||
|
|
||||||
<img
|
<img
|
||||||
class="sort-icon"
|
class="sort-icon"
|
||||||
@@ -27,12 +32,13 @@
|
|||||||
:key="headerName"
|
:key="headerName"
|
||||||
@click="changeSorter(headerName)"
|
@click="changeSorter(headerName)"
|
||||||
class="header-image"
|
class="header-image"
|
||||||
|
:class="headerName"
|
||||||
>
|
>
|
||||||
<span class="header_wrapper">
|
<span class="header_wrapper">
|
||||||
<img
|
<img
|
||||||
:src="`/images/icon-${headerName}.svg`"
|
:src="`/images/icon-${headerName}.svg`"
|
||||||
:alt="headerName"
|
:alt="headerName"
|
||||||
:title="$t(`sceneries.${headerName}`)"
|
:title="$t(`sceneries.headers.${headerName}`)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<img
|
<img
|
||||||
@@ -48,24 +54,23 @@
|
|||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr
|
<tr
|
||||||
class="station"
|
v-for="station in displayedStations"
|
||||||
:class="{ 'last-selected': lastSelectedStationName == station.name }"
|
:class="{ 'last-selected': lastSelectedStationName == station.name }"
|
||||||
v-for="(station, i) in stations"
|
:key="station.name"
|
||||||
:key="i + station.name"
|
|
||||||
@click.left="setScenery(station.name)"
|
@click.left="setScenery(station.name)"
|
||||||
@click.right="openForumSite($event, station.generalInfo?.url)"
|
@click.right="openForumSite($event, station.generalInfo?.url)"
|
||||||
@keydown.enter="setScenery(station.name)"
|
@keydown.enter="setScenery(station.name)"
|
||||||
@keydown.space="openForumSite($event, station.generalInfo?.url)"
|
@keydown.space="openForumSite($event, station.generalInfo?.url)"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<td class="station_name" :class="station.generalInfo?.availability">
|
<td class="station-name" :class="station.generalInfo?.availability">
|
||||||
<b v-if="station.generalInfo?.project" style="color: salmon">{{
|
<b v-if="station.generalInfo?.project" style="color: salmon">{{
|
||||||
station.generalInfo.project
|
station.generalInfo.project
|
||||||
}}</b>
|
}}</b>
|
||||||
{{ station.name }}
|
{{ station.name }}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="station_level">
|
<td class="station-level">
|
||||||
<span v-if="station.generalInfo">
|
<span v-if="station.generalInfo">
|
||||||
<span
|
<span
|
||||||
v-if="
|
v-if="
|
||||||
@@ -82,7 +87,7 @@
|
|||||||
<img
|
<img
|
||||||
src="/images/icon-abandoned.svg"
|
src="/images/icon-abandoned.svg"
|
||||||
alt="non-public"
|
alt="non-public"
|
||||||
:title="$t('desc.abandoned')"
|
:title="$t('sceneries.info.abandoned')"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@@ -90,7 +95,7 @@
|
|||||||
<img
|
<img
|
||||||
src="/images/icon-lock.svg"
|
src="/images/icon-lock.svg"
|
||||||
alt="non-public"
|
alt="non-public"
|
||||||
:title="$t('desc.non-public')"
|
:title="$t('sceneries.info.non-public')"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@@ -98,7 +103,7 @@
|
|||||||
<img
|
<img
|
||||||
src="/images/icon-unavailable.svg"
|
src="/images/icon-unavailable.svg"
|
||||||
alt="unavailable"
|
alt="unavailable"
|
||||||
:title="$t('desc.unavailable')"
|
:title="$t('sceneries.info.unavailable')"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -106,19 +111,20 @@
|
|||||||
<span v-else> ? </span>
|
<span v-else> ? </span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="station_status">
|
<td class="station-status">
|
||||||
<StationStatusBadge
|
<StationStatusBadge
|
||||||
:isOnline="station.onlineInfo ? true : false"
|
:isOnline="station.onlineInfo ? true : false"
|
||||||
:dispatcherStatus="station.onlineInfo?.dispatcherStatus"
|
:dispatcherStatus="station.onlineInfo?.dispatcherStatus"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="station_dispatcher-name">
|
<td class="station-dispatcher-name">
|
||||||
<span v-if="station.onlineInfo?.dispatcherName">
|
<span v-if="station.onlineInfo?.dispatcherName">
|
||||||
<b
|
<b
|
||||||
v-if="store.donatorsData.includes(station.onlineInfo.dispatcherName)"
|
v-if="apiStore.donatorsData.includes(station.onlineInfo.dispatcherName)"
|
||||||
:title="$t('donations.dispatcher-message')"
|
|
||||||
@click.stop="openDonationModal"
|
@click.stop="openDonationModal"
|
||||||
|
data-tooltip-type="DonatorTooltip"
|
||||||
|
:data-tooltip-content="$t('donations.dispatcher-message')"
|
||||||
>
|
>
|
||||||
<img src="/images/icon-diamond.svg" alt="" />
|
<img src="/images/icon-diamond.svg" alt="" />
|
||||||
{{ station.onlineInfo.dispatcherName }}
|
{{ station.onlineInfo.dispatcherName }}
|
||||||
@@ -130,9 +136,9 @@
|
|||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="station_dispatcher-exp">
|
<td class="station-dispatcher-exp">
|
||||||
<span
|
<span
|
||||||
v-if="station.onlineInfo"
|
v-if="station.onlineInfo && station.onlineInfo?.dispatcherExp != -1"
|
||||||
:style="
|
:style="
|
||||||
calculateExpStyle(
|
calculateExpStyle(
|
||||||
station.onlineInfo.dispatcherExp,
|
station.onlineInfo.dispatcherExp,
|
||||||
@@ -144,173 +150,181 @@
|
|||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="station_tracks twoway">
|
<td class="station-tracks">
|
||||||
|
<div v-if="station.generalInfo">
|
||||||
<span
|
<span
|
||||||
v-if="
|
v-if="station.generalInfo.routes.singleElectrifiedNames.length != 0"
|
||||||
station.generalInfo &&
|
|
||||||
station.generalInfo.routes.twoWayCatenaryRouteNames.length > 0
|
|
||||||
"
|
|
||||||
class="track catenary"
|
class="track catenary"
|
||||||
:title="`Liczba zelektryfikowanych szlaków dwutorowych: ${station.generalInfo.routes.twoWayCatenaryRouteNames.length}`"
|
:title="`${$t('sceneries.info.single-track-routes-catenary')}${
|
||||||
|
station.generalInfo.routes.singleElectrifiedNames.length
|
||||||
|
}`"
|
||||||
>
|
>
|
||||||
{{ station.generalInfo.routes.twoWayCatenaryRouteNames.length }}
|
{{ station.generalInfo.routes.singleElectrifiedNames.length }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span
|
<span
|
||||||
v-if="
|
v-if="station.generalInfo.routes.singleOtherNames.length != 0"
|
||||||
station.generalInfo &&
|
|
||||||
station.generalInfo.routes.twoWayNoCatenaryRouteNames.length > 0
|
|
||||||
"
|
|
||||||
class="track no-catenary"
|
class="track no-catenary"
|
||||||
:title="`Liczba niezelektryfikowanych szlaków dwutorowych: ${station.generalInfo.routes.twoWayNoCatenaryRouteNames.length}`"
|
:title="`${$t('sceneries.info.single-track-routes-other')}${
|
||||||
|
station.generalInfo.routes.singleOtherNames.length
|
||||||
|
}`"
|
||||||
>
|
>
|
||||||
{{ station.generalInfo.routes.twoWayNoCatenaryRouteNames.length }}
|
{{ station.generalInfo.routes.singleOtherNames.length }}
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="separator"></span>
|
|
||||||
|
|
||||||
<span
|
|
||||||
v-if="
|
|
||||||
station.generalInfo &&
|
|
||||||
station.generalInfo.routes.oneWayCatenaryRouteNames.length > 0
|
|
||||||
"
|
|
||||||
class="track catenary"
|
|
||||||
:title="`Liczba zelektryfikowanych szlaków jednotorowych: ${station.generalInfo.routes.oneWayCatenaryRouteNames.length}`"
|
|
||||||
>
|
|
||||||
{{ station.generalInfo.routes.oneWayCatenaryRouteNames.length }}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span
|
|
||||||
v-if="
|
|
||||||
station.generalInfo &&
|
|
||||||
station.generalInfo.routes.oneWayNoCatenaryRouteNames.length > 0
|
|
||||||
"
|
|
||||||
class="track no-catenary"
|
|
||||||
:title="`Liczba niezelektryfikowanych szlaków jednotorowych: ${station.generalInfo.routes.oneWayNoCatenaryRouteNames.length}`"
|
|
||||||
>
|
|
||||||
{{ station.generalInfo.routes.oneWayNoCatenaryRouteNames.length }}
|
|
||||||
</span>
|
</span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="station_info" v-if="station.generalInfo">
|
<td class="station-tracks">
|
||||||
|
<div v-if="station.generalInfo">
|
||||||
<span
|
<span
|
||||||
class="scenery-icon icon-info"
|
v-if="station.generalInfo.routes.doubleElectrifiedNames.length != 0"
|
||||||
:class="station.generalInfo.controlType.replace('+', '-')"
|
class="track catenary"
|
||||||
:title="$t('desc.control-type') + $t(`controls.${station.generalInfo.controlType}`)"
|
:title="`${$t('sceneries.info.double-track-routes-catenary')}${
|
||||||
v-html="getControlTypeAbbrev(station.generalInfo.controlType)"
|
station.generalInfo.routes.doubleElectrifiedNames.length
|
||||||
|
}`"
|
||||||
>
|
>
|
||||||
|
{{ station.generalInfo.routes.doubleElectrifiedNames.length }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span>
|
<span
|
||||||
<img
|
v-if="station.generalInfo.routes.doubleOtherNames.length != 0"
|
||||||
class="icon-info"
|
class="track no-catenary"
|
||||||
v-if="station.generalInfo.SUP"
|
:title="`${$t('sceneries.info.double-track-routes-other')}${
|
||||||
src="/images/icon-SUP.svg"
|
station.generalInfo.routes.doubleOtherNames.length
|
||||||
alt="SUP (RASP-UZK)"
|
}`"
|
||||||
:title="$t('desc.SUP')"
|
>
|
||||||
/>
|
{{ station.generalInfo.routes.doubleOtherNames.length }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="station-info">
|
||||||
|
<span
|
||||||
|
v-if="station.generalInfo?.signalType"
|
||||||
|
class="scenery-icon icon-info"
|
||||||
|
:class="station.generalInfo?.controlType.replace('+', '-')"
|
||||||
|
:title="
|
||||||
|
$t('sceneries.info.control-type') +
|
||||||
|
$t(`controls.${station.generalInfo?.controlType}`)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ $t(`controls.abbrevs.${station.generalInfo.controlType}`) }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span>
|
|
||||||
<img
|
<img
|
||||||
|
v-if="station.generalInfo?.signalType"
|
||||||
class="icon-info"
|
class="icon-info"
|
||||||
v-if="station.generalInfo.signalType"
|
|
||||||
:src="`/images/icon-${station.generalInfo.signalType}.svg`"
|
:src="`/images/icon-${station.generalInfo.signalType}.svg`"
|
||||||
:alt="station.generalInfo.signalType"
|
:alt="station.generalInfo.signalType"
|
||||||
:title="$t('desc.signals-type') + $t(`signals.${station.generalInfo.signalType}`)"
|
:title="
|
||||||
|
$t('sceneries.info.signals-type') +
|
||||||
|
$t(`signals.${station.generalInfo.signalType}`)
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</span>
|
|
||||||
|
|
||||||
<span>
|
|
||||||
<img
|
<img
|
||||||
|
v-if="station.generalInfo?.SUP"
|
||||||
class="icon-info"
|
class="icon-info"
|
||||||
v-if="station.generalInfo && station.generalInfo.routes.sblRouteNames.length > 0"
|
src="/images/icon-SUP.svg"
|
||||||
src="/images/icon-SBL.svg"
|
alt="SUP (RASP-UZK)"
|
||||||
alt="SBL"
|
:title="$t('sceneries.info.SUP')"
|
||||||
:title="$t('desc.SBL') + `${station.generalInfo.routes.sblRouteNames.join(',')}`"
|
|
||||||
/>
|
/>
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td class="station_info" v-else>
|
|
||||||
<img
|
<img
|
||||||
|
v-if="station.generalInfo?.ASDEK"
|
||||||
|
class="icon-info"
|
||||||
|
src="/images/icon-ASDEK.svg"
|
||||||
|
alt="dSAT ASDEK"
|
||||||
|
:title="$t('sceneries.info.ASDEK')"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<img
|
||||||
|
v-if="!station.generalInfo"
|
||||||
class="icon-info"
|
class="icon-info"
|
||||||
src="/images/icon-unknown.svg"
|
src="/images/icon-unknown.svg"
|
||||||
alt="icon-unknown"
|
alt="icon-unknown"
|
||||||
:title="$t('desc.unknown')"
|
:title="$t('sceneries.info.unknown')"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="station_users" :class="{ inactive: !station.onlineInfo }">
|
<td
|
||||||
<span>{{ station.onlineInfo?.currentUsers || 0 }}</span>
|
class="station-users"
|
||||||
|
:class="{ inactive: !station.onlineInfo }"
|
||||||
|
data-tooltip-type="UsersTooltip"
|
||||||
|
:data-tooltip-content="JSON.stringify(station.onlineInfo?.stationTrains ?? [])"
|
||||||
|
>
|
||||||
|
<span class="text--primary">{{
|
||||||
|
station.onlineInfo?.stationTrains?.length ?? '-'
|
||||||
|
}}</span>
|
||||||
/
|
/
|
||||||
<span>{{ station.onlineInfo?.maxUsers || 0 }}</span>
|
<span class="text--primary">{{ station.onlineInfo?.maxUsers ?? '-' }}</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="station_spawns" :class="{ inactive: !station.onlineInfo }">
|
<td class="station-likes" :class="{ inactive: !station.onlineInfo }">
|
||||||
<span>{{ station.onlineInfo?.spawns.length || 0 }}</span>
|
<span>{{ station.onlineInfo?.dispatcherRate ?? '-' }}</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td
|
<td
|
||||||
class="station_schedules all"
|
class="station-spawns"
|
||||||
style="width: 30px"
|
|
||||||
:class="{ inactive: !station.onlineInfo }"
|
:class="{ inactive: !station.onlineInfo }"
|
||||||
|
data-tooltip-type="SpawnsTooltip"
|
||||||
|
:data-tooltip-content="JSON.stringify(station.onlineInfo?.spawns ?? [])"
|
||||||
>
|
>
|
||||||
{{ station.onlineInfo?.scheduledTrainCount.all }}
|
<span>{{ station.onlineInfo?.spawns.length ?? '-' }}</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td
|
<td
|
||||||
class="station_schedules unconfirmed"
|
class="station-schedules all"
|
||||||
style="width: 30px"
|
style="width: 30px"
|
||||||
:class="{ inactive: !station.onlineInfo }"
|
:class="{ inactive: !station.onlineInfo }"
|
||||||
>
|
>
|
||||||
{{ station.onlineInfo?.scheduledTrainCount.unconfirmed }}
|
{{ station.onlineInfo?.scheduledTrainCount.all ?? '-' }}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td
|
<td
|
||||||
class="station_schedules confirmed"
|
class="station-schedules unconfirmed"
|
||||||
style="width: 30px"
|
style="width: 30px"
|
||||||
:class="{ inactive: !station.onlineInfo }"
|
:class="{ inactive: !station.onlineInfo }"
|
||||||
>
|
>
|
||||||
{{ station.onlineInfo?.scheduledTrainCount.confirmed }}
|
{{ station.onlineInfo?.scheduledTrainCount.unconfirmed ?? '-' }}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td
|
||||||
|
class="station-schedules confirmed"
|
||||||
|
style="width: 30px"
|
||||||
|
:class="{ inactive: !station.onlineInfo }"
|
||||||
|
>
|
||||||
|
{{ station.onlineInfo?.scheduledTrainCount.confirmed ?? '-' }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Loading v-if="!isDataLoaded && stations.length == 0" />
|
<div class="no-stations" v-else>
|
||||||
|
{{ $t('sceneries.no-stations') }} (region: <b>{{ mainStore.region.name }}</b
|
||||||
<div class="no-stations" v-else-if="stations.length == 0">
|
>)
|
||||||
{{ $t('sceneries.no-stations') }}
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, computed, PropType } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import dateMixin from '../../mixins/dateMixin';
|
import dateMixin from '../../mixins/dateMixin';
|
||||||
import stationInfoMixin from '../../mixins/stationInfoMixin';
|
|
||||||
import styleMixin from '../../mixins/styleMixin';
|
import styleMixin from '../../mixins/styleMixin';
|
||||||
import Station from '../../scripts/interfaces/Station';
|
|
||||||
import { useStationFiltersStore } from '../../store/stationFiltersStore';
|
import { useStationFiltersStore } from '../../store/stationFiltersStore';
|
||||||
import { useStore } from '../../store/mainStore';
|
import { useMainStore } from '../../store/mainStore';
|
||||||
import Loading from '../Global/Loading.vue';
|
import Loading from '../Global/Loading.vue';
|
||||||
import { HeadIdsTypes, headIconsIds, headIds } from '../../scripts/data/stationHeaderNames';
|
import { HeadIdsTypes, headIconsIds, headIds } from '../../scripts/data/stationHeaderNames';
|
||||||
import StationStatusBadge from '../Global/StationStatusBadge.vue';
|
import StationStatusBadge from '../Global/StationStatusBadge.vue';
|
||||||
import { Status } from '../../typings/common';
|
import { Station, Status } from '../../typings/common';
|
||||||
|
import { useApiStore } from '../../store/apiStore';
|
||||||
|
import { useTooltipStore } from '../../store/tooltipStore';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
|
||||||
stations: {
|
|
||||||
type: Array as PropType<Station[]>,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
emits: ['toggleDonationModal'],
|
emits: ['toggleDonationModal'],
|
||||||
components: { Loading, StationStatusBadge },
|
components: { Loading, StationStatusBadge },
|
||||||
mixins: [styleMixin, dateMixin, stationInfoMixin],
|
mixins: [styleMixin, dateMixin],
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
headIconsIds,
|
headIconsIds,
|
||||||
@@ -321,30 +335,37 @@ export default defineComponent({
|
|||||||
computed: {
|
computed: {
|
||||||
sorterActive() {
|
sorterActive() {
|
||||||
return this.stationFiltersStore.sorterActive;
|
return this.stationFiltersStore.sorterActive;
|
||||||
|
},
|
||||||
|
|
||||||
|
displayedStations() {
|
||||||
|
return this.stationFiltersStore.filteredStationList;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
const store = useStore();
|
const mainStore = useMainStore();
|
||||||
|
const apiStore = useApiStore();
|
||||||
|
const tooltipStore = useTooltipStore();
|
||||||
|
|
||||||
const stationFiltersStore = useStationFiltersStore();
|
const stationFiltersStore = useStationFiltersStore();
|
||||||
|
|
||||||
const isDataLoaded = computed(() => {
|
|
||||||
return store.dataStatuses.sceneries != Status.Data.Loading;
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isDataLoaded,
|
Status: Status.Data,
|
||||||
stationFiltersStore,
|
stationFiltersStore,
|
||||||
store
|
mainStore,
|
||||||
|
apiStore,
|
||||||
|
tooltipStore
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
setScenery(name: string) {
|
setScenery(name: string) {
|
||||||
const station = this.stations.find((station) => station.name === name);
|
const station = this.displayedStations.find((station) => station.name === name);
|
||||||
|
|
||||||
if (!station) return;
|
if (!station) return;
|
||||||
|
|
||||||
this.lastSelectedStationName = station.name;
|
this.lastSelectedStationName = station.name;
|
||||||
|
this.tooltipStore.hide();
|
||||||
|
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
name: 'SceneryView',
|
name: 'SceneryView',
|
||||||
@@ -357,7 +378,8 @@ export default defineComponent({
|
|||||||
|
|
||||||
openDonationModal(e: Event) {
|
openDonationModal(e: Event) {
|
||||||
this.$emit('toggleDonationModal', true);
|
this.$emit('toggleDonationModal', true);
|
||||||
this.store.modalLastClickedTarget = e.target;
|
this.mainStore.modalLastClickedTarget = e.target;
|
||||||
|
this.tooltipStore.hide();
|
||||||
},
|
},
|
||||||
|
|
||||||
openForumSite(e: Event, url: string | undefined) {
|
openForumSite(e: Event, url: string | undefined) {
|
||||||
@@ -367,7 +389,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
changeSorter(headerName: HeadIdsTypes) {
|
changeSorter(headerName: HeadIdsTypes) {
|
||||||
if (headerName == 'general' || headerName == 'routes') return;
|
if (headerName == 'general') return;
|
||||||
|
|
||||||
this.stationFiltersStore.changeSorter(headerName);
|
this.stationFiltersStore.changeSorter(headerName);
|
||||||
}
|
}
|
||||||
@@ -382,33 +404,28 @@ export default defineComponent({
|
|||||||
|
|
||||||
$rowCol: #424242;
|
$rowCol: #424242;
|
||||||
|
|
||||||
.change-anim {
|
.station_table {
|
||||||
&-enter-active,
|
height: 80vh;
|
||||||
&-leave-active {
|
min-height: 550px;
|
||||||
transition: opacity 100ms ease-in;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-enter,
|
|
||||||
&-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.table_wrapper {
|
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
overflow-y: hidden;
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
table {
|
.no-stations {
|
||||||
white-space: nowrap;
|
text-align: center;
|
||||||
border-collapse: collapse;
|
font-size: 1.5em;
|
||||||
// min-width: 1350px;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
@include smallScreen() {
|
padding: 1em;
|
||||||
min-width: auto;
|
|
||||||
}
|
background: #1a1a1a;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
table-layout: fixed;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 1250px;
|
||||||
|
white-space: wrap;
|
||||||
|
|
||||||
thead tr {
|
thead tr {
|
||||||
background-color: $bgCol;
|
background-color: $bgCol;
|
||||||
@@ -418,12 +435,41 @@ table {
|
|||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
||||||
&.header-text {
|
&.station {
|
||||||
min-width: 140px;
|
width: 12em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.min-lvl {
|
||||||
|
width: 4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.status {
|
||||||
|
width: 10em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.dispatcher {
|
||||||
|
width: 12em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.dispatcher-lvl {
|
||||||
|
width: 6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.routes-double,
|
||||||
|
&.routes-single {
|
||||||
|
width: 7em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.general {
|
||||||
|
width: 11em;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.header-image {
|
&.header-image {
|
||||||
min-width: 60px;
|
width: 3.5em;
|
||||||
|
|
||||||
|
&.user {
|
||||||
|
width: 5em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
padding: 0.5em 0.25em;
|
padding: 0.5em 0.25em;
|
||||||
@@ -448,7 +494,7 @@ table {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tr.station {
|
tr {
|
||||||
background-color: $rowCol;
|
background-color: $rowCol;
|
||||||
|
|
||||||
&:nth-child(even) {
|
&:nth-child(even) {
|
||||||
@@ -462,10 +508,15 @@ tr.station {
|
|||||||
}
|
}
|
||||||
|
|
||||||
td {
|
td {
|
||||||
padding: 0.25em 1em;
|
padding: 0.15em 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
|
&.inactive {
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
@include smallScreen() {
|
@include smallScreen() {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -475,9 +526,9 @@ tr.station {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
td.station {
|
.station-name {
|
||||||
&_name {
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
max-width: 200px;
|
||||||
|
|
||||||
&.default {
|
&.default {
|
||||||
color: $accentCol;
|
color: $accentCol;
|
||||||
@@ -491,10 +542,10 @@ td.station {
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #bebebe;
|
color: #bebebe;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&_level,
|
.station-level,
|
||||||
&_dispatcher-exp {
|
.station-dispatcher-exp {
|
||||||
span {
|
span {
|
||||||
display: block;
|
display: block;
|
||||||
|
|
||||||
@@ -508,53 +559,38 @@ td.station {
|
|||||||
width: 2em;
|
width: 2em;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// &_dispatcher-name {
|
.station-dispatcher-name {
|
||||||
// position: relative;
|
img {
|
||||||
// }
|
|
||||||
|
|
||||||
&_dispatcher-name img {
|
|
||||||
max-width: 1.35em;
|
max-width: 1.35em;
|
||||||
vertical-align: text-bottom;
|
vertical-align: text-bottom;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&_level {
|
.station-level {
|
||||||
span {
|
span {
|
||||||
background-color: #888;
|
background-color: #888;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&_info {
|
.station-info {
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
/* Images */
|
|
||||||
.icon-info {
|
.icon-info {
|
||||||
display: flex;
|
vertical-align: middle;
|
||||||
justify-content: center;
|
line-height: 2.5em;
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
width: 32px;
|
width: 2.5em;
|
||||||
height: 32px;
|
height: 2.5em;
|
||||||
font-size: 12px;
|
font-size: 0.8em;
|
||||||
|
margin: 0 3px;
|
||||||
|
|
||||||
margin: 0 0.2em;
|
outline: 2px solid #2b2b2b;
|
||||||
|
border-radius: 5px;
|
||||||
outline: 2px solid #444;
|
|
||||||
border-radius: 0.5em;
|
|
||||||
|
|
||||||
@include smallScreen() {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
font-size: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&_tracks {
|
.station-tracks {
|
||||||
.no-catenary {
|
.no-catenary {
|
||||||
background-color: #939393;
|
background-color: #939393;
|
||||||
}
|
}
|
||||||
@@ -563,30 +599,22 @@ td.station {
|
|||||||
background-color: #009dce;
|
background-color: #009dce;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.separator {
|
||||||
|
background-color: #b3b3b3;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
.track {
|
.track {
|
||||||
margin: 0 0.35em;
|
display: inline-block;
|
||||||
padding: 0.35em;
|
text-align: center;
|
||||||
font-size: 1.05em;
|
width: 1.3em;
|
||||||
white-space: pre-wrap;
|
padding: 0.35em 0;
|
||||||
}
|
font-size: 1.1em;
|
||||||
}
|
margin: 0 0.2em;
|
||||||
|
|
||||||
&_users,
|
|
||||||
&_spawns,
|
|
||||||
&_schedules {
|
|
||||||
&.inactive {
|
|
||||||
opacity: 0.2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.station_users {
|
.station-schedules {
|
||||||
span {
|
|
||||||
color: gold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.station_schedules {
|
|
||||||
&.all {
|
&.all {
|
||||||
color: gold;
|
color: gold;
|
||||||
}
|
}
|
||||||
@@ -599,18 +627,4 @@ td.station {
|
|||||||
color: lime;
|
color: lime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.separator {
|
|
||||||
border-left: 3px solid #b3b3b3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-stations {
|
|
||||||
text-align: center;
|
|
||||||
font-size: 1.5em;
|
|
||||||
|
|
||||||
padding: 1em;
|
|
||||||
margin: 1em 0;
|
|
||||||
|
|
||||||
background: #333;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ export interface Filter {
|
|||||||
SPE: boolean;
|
SPE: boolean;
|
||||||
SUP: boolean;
|
SUP: boolean;
|
||||||
noSUP: boolean;
|
noSUP: boolean;
|
||||||
|
ASDEK: boolean;
|
||||||
|
noASDEK: boolean;
|
||||||
ręczne: boolean;
|
ręczne: boolean;
|
||||||
'ręczne+SPK': boolean;
|
'ręczne+SPK': boolean;
|
||||||
'ręczne+SCS': boolean;
|
'ręczne+SCS': boolean;
|
||||||
@@ -34,6 +36,8 @@ export interface Filter {
|
|||||||
minOneWay: number;
|
minOneWay: number;
|
||||||
minTwoWayCatenary: number;
|
minTwoWayCatenary: number;
|
||||||
minTwoWay: number;
|
minTwoWay: number;
|
||||||
|
minVmax: number;
|
||||||
|
maxVmax: number;
|
||||||
'no-1track': boolean;
|
'no-1track': boolean;
|
||||||
'no-2track': boolean;
|
'no-2track': boolean;
|
||||||
'include-selected': boolean;
|
'include-selected': boolean;
|
||||||
@@ -42,14 +46,13 @@ export interface Filter {
|
|||||||
nonPublic: boolean;
|
nonPublic: boolean;
|
||||||
unavailable: boolean;
|
unavailable: boolean;
|
||||||
abandoned: boolean;
|
abandoned: boolean;
|
||||||
|
|
||||||
endingStatus: boolean;
|
endingStatus: boolean;
|
||||||
afkStatus: boolean;
|
afkStatus: boolean;
|
||||||
noSpaceStatus: boolean;
|
noSpaceStatus: boolean;
|
||||||
unavailableStatus: boolean;
|
unavailableStatus: boolean;
|
||||||
unsignedStatus: boolean;
|
unsignedStatus: boolean;
|
||||||
|
|
||||||
authors: string;
|
authors: string;
|
||||||
|
|
||||||
onlineFromHours: number;
|
onlineFromHours: number;
|
||||||
|
withActiveTimetables: boolean;
|
||||||
|
withoutActiveTimetables: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<template>
|
||||||
|
<div class="tooltip-content">
|
||||||
|
<span>{{ tooltipStore.content }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import { useTooltipStore } from '../../store/tooltipStore';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tooltipStore: useTooltipStore()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.tooltip-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5em;
|
||||||
|
|
||||||
|
padding: 0.25em 0.5em;
|
||||||
|
border-radius: 0.25em;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
background-color: #333;
|
||||||
|
box-shadow: 0 0 5px 2px #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 1em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<template>
|
||||||
|
<div class="tooltip-content">
|
||||||
|
<img src="/images/icon-diamond.svg" alt="" />
|
||||||
|
<span>{{ tooltipStore.content }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import { useTooltipStore } from '../../store/tooltipStore';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tooltipStore: useTooltipStore()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.tooltip-content {
|
||||||
|
gap: 0.5em;
|
||||||
|
|
||||||
|
padding: 0.5em;
|
||||||
|
border-radius: 0.25em;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
background-color: #333;
|
||||||
|
box-shadow: 0 0 10px 2px #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
vertical-align: middle;
|
||||||
|
height: 1em;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<template>
|
||||||
|
<div class="tooltip-content" v-if="spawns.length != 0">
|
||||||
|
<span v-for="(spawn, i) in spawns">
|
||||||
|
<template v-if="i > 0"> | </template>
|
||||||
|
<b>{{ spawn.spawnName }}</b> ({{ spawn.spawnLength }}m)
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import { useTooltipStore } from '../../store/tooltipStore';
|
||||||
|
import { ScenerySpawn } from '../../typings/common';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tooltipStore: useTooltipStore()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
spawns() {
|
||||||
|
if (this.tooltipStore.content == '') return [];
|
||||||
|
|
||||||
|
const parsedSpawns = JSON.parse(this.tooltipStore.content) as ScenerySpawn[];
|
||||||
|
return parsedSpawns ?? [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tooltip-content {
|
||||||
|
width: 300px;
|
||||||
|
|
||||||
|
padding: 0.25em 0.5em;
|
||||||
|
border-radius: 0.25em;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
background-color: #1b1b1b;
|
||||||
|
box-shadow: 0 0 5px 2px #aaa;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
<template>
|
||||||
|
<div class="tooltip" v-show="tooltipStore.type" ref="preview">
|
||||||
|
<component v-if="tooltipStore.type" :is="tooltipStore.type" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import { useTooltipStore } from '../../store/tooltipStore';
|
||||||
|
import DonatorTooltip from './DonatorTooltip.vue';
|
||||||
|
import VehiclePreviewTooltip from './VehiclePreviewTooltip.vue';
|
||||||
|
import BaseTooltip from './BaseTooltip.vue';
|
||||||
|
import SpawnsTooltip from './SpawnsTooltip.vue';
|
||||||
|
import UsersTooltip from './UsersTooltip.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: { DonatorTooltip, VehiclePreviewTooltip, BaseTooltip, SpawnsTooltip, UsersTooltip },
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tooltipStore: useTooltipStore()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
'tooltipStore.mousePos': {
|
||||||
|
deep: true,
|
||||||
|
// [x, y]
|
||||||
|
handler(val: [number, number]) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
const previewEl = this.$refs['preview'] as HTMLElement;
|
||||||
|
const clientWidth = document.body.clientWidth;
|
||||||
|
const boxWidth = previewEl.getBoundingClientRect().width;
|
||||||
|
|
||||||
|
let translateX = '0',
|
||||||
|
translateY = '30px';
|
||||||
|
|
||||||
|
if (clientWidth < 500) {
|
||||||
|
previewEl.style.left = '50%';
|
||||||
|
translateX = '-50%';
|
||||||
|
} else if (val[0] <= boxWidth / 2) {
|
||||||
|
previewEl.style.left = '0';
|
||||||
|
translateX = '0px';
|
||||||
|
} else if (val[0] >= clientWidth - boxWidth / 2) {
|
||||||
|
previewEl.style.left = '100%';
|
||||||
|
translateX = '-100%';
|
||||||
|
} else {
|
||||||
|
previewEl.style.left = `${val[0]}px`;
|
||||||
|
translateX = '-50%';
|
||||||
|
}
|
||||||
|
|
||||||
|
previewEl.style.top = `${val[1]}px`;
|
||||||
|
|
||||||
|
const isOutside =
|
||||||
|
val[1] + previewEl.getBoundingClientRect().height + 30 >=
|
||||||
|
window.innerHeight + window.scrollY;
|
||||||
|
|
||||||
|
if (isOutside) translateY = 'calc(-100% - 30px)';
|
||||||
|
previewEl.style.transform = `translate(${translateX}, ${translateY})`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.tooltip {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 250;
|
||||||
|
max-width: 400px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<template>
|
||||||
|
<div class="tooltip-content" v-if="trains.length != 0">
|
||||||
|
<span v-for="(train, i) in trains">
|
||||||
|
<template v-if="i > 0"> | </template>
|
||||||
|
<b>{{ train.trainNo }}</b> {{ train.driverName }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import { useTooltipStore } from '../../store/tooltipStore';
|
||||||
|
import { StationTrain } from '../../typings/common';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tooltipStore: useTooltipStore()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
trains() {
|
||||||
|
if (this.tooltipStore.content == '') return [];
|
||||||
|
|
||||||
|
const parsedTrains = JSON.parse(this.tooltipStore.content) as StationTrain[];
|
||||||
|
return (parsedTrains ?? []).sort((a, b) => a.trainNo - b.trainNo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tooltip-content {
|
||||||
|
width: 300px;
|
||||||
|
|
||||||
|
padding: 0.25em 0.5em;
|
||||||
|
border-radius: 0.25em;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
background-color: #1b1b1b;
|
||||||
|
box-shadow: 0 0 5px 2px #aaa;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
<template>
|
||||||
|
<div class="tooltip-content">
|
||||||
|
<div v-if="imageState == 'loading'" class="loading-info">
|
||||||
|
{{ $t('vehicle-preview.loading') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="imageState == 'error'">{{ $t('vehicle-preview.error') }}</div>
|
||||||
|
|
||||||
|
<img
|
||||||
|
v-if="tooltipStore.type"
|
||||||
|
@load="onImageLoad"
|
||||||
|
@error="onImageError"
|
||||||
|
width="300"
|
||||||
|
height="176"
|
||||||
|
class="rounded-md w-full h-auto"
|
||||||
|
:src="`https://static.spythere.eu/images/${tooltipStore.content}--300px.jpg`"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div v-if="imageState == 'error'" class="error-placeholder"></div>
|
||||||
|
|
||||||
|
<div class="vehicle-name">
|
||||||
|
{{ tooltipStore.content.replace(/_/g, ' ') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import { useTooltipStore } from '../../store/tooltipStore';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tooltipStore: useTooltipStore(),
|
||||||
|
imageState: 'loading'
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.imageState = 'loading';
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
'tooltipStore.type'(prev, val) {
|
||||||
|
if (prev != val) this.imageState = 'loading';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
onImageLoad() {
|
||||||
|
this.imageState = 'loaded';
|
||||||
|
},
|
||||||
|
|
||||||
|
onImageError(e: Event) {
|
||||||
|
this.imageState = 'error';
|
||||||
|
|
||||||
|
(e.target as HTMLElement).style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.tooltip-content {
|
||||||
|
width: 300px;
|
||||||
|
min-height: 200px;
|
||||||
|
background-color: #333;
|
||||||
|
box-shadow: 0 0 10px 2px #aaa;
|
||||||
|
|
||||||
|
padding: 0.5em;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-info {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vehicle-name {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
color: #ccc;
|
||||||
|
text-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-placeholder {
|
||||||
|
height: 176px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
<template>
|
||||||
|
<span class="stop-label" :data-sbl="stop.isSBL">
|
||||||
|
<span class="name" v-html="stop.nameHtml"></span>
|
||||||
|
|
||||||
|
<span
|
||||||
|
v-if="stop.position != 'begin'"
|
||||||
|
class="date arrival"
|
||||||
|
:data-status="
|
||||||
|
stop.arrivalDelay > 0 && stop.status != 'unconfirmed'
|
||||||
|
? 'delayed'
|
||||||
|
: stop.arrivalDelay < 0 && stop.status != 'unconfirmed'
|
||||||
|
? 'preponed'
|
||||||
|
: stop.arrivalDelay == 0 && stop.status == 'confirmed'
|
||||||
|
? 'on-time'
|
||||||
|
: ''
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<span v-if="stop.arrivalDelay != 0 && stop.status != 'unconfirmed'">
|
||||||
|
<s>{{ timestampToString(stop.arrivalScheduled) }}</s>
|
||||||
|
{{ timestampToString(stop.arrivalReal) }}
|
||||||
|
({{ stop.arrivalDelay > 0 ? '+' : '' }}{{ stop.arrivalDelay }})
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span v-else>
|
||||||
|
{{ timestampToString(stop.arrivalScheduled) }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span
|
||||||
|
v-if="
|
||||||
|
stop.duration ||
|
||||||
|
(stop.status == 'stopped' &&
|
||||||
|
stop.position != 'begin' &&
|
||||||
|
stop.departureDelay != stop.arrivalDelay)
|
||||||
|
"
|
||||||
|
class="date stop"
|
||||||
|
:data-stop-types="stop.type.replace(', ', '-')"
|
||||||
|
:data-stop-status="
|
||||||
|
stop.departureDelay - stop.arrivalDelay > 0 && !stop.duration ? 'delayed' : ''
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ stop.duration || stop.departureDelay - stop.arrivalDelay }}
|
||||||
|
{{ stop.type == '' ? 'pt' : stop.type }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span
|
||||||
|
v-if="
|
||||||
|
stop.position != 'end' &&
|
||||||
|
(stop.duration != 0 || stop.status == 'stopped' || stop.departureDelay != stop.arrivalDelay)
|
||||||
|
"
|
||||||
|
class="date departure"
|
||||||
|
:data-status="
|
||||||
|
stop.departureDelay > 0 && stop.status == 'confirmed'
|
||||||
|
? 'delayed'
|
||||||
|
: stop.departureDelay < 0 && stop.status == 'confirmed'
|
||||||
|
? 'preponed'
|
||||||
|
: stop.departureDelay == 0 && stop.status == 'confirmed'
|
||||||
|
? 'on-time'
|
||||||
|
: ''
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<span v-if="stop.departureDelay != 0 && stop.status == 'confirmed'">
|
||||||
|
<s>{{ timestampToString(stop.departureScheduled) }}</s>
|
||||||
|
{{ timestampToString(stop.departureReal) }}
|
||||||
|
|
||||||
|
({{ stop.departureDelay > 0 ? '+' : '' }}{{ stop.departureDelay }})
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span v-else>
|
||||||
|
{{ timestampToString(stop.departureScheduled) }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { PropType, defineComponent } from 'vue';
|
||||||
|
import dateMixin from '../../mixins/dateMixin';
|
||||||
|
import { TrainScheduleStop } from './TrainSchedule.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
mixins: [dateMixin],
|
||||||
|
|
||||||
|
props: {
|
||||||
|
stop: {
|
||||||
|
type: Object as PropType<TrainScheduleStop>,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
$preponedClr: lime;
|
||||||
|
$delayedClr: salmon;
|
||||||
|
$dateClr: #525151;
|
||||||
|
$stopExchangeClr: #db8e29;
|
||||||
|
$stopDefaultClr: #252525;
|
||||||
|
$stopNameClr: #22a8d1;
|
||||||
|
|
||||||
|
.stop-label {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&[data-sbl='true'] {
|
||||||
|
.date {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
background: none;
|
||||||
|
color: #aaa;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
background: $stopNameClr;
|
||||||
|
padding: 0.3em 0.5em;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&.misc {
|
||||||
|
background: gray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.date {
|
||||||
|
background: $dateClr;
|
||||||
|
padding: 0.3em 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stop {
|
||||||
|
&[data-stop-types='ph'],
|
||||||
|
&[data-stop-types='ph-pm'],
|
||||||
|
&[data-stop-types='pm'] {
|
||||||
|
background: $stopExchangeClr;
|
||||||
|
}
|
||||||
|
|
||||||
|
background: $stopDefaultClr;
|
||||||
|
|
||||||
|
&[data-stop-status='delayed'] {
|
||||||
|
color: $delayedClr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrival,
|
||||||
|
.departure {
|
||||||
|
&[data-status='delayed'] {
|
||||||
|
s {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: $delayedClr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-status='preponed'] {
|
||||||
|
s {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: $preponedClr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="train-info">
|
<div class="train-info" :data-extended="extended">
|
||||||
<section class="train-general">
|
<section class="train-general">
|
||||||
<div class="general-info">
|
<div class="general-top-bar">
|
||||||
|
<div>
|
||||||
<b class="warning-timeout" v-if="train.isTimeout" :title="$t('trains.timeout')">?</b>
|
<b class="warning-timeout" v-if="train.isTimeout" :title="$t('trains.timeout')">?</b>
|
||||||
<span class="timetable-id" v-if="train.timetableData">
|
<span class="timetable-id" v-if="train.timetableData">
|
||||||
#{{ train.timetableData.timetableId }}
|
#{{ train.timetableData.timetableId }}
|
||||||
@@ -11,10 +12,18 @@
|
|||||||
class="timetable-warnings"
|
class="timetable-warnings"
|
||||||
v-if="train.timetableData?.TWR || train.timetableData?.SKR"
|
v-if="train.timetableData?.TWR || train.timetableData?.SKR"
|
||||||
>
|
>
|
||||||
<span class="train-badge twr" v-if="train.timetableData?.TWR" :title="$t('general.TWR')">
|
<span
|
||||||
|
class="train-badge twr"
|
||||||
|
v-if="train.timetableData?.TWR"
|
||||||
|
:title="$t('general.TWR')"
|
||||||
|
>
|
||||||
TWR
|
TWR
|
||||||
</span>
|
</span>
|
||||||
<span class="train-badge skr" v-if="train.timetableData?.SKR" :title="$t('general.SKR')">
|
<span
|
||||||
|
class="train-badge skr"
|
||||||
|
v-if="train.timetableData?.SKR"
|
||||||
|
:title="$t('general.SKR')"
|
||||||
|
>
|
||||||
SKR
|
SKR
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -35,26 +44,43 @@
|
|||||||
|
|
||||||
<div class="train-driver">
|
<div class="train-driver">
|
||||||
<b
|
<b
|
||||||
v-if="store.donatorsData.includes(train.driverName)"
|
v-if="apiStore.donatorsData.includes(train.driverName)"
|
||||||
:title="$t('donations.driver-message')"
|
data-tooltip-type="DonatorTooltip"
|
||||||
|
:data-tooltip-content="$t('donations.driver-message')"
|
||||||
>
|
>
|
||||||
{{ train.driverName }}
|
{{ train.driverName }}
|
||||||
<img src="/images/icon-diamond.svg" alt="donator diamond icon" />
|
<img src="/images/icon-diamond.svg" alt="donator diamond icon" />
|
||||||
</b>
|
</b>
|
||||||
|
|
||||||
<span v-else>{{ train.driverName }}</span>
|
<span v-else>{{ train.driverName }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="extended">
|
||||||
|
<button class="btn-timetable btn--image btn--action" @click="navigateToJournal">
|
||||||
|
<img src="/images/icon-train.svg" alt="train icon" />
|
||||||
|
<span>
|
||||||
|
{{ $t('trains.journal-button') }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="btn-exit btn--image btn--action" @click="closeModal">
|
||||||
|
<img src="/images/icon-exit.svg" alt="modal exit icon" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="general-timetable" v-if="train.timetableData">
|
<div class="general-timetable" v-if="train.timetableData">
|
||||||
<strong>{{ train.timetableData.route.replace('|', ' - ') }}</strong>
|
<strong>{{ train.timetableData.route.replace('|', ' - ') }}</strong>
|
||||||
<img
|
<span
|
||||||
v-if="getSceneriesWithComments(train.timetableData).length > 0"
|
v-if="getSceneriesWithComments(train.timetableData).length > 0"
|
||||||
class="image-warning"
|
data-tooltip-type="BaseTooltip"
|
||||||
src="/images/icon-warning.svg"
|
:data-tooltip-content="`${$t('trains.timetable-comments')} (${getSceneriesWithComments(
|
||||||
:title="`${$t('trains.timetable-comments')} (${getSceneriesWithComments(
|
|
||||||
train.timetableData
|
train.timetableData
|
||||||
)})`"
|
)})`"
|
||||||
/>
|
>
|
||||||
|
<img class="image-warning" src="/images/icon-warning.svg" />
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr style="margin: 0.25em 0" />
|
<hr style="margin: 0.25em 0" />
|
||||||
@@ -67,7 +93,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="general-status">
|
<div class="general-status">
|
||||||
<div class="timetable-progress" v-if="train.timetableData">
|
<div class="status-timetable-progress" v-if="train.timetableData">
|
||||||
<ProgressBar :progressPercent="confirmedPercentage(train.timetableData.followingStops)" />
|
<ProgressBar :progressPercent="confirmedPercentage(train.timetableData.followingStops)" />
|
||||||
|
|
||||||
<span class="progress-distance">
|
<span class="progress-distance">
|
||||||
@@ -80,39 +106,55 @@
|
|||||||
|
|
||||||
<div class="status-badges">
|
<div class="status-badges">
|
||||||
<div v-if="!train.currentStationHash" class="train-badge offline">
|
<div v-if="!train.currentStationHash" class="train-badge offline">
|
||||||
|
<img src="/images/icon-offline.svg" alt="" />
|
||||||
{{ $t('trains.scenery-offline') }}
|
{{ $t('trains.scenery-offline') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="!train.online" class="train-badge offline">
|
<div v-if="!train.online" class="train-badge offline">
|
||||||
|
<img src="/images/icon-offline.svg" alt="" />
|
||||||
Offline {{ lastSeenMessage(train.lastSeen) }}
|
Offline {{ lastSeenMessage(train.lastSeen) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="driver_position text--grayed" style="margin-top: 0.25em">
|
<div class="general-stats" v-if="extended">
|
||||||
|
<div>
|
||||||
|
<img src="/images/icon-length.svg" alt="length icon" />
|
||||||
|
{{ train.length }}m
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<img src="/images/icon-mass.svg" alt="mass icon" />
|
||||||
|
{{ (train.mass / 1000).toFixed(1) }}t
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<img src="/images/icon-speed.svg" alt="speed icon" />
|
||||||
|
{{ train.speed }} km/h
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text--grayed" style="margin-top: 0.25em">
|
||||||
{{ displayTrainPosition(train) }}
|
{{ displayTrainPosition(train) }}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="train-stats">
|
<section class="train-stats" v-if="!extended">
|
||||||
<TrainThumbnail :name="train.locoType" :onlyFirstSegment="true" />
|
<StockList :trainStockList="train.stockList" :tractionOnly="true" />
|
||||||
|
|
||||||
<div class="text--grayed">
|
|
||||||
{{ train.locoType }}
|
|
||||||
<span v-if="train.stockList.length > 1">
|
|
||||||
• {{ $t('trains.cars') }}:
|
|
||||||
<span class="count">{{ train.stockList.length - 1 }}</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<span v-for="(stat, i) in STATS.main" :key="stat.name">
|
<span>{{ train.speed }}km/h</span>
|
||||||
<span v-if="i > 0"> • </span>
|
|
||||||
<span
|
<div>
|
||||||
>{{ `${~~((train as any)[stat.name] * (stat.multiplier || 1))}${stat.unit}` }}
|
<span> {{ train.length }}m</span>
|
||||||
</span>
|
•
|
||||||
|
<span> {{ (train.mass / 1000).toFixed(1) }}t</span>
|
||||||
|
<span v-if="train.stockList.length > 1">
|
||||||
|
•
|
||||||
|
{{ $t('trains.cars') }}: {{ train.stockList.length - 1 }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -121,14 +163,16 @@
|
|||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import styleMixin from '../../mixins/styleMixin';
|
import styleMixin from '../../mixins/styleMixin';
|
||||||
import trainInfoMixin from '../../mixins/trainInfoMixin';
|
import trainInfoMixin from '../../mixins/trainInfoMixin';
|
||||||
import Train from '../../scripts/interfaces/Train';
|
|
||||||
import ProgressBar from '../Global/ProgressBar.vue';
|
import ProgressBar from '../Global/ProgressBar.vue';
|
||||||
import TrainThumbnail from '../Global/TrainThumbnail.vue';
|
import { useMainStore } from '../../store/mainStore';
|
||||||
import { useStore } from '../../store/mainStore';
|
import { useApiStore } from '../../store/apiStore';
|
||||||
|
import StockList from '../Global/StockList.vue';
|
||||||
|
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
||||||
|
import { Train } from '../../typings/common';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
mixins: [trainInfoMixin, styleMixin],
|
mixins: [trainInfoMixin, styleMixin, modalTrainMixin],
|
||||||
components: { ProgressBar, TrainThumbnail },
|
components: { ProgressBar, StockList },
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
train: {
|
train: {
|
||||||
@@ -136,34 +180,40 @@ export default defineComponent({
|
|||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
extended: {
|
extended: {
|
||||||
type: Boolean,
|
type: Boolean
|
||||||
default: true
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
store: useStore()
|
store: useMainStore(),
|
||||||
|
apiStore: useApiStore()
|
||||||
};
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
navigateToJournal() {
|
||||||
|
this.$router.push({
|
||||||
|
path: '/journal/timetables',
|
||||||
|
query: {
|
||||||
|
'search-driver': this.train.driverName
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.closeModal();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Global style for TrainThumbnail -->
|
|
||||||
<style lang="scss">
|
|
||||||
.train-stats .train-thumbnail {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../styles/responsive.scss';
|
@import '../../styles/responsive.scss';
|
||||||
@import '../../styles/badge.scss';
|
@import '../../styles/badge.scss';
|
||||||
|
|
||||||
.image-warning {
|
.image-warning {
|
||||||
height: 1em;
|
height: 1em;
|
||||||
|
|
||||||
margin-left: 0.5em;
|
margin-left: 0.5em;
|
||||||
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.train-stats {
|
.train-stats {
|
||||||
@@ -174,7 +224,7 @@ export default defineComponent({
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
gap: 0.25em;
|
line-height: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.train-info {
|
.train-info {
|
||||||
@@ -182,6 +232,10 @@ export default defineComponent({
|
|||||||
grid-template-columns: 2fr 1fr;
|
grid-template-columns: 2fr 1fr;
|
||||||
grid-template-rows: 1fr;
|
grid-template-rows: 1fr;
|
||||||
|
|
||||||
|
&[data-extended='true'] {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
|
|
||||||
background-color: #1a1a1a;
|
background-color: #1a1a1a;
|
||||||
@@ -216,14 +270,29 @@ export default defineComponent({
|
|||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.general-info {
|
.general-top-bar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5em;
|
||||||
|
|
||||||
|
& > div {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
||||||
gap: 0.25em;
|
gap: 0.25em;
|
||||||
margin-right: 1.5em;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-timetable {
|
||||||
|
padding: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-exit {
|
||||||
|
padding: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
.general-status {
|
.general-status {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -232,11 +301,20 @@ export default defineComponent({
|
|||||||
gap: 0.25em;
|
gap: 0.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-badges {
|
.general-stats {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
gap: 0.5em;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
& > div {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
gap: 0.25em;
|
gap: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 1.5em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.general-timetable {
|
.general-timetable {
|
||||||
@@ -244,12 +322,18 @@ export default defineComponent({
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timetable-warnings {
|
.status-badges {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
gap: 0.25em;
|
gap: 0.25em;
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 15px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.timetable-progress {
|
.status-timetable-progress {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -259,32 +343,19 @@ export default defineComponent({
|
|||||||
margin-right: 0.25em;
|
margin-right: 0.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.timetable-warnings {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
@include smallScreen() {
|
@include smallScreen() {
|
||||||
.train-info {
|
.train-info {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
gap: 1em 0;
|
gap: 1em 0;
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
font-size: 1.15em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.general-info,
|
.btn-timetable > span {
|
||||||
.general-status,
|
display: none;
|
||||||
.general-timetable {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timetable-progress {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comments {
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
img {
|
|
||||||
margin: 0 0 0.5em 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="train-modal" v-if="chosenTrain" @keydown.esc="closeModal">
|
<div class="train-modal" v-if="chosenTrain" @keydown.esc="closeModal">
|
||||||
<div class="modal_background" @click="closeModal"></div>
|
<div class="modal-background" @click="closeModal"></div>
|
||||||
<div class="modal_content" ref="content" tabindex="0">
|
<div class="modal-content" ref="content" tabindex="0">
|
||||||
<button class="btn exit" @click="closeModal">
|
<TrainInfo :train="chosenTrain" :extended="true" ref="trainInfo" />
|
||||||
<img src="/images/icon-exit.svg" alt="close card" />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<TrainInfo :train="chosenTrain" :extended="false" ref="trainInfo" />
|
|
||||||
<TrainSchedule :train="chosenTrain" tabindex="0" />
|
<TrainSchedule :train="chosenTrain" tabindex="0" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -15,27 +11,30 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
||||||
import trainInfoMixin from '../../mixins/trainInfoMixin';
|
import TrainInfo from './TrainInfo.vue';
|
||||||
import TrainInfo from '../TrainsView/TrainInfo.vue';
|
import TrainSchedule from './TrainSchedule.vue';
|
||||||
import TrainSchedule from '../TrainsView/TrainSchedule.vue';
|
import { Train } from '../../typings/common';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { TrainInfo, TrainSchedule },
|
components: { TrainInfo, TrainSchedule },
|
||||||
mixins: [trainInfoMixin, modalTrainMixin],
|
mixins: [modalTrainMixin],
|
||||||
|
|
||||||
data() {
|
computed: {
|
||||||
return {
|
chosenTrain() {
|
||||||
isTopBarVisible: false
|
return this.store.trainList.find((train) => train.trainId == this.store.chosenModalTrainId);
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
activated() {
|
watch: {
|
||||||
const contentEl = this.$refs['content'] as HTMLElement;
|
chosenTrain(train: Train | undefined) {
|
||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
|
if (train) {
|
||||||
|
const contentEl = this.$refs['content'] as HTMLElement;
|
||||||
contentEl.focus();
|
contentEl.focus();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -56,23 +55,6 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.exit {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
|
|
||||||
margin: 0.5em 1em;
|
|
||||||
|
|
||||||
padding: 0.25em;
|
|
||||||
|
|
||||||
z-index: 201;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 1.5rem;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.train-modal {
|
.train-modal {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -89,7 +71,7 @@ export default defineComponent({
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal_background {
|
.modal-background {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@@ -101,14 +83,14 @@ export default defineComponent({
|
|||||||
background-color: rgba(0, 0, 0, 0.55);
|
background-color: rgba(0, 0, 0, 0.55);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal_content {
|
.modal-content {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
|
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
|
|
||||||
width: 95vw;
|
width: 95vw;
|
||||||
max-height: 96vh;
|
max-height: 95vh;
|
||||||
|
|
||||||
background-color: #1a1a1a;
|
background-color: #1a1a1a;
|
||||||
box-shadow: 0 0 15px 10px #0e0e0e;
|
box-shadow: 0 0 15px 10px #0e0e0e;
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="filters-options" @keydown.esc="showOptions = false">
|
<div class="dropdown" @keydown.esc="showOptions = false">
|
||||||
<div class="bg" v-if="showOptions" @click="showOptions = false"></div>
|
<div class="dropdown_background" v-if="showOptions" @click="showOptions = false"></div>
|
||||||
|
|
||||||
<button class="filter-button btn--filled btn--image" @click="toggleShowOptions" ref="button">
|
<button class="filter-button btn--filled btn--image" @click="toggleShowOptions" ref="button">
|
||||||
<img src="/images/icon-filter2.svg" alt="Open filters icon" />
|
<img src="/images/icon-filter2.svg" alt="Open filters icon" />
|
||||||
{{ $t('options.filters') }} [F]
|
[F] {{ $t('options.filters') }}
|
||||||
<span class="active-indicator" v-if="currentOptionsActive"></span>
|
<span class="active-indicator" v-if="currentOptionsActive"></span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<transition name="options-anim">
|
<transition name="dropdown-anim">
|
||||||
<div class="options_wrapper" v-if="showOptions">
|
<div class="dropdown_wrapper" v-if="showOptions">
|
||||||
<div class="options_content">
|
<div class="options_content">
|
||||||
<h1 class="option-title">{{ $t('options.search-title') }}</h1>
|
<h1 class="option-title">{{ $t('options.search-title') }}</h1>
|
||||||
<div class="search_content">
|
<div class="search_content">
|
||||||
@@ -87,6 +87,8 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div tabindex="0" @focus="showOptions = false"></div>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
@@ -184,7 +186,8 @@ export default defineComponent({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../styles/filters_options.scss';
|
@import '../../styles/dropdown.scss';
|
||||||
|
@import '../../styles/dropdown_filters.scss';
|
||||||
|
|
||||||
.search_content > div {
|
.search_content > div {
|
||||||
margin: 0.5em auto;
|
margin: 0.5em auto;
|
||||||
|
|||||||
@@ -2,83 +2,126 @@
|
|||||||
<div class="train-schedule" @click="toggleShowState">
|
<div class="train-schedule" @click="toggleShowState">
|
||||||
<StockList :trainStockList="train.stockList" />
|
<StockList :trainStockList="train.stockList" />
|
||||||
|
|
||||||
<!-- <div class="train-stock"> -->
|
|
||||||
<!-- <ul>
|
|
||||||
<li v-for="(stockName, i) in train.stockList" :key="i">
|
|
||||||
<p>{{ stockName.split(':')[0].split('_').splice(0, 2).join(' ') }} {{ stockName.split(':')[1] }}</p>
|
|
||||||
<TrainThumbnail :name="stockName" />
|
|
||||||
</li>
|
|
||||||
</ul> -->
|
|
||||||
<!-- </div> -->
|
|
||||||
|
|
||||||
<div class="schedule-wrapper" v-if="train.timetableData">
|
<div class="schedule-wrapper" v-if="train.timetableData">
|
||||||
<ul class="stop_list">
|
<div class="stops">
|
||||||
<li
|
<div
|
||||||
v-for="(stop, i) in train.timetableData.followingStops"
|
v-for="(stop, i) in scheduleStops"
|
||||||
:key="i"
|
:key="i"
|
||||||
class="stop"
|
class="stop"
|
||||||
:class="addClasses(stop, i)"
|
:data-status="stop.status"
|
||||||
|
:data-position="stop.position"
|
||||||
|
:data-delayed="stop.departureDelay > 0"
|
||||||
|
:data-stop-type="stop.type"
|
||||||
|
:data-minor-stop-active="stop.isActive"
|
||||||
|
:data-last-confirmed="stop.isLastConfirmed"
|
||||||
|
x
|
||||||
>
|
>
|
||||||
<span class="stop_info">
|
<span class="stop_info">
|
||||||
<div class="indicator"></div>
|
<span class="distance">
|
||||||
|
{{ stop.distance ? stop.distance.toFixed(1) : '' }}
|
||||||
<div class="progress-bar"></div>
|
|
||||||
|
|
||||||
<div class="stop-bar"></div>
|
|
||||||
|
|
||||||
<span class="distance" v-if="stop.stopDistance">
|
|
||||||
{{ Math.floor(stop.stopDistance) }}
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="stop-name" v-html="stop.stopName"> </span>
|
<div class="progress">
|
||||||
|
<div class="line line_node line_node-top"></div>
|
||||||
<StopDate :stop="stop" />
|
<div class="node"></div>
|
||||||
</span>
|
<div class="line line_node line_node-bottom"></div>
|
||||||
|
|
||||||
<div class="stop_line" v-if="i < train.timetableData!.followingStops.length - 1">
|
|
||||||
<div class="progress-bar"></div>
|
|
||||||
|
|
||||||
<div v-if="stop.comments" style="color: salmon">
|
|
||||||
<b>{{ stop.stopNameRAW }} </b>: <span v-html="stop.comments"></span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<StopLabel :stop="stop" />
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="stop_line">
|
||||||
|
<!-- Grid placeholder -->
|
||||||
|
<div></div>
|
||||||
|
|
||||||
|
<div class="progress">
|
||||||
|
<div class="line line_connection" v-if="i < scheduleStops.length - 1"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bottom-line-info">
|
||||||
|
<div class="info-comments" v-if="stop.comments" style="color: salmon">
|
||||||
|
<img src="/images/icon-warning.svg" alt="icon-warning" width="20" />
|
||||||
|
<b v-html="stop.comments"></b>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Routes -->
|
||||||
<span
|
<span
|
||||||
v-if="
|
v-if="
|
||||||
stop.departureLine == train.timetableData!.followingStops[i + 1].arrivalLine &&
|
stop.departureLine &&
|
||||||
!/sbl/gi.test(stop.departureLine!)
|
stop.departureLine == scheduleStops[i + 1]?.arrivalLine &&
|
||||||
|
!/sbl/gi.test(stop.departureLine)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
{{ stop.departureLine }}
|
{{ stop.departureLine }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span v-else-if="!/sbl/gi.test(stop.departureLine!)">
|
<span v-else-if="stop.departureLine && !/sbl/gi.test(stop.departureLine)">
|
||||||
{{ stop.departureLine }} /
|
<div>{{ stop.departureLine }}</div>
|
||||||
{{ train.timetableData!.followingStops[i + 1].arrivalLine }}
|
<div
|
||||||
|
class="scenery-change-name"
|
||||||
|
v-if="
|
||||||
|
i < scheduleStops.length - 1 &&
|
||||||
|
stop.sceneryName != scheduleStops[i + 1].sceneryName
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ scheduleStops[i + 1].sceneryName }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ scheduleStops[i + 1].arrivalLine }}
|
||||||
|
</div>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stop_line" v-else>
|
|
||||||
<div v-if="stop.comments" style="color: salmon">
|
|
||||||
<b>{{ stop.stopNameRAW }} </b>: <span v-html="stop.comments"></span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</div>
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, PropType } from 'vue';
|
import { defineComponent, PropType } from 'vue';
|
||||||
import dateMixin from '../../mixins/dateMixin';
|
import dateMixin from '../../mixins/dateMixin';
|
||||||
import Train from '../../scripts/interfaces/Train';
|
import StopLabel from './StopLabel.vue';
|
||||||
import { useStore } from '../../store/mainStore';
|
|
||||||
import StopDate from '../Global/StopDate.vue';
|
|
||||||
import StockList from '../Global/StockList.vue';
|
import StockList from '../Global/StockList.vue';
|
||||||
import { TrainStop } from '../../store/typings';
|
import { useMainStore } from '../../store/mainStore';
|
||||||
|
import { useApiStore } from '../../store/apiStore';
|
||||||
|
import { Train } from '../../typings/common';
|
||||||
|
|
||||||
|
export interface TrainScheduleStop {
|
||||||
|
nameHtml: string;
|
||||||
|
nameRaw: string;
|
||||||
|
|
||||||
|
status: 'confirmed' | 'unconfirmed' | 'stopped';
|
||||||
|
type: string;
|
||||||
|
position: 'begin' | 'end' | 'en-route';
|
||||||
|
|
||||||
|
arrivalScheduled: number;
|
||||||
|
arrivalReal: number;
|
||||||
|
|
||||||
|
departureScheduled: number;
|
||||||
|
departureReal: number;
|
||||||
|
|
||||||
|
departureDelay: number;
|
||||||
|
arrivalDelay: number;
|
||||||
|
|
||||||
|
duration: number | null;
|
||||||
|
|
||||||
|
isActive: boolean;
|
||||||
|
isLastConfirmed: boolean;
|
||||||
|
isSBL: boolean;
|
||||||
|
|
||||||
|
sceneryName: string | null;
|
||||||
|
sceneryHash: string;
|
||||||
|
distance: number;
|
||||||
|
|
||||||
|
arrivalLine: string | null;
|
||||||
|
departureLine: string | null;
|
||||||
|
|
||||||
|
comments: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { StopDate, StockList },
|
components: { StopLabel, StockList },
|
||||||
props: {
|
props: {
|
||||||
train: {
|
train: {
|
||||||
type: Object as PropType<Train>,
|
type: Object as PropType<Train>,
|
||||||
@@ -90,62 +133,95 @@ export default defineComponent({
|
|||||||
|
|
||||||
emits: ['click'],
|
emits: ['click'],
|
||||||
|
|
||||||
setup(props) {
|
data() {
|
||||||
return {
|
return {
|
||||||
store: useStore(),
|
store: useMainStore(),
|
||||||
|
apiStore: useApiStore()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
lastConfirmed: computed(() => {
|
computed: {
|
||||||
return props.train.timetableData!.followingStops.findIndex(
|
scheduleStops(): TrainScheduleStop[] {
|
||||||
|
let currentSceneryIndex = 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
this.train.timetableData?.followingStops.map((stop, i, arr) => {
|
||||||
|
if (
|
||||||
|
i > 0 &&
|
||||||
|
stop.arrivalLine &&
|
||||||
|
stop.arrivalLine != arr[i - 1].departureLine &&
|
||||||
|
!/sbl/gi.test(stop.arrivalLine)
|
||||||
|
)
|
||||||
|
currentSceneryIndex++;
|
||||||
|
|
||||||
|
return {
|
||||||
|
nameHtml: stop.stopName,
|
||||||
|
nameRaw: stop.stopNameRAW,
|
||||||
|
|
||||||
|
arrivalScheduled: stop.arrivalTimestamp,
|
||||||
|
arrivalReal: stop.arrivalRealTimestamp,
|
||||||
|
|
||||||
|
departureScheduled: stop.departureTimestamp,
|
||||||
|
departureReal: stop.departureRealTimestamp,
|
||||||
|
|
||||||
|
departureDelay: stop.departureDelay,
|
||||||
|
arrivalDelay: stop.arrivalDelay,
|
||||||
|
|
||||||
|
duration: stop.stopTime,
|
||||||
|
|
||||||
|
comments: stop.comments ?? null,
|
||||||
|
|
||||||
|
arrivalLine: stop.arrivalLine,
|
||||||
|
departureLine: stop.departureLine,
|
||||||
|
|
||||||
|
type: stop.stopType,
|
||||||
|
distance: stop.stopDistance,
|
||||||
|
isActive: this.activeMinorStops.includes(i),
|
||||||
|
isLastConfirmed: this.lastConfirmed === i && !stop.terminatesHere,
|
||||||
|
isSBL: /sbl/gi.test(stop.stopName),
|
||||||
|
position: stop.beginsHere ? 'begin' : stop.terminatesHere ? 'end' : 'en-route',
|
||||||
|
sceneryHash: '',
|
||||||
|
sceneryName: this.train.timetableData!.sceneryNames[currentSceneryIndex],
|
||||||
|
status: stop.confirmed ? 'confirmed' : stop.stopped ? 'stopped' : 'unconfirmed'
|
||||||
|
};
|
||||||
|
}) ?? []
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
lastConfirmed() {
|
||||||
|
return this.train.timetableData?.followingStops.findIndex(
|
||||||
(stop, i, stops) => stop.confirmed && !stops[i + 1]?.confirmed && !stops[i + 1]?.stopped
|
(stop, i, stops) => stop.confirmed && !stops[i + 1]?.confirmed && !stops[i + 1]?.stopped
|
||||||
);
|
);
|
||||||
}),
|
},
|
||||||
activeMinorStops: computed(() => {
|
|
||||||
const lastMajorConfirmed = props.train.timetableData!.followingStops.findIndex(
|
activeMinorStops() {
|
||||||
|
if (!this.train.timetableData) return [];
|
||||||
|
|
||||||
|
const lastMajorConfirmed = this.train.timetableData.followingStops.findIndex(
|
||||||
(stop, i, stops) => stop.confirmed && !stops[i + 1]?.confirmed
|
(stop, i, stops) => stop.confirmed && !stops[i + 1]?.confirmed
|
||||||
);
|
);
|
||||||
|
|
||||||
const activeMinorStopList: number[] = [];
|
const activeMinorStopList: number[] = [];
|
||||||
if (lastMajorConfirmed + 1 >= props.train.timetableData!.followingStops.length)
|
if (lastMajorConfirmed + 1 >= this.train.timetableData.followingStops.length)
|
||||||
return activeMinorStopList;
|
return activeMinorStopList;
|
||||||
|
|
||||||
for (
|
for (
|
||||||
let i = lastMajorConfirmed + 1;
|
let i = lastMajorConfirmed + 1;
|
||||||
i < props.train.timetableData!.followingStops.length;
|
i < this.train.timetableData!.followingStops.length;
|
||||||
i++
|
i++
|
||||||
) {
|
) {
|
||||||
if (/po\.|sbl/gi.test(props.train.timetableData!.followingStops[i].stopNameRAW))
|
if (/po\.|sbl/gi.test(this.train.timetableData!.followingStops[i].stopNameRAW))
|
||||||
activeMinorStopList.push(i);
|
activeMinorStopList.push(i);
|
||||||
else break;
|
else break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return activeMinorStopList;
|
return activeMinorStopList;
|
||||||
})
|
}
|
||||||
};
|
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
toggleShowState() {
|
toggleShowState() {
|
||||||
this.$emit('click');
|
this.$emit('click');
|
||||||
},
|
|
||||||
|
|
||||||
addClasses(stop: TrainStop, index: number) {
|
|
||||||
return {
|
|
||||||
confirmed: stop.confirmed,
|
|
||||||
stopped: stop.stopped,
|
|
||||||
begin: stop.beginsHere,
|
|
||||||
end: stop.terminatesHere,
|
|
||||||
delayed: stop.departureDelay > 0,
|
|
||||||
sbl: /sbl/gi.test(stop.stopName),
|
|
||||||
[stop.stopType.replaceAll(', ', '-')]:
|
|
||||||
stop.stopType.match(new RegExp('ph|pm|pt')) && !stop.confirmed && !stop.beginsHere,
|
|
||||||
'minor-stop-active': this.activeMinorStops.includes(index),
|
|
||||||
'last-confirmed': index == this.lastConfirmed && !stop.terminatesHere
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
onImageError(e: Event) {
|
|
||||||
const imageEl = e.target as HTMLImageElement;
|
|
||||||
imageEl.src = '/images/icon-unknown.png';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -155,17 +231,18 @@ export default defineComponent({
|
|||||||
@import '../../styles/responsive.scss';
|
@import '../../styles/responsive.scss';
|
||||||
|
|
||||||
$barClr: #b1b1b1;
|
$barClr: #b1b1b1;
|
||||||
$confirmedClr: #18d818;
|
$confirmedClr: #4ae24a;
|
||||||
$stoppedClr: #f55f31;
|
$stoppedClr: #f55f31;
|
||||||
$haltClr: #f8bb36;
|
$haltClr: #f8bb36;
|
||||||
$stopNameClr: #22a8d1;
|
|
||||||
|
$blinkAnim: 0.5s ease-in-out alternate infinite blink;
|
||||||
|
|
||||||
@keyframes blink {
|
@keyframes blink {
|
||||||
from {
|
from {
|
||||||
background-color: $barClr;
|
border-color: $barClr;
|
||||||
}
|
}
|
||||||
to {
|
to {
|
||||||
background-color: $confirmedClr;
|
border-color: $confirmedClr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,216 +258,247 @@ $stopNameClr: #22a8d1;
|
|||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-bar {
|
.stops {
|
||||||
position: absolute;
|
|
||||||
z-index: 10;
|
|
||||||
|
|
||||||
top: -1px;
|
|
||||||
left: -17px;
|
|
||||||
|
|
||||||
height: 100%;
|
|
||||||
width: 3px;
|
|
||||||
|
|
||||||
background-color: $barClr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stop-name {
|
|
||||||
background: $stopNameClr;
|
|
||||||
padding: 0.3em 0.5em;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
&.misc {
|
|
||||||
background: gray;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.stop-comment {
|
|
||||||
background: forestgreen;
|
|
||||||
padding: 0.3em 0.5em;
|
|
||||||
|
|
||||||
max-width: 250px;
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
width: 2em;
|
|
||||||
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
span {
|
|
||||||
font-size: 0.8em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.stop_list {
|
|
||||||
margin-left: 2.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.stop_list > li.stop {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
overflow-y: hidden;
|
||||||
|
gap: 5px;
|
||||||
|
|
||||||
padding: 0 0.5em;
|
padding: 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
&.sbl {
|
.stop {
|
||||||
.stop-date {
|
// Begin stop
|
||||||
display: none;
|
&[data-position='begin'] {
|
||||||
}
|
.node {
|
||||||
|
|
||||||
.stop-name {
|
|
||||||
background: none;
|
|
||||||
color: #aaa;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&[class*='ph'] > .stop_info > .indicator {
|
|
||||||
border-color: $stopNameClr;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[class*='pt'] > .stop_info > .indicator {
|
|
||||||
border-color: #818181;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.begin {
|
|
||||||
.stop_info > .indicator {
|
|
||||||
border-color: lightgreen;
|
border-color: lightgreen;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stop_info > .progress-bar {
|
.line_node-top {
|
||||||
background: lightgreen;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.end {
|
// End stop
|
||||||
.stop_info > .indicator {
|
&[data-position='end'] {
|
||||||
|
.node {
|
||||||
border-color: salmon;
|
border-color: salmon;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stop_info > .progress-bar {
|
.line_node-bottom {
|
||||||
background: salmon;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.minor-stop-active {
|
// Stop types
|
||||||
.stop_info > .progress-bar {
|
&[data-stop-type*='pt'] .node {
|
||||||
animation: 0.5s ease-in-out alternate infinite blink;
|
border-color: #818181;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stop_line > .progress-bar {
|
&[data-stop-type*='ph'] .node {
|
||||||
animation: 0.5s ease-in-out alternate infinite blink;
|
border-color: $haltClr;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-minor-stop-active='true'] {
|
||||||
|
.progress > .line {
|
||||||
|
animation: $blinkAnim;
|
||||||
|
}
|
||||||
|
|
||||||
|
& + div {
|
||||||
|
.progress > .line_node-top {
|
||||||
|
animation: $blinkAnim;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.last-confirmed {
|
// Last confirmed outpost / checkpoint
|
||||||
.stop_line > .progress-bar {
|
&[data-last-confirmed='true'] {
|
||||||
animation: 0.5s ease-in-out alternate infinite blink;
|
.progress > .line_connection {
|
||||||
|
animation: $blinkAnim;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress > .line_node-bottom {
|
||||||
|
animation: $blinkAnim;
|
||||||
|
}
|
||||||
|
|
||||||
|
& + div {
|
||||||
|
.progress > .line_node-top {
|
||||||
|
animation: $blinkAnim;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.confirmed {
|
// Confirmed status
|
||||||
.stop_info {
|
&[data-status='confirmed'] {
|
||||||
> .progress-bar {
|
.progress > .node {
|
||||||
background-color: $confirmedClr;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .indicator {
|
|
||||||
border-color: $confirmedClr;
|
border-color: $confirmedClr;
|
||||||
}
|
}
|
||||||
}
|
.progress > .line {
|
||||||
|
border-left: 2px solid $confirmedClr;
|
||||||
.stop_line > .progress-bar {
|
border-right: 2px solid $confirmedClr;
|
||||||
background-color: $confirmedClr;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.stopped {
|
// Stopped status
|
||||||
.stop_info {
|
&[data-status='stopped'] {
|
||||||
> .indicator {
|
.progress > .node {
|
||||||
border-color: $stoppedClr;
|
border-color: $stoppedClr;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .stop-bar {
|
.progress > .line_node {
|
||||||
background: $stoppedClr;
|
border-color: $stoppedClr;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.stop_line {
|
|
||||||
font-size: 0.8em;
|
|
||||||
color: #ccc;
|
|
||||||
|
|
||||||
padding: 0.35em 0;
|
|
||||||
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.line-segment {
|
|
||||||
color: $barClr;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.stop_info {
|
// Unused so far
|
||||||
|
&[data-track-count-departure='2'] {
|
||||||
|
.progress > .line {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-track-count-arrival='2'] {
|
||||||
|
.progress > .line_node-top {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-track-count-arrival='1'] {
|
||||||
|
.progress > .line_node-top {
|
||||||
|
width: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-electrified-departure] {
|
||||||
|
.stop_line > .line-speed > .speed-departure {
|
||||||
|
color: #00c1c7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-electrified-arrival] {
|
||||||
|
.stop_line > .line-speed > .speed-next-arrival {
|
||||||
|
color: #00c1c7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stop_info,
|
||||||
|
.stop_line {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 30px 40px auto 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-speed {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
color: #9b9b9b;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stop_info {
|
||||||
position: relative;
|
position: relative;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
flex-wrap: wrap;
|
.stop_line {
|
||||||
}
|
font-size: 0.8em;
|
||||||
|
color: #ccc;
|
||||||
.stop-bar {
|
margin-top: 5px;
|
||||||
position: absolute;
|
}
|
||||||
top: 0;
|
|
||||||
left: -17px;
|
|
||||||
|
|
||||||
z-index: 10;
|
|
||||||
|
|
||||||
width: 3px;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.distance {
|
|
||||||
position: absolute;
|
|
||||||
|
|
||||||
top: 50%;
|
|
||||||
transform: translate(-100%, -50%);
|
|
||||||
|
|
||||||
margin-left: -1.75rem;
|
|
||||||
|
|
||||||
|
.distance {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
font-size: 0.75em;
|
font-size: 0.75em;
|
||||||
color: #d6d6d6;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.indicator {
|
.progress {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
& > .node {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 11;
|
|
||||||
|
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: -1rem;
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
transform: translate(-47%, -50%);
|
z-index: 15;
|
||||||
|
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
|
||||||
width: 15px;
|
width: 15px;
|
||||||
height: 15px;
|
height: 15px;
|
||||||
|
|
||||||
background: var(--clr-secondary);
|
background-color: var(--clr-secondary);
|
||||||
border: 3px solid $barClr;
|
border: 4px solid $barClr;
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
& > .line {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, 0);
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
// background-color: $barClr;
|
||||||
|
border-left: 2px solid $barClr;
|
||||||
|
border-right: 2px solid $barClr;
|
||||||
|
|
||||||
|
&.line_connection {
|
||||||
|
transform: translate(-50%, -6px);
|
||||||
|
height: calc(100% + 12px);
|
||||||
|
// height: calc(100% + 0.25em);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.line_node-top {
|
||||||
|
top: 0;
|
||||||
|
height: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.line_node-bottom {
|
||||||
|
top: 50%;
|
||||||
|
height: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.line_stop {
|
||||||
|
border-color: $stoppedClr;
|
||||||
|
z-index: 11;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-comments {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25em;
|
||||||
|
|
||||||
|
margin: 0.25em 0;
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 1.2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-line-info {
|
||||||
|
.scenery-change-name {
|
||||||
|
position: relative;
|
||||||
|
margin: 0.25em 0;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
height: 2px;
|
||||||
|
width: 30px;
|
||||||
|
background-color: #aaa;
|
||||||
|
|
||||||
|
top: 50%;
|
||||||
|
right: calc(100% + 5px);
|
||||||
|
transform: translate(0, -50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,282 @@
|
|||||||
|
<template>
|
||||||
|
<div class="dropdown" @keydown.esc="showOptions = false" @focusout="showOptions = false">
|
||||||
|
<div class="bg" v-if="showOptions" @click="showOptions = false"></div>
|
||||||
|
|
||||||
|
<button class="filter-button btn--filled btn--image" @click="toggleShowOptions" ref="button">
|
||||||
|
<img src="/images/icon-stats.svg" alt="Open filters icon" />
|
||||||
|
{{ $t('train-stats.stats-button') }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<transition name="dropdown-anim">
|
||||||
|
<div class="dropdown_wrapper" v-if="showOptions">
|
||||||
|
<h1 class="text--primary">
|
||||||
|
<img src="/images/icon-stats.svg" alt="Open filters icon" />
|
||||||
|
{{ $t('train-stats.title') }}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<hr style="margin: 0.5em 0" />
|
||||||
|
|
||||||
|
<div v-if="apiStore.dataStatuses.connection == Status.Loaded && regionTrains.length > 0">
|
||||||
|
<div class="top-list general">
|
||||||
|
<transition-group tag="ul" name="stats-anim">
|
||||||
|
<li class="badge" key="timetable-count">
|
||||||
|
<span>{{ $t('train-stats.timetable-count') }}</span>
|
||||||
|
<span>
|
||||||
|
<b>{{ regionTrainsWithTT.length }}</b>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="badge" key="avg-speed">
|
||||||
|
<span>{{ $t('train-stats.avg-speed') }}</span>
|
||||||
|
<span>
|
||||||
|
<b>{{ stats.avgSpeed.toFixed(1) }} km/h</b>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="badge" key="avg-distance">
|
||||||
|
<span>{{ $t('train-stats.avg-timetable') }}</span>
|
||||||
|
<span>
|
||||||
|
<b>{{ stats.avgDistance.toFixed(1) }} km</b>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</transition-group>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="top-list categories">
|
||||||
|
<h3>{{ $t('train-stats.top-categories') }}</h3>
|
||||||
|
|
||||||
|
<transition-group tag="ul" name="stats-anim">
|
||||||
|
<li class="badge" v-for="top in stats.topCategories" :key="top.name">
|
||||||
|
<span>{{ top.name }}</span>
|
||||||
|
<span>{{ top.count }}</span>
|
||||||
|
</li>
|
||||||
|
</transition-group>
|
||||||
|
|
||||||
|
<span class="no-data" v-if="stats.topCategories.length == 0">
|
||||||
|
{{ $t('train-stats.no-timetables') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="top-list vehicles">
|
||||||
|
<h3>{{ $t('train-stats.top-vehicles') }}</h3>
|
||||||
|
|
||||||
|
<transition-group tag="ul" name="stats-anim">
|
||||||
|
<li class="badge" v-for="top in stats.topVehicles" :key="top.name">
|
||||||
|
<span>{{ top.name }}</span>
|
||||||
|
<span>{{ top.count }}</span>
|
||||||
|
</li>
|
||||||
|
</transition-group>
|
||||||
|
|
||||||
|
<span class="no-data" v-if="stats.topVehicles.length == 0">
|
||||||
|
{{ $t('train-stats.no-vehicles') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="top-list vehicle-types">
|
||||||
|
<h3>{{ $t('train-stats.top-units') }}</h3>
|
||||||
|
|
||||||
|
<transition-group tag="ul" name="stats-anim">
|
||||||
|
<li class="badge" v-for="top in stats.topUnits.slice(0, 7)" :key="top.name">
|
||||||
|
<span>{{ top.name }}</span>
|
||||||
|
<span>{{ top.count }}</span>
|
||||||
|
</li>
|
||||||
|
</transition-group>
|
||||||
|
|
||||||
|
<span class="no-data" v-if="stats.topUnits.length == 0">
|
||||||
|
{{ $t('train-stats.no-units') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="apiStore.dataStatuses.connection != Status.Loaded">
|
||||||
|
{{ $t('train-stats.stats-loading') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="no-data" v-else>
|
||||||
|
{{ $t('train-stats.no-stats') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import { useMainStore } from '../../store/mainStore';
|
||||||
|
import { Status } from '../../typings/common';
|
||||||
|
import { useApiStore } from '../../store/apiStore';
|
||||||
|
|
||||||
|
interface ITop {
|
||||||
|
name: string;
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IStats {
|
||||||
|
timetableCount: number;
|
||||||
|
avgSpeed: number;
|
||||||
|
avgDistance: number;
|
||||||
|
topCategories: ITop[];
|
||||||
|
topVehicles: ITop[];
|
||||||
|
topUnits: ITop[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareTop(top1: ITop, top2: ITop) {
|
||||||
|
return Math.sign(top2.count - top1.count) || top1.name.localeCompare(top2.name, 'pl-PL');
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showOptions: false,
|
||||||
|
store: useMainStore(),
|
||||||
|
apiStore: useApiStore(),
|
||||||
|
Status: Status.Data
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
regionTrains() {
|
||||||
|
return this.store.trainList.filter((train) => train.region == this.store.region.id);
|
||||||
|
},
|
||||||
|
|
||||||
|
regionTrainsWithTT() {
|
||||||
|
return this.regionTrains.filter((train) => train.timetableData);
|
||||||
|
},
|
||||||
|
|
||||||
|
stats() {
|
||||||
|
const stats = this.regionTrains.reduce(
|
||||||
|
(acc, train, i, arr) => {
|
||||||
|
// AVG SPEED
|
||||||
|
acc.avgSpeed += train.speed / arr.length;
|
||||||
|
|
||||||
|
// TOP VEHICLES
|
||||||
|
const locoType = train.locoType.split('-')[0];
|
||||||
|
const topVehicle = acc.topVehicles.find((top) => top.name == locoType);
|
||||||
|
|
||||||
|
if (!topVehicle) acc.topVehicles.push({ name: locoType, count: 1 });
|
||||||
|
else topVehicle.count++;
|
||||||
|
|
||||||
|
// TOP UNITS
|
||||||
|
const unitType = train.locoType;
|
||||||
|
const topUnit = acc.topUnits.find((top) => top.name == unitType);
|
||||||
|
|
||||||
|
if (!topUnit) acc.topUnits.push({ name: unitType, count: 1 });
|
||||||
|
else topUnit.count++;
|
||||||
|
|
||||||
|
if (train.timetableData !== undefined) {
|
||||||
|
acc.timetableCount++;
|
||||||
|
// AVG DISTANCE
|
||||||
|
acc.avgDistance += train.timetableData.routeDistance;
|
||||||
|
|
||||||
|
// TOP CATEGORIES
|
||||||
|
const topCategory = acc.topCategories.find(
|
||||||
|
(top) => top.name == train.timetableData!.category
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!topCategory)
|
||||||
|
acc.topCategories.push({ name: train.timetableData!.category, count: 1 });
|
||||||
|
else topCategory.count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == arr.length - 1 && acc.timetableCount != 0) {
|
||||||
|
acc.avgDistance /= acc.timetableCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timetableCount: 0,
|
||||||
|
avgDistance: 0,
|
||||||
|
avgSpeed: 0,
|
||||||
|
topCategories: [],
|
||||||
|
topUnits: [],
|
||||||
|
topVehicles: []
|
||||||
|
} as IStats
|
||||||
|
);
|
||||||
|
|
||||||
|
stats.topCategories.sort(compareTop);
|
||||||
|
stats.topUnits.sort(compareTop);
|
||||||
|
stats.topVehicles.sort(compareTop);
|
||||||
|
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
toggleShowOptions() {
|
||||||
|
this.showOptions = !this.showOptions;
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (this.showOptions) (this.$refs['button'] as HTMLButtonElement)?.focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '../../styles/dropdown.scss';
|
||||||
|
@import '../../styles/badge.scss';
|
||||||
|
@import '../../styles/responsive.scss';
|
||||||
|
|
||||||
|
h1 img {
|
||||||
|
vertical-align: text-bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-data {
|
||||||
|
font-size: 1.1em;
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-list ul {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5em;
|
||||||
|
|
||||||
|
// @include smallScreen {
|
||||||
|
// justify-content: center;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
& > span:first-child {
|
||||||
|
background-color: $accentCol;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown_wrapper {
|
||||||
|
max-width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-anim {
|
||||||
|
&-move,
|
||||||
|
&-enter-active,
|
||||||
|
&-leave-active {
|
||||||
|
transition: all 250ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-enter-from,
|
||||||
|
&-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-leave-active {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include smallScreen {
|
||||||
|
h1,
|
||||||
|
.no-data {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,17 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<transition name="status-anim" mode="out-in" tag="div" class="train-table">
|
<transition name="status-anim" mode="out-in" tag="div" class="train-table">
|
||||||
<div :key="store.dataStatuses.trains">
|
<div :key="apiStore.dataStatuses.connection">
|
||||||
<div class="table-info" key="offline" v-if="store.isOffline">
|
<div class="table-warning" key="offline" v-if="store.isOffline">
|
||||||
{{ $t('app.offline') }}
|
{{ $t('app.offline') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Loading v-else-if="trains.length == 0 && store.dataStatuses.trains == 0" key="loading" />
|
<Loading v-else-if="apiStore.dataStatuses.connection == Status.Loading" key="loading" />
|
||||||
|
|
||||||
<div
|
<div class="table-warning" key="no-trains" v-else-if="trains.length == 0">
|
||||||
class="table-info"
|
|
||||||
key="no-trains"
|
|
||||||
v-else-if="trains.length == 0 && store.dataStatuses.trains != 0"
|
|
||||||
>
|
|
||||||
{{ $t('trains.no-trains') }}
|
{{ $t('trains.no-trains') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -24,7 +20,7 @@
|
|||||||
@click.stop="selectModalTrain(train.trainId, $event.currentTarget)"
|
@click.stop="selectModalTrain(train.trainId, $event.currentTarget)"
|
||||||
@keydown.enter="selectModalTrain(train.trainId, $event.currentTarget)"
|
@keydown.enter="selectModalTrain(train.trainId, $event.currentTarget)"
|
||||||
>
|
>
|
||||||
<TrainInfo :train="train" />
|
<TrainInfo :train="train" :extended="false" />
|
||||||
</li>
|
</li>
|
||||||
</transition-group>
|
</transition-group>
|
||||||
</div>
|
</div>
|
||||||
@@ -34,11 +30,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, inject, PropType, Ref } from 'vue';
|
import { defineComponent, inject, PropType, Ref } from 'vue';
|
||||||
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
||||||
import Train from '../../scripts/interfaces/Train';
|
import { useMainStore } from '../../store/mainStore';
|
||||||
import { useStore } from '../../store/mainStore';
|
|
||||||
import Loading from '../Global/Loading.vue';
|
import Loading from '../Global/Loading.vue';
|
||||||
import TrainInfo from './TrainInfo.vue';
|
import TrainInfo from './TrainInfo.vue';
|
||||||
import { Status } from '../../typings/common';
|
import { Status, Train } from '../../typings/common';
|
||||||
|
import { useApiStore } from '../../store/apiStore';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { Loading, TrainInfo },
|
components: { Loading, TrainInfo },
|
||||||
@@ -53,7 +49,8 @@ export default defineComponent({
|
|||||||
mixins: [modalTrainMixin],
|
mixins: [modalTrainMixin],
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
const store = useStore();
|
const store = useMainStore();
|
||||||
|
const apiStore = useApiStore();
|
||||||
const searchedTrain = inject('searchedTrain') as Ref<string>;
|
const searchedTrain = inject('searchedTrain') as Ref<string>;
|
||||||
const searchedDriver = inject('searchedDriver') as Ref<string>;
|
const searchedDriver = inject('searchedDriver') as Ref<string>;
|
||||||
|
|
||||||
@@ -61,6 +58,8 @@ export default defineComponent({
|
|||||||
searchedTrain,
|
searchedTrain,
|
||||||
searchedDriver,
|
searchedDriver,
|
||||||
store,
|
store,
|
||||||
|
apiStore,
|
||||||
|
Status: Status.Data,
|
||||||
sorterActive: inject('sorterActive') as {
|
sorterActive: inject('sorterActive') as {
|
||||||
id: string | number;
|
id: string | number;
|
||||||
dir: number;
|
dir: number;
|
||||||
@@ -72,7 +71,7 @@ export default defineComponent({
|
|||||||
dataStatus() {
|
dataStatus() {
|
||||||
if (this.store.isOffline) return Status.Data.Offline;
|
if (this.store.isOffline) return Status.Data.Offline;
|
||||||
|
|
||||||
if (this.trains.length == 0 && this.store.dataStatuses.trains == Status.Data.Loading)
|
if (this.trains.length == 0 && this.apiStore.dataStatuses.connection == Status.Data.Loading)
|
||||||
return Status.Data.Loading;
|
return Status.Data.Loading;
|
||||||
|
|
||||||
return Status.Data.Loaded;
|
return Status.Data.Loaded;
|
||||||
@@ -105,7 +104,7 @@ export default defineComponent({
|
|||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-info {
|
.table-warning {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
padding: 1em 0;
|
padding: 1em 0;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+55
-2
@@ -1,13 +1,14 @@
|
|||||||
{
|
{
|
||||||
"optionSections": [
|
"optionSections": [
|
||||||
|
"status",
|
||||||
|
"timetables",
|
||||||
"reality",
|
"reality",
|
||||||
"package-access",
|
"package-access",
|
||||||
"access",
|
"access",
|
||||||
"control",
|
"control",
|
||||||
"addons",
|
|
||||||
"blockades",
|
"blockades",
|
||||||
"signals",
|
"signals",
|
||||||
"status"
|
"addons"
|
||||||
],
|
],
|
||||||
|
|
||||||
"options": [
|
"options": [
|
||||||
@@ -138,6 +139,20 @@
|
|||||||
"value": true,
|
"value": true,
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "ASDEK",
|
||||||
|
"name": "ASDEK",
|
||||||
|
"section": "addons",
|
||||||
|
"value": true,
|
||||||
|
"defaultValue": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "noASDEK",
|
||||||
|
"name": "noASDEK",
|
||||||
|
"section": "addons",
|
||||||
|
"value": true,
|
||||||
|
"defaultValue": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "SBL",
|
"id": "SBL",
|
||||||
"name": "SBL",
|
"name": "SBL",
|
||||||
@@ -228,6 +243,20 @@
|
|||||||
"section": "status",
|
"section": "status",
|
||||||
"value": true,
|
"value": true,
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "withActiveTimetables",
|
||||||
|
"name": "withActiveTimetables",
|
||||||
|
"section": "timetables",
|
||||||
|
"value": true,
|
||||||
|
"defaultValue": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "withoutActiveTimetables",
|
||||||
|
"name": "withoutActiveTimetables",
|
||||||
|
"section": "timetables",
|
||||||
|
"value": true,
|
||||||
|
"defaultValue": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"sliders": [
|
"sliders": [
|
||||||
@@ -236,6 +265,7 @@
|
|||||||
"name": "minLevel",
|
"name": "minLevel",
|
||||||
"minRange": 0,
|
"minRange": 0,
|
||||||
"maxRange": 20,
|
"maxRange": 20,
|
||||||
|
"step": 1,
|
||||||
"value": 0,
|
"value": 0,
|
||||||
"defaultValue": 0
|
"defaultValue": 0
|
||||||
},
|
},
|
||||||
@@ -244,14 +274,34 @@
|
|||||||
"name": "maxLevel",
|
"name": "maxLevel",
|
||||||
"minRange": 0,
|
"minRange": 0,
|
||||||
"maxRange": 20,
|
"maxRange": 20,
|
||||||
|
"step": 1,
|
||||||
"value": 20,
|
"value": 20,
|
||||||
"defaultValue": 20
|
"defaultValue": 20
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "min-vmax",
|
||||||
|
"name": "minVmax",
|
||||||
|
"minRange": 0,
|
||||||
|
"maxRange": 200,
|
||||||
|
"step": 10,
|
||||||
|
"value": 0,
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "max-vmax",
|
||||||
|
"name": "maxVmax",
|
||||||
|
"minRange": 0,
|
||||||
|
"maxRange": 200,
|
||||||
|
"step": 10,
|
||||||
|
"value": 200,
|
||||||
|
"defaultValue": 200
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "routes-1t-cat",
|
"id": "routes-1t-cat",
|
||||||
"name": "minOneWayCatenary",
|
"name": "minOneWayCatenary",
|
||||||
"minRange": 0,
|
"minRange": 0,
|
||||||
"maxRange": 5,
|
"maxRange": 5,
|
||||||
|
"step": 1,
|
||||||
"value": 0,
|
"value": 0,
|
||||||
"defaultValue": 0
|
"defaultValue": 0
|
||||||
},
|
},
|
||||||
@@ -260,6 +310,7 @@
|
|||||||
"name": "minOneWay",
|
"name": "minOneWay",
|
||||||
"minRange": 0,
|
"minRange": 0,
|
||||||
"maxRange": 5,
|
"maxRange": 5,
|
||||||
|
"step": 1,
|
||||||
"value": 0,
|
"value": 0,
|
||||||
"defaultValue": 0
|
"defaultValue": 0
|
||||||
},
|
},
|
||||||
@@ -268,6 +319,7 @@
|
|||||||
"name": "minTwoWayCatenary",
|
"name": "minTwoWayCatenary",
|
||||||
"minRange": 0,
|
"minRange": 0,
|
||||||
"maxRange": 5,
|
"maxRange": 5,
|
||||||
|
"step": 1,
|
||||||
"value": 0,
|
"value": 0,
|
||||||
"defaultValue": 0
|
"defaultValue": 0
|
||||||
},
|
},
|
||||||
@@ -276,6 +328,7 @@
|
|||||||
"name": "minTwoWay",
|
"name": "minTwoWay",
|
||||||
"minRange": 0,
|
"minRange": 0,
|
||||||
"maxRange": 5,
|
"maxRange": 5,
|
||||||
|
"step": 1,
|
||||||
"value": 0,
|
"value": 0,
|
||||||
"defaultValue": 0
|
"defaultValue": 0
|
||||||
}
|
}
|
||||||
|
|||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
import enLang from './locales/en.json';
|
||||||
|
import plLang from './locales/pl.json';
|
||||||
|
|
||||||
|
import { createI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
const i18n = createI18n({
|
||||||
|
locale: 'pl',
|
||||||
|
legacy: false,
|
||||||
|
warnHtmlMessage: false,
|
||||||
|
fallbackLocale: 'pl',
|
||||||
|
|
||||||
|
messages: {
|
||||||
|
en: enLang,
|
||||||
|
pl: plLang
|
||||||
|
},
|
||||||
|
enableLegacy: false
|
||||||
|
});
|
||||||
|
|
||||||
|
export default i18n;
|
||||||
+149
-56
@@ -2,6 +2,7 @@
|
|||||||
"donations": {
|
"donations": {
|
||||||
"button-title": "TOSS A COIN",
|
"button-title": "TOSS A COIN",
|
||||||
"header": "Toss a coin to Stacjownik!",
|
"header": "Toss a coin to Stacjownik!",
|
||||||
|
"donator-title": "Project is supported by more than <b>{count}</b> people, including:",
|
||||||
"p1": "<b>Hello o7!</b> This is Spythere, the creator of Stacjownik, Pojazdownik and several other applications that enhance the gameplay of Train Driver 2!",
|
"p1": "<b>Hello o7!</b> This is Spythere, the creator of Stacjownik, Pojazdownik and several other applications that enhance the gameplay of Train Driver 2!",
|
||||||
"p2": "{b1} is a completely free tool, created and continuously developed for the Train Driver 2 simulator community since 2020. However, a part of the project is sustained solely through my private financial contribution. Features such as {b2} or {b3} (operating on my {link} - to which you are warmly invited) must function on a dedicated server where they can collect and process data, and then display it on the website.",
|
"p2": "{b1} is a completely free tool, created and continuously developed for the Train Driver 2 simulator community since 2020. However, a part of the project is sustained solely through my private financial contribution. Features such as {b2} or {b3} (operating on my {link} - to which you are warmly invited) must function on a dedicated server where they can collect and process data, and then display it on the website.",
|
||||||
"p2-b1": "Stacjownik",
|
"p2-b1": "Stacjownik",
|
||||||
@@ -9,12 +10,13 @@
|
|||||||
"p2-b3": "Stacjobot (Stacjownik bot)",
|
"p2-b3": "Stacjobot (Stacjownik bot)",
|
||||||
"p2-a1": "Discord server",
|
"p2-a1": "Discord server",
|
||||||
"p3": "<b>If you have the means and would like to support my work, I would be grateful for any financial assistance that could help cover at least some of the server costs and further enhance the capabilities of the application!</b>",
|
"p3": "<b>If you have the means and would like to support my work, I would be grateful for any financial assistance that could help cover at least some of the server costs and further enhance the capabilities of the application!</b>",
|
||||||
"p4": "Every person who decides to contribute at least {b1} for the development of Stacjownik, will receive (upon a personal request) {img}{b2} of username in the \"Sceneries\" and \"Trains\" tabs of the application, as well as on my Discord server (after verifying the payment author, preferably by providing the username directly with the payment).",
|
"p4": "Every person who decides to contribute at least {b1} (in case of PayPal it must be a payment including additional transaction fees) for the development of Stacjownik, will receive (upon a personal request) {img}{b2} of username in the app and on my Discord server (after verifying the payment author, preferably by providing the username directly with the payment).",
|
||||||
"p4-b1": "5 PLN",
|
"p4-b1": "5 PLN",
|
||||||
"p4-b2": "a symbolic highlight",
|
"p4-b2": "a symbolic highlight",
|
||||||
"p5": "Thank you and enjoy the app!<br />~ Spythere",
|
"p5": "Thank you and enjoy the app!<br />~ Spythere",
|
||||||
"action-exit": "Maybe next time...",
|
"action-exit": "Maybe next time...",
|
||||||
"action-confirm": "DONATE!",
|
"action-paypal": "DONATE WITH PAYPAL",
|
||||||
|
"action-buycoffee": "BUY ME A COFFEE!",
|
||||||
"dispatcher-message": "Dispatcher supporting the Stacjownik project!",
|
"dispatcher-message": "Dispatcher supporting the Stacjownik project!",
|
||||||
"driver-message": "Driver supporting the Stacjownik project!"
|
"driver-message": "Driver supporting the Stacjownik project!"
|
||||||
},
|
},
|
||||||
@@ -24,6 +26,13 @@
|
|||||||
"TWR": "High risk freight train",
|
"TWR": "High risk freight train",
|
||||||
"SKR": "Train with exceeded gauge"
|
"SKR": "Train with exceeded gauge"
|
||||||
},
|
},
|
||||||
|
"update": {
|
||||||
|
"title": "Stacjownik update!",
|
||||||
|
"confirm": "ROGER THAT!",
|
||||||
|
"no-data": "No data about the latest app update has been found",
|
||||||
|
"info-1": "This changelog will be available to see once again after clicking the version number in the footer",
|
||||||
|
"info-2": "The full app changelog available on <a href='https://github.com/Spythere/stacjownik' target='_blank'>the project's GitHub</a>"
|
||||||
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"sceneries": "SCENERIES",
|
"sceneries": "SCENERIES",
|
||||||
"trains": "TRAINS",
|
"trains": "TRAINS",
|
||||||
@@ -39,12 +48,10 @@
|
|||||||
"footer": {
|
"footer": {
|
||||||
"discord": "Stacjownik Discord server"
|
"discord": "Stacjownik Discord server"
|
||||||
},
|
},
|
||||||
"update": {
|
|
||||||
"title": "New version of the app is available!",
|
"vehicle-preview": {
|
||||||
"paragraph1": "Enjoy the application and may the green signal be with you!",
|
"loading": "Loading preview...",
|
||||||
"release-link": "Click here to browse version changelog (GitHub)",
|
"error": "Oops! The vehicle preview seems to be missing! :/"
|
||||||
"confirm-button": "UPDATE NOW",
|
|
||||||
"later-button": "LATER"
|
|
||||||
},
|
},
|
||||||
"data-status": {
|
"data-status": {
|
||||||
"S1-offline": "<b>S1 signal</b> <br> The app is working in offline mode!",
|
"S1-offline": "<b>S1 signal</b> <br> The app is working in offline mode!",
|
||||||
@@ -56,20 +63,6 @@
|
|||||||
"S5-dispatchers": "<b>S5 signal</b> <br> Cannot load dispatchers status data!",
|
"S5-dispatchers": "<b>S5 signal</b> <br> Cannot load dispatchers status data!",
|
||||||
"S5-trains": "<b>S5 signal</b> <br> Cannot load online trains data!"
|
"S5-trains": "<b>S5 signal</b> <br> Cannot load online trains data!"
|
||||||
},
|
},
|
||||||
"desc": {
|
|
||||||
"control-type": "Control type: ",
|
|
||||||
"signals-type": "Signals type: ",
|
|
||||||
"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: ",
|
|
||||||
"default": "This scenery is available by default",
|
|
||||||
"non-public": "This scenery is not public",
|
|
||||||
"unknown": "This scenery isn't recognizable right now",
|
|
||||||
"unavailable": "This scenery is unavailable",
|
|
||||||
"abandoned": "This scenery is no longer supported by its creators",
|
|
||||||
"real": "Scenery with real lines: "
|
|
||||||
},
|
|
||||||
"signals": {
|
"signals": {
|
||||||
"title": "Signal type",
|
"title": "Signal type",
|
||||||
"współczesna": "modern",
|
"współczesna": "modern",
|
||||||
@@ -88,7 +81,20 @@
|
|||||||
"ręczne+SCS": "manual + SCS",
|
"ręczne+SCS": "manual + SCS",
|
||||||
"mechaniczne": "levers (mechanical)",
|
"mechaniczne": "levers (mechanical)",
|
||||||
"mechaniczne+SPK": "levers + SPK",
|
"mechaniczne+SPK": "levers + SPK",
|
||||||
"mechaniczne+SCS": "levers + SCS"
|
"mechaniczne+SCS": "levers + SCS",
|
||||||
|
|
||||||
|
"abbrevs": {
|
||||||
|
"SPK": "SPK",
|
||||||
|
"SCS": "SCS",
|
||||||
|
"SCS-SPK": "S/S",
|
||||||
|
"SPE": "SPE",
|
||||||
|
"ręczne": "R",
|
||||||
|
"ręczne+SPK": "R",
|
||||||
|
"ręczne+SCS": "R",
|
||||||
|
"mechaniczne": "M",
|
||||||
|
"mechaniczne+SPK": "M",
|
||||||
|
"mechaniczne+SCS": "M"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"online": "UNTIL ",
|
"online": "UNTIL ",
|
||||||
@@ -143,7 +149,8 @@
|
|||||||
"filter-withComments": "COMMENTS",
|
"filter-withComments": "COMMENTS",
|
||||||
"filter-twr": "HIGH RISK CARGO",
|
"filter-twr": "HIGH RISK CARGO",
|
||||||
"filter-skr": "EXCEEDED GAUGE",
|
"filter-skr": "EXCEEDED GAUGE",
|
||||||
"filter-twr-skr": "ALL TYPES",
|
"filter-twr-skr": "BOTH TYPES",
|
||||||
|
"filter-all-specials": "ALL",
|
||||||
"filter-common": "NO WARNINGS",
|
"filter-common": "NO WARNINGS",
|
||||||
"filter-passenger": "PASSENGER",
|
"filter-passenger": "PASSENGER",
|
||||||
"filter-freight": "FREIGHT",
|
"filter-freight": "FREIGHT",
|
||||||
@@ -155,9 +162,9 @@
|
|||||||
"filter-clear": "CLEAR FILTERS",
|
"filter-clear": "CLEAR FILTERS",
|
||||||
|
|
||||||
"filter-section-timetable-status": "TIMETABLE STATUS",
|
"filter-section-timetable-status": "TIMETABLE STATUS",
|
||||||
"filter-section-twrskr": "WARNINGS",
|
"filter-section-special": "SPECIAL TYPE",
|
||||||
|
|
||||||
"filter-all": "ALL ENTRIES",
|
"filter-all-statuses": "ALL",
|
||||||
"filter-abandoned": "ABANDONED",
|
"filter-abandoned": "ABANDONED",
|
||||||
"filter-fulfilled": "FULFILLED",
|
"filter-fulfilled": "FULFILLED",
|
||||||
"filter-active": "ACTIVE"
|
"filter-active": "ACTIVE"
|
||||||
@@ -174,7 +181,9 @@
|
|||||||
"signals": "SIGNALLING",
|
"signals": "SIGNALLING",
|
||||||
"addons": "ADDITIONAL PROGRAMS",
|
"addons": "ADDITIONAL PROGRAMS",
|
||||||
"blockades": "BLOCK SIGNALLING",
|
"blockades": "BLOCK SIGNALLING",
|
||||||
"status": "ONLINE STATUS"
|
"status": "ONLINE STATUS",
|
||||||
|
"timetables": "ACTIVE TIMETABLES",
|
||||||
|
"spawns": "OPEN SPAWNS"
|
||||||
},
|
},
|
||||||
|
|
||||||
"all-available": "ALL AVAILABLE",
|
"all-available": "ALL AVAILABLE",
|
||||||
@@ -208,6 +217,9 @@
|
|||||||
"SUP": "SUP (RASP-UZK)",
|
"SUP": "SUP (RASP-UZK)",
|
||||||
"noSUP": "WITHOUT SUP",
|
"noSUP": "WITHOUT SUP",
|
||||||
|
|
||||||
|
"ASDEK": "ASDEK",
|
||||||
|
"noASDEK": "NO ASDEK",
|
||||||
|
|
||||||
"SBL": "AUTOMATIC (SBL)",
|
"SBL": "AUTOMATIC (SBL)",
|
||||||
"PBL": "SEMIAUTOMATIC (PBL)",
|
"PBL": "SEMIAUTOMATIC (PBL)",
|
||||||
|
|
||||||
@@ -217,15 +229,25 @@
|
|||||||
"historical": "HISTORICAL",
|
"historical": "HISTORICAL",
|
||||||
"free": "FREE",
|
"free": "FREE",
|
||||||
"occupied": "OCCUPIED",
|
"occupied": "OCCUPIED",
|
||||||
|
|
||||||
|
"withActiveTimetables": "ACTIVE",
|
||||||
|
"withoutActiveTimetables": "NO ACTIVE",
|
||||||
|
|
||||||
"sliders": {
|
"sliders": {
|
||||||
"min-lvl": "MIN. REQUIRED DISPATCHER LEVEL",
|
"min-lvl": "MIN. REQUIRED DISPATCHER LEVEL",
|
||||||
"max-lvl": "MAX. REQUIRED DISPATCHER LEVEL",
|
"max-lvl": "MAX. REQUIRED DISPATCHER LEVEL",
|
||||||
|
"min-vmax": "MIN. SCENERY ROUTE SPEED",
|
||||||
|
"max-vmax": "MAX. SCENERY ROUTE SPEED",
|
||||||
"routes-1t-cat": "MIN. CATENARY SINGLE TRACK ROUTES",
|
"routes-1t-cat": "MIN. CATENARY SINGLE TRACK ROUTES",
|
||||||
"routes-1t-other": "MIN. OTHER SINGLE TRACK ROUTES",
|
"routes-1t-other": "MIN. OTHER SINGLE TRACK ROUTES",
|
||||||
"routes-2t-cat": "MIN. CATENARY DOUBLE TRACK ROUTES",
|
"routes-2t-cat": "MIN. CATENARY DOUBLE TRACK ROUTES",
|
||||||
"routes-2t-other": "MIN. OTHER DOUBLE TRACK ROUTES"
|
"routes-2t-other": "MIN. OTHER DOUBLE TRACK ROUTES"
|
||||||
},
|
},
|
||||||
"authors-search": "Search by author (other filters apply)",
|
|
||||||
|
"authors-search": "SEARCH BY AUTHOR NAME (other filters apply):",
|
||||||
|
"authors-placeholder": "Enter the author nickname...",
|
||||||
|
"authors-button-title": "Search",
|
||||||
|
|
||||||
"minimum-hours-title": "SHOW ONLY SCENERIES UNTIL:",
|
"minimum-hours-title": "SHOW ONLY SCENERIES UNTIL:",
|
||||||
"now": "NOW",
|
"now": "NOW",
|
||||||
"hour": "h",
|
"hour": "h",
|
||||||
@@ -236,21 +258,54 @@
|
|||||||
"close": "CLOSE FILTERS"
|
"close": "CLOSE FILTERS"
|
||||||
},
|
},
|
||||||
"sceneries": {
|
"sceneries": {
|
||||||
"station": "Station",
|
"headers": {
|
||||||
"min-lvl": "Min. dispatcher\nlevel",
|
"station": "Scenery",
|
||||||
|
"min-lvl": "Scenery\nlevel",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"dispatcher": "Dispatcher",
|
"dispatcher": "Dispatcher",
|
||||||
"dispatcher-lvl": "Dispatcher\nlevel",
|
"dispatcher-lvl": "Dispatcher\nlevel",
|
||||||
"routes": "Routes\ndouble / single",
|
"routes-single": "1-track\nroutes",
|
||||||
|
"routes-double": "2-track\nroutes",
|
||||||
"general": "General info",
|
"general": "General info",
|
||||||
"user": "Drivers online",
|
"user": "Drivers online",
|
||||||
|
"like": "Dispatcher rating",
|
||||||
"spawn": "Spawns online",
|
"spawn": "Spawns online",
|
||||||
"timetableAll": "Active timetables",
|
"timetableAll": "Active timetables",
|
||||||
"timetableConfirmed": "Confirmed timetables",
|
"timetableConfirmed": "Confirmed timetables",
|
||||||
"timetableUnconfirmed": "Unconfirmed timetables",
|
"timetableUnconfirmed": "Unconfirmed timetables"
|
||||||
|
},
|
||||||
|
"info": {
|
||||||
|
"control-type": "Control type: ",
|
||||||
|
"signals-type": "Signals type: ",
|
||||||
|
"SBL": "This scenery has automatic block signalling (ABS/SBL) system on following routes: ",
|
||||||
|
"SUP": "Requires the SUP program (level crossing remote control)",
|
||||||
|
"ASDEK": "Requires the ASDEK program (defect detection of moving rolling stock)",
|
||||||
|
"TWB-all": "This scenery has two-way route blockade on all routes",
|
||||||
|
"TWB-routes": "This scenery has two-way route blockade on following routes: ",
|
||||||
|
"default": "This scenery is available by default",
|
||||||
|
"non-public": "This scenery is not public",
|
||||||
|
"unavailable": "This scenery is unavailable",
|
||||||
|
"abandoned": "This scenery is no longer supported by its creators",
|
||||||
|
"unknown": "This scenery isn't recognizable right now",
|
||||||
|
"real": "Scenery with real lines: ",
|
||||||
|
"double-track-routes-catenary": "Electrified double-track routes count: ",
|
||||||
|
"single-track-routes-catenary": "Electrified single-track routes count: ",
|
||||||
|
"double-track-routes-other": "Not electrified double-track routes count: ",
|
||||||
|
"single-track-routes-other": "Not electrified single-track routes count: "
|
||||||
|
},
|
||||||
"no-stations": "No stations to show here!",
|
"no-stations": "No stations to show here!",
|
||||||
"scenery-search": "Search for scenery..."
|
"scenery-search": "Search for scenery..."
|
||||||
},
|
},
|
||||||
|
"station-stats": {
|
||||||
|
"u-factor": "U-factor",
|
||||||
|
"u-factor-tooltip": "(?) Current server traffic factor (driver count divided by dispatcher count)",
|
||||||
|
"avg-timetable-count": "Average timetable count for one dispatcher:",
|
||||||
|
"single-track-count": "Available single track routes:",
|
||||||
|
"double-track-count": "Available double track routes:",
|
||||||
|
"electrified": "(electrified)",
|
||||||
|
"not-electrified": "(not electr.)",
|
||||||
|
"open-spawns": "Open spawns:"
|
||||||
|
},
|
||||||
"trains": {
|
"trains": {
|
||||||
"no-trains": "No trains to show here!",
|
"no-trains": "No trains to show here!",
|
||||||
"loading": "Loading train data...",
|
"loading": "Loading train data...",
|
||||||
@@ -292,7 +347,24 @@
|
|||||||
"last-seen-ago": "since {minutes} minutes",
|
"last-seen-ago": "since {minutes} minutes",
|
||||||
|
|
||||||
"scenery-offline": "Offline ride",
|
"scenery-offline": "Offline ride",
|
||||||
"timeout": "An error occured while trying to refresh SWDR timetable data!"
|
"timeout": "An error occured while trying to refresh SWDR timetable data!",
|
||||||
|
|
||||||
|
"journal-button": "DRIVER'S JOURNAL"
|
||||||
|
},
|
||||||
|
"train-stats": {
|
||||||
|
"stats-button": "STATISTICS",
|
||||||
|
"title": "STATISTICS ONLINE",
|
||||||
|
"timetable-count": "ACTIVE TIMETABLES",
|
||||||
|
"avg-speed": "AVG SPEED",
|
||||||
|
"avg-timetable": "AVG TIMETABLE",
|
||||||
|
"top-categories": "Timetable categories",
|
||||||
|
"top-vehicles": "Vehicles online",
|
||||||
|
"top-units": "Common units online",
|
||||||
|
"stats-loading": "Loading...",
|
||||||
|
"no-timetables": "No active timetables in this region!",
|
||||||
|
"no-vehicles": "No active vehicles in this region!",
|
||||||
|
"no-units": "No active units in this region!",
|
||||||
|
"no-stats": "No statistics available for the current region!"
|
||||||
},
|
},
|
||||||
"journal": {
|
"journal": {
|
||||||
"title": "DISPATCHER HISTORY",
|
"title": "DISPATCHER HISTORY",
|
||||||
@@ -313,6 +385,7 @@
|
|||||||
"timetable-active": "ACTIVE",
|
"timetable-active": "ACTIVE",
|
||||||
"timetable-fulfilled": "FULFILLED",
|
"timetable-fulfilled": "FULFILLED",
|
||||||
"timetable-abandoned": "ABANDONED",
|
"timetable-abandoned": "ABANDONED",
|
||||||
|
"timetable-online-button": "ONLINE TIMETABLE",
|
||||||
|
|
||||||
"online-since": "ONLINE SINCE",
|
"online-since": "ONLINE SINCE",
|
||||||
"duty-lasted": "The duty lasted",
|
"duty-lasted": "The duty lasted",
|
||||||
@@ -321,7 +394,7 @@
|
|||||||
"minutes": "{value} min | {value} mins",
|
"minutes": "{value} min | {value} mins",
|
||||||
"seconds": "{value} s",
|
"seconds": "{value} s",
|
||||||
|
|
||||||
"stock-info": "EXTRA INFO",
|
"stock-info": "DETAILS",
|
||||||
"stock-length": "Length",
|
"stock-length": "Length",
|
||||||
"stock-mass": "Mass",
|
"stock-mass": "Mass",
|
||||||
"stock-max-speed": "Max. speed",
|
"stock-max-speed": "Max. speed",
|
||||||
@@ -331,29 +404,49 @@
|
|||||||
"last-seen-at": "Last seen at",
|
"last-seen-at": "Last seen at",
|
||||||
"currently-at": "Currently at",
|
"currently-at": "Currently at",
|
||||||
|
|
||||||
"stats-title": "DRIVING STATISTICS OF",
|
"driver-stats": {
|
||||||
|
"button": "DRIVER STATS",
|
||||||
|
"title": "{name}'s DRIVER STATS",
|
||||||
|
"info": "Enter a proper nickname into filters [F] to see user's driving statistics!",
|
||||||
|
"timetables": "TIMETABLES",
|
||||||
|
"longest-timetable": "LONGEST TIMETABLE",
|
||||||
|
"avg-timetable": "AVERAGE TIMETABLE LENGTH",
|
||||||
|
"distance": "DISTANCE",
|
||||||
|
"stations": "STATIONS"
|
||||||
|
},
|
||||||
|
|
||||||
"stats-timetables": "TIMETABLES",
|
"daily-stats": {
|
||||||
"stats-longest-timetable": "LONGEST TIMETABLE",
|
"button": "DAILY STATS",
|
||||||
"stats-avg-timetable": "AVERAGE TIMETABLE LENGTH",
|
"title": "STATS OF THE DAY",
|
||||||
"stats-distance": "DISTANCE",
|
"info": "Today's statistics are unavailable yet!",
|
||||||
"stats-stations": "STATIONS",
|
"total": "Issued timetables: {count} (total distance: {distance})",
|
||||||
|
"longest": "The longest timetable: #{id} (made by {author} for {driver}, distance: {distance})",
|
||||||
|
"most-active-dr": "The most active dispatcher: {dispatcher} (created {count})",
|
||||||
|
"most-active-dr-many": "The most active dispatchers: {dispatchers} (created {count} each)",
|
||||||
|
"most-active-driver": "The most active driver: {driver} (total driven distance: {distance})",
|
||||||
|
"longest-duties": "The longest service: {dispatcher} at {station} (duration: {duration})",
|
||||||
|
"count": "timetable | timetables",
|
||||||
|
|
||||||
"timetable-stats-title": "Daily stats on {date}",
|
"rippedSwitches": "RIPPED SWITCHES",
|
||||||
"timetable-stats-total": "Issued timetables: {count} (total distance: {distance})",
|
"derailments": "DERAILMENTS",
|
||||||
"timetable-stats-longest": "The longest timetable: #{id} (made by {author} for {driver}, distance: {distance})",
|
"skippedStopSignals": "SKIPPED STOP SIGNALS",
|
||||||
"timetable-stats-most-active-dr": "The most active dispatcher: {dispatcher} (created {count})",
|
"radioStops": "RADIOSTOPS",
|
||||||
"timetable-stats-most-active-dr-many": "The most active dispatchers: {dispatchers} (created {count} each)",
|
"kills": "KILLS"
|
||||||
"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",
|
"dispatcher-stats": {
|
||||||
|
"button": "DISPATCHER STATS",
|
||||||
"daily-stats-title": "DAILY STATS",
|
"title": "{name}'s DISPATCHER STATS",
|
||||||
"daily-stats-info": "Today's statistics are unavailable yet!",
|
"empty": "This user has no statistics saved yet!",
|
||||||
|
"info": "Enter a proper nickname into filters [F] to see user's dispatcher statistics!",
|
||||||
"driver-stats-title": "DRIVER STATS",
|
"services-count": "SERVICES",
|
||||||
"driver-stats-info": "Enter a proper nickname into filters [F] to see user's driving statistics!",
|
"service-max": "MAX SERVICE DURATION",
|
||||||
|
"service-avg": "AVG SERVICE DURATION",
|
||||||
|
"timetables-count": "ISSUED TIMETABLES",
|
||||||
|
"timetables-sum": "TIMETABLES DISTANCE SUM",
|
||||||
|
"timetables-max": "LONGEST TIMETABLE",
|
||||||
|
"timetables-avg": "AVG TIMETABLE DISTANCE"
|
||||||
|
},
|
||||||
|
|
||||||
"stats-loading": "Fetching statistics...",
|
"stats-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! :/",
|
||||||
@@ -389,8 +482,8 @@
|
|||||||
"two-way-routes": "Two way routes",
|
"two-way-routes": "Two way routes",
|
||||||
|
|
||||||
"option-active-timetables": "Active timetables",
|
"option-active-timetables": "Active timetables",
|
||||||
"option-timetables-history": "Timetables history",
|
"option-timetables-history": "Timetables history PL1",
|
||||||
"option-dispatchers-history": "Dispatchers history",
|
"option-dispatchers-history": "Dispatchers history PL1",
|
||||||
|
|
||||||
"timetable-author-title": "Issued by",
|
"timetable-author-title": "Issued by",
|
||||||
"timetable-author-unknown": "Author unknown",
|
"timetable-author-unknown": "Author unknown",
|
||||||
|
|||||||
@@ -1,434 +0,0 @@
|
|||||||
{
|
|
||||||
"general": {
|
|
||||||
"and": " oraz ",
|
|
||||||
"refresh": "ODŚWIEŻ",
|
|
||||||
"TWR": "Towar niebezpieczny wysokiego ryzyka",
|
|
||||||
"SKR": "Przekroczona skrajnia"
|
|
||||||
},
|
|
||||||
"app": {
|
|
||||||
"sceneries": "SCENERIE",
|
|
||||||
"trains": "POCIĄGI",
|
|
||||||
"journal": "DZIENNIK",
|
|
||||||
"loading": "Pobieranie danych...",
|
|
||||||
"support": "Wspomóż projekt",
|
|
||||||
"error": "Wystąpił problem z załadowaniem danych!",
|
|
||||||
"no-result": "Brak wyników o podanych kryteriach!",
|
|
||||||
"migration-warning": "Usługi Stacjownika będą niedostępne w godzinach 1:00-3:00 2 czerwca 2022r. z powodu migracji hostingów API!",
|
|
||||||
"migration-confirm": "Przyjąłem!",
|
|
||||||
"offline": "Aplikacja w trybie offline!"
|
|
||||||
},
|
|
||||||
"footer": {
|
|
||||||
"discord": "Serwer Discord Stacjownika"
|
|
||||||
},
|
|
||||||
"update": {
|
|
||||||
"title": "Nowa wersja Stacjownika jest dostępna!",
|
|
||||||
"paragraph1": "Miłego korzystania z aplikacji i niech S2 będzie z wami!",
|
|
||||||
"release-link": "Kliknij, aby przejrzeć listę zmian (GitHub)",
|
|
||||||
"confirm-button": "ZAKTUALIZUJ",
|
|
||||||
"later-button": "PÓŹNIEJ"
|
|
||||||
},
|
|
||||||
"data-status": {
|
|
||||||
"S1-offline": "<b>Sygnał S1</b> <br> Aplikacja działa w trybie offline!",
|
|
||||||
"S1a-connection": "<b>Sygnał S1a</b> <br> Błąd podczas próby połączenia się z API Stacjownika!",
|
|
||||||
"S1a-sceneries": "<b>Sygnał S1a</b> <br> Błąd podczas pobierania danych o sceneriach online!",
|
|
||||||
"S2": "<b>Sygnał S2</b> <br> Pomyślnie załadowano dane!",
|
|
||||||
"S3": "<b>Sygnał S3</b> <br> Pobieranie danych...",
|
|
||||||
"S5-timetables": "<b>Sygnał S5</b> <br> Rozkłady jazdy mogą być niekompletne!",
|
|
||||||
"S5-dispatchers": "<b>Sygnał S5</b> <br> Błąd podczas pobierania danych o statusach dyżurnych ruchu!",
|
|
||||||
"S5-trains": "<b>Sygnał S5</b> <br> Błąd podczas pobierania danych o pociągach online!"
|
|
||||||
},
|
|
||||||
"desc": {
|
|
||||||
"control-type": "Sterowanie: ",
|
|
||||||
"signals-type": "Sygnalizacja: ",
|
|
||||||
"SBL": "Sceneria posiada SBL na szlakach: ",
|
|
||||||
"SUP": "Wymaga programu SUP do kontroli systemu RASP-UZK",
|
|
||||||
"TWB-all": "Sceneria posiada blokadę dwukierunkową na wszystkich szlakach",
|
|
||||||
"TWB-routes": "Sceneria posiada blokadę dwukierunkową na szlakach: ",
|
|
||||||
"default": "Sceneria dostępna domyślnie w paczce z grą",
|
|
||||||
"non-public": "Sceneria niepubliczna",
|
|
||||||
"unavailable": "Sceneria niedostępna",
|
|
||||||
"unknown": "Nieznana sceneria",
|
|
||||||
"real": "Sceneria z realnymi liniami kolejowymi: ",
|
|
||||||
"abandoned": "Sceneria wycofana z rozgrywki"
|
|
||||||
},
|
|
||||||
"signals": {
|
|
||||||
"title": "Sygnalizacja",
|
|
||||||
"współczesna": "współczesna",
|
|
||||||
"mieszana": "mieszana",
|
|
||||||
"kształtowa": "kształtowa",
|
|
||||||
"historyczna": "historyczna"
|
|
||||||
},
|
|
||||||
"controls": {
|
|
||||||
"title": "Sterowanie",
|
|
||||||
"SPK": "SPK",
|
|
||||||
"SCS": "SCS",
|
|
||||||
"SCS-SPK": "SCS/SPK",
|
|
||||||
"SPE": "SPE",
|
|
||||||
"ręczne": "ręczne",
|
|
||||||
"ręczne+SPK": "ręczne z SPK",
|
|
||||||
"ręczne+SCS": "ręczne z SCS",
|
|
||||||
"mechaniczne": "mechaniczne",
|
|
||||||
"mechaniczne+SPK": "mechaniczne z SPK",
|
|
||||||
"mechaniczne+SCS": "mechaniczne z SCS"
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"online": "DO ",
|
|
||||||
"free": "WOLNA",
|
|
||||||
"ending": "KOŃCZY",
|
|
||||||
"not-signed": "NIEZALOGOWANY",
|
|
||||||
"no-limit": "BEZ LIMITU",
|
|
||||||
"unavailable": "NIEDOSTĘPNY",
|
|
||||||
"brb": "Z/W",
|
|
||||||
"no-space": "BRAK MIEJSCA",
|
|
||||||
"unknown": "NIEZNANY"
|
|
||||||
},
|
|
||||||
"options": {
|
|
||||||
"filters": "FILTRY",
|
|
||||||
"donate": "WESPRZYJ",
|
|
||||||
|
|
||||||
"search-button": "Szukaj",
|
|
||||||
"reset-button": "Zresetuj",
|
|
||||||
|
|
||||||
"sort-title": "SORTUJ WG:",
|
|
||||||
"filter-title": "FILTRUJ WG:",
|
|
||||||
"search-title": "SZUKAJ:",
|
|
||||||
|
|
||||||
"search-train-no": "Nr pociągu",
|
|
||||||
"search-train": "Nr pociągu / #",
|
|
||||||
"search-driver": "Nick maszynisty",
|
|
||||||
"search-dispatcher": "Nick dyżurnego",
|
|
||||||
"search-station": "Nazwa scenerii",
|
|
||||||
"search-author": "Nick autora rozkładu jazdy",
|
|
||||||
"search-issuedFrom": "Sceneria początkowa",
|
|
||||||
"search-timetables-date": "Data rozkładu jazdy (UTC+2 / CEST)",
|
|
||||||
"search-dispatchers-date": "Data służby (UTC+2 / CEST)",
|
|
||||||
"search-date": "Data (UTC+2 / CEST)",
|
|
||||||
|
|
||||||
"sort-routeDistance": "kilometraż",
|
|
||||||
"sort-allStopsCount": "stacje",
|
|
||||||
"sort-beginDate": "data",
|
|
||||||
"sort-timetableId": "ID rozkładu",
|
|
||||||
"sort-timestampFrom": "data",
|
|
||||||
"sort-duration": "czas dyżuru",
|
|
||||||
"sort-id": "id rozkładu",
|
|
||||||
|
|
||||||
"sort-mass": "masa",
|
|
||||||
"sort-speed": "prędkość",
|
|
||||||
"sort-length": "długość",
|
|
||||||
"sort-timetable": "nr pociągu",
|
|
||||||
"sort-progress": "przebyta trasa",
|
|
||||||
"sort-delay": "opóźnienie",
|
|
||||||
"sort-comments": "uwagi ekspl.",
|
|
||||||
|
|
||||||
"filter-withComments": "UWAGI EKSPLOATACYJNE",
|
|
||||||
"filter-noComments": "BEZ UWAG",
|
|
||||||
"filter-twr": "WYS. RYZYKA",
|
|
||||||
"filter-skr": "SKRAJNIA",
|
|
||||||
"filter-twr-skr": "WSZYSTKIE",
|
|
||||||
"filter-common": "ZWYKŁE",
|
|
||||||
"filter-passenger": "PASAŻERSKIE",
|
|
||||||
"filter-freight": "TOWAROWE",
|
|
||||||
"filter-other": "INNE",
|
|
||||||
"filter-noTimetable": "BEZ RJ",
|
|
||||||
"filter-withTimetable": "ROZKŁAD JAZDY",
|
|
||||||
|
|
||||||
"filter-reset": "ZRESETUJ FILTRY",
|
|
||||||
"filter-clear": "WYŁĄCZ FILTRY",
|
|
||||||
|
|
||||||
"filter-section-timetable-status": "STATUS ROZKŁADU JAZDY",
|
|
||||||
"filter-section-twrskr": "UWAGI",
|
|
||||||
|
|
||||||
"filter-all": "WSZYSTKIE",
|
|
||||||
"filter-abandoned": "PORZUCONE",
|
|
||||||
"filter-fulfilled": "WYPEŁNIONE",
|
|
||||||
"filter-active": "AKTYWNE"
|
|
||||||
},
|
|
||||||
"filters": {
|
|
||||||
"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",
|
|
||||||
"unavailableStatus": "NIEDOSTĘPNY",
|
|
||||||
|
|
||||||
"title": "FILTRUJ STACJE",
|
|
||||||
"default": "DOMYŚLNA",
|
|
||||||
"not-default": "POZA PACZKĄ",
|
|
||||||
"real": "REALNA",
|
|
||||||
"fictional": "FIKCYJNA",
|
|
||||||
"unavailable": "NIEDOSTĘPNA",
|
|
||||||
"non-public": "NIEPUBLICZNA",
|
|
||||||
"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 (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",
|
|
||||||
"routes-1t-cat": "SZLAKI JEDNOTOROWE ZELEKTR. (MINIMUM)",
|
|
||||||
"routes-1t-other": "SZLAKI JEDNOTOROWE NIEZELEKTR. (MINIMUM)",
|
|
||||||
"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": "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",
|
|
||||||
"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ę..."
|
|
||||||
},
|
|
||||||
"trains": {
|
|
||||||
"no-trains": "Brak pociągów do wyświetlenia!",
|
|
||||||
"loading": "Pobieranie danych o pociągach...",
|
|
||||||
"offline": "Przejazd offline",
|
|
||||||
|
|
||||||
"stats": "STATYSTYKI RUCHU",
|
|
||||||
"stats-speed": "PRĘDKOŚCI POCIĄGÓW (MIN, ŚR, MAX) [km/h]",
|
|
||||||
"stats-length": "DŁUGOŚCI ROZKŁADÓW (MIN, ŚR, MAX) [km]",
|
|
||||||
"stats-categories": "KATEGORIE RJ",
|
|
||||||
"stats-special-twr": "WYSOKIEGO RYZYKA",
|
|
||||||
"stats-special-skr": "PRZEKROCZONA SKRAJNIA",
|
|
||||||
"stats-locos": "NAJCZĘSTSZE JEDNOSTKI",
|
|
||||||
|
|
||||||
"current-scenery": "na scenerii",
|
|
||||||
"current-signal": "przy semaforze",
|
|
||||||
"current-track": "na szlaku",
|
|
||||||
|
|
||||||
"delayed": "Opóźniony: ",
|
|
||||||
"preponed": "Przed czasem: ",
|
|
||||||
"on-time": "Planowo",
|
|
||||||
|
|
||||||
"route-progress": "Postęp: ",
|
|
||||||
|
|
||||||
"detailed-timetable": "Szczegółowy rozkład jazdy pociągu ",
|
|
||||||
"via-title": "Przez: ",
|
|
||||||
"no-timetable": "brak rozkładu jazdy",
|
|
||||||
"distance-exceeded": "Uwaga! Z powodu wewnętrznego błędu serwera TD2, rozkłady jazdy o kilometrażu powyżej 200km mogą być niepoprawne!",
|
|
||||||
"cars": "Wagony",
|
|
||||||
"EZT": "EZT",
|
|
||||||
"SZT": "SZT",
|
|
||||||
"loco-electric": "Elektrowóz",
|
|
||||||
"loco-diesel": "Spalinowóz",
|
|
||||||
"timetable-comments": "Pociąg z uwagami eksploatacyjnymi",
|
|
||||||
"comment": "Uwagi eksploatacyjne dla: ",
|
|
||||||
"table-limit": "Dla płynności działania strony pokazanych jest tylko 10 pociągów zgodnie z wybranymi filtrami.",
|
|
||||||
|
|
||||||
"last-seen-now": "od niedawna",
|
|
||||||
"last-seen-min": "od minuty",
|
|
||||||
"last-seen-ago": "od {minutes} minut",
|
|
||||||
|
|
||||||
"scenery-offline": "Przejazd offline",
|
|
||||||
|
|
||||||
"timeout": "Wystąpił problem z aktualizacją rozkładów jazdy z SWDR"
|
|
||||||
},
|
|
||||||
"journal": {
|
|
||||||
"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",
|
|
||||||
|
|
||||||
"no-further-data": "Brak dalszych wyników dla podanych parametrów",
|
|
||||||
"loading-further-data": "Ładowanie...",
|
|
||||||
|
|
||||||
"online-since": "ONLINE OD",
|
|
||||||
"duty-lasted": "Dyżur trwał",
|
|
||||||
"hours": "{value} godz.",
|
|
||||||
"minutes": "{value} min.",
|
|
||||||
"seconds": "{value} sek.",
|
|
||||||
|
|
||||||
"route-length": "Kilometraż:",
|
|
||||||
"station-count": "Stacje:",
|
|
||||||
"dispatcher-name": "Autor",
|
|
||||||
"timetable-day": "Rozkład z dnia",
|
|
||||||
"timetable-active": "AKTYWNY",
|
|
||||||
"timetable-fulfilled": "WYPEŁNIONY",
|
|
||||||
"timetable-abandoned": "PORZUCONY",
|
|
||||||
|
|
||||||
"stock-info": "DODATKOWE INFORMACJE",
|
|
||||||
"stock-length": "Długość",
|
|
||||||
"stock-mass": "Masa",
|
|
||||||
"stock-max-speed": "Prędkość maks.",
|
|
||||||
|
|
||||||
"load-data": "Pobierz dalszą historię...",
|
|
||||||
|
|
||||||
"stats-title": "STATYSTYKI MASZYNISTY",
|
|
||||||
|
|
||||||
"last-seen-at": "Ostatnio widziany na: ",
|
|
||||||
"currently-at": "Obecnie na scenerii: ",
|
|
||||||
|
|
||||||
"stats-timetables": "ROZKŁADY JAZDY",
|
|
||||||
"stats-longest-timetable": "NAJDŁUŻSZY RJ",
|
|
||||||
"stats-avg-timetable": "ŚREDNIA DŁUGOŚĆ RJ",
|
|
||||||
"stats-distance": "DYSTANS",
|
|
||||||
"stats-stations": "STACJE",
|
|
||||||
|
|
||||||
"timetable-stats-total": "Stworzone rozkłady jazdy: {count} (łączny dystans: {distance})",
|
|
||||||
"timetable-stats-longest": "Najdłuższy rozkład jazdy: #{id} (stworzony przez dyżurnego {author} dla maszynisty {driver} o dystansie {distance})",
|
|
||||||
"timetable-stats-most-active-dr": "Najaktywniejszy dyżurny: {dispatcher} (stworzył {count})",
|
|
||||||
"timetable-stats-most-active-dr-many": "Najaktywniejsi dyżurni: {dispatchers} (stworzyli po {count})",
|
|
||||||
"timetable-stats-most-active-driver": "Najaktywniejszy maszynista: {driver} (łączny przejechany dystans: {distance})",
|
|
||||||
"timetable-stats-longest-duties": "Najdłuższa służba: {dispatcher} na scenerii {station} (czas trwania: {duration})",
|
|
||||||
|
|
||||||
"timetable-count": "rozkład jazdy | rozkładów jazdy",
|
|
||||||
|
|
||||||
"daily-stats-title": "STATYSTYKI DNIA",
|
|
||||||
"daily-stats-info": "Dzisiejsze statystyki nie są jeszcze dostępne!",
|
|
||||||
|
|
||||||
"driver-stats-title": "STATYSTYKI GRACZA",
|
|
||||||
"driver-stats-info": "Wpisz nazwę użytkownika w filtrach [F], aby zobaczyć jego statystyki maszynisty!",
|
|
||||||
|
|
||||||
"stats-loading": "Pobieranie statystyk...",
|
|
||||||
"stats-error": "Ups! Wystąpił błąd podczas próby pobrania statystyk! :/",
|
|
||||||
|
|
||||||
"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",
|
|
||||||
"spawns": "OTWARTE SPAWNY",
|
|
||||||
"timetables": "AKTYWNE ROZKŁADY JAZDY",
|
|
||||||
"no-timetables": "Brak aktywnych rozkładów!",
|
|
||||||
"offline": "Sceneria jest offline",
|
|
||||||
"no-users": "BRAK AKTYWNYCH GRACZY",
|
|
||||||
"no-spawns": "BRAK OTWARTYCH SPAWNÓW",
|
|
||||||
"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óć 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",
|
|
||||||
"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}",
|
|
||||||
|
|
||||||
"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ść",
|
|
||||||
"default": "w paczce",
|
|
||||||
"nonDefault": "poza paczką",
|
|
||||||
"unavailable": "niedostępna",
|
|
||||||
"nonPublic": "niepubliczna",
|
|
||||||
"abandoned": "wycofana"
|
|
||||||
},
|
|
||||||
"timetables": {
|
|
||||||
"timetable-only": "Wyodrębnij rozkłady jazdy",
|
|
||||||
"end": "Koniec rozkładu jazdy",
|
|
||||||
"terminated": "Rozkład jazdy zakończony",
|
|
||||||
"begins": "ROZPOCZYNA\nBIEG",
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+145
-51
@@ -2,19 +2,21 @@
|
|||||||
"donations": {
|
"donations": {
|
||||||
"button-title": "GROSZA DAJ",
|
"button-title": "GROSZA DAJ",
|
||||||
"header": "Grosza daj Stacjownikowi!",
|
"header": "Grosza daj Stacjownikowi!",
|
||||||
|
"donator-title": "Projekt ma już ponad <b>{count}</b> wspierających, w tym:",
|
||||||
"p1": "<b>Hej o7!</b> Z tej strony Spythere, twórca Stacjownika, Pojazdownika oraz kilku innych aplikacji wspomagających rozgrywkę symulatora Train Driver 2!",
|
"p1": "<b>Hej o7!</b> Z tej strony Spythere, twórca Stacjownika, Pojazdownika oraz kilku innych aplikacji wspomagających rozgrywkę symulatora Train Driver 2!",
|
||||||
"p2": "{b1} to narzędzie całkowicie darmowe, tworzone i rozwijane dla społeczności symulatora TD2 nieprzerwanie od 2020 roku. Jednakże, część projektu jest podtrzymywana wyłącznie dzięki mojemu prywatnemu wkładowi finansowemu. Funkcje takie jak {b2} czy też {b3} działający na moim {link} (na który serdeczne zapraszam) muszą działać na wydzielonym serwerze, gdzie będą mogły zbierać i przetwarzać dane, aby następnie pokazać je na stronie.",
|
"p2": "{b1} to narzędzie całkowicie darmowe, tworzone i rozwijane dla społeczności symulatora TD2 nieprzerwanie od 2020 roku. Jednakże, część projektu jest podtrzymywana wyłącznie dzięki mojemu prywatnemu wkładowi finansowemu. Funkcje takie jak {b2} czy też {b3} działający na moim {link} (na który serdecznie zapraszam) muszą działać na wydzielonym serwerze, gdzie będą mogły zbierać i przetwarzać dane, aby następnie pokazać je na stronie.",
|
||||||
"p2-b1": "Stacjownik",
|
"p2-b1": "Stacjownik",
|
||||||
"p2-b2": "Dziennik",
|
"p2-b2": "Dziennik",
|
||||||
"p2-b3": "Stacjobot",
|
"p2-b3": "Stacjobot",
|
||||||
"p2-a1": "serwerze Discord",
|
"p2-a1": "serwerze Discord",
|
||||||
"p3": "<b>Jeśli masz możliwość i chcesz wspomóc moją twórczość dla symulatora, będę wdzięczny za wszelkie pomoce finansowe, które pozwolą na choćby częściowe pokrycie kosztów serwera oraz dalsze rozwijanie możliwości aplikacji!</b>",
|
"p3": "<b>Jeśli masz możliwość i chcesz wspomóc moją twórczość dla symulatora, będę wdzięczny za wszelkie pomoce finansowe, które pozwolą na choćby częściowe pokrycie kosztów serwera oraz dalsze rozwijanie możliwości aplikacji!</b>",
|
||||||
"p4": "Każda osoba, która postanowi przelać co najmniej {b1} na rozwój Stacjownika, otrzyma na życzenie symboliczne {img}{b2} nicku użytkownika w zakładkach \"Scenerie\" i \"Pociągi\" aplikacji i moim serwerze Discord (po zweryfikowaniu autora płatności, najlepiej poprzez podanie nicku bezpośrednio przy niej).",
|
"p4": "Każda osoba, która postanowi przelać co najmniej {b1} (w przypadku PayPala musi być to opłata brutto z dodatkową opłatą za transakcję) na rozwój Stacjownika, otrzyma na życzenie symboliczne {img}{b2} nicku użytkownika na stronie i moim serwerze Discord (po zweryfikowaniu autora płatności, najlepiej poprzez podanie nicku bezpośrednio przy niej).",
|
||||||
"p4-b1": "5 złotych",
|
"p4-b1": "5 złotych",
|
||||||
"p4-b2": "wyróżnienie",
|
"p4-b2": "wyróżnienie",
|
||||||
"p5": "Dzięki i miłego korzystania z aplikacji! <br />~ Spythere",
|
"p5": "Dzięki i miłego korzystania z aplikacji! <br />~ Spythere",
|
||||||
"action-exit": "Może kiedy indziej...",
|
"action-exit": "Może kiedy indziej...",
|
||||||
"action-confirm": "WSPOMÓŻ!",
|
"action-paypal": "PRZELEJ PAYPALEM",
|
||||||
|
"action-buycoffee": "POSTAW KAWĘ!",
|
||||||
"dispatcher-message": "Dyżurny wspierający projekt Stacjownika!",
|
"dispatcher-message": "Dyżurny wspierający projekt Stacjownika!",
|
||||||
"driver-message": "Maszynista wspierający projekt Stacjownika!"
|
"driver-message": "Maszynista wspierający projekt Stacjownika!"
|
||||||
},
|
},
|
||||||
@@ -24,6 +26,13 @@
|
|||||||
"TWR": "Towar niebezpieczny wysokiego ryzyka",
|
"TWR": "Towar niebezpieczny wysokiego ryzyka",
|
||||||
"SKR": "Przekroczona skrajnia"
|
"SKR": "Przekroczona skrajnia"
|
||||||
},
|
},
|
||||||
|
"update": {
|
||||||
|
"title": "Aktualizacja Stacjownika!",
|
||||||
|
"confirm": "PRZYJĄŁEM!",
|
||||||
|
"no-data": "Nie znaleziono informacji o ostatnich zmianach w aplikacji",
|
||||||
|
"info-1": "Ten changelog będzie zawsze dostępny po kliknięciu numeru wersji w stopce strony",
|
||||||
|
"info-2": "Pełny changelog dostępny na <a href='https://github.com/Spythere/stacjownik' target='_blank'>GitHubie projektu</a>"
|
||||||
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"sceneries": "SCENERIE",
|
"sceneries": "SCENERIE",
|
||||||
"trains": "POCIĄGI",
|
"trains": "POCIĄGI",
|
||||||
@@ -36,6 +45,10 @@
|
|||||||
"footer": {
|
"footer": {
|
||||||
"discord": "Serwer Discord Stacjownika"
|
"discord": "Serwer Discord Stacjownika"
|
||||||
},
|
},
|
||||||
|
"vehicle-preview": {
|
||||||
|
"loading": "Ładowanie podglądu...",
|
||||||
|
"error": "Ups! Nie znaleziono podglądu pojazdu! :/"
|
||||||
|
},
|
||||||
"data-status": {
|
"data-status": {
|
||||||
"S1-offline": "<b>Sygnał S1</b> <br> Aplikacja działa w trybie offline!",
|
"S1-offline": "<b>Sygnał S1</b> <br> Aplikacja działa w trybie offline!",
|
||||||
"S1a-connection": "<b>Sygnał S1a</b> <br> Błąd podczas próby połączenia się z API Stacjownika!",
|
"S1a-connection": "<b>Sygnał S1a</b> <br> Błąd podczas próby połączenia się z API Stacjownika!",
|
||||||
@@ -46,18 +59,6 @@
|
|||||||
"S5-dispatchers": "<b>Sygnał S5</b> <br> Błąd podczas pobierania danych o statusach dyżurnych ruchu!",
|
"S5-dispatchers": "<b>Sygnał S5</b> <br> Błąd podczas pobierania danych o statusach dyżurnych ruchu!",
|
||||||
"S5-trains": "<b>Sygnał S5</b> <br> Błąd podczas pobierania danych o pociągach online!"
|
"S5-trains": "<b>Sygnał S5</b> <br> Błąd podczas pobierania danych o pociągach online!"
|
||||||
},
|
},
|
||||||
"desc": {
|
|
||||||
"control-type": "Sterowanie:",
|
|
||||||
"signals-type": "Sygnalizacja:",
|
|
||||||
"SBL": "Sceneria posiada SBL na szlakach:",
|
|
||||||
"SUP": "Wymaga programu SUP do kontroli systemu RASP-UZK",
|
|
||||||
"default": "Sceneria dostępna domyślnie w paczce z grą",
|
|
||||||
"non-public": "Sceneria niepubliczna",
|
|
||||||
"unavailable": "Sceneria niedostępna",
|
|
||||||
"unknown": "Nieznana sceneria",
|
|
||||||
"real": "Sceneria z realnymi liniami kolejowymi:",
|
|
||||||
"abandoned": "Sceneria wycofana z rozgrywki"
|
|
||||||
},
|
|
||||||
"signals": {
|
"signals": {
|
||||||
"title": "Sygnalizacja",
|
"title": "Sygnalizacja",
|
||||||
"współczesna": "współczesna",
|
"współczesna": "współczesna",
|
||||||
@@ -76,7 +77,20 @@
|
|||||||
"ręczne+SCS": "ręczne z SCS",
|
"ręczne+SCS": "ręczne z SCS",
|
||||||
"mechaniczne": "mechaniczne",
|
"mechaniczne": "mechaniczne",
|
||||||
"mechaniczne+SPK": "mechaniczne z SPK",
|
"mechaniczne+SPK": "mechaniczne z SPK",
|
||||||
"mechaniczne+SCS": "mechaniczne z SCS"
|
"mechaniczne+SCS": "mechaniczne z SCS",
|
||||||
|
|
||||||
|
"abbrevs": {
|
||||||
|
"SPK": "SPK",
|
||||||
|
"SCS": "SCS",
|
||||||
|
"SCS-SPK": "S/S",
|
||||||
|
"SPE": "SPE",
|
||||||
|
"ręczne": "R",
|
||||||
|
"ręczne+SPK": "R",
|
||||||
|
"ręczne+SCS": "R",
|
||||||
|
"mechaniczne": "M",
|
||||||
|
"mechaniczne+SPK": "M",
|
||||||
|
"mechaniczne+SCS": "M"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"online": "DO ",
|
"online": "DO ",
|
||||||
@@ -132,7 +146,8 @@
|
|||||||
"filter-noComments": "BEZ UWAG",
|
"filter-noComments": "BEZ UWAG",
|
||||||
"filter-twr": "WYS. RYZYKA",
|
"filter-twr": "WYS. RYZYKA",
|
||||||
"filter-skr": "SKRAJNIA",
|
"filter-skr": "SKRAJNIA",
|
||||||
"filter-twr-skr": "WSZYSTKIE",
|
"filter-twr-skr": "TWR/SKR",
|
||||||
|
"filter-all-statuses": "WSZYSTKIE",
|
||||||
"filter-common": "ZWYKŁE",
|
"filter-common": "ZWYKŁE",
|
||||||
"filter-passenger": "PASAŻERSKIE",
|
"filter-passenger": "PASAŻERSKIE",
|
||||||
"filter-freight": "TOWAROWE",
|
"filter-freight": "TOWAROWE",
|
||||||
@@ -144,9 +159,9 @@
|
|||||||
"filter-clear": "WYŁĄCZ FILTRY",
|
"filter-clear": "WYŁĄCZ FILTRY",
|
||||||
|
|
||||||
"filter-section-timetable-status": "STATUS ROZKŁADU JAZDY",
|
"filter-section-timetable-status": "STATUS ROZKŁADU JAZDY",
|
||||||
"filter-section-twrskr": "UWAGI",
|
"filter-section-special": "TYPY SPECJALNE",
|
||||||
|
|
||||||
"filter-all": "WSZYSTKIE",
|
"filter-all-specials": "WSZYSTKIE",
|
||||||
"filter-abandoned": "PORZUCONE",
|
"filter-abandoned": "PORZUCONE",
|
||||||
"filter-fulfilled": "WYPEŁNIONE",
|
"filter-fulfilled": "WYPEŁNIONE",
|
||||||
"filter-active": "AKTYWNE"
|
"filter-active": "AKTYWNE"
|
||||||
@@ -161,9 +176,11 @@
|
|||||||
"access": "DOSTĘPNOŚĆ OGÓLNA",
|
"access": "DOSTĘPNOŚĆ OGÓLNA",
|
||||||
"control": "TYP STEROWANIA",
|
"control": "TYP STEROWANIA",
|
||||||
"signals": "TYP SYGNALIZACJI",
|
"signals": "TYP SYGNALIZACJI",
|
||||||
"addons": "DODATKOWE PROGRAMY",
|
"addons": "SZCZEGÓŁY",
|
||||||
"blockades": "BLOKADY LINIOWE",
|
"blockades": "BLOKADY LINIOWE",
|
||||||
"status": "STATUS ONLINE"
|
"status": "STATUS ONLINE",
|
||||||
|
"timetables": "AKTYWNE ROZKŁADY JAZDY",
|
||||||
|
"spawns": "OTWARTE SPAWNY"
|
||||||
},
|
},
|
||||||
|
|
||||||
"all-available": "WSZYSTKIE DOSTĘPNE",
|
"all-available": "WSZYSTKIE DOSTĘPNE",
|
||||||
@@ -195,6 +212,9 @@
|
|||||||
"SUP": "SUP (RASP-UZK)",
|
"SUP": "SUP (RASP-UZK)",
|
||||||
"noSUP": "BEZ SUP",
|
"noSUP": "BEZ SUP",
|
||||||
|
|
||||||
|
"ASDEK": "ASDEK",
|
||||||
|
"noASDEK": "BEZ ASDEK-a",
|
||||||
|
|
||||||
"SBL": "SAMOCZYNNA",
|
"SBL": "SAMOCZYNNA",
|
||||||
"PBL": "PÓŁSAMOCZYNNA",
|
"PBL": "PÓŁSAMOCZYNNA",
|
||||||
|
|
||||||
@@ -203,20 +223,26 @@
|
|||||||
"semaphores": "KSZTAŁTOWA",
|
"semaphores": "KSZTAŁTOWA",
|
||||||
"mixed": "MIESZANA",
|
"mixed": "MIESZANA",
|
||||||
"historical": "HISTORYCZNA",
|
"historical": "HISTORYCZNA",
|
||||||
|
|
||||||
"free": "WOLNA",
|
"free": "WOLNA",
|
||||||
"occupied": "ZAJĘTA",
|
"occupied": "ZAJĘTA",
|
||||||
|
|
||||||
|
"withActiveTimetables": "AKTYWNE",
|
||||||
|
"withoutActiveTimetables": "BEZ AKTYWNYCH",
|
||||||
|
|
||||||
"sliders": {
|
"sliders": {
|
||||||
"min-lvl": "MIN. WYMAGANY POZIOM DYŻURNEGO",
|
"min-lvl": "MIN. WYMAGANY POZIOM DYŻURNEGO",
|
||||||
"max-lvl": "MAKS. WYMAGANY POZIOM DYŻURNEGO",
|
"max-lvl": "MAKS. WYMAGANY POZIOM DYŻURNEGO",
|
||||||
|
"min-vmax": "MIN. PRĘDKOŚĆ SZLAKOWA",
|
||||||
|
"max-vmax": "MAKS. PRĘDKOŚĆ SZLAKOWA",
|
||||||
"routes-1t-cat": "SZLAKI JEDNOTOROWE ZELEKTR. (MINIMUM)",
|
"routes-1t-cat": "SZLAKI JEDNOTOROWE ZELEKTR. (MINIMUM)",
|
||||||
"routes-1t-other": "SZLAKI JEDNOTOROWE NIEZELEKTR. (MINIMUM)",
|
"routes-1t-other": "SZLAKI JEDNOTOROWE NIEZELEKTR. (MINIMUM)",
|
||||||
"routes-2t-cat": "SZLAKI DWUTOROWE ZELEKTR. (MINIMUM)",
|
"routes-2t-cat": "SZLAKI DWUTOROWE ZELEKTR. (MINIMUM)",
|
||||||
"routes-2t-other": "SZLAKI DWUTOROWE NIEZELEKTR. (MINIMUM)"
|
"routes-2t-other": "SZLAKI DWUTOROWE NIEZELEKTR. (MINIMUM)"
|
||||||
},
|
},
|
||||||
|
|
||||||
"authors-search": "Szukaj autora (uwzględnia inne filtry)",
|
"authors-search": "SZUKAJ AUTORA (uwzględnia inne filtry):",
|
||||||
|
"authors-placeholder": "Wpisz nick autora...",
|
||||||
|
"authors-button-title": "Szukaj",
|
||||||
"minimum-hours-title": "POKAŻ TYLKO SCENERIE DOSTĘPNE MINIMUM DO:",
|
"minimum-hours-title": "POKAŻ TYLKO SCENERIE DOSTĘPNE MINIMUM DO:",
|
||||||
"now": "TERAZ",
|
"now": "TERAZ",
|
||||||
"hour": " godz.",
|
"hour": " godz.",
|
||||||
@@ -227,22 +253,52 @@
|
|||||||
"close": "ZAMKNIJ FILTRY"
|
"close": "ZAMKNIJ FILTRY"
|
||||||
},
|
},
|
||||||
"sceneries": {
|
"sceneries": {
|
||||||
"station": "Stacja",
|
"headers": {
|
||||||
"abbr": "Skrót\nposterunku",
|
"station": "Sceneria",
|
||||||
"min-lvl": "Min. poziom\ndyżurnego",
|
"min-lvl": "Poziom\nscenerii",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"dispatcher": "Dyżurny",
|
"dispatcher": "Dyżurny",
|
||||||
"dispatcher-lvl": "Poziom\ndyżurnego",
|
"dispatcher-lvl": "Poziom\ndyżurnego",
|
||||||
"routes": "Szlaki\n2tor / 1tor",
|
"routes-single": "Szlaki\n1-torowe",
|
||||||
|
"routes-double": "Szlaki\n2-torowe",
|
||||||
"general": "Informacje\nogólne",
|
"general": "Informacje\nogólne",
|
||||||
"user": "Maszyniści online",
|
"user": "Maszyniści online",
|
||||||
|
"like": "Ocena dyżurnego",
|
||||||
"spawn": "Otwarte spawny",
|
"spawn": "Otwarte spawny",
|
||||||
"timetableAll": "Aktywne rozkłady jazdy",
|
"timetableAll": "Aktywne rozkłady jazdy",
|
||||||
"timetableConfirmed": "Zatwierdzone rozkłady jazdy",
|
"timetableConfirmed": "Zatwierdzone rozkłady jazdy",
|
||||||
"timetableUnconfirmed": "Niezatwierdzone rozkłady jazdy",
|
"timetableUnconfirmed": "Niezatwierdzone rozkłady jazdy"
|
||||||
|
},
|
||||||
|
"info": {
|
||||||
|
"control-type": "Sterowanie: ",
|
||||||
|
"signals-type": "Sygnalizacja: ",
|
||||||
|
"SBL": "Sceneria posiada SBL na szlakach: ",
|
||||||
|
"SUP": "Wymaga programu SUP do kontroli systemu RASP-UZK",
|
||||||
|
"ASDEK": "Wymaga programu ASDEK do detekcji stanów awaryjnych taboru w ruchu",
|
||||||
|
"default": "Sceneria dostępna domyślnie w paczce z grą",
|
||||||
|
"non-public": "Sceneria niepubliczna",
|
||||||
|
"unavailable": "Sceneria niedostępna",
|
||||||
|
"abandoned": "Sceneria wycofana z rozgrywki",
|
||||||
|
"unknown": "Nieznana sceneria",
|
||||||
|
"real": "Sceneria z realnymi liniami kolejowymi: ",
|
||||||
|
"double-track-routes-catenary": "Liczba zelektryfikowanych szlaków dwutorowych: ",
|
||||||
|
"single-track-routes-catenary": "Liczba zelektryfikowanych szlaków jednotorowych: ",
|
||||||
|
"double-track-routes-other": "Liczba niezelektryfikowanych szlaków dwutorowych: ",
|
||||||
|
"single-track-routes-other": "Liczba niezelektryfikowanych szlaków jednotorowych: "
|
||||||
|
},
|
||||||
"no-stations": "Brak stacji do wyświetlenia!",
|
"no-stations": "Brak stacji do wyświetlenia!",
|
||||||
"scenery-search": "Wyszukaj scenerię..."
|
"scenery-search": "Wyszukaj scenerię..."
|
||||||
},
|
},
|
||||||
|
"station-stats": {
|
||||||
|
"u-factor": "Współczynnik Ugla",
|
||||||
|
"u-factor-tooltip": "(?) Współczynnik ruchu na serwerze (liczba maszynistów online dzielona na liczbę dyżurnych ruchu)",
|
||||||
|
"avg-timetable-count": "Średnia liczba rozkładów jazdy na dyżurnego:",
|
||||||
|
"single-track-count": "Dostępne szlaki jednotorowe:",
|
||||||
|
"double-track-count": "Dostępne szlaki dwutorowe:",
|
||||||
|
"electrified": "(zelektr.)",
|
||||||
|
"not-electrified": "(niezelektr.)",
|
||||||
|
"open-spawns": "Otwarte spawny:"
|
||||||
|
},
|
||||||
"trains": {
|
"trains": {
|
||||||
"no-trains": "Brak pociągów do wyświetlenia!",
|
"no-trains": "Brak pociągów do wyświetlenia!",
|
||||||
"loading": "Pobieranie danych o pociągach...",
|
"loading": "Pobieranie danych o pociągach...",
|
||||||
@@ -275,7 +331,24 @@
|
|||||||
|
|
||||||
"scenery-offline": "Przejazd offline",
|
"scenery-offline": "Przejazd offline",
|
||||||
|
|
||||||
"timeout": "Wystąpił problem z aktualizacją rozkładów jazdy z SWDR"
|
"timeout": "Wystąpił problem z aktualizacją rozkładów jazdy z SWDR",
|
||||||
|
|
||||||
|
"journal-button": "DZIENNIK MASZYNISTY"
|
||||||
|
},
|
||||||
|
"train-stats": {
|
||||||
|
"stats-button": "STATYSTYKI",
|
||||||
|
"title": "STATYSTYKI ONLINE",
|
||||||
|
"timetable-count": "AKTYWNE RJ",
|
||||||
|
"avg-speed": "ŚREDNIA PRĘDKOŚĆ",
|
||||||
|
"avg-timetable": "ŚREDNI RJ",
|
||||||
|
"top-categories": "Kategorie RJ",
|
||||||
|
"top-vehicles": "Pojazdy w grze",
|
||||||
|
"top-units": "Najczęstsze jednostki w grze",
|
||||||
|
"stats-loading": "Ładowanie...",
|
||||||
|
"no-timetables": "Brak aktywnych rozkładów jazdy na tym serwerze!",
|
||||||
|
"no-vehicles": "Brak aktywnych pojazdów na tym serwerze!",
|
||||||
|
"no-units": "Brak aktywnych jednostek na tym serwerze!",
|
||||||
|
"no-stats": "Brak statystyk online dla wybranego serwera!"
|
||||||
},
|
},
|
||||||
"journal": {
|
"journal": {
|
||||||
"title": "HISTORIA DYŻURÓW",
|
"title": "HISTORIA DYŻURÓW",
|
||||||
@@ -302,39 +375,60 @@
|
|||||||
"timetable-active": "AKTYWNY",
|
"timetable-active": "AKTYWNY",
|
||||||
"timetable-fulfilled": "WYPEŁNIONY",
|
"timetable-fulfilled": "WYPEŁNIONY",
|
||||||
"timetable-abandoned": "PORZUCONY",
|
"timetable-abandoned": "PORZUCONY",
|
||||||
|
"timetable-online-button": "RJ ONLINE",
|
||||||
|
|
||||||
"stock-info": "DODATKOWE INFORMACJE",
|
"stock-info": "SZCZEGÓŁY",
|
||||||
"stock-length": "Długość",
|
"stock-length": "Długość",
|
||||||
"stock-mass": "Masa",
|
"stock-mass": "Masa",
|
||||||
"stock-max-speed": "Prędkość maks.",
|
"stock-max-speed": "Prędkość maks.",
|
||||||
|
|
||||||
"load-data": "Pobierz dalszą historię...",
|
"load-data": "Pobierz dalszą historię...",
|
||||||
|
|
||||||
"stats-title": "STATYSTYKI MASZYNISTY",
|
|
||||||
|
|
||||||
"last-seen-at": "Ostatnio widziany na: ",
|
"last-seen-at": "Ostatnio widziany na: ",
|
||||||
"currently-at": "Obecnie na scenerii: ",
|
"currently-at": "Obecnie na scenerii: ",
|
||||||
|
|
||||||
"stats-timetables": "ROZKŁADY JAZDY",
|
"driver-stats": {
|
||||||
"stats-longest-timetable": "NAJDŁUŻSZY RJ",
|
"button": "STAT. MASZYNISTY",
|
||||||
"stats-avg-timetable": "ŚREDNIA DŁUGOŚĆ RJ",
|
"title": "STATYSTYKI MASZYNISTY {name}",
|
||||||
"stats-distance": "DYSTANS",
|
"info": "Wpisz nazwę użytkownika w filtrach [F], aby zobaczyć jego statystyki maszynisty!",
|
||||||
"stats-stations": "STACJE",
|
"timetables": "ROZKŁADY JAZDY",
|
||||||
|
"longest-timetable": "NAJDŁUŻSZY RJ",
|
||||||
|
"avg-timetable": "ŚREDNIA DŁUGOŚĆ RJ",
|
||||||
|
"distance": "DYSTANS",
|
||||||
|
"stations": "STACJE"
|
||||||
|
},
|
||||||
|
|
||||||
"timetable-stats-total": "Stworzone rozkłady jazdy: {count} (łączny dystans: {distance})",
|
"daily-stats": {
|
||||||
"timetable-stats-longest": "Najdłuższy rozkład jazdy: #{id} (stworzony przez dyżurnego {author} dla maszynisty {driver} o dystansie {distance})",
|
"button": "STATYSTYKI DNIA",
|
||||||
"timetable-stats-most-active-dr": "Najaktywniejszy dyżurny: {dispatcher} (stworzył {count})",
|
"title": "STATYSTYKI DNIA",
|
||||||
"timetable-stats-most-active-dr-many": "Najaktywniejsi dyżurni: {dispatchers} (stworzyli po {count})",
|
"info": "Dzisiejsze statystyki nie są jeszcze dostępne!",
|
||||||
"timetable-stats-most-active-driver": "Najaktywniejszy maszynista: {driver} (łączny przejechany dystans: {distance})",
|
"total": "Stworzone rozkłady jazdy: {count} (łączny dystans: {distance})",
|
||||||
"timetable-stats-longest-duties": "Najdłuższa służba: {dispatcher} na scenerii {station} (czas trwania: {duration})",
|
"longest": "Najdłuższy rozkład jazdy: #{id} (stworzony przez dyżurnego {author} dla maszynisty {driver} o dystansie {distance})",
|
||||||
|
"most-active-dr": "Najaktywniejszy dyżurny: {dispatcher} (stworzył {count})",
|
||||||
|
"most-active-dr-many": "Najaktywniejsi dyżurni: {dispatchers} (stworzyli po {count})",
|
||||||
|
"most-active-driver": "Najaktywniejszy maszynista: {driver} (łączny przejechany dystans: {distance})",
|
||||||
|
"longest-duties": "Najdłuższa służba: {dispatcher} na scenerii {station} (czas trwania: {duration})",
|
||||||
|
"count": "rozkład jazdy | rozkładów jazdy",
|
||||||
|
|
||||||
"timetable-count": "rozkład jazdy | rozkładów jazdy",
|
"rippedSwitches": "ROZPRUTE ZWROTNICE",
|
||||||
|
"derailments": "WYKOLEJENIA",
|
||||||
|
"skippedStopSignals": "POMINIĘTE S1",
|
||||||
|
"radioStops": "RADIOSTOPY",
|
||||||
|
"kills": "POTRĄCENIA"
|
||||||
|
},
|
||||||
|
|
||||||
"daily-stats-title": "STATYSTYKI DNIA",
|
"dispatcher-stats": {
|
||||||
"daily-stats-info": "Dzisiejsze statystyki nie są jeszcze dostępne!",
|
"button": "STATYSTYKI DYŻURNEGO",
|
||||||
|
"title": "STATYSTYKI DYŻURNEGO {name}",
|
||||||
"driver-stats-title": "STATYSTYKI GRACZA",
|
"info": "Wpisz nazwę użytkownika w filtrach [F], aby zobaczyć jego statystyki dyżurnego!",
|
||||||
"driver-stats-info": "Wpisz nazwę użytkownika w filtrach [F], aby zobaczyć jego statystyki maszynisty!",
|
"services-count": "DYŻURY",
|
||||||
|
"service-max": "MAKS. CZAS DYŻURU",
|
||||||
|
"service-avg": "ŚREDNI CZAS DYŻURU",
|
||||||
|
"timetables-count": "WYSTAWIONE RJ",
|
||||||
|
"timetables-sum": "SUMA WYSTAWIONYCH RJ",
|
||||||
|
"timetables-max": "NAJDŁUŻSZY WYSTAWIONY RJ",
|
||||||
|
"timetables-avg": "ŚREDNIA WYSTAWIONYCH RJ"
|
||||||
|
},
|
||||||
|
|
||||||
"stats-loading": "Pobieranie statystyk...",
|
"stats-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!",
|
||||||
@@ -370,8 +464,8 @@
|
|||||||
"two-way-routes": "Szlaki dwutorowe",
|
"two-way-routes": "Szlaki dwutorowe",
|
||||||
|
|
||||||
"option-active-timetables": "Aktywne rozkłady jazdy",
|
"option-active-timetables": "Aktywne rozkłady jazdy",
|
||||||
"option-timetables-history": "Historia rozkładów",
|
"option-timetables-history": "Historia rozkładów PL1",
|
||||||
"option-dispatchers-history": "Historia dyżurów",
|
"option-dispatchers-history": "Historia dyżurów PL1",
|
||||||
|
|
||||||
"timetable-author-title": "Wydany przez",
|
"timetable-author-title": "Wydany przez",
|
||||||
"timetable-author-unknown": "Autor nieznany",
|
"timetable-author-unknown": "Autor nieznany",
|
||||||
|
|||||||
+2
-16
@@ -2,26 +2,12 @@ import { createApp, Directive, ref } from 'vue';
|
|||||||
import App from './App.vue';
|
import App from './App.vue';
|
||||||
import router from './router';
|
import router from './router';
|
||||||
|
|
||||||
import enLang from './locales/en.json';
|
import i18n from './i18n';
|
||||||
import plLang from './locales/pl.json';
|
|
||||||
|
|
||||||
import { createI18n } from 'vue-i18n';
|
|
||||||
import { createPinia } from 'pinia';
|
import { createPinia } from 'pinia';
|
||||||
import useCustomSW from './mixins/useCustomSW';
|
import useCustomSW from './mixins/useCustomSW';
|
||||||
|
|
||||||
const i18n = createI18n({
|
// Service worker
|
||||||
locale: 'pl',
|
|
||||||
legacy: false,
|
|
||||||
warnHtmlMessage: false,
|
|
||||||
fallbackLocale: 'pl',
|
|
||||||
messages: {
|
|
||||||
en: enLang,
|
|
||||||
pl: plLang
|
|
||||||
},
|
|
||||||
enableLegacy: false
|
|
||||||
});
|
|
||||||
|
|
||||||
// SW
|
|
||||||
useCustomSW();
|
useCustomSW();
|
||||||
|
|
||||||
const clickOutsideDirective: Directive = {
|
const clickOutsideDirective: Directive = {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { TrainFilter, TrainFilterId } from '../components/TrainsView/typings';
|
import { TrainFilter, TrainFilterId } from '../components/TrainsView/typings';
|
||||||
import Train from '../scripts/interfaces/Train';
|
import { Train, TrainStop } from '../typings/common';
|
||||||
import { TrainStop } from '../store/typings';
|
|
||||||
|
|
||||||
function confirmedPercentage(stops: TrainStop[] | undefined) {
|
function confirmedPercentage(stops: TrainStop[] | undefined) {
|
||||||
if (!stops) return -1;
|
if (!stops) return -1;
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
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,19 +1,15 @@
|
|||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { useStore } from '../store/mainStore';
|
import { useMainStore } from '../store/mainStore';
|
||||||
|
import { useTooltipStore } from '../store/tooltipStore';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
store: useStore()
|
store: useMainStore(),
|
||||||
|
tooltipStore: useTooltipStore()
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
|
||||||
chosenTrain() {
|
|
||||||
return this.store.trainList.find((train) => train.trainId == this.store.chosenModalTrainId);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
selectModalTrain(trainId: string, target?: EventTarget | null) {
|
selectModalTrain(trainId: string, target?: EventTarget | null) {
|
||||||
this.store.chosenModalTrainId = trainId;
|
this.store.chosenModalTrainId = trainId;
|
||||||
@@ -23,6 +19,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
closeModal() {
|
closeModal() {
|
||||||
this.store.chosenModalTrainId = undefined;
|
this.store.chosenModalTrainId = undefined;
|
||||||
|
this.tooltipStore.hide();
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
(this.store.modalLastClickedTarget as any)?.focus();
|
(this.store.modalLastClickedTarget as any)?.focus();
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
import { defineComponent } from 'vue';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
methods: {
|
|
||||||
getControlTypeAbbrev(controlType: string) {
|
|
||||||
switch (controlType) {
|
|
||||||
case 'mechaniczne':
|
|
||||||
return 'M';
|
|
||||||
case 'SCS-SPK':
|
|
||||||
return 'S/S';
|
|
||||||
case 'ręczne':
|
|
||||||
return 'R';
|
|
||||||
case 'mechaniczne+SPK':
|
|
||||||
return 'M';
|
|
||||||
case 'ręczne+SPK':
|
|
||||||
return 'R';
|
|
||||||
case 'mechaniczne+SCS':
|
|
||||||
return 'M';
|
|
||||||
default:
|
|
||||||
return controlType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import Train from '../scripts/interfaces/Train';
|
import { Train, TrainStop } from '../typings/common';
|
||||||
import { TrainStop } from '../store/typings';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
data: () => ({
|
data: () => ({
|
||||||
@@ -148,11 +147,6 @@ export default defineComponent({
|
|||||||
if (distance < 1000) return `${distance}m`;
|
if (distance < 1000) return `${distance}m`;
|
||||||
|
|
||||||
return `${(distance / 1000).toPrecision(2)}km`;
|
return `${(distance / 1000).toPrecision(2)}km`;
|
||||||
},
|
|
||||||
|
|
||||||
onImageError(e: Event) {
|
|
||||||
const imageEl = e.target as HTMLImageElement;
|
|
||||||
imageEl.src = '/images/icon-unknown.png';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+7
-13
@@ -1,6 +1,4 @@
|
|||||||
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
|
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
|
||||||
import JournalDispatchersVue from '../views/JournalDispatchers.vue';
|
|
||||||
import JournalTimetablesVue from '../views/JournalTimetables.vue';
|
|
||||||
|
|
||||||
const routes: Array<RouteRecordRaw> = [
|
const routes: Array<RouteRecordRaw> = [
|
||||||
{
|
{
|
||||||
@@ -18,7 +16,8 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
props: (route) => ({
|
props: (route) => ({
|
||||||
train: route.query.train,
|
train: route.query.train,
|
||||||
driver: route.query.driver,
|
driver: route.query.driver,
|
||||||
trainId: route.query.trainId
|
trainId: route.query.trainId,
|
||||||
|
region: route.query.region
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -37,20 +36,17 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
{
|
{
|
||||||
path: '/journal/timetables',
|
path: '/journal/timetables',
|
||||||
name: 'JournalTimetables',
|
name: 'JournalTimetables',
|
||||||
component: JournalTimetablesVue,
|
component: () => import('../views/JournalTimetables.vue'),
|
||||||
props: (route) => ({
|
props: (route) => ({
|
||||||
trainNo: route.query.trainNo,
|
region: route.query.region
|
||||||
driverName: route.query.driverName,
|
|
||||||
timetableId: route.query.timetableId
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/journal/dispatchers',
|
path: '/journal/dispatchers',
|
||||||
name: 'JournalDispatchers',
|
name: 'JournalDispatchers',
|
||||||
component: JournalDispatchersVue,
|
component: () => import('../views/JournalDispatchers.vue'),
|
||||||
props: (route) => ({
|
props: (route) => ({
|
||||||
sceneryName: route.query.sceneryName,
|
region: route.query.region
|
||||||
dispatcherName: route.query.dispatcherName
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -61,12 +57,10 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
scrollBehavior(to, from, savedPosition) {
|
scrollBehavior(to, from, savedPosition) {
|
||||||
if (to.name == 'SceneryView' && from.name && from.query['view'] === undefined)
|
if (to.name == 'SceneryView' && from.name !== to.name && from.query['view'] === undefined)
|
||||||
return { el: `.app_main` };
|
return { el: `.app_main` };
|
||||||
|
|
||||||
if (savedPosition) return savedPosition;
|
if (savedPosition) return savedPosition;
|
||||||
|
|
||||||
// if (from.name == 'SceneryView' && to.name == 'StationsView') return { el: `.last-selected`, top: 20 };
|
|
||||||
},
|
},
|
||||||
history: createWebHistory(),
|
history: createWebHistory(),
|
||||||
routes
|
routes
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ export const headIds = [
|
|||||||
'status',
|
'status',
|
||||||
'dispatcher',
|
'dispatcher',
|
||||||
'dispatcher-lvl',
|
'dispatcher-lvl',
|
||||||
'routes',
|
'routes-single',
|
||||||
|
'routes-double',
|
||||||
'general'
|
'general'
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export const headIconsIds = [
|
export const headIconsIds = [
|
||||||
'user',
|
'user',
|
||||||
|
'like',
|
||||||
'spawn',
|
'spawn',
|
||||||
'timetableAll',
|
'timetableAll',
|
||||||
'timetableUnconfirmed',
|
'timetableUnconfirmed',
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
import { Availability, OnlineScenery, ScheduledTrain } from '../../store/typings';
|
|
||||||
import StationRoutes from './StationRoutes';
|
|
||||||
|
|
||||||
export default interface Station {
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
generalInfo?: {
|
|
||||||
name: string;
|
|
||||||
url: string;
|
|
||||||
abbr: string;
|
|
||||||
|
|
||||||
reqLevel: number;
|
|
||||||
// supportersOnly: boolean;
|
|
||||||
|
|
||||||
lines: string;
|
|
||||||
project: string;
|
|
||||||
projectUrl?: string;
|
|
||||||
|
|
||||||
signalType: string;
|
|
||||||
controlType: string;
|
|
||||||
|
|
||||||
SUP: boolean;
|
|
||||||
authors?: string[];
|
|
||||||
|
|
||||||
availability: Availability;
|
|
||||||
routes: StationRoutes;
|
|
||||||
|
|
||||||
checkpoints: {
|
|
||||||
checkpointName: string;
|
|
||||||
scheduledTrains: ScheduledTrain[];
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
|
|
||||||
onlineInfo?: OnlineScenery;
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
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[];
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import { TrainStop } from '../../store/typings';
|
|
||||||
|
|
||||||
export default interface Train {
|
|
||||||
trainId: string;
|
|
||||||
|
|
||||||
mass: number;
|
|
||||||
length: number;
|
|
||||||
speed: number;
|
|
||||||
signal: string;
|
|
||||||
distance: number;
|
|
||||||
connectedTrack: string;
|
|
||||||
driverId: number;
|
|
||||||
trainNo: number;
|
|
||||||
driverName: string;
|
|
||||||
driverLevel: number;
|
|
||||||
currentStationName: string;
|
|
||||||
currentStationHash: string;
|
|
||||||
locoType: string;
|
|
||||||
online: boolean;
|
|
||||||
lastSeen: number;
|
|
||||||
region: string;
|
|
||||||
stockList: string[];
|
|
||||||
|
|
||||||
isTimeout: boolean;
|
|
||||||
isSupporter: boolean;
|
|
||||||
|
|
||||||
timetableData?: {
|
|
||||||
timetableId: number;
|
|
||||||
category: string;
|
|
||||||
route: string;
|
|
||||||
followingStops: TrainStop[];
|
|
||||||
TWR: boolean;
|
|
||||||
SKR: boolean;
|
|
||||||
routeDistance: number;
|
|
||||||
sceneries: string[];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
export const URLs = {
|
|
||||||
stacjownikAPI:
|
|
||||||
import.meta.env.VITE_APP_API_DEV === '1' && !import.meta.env.PROD
|
|
||||||
? 'http://localhost:3001'
|
|
||||||
: 'https://stacjownik.spythere.eu',
|
|
||||||
stacjownikAPIDev: 'localhost:3000'
|
|
||||||
};
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Filter } from '../../components/StationsView/typings';
|
import { Filter } from '../../components/StationsView/typings';
|
||||||
import { Status } from '../../typings/common';
|
import { Status } from '../../typings/common';
|
||||||
import { HeadIdsTypes } from '../data/stationHeaderNames';
|
import { HeadIdsTypes } from '../data/stationHeaderNames';
|
||||||
import Station from '../interfaces/Station';
|
import { Station } from '../../typings/common';
|
||||||
|
|
||||||
const dispatcherStatusPriority = [
|
const dispatcherStatusPriority = [
|
||||||
Status.ActiveDispatcher.UNKNOWN,
|
Status.ActiveDispatcher.UNKNOWN,
|
||||||
@@ -54,10 +54,28 @@ export const sortStations = (
|
|||||||
diff = (a.onlineInfo?.dispatcherExp || 0) - (b.onlineInfo?.dispatcherExp || 0);
|
diff = (a.onlineInfo?.dispatcherExp || 0) - (b.onlineInfo?.dispatcherExp || 0);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'routes-single':
|
||||||
|
diff =
|
||||||
|
(a.generalInfo?.routes.single.filter((r) => !r.hidden && !r.isInternal).length ?? -1) -
|
||||||
|
(b.generalInfo?.routes.single.filter((r) => !r.hidden && !r.isInternal).length ?? -1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'routes-double':
|
||||||
|
diff =
|
||||||
|
(a.generalInfo?.routes.double.filter((r) => !r.hidden && !r.isInternal).length ?? -1) -
|
||||||
|
(b.generalInfo?.routes.double.filter((r) => !r.hidden && !r.isInternal).length ?? -1);
|
||||||
|
break;
|
||||||
|
|
||||||
case 'user':
|
case 'user':
|
||||||
diff =
|
diff =
|
||||||
(b.onlineInfo ? b.onlineInfo.currentUsers : -1) -
|
(b.onlineInfo?.stationTrains ? b.onlineInfo.stationTrains.length : -1) -
|
||||||
(a.onlineInfo ? a.onlineInfo.currentUsers : -1);
|
(a.onlineInfo?.stationTrains ? a.onlineInfo.stationTrains.length : -1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'like':
|
||||||
|
diff =
|
||||||
|
(a.onlineInfo ? a.onlineInfo.dispatcherRate : -Infinity) -
|
||||||
|
(b.onlineInfo ? b.onlineInfo.dispatcherRate : -Infinity);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'spawn':
|
case 'spawn':
|
||||||
@@ -93,26 +111,41 @@ export const sortStations = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const filterStations = (station: Station, filters: Filter) => {
|
export const filterStations = (station: Station, filters: Filter) => {
|
||||||
if (!station.onlineInfo && filters['free']) return false;
|
if (filters['free'] && (!station.onlineInfo || station.onlineInfo.dispatcherId == -1))
|
||||||
|
return false;
|
||||||
|
|
||||||
if (station.onlineInfo) {
|
if (station.onlineInfo) {
|
||||||
const { dispatcherStatus } = station.onlineInfo;
|
const { dispatcherStatus } = station.onlineInfo;
|
||||||
|
|
||||||
const isEnding = dispatcherStatus == Status.ActiveDispatcher.ENDING && filters['endingStatus'];
|
const excludeEnding =
|
||||||
|
dispatcherStatus == Status.ActiveDispatcher.ENDING && filters['endingStatus'];
|
||||||
|
|
||||||
const isNotSigned =
|
const excludeNotSigned =
|
||||||
(dispatcherStatus == Status.ActiveDispatcher.NOT_LOGGED_IN ||
|
(dispatcherStatus == Status.ActiveDispatcher.NOT_LOGGED_IN ||
|
||||||
dispatcherStatus == Status.ActiveDispatcher.UNAVAILABLE) &&
|
dispatcherStatus == Status.ActiveDispatcher.UNAVAILABLE) &&
|
||||||
filters['unavailableStatus'];
|
filters['unavailableStatus'];
|
||||||
|
|
||||||
const isAFK = dispatcherStatus == Status.ActiveDispatcher.AFK && filters['afkStatus'];
|
const excludeAFK = dispatcherStatus == Status.ActiveDispatcher.AFK && filters['afkStatus'];
|
||||||
|
|
||||||
const isNoSpace =
|
const excludeNoSpace =
|
||||||
dispatcherStatus == Status.ActiveDispatcher.NO_SPACE && filters['noSpaceStatus'];
|
dispatcherStatus == Status.ActiveDispatcher.NO_SPACE && filters['noSpaceStatus'];
|
||||||
|
|
||||||
const isOccupied = station.onlineInfo && filters['occupied'];
|
const excludeOccupied = filters['occupied'] && dispatcherStatus != Status.ActiveDispatcher.FREE;
|
||||||
|
|
||||||
if (isEnding || isNotSigned || isAFK || isNoSpace || isOccupied) return false;
|
const excludeActiveTTs =
|
||||||
|
(dispatcherStatus == Status.ActiveDispatcher.FREE ||
|
||||||
|
station.onlineInfo.scheduledTrainCount.all != 0) &&
|
||||||
|
filters['withActiveTimetables'];
|
||||||
|
|
||||||
|
if (
|
||||||
|
excludeEnding ||
|
||||||
|
excludeAFK ||
|
||||||
|
excludeNoSpace ||
|
||||||
|
excludeNotSigned ||
|
||||||
|
excludeOccupied ||
|
||||||
|
excludeActiveTTs
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
filters['onlineFromHours'] > 0 &&
|
filters['onlineFromHours'] > 0 &&
|
||||||
@@ -121,6 +154,12 @@ export const filterStations = (station: Station, filters: Filter) => {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const excludeNoActiveTTs =
|
||||||
|
filters['withoutActiveTimetables'] &&
|
||||||
|
(!station.onlineInfo || station.onlineInfo.scheduledTrainCount.all == 0);
|
||||||
|
|
||||||
|
if (excludeNoActiveTTs) return false;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(station.generalInfo?.availability == 'nonPublic' || !station.generalInfo) &&
|
(station.generalInfo?.availability == 'nonPublic' || !station.generalInfo) &&
|
||||||
filters['nonPublic']
|
filters['nonPublic']
|
||||||
@@ -128,7 +167,7 @@ export const filterStations = (station: Station, filters: Filter) => {
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (station.generalInfo) {
|
if (station.generalInfo) {
|
||||||
const { routes, availability, controlType, lines, reqLevel, signalType, SUP, authors } =
|
const { routes, availability, controlType, lines, reqLevel, signalType, SUP, ASDEK, authors } =
|
||||||
station.generalInfo;
|
station.generalInfo;
|
||||||
|
|
||||||
if (availability == 'unavailable' && filters['unavailable'] && !station.onlineInfo)
|
if (availability == 'unavailable' && filters['unavailable'] && !station.onlineInfo)
|
||||||
@@ -150,26 +189,28 @@ export const filterStations = (station: Station, filters: Filter) => {
|
|||||||
availability == 'nonPublic' || availability == 'unavailable' || availability == 'abandoned';
|
availability == 'nonPublic' || availability == 'unavailable' || availability == 'abandoned';
|
||||||
|
|
||||||
if (reqLevel + (otherAvailability ? 1 : 0) < filters['minLevel']) return false;
|
if (reqLevel + (otherAvailability ? 1 : 0) < filters['minLevel']) return false;
|
||||||
|
|
||||||
if (reqLevel + (otherAvailability ? 1 : 0) > filters['maxLevel']) return false;
|
if (reqLevel + (otherAvailability ? 1 : 0) > filters['maxLevel']) return false;
|
||||||
|
|
||||||
|
if (filters['minVmax'] > station.generalInfo.routes.maxRouteSpeed) return false;
|
||||||
|
if (filters['maxVmax'] < station.generalInfo.routes.minRouteSpeed) return false;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
filters['no-1track'] &&
|
filters['no-1track'] &&
|
||||||
(routes.oneWayCatenaryRouteNames.length != 0 || routes.oneWayNoCatenaryRouteNames.length != 0)
|
(routes.singleElectrifiedNames.length != 0 || routes.singleOtherNames.length != 0)
|
||||||
)
|
)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
filters['no-2track'] &&
|
filters['no-2track'] &&
|
||||||
(routes.twoWayCatenaryRouteNames.length != 0 || routes.twoWayNoCatenaryRouteNames.length != 0)
|
(routes.doubleElectrifiedNames.length != 0 || routes.doubleOtherNames.length != 0)
|
||||||
)
|
)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (routes.oneWayCatenaryRouteNames.length < filters['minOneWayCatenary']) return false;
|
if (routes.singleElectrifiedNames.length < filters['minOneWayCatenary']) return false;
|
||||||
if (routes.oneWayNoCatenaryRouteNames.length < filters['minOneWay']) return false;
|
if (routes.singleOtherNames.length < filters['minOneWay']) return false;
|
||||||
|
|
||||||
if (routes.twoWayCatenaryRouteNames.length < filters['minTwoWayCatenary']) return false;
|
if (routes.doubleElectrifiedNames.length < filters['minTwoWayCatenary']) return false;
|
||||||
if (routes.twoWayNoCatenaryRouteNames.length < filters['minTwoWay']) return false;
|
if (routes.doubleOtherNames.length < filters['minTwoWay']) return false;
|
||||||
|
|
||||||
if (filters[controlType]) return false;
|
if (filters[controlType]) return false;
|
||||||
if (filters[signalType]) return false;
|
if (filters[signalType]) return false;
|
||||||
@@ -177,8 +218,11 @@ export const filterStations = (station: Station, filters: Filter) => {
|
|||||||
if (filters['SUP'] && SUP) return false;
|
if (filters['SUP'] && SUP) return false;
|
||||||
if (filters['noSUP'] && !SUP) return false;
|
if (filters['noSUP'] && !SUP) return false;
|
||||||
|
|
||||||
if (filters['SBL'] && routes.sblRouteNames.length > 0) return false;
|
if (filters['ASDEK'] && ASDEK) return false;
|
||||||
if (filters['PBL'] && routes.sblRouteNames.length == 0) return false;
|
if (filters['noASDEK'] && !ASDEK) return false;
|
||||||
|
|
||||||
|
if (filters['SBL'] && routes.sblNames.length > 0) return false;
|
||||||
|
if (filters['PBL'] && routes.sblNames.length == 0) return false;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
filters['authors'].length > 3 &&
|
filters['authors'].length > 3 &&
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { API } from '../typings/api';
|
||||||
|
import { Status } from '../typings/common';
|
||||||
|
import { StationJSONData } from './typings';
|
||||||
|
import axios, { AxiosInstance } from 'axios';
|
||||||
|
|
||||||
|
export enum APIMode {
|
||||||
|
PRODUCTION = 0,
|
||||||
|
DEV = 1,
|
||||||
|
MOCK = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useApiStore = defineStore('apiStore', {
|
||||||
|
state: () => ({
|
||||||
|
dataStatuses: {
|
||||||
|
connection: Status.Data.Loading,
|
||||||
|
sceneries: Status.Data.Loading
|
||||||
|
},
|
||||||
|
|
||||||
|
activeData: undefined as API.ActiveData.Response | undefined,
|
||||||
|
donatorsData: [] as API.Donators.Response,
|
||||||
|
sceneryData: [] as StationJSONData[],
|
||||||
|
|
||||||
|
lastFetchData: new Date(),
|
||||||
|
|
||||||
|
client: undefined as AxiosInstance | undefined,
|
||||||
|
|
||||||
|
activeDataScheduler: undefined as number | undefined
|
||||||
|
}),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
async setupAPIData() {
|
||||||
|
let baseURL = 'https://stacjownik.spythere.eu';
|
||||||
|
|
||||||
|
switch (import.meta.env.VITE_API_MODE) {
|
||||||
|
case 'development':
|
||||||
|
baseURL = 'http://localhost:3001';
|
||||||
|
break;
|
||||||
|
case 'mocking':
|
||||||
|
baseURL = 'http://localhost:3123';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.client = axios.create({
|
||||||
|
baseURL
|
||||||
|
});
|
||||||
|
|
||||||
|
this.connectToAPI();
|
||||||
|
},
|
||||||
|
|
||||||
|
async connectToAPI() {
|
||||||
|
// Static data
|
||||||
|
this.fetchDonatorsData();
|
||||||
|
this.fetchStationsGeneralInfo();
|
||||||
|
},
|
||||||
|
|
||||||
|
async fetchActiveData() {
|
||||||
|
if (!this.activeData) this.dataStatuses.connection = Status.Data.Loading;
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('Fetching active data at ' + new Date().toLocaleTimeString('pl-PL'));
|
||||||
|
|
||||||
|
const response = await this.client!.get<API.ActiveData.Response>('api/getActiveData');
|
||||||
|
|
||||||
|
this.activeData = response.data;
|
||||||
|
this.lastFetchData = new Date();
|
||||||
|
this.dataStatuses.connection = Status.Data.Loaded;
|
||||||
|
} catch (error) {
|
||||||
|
this.dataStatuses.connection = Status.Data.Error;
|
||||||
|
console.error('Ups! Wystąpił błąd podczas pobierania danych online:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async fetchDonatorsData() {
|
||||||
|
try {
|
||||||
|
const response = await this.client!.get<API.Donators.Response>('api/getDonators');
|
||||||
|
|
||||||
|
this.donatorsData = response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ups! Wystąpił błąd podczas pobierania informacji o donatorach:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async fetchStationsGeneralInfo() {
|
||||||
|
const sceneryData: StationJSONData[] = (
|
||||||
|
await this.client!.get<StationJSONData[]>('api/getSceneries')
|
||||||
|
).data;
|
||||||
|
|
||||||
|
if (!sceneryData) {
|
||||||
|
this.dataStatuses.sceneries = Status.Data.Error;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dataStatuses.sceneries = Status.Data.Loaded;
|
||||||
|
this.sceneryData = sceneryData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
+194
-217
@@ -1,42 +1,27 @@
|
|||||||
import axios from 'axios';
|
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { io } from 'socket.io-client';
|
|
||||||
import StationRoutes from '../scripts/interfaces/StationRoutes';
|
|
||||||
import Train from '../scripts/interfaces/Train';
|
|
||||||
import { URLs } from '../scripts/utils/apiURLs';
|
|
||||||
import { parseSpawns, getScheduledTrains, getStationTrains } from './utils';
|
import { parseSpawns, getScheduledTrains, getStationTrains } from './utils';
|
||||||
|
|
||||||
import { OnlineScenery, ScheduledTrain, StationJSONData, StoreState } from './typings';
|
import {
|
||||||
|
ActiveScenery,
|
||||||
|
ScheduledTrain,
|
||||||
|
Station,
|
||||||
|
StationRoutes,
|
||||||
|
Status,
|
||||||
|
Train
|
||||||
|
} from '../typings/common';
|
||||||
|
import { useApiStore } from './apiStore';
|
||||||
|
import { MainStoreState } from './typings';
|
||||||
|
|
||||||
import packageInfo from '../../package.json';
|
export const useMainStore = defineStore('mainStore', {
|
||||||
import { Websocket, API, GithubAPI } from '../typings/api';
|
|
||||||
import { Status } from '../typings/common';
|
|
||||||
|
|
||||||
export const useStore = defineStore('store', {
|
|
||||||
state: () =>
|
state: () =>
|
||||||
({
|
({
|
||||||
activeData: {} as unknown,
|
region: { id: 'eu', value: 'PL1', name: 'PL1' },
|
||||||
rollingStockData: undefined,
|
|
||||||
donatorsData: [],
|
|
||||||
|
|
||||||
stationList: [],
|
|
||||||
regionOnlineCounters: [],
|
|
||||||
|
|
||||||
routesList: [],
|
|
||||||
|
|
||||||
sceneryData: [],
|
|
||||||
lastDispatcherStatuses: [],
|
|
||||||
|
|
||||||
region: { id: 'eu', value: 'PL1' },
|
|
||||||
|
|
||||||
trainCount: 0,
|
|
||||||
stationCount: 0,
|
|
||||||
|
|
||||||
webSocket: undefined,
|
|
||||||
isOffline: false,
|
isOffline: false,
|
||||||
|
appUpdate: null,
|
||||||
|
|
||||||
dispatcherStatsName: '',
|
dispatcherStatsName: '',
|
||||||
dispatcherStatsData: undefined,
|
dispatcherStatsStatus: Status.Data.Initialized,
|
||||||
|
|
||||||
driverStatsName: '',
|
driverStatsName: '',
|
||||||
driverStatsData: undefined,
|
driverStatsData: undefined,
|
||||||
@@ -44,31 +29,14 @@ export const useStore = defineStore('store', {
|
|||||||
|
|
||||||
chosenModalTrainId: undefined,
|
chosenModalTrainId: undefined,
|
||||||
|
|
||||||
dataStatuses: {
|
modalLastClickedTarget: null
|
||||||
connection: Status.Data.Loading,
|
}) as MainStoreState,
|
||||||
sceneries: Status.Data.Loading,
|
|
||||||
timetables: Status.Data.Loading,
|
|
||||||
dispatchers: Status.Data.Loading,
|
|
||||||
trains: Status.Data.Loading
|
|
||||||
},
|
|
||||||
|
|
||||||
currentStatsTab: null,
|
|
||||||
|
|
||||||
blockScroll: false,
|
|
||||||
listenerLaunched: false,
|
|
||||||
modalLastClickedTarget: null,
|
|
||||||
|
|
||||||
tooltip: {
|
|
||||||
content: '',
|
|
||||||
visible: false,
|
|
||||||
x: 0,
|
|
||||||
y: 0
|
|
||||||
}
|
|
||||||
}) as StoreState,
|
|
||||||
|
|
||||||
getters: {
|
getters: {
|
||||||
trainList(): Train[] {
|
trainList(): Train[] {
|
||||||
return (this.activeData?.trains ?? [])
|
const apiStore = useApiStore();
|
||||||
|
|
||||||
|
return (apiStore.activeData?.trains ?? [])
|
||||||
.filter((train) => train.timetable || train.online)
|
.filter((train) => train.timetable || train.online)
|
||||||
.map((train) => {
|
.map((train) => {
|
||||||
const stock = train.stockString.split(';');
|
const stock = train.stockString.split(';');
|
||||||
@@ -76,6 +44,15 @@ export const useStore = defineStore('store', {
|
|||||||
|
|
||||||
const timetable = train.timetable;
|
const timetable = train.timetable;
|
||||||
|
|
||||||
|
const sceneryNames =
|
||||||
|
train.timetable?.sceneries?.map(
|
||||||
|
(sceneryHash) =>
|
||||||
|
apiStore.activeData?.activeSceneries?.find((st) => st.stationHash === sceneryHash)
|
||||||
|
?.stationName ??
|
||||||
|
apiStore.sceneryData.find((sd) => sd.hash === sceneryHash)?.name ??
|
||||||
|
sceneryHash
|
||||||
|
) ?? [];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
trainId: train.driverName + train.trainNo.toString(),
|
trainId: train.driverName + train.trainNo.toString(),
|
||||||
|
|
||||||
@@ -87,7 +64,7 @@ export const useStore = defineStore('store', {
|
|||||||
|
|
||||||
distance: train.distance,
|
distance: train.distance,
|
||||||
signal: train.signal,
|
signal: train.signal,
|
||||||
online: train.online,
|
online: Boolean(train.online),
|
||||||
driverId: train.driverId,
|
driverId: train.driverId,
|
||||||
driverName: train.driverName,
|
driverName: train.driverName,
|
||||||
currentStationName: train.currentStationName,
|
currentStationName: train.currentStationName,
|
||||||
@@ -111,41 +88,71 @@ export const useStore = defineStore('store', {
|
|||||||
category: timetable.category,
|
category: timetable.category,
|
||||||
followingStops: timetable.stopList,
|
followingStops: timetable.stopList,
|
||||||
routeDistance: timetable.stopList[timetable.stopList.length - 1].stopDistance,
|
routeDistance: timetable.stopList[timetable.stopList.length - 1].stopDistance,
|
||||||
sceneries: timetable.sceneries
|
sceneries: timetable.sceneries,
|
||||||
|
sceneryNames: sceneryNames.reverse()
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
} as Train;
|
} as Train;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onlineSceneryList(state): OnlineScenery[] {
|
// computed active sceneries
|
||||||
if (state.isOffline) return [];
|
activeSceneryList(state): ActiveScenery[] {
|
||||||
if (!state.activeData?.activeSceneries) return [];
|
const apiStore = useApiStore();
|
||||||
|
|
||||||
return state.activeData?.activeSceneries.reduce((list, scenery) => {
|
if (state.isOffline) return [];
|
||||||
|
|
||||||
|
if (!apiStore.activeData?.activeSceneries) return [];
|
||||||
|
|
||||||
|
const offlineActiveSceneries = this.trainList.reduce((acc, train) => {
|
||||||
|
if (!train.timetableData) return acc;
|
||||||
|
|
||||||
|
train.timetableData.sceneryNames.forEach((name) => {
|
||||||
|
if (
|
||||||
|
acc.findIndex((v) => v.name == name && v.region == train.region) != -1 ||
|
||||||
|
apiStore.activeData?.activeSceneries?.findIndex(
|
||||||
|
(sc) =>
|
||||||
|
sc.stationName === name &&
|
||||||
|
sc.region == train.region &&
|
||||||
|
Date.now() - sc.lastSeen < 1000 * 60 * 2
|
||||||
|
) != -1
|
||||||
|
)
|
||||||
|
return acc;
|
||||||
|
|
||||||
|
acc.push({
|
||||||
|
name: name,
|
||||||
|
hash: '',
|
||||||
|
region: train.region,
|
||||||
|
maxUsers: 0,
|
||||||
|
currentUsers: 0,
|
||||||
|
spawns: [],
|
||||||
|
dispatcherName: '',
|
||||||
|
dispatcherRate: 0,
|
||||||
|
dispatcherId: -1,
|
||||||
|
dispatcherExp: -1,
|
||||||
|
dispatcherIsSupporter: false,
|
||||||
|
scheduledTrains: [],
|
||||||
|
stationTrains: [],
|
||||||
|
dispatcherStatus: Status.ActiveDispatcher.FREE,
|
||||||
|
dispatcherTimestamp: -1,
|
||||||
|
|
||||||
|
isOnline: false,
|
||||||
|
|
||||||
|
scheduledTrainCount: {
|
||||||
|
all: 0,
|
||||||
|
confirmed: 0,
|
||||||
|
unconfirmed: 0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, [] as ActiveScenery[]);
|
||||||
|
|
||||||
|
const onlineActiveSceneries = apiStore.activeData?.activeSceneries.reduce((list, scenery) => {
|
||||||
if (scenery.isOnline !== 1 && Date.now() - scenery.lastSeen > 1000 * 60 * 2) return list;
|
if (scenery.isOnline !== 1 && Date.now() - scenery.lastSeen > 1000 * 60 * 2) return list;
|
||||||
if (scenery.dispatcherStatus == Status.ActiveDispatcher.UNKNOWN) return list;
|
if (scenery.dispatcherStatus == Status.ActiveDispatcher.UNKNOWN) return list;
|
||||||
|
|
||||||
const station = this.stationList.find((s) => s.name === scenery.stationName);
|
|
||||||
|
|
||||||
const scheduledTrains = getScheduledTrains(this.trainList, scenery, station?.generalInfo);
|
|
||||||
|
|
||||||
const stationTrains = getStationTrains(
|
|
||||||
this.trainList,
|
|
||||||
scheduledTrains,
|
|
||||||
this.region.id,
|
|
||||||
scenery
|
|
||||||
);
|
|
||||||
|
|
||||||
// Remove checkpoint duplicates
|
|
||||||
const uniqueScheduledTrains = scheduledTrains.reduce(
|
|
||||||
(uniqueList, sTrain) =>
|
|
||||||
uniqueList.find((v) => v.trainId === sTrain.trainId)
|
|
||||||
? uniqueList
|
|
||||||
: [...uniqueList, sTrain],
|
|
||||||
[] as ScheduledTrain[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const dispatcherTimestamp =
|
const dispatcherTimestamp =
|
||||||
scenery.dispatcherStatus == Status.ActiveDispatcher.NO_LIMIT
|
scenery.dispatcherStatus == Status.ActiveDispatcher.NO_LIMIT
|
||||||
? Date.now() + 25500000
|
? Date.now() + 25500000
|
||||||
@@ -165,173 +172,143 @@ export const useStore = defineStore('store', {
|
|||||||
dispatcherId: scenery.dispatcherId,
|
dispatcherId: scenery.dispatcherId,
|
||||||
dispatcherExp: scenery.dispatcherExp,
|
dispatcherExp: scenery.dispatcherExp,
|
||||||
dispatcherIsSupporter: scenery.dispatcherIsSupporter,
|
dispatcherIsSupporter: scenery.dispatcherIsSupporter,
|
||||||
scheduledTrains: scheduledTrains,
|
|
||||||
stationTrains: stationTrains,
|
|
||||||
dispatcherStatus: scenery.dispatcherStatus,
|
dispatcherStatus: scenery.dispatcherStatus,
|
||||||
dispatcherTimestamp: dispatcherTimestamp,
|
dispatcherTimestamp: dispatcherTimestamp,
|
||||||
|
|
||||||
isOnline: scenery.isOnline == 1,
|
isOnline: scenery.isOnline == 1,
|
||||||
|
|
||||||
|
scheduledTrains: [],
|
||||||
|
stationTrains: [],
|
||||||
scheduledTrainCount: {
|
scheduledTrainCount: {
|
||||||
all: uniqueScheduledTrains.length,
|
all: 0,
|
||||||
confirmed: uniqueScheduledTrains.filter((train) => train.stopInfo.confirmed).length,
|
confirmed: 0,
|
||||||
unconfirmed: uniqueScheduledTrains.filter((train) => !train.stopInfo.confirmed).length
|
unconfirmed: 0
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}, [] as OnlineScenery[]);
|
}, [] as ActiveScenery[]);
|
||||||
|
|
||||||
|
const allActiveSceneries = [...onlineActiveSceneries, ...offlineActiveSceneries];
|
||||||
|
|
||||||
|
for (let i = 0, n = allActiveSceneries.length; i < n; i++) {
|
||||||
|
const scenery = allActiveSceneries[i];
|
||||||
|
|
||||||
|
const station = this.stationList.find((s) => s.name === scenery.name);
|
||||||
|
|
||||||
|
const scheduledTrains = getScheduledTrains(
|
||||||
|
this.trainList,
|
||||||
|
station?.generalInfo,
|
||||||
|
scenery.name,
|
||||||
|
scenery.region
|
||||||
|
);
|
||||||
|
|
||||||
|
const stationTrains = getStationTrains(
|
||||||
|
this.trainList,
|
||||||
|
scheduledTrains,
|
||||||
|
this.region.id,
|
||||||
|
scenery.name
|
||||||
|
);
|
||||||
|
|
||||||
|
// Remove checkpoint duplicates
|
||||||
|
const uniqueScheduledTrains = scheduledTrains.reduce(
|
||||||
|
(uniqueList, sTrain) =>
|
||||||
|
uniqueList.find((v) => v.trainId === sTrain.trainId)
|
||||||
|
? uniqueList
|
||||||
|
: [...uniqueList, sTrain],
|
||||||
|
[] as ScheduledTrain[]
|
||||||
|
);
|
||||||
|
|
||||||
|
scenery.scheduledTrains = scheduledTrains;
|
||||||
|
scenery.stationTrains = stationTrains;
|
||||||
|
|
||||||
|
scenery.scheduledTrainCount = {
|
||||||
|
all: uniqueScheduledTrains.length,
|
||||||
|
confirmed: uniqueScheduledTrains.filter((train) => train.stopInfo.confirmed).length,
|
||||||
|
unconfirmed: uniqueScheduledTrains.filter((train) => !train.stopInfo.confirmed).length
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return allActiveSceneries;
|
||||||
},
|
},
|
||||||
actions: {
|
|
||||||
async fetchStationsGeneralInfo() {
|
|
||||||
const sceneryData: StationJSONData[] = await (
|
|
||||||
await axios.get(`${URLs.stacjownikAPI}/api/getSceneries`)
|
|
||||||
).data;
|
|
||||||
|
|
||||||
if (!sceneryData) {
|
// computed station data
|
||||||
this.dataStatuses.sceneries = Status.Data.Error;
|
stationList(): Station[] {
|
||||||
return;
|
const apiStore = useApiStore();
|
||||||
}
|
|
||||||
|
return apiStore.sceneryData.map((scenery) => {
|
||||||
|
const routes = scenery.routesInfo.reduce(
|
||||||
|
(acc, route) => {
|
||||||
|
if (route.hidden) return acc;
|
||||||
|
|
||||||
|
const tracksKey = route.routeTracks == 2 ? 'double' : 'single';
|
||||||
|
const isElectric = route.isElectric;
|
||||||
|
const routesKey: keyof StationRoutes = `${tracksKey}${
|
||||||
|
!isElectric ? 'Other' : 'Electrified'
|
||||||
|
}Names`;
|
||||||
|
|
||||||
|
if (!route.isInternal) acc[routesKey].push(route.routeName);
|
||||||
|
if (route.isRouteSBL) acc['sblNames'].push(route.routeName);
|
||||||
|
|
||||||
|
acc.minRouteSpeed =
|
||||||
|
acc.minRouteSpeed == 0
|
||||||
|
? route.routeSpeed
|
||||||
|
: Math.min(route.routeSpeed, acc.minRouteSpeed);
|
||||||
|
|
||||||
|
acc.maxRouteSpeed = Math.max(route.routeSpeed, acc.maxRouteSpeed);
|
||||||
|
|
||||||
|
acc[tracksKey].push(route);
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
single: [],
|
||||||
|
singleElectrifiedNames: [],
|
||||||
|
singleOtherNames: [],
|
||||||
|
double: [],
|
||||||
|
doubleElectrifiedNames: [],
|
||||||
|
doubleOtherNames: [],
|
||||||
|
sblNames: [],
|
||||||
|
minRouteSpeed: 0,
|
||||||
|
maxRouteSpeed: 0
|
||||||
|
} as StationRoutes
|
||||||
|
);
|
||||||
|
|
||||||
this.stationList = sceneryData.map((scenery) => {
|
|
||||||
return {
|
return {
|
||||||
name: scenery.name,
|
name: scenery.name,
|
||||||
|
|
||||||
generalInfo: {
|
generalInfo: {
|
||||||
...scenery,
|
...scenery,
|
||||||
authors: scenery.authors?.split(',').map((a) => a.trim()),
|
authors: scenery.authors?.split(',').map((a) => a.trim()),
|
||||||
routes:
|
routes: routes,
|
||||||
scenery.routesInfo.reduce(
|
checkpoints: scenery.checkpoints?.split(';') ?? []
|
||||||
(acc, route) => {
|
|
||||||
const propName: keyof StationRoutes = `${
|
|
||||||
route.routeTracks == 2 ? 'twoWay' : 'oneWay'
|
|
||||||
}${route.isElectric ? '' : 'No'}CatenaryRouteNames`;
|
|
||||||
|
|
||||||
acc[route.routeTracks == 2 ? 'twoWay' : 'oneWay'].push({
|
|
||||||
name: route.routeName,
|
|
||||||
SBL: route.isRouteSBL,
|
|
||||||
TWB: false,
|
|
||||||
catenary: route.isElectric,
|
|
||||||
isInternal: route.isInternal,
|
|
||||||
tracks: route.routeTracks,
|
|
||||||
length: route.routeLength,
|
|
||||||
speed: route.routeSpeed
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!route.isInternal) acc[propName].push(route.routeName);
|
|
||||||
|
|
||||||
if (route.isRouteSBL) acc['sblRouteNames'].push(route.routeName);
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
{
|
|
||||||
oneWay: [],
|
|
||||||
twoWay: [],
|
|
||||||
sblRouteNames: [],
|
|
||||||
oneWayCatenaryRouteNames: [],
|
|
||||||
oneWayNoCatenaryRouteNames: [],
|
|
||||||
twoWayCatenaryRouteNames: [],
|
|
||||||
twoWayNoCatenaryRouteNames: []
|
|
||||||
} as StationRoutes
|
|
||||||
) || {},
|
|
||||||
checkpoints: scenery.checkpoints
|
|
||||||
? scenery.checkpoints
|
|
||||||
.split(';')
|
|
||||||
.map((sub) => ({ checkpointName: sub, scheduledTrains: [] }))
|
|
||||||
: []
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
async connectToWebsocket() {
|
allStationInfo(): Station[] {
|
||||||
if (import.meta.env.VITE_APP_WS_DEV === '1') {
|
const onlineUnsavedStations = this.activeSceneryList
|
||||||
const mockWebsocketData = await import('../data/mockWebsocketData.json');
|
.filter(
|
||||||
this.dataStatuses.connection = Status.Data.Loaded;
|
(scenery) =>
|
||||||
this.activeData = mockWebsocketData as any;
|
this.stationList.findIndex((st) => st.name == scenery.name) == -1 &&
|
||||||
this.setStatuses();
|
scenery.region == this.region.id
|
||||||
|
|
||||||
console.warn('Stacjownik działa w trybie mockowania danych z WS');
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const socket = io(URLs.stacjownikAPI, {
|
|
||||||
transports: ['websocket', 'polling'],
|
|
||||||
rememberUpgrade: true,
|
|
||||||
reconnection: true
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.emit('CONNECTION', { version: packageInfo.version });
|
|
||||||
|
|
||||||
socket.on('connect_error', () => {
|
|
||||||
this.dataStatuses.connection = Status.Data.Error;
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('UPDATE', (data: Websocket.ActiveData) => {
|
|
||||||
this.activeData = data;
|
|
||||||
this.dataStatuses.connection = Status.Data.Loaded;
|
|
||||||
|
|
||||||
this.setStatuses();
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.emit('FETCH_DATA', { version: packageInfo.version }, (data: Websocket.ActiveData) => {
|
|
||||||
this.dataStatuses.connection = Status.Data.Loaded;
|
|
||||||
this.activeData = data;
|
|
||||||
this.setStatuses();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.webSocket = socket;
|
|
||||||
},
|
|
||||||
|
|
||||||
async connectToAPI() {
|
|
||||||
this.connectToWebsocket();
|
|
||||||
this.fetchStockInfoData();
|
|
||||||
this.fetchDonatorsData();
|
|
||||||
this.fetchStationsGeneralInfo();
|
|
||||||
},
|
|
||||||
|
|
||||||
async changeRegion(region: StoreState['region']) {
|
|
||||||
this.region = region;
|
|
||||||
},
|
|
||||||
|
|
||||||
async fetchStockInfoData() {
|
|
||||||
try {
|
|
||||||
this.rollingStockData = (
|
|
||||||
await axios.get<API.RollingStock.Response>(
|
|
||||||
'https://raw.githubusercontent.com/Spythere/api/main/td2/data/stockInfo.json'
|
|
||||||
)
|
)
|
||||||
).data;
|
.map((os) => ({
|
||||||
} catch (error) {
|
name: os.name,
|
||||||
console.error('Ups! Wystąpił błąd podczas pobierania informacji o taborze z API:', error);
|
generalInfo: undefined,
|
||||||
}
|
onlineInfo: os
|
||||||
},
|
}));
|
||||||
|
|
||||||
async fetchDonatorsData() {
|
return [
|
||||||
try {
|
...onlineUnsavedStations,
|
||||||
const response = await axios.get<GithubAPI.Donators.Response>(
|
...this.stationList.map((st) => ({
|
||||||
'https://raw.githubusercontent.com/Spythere/api/main/td2/data/donators.json'
|
...st,
|
||||||
);
|
onlineInfo: this.activeSceneryList.find(
|
||||||
|
(os) => os.name == st.name && os.region == this.region.id
|
||||||
if (response.data) this.donatorsData = response.data;
|
)
|
||||||
} catch (error) {
|
}))
|
||||||
console.error('Ups! Wystąpił błąd podczas pobierania informacji o donatorach:', error);
|
];
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async setStatuses() {
|
|
||||||
if (!this.activeData.activeSceneries) {
|
|
||||||
this.dataStatuses.sceneries = Status.Data.Error;
|
|
||||||
this.dataStatuses.trains = Status.Data.Error;
|
|
||||||
this.dataStatuses.dispatchers = Status.Data.Error;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.dataStatuses.sceneries = Status.Data.Loaded;
|
|
||||||
this.dataStatuses.trains = !this.activeData.trains ? Status.Data.Warning : Status.Data.Loaded;
|
|
||||||
this.dataStatuses.dispatchers = Status.Data.Loaded;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import inputData from '../data/options.json';
|
import inputData from '../data/options.json';
|
||||||
import { useStore } from './mainStore';
|
import { useMainStore } from './mainStore';
|
||||||
import { filterStations, sortStations } from '../scripts/utils/filterUtils';
|
import { filterStations, sortStations } from '../scripts/utils/stationFilterUtils';
|
||||||
import { HeadIdsTypes } from '../scripts/data/stationHeaderNames';
|
import { HeadIdsTypes } from '../scripts/data/stationHeaderNames';
|
||||||
import StorageManager from '../managers/storageManager';
|
import StorageManager from '../managers/storageManager';
|
||||||
import { Filter } from '../components/StationsView/typings';
|
import { Filter } from '../components/StationsView/typings';
|
||||||
@@ -16,6 +16,8 @@ const filterInitStates: Filter = {
|
|||||||
SPE: false,
|
SPE: false,
|
||||||
SUP: false,
|
SUP: false,
|
||||||
noSUP: false,
|
noSUP: false,
|
||||||
|
ASDEK: false,
|
||||||
|
noASDEK: false,
|
||||||
ręczne: false,
|
ręczne: false,
|
||||||
'ręczne+SPK': false,
|
'ręczne+SPK': false,
|
||||||
'ręczne+SCS': false,
|
'ręczne+SCS': false,
|
||||||
@@ -48,9 +50,11 @@ const filterInitStates: Filter = {
|
|||||||
noSpaceStatus: false,
|
noSpaceStatus: false,
|
||||||
unavailableStatus: false,
|
unavailableStatus: false,
|
||||||
unsignedStatus: false,
|
unsignedStatus: false,
|
||||||
|
withActiveTimetables: false,
|
||||||
|
withoutActiveTimetables: false,
|
||||||
|
maxVmax: 200,
|
||||||
|
minVmax: 0,
|
||||||
authors: '',
|
authors: '',
|
||||||
|
|
||||||
onlineFromHours: 0
|
onlineFromHours: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -70,14 +74,9 @@ export const useStationFiltersStore = defineStore('stationFiltersStore', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
filteredStationList: (state) => {
|
filteredStationList: (state) => {
|
||||||
const store = useStore();
|
const store = useMainStore();
|
||||||
return store.stationList
|
|
||||||
.map((station) => ({
|
return store.allStationInfo
|
||||||
...station,
|
|
||||||
onlineInfo: store.onlineSceneryList.find(
|
|
||||||
(os) => os.name == station.name && os.region == store.region.id
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
.filter((station) => filterStations(station, state.filters))
|
.filter((station) => filterStations(station, state.filters))
|
||||||
.sort((a, b) => sortStations(a, b, state.sorterActive));
|
.sort((a, b) => sortStations(a, b, state.sorterActive));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { defineStore } from 'pinia';
|
||||||
|
|
||||||
|
const isTooltip = (v: any): v is TooltipType => tooltipKeys.includes(v);
|
||||||
|
|
||||||
|
export const tooltipKeys = [
|
||||||
|
'DonatorTooltip',
|
||||||
|
'BaseTooltip',
|
||||||
|
'VehiclePreviewTooltip',
|
||||||
|
'SpawnsTooltip',
|
||||||
|
'UsersTooltip'
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export type TooltipType = (typeof tooltipKeys)[number];
|
||||||
|
|
||||||
|
export const useTooltipStore = defineStore('tooltipStore', {
|
||||||
|
state: () => ({
|
||||||
|
mousePos: [0, 0],
|
||||||
|
type: null as TooltipType | null,
|
||||||
|
content: ''
|
||||||
|
}),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
show(_e: MouseEvent, type: string, value?: string) {
|
||||||
|
if (!isTooltip(type)) return;
|
||||||
|
|
||||||
|
this.type = type;
|
||||||
|
this.content = value ?? '';
|
||||||
|
},
|
||||||
|
|
||||||
|
hide() {
|
||||||
|
this.type = null;
|
||||||
|
this.content = '';
|
||||||
|
},
|
||||||
|
|
||||||
|
handle(e: MouseEvent) {
|
||||||
|
const targetEl = e
|
||||||
|
.composedPath()
|
||||||
|
.find((p) => p instanceof HTMLElement && p.getAttribute('data-tooltip-type'));
|
||||||
|
|
||||||
|
if (!targetEl || !(targetEl instanceof HTMLElement)) {
|
||||||
|
if (this.type != null) this.hide();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tooltipType = targetEl.getAttribute('data-tooltip-type');
|
||||||
|
const tooltipContent = targetEl.getAttribute('data-tooltip-content');
|
||||||
|
|
||||||
|
if (tooltipType && tooltipContent) this.show(e, tooltipType, tooltipContent);
|
||||||
|
else if (this.type != null) this.hide();
|
||||||
|
|
||||||
|
this.mousePos[0] = e.pageX;
|
||||||
|
this.mousePos[1] = e.pageY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
+7
-171
@@ -1,78 +1,17 @@
|
|||||||
import { Socket } from 'socket.io-client';
|
import { API } from '../typings/api';
|
||||||
import Station from '../scripts/interfaces/Station';
|
import { Availability, StationRoutesInfo, Status } from '../typings/common';
|
||||||
import { API, GithubAPI, Websocket } from '../typings/api';
|
|
||||||
import { Status } from '../typings/common';
|
|
||||||
|
|
||||||
export type Availability = 'default' | 'unavailable' | 'nonPublic' | 'abandoned' | 'nonDefault';
|
export interface MainStoreState {
|
||||||
|
region: { id: string; value: string; name: string };
|
||||||
export interface RegionCounters {
|
|
||||||
stationCount: number;
|
|
||||||
trainsCount: number;
|
|
||||||
timetablesCount: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface StoreState {
|
|
||||||
stationList: Station[];
|
|
||||||
activeData: Websocket.ActiveData;
|
|
||||||
rollingStockData?: API.RollingStock.Response;
|
|
||||||
donatorsData: GithubAPI.Donators.Response;
|
|
||||||
|
|
||||||
regionOnlineCounters: RegionCounters[];
|
|
||||||
|
|
||||||
lastDispatcherStatuses: {
|
|
||||||
hash: string;
|
|
||||||
statusTimestamp: number;
|
|
||||||
statusID: Status.ActiveDispatcher;
|
|
||||||
}[];
|
|
||||||
|
|
||||||
sceneryData: any[][];
|
|
||||||
|
|
||||||
region: { id: string; value: string };
|
|
||||||
trainCount: number;
|
|
||||||
stationCount: number;
|
|
||||||
|
|
||||||
webSocket?: Socket;
|
|
||||||
isOffline: boolean;
|
isOffline: boolean;
|
||||||
|
appUpdate: { version: string; changelog: string; releaseURL: string } | null;
|
||||||
dispatcherStatsName: string;
|
dispatcherStatsName: string;
|
||||||
dispatcherStatsData?: API.DispatcherStats.Response;
|
dispatcherStatsData?: API.DispatcherStats.Response;
|
||||||
|
|
||||||
driverStatsName: string;
|
driverStatsName: string;
|
||||||
driverStatsData?: API.DriverStats.Response;
|
driverStatsData?: API.DriverStats.Response;
|
||||||
driverStatsStatus: Status.Data;
|
driverStatsStatus: Status.Data;
|
||||||
|
|
||||||
chosenModalTrainId?: string;
|
chosenModalTrainId?: string;
|
||||||
|
|
||||||
currentStatsTab: 'daily' | 'driver' | null;
|
|
||||||
|
|
||||||
dataStatuses: {
|
|
||||||
connection: Status.Data;
|
|
||||||
sceneries: Status.Data;
|
|
||||||
timetables: Status.Data;
|
|
||||||
dispatchers: Status.Data;
|
|
||||||
trains: Status.Data;
|
|
||||||
};
|
|
||||||
|
|
||||||
listenerLaunched: boolean;
|
|
||||||
blockScroll: boolean;
|
|
||||||
modalLastClickedTarget: EventTarget | null;
|
modalLastClickedTarget: EventTarget | null;
|
||||||
|
|
||||||
tooltip: {
|
|
||||||
visible: boolean;
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
content: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface StationRoutesInfo {
|
|
||||||
routeName: string;
|
|
||||||
isElectric: boolean;
|
|
||||||
isInternal: boolean;
|
|
||||||
isRouteSBL: boolean;
|
|
||||||
routeLength: number;
|
|
||||||
routeSpeed: number;
|
|
||||||
routeTracks: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StationJSONData {
|
export interface StationJSONData {
|
||||||
@@ -82,6 +21,7 @@ export interface StationJSONData {
|
|||||||
lines: string;
|
lines: string;
|
||||||
project: string;
|
project: string;
|
||||||
projectUrl: string;
|
projectUrl: string;
|
||||||
|
hash: string;
|
||||||
|
|
||||||
reqLevel: number;
|
reqLevel: number;
|
||||||
|
|
||||||
@@ -89,6 +29,7 @@ export interface StationJSONData {
|
|||||||
controlType: string;
|
controlType: string;
|
||||||
|
|
||||||
SUP: boolean;
|
SUP: boolean;
|
||||||
|
ASDEK: boolean;
|
||||||
|
|
||||||
// routes: string;
|
// routes: string;
|
||||||
routesInfo: StationRoutesInfo[];
|
routesInfo: StationRoutesInfo[];
|
||||||
@@ -98,108 +39,3 @@ export interface StationJSONData {
|
|||||||
|
|
||||||
availability: Availability;
|
availability: Availability;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OnlineScenery {
|
|
||||||
name: string;
|
|
||||||
hash: string;
|
|
||||||
region: string;
|
|
||||||
maxUsers: number;
|
|
||||||
currentUsers: number;
|
|
||||||
spawns: { spawnName: string; spawnLength: number; isElectrified: boolean }[];
|
|
||||||
dispatcherName: string;
|
|
||||||
dispatcherRate: number;
|
|
||||||
dispatcherId: number;
|
|
||||||
dispatcherExp: number;
|
|
||||||
dispatcherIsSupporter: boolean;
|
|
||||||
|
|
||||||
dispatcherStatus: Status.ActiveDispatcher | number;
|
|
||||||
dispatcherTimestamp: number | null;
|
|
||||||
|
|
||||||
isOnline: boolean;
|
|
||||||
|
|
||||||
stationTrains?: StationTrain[];
|
|
||||||
scheduledTrains?: ScheduledTrain[];
|
|
||||||
|
|
||||||
scheduledTrainCount: {
|
|
||||||
all: number;
|
|
||||||
confirmed: number;
|
|
||||||
unconfirmed: number;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface StationTrain {
|
|
||||||
driverName: string;
|
|
||||||
driverId: number;
|
|
||||||
trainNo: number;
|
|
||||||
trainId: string;
|
|
||||||
stopStatus: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ScheduledTrain {
|
|
||||||
checkpointName: string;
|
|
||||||
|
|
||||||
trainId: string;
|
|
||||||
trainNo: number;
|
|
||||||
|
|
||||||
driverName: string;
|
|
||||||
driverId: number;
|
|
||||||
currentStationName: string;
|
|
||||||
currentStationHash: string;
|
|
||||||
category: string;
|
|
||||||
stopInfo: TrainStop;
|
|
||||||
|
|
||||||
terminatesAt: string;
|
|
||||||
beginsAt: string;
|
|
||||||
|
|
||||||
prevStationName: string;
|
|
||||||
nextStationName: string;
|
|
||||||
|
|
||||||
arrivingLine: string | null;
|
|
||||||
departureLine: string | null;
|
|
||||||
|
|
||||||
prevDepartureLine: string | null;
|
|
||||||
nextArrivalLine: string | null;
|
|
||||||
|
|
||||||
signal: string;
|
|
||||||
connectedTrack: string;
|
|
||||||
|
|
||||||
stopLabel: string;
|
|
||||||
stopStatus: StopStatus;
|
|
||||||
stopStatusID: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum StopStatus {
|
|
||||||
ARRIVING = 'arriving',
|
|
||||||
DEPARTED = 'departed',
|
|
||||||
DEPARTED_AWAY = 'departed-away',
|
|
||||||
ONLINE = 'online',
|
|
||||||
STOPPED = 'stopped',
|
|
||||||
TERMINATED = 'terminated'
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TrainStop {
|
|
||||||
stopName: string;
|
|
||||||
stopNameRAW: string;
|
|
||||||
stopType: string;
|
|
||||||
stopDistance: number;
|
|
||||||
mainStop: boolean;
|
|
||||||
|
|
||||||
arrivalLine: string | null;
|
|
||||||
arrivalTimestamp: number;
|
|
||||||
arrivalRealTimestamp: number;
|
|
||||||
arrivalDelay: number;
|
|
||||||
|
|
||||||
departureLine: string | null;
|
|
||||||
departureTimestamp: number;
|
|
||||||
departureRealTimestamp: number;
|
|
||||||
departureDelay: number;
|
|
||||||
pointId: number;
|
|
||||||
|
|
||||||
comments?: string;
|
|
||||||
|
|
||||||
beginsHere: boolean;
|
|
||||||
terminatesHere: boolean;
|
|
||||||
confirmed: boolean;
|
|
||||||
stopped: boolean;
|
|
||||||
stopTime: number | null;
|
|
||||||
}
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user