Compare commits

...

38 Commits

Author SHA1 Message Date
Spythere c09fc81886 Aktualizacja 1.10.0
Aktualizacja aplikacji do wersji 1.10.0
2022-07-24 00:00:18 +02:00
Spythere 30f72d518d Bump wersji 1.10.0 (prod) 2022-07-23 23:46:17 +02:00
Spythere 9b86e07152 Poprawki responsywności 2022-07-23 23:45:53 +02:00
Spythere 4e0fb5dc01 Fix reaktywności SRJP 2022-07-19 23:32:16 +02:00
Spythere a392991030 PWA: wyłączono funkcję 2022-07-19 22:56:27 +02:00
Spythere ff7ca27fe6 Dodano informację o offline 2022-07-19 14:45:46 +02:00
Spythere 94cd7aaa60 Bump wersji (dev) 2022-07-16 23:21:31 +02:00
Spythere 843289d8d7 Poprawki URL 2022-07-16 17:21:08 +02:00
Spythere 66cae68e19 Update package lock 2022-07-16 17:12:32 +02:00
Spythere b38e50396a package lock, gitignore 2022-07-16 17:10:25 +02:00
Spythere 7888e59117 Poprawki po migracji 2022-07-16 17:07:57 +02:00
Spythere 46e700583d Migracja na Vite 2022-07-16 16:12:31 +02:00
Spythere fc56c38c45 Poprawki stylistyczne modalu aktualizacji; link do najnowszego wydania w stopce 2022-07-16 12:41:24 +02:00
Spythere 9594e2c21a Bump wersji 2022-07-16 00:56:41 +02:00
Spythere a8bab5283b Modal aktualizacji aplikacji 2022-07-16 00:56:25 +02:00
Spythere 1cc799706c Globalny TrainModal; animacja przejścia 2022-07-16 00:27:37 +02:00
Spythere 5ee8f72652 Poprawka semantyki wersji 2022-07-15 15:36:06 +02:00
Spythere 942f883b91 Dodano modal aktualizacji 2022-07-15 15:32:12 +02:00
Spythere 54b47d44e5 PWA: odświeżanie przy wykryciu aktualizacji 2022-07-14 21:25:17 +02:00
Spythere f9aaf21f7a Test PWA 2022-07-14 20:52:53 +02:00
Spythere d79705ca5c Test cachingu scenerii 2022-07-14 20:44:43 +02:00
Spythere 55c64d5f0a Caching service workera 2022-07-14 17:50:17 +02:00
Spythere 4ca1c7bb9c Dodano wsparcie PWA 2022-07-14 14:57:44 +02:00
Spythere abc8fda98e Poprawka kolorów 2022-07-14 14:13:55 +02:00
Spythere aaec23d210 Poprawiono wygląd modalu RJ 2022-07-13 16:34:38 +02:00
Spythere 0af7b68138 Poprawki w widoku RJ 2022-07-12 18:45:18 +02:00
Spythere ae24eaf8e4 Poprawka w info o pozycji pociągu 2022-07-12 13:37:18 +02:00
Spythere f73a07daee Poprawiono wygląd posterunków SBL w widoku RJ 2022-07-12 13:25:16 +02:00
Spythere 89f5bf2e95 Dodano górny pasek z informacjami dla widoku RJ 2022-07-12 13:15:49 +02:00
Spythere 8137c1ff95 Widok pociągów: nowe odznaki statusów 2022-07-12 12:06:27 +02:00
Spythere 4b0d9b887e Historia dr scenerii: dodano odnośniki do dziennika 2022-07-12 11:20:19 +02:00
Spythere 506064cf9a Sceneria: przeniesiono link do wątku forum 2022-07-12 11:14:56 +02:00
Spythere 825e25434a Poprawki tłumaczenia 2022-07-12 11:02:49 +02:00
Spythere 32c601d50a Historia RJ scenerii: dodano odnośniki do dziennika 2022-07-12 10:59:29 +02:00
Spythere b88a96237e Dziennik: dodano filtrowanie po ID rozkładu 2022-07-12 10:54:48 +02:00
Spythere 6c724440d7 Bump wersji: 1.9.9 -> 1.9.10 2022-07-11 18:11:26 +02:00
Spythere 71016e63bb Bump wersji: 1.9.9 -> 1.9.9.1 2022-07-11 18:06:07 +02:00
Spythere fb85352ce3 Modal widoku pociągu 2022-07-11 18:04:07 +02:00
91 changed files with 3737 additions and 18550 deletions
+1
View File
@@ -1,6 +1,7 @@
.DS_Store .DS_Store
node_modules node_modules
/dist /dist
/dev-dist
# local env files # local env files
.env.local .env.local
-5
View File
@@ -1,5 +0,0 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}
+48 -54
View File
@@ -1,54 +1,48 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="pl"> <html lang="pl">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" /> <meta name="viewport" content="width=device-width,initial-scale=1.0" />
<meta name="keywords" content="Stacjownik, TD2, Train Driver 2, stacjownik-td2" /> <meta name="keywords" content="Stacjownik, TD2, Train Driver 2, stacjownik-td2" />
<meta name="description" content="Automatycznie odświeżana strona wyświetlająca stacje w Train Driver 2!" /> <meta name="description" content="Automatycznie odświeżana strona wyświetlająca stacje w Train Driver 2!" />
<title>Stacjownik</title> <title>Stacjownik</title>
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" /> <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" /> <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" /> <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="manifest" href="/site.webmanifest" /> <link rel="manifest" href="/site.webmanifest" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" /> <link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
<meta name="msapplication-TileColor" content="#da532c" /> <meta name="msapplication-TileColor" content="#da532c" />
<meta name="theme-color" content="#ffffff" /> <meta name="theme-color" content="#ffffff" />
<link rel="icon" href="favicon-64.png" sizes="64x64" type="image/png" /> <link rel="icon" href="favicon-64.png" sizes="64x64" type="image/png" />
<link rel="icon" href="favicon-32.png" sizes="32x32" type="image/png" /> <link rel="icon" href="favicon-32.png" sizes="32x32" type="image/png" />
<link rel="icon" href="favicon-62.png" sizes="62x62" type="image/png" /> <link rel="icon" href="favicon-62.png" sizes="62x62" type="image/png" />
<link rel="icon" href="favicon-16.png" sizes="16x16" type="image/png" /> <link rel="icon" href="favicon-16.png" sizes="16x16" type="image/png" />
<link rel="icon" href="favicon.ico" /> <link rel="icon" href="favicon.ico" />
<link href="https://fonts.googleapis.com/css2?family=Quicksand:wght@400;500;700&display=swap" rel="stylesheet" /> <link href="https://fonts.googleapis.com/css2?family=Quicksand:wght@400;500;700&display=swap" rel="stylesheet" />
<script src="https://www.gstatic.com/firebasejs/8.1.1/firebase-app.js"></script> <script src="https://www.gstatic.com/firebasejs/8.1.1/firebase-app.js"></script>
<script> <script>
const firebaseConfig = { const firebaseConfig = {
apiKey: 'AIzaSyBI36X2-p7vU1flxoJdCEc0noByyTe1mpw', apiKey: 'AIzaSyBI36X2-p7vU1flxoJdCEc0noByyTe1mpw',
authDomain: 'stacjownik-td2.firebaseapp.com', authDomain: 'stacjownik-td2.firebaseapp.com',
databaseURL: 'https://stacjownik-td2.firebaseio.com', databaseURL: 'https://stacjownik-td2.firebaseio.com',
projectId: 'stacjownik-td2', projectId: 'stacjownik-td2',
storageBucket: 'stacjownik-td2.appspot.com', storageBucket: 'stacjownik-td2.appspot.com',
}; };
firebase.initializeApp(firebaseConfig); firebase.initializeApp(firebaseConfig);
</script> </script>
</head> </head>
<body> <body>
<noscript> <div id="app"></div>
<strong <script type="module" src="/src/main.ts"></script>
>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please </body>
enable it to continue.</strong </html>
>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
+869 -17438
View File
File diff suppressed because it is too large Load Diff
+35 -41
View File
@@ -1,41 +1,35 @@
{ {
"name": "stacjownik", "name": "stacjownik",
"version": "1.9.9", "version": "1.10.0",
"private": true, "private": true,
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "dev": "vite",
"build": "vue-cli-service build", "build": "vue-tsc --noEmit && vite build",
"deploy-prod": "npm run build && firebase deploy --only hosting:prod", "preview": "vite preview"
"deploy-dev": "npm run build && firebase deploy --only hosting:dev" },
}, "dependencies": {
"dependencies": { "core-js": "^3.12.1",
"core-js": "^3.12.1", "dotenv": "^8.6.0",
"dotenv": "^8.6.0", "firebase": "^9.8.1",
"firebase": "^9.8.1", "howler": "^2.2.1",
"howler": "^2.2.1", "pinia": "^2.0.14",
"pinia": "^2.0.14", "sass": "^1.53.0",
"socket.io-client": "^4.4.1", "socket.io-client": "^4.4.1",
"vue": "^3.2.34", "vue": "^3.2.37",
"vue-i18n": "^9.1.6", "vue-i18n": "^9.1.6",
"vue-router": "^4.0.0-0", "vue-router": "^4.0.0-0"
"vuex": "^4.0.2" },
}, "devDependencies": {
"devDependencies": { "@types/node": "^17.0.35",
"@types/node": "^17.0.35", "@vitejs/plugin-vue": "^3.0.0",
"@vue/cli-plugin-babel": "^5.0.4", "axios": "^0.21.1",
"@vue/cli-plugin-router": "^5.0.4", "typescript": "^4.6.4",
"@vue/cli-plugin-typescript": "^5.0.4", "vite": "^3.0.0",
"@vue/cli-plugin-vuex": "^5.0.4", "vue-tsc": "^0.38.4"
"@vue/cli-service": "^5.0.4", },
"@vue/compiler-sfc": "^3.1.0", "browserslist": [
"axios": "^0.21.1", "> 1%",
"sass": "^1.32.13", "last 2 versions",
"sass-loader": "^8.0.2", "not dead"
"typescript": "^4.7.3" ]
}, }
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 951 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 799 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

+3
View File
@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.00251 14.9297L0 1.07422H6.14651L8.00251 4.27503L9.84583 1.07422H16L8.00251 14.9297Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 215 B

