rework reaktywności danych z API i WS

This commit is contained in:
2023-10-30 23:19:17 +01:00
parent 12ece46089
commit 8de03b9210
24 changed files with 501 additions and 446 deletions
+6 -32
View File
@@ -32,20 +32,19 @@
</template>
<script lang="ts">
import { computed, defineComponent, provide, ref, watch } from 'vue';
import { defineComponent, watch } from 'vue';
import Clock from './components/App/Clock.vue';
import packageInfo from '.././package.json';
import { useStore } from './store/store';
import StatusIndicator from './components/App/StatusIndicator.vue';
import SelectBox from './components/Global/SelectBox.vue';
import { useStore } from './store/store';
import TrainModal from './components/Global/TrainModal.vue';
import StorageManager from './scripts/managers/storageManager';
import AppHeader from './components/App/AppHeader.vue';
import axios from 'axios';
import useCustomSW from './mixins/useCustomSW';
export default defineComponent({
components: {
@@ -56,31 +55,9 @@ export default defineComponent({
AppHeader
},
setup() {
const store = useStore();
store.connectToAPI();
useCustomSW();
const isFilterCardVisible = ref(false);
provide('isFilterCardVisible', isFilterCardVisible);
return {
store,
isFilterCardVisible,
onlineDispatchers: computed(() =>
store.stationList.filter(
(station) => station.onlineInfo && station.onlineInfo.region == store.region.id
)
),
dispatcherDataStatus: store.dataStatuses.dispatchers
};
},
data: () => ({
VERSION: packageInfo.version,
store: useStore(),
currentLang: 'pl',
releaseURL: '',
@@ -89,6 +66,7 @@ export default defineComponent({
created() {
this.loadLang();
this.store.connectToAPI();
this.store.isOffline = !window.navigator.onLine;
@@ -116,12 +94,8 @@ export default defineComponent({
watch(
() => this.store.blockScroll,
(value) => {
if (value) {
document.body.classList.add('no-scroll');
return;
}
document.body.classList.remove('no-scroll');
if (value) document.body.classList.add('no-scroll');
else document.body.classList.remove('no-scroll');
}
);
},
+5 -3
View File
@@ -82,28 +82,30 @@ export default defineComponent({
required: true
}
},
setup() {
return {
store: useStore()
};
},
methods: {
changeRegion(region: { id: string; value: string }) {
this.store.changeRegion(region);
},
changeLang(lang: string) {
this.$emit('changeLang', lang);
}
},
computed: {
onlineTrainsCount() {
return this.store.trainList.filter((train) => train.online).length;
},
onlineDispatchersCount() {
return this.store.stationList.filter(
(station) => station.onlineInfo && station.onlineInfo.region == this.store.region.id
).length;
return this.store.onlineSceneryList.length;
},
factorU() {
+13
View File
@@ -87,6 +87,19 @@ export default defineComponent({
};
},
watch: {
'$route.query': {
immediate: true,
handler(newVal) {
if (newVal.region) {
const item = this.itemList.find((it) => it.id == newVal.region);
if (item) this.selectedItem = item;
}
}
}
},
methods: {
selectOption(item: Item) {
this.selectedItem = item;
@@ -76,6 +76,7 @@ import { URLs } from '../../scripts/utils/apiURLs';
import Loading from '../Global/Loading.vue';
import styleMixin from '../../mixins/styleMixin';
import listObserverMixin from '../../mixins/listObserverMixin';
import { OnlineScenery } from '../../scripts/interfaces/store/storeTypes';
export default defineComponent({
name: 'SceneryDispatchersHistory',
@@ -85,6 +86,10 @@ export default defineComponent({
station: {
type: Object as PropType<Station>,
required: true
},
onlineScenery: {
type: Object as PropType<OnlineScenery>,
required: false
}
},
+7 -1
View File
@@ -8,19 +8,25 @@
{{ $t('scenery.abbrev') }} <b>{{ station.generalInfo?.abbr }}</b>
</div>
<div class="scenery-hash" v-if="station.onlineInfo?.hash">#{{ station.onlineInfo.hash }}</div>
<div class="scenery-hash" v-if="onlineScenery?.hash">#{{ onlineScenery.hash }}</div>
</section>
</template>
<script lang="ts">
import { PropType, defineComponent } from 'vue';
import Station from '../../scripts/interfaces/Station';
import { OnlineScenery } from '../../scripts/interfaces/store/storeTypes';
export default defineComponent({
props: {
station: {
type: Object as PropType<Station>,
required: true
},
onlineScenery: {
type: Object as PropType<OnlineScenery>,
required: false
}
}
});
+9 -9
View File
@@ -1,6 +1,6 @@
<template>
<div class="scenery-info">
<section v-if="!timetableOnly">
<section>
<div class="scenery-info-general" v-if="station.generalInfo">
<SceneryInfoIcons :station="station" />
@@ -68,14 +68,14 @@
<div style="margin: 2em 0; height: 2px; background-color: white"></div>
<!-- info dispatcher -->
<SceneryInfoDispatcher :station="station" :onlineFrom="onlineFrom" />
<SceneryInfoDispatcher :onlineScenery="onlineScenery" />
<div class="info-lists">
<!-- user list -->
<SceneryInfoUserList :station="station" />
<SceneryInfoUserList :onlineScenery="onlineScenery" />
<!-- spawn list -->
<SceneryInfoSpawnList :station="station" />
<SceneryInfoSpawnList :onlineScenery="onlineScenery" />
</div>
</section>
</div>
@@ -90,6 +90,7 @@ import SceneryInfoUserList from './SceneryInfo/SceneryInfoUserList.vue';
import SceneryInfoSpawnList from './SceneryInfo/SceneryInfoSpawnList.vue';
import SceneryInfoRoutes from './SceneryInfo/SceneryInfoRoutes.vue';
import Station from '../../scripts/interfaces/Station';
import { OnlineScenery } from '../../scripts/interfaces/store/storeTypes';
export default defineComponent({
components: {
@@ -105,12 +106,11 @@ export default defineComponent({
required: true
},
timetableOnly: Boolean
onlineScenery: {
type: Object as PropType<OnlineScenery>,
required: false
}
},
data: () => ({
onlineFrom: -1
})
});
</script>
@@ -1,35 +1,30 @@
<template>
<section class="info-dispatcher">
<div class="dispatcher" v-if="station.onlineInfo">
<div class="dispatcher" v-if="onlineScenery">
<span
class="dispatcher_level"
:style="
calculateExpStyle(
station.onlineInfo.dispatcherExp,
station.onlineInfo.dispatcherIsSupporter
)
"
:style="calculateExpStyle(onlineScenery.dispatcherExp, onlineScenery.dispatcherIsSupporter)"
>
{{ station.onlineInfo.dispatcherExp > 1 ? station.onlineInfo.dispatcherExp : 'L' }}
{{ onlineScenery.dispatcherExp > 1 ? onlineScenery.dispatcherExp : 'L' }}
</span>
<router-link
class="dispatcher_name"
:to="`/journal/dispatchers?dispatcherName=${station.onlineInfo.dispatcherName}`"
:to="`/journal/dispatchers?dispatcherName=${onlineScenery.dispatcherName}`"
>
{{ station.onlineInfo.dispatcherName }}
{{ onlineScenery.dispatcherName }}
</router-link>
<span class="dispatcher_likes text--primary">
<img src="/images/icon-like.svg" alt="Likes count icon" />
<span>{{ station.onlineInfo?.dispatcherRate || '0' }}</span>
<span>{{ onlineScenery?.dispatcherRate || '0' }}</span>
</span>
</div>
<StationStatusBadge
:statusID="station.onlineInfo?.statusID"
:isOnline="station.onlineInfo ? true : false"
:statusTimestamp="station.onlineInfo?.statusTimestamp"
:statusID="onlineScenery?.statusID"
:isOnline="onlineScenery ? true : false"
:statusTimestamp="onlineScenery?.statusTimestamp"
/>
</section>
</template>
@@ -39,19 +34,15 @@ import { PropType, defineComponent } from 'vue';
import dateMixin from '../../../mixins/dateMixin';
import routerMixin from '../../../mixins/routerMixin';
import styleMixin from '../../../mixins/styleMixin';
import Station from '../../../scripts/interfaces/Station';
import StationStatusBadge from '../../Global/StationStatusBadge.vue';
import { OnlineScenery } from '../../../scripts/interfaces/store/storeTypes';
export default defineComponent({
mixins: [styleMixin, dateMixin, routerMixin],
props: {
station: {
type: Object as PropType<Station>,
required: true
},
onlineFrom: {
type: Number,
default: -1
onlineScenery: {
type: Object as PropType<OnlineScenery>,
required: false
}
},
components: { StationStatusBadge }
@@ -3,14 +3,14 @@
<h3 class="spawn-header section-header">
<img src="/images/icon-spawn.svg" alt="Open spawns icon" />
&nbsp;{{ $t('scenery.spawns') }} &nbsp;
<span class="text--primary">{{ station.onlineInfo?.spawns.length || '0' }}</span>
<span class="text--primary">{{ onlineScenery?.spawns.length || '0' }}</span>
</h3>
<span v-if="station.onlineInfo">
<span v-if="onlineScenery">
<span
class="badge spawn"
v-for="(spawn, i) in sortedSpawns"
:key="spawn.spawnName + station.onlineInfo?.dispatcherName + i"
:key="spawn.spawnName + onlineScenery?.dispatcherName + i"
:data-electrified="spawn.isElectrified"
>
<span class="spawn_name">{{ spawn.spawnName }}</span>
@@ -18,9 +18,7 @@
</span>
</span>
<span
class="badge spawn badge-none"
v-if="!station.onlineInfo || station.onlineInfo.spawns.length == 0"
<span class="badge spawn badge-none" v-if="!onlineScenery || onlineScenery.spawns.length == 0"
>{{ $t('scenery.no-spawns') }}
</span>
</section>
@@ -28,21 +26,21 @@
<script lang="ts">
import { PropType, defineComponent } from 'vue';
import Station from '../../../scripts/interfaces/Station';
import { OnlineScenery } from '../../../scripts/interfaces/store/storeTypes';
export default defineComponent({
props: {
station: {
type: Object as PropType<Station>,
required: true
onlineScenery: {
type: Object as PropType<OnlineScenery>,
required: false
}
},
computed: {
sortedSpawns() {
if (!this.station.onlineInfo) return [];
if (!this.onlineScenery) return [];
return [...this.station.onlineInfo.spawns].sort((s1, s2) =>
return [...this.onlineScenery.spawns].sort((s1, s2) =>
s1.spawnLength < s2.spawnLength ? 1 : -1
);
}
@@ -3,12 +3,12 @@
<h3 class="user-header section-header">
<img src="/images/icon-user.svg" alt="Users icon" />
&nbsp;{{ $t('scenery.users') }} &nbsp;
<span class="text--primary">{{ station.onlineInfo?.currentUsers || '0' }}</span
>&nbsp;/&nbsp;<span class="text--primary">{{ station.onlineInfo?.maxUsers || '0' }}</span>
<span class="text--primary">{{ onlineScenery?.currentUsers || 0 }}</span
>&nbsp;/&nbsp;<span class="text--primary">{{ onlineScenery?.maxUsers || 0 }}</span>
</h3>
<div
v-for="train in computedStationTrains"
v-for="train in onlineScenery?.stationTrains"
class="badge user"
:class="train.stopStatus"
:key="train.trainId"
@@ -22,7 +22,7 @@
<div
class="badge user badge-none"
v-if="!computedStationTrains || computedStationTrains.length == 0"
v-if="!onlineScenery?.scheduledTrains || onlineScenery.scheduledTrains.length == 0"
>
{{ $t('scenery.no-users') }}
</div>
@@ -30,45 +30,19 @@
</template>
<script lang="ts">
import { PropType, computed, defineComponent } from 'vue';
import { PropType, defineComponent } from 'vue';
import modalTrainMixin from '../../../mixins/modalTrainMixin';
import routerMixin from '../../../mixins/routerMixin';
import Station from '../../../scripts/interfaces/Station';
import { useStore } from '../../../store/store';
import { OnlineScenery } from '../../../scripts/interfaces/store/storeTypes';
export default defineComponent({
mixins: [routerMixin, modalTrainMixin],
props: {
station: {
type: Object as PropType<Station>,
required: true
onlineScenery: {
type: Object as PropType<OnlineScenery>,
required: false
}
},
setup(props) {
const store = useStore();
const computedStationTrains = computed(() => {
if (!props.station) return [];
const station = props.station as Station;
if (!station.onlineInfo) return [];
if (!station.onlineInfo.stationTrains) return [];
return station.onlineInfo.stationTrains.map((train) => {
const scheduledTrainStatus = station.onlineInfo?.scheduledTrains?.find(
(st) => st.trainNo === train.trainNo
);
return {
...train,
stopStatus: scheduledTrainStatus?.stopStatus || 'no-timetable'
};
});
});
return { computedStationTrains, store };
}
});
</script>
+30 -48
View File
@@ -6,14 +6,12 @@
<span>{{ $t('scenery.timetables') }}</span>
<span>
<span class="text--primary">{{
station.onlineInfo?.scheduledTrains?.length || '0'
}}</span>
<span class="text--primary">{{ onlineScenery?.scheduledTrains?.length || '0' }}</span>
<span> / </span>
<span class="text--grayed">
{{
station.onlineInfo?.scheduledTrains?.filter((train) => train.stopInfo.confirmed)
.length || '0'
onlineScenery?.scheduledTrains?.filter((train) => train.stopInfo.confirmed).length ||
'0'
}}
</span>
</span>
@@ -59,7 +57,7 @@
<span
class="timetable-item empty"
v-else-if="computedScheduledTrains.length == 0 && !station.onlineInfo"
v-else-if="computedScheduledTrains.length == 0 && !onlineScenery"
>
{{ $t('scenery.offline') }}
</span>
@@ -186,6 +184,7 @@ import Station from '../../scripts/interfaces/Station';
import { useStore } from '../../store/store';
import modalTrainMixin from '../../mixins/modalTrainMixin';
import ScheduledTrainStatus from './ScheduledTrainStatus.vue';
import { OnlineScenery } from '../../scripts/interfaces/store/storeTypes';
export default defineComponent({
name: 'SceneryTimetable',
@@ -199,9 +198,9 @@ export default defineComponent({
type: Object as PropType<Station>,
required: true
},
timetableOnly: {
type: Boolean
onlineScenery: {
type: Object as PropType<OnlineScenery>,
required: false
}
},
@@ -229,36 +228,9 @@ export default defineComponent({
: props.station?.generalInfo?.checkpoints[0].checkpointName || null
);
const computedScheduledTrains = computed(() => {
if (!props.station) return [];
const station = props.station as Station;
let scheduledTrains =
station.generalInfo?.checkpoints.find((cp) => cp.checkpointName === chosenCheckpoint.value)
?.scheduledTrains ||
station.onlineInfo?.scheduledTrains ||
[];
if (!scheduledTrains) return [];
return (
scheduledTrains.sort((a, b) => {
if (a.stopStatusID > b.stopStatusID) return 1;
if (a.stopStatusID < b.stopStatusID) return -1;
if (a.stopInfo.arrivalTimestamp > b.stopInfo.arrivalTimestamp) return 1;
if (a.stopInfo.arrivalTimestamp < b.stopInfo.arrivalTimestamp) return -1;
return a.stopInfo.departureTimestamp > b.stopInfo.departureTimestamp ? 1 : -1;
}) || []
);
});
return {
currentURL,
chosenCheckpoint,
computedScheduledTrains,
store
};
},
@@ -269,27 +241,37 @@ export default defineComponent({
if (this.chosenCheckpoint) url += `&checkpoint=${this.chosenCheckpoint}`;
return url;
},
computedScheduledTrains() {
return (
this.onlineScenery?.scheduledTrains
?.filter(
(train) =>
train.checkpointName.toLocaleLowerCase() ==
(this.chosenCheckpoint || this.station.name).toLocaleLowerCase()
)
.sort((a, b) => {
if (a.stopStatusID > b.stopStatusID) return 1;
if (a.stopStatusID < b.stopStatusID) return -1;
if (a.stopInfo.arrivalTimestamp > b.stopInfo.arrivalTimestamp) return 1;
if (a.stopInfo.arrivalTimestamp < b.stopInfo.arrivalTimestamp) return -1;
return a.stopInfo.departureTimestamp > b.stopInfo.departureTimestamp ? 1 : -1;
}) || []
);
}
},
methods: {
loadSelectedOption() {
if (!this.station) return;
if (!this.station.generalInfo) return;
if (!this.station.generalInfo.checkpoints) return;
if (this.station.generalInfo.checkpoints.length == 0) return;
if (this.chosenCheckpoint != '') return;
this.chosenCheckpoint = this.station.generalInfo.checkpoints[0].checkpointName;
this.chosenCheckpoint =
this.station.generalInfo?.checkpoints[0]?.checkpointName || this.station.name;
},
setCheckpoint(cp: { checkpointName: string }) {
this.chosenCheckpoint = cp.checkpointName;
},
showTimetableOnlyView() {
this.$router.push(`${this.$route.fullPath}&timetableOnly=1`);
}
}
});
@@ -65,6 +65,7 @@ import Station from '../../scripts/interfaces/Station';
import { URLs } from '../../scripts/utils/apiURLs';
import Loading from '../Global/Loading.vue';
import listObserverMixin from '../../mixins/listObserverMixin';
import { OnlineScenery } from '../../scripts/interfaces/store/storeTypes';
export default defineComponent({
name: 'SceneryTimetablesHistory',
@@ -73,6 +74,10 @@ export default defineComponent({
station: {
type: Object as PropType<Station>,
required: true
},
onlineScenery: {
type: Object as PropType<OnlineScenery>,
required: false
}
},
+2 -2
View File
@@ -1,8 +1,8 @@
<template>
<label @dblclick="handleDbClick">
<input
:value="optionValue"
@input="$emit('update:optionValue', ($event.target as HTMLInputElement).value)"
:checked="optionValue"
@input="$emit('update:optionValue', ($event.target as HTMLInputElement).checked)"
type="checkbox"
:class="option.section"
:name="option.id"
+23 -8
View File
@@ -79,11 +79,19 @@
</span>
<span v-else-if="station.generalInfo.availability == 'abandoned'">
<img src="/images/icon-abandoned.svg" alt="non-public" :title="$t('desc.abandoned')" />
<img
src="/images/icon-abandoned.svg"
alt="non-public"
:title="$t('desc.abandoned')"
/>
</span>
<span v-else-if="station.generalInfo.availability == 'nonPublic'">
<img src="/images/icon-lock.svg" alt="non-public" :title="$t('desc.non-public')" />
<img
src="/images/icon-lock.svg"
alt="non-public"
:title="$t('desc.non-public')"
/>
</span>
<span v-else>
@@ -234,7 +242,7 @@
</td>
<td
class="station_schedules"
class="station_schedules all"
style="width: 30px"
:class="{ inactive: !station.onlineInfo }"
>
@@ -244,20 +252,23 @@
</td>
<td
class="station_schedules"
class="station_schedules unconfirmed"
style="width: 30px"
:class="{ inactive: !station.onlineInfo }"
>
<span style="color: #ccc">
{{
station.onlineInfo?.scheduledTrains?.filter((train) => !train.stopInfo.confirmed)
.length || 0
new Set([
...(station.onlineInfo?.scheduledTrains
?.filter((train) => !train.stopInfo.confirmed)
.map((train) => train.checkpointName) || [])
]).size || 0
}}
</span>
</td>
<td
class="station_schedules"
class="station_schedules confirmed"
style="width: 30px"
:class="{ inactive: !station.onlineInfo }"
>
@@ -336,9 +347,13 @@ export default defineComponent({
if (!station) return;
this.lastSelectedStationName = station.name;
this.$router.push({
name: 'SceneryView',
query: { station: station.name.replaceAll(' ', '_') }
query: {
station: station.name.replaceAll(' ', '_'),
region: this.$route.query.region || undefined
}
});
},
+4
View File
@@ -7,6 +7,7 @@ import plLang from './locales/pl.json';
import { createI18n } from 'vue-i18n';
import { createPinia } from 'pinia';
import useCustomSW from './mixins/useCustomSW';
const i18n = createI18n({
locale: 'pl',
@@ -20,6 +21,9 @@ const i18n = createI18n({
enableLegacy: false
});
// SW
useCustomSW();
const clickOutsideDirective: Directive = {
mounted(el, binding) {
el.clickOutsideEvent = (event: Event) => {
+12 -3
View File
@@ -6,7 +6,10 @@ const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'StationsView',
component: () => import('../views/StationsView.vue')
component: () => import('../views/StationsView.vue'),
props: (route) => ({
region: route.query.region
})
},
{
path: '/trains',
@@ -21,7 +24,11 @@ const routes: Array<RouteRecordRaw> = [
{
path: '/scenery',
name: 'SceneryView',
component: () => import('../views/SceneryView.vue')
component: () => import('../views/SceneryView.vue'),
props: (route) => ({
region: route.query.region,
station: route.query.station
})
},
{
path: '/journal',
@@ -53,9 +60,11 @@ const routes: Array<RouteRecordRaw> = [
];
const router = createRouter({
scrollBehavior(to, from) {
scrollBehavior(to, from, savedPosition) {
if (to.name == 'SceneryView' && from.name) return { el: `.app_main` };
if (savedPosition) return savedPosition;
// if (from.name == 'SceneryView' && to.name == 'StationsView') return { el: `.last-selected`, top: 20 };
},
history: createWebHistory(),
+2
View File
@@ -10,6 +10,8 @@ export enum StopStatus {
}
export interface ScheduledTrain {
checkpointName: string;
trainId: string;
trainNo: number;
+30 -3
View File
@@ -2,17 +2,16 @@ import { Socket } from 'socket.io-client';
import { DataStatus } from '../../enums/DataStatus';
import StationAPIData from '../api/StationAPIData';
import { TrainAPIData } from '../api/TrainAPIData';
import Station from '../Station';
import Train from '../Train';
import { DispatcherStatsAPIData } from '../api/DispatcherStatsAPIData';
import { DriverStatsAPIData } from '../api/DriverStatsAPIData';
import { RollingStockGithubData } from '../github_api/StockInfoGithubData';
import Station from '../Station';
import { ScheduledTrain } from '../ScheduledTrain';
export type Availability = 'default' | 'unavailable' | 'nonPublic' | 'abandoned' | 'nonDefault';
export interface StoreState {
stationList: Station[];
trainList: Train[];
apiData: APIData;
rollingStockData?: RollingStockGithubData;
@@ -91,3 +90,31 @@ export interface StationJSONData {
availability: Availability;
}
export interface StationTrain {
driverName: string;
driverId: number;
trainNo: number;
trainId: string;
stopStatus: string;
}
export interface OnlineScenery {
name: string;
hash: string;
region: string;
maxUsers: number;
currentUsers: number;
spawns: { spawnName: string; spawnLength: number; isElectrified: boolean }[];
dispatcherName: string;
dispatcherRate: number;
dispatcherId: number;
dispatcherExp: number;
dispatcherIsSupporter: boolean;
statusTimestamp: number;
statusID: string;
stationTrains?: StationTrain[];
scheduledTrains?: ScheduledTrain[];
}
+130 -9
View File
@@ -1,6 +1,9 @@
import { ScheduledTrain, StopStatus } from '../interfaces/ScheduledTrain';
import Station from '../interfaces/Station';
import Train from '../interfaces/Train';
import TrainStop from '../interfaces/TrainStop';
import StationAPIData from '../interfaces/api/StationAPIData';
import { StationTrain, StoreState } from '../interfaces/store/storeTypes';
export const getLocoURL = (locoType: string): string =>
`https://rj.td2.info.pl/dist/img/thumbnails/${
@@ -79,7 +82,7 @@ export const getTimestamp = (date: string | null): number => (date ? new Date(da
export const getTrainStopStatus = (
stopInfo: TrainStop,
currentStationName: string,
stationName: string
sceneryName: string
) => {
let stopStatus = StopStatus['arriving'],
stopLabel = '',
@@ -89,23 +92,23 @@ export const getTrainStopStatus = (
stopStatus = StopStatus['terminated'];
stopLabel = 'Skończył bieg';
stopStatusID = 5;
} else if (!stopInfo.terminatesHere && stopInfo.confirmed && currentStationName == stationName) {
} else if (!stopInfo.terminatesHere && stopInfo.confirmed && currentStationName == sceneryName) {
stopStatus = StopStatus['departed'];
stopLabel = 'Odprawiony';
stopStatusID = 2;
} else if (!stopInfo.terminatesHere && stopInfo.confirmed && currentStationName != stationName) {
} else if (!stopInfo.terminatesHere && stopInfo.confirmed && currentStationName != sceneryName) {
stopStatus = StopStatus['departed-away'];
stopLabel = 'Odjechał';
stopStatusID = 4;
} else if (currentStationName == stationName && !stopInfo.stopped) {
} else if (currentStationName == sceneryName && !stopInfo.stopped) {
stopStatus = StopStatus['online'];
stopLabel = 'Na stacji';
stopStatusID = 0;
} else if (currentStationName == stationName && stopInfo.stopped) {
} else if (currentStationName == sceneryName && stopInfo.stopped) {
stopStatus = StopStatus['stopped'];
stopLabel = 'Postój';
stopStatusID = 1;
} else if (currentStationName != stationName) {
} else if (currentStationName != sceneryName) {
stopStatus = StopStatus['arriving'];
stopLabel = 'W drodze';
stopStatusID = 3;
@@ -114,16 +117,16 @@ export const getTrainStopStatus = (
return { stopStatus, stopLabel, stopStatusID };
};
export function getScheduledTrain(
export function getCheckpointTrain(
train: Train,
trainStopIndex: number,
stationName: string
sceneryName: string
): ScheduledTrain {
const timetable = train.timetableData!;
const followingStops = timetable.followingStops;
const trainStop = followingStops[trainStopIndex];
const trainStopStatus = getTrainStopStatus(trainStop, train.currentStationName, stationName);
const trainStopStatus = getTrainStopStatus(trainStop, train.currentStationName, sceneryName);
let prevStationName = '',
nextStationName = '';
@@ -177,6 +180,8 @@ export function getScheduledTrain(
}
return {
checkpointName: trainStop.stopNameRAW,
trainNo: train.trainNo,
trainId: train.trainId,
@@ -206,3 +211,119 @@ export function getScheduledTrain(
prevDepartureLine
};
}
export function getDispatcherStatus(state: StoreState, onlineStationData: StationAPIData) {
const { dispatchers } = state.apiData;
const prevDispatcherStatus = state.lastDispatcherStatuses.find(
(dispatcher) => dispatcher.hash === onlineStationData.stationHash
);
const stationStatus = !dispatchers
? undefined
: dispatchers.find(
(status: string[]) =>
status[0] == onlineStationData.stationHash && status[1] == state.region.id
) || -1;
const statusTimestamp =
prevDispatcherStatus && !dispatchers
? prevDispatcherStatus.statusTimestamp
: getStatusTimestamp(stationStatus);
const statusID =
prevDispatcherStatus && !dispatchers
? prevDispatcherStatus.statusID
: getStatusID(stationStatus);
return {
hash: onlineStationData.stationHash,
statusID,
statusTimestamp
};
}
export function getScheduledTrains(
trainList: Train[],
stationAPIData: StationAPIData,
stationGeneralInfo: Station['generalInfo']
): ScheduledTrain[] {
const stationName = stationAPIData.stationName.toLocaleLowerCase();
stationGeneralInfo?.checkpoints.forEach((cp) => (cp.scheduledTrains.length = 0));
return trainList.reduce((acc: ScheduledTrain[], train) => {
if (!train.timetableData) return acc;
const timetable = train.timetableData;
if (!timetable.sceneries.includes(stationAPIData.stationHash)) return acc;
const stopInfoIndex = timetable.followingStops.findIndex((stop) => {
const stopName = stop.stopNameRAW.toLowerCase();
return (
stationName == stopName ||
(!/(po\.|podg\.)/.test(stationName) && stopName.includes(stationName)) ||
(!/(po\.|podg\.)/.test(stopName) && stationName.includes(stopName)) ||
(stopName.split(', podg.')[0] !== undefined &&
stationName.startsWith(stopName.split(', podg.')[0]))
);
});
const checkpointScheduledTrains: ScheduledTrain[] = [];
if (stopInfoIndex != -1) {
checkpointScheduledTrains.push(
getCheckpointTrain(train, stopInfoIndex, stationAPIData.stationName)
);
}
stationGeneralInfo?.checkpoints?.forEach((checkpoint) => {
if (checkpoint.checkpointName.toLocaleLowerCase() == stationName) return;
if (
checkpointScheduledTrains.findIndex(
(cpTrain) =>
cpTrain.checkpointName.toLocaleLowerCase() ==
checkpoint.checkpointName.toLocaleLowerCase()
) != -1
)
return;
const index = timetable.followingStops.findIndex(
(stop) => stop.stopNameRAW.toLowerCase() == checkpoint.checkpointName.toLowerCase()
);
if (index > -1)
checkpointScheduledTrains.push(
getCheckpointTrain(train, index, stationAPIData.stationName)
);
});
acc.push(...checkpointScheduledTrains);
return acc;
}, []) as ScheduledTrain[];
}
export function getStationTrains(
trainList: Train[],
scheduledTrainList: ScheduledTrain[],
region: string,
apiStation: StationAPIData
): StationTrain[] {
return trainList
.filter(
(train) =>
train?.region === region &&
train.online &&
train.currentStationName === apiStation.stationName
)
.map((train) => ({
driverName: train.driverName,
driverId: train.driverId,
trainNo: train.trainNo,
trainId: train.trainId,
stopStatus:
scheduledTrainList.find((st) => st.trainNo === train.trainNo)?.stopStatus || 'no-timetable'
}));
}
+12 -16
View File
@@ -1,6 +1,5 @@
import { defineStore } from 'pinia';
import inputData from '../data/options.json';
import Station from '../scripts/interfaces/Station';
import StorageManager from '../scripts/managers/storageManager';
import { useStore } from './store';
import { filterInitStates } from '../scripts/constants/stores/initFilterStates';
@@ -13,31 +12,28 @@ export const useStationFiltersStore = defineStore('stationFiltersStore', {
inputs: inputData,
filters: { ...filterInitStates },
sorterActive: { headerName: 'station' as HeadIdsTypes, dir: 1 },
store: useStore(),
lastClickedFilterId: ''
};
},
getters: {
areFiltersAtDefault(state) {
areFiltersAtDefault: (state) => {
return Object.keys(state.filters).every((f) => state.filters[f] === filterInitStates[f]);
},
filteredStationList: (state) => {
const store = useStore();
return store.stationList
.map((station) => ({
...station,
onlineInfo: store.onlineSceneryList.find((os) => os.name == station.name)
}))
.filter((station) => filterStations(station, state.filters))
.sort((a, b) => sortStations(a, b, state.sorterActive));
}
},
actions: {
getFilteredStationList(stationList: Station[], region: string): Station[] {
return stationList
.map((station) => {
if (station.onlineInfo && station.onlineInfo.region != region) {
delete station.onlineInfo;
}
return station;
})
.filter((station) => filterStations(station, this.filters))
.sort((a, b) => sortStations(a, b, this.sorterActive));
},
setupFilters() {
if (!StorageManager.isRegistered('options_saved')) return;
+108 -189
View File
@@ -9,12 +9,18 @@ import StationRoutes from '../scripts/interfaces/StationRoutes';
import Train from '../scripts/interfaces/Train';
import { URLs } from '../scripts/utils/apiURLs';
import {
getStatusTimestamp,
getStatusID,
getScheduledTrain,
parseSpawns
getDispatcherStatus,
getCheckpointTrain,
parseSpawns,
getScheduledTrains,
getStationTrains
} from '../scripts/utils/storeUtils';
import { APIData, StationJSONData, StoreState } from '../scripts/interfaces/store/storeTypes';
import {
APIData,
OnlineScenery,
StationJSONData,
StoreState
} from '../scripts/interfaces/store/storeTypes';
import packageInfo from '../../package.json';
import { RollingStockGithubData } from '../scripts/interfaces/github_api/StockInfoGithubData';
@@ -25,7 +31,7 @@ export const useStore = defineStore('store', {
rollingStockData: undefined,
stationList: [],
trainList: [],
routesList: [],
sceneryData: [],
@@ -63,13 +69,9 @@ export const useStore = defineStore('store', {
modalLastClickedTarget: null
}) as StoreState,
actions: {
setTrainsOnlineData() {
const { trains } = this.apiData;
if (!trains) return [];
this.trainList = trains
getters: {
trainList(): Train[] {
return (this.apiData?.trains ?? [])
.filter(
(train) =>
train.region === this.region.id &&
@@ -123,193 +125,113 @@ export const useStore = defineStore('store', {
});
},
getDispatcherStatus(onlineStationData: StationAPIData) {
const { dispatchers } = this.apiData;
onlineSceneryList(state): OnlineScenery[] {
if (state.isOffline) return [];
if (!state.apiData?.stations) return [];
const prevDispatcherStatus = this.lastDispatcherStatuses.find(
(dispatcher) => dispatcher.hash === onlineStationData.stationHash
);
return state.apiData?.stations
?.filter((apiStation) => apiStation.region == state.region.id && apiStation.isOnline)
.map((apiStation) => {
const dispatcherStatus = getDispatcherStatus(state as StoreState, apiStation);
const station = this.stationList.find((s) => s.name === apiStation.stationName);
const stationStatus = !dispatchers
? undefined
: dispatchers.find(
(status: string[]) =>
status[0] == onlineStationData.stationHash && status[1] == this.region.id
) || -1;
const scheduledTrains = getScheduledTrains(
this.trainList,
apiStation,
station?.generalInfo
);
const statusTimestamp =
prevDispatcherStatus && !dispatchers
? prevDispatcherStatus.statusTimestamp
: getStatusTimestamp(stationStatus);
const statusID =
prevDispatcherStatus && !dispatchers
? prevDispatcherStatus.statusID
: getStatusID(stationStatus);
const stationTrains = getStationTrains(
this.trainList,
scheduledTrains,
this.region.id,
apiStation
);
return {
hash: onlineStationData.stationHash,
statusID,
statusTimestamp
};
},
getScheduledTrains(stationGeneralInfo: Station['generalInfo'], stationAPIData: StationAPIData) {
const stationName = stationAPIData.stationName.toLowerCase();
stationGeneralInfo?.checkpoints.forEach((cp) => (cp.scheduledTrains.length = 0));
return this.trainList.reduce((acc: ScheduledTrain[], train) => {
if (!train.timetableData) return acc;
const timetable = train.timetableData;
if (!timetable.sceneries.includes(stationAPIData.stationHash)) return acc;
const stopInfoIndex = timetable.followingStops.findIndex((stop) => {
const stopName = stop.stopNameRAW.toLowerCase();
if (stationName === stopName) return true;
if (
stopName.includes(stationName) &&
!stop.stopName.includes('po.') &&
!stop.stopName.includes('podg.')
)
return true;
if (
stationName.includes(stopName) &&
!stop.stopName.includes('po.') &&
!stop.stopName.includes('podg.')
)
return true;
if (
stopName.includes('podg.') &&
stopName.split(', podg.')[0] &&
stationName.includes(stopName.split(', podg.')[0])
)
return true;
if (
stationGeneralInfo &&
stationGeneralInfo.checkpoints &&
stationGeneralInfo.checkpoints.length > 0 &&
stationGeneralInfo.checkpoints.some((cp) =>
cp.checkpointName.toLowerCase().includes(stop.stopNameRAW.toLowerCase())
)
)
return true;
return false;
return {
name: apiStation.stationName,
hash: apiStation.stationHash,
region: apiStation.region,
maxUsers: apiStation.maxUsers,
currentUsers: apiStation.currentUsers,
spawns: parseSpawns(apiStation.spawnString),
dispatcherName: apiStation.dispatcherName,
dispatcherRate: apiStation.dispatcherRate,
dispatcherId: apiStation.dispatcherId,
dispatcherExp: apiStation.dispatcherExp,
dispatcherIsSupporter: apiStation.dispatcherIsSupporter,
scheduledTrains: scheduledTrains,
stationTrains: stationTrains,
statusTimestamp: dispatcherStatus.statusTimestamp,
statusID: dispatcherStatus.statusID
};
});
}
},
actions: {
// setStationsOnlineInfo() {
// const onlineStationNames: string[] = [];
// const prevDispatcherStatuses: StoreState['lastDispatcherStatuses'] = [];
if (stopInfoIndex == -1) return acc;
// if (this.isOffline) {
// this.stationList.forEach((station) => {
// station.onlineInfo = undefined;
// });
const scheduledStopTrain = getScheduledTrain(
train,
stopInfoIndex,
stationAPIData.stationName
);
// return;
// }
if (stationGeneralInfo?.checkpoints) {
for (const checkpoint of stationGeneralInfo.checkpoints) {
const index = timetable.followingStops.findIndex(
(stop) => stop.stopNameRAW.toLowerCase() == checkpoint.checkpointName.toLowerCase()
);
// this.apiData.stations?.forEach((stationAPIData) => {
// if (stationAPIData.region !== this.region.id || !stationAPIData.isOnline) return;
if (index == -1) continue;
// const station = this.stationList.find((s) => s.name === stationAPIData.stationName);
const scheduledCheckpointTrain = getScheduledTrain(
train,
index,
stationAPIData.stationName
);
checkpoint.scheduledTrains.push(scheduledCheckpointTrain);
}
}
// onlineStationNames.push(stationAPIData.stationName);
acc.push(scheduledStopTrain);
return acc;
}, []) as ScheduledTrain[];
},
// const dispatcherStatus = this.getDispatcherStatus(stationAPIData);
// prevDispatcherStatuses.push(dispatcherStatus);
getStationTrains(stationAPIData: StationAPIData) {
return this.trainList
.filter(
(train) =>
train?.region === this.region.id &&
train.online &&
train.currentStationName === stationAPIData.stationName
)
.map((train) => ({
driverName: train.driverName,
driverId: train.driverId,
trainNo: train.trainNo,
trainId: train.trainId
}));
},
// const stationTrains = this.getStationTrains(stationAPIData);
// const scheduledTrains = this.getScheduledTrains(station?.generalInfo, stationAPIData);
setStationsOnlineInfo() {
const onlineStationNames: string[] = [];
const prevDispatcherStatuses: StoreState['lastDispatcherStatuses'] = [];
// const onlineInfo = {
// name: stationAPIData.stationName,
// hash: stationAPIData.stationHash,
// region: stationAPIData.region,
// maxUsers: stationAPIData.maxUsers,
// currentUsers: stationAPIData.currentUsers,
// spawns: parseSpawns(stationAPIData.spawnString),
// dispatcherName: stationAPIData.dispatcherName,
// dispatcherRate: stationAPIData.dispatcherRate,
// dispatcherId: stationAPIData.dispatcherId,
// dispatcherExp: stationAPIData.dispatcherExp,
// dispatcherIsSupporter: stationAPIData.dispatcherIsSupporter,
// stationTrains,
// statusTimestamp: dispatcherStatus.statusTimestamp,
// statusID: dispatcherStatus.statusID,
// scheduledTrains
// };
if (this.isOffline) {
this.stationList.forEach((station) => {
station.onlineInfo = undefined;
});
// if (!station) {
// this.stationList.push({
// name: stationAPIData.stationName,
// onlineInfo
// });
return;
}
// return;
// }
this.apiData.stations?.forEach((stationAPIData) => {
if (stationAPIData.region !== this.region.id || !stationAPIData.isOnline) return;
const station = this.stationList.find((s) => s.name === stationAPIData.stationName);
// station.onlineInfo = { ...onlineInfo };
// });
onlineStationNames.push(stationAPIData.stationName);
// this.stationList
// .filter((station) => !onlineStationNames.includes(station.name) && station.onlineInfo)
// .forEach((offlineStation) => {
// offlineStation.onlineInfo = undefined;
// });
const dispatcherStatus = this.getDispatcherStatus(stationAPIData);
prevDispatcherStatuses.push(dispatcherStatus);
const stationTrains = this.getStationTrains(stationAPIData);
const scheduledTrains = this.getScheduledTrains(station?.generalInfo, stationAPIData);
const onlineInfo = {
name: stationAPIData.stationName,
hash: stationAPIData.stationHash,
region: stationAPIData.region,
maxUsers: stationAPIData.maxUsers,
currentUsers: stationAPIData.currentUsers,
spawns: parseSpawns(stationAPIData.spawnString),
dispatcherName: stationAPIData.dispatcherName,
dispatcherRate: stationAPIData.dispatcherRate,
dispatcherId: stationAPIData.dispatcherId,
dispatcherExp: stationAPIData.dispatcherExp,
dispatcherIsSupporter: stationAPIData.dispatcherIsSupporter,
stationTrains,
statusTimestamp: dispatcherStatus.statusTimestamp,
statusID: dispatcherStatus.statusID,
scheduledTrains
};
if (!station) {
this.stationList.push({
name: stationAPIData.stationName,
onlineInfo
});
return;
}
station.onlineInfo = { ...onlineInfo };
this.stationList
.filter((station) => !onlineStationNames.includes(station.name) && station.onlineInfo)
.forEach((offlineStation) => {
offlineStation.onlineInfo = undefined;
});
});
if (this.apiData.dispatchers != null) this.lastDispatcherStatuses = prevDispatcherStatuses;
},
// if (this.apiData.dispatchers != null) this.lastDispatcherStatuses = prevDispatcherStatuses;
// },
async fetchStationsGeneralInfo() {
const sceneryData: StationJSONData[] = await (
@@ -385,7 +307,7 @@ export const useStore = defineStore('store', {
}
const socket = io(URLs.stacjownikAPI, {
// transports: ['websocket', 'polling'],
transports: ['websocket', 'polling'],
rememberUpgrade: true,
reconnection: true,
extraHeaders: {
@@ -405,7 +327,6 @@ export const useStore = defineStore('store', {
socket.emit('FETCH_DATA', { version: packageInfo.version }, (data: APIData) => {
this.dataStatuses.connection = DataStatus.Loaded;
this.apiData = data;
this.setOnlineData();
});
@@ -414,10 +335,9 @@ export const useStore = defineStore('store', {
},
async connectToAPI() {
await this.fetchStationsGeneralInfo();
await this.fetchStockInfoData();
this.connectToWebsocket();
this.fetchStockInfoData();
this.fetchStationsGeneralInfo();
},
async changeRegion(region: StoreState['region']) {
@@ -453,8 +373,7 @@ export const useStore = defineStore('store', {
? DataStatus.Warning
: DataStatus.Loaded;
this.setTrainsOnlineData();
this.setStationsOnlineInfo();
// this.setStationsOnlineInfo();
}
}
});
-4
View File
@@ -35,10 +35,6 @@
}
}
html {
scroll-behavior: smooth;
}
body {
background: var(--clr-bg);
+35 -25
View File
@@ -1,9 +1,6 @@
<template>
<div class="scenery-view">
<div
class="scenery-offline"
v-if="!stationInfo && isComponentVisible && store.dataStatuses.sceneries == 2"
>
<div class="scenery-offline" v-if="!stationInfo && store.dataStatuses.sceneries == 2">
<div>{{ $t('scenery.no-scenery') }}</div>
<action-button>
@@ -11,21 +8,16 @@
</action-button>
</div>
<div
class="scenery-wrapper"
v-if="stationInfo"
ref="card-wrapper"
:data-timetable-only="timetableOnly"
>
<div class="scenery-left" v-if="!timetableOnly">
<div class="scenery-wrapper" v-if="stationInfo" ref="card-wrapper">
<div class="scenery-left">
<div class="scenery-actions">
<button class="back-btn btn" :title="$t('scenery.return-btn')" @click="navigateTo('/')">
<img src="/images/icon-back.svg" alt="Back to scenery" />
</button>
</div>
<SceneryHeader :station="stationInfo" />
<SceneryInfo :station="stationInfo" />
<SceneryHeader :station="stationInfo" :onlineScenery="onlineSceneryInfo" />
<SceneryInfo :station="stationInfo" :onlineScenery="onlineSceneryInfo" />
</div>
<div class="scenery-right">
@@ -44,6 +36,7 @@
<keep-alive>
<component
:is="currentViewCompontent"
:onlineScenery="onlineSceneryInfo"
:station="stationInfo"
:key="currentViewCompontent"
></component>
@@ -82,9 +75,22 @@ export default defineComponent({
SceneryDispatchersHistory
},
props: {
region: {
type: String,
required: false
},
station: {
type: String,
required: true
}
},
mixins: [routerMixin],
data: () => ({
store: useStore(),
viewModes: [
{
id: 'scenery.option-active-timetables',
@@ -111,24 +117,28 @@ export default defineComponent({
setup() {
const route = useRoute();
const store = useStore();
const timetableOnly = computed(() => (route.query['timetableOnly'] == '1' ? true : false));
const isComponentVisible = computed(() => route.path === '/scenery');
const stationInfo = computed(() => {
return store.stationList.find(
(station) => station.name === route.query.station?.toString().replace(/_/g, ' ')
);
});
return {
timetableOnly,
isComponentVisible,
stationInfo,
store
isComponentVisible
};
},
computed: {
stationInfo() {
return this.store.stationList.find(
(station) => station.name === this.station?.toString().replace(/_/g, ' ')
);
},
onlineSceneryInfo() {
return this.store.onlineSceneryList.find(
(scenery) => scenery.name === this.station?.toString().replace(/_/g, ' ')
);
}
},
methods: {
setViewMode(componentName: string) {
this.currentViewCompontent = componentName;
+4 -8
View File
@@ -33,19 +33,15 @@ export default defineComponent({
filterCardOpen: false,
modalHidden: true,
STORAGE_KEY: 'options_saved',
focusedStationName: ''
focusedStationName: '',
filterStore: useStationFiltersStore(),
store: useStore()
}),
setup() {
return {
filterStore: useStationFiltersStore(),
store: useStore()
};
},
computed: {
computedStationList() {
return this.filterStore.getFilteredStationList(this.store.stationList, this.store.region.id);
return this.filterStore.filteredStationList;
}
},
+27 -17
View File
@@ -4,7 +4,7 @@ import { VitePWA } from 'vite-plugin-pwa';
export default defineConfig({
server: {
port: 5001,
port: 5001
},
plugins: [
vue(),
@@ -20,13 +20,25 @@ export default defineConfig({
options: {
cacheName: 'sceneries-cache',
expiration: {
maxEntries: 1,
maxAgeSeconds: 60 * 60 * 24 * 7, // <== 7 days
maxAgeSeconds: 60 * 60 * 24 * 7 // <== 7 days
},
cacheableResponse: {
statuses: [0, 200],
statuses: [0, 200]
}
}
},
{
urlPattern: new RegExp('^https://raw.githubusercontent.com/Spythere/api/*', 'i'),
handler: 'NetworkFirst',
options: {
cacheName: 'github-api-cache',
expiration: {
maxAgeSeconds: 60 * 60 * 24 * 7 // <== 7 days
},
},
cacheableResponse: {
statuses: [0, 200]
}
}
},
{
urlPattern: /^https:\/\/rj.td2.info.pl\/dist\/img\/thumbnails\/.*/i,
@@ -35,21 +47,19 @@ export default defineConfig({
cacheName: 'images-cache',
expiration: {
maxEntries: 100,
maxAgeSeconds: 60 * 60 * 24 * 60,
maxAgeSeconds: 60 * 60 * 24 * 7 // <== 7 days
},
cacheableResponse: {
statuses: [0, 200, 404],
},
},
},
],
statuses: [0, 200, 404]
}
}
}
]
},
devOptions: {
enabled: true,
},
}),
],
suppressWarnings: true
}
})
]
});