Compare commits

...

54 Commits

Author SHA1 Message Date
Spythere fbc9785341 Merge pull request #105 from Spythere/development
v1.27.0
2024-08-24 00:20:08 +02:00
Spythere 9fd02c2336 chore: scenery view border radius 2024-08-23 16:24:21 +02:00
Spythere c031dd55c1 chore: removed return button 2024-08-23 16:22:54 +02:00
Spythere b0870699a4 hotfix: anchor style 2024-08-22 23:20:09 +02:00
Spythere a8da634b0e hotfix: TrainsView watcher causing routing problems 2024-08-22 23:19:42 +02:00
Spythere 8920b1e5e8 hotfix: view styles 2024-08-22 17:00:40 +02:00
Spythere 4fa1c05831 fix: input attrs 2024-08-22 16:44:51 +02:00
Spythere ecef2d5ee4 chore: backwards compatibility with train modal for ext. links 2024-08-22 16:37:47 +02:00
Spythere 1749871d08 fix: filters double click 2024-08-22 16:37:12 +02:00
Spythere 5545616706 chore: styling hotfixes and improvements 2024-08-22 02:28:40 +02:00
Spythere b35bb03868 merge: 'dominik-korsa-links' into development 2024-08-22 02:27:53 +02:00
Spythere b9521918cb hotfix: translation fix 2024-08-21 21:38:21 +02:00
Spythere 7ad17fc2c5 chore: style improvements & finishing touches 2024-08-21 21:33:46 +02:00
Spythere a80144cb1c chore: missing translations 2024-08-21 18:30:17 +02:00
Spythere 1227cdb94a chore: driver view links 2024-08-21 18:12:35 +02:00
Spythere b33594fd6f fix: journal list styles 2024-08-21 17:17:52 +02:00
Spythere 9f8656e590 fix: progress indicator 2024-08-21 17:06:50 +02:00
dominik-korsa 81cd165fe7 Use <router-link> instead of <tr> with click handler in StationTable 2024-08-21 14:42:03 +02:00
dominik-korsa 41e4b45599 Use <router-link> for driver journal button in TrainInfo 2024-08-21 13:54:48 +02:00
dominik-korsa 462dd7dd7a Replace all remaining uses of driverViewMixin with <router-link> 2024-08-21 13:49:31 +02:00
dominik-korsa 9837ae97e1 Use <router-link> instead of a @click handler in SceneryTimetable 2024-08-21 13:29:53 +02:00
dominik-korsa a818cd980b Use <router-link> instead of a @click handler in TrainTable 2024-08-21 12:58:47 +02:00
Spythere 573ebc233b chore: removed registering train modal 2024-08-21 02:06:38 +02:00
Spythere aae47c6abd fix: journal stats badges 2024-08-21 02:06:23 +02:00
Spythere 24c9b62162 feat: driver train view 2024-08-21 02:02:35 +02:00
Spythere 481d43b6d8 chore: selecting station checkpoint from url 2024-08-20 14:31:52 +02:00
Spythere 4969a433cc fix: train modal responsiveness & icons 2024-08-20 13:33:24 +02:00
Spythere 8a2b453dc6 chore: journal daily stats styling 2024-08-20 13:16:37 +02:00
Spythere 86d178ef56 chore: restored Pragotron link 2024-08-20 13:16:02 +02:00
Spythere 7769477508 chore: station stats styling 2024-08-20 00:14:44 +02:00
Spythere 551b60c733 fix: omitting "po" stops in timetable progress bar 2024-08-19 23:56:15 +02:00
Spythere 80a5b56785 feat: router links embeded into timetable stop names 2024-08-18 23:45:42 +02:00
Spythere 6bd62f13a1 chore: stats button responsiveness 2024-08-18 23:14:18 +02:00
Spythere 42591f6e76 chore: journal timetables styling improvements 2024-08-18 23:03:00 +02:00
Spythere 4ca0f09e75 chore: added ZG category 2024-08-18 22:58:22 +02:00
Spythere 02c3629c00 bump: v1.27.0 2024-08-18 01:43:51 +02:00
Spythere 9c4c806f0e chore: moved station stats to a dropdown 2024-08-18 01:43:27 +02:00
Spythere 58d6a97762 fix: twr/skr badges sizing 2024-08-17 15:51:36 +02:00
Spythere cd71c78eb4 chore: apicache typings 2024-08-17 15:51:25 +02:00
Spythere 300e70dcfe chore: journal timetables buttons alignment 2024-08-15 15:01:36 +02:00
Spythere 09b31f7914 Merge pull request #104 from Spythere/development
hotfix: warning icon placement
2024-08-10 23:41:34 +02:00
Spythere c29c3c6abe hotfix: warning icon placement 2024-08-10 23:41:00 +02:00
Spythere 916f6070ac Merge pull request #103 from Spythere/development
chore: added back merge gh workflow
2024-08-10 14:18:45 +02:00
Spythere a74ab6eb2a chore: added back merge gh workflow 2024-08-10 14:18:00 +02:00
Spythere 985c699ced Merge pull request #102 from Spythere/development
v1.26.1
2024-08-10 14:16:06 +02:00
Spythere 7e0e9146a5 fix: vehicle thumbnail cargo info 2024-08-10 14:08:32 +02:00
Spythere 30a0f05922 feat: journal dispatchers filtering by sc. hash 2024-08-10 14:00:25 +02:00
Spythere a30e04ca96 bump: v1.26.1 2024-08-09 15:24:48 +02:00
Spythere 1852d3e234 feat: category codes explanation tooltips 2024-08-09 15:24:26 +02:00
Spythere a17bf6c03f Merge branch 'development' 2024-08-09 14:12:07 +02:00
Spythere 766b08bc15 Merge branch 'master' of github.com:Spythere/stacjownik 2024-08-09 14:11:34 +02:00
Spythere cd1a4fa734 hotfix: checkpoint trains filtering 2024-08-06 14:18:26 +02:00
Spythere acbe761068 Merge branch 'development' 2024-08-05 16:02:20 +02:00
Spythere 44f6cf4232 Merge branch 'development' 2024-05-12 15:22:28 +02:00
45 changed files with 926 additions and 709 deletions
@@ -0,0 +1,17 @@
name: Deploy to Firebase Hosting on merge
'on':
push:
branches:
- master
jobs:
build_and_deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: yarn && yarn build
- uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: '${{ secrets.GITHUB_TOKEN }}'
firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_STACJOWNIK_TD2 }}'
channelId: live
projectId: stacjownik-td2
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "stacjownik", "name": "stacjownik",
"version": "1.26.0", "version": "1.27.0",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
-9
View File
@@ -6,13 +6,6 @@
/> />
<Tooltip /> <Tooltip />
<transition name="modal-anim">
<keep-alive>
<TrainModal />
</keep-alive>
</transition>
<AppHeader :current-lang="currentLang" @change-lang="changeLang" /> <AppHeader :current-lang="currentLang" @change-lang="changeLang" />
<main class="app_main"> <main class="app_main">
@@ -54,7 +47,6 @@ import { useTooltipStore } from './store/tooltipStore';
import Clock from './components/App/Clock.vue'; import Clock from './components/App/Clock.vue';
import StatusIndicator from './components/App/StatusIndicator.vue'; import StatusIndicator from './components/App/StatusIndicator.vue';
import AppHeader from './components/App/AppHeader.vue'; import AppHeader from './components/App/AppHeader.vue';
import TrainModal from './components/TrainsView/TrainModal.vue';
import Tooltip from './components/Tooltip/Tooltip.vue'; import Tooltip from './components/Tooltip/Tooltip.vue';
import UpdateCard from './components/App/UpdateCard.vue'; import UpdateCard from './components/App/UpdateCard.vue';
@@ -67,7 +59,6 @@ export default defineComponent({
Clock, Clock,
StatusIndicator, StatusIndicator,
AppHeader, AppHeader,
TrainModal,
UpdateCard, UpdateCard,
Tooltip Tooltip
}, },
-1
View File
@@ -43,7 +43,6 @@ export default defineComponent({
width: 6em; width: 6em;
height: 1em; height: 1em;
margin: 0.5em 0;
.bar-fg, .bar-fg,
.bar-bg { .bar-bg {
+6 -3
View File
@@ -2,7 +2,9 @@
<div class="stock-list"> <div class="stock-list">
<ul> <ul>
<li <li
v-for="({ vehicleName, vehicleCargo, images, imagesFallbacks }, i) in thumbnailNames" v-for="(
{ vehicleName, vehicleCargo, images, imagesFallbacks, vehicleString }, i
) in thumbnailNames"
:key="i" :key="i"
> >
<div class="stock-text"> <div class="stock-text">
@@ -13,7 +15,7 @@
<span> <span>
<VehicleThumbnail <VehicleThumbnail
v-for="(thumbnailImage, imageIndex) in images" v-for="(thumbnailImage, imageIndex) in images"
:vehicle-name="vehicleName" :vehicle-name="vehicleString"
:img-name="thumbnailImage" :img-name="thumbnailImage"
:fallback-name="imagesFallbacks[imageIndex]" :fallback-name="imagesFallbacks[imageIndex]"
/> />
@@ -63,7 +65,8 @@ export default defineComponent({
images: [] as string[], images: [] as string[],
imagesFallbacks: [] as string[], imagesFallbacks: [] as string[],
vehicleName, vehicleName,
vehicleCargo vehicleCargo,
vehicleString
}; };
// Generowanie członów EN57 // Generowanie członów EN57
+1 -2
View File
@@ -5,10 +5,9 @@
:src="`https://static.spythere.eu/thumbnails/v2/${imgName}.png`" :src="`https://static.spythere.eu/thumbnails/v2/${imgName}.png`"
height="60" height="60"
loading="lazy" loading="lazy"
:data-mouseover="vehicleName" data-tooltip-type="VehiclePreviewTooltip"
:data-tooltip-content="vehicleName" :data-tooltip-content="vehicleName"
:data-load-status="imgStatus" :data-load-status="imgStatus"
data-tooltip-type="VehiclePreviewTooltip"
@error="onImageError" @error="onImageError"
@load="onImageLoad" @load="onImageLoad"
/> />
@@ -1,29 +1,28 @@
<template> <template>
<section class="daily-stats"> <section class="daily-stats">
<span :data-active="statsStatus"> <span :data-active="statsStatus">
<span class="stats-list"> <h3>
<h3> {{ $t('journal.daily-stats.title') }}
{{ $t('journal.daily-stats.title') }} <b class="text--primary">{{ new Date().toLocaleDateString($i18n.locale) }}</b>
<b class="text--primary">{{ new Date().toLocaleDateString($i18n.locale) }}</b> </h3>
</h3>
<hr class="header-separator" /> <hr class="header-separator" />
<b v-if="statsStatus == Status.Data.Loading"> <b v-if="statsStatus == Status.Data.Loading">
{{ $t('app.loading') }} {{ $t('app.loading') }}
</b> </b>
<b class="text--error" v-else-if="statsStatus == Status.Data.Error"> <b class="text--error" v-else-if="statsStatus == Status.Data.Error">
{{ $t('journal.stats-error') }} {{ $t('journal.stats-error') }}
</b> </b>
<b v-else-if="topDispatchers.length == 0"> <b v-else-if="topDispatchers.length == 0">
{{ $t('journal.daily-stats.info') }} {{ $t('journal.daily-stats.info') }}
</b> </b>
<div v-else> <div v-else>
<div v-if="stats.totalTimetables"> <ul class="stats-list">
&bull; <li v-if="stats.totalTimetables">
<i18n-t keypath="journal.daily-stats.total"> <i18n-t keypath="journal.daily-stats.total">
<template #count> <template #count>
<b class="text--primary"> <b class="text--primary">
@@ -36,10 +35,9 @@
<b class="text--primary"> {{ stats.distanceSum?.toFixed(2) }} km</b> <b class="text--primary"> {{ stats.distanceSum?.toFixed(2) }} km</b>
</template> </template>
</i18n-t> </i18n-t>
</div> </li>
<div v-if="stats.maxTimetable"> <li v-if="stats.maxTimetable">
&bull;
<i18n-t keypath="journal.daily-stats.longest"> <i18n-t keypath="journal.daily-stats.longest">
<template #id> <template #id>
<router-link :to="`/journal/timetables?search-train=%23${stats.maxTimetable.id}`"> <router-link :to="`/journal/timetables?search-train=%23${stats.maxTimetable.id}`">
@@ -60,10 +58,9 @@
<b class="text--primary">{{ stats.maxTimetable.routeDistance }} km</b> <b class="text--primary">{{ stats.maxTimetable.routeDistance }} km</b>
</template> </template>
</i18n-t> </i18n-t>
</div> </li>
<div v-if="topDispatchers.length == 1"> <li v-if="topDispatchers.length == 1">
&bull;
<i18n-t keypath="journal.daily-stats.most-active-dr"> <i18n-t keypath="journal.daily-stats.most-active-dr">
<template #dispatcher> <template #dispatcher>
<router-link <router-link
@@ -79,10 +76,9 @@
</b> </b>
</template> </template>
</i18n-t> </i18n-t>
</div> </li>
<div v-if="topDispatchers.length > 1"> <li v-if="topDispatchers.length > 1">
&bull;
<i18n-t keypath="journal.daily-stats.most-active-dr-many"> <i18n-t keypath="journal.daily-stats.most-active-dr-many">
<template #dispatchers> <template #dispatchers>
<span v-for="(disp, i) in topDispatchers" :key="i"> <span v-for="(disp, i) in topDispatchers" :key="i">
@@ -103,10 +99,9 @@
</b> </b>
</template> </template>
</i18n-t> </i18n-t>
</div> </li>
<div v-if="stats.longestDuties.length > 0"> <li v-if="stats.longestDuties.length > 0">
&bull;
<i18n-t keypath="journal.daily-stats.longest-duties"> <i18n-t keypath="journal.daily-stats.longest-duties">
<template #dispatcher> <template #dispatcher>
<router-link <router-link
@@ -122,10 +117,9 @@
{{ calculateDuration(stats.longestDuties[0].duration) }} {{ calculateDuration(stats.longestDuties[0].duration) }}
</template> </template>
</i18n-t> </i18n-t>
</div> </li>
<div v-if="stats.mostActiveDrivers.length > 0"> <li v-if="stats.mostActiveDrivers.length > 0">
&bull;
<i18n-t keypath="journal.daily-stats.most-active-driver"> <i18n-t keypath="journal.daily-stats.most-active-driver">
<template #driver> <template #driver>
<router-link <router-link
@@ -138,30 +132,30 @@
<b class="text--primary">{{ stats.mostActiveDrivers[0].distance.toFixed(2) }} km</b> <b class="text--primary">{{ stats.mostActiveDrivers[0].distance.toFixed(2) }} km</b>
</template> </template>
</i18n-t> </i18n-t>
</div> </li>
</ul>
<hr class="section-separator" /> <hr class="section-separator" />
<div class="stats-badges"> <div class="stats-badges">
<span <span
class="stat-badge" class="badge stat-badge"
v-for="key in [ v-for="key in [
'rippedSwitches', 'rippedSwitches',
'derailments', 'derailments',
'skippedStopSignals', 'skippedStopSignals',
'radioStops', 'radioStops',
'kills' 'kills'
]" ]"
:key="key" :key="key"
> >
<span>{{ $t(`journal.daily-stats.${key}`) }}</span> <span>{{ $t(`journal.daily-stats.${key}`) }}</span>
<span>{{ <span>
Object.entries(stats.globalDiff).find(([k, v]) => k == key)?.[1] || '--' {{ Object.entries(stats.globalDiff).find(([k, v]) => k == key)?.[1] || '--' }}
}}</span>
</span> </span>
</div> </span>
</div> </div>
</span> </div>
</span> </span>
</section> </section>
</template> </template>
@@ -178,7 +172,6 @@ export default defineComponent({
name: 'journal-daily-stats', name: 'journal-daily-stats',
mixins: [dateMixin], mixins: [dateMixin],
// emits: ['toggleStatsOpen'],
data() { data() {
return { return {
@@ -193,7 +186,6 @@ export default defineComponent({
activated() { activated() {
this.startFetchingDailyStats(); this.startFetchingDailyStats();
// this.$emit('toggleStatsOpen', true);
}, },
deactivated() { deactivated() {
@@ -249,14 +241,24 @@ export default defineComponent({
.daily-stats { .daily-stats {
text-align: left; text-align: left;
} }
.daily-stats > span[data-active='0'] { .daily-stats > span[data-active='0'] {
opacity: 0.75; opacity: 0.75;
} }
ul.stats-list {
list-style: disc;
padding: 0 1em;
}
.stats-list a { .stats-list a {
text-decoration: underline; text-decoration: underline;
} }
.stats-list > li {
margin: 0.25em 0;
}
.stats-badges { .stats-badges {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
@@ -16,17 +16,17 @@
<hr class="header-separator" /> <hr class="header-separator" />
<div class="info-stats"> <div class="info-stats">
<span class="stat-badge" v-if="stats.services"> <span class="badge stat-badge" v-if="stats.services">
<span>{{ $t('journal.dispatcher-stats.services-count') }}</span> <span>{{ $t('journal.dispatcher-stats.services-count') }}</span>
<span>{{ stats.services.count }}</span> <span>{{ stats.services.count }}</span>
</span> </span>
<span class="stat-badge" v-if="stats.services"> <span class="badge stat-badge" v-if="stats.services">
<span>{{ $t('journal.dispatcher-stats.service-max') }}</span> <span>{{ $t('journal.dispatcher-stats.service-max') }}</span>
<span>{{ calculateDuration(stats.services.durationMax) }}</span> <span>{{ calculateDuration(stats.services.durationMax) }}</span>
</span> </span>
<span class="stat-badge" v-if="stats.services"> <span class="badge stat-badge" v-if="stats.services">
<span>{{ $t('journal.dispatcher-stats.service-avg') }}</span> <span>{{ $t('journal.dispatcher-stats.service-avg') }}</span>
<span>{{ calculateDuration(stats.services.durationAvg) }}</span> <span>{{ calculateDuration(stats.services.durationAvg) }}</span>
</span> </span>
@@ -35,22 +35,22 @@
<hr class="section-separator" /> <hr class="section-separator" />
<div class="info-stats"> <div class="info-stats">
<span class="stat-badge" v-if="stats.issuedTimetables"> <span class="badge stat-badge" v-if="stats.issuedTimetables">
<span>{{ $t('journal.dispatcher-stats.timetables-count') }}</span> <span>{{ $t('journal.dispatcher-stats.timetables-count') }}</span>
<span>{{ stats.issuedTimetables.count }}</span> <span>{{ stats.issuedTimetables.count }}</span>
</span> </span>
<span class="stat-badge" v-if="stats.issuedTimetables"> <span class="badge stat-badge" v-if="stats.issuedTimetables">
<span>{{ $t('journal.dispatcher-stats.timetables-sum') }}</span> <span>{{ $t('journal.dispatcher-stats.timetables-sum') }}</span>
<span>{{ stats.issuedTimetables.distanceSum.toFixed(2) }}km</span> <span>{{ stats.issuedTimetables.distanceSum.toFixed(2) }}km</span>
</span> </span>
<span class="stat-badge" v-if="stats.issuedTimetables"> <span class="badge stat-badge" v-if="stats.issuedTimetables">
<span>{{ $t('journal.dispatcher-stats.timetables-max') }}</span> <span>{{ $t('journal.dispatcher-stats.timetables-max') }}</span>
<span>{{ stats.issuedTimetables.distanceMax.toFixed(2) }}km</span> <span>{{ stats.issuedTimetables.distanceMax.toFixed(2) }}km</span>
</span> </span>
<span class="stat-badge" v-if="stats.issuedTimetables"> <span class="badge stat-badge" v-if="stats.issuedTimetables">
<span>{{ $t('journal.dispatcher-stats.timetables-avg') }}</span> <span>{{ $t('journal.dispatcher-stats.timetables-avg') }}</span>
<span>{{ stats.issuedTimetables.distanceAvg.toFixed(2) }}km</span> <span>{{ stats.issuedTimetables.distanceAvg.toFixed(2) }}km</span>
</span> </span>
@@ -99,11 +99,4 @@ export default defineComponent({
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../../styles/variables.scss'; @import '../../../styles/variables.scss';
@import '../../../styles/JournalSection.scss'; @import '../../../styles/JournalSection.scss';
.journal-list {
display: flex;
flex-direction: column;
gap: 0.5em;
text-align: left;
}
</style> </style>
@@ -12,7 +12,7 @@
<hr class="header-separator" /> <hr class="header-separator" />
<div class="info-stats"> <div class="info-stats">
<span class="stat-badge"> <span class="badge stat-badge">
<span>{{ $t('journal.driver-stats.timetables') }}</span> <span>{{ $t('journal.driver-stats.timetables') }}</span>
<span <span
>{{ store.driverStatsData._count.fulfilled }} / >{{ store.driverStatsData._count.fulfilled }} /
@@ -20,17 +20,17 @@
> >
</span> </span>
<span class="stat-badge"> <span class="badge stat-badge">
<span>{{ $t('journal.driver-stats.longest-timetable') }}</span> <span>{{ $t('journal.driver-stats.longest-timetable') }}</span>
<span> {{ store.driverStatsData._max.routeDistance.toFixed(2) }}km </span> <span> {{ store.driverStatsData._max.routeDistance.toFixed(2) }}km </span>
</span> </span>
<span class="stat-badge"> <span class="badge stat-badge">
<span>{{ $t('journal.driver-stats.avg-timetable') }}</span> <span>{{ $t('journal.driver-stats.avg-timetable') }}</span>
<span> {{ store.driverStatsData._avg.routeDistance.toFixed(2) }}km </span> <span> {{ store.driverStatsData._avg.routeDistance.toFixed(2) }}km </span>
</span> </span>
<span class="stat-badge"> <span class="badge stat-badge">
<span>{{ $t('journal.driver-stats.distance') }}</span> <span>{{ $t('journal.driver-stats.distance') }}</span>
<span> <span>
{{ store.driverStatsData._sum.currentDistance.toFixed(2) }} / {{ store.driverStatsData._sum.currentDistance.toFixed(2) }} /
@@ -38,7 +38,7 @@
</span> </span>
</span> </span>
<span class="stat-badge"> <span class="badge stat-badge">
<span>{{ $t('journal.driver-stats.stations') }}</span> <span>{{ $t('journal.driver-stats.stations') }}</span>
<span> <span>
{{ store.driverStatsData._sum.confirmedStopsCount }} / {{ store.driverStatsData._sum.confirmedStopsCount }} /
@@ -17,17 +17,13 @@
</div> </div>
<div v-else> <div v-else>
<ul class="journal-list"> <transition-group name="list-anim" tag="ul" class="journal-list">
<transition-group name="list-anim"> <li v-for="timetable in timetableHistory" class="journal_item" :key="timetable.id">
<li <div class="journal_item-info">
v-for="{ timetable, showExtraInfo } in computedTimetableHistory" <!-- General -->
class="journal_item" <TimetableGeneral :timetable="timetable" />
:key="timetable.id"
@click="showExtraInfo.value = !showExtraInfo.value" <div @click="toggleExtraInfo(timetable.id)" style="cursor: pointer">
>
<div class="journal_item-info">
<!-- General -->
<TimetableGeneral :timetable="timetable" />
<!-- Route --> <!-- Route -->
<span class="item-route"> <span class="item-route">
<b>{{ timetable.route.replace('|', ' - ') }}</b> <b>{{ timetable.route.replace('|', ' - ') }}</b>
@@ -35,16 +31,23 @@
<hr /> <hr />
<!-- Stops --> <!-- Stops -->
<TimetableStops :timetable="timetable" :showExtraInfo="showExtraInfo.value" /> <TimetableStops
:timetable="timetable"
:showExtraInfo="extraInfoIndexes.includes(timetable.id)"
/>
<!-- Status --> <!-- Status -->
<TimetableStatus :timetable="timetable" /> <TimetableStatus :timetable="timetable" />
<!-- Extra -->
<TimetableDetails :timetable="timetable" :showExtraInfo="showExtraInfo.value" />
</div> </div>
</li>
</transition-group> <!-- Extra -->
</ul> <TimetableDetails
:timetable="timetable"
:showExtraInfo="extraInfoIndexes.includes(timetable.id)"
@toggle-extra-info="toggleExtraInfo"
/>
</div>
</li>
</transition-group>
<AddDataButton <AddDataButton
:list="timetableHistory" :list="timetableHistory"
@@ -57,6 +60,7 @@
</transition> </transition>
<div class="journal_warning" v-if="scrollNoMoreData">{{ $t('journal.no-further-data') }}</div> <div class="journal_warning" v-if="scrollNoMoreData">{{ $t('journal.no-further-data') }}</div>
<div class="journal_warning" v-else-if="!scrollDataLoaded"> <div class="journal_warning" v-else-if="!scrollDataLoaded">
{{ $t('journal.loading-further-data') }} {{ $t('journal.loading-further-data') }}
</div> </div>
@@ -64,7 +68,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, PropType, ref } from 'vue'; import { defineComponent, Prop, PropType, ref } from 'vue';
import Loading from '../../Global/Loading.vue'; import Loading from '../../Global/Loading.vue';
import AddDataButton from '../../Global/AddDataButton.vue'; import AddDataButton from '../../Global/AddDataButton.vue';
@@ -110,16 +114,17 @@ export default defineComponent({
data() { data() {
return { return {
Status, Status,
store: useMainStore() store: useMainStore(),
extraInfoIndexes: [] as number[]
}; };
}, },
computed: { methods: {
computedTimetableHistory() { toggleExtraInfo(id: number) {
return this.timetableHistory.map((timetable) => ({ const existingIdx = this.extraInfoIndexes.indexOf(id);
timetable,
showExtraInfo: ref(false) if (existingIdx != -1) this.extraInfoIndexes.splice(existingIdx, 1);
})); else this.extraInfoIndexes.push(id);
} }
} }
}); });
@@ -1,10 +1,19 @@
<template> <template>
<div> <div>
<div class="details-actions"> <div class="details-actions">
<button class="btn--action"> <button class="btn--action" @click="toggleExtraInfo">
<b>{{ $t('journal.stock-info') }}</b> <b>{{ $t('journal.stock-info') }}</b>
<img :src="`/images/icon-arrow-${showExtraInfo ? 'asc' : 'desc'}.svg`" alt="Arrow icon" /> <img :src="`/images/icon-arrow-${showExtraInfo ? 'asc' : 'desc'}.svg`" alt="Arrow icon" />
</button> </button>
<router-link
v-if="driverRouteLocation !== null"
class="a-button btn--action btn-timetable"
:to="driverRouteLocation"
>
<img src="/images/icon-train.svg" alt="train icon" />
<b>{{ $t('journal.timetable-online-button') }}</b>
</router-link>
</div> </div>
<div class="details-body" v-if="timetable.stockString && timetable.stockMass && showExtraInfo"> <div class="details-body" v-if="timetable.stockString && timetable.stockMass && showExtraInfo">
@@ -77,9 +86,13 @@
import { PropType, defineComponent } from 'vue'; import { PropType, defineComponent } from 'vue';
import StockList from '../../Global/StockList.vue'; import StockList from '../../Global/StockList.vue';
import { API } from '../../../typings/api'; import { API } from '../../../typings/api';
import { RouteLocationRaw } from 'vue-router';
export default defineComponent({ export default defineComponent({
components: { StockList }, components: { StockList },
emits: ['toggleExtraInfo'],
props: { props: {
showExtraInfo: { showExtraInfo: {
type: Boolean, type: Boolean,
@@ -112,12 +125,25 @@ export default defineComponent({
stockLength: Number(historyData[3]) || undefined stockLength: Number(historyData[3]) || undefined
}; };
}); });
},
driverRouteLocation(): RouteLocationRaw | null {
if (this.timetable.terminated) return null;
return {
name: 'DriverView',
query: {
trainId: `${this.timetable.driverId}|${this.timetable.trainNo}|eu`
}
}
} }
}, },
methods: { methods: {
onImageError(e: Event) { onImageError(e: Event) {
const imageEl = e.target as HTMLImageElement; const imageEl = e.target as HTMLImageElement;
imageEl.src = '/images/icon-unknown.png'; imageEl.src = '/images/icon-unknown.png';
},
toggleExtraInfo() {
this.$emit('toggleExtraInfo', this.timetable.id);
} }
} }
}); });
@@ -134,6 +160,7 @@ export default defineComponent({
.details-actions { .details-actions {
display: flex; display: flex;
gap: 0.5em;
button img { button img {
height: 1.25em; height: 1.25em;
@@ -3,13 +3,15 @@
<span class="general-train"> <span class="general-train">
<span class="text--grayed">#{{ timetable.id }}</span> <span class="text--grayed">#{{ timetable.id }}</span>
<span class="badges" v-if="timetable.skr || timetable.twr"> <span class="train-badge twr" v-if="timetable.twr" :title="$t('general.TWR')">TWR</span>
<span class="train-badge twr" v-if="timetable.twr" :title="$t('general.TWR')">TWR</span> <span class="train-badge skr" v-if="timetable.skr" :title="$t('general.SKR')">SKR</span>
<span class="train-badge skr" v-if="timetable.skr" :title="$t('general.SKR')">SKR</span>
</span>
<span> <span>
<strong class="text--primary"> <strong
data-tooltip-type="BaseTooltip"
:data-tooltip-content="getCategoryExplanation(timetable.trainCategoryCode)"
class="text--primary tooltip-help"
>
{{ timetable.trainCategoryCode }} {{ timetable.trainCategoryCode }}
</strong> </strong>
<strong>&nbsp;{{ timetable.trainNo }}</strong> <strong>&nbsp;{{ timetable.trainNo }}</strong>
@@ -23,18 +25,19 @@
{{ timetable.driverLevel < 2 ? 'L' : `${timetable.driverLevel}` }} {{ timetable.driverLevel < 2 ? 'L' : `${timetable.driverLevel}` }}
</strong> </strong>
<strong <router-link
v-if="apiStore.donatorsData.includes(timetable.driverName)" v-if="apiStore.donatorsData.includes(timetable.driverName)"
class="text--donator" class="text--donator"
data-tooltip-type="DonatorTooltip" data-tooltip-type="DonatorTooltip"
:data-tooltip-content="$t('donations.driver-message')" :data-tooltip-content="$t('donations.driver-message')"
:to="`/journal/timetables?search-driver=${timetable.driverName}`"
> >
{{ timetable.driverName }} <strong>{{ timetable.driverName }}</strong>
</strong> </router-link>
<strong v-else> <router-link v-else :to="`/journal/timetables?search-driver=${timetable.driverName}`">
{{ timetable.driverName }} <strong>{{ timetable.driverName }}</strong>
</strong> </router-link>
</span> </span>
<span class="general-time"> <span class="general-time">
@@ -62,15 +65,6 @@
: `${$t('journal.timetable-abandoned')} ${localeTime(timetable.endDate, $i18n.locale)}` : `${$t('journal.timetable-abandoned')} ${localeTime(timetable.endDate, $i18n.locale)}`
}} }}
</b> </b>
<button
v-if="timetable.terminated == false"
class="btn--action btn-timetable"
@click.stop="showTimetable(timetable, $event.currentTarget)"
>
<img src="/images/icon-train.svg" alt="train icon" />
<b>{{ $t('journal.timetable-online-button') }}</b>
</button>
</span> </span>
</div> </div>
</template> </template>
@@ -80,12 +74,12 @@ import { PropType, defineComponent } from 'vue';
import { API } from '../../../typings/api'; import { API } from '../../../typings/api';
import dateMixin from '../../../mixins/dateMixin'; import dateMixin from '../../../mixins/dateMixin';
import modalTrainMixin from '../../../mixins/modalTrainMixin';
import styleMixin from '../../../mixins/styleMixin'; import styleMixin from '../../../mixins/styleMixin';
import { useApiStore } from '../../../store/apiStore'; import { useApiStore } from '../../../store/apiStore';
import trainCategoryMixin from '../../../mixins/trainCategoryMixin';
export default defineComponent({ export default defineComponent({
mixins: [dateMixin, modalTrainMixin, styleMixin], mixins: [dateMixin, styleMixin, trainCategoryMixin],
data() { data() {
return { return {
@@ -98,14 +92,6 @@ export default defineComponent({
type: Object as PropType<API.TimetableHistory.Data>, type: Object as PropType<API.TimetableHistory.Data>,
required: true required: true
} }
},
methods: {
showTimetable(timetable: API.TimetableHistory.Data, target: EventTarget | null) {
if (timetable?.terminated) return;
this.selectModalTrainById(`${timetable.driverName}${timetable.trainNo}`, target);
}
} }
}); });
</script> </script>
@@ -132,7 +118,6 @@ export default defineComponent({
gap: 0.25em; gap: 0.25em;
cursor: pointer; cursor: pointer;
line-height: 2;
} }
.general-time { .general-time {
@@ -175,6 +160,7 @@ export default defineComponent({
@include smallScreen { @include smallScreen {
.item-general { .item-general {
flex-direction: column;
justify-content: center; justify-content: center;
} }
} }
@@ -15,14 +15,13 @@
<li <li
v-for="{ train, status } in stationTrains" v-for="{ train, status } in stationTrains"
class="badge user" class="badge user"
tabindex="0"
:key="train.id" :key="train.id"
:data-status="status" :data-status="status"
@click.prevent="selectModalTrain(train, $event.currentTarget)"
@keydown.enter="selectModalTrain(train, $event.currentTarget)"
> >
<span class="user_train">{{ train.trainNo }}</span> <router-link :to="train.driverRouteLocation" class="a-block">
<span class="user_name">{{ train.driverName }}</span> <span class="user_train">{{ train.trainNo }}</span>
<span class="user_name">{{ train.driverName }}</span>
</router-link>
</li> </li>
</transition-group> </transition-group>
</section> </section>
@@ -30,14 +29,13 @@
<script lang="ts"> <script lang="ts">
import { PropType, defineComponent } from 'vue'; import { PropType, defineComponent } from 'vue';
import modalTrainMixin from '../../../mixins/modalTrainMixin';
import routerMixin from '../../../mixins/routerMixin'; import routerMixin from '../../../mixins/routerMixin';
import { ActiveScenery, Station, StopStatus } from '../../../typings/common'; import { ActiveScenery, Station } from '../../../typings/common';
import { getTrainStopStatus } from '../utils'; import { getTrainStopStatus } from '../utils';
import { useMainStore } from '../../../store/mainStore'; import { useMainStore } from '../../../store/mainStore';
export default defineComponent({ export default defineComponent({
mixins: [routerMixin, modalTrainMixin], mixins: [routerMixin],
props: { props: {
onlineScenery: { onlineScenery: {
@@ -99,38 +97,27 @@ ul {
} }
.user { .user {
cursor: pointer;
&_train {
color: black;
background-color: $no-timetable;
transition: background-color 200ms;
-ms-transition: background-color 200ms;
-webkit-transition: background-color 200ms;
}
&[data-status='no-timetable'] .user_train { &[data-status='no-timetable'] .user_train {
background-color: $no-timetable; background-color: $no-timetable;
} }
&[data-status='departed'] > &_train { &[data-status='departed'] .user_train {
background-color: $departed; background-color: $departed;
} }
&[data-status='stopped'] > &_train { &[data-status='stopped'] .user_train {
background-color: $stopped; background-color: $stopped;
} }
&[data-status='online'] > &_train { &[data-status='online'] .user_train {
background-color: $online; background-color: $online;
} }
&[data-status='terminated'] > &_train { &[data-status='terminated'] .user_train {
background-color: $terminated; background-color: $terminated;
} }
&[data-status='disconnected'] > &_train { &[data-status='disconnected'] .user_train {
background-color: $disconnected; background-color: $disconnected;
} }
@@ -139,6 +126,16 @@ ul {
pointer-events: none; pointer-events: none;
} }
} }
.user_train {
color: black;
background-color: $no-timetable;
transition: background-color 200ms;
-ms-transition: background-color 200ms;
-webkit-transition: background-color 200ms;
}
.users-anim { .users-anim {
&-move, &-move,
&-enter-active, &-enter-active,
+81 -42
View File
@@ -14,6 +14,10 @@
</span> </span>
<span class="header_links" v-if="station"> <span class="header_links" v-if="station">
<a :href="pragotronHref" target="_blank" :title="$t('scenery.pragotron-link')">
<img src="/images/icon-pragotron.svg" alt="icon-pragotron" />
</a>
<a :href="tabliceZbiorczeHref" target="_blank" :title="$t('scenery.tablice-link')"> <a :href="tabliceZbiorczeHref" target="_blank" :title="$t('scenery.tablice-link')">
<img src="/images/icon-tablice.ico" alt="icon-tablice" /> <img src="/images/icon-tablice.ico" alt="icon-tablice" />
</a> </a>
@@ -21,18 +25,15 @@
</h3> </h3>
<div class="timetable-checkpoints" v-if="station?.generalInfo?.checkpoints"> <div class="timetable-checkpoints" v-if="station?.generalInfo?.checkpoints">
<span v-for="(cp, i) in station.generalInfo.checkpoints" :key="i"> <template v-for="(ch, i) in station.generalInfo.checkpoints" :key="i">
{{ (i > 0 && '&bull;') || '' }} <template v-if="i > 0">&bull;</template>
<router-link
<button class="checkpoint-item"
:key="cp" :class="{ current: chosenCheckpoint === ch }"
class="checkpoint_item" :to="`/scenery?station=${station.name}&checkpoint=${ch}`"
:class="{ current: chosenCheckpoint === cp }" >{{ ch }}</router-link
@click="setCheckpoint(cp)"
> >
{{ cp }} </template>
</button>
</span>
</div> </div>
</div> </div>
@@ -62,29 +63,36 @@
{{ $t('scenery.no-timetables') }} {{ $t('scenery.no-timetables') }}
</div> </div>
<div <router-link
class="timetable-item" class="timetable-item a-block"
v-else v-else
v-for="(row, i) in sceneryTimetables" v-for="(row, i) in sceneryTimetables"
:key="row.train.id + i" :key="row.train.id + i"
tabindex="0" tabindex="0"
@click.prevent.stop="selectModalTrain(row.train, $event.currentTarget)" :to="row.train.driverRouteLocation"
@keydown.enter.prevent="selectModalTrain(row.train, $event.currentTarget)"
> >
<span class="timetable-general"> <span class="timetable-general">
<span class="general-info"> <span class="general-info">
<span class="info-number"> <div class="info-train">
<strong>{{ row.train.timetableData!.category }}</strong> <b
{{ row.train.trainNo }} data-tooltip-type="BaseTooltip"
:data-tooltip-content="getCategoryExplanation(row.train.timetableData!.category)"
<span v-if="row.checkpointStop.comments" :title="row.checkpointStop.comments"> class="text--primary tooltip-help"
>
{{ row.train.timetableData!.category }}
</b>
<span>&nbsp;</span>
<b>{{ row.train.trainNo }}</b>
<span>&nbsp;&bull;&nbsp;</span>
<span>{{ row.train.driverName }}</span>
<span
v-if="row.checkpointStop.comments"
data-tooltip-type="BaseTooltip"
:data-tooltip-content="row.checkpointStop.comments"
>
<img src="/images/icon-warning.svg" /> <img src="/images/icon-warning.svg" />
</span> </span>
</span> </div>
&nbsp;|&nbsp;
<span>
{{ row.train.driverName }}
</span>
<div class="info-route"> <div class="info-route">
<strong>{{ row.train.timetableData!.route.replace('|', ' - ') }}</strong> <strong>{{ row.train.timetableData!.route.replace('|', ' - ') }}</strong>
@@ -160,7 +168,7 @@
</span> </span>
</span> </span>
</span> </span>
</div> </router-link>
</transition-group> </transition-group>
</div> </div>
</section> </section>
@@ -173,12 +181,12 @@ import { useRoute } from 'vue-router';
import Loading from '../Global/Loading.vue'; import Loading from '../Global/Loading.vue';
import dateMixin from '../../mixins/dateMixin'; import dateMixin from '../../mixins/dateMixin';
import routerMixin from '../../mixins/routerMixin'; import routerMixin from '../../mixins/routerMixin';
import trainCategoryMixin from '../../mixins/trainCategoryMixin';
import { useMainStore } from '../../store/mainStore'; import { useMainStore } from '../../store/mainStore';
import modalTrainMixin from '../../mixins/modalTrainMixin';
import ScheduledTrainStatus from './ScheduledTrainStatus.vue';
import { useApiStore } from '../../store/apiStore'; import { useApiStore } from '../../store/apiStore';
import { ActiveScenery, Station } from '../../typings/common'; import ScheduledTrainStatus from './ScheduledTrainStatus.vue';
import { SceneryTimetableRow } from './typings'; import { SceneryTimetableRow } from './typings';
import { ActiveScenery, Station } from '../../typings/common';
import { getTrainStopStatus, stopStatusPriority } from './utils'; import { getTrainStopStatus, stopStatusPriority } from './utils';
export default defineComponent({ export default defineComponent({
@@ -186,7 +194,7 @@ export default defineComponent({
components: { Loading, ScheduledTrainStatus }, components: { Loading, ScheduledTrainStatus },
mixins: [dateMixin, routerMixin, modalTrainMixin], mixins: [dateMixin, routerMixin, trainCategoryMixin],
props: { props: {
station: { station: {
@@ -205,6 +213,12 @@ export default defineComponent({
this.loadSelectedOption(); this.loadSelectedOption();
}, },
watch: {
currentURL() {
this.loadSelectedOption();
}
},
setup(props) { setup(props) {
const route = useRoute(); const route = useRoute();
const currentURL = computed(() => `${location.origin}${route.fullPath}`); const currentURL = computed(() => `${location.origin}${route.fullPath}`);
@@ -235,6 +249,13 @@ export default defineComponent({
return url; return url;
}, },
pragotronHref() {
let url = `https://pragotron-td2.web.app/board?name=${this.station!.name}&region=${this.mainStore.region.id}`;
if (this.chosenCheckpoint) url += `&checkpoint=${this.chosenCheckpoint}`;
return url;
},
sceneryTimetables(): SceneryTimetableRow[] { sceneryTimetables(): SceneryTimetableRow[] {
if (!this.onlineScenery) return []; if (!this.onlineScenery) return [];
@@ -243,7 +264,7 @@ export default defineComponent({
return this.onlineScenery.scheduledTrains return this.onlineScenery.scheduledTrains
.filter( .filter(
(ct) => (ct) =>
ct.timetablePathElement.stationName == sceneryName && // ct.timetablePathElement.stationName == sceneryName &&
ct.train.region == this.mainStore.region.id && ct.train.region == this.mainStore.region.id &&
this.chosenCheckpoint && this.chosenCheckpoint &&
ct.checkpointStop.stopNameRAW.toLowerCase() == this.chosenCheckpoint.toLowerCase() ct.checkpointStop.stopNameRAW.toLowerCase() == this.chosenCheckpoint.toLowerCase()
@@ -286,7 +307,19 @@ export default defineComponent({
loadSelectedOption() { loadSelectedOption() {
if (!this.station) return; if (!this.station) return;
this.chosenCheckpoint = this.station.generalInfo?.checkpoints[0] ?? this.station.name; 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;
}, },
setCheckpoint(cp: string) { setCheckpoint(cp: string) {
@@ -356,7 +389,6 @@ export default defineComponent({
background: #353535; background: #353535;
cursor: pointer;
z-index: 10; z-index: 10;
&.empty { &.empty {
@@ -386,30 +418,35 @@ export default defineComponent({
} }
} }
.timetable-list {
position: relative;
}
.timetable-checkpoints { .timetable-checkpoints {
display: flex; display: flex;
justify-content: center; justify-content: center;
gap: 0.5em;
flex-wrap: wrap; flex-wrap: wrap;
font-size: 1.1em; font-size: 1.1em;
margin-top: 0.5em; margin-top: 0.5em;
}
button.checkpoint_item { .checkpoint-item {
color: #aaa; color: #aaa;
display: inline; display: inline;
&:hover {
color: white;
} }
.checkpoint_item.current { &.current {
font-weight: bold; font-weight: bold;
color: $accentCol; color: $accentCol;
} }
} }
.timetable-list {
position: relative;
}
.general-info { .general-info {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
@@ -423,7 +460,9 @@ export default defineComponent({
} }
img { img {
width: 1.1em; height: 0.9em;
vertical-align: middle;
margin: 0 0.25em;
} }
} }
@@ -6,30 +6,11 @@
<p>[F] {{ $t('options.filters') }}</p> <p>[F] {{ $t('options.filters') }}</p>
<span class="active-indicator" v-if="changedFilters.length != 0"></span> <span class="active-indicator" v-if="changedFilters.length != 0"></span>
</button> </button>
<label for="scenery-search">
<input
id="scenery-search"
list="sceneries"
:placeholder="$t('sceneries.scenery-search')"
@focus="preventKeyDown = true"
@blur="preventKeyDown = false"
v-model="chosenSearchScenery"
/>
<datalist id="sceneries">
<option
v-for="scenery in sortedStationList"
:key="scenery.name"
:value="scenery.name"
></option>
</datalist>
</label>
</div> </div>
<transition name="card-anim"> <transition name="card-anim">
<div class="card" v-if="isVisible" tabindex="0" ref="cardRef" @keydown.r="resetFilters"> <div class="card" v-if="isVisible" ref="cardRef" @keydown.r="resetFilters">
<div class="card_content" @scroll="onScroll" ref="cardContentRef"> <div class="card_content" tabindex="0" @scroll="onScroll" ref="cardContentRef">
<div class="card_title flex">{{ $t('filters.title') }}</div> <div class="card_title flex">{{ $t('filters.title') }}</div>
<p class="card_info" v-html="$t('filters.desc')"></p> <p class="card_info" v-html="$t('filters.desc')"></p>
@@ -40,6 +21,31 @@
<template v-else>{{ $t('filters.no-changed-filters') }}</template> <template v-else>{{ $t('filters.no-changed-filters') }}</template>
</div> </div>
<section class="card_sceneries-search">
<h3 class="section-header">{{ $t('filters.sceneries-search') }}</h3>
<datalist id="sceneries">
<option
v-for="scenery in sortedStationList"
:key="scenery.name"
:value="scenery.name"
></option>
</datalist>
<form action="javascript:void(0);" @submit="handleSceneriesInput">
<input
v-model="chosenSearchScenery"
id="scenery-search"
list="sceneries"
:placeholder="$t('filters.sceneries-placeholder')"
@focus="preventKeyDown = true"
@blur="preventKeyDown = false"
/>
<button class="btn--action">{{ $t('filters.search-button-title') }}</button>
</form>
</section>
<section class="card_options"> <section class="card_options">
<div <div
class="option-section" class="option-section"
@@ -57,16 +63,15 @@
<div class="section-filters"> <div class="section-filters">
<label <label
v-for="filterKey in sectionFilters" v-for="filterKey in sectionFilters"
@click="() => (filters[filterKey] = !filters[filterKey])"
@dblclick="setSingleSectionFilter(sectionKey, filterKey)" @dblclick="setSingleSectionFilter(sectionKey, filterKey)"
:for="filterKey"
> >
<input <input
type="checkbox"
:checked="filters[filterKey]" :checked="filters[filterKey]"
v-model="filters[filterKey]" v-model="filters[filterKey]"
type="checkbox"
:class="sectionKey" :class="sectionKey"
:name="filterKey" :name="filterKey"
:id="filterKey"
/> />
<span> <span>
{{ $t(`filters.${filterKey}`) }} {{ $t(`filters.${filterKey}`) }}
@@ -111,7 +116,7 @@
@blur="preventKeyDown = false" @blur="preventKeyDown = false"
/> />
<button class="btn--action">{{ $t('filters.authors-button-title') }}</button> <button class="btn--action">{{ $t('filters.search-button-title') }}</button>
</form> </form>
</section> </section>
@@ -269,20 +274,11 @@ export default defineComponent({
}, },
watch: { watch: {
chosenSearchScenery(value: string) {
const chosenStation = this.store.stationList.find(({ name }) => name == value);
if (chosenStation) {
this.$router.push(`/scenery?station=${chosenStation.name.replace(/ /g, '_')}`);
this.chosenSearchScenery = '';
}
},
isVisible(value: boolean) { isVisible(value: boolean) {
this.$nextTick(() => { this.$nextTick(() => {
if (value) { if (value) {
(this.$refs['cardRef'] as HTMLDivElement).focus();
(this.$refs['cardContentRef'] as HTMLDivElement).scrollTop = this.scrollTop; (this.$refs['cardContentRef'] as HTMLDivElement).scrollTop = this.scrollTop;
(this.$refs['cardContentRef'] as HTMLDivElement).focus();
} }
}); });
} }
@@ -300,7 +296,18 @@ export default defineComponent({
handleAuthorsInput() { handleAuthorsInput() {
this.filters['authors'] = this.authors; this.filters['authors'] = this.authors;
// if (this.saveOptions) StorageManager.setStringValue('authors', target.value); },
handleSceneriesInput() {
const chosenStation = this.store.stationList.find(
({ name }) => name == this.chosenSearchScenery
);
if (chosenStation) {
this.$router.push(`/scenery?station=${chosenStation.name.replace(/ /g, '_')}`);
this.chosenSearchScenery = '';
this.isVisible = false;
}
}, },
subHour() { subHour() {
@@ -329,6 +336,8 @@ export default defineComponent({
}, },
resetFilters() { resetFilters() {
if (this.preventKeyDown) return;
// Reset local model values // Reset local model values
this.minimumHours = 0; this.minimumHours = 0;
this.authors = ''; this.authors = '';
@@ -353,7 +362,8 @@ export default defineComponent({
setSingleSectionFilter(sectionKey: StationFilterSection, chosenKey: string) { setSingleSectionFilter(sectionKey: StationFilterSection, chosenKey: string) {
filtersSections[sectionKey].forEach((filterKey) => { filtersSections[sectionKey].forEach((filterKey) => {
if (filterKey != chosenKey) this.filters[filterKey] = initFilters[filterKey]; if (typeof this.filters[filterKey] === 'boolean')
this.filters[filterKey] = filterKey != chosenKey;
}); });
}, },
@@ -382,6 +392,7 @@ h3.section-header {
.card { .card {
display: grid; display: grid;
grid-template-rows: 1fr auto; grid-template-rows: 1fr auto;
padding: 1px;
} }
.card_info { .card_info {
@@ -451,8 +462,12 @@ h3.section-header {
} }
} }
.card_authors-search { .card_authors-search,
.card_sceneries-search {
margin: 1em 0; margin: 1em 0;
display: flex;
flex-direction: column;
align-items: center;
form { form {
display: flex; display: flex;
@@ -642,10 +657,6 @@ h3.section-header {
} }
@include smallScreen { @include smallScreen {
.card_controls > button.card-button > p {
display: none;
}
.slider { .slider {
flex-wrap: wrap; flex-wrap: wrap;
justify-content: center; justify-content: center;
+93 -68
View File
@@ -1,56 +1,70 @@
<template> <template>
<div class="station-stats"> <div
<div class="separator" /> class="dropdown"
@keydown.esc="showDropdown = false"
v-click-outside="() => (showDropdown = false)"
>
<div class="bg" v-if="showDropdown" @click="showDropdown = false"></div>
<div class="stats-row"> <button class="filter-button btn--filled btn--image" @click="toggleDropdown" ref="button">
<div> <img src="/images/icon-stats.svg" alt="Open filters icon" />
<span <!-- {{ $t('train-stats.stats-button') }} -->
>{{ $t('station-stats.u-factor') }} <span>STATYSTYKI</span>
<a </button>
href="https://td2.info.pl/dyskusje/wspolczynnik-ugla-czy-to-ma-sens/msg81011/#msg81011"
target="_blank"
:data-tooltip="$t('station-stats.u-factor-tooltip')"
>(?)</a
>:
</span>
<b class="u-factor" :style="calculateFactorStyle()"> <transition name="dropdown-anim">
{{ uFactor.toFixed(2) }} <div class="dropdown_wrapper" v-if="showDropdown">
</b> <div>
<h1 class="text--primary">
<img src="/images/icon-stats.svg" alt="Open filters icon" />
{{ $t('train-stats.title') }}
</h1>
<hr style="margin: 0.5em 0" />
<ul class="stats-list">
<li>
<span>
{{ $t('station-stats.u-factor') }}
<a
href="https://td2.info.pl/dyskusje/wspolczynnik-ugla-czy-to-ma-sens/msg81011/#msg81011"
target="_blank"
:data-tooltip="$t('station-stats.u-factor-tooltip')"
>(?)</a
>:
</span>
<b class="u-factor" :style="calculateFactorStyle()">
{{ uFactor.toFixed(2) }}
</b>
</li>
<li>
{{ $t('station-stats.avg-timetable-count') }}
<b>{{ avgTimetableCount.toFixed(2) }}</b>
</li>
<li>
{{ $t('station-stats.single-track-count') }}
<b>{{ trackCount.oneWay }}</b> (<b>{{ trackCount.oneWayElectric }} </b>)
</li>
<li>
{{ $t('station-stats.double-track-count') }}
<b>{{ trackCount.twoWay }}</b>
(<b>{{ trackCount.twoWayElectric }} </b>)
</li>
<li>
{{ $t('station-stats.cross-sceneries') }}
<b>{{ trackCount.crossTrack }}</b> (<b>{{ trackCount.crossTrackElectric }} </b>)
</li>
<li>
{{ $t('station-stats.open-spawns') }} <b>{{ spawnCount.passenger }}</b> - PAS /
<b>{{ spawnCount.freight }}</b> - TOW / <b>{{ spawnCount.loco }}</b> - LUZ /
<b>{{ spawnCount.all }}</b> - ALL
</li>
</ul>
</div>
<div tabindex="0" @focus="() => (showDropdown = false)"></div>
</div> </div>
</transition>
<div>
&bull;
{{ $t('station-stats.avg-timetable-count') }}
<b>{{ avgTimetableCount.toFixed(2) }}</b>
</div>
<div>
&bull;
{{ $t('station-stats.single-track-count') }}
<b>{{ trackCount.oneWay }}</b> (<b>{{ trackCount.oneWayElectric }} </b>)
</div>
<div>
&bull;
{{ $t('station-stats.double-track-count') }}
<b>{{ trackCount.twoWay }}</b>
(<b>{{ trackCount.twoWayElectric }} </b>)
</div>
<div>
&bull; {{ $t('station-stats.cross-sceneries') }} <b>{{ trackCount.crossTrack }}</b> (<b
>{{ trackCount.crossTrackElectric }} </b
>)
</div>
<div>
&bull;
{{ $t('station-stats.open-spawns') }} <b>{{ spawnCount.passenger }}</b> - PAS /
<b>{{ spawnCount.freight }}</b> - TOW / <b>{{ spawnCount.loco }}</b> - LUZ /
<b>{{ spawnCount.all }}</b> - ALL
</div>
</div>
</div> </div>
</template> </template>
@@ -61,11 +75,16 @@ import { useMainStore } from '../../store/mainStore';
export default defineComponent({ export default defineComponent({
data() { data() {
return { return {
mainStore: useMainStore() mainStore: useMainStore(),
showDropdown: false
}; };
}, },
methods: { methods: {
toggleDropdown() {
this.showDropdown = !this.showDropdown;
},
calculateFactorStyle() { calculateFactorStyle() {
if (this.uFactor == 0) return ''; if (this.uFactor == 0) return '';
@@ -171,25 +190,15 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.separator { @import '../../styles/dropdown.scss';
width: 100%; @import '../../styles/badge.scss';
height: 2px;
h1 img {
vertical-align: text-bottom;
}
h3 {
margin: 0.5em 0; margin: 0.5em 0;
background-color: #aaa;
}
.station-stats {
text-align: center;
color: #ddd;
}
.stats-row {
display: flex;
justify-content: center;
flex-wrap: wrap;
text-wrap: pretty;
gap: 0.25em;
margin-top: 0.25em;
} }
.u-factor { .u-factor {
@@ -209,4 +218,20 @@ export default defineComponent({
color: rgb(22, 245, 22); color: rgb(22, 245, 22);
} }
} }
ul.stats-list {
list-style: disc;
padding-left: 1em;
margin-top: 1em;
& > li {
margin: 0.25em 0;
}
}
@include smallScreen {
.filter-button span {
display: none;
}
}
</style> </style>
+18 -23
View File
@@ -52,15 +52,14 @@
</thead> </thead>
<tbody> <tbody>
<tr <router-link
v-for="station in filteredStationList" v-for="station in filteredStationList"
:class="{ 'last-selected': lastSelectedStationName == station.name }" class="a-row"
role="row"
:key="station.name" :key="station.name"
@click.left="setScenery(station.name)" @click.right.prevent="openForumSite($event, station.generalInfo?.url)"
@click.right="openForumSite($event, station.generalInfo?.url)" @keydown.space.prevent="openForumSite($event, station.generalInfo?.url)"
@keydown.enter="setScenery(station.name)" :to="getSceneryRoute(station)"
@keydown.space="openForumSite($event, station.generalInfo?.url)"
tabindex="0"
> >
<td class="station-name" :class="station.generalInfo?.availability"> <td class="station-name" :class="station.generalInfo?.availability">
<b v-if="station.generalInfo?.project" style="color: salmon">{{ <b v-if="station.generalInfo?.project" style="color: salmon">{{
@@ -121,7 +120,7 @@
<span v-if="station.onlineInfo?.dispatcherName"> <span v-if="station.onlineInfo?.dispatcherName">
<b <b
v-if="apiStore.donatorsData.includes(station.onlineInfo.dispatcherName)" v-if="apiStore.donatorsData.includes(station.onlineInfo.dispatcherName)"
@click.stop="openDonationCard" @click.prevent="openDonationCard"
data-tooltip-type="DonatorTooltip" data-tooltip-type="DonatorTooltip"
:data-tooltip-content="$t('donations.dispatcher-message')" :data-tooltip-content="$t('donations.dispatcher-message')"
> >
@@ -294,7 +293,7 @@
> >
{{ station.onlineInfo?.scheduledTrainCount.confirmed ?? '-' }} {{ station.onlineInfo?.scheduledTrainCount.confirmed ?? '-' }}
</td> </td>
</tr> </router-link>
</tbody> </tbody>
</table> </table>
@@ -319,7 +318,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 { Status } from '../../typings/common'; import { Station, Status } 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';
@@ -334,7 +333,6 @@ export default defineComponent({
data: () => ({ data: () => ({
headIconsIds, headIconsIds,
headIds, headIds,
lastSelectedStationName: '',
getChangedFilters getChangedFilters
}), }),
@@ -364,21 +362,16 @@ export default defineComponent({
}, },
methods: { methods: {
setScenery(name: string) { getSceneryRoute(station: Station) {
const station = this.filteredStationList.find((station) => station.name === name); // TODO: Hide tooltips when navigating away
if (!station) return; return {
this.lastSelectedStationName = station.name;
this.tooltipStore.hide();
this.$router.push({
name: 'SceneryView', name: 'SceneryView',
query: { query: {
station: station.name.replaceAll(' ', '_'), station: station.name,
region: this.$route.query.region || undefined region: this.$route.query.region || undefined
} }
}); };
}, },
openDonationCard(e: Event) { openDonationCard(e: Event) {
@@ -414,7 +407,7 @@ export default defineComponent({
$rowCol: #424242; $rowCol: #424242;
.station_table { .station_table {
height: 80vh; height: 90vh;
max-height: 2000px; max-height: 2000px;
min-height: 700px; min-height: 700px;
overflow: auto; overflow: auto;
@@ -503,8 +496,10 @@ table {
} }
} }
tr { tr,
.a-row {
background-color: $rowCol; background-color: $rowCol;
vertical-align: middle;
&:nth-child(even) { &:nth-child(even) {
background-color: lighten($rowCol, 5); background-color: lighten($rowCol, 5);
+1
View File
@@ -23,6 +23,7 @@ export default defineComponent({
justify-content: center; justify-content: center;
align-items: center; align-items: center;
gap: 0.5em; gap: 0.5em;
white-space: pre-line;
padding: 0.25em 0.5em; padding: 0.25em 0.5em;
border-radius: 0.25em; border-radius: 0.25em;
+28 -14
View File
@@ -1,9 +1,13 @@
<template> <template>
<span <span
class="stop-label" class="stop-label"
:data-minor="stop.isSBL || (stop.nameRaw.endsWith(', po.') && !stop.duration)" :data-minor="stop.isSBL || (stop.nameRaw.endsWith(', po') && !stop.duration)"
> >
<span class="name" v-html="stop.nameHtml"></span> <router-link v-if="/(, podg$|<strong>)/.test(stop.nameHtml)" :to="sceneryHref">
<span class="name" v-html="stop.nameHtml"></span>
</router-link>
<span v-else class="name" v-html="stop.nameHtml"></span>
<span <span
v-if="stop.position != 'begin'" v-if="stop.position != 'begin'"
@@ -76,6 +80,12 @@ export default defineComponent({
type: Object as PropType<TrainScheduleStop>, type: Object as PropType<TrainScheduleStop>,
required: true required: true
} }
},
computed: {
sceneryHref() {
return `/scenery?station=${this.stop.sceneryName}&checkpoint=${this.stop.nameRaw}`;
}
} }
}); });
</script> </script>
@@ -97,18 +107,6 @@ s {
flex-wrap: wrap; flex-wrap: wrap;
align-items: center; align-items: center;
&[data-minor='true'] {
.date {
display: none;
}
.name {
background: none;
color: #aaa;
padding: 0;
}
}
.name { .name {
background: $stopNameClr; background: $stopNameClr;
border-radius: 0.5em 0 0 0.5em; border-radius: 0.5em 0 0 0.5em;
@@ -131,6 +129,18 @@ s {
} }
} }
&[data-minor='true'] {
.date {
display: none;
}
.name {
background: none;
color: #aaa;
padding: 0;
}
}
.stop { .stop {
&[data-stop-types='ph'], &[data-stop-types='ph'],
&[data-stop-types='ph-pm'], &[data-stop-types='ph-pm'],
@@ -146,6 +156,10 @@ s {
} }
} }
.stop-label > a {
z-index: 0;
}
.stop .arrival { .stop .arrival {
&[data-status='confirmed'][data-status-delayed='true'] { &[data-status='confirmed'][data-status-delayed='true'] {
span { span {
+50 -78
View File
@@ -2,47 +2,39 @@
<div class="train-info" :data-extended="extended"> <div class="train-info" :data-extended="extended">
<section class="train-general"> <section class="train-general">
<div class="general-top-bar"> <div class="general-top-bar">
<div> <div class="top-bar-header">
<b class="warning-timeout" v-if="train.isTimeout" :title="$t('trains.timeout')">?</b> <b class="warning-timeout" v-if="train.isTimeout" :title="$t('trains.timeout')">?</b>
<span class="timetable-id" v-if="train.timetableData"> <span class="timetable-id" v-if="train.timetableData">
#{{ train.timetableData.timetableId }} #{{ train.timetableData.timetableId }}
</span> </span>
<span <span class="train-badge twr" v-if="train.timetableData?.TWR" :title="$t('general.TWR')">
class="timetable-warnings" TWR
v-if="train.timetableData?.TWR || train.timetableData?.SKR" </span>
>
<span <span class="train-badge skr" v-if="train.timetableData?.SKR" :title="$t('general.SKR')">
class="train-badge twr" SKR
v-if="train.timetableData?.TWR"
:title="$t('general.TWR')"
>
TWR
</span>
<span
class="train-badge skr"
v-if="train.timetableData?.SKR"
:title="$t('general.SKR')"
>
SKR
</span>
</span> </span>
<strong>
<span v-if="train.timetableData" class="text--primary"
>{{ train.timetableData.category }}&nbsp;</span
>
<span class="train-number">{{ train.trainNo }}</span>
</strong>
<span>&bull;</span>
<b <b
class="level-badge driver" v-if="train.timetableData"
:style="calculateExpStyle(train.driverLevel, train.isSupporter)" data-tooltip-type="BaseTooltip"
:data-tooltip-content="getCategoryExplanation(train.timetableData.category)"
class="text--primary tooltip-help"
> >
{{ train.driverLevel < 2 ? 'L' : `${train.driverLevel}` }} {{ train.timetableData.category }}
</b> </b>
<b class="train-number">{{ train.trainNo }}</b>
<span>&bull;</span>
<div class="train-driver"> <div class="train-driver">
<b
class="level-badge driver"
:style="calculateExpStyle(train.driverLevel, train.isSupporter)"
>
{{ train.driverLevel < 2 ? 'L' : `${train.driverLevel}` }}
</b>
<b <b
v-if="apiStore.donatorsData.includes(train.driverName)" v-if="apiStore.donatorsData.includes(train.driverName)"
data-tooltip-type="DonatorTooltip" data-tooltip-type="DonatorTooltip"
@@ -55,19 +47,6 @@
<span v-else>{{ train.driverName }}</span> <span v-else>{{ train.driverName }}</span>
</div> </div>
</div> </div>
<div v-if="extended">
<button class="btn-timetable btn--image btn--action" @click="navigateToJournal">
<img src="/images/icon-train.svg" alt="train icon" />
<span>
{{ $t('trains.journal-button') }}
</span>
</button>
<button class="btn-exit btn--image btn--action" @click="closeModal">
<img src="/images/icon-exit.svg" alt="modal exit icon" />
</button>
</div>
</div> </div>
<div class="general-timetable" v-if="train.timetableData"> <div class="general-timetable" v-if="train.timetableData">
@@ -88,7 +67,7 @@
<div class="general-stops" v-if="train.timetableData"> <div class="general-stops" v-if="train.timetableData">
<span v-if="train.timetableData.followingStops.length > 2"> <span v-if="train.timetableData.followingStops.length > 2">
{{ $t('trains.via-title') }} {{ $t('trains.via-title') }}
<span v-html="displayStopList(train.timetableData.followingStops)"></span> <span v-html="getTrainStopsHtml(train.timetableData.followingStops)"></span>
</span> </span>
</div> </div>
@@ -97,21 +76,22 @@
<ProgressBar :progressPercent="confirmedPercentage(train.timetableData.followingStops)" /> <ProgressBar :progressPercent="confirmedPercentage(train.timetableData.followingStops)" />
<span class="progress-distance"> <span class="progress-distance">
&nbsp; {{ currentDistance(train.timetableData.followingStops) }} km / <span>{{ currentDistance(train.timetableData.followingStops) }} km</span>
<span class="text--primary"> {{ train.timetableData.routeDistance }} km </span> <span>/</span>
| <span class="text--primary">{{ train.timetableData.routeDistance }} km </span>
<span>|</span>
<span v-html="currentDelay(train.timetableData.followingStops)"></span> <span v-html="currentDelay(train.timetableData.followingStops)"></span>
</span> </span>
</div> </div>
<div class="status-badges"> <div class="status-badges">
<div v-if="!train.currentStationHash" class="train-badge offline"> <div v-if="!train.currentStationHash" class="train-badge offline">
<img src="/images/icon-offline.svg" alt="" /> <img src="/images/icon-offline.svg" alt="offline train icon" />
{{ $t('trains.scenery-offline') }} {{ $t('trains.scenery-offline') }}
</div> </div>
<div v-if="!train.online" class="train-badge offline"> <div v-if="!train.online" class="train-badge offline">
<img src="/images/icon-offline.svg" alt="" /> <img src="/images/icon-offline.svg" alt="offline train icon" />
Offline {{ lastSeenMessage(train.lastSeen) }} Offline {{ lastSeenMessage(train.lastSeen) }}
</div> </div>
</div> </div>
@@ -179,11 +159,11 @@ import ProgressBar from '../Global/ProgressBar.vue';
import { useMainStore } from '../../store/mainStore'; import { useMainStore } from '../../store/mainStore';
import { useApiStore } from '../../store/apiStore'; import { useApiStore } from '../../store/apiStore';
import StockList from '../Global/StockList.vue'; import StockList from '../Global/StockList.vue';
import modalTrainMixin from '../../mixins/modalTrainMixin';
import { Train } from '../../typings/common'; import { Train } from '../../typings/common';
import trainCategoryMixin from '../../mixins/trainCategoryMixin';
export default defineComponent({ export default defineComponent({
mixins: [trainInfoMixin, styleMixin, modalTrainMixin], mixins: [trainInfoMixin, styleMixin, trainCategoryMixin],
components: { ProgressBar, StockList }, components: { ProgressBar, StockList },
props: { props: {
@@ -212,19 +192,14 @@ export default defineComponent({
return Math.min(vehicleSpeed, acc); return Math.min(vehicleSpeed, acc);
}, 300); }, 300);
} },
}, journalRouteLocation() {
return {
methods: {
navigateToJournal() {
this.$router.push({
path: '/journal/timetables', path: '/journal/timetables',
query: { query: {
'search-driver': this.train.driverName 'search-driver': this.train.driverName
} }
}); }
this.closeModal();
} }
} }
}); });
@@ -266,6 +241,12 @@ export default defineComponent({
gap: 0.5em; gap: 0.5em;
} }
.train-driver {
display: flex;
align-items: center;
gap: 0.25em;
}
.train-driver img { .train-driver img {
max-height: 20px; max-height: 20px;
vertical-align: text-bottom; vertical-align: text-bottom;
@@ -297,24 +278,15 @@ export default defineComponent({
.general-top-bar { .general-top-bar {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
flex-wrap: wrap;
gap: 0.5em; gap: 0.5em;
& > div {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0.25em;
}
} }
.btn-timetable { .top-bar-header {
padding: 0.25em; display: flex;
} align-items: center;
flex-wrap: wrap;
.btn-exit { gap: 0.25em;
padding: 0.25em;
} }
.general-status { .general-status {
@@ -361,10 +333,14 @@ export default defineComponent({
display: flex; display: flex;
align-items: center; align-items: center;
flex-wrap: wrap; flex-wrap: wrap;
gap: 0.5em;
padding: 0.5em 0;
} }
.progress-distance { .progress-distance {
margin-right: 0.25em; display: flex;
flex-wrap: wrap;
gap: 0.25em;
} }
.timetable-warnings { .timetable-warnings {
@@ -377,9 +353,5 @@ export default defineComponent({
grid-template-columns: 1fr; grid-template-columns: 1fr;
gap: 1em 0; gap: 1em 0;
} }
.btn-timetable > span {
display: none;
}
} }
</style> </style>
-103
View File
@@ -1,103 +0,0 @@
<template>
<div class="train-modal" v-if="chosenTrain" @keydown.esc="closeModal">
<div class="modal-background" @click="closeModal"></div>
<div class="modal-content" ref="content" tabindex="0">
<TrainInfo :train="chosenTrain" :extended="true" ref="trainInfo" />
<TrainSchedule :train="chosenTrain" tabindex="0" />
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import modalTrainMixin from '../../mixins/modalTrainMixin';
import TrainInfo from './TrainInfo.vue';
import TrainSchedule from './TrainSchedule.vue';
import { Train } from '../../typings/common';
export default defineComponent({
components: { TrainInfo, TrainSchedule },
mixins: [modalTrainMixin],
computed: {
chosenTrain() {
return this.store.trainList.find((train) => train.modalId == this.store.chosenModalTrainId);
}
},
watch: {
chosenTrain(train: Train | undefined) {
this.$nextTick(() => {
if (train) {
document.body.classList.add('no-scroll');
const contentEl = this.$refs['content'] as HTMLElement;
contentEl.focus();
} else {
(this.store.modalLastClickedTarget as any)?.focus();
setTimeout(() => {
document.body.classList.remove('no-scroll');
}, 90);
}
});
}
}
});
</script>
<style lang="scss" scoped>
@import '../../styles/responsive.scss';
.train-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
color: white;
z-index: 200;
display: flex;
justify-content: center;
align-items: flex-start;
text-align: left;
}
.modal-background {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
cursor: pointer;
background-color: rgba(0, 0, 0, 0.55);
}
.modal-content {
position: relative;
overflow-y: scroll;
width: 95vw;
max-height: 95vh;
max-height: 95dvh;
margin-top: 1em;
background-color: #1a1a1a;
box-shadow: 0 0 15px 10px #0e0e0e;
}
@include midScreen {
.exit {
margin: 0.5em;
img {
width: 1.75rem;
}
}
}
</style>
+6 -2
View File
@@ -15,12 +15,14 @@
<div class="search_content"> <div class="search_content">
<div class="search-box"> <div class="search-box">
<input <input
v-model="searchedTrain"
class="search-input" class="search-input"
ref="initFocusedElement" ref="initFocusedElement"
id="train-search"
name="train-search"
@focus="preventKeyDown = true" @focus="preventKeyDown = true"
@blur="preventKeyDown = false" @blur="preventKeyDown = false"
:placeholder="$t(`options.search-train`)" :placeholder="$t(`options.search-train`)"
v-model="searchedTrain"
/> />
<button class="search-exit"> <button class="search-exit">
<img <img
@@ -33,11 +35,13 @@
<div class="search-box"> <div class="search-box">
<input <input
v-model="searchedDriver"
class="search-input" class="search-input"
id="driver-search"
name="driver-search"
@focus="preventKeyDown = true" @focus="preventKeyDown = true"
@blur="preventKeyDown = false" @blur="preventKeyDown = false"
:placeholder="$t(`options.search-driver`)" :placeholder="$t(`options.search-driver`)"
v-model="searchedDriver"
/> />
<button class="search-exit"> <button class="search-exit">
<img <img
+23 -14
View File
@@ -60,7 +60,7 @@
v-else v-else
src="/images/icon-we4a.png" src="/images/icon-we4a.png"
:title="$t('trains.we4a-tooltip')" :title="$t('trains.we4a-tooltip')"
width="12" width="10"
/> />
</span> </span>
</div> </div>
@@ -84,7 +84,7 @@
v-else v-else
src="/images/icon-we4a.png" src="/images/icon-we4a.png"
:title="$t('trains.we4a-tooltip')" :title="$t('trains.we4a-tooltip')"
width="12" width="10"
/> />
</span> </span>
</div> </div>
@@ -165,20 +165,18 @@ export default defineComponent({
computed: { computed: {
scheduleStops(): TrainScheduleStop[] { scheduleStops(): TrainScheduleStop[] {
let currentSceneryIndex = 0; if (!this.train.timetableData) return [];
const { timetablePath } = this.train.timetableData;
let currentPathIndex = 0;
return ( return (
this.train.timetableData?.followingStops.map((stop, i, arr) => { this.train.timetableData?.followingStops.map((stop, i, arr) => {
const isExternal = const isExternal =
i > 0 && i < arr.length - 1 &&
stop.arrivalLine != null && stop.departureLine === timetablePath[currentPathIndex].departureRouteExt;
(stop.arrivalLine != arr[i - 1].departureLine ||
(stop.arrivalLine == arr[i - 1].departureLine &&
!/-|_|(^it\d+)|(^sbl)/gi.test(stop.arrivalLine)));
if (isExternal) currentSceneryIndex++; const sceneryName = timetablePath[currentPathIndex].stationName;
const sceneryName = this.train.timetableData!.sceneryNames[currentSceneryIndex];
const sceneryInfo = this.apiStore.sceneryData.find((st) => st.name == sceneryName); const sceneryInfo = this.apiStore.sceneryData.find((st) => st.name == sceneryName);
const arrivalLineInfo = sceneryInfo?.routesInfo.find( const arrivalLineInfo = sceneryInfo?.routesInfo.find(
@@ -189,6 +187,8 @@ export default defineComponent({
(r) => r.routeName == stop.departureLine (r) => r.routeName == stop.departureLine
); );
if (isExternal) currentPathIndex++;
return { return {
nameHtml: stop.stopName, nameHtml: stop.stopName,
nameRaw: stop.stopNameRAW, nameRaw: stop.stopNameRAW,
@@ -249,7 +249,7 @@ export default defineComponent({
i < this.train.timetableData!.followingStops.length; i < this.train.timetableData!.followingStops.length;
i++ i++
) { ) {
if (/po\.|sbl/gi.test(this.train.timetableData!.followingStops[i].stopNameRAW)) if (/(, po$|sbl|, pe$)/gi.test(this.train.timetableData!.followingStops[i].stopNameRAW))
activeMinorStopList.push(i); activeMinorStopList.push(i);
else break; else break;
} }
@@ -286,7 +286,7 @@ $blinkAnim: 0.5s ease-in-out alternate infinite blink;
} }
.train-schedule { .train-schedule {
padding: 0 1em; padding: 1em;
} }
.schedule-wrapper { .schedule-wrapper {
@@ -523,8 +523,17 @@ $blinkAnim: 0.5s ease-in-out alternate infinite blink;
} }
.scenery-route { .scenery-route {
display: flex;
gap: 0.25em;
span:nth-child(2) {
display: flex;
gap: 0.25em;
align-items: center;
}
img { img {
vertical-align: middle; width: 1em;
} }
} }
+13 -38
View File
@@ -1,5 +1,9 @@
<template> <template>
<div class="dropdown" @keydown.esc="showOptions = false" @focusout="showOptions = false"> <div
class="dropdown"
@keydown.esc="showOptions = false"
v-click-outside="() => (showOptions = false)"
>
<div class="bg" v-if="showOptions" @click="showOptions = false"></div> <div class="bg" v-if="showOptions" @click="showOptions = false"></div>
<button class="filter-button btn--filled btn--image" @click="toggleShowOptions" ref="button"> <button class="filter-button btn--filled btn--image" @click="toggleShowOptions" ref="button">
@@ -19,21 +23,21 @@
<div v-if="apiStore.dataStatuses.connection == Status.Loaded && regionTrains.length > 0"> <div v-if="apiStore.dataStatuses.connection == Status.Loaded && regionTrains.length > 0">
<div class="top-list general"> <div class="top-list general">
<transition-group tag="ul" name="stats-anim"> <transition-group tag="ul" name="stats-anim">
<li class="badge" key="timetable-count"> <li class="badge stat-badge" key="timetable-count">
<span>{{ $t('train-stats.timetable-count') }}</span> <span>{{ $t('train-stats.timetable-count') }}</span>
<span> <span>
<b>{{ regionTrainsWithTT.length }}</b> <b>{{ regionTrainsWithTT.length }}</b>
</span> </span>
</li> </li>
<li class="badge" key="avg-speed"> <li class="badge stat-badge" key="avg-speed">
<span>{{ $t('train-stats.avg-speed') }}</span> <span>{{ $t('train-stats.avg-speed') }}</span>
<span> <span>
<b>{{ stats.avgSpeed.toFixed(1) }} km/h</b> <b>{{ stats.avgSpeed.toFixed(1) }} km/h</b>
</span> </span>
</li> </li>
<li class="badge" key="avg-distance"> <li class="badge stat-badge" key="avg-distance">
<span>{{ $t('train-stats.avg-timetable') }}</span> <span>{{ $t('train-stats.avg-timetable') }}</span>
<span> <span>
<b>{{ stats.avgDistance.toFixed(1) }} km</b> <b>{{ stats.avgDistance.toFixed(1) }} km</b>
@@ -46,7 +50,7 @@
<h3>{{ $t('train-stats.top-categories') }}</h3> <h3>{{ $t('train-stats.top-categories') }}</h3>
<transition-group tag="ul" name="stats-anim"> <transition-group tag="ul" name="stats-anim">
<li class="badge" v-for="top in stats.topCategories" :key="top.name"> <li class="badge stat-badge" v-for="top in stats.topCategories" :key="top.name">
<span>{{ top.name }}</span> <span>{{ top.name }}</span>
<span>{{ top.count }}</span> <span>{{ top.count }}</span>
</li> </li>
@@ -61,7 +65,7 @@
<h3>{{ $t('train-stats.top-vehicles') }}</h3> <h3>{{ $t('train-stats.top-vehicles') }}</h3>
<transition-group tag="ul" name="stats-anim"> <transition-group tag="ul" name="stats-anim">
<li class="badge" v-for="top in stats.topVehicles" :key="top.name"> <li class="badge stat-badge" v-for="top in stats.topVehicles" :key="top.name">
<span>{{ top.name }}</span> <span>{{ top.name }}</span>
<span>{{ top.count }}</span> <span>{{ top.count }}</span>
</li> </li>
@@ -76,7 +80,7 @@
<h3>{{ $t('train-stats.top-units') }}</h3> <h3>{{ $t('train-stats.top-units') }}</h3>
<transition-group tag="ul" name="stats-anim"> <transition-group tag="ul" name="stats-anim">
<li class="badge" v-for="top in stats.topUnits.slice(0, 7)" :key="top.name"> <li class="badge stat-badge" v-for="top in stats.topUnits.slice(0, 7)" :key="top.name">
<span>{{ top.name }}</span> <span>{{ top.name }}</span>
<span>{{ top.count }}</span> <span>{{ top.count }}</span>
</li> </li>
@@ -95,6 +99,8 @@
<div class="no-data" v-else> <div class="no-data" v-else>
{{ $t('train-stats.no-stats') }} {{ $t('train-stats.no-stats') }}
</div> </div>
<div tabindex="0" @focus="() => (showOptions = false)"></div>
</div> </div>
</transition> </transition>
</div> </div>
@@ -236,43 +242,12 @@ h3 {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 0.5em; gap: 0.5em;
// @include smallScreen {
// justify-content: center;
// }
}
.badge {
margin: 0;
& > span:first-child {
background-color: $accentCol;
color: black;
}
} }
.dropdown_wrapper { .dropdown_wrapper {
max-width: 600px; max-width: 600px;
} }
.stats-anim {
&-move,
&-enter-active,
&-leave-active {
transition: all 250ms ease;
}
&-enter-from,
&-leave-to {
opacity: 0;
transform: translateX(5px);
}
&-leave-active {
position: absolute;
}
}
@include smallScreen { @include smallScreen {
h1, h1,
.no-data { .no-data {
+3 -9
View File
@@ -17,11 +17,10 @@
class="train-row" class="train-row"
v-for="train in trains" v-for="train in trains"
:key="train.id" :key="train.id"
tabindex="0"
@click.stop="selectModalTrain(train, $event.currentTarget)"
@keydown.enter="selectModalTrain(train, $event.currentTarget)"
> >
<TrainInfo :train="train" :extended="false" /> <router-link class="a-block" :to="train.driverRouteLocation">
<TrainInfo :train="train" :extended="false" />
</router-link>
</li> </li>
</transition-group> </transition-group>
</div> </div>
@@ -30,7 +29,6 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, inject, PropType, Ref } from 'vue'; import { defineComponent, inject, PropType, Ref } from 'vue';
import modalTrainMixin from '../../mixins/modalTrainMixin';
import { useMainStore } from '../../store/mainStore'; import { useMainStore } from '../../store/mainStore';
import Loading from '../Global/Loading.vue'; import Loading from '../Global/Loading.vue';
import TrainInfo from './TrainInfo.vue'; import TrainInfo from './TrainInfo.vue';
@@ -47,8 +45,6 @@ export default defineComponent({
} }
}, },
mixins: [modalTrainMixin],
setup() { setup() {
const store = useMainStore(); const store = useMainStore();
const apiStore = useApiStore(); const apiStore = useApiStore();
@@ -107,7 +103,5 @@ li.train-row {
background-color: var(--clr-secondary); background-color: var(--clr-secondary);
margin-bottom: 1em; margin-bottom: 1em;
width: 100%; width: 100%;
cursor: pointer;
} }
</style> </style>
+54 -4
View File
@@ -48,7 +48,48 @@
"footer": { "footer": {
"discord": "Stacjownik Discord server" "discord": "Stacjownik Discord server"
}, },
"categories": {
"EI": "domestic express",
"EC": "international express",
"EN": "domestic night express",
"MP": "intervoivodeship bullet",
"MO": "intervoivodeship regio",
"MM": "international bullet",
"MH": "intervoivodeship night bullet",
"RP": "voivodeship bullet",
"RM": "international voivodeship regio",
"RO": "voivodeship regio",
"RA": "voivodeship regio (urban)",
"PW": "empty passenger",
"PX": "empty passenger test drive",
"TC": "international freight (intermodal)",
"TG": "international freight (organized cargo)",
"TR": "international freight (unorganized cargo)",
"TD": "domestic freight (intermodal)",
"TM": "domestic freight (organized cargo)",
"TN": "domestic freight (unorganized cargo)",
"TK": "freight (for stations & sidings)",
"TS": "empty freight test drive",
"TH": "locomotive rolling stock (over 3 vehicles)",
"LT": "freight locomotive only",
"LP": "passenger locomotive only",
"LS": "shunting locomotive only",
"LZ": "shunting locomotive only",
"ZN": "inspection / diagnostic type",
"ZU": "other maintenance type",
"ZG": "emergency (deprecated category)",
"E": "electric loco",
"J": "EMU",
"S": "diesel loco",
"M": "DMU"
},
"vehicle-preview": { "vehicle-preview": {
"loading": "Loading preview...", "loading": "Loading preview...",
"error": "Oops! The vehicle preview seems to be missing! :/" "error": "Oops! The vehicle preview seems to be missing! :/"
@@ -123,7 +164,7 @@
"search-train": "Train no.", "search-train": "Train no.",
"search-driver": "Driver name", "search-driver": "Driver name",
"search-dispatcher": "Dispatcher name", "search-dispatcher": "Dispatcher name",
"search-station": "Scenery name", "search-station": "Scenery name / #",
"search-author": "Timetable author name", "search-author": "Timetable author name",
"search-issuedFrom": "Issuing scenery name", "search-issuedFrom": "Issuing scenery name",
"search-via": "Via scenery name", "search-via": "Via scenery name",
@@ -252,9 +293,11 @@
"minTwoWay": "MIN. OTHER DOUBLE TRACK ROUTES" "minTwoWay": "MIN. OTHER DOUBLE TRACK ROUTES"
}, },
"sceneries-search": "SCENERY SEARCH:",
"sceneries-placeholder": "Enter scenery name...",
"authors-search": "SEARCH BY AUTHOR NAME (other filters apply):", "authors-search": "SEARCH BY AUTHOR NAME (other filters apply):",
"authors-placeholder": "Enter the author nickname...", "authors-placeholder": "Enter the author nickname...",
"authors-button-title": "Search", "search-button-title": "SEARCH",
"minimum-hours-title": "SHOW ONLY SCENERIES UNTIL:", "minimum-hours-title": "SHOW ONLY SCENERIES UNTIL:",
"now": "NOW", "now": "NOW",
@@ -360,7 +403,14 @@
"scenery-offline": "Offline ride", "scenery-offline": "Offline ride",
"timeout": "An error occured while trying to refresh SWDR timetable data!", "timeout": "An error occured while trying to refresh SWDR timetable data!",
"journal-button": "DRIVER'S JOURNAL" "driver-journal-link": "DRIVER JOURNAL",
"driver-return-link": "GO BACK",
"driver-not-found-header": "Train not found! :/",
"driver-not-found-desc-1": "This train has already been terminated or it's offline.",
"driver-not-found-desc-2": "You can browse timetable history in the",
"driver-not-found-journal": "TIMETABLES JOURNAL",
"driver-not-found-return": "GO BACK TO THE MAIN SITE"
}, },
"train-stats": { "train-stats": {
"stats-button": "STATISTICS", "stats-button": "STATISTICS",
@@ -512,7 +562,7 @@
"forum-topic": "Official {name} forum topic", "forum-topic": "Official {name} forum topic",
"pragotron-link": "Timetable pallet board (beta)", "pragotron-link": "Timetable pallet board",
"tablice-link": "Timetable summary board (by Thundo)", "tablice-link": "Timetable summary board (by Thundo)",
"bottom-info": "Show full history in the Journal tab" "bottom-info": "Show full history in the Journal tab"
+56 -5
View File
@@ -45,6 +45,48 @@
"footer": { "footer": {
"discord": "Serwer Discord Stacjownika" "discord": "Serwer Discord Stacjownika"
}, },
"categories": {
"EI": "ekspres krajowy",
"EC": "ekspres międzynarodowy",
"EN": "ekspres krajowy nocny",
"MP": "międzywojewódzki pospieszny",
"MO": "międzywojewódzki osobowy",
"MM": "międzynarodowy pospieszny",
"MH": "międzywojewódzki pospieszny (nocny)",
"RP": "wojewódzki pospieszny",
"RO": "wojewódzki osobowy",
"RM": "wojewódzki osobowy międzynarodowy",
"RA": "wojewódzki osobowy algomeracyjny",
"PW": "pasażerski próżny - służbowy",
"PX": "pasażerski próżny próbny",
"TC": "towarowy międzynarodowy intermodalny",
"TG": "towarowy międzynarodowy masowy",
"TR": "towarowy międzynarodowy niemasowy",
"TD": "towarowy krajowy intermodalny",
"TM": "towarowy krajowy masowy",
"TN": "towarowy krajowy niemasowy",
"TK": "towarowy zdawczy",
"TS": "towarowy próżny próbny",
"TH": "skład lokomotyw (powyżej 3 pojazdów)",
"LT": "lokomotywa towarowa luzem",
"LP": "lokomotywa pasażerska luzem",
"LS": "lokomotywa manewrowa luzem",
"LZ": "lokomotywa dla poc. utrzymaniowo-naprawczych",
"ZN": "inspekcyjny / diagnostyczny",
"ZU": "inny utrzymaniowy",
"ZG": "ratunkowy (kat. wycofana)",
"E": "elektrowóz",
"J": "EZT",
"S": "spalinowóz",
"M": "SZT"
},
"vehicle-preview": { "vehicle-preview": {
"loading": "Ładowanie podglądu...", "loading": "Ładowanie podglądu...",
"error": "Ups! Nie znaleziono podglądu pojazdu! :/" "error": "Ups! Nie znaleziono podglądu pojazdu! :/"
@@ -119,7 +161,7 @@
"search-train": "Nr pociągu / #", "search-train": "Nr pociągu / #",
"search-driver": "Nick maszynisty", "search-driver": "Nick maszynisty",
"search-dispatcher": "Nick dyżurnego", "search-dispatcher": "Nick dyżurnego",
"search-station": "Nazwa scenerii", "search-station": "Nazwa scenerii / #",
"search-author": "Nick autora rozkładu jazdy", "search-author": "Nick autora rozkładu jazdy",
"search-issuedFrom": "Sceneria początkowa", "search-issuedFrom": "Sceneria początkowa",
"search-via": "Przez scenerię", "search-via": "Przez scenerię",
@@ -249,9 +291,11 @@
"minTwoWay": "SZLAKI DWUTOROWE NIEZELEKTR. (MINIMUM)" "minTwoWay": "SZLAKI DWUTOROWE NIEZELEKTR. (MINIMUM)"
}, },
"authors-search": "SZUKAJ AUTORA (uwzględnia inne filtry):", "sceneries-search": "WYSZUKAJ SCENERIĘ:",
"sceneries-placeholder": "Wpisz nazwę scenerii...",
"authors-search": "WYSZUKAJ AUTORA (uwzględnia inne filtry):",
"authors-placeholder": "Wpisz nick autora...", "authors-placeholder": "Wpisz nick autora...",
"authors-button-title": "Szukaj", "search-button-title": "SZUKAJ",
"minimum-hours-title": "POKAŻ TYLKO SCENERIE DOSTĘPNE MINIMUM DO:", "minimum-hours-title": "POKAŻ TYLKO SCENERIE DOSTĘPNE MINIMUM DO:",
"now": "TERAZ", "now": "TERAZ",
"hour": " godz.", "hour": " godz.",
@@ -345,7 +389,14 @@
"timeout": "Wystąpił problem z aktualizacją rozkładów jazdy z SWDR", "timeout": "Wystąpił problem z aktualizacją rozkładów jazdy z SWDR",
"journal-button": "DZIENNIK MASZYNISTY" "driver-journal-link": "DZIENNIK MASZYNISTY",
"driver-return-link": "POWRÓT",
"driver-not-found-header": "Nie znaleziono pociągu! :/",
"driver-not-found-desc-1": "Ten pociąg prawdopodobnie zakończył już swój bieg lub jest offline.",
"driver-not-found-desc-2": "Historię rozkładów jazdy możesz przejrzeć w",
"driver-not-found-journal": "DZIENNIKU RJ",
"driver-not-found-return": "WRÓĆ NA STRONĘ GŁÓWNĄ"
}, },
"train-stats": { "train-stats": {
"stats-button": "STATYSTYKI", "stats-button": "STATYSTYKI",
@@ -495,7 +546,7 @@
"forum-topic": "Oficjalny wątek scenerii {name}", "forum-topic": "Oficjalny wątek scenerii {name}",
"pragotron-link": "Paletowa tablica informacyjna (beta)", "pragotron-link": "Paletowa tablica informacyjna",
"tablice-link": "Tablica informacyjna zbiorcza (autorstwa Thundo)", "tablice-link": "Tablica informacyjna zbiorcza (autorstwa Thundo)",
"bottom-info": "Pokaż pełną historię w zakładce Dziennika" "bottom-info": "Pokaż pełną historię w zakładce Dziennika"
-30
View File
@@ -1,30 +0,0 @@
import { defineComponent } from 'vue';
import { useMainStore } from '../store/mainStore';
import { useTooltipStore } from '../store/tooltipStore';
import { Train } from '../typings/common';
export default defineComponent({
data() {
return {
store: useMainStore(),
tooltipStore: useTooltipStore()
};
},
methods: {
selectModalTrain(train: Train, target?: EventTarget | null) {
this.store.chosenModalTrainId = train.modalId;
if (target) this.store.modalLastClickedTarget = target;
},
selectModalTrainById(modalId: string, target?: EventTarget | null) {
this.store.chosenModalTrainId = modalId;
if (target) this.store.modalLastClickedTarget = target;
},
closeModal() {
this.store.chosenModalTrainId = undefined;
this.tooltipStore.hide();
}
}
});
+12
View File
@@ -0,0 +1,12 @@
import { defineComponent } from 'vue';
export default defineComponent({
methods: {
getCategoryExplanation(categoryCode: string) {
const categoryKey = categoryCode.slice(0, 2);
const vehicleTypeKey = categoryCode.slice(-1);
return `${this.$t('categories.' + categoryKey)}\n(${this.$t('categories.' + vehicleTypeKey)})`;
}
}
});
+3 -3
View File
@@ -75,18 +75,18 @@ export default defineComponent({
return positionString.charAt(0).toUpperCase() + positionString.slice(1); return positionString.charAt(0).toUpperCase() + positionString.slice(1);
}, },
displayStopList(stops: TrainStop[]): string | undefined { getTrainStopsHtml(stops: TrainStop[]): string {
if (!stops) return ''; if (!stops) return '';
return stops return stops
.reduce((acc: string[], stop: TrainStop, i: number) => { .reduce((acc: string[], stop: TrainStop, i: number) => {
if (stop.stopType.includes('ph') && !stop.stopNameRAW.includes('po.')) if (stop.stopType.includes('ph'))
acc.push( acc.push(
`<strong style='color:${stop.confirmed ? 'springgreen' : 'white'}'>${ `<strong style='color:${stop.confirmed ? 'springgreen' : 'white'}'>${
stop.stopName stop.stopName
}</strong>` }</strong>`
); );
else if (i > 0 && i < stops.length - 1 && !/po\.|sbl/gi.test(stop.stopNameRAW)) else if (i > 0 && i < stops.length - 1 && !/(, po$|sbl)/gi.test(stop.stopNameRAW))
acc.push( acc.push(
`<span style='color:${stop.confirmed ? 'springgreen' : 'lightgray'}'>${ `<span style='color:${stop.confirmed ? 'springgreen' : 'lightgray'}'>${
stop.stopName stop.stopName
+20 -1
View File
@@ -20,6 +20,15 @@ const routes: Array<RouteRecordRaw> = [
region: route.query.region region: route.query.region
}) })
}, },
{
path: '/driver',
name: 'DriverView',
component: () => import('../views/DriverView.vue'),
props: (route) => ({
trainId: route.query.trainId,
modalId: route.query.modalId
})
},
{ {
path: '/scenery', path: '/scenery',
name: 'SceneryView', name: 'SceneryView',
@@ -57,7 +66,11 @@ const routes: Array<RouteRecordRaw> = [
const router = createRouter({ const router = createRouter({
scrollBehavior(to, from, savedPosition) { scrollBehavior(to, from, savedPosition) {
if (to.name == 'SceneryView' && from.name !== to.name && from.query['view'] === undefined) if (
(to.name == 'SceneryView' || to.name == 'DriverView') &&
from.name !== to.name &&
from.query['view'] === undefined
)
return { el: `.app_main`, top: -15 }; return { el: `.app_main`, top: -15 };
if (savedPosition) return savedPosition; if (savedPosition) return savedPosition;
@@ -66,4 +79,10 @@ const router = createRouter({
routes routes
}); });
router.beforeEach((to, from, next) => {
next((vm) => {
(vm as any)['xd'] = 'xd';
});
});
export default router; export default router;
+21 -16
View File
@@ -50,15 +50,6 @@ export const useMainStore = defineStore('mainStore', {
const timetable = train.timetable; const timetable = train.timetable;
const sceneryNames =
train.timetable?.sceneries?.map(
(sceneryHash) =>
apiStore.activeData?.activeSceneries?.find((st) => st.stationHash === sceneryHash)
?.stationName ??
apiStore.sceneryData.find((sd) => sd.hash === sceneryHash)?.name ??
sceneryHash
) ?? [];
const trainObj = { const trainObj = {
id: train.id, id: train.id,
modalId: `${train.driverName}${train.trainNo}`, // simplified id for train modal modalId: `${train.driverName}${train.trainNo}`, // simplified id for train modal
@@ -86,6 +77,13 @@ export const useMainStore = defineStore('mainStore', {
isSupporter: train.driverIsSupporter, isSupporter: train.driverIsSupporter,
driverLevel: train.driverLevel, driverLevel: train.driverLevel,
driverRouteLocation: {
name: 'DriverView',
query: {
trainId: train.id
}
},
timetableData: timetable timetableData: timetable
? { ? {
timetableId: timetable.timetableId, timetableId: timetable.timetableId,
@@ -96,7 +94,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,
sceneryNames: sceneryNames.reverse(), // sceneryNames: sceneryNames.reverse(),
timetablePath: timetable.path.split(';').map((pathElementString) => { timetablePath: timetable.path.split(';').map((pathElementString) => {
const [arrival, station, departure] = pathElementString.split(','); const [arrival, station, departure] = pathElementString.split(',');
@@ -104,7 +102,7 @@ export const useMainStore = defineStore('mainStore', {
arrivalRouteExt: arrival, arrivalRouteExt: arrival,
departureRouteExt: departure, departureRouteExt: departure,
stationName: station.split(' ').slice(0, -1).join(' '), stationName: station.split(' ').slice(0, -1).join(' '),
stationHash: station.split(' ').slice(-1).join(' ') stationHash: station.split(' ').slice(-1).join(' ').replace('.sc', '')
}; };
}) })
} }
@@ -169,12 +167,15 @@ export const useMainStore = defineStore('mainStore', {
const offlineActiveSceneries = this.trainList.reduce((acc, train) => { const offlineActiveSceneries = this.trainList.reduce((acc, train) => {
if (!train.timetableData) return acc; if (!train.timetableData) return acc;
train.timetableData.sceneryNames.forEach((name) => { train.timetableData.timetablePath.forEach((p) => {
if ( if (
acc.findIndex((v) => v.name == name && v.region == train.region) != -1 || acc.findIndex(
(v) =>
(v.name == p.stationName || v.hash == p.stationHash) && v.region == train.region
) != -1 ||
apiStore.activeData?.activeSceneries?.findIndex( apiStore.activeData?.activeSceneries?.findIndex(
(sc) => (sc) =>
sc.stationName === name && (sc.stationName == p.stationName || sc.stationHash == p.stationHash) &&
sc.region == train.region && sc.region == train.region &&
Date.now() - sc.lastSeen < 1000 * 60 * 2 Date.now() - sc.lastSeen < 1000 * 60 * 2
) != -1 ) != -1
@@ -182,7 +183,7 @@ export const useMainStore = defineStore('mainStore', {
return acc; return acc;
acc.push({ acc.push({
name: name, name: p.stationName,
hash: '', hash: '',
region: train.region, region: train.region,
maxUsers: 0, maxUsers: 0,
@@ -281,7 +282,11 @@ export const useMainStore = defineStore('mainStore', {
if (!scheduledTrains) return; if (!scheduledTrains) return;
scheduledTrains.forEach(({ train, checkpointStop, timetablePathElement, ...v }) => { scheduledTrains.forEach(({ train, checkpointStop, timetablePathElement, ...v }) => {
if (scenery.name != timetablePathElement.stationName) return; if (
scenery.name != timetablePathElement.stationName &&
scenery.hash != timetablePathElement.stationHash
)
return;
scenery.scheduledTrains.push({ train, checkpointStop, timetablePathElement, ...v }); scenery.scheduledTrains.push({ train, checkpointStop, timetablePathElement, ...v });
+8 -6
View File
@@ -1,6 +1,14 @@
@import 'responsive.scss'; @import 'responsive.scss';
@import 'animations.scss'; @import 'animations.scss';
.journal-list {
display: flex;
flex-direction: column;
gap: 0.5em;
text-align: left;
margin-bottom: 0.5em;
}
.list_wrapper { .list_wrapper {
overflow-y: auto; overflow-y: auto;
height: 90vh; height: 90vh;
@@ -38,16 +46,10 @@
} }
} }
.journal_item {
cursor: pointer;
}
.journal_item, .journal_item,
.journal_warning { .journal_warning {
background-color: #1a1a1a; background-color: #1a1a1a;
padding: 1em; padding: 1em;
margin-bottom: 1em;
cursor: pointer;
} }
.journal_top-bar { .journal_top-bar {
-16
View File
@@ -33,22 +33,6 @@ hr.section-separator {
gap: 0.5em; gap: 0.5em;
} }
.stat-badge {
display: flex;
span {
background-color: $accentCol;
color: black;
font-weight: bold;
padding: 0.2em 0.5em;
}
span:first-child {
background-color: #333;
color: white;
}
}
@include smallScreen { @include smallScreen {
.journal-stats { .journal-stats {
text-align: center; text-align: center;
+13 -3
View File
@@ -1,3 +1,5 @@
@import 'variables.scss';
.badge { .badge {
font-weight: 600; font-weight: 600;
@@ -78,14 +80,12 @@
.train-badge { .train-badge {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.5em; gap: 0.3em;
padding: 0.1em 0.3em; padding: 0.1em 0.3em;
border-radius: 0.2em; border-radius: 0.2em;
font-weight: bold; font-weight: bold;
font-size: 0.9em;
&.twr { &.twr {
background-color: var(--clr-twr); background-color: var(--clr-twr);
box-shadow: 0 0 5px 1px var(--clr-twr); box-shadow: 0 0 5px 1px var(--clr-twr);
@@ -114,3 +114,13 @@
background-color: #007599; background-color: #007599;
} }
} }
.stat-badge {
margin: 0;
color: white;
& > span:first-child {
background-color: $accentCol;
color: black;
}
}
+20 -4
View File
@@ -124,10 +124,12 @@ input {
} }
a { a {
display: inline-block;
color: white;
text-decoration: none; text-decoration: none;
color: inherit;
}
a:not(.a-block):not(.a-button):not(.a-row) {
display: inline-block;
transition: color 0.3s; transition: color 0.3s;
@@ -138,6 +140,14 @@ a {
} }
} }
a.a-block {
display: block;
}
a.a-row {
display: table-row;
}
ul { ul {
padding: 0; padding: 0;
list-style: none; list-style: none;
@@ -184,6 +194,7 @@ a.a-button {
color: white; color: white;
background: none; background: none;
border-radius: 0.25em; border-radius: 0.25em;
text-decoration: none;
display: flex; display: flex;
align-items: center; align-items: center;
@@ -214,7 +225,7 @@ a.a-button {
font-weight: bold; font-weight: bold;
&:hover { &:hover {
background-color: #555; background-color: #424242;
} }
} }
@@ -290,6 +301,7 @@ a.a-button {
// Basic tooltip // Basic tooltip
[data-tooltip] { [data-tooltip] {
cursor: help; cursor: help;
line-height: initial;
} }
[data-tooltip]:hover::after, [data-tooltip]:hover::after,
@@ -308,6 +320,10 @@ a.a-button {
z-index: 100; z-index: 100;
} }
.tooltip-help {
cursor: help;
}
@include smallScreen { @include smallScreen {
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 0.5em; width: 0.5em;
+7
View File
@@ -4,6 +4,12 @@ export enum APIDataStatus {
OK = 'OK', OK = 'OK',
WARNING = 'WARNING' WARNING = 'WARNING'
} }
export interface APICache {
lastModified: string;
name: string;
}
export namespace API { export namespace API {
export namespace ActiveData { export namespace ActiveData {
export interface APIStatuses { export interface APIStatuses {
@@ -11,6 +17,7 @@ export namespace API {
trainsAPI: APIDataStatus; trainsAPI: APIDataStatus;
dispatchersAPI: APIDataStatus; dispatchersAPI: APIDataStatus;
sceneryRequirementsAPI: APIDataStatus; sceneryRequirementsAPI: APIDataStatus;
caches: APICache[];
} }
export interface Response { export interface Response {
+4 -1
View File
@@ -1,3 +1,5 @@
import { RouteLocationRaw } from 'vue-router';
export type Availability = 'default' | 'unavailable' | 'nonPublic' | 'abandoned' | 'nonDefault'; export type Availability = 'default' | 'unavailable' | 'nonPublic' | 'abandoned' | 'nonDefault';
export type ScenerySpawnType = 'passenger' | 'freight' | 'loco' | 'all'; export type ScenerySpawnType = 'passenger' | 'freight' | 'loco' | 'all';
@@ -70,6 +72,8 @@ export interface Train {
isTimeout: boolean; isTimeout: boolean;
isSupporter: boolean; isSupporter: boolean;
driverRouteLocation: RouteLocationRaw,
timetableData?: { timetableData?: {
timetableId: number; timetableId: number;
category: string; category: string;
@@ -79,7 +83,6 @@ export interface Train {
SKR: boolean; SKR: boolean;
routeDistance: number; routeDistance: number;
sceneries: string[]; sceneries: string[];
sceneryNames: string[];
timetablePath: TimetablePathElement[]; timetablePath: TimetablePathElement[];
}; };
} }
+128
View File
@@ -0,0 +1,128 @@
<template>
<section class="driver-view">
<div class="view-wrapper">
<div v-if="chosenTrain">
<div class="actions">
<router-link
:to="`/journal/timetables?search-driver=${chosenTrain.driverName}`"
class="a-button btn--image"
>
<span class="hidable">
{{ $t('trains.driver-journal-link') }}
</span>
<img src="/images/icon-train.svg" alt="train icon" />
</router-link>
</div>
<div class="train-card">
<TrainInfo :train="chosenTrain" :extended="true" ref="trainInfo" />
<TrainSchedule :train="chosenTrain" />
</div>
</div>
<Loading v-else-if="apiStore.dataStatuses.connection == Status.Data.Loading" />
<div v-else class="driver-not-found">
<h2>&olcross; {{ $t('trains.driver-not-found-header') }}</h2>
<p>
{{ $t('trains.driver-not-found-desc-1') }} <br />
{{ $t('trains.driver-not-found-desc-2') }}
<router-link to="/journal/timetables"
>{{ $t('trains.driver-not-found-journal') }} </router-link
>!
</p>
<router-link to="/">&lt;&lt; {{ $t('trains.driver-not-found-return') }}</router-link>
</div>
</div>
</section>
</template>
<script setup lang="ts">
import { computed, onActivated, onMounted, useAttrs } from 'vue';
import TrainInfo from '../components/TrainsView/TrainInfo.vue';
import TrainSchedule from '../components/TrainsView/TrainSchedule.vue';
import Loading from '../components/Global/Loading.vue';
import { useMainStore } from '../store/mainStore';
import { useApiStore } from '../store/apiStore';
import { Status } from '../typings/common';
const props = defineProps({
trainId: {
type: String,
required: true
},
modalId: {
type: String
}
});
const mainStore = useMainStore();
const apiStore = useApiStore();
const chosenTrain = computed(() =>
mainStore.trainList.find((train) => train.id == props.trainId || train.modalId == props.modalId)
);
onActivated(() => {
console.log();
});
</script>
<style lang="scss" scoped>
@import '../styles/responsive';
$viewBgCol: #1a1a1a;
.driver-view {
margin: 0 auto;
padding: 1em 0;
max-width: 2000px;
min-height: 95vh;
}
.actions {
display: flex;
align-items: flex-end;
justify-content: flex-end;
gap: 0.5em;
}
.actions > a {
background-color: $viewBgCol;
padding: 0.5em;
border-radius: 0.5em 0.5em 0 0;
&:hover {
background-color: lighten($viewBgCol, 10);
}
}
.train-card {
background-color: $viewBgCol;
border-radius: 0 0 0.5em 0.5em;
}
.driver-not-found {
background-color: $viewBgCol;
text-align: center;
padding: 1em;
p {
padding: 1em 0;
color: #aaa;
}
a {
text-decoration: underline;
color: white;
}
}
@include smallScreen {
.actions > a > span.hidable {
display: none;
}
}
</style>
+4 -1
View File
@@ -267,7 +267,10 @@ export default defineComponent({
const timestampTo = timestampFrom ? timestampFrom + 86400000 : undefined; const timestampTo = timestampFrom ? timestampFrom + 86400000 : undefined;
if (dispatcher) queries.push(`dispatcherName=${dispatcher}`); if (dispatcher) queries.push(`dispatcherName=${dispatcher}`);
if (station) queries.push(`stationName=${station}`);
if (station.startsWith("#")) queries.push(`stationHash=${station.slice(1)}`);
else if (station.length > 0) queries.push(`stationName=${station}`);
if (timestampFrom && timestampTo) if (timestampFrom && timestampTo)
queries.push(`timestampFrom=${timestampFrom}`, `timestampTo=${timestampTo}`); queries.push(`timestampFrom=${timestampFrom}`, `timestampTo=${timestampTo}`);
+2 -3
View File
@@ -40,7 +40,6 @@ import { defineComponent, provide, reactive, Ref, ref } from 'vue';
import dateMixin from '../mixins/dateMixin'; import dateMixin from '../mixins/dateMixin';
import routerMixin from '../mixins/routerMixin'; import routerMixin from '../mixins/routerMixin';
import modalTrainMixin from '../mixins/modalTrainMixin';
import JournalOptions from '../components/JournalView/JournalOptions.vue'; import JournalOptions from '../components/JournalView/JournalOptions.vue';
import JournalStats from '../components/JournalView/JournalStats.vue'; import JournalStats from '../components/JournalView/JournalStats.vue';
@@ -148,7 +147,7 @@ export default defineComponent({
JournalHeader, JournalHeader,
JournalTimetablesList JournalTimetablesList
}, },
mixins: [dateMixin, routerMixin, modalTrainMixin], mixins: [dateMixin, routerMixin],
name: 'JournalTimetables', name: 'JournalTimetables',
@@ -462,7 +461,7 @@ export default defineComponent({
this.timetableHistory = responseData; this.timetableHistory = responseData;
// Stats display // Stats display
this.store.driverStatsName = this.mainStore.driverStatsName =
this.timetableHistory.length > 0 && this.searchersValues['search-driver'].trim() this.timetableHistory.length > 0 && this.searchersValues['search-driver'].trim()
? this.timetableHistory[0].driverName ? this.timetableHistory[0].driverName
: ''; : '';
+2
View File
@@ -13,6 +13,7 @@
:station="stationInfo" :station="stationInfo"
:onlineScenery="onlineSceneryInfo" :onlineScenery="onlineSceneryInfo"
/> />
<SceneryInfo :station="stationInfo" :onlineScenery="onlineSceneryInfo" /> <SceneryInfo :station="stationInfo" :onlineScenery="onlineSceneryInfo" />
</div> </div>
@@ -229,6 +230,7 @@ button.back-btn {
overflow: auto; overflow: auto;
background-color: #181818; background-color: #181818;
border-radius: 0.5em;
padding: 1em 0.5em; padding: 1em 0.5em;
height: calc(100vh - 0.5em); height: calc(100vh - 0.5em);
+6 -2
View File
@@ -8,6 +8,8 @@
ref="filterCardRef" ref="filterCardRef"
/> />
<StationStats />
<button <button
class="btn-donation btn--image" class="btn-donation btn--image"
ref="btn" ref="btn"
@@ -21,7 +23,6 @@
<DonationCard :is-card-open="isDonationCardOpen" @toggle-card="toggleDonationCard" /> <DonationCard :is-card-open="isDonationCardOpen" @toggle-card="toggleDonationCard" />
<StationTable @toggle-donation-card="toggleDonationCard" /> <StationTable @toggle-donation-card="toggleDonationCard" />
<StationStats />
</div> </div>
</section> </section>
</template> </template>
@@ -96,14 +97,17 @@ export default defineComponent({
.stations-options { .stations-options {
display: flex; display: flex;
justify-content: space-between;
flex-wrap: wrap; flex-wrap: wrap;
gap: 0.5em; gap: 0.5em;
position: relative;
margin-bottom: 0.5em; margin-bottom: 0.5em;
} }
button.btn-donation { button.btn-donation {
margin-left: auto;
$btnColor: #254069; $btnColor: #254069;
background-color: $btnColor; background-color: $btnColor;
+6 -9
View File
@@ -19,7 +19,6 @@
import { computed, ComputedRef, defineComponent, provide, reactive, ref, watch } from 'vue'; import { computed, ComputedRef, defineComponent, provide, reactive, ref, watch } from 'vue';
import TrainOptions from '../components/TrainsView/TrainOptions.vue'; import TrainOptions from '../components/TrainsView/TrainOptions.vue';
import TrainTable from '../components/TrainsView/TrainTable.vue'; import TrainTable from '../components/TrainsView/TrainTable.vue';
import modalTrainMixin from '../mixins/modalTrainMixin';
import { useMainStore } from '../store/mainStore'; import { useMainStore } from '../store/mainStore';
import { TrainFilter, trainFilters } from '../components/TrainsView/typings'; import { TrainFilter, trainFilters } from '../components/TrainsView/typings';
import { filteredTrainList } from '../managers/trainFilterManager'; import { filteredTrainList } from '../managers/trainFilterManager';
@@ -33,8 +32,6 @@ export default defineComponent({
TrainStats TrainStats
}, },
mixins: [modalTrainMixin],
props: { props: {
train: { train: {
type: String, type: String,
@@ -102,16 +99,16 @@ export default defineComponent({
}, },
activated() { activated() {
// Backwards compatibility with external links leading to train modal
if (this.trainId) {
this.$router.replace(`/driver?modalId=${this.trainId}`);
return;
}
if (this.train) { if (this.train) {
this.searchedTrain = this.train; this.searchedTrain = this.train;
this.searchedDriver = this.driver || ''; this.searchedDriver = this.driver || '';
} }
this.$nextTick(() => {
if (this.trainId) {
this.selectModalTrainById(this.trainId);
}
});
} }
}); });
</script> </script>