Compare commits
134 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0ac7ba51e5 | |||
| bdf85cd8ec | |||
| 63b268d9b9 | |||
| d73c8ef112 | |||
| 3d1c66b420 | |||
| b3f7108979 | |||
| feabfd29e0 | |||
| f17fedc976 | |||
| c83c75e014 | |||
| e57143f517 | |||
| 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 |
@@ -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.22.1",
|
"version": "1.25.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
@@ -14,11 +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",
|
||||||
"socket.io-client": "^4.7.4",
|
"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"
|
||||||
@@ -26,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",
|
||||||
@@ -37,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": [
|
||||||
|
|||||||
|
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,8 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="app_container" v-cloak>
|
<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,21 +42,23 @@
|
|||||||
</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 { version } 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';
|
const STORAGE_VERSION_KEY = 'app_version';
|
||||||
|
|
||||||
@@ -55,17 +67,23 @@ export default defineComponent({
|
|||||||
Clock,
|
Clock,
|
||||||
StatusIndicator,
|
StatusIndicator,
|
||||||
AppHeader,
|
AppHeader,
|
||||||
TrainModal
|
TrainModal,
|
||||||
|
UpdateCard,
|
||||||
|
Tooltip
|
||||||
},
|
},
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
VERSION: 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() {
|
||||||
@@ -73,41 +91,53 @@ 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.checkAppVersion();
|
||||||
|
|
||||||
this.apiStore.setupAPIData();
|
this.apiStore.setupAPIData();
|
||||||
|
window.requestAnimationFrame(this.update);
|
||||||
|
|
||||||
if (!this.isOnProductionHost) document.title = 'Stacjownik Dev';
|
if (!this.isOnProductionHost) document.title = 'Stacjownik Dev';
|
||||||
},
|
},
|
||||||
|
|
||||||
checkAppVersion() {
|
update(t: number) {
|
||||||
if (import.meta.env.DEV) {
|
if (t >= this.nextUpdateTime) {
|
||||||
this.store.isNewUpdate = true;
|
this.apiStore.fetchActiveData();
|
||||||
|
this.nextUpdateTime = t + 20000;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
window.requestAnimationFrame(this.update);
|
||||||
|
},
|
||||||
|
|
||||||
|
async checkAppVersion() {
|
||||||
const storageVersion = StorageManager.getStringValue(STORAGE_VERSION_KEY);
|
const storageVersion = StorageManager.getStringValue(STORAGE_VERSION_KEY);
|
||||||
|
|
||||||
if (storageVersion === undefined || storageVersion != version) {
|
try {
|
||||||
this.store.isNewUpdate = true;
|
const releaseData = await (
|
||||||
|
await axios.get('https://api.github.com/repos/Spythere/stacjownik/releases/latest')
|
||||||
|
).data;
|
||||||
|
|
||||||
StorageManager.setStringValue(STORAGE_VERSION_KEY, version);
|
if (!releaseData) return;
|
||||||
|
|
||||||
|
this.store.appUpdate = {
|
||||||
|
version,
|
||||||
|
changelog: releaseData.body,
|
||||||
|
releaseURL: releaseData.html_url
|
||||||
|
};
|
||||||
|
|
||||||
|
this.isUpdateCardOpen =
|
||||||
|
(storageVersion != '' && 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() {
|
||||||
@@ -139,21 +169,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');
|
||||||
|
|
||||||
@@ -166,7 +181,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;
|
||||||
}
|
}
|
||||||
@@ -196,7 +211,7 @@ export default defineComponent({
|
|||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
|
||||||
@include smallScreen() {
|
@include smallScreen() {
|
||||||
font-size: calc(0.65rem + 0.8vw);
|
font-size: calc(0.65rem + 0.85vw);
|
||||||
}
|
}
|
||||||
|
|
||||||
@include screenLandscape() {
|
@include screenLandscape() {
|
||||||
@@ -211,6 +226,8 @@ export default defineComponent({
|
|||||||
grid-template-columns: 100%;
|
grid-template-columns: 100%;
|
||||||
|
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app_main {
|
.app_main {
|
||||||
@@ -228,10 +245,15 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FOOTER
|
// FOOTER
|
||||||
footer.app_footer {
|
.app_footer {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
|
|
||||||
|
button {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.1em;
|
||||||
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 1.1em;
|
width: 1.1em;
|
||||||
vertical-align: text-bottom;
|
vertical-align: text-bottom;
|
||||||
|
|||||||
@@ -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" />
|
||||||
@@ -103,12 +98,6 @@ export default defineComponent({
|
|||||||
return this.store.activeSceneryList.filter(
|
return this.store.activeSceneryList.filter(
|
||||||
(scenery) => scenery.region == this.store.region.id && scenery.dispatcherId != -1
|
(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 }
|
||||||
|
|||||||
@@ -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,48 +0,0 @@
|
|||||||
<template>
|
|
||||||
<AnimatedModal :is-open="mainStore.isNewUpdate" @toggle-modal="toggleModal">
|
|
||||||
<div class="modal_content">
|
|
||||||
<h1 class="header">Aktualizacja Stacjownika</h1>
|
|
||||||
<h2>wersja {{ version }}</h2>
|
|
||||||
|
|
||||||
<b>Co nowego?</b>
|
|
||||||
<p>
|
|
||||||
<ul>
|
|
||||||
<li>test</li>
|
|
||||||
</ul>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</AnimatedModal>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
import { useMainStore } from '../../store/mainStore';
|
|
||||||
import { version } from '../../../package.json';
|
|
||||||
import AnimatedModal from '../Global/AnimatedModal.vue';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: { AnimatedModal },
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
mainStore: useMainStore(),
|
|
||||||
version: version
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
toggleModal(value: boolean) {
|
|
||||||
this.$emit('toggleModal', value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.modal_content {
|
|
||||||
text-align: center;
|
|
||||||
padding: 1em;
|
|
||||||
height: 80vh;
|
|
||||||
min-height: 550px;
|
|
||||||
}
|
|
||||||
</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,12 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<AnimatedModal
|
<Card :isOpen="isCardOpen" @toggleCard="toggleCard" @keydown.esc="toggleCard(false)">
|
||||||
class="donation-modal"
|
<div class="body">
|
||||||
:isOpen="isModalOpen"
|
<div class="content">
|
||||||
@toggleModal="toggleModal"
|
|
||||||
@keydown.esc="toggleModal(false)"
|
|
||||||
>
|
|
||||||
<div class="modal_content">
|
|
||||||
<div class="modal_main">
|
|
||||||
<h1 v-html="$t('donations.header')"></h1>
|
<h1 v-html="$t('donations.header')"></h1>
|
||||||
<div class="donators-slider" v-if="donatorList.length != 0">
|
<div class="donators-slider" v-if="donatorList.length != 0">
|
||||||
<span v-html="$t('donations.donator-title', { count: donatorList.length })"></span>
|
<span v-html="$t('donations.donator-title', { count: donatorList.length })"></span>
|
||||||
@@ -61,18 +56,19 @@
|
|||||||
</i>
|
</i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal_actions">
|
<div class="actions">
|
||||||
<a
|
<a
|
||||||
class="modal-action a-button btn--image coffee"
|
class="action a-button btn--image coffee"
|
||||||
href="https://buycoffee.to/spythere"
|
href="https://buycoffee.to/spythere"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
ref="action"
|
||||||
>
|
>
|
||||||
<img src="/images/icon-coffee.png" width="20" alt="buycoffee.to donation" />
|
<img src="/images/icon-coffee.png" width="20" alt="buycoffee.to donation" />
|
||||||
{{ $t('donations.action-buycoffee') }}
|
{{ $t('donations.action-buycoffee') }}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a
|
<a
|
||||||
class="modal-action a-button btn--image paypal"
|
class="action a-button btn--image paypal"
|
||||||
href="https://www.paypal.com/donate/?hosted_button_id=EDB3SKFAHXFTW"
|
href="https://www.paypal.com/donate/?hosted_button_id=EDB3SKFAHXFTW"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
@@ -80,32 +76,36 @@
|
|||||||
{{ $t('donations.action-paypal') }}
|
{{ $t('donations.action-paypal') }}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<button class="modal-action btn--image exit" @click="toggleModal(false)">
|
<button class="action btn--image exit" @click="toggleCard(false)">
|
||||||
<img src="/images/icon-exit.svg" alt="dollar donation icon" />
|
<img src="/images/icon-exit.svg" alt="dollar donation icon" />
|
||||||
{{ $t('donations.action-exit') }}
|
{{ $t('donations.action-exit') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</AnimatedModal>
|
</Card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import AnimatedModal from './AnimatedModal.vue';
|
|
||||||
import { useApiStore } from '../../store/apiStore';
|
import { useApiStore } from '../../store/apiStore';
|
||||||
|
import Card from './Card.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { AnimatedModal },
|
components: { Card },
|
||||||
props: {
|
props: {
|
||||||
isModalOpen: Boolean
|
isCardOpen: Boolean
|
||||||
},
|
},
|
||||||
|
|
||||||
emits: ['toggleModal'],
|
emits: ['toggleCard'],
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
isModalOpen(b: boolean) {
|
isCardOpen(val: boolean) {
|
||||||
this.running = b;
|
this.running = val;
|
||||||
this.lastUpdate = Date.now();
|
this.lastUpdate = Date.now();
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (val) (this.$refs['action'] as HTMLElement).focus();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -133,8 +133,8 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
toggleModal(value: boolean) {
|
toggleCard(value: boolean) {
|
||||||
this.$emit('toggleModal', value);
|
this.$emit('toggleCard', value);
|
||||||
},
|
},
|
||||||
|
|
||||||
runUpdate() {
|
runUpdate() {
|
||||||
@@ -152,53 +152,53 @@ export default defineComponent({
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../styles/responsive.scss';
|
@import '../../styles/responsive.scss';
|
||||||
|
|
||||||
.modal_content {
|
.body {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: 1fr auto;
|
grid-template-rows: 1fr auto;
|
||||||
gap: 1em;
|
gap: 1em;
|
||||||
|
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
|
|
||||||
& > div {
|
max-width: 820px;
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 1.95em;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
text-align: justify;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.discord {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal_main {
|
.content {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
padding: 1em;
|
||||||
img {
|
|
||||||
max-height: 20px;
|
|
||||||
margin-right: 5px;
|
|
||||||
vertical-align: text-bottom;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal_actions {
|
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;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
|
padding: 1em;
|
||||||
|
|
||||||
form button {
|
form button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal_actions > .modal-action {
|
.actions > .action {
|
||||||
&.paypal {
|
&.paypal {
|
||||||
$btnColor: #254069;
|
$btnColor: #254069;
|
||||||
|
|
||||||
@@ -65,12 +65,12 @@ export default defineComponent({
|
|||||||
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];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -84,7 +84,7 @@ export default defineComponent({
|
|||||||
regionList() {
|
regionList() {
|
||||||
return regionsJSON.map((region) => {
|
return regionsJSON.map((region) => {
|
||||||
const regionStationCount = this.store.activeSceneryList.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 =
|
||||||
@@ -139,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 {
|
||||||
@@ -197,6 +192,8 @@ li.option {
|
|||||||
}
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5em 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@@ -207,10 +204,6 @@ li.option {
|
|||||||
background-color: #333333f2;
|
background-color: #333333f2;
|
||||||
}
|
}
|
||||||
|
|
||||||
padding: 0.5em 0;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,65 +1,26 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="stock-list">
|
<div class="stock-list">
|
||||||
<div v-if="tractionOnly">
|
<ul>
|
||||||
<p>
|
<li
|
||||||
{{ computedStockList[0].split(':')[0].split('_').splice(0, 2).join(' ') }}
|
v-for="({ vehicleName, vehicleCargo, images, imagesFallbacks }, i) in thumbnailNames"
|
||||||
{{ computedStockList[0].split(':')[1] }}
|
:key="i"
|
||||||
</p>
|
>
|
||||||
|
<div class="stock-text">
|
||||||
<img
|
<p>{{ vehicleName.replace(/_/g, ' ') }}</p>
|
||||||
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${computedStockList[0].split(':')[0]}${
|
<small v-if="vehicleCargo">({{ vehicleCargo }})</small>
|
||||||
/^EN/.test(computedStockList[0]) ? 'rb' : ''
|
</div>
|
||||||
}.png`"
|
|
||||||
@error="onImageError($event, computedStockList[0])"
|
|
||||||
width="400"
|
|
||||||
height="60"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul v-else>
|
|
||||||
<li v-for="(stockName, i) in computedStockList" :key="i">
|
|
||||||
<p>
|
|
||||||
{{ stockName.split(':')[0].split('_').splice(0, 2).join(' ') }}
|
|
||||||
{{ stockName.split(':')[1] }}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<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/v2/${thumbnailImage}.png`"
|
||||||
|
@error="onImageError($event, imagesFallbacks[imageIndex])"
|
||||||
|
@click.stop="() => {}"
|
||||||
height="60"
|
height="60"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- /// Manualne dodawanie miniaturek członów dla kibelków /// -->
|
|
||||||
<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>
|
||||||
@@ -91,32 +52,116 @@ export default defineComponent({
|
|||||||
computed: {
|
computed: {
|
||||||
computedStockList() {
|
computedStockList() {
|
||||||
return this.tractionOnly ? this.trainStockList.slice(0, 1) : this.trainStockList;
|
return this.tractionOnly ? this.trainStockList.slice(0, 1) : this.trainStockList;
|
||||||
|
},
|
||||||
|
|
||||||
|
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: {
|
methods: {
|
||||||
onImageError(event: Event, stockName: string) {
|
onImageError(event: Event, fallbackImage: string) {
|
||||||
let fallbackName = '';
|
(event.target as HTMLImageElement).src = `/images/${fallbackImage}.png`;
|
||||||
|
|
||||||
const isLoco = /.-\d{3}/.test(stockName);
|
|
||||||
|
|
||||||
if (isLoco) {
|
|
||||||
fallbackName += 'loco-';
|
|
||||||
fallbackName += /^\d?EN\d{2}/.test(stockName)
|
|
||||||
? 'ezt'
|
|
||||||
: /^SN\d{2}/.test(stockName)
|
|
||||||
? 'szt'
|
|
||||||
: /^\d?E/.test(stockName)
|
|
||||||
? 'e'
|
|
||||||
: 's';
|
|
||||||
} else {
|
|
||||||
const isCarPassenger = /(\d{3}a|(Bau|Gor)\d{2}|304C)_/.test(stockName);
|
|
||||||
|
|
||||||
fallbackName += 'car-';
|
|
||||||
fallbackName += isCarPassenger ? 'passenger' : 'cargo';
|
|
||||||
}
|
|
||||||
|
|
||||||
(event.target as HTMLImageElement).src = `/images/icon-${fallbackName}.png`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -139,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 {
|
||||||
@@ -147,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.95em;
|
font-size: 0.9em;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 0.25em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
@@ -301,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,173 +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(';')
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
|
|
||||||
</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')"
|
||||||
>
|
>
|
||||||
@@ -62,10 +57,19 @@
|
|||||||
!timetable.terminated
|
!timetable.terminated
|
||||||
? $t('journal.timetable-active')
|
? $t('journal.timetable-active')
|
||||||
: timetable.fulfilled
|
: timetable.fulfilled
|
||||||
? $t('journal.timetable-fulfilled')
|
? $t('journal.timetable-fulfilled')
|
||||||
: `${$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 {
|
||||||
|
|||||||
@@ -1,23 +1,41 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="stop-list" v-if="showExtraInfo == true">
|
<div class="timetable-stops">
|
||||||
<span
|
<div class="stop-list">
|
||||||
v-for="(stop, i) in timetableStops.filter((_, i) =>
|
<span
|
||||||
!showExtraInfo ? i == 0 || i == timetableStops.length - 1 : true
|
v-for="(stop, i) in timetableStops.filter((_, i) =>
|
||||||
)"
|
!showExtraInfo ? i == 0 || i == timetableStops.length - 1 : true
|
||||||
class="stop-list-item"
|
)"
|
||||||
:key="stop.stopName"
|
class="stop-list-item"
|
||||||
:data-confirmed="stop.confirmed"
|
:key="stop.stopName"
|
||||||
>
|
:data-confirmed="stop.confirmed"
|
||||||
<span v-if="i > 0">
|
>
|
||||||
>
|
<span v-if="i > 0">
|
||||||
<span v-if="!showExtraInfo && i == 1 && timetableStops.length > 2">
|
>
|
||||||
... (+{{ timetableStops.length - 2 }}) >
|
<span v-if="!showExtraInfo && i == 1 && timetableStops.length > 2">
|
||||||
|
... (+{{ timetableStops.length - 2 }}) >
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="stop-name">{{ stop.stopName }}</span>
|
||||||
|
<span v-html="stop.html"></span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="path-details" v-if="showExtraInfo && timetablePathDetails">
|
||||||
|
<span
|
||||||
|
v-for="(pathData, i) in timetablePathDetails"
|
||||||
|
:data-visited="pathData.isVisited"
|
||||||
|
:data-next-visited="
|
||||||
|
i < timetablePathDetails.length - 1 && timetablePathDetails[i + 1].isVisited
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<span class="path-arrival" v-if="pathData.arrival">/ {{ pathData.arrival }} → </span>
|
||||||
|
<b class="path-scenery">{{ pathData.sceneryName }}</b>
|
||||||
|
<span class="path-departure" v-if="pathData.departure">
|
||||||
|
→ {{ pathData.departure }}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
</div>
|
||||||
<span class="stop-name">{{ stop.stopName }}</span>
|
|
||||||
<span v-html="stop.html"></span>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -42,6 +60,24 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
timetablePathDetails() {
|
||||||
|
if (!this.timetable.path || this.timetable.path == '') return null;
|
||||||
|
|
||||||
|
return this.timetable.path.split(';').map((pathEl, i) => {
|
||||||
|
const [arrival, name, departure] = pathEl.split(',');
|
||||||
|
const sceneryName = name.split(' ').slice(0, -1).join(' ');
|
||||||
|
const sceneryHash = name.split(' ').pop()?.replace('.sc', '') ?? '';
|
||||||
|
|
||||||
|
return {
|
||||||
|
arrival,
|
||||||
|
sceneryName,
|
||||||
|
sceneryHash,
|
||||||
|
departure,
|
||||||
|
isVisited: this.timetable.visitedSceneries?.includes(sceneryHash) ?? false
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
timetableStops() {
|
timetableStops() {
|
||||||
const timetable = this.timetable;
|
const timetable = this.timetable;
|
||||||
|
|
||||||
@@ -94,13 +130,14 @@ export default defineComponent({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.stop-list {
|
.timetable-stops {
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
gap: 0.25em;
|
gap: 0.25em;
|
||||||
font-size: 0.95em;
|
font-size: 0.95em;
|
||||||
|
|
||||||
color: #adadad;
|
color: #adadad;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stop-list {
|
||||||
&-item[data-confirmed='true'] {
|
&-item[data-confirmed='true'] {
|
||||||
color: lightgreen;
|
color: lightgreen;
|
||||||
|
|
||||||
@@ -109,4 +146,19 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.path-details {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-details > span[data-visited='true'] {
|
||||||
|
.path-arrival,
|
||||||
|
.path-scenery {
|
||||||
|
color: lightgreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-next-visited='true'] .path-departure {
|
||||||
|
color: lightgreen;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<!-- Status -->
|
<!-- Status -->
|
||||||
<TimetableStatus :timetable="timetable" />
|
<TimetableStatus :timetable="timetable" />
|
||||||
|
|
||||||
<button class="btn--option btn--show">
|
<button class="btn--action btn--show">
|
||||||
{{ $t('journal.stock-info') }}
|
{{ $t('journal.stock-info') }}
|
||||||
<img
|
<img
|
||||||
:src="`/images/icon-arrow-${showExtraInfo.value ? 'asc' : 'desc'}.svg`"
|
:src="`/images/icon-arrow-${showExtraInfo.value ? 'asc' : 'desc'}.svg`"
|
||||||
@@ -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">
|
||||||
<Loading v-if="dataStatus != DataStatus.Loaded && historyList.length == 0" />
|
<div class="history-wrapper">
|
||||||
|
<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,55 +22,67 @@
|
|||||||
>
|
>
|
||||||
{{ 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) }})
|
||||||
</div>
|
</span>
|
||||||
</td>
|
</span>
|
||||||
</tr>
|
</div>
|
||||||
</tbody>
|
</div>
|
||||||
</table>
|
</div>
|
||||||
</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 { ActiveScenery } from '../../store/typings';
|
|
||||||
import { API } from '../../typings/api';
|
import { API } from '../../typings/api';
|
||||||
import { Status } from '../../typings/common';
|
import { ActiveScenery, Station, Status } from '../../typings/common';
|
||||||
import { useApiStore } from '../../store/apiStore';
|
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: {
|
||||||
@@ -152,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 {
|
||||||
@@ -161,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 { ActiveScenery } from '../../store/typings';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
|
|||||||
@@ -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 { ActiveScenery } from '../../store/typings';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
>
|
>
|
||||||
<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 }}
|
||||||
@@ -49,11 +49,18 @@ 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 { ActiveScenery } 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<ActiveScenery>,
|
type: Object as PropType<ActiveScenery>,
|
||||||
|
|||||||
@@ -24,8 +24,8 @@
|
|||||||
:title="
|
:title="
|
||||||
$t('sceneries.info.control-type') + $t(`controls.${station?.generalInfo.controlType}`)
|
$t('sceneries.info.control-type') + $t(`controls.${station?.generalInfo.controlType}`)
|
||||||
"
|
"
|
||||||
v-html="getControlTypeAbbrev(station?.generalInfo.controlType)"
|
|
||||||
>
|
>
|
||||||
|
{{ $t(`controls.abbrevs.${station.generalInfo.controlType}`) }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<img
|
<img
|
||||||
@@ -88,12 +88,11 @@
|
|||||||
|
|
||||||
<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>
|
||||||
@@ -104,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;
|
||||||
@@ -111,6 +111,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
margin: 1em;
|
margin: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-info {
|
.icon-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { PropType, defineComponent } from 'vue';
|
import { PropType, defineComponent } from 'vue';
|
||||||
import Station from '../../../scripts/interfaces/Station';
|
import { Station } from '../../../typings/common';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
|
|||||||
@@ -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,7 +30,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { PropType, defineComponent } from 'vue';
|
import { PropType, defineComponent } from 'vue';
|
||||||
import { ActiveScenery } from '../../../store/typings';
|
import { ActiveScenery } from '../../../typings/common';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
@@ -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,7 +32,9 @@
|
|||||||
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 { ActiveScenery } 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],
|
||||||
@@ -41,6 +43,40 @@ export default defineComponent({
|
|||||||
onlineScenery: {
|
onlineScenery: {
|
||||||
type: Object as PropType<ActiveScenery>,
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,14 +14,6 @@
|
|||||||
</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>
|
||||||
@@ -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 { ActiveScenery } 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',
|
||||||
@@ -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] ?? null
|
route.query['station']?.toString() ??
|
||||||
|
''
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -250,27 +235,50 @@ 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().replace(/_/g, ' ') ?? '';
|
||||||
this.onlineScenery?.scheduledTrains
|
|
||||||
?.filter(
|
|
||||||
(train) =>
|
|
||||||
train.checkpointName.toLocaleLowerCase() ==
|
|
||||||
(this.chosenCheckpoint || this.station!.name).toLocaleLowerCase() &&
|
|
||||||
train.region == this.mainStore.region.id
|
|
||||||
)
|
|
||||||
.sort((a, b) => {
|
|
||||||
if (a.stopStatusID > b.stopStatusID) return 1;
|
|
||||||
if (a.stopStatusID < b.stopStatusID) return -1;
|
|
||||||
|
|
||||||
if (a.stopInfo.arrivalTimestamp > b.stopInfo.arrivalTimestamp) return 1;
|
return this.onlineScenery.scheduledTrains
|
||||||
if (a.stopInfo.arrivalTimestamp < b.stopInfo.arrivalTimestamp) return -1;
|
.filter(
|
||||||
|
(ct) =>
|
||||||
|
ct.timetablePathElement.stationName == sceneryName &&
|
||||||
|
ct.train.region == this.mainStore.region.id &&
|
||||||
|
this.chosenCheckpoint &&
|
||||||
|
ct.checkpointStop.stopNameRAW.toLowerCase() == this.chosenCheckpoint.toLowerCase()
|
||||||
|
)
|
||||||
|
.map((ct) => {
|
||||||
|
const trainStopStatus = getTrainStopStatus(
|
||||||
|
ct.checkpointStop,
|
||||||
|
ct.train.currentStationName,
|
||||||
|
sceneryName
|
||||||
|
);
|
||||||
|
|
||||||
return a.stopInfo.departureTimestamp > b.stopInfo.departureTimestamp ? 1 : -1;
|
return {
|
||||||
}) || []
|
checkpointStop: ct.checkpointStop,
|
||||||
);
|
train: ct.train,
|
||||||
|
prevDepartureLine: ct.previousSceneryElement?.departureRouteExt ?? null,
|
||||||
|
nextArrivalLine: ct.nextSceneryElement?.arrivalRouteExt ?? null,
|
||||||
|
departureLine: ct.timetablePathElement.departureRouteExt ?? null,
|
||||||
|
arrivingLine: ct.timetablePathElement.arrivalRouteExt ?? null,
|
||||||
|
prevStationName: ct.previousSceneryElement?.stationName ?? null,
|
||||||
|
nextStationName: ct.nextSceneryElement?.stationName ?? null,
|
||||||
|
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;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -414,13 +422,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,69 +1,97 @@
|
|||||||
<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"
|
||||||
<button class="btn btn--option">PRZEZ</button>
|
v-for="mode in historyModeList"
|
||||||
|
:key="mode"
|
||||||
<button class="btn btn--option">KOŃCZY BIEG</button>
|
:class="{ checked: checkedHistoryMode == mode }"
|
||||||
</div> -->
|
@click="checkHistoryMode(mode)"
|
||||||
|
>
|
||||||
<section class="scenery-table-section">
|
{{ $t(`scenery.timetable-${mode}`) }}
|
||||||
<Loading v-if="dataStatus != DataStatus.Loaded" />
|
</button>
|
||||||
|
|
||||||
<div class="no-history" v-else-if="historyList.length == 0">
|
|
||||||
{{ $t('scenery.history-list-empty') }}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table class="scenery-history-table" v-else>
|
<div class="history-wrapper">
|
||||||
<thead>
|
<Loading v-if="dataStatus != DataStatus.Loaded" />
|
||||||
<th>{{ $t('scenery.timetables-history-id') }}</th>
|
|
||||||
<th>{{ $t('scenery.timetables-history-number') }}</th>
|
|
||||||
<th>{{ $t('scenery.timetables-history-route') }}</th>
|
|
||||||
<th>{{ $t('scenery.timetables-history-driver') }}</th>
|
|
||||||
<th>{{ $t('scenery.timetables-history-author') }}</th>
|
|
||||||
<th>{{ $t('scenery.timetables-history-date') }}</th>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
<div v-else-if="historyList.length == 0" class="no-history">
|
||||||
<tr v-for="historyItem in historyList" :key="historyItem.id">
|
{{ $t('scenery.history-list-empty') }}
|
||||||
<td>
|
</div>
|
||||||
<router-link :to="`/journal/timetables?search-train=%23${historyItem.id}`">
|
|
||||||
#{{ historyItem.id }}
|
|
||||||
</router-link>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<b class="text--primary">{{ historyItem.trainCategoryCode }}</b> <br />
|
|
||||||
{{ historyItem.trainNo }}
|
|
||||||
</td>
|
|
||||||
<td>{{ historyItem.route.replace('|', ' -> ') }}</td>
|
|
||||||
<td>
|
|
||||||
<router-link :to="`/journal/timetables?search-driver=${historyItem.driverName}`">
|
|
||||||
{{ historyItem.driverName }}
|
|
||||||
</router-link>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td>
|
<div v-else class="history-list">
|
||||||
<router-link
|
<div v-for="timetableHistory in historyList" :key="timetableHistory.id">
|
||||||
v-if="historyItem.authorName"
|
<span>
|
||||||
:to="`/journal/timetables?search-dispatcher=${historyItem.authorName}`"
|
<div>
|
||||||
>{{ historyItem.authorName }}
|
<span
|
||||||
</router-link>
|
class="timetable-status-indicator"
|
||||||
<i v-else>{{ $t('scenery.timetable-author-unknown') }}</i>
|
:data-terminated="timetableHistory.terminated"
|
||||||
</td>
|
:data-fulfilled="timetableHistory.fulfilled"
|
||||||
<td>
|
>
|
||||||
<b>{{ localeDay(historyItem.beginDate, $i18n.locale) }}</b>
|
⦿
|
||||||
{{ localeTime(historyItem.beginDate, $i18n.locale) }}
|
</span>
|
||||||
</td>
|
#{{ timetableHistory.id }} |
|
||||||
</tr>
|
<b class="text--primary">{{ timetableHistory.trainCategoryCode }}</b>
|
||||||
</tbody>
|
{{ timetableHistory.trainNo }}
|
||||||
</table>
|
{{ timetableHistory.route.replace('|', ' ⇒ ') }}
|
||||||
</section>
|
</div>
|
||||||
|
|
||||||
<div class="bottom-info">
|
<div class="text--grayed">
|
||||||
<button class="btn btn--option" v-if="historyList.length > 0" @click="navigateToHistory()">
|
<span>
|
||||||
{{ $t('scenery.bottom-info') }}
|
{{ $t('scenery.timetable-issued-date') }}
|
||||||
</button>
|
<b>
|
||||||
|
{{
|
||||||
|
localeDateTime(
|
||||||
|
timetableHistory.createdAt > timetableHistory.beginDate
|
||||||
|
? timetableHistory.beginDate
|
||||||
|
: timetableHistory.createdAt,
|
||||||
|
$i18n.locale
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</b></span
|
||||||
|
>
|
||||||
|
<span v-if="timetableHistory.authorName">
|
||||||
|
{{ $t('scenery.timetable-issued-by') }}
|
||||||
|
<b>
|
||||||
|
<router-link
|
||||||
|
:to="`/journal/timetables?search-dispatcher=${timetableHistory.authorName}`"
|
||||||
|
>
|
||||||
|
{{ timetableHistory.authorName }}
|
||||||
|
</router-link>
|
||||||
|
</b>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
{{ $t('scenery.timetable-issued-for') }}
|
||||||
|
<b>
|
||||||
|
<router-link
|
||||||
|
:to="`/journal/timetables?search-driver=${timetableHistory.driverName}`"
|
||||||
|
>
|
||||||
|
{{ 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">
|
||||||
|
<button class="btn btn--option" v-if="historyList.length > 0" @click="navigateToHistory()">
|
||||||
|
{{ $t('scenery.bottom-info') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -71,17 +99,19 @@
|
|||||||
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 { ActiveScenery } from '../../store/typings';
|
|
||||||
import { API } from '../../typings/api';
|
import { API } from '../../typings/api';
|
||||||
import { Status } from '../../typings/common';
|
import { ActiveScenery, Station, Status } from '../../typings/common';
|
||||||
import { useApiStore } from '../../store/apiStore';
|
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>
|
||||||
@@ -94,9 +124,14 @@ export default defineComponent({
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
historyList: [] as API.TimetableHistory.Response,
|
historyList: [] as API.TimetableHistory.Response,
|
||||||
|
historyModeList,
|
||||||
|
|
||||||
apiStore: useApiStore(),
|
apiStore: useApiStore(),
|
||||||
|
mainStore: useMainStore(),
|
||||||
dataStatus: Status.Data.Loading,
|
dataStatus: Status.Data.Loading,
|
||||||
DataStatus: Status.Data
|
DataStatus: Status.Data,
|
||||||
|
|
||||||
|
checkedHistoryMode: 'via' as HistoryMode
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -106,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 this.apiStore.client!.get('api/getTimetables', {
|
await this.apiStore.client!.get('api/getTimetables', {
|
||||||
params: {
|
params: requestFilters
|
||||||
issuedFrom: this.station?.name || this.onlineScenery?.name
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
).data;
|
).data;
|
||||||
|
|
||||||
@@ -128,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
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -145,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.5em;
|
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;
|
||||||
|
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,29 +84,29 @@
|
|||||||
<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>
|
||||||
<button class="btn--action" @click="addHour">+</button>
|
<button class="btn--action" @click="addHour">+</button>
|
||||||
</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,61 +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() {
|
||||||
this.filterStore.changeFilterValue('authors', this.authorsInputValue);
|
this.filters['authors'] = this.authors;
|
||||||
|
// if (this.saveOptions) StorageManager.setStringValue('authors', target.value);
|
||||||
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() {
|
||||||
@@ -316,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;
|
||||||
@@ -335,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;
|
||||||
@@ -363,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;
|
||||||
@@ -430,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;
|
||||||
|
|
||||||
@@ -471,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;
|
||||||
@@ -508,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;
|
||||||
@@ -578,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,360 +1,376 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="station_table">
|
<section class="station_table">
|
||||||
<transition name="status-anim" mode="out-in">
|
<Loading
|
||||||
<div class="table_wrapper" :key="apiStore.dataStatuses.connection">
|
v-if="apiStore.dataStatuses.connection == Status.Loading && filteredStationList.length == 0"
|
||||||
<table>
|
/>
|
||||||
<thead>
|
|
||||||
<tr>
|
<table v-else-if="filteredStationList.length > 0">
|
||||||
<th
|
<thead>
|
||||||
v-for="headerName in headIds"
|
<tr>
|
||||||
:key="headerName"
|
<th
|
||||||
@click="changeSorter(headerName)"
|
v-for="headerName in headIds"
|
||||||
class="header-text"
|
:key="headerName"
|
||||||
:class="headerName"
|
@click="changeSorter(headerName)"
|
||||||
|
class="header-text"
|
||||||
|
:class="headerName"
|
||||||
|
>
|
||||||
|
<span class="header_wrapper">
|
||||||
|
<div v-html="$t(`sceneries.headers.${headerName}`)"></div>
|
||||||
|
|
||||||
|
<img
|
||||||
|
class="sort-icon"
|
||||||
|
v-if="activeSorter.headerName == headerName"
|
||||||
|
:src="`/images/icon-arrow-${activeSorter.dir == 1 ? 'asc' : 'desc'}.svg`"
|
||||||
|
alt="sort icon"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
|
||||||
|
<th
|
||||||
|
v-for="headerName in headIconsIds"
|
||||||
|
:key="headerName"
|
||||||
|
@click="changeSorter(headerName)"
|
||||||
|
class="header-image"
|
||||||
|
:class="headerName"
|
||||||
|
>
|
||||||
|
<span class="header_wrapper">
|
||||||
|
<img
|
||||||
|
:src="`/images/icon-${headerName}.svg`"
|
||||||
|
:alt="headerName"
|
||||||
|
:title="$t(`sceneries.headers.${headerName}`)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<img
|
||||||
|
class="sort-icon"
|
||||||
|
v-if="activeSorter.headerName == headerName"
|
||||||
|
:src="`/images/icon-arrow-${activeSorter.dir == 1 ? 'asc' : 'desc'}.svg`"
|
||||||
|
alt="sort icon"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
<tr
|
||||||
|
v-for="station in filteredStationList"
|
||||||
|
:class="{ 'last-selected': lastSelectedStationName == station.name }"
|
||||||
|
:key="station.name"
|
||||||
|
@click.left="setScenery(station.name)"
|
||||||
|
@click.right="openForumSite($event, station.generalInfo?.url)"
|
||||||
|
@keydown.enter="setScenery(station.name)"
|
||||||
|
@keydown.space="openForumSite($event, station.generalInfo?.url)"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<td class="station-name" :class="station.generalInfo?.availability">
|
||||||
|
<b v-if="station.generalInfo?.project" style="color: salmon">{{
|
||||||
|
station.generalInfo.project
|
||||||
|
}}</b>
|
||||||
|
{{ station.name }}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="station-level">
|
||||||
|
<span v-if="station.generalInfo">
|
||||||
|
<span
|
||||||
|
v-if="
|
||||||
|
station.generalInfo.reqLevel > -1 &&
|
||||||
|
station.generalInfo.availability != 'nonPublic' &&
|
||||||
|
station.generalInfo.availability != 'unavailable'
|
||||||
|
"
|
||||||
|
:style="calculateExpStyle(station.generalInfo.reqLevel)"
|
||||||
>
|
>
|
||||||
<span class="header_wrapper">
|
{{ station.generalInfo.reqLevel >= 2 ? station.generalInfo.reqLevel : 'L' }}
|
||||||
<div v-html="$t(`sceneries.headers.${headerName}`)"></div>
|
</span>
|
||||||
|
|
||||||
<img
|
<span v-else-if="station.generalInfo.availability == 'abandoned'">
|
||||||
class="sort-icon"
|
<img
|
||||||
v-if="sorterActive.headerName == headerName"
|
src="/images/icon-abandoned.svg"
|
||||||
:src="`/images/icon-arrow-${sorterActive.dir == 1 ? 'asc' : 'desc'}.svg`"
|
alt="non-public"
|
||||||
alt="sort icon"
|
:title="$t('sceneries.info.abandoned')"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</th>
|
|
||||||
|
|
||||||
<th
|
<span v-else-if="station.generalInfo.availability == 'nonPublic'">
|
||||||
v-for="headerName in headIconsIds"
|
<img
|
||||||
:key="headerName"
|
src="/images/icon-lock.svg"
|
||||||
@click="changeSorter(headerName)"
|
alt="non-public"
|
||||||
class="header-image"
|
:title="$t('sceneries.info.non-public')"
|
||||||
:class="headerName"
|
/>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span v-else>
|
||||||
|
<img
|
||||||
|
src="/images/icon-unavailable.svg"
|
||||||
|
alt="unavailable"
|
||||||
|
:title="$t('sceneries.info.unavailable')"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span v-else> ? </span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="station-status">
|
||||||
|
<StationStatusBadge
|
||||||
|
:isOnline="station.onlineInfo ? true : false"
|
||||||
|
:dispatcherStatus="station.onlineInfo?.dispatcherStatus"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="station-dispatcher-name">
|
||||||
|
<span v-if="station.onlineInfo?.dispatcherName">
|
||||||
|
<b
|
||||||
|
v-if="apiStore.donatorsData.includes(station.onlineInfo.dispatcherName)"
|
||||||
|
@click.stop="openDonationCard"
|
||||||
|
data-tooltip-type="DonatorTooltip"
|
||||||
|
:data-tooltip-content="$t('donations.dispatcher-message')"
|
||||||
>
|
>
|
||||||
<span class="header_wrapper">
|
<img src="/images/icon-diamond.svg" alt="" />
|
||||||
<img
|
{{ station.onlineInfo.dispatcherName }}
|
||||||
:src="`/images/icon-${headerName}.svg`"
|
</b>
|
||||||
:alt="headerName"
|
|
||||||
:title="$t(`sceneries.headers.${headerName}`)"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<img
|
<div v-else>
|
||||||
class="sort-icon"
|
{{ station.onlineInfo.dispatcherName }}
|
||||||
v-if="sorterActive.headerName == headerName"
|
</div>
|
||||||
:src="`/images/icon-arrow-${sorterActive.dir == 1 ? 'asc' : 'desc'}.svg`"
|
</span>
|
||||||
alt="sort icon"
|
</td>
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
<td class="station-dispatcher-exp">
|
||||||
<tr
|
<span
|
||||||
v-for="station in stations"
|
v-if="station.onlineInfo && station.onlineInfo?.dispatcherExp != -1"
|
||||||
:class="{ 'last-selected': lastSelectedStationName == station.name }"
|
:style="
|
||||||
:key="station.name"
|
calculateExpStyle(
|
||||||
@click.left="setScenery(station.name)"
|
station.onlineInfo.dispatcherExp,
|
||||||
@click.right="openForumSite($event, station.generalInfo?.url)"
|
station.onlineInfo.dispatcherIsSupporter
|
||||||
@keydown.enter="setScenery(station.name)"
|
)
|
||||||
@keydown.space="openForumSite($event, station.generalInfo?.url)"
|
"
|
||||||
tabindex="0"
|
|
||||||
>
|
>
|
||||||
<td class="station-name" :class="station.generalInfo?.availability">
|
{{ station.onlineInfo.dispatcherExp < 2 ? 'L' : station.onlineInfo.dispatcherExp }}
|
||||||
<b v-if="station.generalInfo?.project" style="color: salmon">{{
|
</span>
|
||||||
station.generalInfo.project
|
</td>
|
||||||
}}</b>
|
|
||||||
{{ station.name }}
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td class="station-level">
|
<td class="station-tracks">
|
||||||
<span v-if="station.generalInfo">
|
<div v-if="station.generalInfo">
|
||||||
<span
|
<span
|
||||||
v-if="
|
v-if="station.generalInfo.routes.singleElectrifiedNames.length != 0"
|
||||||
station.generalInfo.reqLevel > -1 &&
|
class="track catenary"
|
||||||
station.generalInfo.availability != 'nonPublic' &&
|
:title="`${$t('sceneries.info.single-track-routes-catenary')}${
|
||||||
station.generalInfo.availability != 'unavailable'
|
station.generalInfo.routes.singleElectrifiedNames.length
|
||||||
"
|
}`"
|
||||||
:style="calculateExpStyle(station.generalInfo.reqLevel)"
|
|
||||||
>
|
|
||||||
{{ station.generalInfo.reqLevel >= 2 ? station.generalInfo.reqLevel : 'L' }}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span v-else-if="station.generalInfo.availability == 'abandoned'">
|
|
||||||
<img
|
|
||||||
src="/images/icon-abandoned.svg"
|
|
||||||
alt="non-public"
|
|
||||||
:title="$t('sceneries.info.abandoned')"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span v-else-if="station.generalInfo.availability == 'nonPublic'">
|
|
||||||
<img
|
|
||||||
src="/images/icon-lock.svg"
|
|
||||||
alt="non-public"
|
|
||||||
:title="$t('sceneries.info.non-public')"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span v-else>
|
|
||||||
<img
|
|
||||||
src="/images/icon-unavailable.svg"
|
|
||||||
alt="unavailable"
|
|
||||||
:title="$t('sceneries.info.unavailable')"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span v-else> ? </span>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td class="station-status">
|
|
||||||
<StationStatusBadge
|
|
||||||
:isOnline="station.onlineInfo ? true : false"
|
|
||||||
:dispatcherStatus="station.onlineInfo?.dispatcherStatus"
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td class="station-dispatcher-name">
|
|
||||||
<span v-if="station.onlineInfo?.dispatcherName">
|
|
||||||
<b
|
|
||||||
v-if="apiStore.donatorsData.includes(station.onlineInfo.dispatcherName)"
|
|
||||||
:title="$t('donations.dispatcher-message')"
|
|
||||||
@click.stop="openDonationModal"
|
|
||||||
>
|
|
||||||
<img src="/images/icon-diamond.svg" alt="" />
|
|
||||||
{{ station.onlineInfo.dispatcherName }}
|
|
||||||
</b>
|
|
||||||
|
|
||||||
<div v-else>
|
|
||||||
{{ station.onlineInfo.dispatcherName }}
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td class="station-dispatcher-exp">
|
|
||||||
<span
|
|
||||||
v-if="station.onlineInfo && station.onlineInfo?.dispatcherExp != -1"
|
|
||||||
:style="
|
|
||||||
calculateExpStyle(
|
|
||||||
station.onlineInfo.dispatcherExp,
|
|
||||||
station.onlineInfo.dispatcherIsSupporter
|
|
||||||
)
|
|
||||||
"
|
|
||||||
>
|
|
||||||
{{
|
|
||||||
station.onlineInfo.dispatcherExp < 2 ? 'L' : station.onlineInfo.dispatcherExp
|
|
||||||
}}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td class="station-tracks">
|
|
||||||
<div v-if="station.generalInfo">
|
|
||||||
<span
|
|
||||||
v-if="station.generalInfo.routes.singleElectrifiedNames.length != 0"
|
|
||||||
class="track catenary"
|
|
||||||
:title="`${$t('sceneries.info.single-track-routes-catenary')}${
|
|
||||||
station.generalInfo.routes.singleElectrifiedNames.length
|
|
||||||
}`"
|
|
||||||
>
|
|
||||||
{{ station.generalInfo.routes.singleElectrifiedNames.length }}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span
|
|
||||||
v-if="station.generalInfo.routes.singleOtherNames.length != 0"
|
|
||||||
class="track no-catenary"
|
|
||||||
:title="`${$t('sceneries.info.single-track-routes-other')}${
|
|
||||||
station.generalInfo.routes.singleOtherNames.length
|
|
||||||
}`"
|
|
||||||
>
|
|
||||||
{{ station.generalInfo.routes.singleOtherNames.length }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td class="station-tracks">
|
|
||||||
<div v-if="station.generalInfo">
|
|
||||||
<span
|
|
||||||
v-if="station.generalInfo.routes.doubleElectrifiedNames.length != 0"
|
|
||||||
class="track catenary"
|
|
||||||
:title="`${$t('sceneries.info.double-track-routes-catenary')}${
|
|
||||||
station.generalInfo.routes.doubleElectrifiedNames.length
|
|
||||||
}`"
|
|
||||||
>
|
|
||||||
{{ station.generalInfo.routes.doubleElectrifiedNames.length }}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span
|
|
||||||
v-if="station.generalInfo.routes.doubleOtherNames.length != 0"
|
|
||||||
class="track no-catenary"
|
|
||||||
:title="`${$t('sceneries.info.double-track-routes-other')}${
|
|
||||||
station.generalInfo.routes.doubleOtherNames.length
|
|
||||||
}`"
|
|
||||||
>
|
|
||||||
{{ 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}`)
|
|
||||||
"
|
|
||||||
v-html="getControlTypeAbbrev(station.generalInfo.controlType)"
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<img
|
|
||||||
v-if="station.generalInfo?.signalType"
|
|
||||||
class="icon-info"
|
|
||||||
:src="`/images/icon-${station.generalInfo.signalType}.svg`"
|
|
||||||
:alt="station.generalInfo.signalType"
|
|
||||||
:title="
|
|
||||||
$t('sceneries.info.signals-type') +
|
|
||||||
$t(`signals.${station.generalInfo.signalType}`)
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<img
|
|
||||||
v-if="station.generalInfo?.SUP"
|
|
||||||
class="icon-info"
|
|
||||||
src="/images/icon-SUP.svg"
|
|
||||||
alt="SUP (RASP-UZK)"
|
|
||||||
:title="$t('sceneries.info.SUP')"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<img
|
|
||||||
v-if="station.generalInfo?.ASDEK"
|
|
||||||
class="icon-info"
|
|
||||||
src="/images/icon-ASDEK.svg"
|
|
||||||
alt="dSAT ASDEK"
|
|
||||||
:title="$t('sceneries.info.ASDEK')"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<img
|
|
||||||
v-if="!station.generalInfo"
|
|
||||||
class="icon-info"
|
|
||||||
src="/images/icon-unknown.svg"
|
|
||||||
alt="icon-unknown"
|
|
||||||
:title="$t('sceneries.info.unknown')"
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td class="station-users" :class="{ inactive: !station.onlineInfo }">
|
|
||||||
<span class="text--primary">{{ station.onlineInfo?.currentUsers ?? '-' }}</span>
|
|
||||||
/
|
|
||||||
<span class="text--primary">{{ station.onlineInfo?.maxUsers ?? '-' }}</span>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td class="station-likes" :class="{ inactive: !station.onlineInfo }">
|
|
||||||
<span>{{ station.onlineInfo?.dispatcherRate ?? '-' }}</span>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td class="station-spawns" :class="{ inactive: !station.onlineInfo }">
|
|
||||||
<span>{{ station.onlineInfo?.spawns.length ?? '-' }}</span>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td
|
|
||||||
class="station-schedules all"
|
|
||||||
style="width: 30px"
|
|
||||||
:class="{ inactive: !station.onlineInfo }"
|
|
||||||
>
|
>
|
||||||
{{ station.onlineInfo?.scheduledTrainCount.all ?? '-' }}
|
{{ station.generalInfo.routes.singleElectrifiedNames.length }}
|
||||||
</td>
|
</span>
|
||||||
|
|
||||||
<td
|
<span
|
||||||
class="station-schedules unconfirmed"
|
v-if="station.generalInfo.routes.singleOtherNames.length != 0"
|
||||||
style="width: 30px"
|
class="track no-catenary"
|
||||||
:class="{ inactive: !station.onlineInfo }"
|
:title="`${$t('sceneries.info.single-track-routes-other')}${
|
||||||
|
station.generalInfo.routes.singleOtherNames.length
|
||||||
|
}`"
|
||||||
>
|
>
|
||||||
{{ station.onlineInfo?.scheduledTrainCount.unconfirmed ?? '-' }}
|
{{ station.generalInfo.routes.singleOtherNames.length }}
|
||||||
</td>
|
</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
<td
|
<td class="station-tracks">
|
||||||
class="station-schedules confirmed"
|
<div v-if="station.generalInfo">
|
||||||
style="width: 30px"
|
<span
|
||||||
:class="{ inactive: !station.onlineInfo }"
|
v-if="station.generalInfo.routes.doubleElectrifiedNames.length != 0"
|
||||||
|
class="track catenary"
|
||||||
|
:title="`${$t('sceneries.info.double-track-routes-catenary')}${
|
||||||
|
station.generalInfo.routes.doubleElectrifiedNames.length
|
||||||
|
}`"
|
||||||
>
|
>
|
||||||
{{ station.onlineInfo?.scheduledTrainCount.confirmed ?? '-' }}
|
{{ station.generalInfo.routes.doubleElectrifiedNames.length }}
|
||||||
</td>
|
</span>
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<Loading
|
<span
|
||||||
v-if="apiStore.dataStatuses.connection == Status.Loading && stations.length == 0"
|
v-if="station.generalInfo.routes.doubleOtherNames.length != 0"
|
||||||
/>
|
class="track no-catenary"
|
||||||
|
:title="`${$t('sceneries.info.double-track-routes-other')}${
|
||||||
|
station.generalInfo.routes.doubleOtherNames.length
|
||||||
|
}`"
|
||||||
|
>
|
||||||
|
{{ station.generalInfo.routes.doubleOtherNames.length }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
<div class="no-stations" v-else-if="stations.length == 0">
|
<td class="station-info">
|
||||||
{{ $t('sceneries.no-stations') }}
|
<span
|
||||||
</div>
|
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>
|
||||||
|
|
||||||
|
<img
|
||||||
|
v-if="station.generalInfo?.signalType"
|
||||||
|
class="icon-info"
|
||||||
|
:src="`/images/icon-${station.generalInfo.signalType}.svg`"
|
||||||
|
:alt="station.generalInfo.signalType"
|
||||||
|
:title="
|
||||||
|
$t('sceneries.info.signals-type') + $t(`signals.${station.generalInfo.signalType}`)
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<img
|
||||||
|
v-if="station.generalInfo?.SUP"
|
||||||
|
class="icon-info"
|
||||||
|
src="/images/icon-SUP.svg"
|
||||||
|
alt="SUP (RASP-UZK)"
|
||||||
|
:title="$t('sceneries.info.SUP')"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<img
|
||||||
|
v-if="station.generalInfo?.ASDEK"
|
||||||
|
class="icon-info"
|
||||||
|
src="/images/icon-ASDEK.svg"
|
||||||
|
alt="dSAT ASDEK"
|
||||||
|
:title="$t('sceneries.info.ASDEK')"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<img
|
||||||
|
v-if="!station.generalInfo"
|
||||||
|
class="icon-info"
|
||||||
|
src="/images/icon-unknown.svg"
|
||||||
|
alt="icon-unknown"
|
||||||
|
:title="$t('sceneries.info.unknown')"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td
|
||||||
|
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 class="text--primary">{{ station.onlineInfo?.maxUsers ?? '-' }}</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="station-likes" :class="{ inactive: !station.onlineInfo }">
|
||||||
|
<span>{{ station.onlineInfo?.dispatcherRate ?? '-' }}</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td
|
||||||
|
class="station-spawns"
|
||||||
|
:class="{ inactive: !station.onlineInfo }"
|
||||||
|
data-tooltip-type="SpawnsTooltip"
|
||||||
|
:data-tooltip-content="JSON.stringify(station.onlineInfo?.spawns ?? [])"
|
||||||
|
>
|
||||||
|
<span>{{ station.onlineInfo?.spawns.length ?? '-' }}</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td
|
||||||
|
class="station-schedules all"
|
||||||
|
style="width: 30px"
|
||||||
|
:class="{ inactive: !station.onlineInfo }"
|
||||||
|
>
|
||||||
|
{{ station.onlineInfo?.scheduledTrainCount.all ?? '-' }}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td
|
||||||
|
class="station-schedules unconfirmed"
|
||||||
|
style="width: 30px"
|
||||||
|
:class="{ inactive: !station.onlineInfo }"
|
||||||
|
>
|
||||||
|
{{ station.onlineInfo?.scheduledTrainCount.unconfirmed ?? '-' }}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td
|
||||||
|
class="station-schedules confirmed"
|
||||||
|
style="width: 30px"
|
||||||
|
:class="{ inactive: !station.onlineInfo }"
|
||||||
|
>
|
||||||
|
{{ station.onlineInfo?.scheduledTrainCount.confirmed ?? '-' }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="no-stations" v-else>
|
||||||
|
<div>
|
||||||
|
{{ $t('sceneries.no-stations') }} (region: <b>{{ mainStore.region.name }}</b
|
||||||
|
>)
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
|
||||||
|
<div class="text--primary" v-if="getChangedFilters(filters).length != 0">
|
||||||
|
⚠ {{ $t('sceneries.active-filters') }}
|
||||||
|
</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',
|
||||||
@@ -365,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) {
|
||||||
@@ -376,10 +393,14 @@ export default defineComponent({
|
|||||||
window.open(url, '_blank');
|
window.open(url, '_blank');
|
||||||
},
|
},
|
||||||
|
|
||||||
changeSorter(headerName: HeadIdsTypes) {
|
changeSorter(headerName: HeadIdsType) {
|
||||||
if (headerName == 'general') 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -392,33 +413,20 @@ 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;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
height: 90vh;
|
|
||||||
min-height: 550px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-stations {
|
.no-stations {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 1.5em;
|
font-size: 1.25em;
|
||||||
|
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
margin: 1em 0;
|
background: #1a1a1a;
|
||||||
|
line-height: 1.5em;
|
||||||
background: #333;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
|
|||||||
@@ -5,52 +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;
|
|
||||||
ASDEK: boolean;
|
export const headIconsIds = [
|
||||||
noASDEK: boolean;
|
'user',
|
||||||
ręczne: boolean;
|
'like',
|
||||||
'ręczne+SPK': boolean;
|
'spawn',
|
||||||
'ręczne+SCS': boolean;
|
'timetableAll',
|
||||||
mechaniczne: boolean;
|
'timetableUnconfirmed',
|
||||||
'mechaniczne+SPK': boolean;
|
'timetableConfirmed'
|
||||||
'mechaniczne+SCS': boolean;
|
] as const;
|
||||||
SBL: boolean;
|
|
||||||
PBL: boolean;
|
export type HeadIdsType = (typeof headIds)[number] | (typeof headIconsIds)[number];
|
||||||
współczesna: boolean;
|
|
||||||
kształtowa: boolean;
|
export interface ActiveSorter {
|
||||||
historyczna: boolean;
|
headerName: HeadIdsType;
|
||||||
mieszana: boolean;
|
dir: number;
|
||||||
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;
|
|
||||||
afkStatus: boolean;
|
|
||||||
noSpaceStatus: boolean;
|
|
||||||
unavailableStatus: boolean;
|
|
||||||
unsignedStatus: boolean;
|
|
||||||
authors: string;
|
|
||||||
onlineFromHours: number;
|
|
||||||
withActiveTimetables: boolean;
|
|
||||||
withoutActiveTimetables: boolean;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -9,12 +12,13 @@
|
|||||||
stop.arrivalDelay > 0 && stop.status != 'unconfirmed'
|
stop.arrivalDelay > 0 && stop.status != 'unconfirmed'
|
||||||
? 'delayed'
|
? 'delayed'
|
||||||
: stop.arrivalDelay < 0 && stop.status != 'unconfirmed'
|
: stop.arrivalDelay < 0 && stop.status != 'unconfirmed'
|
||||||
? 'preponed'
|
? 'preponed'
|
||||||
: stop.arrivalDelay == 0 && stop.status == 'confirmed'
|
: stop.arrivalDelay == 0 && stop.status == 'confirmed'
|
||||||
? 'on-time'
|
? 'on-time'
|
||||||
: ''
|
: ''
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
|
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,17 +33,17 @@
|
|||||||
<span
|
<span
|
||||||
v-if="
|
v-if="
|
||||||
stop.duration ||
|
stop.duration ||
|
||||||
(stop.status == 'stopped' &&
|
(stop.status == 'stopped' && stop.position != 'begin' && stop.departureDelay > 0)
|
||||||
stop.position != 'begin' &&
|
|
||||||
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>
|
||||||
|
|
||||||
@@ -53,13 +57,16 @@
|
|||||||
stop.departureDelay > 0 && stop.status == 'confirmed'
|
stop.departureDelay > 0 && stop.status == 'confirmed'
|
||||||
? 'delayed'
|
? 'delayed'
|
||||||
: stop.departureDelay < 0 && stop.status == 'confirmed'
|
: stop.departureDelay < 0 && stop.status == 'confirmed'
|
||||||
? 'preponed'
|
? 'preponed'
|
||||||
: stop.departureDelay == 0 && stop.status == 'confirmed'
|
: stop.departureDelay == 0 && stop.status == 'confirmed'
|
||||||
? 'on-time'
|
? 'on-time'
|
||||||
: ''
|
: ''
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<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) }}
|
||||||
|
|
||||||
@@ -96,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;
|
||||||
}
|
}
|
||||||
@@ -117,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;
|
||||||
@@ -130,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 {
|
||||||
@@ -150,7 +162,7 @@ $stopNameClr: #22a8d1;
|
|||||||
.departure {
|
.departure {
|
||||||
&[data-status='delayed'] {
|
&[data-status='delayed'] {
|
||||||
s {
|
s {
|
||||||
color: #999;
|
color: #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
span {
|
span {
|
||||||
@@ -160,7 +172,7 @@ $stopNameClr: #22a8d1;
|
|||||||
|
|
||||||
&[data-status='preponed'] {
|
&[data-status='preponed'] {
|
||||||
s {
|
s {
|
||||||
color: #999;
|
color: #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
span {
|
span {
|
||||||
|
|||||||
@@ -1,60 +1,86 @@
|
|||||||
<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">
|
||||||
<b class="warning-timeout" v-if="train.isTimeout" :title="$t('trains.timeout')">?</b>
|
<div>
|
||||||
<span class="timetable-id" v-if="train.timetableData">
|
<b class="warning-timeout" v-if="train.isTimeout" :title="$t('trains.timeout')">?</b>
|
||||||
#{{ train.timetableData.timetableId }}
|
<span class="timetable-id" v-if="train.timetableData">
|
||||||
</span>
|
#{{ train.timetableData.timetableId }}
|
||||||
|
|
||||||
<span
|
|
||||||
class="timetable-warnings"
|
|
||||||
v-if="train.timetableData?.TWR || train.timetableData?.SKR"
|
|
||||||
>
|
|
||||||
<span class="train-badge twr" v-if="train.timetableData?.TWR" :title="$t('general.TWR')">
|
|
||||||
TWR
|
|
||||||
</span>
|
</span>
|
||||||
<span class="train-badge skr" v-if="train.timetableData?.SKR" :title="$t('general.SKR')">
|
|
||||||
SKR
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<strong>
|
<span
|
||||||
<span v-if="train.timetableData" class="text--primary"
|
class="timetable-warnings"
|
||||||
>{{ train.timetableData.category }} </span
|
v-if="train.timetableData?.TWR || train.timetableData?.SKR"
|
||||||
>
|
>
|
||||||
<span class="train-number">{{ train.trainNo }}</span>
|
<span
|
||||||
</strong>
|
class="train-badge twr"
|
||||||
<span>•</span>
|
v-if="train.timetableData?.TWR"
|
||||||
<b
|
:title="$t('general.TWR')"
|
||||||
class="level-badge driver"
|
>
|
||||||
:style="calculateExpStyle(train.driverLevel, train.isSupporter)"
|
TWR
|
||||||
>
|
</span>
|
||||||
{{ train.driverLevel < 2 ? 'L' : `${train.driverLevel}` }}
|
<span
|
||||||
</b>
|
class="train-badge skr"
|
||||||
|
v-if="train.timetableData?.SKR"
|
||||||
|
:title="$t('general.SKR')"
|
||||||
|
>
|
||||||
|
SKR
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
<div class="train-driver">
|
<strong>
|
||||||
|
<span v-if="train.timetableData" class="text--primary"
|
||||||
|
>{{ train.timetableData.category }} </span
|
||||||
|
>
|
||||||
|
<span class="train-number">{{ train.trainNo }}</span>
|
||||||
|
</strong>
|
||||||
|
<span>•</span>
|
||||||
<b
|
<b
|
||||||
v-if="apiStore.donatorsData.includes(train.driverName)"
|
class="level-badge driver"
|
||||||
:title="$t('donations.driver-message')"
|
:style="calculateExpStyle(train.driverLevel, train.isSupporter)"
|
||||||
>
|
>
|
||||||
{{ train.driverName }}
|
{{ train.driverLevel < 2 ? 'L' : `${train.driverLevel}` }}
|
||||||
<img src="/images/icon-diamond.svg" alt="donator diamond icon" />
|
|
||||||
</b>
|
</b>
|
||||||
<span v-else>{{ train.driverName }}</span>
|
|
||||||
|
<div class="train-driver">
|
||||||
|
<b
|
||||||
|
v-if="apiStore.donatorsData.includes(train.driverName)"
|
||||||
|
data-tooltip-type="DonatorTooltip"
|
||||||
|
:data-tooltip-content="$t('donations.driver-message')"
|
||||||
|
>
|
||||||
|
{{ train.driverName }}
|
||||||
|
<img src="/images/icon-diamond.svg" alt="donator diamond icon" />
|
||||||
|
</b>
|
||||||
|
|
||||||
|
<span v-else>{{ train.driverName }}</span>
|
||||||
|
</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>
|
</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,12 +117,41 @@
|
|||||||
</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">
|
||||||
<StockList :trainStockList="train.stockList" :tractionOnly="true" />
|
<StockList :trainStockList="train.stockList" :tractionOnly="true" />
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@@ -120,14 +175,15 @@
|
|||||||
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 { 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 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, StockList },
|
components: { ProgressBar, StockList },
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
@@ -136,8 +192,7 @@ export default defineComponent({
|
|||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
extended: {
|
extended: {
|
||||||
type: Boolean,
|
type: Boolean
|
||||||
default: true
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -146,6 +201,31 @@ 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>
|
||||||
@@ -156,8 +236,8 @@ export default defineComponent({
|
|||||||
|
|
||||||
.image-warning {
|
.image-warning {
|
||||||
height: 1em;
|
height: 1em;
|
||||||
|
|
||||||
margin-left: 0.5em;
|
margin-left: 0.5em;
|
||||||
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.train-stats {
|
.train-stats {
|
||||||
@@ -176,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;
|
||||||
@@ -210,14 +294,29 @@ export default defineComponent({
|
|||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.general-info {
|
.general-top-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
justify-content: space-between;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5em;
|
||||||
|
|
||||||
gap: 0.25em;
|
& > div {
|
||||||
margin-right: 1.5em;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
gap: 0.25em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-timetable {
|
||||||
|
padding: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-exit {
|
||||||
|
padding: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
.general-status {
|
.general-status {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -226,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;
|
||||||
@@ -237,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;
|
||||||
@@ -257,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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
this.$nextTick(() => {
|
watch: {
|
||||||
contentEl.focus();
|
chosenTrain(train: Train | undefined) {
|
||||||
});
|
this.$nextTick(() => {
|
||||||
|
if (train) {
|
||||||
|
document.body.classList.add('no-scroll');
|
||||||
|
const contentEl = this.$refs['content'] as HTMLElement;
|
||||||
|
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">
|
||||||
@@ -48,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>
|
<span>{{ stop.departureLine }}</span>
|
||||||
|
<span v-if="stop.departureLineInfo">
|
||||||
<span v-else-if="stop.departureLine && !/sbl/gi.test(stop.departureLine)">
|
| {{ stop.departureLineInfo.routeSpeed }}
|
||||||
<div>{{ stop.departureLine }}</div>
|
<span v-if="stop.departureLineInfo.isElectric">⚡</span>
|
||||||
<div
|
<img
|
||||||
class="scenery-change-name"
|
v-else
|
||||||
v-if="
|
src="/images/icon-we4a.png"
|
||||||
i < scheduleStops.length - 1 &&
|
:title="$t('trains.we4a-tooltip')"
|
||||||
stop.sceneryName != scheduleStops[i + 1].sceneryName
|
width="12"
|
||||||
"
|
/>
|
||||||
>
|
</span>
|
||||||
{{ 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>
|
||||||
@@ -81,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;
|
||||||
@@ -111,12 +130,16 @@ export interface TrainScheduleStop {
|
|||||||
isSBL: boolean;
|
isSBL: boolean;
|
||||||
|
|
||||||
sceneryName: string | null;
|
sceneryName: string | null;
|
||||||
sceneryHash: string;
|
|
||||||
distance: number;
|
distance: number;
|
||||||
|
|
||||||
arrivalLine: string | null;
|
arrivalLine: string | null;
|
||||||
departureLine: string | null;
|
departureLine: string | null;
|
||||||
|
|
||||||
|
arrivalLineInfo?: StationRoutesInfo;
|
||||||
|
departureLineInfo?: StationRoutesInfo;
|
||||||
|
|
||||||
|
isExternal: boolean;
|
||||||
|
|
||||||
comments: string | null;
|
comments: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,13 +169,25 @@ export default defineComponent({
|
|||||||
|
|
||||||
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++;
|
|
||||||
|
if (isExternal) currentSceneryIndex++;
|
||||||
|
|
||||||
|
const sceneryName = this.train.timetableData!.sceneryNames[currentSceneryIndex];
|
||||||
|
const sceneryInfo = this.apiStore.sceneryData.find((st) => st.name == sceneryName);
|
||||||
|
|
||||||
|
const arrivalLineInfo = sceneryInfo?.routesInfo.find(
|
||||||
|
(r) => r.routeName == stop.arrivalLine
|
||||||
|
);
|
||||||
|
|
||||||
|
const departureLineInfo = sceneryInfo?.routesInfo.find(
|
||||||
|
(r) => r.routeName == stop.departureLine
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
nameHtml: stop.stopName,
|
nameHtml: stop.stopName,
|
||||||
@@ -174,14 +209,18 @@ export default defineComponent({
|
|||||||
arrivalLine: stop.arrivalLine,
|
arrivalLine: stop.arrivalLine,
|
||||||
departureLine: stop.departureLine,
|
departureLine: stop.departureLine,
|
||||||
|
|
||||||
|
arrivalLineInfo: arrivalLineInfo,
|
||||||
|
departureLineInfo: departureLineInfo,
|
||||||
|
|
||||||
|
isExternal,
|
||||||
|
|
||||||
type: stop.stopType,
|
type: stop.stopType,
|
||||||
distance: stop.stopDistance,
|
distance: stop.stopDistance,
|
||||||
isActive: this.activeMinorStops.includes(i),
|
isActive: this.activeMinorStops.includes(i),
|
||||||
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.train.timetableData!.sceneryNames[currentSceneryIndex],
|
|
||||||
status: stop.confirmed ? 'confirmed' : stop.stopped ? 'stopped' : 'unconfirmed'
|
status: stop.confirmed ? 'confirmed' : stop.stopped ? 'stopped' : 'unconfirmed'
|
||||||
};
|
};
|
||||||
}) ?? []
|
}) ?? []
|
||||||
@@ -483,22 +522,26 @@ $blinkAnim: 0.5s ease-in-out alternate infinite blink;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-line-info {
|
.scenery-route {
|
||||||
.scenery-change-name {
|
img {
|
||||||
position: relative;
|
vertical-align: middle;
|
||||||
margin: 0.25em 0;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&::before {
|
.scenery-change-name {
|
||||||
content: '';
|
position: relative;
|
||||||
position: absolute;
|
margin: 0.25em 0;
|
||||||
height: 2px;
|
|
||||||
width: 30px;
|
|
||||||
background-color: #aaa;
|
|
||||||
|
|
||||||
top: 50%;
|
&::before {
|
||||||
right: calc(100% + 5px);
|
content: '';
|
||||||
transform: translate(0, -50%);
|
position: absolute;
|
||||||
}
|
height: 2px;
|
||||||
|
width: 30px;
|
||||||
|
background-color: #aaa;
|
||||||
|
|
||||||
|
top: 50%;
|
||||||
|
right: calc(100% + 5px);
|
||||||
|
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,330 +1,4 @@
|
|||||||
{
|
{
|
||||||
"optionSections": [
|
|
||||||
"status",
|
|
||||||
"timetables",
|
|
||||||
"reality",
|
|
||||||
"package-access",
|
|
||||||
"access",
|
|
||||||
"control",
|
|
||||||
"blockades",
|
|
||||||
"signals",
|
|
||||||
"addons"
|
|
||||||
],
|
|
||||||
|
|
||||||
"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": "ASDEK",
|
|
||||||
"name": "ASDEK",
|
|
||||||
"section": "addons",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "noASDEK",
|
|
||||||
"name": "noASDEK",
|
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "withActiveTimetables",
|
|
||||||
"name": "withActiveTimetables",
|
|
||||||
"section": "timetables",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "withoutActiveTimetables",
|
|
||||||
"name": "withoutActiveTimetables",
|
|
||||||
"section": "timetables",
|
|
||||||
"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",
|
||||||
|
|||||||
@@ -26,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",
|
||||||
@@ -41,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!",
|
||||||
@@ -76,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 ",
|
||||||
@@ -94,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:",
|
||||||
@@ -107,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)",
|
||||||
@@ -156,17 +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"
|
"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",
|
||||||
|
|
||||||
@@ -177,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",
|
||||||
@@ -191,7 +216,6 @@
|
|||||||
"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",
|
||||||
|
|
||||||
@@ -214,14 +238,20 @@
|
|||||||
"withActiveTimetables": "ACTIVE",
|
"withActiveTimetables": "ACTIVE",
|
||||||
"withoutActiveTimetables": "NO 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",
|
||||||
@@ -272,7 +302,17 @@
|
|||||||
"single-track-routes-other": "Not electrified single-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!",
|
||||||
@@ -291,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",
|
||||||
@@ -315,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",
|
||||||
@@ -351,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",
|
||||||
@@ -359,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",
|
||||||
@@ -450,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!",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"header": "Grosza daj Stacjownikowi!",
|
"header": "Grosza daj Stacjownikowi!",
|
||||||
"donator-title": "Projekt ma już ponad <b>{count}</b> wspierających, w tym:",
|
"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",
|
||||||
@@ -26,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",
|
||||||
@@ -38,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!",
|
||||||
@@ -66,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 ",
|
||||||
@@ -84,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:",
|
||||||
@@ -98,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)",
|
||||||
@@ -147,17 +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"
|
"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",
|
||||||
|
|
||||||
@@ -168,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",
|
||||||
@@ -198,20 +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",
|
"withActiveTimetables": "AKTYWNE",
|
||||||
"withoutActiveTimetables": "BEZ AKTYWNYCH",
|
"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):",
|
||||||
@@ -261,7 +296,17 @@
|
|||||||
"single-track-routes-other": "Liczba niezelektryfikowanych szlaków jednotorowych: "
|
"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!",
|
||||||
@@ -272,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",
|
||||||
@@ -295,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",
|
||||||
@@ -337,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.",
|
||||||
@@ -428,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!",
|
||||||
|
|||||||
@@ -5,10 +5,15 @@ import router from './router';
|
|||||||
import i18n from './i18n';
|
import i18n from './i18n';
|
||||||
|
|
||||||
import { createPinia } from 'pinia';
|
import { createPinia } from 'pinia';
|
||||||
import useCustomSW from './mixins/useCustomSW';
|
import { registerSW } from 'virtual:pwa-register';
|
||||||
|
|
||||||
// Service worker
|
// Service worker
|
||||||
useCustomSW();
|
registerSW({
|
||||||
|
immediate: true,
|
||||||
|
onNeedRefresh() {
|
||||||
|
console.log('Needs refresh!');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const clickOutsideDirective: Directive = {
|
const clickOutsideDirective: Directive = {
|
||||||
mounted(el, binding) {
|
mounted(el, binding) {
|
||||||
|
|||||||
@@ -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: {
|
|
||||||
chosenTrain() {
|
|
||||||
return this.store.trainList.find((train) => train.trainId == this.store.chosenModalTrainId);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
selectModalTrain(trainId: string, target?: EventTarget | null) {
|
selectModalTrain(train: Train, target?: EventTarget | null) {
|
||||||
this.store.chosenModalTrainId = trainId;
|
this.store.chosenModalTrainId = train.modalId;
|
||||||
document.body.classList.add('no-scroll');
|
if (target) this.store.modalLastClickedTarget = target;
|
||||||
|
},
|
||||||
|
|
||||||
|
selectModalTrainById(modalId: string, target?: EventTarget | null) {
|
||||||
|
this.store.chosenModalTrainId = modalId;
|
||||||
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: () => ({
|
||||||
@@ -51,8 +50,8 @@ export default defineComponent({
|
|||||||
return diffMins < 1
|
return diffMins < 1
|
||||||
? this.$t('trains.last-seen-now')
|
? this.$t('trains.last-seen-now')
|
||||||
: diffMins < 2
|
: diffMins < 2
|
||||||
? this.$t('trains.last-seen-min')
|
? this.$t('trains.last-seen-min')
|
||||||
: this.$t('trains.last-seen-ago', { minutes: diffMins });
|
: this.$t('trains.last-seen-ago', { minutes: diffMins });
|
||||||
},
|
},
|
||||||
|
|
||||||
displayTrainPosition(train: Train) {
|
displayTrainPosition(train: Train) {
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
import { useRegisterSW } from 'virtual:pwa-register/vue';
|
|
||||||
|
|
||||||
export default () => {
|
|
||||||
const { needRefresh, updateServiceWorker, offlineReady } = useRegisterSW({
|
|
||||||
immediate: true
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
needRefresh,
|
|
||||||
updateServiceWorker,
|
|
||||||
offlineReady
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -58,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,21 +0,0 @@
|
|||||||
export const headIds = [
|
|
||||||
'station',
|
|
||||||
'min-lvl',
|
|
||||||
'status',
|
|
||||||
'dispatcher',
|
|
||||||
'dispatcher-lvl',
|
|
||||||
'routes-single',
|
|
||||||
'routes-double',
|
|
||||||
'general'
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export const headIconsIds = [
|
|
||||||
'user',
|
|
||||||
'like',
|
|
||||||
'spawn',
|
|
||||||
'timetableAll',
|
|
||||||
'timetableUnconfirmed',
|
|
||||||
'timetableConfirmed'
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export type HeadIdsTypes = (typeof headIds)[number] | (typeof headIconsIds)[number];
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
import { Availability, ActiveScenery } from '../../store/typings';
|
|
||||||
import { StationRoutes } from './StationRoutes';
|
|
||||||
|
|
||||||
export default interface Station {
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
generalInfo?: {
|
|
||||||
name: string;
|
|
||||||
url: string;
|
|
||||||
abbr: string;
|
|
||||||
hash?: string;
|
|
||||||
|
|
||||||
reqLevel: number;
|
|
||||||
// supportersOnly: boolean;
|
|
||||||
|
|
||||||
lines: string;
|
|
||||||
project: string;
|
|
||||||
projectUrl?: string;
|
|
||||||
|
|
||||||
signalType: string;
|
|
||||||
controlType: string;
|
|
||||||
|
|
||||||
SUP: boolean;
|
|
||||||
ASDEK: boolean;
|
|
||||||
authors?: string[];
|
|
||||||
|
|
||||||
availability: Availability;
|
|
||||||
routes: StationRoutes;
|
|
||||||
|
|
||||||
checkpoints: string[];
|
|
||||||
};
|
|
||||||
|
|
||||||
onlineInfo?: ActiveScenery;
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { StationRoutesInfo } from '../../store/typings';
|
|
||||||
|
|
||||||
export interface StationRoutes {
|
|
||||||
single: StationRoutesInfo[];
|
|
||||||
double: StationRoutesInfo[];
|
|
||||||
|
|
||||||
singleElectrifiedNames: string[];
|
|
||||||
singleOtherNames: string[];
|
|
||||||
doubleElectrifiedNames: string[];
|
|
||||||
doubleOtherNames: string[];
|
|
||||||
sblNames: string[];
|
|
||||||
}
|
|
||||||
@@ -1,38 +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[];
|
|
||||||
sceneryNames: string[];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,233 +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 '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 ? b.onlineInfo.currentUsers : -1) -
|
|
||||||
(a.onlineInfo ? a.onlineInfo.currentUsers : -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: Filter) => {
|
|
||||||
if (filters['free'] && (!station.onlineInfo || station.onlineInfo.dispatcherId == -1))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (station.onlineInfo) {
|
|
||||||
const { dispatcherStatus } = station.onlineInfo;
|
|
||||||
|
|
||||||
const excludeEnding =
|
|
||||||
dispatcherStatus == Status.ActiveDispatcher.ENDING && filters['endingStatus'];
|
|
||||||
|
|
||||||
const excludeNotSigned =
|
|
||||||
(dispatcherStatus == Status.ActiveDispatcher.NOT_LOGGED_IN ||
|
|
||||||
dispatcherStatus == Status.ActiveDispatcher.UNAVAILABLE) &&
|
|
||||||
filters['unavailableStatus'];
|
|
||||||
|
|
||||||
const excludeAFK = dispatcherStatus == Status.ActiveDispatcher.AFK && filters['afkStatus'];
|
|
||||||
|
|
||||||
const excludeNoSpace =
|
|
||||||
dispatcherStatus == Status.ActiveDispatcher.NO_SPACE && filters['noSpaceStatus'];
|
|
||||||
|
|
||||||
const excludeOccupied = filters['occupied'] && dispatcherStatus != Status.ActiveDispatcher.FREE;
|
|
||||||
|
|
||||||
const excludeActiveTTs =
|
|
||||||
(dispatcherStatus == Status.ActiveDispatcher.FREE ||
|
|
||||||
station.onlineInfo.scheduledTrainCount.all != 0) &&
|
|
||||||
filters['withActiveTimetables'];
|
|
||||||
|
|
||||||
if (
|
|
||||||
excludeEnding ||
|
|
||||||
excludeAFK ||
|
|
||||||
excludeNoSpace ||
|
|
||||||
excludeNotSigned ||
|
|
||||||
excludeOccupied ||
|
|
||||||
excludeActiveTTs
|
|
||||||
)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (
|
|
||||||
filters['onlineFromHours'] > 0 &&
|
|
||||||
dispatcherStatus <= Date.now() + filters['onlineFromHours'] * 3600000
|
|
||||||
)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const excludeNoActiveTTs =
|
|
||||||
filters['withoutActiveTimetables'] &&
|
|
||||||
(!station.onlineInfo || station.onlineInfo.scheduledTrainCount.all == 0);
|
|
||||||
|
|
||||||
if (excludeNoActiveTTs) return false;
|
|
||||||
|
|
||||||
if (
|
|
||||||
(station.generalInfo?.availability == 'nonPublic' || !station.generalInfo) &&
|
|
||||||
filters['nonPublic']
|
|
||||||
)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (station.generalInfo) {
|
|
||||||
const { routes, availability, controlType, lines, reqLevel, signalType, SUP, ASDEK, 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.singleElectrifiedNames.length != 0 || routes.singleOtherNames.length != 0)
|
|
||||||
)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (
|
|
||||||
filters['no-2track'] &&
|
|
||||||
(routes.doubleElectrifiedNames.length != 0 || routes.doubleOtherNames.length != 0)
|
|
||||||
)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (routes.singleElectrifiedNames.length < filters['minOneWayCatenary']) return false;
|
|
||||||
if (routes.singleOtherNames.length < filters['minOneWay']) return false;
|
|
||||||
|
|
||||||
if (routes.doubleElectrifiedNames.length < filters['minTwoWayCatenary']) return false;
|
|
||||||
if (routes.doubleOtherNames.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['ASDEK'] && ASDEK) return false;
|
|
||||||
if (filters['noASDEK'] && !ASDEK) return false;
|
|
||||||
|
|
||||||
if (filters['SBL'] && routes.sblNames.length > 0) return false;
|
|
||||||
if (filters['PBL'] && routes.sblNames.length == 0) return false;
|
|
||||||
|
|
||||||
if (
|
|
||||||
filters['authors'].length > 3 &&
|
|
||||||
!authors?.map((a) => a.toLocaleLowerCase()).includes(filters['authors'].toLocaleLowerCase())
|
|
||||||
)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
@@ -4,26 +4,22 @@ import { Status } from '../typings/common';
|
|||||||
import { StationJSONData } from './typings';
|
import { StationJSONData } from './typings';
|
||||||
import axios, { AxiosInstance } from 'axios';
|
import axios, { AxiosInstance } from 'axios';
|
||||||
|
|
||||||
// Update seconds cron for active data scheduler
|
|
||||||
const UPDATE_SECONDS = [3, 23, 43];
|
|
||||||
|
|
||||||
export enum APIMode {
|
|
||||||
PRODUCTION = 0,
|
|
||||||
DEV = 1,
|
|
||||||
MOCK = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
||||||
|
vehicles: Status.Data.Loading
|
||||||
},
|
},
|
||||||
|
|
||||||
activeData: undefined as API.ActiveData.Response | undefined,
|
activeData: undefined as API.ActiveData.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[],
|
||||||
|
|
||||||
|
lastFetchData: new Date(),
|
||||||
|
|
||||||
client: undefined as AxiosInstance | undefined,
|
client: undefined as AxiosInstance | undefined,
|
||||||
|
|
||||||
activeDataScheduler: undefined as number | undefined
|
activeDataScheduler: undefined as number | undefined
|
||||||
@@ -56,31 +52,35 @@ export const useApiStore = defineStore('apiStore', {
|
|||||||
this.fetchDonatorsData();
|
this.fetchDonatorsData();
|
||||||
this.fetchStationsGeneralInfo();
|
this.fetchStationsGeneralInfo();
|
||||||
|
|
||||||
// Active data schedueler
|
// Ponowne pobieranie danych po ServiceWorkerze
|
||||||
this.fetchActiveData();
|
setTimeout(() => {
|
||||||
this.setupActiveDataFetcher();
|
this.fetchStationsGeneralInfo();
|
||||||
},
|
}, Math.floor(Math.random() * 500) + 1000);
|
||||||
|
|
||||||
async setupActiveDataFetcher() {
|
this.fetchVehiclesInfo();
|
||||||
if (this.activeDataScheduler) return;
|
|
||||||
|
|
||||||
this.dataStatuses.connection = Status.Data.Loading;
|
|
||||||
|
|
||||||
this.activeDataScheduler = window.setInterval(() => {
|
|
||||||
if (UPDATE_SECONDS.includes(new Date().getSeconds())) {
|
|
||||||
this.fetchActiveData();
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async fetchActiveData() {
|
async fetchActiveData() {
|
||||||
|
if (import.meta.env.VITE_API_ACTIVE_DATA_MODE == 'mocking') {
|
||||||
|
import('../../tests/data/getActiveData.json').then((data) => {
|
||||||
|
console.warn('activeData: mocking mode');
|
||||||
|
this.activeData = data.default as API.ActiveData.Response;
|
||||||
|
this.lastFetchData = new Date();
|
||||||
|
|
||||||
|
this.dataStatuses.connection = Status.Data.Loaded;
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.activeData) this.dataStatuses.connection = Status.Data.Loading;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.client!.get<API.ActiveData.Response>('api/getActiveData');
|
const response = await this.client!.get<API.ActiveData.Response>('api/getActiveData');
|
||||||
|
|
||||||
this.activeData = response.data;
|
this.activeData = response.data;
|
||||||
|
this.lastFetchData = new Date();
|
||||||
this.dataStatuses.connection = Status.Data.Loaded;
|
this.dataStatuses.connection = Status.Data.Loaded;
|
||||||
|
|
||||||
console.log('Fetching active data at ' + new Date().toLocaleTimeString('pl-PL'));
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.dataStatuses.connection = Status.Data.Error;
|
this.dataStatuses.connection = Status.Data.Error;
|
||||||
console.error('Ups! Wystąpił błąd podczas pobierania danych online:', error);
|
console.error('Ups! Wystąpił błąd podczas pobierania danych online:', error);
|
||||||
@@ -98,17 +98,39 @@ export const useApiStore = defineStore('apiStore', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async fetchStationsGeneralInfo() {
|
async fetchStationsGeneralInfo() {
|
||||||
const sceneryData: StationJSONData[] = (
|
try {
|
||||||
await this.client!.get<StationJSONData[]>('api/getSceneries')
|
const sceneryData: StationJSONData[] = (
|
||||||
).data;
|
await this.client!.get<StationJSONData[]>('api/getSceneries')
|
||||||
|
).data;
|
||||||
|
|
||||||
if (!sceneryData) {
|
this.dataStatuses.sceneries = Status.Data.Loaded;
|
||||||
|
this.sceneryData = sceneryData;
|
||||||
|
} catch (error) {
|
||||||
this.dataStatuses.sceneries = Status.Data.Error;
|
this.dataStatuses.sceneries = Status.Data.Error;
|
||||||
return;
|
console.error('Ups! Wystąpił błąd podczas pobierania informacji o sceneriach:', error);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
this.dataStatuses.sceneries = Status.Data.Loaded;
|
async fetchVehiclesInfo() {
|
||||||
this.sceneryData = sceneryData;
|
// 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 { ActiveScenery, 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 { StationRoutes } from '../scripts/interfaces/StationRoutes';
|
import { MainStoreState } from './typings';
|
||||||
|
|
||||||
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,
|
||||||
isNewUpdate: 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) => {
|
||||||
@@ -45,13 +53,15 @@ export const useMainStore = defineStore('store', {
|
|||||||
const sceneryNames =
|
const sceneryNames =
|
||||||
train.timetable?.sceneries?.map(
|
train.timetable?.sceneries?.map(
|
||||||
(sceneryHash) =>
|
(sceneryHash) =>
|
||||||
this.activeSceneryList.find((st) => st.hash === sceneryHash)?.name ??
|
apiStore.activeData?.activeSceneries?.find((st) => st.stationHash === sceneryHash)
|
||||||
|
?.stationName ??
|
||||||
apiStore.sceneryData.find((sd) => sd.hash === sceneryHash)?.name ??
|
apiStore.sceneryData.find((sd) => sd.hash === sceneryHash)?.name ??
|
||||||
sceneryHash
|
sceneryHash
|
||||||
) ?? [];
|
) ?? [];
|
||||||
|
|
||||||
return {
|
const trainObj = {
|
||||||
trainId: train.driverName + train.trainNo.toString(),
|
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,
|
||||||
@@ -86,13 +96,69 @@ export const useMainStore = defineStore('store', {
|
|||||||
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()
|
sceneryNames: sceneryNames.reverse(),
|
||||||
|
timetablePath: timetable.path.split(';').map((pathElementString) => {
|
||||||
|
const [arrival, station, departure] = pathElementString.split(',');
|
||||||
|
|
||||||
|
return {
|
||||||
|
arrivalRouteExt: arrival,
|
||||||
|
departureRouteExt: departure,
|
||||||
|
stationName: station.split(' ').slice(0, -1).join(' '),
|
||||||
|
stationHash: station.split(' ').slice(-1).join(' ')
|
||||||
|
};
|
||||||
|
})
|
||||||
}
|
}
|
||||||
: 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
|
||||||
|
if (trainObj.timetableData) {
|
||||||
|
let currentSceneryIndex = 0;
|
||||||
|
const timetablePath = trainObj.timetableData.timetablePath;
|
||||||
|
|
||||||
|
trainObj.timetableData.followingStops.forEach((stop, i) => {
|
||||||
|
if (/strong|podg|pe/.test(stop.stopName)) {
|
||||||
|
const checkpointTrain: CheckpointTrain = {
|
||||||
|
train: trainObj,
|
||||||
|
checkpointStop: stop,
|
||||||
|
|
||||||
|
previousSceneryElement:
|
||||||
|
currentSceneryIndex > 0 ? timetablePath[currentSceneryIndex - 1] : null,
|
||||||
|
|
||||||
|
nextSceneryElement:
|
||||||
|
currentSceneryIndex < timetablePath.length - 1
|
||||||
|
? timetablePath[currentSceneryIndex + 1]
|
||||||
|
: null,
|
||||||
|
|
||||||
|
timetablePathElement: timetablePath[currentSceneryIndex]
|
||||||
|
};
|
||||||
|
|
||||||
|
if (checkpointsTrains.has(stop.stopNameRAW.toLowerCase())) {
|
||||||
|
checkpointsTrains.set(stop.stopNameRAW.toLowerCase(), [
|
||||||
|
...checkpointsTrains.get(stop.stopNameRAW.toLowerCase())!,
|
||||||
|
checkpointTrain
|
||||||
|
]);
|
||||||
|
} else checkpointsTrains.set(stop.stopNameRAW.toLowerCase(), [checkpointTrain]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timetablePath[currentSceneryIndex].departureRouteExt == stop.departureLine)
|
||||||
|
currentSceneryIndex++;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return trainObj;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// computed active sceneries
|
||||||
activeSceneryList(state): ActiveScenery[] {
|
activeSceneryList(state): ActiveScenery[] {
|
||||||
const apiStore = useApiStore();
|
const apiStore = useApiStore();
|
||||||
|
|
||||||
@@ -107,7 +173,10 @@ export const useMainStore = defineStore('store', {
|
|||||||
if (
|
if (
|
||||||
acc.findIndex((v) => v.name == name && v.region == train.region) != -1 ||
|
acc.findIndex((v) => v.name == name && v.region == train.region) != -1 ||
|
||||||
apiStore.activeData?.activeSceneries?.findIndex(
|
apiStore.activeData?.activeSceneries?.findIndex(
|
||||||
(sc) => sc.stationName === name && sc.region == train.region
|
(sc) =>
|
||||||
|
sc.stationName === name &&
|
||||||
|
sc.region == train.region &&
|
||||||
|
Date.now() - sc.lastSeen < 1000 * 60 * 2
|
||||||
) != -1
|
) != -1
|
||||||
)
|
)
|
||||||
return acc;
|
return acc;
|
||||||
@@ -124,13 +193,14 @@ export const useMainStore = defineStore('store', {
|
|||||||
dispatcherId: -1,
|
dispatcherId: -1,
|
||||||
dispatcherExp: -1,
|
dispatcherExp: -1,
|
||||||
dispatcherIsSupporter: false,
|
dispatcherIsSupporter: false,
|
||||||
scheduledTrains: [],
|
|
||||||
stationTrains: [],
|
|
||||||
dispatcherStatus: Status.ActiveDispatcher.FREE,
|
dispatcherStatus: Status.ActiveDispatcher.FREE,
|
||||||
dispatcherTimestamp: -1,
|
dispatcherTimestamp: -1,
|
||||||
|
|
||||||
isOnline: false,
|
isOnline: false,
|
||||||
|
|
||||||
|
stationTrains: [],
|
||||||
|
scheduledTrains: [],
|
||||||
|
|
||||||
scheduledTrainCount: {
|
scheduledTrainCount: {
|
||||||
all: 0,
|
all: 0,
|
||||||
confirmed: 0,
|
confirmed: 0,
|
||||||
@@ -150,8 +220,8 @@ export const useMainStore = defineStore('store', {
|
|||||||
scenery.dispatcherStatus == Status.ActiveDispatcher.NO_LIMIT
|
scenery.dispatcherStatus == Status.ActiveDispatcher.NO_LIMIT
|
||||||
? Date.now() + 25500000
|
? Date.now() + 25500000
|
||||||
: scenery.dispatcherStatus > 5
|
: scenery.dispatcherStatus > 5
|
||||||
? scenery.dispatcherStatus
|
? scenery.dispatcherStatus
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
list.push({
|
list.push({
|
||||||
name: scenery.stationName,
|
name: scenery.stationName,
|
||||||
@@ -170,8 +240,9 @@ export const useMainStore = defineStore('store', {
|
|||||||
|
|
||||||
isOnline: scenery.isOnline == 1,
|
isOnline: scenery.isOnline == 1,
|
||||||
|
|
||||||
scheduledTrains: [],
|
|
||||||
stationTrains: [],
|
stationTrains: [],
|
||||||
|
scheduledTrains: [],
|
||||||
|
|
||||||
scheduledTrainCount: {
|
scheduledTrainCount: {
|
||||||
all: 0,
|
all: 0,
|
||||||
confirmed: 0,
|
confirmed: 0,
|
||||||
@@ -189,42 +260,47 @@ export const useMainStore = defineStore('store', {
|
|||||||
|
|
||||||
const station = this.stationList.find((s) => s.name === scenery.name);
|
const station = this.stationList.find((s) => s.name === scenery.name);
|
||||||
|
|
||||||
const scheduledTrains = getScheduledTrains(
|
let checkpointsSet: Set<string> = new Set();
|
||||||
this.trainList,
|
|
||||||
station?.generalInfo,
|
|
||||||
scenery.name,
|
|
||||||
scenery.region
|
|
||||||
);
|
|
||||||
|
|
||||||
const stationTrains = getStationTrains(
|
// Add checkpoints to active scenery data
|
||||||
this.trainList,
|
checkpointsSet.add(scenery.name.toLowerCase());
|
||||||
scheduledTrains,
|
|
||||||
this.region.id,
|
|
||||||
scenery.name
|
|
||||||
);
|
|
||||||
|
|
||||||
// Remove checkpoint duplicates
|
station?.generalInfo?.checkpoints.forEach((cpName) => {
|
||||||
const uniqueScheduledTrains = scheduledTrains.reduce(
|
checkpointsSet.add(cpName.toLowerCase());
|
||||||
(uniqueList, sTrain) =>
|
});
|
||||||
uniqueList.find((v) => v.trainId === sTrain.trainId)
|
|
||||||
? uniqueList
|
|
||||||
: [...uniqueList, sTrain],
|
|
||||||
[] as ScheduledTrain[]
|
|
||||||
);
|
|
||||||
|
|
||||||
scenery.scheduledTrains = scheduledTrains;
|
const checkpoints = Array.from(checkpointsSet);
|
||||||
scenery.stationTrains = stationTrains;
|
|
||||||
|
|
||||||
scenery.scheduledTrainCount = {
|
scenery.stationTrains =
|
||||||
all: uniqueScheduledTrains.length,
|
sceneriesTrains.get(scenery.name)?.filter((sc) => sc.region == this.region.id) ?? [];
|
||||||
confirmed: uniqueScheduledTrains.filter((train) => train.stopInfo.confirmed).length,
|
|
||||||
unconfirmed: uniqueScheduledTrains.filter((train) => !train.stopInfo.confirmed).length
|
const uniqueTrainIds: string[] = [];
|
||||||
};
|
checkpoints.forEach((cp) => {
|
||||||
|
const scheduledTrains = checkpointsTrains.get(cp.toLowerCase());
|
||||||
|
|
||||||
|
if (!scheduledTrains) return;
|
||||||
|
|
||||||
|
scheduledTrains.forEach(({ train, checkpointStop, timetablePathElement, ...v }) => {
|
||||||
|
if (scenery.name != timetablePathElement.stationName) return;
|
||||||
|
|
||||||
|
scenery.scheduledTrains.push({ train, checkpointStop, timetablePathElement, ...v });
|
||||||
|
|
||||||
|
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;
|
return allActiveSceneries;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// computed station data
|
||||||
stationList(): Station[] {
|
stationList(): Station[] {
|
||||||
const apiStore = useApiStore();
|
const apiStore = useApiStore();
|
||||||
|
|
||||||
@@ -242,6 +318,13 @@ export const useMainStore = defineStore('store', {
|
|||||||
if (!route.isInternal) acc[routesKey].push(route.routeName);
|
if (!route.isInternal) acc[routesKey].push(route.routeName);
|
||||||
if (route.isRouteSBL) acc['sblNames'].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;
|
||||||
@@ -253,7 +336,9 @@ export const useMainStore = defineStore('store', {
|
|||||||
double: [],
|
double: [],
|
||||||
doubleElectrifiedNames: [],
|
doubleElectrifiedNames: [],
|
||||||
doubleOtherNames: [],
|
doubleOtherNames: [],
|
||||||
sblNames: []
|
sblNames: [],
|
||||||
|
minRouteSpeed: 0,
|
||||||
|
maxRouteSpeed: 0
|
||||||
} as StationRoutes
|
} as StationRoutes
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -264,10 +349,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?.split(';') ?? []
|
checkpoints:
|
||||||
|
scenery.checkpoints && scenery.checkpoints.trim().length > 0
|
||||||
|
? scenery.checkpoints.split(';')
|
||||||
|
: []
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
allStationInfo(): Station[] {
|
||||||
|
const onlineUnsavedStations = this.activeSceneryList
|
||||||
|
.filter(
|
||||||
|
(scenery) =>
|
||||||
|
this.stationList.findIndex((st) => st.name == scenery.name) == -1 &&
|
||||||
|
scenery.region == this.region.id
|
||||||
|
)
|
||||||
|
.map((os) => ({
|
||||||
|
name: os.name,
|
||||||
|
generalInfo: undefined,
|
||||||
|
onlineInfo: os
|
||||||
|
}));
|
||||||
|
|
||||||
|
return [
|
||||||
|
...onlineUnsavedStations,
|
||||||
|
...this.stationList.map((st) => ({
|
||||||
|
...st,
|
||||||
|
onlineInfo: this.activeSceneryList.find(
|
||||||
|
(os) => os.name == st.name && os.region == this.region.id
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,160 +0,0 @@
|
|||||||
import { defineStore } from 'pinia';
|
|
||||||
import inputData from '../data/options.json';
|
|
||||||
import { useMainStore } from './mainStore';
|
|
||||||
import { filterStations, sortStations } from '../scripts/utils/stationFilterUtils';
|
|
||||||
import { HeadIdsTypes } from '../scripts/data/stationHeaderNames';
|
|
||||||
import StorageManager from '../managers/storageManager';
|
|
||||||
import { Filter } from '../components/StationsView/typings';
|
|
||||||
|
|
||||||
const filterInitStates: Filter = {
|
|
||||||
default: false,
|
|
||||||
notDefault: false,
|
|
||||||
real: false,
|
|
||||||
fictional: false,
|
|
||||||
SPK: false,
|
|
||||||
SCS: false,
|
|
||||||
SPE: false,
|
|
||||||
SUP: false,
|
|
||||||
noSUP: false,
|
|
||||||
ASDEK: false,
|
|
||||||
noASDEK: false,
|
|
||||||
ręczne: false,
|
|
||||||
'ręczne+SPK': false,
|
|
||||||
'ręczne+SCS': false,
|
|
||||||
mechaniczne: false,
|
|
||||||
'mechaniczne+SPK': false,
|
|
||||||
'mechaniczne+SCS': false,
|
|
||||||
współczesna: false,
|
|
||||||
kształtowa: false,
|
|
||||||
historyczna: false,
|
|
||||||
mieszana: false,
|
|
||||||
SBL: false,
|
|
||||||
PBL: false,
|
|
||||||
minLevel: 0,
|
|
||||||
maxLevel: 20,
|
|
||||||
minOneWayCatenary: 0,
|
|
||||||
minOneWay: 0,
|
|
||||||
minTwoWayCatenary: 0,
|
|
||||||
minTwoWay: 0,
|
|
||||||
'include-selected': false,
|
|
||||||
'no-1track': false,
|
|
||||||
'no-2track': false,
|
|
||||||
free: true,
|
|
||||||
occupied: false,
|
|
||||||
ending: false,
|
|
||||||
nonPublic: false,
|
|
||||||
unavailable: true,
|
|
||||||
abandoned: true,
|
|
||||||
afkStatus: false,
|
|
||||||
endingStatus: false,
|
|
||||||
noSpaceStatus: false,
|
|
||||||
unavailableStatus: false,
|
|
||||||
unsignedStatus: false,
|
|
||||||
withActiveTimetables: false,
|
|
||||||
withoutActiveTimetables: false,
|
|
||||||
|
|
||||||
authors: '',
|
|
||||||
|
|
||||||
onlineFromHours: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useStationFiltersStore = defineStore('stationFiltersStore', {
|
|
||||||
state() {
|
|
||||||
return {
|
|
||||||
inputs: inputData,
|
|
||||||
filters: { ...filterInitStates },
|
|
||||||
sorterActive: { headerName: 'station' as HeadIdsTypes, dir: 1 },
|
|
||||||
lastClickedFilterId: ''
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
getters: {
|
|
||||||
areFiltersAtDefault: (state) => {
|
|
||||||
return Object.keys(state.filters).every((f) => state.filters[f] === filterInitStates[f]);
|
|
||||||
},
|
|
||||||
|
|
||||||
filteredStationList: (state) => {
|
|
||||||
const store = useMainStore();
|
|
||||||
const savedStationNames = store.stationList.map((s) => s.name);
|
|
||||||
|
|
||||||
const onlineUnsavedStations = store.activeSceneryList
|
|
||||||
.filter((os) => !savedStationNames.includes(os.name) && os.region == store.region.id)
|
|
||||||
.map((os) => ({
|
|
||||||
name: os.name,
|
|
||||||
generalInfo: undefined,
|
|
||||||
onlineInfo: os
|
|
||||||
}));
|
|
||||||
|
|
||||||
return [
|
|
||||||
...onlineUnsavedStations,
|
|
||||||
...store.stationList.map((station) => ({
|
|
||||||
...station,
|
|
||||||
// append to 'onlineInfo' object for filtering legacy reasons - to optimize later (hopefully)
|
|
||||||
onlineInfo: store.activeSceneryList.find(
|
|
||||||
(os) => os.name == station.name && os.region == store.region.id
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
]
|
|
||||||
.filter((station) => filterStations(station, state.filters))
|
|
||||||
.sort((a, b) => sortStations(a, b, state.sorterActive));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
setupFilters() {
|
|
||||||
if (!StorageManager.isRegistered('options_saved')) return;
|
|
||||||
|
|
||||||
this.inputs.options.forEach((option) => {
|
|
||||||
if (!StorageManager.isRegistered(option.name)) return;
|
|
||||||
const savedValue = StorageManager.getBooleanValue(option.name);
|
|
||||||
|
|
||||||
this.filters[option.name] = savedValue;
|
|
||||||
option.value = !savedValue;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.inputs.sliders.forEach((slider) => {
|
|
||||||
if (!StorageManager.isRegistered(slider.name)) return;
|
|
||||||
const savedValue = StorageManager.getNumericValue(slider.name);
|
|
||||||
|
|
||||||
this.filters[slider.name] = savedValue;
|
|
||||||
slider.value = savedValue;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
changeFilterValue(name: string, value: any) {
|
|
||||||
this.filters[name] = value;
|
|
||||||
if (StorageManager.isRegistered('options_saved')) StorageManager.setValue(name, value);
|
|
||||||
},
|
|
||||||
|
|
||||||
resetFilters() {
|
|
||||||
this.filters = { ...filterInitStates };
|
|
||||||
|
|
||||||
this.inputs.options.forEach((option) => {
|
|
||||||
option.value = option.defaultValue;
|
|
||||||
StorageManager.setBooleanValue(option.name, !option.defaultValue);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.inputs.sliders.forEach((slider) => {
|
|
||||||
slider.value = slider.defaultValue;
|
|
||||||
StorageManager.setNumericValue(slider.name, slider.defaultValue);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
resetSectionOptions(section: string) {
|
|
||||||
this.inputs.options
|
|
||||||
.filter((option) => option.section == section)
|
|
||||||
.forEach((option) => {
|
|
||||||
option.value = option.defaultValue;
|
|
||||||
StorageManager.setBooleanValue(option.name, !option.defaultValue);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
changeSorter(headerName: HeadIdsTypes) {
|
|
||||||
if (headerName == this.sorterActive.headerName)
|
|
||||||
this.sorterActive.dir = -1 * this.sorterActive.dir;
|
|
||||||
else this.sorterActive.dir = 1;
|
|
||||||
|
|
||||||
this.sorterActive.headerName = headerName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { defineStore } from 'pinia';
|
||||||
|
|
||||||
|
const isTooltip = (v: any): v is TooltipType => tooltipKeys.includes(v);
|
||||||
|
|
||||||
|
export const tooltipKeys = [
|
||||||
|
'DonatorTooltip',
|
||||||
|
'BaseTooltip',
|
||||||
|
'VehiclePreviewTooltip',
|
||||||
|
'SpawnsTooltip',
|
||||||
|
'UsersTooltip'
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export type TooltipType = (typeof tooltipKeys)[number];
|
||||||
|
|
||||||
|
export const useTooltipStore = defineStore('tooltipStore', {
|
||||||
|
state: () => ({
|
||||||
|
mousePos: [0, 0],
|
||||||
|
type: null as TooltipType | null,
|
||||||
|
content: ''
|
||||||
|
}),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
show(_e: MouseEvent, type: string, value?: string) {
|
||||||
|
if (!isTooltip(type)) return;
|
||||||
|
|
||||||
|
this.type = type;
|
||||||
|
this.content = value ?? '';
|
||||||
|
},
|
||||||
|
|
||||||
|
hide() {
|
||||||
|
this.type = null;
|
||||||
|
this.content = '';
|
||||||
|
},
|
||||||
|
|
||||||
|
handle(e: MouseEvent) {
|
||||||
|
const targetEl = e
|
||||||
|
.composedPath()
|
||||||
|
.find((p) => p instanceof HTMLElement && p.getAttribute('data-tooltip-type'));
|
||||||
|
|
||||||
|
if (!targetEl || !(targetEl instanceof HTMLElement)) {
|
||||||
|
if (this.type != null) this.hide();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tooltipType = targetEl.getAttribute('data-tooltip-type');
|
||||||
|
const tooltipContent = targetEl.getAttribute('data-tooltip-content');
|
||||||
|
|
||||||
|
if (tooltipType && tooltipContent) this.show(e, tooltipType, tooltipContent);
|
||||||
|
else if (this.type != null) this.hide();
|
||||||
|
|
||||||
|
this.mousePos[0] = e.pageX;
|
||||||
|
this.mousePos[1] = e.pageY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -1,45 +1,19 @@
|
|||||||
import { API } from '../typings/api';
|
import { API } from '../typings/api';
|
||||||
import { Status } from '../typings/common';
|
import { Availability, CheckpointTrain, StationRoutesInfo, Status } from '../typings/common';
|
||||||
|
|
||||||
export type Availability = 'default' | 'unavailable' | 'nonPublic' | 'abandoned' | 'nonDefault';
|
|
||||||
|
|
||||||
export interface RegionCounters {
|
|
||||||
stationCount: number;
|
|
||||||
trainsCount: number;
|
|
||||||
timetablesCount: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface StoreState {
|
|
||||||
region: { id: string; value: string };
|
|
||||||
|
|
||||||
|
export interface MainStoreState {
|
||||||
|
region: { id: string; value: string; name: string };
|
||||||
isOffline: boolean;
|
isOffline: boolean;
|
||||||
|
appUpdate: { version: string; changelog: string; releaseURL: string } | null;
|
||||||
isNewUpdate: boolean;
|
|
||||||
|
|
||||||
dispatcherStatsName: string;
|
dispatcherStatsName: string;
|
||||||
dispatcherStatsData?: API.DispatcherStats.Response;
|
dispatcherStatsData?: API.DispatcherStats.Response;
|
||||||
|
|
||||||
driverStatsName: string;
|
driverStatsName: string;
|
||||||
driverStatsData?: API.DriverStats.Response;
|
driverStatsData?: API.DriverStats.Response;
|
||||||
driverStatsStatus: Status.Data;
|
driverStatsStatus: Status.Data;
|
||||||
|
|
||||||
chosenModalTrainId?: string;
|
chosenModalTrainId?: string;
|
||||||
|
|
||||||
blockScroll: boolean;
|
|
||||||
modalLastClickedTarget: EventTarget | null;
|
modalLastClickedTarget: EventTarget | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StationRoutesInfo {
|
|
||||||
routeName: string;
|
|
||||||
isElectric: boolean;
|
|
||||||
isInternal: boolean;
|
|
||||||
isRouteSBL: boolean;
|
|
||||||
routeLength: number;
|
|
||||||
routeSpeed: number;
|
|
||||||
routeTracks: number;
|
|
||||||
hidden?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface StationJSONData {
|
export interface StationJSONData {
|
||||||
name: string;
|
name: string;
|
||||||
abbr: string;
|
abbr: string;
|
||||||
@@ -65,110 +39,3 @@ export interface StationJSONData {
|
|||||||
|
|
||||||
availability: Availability;
|
availability: Availability;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ActiveScenery {
|
|
||||||
name: string;
|
|
||||||
hash: string;
|
|
||||||
region: string;
|
|
||||||
maxUsers: number;
|
|
||||||
currentUsers: number;
|
|
||||||
spawns: { spawnName: string; spawnLength: number; isElectrified: boolean }[];
|
|
||||||
dispatcherName: string;
|
|
||||||
dispatcherRate: number;
|
|
||||||
dispatcherId: number;
|
|
||||||
dispatcherExp: number;
|
|
||||||
dispatcherIsSupporter: boolean;
|
|
||||||
|
|
||||||
dispatcherStatus: Status.ActiveDispatcher | number;
|
|
||||||
dispatcherTimestamp: number | null;
|
|
||||||
|
|
||||||
isOnline: boolean;
|
|
||||||
|
|
||||||
stationTrains?: StationTrain[];
|
|
||||||
scheduledTrains?: ScheduledTrain[];
|
|
||||||
|
|
||||||
scheduledTrainCount: {
|
|
||||||
all: number;
|
|
||||||
confirmed: number;
|
|
||||||
unconfirmed: number;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface StationTrain {
|
|
||||||
driverName: string;
|
|
||||||
driverId: number;
|
|
||||||
trainNo: number;
|
|
||||||
trainId: string;
|
|
||||||
stopStatus: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ScheduledTrain {
|
|
||||||
checkpointName: string;
|
|
||||||
|
|
||||||
trainId: string;
|
|
||||||
trainNo: number;
|
|
||||||
|
|
||||||
driverName: string;
|
|
||||||
driverId: number;
|
|
||||||
currentStationName: string;
|
|
||||||
currentStationHash: string;
|
|
||||||
category: string;
|
|
||||||
stopInfo: TrainStop;
|
|
||||||
|
|
||||||
terminatesAt: string;
|
|
||||||
beginsAt: string;
|
|
||||||
|
|
||||||
prevStationName: string;
|
|
||||||
nextStationName: string;
|
|
||||||
|
|
||||||
arrivingLine: string | null;
|
|
||||||
departureLine: string | null;
|
|
||||||
|
|
||||||
prevDepartureLine: string | null;
|
|
||||||
nextArrivalLine: string | null;
|
|
||||||
|
|
||||||
signal: string;
|
|
||||||
connectedTrack: string;
|
|
||||||
|
|
||||||
stopLabel: string;
|
|
||||||
stopStatus: StopStatus;
|
|
||||||
stopStatusID: number;
|
|
||||||
|
|
||||||
region: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum StopStatus {
|
|
||||||
ARRIVING = 'arriving',
|
|
||||||
DEPARTED = 'departed',
|
|
||||||
DEPARTED_AWAY = 'departed-away',
|
|
||||||
ONLINE = 'online',
|
|
||||||
STOPPED = 'stopped',
|
|
||||||
TERMINATED = 'terminated'
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TrainStop {
|
|
||||||
stopName: string;
|
|
||||||
stopNameRAW: string;
|
|
||||||
stopType: string;
|
|
||||||
stopDistance: number;
|
|
||||||
mainStop: boolean;
|
|
||||||
|
|
||||||
arrivalLine: string | null;
|
|
||||||
arrivalTimestamp: number;
|
|
||||||
arrivalRealTimestamp: number;
|
|
||||||
arrivalDelay: number;
|
|
||||||
|
|
||||||
departureLine: string | null;
|
|
||||||
departureTimestamp: number;
|
|
||||||
departureRealTimestamp: number;
|
|
||||||
departureDelay: number;
|
|
||||||
pointId: number;
|
|
||||||
|
|
||||||
comments?: string;
|
|
||||||
|
|
||||||
beginsHere: boolean;
|
|
||||||
terminatesHere: boolean;
|
|
||||||
confirmed: boolean;
|
|
||||||
stopped: boolean;
|
|
||||||
stopTime: number | null;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,12 +1,4 @@
|
|||||||
import Station from '../scripts/interfaces/Station';
|
import { ScenerySpawn, ScenerySpawnType } from '../typings/common';
|
||||||
import Train from '../scripts/interfaces/Train';
|
|
||||||
import { ScheduledTrain, StationTrain, StopStatus, TrainStop } from './typings';
|
|
||||||
|
|
||||||
export function getLocoURL(locoType: string): string {
|
|
||||||
return `https://rj.td2.info.pl/dist/img/thumbnails/${
|
|
||||||
locoType.includes('EN') ? locoType + 'rb' : locoType
|
|
||||||
}.png`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getStatusTimestamp(stationStatus: any): number {
|
export function getStatusTimestamp(stationStatus: any): number {
|
||||||
if (!stationStatus) return -2;
|
if (!stationStatus) return -2;
|
||||||
@@ -31,7 +23,7 @@ export function getStatusTimestamp(stationStatus: any): number {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseSpawns(spawnString: string | null) {
|
export function parseSpawns(spawnString: string | null): ScenerySpawn[] {
|
||||||
if (!spawnString) return [];
|
if (!spawnString) return [];
|
||||||
if (spawnString === 'NO_SPAWN') return [];
|
if (spawnString === 'NO_SPAWN') return [];
|
||||||
|
|
||||||
@@ -41,183 +33,18 @@ export function parseSpawns(spawnString: string | null) {
|
|||||||
const spawnLength = parseInt(spawnArray[2]);
|
const spawnLength = parseInt(spawnArray[2]);
|
||||||
const isElectrified = spawnArray[3] == 'True';
|
const isElectrified = spawnArray[3] == 'True';
|
||||||
|
|
||||||
return { spawnName, spawnLength, isElectrified };
|
let spawnType: ScenerySpawnType = /EZT|POS|OSOB|PAS/i.test(spawnName)
|
||||||
|
? 'passenger'
|
||||||
|
: /TOW/i.test(spawnName)
|
||||||
|
? 'freight'
|
||||||
|
: /LUZ/i.test(spawnName)
|
||||||
|
? 'loco'
|
||||||
|
: 'all';
|
||||||
|
|
||||||
|
return { spawnName, spawnLength, isElectrified, spawnType };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTimestamp(date: string | null): number {
|
export function getTimestamp(date: string | null): number {
|
||||||
return date ? new Date(date).getTime() : 0;
|
return date ? new Date(date).getTime() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTrainStopStatus(
|
|
||||||
stopInfo: TrainStop,
|
|
||||||
currentStationName: string,
|
|
||||||
sceneryName: string
|
|
||||||
) {
|
|
||||||
let stopStatus = StopStatus.ARRIVING,
|
|
||||||
stopLabel = '',
|
|
||||||
stopStatusID = -1;
|
|
||||||
|
|
||||||
if (stopInfo.terminatesHere && stopInfo.confirmed) {
|
|
||||||
stopStatus = StopStatus.TERMINATED;
|
|
||||||
stopLabel = 'Skończył bieg';
|
|
||||||
stopStatusID = 5;
|
|
||||||
} else if (!stopInfo.terminatesHere && stopInfo.confirmed && currentStationName == sceneryName) {
|
|
||||||
stopStatus = StopStatus.DEPARTED;
|
|
||||||
stopLabel = 'Odprawiony';
|
|
||||||
stopStatusID = 2;
|
|
||||||
} else if (!stopInfo.terminatesHere && stopInfo.confirmed && currentStationName != sceneryName) {
|
|
||||||
stopStatus = StopStatus.DEPARTED_AWAY;
|
|
||||||
stopLabel = 'Odjechał';
|
|
||||||
stopStatusID = 4;
|
|
||||||
} else if (currentStationName == sceneryName && !stopInfo.stopped) {
|
|
||||||
stopStatus = StopStatus.ONLINE;
|
|
||||||
stopLabel = 'Na stacji';
|
|
||||||
stopStatusID = 0;
|
|
||||||
} else if (currentStationName == sceneryName && stopInfo.stopped) {
|
|
||||||
stopStatus = StopStatus.STOPPED;
|
|
||||||
stopLabel = 'Postój';
|
|
||||||
stopStatusID = 1;
|
|
||||||
} else if (currentStationName != sceneryName) {
|
|
||||||
stopStatus = StopStatus.ARRIVING;
|
|
||||||
stopLabel = 'W drodze';
|
|
||||||
stopStatusID = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { stopStatus, stopLabel, stopStatusID };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCheckpointTrain(
|
|
||||||
train: Train,
|
|
||||||
trainStopIndex: number,
|
|
||||||
sceneryName: string
|
|
||||||
): ScheduledTrain {
|
|
||||||
const timetable = train.timetableData!;
|
|
||||||
const followingStops = timetable.followingStops;
|
|
||||||
const trainStop = followingStops[trainStopIndex];
|
|
||||||
|
|
||||||
const trainStopStatus = getTrainStopStatus(trainStop, train.currentStationName, sceneryName);
|
|
||||||
|
|
||||||
let prevStationName = '',
|
|
||||||
nextStationName = '';
|
|
||||||
|
|
||||||
let departureLine: string | null = null;
|
|
||||||
let arrivingLine: string | null = null;
|
|
||||||
|
|
||||||
let prevDepartureLine: string | null = null,
|
|
||||||
nextArrivalLine: string | null = null;
|
|
||||||
|
|
||||||
for (let i = trainStopIndex; i >= 0; i--) {
|
|
||||||
const stop = 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 = followingStops[i - 1]?.departureLine || null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = trainStopIndex; i < followingStops.length; i++) {
|
|
||||||
const stop = 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 = followingStops[i + 1]?.arrivalLine || null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
checkpointName: trainStop.stopNameRAW,
|
|
||||||
|
|
||||||
trainNo: train.trainNo,
|
|
||||||
trainId: train.trainId,
|
|
||||||
|
|
||||||
signal: train.signal,
|
|
||||||
connectedTrack: train.connectedTrack,
|
|
||||||
|
|
||||||
driverName: train.driverName,
|
|
||||||
driverId: train.driverId,
|
|
||||||
currentStationName: train.currentStationName,
|
|
||||||
currentStationHash: train.currentStationHash,
|
|
||||||
category: timetable.category,
|
|
||||||
beginsAt: timetable.followingStops[0].stopNameRAW,
|
|
||||||
terminatesAt: timetable.followingStops[timetable.followingStops.length - 1].stopNameRAW,
|
|
||||||
|
|
||||||
nextStationName,
|
|
||||||
prevStationName,
|
|
||||||
|
|
||||||
stopInfo: trainStop,
|
|
||||||
stopLabel: trainStopStatus.stopLabel,
|
|
||||||
stopStatus: trainStopStatus.stopStatus,
|
|
||||||
stopStatusID: trainStopStatus.stopStatusID,
|
|
||||||
|
|
||||||
region: train.region,
|
|
||||||
|
|
||||||
arrivingLine: arrivingLine,
|
|
||||||
departureLine: departureLine,
|
|
||||||
|
|
||||||
nextArrivalLine,
|
|
||||||
prevDepartureLine
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getScheduledTrains(
|
|
||||||
trainList: Train[],
|
|
||||||
stationGeneralInfo: Station['generalInfo'],
|
|
||||||
stationName: string,
|
|
||||||
region: string
|
|
||||||
// sceneryData: API.ActiveSceneries.Data,
|
|
||||||
): ScheduledTrain[] {
|
|
||||||
// stationGeneralInfo?.checkpoints.forEach((cp) => (cp.scheduledTrains.length = 0));
|
|
||||||
|
|
||||||
return trainList.reduce((acc: ScheduledTrain[], train) => {
|
|
||||||
if (!train.timetableData) return acc;
|
|
||||||
if (train.region != region) return acc;
|
|
||||||
|
|
||||||
const timetable = train.timetableData;
|
|
||||||
if (!timetable.sceneryNames.includes(stationName)) return acc;
|
|
||||||
|
|
||||||
const checkpoints = [stationName];
|
|
||||||
if (stationGeneralInfo?.checkpoints) checkpoints.push(...stationGeneralInfo.checkpoints);
|
|
||||||
|
|
||||||
const checkpointScheduledTrains: ScheduledTrain[] = [];
|
|
||||||
for (let i = 0; i < timetable.followingStops.length; i++) {
|
|
||||||
if (
|
|
||||||
new RegExp(`^(${checkpoints.join('|')})$`, 'i').test(
|
|
||||||
timetable.followingStops[i].stopNameRAW
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
checkpointScheduledTrains.push(getCheckpointTrain(train, i, stationName));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
acc.push(...checkpointScheduledTrains);
|
|
||||||
return acc;
|
|
||||||
}, []) as ScheduledTrain[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getStationTrains(
|
|
||||||
trainList: Train[],
|
|
||||||
scheduledTrainList: ScheduledTrain[],
|
|
||||||
region: string,
|
|
||||||
stationName: string
|
|
||||||
): StationTrain[] {
|
|
||||||
return trainList
|
|
||||||
.filter(
|
|
||||||
(train) =>
|
|
||||||
train?.region === region && train.online && train.currentStationName === stationName
|
|
||||||
)
|
|
||||||
.map((train) => ({
|
|
||||||
driverName: train.driverName,
|
|
||||||
driverId: train.driverId,
|
|
||||||
trainNo: train.trainNo,
|
|
||||||
trainId: train.trainId,
|
|
||||||
stopStatus:
|
|
||||||
scheduledTrainList.find((st) => st.trainNo === train.trainNo)?.stopStatus || 'no-timetable'
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,8 +4,9 @@
|
|||||||
.list_wrapper {
|
.list_wrapper {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
height: 90vh;
|
height: 90vh;
|
||||||
min-height: 550px;
|
min-height: 650px;
|
||||||
margin-top: 0.5em;
|
margin-top: 0.5em;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
padding-right: 0.2em;
|
padding-right: 0.2em;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
$animDuration: 150ms;
|
$animDuration: 95ms;
|
||||||
$animType: ease-in-out;
|
$animType: ease-in-out;
|
||||||
|
|
||||||
// List animation
|
// List animation
|
||||||
@@ -72,7 +72,6 @@ $animType: ease-in-out;
|
|||||||
|
|
||||||
&-enter-from,
|
&-enter-from,
|
||||||
&-leave-to {
|
&-leave-to {
|
||||||
transform: translateY(-25%);
|
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,3 +101,16 @@
|
|||||||
background-color: #be3728;
|
background-color: #be3728;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spawn-badge {
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
.length {
|
||||||
|
background-color: #404040;
|
||||||
|
color: #cfcfcf;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-electrified='true'] > .name {
|
||||||
|
background-color: #007599;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -55,6 +55,8 @@ body {
|
|||||||
-webkit-font-smoothing: antialiased !important;
|
-webkit-font-smoothing: antialiased !important;
|
||||||
|
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
|
overflow-x: hidden;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
&.no-scroll {
|
&.no-scroll {
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
@@ -119,8 +121,6 @@ input {
|
|||||||
height: 7px;
|
height: 7px;
|
||||||
background-color: lightgreen;
|
background-color: lightgreen;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
@@ -211,6 +211,7 @@ a.a-button {
|
|||||||
&.btn--action {
|
&.btn--action {
|
||||||
background-color: #424242;
|
background-color: #424242;
|
||||||
border-radius: 0.25em;
|
border-radius: 0.25em;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #555;
|
background-color: #555;
|
||||||
@@ -227,6 +228,10 @@ a.a-button {
|
|||||||
|
|
||||||
background-color: #3c3c3c;
|
background-color: #3c3c3c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #555;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.btn--image {
|
&.btn--image {
|
||||||
@@ -234,7 +239,7 @@ a.a-button {
|
|||||||
padding: 0.35em 0.75em;
|
padding: 0.35em 0.75em;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 1.5em;
|
width: 1.35em;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -282,6 +287,27 @@ a.a-button {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Basic tooltip
|
||||||
|
[data-tooltip] {
|
||||||
|
cursor: help;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-tooltip]:hover::after,
|
||||||
|
[data-tooltip]:focus::after {
|
||||||
|
position: absolute;
|
||||||
|
transform: translate(0, -50%);
|
||||||
|
|
||||||
|
content: attr(data-tooltip);
|
||||||
|
color: white;
|
||||||
|
background-color: #333;
|
||||||
|
box-shadow: 0 0 5px 2px #aaa;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
padding: 0.5em;
|
||||||
|
margin: 0 0.5em;
|
||||||
|
max-width: 300px;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
@include smallScreen {
|
@include smallScreen {
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 0.5em;
|
width: 0.5em;
|
||||||
@@ -295,4 +321,11 @@ a.a-button {
|
|||||||
background-color: #777;
|
background-color: #777;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-tooltip]:hover::after,
|
||||||
|
[data-tooltip]:focus::after {
|
||||||
|
transform: translate(-50%, 2em);
|
||||||
|
left: 50%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
.scenery-table-section {
|
|
||||||
position: relative;
|
|
||||||
height: 100%;
|
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.scenery-history-table {
|
table.scenery-history-table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
table-layout: fixed;
|
||||||
|
min-width: 900px;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
|
|
||||||
thead {
|
thead {
|
||||||
@@ -25,7 +21,7 @@ table.scenery-history-table {
|
|||||||
|
|
||||||
td {
|
td {
|
||||||
padding: 0.75em;
|
padding: 0.75em;
|
||||||
border-bottom: solid 5px #111;
|
border-bottom: solid 5px #181818;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||