Compare commits

...

63 Commits

Author SHA1 Message Date
Spythere 91f4c6bc57 Merge do wersji 1.16.2 2023-07-01 01:25:10 +02:00
Spythere c133eb060b bump: 1.16.2 2023-07-01 01:20:04 +02:00
Spythere 7ffc169d8a hotfix 2023-07-01 01:19:50 +02:00
Spythere 1b85cc5f58 poprawki dziennika rj / dr 2023-06-30 20:50:03 +02:00
Spythere 72ff857fff dodatkowe info o postojach w dzienniku RJ 2023-06-27 02:52:41 +02:00
Spythere 96d64e77fc feature: nieskończona lista historii dr/rj scenerii 2023-06-24 13:46:37 +02:00
Spythere 6ceae3f161 revamp tabeli historii dyżurów 2023-06-23 14:19:28 +02:00
Spythere 8e8e27658c Merge do wersji 1.16.1
Wersja 1.16.1
2023-06-22 19:27:39 +02:00
Spythere 9b6ace394a bump v.1.16.1 2023-06-21 18:36:21 +02:00
Spythere 6cfeaa91bf responsywność dailyStats 2023-06-21 18:35:23 +02:00
Spythere 08b208aeaa fix tłumaczeń 2023-06-21 18:31:16 +02:00
Spythere a089b5275b hotfix daily stats 2023-06-21 18:30:02 +02:00
Spythere 8425cd4371 przyjazdy/odjazdy stacji pośrednich RJ w dzienniku 2023-06-21 18:26:16 +02:00
Spythere dbdc517b87 fix tłumaczeń 2023-06-21 17:32:36 +02:00
Spythere e271358a27 fix timetable id 2023-06-21 17:19:31 +02:00
Spythere 66262e3fcd dodatkowe statystyki dnia 2023-06-21 17:16:02 +02:00
Spythere 5b2b6bdea2 bump 1.16.0.1 2023-06-16 01:17:33 +02:00
Spythere c8587de6d9 npm update 2023-06-15 15:28:32 +02:00
Spythere 1f376085f2 feature: info o elektryfikacji spawnu na scenerii 2023-06-15 15:28:12 +02:00
Spythere f28600a7fa Merge do wersji 1.16.0
Wersja 1.16.0
2023-06-12 01:33:45 +02:00
Spythere d59ead87e6 bump v1.16.0 2023-06-12 01:21:49 +02:00
Spythere 34d91bc800 poprawki w pokazywaniu statystyk 2023-06-12 01:19:31 +02:00
Spythere cf9991d8a0 layout filtrów dzienników 2023-06-12 00:51:17 +02:00
Spythere 4ffb79d62b poprawki pobierania danych 2023-06-11 21:47:50 +02:00
Spythere d9f5edb4fe poprawki tłumaczeń 2023-06-11 21:47:22 +02:00
Spythere 1b2112430a feature: długości szlaków po kliknięciu 2023-06-08 23:35:57 +02:00
Spythere 0a972a23ef fix: asynchroniczność pobierania danych z API 2023-06-04 13:35:53 +02:00
Spythere 6d52724d06 zapamiętywanie stanu statystyk dnia 2023-06-04 12:19:46 +02:00
Spythere 99415c35d3 rozbudowane filtry dziennika RJ 2023-06-04 12:06:15 +02:00
Spythere c3f687d439 hotfixy dzienników 2023-06-04 01:45:58 +02:00
Spythere 266edfd6e6 reorder typów danych 2023-06-04 00:33:43 +02:00
Spythere d32d5ad91b poprawki dzienników 2023-06-03 18:55:44 +02:00
Spythere c3481470cb optymalizacja zapytań; filtr scenerii pocz. 2023-06-03 15:49:15 +02:00
Spythere 57e88b9abc Merge do wersji 1.15.1
Wersja 1.15.1
2023-06-02 20:09:43 +02:00
Spythere 44ebf53798 poprawki pwa 2023-06-02 20:06:25 +02:00
Spythere 145dc72b6b pwa: zmiana na autoUpdate 2023-06-02 19:41:33 +02:00
Spythere b7f3761940 Merge do wersji 1.15.0
Wersja 1.15.0
2023-06-02 01:16:37 +02:00
Spythere ea7c49dfb3 bump: 1.15.0 2023-06-02 01:10:01 +02:00
Spythere 5d6785813a fix: odznaki TWR/SKR; dodano do dziennika 2023-06-02 01:07:44 +02:00
Spythere a0054aed14 fix: optymalizacja zapytań historii RJ scenerii 2023-06-02 00:51:03 +02:00
Spythere 471e6f5216 Wersja 1.14.3
Wersja 1.14.3
2023-05-18 02:42:27 +02:00
Spythere a617eef00e bump 1.14.3 2023-05-18 02:38:13 +02:00
Spythere 38e700ecd6 migracja z route na routeNames 2023-05-18 02:37:27 +02:00
Spythere da1be0e10a Wersja 1.14.2 2023-05-17 02:24:05 +02:00
Spythere f49bb12948 bump 1.14.2 2023-05-17 02:21:13 +02:00
Spythere 02673a3d70 poprawki filtrów scenerii 2023-05-16 14:27:31 +02:00
Spythere 4ddc7345df poprawki filtrów RJ 2023-05-16 02:40:08 +02:00
Spythere 5d822684c0 feature: rozszerzone filtry RJ 2023-05-14 15:05:51 +02:00
Spythere 69fa15c70a v1.14.1
v1.14.1
2023-04-13 19:25:30 +02:00
Spythere 9192067388 1.14.1 2023-04-13 19:21:36 +02:00
Spythere 2b41e5b857 hotfix 2023-04-13 19:21:24 +02:00
Spythere 674680ff14 1.14: hotfixy
1.14 hotfixy
2023-04-13 19:15:38 +02:00
Spythere 475bd2ff10 przeniesienie skrótów do widoku scenerii 2023-04-13 19:11:44 +02:00
Spythere 074d1eb155 Hotfix tłumaczeń 2023-04-13 18:50:00 +02:00
Spythere 378393de89 Wersja 1.14.0
Wersja 1.14.0
2023-04-13 15:45:39 +02:00
Spythere 03e61083a7 tłumaczenie stopki 2023-04-13 15:37:34 +02:00
Spythere 0b746fce8c hotfix headera 2023-04-13 15:32:41 +02:00
Spythere 5883e710be bump wersji: 1.14 2023-04-13 15:26:22 +02:00
Spythere 3d0695a17b Poprawki w headerze i stopce 2023-04-13 15:25:39 +02:00
Spythere 4adb76eeb0 feature: podpisy status indicatorów dla RJ 2023-04-11 00:33:14 +02:00
Spythere 4c41076519 feature: skrót posterunku w tabelce 2023-04-09 01:37:11 +02:00
Spythere 77f61d17fd hotfix: wygląd badge'a 2023-04-09 00:19:53 +02:00
Spythere 032a84cbcf feature: historia zmian w zestawieniu pociągów 2023-03-20 22:39:23 +01:00
66 changed files with 13859 additions and 13457 deletions
+11390 -11386
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "stacjownik", "name": "stacjownik",
"version": "1.13.0", "version": "1.16.2",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
+5
View File
@@ -91,6 +91,11 @@ footer.app_footer {
max-width: 100%; max-width: 100%;
padding: 0.5em; padding: 0.5em;
img {
width: 1.1em;
vertical-align: text-bottom;
}
z-index: 10; z-index: 10;
background: #111; background: #111;
+2
View File
@@ -23,6 +23,8 @@
<a href="https://td2.info.pl/profile/?u=20777" target="_blank">Spythere</a> <a href="https://td2.info.pl/profile/?u=20777" target="_blank">Spythere</a>
{{ new Date().getUTCFullYear() }} | {{ new Date().getUTCFullYear() }} |
<a :href="releaseURL" target="_blank">v{{ VERSION }}{{ isOnProductionHost ? '' : 'dev' }}</a> <a :href="releaseURL" target="_blank">v{{ VERSION }}{{ isOnProductionHost ? '' : 'dev' }}</a>
<br />
<a href="https://discord.gg/x2mpNN3svk"><img :src="getIcon('discord', 'png')" alt="">&nbsp;<b>{{ $t('footer.discord') }}</b></a>
<div style="display: none">&int; ukryta taktyczna całka do programowania w HTMLu</div> <div style="display: none">&int; ukryta taktyczna całka do programowania w HTMLu</div>
</footer> </footer>
+3
View File
@@ -0,0 +1,3 @@
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M23.75 3.75H22.5V1.25H20V3.75H10V1.25H7.5V3.75H6.25C4.875 3.75 3.75 4.875 3.75 6.25V23.75C3.75 25.125 4.875 26.25 6.25 26.25H23.75C25.125 26.25 26.25 25.125 26.25 23.75V6.25C26.25 4.875 25.125 3.75 23.75 3.75ZM23.75 23.75H6.25V11.25H23.75V23.75ZM6.25 8.75V6.25H23.75V8.75H6.25ZM8.75 13.75H21.25V16.25H8.75V13.75ZM8.75 18.75H17.5V21.25H8.75V18.75Z" fill="#F2E147"/>
</svg>

After

Width:  |  Height:  |  Size: 477 B

+3
View File
@@ -0,0 +1,3 @@
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M23.75 3.75H22.5V1.25H20V3.75H10V1.25H7.5V3.75H6.25C4.875 3.75 3.75 4.875 3.75 6.25V23.75C3.75 25.125 4.875 26.25 6.25 26.25H23.75C25.125 26.25 26.25 25.125 26.25 23.75V6.25C26.25 4.875 25.125 3.75 23.75 3.75ZM23.75 23.75H6.25V11.25H23.75V23.75ZM6.25 8.75V6.25H23.75V8.75H6.25ZM8.75 13.75H21.25V16.25H8.75V13.75ZM8.75 18.75H17.5V21.25H8.75V18.75Z" fill="#66FF6C"/>
</svg>

After

Width:  |  Height:  |  Size: 477 B

+3
View File
@@ -0,0 +1,3 @@
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M23.75 3.75H22.5V1.25H20V3.75H10V1.25H7.5V3.75H6.25C4.875 3.75 3.75 4.875 3.75 6.25V23.75C3.75 25.125 4.875 26.25 6.25 26.25H23.75C25.125 26.25 26.25 25.125 26.25 23.75V6.25C26.25 4.875 25.125 3.75 23.75 3.75ZM23.75 23.75H6.25V11.25H23.75V23.75ZM6.25 8.75V6.25H23.75V8.75H6.25ZM8.75 13.75H21.25V16.25H8.75V13.75ZM8.75 18.75H17.5V21.25H8.75V18.75Z" fill="#898989"/>
</svg>

After

Width:  |  Height:  |  Size: 477 B

+27 -65
View File
@@ -6,16 +6,6 @@
<img :src="getIcon('pl')" alt="icon-pl" @click="changeLang('en')" v-if="currentLang == 'pl'" /> <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 /> <img :src="getIcon('en', 'jpg')" alt="icon-en" @click="changeLang('pl')" v-else />
</span> </span>
<span class="icons-bottom">
<a href="https://www.paypal.com/paypalme/spythere" target="_blank">
<img :src="getIcon('dollar')" alt="icon paypal" />
</a>
<a href="https://discord.gg/x2mpNN3svk" target="_blank">
<img :src="getIcon('discord', 'png')" alt="icon discord" />
</a>
</span>
</div> </div>
<div class="header_body"> <div class="header_body">
@@ -33,6 +23,12 @@
<div class="info_counter"> <div class="info_counter">
<img :src="getIcon('dispatcher')" alt="icon dispatcher" /> <img :src="getIcon('dispatcher')" alt="icon dispatcher" />
<span class="text--primary">{{ onlineDispatchersCount }}</span> <span class="text--primary">{{ onlineDispatchersCount }}</span>
<!-- <span class="g-tooltip">
<b class="text--primary">{{ factorU }}U</b>
<div class="content">Test</div>
</span> -->
<span class="text--grayed"> / </span> <span class="text--grayed"> / </span>
<span class="text--primary">{{ onlineTrainsCount }}</span> <span class="text--primary">{{ onlineTrainsCount }}</span>
<img :src="getIcon('train')" alt="icon train" /> <img :src="getIcon('train')" alt="icon train" />
@@ -98,11 +94,17 @@ export default defineComponent({
onlineTrainsCount() { onlineTrainsCount() {
return this.store.trainList.filter((train) => train.online).length; return this.store.trainList.filter((train) => train.online).length;
}, },
onlineDispatchersCount() { onlineDispatchersCount() {
return this.store.stationList.filter( return this.store.stationList.filter(
(station) => station.onlineInfo && station.onlineInfo.region == this.store.region.id (station) => station.onlineInfo && station.onlineInfo.region == this.store.region.id
).length; ).length;
}, },
factorU() {
return this.onlineDispatchersCount == 0 ? '-' : (this.onlineTrainsCount / this.onlineDispatchersCount).toFixed(2);
},
computedRegions() { computedRegions() {
return options.regions.map((region) => { return options.regions.map((region) => {
const regionStationCount = const regionStationCount =
@@ -135,22 +137,20 @@ export default defineComponent({
.header { .header {
&_body { &_body {
max-width: 21em;
position: relative; position: relative;
max-width: 20em;
@include smallScreen {
max-width: 18em;
}
} }
&_container { &_container {
display: flex; display: flex;
justify-content: center; justify-content: center;
position: relative;
width: 1350px;
padding: 0.5em 0.3em 0 0.3em;
border-radius: 0 0 1em 1em; border-radius: 0 0 1em 1em;
@include smallScreen {
position: relative;
margin-top: 0.5em;
}
} }
&_brand { &_brand {
@@ -158,6 +158,7 @@ export default defineComponent({
img { img {
width: 100%; width: 100%;
margin: 0 auto; margin: 0 auto;
} }
} }
@@ -165,9 +166,7 @@ export default defineComponent({
&_info { &_info {
display: grid; display: grid;
grid-template-columns: 1fr 1fr 1fr; grid-template-columns: 1fr 1fr 1fr;
max-width: 100%; font-size: 1.15em;
font-size: 1.2em;
} }
&_links { &_links {
@@ -184,57 +183,20 @@ export default defineComponent({
position: absolute; position: absolute;
right: 0; right: 0;
top: 0; top: 0;
height: 100%;
display: flex; padding: 0.5em;
flex-direction: column;
justify-content: flex-end;
align-items: flex-end;
padding: 0.5em 0.5em;
@include smallScreen() { @include smallScreen {
right: auto; transform: translateX(85%);
left: 0.75em;
padding: 0;
align-items: center;
} }
} }
} }
// ICONS // ICONS
.icons { .icons-top {
position: relative; img {
width: 2.5em;
&-top { cursor: pointer;
img {
width: 2.5em;
cursor: pointer;
}
margin-bottom: 0.5em;
}
&-bottom {
display: flex;
a {
margin-left: 0.6em;
user-select: none;
}
img {
width: 1.9em;
}
@include smallScreen() {
flex-direction: column;
a {
margin: 0.25em 0;
}
}
} }
} }
+12 -15
View File
@@ -164,7 +164,7 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { DataStatus } from '../../scripts/enums/DataStatus'; import { DataStatus } from '../../scripts/enums/DataStatus';
import { useStore } from '../../store/store'; import { useStore } from '../../store/store';
import { StoreState } from '../../store/storeTypes'; import { StoreState } from '../../scripts/interfaces/store/storeTypes';
export default defineComponent({ export default defineComponent({
data() { data() {
@@ -303,9 +303,11 @@ export default defineComponent({
.status-indicator { .status-indicator {
position: absolute; position: absolute;
left: 110%;
bottom: 0; bottom: 0;
right: 0;
z-index: 100; z-index: 100;
transform: translateX(1.5em);
} }
.indicator { .indicator {
@@ -330,7 +332,7 @@ export default defineComponent({
background-color: #171717; background-color: #171717;
border-radius: 0.75em; border-radius: 0.75em;
min-width: 13em; width: 13em;
text-align: center; text-align: center;
overflow: none; overflow: none;
@@ -354,22 +356,16 @@ export default defineComponent({
} }
@include midScreen() { @include midScreen() {
left: 50%; left: auto;
top: 100%; right: 200%;
transform: translate(-50%, 0);
margin-left: 0;
margin-top: 0.75em;
&::before { &::before {
border-left: 10px solid transparent;
border-right: 10px solid transparent; border-right: 10px solid transparent;
border-bottom: 10px solid #171717; border-left: 12px solid #171717;
right: 0;
left: auto;
top: 0; transform: translate(100%, -50%);
left: 50%;
transform: translate(-50%, -100%);
} }
} }
@@ -379,3 +375,4 @@ export default defineComponent({
} }
} }
</style> </style>
+6 -30
View File
@@ -3,6 +3,10 @@
<div class="select-box_content"> <div class="select-box_content">
<button class="selected" @click="toggleBox"> <button class="selected" @click="toggleBox">
<span>{{ computedSelectedItem.selectedValue || computedSelectedItem.value }}</span> <span>{{ computedSelectedItem.selectedValue || computedSelectedItem.value }}</span>
<div class="arrow">
<img :src="listOpen ? getIcon('arrow-asc') : getIcon('arrow-desc')" alt="arrow-icon" />
</div>
</button> </button>
<ul class="options" :ref="(el) => (listRef = el as Element)"> <ul class="options" :ref="(el) => (listRef = el as Element)">
@@ -21,10 +25,6 @@
</li> </li>
</ul> </ul>
</div> </div>
<div class="arrow">
<img :src="listOpen ? getIcon('arrow-asc') : getIcon('arrow-desc')" alt="arrow-icon" />
</div>
</div> </div>
</template> </template>
@@ -129,46 +129,22 @@ export default defineComponent({
} }
.select-box { .select-box {
position: relative; display: flex;
width: auto; align-items: center;
} }
.arrow { .arrow {
position: absolute;
top: 50%;
right: 0;
padding: 0;
img { img {
vertical-align: middle; vertical-align: middle;
width: 1.35em; width: 1.35em;
} }
transform: translateY(-50%);
pointer-events: none;
} }
button.selected { button.selected {
background-color: transparent;
color: paleturquoise; color: paleturquoise;
font-size: 1em;
font-weight: bold; font-weight: bold;
padding: 0.1em 0.5em; padding: 0.1em 0.5em;
margin-right: 2em;
display: flex;
width: 100%;
cursor: pointer;
border: none;
outline: none;
text-align: left;
&:focus { &:focus {
background-color: #262626; background-color: #262626;
+148 -80
View File
@@ -1,56 +1,62 @@
<template> <template>
<section class="daily-stats"> <section class="daily-stats">
<span :data-active="data.statsStatus"> <span :data-active="statsStatus">
<b v-if="data.statsStatus == DataStatus.Loading"> <b v-if="statsStatus == DataStatus.Loading">
{{ $t('app.loading') }} {{ $t('app.loading') }}
</b> </b>
<b v-else-if="data.stats.distanceSum == null"> <b v-else-if="stats.distanceSum == null">
{{ $t('journal.daily-stats-info') }} {{ $t('journal.daily-stats-info') }}
</b> </b>
<span> <span class="stats-list" v-else>
<div v-if="data.stats.totalTimetables"> <h3>
{{ $t('journal.daily-stats-title') }}
<b class="text--primary">{{ new Date().toLocaleDateString($i18n.locale) }}</b>
</h3>
<hr style="margin-bottom: 0.5em" />
<div v-if="stats.totalTimetables">
&bull; &bull;
<i18n-t keypath="journal.timetable-stats-total"> <i18n-t keypath="journal.timetable-stats-total">
<template #count> <template #count>
<b class="text--primary"> <b class="text--primary">
{{ data.stats.totalTimetables }} {{ stats.totalTimetables }}
{{ $t('journal.timetable-count', data.stats.totalTimetables) }} {{ $t('journal.timetable-count', stats.totalTimetables) }}
</b> </b>
</template> </template>
<template #distance> <template #distance>
<b class="text--primary"> {{ data.stats.distanceSum?.toFixed(2) }} km </b> <b class="text--primary"> {{ stats.distanceSum?.toFixed(2) }} km</b>
</template> </template>
</i18n-t> </i18n-t>
</div> </div>
<div v-if="data.stats.timetableId"> <div v-if="stats.timetableId">
&bull; &bull;
<i18n-t keypath="journal.timetable-stats-longest"> <i18n-t keypath="journal.timetable-stats-longest">
<template #id> <template #id>
<router-link :to="`/journal/timetables?timetableId=${data.stats.timetableId}`"> <router-link :to="`/journal/timetables?timetableId=${stats.timetableId}`">
<b>{{ data.stats.timetableId }}</b> <b>{{ stats.timetableId }}</b>
</router-link> </router-link>
</template> </template>
<template #author> <template #author>
<router-link :to="`/journal/dispatchers?dispatcherName=${data.stats.timetableAuthor}`"> <router-link :to="`/journal/dispatchers?dispatcherName=${stats.timetableAuthor}`">
<b>{{ data.stats.timetableAuthor }}</b> <b>{{ stats.timetableAuthor }}</b>
</router-link> </router-link>
</template> </template>
<template #driver> <template #driver>
<b>{{ data.stats.timetableDriver }}</b> <b class="text--primary">{{ stats.timetableDriver }}</b>
</template> </template>
<template #distance> <template #distance>
<b class="text--primary">{{ data.stats.timetableRouteDistance }} km</b> <b class="text--primary">{{ stats.timetableRouteDistance }} km</b>
</template> </template>
</i18n-t> </i18n-t>
</div> </div>
<div v-if="firstPlaceDispatchers.length == 1"> <div v-if="firstPlaceDispatchers.length == 1">
&bull; &bull;
<i18n-t keypath="journal.timetable-stats-most-active"> <i18n-t keypath="journal.timetable-stats-most-active-dr">
<template #dispatcher> <template #dispatcher>
<router-link :to="`/journal/dispatchers?dispatcherName=${firstPlaceDispatchers[0].name}`"> <router-link :to="`/journal/dispatchers?dispatcherName=${firstPlaceDispatchers[0].name}`">
<b>{{ firstPlaceDispatchers[0].name }}</b> <b>{{ firstPlaceDispatchers[0].name }}</b>
@@ -67,7 +73,7 @@
<div v-if="firstPlaceDispatchers.length > 1"> <div v-if="firstPlaceDispatchers.length > 1">
&bull; &bull;
<i18n-t keypath="journal.timetable-stats-most-active-many"> <i18n-t keypath="journal.timetable-stats-most-active-dr-many">
<template #dispatchers> <template #dispatchers>
<span v-for="(disp, i) in firstPlaceDispatchers"> <span v-for="(disp, i) in firstPlaceDispatchers">
<span v-if="i == firstPlaceDispatchers.length - 1"> {{ $t('general.and') }} </span> <span v-if="i == firstPlaceDispatchers.length - 1"> {{ $t('general.and') }} </span>
@@ -88,95 +94,157 @@
</template> </template>
</i18n-t> </i18n-t>
</div> </div>
<div v-if="stats.longestDuties.length > 0">
&bull;
<i18n-t keypath="journal.timetable-stats-longest-duties">
<template #dispatcher>
<router-link :to="`/journal/dispatchers?dispatcherName=${stats.longestDuties[0].name}`">
<b>{{ stats.longestDuties[0].name }}</b>
</router-link>
</template>
<template #station>{{ stats.longestDuties[0].station }}</template>
<template #duration>
{{ calculateDuration(stats.longestDuties[0].duration) }}
</template>
</i18n-t>
</div>
<div v-if="stats.mostActiveDrivers.length > 0">
&bull;
<i18n-t keypath="journal.timetable-stats-most-active-driver">
<template #driver>
<b class="text--primary">{{ stats.mostActiveDrivers[0].name }}</b>
</template>
<template #distance>
<b class="text--primary">{{ stats.mostActiveDrivers[0].distance }} km</b>
</template>
</i18n-t>
</div>
</span> </span>
</span> </span>
</section> </section>
</template> </template>
<script setup lang="ts"> <script lang="ts">
import axios from 'axios'; import axios from 'axios';
import { computed, reactive, ref } from 'vue'; import { defineComponent } from 'vue';
import dateMixin from '../../mixins/dateMixin';
import { DataStatus } from '../../scripts/enums/DataStatus'; import { DataStatus } from '../../scripts/enums/DataStatus';
import { ITimetablesDailyStats, ITimetablesDailyStatsResponse } from '../../scripts/interfaces/api/StatsAPIData'; import { ITimetablesDailyStats, ITimetablesDailyStatsResponse } from '../../scripts/interfaces/api/StatsAPIData';
import { URLs } from '../../scripts/utils/apiURLs'; import { URLs } from '../../scripts/utils/apiURLs';
const intervalId = ref(-1); export default defineComponent({
mixins: [dateMixin],
emits: ['toggleStatsOpen'],
const data = reactive({ data() {
statsStatus: DataStatus.Loading, return {
DataStatus,
statsStatus: DataStatus.Loading,
intervalId: -1,
stats: { stats: {
totalTimetables: 0, totalTimetables: 0,
distanceSum: 0, distanceSum: 0,
distanceAvg: 0, distanceAvg: 0,
timetableAuthor: '', timetableAuthor: '',
timetableDriver: '', timetableDriver: '',
timetableId: 0, timetableId: 0,
timetableRouteDistance: 0, timetableRouteDistance: 0,
longestDuties: [],
mostActiveDispatchers: [], mostActiveDrivers: [],
} as ITimetablesDailyStats, mostActiveDispatchers: [],
}); } as ITimetablesDailyStats,
const firstPlaceDispatchers = computed(() => {
if (data.stats.mostActiveDispatchers.length == 0) return [];
const maxCount = data.stats.mostActiveDispatchers[0].count;
return data.stats.mostActiveDispatchers.filter((disp) => disp.count === maxCount);
});
async function fetchDailyTimetableStats() {
try {
const {
distanceAvg,
distanceSum,
maxTimetable,
totalTimetables,
mostActiveDispatchers,
}: ITimetablesDailyStatsResponse = await (
await axios.get(`${URLs.stacjownikAPI}/api/getDailyTimetableStats`)
).data;
data.stats = {
totalTimetables,
distanceSum,
distanceAvg,
timetableAuthor: maxTimetable?.authorName || '',
timetableDriver: maxTimetable?.driverName || '',
timetableId: maxTimetable?.id || 0,
timetableRouteDistance: maxTimetable?.routeDistance || 0,
mostActiveDispatchers,
}; };
},
data.statsStatus = DataStatus.Loaded; activated() {
} catch (error) { this.startFetchingDailyStats();
console.error('Ups! Wystąpił błąd podczas pobierania statystyk rozkładów jazdy...'); this.$emit('toggleStatsOpen', true);
data.statsStatus = DataStatus.Error; },
}
}
function startFetchingDailyStats() { deactivated() {
fetchDailyTimetableStats(); this.stopFetchingDailyStats();
intervalId.value = setInterval(fetchDailyTimetableStats, 60000); },
}
function stopFetchingDailyStats() { computed: {
clearInterval(intervalId.value); firstPlaceDispatchers() {
} if (this.stats.mostActiveDispatchers.length == 0) return [];
const maxCount = this.stats.mostActiveDispatchers[0].count;
defineExpose({ return this.stats.mostActiveDispatchers.filter((disp) => disp.count === maxCount);
startFetchingDailyStats, },
stopFetchingDailyStats, },
methods: {
async fetchDailyTimetableStats() {
try {
const res: ITimetablesDailyStatsResponse = await (
await axios.get(`${URLs.stacjownikAPI}/api/getDailyTimetableStats`)
).data;
this.stats = {
totalTimetables: res.totalTimetables,
distanceSum: res.distanceSum,
distanceAvg: res.distanceAvg,
timetableAuthor: res.maxTimetable?.authorName || '',
timetableDriver: res.maxTimetable?.driverName || '',
timetableId: res.maxTimetable?.id || 0,
timetableRouteDistance: res.maxTimetable?.routeDistance || 0,
mostActiveDispatchers: res.mostActiveDispatchers,
mostActiveDrivers: res.mostActiveDrivers,
longestDuties: res.longestDuties,
};
this.statsStatus = DataStatus.Loaded;
} catch (error) {
console.error('Ups! Wystąpił błąd podczas pobierania statystyk rozkładów jazdy...');
this.statsStatus = DataStatus.Error;
}
},
startFetchingDailyStats() {
this.fetchDailyTimetableStats();
if (this.intervalId != -1) return;
this.intervalId = setInterval(this.fetchDailyTimetableStats, 60000);
},
stopFetchingDailyStats() {
clearInterval(this.intervalId);
this.intervalId = -1;
},
},
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/responsive.scss';
.daily-stats { .daily-stats {
text-align: left; text-align: left;
} }
.daily-stats > span[data-active='0'] { .daily-stats > span[data-active='0'] {
opacity: 0.75; opacity: 0.75;
} }
.stats-list a {
text-decoration: underline;
}
@include smallScreen {
.daily-stats {
text-align: justify;
}
h3 {
text-align: center;
}
}
</style> </style>
+40 -30
View File
@@ -49,15 +49,6 @@
</button> </button>
</div> </div>
</div> </div>
<div class="search_actions">
<button class="btn--action" @click="onResetButtonClick">
{{ $t('options.reset-button') }}
</button>
<button class="btn--action" @click="onSearchButtonConfirm">
{{ $t('options.search-button') }}
</button>
</div>
</div> </div>
<h1 class="option-title">{{ $t('options.sort-title') }}</h1> <h1 class="option-title">{{ $t('options.sort-title') }}</h1>
@@ -74,15 +65,31 @@
</div> </div>
<h1 class="option-title" v-if="filters.length != 0">{{ $t('options.filter-title') }}</h1> <h1 class="option-title" v-if="filters.length != 0">{{ $t('options.filter-title') }}</h1>
<div class="options_filters">
<button <div class="options_filter-sections" v-if="filters.length != 0 && filterList">
v-for="filter in filters" <section class="filter-section" v-for="section in JournalFilterSection">
class="filter-option btn--option" <p>{{ $t(`options.filter-section-${section}`) }}</p>
:class="{ checked: journalFilterActive.id === filter.id }"
:id="filter.id" <div class="options_filters">
@click="onFilterChange(filter)" <button
> v-for="filter in filterList.filter((f) => f.filterSection == section)"
{{ $t(`options.filter-${filter.id}`) }} class="filter-option btn--option"
:class="{ checked: filter.isActive }"
:id="filter.id"
@click="onFilterChange(filter)"
>
{{ $t(`options.filter-${filter.id}`) }}
</button>
</div>
</section>
</div>
<div class="options_actions">
<button class="btn--action" @click="onResetButtonClick">
{{ $t('options.reset-button') }}
</button>
<button class="btn--action" @click="onSearchButtonConfirm">
{{ $t('options.search-button') }}
</button> </button>
</div> </div>
</div> </div>
@@ -100,9 +107,10 @@ import { DataStatus } from '../../scripts/enums/DataStatus';
import { DriverStatsAPIData } from '../../scripts/interfaces/api/DriverStatsAPIData'; import { DriverStatsAPIData } from '../../scripts/interfaces/api/DriverStatsAPIData';
import { URLs } from '../../scripts/utils/apiURLs'; import { URLs } from '../../scripts/utils/apiURLs';
import { useStore } from '../../store/store'; import { useStore } from '../../store/store';
import { JournalTimetableFilter } from '../../types/Journal/JournalTimetablesTypes';
import ActionButton from '../Global/ActionButton.vue'; import ActionButton from '../Global/ActionButton.vue';
import SelectBox from '../Global/SelectBox.vue'; import SelectBox from '../Global/SelectBox.vue';
import { JournalFilterSection } from '../../scripts/enums/JournalFilterType';
import { JournalFilter } from '../../scripts/types/JournalTimetablesTypes';
export default defineComponent({ export default defineComponent({
components: { SelectBox, ActionButton }, components: { SelectBox, ActionButton },
@@ -116,7 +124,7 @@ export default defineComponent({
}, },
filters: { filters: {
type: Array as PropType<JournalTimetableFilter[]>, type: Array as PropType<JournalFilter[]>,
default: [], default: [],
}, },
@@ -132,13 +140,14 @@ export default defineComponent({
optionsType: { optionsType: {
type: String, type: String,
required: true required: true,
} },
}, },
data() { data() {
return { return {
showOptions: false, showOptions: false,
JournalFilterSection,
driverSuggestions: [] as string[], driverSuggestions: [] as string[],
dispatcherSuggestions: [] as string[], dispatcherSuggestions: [] as string[],
@@ -154,7 +163,8 @@ export default defineComponent({
return { return {
searchersValues: inject('searchersValues') as { [key: string]: 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 JournalTimetableFilter, // journalFilterActive: inject('journalFilterActive') as JournalFilter,
filterList: inject('filterList') as JournalFilter[] | undefined,
}; };
}, },
@@ -174,7 +184,8 @@ export default defineComponent({
watch: { watch: {
async driverStatsName(value: string) { async driverStatsName(value: string) {
await this.fetchDriverStats(); await this.fetchDriverStats();
this.store.currentStatsTab = value ? 'driver' : 'daily';
// if (value) this.store.currentStatsTab = 'driver';
}, },
async 'searchersValues.search-driver'(value: string | undefined) { async 'searchersValues.search-driver'(value: string | undefined) {
@@ -249,18 +260,17 @@ export default defineComponent({
}); });
}, },
focusEnd() {
console.log('focus end');
},
onSorterChange(item: { id: string | number; value: string }) { onSorterChange(item: { id: string | number; value: string }) {
this.sorterActive.id = item.id; this.sorterActive.id = item.id;
this.sorterActive.dir = -1; this.sorterActive.dir = -1;
this.$emit('onSearchConfirm'); this.$emit('onSearchConfirm');
}, },
onFilterChange(filter: JournalTimetableFilter) { onFilterChange(filter: JournalFilter) {
this.journalFilterActive = filter; // this.journalFilterActive = filter;
this.filterList?.filter((f) => f.filterSection === filter.filterSection).forEach((f) => (f.isActive = false));
filter.isActive = true;
this.$emit('onSearchConfirm'); this.$emit('onSearchConfirm');
}, },
+27 -21
View File
@@ -1,20 +1,22 @@
<template> <template>
<div class="journal-stats" v-show="!store.isOffline"> <div class="journal-stats" v-if="!store.isOffline">
<div class="tabs"> <div class="tabs">
<button <button
v-for="tab in data.tabs" v-for="tab in data.tabs"
class="btn--filled" class="btn--filled"
:data-selected="tab.name == store.currentStatsTab && areStatsOpen" :data-selected="tab.name == store.currentStatsTab && areStatsOpen"
:data-inactive="tab.inactive" :data-inactive="tab.inactive"
:data-disabled="tab.inactive"
:disabled="tab.inactive"
@click="onTabButtonClick(tab.name)" @click="onTabButtonClick(tab.name)"
> >
{{ $t(tab.titlePath) }} {{ $t(tab.titlePath) }}
</button> </button>
</div> </div>
<div class="stats-tab" v-show="areStatsOpen"> <div class="stats-tab" v-show="areStatsOpen">
<keep-alive> <keep-alive>
<JournalDailyStats v-if="store.currentStatsTab == 'daily'" ref="dailyStatsComp" /> <JournalDailyStats v-if="store.currentStatsTab == 'daily'" @toggleStatsOpen="toggleStatsOpen" />
<JournalDriverStats v-else-if="store.currentStatsTab == 'driver'" /> <JournalDriverStats v-else-if="store.currentStatsTab == 'driver'" />
</keep-alive> </keep-alive>
</div> </div>
@@ -22,22 +24,21 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, KeepAlive, onActivated, onDeactivated, reactive, Ref, ref, watch } from 'vue'; import { computed, KeepAlive, onMounted, reactive, Ref, ref, watch } from 'vue';
import { useStore } from '../../store/store'; import { useStore } from '../../store/store';
import JournalDailyStats from './DailyStats.vue'; import JournalDailyStats from './DailyStats.vue';
import JournalDriverStats from './JournalDriverStats.vue'; import JournalDriverStats from './JournalDriverStats.vue';
import StorageManager from '../../scripts/managers/storageManager';
// Types // Types
type TStatTab = 'daily' | 'driver'; type TStatTab = 'daily' | 'driver';
// Variables // Variables
const store = useStore(); const store = useStore();
const dailyStatsComp: Ref<InstanceType<typeof JournalDailyStats> | null> = ref(null);
const lastDailyStatsOpen = ref(false); const lastDailyStatsOpen = ref(false);
const areStatsOpen = ref(false); const areStatsOpen = ref(false);
const lastClickedTab = ref('daily'); const lastClickedTab: Ref<'daily' | 'driver' | null> = ref(null);
let data = reactive({ let data = reactive({
tabs: [ tabs: [
@@ -48,7 +49,7 @@ let data = reactive({
{ {
name: 'driver', name: 'driver',
titlePath: 'journal.driver-stats-title', titlePath: 'journal.driver-stats-title',
inactive: true, // inactive: true,
}, },
] as { name: TStatTab; titlePath: string; inactive?: boolean }[], ] as { name: TStatTab; titlePath: string; inactive?: boolean }[],
}); });
@@ -57,30 +58,35 @@ let data = reactive({
function onTabButtonClick(tab: TStatTab) { function onTabButtonClick(tab: TStatTab) {
if (lastClickedTab.value == tab || !areStatsOpen.value) areStatsOpen.value = !areStatsOpen.value; if (lastClickedTab.value == tab || !areStatsOpen.value) areStatsOpen.value = !areStatsOpen.value;
if (tab == 'daily') lastDailyStatsOpen.value = areStatsOpen.value; if (tab == 'daily') {
StorageManager.setBooleanValue('dailyStatsOpen', areStatsOpen.value);
lastDailyStatsOpen.value = areStatsOpen.value;
}
store.currentStatsTab = tab; store.currentStatsTab = tab;
lastClickedTab.value = tab; lastClickedTab.value = tab;
if (areStatsOpen.value == false) store.currentStatsTab = null;
} }
onActivated(() => { function toggleStatsOpen(open: boolean) {
dailyStatsComp.value?.startFetchingDailyStats(); areStatsOpen.value = open;
}); }
onDeactivated(() => {
dailyStatsComp.value?.stopFetchingDailyStats();
});
watch( watch(
computed(() => store.driverStatsData), computed(() => store.driverStatsData),
(statsData) => { (statsData) => {
data.tabs[1].inactive = statsData ? false : true; store.currentStatsTab = statsData ? 'driver' : lastClickedTab.value;
areStatsOpen.value = statsData ? true : lastClickedTab.value !== null;
lastClickedTab.value = statsData ? 'driver' : 'daily';
if (statsData) areStatsOpen.value = true;
if (!statsData) areStatsOpen.value = lastDailyStatsOpen.value;
} }
); );
onMounted(() => {
if (StorageManager.getBooleanValue('dailyStatsOpen')) {
areStatsOpen.value = true;
store.currentStatsTab = 'daily';
}
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@@ -1,10 +1,10 @@
<template> <template>
<transition-group class="journal-list" tag="ul" name="list-anim"> <transition-group class="journal-list" tag="ul" name="list-anim">
<li <li
v-for="{ timetable, sceneryList, ...item } in computedTimetableHistory" v-for="{ timetable, stockHistoryComp, stops, showExtraInfo, ...item } in computedTimetableHistory"
class="journal_item" class="journal_item"
:key="timetable.id" :key="timetable.id"
@click="item.showExtra.value = !item.showExtra.value" @click="showExtraInfo.value = !showExtraInfo.value"
> >
<div class="journal_item-info"> <div class="journal_item-info">
<div class="info-general"> <div class="info-general">
@@ -16,6 +16,12 @@
style="cursor: pointer" style="cursor: pointer"
> >
<span class="text--grayed">#{{ timetable.id }}</span> <span class="text--grayed">#{{ timetable.id }}</span>
<span class="badges" v-if="timetable.skr || timetable.twr">
<span class="train-badge twr" v-if="timetable.twr" :title="$t('general.TWR')">TWR</span>
<span class="train-badge skr" v-if="timetable.skr" :title="$t('general.SKR')">SKR</span>
</span>
<span> <span>
<strong class="text--primary"> <strong class="text--primary">
{{ timetable.trainCategoryCode }} {{ timetable.trainCategoryCode }}
@@ -37,9 +43,9 @@
<span class="general-time"> <span class="general-time">
<b class="info-date">{{ localeDay(timetable.beginDate, $i18n.locale) }}</b> <b class="info-date">{{ localeDay(timetable.beginDate, $i18n.locale) }}</b>
<b <b
class="info-status" class="info-badge"
:class="{ :class="{
fulfilled: timetable.fulfilled || timetable.currentDistance >= timetable.routeDistance * 0.9, fulfilled: timetable.fulfilled,
terminated: timetable.terminated && !timetable.fulfilled, terminated: timetable.terminated && !timetable.fulfilled,
active: !timetable.terminated, active: !timetable.terminated,
}" }"
@@ -47,7 +53,7 @@
{{ {{
!timetable.terminated !timetable.terminated
? $t('journal.timetable-active') ? $t('journal.timetable-active')
: timetable.fulfilled || timetable.currentDistance >= timetable.routeDistance * 0.9 : timetable.fulfilled
? $t('journal.timetable-fulfilled') ? $t('journal.timetable-fulfilled')
: `${$t('journal.timetable-abandoned')} ${localeTime(timetable.endDate, $i18n.locale)}` : `${$t('journal.timetable-abandoned')} ${localeTime(timetable.endDate, $i18n.locale)}`
}} }}
@@ -61,33 +67,28 @@
<hr /> <hr />
<div class="scenery-list"> <!-- Spis postojów -->
<div class="stop-list">
<span <span
v-for="(scenery, i) in sceneryList.filter((_, i) => v-for="(stop, i) in stops.filter((_, i) => (!showExtraInfo.value ? i == 0 || i == stops.length - 1 : true))"
!item.showExtra.value ? i == 0 || i == sceneryList.length - 1 : true class="stop-list-item"
)" :key="stop.stopName"
:key="scenery.name" :data-confirmed="stop.confirmed"
:class="{ confirmed: scenery.confirmed }"
> >
<span v-if="i > 0"> <span v-if="i > 0">
&gt; &gt;
<span v-if="!item.showExtra.value && i == 1 && sceneryList.length > 2" <span v-if="!showExtraInfo.value && i == 1 && stops.length > 2">
>... (+{{ sceneryList.length - 2 }}) &gt;</span ... (+{{ stops.length - 2 }}) &gt;
> </span>
</span> </span>
{{ scenery.name }}
<!-- Data odjazdu ze stacji początkowej --> <span class="stop-name">{{ stop.stopName }}</span>
<span v-if="i == 0" v-html="scenery.beginDateHTML"></span> <span v-html="stop.html"></span>
<!-- Data przyjazdu do stacji końcowej -->
<span
v-if="i == sceneryList.length - 1 || (i == 1 && !item.showExtra.value)"
v-html="scenery.endDateHTML"
></span>
</span> </span>
</div> </div>
<!-- Status RJ --> <!-- Status RJ -->
<div style="margin: 0.5em 0"> <div class="info-status" style="margin: 0.5em 0">
<span> <span>
<b>{{ $t('journal.route-length') }}</b> <b>{{ $t('journal.route-length') }}</b>
{{ !timetable.fulfilled ? timetable.currentDistance + ' /' : '' }} {{ !timetable.fulfilled ? timetable.currentDistance + ' /' : '' }}
@@ -104,12 +105,24 @@
<b> <b>
{{ $t(`journal.${timetable.terminated ? 'last-seen-at' : 'currently-at'}`) }} {{ $t(`journal.${timetable.terminated ? 'last-seen-at' : 'currently-at'}`) }}
{{ timetable.currentSceneryName.replace(/.[a-zA-Z0-9]+.sc/, '') }} {{ timetable.currentSceneryName.replace(/.[a-zA-Z0-9]+.sc/, '') }}
<span v-if="timetable.currentLocation[0] || timetable.currentLocation[1]">&lpar;</span>
<span v-if="timetable.currentLocation[1]">
{{ $t('journal.timetable-location-route') }} {{ timetable.currentLocation[1] }}
</span>
<span v-else-if="timetable.currentLocation[0]">
{{ $t('journal.timetable-location-signal') }} {{ timetable.currentLocation[0] }}
</span>
<span v-if="timetable.currentLocation[0] || timetable.currentLocation[1]">&rpar;</span>
</b> </b>
</span> </span>
</div> </div>
<!-- Nick dyżurnego --> <!-- Info o autorze RJ -->
<div v-if="timetable.authorName"> <div class="info-author" v-if="timetable.authorName">
<b class="text--grayed">{{ $t('journal.dispatcher-name') }}&nbsp;</b> <b class="text--grayed">{{ $t('journal.dispatcher-name') }}&nbsp;</b>
<router-link class="dispatcher-link" :to="`/journal/dispatchers?dispatcherName=${timetable.authorName}`"> <router-link class="dispatcher-link" :to="`/journal/dispatchers?dispatcherName=${timetable.authorName}`">
<b>{{ timetable.authorName }}</b> <b>{{ timetable.authorName }}</b>
@@ -126,27 +139,62 @@
<button class="btn--option btn--show"> <button class="btn--option btn--show">
{{ $t('journal.stock-info') }} {{ $t('journal.stock-info') }}
<img :src="getIcon(`arrow-${item.showExtra.value ? 'asc' : 'desc'}`)" alt="Arrow" /> <img :src="getIcon(`arrow-${showExtraInfo.value ? 'asc' : 'desc'}`)" alt="Arrow" />
</button> </button>
<div class="info-extended" v-if="timetable.stockString && item.showExtra.value"> <!-- Dodatkowe informacje -->
<div class="info-extended" v-if="timetable.stockString && timetable.stockMass && showExtraInfo.value">
<hr /> <hr />
<div>
<span class="badge info-badge"> <div class="stock-specs">
<span class="badge specs-badge">
<span>{{ $t('journal.stock-max-speed') }}</span> <span>{{ $t('journal.stock-max-speed') }}</span>
<span>{{ timetable.maxSpeed }}km/h</span> <span>{{ timetable.maxSpeed }}km/h</span>
</span> </span>
<span class="badge info-badge"> <span class="badge specs-badge">
<span>{{ $t('journal.stock-length') }}</span> <span>{{ $t('journal.stock-length') }}</span>
<span>{{ timetable.stockLength }}m</span> <span>
{{
item.currentHistoryIndex.value == 0
? timetable.stockLength
: stockHistoryComp[item.currentHistoryIndex.value].stockLength || timetable.stockLength
}}m
</span>
</span> </span>
<span class="badge info-badge"> <span class="badge specs-badge">
<span>{{ $t('journal.stock-mass') }}</span> <span>{{ $t('journal.stock-mass') }}</span>
<span>{{ Math.floor(timetable.stockMass! / 1000) }}t</span> <span>
{{
Math.floor(
(item.currentHistoryIndex.value == 0
? timetable.stockMass!
: stockHistoryComp[item.currentHistoryIndex.value].stockMass || timetable.stockMass) / 1000
)
}}t
</span>
</span> </span>
</div> </div>
<!-- Historia zmian w składzie -->
<div class="stock-history" v-if="stockHistoryComp.length > 1">
<button
class="btn--action"
v-for="(sh, i) in stockHistoryComp"
:data-checked="i == item.currentHistoryIndex.value"
@click.stop="item.currentHistoryIndex.value = i"
>
{{ sh.updatedAt }}
</button>
</div>
<ul class="stock-list"> <ul class="stock-list">
<li v-for="(car, i) in timetable.stockString.split(';')" :key="i"> <li
v-for="(car, i) in (item.currentHistoryIndex.value == 0
? timetable.stockString
: stockHistoryComp[item.currentHistoryIndex.value].stockString
).split(';')"
:key="i"
>
<img <img
@error="onImageError" @error="onImageError"
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${car.split(':')[0]}.png`" :src="`https://rj.td2.info.pl/dist/img/thumbnails/${car.split(':')[0]}.png`"
@@ -168,7 +216,6 @@ import imageMixin from '../../mixins/imageMixin';
import modalTrainMixin from '../../mixins/modalTrainMixin'; import modalTrainMixin from '../../mixins/modalTrainMixin';
import styleMixin from '../../mixins/styleMixin'; import styleMixin from '../../mixins/styleMixin';
import { TimetableHistory } from '../../scripts/interfaces/api/TimetablesAPIData'; import { TimetableHistory } from '../../scripts/interfaces/api/TimetablesAPIData';
import { TimetableStop } from '../../scripts/interfaces/api/TrainAPIData';
export default defineComponent({ export default defineComponent({
props: { props: {
@@ -184,38 +231,80 @@ export default defineComponent({
computedTimetableHistory() { computedTimetableHistory() {
return this.timetableHistory.map((timetable) => ({ return this.timetableHistory.map((timetable) => ({
timetable, timetable,
sceneryList: this.getSceneryList(timetable), stockHistoryComp: timetable.stockHistory
showExtra: ref(false), .slice()
.reverse()
.map((h) => {
const historyData = h.split('@');
return {
updatedAt: new Date(Number(historyData[0])).toLocaleTimeString(this.$i18n.locale, {
hour: '2-digit',
minute: '2-digit',
}),
stockString: historyData[1],
stockMass: Number(historyData[2]) || undefined,
stockLength: Number(historyData[3]) || undefined,
};
}),
showExtraInfo: ref(false),
stops: this.getTimetableStops(timetable),
currentHistoryIndex: ref(0),
})); }));
}, },
}, },
methods: { methods: {
getSceneryList(timetable: TimetableHistory) { getTimetableStops(timetable: TimetableHistory) {
return timetable.sceneriesString.split('%').map((name, i) => { const stopNames = timetable.sceneriesString.split('%');
const beginDateHTML =
' (o. ' +
(timetable.beginDate != timetable.scheduledBeginDate
? `<s class='text--grayed'>${this.localeTime(timetable.beginDate, this.$i18n.locale)}</s> `
: '') +
`<span>${this.localeTime(timetable.scheduledBeginDate, this.$i18n.locale)}</span>)`;
const endDateHTML = const beginDateHTML = ` (o. ${
' (p. ' + timetable.beginDate != timetable.scheduledBeginDate
(timetable.endDate != timetable.scheduledEndDate && timetable.fulfilled ? `<s class="text--grayed">${this.localeTime(timetable.beginDate, this.$i18n.locale)}</s>`
? `<s class='text--grayed'>${this.localeTime( : ''
timetable.fulfilled ? timetable.endDate : timetable.scheduledEndDate, } <span>${this.localeTime(timetable.scheduledBeginDate, this.$i18n.locale)}</span>)`;
this.$i18n.locale
)}</s> `
: '') +
`<span>${this.localeTime(
timetable.fulfilled || (timetable.terminated && !timetable.fulfilled)
? timetable.scheduledEndDate
: timetable.endDate,
this.$i18n.locale
)}</span>)`;
return { name, confirmed: i < timetable.confirmedStopsCount, beginDateHTML, endDateHTML }; const endDateHTML = ` (p. ${
timetable.endDate != timetable.scheduledEndDate && timetable.fulfilled
? `<s class="text--grayed">${this.localeTime(timetable.endDate, this.$i18n.locale)}</s>`
: ''
} <span>${this.localeTime(timetable.scheduledEndDate, this.$i18n.locale)}</span>)`;
return stopNames.map((stopName, i) => {
const confirmed = i < timetable.confirmedStopsCount;
if (i == 0) return { stopName, html: beginDateHTML, confirmed };
if (i == stopNames.length - 1) return { stopName, html: endDateHTML, confirmed };
const departureDateScheduled = this.stringToDate(timetable.checkpointDeparturesScheduled?.at(i));
const departureDateReal = this.stringToDate(timetable.checkpointDepartures?.at(i));
const arrivalDateScheduled = this.stringToDate(timetable.checkpointArrivalsScheduled?.at(i));
const arrivalDateReal = this.stringToDate(timetable.checkpointArrivals?.at(i));
// const arrivalDelay =
// arrivalDateReal && arrivalDateScheduled ? arrivalDateReal.getTime() - arrivalDateScheduled.getTime() : 0;
// const departureDelay =
// departureDateReal && departureDateScheduled
// ? departureDateReal.getTime() - departureDateScheduled.getTime()
// : 0;
const arrivalHTML =
(arrivalDateReal && arrivalDateScheduled && arrivalDateReal?.getTime() != arrivalDateScheduled?.getTime()
? `<s class="text--grayed">${this.parseDateToTimeString(arrivalDateScheduled)}</s> `
: '') + this.parseDateToTimeString(arrivalDateReal || arrivalDateScheduled);
const departureHTML =
(departureDateReal &&
departureDateScheduled &&
departureDateReal?.getTime() != departureDateScheduled?.getTime()
? `<s class="text--grayed">${this.parseDateToTimeString(departureDateScheduled)}</s> `
: '') + this.parseDateToTimeString(departureDateReal || departureDateScheduled);
let html = `${arrivalHTML}${departureHTML ? ` / ${departureHTML}` : ''}`;
if (html) html = ` (${html})`;
return { stopName, html, confirmed };
}); });
}, },
@@ -254,7 +343,7 @@ hr {
margin-right: 0.5em; margin-right: 0.5em;
} }
&-status { &-badge {
padding: 0.05em 0.35em; padding: 0.05em 0.35em;
color: black; color: black;
@@ -292,6 +381,7 @@ hr {
.general-train { .general-train {
display: flex; display: flex;
flex-wrap: wrap;
align-items: center; align-items: center;
gap: 0.25em; gap: 0.25em;
} }
@@ -308,12 +398,60 @@ ul.stock-list {
color: #aaa; color: #aaa;
font-size: 0.9em; font-size: 0.9em;
} }
li > img {
vertical-align: text-bottom;
max-height: 60px;
}
} }
.scenery-list { .stock-specs {
display: flex;
flex-wrap: wrap;
gap: 0.5em;
margin-top: 0.5em;
.specs-badge {
margin: 0;
span:last-child {
color: black;
background-color: $accentCol;
}
}
}
.badges {
display: flex;
gap: 0.25em;
// badge.scss
}
.stock-history {
display: flex;
flex-wrap: wrap;
gap: 0.5em;
margin-top: 1em;
button[data-checked='true'] {
color: $accentCol;
}
}
.stop-list {
display: flex;
flex-wrap: wrap;
gap: 0.25em;
color: #adadad; color: #adadad;
span.confirmed {
&-item[data-confirmed='true'] {
color: #a3eba3; color: #a3eba3;
.stop-name {
font-weight: bold;
}
} }
} }
@@ -328,18 +466,8 @@ ul.stock-list {
} }
} }
.info-badge {
span:last-child {
color: black;
background-color: $accentCol;
}
}
@include smallScreen { @include smallScreen {
.info-general { .journal_item-info {
flex-direction: column;
}
.info-extended {
text-align: center; text-align: center;
} }
@@ -351,5 +479,12 @@ ul.stock-list {
.btn--show { .btn--show {
margin: 1em auto 0 auto; margin: 1em auto 0 auto;
} }
.info-general,
.general-train,
.stock-specs,
.stock-history {
justify-content: center;
}
} }
</style> </style>
@@ -2,37 +2,62 @@
<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> <table class="scenery-history-table" v-else-if="historyList.length">
<thead>
<!-- <th>{{ $t('scenery.timetables-history-id') }}</th>
<th>{{ $t('scenery.timetables-history-number') }}</th>
<th>{{ $t('scenery.timetables-history-route') }}</th>
<th>{{ $t('scenery.timetables-history-driver') }}</th>
<th>{{ $t('scenery.timetables-history-author') }}</th>
<th>{{ $t('scenery.timetables-history-date') }}</th> -->
<ul class="history-list" v-else> <th>Hash</th>
<li class="list-item" v-for="item in dispatcherHistoryList"> <th>Dyżurny</th>
<router-link class="item-general" :to="`/journal/dispatchers?dispatcherName=${item.dispatcherName}`"> <th>Poziom</th>
<span class="text--grayed">#{{ item.stationHash }}&nbsp;</span> <th>Ocena</th>
<b <th>Data</th>
v-if="item.dispatcherLevel !== null" </thead>
class="level-badge dispatcher"
:style="calculateExpStyle(item.dispatcherLevel, item.dispatcherIsSupporter)"
>
{{ item.dispatcherLevel >= 2 ? item.dispatcherLevel : 'L' }}
</b>
<b>{{ item.dispatcherName }}</b> <tbody>
</router-link> <tr v-for="historyItem in historyList">
<td>#{{ historyItem.stationHash }}</td>
<td>
<router-link :to="`/journal/dispatchers?dispatcherName=${historyItem.dispatcherName}`">
<b>{{ historyItem.dispatcherName }}</b>
</router-link>
</td>
<td>
<b
v-if="historyItem.dispatcherLevel !== null"
class="level-badge dispatcher"
:style="calculateExpStyle(historyItem.dispatcherLevel, historyItem.dispatcherIsSupporter)"
>
{{ historyItem.dispatcherLevel >= 2 ? historyItem.dispatcherLevel : 'L' }}
</b>
</td>
<td class="text--primary">
<b>{{ historyItem.dispatcherRate }}</b>
</td>
<td style="min-width: 300px">
<div v-if="historyItem.timestampTo">
<b>{{ $d(historyItem.timestampFrom) }}</b>
<div v-if="item.timestampTo"> {{ timestampToString(historyItem.timestampFrom) }}
<b>{{ $d(item.timestampFrom) }}</b> - {{ timestampToString(historyItem.timestampTo) }} ({{ calculateDuration(historyItem.currentDuration) }})
</div>
{{ timestampToString(item.timestampFrom) }} <div class="dispatcher-online" v-else>
- {{ timestampToString(item.timestampTo) }} ({{ calculateDuration(item.currentDuration) }}) {{ $t('journal.online-since') }}
</div> <b>{{ timestampToString(historyItem.timestampFrom) }}</b>
({{ calculateDuration(historyItem.currentDuration) }})
</div>
</td>
</tr>
</tbody>
</table>
<div class="dispatcher-online" v-else> <div class="no-history" v-else>{{ $t('scenery.history-list-empty') }}</div>
{{ $t('journal.online-since') }} <div ref="bottomDiv"></div>
<b>{{ timestampToString(item.timestampFrom) }}</b>
({{ calculateDuration(item.currentDuration) }})
</div>
</li>
</ul>
</section> </section>
</template> </template>
@@ -46,24 +71,35 @@ import Station from '../../scripts/interfaces/Station';
import { URLs } from '../../scripts/utils/apiURLs'; import { URLs } from '../../scripts/utils/apiURLs';
import Loading from '../Global/Loading.vue'; import Loading from '../Global/Loading.vue';
import styleMixin from '../../mixins/styleMixin'; import styleMixin from '../../mixins/styleMixin';
import listObserverMixin from '../../mixins/listObserverMixin';
export default defineComponent({ export default defineComponent({
name: 'SceneryDispatchersHistory', name: 'SceneryDispatchersHistory',
mixins: [dateMixin, styleMixin], mixins: [dateMixin, styleMixin, listObserverMixin],
props: { props: {
station: { station: {
type: Object as PropType<Station>, type: Object as PropType<Station>,
required: true, required: true,
}, },
}, },
data() { data() {
return { return {
dispatcherHistoryList: [] as DispatcherHistory[], historyList: [] as DispatcherHistory[],
dataStatus: DataStatus.Loading, dataStatus: DataStatus.Loading,
}; };
}, },
mounted() {
this.mountObserver(this.fireObserverAction, this.$refs['bottomDiv'] as Element);
},
unmounted() {
this.unmountObserver();
},
activated() { activated() {
this.fetchAPIData(); if (this.historyList.length == 0) this.fetchAPIData();
}, },
methods: { methods: {
async fetchAPIData(countFrom = 0, countLimit = 30) { async fetchAPIData(countFrom = 0, countLimit = 30) {
@@ -71,12 +107,17 @@ export default defineComponent({
const requestString = `${URLs.stacjownikAPI}/api/getDispatchers?stationName=${this.station.name}&countFrom=${countFrom}&countLimit=${countLimit}`; const requestString = `${URLs.stacjownikAPI}/api/getDispatchers?stationName=${this.station.name}&countFrom=${countFrom}&countLimit=${countLimit}`;
const historyAPIData: DispatcherHistory[] = await (await axios.get(requestString)).data; const historyAPIData: DispatcherHistory[] = await (await axios.get(requestString)).data;
this.dispatcherHistoryList = historyAPIData; this.historyList.push(...historyAPIData);
this.dataStatus = DataStatus.Loaded; this.dataStatus = DataStatus.Loaded;
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
}, },
fireObserverAction() {
if (this.historyList.length > 0 && this.dataStatus == DataStatus.Loaded)
this.fetchAPIData(this.historyList.length);
},
}, },
components: { Loading }, components: { Loading },
}); });
@@ -84,30 +125,10 @@ export default defineComponent({
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/responsive.scss'; @import '../../styles/responsive.scss';
@import '../../styles/SceneryView/styles.scss'; @import '../../styles/sceneryViewTables.scss';
.history-list { .level-badge {
padding: 0 0.5em; margin: 0 auto;
}
.list-item {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
text-align: left;
background-color: #353535;
padding: 0.5em;
margin: 0.5em 0;
line-height: 1.5em;
}
.item-general {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0.25em;
} }
.dispatcher-online { .dispatcher-online {
+9 -3
View File
@@ -4,6 +4,8 @@
{{ station.name }} {{ station.name }}
</a> </a>
<div class="scenery-abbrev">{{ $t('scenery.abbrev') }} <b>{{ station.generalInfo?.abbr }}</b></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>
</section> </section>
</template> </template>
@@ -28,16 +30,20 @@ export default defineComponent({
.scenery-name { .scenery-name {
font-weight: bold; font-weight: bold;
position: relative;
font-size: 3em; font-size: 3em;
text-transform: uppercase; text-transform: uppercase;
} }
.scenery-abbrev {
font-size: 1.3em;
color: #aaa;
}
.scenery-hash { .scenery-hash {
margin-top: 0.5em;
color: #aaa; color: #aaa;
font-size: 1.2em; font-size: 1.2em;
} }
</style> </style>
+2 -1
View File
@@ -30,8 +30,9 @@
style="color: salmon; text-decoration: underline; font-weight: bold" style="color: salmon; text-decoration: underline; font-weight: bold"
:href="station.generalInfo.projectUrl" :href="station.generalInfo.projectUrl"
target="_blank" target="_blank"
>{{ station.generalInfo.project }}</a
> >
{{ station.generalInfo.project }}
</a>
</span> </span>
</div> </div>
@@ -4,9 +4,11 @@
<b>{{ $t('scenery.one-way-routes') }}</b> <b>{{ $t('scenery.one-way-routes') }}</b>
<ul class="routes-list"> <ul class="routes-list">
<li v-for="route in station.generalInfo.routes.oneWay"> <li v-for="route in station.generalInfo.routes.oneWay" @click="setActiveShowLength(route.name)">
<span :class="{ 'no-catenary': !route.catenary, internal: route.isInternal }"> {{ route.name }}</span> <span :class="{ 'no-catenary': !route.catenary, internal: route.isInternal }"> {{ route.name }}</span>
<span v-if="route.speed" class="speed">{{ route.speed }}</span> <span v-if="route.speed" class="speed">
{{ activeShowLength.includes(route.name) ? route.length + 'm' : route.speed }}
</span>
<span v-if="route.SBL" class="sbl">SBL</span> <span v-if="route.SBL" class="sbl">SBL</span>
</li> </li>
</ul> </ul>
@@ -16,9 +18,11 @@
<b>{{ $t('scenery.two-way-routes') }}</b> <b>{{ $t('scenery.two-way-routes') }}</b>
<ul class="routes-list"> <ul class="routes-list">
<li v-for="route in station.generalInfo.routes.twoWay"> <li v-for="(route, i) in station.generalInfo.routes.twoWay" @click="setActiveShowLength(route.name)">
<span :class="{ 'no-catenary': !route.catenary, internal: route.isInternal }">{{ route.name }}</span> <span :class="{ 'no-catenary': !route.catenary, internal: route.isInternal }">{{ route.name }}</span>
<span v-if="route.speed" class="speed">{{ route.speed }}</span> <span v-if="route.speed" class="speed">
{{ activeShowLength.includes(route.name) ? route.length + 'm' : route.speed }}
</span>
<span v-if="route.SBL" class="sbl">SBL</span> <span v-if="route.SBL" class="sbl">SBL</span>
</li> </li>
</ul> </ul>
@@ -37,6 +41,19 @@ export default defineComponent({
default: {}, default: {},
}, },
}, },
methods: {
setActiveShowLength(name: string) {
if (this.activeShowLength.includes(name)) this.activeShowLength.splice(this.activeShowLength.indexOf(name), 1);
else this.activeShowLength.push(name);
},
},
data() {
return {
activeShowLength: [] as string[],
};
},
}); });
</script> </script>
@@ -66,6 +83,11 @@ ul.routes-list {
li { li {
margin: 0.5em 0.25em; margin: 0.5em 0.25em;
cursor: pointer;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
span { span {
padding: 0.2em 0.25em; padding: 0.2em 0.25em;
@@ -100,7 +122,6 @@ ul.routes-list {
&:only-child { &:only-child {
border-radius: 0.5em; border-radius: 0.5em;
} }
} }
} }
@@ -1,52 +1,65 @@
<template> <template>
<section class="info-spawn-list"> <section class="info-spawn-list">
<h3 class="spawn-header section-header"> <h3 class="spawn-header section-header">
<img :src="getIcon('spawn')" alt="icon-spawn" /> <img :src="getIcon('spawn')" alt="icon-spawn" />
&nbsp;{{ $t('scenery.spawns') }} &nbsp; &nbsp;{{ $t('scenery.spawns') }} &nbsp;
<span class="text--primary">{{ station.onlineInfo?.spawns.length || '0' }}</span> <span class="text--primary">{{ station.onlineInfo?.spawns.length || '0' }}</span>
</h3> </h3>
<span v-if="station.onlineInfo"> <span v-if="station.onlineInfo">
<span <span
class="badge spawn" class="badge spawn"
v-for="(spawn, i) in station.onlineInfo.spawns" v-for="(spawn, i) in sortedSpawns"
:key="spawn.spawnName + station.onlineInfo?.dispatcherName + i" :key="spawn.spawnName + station.onlineInfo?.dispatcherName + i"
> :data-electrified="spawn.isElectrified"
<span class="spawn_name">{{ spawn.spawnName }}</span> >
<span class="spawn_length">{{ spawn.spawnLength }}m</span> <span class="spawn_name">{{ spawn.spawnName }}</span>
</span> <span class="spawn_length">{{ spawn.spawnLength }}m</span>
</span> </span>
</span>
<span class="badge spawn badge-none" v-if="!station.onlineInfo || station.onlineInfo.spawns.length == 0"
>{{ $t('scenery.no-spawns') }} <span class="badge spawn badge-none" v-if="!station.onlineInfo || station.onlineInfo.spawns.length == 0"
</span> >{{ $t('scenery.no-spawns') }}
</section> </span>
</template> </section>
</template>
<script lang="ts">
import { defineComponent } from 'vue'; <script lang="ts">
import imageMixin from '../../../mixins/imageMixin'; import { defineComponent } from 'vue';
import Station from '../../../scripts/interfaces/Station'; import imageMixin from '../../../mixins/imageMixin';
import Station from '../../../scripts/interfaces/Station';
export default defineComponent({
mixins: [imageMixin], export default defineComponent({
mixins: [imageMixin],
props: {
station: { props: {
type: Object as () => Station, station: {
default: {}, type: Object as () => Station,
}, default: {},
}, },
}); },
</script>
computed: {
<style lang="scss" scoped> sortedSpawns() {
@import '../../../styles/variables.scss'; return this.station.onlineInfo?.spawns.sort((s1, s2) => (s1.spawnLength < s2.spawnLength ? 1 : -1));
},
.spawn { },
&_length { });
background: $accentCol; </script>
color: black;
} <style lang="scss" scoped>
} @import '../../../styles/variables.scss';
</style>
.spawn {
color: white;
&_length {
background-color: #404040;
color: #cfcfcf;
}
&[data-electrified='true'] > &_name {
background-color: #007599;
}
}
</style>
@@ -2,18 +2,18 @@
<section class="scenery-timetables-history scenery-section"> <section class="scenery-timetables-history scenery-section">
<Loading v-if="dataStatus != 2" /> <Loading v-if="dataStatus != 2" />
<table v-else-if="sceneryHistoryList.length"> <table class="scenery-history-table" v-else-if="historyList.length">
<thead> <thead>
<th>{{ $t('scenery.timetables-history-id') }}</th> <th>{{ $t('scenery.timetables-history-id') }}</th>
<th>{{ $t('scenery.timetables-history-number')}}</th> <th>{{ $t('scenery.timetables-history-number') }}</th>
<th>{{ $t('scenery.timetables-history-route')}}</th> <th>{{ $t('scenery.timetables-history-route') }}</th>
<th>{{ $t('scenery.timetables-history-driver')}}</th> <th>{{ $t('scenery.timetables-history-driver') }}</th>
<th>{{ $t('scenery.timetables-history-author')}}</th> <th>{{ $t('scenery.timetables-history-author') }}</th>
<th>{{ $t('scenery.timetables-history-date')}}</th> <th>{{ $t('scenery.timetables-history-date') }}</th>
</thead> </thead>
<tbody> <tbody>
<tr v-for="historyItem in sceneryHistoryList" @click="test"> <tr v-for="historyItem in historyList">
<td> <td>
<router-link :to="`/journal/timetables?timetableId=${historyItem.id}`">#{{ historyItem.id }}</router-link> <router-link :to="`/journal/timetables?timetableId=${historyItem.id}`">#{{ historyItem.id }}</router-link>
</td> </td>
@@ -39,32 +39,8 @@
</tbody> </tbody>
</table> </table>
<div class="list-warning" v-else>{{ $t('scenery.history-list-empty') }}</div> <div class="no-history" v-else>{{ $t('scenery.history-list-empty') }}</div>
<div ref="bottomDiv"></div>
<!-- <ul class="history-list" v-else>
<li class="list-item" v-for="historyItem in sceneryHistoryList">
<div>
<b>{{ localeDay(historyItem.beginDate, $i18n.locale) }}</b>
{{ localeTime(historyItem.beginDate, $i18n.locale) }}
</div>
<div>
<router-link :to="`/journal/timetables?timetableId=${historyItem.id}`">
<span class="text--grayed"> #{{ historyItem.id }} </span>
<b class="text--primary">&nbsp;{{ historyItem.trainCategoryCode }} {{ historyItem.trainNo }}</b>
<div>{{ historyItem.driverName }}</div>
</router-link>
</div>
<div>{{ historyItem.route.replace('|', ' -> ') }}</div>
<div>
{{ $t('scenery.timetable-author-title') }}:
<b v-if="historyItem.authorName">{{ historyItem.authorName }}</b>
<i v-else>{{ $t('scenery.timetable-author-unknown') }}</i>
</div>
</li>
</ul> -->
</section> </section>
</template> </template>
@@ -77,40 +53,53 @@ import { TimetableHistory, SceneryTimetableHistory } from '../../scripts/interfa
import Station from '../../scripts/interfaces/Station'; import Station from '../../scripts/interfaces/Station';
import { URLs } from '../../scripts/utils/apiURLs'; import { URLs } from '../../scripts/utils/apiURLs';
import Loading from '../Global/Loading.vue'; import Loading from '../Global/Loading.vue';
import listObserverMixin from '../../mixins/listObserverMixin';
export default defineComponent({ export default defineComponent({
name: 'SceneryTimetablesHistory', name: 'SceneryTimetablesHistory',
mixins: [dateMixin], mixins: [dateMixin, listObserverMixin],
props: { props: {
station: { station: {
type: Object as PropType<Station>, type: Object as PropType<Station>,
required: true, required: true,
}, },
}, },
data() { data() {
return { return {
sceneryHistoryList: [] as TimetableHistory[], historyList: [] as TimetableHistory[],
dataStatus: DataStatus.Loading, dataStatus: DataStatus.Loading,
}; };
}, },
activated() {
this.fetchAPIData(); mounted() {
this.mountObserver(this.fireObserverAction, this.$refs['bottomDiv'] as Element);
}, },
unmounted() {
this.unmountObserver();
},
activated() {
if (this.historyList.length == 0) this.fetchAPIData();
},
methods: { methods: {
async fetchAPIData(countFrom = 0, countLimit = 15) { async fetchAPIData(countFrom = 0, countLimit = 15) {
try { try {
const requestString = `${URLs.stacjownikAPI}/api/getSceneryTimetables?name=${this.station.name}&countFrom=${countFrom}&countLimit=${countLimit}`; const requestString = `${URLs.stacjownikAPI}/api/getIssuedTimetables?name=${this.station.name}&countFrom=${countFrom}&countLimit=${countLimit}`;
const historyAPIData: SceneryTimetableHistory = await (await axios.get(requestString)).data; const historyAPIData: SceneryTimetableHistory = await (await axios.get(requestString)).data;
this.sceneryHistoryList = historyAPIData.sceneryTimetables; this.historyList.push(...historyAPIData.timetables);
this.dataStatus = DataStatus.Loaded; this.dataStatus = DataStatus.Loaded;
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
}, },
test() { fireObserverAction() {
console.log('test'); if (this.historyList.length > 0 && this.dataStatus == DataStatus.Loaded)
this.fetchAPIData(this.historyList.length);
}, },
}, },
components: { Loading }, components: { Loading },
@@ -119,46 +108,5 @@ export default defineComponent({
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/responsive.scss'; @import '../../styles/responsive.scss';
@import '../../styles/SceneryView/styles.scss'; @import '../../styles/sceneryViewTables.scss';
.list-warning {
padding: 1em 0.5em;
background-color: #444;
font-size: 1.2em;
}
.history-list {
padding: 0 0.5em;
}
table {
width: 100%;
border-collapse: collapse;
thead {
position: sticky;
top: 0;
background-color: #222222;
}
th {
padding: 0.5em;
}
tr {
background-color: #353535;
border: none;
}
td {
padding: 0.75em;
border-bottom: solid 5px #111;
}
}
@include smallScreen {
.list-item {
grid-template-columns: 1fr 1fr;
}
}
</style> </style>
@@ -1,47 +1,19 @@
<template> <template>
<div class="general-status"> <div class="general-status">
<span :class="scheduledTrain.stopStatus"> <span :class="computedScheduledTrain.stopStatus" :title="computedScheduledTrain.stopStatusDescription">
<span v-if="scheduledTrain.stopStatus == 'arriving'"> {{ computedScheduledTrain.stopStatusIndicator }}
<span v-if="scheduledTrain.prevDepartureLine">({{ scheduledTrain.prevDepartureLine }})</span>
{{ scheduledTrain.prevStationName }}
&gt;<span v-if="scheduledTrain.nextArrivalLine"> ({{ scheduledTrain.nextArrivalLine }}) </span>
{{ scheduledTrain.nextStationName || '---' }}
</span>
<span v-else-if="scheduledTrain.stopStatus == 'departed'">
&gt;&gt; <span v-if="scheduledTrain.nextArrivalLine"> ({{ scheduledTrain.nextArrivalLine }}) </span>
{{ scheduledTrain.nextStationName }}
</span>
<span v-else-if="scheduledTrain.stopStatus == 'departed-away'">
&gt;&gt;&gt;
<span v-if="scheduledTrain.nextArrivalLine"> ({{ scheduledTrain.nextArrivalLine }}) </span>
{{ scheduledTrain.nextStationName }}
</span>
<span v-else-if="scheduledTrain.stopStatus == 'online'">
&gt;
<span v-if="scheduledTrain.nextArrivalLine">
({{ scheduledTrain.nextArrivalLine }}) {{ scheduledTrain.nextStationName }}
</span>
<span v-else-if="!scheduledTrain.nextStationName">{{ $t('timetables.end') }}</span>
<span v-else>{{ scheduledTrain.nextStationName }}</span>
</span>
<span v-else-if="scheduledTrain.stopStatus == 'stopped'">
&gt;
<span v-if="scheduledTrain.nextArrivalLine"> ({{ scheduledTrain.nextArrivalLine }}) </span>
{{ scheduledTrain.nextStationName }}
</span>
<span v-else-if="scheduledTrain.stopStatus == 'terminated'">X {{ $t('timetables.terminated') }}</span>
</span> </span>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, PropType } from 'vue'; import { defineComponent, PropType } from 'vue';
import ScheduledTrain from '../../scripts/interfaces/ScheduledTrain'; import { ScheduledTrain, StopStatus } from '../../scripts/interfaces/ScheduledTrain';
interface ScheduledTrainComp extends ScheduledTrain {
stopStatusIndicator: string;
stopStatusDescription: string;
}
export default defineComponent({ export default defineComponent({
props: { props: {
@@ -50,6 +22,58 @@ export default defineComponent({
required: true, required: true,
}, },
}, },
computed: {
computedScheduledTrain(): ScheduledTrainComp {
const { prevDepartureLine, prevStationName, stopStatus, nextArrivalLine, nextStationName } = this.scheduledTrain;
const prevDepartureIndicator = prevDepartureLine ? `(${prevDepartureLine}) ${prevStationName}` : '---';
const nextArrivalIndicator = nextArrivalLine ? `(${nextArrivalLine}) ${nextStationName}` : '---';
let stopStatusDescription = '',
stopStatusIndicator = '';
switch (stopStatus) {
case StopStatus.arriving:
stopStatusIndicator = `${this.$t('timetables.from')}: ${prevDepartureIndicator}`;
stopStatusDescription = this.$t('timetables.desc-arriving', { prevStationName, prevDepartureLine });
break;
case StopStatus.online:
case StopStatus.stopped:
stopStatusIndicator = nextArrivalLine
? `${this.$t('timetables.to')}: ${nextArrivalIndicator}`
: `${this.$t('timetables.desc-end')}`;
stopStatusDescription = nextArrivalLine
? this.$t(`timetables.desc-${stopStatus}`, { nextStationName, nextArrivalLine })
: '';
break;
case StopStatus.departed:
stopStatusIndicator = `${this.$t('timetables.to')}: ${nextArrivalIndicator}`;
stopStatusDescription = this.$t('timetables.desc-departed', { nextStationName, nextArrivalLine });
break;
case StopStatus['departed-away']:
stopStatusIndicator = `${this.$t('timetables.to')}: ${nextArrivalIndicator}`;
stopStatusDescription = this.$t('timetables.desc-departed-away', { nextStationName, nextArrivalLine });
break;
case StopStatus.terminated:
stopStatusIndicator = `X ${this.$t('timetables.desc-terminated')}`;
stopStatusDescription = this.$t('timetables.desc-terminated');
break;
default:
break;
}
return {
...this.scheduledTrain,
stopStatusDescription,
stopStatusIndicator,
};
},
},
}); });
</script> </script>
@@ -86,3 +110,4 @@ export default defineComponent({
} }
} }
</style> </style>
@@ -50,9 +50,6 @@ export default defineComponent({
handleDbClick(e: Event) { handleDbClick(e: Event) {
e.preventDefault(); e.preventDefault();
const lastClicked = this.filterStore.lastClickedFilterId == this.option.id;
console.log(this.filterStore.lastClickedFilterId);
this.filterStore.lastClickedFilterId = this.option.id; this.filterStore.lastClickedFilterId = this.option.id;
this.option.value = true; this.option.value = true;
@@ -96,51 +93,8 @@ button {
} }
&[data-selected='true'] { &[data-selected='true'] {
// &.reality {
// background-color: $realityCol;
// box-shadow: 0 0 6px 1px $realityCol;
// }
// &.access {
// background-color: $accessCol;
// box-shadow: 0 0 6px 1px $accessCol;
// }
// &.control {
// background-color: $controlCol;
// box-shadow: 0 0 6px 1px $controlCol;
// }
// &.signals {
// background-color: $signalCol;
// box-shadow: 0 0 6px 1px $signalCol;
// }
// &.routes {
// background-color: $routesCol;
// box-shadow: 0 0 6px 1px $routesCol;
// }
// &.status {
// background-color: $statusCol;
// box-shadow: 0 0 6px 1px $statusCol;
// }
// &.save {
// background-color: $saveCol;
// box-shadow: 0 0 6px 1px $saveCol;
// }
// &.troll {
// background-color: firebrick;
// box-shadow: 0 0 6px 1px firebrick;
// }
// & {
background-color: forestgreen; background-color: forestgreen;
font-weight: bold; font-weight: bold;
// }
} }
} }
</style> </style>
@@ -128,7 +128,6 @@ import { useStore } from '../../store/store';
import ActionButton from '../Global/ActionButton.vue'; import ActionButton from '../Global/ActionButton.vue';
import FilterOption from './FilterOption.vue'; import FilterOption from './FilterOption.vue';
import { filterInitStates } from '../../store/constants/initFilterStates';
export default defineComponent({ export default defineComponent({
components: { ActionButton, FilterOption }, components: { ActionButton, FilterOption },
+49 -24
View File
@@ -8,26 +8,36 @@
<table> <table>
<thead> <thead>
<tr> <tr>
<th v-for="(id, i) in headIds" :key="id" @click="() => changeSorter(i)"> <th
v-for="(headerName, i) in headIds"
:key="headerName"
@click="changeSorter(headerName)"
class="header-text"
>
<span class="header_wrapper"> <span class="header_wrapper">
<div v-html="$t(`sceneries.${id}`)"></div> <div v-html="$t(`sceneries.${headerName}`)"></div>
<img <img
class="sort-icon" class="sort-icon"
v-if="sorterActive.index == i" v-if="sorterActive.headerName == headerName"
:src="sorterActive.dir == 1 ? getIcon('arrow-asc') : getIcon('arrow-desc')" :src="sorterActive.dir == 1 ? getIcon('arrow-asc') : getIcon('arrow-desc')"
alt="sort icon" alt="sort icon"
/> />
</span> </span>
</th> </th>
<th v-for="(id, i) in headIconsIds" :key="id" @click="() => changeSorter(i + 7)"> <th
v-for="(headerName, i) in headIconsIds"
:key="headerName"
@click="changeSorter(headerName)"
class="header-image"
>
<span class="header_wrapper"> <span class="header_wrapper">
<img :src="getIcon(id)" :alt="id" :title="$t(`sceneries.${id}s`)" /> <img :src="getIcon(headerName)" :alt="headerName" :title="$t(`sceneries.${headerName}`)" />
<img <img
class="sort-icon" class="sort-icon"
v-if="sorterActive.index == i + 7" v-if="sorterActive.headerName == headerName"
:src="sorterActive.dir == 1 ? getIcon('arrow-asc') : getIcon('arrow-desc')" :src="sorterActive.dir == 1 ? getIcon('arrow-asc') : getIcon('arrow-desc')"
alt="sort icon" alt="sort icon"
/> />
@@ -190,25 +200,31 @@
<td class="station_users" :class="{ inactive: !station.onlineInfo }"> <td class="station_users" :class="{ inactive: !station.onlineInfo }">
<span> <span>
<span class="highlight">{{ station.onlineInfo?.currentUsers || '0' }}</span> <span class="highlight">{{ station.onlineInfo?.currentUsers || 0 }}</span>
/ /
<span>{{ station.onlineInfo?.maxUsers || '0' }}</span> <span class="highlight">{{ station.onlineInfo?.maxUsers || 0 }}</span>
</span> </span>
</td> </td>
<td class="station_spawns" :class="{ inactive: !station.onlineInfo }"> <td class="station_spawns" :class="{ inactive: !station.onlineInfo }">
<span class="highlight">{{ station.onlineInfo?.spawns.length || '0' }}</span> <span>{{ station.onlineInfo?.spawns.length || 0 }}</span>
</td> </td>
<td class="station_schedules" :class="{ inactive: !station.onlineInfo }"> <td class="station_schedules" style="width: 30px" :class="{ inactive: !station.onlineInfo }">
<span> <span class="highlight">
<span class="highlight"> {{ station.onlineInfo?.scheduledTrains?.length || 0 }}
{{ station.onlineInfo?.scheduledTrains?.length || '0' }} </span>
</span> </td>
/
<span style="color: #bbb"> <td class="station_schedules" style="width: 30px" :class="{ inactive: !station.onlineInfo }">
{{ station.onlineInfo?.scheduledTrains?.filter((train) => train.stopInfo.confirmed).length || '0' }} <span style="color: #ccc">
</span> {{ station.onlineInfo?.scheduledTrains?.filter((train) => !train.stopInfo.confirmed).length || 0 }}
</span>
</td>
<td class="station_schedules" style="width: 30px" :class="{ inactive: !station.onlineInfo }">
<span style="color: #66ff6c">
{{ station.onlineInfo?.scheduledTrains?.filter((train) => train.stopInfo.confirmed).length || 0 }}
</span> </span>
</td> </td>
</tr> </tr>
@@ -236,6 +252,7 @@ import Station from '../../scripts/interfaces/Station';
import { useStationFiltersStore } from '../../store/stationFiltersStore'; import { useStationFiltersStore } from '../../store/stationFiltersStore';
import { useStore } from '../../store/store'; import { useStore } from '../../store/store';
import Loading from '../Global/Loading.vue'; import Loading from '../Global/Loading.vue';
import { HeadIdsTypes, headIconsIds, headIds } from '../../scripts/data/stationHeaderNames';
export default defineComponent({ export default defineComponent({
props: { props: {
@@ -249,8 +266,8 @@ export default defineComponent({
mixins: [styleMixin, dateMixin, stationInfoMixin, returnBtnMixin, imageMixin], mixins: [styleMixin, dateMixin, stationInfoMixin, returnBtnMixin, imageMixin],
data: () => ({ data: () => ({
headIds: ['station', 'min-lvl', 'status', 'dispatcher', 'dispatcher-lvl', 'routes', 'general'], headIconsIds,
headIconsIds: ['user', 'spawn', 'timetable'], headIds,
lastSelectedStationName: '', lastSelectedStationName: '',
}), }),
@@ -291,8 +308,10 @@ export default defineComponent({
window.open(url, '_blank'); window.open(url, '_blank');
}, },
changeSorter(i: number) { changeSorter(headerName: HeadIdsTypes) {
this.stationFiltersStore.changeSorter(i); if (headerName == 'general' || headerName == 'routes') return;
this.stationFiltersStore.changeSorter(headerName);
}, },
}, },
}); });
@@ -349,9 +368,15 @@ table {
position: sticky; position: sticky;
top: 0; top: 0;
min-width: 75px; &.header-text {
min-width: 140px;
}
padding: 0.5em; &.header-image {
min-width: 60px;
}
padding: 0.5em 0.25em;
background-color: $bgCol; background-color: $bgCol;
white-space: pre-wrap; white-space: pre-wrap;
+3 -26
View File
@@ -6,8 +6,8 @@
<span class="timetable-id" v-if="train.timetableData">#{{ train.timetableData.timetableId }}</span> <span class="timetable-id" v-if="train.timetableData">#{{ train.timetableData.timetableId }}</span>
<span class="timetable_warnings" v-if="train.timetableData?.TWR || train.timetableData?.SKR"> <span class="timetable_warnings" v-if="train.timetableData?.TWR || train.timetableData?.SKR">
<span class="train-badge twr" v-if="train.timetableData?.TWR">TWR</span> <span class="train-badge twr" v-if="train.timetableData?.TWR" :title="$t('general.TWR')">TWR</span>
<span class="train-badge skr" v-if="train.timetableData?.SKR">SKR</span> <span class="train-badge skr" v-if="train.timetableData?.SKR" :title="$t('general.SKR')">SKR</span>
</span> </span>
<strong> <strong>
@@ -118,7 +118,6 @@ export default defineComponent({
@import '../../styles/responsive.scss'; @import '../../styles/responsive.scss';
@import '../../styles/badge.scss'; @import '../../styles/badge.scss';
.image-warning { .image-warning {
height: 1em; height: 1em;
@@ -182,26 +181,6 @@ export default defineComponent({
gap: 0.25em; gap: 0.25em;
} }
.train-badge {
padding: 0.1em 0.2em;
border-radius: 0.2em;
font-weight: bold;
font-size: 0.9em;
&.twr {
background-color: var(--clr-twr);
}
&.skr {
background-color: var(--clr-skr);
}
&.offline {
background-color: #9c362b;
}
}
.train-driver { .train-driver {
&.supporter { &.supporter {
color: orange; color: orange;
@@ -218,9 +197,7 @@ export default defineComponent({
.timetable_warnings { .timetable_warnings {
display: flex; display: flex;
gap: 0.2em; gap: 0.25em;
color: black;
} }
.timetable_progress { .timetable_progress {
+1 -1
View File
@@ -82,10 +82,10 @@
import { defineComponent, inject, PropType } from 'vue'; import { defineComponent, inject, PropType } from 'vue';
import imageMixin from '../../mixins/imageMixin'; import imageMixin from '../../mixins/imageMixin';
import keyMixin from '../../mixins/keyMixin'; import keyMixin from '../../mixins/keyMixin';
import { TrainFilter } from '../../types/Trains/TrainOptionsTypes';
import ActionButton from '../Global/ActionButton.vue'; import ActionButton from '../Global/ActionButton.vue';
import SelectBox from '../Global/SelectBox.vue'; import SelectBox from '../Global/SelectBox.vue';
import { TrainFilterSection } from '../../scripts/enums/TrainFilterType'; import { TrainFilterSection } from '../../scripts/enums/TrainFilterType';
import { TrainFilter } from '../../scripts/interfaces/Trains/TrainFilter';
export default defineComponent({ export default defineComponent({
components: { SelectBox, ActionButton }, components: { SelectBox, ActionButton },
@@ -202,7 +202,6 @@ ul.stock-list {
img { img {
max-height: 60px; max-height: 60px;
max-width: 320px;
} }
} }
@@ -1,28 +1,46 @@
import { JournalFilterType } from "../../scripts/enums/JournalFilterType"; import { JournalFilterSection, JournalFilterType } from '../../scripts/enums/JournalFilterType';
import { JournalTimetableFilter } from "../../types/Journal/JournalTimetablesTypes"; import { JournalFilter } from '../../scripts/types/JournalTimetablesTypes';
export const journalTimetableFilters: JournalTimetableFilter[] = [ export const journalTimetableFilters: JournalFilter[] = [
{ {
id: JournalFilterType.all, id: JournalFilterType.ALL,
filterSection: 'timetable-status', filterSection: JournalFilterSection.TIMETABLE_STATUS,
isActive: true, isActive: true,
}, },
{ {
id: JournalFilterType.active, id: JournalFilterType.ACTIVE,
filterSection: 'timetable-status', filterSection: JournalFilterSection.TIMETABLE_STATUS,
isActive: false, isActive: false,
}, },
{ {
id: JournalFilterType.fulfilled, id: JournalFilterType.FULFILLED,
filterSection: 'timetable-status', filterSection: JournalFilterSection.TIMETABLE_STATUS,
isActive: false, isActive: false,
}, },
{ {
id: JournalFilterType.abandoned, id: JournalFilterType.ABANDONED,
filterSection: 'timetable-status', filterSection: JournalFilterSection.TIMETABLE_STATUS,
isActive: false,
},
{
id: JournalFilterType.TWR_SKR,
filterSection: JournalFilterSection.TWRSKR,
isActive: true,
},
{
id: JournalFilterType.TWR,
filterSection: JournalFilterSection.TWRSKR,
isActive: false,
},
{
id: JournalFilterType.SKR,
filterSection: JournalFilterSection.TWRSKR,
isActive: false, isActive: false,
}, },
]; ];
+1 -1
View File
@@ -1,5 +1,5 @@
import { TrainFilterSection, TrainFilterType } from '../../scripts/enums/TrainFilterType'; import { TrainFilterSection, TrainFilterType } from '../../scripts/enums/TrainFilterType';
import { TrainFilter } from '../../types/Trains/TrainOptionsTypes'; import { TrainFilter } from '../../scripts/interfaces/Trains/TrainFilter';
export const trainFilters: TrainFilter[] = [ export const trainFilters: TrainFilter[] = [
{ {
+53 -18
View File
@@ -1,7 +1,9 @@
{ {
"general": { "general": {
"and": " and ", "and": " and ",
"refresh": "REFRESH" "refresh": "REFRESH",
"TWR": "High risk freight train",
"SKR": "Train with exceeded gauge"
}, },
"app": { "app": {
"sceneries": "SCENERIES", "sceneries": "SCENERIES",
@@ -15,6 +17,9 @@
"migration-confirm": "Roger that!", "migration-confirm": "Roger that!",
"offline": "App is in the offline mode!" "offline": "App is in the offline mode!"
}, },
"footer": {
"discord": "Stacjownik Discord server"
},
"update": { "update": {
"title": "New version of the app is available!", "title": "New version of the app is available!",
"paragraph1": "Enjoy the application and may the green signal be with you!", "paragraph1": "Enjoy the application and may the green signal be with you!",
@@ -94,19 +99,21 @@
"search-dispatcher": "Dispatcher name", "search-dispatcher": "Dispatcher name",
"search-station": "Scenery name", "search-station": "Scenery name",
"search-author": "Timetable author name", "search-author": "Timetable author name",
"search-timetables-date": "Timetable date (CEST / GMT+2)", "search-issuedFrom": "Origin scenery name",
"search-dispatchers-date": "Service date (CEST / GMT+2)", "search-timetables-date": "Timetable date (UTC+2 / CEST)",
"search-dispatchers-date": "Service date (UTC+2 / CEST)",
"search-date": "Date (UTC+2 / CEST)",
"sort-mass": "mass", "sort-mass": "mass",
"sort-speed": "speed", "sort-speed": "speed",
"sort-length": "length", "sort-length": "length",
"sort-distance": "distance", "sort-routeDistance": "route distance",
"sort-timetable": "train no.", "sort-timetable": "train no.",
"sort-progress": "route progress", "sort-progress": "route progress",
"sort-delay": "current delay", "sort-delay": "current delay",
"sort-id": "timetable id", "sort-id": "timetable id",
"sort-total-stops": "total stops", "sort-allStopsCount": "total stops",
"sort-beginDate": "date", "sort-beginDate": "date",
"sort-timetableId": "timetable ID", "sort-timetableId": "timetable ID",
"sort-timestampFrom": "date", "sort-timestampFrom": "date",
@@ -116,6 +123,7 @@
"filter-withComments": "COMMENTS", "filter-withComments": "COMMENTS",
"filter-twr": "HIGH RISK CARGO", "filter-twr": "HIGH RISK CARGO",
"filter-skr": "EXCEEDED GAUGE", "filter-skr": "EXCEEDED GAUGE",
"filter-twr-skr": "ALL TYPES",
"filter-common": "NO WARNINGS", "filter-common": "NO WARNINGS",
"filter-passenger": "PASSENGER", "filter-passenger": "PASSENGER",
"filter-freight": "FREIGHT", "filter-freight": "FREIGHT",
@@ -126,6 +134,9 @@
"filter-reset": "RESET FILTERS", "filter-reset": "RESET FILTERS",
"filter-clear": "CLEAR FILTERS", "filter-clear": "CLEAR FILTERS",
"filter-section-timetable-status": "TIMETABLE STATUS",
"filter-section-twrskr": "WARNINGS",
"filter-all": "ALL ENTRIES", "filter-all": "ALL ENTRIES",
"filter-abandoned": "ABANDONED", "filter-abandoned": "ABANDONED",
"filter-fulfilled": "FULFILLED", "filter-fulfilled": "FULFILLED",
@@ -208,13 +219,15 @@
"dispatcher-lvl": "Dispatcher\nlevel", "dispatcher-lvl": "Dispatcher\nlevel",
"routes": "Routes\ndouble / single", "routes": "Routes\ndouble / single",
"general": "General info", "general": "General info",
"users": "Drivers online", "user": "Drivers online",
"spawns": "Spawns online", "spawn": "Spawns online",
"timetables": "Active timetables", "timetableAll": "Active timetables",
"timetableConfirmed": "Confirmed timetables",
"timetableUnconfirmed": "Unconfirmed timetables",
"no-stations": "No stations to show here!", "no-stations": "No stations to show here!",
"scenery-search": "Search for scenery..." "scenery-search": "Search for scenery..."
}, },
"trains": { "trains": {
"no-trains": "No trains to show here!", "no-trains": "No trains to show here!",
"loading": "Loading train data...", "loading": "Loading train data...",
"offline": "Offline ride", "offline": "Offline ride",
@@ -261,6 +274,7 @@
"title": "DISPATCHER HISTORY", "title": "DISPATCHER HISTORY",
"loading": "Loading dispatcher history data...", "loading": "Loading dispatcher history data...",
"no-history": "No dispatcher history found!", "no-history": "No dispatcher history found!",
"data-refreshed-at": "Data refreshed at",
"section-timetables": "TIMETABLES", "section-timetables": "TIMETABLES",
"section-dispatchers": "DISPATCHERS", "section-dispatchers": "DISPATCHERS",
@@ -278,13 +292,15 @@
"online-since": "ONLINE SINCE", "online-since": "ONLINE SINCE",
"duty-lasted": "The duty lasted", "duty-lasted": "The duty lasted",
"minutes": "{minutes} mins",
"hours": "{hours}h {minutes} mins", "hours": "{value} hour | {value} hours",
"minutes": "{value} min | {value} mins",
"seconds": "{value} s",
"stock-info": "EXTRA INFO", "stock-info": "EXTRA INFO",
"stock-length": "Length", "stock-length": "Length",
"stock-mass": "Mass", "stock-mass": "Mass",
"stock-max-speed": "Maximum registered speed", "stock-max-speed": "Max. speed",
"load-data": "Load further data...", "load-data": "Load further data...",
@@ -299,10 +315,13 @@
"stats-distance": "DISTANCE", "stats-distance": "DISTANCE",
"stats-stations": "STATIONS", "stats-stations": "STATIONS",
"timetable-stats-total": "Today, dispatchers made so far {count} with total distance of {distance}", "timetable-stats-title": "Daily stats on {date}",
"timetable-stats-longest": "The longest timetable today is #{id} made by {author} for {driver} - {distance}", "timetable-stats-total": "Issued timetables: {count} (total distance: {distance})",
"timetable-stats-most-active": "The most active dispatcher today is {dispatcher} who created {count}", "timetable-stats-longest": "The longest timetable: #{id} (made by {author} for {driver}, distance: {distance})",
"timetable-stats-most-active-many": "The most active dispatchers today are {dispatchers} who created {count} each", "timetable-stats-most-active-dr": "The most active dispatcher: {dispatcher} (created {count})",
"timetable-stats-most-active-dr-many": "The most active dispatchers: {dispatchers} (created {count} each)",
"timetable-stats-most-active-driver": "The most active driver: {driver} (total driven distance: {distance})",
"timetable-stats-longest-duties": "The longest service: {dispatcher} at {station} (duration: {duration})",
"timetable-count": "timetable | timetables", "timetable-count": "timetable | timetables",
@@ -313,7 +332,10 @@
"driver-stats-info": "Enter a proper nickname into filters [F] to see user's driving statistics!", "driver-stats-info": "Enter a proper nickname into filters [F] to see user's driving statistics!",
"stats-loading": "Fetching statistics...", "stats-loading": "Fetching statistics...",
"stats-error": "Oops! An unexpected error occurred while trying to fetch statistics! :/" "stats-error": "Oops! An unexpected error occurred while trying to fetch statistics! :/",
"timetable-location-signal": "signal:",
"timetable-location-route": "route:"
}, },
"scenery": { "scenery": {
"users": "PLAYERS ONLINE", "users": "PLAYERS ONLINE",
@@ -328,6 +350,7 @@
"history-btn": "View the dispatcher history", "history-btn": "View the dispatcher history",
"info-btn": "Return to the scenery view", "info-btn": "Return to the scenery view",
"authors-title": "Scenery author | Scenery authors", "authors-title": "Scenery author | Scenery authors",
"abbrev": "Station symbol:",
"lines-title": "Real lines", "lines-title": "Real lines",
"project-title": "Project name", "project-title": "Project name",
"one-way-routes": "One way routes", "one-way-routes": "One way routes",
@@ -368,7 +391,19 @@
"end": "Timetable terminates here", "end": "Timetable terminates here",
"terminated": "Timetable terminated", "terminated": "Timetable terminated",
"begins": "BEGINS HERE", "begins": "BEGINS HERE",
"terminates": "TERMINATES\nHERE" "terminates": "TERMINATES\nHERE",
"from": "FROM",
"to": "TO",
"desc-arriving": "The train is not here yet. It's going to come from: {prevStationName} (szlak {prevDepartureLine})",
"desc-online": "The train is at the station. It's going to leave to: {nextStationName} (szlak {nextArrivalLine})",
"desc-stopped": "The train is at the station and is stopped. It's going to leave towards: {nextStationName} (szlak {nextArrivalLine})",
"desc-next-arrival": "Leaves towards: {nextStationName} (szlak {nextArrivalLine})",
"desc-departed": "The train is at the station and it's been departed. Leaves towards: {nextStationName} (szlak {nextArrivalLine})",
"desc-departed-away": "The train has been departed to: {nextStationName} (szlak {nextArrivalLine})",
"desc-end": "The train terminates here",
"desc-terminated": "The train has been terminated"
}, },
"history": { "history": {
"title": "TIMETABLE JOURNAL", "title": "TIMETABLE JOURNAL",
+52 -20
View File
@@ -1,7 +1,9 @@
{ {
"general": { "general": {
"and": " oraz ", "and": " oraz ",
"refresh": "ODŚWIEŻ" "refresh": "ODŚWIEŻ",
"TWR": "Towar niebezpieczny wysokiego ryzyka",
"SKR": "Przekroczona skrajnia"
}, },
"app": { "app": {
"sceneries": "SCENERIE", "sceneries": "SCENERIE",
@@ -15,7 +17,9 @@
"migration-confirm": "Przyjąłem!", "migration-confirm": "Przyjąłem!",
"offline": "Aplikacja w trybie offline!" "offline": "Aplikacja w trybie offline!"
}, },
"footer": {
"discord": "Serwer Discord Stacjownika"
},
"update": { "update": {
"title": "Nowa wersja Stacjownika jest dostępna!", "title": "Nowa wersja Stacjownika jest dostępna!",
"paragraph1": "Miłego korzystania z aplikacji i niech S2 będzie z wami!", "paragraph1": "Miłego korzystania z aplikacji i niech S2 będzie z wami!",
@@ -23,7 +27,6 @@
"confirm-button": "ZAKTUALIZUJ", "confirm-button": "ZAKTUALIZUJ",
"later-button": "PÓŹNIEJ" "later-button": "PÓŹNIEJ"
}, },
"data-status": { "data-status": {
"S1-offline": "<b>Sygnał S1</b> <br> Aplikacja działa w trybie offline!", "S1-offline": "<b>Sygnał S1</b> <br> Aplikacja działa w trybie offline!",
"S1a-connection": "<b>Sygnał S1a</b> <br> Błąd podczas próby połączenia się z API Stacjownika!", "S1a-connection": "<b>Sygnał S1a</b> <br> Błąd podczas próby połączenia się z API Stacjownika!",
@@ -96,11 +99,13 @@
"search-dispatcher": "Nick dyżurnego", "search-dispatcher": "Nick dyżurnego",
"search-station": "Nazwa scenerii", "search-station": "Nazwa scenerii",
"search-author": "Nick autora rozkładu jazdy", "search-author": "Nick autora rozkładu jazdy",
"search-timetables-date": "Data rozkładu jazdy (czas polski)", "search-issuedFrom": "Sceneria początkowa",
"search-dispatchers-date": "Data służby (czas polski)", "search-timetables-date": "Data rozkładu jazdy (UTC+2 / CEST)",
"search-dispatchers-date": "Data służby (UTC+2 / CEST)",
"search-date": "Data (UTC+2 / CEST)",
"sort-distance": "kilometraż", "sort-routeDistance": "kilometraż",
"sort-total-stops": "stacje", "sort-allStopsCount": "stacje",
"sort-beginDate": "data", "sort-beginDate": "data",
"sort-timetableId": "ID rozkładu", "sort-timetableId": "ID rozkładu",
"sort-timestampFrom": "data", "sort-timestampFrom": "data",
@@ -119,6 +124,7 @@
"filter-noComments": "BEZ UWAG", "filter-noComments": "BEZ UWAG",
"filter-twr": "WYS. RYZYKA", "filter-twr": "WYS. RYZYKA",
"filter-skr": "SKRAJNIA", "filter-skr": "SKRAJNIA",
"filter-twr-skr": "WSZYSTKIE",
"filter-common": "ZWYKŁE", "filter-common": "ZWYKŁE",
"filter-passenger": "PASAŻERSKIE", "filter-passenger": "PASAŻERSKIE",
"filter-freight": "TOWAROWE", "filter-freight": "TOWAROWE",
@@ -129,6 +135,9 @@
"filter-reset": "ZRESETUJ FILTRY", "filter-reset": "ZRESETUJ FILTRY",
"filter-clear": "WYŁĄCZ FILTRY", "filter-clear": "WYŁĄCZ FILTRY",
"filter-section-timetable-status": "STATUS ROZKŁADU JAZDY",
"filter-section-twrskr": "UWAGI",
"filter-all": "WSZYSTKIE", "filter-all": "WSZYSTKIE",
"filter-abandoned": "PORZUCONE", "filter-abandoned": "PORZUCONE",
"filter-fulfilled": "WYPEŁNIONE", "filter-fulfilled": "WYPEŁNIONE",
@@ -136,7 +145,7 @@
}, },
"filters": { "filters": {
"desc": " &bull; Kliknięcie: zaznaczenie / odznaczenie filtru <br /> &bull; Podwójne kliknięcie: odznaczenie reszty filtrów z <b class='text--primary'>grupy</b> <br /> &bull; <span style='color: coral'>RESET</span>: zresetowanie filtrów z <b class='text--primary'>grupy</b>", "desc": " &bull; Kliknięcie: zaznaczenie / odznaczenie filtru <br /> &bull; Podwójne kliknięcie: odznaczenie reszty filtrów z <b class='text--primary'>grupy</b> <br /> &bull; <span style='color: coral'>RESET</span>: zresetowanie filtrów z <b class='text--primary'>grupy</b>",
"sections": { "sections": {
"reality": "FIKCYJNOŚĆ SCENERII", "reality": "FIKCYJNOŚĆ SCENERII",
"package-access": "DOSTĘPNOŚĆ W PACZCE", "package-access": "DOSTĘPNOŚĆ W PACZCE",
@@ -207,15 +216,18 @@
}, },
"sceneries": { "sceneries": {
"station": "Stacja", "station": "Stacja",
"abbr": "Skrót\nposterunku",
"min-lvl": "Min. poziom\ndyżurnego", "min-lvl": "Min. poziom\ndyżurnego",
"status": "Status", "status": "Status",
"dispatcher": "Dyżurny", "dispatcher": "Dyżurny",
"dispatcher-lvl": "Poziom\ndyżurnego", "dispatcher-lvl": "Poziom\ndyżurnego",
"routes": "Szlaki\n2tor / 1tor", "routes": "Szlaki\n2tor / 1tor",
"general": "Informacje\nogólne", "general": "Informacje\nogólne",
"users": "Maszyniści online", "user": "Maszyniści online",
"spawns": "Otwarte spawny", "spawn": "Otwarte spawny",
"timetables": "Aktywne rozkłady jazdy", "timetableAll": "Aktywne rozkłady jazdy",
"timetableConfirmed": "Zatwierdzone rozkłady jazdy",
"timetableUnconfirmed": "Niezatwierdzone rozkłady jazdy",
"no-stations": "Brak stacji do wyświetlenia!", "no-stations": "Brak stacji do wyświetlenia!",
"scenery-search": "Wyszukaj scenerię..." "scenery-search": "Wyszukaj scenerię..."
}, },
@@ -267,6 +279,7 @@
"title": "HISTORIA DYŻURÓW", "title": "HISTORIA DYŻURÓW",
"loading": "Ładowanie historii dyżurów...", "loading": "Ładowanie historii dyżurów...",
"no-history": "Brak historii dyżurów dla tej scenerii!", "no-history": "Brak historii dyżurów dla tej scenerii!",
"data-refreshed-at": "Dane odświeżone o",
"section-timetables": "ROZKŁADY JAZDY", "section-timetables": "ROZKŁADY JAZDY",
"section-dispatchers": "DYŻURNI", "section-dispatchers": "DYŻURNI",
@@ -276,8 +289,9 @@
"online-since": "ONLINE OD", "online-since": "ONLINE OD",
"duty-lasted": "Dyżur trwał", "duty-lasted": "Dyżur trwał",
"minutes": "{minutes} min.", "hours": "{value} godz.",
"hours": "{hours} godz. {minutes} min.", "minutes": "{value} min.",
"seconds": "{value} sek.",
"route-length": "Kilometraż:", "route-length": "Kilometraż:",
"station-count": "Stacje:", "station-count": "Stacje:",
@@ -290,7 +304,7 @@
"stock-info": "DODATKOWE INFORMACJE", "stock-info": "DODATKOWE INFORMACJE",
"stock-length": "Długość", "stock-length": "Długość",
"stock-mass": "Masa", "stock-mass": "Masa",
"stock-max-speed": "Maks. zarejestrowana prędkość", "stock-max-speed": "Prędkość maks.",
"load-data": "Pobierz dalszą historię...", "load-data": "Pobierz dalszą historię...",
@@ -305,10 +319,12 @@
"stats-distance": "DYSTANS", "stats-distance": "DYSTANS",
"stats-stations": "STACJE", "stats-stations": "STACJE",
"timetable-stats-total": "Dyżurni stworzyli dziś {count} o łącznym dystansie {distance}", "timetable-stats-total": "Stworzone rozkłady jazdy: {count} (łączny dystans: {distance})",
"timetable-stats-longest": "Najdłuższym rozkładem jazdy jest dzisiaj #{id} stworzony przez dyżurnego {author} dla maszynisty {driver} - {distance}", "timetable-stats-longest": "Najdłuższy rozkład jazdy: #{id} (stworzony przez dyżurnego {author} dla maszynisty {driver} o dystansie {distance})",
"timetable-stats-most-active": "Dzisiejszym najaktywniejszym dyżurnym jest {dispatcher}, który stworzył {count}", "timetable-stats-most-active-dr": "Najaktywniejszy dyżurny: {dispatcher} (stworzył {count})",
"timetable-stats-most-active-many": "Dzisiejszymi najaktywniejszymi dyżurnymi są {dispatchers}, którzy stworzyli po {count}", "timetable-stats-most-active-dr-many": "Najaktywniejsi dyżurni: {dispatchers} (stworzyli po {count})",
"timetable-stats-most-active-driver": "Najaktywniejszy maszynista: {driver} (łączny przejechany dystans: {distance})",
"timetable-stats-longest-duties": "Najdłuższa służba: {dispatcher} na scenerii {station} (czas trwania: {duration})",
"timetable-count": "rozkład jazdy | rozkładów jazdy", "timetable-count": "rozkład jazdy | rozkładów jazdy",
@@ -319,7 +335,10 @@
"driver-stats-info": "Wpisz nazwę użytkownika w filtrach [F], aby zobaczyć jego statystyki maszynisty!", "driver-stats-info": "Wpisz nazwę użytkownika w filtrach [F], aby zobaczyć jego statystyki maszynisty!",
"stats-loading": "Pobieranie statystyk...", "stats-loading": "Pobieranie statystyk...",
"stats-error": "Ups! Wystąpił błąd podczas próby pobrania statystyk! :/" "stats-error": "Ups! Wystąpił błąd podczas próby pobrania statystyk! :/",
"timetable-location-signal": "semafor:",
"timetable-location-route": "szlak:"
}, },
"scenery": { "scenery": {
"users": "GRACZE ONLINE", "users": "GRACZE ONLINE",
@@ -334,6 +353,7 @@
"history-btn": "Przejdź do widoku historii dyżurnych ruchu", "history-btn": "Przejdź do widoku historii dyżurnych ruchu",
"info-btn": "Wróć do widoku scenerii", "info-btn": "Wróć do widoku scenerii",
"authors-title": "Autor scenerii | Autorzy scenerii", "authors-title": "Autor scenerii | Autorzy scenerii",
"abbrev": "Skrót posterunku:",
"lines-title": "Rzeczywiste linie", "lines-title": "Rzeczywiste linie",
"project-title": "Projekt", "project-title": "Projekt",
"one-way-routes": "Szlaki jednotorowe", "one-way-routes": "Szlaki jednotorowe",
@@ -374,7 +394,19 @@
"end": "Koniec rozkładu jazdy", "end": "Koniec rozkładu jazdy",
"terminated": "Rozkład jazdy zakończony", "terminated": "Rozkład jazdy zakończony",
"begins": "ROZPOCZYNA\nBIEG", "begins": "ROZPOCZYNA\nBIEG",
"terminates": "KOŃCZY BIEG" "terminates": "KOŃCZY BIEG",
"from": "Z",
"to": "DO",
"desc-arriving": "Pociągu nie ma jeszcze na tej scenerii. Przyjedzie z: {prevStationName} (szlak {prevDepartureLine})",
"desc-online": "Pociąg jest na tej scenerii. Odjedzie do: {nextStationName} (szlak {nextArrivalLine})",
"desc-stopped": "Pociąg jest na tej scenerii i odbywa postój. Odjedzie do: {nextStationName} (szlak {nextArrivalLine})",
"desc-next-arrival": "Odjeżdża do: {nextStationName} (szlak {nextArrivalLine})",
"desc-departed": "Pociąg jest na tej scenerii i został odprawiony. Odjeżdża do: {nextStationName} (szlak {nextArrivalLine})",
"desc-departed-away": "Pociąg został odprawiony i odjechał do: {nextStationName} (szlak {nextArrivalLine})",
"desc-end": "Pociąg kończy bieg",
"desc-terminated": "Pociąg skończył bieg"
}, },
"history": { "history": {
"title": "DZIENNIK ROZKŁADÓW JAZDY" "title": "DZIENNIK ROZKŁADÓW JAZDY"
-10
View File
@@ -7,7 +7,6 @@ import plLang from './locales/pl.json';
import { createI18n } from 'vue-i18n'; import { createI18n } from 'vue-i18n';
import { createPinia } from 'pinia'; import { createPinia } from 'pinia';
import { registerSW } from 'virtual:pwa-register';
const i18n = createI18n({ const i18n = createI18n({
locale: 'pl', locale: 'pl',
@@ -21,15 +20,6 @@ const i18n = createI18n({
enableLegacy: false, enableLegacy: false,
}); });
registerSW({
onRegistered(r) {
r &&
setInterval(() => {
r.update();
}, 60 * 60 * 1000);
},
});
const clickOutsideDirective: Directive = { const clickOutsideDirective: Directive = {
mounted(el, binding) { mounted(el, binding) {
el.clickOutsideEvent = (event: Event) => { el.clickOutsideEvent = (event: Event) => {
+70 -50
View File
@@ -1,50 +1,70 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
methods: { methods: {
localeDate(dateString: string, locale: string) { localeDate(dateString: string, locale: string) {
return new Date(dateString).toLocaleDateString(locale == 'pl' ? 'pl-PL' : 'en-GB', { return new Date(dateString).toLocaleDateString(locale == 'pl' ? 'pl-PL' : 'en-GB', {
weekday: 'long', weekday: 'long',
day: 'numeric', day: 'numeric',
month: '2-digit', month: '2-digit',
year: 'numeric', year: 'numeric',
hour: '2-digit', hour: '2-digit',
minute: '2-digit', minute: '2-digit',
}); });
}, },
localeDay(dateString: string, locale: string) { localeDay(dateString: string, locale: string) {
return new Date(dateString).toLocaleDateString(locale == 'pl' ? 'pl-PL' : 'en-GB', { return new Date(dateString).toLocaleDateString(locale == 'pl' ? 'pl-PL' : 'en-GB', {
day: 'numeric', day: 'numeric',
month: '2-digit', month: '2-digit',
year: 'numeric', year: 'numeric',
}); });
}, },
localeTime(dateString: string, locale: string) { localeTime(dateString: string, locale: string) {
return new Date(dateString).toLocaleTimeString(locale == 'pl' ? 'pl-PL' : 'en-GB', { return new Date(dateString).toLocaleTimeString(locale == 'pl' ? 'pl-PL' : 'en-GB', {
hour: '2-digit', hour: '2-digit',
minute: '2-digit', minute: '2-digit',
}); });
}, },
timestampToString(timestamp: number | null) { stringToDate(dateString?: string) {
return timestamp return dateString ? new Date(dateString) : null;
? new Date(timestamp).toLocaleTimeString('pl-PL', { },
hour: '2-digit',
minute: '2-digit', parseDateToTimeString(date: Date | null) {
}) return (
: ''; date?.toLocaleTimeString('pl-PL', {
}, hour: '2-digit',
minute: '2-digit',
calculateDuration(timestampMs: number) { }) || ''
const minsTotal = Math.round(timestampMs / 60000); );
const hoursTotal = Math.floor(minsTotal / 60); },
const minsInHour = minsTotal % 60;
timestampToString(timestamp: number | null) {
return minsTotal > 60 return timestamp
? this.$t('journal.hours', { hours: hoursTotal, minutes: minsInHour }) ? new Date(timestamp).toLocaleTimeString('pl-PL', {
: this.$t('journal.minutes', { minutes: minsTotal }); hour: '2-digit',
}, minute: '2-digit',
}, })
}); : '';
},
calculateDuration(timestampMs: number, showSeconds = false) {
const secondsTotal = Math.floor(timestampMs / 1000);
const minsTotal = Math.round(timestampMs / 60000);
const hoursTotal = Math.floor(minsTotal / 60);
const minsInHour = minsTotal % 60;
return minsTotal >= 60
? `${this.$t('journal.hours', { value: hoursTotal }, hoursTotal)} ${this.$t(
'journal.minutes',
{ value: minsInHour },
minsInHour
)}`
: showSeconds && secondsTotal <= 60
? this.$t('journal.seconds', { value: secondsTotal }, secondsTotal)
: this.$t('journal.minutes', { value: minsTotal }, minsTotal);
},
},
});
+24
View File
@@ -0,0 +1,24 @@
import { defineComponent } from 'vue';
export default defineComponent({
data: () => ({
observer: null as IntersectionObserver | null,
observerTarget: null as Element | null,
}),
methods: {
mountObserver(actionFunction: () => void, target: Element) {
this.observer = new IntersectionObserver((entries) => {
if (entries[0].intersectionRatio > 0) actionFunction();
});
this.observer.observe(target);
},
unmountObserver() {
if (!this.observerTarget) return;
this.observer?.unobserve(this.observerTarget);
},
},
});
@@ -1,49 +1,49 @@
import Filter from "../../scripts/interfaces/Filter"; import Filter from "../../interfaces/Filter";
export const filterInitStates: Filter = { export const filterInitStates: Filter = {
default: false, default: false,
notDefault: false, notDefault: false,
real: false, real: false,
fictional: false, fictional: false,
SPK: false, SPK: false,
SCS: false, SCS: false,
SPE: false, SPE: false,
SUP: false, SUP: false,
noSUP: false, noSUP: false,
ręczne: false, ręczne: false,
'ręczne+SPK': false, 'ręczne+SPK': false,
'ręczne+SCS': false, 'ręczne+SCS': false,
mechaniczne: false, mechaniczne: false,
'mechaniczne+SPK': false, 'mechaniczne+SPK': false,
'mechaniczne+SCS': false, 'mechaniczne+SCS': false,
współczesna: false, współczesna: false,
kształtowa: false, kształtowa: false,
historyczna: false, historyczna: false,
mieszana: false, mieszana: false,
SBL: false, SBL: false,
PBL: false, PBL: false,
minLevel: 0, minLevel: 0,
maxLevel: 20, maxLevel: 20,
minOneWayCatenary: 0, minOneWayCatenary: 0,
minOneWay: 0, minOneWay: 0,
minTwoWayCatenary: 0, minTwoWayCatenary: 0,
minTwoWay: 0, minTwoWay: 0,
'include-selected': false, 'include-selected': false,
'no-1track': false, 'no-1track': false,
'no-2track': false, 'no-2track': false,
free: true, free: true,
occupied: false, occupied: false,
ending: false, ending: false,
nonPublic: false, nonPublic: false,
unavailable: true, unavailable: true,
abandoned: true, abandoned: true,
afkStatus: false, afkStatus: false,
endingStatus: false, endingStatus: false,
noSpaceStatus: false, noSpaceStatus: false,
unavailableStatus: false, unavailableStatus: false,
unsignedStatus: false, unsignedStatus: false,
authors: '', authors: '',
onlineFromHours: 0, onlineFromHours: 0,
}; };
+5
View File
@@ -0,0 +1,5 @@
export const headIds = ['station', 'min-lvl', 'status', 'dispatcher', 'dispatcher-lvl', 'routes', 'general'] as const;
export const headIconsIds = ['user', 'spawn', 'timetableAll', 'timetableUnconfirmed', 'timetableConfirmed'] as const;
export type HeadIdsTypes = typeof headIds[number] | typeof headIconsIds[number];
+14 -6
View File
@@ -1,6 +1,14 @@
export const enum JournalFilterType { export const enum JournalFilterType {
active = "active", ACTIVE = 'active',
fulfilled = "fulfilled", FULFILLED = 'fulfilled',
abandoned = "abandoned", ABANDONED = 'abandoned',
all = "all" ALL = 'all',
} TWR = 'twr',
SKR = 'skr',
TWR_SKR = 'twr-skr',
}
export enum JournalFilterSection {
TIMETABLE_STATUS = 'timetable-status',
TWRSKR = 'twrskr',
}
+34 -25
View File
@@ -1,32 +1,41 @@
import TrainStop from "./TrainStop"; import TrainStop from './TrainStop';
export default interface ScheduledTrain { export enum StopStatus {
trainId: string; 'arriving' = 'arriving',
trainNo: number; 'departed' = 'departed',
'departed-away' = 'departed-away',
driverName: string; 'online' = 'online',
driverId: number; 'stopped' = 'stopped',
currentStationName: string; 'terminated' = 'terminated',
currentStationHash: string; }
category: string;
stopInfo: TrainStop;
terminatesAt: string; export interface ScheduledTrain {
beginsAt: string; trainId: string;
trainNo: number;
prevStationName: string; driverName: string;
nextStationName: string; driverId: number;
currentStationName: string;
currentStationHash: string;
category: string;
stopInfo: TrainStop;
arrivingLine: string | null; terminatesAt: string;
departureLine: string | null; beginsAt: string;
prevDepartureLine: string | null; prevStationName: string;
nextArrivalLine: string | null; nextStationName: string;
signal: string; arrivingLine: string | null;
connectedTrack: string; departureLine: string | null;
stopLabel: string; prevDepartureLine: string | null;
stopStatus: string; nextArrivalLine: string | null;
stopStatusID: number;
} signal: string;
connectedTrack: string;
stopLabel: string;
stopStatus: StopStatus;
stopStatusID: number;
}
+6 -4
View File
@@ -1,5 +1,5 @@
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 {
@@ -8,9 +8,11 @@ export default interface Station {
generalInfo?: { generalInfo?: {
name: string; name: string;
url: string; url: string;
abbr: string;
reqLevel: number; reqLevel: number;
// supportersOnly: boolean; // supportersOnly: boolean;
lines: string; lines: string;
project: string; project: string;
@@ -39,7 +41,7 @@ export default interface Station {
maxUsers: number; maxUsers: number;
currentUsers: number; currentUsers: number;
spawns: { spawnName: string; spawnLength: number }[]; spawns: { spawnName: string; spawnLength: number; isElectrified: boolean }[];
dispatcherRate: number; dispatcherRate: number;
dispatcherName: string; dispatcherName: string;
dispatcherExp: number; dispatcherExp: number;
@@ -1,4 +1,4 @@
import { TrainFilterSection, TrainFilterType } from '../../scripts/enums/TrainFilterType'; import { TrainFilterSection, TrainFilterType } from '../../enums/TrainFilterType'
export interface TrainFilter { export interface TrainFilter {
id: TrainFilterType; id: TrainFilterType;
@@ -14,6 +14,17 @@ export interface ITimetablesDailyStats {
name: string; name: string;
count: number; count: number;
}[]; }[];
mostActiveDrivers: {
name: string;
distance: number;
}[];
longestDuties: {
name: string;
duration: number;
station: string;
}[];
} }
export interface ITimetablesDailyStatsResponse { export interface ITimetablesDailyStatsResponse {
@@ -26,5 +37,16 @@ export interface ITimetablesDailyStatsResponse {
name: string; name: string;
count: number; count: number;
}[]; }[];
mostActiveDrivers: {
name: string;
distance: number;
}[];
longestDuties: {
name: string;
duration: number;
station: string;
}[];
} }
@@ -16,6 +16,7 @@ export interface TimetableHistory {
twr: number; twr: number;
skr: number; skr: number;
sceneriesString: string; sceneriesString: string;
currentLocation: string[];
routeDistance: number; routeDistance: number;
currentDistance: number; currentDistance: number;
@@ -38,6 +39,8 @@ export interface TimetableHistory {
stopsString?: string; stopsString?: string;
stockString?: string; stockString?: string;
stockHistory: string[];
stockMass?: number; stockMass?: number;
stockLength?: number; stockLength?: number;
maxSpeed?: number; maxSpeed?: number;
@@ -45,10 +48,20 @@ export interface TimetableHistory {
hashesString?: string; hashesString?: string;
currentSceneryName?: string; currentSceneryName?: string;
currentSceneryHash?: string; currentSceneryHash?: string;
routeSceneries?: string;
checkpointArrivals?: string[];
checkpointDepartures?: string[];
checkpointArrivalsScheduled?: string[];
checkpointDeparturesScheduled?: string[];
checkpointStopTypes?: string[];
} }
export interface SceneryTimetableHistory { export interface SceneryTimetableHistory {
sceneryTimetables: TimetableHistory[]; timetables: TimetableHistory[];
totalCount: number; // totalCount: number;
sceneryName: string; // sceneryName: string;
} }
@@ -0,0 +1,23 @@
import { JournalTimetableSorter } from '../../types/JournalTimetablesTypes';
export interface TimetablesQueryParams {
driverName?: string;
trainNo?: string;
timetableId?: string;
authorName?: string;
timestampFrom?: number;
timestampTo?: number;
issuedFrom?: string;
countFrom?: number;
countLimit?: number;
fulfilled?: number;
terminated?: number;
twr?: number;
skr?: number;
sortBy?: JournalTimetableSorter['id'];
}
@@ -1,11 +1,11 @@
import { Socket } from 'socket.io-client'; import { Socket } from 'socket.io-client';
import { DataStatus } from '../scripts/enums/DataStatus'; import { DataStatus } from '../../enums/DataStatus';
import StationAPIData from '../scripts/interfaces/api/StationAPIData'; import StationAPIData from '../api/StationAPIData';
import { TrainAPIData } from '../scripts/interfaces/api/TrainAPIData'; import { TrainAPIData } from '../api/TrainAPIData';
import Station from '../scripts/interfaces/Station'; import Station from '../Station';
import Train from '../scripts/interfaces/Train'; import Train from '../Train';
import { DispatcherStatsAPIData } from '../scripts/interfaces/api/DispatcherStatsAPIData'; import { DispatcherStatsAPIData } from '../api/DispatcherStatsAPIData';
import { DriverStatsAPIData } from '../scripts/interfaces/api/DriverStatsAPIData'; import { DriverStatsAPIData } from '../api/DriverStatsAPIData';
export type Availability = 'default' | 'unavailable' | 'nonPublic' | 'abandoned' | 'nonDefault'; export type Availability = 'default' | 'unavailable' | 'nonPublic' | 'abandoned' | 'nonDefault';
@@ -34,7 +34,7 @@ export interface StoreState {
chosenModalTrainId?: string; chosenModalTrainId?: string;
currentStatsTab: 'daily' | 'driver'; currentStatsTab: 'daily' | 'driver' | null;
dataStatuses: { dataStatuses: {
connection: DataStatus; connection: DataStatus;
@@ -55,8 +55,19 @@ export interface APIData {
connectedSocketCount: number; connectedSocketCount: number;
} }
export interface StationRoutesInfo {
routeName: string;
isElectric: boolean;
isInternal: boolean;
isRouteSBL: boolean;
routeLength: number;
routeSpeed: number;
routeTracks: number;
}
export interface StationJSONData { export interface StationJSONData {
name: string; name: string;
abbr: string;
url: string; url: string;
lines: string; lines: string;
project: string; project: string;
@@ -69,7 +80,8 @@ export interface StationJSONData {
SUP: boolean; SUP: boolean;
routes: string; // routes: string;
routesInfo: StationRoutesInfo[];
checkpoints: string | null; checkpoints: string | null;
authors?: string; authors?: string;
+3 -3
View File
@@ -1,4 +1,4 @@
import { TrainFilter } from '../../types/Trains/TrainOptionsTypes'; import { TrainFilter } from '../interfaces/Trains/TrainFilter';
import { TrainFilterType } from '../enums/TrainFilterType'; import { TrainFilterType } from '../enums/TrainFilterType';
import Train from '../interfaces/Train'; import Train from '../interfaces/Train';
import TrainStop from '../interfaces/TrainStop'; import TrainStop from '../interfaces/TrainStop';
@@ -44,7 +44,7 @@ function filterTrainList(trainList: Train[], searchedTrain: string, searchedDriv
return !train.timetableData?.SKR; return !train.timetableData?.SKR;
case TrainFilterType.common: case TrainFilterType.common:
return train.timetableData?.SKR || train.timetableData?.TWR; return train.timetableData?.SKR || train.timetableData?.TWR;
case TrainFilterType.passenger: case TrainFilterType.passenger:
return !/^[AMRE]\D{2}$/.test(train.timetableData?.category || ''); return !/^[AMRE]\D{2}$/.test(train.timetableData?.category || '');
@@ -81,7 +81,7 @@ function sortTrainList(trainList: Train[], sorterActive: { id: string; dir: numb
if (a.mass > b.mass) return sorterActive.dir; if (a.mass > b.mass) return sorterActive.dir;
return -sorterActive.dir; return -sorterActive.dir;
case 'distance': case 'routeDistance':
if ((a.timetableData?.routeDistance || -1) > (b.timetableData?.routeDistance || -1)) return sorterActive.dir; if ((a.timetableData?.routeDistance || -1) > (b.timetableData?.routeDistance || -1)) return sorterActive.dir;
return -sorterActive.dir; return -sorterActive.dir;
@@ -0,0 +1,25 @@
import { JournalFilterType } from '../../scripts/enums/JournalFilterType';
export type JournalTimetableSearchKey =
| 'search-driver'
| 'search-train'
| 'search-date'
| 'search-dispatcher'
| 'search-issuedFrom';
export type JournalTimetableSorterKey = 'timetableId' | 'beginDate' | 'distance' | 'total-stops';
export type JournalTimetableSearchType = {
[key in JournalTimetableSearchKey]: string;
};
export interface JournalFilter {
id: JournalFilterType;
filterSection: string;
isActive: boolean;
}
export interface JournalTimetableSorter {
id: JournalTimetableSorterKey;
dir: 'asc' | 'desc';
}
+1 -1
View File
@@ -1,7 +1,7 @@
export const URLs = { export const URLs = {
stacjownikAPI: stacjownikAPI:
import.meta.env.VITE_APP_API_DEV == 1 && !import.meta.env.PROD import.meta.env.VITE_APP_API_DEV == 1 && !import.meta.env.PROD
? 'http://localhost:3000' ? 'http://localhost:3001'
: 'https://spythere.pl', : 'https://spythere.pl',
stacjownikAPIDev: 'localhost:3000', stacjownikAPIDev: 'localhost:3000',
}; };
@@ -1,141 +1,156 @@
import Filter from '../../scripts/interfaces/Filter'; import { HeadIdsTypes } from '../data/stationHeaderNames';
import Station from '../../scripts/interfaces/Station'; import Filter from '../interfaces/Filter';
import Station from '../interfaces/Station';
export const sortStations = (a: Station, b: Station, sorter: { index: number; dir: number }) => {
switch (sorter.index) { export const sortStations = (a: Station, b: Station, sorter: { headerName: HeadIdsTypes; dir: number }) => {
case 0: let diff = 0;
return sorter.dir == 1 ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name);
switch (sorter.headerName) {
case 1: case 'station':
if ((a.generalInfo?.reqLevel || 0) > (b.generalInfo?.reqLevel || 0)) return sorter.dir; return sorter.dir == 1 ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name);
if ((a.generalInfo?.reqLevel || 0) < (b.generalInfo?.reqLevel || 0)) return -sorter.dir;
break; case 'min-lvl':
diff = (a.generalInfo?.reqLevel || 0) - (b.generalInfo?.reqLevel || 0);
case 2: break;
if ((a.onlineInfo?.statusTimestamp || 0) > (b.onlineInfo?.statusTimestamp || 0)) return sorter.dir;
if ((a.onlineInfo?.statusTimestamp || 0) < (b.onlineInfo?.statusTimestamp || 0)) return -sorter.dir; case 'status':
break; diff = (a.onlineInfo?.statusTimestamp || 0) - (b.onlineInfo?.statusTimestamp || 0);
break;
case 3:
if ((a.onlineInfo?.dispatcherName.toLowerCase() || '') > (b.onlineInfo?.dispatcherName.toLowerCase() || '')) case 'dispatcher':
return sorter.dir; if ((a.onlineInfo?.dispatcherName.toLowerCase() || '') > (b.onlineInfo?.dispatcherName.toLowerCase() || ''))
if ((a.onlineInfo?.dispatcherName.toLowerCase() || '') < (b.onlineInfo?.dispatcherName.toLowerCase() || '')) return sorter.dir;
return -sorter.dir; if ((a.onlineInfo?.dispatcherName.toLowerCase() || '') < (b.onlineInfo?.dispatcherName.toLowerCase() || ''))
break; return -sorter.dir;
break;
case 4:
if ((a.onlineInfo?.dispatcherExp || 0) > (b.onlineInfo?.dispatcherExp || 0)) return sorter.dir; case 'dispatcher-lvl':
if ((a.onlineInfo?.dispatcherExp || 0) < (b.onlineInfo?.dispatcherExp || 0)) return -sorter.dir; diff = (a.onlineInfo?.dispatcherExp || 0) - (b.onlineInfo?.dispatcherExp || 0);
break; break;
case 7: case 'user':
if ((a.onlineInfo?.currentUsers || 0) > (b.onlineInfo?.currentUsers || 0)) return sorter.dir; diff = (b.onlineInfo ? b.onlineInfo.currentUsers : -1) - (a.onlineInfo ? a.onlineInfo.currentUsers : -1);
if ((a.onlineInfo?.currentUsers || 0) < (b.onlineInfo?.currentUsers || 0)) return -sorter.dir; break;
if ((a.onlineInfo?.maxUsers || 0) > (b.onlineInfo?.maxUsers || 0)) return sorter.dir; case 'spawn':
if ((a.onlineInfo?.maxUsers || 0) < (b.onlineInfo?.maxUsers || 0)) return -sorter.dir; diff = (a.onlineInfo ? a.onlineInfo.spawns.length : -1) - (b.onlineInfo ? b.onlineInfo.spawns.length : -1);
break; break;
case 8: case 'timetableConfirmed':
if ((a.onlineInfo?.spawns.length || 0) > (b.onlineInfo?.spawns.length || 0)) return sorter.dir; diff =
if ((a.onlineInfo?.spawns.length || 0) < (b.onlineInfo?.spawns.length || 0)) return -sorter.dir; (a.onlineInfo?.scheduledTrains
? a.onlineInfo.scheduledTrains.filter((train) => train.stopInfo.confirmed).length
break; : -1) -
(b.onlineInfo?.scheduledTrains
case 9: ? b.onlineInfo.scheduledTrains.filter((train) => train.stopInfo.confirmed).length
if ((a.onlineInfo?.scheduledTrains?.length || 0) > (b.onlineInfo?.scheduledTrains?.length || 0)) : -1);
return sorter.dir; break;
if ((a.onlineInfo?.scheduledTrains?.length || 0) < (b.onlineInfo?.scheduledTrains?.length || 0))
return -sorter.dir; case 'timetableUnconfirmed':
diff =
default: (a.onlineInfo?.scheduledTrains
break; ? a.onlineInfo.scheduledTrains.filter((train) => !train.stopInfo.confirmed).length
} : -1) -
(b.onlineInfo?.scheduledTrains
return a.name.localeCompare(b.name); ? b.onlineInfo.scheduledTrains.filter((train) => !train.stopInfo.confirmed).length
}; : -1);
break;
export const filterStations = (station: Station, filters: Filter) => {
if (!station.onlineInfo && filters['free']) return false; case 'timetableAll':
diff =
if (station.onlineInfo) { (a.onlineInfo?.scheduledTrains ? a.onlineInfo.scheduledTrains.length : -1) -
const { statusID, statusTimestamp } = station.onlineInfo; (b.onlineInfo?.scheduledTrains ? b.onlineInfo.scheduledTrains.length : -1);
break;
const isEnding = statusID == 'ending' && filters['endingStatus'];
const isNotSigned = (statusID == 'not-signed' || statusID == 'unavailable') && filters['unavailableStatus']; default:
const isAFK = statusID == 'brb' && filters['afkStatus']; break;
const isNoSpace = statusID == 'no-space' && filters['noSpaceStatus']; }
const isOccupied = station.onlineInfo && filters['occupied'];
if (diff != 0) return Math.sign(diff) * sorter.dir;
const isOnlineInBounds = return a.name.localeCompare(b.name);
(filters['onlineFromHours'] < 8 && };
statusTimestamp > 0 &&
statusTimestamp <= Date.now() + filters['onlineFromHours'] * 3600000) || export const filterStations = (station: Station, filters: Filter) => {
(filters['onlineFromHours'] > 0 && statusTimestamp <= 0) || if (!station.onlineInfo && filters['free']) return false;
(filters['onlineFromHours'] == 8 && statusID != 'no-limit');
if (station.onlineInfo) {
if (isEnding || isOnlineInBounds || isNotSigned || isAFK || isNoSpace || isOccupied) return false; const { statusID, statusTimestamp } = station.onlineInfo;
}
const isEnding = statusID == 'ending' && filters['endingStatus'];
if ((station.generalInfo?.availability == 'nonPublic' || !station.generalInfo) && filters['nonPublic']) return false; const isNotSigned = (statusID == 'not-signed' || statusID == 'unavailable') && filters['unavailableStatus'];
const isAFK = statusID == 'brb' && filters['afkStatus'];
if (station.generalInfo) { const isNoSpace = statusID == 'no-space' && filters['noSpaceStatus'];
const { routes, availability, controlType, lines, reqLevel, signalType, SUP, authors } = station.generalInfo; const isOccupied = station.onlineInfo && filters['occupied'];
if (availability == 'unavailable' && filters['unavailable'] && !station.onlineInfo) return false; const isOnlineInBounds =
if (availability == 'abandoned' && filters['abandoned'] && !station.onlineInfo) return false; (filters['onlineFromHours'] < 8 &&
if (availability == 'default' && filters['default']) return false; statusTimestamp > 0 &&
statusTimestamp <= Date.now() + filters['onlineFromHours'] * 3600000) ||
if ( (filters['onlineFromHours'] > 0 && statusTimestamp <= 0) ||
availability != 'default' && (filters['onlineFromHours'] == 8 && statusID != 'no-limit');
filters['notDefault'] &&
!(availability == 'abandoned' || availability == 'unavailable') if (isEnding || isOnlineInBounds || isNotSigned || isAFK || isNoSpace || isOccupied) return false;
) }
return false;
if ((station.generalInfo?.availability == 'nonPublic' || !station.generalInfo) && filters['nonPublic']) return false;
if (filters['real'] && lines) return false;
if (filters['fictional'] && !lines) return false; if (station.generalInfo) {
const { routes, availability, controlType, lines, reqLevel, signalType, SUP, authors } = station.generalInfo;
const otherAvailability =
availability == 'nonPublic' || availability == 'unavailable' || availability == 'abandoned'; if (availability == 'unavailable' && filters['unavailable'] && !station.onlineInfo) return false;
if (availability == 'abandoned' && filters['abandoned'] && !station.onlineInfo) return false;
if (reqLevel + (otherAvailability ? 1 : 0) < filters['minLevel']) return false; if (availability == 'default' && filters['default']) return false;
if (reqLevel + (otherAvailability ? 1 : 0) > filters['maxLevel']) return false; if (
availability != 'default' &&
if ( filters['notDefault'] &&
filters['no-1track'] && !(availability == 'abandoned' || availability == 'unavailable')
(routes.oneWayCatenaryRouteNames.length != 0 || routes.oneWayNoCatenaryRouteNames.length != 0) )
) return false;
return false;
if (filters['real'] && lines) return false;
if ( if (filters['fictional'] && !lines) return false;
filters['no-2track'] &&
(routes.twoWayCatenaryRouteNames.length != 0 || routes.twoWayNoCatenaryRouteNames.length != 0) const otherAvailability =
) availability == 'nonPublic' || availability == 'unavailable' || availability == 'abandoned';
return false;
if (reqLevel + (otherAvailability ? 1 : 0) < filters['minLevel']) return false;
if (routes.oneWayCatenaryRouteNames.length < filters['minOneWayCatenary']) return false;
if (routes.oneWayNoCatenaryRouteNames.length < filters['minOneWay']) return false; if (reqLevel + (otherAvailability ? 1 : 0) > filters['maxLevel']) return false;
if (routes.twoWayCatenaryRouteNames.length < filters['minTwoWayCatenary']) return false; if (
if (routes.twoWayNoCatenaryRouteNames.length < filters['minTwoWay']) return false; filters['no-1track'] &&
(routes.oneWayCatenaryRouteNames.length != 0 || routes.oneWayNoCatenaryRouteNames.length != 0)
if (filters[controlType]) return false; )
if (filters[signalType]) return false; return false;
if (filters['SUP'] && SUP) return false; if (
if (filters['noSUP'] && !SUP) return false; filters['no-2track'] &&
(routes.twoWayCatenaryRouteNames.length != 0 || routes.twoWayNoCatenaryRouteNames.length != 0)
if (filters['SBL'] && routes.sblRouteNames.length > 0) return false; )
if (filters['PBL'] && routes.sblRouteNames.length == 0) return false; return false;
if ( if (routes.oneWayCatenaryRouteNames.length < filters['minOneWayCatenary']) return false;
filters['authors'].length > 3 && if (routes.oneWayNoCatenaryRouteNames.length < filters['minOneWay']) return false;
!authors?.map((a) => a.toLocaleLowerCase()).includes(filters['authors'].toLocaleLowerCase())
) if (routes.twoWayCatenaryRouteNames.length < filters['minTwoWayCatenary']) return false;
return false; if (routes.twoWayNoCatenaryRouteNames.length < filters['minTwoWay']) return false;
}
if (filters[controlType]) return false;
return true; if (filters[signalType]) return false;
};
if (filters['SUP'] && SUP) return false;
if (filters['noSUP'] && !SUP) return false;
if (filters['SBL'] && routes.sblRouteNames.length > 0) return false;
if (filters['PBL'] && routes.sblRouteNames.length == 0) return false;
if (
filters['authors'].length > 3 &&
!authors?.map((a) => a.toLocaleLowerCase()).includes(filters['authors'].toLocaleLowerCase())
)
return false;
}
return true;
};
+12 -12
View File
@@ -1,4 +1,4 @@
import ScheduledTrain from '../interfaces/ScheduledTrain'; import { ScheduledTrain, StopStatus } from '../interfaces/ScheduledTrain';
import Train from '../interfaces/Train'; import Train from '../interfaces/Train';
import TrainStop from '../interfaces/TrainStop'; import TrainStop from '../interfaces/TrainStop';
@@ -66,40 +66,41 @@ export const parseSpawns = (spawnString: string) => {
const spawnArray = spawn.split(','); const spawnArray = spawn.split(',');
const spawnName = spawnArray[6] ? spawnArray[6] : spawnArray[0]; const spawnName = spawnArray[6] ? spawnArray[6] : spawnArray[0];
const spawnLength = parseInt(spawnArray[2]); const spawnLength = parseInt(spawnArray[2]);
const isElectrified = spawnArray[3] == 'True';
return { spawnName, spawnLength }; return { spawnName, spawnLength, isElectrified };
}); });
}; };
export const getTimestamp = (date: string | null): number => (date ? new Date(date).getTime() : 0); export const getTimestamp = (date: string | null): number => (date ? new Date(date).getTime() : 0);
export const getTrainStopStatus = (stopInfo: TrainStop, currentStationName: string, stationName: string) => { export const getTrainStopStatus = (stopInfo: TrainStop, currentStationName: string, stationName: string) => {
let stopStatus = '', let stopStatus = StopStatus['arriving'],
stopLabel = '', stopLabel = '',
stopStatusID = -1; stopStatusID = -1;
if (stopInfo.terminatesHere && stopInfo.confirmed) { if (stopInfo.terminatesHere && stopInfo.confirmed) {
stopStatus = 'terminated'; stopStatus = StopStatus['terminated'];
stopLabel = 'Skończył bieg'; stopLabel = 'Skończył bieg';
stopStatusID = 5; stopStatusID = 5;
} else if (!stopInfo.terminatesHere && stopInfo.confirmed && currentStationName == stationName) { } else if (!stopInfo.terminatesHere && stopInfo.confirmed && currentStationName == stationName) {
stopStatus = 'departed'; stopStatus = StopStatus['departed'];
stopLabel = 'Odprawiony'; stopLabel = 'Odprawiony';
stopStatusID = 2; stopStatusID = 2;
} else if (!stopInfo.terminatesHere && stopInfo.confirmed && currentStationName != stationName) { } else if (!stopInfo.terminatesHere && stopInfo.confirmed && currentStationName != stationName) {
stopStatus = 'departed-away'; stopStatus = StopStatus['departed-away'];
stopLabel = 'Odjechał'; stopLabel = 'Odjechał';
stopStatusID = 4; stopStatusID = 4;
} else if (currentStationName == stationName && !stopInfo.stopped) { } else if (currentStationName == stationName && !stopInfo.stopped) {
stopStatus = 'online'; stopStatus = StopStatus['online'];
stopLabel = 'Na stacji'; stopLabel = 'Na stacji';
stopStatusID = 0; stopStatusID = 0;
} else if (currentStationName == stationName && stopInfo.stopped) { } else if (currentStationName == stationName && stopInfo.stopped) {
stopStatus = 'stopped'; stopStatus = StopStatus['stopped'];
stopLabel = 'Postój'; stopLabel = 'Postój';
stopStatusID = 1; stopStatusID = 1;
} else if (currentStationName != stationName) { } else if (currentStationName != stationName) {
stopStatus = 'arriving'; stopStatus = StopStatus['arriving'];
stopLabel = 'W drodze'; stopLabel = 'W drodze';
stopStatusID = 3; stopStatusID = 3;
} }
@@ -122,7 +123,7 @@ export function getScheduledTrain(train: Train, trainStopIndex: number, stationN
for (let i = trainStopIndex - 1; i >= 0; i--) { for (let i = trainStopIndex - 1; i >= 0; i--) {
if (/strong|podg/g.test(followingStops[i].stopName)) { if (/strong|podg/g.test(followingStops[i].stopName)) {
prevStationName = followingStops[i].stopNameRAW.replace(/,.*/g,""); prevStationName = followingStops[i].stopNameRAW.replace(/,.*/g, '');
break; break;
} }
@@ -130,7 +131,7 @@ export function getScheduledTrain(train: Train, trainStopIndex: number, stationN
for (let i = trainStopIndex + 1; i < followingStops.length; i++) { for (let i = trainStopIndex + 1; i < followingStops.length; i++) {
if (/strong|podg/g.test(followingStops[i].stopName)) { if (/strong|podg/g.test(followingStops[i].stopName)) {
nextStationName = followingStops[i].stopNameRAW.replace(/,.*/g,""); nextStationName = followingStops[i].stopNameRAW.replace(/,.*/g, '');
break; break;
} }
@@ -172,7 +173,6 @@ export function getScheduledTrain(train: Train, trainStopIndex: number, stationN
signal: train.signal, signal: train.signal,
connectedTrack: train.connectedTrack, connectedTrack: train.connectedTrack,
driverName: train.driverName, driverName: train.driverName,
driverId: train.driverId, driverId: train.driverId,
currentStationName: train.currentStationName, currentStationName: train.currentStationName,
+9
View File
@@ -0,0 +1,9 @@
import { defineStore } from 'pinia';
export const useJournalFiltersStore = defineStore('journalFiltersStore', {
state: () => ({
timetableFilters: {
},
}),
});
+99 -101
View File
@@ -1,101 +1,99 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import inputData from '../data/options.json'; import inputData from '../data/options.json';
import Filter from '../scripts/interfaces/Filter'; import Station from '../scripts/interfaces/Station';
import Station from '../scripts/interfaces/Station'; import StorageManager from '../scripts/managers/storageManager';
import StorageManager from '../scripts/managers/storageManager'; import { useStore } from './store';
import { useStore } from './store'; import { filterInitStates } from '../scripts/constants/stores/initFilterStates';
import { filterInitStates } from './constants/initFilterStates'; import { filterStations, sortStations } from '../scripts/utils/filterUtils';
import { filterStations, sortStations } from './utils/filterUtils'; import { HeadIdsTypes } from '../scripts/data/stationHeaderNames';
export const useStationFiltersStore = defineStore('stationFiltersStore', { export const useStationFiltersStore = defineStore('stationFiltersStore', {
state() { state() {
return { return {
inputs: inputData, inputs: inputData,
filters: { ...filterInitStates }, filters: { ...filterInitStates },
sorterActive: { index: 0, dir: 1 }, sorterActive: { headerName: 'station' as HeadIdsTypes, dir: 1 },
store: useStore(), store: useStore(),
lastClickedFilterId: '', lastClickedFilterId: '',
}; };
}, },
getters: { getters: {
areFiltersAtDefault(state) { areFiltersAtDefault(state) {
return Object.keys(state.filters).every((f) => state.filters[f] === filterInitStates[f]); return Object.keys(state.filters).every((f) => state.filters[f] === filterInitStates[f]);
}, },
}, },
actions: { actions: {
getFilteredStationList(stationList: Station[], region: string): Station[] { getFilteredStationList(stationList: Station[], region: string): Station[] {
return stationList return stationList
.map((station) => { .map((station) => {
if (station.onlineInfo && station.onlineInfo.region != region) { if (station.onlineInfo && station.onlineInfo.region != region) {
delete station.onlineInfo; delete station.onlineInfo;
} }
return station; return station;
}) })
.filter((station) => filterStations(station, this.filters)) .filter((station) => filterStations(station, this.filters))
.sort((a, b) => sortStations(a, b, this.sorterActive)); .sort((a, b) => sortStations(a, b, this.sorterActive));
}, },
setupFilters() { setupFilters() {
if (!StorageManager.isRegistered('options_saved')) return; if (!StorageManager.isRegistered('options_saved')) return;
this.inputs.options.forEach((option) => { this.inputs.options.forEach((option) => {
if (!StorageManager.isRegistered(option.name)) return; if (!StorageManager.isRegistered(option.name)) return;
const savedValue = StorageManager.getBooleanValue(option.name); const savedValue = StorageManager.getBooleanValue(option.name);
this.filters[option.name] = savedValue; this.filters[option.name] = savedValue;
option.value = !savedValue; option.value = !savedValue;
}); });
this.inputs.sliders.forEach((slider) => { this.inputs.sliders.forEach((slider) => {
if (!StorageManager.isRegistered(slider.name)) return; if (!StorageManager.isRegistered(slider.name)) return;
const savedValue = StorageManager.getNumericValue(slider.name); const savedValue = StorageManager.getNumericValue(slider.name);
this.filters[slider.name] = savedValue; this.filters[slider.name] = savedValue;
slider.value = savedValue; slider.value = savedValue;
}); });
}, },
changeFilterValue(filter: { name: string; value: any }) { changeFilterValue(filter: { name: string; value: any }) {
this.filters[filter.name] = filter.value; this.filters[filter.name] = filter.value;
if (StorageManager.isRegistered('options_saved')) StorageManager.setValue(filter.name, filter.value); if (StorageManager.isRegistered('options_saved')) StorageManager.setValue(filter.name, filter.value);
}, },
resetFilters() { resetFilters() {
this.filters = { ...filterInitStates }; this.filters = { ...filterInitStates };
this.inputs.options.forEach((option) => { this.inputs.options.forEach((option) => {
option.value = option.defaultValue; option.value = option.defaultValue;
StorageManager.setBooleanValue(option.name, !option.defaultValue); StorageManager.setBooleanValue(option.name, !option.defaultValue);
}); });
this.inputs.sliders.forEach((slider) => { this.inputs.sliders.forEach((slider) => {
slider.value = slider.defaultValue; slider.value = slider.defaultValue;
StorageManager.setNumericValue(slider.name, slider.defaultValue); StorageManager.setNumericValue(slider.name, slider.defaultValue);
}); });
}, },
resetSectionOptions(section: string) { resetSectionOptions(section: string) {
this.inputs.options.forEach((option) => { this.inputs.options.forEach((option) => {
if (option.section != section) return; if (option.section != section) return;
option.value = option.defaultValue; option.value = option.defaultValue;
this.filters[option.id] = !option.defaultValue; this.filters[option.id] = !option.defaultValue;
StorageManager.setBooleanValue(option.name, !option.defaultValue); StorageManager.setBooleanValue(option.name, !option.defaultValue);
}); });
}, },
changeSorter(index: number) { changeSorter(headerName: HeadIdsTypes) {
if (index > 4 && index < 7) return; if (headerName == this.sorterActive.headerName) this.sorterActive.dir = -1 * this.sorterActive.dir;
else this.sorterActive.dir = 1;
if (index == this.sorterActive.index) this.sorterActive.dir = -1 * this.sorterActive.dir;
else this.sorterActive.dir = 1; this.sorterActive.headerName = headerName;
},
this.sorterActive.index = index; },
}, });
},
});
+32 -50
View File
@@ -3,7 +3,7 @@ import { defineStore } from 'pinia';
import { io } from 'socket.io-client'; import { io } from 'socket.io-client';
import { DataStatus } from '../scripts/enums/DataStatus'; import { DataStatus } from '../scripts/enums/DataStatus';
import StationAPIData from '../scripts/interfaces/api/StationAPIData'; import StationAPIData from '../scripts/interfaces/api/StationAPIData';
import ScheduledTrain from '../scripts/interfaces/ScheduledTrain'; import { ScheduledTrain } from '../scripts/interfaces/ScheduledTrain';
import Station from '../scripts/interfaces/Station'; import Station from '../scripts/interfaces/Station';
import StationRoutes from '../scripts/interfaces/StationRoutes'; import StationRoutes from '../scripts/interfaces/StationRoutes';
import Train from '../scripts/interfaces/Train'; import Train from '../scripts/interfaces/Train';
@@ -15,7 +15,7 @@ import {
getScheduledTrain, getScheduledTrain,
parseSpawns, parseSpawns,
} from '../scripts/utils/storeUtils'; } from '../scripts/utils/storeUtils';
import { APIData, StationJSONData, StoreState } from './storeTypes'; import { APIData, StationJSONData, StoreState } from '../scripts/interfaces/store/storeTypes';
export const useStore = defineStore('store', { export const useStore = defineStore('store', {
state: () => state: () =>
@@ -54,7 +54,7 @@ export const useStore = defineStore('store', {
trains: DataStatus.Loading, trains: DataStatus.Loading,
}, },
currentStatsTab: 'daily', currentStatsTab: null,
blockScroll: false, blockScroll: false,
listenerLaunched: false, listenerLaunched: false,
@@ -303,57 +303,39 @@ export const useStore = defineStore('store', {
...scenery, ...scenery,
authors: scenery.authors?.split(',').map((a) => a.trim()), authors: scenery.authors?.split(',').map((a) => a.trim()),
routes: routes:
scenery.routes scenery.routesInfo.reduce(
?.split(';') (acc, route) => {
.filter((routeString) => routeString) const propName: keyof StationRoutes = `${route.routeTracks == 2 ? 'twoWay' : 'oneWay'}${
.reduce( route.isElectric ? '' : 'No'
(acc, routeString) => { }CatenaryRouteNames`;
const specs1 = routeString.split('_')[0];
const isInternal = specs1.startsWith('!');
const name = isInternal ? specs1.replace('!', '') : specs1;
const specs2 = routeString.split('_')[1].split(''); acc[route.routeTracks == 2 ? 'twoWay' : 'oneWay'].push({
const twoWay = specs2[0] == '2'; name: route.routeName,
const catenary = specs2[1] == 'E'; SBL: route.isRouteSBL,
const SBL = specs2[2] == 'S'; TWB: false,
const TWB = specs2[3] ? true : false; catenary: route.isElectric,
const speed = Number(routeString.split(':')[1]) || 0; isInternal: route.isInternal,
const length = Number(routeString.split(':')[2]) || 0; tracks: route.routeTracks,
length: route.routeLength,
speed: route.routeSpeed,
});
const propName = twoWay if (!route.isInternal) acc[propName].push(route.routeName);
? catenary
? 'twoWayCatenaryRouteNames'
: 'twoWayNoCatenaryRouteNames'
: catenary
? 'oneWayCatenaryRouteNames'
: 'oneWayNoCatenaryRouteNames';
acc[twoWay ? 'twoWay' : 'oneWay'].push({ if (route.isRouteSBL) acc['sblRouteNames'].push(route.routeName);
name,
SBL,
TWB,
catenary,
isInternal,
tracks: twoWay ? 2 : 1,
length,
speed,
});
if (!isInternal) acc[propName].push(name);
if (SBL) acc['sblRouteNames'].push(name); return acc;
},
return acc; {
}, oneWay: [],
{ twoWay: [],
oneWay: [], sblRouteNames: [],
twoWay: [], oneWayCatenaryRouteNames: [],
sblRouteNames: [], oneWayNoCatenaryRouteNames: [],
oneWayCatenaryRouteNames: [], twoWayCatenaryRouteNames: [],
oneWayNoCatenaryRouteNames: [], twoWayNoCatenaryRouteNames: [],
twoWayCatenaryRouteNames: [], } as StationRoutes
twoWayNoCatenaryRouteNames: [], ) || {},
} as StationRoutes
) || {},
checkpoints: scenery.checkpoints checkpoints: scenery.checkpoints
? scenery.checkpoints.split(';').map((sub) => ({ checkpointName: sub, scheduledTrains: [] })) ? scenery.checkpoints.split(';').map((sub) => ({ checkpointName: sub, scheduledTrains: [] }))
: [], : [],
+13
View File
@@ -23,6 +23,15 @@
padding: 1em 0; padding: 1em 0;
} }
.journal_refreshed-date {
background-color: #333;
color: #ddd;
text-align: end;
padding: 0.25em;
margin: 0.5em 0;
}
.journal_warning { .journal_warning {
text-align: center; text-align: center;
font-size: 1.3em; font-size: 1.3em;
@@ -71,6 +80,10 @@
justify-content: center; justify-content: center;
flex-wrap: wrap; flex-wrap: wrap;
} }
.journal_refreshed-date {
text-align: center;
}
} }
@media (orientation: landscape) { @media (orientation: landscape) {
-11
View File
@@ -1,11 +0,0 @@
.scenery-section {
position: relative;
height: 100%;
overflow-y: scroll;
}
.list-warning {
padding: 1em 0.5em;
background-color: #444;
font-size: 1.2em;
}
+24 -3
View File
@@ -4,12 +4,11 @@
display: inline-block; display: inline-block;
padding: 0; padding: 0;
background: #585858;
margin: 0.25em; margin: 0.25em;
span { span {
display: inline-block; display: inline-block;
background: #585858;
padding: 0.2em 0.4em; padding: 0.2em 0.4em;
} }
@@ -31,7 +30,6 @@
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
font-size: 0.9em;
&.driver { &.driver {
border-radius: 50%; border-radius: 50%;
@@ -56,3 +54,26 @@
background-color: forestgreen; background-color: forestgreen;
} }
} }
.train-badge {
padding: 0.1em 0.2em;
border-radius: 0.2em;
font-weight: bold;
font-size: 0.9em;
&.twr {
background-color: var(--clr-twr);
box-shadow: 0 0 5px 1px var(--clr-twr);
color: black;
}
&.skr {
background-color: var(--clr-skr);
box-shadow: 0 0 5px 1px var(--clr-skr);
}
&.offline {
background-color: #be3728;
}
}
+21 -18
View File
@@ -16,7 +16,7 @@
height: 7px; height: 7px;
background-color: lightgreen; background-color: lightgreen;
border-radius: 50%; border-radius: 50%;
margin-left: 10px; margin-left: 10px;
} }
@@ -82,12 +82,16 @@ h1.option-title {
padding: 0.25em 0.25em 0 0; padding: 0.25em 0.25em 0 0;
} }
.options_filter-sections section {
margin: 0.5em 0;
}
.options_filters { .options_filters {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 0.5em; gap: 0.5em;
margin: 0.5em 0 0 0; margin: 0.25em 0;
} }
.sort-option, .sort-option,
@@ -118,17 +122,6 @@ h1.option-title {
margin: 0.5em auto; margin: 0.5em auto;
} }
.search_actions {
display: flex;
gap: 0.5em;
margin: 1em 0;
width: 100%;
button {
width: 100%;
}
}
.search-box { .search-box {
.search-exit { .search-exit {
position: absolute; position: absolute;
@@ -139,6 +132,17 @@ h1.option-title {
} }
} }
.options_actions {
display: flex;
gap: 0.5em;
width: 100%;
margin-top: 1em;
button {
width: 100%;
}
}
@include smallScreen() { @include smallScreen() {
h1 { h1 {
text-align: center; text-align: center;
@@ -155,13 +159,12 @@ h1.option-title {
max-width: 100%; max-width: 100%;
} }
.filter-option,
.sort-option {
margin: 0.25em 0.25em;
}
.options_filters, .options_filters,
.options_sorters { .options_sorters {
justify-content: center; justify-content: center;
} }
.filter-section {
text-align: center;
}
} }
+31
View File
@@ -0,0 +1,31 @@
table.scenery-history-table {
width: 100%;
border-collapse: collapse;
thead {
position: sticky;
top: 0;
background-color: #222222;
}
th {
padding: 0.5em;
}
tr {
background-color: #353535;
border: none;
}
td {
padding: 0.75em;
border-bottom: solid 5px #111;
}
}
.no-history {
padding: 1em 0.5em;
background-color: #444;
font-size: 1.2em;
color: #ccc;
}
@@ -1,18 +0,0 @@
import { JournalFilterType } from '../../scripts/enums/JournalFilterType';
export type JournalTimetableSearchKey = 'search-driver' | 'search-train' | 'search-date' | 'search-dispatcher';
export type JournalTimetableSearchType = {
[key in JournalTimetableSearchKey]: string;
};
export interface JournalTimetableFilter {
id: JournalFilterType;
filterSection: string;
isActive: boolean;
}
export interface JournalTimetableSorter {
id: 'timetableId' | 'beginDate' | 'distance' | 'total-stops';
dir: -1 | 1;
}
+296 -290
View File
@@ -1,290 +1,296 @@
<template> <template>
<section class="journal-timetables"> <section class="journal-timetables">
<JournalHeader /> <JournalHeader />
<div class="journal_wrapper"> <div class="journal_wrapper">
<JournalOptions <JournalOptions
@on-search-confirm="fetchHistoryData" @on-search-confirm="fetchHistoryData"
@on-options-reset="resetOptions" @on-options-reset="resetOptions"
@on-refresh-data="fetchHistoryData" @on-refresh-data="fetchHistoryData"
:sorter-option-ids="['timestampFrom', 'duration']" :sorter-option-ids="['timestampFrom', 'duration']"
:data-status="dataStatus" :data-status="dataStatus"
:current-options-active="currentOptionsActive" :current-options-active="currentOptionsActive"
optionsType="dispatchers" optionsType="dispatchers"
/> />
<div class="list_wrapper" @scroll="handleScroll"> <div class="journal_refreshed-date" v-if="dataRefreshedAt">
<transition name="status-anim" mode="out-in"> {{ $t('journal.data-refreshed-at') }}: {{ dataRefreshedAt.toLocaleString($i18n.locale) }}
<div :key="dataStatus"> </div>
<div class="journal_warning" v-if="store.isOffline">
{{ $t('app.offline') }} <div class="list_wrapper" @scroll="handleScroll">
</div> <transition name="status-anim" mode="out-in">
<div :key="dataStatus">
<Loading v-else-if="dataStatus == DataStatus.Loading" /> <div class="journal_warning" v-if="store.isOffline">
{{ $t('app.offline') }}
<div v-else-if="dataStatus == DataStatus.Error" class="journal_warning error"> </div>
{{ $t('app.error') }}
</div> <Loading v-else-if="dataStatus == DataStatus.Loading" />
<div class="journal_warning" v-else-if="historyList.length == 0"> <div v-else-if="dataStatus == DataStatus.Error" class="journal_warning error">
{{ $t('app.no-result') }} {{ $t('app.error') }}
</div> </div>
<div v-else> <div class="journal_warning" v-else-if="historyList.length == 0">
<JournalDispatchersList :dispatcherHistory="computedHistoryList" /> {{ $t('app.no-result') }}
</div>
<button
class="btn btn--option btn--load-data" <div v-else>
v-if="!scrollNoMoreData && scrollDataLoaded && computedHistoryList.length > 15" <JournalDispatchersList :dispatcherHistory="computedHistoryList" />
@click="addHistoryData"
> <button
{{ $t('journal.load-data') }} class="btn btn--option btn--load-data"
</button> v-if="!scrollNoMoreData && scrollDataLoaded && computedHistoryList.length > 15"
</div> @click="addHistoryData"
</div> >
</transition> {{ $t('journal.load-data') }}
</button>
<div class="journal_warning" v-if="scrollNoMoreData"> </div>
{{ $t('journal.no-further-data') }} </div>
</div> </transition>
<div class="journal_warning" v-else-if="!scrollDataLoaded"> <div class="journal_warning" v-if="scrollNoMoreData">
{{ $t('journal.loading-further-data') }} {{ $t('journal.no-further-data') }}
</div> </div>
</div>
</div> <div class="journal_warning" v-else-if="!scrollDataLoaded">
</section> {{ $t('journal.loading-further-data') }}
</template> </div>
</div>
<script lang="ts"> </div>
import { defineComponent, provide, reactive, Ref, ref } from 'vue'; </section>
import axios from 'axios'; </template>
import ActionButton from '../components/Global/ActionButton.vue'; <script lang="ts">
import JournalOptions from '../components/JournalView/JournalOptions.vue'; import { defineComponent, provide, reactive, Ref, ref } from 'vue';
import DispatcherStats from '../components/JournalView/DispatcherStats.vue'; import axios from 'axios';
import SearchBox from '../components/Global/SearchBox.vue';
import ActionButton from '../components/Global/ActionButton.vue';
import Loading from '../components/Global/Loading.vue'; import JournalOptions from '../components/JournalView/JournalOptions.vue';
import { URLs } from '../scripts/utils/apiURLs'; import DispatcherStats from '../components/JournalView/DispatcherStats.vue';
import { DataStatus } from '../scripts/enums/DataStatus'; import SearchBox from '../components/Global/SearchBox.vue';
import { useStore } from '../store/store';
import JournalDispatchersList from '../components/JournalView/JournalDispatchersList.vue'; import Loading from '../components/Global/Loading.vue';
import { JournalDispatcherSearcher, JournalDispatcherSorter } from '../types/Journal/JournalDispatcherTypes'; import { URLs } from '../scripts/utils/apiURLs';
import { DispatcherHistory } from '../scripts/interfaces/api/DispatchersAPIData'; import { DataStatus } from '../scripts/enums/DataStatus';
import JournalHeader from '../components/JournalView/JournalHeader.vue'; import { useStore } from '../store/store';
import { LocationQuery } from 'vue-router'; import JournalDispatchersList from '../components/JournalView/JournalDispatchersList.vue';
import { JournalDispatcherSearcher, JournalDispatcherSorter } from '../scripts/types/JournalDispatcherTypes';
const DISPATCHERS_API_URL = `${URLs.stacjownikAPI}/api/getDispatchers`; import { DispatcherHistory } from '../scripts/interfaces/api/DispatchersAPIData';
import JournalHeader from '../components/JournalView/JournalHeader.vue';
export default defineComponent({ import { LocationQuery } from 'vue-router';
components: {
SearchBox, const DISPATCHERS_API_URL = `${URLs.stacjownikAPI}/api/getDispatchers`;
ActionButton,
JournalOptions, export default defineComponent({
DispatcherStats, components: {
Loading, SearchBox,
JournalDispatchersList, ActionButton,
JournalHeader, JournalOptions,
}, DispatcherStats,
name: 'JournalDispatchers', Loading,
JournalDispatchersList,
props: { JournalHeader,
sceneryName: { },
type: String, name: 'JournalDispatchers',
required: false,
}, props: {
sceneryName: {
dispatcherName: { type: String,
type: String, required: false,
required: false, },
},
}, dispatcherName: {
type: String,
data: () => ({ required: false,
currentQuery: '', },
currentQueryArray: [] as string[], },
scrollDataLoaded: true, data: () => ({
scrollNoMoreData: false, currentQuery: '',
currentQueryArray: [] as string[],
showReturnButton: false, dataRefreshedAt: null as Date | null,
statsCardOpen: false,
currentOptionsActive: false, scrollDataLoaded: true,
scrollNoMoreData: false,
dataStatus: DataStatus.Loading,
DataStatus, showReturnButton: false,
statsCardOpen: false,
historyList: [] as DispatcherHistory[], currentOptionsActive: false,
}),
dataStatus: DataStatus.Loading,
setup() { DataStatus,
const sorterActive: JournalDispatcherSorter = reactive({ id: 'timestampFrom', dir: -1 });
const journalFilterActive = ref({}); historyList: [] as DispatcherHistory[],
}),
const searchersValues = reactive({
'search-dispatcher': '', setup() {
'search-station': '', const sorterActive: JournalDispatcherSorter = reactive({ id: 'timestampFrom', dir: -1 });
'search-date': '', const journalFilterActive = ref({});
} as JournalDispatcherSearcher);
const searchersValues = reactive({
const countFromIndex = ref(0); 'search-dispatcher': '',
const countLimit = 15; 'search-station': '',
'search-date': '',
provide('sorterActive', sorterActive); } as JournalDispatcherSearcher);
provide('journalFilterActive', journalFilterActive);
provide('searchersValues', searchersValues); const countFromIndex = ref(0);
const countLimit = 15;
const scrollElement: Ref<HTMLElement | null> = ref(null);
provide('sorterActive', sorterActive);
return { provide('journalFilterActive', journalFilterActive);
store: useStore(), provide('searchersValues', searchersValues);
provide('filterList', reactive([]));
sorterActive,
searchersValues, const scrollElement: Ref<HTMLElement | null> = ref(null);
countFromIndex, return {
countLimit, store: useStore(),
scrollElement, sorterActive,
maxCount: ref(15), searchersValues,
};
}, countFromIndex,
countLimit,
watch: {
currentQueryArray(q: string[]) { scrollElement,
this.currentOptionsActive = maxCount: ref(15),
q.length > 2 || q.some((qv) => qv.startsWith('sortBy=') && qv.split('=')[1] != 'timestampFrom'); };
}, },
},
watch: {
computed: { currentQueryArray(q: string[]) {
computedHistoryList() { this.currentOptionsActive =
return this.historyList.filter( q.length > 2 || q.some((qv) => qv.startsWith('sortBy=') && qv.split('=')[1] != 'timestampFrom');
(doc) => doc.isOnline || (doc.currentDuration && doc.currentDuration > 10 * 60000) },
); },
},
}, computed: {
computedHistoryList() {
beforeRouteUpdate(to, _) { return this.historyList.filter(
this.handleQueries(to.query); (doc) => doc.isOnline || (doc.currentDuration && doc.currentDuration > 10 * 60000)
this.fetchHistoryData(); );
}, },
},
activated() {
this.handleQueries(this.$route.query); beforeRouteUpdate(to, _) {
this.fetchHistoryData(); this.handleQueries(to.query);
}, this.fetchHistoryData();
},
methods: {
handleScroll(e: Event) { activated() {
const listElement = e.target as HTMLElement; this.handleQueries(this.$route.query);
const scrollTop = listElement.scrollTop; this.fetchHistoryData();
const elementHeight = listElement.scrollHeight - listElement.offsetHeight; },
if (!this.scrollDataLoaded || this.scrollNoMoreData || this.dataStatus != DataStatus.Loaded) return; methods: {
handleScroll(e: Event) {
if (scrollTop > elementHeight * 0.85) this.addHistoryData(); const listElement = e.target as HTMLElement;
}, const scrollTop = listElement.scrollTop;
const elementHeight = listElement.scrollHeight - listElement.offsetHeight;
handleQueries(query: LocationQuery) {
if ('sceneryName' in query) this.searchersValues['search-station'] = `${query.sceneryName}`; if (!this.scrollDataLoaded || this.scrollNoMoreData || this.dataStatus != DataStatus.Loaded) return;
if ('dispatcherName' in query) this.searchersValues['search-dispatcher'] = `${query.dispatcherName}`;
}, if (scrollTop > elementHeight * 0.85) this.addHistoryData();
},
setSearchers(date: string, station: string, dispatcher: string) {
this.searchersValues['search-date'] = date; handleQueries(query: LocationQuery) {
this.searchersValues['search-station'] = station; if ('sceneryName' in query) this.searchersValues['search-station'] = `${query.sceneryName}`;
this.searchersValues['search-dispatcher'] = dispatcher; if ('dispatcherName' in query) this.searchersValues['search-dispatcher'] = `${query.dispatcherName}`;
}, },
resetOptions() { setSearchers(date: string, station: string, dispatcher: string) {
this.setSearchers('', '', ''); this.searchersValues['search-date'] = date;
this.sorterActive.id = 'timestampFrom'; this.searchersValues['search-station'] = station;
this.searchersValues['search-dispatcher'] = dispatcher;
this.fetchHistoryData(); },
},
resetOptions() {
async addHistoryData() { this.setSearchers('', '', '');
this.scrollDataLoaded = false; this.sorterActive.id = 'timestampFrom';
const countFrom = this.historyList.length; this.fetchHistoryData();
},
const responseData: DispatcherHistory[] = await (
await axios.get(`${DISPATCHERS_API_URL}?${this.currentQuery}&countFrom=${countFrom}`) async addHistoryData() {
).data; this.scrollDataLoaded = false;
if (!responseData) return; const countFrom = this.historyList.length;
if (responseData.length == 0) { const responseData: DispatcherHistory[] = await (
this.scrollNoMoreData = true; await axios.get(`${DISPATCHERS_API_URL}?${this.currentQuery}&countFrom=${countFrom}`)
return; ).data;
}
if (!responseData) return;
this.historyList.push(...responseData);
this.scrollDataLoaded = true; if (responseData.length == 0) {
}, this.scrollNoMoreData = true;
return;
async fetchHistoryData() { }
const queries: string[] = [];
this.historyList.push(...responseData);
const dispatcher = this.searchersValues['search-dispatcher'].trim(); this.scrollDataLoaded = true;
const station = this.searchersValues['search-station'].trim(); },
const dateString = this.searchersValues['search-date'].trim();
async fetchHistoryData() {
const timestampFrom = dateString ? Date.parse(new Date(dateString).toISOString()) - 120 * 60 * 1000 : undefined; const queries: string[] = [];
const timestampTo = timestampFrom ? timestampFrom + 86400000 : undefined;
const dispatcher = this.searchersValues['search-dispatcher'].trim();
if (dispatcher) queries.push(`dispatcherName=${dispatcher}`); const station = this.searchersValues['search-station'].trim();
if (station) queries.push(`stationName=${station}`); const dateString = this.searchersValues['search-date'].trim();
if (timestampFrom && timestampTo) queries.push(`timestampFrom=${timestampFrom}`, `timestampTo=${timestampTo}`);
const timestampFrom = dateString ? Date.parse(new Date(dateString).toISOString()) - 120 * 60 * 1000 : undefined;
// Z API: const SORT_TYPES = ['allStopsCount', 'endDate', 'beginDate', 'routeDistance']; const timestampTo = timestampFrom ? timestampFrom + 86400000 : undefined;
if (this.sorterActive.id == 'timestampFrom') queries.push('sortBy=timestampFrom');
else if (this.sorterActive.id == 'duration') queries.push('sortBy=currentDuration'); if (dispatcher) queries.push(`dispatcherName=${dispatcher}`);
else queries.push('sortBy=timestampFrom'); if (station) queries.push(`stationName=${station}`);
if (timestampFrom && timestampTo) queries.push(`timestampFrom=${timestampFrom}`, `timestampTo=${timestampTo}`);
queries.push('countLimit=30');
// Z API: const SORT_TYPES = ['allStopsCount', 'endDate', 'beginDate', 'routeDistance'];
if (this.currentQuery != queries.join('&')) this.dataStatus = DataStatus.Loading; if (this.sorterActive.id == 'timestampFrom') queries.push('sortBy=timestampFrom');
else if (this.sorterActive.id == 'duration') queries.push('sortBy=currentDuration');
this.currentQuery = queries.join('&'); else queries.push('sortBy=timestampFrom');
this.currentQueryArray = queries;
queries.push('countLimit=30');
try {
const responseData: DispatcherHistory[] = await ( if (this.currentQuery != queries.join('&')) this.dataStatus = DataStatus.Loading;
await axios.get(`${DISPATCHERS_API_URL}?${this.currentQuery}`)
).data; this.currentQuery = queries.join('&');
this.currentQueryArray = queries;
if (!responseData) {
this.dataStatus = DataStatus.Error; try {
return; const responseData: DispatcherHistory[] = await (
} await axios.get(`${DISPATCHERS_API_URL}?${this.currentQuery}`)
).data;
if (!responseData) return;
if (!responseData) {
// Response data exists this.dataStatus = DataStatus.Error;
this.historyList = responseData; return;
}
// Stats display
this.store.dispatcherStatsName = if (!responseData) return;
this.historyList.length > 0 && this.searchersValues['search-dispatcher'].trim()
? this.historyList[0].dispatcherName // Response data exists
: ''; this.historyList = responseData;
this.dataStatus = DataStatus.Loaded; // Stats display
} catch (error) { this.store.dispatcherStatsName =
this.dataStatus = DataStatus.Error; this.historyList.length > 0 && this.searchersValues['search-dispatcher'].trim()
} ? this.historyList[0].dispatcherName
: '';
this.scrollNoMoreData = false;
this.scrollDataLoaded = true; this.dataRefreshedAt = new Date();
}, this.dataStatus = DataStatus.Loaded;
}, } catch (error) {
}); this.dataStatus = DataStatus.Error;
</script> }
<style lang="scss" scoped> this.scrollNoMoreData = false;
@import '../styles/JournalSection.scss'; this.scrollDataLoaded = true;
</style> },
},
});
</script>
<style lang="scss" scoped>
@import '../styles/JournalSection.scss';
</style>
+349 -305
View File
@@ -1,305 +1,349 @@
<template> <template>
<section class="journal-timetables"> <section class="journal-timetables">
<JournalHeader /> <JournalHeader />
<div class="journal_wrapper"> <div class="journal_wrapper">
<JournalOptions <JournalOptions
@on-search-confirm="fetchHistoryData" @on-search-confirm="fetchHistoryData"
@on-options-reset="resetOptions" @on-options-reset="resetOptions"
@on-refresh-data="fetchHistoryData" @on-refresh-data="fetchHistoryData"
:sorter-option-ids="['timetableId', 'beginDate', 'distance', 'total-stops']" :sorter-option-ids="['timetableId', 'beginDate', 'routeDistance', 'allStopsCount']"
:filters="journalTimetableFilters" :filters="journalTimetableFilters"
:currentOptionsActive="currentOptionsActive" :currentOptionsActive="currentOptionsActive"
:data-status="dataStatus" :data-status="dataStatus"
optionsType="timetables" optionsType="timetables"
/> />
<JournalStats /> <JournalStats />
<div class="list_wrapper" @scroll="handleScroll"> <div class="journal_refreshed-date" v-if="dataRefreshedAt">
<transition name="status-anim" mode="out-in"> {{ $t('journal.data-refreshed-at') }}: {{ dataRefreshedAt.toLocaleString($i18n.locale) }}
<div :key="dataStatus"> </div>
<div class="journal_warning" v-if="store.isOffline">
{{ $t('app.offline') }} <div class="list_wrapper" @scroll="handleScroll">
</div> <transition name="status-anim" mode="out-in">
<div :key="dataStatus">
<Loading v-else-if="dataStatus == DataStatus.Loading" /> <div class="journal_warning" v-if="store.isOffline">
{{ $t('app.offline') }}
<div v-else-if="dataStatus == DataStatus.Error" class="journal_warning error"> </div>
{{ $t('app.error') }}
</div> <Loading v-else-if="dataStatus == DataStatus.Loading" />
<div v-else-if="timetableHistory.length == 0" class="journal_warning"> <div v-else-if="dataStatus == DataStatus.Error" class="journal_warning error">
{{ $t('app.no-result') }} {{ $t('app.error') }}
</div> </div>
<div v-else> <div v-else-if="timetableHistory.length == 0" class="journal_warning">
<JournalTimetablesList :timetableHistory="timetableHistory" /> {{ $t('app.no-result') }}
</div>
<button
class="btn btn--option btn--load-data" <div v-else>
v-if="!scrollNoMoreData && scrollDataLoaded && timetableHistory.length >= 15" <JournalTimetablesList :timetableHistory="timetableHistory" />
@click="addHistoryData"
> <button
{{ $t('journal.load-data') }} class="btn btn--option btn--load-data"
</button> v-if="!scrollNoMoreData && scrollDataLoaded && timetableHistory.length >= 15"
</div> @click="addHistoryData"
</div> >
</transition> {{ $t('journal.load-data') }}
</button>
<div class="journal_warning" v-if="scrollNoMoreData">{{ $t('journal.no-further-data') }}</div> </div>
<div class="journal_warning" v-else-if="!scrollDataLoaded">{{ $t('journal.loading-further-data') }}</div> </div>
</div> </transition>
</div>
</section> <div class="journal_warning" v-if="scrollNoMoreData">{{ $t('journal.no-further-data') }}</div>
</template> <div class="journal_warning" v-else-if="!scrollDataLoaded">{{ $t('journal.loading-further-data') }}</div>
</div>
<script lang="ts"> </div>
import { defineComponent, provide, reactive, Ref, ref } from 'vue'; </section>
import axios from 'axios'; </template>
import DriverStats from '../components/JournalView/JournalDriverStats.vue'; <script lang="ts">
import Loading from '../components/Global/Loading.vue'; import { defineComponent, provide, reactive, Ref, ref } from 'vue';
import { JournalTimetableSorter } from '../types/Journal/JournalTimetablesTypes'; import axios from 'axios';
import dateMixin from '../mixins/dateMixin';
import routerMixin from '../mixins/routerMixin'; import imageMixin from '../mixins/imageMixin';
import { DataStatus } from '../scripts/enums/DataStatus'; import dateMixin from '../mixins/dateMixin';
import { JournalFilterType } from '../scripts/enums/JournalFilterType'; import routerMixin from '../mixins/routerMixin';
import { TimetableHistory } from '../scripts/interfaces/api/TimetablesAPIData'; import modalTrainMixin from '../mixins/modalTrainMixin';
import { URLs } from '../scripts/utils/apiURLs';
import { useStore } from '../store/store'; import DriverStats from '../components/JournalView/JournalDriverStats.vue';
import JournalOptions from '../components/JournalView/JournalOptions.vue'; import JournalOptions from '../components/JournalView/JournalOptions.vue';
import { JournalTimetableSearchType } from '../types/Journal/JournalTimetablesTypes'; import JournalStats from '../components/JournalView/JournalStats.vue';
import modalTrainMixin from '../mixins/modalTrainMixin'; import JournalHeader from '../components/JournalView/JournalHeader.vue';
import imageMixin from '../mixins/imageMixin'; import JournalTimetablesList from '../components/JournalView/JournalTimetablesList.vue';
import JournalTimetablesList from '../components/JournalView/JournalTimetablesList.vue'; import Loading from '../components/Global/Loading.vue';
import { journalTimetableFilters } from '../constants/Journal/JournalTimetablesConsts';
import JournalStats from '../components/JournalView/JournalStats.vue'; import { DataStatus } from '../scripts/enums/DataStatus';
import JournalHeader from '../components/JournalView/JournalHeader.vue'; import { TimetableHistory } from '../scripts/interfaces/api/TimetablesAPIData';
import { LocationQuery } from 'vue-router'; import { URLs } from '../scripts/utils/apiURLs';
import { useStore } from '../store/store';
const TIMETABLES_API_URL = `${URLs.stacjownikAPI}/api/getTimetables`;
import { LocationQuery } from 'vue-router';
export default defineComponent({ import { TimetablesQueryParams } from '../scripts/interfaces/api/TimetablesQueryParams';
components: { DriverStats, Loading, JournalOptions, JournalTimetablesList, JournalStats, JournalHeader }, import { JournalFilterType } from '../scripts/enums/JournalFilterType';
mixins: [dateMixin, routerMixin, modalTrainMixin, imageMixin], import {
JournalFilter,
name: 'JournalTimetables', JournalTimetableSearchType,
JournalTimetableSorter,
props: { } from '../scripts/types/JournalTimetablesTypes';
timetableId: { import { journalTimetableFilters } from '../constants/Journal/JournalTimetablesConsts';
type: String,
}, const TIMETABLES_API_URL = `${URLs.stacjownikAPI}/api/getTimetables`;
},
export default defineComponent({
data: () => ({ components: { DriverStats, Loading, JournalOptions, JournalTimetablesList, JournalStats, JournalHeader },
currentQuery: '', mixins: [dateMixin, routerMixin, modalTrainMixin, imageMixin],
currentQueryArray: [] as string[],
name: 'JournalTimetables',
scrollDataLoaded: true,
scrollNoMoreData: false, props: {
timetableId: {
showReturnButton: false, type: String,
statsCardOpen: false, },
currentOptionsActive: false, },
timetableHistory: [] as TimetableHistory[], data: () => ({
journalTimetableFilters, currentQueryParams: {} as TimetablesQueryParams,
dataRefreshedAt: null as Date | null,
dataStatus: DataStatus.Loading,
dataErrorMessage: '', scrollDataLoaded: true,
scrollNoMoreData: false,
DataStatus,
}), showReturnButton: false,
statsCardOpen: false,
setup() { currentOptionsActive: false,
const sorterActive: JournalTimetableSorter = reactive({ id: 'timetableId', dir: 1 });
const journalFilterActive = ref(journalTimetableFilters[0]); timetableHistory: [] as TimetableHistory[],
journalTimetableFilters,
const searchersValues = reactive({
'search-train': '', dataStatus: DataStatus.Loading,
'search-driver': '', dataErrorMessage: '',
'search-dispatcher': '',
'search-date': '', DataStatus,
} as JournalTimetableSearchType); }),
const countFromIndex = ref(0); setup() {
const countLimit = 15; const sorterActive: JournalTimetableSorter = reactive({ id: 'timetableId', dir: 'desc' });
// const journalFilterActive = ref(journalTimetableFilters[0]);
provide('searchersValues', searchersValues); const initFilters: readonly JournalFilter[] = JSON.parse(JSON.stringify(journalTimetableFilters));
provide('sorterActive', sorterActive); const filterList: JournalFilter[] = reactive(JSON.parse(JSON.stringify(initFilters)));
provide('journalFilterActive', journalFilterActive);
const searchersValues = reactive({
const scrollElement: Ref<HTMLElement | null> = ref(null); 'search-train': '',
'search-driver': '',
return { 'search-dispatcher': '',
sorterActive, 'search-issuedFrom': '',
journalFilterActive, 'search-date': '',
searchersValues, } as JournalTimetableSearchType);
countFromIndex, const countFromIndex = ref(0);
countLimit, const countLimit = 15;
scrollElement, provide('searchersValues', searchersValues);
provide('sorterActive', sorterActive);
store: useStore(), provide('filterList', filterList);
};
}, const scrollElement: Ref<HTMLElement | null> = ref(null);
watch: { return {
currentQueryArray(q: string[]) { sorterActive,
this.currentOptionsActive = q.length >= 2 || q.some((qv) => qv.startsWith('sortBy=') && qv.split('=')[1]); searchersValues,
}, filterList,
}, initFilters,
// Handle route updates for route-links countFromIndex,
beforeRouteUpdate(to, _) { countLimit,
this.handleQueries(to.query);
this.fetchHistoryData(); scrollElement,
},
store: useStore(),
activated() { };
this.handleQueries(this.$route.query); },
this.fetchHistoryData();
}, watch: {
currentQueryParams(q: TimetablesQueryParams) {
this.currentOptionsActive = Object.values(q).some((v) => v !== undefined);
methods: { },
handleScroll(e: Event) { },
const listElement = e.target as HTMLElement;
const scrollTop = listElement.scrollTop; // Handle route updates for route-links
const elementHeight = listElement.scrollHeight - listElement.offsetHeight; beforeRouteUpdate(to, _) {
this.handleQueries(to.query);
if (!this.scrollDataLoaded || this.scrollNoMoreData || this.dataStatus != DataStatus.Loaded) return; this.fetchHistoryData();
},
if (scrollTop > elementHeight * 0.85) this.addHistoryData();
}, activated() {
this.handleQueries(this.$route.query);
handleQueries(query: LocationQuery) { this.fetchHistoryData();
if ('timetableId' in query) this.searchersValues['search-train'] = `#${query.timetableId}`; },
},
methods: {
setSearchers(date: string, driver: string, train: string, dispatcher: string) { handleScroll(e: Event) {
this.searchersValues['search-date'] = date; const listElement = e.target as HTMLElement;
this.searchersValues['search-driver'] = driver; const scrollTop = listElement.scrollTop;
this.searchersValues['search-train'] = train; const elementHeight = listElement.scrollHeight - listElement.offsetHeight;
this.searchersValues['search-dispatcher'] = dispatcher;
}, if (!this.scrollDataLoaded || this.scrollNoMoreData || this.dataStatus != DataStatus.Loaded) return;
resetOptions() { if (scrollTop > elementHeight * 0.85) this.addHistoryData();
this.setSearchers('', '', '', ''); },
this.journalFilterActive = this.journalTimetableFilters[0]; handleQueries(query: LocationQuery) {
this.sorterActive.id = 'timetableId'; if ('timetableId' in query) this.searchersValues['search-train'] = `#${query.timetableId}`;
},
this.fetchHistoryData();
}, setSearchers(date: string, driver: string, train: string, dispatcher: string, issuedFrom: string) {
this.searchersValues['search-date'] = date;
async addHistoryData() { this.searchersValues['search-driver'] = driver;
this.scrollDataLoaded = false; this.searchersValues['search-train'] = train;
this.searchersValues['search-dispatcher'] = dispatcher;
const countFrom = this.timetableHistory.length; this.searchersValues['search-issuedFrom'] = issuedFrom;
},
const responseData: TimetableHistory[] = await (
await axios.get(`${TIMETABLES_API_URL}?${this.currentQuery}&countFrom=${countFrom}`) resetOptions() {
).data; this.setSearchers('', '', '', '', '');
if (!responseData) return; this.sorterActive.id = 'timetableId';
if (responseData.length == 0) { this.filterList.forEach(
this.scrollNoMoreData = true; (f) => (f.isActive = this.initFilters.find((initFilter) => initFilter.id == f.id)?.isActive || false)
return; );
}
this.fetchHistoryData();
this.timetableHistory.push(...responseData); },
this.scrollDataLoaded = true;
}, async addHistoryData() {
this.scrollDataLoaded = false;
async fetchHistoryData() {
// if(this.dataStatus == DataStatus.Loading) return; this.currentQueryParams['countFrom'] = this.timetableHistory.length;
const queries: string[] = []; const responseData: TimetableHistory[] = await (
await axios.get(`${TIMETABLES_API_URL}`, {
const driverName = this.searchersValues['search-driver'].trim(); params: { ...this.currentQueryParams },
const trainNo = this.searchersValues['search-train'].trim(); })
const authorName = this.searchersValues['search-dispatcher'].trim(); ).data;
const dateString = this.searchersValues['search-date'].trim();
if (!responseData) return;
const timestampFrom = dateString ? Date.parse(new Date(dateString).toISOString()) - 120 * 60 * 1000 : undefined;
const timestampTo = timestampFrom ? timestampFrom + 86400000 : undefined; if (responseData.length == 0) {
this.scrollNoMoreData = true;
if (driverName) queries.push(`driverName=${driverName}`); return;
if (trainNo) }
queries.push(trainNo.startsWith('#') ? `timetableId=${trainNo.replace('#', '')}` : `trainNo=${trainNo}`);
if (authorName) queries.push(`authorName=${authorName}`); this.timetableHistory.push(...responseData);
if (timestampFrom && timestampTo) queries.push(`timestampFrom=${timestampFrom}`, `timestampTo=${timestampTo}`); this.scrollDataLoaded = true;
},
// Z API: const SORT_TYPES = ['allStopsCount', 'endDate', 'beginDate', 'routeDistance'];
if (this.sorterActive.id == 'distance') queries.push('sortBy=routeDistance'); async fetchHistoryData() {
else if (this.sorterActive.id == 'total-stops') queries.push('sortBy=allStopsCount'); const driverName = this.searchersValues['search-driver'].trim() || undefined;
else if (this.sorterActive.id == 'beginDate') queries.push('sortBy=beginDate'); const trainNo = this.searchersValues['search-train'].trim() || undefined;
// else queries.push('sortBy=timetableId'); const authorName = this.searchersValues['search-dispatcher'].trim() || undefined;
const dateString = this.searchersValues['search-date'].trim() || undefined;
queries.push('countLimit=15'); const issuedFrom = this.searchersValues['search-issuedFrom'].trim() || undefined;
switch (this.journalFilterActive.id) { const timestampFrom = dateString ? Date.parse(new Date(dateString).toISOString()) - 120 * 60 * 1000 : undefined;
case JournalFilterType.abandoned: const timestampTo = timestampFrom ? timestampFrom + 86400000 : undefined;
queries.push('fulfilled=0', 'terminated=1');
break; const queryParams: TimetablesQueryParams = {};
case JournalFilterType.active: this.filterList
queries.push('terminated=0'); .filter((f) => f.isActive)
break; .forEach((f) => {
switch (f.id) {
case JournalFilterType.fulfilled: case JournalFilterType.ABANDONED:
queries.push('fulfilled=1'); queryParams['fulfilled'] = 0;
break; queryParams['terminated'] = 1;
break;
default:
break; case JournalFilterType.ACTIVE:
} queryParams['fulfilled'] = undefined;
queryParams['terminated'] = 0;
if (this.currentQuery != queries.join('&')) this.dataStatus = DataStatus.Loading; break;
this.currentQuery = queries.join('&'); case JournalFilterType.FULFILLED:
this.currentQueryArray = queries; queryParams['terminated'] = undefined;
queryParams['fulfilled'] = 1;
try { break;
const responseData: TimetableHistory[] = await (
await axios.get(`${TIMETABLES_API_URL}?${this.currentQuery}`) case JournalFilterType.ALL:
).data; queryParams['terminated'] = undefined;
queryParams['fulfilled'] = undefined;
if (!responseData) { break;
this.dataStatus = DataStatus.Error;
this.dataErrorMessage = 'Brak danych!'; case JournalFilterType.TWR_SKR:
return; queryParams['twr'] = undefined;
} queryParams['skr'] = undefined;
break;
if (!responseData) return;
case JournalFilterType.TWR:
// Response data exists queryParams['twr'] = 1;
this.timetableHistory = responseData; queryParams['skr'] = undefined;
break;
// Stats display
this.store.driverStatsName = case JournalFilterType.SKR:
this.timetableHistory.length > 0 && this.searchersValues['search-driver'].trim() queryParams['twr'] = undefined;
? this.timetableHistory[0].driverName queryParams['skr'] = 1;
: ''; break;
this.dataStatus = DataStatus.Loaded; default:
} catch (error) { break;
this.dataStatus = DataStatus.Error; }
this.dataErrorMessage = 'Ups! Coś poszło nie tak!'; });
}
queryParams['driverName'] = driverName;
this.scrollNoMoreData = false; queryParams[trainNo?.startsWith('#') ? 'timetableId' : 'trainNo'] = trainNo?.replace('#', '');
this.scrollDataLoaded = true; queryParams['countFrom'] = undefined;
}, queryParams['countLimit'] = undefined;
},
}); queryParams['authorName'] = authorName;
</script> queryParams['timestampFrom'] = timestampFrom;
queryParams['timestampTo'] = timestampTo;
<style lang="scss" scoped> queryParams['issuedFrom'] = issuedFrom;
@import '../styles/JournalSection.scss'; queryParams['sortBy'] = this.sorterActive.id != 'timetableId' ? this.sorterActive.id : undefined;
</style>
if (JSON.stringify(this.currentQueryParams) != JSON.stringify(queryParams)) this.dataStatus = DataStatus.Loading;
this.currentQueryParams = queryParams;
try {
const responseData: TimetableHistory[] = await (
await axios.get(`${TIMETABLES_API_URL}`, {
params: this.currentQueryParams,
})
).data;
if (!responseData) {
this.dataStatus = DataStatus.Error;
this.dataErrorMessage = 'Brak danych!';
return;
}
if (!responseData) return;
// Response data exists
this.timetableHistory = responseData;
// Stats display
this.store.driverStatsName =
this.timetableHistory.length > 0 && this.searchersValues['search-driver'].trim()
? this.timetableHistory[0].driverName
: '';
this.dataStatus = DataStatus.Loaded;
this.dataRefreshedAt = new Date();
} catch (error) {
this.dataStatus = DataStatus.Error;
this.dataErrorMessage = 'Ups! Coś poszło nie tak!';
}
this.scrollNoMoreData = false;
this.scrollDataLoaded = true;
},
},
});
</script>
<style lang="scss" scoped>
@import '../styles/JournalSection.scss';
</style>
+6
View File
@@ -172,6 +172,12 @@ button.back-btn {
} }
} }
.scenery-section {
position: relative;
height: 100%;
overflow-y: scroll;
}
.scenery-wrapper { .scenery-wrapper {
display: grid; display: grid;
grid-template-columns: 4fr 5fr; grid-template-columns: 4fr 5fr;
-11
View File
@@ -52,17 +52,6 @@ export default defineComponent({
mounted() { mounted() {
this.filterStore.setupFilters(); this.filterStore.setupFilters();
// this.filterStore.inputs.options.forEach((option) => {
// const value = StorageManager.getBooleanValue(option.name);
// option.value = value;
// this.filterStore.changeFilterValue({ name: option.name, value: value });
// });
// this.filterStore.inputs.sliders.forEach((slider) => {
// const value = StorageManager.getNumericValue(slider.name);
// slider.value = value;
// this.filterStore.changeFilterValue({ name: slider.name, value: value });
// });
}, },
}); });
</script> </script>
+3 -3
View File
@@ -2,7 +2,7 @@
<section class="trains-view"> <section class="trains-view">
<div class="trains_wrapper"> <div class="trains_wrapper">
<TrainOptions <TrainOptions
:sorter-option-ids="['distance', 'id', 'progress', 'delay', 'mass', 'speed', 'length']" :sorter-option-ids="['routeDistance', 'id', 'progress', 'delay', 'mass', 'speed', 'length']"
:current-options-active="currentOptionsActive" :current-options-active="currentOptionsActive"
/> />
@@ -21,7 +21,7 @@ import modalTrainMixin from '../mixins/modalTrainMixin';
import Train from '../scripts/interfaces/Train'; import Train from '../scripts/interfaces/Train';
import { filteredTrainList } from '../scripts/managers/trainFilterManager'; import { filteredTrainList } from '../scripts/managers/trainFilterManager';
import { useStore } from '../store/store'; import { useStore } from '../store/store';
import { TrainFilter } from '../types/Trains/TrainOptionsTypes'; import { TrainFilter } from '../scripts/interfaces/Trains/TrainFilter';
export default defineComponent({ export default defineComponent({
components: { components: {
@@ -57,7 +57,7 @@ export default defineComponent({
const store = useStore(); const store = useStore();
const initTrainFilters = [...trainFilters.map((f) => ({ ...f }))]; const initTrainFilters = [...trainFilters.map((f) => ({ ...f }))];
const sorterActive = reactive({ id: 'distance', dir: -1 }); const sorterActive = reactive({ id: 'routeDistance', dir: -1 });
const filterList = reactive([...trainFilters]) as TrainFilter[]; const filterList = reactive([...trainFilters]) as TrainFilter[];
const currentOptionsActive = ref(false); const currentOptionsActive = ref(false);
+55 -55
View File
@@ -1,55 +1,55 @@
import { defineConfig } from 'vite'; import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue'; import vue from '@vitejs/plugin-vue';
import { VitePWA } from 'vite-plugin-pwa'; import { VitePWA } from 'vite-plugin-pwa';
export default defineConfig({ export default defineConfig({
server: { server: {
port: 5001, port: 5001,
}, },
plugins: [ plugins: [
vue(), vue(),
VitePWA({ VitePWA({
registerType: 'prompt', registerType: 'autoUpdate',
workbox: { workbox: {
globPatterns: ['**/*.{js,css,html,png,svg,jpg}'], globPatterns: ['**/*.{js,css,html,png,svg,jpg}'],
runtimeCaching: [ runtimeCaching: [
{ {
urlPattern: new RegExp('^https://spythere.pl/api/getSceneries', 'i'), urlPattern: new RegExp('^https://spythere.pl/api/getSceneries', 'i'),
handler: 'NetworkFirst', handler: 'NetworkFirst',
options: { options: {
cacheName: 'sceneries-cache', cacheName: 'sceneries-cache',
expiration: { expiration: {
maxEntries: 1, maxEntries: 1,
maxAgeSeconds: 60 * 60 * 24 * 7, // <== 7 days maxAgeSeconds: 60 * 60 * 24 * 7, // <== 7 days
}, },
cacheableResponse: { cacheableResponse: {
statuses: [0, 200], statuses: [0, 200],
}, },
}, },
}, },
{ {
urlPattern: /^https:\/\/rj.td2.info.pl\/dist\/img\/thumbnails\/.*/i, urlPattern: /^https:\/\/rj.td2.info.pl\/dist\/img\/thumbnails\/.*/i,
handler: 'CacheFirst', handler: 'CacheFirst',
options: { options: {
cacheName: 'images-cache', cacheName: 'images-cache',
expiration: { expiration: {
maxEntries: 100, maxEntries: 100,
maxAgeSeconds: 60 * 60 * 24 * 60, maxAgeSeconds: 60 * 60 * 24 * 60,
}, },
cacheableResponse: { cacheableResponse: {
statuses: [0, 200, 404], statuses: [0, 200, 404],
}, },
}, },
}, },
], ],
}, },
devOptions: { devOptions: {
enabled: true, enabled: true,
}, },
}), }),
], ],
}); });
+106 -193
View File
@@ -31,7 +31,7 @@
resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.10.tgz" resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.10.tgz"
integrity sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg== integrity sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg==
"@babel/core@^7.11.1": "@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.11.1", "@babel/core@^7.12.0", "@babel/core@^7.13.0", "@babel/core@^7.4.0-0":
version "7.20.7" version "7.20.7"
resolved "https://registry.npmjs.org/@babel/core/-/core-7.20.7.tgz" resolved "https://registry.npmjs.org/@babel/core/-/core-7.20.7.tgz"
integrity sha512-t1ZjCluspe5DW24bn2Rr1CDb2v9rn/hROtg9a2tmd0+QYf4bsloYfLQzjG4qHPNMhWtKdGC33R5AxGR2Af2cBw== integrity sha512-t1ZjCluspe5DW24bn2Rr1CDb2v9rn/hROtg9a2tmd0+QYf4bsloYfLQzjG4qHPNMhWtKdGC33R5AxGR2Af2cBw==
@@ -912,114 +912,9 @@
"@babel/helper-validator-identifier" "^7.19.1" "@babel/helper-validator-identifier" "^7.19.1"
to-fast-properties "^2.0.0" to-fast-properties "^2.0.0"
"@esbuild/android-arm64@0.16.10":
version "0.16.10"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.16.10.tgz#d784d8f13dbef50492ea55456fb50651e4036fbf"
integrity sha512-47Y+NwVKTldTlDhSgJHZ/RpvBQMUDG7eKihqaF/u6g7s0ZPz4J1vy8A3rwnnUOF2CuDn7w7Gj/QcMoWz3U3SJw==
"@esbuild/android-arm@0.16.10":
version "0.16.10"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.16.10.tgz#becf6b5647c091b039121db8c17300a7dfd1ab4a"
integrity sha512-RmJjQTRrO6VwUWDrzTBLmV4OJZTarYsiepLGlF2rYTVB701hSorPywPGvP6d8HCuuRibyXa5JX4s3jN2kHEtjQ==
"@esbuild/android-x64@0.16.10":
version "0.16.10"
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.16.10.tgz#648cacbb13a5047380a038e5d6d895015e31b525"
integrity sha512-C4PfnrBMcuAcOurQzpF1tTtZz94IXO5JmICJJ3NFJRHbXXsQUg9RFG45KvydKqtFfBaFLCHpduUkUfXwIvGnRg==
"@esbuild/darwin-arm64@0.16.10":
version "0.16.10"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.16.10.tgz#3ca7fd9a456d11752df77df6c030f2d08f27bda9"
integrity sha512-bH/bpFwldyOKdi9HSLCLhhKeVgRYr9KblchwXgY2NeUHBB/BzTUHtUSBgGBmpydB1/4E37m+ggXXfSrnD7/E7g==
"@esbuild/darwin-x64@0.16.10":
version "0.16.10"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.16.10.tgz#7eb71b8da4106627f01553def517d3c5e5942592"
integrity sha512-OXt7ijoLuy+AjDSKQWu+KdDFMBbdeaL6wtgMKtDUXKWHiAMKHan5+R1QAG6HD4+K0nnOvEJXKHeA9QhXNAjOTQ==
"@esbuild/freebsd-arm64@0.16.10":
version "0.16.10"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.10.tgz#c69c78ee1d17d35ad2cf76a1bb67788000a84b43"
integrity sha512-shSQX/3GHuspE3Uxtq5kcFG/zqC+VuMnJkqV7LczO41cIe6CQaXHD3QdMLA4ziRq/m0vZo7JdterlgbmgNIAlQ==
"@esbuild/freebsd-x64@0.16.10":
version "0.16.10"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.16.10.tgz#a9804ab1b9366f915812af24ad5cfc1c0db01441"
integrity sha512-5YVc1zdeaJGASijZmTzSO4h6uKzsQGG3pkjI6fuXvolhm3hVRhZwnHJkforaZLmzvNv5Tb7a3QL2FAVmrgySIA==
"@esbuild/linux-arm64@0.16.10":
version "0.16.10"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.16.10.tgz#d9a9ddfcb28ed8cced688bc112ef66283d6fa77f"
integrity sha512-2aqeNVxIaRfPcIaMZIFoblLh588sWyCbmj1HHCCs9WmeNWm+EIN0SmvsmPvTa/TsNZFKnxTcvkX2eszTcCqIrA==
"@esbuild/linux-arm@0.16.10":
version "0.16.10"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.16.10.tgz#f32cdac1d3319c83ae7f9f31238dd1284ee6bba2"
integrity sha512-c360287ZWI2miBnvIj23bPyVctgzeMT2kQKR+x94pVqIN44h3GF8VMEs1SFPH1UgyDr3yBbx3vowDS1SVhyVhA==
"@esbuild/linux-ia32@0.16.10":
version "0.16.10"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.16.10.tgz#1e023478e42f3a01cad48f4af50120d4b639af03"
integrity sha512-sqMIEWeyrLGU7J5RB5fTkLRIFwsgsQ7ieWXlDLEmC2HblPYGb3AucD7inw2OrKFpRPKsec1l+lssiM3+NV5aOw==
"@esbuild/linux-loong64@0.16.10":
version "0.16.10"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.16.10.tgz#f9098865a69d1d6e2f8bda51c7f9d4240f20b771"
integrity sha512-O7Pd5hLEtTg37NC73pfhUOGTjx/+aXu5YoSq3ahCxcN7Bcr2F47mv+kG5t840thnsEzrv0oB70+LJu3gUgchvg==
"@esbuild/linux-mips64el@0.16.10":
version "0.16.10"
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.16.10.tgz#574725ad2ea81b7783b7ba7d1ab3475f8fdd8d32"
integrity sha512-FN8mZOH7531iPHM0kaFhAOqqNHoAb6r/YHW2ZIxNi0a85UBi2DO4Vuyn7t1p4UN8a4LoAnLOT1PqNgHkgBJgbA==
"@esbuild/linux-ppc64@0.16.10":
version "0.16.10"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.16.10.tgz#11da658c54514a693813af56bb28951d563a90c3"
integrity sha512-Dg9RiqdvHOAWnOKIOTsIx8dFX9EDlY2IbPEY7YFzchrCiTZmMkD7jWA9UdZbNUygPjdmQBVPRCrLydReFlX9yg==
"@esbuild/linux-riscv64@0.16.10":
version "0.16.10"
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.16.10.tgz#3af4600adbd6c5a4a6f1da05771f4aa6774baab2"
integrity sha512-XMqtpjwzbmlar0BJIxmzu/RZ7EWlfVfH68Vadrva0Wj5UKOdKvqskuev2jY2oPV3aoQUyXwnMbMrFmloO2GfAw==
"@esbuild/linux-s390x@0.16.10":
version "0.16.10"
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.16.10.tgz#9e3377aaf0191a9d6628e806a279085ec4391f3e"
integrity sha512-fu7XtnoeRNFMx8DjK3gPWpFBDM2u5ba+FYwg27SjMJwKvJr4bDyKz5c+FLXLUSSAkMAt/UL+cUbEbra+rYtUgw==
"@esbuild/linux-x64@0.16.10":
version "0.16.10"
resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.10.tgz"
integrity sha512-61lcjVC/RldNNMUzQQdyCWjCxp9YLEQgIxErxU9XluX7juBdGKb0pvddS0vPNuCvotRbzijZ1pzII+26haWzbA==
"@esbuild/netbsd-x64@0.16.10":
version "0.16.10"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.16.10.tgz#ebac59e3986834af04bbafcee7b0c1f31cd477c6"
integrity sha512-JeZXCX3viSA9j4HqSoygjssdqYdfHd6yCFWyfSekLbz4Ef+D2EjvsN02ZQPwYl5a5gg/ehdHgegHhlfOFP0HCA==
"@esbuild/openbsd-x64@0.16.10":
version "0.16.10"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.16.10.tgz#9eaa6cac3b80db45090c0946e62de5b5689c61d1"
integrity sha512-3qpxQKuEVIIg8SebpXsp82OBrqjPV/OwNWmG+TnZDr3VGyChNnGMHccC1xkbxCHDQNnnXjxhMQNyHmdFJbmbRA==
"@esbuild/sunos-x64@0.16.10":
version "0.16.10"
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.16.10.tgz#31e5e4b814ef43d300e26511e486a4716a390d5f"
integrity sha512-z+q0xZ+et/7etz7WoMyXTHZ1rB8PMSNp/FOqURLJLOPb3GWJ2aj4oCqFCjPwEbW1rsT7JPpxeH/DwGAWk/I1Bg==
"@esbuild/win32-arm64@0.16.10":
version "0.16.10"
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.16.10.tgz#ca58472dc03ca79e6d03f8a31113979ff253d94f"
integrity sha512-+YYu5sbQ9npkNT9Dec+tn1F/kjg6SMgr6bfi/6FpXYZvCRfu2YFPZGb+3x8K30s8eRxFpoG4sGhiSUkr1xbHEw==
"@esbuild/win32-ia32@0.16.10":
version "0.16.10"
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.16.10.tgz#c572df2c65ab118feed0a5da5a4a193846d74e43"
integrity sha512-Aw7Fupk7XNehR1ftHGYwUteyJ2q+em/aE+fVU3YMTBN2V5A7Z4aVCSV+SvCp9HIIHZavPFBpbdP3VfjQpdf6Xg==
"@esbuild/win32-x64@0.16.10": "@esbuild/win32-x64@0.16.10":
version "0.16.10" version "0.16.10"
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.16.10.tgz#0e9c6a5e69c10d96aff2386b7ee9646138c2a831" resolved "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.10.tgz"
integrity sha512-qddWullt3sC1EIpfHvCRBq3H4g3L86DZpD6n8k2XFjFVyp01D++uNbN1hT/JRsHxTbyyemZcpwL5aRlJwc/zFw== integrity sha512-qddWullt3sC1EIpfHvCRBq3H4g3L86DZpD6n8k2XFjFVyp01D++uNbN1hT/JRsHxTbyyemZcpwL5aRlJwc/zFw==
"@firebase/analytics-compat@0.1.14": "@firebase/analytics-compat@0.1.14":
@@ -1081,7 +976,7 @@
"@firebase/util" "1.7.0" "@firebase/util" "1.7.0"
tslib "^2.1.0" tslib "^2.1.0"
"@firebase/app-compat@0.1.35": "@firebase/app-compat@0.1.35", "@firebase/app-compat@0.x":
version "0.1.35" version "0.1.35"
resolved "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.1.35.tgz" resolved "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.1.35.tgz"
integrity sha512-6ax9yXCPEBSREHxo+nCpSgSg01mGTvR4I7u/EHqVNNqG8uEWog7sUan3Y3vr3q3zH8t5BkXDGejOH9atF+XnAQ== integrity sha512-6ax9yXCPEBSREHxo+nCpSgSg01mGTvR4I7u/EHqVNNqG8uEWog7sUan3Y3vr3q3zH8t5BkXDGejOH9atF+XnAQ==
@@ -1092,12 +987,12 @@
"@firebase/util" "1.7.0" "@firebase/util" "1.7.0"
tslib "^2.1.0" tslib "^2.1.0"
"@firebase/app-types@0.8.0": "@firebase/app-types@0.8.0", "@firebase/app-types@0.x":
version "0.8.0" version "0.8.0"
resolved "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.8.0.tgz" resolved "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.8.0.tgz"
integrity sha512-Lec3VVquUwXPn2UReGSsfTxuMBVRmzGIwA/CJnF0LQuPgv9kOmXk9mVqsDMfHxHtqjai0n6wWHR2TqjdVV/bYA== integrity sha512-Lec3VVquUwXPn2UReGSsfTxuMBVRmzGIwA/CJnF0LQuPgv9kOmXk9mVqsDMfHxHtqjai0n6wWHR2TqjdVV/bYA==
"@firebase/app@0.8.0": "@firebase/app@0.8.0", "@firebase/app@0.x":
version "0.8.0" version "0.8.0"
resolved "https://registry.npmjs.org/@firebase/app/-/app-0.8.0.tgz" resolved "https://registry.npmjs.org/@firebase/app/-/app-0.8.0.tgz"
integrity sha512-9kZjhIDv4u4PlrCgcQVBA2u8BZHrP8rUWDltmCUi9BLHv0tltfxLMZODV5LeuAfCJKVp2dbIrpGHPxAaLLl/ww== integrity sha512-9kZjhIDv4u4PlrCgcQVBA2u8BZHrP8rUWDltmCUi9BLHv0tltfxLMZODV5LeuAfCJKVp2dbIrpGHPxAaLLl/ww==
@@ -1384,7 +1279,7 @@
node-fetch "2.6.7" node-fetch "2.6.7"
tslib "^2.1.0" tslib "^2.1.0"
"@firebase/util@1.7.0": "@firebase/util@1.7.0", "@firebase/util@1.x":
version "1.7.0" version "1.7.0"
resolved "https://registry.npmjs.org/@firebase/util/-/util-1.7.0.tgz" resolved "https://registry.npmjs.org/@firebase/util/-/util-1.7.0.tgz"
integrity sha512-n5g1WWd+E5IYQwtKxmTJDlhfT762mk/d7yigeh8QaS46cnvngwguOhNwlS8fniEJ7pAgyZ9v05OQMKdfMnws6g== integrity sha512-n5g1WWd+E5IYQwtKxmTJDlhfT762mk/d7yigeh8QaS46cnvngwguOhNwlS8fniEJ7pAgyZ9v05OQMKdfMnws6g==
@@ -1472,7 +1367,16 @@
"@jridgewell/set-array" "^1.0.0" "@jridgewell/set-array" "^1.0.0"
"@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/sourcemap-codec" "^1.4.10"
"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": "@jridgewell/gen-mapping@^0.3.0":
version "0.3.2"
resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz"
integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==
dependencies:
"@jridgewell/set-array" "^1.0.1"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@jridgewell/trace-mapping" "^0.3.9"
"@jridgewell/gen-mapping@^0.3.2":
version "0.3.2" version "0.3.2"
resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz" resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz"
integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==
@@ -1499,7 +1403,7 @@
"@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/gen-mapping" "^0.3.0"
"@jridgewell/trace-mapping" "^0.3.9" "@jridgewell/trace-mapping" "^0.3.9"
"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.13": "@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.13", "@jridgewell/sourcemap-codec@1.4.14":
version "1.4.14" version "1.4.14"
resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz" resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz"
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
@@ -1520,7 +1424,7 @@
"@nodelib/fs.stat" "2.0.5" "@nodelib/fs.stat" "2.0.5"
run-parallel "^1.1.9" run-parallel "^1.1.9"
"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": "@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5":
version "2.0.5" version "2.0.5"
resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
@@ -1655,22 +1559,22 @@
magic-string "^0.25.0" magic-string "^0.25.0"
string.prototype.matchall "^4.0.6" string.prototype.matchall "^4.0.6"
"@types/estree@0.0.39":
version "0.0.39"
resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz"
integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
"@types/estree@^1.0.0": "@types/estree@^1.0.0":
version "1.0.0" version "1.0.0"
resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz" resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz"
integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ== integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==
"@types/estree@0.0.39":
version "0.0.39"
resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz"
integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
"@types/long@^4.0.1": "@types/long@^4.0.1":
version "4.0.2" version "4.0.2"
resolved "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz" resolved "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz"
integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==
"@types/node@*", "@types/node@>=12.12.47", "@types/node@>=13.7.0", "@types/node@^18.11.17": "@types/node@*", "@types/node@^18.11.17", "@types/node@>= 14", "@types/node@>=12.12.47", "@types/node@>=13.7.0":
version "18.11.18" version "18.11.18"
resolved "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz" resolved "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz"
integrity sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA== integrity sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==
@@ -1757,15 +1661,7 @@
estree-walker "^2.0.2" estree-walker "^2.0.2"
source-map "^0.6.1" source-map "^0.6.1"
"@vue/compiler-dom@3.2.40": "@vue/compiler-dom@^3.2.45", "@vue/compiler-dom@3.2.45":
version "3.2.40"
resolved "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.40.tgz"
integrity sha512-OZCNyYVC2LQJy4H7h0o28rtk+4v+HMQygRTpmibGoG9wZyomQiS5otU7qo3Wlq5UfHDw2RFwxb9BJgKjVpjrQw==
dependencies:
"@vue/compiler-core" "3.2.40"
"@vue/shared" "3.2.40"
"@vue/compiler-dom@3.2.45", "@vue/compiler-dom@^3.2.45":
version "3.2.45" version "3.2.45"
resolved "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.45.tgz" resolved "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.45.tgz"
integrity sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw== integrity sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw==
@@ -1773,21 +1669,13 @@
"@vue/compiler-core" "3.2.45" "@vue/compiler-core" "3.2.45"
"@vue/shared" "3.2.45" "@vue/shared" "3.2.45"
"@vue/compiler-sfc@3.2.40": "@vue/compiler-dom@3.2.40":
version "3.2.40" version "3.2.40"
resolved "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.40.tgz" resolved "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.40.tgz"
integrity sha512-tzqwniIN1fu1PDHC3CpqY/dPCfN/RN1thpBC+g69kJcrl7mbGiHKNwbA6kJ3XKKy8R6JLKqcpVugqN4HkeBFFg== integrity sha512-OZCNyYVC2LQJy4H7h0o28rtk+4v+HMQygRTpmibGoG9wZyomQiS5otU7qo3Wlq5UfHDw2RFwxb9BJgKjVpjrQw==
dependencies: dependencies:
"@babel/parser" "^7.16.4"
"@vue/compiler-core" "3.2.40" "@vue/compiler-core" "3.2.40"
"@vue/compiler-dom" "3.2.40"
"@vue/compiler-ssr" "3.2.40"
"@vue/reactivity-transform" "3.2.40"
"@vue/shared" "3.2.40" "@vue/shared" "3.2.40"
estree-walker "^2.0.2"
magic-string "^0.25.7"
postcss "^8.1.10"
source-map "^0.6.1"
"@vue/compiler-sfc@^3.2.45": "@vue/compiler-sfc@^3.2.45":
version "3.2.45" version "3.2.45"
@@ -1805,6 +1693,22 @@
postcss "^8.1.10" postcss "^8.1.10"
source-map "^0.6.1" source-map "^0.6.1"
"@vue/compiler-sfc@3.2.40":
version "3.2.40"
resolved "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.40.tgz"
integrity sha512-tzqwniIN1fu1PDHC3CpqY/dPCfN/RN1thpBC+g69kJcrl7mbGiHKNwbA6kJ3XKKy8R6JLKqcpVugqN4HkeBFFg==
dependencies:
"@babel/parser" "^7.16.4"
"@vue/compiler-core" "3.2.40"
"@vue/compiler-dom" "3.2.40"
"@vue/compiler-ssr" "3.2.40"
"@vue/reactivity-transform" "3.2.40"
"@vue/shared" "3.2.40"
estree-walker "^2.0.2"
magic-string "^0.25.7"
postcss "^8.1.10"
source-map "^0.6.1"
"@vue/compiler-ssr@3.2.40": "@vue/compiler-ssr@3.2.40":
version "3.2.40" version "3.2.40"
resolved "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.40.tgz" resolved "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.40.tgz"
@@ -1848,13 +1752,6 @@
estree-walker "^2.0.2" estree-walker "^2.0.2"
magic-string "^0.25.7" magic-string "^0.25.7"
"@vue/reactivity@3.2.40":
version "3.2.40"
resolved "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.40.tgz"
integrity sha512-N9qgGLlZmtUBMHF9xDT4EkD9RdXde1Xbveb+niWMXuHVWQP5BzgRmE3SFyUBBcyayG4y1lhoz+lphGRRxxK4RA==
dependencies:
"@vue/shared" "3.2.40"
"@vue/reactivity@^3.2.45": "@vue/reactivity@^3.2.45":
version "3.2.45" version "3.2.45"
resolved "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.45.tgz" resolved "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.45.tgz"
@@ -1862,6 +1759,13 @@
dependencies: dependencies:
"@vue/shared" "3.2.45" "@vue/shared" "3.2.45"
"@vue/reactivity@3.2.40":
version "3.2.40"
resolved "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.40.tgz"
integrity sha512-N9qgGLlZmtUBMHF9xDT4EkD9RdXde1Xbveb+niWMXuHVWQP5BzgRmE3SFyUBBcyayG4y1lhoz+lphGRRxxK4RA==
dependencies:
"@vue/shared" "3.2.40"
"@vue/runtime-core@3.2.40": "@vue/runtime-core@3.2.40":
version "3.2.40" version "3.2.40"
resolved "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.40.tgz" resolved "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.40.tgz"
@@ -1887,22 +1791,22 @@
"@vue/compiler-ssr" "3.2.40" "@vue/compiler-ssr" "3.2.40"
"@vue/shared" "3.2.40" "@vue/shared" "3.2.40"
"@vue/shared@^3.2.45", "@vue/shared@3.2.45":
version "3.2.45"
resolved "https://registry.npmjs.org/@vue/shared/-/shared-3.2.45.tgz"
integrity sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==
"@vue/shared@3.2.40": "@vue/shared@3.2.40":
version "3.2.40" version "3.2.40"
resolved "https://registry.npmjs.org/@vue/shared/-/shared-3.2.40.tgz" resolved "https://registry.npmjs.org/@vue/shared/-/shared-3.2.40.tgz"
integrity sha512-0PLQ6RUtZM0vO3teRfzGi4ltLUO5aO+kLgwh4Um3THSR03rpQWLTuRCkuO5A41ITzwdWeKdPHtSARuPkoo5pCQ== integrity sha512-0PLQ6RUtZM0vO3teRfzGi4ltLUO5aO+kLgwh4Um3THSR03rpQWLTuRCkuO5A41ITzwdWeKdPHtSARuPkoo5pCQ==
"@vue/shared@3.2.45", "@vue/shared@^3.2.45":
version "3.2.45"
resolved "https://registry.npmjs.org/@vue/shared/-/shared-3.2.45.tgz"
integrity sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==
acorn@^8.5.0: acorn@^8.5.0:
version "8.8.1" version "8.8.1"
resolved "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz" resolved "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz"
integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA== integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==
ajv@^8.6.0: ajv@^8.6.0, ajv@>=8:
version "8.11.2" version "8.11.2"
resolved "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz" resolved "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz"
integrity sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg== integrity sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==
@@ -2019,7 +1923,7 @@ braces@^3.0.2, braces@~3.0.2:
dependencies: dependencies:
fill-range "^7.0.1" fill-range "^7.0.1"
browserslist@^4.21.3, browserslist@^4.21.4: browserslist@^4.21.3, browserslist@^4.21.4, "browserslist@>= 4.21.0":
version "4.21.4" version "4.21.4"
resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz" resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz"
integrity sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw== integrity sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==
@@ -2048,9 +1952,9 @@ call-bind@^1.0.0, call-bind@^1.0.2:
get-intrinsic "^1.0.2" get-intrinsic "^1.0.2"
caniuse-lite@^1.0.30001400: caniuse-lite@^1.0.30001400:
version "1.0.30001441" version "1.0.30001503"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001441.tgz" resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001503.tgz"
integrity sha512-OyxRR4Vof59I3yGWXws6i908EtGbMzVUi3ganaZQHmydk1iwDhRnvaPG2WaR0KcqrDFKrxVZHULT396LEPhXfg== integrity sha512-Sf9NiF+wZxPfzv8Z3iS0rXM1Do+iOy2Lxvib38glFX+08TCYYYGR5fRJXk4d77C4AYwhUjgYgMsMudbh2TqCKw==
chalk@^2.0.0: chalk@^2.0.0:
version "2.4.2" version "2.4.2"
@@ -2107,16 +2011,16 @@ color-convert@^2.0.1:
dependencies: dependencies:
color-name "~1.1.4" color-name "~1.1.4"
color-name@1.1.3:
version "1.1.3"
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz"
integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
color-name@~1.1.4: color-name@~1.1.4:
version "1.1.4" version "1.1.4"
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
color-name@1.1.3:
version "1.1.3"
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz"
integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
combined-stream@^1.0.8: combined-stream@^1.0.8:
version "1.0.8" version "1.0.8"
resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz"
@@ -2442,11 +2346,6 @@ fs.realpath@^1.0.0:
resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
fsevents@~2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
function-bind@^1.1.1: function-bind@^1.1.1:
version "1.1.1" version "1.1.1"
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz"
@@ -2591,7 +2490,7 @@ http-parser-js@>=0.5.1:
resolved "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz" resolved "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz"
integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==
idb@7.0.1, idb@^7.0.1: idb@^7.0.1, idb@7.0.1:
version "7.0.1" version "7.0.1"
resolved "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz" resolved "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz"
integrity sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg== integrity sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==
@@ -2614,7 +2513,7 @@ inflight@^1.0.4:
once "^1.3.0" once "^1.3.0"
wrappy "1" wrappy "1"
inherits@2, inherits@~2.0.3: inherits@~2.0.3, inherits@2:
version "2.0.4" version "2.0.4"
resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -2937,7 +2836,14 @@ minimatch@^3.0.4, minimatch@^3.1.1:
dependencies: dependencies:
brace-expansion "^1.1.7" brace-expansion "^1.1.7"
minimatch@^5.0.1, minimatch@^5.1.1: minimatch@^5.0.1:
version "5.1.2"
resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz"
integrity sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==
dependencies:
brace-expansion "^2.0.1"
minimatch@^5.1.1:
version "5.1.2" version "5.1.2"
resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz" resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz"
integrity sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg== integrity sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==
@@ -3237,14 +3143,14 @@ rollup-plugin-terser@^7.0.0:
serialize-javascript "^4.0.0" serialize-javascript "^4.0.0"
terser "^5.0.0" terser "^5.0.0"
rollup@^2.43.1: "rollup@^1.20.0 || ^2.0.0", rollup@^1.20.0||^2.0.0, rollup@^2.0.0, rollup@^2.43.1:
version "2.79.1" version "2.79.1"
resolved "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz" resolved "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz"
integrity sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw== integrity sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==
optionalDependencies: optionalDependencies:
fsevents "~2.3.2" fsevents "~2.3.2"
rollup@^3.7.0, rollup@^3.7.2: rollup@^1.20.0||^2.0.0||^3.0.0, rollup@^3.7.0, rollup@^3.7.2:
version "3.7.5" version "3.7.5"
resolved "https://registry.npmjs.org/rollup/-/rollup-3.7.5.tgz" resolved "https://registry.npmjs.org/rollup/-/rollup-3.7.5.tgz"
integrity sha512-z0ZbqHBtS/et2EEUKMrAl2CoSdwN7ZPzL17UMiKN9RjjqHShTlv7F9J6ZJZJNREYjBh3TvBrdfjkFDIXFNeuiQ== integrity sha512-z0ZbqHBtS/et2EEUKMrAl2CoSdwN7ZPzL17UMiKN9RjjqHShTlv7F9J6ZJZJNREYjBh3TvBrdfjkFDIXFNeuiQ==
@@ -3258,16 +3164,16 @@ run-parallel@^1.1.9:
dependencies: dependencies:
queue-microtask "^1.2.2" queue-microtask "^1.2.2"
safe-buffer@>=5.1.0:
version "5.2.1"
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
safe-buffer@^5.1.0, safe-buffer@~5.1.0, safe-buffer@~5.1.1: safe-buffer@^5.1.0, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2" version "5.1.2"
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
safe-buffer@>=5.1.0:
version "5.2.1"
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
safe-regex-test@^1.0.0: safe-regex-test@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz" resolved "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz"
@@ -3277,7 +3183,7 @@ safe-regex-test@^1.0.0:
get-intrinsic "^1.1.3" get-intrinsic "^1.1.3"
is-regex "^1.1.4" is-regex "^1.1.4"
sass@^1.53.0: sass@*, sass@^1.53.0:
version "1.55.0" version "1.55.0"
resolved "https://registry.npmjs.org/sass/-/sass-1.55.0.tgz" resolved "https://registry.npmjs.org/sass/-/sass-1.55.0.tgz"
integrity sha512-Pk+PMy7OGLs9WaxZGJMn7S96dvlyVBwwtToX895WmCpAOr5YiJYEUJfiJidMuKb613z2xNWcXCHEuOvjZbqC6A== integrity sha512-Pk+PMy7OGLs9WaxZGJMn7S96dvlyVBwwtToX895WmCpAOr5YiJYEUJfiJidMuKb613z2xNWcXCHEuOvjZbqC6A==
@@ -3339,7 +3245,7 @@ socket.io-parser@~4.2.0:
"@socket.io/component-emitter" "~3.1.0" "@socket.io/component-emitter" "~3.1.0"
debug "~4.3.1" debug "~4.3.1"
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2: source-map-js@^1.0.2, "source-map-js@>=0.6.2 <2.0.0":
version "1.0.2" version "1.0.2"
resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz" resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz"
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
@@ -3352,7 +3258,7 @@ source-map-support@~0.5.20:
buffer-from "^1.0.0" buffer-from "^1.0.0"
source-map "^0.6.0" source-map "^0.6.0"
source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1: source-map@^0.6.0, source-map@^0.6.1, source-map@0.6.1:
version "0.6.1" version "0.6.1"
resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
@@ -3369,6 +3275,13 @@ sourcemap-codec@^1.4.8:
resolved "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz" resolved "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz"
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
string_decoder@~1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz"
integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
dependencies:
safe-buffer "~5.1.0"
string-width@^4.1.0, string-width@^4.2.0: string-width@^4.1.0, string-width@^4.2.0:
version "4.2.3" version "4.2.3"
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
@@ -3410,13 +3323,6 @@ string.prototype.trimstart@^1.0.6:
define-properties "^1.1.4" define-properties "^1.1.4"
es-abstract "^1.20.4" es-abstract "^1.20.4"
string_decoder@~1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz"
integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
dependencies:
safe-buffer "~5.1.0"
stringify-object@^3.3.0: stringify-object@^3.3.0:
version "3.3.0" version "3.3.0"
resolved "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz" resolved "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz"
@@ -3445,7 +3351,14 @@ supports-color@^5.3.0:
dependencies: dependencies:
has-flag "^3.0.0" has-flag "^3.0.0"
supports-color@^7.0.0, supports-color@^7.1.0: supports-color@^7.0.0:
version "7.2.0"
resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz"
integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
dependencies:
has-flag "^4.0.0"
supports-color@^7.1.0:
version "7.2.0" version "7.2.0"
resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz"
integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
@@ -3472,7 +3385,7 @@ tempy@^0.6.0:
type-fest "^0.16.0" type-fest "^0.16.0"
unique-string "^2.0.0" unique-string "^2.0.0"
terser@^5.0.0: terser@^5.0.0, terser@^5.4.0:
version "5.16.1" version "5.16.1"
resolved "https://registry.npmjs.org/terser/-/terser-5.16.1.tgz" resolved "https://registry.npmjs.org/terser/-/terser-5.16.1.tgz"
integrity sha512-xvQfyfA1ayT0qdK47zskQgRZeWLoOQ8JQ6mIgRGVNwZKdQMU+5FkCBjmv4QjcrTzyZquRw2FVtlJSRUmMKQslw== integrity sha512-xvQfyfA1ayT0qdK47zskQgRZeWLoOQ8JQ6mIgRGVNwZKdQMU+5FkCBjmv4QjcrTzyZquRw2FVtlJSRUmMKQslw==
@@ -3523,7 +3436,7 @@ type-fest@^0.16.0:
resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz" resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz"
integrity sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg== integrity sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==
typescript@^4.9.4: typescript@*, typescript@^4.9.4, typescript@>=4.4.4:
version "4.9.4" version "4.9.4"
resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz" resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz"
integrity sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg== integrity sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==
@@ -3611,7 +3524,7 @@ vite-plugin-pwa@^0.14.0:
workbox-build "^6.5.4" workbox-build "^6.5.4"
workbox-window "^6.5.4" workbox-window "^6.5.4"
vite@^4.0.3: "vite@^3.1.0 || ^4.0.0", vite@^4.0.0, vite@^4.0.3:
version "4.0.3" version "4.0.3"
resolved "https://registry.npmjs.org/vite/-/vite-4.0.3.tgz" resolved "https://registry.npmjs.org/vite/-/vite-4.0.3.tgz"
integrity sha512-HvuNv1RdE7deIfQb8mPk51UKjqptO/4RXZ5yXSAvurd5xOckwS/gg8h9Tky3uSbnjYTgUm0hVCet1cyhKd73ZA== integrity sha512-HvuNv1RdE7deIfQb8mPk51UKjqptO/4RXZ5yXSAvurd5xOckwS/gg8h9Tky3uSbnjYTgUm0hVCet1cyhKd73ZA==
@@ -3661,7 +3574,7 @@ vue-tsc@^1.0.18:
"@volar/vue-language-core" "1.0.18" "@volar/vue-language-core" "1.0.18"
"@volar/vue-typescript" "1.0.18" "@volar/vue-typescript" "1.0.18"
vue@^3.2.37: "vue@^2.6.14 || ^3.2.0", vue@^3.0.0, "vue@^3.0.0-0 || ^2.6.0", vue@^3.2.0, vue@^3.2.25, vue@^3.2.37, vue@3.2.40:
version "3.2.40" version "3.2.40"
resolved "https://registry.npmjs.org/vue/-/vue-3.2.40.tgz" resolved "https://registry.npmjs.org/vue/-/vue-3.2.40.tgz"
integrity sha512-1mGHulzUbl2Nk3pfvI5aXYYyJUs1nm4kyvuz38u4xlQkLUn1i2R7nDbI4TufECmY8v1qNBHYy62bCaM+3cHP2A== integrity sha512-1mGHulzUbl2Nk3pfvI5aXYYyJUs1nm4kyvuz38u4xlQkLUn1i2R7nDbI4TufECmY8v1qNBHYy62bCaM+3cHP2A==
@@ -3874,7 +3787,7 @@ workbox-sw@6.5.4:
resolved "https://registry.npmjs.org/workbox-sw/-/workbox-sw-6.5.4.tgz" resolved "https://registry.npmjs.org/workbox-sw/-/workbox-sw-6.5.4.tgz"
integrity sha512-vo2RQo7DILVRoH5LjGqw3nphavEjK4Qk+FenXeUsknKn14eCNedHOXWbmnvP4ipKhlE35pvJ4yl4YYf6YsJArA== integrity sha512-vo2RQo7DILVRoH5LjGqw3nphavEjK4Qk+FenXeUsknKn14eCNedHOXWbmnvP4ipKhlE35pvJ4yl4YYf6YsJArA==
workbox-window@6.5.4, workbox-window@^6.5.4: workbox-window@^6.5.4, workbox-window@6.5.4:
version "6.5.4" version "6.5.4"
resolved "https://registry.npmjs.org/workbox-window/-/workbox-window-6.5.4.tgz" resolved "https://registry.npmjs.org/workbox-window/-/workbox-window-6.5.4.tgz"
integrity sha512-HnLZJDwYBE+hpG25AQBO8RUWBJRaCsI9ksQJEp3aCOFCaG5kqaToAYXFRAHxzRluM2cQbGzdQF5rjKPWPA1fug== integrity sha512-HnLZJDwYBE+hpG25AQBO8RUWBJRaCsI9ksQJEp3aCOFCaG5kqaToAYXFRAHxzRluM2cQbGzdQF5rjKPWPA1fug==