dodano dymki kontekstowe oraz podgląd pojazdu

This commit is contained in:
2024-03-22 23:41:43 +01:00
parent e3b72c81ea
commit c7162dbd14
12 changed files with 319 additions and 24 deletions
+7 -4
View File
@@ -1,5 +1,7 @@
<template>
<div class="app_container" v-cloak>
<PopUp />
<transition name="modal-anim">
<keep-alive>
<TrainModal v-if="store.chosenModalTrainId" />
@@ -36,15 +38,14 @@ import { defineComponent, watch } from 'vue';
import axios from 'axios';
import { version } from '.././package.json';
import Clock from './components/App/Clock.vue';
import { useMainStore } from './store/mainStore';
import Clock from './components/App/Clock.vue';
import StatusIndicator from './components/App/StatusIndicator.vue';
import AppHeader from './components/App/AppHeader.vue';
import TrainModal from './components/TrainsView/TrainModal.vue';
import StorageManager from './managers/storageManager';
import PopUp from './components/PopUp/PopUp.vue';
import { useApiStore } from './store/apiStore';
import { Status } from './typings/common';
@@ -55,7 +56,8 @@ export default defineComponent({
Clock,
StatusIndicator,
AppHeader,
TrainModal
TrainModal,
PopUp
},
data: () => ({
@@ -217,6 +219,7 @@ export default defineComponent({
grid-template-columns: 100%;
min-height: 100vh;
position: relative;
}
.app_main {
+17 -3
View File
@@ -7,11 +7,12 @@
</p>
<img
class="traction-only"
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${computedStockList[0].split(':')[0]}${
/^EN/.test(computedStockList[0]) ? 'rb' : ''
}.png`"
@error="onImageError($event, computedStockList[0])"
width="400"
width="300"
height="60"
/>
</div>
@@ -23,7 +24,13 @@
{{ stockName.split(':')[1] }}
</p>
<span>
<span
@mouseenter="
popupStore.onPopUpShow($event, 'VehiclePreviewPopUp', stockName.split(':')[0])
"
@mousemove="popupStore.onPopUpMove"
@mouseleave="popupStore.onPopUpHide"
>
<img
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}${
/^EN/.test(stockName) ? 'rb' : ''
@@ -69,6 +76,7 @@
<script lang="ts">
import { PropType, defineComponent } from 'vue';
import { useApiStore } from '../../store/apiStore';
import { usePopupStore } from '../../store/popupStore';
export default defineComponent({
props: {
@@ -84,7 +92,8 @@ export default defineComponent({
data() {
return {
apiStore: useApiStore()
apiStore: useApiStore(),
popupStore: usePopupStore()
};
},
@@ -139,6 +148,7 @@ export default defineComponent({
ul > li > span {
display: flex;
align-items: flex-end;
cursor: crosshair;
}
img {
@@ -147,6 +157,10 @@ img {
height: auto;
}
img.traction-only {
max-width: 100%;
}
p {
text-align: center;
color: #aaa;
+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>
+54
View File
@@ -0,0 +1,54 @@
<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(-50%, 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,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="popupStore.currentPopupContent"
@load="onImageLoad"
@error="onImageError"
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>
+14 -2
View File
@@ -119,8 +119,16 @@
<span v-if="station.onlineInfo?.dispatcherName">
<b
v-if="apiStore.donatorsData.includes(station.onlineInfo.dispatcherName)"
:title="$t('donations.dispatcher-message')"
@click.stop="openDonationModal"
@mouseenter="
popupStore.onPopUpShow(
$event,
'DonatorPopUp',
$t('donations.dispatcher-message')
)
"
@mousemove="popupStore.onPopUpMove"
@mouseleave="popupStore.onPopUpHide"
>
<img src="/images/icon-diamond.svg" alt="" />
{{ station.onlineInfo.dispatcherName }}
@@ -311,6 +319,7 @@ import { HeadIdsTypes, headIconsIds, headIds } from '../../scripts/data/stationH
import StationStatusBadge from '../Global/StationStatusBadge.vue';
import { Status } from '../../typings/common';
import { useApiStore } from '../../store/apiStore';
import { usePopupStore } from '../../store/popupStore';
export default defineComponent({
props: {
@@ -339,13 +348,16 @@ export default defineComponent({
setup() {
const mainStore = useMainStore();
const apiStore = useApiStore();
const popupStore = usePopupStore();
const stationFiltersStore = useStationFiltersStore();
return {
Status: Status.Data,
stationFiltersStore,
mainStore,
apiStore
apiStore,
popupStore
};
},
+23 -9
View File
@@ -36,7 +36,11 @@
<div class="train-driver">
<b
v-if="apiStore.donatorsData.includes(train.driverName)"
:title="$t('donations.driver-message')"
@mouseenter="
popupStore.onPopUpShow($event, 'DonatorPopUp', $t('donations.driver-message'))
"
@mousemove="popupStore.onPopUpMove"
@mouseleave="popupStore.onPopUpHide"
>
{{ train.driverName }}
<img src="/images/icon-diamond.svg" alt="donator diamond icon" />
@@ -47,14 +51,22 @@
<div class="general-timetable" v-if="train.timetableData">
<strong>{{ train.timetableData.route.replace('|', ' - ') }}</strong>
<img
<span
v-if="getSceneriesWithComments(train.timetableData).length > 0"
class="image-warning"
src="/images/icon-warning.svg"
:title="`${$t('trains.timetable-comments')} (${getSceneriesWithComments(
train.timetableData
)})`"
/>
@mouseenter="
popupStore.onPopUpShow(
$event,
'TrainCommentsPopUp',
`${$t('trains.timetable-comments')} (${getSceneriesWithComments(
train.timetableData
)})`
)
"
@mousemove="popupStore.onPopUpMove"
@mouseleave="popupStore.onPopUpHide"
>
<img class="image-warning" src="/images/icon-warning.svg" />
</span>
</div>
<hr style="margin: 0.25em 0" />
@@ -125,6 +137,7 @@ import ProgressBar from '../Global/ProgressBar.vue';
import { useMainStore } from '../../store/mainStore';
import { useApiStore } from '../../store/apiStore';
import StockList from '../Global/StockList.vue';
import { usePopupStore } from '../../store/popupStore';
export default defineComponent({
mixins: [trainInfoMixin, styleMixin],
@@ -144,7 +157,8 @@ export default defineComponent({
data() {
return {
store: useMainStore(),
apiStore: useApiStore()
apiStore: useApiStore(),
popupStore: usePopupStore()
};
}
});
+4
View File
@@ -48,6 +48,10 @@
"confirm-button": "UPDATE NOW",
"later-button": "LATER"
},
"vehicle-preview": {
"loading": "Loading preview...",
"error": "Oops! The vehicle preview seems to be missing! :/"
},
"data-status": {
"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!",
+4
View File
@@ -38,6 +38,10 @@
"footer": {
"discord": "Serwer Discord Stacjownika"
},
"vehicle-preview": {
"loading": "Ładowanie podglądu...",
"error": "Ups! Nie znaleziono podglądu pojazdu! :/"
},
"data-status": {
"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!",
+34
View File
@@ -0,0 +1,34 @@
import { defineStore } from 'pinia';
export const usePopupStore = defineStore('popupStore', {
state: () => ({
popupPosition: { x: 0, y: 0 },
currentPopupComponent: null as
| null
| 'DonatorPopUp'
| 'TrainCommentsPopUp'
| 'VehiclePreviewPopUp',
currentPopupContent: '',
donatorPopupVisible: false
}),
actions: {
onPopUpShow(e: MouseEvent, componentKey: typeof this.currentPopupComponent, value?: string) {
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 {
region: { id: string; value: string };
isOffline: boolean;
isNewUpdate: boolean;
dispatcherStatsName: string;
dispatcherStatsData?: API.DispatcherStats.Response;
driverStatsName: string;
driverStatsData?: API.DriverStats.Response;
driverStatsStatus: Status.Data;
chosenModalTrainId?: string;
blockScroll: boolean;
modalLastClickedTarget: EventTarget | null;
}