Compare commits
176 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fb45a783ee | |||
| 71476e9552 | |||
| 922a338143 | |||
| 231d36e877 | |||
| 27d6ac9f14 | |||
| a6029da2cc | |||
| a3f3790205 | |||
| ebfb24f729 | |||
| e521736618 | |||
| fc7662e431 | |||
| a459fdf178 | |||
| 4e7fba89ee | |||
| 6084e5876d | |||
| 44f548c7b7 | |||
| 59a5fbe5ac | |||
| c252213ed9 | |||
| fb56378f18 | |||
| e9635eae06 | |||
| 1fc98a8f99 | |||
| c9de1a48ce | |||
| fee9774f88 | |||
| 7c974e8d0e | |||
| c84fbbcf42 | |||
| 45af649505 | |||
| 6c1e00d002 | |||
| 69ff85cfb1 | |||
| bdc2ca784c | |||
| dbd73d448d | |||
| 26b1ec246d | |||
| 8190dfa2cb | |||
| 44df685606 | |||
| 785a42b849 | |||
| ccfcca8728 | |||
| d9a7ba122c | |||
| bf8d4a9ef4 | |||
| 6ea1e91d1d | |||
| 813b557455 | |||
| 834b14da69 | |||
| c809b2146d | |||
| 33b98ca313 | |||
| bcb9c63cb0 | |||
| 17d77a80d8 | |||
| 65b159f8fd | |||
| 063d5283e4 | |||
| 29de1b3c4b | |||
| f0c02bf12e | |||
| 8aa23468b3 | |||
| 4c1fcf710b | |||
| a529d6e9eb | |||
| 9fc602e08f | |||
| 56e40bd84b | |||
| a5b5df7452 | |||
| 1a8da02ced | |||
| 7e75fa2516 | |||
| 3ed2c09184 | |||
| 6901c3d2b4 | |||
| 8417754403 | |||
| de5c57181a | |||
| d91d4cc6a8 | |||
| 9a5fd4d670 | |||
| 4202a55673 | |||
| 5181e8f4af | |||
| e117f62fcb | |||
| e0036bf969 | |||
| 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 |
@@ -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
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "stacjownik",
|
"name": "stacjownik",
|
||||||
"version": "1.20.2",
|
"version": "1.25.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
@@ -14,10 +14,9 @@
|
|||||||
"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",
|
||||||
|
"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"
|
||||||
@@ -25,7 +24,8 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rushstack/eslint-patch": "^1.3.3",
|
"@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",
|
||||||
"@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",
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
"prettier": "^3.0.3",
|
"prettier": "^3.0.3",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
"vite": "^4.4.9",
|
"vite": "^4.4.9",
|
||||||
"vite-plugin-pwa": "^0.16.5",
|
"vite-plugin-pwa": "^0.20.0",
|
||||||
"vue-tsc": "^1.8.11"
|
"vue-tsc": "^1.8.11"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
|
|||||||
@@ -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 |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 26 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;
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="app_container">
|
<div class="app_container">
|
||||||
|
<UpdateCard
|
||||||
|
:is-update-card-open="isUpdateCardOpen"
|
||||||
|
@toggle-card="() => (isUpdateCardOpen = 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>
|
||||||
|
|
||||||
@@ -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="() => (isUpdateCardOpen = 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,38 +42,48 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, watch } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import packageInfo from '.././package.json';
|
|
||||||
|
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 { useMainStore } from './store/mainStore';
|
|
||||||
|
|
||||||
import StatusIndicator from './components/App/StatusIndicator.vue';
|
import StatusIndicator from './components/App/StatusIndicator.vue';
|
||||||
import AppHeader from './components/App/AppHeader.vue';
|
import AppHeader from './components/App/AppHeader.vue';
|
||||||
import TrainModal from './components/TrainsView/TrainModal.vue';
|
import TrainModal from './components/TrainsView/TrainModal.vue';
|
||||||
|
import Tooltip from './components/Tooltip/Tooltip.vue';
|
||||||
|
import UpdateCard from './components/App/UpdateCard.vue';
|
||||||
|
|
||||||
import StorageManager from './managers/storageManager';
|
import StorageManager from './managers/storageManager';
|
||||||
import { useApiStore } from './store/apiStore';
|
|
||||||
import { Status } from './typings/common';
|
const STORAGE_VERSION_KEY = 'app_version';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
Clock,
|
Clock,
|
||||||
StatusIndicator,
|
StatusIndicator,
|
||||||
AppHeader,
|
AppHeader,
|
||||||
TrainModal
|
TrainModal,
|
||||||
|
UpdateCard,
|
||||||
|
Tooltip
|
||||||
},
|
},
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
VERSION: packageInfo.version,
|
VERSION: version,
|
||||||
store: useMainStore(),
|
store: useMainStore(),
|
||||||
apiStore: useApiStore(),
|
apiStore: useApiStore(),
|
||||||
|
tooltipStore: useTooltipStore(),
|
||||||
|
|
||||||
|
isUpdateCardOpen: false,
|
||||||
|
|
||||||
currentLang: 'pl',
|
currentLang: 'pl',
|
||||||
releaseURL: '',
|
isOnProductionHost: location.hostname == 'stacjownik-td2.web.app',
|
||||||
isOnProductionHost: location.hostname == 'stacjownik-td2.web.app'
|
|
||||||
|
nextUpdateTime: 0
|
||||||
}),
|
}),
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
@@ -71,22 +91,52 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
async mounted() {
|
async mounted() {
|
||||||
watch(
|
window.addEventListener('mousemove', (e: MouseEvent) => this.tooltipStore.handle(e));
|
||||||
() => this.store.blockScroll,
|
|
||||||
(value) => {
|
|
||||||
if (value) document.body.classList.add('no-scroll');
|
|
||||||
else document.body.classList.remove('no-scroll');
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
init() {
|
init() {
|
||||||
this.loadLang();
|
this.loadLang();
|
||||||
this.setReleaseURL();
|
|
||||||
this.setupOfflineHandling();
|
this.setupOfflineHandling();
|
||||||
|
this.checkAppVersion();
|
||||||
|
|
||||||
this.apiStore.setupAPI();
|
this.apiStore.setupAPIData();
|
||||||
|
window.requestAnimationFrame(this.update);
|
||||||
|
|
||||||
|
if (!this.isOnProductionHost) document.title = 'Stacjownik Dev';
|
||||||
|
},
|
||||||
|
|
||||||
|
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 {
|
||||||
|
const releaseData = await (
|
||||||
|
await axios.get('https://api.github.com/repos/Spythere/stacjownik/releases/latest')
|
||||||
|
).data;
|
||||||
|
|
||||||
|
if (!releaseData) return;
|
||||||
|
|
||||||
|
this.store.appUpdate = {
|
||||||
|
version,
|
||||||
|
changelog: releaseData.body,
|
||||||
|
releaseURL: releaseData.html_url
|
||||||
|
};
|
||||||
|
|
||||||
|
this.isUpdateCardOpen =
|
||||||
|
storageVersion != version || import.meta.env.VITE_UPDATE_TEST === 'test';
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Wystąpił błąd podczas pobierania danych z API GitHuba: ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
StorageManager.setStringValue(STORAGE_VERSION_KEY, version);
|
||||||
},
|
},
|
||||||
|
|
||||||
setupOfflineHandling() {
|
setupOfflineHandling() {
|
||||||
@@ -101,16 +151,14 @@ export default defineComponent({
|
|||||||
handleOfflineMode() {
|
handleOfflineMode() {
|
||||||
this.store.isOffline = true;
|
this.store.isOffline = true;
|
||||||
|
|
||||||
this.apiStore.stopActiveDataScheduler();
|
|
||||||
this.apiStore.activeData = undefined;
|
this.apiStore.activeData = undefined;
|
||||||
|
|
||||||
this.apiStore.dataStatuses.connection = Status.Data.Offline;
|
this.apiStore.dataStatuses.connection = Status.Data.Offline;
|
||||||
},
|
},
|
||||||
|
|
||||||
handleOnlineMode() {
|
handleOnlineMode() {
|
||||||
this.store.isOffline = false;
|
this.store.isOffline = false;
|
||||||
|
|
||||||
this.apiStore.setupAPI();
|
this.apiStore.connectToAPI();
|
||||||
},
|
},
|
||||||
|
|
||||||
changeLang(lang: string) {
|
changeLang(lang: string) {
|
||||||
@@ -120,21 +168,6 @@ export default defineComponent({
|
|||||||
StorageManager.setStringValue('lang', lang);
|
StorageManager.setStringValue('lang', lang);
|
||||||
},
|
},
|
||||||
|
|
||||||
async setReleaseURL() {
|
|
||||||
try {
|
|
||||||
const releaseData = await (
|
|
||||||
await axios.get('https://api.github.com/repos/Spythere/stacjownik/releases/latest')
|
|
||||||
).data;
|
|
||||||
|
|
||||||
if (!releaseData) return;
|
|
||||||
|
|
||||||
this.releaseURL = releaseData.html_url;
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Wystąpił błąd podczas pobierania danych z API GitHuba: ${error}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
loadLang() {
|
loadLang() {
|
||||||
const storageLang = StorageManager.getStringValue('lang');
|
const storageLang = StorageManager.getStringValue('lang');
|
||||||
|
|
||||||
@@ -147,7 +180,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
const naviLanguage = window.navigator.language.toString();
|
const naviLanguage = window.navigator.language.toString();
|
||||||
|
|
||||||
if (naviLanguage.includes('en')) {
|
if (naviLanguage.startsWith('en')) {
|
||||||
this.changeLang('en');
|
this.changeLang('en');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -156,4 +189,81 @@ 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.85vw);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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" />
|
||||||
@@ -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,125 +198,112 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { useMainStore } from '../../store/mainStore';
|
|
||||||
import { Status } from '../../typings/common';
|
import { Status } from '../../typings/common';
|
||||||
import { useApiStore } from '../../store/apiStore';
|
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 = useMainStore();
|
|
||||||
const apiStore = useApiStore();
|
|
||||||
|
|
||||||
return {
|
|
||||||
dataStatus: apiStore.dataStatuses,
|
|
||||||
store
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
watch: {
|
|
||||||
dataStatus: {
|
|
||||||
deep: true,
|
|
||||||
|
|
||||||
handler(statuses: any) {
|
|
||||||
const connectionStatus = statuses.connection;
|
|
||||||
const sceneryDataStatus = statuses.sceneries;
|
|
||||||
const trainsDataStatus = statuses.trains;
|
|
||||||
const dispatcherDataStatus = statuses.dispatchers;
|
|
||||||
|
|
||||||
if (connectionStatus == Status.Data.Offline) {
|
|
||||||
this.setSignalStatus(Status.Data.Offline);
|
|
||||||
this.indicator.status = Status.Data.Offline;
|
|
||||||
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 || status == Status.Data.Offline) {
|
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,123 @@
|
|||||||
|
<template>
|
||||||
|
<Card :is-open="isUpdateCardOpen" @toggle-card="toggleCard(false)">
|
||||||
|
<div class="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="toggleCard(false)">
|
||||||
|
{{ $t('update.confirm') }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<p class="bottom-info">
|
||||||
|
{{ $t('update.info-1') }}
|
||||||
|
<br />
|
||||||
|
<span v-html="$t('update.info-2')"></span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import { useMainStore } from '../../store/mainStore';
|
||||||
|
import { version } from '../../../package.json';
|
||||||
|
import { Converter } from 'showdown';
|
||||||
|
|
||||||
|
import Card from '../Global/Card.vue';
|
||||||
|
|
||||||
|
const converter = new Converter();
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: { Card },
|
||||||
|
|
||||||
|
props: {
|
||||||
|
isUpdateCardOpen: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
emits: ['toggleCard'],
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
mainStore: useMainStore(),
|
||||||
|
version: version
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
isUpdateCardOpen(val: boolean) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (val) (this.$refs['confirm-btn'] as HTMLElement).focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
htmlChangelog() {
|
||||||
|
if (this.mainStore.appUpdate == null) return '';
|
||||||
|
|
||||||
|
return converter.makeHtml(this.mainStore.appUpdate.changelog);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
toggleCard(value: boolean) {
|
||||||
|
this.$emit('toggleCard', value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '../../styles/variables';
|
||||||
|
|
||||||
|
::v-deep(h1) {
|
||||||
|
text-align: center;
|
||||||
|
color: $accentCol;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep(h2) {
|
||||||
|
padding: 0.25em 0;
|
||||||
|
border-bottom: 1px solid #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep(ul) {
|
||||||
|
list-style: initial;
|
||||||
|
padding: 1em;
|
||||||
|
line-height: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto 1fr auto;
|
||||||
|
gap: 0.5em;
|
||||||
|
padding: 1em;
|
||||||
|
min-height: 700px;
|
||||||
|
overflow: auto;
|
||||||
|
text-align: justify;
|
||||||
|
max-width: 700px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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,101 +0,0 @@
|
|||||||
<template>
|
|
||||||
<transition name="modal-anim" tag="div" class="modal">
|
|
||||||
<div class="body" v-if="isOpen">
|
|
||||||
<div class="background" @click="toggleModal(false)"></div>
|
|
||||||
<div class="wrapper" ref="wrapper" tabindex="0">
|
|
||||||
<slot></slot>
|
|
||||||
</div>
|
|
||||||
<div class="tab-exit" ref="exit" tabindex="0" @focus="toggleModal(false)"></div>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
import { useMainStore } from '../../store/mainStore';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
emits: ['toggleModal'],
|
|
||||||
|
|
||||||
props: {
|
|
||||||
isOpen: Boolean
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
store: useMainStore()
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
watch: {
|
|
||||||
isOpen(v) {
|
|
||||||
this.$nextTick(() => {
|
|
||||||
if (v) (this.$refs['wrapper'] as HTMLElement).focus();
|
|
||||||
else (this.store.modalLastClickedTarget as HTMLElement)?.focus();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
toggleModal(value: boolean) {
|
|
||||||
this.$emit('toggleModal', value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@import '../../styles/responsive.scss';
|
|
||||||
|
|
||||||
.body {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
z-index: 200;
|
|
||||||
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.background {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
background-color: rgba(0, 0, 0, 0.55);
|
|
||||||
}
|
|
||||||
|
|
||||||
.wrapper {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
|
|
||||||
background-color: #1a1a1a;
|
|
||||||
box-shadow: 0 0 15px 10px #333333;
|
|
||||||
|
|
||||||
width: 95%;
|
|
||||||
max-width: 800px;
|
|
||||||
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>
|
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
<template>
|
||||||
|
<transition name="modal-anim" tag="div">
|
||||||
|
<div class="card" v-if="isOpen">
|
||||||
|
<div class="card-background" @click="toggleCard(false)"></div>
|
||||||
|
<div class="card-body" tabindex="0">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import { useMainStore } from '../../store/mainStore';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
emits: ['toggleCard'],
|
||||||
|
|
||||||
|
props: {
|
||||||
|
isOpen: Boolean
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
store: useMainStore()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
isOpen(v) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (v == false) (this.store.modalLastClickedTarget as HTMLElement)?.focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
toggleCard(value: boolean) {
|
||||||
|
this.$emit('toggleCard', value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '../../styles/responsive.scss';
|
||||||
|
|
||||||
|
.card {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 200;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-background {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
background-color: rgba(0, 0, 0, 0.55);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-body {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
margin: 1em;
|
||||||
|
|
||||||
|
max-height: 95vh;
|
||||||
|
max-height: 95dvh;
|
||||||
|
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
box-shadow: 0 0 15px 10px #0e0e0e;
|
||||||
|
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include smallScreen {
|
||||||
|
.card {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="donation-modal" @keydown.esc="toggleModal(false)">
|
|
||||||
<button
|
|
||||||
class="btn-toggle 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 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"
|
|
||||||
>
|
|
||||||
<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>
|
|
||||||
</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';
|
|
||||||
|
|
||||||
button.btn-toggle {
|
|
||||||
$btnColor: #254069;
|
|
||||||
|
|
||||||
background-color: $btnColor;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: lighten($btnColor, 5%);
|
|
||||||
}
|
|
||||||
|
|
||||||
@include smallScreen {
|
|
||||||
span {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
|
|
||||||
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%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -0,0 +1,256 @@
|
|||||||
|
<template>
|
||||||
|
<Card :isOpen="isCardOpen" @toggleCard="toggleCard" @keydown.esc="toggleCard(false)">
|
||||||
|
<div class="body">
|
||||||
|
<div class="content">
|
||||||
|
<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="actions">
|
||||||
|
<a
|
||||||
|
class="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="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="action btn--image exit" @click="toggleCard(false)">
|
||||||
|
<img src="/images/icon-exit.svg" alt="dollar donation icon" />
|
||||||
|
{{ $t('donations.action-exit') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import { useApiStore } from '../../store/apiStore';
|
||||||
|
import Card from './Card.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: { Card },
|
||||||
|
props: {
|
||||||
|
isCardOpen: Boolean
|
||||||
|
},
|
||||||
|
|
||||||
|
emits: ['toggleCard'],
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
isCardOpen(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: {
|
||||||
|
toggleCard(value: boolean) {
|
||||||
|
this.$emit('toggleCard', 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';
|
||||||
|
|
||||||
|
.body {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 1fr auto;
|
||||||
|
gap: 1em;
|
||||||
|
|
||||||
|
font-size: 1.1em;
|
||||||
|
|
||||||
|
max-width: 820px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
overflow: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-height: 20px;
|
||||||
|
margin-right: 5px;
|
||||||
|
vertical-align: text-bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 1.95em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
text-align: justify;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.discord {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||||
|
gap: 0.5em;
|
||||||
|
padding: 1em;
|
||||||
|
|
||||||
|
form button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions > .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>
|
||||||
@@ -59,20 +59,18 @@ export default defineComponent({
|
|||||||
'store.region.id': {
|
'store.region.id': {
|
||||||
handler(regionId) {
|
handler(regionId) {
|
||||||
this.selectedItemIndex = this.regionList.findIndex((reg) => reg.id == regionId);
|
this.selectedItemIndex = this.regionList.findIndex((reg) => reg.id == regionId);
|
||||||
|
|
||||||
console.log('region id', regionId);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'$route.query.region': {
|
'$route.query.region': {
|
||||||
immediate: true,
|
immediate: true,
|
||||||
handler(regionQuery: string) {
|
handler(regionQuery: string) {
|
||||||
if (regionQuery) {
|
if (regionQuery) {
|
||||||
this.store.region.id =
|
this.store.region =
|
||||||
regionsJSON.find(
|
regionsJSON.find(
|
||||||
(reg) =>
|
(reg) =>
|
||||||
reg.id == regionQuery.toLocaleLowerCase() ||
|
reg.id == regionQuery.toLocaleLowerCase() ||
|
||||||
reg.value.toLocaleLowerCase() == regionQuery.toLocaleLowerCase()
|
reg.value.toLocaleLowerCase() == regionQuery.toLocaleLowerCase()
|
||||||
)?.id || 'eu';
|
) ?? regionsJSON[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,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 =
|
||||||
@@ -141,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 {
|
||||||
@@ -199,6 +192,8 @@ li.option {
|
|||||||
}
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5em 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@@ -209,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,47 +1,26 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="stock-list">
|
<div class="stock-list">
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="(stockName, i) in trainStockList" :key="i">
|
<li
|
||||||
<p>
|
v-for="({ vehicleName, vehicleCargo, images, imagesFallbacks }, i) in thumbnailNames"
|
||||||
{{ stockName.split(':')[0].split('_').splice(0, 2).join(' ') }}
|
:key="i"
|
||||||
{{ stockName.split(':')[1] }}
|
>
|
||||||
</p>
|
<div class="stock-text">
|
||||||
|
<p>{{ vehicleName.replace(/_/g, ' ') }}</p>
|
||||||
|
<small v-if="vehicleCargo">({{ vehicleCargo }})</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
<img
|
<img
|
||||||
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}${
|
v-for="(thumbnailImage, imageIndex) in images"
|
||||||
/^EN/.test(stockName) ? 'rb' : ''
|
:data-mouseover="vehicleName"
|
||||||
}.png`"
|
data-tooltip-type="VehiclePreviewTooltip"
|
||||||
@error="onImageError($event, stockName)"
|
:data-tooltip-content="vehicleName"
|
||||||
width="400"
|
:src="`https://static.spythere.eu/thumbnails/${thumbnailImage}.png`"
|
||||||
|
@error="onImageError($event, imagesFallbacks[imageIndex])"
|
||||||
|
@click.stop="() => {}"
|
||||||
height="60"
|
height="60"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<img
|
|
||||||
v-if="/^(EN|2EN)/.test(stockName)"
|
|
||||||
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}s.png`"
|
|
||||||
@error="
|
|
||||||
(event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-s.png')
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<img
|
|
||||||
class="train-thumbnail"
|
|
||||||
v-if="/^EN71/.test(stockName)"
|
|
||||||
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}s.png`"
|
|
||||||
@error="
|
|
||||||
(event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-s.png')
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<img
|
|
||||||
class="train-thumbnail"
|
|
||||||
v-if="/^(EN|2EN)/.test(stockName)"
|
|
||||||
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}ra.png`"
|
|
||||||
@error="
|
|
||||||
(event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-ra.png')
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -50,7 +29,6 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { PropType, defineComponent } from 'vue';
|
import { PropType, defineComponent } from 'vue';
|
||||||
import { API } from '../../typings/api';
|
|
||||||
import { useApiStore } from '../../store/apiStore';
|
import { useApiStore } from '../../store/apiStore';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
@@ -58,6 +36,10 @@ export default defineComponent({
|
|||||||
trainStockList: {
|
trainStockList: {
|
||||||
type: Array as PropType<string[]>,
|
type: Array as PropType<string[]>,
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
tractionOnly: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -67,16 +49,119 @@ export default defineComponent({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
computed: {
|
||||||
onImageError(event: Event, stockName: string) {
|
computedStockList() {
|
||||||
const fallbackName =
|
return this.tractionOnly ? this.trainStockList.slice(0, 1) : this.trainStockList;
|
||||||
Object.keys(this.apiStore.rollingStockData!.info).find((type) => {
|
},
|
||||||
return this.apiStore.rollingStockData!.info[type as keyof API.RollingStock.Info].find(
|
|
||||||
(v) => v[0] === stockName.split(':')[0]
|
|
||||||
);
|
|
||||||
}) || 'vehicle-unknown';
|
|
||||||
|
|
||||||
(event.target as HTMLImageElement).src = `/images/icon-${fallbackName}.png`;
|
thumbnailNames() {
|
||||||
|
return (this.tractionOnly ? this.trainStockList.slice(0, 1) : this.trainStockList)
|
||||||
|
.filter((v) => v.length != 0)
|
||||||
|
.map((vehicleString) => {
|
||||||
|
const [vehicleName, vehicleCargo] = vehicleString.split(':');
|
||||||
|
|
||||||
|
const vehicleThumbnailData = {
|
||||||
|
images: [] as string[],
|
||||||
|
imagesFallbacks: [] as string[],
|
||||||
|
vehicleName,
|
||||||
|
vehicleCargo
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generowanie członów EN57
|
||||||
|
if (vehicleName.startsWith('EN57')) {
|
||||||
|
vehicleThumbnailData['images'] = [
|
||||||
|
vehicleName + 'ra',
|
||||||
|
vehicleName + 's',
|
||||||
|
vehicleName + 'rb'
|
||||||
|
];
|
||||||
|
vehicleThumbnailData['imagesFallbacks'] = [
|
||||||
|
'unknown_ezt-ra',
|
||||||
|
'unknown_ezt-s',
|
||||||
|
'unknown_ezt-rb'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
// Generowanie członów EN71
|
||||||
|
else if (vehicleName.startsWith('EN71')) {
|
||||||
|
vehicleThumbnailData['images'] = [
|
||||||
|
vehicleName + 'ra',
|
||||||
|
vehicleName + 'sa',
|
||||||
|
vehicleName + 'sb',
|
||||||
|
vehicleName + 'rb'
|
||||||
|
];
|
||||||
|
vehicleThumbnailData['imagesFallbacks'] = [
|
||||||
|
'unknown_ezt-ra',
|
||||||
|
'unknown_ezt-sa',
|
||||||
|
'unknown_ezt-sb',
|
||||||
|
'unknown_ezt-rb'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
// Generowanie pojazdów i członów 2EN57
|
||||||
|
else if (vehicleString.startsWith('2EN57')) {
|
||||||
|
const [firstVehicleNumber, secondVehicleNumber] = vehicleString
|
||||||
|
.replace('2EN57-', '')
|
||||||
|
.split('+');
|
||||||
|
|
||||||
|
vehicleThumbnailData['images'] = [
|
||||||
|
`EN57-${firstVehicleNumber}ra`,
|
||||||
|
`EN57-${firstVehicleNumber}s`,
|
||||||
|
`EN57-${firstVehicleNumber}rb`,
|
||||||
|
`EN57-${secondVehicleNumber}ra`,
|
||||||
|
`EN57-${secondVehicleNumber}s`,
|
||||||
|
`EN57-${secondVehicleNumber}rb`
|
||||||
|
];
|
||||||
|
|
||||||
|
vehicleThumbnailData['imagesFallbacks'] = [
|
||||||
|
'unknown_ezt-ra',
|
||||||
|
'unknown_ezt-s',
|
||||||
|
'unknown_ezt-rb',
|
||||||
|
'unknown_ezt-ra',
|
||||||
|
'unknown_ezt-s',
|
||||||
|
'unknown_ezt-rb'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
// Generowanie członów Gor77
|
||||||
|
else if (vehicleString.startsWith('Gor77')) {
|
||||||
|
vehicleThumbnailData['images'] = [
|
||||||
|
vehicleName + '-A',
|
||||||
|
vehicleName + '-B',
|
||||||
|
vehicleName + '-C',
|
||||||
|
vehicleName + '-D'
|
||||||
|
];
|
||||||
|
vehicleThumbnailData['imagesFallbacks'] = [
|
||||||
|
'unknown_Gor77-A',
|
||||||
|
'unknown_Gor77-B',
|
||||||
|
'unknown_Gor77-C',
|
||||||
|
'unknown_Gor77-D'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
// Generowanie członów ET41
|
||||||
|
else if (vehicleString.startsWith('ET41')) {
|
||||||
|
vehicleThumbnailData['images'] = [vehicleName + '-A', vehicleName + '-B'];
|
||||||
|
vehicleThumbnailData['imagesFallbacks'] = ['unknown_ET41-A', 'unknown_ET41-B'];
|
||||||
|
}
|
||||||
|
// Generowanie pozostałych pojazdów
|
||||||
|
else {
|
||||||
|
let fallbackVehicleImage = 'unknown_cargo';
|
||||||
|
|
||||||
|
if (/^(EP|EU)/.test(vehicleName)) fallbackVehicleImage = 'unknown_train';
|
||||||
|
else if (/^(SM42)/.test(vehicleName)) fallbackVehicleImage = 'unknown_SM42';
|
||||||
|
else if (/(\d{3}a|(Bau|Gor)\d{2}|304C)_/.test(vehicleName))
|
||||||
|
fallbackVehicleImage = 'unknown_passenger';
|
||||||
|
|
||||||
|
vehicleThumbnailData['images'] = [vehicleName];
|
||||||
|
vehicleThumbnailData['imagesFallbacks'] = [fallbackVehicleImage];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.tractionOnly) vehicleThumbnailData['images'].length = 1;
|
||||||
|
|
||||||
|
return vehicleThumbnailData;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
onImageError(event: Event, fallbackImage: string) {
|
||||||
|
(event.target as HTMLImageElement).src = `/images/${fallbackImage}.png`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -99,6 +184,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 +193,14 @@ img {
|
|||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
img.traction-only {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-text {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 0.25em;
|
||||||
}
|
}
|
||||||
</style>
|
</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 { API } from '../../typings/api';
|
|
||||||
import { useApiStore } from '../../store/apiStore';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
|
|
||||||
onlyFirstSegment: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
apiStore: useApiStore(),
|
|
||||||
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.apiStore.rollingStockData) return 'vehicle-unknown';
|
|
||||||
|
|
||||||
return (
|
|
||||||
Object.keys(this.apiStore.rollingStockData.info).find((type) => {
|
|
||||||
return this.apiStore.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>
|
|
||||||
@@ -172,7 +172,7 @@ import dateMixin from '../../mixins/dateMixin';
|
|||||||
|
|
||||||
import { API } from '../../typings/api';
|
import { API } from '../../typings/api';
|
||||||
import { Status } from '../../typings/common';
|
import { Status } from '../../typings/common';
|
||||||
import http from '../../http';
|
import { useApiStore } from '../../store/apiStore';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'journal-daily-stats',
|
name: 'journal-daily-stats',
|
||||||
@@ -186,7 +186,8 @@ export default defineComponent({
|
|||||||
statsStatus: Status.Data.Loading,
|
statsStatus: Status.Data.Loading,
|
||||||
intervalId: -1,
|
intervalId: -1,
|
||||||
|
|
||||||
stats: {} as API.DailyStats.Response
|
stats: {} as API.DailyStats.Response,
|
||||||
|
apiStore: useApiStore()
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -211,7 +212,9 @@ export default defineComponent({
|
|||||||
methods: {
|
methods: {
|
||||||
async fetchDailyTimetableStats() {
|
async fetchDailyTimetableStats() {
|
||||||
try {
|
try {
|
||||||
const res: API.DailyStats.Response = await (await http.get('api/getDailyStats')).data;
|
const res: API.DailyStats.Response = await (
|
||||||
|
await this.apiStore.client!.get('api/getDailyStats')
|
||||||
|
).data;
|
||||||
|
|
||||||
this.stats = res;
|
this.stats = res;
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
:to="`/journal/dispatchers?search-dispatcher=${historyItem.dispatcherName}`"
|
:to="`/journal/dispatchers?search-dispatcher=${historyItem.dispatcherName}`"
|
||||||
>
|
>
|
||||||
<b
|
<b
|
||||||
v-if="isDonator(historyItem.dispatcherName)"
|
v-if="apiStore.donatorsData.includes(historyItem.dispatcherName)"
|
||||||
class="text--donator"
|
class="text--donator"
|
||||||
:title="$t('donations.dispatcher-message')"
|
:title="$t('donations.dispatcher-message')"
|
||||||
>
|
>
|
||||||
@@ -128,13 +128,13 @@ import { Status } from '../../../typings/common';
|
|||||||
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 dateMixin from '../../../mixins/dateMixin';
|
import dateMixin from '../../../mixins/dateMixin';
|
||||||
import donatorMixin from '../../../mixins/donatorMixin';
|
|
||||||
import styleMixin from '../../../mixins/styleMixin';
|
import styleMixin from '../../../mixins/styleMixin';
|
||||||
|
import { useApiStore } from '../../../store/apiStore';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { Loading, AddDataButton },
|
components: { Loading, AddDataButton },
|
||||||
|
|
||||||
mixins: [dateMixin, styleMixin, donatorMixin],
|
mixins: [dateMixin, styleMixin],
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
dispatcherHistory: {
|
dispatcherHistory: {
|
||||||
@@ -159,6 +159,7 @@ export default defineComponent({
|
|||||||
return {
|
return {
|
||||||
Status,
|
Status,
|
||||||
store: useMainStore(),
|
store: useMainStore(),
|
||||||
|
apiStore: useApiStore(),
|
||||||
regions
|
regions
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ import keyMixin from '../../mixins/keyMixin';
|
|||||||
import { useMainStore } from '../../store/mainStore';
|
import { useMainStore } from '../../store/mainStore';
|
||||||
import { Journal } from './typings';
|
import { Journal } from './typings';
|
||||||
import { Status } from '../../typings/common';
|
import { Status } from '../../typings/common';
|
||||||
import http from '../../http';
|
import { useApiStore } from '../../store/apiStore';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
emits: ['onSearchConfirm', 'onOptionsReset', 'onRefreshData'],
|
emits: ['onSearchConfirm', 'onOptionsReset', 'onRefreshData'],
|
||||||
@@ -158,6 +158,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
searchTimeout: 0,
|
searchTimeout: 0,
|
||||||
store: useMainStore(),
|
store: useMainStore(),
|
||||||
|
apiStore: useApiStore(),
|
||||||
|
|
||||||
JournalFilterSection: Journal.FilterSection
|
JournalFilterSection: Journal.FilterSection
|
||||||
};
|
};
|
||||||
@@ -241,7 +242,7 @@ export default defineComponent({
|
|||||||
this.searchTimeout = window.setTimeout(async () => {
|
this.searchTimeout = window.setTimeout(async () => {
|
||||||
try {
|
try {
|
||||||
const suggestions: string[] = await (
|
const suggestions: string[] = await (
|
||||||
await http.get(`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;
|
||||||
@@ -300,6 +301,6 @@ export default defineComponent({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../styles/dropdown.scss';
|
@import '../../styles/dropdown';
|
||||||
@import '../../styles/dropdown_filters.scss';
|
@import '../../styles/dropdown_filters';
|
||||||
</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 { useMainStore } 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: {
|
||||||
@@ -73,6 +112,15 @@ export default defineComponent({
|
|||||||
Status,
|
Status,
|
||||||
store: useMainStore()
|
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">
|
||||||
@@ -29,7 +24,7 @@
|
|||||||
</strong>
|
</strong>
|
||||||
|
|
||||||
<strong
|
<strong
|
||||||
v-if="isDonator(timetable.driverName)"
|
v-if="apiStore.donatorsData.includes(timetable.driverName)"
|
||||||
class="text--donator"
|
class="text--donator"
|
||||||
:title="$t('donations.driver-message')"
|
:title="$t('donations.driver-message')"
|
||||||
>
|
>
|
||||||
@@ -66,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>
|
||||||
@@ -77,10 +81,16 @@ 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 donatorMixin from '../../../mixins/donatorMixin';
|
import { useApiStore } from '../../../store/apiStore';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
mixins: [dateMixin, modalTrainMixin, styleMixin, donatorMixin],
|
mixins: [dateMixin, modalTrainMixin, styleMixin],
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
apiStore: useApiStore()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
timetable: {
|
timetable: {
|
||||||
@@ -93,15 +103,15 @@ export default defineComponent({
|
|||||||
showTimetable(timetable: API.TimetableHistory.Data, target: EventTarget | null) {
|
showTimetable(timetable: API.TimetableHistory.Data, target: EventTarget | null) {
|
||||||
if (timetable?.terminated) return;
|
if (timetable?.terminated) return;
|
||||||
|
|
||||||
this.selectModalTrain(timetable.driverName + timetable.trainNo.toString(), target);
|
this.selectModalTrainById(`${timetable.driverName}${timetable.trainNo}`, target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</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;
|
||||||
@@ -113,8 +123,22 @@ export default defineComponent({
|
|||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.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 {
|
.badges {
|
||||||
@@ -139,13 +163,13 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.general-train {
|
.btn-timetable {
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
padding: 0.2em 0.5em;
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
img {
|
||||||
gap: 0.25em;
|
height: 1.25em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@include smallScreen {
|
@include smallScreen {
|
||||||
|
|||||||
@@ -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`"
|
||||||
@@ -66,9 +66,9 @@ export default defineComponent({
|
|||||||
</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;
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ export namespace Journal {
|
|||||||
| 'search-train'
|
| 'search-train'
|
||||||
| 'search-date'
|
| 'search-date'
|
||||||
| 'search-dispatcher'
|
| 'search-dispatcher'
|
||||||
| 'search-issuedFrom';
|
| 'search-issuedFrom'
|
||||||
|
| 'search-terminatingAt'
|
||||||
|
| 'search-via';
|
||||||
|
|
||||||
export type TimetableSearchType = {
|
export type TimetableSearchType = {
|
||||||
[key in TimetableSearchKey]: string;
|
[key in TimetableSearchKey]: string;
|
||||||
|
|||||||
@@ -1,31 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="scenery-table-section">
|
<div class="scenery-dispatchers-history">
|
||||||
|
<div class="history-wrapper">
|
||||||
<Loading v-if="dataStatus != DataStatus.Loaded && historyList.length == 0" />
|
<Loading v-if="dataStatus != DataStatus.Loaded && historyList.length == 0" />
|
||||||
|
|
||||||
<div class="no-history" v-else-if="historyList.length == 0">
|
<div v-else-if="historyList.length == 0" class="no-history">
|
||||||
{{ $t('scenery.history-list-empty') }}
|
{{ $t('scenery.history-list-empty') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table class="scenery-history-table" v-else>
|
<div v-else class="history-list">
|
||||||
<thead>
|
<div v-for="historyItem in historyList" :key="historyItem.id">
|
||||||
<th>{{ $t('scenery.dispatchers-history-hash') }}</th>
|
<span>
|
||||||
<th>{{ $t('scenery.dispatchers-history-dispatcher') }}</th>
|
<span class="text--grayed" style="margin-right: 10px">
|
||||||
<th>{{ $t('scenery.dispatchers-history-level') }}</th>
|
#{{ historyItem.stationHash }}
|
||||||
<th>{{ $t('scenery.dispatchers-history-rate') }}</th>
|
</span>
|
||||||
<th>{{ $t('scenery.dispatchers-history-date') }}</th>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
<tr v-for="historyItem in historyList" :key="historyItem.id">
|
|
||||||
<td>#{{ historyItem.stationHash }}</td>
|
|
||||||
<td>
|
|
||||||
<router-link
|
|
||||||
:to="`/journal/dispatchers?search-dispatcher=${historyItem.dispatcherName}`"
|
|
||||||
>
|
|
||||||
<b>{{ historyItem.dispatcherName }}</b>
|
|
||||||
</router-link>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<b
|
<b
|
||||||
v-if="historyItem.dispatcherLevel !== null"
|
v-if="historyItem.dispatcherLevel !== null"
|
||||||
class="level-badge dispatcher"
|
class="level-badge dispatcher"
|
||||||
@@ -35,62 +22,74 @@
|
|||||||
>
|
>
|
||||||
{{ historyItem.dispatcherLevel >= 2 ? historyItem.dispatcherLevel : 'L' }}
|
{{ historyItem.dispatcherLevel >= 2 ? historyItem.dispatcherLevel : 'L' }}
|
||||||
</b>
|
</b>
|
||||||
|
<b style="margin-left: 5px">
|
||||||
|
<router-link
|
||||||
|
:to="`/journal/dispatchers?search-dispatcher=${historyItem.dispatcherName}`"
|
||||||
|
>
|
||||||
|
{{ historyItem.dispatcherName }}
|
||||||
|
</router-link>
|
||||||
|
</b>
|
||||||
|
|
||||||
<b v-else>?</b>
|
<div>
|
||||||
</td>
|
<span>
|
||||||
<td class="text--primary">
|
{{ $t('scenery.dispatcher-rate') }}
|
||||||
<b>{{ historyItem.dispatcherRate }}</b>
|
<b class="text--primary"> {{ historyItem.dispatcherRate }}</b>
|
||||||
</td>
|
</span>
|
||||||
<td style="min-width: 300px">
|
|
|
||||||
<div v-if="historyItem.timestampTo">
|
<span>
|
||||||
|
{{ $t('scenery.dispatcher-status-changes') }}
|
||||||
|
<b class="text--primary">{{ historyItem.statusHistory.length }}</b>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
<span v-if="historyItem.timestampTo">
|
||||||
<b>{{ $d(historyItem.timestampFrom) }}</b>
|
<b>{{ $d(historyItem.timestampFrom) }}</b>
|
||||||
|
|
||||||
{{ timestampToString(historyItem.timestampFrom) }}
|
{{ timestampToString(historyItem.timestampFrom) }}
|
||||||
- {{ timestampToString(historyItem.timestampTo) }} ({{
|
- {{ timestampToString(historyItem.timestampTo) }} ({{
|
||||||
calculateDuration(historyItem.currentDuration)
|
calculateDuration(historyItem.currentDuration)
|
||||||
}})
|
}})
|
||||||
</div>
|
</span>
|
||||||
|
|
||||||
<div class="dispatcher-online" v-else>
|
<span class="dispatcher-online" v-else>
|
||||||
{{ $t('journal.online-since') }}
|
{{ $t('journal.online-since') }}
|
||||||
<b>{{ timestampToString(historyItem.timestampFrom) }}</b>
|
<b>{{ timestampToString(historyItem.timestampFrom) }}</b>
|
||||||
({{ calculateDuration(historyItem.currentDuration) }})
|
({{ calculateDuration(historyItem.currentDuration) }})
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<div class="bottom-info">
|
<div class="bottom-info">
|
||||||
<button class="btn btn--option" v-if="historyList.length > 0" @click="navigateToHistory">
|
<button class="btn btn--option" v-if="historyList.length > 0" @click="navigateToHistory">
|
||||||
{{ $t('scenery.bottom-info') }}
|
{{ $t('scenery.bottom-info') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
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 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 http from '../../http';
|
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>
|
||||||
},
|
},
|
||||||
onlineScenery: {
|
onlineScenery: {
|
||||||
type: Object as PropType<OnlineScenery>
|
type: Object as PropType<ActiveScenery>
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -98,7 +97,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()
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -127,7 +127,7 @@ export default defineComponent({
|
|||||||
}&countFrom=${countFrom}&countLimit=${countLimit}`;
|
}&countFrom=${countFrom}&countLimit=${countLimit}`;
|
||||||
|
|
||||||
const historyAPIData: API.DispatcherHistory.Response = await (
|
const historyAPIData: API.DispatcherHistory.Response = await (
|
||||||
await http.get(requestString)
|
await this.apiStore.client!.get(requestString)
|
||||||
).data;
|
).data;
|
||||||
|
|
||||||
this.dataStatus = Status.Data.Loaded;
|
this.dataStatus = Status.Data.Loaded;
|
||||||
@@ -151,8 +151,43 @@ export default defineComponent({
|
|||||||
@import '../../styles/responsive.scss';
|
@import '../../styles/responsive.scss';
|
||||||
@import '../../styles/sceneryViewTables.scss';
|
@import '../../styles/sceneryViewTables.scss';
|
||||||
|
|
||||||
|
.scenery-dispatchers-history {
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
gap: 0.5em;
|
||||||
|
grid-template-rows: auto 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-wrapper {
|
||||||
|
position: relative;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5em;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-list > div {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
gap: 0.5em;
|
||||||
|
padding: 0.5em;
|
||||||
|
background-color: #2b2b2b;
|
||||||
|
line-height: 1.75em;
|
||||||
|
}
|
||||||
|
|
||||||
.level-badge {
|
.level-badge {
|
||||||
margin: 0 auto;
|
text-align: center;
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 1.6em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dispatcher-online {
|
.dispatcher-online {
|
||||||
@@ -160,13 +195,10 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
@include smallScreen {
|
@include smallScreen {
|
||||||
.history-list {
|
.history-list > div {
|
||||||
font-size: 1.1em;
|
|
||||||
}
|
|
||||||
.list-item {
|
|
||||||
align-items: center;
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
../../store/storeTypes
|
|
||||||
|
|||||||
@@ -14,8 +14,7 @@
|
|||||||
|
|
||||||
<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: {
|
||||||
@@ -29,7 +28,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
onlineScenery: {
|
onlineScenery: {
|
||||||
type: Object as PropType<OnlineScenery>
|
type: Object as PropType<ActiveScenery>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -72,7 +72,7 @@
|
|||||||
|
|
||||||
<div class="info-lists">
|
<div class="info-lists">
|
||||||
<!-- user list -->
|
<!-- user list -->
|
||||||
<SceneryInfoUserList :onlineScenery="onlineScenery" />
|
<SceneryInfoUserList :onlineScenery="onlineScenery" :station="station" />
|
||||||
|
|
||||||
<!-- spawn list -->
|
<!-- spawn list -->
|
||||||
<SceneryInfoSpawnList :onlineScenery="onlineScenery" />
|
<SceneryInfoSpawnList :onlineScenery="onlineScenery" />
|
||||||
@@ -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: {
|
||||||
@@ -106,7 +105,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
onlineScenery: {
|
onlineScenery: {
|
||||||
type: Object as PropType<OnlineScenery>
|
type: Object as PropType<ActiveScenery>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -125,11 +124,6 @@ h3.section-header {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
|
|
||||||
img {
|
|
||||||
width: 1.1em;
|
|
||||||
margin-left: 0.5em;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-lists {
|
.info-lists {
|
||||||
|
|||||||
@@ -1,38 +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?search-dispatcher=${onlineScenery.dispatcherName}`"
|
:to="`/journal/dispatchers?search-dispatcher=${onlineScenery.dispatcherName}`"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="text--donator"
|
class="text--donator"
|
||||||
v-if="isDonator(onlineScenery.dispatcherName)"
|
v-if="apiStore.donatorsData.includes(onlineScenery.dispatcherName)"
|
||||||
:title="$t('donations.dispatcher-message')"
|
:title="$t('donations.dispatcher-message')"
|
||||||
>
|
>
|
||||||
{{ onlineScenery.dispatcherName }}
|
{{ onlineScenery.dispatcherName }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else>{{ 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>
|
||||||
|
|
||||||
@@ -42,14 +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 donatorMixin from '../../../mixins/donatorMixin';
|
import { useApiStore } from '../../../store/apiStore';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
mixins: [styleMixin, dateMixin, routerMixin, donatorMixin],
|
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
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -59,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;
|
||||||
@@ -80,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>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
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>
|
||||||
|
|
||||||
@@ -21,25 +21,19 @@
|
|||||||
v-if="station?.generalInfo"
|
v-if="station?.generalInfo"
|
||||||
class="scenery-icon icon-info"
|
class="scenery-icon icon-info"
|
||||||
:class="station?.generalInfo.controlType.replace('+', '-')"
|
:class="station?.generalInfo.controlType.replace('+', '-')"
|
||||||
:title="$t('desc.control-type') + $t(`controls.${station?.generalInfo.controlType}`)"
|
:title="
|
||||||
v-html="getControlTypeAbbrev(station?.generalInfo.controlType)"
|
$t('sceneries.info.control-type') + $t(`controls.${station?.generalInfo.controlType}`)
|
||||||
|
"
|
||||||
>
|
>
|
||||||
|
{{ $t(`controls.abbrevs.${station.generalInfo.controlType}`) }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<img
|
|
||||||
v-if="station?.generalInfo?.SUP"
|
|
||||||
class="icon-info"
|
|
||||||
src="/images/icon-SUP.svg"
|
|
||||||
alt="SUP (RASP-UZK)"
|
|
||||||
:title="$t('desc.SUP')"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<img
|
<img
|
||||||
v-if="station?.generalInfo?.signalType"
|
v-if="station?.generalInfo?.signalType"
|
||||||
class="icon-info"
|
class="icon-info"
|
||||||
: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}`)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<img
|
<img
|
||||||
@@ -47,7 +41,7 @@
|
|||||||
class="icon-info"
|
class="icon-info"
|
||||||
src="/images/icon-lock.svg"
|
src="/images/icon-lock.svg"
|
||||||
alt="Non-public scenery"
|
alt="Non-public scenery"
|
||||||
:title="$t('desc.non-public')"
|
:title="$t('sceneries.info.non-public')"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<img
|
<img
|
||||||
@@ -55,7 +49,7 @@
|
|||||||
class="icon-info"
|
class="icon-info"
|
||||||
src="/images/icon-unavailable.svg"
|
src="/images/icon-unavailable.svg"
|
||||||
alt="Unavailable scenery"
|
alt="Unavailable scenery"
|
||||||
:title="$t('desc.unavailable')"
|
:title="$t('sceneries.info.unavailable')"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<img
|
<img
|
||||||
@@ -63,7 +57,23 @@
|
|||||||
class="icon-info"
|
class="icon-info"
|
||||||
src="/images/icon-abandoned.svg"
|
src="/images/icon-abandoned.svg"
|
||||||
alt="Abandoned scenery"
|
alt="Abandoned scenery"
|
||||||
:title="$t('desc.abandoned')"
|
: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
|
<img
|
||||||
@@ -71,19 +81,18 @@
|
|||||||
class="icon-info"
|
class="icon-info"
|
||||||
src="/images/icon-real.svg"
|
src="/images/icon-real.svg"
|
||||||
alt="real scenery"
|
alt="real scenery"
|
||||||
:title="`${$t('desc.real')} ${station.generalInfo.lines}`"
|
: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>
|
||||||
@@ -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,11 +1,11 @@
|
|||||||
<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="filteredOneWayRoutes.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 filteredOneWayRoutes"
|
v-for="route in oneWayRoutes"
|
||||||
:key="route.routeName"
|
:key="route.routeName"
|
||||||
@click="setActiveShowLength(route.routeName)"
|
@click="setActiveShowLength(route.routeName)"
|
||||||
>
|
>
|
||||||
@@ -24,12 +24,12 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="routes two-way" v-if="filteredTwoWayRoutes.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 filteredTwoWayRoutes"
|
v-for="route in twoWayRoutes"
|
||||||
:key="route.routeName"
|
:key="route.routeName"
|
||||||
@click="setActiveShowLength(route.routeName)"
|
@click="setActiveShowLength(route.routeName)"
|
||||||
>
|
>
|
||||||
@@ -52,10 +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';
|
||||||
import { StationRoutesInfo } from '../../../store/typings';
|
|
||||||
|
|
||||||
const routeFilter = (route: StationRoutesInfo) => !route.hidden;
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
@@ -80,12 +77,12 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
filteredOneWayRoutes() {
|
oneWayRoutes() {
|
||||||
return this.station.generalInfo?.routes.oneWay.filter(routeFilter) || [];
|
return this.station.generalInfo?.routes.single ?? [];
|
||||||
},
|
},
|
||||||
|
|
||||||
filteredTwoWayRoutes() {
|
twoWayRoutes() {
|
||||||
return this.station.generalInfo?.routes.twoWay.filter(routeFilter) || [];
|
return this.station.generalInfo?.routes.double ?? [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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>
|
|
||||||
@@ -13,13 +13,13 @@
|
|||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li
|
<li
|
||||||
v-for="train in onlineScenery?.stationTrains"
|
v-for="{ train, status } in stationTrains"
|
||||||
class="badge user"
|
class="badge user"
|
||||||
:class="train.stopStatus"
|
|
||||||
:key="train.trainId"
|
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@click.prevent="selectModalTrain(train.trainId, $event.currentTarget)"
|
:key="train.id"
|
||||||
@keydown.enter="selectModalTrain(train.trainId, $event.currentTarget)"
|
:data-status="status"
|
||||||
|
@click.prevent="selectModalTrain(train, $event.currentTarget)"
|
||||||
|
@keydown.enter="selectModalTrain(train, $event.currentTarget)"
|
||||||
>
|
>
|
||||||
<span class="user_train">{{ train.trainNo }}</span>
|
<span class="user_train">{{ train.trainNo }}</span>
|
||||||
<span class="user_name">{{ train.driverName }}</span>
|
<span class="user_name">{{ train.driverName }}</span>
|
||||||
@@ -32,15 +32,51 @@
|
|||||||
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, Station, StopStatus } from '../../../typings/common';
|
||||||
|
import { getTrainStopStatus } from '../utils';
|
||||||
|
import { useMainStore } from '../../../store/mainStore';
|
||||||
|
|
||||||
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
|
||||||
|
},
|
||||||
|
station: {
|
||||||
|
type: Object as PropType<Station>
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
mainStore: useMainStore()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
stationTrains() {
|
||||||
|
if (!this.onlineScenery) return;
|
||||||
|
|
||||||
|
const name = this.station?.generalInfo?.checkpoints[0] ?? this.onlineScenery.name;
|
||||||
|
|
||||||
|
return this.onlineScenery.stationTrains.map((train) => {
|
||||||
|
const stop = train.timetableData?.followingStops.find(
|
||||||
|
(stop) =>
|
||||||
|
stop.stopNameRAW.toLowerCase() == name.toLowerCase() ||
|
||||||
|
this.station?.generalInfo?.checkpoints.includes(stop.stopNameRAW)
|
||||||
|
);
|
||||||
|
|
||||||
|
const status = stop
|
||||||
|
? getTrainStopStatus(stop, train.currentStationName, this.onlineScenery!.name)
|
||||||
|
: 'no-timetable';
|
||||||
|
|
||||||
|
return {
|
||||||
|
train,
|
||||||
|
status
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -74,31 +110,31 @@ ul {
|
|||||||
-webkit-transition: background-color 200ms;
|
-webkit-transition: background-color 200ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.no-timetable .user_train {
|
&[data-status='no-timetable'] .user_train {
|
||||||
background-color: $no-timetable;
|
background-color: $no-timetable;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.departed > &_train {
|
&[data-status='departed'] > &_train {
|
||||||
background-color: $departed;
|
background-color: $departed;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.stopped > &_train {
|
&[data-status='stopped'] > &_train {
|
||||||
background-color: $stopped;
|
background-color: $stopped;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.online > &_train {
|
&[data-status='online'] > &_train {
|
||||||
background-color: $online;
|
background-color: $online;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.terminated > &_train {
|
&[data-status='terminated'] > &_train {
|
||||||
background-color: $terminated;
|
background-color: $terminated;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.disconnected > &_train {
|
&[data-status='disconnected'] > &_train {
|
||||||
background-color: $disconnected;
|
background-color: $disconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.offline {
|
&[data-status='offline'] {
|
||||||
background: firebrick;
|
background: firebrick;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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" v-if="station">
|
<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>
|
||||||
@@ -47,8 +39,8 @@
|
|||||||
<div class="timetable-list">
|
<div class="timetable-list">
|
||||||
<transition-group name="list-anim">
|
<transition-group name="list-anim">
|
||||||
<div
|
<div
|
||||||
|
v-if="apiStore.dataStatuses.connection == 0 && sceneryTimetables.length == 0"
|
||||||
style="padding-bottom: 5em"
|
style="padding-bottom: 5em"
|
||||||
v-if="apiStore.dataStatuses.connection == 0 && computedScheduledTrains.length == 0"
|
|
||||||
key="list-loading"
|
key="list-loading"
|
||||||
>
|
>
|
||||||
<Loading />
|
<Loading />
|
||||||
@@ -56,7 +48,7 @@
|
|||||||
|
|
||||||
<span
|
<span
|
||||||
class="timetable-item empty"
|
class="timetable-item empty"
|
||||||
v-else-if="computedScheduledTrains.length == 0 && !onlineScenery"
|
v-else-if="sceneryTimetables.length == 0 && !onlineScenery"
|
||||||
key="list-offline"
|
key="list-offline"
|
||||||
>
|
>
|
||||||
{{ $t('scenery.offline') }}
|
{{ $t('scenery.offline') }}
|
||||||
@@ -64,7 +56,7 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
class="timetable-item empty"
|
class="timetable-item empty"
|
||||||
v-else-if="computedScheduledTrains.length == 0"
|
v-else-if="sceneryTimetables.length == 0"
|
||||||
key="list-no-timetables"
|
key="list-no-timetables"
|
||||||
>
|
>
|
||||||
{{ $t('scenery.no-timetables') }}
|
{{ $t('scenery.no-timetables') }}
|
||||||
@@ -73,59 +65,56 @@
|
|||||||
<div
|
<div
|
||||||
class="timetable-item"
|
class="timetable-item"
|
||||||
v-else
|
v-else
|
||||||
v-for="scheduledTrain in computedScheduledTrains"
|
v-for="(row, i) in sceneryTimetables"
|
||||||
:key="scheduledTrain.trainId"
|
:key="row.train.id + i"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@click.prevent.stop="selectModalTrain(scheduledTrain.trainId, $event.currentTarget)"
|
@click.prevent.stop="selectModalTrain(row.train, $event.currentTarget)"
|
||||||
@keydown.enter.prevent="selectModalTrain(scheduledTrain.trainId, $event.currentTarget)"
|
@keydown.enter.prevent="selectModalTrain(row.train, $event.currentTarget)"
|
||||||
>
|
>
|
||||||
<span class="timetable-general">
|
<span class="timetable-general">
|
||||||
<span class="general-info">
|
<span class="general-info">
|
||||||
<span class="info-number">
|
<span class="info-number">
|
||||||
<strong>{{ scheduledTrain.category }}</strong>
|
<strong>{{ row.train.timetableData!.category }}</strong>
|
||||||
{{ scheduledTrain.trainNo }}
|
{{ row.train.trainNo }}
|
||||||
|
|
||||||
<span
|
<span v-if="row.checkpointStop.comments" :title="row.checkpointStop.comments">
|
||||||
v-if="scheduledTrain.stopInfo.comments"
|
|
||||||
:title="scheduledTrain.stopInfo.comments"
|
|
||||||
>
|
|
||||||
<img src="/images/icon-warning.svg" />
|
<img src="/images/icon-warning.svg" />
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
|
||||||
<span>
|
<span>
|
||||||
{{ scheduledTrain.driverName }}
|
{{ row.train.driverName }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div class="info-route">
|
<div class="info-route">
|
||||||
<strong>{{ scheduledTrain.beginsAt }} - {{ scheduledTrain.terminatesAt }}</strong>
|
<strong>{{ row.train.timetableData!.route.replace('|', ' - ') }}</strong>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ScheduledTrainStatus :scheduledTrain="scheduledTrain" />
|
<ScheduledTrainStatus :sceneryTimetableRow="row" />
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="timetable-schedule">
|
<span class="timetable-schedule">
|
||||||
<span class="schedule-arrival">
|
<span class="schedule-arrival">
|
||||||
<span class="arrival-time begins" v-if="scheduledTrain.stopInfo.beginsHere">
|
<span class="arrival-time begins" v-if="row.checkpointStop.beginsHere">
|
||||||
{{ $t('timetables.begins') }}
|
{{ $t('timetables.begins') }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="arrival-time" v-else>
|
<span class="arrival-time" v-else>
|
||||||
<div v-if="scheduledTrain.stopInfo.arrivalDelay == 0">
|
<div v-if="row.checkpointStop.arrivalDelay == 0">
|
||||||
<span>{{ timestampToString(scheduledTrain.stopInfo.arrivalTimestamp) }}</span>
|
<span>{{ timestampToString(row.checkpointStop.arrivalTimestamp) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<div>
|
<div>
|
||||||
<s style="margin-right: 0.2em" class="text--grayed">{{
|
<s style="margin-right: 0.2em" class="text--grayed">{{
|
||||||
timestampToString(scheduledTrain.stopInfo.arrivalTimestamp)
|
timestampToString(row.checkpointStop.arrivalTimestamp)
|
||||||
}}</s>
|
}}</s>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
{{ timestampToString(scheduledTrain.stopInfo.arrivalRealTimestamp) }}
|
{{ timestampToString(row.checkpointStop.arrivalRealTimestamp) }}
|
||||||
({{ scheduledTrain.stopInfo.arrivalDelay > 0 ? '+' : ''
|
({{ row.checkpointStop.arrivalDelay > 0 ? '+' : ''
|
||||||
}}{{ scheduledTrain.stopInfo.arrivalDelay }})
|
}}{{ row.checkpointStop.arrivalDelay }})
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
@@ -133,41 +122,39 @@
|
|||||||
|
|
||||||
<span class="schedule-stop">
|
<span class="schedule-stop">
|
||||||
<span class="stop-connection">
|
<span class="stop-connection">
|
||||||
{{ scheduledTrain.arrivingLine }}
|
{{ row.arrivingLine }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="stop-time">
|
<span class="stop-time">
|
||||||
{{ scheduledTrain.stopInfo.stopTime || '' }}
|
{{ row.checkpointStop.stopTime || '' }}
|
||||||
{{
|
{{ row.checkpointStop.stopTime ? row.checkpointStop.stopType || 'pt' : '' }}
|
||||||
scheduledTrain.stopInfo.stopTime ? scheduledTrain.stopInfo.stopType || 'pt' : ''
|
|
||||||
}}
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="stop-connection">
|
<span class="stop-connection">
|
||||||
{{ scheduledTrain.departureLine }}
|
{{ row.departureLine }}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="schedule-departure">
|
<span class="schedule-departure">
|
||||||
<span class="departure-time terminates" v-if="scheduledTrain.stopInfo.terminatesHere">
|
<span class="departure-time terminates" v-if="row.checkpointStop.terminatesHere">
|
||||||
{{ $t('timetables.terminates') }}
|
{{ $t('timetables.terminates') }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="departure-time" v-else>
|
<span class="departure-time" v-else>
|
||||||
<div v-if="scheduledTrain.stopInfo.departureDelay == 0">
|
<div v-if="row.checkpointStop.departureDelay == 0">
|
||||||
<span>{{ timestampToString(scheduledTrain.stopInfo.departureTimestamp) }}</span>
|
<span>{{ timestampToString(row.checkpointStop.departureTimestamp) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<div>
|
<div>
|
||||||
<s style="margin-right: 0.2em" class="text--grayed">{{
|
<s style="margin-right: 0.2em" class="text--grayed">{{
|
||||||
timestampToString(scheduledTrain.stopInfo.departureTimestamp)
|
timestampToString(row.checkpointStop.departureTimestamp)
|
||||||
}}</s>
|
}}</s>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
{{ timestampToString(scheduledTrain.stopInfo.departureRealTimestamp) }}
|
{{ timestampToString(row.checkpointStop.departureRealTimestamp) }}
|
||||||
({{ scheduledTrain.stopInfo.departureDelay > 0 ? '+' : ''
|
({{ row.checkpointStop.departureDelay > 0 ? '+' : ''
|
||||||
}}{{ scheduledTrain.stopInfo.departureDelay }})
|
}}{{ row.checkpointStop.departureDelay }})
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
@@ -186,12 +173,13 @@ 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 { useMainStore } 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 { useApiStore } from '../../store/apiStore';
|
||||||
|
import { ActiveScenery, Station } from '../../typings/common';
|
||||||
|
import { SceneryTimetableRow } from './typings';
|
||||||
|
import { getTrainStopStatus, stopStatusPriority } from './utils';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'SceneryTimetable',
|
name: 'SceneryTimetable',
|
||||||
@@ -205,7 +193,7 @@ export default defineComponent({
|
|||||||
type: Object as PropType<Station>
|
type: Object as PropType<Station>
|
||||||
},
|
},
|
||||||
onlineScenery: {
|
onlineScenery: {
|
||||||
type: Object as PropType<OnlineScenery>
|
type: Object as PropType<ActiveScenery>
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -213,10 +201,6 @@ export default defineComponent({
|
|||||||
listOpen: false
|
listOpen: false
|
||||||
}),
|
}),
|
||||||
|
|
||||||
mounted() {
|
|
||||||
this.loadSelectedOption();
|
|
||||||
},
|
|
||||||
|
|
||||||
activated() {
|
activated() {
|
||||||
this.loadSelectedOption();
|
this.loadSelectedOption();
|
||||||
},
|
},
|
||||||
@@ -229,9 +213,10 @@ export default defineComponent({
|
|||||||
const mainStore = useMainStore();
|
const mainStore = useMainStore();
|
||||||
|
|
||||||
const chosenCheckpoint = ref(
|
const chosenCheckpoint = ref(
|
||||||
props.station?.generalInfo?.checkpoints?.length == 0
|
props.station?.generalInfo?.checkpoints[0] ??
|
||||||
? ''
|
props.station?.name ??
|
||||||
: props.station?.generalInfo?.checkpoints[0].checkpointName || null
|
route.query['station']?.toString() ??
|
||||||
|
''
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -250,27 +235,106 @@ export default defineComponent({
|
|||||||
return url;
|
return url;
|
||||||
},
|
},
|
||||||
|
|
||||||
computedScheduledTrains() {
|
sceneryTimetables(): SceneryTimetableRow[] {
|
||||||
if (!this.station) return [];
|
if (!this.onlineScenery) return [];
|
||||||
|
|
||||||
return (
|
const sceneryName = this.$route.query['station']?.toString() ?? '';
|
||||||
this.onlineScenery?.scheduledTrains
|
|
||||||
?.filter(
|
return this.onlineScenery.scheduledTrains
|
||||||
(train) =>
|
.filter(
|
||||||
train.checkpointName.toLocaleLowerCase() ==
|
(ct) =>
|
||||||
(this.chosenCheckpoint || this.station!.name).toLocaleLowerCase() &&
|
ct.train.region == this.mainStore.region.id &&
|
||||||
train.region == this.mainStore.region.id
|
this.chosenCheckpoint &&
|
||||||
|
ct.checkpointStop.stopNameRAW.toLowerCase() == this.chosenCheckpoint.toLowerCase()
|
||||||
)
|
)
|
||||||
.sort((a, b) => {
|
.map((ct) => {
|
||||||
if (a.stopStatusID > b.stopStatusID) return 1;
|
const trainStopStatus = getTrainStopStatus(
|
||||||
if (a.stopStatusID < b.stopStatusID) return -1;
|
ct.checkpointStop,
|
||||||
|
ct.train.currentStationName,
|
||||||
if (a.stopInfo.arrivalTimestamp > b.stopInfo.arrivalTimestamp) return 1;
|
sceneryName.replace(/_/g, ' ')
|
||||||
if (a.stopInfo.arrivalTimestamp < b.stopInfo.arrivalTimestamp) return -1;
|
|
||||||
|
|
||||||
return a.stopInfo.departureTimestamp > b.stopInfo.departureTimestamp ? 1 : -1;
|
|
||||||
}) || []
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const trainStopIndex =
|
||||||
|
ct.train.timetableData?.followingStops.findIndex(
|
||||||
|
(stop) => stop.stopName == ct.checkpointStop.stopName
|
||||||
|
) ?? -1;
|
||||||
|
|
||||||
|
let prevStationName = '',
|
||||||
|
nextStationName = '';
|
||||||
|
|
||||||
|
let departureLine: string | null = null;
|
||||||
|
let arrivingLine: string | null = null;
|
||||||
|
|
||||||
|
let prevDepartureLine: string | null = null,
|
||||||
|
nextArrivalLine: string | null = null;
|
||||||
|
|
||||||
|
if (trainStopIndex > -1 && ct.train.timetableData?.followingStops !== undefined) {
|
||||||
|
for (let i = trainStopIndex; i >= 0; i--) {
|
||||||
|
const stop = ct.train.timetableData.followingStops[i];
|
||||||
|
|
||||||
|
if (
|
||||||
|
/strong|podg\.|pe\./g.test(stop.stopName) &&
|
||||||
|
!prevStationName &&
|
||||||
|
i <= trainStopIndex - 1
|
||||||
|
)
|
||||||
|
prevStationName = stop.stopNameRAW.replace(/,.*/g, '');
|
||||||
|
|
||||||
|
if (
|
||||||
|
stop.arrivalLine != null &&
|
||||||
|
!arrivingLine &&
|
||||||
|
!/-|_|it|sbl/gi.test(stop.arrivalLine)
|
||||||
|
) {
|
||||||
|
arrivingLine = stop.arrivalLine;
|
||||||
|
prevDepartureLine =
|
||||||
|
ct.train.timetableData.followingStops[i - 1]?.departureLine || null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = trainStopIndex; i < ct.train.timetableData.followingStops.length; i++) {
|
||||||
|
const stop = ct.train.timetableData.followingStops[i];
|
||||||
|
|
||||||
|
if (
|
||||||
|
/strong|podg\.|pe\./g.test(stop.stopName) &&
|
||||||
|
!nextStationName &&
|
||||||
|
i > trainStopIndex
|
||||||
|
)
|
||||||
|
nextStationName = stop.stopNameRAW.replace(/,.*/g, '');
|
||||||
|
|
||||||
|
if (
|
||||||
|
stop.departureLine &&
|
||||||
|
!departureLine &&
|
||||||
|
!/-|_|it|sbl/gi.test(stop.departureLine)
|
||||||
|
) {
|
||||||
|
departureLine = stop.departureLine;
|
||||||
|
nextArrivalLine = ct.train.timetableData.followingStops[i + 1]?.arrivalLine || null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
checkpointStop: ct.checkpointStop,
|
||||||
|
train: ct.train,
|
||||||
|
prevDepartureLine,
|
||||||
|
nextArrivalLine,
|
||||||
|
departureLine,
|
||||||
|
arrivingLine,
|
||||||
|
prevStationName,
|
||||||
|
nextStationName,
|
||||||
|
status: trainStopStatus
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.sort((a, b) => {
|
||||||
|
if (stopStatusPriority.indexOf(a.status) - stopStatusPriority.indexOf(b.status) < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (stopStatusPriority.indexOf(a.status) - stopStatusPriority.indexOf(b.status) > 0)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
if (a.checkpointStop.arrivalTimestamp > b.checkpointStop.arrivalTimestamp) return 1;
|
||||||
|
if (a.checkpointStop.arrivalTimestamp < b.checkpointStop.arrivalTimestamp) return -1;
|
||||||
|
|
||||||
|
return a.checkpointStop.departureTimestamp > b.checkpointStop.departureTimestamp ? 1 : -1;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -278,12 +342,11 @@ export default defineComponent({
|
|||||||
loadSelectedOption() {
|
loadSelectedOption() {
|
||||||
if (!this.station) return;
|
if (!this.station) return;
|
||||||
|
|
||||||
this.chosenCheckpoint =
|
this.chosenCheckpoint = this.station.generalInfo?.checkpoints[0] ?? this.station.name;
|
||||||
this.station.generalInfo?.checkpoints[0]?.checkpointName || this.station.name;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setCheckpoint(cp: { checkpointName: string }) {
|
setCheckpoint(cp: string) {
|
||||||
this.chosenCheckpoint = cp.checkpointName;
|
this.chosenCheckpoint = cp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -415,13 +478,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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,101 +1,137 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- WIP -->
|
<div class="scenery-timetables-history">
|
||||||
<!-- <div class="top-filters">
|
<div class="history-modes">
|
||||||
<button class="btn btn--option">ROZPOCZYNA BIEG</button>
|
<button
|
||||||
|
class="btn btn--option"
|
||||||
|
v-for="mode in historyModeList"
|
||||||
|
:key="mode"
|
||||||
|
:class="{ checked: checkedHistoryMode == mode }"
|
||||||
|
@click="checkHistoryMode(mode)"
|
||||||
|
>
|
||||||
|
{{ $t(`scenery.timetable-${mode}`) }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button class="btn btn--option">PRZEZ</button>
|
<div class="history-wrapper">
|
||||||
|
|
||||||
<button class="btn btn--option">KOŃCZY BIEG</button>
|
|
||||||
</div> -->
|
|
||||||
|
|
||||||
<section class="scenery-table-section">
|
|
||||||
<Loading v-if="dataStatus != DataStatus.Loaded" />
|
<Loading v-if="dataStatus != DataStatus.Loaded" />
|
||||||
|
|
||||||
<div class="no-history" v-else-if="historyList.length == 0">
|
<div v-else-if="historyList.length == 0" class="no-history">
|
||||||
{{ $t('scenery.history-list-empty') }}
|
{{ $t('scenery.history-list-empty') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table class="scenery-history-table" v-else>
|
<div v-else class="history-list">
|
||||||
<thead>
|
<div v-for="timetableHistory in historyList" :key="timetableHistory.id">
|
||||||
<th>{{ $t('scenery.timetables-history-id') }}</th>
|
<span>
|
||||||
<th>{{ $t('scenery.timetables-history-number') }}</th>
|
<div>
|
||||||
<th>{{ $t('scenery.timetables-history-route') }}</th>
|
<span
|
||||||
<th>{{ $t('scenery.timetables-history-driver') }}</th>
|
class="timetable-status-indicator"
|
||||||
<th>{{ $t('scenery.timetables-history-author') }}</th>
|
:data-terminated="timetableHistory.terminated"
|
||||||
<th>{{ $t('scenery.timetables-history-date') }}</th>
|
:data-fulfilled="timetableHistory.fulfilled"
|
||||||
</thead>
|
>
|
||||||
|
⦿
|
||||||
|
</span>
|
||||||
|
#{{ timetableHistory.id }} |
|
||||||
|
<b class="text--primary">{{ timetableHistory.trainCategoryCode }}</b>
|
||||||
|
{{ timetableHistory.trainNo }}
|
||||||
|
{{ timetableHistory.route.replace('|', ' ⇒ ') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
<tbody>
|
<div class="text--grayed">
|
||||||
<tr v-for="historyItem in historyList" :key="historyItem.id">
|
<span>
|
||||||
<td>
|
{{ $t('scenery.timetable-issued-date') }}
|
||||||
<router-link :to="`/journal/timetables?search-train=%23${historyItem.id}`">
|
<b>
|
||||||
#{{ historyItem.id }}
|
{{
|
||||||
</router-link>
|
localeDateTime(
|
||||||
</td>
|
timetableHistory.createdAt > timetableHistory.beginDate
|
||||||
<td>
|
? timetableHistory.beginDate
|
||||||
<b class="text--primary">{{ historyItem.trainCategoryCode }}</b> <br />
|
: timetableHistory.createdAt,
|
||||||
{{ historyItem.trainNo }}
|
$i18n.locale
|
||||||
</td>
|
)
|
||||||
<td>{{ historyItem.route.replace('|', ' -> ') }}</td>
|
}}
|
||||||
<td>
|
</b></span
|
||||||
<router-link :to="`/journal/timetables?search-driver=${historyItem.driverName}`">
|
>
|
||||||
{{ historyItem.driverName }}
|
<span v-if="timetableHistory.authorName">
|
||||||
</router-link>
|
{{ $t('scenery.timetable-issued-by') }}
|
||||||
</td>
|
<b>
|
||||||
|
|
||||||
<td>
|
|
||||||
<router-link
|
<router-link
|
||||||
v-if="historyItem.authorName"
|
:to="`/journal/timetables?search-dispatcher=${timetableHistory.authorName}`"
|
||||||
:to="`/journal/timetables?search-dispatcher=${historyItem.authorName}`"
|
>
|
||||||
>{{ historyItem.authorName }}
|
{{ timetableHistory.authorName }}
|
||||||
</router-link>
|
</router-link>
|
||||||
<i v-else>{{ $t('scenery.timetable-author-unknown') }}</i>
|
</b>
|
||||||
</td>
|
</span>
|
||||||
<td>
|
|
||||||
<b>{{ localeDay(historyItem.beginDate, $i18n.locale) }}</b>
|
<span>
|
||||||
{{ localeTime(historyItem.beginDate, $i18n.locale) }}
|
{{ $t('scenery.timetable-issued-for') }}
|
||||||
</td>
|
<b>
|
||||||
</tr>
|
<router-link
|
||||||
</tbody>
|
:to="`/journal/timetables?search-driver=${timetableHistory.driverName}`"
|
||||||
</table>
|
>
|
||||||
</section>
|
{{ timetableHistory.driverName }}
|
||||||
|
</router-link>
|
||||||
|
</b>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<button
|
||||||
|
@click="
|
||||||
|
navigateTo(`/journal/timetables`, {
|
||||||
|
'search-train': `#${timetableHistory.id}`
|
||||||
|
})
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<img src="/images/icon-back.svg" alt="icon navigate to timetable" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="bottom-info">
|
<div class="bottom-info">
|
||||||
<button class="btn btn--option" v-if="historyList.length > 0" @click="navigateToHistory()">
|
<button class="btn btn--option" v-if="historyList.length > 0" @click="navigateToHistory()">
|
||||||
{{ $t('scenery.bottom-info') }}
|
{{ $t('scenery.bottom-info') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
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 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 http from '../../http';
|
import { useApiStore } from '../../store/apiStore';
|
||||||
|
import routerMixin from '../../mixins/routerMixin';
|
||||||
|
import { useMainStore } from '../../store/mainStore';
|
||||||
|
|
||||||
|
const historyModeList = ['via', 'issuedFrom', 'terminatingAt'] as const;
|
||||||
|
type HistoryMode = (typeof historyModeList)[number];
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'SceneryTimetablesHistory',
|
name: 'SceneryTimetablesHistory',
|
||||||
mixins: [dateMixin, listObserverMixin],
|
mixins: [dateMixin, routerMixin],
|
||||||
props: {
|
props: {
|
||||||
station: {
|
station: {
|
||||||
type: Object as PropType<Station>
|
type: Object as PropType<Station>
|
||||||
},
|
},
|
||||||
onlineScenery: {
|
onlineScenery: {
|
||||||
type: Object as PropType<OnlineScenery>
|
type: Object as PropType<ActiveScenery>
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
historyList: [] as API.TimetableHistory.Response,
|
historyList: [] as API.TimetableHistory.Response,
|
||||||
|
historyModeList,
|
||||||
|
|
||||||
|
apiStore: useApiStore(),
|
||||||
|
mainStore: useMainStore(),
|
||||||
dataStatus: Status.Data.Loading,
|
dataStatus: Status.Data.Loading,
|
||||||
DataStatus: Status.Data
|
DataStatus: Status.Data,
|
||||||
|
|
||||||
|
checkedHistoryMode: 'via' as HistoryMode
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -105,17 +141,22 @@ export default defineComponent({
|
|||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
async fetchAPIData() {
|
async fetchAPIData() {
|
||||||
if (!this.station && !this.onlineScenery) {
|
const stationName = this.$route.query['station'];
|
||||||
|
|
||||||
|
if (!stationName) {
|
||||||
|
this.historyList = [];
|
||||||
this.dataStatus = Status.Data.Loaded;
|
this.dataStatus = Status.Data.Loaded;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const requestFilters: Record<string, any> = {};
|
||||||
|
requestFilters[this.checkedHistoryMode] = stationName.toString();
|
||||||
|
requestFilters.countLimit = 30;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response: API.TimetableHistory.Response = await (
|
const response: API.TimetableHistory.Response = await (
|
||||||
await http.get('api/getTimetables', {
|
await this.apiStore.client!.get('api/getTimetables', {
|
||||||
params: {
|
params: requestFilters
|
||||||
issuedFrom: this.station?.name || this.onlineScenery?.name
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
).data;
|
).data;
|
||||||
|
|
||||||
@@ -127,11 +168,17 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
checkHistoryMode(mode: HistoryMode) {
|
||||||
|
this.checkedHistoryMode = mode;
|
||||||
|
this.dataStatus = Status.Data.Loading;
|
||||||
|
this.fetchAPIData();
|
||||||
|
},
|
||||||
|
|
||||||
navigateToHistory() {
|
navigateToHistory() {
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
path: '/journal/timetables',
|
path: '/journal/timetables',
|
||||||
query: {
|
query: {
|
||||||
'search-issuedFrom': this.station?.name || this.onlineScenery?.name
|
[`search-${this.checkedHistoryMode}`]: this.station?.name || this.onlineScenery?.name
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -144,13 +191,66 @@ export default defineComponent({
|
|||||||
@import '../../styles/responsive.scss';
|
@import '../../styles/responsive.scss';
|
||||||
@import '../../styles/sceneryViewTables.scss';
|
@import '../../styles/sceneryViewTables.scss';
|
||||||
|
|
||||||
.top-filters {
|
.scenery-timetables-history {
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
gap: 1em;
|
||||||
|
grid-template-rows: auto 1fr 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-wrapper {
|
||||||
|
position: relative;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-modes {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
|
padding: 0.25em;
|
||||||
|
|
||||||
button {
|
button {
|
||||||
|
padding: 0.35em;
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5em;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-list > div {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
|
background-color: #2b2b2b;
|
||||||
|
line-height: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-list > div > button > img {
|
||||||
|
width: 2em;
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.timetable-status-indicator {
|
||||||
|
&[data-fulfilled='true'] {
|
||||||
|
color: lightgreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-terminated='false'] {
|
||||||
|
color: lightblue;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-terminated='true'][data-fulfilled='false'] {
|
||||||
|
color: lightcoral;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="general-status">
|
<div class="general-status">
|
||||||
<span
|
<span
|
||||||
:class="computedScheduledTrain.stopStatus"
|
:class="computedScheduledTrain.status"
|
||||||
:title="computedScheduledTrain.stopStatusDescription"
|
:title="computedScheduledTrain.stopStatusDescription"
|
||||||
>
|
>
|
||||||
{{ computedScheduledTrain.stopStatusIndicator }}
|
{{ computedScheduledTrain.stopStatusIndicator }}
|
||||||
@@ -11,25 +11,21 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, PropType } from 'vue';
|
import { defineComponent, PropType } from 'vue';
|
||||||
import { ScheduledTrain, StopStatus } from '../../store/typings';
|
import { StopStatus } from '../../typings/common';
|
||||||
|
import { SceneryTimetableRow } from './typings';
|
||||||
interface ScheduledTrainComp extends ScheduledTrain {
|
|
||||||
stopStatusIndicator: string;
|
|
||||||
stopStatusDescription: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
scheduledTrain: {
|
sceneryTimetableRow: {
|
||||||
type: Object as PropType<ScheduledTrain>,
|
type: Object as PropType<SceneryTimetableRow>,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
computedScheduledTrain(): ScheduledTrainComp {
|
computedScheduledTrain() {
|
||||||
const { prevDepartureLine, prevStationName, stopStatus, nextArrivalLine, nextStationName } =
|
const { prevDepartureLine, prevStationName, nextArrivalLine, nextStationName, status } =
|
||||||
this.scheduledTrain;
|
this.sceneryTimetableRow;
|
||||||
|
|
||||||
const prevDepartureIndicator = prevDepartureLine
|
const prevDepartureIndicator = prevDepartureLine
|
||||||
? `(${prevDepartureLine}) ${prevStationName}`
|
? `(${prevDepartureLine}) ${prevStationName}`
|
||||||
@@ -41,7 +37,7 @@ export default defineComponent({
|
|||||||
let stopStatusDescription = '',
|
let stopStatusDescription = '',
|
||||||
stopStatusIndicator = '';
|
stopStatusIndicator = '';
|
||||||
|
|
||||||
switch (stopStatus) {
|
switch (status) {
|
||||||
case StopStatus.ARRIVING:
|
case StopStatus.ARRIVING:
|
||||||
stopStatusIndicator = `${this.$t('timetables.from')}: ${prevDepartureIndicator}`;
|
stopStatusIndicator = `${this.$t('timetables.from')}: ${prevDepartureIndicator}`;
|
||||||
stopStatusDescription = this.$t('timetables.desc-arriving', {
|
stopStatusDescription = this.$t('timetables.desc-arriving', {
|
||||||
@@ -56,7 +52,7 @@ export default defineComponent({
|
|||||||
? `${this.$t('timetables.to')}: ${nextArrivalIndicator}`
|
? `${this.$t('timetables.to')}: ${nextArrivalIndicator}`
|
||||||
: `${this.$t('timetables.desc-end')}`;
|
: `${this.$t('timetables.desc-end')}`;
|
||||||
stopStatusDescription = nextArrivalLine
|
stopStatusDescription = nextArrivalLine
|
||||||
? this.$t(`timetables.desc-${stopStatus}`, { nextStationName, nextArrivalLine })
|
? this.$t(`timetables.desc-${status}`, { nextStationName, nextArrivalLine })
|
||||||
: '';
|
: '';
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -85,7 +81,7 @@ export default defineComponent({
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...this.scheduledTrain,
|
...this.sceneryTimetableRow,
|
||||||
stopStatusDescription,
|
stopStatusDescription,
|
||||||
stopStatusIndicator
|
stopStatusIndicator
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { StopStatus, Train, TrainStop } from '../../typings/common';
|
||||||
|
|
||||||
|
export interface SceneryTimetableRow {
|
||||||
|
checkpointStop: TrainStop;
|
||||||
|
train: Train;
|
||||||
|
prevDepartureLine: string | null;
|
||||||
|
nextArrivalLine: string | null;
|
||||||
|
departureLine: string | null;
|
||||||
|
arrivingLine: string | null;
|
||||||
|
prevStationName: string | null;
|
||||||
|
nextStationName: string | null;
|
||||||
|
status: StopStatus;
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { StopStatus, TrainStop } from '../../typings/common';
|
||||||
|
|
||||||
|
export const stopStatusPriority = [
|
||||||
|
StopStatus.ONLINE,
|
||||||
|
StopStatus.STOPPED,
|
||||||
|
StopStatus.DEPARTED,
|
||||||
|
StopStatus.ARRIVING,
|
||||||
|
StopStatus.DEPARTED_AWAY,
|
||||||
|
StopStatus.TERMINATED
|
||||||
|
];
|
||||||
|
|
||||||
|
export function getTrainStopStatus(
|
||||||
|
stopInfo: TrainStop,
|
||||||
|
currentStationName: string,
|
||||||
|
sceneryName: string
|
||||||
|
) {
|
||||||
|
if (stopInfo.terminatesHere && stopInfo.confirmed) {
|
||||||
|
return StopStatus.TERMINATED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stopInfo.terminatesHere && stopInfo.confirmed && currentStationName == sceneryName) {
|
||||||
|
return StopStatus.DEPARTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stopInfo.terminatesHere && stopInfo.confirmed && currentStationName != sceneryName) {
|
||||||
|
return StopStatus.DEPARTED_AWAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentStationName == sceneryName && !stopInfo.stopped) {
|
||||||
|
return StopStatus.ONLINE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentStationName == sceneryName && stopInfo.stopped) {
|
||||||
|
return StopStatus.STOPPED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentStationName != sceneryName) {
|
||||||
|
return StopStatus.ARRIVING;
|
||||||
|
}
|
||||||
|
|
||||||
|
return StopStatus.ONLINE;
|
||||||
|
}
|
||||||
@@ -15,7 +15,6 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { useStationFiltersStore } from '../../store/stationFiltersStore';
|
|
||||||
|
|
||||||
interface FilterOption {
|
interface FilterOption {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -40,15 +39,9 @@ export default defineComponent({
|
|||||||
|
|
||||||
emits: ['update:optionValue'],
|
emits: ['update:optionValue'],
|
||||||
|
|
||||||
setup() {
|
|
||||||
return {
|
|
||||||
filterStore: useStationFiltersStore()
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
'option.value'() {
|
'option.value'() {
|
||||||
this.filterStore.changeFilterValue(this.option.name, !this.option.value);
|
// this.filterStore.changeFilterValue(this.option.name, !this.option.value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -56,17 +49,17 @@ export default defineComponent({
|
|||||||
handleDbClick(e: Event) {
|
handleDbClick(e: Event) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
this.filterStore.lastClickedFilterId = this.option.id;
|
// this.filterStore.lastClickedFilterId = this.option.id;
|
||||||
// this.option.value = true;
|
// this.option.value = true;
|
||||||
this.$emit('update:optionValue', true);
|
this.$emit('update:optionValue', true);
|
||||||
|
|
||||||
this.filterStore.inputs.options
|
// this.filterStore.inputs.options
|
||||||
.filter((option) => {
|
// .filter((option) => {
|
||||||
return option.section == this.option.section && option.id != this.option.id;
|
// return option.section == this.option.section && option.id != this.option.id;
|
||||||
})
|
// })
|
||||||
.forEach((option) => {
|
// .forEach((option) => {
|
||||||
option.value = !this.option.value;
|
// option.value = !this.option.value;
|
||||||
});
|
// });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="filter-card" v-click-outside="closeCard" @keydown.esc="closeCard">
|
<section class="filter-card" v-click-outside="closeCard" @keydown.esc="closeCard">
|
||||||
<div class="card_controls">
|
<div class="card_controls">
|
||||||
<button class="btn--filled btn--image" @click="toggleCard">
|
<button class="card-button 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]
|
<p>[F] {{ $t('options.filters') }}</p>
|
||||||
<span class="active-indicator" v-if="!filterStore.areFiltersAtDefault"></span>
|
<span class="active-indicator" v-if="changedFilters.length != 0"></span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<label for="scenery-search">
|
<label for="scenery-search">
|
||||||
@@ -28,34 +28,50 @@
|
|||||||
</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>
|
||||||
|
|
||||||
|
<div class="changed-filters" :data-active="changedFilters.length > 0">
|
||||||
|
<template v-if="changedFilters.length > 0">
|
||||||
|
{{ $t('filters.changed-filters-count') }} <b>{{ changedFilters.length }}</b>
|
||||||
|
</template>
|
||||||
|
<template v-else>{{ $t('filters.no-changed-filters') }}</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
<section class="card_options">
|
<section class="card_options">
|
||||||
<div
|
<div
|
||||||
class="option-section"
|
class="option-section"
|
||||||
v-for="section in filterStore.inputs.optionSections"
|
v-for="(sectionFilters, sectionKey) in filtersSections"
|
||||||
:key="section"
|
:key="sectionKey"
|
||||||
>
|
>
|
||||||
<h3 class="text--primary">
|
<h3 class="text--primary">
|
||||||
{{ $t(`filters.sections.${section}`) }}
|
<span class="active-indicator" v-if="!areSectionFiltersDefault(sectionKey)"></span>
|
||||||
|
{{ $t(`filters.sections.${sectionKey}`) }}
|
||||||
<button @click="filterStore.resetSectionOptions(section)">RESET</button>
|
<button @click="resetSectionFilters(sectionKey)">RESET</button>
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<div class="section-inputs">
|
<div class="section-filters">
|
||||||
<FilterOption
|
<label
|
||||||
v-for="(option, i) in filterStore.inputs.options.filter(
|
v-for="filterKey in sectionFilters"
|
||||||
(o) => o.section == section
|
@click="() => (filters[filterKey] = !filters[filterKey])"
|
||||||
)"
|
@dblclick="setSingleSectionFilter(sectionKey, filterKey)"
|
||||||
v-model:optionValue="option.value"
|
:for="filterKey"
|
||||||
:option="option"
|
>
|
||||||
:key="i"
|
<input
|
||||||
|
:checked="filters[filterKey]"
|
||||||
|
v-model="filters[filterKey]"
|
||||||
|
type="checkbox"
|
||||||
|
:class="sectionKey"
|
||||||
|
:name="filterKey"
|
||||||
/>
|
/>
|
||||||
|
<span>
|
||||||
|
{{ $t(`filters.${filterKey}`) }}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -68,7 +84,7 @@
|
|||||||
<span>{{
|
<span>{{
|
||||||
minimumHours == 0
|
minimumHours == 0
|
||||||
? $t('filters.now')
|
? $t('filters.now')
|
||||||
: minimumHours < 8
|
: minimumHours < 7
|
||||||
? minimumHours + $t('filters.hour')
|
? minimumHours + $t('filters.hour')
|
||||||
: $t('filters.no-limit')
|
: $t('filters.no-limit')
|
||||||
}}</span>
|
}}</span>
|
||||||
@@ -76,21 +92,21 @@
|
|||||||
</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>
|
<h3 class="section-header">{{ $t('filters.authors-search') }}</h3>
|
||||||
|
|
||||||
|
<datalist id="authors" name="authors">
|
||||||
|
<option v-for="(author, i) in authorsHint" :key="i" :value="author"></option>
|
||||||
|
</datalist>
|
||||||
|
|
||||||
<form action="javascript:void(0);" @submit="handleAuthorsInput">
|
<form action="javascript:void(0);" @submit="handleAuthorsInput">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="author"
|
id="author"
|
||||||
list="authors"
|
list="authors"
|
||||||
name="authors"
|
name="authors"
|
||||||
|
v-model="authors"
|
||||||
:placeholder="$t('filters.authors-placeholder')"
|
:placeholder="$t('filters.authors-placeholder')"
|
||||||
v-model="authorsInputValue"
|
|
||||||
@focus="preventKeyDown = true"
|
@focus="preventKeyDown = true"
|
||||||
@blur="preventKeyDown = false"
|
@blur="preventKeyDown = false"
|
||||||
/>
|
/>
|
||||||
@@ -100,18 +116,18 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="card_sliders">
|
<section class="card_sliders">
|
||||||
<div class="slider" v-for="(slider, i) in filterStore.inputs.sliders" :key="i">
|
<div class="slider" v-for="(slider, i) in initSliders" :key="i">
|
||||||
<input
|
<input
|
||||||
class="slider-input"
|
class="slider-input"
|
||||||
type="range"
|
type="range"
|
||||||
:name="slider.name"
|
:name="slider.id"
|
||||||
:id="slider.id"
|
:id="slider.id"
|
||||||
:min="slider.minRange"
|
:min="slider.minRange"
|
||||||
:max="slider.maxRange"
|
:max="slider.maxRange"
|
||||||
v-model="slider.value"
|
:step="slider.step"
|
||||||
@change="handleInput"
|
v-model="filters[slider.id]"
|
||||||
/>
|
/>
|
||||||
<span class="slider-value">{{ slider.value }}</span>
|
<span class="slider-value">{{ filters[slider.id] }}</span>
|
||||||
<div class="slider-content">
|
<div class="slider-content">
|
||||||
{{ $t(`filters.sliders.${slider.id}`) }}
|
{{ $t(`filters.sliders.${slider.id}`) }}
|
||||||
</div>
|
</div>
|
||||||
@@ -132,11 +148,11 @@
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
class="btn--action"
|
class="btn--action"
|
||||||
|
:disabled="changedFilters.length == 0"
|
||||||
|
:data-disabled="changedFilters.length == 0"
|
||||||
@click="resetFilters"
|
@click="resetFilters"
|
||||||
: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>
|
||||||
@@ -150,48 +166,76 @@
|
|||||||
import { defineComponent, inject } from 'vue';
|
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 { useMainStore } 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';
|
||||||
|
|
||||||
|
import {
|
||||||
|
filtersSections,
|
||||||
|
initSliders,
|
||||||
|
initFilters,
|
||||||
|
getChangedFilters
|
||||||
|
} from '../../managers/stationFilterManager';
|
||||||
|
|
||||||
|
import { StationFilterSection } from '../../managers/stationFilterManager';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { watch } from 'vue';
|
||||||
|
|
||||||
|
const STORAGE_KEY = 'options_saved';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { FilterOption },
|
components: { FilterOption },
|
||||||
mixins: [keyMixin, routerMixin],
|
mixins: [keyMixin, routerMixin],
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
saveOptions: false,
|
saveOptions: false,
|
||||||
STORAGE_KEY: 'options_saved',
|
|
||||||
|
|
||||||
authorsInputValue: '',
|
filtersSections,
|
||||||
|
initSliders,
|
||||||
|
|
||||||
minimumHours: 0,
|
minimumHours: 0,
|
||||||
|
authors: '',
|
||||||
|
|
||||||
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 = useMainStore();
|
const store = useMainStore();
|
||||||
const filterStore = useStationFiltersStore();
|
|
||||||
|
const filters = inject('StationsView_filters') as Record<string, any>;
|
||||||
|
|
||||||
|
const changedFilters = computed(() => getChangedFilters(filters));
|
||||||
|
|
||||||
|
// Save filters to persistent storage
|
||||||
|
watch(filters, (value) => {
|
||||||
|
if (!StorageManager.isRegistered(STORAGE_KEY)) return;
|
||||||
|
|
||||||
|
Object.keys(value).forEach((filterKey) => {
|
||||||
|
StorageManager.setValue(filterKey, filters[filterKey]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isVisible,
|
isVisible,
|
||||||
store,
|
store,
|
||||||
filterStore
|
filters,
|
||||||
|
changedFilters
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.saveOptions = StorageManager.isRegistered(this.STORAGE_KEY);
|
this.saveOptions = StorageManager.isRegistered(STORAGE_KEY);
|
||||||
|
|
||||||
if (StorageManager.isRegistered('onlineFromHours') && this.saveOptions) {
|
if (StorageManager.isRegistered('onlineFromHours') && this.saveOptions) {
|
||||||
this.minimumHours = StorageManager.getNumericValue('onlineFromHours');
|
this.minimumHours = StorageManager.getNumericValue('onlineFromHours');
|
||||||
|
|
||||||
this.changeNumericFilterValue('onlineFromHours', this.minimumHours);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currentRegion = this.store.region;
|
this.currentRegion = this.store.region;
|
||||||
@@ -210,7 +254,7 @@ export default defineComponent({
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
authors() {
|
authorsHint() {
|
||||||
return this.store.stationList
|
return this.store.stationList
|
||||||
.reduce((acc, station) => {
|
.reduce((acc, station) => {
|
||||||
station.generalInfo?.authors?.forEach((author) => {
|
station.generalInfo?.authors?.forEach((author) => {
|
||||||
@@ -236,7 +280,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;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -247,63 +294,67 @@ export default defineComponent({
|
|||||||
this.isVisible = !this.isVisible;
|
this.isVisible = !this.isVisible;
|
||||||
},
|
},
|
||||||
|
|
||||||
handleInput(e: Event) {
|
onScroll(e: Event) {
|
||||||
const target = e.target as HTMLInputElement;
|
this.scrollTop = (e.target as HTMLElement).scrollTop;
|
||||||
|
|
||||||
this.filterStore.changeFilterValue(target.name, target.value);
|
|
||||||
|
|
||||||
if (this.saveOptions) StorageManager.setStringValue(target.name, target.value);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
handleAuthorsInput() {
|
handleAuthorsInput() {
|
||||||
console.log(this.authorsInputValue);
|
this.filters['authors'] = this.authors;
|
||||||
|
// if (this.saveOptions) StorageManager.setStringValue('authors', target.value);
|
||||||
this.filterStore.changeFilterValue('authors', this.authorsInputValue);
|
|
||||||
|
|
||||||
if (this.saveOptions) StorageManager.setStringValue('authors', this.authorsInputValue);
|
|
||||||
},
|
|
||||||
|
|
||||||
changeNumericFilterValue(name: string, value: number, saveToStorage = false) {
|
|
||||||
this.filterStore.changeFilterValue(name, value);
|
|
||||||
if (this.saveOptions && saveToStorage) StorageManager.setNumericValue(name, value);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
subHour() {
|
subHour() {
|
||||||
this.minimumHours = this.minimumHours < 1 ? 8 : this.minimumHours - 1;
|
this.minimumHours = this.minimumHours < 1 ? 7 : this.minimumHours - 1;
|
||||||
|
this.filters['onlineFromHours'] = this.minimumHours;
|
||||||
this.changeNumericFilterValue('onlineFromHours', this.minimumHours, true);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
addHour() {
|
addHour() {
|
||||||
this.minimumHours = this.minimumHours > 7 ? 0 : this.minimumHours + 1;
|
this.minimumHours = this.minimumHours > 6 ? 0 : this.minimumHours + 1;
|
||||||
|
this.filters['onlineFromHours'] = this.minimumHours;
|
||||||
this.changeNumericFilterValue('onlineFromHours', this.minimumHours, true);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
saveFilters() {
|
saveFilters() {
|
||||||
this.saveOptions = !this.saveOptions;
|
this.saveOptions = !this.saveOptions;
|
||||||
|
|
||||||
if (!this.saveOptions) {
|
if (!this.saveOptions) {
|
||||||
StorageManager.unregisterStorage(this.STORAGE_KEY);
|
StorageManager.unregisterStorage(STORAGE_KEY);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
StorageManager.registerStorage(this.STORAGE_KEY);
|
StorageManager.registerStorage(STORAGE_KEY);
|
||||||
|
|
||||||
this.filterStore.inputs.options.forEach((option) =>
|
Object.keys(this.filters).forEach((filterKey) => {
|
||||||
StorageManager.setBooleanValue(option.name, !option.value)
|
StorageManager.setValue(filterKey, this.filters[filterKey]);
|
||||||
);
|
});
|
||||||
this.filterStore.inputs.sliders.forEach((slider) =>
|
|
||||||
StorageManager.setNumericValue(slider.name, slider.value)
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
resetFilters() {
|
resetFilters() {
|
||||||
this.authorsInputValue = '';
|
// Reset local model values
|
||||||
|
|
||||||
this.minimumHours = 0;
|
this.minimumHours = 0;
|
||||||
this.changeNumericFilterValue('onlineFromHours', this.minimumHours, true);
|
this.authors = '';
|
||||||
this.filterStore.resetFilters();
|
|
||||||
|
// Reset global filters
|
||||||
|
Object.keys(this.filters).forEach((filterKey) => {
|
||||||
|
this.filters[filterKey] = (initFilters as any)[filterKey];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
areSectionFiltersDefault(sectionKey: StationFilterSection) {
|
||||||
|
return filtersSections[sectionKey].every((filterKey) => {
|
||||||
|
return this.filters[filterKey] == initFilters[filterKey];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
resetSectionFilters(sectionKey: StationFilterSection) {
|
||||||
|
filtersSections[sectionKey].forEach((filterKey) => {
|
||||||
|
this.filters[filterKey] = initFilters[filterKey];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
setSingleSectionFilter(sectionKey: StationFilterSection, chosenKey: string) {
|
||||||
|
filtersSections[sectionKey].forEach((filterKey) => {
|
||||||
|
if (filterKey != chosenKey) this.filters[filterKey] = initFilters[filterKey];
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
closeCard() {
|
closeCard() {
|
||||||
@@ -318,9 +369,10 @@ export default defineComponent({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../styles/responsive.scss';
|
@import '../../styles/responsive';
|
||||||
@import '../../styles/card.scss';
|
@import '../../styles/card';
|
||||||
@import '../../styles/animations.scss';
|
@import '../../styles/animations';
|
||||||
|
@import '../../styles/variables';
|
||||||
|
|
||||||
h3.section-header {
|
h3.section-header {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -337,6 +389,15 @@ h3.section-header {
|
|||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.changed-filters {
|
||||||
|
background-color: #111;
|
||||||
|
padding: 0.5em;
|
||||||
|
|
||||||
|
&[data-active='true'] {
|
||||||
|
color: lightgreen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.card_controls {
|
.card_controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
@@ -365,28 +426,6 @@ h3.section-header {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card_regions {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
label > input {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
label > span {
|
|
||||||
padding: 0.25em 0.5em;
|
|
||||||
margin: 0 0.25em;
|
|
||||||
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
background-color: gray;
|
|
||||||
|
|
||||||
&.checked {
|
|
||||||
background-color: seagreen;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.card_timestamp {
|
.card_timestamp {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -432,24 +471,63 @@ h3.section-header {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.card_actions {
|
.section-filters {
|
||||||
width: 100%;
|
display: grid;
|
||||||
padding: 0.5em;
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 0.5em;
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
.filter-option {
|
.section-filters > label {
|
||||||
max-width: 50%;
|
position: relative;
|
||||||
margin: 0 auto;
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
|
||||||
|
span {
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
padding: 0.25em;
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: forestgreen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span:hover {
|
||||||
|
background-color: #22aa22;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='checkbox'] {
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
|
&:checked + span {
|
||||||
|
background-color: #444;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #555;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-visible + span {
|
||||||
|
outline: 1px solid $accentCol;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card_actions {
|
||||||
|
padding: 0.5em;
|
||||||
|
|
||||||
.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;
|
||||||
|
|
||||||
@@ -473,35 +551,18 @@ h3.section-header {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-inputs {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
gap: 0.5em;
|
|
||||||
margin: 1em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quick-actions div {
|
|
||||||
display: flex;
|
|
||||||
margin: 1em 0;
|
|
||||||
gap: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider {
|
.slider {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 0.25em;
|
||||||
|
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
|
|
||||||
&-value {
|
&-value {
|
||||||
color: $accentCol;
|
color: $accentCol;
|
||||||
margin-right: 0.5em;
|
|
||||||
padding: 0.1em 0.2em;
|
padding: 0.1em 0.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-content {
|
|
||||||
flex-grow: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-input {
|
&-input {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
appearance: none;
|
appearance: none;
|
||||||
@@ -510,7 +571,6 @@ h3.section-header {
|
|||||||
outline: none;
|
outline: none;
|
||||||
|
|
||||||
min-width: 25%;
|
min-width: 25%;
|
||||||
max-width: 120px;
|
|
||||||
|
|
||||||
&:focus-visible ~ * {
|
&:focus-visible ~ * {
|
||||||
color: gold;
|
color: gold;
|
||||||
@@ -580,4 +640,19 @@ h3.section-header {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@include smallScreen {
|
||||||
|
.card_controls > button.card-button > p {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&-input {
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,212 @@
|
|||||||
|
<template>
|
||||||
|
<div class="station-stats">
|
||||||
|
<div class="separator" />
|
||||||
|
|
||||||
|
<div class="stats-row">
|
||||||
|
<div>
|
||||||
|
<span
|
||||||
|
>{{ $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
|
||||||
|
>:
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<b class="u-factor" :style="calculateFactorStyle()">
|
||||||
|
{{ uFactor.toFixed(2) }}
|
||||||
|
</b>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
•
|
||||||
|
{{ $t('station-stats.avg-timetable-count') }}
|
||||||
|
<b>{{ avgTimetableCount.toFixed(2) }}</b>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
•
|
||||||
|
{{ $t('station-stats.single-track-count') }}
|
||||||
|
<b>{{ trackCount.oneWay }}</b> (<b>{{ trackCount.oneWayElectric }} ⚡</b>)
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
•
|
||||||
|
{{ $t('station-stats.double-track-count') }}
|
||||||
|
<b>{{ trackCount.twoWay }}</b>
|
||||||
|
(<b>{{ trackCount.twoWayElectric }} ⚡</b>)
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
• {{ $t('station-stats.cross-sceneries') }} <b>{{ trackCount.crossTrack }}</b> (<b
|
||||||
|
>{{ trackCount.crossTrackElectric }} ⚡</b
|
||||||
|
>)
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
•
|
||||||
|
{{ $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>
|
||||||
|
</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 regionSceneries = this.mainStore.activeSceneryList.filter((sc) => {
|
||||||
|
return sc.region == this.mainStore.region.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
const timetableCountSum = regionSceneries.reduce((acc, sc) => {
|
||||||
|
acc += sc.scheduledTrainCount.all;
|
||||||
|
return acc;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
if (regionSceneries.length == 0) return 0;
|
||||||
|
|
||||||
|
return timetableCountSum / regionSceneries.length;
|
||||||
|
},
|
||||||
|
|
||||||
|
trackCount() {
|
||||||
|
return this.mainStore.allStationInfo
|
||||||
|
.filter(
|
||||||
|
(st) =>
|
||||||
|
st.onlineInfo?.dispatcherId != -1 &&
|
||||||
|
st.onlineInfo?.region == this.mainStore.region.id &&
|
||||||
|
st.generalInfo?.routes
|
||||||
|
)
|
||||||
|
.reduce(
|
||||||
|
(acc, st) => {
|
||||||
|
const { routes } = st.generalInfo!;
|
||||||
|
|
||||||
|
if (
|
||||||
|
routes.single.filter((r) => !r.isInternal).length > 0 &&
|
||||||
|
routes.double.filter((r) => !r.isInternal).length > 0
|
||||||
|
) {
|
||||||
|
acc.crossTrack++;
|
||||||
|
|
||||||
|
if (
|
||||||
|
routes.single.some((r) => r.isElectric) &&
|
||||||
|
routes.double.some((r) => r.isElectric)
|
||||||
|
)
|
||||||
|
acc.crossTrackElectric++;
|
||||||
|
}
|
||||||
|
|
||||||
|
[...routes.single, ...routes.double].forEach((r) => {
|
||||||
|
if (r.isInternal) return;
|
||||||
|
|
||||||
|
acc[r.routeTracks == 2 ? 'twoWay' : 'oneWay'] += 1;
|
||||||
|
if (r.isElectric) acc[r.routeTracks == 2 ? 'twoWayElectric' : 'oneWayElectric'] += 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
oneWay: 0,
|
||||||
|
oneWayElectric: 0,
|
||||||
|
twoWay: 0,
|
||||||
|
twoWayElectric: 0,
|
||||||
|
crossTrack: 0,
|
||||||
|
crossTrackElectric: 0
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
spawnCount() {
|
||||||
|
return this.mainStore.activeSceneryList.reduce(
|
||||||
|
(acc, scenery) => {
|
||||||
|
if (scenery.region != this.mainStore.region.id) return acc;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
text-wrap: pretty;
|
||||||
|
gap: 0.25em;
|
||||||
|
margin-top: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.u-factor {
|
||||||
|
[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,7 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="station_table">
|
<section class="station_table">
|
||||||
<div class="table_wrapper">
|
<Loading
|
||||||
<table>
|
v-if="apiStore.dataStatuses.connection == Status.Loading && filteredStationList.length == 0"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<table v-else-if="filteredStationList.length > 0">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th
|
<th
|
||||||
@@ -9,14 +12,15 @@
|
|||||||
: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"
|
||||||
v-if="sorterActive.headerName == headerName"
|
v-if="activeSorter.headerName == headerName"
|
||||||
:src="`/images/icon-arrow-${sorterActive.dir == 1 ? 'asc' : 'desc'}.svg`"
|
:src="`/images/icon-arrow-${activeSorter.dir == 1 ? 'asc' : 'desc'}.svg`"
|
||||||
alt="sort icon"
|
alt="sort icon"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
@@ -27,18 +31,19 @@
|
|||||||
: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
|
||||||
class="sort-icon"
|
class="sort-icon"
|
||||||
v-if="sorterActive.headerName == headerName"
|
v-if="activeSorter.headerName == headerName"
|
||||||
:src="`/images/icon-arrow-${sorterActive.dir == 1 ? 'asc' : 'desc'}.svg`"
|
:src="`/images/icon-arrow-${activeSorter.dir == 1 ? 'asc' : 'desc'}.svg`"
|
||||||
alt="sort icon"
|
alt="sort icon"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
@@ -48,24 +53,23 @@
|
|||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr
|
<tr
|
||||||
class="station"
|
v-for="station in filteredStationList"
|
||||||
: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 +86,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 +94,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 +102,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 +110,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="apiStore.donatorsData.includes(station.onlineInfo.dispatcherName)"
|
v-if="apiStore.donatorsData.includes(station.onlineInfo.dispatcherName)"
|
||||||
:title="$t('donations.dispatcher-message')"
|
@click.stop="openDonationCard"
|
||||||
@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 +135,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,206 +149,228 @@
|
|||||||
</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 class="no-stations" v-else>
|
||||||
|
<div>
|
||||||
|
{{ $t('sceneries.no-stations') }} (region: <b>{{ mainStore.region.name }}</b
|
||||||
|
>)
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Loading v-if="apiStore.dataStatuses.connection == Status.Loading" />
|
<div class="text--primary" v-if="getChangedFilters(filters).length != 0">
|
||||||
|
⚠ {{ $t('sceneries.active-filters') }}
|
||||||
<div class="no-stations" v-else-if="stations.length == 0">
|
</div>
|
||||||
{{ $t('sceneries.no-stations') }}
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, PropType } from 'vue';
|
import { defineComponent, inject, computed } from 'vue';
|
||||||
import dateMixin from '../../mixins/dateMixin';
|
|
||||||
import stationInfoMixin from '../../mixins/stationInfoMixin';
|
|
||||||
import styleMixin from '../../mixins/styleMixin';
|
|
||||||
import Station from '../../scripts/interfaces/Station';
|
|
||||||
import { useStationFiltersStore } from '../../store/stationFiltersStore';
|
|
||||||
import { useMainStore } from '../../store/mainStore';
|
|
||||||
import Loading from '../Global/Loading.vue';
|
|
||||||
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 Loading from '../Global/Loading.vue';
|
||||||
|
import dateMixin from '../../mixins/dateMixin';
|
||||||
|
import styleMixin from '../../mixins/styleMixin';
|
||||||
import { useApiStore } from '../../store/apiStore';
|
import { useApiStore } from '../../store/apiStore';
|
||||||
|
import { useMainStore } from '../../store/mainStore';
|
||||||
|
import { Status } from '../../typings/common';
|
||||||
|
import { useTooltipStore } from '../../store/tooltipStore';
|
||||||
|
import { getChangedFilters } from '../../managers/stationFilterManager';
|
||||||
|
import { ActiveSorter, HeadIdsType, headIconsIds, headIds } from './typings';
|
||||||
|
import { filterStations, sortStations } from './utils';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
emits: ['toggleDonationCard'],
|
||||||
stations: {
|
|
||||||
type: Array as PropType<Station[]>,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
emits: ['toggleDonationModal'],
|
|
||||||
components: { Loading, StationStatusBadge },
|
components: { Loading, StationStatusBadge },
|
||||||
mixins: [styleMixin, dateMixin, stationInfoMixin],
|
mixins: [styleMixin, dateMixin],
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
headIconsIds,
|
headIconsIds,
|
||||||
headIds,
|
headIds,
|
||||||
lastSelectedStationName: ''
|
lastSelectedStationName: '',
|
||||||
|
getChangedFilters
|
||||||
}),
|
}),
|
||||||
|
|
||||||
computed: {
|
|
||||||
sorterActive() {
|
|
||||||
return this.stationFiltersStore.sorterActive;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
const mainStore = useMainStore();
|
const mainStore = useMainStore();
|
||||||
const apiStore = useApiStore();
|
const apiStore = useApiStore();
|
||||||
const stationFiltersStore = useStationFiltersStore();
|
const tooltipStore = useTooltipStore();
|
||||||
|
|
||||||
|
const filters = inject('StationsView_filters') as Record<string, any>;
|
||||||
|
const activeSorter = inject('StationsView_activeSorter') as ActiveSorter;
|
||||||
|
|
||||||
|
const filteredStationList = computed(() =>
|
||||||
|
mainStore.allStationInfo
|
||||||
|
.filter((station) => filterStations(station, filters))
|
||||||
|
.sort((a, b) => sortStations(a, b, activeSorter))
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
Status: Status.Data,
|
Status: Status.Data,
|
||||||
stationFiltersStore,
|
|
||||||
mainStore,
|
mainStore,
|
||||||
apiStore
|
apiStore,
|
||||||
|
tooltipStore,
|
||||||
|
filters,
|
||||||
|
filteredStationList,
|
||||||
|
activeSorter
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
setScenery(name: string) {
|
setScenery(name: string) {
|
||||||
const station = this.stations.find((station) => station.name === name);
|
const station = this.filteredStationList.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',
|
||||||
@@ -354,9 +381,10 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
openDonationModal(e: Event) {
|
openDonationCard(e: Event) {
|
||||||
this.$emit('toggleDonationModal', true);
|
this.$emit('toggleDonationCard', true);
|
||||||
this.mainStore.modalLastClickedTarget = e.target;
|
this.mainStore.modalLastClickedTarget = e.target;
|
||||||
|
this.tooltipStore.hide();
|
||||||
},
|
},
|
||||||
|
|
||||||
openForumSite(e: Event, url: string | undefined) {
|
openForumSite(e: Event, url: string | undefined) {
|
||||||
@@ -365,10 +393,14 @@ export default defineComponent({
|
|||||||
window.open(url, '_blank');
|
window.open(url, '_blank');
|
||||||
},
|
},
|
||||||
|
|
||||||
changeSorter(headerName: HeadIdsTypes) {
|
changeSorter(headerName: HeadIdsType) {
|
||||||
if (headerName == 'general' || headerName == 'routes') return;
|
if (headerName == 'general') return;
|
||||||
|
|
||||||
this.stationFiltersStore.changeSorter(headerName);
|
if (headerName == this.activeSorter.headerName)
|
||||||
|
this.activeSorter.dir = -1 * this.activeSorter.dir;
|
||||||
|
else this.activeSorter.dir = 1;
|
||||||
|
|
||||||
|
this.activeSorter.headerName = headerName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -381,33 +413,28 @@ export default defineComponent({
|
|||||||
|
|
||||||
$rowCol: #424242;
|
$rowCol: #424242;
|
||||||
|
|
||||||
.change-anim {
|
.station_table {
|
||||||
&-enter-active,
|
height: 80vh;
|
||||||
&-leave-active {
|
max-height: 2000px;
|
||||||
transition: opacity 100ms ease-in;
|
min-height: 700px;
|
||||||
}
|
|
||||||
|
|
||||||
&-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.25em;
|
||||||
// min-width: 1350px;
|
padding: 1em;
|
||||||
width: 100%;
|
background: #1a1a1a;
|
||||||
|
line-height: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
@include smallScreen() {
|
table {
|
||||||
min-width: auto;
|
border-collapse: collapse;
|
||||||
}
|
table-layout: fixed;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 1250px;
|
||||||
|
white-space: wrap;
|
||||||
|
|
||||||
thead tr {
|
thead tr {
|
||||||
background-color: $bgCol;
|
background-color: $bgCol;
|
||||||
@@ -417,12 +444,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;
|
||||||
@@ -447,7 +503,7 @@ table {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tr.station {
|
tr {
|
||||||
background-color: $rowCol;
|
background-color: $rowCol;
|
||||||
|
|
||||||
&:nth-child(even) {
|
&:nth-child(even) {
|
||||||
@@ -461,10 +517,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;
|
||||||
@@ -474,9 +535,9 @@ tr.station {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
td.station {
|
.station-name {
|
||||||
&_name {
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
max-width: 200px;
|
||||||
|
|
||||||
&.default {
|
&.default {
|
||||||
color: $accentCol;
|
color: $accentCol;
|
||||||
@@ -490,10 +551,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;
|
||||||
|
|
||||||
@@ -507,53 +568,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;
|
||||||
}
|
}
|
||||||
@@ -562,30 +608,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;
|
||||||
}
|
}
|
||||||
@@ -598,18 +636,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>
|
||||||
|
|||||||
@@ -5,51 +5,29 @@ export interface FilterOption {
|
|||||||
defaultValue: boolean;
|
defaultValue: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Filter {
|
export const headIds = [
|
||||||
[key: string]: boolean | number | string;
|
'station',
|
||||||
default: boolean;
|
'min-lvl',
|
||||||
notDefault: boolean;
|
'status',
|
||||||
real: boolean;
|
'dispatcher',
|
||||||
fictional: boolean;
|
'dispatcher-lvl',
|
||||||
SPK: boolean;
|
'routes-single',
|
||||||
SCS: boolean;
|
'routes-double',
|
||||||
SPE: boolean;
|
'general'
|
||||||
SUP: boolean;
|
] as const;
|
||||||
noSUP: boolean;
|
|
||||||
ręczne: boolean;
|
|
||||||
'ręczne+SPK': boolean;
|
|
||||||
'ręczne+SCS': boolean;
|
|
||||||
mechaniczne: boolean;
|
|
||||||
'mechaniczne+SPK': boolean;
|
|
||||||
'mechaniczne+SCS': boolean;
|
|
||||||
SBL: boolean;
|
|
||||||
PBL: boolean;
|
|
||||||
współczesna: boolean;
|
|
||||||
kształtowa: boolean;
|
|
||||||
historyczna: boolean;
|
|
||||||
mieszana: boolean;
|
|
||||||
minLevel: number;
|
|
||||||
maxLevel: number;
|
|
||||||
minOneWayCatenary: number;
|
|
||||||
minOneWay: number;
|
|
||||||
minTwoWayCatenary: number;
|
|
||||||
minTwoWay: number;
|
|
||||||
'no-1track': boolean;
|
|
||||||
'no-2track': boolean;
|
|
||||||
'include-selected': boolean;
|
|
||||||
free: boolean;
|
|
||||||
occupied: boolean;
|
|
||||||
nonPublic: boolean;
|
|
||||||
unavailable: boolean;
|
|
||||||
abandoned: boolean;
|
|
||||||
|
|
||||||
endingStatus: boolean;
|
export const headIconsIds = [
|
||||||
afkStatus: boolean;
|
'user',
|
||||||
noSpaceStatus: boolean;
|
'like',
|
||||||
unavailableStatus: boolean;
|
'spawn',
|
||||||
unsignedStatus: boolean;
|
'timetableAll',
|
||||||
|
'timetableUnconfirmed',
|
||||||
|
'timetableConfirmed'
|
||||||
|
] as const;
|
||||||
|
|
||||||
authors: string;
|
export type HeadIdsType = (typeof headIds)[number] | (typeof headIconsIds)[number];
|
||||||
|
|
||||||
onlineFromHours: number;
|
export interface ActiveSorter {
|
||||||
|
headerName: HeadIdsType;
|
||||||
|
dir: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,275 @@
|
|||||||
|
import { ActiveSorter } from '../../components/StationsView/typings';
|
||||||
|
import { ActiveScenery, StationGeneralInfo, Status } from '../../typings/common';
|
||||||
|
import { Station } from '../../typings/common';
|
||||||
|
|
||||||
|
const dispatcherStatusPriority = [
|
||||||
|
Status.ActiveDispatcher.UNKNOWN,
|
||||||
|
Status.ActiveDispatcher.INVALID,
|
||||||
|
Status.ActiveDispatcher.NOT_LOGGED_IN,
|
||||||
|
Status.ActiveDispatcher.UNAVAILABLE,
|
||||||
|
Status.ActiveDispatcher.AFK,
|
||||||
|
Status.ActiveDispatcher.ENDING,
|
||||||
|
Status.ActiveDispatcher.NO_SPACE,
|
||||||
|
undefined
|
||||||
|
];
|
||||||
|
|
||||||
|
const filtersAssociations: Record<string, string> = {
|
||||||
|
mechaniczne: 'mechanical',
|
||||||
|
ręczne: 'manual',
|
||||||
|
'mechaniczne+SPK': 'SPK-M',
|
||||||
|
'ręczne+SPK': 'SPK-R',
|
||||||
|
'mechaniczne+SCS': 'SCS-M',
|
||||||
|
'ręczne+SCS': 'SCS-R',
|
||||||
|
współczesna: 'modern',
|
||||||
|
historyczna: 'historical',
|
||||||
|
kształtowa: 'semaphores',
|
||||||
|
mieszana: 'mixed'
|
||||||
|
};
|
||||||
|
|
||||||
|
function filterStatusSection(
|
||||||
|
filters: Record<string, any>,
|
||||||
|
{ dispatcherStatus, dispatcherTimestamp }: ActiveScenery
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
(filters['endingStatus'] && dispatcherStatus == Status.ActiveDispatcher.ENDING) ||
|
||||||
|
(filters['unavailableStatus'] &&
|
||||||
|
(dispatcherStatus == Status.ActiveDispatcher.UNAVAILABLE ||
|
||||||
|
dispatcherStatus == Status.ActiveDispatcher.NOT_LOGGED_IN)) ||
|
||||||
|
(filters['afkStatus'] && dispatcherStatus == Status.ActiveDispatcher.AFK) ||
|
||||||
|
(filters['noSpaceStatus'] && dispatcherStatus == Status.ActiveDispatcher.NO_SPACE) ||
|
||||||
|
(filters['occupied'] && dispatcherStatus != Status.ActiveDispatcher.FREE) ||
|
||||||
|
(filters['onlineFromHours'] > 0 &&
|
||||||
|
(dispatcherTimestamp ?? 0) <= Date.now() + filters['onlineFromHours'] * 3600000)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterTimetablesSection(filters: Record<string, any>, station: Station) {
|
||||||
|
return (
|
||||||
|
(filters['withoutActiveTimetables'] &&
|
||||||
|
(!station.onlineInfo || station.onlineInfo.scheduledTrainCount.all == 0)) ||
|
||||||
|
(filters['withActiveTimetables'] &&
|
||||||
|
station.onlineInfo &&
|
||||||
|
(station.onlineInfo.scheduledTrainCount.all != 0 ||
|
||||||
|
station.onlineInfo.dispatcherStatus == Status.ActiveDispatcher.FREE))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterAccessibilitySection(filters: Record<string, any>, station: Station) {
|
||||||
|
if (
|
||||||
|
filters['nonPublic'] &&
|
||||||
|
(!station.generalInfo || station.generalInfo.availability == 'nonPublic')
|
||||||
|
)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (!station.generalInfo) return false;
|
||||||
|
|
||||||
|
const { availability } = station.generalInfo;
|
||||||
|
|
||||||
|
return (
|
||||||
|
(filters['unavailable'] && availability == 'unavailable' && !station.onlineInfo) ||
|
||||||
|
(filters['abandoned'] && availability == 'abandoned' && !station.onlineInfo) ||
|
||||||
|
(filters['default'] && availability == 'default') ||
|
||||||
|
(filters['notDefault'] &&
|
||||||
|
availability != 'default' &&
|
||||||
|
availability != 'abandoned' &&
|
||||||
|
availability != 'unavailable')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterRealitySection(filters: Record<string, any>, generalInfo: StationGeneralInfo) {
|
||||||
|
return (filters['real'] && generalInfo.lines) || (filters['fictional'] && !generalInfo.lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterProgramsSection(filters: Record<string, any>, generalInfo: StationGeneralInfo) {
|
||||||
|
return (
|
||||||
|
(filters['SUP'] && generalInfo.SUP) ||
|
||||||
|
(filters['noSUP'] && !generalInfo.SUP) ||
|
||||||
|
(filters['ASDEK'] && generalInfo.ASDEK) ||
|
||||||
|
(filters['noASDEK'] && !generalInfo.ASDEK)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterControlsSection(filters: Record<string, any>, generalInfo: StationGeneralInfo) {
|
||||||
|
return (
|
||||||
|
filters[generalInfo.controlType] == true ||
|
||||||
|
filters[filtersAssociations[generalInfo.controlType]] == true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterSignalsSection(filters: Record<string, any>, generalInfo: StationGeneralInfo) {
|
||||||
|
return (
|
||||||
|
filters[generalInfo.signalType] == true ||
|
||||||
|
filters[filtersAssociations[generalInfo.signalType]] == true ||
|
||||||
|
(filters['SBL'] && generalInfo.routes.sblNames.length > 0) ||
|
||||||
|
(filters['PBL'] && generalInfo.routes.sblNames.length == 0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterStationType(filters: Record<string, any>, generalInfo: StationGeneralInfo) {
|
||||||
|
const singleTracks = generalInfo.routes.single.filter((r) => !r.isInternal);
|
||||||
|
const doubleTracks = generalInfo.routes.double.filter((r) => !r.isInternal);
|
||||||
|
|
||||||
|
let isJunction = singleTracks.length > 0 && doubleTracks.length > 0;
|
||||||
|
|
||||||
|
return (filters['junction'] && isJunction) || (filters['nonJunction'] && !isJunction);
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterSliderValues(filters: Record<string, any>, generalInfo: StationGeneralInfo) {
|
||||||
|
const { availability, reqLevel, routes } = generalInfo;
|
||||||
|
|
||||||
|
const otherAvailability =
|
||||||
|
availability == 'nonPublic' || availability == 'unavailable' || availability == 'abandoned';
|
||||||
|
|
||||||
|
return (
|
||||||
|
filters['minLevel'] > reqLevel + (otherAvailability ? 1 : 0) ||
|
||||||
|
filters['maxLevel'] < reqLevel + (otherAvailability ? 1 : 0) ||
|
||||||
|
filters['minVmax'] > routes.maxRouteSpeed ||
|
||||||
|
filters['maxVmax'] < routes.minRouteSpeed ||
|
||||||
|
(filters['no-1track'] && routes.single.length != 0) ||
|
||||||
|
(filters['no-2track'] && routes.double.length != 0) ||
|
||||||
|
filters['minOneWayCatenary'] > routes.singleElectrifiedNames.length ||
|
||||||
|
filters['minOneWay'] > routes.singleOtherNames.length ||
|
||||||
|
filters['minTwoWayCatenary'] > routes.doubleElectrifiedNames.length ||
|
||||||
|
filters['minTwoWay'] > routes.doubleOtherNames.length
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterInputValues(filters: Record<string, any>, generalInfo: StationGeneralInfo) {
|
||||||
|
return (
|
||||||
|
filters['authors'].length > 3 &&
|
||||||
|
!generalInfo.authors
|
||||||
|
?.map((a) => a.toLocaleLowerCase())
|
||||||
|
.includes(filters['authors'].toLocaleLowerCase())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const sortStations = (a: Station, b: Station, sorter: ActiveSorter) => {
|
||||||
|
let diff = 0;
|
||||||
|
|
||||||
|
switch (sorter.headerName) {
|
||||||
|
case 'station':
|
||||||
|
return sorter.dir == 1 ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name);
|
||||||
|
|
||||||
|
case 'min-lvl':
|
||||||
|
diff = (a.generalInfo?.reqLevel || 0) - (b.generalInfo?.reqLevel || 0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'status':
|
||||||
|
diff =
|
||||||
|
(a.onlineInfo?.dispatcherTimestamp ??
|
||||||
|
dispatcherStatusPriority.indexOf(a.onlineInfo?.dispatcherStatus)) -
|
||||||
|
(b.onlineInfo?.dispatcherTimestamp ??
|
||||||
|
dispatcherStatusPriority.indexOf(b.onlineInfo?.dispatcherStatus));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'dispatcher':
|
||||||
|
if (
|
||||||
|
(a.onlineInfo?.dispatcherName.toLowerCase() || '') >
|
||||||
|
(b.onlineInfo?.dispatcherName.toLowerCase() || '')
|
||||||
|
)
|
||||||
|
return sorter.dir;
|
||||||
|
if (
|
||||||
|
(a.onlineInfo?.dispatcherName.toLowerCase() || '') <
|
||||||
|
(b.onlineInfo?.dispatcherName.toLowerCase() || '')
|
||||||
|
)
|
||||||
|
return -sorter.dir;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'dispatcher-lvl':
|
||||||
|
diff = (a.onlineInfo?.dispatcherExp || 0) - (b.onlineInfo?.dispatcherExp || 0);
|
||||||
|
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':
|
||||||
|
diff =
|
||||||
|
(b.onlineInfo?.stationTrains ? b.onlineInfo.stationTrains.length : -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;
|
||||||
|
|
||||||
|
case 'spawn':
|
||||||
|
diff =
|
||||||
|
(a.onlineInfo ? a.onlineInfo.spawns.length : -1) -
|
||||||
|
(b.onlineInfo ? b.onlineInfo.spawns.length : -1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'timetableConfirmed':
|
||||||
|
diff =
|
||||||
|
(a.onlineInfo?.scheduledTrainCount.confirmed ?? -1) -
|
||||||
|
(b.onlineInfo?.scheduledTrainCount.confirmed ?? -1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'timetableUnconfirmed':
|
||||||
|
diff =
|
||||||
|
(a.onlineInfo?.scheduledTrainCount.unconfirmed ?? -1) -
|
||||||
|
(b.onlineInfo?.scheduledTrainCount.unconfirmed ?? -1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'timetableAll':
|
||||||
|
diff =
|
||||||
|
(a.onlineInfo?.scheduledTrainCount.all ?? -1) -
|
||||||
|
(b.onlineInfo?.scheduledTrainCount.all ?? -1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (diff != 0) return Math.sign(diff) * sorter.dir;
|
||||||
|
return a.name.localeCompare(b.name);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const filterStations = (station: Station, filters: Record<string, any>) => {
|
||||||
|
if (filters['free'] && (!station.onlineInfo || station.onlineInfo.dispatcherId == -1))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Scenery Timetables section
|
||||||
|
if (filterTimetablesSection(filters, station)) return false;
|
||||||
|
|
||||||
|
// Scenery Accessibility section
|
||||||
|
if (filterAccessibilitySection(filters, station)) return false;
|
||||||
|
|
||||||
|
// Scenery Status section
|
||||||
|
if (station.onlineInfo && filterStatusSection(filters, station.onlineInfo)) return false;
|
||||||
|
|
||||||
|
if (station.generalInfo) {
|
||||||
|
// Scenery Reality section
|
||||||
|
if (filterRealitySection(filters, station.generalInfo)) return false;
|
||||||
|
|
||||||
|
// Scenery Additional Programs section
|
||||||
|
if (filterProgramsSection(filters, station.generalInfo)) return false;
|
||||||
|
|
||||||
|
// Scenery Controls section
|
||||||
|
if (filterControlsSection(filters, station.generalInfo)) return false;
|
||||||
|
|
||||||
|
// Scenery Signalling section(s)
|
||||||
|
if (filterSignalsSection(filters, station.generalInfo)) return false;
|
||||||
|
|
||||||
|
// Scenery Station Type section
|
||||||
|
if (filterStationType(filters, station.generalInfo)) return false;
|
||||||
|
|
||||||
|
// Scenery sliders
|
||||||
|
if (filterSliderValues(filters, station.generalInfo)) return false;
|
||||||
|
|
||||||
|
// Scenery Authors section
|
||||||
|
if (filterInputValues(filters, station.generalInfo)) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
@@ -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,71 @@
|
|||||||
|
<template>
|
||||||
|
<div class="tooltip" 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 (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 { Train } 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 Train[];
|
||||||
|
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,135 @@
|
|||||||
|
<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/${vehicleName}--300px.jpg`"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div v-if="imageState == 'error'" class="error-placeholder"></div>
|
||||||
|
|
||||||
|
<div class="vehicle-name">
|
||||||
|
{{ vehicleName.replace(/_/g, ' ') }}
|
||||||
|
<span v-if="vehicleCargo">({{ vehicleCargo.id }})</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="vehicle-props" v-if="vehicleData">
|
||||||
|
{{ vehicleData.group.speed }}km/h • {{ vehicleData.group.length }}m •
|
||||||
|
{{ (vehicleData.group.weight / 1000).toFixed(1) }}t
|
||||||
|
<span v-if="vehicleCargo">(+{{ (vehicleCargo.weight / 1000).toFixed(1) }}t)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import { useTooltipStore } from '../../store/tooltipStore';
|
||||||
|
import { useApiStore } from '../../store/apiStore';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tooltipStore: useTooltipStore(),
|
||||||
|
apiStore: useApiStore(),
|
||||||
|
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';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
vehicleName() {
|
||||||
|
return this.tooltipStore.content.split(':')[0];
|
||||||
|
},
|
||||||
|
|
||||||
|
vehicleData() {
|
||||||
|
return this.apiStore.vehiclesData?.find((v) => v.name == this.vehicleName);
|
||||||
|
},
|
||||||
|
|
||||||
|
vehicleCargo() {
|
||||||
|
return this.vehicleData?.group.cargoTypes?.find(
|
||||||
|
(c) => c.id == this.tooltipStore.content.split(':')[1]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// vehicleProps() {
|
||||||
|
// const vehicleDataArray = this.apiStore.vehiclesData?.vehicleList.find(
|
||||||
|
// ([name]) => name === this.vehicleName
|
||||||
|
// );
|
||||||
|
|
||||||
|
// if (!vehicleDataArray) return null;
|
||||||
|
|
||||||
|
// return (
|
||||||
|
// this.apiStore.vehiclesData!.vehicleProps.find((v) => v.type == vehicleDataArray[1]) ?? null
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</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;
|
||||||
|
text-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vehicle-props {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-placeholder {
|
||||||
|
height: 176px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<span class="stop-label" :data-sbl="stop.isSBL">
|
<span
|
||||||
|
class="stop-label"
|
||||||
|
:data-minor="stop.isSBL || (stop.nameRaw.endsWith(', po.') && !stop.duration)"
|
||||||
|
>
|
||||||
<span class="name" v-html="stop.nameHtml"></span>
|
<span class="name" v-html="stop.nameHtml"></span>
|
||||||
|
|
||||||
<span
|
<span
|
||||||
@@ -15,6 +18,7 @@
|
|||||||
: ''
|
: ''
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
|
p.
|
||||||
<span v-if="stop.arrivalDelay != 0 && stop.status != 'unconfirmed'">
|
<span v-if="stop.arrivalDelay != 0 && stop.status != 'unconfirmed'">
|
||||||
<s>{{ timestampToString(stop.arrivalScheduled) }}</s>
|
<s>{{ timestampToString(stop.arrivalScheduled) }}</s>
|
||||||
{{ timestampToString(stop.arrivalReal) }}
|
{{ timestampToString(stop.arrivalReal) }}
|
||||||
@@ -29,16 +33,17 @@
|
|||||||
<span
|
<span
|
||||||
v-if="
|
v-if="
|
||||||
stop.duration ||
|
stop.duration ||
|
||||||
(stop.status == 'stopped' && stop.position != 'begin') ||
|
(stop.status == 'stopped' && stop.position != 'begin' && stop.departureDelay > 0)
|
||||||
stop.departureDelay != stop.arrivalDelay
|
|
||||||
"
|
"
|
||||||
class="date stop"
|
class="date stop"
|
||||||
:data-stop-types="stop.type.replace(', ', '-')"
|
:data-stop-types="stop.type.replace(', ', '-')"
|
||||||
:data-stop-status="
|
:data-stop-status="stop.departureDelay > 0 && !stop.duration ? 'delayed' : ''"
|
||||||
stop.departureDelay - stop.arrivalDelay > 0 && !stop.duration ? 'delayed' : ''
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
{{ stop.duration || stop.departureDelay - stop.arrivalDelay }}
|
{{
|
||||||
|
stop.duration == 0 && stop.departureDelay > 0
|
||||||
|
? stop.departureDelay - stop.arrivalDelay
|
||||||
|
: stop.duration
|
||||||
|
}}
|
||||||
{{ stop.type == '' ? 'pt' : stop.type }}
|
{{ stop.type == '' ? 'pt' : stop.type }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@@ -58,7 +63,10 @@
|
|||||||
: ''
|
: ''
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<span v-if="stop.departureDelay != 0 && stop.status == 'confirmed'">
|
o.
|
||||||
|
<span
|
||||||
|
v-if="stop.departureDelay != 0 && (stop.status == 'confirmed' || stop.status == 'stopped')"
|
||||||
|
>
|
||||||
<s>{{ timestampToString(stop.departureScheduled) }}</s>
|
<s>{{ timestampToString(stop.departureScheduled) }}</s>
|
||||||
{{ timestampToString(stop.departureReal) }}
|
{{ timestampToString(stop.departureReal) }}
|
||||||
|
|
||||||
@@ -95,14 +103,14 @@ $delayedClr: salmon;
|
|||||||
$dateClr: #525151;
|
$dateClr: #525151;
|
||||||
$stopExchangeClr: #db8e29;
|
$stopExchangeClr: #db8e29;
|
||||||
$stopDefaultClr: #252525;
|
$stopDefaultClr: #252525;
|
||||||
$stopNameClr: #22a8d1;
|
$stopNameClr: #303030;
|
||||||
|
|
||||||
.stop-label {
|
.stop-label {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
&[data-sbl='true'] {
|
&[data-minor='true'] {
|
||||||
.date {
|
.date {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -116,6 +124,7 @@ $stopNameClr: #22a8d1;
|
|||||||
|
|
||||||
.name {
|
.name {
|
||||||
background: $stopNameClr;
|
background: $stopNameClr;
|
||||||
|
border-radius: 0.5em 0 0 0.5em;
|
||||||
padding: 0.3em 0.5em;
|
padding: 0.3em 0.5em;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -129,6 +138,10 @@ $stopNameClr: #22a8d1;
|
|||||||
.date {
|
.date {
|
||||||
background: $dateClr;
|
background: $dateClr;
|
||||||
padding: 0.3em 0.5em;
|
padding: 0.3em 0.5em;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-radius: 0 0.5em 0.5em 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.stop {
|
.stop {
|
||||||
@@ -149,7 +162,7 @@ $stopNameClr: #22a8d1;
|
|||||||
.departure {
|
.departure {
|
||||||
&[data-status='delayed'] {
|
&[data-status='delayed'] {
|
||||||
s {
|
s {
|
||||||
color: #999;
|
color: #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
span {
|
span {
|
||||||
@@ -159,7 +172,7 @@ $stopNameClr: #22a8d1;
|
|||||||
|
|
||||||
&[data-status='preponed'] {
|
&[data-status='preponed'] {
|
||||||
s {
|
s {
|
||||||
color: #999;
|
color: #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
span {
|
span {
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -36,25 +45,42 @@
|
|||||||
<div class="train-driver">
|
<div class="train-driver">
|
||||||
<b
|
<b
|
||||||
v-if="apiStore.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">
|
||||||
@@ -91,30 +117,56 @@
|
|||||||
</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
|
||||||
|
|
||||||
|
<span v-if="stockSpeedLimit != Infinity">
|
||||||
|
•
|
||||||
|
<em
|
||||||
|
class="text--grayed"
|
||||||
|
style="text-decoration: underline dotted"
|
||||||
|
tabindex="0"
|
||||||
|
:data-tooltip="$t('trains.vmax-tooltip')"
|
||||||
|
>
|
||||||
|
{{ stockSpeedLimit }} km/h
|
||||||
|
</em>
|
||||||
|
</span>
|
||||||
|
</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>
|
||||||
@@ -123,15 +175,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 { useMainStore } from '../../store/mainStore';
|
||||||
import { useApiStore } from '../../store/apiStore';
|
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: {
|
||||||
@@ -139,8 +192,7 @@ export default defineComponent({
|
|||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
extended: {
|
extended: {
|
||||||
type: Boolean,
|
type: Boolean
|
||||||
default: true
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -149,25 +201,43 @@ export default defineComponent({
|
|||||||
store: useMainStore(),
|
store: useMainStore(),
|
||||||
apiStore: useApiStore()
|
apiStore: useApiStore()
|
||||||
};
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
stockSpeedLimit() {
|
||||||
|
return this.train.stockList.reduce((acc, stockName) => {
|
||||||
|
const vehicleSpeed =
|
||||||
|
this.apiStore.vehiclesData?.find((v) => v.name == stockName.split(':')[0])?.group.speed ??
|
||||||
|
300;
|
||||||
|
|
||||||
|
return Math.min(vehicleSpeed, acc);
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
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 {
|
||||||
@@ -178,7 +248,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 {
|
||||||
@@ -186,6 +256,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;
|
||||||
@@ -220,14 +294,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;
|
||||||
@@ -236,6 +325,27 @@ export default defineComponent({
|
|||||||
gap: 0.25em;
|
gap: 0.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.general-stats {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5em;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
& > div {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 1.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.general-timetable {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.status-badges {
|
.status-badges {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -247,17 +357,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.general-timetable {
|
.status-timetable-progress {
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timetable-warnings {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timetable-progress {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -267,32 +367,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>
|
||||||
@@ -17,54 +13,40 @@ import { defineComponent } from 'vue';
|
|||||||
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
||||||
import TrainInfo from './TrainInfo.vue';
|
import TrainInfo from './TrainInfo.vue';
|
||||||
import TrainSchedule from './TrainSchedule.vue';
|
import TrainSchedule from './TrainSchedule.vue';
|
||||||
|
import { Train } from '../../typings/common';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { TrainInfo, TrainSchedule },
|
components: { TrainInfo, TrainSchedule },
|
||||||
mixins: [modalTrainMixin],
|
mixins: [modalTrainMixin],
|
||||||
|
|
||||||
activated() {
|
computed: {
|
||||||
const contentEl = this.$refs['content'] as HTMLElement;
|
chosenTrain() {
|
||||||
|
return this.store.trainList.find((train) => train.modalId == this.store.chosenModalTrainId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
chosenTrain(train: Train | undefined) {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
|
if (train) {
|
||||||
|
document.body.classList.add('no-scroll');
|
||||||
|
const contentEl = this.$refs['content'] as HTMLElement;
|
||||||
contentEl.focus();
|
contentEl.focus();
|
||||||
|
} else {
|
||||||
|
(this.store.modalLastClickedTarget as any)?.focus();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
document.body.classList.remove('no-scroll');
|
||||||
|
}, 90);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../styles/responsive.scss';
|
@import '../../styles/responsive.scss';
|
||||||
@import '../../styles/card.scss';
|
|
||||||
|
|
||||||
.top-info-bar-anim {
|
|
||||||
&-enter-active,
|
|
||||||
&-leave-active {
|
|
||||||
transition: all 150ms ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-enter-from,
|
|
||||||
&-leave-to {
|
|
||||||
transform: translate(-50%, -50%) scale(0.8);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.exit {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
|
|
||||||
margin: 0.5em 1em;
|
|
||||||
|
|
||||||
padding: 0.25em;
|
|
||||||
|
|
||||||
z-index: 201;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 1.5rem;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.train-modal {
|
.train-modal {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -72,17 +54,19 @@ export default defineComponent({
|
|||||||
left: 0;
|
left: 0;
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
color: white;
|
color: white;
|
||||||
z-index: 200;
|
z-index: 200;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal_background {
|
.modal-background {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@@ -94,14 +78,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;
|
|
||||||
|
|
||||||
width: 95vw;
|
width: 95vw;
|
||||||
max-height: 96vh;
|
max-height: 95vh;
|
||||||
|
max-height: 95dvh;
|
||||||
|
margin-top: 1em;
|
||||||
|
|
||||||
background-color: #1a1a1a;
|
background-color: #1a1a1a;
|
||||||
box-shadow: 0 0 15px 10px #0e0e0e;
|
box-shadow: 0 0 15px 10px #0e0e0e;
|
||||||
@@ -116,10 +100,4 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@include smallScreen {
|
|
||||||
.modal_content {
|
|
||||||
max-height: 85vh;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<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>
|
||||||
|
|
||||||
@@ -81,7 +81,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="filter-actions">
|
<div class="filter-actions">
|
||||||
<div></div>
|
|
||||||
<button class="btn--action" @click="resetAllFilters">
|
<button class="btn--action" @click="resetAllFilters">
|
||||||
{{ $t('options.filter-reset') }}
|
{{ $t('options.filter-reset') }}
|
||||||
</button>
|
</button>
|
||||||
@@ -223,9 +222,6 @@ export default defineComponent({
|
|||||||
|
|
||||||
.filter-actions {
|
.filter-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.5em;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
|
|
||||||
> * {
|
> * {
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
:data-stop-type="stop.type"
|
:data-stop-type="stop.type"
|
||||||
:data-minor-stop-active="stop.isActive"
|
:data-minor-stop-active="stop.isActive"
|
||||||
:data-last-confirmed="stop.isLastConfirmed"
|
:data-last-confirmed="stop.isLastConfirmed"
|
||||||
x
|
|
||||||
>
|
>
|
||||||
<span class="stop_info">
|
<span class="stop_info">
|
||||||
<span class="distance">
|
<span class="distance">
|
||||||
@@ -30,19 +29,12 @@
|
|||||||
<StopLabel :stop="stop" />
|
<StopLabel :stop="stop" />
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div class="stop_line" v-if="i < scheduleStops.length - 1">
|
<div class="stop_line">
|
||||||
<!-- Grid placeholder -->
|
<!-- Grid placeholder -->
|
||||||
<div>
|
<div></div>
|
||||||
<!-- <div class="speed-departure" v-if="stop.currentDepartureRoute">
|
|
||||||
{{ stop.currentDepartureRoute.routeSpeed }}
|
|
||||||
</div>
|
|
||||||
<div class="speed-next-arrival" v-if="stop.nextArrivalRoute">
|
|
||||||
{{ stop.nextArrivalRoute.routeSpeed }}
|
|
||||||
</div> -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="progress">
|
<div class="progress">
|
||||||
<div class="line line_connection"></div>
|
<div class="line line_connection" v-if="i < scheduleStops.length - 1"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bottom-line-info">
|
<div class="bottom-line-info">
|
||||||
@@ -55,26 +47,46 @@
|
|||||||
<span
|
<span
|
||||||
v-if="
|
v-if="
|
||||||
stop.departureLine &&
|
stop.departureLine &&
|
||||||
stop.departureLine == scheduleStops[i + 1]?.arrivalLine &&
|
scheduleStops[i + 1] != undefined &&
|
||||||
!/sbl/gi.test(stop.departureLine)
|
!/-|_|(^it\d+)|(^sbl)/gi.test(stop.departureLine)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
{{ stop.departureLine }}
|
<div class="scenery-route">
|
||||||
|
<span>{{ stop.departureLine }}</span>
|
||||||
|
<span v-if="stop.departureLineInfo">
|
||||||
|
| {{ stop.departureLineInfo.routeSpeed }}
|
||||||
|
<span v-if="stop.departureLineInfo.isElectric">⚡</span>
|
||||||
|
<img
|
||||||
|
v-else
|
||||||
|
src="/images/icon-we4a.png"
|
||||||
|
:title="$t('trains.we4a-tooltip')"
|
||||||
|
width="12"
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span v-else-if="stop.departureLine && !/sbl/gi.test(stop.departureLine)">
|
|
||||||
<div>{{ stop.departureLine }}</div>
|
|
||||||
<div
|
|
||||||
class="scenery-change-name"
|
|
||||||
v-if="
|
|
||||||
i < scheduleStops.length - 1 &&
|
|
||||||
stop.sceneryName != scheduleStops[i + 1].sceneryName
|
|
||||||
"
|
|
||||||
>
|
|
||||||
{{ scheduleStops[i + 1].sceneryName }}
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
{{ scheduleStops[i + 1].arrivalLine }}
|
<div
|
||||||
|
v-if="stop.sceneryName != scheduleStops[i + 1]?.sceneryName"
|
||||||
|
class="scenery-change-name"
|
||||||
|
>
|
||||||
|
<span>{{ scheduleStops[i + 1].sceneryName }}</span>
|
||||||
|
<span v-if="stop.departureLineInfo?.routeTracks == 1"> ↕</span>
|
||||||
|
<span v-else> ⇅</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="scenery-route">
|
||||||
|
<span> {{ scheduleStops[i + 1].arrivalLine }}</span>
|
||||||
|
|
||||||
|
<span v-if="scheduleStops[i + 1].arrivalLineInfo">
|
||||||
|
| {{ scheduleStops[i + 1].arrivalLineInfo!.routeSpeed }}
|
||||||
|
<span v-if="scheduleStops[i + 1].arrivalLineInfo!.isElectric">⚡</span>
|
||||||
|
<img
|
||||||
|
v-else
|
||||||
|
src="/images/icon-we4a.png"
|
||||||
|
:title="$t('trains.we4a-tooltip')"
|
||||||
|
width="12"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -88,11 +100,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { 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 StopLabel from './StopLabel.vue';
|
||||||
import StockList from '../Global/StockList.vue';
|
import StockList from '../Global/StockList.vue';
|
||||||
import { useMainStore } from '../../store/mainStore';
|
import { useMainStore } from '../../store/mainStore';
|
||||||
import { useApiStore } from '../../store/apiStore';
|
import { useApiStore } from '../../store/apiStore';
|
||||||
|
import { StationRoutesInfo, Train } from '../../typings/common';
|
||||||
|
|
||||||
export interface TrainScheduleStop {
|
export interface TrainScheduleStop {
|
||||||
nameHtml: string;
|
nameHtml: string;
|
||||||
@@ -118,19 +130,16 @@ export interface TrainScheduleStop {
|
|||||||
isSBL: boolean;
|
isSBL: boolean;
|
||||||
|
|
||||||
sceneryName: string | null;
|
sceneryName: string | null;
|
||||||
sceneryHash: string;
|
|
||||||
distance: number;
|
distance: number;
|
||||||
|
|
||||||
// arrivalTrackCount: number;
|
|
||||||
// departureTrackCount: number;
|
|
||||||
|
|
||||||
// currentArrivalRoute?: StationRoutesInfo;
|
|
||||||
// currentDepartureRoute?: StationRoutesInfo;
|
|
||||||
// nextArrivalRoute?: StationRoutesInfo;
|
|
||||||
|
|
||||||
arrivalLine: string | null;
|
arrivalLine: string | null;
|
||||||
departureLine: string | null;
|
departureLine: string | null;
|
||||||
|
|
||||||
|
arrivalLineInfo?: StationRoutesInfo;
|
||||||
|
departureLineInfo?: StationRoutesInfo;
|
||||||
|
|
||||||
|
isExternal: boolean;
|
||||||
|
|
||||||
comments: string | null;
|
comments: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,45 +166,28 @@ export default defineComponent({
|
|||||||
computed: {
|
computed: {
|
||||||
scheduleStops(): TrainScheduleStop[] {
|
scheduleStops(): TrainScheduleStop[] {
|
||||||
let currentSceneryIndex = 0;
|
let currentSceneryIndex = 0;
|
||||||
// let lastDepartureTrackCount = 2;
|
|
||||||
// let lastArrivalTrackCount = 2;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
this.train.timetableData?.followingStops.map((stop, i, arr) => {
|
this.train.timetableData?.followingStops.map((stop, i, arr) => {
|
||||||
if (
|
const isExternal =
|
||||||
i > 0 &&
|
i > 0 &&
|
||||||
stop.arrivalLine &&
|
stop.arrivalLine != null &&
|
||||||
stop.arrivalLine != arr[i - 1].departureLine &&
|
(stop.arrivalLine != arr[i - 1].departureLine ||
|
||||||
!/sbl/gi.test(stop.arrivalLine)
|
(stop.arrivalLine == arr[i - 1].departureLine &&
|
||||||
)
|
!/-|_|(^it\d+)|(^sbl)/gi.test(stop.arrivalLine)));
|
||||||
currentSceneryIndex++;
|
|
||||||
|
|
||||||
// const sceneryInfo = this.apiStore.sceneryData.find(
|
if (isExternal) currentSceneryIndex++;
|
||||||
// (sd) =>
|
|
||||||
// sd.name.toLocaleLowerCase() ==
|
|
||||||
// this.timetableSceneryNames[currentSceneryIndex].toLocaleLowerCase()
|
|
||||||
// );
|
|
||||||
|
|
||||||
// const nextSceneryInfo = this.apiStore.sceneryData.find(
|
const sceneryName = this.train.timetableData!.sceneryNames[currentSceneryIndex];
|
||||||
// (sd) =>
|
const sceneryInfo = this.apiStore.sceneryData.find((st) => st.name == sceneryName);
|
||||||
// sd.name.toLocaleLowerCase() ==
|
|
||||||
// this.timetableSceneryNames[currentSceneryIndex + 1]?.toLocaleLowerCase()
|
|
||||||
// );
|
|
||||||
|
|
||||||
// const currentDepartureRoute = sceneryInfo?.routesInfo.find(
|
const arrivalLineInfo = sceneryInfo?.routesInfo.find(
|
||||||
// (r) => r.routeName == stop.departureLine
|
(r) => r.routeName == stop.arrivalLine
|
||||||
// );
|
);
|
||||||
|
|
||||||
// const currentArrivalRoute = sceneryInfo?.routesInfo.find(
|
const departureLineInfo = sceneryInfo?.routesInfo.find(
|
||||||
// (r) => r.routeName == stop.arrivalLine
|
(r) => r.routeName == stop.departureLine
|
||||||
// );
|
);
|
||||||
|
|
||||||
// const nextArrivalRoute = nextSceneryInfo?.routesInfo.find(
|
|
||||||
// (r) => r.routeName == arr[i + 1]?.arrivalLine
|
|
||||||
// );
|
|
||||||
|
|
||||||
// lastDepartureTrackCount = currentDepartureRoute?.routeTracks ?? lastDepartureTrackCount;
|
|
||||||
// lastArrivalTrackCount = currentArrivalRoute?.routeTracks ?? lastArrivalTrackCount;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
nameHtml: stop.stopName,
|
nameHtml: stop.stopName,
|
||||||
@@ -217,15 +209,10 @@ export default defineComponent({
|
|||||||
arrivalLine: stop.arrivalLine,
|
arrivalLine: stop.arrivalLine,
|
||||||
departureLine: stop.departureLine,
|
departureLine: stop.departureLine,
|
||||||
|
|
||||||
// arrivalSpeed: nextArrivalRoute?.routeSpeed ?? null,
|
arrivalLineInfo: arrivalLineInfo,
|
||||||
// departureSpeed: currentDepartureRoute?.routeSpeed ?? null,
|
departureLineInfo: departureLineInfo,
|
||||||
|
|
||||||
// arrivalTrackCount: currentArrivalRoute?.routeTracks ?? lastArrivalTrackCount,
|
isExternal,
|
||||||
// departureTrackCount: currentDepartureRoute?.routeTracks ?? lastDepartureTrackCount,
|
|
||||||
|
|
||||||
// currentArrivalRoute,
|
|
||||||
// currentDepartureRoute,
|
|
||||||
// nextArrivalRoute,
|
|
||||||
|
|
||||||
type: stop.stopType,
|
type: stop.stopType,
|
||||||
distance: stop.stopDistance,
|
distance: stop.stopDistance,
|
||||||
@@ -233,8 +220,7 @@ export default defineComponent({
|
|||||||
isLastConfirmed: this.lastConfirmed === i && !stop.terminatesHere,
|
isLastConfirmed: this.lastConfirmed === i && !stop.terminatesHere,
|
||||||
isSBL: /sbl/gi.test(stop.stopName),
|
isSBL: /sbl/gi.test(stop.stopName),
|
||||||
position: stop.beginsHere ? 'begin' : stop.terminatesHere ? 'end' : 'en-route',
|
position: stop.beginsHere ? 'begin' : stop.terminatesHere ? 'end' : 'en-route',
|
||||||
sceneryHash: '',
|
sceneryName,
|
||||||
sceneryName: this.timetableSceneryNames[currentSceneryIndex],
|
|
||||||
status: stop.confirmed ? 'confirmed' : stop.stopped ? 'stopped' : 'unconfirmed'
|
status: stop.confirmed ? 'confirmed' : stop.stopped ? 'stopped' : 'unconfirmed'
|
||||||
};
|
};
|
||||||
}) ?? []
|
}) ?? []
|
||||||
@@ -269,19 +255,6 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return activeMinorStopList;
|
return activeMinorStopList;
|
||||||
},
|
|
||||||
|
|
||||||
timetableSceneryNames() {
|
|
||||||
if (!this.train.timetableData?.sceneries) return [];
|
|
||||||
|
|
||||||
return this.train.timetableData?.sceneries
|
|
||||||
.map(
|
|
||||||
(sceneryHash) =>
|
|
||||||
this.store.onlineSceneryList.find((st) => st.hash === sceneryHash)?.name ??
|
|
||||||
this.apiStore.sceneryData.find((sd) => sd.hash === sceneryHash)?.name ??
|
|
||||||
sceneryHash
|
|
||||||
)
|
|
||||||
.reverse();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -549,8 +522,13 @@ $blinkAnim: 0.5s ease-in-out alternate infinite blink;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-line-info {
|
.scenery-route {
|
||||||
.scenery-change-name {
|
img {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scenery-change-name {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 0.25em 0;
|
margin: 0.25em 0;
|
||||||
|
|
||||||
@@ -565,6 +543,5 @@ $blinkAnim: 0.5s ease-in-out alternate infinite blink;
|
|||||||
right: calc(100% + 5px);
|
right: calc(100% + 5px);
|
||||||
transform: translate(0, -50%);
|
transform: translate(0, -50%);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,26 +1,27 @@
|
|||||||
<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="apiStore.dataStatuses.connection">
|
<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="apiStore.dataStatuses.connection == Status.Loading" key="loading" />
|
<Loading v-else-if="apiStore.dataStatuses.connection == Status.Loading" key="loading" />
|
||||||
|
|
||||||
<div class="table-info" key="no-trains" v-else-if="trains.length == 0">
|
<div class="table-warning" key="no-trains" v-else-if="trains.length == 0">
|
||||||
{{ $t('trains.no-trains') }}
|
{{ $t('trains.no-trains') }} (region: <b>{{ store.region.name }}</b
|
||||||
|
>)
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<transition-group name="list-anim" tag="ul">
|
<transition-group name="list-anim" tag="ul">
|
||||||
<li
|
<li
|
||||||
class="train-row"
|
class="train-row"
|
||||||
v-for="train in trains"
|
v-for="train in trains"
|
||||||
:key="train.trainId"
|
:key="train.id"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@click.stop="selectModalTrain(train.trainId, $event.currentTarget)"
|
@click.stop="selectModalTrain(train, $event.currentTarget)"
|
||||||
@keydown.enter="selectModalTrain(train.trainId, $event.currentTarget)"
|
@keydown.enter="selectModalTrain(train, $event.currentTarget)"
|
||||||
>
|
>
|
||||||
<TrainInfo :train="train" />
|
<TrainInfo :train="train" :extended="false" />
|
||||||
</li>
|
</li>
|
||||||
</transition-group>
|
</transition-group>
|
||||||
</div>
|
</div>
|
||||||
@@ -30,11 +31,10 @@
|
|||||||
<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 { useMainStore } 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';
|
import { useApiStore } from '../../store/apiStore';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
@@ -77,17 +77,6 @@ export default defineComponent({
|
|||||||
|
|
||||||
return Status.Data.Loaded;
|
return Status.Data.Loaded;
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
activated() {
|
|
||||||
const query = this.$route.query;
|
|
||||||
if (query.trainNo && query.driverName) {
|
|
||||||
this.searchedDriver = query.driverName.toString();
|
|
||||||
this.searchedTrain = query.trainNo.toString();
|
|
||||||
setTimeout(() => {
|
|
||||||
this.selectModalTrain(query.driverName! + query.trainNo!.toString());
|
|
||||||
}, 20);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -105,12 +94,11 @@ 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;
|
||||||
|
font-size: 1.25em;
|
||||||
font-size: 1.5em;
|
|
||||||
|
|
||||||
background: #1a1a1a;
|
background: #1a1a1a;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,301 +1,4 @@
|
|||||||
{
|
{
|
||||||
"optionSections": [
|
|
||||||
"reality",
|
|
||||||
"package-access",
|
|
||||||
"access",
|
|
||||||
"control",
|
|
||||||
"addons",
|
|
||||||
"blockades",
|
|
||||||
"signals",
|
|
||||||
"status"
|
|
||||||
],
|
|
||||||
|
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"id": "real",
|
|
||||||
"name": "real",
|
|
||||||
"section": "reality",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "fictional",
|
|
||||||
"name": "fictional",
|
|
||||||
"section": "reality",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "default",
|
|
||||||
"name": "default",
|
|
||||||
"section": "package-access",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "not-default",
|
|
||||||
"name": "notDefault",
|
|
||||||
"section": "package-access",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "non-public",
|
|
||||||
"name": "nonPublic",
|
|
||||||
"section": "access",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "unavailable",
|
|
||||||
"name": "unavailable",
|
|
||||||
"section": "access",
|
|
||||||
"value": false,
|
|
||||||
"defaultValue": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "abandoned",
|
|
||||||
"name": "abandoned",
|
|
||||||
"section": "access",
|
|
||||||
"value": false,
|
|
||||||
"defaultValue": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "SPK",
|
|
||||||
"name": "SPK",
|
|
||||||
"section": "control",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "SCS",
|
|
||||||
"name": "SCS",
|
|
||||||
"section": "control",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "SPE",
|
|
||||||
"name": "SPE",
|
|
||||||
"section": "control",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"id": "SPK-M",
|
|
||||||
"name": "mechaniczne+SPK",
|
|
||||||
"section": "control",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "SCS-M",
|
|
||||||
"name": "mechaniczne+SCS",
|
|
||||||
"section": "control",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "mechanical",
|
|
||||||
"name": "mechaniczne",
|
|
||||||
"section": "control",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "SPK-R",
|
|
||||||
"name": "ręczne+SPK",
|
|
||||||
"section": "control",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "SCS-R",
|
|
||||||
"name": "ręczne+SCS",
|
|
||||||
"section": "control",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "manual",
|
|
||||||
"name": "ręczne",
|
|
||||||
"section": "control",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "SUP",
|
|
||||||
"name": "SUP",
|
|
||||||
"section": "addons",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "noSUP",
|
|
||||||
"name": "noSUP",
|
|
||||||
"section": "addons",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "SBL",
|
|
||||||
"name": "SBL",
|
|
||||||
"section": "blockades",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "PBL",
|
|
||||||
"name": "PBL",
|
|
||||||
"section": "blockades",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "modern",
|
|
||||||
"name": "współczesna",
|
|
||||||
"section": "signals",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "semaphores",
|
|
||||||
"name": "kształtowa",
|
|
||||||
"section": "signals",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "mixed",
|
|
||||||
"name": "mieszana",
|
|
||||||
"section": "signals",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "historical",
|
|
||||||
"name": "historyczna",
|
|
||||||
"section": "signals",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"id": "free",
|
|
||||||
"name": "free",
|
|
||||||
|
|
||||||
"section": "status",
|
|
||||||
"value": false,
|
|
||||||
"defaultValue": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "occupied",
|
|
||||||
"name": "occupied",
|
|
||||||
|
|
||||||
"section": "status",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "endingStatus",
|
|
||||||
"name": "endingStatus",
|
|
||||||
|
|
||||||
"section": "status",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "afkStatus",
|
|
||||||
"name": "afkStatus",
|
|
||||||
|
|
||||||
"section": "status",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "noSpaceStatus",
|
|
||||||
"name": "noSpaceStatus",
|
|
||||||
|
|
||||||
"section": "status",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "unavailableStatus",
|
|
||||||
"name": "unavailableStatus",
|
|
||||||
|
|
||||||
"section": "status",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"sliders": [
|
|
||||||
{
|
|
||||||
"id": "min-lvl",
|
|
||||||
"name": "minLevel",
|
|
||||||
"minRange": 0,
|
|
||||||
"maxRange": 20,
|
|
||||||
"value": 0,
|
|
||||||
"defaultValue": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "max-lvl",
|
|
||||||
"name": "maxLevel",
|
|
||||||
"minRange": 0,
|
|
||||||
"maxRange": 20,
|
|
||||||
"value": 20,
|
|
||||||
"defaultValue": 20
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "routes-1t-cat",
|
|
||||||
"name": "minOneWayCatenary",
|
|
||||||
"minRange": 0,
|
|
||||||
"maxRange": 5,
|
|
||||||
"value": 0,
|
|
||||||
"defaultValue": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "routes-1t-other",
|
|
||||||
"name": "minOneWay",
|
|
||||||
"minRange": 0,
|
|
||||||
"maxRange": 5,
|
|
||||||
"value": 0,
|
|
||||||
"defaultValue": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "routes-2t-cat",
|
|
||||||
"name": "minTwoWayCatenary",
|
|
||||||
"minRange": 0,
|
|
||||||
"maxRange": 5,
|
|
||||||
"value": 0,
|
|
||||||
"defaultValue": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "routes-2t-other",
|
|
||||||
"name": "minTwoWay",
|
|
||||||
"minRange": 0,
|
|
||||||
"maxRange": 5,
|
|
||||||
"value": 0,
|
|
||||||
"defaultValue": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"modes": [
|
|
||||||
{
|
|
||||||
"id": "include-selected",
|
|
||||||
"name": "include-selected",
|
|
||||||
"section": "mode",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "save",
|
|
||||||
"name": "save",
|
|
||||||
"section": "mode",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"regions": [
|
"regions": [
|
||||||
{
|
{
|
||||||
"id": "eu",
|
"id": "eu",
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
import axios from 'axios';
|
|
||||||
|
|
||||||
const http = axios.create({
|
|
||||||
baseURL:
|
|
||||||
import.meta.env.VITE_API_MODE === 'development'
|
|
||||||
? 'http://localhost:3001'
|
|
||||||
: 'https://stacjownik.spythere.eu'
|
|
||||||
});
|
|
||||||
|
|
||||||
export default http;
|
|
||||||
@@ -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;
|
||||||
@@ -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",
|
||||||
@@ -25,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",
|
||||||
@@ -40,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!",
|
||||||
@@ -57,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",
|
||||||
@@ -89,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 ",
|
||||||
@@ -107,8 +112,8 @@
|
|||||||
"filters": "FILTERS",
|
"filters": "FILTERS",
|
||||||
"donate": "DONATE",
|
"donate": "DONATE",
|
||||||
|
|
||||||
"search-button": "Search",
|
"search-button": "SEARCH",
|
||||||
"reset-button": "Reset",
|
"reset-button": "RESET",
|
||||||
|
|
||||||
"sort-title": "SORT BY:",
|
"sort-title": "SORT BY:",
|
||||||
"filter-title": "FILTER BY:",
|
"filter-title": "FILTER BY:",
|
||||||
@@ -120,7 +125,9 @@
|
|||||||
"search-dispatcher": "Dispatcher name",
|
"search-dispatcher": "Dispatcher name",
|
||||||
"search-station": "Scenery name",
|
"search-station": "Scenery name",
|
||||||
"search-author": "Timetable author name",
|
"search-author": "Timetable author name",
|
||||||
"search-issuedFrom": "Origin scenery name",
|
"search-issuedFrom": "Issuing scenery name",
|
||||||
|
"search-via": "Via scenery name",
|
||||||
|
"search-terminatingAt": "Terminating scenery name",
|
||||||
"search-timetables-date": "Timetable date (UTC+2 / CEST)",
|
"search-timetables-date": "Timetable date (UTC+2 / CEST)",
|
||||||
"search-dispatchers-date": "Service date (UTC+2 / CEST)",
|
"search-dispatchers-date": "Service date (UTC+2 / CEST)",
|
||||||
"search-date": "Date (UTC+2 / CEST)",
|
"search-date": "Date (UTC+2 / CEST)",
|
||||||
@@ -169,16 +176,22 @@
|
|||||||
|
|
||||||
"sections": {
|
"sections": {
|
||||||
"quick": "QUICK FILTERS",
|
"quick": "QUICK FILTERS",
|
||||||
|
"stationType": "STATION TYPE",
|
||||||
"reality": "SCENERY REALITY",
|
"reality": "SCENERY REALITY",
|
||||||
"package-access": "IN-GAME AVAILABILITY",
|
"packageAccess": "IN-GAME AVAILABILITY",
|
||||||
"access": "GENERAL AVAILABILITY",
|
"access": "GENERAL AVAILABILITY",
|
||||||
"control": "CONTROLS",
|
"control": "CONTROLS",
|
||||||
"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"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"changed-filters-count": "Changed filters:",
|
||||||
|
"no-changed-filters": "No changed filters",
|
||||||
|
|
||||||
"all-available": "ALL AVAILABLE",
|
"all-available": "ALL AVAILABLE",
|
||||||
"all-free": "CURRENTLY FREE",
|
"all-free": "CURRENTLY FREE",
|
||||||
|
|
||||||
@@ -189,11 +202,11 @@
|
|||||||
|
|
||||||
"title": "STATION FILTERS",
|
"title": "STATION FILTERS",
|
||||||
"default": "IN-GAME",
|
"default": "IN-GAME",
|
||||||
"not-default": "ADDITIONAL",
|
"notDefault": "ADDITIONAL",
|
||||||
"real": "REAL",
|
"real": "REAL",
|
||||||
"fictional": "FICTIONAL",
|
"fictional": "FICTIONAL",
|
||||||
"unavailable": "UNSUPPORTED",
|
"unavailable": "UNSUPPORTED",
|
||||||
"non-public": "NON-PUBLIC",
|
"nonPublic": "NON-PUBLIC",
|
||||||
"abandoned": "ABANDONED",
|
"abandoned": "ABANDONED",
|
||||||
|
|
||||||
"SPK": "SPK",
|
"SPK": "SPK",
|
||||||
@@ -203,13 +216,15 @@
|
|||||||
"SCS-R": "SCS + MANUAL",
|
"SCS-R": "SCS + MANUAL",
|
||||||
"SCS-M": "SCS + MECH.",
|
"SCS-M": "SCS + MECH.",
|
||||||
"SPE": "SPE",
|
"SPE": "SPE",
|
||||||
|
|
||||||
"manual": "MANUAL",
|
"manual": "MANUAL",
|
||||||
"mechanical": "MECHANICAL",
|
"mechanical": "MECHANICAL",
|
||||||
|
|
||||||
"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)",
|
||||||
|
|
||||||
@@ -219,14 +234,24 @@
|
|||||||
"historical": "HISTORICAL",
|
"historical": "HISTORICAL",
|
||||||
"free": "FREE",
|
"free": "FREE",
|
||||||
"occupied": "OCCUPIED",
|
"occupied": "OCCUPIED",
|
||||||
|
|
||||||
|
"withActiveTimetables": "ACTIVE",
|
||||||
|
"withoutActiveTimetables": "NO ACTIVE",
|
||||||
|
|
||||||
|
"junction": "JUNCTIONS",
|
||||||
|
"nonJunction": "OTHER",
|
||||||
|
|
||||||
"sliders": {
|
"sliders": {
|
||||||
"min-lvl": "MIN. REQUIRED DISPATCHER LEVEL",
|
"minLevel": "MIN. REQUIRED DISPATCHER LEVEL",
|
||||||
"max-lvl": "MAX. REQUIRED DISPATCHER LEVEL",
|
"maxLevel": "MAX. REQUIRED DISPATCHER LEVEL",
|
||||||
"routes-1t-cat": "MIN. CATENARY SINGLE TRACK ROUTES",
|
"minVmax": "MIN. SCENERY ROUTE SPEED",
|
||||||
"routes-1t-other": "MIN. OTHER SINGLE TRACK ROUTES",
|
"maxVmax": "MAX. SCENERY ROUTE SPEED",
|
||||||
"routes-2t-cat": "MIN. CATENARY DOUBLE TRACK ROUTES",
|
"minOneWayCatenary": "MIN. CATENARY SINGLE TRACK ROUTES",
|
||||||
"routes-2t-other": "MIN. OTHER DOUBLE TRACK ROUTES"
|
"minOneWay": "MIN. OTHER SINGLE TRACK ROUTES",
|
||||||
|
"minTwoWayCatenary": "MIN. CATENARY DOUBLE TRACK ROUTES",
|
||||||
|
"minTwoWay": "MIN. OTHER DOUBLE TRACK ROUTES"
|
||||||
},
|
},
|
||||||
|
|
||||||
"authors-search": "SEARCH BY AUTHOR NAME (other filters apply):",
|
"authors-search": "SEARCH BY AUTHOR NAME (other filters apply):",
|
||||||
"authors-placeholder": "Enter the author nickname...",
|
"authors-placeholder": "Enter the author nickname...",
|
||||||
"authors-button-title": "Search",
|
"authors-button-title": "Search",
|
||||||
@@ -241,20 +266,53 @@
|
|||||||
"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...",
|
||||||
|
"active-filters": "Attention! You got active filters!"
|
||||||
|
},
|
||||||
|
"station-stats": {
|
||||||
|
"u-factor": "U-factor",
|
||||||
|
"u-factor-tooltip": "(?) Current server traffic factor (driver count divided by dispatcher count)",
|
||||||
|
"avg-timetable-count": "Average count of scenery timetables:",
|
||||||
|
"single-track-count": "Single track routes:",
|
||||||
|
"double-track-count": "Double track routes:",
|
||||||
|
"cross-sceneries": "Cross-track sceneries (1-track <-> 2-track)",
|
||||||
|
"open-spawns": "Open spawns:"
|
||||||
},
|
},
|
||||||
"trains": {
|
"trains": {
|
||||||
"no-trains": "No trains to show here!",
|
"no-trains": "No trains to show here!",
|
||||||
@@ -273,6 +331,9 @@
|
|||||||
"current-signal": "at signal",
|
"current-signal": "at signal",
|
||||||
"current-track": "on track",
|
"current-track": "on track",
|
||||||
|
|
||||||
|
"vmax-tooltip": "Maximum train speed based on rolling stock vehicles - braked weight is not included",
|
||||||
|
"we4a-tooltip": "Non-electrified track",
|
||||||
|
|
||||||
"delayed": "Delayed: ",
|
"delayed": "Delayed: ",
|
||||||
"preponed": "Ahead of schedule: ",
|
"preponed": "Ahead of schedule: ",
|
||||||
"on-time": "On time",
|
"on-time": "On time",
|
||||||
@@ -297,7 +358,9 @@
|
|||||||
"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": {
|
"train-stats": {
|
||||||
"stats-button": "STATISTICS",
|
"stats-button": "STATISTICS",
|
||||||
@@ -333,6 +396,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",
|
||||||
@@ -341,7 +405,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",
|
||||||
@@ -432,21 +496,16 @@
|
|||||||
"option-timetables-history": "Timetables history PL1",
|
"option-timetables-history": "Timetables history PL1",
|
||||||
"option-dispatchers-history": "Dispatchers history PL1",
|
"option-dispatchers-history": "Dispatchers history PL1",
|
||||||
|
|
||||||
"timetable-author-title": "Issued by",
|
"timetable-via": "ALL TIMETABLES",
|
||||||
"timetable-author-unknown": "Author unknown",
|
"timetable-issuedFrom": "BEGINS HERE",
|
||||||
|
"timetable-terminatingAt": "TERMINATES HERE",
|
||||||
|
|
||||||
"timetables-history-id": "ID",
|
"timetable-issued-date": "Issued",
|
||||||
"timetables-history-number": "Number",
|
"timetable-issued-by": " by:",
|
||||||
"timetables-history-route": "Route",
|
"timetable-issued-for": " for driver:",
|
||||||
"timetables-history-driver": "Driver",
|
|
||||||
"timetables-history-author": "TT author",
|
|
||||||
"timetables-history-date": "Date",
|
|
||||||
|
|
||||||
"dispatchers-history-hash": "Hash",
|
"dispatcher-rate": "Rate:",
|
||||||
"dispatchers-history-dispatcher": "Dispatcher",
|
"dispatcher-status-changes": "Status changes:",
|
||||||
"dispatchers-history-level": "Level",
|
|
||||||
"dispatchers-history-rate": "Rate",
|
|
||||||
"dispatchers-history-date": "Service date",
|
|
||||||
|
|
||||||
"req-level": "all dispatcher levels | dispatcher level {lvl} required | dispatcher level {lvl} required",
|
"req-level": "all dispatcher levels | dispatcher level {lvl} required | dispatcher level {lvl} required",
|
||||||
"history-list-empty": "No recorded scenery history!",
|
"history-list-empty": "No recorded scenery history!",
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
"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",
|
||||||
@@ -25,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",
|
||||||
@@ -37,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!",
|
||||||
@@ -47,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",
|
||||||
@@ -77,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 ",
|
||||||
@@ -95,8 +108,8 @@
|
|||||||
"filters": "FILTRY",
|
"filters": "FILTRY",
|
||||||
"donate": "WESPRZYJ",
|
"donate": "WESPRZYJ",
|
||||||
|
|
||||||
"search-button": "Szukaj",
|
"search-button": "SZUKAJ",
|
||||||
"reset-button": "Zresetuj",
|
"reset-button": "ZRESETUJ",
|
||||||
|
|
||||||
"sort-title": "SORTUJ WG:",
|
"sort-title": "SORTUJ WG:",
|
||||||
"filter-title": "FILTRUJ WG:",
|
"filter-title": "FILTRUJ WG:",
|
||||||
@@ -109,6 +122,8 @@
|
|||||||
"search-station": "Nazwa scenerii",
|
"search-station": "Nazwa scenerii",
|
||||||
"search-author": "Nick autora rozkładu jazdy",
|
"search-author": "Nick autora rozkładu jazdy",
|
||||||
"search-issuedFrom": "Sceneria początkowa",
|
"search-issuedFrom": "Sceneria początkowa",
|
||||||
|
"search-via": "Przez scenerię",
|
||||||
|
"search-terminatingAt": "Sceneria końcowa",
|
||||||
"search-timetables-date": "Data rozkładu jazdy (UTC+2 / CEST)",
|
"search-timetables-date": "Data rozkładu jazdy (UTC+2 / CEST)",
|
||||||
"search-dispatchers-date": "Data służby (UTC+2 / CEST)",
|
"search-dispatchers-date": "Data służby (UTC+2 / CEST)",
|
||||||
"search-date": "Data (UTC+2 / CEST)",
|
"search-date": "Data (UTC+2 / CEST)",
|
||||||
@@ -158,16 +173,22 @@
|
|||||||
|
|
||||||
"sections": {
|
"sections": {
|
||||||
"quick": "SZYBKIE FILTRY",
|
"quick": "SZYBKIE FILTRY",
|
||||||
|
"stationType": "RODZAJ STACJI",
|
||||||
"reality": "FIKCYJNOŚĆ SCENERII",
|
"reality": "FIKCYJNOŚĆ SCENERII",
|
||||||
"package-access": "DOSTĘPNOŚĆ W PACZCE",
|
"packageAccess": "DOSTĘPNOŚĆ W PACZCE",
|
||||||
"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"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"changed-filters-count": "Zmienione filtry:",
|
||||||
|
"no-changed-filters": "Brak zmienionych filtrów",
|
||||||
|
|
||||||
"all-available": "WSZYSTKIE DOSTĘPNE",
|
"all-available": "WSZYSTKIE DOSTĘPNE",
|
||||||
"all-free": "WSZYSTKIE WOLNE",
|
"all-free": "WSZYSTKIE WOLNE",
|
||||||
|
|
||||||
@@ -178,11 +199,11 @@
|
|||||||
|
|
||||||
"title": "FILTRUJ STACJE",
|
"title": "FILTRUJ STACJE",
|
||||||
"default": "DOMYŚLNA",
|
"default": "DOMYŚLNA",
|
||||||
"not-default": "POZA PACZKĄ",
|
"notDefault": "POZA PACZKĄ",
|
||||||
"real": "REALNA",
|
"real": "REALNA",
|
||||||
"fictional": "FIKCYJNA",
|
"fictional": "FIKCYJNA",
|
||||||
"unavailable": "NIEDOSTĘPNA",
|
"unavailable": "NIEDOSTĘPNA",
|
||||||
"non-public": "NIEPUBLICZNA",
|
"nonPublic": "NIEPUBLICZNA",
|
||||||
"abandoned": "WYCOFANA",
|
"abandoned": "WYCOFANA",
|
||||||
|
|
||||||
"SPK": "SPK",
|
"SPK": "SPK",
|
||||||
@@ -197,6 +218,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",
|
||||||
|
|
||||||
@@ -205,17 +229,24 @@
|
|||||||
"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",
|
||||||
|
|
||||||
|
"junction": "WĘZŁOWE",
|
||||||
|
"nonJunction": "INNE",
|
||||||
|
|
||||||
"sliders": {
|
"sliders": {
|
||||||
"min-lvl": "MIN. WYMAGANY POZIOM DYŻURNEGO",
|
"minLevel": "MIN. WYMAGANY POZIOM DYŻURNEGO",
|
||||||
"max-lvl": "MAKS. WYMAGANY POZIOM DYŻURNEGO",
|
"maxLevel": "MAKS. WYMAGANY POZIOM DYŻURNEGO",
|
||||||
"routes-1t-cat": "SZLAKI JEDNOTOROWE ZELEKTR. (MINIMUM)",
|
"minVmax": "MIN. PRĘDKOŚĆ SZLAKOWA",
|
||||||
"routes-1t-other": "SZLAKI JEDNOTOROWE NIEZELEKTR. (MINIMUM)",
|
"maxVmax": "MAKS. PRĘDKOŚĆ SZLAKOWA",
|
||||||
"routes-2t-cat": "SZLAKI DWUTOROWE ZELEKTR. (MINIMUM)",
|
"minOneWayCatenary": "SZLAKI JEDNOTOROWE ZELEKTR. (MINIMUM)",
|
||||||
"routes-2t-other": "SZLAKI DWUTOROWE NIEZELEKTR. (MINIMUM)"
|
"minOneWay": "SZLAKI JEDNOTOROWE NIEZELEKTR. (MINIMUM)",
|
||||||
|
"minTwoWayCatenary": "SZLAKI DWUTOROWE ZELEKTR. (MINIMUM)",
|
||||||
|
"minTwoWay": "SZLAKI DWUTOROWE NIEZELEKTR. (MINIMUM)"
|
||||||
},
|
},
|
||||||
|
|
||||||
"authors-search": "SZUKAJ AUTORA (uwzględnia inne filtry):",
|
"authors-search": "SZUKAJ AUTORA (uwzględnia inne filtry):",
|
||||||
@@ -231,21 +262,51 @@
|
|||||||
"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ę...",
|
||||||
|
"active-filters": "Uwaga! Masz obecnie aktywne filtry!"
|
||||||
|
},
|
||||||
|
"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 sceneriach:",
|
||||||
|
"single-track-count": "Szlaki jednotorowe:",
|
||||||
|
"double-track-count": "Szlaki dwutorowe:",
|
||||||
|
"cross-sceneries": "Scenerie przejściowe (1-tor <-> 2-tor):",
|
||||||
|
"open-spawns": "Otwarte spawny:"
|
||||||
},
|
},
|
||||||
"trains": {
|
"trains": {
|
||||||
"no-trains": "Brak pociągów do wyświetlenia!",
|
"no-trains": "Brak pociągów do wyświetlenia!",
|
||||||
@@ -256,6 +317,9 @@
|
|||||||
"current-signal": "przy semaforze",
|
"current-signal": "przy semaforze",
|
||||||
"current-track": "na szlaku",
|
"current-track": "na szlaku",
|
||||||
|
|
||||||
|
"vmax-tooltip": "Maksymalna prędkość na podstawie pojazdów w składzie - nie bierze pod uwagę masy hamowania",
|
||||||
|
"we4a-tooltip": "Szlak niezelektryfikowany",
|
||||||
|
|
||||||
"delayed": "Opóźniony: ",
|
"delayed": "Opóźniony: ",
|
||||||
"preponed": "Przed czasem: ",
|
"preponed": "Przed czasem: ",
|
||||||
"on-time": "Planowo",
|
"on-time": "Planowo",
|
||||||
@@ -279,7 +343,9 @@
|
|||||||
|
|
||||||
"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": {
|
"train-stats": {
|
||||||
"stats-button": "STATYSTYKI",
|
"stats-button": "STATYSTYKI",
|
||||||
@@ -321,8 +387,9 @@
|
|||||||
"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.",
|
||||||
@@ -412,21 +479,16 @@
|
|||||||
"option-timetables-history": "Historia rozkładów PL1",
|
"option-timetables-history": "Historia rozkładów PL1",
|
||||||
"option-dispatchers-history": "Historia dyżurów PL1",
|
"option-dispatchers-history": "Historia dyżurów PL1",
|
||||||
|
|
||||||
"timetable-author-title": "Wydany przez",
|
"timetable-via": "WSZYSTKIE RJ",
|
||||||
"timetable-author-unknown": "Autor nieznany",
|
"timetable-issuedFrom": "ROZPOCZYNA BIEG",
|
||||||
|
"timetable-terminatingAt": "KOŃCZY BIEG",
|
||||||
|
|
||||||
"timetables-history-id": "ID",
|
"timetable-issued-date": "Wystawiony",
|
||||||
"timetables-history-number": "Numer",
|
"timetable-issued-by": " przez:",
|
||||||
"timetables-history-route": "Trasa",
|
"timetable-issued-for": " dla maszynisty:",
|
||||||
"timetables-history-driver": "Maszynista",
|
|
||||||
"timetables-history-author": "Autor RJ",
|
|
||||||
"timetables-history-date": "Data",
|
|
||||||
|
|
||||||
"dispatchers-history-hash": "Hash",
|
"dispatcher-rate": "Ocena:",
|
||||||
"dispatchers-history-dispatcher": "Dyżurny",
|
"dispatcher-status-changes": "Zmiany statusów:",
|
||||||
"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",
|
"req-level": "ogólnodostępna | minimum {lvl} poziom dyżurnego | minimum {lvl} poziom dyżurnego",
|
||||||
"history-list-empty": "Brak historii dla tej scenerii!",
|
"history-list-empty": "Brak historii dla tej scenerii!",
|
||||||
|
|||||||
@@ -2,28 +2,19 @@ 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 { registerSW } from 'virtual:pwa-register';
|
||||||
|
|
||||||
const i18n = createI18n({
|
// Service worker
|
||||||
locale: 'pl',
|
registerSW({
|
||||||
legacy: false,
|
immediate: true,
|
||||||
warnHtmlMessage: false,
|
onNeedRefresh() {
|
||||||
fallbackLocale: 'pl',
|
console.log('Needs refresh!');
|
||||||
messages: {
|
}
|
||||||
en: enLang,
|
|
||||||
pl: plLang
|
|
||||||
},
|
|
||||||
enableLegacy: false
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// SW
|
|
||||||
useCustomSW();
|
|
||||||
|
|
||||||
const clickOutsideDirective: Directive = {
|
const clickOutsideDirective: Directive = {
|
||||||
mounted(el, binding) {
|
mounted(el, binding) {
|
||||||
el.clickOutsideEvent = (event: Event) => {
|
el.clickOutsideEvent = (event: Event) => {
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
import StorageManager from './storageManager';
|
||||||
|
|
||||||
|
export const sections = [
|
||||||
|
'status',
|
||||||
|
'timetables',
|
||||||
|
'reality',
|
||||||
|
'packageAccess',
|
||||||
|
'stationType',
|
||||||
|
'access',
|
||||||
|
'control',
|
||||||
|
'blockades',
|
||||||
|
'signals',
|
||||||
|
'addons'
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export const initFilters = {
|
||||||
|
default: false,
|
||||||
|
notDefault: false,
|
||||||
|
real: false,
|
||||||
|
fictional: false,
|
||||||
|
SPK: false,
|
||||||
|
SCS: false,
|
||||||
|
SPE: false,
|
||||||
|
SUP: false,
|
||||||
|
noSUP: false,
|
||||||
|
ASDEK: false,
|
||||||
|
noASDEK: false,
|
||||||
|
manual: false,
|
||||||
|
'SPK-R': false,
|
||||||
|
'SCS-R': false,
|
||||||
|
mechanical: false,
|
||||||
|
'SPK-M': false,
|
||||||
|
'SCS-M': false,
|
||||||
|
modern: false,
|
||||||
|
semaphores: false,
|
||||||
|
historical: false,
|
||||||
|
mixed: false,
|
||||||
|
SBL: false,
|
||||||
|
PBL: false,
|
||||||
|
'include-selected': false,
|
||||||
|
'no-1track': false,
|
||||||
|
'no-2track': false,
|
||||||
|
free: true,
|
||||||
|
occupied: false,
|
||||||
|
nonPublic: false,
|
||||||
|
unavailable: true,
|
||||||
|
abandoned: true,
|
||||||
|
afkStatus: false,
|
||||||
|
endingStatus: false,
|
||||||
|
noSpaceStatus: false,
|
||||||
|
unavailableStatus: false,
|
||||||
|
unsignedStatus: false,
|
||||||
|
withActiveTimetables: false,
|
||||||
|
withoutActiveTimetables: false,
|
||||||
|
junction: false,
|
||||||
|
nonJunction: false,
|
||||||
|
maxVmax: 200,
|
||||||
|
minVmax: 0,
|
||||||
|
onlineFromHours: 0,
|
||||||
|
minLevel: 0,
|
||||||
|
maxLevel: 20,
|
||||||
|
minOneWayCatenary: 0,
|
||||||
|
minOneWay: 0,
|
||||||
|
minTwoWayCatenary: 0,
|
||||||
|
minTwoWay: 0,
|
||||||
|
authors: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
export const initSliders = [
|
||||||
|
{ id: 'maxVmax', minRange: 0, maxRange: 200, step: 10 },
|
||||||
|
{ id: 'minVmax', minRange: 0, maxRange: 200, step: 10 },
|
||||||
|
{ id: 'minLevel', minRange: 0, maxRange: 20, step: 1 },
|
||||||
|
{ id: 'maxLevel', minRange: 0, maxRange: 20, step: 1 },
|
||||||
|
{ id: 'minOneWayCatenary', minRange: 0, maxRange: 5, step: 1 },
|
||||||
|
{ id: 'minOneWay', minRange: 0, maxRange: 5, step: 1 },
|
||||||
|
{ id: 'minTwoWayCatenary', minRange: 0, maxRange: 5, step: 1 },
|
||||||
|
{ id: 'minTwoWay', minRange: 0, maxRange: 5, step: 1 }
|
||||||
|
];
|
||||||
|
|
||||||
|
export type StationFilter = keyof typeof initFilters;
|
||||||
|
export type StationFilterSection = (typeof sections)[number];
|
||||||
|
|
||||||
|
export const filtersSections: Record<StationFilterSection, StationFilter[]> = {
|
||||||
|
status: ['free', 'occupied', 'endingStatus', 'afkStatus', 'noSpaceStatus', 'unavailableStatus'],
|
||||||
|
timetables: ['withActiveTimetables', 'withoutActiveTimetables'],
|
||||||
|
reality: ['real', 'fictional'],
|
||||||
|
packageAccess: ['default', 'notDefault'],
|
||||||
|
stationType: ['junction', 'nonJunction'],
|
||||||
|
access: ['nonPublic', 'unavailable', 'abandoned'],
|
||||||
|
addons: ['SUP', 'ASDEK', 'noSUP', 'noASDEK'],
|
||||||
|
control: ['SPK', 'SCS', 'SPE', 'SPK-M', 'SCS-M', 'mechanical', 'SPK-R', 'SCS-R', 'manual'],
|
||||||
|
blockades: ['SBL', 'PBL'],
|
||||||
|
signals: ['modern', 'semaphores', 'mixed', 'historical']
|
||||||
|
};
|
||||||
|
|
||||||
|
export function setupFilters(currentFilters: Record<string, any>) {
|
||||||
|
if (!StorageManager.isRegistered('options_saved')) return;
|
||||||
|
|
||||||
|
Object.keys(currentFilters).forEach((filterKey) => {
|
||||||
|
const savedValue = StorageManager.getValue(filterKey);
|
||||||
|
|
||||||
|
if (savedValue != null) {
|
||||||
|
if (typeof currentFilters[filterKey] == 'boolean')
|
||||||
|
currentFilters[filterKey] = savedValue === 'true';
|
||||||
|
else if (typeof currentFilters[filterKey] == 'number')
|
||||||
|
currentFilters[filterKey] = Number(savedValue);
|
||||||
|
else currentFilters[filterKey] = savedValue.toString();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getChangedFilters(currentFilters: Record<string, any>): string[] {
|
||||||
|
return (
|
||||||
|
Object.keys(currentFilters).filter(
|
||||||
|
(filterKey) =>
|
||||||
|
currentFilters[filterKey] !== initFilters[filterKey as keyof typeof initFilters]
|
||||||
|
) ?? []
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -34,6 +34,10 @@ export default class StorageManager {
|
|||||||
window.localStorage.removeItem(key);
|
window.localStorage.removeItem(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getValue(key: string) {
|
||||||
|
return window.localStorage.getItem(key);
|
||||||
|
}
|
||||||
|
|
||||||
static getBooleanValue(key: string): boolean {
|
static getBooleanValue(key: string): boolean {
|
||||||
return window.localStorage.getItem(key) === 'true' ? true : false;
|
return window.localStorage.getItem(key) === 'true' ? true : false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,16 +0,0 @@
|
|||||||
import { defineComponent } from 'vue';
|
|
||||||
import { useApiStore } from '../store/apiStore';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
apiStore: useApiStore()
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
isDonator(name: string) {
|
|
||||||
return this.apiStore.donatorsData.includes(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,27 +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) => {
|
|
||||||
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,33 +1,30 @@
|
|||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { useMainStore } from '../store/mainStore';
|
import { useMainStore } from '../store/mainStore';
|
||||||
|
import { useTooltipStore } from '../store/tooltipStore';
|
||||||
|
import { Train } from '../typings/common';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
store: useMainStore()
|
store: useMainStore(),
|
||||||
|
tooltipStore: useTooltipStore()
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
methods: {
|
||||||
chosenTrain() {
|
selectModalTrain(train: Train, target?: EventTarget | null) {
|
||||||
return this.store.trainList.find((train) => train.trainId == this.store.chosenModalTrainId);
|
this.store.chosenModalTrainId = train.modalId;
|
||||||
}
|
if (target) this.store.modalLastClickedTarget = target;
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
selectModalTrainById(modalId: string, target?: EventTarget | null) {
|
||||||
selectModalTrain(trainId: string, target?: EventTarget | null) {
|
this.store.chosenModalTrainId = modalId;
|
||||||
this.store.chosenModalTrainId = trainId;
|
|
||||||
document.body.classList.add('no-scroll');
|
|
||||||
if (target) this.store.modalLastClickedTarget = target;
|
if (target) this.store.modalLastClickedTarget = target;
|
||||||
},
|
},
|
||||||
|
|
||||||
closeModal() {
|
closeModal() {
|
||||||
this.store.chosenModalTrainId = undefined;
|
this.store.chosenModalTrainId = undefined;
|
||||||
|
this.tooltipStore.hide();
|
||||||
setTimeout(() => {
|
|
||||||
(this.store.modalLastClickedTarget as any)?.focus();
|
|
||||||
document.body.classList.remove('no-scroll');
|
|
||||||
}, 150);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
import { useRegisterSW } from 'virtual:pwa-register/vue';
|
|
||||||
|
|
||||||
export default () => {
|
|
||||||
const { needRefresh, updateServiceWorker, offlineReady } = useRegisterSW({
|
|
||||||
immediate: true
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
needRefresh,
|
|
||||||
updateServiceWorker,
|
|
||||||
offlineReady
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -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> = [
|
||||||
{
|
{
|
||||||
@@ -38,7 +36,7 @@ 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) => ({
|
||||||
region: route.query.region
|
region: route.query.region
|
||||||
})
|
})
|
||||||
@@ -46,7 +44,7 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
{
|
{
|
||||||
path: '/journal/dispatchers',
|
path: '/journal/dispatchers',
|
||||||
name: 'JournalDispatchers',
|
name: 'JournalDispatchers',
|
||||||
component: JournalDispatchersVue,
|
component: () => import('../views/JournalDispatchers.vue'),
|
||||||
props: (route) => ({
|
props: (route) => ({
|
||||||
region: route.query.region
|
region: route.query.region
|
||||||
})
|
})
|
||||||
@@ -60,7 +58,7 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
scrollBehavior(to, from, savedPosition) {
|
scrollBehavior(to, from, savedPosition) {
|
||||||
if (to.name == 'SceneryView' && from.name !== to.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`, top: -15 };
|
||||||
|
|
||||||
if (savedPosition) return savedPosition;
|
if (savedPosition) return savedPosition;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
export const headIds = [
|
|
||||||
'station',
|
|
||||||
'min-lvl',
|
|
||||||
'status',
|
|
||||||
'dispatcher',
|
|
||||||
'dispatcher-lvl',
|
|
||||||
'routes',
|
|
||||||
'general'
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export const headIconsIds = [
|
|
||||||
'user',
|
|
||||||
'spawn',
|
|
||||||
'timetableAll',
|
|
||||||
'timetableUnconfirmed',
|
|
||||||
'timetableConfirmed'
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export type HeadIdsTypes = (typeof headIds)[number] | (typeof headIconsIds)[number];
|
|
||||||
@@ -1,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,13 +0,0 @@
|
|||||||
import { StationRoutesInfo } from '../../store/typings';
|
|
||||||
|
|
||||||
export interface StationRoutes {
|
|
||||||
oneWay: StationRoutesInfo[];
|
|
||||||
twoWay: StationRoutesInfo[];
|
|
||||||
|
|
||||||
/* [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,191 +0,0 @@
|
|||||||
import { Filter } from '../../components/StationsView/typings';
|
|
||||||
import { Status } from '../../typings/common';
|
|
||||||
import { HeadIdsTypes } from '../data/stationHeaderNames';
|
|
||||||
import Station from '../interfaces/Station';
|
|
||||||
|
|
||||||
const dispatcherStatusPriority = [
|
|
||||||
Status.ActiveDispatcher.UNKNOWN,
|
|
||||||
Status.ActiveDispatcher.INVALID,
|
|
||||||
Status.ActiveDispatcher.NOT_LOGGED_IN,
|
|
||||||
Status.ActiveDispatcher.UNAVAILABLE,
|
|
||||||
Status.ActiveDispatcher.AFK,
|
|
||||||
Status.ActiveDispatcher.ENDING,
|
|
||||||
Status.ActiveDispatcher.NO_SPACE,
|
|
||||||
undefined
|
|
||||||
];
|
|
||||||
|
|
||||||
export const sortStations = (
|
|
||||||
a: Station,
|
|
||||||
b: Station,
|
|
||||||
sorter: { headerName: HeadIdsTypes; dir: number }
|
|
||||||
) => {
|
|
||||||
let diff = 0;
|
|
||||||
|
|
||||||
switch (sorter.headerName) {
|
|
||||||
case 'station':
|
|
||||||
return sorter.dir == 1 ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name);
|
|
||||||
|
|
||||||
case 'min-lvl':
|
|
||||||
diff = (a.generalInfo?.reqLevel || 0) - (b.generalInfo?.reqLevel || 0);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'status':
|
|
||||||
diff =
|
|
||||||
(a.onlineInfo?.dispatcherTimestamp ??
|
|
||||||
dispatcherStatusPriority.indexOf(a.onlineInfo?.dispatcherStatus)) -
|
|
||||||
(b.onlineInfo?.dispatcherTimestamp ??
|
|
||||||
dispatcherStatusPriority.indexOf(b.onlineInfo?.dispatcherStatus));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'dispatcher':
|
|
||||||
if (
|
|
||||||
(a.onlineInfo?.dispatcherName.toLowerCase() || '') >
|
|
||||||
(b.onlineInfo?.dispatcherName.toLowerCase() || '')
|
|
||||||
)
|
|
||||||
return sorter.dir;
|
|
||||||
if (
|
|
||||||
(a.onlineInfo?.dispatcherName.toLowerCase() || '') <
|
|
||||||
(b.onlineInfo?.dispatcherName.toLowerCase() || '')
|
|
||||||
)
|
|
||||||
return -sorter.dir;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'dispatcher-lvl':
|
|
||||||
diff = (a.onlineInfo?.dispatcherExp || 0) - (b.onlineInfo?.dispatcherExp || 0);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'user':
|
|
||||||
diff =
|
|
||||||
(b.onlineInfo ? b.onlineInfo.currentUsers : -1) -
|
|
||||||
(a.onlineInfo ? a.onlineInfo.currentUsers : -1);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'spawn':
|
|
||||||
diff =
|
|
||||||
(a.onlineInfo ? a.onlineInfo.spawns.length : -1) -
|
|
||||||
(b.onlineInfo ? b.onlineInfo.spawns.length : -1);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'timetableConfirmed':
|
|
||||||
diff =
|
|
||||||
(a.onlineInfo?.scheduledTrainCount.confirmed ?? -1) -
|
|
||||||
(b.onlineInfo?.scheduledTrainCount.confirmed ?? -1);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'timetableUnconfirmed':
|
|
||||||
diff =
|
|
||||||
(a.onlineInfo?.scheduledTrainCount.unconfirmed ?? -1) -
|
|
||||||
(b.onlineInfo?.scheduledTrainCount.unconfirmed ?? -1);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'timetableAll':
|
|
||||||
diff =
|
|
||||||
(a.onlineInfo?.scheduledTrainCount.all ?? -1) -
|
|
||||||
(b.onlineInfo?.scheduledTrainCount.all ?? -1);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (diff != 0) return Math.sign(diff) * sorter.dir;
|
|
||||||
return a.name.localeCompare(b.name);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const filterStations = (station: Station, filters: Filter) => {
|
|
||||||
if (!station.onlineInfo && filters['free']) return false;
|
|
||||||
|
|
||||||
if (station.onlineInfo) {
|
|
||||||
const { dispatcherStatus } = station.onlineInfo;
|
|
||||||
|
|
||||||
const isEnding = dispatcherStatus == Status.ActiveDispatcher.ENDING && filters['endingStatus'];
|
|
||||||
|
|
||||||
const isNotSigned =
|
|
||||||
(dispatcherStatus == Status.ActiveDispatcher.NOT_LOGGED_IN ||
|
|
||||||
dispatcherStatus == Status.ActiveDispatcher.UNAVAILABLE) &&
|
|
||||||
filters['unavailableStatus'];
|
|
||||||
|
|
||||||
const isAFK = dispatcherStatus == Status.ActiveDispatcher.AFK && filters['afkStatus'];
|
|
||||||
|
|
||||||
const isNoSpace =
|
|
||||||
dispatcherStatus == Status.ActiveDispatcher.NO_SPACE && filters['noSpaceStatus'];
|
|
||||||
|
|
||||||
const isOccupied = station.onlineInfo && filters['occupied'];
|
|
||||||
|
|
||||||
if (isEnding || isNotSigned || isAFK || isNoSpace || isOccupied) return false;
|
|
||||||
|
|
||||||
if (
|
|
||||||
filters['onlineFromHours'] > 0 &&
|
|
||||||
dispatcherStatus <= Date.now() + filters['onlineFromHours'] * 3600000
|
|
||||||
)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
(station.generalInfo?.availability == 'nonPublic' || !station.generalInfo) &&
|
|
||||||
filters['nonPublic']
|
|
||||||
)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (station.generalInfo) {
|
|
||||||
const { routes, availability, controlType, lines, reqLevel, signalType, SUP, authors } =
|
|
||||||
station.generalInfo;
|
|
||||||
|
|
||||||
if (availability == 'unavailable' && filters['unavailable'] && !station.onlineInfo)
|
|
||||||
return false;
|
|
||||||
if (availability == 'abandoned' && filters['abandoned'] && !station.onlineInfo) return false;
|
|
||||||
if (availability == 'default' && filters['default']) return false;
|
|
||||||
|
|
||||||
if (
|
|
||||||
availability != 'default' &&
|
|
||||||
filters['notDefault'] &&
|
|
||||||
!(availability == 'abandoned' || availability == 'unavailable')
|
|
||||||
)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (filters['real'] && lines) return false;
|
|
||||||
if (filters['fictional'] && !lines) return false;
|
|
||||||
|
|
||||||
const otherAvailability =
|
|
||||||
availability == 'nonPublic' || availability == 'unavailable' || availability == 'abandoned';
|
|
||||||
|
|
||||||
if (reqLevel + (otherAvailability ? 1 : 0) < filters['minLevel']) return false;
|
|
||||||
|
|
||||||
if (reqLevel + (otherAvailability ? 1 : 0) > filters['maxLevel']) return false;
|
|
||||||
|
|
||||||
if (
|
|
||||||
filters['no-1track'] &&
|
|
||||||
(routes.oneWayCatenaryRouteNames.length != 0 || routes.oneWayNoCatenaryRouteNames.length != 0)
|
|
||||||
)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (
|
|
||||||
filters['no-2track'] &&
|
|
||||||
(routes.twoWayCatenaryRouteNames.length != 0 || routes.twoWayNoCatenaryRouteNames.length != 0)
|
|
||||||
)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (routes.oneWayCatenaryRouteNames.length < filters['minOneWayCatenary']) return false;
|
|
||||||
if (routes.oneWayNoCatenaryRouteNames.length < filters['minOneWay']) return false;
|
|
||||||
|
|
||||||
if (routes.twoWayCatenaryRouteNames.length < filters['minTwoWayCatenary']) return false;
|
|
||||||
if (routes.twoWayNoCatenaryRouteNames.length < filters['minTwoWay']) return false;
|
|
||||||
|
|
||||||
if (filters[controlType]) return false;
|
|
||||||
if (filters[signalType]) return false;
|
|
||||||
|
|
||||||
if (filters['SUP'] && SUP) return false;
|
|
||||||
if (filters['noSUP'] && !SUP) return false;
|
|
||||||
|
|
||||||
if (filters['SBL'] && routes.sblRouteNames.length > 0) return false;
|
|
||||||
if (filters['PBL'] && routes.sblRouteNames.length == 0) return false;
|
|
||||||
|
|
||||||
if (
|
|
||||||
filters['authors'].length > 3 &&
|
|
||||||
!authors?.map((a) => a.toLocaleLowerCase()).includes(filters['authors'].toLocaleLowerCase())
|
|
||||||
)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
@@ -1,65 +1,89 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import http from '../http';
|
|
||||||
import { API } from '../typings/api';
|
import { API } from '../typings/api';
|
||||||
import axios from 'axios';
|
|
||||||
import { Status } from '../typings/common';
|
import { Status } from '../typings/common';
|
||||||
import { StationJSONData } from './typings';
|
import { StationJSONData } from './typings';
|
||||||
|
import axios, { AxiosInstance } from 'axios';
|
||||||
|
|
||||||
export const useApiStore = defineStore('apiStore', {
|
export const useApiStore = defineStore('apiStore', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
dataStatuses: {
|
dataStatuses: {
|
||||||
connection: Status.Data.Loading,
|
connection: Status.Data.Loading,
|
||||||
sceneries: Status.Data.Loading,
|
sceneries: Status.Data.Loading,
|
||||||
timetables: Status.Data.Loading,
|
vehicles: Status.Data.Loading
|
||||||
dispatchers: Status.Data.Loading,
|
|
||||||
trains: Status.Data.Loading
|
|
||||||
},
|
},
|
||||||
|
|
||||||
activeData: undefined as API.ActiveData.Response | undefined,
|
activeData: undefined as API.ActiveData.Response | undefined,
|
||||||
rollingStockData: undefined as API.RollingStock.Response | undefined,
|
vehiclesData: undefined as API.Vehicles.Response | undefined,
|
||||||
|
|
||||||
donatorsData: [] as API.Donators.Response,
|
donatorsData: [] as API.Donators.Response,
|
||||||
sceneryData: [] as StationJSONData[],
|
sceneryData: [] as StationJSONData[],
|
||||||
|
|
||||||
activeDataTimeout: undefined as number | undefined
|
lastFetchData: new Date(),
|
||||||
|
|
||||||
|
client: undefined as AxiosInstance | undefined,
|
||||||
|
|
||||||
|
activeDataScheduler: undefined as number | undefined
|
||||||
}),
|
}),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
async setupAPI() {
|
async setupAPIData() {
|
||||||
// Static data
|
let baseURL = 'https://stacjownik.spythere.eu';
|
||||||
this.fetchStockInfoData();
|
|
||||||
this.fetchDonatorsData();
|
|
||||||
this.fetchStationsGeneralInfo();
|
|
||||||
|
|
||||||
if (this.activeDataTimeout === undefined) this.startActiveDataScheduler();
|
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 setDataStatuses() {
|
async connectToAPI() {
|
||||||
// if (!window.navigator.onLine) {
|
// Static data
|
||||||
// this.dataStatuses.connection = Status.Data.Offline;
|
this.fetchDonatorsData();
|
||||||
// this.dataStatuses.sceneries = Status.Data.Offline;
|
this.fetchStationsGeneralInfo();
|
||||||
// this.dataStatuses.trains = Status.Data.Offline;
|
this.fetchVehiclesInfo();
|
||||||
// this.dataStatuses.dispatchers = Status.Data.Offline;
|
},
|
||||||
// this.dataStatuses.timetables = Status.Data.Offline;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (!this.activeData?.activeSceneries) {
|
async fetchActiveData() {
|
||||||
// this.dataStatuses.connection = Status.Data.Loaded;
|
if (import.meta.env.VITE_API_ACTIVE_DATA_MODE == 'mocking') {
|
||||||
// this.dataStatuses.sceneries = Status.Data.Error;
|
import('../../tests/data/getActiveData.json').then((data) => {
|
||||||
// this.dataStatuses.trains = Status.Data.Error;
|
console.warn('activeData: mocking mode');
|
||||||
// this.dataStatuses.dispatchers = Status.Data.Error;
|
this.activeData = data.default as API.ActiveData.Response;
|
||||||
|
this.lastFetchData = new Date();
|
||||||
|
|
||||||
// return;
|
this.dataStatuses.connection = Status.Data.Loaded;
|
||||||
// }
|
});
|
||||||
|
|
||||||
// this.dataStatuses.connection = Status.Data.Loaded;
|
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;
|
if (!this.activeData) this.dataStatuses.connection = Status.Data.Loading;
|
||||||
// },
|
|
||||||
|
try {
|
||||||
|
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() {
|
async fetchDonatorsData() {
|
||||||
try {
|
try {
|
||||||
const response = await http.get<API.Donators.Response>('api/getDonators');
|
const response = await this.client!.get<API.Donators.Response>('api/getDonators');
|
||||||
|
|
||||||
this.donatorsData = response.data;
|
this.donatorsData = response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -67,68 +91,40 @@ export const useApiStore = defineStore('apiStore', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async fetchStockInfoData() {
|
|
||||||
try {
|
|
||||||
this.rollingStockData = (
|
|
||||||
await axios.get<API.RollingStock.Response>(
|
|
||||||
'https://raw.githubusercontent.com/Spythere/api/main/td2/data/stockInfo.json'
|
|
||||||
)
|
|
||||||
).data;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Ups! Wystąpił błąd podczas pobierania informacji o taborze z API:', error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async startActiveDataScheduler() {
|
|
||||||
if (!window.navigator.onLine) {
|
|
||||||
this.dataStatuses.connection = Status.Data.Offline;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (import.meta.env.VITE_API_MODE === 'mock') {
|
|
||||||
const mockActiveData = await import('../data/mockActiveData.json');
|
|
||||||
this.dataStatuses.connection = Status.Data.Loaded;
|
|
||||||
this.activeData = mockActiveData;
|
|
||||||
|
|
||||||
console.warn('Stacjownik działa w trybie mockowania danych z WS');
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const data = (await http.get<API.ActiveData.Response>('api/getActiveData')).data;
|
|
||||||
|
|
||||||
this.activeData = data;
|
|
||||||
this.dataStatuses.connection = Status.Data.Loaded;
|
|
||||||
} catch (error) {
|
|
||||||
this.dataStatuses.connection = Status.Data.Error;
|
|
||||||
console.error('Wystąpił błąd podczas pobierania danych online z API!');
|
|
||||||
} finally {
|
|
||||||
this.activeDataTimeout = window.setTimeout(
|
|
||||||
() => {
|
|
||||||
this.startActiveDataScheduler();
|
|
||||||
},
|
|
||||||
~~(1000 * (Math.random() * (25 - 20) + 25))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async stopActiveDataScheduler() {
|
|
||||||
window.clearTimeout(this.activeDataTimeout);
|
|
||||||
this.activeDataTimeout = undefined;
|
|
||||||
},
|
|
||||||
|
|
||||||
async fetchStationsGeneralInfo() {
|
async fetchStationsGeneralInfo() {
|
||||||
const sceneryData: StationJSONData[] = (await http.get<StationJSONData[]>('api/getSceneries'))
|
try {
|
||||||
.data;
|
const sceneryData: StationJSONData[] = (
|
||||||
|
await this.client!.get<StationJSONData[]>('api/getSceneries')
|
||||||
if (!sceneryData) {
|
).data;
|
||||||
this.dataStatuses.sceneries = Status.Data.Error;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.dataStatuses.sceneries = Status.Data.Loaded;
|
this.dataStatuses.sceneries = Status.Data.Loaded;
|
||||||
this.sceneryData = sceneryData;
|
this.sceneryData = sceneryData;
|
||||||
|
} catch (error) {
|
||||||
|
this.dataStatuses.sceneries = Status.Data.Error;
|
||||||
|
console.error('Ups! Wystąpił błąd podczas pobierania informacji o sceneriach:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async fetchVehiclesInfo() {
|
||||||
|
// if (import.meta.env.VITE_API_VEHICLES_MODE == 'mocking') {
|
||||||
|
// import('../../tests/data/vehicles.json').then((data) => {
|
||||||
|
// console.warn('vehicles.json: mocking mode');
|
||||||
|
// this.vehiclesData = data.default;
|
||||||
|
// this.dataStatuses.vehicles = Status.Data.Loaded;
|
||||||
|
// });
|
||||||
|
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await this.client!.get<API.Vehicles.Response>('api/getVehicles');
|
||||||
|
|
||||||
|
this.vehiclesData = response.data;
|
||||||
|
this.dataStatuses.vehicles = response.data ? Status.Data.Loaded : Status.Data.Warning;
|
||||||
|
} catch (error) {
|
||||||
|
this.dataStatuses.vehicles = Status.Data.Error;
|
||||||
|
console.error('Ups! Wystąpił błąd podczas pobierania informacji o pojazdach:', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,21 +1,27 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import Train from '../scripts/interfaces/Train';
|
import { parseSpawns } from './utils';
|
||||||
import { parseSpawns, getScheduledTrains, getStationTrains } from './utils';
|
|
||||||
|
|
||||||
import { OnlineScenery, ScheduledTrain, StoreState } from './typings';
|
import {
|
||||||
|
ActiveScenery,
|
||||||
import { Status } from '../typings/common';
|
CheckpointTrain,
|
||||||
import Station from '../scripts/interfaces/Station';
|
Station,
|
||||||
|
StationRoutes,
|
||||||
|
Status,
|
||||||
|
Train
|
||||||
|
} from '../typings/common';
|
||||||
import { useApiStore } from './apiStore';
|
import { useApiStore } from './apiStore';
|
||||||
import { API } from '../typings/api';
|
import { MainStoreState } from './typings';
|
||||||
import { StationRoutes } from '../scripts/interfaces/StationRoutes';
|
|
||||||
|
|
||||||
export const useMainStore = defineStore('store', {
|
const checkpointsTrains: Map<string, CheckpointTrain[]> = new Map();
|
||||||
|
const sceneriesTrains: Map<string, Train[]> = new Map();
|
||||||
|
|
||||||
|
export const useMainStore = defineStore('mainStore', {
|
||||||
state: () =>
|
state: () =>
|
||||||
({
|
({
|
||||||
region: { id: 'eu', value: 'PL1' },
|
region: { id: 'eu', value: 'PL1', name: 'PL1' },
|
||||||
|
|
||||||
isOffline: false,
|
isOffline: false,
|
||||||
|
appUpdate: null,
|
||||||
|
|
||||||
dispatcherStatsName: '',
|
dispatcherStatsName: '',
|
||||||
dispatcherStatsStatus: Status.Data.Initialized,
|
dispatcherStatsStatus: Status.Data.Initialized,
|
||||||
@@ -26,14 +32,16 @@ export const useMainStore = defineStore('store', {
|
|||||||
|
|
||||||
chosenModalTrainId: undefined,
|
chosenModalTrainId: undefined,
|
||||||
|
|
||||||
blockScroll: false,
|
|
||||||
modalLastClickedTarget: null
|
modalLastClickedTarget: null
|
||||||
}) as StoreState,
|
}) as MainStoreState,
|
||||||
|
|
||||||
getters: {
|
getters: {
|
||||||
trainList(): Train[] {
|
trainList(): Train[] {
|
||||||
const apiStore = useApiStore();
|
const apiStore = useApiStore();
|
||||||
|
|
||||||
|
checkpointsTrains.clear();
|
||||||
|
sceneriesTrains.clear();
|
||||||
|
|
||||||
return (apiStore.activeData?.trains ?? [])
|
return (apiStore.activeData?.trains ?? [])
|
||||||
.filter((train) => train.timetable || train.online)
|
.filter((train) => train.timetable || train.online)
|
||||||
.map((train) => {
|
.map((train) => {
|
||||||
@@ -42,8 +50,18 @@ export const useMainStore = defineStore('store', {
|
|||||||
|
|
||||||
const timetable = train.timetable;
|
const timetable = train.timetable;
|
||||||
|
|
||||||
return {
|
const sceneryNames =
|
||||||
trainId: train.driverName + train.trainNo.toString(),
|
train.timetable?.sceneries?.map(
|
||||||
|
(sceneryHash) =>
|
||||||
|
apiStore.activeData?.activeSceneries?.find((st) => st.stationHash === sceneryHash)
|
||||||
|
?.stationName ??
|
||||||
|
apiStore.sceneryData.find((sd) => sd.hash === sceneryHash)?.name ??
|
||||||
|
sceneryHash
|
||||||
|
) ?? [];
|
||||||
|
|
||||||
|
const trainObj = {
|
||||||
|
id: train.id,
|
||||||
|
modalId: `${train.driverName}${train.trainNo}`, // simplified id for train modal
|
||||||
|
|
||||||
trainNo: train.trainNo,
|
trainNo: train.trainNo,
|
||||||
mass: train.mass,
|
mass: train.mass,
|
||||||
@@ -77,43 +95,99 @@ export const useMainStore = 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;
|
||||||
|
|
||||||
|
// Sceneries trains map
|
||||||
|
if (sceneriesTrains.has(train.currentStationName)) {
|
||||||
|
sceneriesTrains.set(train.currentStationName, [
|
||||||
|
...sceneriesTrains.get(train.currentStationName)!,
|
||||||
|
trainObj
|
||||||
|
]);
|
||||||
|
} else sceneriesTrains.set(train.currentStationName, [trainObj]);
|
||||||
|
|
||||||
|
// Checkpoints trains map
|
||||||
|
timetable?.stopList.forEach((stop, i) => {
|
||||||
|
if (/strong|podg\.|pe\./.test(stop.stopName)) {
|
||||||
|
const checkpointTrain: CheckpointTrain = {
|
||||||
|
train: trainObj,
|
||||||
|
checkpointStop: stop
|
||||||
|
};
|
||||||
|
|
||||||
|
if (checkpointsTrains.has(stop.stopNameRAW.toLowerCase())) {
|
||||||
|
checkpointsTrains.set(stop.stopNameRAW.toLowerCase(), [
|
||||||
|
...checkpointsTrains.get(stop.stopNameRAW.toLowerCase())!,
|
||||||
|
checkpointTrain
|
||||||
|
]);
|
||||||
|
} else checkpointsTrains.set(stop.stopNameRAW.toLowerCase(), [checkpointTrain]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return trainObj;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onlineSceneryList(state): OnlineScenery[] {
|
// computed active sceneries
|
||||||
|
activeSceneryList(state): ActiveScenery[] {
|
||||||
const apiStore = useApiStore();
|
const apiStore = useApiStore();
|
||||||
|
|
||||||
if (state.isOffline) return [];
|
if (state.isOffline) return [];
|
||||||
|
|
||||||
if (!apiStore.activeData?.activeSceneries) return [];
|
if (!apiStore.activeData?.activeSceneries) return [];
|
||||||
|
|
||||||
return apiStore.activeData?.activeSceneries.reduce((list, scenery) => {
|
const offlineActiveSceneries = this.trainList.reduce((acc, train) => {
|
||||||
|
if (!train.timetableData) return acc;
|
||||||
|
|
||||||
|
train.timetableData.sceneryNames.forEach((name) => {
|
||||||
|
if (
|
||||||
|
acc.findIndex((v) => v.name == name && v.region == train.region) != -1 ||
|
||||||
|
apiStore.activeData?.activeSceneries?.findIndex(
|
||||||
|
(sc) =>
|
||||||
|
sc.stationName === name &&
|
||||||
|
sc.region == train.region &&
|
||||||
|
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,
|
||||||
|
dispatcherStatus: Status.ActiveDispatcher.FREE,
|
||||||
|
dispatcherTimestamp: -1,
|
||||||
|
|
||||||
|
isOnline: false,
|
||||||
|
|
||||||
|
stationTrains: [],
|
||||||
|
scheduledTrains: [],
|
||||||
|
|
||||||
|
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
|
||||||
@@ -133,51 +207,108 @@ export const useMainStore = 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,
|
||||||
|
|
||||||
|
stationTrains: [],
|
||||||
|
scheduledTrains: [],
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
|
let checkpointsSet: Set<string> = new Set();
|
||||||
|
|
||||||
|
// Add checkpoints to active scenery data
|
||||||
|
checkpointsSet.add(scenery.name.toLowerCase());
|
||||||
|
|
||||||
|
station?.generalInfo?.checkpoints.forEach((cpName) => {
|
||||||
|
checkpointsSet.add(cpName.toLowerCase());
|
||||||
|
});
|
||||||
|
|
||||||
|
const checkpoints = Array.from(checkpointsSet);
|
||||||
|
|
||||||
|
scenery.stationTrains =
|
||||||
|
sceneriesTrains.get(scenery.name)?.filter((sc) => sc.region == this.region.id) ?? [];
|
||||||
|
|
||||||
|
const uniqueTrainIds: string[] = [];
|
||||||
|
checkpoints.forEach((cp) => {
|
||||||
|
const scheduledTrains = checkpointsTrains.get(cp.toLowerCase());
|
||||||
|
|
||||||
|
if (!scheduledTrains) return;
|
||||||
|
|
||||||
|
scheduledTrains.forEach(({ train, checkpointStop }) => {
|
||||||
|
scenery.scheduledTrains.push({ train, checkpointStop });
|
||||||
|
|
||||||
|
if (uniqueTrainIds.includes(train.id) || train.region != this.region.id) return;
|
||||||
|
|
||||||
|
scenery.scheduledTrainCount.all += 1;
|
||||||
|
|
||||||
|
if (checkpointStop.confirmed) scenery.scheduledTrainCount.confirmed++;
|
||||||
|
else scenery.scheduledTrainCount.unconfirmed++;
|
||||||
|
|
||||||
|
uniqueTrainIds.push(train.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return allActiveSceneries;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// computed station data
|
||||||
stationList(): Station[] {
|
stationList(): Station[] {
|
||||||
const apiStore = useApiStore();
|
const apiStore = useApiStore();
|
||||||
|
|
||||||
return apiStore.sceneryData.map((scenery) => {
|
return apiStore.sceneryData.map((scenery) => {
|
||||||
const routes = scenery.routesInfo.reduce(
|
const routes = scenery.routesInfo.reduce(
|
||||||
(acc, route) => {
|
(acc, route) => {
|
||||||
const tracksKey = route.routeTracks == 2 ? 'twoWay' : 'oneWay';
|
if (route.hidden) return acc;
|
||||||
|
|
||||||
|
const tracksKey = route.routeTracks == 2 ? 'double' : 'single';
|
||||||
const isElectric = route.isElectric;
|
const isElectric = route.isElectric;
|
||||||
const routesKey: keyof StationRoutes = `${tracksKey}${
|
const routesKey: keyof StationRoutes = `${tracksKey}${
|
||||||
!isElectric ? 'No' : ''
|
!isElectric ? 'Other' : 'Electrified'
|
||||||
}CatenaryRouteNames`;
|
}Names`;
|
||||||
|
|
||||||
if (!route.isInternal) acc[routesKey].push(route.routeName);
|
if (!route.isInternal) acc[routesKey].push(route.routeName);
|
||||||
if (route.isRouteSBL) acc['sblRouteNames'].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);
|
acc[tracksKey].push(route);
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
oneWay: [],
|
single: [],
|
||||||
oneWayCatenaryRouteNames: [],
|
singleElectrifiedNames: [],
|
||||||
oneWayNoCatenaryRouteNames: [],
|
singleOtherNames: [],
|
||||||
twoWay: [],
|
double: [],
|
||||||
twoWayCatenaryRouteNames: [],
|
doubleElectrifiedNames: [],
|
||||||
twoWayNoCatenaryRouteNames: [],
|
doubleOtherNames: [],
|
||||||
sblRouteNames: []
|
sblNames: [],
|
||||||
|
minRouteSpeed: 0,
|
||||||
|
maxRouteSpeed: 0
|
||||||
} as StationRoutes
|
} as StationRoutes
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -188,105 +319,37 @@ export const useMainStore = defineStore('store', {
|
|||||||
...scenery,
|
...scenery,
|
||||||
authors: scenery.authors?.split(',').map((a) => a.trim()),
|
authors: scenery.authors?.split(',').map((a) => a.trim()),
|
||||||
routes: routes,
|
routes: routes,
|
||||||
checkpoints: scenery.checkpoints
|
checkpoints:
|
||||||
? scenery.checkpoints
|
scenery.checkpoints && scenery.checkpoints.trim().length > 0
|
||||||
.split(';')
|
? scenery.checkpoints.split(';')
|
||||||
.map((sub) => ({ checkpointName: sub, scheduledTrains: [] }))
|
|
||||||
: []
|
: []
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
|
||||||
},
|
},
|
||||||
actions: {
|
|
||||||
async processStationsOnlineInfo(activeData: API.ActiveData.Response) {
|
|
||||||
if (!activeData.activeSceneries) return;
|
|
||||||
|
|
||||||
const onlineSceneries = activeData.activeSceneries.reduce((acc, scenery) => {
|
allStationInfo(): Station[] {
|
||||||
const savedStation = this.stationList.find((st) => scenery.stationName === st.name);
|
const onlineUnsavedStations = this.activeSceneryList
|
||||||
|
|
||||||
if (scenery.isOnline !== 1 && Date.now() - scenery.lastSeen > 1000 * 60 * 2) return acc;
|
|
||||||
if (scenery.dispatcherStatus == Status.ActiveDispatcher.UNKNOWN) return acc;
|
|
||||||
|
|
||||||
const station = this.stationList.find((s) => s.name === scenery.stationName);
|
|
||||||
|
|
||||||
const scheduledTrains = getScheduledTrains(this.trainList, scenery, station?.generalInfo);
|
|
||||||
|
|
||||||
const stationTrains = getStationTrains(
|
|
||||||
this.trainList,
|
|
||||||
scheduledTrains,
|
|
||||||
this.region.id,
|
|
||||||
scenery
|
|
||||||
);
|
|
||||||
|
|
||||||
// Remove checkpoint duplicates
|
|
||||||
const uniqueScheduledTrains = scheduledTrains.reduce(
|
|
||||||
(uniqueList, sTrain) =>
|
|
||||||
uniqueList.find((v) => v.trainId === sTrain.trainId)
|
|
||||||
? uniqueList
|
|
||||||
: [...uniqueList, sTrain],
|
|
||||||
[] as ScheduledTrain[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const dispatcherTimestamp =
|
|
||||||
scenery.dispatcherStatus == Status.ActiveDispatcher.NO_LIMIT
|
|
||||||
? Date.now() + 25500000
|
|
||||||
: scenery.dispatcherStatus > 5
|
|
||||||
? scenery.dispatcherStatus
|
|
||||||
: null;
|
|
||||||
|
|
||||||
const onlineInfo = {
|
|
||||||
name: scenery.stationName,
|
|
||||||
hash: scenery.stationHash,
|
|
||||||
region: scenery.region,
|
|
||||||
maxUsers: scenery.maxUsers,
|
|
||||||
currentUsers: scenery.currentUsers,
|
|
||||||
spawns: parseSpawns(scenery.spawnString),
|
|
||||||
dispatcherName: scenery.dispatcherName,
|
|
||||||
dispatcherRate: scenery.dispatcherRate,
|
|
||||||
dispatcherId: scenery.dispatcherId,
|
|
||||||
dispatcherExp: scenery.dispatcherExp,
|
|
||||||
dispatcherIsSupporter: scenery.dispatcherIsSupporter,
|
|
||||||
scheduledTrains: scheduledTrains,
|
|
||||||
stationTrains: stationTrains,
|
|
||||||
dispatcherStatus: scenery.dispatcherStatus,
|
|
||||||
dispatcherTimestamp: dispatcherTimestamp,
|
|
||||||
|
|
||||||
isOnline: scenery.isOnline == 1,
|
|
||||||
|
|
||||||
scheduledTrainCount: {
|
|
||||||
all: uniqueScheduledTrains.length,
|
|
||||||
confirmed: uniqueScheduledTrains.filter((train) => train.stopInfo.confirmed).length,
|
|
||||||
unconfirmed: uniqueScheduledTrains.filter((train) => !train.stopInfo.confirmed).length
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (savedStation) savedStation.onlineInfo = onlineInfo;
|
|
||||||
else
|
|
||||||
this.stationList.push({
|
|
||||||
name: onlineInfo.name,
|
|
||||||
onlineInfo: onlineInfo
|
|
||||||
});
|
|
||||||
|
|
||||||
acc.push(onlineInfo);
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, [] as OnlineScenery[]);
|
|
||||||
|
|
||||||
// Reset online info of already offline sceneries
|
|
||||||
this.stationList
|
|
||||||
.filter(
|
.filter(
|
||||||
(station) =>
|
(scenery) =>
|
||||||
station.onlineInfo &&
|
this.stationList.findIndex((st) => st.name == scenery.name) == -1 &&
|
||||||
onlineSceneries.findIndex(
|
scenery.region == this.region.id
|
||||||
(os) => os.region == station.onlineInfo!.region && station.name == os.name
|
|
||||||
) != -1
|
|
||||||
)
|
)
|
||||||
.forEach((station) => (station.onlineInfo = undefined));
|
.map((os) => ({
|
||||||
},
|
name: os.name,
|
||||||
|
generalInfo: undefined,
|
||||||
|
onlineInfo: os
|
||||||
|
}));
|
||||||
|
|
||||||
async changeRegion(region: StoreState['region']) {
|
return [
|
||||||
this.region = region;
|
...onlineUnsavedStations,
|
||||||
|
...this.stationList.map((st) => ({
|
||||||
|
...st,
|
||||||
|
onlineInfo: this.activeSceneryList.find(
|
||||||
|
(os) => os.name == st.name && os.region == this.region.id
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||