+2
View File
@@ -0,0 +1,2 @@
User-agent: *
Disallow:
+6 -5
View File
@@ -1,6 +1,6 @@
{ {
"name": "", "name": "Stacjownik TD2",
"short_name": "", "short_name": "Stacjownik",
"icons": [ "icons": [
{ {
"src": "/android-chrome-192x192.png", "src": "/android-chrome-192x192.png",
@@ -13,7 +13,8 @@
"type": "image/png" "type": "image/png"
} }
], ],
"theme_color": "#ffffff", "theme_color": "#ffc014",
"background_color": "#ffffff", "background_color": "#4d4d4d",
"display": "standalone" "display": "standalone",
"start_url": "."
} }
+16 -4
View File
@@ -17,6 +17,19 @@
} }
} }
.modal-anim {
&-enter-active,
&-leave-active {
transition: all $animDuration $animType;
}
&-enter-from,
&-leave-to {
transform: translateY(-25%);
opacity: 0;
}
}
.route { .route {
margin: 0 0.2em; margin: 0 0.2em;
@@ -27,7 +40,7 @@
} }
// APP // APP
.app { #app {
color: white; color: white;
font-size: 1rem; font-size: 1rem;
@@ -40,8 +53,8 @@
.app_container { .app_container {
display: flex; display: flex;
flex-flow: column; flex-flow: column;
height: 100vh;
min-height: 800px; min-height: 100vh;
header { header {
flex: 0 0 auto; flex: 0 0 auto;
@@ -213,7 +226,6 @@
font-weight: bold; font-weight: bold;
padding: 0.1em 0.5em; padding: 0.1em 0.5em;
color: paleturquoise; color: paleturquoise;
} }
.options { .options {
+96 -107
View File
@@ -1,106 +1,113 @@
<template> <template>
<div class="app"> <div class="app_container">
<div class="app_container"> <UpdateModal />
<!-- <div class="wip-alert">
<img class="icon-error" :src="iconError" alt="error" />
<h2>Stacjownik tymczasowo nieaktywny!</h2>
<p>Absolutny zakaz wjazdu!</p>
</div> -->
<header class="app_header">
<div class="header_container">
<div class="header_icons">
<span class="icons-top">
<img :src="icons.pl" alt="icon-pl" @click="changeLang('en')" v-if="currentLang == 'pl'" />
<img :src="icons.en" alt="icon-en" @click="changeLang('pl')" v-else />
</span>
<span class="icons-bottom">
<a href="https://www.paypal.com/paypalme/spythere" target="_blank">
<img :src="icons.dollar" alt="icon paypal" />
</a>
<a href="https://discord.gg/x2mpNN3svk" target="_blank"> <transition name="modal-anim">
<img :src="icons.discord" alt="icon discord" /> <keep-alive>
</a> <TrainModal v-if="store.chosenModalTrainId" />
</span> </keep-alive>
</div> </transition>
<div class="header_body"> <header class="app_header">
<status-indicator /> <div class="header_container">
<span class="header_brand"> <div class="header_icons">
<img :src="brand_logo" alt="Stacjownik" /> <span class="icons-top">
</span> <img :src="getIcon('pl')" alt="icon-pl" @click="changeLang('en')" v-if="currentLang == 'pl'" />
<img :src="getIcon('en', 'jpg')" alt="icon-en" @click="changeLang('pl')" v-else />
</span>
<span class="icons-bottom">
<a href="https://www.paypal.com/paypalme/spythere" target="_blank">
<img :src="getIcon('dollar')" alt="icon paypal" />
</a>
<span class="header_info"> <a href="https://discord.gg/x2mpNN3svk" target="_blank">
<Clock /> <img :src="getIcon('discord', 'png')" alt="icon discord" />
</a>
<div class="info_counter"> </span>
<img src="@/assets/icon-dispatcher.svg" alt="icon dispatcher" />
<span class="text--primary">{{ onlineDispatchers.length }}</span>
<span class="text--grayed"> / </span>
<span class="text--primary">{{ trainList.length }}</span>
<img src="@/assets/icon-train.svg" alt="icon train" />
</div>
<span class="info_region">
<SelectBox :itemList="computedRegions" :defaultItemIndex="0" @selected="changeRegion" />
</span>
</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="/journal">
{{ $t('app.journal') }}
</router-link>
</span>
</div>
</div> </div>
</header>
<main class="app_main"> <div class="header_body">
<router-view v-slot="{ Component }"> <status-indicator />
<!-- <transition name="view-anim" mode="out-in"> --> <span class="header_brand">
<keep-alive> <img :src="getImage('stacjownik-header-logo.svg')" alt="Stacjownik" />
<component :is="Component" :key="$route.path" /> </span>
</keep-alive>
</router-view>
</main>
<footer class="app_footer"> <span class="header_info">
&copy; <Clock />
<a href="https://td2.info.pl/profile/?u=20777" target="_blank">Spythere</a>
{{ new Date().getUTCFullYear() }} | v{{ VERSION }}
<div style="display: none">&int; ukryta taktyczna całka do programowania w HTMLu</div> <div class="info_counter">
</footer> <img :src="getIcon('dispatcher')" alt="icon dispatcher" />
</div> <span class="text--primary">{{ onlineDispatchers.length }}</span>
<span class="text--grayed"> / </span>
<span class="text--primary">{{ trainList.length }}</span>
<img :src="getIcon('train')" alt="icon train" />
</div>
<span class="info_region">
<SelectBox :itemList="computedRegions" :defaultItemIndex="0" @selected="changeRegion" />
</span>
</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="/journal/timetables">
{{ $t('app.journal') }}
</router-link>
</span>
</div>
</div>
</header>
<main class="app_main">
<router-view v-slot="{ Component }">
<keep-alive>
<component :is="Component" :key="$route.path" />
</keep-alive>
</router-view>
</main>
<footer class="app_footer">
&copy;
<a href="https://td2.info.pl/profile/?u=20777" target="_blank">Spythere</a>
{{ new Date().getUTCFullYear() }} | <a :href="releaseURL" target="_blank">v{{ VERSION }}</a>
<div style="display: none">&int; ukryta taktyczna całka do programowania w HTMLu</div>
</footer>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, provide, ref } from 'vue'; import { computed, defineComponent, provide, ref } from 'vue';
import Clock from '@/components/App/Clock.vue'; import Clock from './components/App/Clock.vue';
import StorageManager from '@/scripts/managers/storageManager';
import packageInfo from '.././package.json'; import packageInfo from '.././package.json';
import options from '@/data/options.json'; import options from './data/options.json';
import StatusIndicator from '@/components/App/StatusIndicator.vue'; import StatusIndicator from './components/App/StatusIndicator.vue';
import SelectBox from '@/components/Global/SelectBox.vue'; import SelectBox from './components/Global/SelectBox.vue';
import { useStore } from './store/store'; import { useStore } from './store/store';
import UpdateModal from './components/App/UpdateModal.vue';
import TrainModal from './components/Global/TrainModal.vue';
import StorageManager from './scripts/managers/storageManager';
import imageMixin from './mixins/imageMixin';
export default defineComponent({ export default defineComponent({
components: { components: {
Clock, Clock,
StatusIndicator, StatusIndicator,
SelectBox, SelectBox,
UpdateModal,
TrainModal,
}, },
mixins: [imageMixin],
setup() { setup() {
const store = useStore(); const store = useStore();
store.connectToAPI(); store.connectToAPI();
@@ -129,7 +136,8 @@ export default defineComponent({
return this.options.regions.map((region) => { return this.options.regions.map((region) => {
const regionStationCount = const regionStationCount =
this.store.apiData.stations?.filter((station) => station.region == region.id && station.isOnline).length || 0; this.store.apiData.stations?.filter((station) => station.region == region.id && station.isOnline).length || 0;
const regionTrainCount = this.store.apiData.trains?.filter((train) => train.region == region.id && train.online).length || 0; const regionTrainCount =
this.store.apiData.trains?.filter((train) => train.region == region.id && train.online).length || 0;
return { return {
id: region.id, id: region.id,
@@ -142,23 +150,10 @@ export default defineComponent({
data: () => ({ data: () => ({
VERSION: packageInfo.version, VERSION: packageInfo.version,
updateModalVisible: false,
hasReleaseNotes: false,
options, options,
currentLang: 'pl', currentLang: 'pl',
releaseURL: '',
brand_logo: require('@/assets/stacjownik-header-logo.svg'),
icons: {
en: require('@/assets/icon-en.jpg'),
pl: require('@/assets/icon-pl.svg'),
error: require('@/assets/icon-error.svg'),
dollar: require('@/assets/icon-dollar.svg'),
dispatcher: require('@/assets/icon-dispatcher.svg'),
train: require('@/assets/icon-train.svg'),
discord: require('@/assets/icon-discord.png'),
},
}), }),
created() { created() {
@@ -166,23 +161,11 @@ export default defineComponent({
}, },
async mounted() { async mounted() {
if (StorageManager.getStringValue('version') != this.VERSION) { this.updateStorage();
StorageManager.setStringValue('version', this.VERSION); this.setReleaseURL();
if (this.hasReleaseNotes) StorageManager.setBooleanValue('version_notes_read', false);
}
this.updateModalVisible = this.hasReleaseNotes && !StorageManager.getBooleanValue('version_notes_read');
this.updateToNewestVersion();
}, },
methods: { methods: {
toggleUpdateModal() {
this.updateModalVisible = !this.updateModalVisible;
StorageManager.setBooleanValue('version_notes_read', true);
},
changeRegion(region: { id: string; value: string }) { changeRegion(region: { id: string; value: string }) {
this.store.changeRegion(region); this.store.changeRegion(region);
}, },
@@ -194,7 +177,13 @@ export default defineComponent({
StorageManager.setStringValue('lang', lang); StorageManager.setStringValue('lang', lang);
}, },
updateToNewestVersion() { setReleaseURL() {
const releaseURL = StorageManager.getStringValue('releaseURL');
this.releaseURL = releaseURL || '';
},
updateStorage() {
if (!StorageManager.isRegistered('unavailable-status')) { if (!StorageManager.isRegistered('unavailable-status')) {
StorageManager.setBooleanValue('unavailable-status', true); StorageManager.setBooleanValue('unavailable-status', true);
StorageManager.setBooleanValue('ending-status', true); StorageManager.setBooleanValue('ending-status', true);
+1 -1
View File
@@ -3,7 +3,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from "@vue/runtime-core"; import { defineComponent } from "vue";
export default defineComponent({ export default defineComponent({
props: ["message"], props: ["message"],
+5 -8
View File
@@ -161,18 +161,15 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { DataStatus } from '@/scripts/enums/DataStatus';
import { StoreData } from '@/scripts/interfaces/StoreData'; import { defineComponent } from 'vue';
import { useStore } from '@/store/store'; import { DataStatus } from '../../scripts/enums/DataStatus';
import { StoreState } from '@/store/storeTypes'; import { useStore } from '../../store/store';
import { computed, defineComponent, watch } from 'vue'; import { StoreState } from '../../store/storeTypes';
export default defineComponent({ export default defineComponent({
data() { data() {
return { return {
icons: {
statusIndicator: require('@/assets/signal-status-indicator.svg'),
},
tooltipActive: false, tooltipActive: false,
indicator: { indicator: {
status: DataStatus.Loading, status: DataStatus.Loading,
+168
View File
@@ -0,0 +1,168 @@
<template>
<transition name="modal-anim">
<section class="update-modal card" v-if="releaseData && modalOpen">
<h2 class="modal_header text--primary">
<img :src="getImage('stacjownik-header-logo.svg')" alt="stacjownik logo" />
{{ releaseData.tag_name }}
</h2>
<div class="horizontal"></div>
<div class="modal_content">
<h3>{{ $t('update.title') }}</h3>
<a :href="releaseData.html_url" target="_blank">{{ $t('update.release-link') }}</a>
<br />
<br />
<p>{{ $t('update.paragraph1') }}</p>
<!-- <div class="modal_changelog" v-html="markdownReleaseBody"></div> -->
</div>
<div class="modal_actions">
<button class="btn btn--option" @click="modalOpen = false">{{ $t('update.confirm-button') }}</button>
</div>
</section>
</transition>
</template>
<script lang="ts">
import axios from 'axios';
import { defineComponent } from 'vue';
import packageInfo from '../../../package.json';
import imageMixin from '../../mixins/imageMixin';
import { ReleaseAPIData } from '../../scripts/interfaces/github_api/ReleaseAPIData';
import StorageManager from '../../scripts/managers/storageManager';
import { useStore } from '../../store/store';
const GH_LASTEST_RELEASE_URL = 'https://api.github.com/repos/Spythere/stacjownik/releases/latest';
export default defineComponent({
mixins: [imageMixin],
mounted() {
this.fetchReleases();
},
data() {
return {
modalOpen: false,
releaseData: null as ReleaseAPIData | null,
};
},
setup() {
return {
store: useStore()
}
},
methods: {
async fetchReleases() {
const storedVersion = StorageManager.getStringValue('appVersion');
const appVersion = packageInfo.version;
// Zmiana
if (appVersion != storedVersion) {
StorageManager.setStringValue('appVersion', appVersion);
// Znajdź changelog na GitHubie, jeśli jest pokaż modal
try {
const releaseData: ReleaseAPIData = await (await axios.get(GH_LASTEST_RELEASE_URL)).data;
if (!releaseData) return;
const lastReleaseVersion = releaseData.tag_name.slice(1);
if (lastReleaseVersion == appVersion) {
this.releaseData = releaseData;
this.modalOpen = true;
StorageManager.setStringValue('releaseURL', releaseData.html_url);
}
} catch (error) {
console.error(`Wystąpił błąd podczas pobierania danych z API GitHuba: ${error}`);
}
}
},
},
});
</script>
<style lang="scss" scoped>
@import '../../styles/card.scss';
@import '../../styles/responsive.scss';
.modal-anim {
&-enter-active,
&-leave-active {
transition: all $animDuration $animType;
}
&-enter-from,
&-leave-to {
opacity: 0;
transform: translate(-50%, -50%) scale(0.45);
}
}
.update-modal {
text-align: center;
background-color: var(--clr-secondary);
padding: 1em;
}
.horizontal {
margin: 1em 0;
height: 2px;
width: 100%;
background-color: white;
}
.modal_header {
font-size: 1.6em;
img {
width: 50%;
vertical-align: text-top;
}
}
.modal_content {
font-size: 1.1em;
a {
text-decoration: underline;
}
}
.modal_actions {
margin-top: 2em;
button {
color: white;
padding: 0.5em;
font-size: 1.2em;
background-color: black;
}
}
.modal_changelog {
font-size: 0.8em;
margin-top: 2em;
}
@include smallScreen {
.update-modal {
height: auto;
max-width: 95%;
}
}
</style>
-47
View File
@@ -1,47 +0,0 @@
<template>
<section
class="updates card"
v-if="cardOpen"
>
<h2>Ostatnie aktualizacje w Stacjowniku</h2>
<p>Tutaj będą pojawiać się informacje o kolejnych nowościach na stronie :)</p>
<ul>
<li
v-for="(update, i) in updates"
:key="i"
>
<div>{{update.date}}</div>
<div>
<span
v-for="(line, l) in content"
:key="l"
>{{line}}</span>
</div>
</li>
</ul>
</section>
</template>
<script>
import { defineComponent } from "@vue/runtime-core";
export default defineComponent({
data() {
return {
updates: {
date: "08/08/20",
content: [
"Lekko odświeżono wygląd strony, dodano nowy widok z pociągami online",
"Dodano animacje zmieniania widoków (zakładek)",
"Dodano przycisk zamykający kartę z filtrami",
],
},
};
},
});
</script>
<style lang="scss" scoped>
</style>
+5 -5
View File
@@ -9,7 +9,7 @@
<img <img
class="search-exit" class="search-exit"
:src="exitIcon" :src="getIcon('exit')"
alt="exit-icon" alt="exit-icon"
@click="clearValue" @click="clearValue"
/> />
@@ -18,11 +18,11 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, ref, watch } from "vue"; import { defineComponent, ref, watch } from "vue";
import imageMixin from "../../mixins/imageMixin";
export default defineComponent({ export default defineComponent({
data: () => ({ mixins: [imageMixin],
exitIcon: require("@/assets/icon-exit.svg"),
}),
emits: ["update:searchedValue", "clearValue"], emits: ["update:searchedValue", "clearValue"],
props: { props: {
searchedValue: { searchedValue: {
@@ -59,7 +59,7 @@ export default defineComponent({
emit("clearValue"); emit("clearValue");
}; };
const updateValue = (e) => { const updateValue = (e: any) => {
if (!props.updateOnInput && e.keyCode == 13) if (!props.updateOnInput && e.keyCode == 13)
emit("update:searchedValue", compSearchedValue.value); emit("update:searchedValue", compSearchedValue.value);
}; };
+5 -8
View File
@@ -1,5 +1,5 @@
<template> <template>
<div class="select-box" > <div class="select-box">
<div class="select-box_content"> <div class="select-box_content">
<button class="selected" @click="toggleBox"> <button class="selected" @click="toggleBox">
<span class="text--primary">{{ prefix }}</span> <span class="text--primary">{{ prefix }}</span>
@@ -24,13 +24,14 @@
</div> </div>
<div class="arrow"> <div class="arrow">
<img :src="listOpen ? ascIcon : descIcon" alt="arrow-icon" /> <img :src="listOpen ? getIcon('arrow-asc') : getIcon('arrow-desc')" alt="arrow-icon" />
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, Ref, ref } from '@vue/runtime-core'; import { defineComponent, Ref, ref, computed } from 'vue';
import imageMixin from '../../mixins/imageMixin';
interface Item { interface Item {
id: string; id: string;
@@ -40,6 +41,7 @@ interface Item {
export default defineComponent({ export default defineComponent({
emits: ['selected'], emits: ['selected'],
mixins: [imageMixin],
props: { props: {
itemList: { itemList: {
@@ -58,11 +60,6 @@ export default defineComponent({
}, },
}, },
data: () => ({
ascIcon: require('@/assets/icon-arrow-asc.svg'),
descIcon: require('@/assets/icon-arrow-desc.svg'),
}),
setup(props) { setup(props) {
let listRef: Ref<Element | null> = ref(null); let listRef: Ref<Element | null> = ref(null);
let buttonRef: Ref<HTMLButtonElement | null> = ref(null); let buttonRef: Ref<HTMLButtonElement | null> = ref(null);
+2 -2
View File
@@ -47,9 +47,9 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import dateMixin from '@/mixins/dateMixin';
import TrainStop from '@/scripts/interfaces/TrainStop';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import dateMixin from '../../mixins/dateMixin';
import TrainStop from '../../scripts/interfaces/TrainStop';
export default defineComponent({ export default defineComponent({
mixins: [dateMixin], mixins: [dateMixin],
+155
View File
@@ -0,0 +1,155 @@
<template>
<div class="train-modal" v-if="chosenTrain" @keydown.esc="closeModal">
<div class="modal_background" @click="closeModal"></div>
<div class="modal_content" ref="content" tabindex="0">
<button class="btn exit" @click="closeModal">
<img :src="getIcon('exit')" alt="close card" />
</button>
<TrainInfo :train="chosenTrain" :extended="false" ref="trainInfo" />
<TrainSchedule :train="chosenTrain" tabindex="0" />
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import imageMixin from '../../mixins/imageMixin';
import modalTrainMixin from '../../mixins/modalTrainMixin';
import trainInfoMixin from '../../mixins/trainInfoMixin';
import { useStore } from '../../store/store';
import TrainInfo from '../TrainsView/TrainInfo.vue';
import TrainSchedule from '../TrainsView/TrainSchedule.vue';
export default defineComponent({
components: { TrainInfo, TrainSchedule },
mixins: [trainInfoMixin, modalTrainMixin, imageMixin],
data() {
return {
isTopBarVisible: false,
};
},
setup() {
const store = useStore();
return {
store,
};
},
activated() {
const contentEl = this.$refs['content'] as HTMLElement;
this.$nextTick(() => {
contentEl.focus();
});
},
methods: {
handleContentScroll(e: Event) {
const trainInfoCompHeight: number = (this.$refs['trainInfo'] as any).$el.getBoundingClientRect().height;
const posTop = (e.target as HTMLElement).scrollTop;
this.isTopBarVisible = posTop > trainInfoCompHeight;
},
},
});
</script>
<style lang="scss" scoped>
@import '../../styles/responsive.scss';
@import '../../styles/card.scss';
.top-info-bar-anim {
&-enter-active,
&-leave-active {
transition: all 150ms ease-in-out;
}
&-enter-from,
&-leave-to {
transform: translate(-50%, -50%) scale(0.8);
opacity: 0;
}
}
.exit {
position: absolute;
top: 0;
right: 0;
margin: 0.5em 1em;
padding: 0.25em;
z-index: 201;
img {
width: 1.5rem;
vertical-align: middle;
}
}
.train-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
color: white;
z-index: 200;
display: flex;
justify-content: center;
text-align: left;
}
.modal_background {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
cursor: pointer;
background-color: rgba(0, 0, 0, 0.55);
}
.modal_content {
position: relative;
overflow-y: scroll;
margin-top: 1em;
width: 95vw;
max-height: 96vh;
background-color: #1a1a1a;
box-shadow: 0 0 15px 10px #0e0e0e;
}
@include midScreen {
.exit {
margin: 0.5em;
img {
width: 1.75rem;
}
}
}
@include smallScreen {
.train-modal {
font-size: 1.05em;
}
.modal_content {
max-height: 85vh;
}
}
</style>
@@ -48,12 +48,13 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { DispatcherStatsAPIData } from '@/scripts/interfaces/api/DispatcherStatsAPIData';
import { TimetableHistory } from '@/scripts/interfaces/api/TimetablesAPIData';
import { URLs } from '@/scripts/utils/apiURLs';
import { useStore } from '@/store/store';
import axios from 'axios'; import axios from 'axios';
import { computed, defineComponent } from 'vue'; import { computed, defineComponent } from 'vue';
import { DispatcherStatsAPIData } from '../../scripts/interfaces/api/DispatcherStatsAPIData';
import { TimetableHistory } from '../../scripts/interfaces/api/TimetablesAPIData';
import { URLs } from '../../scripts/utils/apiURLs';
import { useStore } from '../../store/store';
import Loading from '../Global/Loading.vue'; import Loading from '../Global/Loading.vue';
export default defineComponent({ export default defineComponent({
+5 -4
View File
@@ -51,12 +51,13 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { DriverStatsAPIData } from '@/scripts/interfaces/api/DriverStatsAPIData';
import { TimetableHistory } from '@/scripts/interfaces/api/TimetablesAPIData';
import { URLs } from '@/scripts/utils/apiURLs';
import { useStore } from '@/store/store';
import axios from 'axios'; import axios from 'axios';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { DriverStatsAPIData } from '../../scripts/interfaces/api/DriverStatsAPIData';
import { TimetableHistory } from '../../scripts/interfaces/api/TimetablesAPIData';
import { URLs } from '../../scripts/utils/apiURLs';
import { useStore } from '../../store/store';
export default defineComponent({ export default defineComponent({
emits: ['closeCard'], emits: ['closeCard'],
@@ -79,26 +79,21 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, JournalFilter, JournalSearcher, provide, reactive, Ref, ref, watch } from 'vue'; import { computed, defineComponent, JournalFilter, provide, reactive, Ref, ref } from 'vue';
import axios from 'axios'; import axios from 'axios';
import SearchBox from '@/components/Global/SearchBox.vue'; import ActionButton from '../../components/Global/ActionButton.vue';
import dateMixin from '@/mixins/dateMixin'; import JournalOptions from '../../components/JournalView/JournalOptions.vue';
import { DataStatus } from '@/scripts/enums/DataStatus'; import DispatcherStats from '../../components/JournalView/DispatcherStats.vue';
import SearchBox from '../Global/SearchBox.vue';
import ActionButton from '@/components/Global/ActionButton.vue';
import JournalOptions from '@/components/JournalView/JournalOptions.vue';
import DispatcherStats from '@/components/JournalView/DispatcherStats.vue';
import { URLs } from '@/scripts/utils/apiURLs';
import { useStore } from '@/store/store';
import { DispatcherStatsAPIData } from '@/scripts/interfaces/api/DispatcherStatsAPIData';
import Loading from '../Global/Loading.vue'; import Loading from '../Global/Loading.vue';
import { useRoute, useRouter } from 'vue-router'; import { URLs } from '../../scripts/utils/apiURLs';
import dateMixin from '../../mixins/dateMixin';
import { DataStatus } from '../../scripts/enums/DataStatus';
import { useStore } from '../../store/store';
const PROD_MODE = process.env.VUE_APP_JORUNAL_DISPATCHERS_DEV != '1' || process.env.NODE_ENV === 'production'; const DISPATCHERS_API_URL = `${URLs.stacjownikAPI}/api/getDispatchers`;
const DISPATCHERS_API_URL = (PROD_MODE ? `${URLs.stacjownikAPI}/api` : 'http://localhost:3001/api') + '/getDispatchers';
interface DispatcherHistoryItem { interface DispatcherHistoryItem {
id: string; id: string;
@@ -119,6 +114,10 @@ interface DispatcherHistoryItem {
isOnline: boolean; isOnline: boolean;
} }
type JournalDispatcherSearcher = {
[key in 'search-dispatcher' | 'search-station']: string;
};
export default defineComponent({ export default defineComponent({
components: { SearchBox, ActionButton, JournalOptions, DispatcherStats, Loading }, components: { SearchBox, ActionButton, JournalOptions, DispatcherStats, Loading },
mixins: [dateMixin], mixins: [dateMixin],
@@ -137,10 +136,6 @@ export default defineComponent({
}, },
data: () => ({ data: () => ({
icons: {
arrow: require('@/assets/icon-arrow-asc.svg'),
},
currentQuery: '', currentQuery: '',
scrollDataLoaded: true, scrollDataLoaded: true,
scrollNoMoreData: false, scrollNoMoreData: false,
@@ -157,10 +152,10 @@ export default defineComponent({
const sorterActive = ref({ id: 'timestampFrom', dir: -1 }); const sorterActive = ref({ id: 'timestampFrom', dir: -1 });
const journalFilterActive = ref({}); const journalFilterActive = ref({});
const searchersValues = reactive([ const searchersValues = reactive({
{ id: 'search-dispatcher', value: '' }, 'search-dispatcher': '',
{ id: 'search-station', value: '' }, 'search-station': '',
]); } as JournalDispatcherSearcher);
const countFromIndex = ref(0); const countFromIndex = ref(0);
const countLimit = 15; const countLimit = 15;
@@ -202,8 +197,8 @@ export default defineComponent({
activated() { activated() {
if (this.sceneryName || this.dispatcherName) { if (this.sceneryName || this.dispatcherName) {
this.searchersValues[1].value = this.sceneryName?.toString() || ''; this.searchersValues['search-station'] = this.sceneryName?.toString() || '';
this.searchersValues[0].value = this.dispatcherName?.toString() || ''; this.searchersValues['search-dispatcher'] = this.dispatcherName?.toString() || '';
this.search(); this.search();
} }
@@ -289,7 +284,7 @@ export default defineComponent({
async fetchHistoryData( async fetchHistoryData(
props: { props: {
searchers?: JournalSearcher[]; searchers?: JournalDispatcherSearcher;
filter?: JournalFilter; filter?: JournalFilter;
} = {} } = {}
) { ) {
@@ -297,8 +292,11 @@ export default defineComponent({
const queries: string[] = []; const queries: string[] = [];
const dispatcher = props.searchers?.find((s) => s.id == 'search-dispatcher')?.value.trim(); // const dispatcher = props.searchers?.find((s) => s.id == 'search-dispatcher')?.value.trim();
const station = props.searchers?.find((s) => s.id == 'search-station')?.value.trim(); // const station = props.searchers?.find((s) => s.id == 'search-station')?.value.trim();
const dispatcher = props.searchers?.['search-dispatcher'].trim();
const station = props.searchers?.['search-station'].trim();
if (dispatcher) queries.push(`dispatcherName=${dispatcher}`); if (dispatcher) queries.push(`dispatcherName=${dispatcher}`);
if (station) queries.push(`stationName=${station}`); if (station) queries.push(`stationName=${station}`);
@@ -330,7 +328,9 @@ export default defineComponent({
// Stats display // Stats display
this.store.dispatcherStatsName = this.store.dispatcherStatsName =
this.historyList.length > 0 && this.searchersValues[0].value.trim() ? this.historyList[0].dispatcherName : ''; this.historyList.length > 0 && this.searchersValues['search-dispatcher'].trim()
? this.historyList[0].dispatcherName
: '';
this.historyDataStatus.status = DataStatus.Loaded; this.historyDataStatus.status = DataStatus.Loaded;
} catch (error) { } catch (error) {
+10 -10
View File
@@ -12,15 +12,15 @@
</div> </div>
<div class="content_search"> <div class="content_search">
<div class="search-box" v-for="search in searchersValues" :key="search.id"> <div class="search-box" v-for="(value, propName) in searchersValues" :key="propName">
<input <input
class="search-input" class="search-input"
:placeholder="$t(`journal.${search.id}`)" :placeholder="$t(`journal.${propName}`)"
v-model="search.value" v-model="searchersValues[propName]"
@keydown.enter="onInputSearch" @keydown.enter="onInputSearch"
/> />
<img class="search-exit" :src="exitIcon" alt="exit-icon" @click="onInputClear(search.id)" /> <img class="search-exit" :src="getIcon('exit')" alt="exit-icon" @click="onInputClear(propName)" />
</div> </div>
<!-- <div class="search-box"> <!-- <div class="search-box">
<input <input
@@ -67,12 +67,15 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, inject, JournalFilter, PropType } from 'vue'; import { defineComponent, inject, JournalFilter, PropType } from 'vue';
import imageMixin from '../../mixins/imageMixin';
import ActionButton from '../Global/ActionButton.vue'; import ActionButton from '../Global/ActionButton.vue';
import SelectBox from '../Global/SelectBox.vue'; import SelectBox from '../Global/SelectBox.vue';
export default defineComponent({ export default defineComponent({
components: { SelectBox, ActionButton }, components: { SelectBox, ActionButton },
emits: ['onSorterChange', 'onInputChange', 'onFilterChange'], emits: ['onSorterChange', 'onInputChange', 'onFilterChange'],
mixins: [imageMixin],
props: { props: {
sorterOptionIds: { sorterOptionIds: {
type: Array as PropType<Array<string>>, type: Array as PropType<Array<string>>,
@@ -85,13 +88,10 @@ export default defineComponent({
}, },
}, },
data: () => ({
exitIcon: require('@/assets/icon-exit.svg'),
}),
setup() { setup() {
return { return {
searchersValues: inject('searchersValues') as {id: string; value: string}[], searchersValues: inject('searchersValues') as {[key: string]: string},
sorterActive: inject('sorterActive') as { id: string | number; dir: number }, sorterActive: inject('sorterActive') as { id: string | number; dir: number },
journalFilterActive: inject('journalFilterActive') as JournalFilter, journalFilterActive: inject('journalFilterActive') as JournalFilter,
}; };
@@ -123,8 +123,8 @@ export default defineComponent({
this.$emit('onInputChange'); this.$emit('onInputChange');
}, },
onInputClear(id: string) { onInputClear(id: any) {
this.searchersValues.find(s => s.id == id)!.value = ""; this.searchersValues[id] = '';
this.onInputSearch(); this.onInputSearch();
}, },
}, },
@@ -13,17 +13,6 @@
:sorter-option-ids="['timetableId', 'beginDate', 'distance', 'total-stops']" :sorter-option-ids="['timetableId', 'beginDate', 'distance', 'total-stops']"
:filters="journalTimetableFilters" :filters="journalTimetableFilters"
/> />
<!-- <button
class="btn btn--option"
:disabled="store.driverStatsName == ''"
@click="() => (statsCardOpen = !statsCardOpen)"
>
<span v-if="store.driverStatsName">
Statystyki maszynisty <b>{{ store.driverStatsName }}</b>
</span>
<span v-else>Statystyki maszynisty niedostępne</span>
</button> -->
</div> </div>
<div class="journal-list"> <div class="journal-list">
@@ -158,42 +147,40 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, JournalFilter, JournalSearcher, provide, reactive, Ref, ref } from 'vue'; import { computed, defineComponent, JournalFilter, provide, reactive, Ref, ref } from 'vue';
import axios from 'axios'; import axios from 'axios';
import SearchBox from '@/components/Global/SearchBox.vue';
import dateMixin from '@/mixins/dateMixin';
import { DataStatus } from '@/scripts/enums/DataStatus';
import ActionButton from '@/components/Global/ActionButton.vue';
import JournalOptions from '@/components/JournalView/JournalOptions.vue';
import { URLs } from '@/scripts/utils/apiURLs';
import { journalTimetableFilters } from '@/data/journalFilters';
import { JournalFilterType } from '@/scripts/enums/JournalFilterType';
import routerMixin from '@/mixins/routerMixin';
import { useStore } from '@/store/store';
import DriverStats from './DriverStats.vue'; import DriverStats from './DriverStats.vue';
import { TimetableHistory } from '@/scripts/interfaces/api/TimetablesAPIData';
import Loading from '../Global/Loading.vue'; import Loading from '../Global/Loading.vue';
import { journalTimetableFilters } from '../../data/journalFilters';
import dateMixin from '../../mixins/dateMixin';
import routerMixin from '../../mixins/routerMixin';
import { DataStatus } from '../../scripts/enums/DataStatus';
import { JournalFilterType } from '../../scripts/enums/JournalFilterType';
import { TimetableHistory } from '../../scripts/interfaces/api/TimetablesAPIData';
import { URLs } from '../../scripts/utils/apiURLs';
import { useStore } from '../../store/store';
import JournalOptions from './JournalOptions.vue';
const PROD_MODE = process.env.VUE_APP_JOURNAL_TIMETABLES_DEV != '1' || process.env.NODE_ENV === 'production'; const TIMETABLES_API_URL = `${URLs.stacjownikAPI}/api/getTimetables`;
const TIMETABLES_API_URL = PROD_MODE type JournalTimetableSearcher = {
? `${URLs.stacjownikAPI}/api/getTimetables` [key in 'search-driver' | 'search-train']: string;
: 'http://localhost:3001/api/getTimetables'; };
export default defineComponent({ export default defineComponent({
components: { SearchBox, ActionButton, JournalOptions, DriverStats, Loading }, components: { DriverStats, Loading, JournalOptions },
mixins: [dateMixin, routerMixin], mixins: [dateMixin, routerMixin],
name: 'JournalTimetables', name: 'JournalTimetables',
data: () => ({ props: {
icons: { timetableId: {
arrow: require('@/assets/icon-arrow-asc.svg'), type: String,
}, },
},
data: () => ({
currentQuery: '', currentQuery: '',
scrollDataLoaded: true, scrollDataLoaded: true,
scrollNoMoreData: false, scrollNoMoreData: false,
@@ -213,10 +200,11 @@ export default defineComponent({
const sorterActive = ref({ id: 'timetableId', dir: -1 }); const sorterActive = ref({ id: 'timetableId', dir: -1 });
const journalFilterActive = ref(journalTimetableFilters[0]); const journalFilterActive = ref(journalTimetableFilters[0]);
const searchersValues = reactive([ const searchersValues = reactive({
{ id: 'search-train', value: '' }, 'search-train': '',
{ id: 'search-driver', value: '' }, 'search-driver': '',
]); } as JournalTimetableSearcher);
const countFromIndex = ref(0); const countFromIndex = ref(0);
const countLimit = 15; const countLimit = 15;
@@ -249,10 +237,15 @@ export default defineComponent({
activated() { activated() {
window.addEventListener('scroll', this.handleScroll); window.addEventListener('scroll', this.handleScroll);
if (this.timetableId) {
this.searchersValues['search-train'] = `#${this.timetableId}`;
this.search();
}
}, },
mounted() { mounted() {
this.search(); if (!this.timetableId) this.search();
}, },
deactivated() { deactivated() {
@@ -329,7 +322,7 @@ export default defineComponent({
async fetchHistoryData( async fetchHistoryData(
props: { props: {
searchers?: JournalSearcher[]; searchers?: JournalTimetableSearcher;
filter?: JournalFilter; filter?: JournalFilter;
} = {} } = {}
) { ) {
@@ -337,11 +330,11 @@ export default defineComponent({
const queries: string[] = []; const queries: string[] = [];
const driver = props.searchers?.find((s) => s.id == 'search-driver')?.value.trim(); const driver = props.searchers?.['search-driver'].trim();
const train = props.searchers?.find((s) => s.id == 'search-train')?.value.trim(); const train = props.searchers?.['search-train'].trim();
if (driver) queries.push(`driverName=${driver}`); if (driver) queries.push(`driverName=${driver}`);
if (train) queries.push(`trainNo=${train}`); if (train) queries.push(train.startsWith('#') ? `timetableId=${train.replace('#', '')}` : `trainNo=${train}`);
// Z API: const SORT_TYPES = ['allStopsCount', 'endDate', 'beginDate', 'routeDistance']; // Z API: const SORT_TYPES = ['allStopsCount', 'endDate', 'beginDate', 'routeDistance'];
if (this.sorterActive.id == 'distance') queries.push('sortBy=routeDistance'); if (this.sorterActive.id == 'distance') queries.push('sortBy=routeDistance');
@@ -381,13 +374,6 @@ export default defineComponent({
return; return;
} }
// if (responseData) {
// this.historyDataStatus.status = DataStatus.Error;
// this.historyDataStatus.error = responseData;
// return;
// }
if (!responseData) return; if (!responseData) return;
// Response data exists // Response data exists
@@ -395,7 +381,9 @@ export default defineComponent({
// Stats display // Stats display
this.store.driverStatsName = this.store.driverStatsName =
this.historyList.length > 0 && this.searchersValues[1].value.trim() ? this.historyList[0].driverName : ''; this.historyList.length > 0 && this.searchersValues['search-driver'].trim()
? this.historyList[0].driverName
: '';
this.historyDataStatus.status = DataStatus.Loaded; this.historyDataStatus.status = DataStatus.Loaded;
} catch (error) { } catch (error) {
@@ -1,5 +1,5 @@
<template> <template>
<section class="scenery-dispatchers-history scenery-section"> <section class="scenery-dispatchers-history scenery-section">
<Loading v-if="dataStatus != 2" /> <Loading v-if="dataStatus != 2" />
<div class="list-warning" v-else-if="dispatcherHistoryList.length == 0">{{ $t('scenery.history-list-empty') }}</div> <div class="list-warning" v-else-if="dispatcherHistoryList.length == 0">{{ $t('scenery.history-list-empty') }}</div>
@@ -7,8 +7,10 @@
<ul class="history-list" v-else> <ul class="history-list" v-else>
<li class="list-item" v-for="historyItem in dispatcherHistoryList"> <li class="list-item" v-for="historyItem in dispatcherHistoryList">
<div> <div>
<span class="text--grayed">#{{ historyItem.stationHash }}&nbsp;</span> <router-link :to="`/journal/dispatchers?dispatcherName=${historyItem.dispatcherName}`">
<b class="text--primary">{{ historyItem.dispatcherName }}</b> <span class="text--grayed">#{{ historyItem.stationHash }}&nbsp;</span>
<b>{{ historyItem.dispatcherName }}</b>
</router-link>
</div> </div>
<div v-if="historyItem.timestampTo"> <div v-if="historyItem.timestampTo">
@@ -22,7 +24,6 @@
{{ $t('journal.online-since') }} {{ $t('journal.online-since') }}
<b>{{ timestampToString(historyItem.timestampFrom) }}</b> <b>{{ timestampToString(historyItem.timestampFrom) }}</b>
({{ calculateDuration(historyItem.currentDuration) }}) ({{ calculateDuration(historyItem.currentDuration) }})
<span></span>
</div> </div>
</li> </li>
</ul> </ul>
@@ -30,13 +31,14 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import dateMixin from '@/mixins/dateMixin';
import { DataStatus } from '@/scripts/enums/DataStatus';
import { DispatcherHistory } from '@/scripts/interfaces/api/DispatchersAPIData';
import Station from '@/scripts/interfaces/Station';
import { URLs } from '@/scripts/utils/apiURLs';
import axios from 'axios'; import axios from 'axios';
import { defineComponent, PropType } from 'vue'; import { defineComponent, PropType } from 'vue';
import dateMixin from '../../mixins/dateMixin';
import { DataStatus } from '../../scripts/enums/DataStatus';
import { DispatcherHistory } from '../../scripts/interfaces/api/DispatchersAPIData';
import Station from '../../scripts/interfaces/Station';
import { URLs } from '../../scripts/utils/apiURLs';
import Loading from '../Global/Loading.vue'; import Loading from '../Global/Loading.vue';
export default defineComponent({ export default defineComponent({
@@ -80,7 +82,6 @@ export default defineComponent({
@import '../../styles/responsive.scss'; @import '../../styles/responsive.scss';
@import '../../styles/SceneryView/styles.scss'; @import '../../styles/SceneryView/styles.scss';
.history-list { .history-list {
padding: 0 0.5em; padding: 0 0.5em;
} }
@@ -103,6 +104,9 @@ export default defineComponent({
} }
@include smallScreen { @include smallScreen {
.history-list {
font-size: 1.2em;
}
.list-item { .list-item {
align-items: center; align-items: center;
flex-direction: column; flex-direction: column;
+3 -7
View File
@@ -1,11 +1,7 @@
<template> <template>
<section class="info-header"> <section class="info-header">
<div class="scenery-name"> <div class="scenery-name">
<a v-if="station.generalInfo?.url" :href="station.generalInfo.url" target="_blank" rel="noopener noreferrer"> {{ station.name }}
{{ station.name }}
</a>
<span v-else>{{ station.name }}</span>
</div> </div>
<div class="scenery-hash" v-if="station.onlineInfo?.hash">#{{ station.onlineInfo.hash }}</div> <div class="scenery-hash" v-if="station.onlineInfo?.hash">#{{ station.onlineInfo.hash }}</div>
@@ -14,8 +10,8 @@
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import Station from '../../scripts/interfaces/Station';
import Station from '@/scripts/interfaces/Station';
export default defineComponent({ export default defineComponent({
props: { props: {
@@ -33,7 +29,6 @@ export default defineComponent({
.scenery-name { .scenery-name {
font-weight: bold; font-weight: bold;
color: $accentCol;
position: relative; position: relative;
@@ -52,3 +47,4 @@ export default defineComponent({
font-size: 1.2em; font-size: 1.2em;
} }
</style> </style>
+11 -39
View File
@@ -11,11 +11,6 @@
<span v-if="station.generalInfo.reqLevel > -1"> <span v-if="station.generalInfo.reqLevel > -1">
- {{ $tc('scenery.req-level', station.generalInfo.reqLevel, { lvl: station.generalInfo.reqLevel }) }} - {{ $tc('scenery.req-level', station.generalInfo.reqLevel, { lvl: station.generalInfo.reqLevel }) }}
</span> </span>
<!-- <span v-if="station.generalInfo.reqLevel > 0">
- minimum {{ station.generalInfo.reqLevel }} poziom dyżurnego
</span>
<span v-else-if="station.generalInfo.reqLevel == 0">- dla wszystkich poziomów</span> -->
</span> </span>
<span> <span>
@@ -41,12 +36,17 @@
<b> {{ $tc('scenery.authors-title', station.generalInfo.authors.length) }}: </b> <b> {{ $tc('scenery.authors-title', station.generalInfo.authors.length) }}: </b>
{{ station.generalInfo.authors.join(', ') }} {{ station.generalInfo.authors.join(', ') }}
</div> </div>
<br />
<div class="scenery-topic" v-if="station.generalInfo.url">
<a :href="station.generalInfo.url" target="_blank">
&gt; {{ $t('scenery.forum-topic', { name: station.name }) }} &lt;
</a>
</div>
</div> </div>
<div style="margin: 2em 0; height: 2px; background-color: white" /> <div style="margin: 2em 0; height: 2px; background-color: white" />
<!-- info stats -->
<!-- <scenery-info-stats :station="station" /> -->
<!-- info dispatcher --> <!-- info dispatcher -->
<scenery-info-dispatcher :station="station" :onlineFrom="onlineFrom" /> <scenery-info-dispatcher :station="station" :onlineFrom="onlineFrom" />
@@ -57,10 +57,6 @@
<!-- spawn list --> <!-- spawn list -->
<scenery-info-spawn-list :station="station" /> <scenery-info-spawn-list :station="station" />
</div> </div>
<!-- info icons -->
<!-- info routes -->
</section> </section>
</div> </div>
</template> </template>
@@ -74,8 +70,8 @@ import SceneryInfoStats from './SceneryInfo/SceneryInfoStats.vue';
import SceneryInfoUserList from './SceneryInfo/SceneryInfoUserList.vue'; import SceneryInfoUserList from './SceneryInfo/SceneryInfoUserList.vue';
import SceneryInfoSpawnList from './SceneryInfo/SceneryInfoSpawnList.vue'; import SceneryInfoSpawnList from './SceneryInfo/SceneryInfoSpawnList.vue';
import SceneryInfoRoutes from './SceneryInfo/SceneryInfoRoutes.vue'; import SceneryInfoRoutes from './SceneryInfo/SceneryInfoRoutes.vue';
import Station from '../../scripts/interfaces/Station';
import Station from '@/scripts/interfaces/Station';
export default defineComponent({ export default defineComponent({
components: { components: {
@@ -103,6 +99,7 @@ export default defineComponent({
<style lang="scss"> <style lang="scss">
@import '../../styles/responsive.scss'; @import '../../styles/responsive.scss';
@import '../../styles/badge.scss';
h3.section-header { h3.section-header {
margin: 0.5em 0; margin: 0.5em 0;
@@ -143,32 +140,7 @@ h3.section-header {
} }
} }
.badge { .scenery-topic a {
font-weight: 600; font-weight: bold;
display: inline-block;
padding: 0;
background: #585858;
margin: 0.25em;
span {
display: inline-block;
padding: 0.2em 0.4em;
}
&-none {
font-weight: 600;
padding: 0.2em 0.4em;
background: firebrick;
text-align: center;
@include smallScreen() {
font-size: 1em;
}
}
} }
</style> </style>
@@ -13,7 +13,7 @@
</router-link> </router-link>
<span class="dispatcher_likes text--primary"> <span class="dispatcher_likes text--primary">
<img :src="icons.like" alt="icon-like" /> <img :src="getIcon('like')" alt="icon-like" />
<span>{{ station.onlineInfo?.dispatcherRate || '0' }}</span> <span>{{ station.onlineInfo?.dispatcherRate || '0' }}</span>
</span> </span>
</div> </div>
@@ -35,14 +35,14 @@
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import dateMixin from '../../../mixins/dateMixin';
import styleMixin from '@/mixins/styleMixin'; import imageMixin from '../../../mixins/imageMixin';
import Station from '@/scripts/interfaces/Station'; import routerMixin from '../../../mixins/routerMixin';
import dateMixin from '@/mixins/dateMixin'; import styleMixin from '../../../mixins/styleMixin';
import routerMixin from '@/mixins/routerMixin'; import Station from '../../../scripts/interfaces/Station';
export default defineComponent({ export default defineComponent({
mixins: [styleMixin, dateMixin, routerMixin], mixins: [styleMixin, dateMixin, routerMixin, imageMixin],
props: { props: {
station: { station: {
type: Object as () => Station, type: Object as () => Station,
@@ -54,13 +54,6 @@ export default defineComponent({
default: -1, default: -1,
}, },
}, },
data: () => ({
icons: {
spawn: require('@/assets/icon-spawn.svg'),
like: require('@/assets/icon-like.svg'),
},
}),
}); });
</script> </script>
@@ -20,7 +20,7 @@
<img <img
v-if="station.generalInfo?.SUP" v-if="station.generalInfo?.SUP"
class="icon-info" class="icon-info"
:src="require(`@/assets/icon-SUP.svg`)" :src="getIcon('SUP')"
alt="SUP (RASP-UZK)" alt="SUP (RASP-UZK)"
:title="$t('desc.SUP')" :title="$t('desc.SUP')"
/> />
@@ -28,7 +28,7 @@
<img <img
v-if="station.generalInfo?.signalType" v-if="station.generalInfo?.signalType"
class="icon-info" class="icon-info"
:src="require(`@/assets/icon-${station.generalInfo.signalType}.svg`)" :src="getIcon(station.generalInfo.signalType)"
:alt="station.generalInfo.signalType" :alt="station.generalInfo.signalType"
:title="$t('desc.signals-type') + $t(`signals.${station.generalInfo.signalType}`)" :title="$t('desc.signals-type') + $t(`signals.${station.generalInfo.signalType}`)"
/> />
@@ -36,7 +36,7 @@
<img <img
v-if="station.generalInfo?.availability == 'nonPublic'" v-if="station.generalInfo?.availability == 'nonPublic'"
class="icon-info" class="icon-info"
:src="icons.lock" :src="getIcon('lock')"
alt="Non-public scenery" alt="Non-public scenery"
:title="$t('desc.non-public')" :title="$t('desc.non-public')"
/> />
@@ -44,7 +44,7 @@
<img <img
v-if="station.generalInfo?.availability == 'unavailable'" v-if="station.generalInfo?.availability == 'unavailable'"
class="icon-info" class="icon-info"
:src="icons.unavailable" :src="getIcon('unavailable')"
alt="Unavailable scenery" alt="Unavailable scenery"
:title="$t('desc.unavailable')" :title="$t('desc.unavailable')"
/> />
@@ -52,7 +52,7 @@
<img <img
v-if="station.generalInfo?.availability == 'abandoned'" v-if="station.generalInfo?.availability == 'abandoned'"
class="icon-info" class="icon-info"
:src="icons.abandoned" :src="getIcon('abandoned')"
alt="Abandoned scenery" alt="Abandoned scenery"
:title="$t('desc.abandoned')" :title="$t('desc.abandoned')"
/> />
@@ -60,7 +60,7 @@
<img <img
v-if="station.generalInfo?.lines" v-if="station.generalInfo?.lines"
class="icon-info" class="icon-info"
:src="icons.real" :src="getIcon('real')"
alt="real scenery" alt="real scenery"
:title="`${$t('desc.real')} ${station.generalInfo.lines}`" :title="`${$t('desc.real')} ${station.generalInfo.lines}`"
/> />
@@ -68,7 +68,7 @@
<img <img
v-if="!station.generalInfo" v-if="!station.generalInfo"
class="icon-info" class="icon-info"
:src="icons.unknown" :src="getImage('unknown.png')"
alt="icon-unknown" alt="icon-unknown"
:title="$t('desc.unknown')" :title="$t('desc.unknown')"
/> />
@@ -77,31 +77,19 @@
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import stationInfoMixin from '@/mixins/stationInfoMixin'; import imageMixin from '../../../mixins/imageMixin';
import stationInfoMixin from '../../../mixins/stationInfoMixin';
import Station from '@/scripts/interfaces/Station'; import styleMixin from '../../../mixins/styleMixin';
import styleMixin from '@/mixins/styleMixin'; import Station from '../../../scripts/interfaces/Station';
export default defineComponent({ export default defineComponent({
mixins: [stationInfoMixin, styleMixin], mixins: [stationInfoMixin, styleMixin, imageMixin],
props: { props: {
station: { station: {
type: Object as () => Station, type: Object as () => Station,
default: {}, default: {},
}, },
}, },
data: () => ({
icons: {
td2: require('@/assets/icon-td2.svg'),
lock: require('@/assets/icon-lock.svg'),
unavailable: require('@/assets/icon-unavailable.svg'),
unknown: require('@/assets/icon-unknown.svg'),
abandoned: require('@/assets/icon-abandoned.svg'),
real: require('@/assets/icon-real.svg'),
},
}),
}); });
</script> </script>
@@ -130,3 +118,4 @@ export default defineComponent({
} }
} }
</style> </style>
@@ -57,8 +57,8 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import Station from '@/scripts/interfaces/Station';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import Station from '../../../scripts/interfaces/Station';
export default defineComponent({ export default defineComponent({
props: { props: {
@@ -1,7 +1,7 @@
<template> <template>
<section class="info-spawn-list"> <section class="info-spawn-list">
<h3 class="spawn-header section-header"> <h3 class="spawn-header section-header">
<img :src="icons.spawn" alt="icon-spawn" /> <img :src="getIcon('spawn')" alt="icon-spawn" />
&nbsp;{{ $t('scenery.spawns') }} &nbsp; &nbsp;{{ $t('scenery.spawns') }} &nbsp;
<span class="text--primary">{{ station.onlineInfo?.spawns.length || '0' }}</span> <span class="text--primary">{{ station.onlineInfo?.spawns.length || '0' }}</span>
</h3> </h3>
@@ -24,22 +24,19 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import Station from '@/scripts/interfaces/Station';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import imageMixin from '../../../mixins/imageMixin';
import Station from '../../../scripts/interfaces/Station';
export default defineComponent({ export default defineComponent({
mixins: [imageMixin],
props: { props: {
station: { station: {
type: Object as () => Station, type: Object as () => Station,
default: {}, default: {},
}, },
}, },
data: () => ({
icons: {
spawn: require('@/assets/icon-spawn.svg'),
},
}),
}); });
</script> </script>
@@ -1,24 +1,24 @@
<template> <template>
<section class="info-stats" :class="!station.onlineInfo ? 'no-stats' : ''"> <section class="info-stats" :class="!station.onlineInfo ? 'no-stats' : ''">
<span class="likes"> <span class="likes">
<img :src="icons.like" alt="icon-like" /> <img :src="getIcon('like')" alt="icon-like" />
<span>{{ station.onlineInfo?.dispatcherRate || '0' }}</span> <span>{{ station.onlineInfo?.dispatcherRate || '0' }}</span>
</span> </span>
<span class="users"> <span class="users">
<img :src="icons.user" alt="icon-user" /> <img :src="getIcon('user')" alt="icon-user" />
<span>{{ station.onlineInfo?.currentUsers || '0' }}</span> <span>{{ station.onlineInfo?.currentUsers || '0' }}</span>
/ /
<span>{{ station.onlineInfo?.maxUsers || '0' }}</span> <span>{{ station.onlineInfo?.maxUsers || '0' }}</span>
</span> </span>
<span class="spawns"> <span class="spawns">
<img :src="icons.spawn" alt="icon-spawn" /> <img :src="getIcon('spawn')" alt="icon-spawn" />
<span>{{ station.onlineInfo?.spawns.length || '0' }}</span> <span>{{ station.onlineInfo?.spawns.length || '0' }}</span>
</span> </span>
<span class="schedules"> <span class="schedules">
<img :src="icons.timetable" alt="icon-timetable" /> <img :src="getIcon('timetable')" alt="icon-timetable" />
<span> <span>
<span style="color: #eee">{{ station.onlineInfo?.scheduledTrains?.length || '0' }}</span> <span style="color: #eee">{{ station.onlineInfo?.scheduledTrains?.length || '0' }}</span>
/ /
@@ -32,25 +32,17 @@
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import imageMixin from '../../../mixins/imageMixin';
import Station from '@/scripts/interfaces/Station'; import Station from '../../../scripts/interfaces/Station';
export default defineComponent({ export default defineComponent({
mixins: [imageMixin],
props: { props: {
station: { station: {
type: Object as () => Station, type: Object as () => Station,
default: {}, default: {},
}, },
}, },
data: () => ({
icons: {
like: require('@/assets/icon-like.svg'),
timetable: require('@/assets/icon-timetable.svg'),
user: require('@/assets/icon-user.svg'),
spawn: require('@/assets/icon-spawn.svg'),
},
}),
}); });
</script> </script>
@@ -83,7 +75,7 @@ export default defineComponent({
} }
span > img { span > img {
width: 1.2em; width: 1.2em;
margin-right: 0.5em; margin-right: 0.5em;
} }
} }
@@ -1,7 +1,7 @@
<template> <template>
<section class="info-user-list"> <section class="info-user-list">
<h3 class="user-header section-header"> <h3 class="user-header section-header">
<img :src="icons.user" alt="icon-user" /> <img :src="getIcon('user')" alt="icon-user" />
&nbsp;{{ $t('scenery.users') }} &nbsp; &nbsp;{{ $t('scenery.users') }} &nbsp;
<span class="text--primary">{{ station.onlineInfo?.currentUsers || '0' }}</span <span class="text--primary">{{ station.onlineInfo?.currentUsers || '0' }}</span
>&nbsp;/&nbsp;<span class="text--primary">{{ station.onlineInfo?.maxUsers || '0' }}</span> >&nbsp;/&nbsp;<span class="text--primary">{{ station.onlineInfo?.maxUsers || '0' }}</span>
@@ -11,10 +11,10 @@
v-for="(train, i) in computedStationTrains" v-for="(train, i) in computedStationTrains"
class="badge user" class="badge user"
:class="train.stopStatus" :class="train.stopStatus"
:key="train.trainNo + i" :key="train.trainId"
tabindex="0" tabindex="0"
@click="navigateTo('/trains', { trainNo: train.trainNo, driverName: train.driverName })" @click="selectModalTrain(train.trainId)"
@keydown.enter="navigateTo('/trains', { trainNo: train.trainNo, driverName: train.driverName })" @keydown.enter="selectModalTrain(train.trainId)"
> >
<span class="user_train">{{ train.trainNo }}</span> <span class="user_train">{{ train.trainNo }}</span>
<span class="user_name">{{ train.driverName }}</span> <span class="user_name">{{ train.driverName }}</span>
@@ -27,12 +27,16 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import routerMixin from '@/mixins/routerMixin';
import Station from '@/scripts/interfaces/Station';
import { computed, defineComponent } from 'vue'; import { computed, defineComponent } from 'vue';
import imageMixin from '../../../mixins/imageMixin';
import modalTrainMixin from '../../../mixins/modalTrainMixin';
import routerMixin from '../../../mixins/routerMixin';
import Station from '../../../scripts/interfaces/Station';
import { useStore } from '../../../store/store';
export default defineComponent({ export default defineComponent({
mixins: [routerMixin], mixins: [routerMixin, imageMixin, modalTrainMixin],
props: { props: {
station: { station: {
@@ -42,6 +46,8 @@ export default defineComponent({
}, },
setup(props) { setup(props) {
const store = useStore();
const computedStationTrains = computed(() => { const computedStationTrains = computed(() => {
if (!props.station) return []; if (!props.station) return [];
@@ -59,14 +65,8 @@ export default defineComponent({
}); });
}); });
return { computedStationTrains }; return { computedStationTrains, store };
}, },
data: () => ({
icons: {
user: require('@/assets/icon-user.svg'),
},
}),
}); });
</script> </script>
@@ -130,3 +130,4 @@ $disconnected: slategray;
} }
} }
</style> </style>
+20 -21
View File
@@ -2,7 +2,7 @@
<section class="scenery-timetable"> <section class="scenery-timetable">
<div class="timetable-header"> <div class="timetable-header">
<h3> <h3>
<img :src="icons.timetable" alt="icon-timetable" />&nbsp; <img :src="getIcon('timetable')" alt="icon-timetable" />&nbsp;
<span>{{ $t('scenery.timetables') }}</span> <span>{{ $t('scenery.timetables') }}</span>
&nbsp; &nbsp;
<span class="text--primary">{{ station.onlineInfo?.scheduledTrains?.length || '0' }}</span> <span class="text--primary">{{ station.onlineInfo?.scheduledTrains?.length || '0' }}</span>
@@ -32,7 +32,11 @@
<Loading /> <Loading />
</div> </div>
<span class="timetable-item empty" v-else-if="computedScheduledTrains.length == 0"> <span class="timetable-item empty" v-else-if="computedScheduledTrains.length == 0 && !station.onlineInfo">
{{ $t('scenery.offline') }}
</span>
<span class="timetable-item empty" v-else-if="computedScheduledTrains.length == 0">
{{ $t('scenery.no-timetables') }} {{ $t('scenery.no-timetables') }}
</span> </span>
@@ -41,13 +45,8 @@
v-for="(scheduledTrain, i) in computedScheduledTrains" v-for="(scheduledTrain, i) in computedScheduledTrains"
:key="i + 1" :key="i + 1"
tabindex="0" tabindex="0"
@click="navigateTo('/trains', { trainNo: scheduledTrain.trainNo, driverName: scheduledTrain.driverName })" @click.prevent.stop="selectModalTrain(scheduledTrain.trainId)"
@keydown.enter=" @keydown.enter.prevent="selectModalTrain(scheduledTrain.trainId)"
navigateTo('/trains', {
trainNo: scheduledTrain.trainNo,
driverName: scheduledTrain.driverName,
})
"
> >
<span class="timetable-general"> <span class="timetable-general">
<span class="general-info"> <span class="general-info">
@@ -56,7 +55,7 @@
{{ scheduledTrain.trainNo }} {{ scheduledTrain.trainNo }}
<span class="g-tooltip" v-if="scheduledTrain.stopInfo.comments"> <span class="g-tooltip" v-if="scheduledTrain.stopInfo.comments">
<img :src="icons.warning" /> <img :src="getIcon('warning')" />
<span class="content" v-html="scheduledTrain.stopInfo.comments"> </span> <span class="content" v-html="scheduledTrain.stopInfo.comments"> </span>
</span> </span>
</span> </span>
@@ -158,21 +157,25 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import Station from '@/scripts/interfaces/Station';
import SelectBox from '../Global/SelectBox.vue'; import SelectBox from '../Global/SelectBox.vue';
import { computed, defineComponent, PropType, ref } from '@vue/runtime-core'; import { computed, defineComponent, PropType, ref } from '@vue/runtime-core';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import dateMixin from '@/mixins/dateMixin';
import routerMixin from '@/mixins/routerMixin';
import { useStore } from '@/store/store';
import Loading from '../Global/Loading.vue'; import Loading from '../Global/Loading.vue';
import TrainModal from '../Global/TrainModal.vue';
import dateMixin from '../../mixins/dateMixin';
import routerMixin from '../../mixins/routerMixin';
import Station from '../../scripts/interfaces/Station';
import { useStore } from '../../store/store';
import imageMixin from '../../mixins/imageMixin';
import modalTrainMixin from '../../mixins/modalTrainMixin';
export default defineComponent({ export default defineComponent({
name: 'SceneryTimetable', name: 'SceneryTimetable',
components: { SelectBox, Loading }, components: { SelectBox, Loading, TrainModal },
mixins: [dateMixin, routerMixin], mixins: [dateMixin, routerMixin, imageMixin, modalTrainMixin],
props: { props: {
station: { station: {
@@ -182,12 +185,8 @@ export default defineComponent({
}, },
data: () => ({ data: () => ({
viewIcon: require('@/assets/icon-view.svg'),
listOpen: false, listOpen: false,
icons: {
warning: require('@/assets/icon-warning.svg'),
timetable: require('@/assets/icon-timetable.svg'),
},
}), }),
setup(props) { setup(props) {
@@ -9,10 +9,13 @@
<b>{{ localeDay(historyItem.beginDate, $i18n.locale) }}</b> <b>{{ localeDay(historyItem.beginDate, $i18n.locale) }}</b>
{{ localeTime(historyItem.beginDate, $i18n.locale) }} {{ localeTime(historyItem.beginDate, $i18n.locale) }}
</div> </div>
<div> <div>
<span class="text--grayed"> #{{ historyItem.timetableId }} </span> <router-link :to="`/journal/timetables?timetableId=${historyItem.timetableId}`">
<b class="text--primary">&nbsp;{{ historyItem.trainCategoryCode }} {{ historyItem.trainNo }}</b> <span class="text--grayed"> #{{ historyItem.timetableId }} </span>
<div>{{ historyItem.driverName }}</div> <b class="text--primary">&nbsp;{{ historyItem.trainCategoryCode }} {{ historyItem.trainNo }}</b>
<div>{{ historyItem.driverName }}</div>
</router-link>
</div> </div>
<div>{{ historyItem.route.replace('|', ' -> ') }}</div> <div>{{ historyItem.route.replace('|', ' -> ') }}</div>
@@ -30,13 +33,14 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import dateMixin from '@/mixins/dateMixin';
import { DataStatus } from '@/scripts/enums/DataStatus';
import { SceneryTimetableHistory, TimetableHistory } from '@/scripts/interfaces/api/TimetablesAPIData';
import Station from '@/scripts/interfaces/Station';
import { URLs } from '@/scripts/utils/apiURLs';
import axios from 'axios'; import axios from 'axios';
import { defineComponent, PropType } from 'vue'; import { defineComponent, PropType } from 'vue';
import dateMixin from '../../mixins/dateMixin';
import { DataStatus } from '../../scripts/enums/DataStatus';
import { TimetableHistory, SceneryTimetableHistory } from '../../scripts/interfaces/api/TimetablesAPIData';
import Station from '../../scripts/interfaces/Station';
import { URLs } from '../../scripts/utils/apiURLs';
import Loading from '../Global/Loading.vue'; import Loading from '../Global/Loading.vue';
export default defineComponent({ export default defineComponent({
@@ -102,9 +106,13 @@ export default defineComponent({
} }
@include smallScreen { @include smallScreen {
.history-list {
font-size: 1.1em;
}
.list-item { .list-item {
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
font-size: 1.05em; font-size: 1.05em;
} }
} }
</style> </style>
@@ -2,7 +2,7 @@
<section class="filter-card" v-click-outside="closeCard"> <section class="filter-card" v-click-outside="closeCard">
<div class="card_btn"> <div class="card_btn">
<button class="btn btn--option" @click="toggleCard"> <button class="btn btn--option" @click="toggleCard">
<img class="button_icon" :src="filterIcon" alt="icon-filter" /> <img class="button_icon" :src="getIcon('filter2')" alt="icon-filter" />
{{ $t('options.filters') }} {{ $t('options.filters') }}
</button> </button>
</div> </div>
@@ -91,21 +91,22 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, inject } from '@vue/runtime-core';
import inputData from '@/data/options.json'; import { defineComponent, inject } from 'vue';
import inputData from '../../data/options.json';
import imageMixin from '../../mixins/imageMixin';
import StorageManager from '../../scripts/managers/storageManager';
import { useStore } from '../../store/store';
import StorageManager from '@/scripts/managers/storageManager';
import ActionButton from '../Global/ActionButton.vue'; import ActionButton from '../Global/ActionButton.vue';
import FilterOption from './FilterOption.vue'; import FilterOption from './FilterOption.vue';
import { useStore } from '@/store/store';
export default defineComponent({ export default defineComponent({
components: { ActionButton, FilterOption }, components: { ActionButton, FilterOption },
emits: ['changeFilterValue', 'invertFilters', 'resetFilters'], emits: ['changeFilterValue', 'invertFilters', 'resetFilters'],
mixins: [imageMixin],
data: () => ({ data: () => ({
filterIcon: require('@/assets/icon-filter2.svg'),
inputs: { ...inputData }, inputs: { ...inputData },
saveOptions: false, saveOptions: false,
@@ -165,7 +166,7 @@ export default defineComponent({
handleAuthorsInput(e: Event) { handleAuthorsInput(e: Event) {
clearTimeout(this.delayInputTimer); clearTimeout(this.delayInputTimer);
this.delayInputTimer = setTimeout(() => { this.delayInputTimer = window.setTimeout(() => {
this.handleInput(e); this.handleInput(e);
}, 400); }, 400);
}, },
@@ -200,7 +201,7 @@ export default defineComponent({
this.$emit('invertFilters'); this.$emit('invertFilters');
}, },
saveFilters(change: { value }) { saveFilters(change: { value: any }) {
this.saveOptions = change.value; this.saveOptions = change.value;
if (!this.saveOptions) { if (!this.saveOptions) {
@@ -257,8 +258,8 @@ export default defineComponent({
&-enter-from, &-enter-from,
&-leave-to { &-leave-to {
transform: translate(-50%, -50%) scale(0.8);
opacity: 0; opacity: 0;
transform: translate(-50%, -50%) scale(0.45);
} }
} }
+20 -34
View File
@@ -15,7 +15,7 @@
<img <img
class="sort-icon" class="sort-icon"
v-if="sorterActive.index == i" v-if="sorterActive.index == i"
:src="sorterActive.dir == 1 ? ascIcon : descIcon" :src="sorterActive.dir == 1 ? getIcon('arrow-asc') : getIcon('arrow-desc')"
alt="sort icon" alt="sort icon"
/> />
</span> </span>
@@ -23,12 +23,12 @@
<th v-for="(id, i) in headIconsIds" :key="id" @click="() => changeSorter(i + 7)"> <th v-for="(id, i) in headIconsIds" :key="id" @click="() => changeSorter(i + 7)">
<span class="header_wrapper"> <span class="header_wrapper">
<img :src="require(`@/assets/icon-${id}.svg`)" :alt="id" :title="$t(`sceneries.${id}s`)" /> <img :src="getIcon(id)" :alt="id" :title="$t(`sceneries.${id}s`)" />
<img <img
class="sort-icon" class="sort-icon"
v-if="sorterActive.index == i + 7" v-if="sorterActive.index == i + 7"
:src="sorterActive.dir == 1 ? ascIcon : descIcon" :src="sorterActive.dir == 1 ? getIcon('arrow-asc') : getIcon('arrow-desc')"
alt="sort icon" alt="sort icon"
/> />
</span> </span>
@@ -67,15 +67,15 @@
</span> </span>
<span v-else-if="station.generalInfo.availability == 'abandoned'"> <span v-else-if="station.generalInfo.availability == 'abandoned'">
<img :src="abandonedIcon" alt="non-public" :title="$t('desc.abandoned')" /> <img :src="getIcon('abandoned')" alt="non-public" :title="$t('desc.abandoned')" />
</span> </span>
<span v-else-if="station.generalInfo.availability == 'nonPublic'"> <span v-else-if="station.generalInfo.availability == 'nonPublic'">
<img :src="lockIcon" alt="non-public" :title="$t('desc.non-public')" /> <img :src="getIcon('lock')" alt="non-public" :title="$t('desc.non-public')" />
</span> </span>
<span v-else> <span v-else>
<img :src="unavailableIcon" alt="unavailable" :title="$t('desc.unavailable')" /> <img :src="getIcon('unavailable')" alt="unavailable" :title="$t('desc.unavailable')" />
</span> </span>
</span> </span>
@@ -154,7 +154,7 @@
<img <img
class="icon-info" class="icon-info"
v-if="station.generalInfo.SUP" v-if="station.generalInfo.SUP"
:src="require(`@/assets/icon-SUP.svg`)" :src="getIcon('SUP')"
alt="SUP (RASP-UZK)" alt="SUP (RASP-UZK)"
:title="$t('desc.SUP')" :title="$t('desc.SUP')"
/> />
@@ -164,7 +164,7 @@
<img <img
class="icon-info" class="icon-info"
v-if="station.generalInfo.signalType" v-if="station.generalInfo.signalType"
:src="require(`@/assets/icon-${station.generalInfo.signalType}.svg`)" :src="getIcon(station.generalInfo.signalType)"
:alt="station.generalInfo.signalType" :alt="station.generalInfo.signalType"
:title="$t('desc.signals-type') + $t(`signals.${station.generalInfo.signalType}`)" :title="$t('desc.signals-type') + $t(`signals.${station.generalInfo.signalType}`)"
/> />
@@ -174,7 +174,7 @@
<img <img
class="icon-info" class="icon-info"
v-if="station.generalInfo && station.generalInfo.routes.sblRouteNames.length > 0" v-if="station.generalInfo && station.generalInfo.routes.sblRouteNames.length > 0"
:src="SBLIcon" :src="getIcon('SBL')"
alt="SBL" alt="SBL"
:title="$t('desc.SBL') + `${station.generalInfo.routes.sblRouteNames.join(',')}`" :title="$t('desc.SBL') + `${station.generalInfo.routes.sblRouteNames.join(',')}`"
/> />
@@ -182,7 +182,7 @@
</td> </td>
<td class="station_info" v-else> <td class="station_info" v-else>
<img class="icon-info" :src="unknownIcon" alt="icon-unknown" :title="$t('desc.unknown')" /> <img class="icon-info" :src="getImage('unknown.png')" alt="icon-unknown" :title="$t('desc.unknown')" />
</td> </td>
<td class="station_users" :class="{ inactive: !station.onlineInfo }"> <td class="station_users" :class="{ inactive: !station.onlineInfo }">
@@ -222,16 +222,15 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import styleMixin from '@/mixins/styleMixin'; import { defineComponent, computed } from 'vue';
import dateMixin from '@/mixins/dateMixin'; import dateMixin from '../../mixins/dateMixin';
import stationInfoMixin from '@/mixins/stationInfoMixin'; import imageMixin from '../../mixins/imageMixin';
import returnBtnMixin from '@/mixins/returnBtnMixin'; import returnBtnMixin from '../../mixins/returnBtnMixin';
import stationInfoMixin from '../../mixins/stationInfoMixin';
import { DataStatus } from '@/scripts/enums/DataStatus'; import styleMixin from '../../mixins/styleMixin';
import { computed, ComputedRef, defineComponent } from '@vue/runtime-core'; import { DataStatus } from '../../scripts/enums/DataStatus';
import Station from '@/scripts/interfaces/Station'; import Station from '../../scripts/interfaces/Station';
import { StoreData } from '@/scripts/interfaces/StoreData'; import { useStore } from '../../store/store';
import { useStore } from '@/store/store';
import Loading from '../Global/Loading.vue'; import Loading from '../Global/Loading.vue';
export default defineComponent({ export default defineComponent({
@@ -250,21 +249,8 @@ export default defineComponent({
setFocusedStation: { type: Function, required: true }, setFocusedStation: { type: Function, required: true },
changeSorter: { type: Function, required: true }, changeSorter: { type: Function, required: true },
}, },
mixins: [styleMixin, dateMixin, stationInfoMixin, returnBtnMixin], mixins: [styleMixin, dateMixin, stationInfoMixin, returnBtnMixin, imageMixin],
data: () => ({ data: () => ({
likeIcon: require('@/assets/icon-like.svg'),
spawnIcon: require('@/assets/icon-spawn.svg'),
timetableIcon: require('@/assets/icon-timetable.svg'),
userIcon: require('@/assets/icon-user.svg'),
trainIcon: require('@/assets/icon-train.svg'),
SBLIcon: require('@/assets/icon-SBL.svg'),
SUPIcon: require('@/assets/icon-SUP.svg'),
lockIcon: require('@/assets/icon-lock.svg'),
unavailableIcon: require('@/assets/icon-unavailable.svg'),
unknownIcon: require('@/assets/icon-unknown.svg'),
abandonedIcon: require('@/assets/icon-abandoned.svg'),
ascIcon: require('@/assets/icon-arrow-asc.svg'),
descIcon: require('@/assets/icon-arrow-desc.svg'),
headIds: ['station', 'min-lvl', 'status', 'dispatcher', 'dispatcher-lvl', 'routes', 'general'], headIds: ['station', 'min-lvl', 'status', 'dispatcher', 'dispatcher-lvl', 'routes', 'general'],
headIconsIds: ['user', 'spawn', 'timetable'], headIconsIds: ['user', 'spawn', 'timetable'],
lastSelectedStationName: '', lastSelectedStationName: '',
+134 -137
View File
@@ -1,103 +1,72 @@
<template> <template>
<div class="train-info simple" tabindex="0"> <div class="train-info" tabindex="0">
<section> <section class="train-route">
<span> <div class="train_general">
<div> <span>
<span> <span class="timetable-id" v-if="train.timetableData">#{{ train.timetableData.timetableId }}</span>
<!-- <router-link
v-if="train.timetableData"
:to="`/journal/timetables?timetableId=${train.timetableData.timetableId}`"
style="color: #ddd; margin-right: 0.3em"
>
#{{ train.timetableData.timetableId }}
</router-link> -->
<span class="timetable-id" v-if="train.timetableData">#{{ train.timetableData.timetableId }}</span> <span class="timetable_warnings">
<span class="train-badge twr" v-if="train.timetableData?.TWR">TWR</span>
<span class="timetable_warnings"> <span class="train-badge skr" v-if="train.timetableData?.SKR">SKR</span>
<span class="warning twr" v-if="train.timetableData?.TWR">TWR</span>
<span class="warning skr" v-if="train.timetableData?.SKR">SKR</span>
</span>
<strong v-if="train.timetableData">{{ train.timetableData.category }}&nbsp;</strong>
<strong>{{ train.trainNo }}</strong>
<span>&nbsp;| {{ train.driverName }}&nbsp;</span>
</span> </span>
<strong v-if="train.timetableData">{{ train.timetableData.category }}&nbsp;</strong>
<strong>{{ train.trainNo }}</strong>
<span>&nbsp;| {{ train.driverName }}&nbsp;</span>
</span>
</div>
<img <div class="timetable_route" v-if="train.timetableData">
class="image-offline" <strong>{{ train.timetableData.route.replace('|', ' - ') }}</strong>
style="height: 1em" <img
v-if="!train.currentStationHash" v-if="getSceneriesWithComments(train.timetableData).length > 0"
:src="icons.offline" class="image-warning"
alt="offline" :src="getIcon('warning')"
:title="$t('trains.offline')" :title="`${$t('trains.timetable-comments')} (${getSceneriesWithComments(train.timetableData)})`"
/> />
</div>
<hr style="margin: 0.25em 0" />
<div class="timetable_stops" v-if="train.timetableData">
<span v-if="train.timetableData.followingStops.length > 2">
{{ $t('trains.via-title') }}
<span v-html="displayStopList(train.timetableData.followingStops)"></span>
</span>
</div>
<div class="timetable_progress" style="margin-top: 0.5em" v-if="train.timetableData">
<!-- <span> </span> -->
<span class="timetable_progress-bar">
<!-- {{ confirmedPercentage(train.timetableData.followingStops) }}%&nbsp; -->
<span class="bar-bg"></span>
<span
class="bar-fg"
:style="{ width: `${Math.floor(confirmedPercentage(train.timetableData.followingStops))}%` }"
></span>
</span>
<span class="timetable_progress-distance">
&nbsp; {{ currentDistance(train.timetableData.followingStops) }} km /
<span class="text--primary"> {{ train.timetableData.routeDistance }} km </span>
|
<span v-html="currentDelay(train.timetableData.followingStops)"></span>
</span>
<div class="train-status-badges">
<div v-if="!train.currentStationHash" class="train-badge offline">{{ $t('trains.scenery-offline') }}</div>
<div v-if="!train.online" class="train-badge offline">Offline {{ lastSeenMessage(train.lastSeen) }}</div>
</div> </div>
</div>
<div class="timetable_route" v-if="train.timetableData"> <div class="driver_position text--grayed" style="margin-top: 0.25em">
<strong>{{ train.timetableData.route.replace('|', ' - ') }}</strong> {{ displayTrainPosition(train) }}
<img </div>
v-if="getSceneriesWithComments(train.timetableData).length > 0"
class="image-warning"
:src="icons.warning"
:title="`${$t('trains.timetable-comments')} (${getSceneriesWithComments(train.timetableData)})`"
/>
</div>
<hr style="margin: 0.25em 0" />
<div class="timetable_stops" v-if="train.timetableData">
<span v-if="train.timetableData.followingStops.length > 2">
{{ $t('trains.via-title') }}
<span v-html="displayStopList(train.timetableData.followingStops)"></span>
</span>
</div>
<div class="timetable_progress" style="margin-top: 0.5em" v-if="train.timetableData">
<!-- <span> </span> -->
<span class="timetable_progress-bar">
<!-- {{ confirmedPercentage(train.timetableData.followingStops) }}%&nbsp; -->
<span class="bar-bg"></span>
<span
class="bar-fg"
:style="{ width: `${Math.floor(confirmedPercentage(train.timetableData.followingStops))}%` }"
></span>
</span>
<span>
&nbsp; {{ currentDistance(train.timetableData.followingStops) }} km /
<span class="text--primary"> {{ train.timetableData.routeDistance }} km </span>
|
<span v-html="currentDelay(train.timetableData.followingStops)"></span>
</span>
</div>
<div v-if="!train.online" style="color: salmon">Offline - {{ lastSeenMessage(train.lastSeen) }}</div>
<div class="driver_position text--grayed" style="margin-top: 0.25em">
<span v-if="train.currentStationHash">
{{ $t('trains.current-scenery') }} <span>{{ train['currentStationName'] }}&nbsp;</span>
</span>
<span v-else>
{{ $t('trains.current-scenery') }}
<span>{{ train['currentStationName'].replace(/.[a-zA-Z0-9]+.sc/, '') }} (offline)&nbsp;</span>
</span>
<span v-if="train.signal">
{{ $t('trains.current-signal') }} <span>{{ train['signal'] }}&nbsp;</span>
</span>
<span v-if="train.connectedTrack">
{{ $t('trains.current-track') }} <span>{{ train['connectedTrack'] }}&nbsp;</span>
</span>
<span v-if="train.distance">({{ displayDistance(train.distance) }})</span>
</div>
</span>
</section> </section>
<section class="train-image" style="display: flex; justify-content: center; align-items: center"> <section class="train-stats">
<img :src="train.locoURL" loading="lazy" alt="Loco image not found" @error="onImageError" /> <div>
<img :src="train.locoURL" loading="lazy" alt="Loco image not found" @error="onImageError" />
</div>
<div class="text--grayed"> <div class="text--grayed">
{{ train.locoType }} {{ train.locoType }}
@@ -108,21 +77,20 @@
</div> </div>
<div> <div>
<div> <span v-for="(stat, i) in STATS.main" :key="stat.name">
<span v-for="(stat, i) in STATS.main" :key="stat.name"> <span v-if="i > 0"> &bull; </span>
<span v-if="i > 0"> &bull; </span> <span>{{ `${~~((train as any)[stat.name] * (stat.multiplier || 1))}${stat.unit}` }} </span>
<span>{{ `${~~(train[stat.name] * (stat.multiplier || 1))}${stat.unit}` }} </span> </span>
</span>
</div>
</div> </div>
</section> </section>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import trainInfoMixin from '@/mixins/trainInfoMixin';
import Train from '@/scripts/interfaces/Train';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import imageMixin from '../../mixins/imageMixin';
import trainInfoMixin from '../../mixins/trainInfoMixin';
import Train from '../../scripts/interfaces/Train';
export default defineComponent({ export default defineComponent({
props: { props: {
@@ -130,32 +98,33 @@ export default defineComponent({
type: Object as () => Train, type: Object as () => Train,
required: true, required: true,
}, },
extended: {
type: Boolean,
default: true,
},
}, },
mixins: [trainInfoMixin], mixins: [trainInfoMixin, imageMixin],
data: () => ({
icons: {
warning: require('@/assets/icon-warning.svg'),
offline: require('@/assets/icon-offline.svg'),
},
}),
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/responsive.scss'; @import '../../styles/responsive.scss';
.image-warning, .image-warning {
.image-offline {
height: 1em; height: 1em;
margin-left: 0.5em; margin-left: 0.5em;
} }
.train-image { .train-stats {
display: flex; display: flex;
justify-content: center;
align-content: center;
flex-direction: column; flex-direction: column;
text-align: center;
img { img {
margin: 0.5em 0; margin: 0.5em 0;
@@ -163,18 +132,15 @@ export default defineComponent({
} }
} }
.simple { .train-info {
display: grid; display: grid;
grid-template-columns: 2fr 1fr; grid-template-columns: 2fr 1fr;
grid-template-rows: 1fr; grid-template-rows: 1fr;
padding: 1em; padding: 1em;
background-color: #202020;
gap: 0.5em;
}
.driver_position:first-letter { background-color: #1a1a1a;
text-transform: capitalize; gap: 0.5em;
} }
.timetable-id { .timetable-id {
@@ -186,6 +152,37 @@ export default defineComponent({
font-size: 0.75em; font-size: 0.75em;
} }
.train_general {
display: flex;
align-items: center;
flex-wrap: wrap;
}
.train-status-badges {
display: flex;
flex-wrap: wrap;
}
.train-badge {
padding: 0.15em 0.35em;
margin-right: 0.3em;
font-weight: bold;
font-size: 0.9em;
&.twr {
background-color: var(--clr-twr);
}
&.skr {
background-color: var(--clr-skr);
}
&.offline {
background-color: #b83b2d;
}
}
.timetable_route { .timetable_route {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -195,22 +192,6 @@ export default defineComponent({
.timetable_warnings { .timetable_warnings {
color: black; color: black;
.warning {
padding: 0.1em 0.3em;
margin-right: 0.3em;
border-radius: 1em;
font-weight: bold;
&.twr {
background: var(--clr-twr);
}
&.skr {
background: var(--clr-skr);
}
}
} }
.timetable_progress { .timetable_progress {
@@ -244,6 +225,10 @@ export default defineComponent({
} }
} }
.timetable_progress-distance {
margin-right: 0.25em;
}
.comments { .comments {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -258,16 +243,28 @@ export default defineComponent({
} }
@include smallScreen() { @include smallScreen() {
.simple { .train-info {
grid-template-columns: 1fr; grid-template-columns: 1fr;
gap: 1em 0; gap: 1em 0;
text-align: center; text-align: center;
font-size: 1.25em; font-size: 1.15em;
} }
.info-stats { .train-stats {
text-align: center; font-size: 1.1em;
img {
display: none;
}
}
.train_general {
justify-content: center;
}
.train-status-badges {
justify-content: center;
} }
.timetable_route { .timetable_route {
+4 -6
View File
@@ -15,13 +15,13 @@
<div class="search-box"> <div class="search-box">
<input class="search-input" v-model="searchedTrain" :placeholder="$t('trains.search-train')" /> <input class="search-input" v-model="searchedTrain" :placeholder="$t('trains.search-train')" />
<img class="search-exit" :src="exitIcon" alt="exit-icon" @click="() => (searchedTrain = '')" /> <img class="search-exit" :src="getIcon('exit')" alt="exit-icon" @click="() => (searchedTrain = '')" />
</div> </div>
<div class="search-box"> <div class="search-box">
<input class="search-input" v-model="searchedDriver" :placeholder="$t('trains.search-driver')" /> <input class="search-input" v-model="searchedDriver" :placeholder="$t('trains.search-driver')" />
<img class="search-exit" :src="exitIcon" alt="exit-icon" @click="() => (searchedDriver = '')" /> <img class="search-exit" :src="getIcon('exit')" alt="exit-icon" @click="() => (searchedDriver = '')" />
</div> </div>
</div> </div>
</div> </div>
@@ -58,15 +58,13 @@
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, inject, TrainFilter } from 'vue'; import { computed, defineComponent, inject, TrainFilter } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import imageMixin from '../../mixins/imageMixin';
import SelectBox from '../Global/SelectBox.vue'; import SelectBox from '../Global/SelectBox.vue';
export default defineComponent({ export default defineComponent({
components: { SelectBox }, components: { SelectBox },
emits: ['changeSearchedTrain', 'changeSearchedDriver', 'changeSorter'], emits: ['changeSearchedTrain', 'changeSearchedDriver', 'changeSorter'],
mixins: [imageMixin],
data: () => ({
exitIcon: require('@/assets/icon-exit.svg'),
}),
setup() { setup() {
const { t } = useI18n(); const { t } = useI18n();
+9 -24
View File
@@ -2,7 +2,7 @@
<section class="filter-card" v-click-outside="closeCard"> <section class="filter-card" v-click-outside="closeCard">
<div class="card_btn"> <div class="card_btn">
<action-button @click="toggleCard"> <action-button @click="toggleCard">
<img class="button_icon" :src="filterIcon" alt="icon-filter" /> <img class="button_icon" :src="getIcon('filter2')" alt="icon-filter" />
<p>{{ $t('options.filters') }}</p> <p>{{ $t('options.filters') }}</p>
</action-button> </action-button>
</div> </div>
@@ -26,13 +26,13 @@
<div class="search-box"> <div class="search-box">
<input class="search-input" v-model="searchedTrain" :placeholder="$t('trains.search-train')" /> <input class="search-input" v-model="searchedTrain" :placeholder="$t('trains.search-train')" />
<img class="search-exit" :src="exitIcon" alt="exit-icon" @click="() => (searchedTrain = '')" /> <img class="search-exit" :src="getIcon('exit')" alt="exit-icon" @click="() => (searchedTrain = '')" />
</div> </div>
<div class="search-box"> <div class="search-box">
<input class="search-input" v-model="searchedDriver" :placeholder="$t('trains.search-driver')" /> <input class="search-input" v-model="searchedDriver" :placeholder="$t('trains.search-driver')" />
<img class="search-exit" :src="exitIcon" alt="exit-icon" @click="() => (searchedDriver = '')" /> <img class="search-exit" :src="getIcon('exit')" alt="exit-icon" @click="() => (searchedDriver = '')" />
</div> </div>
</div> </div>
</div> </div>
@@ -50,24 +50,21 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, inject } from '@vue/runtime-core'; import inputData from "../../data/options.json";
import inputData from '@/data/options.json'; import { TrainFilter, computed, defineComponent, inject } from 'vue';
import ActionButton from '@/components/Global/ActionButton.vue';
import { sorterOptions } from '@/data/trainOptions';
import { TrainFilter, computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import SelectBox from '../Global/SelectBox.vue'; import SelectBox from '../Global/SelectBox.vue';
import ActionButton from '../Global/ActionButton.vue';
import { sorterOptions } from '../../data/trainOptions';
import imageMixin from "../../mixins/imageMixin";
export default defineComponent({ export default defineComponent({
components: { ActionButton, SelectBox }, components: { ActionButton, SelectBox },
emits: ['changeFilterValue', 'invertFilters', 'resetFilters'], emits: ['changeFilterValue', 'invertFilters', 'resetFilters'],
mixins: [imageMixin],
data: () => ({ data: () => ({
filterIcon: require('@/assets/icon-filter2.svg'),
exitIcon: require('@/assets/icon-exit.svg'),
inputs: { ...inputData }, inputs: { ...inputData },
}), }),
@@ -128,18 +125,6 @@ export default defineComponent({
@import '../../styles/responsive'; @import '../../styles/responsive';
@import '../../styles/card'; @import '../../styles/card';
.card-anim {
&-enter-active,
&-leave-active {
transition: all $animDuration $animType;
}
&-enter-from,
&-leave-to {
transform: translate(-50%, -50%) scale(0.85);
opacity: 0;
}
}
.card { .card {
section { section {
+14 -21
View File
@@ -60,11 +60,11 @@
<b>{{ stop.stopNameRAW }} </b>: <span v-html="stop.comments"></span> <b>{{ stop.stopNameRAW }} </b>: <span v-html="stop.comments"></span>
</div> </div>
<span v-if="stop.departureLine == train.timetableData!.followingStops[i + 1].arrivalLine"> <span v-if="stop.departureLine == train.timetableData!.followingStops[i + 1].arrivalLine && !/sbl/gi.test(stop.departureLine!)">
{{ stop.departureLine }} {{ stop.departureLine }}
</span> </span>
<span v-else> <span v-else-if="!/sbl/gi.test(stop.departureLine!)">
{{ stop.departureLine }} / {{ stop.departureLine }} /
{{ train.timetableData!.followingStops[i + 1].arrivalLine }} {{ train.timetableData!.followingStops[i + 1].arrivalLine }}
</span> </span>
@@ -83,10 +83,11 @@
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, PropType } from '@vue/runtime-core'; import { computed, defineComponent, PropType } from '@vue/runtime-core';
import dateMixin from '@/mixins/dateMixin'; import dateMixin from '../../mixins/dateMixin';
import TrainStop from '@/scripts/interfaces/TrainStop'; import imageMixin from '../../mixins/imageMixin';
import Train from '../../scripts/interfaces/Train';
import TrainStop from '../../scripts/interfaces/TrainStop';
import StopDate from '../Global/StopDate.vue'; import StopDate from '../Global/StopDate.vue';
import Train from '@/scripts/interfaces/Train';
export default defineComponent({ export default defineComponent({
components: { StopDate }, components: { StopDate },
@@ -97,16 +98,10 @@ export default defineComponent({
}, },
}, },
mixins: [dateMixin], mixins: [dateMixin, imageMixin],
emits: ['click'], emits: ['click'],
data: () => ({
icons: {
warning: require('@/assets/icon-warning.svg'),
},
}),
setup(props) { setup(props) {
return { return {
lastConfirmed: computed(() => { lastConfirmed: computed(() => {
@@ -154,7 +149,7 @@ export default defineComponent({
onImageError(e: Event) { onImageError(e: Event) {
const imageEl = e.target as HTMLImageElement; const imageEl = e.target as HTMLImageElement;
imageEl.src = require('@/assets/unknown.png'); imageEl.src = this.getImage('unknown.png');
}, },
}, },
}); });
@@ -179,7 +174,6 @@ $stopNameClr: #22a8d1;
} }
.train-schedule { .train-schedule {
background-color: #202020;
padding: 0 0.25em; padding: 0 0.25em;
@include smallScreen() { @include smallScreen() {
@@ -192,10 +186,11 @@ $stopNameClr: #22a8d1;
display: flex; display: flex;
justify-content: center; justify-content: center;
} }
ul.stock-list { ul.stock-list {
display: flex; display: flex;
align-items: flex-end; align-items: flex-end;
overflow-x: auto; overflow: auto;
padding-bottom: 1em; padding-bottom: 1em;
li > div { li > div {
@@ -207,7 +202,6 @@ ul.stock-list {
.schedule-wrapper { .schedule-wrapper {
overflow-y: auto; overflow-y: auto;
max-height: 500px;
width: 100%; width: 100%;
z-index: 5; z-index: 5;
@@ -278,13 +272,14 @@ ul.stop_list > li.stop {
padding: 0 0.5em; padding: 0 0.5em;
&.sbl { &.sbl {
.stop-name,
.stop-date { .stop-date {
opacity: 0.7; display: none;
} }
.stop-name { .stop-name {
background-color: #333; background: none;
color: #aaa;
padding: 0;
} }
} }
@@ -381,8 +376,6 @@ ul.stop_list > li.stop {
text-align: center; text-align: center;
flex-wrap: wrap; flex-wrap: wrap;
padding: 0.15em 0;
} }
.stop-bar { .stop-bar {
+36 -72
View File
@@ -1,29 +1,27 @@
<template> <template>
<div class="train-stats" v-click-outside="closeStats"> <div class="train-stats" v-click-outside="closeStats">
<action-button class="stats_button" @click="toggleStatsOpen"> <action-button class="stats_button" @click="toggleStatsOpen">
<img :src="statsIcon" :alt="$t('trains.stats')" /> <img :src="getIcon('stats')" :alt="$t('trains.stats')" />
<p>{{ $t("trains.stats") }}</p> <p>{{ $t('trains.stats') }}</p>
</action-button> </action-button>
<transition name="stats-anim" class="stats_wrapper" tag="div"> <transition name="stats-anim" class="stats_wrapper" tag="div">
<div class="stats-body" v-if="trainStatsOpen"> <div class="stats-body" v-if="trainStatsOpen">
<h2 class="stats-header"> <h2 class="stats-header">
<img :src="statsIcon" :alt="$t('trains.stats')" /> <img :src="getIcon('stats')" :alt="$t('trains.stats')" />
{{ $t("trains.stats") }} {{ $t('trains.stats') }}
</h2> </h2>
<div class="stats-speed"> <div class="stats-speed">
<div class="title stats-title"> <div class="title stats-title">
{{ $t("trains.stats-speed") }} {{ $t('trains.stats-speed') }}
</div>
<div class="stats-content">
{{ speedStats.min }} | {{ speedStats.avg }} | {{ speedStats.max }}
</div> </div>
<div class="stats-content">{{ speedStats.min }} | {{ speedStats.avg }} | {{ speedStats.max }}</div>
</div> </div>
<div class="stats-length"> <div class="stats-length">
<div class="title stats-title"> <div class="title stats-title">
{{ $t("trains.stats-length") }} {{ $t('trains.stats-length') }}
</div> </div>
<div class="stats-content"> <div class="stats-content">
{{ timetableStats.min }} | {{ timetableStats.avg }} | {{ timetableStats.min }} | {{ timetableStats.avg }} |
@@ -33,15 +31,11 @@
<div class="stats-categories"> <div class="stats-categories">
<div class="title stats-title"> <div class="title stats-title">
{{ $t("trains.stats-categories") }} {{ $t('trains.stats-categories') }}
</div> </div>
<div class="category-list"> <div class="category-list">
<span <span class="category" v-for="[key, value] of categoryList" :key="key">
class="category"
v-for="[key, value] of categoryList"
:key="key"
>
<span class="category-type">{{ key }}</span> <span class="category-type">{{ key }}</span>
<span class="category-count">{{ value }}</span> <span class="category-count">{{ value }}</span>
</span> </span>
@@ -49,28 +43,22 @@
<div class="special-list"> <div class="special-list">
<span class="special twr"> <span class="special twr">
<span class="special-type">{{ <span class="special-type">{{ $t('trains.stats-special-twr') }}</span>
$t("trains.stats-special-twr")
}}</span>
<span class="special-count">{{ specialTrainCount[0] }}</span> <span class="special-count">{{ specialTrainCount[0] }}</span>
</span> </span>
<span class="special skr"> <span class="special skr">
<span class="special-type">{{ <span class="special-type">{{ $t('trains.stats-special-skr') }}</span>
$t("trains.stats-special-skr")
}}</span>
<span class="special-count">{{ specialTrainCount[1] }}</span> <span class="special-count">{{ specialTrainCount[1] }}</span>
</span> </span>
</div> </div>
</div> </div>
<div class="stats-locos"> <div class="stats-locos">
<div class="title stats-title">{{ $t("trains.stats-locos") }}</div> <div class="title stats-title">{{ $t('trains.stats-locos') }}</div>
<div class="loco-list stats-content"> <div class="loco-list stats-content">
<div class="loco-item" v-for="(loco, i) in locoList" :key="i"> <div class="loco-item" v-for="(loco, i) in locoList" :key="i">{{ loco[0] }} | {{ loco[1] }}</div>
{{ loco[0] }} | {{ loco[1] }}
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -79,13 +67,15 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import ActionButton from "@/components/Global/ActionButton.vue"; import { defineComponent, computed, inject } from 'vue';
import imageMixin from '../../mixins/imageMixin';
import Train from "@/scripts/interfaces/Train"; import Train from '../../scripts/interfaces/Train';
import { computed, defineComponent, inject } from "@vue/runtime-core"; import ActionButton from '../Global/ActionButton.vue';
export default defineComponent({ export default defineComponent({
components: { ActionButton }, components: { ActionButton },
mixins: [imageMixin],
props: { props: {
trains: { trains: {
type: Array as () => Train[], type: Array as () => Train[],
@@ -95,7 +85,6 @@ export default defineComponent({
data: () => ({ data: () => ({
trainStatsOpen: false, trainStatsOpen: false,
statsIcon: require("@/assets/icon-stats.svg"),
}), }),
methods: { methods: {
@@ -110,14 +99,11 @@ export default defineComponent({
setup(props) { setup(props) {
const speedStats = computed(() => { const speedStats = computed(() => {
if (props.trains.length == 0) return { avg: "0", min: "0", max: "0" }; if (props.trains.length == 0) return { avg: '0', min: '0', max: '0' };
const trainList = props.trains.filter((train) => train.timetableData); const trainList = props.trains.filter((train) => train.timetableData);
const avg = ( const avg = (trainList.reduce((acc, train) => acc + train.speed, 0) / trainList.length).toFixed(2);
trainList.reduce((acc, train) => acc + train.speed, 0) /
trainList.length
).toFixed(2);
const minMaxSpeed = trainList.reduce((acc, train) => { const minMaxSpeed = trainList.reduce((acc, train) => {
if (!train.timetableData) return acc; if (!train.timetableData) return acc;
@@ -136,32 +122,21 @@ export default defineComponent({
}); });
const timetableStats = computed(() => { const timetableStats = computed(() => {
if (props.trains.length == 0) return { avg: "0", min: "0", max: "0" }; if (props.trains.length == 0) return { avg: '0', min: '0', max: '0' };
const activeTrainsLength = props.trains.filter( const activeTrainsLength = props.trains.filter((train) => train.timetableData).length;
(train) => train.timetableData
).length;
const avg = ( const avg = (
props.trains.reduce( props.trains.reduce((acc, train) => (train.timetableData ? acc + train.timetableData.routeDistance : acc), 0) /
(acc, train) => activeTrainsLength
train.timetableData ? acc + train.timetableData.routeDistance : acc,
0
) / activeTrainsLength
).toFixed(2); ).toFixed(2);
const minMaxDistance = props.trains.reduce((acc, train) => { const minMaxDistance = props.trains.reduce((acc, train) => {
if (!train.timetableData) return acc; if (!train.timetableData) return acc;
acc[0] = acc[0] = !acc[0] || train.timetableData.routeDistance < acc[0] ? train.timetableData.routeDistance : acc[0];
!acc[0] || train.timetableData.routeDistance < acc[0]
? train.timetableData.routeDistance
: acc[0];
acc[1] = acc[1] = !acc[1] || train.timetableData.routeDistance > acc[1] ? train.timetableData.routeDistance : acc[1];
!acc[1] || train.timetableData.routeDistance > acc[1]
? train.timetableData.routeDistance
: acc[1];
return acc; return acc;
}, [] as any); }, [] as any);
@@ -178,9 +153,7 @@ export default defineComponent({
acc.set( acc.set(
train.timetableData.category, train.timetableData.category,
acc.get(train.timetableData.category) acc.get(train.timetableData.category) ? acc.get(train.timetableData.category) + 1 : 1
? acc.get(train.timetableData.category) + 1
: 1
); );
return acc; return acc;
@@ -193,35 +166,26 @@ export default defineComponent({
const map: Map<string, number> = props.trains.reduce((acc, train) => { const map: Map<string, number> = props.trains.reduce((acc, train) => {
if (!train.timetableData || !train.locoType) return acc; if (!train.timetableData || !train.locoType) return acc;
acc.set( acc.set(train.locoType, acc.get(train.locoType) ? acc.get(train.locoType) + 1 : 1);
train.locoType,
acc.get(train.locoType) ? acc.get(train.locoType) + 1 : 1
);
return acc; return acc;
}, new Map()); }, new Map());
const sorted = [...map.entries()] const sorted = [...map.entries()].sort((a, b) => b[1] - a[1]).filter((v, i) => i < 3);
.sort((a, b) => b[1] - a[1])
.filter((v, i) => i < 3);
return sorted; return sorted;
}); });
const specialTrainCount = computed(() => { const specialTrainCount = computed(() => {
const twrList = props.trains.filter( const twrList = props.trains.filter((train) => train.timetableData && train.timetableData.TWR);
(train) => train.timetableData && train.timetableData.TWR
);
const skrList = props.trains.filter( const skrList = props.trains.filter((train) => train.timetableData && train.timetableData.SKR);
(train) => train.timetableData && train.timetableData.SKR
);
return [twrList.length, skrList.length]; return [twrList.length, skrList.length];
}); });
/* Inject list from TrainsView for category filter */ /* Inject list from TrainsView for category filter */
const chosenTrainCategories = inject("chosenTrainCategories") as string[]; const chosenTrainCategories = inject('chosenTrainCategories') as string[];
return { return {
speedStats, speedStats,
@@ -229,14 +193,14 @@ export default defineComponent({
categoryList, categoryList,
locoList, locoList,
specialTrainCount, specialTrainCount,
chosenTrainCategories chosenTrainCategories,
}; };
}, },
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "../../styles/responsive"; @import '../../styles/responsive';
.stats-anim { .stats-anim {
&-enter-active, &-enter-active,
@@ -370,4 +334,4 @@ export default defineComponent({
justify-content: center; justify-content: center;
} }
} }
</style> </style>
+15 -57
View File
@@ -1,9 +1,5 @@
<template> <template>
<div class="train-table" @keydown.esc="closeTimetable"> <div class="train-table">
<button class="return-btn" @click="scrollToTop" v-if="showReturnButton">
<img :src="icons.arrowAsc" alt="return arrow" />
</button>
<transition name="anim" mode="out-in"> <transition name="anim" mode="out-in">
<div :key="store.dataStatuses.trains"> <div :key="store.dataStatuses.trains">
<Loading v-if="trains.length == 0 && store.dataStatuses.trains == 0" /> <Loading v-if="trains.length == 0 && store.dataStatuses.trains == 0" />
@@ -16,13 +12,11 @@
<li <li
class="train-row" class="train-row"
v-for="train in currentTrains" v-for="train in currentTrains"
:key="train.trainNo + train.driverId" :key="train.trainId"
@click="toggleTimetable(train)" @click.stop="selectModalTrain(train.trainId)"
@keydown.enter="toggleTimetable(train)" @keydown.enter="selectModalTrain(train.trainId)"
> >
<TrainInfo :train="train" /> <TrainInfo :train="train" />
<TrainSchedule v-if="chosenTrainId == getTrainId(train)" :train="train" ref="card-inner" tabindex="0" />
</li> </li>
</ul> </ul>
</div> </div>
@@ -31,27 +25,25 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, inject, Ref } from '@vue/runtime-core'; import { defineComponent, inject, Ref, computed } from 'vue';
import modalTrainMixin from '../../mixins/modalTrainMixin';
import defaultVehicleIconsJSON from '@/data/defaultVehicleIcons.json'; import returnBtnMixin from '../../mixins/returnBtnMixin';
import Train from '../../scripts/interfaces/Train';
import Train from '@/scripts/interfaces/Train'; import { useStore } from '../../store/store';
import TrainSchedule from '@/components/TrainsView/TrainSchedule.vue';
import TrainInfo from '@/components/TrainsView/TrainInfo.vue';
import returnBtnMixin from '@/mixins/returnBtnMixin';
import { useStore } from '@/store/store';
import Loading from '../Global/Loading.vue'; import Loading from '../Global/Loading.vue';
import TrainModal from '../Global/TrainModal.vue';
import TrainInfo from './TrainInfo.vue';
import TrainSchedule from './TrainSchedule.vue';
export default defineComponent({ export default defineComponent({
components: { components: {
TrainSchedule, TrainSchedule,
TrainInfo, TrainInfo,
Loading, Loading,
TrainModal,
}, },
mixins: [returnBtnMixin], mixins: [returnBtnMixin, modalTrainMixin],
props: { props: {
trains: { trains: {
@@ -60,18 +52,6 @@ export default defineComponent({
}, },
}, },
data: () => ({
defaultLocoImage: require('@/assets/unknown.png'),
icons: {
arrowAsc: require('@/assets/icon-arrow-asc.svg'),
arrowDesc: require('@/assets/icon-arrow-desc.svg'),
},
defaultVehicleIcons: defaultVehicleIconsJSON,
chosenTrainId: null as string | null,
}),
setup(props) { setup(props) {
const store = useStore(); const store = useStore();
@@ -103,15 +83,11 @@ export default defineComponent({
this.searchedTrain = query.trainNo.toString(); this.searchedTrain = query.trainNo.toString();
setTimeout(() => { setTimeout(() => {
this.chosenTrainId = query.driverName + <string>query.trainNo; this.selectModalTrain(query.driverName + <string>query.trainNo);
}, 20); }, 20);
} }
}, },
deactivated() {
this.chosenTrainId = null;
},
methods: { methods: {
enter(el: HTMLElement) { enter(el: HTMLElement) {
const maxHeight = getComputedStyle(el).height; const maxHeight = getComputedStyle(el).height;
@@ -137,24 +113,6 @@ export default defineComponent({
}, 10); }, 10);
}, },
toggleTimetable(train: Train, state?: boolean) {
const id = this.getTrainId(train);
if (state !== undefined) {
this.chosenTrainId = state ? id : null;
return;
}
this.chosenTrainId = this.chosenTrainId && this.chosenTrainId == id ? null : id;
},
closeTimetable() {
this.chosenTrainId = null;
},
getTrainId(train: Train) {
return train.driverName + train.trainNo.toString();
},
}, },
}); });
</script> </script>
+1 -1
View File
@@ -1,5 +1,5 @@
import { JournalFilterType } from "@/scripts/enums/JournalFilterType";
import { JournalFilter } from "vue"; import { JournalFilter } from "vue";
import { JournalFilterType } from "../scripts/enums/JournalFilterType";
export const journalTimetableFilters: JournalFilter[] = [ export const journalTimetableFilters: JournalFilter[] = [
{ {
+1 -1
View File
@@ -1,5 +1,5 @@
import { TrainFilterType } from "@/scripts/enums/TrainFilterType";
import { TrainFilter } from "vue"; import { TrainFilter } from "vue";
import { TrainFilterType } from "../scripts/enums/TrainFilterType";
export const trainFilters: TrainFilter[] = [ export const trainFilters: TrainFilter[] = [
{ {
View File
+17 -6
View File
@@ -10,6 +10,12 @@
"migration-warning": "Stacjownik services will be unavailable 2/06/2022 between 1-3am (CEST time) due to the migration of API hostings!", "migration-warning": "Stacjownik services will be unavailable 2/06/2022 between 1-3am (CEST time) due to the migration of API hostings!",
"migration-confirm": "Roger that!" "migration-confirm": "Roger that!"
}, },
"update": {
"title": "New Stacjownik version is available!",
"paragraph1": "Enjoy the application and may the green signal be with you!",
"release-link": "Click here to browse version changelog (GitHub)",
"confirm-button": "Understood!"
},
"data-status": { "data-status": {
"S1a-connection": "<b>S1a signal</b> <br> Cannot connect with Stacjownik API service!", "S1a-connection": "<b>S1a signal</b> <br> Cannot connect with Stacjownik API service!",
"S1a-sceneries": "<b>S1a signal</b> <br> Cannot load online stations data!", "S1a-sceneries": "<b>S1a signal</b> <br> Cannot load online stations data!",
@@ -185,9 +191,11 @@
"comment": "Exploitation comments for: ", "comment": "Exploitation comments for: ",
"table-limit": "For performance reasons there's a limit of 10 trains shown at the same time.", "table-limit": "For performance reasons there's a limit of 10 trains shown at the same time.",
"last-seen-now": "last seen: just now", "last-seen-now": "since now",
"last-seen-min": "last seen: one minute ago", "last-seen-min": "since one minute",
"last-seen-ago": "last seen: {minutes} mins ago" "last-seen-ago": "since {minutes} minutes",
"scenery-offline": "Offline ride"
}, },
"journal": { "journal": {
"title": "DISPATCHER HISTORY", "title": "DISPATCHER HISTORY",
@@ -198,7 +206,7 @@
"section-dispatchers": "DISPATCHERS", "section-dispatchers": "DISPATCHERS",
"search": "Search", "search": "Search",
"search-train": "Train no.", "search-train": "Train no. / #",
"search-driver": "Driver name", "search-driver": "Driver name",
"search-dispatcher": "Dispatcher name", "search-dispatcher": "Dispatcher name",
"search-station": "Scenery name", "search-station": "Scenery name",
@@ -238,6 +246,7 @@
"spawns": "OPEN SPAWNS", "spawns": "OPEN SPAWNS",
"timetables": "ACTIVE TIMETABLES", "timetables": "ACTIVE TIMETABLES",
"no-timetables": "No active timetables!", "no-timetables": "No active timetables!",
"offline": "Scenery is offline",
"no-users": "NO ACTIVE PLAYERS", "no-users": "NO ACTIVE PLAYERS",
"no-spawns": "NO OPEN SPAWNS", "no-spawns": "NO OPEN SPAWNS",
"no-scenery": "Oops! This scenery doesn't exist!", "no-scenery": "Oops! This scenery doesn't exist!",
@@ -258,12 +267,14 @@
"timetable-author-unknown": "Author unknown", "timetable-author-unknown": "Author unknown",
"req-level": "all dispatcher levels | dispatcher level {lvl} required | dispatcher level {lvl} required", "req-level": "all dispatcher levels | dispatcher level {lvl} required | dispatcher level {lvl} required",
"history-list-empty": "No recorded scenery history!" "history-list-empty": "No recorded scenery history!",
"forum-topic": "Official {name} forum topic"
}, },
"availability": { "availability": {
"title": "Availability", "title": "Availability",
"default": "in-game", "default": "in-game",
"nonDefault": "downloadable", "nonDefault": "additional",
"unavailable": "unavailable", "unavailable": "unavailable",
"nonPublic": "private", "nonPublic": "private",
"abandoned": "abandoned" "abandoned": "abandoned"
+17 -5
View File
@@ -11,6 +11,13 @@
"migration-confirm": "Przyjąłem!" "migration-confirm": "Przyjąłem!"
}, },
"update": {
"title": "Nowa wersja Stacjownika jest dostępna!",
"paragraph1": "Miłego korzystania z aplikacji i niech S2 będzie z wami!",
"release-link": "Kliknij, aby przejrzeć listę zmian (GitHub)",
"confirm-button": "Przyjąłem!"
},
"data-status": { "data-status": {
"S1a-connection": "<b>Sygnał S1a</b> <br> Błąd podczas próby połączenia się z API Stacjownika!", "S1a-connection": "<b>Sygnał S1a</b> <br> Błąd podczas próby połączenia się z API Stacjownika!",
"S1a-sceneries": "<b>Sygnał S1a</b> <br> Błąd podczas pobierania danych o sceneriach online!", "S1a-sceneries": "<b>Sygnał S1a</b> <br> Błąd podczas pobierania danych o sceneriach online!",
@@ -186,9 +193,11 @@
"comment": "Uwagi eksploatacyjne dla: ", "comment": "Uwagi eksploatacyjne dla: ",
"table-limit": "Dla płynności działania strony pokazanych jest tylko 10 pociągów zgodnie z wybranymi filtrami.", "table-limit": "Dla płynności działania strony pokazanych jest tylko 10 pociągów zgodnie z wybranymi filtrami.",
"last-seen-now": "ostatnio widziany: przed chwilą", "last-seen-now": "od niedawna",
"last-seen-min": "ostatnio widziany: minutę temu", "last-seen-min": "od minuty",
"last-seen-ago": "ostatnio widziany: {minutes} min. temu" "last-seen-ago": "od {minutes} minut",
"scenery-offline": "Przejazd offline"
}, },
"journal": { "journal": {
"title": "HISTORIA DYŻURÓW", "title": "HISTORIA DYŻURÓW",
@@ -199,7 +208,7 @@
"section-dispatchers": "DYŻURNI", "section-dispatchers": "DYŻURNI",
"search": "Szukaj", "search": "Szukaj",
"search-train": "Numer pociągu", "search-train": "Nr pociągu / #",
"search-driver": "Nick maszynisty", "search-driver": "Nick maszynisty",
"search-dispatcher": "Nick dyżurnego", "search-dispatcher": "Nick dyżurnego",
"search-station": "Nazwa scenerii", "search-station": "Nazwa scenerii",
@@ -239,6 +248,7 @@
"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!",
"offline": "Sceneria jest offline",
"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! Ta sceneria nie istnieje!", "no-scenery": "Ups! Ta sceneria nie istnieje!",
@@ -259,7 +269,9 @@
"timetable-author-unknown": "Autor nieznany", "timetable-author-unknown": "Autor nieznany",
"req-level": "ogólnodostępna | minimum {lvl} poziom dyżurnego | minimum {lvl} poziom dyżurnego", "req-level": "ogólnodostępna | minimum {lvl} poziom dyżurnego | minimum {lvl} poziom dyżurnego",
"history-list-empty": "Brak historii dla tej scenerii!" "history-list-empty": "Brak historii dla tej scenerii!",
"forum-topic": "Oficjalny wątek scenerii {name}"
}, },
"availability": { "availability": {
"title": "Dostępność", "title": "Dostępność",
+2 -2
View File
@@ -2,8 +2,8 @@ import { createApp, Directive, ref } from 'vue';
import App from './App.vue'; import App from './App.vue';
import router from './router'; import router from './router';
import enLang from '@/locales/en.json'; import enLang from './locales/en.json';
import plLang from '@/locales/pl.json'; import plLang from './locales/pl.json';
import { createI18n } from 'vue-i18n'; import { createI18n } from 'vue-i18n';
import { createPinia } from 'pinia'; import { createPinia } from 'pinia';
+13
View File
@@ -0,0 +1,13 @@
import { defineComponent } from 'vue';
export default defineComponent({
methods: {
getIcon(name: string, ext = 'svg') {
return new URL(`../assets/icon-${name}.${ext}`, import.meta.url).href;
},
getImage(name: string) {
return new URL(`../assets/${name}`, import.meta.url).href;
}
},
});
+30
View File
@@ -0,0 +1,30 @@
import { defineComponent } from 'vue';
import { useStore } from '../store/store';
export default defineComponent({
setup() {
return {
store: useStore(),
};
},
mounted() {
console.log('Mixin mounted');
},
computed: {
chosenTrain() {
return this.store.trainList.find((train) => train.trainId == this.store.chosenModalTrainId);
},
},
methods: {
selectModalTrain(trainId: string) {
this.store.chosenModalTrainId = trainId;
},
closeModal() {
this.store.chosenModalTrainId = undefined;
},
},
});
+26 -23
View File
@@ -1,31 +1,34 @@
import { defineComponent, h } from "vue"; import { defineComponent, h } from 'vue';
import imageMixin from './imageMixin';
export default defineComponent({ export default defineComponent({
data() { mixins: [imageMixin],
return {
icons: {
arrow: require('@/assets/icon-arrow-asc.svg'),
},
showReturnButton: false data() {
} return {
icons: {
arrow: this.getIcon('arrow-asc'),
},
showReturnButton: false,
};
},
methods: {
scrollToTop() {
window.scrollTo({ top: 0 });
}, },
methods: { handleScroll() {
scrollToTop() { this.showReturnButton = window.scrollY > window.innerHeight * 0.35;
window.scrollTo({ top: 0 });
},
handleScroll() {
this.showReturnButton = window.scrollY > window.innerHeight * 0.35;
}
}, },
},
activated() { activated() {
window.addEventListener('scroll', this.handleScroll); window.addEventListener('scroll', this.handleScroll);
}, },
deactivated() { deactivated() {
window.removeEventListener('scroll', this.handleScroll); window.removeEventListener('scroll', this.handleScroll);
}, },
}) });
+24 -8
View File
@@ -1,8 +1,11 @@
import Train from '@/scripts/interfaces/Train';
import TrainStop from '@/scripts/interfaces/TrainStop';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import Train from '../scripts/interfaces/Train';
import TrainStop from '../scripts/interfaces/TrainStop';
import imageMixin from './imageMixin';
export default defineComponent({ export default defineComponent({
mixins: [imageMixin],
data: () => ({ data: () => ({
STATS: { STATS: {
main: [ main: [
@@ -55,6 +58,23 @@ export default defineComponent({
: this.$t('trains.last-seen-ago', { minutes: diffMins }); : this.$t('trains.last-seen-ago', { minutes: diffMins });
}, },
displayTrainPosition(train: Train) {
let positionString = '';
positionString += this.$t('trains.current-scenery') + ' ';
if (train.currentStationHash) positionString += train.currentStationName + ' ';
else positionString += train['currentStationName'].replace(/.[a-zA-Z0-9]+.sc/, '') + ' (offline) ';
if (train.signal) positionString += this.$t('trains.current-signal') + ' ' + train.signal + ' ';
if (train.connectedTrack) positionString += this.$t('trains.current-track') + ' ' + train.connectedTrack + ' ';
if (train.distance) positionString += `(${this.displayDistance(train.distance)})`;
return positionString.charAt(0).toUpperCase() + positionString.slice(1);
},
displayStopList(stops: TrainStop[]): string | undefined { displayStopList(stops: TrainStop[]): string | undefined {
if (!stops) return ''; if (!stops) return '';
@@ -62,11 +82,7 @@ export default defineComponent({
.reduce((acc: string[], stop: TrainStop, i: number) => { .reduce((acc: string[], stop: TrainStop, i: number) => {
if (stop.stopType.includes('ph') && !stop.stopNameRAW.includes('po.')) if (stop.stopType.includes('ph') && !stop.stopNameRAW.includes('po.'))
acc.push(`<strong style='color:${stop.confirmed ? 'springgreen' : 'white'}'>${stop.stopName}</strong>`); acc.push(`<strong style='color:${stop.confirmed ? 'springgreen' : 'white'}'>${stop.stopName}</strong>`);
else if ( else if (i > 0 && i < stops.length - 1 && !/po\.|sbl/gi.test(stop.stopNameRAW))
i > 0 &&
i < stops.length - 1 &&
!/po\.|sbl/gi.test(stop.stopNameRAW)
)
acc.push(`<span style='color:${stop.confirmed ? 'springgreen' : 'lightgray'}'>${stop.stopName}</span>`); acc.push(`<span style='color:${stop.confirmed ? 'springgreen' : 'lightgray'}'>${stop.stopName}</span>`);
return acc; return acc;
}, []) }, [])
@@ -121,7 +137,7 @@ export default defineComponent({
onImageError(e: Event) { onImageError(e: Event) {
const imageEl = e.target as HTMLImageElement; const imageEl = e.target as HTMLImageElement;
imageEl.src = require('@/assets/unknown.png'); imageEl.src = this.getImage('unknown.png');
}, },
}, },
}); });
+10 -7
View File
@@ -1,42 +1,45 @@
import JournalDispatchersVue from '@/components/JournalView/JournalDispatchers.vue';
import JournalTimetablesVue from '@/components/JournalView/JournalTimetables.vue';
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'; import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
import JournalDispatchersVue from '../components/JournalView/JournalDispatchers.vue';
import JournalTimetablesVue from '../components/JournalView/JournalTimetables.vue';
const routes: Array<RouteRecordRaw> = [ const routes: Array<RouteRecordRaw> = [
{ {
path: '/', path: '/',
name: 'StationsView', name: 'StationsView',
component: () => import('@/views/StationsView.vue'), component: () => import('../views/StationsView.vue'),
}, },
{ {
path: '/trains', path: '/trains',
name: 'TrainsView', name: 'TrainsView',
component: () => import('@/views/TrainsView.vue'), component: () => import('../views/TrainsView.vue'),
props: (route) => ({ train: route.query.train, driver: route.query.driver }), props: (route) => ({ train: route.query.train, driver: route.query.driver }),
}, },
{ {
path: '/scenery', path: '/scenery',
name: 'SceneryView', name: 'SceneryView',
component: () => import('@/views/SceneryView.vue'), component: () => import('../views/SceneryView.vue'),
props: true, props: true,
}, },
{ {
path: '/journal', path: '/journal',
name: 'JournalView', name: 'JournalView',
component: () => import('@/views/JournalView.vue'), component: () => import('../views/JournalView.vue'),
children: [ children: [
{ {
path: '', path: '',
redirect: '/journal/timetables', name: 'JournalTimetables',
component: JournalTimetablesVue, component: JournalTimetablesVue,
alias: '/timetables',
}, },
{ {
path: 'dispatchers', path: 'dispatchers',
name: 'JournalDispatchers',
component: JournalDispatchersVue, component: JournalDispatchersVue,
props: (route) => ({ sceneryName: route.query.sceneryName, dispatcherName: route.query.dispatcherName }), props: (route) => ({ sceneryName: route.query.sceneryName, dispatcherName: route.query.dispatcherName }),
}, },
{ {
path: 'timetables', path: 'timetables',
name: 'JournalTimetables',
component: JournalTimetablesVue, component: JournalTimetablesVue,
props: (route) => ({ props: (route) => ({
trainNo: route.query.trainNo, trainNo: route.query.trainNo,
+2
View File
@@ -1,7 +1,9 @@
import TrainStop from "./TrainStop"; import TrainStop from "./TrainStop";
export default interface ScheduledTrain { export default interface ScheduledTrain {
trainId: string;
trainNo: number; trainNo: number;
driverName: string; driverName: string;
driverId: number; driverId: number;
currentStationName: string; currentStationName: string;
+5 -4
View File
@@ -1,6 +1,6 @@
import { Availability } from "@/store/storeTypes"; import { Availability } from '../../store/storeTypes';
import ScheduledTrain from "./ScheduledTrain"; import ScheduledTrain from './ScheduledTrain';
import StationRoutes from "./StationRoutes"; import StationRoutes from './StationRoutes';
export default interface Station { export default interface Station {
name: string; name: string;
@@ -53,9 +53,10 @@ export default interface Station {
driverName: string; driverName: string;
driverId: number; driverId: number;
trainNo: number; trainNo: number;
trainId: string;
stopStatus?: string; stopStatus?: string;
}[]; }[];
scheduledTrains?: ScheduledTrain[]; scheduledTrains?: ScheduledTrain[];
} };
} }
+3 -1
View File
@@ -1,6 +1,8 @@
import TrainStop from "@/scripts/interfaces/TrainStop"; import TrainStop from './TrainStop';
export default interface Train { export default interface Train {
trainId: string;
mass: number; mass: number;
length: number; length: number;
speed: number; speed: number;
@@ -0,0 +1,41 @@
export interface Author {
login: string;
id: number;
node_id: string;
avatar_url: string;
gravatar_id: string;
url: string;
html_url: string;
followers_url: string;
following_url: string;
gists_url: string;
starred_url: string;
subscriptions_url: string;
organizations_url: string;
repos_url: string;
events_url: string;
received_events_url: string;
type: string;
site_admin: boolean;
}
export interface ReleaseAPIData {
url: string;
assets_url: string;
upload_url: string;
html_url: string;
id: number;
author: Author;
node_id: string;
tag_name: string;
target_commitish: string;
name: string;
draft: boolean;
prerelease: boolean;
created_at: Date;
published_at: Date;
assets: any[];
tarball_url: string;
zipball_url: string;
body: string;
}
+2 -2
View File
@@ -1,5 +1,5 @@
import Station from '@/scripts/interfaces/Station'; import Filter from '../interfaces/Filter';
import Filter from '@/scripts/interfaces/Filter'; import Station from '../interfaces/Station';
import StorageManager from './storageManager'; import StorageManager from './storageManager';
const sortStations = (a: Station, b: Station, sorter: { index: number; dir: number }) => { const sortStations = (a: Station, b: Station, sorter: { index: number; dir: number }) => {
+4 -1
View File
@@ -1,5 +1,8 @@
export const URLs = { export const URLs = {
stacjownikAPI: process.env.VUE_APP_API_DEV != 1 ? 'https://stacjownik.eu-4.evennode.com' : 'http://localhost:3000', stacjownikAPI:
import.meta.env.VITE_APP_API_DEV == 1 && !import.meta.env.PROD
? 'http://localhost:3000'
: 'https://stacjownik.eu-4.evennode.com',
stacjownikAPIDev: 'localhost:3000', stacjownikAPIDev: 'localhost:3000',
// trains: "https://api.td2.info.pl:9640/?method=getTrainsOnline", // trains: "https://api.td2.info.pl:9640/?method=getTrainsOnline",
// getTimetableURL: (trainNo: string | number, region = "eu") => `https://api.td2.info.pl:9640/?method=readFromSWDR&value=getTimetable%3B${trainNo}%3B${region}` // getTimetableURL: (trainNo: string | number, region = "eu") => `https://api.td2.info.pl:9640/?method=readFromSWDR&value=getTimetable%3B${trainNo}%3B${region}`
+2
View File
@@ -159,6 +159,8 @@ export function getScheduledTrain(train: Train, trainStopIndex: number, stationN
return { return {
trainNo: train.trainNo, trainNo: train.trainNo,
trainId: train.trainId,
driverName: train.driverName, driverName: train.driverName,
driverId: train.driverId, driverId: train.driverId,
currentStationName: train.currentStationName, currentStationName: train.currentStationName,
-6
View File
@@ -1,6 +0,0 @@
/* eslint-disable */
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
+28 -19
View File
@@ -1,20 +1,20 @@
import { DataStatus } from '@/scripts/enums/DataStatus';
import StationAPIData from '@/scripts/interfaces/api/StationAPIData';
import ScheduledTrain from '@/scripts/interfaces/ScheduledTrain';
import Station from '@/scripts/interfaces/Station';
import StationRoutes from '@/scripts/interfaces/StationRoutes';
import Train from '@/scripts/interfaces/Train';
import { URLs } from '@/scripts/utils/apiURLs';
import {
getLocoURL,
getScheduledTrain,
getStatusID,
getStatusTimestamp,
parseSpawns,
} from '@/scripts/utils/storeUtils';
import axios from 'axios'; import axios from 'axios';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { io } from 'socket.io-client'; import { io } from 'socket.io-client';
import { DataStatus } from '../scripts/enums/DataStatus';
import StationAPIData from '../scripts/interfaces/api/StationAPIData';
import ScheduledTrain from '../scripts/interfaces/ScheduledTrain';
import Station from '../scripts/interfaces/Station';
import StationRoutes from '../scripts/interfaces/StationRoutes';
import Train from '../scripts/interfaces/Train';
import { URLs } from '../scripts/utils/apiURLs';
import {
getLocoURL,
getStatusTimestamp,
getStatusID,
getScheduledTrain,
parseSpawns,
} from '../scripts/utils/storeUtils';
import { APIData, StationJSONData, StoreState } from './storeTypes'; import { APIData, StationJSONData, StoreState } from './storeTypes';
export const useStore = defineStore('store', { export const useStore = defineStore('store', {
@@ -41,6 +41,8 @@ export const useStore = defineStore('store', {
driverStatsName: '', driverStatsName: '',
driverStatsData: undefined, driverStatsData: undefined,
chosenModalTrainId: undefined,
dataStatuses: { dataStatuses: {
connection: DataStatus.Loading, connection: DataStatus.Loading,
sceneries: DataStatus.Loading, sceneries: DataStatus.Loading,
@@ -56,7 +58,7 @@ export const useStore = defineStore('store', {
setTrainsOnlineData() { setTrainsOnlineData() {
const { trains } = this.apiData; const { trains } = this.apiData;
if (!trains) return []; if (!trains) return [];
this.trainList = trains this.trainList = trains
.filter( .filter(
@@ -70,6 +72,8 @@ export const useStore = defineStore('store', {
const timetable = train.timetable; const timetable = train.timetable;
return { return {
trainId: train.driverName + train.trainNo.toString(),
trainNo: train.trainNo, trainNo: train.trainNo,
mass: train.mass, mass: train.mass,
length: train.length, length: train.length,
@@ -200,7 +204,12 @@ export const useStore = defineStore('store', {
(train) => (train) =>
train?.region === this.region.id && train.online && train.currentStationName === stationAPIData.stationName train?.region === this.region.id && train.online && train.currentStationName === stationAPIData.stationName
) )
.map((train) => ({ driverName: train.driverName, driverId: train.driverId, trainNo: train.trainNo })); .map((train) => ({
driverName: train.driverName,
driverId: train.driverId,
trainNo: train.trainNo,
trainId: train.trainId,
}));
}, },
setStationsOnlineInfo() { setStationsOnlineInfo() {
@@ -334,7 +343,7 @@ export const useStore = defineStore('store', {
transports: ['websocket', 'polling'], transports: ['websocket', 'polling'],
rememberUpgrade: true, rememberUpgrade: true,
reconnection: true, reconnection: true,
timeout: 10000 timeout: 10000,
}); });
socket.on('connect_error', (err) => { socket.on('connect_error', (err) => {
@@ -377,13 +386,13 @@ export const useStore = defineStore('store', {
return; return;
} }
this.dataStatuses.sceneries = DataStatus.Loaded; this.dataStatuses.sceneries = DataStatus.Loaded;
this.dataStatuses.trains = !this.apiData.trains ? DataStatus.Warning : DataStatus.Loaded; this.dataStatuses.trains = !this.apiData.trains ? DataStatus.Warning : DataStatus.Loaded;
this.dataStatuses.dispatchers = !this.apiData.dispatchers ? DataStatus.Warning : DataStatus.Loaded; this.dataStatuses.dispatchers = !this.apiData.dispatchers ? DataStatus.Warning : DataStatus.Loaded;
this.setTrainsOnlineData(); this.setTrainsOnlineData();
this.setStationsOnlineInfo(); this.setStationsOnlineInfo();
}, },
}, },
}); });
+10 -7
View File
@@ -1,11 +1,12 @@
import { DataStatus } from '@/scripts/enums/DataStatus';
import { DispatcherStatsAPIData } from '@/scripts/interfaces/api/DispatcherStatsAPIData';
import { DriverStatsAPIData } from '@/scripts/interfaces/api/DriverStatsAPIData';
import StationAPIData from '@/scripts/interfaces/api/StationAPIData';
import TrainAPIData from '@/scripts/interfaces/api/TrainAPIData';
import Station from '@/scripts/interfaces/Station';
import Train from '@/scripts/interfaces/Train';
import { Socket } from 'socket.io-client'; import { Socket } from 'socket.io-client';
import { DataStatus } from '../scripts/enums/DataStatus';
import { DispatcherStatsAPIData } from '../scripts/interfaces/api/DispatcherStatsAPIData';
import { DriverStatsAPIData } from '../scripts/interfaces/api/DriverStatsAPIData';
import StationAPIData from '../scripts/interfaces/api/StationAPIData';
import TrainAPIData from '../scripts/interfaces/api/TrainAPIData';
import Station from '../scripts/interfaces/Station';
import Train from '../scripts/interfaces/Train';
export type Availability = 'default' | 'unavailable' | 'nonPublic' | 'abandoned' | 'nonDefault'; export type Availability = 'default' | 'unavailable' | 'nonPublic' | 'abandoned' | 'nonDefault';
@@ -30,6 +31,8 @@ export interface StoreState {
driverStatsName: string; driverStatsName: string;
driverStatsData?: DriverStatsAPIData; driverStatsData?: DriverStatsAPIData;
chosenModalTrainId?: string;
dataStatuses: { dataStatuses: {
connection: DataStatus; connection: DataStatus;
sceneries: DataStatus; sceneries: DataStatus;
+28
View File
@@ -0,0 +1,28 @@
.badge {
font-weight: 600;
display: inline-block;
padding: 0;
background: #585858;
margin: 0.25em;
span {
display: inline-block;
padding: 0.2em 0.4em;
}
&-none {
font-weight: 600;
padding: 0.2em 0.4em;
background: firebrick;
text-align: center;
@include smallScreen() {
font-size: 1em;
}
}
}
+5 -7
View File
@@ -1,21 +1,19 @@
<template> <template>
<div class="error-view"> <div class="error-view">
<div class="container"> <div class="container">
<img :src="icons.error" alt="error" /> <img :src="getIcon('error')" alt="error" />
<div class="desc">Z powodu błędu w zapisywaniu rozkładów jazdy w tej zakładce można do odwołania nacieszyć się animacją sygnału S1a. Jak naprawię to będzie :)</div> <div class="desc">Z powodu błędu w zapisywaniu rozkładów jazdy w tej zakładce można do odwołania nacieszyć się animacją sygnału S1a. Jak naprawię to będzie :)</div>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from '@vue/runtime-core'; import { defineComponent } from "vue";
import imageMixin from "../mixins/imageMixin";
export default defineComponent({ export default defineComponent({
data: () => ({ mixins: [imageMixin]
icons: {
error: require('@/assets/icon-error.svg'),
},
}),
}); });
</script> </script>
+3 -4
View File
@@ -21,13 +21,12 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import JournalTimetables from '@/components/JournalView/JournalTimetables.vue';
import { defineComponent, ref } from 'vue'; import { defineComponent, ref } from 'vue';
import JournalDispatchers from '@/components/JournalView/JournalDispatchers.vue'; import JournalDispatchers from '../components/JournalView/JournalDispatchers.vue';
import JournalTimetables from '../components/JournalView/JournalTimetables.vue';
export default defineComponent({ export default defineComponent({
components: { JournalTimetables, JournalDispatchers }, components: { JournalDispatchers, JournalTimetables },
setup() { setup() {
const journalTypeChosen = ref('journalTimetables'); const journalTypeChosen = ref('journalTimetables');
+17 -40
View File
@@ -12,7 +12,7 @@
<div class="scenery-left"> <div class="scenery-left">
<div class="scenery-actions"> <div class="scenery-actions">
<button v-if="!timetableOnly" class="back-btn btn" :title="$t('scenery.return-btn')" @click="navigateTo('/')"> <button v-if="!timetableOnly" class="back-btn btn" :title="$t('scenery.return-btn')" @click="navigateTo('/')">
<img :src="icons.back" alt="Back to scenery" /> <img :src="getIcon('back')" alt="Back to scenery" />
</button> </button>
</div> </div>
@@ -41,19 +41,17 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import SceneryInfo from '@/components/SceneryView/SceneryInfo.vue'; import { computed, defineComponent } from 'vue';
import SceneryTimetable from '@/components/SceneryView/SceneryTimetable.vue';
import SceneryTimetablesHistory from '../components/SceneryView/SceneryTimetablesHistory.vue';
import SceneryDispatchersHistory from '@/components/SceneryView/SceneryDispatchersHistory.vue';
import SceneryHeader from '@/components/SceneryView/SceneryHeader.vue';
import ActionButton from '@/components/Global/ActionButton.vue';
import { computed, defineComponent, ref } from '@vue/runtime-core';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import routerMixin from '../mixins/routerMixin';
import { useStore } from '@/store/store'; import { useStore } from '../store/store';
import routerMixin from '@/mixins/routerMixin'; import SceneryInfo from '../components/SceneryView/SceneryInfo.vue';
import SceneryHeader from '../components/SceneryView/SceneryHeader.vue';
import SceneryTimetable from '../components/SceneryView/SceneryTimetable.vue';
import SceneryTimetablesHistory from '../components/SceneryView/SceneryTimetablesHistory.vue';
import SceneryDispatchersHistory from '../components/SceneryView/SceneryDispatchersHistory.vue';
import ActionButton from '../components/Global/ActionButton.vue';
import imageMixin from '../mixins/imageMixin';
enum SceneryViewMode { enum SceneryViewMode {
'TIMETABLES_ACTIVE', 'TIMETABLES_ACTIVE',
@@ -70,15 +68,8 @@ export default defineComponent({
SceneryTimetablesHistory, SceneryTimetablesHistory,
SceneryDispatchersHistory, SceneryDispatchersHistory,
}, },
mixins: [routerMixin, imageMixin],
mixins: [routerMixin],
data: () => ({ data: () => ({
icons: {
user: require('@/assets/icon-user.svg'),
back: require('@/assets/icon-back.svg'),
},
viewModes: [ viewModes: [
{ {
id: 'scenery.option-active-timetables', id: 'scenery.option-active-timetables',
@@ -93,32 +84,22 @@ export default defineComponent({
component: 'SceneryDispatchersHistory', component: 'SceneryDispatchersHistory',
}, },
], ],
sceneryViewMode: SceneryViewMode, sceneryViewMode: SceneryViewMode,
selectedCheckpoint: '', selectedCheckpoint: '',
currentViewCompontent: 'SceneryTimetable', currentViewCompontent: 'SceneryTimetable',
onlineFrom: -1, onlineFrom: -1,
}), }),
activated() { activated() {
this.loadSelectedCheckpoint(); this.loadSelectedCheckpoint();
}, },
setup() { setup() {
const route = useRoute(); const route = useRoute();
const store = useStore(); const store = useStore();
const timetableOnly = computed(() => (route.query['timetable_only'] == '1' ? true : false)); const timetableOnly = computed(() => (route.query['timetable_only'] == '1' ? true : false));
const isComponentVisible = computed(() => route.path === '/scenery'); const isComponentVisible = computed(() => route.path === '/scenery');
const stationInfo = computed(() => { const stationInfo = computed(() => {
return store.stationList.find((station) => station.name === route.query.station?.toString().replace(/_/g, ' ')); return store.stationList.find((station) => station.name === route.query.station?.toString().replace(/_/g, ' '));
}); });
return { return {
timetableOnly, timetableOnly,
isComponentVisible, isComponentVisible,
@@ -126,19 +107,15 @@ export default defineComponent({
store, store,
}; };
}, },
methods: { methods: {
setViewMode(componentName: string) { setViewMode(componentName: string) {
this.currentViewCompontent = componentName; this.currentViewCompontent = componentName;
}, },
loadSelectedCheckpoint() { loadSelectedCheckpoint() {
if (!this.stationInfo?.generalInfo?.checkpoints) return; if (!this.stationInfo?.generalInfo?.checkpoints) return;
if (this.stationInfo.generalInfo.checkpoints.length == 0) return; if (this.stationInfo.generalInfo.checkpoints.length == 0) return;
this.selectedCheckpoint = this.stationInfo.generalInfo.checkpoints[0].checkpointName; this.selectedCheckpoint = this.stationInfo.generalInfo.checkpoints[0].checkpointName;
}, },
selectCheckpoint(cp: { checkpointName: string }) { selectCheckpoint(cp: { checkpointName: string }) {
this.selectedCheckpoint = cp.checkpointName; this.selectedCheckpoint = cp.checkpointName;
}, },
@@ -283,15 +260,15 @@ button.back-btn {
border-radius: 1em; border-radius: 1em;
height: auto; height: auto;
} }
.info-actions {
flex-wrap: wrap;
}
} }
@include smallScreen { @include smallScreen {
.scenery-left, .scenery-right { .scenery-left {
max-height: 100vh; max-height: 100vh;
} }
.scenery-right {
height: 100vh;
}
} }
</style> </style>
+10 -15
View File
@@ -3,7 +3,7 @@
<div class="wrapper"> <div class="wrapper">
<div class="body"> <div class="body">
<div class="options-bar"> <div class="options-bar">
<FilterCard <StationFilterCard
:showCard="filterCardOpen" :showCard="filterCardOpen"
:exit="closeCard" :exit="closeCard"
@changeFilterValue="changeFilterValue" @changeFilterValue="changeFilterValue"
@@ -25,30 +25,25 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import Station from '@/scripts/interfaces/Station';
import StorageManager from '@/scripts/managers/storageManager'; import inputData from '../data/options.json';
import StationFilterManager from '@/scripts/managers/stationFilterManager';
import inputData from '@/data/options.json';
import StationTable from '@/components/StationsView/StationTable.vue';
import FilterCard from '@/components/StationsView/StationFilterCard.vue';
import SelectBox from '@/components/Global/SelectBox.vue';
import { computed, ComputedRef, defineComponent, reactive } from 'vue'; import { computed, ComputedRef, defineComponent, reactive } from 'vue';
import { useStore } from '@/store/store'; import { useStore } from '../store/store';
import StationFilterManager from '../scripts/managers/stationFilterManager';
import Station from '../scripts/interfaces/Station';
import StorageManager from '../scripts/managers/storageManager';
import StationTable from '../components/StationsView/StationTable.vue';
import StationFilterCard from '../components/StationsView/StationFilterCard.vue';
import SelectBox from '../components/Global/SelectBox.vue';
export default defineComponent({ export default defineComponent({
components: { components: {
StationTable, StationTable,
FilterCard, StationFilterCard,
SelectBox, SelectBox,
}, },
data: () => ({ data: () => ({
trainIcon: require('@/assets/icon-train.svg'),
timetableIcon: require('@/assets/icon-timetable.svg'),
dolarIcon: require('@/assets/icon-dolar.svg'),
filterCardOpen: false, filterCardOpen: false,
modalHidden: true, modalHidden: true,
STORAGE_KEY: 'options_saved', STORAGE_KEY: 'options_saved',
+8 -11
View File
@@ -11,16 +11,14 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { computed, ComputedRef, defineComponent, PropType, provide, reactive, ref, TrainFilter } from 'vue'; import { computed, ComputedRef, defineComponent, provide, reactive, ref, TrainFilter } from 'vue';
import { filteredTrainList } from '@/scripts/managers/trainFilterManager'; import TrainOptions from '../components/TrainsView/TrainOptions.vue';
import { trainFilters } from '@/data/trainOptions'; import TrainStats from '../components/TrainsView/TrainStats.vue';
import TrainTable from '../components/TrainsView/TrainTable.vue';
import Train from '@/scripts/interfaces/Train'; import { trainFilters } from '../data/trainOptions';
import TrainTable from '@/components/TrainsView/TrainTable.vue'; import Train from '../scripts/interfaces/Train';
import TrainStats from '@/components/TrainsView/TrainStats.vue'; import { filteredTrainList } from '../scripts/managers/trainFilterManager';
import TrainOptions from '@/components/TrainsView/TrainOptions.vue'; import { useStore } from '../store/store';
import { useStore } from '@/store/store';
export default defineComponent({ export default defineComponent({
components: { components: {
@@ -42,7 +40,6 @@ export default defineComponent({
}, },
data: () => ({ data: () => ({
statsIcon: require('@/assets/icon-stats.svg'),
trainStatsOpen: false, trainStatsOpen: false,
}), }),
+19
View File
@@ -0,0 +1,19 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
interface ImportMetaEnv {
readonly VITE_APP_API_DEV: number;
readonly VITE_APP_WS_DEV: number;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
+1 -4
View File
@@ -26,8 +26,5 @@ declare module '@vue/runtime-core' {
isActive: boolean; isActive: boolean;
} }
interface JournalSearcher {
id: string;
value: string;
}
} }
+16 -29
View File
@@ -1,43 +1,30 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "esnext", "target": "ESNext",
"module": "esnext", "useDefineForClassFields": true,
"module": "ESNext",
"moduleResolution": "Node",
"strict": true, "strict": true,
"jsx": "preserve", "jsx": "preserve",
"importHelpers": true,
"suppressImplicitAnyIndexErrors": true,
"noImplicitAny": false,
"moduleResolution": "node",
"experimentalDecorators": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true, "sourceMap": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"baseUrl": ".", "isolatedModules": true,
"types": [ "esModuleInterop": true,
"webpack-env"
],
"paths": {
"@/*": [
"src/*"
]
},
"lib": [ "lib": [
"esnext", "ESNext",
"dom", "DOM"
"dom.iterable", ],
"scripthost" "skipLibCheck": true
]
}, },
"include": [ "include": [
"src/**/*.ts", "src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx", "src/**/*.tsx",
"src/**/*.vue", "src/**/*.vue"
"src/**/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
], ],
"exclude": [ "references": [
"node_modules" {
"path": "./tsconfig.node.json"
}
] ]
} }
+9
View File
@@ -0,0 +1,9 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}
+34
View File
@@ -0,0 +1,34 @@
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [vue()],
});
// PWA
// VitePWA({
// registerType: 'autoUpdate',
// workbox: {
// globPatterns: ['**/*.{js,css,html,png,svg,img}'],
// runtimeCaching: [
// {
// urlPattern: new RegExp('^https://stacjownik.eu-4.evennode.com/api/getSceneries'),
// handler: 'NetworkFirst',
// options: {
// cacheName: 'sceneries-cache',
// expiration: {
// maxEntries: 200,
// maxAgeSeconds: 60 * 60 * 24 * 60, // <== 60 days
// },
// cacheableResponse: {
// statuses: [0, 200],
// },
// },
// },
// ],
// },
// devOptions: {
// enabled: true,
// },
// }),
-3
View File
@@ -1,3 +0,0 @@
// module.exports = {
// publicPath: process.env.NODE_ENV === "production" ? "/dist" : "/",
// };
+1490
View File
File diff suppressed because it is too large Load Diff