format & lint

This commit is contained in:
2023-10-04 15:01:01 +02:00
parent 800fc35e63
commit 45c1d83512
125 changed files with 15006 additions and 13222 deletions
+18
View File
@@ -0,0 +1,18 @@
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = {
root: true,
extends: [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-typescript',
'@vue/eslint-config-prettier/skip-formatting'
],
rules: {
'vue/multi-word-component-names': 'off'
},
parserOptions: {
ecmaVersion: 'latest'
}
}
+7
View File
@@ -0,0 +1,7 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"tabWidth": 2,
"singleQuote": true,
"printWidth": 100,
"trailingComma": "none"
}
+22
View File
@@ -24,6 +24,7 @@
"@vite-pwa/assets-generator": "^0.0.10", "@vite-pwa/assets-generator": "^0.0.10",
"@vitejs/plugin-vue": "^4.3.4", "@vitejs/plugin-vue": "^4.3.4",
"axios": "^1.5.0", "axios": "^1.5.0",
"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.16.5",
@@ -5440,6 +5441,21 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/prettier": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz",
"integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==",
"dev": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/pretty-bytes": { "node_modules/pretty-bytes": {
"version": "6.1.1", "version": "6.1.1",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz",
@@ -10873,6 +10889,12 @@
} }
} }
}, },
"prettier": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz",
"integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==",
"dev": true
},
"pretty-bytes": { "pretty-bytes": {
"version": "6.1.1", "version": "6.1.1",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz",
+12 -2
View File
@@ -6,7 +6,10 @@
"dev": "vite", "dev": "vite",
"build": "vue-tsc --noEmit && vite build", "build": "vue-tsc --noEmit && vite build",
"deploy": "yarn build && firebase deploy --only hosting", "deploy": "yarn build && firebase deploy --only hosting",
"preview": "yarn build && vite preview" "preview": "yarn build && vite preview",
"type-check": "vue-tsc --noEmit -p tsconfig.app.json --composite false",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"format": "prettier --write src/"
}, },
"dependencies": { "dependencies": {
"core-js": "^3.32.2", "core-js": "^3.32.2",
@@ -25,10 +28,17 @@
"@vite-pwa/assets-generator": "^0.0.10", "@vite-pwa/assets-generator": "^0.0.10",
"@vitejs/plugin-vue": "^4.3.4", "@vitejs/plugin-vue": "^4.3.4",
"axios": "^1.5.0", "axios": "^1.5.0",
"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.16.5",
"vue-tsc": "^1.8.11" "vue-tsc": "^1.8.11",
"@vue/eslint-config-prettier": "^8.0.0",
"@vue/eslint-config-typescript": "^12.0.0",
"@vue/tsconfig": "^0.4.0",
"eslint": "^8.49.0",
"eslint-plugin-vue": "^9.17.0",
"@rushstack/eslint-patch": "^1.3.3"
}, },
"browserslist": [ "browserslist": [
"> 1%", "> 1%",
+105 -105
View File
@@ -1,105 +1,105 @@
@import './styles/responsive.scss'; @import './styles/responsive.scss';
@import './styles/variables.scss'; @import './styles/variables.scss';
@import './styles/global.scss'; @import './styles/global.scss';
// VUE ROUTE CHANGE ANIMATION // VUE ROUTE CHANGE ANIMATION
.view-anim { .view-anim {
&-enter-from, &-enter-from,
&-leave-to { &-leave-to {
opacity: 0.02; opacity: 0.02;
} }
&-enter-active, &-enter-active,
&-leave-active { &-leave-active {
transition: all $animDuration $animType; transition: all $animDuration $animType;
min-height: 100%; min-height: 100%;
} }
} }
.modal-anim { .modal-anim {
&-enter-active, &-enter-active,
&-leave-active { &-leave-active {
transition: all $animDuration $animType; transition: all $animDuration $animType;
} }
&-enter-from, &-enter-from,
&-leave-to { &-leave-to {
transform: translateY(-25%); transform: translateY(-25%);
opacity: 0; opacity: 0;
} }
} }
.route { .route {
margin: 0 0.2em; margin: 0 0.2em;
&-active, &-active,
&[data-active='true'] { &[data-active='true'] {
color: $accentCol; color: $accentCol;
font-weight: bold; font-weight: bold;
} }
} }
// APP // APP
#app { #app {
color: white; color: white;
font-size: 1rem; font-size: 1rem;
@include smallScreen() { @include smallScreen() {
font-size: calc(0.55rem + 1.1vw); font-size: calc(0.55rem + 1.1vw);
} }
@include screenLandscape() { @include screenLandscape() {
font-size: calc(0.45rem + 0.8vw); font-size: calc(0.45rem + 0.8vw);
} }
} }
// CONTAINER // CONTAINER
.app_container { .app_container {
display: flex; display: flex;
flex-flow: column; flex-flow: column;
min-height: 100vh; min-height: 100vh;
header { header {
flex: 0 0 auto; flex: 0 0 auto;
} }
main { main {
flex: 1 1 auto; flex: 1 1 auto;
padding: 0 0.5em; padding: 0 0.5em;
} }
footer { footer {
flex: 0 1 0.2em; flex: 0 1 0.2em;
} }
} }
.warning { .warning {
background-color: firebrick; background-color: firebrick;
text-align: center; text-align: center;
padding: 0.5em 0.4em; padding: 0.5em 0.4em;
max-width: 1100px; max-width: 1100px;
margin: 0 auto; margin: 0 auto;
border-radius: 0 0 1em 1em; border-radius: 0 0 1em 1em;
} }
// FOOTER // FOOTER
footer.app_footer { footer.app_footer {
max-width: 100%; max-width: 100%;
padding: 0.5em; padding: 0.5em;
img { img {
width: 1.1em; width: 1.1em;
vertical-align: text-bottom; vertical-align: text-bottom;
} }
z-index: 10; z-index: 10;
background: #111; background: #111;
color: white; color: white;
text-align: center; text-align: center;
vertical-align: middle; vertical-align: middle;
} }
+176 -178
View File
@@ -1,178 +1,176 @@
<template> <template>
<div class="app_container"> <div class="app_container">
<transition name="modal-anim"> <transition name="modal-anim">
<keep-alive> <keep-alive>
<TrainModal v-if="store.chosenModalTrainId" /> <TrainModal v-if="store.chosenModalTrainId" />
</keep-alive> </keep-alive>
</transition> </transition>
<UpdatePrompt /> <AppHeader :current-lang="currentLang" @change-lang="changeLang" />
<AppHeader :current-lang="currentLang" @change-lang="changeLang" /> <main class="app_main">
<router-view v-slot="{ Component }">
<main class="app_main"> <keep-alive exclude="JournalView">
<router-view v-slot="{ Component }"> <component :is="Component" :key="$route.name" />
<keep-alive exclude="JournalView"> </keep-alive>
<component :is="Component" :key="$route.name" /> </router-view>
</keep-alive> </main>
</router-view>
</main> <footer class="app_footer">
&copy;
<footer class="app_footer"> <a href="https://td2.info.pl/profile/?u=20777" target="_blank">Spythere</a>
&copy; {{ new Date().getUTCFullYear() }} |
<a href="https://td2.info.pl/profile/?u=20777" target="_blank">Spythere</a> <a :href="releaseURL" target="_blank">v{{ VERSION }}{{ isOnProductionHost ? '' : 'dev' }}</a>
{{ new Date().getUTCFullYear() }} | <br />
<a :href="releaseURL" target="_blank">v{{ VERSION }}{{ isOnProductionHost ? '' : 'dev' }}</a> <a href="https://discord.gg/x2mpNN3svk"
<br /> ><img :src="getIcon('discord', 'png')" alt="" />&nbsp;<b>{{ $t('footer.discord') }}</b></a
<a href="https://discord.gg/x2mpNN3svk"><img :src="getIcon('discord', 'png')" alt="">&nbsp;<b>{{ $t('footer.discord') }}</b></a> >
<div style="display: none">&int; ukryta taktyczna całka do programowania w HTMLu</div> <div style="display: none">&int; ukryta taktyczna całka do programowania w HTMLu</div>
</footer> </footer>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, KeepAlive, provide, ref, watch } from 'vue'; import { computed, defineComponent, provide, ref, watch } from 'vue';
import Clock from './components/App/Clock.vue'; import Clock from './components/App/Clock.vue';
import packageInfo from '.././package.json'; import packageInfo from '.././package.json';
import StatusIndicator from './components/App/StatusIndicator.vue'; import StatusIndicator from './components/App/StatusIndicator.vue';
import SelectBox from './components/Global/SelectBox.vue'; import SelectBox from './components/Global/SelectBox.vue';
import { useStore } from './store/store'; import { useStore } from './store/store';
import TrainModal from './components/Global/TrainModal.vue'; import TrainModal from './components/Global/TrainModal.vue';
import StorageManager from './scripts/managers/storageManager'; import StorageManager from './scripts/managers/storageManager';
import imageMixin from './mixins/imageMixin'; import imageMixin from './mixins/imageMixin';
import AppHeader from './components/App/AppHeader.vue'; import AppHeader from './components/App/AppHeader.vue';
import axios from 'axios'; import axios from 'axios';
import UpdatePrompt from './components/App/UpdatePrompt.vue'; import useCustomSW from './mixins/useCustomSW';
import { VERSION } from 'vue-i18n';
import { RouterView } from 'vue-router'; export default defineComponent({
import useCustomSW from './mixins/useCustomSW'; components: {
Clock,
export default defineComponent({ StatusIndicator,
components: { SelectBox,
Clock, TrainModal,
StatusIndicator, AppHeader
SelectBox, },
TrainModal,
AppHeader, mixins: [imageMixin],
UpdatePrompt,
}, setup() {
const store = useStore();
mixins: [imageMixin], store.connectToAPI();
setup() { useCustomSW();
const store = useStore();
store.connectToAPI(); const isFilterCardVisible = ref(false);
const { offlineReady } = useCustomSW(); provide('isFilterCardVisible', isFilterCardVisible);
const isFilterCardVisible = ref(false); return {
store,
provide('isFilterCardVisible', isFilterCardVisible); isFilterCardVisible,
onlineDispatchers: computed(() =>
return { store.stationList.filter(
store, (station) => station.onlineInfo && station.onlineInfo.region == store.region.id
isFilterCardVisible, )
onlineDispatchers: computed(() => ),
store.stationList.filter((station) => station.onlineInfo && station.onlineInfo.region == store.region.id)
), dispatcherDataStatus: store.dataStatuses.dispatchers
};
dispatcherDataStatus: store.dataStatuses.dispatchers, },
};
}, data: () => ({
VERSION: packageInfo.version,
data: () => ({
VERSION: packageInfo.version, currentLang: 'pl',
releaseURL: '',
currentLang: 'pl', isOnProductionHost: location.hostname == 'stacjownik-td2.web.app'
releaseURL: '', }),
isOnProductionHost: location.hostname == 'stacjownik-td2.web.app',
}), created() {
this.loadLang();
created() {
this.loadLang(); this.store.isOffline = !window.navigator.onLine;
this.store.isOffline = !window.navigator.onLine; window.addEventListener('offline', () => {
this.store.isOffline = true;
window.addEventListener('offline', () => {
this.store.isOffline = true; this.store.apiData = {
stations: [],
this.store.apiData = { dispatchers: [],
stations: [], trains: [],
dispatchers: [], connectedSocketCount: 0
trains: [], };
connectedSocketCount: 0,
}; this.store.setOnlineData();
});
this.store.setOnlineData();
}); window.addEventListener('online', () => {
this.store.isOffline = false;
window.addEventListener('online', () => { });
this.store.isOffline = false; },
});
}, async mounted() {
this.setReleaseURL();
async mounted() {
this.setReleaseURL(); watch(
() => this.store.blockScroll,
watch( (value) => {
() => this.store.blockScroll, if (value) {
(value) => { document.body.classList.add('no-scroll');
if (value) { return;
document.body.classList.add('no-scroll'); }
return;
} document.body.classList.remove('no-scroll');
}
document.body.classList.remove('no-scroll'); );
} },
);
}, methods: {
changeLang(lang: string) {
methods: { this.$i18n.locale = lang;
changeLang(lang: string) { this.currentLang = lang;
this.$i18n.locale = lang;
this.currentLang = lang; StorageManager.setStringValue('lang', lang);
},
StorageManager.setStringValue('lang', lang);
}, async setReleaseURL() {
try {
async setReleaseURL() { const releaseData = await (
try { await axios.get('https://api.github.com/repos/Spythere/stacjownik/releases/latest')
const releaseData = await ( ).data;
await axios.get('https://api.github.com/repos/Spythere/stacjownik/releases/latest')
).data; if (!releaseData) return;
if (!releaseData) return; this.releaseURL = releaseData.html_url;
} catch (error) {
this.releaseURL = releaseData.html_url; console.error(`Wystąpił błąd podczas pobierania danych z API GitHuba: ${error}`);
} catch (error) { return;
console.error(`Wystąpił błąd podczas pobierania danych z API GitHuba: ${error}`); }
return; },
}
}, loadLang() {
const storageLang = StorageManager.getStringValue('lang');
loadLang() {
const storageLang = StorageManager.getStringValue('lang'); if (storageLang) {
this.changeLang(storageLang);
if (storageLang) { return;
this.changeLang(storageLang); }
return;
} if (!window.navigator.language) return;
if (!window.navigator.language) return; const naviLanguage = window.navigator.language.toString();
const naviLanguage = window.navigator.language.toString(); if (naviLanguage.includes('en')) {
this.changeLang('en');
if (naviLanguage.includes('en')) { return;
this.changeLang('en'); }
return; }
} }
}, });
}, </script>
});
</script> <style lang="scss" src="./App.scss"></style>
<style lang="scss" src="./App.scss"></style>
+249 -237
View File
@@ -1,237 +1,249 @@
<template> <template>
<header class="app_header"> <header class="app_header">
<div class="header_container"> <div class="header_container">
<div class="header_icons"> <div class="header_icons">
<span class="icons-top"> <span class="icons-top">
<img :src="getIcon('pl')" alt="icon-pl" @click="changeLang('en')" v-if="currentLang == 'pl'" /> <img
<img :src="getIcon('en', 'jpg')" alt="icon-en" @click="changeLang('pl')" v-else /> :src="getIcon('pl')"
</span> alt="icon-pl"
</div> @click="changeLang('en')"
v-if="currentLang == 'pl'"
<div class="header_body"> />
<StatusIndicator /> <img :src="getIcon('en', 'jpg')" alt="icon-en" @click="changeLang('pl')" v-else />
</span>
<span class="header_brand"> </div>
<router-link to="/">
<img :src="getImage('stacjownik-header-logo.svg')" alt="Stacjownik" /> <div class="header_body">
</router-link> <StatusIndicator />
</span>
<span class="header_brand">
<span class="header_info"> <router-link to="/">
<Clock /> <img :src="getImage('stacjownik-header-logo.svg')" alt="Stacjownik" />
</router-link>
<div class="info_counter"> </span>
<img :src="getIcon('dispatcher')" alt="icon dispatcher" />
<span class="text--primary">{{ onlineDispatchersCount }}</span> <span class="header_info">
<Clock />
<!-- <span class="g-tooltip">
<b class="text--primary">{{ factorU }}U</b> <div class="info_counter">
<div class="content">Test</div> <img :src="getIcon('dispatcher')" alt="icon dispatcher" />
</span> --> <span class="text--primary">{{ onlineDispatchersCount }}</span>
<span class="text--grayed"> / </span> <!-- <span class="g-tooltip">
<span class="text--primary">{{ onlineTrainsCount }}</span> <b class="text--primary">{{ factorU }}U</b>
<img :src="getIcon('train')" alt="icon train" /> <div class="content">Test</div>
</div> </span> -->
<span class="info_region"> <span class="text--grayed"> / </span>
<SelectBox :itemList="computedRegions" :defaultItemIndex="0" @selected="changeRegion" /> <span class="text--primary">{{ onlineTrainsCount }}</span>
</span> <img :src="getIcon('train')" alt="icon train" />
</span> </div>
<span class="header_links"> <span class="info_region">
<router-link class="route" active-class="route-active" to="/" exact> <SelectBox :itemList="computedRegions" :defaultItemIndex="0" @selected="changeRegion" />
{{ $t('app.sceneries') }} </span>
</router-link> </span>
/
<router-link class="route" active-class="route-active" to="/trains">{{ $t('app.trains') }}</router-link> <span class="header_links">
/ <router-link class="route" active-class="route-active" to="/" exact>
<router-link {{ $t('app.sceneries') }}
class="route" </router-link>
active-class="route-active" /
:data-active="$route.path.startsWith('/journal')" <router-link class="route" active-class="route-active" to="/trains">{{
to="/journal" $t('app.trains')
> }}</router-link>
{{ $t('app.journal') }} /
</router-link> <router-link
</span> class="route"
</div> active-class="route-active"
</div> :data-active="$route.path.startsWith('/journal')"
</header> to="/journal"
</template> >
<script lang="ts"> {{ $t('app.journal') }}
import { defineComponent } from 'vue'; </router-link>
import { useStore } from '../../store/store'; </span>
import options from '../../data/options.json'; </div>
import imageMixin from '../../mixins/imageMixin'; </div>
import SelectBox from '../Global/SelectBox.vue'; </header>
import StatusIndicator from './StatusIndicator.vue'; </template>
import Clock from './Clock.vue'; <script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({ import { useStore } from '../../store/store';
emits: ['changeLang'], import options from '../../data/options.json';
mixins: [imageMixin], import imageMixin from '../../mixins/imageMixin';
props: { import SelectBox from '../Global/SelectBox.vue';
currentLang: { import StatusIndicator from './StatusIndicator.vue';
type: String, import Clock from './Clock.vue';
required: true,
}, export default defineComponent({
}, emits: ['changeLang'],
setup() { mixins: [imageMixin],
return { props: {
store: useStore(), currentLang: {
}; type: String,
}, required: true
methods: { }
changeRegion(region: { id: string; value: string }) { },
this.store.changeRegion(region); setup() {
}, return {
changeLang(lang: string) { store: useStore()
this.$emit('changeLang', lang); };
}, },
}, methods: {
computed: { changeRegion(region: { id: string; value: string }) {
onlineTrainsCount() { this.store.changeRegion(region);
return this.store.trainList.filter((train) => train.online).length; },
}, changeLang(lang: string) {
this.$emit('changeLang', lang);
onlineDispatchersCount() { }
return this.store.stationList.filter( },
(station) => station.onlineInfo && station.onlineInfo.region == this.store.region.id computed: {
).length; onlineTrainsCount() {
}, return this.store.trainList.filter((train) => train.online).length;
},
factorU() {
return this.onlineDispatchersCount == 0 ? '-' : (this.onlineTrainsCount / this.onlineDispatchersCount).toFixed(2); onlineDispatchersCount() {
}, return this.store.stationList.filter(
(station) => station.onlineInfo && station.onlineInfo.region == this.store.region.id
computedRegions() { ).length;
return options.regions.map((region) => { },
const regionStationCount =
this.store.apiData.stations?.filter((station) => station.region == region.id && station.isOnline).length || 0; factorU() {
const regionTrainCount = return this.onlineDispatchersCount == 0
this.store.apiData.trains?.filter((train) => train.region == region.id && train.online).length || 0; ? '-'
return { : (this.onlineTrainsCount / this.onlineDispatchersCount).toFixed(2);
id: region.id, },
value: `${region.value} <div class='text--grayed'>${regionStationCount} / ${regionTrainCount}</div>`,
selectedValue: region.value, computedRegions() {
}; return options.regions.map((region) => {
}); const regionStationCount =
}, this.store.apiData.stations?.filter(
}, (station) => station.region == region.id && station.isOnline
components: { SelectBox, StatusIndicator, Clock }, ).length || 0;
}); const regionTrainCount =
</script> this.store.apiData.trains?.filter((train) => train.region == region.id && train.online)
<style lang="scss" scoped> .length || 0;
@import '../../styles/variables.scss'; return {
@import '../../styles/responsive.scss'; id: region.id,
value: `${region.value} <div class='text--grayed'>${regionStationCount} / ${regionTrainCount}</div>`,
// HEADER selectedValue: region.value
.app_header { };
display: flex; });
justify-content: center; }
},
position: relative; components: { SelectBox, StatusIndicator, Clock }
background-color: $primaryCol; });
} </script>
<style lang="scss" scoped>
.header { @import '../../styles/variables.scss';
&_body { @import '../../styles/responsive.scss';
position: relative;
max-width: 20em; // HEADER
} .app_header {
display: flex;
&_container { justify-content: center;
display: flex;
justify-content: center; position: relative;
background-color: $primaryCol;
border-radius: 0 0 1em 1em; }
@include smallScreen { .header {
position: relative; &_body {
margin-top: 0.5em; position: relative;
} max-width: 20em;
} }
&_brand { &_container {
display: flex; display: flex;
justify-content: center;
img {
width: 100%; border-radius: 0 0 1em 1em;
margin: 0 auto; @include smallScreen {
} position: relative;
} margin-top: 0.5em;
}
&_info { }
display: grid;
grid-template-columns: 1fr 1fr 1fr; &_brand {
font-size: 1.15em; display: flex;
}
img {
&_links { width: 100%;
display: flex;
justify-content: center; margin: 0 auto;
}
border-radius: 0.7em; }
font-size: 1.25em; &_info {
padding: 0.5em; display: grid;
} grid-template-columns: 1fr 1fr 1fr;
font-size: 1.15em;
&_icons { }
position: absolute;
right: 0; &_links {
top: 0; display: flex;
justify-content: center;
padding: 0.5em;
border-radius: 0.7em;
@include smallScreen {
transform: translateX(85%); font-size: 1.25em;
} padding: 0.5em;
} }
}
&_icons {
// ICONS position: absolute;
.icons-top { right: 0;
img { top: 0;
width: 2.5em;
cursor: pointer; padding: 0.5em;
}
} @include smallScreen {
transform: translateX(85%);
// COUNTER }
.info_counter { }
display: flex; }
justify-content: center;
align-items: center; // ICONS
.icons-top {
span { img {
margin: 0 0.15em; width: 2.5em;
} cursor: pointer;
}
img { }
width: 1.35em;
} // COUNTER
} .info_counter {
display: flex;
// REGION SELECTION justify-content: center;
.info_region { align-items: center;
color: white;
font-weight: bold; span {
margin: 0 0.15em;
display: flex; }
justify-content: flex-end;
img {
.select-box_content button { width: 1.35em;
background-color: transparent; }
font-weight: bold; }
padding: 0.1em 0.5em;
color: paleturquoise; // REGION SELECTION
} .info_region {
color: white;
.options { font-weight: bold;
font-size: 0.9em;
} display: flex;
} justify-content: flex-end;
</style>
.select-box_content button {
background-color: transparent;
font-weight: bold;
padding: 0.1em 0.5em;
color: paleturquoise;
}
.options {
font-size: 0.9em;
}
}
</style>
+37 -36
View File
@@ -1,36 +1,37 @@
<template> <template>
<div class="clock">{{ computedDate }}</div> <div class="clock">{{ computedDate }}</div>
</template> </template>
<script lang="ts">
<script lang="ts"> import { computed, defineComponent, ref } from 'vue';
import { computed, defineComponent, ref } from "vue"; export default defineComponent({
export default defineComponent({ name: 'VueClock',
name: "clock", data: () => ({
data: () => ({ timestamp: Date.now()
timestamp: Date.now(), }),
}), setup() {
setup() { let timestamp = ref(Date.now());
let timestamp = ref(Date.now());
const computedDate = computed(() =>
const computedDate = computed(() => new Date(timestamp.value).toLocaleString("pl-PL", { new Date(timestamp.value).toLocaleString('pl-PL', {
hour: "2-digit", hour: '2-digit',
minute: "2-digit", minute: '2-digit',
second: "2-digit", second: '2-digit'
})); })
);
setInterval(() => (timestamp.value = Date.now()), 1000);
setInterval(() => (timestamp.value = Date.now()), 1000);
return { computedDate }
} return { computedDate };
}); }
</script> });
</script>
<style lang="scss" scoped>
@import "../../styles/responsive.scss"; <style lang="scss" scoped>
@import '../../styles/responsive.scss';
.clock {
display: flex; .clock {
align-items: center; display: flex;
} align-items: center;
</style> }
</style>
+45 -14
View File
@@ -43,7 +43,13 @@
<g v-if="greenBlinkLight" filter="url(#filter0_d_843_28)"> <g v-if="greenBlinkLight" filter="url(#filter0_d_843_28)">
<circle cx="15" cy="17" r="7" fill="#00FF0A" /> <circle cx="15" cy="17" r="7" fill="#00FF0A" />
<animate attributeType="XML" attributeName="opacity" values="1;0;1" dur="1s" repeatCount="indefinite" /> <animate
attributeType="XML"
attributeName="opacity"
values="1;0;1"
dur="1s"
repeatCount="indefinite"
/>
</g> </g>
<g v-if="redTopLight" filter="url(#filter1_d_843_28)"> <g v-if="redTopLight" filter="url(#filter1_d_843_28)">
@@ -56,7 +62,13 @@
<g v-if="redBottomLight" filter="url(#filter3_d_843_28)"> <g v-if="redBottomLight" filter="url(#filter3_d_843_28)">
<circle cx="15" cy="74" r="7" fill="#F40000" /> <circle cx="15" cy="74" r="7" fill="#F40000" />
<animate attributeType="XML" attributeName="opacity" values="1;0;1" dur="1s" repeatCount="indefinite" /> <animate
attributeType="XML"
attributeName="opacity"
values="1;0;1"
dur="1s"
repeatCount="indefinite"
/>
</g> </g>
</g> </g>
@@ -82,7 +94,12 @@
<feComposite in2="hardAlpha" operator="out" /> <feComposite in2="hardAlpha" operator="out" />
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 1 0 0 0 0 0.04 0 0 0 1 0" /> <feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 1 0 0 0 0 0.04 0 0 0 1 0" />
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_843_28" /> <feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_843_28" />
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_843_28" result="shape" /> <feBlend
mode="normal"
in="SourceGraphic"
in2="effect1_dropShadow_843_28"
result="shape"
/>
</filter> </filter>
<filter <filter
id="filter1_d_843_28" id="filter1_d_843_28"
@@ -104,7 +121,12 @@
<feGaussianBlur stdDeviation="2.5" /> <feGaussianBlur stdDeviation="2.5" />
<feColorMatrix type="matrix" values="0 0 0 0 0.770833 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0" /> <feColorMatrix type="matrix" values="0 0 0 0 0.770833 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0" />
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_843_28" /> <feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_843_28" />
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_843_28" result="shape" /> <feBlend
mode="normal"
in="SourceGraphic"
in2="effect1_dropShadow_843_28"
result="shape"
/>
</filter> </filter>
<filter <filter
id="filter2_d_843_28" id="filter2_d_843_28"
@@ -126,7 +148,12 @@
<feGaussianBlur stdDeviation="2.5" /> <feGaussianBlur stdDeviation="2.5" />
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 0.72 0 0 0 0 0 0 0 0 1 0" /> <feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 0.72 0 0 0 0 0 0 0 0 1 0" />
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_843_28" /> <feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_843_28" />
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_843_28" result="shape" /> <feBlend
mode="normal"
in="SourceGraphic"
in2="effect1_dropShadow_843_28"
result="shape"
/>
</filter> </filter>
<filter <filter
id="filter3_d_843_28" id="filter3_d_843_28"
@@ -148,7 +175,12 @@
<feGaussianBlur stdDeviation="2.5" /> <feGaussianBlur stdDeviation="2.5" />
<feColorMatrix type="matrix" values="0 0 0 0 0.770833 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0" /> <feColorMatrix type="matrix" values="0 0 0 0 0.770833 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0" />
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_843_28" /> <feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_843_28" />
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_843_28" result="shape" /> <feBlend
mode="normal"
in="SourceGraphic"
in2="effect1_dropShadow_843_28"
result="shape"
/>
</filter> </filter>
</defs> </defs>
</svg> </svg>
@@ -173,14 +205,14 @@ export default defineComponent({
indicator: { indicator: {
offline: false, offline: false,
status: DataStatus.Loading, status: DataStatus.Loading,
message: 'data-status.S3', message: 'data-status.S3'
}, },
greenLight: false, greenLight: false,
greenBlinkLight: false, greenBlinkLight: false,
redTopLight: false, redTopLight: false,
orangeLight: false, orangeLight: false,
redBottomLight: false, redBottomLight: false
}; };
}, },
@@ -193,7 +225,7 @@ export default defineComponent({
return { return {
dataStatus: store.dataStatuses, dataStatus: store.dataStatuses,
store, store
}; };
}, },
@@ -248,8 +280,8 @@ export default defineComponent({
this.indicator.status = DataStatus.Loaded; this.indicator.status = DataStatus.Loaded;
this.indicator.message = 'data-status.S2'; this.indicator.message = 'data-status.S2';
} }
}, }
}, }
}, },
methods: { methods: {
@@ -280,8 +312,8 @@ export default defineComponent({
if (status == DataStatus.Loading) { if (status == DataStatus.Loading) {
this.greenBlinkLight = true; this.greenBlinkLight = true;
} }
}, }
}, }
}); });
</script> </script>
@@ -375,4 +407,3 @@ export default defineComponent({
} }
} }
</style> </style>
-168
View File
@@ -1,168 +0,0 @@
<template>
<transition name="modal-anim">
<section class="update-modal card" v-if="releaseData && modalOpen">
<h2 class="modal_header text--primary">
<img :src="getImage('stacjownik-header-logo.svg')" alt="stacjownik logo" />
{{ releaseData.tag_name }}
</h2>
<div class="horizontal"></div>
<div class="modal_content">
<h3>{{ $t('update.title') }}</h3>
<a :href="releaseData.html_url" target="_blank">{{ $t('update.release-link') }}</a>
<br />
<br />
<p>{{ $t('update.paragraph1') }}</p>
<!-- <div class="modal_changelog" v-html="markdownReleaseBody"></div> -->
</div>
<div class="modal_actions">
<button class="btn btn--option" @click="modalOpen = false">{{ $t('update.confirm-button') }}</button>
</div>
</section>
</transition>
</template>
<script lang="ts">
import axios from 'axios';
import { defineComponent } from 'vue';
import packageInfo from '../../../package.json';
import imageMixin from '../../mixins/imageMixin';
import { ReleaseAPIData } from '../../scripts/interfaces/github_api/ReleaseAPIData';
import StorageManager from '../../scripts/managers/storageManager';
import { useStore } from '../../store/store';
const GH_LASTEST_RELEASE_URL = 'https://api.github.com/repos/Spythere/stacjownik/releases/latest';
export default defineComponent({
mixins: [imageMixin],
mounted() {
this.fetchReleases();
},
data() {
return {
modalOpen: false,
releaseData: null as ReleaseAPIData | null,
};
},
setup() {
return {
store: useStore()
}
},
methods: {
async fetchReleases() {
const storedVersion = StorageManager.getStringValue('appVersion');
const appVersion = packageInfo.version;
// Zmiana
if (appVersion != storedVersion) {
StorageManager.setStringValue('appVersion', appVersion);
// Znajdź changelog na GitHubie, jeśli jest pokaż modal
try {
const releaseData: ReleaseAPIData = await (await axios.get(GH_LASTEST_RELEASE_URL)).data;
if (!releaseData) return;
const lastReleaseVersion = releaseData.tag_name.slice(1);
if (lastReleaseVersion == appVersion) {
this.releaseData = releaseData;
this.modalOpen = true;
StorageManager.setStringValue('releaseURL', releaseData.html_url);
}
} catch (error) {
console.error(`Wystąpił błąd podczas pobierania danych z API GitHuba: ${error}`);
}
}
},
},
});
</script>
<style lang="scss" scoped>
@import '../../styles/card.scss';
@import '../../styles/responsive.scss';
.modal-anim {
&-enter-active,
&-leave-active {
transition: all $animDuration $animType;
}
&-enter-from,
&-leave-to {
opacity: 0;
transform: translate(-50%, -50%) scale(0.45);
}
}
.update-modal {
text-align: center;
background-color: var(--clr-secondary);
padding: 1em;
}
.horizontal {
margin: 1em 0;
height: 2px;
width: 100%;
background-color: white;
}
.modal_header {
font-size: 1.6em;
img {
width: 50%;
vertical-align: text-top;
}
}
.modal_content {
font-size: 1.1em;
a {
text-decoration: underline;
}
}
.modal_actions {
margin-top: 2em;
button {
color: white;
padding: 0.5em;
font-size: 1.2em;
background-color: black;
}
}
.modal_changelog {
font-size: 0.8em;
margin-top: 2em;
}
@include smallScreen {
.update-modal {
height: auto;
max-width: 95%;
}
}
</style>
-69
View File
@@ -1,69 +0,0 @@
<template>
<div class="update-prompt">
<transition name="prompt-anim">
<div class="prompt_content" v-if="!hidePrompt && needRefresh">
<div>{{ $t('update.title') }}</div>
<div class="prompt_actions">
<button class="btn btn--filled" @click="updateServiceWorker(true)">{{ $t('update.confirm-button') }}</button>
<button class="btn btn--filled" @click="hidePrompt = true">{{ $t('update.later-button') }}</button>
</div>
</div>
</transition>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import useCustomSW from '../../mixins/useCustomSW';
const hidePrompt = ref(false);
const { needRefresh, updateServiceWorker } = useCustomSW();
</script>
<style lang="scss" scoped>
@import '../../styles/variables.scss';
.update-prompt {
position: fixed;
bottom: 0;
right: 0;
z-index: 200;
}
.prompt_content {
margin: 1em;
padding: 1em;
font-weight: bold;
background-color: black;
box-shadow: 0 0 10px 1px $accentCol;
border-radius: 1em;
}
.prompt_actions {
display: flex;
margin-top: 1em;
gap: 0.5em;
button {
width: 100%;
}
}
// Animation
.prompt-anim {
&-enter-active,
&-leave-active {
transition: all 120ms ease-in;
transform: translateY(0);
}
&-enter-from,
&-leave-to {
transform: translateY(100%);
}
}
</style>
+24 -24
View File
@@ -1,24 +1,24 @@
<template> <template>
<button class="action-btn btn--filled"> <button class="action-btn btn--filled">
<div class="button_content"> <div class="button_content">
<slot></slot> <slot></slot>
</div> </div>
</button> </button>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from "vue"; import { defineComponent } from 'vue';
export default defineComponent({}); export default defineComponent({});
</script> </script>
<style lang="scss"> <style lang="scss">
@import "../../styles/variables"; @import '../../styles/variables';
@import "../../styles/responsive"; @import '../../styles/responsive';
.button_content { .button_content {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
</style> </style>
+6 -6
View File
@@ -15,18 +15,18 @@ export default defineComponent({
props: { props: {
scrollNoMoreData: { scrollNoMoreData: {
type: Boolean, type: Boolean,
required: true, required: true
}, },
scrollDataLoaded: { scrollDataLoaded: {
type: Boolean, type: Boolean,
required: true, required: true
}, },
list: { list: {
type: Array as PropType<any[]>, type: Array as PropType<any[]>,
required: true, required: true
}, }
}, },
emits: ['addHistoryData'], emits: ['addHistoryData'],
@@ -34,8 +34,8 @@ export default defineComponent({
methods: { methods: {
addHistoryData() { addHistoryData() {
this.$emit('addHistoryData'); this.$emit('addHistoryData');
}, }
}, }
}); });
</script> </script>
+1 -1
View File
@@ -12,7 +12,7 @@ import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
setup() { setup() {
return {}; return {};
}, }
}); });
</script> </script>
+65 -62
View File
@@ -1,62 +1,65 @@
<template> <template>
<div class="progress-bar"> <div class="progress-bar">
<span class="bar-bg"></span> <span class="bar-bg"></span>
<span class="bar-fg" :style="{ width: `${~~progressPercent}%`, backgroundColor: bgColor }"></span> <span
</div> class="bar-fg"
</template> :style="{ width: `${~~progressPercent}%`, backgroundColor: bgColor }"
></span>
<script lang="ts"> </div>
import { defineComponent } from 'vue'; </template>
export default defineComponent({ <script lang="ts">
props: { import { defineComponent } from 'vue';
progressPercent: {
type: Number, export default defineComponent({
required: true, props: {
}, progressPercent: {
progressType: { type: Number,
type: String, required: true
required: false, },
}, progressType: {
}, type: String,
required: false
computed: { }
bgColor() { },
switch (this.progressType) {
case 'abandoned': computed: {
return 'salmon'; bgColor() {
switch (this.progressType) {
default: case 'abandoned':
return 'springgreen'; return 'salmon';
}
}, default:
}, return 'springgreen';
}); }
</script> }
}
<style lang="scss" scoped> });
.progress-bar { </script>
position: relative;
<style lang="scss" scoped>
width: 6em; .progress-bar {
height: 1em; position: relative;
margin: 0.5em 0;
width: 6em;
.bar-fg, height: 1em;
.bar-bg { margin: 0.5em 0;
position: absolute;
height: 1em; .bar-fg,
width: 100%; .bar-bg {
position: absolute;
left: 0; height: 1em;
} width: 100%;
.bar-fg { left: 0;
background-color: springgreen; }
}
.bar-fg {
.bar-bg { background-color: springgreen;
background-color: #5b5b5b; }
}
} .bar-bg {
</style> background-color: #5b5b5b;
}
}
</style>
+18 -23
View File
@@ -7,39 +7,34 @@
@keypress="updateValue" @keypress="updateValue"
/> />
<img <img class="search-exit" :src="getIcon('exit')" alt="exit-icon" @click="clearSearchValue" />
class="search-exit"
:src="getIcon('exit')"
alt="exit-icon"
@click="clearValue"
/>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, ref, watch } from "vue"; import { defineComponent, ref, watch } from 'vue';
import imageMixin from "../../mixins/imageMixin"; import imageMixin from '../../mixins/imageMixin';
export default defineComponent({ export default defineComponent({
mixins: [imageMixin], mixins: [imageMixin],
emits: ["update:searchedValue", "clearValue"], emits: ['update:searchedValue', 'clearValue'],
props: { props: {
searchedValue: { searchedValue: {
type: String, type: String,
required: true, required: true
}, },
updateOnInput: { updateOnInput: {
type: Boolean, type: Boolean,
default: true, default: true
}, },
titleToTranslate: { titleToTranslate: {
type: String, type: String,
required: true, required: true
}, },
clearValue: { clearValue: {
type: Function, type: Function
}, }
}, },
setup(props, { emit }) { setup(props, { emit }) {
@@ -49,32 +44,32 @@ export default defineComponent({
watch( watch(
() => compSearchedValue.value, () => compSearchedValue.value,
(value) => { (value) => {
emit("update:searchedValue", value); emit('update:searchedValue', value);
} }
); );
} }
const clearValue = () => { const clearSearchValue = () => {
compSearchedValue.value = ""; compSearchedValue.value = '';
emit("clearValue"); emit('clearValue');
}; };
const updateValue = (e: any) => { const updateValue = (e: any) => {
if (!props.updateOnInput && e.keyCode == 13) if (!props.updateOnInput && e.keyCode == 13)
emit("update:searchedValue", compSearchedValue.value); emit('update:searchedValue', compSearchedValue.value);
}; };
return { return {
compSearchedValue, compSearchedValue,
updateValue, updateValue,
clearValue, clearSearchValue
}; };
}, }
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "../../styles/responsive"; @import '../../styles/responsive';
.search { .search {
&-box { &-box {
@@ -109,4 +104,4 @@ export default defineComponent({
width: 1em; width: 1em;
} }
} }
</style> </style>
+224 -217
View File
@@ -1,217 +1,224 @@
<template> <template>
<div class="select-box"> <div class="select-box">
<div class="select-box_content"> <div class="select-box_content">
<button class="selected" @click="toggleBox"> <button class="selected" @click="toggleBox">
<span>{{ computedSelectedItem.selectedValue || computedSelectedItem.value }}</span> <span>{{ computedSelectedItem.selectedValue || computedSelectedItem.value }}</span>
<div class="arrow"> <div class="arrow">
<img :src="listOpen ? getIcon('arrow-asc') : getIcon('arrow-desc')" alt="arrow-icon" /> <img :src="listOpen ? getIcon('arrow-asc') : getIcon('arrow-desc')" alt="arrow-icon" />
</div> </div>
</button> </button>
<ul class="options" :ref="(el) => (listRef = el as Element)"> <ul class="options" :ref="(el) => (listRef = el as Element)">
<li class="option" v-for="(item, i) in itemList" :key="item.id"> <li class="option" v-for="(item, i) in itemList" :key="item.id">
<transition <transition
name="unfold" name="unfold"
:style="` :style="`
--delay-in: ${i * 55}ms; --delay-in: ${i * 55}ms;
--delay-out: ${(itemList.length - 1 - i) * 55}ms`" --delay-out: ${(itemList.length - 1 - i) * 55}ms`"
> >
<label :for="item.id" v-if="listOpen"> <label :for="item.id" v-if="listOpen">
<input type="button" :id="item.id" name="select-box" @click="selectOption(item)" /> <input type="button" :id="item.id" name="select-box" @click="selectOption(item)" />
<span :style="computedSelectedItem.id == item.id ? 'color: gold;' : ''" v-html="item.value"> </span> <span
</label> :style="computedSelectedItem.id == item.id ? 'color: gold;' : ''"
</transition> v-html="item.value"
</li> >
</ul> </span>
</div> </label>
</div> </transition>
</template> </li>
</ul>
<script lang="ts"> </div>
import { defineComponent, Ref, ref, computed } from 'vue'; </div>
import imageMixin from '../../mixins/imageMixin'; </template>
interface Item { <script lang="ts">
id: string; import { defineComponent, Ref, ref, computed } from 'vue';
value: string; import imageMixin from '../../mixins/imageMixin';
selectedValue?: string;
} interface Item {
id: string;
export default defineComponent({ value: string;
emits: ['selected'], selectedValue?: string;
mixins: [imageMixin], }
props: { export default defineComponent({
itemList: { emits: ['selected'],
type: Array as () => Item[], mixins: [imageMixin],
required: true,
}, props: {
itemList: {
defaultItemIndex: { type: Array as () => Item[],
type: Number, required: true
default: 0, },
},
defaultItemIndex: {
prefix: { type: Number,
type: String, default: 0
default: '', },
},
}, prefix: {
type: String,
setup(props) { default: ''
let listRef: Ref<Element | null> = ref(null); }
let buttonRef: Ref<HTMLButtonElement | null> = ref(null); },
let activeEl: Ref<Element | null> = ref(document.activeElement); setup(props) {
let listRef: Ref<Element | null> = ref(null);
let listOpen = ref(false); let buttonRef: Ref<HTMLButtonElement | null> = ref(null);
let selectedItem: Ref<Item> = ref(props.itemList[props.defaultItemIndex]);
let activeEl: Ref<Element | null> = ref(document.activeElement);
const computedSelectedItem = computed(() => {
return props.itemList.find((item) => item.id === selectedItem.value.id) || props.itemList[props.defaultItemIndex]; let listOpen = ref(false);
}); let selectedItem: Ref<Item> = ref(props.itemList[props.defaultItemIndex]);
return { const computedSelectedItem = computed(() => {
computedSelectedItem, return (
listOpen, props.itemList.find((item) => item.id === selectedItem.value.id) ||
selectedItem, props.itemList[props.defaultItemIndex]
listRef, );
buttonRef, });
activeEl,
}; return {
}, computedSelectedItem,
listOpen,
methods: { selectedItem,
selectOption(item: Item) { listRef,
this.selectedItem = item; buttonRef,
this.listOpen = false; activeEl
};
this.$emit('selected', item); },
},
methods: {
toggleBox(e: Event) { selectOption(item: Item) {
this.listOpen = !this.listOpen; this.selectedItem = item;
this.listOpen = false;
if (!this.listOpen) (e.target as HTMLButtonElement).blur();
}, this.$emit('selected', item);
},
clickedOutside() {
this.listOpen = false; toggleBox(e: Event) {
this.buttonRef?.blur(); this.listOpen = !this.listOpen;
},
}, if (!this.listOpen) (e.target as HTMLButtonElement).blur();
}); },
</script>
clickedOutside() {
<style lang="scss" scoped> this.listOpen = false;
@import '../../styles/variables.scss'; this.buttonRef?.blur();
}
.unfold { }
&-enter-from, });
&-leave-to { </script>
opacity: 0;
transform: translateY(-10px) scale(0.85); <style lang="scss" scoped>
} @import '../../styles/variables.scss';
&-enter-active, .unfold {
&-leave-active { &-enter-from,
transition: all 110ms ease-out; &-leave-to {
} opacity: 0;
transform: translateY(-10px) scale(0.85);
&-enter-active { }
transition-delay: var(--delay-in);
} &-enter-active,
&-leave-active {
&-leave-active { transition: all 110ms ease-out;
transition-delay: var(--delay-out); }
}
} &-enter-active {
transition-delay: var(--delay-in);
.select-box { }
display: flex;
align-items: center; &-leave-active {
} transition-delay: var(--delay-out);
}
.arrow { }
img {
vertical-align: middle; .select-box {
width: 1.35em; display: flex;
} align-items: center;
} }
button.selected { .arrow {
color: paleturquoise; img {
vertical-align: middle;
font-weight: bold; width: 1.35em;
padding: 0.1em 0.5em; }
}
&:focus {
background-color: #262626; button.selected {
} color: paleturquoise;
}
font-weight: bold;
.select-box_content { padding: 0.1em 0.5em;
position: relative;
margin: 0 auto; &:focus {
background-color: #262626;
height: 100%; }
}
text-align: center;
} .select-box_content {
position: relative;
ul.options { margin: 0 auto;
position: absolute;
top: 100%; height: 100%;
left: 0;
text-align: center;
height: auto; }
z-index: 100; ul.options {
width: 100%; position: absolute;
top: 100%;
font-size: 0.9em; left: 0;
}
height: auto;
li.option {
input { z-index: 100;
position: absolute; width: 100%;
top: 0;
left: 0; font-size: 0.9em;
}
-webkit-appearance: none;
-moz-appearance: none; li.option {
appearance: none; input {
border: none; position: absolute;
outline: none; top: 0;
background: none; left: 0;
&:focus + span { -webkit-appearance: none;
color: $accentCol; -moz-appearance: none;
font-weight: 800; appearance: none;
} border: none;
} outline: none;
background: none;
&:last-child label {
border-radius: 0 0 1em 1em; &:focus + span {
} color: $accentCol;
font-weight: 800;
label { }
position: relative; }
display: inline-block; &:last-child label {
background-color: #262626f2; border-radius: 0 0 1em 1em;
}
&:hover,
&:focus { label {
background-color: #333333f2; position: relative;
}
display: inline-block;
padding: 0.5em 0; background-color: #262626f2;
width: 100%; &:hover,
&:focus {
cursor: pointer; background-color: #333333f2;
} }
}
</style> padding: 0.5em 0;
width: 100%;
cursor: pointer;
}
}
</style>
+90 -90
View File
@@ -1,90 +1,90 @@
<template> <template>
<span class="status-badge" :class="statusID" v-if="isOnline"> <span class="status-badge" :class="statusID" v-if="isOnline">
{{ $t(`status.${statusID}`) }} {{ $t(`status.${statusID}`) }}
{{ statusID == 'online' ? timestampToString(statusTimestamp!) : '' }} {{ statusID == 'online' ? timestampToString(statusTimestamp!) : '' }}
</span> </span>
<span class="status-badge free" v-else> <span class="status-badge free" v-else>
{{ $t('status.free') }} {{ $t('status.free') }}
</span> </span>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import dateMixin from '../../mixins/dateMixin'; import dateMixin from '../../mixins/dateMixin';
export default defineComponent({ export default defineComponent({
props: { props: {
statusID: { statusID: {
type: String, type: String
}, },
statusTimestamp: { statusTimestamp: {
type: Number, type: Number
}, },
isOnline: { isOnline: {
type: Boolean, type: Boolean
}, }
}, },
mixins: [dateMixin], mixins: [dateMixin]
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
$free: #8a8a8a; $free: #8a8a8a;
$ending: #e6c300; $ending: #e6c300;
$no-limit: #117fc9; $no-limit: #117fc9;
$unav: #ff3d5d; $unav: #ff3d5d;
$brb: #e6a100; $brb: #e6a100;
$no-space: #222; $no-space: #222;
$online: #09a116; $online: #09a116;
$unknown: rgb(185, 60, 60); $unknown: rgb(185, 60, 60);
.status-badge { .status-badge {
border-radius: 1rem; border-radius: 1rem;
font-weight: 500; font-weight: 500;
padding: 0.2em 0.55em; padding: 0.2em 0.55em;
background-color: $online; background-color: $online;
&.free { &.free {
background-color: $free; background-color: $free;
font-size: 0.95em; font-size: 0.95em;
} }
&.ending { &.ending {
background-color: $ending; background-color: $ending;
color: black; color: black;
font-size: 0.9em; font-size: 0.9em;
} }
&.no-limit { &.no-limit {
background-color: $no-limit; background-color: $no-limit;
font-size: 0.85em; font-size: 0.85em;
} }
&.not-signed, &.not-signed,
&.unavailable { &.unavailable {
background-color: $unav; background-color: $unav;
font-size: 0.85em; font-size: 0.85em;
} }
&.brb { &.brb {
background-color: $brb; background-color: $brb;
color: black; color: black;
font-size: 0.95em; font-size: 0.95em;
} }
&.no-space { &.no-space {
background-color: $no-space; background-color: $no-space;
border: 1px solid white; border: 1px solid white;
color: white; color: white;
font-size: 0.85em; font-size: 0.85em;
} }
&.unknown { &.unknown {
background-color: $unknown; background-color: $unknown;
font-size: 0.95em; font-size: 0.95em;
} }
} }
</style> </style>
+25 -12
View File
@@ -1,12 +1,17 @@
<template> <template>
<div class="stock-list"> <div class="stock-list">
<ul> <ul>
<li v-for="(stockName, i) in trainStockList"> <li v-for="(stockName, i) in trainStockList" :key="i">
<p>{{ stockName.split(':')[0].split('_').splice(0, 2).join(' ') }} {{ stockName.split(':')[1] }}</p> <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]}${/^EN/.test(stockName) ? 'rb' : ''}.png`" :src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}${
/^EN/.test(stockName) ? 'rb' : ''
}.png`"
@error="onImageError($event, stockName)" @error="onImageError($event, stockName)"
width="400" width="400"
height="60" height="60"
@@ -15,21 +20,27 @@
<img <img
v-if="/^(EN|2EN)/.test(stockName)" v-if="/^(EN|2EN)/.test(stockName)"
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}s.png`" :src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}s.png`"
@error="(event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-s.png')" @error="
(event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-s.png')
"
/> />
<img <img
class="train-thumbnail" class="train-thumbnail"
v-if="/^EN71/.test(stockName)" v-if="/^EN71/.test(stockName)"
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}s.png`" :src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}s.png`"
@error="(event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-s.png')" @error="
(event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-s.png')
"
/> />
<img <img
class="train-thumbnail" class="train-thumbnail"
v-if="/^(EN|2EN)/.test(stockName)" v-if="/^(EN|2EN)/.test(stockName)"
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}ra.png`" :src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}ra.png`"
@error="(event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-ra.png')" @error="
(event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-ra.png')
"
/> />
</span> </span>
</li> </li>
@@ -49,13 +60,13 @@ export default defineComponent({
props: { props: {
trainStockList: { trainStockList: {
type: Array as PropType<string[]>, type: Array as PropType<string[]>,
required: true, required: true
}, }
}, },
data() { data() {
return { return {
store: useStore(), store: useStore()
}; };
}, },
@@ -63,12 +74,14 @@ export default defineComponent({
onImageError(event: Event, stockName: string) { onImageError(event: Event, stockName: string) {
const fallbackName = const fallbackName =
Object.keys(this.store.rollingStockData!.info).find((type) => { Object.keys(this.store.rollingStockData!.info).find((type) => {
return this.store.rollingStockData!.info[type as keyof RollingStockInfo].find((v) => v[0] === stockName.split(':')[0]); return this.store.rollingStockData!.info[type as keyof RollingStockInfo].find(
(v) => v[0] === stockName.split(':')[0]
);
}) || 'vehicle-unknown'; }) || 'vehicle-unknown';
(event.target as HTMLImageElement).src = `/images/icon-${fallbackName}.png`; (event.target as HTMLImageElement).src = `/images/icon-${fallbackName}.png`;
}, }
}, }
}); });
</script> </script>
+123 -119
View File
@@ -1,119 +1,123 @@
<template> <template>
<span class="stop-date"> <span class="stop-date">
<span <span
class="date arrival" class="date arrival"
v-if="!stop.beginsHere" v-if="!stop.beginsHere"
:class="{ :class="{
delayed: stop.arrivalDelay > 0 && (stop.confirmed || stop.stopped), delayed: stop.arrivalDelay > 0 && (stop.confirmed || stop.stopped),
preponed: stop.arrivalDelay < 0 && (stop.confirmed || stop.stopped), preponed: stop.arrivalDelay < 0 && (stop.confirmed || stop.stopped),
'on-time': stop.arrivalDelay == 0 && stop.confirmed, 'on-time': stop.arrivalDelay == 0 && stop.confirmed
}" }"
> >
<span v-if="stop.arrivalDelay != 0 && (stop.confirmed || stop.stopped)"> <span v-if="stop.arrivalDelay != 0 && (stop.confirmed || stop.stopped)">
<s>{{ timestampToString(stop.arrivalTimestamp) }}</s> <s>{{ timestampToString(stop.arrivalTimestamp) }}</s>
{{ timestampToString(stop.arrivalRealTimestamp) }} {{ timestampToString(stop.arrivalRealTimestamp) }}
({{ stop.arrivalDelay > 0 ? '+' : '' }}{{ stop.arrivalDelay }}) ({{ stop.arrivalDelay > 0 ? '+' : '' }}{{ stop.arrivalDelay }})
</span> </span>
<span v-else> <span v-else>
{{ timestampToString(stop.arrivalTimestamp) }} {{ timestampToString(stop.arrivalTimestamp) }}
</span> </span>
</span> </span>
<span class="date stop" v-if="stop.stopTime || stop.stopped" :class="stop.stopType.replace(', ', '-')"> <span
{{ stop.stopTime }} {{ stop.stopType == '' ? 'pt' : stop.stopType }} class="date stop"
</span> v-if="stop.stopTime || stop.stopped"
:class="stop.stopType.replace(', ', '-')"
<span >
class="date departure" {{ stop.stopTime }} {{ stop.stopType == '' ? 'pt' : stop.stopType }}
v-if="!stop.terminatesHere && (stop.stopTime != 0 || stop.stopped)" </span>
:class="{
delayed: stop.departureDelay > 0 && stop.confirmed, <span
preponed: stop.departureDelay < 0 && stop.confirmed, class="date departure"
}" v-if="!stop.terminatesHere && (stop.stopTime != 0 || stop.stopped)"
> :class="{
<span v-if="stop.departureDelay != 0 && stop.confirmed"> delayed: stop.departureDelay > 0 && stop.confirmed,
<s>{{ timestampToString(stop.departureTimestamp) }}</s> preponed: stop.departureDelay < 0 && stop.confirmed
{{ timestampToString(stop.departureRealTimestamp) }} }"
>
({{ stop.departureDelay > 0 ? '+' : '' }}{{ stop.departureDelay }}) <span v-if="stop.departureDelay != 0 && stop.confirmed">
</span> <s>{{ timestampToString(stop.departureTimestamp) }}</s>
{{ timestampToString(stop.departureRealTimestamp) }}
<span v-else>
{{ timestampToString(stop.departureTimestamp) }} ({{ stop.departureDelay > 0 ? '+' : '' }}{{ stop.departureDelay }})
</span> </span>
</span>
</span> <span v-else>
</template> {{ timestampToString(stop.departureTimestamp) }}
</span>
<script lang="ts"> </span>
import { defineComponent } from 'vue'; </span>
import dateMixin from '../../mixins/dateMixin'; </template>
import TrainStop from '../../scripts/interfaces/TrainStop';
<script lang="ts">
export default defineComponent({ import { defineComponent } from 'vue';
mixins: [dateMixin], import dateMixin from '../../mixins/dateMixin';
import TrainStop from '../../scripts/interfaces/TrainStop';
props: {
stop: { export default defineComponent({
type: Object as () => TrainStop, mixins: [dateMixin],
required: true,
}, props: {
}, stop: {
type: Object as () => TrainStop,
setup() { required: true
return {}; }
}, },
});
</script> setup() {
return {};
<style lang="scss" scoped> }
$preponedClr: lime; });
$delayedClr: salmon; </script>
$dateClr: #525151;
$stopExchangeClr: #db8e29; <style lang="scss" scoped>
$stopDefaultClr: #252525; $preponedClr: lime;
$delayedClr: salmon;
.stop-date { $dateClr: #525151;
display: flex; $stopExchangeClr: #db8e29;
align-items: center; $stopDefaultClr: #252525;
.date { .stop-date {
background: $dateClr; display: flex;
padding: 0.3em 0.5em; align-items: center;
}
.date {
.stop { background: $dateClr;
&.ph, padding: 0.3em 0.5em;
&.ph-pm, }
&.pm {
background: $stopExchangeClr; .stop {
} &.ph,
&.ph-pm,
background: $stopDefaultClr; &.pm {
} background: $stopExchangeClr;
}
.arrival,
.departure { background: $stopDefaultClr;
&.delayed { }
s {
color: #999; .arrival,
} .departure {
&.delayed {
span { s {
color: $delayedClr; color: #999;
} }
}
span {
&.preponed { color: $delayedClr;
s { }
color: #999; }
}
&.preponed {
span { s {
color: $preponedClr; color: #999;
} }
}
} span {
} color: $preponedClr;
</style> }
}
}
}
</style>
+7 -6
View File
@@ -27,7 +27,7 @@ export default defineComponent({
data() { data() {
return { return {
isTopBarVisible: false, isTopBarVisible: false
}; };
}, },
@@ -35,7 +35,7 @@ export default defineComponent({
const store = useStore(); const store = useStore();
return { return {
store, store
}; };
}, },
@@ -49,12 +49,14 @@ export default defineComponent({
methods: { methods: {
handleContentScroll(e: Event) { handleContentScroll(e: Event) {
const trainInfoCompHeight: number = (this.$refs['trainInfo'] as any).$el.getBoundingClientRect().height; const trainInfoCompHeight: number = (
this.$refs['trainInfo'] as any
).$el.getBoundingClientRect().height;
const posTop = (e.target as HTMLElement).scrollTop; const posTop = (e.target as HTMLElement).scrollTop;
this.isTopBarVisible = posTop > trainInfoCompHeight; this.isTopBarVisible = posTop > trainInfoCompHeight;
}, }
}, }
}); });
</script> </script>
@@ -144,7 +146,6 @@ export default defineComponent({
} }
@include smallScreen { @include smallScreen {
.modal_content { .modal_content {
max-height: 85vh; max-height: 85vh;
} }
+13 -10
View File
@@ -4,7 +4,9 @@
<img <img
class="train-thumbnail" class="train-thumbnail"
v-else v-else
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${name.split(':')[0]}${stockType == 'loco-ezt' ? 'rb' : ''}.png`" :src="`https://rj.td2.info.pl/dist/img/thumbnails/${name.split(':')[0]}${
stockType == 'loco-ezt' ? 'rb' : ''
}.png`"
@error="onImageError" @error="onImageError"
@load="onImageLoad" @load="onImageLoad"
width="220" width="220"
@@ -14,7 +16,6 @@
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import imageMixin from '../../mixins/imageMixin';
import { useStore } from '../../store/store'; import { useStore } from '../../store/store';
import { RollingStockInfo } from '../../scripts/interfaces/github_api/StockInfoGithubData'; import { RollingStockInfo } from '../../scripts/interfaces/github_api/StockInfoGithubData';
@@ -22,20 +23,20 @@ export default defineComponent({
props: { props: {
name: { name: {
type: String, type: String,
required: true, required: true
}, },
onlyFirstSegment: { onlyFirstSegment: {
type: Boolean, type: Boolean,
default: false, default: false
}, }
}, },
data() { data() {
return { return {
store: useStore(), store: useStore(),
isNotFound: false, isNotFound: false,
isLoaded: false, isLoaded: false
}; };
}, },
@@ -53,10 +54,12 @@ export default defineComponent({
return ( return (
Object.keys(this.store.rollingStockData.info).find((type) => { Object.keys(this.store.rollingStockData.info).find((type) => {
return this.store.rollingStockData?.info[type as keyof RollingStockInfo].find((v) => v[0] === this.name.split(':')[0]); return this.store.rollingStockData?.info[type as keyof RollingStockInfo].find(
(v) => v[0] === this.name.split(':')[0]
);
}) || 'vehicle-unknown' }) || 'vehicle-unknown'
); );
}, }
}, },
methods: { methods: {
@@ -68,8 +71,8 @@ export default defineComponent({
onImageLoad() { onImageLoad() {
this.isNotFound = false; this.isNotFound = false;
this.isLoaded = true; this.isLoaded = true;
}, }
}, }
}); });
</script> </script>
+25 -21
View File
@@ -54,42 +54,42 @@
</i18n-t> </i18n-t>
</div> </div>
<div v-if="firstPlaceDispatchers.length == 1"> <div v-if="topDispatchers.length == 1">
&bull; &bull;
<i18n-t keypath="journal.timetable-stats-most-active-dr"> <i18n-t keypath="journal.timetable-stats-most-active-dr">
<template #dispatcher> <template #dispatcher>
<router-link :to="`/journal/dispatchers?dispatcherName=${firstPlaceDispatchers[0].name}`"> <router-link :to="`/journal/dispatchers?dispatcherName=${topDispatchers[0].name}`">
<b>{{ firstPlaceDispatchers[0].name }}</b> <b>{{ topDispatchers[0].name }}</b>
</router-link> </router-link>
</template> </template>
<template #count> <template #count>
<b class="text--primary"> <b class="text--primary">
{{ firstPlaceDispatchers[0].count }} {{ topDispatchers[0].count }}
{{ $t('journal.timetable-count', firstPlaceDispatchers[0].count) }} {{ $t('journal.timetable-count', topDispatchers[0].count) }}
</b> </b>
</template> </template>
</i18n-t> </i18n-t>
</div> </div>
<div v-if="firstPlaceDispatchers.length > 1"> <div v-if="topDispatchers.length > 1">
&bull; &bull;
<i18n-t keypath="journal.timetable-stats-most-active-dr-many"> <i18n-t keypath="journal.timetable-stats-most-active-dr-many">
<template #dispatchers> <template #dispatchers>
<span v-for="(disp, i) in firstPlaceDispatchers"> <span v-for="(disp, i) in topDispatchers" :key="i">
<span v-if="i == firstPlaceDispatchers.length - 1"> {{ $t('general.and') }} </span> <span v-if="i == topDispatchers.length - 1"> {{ $t('general.and') }} </span>
<router-link :to="`/journal/dispatchers?dispatcherName=${disp.name}`"> <router-link :to="`/journal/dispatchers?dispatcherName=${disp.name}`">
<b>{{ disp.name }}</b> <b>{{ disp.name }}</b>
</router-link> </router-link>
<span v-if="i < firstPlaceDispatchers.length - 2">, </span> <span v-if="i < topDispatchers.length - 2">, </span>
</span> </span>
</template> </template>
<template #count> <template #count>
<b class="text--primary"> <b class="text--primary">
{{ firstPlaceDispatchers[0].count }} {{ topDispatchers[0].count }}
{{ $t('journal.timetable-count', firstPlaceDispatchers[0].count) }} {{ $t('journal.timetable-count', topDispatchers[0].count) }}
</b> </b>
</template> </template>
</i18n-t> </i18n-t>
@@ -99,7 +99,9 @@
&bull; &bull;
<i18n-t keypath="journal.timetable-stats-longest-duties"> <i18n-t keypath="journal.timetable-stats-longest-duties">
<template #dispatcher> <template #dispatcher>
<router-link :to="`/journal/dispatchers?dispatcherName=${stats.longestDuties[0].name}`"> <router-link
:to="`/journal/dispatchers?dispatcherName=${stats.longestDuties[0].name}`"
>
<b>{{ stats.longestDuties[0].name }}</b> <b>{{ stats.longestDuties[0].name }}</b>
</router-link> </router-link>
</template> </template>
@@ -133,7 +135,10 @@ import axios from 'axios';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import dateMixin from '../../mixins/dateMixin'; import dateMixin from '../../mixins/dateMixin';
import { DataStatus } from '../../scripts/enums/DataStatus'; import { DataStatus } from '../../scripts/enums/DataStatus';
import { ITimetablesDailyStats, ITimetablesDailyStatsResponse } from '../../scripts/interfaces/api/StatsAPIData'; import {
ITimetablesDailyStats,
ITimetablesDailyStatsResponse
} from '../../scripts/interfaces/api/StatsAPIData';
import { URLs } from '../../scripts/utils/apiURLs'; import { URLs } from '../../scripts/utils/apiURLs';
export default defineComponent({ export default defineComponent({
@@ -156,8 +161,8 @@ export default defineComponent({
timetableRouteDistance: 0, timetableRouteDistance: 0,
longestDuties: [], longestDuties: [],
mostActiveDrivers: [], mostActiveDrivers: [],
mostActiveDispatchers: [], mostActiveDispatchers: []
} as ITimetablesDailyStats, } as ITimetablesDailyStats
}; };
}, },
@@ -171,12 +176,12 @@ export default defineComponent({
}, },
computed: { computed: {
firstPlaceDispatchers() { topDispatchers() {
if (this.stats.mostActiveDispatchers.length == 0) return []; if (this.stats.mostActiveDispatchers.length == 0) return [];
const maxCount = this.stats.mostActiveDispatchers[0].count; const maxCount = this.stats.mostActiveDispatchers[0].count;
return this.stats.mostActiveDispatchers.filter((disp) => disp.count === maxCount); return this.stats.mostActiveDispatchers.filter((disp) => disp.count === maxCount);
}, }
}, },
methods: { methods: {
@@ -197,7 +202,7 @@ export default defineComponent({
mostActiveDispatchers: res.mostActiveDispatchers, mostActiveDispatchers: res.mostActiveDispatchers,
mostActiveDrivers: res.mostActiveDrivers, mostActiveDrivers: res.mostActiveDrivers,
longestDuties: res.longestDuties, longestDuties: res.longestDuties
}; };
this.statsStatus = DataStatus.Loaded; this.statsStatus = DataStatus.Loaded;
@@ -218,8 +223,8 @@ export default defineComponent({
stopFetchingDailyStats() { stopFetchingDailyStats() {
clearInterval(this.intervalId); clearInterval(this.intervalId);
this.intervalId = -1; this.intervalId = -1;
}, }
}, }
}); });
</script> </script>
@@ -247,4 +252,3 @@ export default defineComponent({
} }
} }
</style> </style>
+15 -22
View File
@@ -14,7 +14,7 @@
<div v-else> <div v-else>
<h3>STATYSTYKI WYSTAWIONYCH ROZKŁADÓW</h3> <h3>STATYSTYKI WYSTAWIONYCH ROZKŁADÓW</h3>
<div class="info-stats" v-if="store.dispatcherStatsData._count._all"> <div class="info-stats" v-if="store.dispatcherStatsData._count._all">
<span class="stat-badge"> <span class="stat-badge">
<span>LICZBA</span> <span>LICZBA</span>
@@ -36,8 +36,9 @@
<h3>OSTATNIE WYSTAWIONE ROZKŁADY</h3> <h3>OSTATNIE WYSTAWIONE ROZKŁADY</h3>
<div class="last-timetables"> <div class="last-timetables">
<div class="timetable-row" v-for="timetable in timetables"> <div class="timetable-row" v-for="timetable in timetables" :key="timetable.id">
#{{ timetable.timetableId }} | <b>{{ timetable.trainCategoryCode }} {{ timetable.trainNo }}</b> | #{{ timetable.timetableId }} |
<b>{{ timetable.trainCategoryCode }} {{ timetable.trainNo }}</b> |
{{ timetable.driverName }} ({{ timetable.routeDistance }}km) {{ timetable.driverName }} ({{ timetable.routeDistance }}km)
<div>{{ timetable.route.replace('|', ' > ') }}</div> <div>{{ timetable.route.replace('|', ' > ') }}</div>
</div> </div>
@@ -49,9 +50,8 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import axios from 'axios'; import axios from 'axios';
import { computed, defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { DispatcherStatsAPIData } from '../../scripts/interfaces/api/DispatcherStatsAPIData'; import { DispatcherStatsAPIData } from '../../scripts/interfaces/api/DispatcherStatsAPIData';
import { TimetableHistory } from '../../scripts/interfaces/api/TimetablesAPIData'; import { TimetableHistory } from '../../scripts/interfaces/api/TimetablesAPIData';
import { URLs } from '../../scripts/utils/apiURLs'; import { URLs } from '../../scripts/utils/apiURLs';
@@ -64,15 +64,8 @@ export default defineComponent({
setup() { setup() {
const store = useStore(); const store = useStore();
const statsData2 = computed(async () => {
return await (
await axios.get(`${URLs.stacjownikAPI}/api/getDispatcherInfo?name=${store.dispatcherStatsName}`)
).data;
});
return { return {
store, store
statsData2,
}; };
}, },
@@ -80,7 +73,7 @@ export default defineComponent({
return { return {
cardVisible: false, cardVisible: false,
lastDispatcherName: '', lastDispatcherName: '',
timetables: [] as TimetableHistory[], timetables: [] as TimetableHistory[]
}; };
}, },
@@ -98,18 +91,22 @@ export default defineComponent({
} }
const statsData: DispatcherStatsAPIData = await ( const statsData: DispatcherStatsAPIData = await (
await axios.get(`${URLs.stacjownikAPI}/api/getDispatcherInfo?name=${this.store.dispatcherStatsName}`) await axios.get(
`${URLs.stacjownikAPI}/api/getDispatcherInfo?name=${this.store.dispatcherStatsName}`
)
).data; ).data;
const timetables: TimetableHistory[] = await ( const timetables: TimetableHistory[] = await (
await axios.get(`${URLs.stacjownikAPI}/api/getTimetables?authorName=${this.store.dispatcherStatsName}`) await axios.get(
`${URLs.stacjownikAPI}/api/getTimetables?authorName=${this.store.dispatcherStatsName}`
)
).data; ).data;
this.timetables = timetables; this.timetables = timetables;
this.store.dispatcherStatsData = statsData; this.store.dispatcherStatsData = statsData;
this.lastDispatcherName = this.store.dispatcherStatsName; this.lastDispatcherName = this.store.dispatcherStatsName;
}, }
}, }
}); });
</script> </script>
@@ -163,11 +160,7 @@ h3 {
text-align: center; text-align: center;
} }
.last-timetables { .last-timetables {
overflow-y: auto; overflow-y: auto;
} }
</style> </style>
@@ -1,241 +1,254 @@
<template> <template>
<div> <div>
<transition name="status-anim" mode="out-in"> <transition name="status-anim" mode="out-in">
<div :key="dataStatus"> <div :key="dataStatus">
<div class="journal_warning" v-if="store.isOffline"> <div class="journal_warning" v-if="store.isOffline">
{{ $t('app.offline') }} {{ $t('app.offline') }}
</div> </div>
<Loading v-else-if="dataStatus == DataStatus.Loading" /> <Loading v-else-if="dataStatus == DataStatus.Loading" />
<div v-else-if="dataStatus == DataStatus.Error" class="journal_warning error"> <div v-else-if="dataStatus == DataStatus.Error" class="journal_warning error">
{{ $t('app.error') }} {{ $t('app.error') }}
</div> </div>
<div class="journal_warning" v-else-if="dispatcherHistory.length == 0"> <div class="journal_warning" v-else-if="dispatcherHistory.length == 0">
{{ $t('app.no-result') }} {{ $t('app.no-result') }}
</div> </div>
<div v-else> <div v-else>
<table class="scenery-history-table"> <table class="scenery-history-table">
<thead> <thead>
<th>{{ $t('journal.history-name') }}</th> <th>{{ $t('journal.history-name') }}</th>
<th>{{ $t('journal.history-hash') }}</th> <th>{{ $t('journal.history-hash') }}</th>
<th>{{ $t('journal.history-dispatcher') }}</th> <th>{{ $t('journal.history-dispatcher') }}</th>
<th>{{ $t('journal.history-level') }}</th> <th>{{ $t('journal.history-level') }}</th>
<th>{{ $t('journal.history-rate') }}</th> <th>{{ $t('journal.history-rate') }}</th>
<th>{{ $t('journal.history-region') }}</th> <th>{{ $t('journal.history-region') }}</th>
<th>{{ $t('journal.history-date') }}</th> <th>{{ $t('journal.history-date') }}</th>
</thead> </thead>
<tbody> <tbody>
<transition-group name="list-anim"> <transition-group name="list-anim">
<tr v-for="historyItem in dispatcherHistory" :key="historyItem.id"> <tr v-for="historyItem in dispatcherHistory" :key="historyItem.id">
<td> <td>
<router-link :to="`/journal/dispatchers?sceneryName=${historyItem.stationName}`"> <router-link
<b>{{ historyItem.stationName }}</b> :to="`/journal/dispatchers?sceneryName=${historyItem.stationName}`"
</router-link> >
</td> <b>{{ historyItem.stationName }}</b>
<td>#{{ historyItem.stationHash }}</td> </router-link>
<td> </td>
<router-link :to="`/journal/dispatchers?dispatcherName=${historyItem.dispatcherName}`"> <td>#{{ historyItem.stationHash }}</td>
<b>{{ historyItem.dispatcherName }}</b> <td>
</router-link> <router-link
</td> :to="`/journal/dispatchers?dispatcherName=${historyItem.dispatcherName}`"
<td> >
<b <b>{{ historyItem.dispatcherName }}</b>
v-if="historyItem.dispatcherLevel !== null" </router-link>
class="level-badge dispatcher" </td>
:style="calculateExpStyle(historyItem.dispatcherLevel, historyItem.dispatcherIsSupporter)" <td>
> <b
{{ historyItem.dispatcherLevel >= 2 ? historyItem.dispatcherLevel : 'L' }} v-if="historyItem.dispatcherLevel !== null"
</b> class="level-badge dispatcher"
</td> :style="
<td class="text--primary"> calculateExpStyle(
<b>{{ historyItem.dispatcherRate }}</b> historyItem.dispatcherLevel,
</td> historyItem.dispatcherIsSupporter
<td> )
<b class="region-badge" :aria-describedby="historyItem.region">{{ "
regions.find((r) => r.id == historyItem.region)?.value || '???' >
}}</b> {{ historyItem.dispatcherLevel >= 2 ? historyItem.dispatcherLevel : 'L' }}
</td> </b>
<td style="min-width: 200px" class="time"> </td>
<span v-if="historyItem.timestampTo" class="text--offline"> <td class="text--primary">
<b>{{ $d(historyItem.timestampFrom) }}</b> <b>{{ historyItem.dispatcherRate }}</b>
{{ timestampToString(historyItem.timestampFrom) }} </td>
- {{ timestampToString(historyItem.timestampTo) }} ({{ <td>
calculateDuration(historyItem.currentDuration) <b class="region-badge" :aria-describedby="historyItem.region">{{
}}) regions.find((r) => r.id == historyItem.region)?.value || '???'
</span> }}</b>
<span class="dispatcher-online" v-else> </td>
<b class="text--online"> <td style="min-width: 200px" class="time">
<router-link :to="`/scenery?station=${historyItem.stationName}`">{{ <span v-if="historyItem.timestampTo" class="text--offline">
$t('journal.online-since') <b>{{ $d(historyItem.timestampFrom) }}</b>
}}</router-link> {{ timestampToString(historyItem.timestampFrom) }}
{{ timestampToString(historyItem.timestampFrom) }} - {{ timestampToString(historyItem.timestampTo) }} ({{
</b> calculateDuration(historyItem.currentDuration)
({{ calculateDuration(historyItem.currentDuration) }}) }})
</span> </span>
</td> <span class="dispatcher-online" v-else>
</tr> <b class="text--online">
</transition-group> <router-link :to="`/scenery?station=${historyItem.stationName}`">{{
</tbody> $t('journal.online-since')
</table> }}</router-link>
{{ timestampToString(historyItem.timestampFrom) }}
<AddDataButton </b>
:list="dispatcherHistory" ({{ calculateDuration(historyItem.currentDuration) }})
:scrollDataLoaded="scrollDataLoaded" </span>
:scrollNoMoreData="scrollNoMoreData" </td>
@addHistoryData="addHistoryData" </tr>
/> </transition-group>
</div> </tbody>
</div> </table>
</transition>
<AddDataButton
<div class="journal_warning" v-if="scrollNoMoreData"> :list="dispatcherHistory"
{{ $t('journal.no-further-data') }} :scrollDataLoaded="scrollDataLoaded"
</div> :scrollNoMoreData="scrollNoMoreData"
@addHistoryData="addHistoryData"
<div class="journal_warning" v-else-if="!scrollDataLoaded"> />
{{ $t('journal.loading-further-data') }} </div>
</div> </div>
</div> </transition>
</template>
<div class="journal_warning" v-if="scrollNoMoreData">
<script lang="ts"> {{ $t('journal.no-further-data') }}
import { defineComponent, PropType } from 'vue'; </div>
import dateMixin from '../../mixins/dateMixin';
import { DispatcherHistory } from '../../scripts/interfaces/api/DispatchersAPIData'; <div class="journal_warning" v-else-if="!scrollDataLoaded">
import styleMixin from '../../mixins/styleMixin'; {{ $t('journal.loading-further-data') }}
import imageMixin from '../../mixins/imageMixin'; </div>
import { DataStatus } from '../../scripts/enums/DataStatus'; </div>
import { useStore } from '../../store/store'; </template>
import Loading from '../Global/Loading.vue';
import { regions } from '../../data/options.json'; <script lang="ts">
import AddDataButton from '../Global/AddDataButton.vue'; import { defineComponent, PropType } from 'vue';
import dateMixin from '../../mixins/dateMixin';
export default defineComponent({ import { DispatcherHistory } from '../../scripts/interfaces/api/DispatchersAPIData';
components: { Loading, AddDataButton }, import styleMixin from '../../mixins/styleMixin';
import imageMixin from '../../mixins/imageMixin';
mixins: [dateMixin, styleMixin, imageMixin], import { DataStatus } from '../../scripts/enums/DataStatus';
import { useStore } from '../../store/store';
props: { import Loading from '../Global/Loading.vue';
dispatcherHistory: { import { regions } from '../../data/options.json';
type: Array as PropType<DispatcherHistory[]>, import AddDataButton from '../Global/AddDataButton.vue';
required: true,
}, export default defineComponent({
scrollNoMoreData: { components: { Loading, AddDataButton },
type: Boolean,
}, mixins: [dateMixin, styleMixin, imageMixin],
scrollDataLoaded: {
type: Boolean, props: {
}, dispatcherHistory: {
addHistoryData: { type: Array as PropType<DispatcherHistory[]>,
type: Function as PropType<() => void>, required: true
}, },
dataStatus: { scrollNoMoreData: {
type: Number as PropType<DataStatus>, type: Boolean
}, },
}, scrollDataLoaded: {
type: Boolean
data() { },
return { addHistoryData: {
DataStatus, type: Function as PropType<() => void>
store: useStore(), },
regions, dataStatus: {
}; type: Number as PropType<DataStatus>
}, }
},
computed: {
computedDispatcherHistory() { data() {
console.log(this.dispatcherHistory.length); return {
DataStatus,
return this.dispatcherHistory.reduce((acc, historyItem, i) => { store: useStore(),
if (this.isAnotherDay(i - 1, i)) acc.push(new Date(historyItem.timestampFrom).toLocaleDateString('pl-PL')); regions
acc.push(historyItem); };
},
return acc;
}, [] as (DispatcherHistory | string)[]); computed: {
}, computedDispatcherHistory() {
}, console.log(this.dispatcherHistory.length);
methods: { return this.dispatcherHistory.reduce(
navigateToScenery(name: string, isOnline: boolean) { (acc, historyItem, i) => {
if (!isOnline) return; if (this.isAnotherDay(i - 1, i))
acc.push(new Date(historyItem.timestampFrom).toLocaleDateString('pl-PL'));
this.$router.push(`/scenery?station=${name.trim().replace(/ /g, '_')}`); acc.push(historyItem);
},
return acc;
isAnotherDay(prevIndex: number, currIndex: number) { },
if (currIndex == 0) return true; [] as (DispatcherHistory | string)[]
);
return ( }
new Date(this.dispatcherHistory[prevIndex].timestampFrom).getDate() != },
new Date(this.dispatcherHistory[currIndex].timestampFrom).getDate()
); methods: {
}, navigateToScenery(name: string, isOnline: boolean) {
}, if (!isOnline) return;
});
</script> this.$router.push(`/scenery?station=${name.trim().replace(/ /g, '_')}`);
},
<style lang="scss" scoped>
@import '../../styles/animations.scss'; isAnotherDay(prevIndex: number, currIndex: number) {
@import '../../styles/responsive.scss'; if (currIndex == 0) return true;
@import '../../styles/badge.scss';
@import '../../styles/variables.scss'; return (
@import '../../styles/JournalSection.scss'; new Date(this.dispatcherHistory[prevIndex].timestampFrom).getDate() !=
new Date(this.dispatcherHistory[currIndex].timestampFrom).getDate()
table.scenery-history-table { );
--_bg-table: #111; }
--_bg-head: #101010; }
--_bg-row: #2f2f2f; });
</script>
width: 100%;
border-collapse: collapse; <style lang="scss" scoped>
position: relative; @import '../../styles/animations.scss';
text-align: center; @import '../../styles/responsive.scss';
@import '../../styles/badge.scss';
margin-bottom: 1em; @import '../../styles/variables.scss';
@import '../../styles/JournalSection.scss';
thead {
position: sticky; table.scenery-history-table {
top: 0; --_bg-table: #111;
background-color: var(--_bg-head); --_bg-head: #101010;
} --_bg-row: #2f2f2f;
th { width: 100%;
padding: 0.5em; border-collapse: collapse;
} position: relative;
text-align: center;
tr {
background-color: var(--_bg-row); margin-bottom: 1em;
border-bottom: 2px solid black;
thead {
&:last-child { position: sticky;
border: none; top: 0;
} background-color: var(--_bg-head);
} }
td { th {
padding: 0.75em; padding: 0.5em;
}
.level-badge {
margin: 0 auto; tr {
} background-color: var(--_bg-row);
} border-bottom: 2px solid black;
@media screen and (max-width: 550px) { &:last-child {
font-size: 0.9em; border: none;
} }
} }
.text { td {
&--online { padding: 0.75em;
color: springgreen;
} .level-badge {
margin: 0 auto;
&--offline { }
color: #ddd; }
}
} @media screen and (max-width: 550px) {
</style> font-size: 0.9em;
}
}
.text {
&--online {
color: springgreen;
}
&--offline {
color: #ddd;
}
}
</style>
@@ -2,13 +2,17 @@
<div class="journal-stats"> <div class="journal-stats">
<span v-if="store.driverStatsData"> <span v-if="store.driverStatsData">
<h3> <h3>
{{ $t('journal.stats-title') }} <span class="text--primary">{{ store.driverStatsName.toUpperCase() }}</span> {{ $t('journal.stats-title') }}
<span class="text--primary">{{ store.driverStatsName.toUpperCase() }}</span>
</h3> </h3>
<div class="info-stats"> <div class="info-stats">
<span class="stat-badge"> <span class="stat-badge">
<span>{{ $t('journal.stats-timetables') }}</span> <span>{{ $t('journal.stats-timetables') }}</span>
<span>{{ store.driverStatsData._count.fulfilled }} / {{ store.driverStatsData._count._all }}</span> <span
>{{ store.driverStatsData._count.fulfilled }} /
{{ store.driverStatsData._count._all }}</span
>
</span> </span>
<span class="stat-badge"> <span class="stat-badge">
@@ -39,7 +43,9 @@
</div> </div>
</span> </span>
<b v-else-if="store.driverStatsStatus == DataStatus.Loading">{{ $t('journal.stats-loading') }}</b> <b v-else-if="store.driverStatsStatus == DataStatus.Loading">{{
$t('journal.stats-loading')
}}</b>
<b v-else-if="store.driverStatsStatus == DataStatus.Error"> <b v-else-if="store.driverStatsStatus == DataStatus.Error">
{{ $t('journal.stats-error ') }} {{ $t('journal.stats-error ') }}
</b> </b>
@@ -56,9 +62,9 @@ export default defineComponent({
data() { data() {
return { return {
store: useStore(), store: useStore(),
DataStatus, DataStatus
}; };
}, }
}); });
</script> </script>
+303 -300
View File
@@ -1,300 +1,303 @@
<template> <template>
<div class="filters-options" @keydown.esc="showOptions = false"> <div class="filters-options" @keydown.esc="showOptions = false">
<div class="bg" v-if="showOptions" @click="showOptions = false"></div> <div class="bg" v-if="showOptions" @click="showOptions = false"></div>
<div class="actions-bar"> <div class="actions-bar">
<button class="filter-button btn--filled btn--image" @click="showOptions = !showOptions" ref="button"> <button
<img :src="getIcon('filter2')" alt="Open filters" /> class="filter-button btn--filled btn--image"
{{ $t('options.filters') }} [F] @click="showOptions = !showOptions"
<span class="active-indicator" v-if="currentOptionsActive"></span> ref="button"
</button> >
<img :src="getIcon('filter2')" alt="Open filters" />
<button class="filter-button btn--filled btn--image" @click="refreshData"> {{ $t('options.filters') }} [F]
<img :src="getIcon('refresh')" alt="Refresh data" /> <span class="active-indicator" v-if="currentOptionsActive"></span>
{{ $t('general.refresh') }} </button>
</button>
</div> <button class="filter-button btn--filled btn--image" @click="refreshData">
<img :src="getIcon('refresh')" alt="Refresh data" />
<datalist id="search-driver"> {{ $t('general.refresh') }}
<option v-for="sugg in driverSuggestions" :value="sugg"></option> </button>
</datalist> </div>
<datalist id="search-dispatcher"> <datalist id="search-driver">
<option v-for="sugg in dispatcherSuggestions" :value="sugg"></option> <option v-for="(sugg, i) in driverSuggestions" :key="i" :value="sugg"></option>
</datalist> </datalist>
<transition name="options-anim"> <datalist id="search-dispatcher">
<div class="options_wrapper" v-if="showOptions"> <option v-for="(sugg, i) in dispatcherSuggestions" :key="i" :value="sugg"></option>
<div class="options_content"> </datalist>
<h1 class="option-title">{{ $t('options.search-title') }}</h1>
<div class="search_content"> <transition name="options-anim">
<div class="search" v-for="(_, propName) in searchersValues" :key="propName"> <div class="options_wrapper" v-if="showOptions">
<label v-if="propName == 'search-date'" for="date">{{ $t(`options.search-${optionsType}-date`) }}</label> <div class="options_content">
<h1 class="option-title">{{ $t('options.search-title') }}</h1>
<div class="search-box"> <div class="search_content">
<input <div class="search" v-for="(_, propName) in searchersValues" :key="propName">
class="search-input" <label v-if="propName == 'search-date'" for="date">{{
v-model="searchersValues[propName]" $t(`options.search-${optionsType}-date`)
@keydown.enter="onSearchConfirm" }}</label>
@focus="preventKeyDown = true"
@blur="preventKeyDown = false" <div class="search-box">
:placeholder="$t(`options.${propName}`)" <input
:type="propName == 'search-date' ? 'date' : 'text'" class="search-input"
:min="propName == 'search-date' ? '2022-02-01' : undefined" v-model="searchersValues[propName]"
:list="propName.toString()" @keydown.enter="onSearchConfirm"
/> @focus="preventKeyDown = true"
@blur="preventKeyDown = false"
<button class="search-exit" v-if="propName != 'search-date'"> :placeholder="$t(`options.${propName}`)"
<img :src="getIcon('exit')" alt="exit-icon" @click="onInputClear(propName)" /> :type="propName == 'search-date' ? 'date' : 'text'"
</button> :min="propName == 'search-date' ? '2022-02-01' : undefined"
</div> :list="propName.toString()"
</div> />
</div>
<button class="search-exit" v-if="propName != 'search-date'">
<h1 class="option-title">{{ $t('options.sort-title') }}</h1> <img :src="getIcon('exit')" alt="exit-icon" @click="onInputClear(propName)" />
<div class="options_sorters"> </button>
<div v-for="opt in translatedSorterOptions"> </div>
<button </div>
class="sort-option btn--option" </div>
:data-selected="opt.id == sorterActive.id"
@click="onSorterChange(opt)" <h1 class="option-title">{{ $t('options.sort-title') }}</h1>
> <div class="options_sorters">
{{ opt.value.toUpperCase() }} <div v-for="opt in translatedSorterOptions" :key="opt.id">
</button> <button
</div> class="sort-option btn--option"
</div> :data-selected="opt.id == sorterActive.id"
@click="onSorterChange(opt)"
<h1 class="option-title" v-if="filters.length != 0">{{ $t('options.filter-title') }}</h1> >
{{ opt.value.toUpperCase() }}
<div class="options_filter-sections" v-if="filters.length != 0 && filterList"> </button>
<section class="filter-section" v-for="section in JournalFilterSection"> </div>
<p>{{ $t(`options.filter-section-${section}`) }}</p> </div>
<div class="options_filters"> <h1 class="option-title" v-if="filters.length != 0">{{ $t('options.filter-title') }}</h1>
<button
v-for="filter in filterList.filter((f) => f.filterSection == section)" <div class="options_filter-sections" v-if="filters.length != 0 && filterList">
class="filter-option btn--option" <section class="filter-section" v-for="section in JournalFilterSection" :key="section">
:class="{ checked: filter.isActive }" <p>{{ $t(`options.filter-section-${section}`) }}</p>
:id="filter.id"
@click="onFilterChange(filter)" <div class="options_filters">
> <button
{{ $t(`options.filter-${filter.id}`) }} v-for="filter in filterList.filter((f) => f.filterSection == section)"
</button> :key="filter.id"
</div> class="filter-option btn--option"
</section> :class="{ checked: filter.isActive }"
</div> :id="filter.id"
@click="onFilterChange(filter)"
<div class="options_actions"> >
<button class="btn--action" @click="onResetButtonClick"> {{ $t(`options.filter-${filter.id}`) }}
{{ $t('options.reset-button') }} </button>
</button> </div>
<button class="btn--action" @click="onSearchButtonConfirm"> </section>
{{ $t('options.search-button') }} </div>
</button>
</div> <div class="options_actions">
</div> <button class="btn--action" @click="onResetButtonClick">
</div> {{ $t('options.reset-button') }}
</transition> </button>
</div> <button class="btn--action" @click="onSearchButtonConfirm">
</template> {{ $t('options.search-button') }}
</button>
<script lang="ts"> </div>
import axios from 'axios'; </div>
import { defineComponent, inject, PropType } from 'vue'; </div>
import imageMixin from '../../mixins/imageMixin'; </transition>
import keyMixin from '../../mixins/keyMixin'; </div>
import { DataStatus } from '../../scripts/enums/DataStatus'; </template>
import { DriverStatsAPIData } from '../../scripts/interfaces/api/DriverStatsAPIData';
import { URLs } from '../../scripts/utils/apiURLs'; <script lang="ts">
import { useStore } from '../../store/store'; import axios from 'axios';
import ActionButton from '../Global/ActionButton.vue'; import { defineComponent, inject, PropType } from 'vue';
import SelectBox from '../Global/SelectBox.vue'; import imageMixin from '../../mixins/imageMixin';
import { JournalFilterSection } from '../../scripts/enums/JournalFilterType'; import keyMixin from '../../mixins/keyMixin';
import { JournalFilter } from '../../scripts/types/JournalTimetablesTypes'; import { DataStatus } from '../../scripts/enums/DataStatus';
import { DriverStatsAPIData } from '../../scripts/interfaces/api/DriverStatsAPIData';
export default defineComponent({ import { URLs } from '../../scripts/utils/apiURLs';
components: { SelectBox, ActionButton }, import { useStore } from '../../store/store';
emits: ['onSearchConfirm', 'onOptionsReset', 'onRefreshData'], import { JournalFilterSection } from '../../scripts/enums/JournalFilterType';
mixins: [imageMixin, keyMixin], import { JournalFilter } from '../../scripts/types/JournalTimetablesTypes';
props: { export default defineComponent({
sorterOptionIds: { emits: ['onSearchConfirm', 'onOptionsReset', 'onRefreshData'],
type: Array as PropType<Array<string>>, mixins: [imageMixin, keyMixin],
required: true,
}, props: {
sorterOptionIds: {
filters: { type: Array as PropType<Array<string>>,
type: Array as PropType<JournalFilter[]>, required: true
default: [], },
},
filters: {
dataStatus: { type: Array as PropType<JournalFilter[]>,
type: Number as PropType<DataStatus>, default: () => []
default: DataStatus.Initialized, },
},
dataStatus: {
currentOptionsActive: { type: Number as PropType<DataStatus>,
type: Boolean, default: DataStatus.Initialized
default: false, },
},
currentOptionsActive: {
optionsType: { type: Boolean,
type: String, default: false
required: true, },
},
}, optionsType: {
type: String,
data() { required: true
return { }
showOptions: false, },
JournalFilterSection,
data() {
driverSuggestions: [] as string[], return {
dispatcherSuggestions: [] as string[], showOptions: false,
JournalFilterSection,
searchTimeout: 0,
store: useStore(), driverSuggestions: [] as string[],
dispatcherSuggestions: [] as string[],
DataStatus,
}; searchTimeout: 0,
}, store: useStore(),
setup() { DataStatus
return { };
searchersValues: inject('searchersValues') as { [key: string]: string }, },
sorterActive: inject('sorterActive') as { id: string | number; dir: number },
// journalFilterActive: inject('journalFilterActive') as JournalFilter, setup() {
filterList: inject('filterList') as JournalFilter[] | undefined, return {
}; searchersValues: inject('searchersValues') as { [key: string]: string },
}, sorterActive: inject('sorterActive') as { id: string | number; dir: number },
filterList: inject('filterList') as JournalFilter[] | undefined
computed: { };
driverStatsName() { },
return this.store.driverStatsName;
}, computed: {
translatedSorterOptions() {
translatedSorterOptions() { return this.$props.sorterOptionIds.map((id) => ({
return this.$props.sorterOptionIds.map((id) => ({ id,
id, value: this.$t(`options.sort-${id}`)
value: this.$t(`options.sort-${id}`), }));
})); }
}, },
},
watch: {
watch: { async driverStatsName() {
async driverStatsName(value: string) { await this.fetchDriverStats();
await this.fetchDriverStats();
// if (value) this.store.currentStatsTab = 'driver';
// if (value) this.store.currentStatsTab = 'driver'; },
},
async 'searchersValues.search-driver'(value: string | undefined) {
async 'searchersValues.search-driver'(value: string | undefined) { clearTimeout(this.searchTimeout);
clearTimeout(this.searchTimeout);
if (!value || value == '') return;
if (!value || value == '') return; if (value.length < 3) return;
if (value.length < 3) return;
this.startSearchTimeout('driver', value);
this.startSearchTimeout('driver', value); },
},
async 'searchersValues.search-dispatcher'(value: string | undefined) {
async 'searchersValues.search-dispatcher'(value: string | undefined) { if (!value || value == '') return;
if (!value || value == '') return; if (value.length < 3) return;
if (value.length < 3) return;
this.startSearchTimeout('dispatcher', value);
this.startSearchTimeout('dispatcher', value); }
}, },
},
methods: {
methods: { async fetchDriverStats() {
async fetchDriverStats() { this.store.driverStatsData = undefined;
this.store.driverStatsData = undefined;
if (!this.store.driverStatsName) {
if (!this.store.driverStatsName) { this.store.driverStatsStatus = DataStatus.Initialized;
this.store.driverStatsStatus = DataStatus.Initialized; return;
return; }
}
try {
try { this.store.driverStatsStatus = DataStatus.Loading;
this.store.driverStatsStatus = DataStatus.Loading;
const statsData: DriverStatsAPIData = await (
const statsData: DriverStatsAPIData = await ( await axios.get(
await axios.get(`${URLs.stacjownikAPI}/api/getDriverInfo?name=${this.store.driverStatsName}`) `${URLs.stacjownikAPI}/api/getDriverInfo?name=${this.store.driverStatsName}`
).data; )
).data;
this.store.driverStatsData = statsData;
this.store.driverStatsStatus = DataStatus.Loaded; this.store.driverStatsData = statsData;
} catch (error) { this.store.driverStatsStatus = DataStatus.Loaded;
this.store.driverStatsStatus = DataStatus.Error; } catch (error) {
console.error('Ups! Wystąpił błąd przy próbie pobrania statystyk maszynisty! :/'); this.store.driverStatsStatus = DataStatus.Error;
} console.error('Ups! Wystąpił błąd przy próbie pobrania statystyk maszynisty! :/');
}, }
},
refreshData() {
this.$emit('onRefreshData'); refreshData() {
}, this.$emit('onRefreshData');
},
startSearchTimeout(type: 'driver' | 'dispatcher', value: string) {
if (this[`${type}Suggestions`].includes(value)) return; startSearchTimeout(type: 'driver' | 'dispatcher', value: string) {
if (this[`${type}Suggestions`].includes(value)) return;
window.clearTimeout(this.searchTimeout);
window.clearTimeout(this.searchTimeout);
this.searchTimeout = setTimeout(async () => {
try { this.searchTimeout = setTimeout(async () => {
const suggestions: string[] = await ( try {
await axios.get(`${URLs.stacjownikAPI}/api/get${type}Suggestions?name=${value}`) const suggestions: string[] = await (
).data; await axios.get(`${URLs.stacjownikAPI}/api/get${type}Suggestions?name=${value}`)
).data;
this[`${type}Suggestions`] = suggestions;
} catch (error) { this[`${type}Suggestions`] = suggestions;
this[`${type}Suggestions`] = []; } catch (error) {
} this[`${type}Suggestions`] = [];
}, 450); }
}, }, 450);
},
// Override keyMixin function
onKeyDownFunction() { // Override keyMixin function
this.showOptions = !this.showOptions; onKeyDownFunction() {
this.showOptions = !this.showOptions;
this.$nextTick(() => {
if (this.showOptions) (this.$refs['button'] as HTMLButtonElement)?.focus(); this.$nextTick(() => {
}); if (this.showOptions) (this.$refs['button'] as HTMLButtonElement)?.focus();
}, });
},
onSorterChange(item: { id: string | number; value: string }) {
this.sorterActive.id = item.id; onSorterChange(item: { id: string | number; value: string }) {
this.sorterActive.dir = -1; this.sorterActive.id = item.id;
this.$emit('onSearchConfirm'); this.sorterActive.dir = -1;
}, this.$emit('onSearchConfirm');
},
onFilterChange(filter: JournalFilter) {
// this.journalFilterActive = filter; onFilterChange(filter: JournalFilter) {
this.filterList?.filter((f) => f.filterSection === filter.filterSection).forEach((f) => (f.isActive = false)); // this.journalFilterActive = filter;
filter.isActive = true; this.filterList
?.filter((f) => f.filterSection === filter.filterSection)
this.$emit('onSearchConfirm'); .forEach((f) => (f.isActive = false));
}, filter.isActive = true;
onInputClear(id: any) { this.$emit('onSearchConfirm');
this.searchersValues[id] = ''; },
this.$emit('onSearchConfirm');
}, onInputClear(id: any) {
this.searchersValues[id] = '';
onSearchConfirm() { this.$emit('onSearchConfirm');
this.$emit('onSearchConfirm'); },
},
onSearchConfirm() {
onSearchButtonConfirm() { this.$emit('onSearchConfirm');
this.showOptions = false; },
this.$emit('onSearchConfirm');
}, onSearchButtonConfirm() {
this.showOptions = false;
onResetButtonClick() { this.$emit('onSearchConfirm');
this.$emit('onOptionsReset'); },
},
}, onResetButtonClick() {
}); this.$emit('onOptionsReset');
</script> }
}
<style lang="scss" scoped> });
@import '../../styles/filters_options.scss'; </script>
</style>
<style lang="scss" scoped>
@import '../../styles/filters_options.scss';
</style>
+10 -7
View File
@@ -3,6 +3,7 @@
<div class="tabs"> <div class="tabs">
<button <button
v-for="tab in data.tabs" v-for="tab in data.tabs"
:key="tab.name"
class="btn--filled" class="btn--filled"
:data-selected="tab.name == store.currentStatsTab && areStatsOpen" :data-selected="tab.name == store.currentStatsTab && areStatsOpen"
:data-inactive="tab.inactive" :data-inactive="tab.inactive"
@@ -16,7 +17,10 @@
<div class="stats-tab" v-show="areStatsOpen"> <div class="stats-tab" v-show="areStatsOpen">
<keep-alive> <keep-alive>
<JournalDailyStats v-if="store.currentStatsTab == 'daily'" @toggleStatsOpen="toggleStatsOpen" /> <JournalDailyStats
v-if="store.currentStatsTab == 'daily'"
@toggleStatsOpen="toggleStatsOpen"
/>
<JournalDriverStats v-else-if="store.currentStatsTab == 'driver'" /> <JournalDriverStats v-else-if="store.currentStatsTab == 'driver'" />
</keep-alive> </keep-alive>
</div> </div>
@@ -24,7 +28,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, KeepAlive, onMounted, reactive, Ref, ref, watch } from 'vue'; import { computed, onMounted, reactive, Ref, ref, watch } from 'vue';
import { useStore } from '../../store/store'; import { useStore } from '../../store/store';
import JournalDailyStats from './DailyStats.vue'; import JournalDailyStats from './DailyStats.vue';
import JournalDriverStats from './JournalDriverStats.vue'; import JournalDriverStats from './JournalDriverStats.vue';
@@ -44,14 +48,14 @@ let data = reactive({
tabs: [ tabs: [
{ {
name: 'daily', name: 'daily',
titlePath: 'journal.daily-stats-title', titlePath: 'journal.daily-stats-title'
}, },
{ {
name: 'driver', name: 'driver',
titlePath: 'journal.driver-stats-title', titlePath: 'journal.driver-stats-title'
// inactive: true, // inactive: true,
}, }
] as { name: TStatTab; titlePath: string; inactive?: boolean }[], ] as { name: TStatTab; titlePath: string; inactive?: boolean }[]
}); });
// Methods // Methods
@@ -115,4 +119,3 @@ onMounted(() => {
} }
} }
</style> </style>
@@ -1,82 +1,83 @@
<template> <template>
<div> <div>
<transition name="status-anim" mode="out-in"> <transition name="status-anim" mode="out-in">
<div :key="dataStatus"> <div :key="dataStatus">
<div class="journal_warning" v-if="store.isOffline"> <div class="journal_warning" v-if="store.isOffline">
{{ $t('app.offline') }} {{ $t('app.offline') }}
</div> </div>
<Loading v-else-if="dataStatus == DataStatus.Loading" /> <Loading v-else-if="dataStatus == DataStatus.Loading" />
<div v-else-if="dataStatus == DataStatus.Error" class="journal_warning error"> <div v-else-if="dataStatus == DataStatus.Error" class="journal_warning error">
{{ $t('app.error') }} {{ $t('app.error') }}
</div> </div>
<div v-else-if="timetableHistory.length == 0" class="journal_warning"> <div v-else-if="timetableHistory.length == 0" class="journal_warning">
{{ $t('app.no-result') }} {{ $t('app.no-result') }}
</div> </div>
<div v-else> <div v-else>
<TimetableHistoryList :timetableHistory="timetableHistory" /> <TimetableHistoryList :timetableHistory="timetableHistory" />
<AddDataButton <AddDataButton
:list="timetableHistory" :list="timetableHistory"
:scrollDataLoaded="scrollDataLoaded" :scrollDataLoaded="scrollDataLoaded"
:scrollNoMoreData="scrollNoMoreData" :scrollNoMoreData="scrollNoMoreData"
@addHistoryData="addHistoryData" @addHistoryData="addHistoryData"
/> />
</div> </div>
</div> </div>
</transition> </transition>
<div class="journal_warning" v-if="scrollNoMoreData">{{ $t('journal.no-further-data') }}</div> <div class="journal_warning" v-if="scrollNoMoreData">{{ $t('journal.no-further-data') }}</div>
<div class="journal_warning" v-else-if="!scrollDataLoaded">{{ $t('journal.loading-further-data') }}</div> <div class="journal_warning" v-else-if="!scrollDataLoaded">
</div> {{ $t('journal.loading-further-data') }}
</template> </div>
</div>
<script lang="ts"> </template>
import { defineComponent, PropType } from 'vue';
import { DataStatus } from '../../../scripts/enums/DataStatus'; <script lang="ts">
import { TimetableHistory } from '../../../scripts/interfaces/api/TimetablesAPIData'; import { defineComponent, PropType } from 'vue';
import { useStore } from '../../../store/store'; import { DataStatus } from '../../../scripts/enums/DataStatus';
import { TimetableHistory } from '../../../scripts/interfaces/api/TimetablesAPIData';
import Loading from '../../Global/Loading.vue'; import { useStore } from '../../../store/store';
import ProgressBar from '../../Global/ProgressBar.vue';
import AddDataButton from '../../Global/AddDataButton.vue'; import Loading from '../../Global/Loading.vue';
import TimetableHistoryList from './TimetableHistoryList.vue'; import AddDataButton from '../../Global/AddDataButton.vue';
import TimetableHistoryList from './TimetableHistoryList.vue';
export default defineComponent({
components: { ProgressBar, Loading, AddDataButton, TimetableHistoryList }, export default defineComponent({
components: { Loading, AddDataButton, TimetableHistoryList },
props: {
timetableHistory: { props: {
type: Array as PropType<TimetableHistory[]>, timetableHistory: {
required: true, type: Array as PropType<TimetableHistory[]>,
}, required: true
scrollNoMoreData: { },
type: Boolean, scrollNoMoreData: {
}, type: Boolean
scrollDataLoaded: { },
type: Boolean, scrollDataLoaded: {
}, type: Boolean
addHistoryData: { },
type: Function as PropType<() => void>, addHistoryData: {
}, type: Function as PropType<() => void>
dataStatus: { },
type: Number as PropType<DataStatus>, dataStatus: {
}, type: Number as PropType<DataStatus>
}, }
},
data() {
return { data() {
DataStatus, return {
store: useStore(), DataStatus,
}; store: useStore()
}, };
}); }
</script> });
</script>
<style lang="scss" scoped>
@import '../../../styles/JournalSection.scss'; <style lang="scss" scoped>
@import '../../../styles/animations.scss'; @import '../../../styles/JournalSection.scss';
</style> @import '../../../styles/animations.scss';
</style>
@@ -18,7 +18,11 @@
<span class="badge"> <span class="badge">
<span>{{ $t('journal.stock-length') }}</span> <span>{{ $t('journal.stock-length') }}</span>
<span> <span>
{{ currentHistoryIndex == 0 ? timetable.stockLength : stockHistory[currentHistoryIndex].stockLength || timetable.stockLength }}m {{
currentHistoryIndex == 0
? timetable.stockLength
: stockHistory[currentHistoryIndex].stockLength || timetable.stockLength
}}m
</span> </span>
</span> </span>
@@ -26,7 +30,11 @@
<span>{{ $t('journal.stock-mass') }}</span> <span>{{ $t('journal.stock-mass') }}</span>
<span> <span>
{{ {{
Math.floor((currentHistoryIndex == 0 ? timetable.stockMass! : stockHistory[currentHistoryIndex].stockMass || timetable.stockMass) / 1000) Math.floor(
(currentHistoryIndex == 0
? timetable.stockMass!
: stockHistory[currentHistoryIndex].stockMass || timetable.stockMass) / 1000
)
}}t }}t
</span> </span>
</span> </span>
@@ -34,13 +42,26 @@
<!-- Historia zmian w składzie --> <!-- Historia zmian w składzie -->
<div class="stock-history" v-if="stockHistory.length > 1"> <div class="stock-history" v-if="stockHistory.length > 1">
<button class="btn--action" v-for="(sh, i) in stockHistory" :data-checked="i == currentHistoryIndex" @click.stop="currentHistoryIndex = i"> <button
v-for="(sh, i) in stockHistory"
:key="i"
class="btn--action"
:data-checked="i == currentHistoryIndex"
@click.stop="currentHistoryIndex = i"
>
{{ sh.updatedAt }} {{ sh.updatedAt }}
</button> </button>
</div> </div>
<!-- <StockList :trainStockList="currentHistoryIndex == 0 ? timetable.stockString : stockHistory[currentHistoryIndex].stockString).split(';')" /> --> <!-- <StockList :trainStockList="currentHistoryIndex == 0 ? timetable.stockString : stockHistory[currentHistoryIndex].stockString).split(';')" /> -->
<StockList :trainStockList="(currentHistoryIndex == 0 ? timetable.stockString : stockHistory[currentHistoryIndex].stockString).split(';') " /> <StockList
:trainStockList="
(currentHistoryIndex == 0
? timetable.stockString
: stockHistory[currentHistoryIndex].stockString
).split(';')
"
/>
<!-- <ul class="stock-list"> <!-- <ul class="stock-list">
<li <li
@@ -58,24 +79,24 @@
import { PropType, defineComponent } from 'vue'; import { PropType, defineComponent } from 'vue';
import { TimetableHistory } from '../../../scripts/interfaces/api/TimetablesAPIData'; import { TimetableHistory } from '../../../scripts/interfaces/api/TimetablesAPIData';
import imageMixin from '../../../mixins/imageMixin'; import imageMixin from '../../../mixins/imageMixin';
import TrainThumbnail from '../../Global/TrainThumbnail.vue';
import StockList from '../../Global/StockList.vue'; import StockList from '../../Global/StockList.vue';
export default defineComponent({ export default defineComponent({
mixins: [imageMixin], mixins: [imageMixin],
components: { StockList },
props: { props: {
showExtraInfo: { showExtraInfo: {
type: Boolean, type: Boolean,
required: true, required: true
}, },
timetable: { timetable: {
type: Object as PropType<TimetableHistory>, type: Object as PropType<TimetableHistory>,
required: true, required: true
}, }
}, },
data() { data() {
return { return {
currentHistoryIndex: 0, currentHistoryIndex: 0
}; };
}, },
computed: { computed: {
@@ -88,22 +109,21 @@ export default defineComponent({
return { return {
updatedAt: new Date(Number(historyData[0])).toLocaleTimeString(this.$i18n.locale, { updatedAt: new Date(Number(historyData[0])).toLocaleTimeString(this.$i18n.locale, {
hour: '2-digit', hour: '2-digit',
minute: '2-digit', minute: '2-digit'
}), }),
stockString: historyData[1], stockString: historyData[1],
stockMass: Number(historyData[2]) || undefined, stockMass: Number(historyData[2]) || undefined,
stockLength: Number(historyData[3]) || undefined, stockLength: Number(historyData[3]) || undefined
}; };
}); });
}, }
}, },
methods: { methods: {
onImageError(e: Event) { onImageError(e: Event) {
const imageEl = e.target as HTMLImageElement; const imageEl = e.target as HTMLImageElement;
imageEl.src = this.getImage('unknown.png'); imageEl.src = this.getImage('unknown.png');
}, }
}, }
components: { TrainThumbnail, StockList },
}); });
</script> </script>
@@ -45,7 +45,7 @@
:class="{ :class="{
fulfilled: timetable.fulfilled, fulfilled: timetable.fulfilled,
terminated: timetable.terminated && !timetable.fulfilled, terminated: timetable.terminated && !timetable.fulfilled,
active: !timetable.terminated, active: !timetable.terminated
}" }"
> >
{{ {{
@@ -74,8 +74,8 @@ export default defineComponent({
props: { props: {
timetable: { timetable: {
type: Object as PropType<TimetableHistory>, type: Object as PropType<TimetableHistory>,
required: true, required: true
}, }
}, },
methods: { methods: {
@@ -83,8 +83,8 @@ export default defineComponent({
if (timetable?.terminated) return; if (timetable?.terminated) return;
this.selectModalTrain(timetable.driverName + timetable.trainNo.toString(), target); this.selectModalTrain(timetable.driverName + timetable.trainNo.toString(), target);
}, }
}, }
}); });
</script> </script>
@@ -48,19 +48,19 @@ export default defineComponent({
props: { props: {
timetableHistory: { timetableHistory: {
type: Array as PropType<TimetableHistory[]>, type: Array as PropType<TimetableHistory[]>,
required: true, required: true
}, }
}, },
computed: { computed: {
computedTimetableHistory() { computedTimetableHistory() {
return this.timetableHistory.map((timetable) => ({ return this.timetableHistory.map((timetable) => ({
timetable, timetable,
showExtraInfo: ref(false), showExtraInfo: ref(false)
})); }));
}, }
}, },
methods: {}, methods: {},
components: { TimetableGeneral, TimetableStops, TimetableStatus, TimetableExtra }, components: { TimetableGeneral, TimetableStops, TimetableStatus, TimetableExtra }
}); });
</script> </script>
@@ -6,13 +6,19 @@
/> />
<span> <span>
<span :style="{ color: timetable.fulfilled ? 'lightgreen' : timetable.terminated ? 'salmon' : '' }"> <span
:style="{
color: timetable.fulfilled ? 'lightgreen' : timetable.terminated ? 'salmon' : ''
}"
>
{{ timetable.currentDistance + ' km' }} {{ timetable.currentDistance + ' km' }}
</span> </span>
<span> / </span> <span> / </span>
<span class="text--primary">{{ timetable.routeDistance }} km</span> <span class="text--primary">{{ timetable.routeDistance }} km</span>
| |
<span class="text--grayed">{{ timetable.confirmedStopsCount }}/{{ timetable.allStopsCount }}</span> <span class="text--grayed"
>{{ timetable.confirmedStopsCount }}/{{ timetable.allStopsCount }}</span
>
</span> </span>
<span class="text--grayed" v-if="timetable.currentSceneryName"> <span class="text--grayed" v-if="timetable.currentSceneryName">
@@ -46,9 +52,9 @@ export default defineComponent({
props: { props: {
timetable: { timetable: {
type: Object as PropType<TimetableHistory>, type: Object as PropType<TimetableHistory>,
required: true, required: true
}, }
}, }
}); });
</script> </script>
@@ -38,8 +38,8 @@ export default defineComponent({
timetable: { timetable: {
type: Object as PropType<TimetableHistory>, type: Object as PropType<TimetableHistory>,
required: true, required: true
}, }
}, },
computed: { computed: {
@@ -65,12 +65,18 @@ export default defineComponent({
if (i == 0) return { stopName, html: beginDateHTML, confirmed }; if (i == 0) return { stopName, html: beginDateHTML, confirmed };
if (i == stopNames.length - 1) return { stopName, html: endDateHTML, confirmed }; if (i == stopNames.length - 1) return { stopName, html: endDateHTML, confirmed };
const departureDateScheduled = this.stringToDate(timetable.checkpointDeparturesScheduled?.at(i)); const departureDateScheduled = this.stringToDate(
timetable.checkpointDeparturesScheduled?.at(i)
);
const departureDateReal = this.stringToDate(timetable.checkpointDepartures?.at(i)); const departureDateReal = this.stringToDate(timetable.checkpointDepartures?.at(i));
const arrivalDateScheduled = this.stringToDate(timetable.checkpointArrivalsScheduled?.at(i)); const arrivalDateScheduled = this.stringToDate(
timetable.checkpointArrivalsScheduled?.at(i)
);
const arrivalDateReal = this.stringToDate(timetable.checkpointArrivals?.at(i)); const arrivalDateReal = this.stringToDate(timetable.checkpointArrivals?.at(i));
const arrivalHTML = const arrivalHTML =
(arrivalDateReal && arrivalDateScheduled && arrivalDateReal?.getTime() != arrivalDateScheduled?.getTime() (arrivalDateReal &&
arrivalDateScheduled &&
arrivalDateReal?.getTime() != arrivalDateScheduled?.getTime()
? `<s class="text--grayed">${this.parseDateToTimeString(arrivalDateScheduled)}</s> ` ? `<s class="text--grayed">${this.parseDateToTimeString(arrivalDateScheduled)}</s> `
: '') + this.parseDateToTimeString(arrivalDateReal || arrivalDateScheduled); : '') + this.parseDateToTimeString(arrivalDateReal || arrivalDateScheduled);
const departureHTML = const departureHTML =
@@ -83,8 +89,8 @@ export default defineComponent({
if (html) html = ` (${html})`; if (html) html = ` (${html})`;
return { stopName, html, confirmed }; return { stopName, html, confirmed };
}); });
}, }
}, }
}); });
</script> </script>
@@ -1,143 +1,150 @@
<template> <template>
<section class="scenery-table-section"> <section class="scenery-table-section">
<Loading v-if="dataStatus != DataStatus.Loaded && historyList.length == 0" /> <Loading v-if="dataStatus != DataStatus.Loaded && historyList.length == 0" />
<div class="no-history" v-else-if="historyList.length == 0">{{ $t('scenery.history-list-empty') }}</div>
<div class="no-history" v-else-if="historyList.length == 0">
<table class="scenery-history-table" v-else="historyList.length"> {{ $t('scenery.history-list-empty') }}
<thead> </div>
<th>{{ $t('scenery.dispatchers-history-hash') }}</th>
<th>{{ $t('scenery.dispatchers-history-dispatcher') }}</th> <table class="scenery-history-table" v-else>
<th>{{ $t('scenery.dispatchers-history-level') }}</th> <thead>
<th>{{ $t('scenery.dispatchers-history-rate') }}</th> <th>{{ $t('scenery.dispatchers-history-hash') }}</th>
<th>{{ $t('scenery.dispatchers-history-date') }}</th> <th>{{ $t('scenery.dispatchers-history-dispatcher') }}</th>
</thead> <th>{{ $t('scenery.dispatchers-history-level') }}</th>
<th>{{ $t('scenery.dispatchers-history-rate') }}</th>
<tbody> <th>{{ $t('scenery.dispatchers-history-date') }}</th>
<tr v-for="historyItem in historyList"> </thead>
<td>#{{ historyItem.stationHash }}</td>
<td> <tbody>
<router-link :to="`/journal/dispatchers?dispatcherName=${historyItem.dispatcherName}`"> <tr v-for="historyItem in historyList" :key="historyItem.id">
<b>{{ historyItem.dispatcherName }}</b> <td>#{{ historyItem.stationHash }}</td>
</router-link> <td>
</td> <router-link :to="`/journal/dispatchers?dispatcherName=${historyItem.dispatcherName}`">
<td> <b>{{ historyItem.dispatcherName }}</b>
<b </router-link>
v-if="historyItem.dispatcherLevel !== null" </td>
class="level-badge dispatcher" <td>
:style="calculateExpStyle(historyItem.dispatcherLevel, historyItem.dispatcherIsSupporter)" <b
> v-if="historyItem.dispatcherLevel !== null"
{{ historyItem.dispatcherLevel >= 2 ? historyItem.dispatcherLevel : 'L' }} class="level-badge dispatcher"
</b> :style="
</td> calculateExpStyle(historyItem.dispatcherLevel, historyItem.dispatcherIsSupporter)
<td class="text--primary"> "
<b>{{ historyItem.dispatcherRate }}</b> >
</td> {{ historyItem.dispatcherLevel >= 2 ? historyItem.dispatcherLevel : 'L' }}
<td style="min-width: 300px"> </b>
<div v-if="historyItem.timestampTo"> </td>
<b>{{ $d(historyItem.timestampFrom) }}</b> <td class="text--primary">
<b>{{ historyItem.dispatcherRate }}</b>
{{ timestampToString(historyItem.timestampFrom) }} </td>
- {{ timestampToString(historyItem.timestampTo) }} ({{ calculateDuration(historyItem.currentDuration) }}) <td style="min-width: 300px">
</div> <div v-if="historyItem.timestampTo">
<b>{{ $d(historyItem.timestampFrom) }}</b>
<div class="dispatcher-online" v-else>
{{ $t('journal.online-since') }} {{ timestampToString(historyItem.timestampFrom) }}
<b>{{ timestampToString(historyItem.timestampFrom) }}</b> - {{ timestampToString(historyItem.timestampTo) }} ({{
({{ calculateDuration(historyItem.currentDuration) }}) calculateDuration(historyItem.currentDuration)
</div> }})
</td> </div>
</tr>
</tbody> <div class="dispatcher-online" v-else>
</table> {{ $t('journal.online-since') }}
</section> <b>{{ timestampToString(historyItem.timestampFrom) }}</b>
({{ calculateDuration(historyItem.currentDuration) }})
<div class="bottom-info"> </div>
<button class="btn btn--option" v-if="historyList.length > 0" @click="navigateToHistory"> </td>
{{ $t('scenery.bottom-info') }} </tr>
</button> </tbody>
</div> </table>
</template> </section>
<script lang="ts"> <div class="bottom-info">
import axios from 'axios'; <button class="btn btn--option" v-if="historyList.length > 0" @click="navigateToHistory">
import { defineComponent, PropType } from 'vue'; {{ $t('scenery.bottom-info') }}
import dateMixin from '../../mixins/dateMixin'; </button>
import { DataStatus } from '../../scripts/enums/DataStatus'; </div>
import { DispatcherHistory } from '../../scripts/interfaces/api/DispatchersAPIData'; </template>
import Station from '../../scripts/interfaces/Station';
import { URLs } from '../../scripts/utils/apiURLs'; <script lang="ts">
import Loading from '../Global/Loading.vue'; import axios from 'axios';
import styleMixin from '../../mixins/styleMixin'; import { defineComponent, PropType } from 'vue';
import listObserverMixin from '../../mixins/listObserverMixin'; import dateMixin from '../../mixins/dateMixin';
import { DataStatus } from '../../scripts/enums/DataStatus';
export default defineComponent({ import { DispatcherHistory } from '../../scripts/interfaces/api/DispatchersAPIData';
name: 'SceneryDispatchersHistory', import Station from '../../scripts/interfaces/Station';
mixins: [dateMixin, styleMixin, listObserverMixin], import { URLs } from '../../scripts/utils/apiURLs';
props: { import Loading from '../Global/Loading.vue';
station: { import styleMixin from '../../mixins/styleMixin';
type: Object as PropType<Station>, import listObserverMixin from '../../mixins/listObserverMixin';
required: true,
}, export default defineComponent({
}, name: 'SceneryDispatchersHistory',
mixins: [dateMixin, styleMixin, listObserverMixin],
data() { components: { Loading },
return { props: {
historyList: [] as DispatcherHistory[], station: {
dataStatus: DataStatus.Loading, type: Object as PropType<Station>,
DataStatus, required: true
}; }
}, },
async activated() { data() {
// if (this.historyList.length == 0) { return {
const fetchedHistory = await this.fetchAPIData(); historyList: [] as DispatcherHistory[],
if (fetchedHistory) this.historyList = fetchedHistory; dataStatus: DataStatus.Loading,
// } DataStatus
}, };
},
methods: {
async fetchAPIData(countFrom = 0, countLimit = 30): Promise<DispatcherHistory[] | null> { async activated() {
try { // if (this.historyList.length == 0) {
this.dataStatus = DataStatus.Loading; const fetchedHistory = await this.fetchAPIData();
if (fetchedHistory) this.historyList = fetchedHistory;
const requestString = `${URLs.stacjownikAPI}/api/getDispatchers?stationName=${this.station.name}&countFrom=${countFrom}&countLimit=${countLimit}`; // }
const historyAPIData: DispatcherHistory[] = await (await axios.get(requestString)).data; },
this.dataStatus = DataStatus.Loaded; methods: {
return historyAPIData; async fetchAPIData(countFrom = 0, countLimit = 30): Promise<DispatcherHistory[] | null> {
} catch (error) { try {
this.dataStatus = DataStatus.Error; this.dataStatus = DataStatus.Loading;
console.error(error);
return null; const requestString = `${URLs.stacjownikAPI}/api/getDispatchers?stationName=${this.station.name}&countFrom=${countFrom}&countLimit=${countLimit}`;
} const historyAPIData: DispatcherHistory[] = await (await axios.get(requestString)).data;
},
navigateToHistory() { this.dataStatus = DataStatus.Loaded;
this.$router.push(`/journal/dispatchers?sceneryName=${this.station.name}`); return historyAPIData;
}, } catch (error) {
}, this.dataStatus = DataStatus.Error;
components: { Loading }, console.error(error);
}); return null;
</script> }
},
<style lang="scss" scoped> navigateToHistory() {
@import '../../styles/responsive.scss'; this.$router.push(`/journal/dispatchers?sceneryName=${this.station.name}`);
@import '../../styles/sceneryViewTables.scss'; }
}
.level-badge { });
margin: 0 auto; </script>
}
<style lang="scss" scoped>
.dispatcher-online { @import '../../styles/responsive.scss';
color: springgreen; @import '../../styles/sceneryViewTables.scss';
}
.level-badge {
@include smallScreen { margin: 0 auto;
.history-list { }
font-size: 1.1em;
} .dispatcher-online {
.list-item { color: springgreen;
align-items: center; }
flex-direction: column;
} @include smallScreen {
} .history-list {
</style> font-size: 1.1em;
}
.list-item {
align-items: center;
flex-direction: column;
}
}
</style>
+5 -6
View File
@@ -13,16 +13,16 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { PropType, defineComponent } from 'vue';
import Station from '../../scripts/interfaces/Station'; import Station from '../../scripts/interfaces/Station';
export default defineComponent({ export default defineComponent({
props: { props: {
station: { station: {
type: Object as () => Station, type: Object as PropType<Station>,
default: {}, required: true
}, }
}, }
}); });
</script> </script>
@@ -52,4 +52,3 @@ export default defineComponent({
font-size: 1.2em; font-size: 1.2em;
} }
</style> </style>
+162 -139
View File
@@ -1,139 +1,162 @@
<template> <template>
<div class="scenery-info"> <div class="scenery-info">
<section v-if="!timetableOnly"> <section v-if="!timetableOnly">
<div class="scenery-info-general" v-if="station.generalInfo"> <div class="scenery-info-general" v-if="station.generalInfo">
<SceneryInfoIcons :station="station" /> <SceneryInfoIcons :station="station" />
<div class="scenery-general-list"> <div class="scenery-general-list">
<span> <span>
<b>{{ $t('availability.title') }}:</b> {{ $t(`availability.${station.generalInfo.availability}`) }} <b>{{ $t('availability.title') }}:</b>
{{ $t(`availability.${station.generalInfo.availability}`) }}
<span v-if="station.generalInfo.reqLevel > -1">
- {{ $t('scenery.req-level', { lvl: station.generalInfo.reqLevel }, station.generalInfo.reqLevel) }} <span v-if="station.generalInfo.reqLevel > -1">
</span> -
</span> {{
$t(
<span> 'scenery.req-level',
&bull; <b>{{ $t('controls.title') }}:</b> {{ $t(`controls.${station.generalInfo.controlType}`) }} { lvl: station.generalInfo.reqLevel },
</span> station.generalInfo.reqLevel
)
<span> }}
&bull; <b>{{ $t('signals.title') }}:</b> {{ $t(`signals.${station.generalInfo.signalType}`) }} </span>
</span> </span>
<span v-if="station.generalInfo.lines"> <span>
&bull; <b>{{ $t('scenery.lines-title') }}:</b> {{ station.generalInfo.lines }} &bull; <b>{{ $t('controls.title') }}:</b>
</span> {{ $t(`controls.${station.generalInfo.controlType}`) }}
<span v-if="station.generalInfo.project"> </span>
&bull; <b>{{ $t('scenery.project-title') }}: </b>
<a style="color: salmon; text-decoration: underline; font-weight: bold" :href="station.generalInfo.projectUrl" target="_blank"> <span>
{{ station.generalInfo.project }} &bull; <b>{{ $t('signals.title') }}:</b>
</a> {{ $t(`signals.${station.generalInfo.signalType}`) }}
</span> </span>
</div>
<span v-if="station.generalInfo.lines">
<SceneryInfoRoutes :station="station" /> &bull; <b>{{ $t('scenery.lines-title') }}:</b> {{ station.generalInfo.lines }}
</span>
<div class="scenery-authors" v-if="station.generalInfo.authors && station.generalInfo.authors.length > 0"> <span v-if="station.generalInfo.project">
<b> {{ $t('scenery.authors-title', { authors: station.generalInfo.authors.length }, station.generalInfo.authors.length) }}: </b> &bull; <b>{{ $t('scenery.project-title') }}: </b>
{{ station.generalInfo.authors.join(', ') }} <a
</div> style="color: salmon; text-decoration: underline; font-weight: bold"
</div> :href="station.generalInfo.projectUrl"
target="_blank"
<div style="margin: 2em 0; height: 2px; background-color: white"></div> >
{{ station.generalInfo.project }}
<!-- info dispatcher --> </a>
<SceneryInfoDispatcher :station="station" :onlineFrom="onlineFrom" /> </span>
</div>
<div class="info-lists">
<!-- user list --> <SceneryInfoRoutes :station="station" />
<SceneryInfoUserList :station="station" />
<div
<!-- spawn list --> class="scenery-authors"
<SceneryInfoSpawnList :station="station" /> v-if="station.generalInfo.authors && station.generalInfo.authors.length > 0"
</div> >
</section> <b>
</div> {{
</template> $t(
'scenery.authors-title',
<script lang="ts"> { authors: station.generalInfo.authors.length },
import { defineComponent } from '@vue/runtime-core'; station.generalInfo.authors.length
)
import SceneryInfoDispatcher from './SceneryInfo/SceneryInfoDispatcher.vue'; }}:
import SceneryInfoIcons from './SceneryInfo/SceneryInfoIcons.vue'; </b>
import SceneryInfoStats from './SceneryInfo/SceneryInfoStats.vue'; {{ station.generalInfo.authors.join(', ') }}
import SceneryInfoUserList from './SceneryInfo/SceneryInfoUserList.vue'; </div>
import SceneryInfoSpawnList from './SceneryInfo/SceneryInfoSpawnList.vue'; </div>
import SceneryInfoRoutes from './SceneryInfo/SceneryInfoRoutes.vue';
import Station from '../../scripts/interfaces/Station'; <div style="margin: 2em 0; height: 2px; background-color: white"></div>
export default defineComponent({ <!-- info dispatcher -->
components: { <SceneryInfoDispatcher :station="station" :onlineFrom="onlineFrom" />
SceneryInfoDispatcher,
SceneryInfoIcons, <div class="info-lists">
SceneryInfoStats, <!-- user list -->
SceneryInfoUserList, <SceneryInfoUserList :station="station" />
SceneryInfoSpawnList,
SceneryInfoRoutes, <!-- spawn list -->
}, <SceneryInfoSpawnList :station="station" />
props: { </div>
station: { </section>
type: Object as () => Station, </div>
default: {}, </template>
},
<script lang="ts">
timetableOnly: Boolean, import { PropType, defineComponent } from 'vue';
},
import SceneryInfoDispatcher from './SceneryInfo/SceneryInfoDispatcher.vue';
data: () => ({ import SceneryInfoIcons from './SceneryInfo/SceneryInfoIcons.vue';
onlineFrom: -1, import SceneryInfoUserList from './SceneryInfo/SceneryInfoUserList.vue';
}), import SceneryInfoSpawnList from './SceneryInfo/SceneryInfoSpawnList.vue';
}); import SceneryInfoRoutes from './SceneryInfo/SceneryInfoRoutes.vue';
</script> import Station from '../../scripts/interfaces/Station';
<style lang="scss"> export default defineComponent({
@import '../../styles/responsive.scss'; components: {
@import '../../styles/badge.scss'; SceneryInfoDispatcher,
SceneryInfoIcons,
h3.section-header { SceneryInfoUserList,
margin: 0.5em 0; SceneryInfoSpawnList,
padding: 0.3em; SceneryInfoRoutes
},
display: flex; props: {
justify-content: center; station: {
align-items: center; type: Object as PropType<Station>,
required: true
font-size: 1.2em; },
img { timetableOnly: Boolean
width: 1.1em; },
margin-left: 0.5em;
} data: () => ({
} onlineFrom: -1
})
.info-lists { });
display: flex; </script>
flex-wrap: wrap;
justify-content: space-around; <style lang="scss">
@import '../../styles/responsive.scss';
margin-top: 1em; @import '../../styles/badge.scss';
}
h3.section-header {
.scenery-info-general { margin: 0.5em 0;
margin-top: 1em; padding: 0.3em;
}
display: flex;
.scenery-general-list { justify-content: center;
display: flex; align-items: center;
justify-content: center;
flex-wrap: wrap; font-size: 1.2em;
span { img {
margin: 0 0.15em; width: 1.1em;
} margin-left: 0.5em;
} }
}
.scenery-topic a {
font-weight: bold; .info-lists {
} display: flex;
</style> flex-wrap: wrap;
justify-content: space-around;
margin-top: 1em;
}
.scenery-info-general {
margin-top: 1em;
}
.scenery-general-list {
display: flex;
justify-content: center;
flex-wrap: wrap;
span {
margin: 0 0.15em;
}
}
.scenery-topic a {
font-weight: bold;
}
</style>
@@ -3,7 +3,12 @@
<div class="dispatcher" v-if="station.onlineInfo"> <div class="dispatcher" v-if="station.onlineInfo">
<span <span
class="dispatcher_level" class="dispatcher_level"
:style="calculateExpStyle(station.onlineInfo.dispatcherExp, station.onlineInfo.dispatcherIsSupporter)" :style="
calculateExpStyle(
station.onlineInfo.dispatcherExp,
station.onlineInfo.dispatcherIsSupporter
)
"
> >
{{ station.onlineInfo.dispatcherExp > 1 ? station.onlineInfo.dispatcherExp : 'L' }} {{ station.onlineInfo.dispatcherExp > 1 ? station.onlineInfo.dispatcherExp : 'L' }}
</span> </span>
@@ -30,7 +35,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { PropType, defineComponent } from 'vue';
import dateMixin from '../../../mixins/dateMixin'; import dateMixin from '../../../mixins/dateMixin';
import imageMixin from '../../../mixins/imageMixin'; import imageMixin from '../../../mixins/imageMixin';
import routerMixin from '../../../mixins/routerMixin'; import routerMixin from '../../../mixins/routerMixin';
@@ -39,18 +44,18 @@ import Station from '../../../scripts/interfaces/Station';
import StationStatusBadge from '../../Global/StationStatusBadge.vue'; import StationStatusBadge from '../../Global/StationStatusBadge.vue';
export default defineComponent({ export default defineComponent({
mixins: [styleMixin, dateMixin, routerMixin, imageMixin], mixins: [styleMixin, dateMixin, routerMixin, imageMixin],
props: { props: {
station: { station: {
type: Object as () => Station, type: Object as PropType<Station>,
default: {}, required: true
},
onlineFrom: {
type: Number,
default: -1,
},
}, },
components: { StationStatusBadge } onlineFrom: {
type: Number,
default: -1
}
},
components: { StationStatusBadge }
}); });
</script> </script>
@@ -98,4 +103,3 @@ export default defineComponent({
} }
} }
</style> </style>
@@ -76,7 +76,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { PropType, defineComponent } from 'vue';
import imageMixin from '../../../mixins/imageMixin'; import imageMixin from '../../../mixins/imageMixin';
import stationInfoMixin from '../../../mixins/stationInfoMixin'; import stationInfoMixin from '../../../mixins/stationInfoMixin';
import styleMixin from '../../../mixins/styleMixin'; import styleMixin from '../../../mixins/styleMixin';
@@ -86,10 +86,10 @@ export default defineComponent({
mixins: [stationInfoMixin, styleMixin, imageMixin], mixins: [stationInfoMixin, styleMixin, imageMixin],
props: { props: {
station: { station: {
type: Object as () => Station, type: Object as PropType<Station>,
default: {}, required: true
}, }
}, }
}); });
</script> </script>
@@ -118,4 +118,3 @@ export default defineComponent({
} }
} }
</style> </style>
@@ -1,129 +1,142 @@
<template> <template>
<section class="info-routes" v-if="station.generalInfo"> <section class="info-routes" v-if="station.generalInfo">
<div class="routes one-way" v-if="station.generalInfo.routes.oneWay.length > 0"> <div class="routes one-way" v-if="station.generalInfo.routes.oneWay.length > 0">
<b>{{ $t('scenery.one-way-routes') }}</b> <b>{{ $t('scenery.one-way-routes') }}</b>
<ul class="routes-list"> <ul class="routes-list">
<li v-for="route in station.generalInfo.routes.oneWay" @click="setActiveShowLength(route.name)"> <li
<span :class="{ 'no-catenary': !route.catenary, internal: route.isInternal }"> {{ route.name }}</span> v-for="route in station.generalInfo.routes.oneWay"
<span v-if="route.speed" class="speed"> :key="route.name"
{{ activeShowLength.includes(route.name) ? route.length + 'm' : route.speed }} @click="setActiveShowLength(route.name)"
</span> >
<span v-if="route.SBL" class="sbl">SBL</span> <span :class="{ 'no-catenary': !route.catenary, internal: route.isInternal }">
</li> {{ route.name }}</span
</ul> >
</div> <span v-if="route.speed" class="speed">
{{ activeShowLength.includes(route.name) ? route.length + 'm' : route.speed }}
<div class="routes two-way" v-if="station.generalInfo.routes.twoWay.length > 0"> </span>
<b>{{ $t('scenery.two-way-routes') }}</b> <span v-if="route.SBL" class="sbl">SBL</span>
</li>
<ul class="routes-list"> </ul>
<li v-for="(route, i) in station.generalInfo.routes.twoWay" @click="setActiveShowLength(route.name)"> </div>
<span :class="{ 'no-catenary': !route.catenary, internal: route.isInternal }">{{ route.name }}</span>
<span v-if="route.speed" class="speed"> <div class="routes two-way" v-if="station.generalInfo.routes.twoWay.length > 0">
{{ activeShowLength.includes(route.name) ? route.length + 'm' : route.speed }} <b>{{ $t('scenery.two-way-routes') }}</b>
</span>
<span v-if="route.SBL" class="sbl">SBL</span> <ul class="routes-list">
</li> <li
</ul> v-for="route in station.generalInfo.routes.twoWay"
</div> :key="route.name"
</section> @click="setActiveShowLength(route.name)"
</template> >
<span :class="{ 'no-catenary': !route.catenary, internal: route.isInternal }">{{
<script lang="ts"> route.name
import { defineComponent } from 'vue'; }}</span>
import Station from '../../../scripts/interfaces/Station'; <span v-if="route.speed" class="speed">
{{ activeShowLength.includes(route.name) ? route.length + 'm' : route.speed }}
export default defineComponent({ </span>
props: { <span v-if="route.SBL" class="sbl">SBL</span>
station: { </li>
type: Object as () => Station, </ul>
default: {}, </div>
}, </section>
}, </template>
methods: { <script lang="ts">
setActiveShowLength(name: string) { import { PropType, defineComponent } from 'vue';
if (this.activeShowLength.includes(name)) this.activeShowLength.splice(this.activeShowLength.indexOf(name), 1); import Station from '../../../scripts/interfaces/Station';
else this.activeShowLength.push(name);
}, export default defineComponent({
}, props: {
station: {
data() { type: Object as PropType<Station>,
return { required: true
activeShowLength: [] as string[], }
}; },
},
}); methods: {
</script> setActiveShowLength(name: string) {
if (this.activeShowLength.includes(name))
<style lang="scss" scoped> this.activeShowLength.splice(this.activeShowLength.indexOf(name), 1);
.info-routes { else this.activeShowLength.push(name);
display: flex; }
justify-content: center; },
flex-wrap: wrap;
data() {
margin: 1em 0; return {
} activeShowLength: [] as string[]
};
.routes { }
display: flex; });
justify-content: center; </script>
align-items: center;
flex-wrap: wrap; <style lang="scss" scoped>
.info-routes {
padding: 0.25em; display: flex;
} justify-content: center;
flex-wrap: wrap;
ul.routes-list {
margin: 0.45em 0.25em; margin: 1em 0;
display: flex; }
justify-content: center;
flex-wrap: wrap; .routes {
display: flex;
li { justify-content: center;
margin: 0.5em 0.25em; align-items: center;
cursor: pointer; flex-wrap: wrap;
user-select: none; padding: 0.25em;
-moz-user-select: none; }
-webkit-user-select: none;
ul.routes-list {
span { margin: 0.45em 0.25em;
padding: 0.2em 0.25em; display: flex;
background-color: #007599; justify-content: center;
font-weight: bold; flex-wrap: wrap;
&.no-catenary { li {
background-color: #686868; margin: 0.5em 0.25em;
} cursor: pointer;
&.internal { user-select: none;
text-decoration: underline; -moz-user-select: none;
} -webkit-user-select: none;
&.speed { span {
background-color: #404040; padding: 0.2em 0.25em;
color: #cfcfcf; background-color: #007599;
} font-weight: bold;
&.sbl { &.no-catenary {
color: var(--clr-primary); background-color: #686868;
background-color: #404040; }
}
&.internal {
&:last-child { text-decoration: underline;
border-radius: 0 0.5em 0.5em 0; }
}
&.speed {
&:first-child { background-color: #404040;
border-radius: 0.5em 0 0 0.5em; color: #cfcfcf;
} }
&:only-child { &.sbl {
border-radius: 0.5em; color: var(--clr-primary);
} background-color: #404040;
} }
}
} &:last-child {
</style> border-radius: 0 0.5em 0.5em 0;
}
&:first-child {
border-radius: 0.5em 0 0 0.5em;
}
&:only-child {
border-radius: 0.5em;
}
}
}
}
</style>
@@ -1,65 +1,71 @@
<template> <template>
<section class="info-spawn-list"> <section class="info-spawn-list">
<h3 class="spawn-header section-header"> <h3 class="spawn-header section-header">
<img :src="getIcon('spawn')" alt="icon-spawn" /> <img :src="getIcon('spawn')" alt="icon-spawn" />
&nbsp;{{ $t('scenery.spawns') }} &nbsp; &nbsp;{{ $t('scenery.spawns') }} &nbsp;
<span class="text--primary">{{ station.onlineInfo?.spawns.length || '0' }}</span> <span class="text--primary">{{ station.onlineInfo?.spawns.length || '0' }}</span>
</h3> </h3>
<span v-if="station.onlineInfo"> <span v-if="station.onlineInfo">
<span <span
class="badge spawn" class="badge spawn"
v-for="(spawn, i) in sortedSpawns" v-for="(spawn, i) in sortedSpawns"
:key="spawn.spawnName + station.onlineInfo?.dispatcherName + i" :key="spawn.spawnName + station.onlineInfo?.dispatcherName + i"
:data-electrified="spawn.isElectrified" :data-electrified="spawn.isElectrified"
> >
<span class="spawn_name">{{ spawn.spawnName }}</span> <span class="spawn_name">{{ spawn.spawnName }}</span>
<span class="spawn_length">{{ spawn.spawnLength }}m</span> <span class="spawn_length">{{ spawn.spawnLength }}m</span>
</span> </span>
</span> </span>
<span class="badge spawn badge-none" v-if="!station.onlineInfo || station.onlineInfo.spawns.length == 0" <span
>{{ $t('scenery.no-spawns') }} class="badge spawn badge-none"
</span> v-if="!station.onlineInfo || station.onlineInfo.spawns.length == 0"
</section> >{{ $t('scenery.no-spawns') }}
</template> </span>
</section>
<script lang="ts"> </template>
import { defineComponent } from 'vue';
import imageMixin from '../../../mixins/imageMixin'; <script lang="ts">
import Station from '../../../scripts/interfaces/Station'; import { PropType, defineComponent } from 'vue';
import imageMixin from '../../../mixins/imageMixin';
export default defineComponent({ import Station from '../../../scripts/interfaces/Station';
mixins: [imageMixin],
export default defineComponent({
props: { mixins: [imageMixin],
station: {
type: Object as () => Station, props: {
default: {}, station: {
}, type: Object as PropType<Station>,
}, required: true
}
computed: { },
sortedSpawns() {
return this.station.onlineInfo?.spawns.sort((s1, s2) => (s1.spawnLength < s2.spawnLength ? 1 : -1)); computed: {
}, sortedSpawns() {
}, if (!this.station.onlineInfo) return [];
});
</script> return [...this.station.onlineInfo.spawns].sort((s1, s2) =>
s1.spawnLength < s2.spawnLength ? 1 : -1
<style lang="scss" scoped> );
@import '../../../styles/variables.scss'; }
}
.spawn { });
color: white; </script>
&_length { <style lang="scss" scoped>
background-color: #404040; @import '../../../styles/variables.scss';
color: #cfcfcf;
} .spawn {
color: white;
&[data-electrified='true'] > &_name {
background-color: #007599; &_length {
} background-color: #404040;
} color: #cfcfcf;
</style> }
&[data-electrified='true'] > &_name {
background-color: #007599;
}
}
</style>
@@ -23,7 +23,10 @@
<span style="color: #eee">{{ station.onlineInfo?.scheduledTrains?.length || '0' }}</span> <span style="color: #eee">{{ station.onlineInfo?.scheduledTrains?.length || '0' }}</span>
/ /
<span style="color: #bbb" <span style="color: #bbb"
>{{ station.onlineInfo?.scheduledTrains?.filter((train) => train.stopInfo.confirmed).length || '0' }} >{{
station.onlineInfo?.scheduledTrains?.filter((train) => train.stopInfo.confirmed)
.length || '0'
}}
</span> </span>
</span> </span>
</span> </span>
@@ -31,7 +34,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { PropType, defineComponent } from 'vue';
import imageMixin from '../../../mixins/imageMixin'; import imageMixin from '../../../mixins/imageMixin';
import Station from '../../../scripts/interfaces/Station'; import Station from '../../../scripts/interfaces/Station';
@@ -39,10 +42,10 @@ export default defineComponent({
mixins: [imageMixin], mixins: [imageMixin],
props: { props: {
station: { station: {
type: Object as () => Station, type: Object as PropType<Station>,
default: {}, required: true
}, }
}, }
}); });
</script> </script>
@@ -1,131 +1,136 @@
<template> <template>
<section class="info-user-list"> <section class="info-user-list">
<h3 class="user-header section-header"> <h3 class="user-header section-header">
<img :src="getIcon('user')" alt="icon-user" /> <img :src="getIcon('user')" alt="icon-user" />
&nbsp;{{ $t('scenery.users') }} &nbsp; &nbsp;{{ $t('scenery.users') }} &nbsp;
<span class="text--primary">{{ station.onlineInfo?.currentUsers || '0' }}</span <span class="text--primary">{{ station.onlineInfo?.currentUsers || '0' }}</span
>&nbsp;/&nbsp;<span class="text--primary">{{ station.onlineInfo?.maxUsers || '0' }}</span> >&nbsp;/&nbsp;<span class="text--primary">{{ station.onlineInfo?.maxUsers || '0' }}</span>
</h3> </h3>
<div <div
v-for="(train, i) in computedStationTrains" v-for="train in computedStationTrains"
class="badge user" class="badge user"
:class="train.stopStatus" :class="train.stopStatus"
:key="train.trainId" :key="train.trainId"
tabindex="0" tabindex="0"
@click.prevent="selectModalTrain(train.trainId, $event.currentTarget)" @click.prevent="selectModalTrain(train.trainId, $event.currentTarget)"
@keydown.enter="selectModalTrain(train.trainId, $event.currentTarget)" @keydown.enter="selectModalTrain(train.trainId, $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>
</div> </div>
<div class="badge user badge-none" v-if="!computedStationTrains || computedStationTrains.length == 0"> <div
{{ $t('scenery.no-users') }} class="badge user badge-none"
</div> v-if="!computedStationTrains || computedStationTrains.length == 0"
</section> >
</template> {{ $t('scenery.no-users') }}
</div>
<script lang="ts"> </section>
import { computed, defineComponent } from 'vue'; </template>
import imageMixin from '../../../mixins/imageMixin';
import modalTrainMixin from '../../../mixins/modalTrainMixin'; <script lang="ts">
import routerMixin from '../../../mixins/routerMixin'; import { PropType, computed, defineComponent } from 'vue';
import Station from '../../../scripts/interfaces/Station'; import imageMixin from '../../../mixins/imageMixin';
import { useStore } from '../../../store/store'; import modalTrainMixin from '../../../mixins/modalTrainMixin';
import routerMixin from '../../../mixins/routerMixin';
export default defineComponent({ import Station from '../../../scripts/interfaces/Station';
mixins: [routerMixin, imageMixin, modalTrainMixin], import { useStore } from '../../../store/store';
props: { export default defineComponent({
station: { mixins: [routerMixin, imageMixin, modalTrainMixin],
type: Object as () => Station,
default: {}, props: {
}, station: {
}, type: Object as PropType<Station>,
required: true
setup(props) { }
const store = useStore(); },
const computedStationTrains = computed(() => { setup(props) {
if (!props.station) return []; const store = useStore();
const station = props.station as Station; const computedStationTrains = computed(() => {
if (!station.onlineInfo) return []; if (!props.station) return [];
if (!station.onlineInfo.stationTrains) return [];
const station = props.station as Station;
return station.onlineInfo.stationTrains.map((train) => { if (!station.onlineInfo) return [];
const scheduledTrainStatus = station.onlineInfo?.scheduledTrains?.find((st) => st.trainNo === train.trainNo); if (!station.onlineInfo.stationTrains) return [];
return { return station.onlineInfo.stationTrains.map((train) => {
...train, const scheduledTrainStatus = station.onlineInfo?.scheduledTrains?.find(
stopStatus: scheduledTrainStatus?.stopStatus || 'no-timetable', (st) => st.trainNo === train.trainNo
}; );
});
}); return {
...train,
return { computedStationTrains, store }; stopStatus: scheduledTrainStatus?.stopStatus || 'no-timetable'
}, };
}); });
</script> });
<style lang="scss" scoped> return { computedStationTrains, store };
$no-timetable: #aaa; }
$departed: springgreen; });
$stopped: #ffa600; </script>
$online: gold;
$terminated: salmon; <style lang="scss" scoped>
$disconnected: slategray; $no-timetable: #aaa;
$departed: springgreen;
.info-user-list { $stopped: #ffa600;
width: 100%; $online: gold;
$terminated: salmon;
ul { $disconnected: slategray;
display: flex;
flex-wrap: wrap; .info-user-list {
justify-content: center; width: 100%;
}
} ul {
display: flex;
.user { flex-wrap: wrap;
cursor: pointer; justify-content: center;
}
&_train { }
color: black;
background-color: $no-timetable; .user {
cursor: pointer;
transition: background-color 200ms;
-ms-transition: background-color 200ms; &_train {
-webkit-transition: background-color 200ms; color: black;
} background-color: $no-timetable;
&.no-timetable .user_train { transition: background-color 200ms;
background-color: $no-timetable; -ms-transition: background-color 200ms;
} -webkit-transition: background-color 200ms;
}
&.departed > &_train {
background-color: $departed; &.no-timetable .user_train {
} background-color: $no-timetable;
}
&.stopped > &_train {
background-color: $stopped; &.departed > &_train {
} background-color: $departed;
}
&.online > &_train {
background-color: $online; &.stopped > &_train {
} background-color: $stopped;
}
&.terminated > &_train {
background-color: $terminated; &.online > &_train {
} background-color: $online;
}
&.disconnected > &_train {
background-color: $disconnected; &.terminated > &_train {
} background-color: $terminated;
}
&.offline {
background: firebrick; &.disconnected > &_train {
pointer-events: none; background-color: $disconnected;
} }
}
</style> &.offline {
background: firebrick;
pointer-events: none;
}
}
</style>
+481 -457
View File
@@ -1,457 +1,481 @@
<template> <template>
<section class="scenery-timetable"> <section class="scenery-timetable">
<div class="timetable-header"> <div class="timetable-header">
<h3> <h3>
<img :src="getIcon('timetable')" alt="icon-timetable" /> <img :src="getIcon('timetable')" alt="icon-timetable" />
<span>{{ $t('scenery.timetables') }}</span> <span>{{ $t('scenery.timetables') }}</span>
<span> <span>
<span class="text--primary">{{ station.onlineInfo?.scheduledTrains?.length || '0' }}</span> <span class="text--primary">{{
<span> / </span> station.onlineInfo?.scheduledTrains?.length || '0'
<span class="text--grayed"> }}</span>
{{ station.onlineInfo?.scheduledTrains?.filter((train) => train.stopInfo.confirmed).length || '0' }} <span> / </span>
</span> <span class="text--grayed">
</span> {{
station.onlineInfo?.scheduledTrains?.filter((train) => train.stopInfo.confirmed)
<span class="header_links"> .length || '0'
<a :href="`https://pragotron-td2.web.app/board?name=${station.name}`" target="_blank" :title="$t('scenery.pragotron-link')"> }}
<img :src="getIcon('pragotron')" alt="icon-pragotron" /> </span>
</a> </span>
<a :href="tabliceZbiorczeHref" target="_blank" :title="$t('scenery.tablice-link')"> <span class="header_links">
<img :src="getIcon('tablice', 'ico')" alt="icon-tablice" /> <a
</a> :href="`https://pragotron-td2.web.app/board?name=${station.name}`"
</span> target="_blank"
</h3> :title="$t('scenery.pragotron-link')"
>
<div class="timetable-checkpoints" v-if="station?.generalInfo?.checkpoints"> <img :src="getIcon('pragotron')" alt="icon-pragotron" />
<span v-for="(cp, i) in station.generalInfo.checkpoints" :key="i"> </a>
{{ (i > 0 && '&bull;') || '' }}
<a :href="tabliceZbiorczeHref" target="_blank" :title="$t('scenery.tablice-link')">
<button <img :src="getIcon('tablice', 'ico')" alt="icon-tablice" />
:key="cp.checkpointName" </a>
class="checkpoint_item" </span>
:class="{ current: chosenCheckpoint === cp.checkpointName }" </h3>
@click="setCheckpoint(cp)"
> <div class="timetable-checkpoints" v-if="station?.generalInfo?.checkpoints">
{{ cp.checkpointName }} <span v-for="(cp, i) in station.generalInfo.checkpoints" :key="i">
</button> {{ (i > 0 && '&bull;') || '' }}
</span>
</div> <button
</div> :key="cp.checkpointName"
class="checkpoint_item"
<div class="timetable-list"> :class="{ current: chosenCheckpoint === cp.checkpointName }"
<div style="padding-bottom: 5em" v-if="store.dataStatuses.trains == 0 && computedScheduledTrains.length == 0"> @click="setCheckpoint(cp)"
<Loading /> >
</div> {{ cp.checkpointName }}
</button>
<span class="timetable-item empty" v-else-if="computedScheduledTrains.length == 0 && !station.onlineInfo"> </span>
{{ $t('scenery.offline') }} </div>
</span> </div>
<span class="timetable-item empty" v-else-if="computedScheduledTrains.length == 0"> <div class="timetable-list">
{{ $t('scenery.no-timetables') }} <div
</span> style="padding-bottom: 5em"
v-if="store.dataStatuses.trains == 0 && computedScheduledTrains.length == 0"
<transition-group name="list-anim"> >
<div <Loading />
class="timetable-item" </div>
v-for="(scheduledTrain, i) in computedScheduledTrains"
:key="scheduledTrain.trainId" <span
tabindex="0" class="timetable-item empty"
@click.prevent.stop="selectModalTrain(scheduledTrain.trainId, $event.currentTarget)" v-else-if="computedScheduledTrains.length == 0 && !station.onlineInfo"
@keydown.enter.prevent="selectModalTrain(scheduledTrain.trainId, $event.currentTarget)" >
> {{ $t('scenery.offline') }}
<span class="timetable-general"> </span>
<span class="general-info">
<span class="info-number"> <span class="timetable-item empty" v-else-if="computedScheduledTrains.length == 0">
<strong>{{ scheduledTrain.category }}</strong> {{ $t('scenery.no-timetables') }}
{{ scheduledTrain.trainNo }} </span>
<span class="g-tooltip" v-if="scheduledTrain.stopInfo.comments"> <transition-group name="list-anim">
<img :src="getIcon('warning')" /> <div
<span class="content" v-html="scheduledTrain.stopInfo.comments"> </span> class="timetable-item"
</span> v-for="scheduledTrain in computedScheduledTrains"
</span> :key="scheduledTrain.trainId"
&nbsp;|&nbsp; tabindex="0"
<span> @click.prevent.stop="selectModalTrain(scheduledTrain.trainId, $event.currentTarget)"
{{ scheduledTrain.driverName }} @keydown.enter.prevent="selectModalTrain(scheduledTrain.trainId, $event.currentTarget)"
</span> >
<span class="timetable-general">
<div class="info-route"> <span class="general-info">
<strong>{{ scheduledTrain.beginsAt }} - {{ scheduledTrain.terminatesAt }}</strong> <span class="info-number">
</div> <strong>{{ scheduledTrain.category }}</strong>
{{ scheduledTrain.trainNo }}
<ScheduledTrainStatus :scheduledTrain="scheduledTrain" />
</span> <span class="g-tooltip" v-if="scheduledTrain.stopInfo.comments">
</span> <img :src="getIcon('warning')" />
<span class="content" v-html="scheduledTrain.stopInfo.comments"> </span>
<span class="timetable-schedule"> </span>
<span class="schedule-arrival"> </span>
<span class="arrival-time begins" v-if="scheduledTrain.stopInfo.beginsHere"> &nbsp;|&nbsp;
{{ $t('timetables.begins') }} <span>
</span> {{ scheduledTrain.driverName }}
</span>
<span class="arrival-time" v-else>
<div v-if="scheduledTrain.stopInfo.arrivalDelay == 0"> <div class="info-route">
<span>{{ timestampToString(scheduledTrain.stopInfo.arrivalTimestamp) }}</span> <strong>{{ scheduledTrain.beginsAt }} - {{ scheduledTrain.terminatesAt }}</strong>
</div> </div>
<div v-else>
<div> <ScheduledTrainStatus :scheduledTrain="scheduledTrain" />
<s style="margin-right: 0.2em" class="text--grayed">{{ timestampToString(scheduledTrain.stopInfo.arrivalTimestamp) }}</s> </span>
</div> </span>
<span> <span class="timetable-schedule">
{{ timestampToString(scheduledTrain.stopInfo.arrivalRealTimestamp) }} <span class="schedule-arrival">
({{ scheduledTrain.stopInfo.arrivalDelay > 0 ? '+' : '' }}{{ scheduledTrain.stopInfo.arrivalDelay }}) <span class="arrival-time begins" v-if="scheduledTrain.stopInfo.beginsHere">
</span> {{ $t('timetables.begins') }}
</div> </span>
</span>
</span> <span class="arrival-time" v-else>
<div v-if="scheduledTrain.stopInfo.arrivalDelay == 0">
<span class="schedule-stop"> <span>{{ timestampToString(scheduledTrain.stopInfo.arrivalTimestamp) }}</span>
<span class="stop-connection"> </div>
{{ scheduledTrain.arrivingLine }} <div v-else>
</span> <div>
<s style="margin-right: 0.2em" class="text--grayed">{{
<span class="stop-time"> timestampToString(scheduledTrain.stopInfo.arrivalTimestamp)
{{ scheduledTrain.stopInfo.stopTime || '' }} }}</s>
{{ scheduledTrain.stopInfo.stopTime ? scheduledTrain.stopInfo.stopType || 'pt' : '' }} </div>
</span>
<span>
<span class="stop-connection"> {{ timestampToString(scheduledTrain.stopInfo.arrivalRealTimestamp) }}
{{ scheduledTrain.departureLine }} ({{ scheduledTrain.stopInfo.arrivalDelay > 0 ? '+' : ''
</span> }}{{ scheduledTrain.stopInfo.arrivalDelay }})
</span> </span>
</div>
<span class="schedule-departure"> </span>
<span class="departure-time terminates" v-if="scheduledTrain.stopInfo.terminatesHere"> </span>
{{ $t('timetables.terminates') }}
</span> <span class="schedule-stop">
<span class="stop-connection">
<span class="departure-time" v-else> {{ scheduledTrain.arrivingLine }}
<div v-if="scheduledTrain.stopInfo.departureDelay == 0"> </span>
<span>{{ timestampToString(scheduledTrain.stopInfo.departureTimestamp) }}</span>
</div> <span class="stop-time">
<div v-else> {{ scheduledTrain.stopInfo.stopTime || '' }}
<div> {{
<s style="margin-right: 0.2em" class="text--grayed">{{ timestampToString(scheduledTrain.stopInfo.departureTimestamp) }}</s> scheduledTrain.stopInfo.stopTime ? scheduledTrain.stopInfo.stopType || 'pt' : ''
</div> }}
</span>
<span>
{{ timestampToString(scheduledTrain.stopInfo.departureRealTimestamp) }} <span class="stop-connection">
({{ scheduledTrain.stopInfo.departureDelay > 0 ? '+' : '' }}{{ scheduledTrain.stopInfo.departureDelay }}) {{ scheduledTrain.departureLine }}
</span> </span>
</div> </span>
</span>
</span> <span class="schedule-departure">
</span> <span class="departure-time terminates" v-if="scheduledTrain.stopInfo.terminatesHere">
</div> {{ $t('timetables.terminates') }}
</transition-group> </span>
</div>
</section> <span class="departure-time" v-else>
</template> <div v-if="scheduledTrain.stopInfo.departureDelay == 0">
<span>{{ timestampToString(scheduledTrain.stopInfo.departureTimestamp) }}</span>
<script lang="ts"> </div>
import SelectBox from '../Global/SelectBox.vue'; <div v-else>
import { computed, defineComponent, PropType, ref } from '@vue/runtime-core'; <div>
import { useRoute } from 'vue-router'; <s style="margin-right: 0.2em" class="text--grayed">{{
timestampToString(scheduledTrain.stopInfo.departureTimestamp)
import Loading from '../Global/Loading.vue'; }}</s>
import TrainModal from '../Global/TrainModal.vue'; </div>
import dateMixin from '../../mixins/dateMixin';
import routerMixin from '../../mixins/routerMixin'; <span>
import Station from '../../scripts/interfaces/Station'; {{ timestampToString(scheduledTrain.stopInfo.departureRealTimestamp) }}
import { useStore } from '../../store/store'; ({{ scheduledTrain.stopInfo.departureDelay > 0 ? '+' : ''
import imageMixin from '../../mixins/imageMixin'; }}{{ scheduledTrain.stopInfo.departureDelay }})
import modalTrainMixin from '../../mixins/modalTrainMixin'; </span>
import ScheduledTrainStatus from './ScheduledTrainStatus.vue'; </div>
</span>
export default defineComponent({ </span>
name: 'SceneryTimetable', </span>
</div>
components: { SelectBox, Loading, TrainModal, ScheduledTrainStatus }, </transition-group>
</div>
mixins: [dateMixin, routerMixin, imageMixin, modalTrainMixin], </section>
</template>
props: {
station: { <script lang="ts">
type: Object as PropType<Station>, import { computed, defineComponent, PropType, ref } from 'vue';
required: true, import { useRoute } from 'vue-router';
},
import Loading from '../Global/Loading.vue';
timetableOnly: { import dateMixin from '../../mixins/dateMixin';
type: Boolean, import routerMixin from '../../mixins/routerMixin';
}, import Station from '../../scripts/interfaces/Station';
}, import { useStore } from '../../store/store';
import imageMixin from '../../mixins/imageMixin';
data: () => ({ import modalTrainMixin from '../../mixins/modalTrainMixin';
listOpen: false, import ScheduledTrainStatus from './ScheduledTrainStatus.vue';
}),
export default defineComponent({
mounted() { name: 'SceneryTimetable',
this.loadSelectedOption();
}, components: { Loading, ScheduledTrainStatus },
activated() { mixins: [dateMixin, routerMixin, imageMixin, modalTrainMixin],
this.loadSelectedOption();
}, props: {
station: {
setup(props) { type: Object as PropType<Station>,
const route = useRoute(); required: true
const currentURL = computed(() => `${location.origin}${route.fullPath}`); },
const store = useStore(); timetableOnly: {
type: Boolean
const chosenCheckpoint = ref( }
props.station?.generalInfo?.checkpoints?.length == 0 ? '' : props.station?.generalInfo?.checkpoints[0].checkpointName || null },
);
data: () => ({
const computedScheduledTrains = computed(() => { listOpen: false
if (!props.station) return []; }),
const station = props.station as Station; mounted() {
this.loadSelectedOption();
let scheduledTrains = },
station.generalInfo?.checkpoints.find((cp) => cp.checkpointName === chosenCheckpoint.value)?.scheduledTrains ||
station.onlineInfo?.scheduledTrains || activated() {
[]; this.loadSelectedOption();
},
if (!scheduledTrains) return [];
setup(props) {
return ( const route = useRoute();
scheduledTrains.sort((a, b) => { const currentURL = computed(() => `${location.origin}${route.fullPath}`);
if (a.stopStatusID > b.stopStatusID) return 1;
if (a.stopStatusID < b.stopStatusID) return -1; const store = useStore();
if (a.stopInfo.arrivalTimestamp > b.stopInfo.arrivalTimestamp) return 1; const chosenCheckpoint = ref(
if (a.stopInfo.arrivalTimestamp < b.stopInfo.arrivalTimestamp) return -1; props.station?.generalInfo?.checkpoints?.length == 0
? ''
return a.stopInfo.departureTimestamp > b.stopInfo.departureTimestamp ? 1 : -1; : props.station?.generalInfo?.checkpoints[0].checkpointName || null
}) || [] );
);
}); const computedScheduledTrains = computed(() => {
if (!props.station) return [];
return {
currentURL, const station = props.station as Station;
chosenCheckpoint,
computedScheduledTrains, let scheduledTrains =
store, station.generalInfo?.checkpoints.find((cp) => cp.checkpointName === chosenCheckpoint.value)
}; ?.scheduledTrains ||
}, station.onlineInfo?.scheduledTrains ||
[];
computed: {
tabliceZbiorczeHref() { if (!scheduledTrains) return [];
let url = `https://tablice-td2.web.app/?station=${this.station.name}`;
if (this.chosenCheckpoint) url += `&checkpoint=${this.chosenCheckpoint}`; return (
scheduledTrains.sort((a, b) => {
return url; if (a.stopStatusID > b.stopStatusID) return 1;
}, if (a.stopStatusID < b.stopStatusID) return -1;
},
if (a.stopInfo.arrivalTimestamp > b.stopInfo.arrivalTimestamp) return 1;
methods: { if (a.stopInfo.arrivalTimestamp < b.stopInfo.arrivalTimestamp) return -1;
loadSelectedOption() {
if (!this.station) return; return a.stopInfo.departureTimestamp > b.stopInfo.departureTimestamp ? 1 : -1;
if (!this.station.generalInfo) return; }) || []
if (!this.station.generalInfo.checkpoints) return; );
if (this.station.generalInfo.checkpoints.length == 0) return; });
if (this.chosenCheckpoint != '') return; return {
currentURL,
this.chosenCheckpoint = this.station.generalInfo.checkpoints[0].checkpointName; chosenCheckpoint,
}, computedScheduledTrains,
store
setCheckpoint(cp: { checkpointName: string }) { };
this.chosenCheckpoint = cp.checkpointName; },
},
computed: {
showTimetableOnlyView() { tabliceZbiorczeHref() {
this.$router.push(`${this.$route.fullPath}&timetableOnly=1`); let url = `https://tablice-td2.web.app/?station=${this.station.name}`;
}, if (this.chosenCheckpoint) url += `&checkpoint=${this.chosenCheckpoint}`;
},
}); return url;
</script> }
},
<style lang="scss" scoped>
@import '../../styles/responsive.scss'; methods: {
@import '../../styles/variables.scss'; loadSelectedOption() {
@import '../../styles/animations.scss'; if (!this.station) return;
if (!this.station.generalInfo) return;
.scenery-timetable { if (!this.station.generalInfo.checkpoints) return;
height: 100%; if (this.station.generalInfo.checkpoints.length == 0) return;
overflow-y: scroll;
padding: 0 0.5em; if (this.chosenCheckpoint != '') return;
}
this.chosenCheckpoint = this.station.generalInfo.checkpoints[0].checkpointName;
.timetable-header { },
position: sticky;
top: 0; setCheckpoint(cp: { checkpointName: string }) {
z-index: 99; this.chosenCheckpoint = cp.checkpointName;
},
background-color: #181818;
showTimetableOnlyView() {
padding: 0.5em; this.$router.push(`${this.$route.fullPath}&timetableOnly=1`);
}
img { }
width: 25px; });
vertical-align: middle; </script>
}
<style lang="scss" scoped>
h3 { @import '../../styles/responsive.scss';
display: flex; @import '../../styles/variables.scss';
justify-content: center; @import '../../styles/animations.scss';
flex-wrap: wrap;
align-items: center; .scenery-timetable {
height: 100%;
gap: 0.5em; overflow-y: scroll;
font-size: 1.3em; padding: 0 0.5em;
} }
}
.timetable-header {
.header_links { position: sticky;
display: flex; top: 0;
gap: 0.5em; z-index: 99;
margin-left: 0.5em;
} background-color: #181818;
.timetable { padding: 0.5em;
&-count {
margin-left: 0.5em; img {
} width: 25px;
vertical-align: middle;
&-item { }
margin: 0.5em auto;
padding: 0.5em; h3 {
max-width: 1100px; display: flex;
justify-content: center;
display: grid; flex-wrap: wrap;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); align-items: center;
gap: 1.2em 0.5em;
gap: 0.5em;
overflow: hidden; font-size: 1.3em;
}
background: #353535; }
cursor: pointer; .header_links {
z-index: 10; display: flex;
gap: 0.5em;
&.empty { margin-left: 0.5em;
padding: 1rem; }
font-size: 1.2em;
color: #bbb; .timetable {
} &-count {
} margin-left: 0.5em;
}
&-general {
display: flex; &-item {
align-items: center; margin: 0.5em auto;
justify-content: space-between; padding: 0.5em;
max-width: 1100px;
text-align: left;
} display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
&-schedule { gap: 1.2em 0.5em;
display: grid;
grid-template-columns: repeat(3, 1fr); overflow: hidden;
gap: 0.2em;
align-items: center; background: #353535;
width: 100%; cursor: pointer;
max-width: 400px; z-index: 10;
margin: 0 auto;
} &.empty {
} padding: 1rem;
font-size: 1.2em;
.timetable-list { color: #bbb;
position: relative; }
} }
.timetable-checkpoints { &-general {
display: flex; display: flex;
justify-content: center; align-items: center;
justify-content: space-between;
flex-wrap: wrap;
font-size: 1.1em; text-align: left;
}
margin-top: 0.5em;
&-schedule {
button.checkpoint_item { display: grid;
color: #aaa; grid-template-columns: repeat(3, 1fr);
display: inline; gap: 0.2em;
} align-items: center;
.checkpoint_item.current { width: 100%;
font-weight: bold; max-width: 400px;
color: $accentCol; margin: 0 auto;
} }
} }
.general-info { .timetable-list {
display: flex; position: relative;
flex-wrap: wrap; }
.info-number { .timetable-checkpoints {
color: $accentCol; display: flex;
} justify-content: center;
.info-route { flex-wrap: wrap;
width: 100%; font-size: 1.1em;
}
margin-top: 0.5em;
.g-tooltip > .content {
z-index: 100; button.checkpoint_item {
color: white; color: #aaa;
display: inline;
left: 110%; }
}
.checkpoint_item.current {
img { font-weight: bold;
width: 1.1em; color: $accentCol;
} }
} }
.schedule { .general-info {
&-arrival, display: flex;
&-departure { flex-wrap: wrap;
font-size: 1.15em;
} .info-number {
color: $accentCol;
&-stop { }
display: grid;
grid-template-columns: repeat(3, 1fr); .info-route {
gap: 0.5em; width: 100%;
align-items: end; }
.stop-connection { .g-tooltip > .content {
font-size: 0.95em; z-index: 100;
} color: white;
.stop-time { left: 110%;
position: relative; }
inline-size: max-content;
align-self: center; img {
font-size: 0.9em; width: 1.1em;
}
color: $accentCol; }
&::after { .schedule {
content: '\027F6'; &-arrival,
display: block; &-departure {
font-size: 2.2em; font-size: 1.15em;
line-height: 0.65em; }
}
} &-stop {
} display: grid;
} grid-template-columns: repeat(3, 1fr);
gap: 0.5em;
.arrival-time.begins, align-items: end;
.departure-time.terminates {
font-size: 0.85em; .stop-connection {
} font-size: 0.95em;
}
@include smallScreen {
.timetable-item { .stop-time {
grid-template-columns: 1fr; position: relative;
} inline-size: max-content;
} align-self: center;
</style> font-size: 0.9em;
color: $accentCol;
&::after {
content: '\027F6';
display: block;
font-size: 2.2em;
line-height: 0.65em;
}
}
}
}
.arrival-time.begins,
.departure-time.terminates {
font-size: 0.85em;
}
@include smallScreen {
.timetable-item {
grid-template-columns: 1fr;
}
}
</style>
@@ -1,110 +1,117 @@
<template> <template>
<section class="scenery-table-section"> <section class="scenery-table-section">
<Loading v-if="dataStatus != DataStatus.Loaded" /> <Loading v-if="dataStatus != DataStatus.Loaded" />
<div class="no-history" v-else-if="historyList.length == 0">{{ $t('scenery.history-list-empty') }}</div> <div class="no-history" v-else-if="historyList.length == 0">
{{ $t('scenery.history-list-empty') }}
<table class="scenery-history-table" v-else> </div>
<thead>
<th>{{ $t('scenery.timetables-history-id') }}</th> <table class="scenery-history-table" v-else>
<th>{{ $t('scenery.timetables-history-number') }}</th> <thead>
<th>{{ $t('scenery.timetables-history-route') }}</th> <th>{{ $t('scenery.timetables-history-id') }}</th>
<th>{{ $t('scenery.timetables-history-driver') }}</th> <th>{{ $t('scenery.timetables-history-number') }}</th>
<th>{{ $t('scenery.timetables-history-author') }}</th> <th>{{ $t('scenery.timetables-history-route') }}</th>
<th>{{ $t('scenery.timetables-history-date') }}</th> <th>{{ $t('scenery.timetables-history-driver') }}</th>
</thead> <th>{{ $t('scenery.timetables-history-author') }}</th>
<th>{{ $t('scenery.timetables-history-date') }}</th>
<tbody> </thead>
<tr v-for="historyItem in historyList">
<td> <tbody>
<router-link :to="`/journal/timetables?timetableId=${historyItem.id}`">#{{ historyItem.id }}</router-link> <tr v-for="historyItem in historyList" :key="historyItem.id">
</td> <td>
<td> <router-link :to="`/journal/timetables?timetableId=${historyItem.id}`"
<b class="text--primary">{{ historyItem.trainCategoryCode }}</b> <br /> >#{{ historyItem.id }}</router-link
{{ historyItem.trainNo }} >
</td> </td>
<td>{{ historyItem.route.replace('|', ' -> ') }}</td> <td>
<td>{{ historyItem.driverName }}</td> <b class="text--primary">{{ historyItem.trainCategoryCode }}</b> <br />
<td> {{ historyItem.trainNo }}
<router-link </td>
v-if="historyItem.authorName" <td>{{ historyItem.route.replace('|', ' -> ') }}</td>
:to="`/journal/timetables?authorName=${historyItem.authorName}`" <td>{{ historyItem.driverName }}</td>
>{{ historyItem.authorName }} <td>
</router-link> <router-link
<i v-else>{{ $t('scenery.timetable-author-unknown') }}</i> v-if="historyItem.authorName"
</td> :to="`/journal/timetables?authorName=${historyItem.authorName}`"
<td> >{{ historyItem.authorName }}
<b>{{ localeDay(historyItem.beginDate, $i18n.locale) }}</b> </router-link>
{{ localeTime(historyItem.beginDate, $i18n.locale) }} <i v-else>{{ $t('scenery.timetable-author-unknown') }}</i>
</td> </td>
</tr> <td>
</tbody> <b>{{ localeDay(historyItem.beginDate, $i18n.locale) }}</b>
</table> {{ localeTime(historyItem.beginDate, $i18n.locale) }}
</section> </td>
</tr>
<div class="bottom-info"> </tbody>
<button class="btn btn--option" v-if="historyList.length > 0" @click="navigateToHistory()"> </table>
{{ $t('scenery.bottom-info') }} </section>
</button>
</div> <div class="bottom-info">
</template> <button class="btn btn--option" v-if="historyList.length > 0" @click="navigateToHistory()">
{{ $t('scenery.bottom-info') }}
<script lang="ts"> </button>
import axios from 'axios'; </div>
import { defineComponent, PropType } from 'vue'; </template>
import dateMixin from '../../mixins/dateMixin';
import { DataStatus } from '../../scripts/enums/DataStatus'; <script lang="ts">
import { TimetableHistory, SceneryTimetableHistory } from '../../scripts/interfaces/api/TimetablesAPIData'; import axios from 'axios';
import Station from '../../scripts/interfaces/Station'; import { defineComponent, PropType } from 'vue';
import { URLs } from '../../scripts/utils/apiURLs'; import dateMixin from '../../mixins/dateMixin';
import Loading from '../Global/Loading.vue'; import { DataStatus } from '../../scripts/enums/DataStatus';
import listObserverMixin from '../../mixins/listObserverMixin'; import {
TimetableHistory,
export default defineComponent({ SceneryTimetableHistory
name: 'SceneryTimetablesHistory', } from '../../scripts/interfaces/api/TimetablesAPIData';
mixins: [dateMixin, listObserverMixin], import Station from '../../scripts/interfaces/Station';
props: { import { URLs } from '../../scripts/utils/apiURLs';
station: { import Loading from '../Global/Loading.vue';
type: Object as PropType<Station>, import listObserverMixin from '../../mixins/listObserverMixin';
required: true,
}, export default defineComponent({
}, name: 'SceneryTimetablesHistory',
mixins: [dateMixin, listObserverMixin],
data() { props: {
return { station: {
historyList: [] as TimetableHistory[], type: Object as PropType<Station>,
dataStatus: DataStatus.Loading, required: true
DataStatus, }
}; },
},
data() {
async activated() { return {
const fetchedHistory = await this.fetchAPIData(); historyList: [] as TimetableHistory[],
if (fetchedHistory) this.historyList = fetchedHistory.timetables; dataStatus: DataStatus.Loading,
}, DataStatus
};
methods: { },
async fetchAPIData(countFrom = 0, countLimit = 15): Promise<SceneryTimetableHistory | null> {
try { async activated() {
const requestString = `${URLs.stacjownikAPI}/api/getIssuedTimetables?name=${this.station.name}&countFrom=${countFrom}&countLimit=${countLimit}`; const fetchedHistory = await this.fetchAPIData();
const historyAPIData: SceneryTimetableHistory = await (await axios.get(requestString)).data; if (fetchedHistory) this.historyList = fetchedHistory.timetables;
},
this.dataStatus = DataStatus.Loaded;
return historyAPIData; methods: {
} catch (error) { async fetchAPIData(countFrom = 0, countLimit = 15): Promise<SceneryTimetableHistory | null> {
console.error(error); try {
return null; const requestString = `${URLs.stacjownikAPI}/api/getIssuedTimetables?name=${this.station.name}&countFrom=${countFrom}&countLimit=${countLimit}`;
} const historyAPIData: SceneryTimetableHistory = await (await axios.get(requestString)).data;
},
this.dataStatus = DataStatus.Loaded;
navigateToHistory() { return historyAPIData;
this.$router.push(`/journal/timetables?issuedFrom=${this.station.name}`); } catch (error) {
}, console.error(error);
}, return null;
components: { Loading }, }
}); },
</script>
navigateToHistory() {
<style lang="scss" scoped> this.$router.push(`/journal/timetables?issuedFrom=${this.station.name}`);
@import '../../styles/responsive.scss'; }
@import '../../styles/sceneryViewTables.scss'; },
</style> components: { Loading }
});
</script>
<style lang="scss" scoped>
@import '../../styles/responsive.scss';
@import '../../styles/sceneryViewTables.scss';
</style>
@@ -1,6 +1,9 @@
<template> <template>
<div class="general-status"> <div class="general-status">
<span :class="computedScheduledTrain.stopStatus" :title="computedScheduledTrain.stopStatusDescription"> <span
:class="computedScheduledTrain.stopStatus"
:title="computedScheduledTrain.stopStatusDescription"
>
{{ computedScheduledTrain.stopStatusIndicator }} {{ computedScheduledTrain.stopStatusIndicator }}
</span> </span>
</div> </div>
@@ -19,16 +22,21 @@ export default defineComponent({
props: { props: {
scheduledTrain: { scheduledTrain: {
type: Object as PropType<ScheduledTrain>, type: Object as PropType<ScheduledTrain>,
required: true, required: true
}, }
}, },
computed: { computed: {
computedScheduledTrain(): ScheduledTrainComp { computedScheduledTrain(): ScheduledTrainComp {
const { prevDepartureLine, prevStationName, stopStatus, nextArrivalLine, nextStationName } = this.scheduledTrain; const { prevDepartureLine, prevStationName, stopStatus, nextArrivalLine, nextStationName } =
this.scheduledTrain;
const prevDepartureIndicator = prevDepartureLine ? `(${prevDepartureLine}) ${prevStationName}` : '---'; const prevDepartureIndicator = prevDepartureLine
const nextArrivalIndicator = nextArrivalLine ? `(${nextArrivalLine}) ${nextStationName}` : '---'; ? `(${prevDepartureLine}) ${prevStationName}`
: '---';
const nextArrivalIndicator = nextArrivalLine
? `(${nextArrivalLine}) ${nextStationName}`
: '---';
let stopStatusDescription = '', let stopStatusDescription = '',
stopStatusIndicator = ''; stopStatusIndicator = '';
@@ -36,7 +44,10 @@ export default defineComponent({
switch (stopStatus) { switch (stopStatus) {
case StopStatus.arriving: case StopStatus.arriving:
stopStatusIndicator = `${this.$t('timetables.from')}: ${prevDepartureIndicator}`; stopStatusIndicator = `${this.$t('timetables.from')}: ${prevDepartureIndicator}`;
stopStatusDescription = this.$t('timetables.desc-arriving', { prevStationName, prevDepartureLine }); stopStatusDescription = this.$t('timetables.desc-arriving', {
prevStationName,
prevDepartureLine
});
break; break;
case StopStatus.online: case StopStatus.online:
@@ -51,12 +62,18 @@ export default defineComponent({
case StopStatus.departed: case StopStatus.departed:
stopStatusIndicator = `${this.$t('timetables.to')}: ${nextArrivalIndicator}`; stopStatusIndicator = `${this.$t('timetables.to')}: ${nextArrivalIndicator}`;
stopStatusDescription = this.$t('timetables.desc-departed', { nextStationName, nextArrivalLine }); stopStatusDescription = this.$t('timetables.desc-departed', {
nextStationName,
nextArrivalLine
});
break; break;
case StopStatus['departed-away']: case StopStatus['departed-away']:
stopStatusIndicator = `${this.$t('timetables.to')}: ${nextArrivalIndicator}`; stopStatusIndicator = `${this.$t('timetables.to')}: ${nextArrivalIndicator}`;
stopStatusDescription = this.$t('timetables.desc-departed-away', { nextStationName, nextArrivalLine }); stopStatusDescription = this.$t('timetables.desc-departed-away', {
nextStationName,
nextArrivalLine
});
break; break;
case StopStatus.terminated: case StopStatus.terminated:
@@ -70,10 +87,10 @@ export default defineComponent({
return { return {
...this.scheduledTrain, ...this.scheduledTrain,
stopStatusDescription, stopStatusDescription,
stopStatusIndicator, stopStatusIndicator
}; };
}, }
}, }
}); });
</script> </script>
@@ -110,4 +127,3 @@ export default defineComponent({
} }
} }
</style> </style>
+8 -9
View File
@@ -1,6 +1,6 @@
<template> <template>
<label @dblclick="handleDbClick"> <label @dblclick="handleDbClick">
<input v-model="option.value" type="checkbox" :class="option.section" :name="option.id" /> <input type="checkbox" :class="option.section" :name="option.id" />
<span> <span>
{{ $t(`filters.${option.id}`) }} {{ $t(`filters.${option.id}`) }}
</span> </span>
@@ -23,20 +23,20 @@ export default defineComponent({
props: { props: {
option: { option: {
type: Object as () => FilterOption, type: Object as () => FilterOption,
required: true, required: true
}, }
}, },
setup() { setup() {
return { return {
filterStore: useStationFiltersStore(), 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);
}, }
}, },
methods: { methods: {
@@ -44,7 +44,7 @@ export default defineComponent({
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.filterStore.inputs.options this.filterStore.inputs.options
.filter((option) => { .filter((option) => {
@@ -53,8 +53,8 @@ export default defineComponent({
.forEach((option) => { .forEach((option) => {
option.value = !this.option.value; option.value = !this.option.value;
}); });
}, }
}, }
}); });
</script> </script>
@@ -96,4 +96,3 @@ label {
} }
} }
</style> </style>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+283 -261
View File
@@ -1,261 +1,283 @@
<template> <template>
<div class="train-info"> <div class="train-info">
<section class="train-route"> <section class="train-route">
<div class="train_general"> <div class="train_general">
<b class="warning-timeout" v-if="train.isTimeout" :title="$t('trains.timeout')">?</b> <b class="warning-timeout" v-if="train.isTimeout" :title="$t('trains.timeout')">?</b>
<span class="timetable-id" v-if="train.timetableData">#{{ train.timetableData.timetableId }}</span> <span class="timetable-id" v-if="train.timetableData"
>#{{ train.timetableData.timetableId }}</span
<span class="timetable_warnings" v-if="train.timetableData?.TWR || train.timetableData?.SKR"> >
<span class="train-badge twr" v-if="train.timetableData?.TWR" :title="$t('general.TWR')">TWR</span>
<span class="train-badge skr" v-if="train.timetableData?.SKR" :title="$t('general.SKR')">SKR</span> <span
</span> class="timetable_warnings"
v-if="train.timetableData?.TWR || train.timetableData?.SKR"
<strong> >
<span v-if="train.timetableData" class="text--primary">{{ train.timetableData.category }}&nbsp;</span> <span class="train-badge twr" v-if="train.timetableData?.TWR" :title="$t('general.TWR')"
<span class="train-number">{{ train.trainNo }}</span> >TWR</span
</strong> >
<span>&bull;</span> <span class="train-badge skr" v-if="train.timetableData?.SKR" :title="$t('general.SKR')"
<b class="level-badge driver" :style="calculateExpStyle(train.driverLevel, train.isSupporter)"> >SKR</span
{{ train.driverLevel < 2 ? 'L' : `${train.driverLevel}` }} >
</b> </span>
<span>{{ train.driverName }}</span>
</div> <strong>
<span v-if="train.timetableData" class="text--primary"
<div class="timetable_route" v-if="train.timetableData"> >{{ train.timetableData.category }}&nbsp;</span
<strong>{{ train.timetableData.route.replace('|', ' - ') }}</strong> >
<img <span class="train-number">{{ train.trainNo }}</span>
v-if="getSceneriesWithComments(train.timetableData).length > 0" </strong>
class="image-warning" <span>&bull;</span>
:src="getIcon('warning')" <b
:title="`${$t('trains.timetable-comments')} (${getSceneriesWithComments(train.timetableData)})`" class="level-badge driver"
/> :style="calculateExpStyle(train.driverLevel, train.isSupporter)"
</div> >
{{ train.driverLevel < 2 ? 'L' : `${train.driverLevel}` }}
<hr style="margin: 0.25em 0" /> </b>
<span>{{ train.driverName }}</span>
<div class="timetable_stops" v-if="train.timetableData"> </div>
<span v-if="train.timetableData.followingStops.length > 2">
{{ $t('trains.via-title') }} <div class="timetable_route" v-if="train.timetableData">
<span v-html="displayStopList(train.timetableData.followingStops)"></span> <strong>{{ train.timetableData.route.replace('|', ' - ') }}</strong>
</span> <img
</div> v-if="getSceneriesWithComments(train.timetableData).length > 0"
class="image-warning"
<div class="timetable_progress" style="margin-top: 0.5em" v-if="train.timetableData"> :src="getIcon('warning')"
<ProgressBar :progressPercent="confirmedPercentage(train.timetableData.followingStops)" /> :title="`${$t('trains.timetable-comments')} (${getSceneriesWithComments(
train.timetableData
<span class="timetable_progress-distance"> )})`"
&nbsp; {{ currentDistance(train.timetableData.followingStops) }} km / />
<span class="text--primary"> {{ train.timetableData.routeDistance }} km </span> </div>
|
<span v-html="currentDelay(train.timetableData.followingStops)"></span> <hr style="margin: 0.25em 0" />
</span>
<div class="timetable_stops" v-if="train.timetableData">
<div class="train-status-badges"> <span v-if="train.timetableData.followingStops.length > 2">
<div v-if="!train.currentStationHash" class="train-badge offline">{{ $t('trains.scenery-offline') }}</div> {{ $t('trains.via-title') }}
<div v-if="!train.online" class="train-badge offline">Offline {{ lastSeenMessage(train.lastSeen) }}</div> <span v-html="displayStopList(train.timetableData.followingStops)"></span>
</div> </span>
</div> </div>
<div class="driver_position text--grayed" style="margin-top: 0.25em"> <div class="timetable_progress" style="margin-top: 0.5em" v-if="train.timetableData">
{{ displayTrainPosition(train) }} <ProgressBar :progressPercent="confirmedPercentage(train.timetableData.followingStops)" />
</div>
</section> <span class="timetable_progress-distance">
&nbsp; {{ currentDistance(train.timetableData.followingStops) }} km /
<section class="train-stats"> <span class="text--primary"> {{ train.timetableData.routeDistance }} km </span>
<TrainThumbnail :name="train.locoType" :onlyFirstSegment="true" /> |
<span v-html="currentDelay(train.timetableData.followingStops)"></span>
<div class="text--grayed"> </span>
{{ train.locoType }}
<span v-if="train.stockList.length > 1"> <div class="train-status-badges">
&nbsp;&bull; {{ $t('trains.cars') }}: <div v-if="!train.currentStationHash" class="train-badge offline">
<span class="count">{{ train.stockList.length - 1 }}</span> {{ $t('trains.scenery-offline') }}
</span> </div>
</div> <div v-if="!train.online" class="train-badge offline">
Offline {{ lastSeenMessage(train.lastSeen) }}
<div> </div>
<span v-for="(stat, i) in STATS.main" :key="stat.name"> </div>
<span v-if="i > 0"> &bull; </span> </div>
<span>{{ `${~~((train as any)[stat.name] * (stat.multiplier || 1))}${stat.unit}` }} </span>
</span> <div class="driver_position text--grayed" style="margin-top: 0.25em">
</div> {{ displayTrainPosition(train) }}
</section> </div>
</div> </section>
</template>
<section class="train-stats">
<script lang="ts"> <TrainThumbnail :name="train.locoType" :onlyFirstSegment="true" />
import { defineComponent } from 'vue';
import imageMixin from '../../mixins/imageMixin'; <div class="text--grayed">
import styleMixin from '../../mixins/styleMixin'; {{ train.locoType }}
import trainInfoMixin from '../../mixins/trainInfoMixin'; <span v-if="train.stockList.length > 1">
import Train from '../../scripts/interfaces/Train'; &nbsp;&bull; {{ $t('trains.cars') }}:
import ProgressBar from '../Global/ProgressBar.vue'; <span class="count">{{ train.stockList.length - 1 }}</span>
import TrainThumbnail from '../Global/TrainThumbnail.vue'; </span>
</div>
export default defineComponent({
props: { <div>
train: { <span v-for="(stat, i) in STATS.main" :key="stat.name">
type: Object as () => Train, <span v-if="i > 0"> &bull; </span>
required: true, <span
}, >{{ `${~~((train as any)[stat.name] * (stat.multiplier || 1))}${stat.unit}` }}
extended: { </span>
type: Boolean, </span>
default: true, </div>
}, </section>
}, </div>
mixins: [trainInfoMixin, imageMixin, styleMixin], </template>
components: { ProgressBar, TrainThumbnail },
}); <script lang="ts">
</script> import { defineComponent } from 'vue';
import imageMixin from '../../mixins/imageMixin';
<!-- Global style for TrainThumbnail --> import styleMixin from '../../mixins/styleMixin';
<style lang="scss"> import trainInfoMixin from '../../mixins/trainInfoMixin';
.train-stats .train-thumbnail { import Train from '../../scripts/interfaces/Train';
max-width: 100%; import ProgressBar from '../Global/ProgressBar.vue';
} import TrainThumbnail from '../Global/TrainThumbnail.vue';
</style>
export default defineComponent({
<style lang="scss" scoped> props: {
@import '../../styles/responsive.scss'; train: {
@import '../../styles/badge.scss'; type: Object as () => Train,
required: true
.image-warning { },
height: 1em; extended: {
type: Boolean,
margin-left: 0.5em; default: true
} }
},
.train-stats { mixins: [trainInfoMixin, imageMixin, styleMixin],
display: flex; components: { ProgressBar, TrainThumbnail }
justify-content: center; });
align-items: center; </script>
flex-direction: column; <!-- Global style for TrainThumbnail -->
text-align: center; <style lang="scss">
.train-stats .train-thumbnail {
gap: 0.25em; max-width: 100%;
} }
</style>
.train-info {
display: grid; <style lang="scss" scoped>
grid-template-columns: 2fr 1fr; @import '../../styles/responsive.scss';
grid-template-rows: 1fr; @import '../../styles/badge.scss';
padding: 1em; .image-warning {
height: 1em;
background-color: #1a1a1a;
gap: 0.5em; margin-left: 0.5em;
} }
.timetable-id { .train-stats {
color: #d2d2d2; display: flex;
} justify-content: center;
align-items: center;
.warning-timeout {
background-color: #be3728; flex-direction: column;
text-align: center;
display: inline-block;
text-align: center; gap: 0.25em;
}
padding: 0 0.25em;
} .train-info {
display: grid;
.timetable_stops { grid-template-columns: 2fr 1fr;
font-size: 0.75em; grid-template-rows: 1fr;
}
padding: 1em;
.train_general {
display: flex; background-color: #1a1a1a;
align-items: center; gap: 0.5em;
flex-wrap: wrap; }
gap: 0.25em; .timetable-id {
margin-right: 1.5em; color: #d2d2d2;
} }
.train-status-badges {
display: flex; .warning-timeout {
flex-wrap: wrap; background-color: #be3728;
gap: 0.25em; display: inline-block;
} text-align: center;
.train-driver { padding: 0 0.25em;
&.supporter { }
color: orange;
text-shadow: orange 0 0 5px; .timetable_stops {
} font-size: 0.75em;
} }
.timetable_route { .train_general {
display: flex; display: flex;
align-items: center; align-items: center;
flex-wrap: wrap;
margin-top: 0.5em;
} gap: 0.25em;
margin-right: 1.5em;
.timetable_warnings { }
display: flex; .train-status-badges {
gap: 0.25em; display: flex;
} flex-wrap: wrap;
.timetable_progress { gap: 0.25em;
display: flex; }
align-items: center;
flex-wrap: wrap; .train-driver {
} &.supporter {
color: orange;
.timetable_progress-distance { text-shadow: orange 0 0 5px;
margin-right: 0.25em; }
} }
.comments { .timetable_route {
display: flex; display: flex;
align-items: center; align-items: center;
font-size: 0.9em; margin-top: 0.5em;
}
margin-top: 1em;
.timetable_warnings {
img { display: flex;
margin-right: 0.5em; gap: 0.25em;
} }
}
.timetable_progress {
@include smallScreen() { display: flex;
.train-info { align-items: center;
grid-template-columns: 1fr; flex-wrap: wrap;
gap: 1em 0; }
text-align: center;
.timetable_progress-distance {
font-size: 1.15em; margin-right: 0.25em;
} }
.train-stats { .comments {
font-size: 1.1em; display: flex;
} align-items: center;
.train_general { font-size: 0.9em;
justify-content: center;
} margin-top: 1em;
.train-status-badges { img {
justify-content: center; margin-right: 0.5em;
} }
}
.timetable_route {
justify-content: center; @include smallScreen() {
} .train-info {
grid-template-columns: 1fr;
.timetable_progress { gap: 1em 0;
justify-content: center; text-align: center;
}
font-size: 1.15em;
.comments { }
flex-direction: column;
justify-content: center; .train-stats {
font-size: 1.1em;
img { }
margin: 0 0 0.5em 0;
} .train_general {
} justify-content: center;
} }
</style>
.train-status-badges {
justify-content: center;
}
.timetable_route {
justify-content: center;
}
.timetable_progress {
justify-content: center;
}
.comments {
flex-direction: column;
justify-content: center;
img {
margin: 0 0 0.5em 0;
}
}
}
</style>
+229 -226
View File
@@ -1,226 +1,229 @@
<template> <template>
<div class="filters-options" @keydown.esc="showOptions = false"> <div class="filters-options" @keydown.esc="showOptions = false">
<div class="bg" v-if="showOptions" @click="showOptions = false"></div> <div class="bg" v-if="showOptions" @click="showOptions = false"></div>
<button class="filter-button btn--filled btn--image" @click="toggleShowOptions" ref="button"> <button class="filter-button btn--filled btn--image" @click="toggleShowOptions" ref="button">
<img :src="getIcon('filter2')" alt="Open filters" /> <img :src="getIcon('filter2')" alt="Open filters" />
{{ $t('options.filters') }} [F] {{ $t('options.filters') }} [F]
<span class="active-indicator" v-if="currentOptionsActive"></span> <span class="active-indicator" v-if="currentOptionsActive"></span>
</button> </button>
<transition name="options-anim"> <transition name="options-anim">
<div class="options_wrapper" v-if="showOptions"> <div class="options_wrapper" v-if="showOptions">
<div class="options_content"> <div class="options_content">
<h1 class="option-title">{{ $t('options.search-title') }}</h1> <h1 class="option-title">{{ $t('options.search-title') }}</h1>
<div class="search_content"> <div class="search_content">
<div class="search-box"> <div class="search-box">
<input <input
class="search-input" class="search-input"
ref="initFocusedElement" ref="initFocusedElement"
@focus="preventKeyDown = true" @focus="preventKeyDown = true"
@blur="preventKeyDown = false" @blur="preventKeyDown = false"
:placeholder="$t(`options.search-train`)" :placeholder="$t(`options.search-train`)"
v-model="searchedTrain" v-model="searchedTrain"
/> />
<button class="search-exit"> <button class="search-exit">
<img :src="getIcon('exit')" alt="exit-icon" @click="onInputClear('train')" /> <img :src="getIcon('exit')" alt="exit-icon" @click="onInputClear('train')" />
</button> </button>
</div> </div>
<div class="search-box"> <div class="search-box">
<input <input
class="search-input" class="search-input"
@focus="preventKeyDown = true" @focus="preventKeyDown = true"
@blur="preventKeyDown = false" @blur="preventKeyDown = false"
:placeholder="$t(`options.search-driver`)" :placeholder="$t(`options.search-driver`)"
v-model="searchedDriver" v-model="searchedDriver"
/> />
<button class="search-exit"> <button class="search-exit">
<img :src="getIcon('exit')" alt="exit-icon" @click="onInputClear('driver')" /> <img :src="getIcon('exit')" alt="exit-icon" @click="onInputClear('driver')" />
</button> </button>
</div> </div>
</div> </div>
<h1 class="option-title">{{ $t('options.sort-title') }}</h1> <h1 class="option-title">{{ $t('options.sort-title') }}</h1>
<div class="options_sorters"> <div class="options_sorters">
<button <button
v-for="opt in translatedSorterOptions" v-for="opt in translatedSorterOptions"
class="sort-option btn--option" :key="opt.id"
:data-selected="opt.id == sorterActive.id" class="sort-option btn--option"
@click="onSorterChange(opt)" :data-selected="opt.id == sorterActive.id"
> @click="onSorterChange(opt)"
{{ opt.value.toUpperCase() }} >
</button> {{ opt.value.toUpperCase() }}
</div> </button>
</div>
<h1 class="option-title" v-if="trainFilterList.length != 0">{{ $t('options.filter-title') }}</h1>
<h1 class="option-title" v-if="trainFilterList.length != 0">
<div class="options_filters"> {{ $t('options.filter-title') }}
<div v-for="section in Object.keys(TrainFilterSection)"> </h1>
<button
class="btn--option" <div class="options_filters">
v-for="filter in trainFilterList.filter((f) => f.section == section)" <div v-for="section in Object.keys(TrainFilterSection)" :key="section">
:data-inactive="!filter.isActive" <button
@click="onFilterChange(filter)" class="btn--option"
> v-for="filter in trainFilterList.filter((f) => f.section == section)"
{{ $t(`options.filter-${filter.id}`) }} :key="filter.id"
</button> :data-inactive="!filter.isActive"
</div> @click="onFilterChange(filter)"
</div> >
{{ $t(`options.filter-${filter.id}`) }}
<div class="filter-actions"> </button>
<div></div> </div>
<button class="btn--action" @click="resetAllFilters">{{ $t('options.filter-reset') }}</button> </div>
</div>
</div> <div class="filter-actions">
</div> <div></div>
</transition> <button class="btn--action" @click="resetAllFilters">
</div> {{ $t('options.filter-reset') }}
</template> </button>
</div>
<script lang="ts"> </div>
import { defineComponent, inject, PropType } from 'vue'; </div>
import imageMixin from '../../mixins/imageMixin'; </transition>
import keyMixin from '../../mixins/keyMixin'; </div>
import ActionButton from '../Global/ActionButton.vue'; </template>
import SelectBox from '../Global/SelectBox.vue';
import { TrainFilterSection } from '../../scripts/enums/TrainFilterType'; <script lang="ts">
import { TrainFilter } from '../../scripts/interfaces/Trains/TrainFilter'; import { defineComponent, inject, PropType } from 'vue';
import imageMixin from '../../mixins/imageMixin';
export default defineComponent({ import keyMixin from '../../mixins/keyMixin';
components: { SelectBox, ActionButton }, import { TrainFilterSection } from '../../scripts/enums/TrainFilterType';
mixins: [imageMixin, keyMixin], import { TrainFilter } from '../../scripts/interfaces/Trains/TrainFilter';
props: { export default defineComponent({
sorterOptionIds: { mixins: [imageMixin, keyMixin],
type: Array as PropType<Array<string>>,
required: true, props: {
}, sorterOptionIds: {
type: Array as PropType<Array<string>>,
currentOptionsActive: { required: true
type: Boolean, },
default: false,
}, currentOptionsActive: {
}, type: Boolean,
default: false
data() { }
return { },
showOptions: false,
lastSelectedFilter: null as TrainFilter | null, data() {
TrainFilterSection, return {
}; showOptions: false,
}, lastSelectedFilter: null as TrainFilter | null,
TrainFilterSection
setup() { };
return { },
searchedTrain: inject('searchedTrain') as string,
searchedDriver: inject('searchedDriver') as string, setup() {
return {
sorterActive: inject('sorterActive') as { id: string | number; dir: number }, searchedTrain: inject('searchedTrain') as string,
trainFilterList: inject('filterList') as TrainFilter[], searchedDriver: inject('searchedDriver') as string,
};
}, sorterActive: inject('sorterActive') as { id: string | number; dir: number },
trainFilterList: inject('filterList') as TrainFilter[]
computed: { };
translatedSorterOptions() { },
return this.$props.sorterOptionIds.map((id) => ({
id, computed: {
value: this.$t(`options.sort-${id}`), translatedSorterOptions() {
})); return this.$props.sorterOptionIds.map((id) => ({
}, id,
}, value: this.$t(`options.sort-${id}`)
}));
methods: { }
// Override keyMixin function },
onKeyDownFunction() {
this.toggleShowOptions(); methods: {
}, // Override keyMixin function
onKeyDownFunction() {
toggleShowOptions() { this.toggleShowOptions();
this.showOptions = !this.showOptions; },
this.$nextTick(() => { toggleShowOptions() {
if (this.showOptions) (this.$refs['button'] as HTMLButtonElement)?.focus(); this.showOptions = !this.showOptions;
});
}, this.$nextTick(() => {
if (this.showOptions) (this.$refs['button'] as HTMLButtonElement)?.focus();
onSorterChange(item: { id: string | number; value: string }) { });
this.sorterActive.id = item.id; },
this.sorterActive.dir = -1;
}, onSorterChange(item: { id: string | number; value: string }) {
this.sorterActive.id = item.id;
onFilterChange(filter: TrainFilter) { this.sorterActive.dir = -1;
// if (this.lastSelectedFilter?.id === filter.id) },
// this.trainFilterList.forEach((tf) => (tf.isActive = filter.id === tf.id));
onFilterChange(filter: TrainFilter) {
filter.isActive = !filter.isActive; // if (this.lastSelectedFilter?.id === filter.id)
this.lastSelectedFilter = filter; // this.trainFilterList.forEach((tf) => (tf.isActive = filter.id === tf.id));
},
filter.isActive = !filter.isActive;
clearAllFilters() { this.lastSelectedFilter = filter;
this.trainFilterList.forEach((filter) => { },
filter.isActive = false;
}); clearAllFilters() {
}, this.trainFilterList.forEach((filter) => {
filter.isActive = false;
resetAllFilters() { });
this.trainFilterList.forEach((filter) => { },
filter.isActive = true;
}); resetAllFilters() {
}, this.trainFilterList.forEach((filter) => {
filter.isActive = true;
onInputClear(id: 'driver' | 'train') { });
if (id == 'driver') this.searchedDriver = ''; },
if (id == 'train') this.searchedTrain = '';
}, onInputClear(id: 'driver' | 'train') {
}, if (id == 'driver') this.searchedDriver = '';
}); if (id == 'train') this.searchedTrain = '';
</script> }
}
<style lang="scss" scoped> });
@import '../../styles/filters_options.scss'; </script>
.search_content > div { <style lang="scss" scoped>
margin: 0.5em auto; @import '../../styles/filters_options.scss';
}
.search_content > div {
.search_content > button { margin: 0.5em auto;
display: flex; }
justify-content: center;
margin: 0 auto; .search_content > button {
} display: flex;
justify-content: center;
.options_sorters { margin: 0 auto;
display: flex; }
grid-template-columns: repeat(3, 1fr);
} .options_sorters {
display: flex;
.options_filters > div { grid-template-columns: repeat(3, 1fr);
display: flex; }
width: 100%;
.options_filters > div {
gap: 0.5em; display: flex;
width: 100%;
button {
width: 100%; gap: 0.5em;
color: springgreen;
font-weight: bold; button {
width: 100%;
&[data-inactive='true'] { color: springgreen;
color: #aaa; font-weight: bold;
}
} &[data-inactive='true'] {
} color: #aaa;
}
.filter-actions { }
display: flex; }
gap: 0.5em;
width: 100%; .filter-actions {
display: flex;
margin-top: 1em; gap: 0.5em;
width: 100%;
> * {
width: 100%; margin-top: 1em;
}
} > * {
</style> width: 100%;
}
}
</style>
+31 -15
View File
@@ -13,7 +13,12 @@
<div class="schedule-wrapper" v-if="train.timetableData"> <div class="schedule-wrapper" v-if="train.timetableData">
<ul class="stop_list"> <ul class="stop_list">
<li v-for="(stop, i) in train.timetableData.followingStops" :key="i" class="stop" :class="addClasses(stop, i)"> <li
v-for="(stop, i) in train.timetableData.followingStops"
:key="i"
class="stop"
:class="addClasses(stop, i)"
>
<span class="stop_info"> <span class="stop_info">
<div class="indicator"></div> <div class="indicator"></div>
@@ -37,7 +42,12 @@
<b>{{ stop.stopNameRAW }} </b>: <span v-html="stop.comments"></span> <b>{{ stop.stopNameRAW }} </b>: <span v-html="stop.comments"></span>
</div> </div>
<span v-if="stop.departureLine == train.timetableData!.followingStops[i + 1].arrivalLine && !/sbl/gi.test(stop.departureLine!)"> <span
v-if="
stop.departureLine == train.timetableData!.followingStops[i + 1].arrivalLine &&
!/sbl/gi.test(stop.departureLine!)
"
>
{{ stop.departureLine }} {{ stop.departureLine }}
</span> </span>
@@ -59,23 +69,22 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, PropType } from '@vue/runtime-core'; import { computed, defineComponent, PropType } from 'vue';
import dateMixin from '../../mixins/dateMixin'; import dateMixin from '../../mixins/dateMixin';
import imageMixin from '../../mixins/imageMixin'; import imageMixin from '../../mixins/imageMixin';
import Train from '../../scripts/interfaces/Train'; import Train from '../../scripts/interfaces/Train';
import TrainStop from '../../scripts/interfaces/TrainStop'; import TrainStop from '../../scripts/interfaces/TrainStop';
import { useStore } from '../../store/store'; import { useStore } from '../../store/store';
import StopDate from '../Global/StopDate.vue'; import StopDate from '../Global/StopDate.vue';
import TrainThumbnail from '../Global/TrainThumbnail.vue';
import StockList from '../Global/StockList.vue'; import StockList from '../Global/StockList.vue';
export default defineComponent({ export default defineComponent({
components: { StopDate, TrainThumbnail, StockList }, components: { StopDate, StockList },
props: { props: {
train: { train: {
type: Object as PropType<Train>, type: Object as PropType<Train>,
required: true, required: true
}, }
}, },
mixins: [dateMixin, imageMixin], mixins: [dateMixin, imageMixin],
@@ -97,15 +106,21 @@ export default defineComponent({
); );
const activeMinorStopList: number[] = []; const activeMinorStopList: number[] = [];
if (lastMajorConfirmed + 1 >= props.train.timetableData!.followingStops.length) return activeMinorStopList; if (lastMajorConfirmed + 1 >= props.train.timetableData!.followingStops.length)
return activeMinorStopList;
for (let i = lastMajorConfirmed + 1; i < props.train.timetableData!.followingStops.length; i++) { for (
if (/po\.|sbl/gi.test(props.train.timetableData!.followingStops[i].stopNameRAW)) activeMinorStopList.push(i); let i = lastMajorConfirmed + 1;
i < props.train.timetableData!.followingStops.length;
i++
) {
if (/po\.|sbl/gi.test(props.train.timetableData!.followingStops[i].stopNameRAW))
activeMinorStopList.push(i);
else break; else break;
} }
return activeMinorStopList; return activeMinorStopList;
}), })
}; };
}, },
@@ -122,17 +137,18 @@ export default defineComponent({
end: stop.terminatesHere, end: stop.terminatesHere,
delayed: stop.departureDelay > 0, delayed: stop.departureDelay > 0,
sbl: /sbl/gi.test(stop.stopName), sbl: /sbl/gi.test(stop.stopName),
[stop.stopType.replaceAll(', ', '-')]: stop.stopType.match(new RegExp('ph|pm|pt')) && !stop.confirmed && !stop.beginsHere, [stop.stopType.replaceAll(', ', '-')]:
stop.stopType.match(new RegExp('ph|pm|pt')) && !stop.confirmed && !stop.beginsHere,
'minor-stop-active': this.activeMinorStops.includes(index), 'minor-stop-active': this.activeMinorStops.includes(index),
'last-confirmed': index == this.lastConfirmed && !stop.terminatesHere, 'last-confirmed': index == this.lastConfirmed && !stop.terminatesHere
}; };
}, },
onImageError(e: Event) { onImageError(e: Event) {
const imageEl = e.target as HTMLImageElement; const imageEl = e.target as HTMLImageElement;
imageEl.src = this.getImage('unknown.png'); imageEl.src = this.getImage('unknown.png');
}, }
}, }
}); });
</script> </script>
+210 -207
View File
@@ -1,207 +1,210 @@
<template> <template>
<div class="train-table"> <div class="train-table">
<transition name="anim" mode="out-in"> <transition name="anim" mode="out-in">
<div :key="store.dataStatuses.trains"> <div :key="store.dataStatuses.trains">
<div class="table-info" v-if="store.isOffline"> <div class="table-info" v-if="store.isOffline">
{{ $t('app.offline') }} {{ $t('app.offline') }}
</div> </div>
<Loading v-else-if="trains.length == 0 && store.dataStatuses.trains == 0" /> <Loading v-else-if="trains.length == 0 && store.dataStatuses.trains == 0" />
<div class="table-info no-trains" v-else-if="trains.length == 0 && store.dataStatuses.trains != 0"> <div
{{ $t('trains.no-trains') }} class="table-info no-trains"
</div> v-else-if="trains.length == 0 && store.dataStatuses.trains != 0"
>
<transition-group name="list-anim" tag="ul" class="train-list" v-else> {{ $t('trains.no-trains') }}
<li </div>
class="train-row"
v-for="train in currentTrains" <transition-group name="list-anim" tag="ul" class="train-list" v-else>
:key="train.trainId" <li
tabindex="0" class="train-row"
@click.stop="selectModalTrain(train.trainId, $event.currentTarget)" v-for="train in currentTrains"
@keydown.enter="selectModalTrain(train.trainId, $event.currentTarget)" :key="train.trainId"
> tabindex="0"
<TrainInfo :train="train" /> @click.stop="selectModalTrain(train.trainId, $event.currentTarget)"
</li> @keydown.enter="selectModalTrain(train.trainId, $event.currentTarget)"
</transition-group> >
</div> <TrainInfo :train="train" />
</transition> </li>
</div> </transition-group>
</template> </div>
</transition>
<script lang="ts"> </div>
import { computed, defineComponent, inject, PropType, Ref } from 'vue'; </template>
import modalTrainMixin from '../../mixins/modalTrainMixin';
import returnBtnMixin from '../../mixins/returnBtnMixin'; <script lang="ts">
import Train from '../../scripts/interfaces/Train'; import { computed, defineComponent, inject, PropType, Ref } from 'vue';
import { useStore } from '../../store/store'; import modalTrainMixin from '../../mixins/modalTrainMixin';
import Loading from '../Global/Loading.vue'; import returnBtnMixin from '../../mixins/returnBtnMixin';
import TrainInfo from './TrainInfo.vue'; import Train from '../../scripts/interfaces/Train';
import { useStore } from '../../store/store';
export default defineComponent({ import Loading from '../Global/Loading.vue';
components: { Loading, TrainInfo }, import TrainInfo from './TrainInfo.vue';
props: { export default defineComponent({
trains: { components: { Loading, TrainInfo },
type: Array as PropType<Train[]>,
required: true, props: {
}, trains: {
}, type: Array as PropType<Train[]>,
required: true
mixins: [returnBtnMixin, modalTrainMixin], }
},
setup(props) {
const store = useStore(); mixins: [returnBtnMixin, modalTrainMixin],
const searchedTrain = inject('searchedTrain') as Ref<string>;
const searchedDriver = inject('searchedDriver') as Ref<string>; setup(props) {
const currentTrains = computed(() => { const store = useStore();
return props.trains; const searchedTrain = inject('searchedTrain') as Ref<string>;
}); const searchedDriver = inject('searchedDriver') as Ref<string>;
const currentTrains = computed(() => {
return { return props.trains;
searchedTrain, });
searchedDriver,
currentTrains, return {
store, searchedTrain,
sorterActive: inject('sorterActive') as { searchedDriver,
id: string | number; currentTrains,
dir: number; store,
}, sorterActive: inject('sorterActive') as {
}; id: string | number;
}, dir: number;
}
activated() { };
const query = this.$route.query; },
if (query.trainNo && query.driverName) {
this.searchedDriver = query.driverName.toString(); activated() {
this.searchedTrain = query.trainNo.toString(); const query = this.$route.query;
setTimeout(() => { if (query.trainNo && query.driverName) {
this.selectModalTrain(query.driverName! + query.trainNo!.toString()); this.searchedDriver = query.driverName.toString();
}, 20); this.searchedTrain = query.trainNo.toString();
} setTimeout(() => {
}, this.selectModalTrain(query.driverName! + query.trainNo!.toString());
}); }, 20);
</script> }
}
<style lang="scss" scoped> });
@import '../../styles/responsive.scss'; </script>
@import '../../styles/animations.scss';
<style lang="scss" scoped>
.anim { @import '../../styles/responsive.scss';
&-enter-from, @import '../../styles/animations.scss';
&-leave-to {
opacity: 0; .anim {
} &-enter-from,
&-leave-to {
&-enter-active { opacity: 0;
transition: all 100ms ease-out; }
}
&-enter-active {
&-leave-active { transition: all 100ms ease-out;
transition: all 100ms ease-out; }
}
} &-leave-active {
transition: all 100ms ease-out;
.table-info { }
text-align: center; }
padding: 1em 0; .table-info {
text-align: center;
font-size: 1.5em;
padding: 1em 0;
background: #1a1a1a;
} font-size: 1.5em;
img.train-image { background: #1a1a1a;
width: 12em; }
}
img.train-image {
.traffic-warning { width: 12em;
padding: 1em 0; }
margin-bottom: 0.5em;
background: var(--clr-warning); .traffic-warning {
} padding: 1em 0;
margin-bottom: 0.5em;
.timeouts-warning { background: var(--clr-warning);
background-color: #333; }
font-weight: bold; .timeouts-warning {
font-size: 1.05em; background-color: #333;
margin-bottom: 0.5em; font-weight: bold;
padding: 0.5em; font-size: 1.05em;
}
margin-bottom: 0.5em;
.warning-timeout { padding: 0.5em;
background-color: #be3728; }
color: white;
.warning-timeout {
display: inline-block; background-color: #be3728;
text-align: center; color: white;
width: 1.25em; display: inline-block;
height: 1.25em; text-align: center;
border-radius: 50%;
} width: 1.25em;
height: 1.25em;
.train { border-radius: 50%;
&-list { }
position: relative;
.train {
@include smallScreen() { &-list {
width: 100%; position: relative;
}
} @include smallScreen() {
width: 100%;
&-row { }
background-color: var(--clr-secondary); }
margin-bottom: 1em;
&-row {
cursor: pointer; background-color: var(--clr-secondary);
} margin-bottom: 1em;
&_cars { cursor: pointer;
display: flex; }
align-items: center;
&_cars {
overflow: auto; display: flex;
} align-items: center;
}
overflow: auto;
.paginator { }
display: flex; }
justify-content: center;
.paginator {
&_item { display: flex;
padding: 0.25em 0.5em; justify-content: center;
margin: 0 0.5em;
outline: 2px solid salmon; &_item {
padding: 0.25em 0.5em;
min-width: 30px; margin: 0 0.5em;
outline: 2px solid salmon;
text-align: center;
min-width: 30px;
cursor: pointer;
text-align: center;
&.page-number {
font-weight: bold; cursor: pointer;
color: gold;
} &.page-number {
font-weight: bold;
&.disabled { color: gold;
outline: 2px solid lightgray; }
color: lightgray;
} &.disabled {
outline: 2px solid lightgray;
&:focus { color: lightgray;
outline: 2px solid white; }
}
} &:focus {
} outline: 2px solid white;
}
@include smallScreen() { }
.info-bottom { }
text-align: center;
} @include smallScreen() {
} .info-bottom {
</style> text-align: center;
}
}
</style>
@@ -1,46 +1,46 @@
import { JournalFilterSection, JournalFilterType } from '../../scripts/enums/JournalFilterType'; import { JournalFilterSection, JournalFilterType } from '../../scripts/enums/JournalFilterType';
import { JournalFilter } from '../../scripts/types/JournalTimetablesTypes'; import { JournalFilter } from '../../scripts/types/JournalTimetablesTypes';
export const journalTimetableFilters: JournalFilter[] = [ export const journalTimetableFilters: JournalFilter[] = [
{ {
id: JournalFilterType.ALL, id: JournalFilterType.ALL,
filterSection: JournalFilterSection.TIMETABLE_STATUS, filterSection: JournalFilterSection.TIMETABLE_STATUS,
isActive: true, isActive: true
}, },
{ {
id: JournalFilterType.ACTIVE, id: JournalFilterType.ACTIVE,
filterSection: JournalFilterSection.TIMETABLE_STATUS, filterSection: JournalFilterSection.TIMETABLE_STATUS,
isActive: false, isActive: false
}, },
{ {
id: JournalFilterType.FULFILLED, id: JournalFilterType.FULFILLED,
filterSection: JournalFilterSection.TIMETABLE_STATUS, filterSection: JournalFilterSection.TIMETABLE_STATUS,
isActive: false, isActive: false
}, },
{ {
id: JournalFilterType.ABANDONED, id: JournalFilterType.ABANDONED,
filterSection: JournalFilterSection.TIMETABLE_STATUS, filterSection: JournalFilterSection.TIMETABLE_STATUS,
isActive: false, isActive: false
}, },
{ {
id: JournalFilterType.TWR_SKR, id: JournalFilterType.TWR_SKR,
filterSection: JournalFilterSection.TWRSKR, filterSection: JournalFilterSection.TWRSKR,
isActive: true, isActive: true
}, },
{ {
id: JournalFilterType.TWR, id: JournalFilterType.TWR,
filterSection: JournalFilterSection.TWRSKR, filterSection: JournalFilterSection.TWRSKR,
isActive: false, isActive: false
}, },
{ {
id: JournalFilterType.SKR, id: JournalFilterType.SKR,
filterSection: JournalFilterSection.TWRSKR, filterSection: JournalFilterSection.TWRSKR,
isActive: false, isActive: false
}, }
]; ];
+89 -89
View File
@@ -1,89 +1,89 @@
import { TrainFilterSection, TrainFilterType } from '../../scripts/enums/TrainFilterType'; import { TrainFilterSection, TrainFilterType } from '../../scripts/enums/TrainFilterType';
import { TrainFilter } from '../../scripts/interfaces/Trains/TrainFilter'; import { TrainFilter } from '../../scripts/interfaces/Trains/TrainFilter';
export const trainFilters: TrainFilter[] = [ export const trainFilters: TrainFilter[] = [
{ {
id: TrainFilterType.twr, id: TrainFilterType.twr,
section: TrainFilterSection.TRAIN_TYPE, section: TrainFilterSection.TRAIN_TYPE,
isActive: true, isActive: true
}, },
{ {
id: TrainFilterType.skr, id: TrainFilterType.skr,
section: TrainFilterSection.TRAIN_TYPE, section: TrainFilterSection.TRAIN_TYPE,
isActive: true, isActive: true
}, },
{ {
id: TrainFilterType.common, id: TrainFilterType.common,
section: TrainFilterSection.TRAIN_TYPE, section: TrainFilterSection.TRAIN_TYPE,
isActive: true, isActive: true
}, },
{ {
id: TrainFilterType.passenger, id: TrainFilterType.passenger,
section: TrainFilterSection.TIMETABLE_TYPE, section: TrainFilterSection.TIMETABLE_TYPE,
isActive: true, isActive: true
}, },
{ {
id: TrainFilterType.freight, id: TrainFilterType.freight,
section: TrainFilterSection.TIMETABLE_TYPE, section: TrainFilterSection.TIMETABLE_TYPE,
isActive: true, isActive: true
}, },
{ {
id: TrainFilterType.other, id: TrainFilterType.other,
section: TrainFilterSection.TIMETABLE_TYPE, section: TrainFilterSection.TIMETABLE_TYPE,
isActive: true, isActive: true
}, },
{ {
id: TrainFilterType.withComments, id: TrainFilterType.withComments,
section: TrainFilterSection.COMMENTS, section: TrainFilterSection.COMMENTS,
isActive: true, isActive: true
}, },
{ {
id: TrainFilterType.noComments, id: TrainFilterType.noComments,
section: TrainFilterSection.COMMENTS, section: TrainFilterSection.COMMENTS,
isActive: true, isActive: true
}, },
{ {
id: TrainFilterType.withTimetable, id: TrainFilterType.withTimetable,
section: TrainFilterSection.TIMETABLE, section: TrainFilterSection.TIMETABLE,
isActive: true, isActive: true
}, },
{ {
id: TrainFilterType.noTimetable, id: TrainFilterType.noTimetable,
section: TrainFilterSection.TIMETABLE, section: TrainFilterSection.TIMETABLE,
isActive: true, isActive: true
}, }
]; ];
export const sorterOptions = [ export const sorterOptions = [
{ {
id: 'distance', id: 'distance',
value: 'kilometraż', value: 'kilometraż'
}, },
{ {
id: 'id', id: 'id',
value: 'id rozkładu', value: 'id rozkładu'
}, },
{ {
id: 'progress', id: 'progress',
value: 'przebyta trasa', value: 'przebyta trasa'
}, },
{ {
id: 'delay', id: 'delay',
value: 'opóźnienie', value: 'opóźnienie'
}, },
{ {
id: 'mass', id: 'mass',
value: 'masa', value: 'masa'
}, },
{ {
id: 'speed', id: 'speed',
value: 'prędkość', value: 'prędkość'
}, },
{ {
id: 'length', id: 'length',
value: 'długość', value: 'długość'
}, }
]; ];
+23 -1
View File
@@ -1 +1,23 @@
["EP07-356","EP07-356","EP07-356","ET41-074","2EN57-694+716rb","EU07E-083","EN57-716rb","EN57-716rb","EN57-716rb","EN57-038rb","EN57-038rb","SM42-329_PLREG","2EN57-038+1715rb","EN57-1953rb","EN57-1953rb","SM42-1121","SM42-091","SM42-404","SM42-404","EN57-1914rb","EN57-961rb"] [
"EP07-356",
"EP07-356",
"EP07-356",
"ET41-074",
"2EN57-694+716rb",
"EU07E-083",
"EN57-716rb",
"EN57-716rb",
"EN57-716rb",
"EN57-038rb",
"EN57-038rb",
"SM42-329_PLREG",
"2EN57-038+1715rb",
"EN57-1953rb",
"EN57-1953rb",
"SM42-1121",
"SM42-091",
"SM42-404",
"SM42-404",
"EN57-1914rb",
"EN57-961rb"
]
+59 -6
View File
@@ -2650,7 +2650,16 @@
], ],
"route": "DOBRZYNIEC|CZERMIN", "route": "DOBRZYNIEC|CZERMIN",
"timetableId": 441366, "timetableId": 441366,
"sceneries": ["2ce4e4b8", "e89b093c", "7fc24616", "9e8e828e", "a08efab9", "32d49e1d", "89fcee89", "beca9dd8"] "sceneries": [
"2ce4e4b8",
"e89b093c",
"7fc24616",
"9e8e828e",
"a08efab9",
"32d49e1d",
"89fcee89",
"beca9dd8"
]
} }
}, },
{ {
@@ -4624,7 +4633,18 @@
], ],
"route": "CZERMIN|Zakopane", "route": "CZERMIN|Zakopane",
"timetableId": 441329, "timetableId": 441329,
"sceneries": ["2a60af79", "9e8e828e", "89fcee89", "32d49e1d", "e89b093c", "d60a1f02", "4e0599d3", "beca9dd8", "b7fea344", "2ce4e4b8"] "sceneries": [
"2a60af79",
"9e8e828e",
"89fcee89",
"32d49e1d",
"e89b093c",
"d60a1f02",
"4e0599d3",
"beca9dd8",
"b7fea344",
"2ce4e4b8"
]
} }
}, },
{ {
@@ -4940,7 +4960,15 @@
], ],
"route": "Suszec Kopalnia|Wielichowo Główne gt", "route": "Suszec Kopalnia|Wielichowo Główne gt",
"timetableId": 441331, "timetableId": 441331,
"sceneries": ["a07a1966", "70717e39", "9e8e828e", "d60a1f02", "89fcee89", "beca9dd8", "8ce88788"] "sceneries": [
"a07a1966",
"70717e39",
"9e8e828e",
"d60a1f02",
"89fcee89",
"beca9dd8",
"8ce88788"
]
} }
}, },
{ {
@@ -6620,7 +6648,15 @@
], ],
"route": "ŁAPANÓW|LISKÓW", "route": "ŁAPANÓW|LISKÓW",
"timetableId": 441339, "timetableId": 441339,
"sceneries": ["e2517545", "8052a490", "1800a035", "f58b0066", "beca9dd8", "a07a1966", "9e8e828e"] "sceneries": [
"e2517545",
"8052a490",
"1800a035",
"f58b0066",
"beca9dd8",
"a07a1966",
"9e8e828e"
]
} }
}, },
{ {
@@ -8902,7 +8938,15 @@
], ],
"route": "ŁAPANÓW|GRABÓW", "route": "ŁAPANÓW|GRABÓW",
"timetableId": 441348, "timetableId": 441348,
"sceneries": ["e3222787", "2ce4e4b8", "db41867c", "f58b0066", "b7fea344", "073ff753", "9e8e828e"] "sceneries": [
"e3222787",
"2ce4e4b8",
"db41867c",
"f58b0066",
"b7fea344",
"073ff753",
"9e8e828e"
]
} }
}, },
{ {
@@ -9686,7 +9730,16 @@
], ],
"route": "KRNÓW|ORNIKI", "route": "KRNÓW|ORNIKI",
"timetableId": 441355, "timetableId": 441355,
"sceneries": ["4590c058", "f58b0066", "2ce4e4b8", "b0eecdb9", "5186fd9c", "e3222787", "5d22ada6", "03cd8e91"] "sceneries": [
"4590c058",
"f58b0066",
"2ce4e4b8",
"b0eecdb9",
"5186fd9c",
"e3222787",
"5d22ada6",
"03cd8e91"
]
} }
}, },
{ {
+321 -312
View File
@@ -1,312 +1,321 @@
{ {
"optionSections": ["reality", "package-access", "access", "control", "addons", "blockades", "signals", "status"], "optionSections": [
"reality",
"options": [ "package-access",
{ "access",
"id": "real", "control",
"name": "real", "addons",
"section": "reality", "blockades",
"value": true, "signals",
"defaultValue": true "status"
}, ],
{
"id": "fictional", "options": [
"name": "fictional", {
"section": "reality", "id": "real",
"value": true, "name": "real",
"defaultValue": true "section": "reality",
}, "value": true,
{ "defaultValue": true
"id": "default", },
"name": "default", {
"section": "package-access", "id": "fictional",
"value": true, "name": "fictional",
"defaultValue": true "section": "reality",
}, "value": true,
{ "defaultValue": true
"id": "not-default", },
"name": "notDefault", {
"section": "package-access", "id": "default",
"value": true, "name": "default",
"defaultValue": true "section": "package-access",
}, "value": true,
{ "defaultValue": true
"id": "non-public", },
"name": "nonPublic", {
"section": "access", "id": "not-default",
"value": true, "name": "notDefault",
"defaultValue": true "section": "package-access",
}, "value": true,
{ "defaultValue": true
"id": "unavailable", },
"name": "unavailable", {
"section": "access", "id": "non-public",
"value": false, "name": "nonPublic",
"defaultValue": false "section": "access",
}, "value": true,
{ "defaultValue": true
"id": "abandoned", },
"name": "abandoned", {
"section": "access", "id": "unavailable",
"value": false, "name": "unavailable",
"defaultValue": false "section": "access",
}, "value": false,
{ "defaultValue": false
"id": "SPK", },
"name": "SPK", {
"section": "control", "id": "abandoned",
"value": true, "name": "abandoned",
"defaultValue": true "section": "access",
}, "value": false,
{ "defaultValue": false
"id": "SCS", },
"name": "SCS", {
"section": "control", "id": "SPK",
"value": true, "name": "SPK",
"defaultValue": true "section": "control",
}, "value": true,
{ "defaultValue": true
"id": "SPE", },
"name": "SPE", {
"section": "control", "id": "SCS",
"value": true, "name": "SCS",
"defaultValue": true "section": "control",
}, "value": true,
"defaultValue": true
{ },
"id": "SPK-M", {
"name": "mechaniczne+SPK", "id": "SPE",
"section": "control", "name": "SPE",
"value": true, "section": "control",
"defaultValue": true "value": true,
}, "defaultValue": true
{ },
"id": "SCS-M",
"name": "mechaniczne+SCS", {
"section": "control", "id": "SPK-M",
"value": true, "name": "mechaniczne+SPK",
"defaultValue": true "section": "control",
}, "value": true,
{ "defaultValue": true
"id": "mechanical", },
"name": "mechaniczne", {
"section": "control", "id": "SCS-M",
"value": true, "name": "mechaniczne+SCS",
"defaultValue": true "section": "control",
}, "value": true,
{ "defaultValue": true
"id": "SPK-R", },
"name": "ręczne+SPK", {
"section": "control", "id": "mechanical",
"value": true, "name": "mechaniczne",
"defaultValue": true "section": "control",
}, "value": true,
{ "defaultValue": true
"id": "SCS-R", },
"name": "ręczne+SCS", {
"section": "control", "id": "SPK-R",
"value": true, "name": "ręczne+SPK",
"defaultValue": true "section": "control",
}, "value": true,
{ "defaultValue": true
"id": "manual", },
"name": "ręczne", {
"section": "control", "id": "SCS-R",
"value": true, "name": "ręczne+SCS",
"defaultValue": true "section": "control",
}, "value": true,
{ "defaultValue": true
"id": "SUP", },
"name": "SUP", {
"section": "addons", "id": "manual",
"value": true, "name": "ręczne",
"defaultValue": true "section": "control",
}, "value": true,
{ "defaultValue": true
"id": "noSUP", },
"name": "noSUP", {
"section": "addons", "id": "SUP",
"value": true, "name": "SUP",
"defaultValue": true "section": "addons",
}, "value": true,
{ "defaultValue": true
"id": "SBL", },
"name": "SBL", {
"section": "blockades", "id": "noSUP",
"value": true, "name": "noSUP",
"defaultValue": true "section": "addons",
}, "value": true,
{ "defaultValue": true
"id": "PBL", },
"name": "PBL", {
"section": "blockades", "id": "SBL",
"value": true, "name": "SBL",
"defaultValue": true "section": "blockades",
}, "value": true,
{ "defaultValue": true
"id": "modern", },
"name": "współczesna", {
"section": "signals", "id": "PBL",
"value": true, "name": "PBL",
"defaultValue": true "section": "blockades",
}, "value": true,
{ "defaultValue": true
"id": "semaphores", },
"name": "kształtowa", {
"section": "signals", "id": "modern",
"value": true, "name": "współczesna",
"defaultValue": true "section": "signals",
}, "value": true,
{ "defaultValue": true
"id": "mixed", },
"name": "mieszana", {
"section": "signals", "id": "semaphores",
"value": true, "name": "kształtowa",
"defaultValue": true "section": "signals",
}, "value": true,
{ "defaultValue": true
"id": "historical", },
"name": "historyczna", {
"section": "signals", "id": "mixed",
"value": true, "name": "mieszana",
"defaultValue": true "section": "signals",
}, "value": true,
"defaultValue": true
{ },
"id": "free", {
"name": "free", "id": "historical",
"name": "historyczna",
"section": "status", "section": "signals",
"value": false, "value": true,
"defaultValue": false "defaultValue": true
}, },
{
"id": "occupied", {
"name": "occupied", "id": "free",
"name": "free",
"section": "status",
"value": true, "section": "status",
"defaultValue": true "value": false,
}, "defaultValue": false
{ },
"id": "endingStatus", {
"name": "endingStatus", "id": "occupied",
"name": "occupied",
"section": "status",
"value": true, "section": "status",
"defaultValue": true "value": true,
}, "defaultValue": true
{ },
"id": "afkStatus", {
"name": "afkStatus", "id": "endingStatus",
"name": "endingStatus",
"section": "status",
"value": true, "section": "status",
"defaultValue": true "value": true,
}, "defaultValue": true
{ },
"id": "noSpaceStatus", {
"name": "noSpaceStatus", "id": "afkStatus",
"name": "afkStatus",
"section": "status",
"value": true, "section": "status",
"defaultValue": true "value": true,
}, "defaultValue": true
{ },
"id": "unavailableStatus", {
"name": "unavailableStatus", "id": "noSpaceStatus",
"name": "noSpaceStatus",
"section": "status",
"value": true, "section": "status",
"defaultValue": true "value": true,
} "defaultValue": true
], },
"sliders": [ {
{ "id": "unavailableStatus",
"id": "min-lvl", "name": "unavailableStatus",
"name": "minLevel",
"minRange": 0, "section": "status",
"maxRange": 20, "value": true,
"value": 0, "defaultValue": true
"defaultValue": 0 }
}, ],
{ "sliders": [
"id": "max-lvl", {
"name": "maxLevel", "id": "min-lvl",
"minRange": 0, "name": "minLevel",
"maxRange": 20, "minRange": 0,
"value": 20, "maxRange": 20,
"defaultValue": 20 "value": 0,
}, "defaultValue": 0
{ },
"id": "routes-1t-cat", {
"name": "minOneWayCatenary", "id": "max-lvl",
"minRange": 0, "name": "maxLevel",
"maxRange": 5, "minRange": 0,
"value": 0, "maxRange": 20,
"defaultValue": 0 "value": 20,
}, "defaultValue": 20
{ },
"id": "routes-1t-other", {
"name": "minOneWay", "id": "routes-1t-cat",
"minRange": 0, "name": "minOneWayCatenary",
"maxRange": 5, "minRange": 0,
"value": 0, "maxRange": 5,
"defaultValue": 0 "value": 0,
}, "defaultValue": 0
{ },
"id": "routes-2t-cat", {
"name": "minTwoWayCatenary", "id": "routes-1t-other",
"minRange": 0, "name": "minOneWay",
"maxRange": 5, "minRange": 0,
"value": 0, "maxRange": 5,
"defaultValue": 0 "value": 0,
}, "defaultValue": 0
{ },
"id": "routes-2t-other", {
"name": "minTwoWay", "id": "routes-2t-cat",
"minRange": 0, "name": "minTwoWayCatenary",
"maxRange": 5, "minRange": 0,
"value": 0, "maxRange": 5,
"defaultValue": 0 "value": 0,
} "defaultValue": 0
], },
"modes": [ {
{ "id": "routes-2t-other",
"id": "include-selected", "name": "minTwoWay",
"name": "include-selected", "minRange": 0,
"section": "mode", "maxRange": 5,
"value": true, "value": 0,
"defaultValue": true "defaultValue": 0
}, }
{ ],
"id": "save", "modes": [
"name": "save", {
"section": "mode", "id": "include-selected",
"value": true, "name": "include-selected",
"defaultValue": true "section": "mode",
} "value": true,
], "defaultValue": true
"regions": [ },
{ {
"id": "eu", "id": "save",
"value": "PL1" "name": "save",
}, "section": "mode",
{ "value": true,
"id": "cae", "defaultValue": true
"value": "PL2" }
}, ],
{ "regions": [
"id": "usw", {
"value": "DE" "id": "eu",
}, "value": "PL1"
{ },
"id": "us", {
"value": "CZE" "id": "cae",
}, "value": "PL2"
{ },
"id": "ru", {
"value": "ENG" "id": "usw",
} "value": "DE"
] },
} {
"id": "us",
"value": "CZE"
},
{
"id": "ru",
"value": "ENG"
}
]
}
+433 -433
View File
@@ -1,433 +1,433 @@
{ {
"general": { "general": {
"and": " and ", "and": " and ",
"refresh": "REFRESH", "refresh": "REFRESH",
"TWR": "High risk freight train", "TWR": "High risk freight train",
"SKR": "Train with exceeded gauge" "SKR": "Train with exceeded gauge"
}, },
"app": { "app": {
"sceneries": "SCENERIES", "sceneries": "SCENERIES",
"trains": "TRAINS", "trains": "TRAINS",
"journal": "JOURNAL", "journal": "JOURNAL",
"loading": "Loading data...", "loading": "Loading data...",
"support": "Support the project", "support": "Support the project",
"error": "An error occured while loading data!", "error": "An error occured while loading data!",
"no-result": "No results for current search!", "no-result": "No results for current search!",
"migration-warning": "Stacjownik services will be unavailable 2/06/2022 between 1-3am (CEST time) due to the migration of API hostings!", "migration-warning": "Stacjownik services will be unavailable 2/06/2022 between 1-3am (CEST time) due to the migration of API hostings!",
"migration-confirm": "Roger that!", "migration-confirm": "Roger that!",
"offline": "App is in the offline mode!" "offline": "App is in the offline mode!"
}, },
"footer": { "footer": {
"discord": "Stacjownik Discord server" "discord": "Stacjownik Discord server"
}, },
"update": { "update": {
"title": "New version of the app is available!", "title": "New version of the app is available!",
"paragraph1": "Enjoy the application and may the green signal be with you!", "paragraph1": "Enjoy the application and may the green signal be with you!",
"release-link": "Click here to browse version changelog (GitHub)", "release-link": "Click here to browse version changelog (GitHub)",
"confirm-button": "UPDATE NOW", "confirm-button": "UPDATE NOW",
"later-button": "LATER" "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!",
"S1a-connection": "<b>S1a signal</b> <br> Cannot connect with Stacjownik API service!", "S1a-connection": "<b>S1a signal</b> <br> Cannot connect with Stacjownik API service!",
"S1a-sceneries": "<b>S1a signal</b> <br> Cannot load online stations data!", "S1a-sceneries": "<b>S1a signal</b> <br> Cannot load online stations data!",
"S2": "<b>S2 signal</b> <br> All data loaded successfully!", "S2": "<b>S2 signal</b> <br> All data loaded successfully!",
"S3": "<b>S3 signal</b> <br> Loading data...", "S3": "<b>S3 signal</b> <br> Loading data...",
"S5-timetables": "<b>S5 signal</b> <br> Timetables might be incorrect or missing!", "S5-timetables": "<b>S5 signal</b> <br> Timetables might be incorrect or missing!",
"S5-dispatchers": "<b>S5 signal</b> <br> Cannot load dispatchers status data!", "S5-dispatchers": "<b>S5 signal</b> <br> Cannot load dispatchers status data!",
"S5-trains": "<b>S5 signal</b> <br> Cannot load online trains data!" "S5-trains": "<b>S5 signal</b> <br> Cannot load online trains data!"
}, },
"desc": { "desc": {
"control-type": "Control type: ", "control-type": "Control type: ",
"signals-type": "Signals type: ", "signals-type": "Signals type: ",
"SBL": "This scenery has automatic block signalling (ABS/SBL) system on following routes: ", "SBL": "This scenery has automatic block signalling (ABS/SBL) system on following routes: ",
"SUP": "Requires the SUP application (level crossing remote control simulator)", "SUP": "Requires the SUP application (level crossing remote control simulator)",
"TWB-all": "This scenery has two-way route blockade on all routes", "TWB-all": "This scenery has two-way route blockade on all routes",
"TWB-routes": "This scenery has two-way route blockade on following routes: ", "TWB-routes": "This scenery has two-way route blockade on following routes: ",
"default": "This scenery is available by default", "default": "This scenery is available by default",
"non-public": "This scenery is not public", "non-public": "This scenery is not public",
"unknown": "This scenery isn't recognizable right now", "unknown": "This scenery isn't recognizable right now",
"unavailable": "This scenery is unavailable", "unavailable": "This scenery is unavailable",
"abandoned": "This scenery is no longer supported by its creators", "abandoned": "This scenery is no longer supported by its creators",
"real": "Scenery with real lines: " "real": "Scenery with real lines: "
}, },
"signals": { "signals": {
"title": "Signal type", "title": "Signal type",
"współczesna": "modern", "współczesna": "modern",
"mieszana": "mixed", "mieszana": "mixed",
"kształtowa": "mechanical", "kształtowa": "mechanical",
"historyczna": "historical" "historyczna": "historical"
}, },
"controls": { "controls": {
"title": "Control type", "title": "Control type",
"SPK": "SPK", "SPK": "SPK",
"SCS": "SCS", "SCS": "SCS",
"SCS-SPK": "SCS/SPK", "SCS-SPK": "SCS/SPK",
"SPE": "SPE", "SPE": "SPE",
"ręczne": "manual", "ręczne": "manual",
"ręczne+SPK": "manual + SPK", "ręczne+SPK": "manual + SPK",
"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"
}, },
"status": { "status": {
"online": "UNTIL ", "online": "UNTIL ",
"free": "FREE", "free": "FREE",
"ending": "ENDS SOON", "ending": "ENDS SOON",
"not-signed": "NOT SIGNED IN", "not-signed": "NOT SIGNED IN",
"no-limit": "NO LIMIT", "no-limit": "NO LIMIT",
"unavailable": "UNAVAILABLE", "unavailable": "UNAVAILABLE",
"brb": "AFK", "brb": "AFK",
"no-space": "NO SPACE", "no-space": "NO SPACE",
"unknown": "UNKNOWN" "unknown": "UNKNOWN"
}, },
"options": { "options": {
"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:",
"search-title": "SEARCH:", "search-title": "SEARCH:",
"search-train-no": "Train no. / #", "search-train-no": "Train no. / #",
"search-train": "Train no.", "search-train": "Train no.",
"search-driver": "Driver name", "search-driver": "Driver name",
"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": "Origin 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)",
"sort-mass": "mass", "sort-mass": "mass",
"sort-speed": "speed", "sort-speed": "speed",
"sort-length": "length", "sort-length": "length",
"sort-routeDistance": "route distance", "sort-routeDistance": "route distance",
"sort-timetable": "train no.", "sort-timetable": "train no.",
"sort-progress": "route progress", "sort-progress": "route progress",
"sort-delay": "current delay", "sort-delay": "current delay",
"sort-id": "timetable id", "sort-id": "timetable id",
"sort-allStopsCount": "total stops", "sort-allStopsCount": "total stops",
"sort-beginDate": "date", "sort-beginDate": "date",
"sort-timetableId": "timetable ID", "sort-timetableId": "timetable ID",
"sort-timestampFrom": "date", "sort-timestampFrom": "date",
"sort-duration": "duration", "sort-duration": "duration",
"filter-noComments": "NO COMMENTS", "filter-noComments": "NO COMMENTS",
"filter-withComments": "COMMENTS", "filter-withComments": "COMMENTS",
"filter-twr": "HIGH RISK CARGO", "filter-twr": "HIGH RISK CARGO",
"filter-skr": "EXCEEDED GAUGE", "filter-skr": "EXCEEDED GAUGE",
"filter-twr-skr": "ALL TYPES", "filter-twr-skr": "ALL TYPES",
"filter-common": "NO WARNINGS", "filter-common": "NO WARNINGS",
"filter-passenger": "PASSENGER", "filter-passenger": "PASSENGER",
"filter-freight": "FREIGHT", "filter-freight": "FREIGHT",
"filter-other": "OTHER", "filter-other": "OTHER",
"filter-noTimetable": "NO TIMETABLE", "filter-noTimetable": "NO TIMETABLE",
"filter-withTimetable": "TIMETABLE", "filter-withTimetable": "TIMETABLE",
"filter-reset": "RESET FILTERS", "filter-reset": "RESET FILTERS",
"filter-clear": "CLEAR FILTERS", "filter-clear": "CLEAR FILTERS",
"filter-section-timetable-status": "TIMETABLE STATUS", "filter-section-timetable-status": "TIMETABLE STATUS",
"filter-section-twrskr": "WARNINGS", "filter-section-twrskr": "WARNINGS",
"filter-all": "ALL ENTRIES", "filter-all": "ALL ENTRIES",
"filter-abandoned": "ABANDONED", "filter-abandoned": "ABANDONED",
"filter-fulfilled": "FULFILLED", "filter-fulfilled": "FULFILLED",
"filter-active": "ACTIVE" "filter-active": "ACTIVE"
}, },
"filters": { "filters": {
"desc": " &bull; Left mouse click: select / unselect chosen filter <br /> &bull; Double left click: unselect all filters but chosen from a <b class='text--primary'>group</b> <br /> &bull; <span style='color: coral'>RESET</span>: reset all filters from a <b class='text--primary'>group</b>", "desc": " &bull; Left mouse click: select / unselect chosen filter <br /> &bull; Double left click: unselect all filters but chosen from a <b class='text--primary'>group</b> <br /> &bull; <span style='color: coral'>RESET</span>: reset all filters from a <b class='text--primary'>group</b>",
"sections": { "sections": {
"quick": "QUICK FILTERS", "quick": "QUICK FILTERS",
"reality": "SCENERY REALITY", "reality": "SCENERY REALITY",
"package-access": "IN-GAME AVAILABILITY", "package-access": "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"
}, },
"all-available": "ALL AVAILABLE", "all-available": "ALL AVAILABLE",
"all-free": "CURRENTLY FREE", "all-free": "CURRENTLY FREE",
"endingStatus": "ENDS SOON", "endingStatus": "ENDS SOON",
"afkStatus": "AFK", "afkStatus": "AFK",
"noSpaceStatus": "NO SPACE", "noSpaceStatus": "NO SPACE",
"unavailableStatus": "UNAVAILABLE", "unavailableStatus": "UNAVAILABLE",
"title": "STATION FILTERS", "title": "STATION FILTERS",
"default": "IN-GAME", "default": "IN-GAME",
"not-default": "ADDITIONAL", "not-default": "ADDITIONAL",
"real": "REAL", "real": "REAL",
"fictional": "FICTIONAL", "fictional": "FICTIONAL",
"unavailable": "UNSUPPORTED", "unavailable": "UNSUPPORTED",
"non-public": "NON-PUBLIC", "non-public": "NON-PUBLIC",
"abandoned": "ABANDONED", "abandoned": "ABANDONED",
"SPK": "SPK", "SPK": "SPK",
"SPK-R": "SPK + MANUAL", "SPK-R": "SPK + MANUAL",
"SPK-M": "SPK + MECH.", "SPK-M": "SPK + MECH.",
"SCS": "SCS", "SCS": "SCS",
"SCS-R": "SCS + MANUAL", "SCS-R": "SCS + MANUAL",
"SCS-M": "SCS + MECH.", "SCS-M": "SCS + MECH.",
"SPE": "SPE", "SPE": "SPE",
"manual": "MANUAL", "manual": "MANUAL",
"mechanical": "MECHANICAL", "mechanical": "MECHANICAL",
"SUP": "SUP (RASP-UZK)", "SUP": "SUP (RASP-UZK)",
"noSUP": "WITHOUT SUP", "noSUP": "WITHOUT SUP",
"SBL": "AUTOMATIC (SBL)", "SBL": "AUTOMATIC (SBL)",
"PBL": "SEMIAUTOMATIC (PBL)", "PBL": "SEMIAUTOMATIC (PBL)",
"modern": "MODERN", "modern": "MODERN",
"semaphores": "SEMAPHORES", "semaphores": "SEMAPHORES",
"mixed": "MIXED", "mixed": "MIXED",
"historical": "HISTORICAL", "historical": "HISTORICAL",
"free": "FREE", "free": "FREE",
"occupied": "OCCUPIED", "occupied": "OCCUPIED",
"sliders": { "sliders": {
"min-lvl": "MIN. REQUIRED DISPATCHER LEVEL", "min-lvl": "MIN. REQUIRED DISPATCHER LEVEL",
"max-lvl": "MAX. REQUIRED DISPATCHER LEVEL", "max-lvl": "MAX. REQUIRED DISPATCHER LEVEL",
"routes-1t-cat": "MIN. CATENARY SINGLE TRACK ROUTES", "routes-1t-cat": "MIN. CATENARY SINGLE TRACK ROUTES",
"routes-1t-other": "MIN. OTHER SINGLE TRACK ROUTES", "routes-1t-other": "MIN. OTHER SINGLE TRACK ROUTES",
"routes-2t-cat": "MIN. CATENARY DOUBLE TRACK ROUTES", "routes-2t-cat": "MIN. CATENARY DOUBLE TRACK ROUTES",
"routes-2t-other": "MIN. OTHER DOUBLE TRACK ROUTES" "routes-2t-other": "MIN. OTHER DOUBLE TRACK ROUTES"
}, },
"authors-search": "Search by author (other filters apply)", "authors-search": "Search by author (other filters apply)",
"minimum-hours-title": "SHOW ONLY SCENERIES UNTIL:", "minimum-hours-title": "SHOW ONLY SCENERIES UNTIL:",
"now": "NOW", "now": "NOW",
"hour": "h", "hour": "h",
"no-limit": "NO LIMIT", "no-limit": "NO LIMIT",
"include-selected": "INCLUDE SELECTED", "include-selected": "INCLUDE SELECTED",
"save": "REMEMBER FILTERS", "save": "REMEMBER FILTERS",
"reset": "RESET FILTERS", "reset": "RESET FILTERS",
"close": "CLOSE FILTERS" "close": "CLOSE FILTERS"
}, },
"sceneries": { "sceneries": {
"station": "Station", "station": "Station",
"min-lvl": "Min. dispatcher\nlevel", "min-lvl": "Min. dispatcher\nlevel",
"status": "Status", "status": "Status",
"dispatcher": "Dispatcher", "dispatcher": "Dispatcher",
"dispatcher-lvl": "Dispatcher\nlevel", "dispatcher-lvl": "Dispatcher\nlevel",
"routes": "Routes\ndouble / single", "routes": "Routes\ndouble / single",
"general": "General info", "general": "General info",
"user": "Drivers online", "user": "Drivers online",
"spawn": "Spawns online", "spawn": "Spawns online",
"timetableAll": "Active timetables", "timetableAll": "Active timetables",
"timetableConfirmed": "Confirmed timetables", "timetableConfirmed": "Confirmed timetables",
"timetableUnconfirmed": "Unconfirmed timetables", "timetableUnconfirmed": "Unconfirmed timetables",
"no-stations": "No stations to show here!", "no-stations": "No stations to show here!",
"scenery-search": "Search for scenery..." "scenery-search": "Search for scenery..."
}, },
"trains": { "trains": {
"no-trains": "No trains to show here!", "no-trains": "No trains to show here!",
"loading": "Loading train data...", "loading": "Loading train data...",
"offline": "Offline ride", "offline": "Offline ride",
"stats": "TRAFFIC STATISTICS", "stats": "TRAFFIC STATISTICS",
"stats-speed": "TRAINS SPEED (MIN, AVG, MAX) [km/h]", "stats-speed": "TRAINS SPEED (MIN, AVG, MAX) [km/h]",
"stats-length": "TIMETABLES LENGTH (MIN, AVG, MAX) [km]", "stats-length": "TIMETABLES LENGTH (MIN, AVG, MAX) [km]",
"stats-categories": "TIMETABLE CATEGORIES", "stats-categories": "TIMETABLE CATEGORIES",
"stats-special-twr": "HIGH RISK", "stats-special-twr": "HIGH RISK",
"stats-special-skr": "EXCEEDED STRUCT. GAUGE", "stats-special-skr": "EXCEEDED STRUCT. GAUGE",
"stats-locos": "MOST COMMON UNITS", "stats-locos": "MOST COMMON UNITS",
"current-scenery": "on scenery", "current-scenery": "on scenery",
"current-signal": "at signal", "current-signal": "at signal",
"current-track": "on track", "current-track": "on track",
"delayed": "Delayed: ", "delayed": "Delayed: ",
"preponed": "Ahead of schedule: ", "preponed": "Ahead of schedule: ",
"on-time": "On time", "on-time": "On time",
"route-progress": "Progress: ", "route-progress": "Progress: ",
"detailed-timetable": "Detailed timetable for train no. ", "detailed-timetable": "Detailed timetable for train no. ",
"via-title": "Via: ", "via-title": "Via: ",
"no-timetable": "no current timetable", "no-timetable": "no current timetable",
"distance-exceeded": "Attention! Due to an internal error, timetables with route distance greater than 200km might be incorrect!", "distance-exceeded": "Attention! Due to an internal error, timetables with route distance greater than 200km might be incorrect!",
"cars": "Car count", "cars": "Car count",
"EZT": "EMU", "EZT": "EMU",
"SZT": "DMU", "SZT": "DMU",
"loco-electric": "Electric locomotive", "loco-electric": "Electric locomotive",
"loco-diesel": "Diesel locomotive", "loco-diesel": "Diesel locomotive",
"timetable-comments": "Exploitation comments available for this train", "timetable-comments": "Exploitation comments available for this train",
"comment": "Exploitation comments for: ", "comment": "Exploitation comments for: ",
"table-limit": "For performance reasons there's a limit of 10 trains shown at the same time.", "table-limit": "For performance reasons there's a limit of 10 trains shown at the same time.",
"last-seen-now": "since now", "last-seen-now": "since now",
"last-seen-min": "since one minute", "last-seen-min": "since one minute",
"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": { "journal": {
"title": "DISPATCHER HISTORY", "title": "DISPATCHER HISTORY",
"loading": "Loading dispatcher history data...", "loading": "Loading dispatcher history data...",
"no-history": "No dispatcher history found!", "no-history": "No dispatcher history found!",
"data-refreshed-at": "Data refreshed at", "data-refreshed-at": "Data refreshed at",
"section-timetables": "TIMETABLES", "section-timetables": "TIMETABLES",
"section-dispatchers": "DISPATCHERS", "section-dispatchers": "DISPATCHERS",
"no-further-data": "No further data for current parameters", "no-further-data": "No further data for current parameters",
"loading-further-data": "Loading...", "loading-further-data": "Loading...",
"route-length": "Route length:", "route-length": "Route length:",
"station-count": "Stations:", "station-count": "Stations:",
"dispatcher-name": "Author", "dispatcher-name": "Author",
"timetable-day": "Timetable created at", "timetable-day": "Timetable created at",
"timetable-active": "ACTIVE", "timetable-active": "ACTIVE",
"timetable-fulfilled": "FULFILLED", "timetable-fulfilled": "FULFILLED",
"timetable-abandoned": "ABANDONED", "timetable-abandoned": "ABANDONED",
"online-since": "ONLINE SINCE", "online-since": "ONLINE SINCE",
"duty-lasted": "The duty lasted", "duty-lasted": "The duty lasted",
"hours": "{value} hour | {value} hours", "hours": "{value} hour | {value} hours",
"minutes": "{value} min | {value} mins", "minutes": "{value} min | {value} mins",
"seconds": "{value} s", "seconds": "{value} s",
"stock-info": "EXTRA INFO", "stock-info": "EXTRA INFO",
"stock-length": "Length", "stock-length": "Length",
"stock-mass": "Mass", "stock-mass": "Mass",
"stock-max-speed": "Max. speed", "stock-max-speed": "Max. speed",
"load-data": "Load further data...", "load-data": "Load further data...",
"last-seen-at": "Last seen at", "last-seen-at": "Last seen at",
"currently-at": "Currently at", "currently-at": "Currently at",
"stats-title": "DRIVING STATISTICS OF", "stats-title": "DRIVING STATISTICS OF",
"stats-timetables": "TIMETABLES", "stats-timetables": "TIMETABLES",
"stats-longest-timetable": "LONGEST TIMETABLE", "stats-longest-timetable": "LONGEST TIMETABLE",
"stats-avg-timetable": "AVERAGE TIMETABLE LENGTH", "stats-avg-timetable": "AVERAGE TIMETABLE LENGTH",
"stats-distance": "DISTANCE", "stats-distance": "DISTANCE",
"stats-stations": "STATIONS", "stats-stations": "STATIONS",
"timetable-stats-title": "Daily stats on {date}", "timetable-stats-title": "Daily stats on {date}",
"timetable-stats-total": "Issued timetables: {count} (total distance: {distance})", "timetable-stats-total": "Issued timetables: {count} (total distance: {distance})",
"timetable-stats-longest": "The longest timetable: #{id} (made by {author} for {driver}, distance: {distance})", "timetable-stats-longest": "The longest timetable: #{id} (made by {author} for {driver}, distance: {distance})",
"timetable-stats-most-active-dr": "The most active dispatcher: {dispatcher} (created {count})", "timetable-stats-most-active-dr": "The most active dispatcher: {dispatcher} (created {count})",
"timetable-stats-most-active-dr-many": "The most active dispatchers: {dispatchers} (created {count} each)", "timetable-stats-most-active-dr-many": "The most active dispatchers: {dispatchers} (created {count} each)",
"timetable-stats-most-active-driver": "The most active driver: {driver} (total driven distance: {distance})", "timetable-stats-most-active-driver": "The most active driver: {driver} (total driven distance: {distance})",
"timetable-stats-longest-duties": "The longest service: {dispatcher} at {station} (duration: {duration})", "timetable-stats-longest-duties": "The longest service: {dispatcher} at {station} (duration: {duration})",
"timetable-count": "timetable | timetables", "timetable-count": "timetable | timetables",
"daily-stats-title": "DAILY STATS", "daily-stats-title": "DAILY STATS",
"daily-stats-info": "Today's statistics are unavailable yet!", "daily-stats-info": "Today's statistics are unavailable yet!",
"driver-stats-title": "DRIVER STATS", "driver-stats-title": "DRIVER STATS",
"driver-stats-info": "Enter a proper nickname into filters [F] to see user's driving statistics!", "driver-stats-info": "Enter a proper nickname into filters [F] to see user's driving statistics!",
"stats-loading": "Fetching statistics...", "stats-loading": "Fetching statistics...",
"stats-error": "Oops! An unexpected error occurred while trying to fetch statistics! :/", "stats-error": "Oops! An unexpected error occurred while trying to fetch statistics! :/",
"timetable-location-signal": "signal:", "timetable-location-signal": "signal:",
"timetable-location-route": "route:", "timetable-location-route": "route:",
"history-name": "Scenery name", "history-name": "Scenery name",
"history-hash": "Hash", "history-hash": "Hash",
"history-dispatcher": "Dispatcher", "history-dispatcher": "Dispatcher",
"history-level": "Level", "history-level": "Level",
"history-rate": "Rate", "history-rate": "Rate",
"history-region": "Region", "history-region": "Region",
"history-date": "Service date" "history-date": "Service date"
}, },
"scenery": { "scenery": {
"users": "PLAYERS ONLINE", "users": "PLAYERS ONLINE",
"spawns": "OPEN SPAWNS", "spawns": "OPEN SPAWNS",
"timetables": "ACTIVE TIMETABLES", "timetables": "ACTIVE TIMETABLES",
"no-timetables": "No active timetables!", "no-timetables": "No active timetables!",
"offline": "Scenery is offline", "offline": "Scenery is offline",
"no-users": "NO ACTIVE PLAYERS", "no-users": "NO ACTIVE PLAYERS",
"no-spawns": "NO OPEN SPAWNS", "no-spawns": "NO OPEN SPAWNS",
"no-scenery": "Oops! This scenery doesn't exist!", "no-scenery": "Oops! This scenery doesn't exist!",
"return-btn": "Return to main site", "return-btn": "Return to main site",
"history-btn": "View the dispatcher history", "history-btn": "View the dispatcher history",
"info-btn": "Return to the scenery view", "info-btn": "Return to the scenery view",
"authors-title": "Scenery author | Scenery authors", "authors-title": "Scenery author | Scenery authors",
"abbrev": "Station symbol:", "abbrev": "Station symbol:",
"lines-title": "Real lines", "lines-title": "Real lines",
"project-title": "Project name", "project-title": "Project name",
"one-way-routes": "One way routes", "one-way-routes": "One way routes",
"two-way-routes": "Two way routes", "two-way-routes": "Two way routes",
"option-active-timetables": "Active timetables", "option-active-timetables": "Active timetables",
"option-timetables-history": "Timetables history", "option-timetables-history": "Timetables history",
"option-dispatchers-history": "Dispatchers history", "option-dispatchers-history": "Dispatchers history",
"timetable-author-title": "Issued by", "timetable-author-title": "Issued by",
"timetable-author-unknown": "Author unknown", "timetable-author-unknown": "Author unknown",
"timetables-history-id": "ID", "timetables-history-id": "ID",
"timetables-history-number": "Number", "timetables-history-number": "Number",
"timetables-history-route": "Route", "timetables-history-route": "Route",
"timetables-history-driver": "Driver", "timetables-history-driver": "Driver",
"timetables-history-author": "TT author", "timetables-history-author": "TT author",
"timetables-history-date": "Date", "timetables-history-date": "Date",
"dispatchers-history-hash": "Hash", "dispatchers-history-hash": "Hash",
"dispatchers-history-dispatcher": "Dispatcher", "dispatchers-history-dispatcher": "Dispatcher",
"dispatchers-history-level": "Level", "dispatchers-history-level": "Level",
"dispatchers-history-rate": "Rate", "dispatchers-history-rate": "Rate",
"dispatchers-history-date": "Service date", "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!",
"forum-topic": "Official {name} forum topic", "forum-topic": "Official {name} forum topic",
"pragotron-link": "Timetable pallet board (beta)", "pragotron-link": "Timetable pallet board (beta)",
"tablice-link": "Timetable summary board (by Thundo)", "tablice-link": "Timetable summary board (by Thundo)",
"bottom-info": "Show full history in the Journal tab" "bottom-info": "Show full history in the Journal tab"
}, },
"availability": { "availability": {
"title": "Availability", "title": "Availability",
"default": "in-game", "default": "in-game",
"nonDefault": "additional", "nonDefault": "additional",
"unavailable": "unavailable", "unavailable": "unavailable",
"nonPublic": "private", "nonPublic": "private",
"abandoned": "abandoned" "abandoned": "abandoned"
}, },
"timetables": { "timetables": {
"timetable-only": "Switch to timetable-only view", "timetable-only": "Switch to timetable-only view",
"end": "Timetable terminates here", "end": "Timetable terminates here",
"terminated": "Timetable terminated", "terminated": "Timetable terminated",
"begins": "BEGINS HERE", "begins": "BEGINS HERE",
"terminates": "TERMINATES\nHERE", "terminates": "TERMINATES\nHERE",
"from": "FROM", "from": "FROM",
"to": "TO", "to": "TO",
"desc-arriving": "The train is not here yet. It's going to come from: {prevStationName} (szlak {prevDepartureLine})", "desc-arriving": "The train is not here yet. It's going to come from: {prevStationName} (szlak {prevDepartureLine})",
"desc-online": "The train is at the station. It's going to leave to: {nextStationName} (szlak {nextArrivalLine})", "desc-online": "The train is at the station. It's going to leave to: {nextStationName} (szlak {nextArrivalLine})",
"desc-stopped": "The train is at the station and is stopped. It's going to leave towards: {nextStationName} (szlak {nextArrivalLine})", "desc-stopped": "The train is at the station and is stopped. It's going to leave towards: {nextStationName} (szlak {nextArrivalLine})",
"desc-next-arrival": "Leaves towards: {nextStationName} (szlak {nextArrivalLine})", "desc-next-arrival": "Leaves towards: {nextStationName} (szlak {nextArrivalLine})",
"desc-departed": "The train is at the station and it's been departed. Leaves towards: {nextStationName} (szlak {nextArrivalLine})", "desc-departed": "The train is at the station and it's been departed. Leaves towards: {nextStationName} (szlak {nextArrivalLine})",
"desc-departed-away": "The train has been departed to: {nextStationName} (szlak {nextArrivalLine})", "desc-departed-away": "The train has been departed to: {nextStationName} (szlak {nextArrivalLine})",
"desc-end": "The train terminates here", "desc-end": "The train terminates here",
"desc-terminated": "The train has been terminated" "desc-terminated": "The train has been terminated"
}, },
"history": { "history": {
"title": "TIMETABLE JOURNAL", "title": "TIMETABLE JOURNAL",
"search-train": "Train no.", "search-train": "Train no.",
"search-driver": "Driver name" "search-driver": "Driver name"
} }
} }
+434
View File
@@ -0,0 +1,434 @@
{
"general": {
"and": " oraz ",
"refresh": "ODŚWIEŻ",
"TWR": "Towar niebezpieczny wysokiego ryzyka",
"SKR": "Przekroczona skrajnia"
},
"app": {
"sceneries": "SCENERIE",
"trains": "POCIĄGI",
"journal": "DZIENNIK",
"loading": "Pobieranie danych...",
"support": "Wspomóż projekt",
"error": "Wystąpił problem z załadowaniem danych!",
"no-result": "Brak wyników o podanych kryteriach!",
"migration-warning": "Usługi Stacjownika będą niedostępne w godzinach 1:00-3:00 2 czerwca 2022r. z powodu migracji hostingów API!",
"migration-confirm": "Przyjąłem!",
"offline": "Aplikacja w trybie offline!"
},
"footer": {
"discord": "Serwer Discord Stacjownika"
},
"update": {
"title": "Nowa wersja Stacjownika jest dostępna!",
"paragraph1": "Miłego korzystania z aplikacji i niech S2 będzie z wami!",
"release-link": "Kliknij, aby przejrzeć listę zmian (GitHub)",
"confirm-button": "ZAKTUALIZUJ",
"later-button": "PÓŹNIEJ"
},
"data-status": {
"S1-offline": "<b>Sygnał S1</b> <br> Aplikacja działa w trybie offline!",
"S1a-connection": "<b>Sygnał S1a</b> <br> Błąd podczas próby połączenia się z API Stacjownika!",
"S1a-sceneries": "<b>Sygnał S1a</b> <br> Błąd podczas pobierania danych o sceneriach online!",
"S2": "<b>Sygnał S2</b> <br> Pomyślnie załadowano dane!",
"S3": "<b>Sygnał S3</b> <br> Pobieranie danych...",
"S5-timetables": "<b>Sygnał S5</b> <br> Rozkłady jazdy mogą być niekompletne!",
"S5-dispatchers": "<b>Sygnał S5</b> <br> Błąd podczas pobierania danych o statusach dyżurnych ruchu!",
"S5-trains": "<b>Sygnał S5</b> <br> Błąd podczas pobierania danych o pociągach online!"
},
"desc": {
"control-type": "Sterowanie: ",
"signals-type": "Sygnalizacja: ",
"SBL": "Sceneria posiada SBL na szlakach: ",
"SUP": "Wymaga programu SUP do kontroli systemu RASP-UZK",
"TWB-all": "Sceneria posiada blokadę dwukierunkową na wszystkich szlakach",
"TWB-routes": "Sceneria posiada blokadę dwukierunkową na szlakach: ",
"default": "Sceneria dostępna domyślnie w paczce z grą",
"non-public": "Sceneria niepubliczna",
"unavailable": "Sceneria niedostępna",
"unknown": "Nieznana sceneria",
"real": "Sceneria z realnymi liniami kolejowymi: ",
"abandoned": "Sceneria wycofana z rozgrywki"
},
"signals": {
"title": "Sygnalizacja",
"współczesna": "współczesna",
"mieszana": "mieszana",
"kształtowa": "kształtowa",
"historyczna": "historyczna"
},
"controls": {
"title": "Sterowanie",
"SPK": "SPK",
"SCS": "SCS",
"SCS-SPK": "SCS/SPK",
"SPE": "SPE",
"ręczne": "ręczne",
"ręczne+SPK": "ręczne z SPK",
"ręczne+SCS": "ręczne z SCS",
"mechaniczne": "mechaniczne",
"mechaniczne+SPK": "mechaniczne z SPK",
"mechaniczne+SCS": "mechaniczne z SCS"
},
"status": {
"online": "DO ",
"free": "WOLNA",
"ending": "KOŃCZY",
"not-signed": "NIEZALOGOWANY",
"no-limit": "BEZ LIMITU",
"unavailable": "NIEDOSTĘPNY",
"brb": "Z/W",
"no-space": "BRAK MIEJSCA",
"unknown": "NIEZNANY"
},
"options": {
"filters": "FILTRY",
"donate": "WESPRZYJ",
"search-button": "Szukaj",
"reset-button": "Zresetuj",
"sort-title": "SORTUJ WG:",
"filter-title": "FILTRUJ WG:",
"search-title": "SZUKAJ:",
"search-train-no": "Nr pociągu",
"search-train": "Nr pociągu / #",
"search-driver": "Nick maszynisty",
"search-dispatcher": "Nick dyżurnego",
"search-station": "Nazwa scenerii",
"search-author": "Nick autora rozkładu jazdy",
"search-issuedFrom": "Sceneria początkowa",
"search-timetables-date": "Data rozkładu jazdy (UTC+2 / CEST)",
"search-dispatchers-date": "Data służby (UTC+2 / CEST)",
"search-date": "Data (UTC+2 / CEST)",
"sort-routeDistance": "kilometraż",
"sort-allStopsCount": "stacje",
"sort-beginDate": "data",
"sort-timetableId": "ID rozkładu",
"sort-timestampFrom": "data",
"sort-duration": "czas dyżuru",
"sort-id": "id rozkładu",
"sort-mass": "masa",
"sort-speed": "prędkość",
"sort-length": "długość",
"sort-timetable": "nr pociągu",
"sort-progress": "przebyta trasa",
"sort-delay": "opóźnienie",
"sort-comments": "uwagi ekspl.",
"filter-withComments": "UWAGI EKSPLOATACYJNE",
"filter-noComments": "BEZ UWAG",
"filter-twr": "WYS. RYZYKA",
"filter-skr": "SKRAJNIA",
"filter-twr-skr": "WSZYSTKIE",
"filter-common": "ZWYKŁE",
"filter-passenger": "PASAŻERSKIE",
"filter-freight": "TOWAROWE",
"filter-other": "INNE",
"filter-noTimetable": "BEZ RJ",
"filter-withTimetable": "ROZKŁAD JAZDY",
"filter-reset": "ZRESETUJ FILTRY",
"filter-clear": "WYŁĄCZ FILTRY",
"filter-section-timetable-status": "STATUS ROZKŁADU JAZDY",
"filter-section-twrskr": "UWAGI",
"filter-all": "WSZYSTKIE",
"filter-abandoned": "PORZUCONE",
"filter-fulfilled": "WYPEŁNIONE",
"filter-active": "AKTYWNE"
},
"filters": {
"desc": " &bull; Kliknięcie: zaznaczenie / odznaczenie filtru <br /> &bull; Podwójne kliknięcie: odznaczenie reszty filtrów z <b class='text--primary'>grupy</b> <br /> &bull; <span style='color: coral'>RESET</span>: zresetowanie filtrów z <b class='text--primary'>grupy</b>",
"sections": {
"quick": "SZYBKIE FILTRY",
"reality": "FIKCYJNOŚĆ SCENERII",
"package-access": "DOSTĘPNOŚĆ W PACZCE",
"access": "DOSTĘPNOŚĆ OGÓLNA",
"control": "TYP STEROWANIA",
"signals": "TYP SYGNALIZACJI",
"addons": "DODATKOWE PROGRAMY",
"blockades": "BLOKADY LINIOWE",
"status": "STATUS ONLINE"
},
"all-available": "WSZYSTKIE DOSTĘPNE",
"all-free": "WSZYSTKIE WOLNE",
"endingStatus": "KOŃCZY",
"afkStatus": "Z/W",
"noSpaceStatus": "BRAK MIEJSCA",
"unavailableStatus": "NIEDOSTĘPNY",
"title": "FILTRUJ STACJE",
"default": "DOMYŚLNA",
"not-default": "POZA PACZKĄ",
"real": "REALNA",
"fictional": "FIKCYJNA",
"unavailable": "NIEDOSTĘPNA",
"non-public": "NIEPUBLICZNA",
"abandoned": "WYCOFANA",
"SPK": "SPK",
"SPK-R": "SPK + RĘCZNE",
"SPK-M": "SPK + MECH.",
"SCS": "SCS",
"SCS-R": "SCS + RĘCZNE",
"SCS-M": "SCS + MECH.",
"SPE": "SPE",
"manual": "RĘCZNE",
"SUP": "SUP (RASP-UZK)",
"noSUP": "BEZ SUP",
"SBL": "SAMOCZYNNA",
"PBL": "PÓŁSAMOCZYNNA",
"mechanical": "MECHANICZNE",
"modern": "WSPÓŁCZESNA",
"semaphores": "KSZTAŁTOWA",
"mixed": "MIESZANA",
"historical": "HISTORYCZNA",
"free": "WOLNA",
"occupied": "ZAJĘTA",
"sliders": {
"min-lvl": "MIN. WYMAGANY POZIOM DYŻURNEGO",
"max-lvl": "MAKS. WYMAGANY POZIOM DYŻURNEGO",
"routes-1t-cat": "SZLAKI JEDNOTOROWE ZELEKTR. (MINIMUM)",
"routes-1t-other": "SZLAKI JEDNOTOROWE NIEZELEKTR. (MINIMUM)",
"routes-2t-cat": "SZLAKI DWUTOROWE ZELEKTR. (MINIMUM)",
"routes-2t-other": "SZLAKI DWUTOROWE NIEZELEKTR. (MINIMUM)"
},
"authors-search": "Szukaj autora (uwzględnia inne filtry)",
"minimum-hours-title": "POKAŻ TYLKO SCENERIE DOSTĘPNE MINIMUM DO:",
"now": "TERAZ",
"hour": " godz.",
"no-limit": "BEZ LIMITU",
"include-selected": "POKAŻ ZAZNACZONE",
"save": "ZAPAMIĘTAJ FILTRY",
"reset": "RESETUJ FILTRY",
"close": "ZAMKNIJ FILTRY"
},
"sceneries": {
"station": "Stacja",
"abbr": "Skrót\nposterunku",
"min-lvl": "Min. poziom\ndyżurnego",
"status": "Status",
"dispatcher": "Dyżurny",
"dispatcher-lvl": "Poziom\ndyżurnego",
"routes": "Szlaki\n2tor / 1tor",
"general": "Informacje\nogólne",
"user": "Maszyniści online",
"spawn": "Otwarte spawny",
"timetableAll": "Aktywne rozkłady jazdy",
"timetableConfirmed": "Zatwierdzone rozkłady jazdy",
"timetableUnconfirmed": "Niezatwierdzone rozkłady jazdy",
"no-stations": "Brak stacji do wyświetlenia!",
"scenery-search": "Wyszukaj scenerię..."
},
"trains": {
"no-trains": "Brak pociągów do wyświetlenia!",
"loading": "Pobieranie danych o pociągach...",
"offline": "Przejazd offline",
"stats": "STATYSTYKI RUCHU",
"stats-speed": "PRĘDKOŚCI POCIĄGÓW (MIN, ŚR, MAX) [km/h]",
"stats-length": "DŁUGOŚCI ROZKŁADÓW (MIN, ŚR, MAX) [km]",
"stats-categories": "KATEGORIE RJ",
"stats-special-twr": "WYSOKIEGO RYZYKA",
"stats-special-skr": "PRZEKROCZONA SKRAJNIA",
"stats-locos": "NAJCZĘSTSZE JEDNOSTKI",
"current-scenery": "na scenerii",
"current-signal": "przy semaforze",
"current-track": "na szlaku",
"delayed": "Opóźniony: ",
"preponed": "Przed czasem: ",
"on-time": "Planowo",
"route-progress": "Postęp: ",
"detailed-timetable": "Szczegółowy rozkład jazdy pociągu ",
"via-title": "Przez: ",
"no-timetable": "brak rozkładu jazdy",
"distance-exceeded": "Uwaga! Z powodu wewnętrznego błędu serwera TD2, rozkłady jazdy o kilometrażu powyżej 200km mogą być niepoprawne!",
"cars": "Wagony",
"EZT": "EZT",
"SZT": "SZT",
"loco-electric": "Elektrowóz",
"loco-diesel": "Spalinowóz",
"timetable-comments": "Pociąg z uwagami eksploatacyjnymi",
"comment": "Uwagi eksploatacyjne dla: ",
"table-limit": "Dla płynności działania strony pokazanych jest tylko 10 pociągów zgodnie z wybranymi filtrami.",
"last-seen-now": "od niedawna",
"last-seen-min": "od minuty",
"last-seen-ago": "od {minutes} minut",
"scenery-offline": "Przejazd offline",
"timeout": "Wystąpił problem z aktualizacją rozkładów jazdy z SWDR"
},
"journal": {
"title": "HISTORIA DYŻURÓW",
"loading": "Ładowanie historii dyżurów...",
"no-history": "Brak historii dyżurów dla tej scenerii!",
"data-refreshed-at": "Dane odświeżone o",
"section-timetables": "ROZKŁADY JAZDY",
"section-dispatchers": "DYŻURNI",
"no-further-data": "Brak dalszych wyników dla podanych parametrów",
"loading-further-data": "Ładowanie...",
"online-since": "ONLINE OD",
"duty-lasted": "Dyżur trwał",
"hours": "{value} godz.",
"minutes": "{value} min.",
"seconds": "{value} sek.",
"route-length": "Kilometraż:",
"station-count": "Stacje:",
"dispatcher-name": "Autor",
"timetable-day": "Rozkład z dnia",
"timetable-active": "AKTYWNY",
"timetable-fulfilled": "WYPEŁNIONY",
"timetable-abandoned": "PORZUCONY",
"stock-info": "DODATKOWE INFORMACJE",
"stock-length": "Długość",
"stock-mass": "Masa",
"stock-max-speed": "Prędkość maks.",
"load-data": "Pobierz dalszą historię...",
"stats-title": "STATYSTYKI MASZYNISTY",
"last-seen-at": "Ostatnio widziany na: ",
"currently-at": "Obecnie na scenerii: ",
"stats-timetables": "ROZKŁADY JAZDY",
"stats-longest-timetable": "NAJDŁUŻSZY RJ",
"stats-avg-timetable": "ŚREDNIA DŁUGOŚĆ RJ",
"stats-distance": "DYSTANS",
"stats-stations": "STACJE",
"timetable-stats-total": "Stworzone rozkłady jazdy: {count} (łączny dystans: {distance})",
"timetable-stats-longest": "Najdłuższy rozkład jazdy: #{id} (stworzony przez dyżurnego {author} dla maszynisty {driver} o dystansie {distance})",
"timetable-stats-most-active-dr": "Najaktywniejszy dyżurny: {dispatcher} (stworzył {count})",
"timetable-stats-most-active-dr-many": "Najaktywniejsi dyżurni: {dispatchers} (stworzyli po {count})",
"timetable-stats-most-active-driver": "Najaktywniejszy maszynista: {driver} (łączny przejechany dystans: {distance})",
"timetable-stats-longest-duties": "Najdłuższa służba: {dispatcher} na scenerii {station} (czas trwania: {duration})",
"timetable-count": "rozkład jazdy | rozkładów jazdy",
"daily-stats-title": "STATYSTYKI DNIA",
"daily-stats-info": "Dzisiejsze statystyki nie są jeszcze dostępne!",
"driver-stats-title": "STATYSTYKI GRACZA",
"driver-stats-info": "Wpisz nazwę użytkownika w filtrach [F], aby zobaczyć jego statystyki maszynisty!",
"stats-loading": "Pobieranie statystyk...",
"stats-error": "Ups! Wystąpił błąd podczas próby pobrania statystyk! :/",
"timetable-location-signal": "semafor:",
"timetable-location-route": "szlak:",
"history-name": "Sceneria",
"history-hash": "Hash",
"history-dispatcher": "Dyżurny",
"history-level": "Poziom",
"history-rate": "Ocena",
"history-region": "Region",
"history-date": "Data służby"
},
"scenery": {
"users": "GRACZE ONLINE",
"spawns": "OTWARTE SPAWNY",
"timetables": "AKTYWNE ROZKŁADY JAZDY",
"no-timetables": "Brak aktywnych rozkładów!",
"offline": "Sceneria jest offline",
"no-users": "BRAK AKTYWNYCH GRACZY",
"no-spawns": "BRAK OTWARTYCH SPAWNÓW",
"no-scenery": "Ups! Ta sceneria nie istnieje!",
"return-btn": "Wróć na stronę główną",
"history-btn": "Przejdź do widoku historii dyżurnych ruchu",
"info-btn": "Wróć do widoku scenerii",
"authors-title": "Autor scenerii | Autorzy scenerii",
"abbrev": "Skrót posterunku:",
"lines-title": "Rzeczywiste linie",
"project-title": "Projekt",
"one-way-routes": "Szlaki jednotorowe",
"two-way-routes": "Szlaki dwutorowe",
"option-active-timetables": "Aktywne rozkłady jazdy",
"option-timetables-history": "Historia rozkładów",
"option-dispatchers-history": "Historia dyżurów",
"timetable-author-title": "Wydany przez",
"timetable-author-unknown": "Autor nieznany",
"timetables-history-id": "ID",
"timetables-history-number": "Numer",
"timetables-history-route": "Trasa",
"timetables-history-driver": "Maszynista",
"timetables-history-author": "Autor RJ",
"timetables-history-date": "Data",
"dispatchers-history-hash": "Hash",
"dispatchers-history-dispatcher": "Dyżurny",
"dispatchers-history-level": "Poziom",
"dispatchers-history-rate": "Ocena",
"dispatchers-history-date": "Data służby",
"req-level": "ogólnodostępna | minimum {lvl} poziom dyżurnego | minimum {lvl} poziom dyżurnego",
"history-list-empty": "Brak historii dla tej scenerii!",
"forum-topic": "Oficjalny wątek scenerii {name}",
"pragotron-link": "Paletowa tablica informacyjna (beta)",
"tablice-link": "Tablica informacyjna zbiorcza (autorstwa Thundo)",
"bottom-info": "Pokaż pełną historię w zakładce Dziennika"
},
"availability": {
"title": "Dostępność",
"default": "w paczce",
"nonDefault": "poza paczką",
"unavailable": "niedostępna",
"nonPublic": "niepubliczna",
"abandoned": "wycofana"
},
"timetables": {
"timetable-only": "Wyodrębnij rozkłady jazdy",
"end": "Koniec rozkładu jazdy",
"terminated": "Rozkład jazdy zakończony",
"begins": "ROZPOCZYNA\nBIEG",
"terminates": "KOŃCZY BIEG",
"from": "Z",
"to": "DO",
"desc-arriving": "Pociągu nie ma jeszcze na tej scenerii. Przyjedzie z: {prevStationName} (szlak {prevDepartureLine})",
"desc-online": "Pociąg jest na tej scenerii. Odjedzie do: {nextStationName} (szlak {nextArrivalLine})",
"desc-stopped": "Pociąg jest na tej scenerii i odbywa postój. Odjedzie do: {nextStationName} (szlak {nextArrivalLine})",
"desc-next-arrival": "Odjeżdża do: {nextStationName} (szlak {nextArrivalLine})",
"desc-departed": "Pociąg jest na tej scenerii i został odprawiony. Odjeżdża do: {nextStationName} (szlak {nextArrivalLine})",
"desc-departed-away": "Pociąg został odprawiony i odjechał do: {nextStationName} (szlak {nextArrivalLine})",
"desc-end": "Pociąg kończy bieg",
"desc-terminated": "Pociąg skończył bieg"
},
"history": {
"title": "DZIENNIK ROZKŁADÓW JAZDY"
}
}
+412 -434
View File
@@ -1,434 +1,412 @@
{ {
"general": { "general": {
"and": " oraz ", "and": " oraz ",
"refresh": "ODŚWIEŻ", "refresh": "ODŚWIEŻ",
"TWR": "Towar niebezpieczny wysokiego ryzyka", "TWR": "Towar niebezpieczny wysokiego ryzyka",
"SKR": "Przekroczona skrajnia" "SKR": "Przekroczona skrajnia"
}, },
"app": { "app": {
"sceneries": "SCENERIE", "sceneries": "SCENERIE",
"trains": "POCIĄGI", "trains": "POCIĄGI",
"journal": "DZIENNIK", "journal": "DZIENNIK",
"loading": "Pobieranie danych...", "loading": "Pobieranie danych...",
"support": "Wspomóż projekt", "error": "Wystąpił problem z załadowaniem danych!",
"error": "Wystąpił problem z załadowaniem danych!", "no-result": "Brak wyników o podanych kryteriach!",
"no-result": "Brak wyników o podanych kryteriach!", "offline": "Aplikacja w trybie offline!"
"migration-warning": "Usługi Stacjownika będą niedostępne w godzinach 1:00-3:00 2 czerwca 2022r. z powodu migracji hostingów API!", },
"migration-confirm": "Przyjąłem!", "footer": {
"offline": "Aplikacja w trybie offline!" "discord": "Serwer Discord Stacjownika"
}, },
"footer": { "data-status": {
"discord": "Serwer Discord Stacjownika" "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!",
"update": { "S1a-sceneries": "<b>Sygnał S1a</b> <br> Błąd podczas pobierania danych o sceneriach online!",
"title": "Nowa wersja Stacjownika jest dostępna!", "S2": "<b>Sygnał S2</b> <br> Pomyślnie załadowano dane!",
"paragraph1": "Miłego korzystania z aplikacji i niech S2 będzie z wami!", "S3": "<b>Sygnał S3</b> <br> Pobieranie danych...",
"release-link": "Kliknij, aby przejrzeć listę zmian (GitHub)", "S5-timetables": "<b>Sygnał S5</b> <br> Rozkłady jazdy mogą być niekompletne!",
"confirm-button": "ZAKTUALIZUJ", "S5-dispatchers": "<b>Sygnał S5</b> <br> Błąd podczas pobierania danych o statusach dyżurnych ruchu!",
"later-button": "PÓŹNIEJ" "S5-trains": "<b>Sygnał S5</b> <br> Błąd podczas pobierania danych o pociągach online!"
}, },
"data-status": { "desc": {
"S1-offline": "<b>Sygnał S1</b> <br> Aplikacja działa w trybie offline!", "control-type": "Sterowanie:",
"S1a-connection": "<b>Sygnał S1a</b> <br> Błąd podczas próby połączenia się z API Stacjownika!", "signals-type": "Sygnalizacja:",
"S1a-sceneries": "<b>Sygnał S1a</b> <br> Błąd podczas pobierania danych o sceneriach online!", "SBL": "Sceneria posiada SBL na szlakach:",
"S2": "<b>Sygnał S2</b> <br> Pomyślnie załadowano dane!", "SUP": "Wymaga programu SUP do kontroli systemu RASP-UZK",
"S3": "<b>Sygnał S3</b> <br> Pobieranie danych...", "default": "Sceneria dostępna domyślnie w paczce z grą",
"S5-timetables": "<b>Sygnał S5</b> <br> Rozkłady jazdy mogą być niekompletne!", "non-public": "Sceneria niepubliczna",
"S5-dispatchers": "<b>Sygnał S5</b> <br> Błąd podczas pobierania danych o statusach dyżurnych ruchu!", "unavailable": "Sceneria niedostępna",
"S5-trains": "<b>Sygnał S5</b> <br> Błąd podczas pobierania danych o pociągach online!" "unknown": "Nieznana sceneria",
}, "real": "Sceneria z realnymi liniami kolejowymi:",
"desc": { "abandoned": "Sceneria wycofana z rozgrywki"
"control-type": "Sterowanie: ", },
"signals-type": "Sygnalizacja: ", "signals": {
"SBL": "Sceneria posiada SBL na szlakach: ", "title": "Sygnalizacja",
"SUP": "Wymaga programu SUP do kontroli systemu RASP-UZK", "współczesna": "współczesna",
"TWB-all": "Sceneria posiada blokadę dwukierunkową na wszystkich szlakach", "mieszana": "mieszana",
"TWB-routes": "Sceneria posiada blokadę dwukierunkową na szlakach: ", "kształtowa": "kształtowa",
"default": "Sceneria dostępna domyślnie w paczce z grą", "historyczna": "historyczna"
"non-public": "Sceneria niepubliczna", },
"unavailable": "Sceneria niedostępna", "controls": {
"unknown": "Nieznana sceneria", "title": "Sterowanie",
"real": "Sceneria z realnymi liniami kolejowymi: ", "SPK": "SPK",
"abandoned": "Sceneria wycofana z rozgrywki" "SCS": "SCS",
}, "SCS-SPK": "SCS/SPK",
"signals": { "SPE": "SPE",
"title": "Sygnalizacja", "ręczne": "ręczne",
"współczesna": "współczesna", "ręczne+SPK": "ręczne z SPK",
"mieszana": "mieszana", "ręczne+SCS": "ręczne z SCS",
"kształtowa": "kształtowa", "mechaniczne": "mechaniczne",
"historyczna": "historyczna" "mechaniczne+SPK": "mechaniczne z SPK",
}, "mechaniczne+SCS": "mechaniczne z SCS"
"controls": { },
"title": "Sterowanie", "status": {
"SPK": "SPK", "online": "DO ",
"SCS": "SCS", "free": "WOLNA",
"SCS-SPK": "SCS/SPK", "ending": "KOŃCZY",
"SPE": "SPE", "not-signed": "NIEZALOGOWANY",
"ręczne": "ręczne", "no-limit": "BEZ LIMITU",
"ręczne+SPK": "ręczne z SPK", "unavailable": "NIEDOSTĘPNY",
"ręczne+SCS": "ręczne z SCS", "brb": "Z/W",
"mechaniczne": "mechaniczne", "no-space": "BRAK MIEJSCA",
"mechaniczne+SPK": "mechaniczne z SPK", "unknown": "NIEZNANY"
"mechaniczne+SCS": "mechaniczne z SCS" },
}, "options": {
"status": { "filters": "FILTRY",
"online": "DO ", "donate": "WESPRZYJ",
"free": "WOLNA",
"ending": "KOŃCZY", "search-button": "Szukaj",
"not-signed": "NIEZALOGOWANY", "reset-button": "Zresetuj",
"no-limit": "BEZ LIMITU",
"unavailable": "NIEDOSTĘPNY", "sort-title": "SORTUJ WG:",
"brb": "Z/W", "filter-title": "FILTRUJ WG:",
"no-space": "BRAK MIEJSCA", "search-title": "SZUKAJ:",
"unknown": "NIEZNANY"
}, "search-train-no": "Nr pociągu",
"options": { "search-train": "Nr pociągu / #",
"filters": "FILTRY", "search-driver": "Nick maszynisty",
"donate": "WESPRZYJ", "search-dispatcher": "Nick dyżurnego",
"search-station": "Nazwa scenerii",
"search-button": "Szukaj", "search-author": "Nick autora rozkładu jazdy",
"reset-button": "Zresetuj", "search-issuedFrom": "Sceneria początkowa",
"search-timetables-date": "Data rozkładu jazdy (UTC+2 / CEST)",
"sort-title": "SORTUJ WG:", "search-dispatchers-date": "Data służby (UTC+2 / CEST)",
"filter-title": "FILTRUJ WG:", "search-date": "Data (UTC+2 / CEST)",
"search-title": "SZUKAJ:",
"sort-routeDistance": "kilometraż",
"search-train-no": "Nr pociągu", "sort-allStopsCount": "stacje",
"search-train": "Nr pociągu / #", "sort-beginDate": "data",
"search-driver": "Nick maszynisty", "sort-timetableId": "ID rozkładu",
"search-dispatcher": "Nick dyżurnego", "sort-timestampFrom": "data",
"search-station": "Nazwa scenerii", "sort-duration": "czas dyżuru",
"search-author": "Nick autora rozkładu jazdy", "sort-id": "id rozkładu",
"search-issuedFrom": "Sceneria początkowa",
"search-timetables-date": "Data rozkładu jazdy (UTC+2 / CEST)", "sort-mass": "masa",
"search-dispatchers-date": "Data służby (UTC+2 / CEST)", "sort-speed": "prędkość",
"search-date": "Data (UTC+2 / CEST)", "sort-length": "długość",
"sort-timetable": "nr pociągu",
"sort-routeDistance": "kilometraż", "sort-progress": "przebyta trasa",
"sort-allStopsCount": "stacje", "sort-delay": "opóźnienie",
"sort-beginDate": "data", "sort-comments": "uwagi ekspl.",
"sort-timetableId": "ID rozkładu",
"sort-timestampFrom": "data", "filter-withComments": "UWAGI EKSPLOATACYJNE",
"sort-duration": "czas dyżuru", "filter-noComments": "BEZ UWAG",
"sort-id": "id rozkładu", "filter-twr": "WYS. RYZYKA",
"filter-skr": "SKRAJNIA",
"sort-mass": "masa", "filter-twr-skr": "WSZYSTKIE",
"sort-speed": "prędkość", "filter-common": "ZWYKŁE",
"sort-length": "długość", "filter-passenger": "PASAŻERSKIE",
"sort-timetable": "nr pociągu", "filter-freight": "TOWAROWE",
"sort-progress": "przebyta trasa", "filter-other": "INNE",
"sort-delay": "opóźnienie", "filter-noTimetable": "BEZ RJ",
"sort-comments": "uwagi ekspl.", "filter-withTimetable": "ROZKŁAD JAZDY",
"filter-withComments": "UWAGI EKSPLOATACYJNE", "filter-reset": "ZRESETUJ FILTRY",
"filter-noComments": "BEZ UWAG", "filter-clear": "WYŁĄCZ FILTRY",
"filter-twr": "WYS. RYZYKA",
"filter-skr": "SKRAJNIA", "filter-section-timetable-status": "STATUS ROZKŁADU JAZDY",
"filter-twr-skr": "WSZYSTKIE", "filter-section-twrskr": "UWAGI",
"filter-common": "ZWYKŁE",
"filter-passenger": "PASAŻERSKIE", "filter-all": "WSZYSTKIE",
"filter-freight": "TOWAROWE", "filter-abandoned": "PORZUCONE",
"filter-other": "INNE", "filter-fulfilled": "WYPEŁNIONE",
"filter-noTimetable": "BEZ RJ", "filter-active": "AKTYWNE"
"filter-withTimetable": "ROZKŁAD JAZDY", },
"filters": {
"filter-reset": "ZRESETUJ FILTRY", "desc": " &bull; Kliknięcie: zaznaczenie / odznaczenie filtru <br /> &bull; Podwójne kliknięcie: odznaczenie reszty filtrów z <b class='text--primary'>grupy</b> <br /> &bull; <span style='color: coral'>RESET</span>: zresetowanie filtrów z <b class='text--primary'>grupy</b>",
"filter-clear": "WYŁĄCZ FILTRY",
"sections": {
"filter-section-timetable-status": "STATUS ROZKŁADU JAZDY", "quick": "SZYBKIE FILTRY",
"filter-section-twrskr": "UWAGI", "reality": "FIKCYJNOŚĆ SCENERII",
"package-access": "DOSTĘPNOŚĆ W PACZCE",
"filter-all": "WSZYSTKIE", "access": "DOSTĘPNOŚĆ OGÓLNA",
"filter-abandoned": "PORZUCONE", "control": "TYP STEROWANIA",
"filter-fulfilled": "WYPEŁNIONE", "signals": "TYP SYGNALIZACJI",
"filter-active": "AKTYWNE" "addons": "DODATKOWE PROGRAMY",
}, "blockades": "BLOKADY LINIOWE",
"filters": { "status": "STATUS ONLINE"
"desc": " &bull; Kliknięcie: zaznaczenie / odznaczenie filtru <br /> &bull; Podwójne kliknięcie: odznaczenie reszty filtrów z <b class='text--primary'>grupy</b> <br /> &bull; <span style='color: coral'>RESET</span>: zresetowanie filtrów z <b class='text--primary'>grupy</b>", },
"sections": { "all-available": "WSZYSTKIE DOSTĘPNE",
"quick": "SZYBKIE FILTRY", "all-free": "WSZYSTKIE WOLNE",
"reality": "FIKCYJNOŚĆ SCENERII",
"package-access": "DOSTĘPNOŚĆ W PACZCE", "endingStatus": "KOŃCZY",
"access": "DOSTĘPNOŚĆ OGÓLNA", "afkStatus": "Z/W",
"control": "TYP STEROWANIA", "noSpaceStatus": "BRAK MIEJSCA",
"signals": "TYP SYGNALIZACJI", "unavailableStatus": "NIEDOSTĘPNY",
"addons": "DODATKOWE PROGRAMY",
"blockades": "BLOKADY LINIOWE", "title": "FILTRUJ STACJE",
"status": "STATUS ONLINE" "default": "DOMYŚLNA",
}, "not-default": "POZA PACZKĄ",
"real": "REALNA",
"all-available": "WSZYSTKIE DOSTĘPNE", "fictional": "FIKCYJNA",
"all-free": "WSZYSTKIE WOLNE", "unavailable": "NIEDOSTĘPNA",
"non-public": "NIEPUBLICZNA",
"endingStatus": "KOŃCZY", "abandoned": "WYCOFANA",
"afkStatus": "Z/W",
"noSpaceStatus": "BRAK MIEJSCA", "SPK": "SPK",
"unavailableStatus": "NIEDOSTĘPNY", "SPK-R": "SPK + RĘCZNE",
"SPK-M": "SPK + MECH.",
"title": "FILTRUJ STACJE", "SCS": "SCS",
"default": "DOMYŚLNA", "SCS-R": "SCS + RĘCZNE",
"not-default": "POZA PACZKĄ", "SCS-M": "SCS + MECH.",
"real": "REALNA", "SPE": "SPE",
"fictional": "FIKCYJNA", "manual": "RĘCZNE",
"unavailable": "NIEDOSTĘPNA",
"non-public": "NIEPUBLICZNA", "SUP": "SUP (RASP-UZK)",
"abandoned": "WYCOFANA", "noSUP": "BEZ SUP",
"SPK": "SPK", "SBL": "SAMOCZYNNA",
"SPK-R": "SPK + RĘCZNE", "PBL": "PÓŁSAMOCZYNNA",
"SPK-M": "SPK + MECH.",
"SCS": "SCS", "mechanical": "MECHANICZNE",
"SCS-R": "SCS + RĘCZNE", "modern": "WSPÓŁCZESNA",
"SCS-M": "SCS + MECH.", "semaphores": "KSZTAŁTOWA",
"SPE": "SPE", "mixed": "MIESZANA",
"manual": "CZNE", "historical": "HISTORYCZNA",
"SUP": "SUP (RASP-UZK)", "free": "WOLNA",
"noSUP": "BEZ SUP", "occupied": "ZAJĘTA",
"SBL": "SAMOCZYNNA", "sliders": {
"PBL": "PÓŁSAMOCZYNNA", "min-lvl": "MIN. WYMAGANY POZIOM DYŻURNEGO",
"max-lvl": "MAKS. WYMAGANY POZIOM DYŻURNEGO",
"mechanical": "MECHANICZNE", "routes-1t-cat": "SZLAKI JEDNOTOROWE ZELEKTR. (MINIMUM)",
"modern": "WSPÓŁCZESNA", "routes-1t-other": "SZLAKI JEDNOTOROWE NIEZELEKTR. (MINIMUM)",
"semaphores": "KSZTAŁTOWA", "routes-2t-cat": "SZLAKI DWUTOROWE ZELEKTR. (MINIMUM)",
"mixed": "MIESZANA", "routes-2t-other": "SZLAKI DWUTOROWE NIEZELEKTR. (MINIMUM)"
"historical": "HISTORYCZNA", },
"free": "WOLNA", "authors-search": "Szukaj autora (uwzględnia inne filtry)",
"occupied": "ZAJĘTA", "minimum-hours-title": "POKAŻ TYLKO SCENERIE DOSTĘPNE MINIMUM DO:",
"now": "TERAZ",
"sliders": { "hour": " godz.",
"min-lvl": "MIN. WYMAGANY POZIOM DYŻURNEGO", "no-limit": "BEZ LIMITU",
"max-lvl": "MAKS. WYMAGANY POZIOM DYŻURNEGO", "include-selected": "POKAŻ ZAZNACZONE",
"routes-1t-cat": "SZLAKI JEDNOTOROWE ZELEKTR. (MINIMUM)", "save": "ZAPAMIĘTAJ FILTRY",
"routes-1t-other": "SZLAKI JEDNOTOROWE NIEZELEKTR. (MINIMUM)", "reset": "RESETUJ FILTRY",
"routes-2t-cat": "SZLAKI DWUTOROWE ZELEKTR. (MINIMUM)", "close": "ZAMKNIJ FILTRY"
"routes-2t-other": "SZLAKI DWUTOROWE NIEZELEKTR. (MINIMUM)" },
}, "sceneries": {
"station": "Stacja",
"authors-search": "Szukaj autora (uwzględnia inne filtry)", "abbr": "Skrót\nposterunku",
"minimum-hours-title": "POKAŻ TYLKO SCENERIE DOSTĘPNE MINIMUM DO:", "min-lvl": "Min. poziom\ndyżurnego",
"now": "TERAZ", "status": "Status",
"hour": " godz.", "dispatcher": "Dyżurny",
"no-limit": "BEZ LIMITU", "dispatcher-lvl": "Poziom\ndyżurnego",
"include-selected": "POKAŻ ZAZNACZONE", "routes": "Szlaki\n2tor / 1tor",
"save": "ZAPAMIĘTAJ FILTRY", "general": "Informacje\nogólne",
"reset": "RESETUJ FILTRY", "user": "Maszyniści online",
"close": "ZAMKNIJ FILTRY" "spawn": "Otwarte spawny",
}, "timetableAll": "Aktywne rozkłady jazdy",
"sceneries": { "timetableConfirmed": "Zatwierdzone rozkłady jazdy",
"station": "Stacja", "timetableUnconfirmed": "Niezatwierdzone rozkłady jazdy",
"abbr": "Skrót\nposterunku", "no-stations": "Brak stacji do wyświetlenia!",
"min-lvl": "Min. poziom\ndyżurnego", "scenery-search": "Wyszukaj scenerię..."
"status": "Status", },
"dispatcher": "Dyżurny", "trains": {
"dispatcher-lvl": "Poziom\ndyżurnego", "no-trains": "Brak pociągów do wyświetlenia!",
"routes": "Szlaki\n2tor / 1tor", "loading": "Pobieranie danych o pociągach...",
"general": "Informacje\nogólne", "offline": "Przejazd offline",
"user": "Maszyniści online",
"spawn": "Otwarte spawny", "current-scenery": "na scenerii",
"timetableAll": "Aktywne rozkłady jazdy", "current-signal": "przy semaforze",
"timetableConfirmed": "Zatwierdzone rozkłady jazdy", "current-track": "na szlaku",
"timetableUnconfirmed": "Niezatwierdzone rozkłady jazdy",
"no-stations": "Brak stacji do wyświetlenia!", "delayed": "Opóźniony: ",
"scenery-search": "Wyszukaj scenerię..." "preponed": "Przed czasem: ",
}, "on-time": "Planowo",
"trains": {
"no-trains": "Brak pociągów do wyświetlenia!", "route-progress": "Postęp: ",
"loading": "Pobieranie danych o pociągach...",
"offline": "Przejazd offline", "detailed-timetable": "Szczegółowy rozkład jazdy pociągu ",
"via-title": "Przez: ",
"stats": "STATYSTYKI RUCHU", "no-timetable": "brak rozkładu jazdy",
"stats-speed": "PRĘDKOŚCI POCIĄGÓW (MIN, ŚR, MAX) [km/h]", "cars": "Wagony",
"stats-length": "DŁUGOŚCI ROZKŁADÓW (MIN, ŚR, MAX) [km]", "EZT": "EZT",
"stats-categories": "KATEGORIE RJ", "SZT": "SZT",
"stats-special-twr": "WYSOKIEGO RYZYKA", "loco-electric": "Elektrowóz",
"stats-special-skr": "PRZEKROCZONA SKRAJNIA", "loco-diesel": "Spalinowóz",
"stats-locos": "NAJCZĘSTSZE JEDNOSTKI", "timetable-comments": "Pociąg z uwagami eksploatacyjnymi",
"comment": "Uwagi eksploatacyjne dla: ",
"current-scenery": "na scenerii",
"current-signal": "przy semaforze", "last-seen-now": "od niedawna",
"current-track": "na szlaku", "last-seen-min": "od minuty",
"last-seen-ago": "od {minutes} minut",
"delayed": "Opóźniony: ",
"preponed": "Przed czasem: ", "scenery-offline": "Przejazd offline",
"on-time": "Planowo",
"timeout": "Wystąpił problem z aktualizacją rozkładów jazdy z SWDR"
"route-progress": "Postęp: ", },
"journal": {
"detailed-timetable": "Szczegółowy rozkład jazdy pociągu ", "title": "HISTORIA DYŻURÓW",
"via-title": "Przez: ", "loading": "Ładowanie historii dyżurów...",
"no-timetable": "brak rozkładu jazdy", "no-history": "Brak historii dyżurów dla tej scenerii!",
"distance-exceeded": "Uwaga! Z powodu wewnętrznego błędu serwera TD2, rozkłady jazdy o kilometrażu powyżej 200km mogą być niepoprawne!", "data-refreshed-at": "Dane odświeżone o",
"cars": "Wagony",
"EZT": "EZT", "section-timetables": "ROZKŁADY JAZDY",
"SZT": "SZT", "section-dispatchers": "DYŻURNI",
"loco-electric": "Elektrowóz",
"loco-diesel": "Spalinowóz", "no-further-data": "Brak dalszych wyników dla podanych parametrów",
"timetable-comments": "Pociąg z uwagami eksploatacyjnymi", "loading-further-data": "Ładowanie...",
"comment": "Uwagi eksploatacyjne dla: ",
"table-limit": "Dla płynności działania strony pokazanych jest tylko 10 pociągów zgodnie z wybranymi filtrami.", "online-since": "ONLINE OD",
"duty-lasted": "Dyżur trwał",
"last-seen-now": "od niedawna", "hours": "{value} godz.",
"last-seen-min": "od minuty", "minutes": "{value} min.",
"last-seen-ago": "od {minutes} minut", "seconds": "{value} sek.",
"scenery-offline": "Przejazd offline", "route-length": "Kilometraż:",
"station-count": "Stacje:",
"timeout": "Wystąpił problem z aktualizacją rozkładów jazdy z SWDR" "dispatcher-name": "Autor",
}, "timetable-day": "Rozkład z dnia",
"journal": { "timetable-active": "AKTYWNY",
"title": "HISTORIA DYŻURÓW", "timetable-fulfilled": "WYPEŁNIONY",
"loading": "Ładowanie historii dyżurów...", "timetable-abandoned": "PORZUCONY",
"no-history": "Brak historii dyżurów dla tej scenerii!",
"data-refreshed-at": "Dane odświeżone o", "stock-info": "DODATKOWE INFORMACJE",
"stock-length": "Długość",
"section-timetables": "ROZKŁADY JAZDY", "stock-mass": "Masa",
"section-dispatchers": "DYŻURNI", "stock-max-speed": "Prędkość maks.",
"no-further-data": "Brak dalszych wyników dla podanych parametrów", "load-data": "Pobierz dalszą historię...",
"loading-further-data": "Ładowanie...",
"stats-title": "STATYSTYKI MASZYNISTY",
"online-since": "ONLINE OD",
"duty-lasted": "Dyżur trwał", "last-seen-at": "Ostatnio widziany na: ",
"hours": "{value} godz.", "currently-at": "Obecnie na scenerii: ",
"minutes": "{value} min.",
"seconds": "{value} sek.", "stats-timetables": "ROZKŁADY JAZDY",
"stats-longest-timetable": "NAJDŁUŻSZY RJ",
"route-length": "Kilometraż:", "stats-avg-timetable": "ŚREDNIA DŁUGOŚĆ RJ",
"station-count": "Stacje:", "stats-distance": "DYSTANS",
"dispatcher-name": "Autor", "stats-stations": "STACJE",
"timetable-day": "Rozkład z dnia",
"timetable-active": "AKTYWNY", "timetable-stats-total": "Stworzone rozkłady jazdy: {count} (łączny dystans: {distance})",
"timetable-fulfilled": "WYPEŁNIONY", "timetable-stats-longest": "Najdłuższy rozkład jazdy: #{id} (stworzony przez dyżurnego {author} dla maszynisty {driver} o dystansie {distance})",
"timetable-abandoned": "PORZUCONY", "timetable-stats-most-active-dr": "Najaktywniejszy dyżurny: {dispatcher} (stworzył {count})",
"timetable-stats-most-active-dr-many": "Najaktywniejsi dyżurni: {dispatchers} (stworzyli po {count})",
"stock-info": "DODATKOWE INFORMACJE", "timetable-stats-most-active-driver": "Najaktywniejszy maszynista: {driver} (łączny przejechany dystans: {distance})",
"stock-length": "Długość", "timetable-stats-longest-duties": "Najdłuższa służba: {dispatcher} na scenerii {station} (czas trwania: {duration})",
"stock-mass": "Masa",
"stock-max-speed": "Prędkość maks.", "timetable-count": "rozkład jazdy | rozkładów jazdy",
"load-data": "Pobierz dalszą historię...", "daily-stats-title": "STATYSTYKI DNIA",
"daily-stats-info": "Dzisiejsze statystyki nie są jeszcze dostępne!",
"stats-title": "STATYSTYKI MASZYNISTY",
"driver-stats-title": "STATYSTYKI GRACZA",
"last-seen-at": "Ostatnio widziany na: ", "driver-stats-info": "Wpisz nazwę użytkownika w filtrach [F], aby zobaczyć jego statystyki maszynisty!",
"currently-at": "Obecnie na scenerii: ",
"stats-loading": "Pobieranie statystyk...",
"stats-timetables": "ROZKŁADY JAZDY", "stats-error": "Ups! Wystąpił błąd podczas próby pobrania statystyk!",
"stats-longest-timetable": "NAJDŁUŻSZY RJ",
"stats-avg-timetable": "ŚREDNIA DŁUGOŚĆ RJ", "timetable-location-signal": "semafor:",
"stats-distance": "DYSTANS", "timetable-location-route": "szlak:",
"stats-stations": "STACJE",
"history-name": "Sceneria",
"timetable-stats-total": "Stworzone rozkłady jazdy: {count} (łączny dystans: {distance})", "history-hash": "Hash",
"timetable-stats-longest": "Najdłuższy rozkład jazdy: #{id} (stworzony przez dyżurnego {author} dla maszynisty {driver} o dystansie {distance})", "history-dispatcher": "Dyżurny",
"timetable-stats-most-active-dr": "Najaktywniejszy dyżurny: {dispatcher} (stworzył {count})", "history-level": "Poziom",
"timetable-stats-most-active-dr-many": "Najaktywniejsi dyżurni: {dispatchers} (stworzyli po {count})", "history-rate": "Ocena",
"timetable-stats-most-active-driver": "Najaktywniejszy maszynista: {driver} (łączny przejechany dystans: {distance})", "history-region": "Region",
"timetable-stats-longest-duties": "Najdłuższa służba: {dispatcher} na scenerii {station} (czas trwania: {duration})", "history-date": "Data służby"
},
"timetable-count": "rozkład jazdy | rozkładów jazdy", "scenery": {
"users": "GRACZE ONLINE",
"daily-stats-title": "STATYSTYKI DNIA", "spawns": "OTWARTE SPAWNY",
"daily-stats-info": "Dzisiejsze statystyki nie są jeszcze dostępne!", "timetables": "AKTYWNE ROZKŁADY JAZDY",
"no-timetables": "Brak aktywnych rozkładów!",
"driver-stats-title": "STATYSTYKI GRACZA", "offline": "Sceneria jest offline",
"driver-stats-info": "Wpisz nazwę użytkownika w filtrach [F], aby zobaczyć jego statystyki maszynisty!", "no-users": "BRAK AKTYWNYCH GRACZY",
"no-spawns": "BRAK OTWARTYCH SPAWNÓW",
"stats-loading": "Pobieranie statystyk...", "no-scenery": "Ups! Ta sceneria nie istnieje!",
"stats-error": "Ups! Wystąpił błąd podczas próby pobrania statystyk! :/", "return-btn": "Wróć na stronę główną",
"history-btn": "Przejdź do widoku historii dyżurnych ruchu",
"timetable-location-signal": "semafor:", "info-btn": "Wróć do widoku scenerii",
"timetable-location-route": "szlak:", "authors-title": "Autor scenerii | Autorzy scenerii",
"abbrev": "Skrót posterunku:",
"history-name": "Sceneria", "lines-title": "Rzeczywiste linie",
"history-hash": "Hash", "project-title": "Projekt",
"history-dispatcher": "Dyżurny", "one-way-routes": "Szlaki jednotorowe",
"history-level": "Poziom", "two-way-routes": "Szlaki dwutorowe",
"history-rate": "Ocena",
"history-region": "Region", "option-active-timetables": "Aktywne rozkłady jazdy",
"history-date": "Data służby" "option-timetables-history": "Historia rozkładów",
}, "option-dispatchers-history": "Historia dyżurów",
"scenery": {
"users": "GRACZE ONLINE", "timetable-author-title": "Wydany przez",
"spawns": "OTWARTE SPAWNY", "timetable-author-unknown": "Autor nieznany",
"timetables": "AKTYWNE ROZKŁADY JAZDY",
"no-timetables": "Brak aktywnych rozkładów!", "timetables-history-id": "ID",
"offline": "Sceneria jest offline", "timetables-history-number": "Numer",
"no-users": "BRAK AKTYWNYCH GRACZY", "timetables-history-route": "Trasa",
"no-spawns": "BRAK OTWARTYCH SPAWNÓW", "timetables-history-driver": "Maszynista",
"no-scenery": "Ups! Ta sceneria nie istnieje!", "timetables-history-author": "Autor RJ",
"return-btn": "Wróć na stronę główną", "timetables-history-date": "Data",
"history-btn": "Przejdź do widoku historii dyżurnych ruchu",
"info-btn": "Wróć do widoku scenerii", "dispatchers-history-hash": "Hash",
"authors-title": "Autor scenerii | Autorzy scenerii", "dispatchers-history-dispatcher": "Dyżurny",
"abbrev": "Skrót posterunku:", "dispatchers-history-level": "Poziom",
"lines-title": "Rzeczywiste linie", "dispatchers-history-rate": "Ocena",
"project-title": "Projekt", "dispatchers-history-date": "Data służby",
"one-way-routes": "Szlaki jednotorowe",
"two-way-routes": "Szlaki dwutorowe", "req-level": "ogólnodostępna | minimum {lvl} poziom dyżurnego | minimum {lvl} poziom dyżurnego",
"history-list-empty": "Brak historii dla tej scenerii!",
"option-active-timetables": "Aktywne rozkłady jazdy",
"option-timetables-history": "Historia rozkładów", "forum-topic": "Oficjalny wątek scenerii {name}",
"option-dispatchers-history": "Historia dyżurów",
"pragotron-link": "Paletowa tablica informacyjna (beta)",
"timetable-author-title": "Wydany przez", "tablice-link": "Tablica informacyjna zbiorcza (autorstwa Thundo)",
"timetable-author-unknown": "Autor nieznany",
"bottom-info": "Pokaż pełną historię w zakładce Dziennika"
"timetables-history-id": "ID", },
"timetables-history-number": "Numer", "availability": {
"timetables-history-route": "Trasa", "title": "Dostępność",
"timetables-history-driver": "Maszynista", "default": "w paczce",
"timetables-history-author": "Autor RJ", "nonDefault": "poza paczką",
"timetables-history-date": "Data", "unavailable": "niedostępna",
"nonPublic": "niepubliczna",
"dispatchers-history-hash": "Hash", "abandoned": "wycofana"
"dispatchers-history-dispatcher": "Dyżurny", },
"dispatchers-history-level": "Poziom", "timetables": {
"dispatchers-history-rate": "Ocena", "timetable-only": "Wyodrębnij rozkłady jazdy",
"dispatchers-history-date": "Data służby", "end": "Koniec rozkładu jazdy",
"terminated": "Rozkład jazdy zakończony",
"req-level": "ogólnodostępna | minimum {lvl} poziom dyżurnego | minimum {lvl} poziom dyżurnego", "begins": "ROZPOCZYNA\nBIEG",
"history-list-empty": "Brak historii dla tej scenerii!", "terminates": "KOŃCZY BIEG",
"forum-topic": "Oficjalny wątek scenerii {name}", "from": "Z",
"to": "DO",
"pragotron-link": "Paletowa tablica informacyjna (beta)",
"tablice-link": "Tablica informacyjna zbiorcza (autorstwa Thundo)", "desc-arriving": "Pociągu nie ma jeszcze na tej scenerii. Przyjedzie z: {prevStationName} (szlak {prevDepartureLine})",
"desc-online": "Pociąg jest na tej scenerii. Odjedzie do: {nextStationName} (szlak {nextArrivalLine})",
"bottom-info": "Pokaż pełną historię w zakładce Dziennika" "desc-stopped": "Pociąg jest na tej scenerii i odbywa postój. Odjedzie do: {nextStationName} (szlak {nextArrivalLine})",
}, "desc-next-arrival": "Odjeżdża do: {nextStationName} (szlak {nextArrivalLine})",
"availability": { "desc-departed": "Pociąg jest na tej scenerii i został odprawiony. Odjeżdża do: {nextStationName} (szlak {nextArrivalLine})",
"title": "Dostępność", "desc-departed-away": "Pociąg został odprawiony i odjechał do: {nextStationName} (szlak {nextArrivalLine})",
"default": "w paczce", "desc-end": "Pociąg kończy bieg",
"nonDefault": "poza paczką", "desc-terminated": "Pociąg skończył bieg"
"unavailable": "niedostępna", },
"nonPublic": "niepubliczna", "history": {
"abandoned": "wycofana" "title": "DZIENNIK ROZKŁADÓW JAZDY"
}, }
"timetables": { }
"timetable-only": "Wyodrębnij rozkłady jazdy",
"end": "Koniec rozkładu jazdy",
"terminated": "Rozkład jazdy zakończony",
"begins": "ROZPOCZYNA\nBIEG",
"terminates": "KOŃCZY BIEG",
"from": "Z",
"to": "DO",
"desc-arriving": "Pociągu nie ma jeszcze na tej scenerii. Przyjedzie z: {prevStationName} (szlak {prevDepartureLine})",
"desc-online": "Pociąg jest na tej scenerii. Odjedzie do: {nextStationName} (szlak {nextArrivalLine})",
"desc-stopped": "Pociąg jest na tej scenerii i odbywa postój. Odjedzie do: {nextStationName} (szlak {nextArrivalLine})",
"desc-next-arrival": "Odjeżdża do: {nextStationName} (szlak {nextArrivalLine})",
"desc-departed": "Pociąg jest na tej scenerii i został odprawiony. Odjeżdża do: {nextStationName} (szlak {nextArrivalLine})",
"desc-departed-away": "Pociąg został odprawiony i odjechał do: {nextStationName} (szlak {nextArrivalLine})",
"desc-end": "Pociąg kończy bieg",
"desc-terminated": "Pociąg skończył bieg"
},
"history": {
"title": "DZIENNIK ROZKŁADÓW JAZDY"
}
}
+41 -41
View File
@@ -1,41 +1,41 @@
import { createApp, Directive, ref } from 'vue'; import { createApp, Directive, ref } from 'vue';
import App from './App.vue'; import App from './App.vue';
import router from './router'; import router from './router';
import enLang from './locales/en.json'; import enLang from './locales/en.json';
import plLang from './locales/pl.json'; import plLang from './locales/pl.json';
import { createI18n } from 'vue-i18n'; import { createI18n } from 'vue-i18n';
import { createPinia } from 'pinia'; import { createPinia } from 'pinia';
const i18n = createI18n({ const i18n = createI18n({
locale: 'pl', locale: 'pl',
legacy: false, legacy: false,
warnHtmlMessage: false, warnHtmlMessage: false,
fallbackLocale: 'pl', fallbackLocale: 'pl',
messages: { messages: {
en: enLang, en: enLang,
pl: plLang, pl: plLang
}, },
enableLegacy: false, enableLegacy: false
}); });
const clickOutsideDirective: Directive = { const clickOutsideDirective: Directive = {
mounted(el, binding) { mounted(el, binding) {
el.clickOutsideEvent = (event: Event) => { el.clickOutsideEvent = (event: Event) => {
if (!(el == event.target || el.contains(event.target))) { if (!(el == event.target || el.contains(event.target))) {
binding.value(); binding.value();
} }
}; };
document.addEventListener('click', el.clickOutsideEvent); document.addEventListener('click', el.clickOutsideEvent);
}, }
}; };
createApp(App) createApp(App)
.provide('isFilterCardVisible', ref(false)) .provide('isFilterCardVisible', ref(false))
.use(createPinia()) .use(createPinia())
.use(router) .use(router)
.use(i18n) .use(i18n)
.directive('click-outside', clickOutsideDirective) .directive('click-outside', clickOutsideDirective)
.mount('#app'); .mount('#app');
+77 -77
View File
@@ -1,77 +1,77 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
methods: { methods: {
localeDate(dateString: string, locale: string) { localeDate(dateString: string, locale: string) {
return new Date(dateString).toLocaleDateString(locale == 'pl' ? 'pl-PL' : 'en-GB', { return new Date(dateString).toLocaleDateString(locale == 'pl' ? 'pl-PL' : 'en-GB', {
weekday: 'long', weekday: 'long',
day: 'numeric', day: 'numeric',
month: '2-digit', month: '2-digit',
year: 'numeric', year: 'numeric',
hour: '2-digit', hour: '2-digit',
minute: '2-digit', minute: '2-digit'
}); });
}, },
localeDay(dateString: string, locale: string) { localeDay(dateString: string, locale: string) {
return new Date(dateString).toLocaleDateString(locale == 'pl' ? 'pl-PL' : 'en-GB', { return new Date(dateString).toLocaleDateString(locale == 'pl' ? 'pl-PL' : 'en-GB', {
day: 'numeric', day: 'numeric',
month: '2-digit', month: '2-digit',
year: 'numeric', year: 'numeric'
}); });
}, },
localeDateTime(dateString: string, locale: string) { localeDateTime(dateString: string, locale: string) {
return new Date(dateString).toLocaleString(locale == 'pl' ? 'pl-PL' : 'en-GB', { return new Date(dateString).toLocaleString(locale == 'pl' ? 'pl-PL' : 'en-GB', {
timeStyle: 'short', timeStyle: 'short',
dateStyle: 'medium' dateStyle: 'medium'
}); });
}, },
localeTime(dateString: string, locale: string) { localeTime(dateString: string, locale: string) {
return new Date(dateString).toLocaleTimeString(locale == 'pl' ? 'pl-PL' : 'en-GB', { return new Date(dateString).toLocaleTimeString(locale == 'pl' ? 'pl-PL' : 'en-GB', {
hour: '2-digit', hour: '2-digit',
minute: '2-digit', minute: '2-digit'
}); });
}, },
stringToDate(dateString?: string) { stringToDate(dateString?: string) {
return dateString ? new Date(dateString) : null; return dateString ? new Date(dateString) : null;
}, },
parseDateToTimeString(date: Date | null) { parseDateToTimeString(date: Date | null) {
return ( return (
date?.toLocaleTimeString('pl-PL', { date?.toLocaleTimeString('pl-PL', {
hour: '2-digit', hour: '2-digit',
minute: '2-digit', minute: '2-digit'
}) || '' }) || ''
); );
}, },
timestampToString(timestamp: number | null) { timestampToString(timestamp: number | null) {
return timestamp return timestamp
? new Date(timestamp).toLocaleTimeString('pl-PL', { ? new Date(timestamp).toLocaleTimeString('pl-PL', {
hour: '2-digit', hour: '2-digit',
minute: '2-digit', minute: '2-digit'
}) })
: ''; : '';
}, },
calculateDuration(timestampMs: number, showSeconds = false) { calculateDuration(timestampMs: number, showSeconds = false) {
const secondsTotal = Math.floor(timestampMs / 1000); const secondsTotal = Math.floor(timestampMs / 1000);
const minsTotal = Math.round(timestampMs / 60000); const minsTotal = Math.round(timestampMs / 60000);
const hoursTotal = Math.floor(minsTotal / 60); const hoursTotal = Math.floor(minsTotal / 60);
const minsInHour = minsTotal % 60; const minsInHour = minsTotal % 60;
return minsTotal >= 60 return minsTotal >= 60
? `${this.$t('journal.hours', { value: hoursTotal }, hoursTotal)} ${this.$t( ? `${this.$t('journal.hours', { value: hoursTotal }, hoursTotal)} ${this.$t(
'journal.minutes', 'journal.minutes',
{ value: minsInHour }, { value: minsInHour },
minsInHour minsInHour
)}` )}`
: showSeconds && secondsTotal <= 60 : showSeconds && secondsTotal <= 60
? this.$t('journal.seconds', { value: secondsTotal }, secondsTotal) ? this.$t('journal.seconds', { value: secondsTotal }, secondsTotal)
: this.$t('journal.minutes', { value: minsTotal }, minsTotal); : this.$t('journal.minutes', { value: minsTotal }, minsTotal);
}, }
}, }
}); });
+2 -2
View File
@@ -7,7 +7,7 @@ export default defineComponent({
}, },
getImage(name: string) { getImage(name: string) {
return new URL(`../assets/${name}`, import.meta.url).href; return new URL(`../assets/${name}`, import.meta.url).href;
} }
}, }
}); });
+27 -26
View File
@@ -1,26 +1,27 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
data() { data() {
return { return {
preventKeyDown: false, preventKeyDown: false
}; };
}, },
activated() { activated() {
window.addEventListener('keydown', this.handleKeyDown); window.addEventListener('keydown', this.handleKeyDown);
}, },
deactivated() { deactivated() {
window.removeEventListener('keydown', this.handleKeyDown); window.removeEventListener('keydown', this.handleKeyDown);
}, },
methods: { methods: {
onKeyDownFunction() {}, onKeyDownFunction() {},
handleKeyDown(e: KeyboardEvent) { handleKeyDown(e: KeyboardEvent) {
if (!e.key) return; if (!e.key) return;
if (e.key.toLowerCase() == 'f' && !this.preventKeyDown && !e.ctrlKey && !e.altKey) this.onKeyDownFunction(); if (e.key.toLowerCase() == 'f' && !this.preventKeyDown && !e.ctrlKey && !e.altKey)
}, this.onKeyDownFunction();
}, }
}); }
});
+29 -26
View File
@@ -1,26 +1,29 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
data: () => ({ data: () => ({
observer: null as IntersectionObserver | null, observer: null as IntersectionObserver | null,
observerTarget: null as Element | null, observerTarget: null as Element | null
}), }),
methods: { methods: {
mountObserver(actionFunction: () => void, target: Element) { mountObserver(actionFunction: () => void, target: Element) {
this.observer = new IntersectionObserver((entries) => { this.observer = new IntersectionObserver(
console.log(entries); (entries) => {
console.log(entries);
if (entries[0].intersectionRatio > 0.5) actionFunction();
}, { threshold: 0.2 }); if (entries[0].intersectionRatio > 0.5) actionFunction();
},
this.observer.observe(target); { threshold: 0.2 }
}, );
unmountObserver() { this.observer.observe(target);
if (!this.observerTarget) return; },
this.observer?.unobserve(this.observerTarget); unmountObserver() {
}, if (!this.observerTarget) return;
},
}); this.observer?.unobserve(this.observerTarget);
}
}
});
+33 -33
View File
@@ -1,33 +1,33 @@
import { Ref, defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { useStore } from '../store/store'; import { useStore } from '../store/store';
export default defineComponent({ export default defineComponent({
data() { data() {
return { return {
store: useStore(), store: useStore()
}; };
}, },
computed: { computed: {
chosenTrain() { chosenTrain() {
return this.store.trainList.find((train) => train.trainId == this.store.chosenModalTrainId); return this.store.trainList.find((train) => train.trainId == this.store.chosenModalTrainId);
}, }
}, },
methods: { methods: {
selectModalTrain(trainId: string, target?: EventTarget | null) { selectModalTrain(trainId: string, target?: EventTarget | null) {
this.store.chosenModalTrainId = trainId; this.store.chosenModalTrainId = trainId;
document.body.classList.add('no-scroll'); document.body.classList.add('no-scroll');
if (target) this.store.modalLastClickedTarget = target; if (target) this.store.modalLastClickedTarget = target;
}, },
closeModal() { closeModal() {
this.store.chosenModalTrainId = undefined; this.store.chosenModalTrainId = undefined;
setTimeout(() => { setTimeout(() => {
(this.store.modalLastClickedTarget as any)?.focus(); (this.store.modalLastClickedTarget as any)?.focus();
document.body.classList.remove('no-scroll'); document.body.classList.remove('no-scroll');
}, 150); }, 150);
}, }
}, }
}); });
+34 -34
View File
@@ -1,34 +1,34 @@
import { defineComponent, h } from 'vue'; import { defineComponent } from 'vue';
import imageMixin from './imageMixin'; import imageMixin from './imageMixin';
export default defineComponent({ export default defineComponent({
mixins: [imageMixin], mixins: [imageMixin],
data() { data() {
return { return {
icons: { icons: {
arrow: this.getIcon('arrow-asc'), arrow: this.getIcon('arrow-asc')
}, },
showReturnButton: false, showReturnButton: false
}; };
}, },
methods: { methods: {
scrollToTop() { scrollToTop() {
window.scrollTo({ top: 0 }); window.scrollTo({ top: 0 });
}, },
handleScroll() { handleScroll() {
this.showReturnButton = window.scrollY > window.innerHeight * 0.35; this.showReturnButton = window.scrollY > window.innerHeight * 0.35;
}, }
}, },
activated() { activated() {
window.addEventListener('wheel', this.handleScroll); window.addEventListener('wheel', this.handleScroll);
}, },
deactivated() { deactivated() {
window.removeEventListener('wheel', this.handleScroll); window.removeEventListener('wheel', this.handleScroll);
}, }
}); });
+3 -4
View File
@@ -2,12 +2,11 @@ import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
methods: { methods: {
navigateTo(path: string, query?: {}) { navigateTo(path: string, query?: {}) {
this.$router.push({ this.$router.push({
path, path,
query, query
}); });
}, }
}, }
}); });
+20 -21
View File
@@ -1,25 +1,24 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
methods: { methods: {
getControlTypeAbbrev(controlType: string) { getControlTypeAbbrev(controlType: string) {
switch (controlType) { switch (controlType) {
case 'mechaniczne': case 'mechaniczne':
return 'M'; return 'M';
case 'SCS-SPK': case 'SCS-SPK':
return 'S/S'; return 'S/S';
case 'ręczne': case 'ręczne':
return 'R'; return 'R';
case 'mechaniczne+SPK': case 'mechaniczne+SPK':
return 'M'; return 'M';
case 'ręczne+SPK': case 'ręczne+SPK':
return 'R'; return 'R';
case 'mechaniczne+SCS': case 'mechaniczne+SCS':
return 'M'; return 'M';
default: default:
return controlType; return controlType;
} }
}
} }
}) }
});
+54 -52
View File
@@ -1,52 +1,54 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
methods: { methods: {
calculateExpStyle(exp: number, isSupporter = false): string { calculateExpStyle(exp: number, isSupporter = false): string {
const bgColor = exp > -1 ? (exp < 2 ? '#26B0D9' : `hsl(${-exp * 5 + 100}, 85%, 50%)`) : '#666'; const bgColor =
exp > -1 ? (exp < 2 ? '#26B0D9' : `hsl(${-exp * 5 + 100}, 85%, 50%)`) : '#666';
const fontColor = exp > 14 || exp == -1 ? 'white' : 'black';
const boxShadow = isSupporter ? `box-shadow: 0 0 6px 2px ${bgColor};` : ''; const fontColor = exp > 14 || exp == -1 ? 'white' : 'black';
const boxShadow = isSupporter ? `box-shadow: 0 0 6px 2px ${bgColor};` : '';
return `background-color: ${bgColor}; color: ${fontColor}; ${boxShadow};`;
}, return `background-color: ${bgColor}; color: ${fontColor}; ${boxShadow};`;
},
calculateTextExpStyle(exp: number, isSupporter = false): string {
const textColor = exp > -1 ? (exp < 2 ? '#26B0D9' : `hsl(${-exp * 5 + 100}, 75%, 50%)`) : '#666'; calculateTextExpStyle(exp: number, isSupporter = false): string {
const textColor =
return `color: ${textColor}; ${isSupporter ? 'text-shadow: 0 0 6px ' + textColor : ''};`; exp > -1 ? (exp < 2 ? '#26B0D9' : `hsl(${-exp * 5 + 100}, 75%, 50%)`) : '#666';
},
return `color: ${textColor}; ${isSupporter ? 'text-shadow: 0 0 6px ' + textColor : ''};`;
statusClasses(occupiedTo: string) { },
let className = '';
statusClasses(occupiedTo: string) {
switch (occupiedTo) { let className = '';
case 'WOLNA':
className = 'free'; switch (occupiedTo) {
break; case 'WOLNA':
case 'KOŃCZY': className = 'free';
className = 'ending'; break;
break; case 'KOŃCZY':
case 'NIEZALOGOWANY': className = 'ending';
className = 'not-signed'; break;
break; case 'NIEZALOGOWANY':
case 'BEZ LIMITU': className = 'not-signed';
className = 'no-limit'; break;
break; case 'BEZ LIMITU':
case 'NIEDOSTĘPNY': className = 'no-limit';
className = 'unavailable'; break;
break; case 'NIEDOSTĘPNY':
case 'Z/W': className = 'unavailable';
className = 'brb'; break;
break; case 'Z/W':
case 'BRAK MIEJSCA': className = 'brb';
className = 'no-space'; break;
break; case 'BRAK MIEJSCA':
default: className = 'no-space';
break; break;
} default:
break;
return className; }
},
}, return className;
}); }
}
});
+42 -24
View File
@@ -11,39 +11,39 @@ export default defineComponent({
main: [ main: [
{ {
name: 'speed', name: 'speed',
unit: 'km/h', unit: 'km/h'
}, },
{ {
name: 'length', name: 'length',
unit: 'm', unit: 'm'
}, },
{ {
name: 'mass', name: 'mass',
unit: 't', unit: 't',
multiplier: 0.001, multiplier: 0.001
}, }
], ],
position: [ position: [
{ {
name: 'scenery', name: 'scenery',
prop: 'currentStationName', prop: 'currentStationName'
}, },
{ {
name: 'route', name: 'route',
prop: 'connectedTrack', prop: 'connectedTrack'
}, },
{ {
name: 'signal', name: 'signal',
prop: 'signal', prop: 'signal'
}, },
{ {
name: 'distance', name: 'distance',
prop: 'distance', prop: 'distance',
unit: 'm', unit: 'm'
}, }
], ]
}, }
}), }),
methods: { methods: {
@@ -64,11 +64,15 @@ export default defineComponent({
positionString += this.$t('trains.current-scenery') + ' '; positionString += this.$t('trains.current-scenery') + ' ';
if (train.currentStationHash) positionString += train.currentStationName + ' '; if (train.currentStationHash) positionString += train.currentStationName + ' ';
else positionString += train['currentStationName'].replace(/.[a-zA-Z0-9]+.sc/, '') + ' (offline) '; else
positionString +=
train['currentStationName'].replace(/.[a-zA-Z0-9]+.sc/, '') + ' (offline) ';
if (train.signal) positionString += this.$t('trains.current-signal') + ' ' + train.signal + ' '; if (train.signal)
positionString += this.$t('trains.current-signal') + ' ' + train.signal + ' ';
if (train.connectedTrack) positionString += this.$t('trains.current-track') + ' ' + train.connectedTrack + ' '; if (train.connectedTrack)
positionString += this.$t('trains.current-track') + ' ' + train.connectedTrack + ' ';
if (train.distance) positionString += `(${this.displayDistance(train.distance)})`; if (train.distance) positionString += `(${this.displayDistance(train.distance)})`;
@@ -81,9 +85,17 @@ export default defineComponent({
return stops return stops
.reduce((acc: string[], stop: TrainStop, i: number) => { .reduce((acc: string[], stop: TrainStop, i: number) => {
if (stop.stopType.includes('ph') && !stop.stopNameRAW.includes('po.')) if (stop.stopType.includes('ph') && !stop.stopNameRAW.includes('po.'))
acc.push(`<strong style='color:${stop.confirmed ? 'springgreen' : 'white'}'>${stop.stopName}</strong>`); acc.push(
`<strong style='color:${stop.confirmed ? 'springgreen' : 'white'}'>${
stop.stopName
}</strong>`
);
else if (i > 0 && i < stops.length - 1 && !/po\.|sbl/gi.test(stop.stopNameRAW)) else if (i > 0 && i < stops.length - 1 && !/po\.|sbl/gi.test(stop.stopNameRAW))
acc.push(`<span style='color:${stop.confirmed ? 'springgreen' : 'lightgray'}'>${stop.stopName}</span>`); acc.push(
`<span style='color:${stop.confirmed ? 'springgreen' : 'lightgray'}'>${
stop.stopName
}</span>`
);
return acc; return acc;
}, []) }, [])
.join(' > '); .join(' > ');
@@ -94,16 +106,22 @@ export default defineComponent({
}, },
confirmedPercentage(stops: TrainStop[]) { confirmedPercentage(stops: TrainStop[]) {
return Number(((stops.filter((stop) => stop.confirmed).length / stops.length) * 100).toFixed(0)); return Number(
((stops.filter((stop) => stop.confirmed).length / stops.length) * 100).toFixed(0)
);
}, },
currentDelay(stops: TrainStop[]) { currentDelay(stops: TrainStop[]) {
const delay = const delay =
stops.find((stop, i) => (i == 0 && !stop.confirmed) || (i > 0 && stops[i - 1].confirmed && !stop.confirmed)) stops.find(
?.departureDelay || 0; (stop, i) =>
(i == 0 && !stop.confirmed) || (i > 0 && stops[i - 1].confirmed && !stop.confirmed)
)?.departureDelay || 0;
if (delay > 0) return `<span style='color: salmon'>${this.$t('trains.delayed')} ${delay} min</span>`; if (delay > 0)
else if (delay < 0) return `<span style='color: lightgreen'>${this.$t('trains.preponed')} ${delay} min</span>`; return `<span style='color: salmon'>${this.$t('trains.delayed')} ${delay} min</span>`;
else if (delay < 0)
return `<span style='color: lightgreen'>${this.$t('trains.preponed')} ${delay} min</span>`;
else return this.$t('trains.on-time'); else return this.$t('trains.on-time');
}, },
@@ -118,7 +136,7 @@ export default defineComponent({
getSceneriesWithComments(timetableData: Train['timetableData']) { getSceneriesWithComments(timetableData: Train['timetableData']) {
const commentList = const commentList =
timetableData?.followingStops.reduce((acc, stop, i) => { timetableData?.followingStops.reduce((acc, stop) => {
if (stop.comments) acc.push(stop.stopNameRAW); if (stop.comments) acc.push(stop.stopNameRAW);
return acc; return acc;
@@ -138,6 +156,6 @@ export default defineComponent({
onImageError(e: Event) { onImageError(e: Event) {
const imageEl = e.target as HTMLImageElement; const imageEl = e.target as HTMLImageElement;
imageEl.src = this.getImage('unknown.png'); imageEl.src = this.getImage('unknown.png');
}, }
}, }
}); });
+2 -2
View File
@@ -2,12 +2,12 @@ import { useRegisterSW } from 'virtual:pwa-register/vue';
export default () => { export default () => {
const { needRefresh, updateServiceWorker, offlineReady } = useRegisterSW({ const { needRefresh, updateServiceWorker, offlineReady } = useRegisterSW({
immediate: true, immediate: true
}); });
return { return {
needRefresh, needRefresh,
updateServiceWorker, updateServiceWorker,
offlineReady, offlineReady
}; };
}; };
+65 -58
View File
@@ -1,58 +1,65 @@
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'; import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
import JournalDispatchersVue from '../views/JournalDispatchers.vue'; import JournalDispatchersVue from '../views/JournalDispatchers.vue';
import JournalTimetablesVue from '../views/JournalTimetables.vue'; import JournalTimetablesVue from '../views/JournalTimetables.vue';
const routes: Array<RouteRecordRaw> = [ const routes: Array<RouteRecordRaw> = [
{ {
path: '/', path: '/',
name: 'StationsView', name: 'StationsView',
component: () => import('../views/StationsView.vue'), component: () => import('../views/StationsView.vue')
}, },
{ {
path: '/trains', path: '/trains',
name: 'TrainsView', name: 'TrainsView',
component: () => import('../views/TrainsView.vue'), component: () => import('../views/TrainsView.vue'),
props: (route) => ({ train: route.query.train, driver: route.query.driver, trainId: route.query.trainId }), props: (route) => ({
}, train: route.query.train,
{ driver: route.query.driver,
path: '/scenery', trainId: route.query.trainId
name: 'SceneryView', })
component: () => import('../views/SceneryView.vue'), },
}, {
{ path: '/scenery',
path: '/journal', name: 'SceneryView',
redirect: '/journal/timetables' component: () => import('../views/SceneryView.vue')
}, },
{ {
path: '/journal/timetables', path: '/journal',
name: 'JournalTimetables', redirect: '/journal/timetables'
component: JournalTimetablesVue, },
props: (route) => ({ {
trainNo: route.query.trainNo, path: '/journal/timetables',
driverName: route.query.driverName, name: 'JournalTimetables',
timetableId: route.query.timetableId, component: JournalTimetablesVue,
}), props: (route) => ({
}, trainNo: route.query.trainNo,
{ driverName: route.query.driverName,
path: '/journal/dispatchers', timetableId: route.query.timetableId
name: 'JournalDispatchers', })
component: JournalDispatchersVue, },
props: (route) => ({ sceneryName: route.query.sceneryName, dispatcherName: route.query.dispatcherName }), {
}, path: '/journal/dispatchers',
{ name: 'JournalDispatchers',
path: '/:catchAll(.*)', component: JournalDispatchersVue,
redirect: '/', props: (route) => ({
}, sceneryName: route.query.sceneryName,
]; dispatcherName: route.query.dispatcherName
})
const router = createRouter({ },
scrollBehavior(to, from) { {
if (to.name == 'SceneryView' && from.name) return { el: `.app_main` }; path: '/:catchAll(.*)',
redirect: '/'
// if (from.name == 'SceneryView' && to.name == 'StationsView') return { el: `.last-selected`, top: 20 }; }
}, ];
history: createWebHistory(),
routes, const router = createRouter({
}); scrollBehavior(to, from) {
if (to.name == 'SceneryView' && from.name) return { el: `.app_main` };
export default router;
// if (from.name == 'SceneryView' && to.name == 'StationsView') return { el: `.last-selected`, top: 20 };
},
history: createWebHistory(),
routes
});
export default router;
@@ -1,49 +1,49 @@
import Filter from "../../interfaces/Filter"; import Filter from '../../interfaces/Filter';
export const filterInitStates: Filter = { export const filterInitStates: Filter = {
default: false, default: false,
notDefault: false, notDefault: false,
real: false, real: false,
fictional: false, fictional: false,
SPK: false, SPK: false,
SCS: false, SCS: false,
SPE: false, SPE: false,
SUP: false, SUP: false,
noSUP: false, noSUP: false,
ręczne: false, ręczne: false,
'ręczne+SPK': false, 'ręczne+SPK': false,
'ręczne+SCS': false, 'ręczne+SCS': false,
mechaniczne: false, mechaniczne: false,
'mechaniczne+SPK': false, 'mechaniczne+SPK': false,
'mechaniczne+SCS': false, 'mechaniczne+SCS': false,
współczesna: false, współczesna: false,
kształtowa: false, kształtowa: false,
historyczna: false, historyczna: false,
mieszana: false, mieszana: false,
SBL: false, SBL: false,
PBL: false, PBL: false,
minLevel: 0, minLevel: 0,
maxLevel: 20, maxLevel: 20,
minOneWayCatenary: 0, minOneWayCatenary: 0,
minOneWay: 0, minOneWay: 0,
minTwoWayCatenary: 0, minTwoWayCatenary: 0,
minTwoWay: 0, minTwoWay: 0,
'include-selected': false, 'include-selected': false,
'no-1track': false, 'no-1track': false,
'no-2track': false, 'no-2track': false,
free: true, free: true,
occupied: false, occupied: false,
ending: false, ending: false,
nonPublic: false, nonPublic: false,
unavailable: true, unavailable: true,
abandoned: true, abandoned: true,
afkStatus: false, afkStatus: false,
endingStatus: false, endingStatus: false,
noSpaceStatus: false, noSpaceStatus: false,
unavailableStatus: false, unavailableStatus: false,
unsignedStatus: false, unsignedStatus: false,
authors: '', authors: '',
onlineFromHours: 0, onlineFromHours: 0
}; };
+17 -3
View File
@@ -1,5 +1,19 @@
export const headIds = ['station', 'min-lvl', 'status', 'dispatcher', 'dispatcher-lvl', 'routes', 'general'] as const; export const headIds = [
'station',
'min-lvl',
'status',
'dispatcher',
'dispatcher-lvl',
'routes',
'general'
] as const;
export const headIconsIds = ['user', 'spawn', 'timetableAll', 'timetableUnconfirmed', 'timetableConfirmed'] as const; export const headIconsIds = [
'user',
'spawn',
'timetableAll',
'timetableUnconfirmed',
'timetableConfirmed'
] as const;
export type HeadIdsTypes = typeof headIds[number] | typeof headIconsIds[number]; export type HeadIdsTypes = (typeof headIds)[number] | (typeof headIconsIds)[number];
+7 -7
View File
@@ -1,7 +1,7 @@
export enum DataStatus { export enum DataStatus {
Initialized = -1, Initialized = -1,
Loading = 0, Loading = 0,
Error = 1, Error = 1,
Loaded = 2, Loaded = 2,
Warning = 3 Warning = 3
} }
+14 -14
View File
@@ -1,14 +1,14 @@
export const enum JournalFilterType { export const enum JournalFilterType {
ACTIVE = 'active', ACTIVE = 'active',
FULFILLED = 'fulfilled', FULFILLED = 'fulfilled',
ABANDONED = 'abandoned', ABANDONED = 'abandoned',
ALL = 'all', ALL = 'all',
TWR = 'twr', TWR = 'twr',
SKR = 'skr', SKR = 'skr',
TWR_SKR = 'twr-skr', TWR_SKR = 'twr-skr'
} }
export enum JournalFilterSection { export enum JournalFilterSection {
TIMETABLE_STATUS = 'timetable-status', TIMETABLE_STATUS = 'timetable-status',
TWRSKR = 'twrskr', TWRSKR = 'twrskr'
} }
+2 -2
View File
@@ -2,7 +2,7 @@ export enum TrainFilterSection {
TRAIN_TYPE = 'TRAIN_TYPE', TRAIN_TYPE = 'TRAIN_TYPE',
TIMETABLE_TYPE = 'TIMETABLE_TYPE', TIMETABLE_TYPE = 'TIMETABLE_TYPE',
COMMENTS = 'COMMENTS', COMMENTS = 'COMMENTS',
TIMETABLE = 'TIMETABLE', TIMETABLE = 'TIMETABLE'
} }
export const enum TrainFilterType { export const enum TrainFilterType {
@@ -17,5 +17,5 @@ export const enum TrainFilterType {
freight = 'freight', freight = 'freight',
other = 'other', other = 'other',
noTimetable = 'noTimetable', noTimetable = 'noTimetable',
withTimetable = 'withTimetable', withTimetable = 'withTimetable'
} }
+48 -48
View File
@@ -1,48 +1,48 @@
export default interface Filter { export default interface Filter {
[key: string]: boolean | number | string; [key: string]: boolean | number | string;
default: boolean; default: boolean;
notDefault: boolean; notDefault: boolean;
real: boolean; real: boolean;
fictional: boolean; fictional: boolean;
SPK: boolean; SPK: boolean;
SCS: boolean; SCS: boolean;
SPE: boolean; SPE: boolean;
SUP: boolean; SUP: boolean;
noSUP: boolean; noSUP: boolean;
ręczne: boolean; ręczne: boolean;
'ręczne+SPK': boolean; 'ręczne+SPK': boolean;
'ręczne+SCS': boolean; 'ręczne+SCS': boolean;
mechaniczne: boolean; mechaniczne: boolean;
'mechaniczne+SPK': boolean; 'mechaniczne+SPK': boolean;
'mechaniczne+SCS': boolean; 'mechaniczne+SCS': boolean;
SBL: boolean; SBL: boolean;
PBL: boolean; PBL: boolean;
współczesna: boolean; współczesna: boolean;
kształtowa: boolean; kształtowa: boolean;
historyczna: boolean; historyczna: boolean;
mieszana: boolean; mieszana: boolean;
minLevel: number; minLevel: number;
maxLevel: number; maxLevel: number;
minOneWayCatenary: number; minOneWayCatenary: number;
minOneWay: number; minOneWay: number;
minTwoWayCatenary: number; minTwoWayCatenary: number;
minTwoWay: number; minTwoWay: number;
'no-1track': boolean; 'no-1track': boolean;
'no-2track': boolean; 'no-2track': boolean;
'include-selected': boolean; 'include-selected': boolean;
free: boolean; free: boolean;
occupied: boolean; occupied: boolean;
nonPublic: boolean; nonPublic: boolean;
unavailable: boolean; unavailable: boolean;
abandoned: boolean; abandoned: boolean;
endingStatus: boolean; endingStatus: boolean;
afkStatus: boolean; afkStatus: boolean;
noSpaceStatus: boolean; noSpaceStatus: boolean;
unavailableStatus: boolean; unavailableStatus: boolean;
unsignedStatus: boolean; unsignedStatus: boolean;
authors: string; authors: string;
onlineFromHours: number; onlineFromHours: number;
} }
+5 -5
View File
@@ -1,6 +1,6 @@
export default interface FilterOption { export default interface FilterOption {
id: string; id: string;
name: string; name: string;
value: boolean; value: boolean;
defaultValue: boolean; defaultValue: boolean;
} }
+38 -33
View File
@@ -1,33 +1,38 @@
interface Scenery { interface Scenery {
stationName: string; stationName: string;
stationURL: string; stationURL: string;
stationLines: string; stationLines: string;
stationProject: string; stationProject: string;
reqLevel: string; reqLevel: string;
supportersOnly: string; supportersOnly: string;
signalType: string; signalType: string;
controlType: string; controlType: string;
SBL: string; SBL: string;
twoWayBlock: string; twoWayBlock: string;
routesOneWayCatenary: number; routesOneWayCatenary: number;
routesOneWayOther: number; routesOneWayOther: number;
routesTwoWayCatenary: number; routesTwoWayCatenary: number;
routesToWayOther: number; routesToWayOther: number;
default: boolean; default: boolean;
nonPublic: boolean; nonPublic: boolean;
unavailable: boolean; unavailable: boolean;
hasData: boolean; hasData: boolean;
stops: string[]; stops: string[];
checkpoints: string[]; checkpoints: string[];
currentDispatcher: string; currentDispatcher: string;
currentDispatcherId: number; currentDispatcherId: number;
currentDispatcherFrom: number; currentDispatcherFrom: number;
dispatcherHistory: { dispatcherName: string; dispatcherId: number; dispatcherFrom: number; dispatcherTo: number }[]; dispatcherHistory: {
} dispatcherName: string;
dispatcherId: number;
export default Scenery; dispatcherFrom: number;
dispatcherTo: number;
}[];
}
export default Scenery;
+41 -41
View File
@@ -1,41 +1,41 @@
import TrainStop from './TrainStop'; import TrainStop from './TrainStop';
export enum StopStatus { export enum StopStatus {
'arriving' = 'arriving', 'arriving' = 'arriving',
'departed' = 'departed', 'departed' = 'departed',
'departed-away' = 'departed-away', 'departed-away' = 'departed-away',
'online' = 'online', 'online' = 'online',
'stopped' = 'stopped', 'stopped' = 'stopped',
'terminated' = 'terminated', 'terminated' = 'terminated'
} }
export interface ScheduledTrain { export interface ScheduledTrain {
trainId: string; trainId: string;
trainNo: number; trainNo: number;
driverName: string; driverName: string;
driverId: number; driverId: number;
currentStationName: string; currentStationName: string;
currentStationHash: string; currentStationHash: string;
category: string; category: string;
stopInfo: TrainStop; stopInfo: TrainStop;
terminatesAt: string; terminatesAt: string;
beginsAt: string; beginsAt: string;
prevStationName: string; prevStationName: string;
nextStationName: string; nextStationName: string;
arrivingLine: string | null; arrivingLine: string | null;
departureLine: string | null; departureLine: string | null;
prevDepartureLine: string | null; prevDepartureLine: string | null;
nextArrivalLine: string | null; nextArrivalLine: string | null;
signal: string; signal: string;
connectedTrack: string; connectedTrack: string;
stopLabel: string; stopLabel: string;
stopStatus: StopStatus; stopStatus: StopStatus;
stopStatusID: number; stopStatusID: number;
} }
+64 -65
View File
@@ -1,65 +1,64 @@
import { Availability } from './store/storeTypes'; import { Availability } from './store/storeTypes';
import {ScheduledTrain} from './ScheduledTrain'; import { ScheduledTrain } from './ScheduledTrain';
import StationRoutes from './StationRoutes'; import StationRoutes from './StationRoutes';
export default interface Station { export default interface Station {
name: string; name: string;
generalInfo?: { generalInfo?: {
name: string; name: string;
url: string; url: string;
abbr: string; abbr: string;
reqLevel: number; reqLevel: number;
// supportersOnly: boolean; // supportersOnly: boolean;
lines: string;
lines: string; project: string;
project: string; projectUrl?: string;
projectUrl?: string;
signalType: string;
signalType: string; controlType: string;
controlType: string;
SUP: boolean;
SUP: boolean; authors?: string[];
authors?: string[];
availability: Availability;
availability: Availability; routes: StationRoutes;
routes: StationRoutes;
checkpoints: {
checkpoints: { checkpointName: string;
checkpointName: string; scheduledTrains: ScheduledTrain[];
scheduledTrains: ScheduledTrain[]; }[];
}[]; };
};
onlineInfo?: {
onlineInfo?: { hash: string;
hash: string; name: string;
name: string; region: string;
region: string;
maxUsers: number;
maxUsers: number; currentUsers: number;
currentUsers: number;
spawns: { spawnName: string; spawnLength: number; isElectrified: boolean }[];
spawns: { spawnName: string; spawnLength: number; isElectrified: boolean }[]; dispatcherRate: number;
dispatcherRate: number; dispatcherName: string;
dispatcherName: string; dispatcherExp: number;
dispatcherExp: number; dispatcherId: number;
dispatcherId: number; dispatcherIsSupporter: boolean;
dispatcherIsSupporter: boolean;
statusTimestamp: number;
statusTimestamp: number; // statusTimeString: string;
// statusTimeString: string; statusID: string;
statusID: string;
stationTrains?: {
stationTrains?: { driverName: string;
driverName: string; driverId: number;
driverId: number; trainNo: number;
trainNo: number; trainId: string;
trainId: string; stopStatus?: string;
stopStatus?: string; }[];
}[];
scheduledTrains?: ScheduledTrain[];
scheduledTrains?: ScheduledTrain[]; };
}; }
}
+30 -30
View File
@@ -1,30 +1,30 @@
export default interface StationRoutes { export default interface StationRoutes {
oneWay: { oneWay: {
name: string; name: string;
catenary: boolean; catenary: boolean;
SBL: boolean; SBL: boolean;
TWB: boolean; TWB: boolean;
isInternal: boolean; isInternal: boolean;
tracks: number; tracks: number;
speed: number; speed: number;
length: number; length: number;
}[]; }[];
twoWay: { twoWay: {
name: string; name: string;
catenary: boolean; catenary: boolean;
SBL: boolean; SBL: boolean;
TWB: boolean; TWB: boolean;
isInternal: boolean; isInternal: boolean;
tracks: number; tracks: number;
speed: number; speed: number;
length: number; length: number;
}[]; }[];
/* [catenary, noCatenary] */ /* [catenary, noCatenary] */
oneWayCatenaryRouteNames: string[]; oneWayCatenaryRouteNames: string[];
oneWayNoCatenaryRouteNames: string[]; oneWayNoCatenaryRouteNames: string[];
twoWayCatenaryRouteNames: string[]; twoWayCatenaryRouteNames: string[];
twoWayNoCatenaryRouteNames: string[]; twoWayNoCatenaryRouteNames: string[];
sblRouteNames: string[]; sblRouteNames: string[];
} }
+13 -13
View File
@@ -1,13 +1,13 @@
import { DataStatus } from '../enums/DataStatus'; import { DataStatus } from '../enums/DataStatus';
import Station from './Station'; import Station from './Station';
import Train from './Train'; import Train from './Train';
export interface StoreData { export interface StoreData {
stationList: Station[]; stationList: Station[];
trainList: Train[]; trainList: Train[];
dispatcherCount: number; dispatcherCount: number;
sceneryDataStatus: DataStatus; sceneryDataStatus: DataStatus;
dispatcherDataStatus: DataStatus; dispatcherDataStatus: DataStatus;
trainsDataStatus: DataStatus; trainsDataStatus: DataStatus;
} }
+23 -23
View File
@@ -1,23 +1,23 @@
import TrainStop from "./TrainStop"; import TrainStop from './TrainStop';
export default interface Timetable { export default interface Timetable {
trainNo: number; trainNo: number;
success: boolean; success: boolean;
data?: { data?: {
trainNo: number; trainNo: number;
driverName: string; driverName: string;
driverId: number; driverId: number;
currentStationName: string; currentStationName: string;
currentStationHash: string; currentStationHash: string;
timetableId: number; timetableId: number;
category: string; category: string;
route: string; route: string;
TWR: boolean; TWR: boolean;
SKR: boolean; SKR: boolean;
routeDistance: number; routeDistance: number;
followingStops: TrainStop[]; followingStops: TrainStop[];
followingSceneries: string[]; followingSceneries: string[];
} };
} }
+37 -37
View File
@@ -1,37 +1,37 @@
import TrainStop from './TrainStop'; import TrainStop from './TrainStop';
export default interface Train { export default interface Train {
trainId: string; trainId: string;
mass: number; mass: number;
length: number; length: number;
speed: number; speed: number;
signal: string; signal: string;
distance: number; distance: number;
connectedTrack: string; connectedTrack: string;
driverId: number; driverId: number;
trainNo: number; trainNo: number;
driverName: string; driverName: string;
driverLevel: number; driverLevel: number;
currentStationName: string; currentStationName: string;
currentStationHash: string; currentStationHash: string;
locoType: string; locoType: string;
online: boolean; online: boolean;
lastSeen: number; lastSeen: number;
region: string; region: string;
stockList: string[]; stockList: string[];
isTimeout: boolean; isTimeout: boolean;
isSupporter: boolean; isSupporter: boolean;
timetableData?: { timetableData?: {
timetableId: number; timetableId: number;
category: string; category: string;
route: string; route: string;
followingStops: TrainStop[]; followingStops: TrainStop[];
TWR: boolean; TWR: boolean;
SKR: boolean; SKR: boolean;
routeDistance: number; routeDistance: number;
sceneries: string[]; sceneries: string[];
}; };
} }
+30 -30
View File
@@ -1,30 +1,30 @@
export default interface TrainStop { export default interface TrainStop {
stopName: string; stopName: string;
stopNameRAW: string; stopNameRAW: string;
stopType: string; stopType: string;
stopDistance: number; stopDistance: number;
mainStop: boolean; mainStop: boolean;
arrivalLine: string | null; arrivalLine: string | null;
// arrivalTimeString: string | null; // arrivalTimeString: string | null;
arrivalTimestamp: number; arrivalTimestamp: number;
// arrivalRealTimeString: string | null; // arrivalRealTimeString: string | null;
arrivalRealTimestamp: number; arrivalRealTimestamp: number;
arrivalDelay: number; arrivalDelay: number;
departureLine: string | null; departureLine: string | null;
// departureTimeString: string | null; // departureTimeString: string | null;
departureTimestamp: number; departureTimestamp: number;
// departureRealTimeString: string | null; // departureRealTimeString: string | null;
departureRealTimestamp: number; departureRealTimestamp: number;
departureDelay: number; departureDelay: number;
pointId: number; pointId: number;
comments?: any; comments?: any;
beginsHere: boolean; beginsHere: boolean;
terminatesHere: boolean; terminatesHere: boolean;
confirmed: boolean; confirmed: boolean;
stopped: boolean; stopped: boolean;
stopTime: number | null; stopTime: number | null;
} }
+7 -7
View File
@@ -1,7 +1,7 @@
import { TrainFilterSection, TrainFilterType } from '../../enums/TrainFilterType' import { TrainFilterSection, TrainFilterType } from '../../enums/TrainFilterType';
export interface TrainFilter { export interface TrainFilter {
id: TrainFilterType; id: TrainFilterType;
section: TrainFilterSection; section: TrainFilterSection;
isActive: boolean; isActive: boolean;
} }
@@ -1,18 +1,18 @@
export interface DispatcherHistory { export interface DispatcherHistory {
id: string; id: string;
currentDuration: number; currentDuration: number;
dispatcherId: number; dispatcherId: number;
dispatcherName: string; dispatcherName: string;
dispatcherLevel: number | null; dispatcherLevel: number | null;
dispatcherRate: number; dispatcherRate: number;
dispatcherIsSupporter: boolean; dispatcherIsSupporter: boolean;
dispatcherStatus?: number; dispatcherStatus?: number;
isOnline: boolean; isOnline: boolean;
lastOnlineTimestamp: number; lastOnlineTimestamp: number;
region: string; region: string;
stationHash: string; stationHash: string;
stationName: string; stationName: string;
timestampFrom: number; timestampFrom: number;
timestampTo?: number; timestampTo?: number;
} }
+18 -18
View File
@@ -1,18 +1,18 @@
export default interface StationAPIData { export default interface StationAPIData {
dispatcherId: number; dispatcherId: number;
dispatcherName: string; dispatcherName: string;
dispatcherIsSupporter: boolean; dispatcherIsSupporter: boolean;
stationName: string; stationName: string;
stationHash: string; stationHash: string;
region: string; region: string;
maxUsers: number; maxUsers: number;
currentUsers: number; currentUsers: number;
spawn: number; spawn: number;
lastSeen: number; lastSeen: number;
dispatcherExp: number; dispatcherExp: number;
nameFromHeader: string; nameFromHeader: string;
spawnString: string | null; spawnString: string | null;
networkConnectionString: string; networkConnectionString: string;
isOnline: number; isOnline: number;
dispatcherRate: number; dispatcherRate: number;
} }
@@ -49,4 +49,3 @@ export interface ITimetablesDailyStatsResponse {
station: string; station: string;
}[]; }[];
} }
+67 -67
View File
@@ -1,67 +1,67 @@
export interface TimetableHistory { export interface TimetableHistory {
id: number; id: number;
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
timetableId: number; timetableId: number;
trainNo: number; trainNo: number;
trainCategoryCode: string; trainCategoryCode: string;
driverId: number; driverId: number;
driverName: string; driverName: string;
driverLevel: number | null; driverLevel: number | null;
driverIsSupporter: boolean; driverIsSupporter: boolean;
route: string; route: string;
twr: number; twr: number;
skr: number; skr: number;
sceneriesString: string; sceneriesString: string;
currentLocation: string[]; currentLocation: string[];
routeDistance: number; routeDistance: number;
currentDistance: number; currentDistance: number;
confirmedStopsCount: number; confirmedStopsCount: number;
allStopsCount: number; allStopsCount: number;
beginDate: string; beginDate: string;
endDate: string; endDate: string;
scheduledBeginDate: string; scheduledBeginDate: string;
scheduledEndDate: string; scheduledEndDate: string;
terminated: boolean; terminated: boolean;
fulfilled: boolean; fulfilled: boolean;
authorName?: string; authorName?: string;
authorId?: number; authorId?: number;
stopsString?: string; stopsString?: string;
stockString?: string; stockString?: string;
stockHistory: string[]; stockHistory: string[];
stockMass?: number; stockMass?: number;
stockLength?: number; stockLength?: number;
maxSpeed?: number; maxSpeed?: number;
hashesString?: string; hashesString?: string;
currentSceneryName?: string; currentSceneryName?: string;
currentSceneryHash?: string; currentSceneryHash?: string;
routeSceneries?: string; routeSceneries?: string;
checkpointArrivals?: string[]; checkpointArrivals?: string[];
checkpointDepartures?: string[]; checkpointDepartures?: string[];
checkpointArrivalsScheduled?: string[]; checkpointArrivalsScheduled?: string[];
checkpointDeparturesScheduled?: string[]; checkpointDeparturesScheduled?: string[];
checkpointStopTypes?: string[]; checkpointStopTypes?: string[];
} }
export interface SceneryTimetableHistory { export interface SceneryTimetableHistory {
timetables: TimetableHistory[]; timetables: TimetableHistory[];
// totalCount: number; // totalCount: number;
// sceneryName: string; // sceneryName: string;
} }
@@ -1,23 +1,23 @@
import { JournalTimetableSorter } from '../../types/JournalTimetablesTypes'; import { JournalTimetableSorter } from '../../types/JournalTimetablesTypes';
export interface TimetablesQueryParams { export interface TimetablesQueryParams {
driverName?: string; driverName?: string;
trainNo?: string; trainNo?: string;
timetableId?: string; timetableId?: string;
authorName?: string; authorName?: string;
timestampFrom?: number; timestampFrom?: number;
timestampTo?: number; timestampTo?: number;
issuedFrom?: string; issuedFrom?: string;
countFrom?: number; countFrom?: number;
countLimit?: number; countLimit?: number;
fulfilled?: number; fulfilled?: number;
terminated?: number; terminated?: number;
twr?: number; twr?: number;
skr?: number; skr?: number;
sortBy?: JournalTimetableSorter['id']; sortBy?: JournalTimetableSorter['id'];
} }
+68 -68
View File
@@ -1,68 +1,68 @@
export interface TimetableStop { export interface TimetableStop {
stopName: string; stopName: string;
stopNameRAW: string; stopNameRAW: string;
stopType: string; stopType: string;
stopDistance: number; stopDistance: number;
pointId: number; pointId: number;
mainStop: boolean; mainStop: boolean;
arrivalLine: string; arrivalLine: string;
arrivalTimestamp: number; arrivalTimestamp: number;
arrivalRealTimestamp: number; arrivalRealTimestamp: number;
arrivalDelay: number; arrivalDelay: number;
departureLine: string; departureLine: string;
departureTimestamp: number; departureTimestamp: number;
departureRealTimestamp: number; departureRealTimestamp: number;
departureDelay: number; departureDelay: number;
comments?: any; comments?: any;
beginsHere: boolean; beginsHere: boolean;
terminatesHere: boolean; terminatesHere: boolean;
confirmed: boolean; confirmed: boolean;
stopped: boolean; stopped: boolean;
stopTime: number; stopTime: number;
} }
export interface TrainTimetable { export interface TrainTimetable {
timetableId: number; timetableId: number;
category: string; category: string;
route: string; route: string;
stopList: TimetableStop[]; stopList: TimetableStop[];
TWR: boolean; TWR: boolean;
SKR: boolean; SKR: boolean;
sceneries: string[]; sceneries: string[];
} }
export interface TrainAPIData { export interface TrainAPIData {
trainNo: number; trainNo: number;
mass: number; mass: number;
length: number; length: number;
speed: number; speed: number;
stockString: string; stockString: string;
signal: string; signal: string;
distance: number; distance: number;
connectedTrack: string; connectedTrack: string;
driverName: string; driverName: string;
driverId: number; driverId: number;
driverIsSupporter: boolean; driverIsSupporter: boolean;
driverLevel?: number; driverLevel?: number;
currentStationName: string; currentStationName: string;
currentStationHash: string; currentStationHash: string;
online: boolean; online: boolean;
lastSeen: number; lastSeen: number;
region: string; region: string;
isTimeout: boolean; isTimeout: boolean;
timetable?: TrainTimetable; timetable?: TrainTimetable;
} }
+93 -93
View File
@@ -1,93 +1,93 @@
import { Socket } from 'socket.io-client'; import { Socket } from 'socket.io-client';
import { DataStatus } from '../../enums/DataStatus'; import { DataStatus } from '../../enums/DataStatus';
import StationAPIData from '../api/StationAPIData'; import StationAPIData from '../api/StationAPIData';
import { TrainAPIData } from '../api/TrainAPIData'; import { TrainAPIData } from '../api/TrainAPIData';
import Station from '../Station'; import Station from '../Station';
import Train from '../Train'; import Train from '../Train';
import { DispatcherStatsAPIData } from '../api/DispatcherStatsAPIData'; import { DispatcherStatsAPIData } from '../api/DispatcherStatsAPIData';
import { DriverStatsAPIData } from '../api/DriverStatsAPIData'; import { DriverStatsAPIData } from '../api/DriverStatsAPIData';
import { RollingStockGithubData } from '../github_api/StockInfoGithubData'; import { RollingStockGithubData } from '../github_api/StockInfoGithubData';
export type Availability = 'default' | 'unavailable' | 'nonPublic' | 'abandoned' | 'nonDefault'; export type Availability = 'default' | 'unavailable' | 'nonPublic' | 'abandoned' | 'nonDefault';
export interface StoreState { export interface StoreState {
stationList: Station[]; stationList: Station[];
trainList: Train[]; trainList: Train[];
apiData: APIData; apiData: APIData;
rollingStockData?: RollingStockGithubData; rollingStockData?: RollingStockGithubData;
lastDispatcherStatuses: { hash: string; statusTimestamp: number; statusID: string }[]; lastDispatcherStatuses: { hash: string; statusTimestamp: number; statusID: string }[];
sceneryData: any[][]; sceneryData: any[][];
region: { id: string; value: string }; region: { id: string; value: string };
trainCount: number; trainCount: number;
stationCount: number; stationCount: number;
webSocket?: Socket; webSocket?: Socket;
isOffline: boolean; isOffline: boolean;
dispatcherStatsName: string; dispatcherStatsName: string;
dispatcherStatsData?: DispatcherStatsAPIData; dispatcherStatsData?: DispatcherStatsAPIData;
driverStatsName: string; driverStatsName: string;
driverStatsData?: DriverStatsAPIData; driverStatsData?: DriverStatsAPIData;
driverStatsStatus: DataStatus; driverStatsStatus: DataStatus;
chosenModalTrainId?: string; chosenModalTrainId?: string;
currentStatsTab: 'daily' | 'driver' | null; currentStatsTab: 'daily' | 'driver' | null;
dataStatuses: { dataStatuses: {
connection: DataStatus; connection: DataStatus;
sceneries: DataStatus; sceneries: DataStatus;
timetables: DataStatus; timetables: DataStatus;
dispatchers: DataStatus; dispatchers: DataStatus;
trains: DataStatus; trains: DataStatus;
}; };
listenerLaunched: boolean; listenerLaunched: boolean;
blockScroll: boolean; blockScroll: boolean;
modalLastClickedTarget: EventTarget | null; modalLastClickedTarget: EventTarget | null;
} }
export interface APIData { export interface APIData {
stations?: StationAPIData[]; stations?: StationAPIData[];
dispatchers?: string[][]; dispatchers?: string[][];
trains?: TrainAPIData[]; trains?: TrainAPIData[];
connectedSocketCount: number; connectedSocketCount: number;
} }
export interface StationRoutesInfo { export interface StationRoutesInfo {
routeName: string; routeName: string;
isElectric: boolean; isElectric: boolean;
isInternal: boolean; isInternal: boolean;
isRouteSBL: boolean; isRouteSBL: boolean;
routeLength: number; routeLength: number;
routeSpeed: number; routeSpeed: number;
routeTracks: number; routeTracks: number;
} }
export interface StationJSONData { export interface StationJSONData {
name: string; name: string;
abbr: string; abbr: string;
url: string; url: string;
lines: string; lines: string;
project: string; project: string;
projectUrl: string; projectUrl: string;
reqLevel: number; reqLevel: number;
signalType: string; signalType: string;
controlType: string; controlType: string;
SUP: boolean; SUP: boolean;
// routes: string; // routes: string;
routesInfo: StationRoutesInfo[]; routesInfo: StationRoutesInfo[];
checkpoints: string | null; checkpoints: string | null;
authors?: string; authors?: string;
availability: Availability; availability: Availability;
} }
+49 -49
View File
@@ -1,49 +1,49 @@
export default class StorageManager { export default class StorageManager {
static registerStorage(name: string) { static registerStorage(name: string) {
window.localStorage.setItem(name, '1'); window.localStorage.setItem(name, '1');
} }
static unregisterStorage(name: string) { static unregisterStorage(name: string) {
window.localStorage.removeItem(name); window.localStorage.removeItem(name);
} }
static isRegistered(name: string) { static isRegistered(name: string) {
return window.localStorage.getItem(name) ? true : false; return window.localStorage.getItem(name) ? true : false;
} }
static setBooleanValue(key: string, val: boolean) { static setBooleanValue(key: string, val: boolean) {
window.localStorage.setItem(key, val.toString()); window.localStorage.setItem(key, val.toString());
} }
static setNumericValue(key: string, val: number) { static setNumericValue(key: string, val: number) {
window.localStorage.setItem(key, val.toString()); window.localStorage.setItem(key, val.toString());
} }
static setStringValue(key: string, val: string) { static setStringValue(key: string, val: string) {
window.localStorage.setItem(key, val); window.localStorage.setItem(key, val);
} }
static setValue(key: string, val: any) { static setValue(key: string, val: any) {
if (typeof val == 'boolean') this.setBooleanValue(key, val); if (typeof val == 'boolean') this.setBooleanValue(key, val);
else if (typeof val == 'number') this.setNumericValue(key, val); else if (typeof val == 'number') this.setNumericValue(key, val);
else if (typeof val == 'string') this.setStringValue(key, val); else if (typeof val == 'string') this.setStringValue(key, val);
else this.setStringValue(key, val); else this.setStringValue(key, val);
} }
static removeValue(key: string) { static removeValue(key: string) {
window.localStorage.removeItem(key); window.localStorage.removeItem(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;
} }
static getStringValue(key: string): string { static getStringValue(key: string): string {
return window.localStorage.getItem(key) || ''; return window.localStorage.getItem(key) || '';
} }
static getNumericValue(key: string): number { static getNumericValue(key: string): number {
const itemValue = window.localStorage.getItem(key); const itemValue = window.localStorage.getItem(key);
return itemValue ? parseInt(itemValue) : 0; return itemValue ? parseInt(itemValue) : 0;
} }
} }
+147 -130
View File
@@ -1,130 +1,147 @@
import { TrainFilter } from '../interfaces/Trains/TrainFilter'; import { TrainFilter } from '../interfaces/Trains/TrainFilter';
import { TrainFilterType } from '../enums/TrainFilterType'; import { TrainFilterType } from '../enums/TrainFilterType';
import Train from '../interfaces/Train'; import Train from '../interfaces/Train';
import TrainStop from '../interfaces/TrainStop'; import TrainStop from '../interfaces/TrainStop';
function confirmedPercentage(stops: TrainStop[] | undefined) { function confirmedPercentage(stops: TrainStop[] | undefined) {
if (!stops) return -1; if (!stops) return -1;
return Number(((stops.filter((stop) => stop.confirmed).length / stops.length) * 100).toFixed(0)); return Number(((stops.filter((stop) => stop.confirmed).length / stops.length) * 100).toFixed(0));
} }
function currentDelay(stops: TrainStop[] | undefined) { function currentDelay(stops: TrainStop[] | undefined) {
if (!stops) return -Infinity; if (!stops) return -Infinity;
const delay = const delay =
stops.find((stop, i) => (i == 0 && !stop.confirmed) || (i > 0 && stops[i - 1].confirmed && !stop.confirmed)) stops.find(
?.departureDelay || 0; (stop, i) =>
(i == 0 && !stop.confirmed) || (i > 0 && stops[i - 1].confirmed && !stop.confirmed)
return delay; )?.departureDelay || 0;
}
return delay;
function filterTrainList(trainList: Train[], searchedTrain: string, searchedDriver: string, filters: TrainFilter[]) { }
return trainList.filter((train) => {
const isFiltered = filters.every((f) => { function filterTrainList(
if (f.isActive) return true; trainList: Train[],
searchedTrain: string,
switch (f.id) { searchedDriver: string,
case TrainFilterType.noTimetable: filters: TrainFilter[]
return train.timetableData; ) {
return trainList.filter((train) => {
case TrainFilterType.withTimetable: const isFiltered = filters.every((f) => {
return !train.timetableData; if (f.isActive) return true;
case TrainFilterType.withComments: switch (f.id) {
return !train.timetableData?.followingStops.some((stop) => stop.comments); case TrainFilterType.noTimetable:
return train.timetableData;
case TrainFilterType.noComments:
return train.timetableData?.followingStops.some((stop) => stop.comments); case TrainFilterType.withTimetable:
return !train.timetableData;
case TrainFilterType.twr:
return !train.timetableData?.TWR; case TrainFilterType.withComments:
return !train.timetableData?.followingStops.some((stop) => stop.comments);
case TrainFilterType.skr:
return !train.timetableData?.SKR; case TrainFilterType.noComments:
return train.timetableData?.followingStops.some((stop) => stop.comments);
case TrainFilterType.common:
return train.timetableData?.SKR || train.timetableData?.TWR; case TrainFilterType.twr:
return !train.timetableData?.TWR;
case TrainFilterType.passenger:
return !/^[AMRE]\D{2}$/.test(train.timetableData?.category || ''); case TrainFilterType.skr:
return !train.timetableData?.SKR;
case TrainFilterType.freight:
return !train.timetableData?.category.startsWith('T'); case TrainFilterType.common:
return train.timetableData?.SKR || train.timetableData?.TWR;
case TrainFilterType.other:
return !/^[PXZL]\D{2}$/.test(train.timetableData?.category || ''); case TrainFilterType.passenger:
return !/^[AMRE]\D{2}$/.test(train.timetableData?.category || '');
default:
return true; case TrainFilterType.freight:
} return !train.timetableData?.category.startsWith('T');
});
case TrainFilterType.other:
return ( return !/^[PXZL]\D{2}$/.test(train.timetableData?.category || '');
(searchedTrain.length > 0 ? train.trainNo.toString().startsWith(searchedTrain) : true) &&
(searchedDriver.length > 0 ? train.driverName.toLowerCase().startsWith(searchedDriver.toLowerCase()) : true) && default:
(!train.timetableData ? train.online : train.timetableData) && return true;
isFiltered }
); });
});
} return (
(searchedTrain.length > 0 ? train.trainNo.toString().startsWith(searchedTrain) : true) &&
function sortTrainList(trainList: Train[], sorterActive: { id: string; dir: number }) { (searchedDriver.length > 0
return trainList.sort((a: Train, b: Train) => { ? train.driverName.toLowerCase().startsWith(searchedDriver.toLowerCase())
switch (sorterActive.id) { : true) &&
case 'id': (!train.timetableData ? train.online : train.timetableData) &&
if ((a.timetableData?.timetableId || -1) > (b.timetableData?.timetableId || -1)) return sorterActive.dir; isFiltered
);
return -sorterActive.dir; });
}
case 'mass':
if (a.mass > b.mass) return sorterActive.dir; function sortTrainList(trainList: Train[], sorterActive: { id: string; dir: number }) {
return -sorterActive.dir; return trainList.sort((a: Train, b: Train) => {
switch (sorterActive.id) {
case 'routeDistance': case 'id':
if ((a.timetableData?.routeDistance || -1) > (b.timetableData?.routeDistance || -1)) return sorterActive.dir; if ((a.timetableData?.timetableId || -1) > (b.timetableData?.timetableId || -1))
return sorterActive.dir;
return -sorterActive.dir;
return -sorterActive.dir;
case 'progress':
if (confirmedPercentage(a.timetableData?.followingStops) > confirmedPercentage(b.timetableData?.followingStops)) case 'mass':
return sorterActive.dir; if (a.mass > b.mass) return sorterActive.dir;
return -sorterActive.dir;
return -sorterActive.dir;
case 'routeDistance':
case 'delay': if ((a.timetableData?.routeDistance || -1) > (b.timetableData?.routeDistance || -1))
if (currentDelay(a.timetableData?.followingStops) > currentDelay(b.timetableData?.followingStops)) return sorterActive.dir;
return sorterActive.dir;
return -sorterActive.dir;
return -sorterActive.dir;
case 'progress':
case 'speed': if (
if (a.speed > b.speed) return sorterActive.dir; confirmedPercentage(a.timetableData?.followingStops) >
return -sorterActive.dir; confirmedPercentage(b.timetableData?.followingStops)
)
case 'timetable': return sorterActive.dir;
if (a.trainNo > b.trainNo) return sorterActive.dir;
return -sorterActive.dir; return -sorterActive.dir;
case 'length': case 'delay':
if (a.length > b.length) return sorterActive.dir; if (
return -sorterActive.dir; currentDelay(a.timetableData?.followingStops) >
currentDelay(b.timetableData?.followingStops)
default: )
break; return sorterActive.dir;
}
return -sorterActive.dir;
return 0;
}); case 'speed':
} if (a.speed > b.speed) return sorterActive.dir;
return -sorterActive.dir;
export function filteredTrainList(
trainList: Train[], case 'timetable':
searchedTrain: string, if (a.trainNo > b.trainNo) return sorterActive.dir;
searchedDriver: string, return -sorterActive.dir;
sorterActive: { id: string; dir: number },
filters: TrainFilter[] case 'length':
) { if (a.length > b.length) return sorterActive.dir;
const filtered = filterTrainList(trainList, searchedTrain, searchedDriver, filters); return -sorterActive.dir;
return [...sortTrainList(filtered, sorterActive)];
} default:
break;
}
return 0;
});
}
export function filteredTrainList(
trainList: Train[],
searchedTrain: string,
searchedDriver: string,
sorterActive: { id: string; dir: number },
filters: TrainFilter[]
) {
const filtered = filterTrainList(trainList, searchedTrain, searchedDriver, filters);
return [...sortTrainList(filtered, sorterActive)];
}
+8 -8
View File
@@ -1,8 +1,8 @@
export type JournalDispatcherSearcher = { export type JournalDispatcherSearcher = {
[key in 'search-dispatcher' | 'search-station' | 'search-date']: string; [key in 'search-dispatcher' | 'search-station' | 'search-date']: string;
}; };
export interface JournalDispatcherSorter { export interface JournalDispatcherSorter {
id: 'timestampFrom' | 'duration'; id: 'timestampFrom' | 'duration';
dir: -1 | 1; dir: -1 | 1;
} }

Some files were not shown because too many files have changed in this diff Show More