Compare commits

...

36 Commits

Author SHA1 Message Date
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
Spythere af6eb35b67 Merge pull request #134 from Spythere/development
v1.30.3
2025-07-05 02:20:55 +02:00
Spythere 1e6ab1c2d1 fix: status messages 2025-07-04 23:37:59 +02:00
Spythere fd4849bd5e bump: v1.30.3 2025-07-04 18:29:00 +02:00
Spythere bc0f4c5d3f chore: added head unit information in scenery active timetables 2025-07-04 18:28:41 +02:00
Spythere 8909a0cd40 fix: added timetable status for beginning offline 2025-07-04 18:22:41 +02:00
Spythere a2602aeefe chore: added displaying exit route speed limits in scenery view 2025-07-04 18:13:47 +02:00
Spythere 37ad9b2787 refactor: left & right track speed limits for routes in active train's timetable 2025-07-04 18:09:18 +02:00
Spythere 0b4ad679b3 chore: adjusted scenery view return button appearance 2025-07-04 17:16:08 +02:00
Spythere dd0d7897cf chore: better descriptions for active timetables statuses in scenery view 2025-07-04 17:12:38 +02:00
Spythere 1453dbda01 chore(scenery): added TWR/TN/PN badges to active timetables 2025-07-02 19:05:38 +02:00
Spythere 4af856b833 chore(scenery): changed appearance of the return button 2025-07-02 18:50:11 +02:00
Spythere 182b46a377 Merge pull request #133 from Spythere/development
hotfixes: v1.30.2
2025-06-02 01:39:13 +02:00
Spythere bb5fc395d2 chore: added LPS category to journal timetables filters 2025-06-02 01:38:01 +02:00
Spythere a91a00f88a refactor: scenery view back button routing; component setup script 2025-06-02 01:35:09 +02:00
Spythere c8d481a952 hotfix: EN category desc typo 2025-06-02 00:44:23 +02:00
Spythere 03ff4d8648 Merge pull request #132 from Spythere/development
v1.30.2
2025-05-27 17:47:28 +02:00
Spythere 23767801d5 chore: added timeout for welcome card appearance 2025-05-27 17:42:31 +02:00
Spythere 310261fb59 chore: queries handling 2025-05-27 17:38:53 +02:00
Spythere 742754ceef bump: v1.30.2 2025-05-27 17:19:48 +02:00
Spythere b9bb9dc201 refactor: globalized current locale, translation improvements 2025-05-27 17:19:32 +02:00
Spythere 611927f96f feat: added welcome card for new incoming users 2025-05-27 16:55:58 +02:00
Spythere 2e191f355e Merge pull request #131 from Spythere/development
fix: english typo
2025-05-24 14:42:22 +02:00
Spythere f974643e37 fix: english typo 2025-05-14 13:59:07 +02:00
Spythere 02afe2bf33 Merge pull request #130 from Spythere/development
hotfix: twr detection
2025-05-12 21:23:49 +02:00
Spythere ebdffc6241 hotfix: twr detection 2025-05-12 21:18:50 +02:00
30 changed files with 876 additions and 375 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "stacjownik", "name": "stacjownik",
"version": "1.30.1", "version": "1.30.4",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
+30 -4
View File
@@ -5,9 +5,11 @@
@toggle-card="() => (isUpdateCardOpen = false)" @toggle-card="() => (isUpdateCardOpen = false)"
/> />
<AppWelcomeCard :is-card-open="isWelcomeCardOpen" @toggle-card="closeWelcomeCard" />
<Tooltip /> <Tooltip />
<AppHeader :current-lang="currentLang" @change-lang="changeLang" /> <AppHeader :current-lang="store.currentLocale" @change-lang="changeLang" />
<main class="app_main"> <main class="app_main">
<router-view v-slot="{ Component }"> <router-view v-slot="{ Component }">
@@ -44,8 +46,10 @@ import UpdateCard from './components/App/UpdateCard.vue';
import StorageManager from './managers/storageManager'; import StorageManager from './managers/storageManager';
import AppFooter from './components/App/AppFooter.vue'; import AppFooter from './components/App/AppFooter.vue';
import AppWelcomeCard from './components/App/AppWelcomeCard.vue';
const STORAGE_VERSION_KEY = 'app_version'; const STORAGE_VERSION_KEY = 'app_version';
const WELCOME_CARD_SEEN_KEY = 'welcome_card_seen';
export default defineComponent({ export default defineComponent({
components: { components: {
@@ -54,6 +58,7 @@ export default defineComponent({
AppHeader, AppHeader,
AppFooter, AppFooter,
UpdateCard, UpdateCard,
AppWelcomeCard,
Tooltip Tooltip
}, },
@@ -64,8 +69,8 @@ export default defineComponent({
tooltipStore: useTooltipStore(), tooltipStore: useTooltipStore(),
isUpdateCardOpen: false, isUpdateCardOpen: false,
isWelcomeCardOpen: false,
currentLang: 'pl',
isOnProductionHost: location.hostname == 'stacjownik-td2.web.app' isOnProductionHost: location.hostname == 'stacjownik-td2.web.app'
}), }),
@@ -85,13 +90,29 @@ export default defineComponent({
this.loadLang(); this.loadLang();
this.setupOfflineHandling(); this.setupOfflineHandling();
this.checkAppVersion(); this.checkAppVersion();
this.handleQueries();
this.apiStore.setupAPIData(); this.apiStore.setupAPIData();
}, },
handleQueries() {
const query = new URLSearchParams(window.location.search);
if (query.get('welcomeCard') == '1') {
this.isWelcomeCardOpen = true;
}
},
async checkAppVersion() { async checkAppVersion() {
const isWelcomeCardSeen = StorageManager.getBooleanValue(WELCOME_CARD_SEEN_KEY);
const storageVersion = StorageManager.getStringValue(STORAGE_VERSION_KEY); const storageVersion = StorageManager.getStringValue(STORAGE_VERSION_KEY);
if (isWelcomeCardSeen == false && storageVersion == '') {
setTimeout(() => {
this.isWelcomeCardOpen = true;
}, 1500);
}
try { try {
const releaseData = await ( const releaseData = await (
await axios.get('https://api.github.com/repos/Spythere/stacjownik/releases/latest') await axios.get('https://api.github.com/repos/Spythere/stacjownik/releases/latest')
@@ -140,7 +161,7 @@ export default defineComponent({
changeLang(lang: string) { changeLang(lang: string) {
this.$i18n.locale = lang; this.$i18n.locale = lang;
this.currentLang = lang; this.store.currentLocale = lang;
StorageManager.setStringValue('lang', lang); StorageManager.setStringValue('lang', lang);
}, },
@@ -157,10 +178,15 @@ export default defineComponent({
const naviLanguage = window.navigator.language.toString(); const naviLanguage = window.navigator.language.toString();
if (naviLanguage.startsWith('en')) { if (!naviLanguage.startsWith('pl')) {
this.changeLang('en'); this.changeLang('en');
return; return;
} }
},
closeWelcomeCard() {
this.isWelcomeCardOpen = false;
StorageManager.setBooleanValue(WELCOME_CARD_SEEN_KEY, true);
} }
} }
}); });
+3 -1
View File
@@ -9,7 +9,9 @@
<br /> <br />
<a href="https://discord.gg/x2mpNN3svk"> <a href="https://discord.gg/x2mpNN3svk">
<img src="/images/icon-discord.png" alt="" />&nbsp;<b>{{ $t('footer.discord') }}</b> <img src="/images/icon-discord.png" alt="discord logo icon" />&nbsp;<b class="text--discord">
{{ $t('footer.discord') }}
</b>
</a> </a>
<div style="display: none">&int; ukryta taktyczna całka do programowania w HTMLu</div> <div style="display: none">&int; ukryta taktyczna całka do programowania w HTMLu</div>
+246
View File
@@ -0,0 +1,246 @@
<template>
<Card :is-open="props.isCardOpen">
<div class="body-content">
<h1>{{ $t('welcome.title') }}</h1>
<div class="language-select">
<button :data-active="$i18n.locale == 'pl'" @click="changeLang('pl')">
<img src="/images/icon-pl.svg" alt="" width="45" />
</button>
<button :data-active="$i18n.locale == 'en'" @click="changeLang('en')">
<img src="/images/icon-en.jpg" alt="" width="45" />
</button>
</div>
<section class="app-description">
<i18n-t keypath="welcome.app-desc" tag="p">
<template v-slot:b1>
<b>{{ $t('welcome.app-desc-b1') }}</b>
</template>
<template v-slot:link>
<a href="https://td2.info.pl/" class="link" target="_blank">Train Driver 2</a>
</template>
</i18n-t>
</section>
<section class="tabs">
<div class="tab-description">
<h2 class="text--primary">{{ $t('welcome.sceneries-header') }}</h2>
<hr />
<i18n-t keypath="welcome.sceneries-desc" tag="p">
<template v-slot:b1>
<b>{{ $t('welcome.sceneries-desc-b1') }}</b>
</template>
</i18n-t>
</div>
<div class="tab-description">
<h2 class="text--primary">{{ $t('welcome.trains-header') }}</h2>
<hr />
<i18n-t keypath="welcome.trains-desc" tag="p">
<template v-slot:b1>
<b>{{ $t('welcome.trains-desc-b1') }}</b>
</template>
</i18n-t>
</div>
<div class="tab-description">
<h2 class="text--primary">{{ $t('welcome.journal-header') }}</h2>
<hr />
<i18n-t keypath="welcome.journal-desc" tag="p">
<template v-slot:b1>
<b>{{ $t('welcome.journal-desc-b1') }}</b>
</template>
</i18n-t>
</div>
</section>
<section class="other-apps">
<b class="text--primary">
{{ $t('welcome.other-apps') }}
</b>
<div class="apps-grid">
<a class="app-item" href="https://pojazdownik-td2.web.app/" target="_blank">
<img src="/images/icon-pojazdownik.svg" alt="pojazdownik app logo" />
<h3 class="text--primary">Pojazdownik</h3>
<p>{{ $t('welcome.pojazdownik-desc') }}</p>
</a>
<a class="app-item" href="https://generator-td2.web.app/" target="_blank">
<img src="/images/icon-gnr.svg" alt="generator app logo" />
<h3 class="text--primary">GeneraTOR</h3>
<p>{{ $t('welcome.generator-desc') }}</p>
</a>
<a class="app-item" href="https://srjp-td2.web.app/" target="_blank">
<img src="/images/icon-srjp.svg" alt="srjp app logo" />
<h3 class="text--primary">Rozkładownik</h3>
<p>{{ $t('welcome.srjp-desc') }}</p>
</a>
</div>
</section>
<section class="bottom-info">
<i18n-t keypath="welcome.donation-info" tag="div" class="donation-info">
<template v-slot:icon1>
<img src="/images/icon-diamond.svg" alt="diamond icon" width="25" />
<span class="text--donator">&nbsp;{{ $t('welcome.donation-info-icon1-text') }}</span>
</template>
</i18n-t>
<i18n-t keypath="welcome.discord-info" tag="div" class="discord-info">
<template v-slot:discord>
<a href="https://discord.gg/x2mpNN3svk" class="link" target="_blank">
<b class="text--discord">{{ $t('welcome.discord-info-link-text') }}</b>
</a>
</template>
</i18n-t>
<div class="bottom-text">
<i>{{ $t('welcome.bottom-text') }}</i>
</div>
<div class="bottom-actions">
<button class="btn btn--action" @click="toggleCard(false)">
{{ $t('welcome.button-confirm') }}
</button>
</div>
</section>
</div>
</Card>
</template>
<script setup lang="ts">
import { useI18n } from 'vue-i18n';
import Card from '../Global/Card.vue';
import { useMainStore } from '../../store/mainStore';
import StorageManager from '../../managers/storageManager';
const i18n = useI18n();
const store = useMainStore();
const emit = defineEmits(['toggleCard']);
const props = defineProps({
isCardOpen: Boolean
});
function toggleCard(state: boolean) {
emit('toggleCard', state);
}
function changeLang(localeName: string) {
i18n.locale.value = localeName;
store.currentLocale = localeName;
StorageManager.setStringValue('lang', localeName);
}
</script>
<style lang="scss" scoped>
.body-content {
max-width: 800px;
min-height: 900px;
padding: 1em 0.5em;
text-align: center;
font-size: 1.1em;
}
hr {
margin-bottom: 0.5em;
}
a.link {
text-decoration: underline;
img {
vertical-align: middle;
margin-right: 0.2em;
}
}
.language-select {
display: flex;
justify-content: center;
margin: 0.5em 0;
button[data-active='false'] img {
opacity: 0.5;
}
}
.app-description {
margin: 1em 0;
}
.tab-description {
margin-top: 0.5em;
}
.other-apps {
font-weight: bold;
margin: 1em 0;
font-size: 1.1em;
}
.apps-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1em;
padding: 1em;
}
.apps-grid > a.app-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5em;
padding: 1em;
background-color: #2b2b2b;
transition: background-color 100ms ease-in-out;
border-radius: 0.5em;
&:hover {
background-color: #3b3b3b;
}
img {
width: 2.5em;
}
}
.donation-info {
font-weight: bold;
font-size: 1.1em;
img {
vertical-align: middle;
}
}
.discord-info {
margin-top: 1em;
font-weight: bold;
img {
vertical-align: middle;
}
}
.bottom-text {
margin: 1em 0;
font-weight: bold;
font-size: 1.2em;
}
.bottom-actions {
display: flex;
justify-content: center;
margin-top: 1em;
font-size: 1.25em;
}
</style>
+55 -16
View File
@@ -1,5 +1,13 @@
<template> <template>
<section class="info-header"> <section class="info-header">
<button
class="btn btn-return"
:title="$t('scenery.return-btn')"
@click="onReturnButtonClick"
>
<img src="/images/icon-back.svg" alt="return button" />
</button>
<a class="scenery-name" :href="station?.generalInfo?.url" target="_blank"> <a class="scenery-name" :href="station?.generalInfo?.url" target="_blank">
{{ stationName.replace(/_/g, ' ') }} {{ stationName.replace(/_/g, ' ') }}
</a> </a>
@@ -12,39 +20,64 @@
</section> </section>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { PropType, defineComponent } from 'vue'; import { onMounted, PropType, ref } from 'vue';
import { ActiveScenery, Station } from '../../typings/common'; import { ActiveScenery, Station } from '../../typings/common';
import { useRoute, useRouter } from 'vue-router';
export default defineComponent({ const route = useRoute();
props: { const router = useRouter();
station: {
type: Object as PropType<Station>
},
stationName: { const prevPath = ref('/');
type: String,
required: true
},
onlineScenery: { onMounted(() => {
type: Object as PropType<ActiveScenery> prevPath.value = (route.meta['prevPath'] as string) ?? '/';
} });
defineProps({
station: {
type: Object as PropType<Station>
},
stationName: {
type: String,
required: true
},
onlineScenery: {
type: Object as PropType<ActiveScenery>
} }
}); });
function onReturnButtonClick() {
router.push(prevPath.value);
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@use '../../styles/responsive'; @use '../../styles/responsive';
@use 'sass:color';
.info-header { .btn-return {
margin-top: 1em; $bgColor: #2b2b2b;
background-color: $bgColor;
margin-bottom: 0.5em;
img {
width: 2em;
}
&:hover {
background-color: color.adjust($color: $bgColor, $lightness: 15%);
}
} }
.scenery-name { .scenery-name {
font-weight: bold; font-weight: bold;
font-size: 3em; font-size: 3em;
text-align: center;
text-transform: uppercase; text-transform: uppercase;
} }
@@ -58,4 +91,10 @@ export default defineComponent({
color: #aaa; color: #aaa;
font-size: 1.2em; font-size: 1.2em;
} }
@include responsive.smallScreen {
.scenery-name {
font-size: 2.5em;
}
}
</style> </style>
@@ -44,6 +44,7 @@
{{ route.routeName }} {{ route.routeName }}
</span> </span>
<span v-if="route.routeSpeed" class="speed">{{ route.routeSpeed }}</span> <span v-if="route.routeSpeed" class="speed">{{ route.routeSpeed }}</span>
<span v-if="route.routeSpeedExit" class="speed">| {{ route.routeSpeedExit }}</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>
@@ -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 }}
+73 -21
View File
@@ -93,19 +93,59 @@
<span class="timetable-general"> <span class="timetable-general">
<span class="general-info"> <span class="general-info">
<div class="info-train"> <div class="info-train">
<b <!-- Cargo warnings & details badges -->
<span
class="train-badge twr"
v-if="row.train.timetableData!.twr"
data-tooltip-type="BaseTooltip" data-tooltip-type="BaseTooltip"
:data-tooltip-content="getCategoryExplanation(row.train.timetableData!.category)" :data-tooltip-content="$t('warnings.TWR')"
class="text--primary tooltip-help"
> >
{{ row.train.timetableData!.category }} TWR
</b> </span>
<span>&nbsp;</span>
<b>{{ row.train.trainNo }}</b> <span
<span>&nbsp;&bull;&nbsp;</span> class="train-badge tn"
<span>{{ row.train.driverName }}</span> v-if="row.train.timetableData!.hasDangerousCargo"
data-tooltip-type="BaseTooltip"
:data-tooltip-content="$t('warnings.TN')"
>
TN
</span>
<span
class="train-badge pn"
v-if="row.train.timetableData!.hasExtraDeliveries"
data-tooltip-type="BaseTooltip"
:data-tooltip-content="$t('warnings.PN')"
>
PN
</span>
<!-- Train info -->
<span
data-tooltip-type="TrainInfoTooltip"
:data-tooltip-content="row.train.id"
class="tooltip-help"
>
<b class="text--primary">
{{ row.train.timetableData!.category }}
</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>
<!-- Train stop comments -->
<span <span
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"
> >
@@ -205,7 +245,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({
@@ -352,6 +392,7 @@ export default defineComponent({
<style lang="scss" scoped> <style lang="scss" scoped>
@use '../../styles/responsive'; @use '../../styles/responsive';
@use '../../styles/animations'; @use '../../styles/animations';
@use '../../styles/badge';
.scenery-timetable { .scenery-timetable {
height: 100%; height: 100%;
@@ -468,21 +509,32 @@ export default defineComponent({
.general-info { .general-info {
display: flex; display: flex;
flex-direction: column;
flex-wrap: wrap; flex-wrap: wrap;
}
.info-number { .info-train {
color: var(--clr-primary); display: flex;
} flex-wrap: wrap;
gap: 0.25em;
}
.info-route { .info-train > .train-badge {
width: 100%; font-size: 0.85em;
} }
img { .info-number {
height: 0.9em; color: var(--clr-primary);
vertical-align: middle; }
margin: 0 0.25em;
} .info-route {
width: 100%;
margin-top: 0.25em;
}
.stop-comments-icon > img {
width: 1.3em;
vertical-align: top;
} }
.schedule { .schedule {
@@ -1,13 +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"
data-tooltip-type="HtmlTooltip" v-html="computedScheduledTrain.stopStatusIndicator"
:data-tooltip-content="computedScheduledTrain.stopStatusDescription"
@click.prevent="() => {}"
> >
{{ computedScheduledTrain.stopStatusIndicator }} </router-link>
</span>
<span
v-else
:class="computedScheduledTrain.status"
v-html="computedScheduledTrain.stopStatusIndicator"
></span>
</div> </div>
</template> </template>
@@ -28,66 +33,65 @@ export default defineComponent({
computedScheduledTrain() { computedScheduledTrain() {
const { status, prevElement, currentElement, nextElement } = this.sceneryTimetableRow; const { status, prevElement, currentElement, nextElement } = this.sceneryTimetableRow;
const prevDepartureIndicator = prevElement?.departureRouteExt let stopStatusIndicator = '';
? `(${prevElement.departureRouteExt}) ${prevElement.stationName}` let stationNameHref = '';
: '---';
const nextArrivalIndicator = nextElement?.arrivalRouteExt
? `(${nextElement.arrivalRouteExt}) ${nextElement.stationName}`
: `${currentElement.stationName}`;
let stopStatusDescription = '',
stopStatusIndicator = '';
switch (status) { switch (status) {
case StopStatus.ARRIVING: case StopStatus.ARRIVING:
stopStatusIndicator = `${this.$t('timetables.from')}: ${prevDepartureIndicator}`; if (prevElement) {
stopStatusDescription = this.$t('timetables.desc-arriving', { stopStatusIndicator = this.$t('timetables.desc-arriving', {
prevStationName: prevElement?.stationName ?? '', prevStationName: prevElement?.stationName ?? '',
prevDepartureLine: prevElement?.departureRouteExt ?? '' prevDepartureLine: prevElement?.departureRouteExt ?? ''
}); });
stationNameHref = prevElement?.stationName ?? '';
} else {
stopStatusIndicator = this.$t('timetables.desc-beginning');
}
break; break;
case StopStatus.ONLINE: case StopStatus.ONLINE:
case StopStatus.STOPPED: case StopStatus.STOPPED:
stopStatusIndicator = nextElement?.arrivalRouteExt stopStatusIndicator = nextElement?.arrivalRouteExt
? `${this.$t('timetables.to')}: ${nextArrivalIndicator}`
: `${this.$t('timetables.desc-end')}`;
stopStatusDescription = nextElement?.arrivalRouteExt
? this.$t(`timetables.desc-${status}`, { ? this.$t(`timetables.desc-${status}`, {
nextStationName: nextElement?.stationName, nextStationName: nextElement?.stationName,
nextArrivalLine: nextElement?.arrivalRouteExt nextArrivalLine: nextElement?.arrivalRouteExt
}) })
: ''; : this.$t(`timetables.desc-end`);
stationNameHref = nextElement?.stationName ?? '';
break; break;
case StopStatus.DEPARTED: case StopStatus.DEPARTED:
stopStatusIndicator = `${this.$t('timetables.to')}: ${nextArrivalIndicator}`;
if (!nextElement?.stationName) { if (!nextElement?.stationName) {
stopStatusDescription = this.$t('timetables.desc-departed-ends', { stopStatusIndicator = this.$t('timetables.desc-departed-ends', {
nextStationName: currentElement.stationName nextStationName: currentElement.stationName
}); });
stationNameHref = nextElement?.stationName ?? '';
} else { } else {
stopStatusDescription = 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;
case StopStatus.DEPARTED_AWAY: case StopStatus.DEPARTED_AWAY:
stopStatusIndicator = `${this.$t('timetables.to')}: ${nextArrivalIndicator}`; stopStatusIndicator = this.$t('timetables.desc-departed-away', {
stopStatusDescription = this.$t('timetables.desc-departed-away', {
nextStationName: nextElement?.stationName, nextStationName: nextElement?.stationName,
nextArrivalLine: nextElement?.arrivalRouteExt nextArrivalLine: nextElement?.arrivalRouteExt
}); });
stationNameHref = nextElement?.stationName ?? '';
break; break;
case StopStatus.TERMINATED: case StopStatus.TERMINATED:
stopStatusIndicator = `X ${this.$t('timetables.desc-terminated')}`; stopStatusIndicator = this.$t('timetables.desc-terminated');
stopStatusDescription = this.$t('timetables.desc-terminated');
break; break;
default: default:
@@ -95,10 +99,18 @@ export default defineComponent({
} }
return { return {
...this.sceneryTimetableRow, ...this.sceneryTimetableRow,
stopStatusDescription, stationNameHref,
stopStatusIndicator stopStatusIndicator
}; };
} }
},
methods: {
navigateToScenery(sceneryName?: string) {
if (!sceneryName) return;
this.$router.push(`/scenery?station=${sceneryName}`);
}
} }
}); });
</script> </script>
@@ -106,34 +118,29 @@ export default defineComponent({
<style lang="scss" scoped> <style lang="scss" scoped>
.general-status { .general-status {
margin-top: 0.5em; margin-top: 0.5em;
cursor: help;
span.arriving { & > .arriving {
color: #ccc; color: #ccc;
} }
span.departed { & > .departed {
color: lime; color: lime;
font-weight: bold;
&-away { &-away {
font-weight: bold;
color: #5ecc5e; color: #5ecc5e;
} }
} }
span.stopped { & > .stopped {
color: #ffa600; color: #ffa600;
font-weight: bold;
} }
span.online { & > .online {
color: gold; color: gold;
} }
span.terminated { & > .terminated {
color: salmon; color: salmon;
font-weight: bold;
} }
} }
</style> </style>
+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);
} }
} }
}); });
+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,69 @@
<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
</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);
} }
} }
+6 -3
View File
@@ -9,7 +9,7 @@
<span <span
class="train-badge twr" class="train-badge twr"
v-if="train.timetableData?.TWR" v-if="train.timetableData?.twr"
data-tooltip-type="BaseTooltip" data-tooltip-type="BaseTooltip"
:data-tooltip-content="$t('warnings.TWR')" :data-tooltip-content="$t('warnings.TWR')"
> >
@@ -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>
@@ -156,7 +159,7 @@
v-if="extended && train.timetableData && train.timetableData.warningNotes" v-if="extended && train.timetableData && train.timetableData.warningNotes"
> >
<div class="dangers-badges"> <div class="dangers-badges">
<div v-if="train.timetableData?.TWR"> <div v-if="train.timetableData?.twr">
<div class="train-badge twr">TWR</div> <div class="train-badge twr">TWR</div>
- {{ $t('warnings.TWR') }} - {{ $t('warnings.TWR') }}
</div> </div>
+11 -8
View File
@@ -30,17 +30,20 @@
</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"
/>
<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" />
+31 -14
View File
@@ -57,7 +57,14 @@
<span>{{ stop.departureLine }}</span> <span>{{ stop.departureLine }}</span>
<span v-if="stop.departureLineInfo"> <span v-if="stop.departureLineInfo">
<span> | {{ stop.departureLineInfo.routeSpeed }}</span> <span>
|
{{
stop.departureLineInfo.routeSpeedExit
? `${stop.departureLineInfo.routeSpeedExit} (${stop.departureLineInfo.routeSpeed})`
: stop.departureLineInfo.routeSpeed
}}</span
>
<img <img
:src=" :src="
@@ -85,13 +92,13 @@
</div> </div>
<div <div
v-if="stop.sceneryName != scheduleStops[i + 1]?.sceneryName" v-if="stop.nextPointRef && stop.sceneryName != stop.nextPointRef.sceneryName"
class="scenery-change-name" class="scenery-change-name"
> >
<span>{{ scheduleStops[i + 1].sceneryName }}</span> <span>{{ stop.nextPointRef.sceneryName }}</span>
<i <i
v-if="!scheduleStops[i + 1].isSceneryOnline" v-if="!stop.nextPointRef.isSceneryOnline"
class="fa-solid fa-ban fa-sm" class="fa-solid fa-ban fa-sm"
data-tooltip-type="BaseTooltip" data-tooltip-type="BaseTooltip"
:data-tooltip-content="$t('app.tooltip-scenery-offline')" :data-tooltip-content="$t('app.tooltip-scenery-offline')"
@@ -101,30 +108,33 @@
<div <div
class="scenery-route" class="scenery-route"
v-if="stop.sceneryName != scheduleStops[i + 1]?.sceneryName" v-if="stop.nextPointRef && stop.sceneryName != stop.nextPointRef.sceneryName"
> >
<span> {{ scheduleStops[i + 1].arrivalLine }}</span> <span> {{ stop.nextPointRef.arrivalLine }}</span>
<span v-if="scheduleStops[i + 1].arrivalLineInfo"> <span v-if="stop.nextPointRef.arrivalLineInfo">
<span> | {{ scheduleStops[i + 1].arrivalLineInfo!.routeSpeed }} </span> <span> | {{ stop.nextPointRef.arrivalLineInfo!.routeSpeed }}</span>
<span v-if="stop.nextPointRef.arrivalLineInfo!.routeSpeedExit"
>({{ stop.nextPointRef.arrivalLineInfo!.routeSpeedExit }})</span
>
<img <img
:src=" :src="
scheduleStops[i + 1].arrivalLineInfo?.isElectric stop.nextPointRef.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.${!stop.nextPointRef.arrivalLineInfo?.isElectric ? 'no-' : ''}catenary-tooltip`
) )
" "
width="14" width="14"
/> />
<img <img
v-if="scheduleStops[i + 1].arrivalLineInfo!.isRouteSBL" v-if="stop.nextPointRef.arrivalLineInfo!.isRouteSBL"
src="/images/icon-sbl-transparent.svg" src="/images/icon-sbl-transparent.svg"
width="14" width="14"
data-tooltip-type="BaseTooltip" data-tooltip-type="BaseTooltip"
@@ -228,7 +238,7 @@ export default defineComponent({
departureLineInfo = pathData.departureLineData; departureLineInfo = pathData.departureLineData;
} }
for (const stop of followingStops) { followingStops.forEach((stop, i) => {
let isExternal = false; let isExternal = false;
if (stop.arrivalLine === currentPath.arrivalRouteExt) { if (stop.arrivalLine === currentPath.arrivalRouteExt) {
@@ -287,7 +297,9 @@ export default defineComponent({
status: stop.confirmed ? 'confirmed' : stop.stopped ? 'stopped' : 'unconfirmed', status: stop.confirmed ? 'confirmed' : stop.stopped ? 'stopped' : 'unconfirmed',
sceneryName: currentPath.stationName, sceneryName: currentPath.stationName,
isSceneryOnline: pathData?.isOnline ?? false isSceneryOnline: pathData?.isOnline ?? false,
nextPointRef: null
}; };
if (internalRouteInfo) { if (internalRouteInfo) {
@@ -309,6 +321,11 @@ export default defineComponent({
stopRows.push(rowData); stopRows.push(rowData);
// Assign this row data object to the last one as reference
if (i != 0) {
stopRows[i - 1].nextPointRef = rowData;
}
if (stop.departureLine === currentPath.departureRouteExt) { if (stop.departureLine === currentPath.departureRouteExt) {
// Reverse search for last scenery checkpoint // Reverse search for last scenery checkpoint
if (pathData?.departureLineData) { if (pathData?.departureLineData) {
@@ -328,7 +345,7 @@ export default defineComponent({
currentPath = timetablePath[++currentPathIndex]; currentPath = timetablePath[++currentPathIndex];
pathData = this.getPathSceneryData(currentPath); pathData = this.getPathSceneryData(currentPath);
} }
} });
return stopRows; return stopRows;
}, },
+2
View File
@@ -196,4 +196,6 @@ export interface TrainSchedulePoint {
isSBL: boolean; isSBL: boolean;
sceneryName: string | null; sceneryName: string | null;
isSceneryOnline: boolean; isSceneryOnline: boolean;
nextPointRef: TrainSchedulePoint | null;
} }
+1 -1
View File
@@ -22,7 +22,7 @@
"TRE", "TRS", "TRE", "TRS",
"TSE", "TSS", "TSE", "TSS",
"THE", "THS", "THE", "THS",
"LPE", "LPE", "LPS",
"LTE", "LTS", "LTE", "LTS",
"LSS", "LSS",
"LZE", "LZS", "LZE", "LZS",
+48 -21
View File
@@ -1,4 +1,28 @@
{ {
"welcome": {
"title": "Welcome to Stacjownik!",
"app-desc": "{b1} is a web tool made for {link}, which main goal is to assist in-game dispatchers and drivers on their duties. Here you can find who is currently online and on what scenery, find a train driver or browse the journal, which contains a history of past timetables and dispatcher duty.",
"app-desc-b1": "Stacjownik",
"sceneries-header": "Sceneries",
"sceneries-desc": "Under the {b1} tab, you will find information about the dispatchers and sceneries they currently occupy. You can also browse all available sceneries in the simulator (in the filters, check the “Free” option to show the rest of the unoccupied ones) and filter them by various aspects, such as control types, additional software, signaling types, required duty level or types of routes. Click on the corresponding scenery in the table to show its details.",
"sceneries-desc-b1": "Sceneries",
"trains-header": "Trains",
"trains-desc": "The {b1} tab contains a list of active drivers and timetables that are currently realized on the selected server (server selection is at the top of the page, next to the counters). The list can be filtered and sorted, taking into account the most important criteria, such as driver name, train number or timetable details. You can also click on the train to show additional information.",
"trains-desc-b1": "Trains",
"journal-header": "Journal",
"journal-desc": "The {b1} is a tab where you can find dispatcher duty and timetables history (currently only from the main PL1 game server). You can also search for a particular player's history using additional filters.",
"journal-desc-b1": "Journal",
"other-apps": "Also check out other apps designed to make TD2 gameplay easier:",
"pojazdownik-desc": "online rolling stock editor",
"generator-desc": "graphical manager of train orders",
"srjp-desc": "Polish working train timetable",
"donation-info": "If you appreciate the Stacjownik project as well as other applications, please consider supporting it financially - click the button with {icon1} to learn more!",
"donation-info-icon1-text": "the diamond icon",
"discord-info": "I also invite you to the official {discord}, where you will find a dedicated Stacjobot, with which you can search for additional data from the simulator, unavailable on this site!",
"discord-info-link-text": "Stacjownik Discord server",
"bottom-text": "Enjoy!\n~Spythere",
"button-confirm": "Start using the app!"
},
"donations": { "donations": {
"button-title": "TOSS A COIN", "button-title": "TOSS A COIN",
"header": "Toss a coin to Stacjownik!", "header": "Toss a coin to Stacjownik!",
@@ -60,7 +84,7 @@
"categories": { "categories": {
"EI": "domestic express", "EI": "domestic express",
"EC": "international express", "EC": "international express",
"EN": "domestic night express", "EN": "international night express",
"MP": "intervoivodeship bullet", "MP": "intervoivodeship bullet",
"MO": "intervoivodeship regio", "MO": "intervoivodeship regio",
"MM": "international bullet", "MM": "international bullet",
@@ -313,18 +337,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 game package",
"non-public": "This scenery is not public", "nonDefault": "Sceneria available to download from 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: ",
@@ -519,7 +545,7 @@
"no-users": "NO ACTIVE PLAYERS", "no-users": "NO ACTIVE PLAYERS",
"no-spawns": "NO OPEN SPAWNS", "no-spawns": "NO OPEN SPAWNS",
"no-scenery": "Oops! This scenery doesn't exist!", "no-scenery": "Oops! This scenery doesn't exist!",
"return-btn": "Return", "return-btn": "BACK TO THE LAST SITE",
"history-btn": "View the dispatcher history", "history-btn": "View the dispatcher history",
"info-btn": "Return to the scenery view", "info-btn": "Return to the scenery view",
"authors-title": "Scenery author | Scenery authors", "authors-title": "Scenery author | Scenery authors",
@@ -527,7 +553,7 @@
"lines-title": "Real lines", "lines-title": "Real lines",
"project-title": "Project name", "project-title": "Project name",
"additional-tools-title": "Additional tools", "additional-tools-title": "Additional tools",
"one-way-routes": "Signle track routes", "one-way-routes": "Single track routes",
"two-way-routes": "Double track routes", "two-way-routes": "Double track routes",
"no-data": "No available data about this scenery", "no-data": "No available data about this scenery",
"option-active-timetables": "Active timetables", "option-active-timetables": "Active timetables",
@@ -565,15 +591,16 @@
"terminated": "Timetable terminated", "terminated": "Timetable terminated",
"begins": "BEGINS HERE", "begins": "BEGINS HERE",
"terminates": "TERMINATES\nHERE", "terminates": "TERMINATES\nHERE",
"from": "FROM", "from": "Arrives from",
"to": "TO", "to": "Departs to",
"desc-arriving": "The train is not here yet.\nIt's going to come from: <b>{prevStationName} (route {prevDepartureLine})</b>", "desc-beginning": "Outside scenery / begins here",
"desc-online": "The train is at the station.\nIt's going to leave to: <b>{nextStationName} (route {nextArrivalLine})</b>", "desc-arriving": "Arrives from: <b><u>{prevStationName} ({prevDepartureLine})</u></b>",
"desc-stopped": "The train is at the station and is stopped.\nIt's going to leave towards: <b>{nextStationName} (route {nextArrivalLine})</b>", "desc-online": "On scenery / direction: <b><u>{nextStationName} ({nextArrivalLine})</u></b>",
"desc-next-arrival": "Leaves towards: <b>{nextStationName} (route {nextArrivalLine})</b>", "desc-stopped": "On scenery - stopped / direction: <b><u>{nextStationName} ({nextArrivalLine})</u></b>",
"desc-departed": "The train is at the station and it's been departed.\nLeaves towards: <b>{nextStationName} (route {nextArrivalLine})</b>", "desc-next-arrival": "On scenery / direction: <b><u>{nextStationName} ({nextArrivalLine})</u></b>",
"desc-departed-ends": "The train is at the station and it's been departed.\nLeaves towards station: <b>{nextStationName}</b>", "desc-departed": "On scenery / departed to: <b><u>{nextStationName} ({nextArrivalLine})</u></b>",
"desc-departed-away": "The train has been departed to:\n<b>{nextStationName} (route {nextArrivalLine})</b>", "desc-departed-ends": "On scenery / departed to: <b><u>{nextStationName}</u></b>",
"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"
}, },
+40 -13
View File
@@ -1,4 +1,28 @@
{ {
"welcome": {
"title": "Witaj na Stacjowniku!",
"app-desc": "{b1} to aplikacja stworzona dla symulatora {link}, której celem jest wspomaganie dyżurnego ruchu i maszynisty. Możesz sprawdzić tutaj kto i na jakiej scenerii obecnie pełni służbę, znaleźć maszynistę lub przejrzeć dziennik, który zawiera historię przeszłych dyżurów i rozkładów jazdy.",
"app-desc-b1": "Stacjownik",
"sceneries-header": "Scenerie",
"sceneries-desc": "W zakładce {b1} znajdziesz informacje o dyżurnych ruchu pełniących służby na wybranych sceneriach. Możesz również przeglądać wszystkie dostępne scenerie w symulatorze (w filtrach należy zaznaczyć opcję \"Wolna\", aby pokazać resztę niezajętych) i filtrować je pod względem wielu kryteriów, takich jak sterowanie, dodatkowe oprogramowanie, sygnalizacja, wymagany poziom dyżurnego czy szlaki. Aby wyświetlić detale danej scenerii, kliknij na nią w tabelce.",
"sceneries-desc-b1": "Scenerie",
"trains-header": "Pociągi",
"trains-desc": " Zakładka {b1} zawiera listę aktywnych maszynistów i rozkładów jazdy, które są obecnie realizowane na wybranym serwerze (wybór serwera znajduje się na górze strony). Listę można filtrować i sortować uwzględniając najważniejsze kryteria, takie jak nazwa maszynisty, numer pociągu lub detale rozkładu jazdy. Możesz również kliknąć na dany pociąg, aby wyświetlić dodatkowe informacje.",
"trains-desc-b1": "Pociągi",
"journal-header": "Dziennik",
"journal-desc": "{b1} to zakładka, w której znajdziesz historię dyżurów i rozkładów jazdy, obecnie jedynie z głównego serwera rozgrywki PL1. Możesz także wyszukać historię danego użytkownika używając dodatkowych filtrów.",
"journal-desc-b1": "Dziennik",
"other-apps": "Sprawdź także inne aplikacje stworzone z myślą ułatwienia rozgrywki w TD2:",
"pojazdownik-desc": "edytor składów online",
"generator-desc": "graficzny menadżer rozkazów pisemnych",
"srjp-desc": "służbowy rozkład jazdy pociągu",
"donation-info": "Jeśli doceniasz projekt Stacjownika jak i inne aplikacje mojego autorstwa, rozważ jego wsparcie finansowe - kliknij przycisk z {icon1}, aby dowiedzieć się więcej!",
"donation-info-icon1-text": "ikoną diamentu",
"discord-info": "Zapraszam także na oficjalnego {discord}, gdzie znajdziesz dedykowanego Stacjobota, za pomocą którego możesz wyszukiwać dodatkowe dane z symulatora niedostępne na tej stronie",
"discord-info-link-text": "Discorda Stacjownika",
"bottom-text": "Miłego korzystania\n~Spythere",
"button-confirm": "Zacznij korzystać z aplikacji!"
},
"donations": { "donations": {
"button-title": "GROSZA DAJ", "button-title": "GROSZA DAJ",
"header": "Grosza daj Stacjownikowi!", "header": "Grosza daj Stacjownikowi!",
@@ -57,7 +81,7 @@
"categories": { "categories": {
"EI": "ekspres krajowy", "EI": "ekspres krajowy",
"EC": "ekspres międzynarodowy", "EC": "ekspres międzynarodowy",
"EN": "ekspres krajowy nocny", "EN": "ekspres międzynarodowy nocny",
"MP": "międzywojewódzki pospieszny", "MP": "międzywojewódzki pospieszny",
"MO": "międzywojewódzki osobowy", "MO": "międzywojewódzki osobowy",
"MM": "międzynarodowy pospieszny", "MM": "międzynarodowy pospieszny",
@@ -314,8 +338,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 ze strony forum",
"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",
@@ -505,7 +531,7 @@
"no-users": "BRAK AKTYWNYCH GRACZY", "no-users": "BRAK AKTYWNYCH GRACZY",
"no-spawns": "BRAK OTWARTYCH SPAWNÓW", "no-spawns": "BRAK OTWARTYCH SPAWNÓW",
"no-scenery": "Ups! Ta sceneria nie istnieje!", "no-scenery": "Ups! Ta sceneria nie istnieje!",
"return-btn": "Powrót", "return-btn": "POWRÓT DO POPRZEDNIEJ STRONY",
"history-btn": "Przejdź do widoku historii dyżurnych ruchu", "history-btn": "Przejdź do widoku historii dyżurnych ruchu",
"info-btn": "Wróć do widoku scenerii", "info-btn": "Wróć do widoku scenerii",
"authors-title": "Autor scenerii | Autorzy scenerii", "authors-title": "Autor scenerii | Autorzy scenerii",
@@ -551,17 +577,18 @@
"terminated": "Rozkład jazdy zakończony", "terminated": "Rozkład jazdy zakończony",
"begins": "ROZPOCZYNA\nBIEG", "begins": "ROZPOCZYNA\nBIEG",
"terminates": "KOŃCZY BIEG", "terminates": "KOŃCZY BIEG",
"from": "Z", "from": "Przyjedzie z",
"to": "DO", "to": "Odjeżdża do",
"desc-arriving": "Pociągu nie ma jeszcze na tej scenerii.\nPrzyjedzie z: <b>{prevStationName} (szlak {prevDepartureLine})</b>", "desc-beginning": "Poza scenerią / rozpoczyna bieg",
"desc-online": "Pociąg jest na tej scenerii.\nOdjedzie w kierunku: <b>{nextStationName} (szlak {nextArrivalLine})</b>", "desc-arriving": "Przyjedzie z: <b><u>{prevStationName} ({prevDepartureLine})</u></b>",
"desc-stopped": "Pociąg jest na tej scenerii i odbywa postój.\nOdjedzie w kierunku: <b>{nextStationName} (szlak {nextArrivalLine})</b>", "desc-online": "Na scenerii / kierunek: <b><u>{nextStationName} ({nextArrivalLine})</u></b>",
"desc-next-arrival": "Odjeżdża do:\n<b>{nextStationName} (szlak {nextArrivalLine})</b>", "desc-stopped": "Na scenerii - postój / kierunek: <b><u>{nextStationName} ({nextArrivalLine})</u></b>",
"desc-departed": "Pociąg jest na tej scenerii i został odprawiony.\nOdjeżdża w kierunku: <b>{nextStationName} (szlak {nextArrivalLine})</b>", "desc-next-arrival": "Na scenerii / kierunek: <b><u>{nextStationName} ({nextArrivalLine})</u></b>",
"desc-departed-ends": "Pociąg jest na tej scenerii i został odprawiony.\nOdjechał w kierunku stacji: <b>{nextStationName}</b>", "desc-departed": "Na scenerii / odprawiony do: <b><u>{nextStationName} ({nextArrivalLine})</u></b>",
"desc-departed-away": "Pociąg został odprawiony i odjechał do:\n<b>{nextStationName} (szlak {nextArrivalLine})</b>", "desc-departed-ends": "Na scenerii / odprawiony do: <b><u>{nextStationName}</u></b>",
"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 skończył bieg" "desc-terminated": "Pociąg zakończył bieg"
}, },
"history": { "history": {
"title": "DZIENNIK ROZKŁADÓW JAZDY" "title": "DZIENNIK ROZKŁADÓW JAZDY"
+2 -2
View File
@@ -43,7 +43,7 @@ function filterTrainList(
return train.timetableData?.followingStops.some((stop) => stop.comments); return train.timetableData?.followingStops.some((stop) => stop.comments);
case TrainFilterId.twr: case TrainFilterId.twr:
return !train.timetableData?.TWR; return !train.timetableData?.twr;
case TrainFilterId.pn: case TrainFilterId.pn:
return !train.timetableData?.hasExtraDeliveries; return !train.timetableData?.hasExtraDeliveries;
@@ -52,7 +52,7 @@ function filterTrainList(
return !train.timetableData?.hasDangerousCargo; return !train.timetableData?.hasDangerousCargo;
case TrainFilterId.common: case TrainFilterId.common:
return train.timetableData?.SKR || train.timetableData?.TWR; return train.timetableData?.twr;
case TrainFilterId.passenger: case TrainFilterId.passenger:
return !/^[AMRE]\D{2}$/.test(train.timetableData?.category || ''); return !/^[AMRE]\D{2}$/.test(train.timetableData?.category || '');
+5 -2
View File
@@ -36,7 +36,10 @@ const routes: Array<RouteRecordRaw> = [
props: (route) => ({ props: (route) => ({
region: route.query.region, region: route.query.region,
station: route.query.station station: route.query.station
}) }),
beforeEnter: (to, from) => {
to.meta['prevPath'] = from.fullPath;
}
}, },
{ {
path: '/journal', path: '/journal',
@@ -72,7 +75,7 @@ const router = createRouter({
from.query['view'] === undefined && from.query['view'] === undefined &&
!savedPosition !savedPosition
) )
return { el: `.scenery-left`, behavior: 'instant', top: 3 }; return { el: `.app_main`, behavior: 'instant', top: -13 };
if (savedPosition) return savedPosition; if (savedPosition) return savedPosition;
}, },
+3 -3
View File
@@ -32,7 +32,8 @@ export const useMainStore = defineStore('mainStore', {
chosenModalTrainId: undefined, chosenModalTrainId: undefined,
modalLastClickedTarget: null modalLastClickedTarget: null,
currentLocale: 'pl'
}) as MainStoreState, }) as MainStoreState,
getters: { getters: {
@@ -97,8 +98,7 @@ export const useMainStore = defineStore('mainStore', {
followingStops: timetable.stopList, followingStops: timetable.stopList,
routeDistance: timetable.stopList[timetable.stopList.length - 1].stopDistance, routeDistance: timetable.stopList[timetable.stopList.length - 1].stopDistance,
sceneries: timetable.sceneries, sceneries: timetable.sceneries,
TWR: timetable.TWR, twr: timetable.twr,
SKR: timetable.SKR,
warningNotes: timetable.warningNotes, warningNotes: timetable.warningNotes,
hasDangerousCargo: timetable.hasDangerousCargo, hasDangerousCargo: timetable.hasDangerousCargo,
hasExtraDeliveries: timetable.hasExtraDeliveries, hasExtraDeliveries: timetable.hasExtraDeliveries,
+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');
+1
View File
@@ -12,6 +12,7 @@ export interface MainStoreState {
driverStatsStatus: Status.Data; driverStatsStatus: Status.Data;
chosenModalTrainId?: string; chosenModalTrainId?: string;
modalLastClickedTarget: EventTarget | null; modalLastClickedTarget: EventTarget | null;
currentLocale: string;
} }
export interface StationJSONData { export interface StationJSONData {
+13 -43
View File
@@ -23,7 +23,6 @@
--clr-donator: #f7a4ff; --clr-donator: #f7a4ff;
--no-scroll-padding: 17px; --no-scroll-padding: 17px;
--max-container-width: 1700px; --max-container-width: 1700px;
@@ -211,6 +210,19 @@ ul {
text-shadow: #f050ff 0 0 10px; text-shadow: #f050ff 0 0 10px;
} }
&--discord {
color: var(--clr-donator);
color: transparent;
background: var(--clr-donator);
background: linear-gradient(90deg, #7fb6ff 0%, #60ecff 70%);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: #88ddff 0 0 10px;
}
} }
button, button,
@@ -285,48 +297,6 @@ a.a-button {
} }
} }
.return-btn {
display: none;
justify-content: center;
align-items: center;
position: fixed;
right: 2.5rem;
bottom: 4rem;
z-index: 100;
width: 3.5rem;
font-size: 3rem;
background-color: #555;
outline: 3px solid #222;
color: white;
border-radius: 50%;
cursor: pointer;
&:hover {
background-color: #3c3c3c;
}
img {
width: 1.3em;
}
@include responsive.smallScreen {
bottom: 1em;
right: 0;
left: 50%;
width: 1em;
height: 1em;
transform: translateX(-50%);
}
}
// Basic tooltip // Basic tooltip
[data-tooltip] { [data-tooltip] {
cursor: help; cursor: help;
+1 -2
View File
@@ -197,8 +197,7 @@ export namespace API {
category: string; category: string;
route: string; route: string;
stopList: TimetableStop[]; stopList: TimetableStop[];
TWR: boolean; twr: boolean;
SKR: boolean;
hasDangerousCargo: boolean; hasDangerousCargo: boolean;
hasExtraDeliveries: boolean; hasExtraDeliveries: boolean;
warningNotes: string | null; warningNotes: string | null;
+24 -2
View File
@@ -83,8 +83,7 @@ export interface TrainTimetableData {
category: string; category: string;
route: string; route: string;
followingStops: TrainStop[]; followingStops: TrainStop[];
TWR: boolean; twr: boolean;
SKR: boolean;
hasDangerousCargo: boolean; hasDangerousCargo: boolean;
hasExtraDeliveries: boolean; hasExtraDeliveries: boolean;
warningNotes: string | null; warningNotes: string | null;
@@ -143,6 +142,7 @@ export interface StationRoutesInfo {
isRouteSBL: boolean; isRouteSBL: boolean;
routeLength: number; routeLength: number;
routeSpeed: number; routeSpeed: number;
routeSpeedExit?: number;
routeTracks: number; routeTracks: number;
hidden?: boolean; hidden?: boolean;
realLineNo?: number; realLineNo?: number;
@@ -253,3 +253,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;
}
+74 -128
View File
@@ -2,12 +2,6 @@
<div class="scenery-view"> <div class="scenery-view">
<div class="scenery-wrapper" ref="card-wrapper"> <div class="scenery-wrapper" ref="card-wrapper">
<div class="scenery-left"> <div class="scenery-left">
<div class="scenery-actions">
<button class="back-btn" :title="$t('scenery.return-btn')" @click="onReturnButtonClick">
<img src="/images/icon-back.svg" alt="return button" />
</button>
</div>
<SceneryHeader <SceneryHeader
:stationName="station" :stationName="station"
:station="stationInfo" :station="stationInfo"
@@ -23,8 +17,8 @@
v-for="(viewMode, i) in viewModes" v-for="(viewMode, i) in viewModes"
:key="i" :key="i"
class="btn btn--option" class="btn btn--option"
:class="{ checked: currentMode == viewMode.component }" :class="{ checked: currentMode == viewMode.component.name }"
@click="setViewMode(viewMode.component)" @click="setViewMode(viewMode.component.name!)"
> >
{{ $t(viewMode.id) }} {{ $t(viewMode.id) }}
</button> </button>
@@ -32,17 +26,17 @@
<div <div
v-if=" v-if="
apiStore.dataStatuses.sceneries == Status.Loading || apiStore.dataStatuses.sceneries == Status.Data.Loading ||
apiStore.dataStatuses.connection == Status.Loading apiStore.dataStatuses.connection == Status.Data.Loading
" "
></div> ></div>
<keep-alive v-else> <keep-alive v-else>
<component <component
:is="currentMode" :is="currentViewComponent"
:onlineScenery="onlineSceneryInfo" :onlineScenery="onlineSceneryInfo"
:station="stationInfo" :station="stationInfo"
:key="currentMode" :key="currentViewComponent.name"
></component> ></component>
</keep-alive> </keep-alive>
</div> </div>
@@ -50,141 +44,93 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { computed, defineComponent } from 'vue'; import { computed, onMounted } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import routerMixin from '../mixins/routerMixin';
import { useMainStore } from '../store/mainStore'; import { useMainStore } from '../store/mainStore';
import SceneryInfo from '../components/SceneryView/SceneryInfo.vue'; import SceneryInfo from '../components/SceneryView/SceneryInfo.vue';
import SceneryHeader from '../components/SceneryView/SceneryHeader.vue'; import SceneryHeader from '../components/SceneryView/SceneryHeader.vue';
import SceneryTimetable from '../components/SceneryView/SceneryTimetable.vue'; import SceneryTimetable from '../components/SceneryView/SceneryTimetable.vue';
import SceneryTimetablesHistory from '../components/SceneryView/SceneryTimetablesHistory.vue'; import SceneryTimetablesHistory from '../components/SceneryView/SceneryTimetablesHistory.vue';
import SceneryDispatchersHistory from '../components/SceneryView/SceneryDispatchersHistory.vue'; import SceneryDispatchersHistory from '../components/SceneryView/SceneryDispatchersHistory.vue';
import ActionButton from '../components/Global/ActionButton.vue';
import { Status } from '../typings/common';
import { useApiStore } from '../store/apiStore'; import { useApiStore } from '../store/apiStore';
import { Status } from '../typings/common';
enum SceneryViewMode { const route = useRoute();
'TIMETABLES_ACTIVE', const router = useRouter();
'TIMETABLES_HISTORY',
'SCENERY_HISTORY'
}
export default defineComponent({ const props = defineProps({
name: 'SceneryView', region: {
type: String,
components: { required: false
SceneryInfo,
SceneryTimetable,
ActionButton,
SceneryHeader,
SceneryTimetablesHistory,
SceneryDispatchersHistory
}, },
props: { station: {
region: { type: String,
type: String, required: true
required: false
},
station: {
type: String,
required: true
}
},
mixins: [routerMixin],
data: () => ({
store: useMainStore(),
apiStore: useApiStore(),
viewModes: [
{
id: 'scenery.option-active-timetables',
component: 'SceneryTimetable'
},
{
id: 'scenery.option-timetables-history',
component: 'SceneryTimetablesHistory'
},
{
id: 'scenery.option-dispatchers-history',
component: 'SceneryDispatchersHistory'
}
],
sceneryViewMode: SceneryViewMode,
selectedCheckpoint: '',
currentViewCompontent: 'SceneryTimetable',
onlineFrom: -1,
Status: Status.Data
}),
setup() {
const route = useRoute();
const isComponentVisible = computed(() => route.path === '/scenery');
return {
isComponentVisible
};
},
computed: {
currentMode() {
return this.$route.query.view?.toString() ?? 'SceneryTimetable';
},
stationInfo() {
return this.store.stationList.find(
(station) => station.name === this.station?.toString().replace(/_/g, ' ')
);
},
onlineSceneryInfo() {
return this.store.activeSceneryList.find(
(scenery) =>
scenery.name === this.station?.toString().replace(/_/g, ' ') &&
scenery.region == this.store.region.id
);
}
},
methods: {
setViewMode(componentName: string) {
this.$router.push({
path: this.$route.path,
query: {
...this.$route.query,
view: componentName
}
});
},
loadSelectedCheckpoint() {
if (!this.stationInfo?.generalInfo?.checkpoints) return;
if (this.stationInfo.generalInfo.checkpoints.length == 0) return;
this.selectedCheckpoint = this.stationInfo.generalInfo.checkpoints[0];
},
onReturnButtonClick() {
this.$router.back();
}
} }
}); });
const store = useMainStore();
const apiStore = useApiStore();
const viewModes = [
{
id: 'scenery.option-active-timetables',
component: SceneryTimetable
},
{
id: 'scenery.option-timetables-history',
component: SceneryTimetablesHistory
},
{
id: 'scenery.option-dispatchers-history',
component: SceneryDispatchersHistory
}
];
const currentMode = computed(() => {
return route.query.view?.toString() ?? 'SceneryTimetable';
});
const currentViewComponent = computed(() => {
return (
viewModes.find((mode) => mode.component.name == currentMode.value)?.component ??
SceneryTimetable
);
});
const stationInfo = computed(() => {
return store.stationList.find(
(station) => station.name === props.station?.toString().replace(/_/g, ' ')
);
});
const onlineSceneryInfo = computed(() => {
return store.activeSceneryList.find(
(scenery) =>
scenery.name === props.station?.toString().replace(/_/g, ' ') &&
scenery.region == store.region.id
);
});
function setViewMode(componentName: string) {
router.push({
path: route.path,
query: {
...route.query,
view: componentName
}
});
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@use '../styles/responsive'; @use '../styles/responsive';
button.back-btn {
img {
width: 2em;
}
}
.scenery { .scenery {
&-view { &-view {
display: flex; display: flex;
+1 -1
View File
@@ -3202,7 +3202,7 @@ sharp@*, sharp@^0.33.5:
showdown@^2.1.0: showdown@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.npmjs.org/showdown/-/showdown-2.1.0.tgz" resolved "https://registry.yarnpkg.com/showdown/-/showdown-2.1.0.tgz#1251f5ed8f773f0c0c7bfc8e6fd23581f9e545c5"
integrity sha512-/6NVYu4U819R2pUIk79n67SYgJHWCce0a5xTP979WbNp0FL9MN1I1QK662IDU1b6JzKTvmhgI7T7JYIxBi3kMQ== integrity sha512-/6NVYu4U819R2pUIk79n67SYgJHWCce0a5xTP979WbNp0FL9MN1I1QK662IDU1b6JzKTvmhgI7T7JYIxBi3kMQ==
dependencies: dependencies:
commander "^9.0.0" commander "^9.0.0"