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
+43 -43
View File
@@ -3444,9 +3444,9 @@
} }
}, },
"core-js": { "core-js": {
"version": "3.6.5", "version": "3.10.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.10.1.tgz",
"integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==" "integrity": "sha512-pwCxEXnj27XG47mu7SXAwhLP3L5CrlvCB91ANUkIz40P27kUcvNfSdvyZJ9CLHiVoKSp+TTChMQMSKQEH/IQxA=="
}, },
"core-js-compat": { "core-js-compat": {
"version": "3.6.5", "version": "3.6.5",
@@ -4289,24 +4289,24 @@
"dev": true "dev": true
}, },
"elliptic": { "elliptic": {
"version": "6.5.3", "version": "6.5.4",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
"integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"bn.js": "^4.4.0", "bn.js": "^4.11.9",
"brorand": "^1.0.1", "brorand": "^1.1.0",
"hash.js": "^1.0.0", "hash.js": "^1.0.0",
"hmac-drbg": "^1.0.0", "hmac-drbg": "^1.0.1",
"inherits": "^2.0.1", "inherits": "^2.0.4",
"minimalistic-assert": "^1.0.0", "minimalistic-assert": "^1.0.1",
"minimalistic-crypto-utils": "^1.0.0" "minimalistic-crypto-utils": "^1.0.1"
}, },
"dependencies": { "dependencies": {
"bn.js": { "bn.js": {
"version": "4.11.9", "version": "4.12.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
"dev": true "dev": true
} }
} }
@@ -8588,12 +8588,12 @@
"dev": true "dev": true
}, },
"sass": { "sass": {
"version": "1.26.10", "version": "1.32.9",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.26.10.tgz", "resolved": "https://registry.npmjs.org/sass/-/sass-1.32.9.tgz",
"integrity": "sha512-bzN0uvmzfsTvjz0qwccN1sPm2HxxpNI/Xa+7PlUEMS+nQvbyuEK7Y0qFqxlPHhiNHb1Ze8WQJtU31olMObkAMw==", "integrity": "sha512-DGXRkoCF5w+WnlcfolMiNsZ/D0UfmOi4CW2ORMgrXg1eMF6Aoq7kj5qlMrkiXhXdRufTYclMsJUtxYozQT65Ig==",
"dev": true, "dev": true,
"requires": { "requires": {
"chokidar": ">=2.0.0 <4.0.0" "chokidar": ">=3.0.0 <4.0.0"
} }
}, },
"sass-loader": { "sass-loader": {
@@ -9820,9 +9820,9 @@
"dev": true "dev": true
}, },
"typescript": { "typescript": {
"version": "3.9.7", "version": "3.9.9",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.9.tgz",
"integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", "integrity": "sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==",
"dev": true "dev": true
}, },
"uglify-js": { "uglify-js": {
@@ -10142,14 +10142,14 @@
"dev": true "dev": true
}, },
"vue": { "vue": {
"version": "2.6.11", "version": "2.6.12",
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.11.tgz", "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.12.tgz",
"integrity": "sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ==" "integrity": "sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg=="
}, },
"vue-class-component": { "vue-class-component": {
"version": "7.2.5", "version": "7.2.6",
"resolved": "https://registry.npmjs.org/vue-class-component/-/vue-class-component-7.2.5.tgz", "resolved": "https://registry.npmjs.org/vue-class-component/-/vue-class-component-7.2.6.tgz",
"integrity": "sha512-0CSftHY0bDTD+4FbYkuFf6+iKDjZ4h2in2YYJDRMk5daZIjrgT9LjFHvP7Rzqy9/s1pij3zDtTSLRUjsPWMwqg==" "integrity": "sha512-+eaQXVrAm/LldalI272PpDe3+i4mPis0ORiMYxF6Ae4hyuCh15W8Idet7wPUEs4N4YptgFHGys4UrgNQOMyO6w=="
}, },
"vue-hot-reload-api": { "vue-hot-reload-api": {
"version": "2.3.4", "version": "2.3.4",
@@ -10158,9 +10158,9 @@
"dev": true "dev": true
}, },
"vue-i18n": { "vue-i18n": {
"version": "8.23.0", "version": "8.24.3",
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-8.23.0.tgz", "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-8.24.3.tgz",
"integrity": "sha512-mXgniaumwca8tKdp55fmvqIcW658vQQXq0zEyRHp8sgZ6t+Md+Whhu6CCPg9/erVNlvpKzsGsucGjt2N8GrFCA==" "integrity": "sha512-uKAYzGbwGIJndY7JwhQwIGi1uyvErWkBfFwooOtjcNnIfMbAR49ad5dT/MiykrJ9pCcgvnocFjFsNLtTzyW+rg=="
}, },
"vue-loader": { "vue-loader": {
"version": "15.9.3", "version": "15.9.3",
@@ -10192,9 +10192,9 @@
} }
}, },
"vue-router": { "vue-router": {
"version": "3.4.3", "version": "3.5.1",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.4.3.tgz", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.5.1.tgz",
"integrity": "sha512-BADg1mjGWX18Dpmy6bOGzGNnk7B/ZA0RxuA6qedY/YJwirMfKXIDzcccmHbQI0A6k5PzMdMloc0ElHfyOoX35A==" "integrity": "sha512-RRQNLT8Mzr8z7eL4p7BtKvRaTSGdCbTy2+Mm5HTJvLGYSSeG9gDzNasJPP/yOYKLy+/cLG/ftrqq5fvkFwBJEw=="
}, },
"vue-style-loader": { "vue-style-loader": {
"version": "4.1.2", "version": "4.1.2",
@@ -10215,9 +10215,9 @@
} }
}, },
"vue-template-compiler": { "vue-template-compiler": {
"version": "2.6.11", "version": "2.6.12",
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.11.tgz", "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.12.tgz",
"integrity": "sha512-KIq15bvQDrcCjpGjrAhx4mUlyyHfdmTaoNfeoATHLAiWB+MU3cx4lOzMwrnUh9cCxy0Lt1T11hAFY6TQgroUAA==", "integrity": "sha512-OzzZ52zS41YUbkCBfdXShQTe69j1gQDZ9HIX8miuC9C3rBCk9wIRjLiZZLrmX9V+Ftq/YEyv1JaVr5Y/hNtByg==",
"dev": true, "dev": true,
"requires": { "requires": {
"de-indent": "^1.0.2", "de-indent": "^1.0.2",
@@ -10231,9 +10231,9 @@
"dev": true "dev": true
}, },
"vuex": { "vuex": {
"version": "3.5.1", "version": "3.6.2",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.5.1.tgz", "resolved": "https://registry.npmjs.org/vuex/-/vuex-3.6.2.tgz",
"integrity": "sha512-w7oJzmHQs0FM9LXodfskhw9wgKBiaB+totOdb8sNzbTB2KDCEEwEs29NzBZFh/lmEK1t5tDmM1vtsO7ubG1DFw==" "integrity": "sha512-ETW44IqCgBpVomy520DT5jf8n0zoCac+sxWnn+hMe/CzaSejb/eVw2YToiXYX+Ex/AuHHia28vWTq4goAexFbw=="
}, },
"vuex-class": { "vuex-class": {
"version": "0.3.2", "version": "0.3.2",
@@ -10968,9 +10968,9 @@
"dev": true "dev": true
}, },
"y18n": { "y18n": {
"version": "4.0.0", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
"dev": true "dev": true
}, },
"yallist": { "yallist": {
+9 -9
View File
@@ -8,16 +8,16 @@
"deploy": "npm run build && firebase deploy --only hosting" "deploy": "npm run build && firebase deploy --only hosting"
}, },
"dependencies": { "dependencies": {
"core-js": "^3.6.5", "core-js": "^3.10.1",
"dotenv": "^8.2.0", "dotenv": "^8.2.0",
"firestore": "^1.1.6", "firestore": "^1.1.6",
"howler": "^2.2.1", "howler": "^2.2.1",
"vue": "^2.6.11", "vue": "^2.6.12",
"vue-class-component": "^7.2.5", "vue-class-component": "^7.2.6",
"vue-i18n": "^8.23.0", "vue-i18n": "^8.24.3",
"vue-property-decorator": "^8.4.2", "vue-property-decorator": "^8.4.2",
"vue-router": "^3.4.3", "vue-router": "^3.5.1",
"vuex": "^3.4.0" "vuex": "^3.6.2"
}, },
"devDependencies": { "devDependencies": {
"@vue/cli-plugin-babel": "~4.4.0", "@vue/cli-plugin-babel": "~4.4.0",
@@ -26,10 +26,10 @@
"@vue/cli-plugin-vuex": "~4.4.0", "@vue/cli-plugin-vuex": "~4.4.0",
"@vue/cli-service": "~4.4.0", "@vue/cli-service": "~4.4.0",
"axios": "^0.21.1", "axios": "^0.21.1",
"sass": "^1.26.10", "sass": "^1.32.9",
"sass-loader": "^8.0.2", "sass-loader": "^8.0.2",
"typescript": "^3.9.7", "typescript": "^3.9.9",
"vue-template-compiler": "^2.6.11", "vue-template-compiler": "^2.6.12",
"vuex-class": "^0.3.2", "vuex-class": "^0.3.2",
"vuex-module-decorators": "^0.17.0" "vuex-module-decorators": "^0.17.0"
}, },
+334 -342
View File
@@ -1,342 +1,334 @@
<template> <template>
<div class="app"> <div class="app">
<UpdateModal <UpdateModal
:currentVersion="VERSION" :currentVersion="VERSION"
@toggleUpdateModal="toggleUpdateModal" @toggleUpdateModal="toggleUpdateModal"
v-if="updateModalVisible" v-if="updateModalVisible"
/> />
<div class="app_container"> <div class="app_container">
<header class="app_header"> <header class="app_header">
<div class="header_body"> <div class="header_body">
<span class="header_brand"> <span class="header_brand">
<span> <span>
<span>Stacj</span> <span>Stacj</span>
<img src="@/assets/trainlogo.png" alt="trainlogo" /> <img src="@/assets/trainlogo.png" alt="trainlogo" />
<span>wnik</span> <span>wnik</span>
</span> </span>
<span class="brand_lang"> <span class="brand_lang">
<span <span
class="lang pl" class="lang pl"
@click="changeLang('en')" @click="changeLang('en')"
:class="{ current: currentLang == 'pl' }" :class="{ current: currentLang == 'pl' }"
v-if="currentLang == 'pl'" v-if="currentLang == 'pl'"
> >
<img :src="iconPL" alt="icon-pl" /> <img :src="iconPL" alt="icon-pl" />
</span> </span>
<span <span
class="lang en" class="lang en"
@click="changeLang('pl')" @click="changeLang('pl')"
:class="{ current: currentLang == 'en' }" :class="{ current: currentLang == 'en' }"
v-if="currentLang == 'en'" v-if="currentLang == 'en'"
> >
<img :src="iconEN" alt="icon-en" /> <img :src="iconEN" alt="icon-en" />
</span> </span>
</span> </span>
</span> </span>
<span class="header_info"> <span class="header_info">
<Clock /> <Clock />
<div class="info_counter"> <div class="info_counter">
<img src="@/assets/icon-dispatcher.svg" alt="icon dispatcher" /> <img src="@/assets/icon-dispatcher.svg" alt="icon dispatcher" />
<span>{{ data.stationCount }}</span> <span>{{ data.stationCount }}</span>
<span>{{ data.trainCount }}</span> <span>{{ data.trainCount }}</span>
<img src="@/assets/icon-train.svg" alt="icon train" /> <img src="@/assets/icon-train.svg" alt="icon train" />
</div> </div>
</span> </span>
<span class="header_links"> <span class="header_links">
<router-link class="route" active-class="route-active" to="/" exact <router-link class="route" active-class="route-active" to="/" exact
>{{ $t("app.sceneries") }} >{{ $t("app.sceneries") }}
</router-link> </router-link>
/ /
<router-link class="route" active-class="route-active" to="/trains" <router-link class="route" active-class="route-active" to="/trains"
>{{ $t("app.trains") }} >{{ $t("app.trains") }}
</router-link> </router-link>
</span>
<!-- <router-link </div>
class="route" </header>
active-class="route-active"
to="/history" <main class="app_main">
>{{ $t("app.journal") }}</router-link <transition name="view-anim" mode="out-in">
> --> <keep-alive>
</span> <router-view />
</div> </keep-alive>
</header> </transition>
</main>
<main class="app_main">
<transition name="view-anim" mode="out-in"> <footer class="app_footer">
<keep-alive> &copy;
<router-view /> <a href="https://td2.info.pl/profile/?u=20777" target="_blank">
</keep-alive> Spythere
</transition> </a>
</main> 2021 | v{{ VERSION }} | [<a
target="_blank"
<footer class="app_footer"> href="https://paypal.me/spythere"
&copy; >{{ $t("app.support") }}!</a
<a href="https://td2.info.pl/profile/?u=20777" target="_blank"> >]
Spythere </footer>
</a> </div>
2021 | v{{ VERSION }} | [<a </div>
target="_blank" </template>
href="https://paypal.me/spythere"
>{{ $t("app.support") }}!</a <script lang="ts">
>] import { Vue, Component } from "vue-property-decorator";
</footer> import { Action, Getter } from "vuex-class";
</div>
</div> import UpdateModal from "@/components/Global/UpdateModal.vue";
</template> import Clock from "@/components/App/Clock.vue";
<script lang="ts"> import StorageManager from "@/scripts/storageManager";
import { Vue, Component } from "vue-property-decorator";
import { Action, Getter } from "vuex-class"; @Component({
components: { Clock, UpdateModal },
import UpdateModal from "@/components/Global/UpdateModal.vue"; })
import Clock from "@/components/App/Clock.vue"; export default class App extends Vue {
@Action("synchronizeData") synchronizeData;
import StorageManager from "@/scripts/storageManager"; @Getter("getAllData") data;
import DataModule from "@/store/modules/DataModule"; private VERSION = "1.4.4";
import { getModule } from "vuex-module-decorators";
hasReleaseNotes = false;
@Component({ updateModalVisible = false;
components: { Clock, UpdateModal },
}) currentLang = "pl";
export default class App extends Vue {
@Action("synchronizeData") synchronizeData; iconEN = require("@/assets/icon-en.jpg");
@Getter("getAllData") data; iconPL = require("@/assets/icon-pl.svg");
private VERSION = "1.4.4"; toggleUpdateModal() {
this.updateModalVisible = !this.updateModalVisible;
hasReleaseNotes = false; StorageManager.setBooleanValue("version_notes_read", true);
updateModalVisible = false; }
currentLang = "pl"; changeLang(lang: string) {
this.$i18n.locale = lang;
iconEN = require("@/assets/icon-en.jpg"); this.currentLang = lang;
iconPL = require("@/assets/icon-pl.svg");
StorageManager.setStringValue("lang", lang);
dataStore: DataModule = getModule(DataModule); }
get test() { loadLang() {
return this.dataStore.getTest; const storageLang = StorageManager.getStringValue("lang");
}
if (storageLang) {
mounted() { this.changeLang(storageLang);
this.synchronizeData(); return;
}
setTimeout(() => {
this.dataStore.fetchTest(); if (!window.navigator.language) {
}, 3000); this.changeLang("pl");
return;
if (StorageManager.getStringValue("lang")) { }
this.changeLang(StorageManager.getStringValue("lang"));
} else if (window.navigator.language) { switch (window.navigator.language) {
switch (window.navigator.language) { case "pl-PL":
case "pl-PL": this.changeLang("pl");
this.changeLang("pl"); break;
break; case "en-EN":
case "en-EN": default:
default: this.changeLang("en");
this.changeLang("en"); break;
break; }
}
return;
this.currentLang = this.$i18n.locale; }
}
created() {
if (StorageManager.getStringValue("version") != this.VERSION) { this.loadLang();
StorageManager.setStringValue("version", this.VERSION); this.synchronizeData();
}
if (this.hasReleaseNotes)
StorageManager.setBooleanValue("version_notes_read", false); mounted() {
} if (StorageManager.getStringValue("version") != this.VERSION) {
StorageManager.setStringValue("version", this.VERSION);
this.updateModalVisible =
this.hasReleaseNotes && if (this.hasReleaseNotes)
!StorageManager.getBooleanValue("version_notes_read"); StorageManager.setBooleanValue("version_notes_read", false);
} }
changeLang(lang: string) { this.updateModalVisible =
this.$i18n.locale = lang; this.hasReleaseNotes &&
this.currentLang = lang; !StorageManager.getBooleanValue("version_notes_read");
}
StorageManager.setStringValue("lang", lang); }
console.log("Switched to: " + lang); </script>
}
<style lang="scss">
toggleUpdateModal() { @import "./styles/responsive.scss";
this.updateModalVisible = !this.updateModalVisible; @import "./styles/variables.scss";
StorageManager.setBooleanValue("version_notes_read", true); @import "./styles/global.scss";
} @import "./styles/scenery_status.scss";
}
</script> :root {
--clr-primary: #ffc014;
<style lang="scss"> --clr-secondary: #2f2f2f;
@import "./styles/responsive.scss";
@import "./styles/variables.scss"; --clr-bg: #333;
@import "./styles/global.scss";
@import "./styles/scenery_status.scss"; --clr-accent: #1085b3;
--clr-accent2: #ff3d5d;
:root {
--clr-primary: #ffc014; --clr-skr: #ff5100;
--clr-secondary: #2f2f2f; --clr-twr: #ffbb00;
}
--clr-bg: #333;
// VUE ROUTE CHANGE ANIMATION
--clr-accent: #1085b3; .view-anim {
--clr-accent2: #ff3d5d; &-enter {
opacity: 0.02;
--clr-skr: #ff5100; }
--clr-twr: #ffbb00;
} &-leave-to {
opacity: 0.02;
// VUE ROUTE CHANGE ANIMATION }
.view-anim {
&-enter { &-enter-active,
opacity: 0.02; &-leave-active {
} transition: all $animDuration $animType;
min-height: 100%;
&-leave-to { }
opacity: 0.02; }
}
.route {
&-enter-active, margin: 0 0.2em;
&-leave-active {
transition: all $animDuration $animType; &-active {
min-height: 100%; color: $accentCol;
} font-weight: bold;
} }
}
.route {
margin: 0 0.2em; // APP
.app {
&-active { background: $bgCol;
color: $accentCol; color: white;
font-weight: bold;
} overflow: hidden;
}
font-size: 1rem;
// APP
.app { @include smallScreen() {
background: $bgCol; font-size: calc(0.45rem + 1vw);
color: white; }
}
overflow: hidden;
// CONTAINER
font-size: 1rem; .app_container {
display: flex;
@include smallScreen() { flex-flow: column;
font-size: calc(0.45rem + 1vw);
} min-width: 0;
} min-height: 100vh;
// CONTAINER header {
.app_container { flex: 0 0 auto;
display: flex; }
flex-flow: column;
main {
min-width: 0; flex: 1 1 auto;
min-height: 100vh; }
header { footer {
flex: 0 0 auto; flex: 0 1 0.2em;
} }
}
main {
flex: 1 1 auto; // HEADER
} .app_header {
background: $primaryCol;
footer { padding: 0.15em;
flex: 0 1 0.2em;
} border-radius: 0 0 1em 1em;
}
display: flex;
// HEADER justify-content: center;
.app_header { }
background: $primaryCol;
padding: 0.15em; .header {
&_brand {
border-radius: 0 0 1em 1em; position: relative;
width: 100%;
display: flex; font-size: 4.25em;
justify-content: center;
} text-align: center;
.header { img {
&_brand { width: 0.8em;
position: relative; }
width: 100%;
font-size: 4.25em; .brand_lang {
position: absolute;
text-align: center; right: 0;
img { transform: translate(110%, -35%);
width: 0.8em;
} img {
width: 0.6em;
.brand_lang { }
position: absolute;
right: 0; cursor: pointer;
}
transform: translate(110%, -35%); }
img { &_info {
width: 0.6em; display: flex;
} justify-content: space-between;
cursor: pointer; font-size: 1.25em;
}
} margin: 0 0.3em;
padding: 0.2em;
&_info { }
display: flex;
justify-content: space-between; &_links {
display: flex;
font-size: 1.25em; justify-content: center;
margin: 0 0.3em; border-radius: 0.7em;
padding: 0.2em;
} font-size: 1.25em;
padding: 0.5em;
&_links { }
display: flex; }
justify-content: center;
// COUNTER
border-radius: 0.7em; .info_counter {
display: flex;
font-size: 1.25em; align-items: center;
padding: 0.5em; color: $accentCol;
}
} span {
margin: 0 0.15em;
// COUNTER }
.info_counter {
display: flex; img {
align-items: center; width: 1.35em;
color: $accentCol; }
}
span {
margin: 0 0.15em; // FOOTER
} footer.app_footer {
max-width: 100%;
img { padding: 0.5em;
width: 1.35em;
} z-index: 10;
}
background: #111;
// FOOTER color: white;
footer.app_footer {
max-width: 100%; text-align: center;
padding: 0.5em; }
</style>
z-index: 10;
background: #111;
color: white;
text-align: center;
}
</style>
+96 -96
View File
@@ -1,97 +1,97 @@
<template> <template>
<div class="modal"> <div class="modal">
<div class="header"> <div class="header">
<span>Stacj</span> <span>Stacj</span>
<img src="@/assets/trainlogo.png" alt="trainlogo" /> <img src="@/assets/trainlogo.png" alt="trainlogo" />
<span>wnik</span> <span>wnik</span>
<sup style="font-size: 0.5em; margin-left: 10px;" class="title">1.4</sup> <sup style="font-size: 0.5em; margin-left: 10px;" class="title">1.4</sup>
</div> </div>
<div class="title">Dziennik Aktywności Scenerii dostępny w wersji beta!</div> <div class="title">Dziennik Aktywności Scenerii dostępny w wersji beta!</div>
<div class="content"> <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. 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, 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. 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 style="text-align: center; font-weight: bold; margin: 0.5em 0;">Miłego korzystania!</div>
</div> </div>
<button class="button" @click="toggleUpdateModal">PRZYJĄŁEM!</button> <button class="button" @click="toggleUpdateModal">PRZYJĄŁEM!</button>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator"; import { Component, Vue, Prop } from "vue-property-decorator";
@Component @Component
export default class UpdateModal extends Vue { export default class UpdateModal extends Vue {
@Prop() currentVersion!: string; @Prop() currentVersion!: string;
STORAGE_ID = "modal_update"; STORAGE_ID = "modal_update";
toggleUpdateModal(type: string) { toggleUpdateModal(type: string) {
this.$emit("toggleUpdateModal"); this.$emit("toggleUpdateModal");
} }
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "../../styles/responsive"; @import "../../styles/responsive";
.modal { .modal {
z-index: 100; z-index: 100;
padding: 1em; padding: 1em;
border-radius: 1em; border-radius: 1em;
position: fixed; position: fixed;
top: 50%; top: 50%;
left: 50%; left: 50%;
width: 65%; width: 65%;
max-width: 950px; max-width: 950px;
max-height: 95vh; max-height: 95vh;
overflow: auto; overflow: auto;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
background: rgba(black, 0.85); background: rgba(black, 0.85);
color: white; color: white;
text-align: center; text-align: center;
@include smallScreen() { @include smallScreen() {
font-size: 0.8em; font-size: 0.8em;
width: 95%; width: 95%;
} }
} }
.header { .header {
font-size: 4.5em; font-size: 4.5em;
img { img {
width: 0.8em; width: 0.8em;
} }
} }
.title { .title {
font-size: 2em; font-size: 2em;
} }
.content { .content {
font-size: 1.4em; font-size: 1.4em;
text-align: justify; text-align: justify;
ul { ul {
list-style: square inside; list-style: square inside;
} }
} }
.button { .button {
font-size: 1.25em; font-size: 1.25em;
margin: 0 auto; margin: 0 auto;
} }
</style> </style>
+286 -286
View File
@@ -1,287 +1,287 @@
<template> <template>
<div class="train-options"> <div class="train-options">
<div class="options_wrapper"> <div class="options_wrapper">
<div class="train-sorter option"> <div class="train-sorter option">
<div class="train-sorter_wrapper"> <div class="train-sorter_wrapper">
<div class="train-sorter_selected" @click="toggleSorterOptions"> <div class="train-sorter_selected" @click="toggleSorterOptions">
<span>{{ currentSorterOption }}</span> <span>{{ currentSorterOption }}</span>
<img :src="descIcon" alt="icon-select" /> <img :src="descIcon" alt="icon-select" />
</div> </div>
<div class="train-sorter_options"> <div class="train-sorter_options">
<ul :class="{ open: sorterOptionsOpen }"> <ul :class="{ open: sorterOptionsOpen }">
<li <li
v-for="(option, i) in sorterOptions" v-for="(option, i) in sorterOptions"
:key="i" :key="i"
@click="() => chooseOption(option)" @click="() => chooseOption(option)"
> >
<input type="radio" name="sort" :id="option.id" /> <input type="radio" name="sort" :id="option.id" />
<label :for="option.id"> <label :for="option.id">
{{ $t(`trains.option-${option.id}`) }} {{ $t(`trains.option-${option.id}`) }}
</label> </label>
</li> </li>
</ul> </ul>
</div> </div>
</div> </div>
</div> </div>
<div class="train-search_train option"> <div class="train-search_train option">
<div class="search-box"> <div class="search-box">
<input <input
class="search-input" class="search-input"
:placeholder="$t('trains.search-no')" :placeholder="$t('trains.search-no')"
v-model="searchedTrain" v-model="searchedTrain"
/> />
<img <img
class="search-exit" class="search-exit"
:src="exitIcon" :src="exitIcon"
alt="exit-icon" alt="exit-icon"
@click="() => (searchedTrain = '')" @click="() => (searchedTrain = '')"
/> />
</div> </div>
</div> </div>
<div class="train-search_driver option"> <div class="train-search_driver option">
<div class="search-box"> <div class="search-box">
<input <input
class="search-input" class="search-input"
:placeholder="$t('trains.search-driver')" :placeholder="$t('trains.search-driver')"
v-model="searchedDriver" v-model="searchedDriver"
/> />
<img <img
class="search-exit" class="search-exit"
:src="exitIcon" :src="exitIcon"
alt="exit-icon" alt="exit-icon"
@click="() => (searchedDriver = '')" @click="() => (searchedDriver = '')"
/> />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Vue, Watch, Prop } from "vue-property-decorator"; import { Component, Vue, Watch, Prop } from "vue-property-decorator";
@Component @Component
export default class TrainOptions extends Vue { export default class TrainOptions extends Vue {
ascIcon = require("@/assets/icon-arrow-asc.svg"); ascIcon = require("@/assets/icon-arrow-asc.svg");
descIcon = require("@/assets/icon-arrow-desc.svg"); descIcon = require("@/assets/icon-arrow-desc.svg");
exitIcon = require("@/assets/icon-exit.svg"); exitIcon = require("@/assets/icon-exit.svg");
clickEventListener!: EventListener; clickEventListener!: EventListener;
sorterOptionsOpen = false; sorterOptionsOpen = false;
currentSorterOption = this.$t("trains.option-distance"); currentSorterOption = this.$t("trains.option-distance");
searchedTrain = ""; searchedTrain = "";
searchedDriver = ""; searchedDriver = "";
// Passed as component parameters // Passed as component parameters
@Prop() readonly queryTrain!: string; @Prop() readonly queryTrain!: string;
@Prop() readonly focusedTrain!: string; @Prop() readonly focusedTrain!: string;
mounted() { mounted() {
if (this.queryTrain) { if (this.queryTrain) {
this.searchedTrain = this.queryTrain; this.searchedTrain = this.queryTrain;
this.searchedDriver = ""; this.searchedDriver = "";
} }
} }
sorterOptions: { id: string; content: string }[] = [ sorterOptions: { id: string; content: string }[] = [
{ {
id: "mass", id: "mass",
content: "masa", content: "masa",
}, },
{ {
id: "speed", id: "speed",
content: "prędkość", content: "prędkość",
}, },
{ {
id: "length", id: "length",
content: "długość", content: "długość",
}, },
{ {
id: "distance", id: "distance",
content: "kilometraż", content: "kilometraż",
}, },
{ {
id: "timetable", id: "timetable",
content: "numer pociągu", content: "numer pociągu",
}, },
]; ];
toggleSorterOptions() { toggleSorterOptions() {
this.sorterOptionsOpen = !this.sorterOptionsOpen; this.sorterOptionsOpen = !this.sorterOptionsOpen;
} }
closeSorterOptions() { closeSorterOptions() {
this.sorterOptionsOpen = false; this.sorterOptionsOpen = false;
} }
chooseOption(option: { id: string; content: string }) { chooseOption(option: { id: string; content: string }) {
this.$emit("changeSorter", { id: option.id, dir: -1 }); this.$emit("changeSorter", { id: option.id, dir: -1 });
this.currentSorterOption = this.$t(`trains.option-${option.id}`); this.currentSorterOption = this.$t(`trains.option-${option.id}`);
this.closeSorterOptions(); this.closeSorterOptions();
} }
@Watch("searchedTrain") @Watch("searchedTrain")
onSearchedTrainChanged(train: string) { onSearchedTrainChanged(train: string) {
this.$emit("changeSearchedTrain", train); this.$emit("changeSearchedTrain", train);
} }
@Watch("searchedDriver") @Watch("searchedDriver")
onSearchedDriverChanged(driver: string) { onSearchedDriverChanged(driver: string) {
this.$emit("changeSearchedDriver", driver); this.$emit("changeSearchedDriver", driver);
} }
@Watch("queryTrain") @Watch("queryTrain")
onQueryTrainChanged(train: string) { onQueryTrainChanged(train: string) {
if (train && train != "") { if (train && train != "") {
this.searchedTrain = train; this.searchedTrain = train;
this.searchedDriver = ""; this.searchedDriver = "";
} }
} }
@Watch("focusedTrain") @Watch("focusedTrain")
onFocusedTrainChanged(train: string) { onFocusedTrainChanged(train: string) {
this.searchedTrain = train; this.searchedTrain = train;
this.searchedDriver = ""; this.searchedDriver = "";
} }
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "../../styles/responsive"; @import "../../styles/responsive";
.train-options { .train-options {
@include smallScreen() { @include smallScreen() {
width: 100%; width: 100%;
} }
} }
.options_wrapper { .options_wrapper {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
margin-bottom: 0.5em; margin-bottom: 0.5em;
} }
.option { .option {
background: #333; background: #333;
border-radius: 0.5em 0.5em 0 0; border-radius: 0.5em 0.5em 0 0;
margin-right: 0.35em; margin-right: 0.35em;
@include smallScreen() { @include smallScreen() {
width: 100%; width: 100%;
margin: 0.35em 0; margin: 0.35em 0;
} }
} }
.train-sorter { .train-sorter {
user-select: none; user-select: none;
-moz-user-select: none; -moz-user-select: none;
-webkit-user-select: none; -webkit-user-select: none;
&_options { &_options {
position: relative; position: relative;
ul { ul {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
width: 100%; width: 100%;
transition: all 150ms ease-in; transition: all 150ms ease-in;
z-index: 9; z-index: 9;
overflow: hidden; overflow: hidden;
max-height: 0; max-height: 0;
&.open { &.open {
max-height: 250px; max-height: 250px;
opacity: 1; opacity: 1;
} }
li { li {
display: flex; display: flex;
transition: background 150ms ease-in; transition: background 150ms ease-in;
background-color: rgba(#222, 0.95); background-color: rgba(#222, 0.95);
&:last-child { &:last-child {
border-radius: 0 0 0.5em 0.5em; border-radius: 0 0 0.5em 0.5em;
} }
&:hover { &:hover {
background-color: rgba(#868686, 0.85); background-color: rgba(#868686, 0.85);
} }
input { input {
display: none; display: none;
} }
label { label {
padding: 0.5em 1em; padding: 0.5em 1em;
width: 100%; width: 100%;
cursor: pointer; cursor: pointer;
} }
} }
} }
} }
&_selected { &_selected {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 0.5em 0.5em; padding: 0.5em 0.5em;
min-width: 200px; min-width: 200px;
cursor: pointer; cursor: pointer;
span { span {
margin-right: 1em; margin-right: 1em;
} }
img { img {
max-width: 2em; max-width: 2em;
} }
} }
} }
.search { .search {
&-box { &-box {
position: relative; position: relative;
background: #333; background: #333;
border-radius: 0.5em; border-radius: 0.5em;
min-width: 200px; min-width: 200px;
} }
&-input { &-input {
border: none; border: none;
padding: 0.5em 0.5em; padding: 0.5em 0.5em;
margin: 0; margin: 0;
min-width: 85%; min-width: 85%;
} }
&-exit { &-exit {
position: absolute; position: absolute;
cursor: pointer; cursor: pointer;
top: 50%; top: 50%;
right: 10px; right: 10px;
transform: translateY(-50%); transform: translateY(-50%);
width: 1em; width: 1em;
} }
} }
</style> </style>
+2147 -2147
View File
File diff suppressed because it is too large Load Diff
+134 -134
View File
@@ -1,134 +1,134 @@
{ {
"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"
}, },
"desc": { "desc": {
"control-type": "Control type: ", "control-type": "Control type: ",
"signals-type": "Signals type: ", "signals-type": "Signals type: ",
"SBL": "This scenery has automatic line blockade system on following routes: ", "SBL": "This scenery has automatic line blockade system 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",
"unavailable": "This scenery is unavailable", "unavailable": "This scenery is unavailable",
"real": "This scenery is real" "real": "This scenery is real"
}, },
"signals": { "signals": {
"współczesna": "modern", "współczesna": "modern",
"mieszana": "mixed", "mieszana": "mixed",
"kształtowa": "mechanical", "kształtowa": "mechanical",
"historyczna": "historyczna" "historyczna": "historyczna"
}, },
"controls": { "controls": {
"SPK": "SPK", "SPK": "SPK",
"SCS": "SCS", "SCS": "SCS",
"SCS-SPK": "SCS/SPK", "SCS-SPK": "SCS/SPK",
"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"
}, },
"options": { "options": {
"filters": "FILTERS", "filters": "FILTERS",
"donate": "DONATE" "donate": "DONATE"
}, },
"filters": { "filters": {
"title": "STATION FILTER", "title": "STATION FILTER",
"default": "DEFAULT", "default": "DEFAULT",
"not-default": "OTHER", "not-default": "OTHER",
"real": "REAL", "real": "REAL",
"fictional": "FICTIONAL", "fictional": "FICTIONAL",
"SPK": "SPK", "SPK": "SPK",
"SCS": "SCS", "SCS": "SCS",
"manual": "MANUAL", "manual": "MANUAL",
"mechanical": "MECHANICAL", "mechanical": "MECHANICAL",
"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": "MINIMUM REQUIRED DISPATCHER LEVEL", "min-lvl": "MINIMUM REQUIRED DISPATCHER LEVEL",
"routes-1t-cat": "MINIMUM CATENARY SINGLE TRACK ROUTES", "routes-1t-cat": "MINIMUM CATENARY SINGLE TRACK ROUTES",
"routes-1t-other": "MINIMUM OTHER SINGLE TRACK ROUTES", "routes-1t-other": "MINIMUM OTHER SINGLE TRACK ROUTES",
"routes-2t-cat": "MINIMUM CATENARY DOUBLE TRACK ROUTES", "routes-2t-cat": "MINIMUM CATENARY DOUBLE TRACK ROUTES",
"routes-2t-other": "MINIMUM OTHER DOUBLE TRACK ROUTES" "routes-2t-other": "MINIMUM OTHER DOUBLE TRACK ROUTES"
}, },
"save": "SAVE FILTERS", "save": "SAVE FILTERS",
"reset": "RESET FILTERS", "reset": "RESET FILTERS",
"close": "CLOSE FILTERS" "close": "CLOSE FILTERS"
}, },
"sceneries": { "sceneries": {
"station": "Station", "station": "Station",
"min-lvl": "Min. dispatcher <br> level", "min-lvl": "Min. dispatcher <br> level",
"status": "Status", "status": "Status",
"dispatcher": "Dispatcher", "dispatcher": "Dispatcher",
"dispatcher-lvl": "Dispatcher <br> level", "dispatcher-lvl": "Dispatcher <br> level",
"routes": "Routes <br> double | single", "routes": "Routes <br> double | single",
"general": "General info", "general": "General info",
"users": "Drivers online", "users": "Drivers online",
"spawns": "Spawns online", "spawns": "Spawns online",
"timetables": "Active timetables", "timetables": "Active timetables",
"no-stations": "No stations to show here!" "no-stations": "No stations to show here!"
}, },
"trains": { "trains": {
"no-trains": "Oops! No trains online!", "no-trains": "Oops! No trains online!",
"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",
"option-mass": "mass", "option-mass": "mass",
"option-speed": "speed", "option-speed": "speed",
"option-length": "length", "option-length": "length",
"option-distance": "distance", "option-distance": "distance",
"option-timetable": "train no.", "option-timetable": "train no.",
"search-no": "Search for train no...", "search-no": "Search for train no...",
"search-driver": "Search for driver...", "search-driver": "Search for driver...",
"detailed-timetable": "Detailed timetable for train no. ", "detailed-timetable": "Detailed timetable for train no. ",
"via-title": "Via: " "via-title": "Via: "
}, },
"journal": { "journal": {
"title": "SCENERY ACTIVITY JOURNAL", "title": "SCENERY ACTIVITY JOURNAL",
"subtitle": "Shows all recent dispatchers on a selected scenery", "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!", "disclaimer": "<b>This functionality is unfinished!</b> <br> Information shown here could be false or incorrect!",
"select": "Select a scenery" "select": "Select a scenery"
}, },
"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!",
"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 or is offline!", "no-scenery": "Oops! This scenery doesn't exist or is offline!",
"return-btn": "Return to main site" "return-btn": "Return to main site"
}, },
"timetables": { "timetables": {
"online": "At station", "online": "At station",
"departed": "Dispatched", "departed": "Dispatched",
"departed-away": "Departed", "departed-away": "Departed",
"arriving": "En route", "arriving": "En route",
"stopped": "Stopped", "stopped": "Stopped",
"terminated": "Terminated", "terminated": "Terminated",
"begins": "BEGINS HERE", "begins": "BEGINS HERE",
"terminates": "TERMINATES <br /> HERE" "terminates": "TERMINATES <br /> HERE"
} }
} }
+134 -134
View File
@@ -1,134 +1,134 @@
{ {
"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" "support": "Wspomóż projekt"
}, },
"desc": { "desc": {
"control-type": "Sterowanie: ", "control-type": "Sterowanie: ",
"signals-type": "Sygnalizacja: ", "signals-type": "Sygnalizacja: ",
"SBL": "Sceneria posiada SBL na szlakach: ", "SBL": "Sceneria posiada SBL na szlakach: ",
"default": "Sceneria dostępna domyślnie w paczce z grą", "default": "Sceneria dostępna domyślnie w paczce z grą",
"non-public": "Sceneria niepubliczna", "non-public": "Sceneria niepubliczna",
"unavailable": "Sceneria niedostępna", "unavailable": "Sceneria niedostępna",
"real": "Sceneria realna" "real": "Sceneria realna"
}, },
"signals": { "signals": {
"współczesna": "współczesna", "współczesna": "współczesna",
"mieszana": "mieszana", "mieszana": "mieszana",
"kształtowa": "kształtowa", "kształtowa": "kształtowa",
"historyczna": "historyczna" "historyczna": "historyczna"
}, },
"controls": { "controls": {
"SPK": "SPK", "SPK": "SPK",
"SCS": "SCS", "SCS": "SCS",
"SCS-SPK": "SCS/SPK", "SCS-SPK": "SCS/SPK",
"ręczne": "ręczne", "ręczne": "ręczne",
"ręczne+SPK": "ręczne + SPK", "ręczne+SPK": "ręczne + SPK",
"ręczne+SCS": "ręczne + SCS", "ręczne+SCS": "ręczne + SCS",
"mechaniczne": "mechaniczne", "mechaniczne": "mechaniczne",
"mechaniczne+SPK": "mechaniczne + SPK", "mechaniczne+SPK": "mechaniczne + SPK",
"mechaniczne+SCS": "mechaniczne + SCS" "mechaniczne+SCS": "mechaniczne + SCS"
}, },
"status": { "status": {
"online": "DO ", "online": "DO ",
"free": "WOLNA", "free": "WOLNA",
"ending": "KOŃCZY", "ending": "KOŃCZY",
"not-signed": "NIEZALOGOWANY", "not-signed": "NIEZALOGOWANY",
"no-limit": "BEZ LIMITU", "no-limit": "BEZ LIMITU",
"unavailable": "NIEDOSTĘPNY", "unavailable": "NIEDOSTĘPNY",
"brb": "Z/W", "brb": "Z/W",
"no-space": "BRAK MIEJSCA" "no-space": "BRAK MIEJSCA"
}, },
"options": { "options": {
"filters": "FILTRY", "filters": "FILTRY",
"donate": "WESPRZYJ" "donate": "WESPRZYJ"
}, },
"filters": { "filters": {
"title": "FILTRUJ STACJE", "title": "FILTRUJ STACJE",
"default": "DOMYŚLNA", "default": "DOMYŚLNA",
"not-default": "POZA PACZKĄ", "not-default": "POZA PACZKĄ",
"real": "REALNA", "real": "REALNA",
"fictional": "FIKCYJNA", "fictional": "FIKCYJNA",
"SPK": "SPK", "SPK": "SPK",
"SCS": "SCS", "SCS": "SCS",
"manual": "RĘCZNE", "manual": "RĘCZNE",
"mechanical": "MECHANICZNE", "mechanical": "MECHANICZNE",
"modern": "WSPÓŁCZESNA", "modern": "WSPÓŁCZESNA",
"semaphores": "KSZTAŁTOWA", "semaphores": "KSZTAŁTOWA",
"mixed": "MIESZANA", "mixed": "MIESZANA",
"historical": "HISTORYCZNA", "historical": "HISTORYCZNA",
"free": "WOLNA", "free": "WOLNA",
"occupied": "ZAJĘTA", "occupied": "ZAJĘTA",
"sliders": { "sliders": {
"min-lvl": "MINIMALNY WYMAGANY POZIOM DYŻURNEGO", "min-lvl": "MINIMALNY WYMAGANY POZIOM DYŻURNEGO",
"routes-1t-cat": "SZLAKI JEDNOTOROWE ZELEKTR. (MINIMUM)", "routes-1t-cat": "SZLAKI JEDNOTOROWE ZELEKTR. (MINIMUM)",
"routes-1t-other": "SZLAKI JEDNOTOROWE NIEZELEKTR. (MINIMUM)", "routes-1t-other": "SZLAKI JEDNOTOROWE NIEZELEKTR. (MINIMUM)",
"routes-2t-cat": "SZLAKI DWUTOROWE ZELEKTR. (MINIMUM)", "routes-2t-cat": "SZLAKI DWUTOROWE ZELEKTR. (MINIMUM)",
"routes-2t-other": "SZLAKI DWUTOROWE NIEZELEKTR. (MINIMUM)" "routes-2t-other": "SZLAKI DWUTOROWE NIEZELEKTR. (MINIMUM)"
}, },
"save": "ZAPISZ FILTRY", "save": "ZAPISZ FILTRY",
"reset": "RESETUJ FILTRY", "reset": "RESETUJ FILTRY",
"close": "ZAMKNIJ FILTRY" "close": "ZAMKNIJ FILTRY"
}, },
"sceneries": { "sceneries": {
"station": "Stacja", "station": "Stacja",
"min-lvl": "Min. poziom <br/> dyżurnego", "min-lvl": "Min. poziom <br/> dyżurnego",
"status": "Status", "status": "Status",
"dispatcher": "Dyżurny", "dispatcher": "Dyżurny",
"dispatcher-lvl": "Poziom <br> dyżurnego", "dispatcher-lvl": "Poziom <br> dyżurnego",
"routes": "Szlaki <br> 2tor | 1tor", "routes": "Szlaki <br> 2tor | 1tor",
"general": "Informacje <br> ogólne", "general": "Informacje <br> ogólne",
"users": "Maszyniści online", "users": "Maszyniści online",
"spawns": "Otwarte spawny", "spawns": "Otwarte spawny",
"timetables": "Aktywne rozkłady jazdy", "timetables": "Aktywne rozkłady jazdy",
"no-stations": "Brak stacji do wyświetlenia!" "no-stations": "Brak stacji do wyświetlenia!"
}, },
"trains": { "trains": {
"no-trains": "Brak pociągów online!", "no-trains": "Brak pociągów online!",
"stats": "STATYSTYKI RUCHU", "stats": "STATYSTYKI RUCHU",
"stats-speed": "PRĘDKOŚCI POCIĄGÓW (MIN | ŚR | MAX) [km/h]", "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-length": "DŁUGOŚCI ROZKŁADÓW (MIN | ŚR | MAX) [km]",
"stats-categories": "KATEGORIE RJ", "stats-categories": "KATEGORIE RJ",
"stats-special-twr": "WYSOKIEGO RYZYKA", "stats-special-twr": "WYSOKIEGO RYZYKA",
"stats-special-skr": "PRZEKROCZONA SKRAJNIA", "stats-special-skr": "PRZEKROCZONA SKRAJNIA",
"stats-locos": "NAJCZĘSTSZE JEDNOSTKI", "stats-locos": "NAJCZĘSTSZE JEDNOSTKI",
"option-mass": "masa", "option-mass": "masa",
"option-speed": "prędkość", "option-speed": "prędkość",
"option-length": "długość", "option-length": "długość",
"option-distance": "kilometraż", "option-distance": "kilometraż",
"option-timetable": "numer pociagu", "option-timetable": "numer pociagu",
"search-no": "Szukaj nr pociągu...", "search-no": "Szukaj nr pociągu...",
"search-driver": "Szukaj maszynisty...", "search-driver": "Szukaj maszynisty...",
"detailed-timetable": "Szczegółowy rozkład jazdy pociągu ", "detailed-timetable": "Szczegółowy rozkład jazdy pociągu ",
"via-title": "Przez: " "via-title": "Przez: "
}, },
"journal": { "journal": {
"title": "DZIENNIK AKTYWNOŚCI SCENERII", "title": "DZIENNIK AKTYWNOŚCI SCENERII",
"subtitle": "Pokazuje dyżurnych, którzy ostatnio byli aktywni na wybranej 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!", "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ę" "select": "Wybierz scenerię"
}, },
"scenery": { "scenery": {
"users": "GRACZE ONLINE", "users": "GRACZE ONLINE",
"spawns": "OTWARTE SPAWNY", "spawns": "OTWARTE SPAWNY",
"timetables": "AKTYWNE ROZKŁADY JAZDY", "timetables": "AKTYWNE ROZKŁADY JAZDY",
"no-timetables": "Brak aktywnych rozkładów!", "no-timetables": "Brak aktywnych rozkładów!",
"no-users": "BRAK AKTYWNYCH GRACZY", "no-users": "BRAK AKTYWNYCH GRACZY",
"no-spawns": "BRAK OTWARTYCH SPAWNÓW", "no-spawns": "BRAK OTWARTYCH SPAWNÓW",
"no-scenery": "Ups! Nie znaleziono danej stacji bądź jest ona offline!", "no-scenery": "Ups! Nie znaleziono danej stacji bądź jest ona offline!",
"return-btn": "Wróć na stronę główną" "return-btn": "Wróć na stronę główną"
}, },
"timetables": { "timetables": {
"online": "Na stacji", "online": "Na stacji",
"departed": "Odprawiony", "departed": "Odprawiony",
"departed-away": "Odjechał", "departed-away": "Odjechał",
"arriving": "W drodze", "arriving": "W drodze",
"stopped": "Postój", "stopped": "Postój",
"terminated": "Skończył bieg", "terminated": "Skończył bieg",
"begins": "ROZPOCZYNA <br /> BIEG", "begins": "ROZPOCZYNA <br /> BIEG",
"terminates": "KOŃCZY BIEG" "terminates": "KOŃCZY BIEG"
} }
} }
+33 -33
View File
@@ -1,33 +1,33 @@
interface ISceneryInfoData { interface ISceneryInfoData {
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; dispatcherFrom: number; dispatcherTo: number }[];
} }
export default ISceneryInfoData; 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 { Module, VuexModule, Mutation, Action } from "vuex-module-decorators";
import axios from 'axios'; import axios from "axios";
import JSONStationData from '@/data/stationData.json'; import JSONStationData from "@/data/stationData.json";
import Station from '@/scripts/interfaces/Station'; import Station from "@/scripts/interfaces/Station";
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 utils from "@/scripts/utils/storeUtils";
enum Status { enum Status {
Initialized = -1, Initialized = -1,
Loading = 0, Loading = 0,
Error = 1, Error = 1,
Loaded = 2, Loaded = 2
} }
interface ITimetableData { interface TimetableData {
trainNo: number; trainNo: number;
driverName: string; driverName: string;
driverId: number; driverId: number;
@@ -50,87 +52,11 @@ interface IOnlineStationData {
} }
const URLs = { const URLs = {
stations: 'https://api.td2.info.pl:9640/?method=getStationsOnline', stations: "https://api.td2.info.pl:9640/?method=getStationsOnline",
trains: 'https://api.td2.info.pl:9640/?method=getTrainsOnline', trains: "https://api.td2.info.pl:9640/?method=getTrainsOnline",
dispatchers: 'https://api.td2.info.pl:9640/?method=readFromSWDR&value=getDispatcherStatusList%3B1', 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 @Module
export default class Store extends VuexModule { export default class Store extends VuexModule {
private trainCount: number = 0; private trainCount: number = 0;
@@ -151,7 +77,7 @@ export default class Store extends VuexModule {
trainCount: this.trainCount, trainCount: this.trainCount,
stationCount: this.stationCount, stationCount: this.stationCount,
dataConnectionStatus: this.dataConnectionStatus, dataConnectionStatus: this.dataConnectionStatus,
timetableDataStatus: this.timetableLoaded, timetableDataStatus: this.timetableLoaded
}; };
} }
@@ -177,27 +103,27 @@ export default class Store extends VuexModule {
//ACTIONS //ACTIONS
@Action @Action
async synchronizeData() { async synchronizeData() {
this.context.commit('setSceneryData'); this.context.commit("setSceneryData");
this.context.commit('setSceneryDataStatus', Status.Loaded); this.context.commit("setSceneryDataStatus", Status.Loaded);
this.context.dispatch('fetchOnlineData'); this.context.dispatch("fetchOnlineData");
setInterval(() => this.context.dispatch('fetchOnlineData'), 20000); setInterval(() => this.context.dispatch("fetchOnlineData"), 20000);
} }
@Action({ commit: 'updateTimetableData' }) @Action({ commit: "updateTimetableData" })
async fetchTimetableData() { async fetchTimetableData() {
return this.trainList.reduce(async (acc: Promise<ITimetableData[]>, train) => { return this.trainList.reduce(async (acc: Promise<TimetableData[]>, train) => {
const timetable = await (await axios.get(timetableURL(train.trainNo))).data.message; const timetable = await (await axios.get(utils.timetableURL(train.trainNo))).data.message;
const trainInfo = timetable.trainInfo; const trainInfo = timetable.trainInfo;
if (!timetable || !trainInfo) return acc; if (!timetable || !trainInfo) return acc;
const followingStops: TrainStop[] = timetable.stopPoints.reduce((stopsAcc: TrainStop[], point) => { const followingStops: TrainStop[] = timetable.stopPoints.reduce((stopsAcc: TrainStop[], point) => {
const arrivalTimestamp = getTimestamp(point.arrivalTime); const arrivalTimestamp = utils.getTimestamp(point.arrivalTime);
const arrivalRealTimestamp = getTimestamp(point.arrivalRealTime); const arrivalRealTimestamp = utils.getTimestamp(point.arrivalRealTime);
const departureTimestamp = getTimestamp(point.departureTime); const departureTimestamp = utils.getTimestamp(point.departureTime);
const departureRealTimestamp = getTimestamp(point.departureRealTime); const departureRealTimestamp = utils.getTimestamp(point.departureRealTime);
stopsAcc.push({ stopsAcc.push({
stopName: point.pointName, stopName: point.pointName,
@@ -205,19 +131,19 @@ export default class Store extends VuexModule {
stopType: point.pointStopType, stopType: point.pointStopType,
stopDistance: point.pointDistance, stopDistance: point.pointDistance,
mainStop: point.pointName.includes('strong'), mainStop: point.pointName.includes("strong"),
arrivalLine: point.arrivalLine, arrivalLine: point.arrivalLine,
arrivalTimeString: timestampToString(point.arrivalTime), arrivalTimeString: utils.timestampToString(point.arrivalTime),
arrivalTimestamp: arrivalTimestamp, arrivalTimestamp: arrivalTimestamp,
arrivalRealTimeString: timestampToString(point.arrivalRealTime), arrivalRealTimeString: utils.timestampToString(point.arrivalRealTime),
arrivalRealTimestamp: arrivalRealTimestamp, arrivalRealTimestamp: arrivalRealTimestamp,
arrivalDelay: point.arrivalDelay, arrivalDelay: point.arrivalDelay,
departureLine: point.departureLine, departureLine: point.departureLine,
departureTimeString: timestampToString(point.departureTime), departureTimeString: utils.timestampToString(point.departureTime),
departureTimestamp: departureTimestamp, departureTimestamp: departureTimestamp,
departureRealTimeString: timestampToString(point.departureRealTime), departureRealTimeString: utils.timestampToString(point.departureRealTime),
departureRealTimestamp: departureRealTimestamp, departureRealTimestamp: departureRealTimestamp,
departureDelay: point.departureDelay, departureDelay: point.departureDelay,
@@ -226,7 +152,7 @@ export default class Store extends VuexModule {
confirmed: point.confirmed, confirmed: point.confirmed,
stopped: point.isStopped, stopped: point.isStopped,
stopTime: point.pointStopTime, stopTime: point.pointStopTime
}); });
return stopsAcc; return stopsAcc;
@@ -245,7 +171,7 @@ export default class Store extends VuexModule {
SKR: trainInfo.skr, SKR: trainInfo.skr,
routeDistance: timetable.stopPoints[timetable.stopPoints.length - 1].pointDistance, routeDistance: timetable.stopPoints[timetable.stopPoints.length - 1].pointDistance,
followingStops, followingStops,
followingSceneries: trainInfo.sceneries, followingSceneries: trainInfo.sceneries
}); });
return acc; return acc;
@@ -261,15 +187,15 @@ export default class Store extends VuexModule {
const onlineDispatchersData = await response[2].data.message; const onlineDispatchersData = await response[2].data.message;
let updatedStationList = onlineStationsData.reduce((acc, station) => { 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 statusTimestamp = utils.getStatusTimestamp(stationStatus);
const statusID = getStatusID(stationStatus); const statusID = utils.getStatusID(stationStatus);
const stationTrains = onlineTrainsData.filter( 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({ acc.push({
@@ -277,7 +203,7 @@ export default class Store extends VuexModule {
stationHash: station.stationHash, stationHash: station.stationHash,
maxUsers: station.maxUsers, maxUsers: station.maxUsers,
currentUsers: station.currentUsers, currentUsers: station.currentUsers,
spawns: parseSpawns(station.spawnString), spawns: utils.parseSpawns(station.spawnString),
dispatcherName: station.dispatcherName, dispatcherName: station.dispatcherName,
dispatcherRate: station.dispatcherRate, dispatcherRate: station.dispatcherRate,
dispatcherId: station.dispatcherId, dispatcherId: station.dispatcherId,
@@ -286,7 +212,7 @@ export default class Store extends VuexModule {
stationTrains, stationTrains,
statusTimestamp, statusTimestamp,
statusID, statusID,
statusTimeString: timestampToString(statusTimestamp), statusTimeString: utils.timestampToString(statusTimestamp)
}); });
return acc; return acc;
@@ -294,9 +220,9 @@ export default class Store extends VuexModule {
let updatedTrainList = await Promise.all( let updatedTrainList = await Promise.all(
onlineTrainsData onlineTrainsData
.filter(train => train.region === 'eu') .filter(train => train.region === "eu")
.map(async train => { .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 { return {
trainNo: train.trainNo, trainNo: train.trainNo,
@@ -312,18 +238,18 @@ export default class Store extends VuexModule {
currentStationHash: train.station.stationHash, currentStationHash: train.station.stationHash,
connectedTrack: train.dataSceneryConnection, connectedTrack: train.dataSceneryConnection,
locoType, locoType,
locoURL: getLocoURL(locoType), locoURL: utils.getLocoURL(locoType)
}; };
}) })
); );
this.context.commit('updateOnlineStations', updatedStationList); this.context.commit("updateOnlineStations", updatedStationList);
this.context.commit('updateOnlineTrains', updatedTrainList); this.context.commit("updateOnlineTrains", updatedTrainList);
this.context.dispatch('fetchTimetableData'); this.context.dispatch("fetchTimetableData");
}) })
.catch(err => { .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, stationLines: station[2] as string,
stationProject: station[3] as string, stationProject: station[3] as string,
reqLevel: station[4] as string, reqLevel: station[4] as string,
supportersOnly: station[5] == 'TAK', supportersOnly: station[5] == "TAK",
signalType: station[6] as string, signalType: station[6] as string,
controlType: station[7] as string, controlType: station[7] as string,
SBL: station[8] as string, SBL: station[8] as string,
@@ -376,12 +302,12 @@ export default class Store extends VuexModule {
routes: { routes: {
oneWay: { oneWay: {
catenary: station[10] as number, catenary: station[10] as number,
noCatenary: station[11] as number, noCatenary: station[11] as number
}, },
twoWay: { twoWay: {
catenary: station[12] as number, 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, checkpoints: station[14] ? (station[14] as string[]).map(sub => ({ checkpointName: sub, scheduledTrains: [] })) : null,
stops: station[15] as string[], stops: station[15] as string[],
@@ -390,21 +316,21 @@ export default class Store extends VuexModule {
nonPublic: station[17] as boolean, nonPublic: station[17] as boolean,
unavailable: station[18] as boolean, unavailable: station[18] as boolean,
stationHash: '', stationHash: "",
maxUsers: 0, maxUsers: 0,
currentUsers: 0, currentUsers: 0,
dispatcherName: '', dispatcherName: "",
dispatcherRate: 0, dispatcherRate: 0,
dispatcherExp: -1, dispatcherExp: -1,
dispatcherId: 0, dispatcherId: 0,
dispatcherIsSupporter: false, dispatcherIsSupporter: false,
online: false, online: false,
statusTimestamp: -3, statusTimestamp: -3,
statusID: 'free', statusID: "free",
statusTimeString: '', statusTimeString: "",
stationTrains: [], stationTrains: [],
scheduledTrains: [], scheduledTrains: [],
spawns: [], spawns: []
})); }));
} }
@@ -418,27 +344,27 @@ export default class Store extends VuexModule {
acc.push({ acc.push({
...station, ...station,
...onlineStationData, ...onlineStationData,
online: true, online: true
}); });
else if (registeredStation) else if (registeredStation)
acc.push({ acc.push({
...station, ...station,
stationProject: '', stationProject: "",
stationHash: '', stationHash: "",
maxUsers: 0, maxUsers: 0,
currentUsers: 0, currentUsers: 0,
dispatcherName: '', dispatcherName: "",
dispatcherRate: 0, dispatcherRate: 0,
dispatcherExp: -1, dispatcherExp: -1,
dispatcherId: 0, dispatcherId: 0,
dispatcherIsSupporter: false, dispatcherIsSupporter: false,
online: false, online: false,
statusID: 'free', statusID: "free",
statusTimestamp: -3, statusTimestamp: -3,
statusTimeString: '', statusTimeString: "",
stationTrains: [], stationTrains: [],
scheduledTrains: [], scheduledTrains: [],
checkpoints: null, checkpoints: null
}); });
return acc; return acc;
@@ -453,8 +379,8 @@ export default class Store extends VuexModule {
stationTrains: [], stationTrains: [],
subStations: [], subStations: [],
online: true, online: true,
reqLevel: '-1', reqLevel: "-1",
nonPublic: true, nonPublic: true
}); });
}); });
@@ -478,153 +404,77 @@ export default class Store extends VuexModule {
} }
@Mutation @Mutation
private updateTimetableData(timetableList: ITimetableData[]) { private updateTimetableData(timetableList: TimetableData[]) {
this.stationList = this.stationList.map(station => { this.stationList = this.stationList.map(station => {
const stationName = station.stationName.toLowerCase(); 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 scheduledTrains: Station["scheduledTrains"] = timetableList.reduce((acc: Station["scheduledTrains"], timetable: TimetableData, index) => {
const stopName = stop.stopNameRAW.toLowerCase(); if (!timetable.followingSceneries.includes(station.stationHash)) return acc;
if (stationName === stopName) return true; const stopInfoIndex = timetable.followingStops.findIndex(stop => {
if (stopName.includes(stationName) && !stop.stopName.includes('po.') && !stop.stopName.includes('podg.')) return true; const stopName = stop.stopNameRAW.toLowerCase();
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;
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 = ''; const trainStop = timetable.followingStops[stopInfoIndex];
let stopLabel = ''; const trainStopStatus = utils.getTrainStopStatus(trainStop, timetable, station);
let stopStatusID = 0;
let nearestStop = '';
if (stopInfo.terminatesHere && stopInfo.confirmed) { acc.push({
stopStatus = 'terminated'; trainNo: timetable.trainNo,
stopLabel = 'Skończył bieg'; driverName: timetable.driverName,
stopStatusID = 5; driverId: timetable.driverId,
} else if (!stopInfo.terminatesHere && stopInfo.confirmed && timetableData.currentStationName == station.stationName) { currentStationName: timetable.currentStationName,
stopStatus = 'departed'; currentStationHash: timetable.currentStationHash,
stopLabel = 'Odprawiony'; category: timetable.category,
stopStatusID = 2; beginsAt: timetable.followingStops[0].stopNameRAW,
} else if (!stopInfo.terminatesHere && stopInfo.confirmed && timetableData.currentStationName != station.stationName) { terminatesAt: timetable.followingStops[timetable.followingStops.length - 1].stopNameRAW,
stopStatus = 'departed-away'; nearestStop: "",
stopLabel = 'Odjechał'; stopInfo: trainStop,
stopStatusID = 4; stopLabel: trainStopStatus.stopLabel,
} else if (timetableData.currentStationName == station.stationName && !stopInfo.stopped) { stopStatus: trainStopStatus.stopStatus,
stopStatus = 'online'; stopStatusID: trainStopStatus.stopStatusID
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;
}
if (stopInfoIndex < timetableData.followingStops.length - 2) { return acc;
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;
},
[]
);
if (station.checkpoints) { if (station.checkpoints) {
station.checkpoints.forEach(cp => (cp.scheduledTrains.length = 0)); station.checkpoints.forEach(cp => (cp.scheduledTrains.length = 0));
for (let checkpoint of station.checkpoints) { for (let checkpoint of station.checkpoints) {
timetableList.reduce((acc, data) => { timetableList.forEach(timetable => {
data.followingStops timetable.followingStops
.filter(stop => stop.stopNameRAW.toLowerCase() === checkpoint.checkpointName.toLowerCase()) .filter(trainStop => trainStop.stopNameRAW.toLowerCase() === checkpoint.checkpointName.toLowerCase())
.forEach(stopInfo => { .forEach(trainStop => {
// const stopInfo = data.followingStops[stopInfoIndex]; const trainStopStatus = utils.getTrainStopStatus(trainStop, timetable, station);
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;
}
checkpoint.scheduledTrains.push({ checkpoint.scheduledTrains.push({
trainNo: data.trainNo, trainNo: timetable.trainNo,
driverName: data.driverName, driverName: timetable.driverName,
driverId: data.driverId, driverId: timetable.driverId,
currentStationName: data.currentStationName, currentStationName: timetable.currentStationName,
currentStationHash: data.currentStationHash, currentStationHash: timetable.currentStationHash,
category: data.category, category: timetable.category,
beginsAt: data.followingStops[0].stopNameRAW, beginsAt: timetable.followingStops[0].stopNameRAW,
terminatesAt: data.followingStops[data.followingStops.length - 1].stopNameRAW, terminatesAt: timetable.followingStops[timetable.followingStops.length - 1].stopNameRAW,
stopInfo, nearestStop: "",
stopLabel, stopInfo: trainStop,
stopStatus, stopLabel: trainStopStatus.stopLabel,
nearestStop, stopStatus: trainStopStatus.stopStatus,
stopStatusID, stopStatusID: trainStopStatus.stopStatusID
}); });
}); });
});
return acc;
}, []);
} }
} }
@@ -639,7 +489,7 @@ export default class Store extends VuexModule {
.find(station => station.stationName === train.currentStationName) .find(station => station.stationName === train.currentStationName)
?.scheduledTrains.find(stationTrain => stationTrain.trainNo === train.trainNo); ?.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; return acc;
+46 -46
View File
@@ -1,47 +1,47 @@
@import './variables.scss'; @import './variables.scss';
.card { .card {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
position: fixed; position: fixed;
top: 50%; top: 50%;
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
z-index: 4; z-index: 4;
overflow: auto; overflow: auto;
background: $primaryCol; background: $primaryCol;
box-shadow: 0 0 15px 5px #474747; box-shadow: 0 0 15px 5px #474747;
// width: 75%; // width: 75%;
width: 650px; width: 650px;
max-height: 95%; max-height: 95%;
padding: 0.5em 1em; padding: 0.5em 1em;
@include smallScreen { @include smallScreen {
width: 95%; width: 95%;
} }
// @include midScreen { // @include midScreen {
// width: 85%; // width: 85%;
// } // }
&-exit { &-exit {
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
margin: 0.3em 0em; margin: 0.3em 0em;
img { img {
width: 1.6em; width: 1.6em;
} }
cursor: pointer; cursor: pointer;
} }
} }
+347 -347
View File
@@ -1,348 +1,348 @@
<template> <template>
<div class="history_view"> <div class="history_view">
<div class="history_wrapper"> <div class="history_wrapper">
<div class="header"> <div class="header">
<h2>{{ $t("journal.title") }}</h2> <h2>{{ $t("journal.title") }}</h2>
<p style="color: #ccc"> <p style="color: #ccc">
{{ $t("journal.subtitle") }} {{ $t("journal.subtitle") }}
</p> </p>
<div class="search-box"> <div class="search-box">
<div class="search-box_content"> <div class="search-box_content">
<label :class="{ disabled: dataLoading }"> <label :class="{ disabled: dataLoading }">
<select v-model="inputStationName" :disabled="dataLoading"> <select v-model="inputStationName" :disabled="dataLoading">
<option value disabled selected hidden> <option value disabled selected hidden>
{{ dataLoading ? $t("app.loading") : $t("journal.select") }} {{ dataLoading ? $t("app.loading") : $t("journal.select") }}
</option> </option>
<option <option
v-for="station in filteredStationList" v-for="station in filteredStationList"
:key="station" :key="station"
:value="station" :value="station"
> >
{{ station }} {{ station }}
</option> </option>
</select> </select>
</label> </label>
</div> </div>
</div> </div>
<div class="disclaimer" v-html="$t('journal.disclaimer')"></div> <div class="disclaimer" v-html="$t('journal.disclaimer')"></div>
</div> </div>
<div class="list"> <div class="list">
<div class="list_wrapper"> <div class="list_wrapper">
<!-- <div class="list_loading" v-if="dataLoading">POBIERANIE DANYCH...</div> --> <!-- <div class="list_loading" v-if="dataLoading">POBIERANIE DANYCH...</div> -->
<transition name="list-anim" mode="out-in"> <transition name="list-anim" mode="out-in">
<ul <ul
class="list_content" class="list_content"
v-if=" v-if="
!dataLoading && !dataLoading &&
!historyLoading && !historyLoading &&
computedHistoryList.length != 0 computedHistoryList.length != 0
" "
:key="inputStationName" :key="inputStationName"
> >
<li v-if="currentDispatcherFrom != -1" class="current"> <li v-if="currentDispatcherFrom != -1" class="current">
<div class="dispatcher-name"> <div class="dispatcher-name">
<a <a
:href="`https://td2.info.pl/profile/?u=${currentDispatcherId}`" :href="`https://td2.info.pl/profile/?u=${currentDispatcherId}`"
>{{ currentDispatcher }}</a >{{ currentDispatcher }}</a
> >
</div> </div>
<div class="dispatcher-date"> <div class="dispatcher-date">
<span style="color: #bbb">{{ <span style="color: #bbb">{{
new Date(currentDispatcherFrom).toLocaleDateString("pl-PL") new Date(currentDispatcherFrom).toLocaleDateString("pl-PL")
}}</span> }}</span>
{{ {{
new Date(currentDispatcherFrom).toLocaleTimeString( new Date(currentDispatcherFrom).toLocaleTimeString(
"pl-PL", "pl-PL",
{ hour: "2-digit", minute: "2-digit" } { hour: "2-digit", minute: "2-digit" }
) )
}} }}
</div> </div>
</li> </li>
<li v-for="(history, i) in computedHistoryList" :key="i"> <li v-for="(history, i) in computedHistoryList" :key="i">
<div class="dispatcher-name"> <div class="dispatcher-name">
<a <a
:href="`https://td2.info.pl/profile/?u=${history.dispatcherId}`" :href="`https://td2.info.pl/profile/?u=${history.dispatcherId}`"
>{{ history.dispatcherName }}</a >{{ history.dispatcherName }}</a
> >
</div> </div>
<div class="dispatcher-date"> <div class="dispatcher-date">
<div> <div>
<span style="color: #888">{{ <span style="color: #888">{{
history.dispatcherFromDate history.dispatcherFromDate
}}</span> }}</span>
{{ history.dispatcherFromTime }} {{ history.dispatcherFromTime }}
</div> </div>
<div> <div>
<span style="color: #888">{{ <span style="color: #888">{{
history.dispatcherToDate history.dispatcherToDate
}}</span> }}</span>
{{ history.dispatcherToTime }} {{ history.dispatcherToTime }}
</div> </div>
</div> </div>
</li> </li>
</ul> </ul>
</transition> </transition>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import axios from "axios"; import axios from "axios";
import { Component, Vue, Watch } from "vue-property-decorator"; import { Component, Vue, Watch } from "vue-property-decorator";
import { Getter } from "vuex-class"; import { Getter } from "vuex-class";
import Station from "@/scripts/interfaces/Station"; import Station from "@/scripts/interfaces/Station";
import ISceneryInfoData from "@/scripts/interfaces/ISceneryInfoData"; import ISceneryInfoData from "@/scripts/interfaces/ISceneryInfoData";
@Component @Component
export default class HistoryView extends Vue { export default class HistoryView extends Vue {
@Getter("getStationList") stationList!: Station[]; @Getter("getStationList") stationList!: Station[];
sceneryHistoryList: ISceneryInfoData[] = []; sceneryHistoryList: ISceneryInfoData[] = [];
currentSceneryHistory: ISceneryInfoData["dispatcherHistory"] = []; currentSceneryHistory: ISceneryInfoData["dispatcherHistory"] = [];
currentDispatcher: string = ""; currentDispatcher: string = "";
currentDispatcherId: number = 0; currentDispatcherId: number = 0;
currentDispatcherFrom: number = -1; currentDispatcherFrom: number = -1;
inputStationName = ""; inputStationName = "";
dataLoading = true; /* Initial data */ dataLoading = true; /* Initial data */
historyLoading = false; /* History loaded after input is checked */ historyLoading = false; /* History loaded after input is checked */
async mounted() { async mounted() {
try { try {
const responseData: ISceneryInfoData[] = await ( const responseData: ISceneryInfoData[] = await (
await axios.get( await axios.get(
"https://stacjownik.herokuapp.com/api/getSceneryInfo?items=-1" "https://stacjownik.herokuapp.com/api/getSceneryInfo?items=-1"
) )
).data; ).data;
this.sceneryHistoryList = responseData; this.sceneryHistoryList = responseData;
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
this.dataLoading = false; this.dataLoading = false;
} }
@Watch("inputStationName") @Watch("inputStationName")
onInputChanged(val: string) { onInputChanged(val: string) {
this.itemSelected(val); this.itemSelected(val);
} }
get filteredStationList() { get filteredStationList() {
return this.sceneryHistoryList return this.sceneryHistoryList
.map((station) => station.stationName) .map((station) => station.stationName)
.sort((a, b) => (a.toLowerCase() >= b.toLowerCase() ? 1 : -1)); .sort((a, b) => (a.toLowerCase() >= b.toLowerCase() ? 1 : -1));
} }
get computedHistoryList() { get computedHistoryList() {
return this.currentSceneryHistory return this.currentSceneryHistory
.map( .map(
({ dispatcherName, dispatcherFrom, dispatcherTo, dispatcherId }) => ({ ({ dispatcherName, dispatcherFrom, dispatcherTo, dispatcherId }) => ({
dispatcherName, dispatcherName,
dispatcherFrom, dispatcherFrom,
dispatcherTo, dispatcherTo,
dispatcherId, dispatcherId,
dispatcherFromDate: new Date(dispatcherFrom).toLocaleDateString( dispatcherFromDate: new Date(dispatcherFrom).toLocaleDateString(
"pl-PL" "pl-PL"
), ),
dispatcherFromTime: new Date( dispatcherFromTime: new Date(
dispatcherFrom dispatcherFrom
).toLocaleTimeString("pl-PL", { hour: "2-digit", minute: "2-digit" }), ).toLocaleTimeString("pl-PL", { hour: "2-digit", minute: "2-digit" }),
dispatcherToDate: new Date(dispatcherTo).toLocaleDateString("pl-PL"), dispatcherToDate: new Date(dispatcherTo).toLocaleDateString("pl-PL"),
dispatcherToTime: new Date(dispatcherTo).toLocaleTimeString("pl-PL", { dispatcherToTime: new Date(dispatcherTo).toLocaleTimeString("pl-PL", {
hour: "2-digit", hour: "2-digit",
minute: "2-digit", minute: "2-digit",
}), }),
}) })
) )
.reverse(); .reverse();
} }
async itemSelected(itemName: string) { async itemSelected(itemName: string) {
try { try {
this.historyLoading = true; this.historyLoading = true;
const selectedScenery: ISceneryInfoData = await ( const selectedScenery: ISceneryInfoData = await (
await axios.get( await axios.get(
`https://stacjownik.herokuapp.com/api/getSceneryInfo?name=${itemName}&items=10` `https://stacjownik.herokuapp.com/api/getSceneryInfo?name=${itemName}&items=10`
) )
).data; ).data;
this.currentSceneryHistory = selectedScenery.dispatcherHistory; this.currentSceneryHistory = selectedScenery.dispatcherHistory;
this.currentDispatcher = selectedScenery.currentDispatcher; this.currentDispatcher = selectedScenery.currentDispatcher;
this.currentDispatcherId = selectedScenery.currentDispatcherId; this.currentDispatcherId = selectedScenery.currentDispatcherId;
this.currentDispatcherFrom = selectedScenery.currentDispatcherFrom; this.currentDispatcherFrom = selectedScenery.currentDispatcherFrom;
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
this.historyLoading = false; this.historyLoading = false;
} }
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "../styles/responsive.scss"; @import "../styles/responsive.scss";
.history { .history {
&_view { &_view {
font-size: 1.2em; font-size: 1.2em;
} }
&_wrapper { &_wrapper {
width: 100%; width: 100%;
height: 100%; height: 100%;
text-align: center; text-align: center;
margin-top: 0.5em; margin-top: 0.5em;
} }
} }
.list-anim { .list-anim {
&-enter-active, &-enter-active,
&-leave-active { &-leave-active {
transition: all 150ms ease-out; transition: all 150ms ease-out;
} }
&-enter, &-enter,
&-leave-to { &-leave-to {
opacity: 0.1; opacity: 0.1;
transform: scale(0.95); transform: scale(0.95);
} }
&-move { &-move {
transition: transform 100ms; transition: transform 100ms;
} }
} }
.disclaimer { .disclaimer {
color: #aaa; color: #aaa;
} }
.search-box { .search-box {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
&_content { &_content {
position: relative; position: relative;
margin: 1em 0; margin: 1em 0;
font-size: 1em; font-size: 1em;
} }
select { select {
border: none; border: none;
font-size: 1em; font-size: 1em;
background-color: rgb(87, 87, 87); background-color: rgb(87, 87, 87);
padding: 0.3em; padding: 0.3em;
padding-right: 50px; padding-right: 50px;
outline: none; outline: none;
color: white; color: white;
-webkit-appearance: none; -webkit-appearance: none;
-moz-appearance: none; -moz-appearance: none;
appearance: none; appearance: none;
cursor: pointer; cursor: pointer;
} }
label { label {
position: relative; position: relative;
&.disabled::after { &.disabled::after {
color: gray; color: gray;
} }
&::after { &::after {
content: "<>"; content: "<>";
position: absolute; position: absolute;
top: -5%; top: -5%;
right: 0.3em; right: 0.3em;
font-weight: bold; font-weight: bold;
} }
} }
} }
.list { .list {
text-align: center; text-align: center;
margin: 1em 0; margin: 1em 0;
display: flex; display: flex;
justify-content: center; justify-content: center;
&_loading, &_loading,
&_no-info { &_no-info {
margin: 0.3em 0; margin: 0.3em 0;
padding: 0.5em 2em; padding: 0.5em 2em;
color: white; color: white;
} }
&_loading { &_loading {
background-color: #b96b11; background-color: #b96b11;
} }
&_no-info { &_no-info {
background-color: firebrick; background-color: firebrick;
} }
&_wrapper { &_wrapper {
@include smallScreen() { @include smallScreen() {
width: 95%; width: 95%;
font-size: 0.9em; font-size: 0.9em;
} }
} }
&_content { &_content {
max-height: 75vh; max-height: 75vh;
overflow: auto; overflow: auto;
padding: 0.2em 0.5em; padding: 0.2em 0.5em;
} }
&_content > li { &_content > li {
display: grid; display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(2, minmax(0, 1fr));
background: #222; background: #222;
padding: 0.3em 0.8em; padding: 0.3em 0.8em;
margin: 0.3em 0; margin: 0.3em 0;
gap: 10em; gap: 10em;
@include smallScreen() { @include smallScreen() {
gap: 1em; gap: 1em;
} }
& > div { & > div {
margin: 0 1em; margin: 0 1em;
} }
&.current { &.current {
background: #007200; background: #007200;
} }
& > .dispatcher-name { & > .dispatcher-name {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
font-size: 1.1em; font-size: 1.1em;
font-weight: 500; font-weight: 500;
} }
} }
} }
</style> </style>