mirror of
https://github.com/Spythere/pragotron-td2.git
synced 2026-05-03 13:38:14 +00:00
546 lines
14 KiB
Vue
546 lines
14 KiB
Vue
<template>
|
|
<div class="pragotron">
|
|
<div class="pragotron_content">
|
|
<div class="wrapper" ref="pragotron">
|
|
<div class="top-pane">
|
|
<span class="title">
|
|
<div>{{ mainStore.selectedCheckpointName.toUpperCase() }}</div>
|
|
</span>
|
|
<div class="headers">
|
|
<span>GODZ.</span>
|
|
<span>POCIĄG</span>
|
|
<span>PRZEZ</span>
|
|
<span>DO STACJI</span>
|
|
<span>OPÓŹNIONY</span>
|
|
</div>
|
|
</div>
|
|
<div class="table">
|
|
<div class="row" v-for="(departure, i) in departureTable" :key="i">
|
|
<div class="row-content">
|
|
<span class="departure-date">
|
|
<transition name="slot-anim" mode="out-in">
|
|
<span class="slider-slot-digit" :key="departure.tableValues.dateDigits[0]">{{
|
|
departure.tableValues.dateDigits[0]
|
|
}}</span>
|
|
</transition>
|
|
<transition name="slot-anim" mode="out-in">
|
|
<span class="slider-slot-digit" :key="departure.tableValues.dateDigits[1]">
|
|
{{ departure.tableValues.dateDigits[1] }}</span
|
|
>
|
|
</transition>
|
|
<span :key="departure.tableValues.dateDigits[1]"> : </span>
|
|
<transition name="slot-anim" mode="out-in">
|
|
<span class="slider-slot-digit" :key="departure.tableValues.dateDigits[2]">
|
|
{{ departure.tableValues.dateDigits[2] }}
|
|
</span>
|
|
</transition>
|
|
<transition name="slot-anim" mode="out-in">
|
|
<span class="slider-slot-digit" :key="departure.tableValues.dateDigits[3]">
|
|
{{ departure.tableValues.dateDigits[3] }}
|
|
</span>
|
|
</transition>
|
|
</span>
|
|
<span class="train-class">
|
|
<transition name="slot-anim" mode="out-in">
|
|
<div class="slider-slot" :key="departure.tableValues.trainNumber">
|
|
{{ departure.tableValues.trainNumber }}
|
|
</div>
|
|
</transition>
|
|
</span>
|
|
<span class="route-via">
|
|
<transition name="slot-anim" mode="out-in">
|
|
<div class="slider-slot" :key="departure.tableValues.routeVia">
|
|
{{ abbrevStationName(departure.tableValues.routeVia) }}
|
|
</div>
|
|
</transition>
|
|
</span>
|
|
<span class="route-to">
|
|
<transition name="slot-anim" mode="out-in">
|
|
<div class="slider-slot" :key="departure.tableValues.routeTo">
|
|
{{ abbrevStationName(departure.tableValues.routeTo) }}
|
|
</div>
|
|
</transition>
|
|
</span>
|
|
<span class="delay">
|
|
<transition name="slot-anim" mode="out-in">
|
|
<div class="slider-slot" :key="departure.delayMinutes">
|
|
{{ departure.delayMinutes > 0 ? departure.delayMinutes + ' min' : '' }}
|
|
</div>
|
|
</transition>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<audio loop ref="audio">
|
|
<source src="/pragotron.ogg" type="audio/ogg" />
|
|
<source src="/pragotron.flac" type="audio/mpeg" />
|
|
<source src="/pragotron.mp3" type="audio/mpeg" />
|
|
</audio>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { defineComponent } from 'vue';
|
|
|
|
import { useMainStore } from '../stores/mainStore';
|
|
import { useApiStore } from '../stores/apiStore';
|
|
import { RowIndex, type ITableRow } from '../typings/common';
|
|
|
|
const departureInfoEmptyObj: ITableRow = {
|
|
timetableId: -1,
|
|
|
|
routeTo: '',
|
|
routeVia: '',
|
|
|
|
trainNumber: '',
|
|
|
|
date: new Date(0),
|
|
dateDigits: ['', '', '', ''],
|
|
|
|
arrivalTimestamp: 0,
|
|
departureTimestamp: 0,
|
|
checkpointName: '',
|
|
|
|
delayMinutes: 0,
|
|
|
|
tableValues: {
|
|
routeTo: '',
|
|
routeVia: '',
|
|
trainNumber: '',
|
|
|
|
currentRowIndexes: [0, 0, 0, 0, 0, 0, 0],
|
|
dateDigits: ['', '', '', '']
|
|
}
|
|
};
|
|
|
|
export default defineComponent({
|
|
props: {
|
|
stationName: {
|
|
type: String,
|
|
required: true
|
|
},
|
|
|
|
region: {
|
|
type: String,
|
|
default: 'pl1'
|
|
}
|
|
},
|
|
|
|
data: () => ({
|
|
mainStore: useMainStore(),
|
|
apiStore: useApiStore(),
|
|
|
|
includeNonPassenger: true,
|
|
includeArrivals: true,
|
|
|
|
isAnimationRunning: true,
|
|
lastRefreshTime: 0,
|
|
|
|
animatingStatus: 'init' as 'init' | 'running' | 'complete',
|
|
|
|
departureTable: Array.from({ length: 7 })
|
|
.fill(0)
|
|
.map(() => ({ ...departureInfoEmptyObj })) as ITableRow[],
|
|
departureRoutes: [''],
|
|
dateDigits: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ''],
|
|
trainNumbersSet: new Set<string>(['']),
|
|
|
|
currentRouteIndex: 0,
|
|
currentDateDigitIndex: 0,
|
|
|
|
currentRowAnimating: 0
|
|
}),
|
|
|
|
async created() {
|
|
// this.selectDefaultCheckpoint();
|
|
|
|
window.addEventListener('resize', () => {
|
|
this.resizeTable();
|
|
});
|
|
},
|
|
|
|
activated() {
|
|
this.mainStore.selectedStationName = this.stationName;
|
|
|
|
this.resizeTable();
|
|
|
|
this.selectDefaultCheckpoint();
|
|
this.shuffleRoutes();
|
|
|
|
this.isAnimationRunning = true;
|
|
requestAnimationFrame(this.update);
|
|
},
|
|
|
|
deactivated() {
|
|
this.isAnimationRunning = false;
|
|
this.animatingStatus = 'init';
|
|
},
|
|
|
|
watch: {
|
|
filledTable: {
|
|
deep: true,
|
|
handler(value) {
|
|
for (let i = 0; i < this.departureTable.length; i++) {
|
|
if (i <= value.length - 1) {
|
|
const updateInfo = value[i];
|
|
const existingInfo = this.departureTable[i];
|
|
|
|
this.departureTable[i] = { ...updateInfo };
|
|
this.departureTable[i].tableValues.routeTo = existingInfo.routeTo;
|
|
this.departureTable[i].tableValues.routeVia = existingInfo.routeVia;
|
|
this.departureTable[i].tableValues.trainNumber = existingInfo.trainNumber;
|
|
|
|
this.departureTable[i].tableValues.dateDigits = [
|
|
...existingInfo.tableValues.dateDigits
|
|
];
|
|
this.departureTable[i].tableValues.currentRowIndexes = [
|
|
...existingInfo.tableValues.currentRowIndexes
|
|
];
|
|
} else {
|
|
this.departureTable[i] = {
|
|
...this.departureTable[i],
|
|
timetableId: -1,
|
|
|
|
routeTo: '',
|
|
routeVia: '',
|
|
|
|
trainNumber: '',
|
|
|
|
date: new Date(0),
|
|
dateDigits: ['', '', '', ''],
|
|
|
|
arrivalTimestamp: 0,
|
|
departureTimestamp: 0,
|
|
checkpointName: '',
|
|
|
|
delayMinutes: 0
|
|
};
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
animatingStatus(val: typeof this.animatingStatus) {
|
|
if (val == 'running' && this.mainStore.filters.soundsEnabled)
|
|
(this.$refs['audio'] as HTMLAudioElement).play().catch((err) => {
|
|
console.error('Dźwięk nie mógł zostać odtworzony:', err);
|
|
});
|
|
else {
|
|
(this.$refs['audio'] as HTMLAudioElement).currentTime = 0;
|
|
(this.$refs['audio'] as HTMLAudioElement).pause();
|
|
}
|
|
},
|
|
|
|
'apiStore.activeData'(_val, prevVal) {
|
|
if (prevVal == undefined) {
|
|
this.selectDefaultCheckpoint();
|
|
}
|
|
}
|
|
},
|
|
|
|
computed: {
|
|
filledTable() {
|
|
const filteredData = this.apiStore.activeData?.trains
|
|
.reduce((list, train) => {
|
|
if (!train.timetable) return list;
|
|
|
|
const timetable = train.timetable;
|
|
|
|
const stopInfo = timetable.stopList.find(
|
|
(sp) =>
|
|
sp.stopNameRAW.toLowerCase() == this.mainStore.selectedCheckpointName.toLowerCase()
|
|
);
|
|
|
|
if (!stopInfo || stopInfo.confirmed) return list;
|
|
|
|
const stopInfoIndex = timetable.stopList.indexOf(stopInfo);
|
|
const { departureTimestamp, departureDelay, arrivalTimestamp, departureLine } = stopInfo;
|
|
|
|
const routeVia =
|
|
timetable.stopList.find((stop, i) => {
|
|
return (
|
|
i > stopInfoIndex &&
|
|
// i < timetable.stopList.length - 1,
|
|
stop.stopName.includes('strong') &&
|
|
stop.stopTime &&
|
|
stop.stopTime > 0
|
|
);
|
|
})?.stopNameRAW || '';
|
|
|
|
const date = departureLine ? new Date(departureTimestamp) : new Date(arrivalTimestamp);
|
|
|
|
// [HH, MM, SS] - nienawidzę dat w JavaScripcie
|
|
const dateArray = date.toLocaleString('pl-PL').split(', ')[1].split(':') || [
|
|
'',
|
|
'',
|
|
'',
|
|
''
|
|
];
|
|
|
|
// [H,H,M,M] - ZABIJCIE MNIE BŁAGAM
|
|
const dateDigits = [...dateArray[0].split(''), ...dateArray[1].split('')];
|
|
|
|
const routeTo = timetable.route.split('|')[1];
|
|
|
|
list.push({
|
|
trainNumber: `${timetable.category} ${train.trainNo}`,
|
|
timetableId: timetable.timetableId,
|
|
routeTo: routeTo,
|
|
routeVia: routeVia,
|
|
date,
|
|
dateDigits,
|
|
delayMinutes: departureDelay,
|
|
checkpointName: this.mainStore.selectedCheckpointName.toLowerCase(),
|
|
|
|
arrivalTimestamp,
|
|
departureTimestamp,
|
|
|
|
tableValues: {
|
|
routeTo: '',
|
|
routeVia: '',
|
|
trainNumber: '',
|
|
dateDigits: ['', '', '', ''],
|
|
currentRowIndexes: [0, 0, 0, 0, 0, 0, 0]
|
|
}
|
|
});
|
|
|
|
if (!this.departureRoutes.includes(routeVia)) this.departureRoutes.push(routeVia);
|
|
if (!this.departureRoutes.includes(routeTo)) this.departureRoutes.push(routeTo);
|
|
|
|
this.trainNumbersSet.add(`${timetable.category} ${train.trainNo}`);
|
|
|
|
return list;
|
|
}, [] as ITableRow[])
|
|
.filter(
|
|
(dep) =>
|
|
(this.mainStore.filters.nonPassenger || !/^[T|L|Z|P]/g.test(dep.trainNumber)) &&
|
|
(this.mainStore.filters.terminating || dep.departureTimestamp)
|
|
)
|
|
.sort((dep1, dep2) => (dep1.date?.getTime() || 0) - (dep2.date?.getTime() || 0));
|
|
|
|
return filteredData;
|
|
}
|
|
},
|
|
|
|
methods: {
|
|
resizeTable() {
|
|
const elRef = this.$refs['pragotron'] as HTMLElement;
|
|
if (!elRef) return;
|
|
|
|
const scale = Math.min(
|
|
window.innerWidth / elRef.clientWidth,
|
|
window.innerHeight / elRef.clientHeight,
|
|
1
|
|
);
|
|
|
|
elRef.style.transform = `scale(${scale})`;
|
|
},
|
|
|
|
selectDefaultCheckpoint() {
|
|
this.mainStore.selectedCheckpointName =
|
|
this.mainStore.selectedStation?.stationCheckpoints[0] || this.stationName;
|
|
},
|
|
|
|
abbrevStationName(name: string) {
|
|
return name.toUpperCase();
|
|
},
|
|
|
|
update(time: number) {
|
|
if (!this.isAnimationRunning) return;
|
|
|
|
if (time >= this.lastRefreshTime + 160) {
|
|
this.updateTableRows();
|
|
|
|
this.currentRouteIndex = (this.currentRouteIndex + 1) % this.departureRoutes.length;
|
|
this.lastRefreshTime = time;
|
|
}
|
|
|
|
requestAnimationFrame(this.update);
|
|
},
|
|
|
|
updateTableRows() {
|
|
let isCurrentTickAnimating = false;
|
|
|
|
for (let i = 0; i < this.departureTable.length; i++) {
|
|
const dep = this.departureTable[i];
|
|
|
|
if (dep.tableValues.routeTo.toLowerCase() != dep.routeTo.toLowerCase()) {
|
|
dep.tableValues.routeTo =
|
|
this.departureRoutes[dep.tableValues.currentRowIndexes[RowIndex.RouteTo]];
|
|
|
|
dep.tableValues.currentRowIndexes[RowIndex.RouteTo] =
|
|
(dep.tableValues.currentRowIndexes[RowIndex.RouteTo] + 1) % this.departureRoutes.length;
|
|
|
|
isCurrentTickAnimating = true;
|
|
}
|
|
|
|
if (dep.tableValues.routeVia.toLowerCase() != dep.routeVia.toLowerCase()) {
|
|
dep.tableValues.routeVia =
|
|
this.departureRoutes[dep.tableValues.currentRowIndexes[RowIndex.RouteVia]];
|
|
|
|
dep.tableValues.currentRowIndexes[RowIndex.RouteVia] =
|
|
(dep.tableValues.currentRowIndexes[RowIndex.RouteVia] + 1) %
|
|
this.departureRoutes.length;
|
|
|
|
isCurrentTickAnimating = true;
|
|
}
|
|
|
|
dep.tableValues.dateDigits.forEach((digit, j) => {
|
|
if (dep.dateDigits[j] != digit) {
|
|
dep.tableValues.dateDigits[j] = this.dateDigits[dep.tableValues.currentRowIndexes[j]];
|
|
dep.tableValues.currentRowIndexes[j] =
|
|
(dep.tableValues.currentRowIndexes[j] + 1) % this.dateDigits.length;
|
|
|
|
isCurrentTickAnimating = true;
|
|
}
|
|
});
|
|
|
|
if (dep.trainNumber != dep.tableValues.trainNumber) {
|
|
dep.tableValues.trainNumber = Array.from(this.trainNumbersSet)[
|
|
dep.tableValues.currentRowIndexes[RowIndex.TrainNumber]
|
|
];
|
|
|
|
dep.tableValues.currentRowIndexes[RowIndex.TrainNumber] =
|
|
(dep.tableValues.currentRowIndexes[RowIndex.TrainNumber] + 1) %
|
|
this.trainNumbersSet.size;
|
|
|
|
isCurrentTickAnimating = true;
|
|
}
|
|
}
|
|
|
|
this.animatingStatus = isCurrentTickAnimating ? 'running' : 'complete';
|
|
},
|
|
|
|
shuffleRoutes() {
|
|
this.departureRoutes.sort(() => Math.random() - 0.5);
|
|
}
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
/* ****** ANIMATION ****** */
|
|
|
|
.slot-anim {
|
|
&-enter-active,
|
|
&-leave-active {
|
|
transition: all 80ms;
|
|
}
|
|
|
|
&-enter-from,
|
|
&-leave-to {
|
|
transform: rotateX(90deg) perspective(300px);
|
|
}
|
|
}
|
|
|
|
.digit-slot-anim {
|
|
&-enter-active,
|
|
&-leave-active {
|
|
transition: all 150ms ease-out;
|
|
}
|
|
|
|
&-enter-from,
|
|
&-leave-to {
|
|
opacity: 0;
|
|
}
|
|
}
|
|
|
|
/* ************** */
|
|
|
|
.pragotron_content {
|
|
display: flex;
|
|
justify-content: center;
|
|
padding: 1em;
|
|
}
|
|
|
|
.wrapper {
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-width: 1400px;
|
|
min-height: 700px;
|
|
padding: 2em;
|
|
|
|
transform-origin: top;
|
|
}
|
|
|
|
.top-pane > .headers,
|
|
.row-content {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr 2fr 2fr 1fr;
|
|
gap: 0 10px;
|
|
padding: 0 10px;
|
|
}
|
|
|
|
.top-pane {
|
|
background-color: white;
|
|
color: black;
|
|
height: 180px;
|
|
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: space-between;
|
|
|
|
.title {
|
|
padding: 0;
|
|
|
|
font-size: 3.5em;
|
|
}
|
|
|
|
.headers {
|
|
text-align: center;
|
|
|
|
font-size: 1.35em;
|
|
}
|
|
}
|
|
|
|
.table {
|
|
background: white;
|
|
flex-grow: 1;
|
|
|
|
display: grid;
|
|
grid-template-rows: repeat(7, 1fr);
|
|
gap: 5px 0;
|
|
}
|
|
|
|
.row {
|
|
&-content {
|
|
height: 100%;
|
|
|
|
align-items: center;
|
|
color: white;
|
|
font-size: 1.3em;
|
|
|
|
background: #1a1a1a;
|
|
|
|
span {
|
|
display: flex;
|
|
justify-content: center;
|
|
}
|
|
}
|
|
}
|
|
|
|
.departure-date {
|
|
background: black;
|
|
|
|
span {
|
|
background: black;
|
|
height: 2em;
|
|
line-height: 2em;
|
|
flex-grow: 2;
|
|
width: 100%;
|
|
}
|
|
}
|
|
|
|
.slider-slot {
|
|
background: #010101;
|
|
width: 100%;
|
|
height: 2em;
|
|
line-height: 2em;
|
|
|
|
will-change: contents;
|
|
}
|
|
</style>
|