Compare commits

..

26 Commits

Author SHA1 Message Date
Spythere 3ef27e1d69 Merge pull request #85 from Spythere/development
Wersja 1.23.1
2024-04-01 13:00:28 +02:00
Spythere f53993c717 hotfix 2024-03-31 21:55:33 +02:00
Spythere 235c16e30f train modal 2024-03-31 21:37:14 +02:00
Spythere c3533f07ad literówka 2024-03-30 17:48:34 +01:00
Spythere d05579c5ee popupy 2024-03-30 13:24:39 +01:00
Spythere c8f53c2f06 hotfixy designu 2024-03-30 00:18:54 +01:00
Spythere b44f88ebcd src miniaturek 2024-03-29 23:37:26 +01:00
Spythere 7805d1350c responsywność 2024-03-29 23:35:56 +01:00
Spythere b17bd19433 zmiana położenia przycisku RJ ONLINE w dzienniku 2024-03-29 23:23:14 +01:00
Spythere c12a6cbacd zmiana rozłożenia elementów w modalu aktywnego pociągu 2024-03-29 23:21:15 +01:00
Spythere ba650238db poprawki rozmieszczenia popupu 2024-03-29 23:04:08 +01:00
Spythere d5ec9919e2 update modal (wip) 2024-03-29 20:34:56 +01:00
Spythere 20cd393e05 Merge pull request #84 from Spythere/development
Wersja 1.23.0
2024-03-24 01:30:03 +01:00
Spythere 31e65c09d6 hotfix: podgląd pojazdów 2024-03-24 00:05:39 +01:00
Spythere fb2348e774 hotfixy designu 2024-03-23 23:55:18 +01:00
Spythere 1ec75bda70 poprawki do popupów 2024-03-23 16:47:57 +01:00
Spythere 6b6b837dde bump: v1.23.0 2024-03-23 00:01:15 +01:00
Spythere 66a02d76bd dodano odnośnik do dziennika RJ maszynisty 2024-03-23 00:00:52 +01:00
Spythere c7162dbd14 dodano dymki kontekstowe oraz podgląd pojazdu 2024-03-22 23:41:43 +01:00
Spythere 1cfe073bab Merge pull request #83 from Spythere/development
Wersja 1.22.3
2024-03-17 18:38:27 +01:00
Spythere e3b72c81ea bump: 1.22.3 2024-03-17 18:37:58 +01:00
Spythere 5552995564 fix: duplikujące się aktywne RJ scenerii 2024-03-17 18:37:45 +01:00
Spythere 623d5dd2ce fix: RJ scenerii offline 2024-03-17 17:33:19 +01:00
Spythere 6992b998a8 Merge pull request #82 from Spythere/development
Wersja 1.22.2
2024-03-17 16:47:52 +01:00
Spythere 669975c68e hotfixy 2024-03-17 16:42:35 +01:00
Spythere 084823de44 fix pobierania danych 2024-03-16 22:13:38 +01:00
25 changed files with 682 additions and 188 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "stacjownik", "name": "stacjownik",
"version": "1.22.1", "version": "1.23.1",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
+40 -13
View File
@@ -1,8 +1,10 @@
<template> <template>
<div class="app_container" v-cloak> <div class="app_container" v-cloak>
<PopUp />
<transition name="modal-anim"> <transition name="modal-anim">
<keep-alive> <keep-alive>
<TrainModal v-if="store.chosenModalTrainId" /> <TrainModal />
</keep-alive> </keep-alive>
</transition> </transition>
@@ -32,19 +34,19 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, watch } from 'vue'; import { defineComponent } from 'vue';
import axios from 'axios'; import axios from 'axios';
import { version } from '.././package.json'; import { version } from '.././package.json';
import Clock from './components/App/Clock.vue';
import { useMainStore } from './store/mainStore'; import { useMainStore } from './store/mainStore';
import popupMixin from './mixins/popupMixin';
import Clock from './components/App/Clock.vue';
import StatusIndicator from './components/App/StatusIndicator.vue'; import StatusIndicator from './components/App/StatusIndicator.vue';
import AppHeader from './components/App/AppHeader.vue'; import AppHeader from './components/App/AppHeader.vue';
import TrainModal from './components/TrainsView/TrainModal.vue'; import TrainModal from './components/TrainsView/TrainModal.vue';
import StorageManager from './managers/storageManager'; import StorageManager from './managers/storageManager';
import PopUp from './components/PopUp/PopUp.vue';
import { useApiStore } from './store/apiStore'; import { useApiStore } from './store/apiStore';
import { Status } from './typings/common'; import { Status } from './typings/common';
@@ -55,9 +57,12 @@ export default defineComponent({
Clock, Clock,
StatusIndicator, StatusIndicator,
AppHeader, AppHeader,
TrainModal TrainModal,
PopUp
}, },
mixins: [popupMixin],
data: () => ({ data: () => ({
VERSION: version, VERSION: version,
store: useMainStore(), store: useMainStore(),
@@ -73,13 +78,13 @@ export default defineComponent({
}, },
async mounted() { async mounted() {
watch( window.addEventListener('focus', () => {
() => this.store.blockScroll, if (Date.now() - this.apiStore.lastFetchData.getTime() < 15000) return;
(value) => {
if (value) document.body.classList.add('no-scroll'); this.apiStore.fetchActiveData();
else document.body.classList.remove('no-scroll'); });
}
); window.addEventListener('mousemove', (e: MouseEvent) => this.handlePopUpEvents(e));
}, },
methods: { methods: {
@@ -132,6 +137,27 @@ export default defineComponent({
this.apiStore.connectToAPI(); this.apiStore.connectToAPI();
}, },
handlePopUpEvents(e: MouseEvent) {
const targetEl = e
.composedPath()
.find((p) => p instanceof HTMLElement && p.getAttribute('data-popup-key'));
if (!targetEl || !(targetEl instanceof HTMLElement)) {
if (this.store.popUpData.key != null) this.hidePopUp();
return;
}
const popupComponentKey = targetEl.getAttribute('data-popup-key');
const popupContent = targetEl.getAttribute('data-popup-content');
if (popupComponentKey && popupContent) this.showPopUp(e, popupComponentKey, popupContent);
else if (this.store.popUpData.key != null) this.hidePopUp();
this.store.mousePos.x = e.pageX;
this.store.mousePos.y = e.pageY;
},
changeLang(lang: string) { changeLang(lang: string) {
this.$i18n.locale = lang; this.$i18n.locale = lang;
this.currentLang = lang; this.currentLang = lang;
@@ -211,6 +237,7 @@ export default defineComponent({
grid-template-columns: 100%; grid-template-columns: 100%;
min-height: 100vh; min-height: 100vh;
overflow: hidden;
} }
.app_main { .app_main {
+93 -8
View File
@@ -1,15 +1,36 @@
<template> <template>
<AnimatedModal :is-open="mainStore.isNewUpdate" @toggle-modal="toggleModal"> <AnimatedModal :is-open="mainStore.isNewUpdate" @toggle-modal="toggleModal">
<div class="modal_content"> <div class="modal_content">
<h1 class="header">Aktualizacja Stacjownika</h1> <div>
<h2>wersja {{ version }}</h2> <h1 style="margin-bottom: 0.5em">{{ $t('update.title') }}</h1>
<h2 class="text--primary">{{ $t('update.version', [version]) }}</h2>
<hr class="separator" />
</div>
<b>Co nowego?</b> <div class="features-list">
<p> <h2>Nowości i zmiany:</h2>
<ul> <ul>
<li>test</li> <li v-for="content in localeChangesArray" :key="content">{{ content }}</li>
</ul> </ul>
</p> </div>
<div class="modal_actions">
<button class="btn--action">Przyjąłem!</button>
<p>Ten changelog będzie zawsze dostępny po kliknięciu numeru wersji w stopce strony!</p>
<!-- <div class="actions-checkboxes">
<label>
<input type="checkbox" />
<span>nie pokazuj dla przyszłych aktualizacji</span>
</label>
<label>
<input type="checkbox" />
<span>nie pokazuj dla przyszłych aktualizacji</span>
</label>
</div> -->
</div>
</div> </div>
</AnimatedModal> </AnimatedModal>
</template> </template>
@@ -30,6 +51,12 @@ export default defineComponent({
}; };
}, },
computed: {
localeChangesArray() {
return this.$t('update.content').split('\n');
}
},
methods: { methods: {
toggleModal(value: boolean) { toggleModal(value: boolean) {
this.$emit('toggleModal', value); this.$emit('toggleModal', value);
@@ -40,9 +67,67 @@ export default defineComponent({
<style lang="scss" scoped> <style lang="scss" scoped>
.modal_content { .modal_content {
font-size: 1.2em;
text-align: center; text-align: center;
padding: 1em; padding: 1em;
height: 80vh; height: 80vh;
min-height: 550px; min-height: 550px;
display: grid;
grid-template-rows: auto 1fr auto;
gap: 0.5em;
}
hr.separator {
margin: 0.5em 0;
padding: 0;
height: 3px;
background-color: #fff;
}
.features-list {
margin-top: 0.5em;
overflow: auto;
ul {
text-align: left;
list-style: '\21D2 ';
padding: 1em;
}
li {
margin: 0.5em 0;
}
}
.modal_actions {
display: flex;
flex-wrap: wrap;
gap: 0.5em;
button {
font-weight: bold;
padding: 0.35em;
}
p {
font-size: 0.9em;
}
}
.actions-checkboxes {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 1em;
label {
font-size: 0.9em;
}
label > input {
margin-right: 0.5em;
}
} }
</style> </style>
+1 -1
View File
@@ -84,7 +84,7 @@ export default defineComponent({
regionList() { regionList() {
return regionsJSON.map((region) => { return regionsJSON.map((region) => {
const regionStationCount = this.store.activeSceneryList.filter( const regionStationCount = this.store.activeSceneryList.filter(
(scenery) => scenery.region == region.id (scenery) => scenery.region == region.id && scenery.dispatcherId != -1
).length; ).length;
const regionTrainCount = const regionTrainCount =
+23 -3
View File
@@ -7,11 +7,12 @@
</p> </p>
<img <img
class="traction-only"
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${computedStockList[0].split(':')[0]}${ :src="`https://rj.td2.info.pl/dist/img/thumbnails/${computedStockList[0].split(':')[0]}${
/^EN/.test(computedStockList[0]) ? 'rb' : '' /^EN/.test(computedStockList[0]) ? 'rb' : ''
}.png`" }.png`"
@error="onImageError($event, computedStockList[0])" @error="onImageError($event, computedStockList[0])"
width="400" width="300"
height="60" height="60"
/> />
</div> </div>
@@ -25,39 +26,53 @@
<span> <span>
<img <img
:data-mouseover="stockName"
data-popup-key="VehiclePreviewPopUp"
:data-popup-content="stockName.split(':')[0]"
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}${ :src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}${
/^EN/.test(stockName) ? 'rb' : '' /^EN/.test(stockName) ? 'rb' : ''
}.png`" }.png`"
@error="onImageError($event, stockName)" @error="onImageError($event, stockName)"
@click.stop="() => {}"
width="400" width="400"
height="60" height="60"
/> />
<!-- /// Manualne dodawanie miniaturek członów dla kibelków /// --> <!-- /// Manualne dodawanie miniaturek członów dla kibelków /// -->
<img <img
:data-mouseover="stockName"
data-popup-key="VehiclePreviewPopUp"
:data-popup-content="stockName.split(':')[0]"
v-if="/^(EN|2EN)/.test(stockName)" v-if="/^(EN|2EN)/.test(stockName)"
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}s.png`" :src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}s.png`"
@error=" @error="
(event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-s.png') (event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-s.png')
" "
@click.stop="() => {}"
/> />
<img <img
class="train-thumbnail" :data-mouseover="stockName"
data-popup-key="VehiclePreviewPopUp"
:data-popup-content="stockName.split(':')[0]"
v-if="/^EN71/.test(stockName)" v-if="/^EN71/.test(stockName)"
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}s.png`" :src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}s.png`"
@error=" @error="
(event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-s.png') (event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-s.png')
" "
@click.stop="() => {}"
/> />
<img <img
class="train-thumbnail" :data-mouseover="stockName"
data-popup-key="VehiclePreviewPopUp"
:data-popup-content="stockName.split(':')[0]"
v-if="/^(EN|2EN)/.test(stockName)" v-if="/^(EN|2EN)/.test(stockName)"
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}ra.png`" :src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}ra.png`"
@error=" @error="
(event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-ra.png') (event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-ra.png')
" "
@click.stop="() => {}"
/> />
<!-- /// --> <!-- /// -->
</span> </span>
@@ -139,6 +154,7 @@ export default defineComponent({
ul > li > span { ul > li > span {
display: flex; display: flex;
align-items: flex-end; align-items: flex-end;
cursor: crosshair;
} }
img { img {
@@ -147,6 +163,10 @@ img {
height: auto; height: auto;
} }
img.traction-only {
max-width: 100%;
}
p { p {
text-align: center; text-align: center;
color: #aaa; color: #aaa;
@@ -1,11 +1,6 @@
<template> <template>
<div class="item-general"> <div class="item-general">
<span <span class="general-train">
class="general-train"
tabindex="0"
@click.stop="showTimetable(timetable, $event.currentTarget)"
@keydown.enter="showTimetable(timetable, $event.currentTarget)"
>
<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="badges" v-if="timetable.skr || timetable.twr">
@@ -66,6 +61,15 @@
: `${$t('journal.timetable-abandoned')} ${localeTime(timetable.endDate, $i18n.locale)}` : `${$t('journal.timetable-abandoned')} ${localeTime(timetable.endDate, $i18n.locale)}`
}} }}
</b> </b>
<button
v-if="timetable.terminated == false"
class="btn--image btn--action btn-timetable"
@click.stop="showTimetable(timetable, $event.currentTarget)"
>
<img src="/images/icon-train.svg" alt="" />
{{ $t('journal.timetable-online-button') }}
</button>
</span> </span>
</div> </div>
</template> </template>
@@ -140,12 +144,24 @@ export default defineComponent({
} }
.general-train { .general-train {
cursor: pointer;
display: flex; display: flex;
flex-wrap: wrap;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
flex-wrap: wrap;
gap: 0.25em; gap: 0.25em;
cursor: pointer;
line-height: 2;
}
.btn-timetable {
display: inline-block;
padding: 0.1em 0.4em;
margin-left: 0.5em;
img {
vertical-align: top;
}
} }
@include smallScreen { @include smallScreen {
+39
View File
@@ -0,0 +1,39 @@
<template>
<div class="popup-content">
<img src="/images/icon-diamond.svg" alt="" />
<span>{{ store.popUpData.content }}</span>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { useMainStore } from '../../store/mainStore';
export default defineComponent({
data() {
return {
store: useMainStore()
};
}
});
</script>
<style lang="scss" scoped>
.popup-content {
gap: 0.5em;
padding: 0.5em;
border-radius: 0.25em;
width: 100%;
background-color: #333;
box-shadow: 0 0 10px 2px #aaa;
}
img {
vertical-align: middle;
height: 1em;
margin-right: 0.5em;
}
</style>
+71
View File
@@ -0,0 +1,71 @@
<template>
<div class="popup" v-show="store.popUpData.key" ref="preview">
<component v-if="store.popUpData.key" :is="store.popUpData.key" />
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import DonatorPopUp from './DonatorPopUp.vue';
import TrainCommentsPopUp from './TrainCommentsPopUp.vue';
import VehiclePreviewPopUp from './VehiclePreviewPopUp.vue';
import { useMainStore } from '../../store/mainStore';
export default defineComponent({
components: { DonatorPopUp, TrainCommentsPopUp, VehiclePreviewPopUp },
data() {
return {
store: useMainStore()
};
},
watch: {
'store.mousePos': {
deep: true,
handler(val: typeof this.store.mousePos) {
this.$nextTick(() => {
const previewEl = this.$refs['preview'] as HTMLElement;
const clientWidth = document.body.clientWidth;
const boxWidth = previewEl.getBoundingClientRect().width;
let translateX = '0px',
translateY = '30px';
if (clientWidth < 500) {
previewEl.style.left = '50%';
translateX = '-50%';
} else if (val.x <= boxWidth / 2) {
previewEl.style.left = '0';
translateX = '0px';
} else if (val.x >= clientWidth - boxWidth / 2) {
previewEl.style.left = '100%';
translateX = '-100%';
} else {
previewEl.style.left = `${val.x}px`;
translateX = '-50%';
}
previewEl.style.top = `${val.y}px`;
const isOutside =
val.y + previewEl.getBoundingClientRect().height + 30 >=
window.innerHeight + window.scrollY;
if (isOutside) translateY = 'calc(-100% - 30px)';
previewEl.style.transform = `translate(${translateX}, ${translateY})`;
});
}
}
}
});
</script>
<style lang="scss" scoped>
.popup {
position: absolute;
z-index: 250;
max-width: 400px;
text-align: center;
}
</style>
@@ -0,0 +1,38 @@
<template>
<div class="popup-content">
<span>{{ store.popUpData.content }}</span>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { useMainStore } from '../../store/mainStore';
export default defineComponent({
data() {
return {
store: useMainStore()
};
}
});
</script>
<style lang="scss" scoped>
.popup-content {
display: flex;
justify-content: center;
align-items: center;
gap: 0.5em;
padding: 0.25em 0.5em;
border-radius: 0.25em;
width: 100%;
background-color: #333;
box-shadow: 0 0 5px 2px #aaa;
}
img {
height: 1em;
}
</style>
@@ -0,0 +1,85 @@
<template>
<div class="popup-content">
<div v-if="imageState == 'loading'" class="loading-info">
{{ $t('vehicle-preview.loading') }}
</div>
<div v-if="imageState == 'error'">{{ $t('vehicle-preview.error') }}</div>
<img
v-if="store.popUpData.key"
@load="onImageLoad"
@error="onImageError"
width="300"
height="176"
class="rounded-md w-full h-auto"
:src="`https://static.spythere.eu/images/${store.popUpData.content}--300px.jpg`"
/>
<div class="vehicle-name" v-if="imageState != 'error'">
{{ store.popUpData.content.replace(/_/g, ' ') }}
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { useMainStore } from '../../store/mainStore';
export default defineComponent({
data() {
return {
store: useMainStore(),
imageState: 'loading'
};
},
mounted() {
this.imageState = 'loading';
},
methods: {
onImageLoad() {
this.imageState = 'loaded';
},
onImageError(e: Event) {
this.imageState = 'error';
(e.target as HTMLElement).style.display = 'none';
}
}
});
</script>
<style lang="scss" scoped>
.popup-content {
// min-w-[300px] min-h-[200px] p-2 bg-slate-800 rounded-md
width: 300px;
min-height: 200px;
background-color: #333;
box-shadow: 0 0 10px 2px #aaa;
padding: 0.5em;
border-radius: 0.5em;
}
.loading-info {
position: absolute;
left: 50%;
transform: translateX(-50%);
}
img {
width: 100%;
height: auto;
}
.vehicle-name {
text-align: center;
margin-top: 0.5em;
color: #ccc;
text-wrap: wrap;
}
</style>
@@ -74,7 +74,7 @@
class="timetable-item" class="timetable-item"
v-else v-else
v-for="scheduledTrain in computedScheduledTrains" v-for="scheduledTrain in computedScheduledTrains"
:key="scheduledTrain.trainId" :key="scheduledTrain.trainId + scheduledTrain.stopInfo.arrivalTimestamp"
tabindex="0" tabindex="0"
@click.prevent.stop="selectModalTrain(scheduledTrain.trainId, $event.currentTarget)" @click.prevent.stop="selectModalTrain(scheduledTrain.trainId, $event.currentTarget)"
@keydown.enter.prevent="selectModalTrain(scheduledTrain.trainId, $event.currentTarget)" @keydown.enter.prevent="selectModalTrain(scheduledTrain.trainId, $event.currentTarget)"
+6 -2
View File
@@ -119,8 +119,9 @@
<span v-if="station.onlineInfo?.dispatcherName"> <span v-if="station.onlineInfo?.dispatcherName">
<b <b
v-if="apiStore.donatorsData.includes(station.onlineInfo.dispatcherName)" v-if="apiStore.donatorsData.includes(station.onlineInfo.dispatcherName)"
:title="$t('donations.dispatcher-message')"
@click.stop="openDonationModal" @click.stop="openDonationModal"
data-popup-key="DonatorPopUp"
:data-popup-content="$t('donations.dispatcher-message')"
> >
<img src="/images/icon-diamond.svg" alt="" /> <img src="/images/icon-diamond.svg" alt="" />
{{ station.onlineInfo.dispatcherName }} {{ station.onlineInfo.dispatcherName }}
@@ -311,6 +312,7 @@ import { HeadIdsTypes, headIconsIds, headIds } from '../../scripts/data/stationH
import StationStatusBadge from '../Global/StationStatusBadge.vue'; import StationStatusBadge from '../Global/StationStatusBadge.vue';
import { Status } from '../../typings/common'; import { Status } from '../../typings/common';
import { useApiStore } from '../../store/apiStore'; import { useApiStore } from '../../store/apiStore';
import popupMixin from '../../mixins/popupMixin';
export default defineComponent({ export default defineComponent({
props: { props: {
@@ -322,7 +324,7 @@ export default defineComponent({
emits: ['toggleDonationModal'], emits: ['toggleDonationModal'],
components: { Loading, StationStatusBadge }, components: { Loading, StationStatusBadge },
mixins: [styleMixin, dateMixin, stationInfoMixin], mixins: [styleMixin, dateMixin, stationInfoMixin, popupMixin],
data: () => ({ data: () => ({
headIconsIds, headIconsIds,
@@ -339,6 +341,7 @@ export default defineComponent({
setup() { setup() {
const mainStore = useMainStore(); const mainStore = useMainStore();
const apiStore = useApiStore(); const apiStore = useApiStore();
const stationFiltersStore = useStationFiltersStore(); const stationFiltersStore = useStationFiltersStore();
return { return {
@@ -368,6 +371,7 @@ export default defineComponent({
openDonationModal(e: Event) { openDonationModal(e: Event) {
this.$emit('toggleDonationModal', true); this.$emit('toggleDonationModal', true);
this.mainStore.modalLastClickedTarget = e.target; this.mainStore.modalLastClickedTarget = e.target;
this.hidePopUp();
}, },
openForumSite(e: Event, url: string | undefined) { openForumSite(e: Event, url: string | undefined) {
+155 -82
View File
@@ -1,60 +1,86 @@
<template> <template>
<div class="train-info"> <div class="train-info" :data-extended="extended">
<section class="train-general"> <section class="train-general">
<div class="general-info"> <div class="general-top-bar">
<b class="warning-timeout" v-if="train.isTimeout" :title="$t('trains.timeout')">?</b> <div>
<span class="timetable-id" v-if="train.timetableData"> <b class="warning-timeout" v-if="train.isTimeout" :title="$t('trains.timeout')">?</b>
#{{ train.timetableData.timetableId }} <span class="timetable-id" v-if="train.timetableData">
</span> #{{ train.timetableData.timetableId }}
<span
class="timetable-warnings"
v-if="train.timetableData?.TWR || train.timetableData?.SKR"
>
<span class="train-badge twr" v-if="train.timetableData?.TWR" :title="$t('general.TWR')">
TWR
</span> </span>
<span class="train-badge skr" v-if="train.timetableData?.SKR" :title="$t('general.SKR')">
SKR
</span>
</span>
<strong> <span
<span v-if="train.timetableData" class="text--primary" class="timetable-warnings"
>{{ train.timetableData.category }}&nbsp;</span v-if="train.timetableData?.TWR || train.timetableData?.SKR"
> >
<span class="train-number">{{ train.trainNo }}</span> <span
</strong> class="train-badge twr"
<span>&bull;</span> v-if="train.timetableData?.TWR"
<b :title="$t('general.TWR')"
class="level-badge driver" >
:style="calculateExpStyle(train.driverLevel, train.isSupporter)" TWR
> </span>
{{ train.driverLevel < 2 ? 'L' : `${train.driverLevel}` }} <span
</b> class="train-badge skr"
v-if="train.timetableData?.SKR"
:title="$t('general.SKR')"
>
SKR
</span>
</span>
<div class="train-driver"> <strong>
<span v-if="train.timetableData" class="text--primary"
>{{ train.timetableData.category }}&nbsp;</span
>
<span class="train-number">{{ train.trainNo }}</span>
</strong>
<span>&bull;</span>
<b <b
v-if="apiStore.donatorsData.includes(train.driverName)" class="level-badge driver"
:title="$t('donations.driver-message')" :style="calculateExpStyle(train.driverLevel, train.isSupporter)"
> >
{{ train.driverName }} {{ train.driverLevel < 2 ? 'L' : `${train.driverLevel}` }}
<img src="/images/icon-diamond.svg" alt="donator diamond icon" />
</b> </b>
<span v-else>{{ train.driverName }}</span>
<div class="train-driver">
<b
v-if="apiStore.donatorsData.includes(train.driverName)"
data-popup-key="DonatorPopUp"
:data-popup-content="$t('donations.driver-message')"
>
{{ train.driverName }}
<img src="/images/icon-diamond.svg" alt="donator diamond icon" />
</b>
<span v-else>{{ train.driverName }}</span>
</div>
</div>
<div v-if="extended">
<button class="btn-timetable btn--image btn--action" @click="navigateToJournal">
<img src="/images/icon-train.svg" alt="train icon" />
<span>
{{ $t('trains.journal-button') }}
</span>
</button>
<button class="btn-exit btn--image btn--action" @click="closeModal">
<img src="/images/icon-exit.svg" alt="modal exit icon" />
</button>
</div> </div>
</div> </div>
<div class="general-timetable" v-if="train.timetableData"> <div class="general-timetable" v-if="train.timetableData">
<strong>{{ train.timetableData.route.replace('|', ' - ') }}</strong> <strong>{{ train.timetableData.route.replace('|', ' - ') }}</strong>
<img <span
v-if="getSceneriesWithComments(train.timetableData).length > 0" v-if="getSceneriesWithComments(train.timetableData).length > 0"
class="image-warning" data-popup-key="TrainCommentsPopUp"
src="/images/icon-warning.svg" :data-popup-content="`${$t('trains.timetable-comments')} (${getSceneriesWithComments(
:title="`${$t('trains.timetable-comments')} (${getSceneriesWithComments(
train.timetableData train.timetableData
)})`" )})`"
/> >
<img class="image-warning" src="/images/icon-warning.svg" />
</span>
</div> </div>
<hr style="margin: 0.25em 0" /> <hr style="margin: 0.25em 0" />
@@ -67,7 +93,7 @@
</div> </div>
<div class="general-status"> <div class="general-status">
<div class="timetable-progress" v-if="train.timetableData"> <div class="status-timetable-progress" v-if="train.timetableData">
<ProgressBar :progressPercent="confirmedPercentage(train.timetableData.followingStops)" /> <ProgressBar :progressPercent="confirmedPercentage(train.timetableData.followingStops)" />
<span class="progress-distance"> <span class="progress-distance">
@@ -91,12 +117,29 @@
</div> </div>
</div> </div>
<div class="driver_position text--grayed" style="margin-top: 0.25em"> <div class="general-stats" v-if="extended">
<div>
<img src="/images/icon-length.svg" alt="length icon" />
{{ train.length }}m
</div>
<div>
<img src="/images/icon-mass.svg" alt="mass icon" />
{{ (train.mass / 1000).toFixed(1) }}t
</div>
<div>
<img src="/images/icon-speed.svg" alt="speed icon" />
{{ train.speed }} km/h
</div>
</div>
<div class="text--grayed" style="margin-top: 0.25em">
{{ displayTrainPosition(train) }} {{ displayTrainPosition(train) }}
</div> </div>
</section> </section>
<section class="train-stats"> <section class="train-stats" v-if="!extended">
<StockList :trainStockList="train.stockList" :tractionOnly="true" /> <StockList :trainStockList="train.stockList" :tractionOnly="true" />
<div> <div>
@@ -125,9 +168,10 @@ import ProgressBar from '../Global/ProgressBar.vue';
import { useMainStore } from '../../store/mainStore'; import { useMainStore } from '../../store/mainStore';
import { useApiStore } from '../../store/apiStore'; import { useApiStore } from '../../store/apiStore';
import StockList from '../Global/StockList.vue'; import StockList from '../Global/StockList.vue';
import modalTrainMixin from '../../mixins/modalTrainMixin';
export default defineComponent({ export default defineComponent({
mixins: [trainInfoMixin, styleMixin], mixins: [trainInfoMixin, styleMixin, modalTrainMixin],
components: { ProgressBar, StockList }, components: { ProgressBar, StockList },
props: { props: {
@@ -136,8 +180,7 @@ export default defineComponent({
required: true required: true
}, },
extended: { extended: {
type: Boolean, type: Boolean
default: true
} }
}, },
@@ -146,6 +189,19 @@ export default defineComponent({
store: useMainStore(), store: useMainStore(),
apiStore: useApiStore() apiStore: useApiStore()
}; };
},
methods: {
navigateToJournal() {
this.$router.push({
path: '/journal/timetables',
query: {
'search-driver': this.train.driverName
}
});
this.closeModal();
}
} }
}); });
</script> </script>
@@ -156,8 +212,8 @@ export default defineComponent({
.image-warning { .image-warning {
height: 1em; height: 1em;
margin-left: 0.5em; margin-left: 0.5em;
vertical-align: middle;
} }
.train-stats { .train-stats {
@@ -176,6 +232,10 @@ export default defineComponent({
grid-template-columns: 2fr 1fr; grid-template-columns: 2fr 1fr;
grid-template-rows: 1fr; grid-template-rows: 1fr;
&[data-extended='true'] {
grid-template-columns: 1fr;
}
padding: 1em; padding: 1em;
background-color: #1a1a1a; background-color: #1a1a1a;
@@ -210,14 +270,29 @@ export default defineComponent({
font-size: 0.8em; font-size: 0.8em;
} }
.general-info { .general-top-bar {
display: flex; display: flex;
align-items: center; justify-content: space-between;
flex-wrap: wrap; flex-wrap: wrap;
gap: 0.5em;
gap: 0.25em; & > div {
margin-right: 1.5em; display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0.25em;
}
} }
.btn-timetable {
padding: 0.25em;
}
.btn-exit {
padding: 0.25em;
}
.general-status { .general-status {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -226,6 +301,27 @@ export default defineComponent({
gap: 0.25em; gap: 0.25em;
} }
.general-stats {
display: flex;
gap: 0.5em;
flex-wrap: wrap;
& > div {
display: flex;
align-items: center;
gap: 0.25em;
}
img {
width: 1.5em;
}
}
.general-timetable {
display: flex;
align-items: center;
}
.status-badges { .status-badges {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
@@ -237,17 +333,7 @@ export default defineComponent({
} }
} }
.general-timetable { .status-timetable-progress {
display: flex;
align-items: center;
}
.timetable-warnings {
display: flex;
gap: 0.25em;
}
.timetable-progress {
display: flex; display: flex;
align-items: center; align-items: center;
flex-wrap: wrap; flex-wrap: wrap;
@@ -257,32 +343,19 @@ export default defineComponent({
margin-right: 0.25em; margin-right: 0.25em;
} }
.timetable-warnings {
display: flex;
gap: 0.25em;
}
@include smallScreen() { @include smallScreen() {
.train-info { .train-info {
grid-template-columns: 1fr; grid-template-columns: 1fr;
gap: 1em 0; gap: 1em 0;
text-align: center;
font-size: 1.15em;
} }
.general-info, .btn-timetable > span {
.general-status, display: none;
.general-timetable {
justify-content: center;
}
.timetable-progress {
justify-content: center;
}
.comments {
flex-direction: column;
justify-content: center;
img {
margin: 0 0 0.5em 0;
}
} }
} }
</style> </style>
+16 -27
View File
@@ -2,11 +2,7 @@
<div class="train-modal" v-if="chosenTrain" @keydown.esc="closeModal"> <div class="train-modal" v-if="chosenTrain" @keydown.esc="closeModal">
<div class="modal_background" @click="closeModal"></div> <div class="modal_background" @click="closeModal"></div>
<div class="modal_content" ref="content" tabindex="0"> <div class="modal_content" ref="content" tabindex="0">
<button class="btn exit" @click="closeModal"> <TrainInfo :train="chosenTrain" :extended="true" ref="trainInfo" />
<img src="/images/icon-exit.svg" alt="close card" />
</button>
<TrainInfo :train="chosenTrain" :extended="false" ref="trainInfo" />
<TrainSchedule :train="chosenTrain" tabindex="0" /> <TrainSchedule :train="chosenTrain" tabindex="0" />
</div> </div>
</div> </div>
@@ -17,17 +13,27 @@ import { defineComponent } from 'vue';
import modalTrainMixin from '../../mixins/modalTrainMixin'; import modalTrainMixin from '../../mixins/modalTrainMixin';
import TrainInfo from './TrainInfo.vue'; import TrainInfo from './TrainInfo.vue';
import TrainSchedule from './TrainSchedule.vue'; import TrainSchedule from './TrainSchedule.vue';
import Train from '../../scripts/interfaces/Train';
export default defineComponent({ export default defineComponent({
components: { TrainInfo, TrainSchedule }, components: { TrainInfo, TrainSchedule },
mixins: [modalTrainMixin], mixins: [modalTrainMixin],
activated() { computed: {
const contentEl = this.$refs['content'] as HTMLElement; chosenTrain() {
return this.store.trainList.find((train) => train.trainId == this.store.chosenModalTrainId);
}
},
this.$nextTick(() => { watch: {
contentEl.focus(); chosenTrain(train: Train | undefined) {
}); this.$nextTick(() => {
if (train) {
const contentEl = this.$refs['content'] as HTMLElement;
contentEl.focus();
}
});
}
} }
}); });
</script> </script>
@@ -49,23 +55,6 @@ export default defineComponent({
} }
} }
.exit {
position: absolute;
top: 0;
right: 0;
margin: 0.5em 1em;
padding: 0.25em;
z-index: 201;
img {
width: 1.5rem;
vertical-align: middle;
}
}
.train-modal { .train-modal {
position: fixed; position: fixed;
top: 0; top: 0;
+4 -4
View File
@@ -1,13 +1,13 @@
<template> <template>
<transition name="status-anim" mode="out-in" tag="div" class="train-table"> <transition name="status-anim" mode="out-in" tag="div" class="train-table">
<div :key="apiStore.dataStatuses.connection"> <div :key="apiStore.dataStatuses.connection">
<div class="table-info" key="offline" v-if="store.isOffline"> <div class="table-warning" key="offline" v-if="store.isOffline">
{{ $t('app.offline') }} {{ $t('app.offline') }}
</div> </div>
<Loading v-else-if="apiStore.dataStatuses.connection == Status.Loading" key="loading" /> <Loading v-else-if="apiStore.dataStatuses.connection == Status.Loading" key="loading" />
<div class="table-info" key="no-trains" v-else-if="trains.length == 0"> <div class="table-warning" key="no-trains" v-else-if="trains.length == 0">
{{ $t('trains.no-trains') }} {{ $t('trains.no-trains') }}
</div> </div>
@@ -20,7 +20,7 @@
@click.stop="selectModalTrain(train.trainId, $event.currentTarget)" @click.stop="selectModalTrain(train.trainId, $event.currentTarget)"
@keydown.enter="selectModalTrain(train.trainId, $event.currentTarget)" @keydown.enter="selectModalTrain(train.trainId, $event.currentTarget)"
> >
<TrainInfo :train="train" /> <TrainInfo :train="train" :extended="false" />
</li> </li>
</transition-group> </transition-group>
</div> </div>
@@ -105,7 +105,7 @@ export default defineComponent({
overflow-x: hidden; overflow-x: hidden;
} }
.table-info { .table-warning {
text-align: center; text-align: center;
padding: 1em 0; padding: 1em 0;
+15 -7
View File
@@ -26,6 +26,13 @@
"TWR": "High risk freight train", "TWR": "High risk freight train",
"SKR": "Train with exceeded gauge" "SKR": "Train with exceeded gauge"
}, },
"update": {
"title": "Stacjownik app update!",
"version": "Version {0}",
"confirm-button": "UPDATE NOW",
"later-button": "LATER",
"content": "context tooltips when hovering over project sponsors and timetable comment warnings\nvehicle image preview when hovering over its thumbnail in the active timetable card view and timetable journal\nlink to the driver's timetable history in the active timetable card view\nlink to the driver's active timetable card view in the timetable journal (available for online trains only)\nnew update card with version changelog"
},
"app": { "app": {
"sceneries": "SCENERIES", "sceneries": "SCENERIES",
"trains": "TRAINS", "trains": "TRAINS",
@@ -41,12 +48,10 @@
"footer": { "footer": {
"discord": "Stacjownik Discord server" "discord": "Stacjownik Discord server"
}, },
"update": {
"title": "New version of the app is available!", "vehicle-preview": {
"paragraph1": "Enjoy the application and may the green signal be with you!", "loading": "Loading preview...",
"release-link": "Click here to browse version changelog (GitHub)", "error": "Oops! The vehicle preview seems to be missing! :/"
"confirm-button": "UPDATE NOW",
"later-button": "LATER"
}, },
"data-status": { "data-status": {
"S1-offline": "<b>S1 signal</b> <br> The app is working in offline mode!", "S1-offline": "<b>S1 signal</b> <br> The app is working in offline mode!",
@@ -315,7 +320,9 @@
"last-seen-ago": "since {minutes} minutes", "last-seen-ago": "since {minutes} minutes",
"scenery-offline": "Offline ride", "scenery-offline": "Offline ride",
"timeout": "An error occured while trying to refresh SWDR timetable data!" "timeout": "An error occured while trying to refresh SWDR timetable data!",
"journal-button": "DRIVER'S JOURNAL"
}, },
"train-stats": { "train-stats": {
"stats-button": "STATISTICS", "stats-button": "STATISTICS",
@@ -351,6 +358,7 @@
"timetable-active": "ACTIVE", "timetable-active": "ACTIVE",
"timetable-fulfilled": "FULFILLED", "timetable-fulfilled": "FULFILLED",
"timetable-abandoned": "ABANDONED", "timetable-abandoned": "ABANDONED",
"timetable-online-button": "ONLINE TIMETABLE",
"online-since": "ONLINE SINCE", "online-since": "ONLINE SINCE",
"duty-lasted": "The duty lasted", "duty-lasted": "The duty lasted",
+16 -2
View File
@@ -4,7 +4,7 @@
"header": "Grosza daj Stacjownikowi!", "header": "Grosza daj Stacjownikowi!",
"donator-title": "Projekt ma już ponad <b>{count}</b> wspierających, w tym:", "donator-title": "Projekt ma już ponad <b>{count}</b> wspierających, w tym:",
"p1": "<b>Hej o7!</b> Z tej strony Spythere, twórca Stacjownika, Pojazdownika oraz kilku innych aplikacji wspomagających rozgrywkę symulatora Train Driver 2!", "p1": "<b>Hej o7!</b> Z tej strony Spythere, twórca Stacjownika, Pojazdownika oraz kilku innych aplikacji wspomagających rozgrywkę symulatora Train Driver 2!",
"p2": "{b1} to narzędzie całkowicie darmowe, tworzone i rozwijane dla społeczności symulatora TD2 nieprzerwanie od 2020 roku. Jednakże, część projektu jest podtrzymywana wyłącznie dzięki mojemu prywatnemu wkładowi finansowemu. Funkcje takie jak {b2} czy też {b3} działający na moim {link} (na który serdeczne zapraszam) muszą działać na wydzielonym serwerze, gdzie będą mogły zbierać i przetwarzać dane, aby następnie pokazać je na stronie.", "p2": "{b1} to narzędzie całkowicie darmowe, tworzone i rozwijane dla społeczności symulatora TD2 nieprzerwanie od 2020 roku. Jednakże, część projektu jest podtrzymywana wyłącznie dzięki mojemu prywatnemu wkładowi finansowemu. Funkcje takie jak {b2} czy też {b3} działający na moim {link} (na który serdecznie zapraszam) muszą działać na wydzielonym serwerze, gdzie będą mogły zbierać i przetwarzać dane, aby następnie pokazać je na stronie.",
"p2-b1": "Stacjownik", "p2-b1": "Stacjownik",
"p2-b2": "Dziennik", "p2-b2": "Dziennik",
"p2-b3": "Stacjobot", "p2-b3": "Stacjobot",
@@ -26,6 +26,13 @@
"TWR": "Towar niebezpieczny wysokiego ryzyka", "TWR": "Towar niebezpieczny wysokiego ryzyka",
"SKR": "Przekroczona skrajnia" "SKR": "Przekroczona skrajnia"
}, },
"update": {
"title": "Aktualizacja Stacjownika!",
"version": "Wersja {0}",
"confirm-button": "UPDATE NOW",
"later-button": "LATER",
"content": "dymki kontekstowe po najechaniu kursorem na m.in. sponsorów projektu i uwagi eksploatacyjne\npodgląd pojazdu po najechaniu kursorem na jego miniaturkę w karcie aktywnego rozkładu jazdy oraz dzienniku RJ\nodnośnik do historii RJ maszynisty w widoku karty aktywnego rozkładu jazdy\nodnośnik do karty aktywnego rozkładu jazdy maszynisty w dzienniku (dostępny tylko dla pociągów online)\nnowa karta ze zmianami w aktualizacji"
},
"app": { "app": {
"sceneries": "SCENERIE", "sceneries": "SCENERIE",
"trains": "POCIĄGI", "trains": "POCIĄGI",
@@ -38,6 +45,10 @@
"footer": { "footer": {
"discord": "Serwer Discord Stacjownika" "discord": "Serwer Discord Stacjownika"
}, },
"vehicle-preview": {
"loading": "Ładowanie podglądu...",
"error": "Ups! Nie znaleziono podglądu pojazdu! :/"
},
"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!",
@@ -295,7 +306,9 @@
"scenery-offline": "Przejazd offline", "scenery-offline": "Przejazd offline",
"timeout": "Wystąpił problem z aktualizacją rozkładów jazdy z SWDR" "timeout": "Wystąpił problem z aktualizacją rozkładów jazdy z SWDR",
"journal-button": "DZIENNIK MASZYNISTY"
}, },
"train-stats": { "train-stats": {
"stats-button": "STATYSTYKI", "stats-button": "STATYSTYKI",
@@ -337,6 +350,7 @@
"timetable-active": "AKTYWNY", "timetable-active": "AKTYWNY",
"timetable-fulfilled": "WYPEŁNIONY", "timetable-fulfilled": "WYPEŁNIONY",
"timetable-abandoned": "PORZUCONY", "timetable-abandoned": "PORZUCONY",
"timetable-online-button": "RJ ONLINE",
"stock-info": "DODATKOWE INFORMACJE", "stock-info": "DODATKOWE INFORMACJE",
"stock-length": "Długość", "stock-length": "Długość",
+1 -6
View File
@@ -8,12 +8,6 @@ export default defineComponent({
}; };
}, },
computed: {
chosenTrain() {
return this.store.trainList.find((train) => train.trainId == this.store.chosenModalTrainId);
}
},
methods: { methods: {
selectModalTrain(trainId: string, target?: EventTarget | null) { selectModalTrain(trainId: string, target?: EventTarget | null) {
this.store.chosenModalTrainId = trainId; this.store.chosenModalTrainId = trainId;
@@ -23,6 +17,7 @@ export default defineComponent({
closeModal() { closeModal() {
this.store.chosenModalTrainId = undefined; this.store.chosenModalTrainId = undefined;
this.store.popUpData.key = null;
setTimeout(() => { setTimeout(() => {
(this.store.modalLastClickedTarget as any)?.focus(); (this.store.modalLastClickedTarget as any)?.focus();
+27
View File
@@ -0,0 +1,27 @@
import { defineComponent } from 'vue';
import { useMainStore } from '../store/mainStore';
import { PopUpType, popupKeys } from '../store/typings';
const isPopUp = (v: any): v is PopUpType => popupKeys.includes(v);
export default defineComponent({
data() {
return {
store: useMainStore()
};
},
methods: {
showPopUp(e: MouseEvent, componentKey: string, value?: string) {
if (!isPopUp(componentKey)) return;
this.store.popUpData['key'] = componentKey;
this.store.popUpData['content'] = value ?? '';
},
hidePopUp() {
this.store.popUpData['key'] = null;
this.store.popUpData['content'] = '';
}
}
});
+7 -9
View File
@@ -4,9 +4,6 @@ import { Status } from '../typings/common';
import { StationJSONData } from './typings'; import { StationJSONData } from './typings';
import axios, { AxiosInstance } from 'axios'; import axios, { AxiosInstance } from 'axios';
// Update seconds cron for active data scheduler
const UPDATE_SECONDS = [3, 23, 43];
export enum APIMode { export enum APIMode {
PRODUCTION = 0, PRODUCTION = 0,
DEV = 1, DEV = 1,
@@ -24,6 +21,8 @@ export const useApiStore = defineStore('apiStore', {
donatorsData: [] as API.Donators.Response, donatorsData: [] as API.Donators.Response,
sceneryData: [] as StationJSONData[], sceneryData: [] as StationJSONData[],
lastFetchData: new Date(),
client: undefined as AxiosInstance | undefined, client: undefined as AxiosInstance | undefined,
activeDataScheduler: undefined as number | undefined activeDataScheduler: undefined as number | undefined
@@ -64,20 +63,19 @@ export const useApiStore = defineStore('apiStore', {
async setupActiveDataFetcher() { async setupActiveDataFetcher() {
if (this.activeDataScheduler) return; if (this.activeDataScheduler) return;
this.dataStatuses.connection = Status.Data.Loading;
this.activeDataScheduler = window.setInterval(() => { this.activeDataScheduler = window.setInterval(() => {
if (UPDATE_SECONDS.includes(new Date().getSeconds())) { this.fetchActiveData();
this.fetchActiveData(); }, 25000);
}
}, 1000);
}, },
async fetchActiveData() { async fetchActiveData() {
if (!this.activeData) this.dataStatuses.connection = Status.Data.Loading;
try { try {
const response = await this.client!.get<API.ActiveData.Response>('api/getActiveData'); const response = await this.client!.get<API.ActiveData.Response>('api/getActiveData');
this.activeData = response.data; this.activeData = response.data;
this.lastFetchData = new Date();
this.dataStatuses.connection = Status.Data.Loaded; this.dataStatuses.connection = Status.Data.Loaded;
console.log('Fetching active data at ' + new Date().toLocaleTimeString('pl-PL')); console.log('Fetching active data at ' + new Date().toLocaleTimeString('pl-PL'));
+10 -4
View File
@@ -26,8 +26,10 @@ export const useMainStore = defineStore('store', {
chosenModalTrainId: undefined, chosenModalTrainId: undefined,
blockScroll: false, modalLastClickedTarget: null,
modalLastClickedTarget: null
mousePos: { x: 0, y: 0 },
popUpData: { key: null, content: '' }
}) as StoreState, }) as StoreState,
getters: { getters: {
@@ -45,7 +47,8 @@ export const useMainStore = defineStore('store', {
const sceneryNames = const sceneryNames =
train.timetable?.sceneries?.map( train.timetable?.sceneries?.map(
(sceneryHash) => (sceneryHash) =>
this.activeSceneryList.find((st) => st.hash === sceneryHash)?.name ?? apiStore.activeData?.activeSceneries?.find((st) => st.stationHash === sceneryHash)
?.stationName ??
apiStore.sceneryData.find((sd) => sd.hash === sceneryHash)?.name ?? apiStore.sceneryData.find((sd) => sd.hash === sceneryHash)?.name ??
sceneryHash sceneryHash
) ?? []; ) ?? [];
@@ -107,7 +110,10 @@ export const useMainStore = defineStore('store', {
if ( if (
acc.findIndex((v) => v.name == name && v.region == train.region) != -1 || acc.findIndex((v) => v.name == name && v.region == train.region) != -1 ||
apiStore.activeData?.activeSceneries?.findIndex( apiStore.activeData?.activeSceneries?.findIndex(
(sc) => sc.stationName === name && sc.region == train.region (sc) =>
sc.stationName === name &&
sc.region == train.region &&
Date.now() - sc.lastSeen < 1000 * 60 * 2
) != -1 ) != -1
) )
return acc; return acc;
+4 -7
View File
@@ -1,7 +1,9 @@
import { API } from '../typings/api'; import { API } from '../typings/api';
import { Status } from '../typings/common'; import { Status } from '../typings/common';
export const popupKeys = ['DonatorPopUp', 'TrainCommentsPopUp', 'VehiclePreviewPopUp'] as const;
export type Availability = 'default' | 'unavailable' | 'nonPublic' | 'abandoned' | 'nonDefault'; export type Availability = 'default' | 'unavailable' | 'nonPublic' | 'abandoned' | 'nonDefault';
export type PopUpType = (typeof popupKeys)[number];
export interface RegionCounters { export interface RegionCounters {
stationCount: number; stationCount: number;
@@ -11,22 +13,17 @@ export interface RegionCounters {
export interface StoreState { export interface StoreState {
region: { id: string; value: string }; region: { id: string; value: string };
isOffline: boolean; isOffline: boolean;
isNewUpdate: boolean; isNewUpdate: boolean;
dispatcherStatsName: string; dispatcherStatsName: string;
dispatcherStatsData?: API.DispatcherStats.Response; dispatcherStatsData?: API.DispatcherStats.Response;
driverStatsName: string; driverStatsName: string;
driverStatsData?: API.DriverStats.Response; driverStatsData?: API.DriverStats.Response;
driverStatsStatus: Status.Data; driverStatsStatus: Status.Data;
chosenModalTrainId?: string; chosenModalTrainId?: string;
blockScroll: boolean;
modalLastClickedTarget: EventTarget | null; modalLastClickedTarget: EventTarget | null;
mousePos: { x: number; y: number };
popUpData: { key: PopUpType | null; content: string };
} }
export interface StationRoutesInfo { export interface StationRoutesInfo {
+1
View File
@@ -6,6 +6,7 @@
height: 90vh; height: 90vh;
min-height: 550px; min-height: 550px;
margin-top: 0.5em; margin-top: 0.5em;
position: relative;
padding-right: 0.2em; padding-right: 0.2em;
} }
+1 -2
View File
@@ -1,4 +1,4 @@
$animDuration: 150ms; $animDuration: 95ms;
$animType: ease-in-out; $animType: ease-in-out;
// List animation // List animation
@@ -72,7 +72,6 @@ $animType: ease-in-out;
&-enter-from, &-enter-from,
&-leave-to { &-leave-to {
transform: translateY(-25%);
opacity: 0; opacity: 0;
} }
} }
+3 -1
View File
@@ -55,6 +55,8 @@ body {
-webkit-font-smoothing: antialiased !important; -webkit-font-smoothing: antialiased !important;
overflow-y: scroll; overflow-y: scroll;
overflow-x: hidden;
position: relative;
&.no-scroll { &.no-scroll {
overflow-y: hidden; overflow-y: hidden;
@@ -234,7 +236,7 @@ a.a-button {
padding: 0.35em 0.75em; padding: 0.35em 0.75em;
img { img {
width: 1.5em; width: 1.35em;
vertical-align: middle; vertical-align: middle;
} }
} }