restruct: added interal lines info to train schedule; minor fixes

This commit is contained in:
2025-02-02 22:22:04 +01:00
parent 1b2cd34e86
commit 5787deeaf8
5 changed files with 257 additions and 89 deletions
+15 -10
View File
@@ -4,15 +4,17 @@
:data-minor="stop.isSBL || (stop.nameRaw.endsWith(', po') && !stop.duration)" :data-minor="stop.isSBL || (stop.nameRaw.endsWith(', po') && !stop.duration)"
> >
<router-link v-if="/(, podg$|<strong>)/.test(stop.nameHtml)" :to="sceneryHref"> <router-link v-if="/(, podg$|<strong>)/.test(stop.nameHtml)" :to="sceneryHref">
<b class="stop-name" <b class="stop-name">
><i <span>
v-if="!stop.isSceneryOnline" {{ stop.nameRaw }}
class="fa-solid fa-ban" </span>
<i
v-if="!stop.isSceneryOnline && stop.status != 'confirmed'"
class="fa-solid fa-ban fa-md"
data-tooltip-type="BaseTooltip" data-tooltip-type="BaseTooltip"
:data-tooltip-content="$t('app.tooltip-scenery-offline')" :data-tooltip-content="$t('app.tooltip-scenery-offline')"
style="margin-right: 0.25rem; color: salmon" style="color: salmon; margin-left: 0.25em"
></i> ></i>
{{ stop.nameRaw }}
</b> </b>
</router-link> </router-link>
@@ -54,7 +56,10 @@
<span <span
v-if=" v-if="
stop.position != 'end' && stop.position != 'end' &&
(stop.duration != 0 || stop.status == 'stopped' || stop.departureDelay != stop.arrivalDelay) (stop.duration != 0 ||
stop.position == 'begin' ||
stop.status == 'stopped' ||
stop.departureDelay != stop.arrivalDelay)
" "
class="date departure" class="date departure"
:data-status-delayed="stop.departureDelay > 0" :data-status-delayed="stop.departureDelay > 0"
@@ -77,16 +82,16 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { PropType, defineComponent } from 'vue'; import { PropType, defineComponent, stop } from 'vue';
import dateMixin from '../../mixins/dateMixin'; import dateMixin from '../../mixins/dateMixin';
import { TrainScheduleStop } from './typings'; import { TrainSchedulePoint } from './typings';
export default defineComponent({ export default defineComponent({
mixins: [dateMixin], mixins: [dateMixin],
props: { props: {
stop: { stop: {
type: Object as PropType<TrainScheduleStop>, type: Object as PropType<TrainSchedulePoint>,
required: true required: true
} }
}, },
+199 -78
View File
@@ -3,15 +3,15 @@
<div class="schedule-wrapper" v-if="train.timetableData"> <div class="schedule-wrapper" v-if="train.timetableData">
<div class="stops"> <div class="stops">
<div <div
v-for="(stop, i) in scheduleStops" v-for="(stop, i) in scheduleStopsV2"
:key="i" :key="i"
class="stop" class="stop"
:data-status="stop.status" :data-status="stop.status"
:data-sbl="stop.isSBL && stop.sceneryName == scheduleStopsV2[i + 1]?.sceneryName"
:data-position="stop.position" :data-position="stop.position"
:data-delayed="stop.departureDelay > 0" :data-delayed="stop.departureDelay > 0"
:data-stop-type="stop.type" :data-stop-type="stop.type"
:data-minor-stop-active="stop.isActive" :data-is-active="stop.isActive"
:data-last-confirmed="stop.isLastConfirmed"
> >
<span class="stop_info"> <span class="stop_info">
<span class="distance"> <span class="distance">
@@ -32,7 +32,7 @@
<div></div> <div></div>
<div class="progress"> <div class="progress">
<div class="line line_connection" v-if="i < scheduleStops.length - 1"></div> <div class="line line_connection" v-if="i < scheduleStopsV2.length - 1"></div>
</div> </div>
<div class="bottom-line-info"> <div class="bottom-line-info">
@@ -42,18 +42,21 @@
</div> </div>
<!-- Routes --> <!-- Routes -->
<span <span
v-if=" v-if="
stop.departureLine && stop.departureLine &&
scheduleStops[i + 1] != undefined && (scheduleStopsV2[i + 1]?.arrivalLineInfo?.routeSpeed !=
!/-|_|(^it\d+)|(^sbl)/gi.test(stop.departureLine) stop.arrivalLineInfo?.routeSpeed ||
stop.sceneryName != scheduleStopsV2[i + 1]?.sceneryName)
" "
> >
<div class="scenery-route"> <div class="scenery-route">
<span>{{ stop.departureLine }}</span> <span>{{ stop.departureLine }}</span>
<span v-if="stop.departureLineInfo">
| {{ stop.departureLineInfo.routeSpeed }}
<span v-if="stop.departureLineInfo">
<span v-if="stop.departureLineInfo.routeTracks == 1"> &UpDownArrow;</span>
<span v-else> &DownArrowUpArrow;</span> {{ stop.departureLineInfo.routeSpeed }}
<img <img
:src=" :src="
stop.departureLineInfo.isElectric stop.departureLineInfo.isElectric
@@ -80,37 +83,42 @@
</div> </div>
<div <div
v-if="stop.sceneryName != scheduleStops[i + 1]?.sceneryName" v-if="stop.sceneryName != scheduleStopsV2[i + 1]?.sceneryName"
class="scenery-change-name" class="scenery-change-name"
> >
<span>{{ scheduleStops[i + 1].sceneryName }}</span> <span>{{ scheduleStopsV2[i + 1].sceneryName }}</span>
<span v-if="stop.departureLineInfo?.routeTracks == 1"> &UpDownArrow;</span> <span v-if="stop.departureLineInfo?.routeTracks == 1"> &UpDownArrow;</span>
<span v-else> &DownArrowUpArrow;</span> <span v-else> &DownArrowUpArrow;</span>
</div> </div>
<div class="scenery-route"> <div
<span> {{ scheduleStops[i + 1].arrivalLine }}</span> class="scenery-route"
v-if="stop.sceneryName != scheduleStopsV2[i + 1]?.sceneryName"
>
<span> {{ scheduleStopsV2[i + 1].arrivalLine }}</span>
<span v-if="scheduleStops[i + 1].arrivalLineInfo"> <span v-if="scheduleStopsV2[i + 1].arrivalLineInfo">
| {{ scheduleStops[i + 1].arrivalLineInfo!.routeSpeed }} <span v-if="stop.arrivalLineInfo?.routeTracks == 1"> &UpDownArrow;</span>
<span v-else> &DownArrowUpArrow;</span>
{{ scheduleStopsV2[i + 1].arrivalLineInfo!.routeSpeed }}
<img <img
:src=" :src="
scheduleStops[i + 1].arrivalLineInfo!.isElectric scheduleStopsV2[i + 1].arrivalLineInfo?.isElectric
? '/images/icon-catenary.svg' ? '/images/icon-catenary.svg'
: '/images/icon-we4a.png' : '/images/icon-we4a.png'
" "
data-tooltip-type="BaseTooltip" data-tooltip-type="BaseTooltip"
:data-tooltip-content=" :data-tooltip-content="
$t( $t(
`trains.${!scheduleStops[i + 1].arrivalLineInfo!.isElectric ? 'no-' : ''}catenary-tooltip` `trains.${!scheduleStopsV2[i + 1].arrivalLineInfo?.isElectric ? 'no-' : ''}catenary-tooltip`
) )
" "
width="10" width="10"
/> />
<img <img
v-if="scheduleStops[i + 1].arrivalLineInfo!.isRouteSBL" v-if="scheduleStopsV2[i + 1].arrivalLineInfo!.isRouteSBL"
src="/images/icon-sbl-transparent.svg" src="/images/icon-sbl-transparent.svg"
width="10" width="10"
data-tooltip-type="BaseTooltip" data-tooltip-type="BaseTooltip"
@@ -134,8 +142,8 @@ import StopLabel from './StopLabel.vue';
import StockList from '../Global/StockList.vue'; import StockList from '../Global/StockList.vue';
import { useMainStore } from '../../store/mainStore'; import { useMainStore } from '../../store/mainStore';
import { useApiStore } from '../../store/apiStore'; import { useApiStore } from '../../store/apiStore';
import { Train } from '../../typings/common'; import { StationRoutesInfo, TimetablePathElement, Train } from '../../typings/common';
import { TrainScheduleStop } from './typings'; import { TrainSchedulePoint } from './typings';
export default defineComponent({ export default defineComponent({
components: { StopLabel, StockList }, components: { StopLabel, StockList },
@@ -157,74 +165,183 @@ export default defineComponent({
}; };
}, },
methods: {
getPathSceneryData(pathEl: TimetablePathElement) {
const sceneryData =
this.store.stationList?.find((sc) => sc.name == pathEl.stationName) ?? null;
if (!sceneryData || !sceneryData.generalInfo) return null;
const activeScenery = this.apiStore.activeData?.activeSceneries?.find(
(sc) => sc.stationName == pathEl.stationName
);
const arrivalLineData = pathEl.arrivalRouteExt
? (sceneryData.generalInfo.routes.all.find(
(rt) => rt.routeName == pathEl.arrivalRouteExt
) ?? null)
: null;
const departureLineData = pathEl.departureRouteExt
? (sceneryData.generalInfo.routes.all.find(
(rt) => rt.routeName == pathEl.departureRouteExt
) ?? null)
: null;
return {
generalInfo: sceneryData.generalInfo,
isOnline:
activeScenery &&
(activeScenery.isOnline == 1 || activeScenery.lastSeen >= Date.now() - 60000),
arrivalLineData,
departureLineData
};
}
},
computed: { computed: {
scheduleStops(): TrainScheduleStop[] { scheduleStopsV2() {
if (!this.train.timetableData) return []; if (!this.train.timetableData) return [];
const { timetablePath } = this.train.timetableData; const { timetablePath, followingStops } = this.train.timetableData;
const stopRows: TrainSchedulePoint[] = [];
let currentPathIndex = 0; let currentPathIndex = 0;
let currentPath = timetablePath[0];
return ( let pathData = this.getPathSceneryData(currentPath);
this.train.timetableData?.followingStops.map((stop, i, arr) => {
const isExternal =
i < arr.length - 1 &&
stop.departureLine === timetablePath[currentPathIndex].departureRouteExt;
const sceneryName = timetablePath[currentPathIndex].stationName; let arrivalLineInfo: StationRoutesInfo | null = null;
const sceneryInfo = this.apiStore.sceneryData.find((st) => st.name == sceneryName); let departureLineInfo: StationRoutesInfo | null = null;
const isSceneryOnline = let isActive = false;
(this.apiStore.activeData?.activeSceneries?.find((sc) => sc.stationName == sceneryName)
?.isOnline ?? 0) == 1;
const arrivalLineInfo = sceneryInfo?.routesInfo.find( if (pathData?.departureLineData) {
(r) => r.routeName == stop.arrivalLine arrivalLineInfo = pathData.departureLineData;
); departureLineInfo = pathData.departureLineData;
}
const departureLineInfo = sceneryInfo?.routesInfo.find( for (const stop of followingStops) {
(r) => r.routeName == stop.departureLine let isExternal = false;
);
if (isExternal) currentPathIndex++; if (
stop.arrivalLine &&
currentPath.arrivalRouteExt &&
stop.arrivalLine == currentPath.arrivalRouteExt
) {
isExternal = true;
return { if (pathData?.arrivalLineData) {
nameHtml: stop.stopName, arrivalLineInfo = pathData.arrivalLineData;
nameRaw: stop.stopNameRAW, }
}
arrivalScheduled: stop.arrivalTimestamp, let correctedDepartureLineData: StationRoutesInfo | null = null;
arrivalReal: stop.arrivalRealTimestamp,
departureScheduled: stop.departureTimestamp, const internalRouteInfo = stop.departureLine
departureReal: stop.departureRealTimestamp, ? pathData?.generalInfo.routes.all.find(
(route) => route.isInternal && route.routeName == stop.departureLine
)
: undefined;
departureDelay: stop.departureDelay, if (internalRouteInfo) {
arrivalDelay: stop.arrivalDelay, correctedDepartureLineData = internalRouteInfo;
departureLineInfo = internalRouteInfo;
}
duration: stop.stopTime, let rowData: TrainSchedulePoint = {
nameHtml: stop.stopName,
nameRaw: stop.stopNameRAW,
comments: stop.comments ?? null, arrivalScheduled: stop.arrivalTimestamp,
arrivalReal: stop.arrivalRealTimestamp,
arrivalLine: stop.arrivalLine, departureScheduled: stop.departureTimestamp,
departureLine: stop.departureLine, departureReal: stop.departureRealTimestamp,
arrivalLineInfo: arrivalLineInfo, departureDelay: stop.departureDelay,
departureLineInfo: departureLineInfo, arrivalDelay: stop.arrivalDelay,
isExternal, duration: stop.stopTime ?? 0,
type: stop.stopType,
distance: stop.stopDistance,
type: stop.stopType, comments: stop.comments ?? null,
distance: stop.stopDistance,
isActive: this.activeMinorStops.includes(i),
isLastConfirmed: this.lastConfirmed === i && !stop.terminatesHere,
isSBL: /sbl/gi.test(stop.stopName),
position: stop.beginsHere ? 'begin' : stop.terminatesHere ? 'end' : 'en-route',
status: stop.confirmed ? 'confirmed' : stop.stopped ? 'stopped' : 'unconfirmed',
sceneryName, arrivalLine: stop.arrivalLine,
isSceneryOnline departureLine: stop.departureLine,
};
}) ?? [] arrivalLineInfo,
); departureLineInfo,
isExternal,
isActive: isActive,
isSBL: /sbl/gi.test(stop.stopName),
position: stop.beginsHere ? 'begin' : stop.terminatesHere ? 'end' : 'en-route',
status: stop.confirmed ? 'confirmed' : stop.stopped ? 'stopped' : 'unconfirmed',
sceneryName: currentPath.stationName,
isSceneryOnline: pathData?.isOnline ?? false
};
if (internalRouteInfo) {
arrivalLineInfo = departureLineInfo;
}
if (
stopRows[stopRows.length - 1]?.status == 'confirmed' &&
rowData.status != 'confirmed' &&
rowData.status != 'stopped'
)
stopRows[stopRows.length - 1].isActive = true;
if (
stopRows[stopRows.length - 1]?.isActive == true &&
!/(^<strong>|, podg$)/.test(rowData.nameHtml)
)
rowData.isActive = true;
stopRows.push(rowData);
if (
stop.departureLine &&
currentPath.departureRouteExt &&
stop.departureLine == currentPath.departureRouteExt
) {
// Reverse search for last scenery checkpoint
if (pathData?.departureLineData) {
stopRows[stopRows.length - 1].isExternal = true;
for (let i = stopRows.length - 1; i > 0; i--) {
stopRows[i].departureLineInfo = pathData.departureLineData;
// stopRows[i].departureTracks = pathData.departureLineData.routeTracks;
// stopRows[i].departureSpeed = pathData.departureLineData.routeSpeed;
// stopRows[i].departureElectric = pathData.departureLineData.isElectric;
// stopRows[i].realLineNo = pathData.departureLineData.realLineNo ?? 0;
if (/(^<strong>|, podg$)/.test(stopRows[i].nameHtml)) {
// stopRows[i].departureSpeed = pathData.departureLineData.routeSpeed;
// stopRows[i].departureTracks = pathData.departureLineData.routeTracks;
// stopRows[i].departureElectric = pathData.departureLineData.isElectric;
break;
}
stopRows[i].arrivalLineInfo = pathData.departureLineData;
// stopRows[i].arrivalSpeed = pathData.departureLineData.routeSpeed;
// stopRows[i].arrivalTracks = pathData.departureLineData.routeTracks;
// stopRows[i].arrivalElectric = pathData.departureLineData.isElectric;
}
}
currentPath = timetablePath[++currentPathIndex];
pathData = this.getPathSceneryData(currentPath);
}
}
return stopRows;
}, },
lastConfirmed() { lastConfirmed() {
@@ -297,6 +414,10 @@ $blinkAnim: 0.5s ease-in-out alternate infinite blink;
} }
.stop { .stop {
&[data-sbl='true'] {
display: none;
}
// Begin stop // Begin stop
&[data-position='begin'] { &[data-position='begin'] {
.node { .node {
@@ -328,20 +449,19 @@ $blinkAnim: 0.5s ease-in-out alternate infinite blink;
border-color: $haltClr; border-color: $haltClr;
} }
&[data-minor-stop-active='true'] { // &[data-minor-stop-active='true'] {
.progress > .line { // .progress > .line {
animation: $blinkAnim; // animation: $blinkAnim;
} // }
& + div { // & + div {
.progress > .line_node-top { // .progress > .line_node-top {
animation: $blinkAnim; // animation: $blinkAnim;
} // }
} // }
} // }
// Last confirmed outpost / checkpoint &[data-is-active='true'] {
&[data-last-confirmed='true'] {
.progress > .line_connection { .progress > .line_connection {
animation: $blinkAnim; animation: $blinkAnim;
} }
@@ -362,6 +482,7 @@ $blinkAnim: 0.5s ease-in-out alternate infinite blink;
.progress > .node { .progress > .node {
border-color: $confirmedClr; border-color: $confirmedClr;
} }
.progress > .line { .progress > .line {
border-left: 2px solid $confirmedClr; border-left: 2px solid $confirmedClr;
border-right: 2px solid $confirmedClr; border-right: 2px solid $confirmedClr;
+32
View File
@@ -164,4 +164,36 @@ export interface TrainScheduleStop {
isExternal: boolean; isExternal: boolean;
comments: string | null; comments: string | null;
}
export interface TrainSchedulePoint {
nameHtml: string;
nameRaw: string;
status: 'confirmed' | 'unconfirmed' | 'stopped';
position: 'begin' | 'end' | 'en-route';
type: string;
duration: number;
distance: number;
arrivalScheduled: number;
arrivalReal: number;
departureScheduled: number;
departureReal: number;
comments: string | null;
arrivalDelay: number;
departureDelay: number;
arrivalLine: string | null;
departureLine: string | null;
arrivalLineInfo: StationRoutesInfo | null;
departureLineInfo: StationRoutesInfo | null;
isExternal: boolean,
isActive: boolean;
isSBL: boolean;
sceneryName: string | null;
isSceneryOnline: boolean;
} }
+7 -1
View File
@@ -112,7 +112,10 @@ export const useMainStore = defineStore('mainStore', {
: undefined : undefined
} as Train; } as Train;
const stationNameKey = train.currentStationName.indexOf('.sc') != -1 ? train.currentStationName.split(' ').slice(0, -1).join(' ') : train.currentStationName; const stationNameKey =
train.currentStationName.indexOf('.sc') != -1
? train.currentStationName.split(' ').slice(0, -1).join(' ')
: train.currentStationName;
// Sceneries trains map // Sceneries trains map
if (sceneriesTrains.has(stationNameKey)) { if (sceneriesTrains.has(stationNameKey)) {
@@ -319,6 +322,8 @@ export const useMainStore = defineStore('mainStore', {
return apiStore.sceneryData.map((scenery) => { return apiStore.sceneryData.map((scenery) => {
const routes = scenery.routesInfo.reduce( const routes = scenery.routesInfo.reduce(
(acc, route) => { (acc, route) => {
acc['all'].push(route);
if (route.hidden) return acc; if (route.hidden) return acc;
const tracksKey = route.routeTracks == 2 ? 'double' : 'single'; const tracksKey = route.routeTracks == 2 ? 'double' : 'single';
@@ -349,6 +354,7 @@ export const useMainStore = defineStore('mainStore', {
doubleElectrifiedNames: [], doubleElectrifiedNames: [],
doubleOtherNames: [], doubleOtherNames: [],
sblNames: [], sblNames: [],
all: [],
minRouteSpeed: 0, minRouteSpeed: 0,
maxRouteSpeed: 0 maxRouteSpeed: 0
} as StationRoutes } as StationRoutes
+4
View File
@@ -1,4 +1,5 @@
import { RouteLocationRaw } from 'vue-router'; import { RouteLocationRaw } from 'vue-router';
import { StationJSONData } from '../store/typings';
export type Availability = 'default' | 'unavailable' | 'nonPublic' | 'abandoned' | 'nonDefault'; export type Availability = 'default' | 'unavailable' | 'nonPublic' | 'abandoned' | 'nonDefault';
export type ScenerySpawnType = 'passenger' | 'freight' | 'loco' | 'all'; export type ScenerySpawnType = 'passenger' | 'freight' | 'loco' | 'all';
@@ -90,6 +91,7 @@ export interface TrainTimetableData {
routeDistance: number; routeDistance: number;
sceneries: string[]; sceneries: string[];
timetablePath: TimetablePathElement[]; timetablePath: TimetablePathElement[];
trainMaxSpeed: number;
} }
export interface Station { export interface Station {
@@ -122,6 +124,7 @@ export interface StationGeneralInfo {
export interface StationRoutes { export interface StationRoutes {
single: StationRoutesInfo[]; single: StationRoutesInfo[];
double: StationRoutesInfo[]; double: StationRoutesInfo[];
all: StationRoutesInfo[];
singleElectrifiedNames: string[]; singleElectrifiedNames: string[];
singleOtherNames: string[]; singleOtherNames: string[];
@@ -142,6 +145,7 @@ export interface StationRoutesInfo {
routeSpeed: number; routeSpeed: number;
routeTracks: number; routeTracks: number;
hidden?: boolean; hidden?: boolean;
realLineNo?: number;
} }
export interface ActiveScenery { export interface ActiveScenery {