Compare commits

...

49 Commits

Author SHA1 Message Date
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
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
32 changed files with 2408 additions and 8629 deletions
-6962
View File
File diff suppressed because it is too large Load Diff
+7 -7
View File
@@ -1,6 +1,6 @@
{
"name": "stacjownik",
"version": "1.30.1",
"version": "1.30.5",
"private": true,
"type": "module",
"scripts": {
@@ -17,7 +17,7 @@
},
"dependencies": {
"core-js": "^3.42.0",
"dotenv": "^16.5.0",
"dotenv": "^17.2.2",
"pinia": "^3.0.2",
"sass": "^1.87.0",
"showdown": "^2.1.0",
@@ -26,17 +26,17 @@
"vue-router": "^4.4.0"
},
"devDependencies": {
"@types/node": "^22.15.15",
"@types/node": "^24.3.1",
"@types/showdown": "^2.0.6",
"@vite-pwa/assets-generator": "^1.0.0",
"@vitejs/plugin-vue": "^5.1.0",
"@vue/tsconfig": "^0.7.0",
"@vitejs/plugin-vue": "^6.0.1",
"@vue/tsconfig": "^0.8.1",
"axios": "^1.9.0",
"prettier": "^3.3.3",
"typescript": "^5.5.4",
"vite": "^6.3.5",
"vite": "^7.1.4",
"vite-plugin-pwa": "^1.0.0",
"vue-tsc": "^2.0.28"
"vue-tsc": "^3.0.6"
},
"browserslist": [
"> 1%",
+31 -5
View File
@@ -5,9 +5,11 @@
@toggle-card="() => (isUpdateCardOpen = false)"
/>
<AppWelcomeCard :is-card-open="isWelcomeCardOpen" @toggle-card="closeWelcomeCard" />
<Tooltip />
<AppHeader :current-lang="currentLang" @change-lang="changeLang" />
<AppHeader :current-lang="store.currentLocale" @change-lang="changeLang" />
<main class="app_main">
<router-view v-slot="{ Component }">
@@ -44,8 +46,10 @@ import UpdateCard from './components/App/UpdateCard.vue';
import StorageManager from './managers/storageManager';
import AppFooter from './components/App/AppFooter.vue';
import AppWelcomeCard from './components/App/AppWelcomeCard.vue';
const STORAGE_VERSION_KEY = 'app_version';
const WELCOME_CARD_SEEN_KEY = 'welcome_card_seen';
export default defineComponent({
components: {
@@ -54,6 +58,7 @@ export default defineComponent({
AppHeader,
AppFooter,
UpdateCard,
AppWelcomeCard,
Tooltip
},
@@ -64,8 +69,8 @@ export default defineComponent({
tooltipStore: useTooltipStore(),
isUpdateCardOpen: false,
isWelcomeCardOpen: false,
currentLang: 'pl',
isOnProductionHost: location.hostname == 'stacjownik-td2.web.app'
}),
@@ -85,13 +90,29 @@ export default defineComponent({
this.loadLang();
this.setupOfflineHandling();
this.checkAppVersion();
this.handleQueries();
this.apiStore.setupAPIData();
},
handleQueries() {
const query = new URLSearchParams(window.location.search);
if (query.get('welcomeCard') == '1') {
this.isWelcomeCardOpen = true;
}
},
async checkAppVersion() {
const isWelcomeCardSeen = StorageManager.getBooleanValue(WELCOME_CARD_SEEN_KEY);
const storageVersion = StorageManager.getStringValue(STORAGE_VERSION_KEY);
if (isWelcomeCardSeen == false && storageVersion == '') {
setTimeout(() => {
this.isWelcomeCardOpen = true;
}, 1500);
}
try {
const releaseData = await (
await axios.get('https://api.github.com/repos/Spythere/stacjownik/releases/latest')
@@ -140,7 +161,7 @@ export default defineComponent({
changeLang(lang: string) {
this.$i18n.locale = lang;
this.currentLang = lang;
this.store.currentLocale = lang;
StorageManager.setStringValue('lang', lang);
},
@@ -157,10 +178,15 @@ export default defineComponent({
const naviLanguage = window.navigator.language.toString();
if (naviLanguage.startsWith('en')) {
if (!naviLanguage.startsWith('pl')) {
this.changeLang('en');
return;
}
},
closeWelcomeCard() {
this.isWelcomeCardOpen = false;
StorageManager.setBooleanValue(WELCOME_CARD_SEEN_KEY, true);
}
}
});
+4 -2
View File
@@ -9,7 +9,9 @@
<br />
<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>
<div style="display: none">&int; ukryta taktyczna całka do programowania w HTMLu</div>
@@ -36,4 +38,4 @@ export default defineComponent({
}
}
});
</script>
</script>
+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>
<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">
{{ stationName.replace(/_/g, ' ') }}
</a>
@@ -12,39 +20,64 @@
</section>
</template>
<script lang="ts">
import { PropType, defineComponent } from 'vue';
<script lang="ts" setup>
import { onMounted, PropType, ref } from 'vue';
import { ActiveScenery, Station } from '../../typings/common';
import { useRoute, useRouter } from 'vue-router';
export default defineComponent({
props: {
station: {
type: Object as PropType<Station>
},
const route = useRoute();
const router = useRouter();
stationName: {
type: String,
required: true
},
const prevPath = ref('/');
onlineScenery: {
type: Object as PropType<ActiveScenery>
}
onMounted(() => {
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>
<style lang="scss" scoped>
@use '../../styles/responsive';
@use 'sass:color';
.info-header {
margin-top: 1em;
.btn-return {
$bgColor: #2b2b2b;
background-color: $bgColor;
margin-bottom: 0.5em;
img {
width: 2em;
}
&:hover {
background-color: color.adjust($color: $bgColor, $lightness: 15%);
}
}
.scenery-name {
font-weight: bold;
font-size: 3em;
text-align: center;
text-transform: uppercase;
}
@@ -58,4 +91,10 @@ export default defineComponent({
color: #aaa;
font-size: 1.2em;
}
@include responsive.smallScreen {
.scenery-name {
font-size: 2.5em;
}
}
</style>
@@ -43,7 +43,12 @@
<span :class="{ 'no-catenary': !route.isElectric, internal: route.isInternal }">
{{ route.routeName }}
</span>
<span v-if="route.routeSpeed" class="speed">{{ route.routeSpeed }}</span>
<span v-if="route.routeSpeed" class="speed">
<span>{{ route.routeSpeed }}</span>
<span v-if="route.routeSpeedExit && route.routeSpeedExit != route.routeSpeed">
| {{ route.routeSpeedExit }}
</span>
</span>
<span v-if="route.routeLength" class="length">
{{ (route.routeLength / 1000).toFixed(1) + 'km' }}
</span>
@@ -155,7 +160,7 @@ ul.routes-list {
-moz-user-select: none;
-webkit-user-select: none;
span {
& > span {
padding: 0.2em;
background-color: #007599;
font-weight: bold;
@@ -18,7 +18,11 @@
:key="train.id"
: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_name">
{{ train.driverName }}
@@ -83,7 +87,8 @@ export default defineComponent({
const stop = train.timetableData?.followingStops.find(
(stop) =>
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 =
+108 -34
View File
@@ -54,6 +54,18 @@
>
</template>
</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 class="timetable-list">
@@ -93,19 +105,59 @@
<span class="timetable-general">
<span class="general-info">
<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-content="getCategoryExplanation(row.train.timetableData!.category)"
class="text--primary tooltip-help"
:data-tooltip-content="$t('warnings.TWR')"
>
{{ row.train.timetableData!.category }}
</b>
<span>&nbsp;</span>
<b>{{ row.train.trainNo }}</b>
<span>&nbsp;&bull;&nbsp;</span>
<span>{{ row.train.driverName }}</span>
TWR
</span>
<span
class="train-badge tn"
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
v-if="row.checkpointStop.comments"
class="stop-comments-icon"
data-tooltip-type="BaseTooltip"
:data-tooltip-content="row.checkpointStop.comments"
>
@@ -205,7 +257,7 @@ import { useMainStore } from '../../store/mainStore';
import { useApiStore } from '../../store/apiStore';
import ScheduledTrainStatus from './ScheduledTrainStatus.vue';
import { SceneryTimetableRow } from './typings';
import { ActiveScenery, Station } from '../../typings/common';
import { ActiveScenery, Station, TooltipTrainInfo, Train } from '../../typings/common';
import { getTrainStopStatus, stopStatusPriority } from './utils';
export default defineComponent({
@@ -247,6 +299,7 @@ export default defineComponent({
const chosenCheckpoint = ref(
props.station?.generalInfo?.checkpoints[0] ??
props.onlineScenery?.missingCheckpoints[0] ??
props.station?.name ??
route.query['station']?.toString() ??
''
@@ -325,21 +378,30 @@ export default defineComponent({
methods: {
loadSelectedOption() {
if (!this.station) return;
if (!this.station.generalInfo) {
this.chosenCheckpoint = this.station.name;
return;
}
const queryCheckpoint = this.$route.query['checkpoint']?.toString();
this.chosenCheckpoint =
this.station.generalInfo.checkpoints.find(
(ch) => ch.toLocaleLowerCase() === queryCheckpoint?.toLocaleLowerCase()
) ??
this.station.generalInfo.checkpoints[0] ??
this.station.name;
let checkpointsListRef: string[] | null = null;
let sceneryName = '';
if (this.station && this.station.generalInfo) {
checkpointsListRef = this.station.generalInfo.checkpoints;
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) {
@@ -352,6 +414,7 @@ export default defineComponent({
<style lang="scss" scoped>
@use '../../styles/responsive';
@use '../../styles/animations';
@use '../../styles/badge';
.scenery-timetable {
height: 100%;
@@ -468,21 +531,32 @@ export default defineComponent({
.general-info {
display: flex;
flex-direction: column;
flex-wrap: wrap;
}
.info-number {
color: var(--clr-primary);
}
.info-train {
display: flex;
flex-wrap: wrap;
gap: 0.25em;
}
.info-route {
width: 100%;
}
.info-train > .train-badge {
font-size: 0.85em;
}
img {
height: 0.9em;
vertical-align: middle;
margin: 0 0.25em;
}
.info-number {
color: var(--clr-primary);
}
.info-route {
width: 100%;
margin-top: 0.25em;
}
.stop-comments-icon > img {
width: 1.3em;
vertical-align: top;
}
.schedule {
@@ -1,13 +1,18 @@
<template>
<div class="general-status">
<span
<router-link
v-if="computedScheduledTrain.stationNameHref"
:to="`/scenery?station=${computedScheduledTrain.stationNameHref}`"
:class="computedScheduledTrain.status"
data-tooltip-type="HtmlTooltip"
:data-tooltip-content="computedScheduledTrain.stopStatusDescription"
@click.prevent="() => {}"
v-html="computedScheduledTrain.stopStatusIndicator"
>
{{ computedScheduledTrain.stopStatusIndicator }}
</span>
</router-link>
<span
v-else
:class="computedScheduledTrain.status"
v-html="computedScheduledTrain.stopStatusIndicator"
></span>
</div>
</template>
@@ -28,66 +33,65 @@ export default defineComponent({
computedScheduledTrain() {
const { status, prevElement, currentElement, nextElement } = this.sceneryTimetableRow;
const prevDepartureIndicator = prevElement?.departureRouteExt
? `(${prevElement.departureRouteExt}) ${prevElement.stationName}`
: '---';
const nextArrivalIndicator = nextElement?.arrivalRouteExt
? `(${nextElement.arrivalRouteExt}) ${nextElement.stationName}`
: `${currentElement.stationName}`;
let stopStatusDescription = '',
stopStatusIndicator = '';
let stopStatusIndicator = '';
let stationNameHref = '';
switch (status) {
case StopStatus.ARRIVING:
stopStatusIndicator = `${this.$t('timetables.from')}: ${prevDepartureIndicator}`;
stopStatusDescription = this.$t('timetables.desc-arriving', {
prevStationName: prevElement?.stationName ?? '',
prevDepartureLine: prevElement?.departureRouteExt ?? ''
});
if (prevElement) {
stopStatusIndicator = this.$t('timetables.desc-arriving', {
prevStationName: prevElement?.stationName ?? '',
prevDepartureLine: prevElement?.departureRouteExt ?? ''
});
stationNameHref = prevElement?.stationName ?? '';
} else {
stopStatusIndicator = this.$t('timetables.desc-beginning');
}
break;
case StopStatus.ONLINE:
case StopStatus.STOPPED:
stopStatusIndicator = nextElement?.arrivalRouteExt
? `${this.$t('timetables.to')}: ${nextArrivalIndicator}`
: `${this.$t('timetables.desc-end')}`;
stopStatusDescription = nextElement?.arrivalRouteExt
? this.$t(`timetables.desc-${status}`, {
nextStationName: nextElement?.stationName,
nextArrivalLine: nextElement?.arrivalRouteExt
})
: '';
: this.$t(`timetables.desc-end`);
stationNameHref = nextElement?.stationName ?? '';
break;
case StopStatus.DEPARTED:
stopStatusIndicator = `${this.$t('timetables.to')}: ${nextArrivalIndicator}`;
if (!nextElement?.stationName) {
stopStatusDescription = this.$t('timetables.desc-departed-ends', {
stopStatusIndicator = this.$t('timetables.desc-departed-ends', {
nextStationName: currentElement.stationName
});
stationNameHref = nextElement?.stationName ?? '';
} else {
stopStatusDescription = this.$t('timetables.desc-departed', {
stopStatusIndicator = this.$t('timetables.desc-departed', {
nextStationName: nextElement?.stationName ?? currentElement.stationName,
nextArrivalLine: nextElement?.arrivalRouteExt
});
stationNameHref = nextElement?.stationName ?? '';
}
break;
case StopStatus.DEPARTED_AWAY:
stopStatusIndicator = `${this.$t('timetables.to')}: ${nextArrivalIndicator}`;
stopStatusDescription = this.$t('timetables.desc-departed-away', {
stopStatusIndicator = this.$t('timetables.desc-departed-away', {
nextStationName: nextElement?.stationName,
nextArrivalLine: nextElement?.arrivalRouteExt
});
stationNameHref = nextElement?.stationName ?? '';
break;
case StopStatus.TERMINATED:
stopStatusIndicator = `X ${this.$t('timetables.desc-terminated')}`;
stopStatusDescription = this.$t('timetables.desc-terminated');
stopStatusIndicator = this.$t('timetables.desc-terminated');
break;
default:
@@ -95,10 +99,18 @@ export default defineComponent({
}
return {
...this.sceneryTimetableRow,
stopStatusDescription,
stationNameHref,
stopStatusIndicator
};
}
},
methods: {
navigateToScenery(sceneryName?: string) {
if (!sceneryName) return;
this.$router.push(`/scenery?station=${sceneryName}`);
}
}
});
</script>
@@ -106,34 +118,29 @@ export default defineComponent({
<style lang="scss" scoped>
.general-status {
margin-top: 0.5em;
cursor: help;
span.arriving {
& > .arriving {
color: #ccc;
}
span.departed {
& > .departed {
color: lime;
font-weight: bold;
&-away {
font-weight: bold;
color: #5ecc5e;
}
}
span.stopped {
& > .stopped {
color: #ffa600;
font-weight: bold;
}
span.online {
& > .online {
color: gold;
}
span.terminated {
& > .terminated {
color: salmon;
font-weight: bold;
}
}
</style>
+66 -36
View File
@@ -33,12 +33,12 @@
class="header-image"
:class="headerName"
>
<span class="header_wrapper">
<img
:src="`/images/icon-${headerName}.svg`"
:alt="headerName"
:title="$t(`sceneries.headers.${headerName}`)"
/>
<span
class="header_wrapper"
data-tooltip-type="BaseTooltip"
:data-tooltip-content="$t(`sceneries.headers.${headerName}`)"
>
<img :src="`/images/icon-${headerName}.svg`" :alt="headerName" />
<img
class="sort-icon"
@@ -76,37 +76,49 @@
station.generalInfo.availability != 'nonPublic' &&
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)"
>
{{ station.generalInfo.reqLevel >= 2 ? station.generalInfo.reqLevel : 'L' }}
</span>
<span v-else-if="station.generalInfo.availability == 'abandoned'">
<img
src="/images/icon-abandoned.svg"
alt="non-public"
:title="$t('sceneries.info.abandoned')"
/>
<span
v-else-if="station.generalInfo.availability == 'abandoned'"
data-tooltip-type="BaseTooltip"
:data-tooltip-content="$t('sceneries.info.abandoned')"
>
<img src="/images/icon-abandoned.svg" alt="non-public" />
</span>
<span v-else-if="station.generalInfo.availability == 'nonPublic'">
<img
src="/images/icon-lock.svg"
alt="non-public"
:title="$t('sceneries.info.non-public')"
/>
<span
v-else-if="station.generalInfo.availability == 'nonPublic'"
data-tooltip-type="BaseTooltip"
:data-tooltip-content="$t('sceneries.info.non-public')"
>
<img src="/images/icon-lock.svg" alt="non-public" />
</span>
<span v-else>
<img
src="/images/icon-unavailable.svg"
alt="unavailable"
:title="$t('sceneries.info.unavailable')"
/>
<span
v-else
data-tooltip-type="BaseTooltip"
:data-tooltip-content="$t('sceneries.info.unavailable')"
>
<img src="/images/icon-unavailable.svg" alt="unavailable" />
</span>
</span>
<span v-else> ? </span>
<span
v-else
data-tooltip-type="BaseTooltip"
:data-tooltip-content="$t('sceneries.info.unknown')"
>
?
</span>
</td>
<td class="station-status">
@@ -153,7 +165,8 @@
<span
v-if="station.generalInfo.routes.singleElectrifiedNames.length != 0"
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
}`"
>
@@ -163,7 +176,8 @@
<span
v-if="station.generalInfo.routes.singleOtherNames.length != 0"
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
}`"
>
@@ -177,7 +191,8 @@
<span
v-if="station.generalInfo.routes.doubleElectrifiedNames.length != 0"
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
}`"
>
@@ -187,7 +202,8 @@
<span
v-if="station.generalInfo.routes.doubleOtherNames.length != 0"
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
}`"
>
@@ -201,7 +217,8 @@
v-if="station.generalInfo?.signalType"
class="scenery-icon icon-info"
:class="station.generalInfo?.controlType.replace('+', '-')"
:title="
data-tooltip-type="BaseTooltip"
:data-tooltip-content="
$t('sceneries.info.control-type') +
$t(`controls.${station.generalInfo?.controlType}`)
"
@@ -214,7 +231,8 @@
class="icon-info"
:src="`/images/icon-${station.generalInfo.signalType}.svg`"
:alt="station.generalInfo.signalType"
:title="
data-tooltip-type="BaseTooltip"
:data-tooltip-content="
$t('sceneries.info.signals-type') + $t(`signals.${station.generalInfo.signalType}`)
"
/>
@@ -224,7 +242,8 @@
class="icon-info"
src="/images/icon-SUP.svg"
alt="SUP (RASP-UZK)"
:title="$t('sceneries.info.SUP')"
data-tooltip-type="BaseTooltip"
:data-tooltip-content="$t('sceneries.info.SUP')"
/>
<img
@@ -232,7 +251,8 @@
class="icon-info"
src="/images/icon-ASDEK.svg"
alt="dSAT ASDEK"
:title="$t('sceneries.info.ASDEK')"
data-tooltip-type="BaseTooltip"
:data-tooltip-content="$t('sceneries.info.ASDEK')"
/>
<img
@@ -240,7 +260,8 @@
class="icon-info"
src="/images/icon-unknown.svg"
alt="icon-unknown"
:title="$t('sceneries.info.unknown')"
data-tooltip-type="BaseTooltip"
:data-tooltip-content="$t('sceneries.info.unknown')"
/>
</td>
@@ -248,7 +269,7 @@
class="station-users"
:class="{ inactive: !station.onlineInfo }"
data-tooltip-type="UsersTooltip"
:data-tooltip-content="JSON.stringify(station.onlineInfo?.stationTrains ?? [])"
:data-tooltip-content="getUsersTooltipContent(station.onlineInfo?.stationTrains ?? [])"
>
<span class="text--primary">{{
station.onlineInfo?.stationTrains?.length ?? '-'
@@ -318,7 +339,7 @@ import dateMixin from '../../mixins/dateMixin';
import styleMixin from '../../mixins/styleMixin';
import { useApiStore } from '../../store/apiStore';
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 { getChangedFilters } from '../../managers/stationFilterManager';
import { ActiveSorter, HeadIdsType, headIconsIds, headIds } from './typings';
@@ -394,6 +415,15 @@ export default defineComponent({
else this.activeSorter.dir = 1;
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 UsersTooltip from './UsersTooltip.vue';
import HtmlTooltip from './HtmlTooltip.vue';
import TrainInfoTooltip from "./TrainInfoTooltip.vue";
const BOX_PADDING_PX = 20;
@@ -23,7 +24,8 @@ export default defineComponent({
BaseTooltip,
SpawnsTooltip,
UsersTooltip,
HtmlTooltip
HtmlTooltip,
TrainInfoTooltip
},
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">
import { defineComponent } from 'vue';
import { useTooltipStore } from '../../store/tooltipStore';
import { Train } from '../../typings/common';
import { TooltipUserTrain } from '../../typings/common';
export default defineComponent({
data() {
@@ -23,7 +23,7 @@ export default defineComponent({
trains() {
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);
}
}
+7 -3
View File
@@ -9,7 +9,7 @@
<span
class="train-badge twr"
v-if="train.timetableData?.TWR"
v-if="train.timetableData?.twr"
data-tooltip-type="BaseTooltip"
:data-tooltip-content="$t('warnings.TWR')"
>
@@ -110,7 +110,10 @@
{{ $t('trains.scenery-offline') }}
</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>
Offline {{ lastSeenMessage(train.lastSeen) }}
</div>
@@ -156,7 +159,7 @@
v-if="extended && train.timetableData && train.timetableData.warningNotes"
>
<div class="dangers-badges">
<div v-if="train.timetableData?.TWR">
<div v-if="train.timetableData?.twr">
<div class="train-badge twr">TWR</div>
- {{ $t('warnings.TWR') }}
</div>
@@ -394,6 +397,7 @@ export default defineComponent({
.status-badges {
display: flex;
flex-wrap: wrap;
margin-left: 0.25em;
gap: 0.25em;
+13 -8
View File
@@ -30,17 +30,22 @@
</div>
<div class="search-box">
<select
class="search-input"
name="active-trains"
id="active-trains"
v-model="searchedDriver"
>
<option value="">{{ $t('options.select-driver') }}</option>
<datalist id="search-active-driver">
<option v-for="driverName in activeDriverNames" :value="driverName">
{{ driverName }}
</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')">
<img src="/images/icon-exit.svg" alt="Trains search clear icon" />
+64 -36
View File
@@ -12,8 +12,16 @@
:data-delayed="stop.departureDelay > 0"
:data-stop-type="stop.type"
:data-is-active="stop.isActive"
:data-track-count-departure="stop.departureLineInfo?.routeTracks ?? 2"
:data-track-count-arrival="stop.arrivalLineInfo?.routeTracks ?? 2"
:data-track-count-departure="
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="distance">
@@ -57,7 +65,15 @@
<span>{{ stop.departureLine }}</span>
<span v-if="stop.departureLineInfo">
<span> | {{ stop.departureLineInfo.routeSpeed }}</span>
<span>
|
{{
stop.departureLineInfo.routeSpeedExit &&
stop.departureLineInfo.routeSpeedExit != stop.departureLineInfo.routeSpeed
? `${stop.departureLineInfo.routeSpeedExit} (${stop.departureLineInfo.routeSpeed})`
: stop.departureLineInfo.routeSpeed
}}</span
>
<img
:src="
@@ -85,13 +101,13 @@
</div>
<div
v-if="stop.sceneryName != scheduleStops[i + 1]?.sceneryName"
v-if="stop.nextPointRef && stop.sceneryName != stop.nextPointRef.sceneryName"
class="scenery-change-name"
>
<span>{{ scheduleStops[i + 1].sceneryName }}</span>
<span>{{ stop.nextPointRef.sceneryName }}</span>
<i
v-if="!scheduleStops[i + 1].isSceneryOnline"
v-if="!stop.nextPointRef.isSceneryOnline"
class="fa-solid fa-ban fa-sm"
data-tooltip-type="BaseTooltip"
:data-tooltip-content="$t('app.tooltip-scenery-offline')"
@@ -101,30 +117,39 @@
<div
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> | {{ scheduleStops[i + 1].arrivalLineInfo!.routeSpeed }} </span>
<span v-if="stop.nextPointRef.arrivalLineInfo">
<span> | {{ stop.nextPointRef.arrivalLineInfo.routeSpeed }}</span>
<span
v-if="
stop.nextPointRef.arrivalLineInfo.routeSpeedExit &&
stop.nextPointRef.arrivalLineInfo.routeSpeedExit !=
stop.nextPointRef.arrivalLineInfo.routeSpeed
"
>
({{ stop.nextPointRef.arrivalLineInfo.routeSpeedExit }})
</span>
<img
:src="
scheduleStops[i + 1].arrivalLineInfo?.isElectric
stop.nextPointRef.arrivalLineInfo?.isElectric
? '/images/icon-catenary.svg'
: '/images/icon-we4a.png'
"
data-tooltip-type="BaseTooltip"
:data-tooltip-content="
$t(
`trains.${!scheduleStops[i + 1].arrivalLineInfo?.isElectric ? 'no-' : ''}catenary-tooltip`
`trains.${!stop.nextPointRef.arrivalLineInfo?.isElectric ? 'no-' : ''}catenary-tooltip`
)
"
width="14"
/>
<img
v-if="scheduleStops[i + 1].arrivalLineInfo!.isRouteSBL"
v-if="stop.nextPointRef.arrivalLineInfo!.isRouteSBL"
src="/images/icon-sbl-transparent.svg"
width="14"
data-tooltip-type="BaseTooltip"
@@ -176,26 +201,28 @@ export default defineComponent({
const sceneryData =
this.store.stationList?.find((sc) => sc.name == pathEl.stationName) ?? null;
if (!sceneryData || !sceneryData.generalInfo) return null;
const activeScenery = this.apiStore.activeData?.activeSceneries?.find(
(sc) => sc.stationName == pathEl.stationName
);
const arrivalLineData = pathEl.arrivalRouteExt
? (sceneryData.generalInfo.routes.all.find(
(rt) => rt.routeName == pathEl.arrivalRouteExt
) ?? null)
const arrivalLineData = sceneryData?.generalInfo
? pathEl.arrivalRouteExt
? (sceneryData.generalInfo.routes.all.find(
(rt) => rt.routeName == pathEl.arrivalRouteExt
) ?? null)
: null
: null;
const departureLineData = pathEl.departureRouteExt
? (sceneryData.generalInfo.routes.all.find(
(rt) => rt.routeName == pathEl.departureRouteExt
) ?? null)
const departureLineData = sceneryData?.generalInfo
? pathEl.departureRouteExt
? (sceneryData.generalInfo.routes.all.find(
(rt) => rt.routeName == pathEl.departureRouteExt
) ?? null)
: null
: null;
return {
generalInfo: sceneryData.generalInfo,
generalInfo: sceneryData?.generalInfo ?? null,
isOnline:
activeScenery &&
(activeScenery.isOnline == 1 || activeScenery.lastSeen >= Date.now() - 60000),
@@ -224,33 +251,27 @@ export default defineComponent({
let isActive = false;
if (pathData?.departureLineData) {
// arrivalLineInfo = pathData.departureLineData;
arrivalLineInfo = pathData.departureLineData;
departureLineInfo = pathData.departureLineData;
}
for (const stop of followingStops) {
followingStops.forEach((stop, i) => {
let isExternal = false;
if (stop.arrivalLine === currentPath.arrivalRouteExt) {
isExternal = true;
departureLineInfo = pathData?.arrivalLineData ?? null;
if (pathData?.arrivalLineData) {
arrivalLineInfo = pathData.arrivalLineData;
}
arrivalLineInfo = pathData.arrivalLineData;
}
let correctedDepartureLineData: StationRoutesInfo | null = null;
const internalRouteInfo = stop.departureLine
? pathData?.generalInfo.routes.all.find(
? pathData?.generalInfo?.routes.all.find(
(route) => route.isInternal && route.routeName == stop.departureLine
)
: undefined;
if (internalRouteInfo) {
correctedDepartureLineData = internalRouteInfo;
departureLineInfo = internalRouteInfo;
}
@@ -287,7 +308,9 @@ export default defineComponent({
status: stop.confirmed ? 'confirmed' : stop.stopped ? 'stopped' : 'unconfirmed',
sceneryName: currentPath.stationName,
isSceneryOnline: pathData?.isOnline ?? false
isSceneryOnline: pathData?.isOnline ?? false,
nextPointRef: null
};
if (internalRouteInfo) {
@@ -309,6 +332,11 @@ export default defineComponent({
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) {
// Reverse search for last scenery checkpoint
if (pathData?.departureLineData) {
@@ -328,7 +356,7 @@ export default defineComponent({
currentPath = timetablePath[++currentPathIndex];
pathData = this.getPathSceneryData(currentPath);
}
}
});
return stopRows;
},
+2
View File
@@ -196,4 +196,6 @@ export interface TrainSchedulePoint {
isSBL: boolean;
sceneryName: string | null;
isSceneryOnline: boolean;
nextPointRef: TrainSchedulePoint | null;
}
+1 -1
View File
@@ -22,7 +22,7 @@
"TRE", "TRS",
"TSE", "TSS",
"THE", "THS",
"LPE",
"LPE", "LPS",
"LTE", "LTS",
"LSS",
"LZE", "LZS",
+50 -23
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": {
"button-title": "TOSS A COIN",
"header": "Toss a coin to Stacjownik!",
@@ -60,7 +84,7 @@
"categories": {
"EI": "domestic express",
"EC": "international express",
"EN": "domestic night express",
"EN": "international night express",
"MP": "intervoivodeship bullet",
"MO": "intervoivodeship regio",
"MM": "international bullet",
@@ -118,7 +142,7 @@
"title": "Control type",
"SPK": "SPK",
"SCS": "SCS",
"SCS-SPK": "SCS/SPK",
"SCS-SPK": "SCS + SPK",
"SPE": "SPE",
"ręczne": "manual",
"ręczne+SPK": "manual + SPK",
@@ -129,7 +153,7 @@
"abbrevs": {
"SPK": "SPK",
"SCS": "SCS",
"SCS-SPK": "S/S",
"SCS-SPK": "S+S",
"SPE": "SPE",
"ręczne": "R",
"ręczne+SPK": "R",
@@ -313,18 +337,20 @@
},
"info": {
"control-type": "Control type: ",
"signals-type": "Signals type: ",
"SBL": "This scenery has automatic block signalling (ABS/SBL) system on following routes: ",
"signals-type": "Signalling type: ",
"SBL": "A scenery with automatic block signalling (ABS/SBL) on routes: ",
"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-routes": "This scenery has two-way route blockade on following routes: ",
"default": "This scenery is available by default",
"non-public": "This scenery is not public",
"unavailable": "This scenery is unavailable",
"abandoned": "This scenery is no longer supported by its creators",
"unknown": "This scenery isn't recognizable right now",
"real": "Scenery with real lines: ",
"default": "Scenery available in the game package",
"nonDefault": "Scenery available to download from the forum site",
"req-level": "all dispatcher levels | requries {lvl} dispatcher lvl | requires {lvl} dispatcher lvl",
"non-public": "Non-public scenery",
"unavailable": "Unavailable scenery",
"abandoned": "Abandoned scenery",
"unknown": "Unknown scenery",
"real": "Scenery with real Polish routes: ",
"double-track-routes-catenary": "Electrified double-track routes count: ",
"single-track-routes-catenary": "Electrified single-track routes count: ",
"double-track-routes-other": "Not electrified double-track routes count: ",
@@ -519,7 +545,7 @@
"no-users": "NO ACTIVE PLAYERS",
"no-spawns": "NO OPEN SPAWNS",
"no-scenery": "Oops! This scenery doesn't exist!",
"return-btn": "Return",
"return-btn": "BACK TO THE LAST SITE",
"history-btn": "View the dispatcher history",
"info-btn": "Return to the scenery view",
"authors-title": "Scenery author | Scenery authors",
@@ -527,7 +553,7 @@
"lines-title": "Real lines",
"project-title": "Project name",
"additional-tools-title": "Additional tools",
"one-way-routes": "Signle track routes",
"one-way-routes": "Single track routes",
"two-way-routes": "Double track routes",
"no-data": "No available data about this scenery",
"option-active-timetables": "Active timetables",
@@ -565,15 +591,16 @@
"terminated": "Timetable terminated",
"begins": "BEGINS HERE",
"terminates": "TERMINATES\nHERE",
"from": "FROM",
"to": "TO",
"desc-arriving": "The train is not here yet.\nIt's going to come from: <b>{prevStationName} (route {prevDepartureLine})</b>",
"desc-online": "The train is at the station.\nIt's going to leave to: <b>{nextStationName} (route {nextArrivalLine})</b>",
"desc-stopped": "The train is at the station and is stopped.\nIt's going to leave towards: <b>{nextStationName} (route {nextArrivalLine})</b>",
"desc-next-arrival": "Leaves towards: <b>{nextStationName} (route {nextArrivalLine})</b>",
"desc-departed": "The train is at the station and it's been departed.\nLeaves towards: <b>{nextStationName} (route {nextArrivalLine})</b>",
"desc-departed-ends": "The train is at the station and it's been departed.\nLeaves towards station: <b>{nextStationName}</b>",
"desc-departed-away": "The train has been departed to:\n<b>{nextStationName} (route {nextArrivalLine})</b>",
"from": "Arrives from",
"to": "Departs to",
"desc-beginning": "Outside scenery / begins here",
"desc-arriving": "Arrives from: <b><u>{prevStationName} ({prevDepartureLine})</u></b>",
"desc-online": "On scenery / direction: <b><u>{nextStationName} ({nextArrivalLine})</u></b>",
"desc-stopped": "On scenery - stopped / direction: <b><u>{nextStationName} ({nextArrivalLine})</u></b>",
"desc-next-arrival": "On scenery / direction: <b><u>{nextStationName} ({nextArrivalLine})</u></b>",
"desc-departed": "On scenery / departed to: <b><u>{nextStationName} ({nextArrivalLine})</u></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-terminated": "The train has been terminated"
},
+43 -16
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": {
"button-title": "GROSZA DAJ",
"header": "Grosza daj Stacjownikowi!",
@@ -57,7 +81,7 @@
"categories": {
"EI": "ekspres krajowy",
"EC": "ekspres międzynarodowy",
"EN": "ekspres krajowy nocny",
"EN": "ekspres międzynarodowy nocny",
"MP": "międzywojewódzki pospieszny",
"MO": "międzywojewódzki osobowy",
"MM": "międzynarodowy pospieszny",
@@ -115,7 +139,7 @@
"title": "Sterowanie",
"SPK": "SPK",
"SCS": "SCS",
"SCS-SPK": "SCS/SPK",
"SCS-SPK": "SCS + SPK",
"SPE": "SPE",
"ręczne": "ręczne",
"ręczne+SPK": "ręczne z SPK",
@@ -126,7 +150,7 @@
"abbrevs": {
"SPK": "SPK",
"SCS": "SCS",
"SCS-SPK": "S/S",
"SCS-SPK": "S+S",
"SPE": "SPE",
"ręczne": "R",
"ręczne+SPK": "R",
@@ -314,8 +338,10 @@
"signals-type": "Sygnalizacja: ",
"SBL": "Sceneria posiada SBL na szlakach: ",
"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ą",
"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",
"unavailable": "Sceneria niedostępna",
"abandoned": "Sceneria wycofana z rozgrywki",
@@ -505,7 +531,7 @@
"no-users": "BRAK AKTYWNYCH GRACZY",
"no-spawns": "BRAK OTWARTYCH SPAWNÓW",
"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",
"info-btn": "Wróć do widoku scenerii",
"authors-title": "Autor scenerii | Autorzy scenerii",
@@ -551,19 +577,20 @@
"terminated": "Rozkład jazdy zakończony",
"begins": "ROZPOCZYNA\nBIEG",
"terminates": "KOŃCZY BIEG",
"from": "Z",
"to": "DO",
"desc-arriving": "Pociągu nie ma jeszcze na tej scenerii.\nPrzyjedzie z: <b>{prevStationName} (szlak {prevDepartureLine})</b>",
"desc-online": "Pociąg jest na tej scenerii.\nOdjedzie w kierunku: <b>{nextStationName} (szlak {nextArrivalLine})</b>",
"desc-stopped": "Pociąg jest na tej scenerii i odbywa postój.\nOdjedzie w kierunku: <b>{nextStationName} (szlak {nextArrivalLine})</b>",
"desc-next-arrival": "Odjeżdża do:\n<b>{nextStationName} (szlak {nextArrivalLine})</b>",
"desc-departed": "Pociąg jest na tej scenerii i został odprawiony.\nOdjeżdża w kierunku: <b>{nextStationName} (szlak {nextArrivalLine})</b>",
"desc-departed-ends": "Pociąg jest na tej scenerii i został odprawiony.\nOdjechał w kierunku stacji: <b>{nextStationName}</b>",
"desc-departed-away": "Pociąg został odprawiony i odjechał do:\n<b>{nextStationName} (szlak {nextArrivalLine})</b>",
"from": "Przyjedzie z",
"to": "Odjeżdża do",
"desc-beginning": "Poza scenerią / rozpoczyna bieg",
"desc-arriving": "Przyjedzie z: <b><u>{prevStationName} ({prevDepartureLine})</u></b>",
"desc-online": "Na scenerii / kierunek: <b><u>{nextStationName} ({nextArrivalLine})</u></b>",
"desc-stopped": "Na scenerii - postój / kierunek: <b><u>{nextStationName} ({nextArrivalLine})</u></b>",
"desc-next-arrival": "Na scenerii / kierunek: <b><u>{nextStationName} ({nextArrivalLine})</u></b>",
"desc-departed": "Na scenerii / odprawiony do: <b><u>{nextStationName} ({nextArrivalLine})</u></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-terminated": "Pociąg skończył bieg"
"desc-terminated": "Pociąg zakończył bieg"
},
"history": {
"title": "DZIENNIK ROZKŁADÓW JAZDY"
}
}
}
+2 -2
View File
@@ -43,7 +43,7 @@ function filterTrainList(
return train.timetableData?.followingStops.some((stop) => stop.comments);
case TrainFilterId.twr:
return !train.timetableData?.TWR;
return !train.timetableData?.twr;
case TrainFilterId.pn:
return !train.timetableData?.hasExtraDeliveries;
@@ -52,7 +52,7 @@ function filterTrainList(
return !train.timetableData?.hasDangerousCargo;
case TrainFilterId.common:
return train.timetableData?.SKR || train.timetableData?.TWR;
return train.timetableData?.twr;
case TrainFilterId.passenger:
return !/^[AMRE]\D{2}$/.test(train.timetableData?.category || '');
+5 -2
View File
@@ -36,7 +36,10 @@ const routes: Array<RouteRecordRaw> = [
props: (route) => ({
region: route.query.region,
station: route.query.station
})
}),
beforeEnter: (to, from) => {
to.meta['prevPath'] = from.fullPath;
}
},
{
path: '/journal',
@@ -72,7 +75,7 @@ const router = createRouter({
from.query['view'] === undefined &&
!savedPosition
)
return { el: `.scenery-left`, behavior: 'instant', top: 3 };
return { el: `.app_main`, behavior: 'instant', top: -13 };
if (savedPosition) return savedPosition;
},
+53 -9
View File
@@ -13,6 +13,7 @@ import { useApiStore } from './apiStore';
import { MainStoreState } from './typings';
const checkpointsTrains: Map<string, CheckpointTrain[]> = new Map();
const unknownSceneryCheckpoints: Map<string, Set<string>> = new Map();
const sceneriesTrains: Map<string, Train[]> = new Map();
export const useMainStore = defineStore('mainStore', {
@@ -32,7 +33,8 @@ export const useMainStore = defineStore('mainStore', {
chosenModalTrainId: undefined,
modalLastClickedTarget: null
modalLastClickedTarget: null,
currentLocale: 'pl'
}) as MainStoreState,
getters: {
@@ -41,6 +43,7 @@ export const useMainStore = defineStore('mainStore', {
checkpointsTrains.clear();
sceneriesTrains.clear();
unknownSceneryCheckpoints.clear();
const dateNow = new Date();
@@ -97,8 +100,7 @@ export const useMainStore = defineStore('mainStore', {
followingStops: timetable.stopList,
routeDistance: timetable.stopList[timetable.stopList.length - 1].stopDistance,
sceneries: timetable.sceneries,
TWR: timetable.TWR,
SKR: timetable.SKR,
twr: timetable.twr,
warningNotes: timetable.warningNotes,
hasDangerousCargo: timetable.hasDangerousCargo,
hasExtraDeliveries: timetable.hasExtraDeliveries,
@@ -133,8 +135,13 @@ export const useMainStore = defineStore('mainStore', {
// Checkpoints trains map
if (trainObj.timetableData) {
let currentSceneryIndex = 0;
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) => {
if (/strong|podg|pe/.test(stop.stopName)) {
@@ -153,16 +160,41 @@ export const useMainStore = defineStore('mainStore', {
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())) {
checkpointsTrains.set(stop.stopNameRAW.toLowerCase(), [
...checkpointsTrains.get(stop.stopNameRAW.toLowerCase())!,
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++;
currentSceneryData =
this.stationList.find(
(s) => s.name == timetablePath[currentSceneryIndex].stationName
) ?? null;
}
});
}
@@ -222,7 +254,9 @@ export const useMainStore = defineStore('mainStore', {
all: 0,
confirmed: 0,
unconfirmed: 0
}
},
missingCheckpoints: []
});
});
@@ -266,7 +300,9 @@ export const useMainStore = defineStore('mainStore', {
all: 0,
confirmed: 0,
unconfirmed: 0
}
},
missingCheckpoints: []
});
return list;
@@ -277,7 +313,7 @@ export const useMainStore = defineStore('mainStore', {
for (let i = 0, n = allActiveSceneries.length; i < n; 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();
@@ -293,6 +329,14 @@ export const useMainStore = defineStore('mainStore', {
scenery.stationTrains =
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[] = [];
checkpoints.forEach((cp) => {
const scheduledTrains = checkpointsTrains.get(cp.toLowerCase());
+4 -1
View File
@@ -8,7 +8,8 @@ export const tooltipKeys = [
'VehiclePreviewTooltip',
'SpawnsTooltip',
'UsersTooltip',
'HtmlTooltip'
'HtmlTooltip',
'TrainInfoTooltip'
] as const;
export type TooltipType = (typeof tooltipKeys)[number];
@@ -33,6 +34,7 @@ export const useTooltipStore = defineStore('tooltipStore', {
this.content = '';
},
// Tooltip handler reading attributes of DOM elements
handle(e: MouseEvent) {
const targetEl = e
.composedPath()
@@ -44,6 +46,7 @@ export const useTooltipStore = defineStore('tooltipStore', {
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 tooltipContent = targetEl.getAttribute('data-tooltip-content');
+1
View File
@@ -12,6 +12,7 @@ export interface MainStoreState {
driverStatsStatus: Status.Data;
chosenModalTrainId?: string;
modalLastClickedTarget: EventTarget | null;
currentLocale: string;
}
export interface StationJSONData {
+14 -44
View File
@@ -23,7 +23,6 @@
--clr-donator: #f7a4ff;
--no-scroll-padding: 17px;
--max-container-width: 1700px;
@@ -207,10 +206,23 @@ ul {
background: linear-gradient(90deg, #ff88db 30%, #ffffff 70%);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
-webkit-text-fill-color: transparent;
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,
@@ -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
[data-tooltip] {
cursor: help;
+1 -1
View File
@@ -36,6 +36,6 @@
}
&.SCS-SPK {
color: white;
color: #aefff8;
}
}
+1 -2
View File
@@ -197,8 +197,7 @@ export namespace API {
category: string;
route: string;
stopList: TimetableStop[];
TWR: boolean;
SKR: boolean;
twr: boolean;
hasDangerousCargo: boolean;
hasExtraDeliveries: boolean;
warningNotes: string | null;
+25 -2
View File
@@ -83,8 +83,7 @@ export interface TrainTimetableData {
category: string;
route: string;
followingStops: TrainStop[];
TWR: boolean;
SKR: boolean;
twr: boolean;
hasDangerousCargo: boolean;
hasExtraDeliveries: boolean;
warningNotes: string | null;
@@ -143,6 +142,7 @@ export interface StationRoutesInfo {
isRouteSBL: boolean;
routeLength: number;
routeSpeed: number;
routeSpeedExit?: number;
routeTracks: number;
hidden?: boolean;
realLineNo?: number;
@@ -170,6 +170,7 @@ export interface ActiveScenery {
confirmed: number;
unconfirmed: number;
};
missingCheckpoints: string[];
}
export interface ScenerySpawn {
@@ -253,3 +254,25 @@ export interface VehicleCargo {
id: string;
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-wrapper" ref="card-wrapper">
<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
:stationName="station"
:station="stationInfo"
@@ -23,8 +17,8 @@
v-for="(viewMode, i) in viewModes"
:key="i"
class="btn btn--option"
:class="{ checked: currentMode == viewMode.component }"
@click="setViewMode(viewMode.component)"
:class="{ checked: currentMode == viewMode.component.name }"
@click="setViewMode(viewMode.component.name!)"
>
{{ $t(viewMode.id) }}
</button>
@@ -32,17 +26,17 @@
<div
v-if="
apiStore.dataStatuses.sceneries == Status.Loading ||
apiStore.dataStatuses.connection == Status.Loading
apiStore.dataStatuses.sceneries == Status.Data.Loading ||
apiStore.dataStatuses.connection == Status.Data.Loading
"
></div>
<keep-alive v-else>
<component
:is="currentMode"
:is="currentViewComponent"
:onlineScenery="onlineSceneryInfo"
:station="stationInfo"
:key="currentMode"
:key="currentViewComponent.name"
></component>
</keep-alive>
</div>
@@ -50,141 +44,93 @@
</div>
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
import { useRoute } from 'vue-router';
import routerMixin from '../mixins/routerMixin';
<script lang="ts" setup>
import { computed, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useMainStore } from '../store/mainStore';
import SceneryInfo from '../components/SceneryView/SceneryInfo.vue';
import SceneryHeader from '../components/SceneryView/SceneryHeader.vue';
import SceneryTimetable from '../components/SceneryView/SceneryTimetable.vue';
import SceneryTimetablesHistory from '../components/SceneryView/SceneryTimetablesHistory.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 { Status } from '../typings/common';
enum SceneryViewMode {
'TIMETABLES_ACTIVE',
'TIMETABLES_HISTORY',
'SCENERY_HISTORY'
}
const route = useRoute();
const router = useRouter();
export default defineComponent({
name: 'SceneryView',
components: {
SceneryInfo,
SceneryTimetable,
ActionButton,
SceneryHeader,
SceneryTimetablesHistory,
SceneryDispatchersHistory
const props = defineProps({
region: {
type: String,
required: false
},
props: {
region: {
type: String,
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();
}
station: {
type: String,
required: true
}
});
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>
<style lang="scss" scoped>
@use '../styles/responsive';
button.back-btn {
img {
width: 2em;
}
}
.scenery {
&-view {
display: flex;
+1392 -1238
View File
File diff suppressed because it is too large Load Diff