Aktualizacja

This commit is contained in:
2021-04-17 00:14:44 +02:00
parent 6b867cc457
commit d31e88260a
13 changed files with 3725 additions and 3901 deletions
+334 -342
View File
@@ -1,342 +1,334 @@
<template>
<div class="app">
<UpdateModal
:currentVersion="VERSION"
@toggleUpdateModal="toggleUpdateModal"
v-if="updateModalVisible"
/>
<div class="app_container">
<header class="app_header">
<div class="header_body">
<span class="header_brand">
<span>
<span>Stacj</span>
<img src="@/assets/trainlogo.png" alt="trainlogo" />
<span>wnik</span>
</span>
<span class="brand_lang">
<span
class="lang pl"
@click="changeLang('en')"
:class="{ current: currentLang == 'pl' }"
v-if="currentLang == 'pl'"
>
<img :src="iconPL" alt="icon-pl" />
</span>
<span
class="lang en"
@click="changeLang('pl')"
:class="{ current: currentLang == 'en' }"
v-if="currentLang == 'en'"
>
<img :src="iconEN" alt="icon-en" />
</span>
</span>
</span>
<span class="header_info">
<Clock />
<div class="info_counter">
<img src="@/assets/icon-dispatcher.svg" alt="icon dispatcher" />
<span>{{ data.stationCount }}</span>
<span>{{ data.trainCount }}</span>
<img src="@/assets/icon-train.svg" alt="icon train" />
</div>
</span>
<span class="header_links">
<router-link class="route" active-class="route-active" to="/" exact
>{{ $t("app.sceneries") }}
</router-link>
/
<router-link class="route" active-class="route-active" to="/trains"
>{{ $t("app.trains") }}
</router-link>
<!-- <router-link
class="route"
active-class="route-active"
to="/history"
>{{ $t("app.journal") }}</router-link
> -->
</span>
</div>
</header>
<main class="app_main">
<transition name="view-anim" mode="out-in">
<keep-alive>
<router-view />
</keep-alive>
</transition>
</main>
<footer class="app_footer">
&copy;
<a href="https://td2.info.pl/profile/?u=20777" target="_blank">
Spythere
</a>
2021 | v{{ VERSION }} | [<a
target="_blank"
href="https://paypal.me/spythere"
>{{ $t("app.support") }}!</a
>]
</footer>
</div>
</div>
</template>
<script lang="ts">
import { Vue, Component } from "vue-property-decorator";
import { Action, Getter } from "vuex-class";
import UpdateModal from "@/components/Global/UpdateModal.vue";
import Clock from "@/components/App/Clock.vue";
import StorageManager from "@/scripts/storageManager";
import DataModule from "@/store/modules/DataModule";
import { getModule } from "vuex-module-decorators";
@Component({
components: { Clock, UpdateModal },
})
export default class App extends Vue {
@Action("synchronizeData") synchronizeData;
@Getter("getAllData") data;
private VERSION = "1.4.4";
hasReleaseNotes = false;
updateModalVisible = false;
currentLang = "pl";
iconEN = require("@/assets/icon-en.jpg");
iconPL = require("@/assets/icon-pl.svg");
dataStore: DataModule = getModule(DataModule);
get test() {
return this.dataStore.getTest;
}
mounted() {
this.synchronizeData();
setTimeout(() => {
this.dataStore.fetchTest();
}, 3000);
if (StorageManager.getStringValue("lang")) {
this.changeLang(StorageManager.getStringValue("lang"));
} else if (window.navigator.language) {
switch (window.navigator.language) {
case "pl-PL":
this.changeLang("pl");
break;
case "en-EN":
default:
this.changeLang("en");
break;
}
this.currentLang = this.$i18n.locale;
}
if (StorageManager.getStringValue("version") != this.VERSION) {
StorageManager.setStringValue("version", this.VERSION);
if (this.hasReleaseNotes)
StorageManager.setBooleanValue("version_notes_read", false);
}
this.updateModalVisible =
this.hasReleaseNotes &&
!StorageManager.getBooleanValue("version_notes_read");
}
changeLang(lang: string) {
this.$i18n.locale = lang;
this.currentLang = lang;
StorageManager.setStringValue("lang", lang);
console.log("Switched to: " + lang);
}
toggleUpdateModal() {
this.updateModalVisible = !this.updateModalVisible;
StorageManager.setBooleanValue("version_notes_read", true);
}
}
</script>
<style lang="scss">
@import "./styles/responsive.scss";
@import "./styles/variables.scss";
@import "./styles/global.scss";
@import "./styles/scenery_status.scss";
:root {
--clr-primary: #ffc014;
--clr-secondary: #2f2f2f;
--clr-bg: #333;
--clr-accent: #1085b3;
--clr-accent2: #ff3d5d;
--clr-skr: #ff5100;
--clr-twr: #ffbb00;
}
// VUE ROUTE CHANGE ANIMATION
.view-anim {
&-enter {
opacity: 0.02;
}
&-leave-to {
opacity: 0.02;
}
&-enter-active,
&-leave-active {
transition: all $animDuration $animType;
min-height: 100%;
}
}
.route {
margin: 0 0.2em;
&-active {
color: $accentCol;
font-weight: bold;
}
}
// APP
.app {
background: $bgCol;
color: white;
overflow: hidden;
font-size: 1rem;
@include smallScreen() {
font-size: calc(0.45rem + 1vw);
}
}
// CONTAINER
.app_container {
display: flex;
flex-flow: column;
min-width: 0;
min-height: 100vh;
header {
flex: 0 0 auto;
}
main {
flex: 1 1 auto;
}
footer {
flex: 0 1 0.2em;
}
}
// HEADER
.app_header {
background: $primaryCol;
padding: 0.15em;
border-radius: 0 0 1em 1em;
display: flex;
justify-content: center;
}
.header {
&_brand {
position: relative;
width: 100%;
font-size: 4.25em;
text-align: center;
img {
width: 0.8em;
}
.brand_lang {
position: absolute;
right: 0;
transform: translate(110%, -35%);
img {
width: 0.6em;
}
cursor: pointer;
}
}
&_info {
display: flex;
justify-content: space-between;
font-size: 1.25em;
margin: 0 0.3em;
padding: 0.2em;
}
&_links {
display: flex;
justify-content: center;
border-radius: 0.7em;
font-size: 1.25em;
padding: 0.5em;
}
}
// COUNTER
.info_counter {
display: flex;
align-items: center;
color: $accentCol;
span {
margin: 0 0.15em;
}
img {
width: 1.35em;
}
}
// FOOTER
footer.app_footer {
max-width: 100%;
padding: 0.5em;
z-index: 10;
background: #111;
color: white;
text-align: center;
}
</style>
<template>
<div class="app">
<UpdateModal
:currentVersion="VERSION"
@toggleUpdateModal="toggleUpdateModal"
v-if="updateModalVisible"
/>
<div class="app_container">
<header class="app_header">
<div class="header_body">
<span class="header_brand">
<span>
<span>Stacj</span>
<img src="@/assets/trainlogo.png" alt="trainlogo" />
<span>wnik</span>
</span>
<span class="brand_lang">
<span
class="lang pl"
@click="changeLang('en')"
:class="{ current: currentLang == 'pl' }"
v-if="currentLang == 'pl'"
>
<img :src="iconPL" alt="icon-pl" />
</span>
<span
class="lang en"
@click="changeLang('pl')"
:class="{ current: currentLang == 'en' }"
v-if="currentLang == 'en'"
>
<img :src="iconEN" alt="icon-en" />
</span>
</span>
</span>
<span class="header_info">
<Clock />
<div class="info_counter">
<img src="@/assets/icon-dispatcher.svg" alt="icon dispatcher" />
<span>{{ data.stationCount }}</span>
<span>{{ data.trainCount }}</span>
<img src="@/assets/icon-train.svg" alt="icon train" />
</div>
</span>
<span class="header_links">
<router-link class="route" active-class="route-active" to="/" exact
>{{ $t("app.sceneries") }}
</router-link>
/
<router-link class="route" active-class="route-active" to="/trains"
>{{ $t("app.trains") }}
</router-link>
</span>
</div>
</header>
<main class="app_main">
<transition name="view-anim" mode="out-in">
<keep-alive>
<router-view />
</keep-alive>
</transition>
</main>
<footer class="app_footer">
&copy;
<a href="https://td2.info.pl/profile/?u=20777" target="_blank">
Spythere
</a>
2021 | v{{ VERSION }} | [<a
target="_blank"
href="https://paypal.me/spythere"
>{{ $t("app.support") }}!</a
>]
</footer>
</div>
</div>
</template>
<script lang="ts">
import { Vue, Component } from "vue-property-decorator";
import { Action, Getter } from "vuex-class";
import UpdateModal from "@/components/Global/UpdateModal.vue";
import Clock from "@/components/App/Clock.vue";
import StorageManager from "@/scripts/storageManager";
@Component({
components: { Clock, UpdateModal },
})
export default class App extends Vue {
@Action("synchronizeData") synchronizeData;
@Getter("getAllData") data;
private VERSION = "1.4.4";
hasReleaseNotes = false;
updateModalVisible = false;
currentLang = "pl";
iconEN = require("@/assets/icon-en.jpg");
iconPL = require("@/assets/icon-pl.svg");
toggleUpdateModal() {
this.updateModalVisible = !this.updateModalVisible;
StorageManager.setBooleanValue("version_notes_read", true);
}
changeLang(lang: string) {
this.$i18n.locale = lang;
this.currentLang = lang;
StorageManager.setStringValue("lang", lang);
}
loadLang() {
const storageLang = StorageManager.getStringValue("lang");
if (storageLang) {
this.changeLang(storageLang);
return;
}
if (!window.navigator.language) {
this.changeLang("pl");
return;
}
switch (window.navigator.language) {
case "pl-PL":
this.changeLang("pl");
break;
case "en-EN":
default:
this.changeLang("en");
break;
}
return;
}
created() {
this.loadLang();
this.synchronizeData();
}
mounted() {
if (StorageManager.getStringValue("version") != this.VERSION) {
StorageManager.setStringValue("version", this.VERSION);
if (this.hasReleaseNotes)
StorageManager.setBooleanValue("version_notes_read", false);
}
this.updateModalVisible =
this.hasReleaseNotes &&
!StorageManager.getBooleanValue("version_notes_read");
}
}
</script>
<style lang="scss">
@import "./styles/responsive.scss";
@import "./styles/variables.scss";
@import "./styles/global.scss";
@import "./styles/scenery_status.scss";
:root {
--clr-primary: #ffc014;
--clr-secondary: #2f2f2f;
--clr-bg: #333;
--clr-accent: #1085b3;
--clr-accent2: #ff3d5d;
--clr-skr: #ff5100;
--clr-twr: #ffbb00;
}
// VUE ROUTE CHANGE ANIMATION
.view-anim {
&-enter {
opacity: 0.02;
}
&-leave-to {
opacity: 0.02;
}
&-enter-active,
&-leave-active {
transition: all $animDuration $animType;
min-height: 100%;
}
}
.route {
margin: 0 0.2em;
&-active {
color: $accentCol;
font-weight: bold;
}
}
// APP
.app {
background: $bgCol;
color: white;
overflow: hidden;
font-size: 1rem;
@include smallScreen() {
font-size: calc(0.45rem + 1vw);
}
}
// CONTAINER
.app_container {
display: flex;
flex-flow: column;
min-width: 0;
min-height: 100vh;
header {
flex: 0 0 auto;
}
main {
flex: 1 1 auto;
}
footer {
flex: 0 1 0.2em;
}
}
// HEADER
.app_header {
background: $primaryCol;
padding: 0.15em;
border-radius: 0 0 1em 1em;
display: flex;
justify-content: center;
}
.header {
&_brand {
position: relative;
width: 100%;
font-size: 4.25em;
text-align: center;
img {
width: 0.8em;
}
.brand_lang {
position: absolute;
right: 0;
transform: translate(110%, -35%);
img {
width: 0.6em;
}
cursor: pointer;
}
}
&_info {
display: flex;
justify-content: space-between;
font-size: 1.25em;
margin: 0 0.3em;
padding: 0.2em;
}
&_links {
display: flex;
justify-content: center;
border-radius: 0.7em;
font-size: 1.25em;
padding: 0.5em;
}
}
// COUNTER
.info_counter {
display: flex;
align-items: center;
color: $accentCol;
span {
margin: 0 0.15em;
}
img {
width: 1.35em;
}
}
// FOOTER
footer.app_footer {
max-width: 100%;
padding: 0.5em;
z-index: 10;
background: #111;
color: white;
text-align: center;
}
</style>
+96 -96
View File
@@ -1,97 +1,97 @@
<template>
<div class="modal">
<div class="header">
<span>Stacj</span>
<img src="@/assets/trainlogo.png" alt="trainlogo" />
<span>wnik</span>
<sup style="font-size: 0.5em; margin-left: 10px;" class="title">1.4</sup>
</div>
<div class="title">Dziennik Aktywności Scenerii dostępny w wersji beta!</div>
<div class="content">
Do użytku został oddany Dziennik Aktywności Scenerii, który pozwala na dostęp do informacji kto i kiedy dyżurował na danej stacji.
Aby przejść do zakładki z dziennikiem wystarczy wybrać opcję "DZIENNIK" w menu na górze strony. Funkcjonalność ta jest nadal w trakcie prac,
więc informacje, które pokazuje, mogą być niepoprawne, a dane kasowane w ramach dalszych testów.
<div style="text-align: center; font-weight: bold; margin: 0.5em 0;">Miłego korzystania!</div>
</div>
<button class="button" @click="toggleUpdateModal">PRZYJĄŁEM!</button>
</div>
</template>
<script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator";
@Component
export default class UpdateModal extends Vue {
@Prop() currentVersion!: string;
STORAGE_ID = "modal_update";
toggleUpdateModal(type: string) {
this.$emit("toggleUpdateModal");
}
}
</script>
<style lang="scss" scoped>
@import "../../styles/responsive";
.modal {
z-index: 100;
padding: 1em;
border-radius: 1em;
position: fixed;
top: 50%;
left: 50%;
width: 65%;
max-width: 950px;
max-height: 95vh;
overflow: auto;
transform: translate(-50%, -50%);
background: rgba(black, 0.85);
color: white;
text-align: center;
@include smallScreen() {
font-size: 0.8em;
width: 95%;
}
}
.header {
font-size: 4.5em;
img {
width: 0.8em;
}
}
.title {
font-size: 2em;
}
.content {
font-size: 1.4em;
text-align: justify;
ul {
list-style: square inside;
}
}
.button {
font-size: 1.25em;
margin: 0 auto;
}
<template>
<div class="modal">
<div class="header">
<span>Stacj</span>
<img src="@/assets/trainlogo.png" alt="trainlogo" />
<span>wnik</span>
<sup style="font-size: 0.5em; margin-left: 10px;" class="title">1.4</sup>
</div>
<div class="title">Dziennik Aktywności Scenerii dostępny w wersji beta!</div>
<div class="content">
Do użytku został oddany Dziennik Aktywności Scenerii, który pozwala na dostęp do informacji kto i kiedy dyżurował na danej stacji.
Aby przejść do zakładki z dziennikiem wystarczy wybrać opcję "DZIENNIK" w menu na górze strony. Funkcjonalność ta jest nadal w trakcie prac,
więc informacje, które pokazuje, mogą być niepoprawne, a dane kasowane w ramach dalszych testów.
<div style="text-align: center; font-weight: bold; margin: 0.5em 0;">Miłego korzystania!</div>
</div>
<button class="button" @click="toggleUpdateModal">PRZYJĄŁEM!</button>
</div>
</template>
<script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator";
@Component
export default class UpdateModal extends Vue {
@Prop() currentVersion!: string;
STORAGE_ID = "modal_update";
toggleUpdateModal(type: string) {
this.$emit("toggleUpdateModal");
}
}
</script>
<style lang="scss" scoped>
@import "../../styles/responsive";
.modal {
z-index: 100;
padding: 1em;
border-radius: 1em;
position: fixed;
top: 50%;
left: 50%;
width: 65%;
max-width: 950px;
max-height: 95vh;
overflow: auto;
transform: translate(-50%, -50%);
background: rgba(black, 0.85);
color: white;
text-align: center;
@include smallScreen() {
font-size: 0.8em;
width: 95%;
}
}
.header {
font-size: 4.5em;
img {
width: 0.8em;
}
}
.title {
font-size: 2em;
}
.content {
font-size: 1.4em;
text-align: justify;
ul {
list-style: square inside;
}
}
.button {
font-size: 1.25em;
margin: 0 auto;
}
</style>
+286 -286
View File
@@ -1,287 +1,287 @@
<template>
<div class="train-options">
<div class="options_wrapper">
<div class="train-sorter option">
<div class="train-sorter_wrapper">
<div class="train-sorter_selected" @click="toggleSorterOptions">
<span>{{ currentSorterOption }}</span>
<img :src="descIcon" alt="icon-select" />
</div>
<div class="train-sorter_options">
<ul :class="{ open: sorterOptionsOpen }">
<li
v-for="(option, i) in sorterOptions"
:key="i"
@click="() => chooseOption(option)"
>
<input type="radio" name="sort" :id="option.id" />
<label :for="option.id">
{{ $t(`trains.option-${option.id}`) }}
</label>
</li>
</ul>
</div>
</div>
</div>
<div class="train-search_train option">
<div class="search-box">
<input
class="search-input"
:placeholder="$t('trains.search-no')"
v-model="searchedTrain"
/>
<img
class="search-exit"
:src="exitIcon"
alt="exit-icon"
@click="() => (searchedTrain = '')"
/>
</div>
</div>
<div class="train-search_driver option">
<div class="search-box">
<input
class="search-input"
:placeholder="$t('trains.search-driver')"
v-model="searchedDriver"
/>
<img
class="search-exit"
:src="exitIcon"
alt="exit-icon"
@click="() => (searchedDriver = '')"
/>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue, Watch, Prop } from "vue-property-decorator";
@Component
export default class TrainOptions extends Vue {
ascIcon = require("@/assets/icon-arrow-asc.svg");
descIcon = require("@/assets/icon-arrow-desc.svg");
exitIcon = require("@/assets/icon-exit.svg");
clickEventListener!: EventListener;
sorterOptionsOpen = false;
currentSorterOption = this.$t("trains.option-distance");
searchedTrain = "";
searchedDriver = "";
// Passed as component parameters
@Prop() readonly queryTrain!: string;
@Prop() readonly focusedTrain!: string;
mounted() {
if (this.queryTrain) {
this.searchedTrain = this.queryTrain;
this.searchedDriver = "";
}
}
sorterOptions: { id: string; content: string }[] = [
{
id: "mass",
content: "masa",
},
{
id: "speed",
content: "prędkość",
},
{
id: "length",
content: "długość",
},
{
id: "distance",
content: "kilometraż",
},
{
id: "timetable",
content: "numer pociągu",
},
];
toggleSorterOptions() {
this.sorterOptionsOpen = !this.sorterOptionsOpen;
}
closeSorterOptions() {
this.sorterOptionsOpen = false;
}
chooseOption(option: { id: string; content: string }) {
this.$emit("changeSorter", { id: option.id, dir: -1 });
this.currentSorterOption = this.$t(`trains.option-${option.id}`);
this.closeSorterOptions();
}
@Watch("searchedTrain")
onSearchedTrainChanged(train: string) {
this.$emit("changeSearchedTrain", train);
}
@Watch("searchedDriver")
onSearchedDriverChanged(driver: string) {
this.$emit("changeSearchedDriver", driver);
}
@Watch("queryTrain")
onQueryTrainChanged(train: string) {
if (train && train != "") {
this.searchedTrain = train;
this.searchedDriver = "";
}
}
@Watch("focusedTrain")
onFocusedTrainChanged(train: string) {
this.searchedTrain = train;
this.searchedDriver = "";
}
}
</script>
<style lang="scss" scoped>
@import "../../styles/responsive";
.train-options {
@include smallScreen() {
width: 100%;
}
}
.options_wrapper {
display: flex;
flex-wrap: wrap;
margin-bottom: 0.5em;
}
.option {
background: #333;
border-radius: 0.5em 0.5em 0 0;
margin-right: 0.35em;
@include smallScreen() {
width: 100%;
margin: 0.35em 0;
}
}
.train-sorter {
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
&_options {
position: relative;
ul {
position: absolute;
top: 0;
left: 0;
width: 100%;
transition: all 150ms ease-in;
z-index: 9;
overflow: hidden;
max-height: 0;
&.open {
max-height: 250px;
opacity: 1;
}
li {
display: flex;
transition: background 150ms ease-in;
background-color: rgba(#222, 0.95);
&:last-child {
border-radius: 0 0 0.5em 0.5em;
}
&:hover {
background-color: rgba(#868686, 0.85);
}
input {
display: none;
}
label {
padding: 0.5em 1em;
width: 100%;
cursor: pointer;
}
}
}
}
&_selected {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5em 0.5em;
min-width: 200px;
cursor: pointer;
span {
margin-right: 1em;
}
img {
max-width: 2em;
}
}
}
.search {
&-box {
position: relative;
background: #333;
border-radius: 0.5em;
min-width: 200px;
}
&-input {
border: none;
padding: 0.5em 0.5em;
margin: 0;
min-width: 85%;
}
&-exit {
position: absolute;
cursor: pointer;
top: 50%;
right: 10px;
transform: translateY(-50%);
width: 1em;
}
}
<template>
<div class="train-options">
<div class="options_wrapper">
<div class="train-sorter option">
<div class="train-sorter_wrapper">
<div class="train-sorter_selected" @click="toggleSorterOptions">
<span>{{ currentSorterOption }}</span>
<img :src="descIcon" alt="icon-select" />
</div>
<div class="train-sorter_options">
<ul :class="{ open: sorterOptionsOpen }">
<li
v-for="(option, i) in sorterOptions"
:key="i"
@click="() => chooseOption(option)"
>
<input type="radio" name="sort" :id="option.id" />
<label :for="option.id">
{{ $t(`trains.option-${option.id}`) }}
</label>
</li>
</ul>
</div>
</div>
</div>
<div class="train-search_train option">
<div class="search-box">
<input
class="search-input"
:placeholder="$t('trains.search-no')"
v-model="searchedTrain"
/>
<img
class="search-exit"
:src="exitIcon"
alt="exit-icon"
@click="() => (searchedTrain = '')"
/>
</div>
</div>
<div class="train-search_driver option">
<div class="search-box">
<input
class="search-input"
:placeholder="$t('trains.search-driver')"
v-model="searchedDriver"
/>
<img
class="search-exit"
:src="exitIcon"
alt="exit-icon"
@click="() => (searchedDriver = '')"
/>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue, Watch, Prop } from "vue-property-decorator";
@Component
export default class TrainOptions extends Vue {
ascIcon = require("@/assets/icon-arrow-asc.svg");
descIcon = require("@/assets/icon-arrow-desc.svg");
exitIcon = require("@/assets/icon-exit.svg");
clickEventListener!: EventListener;
sorterOptionsOpen = false;
currentSorterOption = this.$t("trains.option-distance");
searchedTrain = "";
searchedDriver = "";
// Passed as component parameters
@Prop() readonly queryTrain!: string;
@Prop() readonly focusedTrain!: string;
mounted() {
if (this.queryTrain) {
this.searchedTrain = this.queryTrain;
this.searchedDriver = "";
}
}
sorterOptions: { id: string; content: string }[] = [
{
id: "mass",
content: "masa",
},
{
id: "speed",
content: "prędkość",
},
{
id: "length",
content: "długość",
},
{
id: "distance",
content: "kilometraż",
},
{
id: "timetable",
content: "numer pociągu",
},
];
toggleSorterOptions() {
this.sorterOptionsOpen = !this.sorterOptionsOpen;
}
closeSorterOptions() {
this.sorterOptionsOpen = false;
}
chooseOption(option: { id: string; content: string }) {
this.$emit("changeSorter", { id: option.id, dir: -1 });
this.currentSorterOption = this.$t(`trains.option-${option.id}`);
this.closeSorterOptions();
}
@Watch("searchedTrain")
onSearchedTrainChanged(train: string) {
this.$emit("changeSearchedTrain", train);
}
@Watch("searchedDriver")
onSearchedDriverChanged(driver: string) {
this.$emit("changeSearchedDriver", driver);
}
@Watch("queryTrain")
onQueryTrainChanged(train: string) {
if (train && train != "") {
this.searchedTrain = train;
this.searchedDriver = "";
}
}
@Watch("focusedTrain")
onFocusedTrainChanged(train: string) {
this.searchedTrain = train;
this.searchedDriver = "";
}
}
</script>
<style lang="scss" scoped>
@import "../../styles/responsive";
.train-options {
@include smallScreen() {
width: 100%;
}
}
.options_wrapper {
display: flex;
flex-wrap: wrap;
margin-bottom: 0.5em;
}
.option {
background: #333;
border-radius: 0.5em 0.5em 0 0;
margin-right: 0.35em;
@include smallScreen() {
width: 100%;
margin: 0.35em 0;
}
}
.train-sorter {
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
&_options {
position: relative;
ul {
position: absolute;
top: 0;
left: 0;
width: 100%;
transition: all 150ms ease-in;
z-index: 9;
overflow: hidden;
max-height: 0;
&.open {
max-height: 250px;
opacity: 1;
}
li {
display: flex;
transition: background 150ms ease-in;
background-color: rgba(#222, 0.95);
&:last-child {
border-radius: 0 0 0.5em 0.5em;
}
&:hover {
background-color: rgba(#868686, 0.85);
}
input {
display: none;
}
label {
padding: 0.5em 1em;
width: 100%;
cursor: pointer;
}
}
}
}
&_selected {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5em 0.5em;
min-width: 200px;
cursor: pointer;
span {
margin-right: 1em;
}
img {
max-width: 2em;
}
}
}
.search {
&-box {
position: relative;
background: #333;
border-radius: 0.5em;
min-width: 200px;
}
&-input {
border: none;
padding: 0.5em 0.5em;
margin: 0;
min-width: 85%;
}
&-exit {
position: absolute;
cursor: pointer;
top: 50%;
right: 10px;
transform: translateY(-50%);
width: 1em;
}
}
</style>
+2147 -2147
View File
File diff suppressed because it is too large Load Diff
+134 -134
View File
@@ -1,134 +1,134 @@
{
"app": {
"sceneries": "SCENERIES",
"trains": "TRAINS",
"journal": "JOURNAL",
"loading": "Loading data...",
"support": "Support the project"
},
"desc": {
"control-type": "Control type: ",
"signals-type": "Signals type: ",
"SBL": "This scenery has automatic line blockade system on following routes: ",
"default": "This scenery is available by default",
"non-public": "This scenery is not public",
"unavailable": "This scenery is unavailable",
"real": "This scenery is real"
},
"signals": {
"współczesna": "modern",
"mieszana": "mixed",
"kształtowa": "mechanical",
"historyczna": "historyczna"
},
"controls": {
"SPK": "SPK",
"SCS": "SCS",
"SCS-SPK": "SCS/SPK",
"ręczne": "manual",
"ręczne+SPK": "manual + SPK",
"ręczne+SCS": "manual + SCS",
"mechaniczne": "levers (mechanical)",
"mechaniczne+SPK": "levers + SPK",
"mechaniczne+SCS": "levers + SCS"
},
"status": {
"online": "UNTIL ",
"free": "FREE",
"ending": "ENDS SOON",
"not-signed": "NOT SIGNED IN",
"no-limit": "NO LIMIT",
"unavailable": "UNAVAILABLE",
"brb": "AFK",
"no-space": "NO SPACE"
},
"options": {
"filters": "FILTERS",
"donate": "DONATE"
},
"filters": {
"title": "STATION FILTER",
"default": "DEFAULT",
"not-default": "OTHER",
"real": "REAL",
"fictional": "FICTIONAL",
"SPK": "SPK",
"SCS": "SCS",
"manual": "MANUAL",
"mechanical": "MECHANICAL",
"modern": "MODERN",
"semaphores": "SEMAPHORES",
"mixed": "MIXED",
"historical": "HISTORICAL",
"free": "FREE",
"occupied": "OCCUPIED",
"sliders": {
"min-lvl": "MINIMUM REQUIRED DISPATCHER LEVEL",
"routes-1t-cat": "MINIMUM CATENARY SINGLE TRACK ROUTES",
"routes-1t-other": "MINIMUM OTHER SINGLE TRACK ROUTES",
"routes-2t-cat": "MINIMUM CATENARY DOUBLE TRACK ROUTES",
"routes-2t-other": "MINIMUM OTHER DOUBLE TRACK ROUTES"
},
"save": "SAVE FILTERS",
"reset": "RESET FILTERS",
"close": "CLOSE FILTERS"
},
"sceneries": {
"station": "Station",
"min-lvl": "Min. dispatcher <br> level",
"status": "Status",
"dispatcher": "Dispatcher",
"dispatcher-lvl": "Dispatcher <br> level",
"routes": "Routes <br> double | single",
"general": "General info",
"users": "Drivers online",
"spawns": "Spawns online",
"timetables": "Active timetables",
"no-stations": "No stations to show here!"
},
"trains": {
"no-trains": "Oops! No trains online!",
"stats": "TRAFFIC STATISTICS",
"stats-speed": "TRAINS SPEED (MIN | AVG | MAX) [km/h]",
"stats-length": "TIMETABLES LENGTH (MIN | AVG | MAX) [km]",
"stats-categories": "TIMETABLE CATEGORIES",
"stats-special-twr": "HIGH RISK",
"stats-special-skr": "EXCEEDED STRUCT. GAUGE",
"stats-locos": "MOST COMMON UNITS",
"option-mass": "mass",
"option-speed": "speed",
"option-length": "length",
"option-distance": "distance",
"option-timetable": "train no.",
"search-no": "Search for train no...",
"search-driver": "Search for driver...",
"detailed-timetable": "Detailed timetable for train no. ",
"via-title": "Via: "
},
"journal": {
"title": "SCENERY ACTIVITY JOURNAL",
"subtitle": "Shows all recent dispatchers on a selected scenery",
"disclaimer": "<b>This functionality is unfinished!</b> <br> Information shown here could be false or incorrect!",
"select": "Select a scenery"
},
"scenery": {
"users": "PLAYERS ONLINE",
"spawns": "OPEN SPAWNS",
"timetables": "ACTIVE TIMETABLES",
"no-timetables": "No active timetables!",
"no-users": "NO ACTIVE PLAYERS",
"no-spawns": "NO OPEN SPAWNS",
"no-scenery": "Oops! This scenery doesn't exist or is offline!",
"return-btn": "Return to main site"
},
"timetables": {
"online": "At station",
"departed": "Dispatched",
"departed-away": "Departed",
"arriving": "En route",
"stopped": "Stopped",
"terminated": "Terminated",
"begins": "BEGINS HERE",
"terminates": "TERMINATES <br /> HERE"
}
}
{
"app": {
"sceneries": "SCENERIES",
"trains": "TRAINS",
"journal": "JOURNAL",
"loading": "Loading data...",
"support": "Support the project"
},
"desc": {
"control-type": "Control type: ",
"signals-type": "Signals type: ",
"SBL": "This scenery has automatic line blockade system on following routes: ",
"default": "This scenery is available by default",
"non-public": "This scenery is not public",
"unavailable": "This scenery is unavailable",
"real": "This scenery is real"
},
"signals": {
"współczesna": "modern",
"mieszana": "mixed",
"kształtowa": "mechanical",
"historyczna": "historyczna"
},
"controls": {
"SPK": "SPK",
"SCS": "SCS",
"SCS-SPK": "SCS/SPK",
"ręczne": "manual",
"ręczne+SPK": "manual + SPK",
"ręczne+SCS": "manual + SCS",
"mechaniczne": "levers (mechanical)",
"mechaniczne+SPK": "levers + SPK",
"mechaniczne+SCS": "levers + SCS"
},
"status": {
"online": "UNTIL ",
"free": "FREE",
"ending": "ENDS SOON",
"not-signed": "NOT SIGNED IN",
"no-limit": "NO LIMIT",
"unavailable": "UNAVAILABLE",
"brb": "AFK",
"no-space": "NO SPACE"
},
"options": {
"filters": "FILTERS",
"donate": "DONATE"
},
"filters": {
"title": "STATION FILTER",
"default": "DEFAULT",
"not-default": "OTHER",
"real": "REAL",
"fictional": "FICTIONAL",
"SPK": "SPK",
"SCS": "SCS",
"manual": "MANUAL",
"mechanical": "MECHANICAL",
"modern": "MODERN",
"semaphores": "SEMAPHORES",
"mixed": "MIXED",
"historical": "HISTORICAL",
"free": "FREE",
"occupied": "OCCUPIED",
"sliders": {
"min-lvl": "MINIMUM REQUIRED DISPATCHER LEVEL",
"routes-1t-cat": "MINIMUM CATENARY SINGLE TRACK ROUTES",
"routes-1t-other": "MINIMUM OTHER SINGLE TRACK ROUTES",
"routes-2t-cat": "MINIMUM CATENARY DOUBLE TRACK ROUTES",
"routes-2t-other": "MINIMUM OTHER DOUBLE TRACK ROUTES"
},
"save": "SAVE FILTERS",
"reset": "RESET FILTERS",
"close": "CLOSE FILTERS"
},
"sceneries": {
"station": "Station",
"min-lvl": "Min. dispatcher <br> level",
"status": "Status",
"dispatcher": "Dispatcher",
"dispatcher-lvl": "Dispatcher <br> level",
"routes": "Routes <br> double | single",
"general": "General info",
"users": "Drivers online",
"spawns": "Spawns online",
"timetables": "Active timetables",
"no-stations": "No stations to show here!"
},
"trains": {
"no-trains": "Oops! No trains online!",
"stats": "TRAFFIC STATISTICS",
"stats-speed": "TRAINS SPEED (MIN | AVG | MAX) [km/h]",
"stats-length": "TIMETABLES LENGTH (MIN | AVG | MAX) [km]",
"stats-categories": "TIMETABLE CATEGORIES",
"stats-special-twr": "HIGH RISK",
"stats-special-skr": "EXCEEDED STRUCT. GAUGE",
"stats-locos": "MOST COMMON UNITS",
"option-mass": "mass",
"option-speed": "speed",
"option-length": "length",
"option-distance": "distance",
"option-timetable": "train no.",
"search-no": "Search for train no...",
"search-driver": "Search for driver...",
"detailed-timetable": "Detailed timetable for train no. ",
"via-title": "Via: "
},
"journal": {
"title": "SCENERY ACTIVITY JOURNAL",
"subtitle": "Shows all recent dispatchers on a selected scenery",
"disclaimer": "<b>This functionality is unfinished!</b> <br> Information shown here could be false or incorrect!",
"select": "Select a scenery"
},
"scenery": {
"users": "PLAYERS ONLINE",
"spawns": "OPEN SPAWNS",
"timetables": "ACTIVE TIMETABLES",
"no-timetables": "No active timetables!",
"no-users": "NO ACTIVE PLAYERS",
"no-spawns": "NO OPEN SPAWNS",
"no-scenery": "Oops! This scenery doesn't exist or is offline!",
"return-btn": "Return to main site"
},
"timetables": {
"online": "At station",
"departed": "Dispatched",
"departed-away": "Departed",
"arriving": "En route",
"stopped": "Stopped",
"terminated": "Terminated",
"begins": "BEGINS HERE",
"terminates": "TERMINATES <br /> HERE"
}
}
+134 -134
View File
@@ -1,134 +1,134 @@
{
"app": {
"sceneries": "SCENERIE",
"trains": "POCIĄGI",
"journal": "DZIENNIK",
"loading": "Pobieranie danych...",
"support": "Wspomóż projekt"
},
"desc": {
"control-type": "Sterowanie: ",
"signals-type": "Sygnalizacja: ",
"SBL": "Sceneria posiada SBL na szlakach: ",
"default": "Sceneria dostępna domyślnie w paczce z grą",
"non-public": "Sceneria niepubliczna",
"unavailable": "Sceneria niedostępna",
"real": "Sceneria realna"
},
"signals": {
"współczesna": "współczesna",
"mieszana": "mieszana",
"kształtowa": "kształtowa",
"historyczna": "historyczna"
},
"controls": {
"SPK": "SPK",
"SCS": "SCS",
"SCS-SPK": "SCS/SPK",
"ręczne": "ręczne",
"ręczne+SPK": "ręczne + SPK",
"ręczne+SCS": "ręczne + SCS",
"mechaniczne": "mechaniczne",
"mechaniczne+SPK": "mechaniczne + SPK",
"mechaniczne+SCS": "mechaniczne + 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"
},
"options": {
"filters": "FILTRY",
"donate": "WESPRZYJ"
},
"filters": {
"title": "FILTRUJ STACJE",
"default": "DOMYŚLNA",
"not-default": "POZA PACZKĄ",
"real": "REALNA",
"fictional": "FIKCYJNA",
"SPK": "SPK",
"SCS": "SCS",
"manual": "RĘCZNE",
"mechanical": "MECHANICZNE",
"modern": "WSPÓŁCZESNA",
"semaphores": "KSZTAŁTOWA",
"mixed": "MIESZANA",
"historical": "HISTORYCZNA",
"free": "WOLNA",
"occupied": "ZAJĘTA",
"sliders": {
"min-lvl": "MINIMALNY 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)"
},
"save": "ZAPISZ FILTRY",
"reset": "RESETUJ FILTRY",
"close": "ZAMKNIJ FILTRY"
},
"sceneries": {
"station": "Stacja",
"min-lvl": "Min. poziom <br/> dyżurnego",
"status": "Status",
"dispatcher": "Dyżurny",
"dispatcher-lvl": "Poziom <br> dyżurnego",
"routes": "Szlaki <br> 2tor | 1tor",
"general": "Informacje <br> ogólne",
"users": "Maszyniści online",
"spawns": "Otwarte spawny",
"timetables": "Aktywne rozkłady jazdy",
"no-stations": "Brak stacji do wyświetlenia!"
},
"trains": {
"no-trains": "Brak pociągów online!",
"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",
"option-mass": "masa",
"option-speed": "prędkość",
"option-length": "długość",
"option-distance": "kilometraż",
"option-timetable": "numer pociagu",
"search-no": "Szukaj nr pociągu...",
"search-driver": "Szukaj maszynisty...",
"detailed-timetable": "Szczegółowy rozkład jazdy pociągu ",
"via-title": "Przez: "
},
"journal": {
"title": "DZIENNIK AKTYWNOŚCI SCENERII",
"subtitle": "Pokazuje dyżurnych, którzy ostatnio byli aktywni na wybranej scenerii",
"disclaimer": "<b>Ta funkcjonalność jest w testach beta!</b> <br> Informacje pokazywane na ekranie mogą znikać, a ich zawartość może być fałszywa!",
"select": "Wybierz scenerię"
},
"scenery": {
"users": "GRACZE ONLINE",
"spawns": "OTWARTE SPAWNY",
"timetables": "AKTYWNE ROZKŁADY JAZDY",
"no-timetables": "Brak aktywnych rozkładów!",
"no-users": "BRAK AKTYWNYCH GRACZY",
"no-spawns": "BRAK OTWARTYCH SPAWNÓW",
"no-scenery": "Ups! Nie znaleziono danej stacji bądź jest ona offline!",
"return-btn": "Wróć na stronę główną"
},
"timetables": {
"online": "Na stacji",
"departed": "Odprawiony",
"departed-away": "Odjechał",
"arriving": "W drodze",
"stopped": "Postój",
"terminated": "Skończył bieg",
"begins": "ROZPOCZYNA <br /> BIEG",
"terminates": "KOŃCZY BIEG"
}
}
{
"app": {
"sceneries": "SCENERIE",
"trains": "POCIĄGI",
"journal": "DZIENNIK",
"loading": "Pobieranie danych...",
"support": "Wspomóż projekt"
},
"desc": {
"control-type": "Sterowanie: ",
"signals-type": "Sygnalizacja: ",
"SBL": "Sceneria posiada SBL na szlakach: ",
"default": "Sceneria dostępna domyślnie w paczce z grą",
"non-public": "Sceneria niepubliczna",
"unavailable": "Sceneria niedostępna",
"real": "Sceneria realna"
},
"signals": {
"współczesna": "współczesna",
"mieszana": "mieszana",
"kształtowa": "kształtowa",
"historyczna": "historyczna"
},
"controls": {
"SPK": "SPK",
"SCS": "SCS",
"SCS-SPK": "SCS/SPK",
"ręczne": "ręczne",
"ręczne+SPK": "ręczne + SPK",
"ręczne+SCS": "ręczne + SCS",
"mechaniczne": "mechaniczne",
"mechaniczne+SPK": "mechaniczne + SPK",
"mechaniczne+SCS": "mechaniczne + 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"
},
"options": {
"filters": "FILTRY",
"donate": "WESPRZYJ"
},
"filters": {
"title": "FILTRUJ STACJE",
"default": "DOMYŚLNA",
"not-default": "POZA PACZKĄ",
"real": "REALNA",
"fictional": "FIKCYJNA",
"SPK": "SPK",
"SCS": "SCS",
"manual": "RĘCZNE",
"mechanical": "MECHANICZNE",
"modern": "WSPÓŁCZESNA",
"semaphores": "KSZTAŁTOWA",
"mixed": "MIESZANA",
"historical": "HISTORYCZNA",
"free": "WOLNA",
"occupied": "ZAJĘTA",
"sliders": {
"min-lvl": "MINIMALNY 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)"
},
"save": "ZAPISZ FILTRY",
"reset": "RESETUJ FILTRY",
"close": "ZAMKNIJ FILTRY"
},
"sceneries": {
"station": "Stacja",
"min-lvl": "Min. poziom <br/> dyżurnego",
"status": "Status",
"dispatcher": "Dyżurny",
"dispatcher-lvl": "Poziom <br> dyżurnego",
"routes": "Szlaki <br> 2tor | 1tor",
"general": "Informacje <br> ogólne",
"users": "Maszyniści online",
"spawns": "Otwarte spawny",
"timetables": "Aktywne rozkłady jazdy",
"no-stations": "Brak stacji do wyświetlenia!"
},
"trains": {
"no-trains": "Brak pociągów online!",
"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",
"option-mass": "masa",
"option-speed": "prędkość",
"option-length": "długość",
"option-distance": "kilometraż",
"option-timetable": "numer pociagu",
"search-no": "Szukaj nr pociągu...",
"search-driver": "Szukaj maszynisty...",
"detailed-timetable": "Szczegółowy rozkład jazdy pociągu ",
"via-title": "Przez: "
},
"journal": {
"title": "DZIENNIK AKTYWNOŚCI SCENERII",
"subtitle": "Pokazuje dyżurnych, którzy ostatnio byli aktywni na wybranej scenerii",
"disclaimer": "<b>Ta funkcjonalność jest w testach beta!</b> <br> Informacje pokazywane na ekranie mogą znikać, a ich zawartość może być fałszywa!",
"select": "Wybierz scenerię"
},
"scenery": {
"users": "GRACZE ONLINE",
"spawns": "OTWARTE SPAWNY",
"timetables": "AKTYWNE ROZKŁADY JAZDY",
"no-timetables": "Brak aktywnych rozkładów!",
"no-users": "BRAK AKTYWNYCH GRACZY",
"no-spawns": "BRAK OTWARTYCH SPAWNÓW",
"no-scenery": "Ups! Nie znaleziono danej stacji bądź jest ona offline!",
"return-btn": "Wróć na stronę główną"
},
"timetables": {
"online": "Na stacji",
"departed": "Odprawiony",
"departed-away": "Odjechał",
"arriving": "W drodze",
"stopped": "Postój",
"terminated": "Skończył bieg",
"begins": "ROZPOCZYNA <br /> BIEG",
"terminates": "KOŃCZY BIEG"
}
}
+33 -33
View File
@@ -1,33 +1,33 @@
interface ISceneryInfoData {
stationName: string;
stationURL: string;
stationLines: string;
stationProject: string;
reqLevel: string;
supportersOnly: string;
signalType: string;
controlType: string;
SBL: string;
twoWayBlock: string;
routesOneWayCatenary: number;
routesOneWayOther: number;
routesTwoWayCatenary: number;
routesToWayOther: number;
default: boolean;
nonPublic: boolean;
unavailable: boolean;
hasData: boolean;
stops: string[];
checkpoints: string[];
currentDispatcher: string;
currentDispatcherId: number;
currentDispatcherFrom: number;
dispatcherHistory: { dispatcherName: string; dispatcherId: number; dispatcherFrom: number; dispatcherTo: number }[];
}
export default ISceneryInfoData;
interface ISceneryInfoData {
stationName: string;
stationURL: string;
stationLines: string;
stationProject: string;
reqLevel: string;
supportersOnly: string;
signalType: string;
controlType: string;
SBL: string;
twoWayBlock: string;
routesOneWayCatenary: number;
routesOneWayOther: number;
routesTwoWayCatenary: number;
routesToWayOther: number;
default: boolean;
nonPublic: boolean;
unavailable: boolean;
hasData: boolean;
stops: string[];
checkpoints: string[];
currentDispatcher: string;
currentDispatcherId: number;
currentDispatcherFrom: number;
dispatcherHistory: { dispatcherName: string; dispatcherId: number; dispatcherFrom: number; dispatcherTo: number }[];
}
export default ISceneryInfoData;
-18
View File
@@ -1,18 +0,0 @@
import store from "@/store";
import { Module, VuexModule, Mutation, Action, MutationAction } from "vuex-module-decorators";
@Module({ dynamic: true, store, name: "dataModule" })
export default class MyModule extends VuexModule {
test: string = "xd";
get getTest() {
return this.test;
}
@MutationAction
async fetchTest() {
const fetched = "aaa";
return { test: fetched };
}
}
+116 -266
View File
@@ -1,20 +1,22 @@
import { Module, VuexModule, Mutation, Action } from 'vuex-module-decorators';
import axios from 'axios';
import { Module, VuexModule, Mutation, Action } from "vuex-module-decorators";
import axios from "axios";
import JSONStationData from '@/data/stationData.json';
import JSONStationData from "@/data/stationData.json";
import Station from '@/scripts/interfaces/Station';
import Train from '@/scripts/interfaces/Train';
import TrainStop from '@/scripts/interfaces/TrainStop';
import Station from "@/scripts/interfaces/Station";
import Train from "@/scripts/interfaces/Train";
import TrainStop from "@/scripts/interfaces/TrainStop";
import utils from "@/scripts/utils/storeUtils";
enum Status {
Initialized = -1,
Loading = 0,
Error = 1,
Loaded = 2,
Loaded = 2
}
interface ITimetableData {
interface TimetableData {
trainNo: number;
driverName: string;
driverId: number;
@@ -50,87 +52,11 @@ interface IOnlineStationData {
}
const URLs = {
stations: 'https://api.td2.info.pl:9640/?method=getStationsOnline',
trains: 'https://api.td2.info.pl:9640/?method=getTrainsOnline',
dispatchers: 'https://api.td2.info.pl:9640/?method=readFromSWDR&value=getDispatcherStatusList%3B1',
stations: "https://api.td2.info.pl:9640/?method=getStationsOnline",
trains: "https://api.td2.info.pl:9640/?method=getTrainsOnline",
dispatchers: "https://api.td2.info.pl:9640/?method=readFromSWDR&value=getDispatcherStatusList%3B1"
};
const timetableURL = (trainNo: number) => `https://api.td2.info.pl:9640/?method=readFromSWDR&value=getTimetable%3B${trainNo}%3Beu`;
const getLocoURL = (locoType: string) => `https://rj.td2.info.pl/dist/img/thumbnails/${locoType.includes('EN') ? locoType + 'rb' : locoType}.png`;
const getStatusID = (stationStatus: any) => {
if (!stationStatus) return 'not-signed';
const statusCode = stationStatus[2];
const statusTimestamp = stationStatus[3];
switch (statusCode) {
case 0:
if (statusTimestamp - Date.now() > 21000000) return 'no-limit';
return 'online';
case 1:
return 'brb';
case 2:
if (statusTimestamp == 0) return 'ending';
break;
case 3:
return 'no-space';
default:
break;
}
return 'unavailable';
};
const getStatusTimestamp = (stationStatus: any) => {
if (!stationStatus) return -2;
const statusCode = stationStatus[2];
const statusTimestamp = stationStatus[3];
switch (statusCode) {
case 0:
case 1:
case 3:
return statusTimestamp;
case 2:
if (statusTimestamp == 0) return 0;
break;
default:
break;
}
return -1;
};
const parseSpawns = (spawnString: string) => {
if (!spawnString) return [];
if (spawnString === 'NO_SPAWN') return [];
return spawnString.split(';').map(spawn => {
const spawnArray = spawn.split(',');
const spawnName = spawnArray[6] ? spawnArray[6] : spawnArray[0];
const spawnLength = parseInt(spawnArray[2]);
return { spawnName, spawnLength };
});
};
const getTimestamp = (date: string) => (date ? new Date(date).getTime() : 0);
const timestampToString = (timestamp: number) =>
new Date(timestamp).toLocaleTimeString('pl-PL', {
hour: '2-digit',
minute: '2-digit',
});
@Module
export default class Store extends VuexModule {
private trainCount: number = 0;
@@ -151,7 +77,7 @@ export default class Store extends VuexModule {
trainCount: this.trainCount,
stationCount: this.stationCount,
dataConnectionStatus: this.dataConnectionStatus,
timetableDataStatus: this.timetableLoaded,
timetableDataStatus: this.timetableLoaded
};
}
@@ -177,27 +103,27 @@ export default class Store extends VuexModule {
//ACTIONS
@Action
async synchronizeData() {
this.context.commit('setSceneryData');
this.context.commit('setSceneryDataStatus', Status.Loaded);
this.context.commit("setSceneryData");
this.context.commit("setSceneryDataStatus", Status.Loaded);
this.context.dispatch('fetchOnlineData');
setInterval(() => this.context.dispatch('fetchOnlineData'), 20000);
this.context.dispatch("fetchOnlineData");
setInterval(() => this.context.dispatch("fetchOnlineData"), 20000);
}
@Action({ commit: 'updateTimetableData' })
@Action({ commit: "updateTimetableData" })
async fetchTimetableData() {
return this.trainList.reduce(async (acc: Promise<ITimetableData[]>, train) => {
const timetable = await (await axios.get(timetableURL(train.trainNo))).data.message;
return this.trainList.reduce(async (acc: Promise<TimetableData[]>, train) => {
const timetable = await (await axios.get(utils.timetableURL(train.trainNo))).data.message;
const trainInfo = timetable.trainInfo;
if (!timetable || !trainInfo) return acc;
const followingStops: TrainStop[] = timetable.stopPoints.reduce((stopsAcc: TrainStop[], point) => {
const arrivalTimestamp = getTimestamp(point.arrivalTime);
const arrivalRealTimestamp = getTimestamp(point.arrivalRealTime);
const arrivalTimestamp = utils.getTimestamp(point.arrivalTime);
const arrivalRealTimestamp = utils.getTimestamp(point.arrivalRealTime);
const departureTimestamp = getTimestamp(point.departureTime);
const departureRealTimestamp = getTimestamp(point.departureRealTime);
const departureTimestamp = utils.getTimestamp(point.departureTime);
const departureRealTimestamp = utils.getTimestamp(point.departureRealTime);
stopsAcc.push({
stopName: point.pointName,
@@ -205,19 +131,19 @@ export default class Store extends VuexModule {
stopType: point.pointStopType,
stopDistance: point.pointDistance,
mainStop: point.pointName.includes('strong'),
mainStop: point.pointName.includes("strong"),
arrivalLine: point.arrivalLine,
arrivalTimeString: timestampToString(point.arrivalTime),
arrivalTimeString: utils.timestampToString(point.arrivalTime),
arrivalTimestamp: arrivalTimestamp,
arrivalRealTimeString: timestampToString(point.arrivalRealTime),
arrivalRealTimeString: utils.timestampToString(point.arrivalRealTime),
arrivalRealTimestamp: arrivalRealTimestamp,
arrivalDelay: point.arrivalDelay,
departureLine: point.departureLine,
departureTimeString: timestampToString(point.departureTime),
departureTimeString: utils.timestampToString(point.departureTime),
departureTimestamp: departureTimestamp,
departureRealTimeString: timestampToString(point.departureRealTime),
departureRealTimeString: utils.timestampToString(point.departureRealTime),
departureRealTimestamp: departureRealTimestamp,
departureDelay: point.departureDelay,
@@ -226,7 +152,7 @@ export default class Store extends VuexModule {
confirmed: point.confirmed,
stopped: point.isStopped,
stopTime: point.pointStopTime,
stopTime: point.pointStopTime
});
return stopsAcc;
@@ -245,7 +171,7 @@ export default class Store extends VuexModule {
SKR: trainInfo.skr,
routeDistance: timetable.stopPoints[timetable.stopPoints.length - 1].pointDistance,
followingStops,
followingSceneries: trainInfo.sceneries,
followingSceneries: trainInfo.sceneries
});
return acc;
@@ -261,15 +187,15 @@ export default class Store extends VuexModule {
const onlineDispatchersData = await response[2].data.message;
let updatedStationList = onlineStationsData.reduce((acc, station) => {
if (station.region !== 'eu' || !station.isOnline) return acc;
if (station.region !== "eu" || !station.isOnline) return acc;
const stationStatus = onlineDispatchersData.find(status => status[0] == station.stationHash && status[1] == 'eu');
const stationStatus = onlineDispatchersData.find(status => status[0] == station.stationHash && status[1] == "eu");
const statusTimestamp = getStatusTimestamp(stationStatus);
const statusID = getStatusID(stationStatus);
const statusTimestamp = utils.getStatusTimestamp(stationStatus);
const statusID = utils.getStatusID(stationStatus);
const stationTrains = onlineTrainsData.filter(
train => train.region === 'eu' && train.isOnline && train.station.stationName === station.stationName
train => train.region === "eu" && train.isOnline && train.station.stationName === station.stationName
);
acc.push({
@@ -277,7 +203,7 @@ export default class Store extends VuexModule {
stationHash: station.stationHash,
maxUsers: station.maxUsers,
currentUsers: station.currentUsers,
spawns: parseSpawns(station.spawnString),
spawns: utils.parseSpawns(station.spawnString),
dispatcherName: station.dispatcherName,
dispatcherRate: station.dispatcherRate,
dispatcherId: station.dispatcherId,
@@ -286,7 +212,7 @@ export default class Store extends VuexModule {
stationTrains,
statusTimestamp,
statusID,
statusTimeString: timestampToString(statusTimestamp),
statusTimeString: utils.timestampToString(statusTimestamp)
});
return acc;
@@ -294,9 +220,9 @@ export default class Store extends VuexModule {
let updatedTrainList = await Promise.all(
onlineTrainsData
.filter(train => train.region === 'eu')
.filter(train => train.region === "eu")
.map(async train => {
const locoType = train.dataCon.split(';') ? train.dataCon.split(';')[0] : train.dataCon;
const locoType = train.dataCon.split(";") ? train.dataCon.split(";")[0] : train.dataCon;
return {
trainNo: train.trainNo,
@@ -312,18 +238,18 @@ export default class Store extends VuexModule {
currentStationHash: train.station.stationHash,
connectedTrack: train.dataSceneryConnection,
locoType,
locoURL: getLocoURL(locoType),
locoURL: utils.getLocoURL(locoType)
};
})
);
this.context.commit('updateOnlineStations', updatedStationList);
this.context.commit('updateOnlineTrains', updatedTrainList);
this.context.commit("updateOnlineStations", updatedStationList);
this.context.commit("updateOnlineTrains", updatedTrainList);
this.context.dispatch('fetchTimetableData');
this.context.dispatch("fetchTimetableData");
})
.catch(err => {
this.context.commit('setDataConnectionStatus', Status.Error);
this.context.commit("setDataConnectionStatus", Status.Error);
});
}
@@ -368,7 +294,7 @@ export default class Store extends VuexModule {
stationLines: station[2] as string,
stationProject: station[3] as string,
reqLevel: station[4] as string,
supportersOnly: station[5] == 'TAK',
supportersOnly: station[5] == "TAK",
signalType: station[6] as string,
controlType: station[7] as string,
SBL: station[8] as string,
@@ -376,12 +302,12 @@ export default class Store extends VuexModule {
routes: {
oneWay: {
catenary: station[10] as number,
noCatenary: station[11] as number,
noCatenary: station[11] as number
},
twoWay: {
catenary: station[12] as number,
noCatenary: station[13] as number,
},
noCatenary: station[13] as number
}
},
checkpoints: station[14] ? (station[14] as string[]).map(sub => ({ checkpointName: sub, scheduledTrains: [] })) : null,
stops: station[15] as string[],
@@ -390,21 +316,21 @@ export default class Store extends VuexModule {
nonPublic: station[17] as boolean,
unavailable: station[18] as boolean,
stationHash: '',
stationHash: "",
maxUsers: 0,
currentUsers: 0,
dispatcherName: '',
dispatcherName: "",
dispatcherRate: 0,
dispatcherExp: -1,
dispatcherId: 0,
dispatcherIsSupporter: false,
online: false,
statusTimestamp: -3,
statusID: 'free',
statusTimeString: '',
statusID: "free",
statusTimeString: "",
stationTrains: [],
scheduledTrains: [],
spawns: [],
spawns: []
}));
}
@@ -418,27 +344,27 @@ export default class Store extends VuexModule {
acc.push({
...station,
...onlineStationData,
online: true,
online: true
});
else if (registeredStation)
acc.push({
...station,
stationProject: '',
stationHash: '',
stationProject: "",
stationHash: "",
maxUsers: 0,
currentUsers: 0,
dispatcherName: '',
dispatcherName: "",
dispatcherRate: 0,
dispatcherExp: -1,
dispatcherId: 0,
dispatcherIsSupporter: false,
online: false,
statusID: 'free',
statusID: "free",
statusTimestamp: -3,
statusTimeString: '',
statusTimeString: "",
stationTrains: [],
scheduledTrains: [],
checkpoints: null,
checkpoints: null
});
return acc;
@@ -453,8 +379,8 @@ export default class Store extends VuexModule {
stationTrains: [],
subStations: [],
online: true,
reqLevel: '-1',
nonPublic: true,
reqLevel: "-1",
nonPublic: true
});
});
@@ -478,153 +404,77 @@ export default class Store extends VuexModule {
}
@Mutation
private updateTimetableData(timetableList: ITimetableData[]) {
private updateTimetableData(timetableList: TimetableData[]) {
this.stationList = this.stationList.map(station => {
const stationName = station.stationName.toLowerCase();
const scheduledTrains: Station['scheduledTrains'] = timetableList.reduce(
(acc: Station['scheduledTrains'], timetableData: ITimetableData, index) => {
if (!timetableData.followingSceneries.includes(station.stationHash)) return acc;
const stopInfoIndex = timetableData.followingStops.findIndex(stop => {
const stopName = stop.stopNameRAW.toLowerCase();
const scheduledTrains: Station["scheduledTrains"] = timetableList.reduce((acc: Station["scheduledTrains"], timetable: TimetableData, index) => {
if (!timetable.followingSceneries.includes(station.stationHash)) return acc;
if (stationName === stopName) return true;
if (stopName.includes(stationName) && !stop.stopName.includes('po.') && !stop.stopName.includes('podg.')) return true;
if (stationName.includes(stopName) && !stop.stopName.includes('po.') && !stop.stopName.includes('podg.')) return true;
if (stopName.includes('podg.') && stopName.split(', podg.')[0] && stationName.includes(stopName.split(', podg.')[0])) return true;
const stopInfoIndex = timetable.followingStops.findIndex(stop => {
const stopName = stop.stopNameRAW.toLowerCase();
if (station.stops && station.stops.includes(stop.stopNameRAW)) return true;
if (stationName === stopName) return true;
if (stopName.includes(stationName) && !stop.stopName.includes("po.") && !stop.stopName.includes("podg.")) return true;
if (stationName.includes(stopName) && !stop.stopName.includes("po.") && !stop.stopName.includes("podg.")) return true;
if (stopName.includes("podg.") && stopName.split(", podg.")[0] && stationName.includes(stopName.split(", podg.")[0])) return true;
return false;
});
if (station.stops && station.stops.includes(stop.stopNameRAW)) return true;
if (stopInfoIndex == -1) return acc;
return false;
});
const stopInfo = timetableData.followingStops[stopInfoIndex];
if (stopInfoIndex == -1) return acc;
let stopStatus = '';
let stopLabel = '';
let stopStatusID = 0;
let nearestStop = '';
const trainStop = timetable.followingStops[stopInfoIndex];
const trainStopStatus = utils.getTrainStopStatus(trainStop, timetable, station);
if (stopInfo.terminatesHere && stopInfo.confirmed) {
stopStatus = 'terminated';
stopLabel = 'Skończył bieg';
stopStatusID = 5;
} else if (!stopInfo.terminatesHere && stopInfo.confirmed && timetableData.currentStationName == station.stationName) {
stopStatus = 'departed';
stopLabel = 'Odprawiony';
stopStatusID = 2;
} else if (!stopInfo.terminatesHere && stopInfo.confirmed && timetableData.currentStationName != station.stationName) {
stopStatus = 'departed-away';
stopLabel = 'Odjechał';
stopStatusID = 4;
} else if (timetableData.currentStationName == station.stationName && !stopInfo.stopped) {
stopStatus = 'online';
stopLabel = 'Na stacji';
stopStatusID = 0;
} else if (timetableData.currentStationName == station.stationName && stopInfo.stopped) {
stopStatus = 'stopped';
stopLabel = 'Postój';
stopStatusID = 1;
} else if (timetableData.currentStationName != station.stationName) {
stopStatus = 'arriving';
stopLabel = 'W drodze';
stopStatusID = 3;
}
acc.push({
trainNo: timetable.trainNo,
driverName: timetable.driverName,
driverId: timetable.driverId,
currentStationName: timetable.currentStationName,
currentStationHash: timetable.currentStationHash,
category: timetable.category,
beginsAt: timetable.followingStops[0].stopNameRAW,
terminatesAt: timetable.followingStops[timetable.followingStops.length - 1].stopNameRAW,
nearestStop: "",
stopInfo: trainStop,
stopLabel: trainStopStatus.stopLabel,
stopStatus: trainStopStatus.stopStatus,
stopStatusID: trainStopStatus.stopStatusID
});
if (stopInfoIndex < timetableData.followingStops.length - 2) {
for (let i = stopInfoIndex + 1; i < timetableData.followingStops.length - 1; i++) {
const stop = timetableData.followingStops[i];
if (stop.mainStop && stop.stopType.includes('ph')) {
nearestStop = stop.stopNameRAW;
break;
}
}
}
acc.push({
trainNo: timetableData.trainNo,
driverName: timetableData.driverName,
driverId: timetableData.driverId,
currentStationName: timetableData.currentStationName,
currentStationHash: timetableData.currentStationHash,
category: timetableData.category,
beginsAt: timetableData.followingStops[0].stopNameRAW,
terminatesAt: timetableData.followingStops[timetableData.followingStops.length - 1].stopNameRAW,
nearestStop,
stopInfo,
stopLabel,
stopStatus,
stopStatusID,
});
return acc;
},
[]
);
return acc;
}, []);
if (station.checkpoints) {
station.checkpoints.forEach(cp => (cp.scheduledTrains.length = 0));
for (let checkpoint of station.checkpoints) {
timetableList.reduce((acc, data) => {
data.followingStops
.filter(stop => stop.stopNameRAW.toLowerCase() === checkpoint.checkpointName.toLowerCase())
.forEach(stopInfo => {
// const stopInfo = data.followingStops[stopInfoIndex];
let stopStatus = '';
let stopLabel = '';
let nearestStop = '';
let stopStatusID = 0;
if (stopInfo.terminatesHere && stopInfo.confirmed) {
stopStatus = 'terminated';
stopLabel = 'Skończył bieg';
stopStatusID = 5;
} else if (!stopInfo.terminatesHere && stopInfo.confirmed && data.currentStationName == station.stationName) {
stopStatus = 'departed';
stopLabel = 'Odprawiony';
stopStatusID = 2;
} else if (!stopInfo.terminatesHere && stopInfo.confirmed && data.currentStationName != station.stationName) {
stopStatus = 'departed-away';
stopLabel = 'Odjechał';
stopStatusID = 4;
} else if (data.currentStationName == station.stationName && !stopInfo.stopped) {
stopStatus = 'online';
stopLabel = 'Na stacji';
stopStatusID = 0;
} else if (data.currentStationName == station.stationName && stopInfo.stopped) {
stopStatus = 'stopped';
stopLabel = 'Postój';
stopStatusID = 1;
} else if (data.currentStationName != station.stationName) {
stopStatus = 'arriving';
stopLabel = 'W drodze';
stopStatusID = 3;
}
timetableList.forEach(timetable => {
timetable.followingStops
.filter(trainStop => trainStop.stopNameRAW.toLowerCase() === checkpoint.checkpointName.toLowerCase())
.forEach(trainStop => {
const trainStopStatus = utils.getTrainStopStatus(trainStop, timetable, station);
checkpoint.scheduledTrains.push({
trainNo: data.trainNo,
driverName: data.driverName,
driverId: data.driverId,
currentStationName: data.currentStationName,
currentStationHash: data.currentStationHash,
category: data.category,
beginsAt: data.followingStops[0].stopNameRAW,
terminatesAt: data.followingStops[data.followingStops.length - 1].stopNameRAW,
stopInfo,
stopLabel,
stopStatus,
nearestStop,
stopStatusID,
trainNo: timetable.trainNo,
driverName: timetable.driverName,
driverId: timetable.driverId,
currentStationName: timetable.currentStationName,
currentStationHash: timetable.currentStationHash,
category: timetable.category,
beginsAt: timetable.followingStops[0].stopNameRAW,
terminatesAt: timetable.followingStops[timetable.followingStops.length - 1].stopNameRAW,
nearestStop: "",
stopInfo: trainStop,
stopLabel: trainStopStatus.stopLabel,
stopStatus: trainStopStatus.stopStatus,
stopStatusID: trainStopStatus.stopStatusID
});
});
return acc;
}, []);
});
}
}
@@ -639,7 +489,7 @@ export default class Store extends VuexModule {
.find(station => station.stationName === train.currentStationName)
?.scheduledTrains.find(stationTrain => stationTrain.trainNo === train.trainNo);
acc.push({ ...train, timetableData, stopStatus: trainData?.stopStatus || '', stopLabel: trainData?.stopLabel || '' });
acc.push({ ...train, timetableData, stopStatus: trainData?.stopStatus || "", stopLabel: trainData?.stopLabel || "" });
}
return acc;
+46 -46
View File
@@ -1,47 +1,47 @@
@import './variables.scss';
.card {
display: flex;
flex-direction: column;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 4;
overflow: auto;
background: $primaryCol;
box-shadow: 0 0 15px 5px #474747;
// width: 75%;
width: 650px;
max-height: 95%;
padding: 0.5em 1em;
@include smallScreen {
width: 95%;
}
// @include midScreen {
// width: 85%;
// }
&-exit {
position: absolute;
top: 0;
right: 0;
margin: 0.3em 0em;
img {
width: 1.6em;
}
cursor: pointer;
}
@import './variables.scss';
.card {
display: flex;
flex-direction: column;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 4;
overflow: auto;
background: $primaryCol;
box-shadow: 0 0 15px 5px #474747;
// width: 75%;
width: 650px;
max-height: 95%;
padding: 0.5em 1em;
@include smallScreen {
width: 95%;
}
// @include midScreen {
// width: 85%;
// }
&-exit {
position: absolute;
top: 0;
right: 0;
margin: 0.3em 0em;
img {
width: 1.6em;
}
cursor: pointer;
}
}
+347 -347
View File
@@ -1,348 +1,348 @@
<template>
<div class="history_view">
<div class="history_wrapper">
<div class="header">
<h2>{{ $t("journal.title") }}</h2>
<p style="color: #ccc">
{{ $t("journal.subtitle") }}
</p>
<div class="search-box">
<div class="search-box_content">
<label :class="{ disabled: dataLoading }">
<select v-model="inputStationName" :disabled="dataLoading">
<option value disabled selected hidden>
{{ dataLoading ? $t("app.loading") : $t("journal.select") }}
</option>
<option
v-for="station in filteredStationList"
:key="station"
:value="station"
>
{{ station }}
</option>
</select>
</label>
</div>
</div>
<div class="disclaimer" v-html="$t('journal.disclaimer')"></div>
</div>
<div class="list">
<div class="list_wrapper">
<!-- <div class="list_loading" v-if="dataLoading">POBIERANIE DANYCH...</div> -->
<transition name="list-anim" mode="out-in">
<ul
class="list_content"
v-if="
!dataLoading &&
!historyLoading &&
computedHistoryList.length != 0
"
:key="inputStationName"
>
<li v-if="currentDispatcherFrom != -1" class="current">
<div class="dispatcher-name">
<a
:href="`https://td2.info.pl/profile/?u=${currentDispatcherId}`"
>{{ currentDispatcher }}</a
>
</div>
<div class="dispatcher-date">
<span style="color: #bbb">{{
new Date(currentDispatcherFrom).toLocaleDateString("pl-PL")
}}</span>
{{
new Date(currentDispatcherFrom).toLocaleTimeString(
"pl-PL",
{ hour: "2-digit", minute: "2-digit" }
)
}}
</div>
</li>
<li v-for="(history, i) in computedHistoryList" :key="i">
<div class="dispatcher-name">
<a
:href="`https://td2.info.pl/profile/?u=${history.dispatcherId}`"
>{{ history.dispatcherName }}</a
>
</div>
<div class="dispatcher-date">
<div>
<span style="color: #888">{{
history.dispatcherFromDate
}}</span>
{{ history.dispatcherFromTime }}
</div>
<div>
<span style="color: #888">{{
history.dispatcherToDate
}}</span>
{{ history.dispatcherToTime }}
</div>
</div>
</li>
</ul>
</transition>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import axios from "axios";
import { Component, Vue, Watch } from "vue-property-decorator";
import { Getter } from "vuex-class";
import Station from "@/scripts/interfaces/Station";
import ISceneryInfoData from "@/scripts/interfaces/ISceneryInfoData";
@Component
export default class HistoryView extends Vue {
@Getter("getStationList") stationList!: Station[];
sceneryHistoryList: ISceneryInfoData[] = [];
currentSceneryHistory: ISceneryInfoData["dispatcherHistory"] = [];
currentDispatcher: string = "";
currentDispatcherId: number = 0;
currentDispatcherFrom: number = -1;
inputStationName = "";
dataLoading = true; /* Initial data */
historyLoading = false; /* History loaded after input is checked */
async mounted() {
try {
const responseData: ISceneryInfoData[] = await (
await axios.get(
"https://stacjownik.herokuapp.com/api/getSceneryInfo?items=-1"
)
).data;
this.sceneryHistoryList = responseData;
} catch (error) {
console.error(error);
}
this.dataLoading = false;
}
@Watch("inputStationName")
onInputChanged(val: string) {
this.itemSelected(val);
}
get filteredStationList() {
return this.sceneryHistoryList
.map((station) => station.stationName)
.sort((a, b) => (a.toLowerCase() >= b.toLowerCase() ? 1 : -1));
}
get computedHistoryList() {
return this.currentSceneryHistory
.map(
({ dispatcherName, dispatcherFrom, dispatcherTo, dispatcherId }) => ({
dispatcherName,
dispatcherFrom,
dispatcherTo,
dispatcherId,
dispatcherFromDate: new Date(dispatcherFrom).toLocaleDateString(
"pl-PL"
),
dispatcherFromTime: new Date(
dispatcherFrom
).toLocaleTimeString("pl-PL", { hour: "2-digit", minute: "2-digit" }),
dispatcherToDate: new Date(dispatcherTo).toLocaleDateString("pl-PL"),
dispatcherToTime: new Date(dispatcherTo).toLocaleTimeString("pl-PL", {
hour: "2-digit",
minute: "2-digit",
}),
})
)
.reverse();
}
async itemSelected(itemName: string) {
try {
this.historyLoading = true;
const selectedScenery: ISceneryInfoData = await (
await axios.get(
`https://stacjownik.herokuapp.com/api/getSceneryInfo?name=${itemName}&items=10`
)
).data;
this.currentSceneryHistory = selectedScenery.dispatcherHistory;
this.currentDispatcher = selectedScenery.currentDispatcher;
this.currentDispatcherId = selectedScenery.currentDispatcherId;
this.currentDispatcherFrom = selectedScenery.currentDispatcherFrom;
} catch (error) {
console.error(error);
}
this.historyLoading = false;
}
}
</script>
<style scoped lang="scss">
@import "../styles/responsive.scss";
.history {
&_view {
font-size: 1.2em;
}
&_wrapper {
width: 100%;
height: 100%;
text-align: center;
margin-top: 0.5em;
}
}
.list-anim {
&-enter-active,
&-leave-active {
transition: all 150ms ease-out;
}
&-enter,
&-leave-to {
opacity: 0.1;
transform: scale(0.95);
}
&-move {
transition: transform 100ms;
}
}
.disclaimer {
color: #aaa;
}
.search-box {
display: flex;
justify-content: center;
align-items: center;
&_content {
position: relative;
margin: 1em 0;
font-size: 1em;
}
select {
border: none;
font-size: 1em;
background-color: rgb(87, 87, 87);
padding: 0.3em;
padding-right: 50px;
outline: none;
color: white;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
cursor: pointer;
}
label {
position: relative;
&.disabled::after {
color: gray;
}
&::after {
content: "<>";
position: absolute;
top: -5%;
right: 0.3em;
font-weight: bold;
}
}
}
.list {
text-align: center;
margin: 1em 0;
display: flex;
justify-content: center;
&_loading,
&_no-info {
margin: 0.3em 0;
padding: 0.5em 2em;
color: white;
}
&_loading {
background-color: #b96b11;
}
&_no-info {
background-color: firebrick;
}
&_wrapper {
@include smallScreen() {
width: 95%;
font-size: 0.9em;
}
}
&_content {
max-height: 75vh;
overflow: auto;
padding: 0.2em 0.5em;
}
&_content > li {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
background: #222;
padding: 0.3em 0.8em;
margin: 0.3em 0;
gap: 10em;
@include smallScreen() {
gap: 1em;
}
& > div {
margin: 0 1em;
}
&.current {
background: #007200;
}
& > .dispatcher-name {
display: flex;
justify-content: center;
align-items: center;
font-size: 1.1em;
font-weight: 500;
}
}
}
<template>
<div class="history_view">
<div class="history_wrapper">
<div class="header">
<h2>{{ $t("journal.title") }}</h2>
<p style="color: #ccc">
{{ $t("journal.subtitle") }}
</p>
<div class="search-box">
<div class="search-box_content">
<label :class="{ disabled: dataLoading }">
<select v-model="inputStationName" :disabled="dataLoading">
<option value disabled selected hidden>
{{ dataLoading ? $t("app.loading") : $t("journal.select") }}
</option>
<option
v-for="station in filteredStationList"
:key="station"
:value="station"
>
{{ station }}
</option>
</select>
</label>
</div>
</div>
<div class="disclaimer" v-html="$t('journal.disclaimer')"></div>
</div>
<div class="list">
<div class="list_wrapper">
<!-- <div class="list_loading" v-if="dataLoading">POBIERANIE DANYCH...</div> -->
<transition name="list-anim" mode="out-in">
<ul
class="list_content"
v-if="
!dataLoading &&
!historyLoading &&
computedHistoryList.length != 0
"
:key="inputStationName"
>
<li v-if="currentDispatcherFrom != -1" class="current">
<div class="dispatcher-name">
<a
:href="`https://td2.info.pl/profile/?u=${currentDispatcherId}`"
>{{ currentDispatcher }}</a
>
</div>
<div class="dispatcher-date">
<span style="color: #bbb">{{
new Date(currentDispatcherFrom).toLocaleDateString("pl-PL")
}}</span>
{{
new Date(currentDispatcherFrom).toLocaleTimeString(
"pl-PL",
{ hour: "2-digit", minute: "2-digit" }
)
}}
</div>
</li>
<li v-for="(history, i) in computedHistoryList" :key="i">
<div class="dispatcher-name">
<a
:href="`https://td2.info.pl/profile/?u=${history.dispatcherId}`"
>{{ history.dispatcherName }}</a
>
</div>
<div class="dispatcher-date">
<div>
<span style="color: #888">{{
history.dispatcherFromDate
}}</span>
{{ history.dispatcherFromTime }}
</div>
<div>
<span style="color: #888">{{
history.dispatcherToDate
}}</span>
{{ history.dispatcherToTime }}
</div>
</div>
</li>
</ul>
</transition>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import axios from "axios";
import { Component, Vue, Watch } from "vue-property-decorator";
import { Getter } from "vuex-class";
import Station from "@/scripts/interfaces/Station";
import ISceneryInfoData from "@/scripts/interfaces/ISceneryInfoData";
@Component
export default class HistoryView extends Vue {
@Getter("getStationList") stationList!: Station[];
sceneryHistoryList: ISceneryInfoData[] = [];
currentSceneryHistory: ISceneryInfoData["dispatcherHistory"] = [];
currentDispatcher: string = "";
currentDispatcherId: number = 0;
currentDispatcherFrom: number = -1;
inputStationName = "";
dataLoading = true; /* Initial data */
historyLoading = false; /* History loaded after input is checked */
async mounted() {
try {
const responseData: ISceneryInfoData[] = await (
await axios.get(
"https://stacjownik.herokuapp.com/api/getSceneryInfo?items=-1"
)
).data;
this.sceneryHistoryList = responseData;
} catch (error) {
console.error(error);
}
this.dataLoading = false;
}
@Watch("inputStationName")
onInputChanged(val: string) {
this.itemSelected(val);
}
get filteredStationList() {
return this.sceneryHistoryList
.map((station) => station.stationName)
.sort((a, b) => (a.toLowerCase() >= b.toLowerCase() ? 1 : -1));
}
get computedHistoryList() {
return this.currentSceneryHistory
.map(
({ dispatcherName, dispatcherFrom, dispatcherTo, dispatcherId }) => ({
dispatcherName,
dispatcherFrom,
dispatcherTo,
dispatcherId,
dispatcherFromDate: new Date(dispatcherFrom).toLocaleDateString(
"pl-PL"
),
dispatcherFromTime: new Date(
dispatcherFrom
).toLocaleTimeString("pl-PL", { hour: "2-digit", minute: "2-digit" }),
dispatcherToDate: new Date(dispatcherTo).toLocaleDateString("pl-PL"),
dispatcherToTime: new Date(dispatcherTo).toLocaleTimeString("pl-PL", {
hour: "2-digit",
minute: "2-digit",
}),
})
)
.reverse();
}
async itemSelected(itemName: string) {
try {
this.historyLoading = true;
const selectedScenery: ISceneryInfoData = await (
await axios.get(
`https://stacjownik.herokuapp.com/api/getSceneryInfo?name=${itemName}&items=10`
)
).data;
this.currentSceneryHistory = selectedScenery.dispatcherHistory;
this.currentDispatcher = selectedScenery.currentDispatcher;
this.currentDispatcherId = selectedScenery.currentDispatcherId;
this.currentDispatcherFrom = selectedScenery.currentDispatcherFrom;
} catch (error) {
console.error(error);
}
this.historyLoading = false;
}
}
</script>
<style scoped lang="scss">
@import "../styles/responsive.scss";
.history {
&_view {
font-size: 1.2em;
}
&_wrapper {
width: 100%;
height: 100%;
text-align: center;
margin-top: 0.5em;
}
}
.list-anim {
&-enter-active,
&-leave-active {
transition: all 150ms ease-out;
}
&-enter,
&-leave-to {
opacity: 0.1;
transform: scale(0.95);
}
&-move {
transition: transform 100ms;
}
}
.disclaimer {
color: #aaa;
}
.search-box {
display: flex;
justify-content: center;
align-items: center;
&_content {
position: relative;
margin: 1em 0;
font-size: 1em;
}
select {
border: none;
font-size: 1em;
background-color: rgb(87, 87, 87);
padding: 0.3em;
padding-right: 50px;
outline: none;
color: white;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
cursor: pointer;
}
label {
position: relative;
&.disabled::after {
color: gray;
}
&::after {
content: "<>";
position: absolute;
top: -5%;
right: 0.3em;
font-weight: bold;
}
}
}
.list {
text-align: center;
margin: 1em 0;
display: flex;
justify-content: center;
&_loading,
&_no-info {
margin: 0.3em 0;
padding: 0.5em 2em;
color: white;
}
&_loading {
background-color: #b96b11;
}
&_no-info {
background-color: firebrick;
}
&_wrapper {
@include smallScreen() {
width: 95%;
font-size: 0.9em;
}
}
&_content {
max-height: 75vh;
overflow: auto;
padding: 0.2em 0.5em;
}
&_content > li {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
background: #222;
padding: 0.3em 0.8em;
margin: 0.3em 0;
gap: 10em;
@include smallScreen() {
gap: 1em;
}
& > div {
margin: 0 1em;
}
&.current {
background: #007200;
}
& > .dispatcher-name {
display: flex;
justify-content: center;
align-items: center;
font-size: 1.1em;
font-weight: 500;
}
}
}
</style>