Compare commits

...

32 Commits

Author SHA1 Message Date
Spythere 580d404d4a Merge pull request #139 from Spythere/development
v1.30.6
2025-09-15 14:23:43 +02:00
Spythere 6d1ef26ac1 chore: added a display of the timetable max speed in the active train info & tooltip 2025-09-14 15:10:48 +02:00
Spythere bf9799e0c3 bump: v1.30.6 2025-09-14 14:52:04 +02:00
Spythere 1d13e31d79 chore: restored station filtering by non-electric double track routes 2025-09-14 14:51:44 +02:00
Spythere 16f272bd7d chore: added SCS+SPK station filter 2025-09-14 14:43:20 +02:00
Spythere 23ca33264c chore: PWA caching 2025-09-14 14:38:56 +02:00
Spythere 324ca3de4d chore: config adjustments 2025-09-14 14:38:42 +02:00
Spythere e0548e593c fix: stats header font size 2025-09-14 14:38:17 +02:00
Spythere 2727350837 Merge pull request #138 from Spythere/development
v1.30.5
2025-09-07 23:57:19 +02:00
Spythere 6c3af0a8d3 chore: updated packages versions 2025-09-06 14:04:18 +02:00
Spythere e784202a36 chore: removed displaying exit track speeds if they are the same as the base ones 2025-09-06 13:42:00 +02:00
Spythere c24f691693 fix: track count depiction in train schedule for unknown sceneries 2025-09-06 13:34:03 +02:00
Spythere 3aeabd63c9 bump: v1.30.5 2025-09-06 02:18:14 +02:00
Spythere 4c79376318 chore: restored SCS+SPK control type support 2025-09-06 02:17:28 +02:00
Spythere bc1c446c37 chore: added missing checkpoints data fallback for unknown sceneries 2025-09-06 01:57:59 +02:00
Spythere fba335d0c7 fix: scenery without general info shown always as offline; wrong route info for unknown sceneries 2025-09-05 20:03:17 +02:00
Spythere b4e536da40 Merge pull request #137 from Spythere/development
v1.30.4 hotfixes
2025-07-27 14:42:53 +02:00
Spythere 8cde8e6323 hotfix: offline train badge visibility 2025-07-27 14:39:44 +02:00
Spythere d7a9e93978 hotfix: added missing input field keybind prevention 2025-07-27 14:37:50 +02:00
Spythere 69aa62e77f Merge pull request #136 from Spythere/development
hotfix: translations
2025-07-20 14:09:44 +02:00
Spythere 4b842627fb hotfix: translations 2025-07-20 14:09:07 +02:00
Spythere 5ffc63a815 Merge pull request #135 from Spythere/development
v1.30.4
2025-07-20 14:03:10 +02:00
Spythere 87f7ff58e8 chore: added info about offline driver for train info tooltip and scenery timetables 2025-07-19 15:34:49 +02:00
Spythere 8b6944a8e5 fix(ScheduledTrainStatus): router link only for timetable statuses with station name hrefs 2025-07-19 15:21:32 +02:00
Spythere cfeeb8fddd bump: v1.30.4 2025-07-17 14:46:02 +02:00
Spythere 89f7fd3c53 refactor: changed drivers select to datalist in active train options 2025-07-17 14:45:33 +02:00
Spythere 86259988c9 refactor: added more advanced tooltips for station table icons 2025-07-17 14:34:05 +02:00
Spythere 7b5ef18ad6 chore: added train info tooltips for scenery user badges 2025-07-17 14:09:10 +02:00
Spythere d784042691 chore: added router links to timetable train statuses in scenery view 2025-07-17 14:03:18 +02:00
Spythere d0e482aa4f chore: changed speed placement in train info tooltip 2025-07-17 13:45:45 +02:00
Spythere 3bf1db52b4 chore(scenery): advanced active train tooltip information 2025-07-11 15:33:28 +02:00
Spythere 8e713a5c6e chore: added & optimized users tooltip data typings 2025-07-11 14:46:07 +02:00
28 changed files with 1919 additions and 8479 deletions
-6962
View File
File diff suppressed because it is too large Load Diff
+7 -7
View File
@@ -1,6 +1,6 @@
{ {
"name": "stacjownik", "name": "stacjownik",
"version": "1.30.3", "version": "1.30.6",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
@@ -17,7 +17,7 @@
}, },
"dependencies": { "dependencies": {
"core-js": "^3.42.0", "core-js": "^3.42.0",
"dotenv": "^16.5.0", "dotenv": "^17.2.2",
"pinia": "^3.0.2", "pinia": "^3.0.2",
"sass": "^1.87.0", "sass": "^1.87.0",
"showdown": "^2.1.0", "showdown": "^2.1.0",
@@ -26,17 +26,17 @@
"vue-router": "^4.4.0" "vue-router": "^4.4.0"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^22.15.15", "@types/node": "^24.3.1",
"@types/showdown": "^2.0.6", "@types/showdown": "^2.0.6",
"@vite-pwa/assets-generator": "^1.0.0", "@vite-pwa/assets-generator": "^1.0.0",
"@vitejs/plugin-vue": "^5.1.0", "@vitejs/plugin-vue": "^6.0.1",
"@vue/tsconfig": "^0.7.0", "@vue/tsconfig": "^0.8.1",
"axios": "^1.9.0", "axios": "^1.9.0",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"typescript": "^5.5.4", "typescript": "^5.5.4",
"vite": "^6.3.5", "vite": "^7.1.4",
"vite-plugin-pwa": "^1.0.0", "vite-plugin-pwa": "^1.0.0",
"vue-tsc": "^2.0.28" "vue-tsc": "^3.0.6"
}, },
"browserslist": [ "browserslist": [
"> 1%", "> 1%",
@@ -43,8 +43,12 @@
<span :class="{ 'no-catenary': !route.isElectric, internal: route.isInternal }"> <span :class="{ 'no-catenary': !route.isElectric, internal: route.isInternal }">
{{ route.routeName }} {{ route.routeName }}
</span> </span>
<span v-if="route.routeSpeed" class="speed">{{ route.routeSpeed }}</span> <span v-if="route.routeSpeed" class="speed">
<span v-if="route.routeSpeedExit" class="speed">| {{ route.routeSpeedExit }}</span> <span>{{ route.routeSpeed }}</span>
<span v-if="route.routeSpeedExit && route.routeSpeedExit != route.routeSpeed">
| {{ route.routeSpeedExit }}
</span>
</span>
<span v-if="route.routeLength" class="length"> <span v-if="route.routeLength" class="length">
{{ (route.routeLength / 1000).toFixed(1) + 'km' }} {{ (route.routeLength / 1000).toFixed(1) + 'km' }}
</span> </span>
@@ -156,7 +160,7 @@ ul.routes-list {
-moz-user-select: none; -moz-user-select: none;
-webkit-user-select: none; -webkit-user-select: none;
span { & > span {
padding: 0.2em; padding: 0.2em;
background-color: #007599; background-color: #007599;
font-weight: bold; font-weight: bold;
@@ -18,7 +18,11 @@
:key="train.id" :key="train.id"
:data-status="status" :data-status="status"
> >
<router-link :to="train.driverRouteLocation"> <router-link
:to="train.driverRouteLocation"
data-tooltip-type="TrainInfoTooltip"
:data-tooltip-content="train.id"
>
<span class="user_train"> {{ train.trainNo }}</span> <span class="user_train"> {{ train.trainNo }}</span>
<span class="user_name"> <span class="user_name">
{{ train.driverName }} {{ train.driverName }}
@@ -83,7 +87,8 @@ export default defineComponent({
const stop = train.timetableData?.followingStops.find( const stop = train.timetableData?.followingStops.find(
(stop) => (stop) =>
stop.stopNameRAW.toLowerCase() == name.toLowerCase() || stop.stopNameRAW.toLowerCase() == name.toLowerCase() ||
this.station?.generalInfo?.checkpoints.includes(stop.stopNameRAW) this.station?.generalInfo?.checkpoints.includes(stop.stopNameRAW) ||
this.onlineScenery?.missingCheckpoints.includes(stop.stopNameRAW)
); );
const sceneryName = const sceneryName =
+54 -29
View File
@@ -54,6 +54,18 @@
> >
</template> </template>
</div> </div>
<div class="timetable-checkpoints" v-else-if="onlineScenery">
<template v-for="(ch, i) in onlineScenery.missingCheckpoints" :key="i">
<template v-if="i > 0">&bull;</template>
<router-link
class="checkpoint-item"
:class="{ current: chosenCheckpoint === ch }"
:to="`/scenery?station=${onlineScenery.name}&checkpoint=${ch}`"
>{{ ch }}</router-link
>
</template>
</div>
</div> </div>
<div class="timetable-list"> <div class="timetable-list">
@@ -122,28 +134,30 @@
</span> </span>
<!-- Train info --> <!-- Train info -->
<span> <span
<b data-tooltip-type="TrainInfoTooltip"
data-tooltip-type="BaseTooltip" :data-tooltip-content="row.train.id"
:data-tooltip-content=" class="tooltip-help"
getCategoryExplanation(row.train.timetableData!.category) >
" <b class="text--primary">
class="text--primary tooltip-help"
>
{{ row.train.timetableData!.category }} {{ row.train.timetableData!.category }}
</b> </b>
<b>&nbsp;{{ row.train.trainNo }}</b> <b>&nbsp;{{ row.train.trainNo }}</b>
&bull;
{{ row.train.driverName }}
<i
class="fa-solid fa-user-slash"
style="color: salmon"
v-if="!row.train.online && row.train.lastSeen <= Date.now() - 60000"
></i>
</span> </span>
<span>&bull;</span>
<span>{{ row.train.driverName }}</span>
<span>&bull;</span>
<b style="color: #ddd">{{ row.train.stockList[0] }}</b>
<!-- Train stop comments --> <!-- Train stop comments -->
<span <span
class="stop-comments-icon"
v-if="row.checkpointStop.comments" v-if="row.checkpointStop.comments"
class="stop-comments-icon"
data-tooltip-type="BaseTooltip" data-tooltip-type="BaseTooltip"
:data-tooltip-content="row.checkpointStop.comments" :data-tooltip-content="row.checkpointStop.comments"
> >
@@ -243,7 +257,7 @@ import { useMainStore } from '../../store/mainStore';
import { useApiStore } from '../../store/apiStore'; import { useApiStore } from '../../store/apiStore';
import ScheduledTrainStatus from './ScheduledTrainStatus.vue'; import ScheduledTrainStatus from './ScheduledTrainStatus.vue';
import { SceneryTimetableRow } from './typings'; import { SceneryTimetableRow } from './typings';
import { ActiveScenery, Station } from '../../typings/common'; import { ActiveScenery, Station, TooltipTrainInfo, Train } from '../../typings/common';
import { getTrainStopStatus, stopStatusPriority } from './utils'; import { getTrainStopStatus, stopStatusPriority } from './utils';
export default defineComponent({ export default defineComponent({
@@ -285,6 +299,7 @@ export default defineComponent({
const chosenCheckpoint = ref( const chosenCheckpoint = ref(
props.station?.generalInfo?.checkpoints[0] ?? props.station?.generalInfo?.checkpoints[0] ??
props.onlineScenery?.missingCheckpoints[0] ??
props.station?.name ?? props.station?.name ??
route.query['station']?.toString() ?? route.query['station']?.toString() ??
'' ''
@@ -363,21 +378,30 @@ export default defineComponent({
methods: { methods: {
loadSelectedOption() { loadSelectedOption() {
if (!this.station) return;
if (!this.station.generalInfo) {
this.chosenCheckpoint = this.station.name;
return;
}
const queryCheckpoint = this.$route.query['checkpoint']?.toString(); const queryCheckpoint = this.$route.query['checkpoint']?.toString();
this.chosenCheckpoint = let checkpointsListRef: string[] | null = null;
this.station.generalInfo.checkpoints.find( let sceneryName = '';
(ch) => ch.toLocaleLowerCase() === queryCheckpoint?.toLocaleLowerCase()
) ?? if (this.station && this.station.generalInfo) {
this.station.generalInfo.checkpoints[0] ?? checkpointsListRef = this.station.generalInfo.checkpoints;
this.station.name; sceneryName = this.station.name;
} else if (this.onlineScenery) {
checkpointsListRef = this.onlineScenery.missingCheckpoints;
sceneryName = this.onlineScenery.name;
} else if (this.station) {
this.chosenCheckpoint = this.station.name;
sceneryName = this.station.name;
}
if (checkpointsListRef) {
this.chosenCheckpoint =
checkpointsListRef.find(
(ch) => ch.toLocaleLowerCase() === queryCheckpoint?.toLocaleLowerCase()
) ??
checkpointsListRef[0] ??
sceneryName;
}
}, },
setCheckpoint(cp: string) { setCheckpoint(cp: string) {
@@ -527,11 +551,12 @@ export default defineComponent({
.info-route { .info-route {
width: 100%; width: 100%;
margin-top: 0.25em;
} }
.stop-comments-icon > img { .stop-comments-icon > img {
width: 1.2em; width: 1.3em;
vertical-align: middle; vertical-align: top;
} }
.schedule { .schedule {
@@ -1,11 +1,18 @@
<template> <template>
<div class="general-status"> <div class="general-status">
<span <router-link
v-if="computedScheduledTrain.stationNameHref"
:to="`/scenery?station=${computedScheduledTrain.stationNameHref}`"
:class="computedScheduledTrain.status" :class="computedScheduledTrain.status"
@click.prevent="() => {}"
v-html="computedScheduledTrain.stopStatusIndicator" v-html="computedScheduledTrain.stopStatusIndicator"
> >
</span> </router-link>
<span
v-else
:class="computedScheduledTrain.status"
v-html="computedScheduledTrain.stopStatusIndicator"
></span>
</div> </div>
</template> </template>
@@ -27,6 +34,7 @@ export default defineComponent({
const { status, prevElement, currentElement, nextElement } = this.sceneryTimetableRow; const { status, prevElement, currentElement, nextElement } = this.sceneryTimetableRow;
let stopStatusIndicator = ''; let stopStatusIndicator = '';
let stationNameHref = '';
switch (status) { switch (status) {
case StopStatus.ARRIVING: case StopStatus.ARRIVING:
@@ -35,6 +43,8 @@ export default defineComponent({
prevStationName: prevElement?.stationName ?? '', prevStationName: prevElement?.stationName ?? '',
prevDepartureLine: prevElement?.departureRouteExt ?? '' prevDepartureLine: prevElement?.departureRouteExt ?? ''
}); });
stationNameHref = prevElement?.stationName ?? '';
} else { } else {
stopStatusIndicator = this.$t('timetables.desc-beginning'); stopStatusIndicator = this.$t('timetables.desc-beginning');
} }
@@ -48,6 +58,9 @@ export default defineComponent({
nextArrivalLine: nextElement?.arrivalRouteExt nextArrivalLine: nextElement?.arrivalRouteExt
}) })
: this.$t(`timetables.desc-end`); : this.$t(`timetables.desc-end`);
stationNameHref = nextElement?.stationName ?? '';
break; break;
case StopStatus.DEPARTED: case StopStatus.DEPARTED:
@@ -55,11 +68,15 @@ export default defineComponent({
stopStatusIndicator = this.$t('timetables.desc-departed-ends', { stopStatusIndicator = this.$t('timetables.desc-departed-ends', {
nextStationName: currentElement.stationName nextStationName: currentElement.stationName
}); });
stationNameHref = nextElement?.stationName ?? '';
} else { } else {
stopStatusIndicator = this.$t('timetables.desc-departed', { stopStatusIndicator = this.$t('timetables.desc-departed', {
nextStationName: nextElement?.stationName ?? currentElement.stationName, nextStationName: nextElement?.stationName ?? currentElement.stationName,
nextArrivalLine: nextElement?.arrivalRouteExt nextArrivalLine: nextElement?.arrivalRouteExt
}); });
stationNameHref = nextElement?.stationName ?? '';
} }
break; break;
@@ -69,6 +86,8 @@ export default defineComponent({
nextStationName: nextElement?.stationName, nextStationName: nextElement?.stationName,
nextArrivalLine: nextElement?.arrivalRouteExt nextArrivalLine: nextElement?.arrivalRouteExt
}); });
stationNameHref = nextElement?.stationName ?? '';
break; break;
case StopStatus.TERMINATED: case StopStatus.TERMINATED:
@@ -80,9 +99,18 @@ export default defineComponent({
} }
return { return {
...this.sceneryTimetableRow, ...this.sceneryTimetableRow,
stationNameHref,
stopStatusIndicator stopStatusIndicator
}; };
} }
},
methods: {
navigateToScenery(sceneryName?: string) {
if (!sceneryName) return;
this.$router.push(`/scenery?station=${sceneryName}`);
}
} }
}); });
</script> </script>
@@ -91,11 +119,11 @@ export default defineComponent({
.general-status { .general-status {
margin-top: 0.5em; margin-top: 0.5em;
span.arriving { & > .arriving {
color: #ccc; color: #ccc;
} }
span.departed { & > .departed {
color: lime; color: lime;
&-away { &-away {
@@ -103,15 +131,15 @@ export default defineComponent({
} }
} }
span.stopped { & > .stopped {
color: #ffa600; color: #ffa600;
} }
span.online { & > .online {
color: gold; color: gold;
} }
span.terminated { & > .terminated {
color: salmon; color: salmon;
} }
} }
+4 -4
View File
@@ -14,10 +14,10 @@
<transition name="dropdown-anim"> <transition name="dropdown-anim">
<div class="dropdown_wrapper" v-if="showDropdown"> <div class="dropdown_wrapper" v-if="showDropdown">
<div> <div>
<h1 class="stats-title text--primary"> <h2 class="stats-title text--primary">
<img src="/images/icon-stats.svg" alt="Open filters icon" height="28" /> <img src="/images/icon-stats.svg" alt="Open filters icon" height="28" />
{{ $t('station-stats.title') }} {{ $t('station-stats.title') }}
</h1> </h2>
<hr style="margin: 0.5em 0" /> <hr style="margin: 0.5em 0" />
@@ -245,7 +245,7 @@ export default defineComponent({
@use '../../styles/badge'; @use '../../styles/badge';
@use '../../styles/responsive'; @use '../../styles/responsive';
h1.stats-title img { .stats-title img {
vertical-align: text-bottom; vertical-align: text-bottom;
} }
@@ -279,7 +279,7 @@ h1.stats-title img {
} }
@include responsive.smallScreen { @include responsive.smallScreen {
h1.stats-title { .stats-title {
text-align: center; text-align: center;
} }
+66 -36
View File
@@ -33,12 +33,12 @@
class="header-image" class="header-image"
:class="headerName" :class="headerName"
> >
<span class="header_wrapper"> <span
<img class="header_wrapper"
:src="`/images/icon-${headerName}.svg`" data-tooltip-type="BaseTooltip"
:alt="headerName" :data-tooltip-content="$t(`sceneries.headers.${headerName}`)"
:title="$t(`sceneries.headers.${headerName}`)" >
/> <img :src="`/images/icon-${headerName}.svg`" :alt="headerName" />
<img <img
class="sort-icon" class="sort-icon"
@@ -76,37 +76,49 @@
station.generalInfo.availability != 'nonPublic' && station.generalInfo.availability != 'nonPublic' &&
station.generalInfo.availability != 'unavailable' station.generalInfo.availability != 'unavailable'
" "
data-tooltip-type="BaseTooltip"
:data-tooltip-content="`${$t(`sceneries.info.${station.generalInfo.availability}`)} (${$t(
'sceneries.info.req-level',
{ lvl: station.generalInfo.reqLevel },
station.generalInfo.reqLevel
)})`"
:style="calculateExpStyle(station.generalInfo.reqLevel)" :style="calculateExpStyle(station.generalInfo.reqLevel)"
> >
{{ station.generalInfo.reqLevel >= 2 ? station.generalInfo.reqLevel : 'L' }} {{ station.generalInfo.reqLevel >= 2 ? station.generalInfo.reqLevel : 'L' }}
</span> </span>
<span v-else-if="station.generalInfo.availability == 'abandoned'"> <span
<img v-else-if="station.generalInfo.availability == 'abandoned'"
src="/images/icon-abandoned.svg" data-tooltip-type="BaseTooltip"
alt="non-public" :data-tooltip-content="$t('sceneries.info.abandoned')"
:title="$t('sceneries.info.abandoned')" >
/> <img src="/images/icon-abandoned.svg" alt="non-public" />
</span> </span>
<span v-else-if="station.generalInfo.availability == 'nonPublic'"> <span
<img v-else-if="station.generalInfo.availability == 'nonPublic'"
src="/images/icon-lock.svg" data-tooltip-type="BaseTooltip"
alt="non-public" :data-tooltip-content="$t('sceneries.info.non-public')"
:title="$t('sceneries.info.non-public')" >
/> <img src="/images/icon-lock.svg" alt="non-public" />
</span> </span>
<span v-else> <span
<img v-else
src="/images/icon-unavailable.svg" data-tooltip-type="BaseTooltip"
alt="unavailable" :data-tooltip-content="$t('sceneries.info.unavailable')"
:title="$t('sceneries.info.unavailable')" >
/> <img src="/images/icon-unavailable.svg" alt="unavailable" />
</span> </span>
</span> </span>
<span v-else> ? </span> <span
v-else
data-tooltip-type="BaseTooltip"
:data-tooltip-content="$t('sceneries.info.unknown')"
>
?
</span>
</td> </td>
<td class="station-status"> <td class="station-status">
@@ -153,7 +165,8 @@
<span <span
v-if="station.generalInfo.routes.singleElectrifiedNames.length != 0" v-if="station.generalInfo.routes.singleElectrifiedNames.length != 0"
class="track catenary" class="track catenary"
:title="`${$t('sceneries.info.single-track-routes-catenary')}${ data-tooltip-type="BaseTooltip"
:data-tooltip-content="`${$t('sceneries.info.single-track-routes-catenary')}${
station.generalInfo.routes.singleElectrifiedNames.length station.generalInfo.routes.singleElectrifiedNames.length
}`" }`"
> >
@@ -163,7 +176,8 @@
<span <span
v-if="station.generalInfo.routes.singleOtherNames.length != 0" v-if="station.generalInfo.routes.singleOtherNames.length != 0"
class="track no-catenary" class="track no-catenary"
:title="`${$t('sceneries.info.single-track-routes-other')}${ data-tooltip-type="BaseTooltip"
:data-tooltip-content="`${$t('sceneries.info.single-track-routes-other')}${
station.generalInfo.routes.singleOtherNames.length station.generalInfo.routes.singleOtherNames.length
}`" }`"
> >
@@ -177,7 +191,8 @@
<span <span
v-if="station.generalInfo.routes.doubleElectrifiedNames.length != 0" v-if="station.generalInfo.routes.doubleElectrifiedNames.length != 0"
class="track catenary" class="track catenary"
:title="`${$t('sceneries.info.double-track-routes-catenary')}${ data-tooltip-type="BaseTooltip"
:data-tooltip-content="`${$t('sceneries.info.double-track-routes-catenary')}${
station.generalInfo.routes.doubleElectrifiedNames.length station.generalInfo.routes.doubleElectrifiedNames.length
}`" }`"
> >
@@ -187,7 +202,8 @@
<span <span
v-if="station.generalInfo.routes.doubleOtherNames.length != 0" v-if="station.generalInfo.routes.doubleOtherNames.length != 0"
class="track no-catenary" class="track no-catenary"
:title="`${$t('sceneries.info.double-track-routes-other')}${ data-tooltip-type="BaseTooltip"
:data-tooltip-content="`${$t('sceneries.info.double-track-routes-other')}${
station.generalInfo.routes.doubleOtherNames.length station.generalInfo.routes.doubleOtherNames.length
}`" }`"
> >
@@ -201,7 +217,8 @@
v-if="station.generalInfo?.signalType" v-if="station.generalInfo?.signalType"
class="scenery-icon icon-info" class="scenery-icon icon-info"
:class="station.generalInfo?.controlType.replace('+', '-')" :class="station.generalInfo?.controlType.replace('+', '-')"
:title=" data-tooltip-type="BaseTooltip"
:data-tooltip-content="
$t('sceneries.info.control-type') + $t('sceneries.info.control-type') +
$t(`controls.${station.generalInfo?.controlType}`) $t(`controls.${station.generalInfo?.controlType}`)
" "
@@ -214,7 +231,8 @@
class="icon-info" class="icon-info"
:src="`/images/icon-${station.generalInfo.signalType}.svg`" :src="`/images/icon-${station.generalInfo.signalType}.svg`"
:alt="station.generalInfo.signalType" :alt="station.generalInfo.signalType"
:title=" data-tooltip-type="BaseTooltip"
:data-tooltip-content="
$t('sceneries.info.signals-type') + $t(`signals.${station.generalInfo.signalType}`) $t('sceneries.info.signals-type') + $t(`signals.${station.generalInfo.signalType}`)
" "
/> />
@@ -224,7 +242,8 @@
class="icon-info" class="icon-info"
src="/images/icon-SUP.svg" src="/images/icon-SUP.svg"
alt="SUP (RASP-UZK)" alt="SUP (RASP-UZK)"
:title="$t('sceneries.info.SUP')" data-tooltip-type="BaseTooltip"
:data-tooltip-content="$t('sceneries.info.SUP')"
/> />
<img <img
@@ -232,7 +251,8 @@
class="icon-info" class="icon-info"
src="/images/icon-ASDEK.svg" src="/images/icon-ASDEK.svg"
alt="dSAT ASDEK" alt="dSAT ASDEK"
:title="$t('sceneries.info.ASDEK')" data-tooltip-type="BaseTooltip"
:data-tooltip-content="$t('sceneries.info.ASDEK')"
/> />
<img <img
@@ -240,7 +260,8 @@
class="icon-info" class="icon-info"
src="/images/icon-unknown.svg" src="/images/icon-unknown.svg"
alt="icon-unknown" alt="icon-unknown"
:title="$t('sceneries.info.unknown')" data-tooltip-type="BaseTooltip"
:data-tooltip-content="$t('sceneries.info.unknown')"
/> />
</td> </td>
@@ -248,7 +269,7 @@
class="station-users" class="station-users"
:class="{ inactive: !station.onlineInfo }" :class="{ inactive: !station.onlineInfo }"
data-tooltip-type="UsersTooltip" data-tooltip-type="UsersTooltip"
:data-tooltip-content="JSON.stringify(station.onlineInfo?.stationTrains ?? [])" :data-tooltip-content="getUsersTooltipContent(station.onlineInfo?.stationTrains ?? [])"
> >
<span class="text--primary">{{ <span class="text--primary">{{
station.onlineInfo?.stationTrains?.length ?? '-' station.onlineInfo?.stationTrains?.length ?? '-'
@@ -318,7 +339,7 @@ import dateMixin from '../../mixins/dateMixin';
import styleMixin from '../../mixins/styleMixin'; import styleMixin from '../../mixins/styleMixin';
import { useApiStore } from '../../store/apiStore'; import { useApiStore } from '../../store/apiStore';
import { useMainStore } from '../../store/mainStore'; import { useMainStore } from '../../store/mainStore';
import { Station, Status } from '../../typings/common'; import { Station, Status, TooltipUserTrain, Train } from '../../typings/common';
import { useTooltipStore } from '../../store/tooltipStore'; import { useTooltipStore } from '../../store/tooltipStore';
import { getChangedFilters } from '../../managers/stationFilterManager'; import { getChangedFilters } from '../../managers/stationFilterManager';
import { ActiveSorter, HeadIdsType, headIconsIds, headIds } from './typings'; import { ActiveSorter, HeadIdsType, headIconsIds, headIds } from './typings';
@@ -394,6 +415,15 @@ export default defineComponent({
else this.activeSorter.dir = 1; else this.activeSorter.dir = 1;
this.activeSorter.headerName = headerName; this.activeSorter.headerName = headerName;
},
getUsersTooltipContent(stationTrains: Train[]): string {
const usersTrains: TooltipUserTrain[] = stationTrains.map((train) => ({
driverName: train.driverName,
trainNo: train.trainNo
}));
return JSON.stringify(usersTrains);
} }
} }
}); });
+5 -3
View File
@@ -132,13 +132,15 @@ function filterSliderValues(filters: Record<string, any>, generalInfo: StationGe
filters['minOneWayCatenary'] > routes.singleElectrifiedNames.length || filters['minOneWayCatenary'] > routes.singleElectrifiedNames.length ||
filters['minOneWay'] > routes.singleOtherNames.length || filters['minOneWay'] > routes.singleOtherNames.length ||
filters['minTwoWayCatenary'] > routes.doubleElectrifiedNames.length || filters['minTwoWayCatenary'] > routes.doubleElectrifiedNames.length ||
// filters['minTwoWay'] > routes.doubleOtherNames.length || filters['minTwoWay'] > routes.doubleOtherNames.length ||
filters['minOneWayCatenaryInt'] > filters['minOneWayCatenaryInt'] >
internalRoutes.filter((r) => r.routeTracks == 1 && r.isElectric == true).length || internalRoutes.filter((r) => r.routeTracks == 1 && r.isElectric == true).length ||
filters['minOneWayInt'] > filters['minOneWayInt'] >
internalRoutes.filter((r) => r.routeTracks == 1 && r.isElectric == false).length || internalRoutes.filter((r) => r.routeTracks == 1 && r.isElectric == false).length ||
filters['minTwoWayCatenaryInt'] > filters['minTwoWayCatenaryInt'] >
internalRoutes.filter((r) => r.routeTracks == 2 && r.isElectric == true).length internalRoutes.filter((r) => r.routeTracks == 2 && r.isElectric == true).length ||
filters['minTwoWayInt'] >
internalRoutes.filter((r) => r.routeTracks == 2 && r.isElectric == false).length
); );
} }
@@ -243,7 +245,7 @@ export const sortStations = (a: Station, b: Station, sorter: ActiveSorter) => {
return a.name.localeCompare(b.name); return a.name.localeCompare(b.name);
}; };
export const filterStations = (station: Station, filters: Record<string, any>) => { export const filterStations = (station: Station, filters: Record<string, any>) => {
if (filters['free'] && (!station.onlineInfo || station.onlineInfo.dispatcherId == -1)) if (filters['free'] && (!station.onlineInfo || station.onlineInfo.dispatcherId == -1))
return false; return false;
+3 -1
View File
@@ -13,6 +13,7 @@ import BaseTooltip from './BaseTooltip.vue';
import SpawnsTooltip from './SpawnsTooltip.vue'; import SpawnsTooltip from './SpawnsTooltip.vue';
import UsersTooltip from './UsersTooltip.vue'; import UsersTooltip from './UsersTooltip.vue';
import HtmlTooltip from './HtmlTooltip.vue'; import HtmlTooltip from './HtmlTooltip.vue';
import TrainInfoTooltip from "./TrainInfoTooltip.vue";
const BOX_PADDING_PX = 20; const BOX_PADDING_PX = 20;
@@ -23,7 +24,8 @@ export default defineComponent({
BaseTooltip, BaseTooltip,
SpawnsTooltip, SpawnsTooltip,
UsersTooltip, UsersTooltip,
HtmlTooltip HtmlTooltip,
TrainInfoTooltip
}, },
data() { data() {
@@ -0,0 +1,78 @@
<template>
<div class="tooltip-content">
<span v-if="trainInfo">
<b v-if="trainInfo.timetableData" style="text-transform: uppercase">
<span class="text--primary">{{ trainInfo.timetableData.category }}</span>
{{ getCategoryExplanation(trainInfo.timetableData.category) }}
</b>
<div class="text--primary">
<b>{{ trainInfo.stockList[0] }}</b> &bull; {{ trainInfo.length }}m &bull;
{{ (trainInfo.mass / 1000).toFixed(2) }}t
<span v-if="trainInfo.timetableData">
&bull; vRJ:
{{
trainInfo.timetableData?.trainMaxSpeed ||
getStockSpeedLimit(trainInfo.stockList, trainInfo.mass)
}}km/h
</span>
<span v-else class="text--grayed font--italic">
&bull; vMax:
{{ getStockSpeedLimit(trainInfo.stockList, trainInfo.mass) }}km/h
</span>
</div>
<div class="text--grayed">
{{ displayTrainPosition(trainInfo) }} - {{ trainInfo.speed }}km/h
<span v-if="!trainInfo.online" style="color: salmon">
- offline {{ lastSeenMessage(trainInfo.lastSeen) }}</span
>
</div>
<div></div>
</span>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { useTooltipStore } from '../../store/tooltipStore';
import trainCategoryMixin from '../../mixins/trainCategoryMixin';
import trainInfoMixin from '../../mixins/trainInfoMixin';
import { useMainStore } from '../../store/mainStore';
export default defineComponent({
mixins: [trainCategoryMixin, trainInfoMixin],
data: () => ({
tooltipStore: useTooltipStore(),
mainStore: useMainStore()
}),
computed: {
trainInfo() {
if (this.tooltipStore.content == '') return null;
// Passed "content" string should be the desired train's ID
return this.mainStore.trainList.find((t) => t.id === this.tooltipStore.content);
},
lastSceneryStatus() {}
}
});
</script>
<style lang="scss" scoped>
.tooltip-content {
padding: 0.25em 0.5em;
border-radius: 0.25em;
width: 100%;
background-color: #1f1f1f;
box-shadow: 0 0 5px 2px #aaa;
}
img {
height: 1em;
}
</style>
+2 -2
View File
@@ -10,7 +10,7 @@
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { useTooltipStore } from '../../store/tooltipStore'; import { useTooltipStore } from '../../store/tooltipStore';
import { Train } from '../../typings/common'; import { TooltipUserTrain } from '../../typings/common';
export default defineComponent({ export default defineComponent({
data() { data() {
@@ -23,7 +23,7 @@ export default defineComponent({
trains() { trains() {
if (this.tooltipStore.content == '') return []; if (this.tooltipStore.content == '') return [];
const parsedTrains = JSON.parse(this.tooltipStore.content) as Train[]; const parsedTrains = JSON.parse(this.tooltipStore.content) as TooltipUserTrain[];
return (parsedTrains ?? []).sort((a, b) => a.trainNo - b.trainNo); return (parsedTrains ?? []).sort((a, b) => a.trainNo - b.trainNo);
} }
} }
+12 -52
View File
@@ -110,7 +110,10 @@
{{ $t('trains.scenery-offline') }} {{ $t('trains.scenery-offline') }}
</div> </div>
<div v-if="!train.online" class="train-badge offline"> <div
v-if="!train.online && train.lastSeen <= Date.now() - 60000"
class="train-badge offline"
>
<i class="fa-solid fa-user-slash"></i> <i class="fa-solid fa-user-slash"></i>
Offline {{ lastSeenMessage(train.lastSeen) }} Offline {{ lastSeenMessage(train.lastSeen) }}
</div> </div>
@@ -132,7 +135,11 @@
<img src="/images/icon-speed.svg" alt="speed icon" /> <img src="/images/icon-speed.svg" alt="speed icon" />
{{ train.speed }} km/h {{ train.speed }} km/h
<span v-if="stockSpeedLimit != Infinity"> <span v-if="train.timetableData">
&bull; vRJ: {{ train.timetableData.trainMaxSpeed }} km/h
</span>
<span v-else-if="stockSpeedLimit != Infinity">
&bull; &bull;
<em <em
class="text--grayed" class="text--grayed"
@@ -216,57 +223,9 @@ export default defineComponent({
computed: { computed: {
stockSpeedLimit() { stockSpeedLimit() {
let isPassenger = true; return this.getStockSpeedLimit(this.train.stockList, this.train.mass);
// Check the whole consist speed limit
const vehicleMaxSpeed = this.train.stockList.reduce((acc, stockName, i) => {
const [vehicleName, vehicleCargo] = stockName.split(':');
const vehicleData = this.apiStore.vehiclesData?.find((v) => v.name == vehicleName);
if (!vehicleData) return acc;
let vehicleSpeed = vehicleData.group.speed;
if (vehicleData.type == 'wagon-freight') {
isPassenger = false;
if (vehicleCargo !== undefined && vehicleData.group.speedLoaded) {
vehicleSpeed = vehicleData.group.speedLoaded;
}
}
return Math.min(vehicleSpeed, acc);
}, Infinity);
// Check the head vehicle speed limit
const headLocoName = this.train.stockList[0];
const headLocoVehicleData = this.apiStore.vehiclesData?.find((v) => v.name == headLocoName);
// Omit speed check for head vehicle if there's no data for it
if (!headLocoName || !headLocoVehicleData || !headLocoVehicleData.group.massSpeeds)
return vehicleMaxSpeed;
const massSpeeds =
headLocoVehicleData.group.massSpeeds[
this.train.stockList.length == 1 ? 'none' : isPassenger ? 'passenger' : 'cargo'
];
// Omit speed check if there's no data on mass speeds
if (!massSpeeds) return vehicleMaxSpeed;
// Number type for locomotives alone
if (typeof massSpeeds === 'number') return massSpeeds;
// Record type for passenger or cargo, find the closest range
const massKey = Object.keys(massSpeeds).findLast(
(massKey) => this.train.mass >= Number(massKey)
);
const massMaxSpeed = massKey ? massSpeeds[massKey] : Infinity;
return Math.min(massMaxSpeed, vehicleMaxSpeed);
}, },
journalRouteLocation() { journalRouteLocation() {
return { return {
path: '/journal/timetables', path: '/journal/timetables',
@@ -394,6 +353,7 @@ export default defineComponent({
.status-badges { .status-badges {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
margin-left: 0.25em;
gap: 0.25em; gap: 0.25em;
+13 -8
View File
@@ -30,17 +30,22 @@
</div> </div>
<div class="search-box"> <div class="search-box">
<select <datalist id="search-active-driver">
class="search-input"
name="active-trains"
id="active-trains"
v-model="searchedDriver"
>
<option value="">{{ $t('options.select-driver') }}</option>
<option v-for="driverName in activeDriverNames" :value="driverName"> <option v-for="driverName in activeDriverNames" :value="driverName">
{{ driverName }} {{ driverName }}
</option> </option>
</select> </datalist>
<input
class="search-input"
list="search-active-driver"
name="search-active-driver"
id="search-active-driver"
:placeholder="$t(`options.search-driver`)"
v-model="searchedDriver"
@focus="preventKeyDown = true"
@blur="preventKeyDown = false"
/>
<button class="btn btn--action search-exit" @click="onInputClear('driver')"> <button class="btn btn--action search-exit" @click="onInputClear('driver')">
<img src="/images/icon-exit.svg" alt="Trains search clear icon" /> <img src="/images/icon-exit.svg" alt="Trains search clear icon" />
+37 -26
View File
@@ -12,8 +12,16 @@
:data-delayed="stop.departureDelay > 0" :data-delayed="stop.departureDelay > 0"
:data-stop-type="stop.type" :data-stop-type="stop.type"
:data-is-active="stop.isActive" :data-is-active="stop.isActive"
:data-track-count-departure="stop.departureLineInfo?.routeTracks ?? 2" :data-track-count-departure="
:data-track-count-arrival="stop.arrivalLineInfo?.routeTracks ?? 2" stop.departureLineInfo?.routeTracks ??
stop.nextPointRef?.arrivalLineInfo?.routeTracks ??
2
"
:data-track-count-arrival="
stop.arrivalLineInfo?.routeTracks ??
scheduleStops[i - 1]?.departureLineInfo?.routeTracks ??
2
"
> >
<span class="stop_info"> <span class="stop_info">
<span class="distance"> <span class="distance">
@@ -60,7 +68,8 @@
<span> <span>
| |
{{ {{
stop.departureLineInfo.routeSpeedExit stop.departureLineInfo.routeSpeedExit &&
stop.departureLineInfo.routeSpeedExit != stop.departureLineInfo.routeSpeed
? `${stop.departureLineInfo.routeSpeedExit} (${stop.departureLineInfo.routeSpeed})` ? `${stop.departureLineInfo.routeSpeedExit} (${stop.departureLineInfo.routeSpeed})`
: stop.departureLineInfo.routeSpeed : stop.departureLineInfo.routeSpeed
}}</span }}</span
@@ -113,10 +122,16 @@
<span> {{ stop.nextPointRef.arrivalLine }}</span> <span> {{ stop.nextPointRef.arrivalLine }}</span>
<span v-if="stop.nextPointRef.arrivalLineInfo"> <span v-if="stop.nextPointRef.arrivalLineInfo">
<span> | {{ stop.nextPointRef.arrivalLineInfo!.routeSpeed }}</span> <span> | {{ stop.nextPointRef.arrivalLineInfo.routeSpeed }}</span>
<span v-if="stop.nextPointRef.arrivalLineInfo!.routeSpeedExit" <span
>({{ stop.nextPointRef.arrivalLineInfo!.routeSpeedExit }})</span v-if="
stop.nextPointRef.arrivalLineInfo.routeSpeedExit &&
stop.nextPointRef.arrivalLineInfo.routeSpeedExit !=
stop.nextPointRef.arrivalLineInfo.routeSpeed
"
> >
({{ stop.nextPointRef.arrivalLineInfo.routeSpeedExit }})
</span>
<img <img
:src=" :src="
@@ -186,26 +201,28 @@ export default defineComponent({
const sceneryData = const sceneryData =
this.store.stationList?.find((sc) => sc.name == pathEl.stationName) ?? null; this.store.stationList?.find((sc) => sc.name == pathEl.stationName) ?? null;
if (!sceneryData || !sceneryData.generalInfo) return null;
const activeScenery = this.apiStore.activeData?.activeSceneries?.find( const activeScenery = this.apiStore.activeData?.activeSceneries?.find(
(sc) => sc.stationName == pathEl.stationName (sc) => sc.stationName == pathEl.stationName
); );
const arrivalLineData = pathEl.arrivalRouteExt const arrivalLineData = sceneryData?.generalInfo
? (sceneryData.generalInfo.routes.all.find( ? pathEl.arrivalRouteExt
(rt) => rt.routeName == pathEl.arrivalRouteExt ? (sceneryData.generalInfo.routes.all.find(
) ?? null) (rt) => rt.routeName == pathEl.arrivalRouteExt
) ?? null)
: null
: null; : null;
const departureLineData = pathEl.departureRouteExt const departureLineData = sceneryData?.generalInfo
? (sceneryData.generalInfo.routes.all.find( ? pathEl.departureRouteExt
(rt) => rt.routeName == pathEl.departureRouteExt ? (sceneryData.generalInfo.routes.all.find(
) ?? null) (rt) => rt.routeName == pathEl.departureRouteExt
) ?? null)
: null
: null; : null;
return { return {
generalInfo: sceneryData.generalInfo, generalInfo: sceneryData?.generalInfo ?? null,
isOnline: isOnline:
activeScenery && activeScenery &&
(activeScenery.isOnline == 1 || activeScenery.lastSeen >= Date.now() - 60000), (activeScenery.isOnline == 1 || activeScenery.lastSeen >= Date.now() - 60000),
@@ -234,7 +251,7 @@ export default defineComponent({
let isActive = false; let isActive = false;
if (pathData?.departureLineData) { if (pathData?.departureLineData) {
// arrivalLineInfo = pathData.departureLineData; arrivalLineInfo = pathData.departureLineData;
departureLineInfo = pathData.departureLineData; departureLineInfo = pathData.departureLineData;
} }
@@ -245,22 +262,16 @@ export default defineComponent({
isExternal = true; isExternal = true;
departureLineInfo = pathData?.arrivalLineData ?? null; departureLineInfo = pathData?.arrivalLineData ?? null;
arrivalLineInfo = pathData.arrivalLineData;
if (pathData?.arrivalLineData) {
arrivalLineInfo = pathData.arrivalLineData;
}
} }
let correctedDepartureLineData: StationRoutesInfo | null = null;
const internalRouteInfo = stop.departureLine const internalRouteInfo = stop.departureLine
? pathData?.generalInfo.routes.all.find( ? pathData?.generalInfo?.routes.all.find(
(route) => route.isInternal && route.routeName == stop.departureLine (route) => route.isInternal && route.routeName == stop.departureLine
) )
: undefined; : undefined;
if (internalRouteInfo) { if (internalRouteInfo) {
correctedDepartureLineData = internalRouteInfo;
departureLineInfo = internalRouteInfo; departureLineInfo = internalRouteInfo;
} }
+4 -4
View File
@@ -13,10 +13,10 @@
<transition name="dropdown-anim"> <transition name="dropdown-anim">
<div class="dropdown_wrapper" v-if="showOptions"> <div class="dropdown_wrapper" v-if="showOptions">
<h1 class="text--primary"> <h2 class="stats-title text--primary">
<img src="/images/icon-stats.svg" alt="Open filters icon" height="28" /> <img src="/images/icon-stats.svg" alt="Open filters icon" height="28" />
{{ $t('train-stats.title') }} {{ $t('train-stats.title') }}
</h1> </h2>
<hr style="margin: 0.5em 0" /> <hr style="margin: 0.5em 0" />
@@ -229,7 +229,7 @@ export default defineComponent({
@use '../../styles/badge'; @use '../../styles/badge';
@use '../../styles/responsive'; @use '../../styles/responsive';
h1 img { .stats-title img {
vertical-align: text-bottom; vertical-align: text-bottom;
} }
@@ -257,7 +257,7 @@ h3 {
text-align: center; text-align: center;
} }
h1 { .stats-title {
text-align: center; text-align: center;
} }
} }
+22 -19
View File
@@ -142,7 +142,7 @@
"title": "Control type", "title": "Control type",
"SPK": "SPK", "SPK": "SPK",
"SCS": "SCS", "SCS": "SCS",
"SCS-SPK": "SCS/SPK", "SCS-SPK": "SCS + SPK",
"SPE": "SPE", "SPE": "SPE",
"ręczne": "manual", "ręczne": "manual",
"ręczne+SPK": "manual + SPK", "ręczne+SPK": "manual + SPK",
@@ -153,7 +153,7 @@
"abbrevs": { "abbrevs": {
"SPK": "SPK", "SPK": "SPK",
"SCS": "SCS", "SCS": "SCS",
"SCS-SPK": "S/S", "SCS-SPK": "S+S",
"SPE": "SPE", "SPE": "SPE",
"ręczne": "R", "ręczne": "R",
"ręczne+SPK": "R", "ręczne+SPK": "R",
@@ -271,6 +271,7 @@
"SCS": "SCS", "SCS": "SCS",
"SCS-R": "SCS + MANUAL", "SCS-R": "SCS + MANUAL",
"SCS-M": "SCS + MECH.", "SCS-M": "SCS + MECH.",
"SCS-SPK": "SCS + SPK",
"SPE": "SPE", "SPE": "SPE",
"manual": "MANUAL", "manual": "MANUAL",
"mechanical": "MECHANICAL", "mechanical": "MECHANICAL",
@@ -337,18 +338,20 @@
}, },
"info": { "info": {
"control-type": "Control type: ", "control-type": "Control type: ",
"signals-type": "Signals type: ", "signals-type": "Signalling type: ",
"SBL": "This scenery has automatic block signalling (ABS/SBL) system on following routes: ", "SBL": "A scenery with automatic block signalling (ABS/SBL) on routes: ",
"SUP": "Requires the SUP program (level crossing remote control)", "SUP": "Requires the SUP program (level crossing remote control)",
"ASDEK": "Requires the ASDEK program (defect detection of moving rolling stock)", "ASDEK": "ASDEK program available (defect detection of moving rolling stock)",
"TWB-all": "This scenery has two-way route blockade on all routes", "TWB-all": "This scenery has two-way route blockade on all routes",
"TWB-routes": "This scenery has two-way route blockade on following routes: ", "TWB-routes": "This scenery has two-way route blockade on following routes: ",
"default": "This scenery is available by default", "default": "Scenery available in the game package",
"non-public": "This scenery is not public", "nonDefault": "Scenery available to download from the forum site",
"unavailable": "This scenery is unavailable", "req-level": "all dispatcher levels | requries {lvl} dispatcher lvl | requires {lvl} dispatcher lvl",
"abandoned": "This scenery is no longer supported by its creators", "non-public": "Non-public scenery",
"unknown": "This scenery isn't recognizable right now", "unavailable": "Unavailable scenery",
"real": "Scenery with real lines: ", "abandoned": "Abandoned scenery",
"unknown": "Unknown scenery",
"real": "Scenery with real Polish routes: ",
"double-track-routes-catenary": "Electrified double-track routes count: ", "double-track-routes-catenary": "Electrified double-track routes count: ",
"single-track-routes-catenary": "Electrified single-track routes count: ", "single-track-routes-catenary": "Electrified single-track routes count: ",
"double-track-routes-other": "Not electrified double-track routes count: ", "double-track-routes-other": "Not electrified double-track routes count: ",
@@ -591,14 +594,14 @@
"terminates": "TERMINATES\nHERE", "terminates": "TERMINATES\nHERE",
"from": "Arrives from", "from": "Arrives from",
"to": "Departs to", "to": "Departs to",
"desc-beginning": "The train begins here", "desc-beginning": "Outside scenery / begins here",
"desc-arriving": "<i>Arrives from: <b>{prevStationName} ({prevDepartureLine})</b></i>", "desc-arriving": "Arrives from: <b><u>{prevStationName} ({prevDepartureLine})</u></b>",
"desc-online": "On scenery / <i>direction: <b>{nextStationName} ({nextArrivalLine})</b></i>", "desc-online": "On scenery / direction: <b><u>{nextStationName} ({nextArrivalLine})</u></b>",
"desc-stopped": "On scenery - stopped / <i>direction: <b>{nextStationName} ({nextArrivalLine})</b></i>", "desc-stopped": "On scenery - stopped / direction: <b><u>{nextStationName} ({nextArrivalLine})</u></b>",
"desc-next-arrival": "On scenery / <i>direction: <b>{nextStationName} ({nextArrivalLine})</b></i>", "desc-next-arrival": "On scenery / direction: <b><u>{nextStationName} ({nextArrivalLine})</u></b>",
"desc-departed": "On scenery / <i>departed to: <b>{nextStationName} ({nextArrivalLine})</b></i>", "desc-departed": "On scenery / departed to: <b><u>{nextStationName} ({nextArrivalLine})</u></b>",
"desc-departed-ends": "On scenery / <i>departed to: <b>{nextStationName}</b></i>", "desc-departed-ends": "On scenery / departed to: <b><u>{nextStationName}</u></b>",
"desc-departed-away": "<i>Departed to: <b>{nextStationName} ({nextArrivalLine})</b></i>", "desc-departed-away": "Departed to: <b><u>{nextStationName} ({nextArrivalLine})</u></b>",
"desc-end": "The train terminates here", "desc-end": "The train terminates here",
"desc-terminated": "The train has been terminated" "desc-terminated": "The train has been terminated"
}, },
+14 -11
View File
@@ -139,7 +139,7 @@
"title": "Sterowanie", "title": "Sterowanie",
"SPK": "SPK", "SPK": "SPK",
"SCS": "SCS", "SCS": "SCS",
"SCS-SPK": "SCS/SPK", "SCS-SPK": "SCS + SPK",
"SPE": "SPE", "SPE": "SPE",
"ręczne": "ręczne", "ręczne": "ręczne",
"ręczne+SPK": "ręczne z SPK", "ręczne+SPK": "ręczne z SPK",
@@ -150,7 +150,7 @@
"abbrevs": { "abbrevs": {
"SPK": "SPK", "SPK": "SPK",
"SCS": "SCS", "SCS": "SCS",
"SCS-SPK": "S/S", "SCS-SPK": "S+S",
"SPE": "SPE", "SPE": "SPE",
"ręczne": "R", "ręczne": "R",
"ręczne+SPK": "R", "ręczne+SPK": "R",
@@ -269,6 +269,7 @@
"SCS": "SCS", "SCS": "SCS",
"SCS-R": "SCS + RĘCZNE", "SCS-R": "SCS + RĘCZNE",
"SCS-M": "SCS + MECH.", "SCS-M": "SCS + MECH.",
"SCS-SPK": "SCS + SPK",
"SPE": "SPE", "SPE": "SPE",
"manual": "RĘCZNE", "manual": "RĘCZNE",
"SUP": "SUP (RASP-UZK)", "SUP": "SUP (RASP-UZK)",
@@ -338,8 +339,10 @@
"signals-type": "Sygnalizacja: ", "signals-type": "Sygnalizacja: ",
"SBL": "Sceneria posiada SBL na szlakach: ", "SBL": "Sceneria posiada SBL na szlakach: ",
"SUP": "Wymaga programu SUP do kontroli systemu RASP-UZK", "SUP": "Wymaga programu SUP do kontroli systemu RASP-UZK",
"ASDEK": "Wymaga programu ASDEK do detekcji stanów awaryjnych taboru w ruchu", "ASDEK": "Dostępny program ASDEK do detekcji stanów awaryjnych taboru w ruchu",
"default": "Sceneria dostępna domyślnie w paczce z grą", "default": "Sceneria dostępna domyślnie w paczce z grą",
"nonDefault": "Sceneria dostępna do pobrania z forum symulatora",
"req-level": "ogólnodostępna | od {lvl} poz. DR | od {lvl} poz. DR",
"non-public": "Sceneria niepubliczna", "non-public": "Sceneria niepubliczna",
"unavailable": "Sceneria niedostępna", "unavailable": "Sceneria niedostępna",
"abandoned": "Sceneria wycofana z rozgrywki", "abandoned": "Sceneria wycofana z rozgrywki",
@@ -577,14 +580,14 @@
"terminates": "KOŃCZY BIEG", "terminates": "KOŃCZY BIEG",
"from": "Przyjedzie z", "from": "Przyjedzie z",
"to": "Odjeżdża do", "to": "Odjeżdża do",
"desc-beginning": "Pociąg rozpoczyna bieg", "desc-beginning": "Poza scenerią / rozpoczyna bieg",
"desc-arriving": "<i>Przyjedzie z: <b>{prevStationName} ({prevDepartureLine})</b></i>", "desc-arriving": "Przyjedzie z: <b><u>{prevStationName} ({prevDepartureLine})</u></b>",
"desc-online": "Na scenerii / <i>kierunek: <b>{nextStationName} ({nextArrivalLine})</b></i>", "desc-online": "Na scenerii / kierunek: <b><u>{nextStationName} ({nextArrivalLine})</u></b>",
"desc-stopped": "Na scenerii - postój / <i>kierunek: <b>{nextStationName} ({nextArrivalLine})</b></i>", "desc-stopped": "Na scenerii - postój / kierunek: <b><u>{nextStationName} ({nextArrivalLine})</u></b>",
"desc-next-arrival": "Na scenerii / <i>kierunek: <b>{nextStationName} ({nextArrivalLine})</b></i>", "desc-next-arrival": "Na scenerii / kierunek: <b><u>{nextStationName} ({nextArrivalLine})</u></b>",
"desc-departed": "Na scenerii / <i>odprawiony do: <b>{nextStationName} ({nextArrivalLine})</b></i>", "desc-departed": "Na scenerii / odprawiony do: <b><u>{nextStationName} ({nextArrivalLine})</u></b>",
"desc-departed-ends": "Na scenerii / <i>odprawiony do: <b>{nextStationName}</b></i>", "desc-departed-ends": "Na scenerii / odprawiony do: <b><u>{nextStationName}</u></b>",
"desc-departed-away": "<i>Odprawiony do: <b>{nextStationName} ({nextArrivalLine})</b></i>", "desc-departed-away": "Odprawiony do: <b><u>{nextStationName} ({nextArrivalLine})</u></b>",
"desc-end": "Pociąg kończy bieg", "desc-end": "Pociąg kończy bieg",
"desc-terminated": "Pociąg zakończył bieg" "desc-terminated": "Pociąg zakończył bieg"
}, },
+8 -6
View File
@@ -31,6 +31,7 @@ export const initFilters = {
mechanical: false, mechanical: false,
'SPK-M': false, 'SPK-M': false,
'SCS-M': false, 'SCS-M': false,
'SCS-SPK': false,
modern: false, modern: false,
semaphores: false, semaphores: false,
historical: false, historical: false,
@@ -61,11 +62,12 @@ export const initFilters = {
maxLevel: 20, maxLevel: 20,
minOneWay: 0, minOneWay: 0,
minOneWayCatenary: 0, minOneWayCatenary: 0,
minTwoWayCatenary: 0,
minOneWayInt: 0, minOneWayInt: 0,
minOneWayCatenaryInt: 0, minOneWayCatenaryInt: 0,
minTwoWay: 0,
minTwoWayCatenary: 0,
minTwoWayInt: 0,
minTwoWayCatenaryInt: 0, minTwoWayCatenaryInt: 0,
// minTwoWay: 0,
authors: '' authors: ''
}; };
@@ -76,12 +78,12 @@ export const sliderStates = [
{ id: 'maxLevel', minRange: 0, maxRange: 20, step: 1 }, { id: 'maxLevel', minRange: 0, maxRange: 20, step: 1 },
{ id: 'minOneWay', minRange: 0, maxRange: 5, step: 1 }, { id: 'minOneWay', minRange: 0, maxRange: 5, step: 1 },
{ id: 'minOneWayCatenary', minRange: 0, maxRange: 5, step: 1 }, { id: 'minOneWayCatenary', minRange: 0, maxRange: 5, step: 1 },
{ id: 'minTwoWayCatenary', minRange: 0, maxRange: 5, step: 1 },
{ id: 'minOneWayInt', minRange: 0, maxRange: 5, step: 1 }, { id: 'minOneWayInt', minRange: 0, maxRange: 5, step: 1 },
{ id: 'minOneWayCatenaryInt', minRange: 0, maxRange: 5, step: 1 }, { id: 'minOneWayCatenaryInt', minRange: 0, maxRange: 5, step: 1 },
{ id: 'minTwoWay', minRange: 0, maxRange: 5, step: 1 },
{ id: 'minTwoWayCatenary', minRange: 0, maxRange: 5, step: 1 },
{ id: 'minTwoWayInt', minRange: 0, maxRange: 5, step: 1 },
{ id: 'minTwoWayCatenaryInt', minRange: 0, maxRange: 5, step: 1 }, { id: 'minTwoWayCatenaryInt', minRange: 0, maxRange: 5, step: 1 },
// { id: 'minTwoWay', minRange: 0, maxRange: 5, step: 1 },
// { id: 'minTwoWayInt', minRange: 0, maxRange: 5, step: 1 }
]; ];
export type StationFilter = keyof typeof initFilters; export type StationFilter = keyof typeof initFilters;
@@ -95,7 +97,7 @@ export const filtersSections: Record<StationFilterSection, StationFilter[]> = {
stationType: ['junction', 'nonJunction'], stationType: ['junction', 'nonJunction'],
access: ['nonPublic', 'unavailable', 'abandoned'], access: ['nonPublic', 'unavailable', 'abandoned'],
addons: ['SUP', 'ASDEK', 'noSUP', 'noASDEK'], addons: ['SUP', 'ASDEK', 'noSUP', 'noASDEK'],
control: ['SPK', 'SCS', 'SPE', 'SPK-M', 'SCS-M', 'mechanical', 'SPK-R', 'SCS-R', 'manual'], control: ['SPK', 'SCS', 'SPE', 'SCS-SPK', 'SPK-M', 'SCS-M', 'mechanical', 'SPK-R', 'SCS-R', 'manual'],
blockades: ['SBL', 'PBL'], blockades: ['SBL', 'PBL'],
signals: ['modern', 'semaphores', 'mixed', 'historical'] signals: ['modern', 'semaphores', 'mixed', 'historical']
}; };
+53 -37
View File
@@ -1,45 +1,10 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { Train, TrainStop } from '../typings/common'; import { Train, TrainStop } from '../typings/common';
import { useApiStore } from '../store/apiStore';
export default defineComponent({ export default defineComponent({
data: () => ({ data: () => ({
STATS: { apiStore: useApiStore()
main: [
{
name: 'speed',
unit: 'km/h'
},
{
name: 'length',
unit: 'm'
},
{
name: 'mass',
unit: 't',
multiplier: 0.001
}
],
position: [
{
name: 'scenery',
prop: 'currentStationName'
},
{
name: 'route',
prop: 'connectedTrack'
},
{
name: 'signal',
prop: 'signal'
},
{
name: 'distance',
prop: 'distance',
unit: 'm'
}
]
}
}), }),
methods: { methods: {
@@ -150,6 +115,57 @@ export default defineComponent({
if (distance < 1000) return `${distance}m`; if (distance < 1000) return `${distance}m`;
return `${(distance / 1000).toPrecision(2)}km`; return `${(distance / 1000).toPrecision(2)}km`;
},
getStockSpeedLimit(stockList: string[], trainMass: number) {
let isPassenger = true;
// Check the whole consist speed limit
const vehicleMaxSpeed = stockList.reduce((acc, stockName, i) => {
const [vehicleName, vehicleCargo] = stockName.split(':');
const vehicleData = this.apiStore.vehiclesData?.find((v) => v.name == vehicleName);
if (!vehicleData) return acc;
let vehicleSpeed = vehicleData.group.speed;
if (vehicleData.type == 'wagon-freight') {
isPassenger = false;
if (vehicleCargo !== undefined && vehicleData.group.speedLoaded) {
vehicleSpeed = vehicleData.group.speedLoaded;
}
}
return Math.min(vehicleSpeed, acc);
}, Infinity);
// Check the head vehicle speed limit
const headLocoName = stockList[0];
const headLocoVehicleData = this.apiStore.vehiclesData?.find((v) => v.name == headLocoName);
// Omit speed check for head vehicle if there's no data for it
if (!headLocoName || !headLocoVehicleData || !headLocoVehicleData.group.massSpeeds)
return vehicleMaxSpeed;
const massSpeeds =
headLocoVehicleData.group.massSpeeds[
stockList.length == 1 ? 'none' : isPassenger ? 'passenger' : 'cargo'
];
// Omit speed check if there's no data on mass speeds
if (!massSpeeds) return vehicleMaxSpeed;
// Number type for locomotives alone
if (typeof massSpeeds === 'number') return massSpeeds;
// Record type for passenger or cargo, find the closest range
const massKey = Object.keys(massSpeeds).findLast((massKey) => trainMass >= Number(massKey));
const massMaxSpeed = massKey ? massSpeeds[massKey] : Infinity;
return Math.min(massMaxSpeed, vehicleMaxSpeed);
} }
} }
}); });
+50 -6
View File
@@ -13,6 +13,7 @@ import { useApiStore } from './apiStore';
import { MainStoreState } from './typings'; import { MainStoreState } from './typings';
const checkpointsTrains: Map<string, CheckpointTrain[]> = new Map(); const checkpointsTrains: Map<string, CheckpointTrain[]> = new Map();
const unknownSceneryCheckpoints: Map<string, Set<string>> = new Map();
const sceneriesTrains: Map<string, Train[]> = new Map(); const sceneriesTrains: Map<string, Train[]> = new Map();
export const useMainStore = defineStore('mainStore', { export const useMainStore = defineStore('mainStore', {
@@ -42,6 +43,7 @@ export const useMainStore = defineStore('mainStore', {
checkpointsTrains.clear(); checkpointsTrains.clear();
sceneriesTrains.clear(); sceneriesTrains.clear();
unknownSceneryCheckpoints.clear();
const dateNow = new Date(); const dateNow = new Date();
@@ -133,8 +135,13 @@ export const useMainStore = defineStore('mainStore', {
// Checkpoints trains map // Checkpoints trains map
if (trainObj.timetableData) { if (trainObj.timetableData) {
let currentSceneryIndex = 0;
const timetablePath = trainObj.timetableData.timetablePath; const timetablePath = trainObj.timetableData.timetablePath;
let currentSceneryIndex = 0;
let currentSceneryData: Station | null =
this.stationList.find(
(s) => s.name == timetablePath[currentSceneryIndex].stationName
) ?? null;
trainObj.timetableData.followingStops.forEach((stop, i) => { trainObj.timetableData.followingStops.forEach((stop, i) => {
if (/strong|podg|pe/.test(stop.stopName)) { if (/strong|podg|pe/.test(stop.stopName)) {
@@ -153,16 +160,41 @@ export const useMainStore = defineStore('mainStore', {
timetablePathElement: timetablePath[currentSceneryIndex] timetablePathElement: timetablePath[currentSceneryIndex]
}; };
// Adding missing sceneries checkpoints as a fallback when scenery data is missing (and "generalInfo" is unavailable)
if (!currentSceneryData) {
const sceneryCheckpointsSet = unknownSceneryCheckpoints.get(
checkpointTrain.timetablePathElement.stationName
);
if (!sceneryCheckpointsSet) {
unknownSceneryCheckpoints.set(
checkpointTrain.timetablePathElement.stationName,
new Set([stop.stopNameRAW])
);
} else {
sceneryCheckpointsSet.add(stop.stopNameRAW);
}
}
// Adding trains to their corresponding checkpoints
if (checkpointsTrains.has(stop.stopNameRAW.toLowerCase())) { if (checkpointsTrains.has(stop.stopNameRAW.toLowerCase())) {
checkpointsTrains.set(stop.stopNameRAW.toLowerCase(), [ checkpointsTrains.set(stop.stopNameRAW.toLowerCase(), [
...checkpointsTrains.get(stop.stopNameRAW.toLowerCase())!, ...checkpointsTrains.get(stop.stopNameRAW.toLowerCase())!,
checkpointTrain checkpointTrain
]); ]);
} else checkpointsTrains.set(stop.stopNameRAW.toLowerCase(), [checkpointTrain]); } else {
checkpointsTrains.set(stop.stopNameRAW.toLowerCase(), [checkpointTrain]);
}
} }
if (timetablePath[currentSceneryIndex].departureRouteExt == stop.departureLine) if (timetablePath[currentSceneryIndex].departureRouteExt == stop.departureLine) {
currentSceneryIndex++; currentSceneryIndex++;
currentSceneryData =
this.stationList.find(
(s) => s.name == timetablePath[currentSceneryIndex].stationName
) ?? null;
}
}); });
} }
@@ -222,7 +254,9 @@ export const useMainStore = defineStore('mainStore', {
all: 0, all: 0,
confirmed: 0, confirmed: 0,
unconfirmed: 0 unconfirmed: 0
} },
missingCheckpoints: []
}); });
}); });
@@ -266,7 +300,9 @@ export const useMainStore = defineStore('mainStore', {
all: 0, all: 0,
confirmed: 0, confirmed: 0,
unconfirmed: 0 unconfirmed: 0
} },
missingCheckpoints: []
}); });
return list; return list;
@@ -277,7 +313,7 @@ export const useMainStore = defineStore('mainStore', {
for (let i = 0, n = allActiveSceneries.length; i < n; i++) { for (let i = 0, n = allActiveSceneries.length; i < n; i++) {
const scenery = allActiveSceneries[i]; const scenery = allActiveSceneries[i];
const station = this.stationList.find((s) => s.name === scenery.name); let station = this.stationList.find((s) => s.name === scenery.name);
let checkpointsSet: Set<string> = new Set(); let checkpointsSet: Set<string> = new Set();
@@ -293,6 +329,14 @@ export const useMainStore = defineStore('mainStore', {
scenery.stationTrains = scenery.stationTrains =
sceneriesTrains.get(scenery.name)?.filter((sc) => sc.region == this.region.id) ?? []; sceneriesTrains.get(scenery.name)?.filter((sc) => sc.region == this.region.id) ?? [];
// Missing checkpoints as a fallback for sceneries without generalInfo & checkpoints property
const missingCheckpointsToAdd = unknownSceneryCheckpoints.get(scenery.name);
if (missingCheckpointsToAdd) {
checkpoints.push(...missingCheckpointsToAdd);
scenery.missingCheckpoints.push(...missingCheckpointsToAdd);
}
const uniqueTrainIds: string[] = []; const uniqueTrainIds: string[] = [];
checkpoints.forEach((cp) => { checkpoints.forEach((cp) => {
const scheduledTrains = checkpointsTrains.get(cp.toLowerCase()); const scheduledTrains = checkpointsTrains.get(cp.toLowerCase());
+4 -1
View File
@@ -8,7 +8,8 @@ export const tooltipKeys = [
'VehiclePreviewTooltip', 'VehiclePreviewTooltip',
'SpawnsTooltip', 'SpawnsTooltip',
'UsersTooltip', 'UsersTooltip',
'HtmlTooltip' 'HtmlTooltip',
'TrainInfoTooltip'
] as const; ] as const;
export type TooltipType = (typeof tooltipKeys)[number]; export type TooltipType = (typeof tooltipKeys)[number];
@@ -33,6 +34,7 @@ export const useTooltipStore = defineStore('tooltipStore', {
this.content = ''; this.content = '';
}, },
// Tooltip handler reading attributes of DOM elements
handle(e: MouseEvent) { handle(e: MouseEvent) {
const targetEl = e const targetEl = e
.composedPath() .composedPath()
@@ -44,6 +46,7 @@ export const useTooltipStore = defineStore('tooltipStore', {
return; return;
} }
// Tooltip content is a string but may be parsed to objects / html in corresponding tooltip type components
const tooltipType = targetEl.getAttribute('data-tooltip-type'); const tooltipType = targetEl.getAttribute('data-tooltip-type');
const tooltipContent = targetEl.getAttribute('data-tooltip-content'); const tooltipContent = targetEl.getAttribute('data-tooltip-content');
+6
View File
@@ -225,6 +225,12 @@ ul {
} }
} }
.font {
&--italic {
font-style: italic;
}
}
button, button,
a.a-button { a.a-button {
cursor: pointer; cursor: pointer;
+1 -1
View File
@@ -36,6 +36,6 @@
} }
&.SCS-SPK { &.SCS-SPK {
color: white; color: #aefff8;
} }
} }
+23
View File
@@ -170,6 +170,7 @@ export interface ActiveScenery {
confirmed: number; confirmed: number;
unconfirmed: number; unconfirmed: number;
}; };
missingCheckpoints: string[];
} }
export interface ScenerySpawn { export interface ScenerySpawn {
@@ -253,3 +254,25 @@ export interface VehicleCargo {
id: string; id: string;
weight: number; weight: number;
} }
export interface TooltipUserTrain {
driverName: string;
trainNo: number;
}
export interface TooltipTrainInfo {
mass: number;
length: number;
speed: number;
signal: string;
distance: number;
connectedTrack: string;
trainNo: number;
driverName: string;
driverLevel: number;
currentStationName: string;
currentStationHash: string;
headVehicleName: string;
stockCount: number;
trainTimetableCategory?: string;
}
+2 -2
View File
@@ -1,8 +1,8 @@
{ {
"compilerOptions": { "compilerOptions": {
"composite": true, "composite": true,
"module": "ESNext", "module": "nodenext",
"moduleResolution": "Node", "moduleResolution": "nodenext",
"allowSyntheticDefaultImports": true "allowSyntheticDefaultImports": true
}, },
"include": ["vite.config.ts"] "include": ["vite.config.ts"]
+2 -4
View File
@@ -21,11 +21,9 @@ export default defineConfig({
vue(), vue(),
VitePWA({ VitePWA({
registerType: 'autoUpdate', registerType: 'autoUpdate',
includeAssets: ['/images/*.{png,svg,jpg}', '/fonts/*.{woff,woff2}'],
workbox: { workbox: {
disableDevLogs: true, disableDevLogs: true,
globPatterns: ['**/*.{js,css,html,png,svg,jpg}'], globPatterns: ['**/*.{js,css,html,png,svg,jpg,ico,woff,woff2,ttf}'],
cleanupOutdatedCaches: true, cleanupOutdatedCaches: true,
runtimeCaching: [ runtimeCaching: [
{ {
@@ -36,7 +34,7 @@ export default defineConfig({
cacheName: 'stacjownik-api-cache', cacheName: 'stacjownik-api-cache',
cacheableResponse: { statuses: [0, 200] } cacheableResponse: { statuses: [0, 200] }
} }
} },
] ]
}, },
devOptions: { enabled: true, suppressWarnings: true } devOptions: { enabled: true, suppressWarnings: true }
+1399 -1245
View File
File diff suppressed because it is too large Load Diff