Compare commits

..

11 Commits

Author SHA1 Message Date
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
22 changed files with 415 additions and 49 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "stacjownik", "name": "stacjownik",
"version": "1.22.2", "version": "1.23.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
+31 -4
View File
@@ -1,5 +1,7 @@
<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 v-if="store.chosenModalTrainId" />
@@ -36,17 +38,17 @@ import { defineComponent, watch } 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 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';
import { usePopupStore } from './store/popupStore';
const STORAGE_VERSION_KEY = 'app_version'; const STORAGE_VERSION_KEY = 'app_version';
@@ -55,13 +57,15 @@ export default defineComponent({
Clock, Clock,
StatusIndicator, StatusIndicator,
AppHeader, AppHeader,
TrainModal TrainModal,
PopUp
}, },
data: () => ({ data: () => ({
VERSION: version, VERSION: version,
store: useMainStore(), store: useMainStore(),
apiStore: useApiStore(), apiStore: useApiStore(),
popupStore: usePopupStore(),
currentLang: 'pl', currentLang: 'pl',
releaseURL: '', releaseURL: '',
@@ -79,6 +83,28 @@ export default defineComponent({
this.apiStore.fetchActiveData(); this.apiStore.fetchActiveData();
}); });
// popup handling
window.addEventListener('mousemove', (e: MouseEvent) => {
e.stopPropagation();
const targetEl = e
.composedPath()
.find((p) => p instanceof HTMLElement && p.getAttribute('data-popup-key'));
if (!targetEl || !(targetEl instanceof HTMLElement)) {
if (this.popupStore.currentPopupComponent != null) this.popupStore.onPopUpHide();
return;
}
const popupComponentKey = targetEl.getAttribute('data-popup-key');
const popupContent = targetEl.getAttribute('data-popup-content');
if (popupComponentKey && popupContent)
this.popupStore.onPopUpShow(e, popupComponentKey, popupContent);
else if (this.popupStore.currentPopupComponent != null) this.popupStore.onPopUpHide();
});
watch( watch(
() => this.store.blockScroll, () => this.store.blockScroll,
(value) => { (value) => {
@@ -217,6 +243,7 @@ export default defineComponent({
grid-template-columns: 100%; grid-template-columns: 100%;
min-height: 100vh; min-height: 100vh;
position: relative;
} }
.app_main { .app_main {
+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">
@@ -39,6 +34,15 @@
<strong v-else> <strong v-else>
{{ timetable.driverName }} {{ timetable.driverName }}
</strong> </strong>
<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>
<span class="general-time"> <span class="general-time">
@@ -148,6 +152,16 @@ export default defineComponent({
gap: 0.25em; gap: 0.25em;
} }
.btn-timetable {
display: inline-block;
padding: 0.1em 0.4em;
margin-left: 0.5em;
img {
vertical-align: top;
}
}
@include smallScreen { @include smallScreen {
.item-general { .item-general {
justify-content: center; justify-content: center;
@@ -5,7 +5,7 @@
v-for="{ timetable, showExtraInfo } in computedTimetableHistory" v-for="{ timetable, showExtraInfo } in computedTimetableHistory"
class="journal_item" class="journal_item"
:key="timetable.id" :key="timetable.id"
@click="showExtraInfo.value = !showExtraInfo.value" @click.stop.prevent="showExtraInfo.value = !showExtraInfo.value"
> >
<div class="journal_item-info"> <div class="journal_item-info">
<!-- General --> <!-- General -->
+39
View File
@@ -0,0 +1,39 @@
<template>
<div class="popup-content">
<img src="/images/icon-diamond.svg" alt="" />
<span>{{ popupStore.currentPopupContent }}</span>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { usePopupStore } from '../../store/popupStore';
export default defineComponent({
data() {
return {
popupStore: usePopupStore()
};
}
});
</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>
+55
View File
@@ -0,0 +1,55 @@
<template>
<div class="popup" v-show="popupStore.currentPopupComponent" ref="preview">
<component v-if="popupStore.currentPopupComponent" :is="popupStore.currentPopupComponent" />
</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 { usePopupStore } from '../../store/popupStore';
export default defineComponent({
components: { DonatorPopUp, TrainCommentsPopUp, VehiclePreviewPopUp },
data() {
return {
popupStore: usePopupStore()
};
},
watch: {
'popupStore.popupPosition': {
deep: true,
handler(val: typeof this.popupStore.popupPosition) {
const previewEl = this.$refs['preview'] as HTMLElement;
previewEl.style.top = `${val.y}px`;
previewEl.style.left = `${val.x}px`;
previewEl.style.transform = 'translateY(1.5rem)';
this.$nextTick(() => {
const isOutside =
val.y + previewEl.getBoundingClientRect().height > window.innerHeight + window.scrollY;
// previewEl.style.transform = `translate(-${~~((val.x / window.innerWidth) * 100)}%, calc(${isOutside ? '-100% - 1.5rem' : '1.5rem'}))`;
previewEl.style.transform = `translate(-${~~((val.x / window.innerWidth) * 100)}%, calc(${
isOutside ? '-100% - 1.5rem' : '1.5rem'
}))`;
});
}
}
}
});
</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>{{ popupStore.currentPopupContent }}</span>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { usePopupStore } from '../../store/popupStore';
export default defineComponent({
data() {
return {
popupStore: usePopupStore()
};
}
});
</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,86 @@
<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="popupStore.currentPopupContent"
@load="onImageLoad"
@error="onImageError"
@click="popupStore.onPopUpHide"
width="300"
height="176"
class="rounded-md w-full h-auto"
:src="`https://spythere.github.io/api/td2/images/${popupStore.currentPopupContent}--300px.jpg`"
/>
<div class="vehicle-name" v-if="imageState != 'error'">
{{ popupStore.currentPopupContent.replace(/_/g, ' ') }}
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { usePopupStore } from '../../store/popupStore';
export default defineComponent({
data() {
return {
popupStore: usePopupStore(),
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)"
+8 -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 { usePopupStore } from '../../store/popupStore';
export default defineComponent({ export default defineComponent({
props: { props: {
@@ -339,13 +341,16 @@ export default defineComponent({
setup() { setup() {
const mainStore = useMainStore(); const mainStore = useMainStore();
const apiStore = useApiStore(); const apiStore = useApiStore();
const popupStore = usePopupStore();
const stationFiltersStore = useStationFiltersStore(); const stationFiltersStore = useStationFiltersStore();
return { return {
Status: Status.Data, Status: Status.Data,
stationFiltersStore, stationFiltersStore,
mainStore, mainStore,
apiStore apiStore,
popupStore
}; };
}, },
@@ -368,6 +373,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.popupStore.currentPopupComponent = null;
}, },
openForumSite(e: Event, url: string | undefined) { openForumSite(e: Event, url: string | undefined) {
+44 -13
View File
@@ -36,25 +36,37 @@
<div class="train-driver"> <div class="train-driver">
<b <b
v-if="apiStore.donatorsData.includes(train.driverName)" v-if="apiStore.donatorsData.includes(train.driverName)"
:title="$t('donations.driver-message')" data-popup-key="DonatorPopUp"
:data-popup-content="$t('donations.driver-message')"
> >
{{ train.driverName }} {{ train.driverName }}
<img src="/images/icon-diamond.svg" alt="donator diamond icon" /> <img src="/images/icon-diamond.svg" alt="donator diamond icon" />
</b> </b>
<span v-else>{{ train.driverName }}</span> <span v-else>{{ train.driverName }}</span>
<button
class="btn--image btn--action btn-timetable"
@click="navigateToJournal"
v-if="extended"
>
<img src="/images/icon-train.svg" alt="" />
{{ $t('trains.journal-button') }}
</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" />
@@ -125,9 +137,11 @@ 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 { usePopupStore } from '../../store/popupStore';
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,16 +150,29 @@ export default defineComponent({
required: true required: true
}, },
extended: { extended: {
type: Boolean, type: Boolean
default: true
} }
}, },
data() { data() {
return { return {
store: useMainStore(), store: useMainStore(),
apiStore: useApiStore() apiStore: useApiStore(),
popupStore: usePopupStore()
}; };
},
methods: {
navigateToJournal() {
this.$router.push({
path: '/journal/timetables',
query: {
'search-driver': this.train.driverName
}
});
this.closeModal();
}
} }
}); });
</script> </script>
@@ -156,8 +183,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 {
@@ -187,6 +214,12 @@ export default defineComponent({
vertical-align: text-bottom; vertical-align: text-bottom;
} }
.btn-timetable {
display: inline-block;
padding: 0.25em;
margin-left: 0.5em;
}
.timetable-id { .timetable-id {
color: #d2d2d2; color: #d2d2d2;
} }
@@ -262,8 +295,6 @@ export default defineComponent({
grid-template-columns: 1fr; grid-template-columns: 1fr;
gap: 1em 0; gap: 1em 0;
text-align: center; text-align: center;
font-size: 1.15em;
} }
.general-info, .general-info,
+1 -1
View File
@@ -6,7 +6,7 @@
<img src="/images/icon-exit.svg" alt="close card" /> <img src="/images/icon-exit.svg" alt="close card" />
</button> </button>
<TrainInfo :train="chosenTrain" :extended="false" ref="trainInfo" /> <TrainInfo :train="chosenTrain" :extended="true" ref="trainInfo" />
<TrainSchedule :train="chosenTrain" tabindex="0" /> <TrainSchedule :train="chosenTrain" tabindex="0" />
</div> </div>
</div> </div>
+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;
+8 -1
View File
@@ -48,6 +48,10 @@
"confirm-button": "UPDATE NOW", "confirm-button": "UPDATE NOW",
"later-button": "LATER" "later-button": "LATER"
}, },
"vehicle-preview": {
"loading": "Loading preview...",
"error": "Oops! The vehicle preview seems to be missing! :/"
},
"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!",
"S1a-connection": "<b>S1a signal</b> <br> Cannot connect with Stacjownik API service!", "S1a-connection": "<b>S1a signal</b> <br> Cannot connect with Stacjownik API service!",
@@ -315,7 +319,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 +357,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",
+8 -1
View File
@@ -38,6 +38,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 +299,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 +343,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ść",
+4 -1
View File
@@ -1,10 +1,12 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { useMainStore } from '../store/mainStore'; import { useMainStore } from '../store/mainStore';
import { usePopupStore } from '../store/popupStore';
export default defineComponent({ export default defineComponent({
data() { data() {
return { return {
store: useMainStore() store: useMainStore(),
popupStore: usePopupStore()
}; };
}, },
@@ -23,6 +25,7 @@ export default defineComponent({
closeModal() { closeModal() {
this.store.chosenModalTrainId = undefined; this.store.chosenModalTrainId = undefined;
this.popupStore.currentPopupComponent = null;
setTimeout(() => { setTimeout(() => {
(this.store.modalLastClickedTarget as any)?.focus(); (this.store.modalLastClickedTarget as any)?.focus();
+4 -1
View File
@@ -107,7 +107,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;
+37
View File
@@ -0,0 +1,37 @@
import { defineStore } from 'pinia';
export const popupKeys = ['DonatorPopUp', 'TrainCommentsPopUp', 'VehiclePreviewPopUp'] as const;
export type PopUp = (typeof popupKeys)[number];
const isPopUp = (v: any): v is PopUp => popupKeys.includes(v);
export const usePopupStore = defineStore('popupStore', {
state: () => ({
popupPosition: { x: 0, y: 0 },
currentPopupComponent: null as PopUp | null,
currentPopupContent: '',
donatorPopupVisible: false
}),
actions: {
onPopUpShow(e: MouseEvent, componentKey: string, value?: string) {
if (!isPopUp(componentKey)) return;
this.popupPosition.x = e.pageX;
this.popupPosition.y = e.pageY;
this.currentPopupComponent = componentKey;
this.currentPopupContent = value ?? '';
},
onPopUpMove(e: MouseEvent) {
this.popupPosition.x = e.pageX;
this.popupPosition.y = e.pageY;
},
onPopUpHide() {
this.currentPopupComponent = null;
this.currentPopupContent = '';
}
}
});
-6
View File
@@ -11,20 +11,14 @@ 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; blockScroll: boolean;
modalLastClickedTarget: EventTarget | null; modalLastClickedTarget: EventTarget | null;
} }
+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;
} }
} }
+1 -1
View File
@@ -234,7 +234,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;
} }
} }