Compare commits

..

34 Commits

Author SHA1 Message Date
Spythere 43c939bf01 Merge pull request #110 from Spythere/development
v1.28.1
2024-09-18 20:09:22 +02:00
Spythere 8285d5c579 fix: tooltips width 2024-09-18 20:04:53 +02:00
Spythere 547248b478 chore: updown arrow fix 2024-09-18 16:08:54 +02:00
Spythere e1b9b37ac8 bump: v1.28.1 2024-09-18 15:57:56 +02:00
Spythere c8ec28292b chore: removed obsolete console logs 2024-09-18 15:57:45 +02:00
Spythere 3daf800a89 chore: added SBL icon & route tooltips 2024-09-18 15:55:33 +02:00
Spythere 69d9be0bb3 chore: minor thumbnail loading changes 2024-09-18 15:26:14 +02:00
Spythere f53f3a18fe Merge pull request #109 from Spythere/development
v1.28.0
2024-09-09 14:26:32 +02:00
Spythere fac8fced3e chore: removed journal list status animations 2024-09-09 14:11:35 +02:00
Spythere a3d9e68c8a chore: tooltip placing 2024-09-08 16:32:11 +02:00
Spythere b09761de58 chore: TWR & SKR badges fixes 2024-09-07 21:07:41 +02:00
Spythere 8ac2c68660 bump: v1.28.0 2024-09-07 17:28:29 +02:00
Spythere 4177c6e5f4 chore: displaying warning notes in driver view & journal timetables 2024-09-07 17:28:05 +02:00
Spythere b8f135a454 chore: thumbnail loading optimization 2024-09-06 15:23:22 +02:00
Spythere f0863b2459 chore: added static data hourly refresh 2024-09-05 17:00:15 +02:00
Spythere 55b4732992 chore: cleanup 2024-09-05 15:34:11 +02:00
Spythere 7b3dcea89e fix: missing category translations 2024-09-03 15:25:10 +02:00
Spythere f4b0c39185 chore: entry details backwards comp. 2024-09-03 15:18:59 +02:00
Spythere 275d602f97 chore: hiding entry details on history change 2024-09-03 14:43:16 +02:00
Spythere c93514fdf0 refactor: journal timetable entries 2024-09-03 14:29:59 +02:00
Spythere 0861d92e4b hotfix: class name 2024-09-02 23:41:39 +02:00
Spythere bdfd73f4be chore: stops design 2024-09-02 22:56:00 +02:00
Spythere df86364c51 chore: journal timetable stop labels 2024-09-02 22:39:41 +02:00
Spythere 631bb20c61 hotfix: resolved merge conflicts 2024-09-01 16:09:16 +02:00
Spythere bed79ed2d0 chore: translations 2024-09-01 16:07:40 +02:00
Spythere 2a07471e12 chore: displaying other driver's trains in the driver view 2024-08-31 13:51:22 +02:00
Spythere cfe188d0dc bump: v1.27.1 2024-08-29 16:04:14 +02:00
Spythere d9865be83e fix: views viewport height 2024-08-29 16:03:26 +02:00
Spythere 9155fd9f8d chore: driver view return button 2024-08-29 15:51:53 +02:00
Spythere 4674bf886e fix: manifest theme color 2024-08-29 15:47:23 +02:00
Spythere 289fd310df chore: viewport & routing fixes 2024-08-29 15:42:44 +02:00
Spythere b04797052f chore: expanded containers width in views, adjusted dropdowns 2024-08-24 16:16:32 +02:00
Spythere 7079f20791 Merge pull request #107 from Spythere/development
hotfix: journal stats badge styles
2024-08-24 00:25:35 +02:00
Spythere c244275aee hotfix: journal stats badge styles 2024-08-24 00:24:55 +02:00
46 changed files with 964 additions and 537 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "stacjownik", "name": "stacjownik",
"version": "1.27.0", "version": "1.28.1",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
+3
View File
@@ -0,0 +1,3 @@
<svg width="144" height="144" viewBox="0 0 144 144" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M82.7143 61.3284L118.429 7L22 74.9104H68.4286L36.2857 137L122 61.3284H82.7143Z" fill="#FFF500"/>
</svg>

After

Width:  |  Height:  |  Size: 213 B

+5
View File
@@ -0,0 +1,5 @@
<svg width="144" height="144" viewBox="0 0 144 144" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="9.20437" y="2.4661" width="36.5457" height="57.8287" rx="16.7449" transform="matrix(0.869001 -0.494811 0.505207 0.862998 50.006 87.4256)" stroke="white" stroke-width="13.3959"/>
<rect x="9.20437" y="2.4661" width="36.5457" height="57.8287" rx="16.7449" transform="matrix(0.869001 -0.494811 0.505207 0.862998 14.9599 29.6039)" stroke="white" stroke-width="13.3959"/>
<path d="M65.1133 58.145L79.8524 84.3103" stroke="white" stroke-width="10.0469" stroke-linecap="round" stroke-linejoin="bevel"/>
</svg>

After

Width:  |  Height:  |  Size: 611 B

+1 -1
View File
@@ -13,7 +13,7 @@
"type": "image/png" "type": "image/png"
} }
], ],
"theme_color": "#ffc014", "theme_color": "#4d4d4d",
"background_color": "#4d4d4d", "background_color": "#4d4d4d",
"display": "standalone", "display": "standalone",
"start_url": "." "start_url": "."
+1
View File
@@ -81,6 +81,7 @@ export default defineComponent({
async mounted() { async mounted() {
window.addEventListener('mousemove', (e: MouseEvent) => this.tooltipStore.handle(e)); window.addEventListener('mousemove', (e: MouseEvent) => this.tooltipStore.handle(e));
window.addEventListener('mousedown', () => this.tooltipStore.hide());
}, },
methods: { methods: {
+3 -32
View File
@@ -1,21 +1,11 @@
<template> <template>
<div class="stock-list"> <div class="stock-list">
<ul> <ul>
<li <li v-for="({ images, imagesFallbacks, vehicleString }, i) in thumbnailNames" :key="i">
v-for="(
{ vehicleName, vehicleCargo, images, imagesFallbacks, vehicleString }, i
) in thumbnailNames"
:key="i"
>
<div class="stock-text">
<p>{{ vehicleName.replace(/_/g, ' ') }}</p>
<small v-if="vehicleCargo">({{ vehicleCargo }})</small>
</div>
<span> <span>
<VehicleThumbnail <VehicleThumbnail
v-for="(thumbnailImage, imageIndex) in images" v-for="(thumbnailImage, imageIndex) in images"
:vehicle-name="vehicleString" :vehicle-string="vehicleString"
:img-name="thumbnailImage" :img-name="thumbnailImage"
:fallback-name="imagesFallbacks[imageIndex]" :fallback-name="imagesFallbacks[imageIndex]"
/> />
@@ -59,13 +49,12 @@ export default defineComponent({
return (this.tractionOnly ? this.trainStockList.slice(0, 1) : this.trainStockList) return (this.tractionOnly ? this.trainStockList.slice(0, 1) : this.trainStockList)
.filter((v) => v.length != 0) .filter((v) => v.length != 0)
.map((vehicleString) => { .map((vehicleString) => {
const [vehicleName, vehicleCargo] = vehicleString.split(':'); const [vehicleName] = vehicleString.split(':');
const vehicleThumbnailData = { const vehicleThumbnailData = {
images: [] as string[], images: [] as string[],
imagesFallbacks: [] as string[], imagesFallbacks: [] as string[],
vehicleName, vehicleName,
vehicleCargo,
vehicleString vehicleString
}; };
@@ -180,7 +169,6 @@ export default defineComponent({
align-items: flex-end; align-items: flex-end;
overflow: auto; overflow: auto;
margin: 0 auto; margin: 0 auto;
padding: 1em 0;
} }
ul > li > span { ul > li > span {
@@ -188,21 +176,4 @@ ul > li > span {
align-items: flex-end; align-items: flex-end;
cursor: crosshair; cursor: crosshair;
} }
img {
max-height: 60px;
width: auto;
height: auto;
}
img.traction-only {
max-width: 100%;
}
.stock-text {
text-align: center;
color: #aaa;
font-size: 0.9em;
margin-bottom: 0.25em;
}
</style> </style>
+23 -15
View File
@@ -1,13 +1,16 @@
<template> <template>
<div class="vehicle-thumbnail"> <div class="vehicle-thumbnail" :data-load-status="imgStatus" ref="thumbRef">
<div class="stock-text">
<div>{{ vehicleName }}</div>
<small v-if="vehicleCargo">({{ vehicleCargo }})</small>
</div>
<img <img
ref="imgRef"
: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-tooltip-type="VehiclePreviewTooltip" data-tooltip-type="VehiclePreviewTooltip"
:data-tooltip-content="vehicleName" :data-tooltip-content="vehicleString"
:data-load-status="imgStatus"
@error="onImageError" @error="onImageError"
@load="onImageLoad" @load="onImageLoad"
/> />
@@ -15,22 +18,22 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, Ref, ref } from 'vue'; import { computed, Ref, ref } from 'vue';
const props = defineProps({ const props = defineProps({
vehicleName: { type: String, required: true }, vehicleString: { type: String, required: true },
imgName: { type: String, required: true }, imgName: { type: String, required: true },
fallbackName: { type: String, required: true }, fallbackName: { type: String, required: true },
placeholderName: String placeholderName: String
}); });
const imgRef = ref(null) as Ref<HTMLElement | null>; const thumbRef = ref(null) as Ref<HTMLElement | null>;
const imgStatus = ref('loading'); const imgStatus = ref('loading');
function onImageError(event: Event) { const vehicleName = computed(() => props.vehicleString.split(':')[0].replace(/_/g, ' '));
console.log('error'); const vehicleCargo = computed(() => props.vehicleString.split(':')[1]);
function onImageError(event: Event) {
(event.target as HTMLImageElement).src = `/images/${props.fallbackName}.png`; (event.target as HTMLImageElement).src = `/images/${props.fallbackName}.png`;
imgStatus.value = 'error'; imgStatus.value = 'error';
} }
@@ -40,22 +43,27 @@ function onImageLoad() {
imgStatus.value = 'loaded'; imgStatus.value = 'loaded';
} }
imgRef.value!.style.opacity = '1'; if (thumbRef.value) thumbRef.value.style.opacity = '1';
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.vehicle-thumbnail { .vehicle-thumbnail {
position: relative; position: relative;
}
img {
opacity: 0; opacity: 0;
transition: opacity 100ms ease-in-out; transition: opacity 100ms ease-in-out;
&[data-load-status='loading'] { &[data-load-status='loading'] {
min-height: 60px; min-height: 60px;
min-width: 150px; min-width: 200px;
} }
} }
.stock-text {
text-align: center;
color: #aaa;
font-size: 0.9em;
margin-bottom: 0.25em;
padding: 0.25em 0;
}
</style> </style>
@@ -1,48 +1,44 @@
<template> <template>
<transition name="status-anim" mode="out-in"> <div class="journal_warning" v-if="store.isOffline">
<div :key="dataStatus"> {{ $t('app.offline') }}
<div class="journal_warning" v-if="store.isOffline"> </div>
{{ $t('app.offline') }}
</div>
<Loading v-else-if="dataStatus == Status.Data.Loading" /> <Loading v-else-if="dataStatus == Status.Data.Loading" />
<div v-else-if="dataStatus == Status.Data.Error" class="journal_warning error"> <div v-else-if="dataStatus == Status.Data.Error" class="journal_warning error">
{{ $t('app.error') }} {{ $t('app.error') }}
</div> </div>
<div class="journal_warning" v-else-if="dispatcherHistory.length == 0"> <div class="journal_warning" v-else-if="dispatcherHistory.length == 0">
{{ $t('app.no-result') }} {{ $t('app.no-result') }}
</div> </div>
<ul v-else class="journal-list"> <div v-else>
<transition-group name="list-anim"> <transition-group name="list-anim" class="journal-list" tag="ul">
<JournalDispatcherEntry <JournalDispatcherEntry
v-for="entry in dispatcherHistory" v-for="entry in dispatcherHistory"
:key="entry.id" :key="entry.id"
:entry="entry" :entry="entry"
:onToggleShowExtraInfo="toggleExtraInfo" :onToggleShowExtraInfo="toggleExtraInfo"
:showExtraInfo="extraInfoIndexes.includes(entry.id)" :showExtraInfo="extraInfoIndexes.includes(entry.id)"
/> />
</transition-group> </transition-group>
<AddDataButton <AddDataButton
:list="dispatcherHistory" :list="dispatcherHistory"
:scrollDataLoaded="scrollDataLoaded" :scrollDataLoaded="scrollDataLoaded"
:scrollNoMoreData="scrollNoMoreData" :scrollNoMoreData="scrollNoMoreData"
@addHistoryData="addHistoryData" @addHistoryData="addHistoryData"
/> />
</ul> </div>
<div class="journal_warning" v-if="scrollNoMoreData"> <div class="journal_warning" v-if="scrollNoMoreData">
{{ $t('journal.no-further-data') }} {{ $t('journal.no-further-data') }}
</div> </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>
</div>
</transition>
</template> </template>
<script lang="ts"> <script lang="ts">
+10 -3
View File
@@ -30,7 +30,11 @@
</div> </div>
<transition name="dropdown-anim"> <transition name="dropdown-anim">
<div class="dropdown_wrapper" v-if="currentStatsTab !== null"> <div
class="dropdown_wrapper"
:class="{ 'dropdown-align-right': true }"
v-if="currentStatsTab !== null"
>
<keep-alive> <keep-alive>
<component :is="currentStatsTab" :key="currentStatsTab"></component> <component :is="currentStatsTab" :key="currentStatsTab"></component>
</keep-alive> </keep-alive>
@@ -79,7 +83,10 @@ export default defineComponent({
@import '../../styles/dropdown_filters.scss'; @import '../../styles/dropdown_filters.scss';
@import '../../styles/variables.scss'; @import '../../styles/variables.scss';
.dropdown_wrapper { .dropdown_wrapper.dropdown-align-right {
max-width: 100%; left: auto;
right: 0;
max-width: 700px;
// max-width: 100%;
} }
</style> </style>
@@ -2,7 +2,7 @@
<div> <div>
<div class="details-actions"> <div class="details-actions">
<button class="btn--action" @click="toggleExtraInfo"> <button class="btn--action" @click="toggleExtraInfo">
<b>{{ $t('journal.stock-info') }}</b> <b>{{ $t('journal.entry-details') }}</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>
@@ -16,23 +16,25 @@
</router-link> </router-link>
</div> </div>
<div class="details-body" v-if="timetable.stockString && timetable.stockMass && showExtraInfo"> <div class="details-body" v-if="showExtraInfo">
<hr /> <div class="g-separator"></div>
<EntryStops :timetable="timetable" />
<div class="g-separator"></div>
<div class="stock-specs"> <div class="stock-specs">
<span class="badge"> <span class="badge" v-if="timetable.authorName">
<span>{{ $t('journal.dispatcher-name') }}</span> <span>{{ $t('journal.dispatcher-name') }}</span>
<span>{{ timetable.authorName }}</span> <span>{{ timetable.authorName }}</span>
</span> </span>
</div>
<div class="stock-specs"> <span class="badge" v-if="timetable.maxSpeed">
<span class="badge">
<span>{{ $t('journal.stock-max-speed') }}</span> <span>{{ $t('journal.stock-max-speed') }}</span>
<span>{{ timetable.maxSpeed }}km/h</span> <span>{{ timetable.maxSpeed }}km/h</span>
</span> </span>
<span class="badge"> <span class="badge" v-if="timetable.stockLength">
<span>{{ $t('journal.stock-length') }}</span> <span>{{ $t('journal.stock-length') }}</span>
<span> <span>
{{ {{
@@ -43,13 +45,13 @@
</span> </span>
</span> </span>
<span class="badge"> <span class="badge" v-if="timetable.stockMass">
<span>{{ $t('journal.stock-mass') }}</span> <span>{{ $t('journal.stock-mass') }}</span>
<span> <span>
{{ {{
Math.floor( Math.floor(
(currentHistoryIndex == 0 (currentHistoryIndex == 0
? timetable.stockMass! ? timetable.stockMass
: stockHistory[currentHistoryIndex].stockMass || timetable.stockMass) / 1000 : stockHistory[currentHistoryIndex].stockMass || timetable.stockMass) / 1000
) )
}}t }}t
@@ -57,27 +59,56 @@
</span> </span>
</div> </div>
<!-- Historia zmian w składzie --> <div class="stock-dangers" v-if="timetable.twr || timetable.skr">
<div class="stock-history" v-if="stockHistory.length > 1"> <div class="g-separator"></div>
<button
v-for="(sh, i) in stockHistory" <b>{{ $t('journal.stock-dangers') }}:</b>
:key="i"
class="btn--action" <ul>
:data-checked="i == currentHistoryIndex" <li v-if="timetable.twr">
@click.stop="currentHistoryIndex = i" <b class="text--primary">{{ $t('general.TWR') }} (TWR)</b>
> <span v-if="timetable.warningNotes">
{{ sh.updatedAt }} | <i>{{ timetable.warningNotes }}</i>
</button> </span>
</li>
<li v-if="timetable.skr">
<b class="text--primary">{{ $t('general.SKR') }}</b>
<span v-if="timetable.warningNotes">
| Komentarze: <i>{{ timetable.warningNotes }}</i>
</span>
</li>
</ul>
</div> </div>
<StockList <!-- Historia zmian w składzie -->
:trainStockList=" <div v-if="timetable.stockString || stockHistory.length != 0">
(currentHistoryIndex == 0 <div class="g-separator"></div>
? timetable.stockString <b>{{ $t('journal.stock-preview') }}:</b>
: stockHistory[currentHistoryIndex].stockString
).split(';') <div class="stock-history" v-if="stockHistory.length > 1">
" <button
/> v-for="(sh, i) in stockHistory"
:key="i"
class="btn--action"
:data-checked="i == currentHistoryIndex"
@click.stop="currentHistoryIndex = i"
>
{{ sh.updatedAt }}
</button>
</div>
<div v-if="timetable.stockString" style="margin-top: 1em">
<StockList
:trainStockList="
(currentHistoryIndex == 0
? timetable.stockString
: stockHistory[currentHistoryIndex].stockString
).split(';')
"
/>
</div>
</div>
</div> </div>
</div> </div>
</template> </template>
@@ -87,9 +118,10 @@ 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'; import { RouteLocationRaw } from 'vue-router';
import EntryStops from './EntryStops.vue';
export default defineComponent({ export default defineComponent({
components: { StockList }, components: { StockList, EntryStops },
emits: ['toggleExtraInfo'], emits: ['toggleExtraInfo'],
@@ -133,7 +165,7 @@ export default defineComponent({
query: { query: {
trainId: `${this.timetable.driverId}|${this.timetable.trainNo}|eu` trainId: `${this.timetable.driverId}|${this.timetable.trainNo}|eu`
} }
} };
} }
}, },
methods: { methods: {
@@ -161,6 +193,7 @@ export default defineComponent({
.details-actions { .details-actions {
display: flex; display: flex;
gap: 0.5em; gap: 0.5em;
margin-top: 1em;
button img { button img {
height: 1.25em; height: 1.25em;
@@ -182,7 +215,6 @@ export default defineComponent({
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 0.5em; gap: 0.5em;
margin-top: 0.5em;
.badge { .badge {
margin: 0; margin: 0;
@@ -194,20 +226,14 @@ export default defineComponent({
} }
} }
ul.stock-list { hr {
display: flex; margin: 0.5em 0;
align-items: flex-end; }
overflow: auto;
padding-bottom: 0.5em; .stock-dangers ul {
list-style: disc;
li > div { padding-left: 1em;
margin: 1em 0; padding-top: 0.5em;
text-align: center;
color: #aaa;
font-size: 0.9em;
}
} }
@include smallScreen() { @include smallScreen() {
@@ -3,8 +3,24 @@
<span class="general-train"> <span class="general-train">
<span class="text--grayed">#{{ timetable.id }}</span> <span class="text--grayed">#{{ timetable.id }}</span>
<span class="train-badge twr" v-if="timetable.twr" :title="$t('general.TWR')">TWR</span> <span
<span class="train-badge skr" v-if="timetable.skr" :title="$t('general.SKR')">SKR</span> class="train-badge twr"
v-if="timetable.twr"
data-tooltip-type="BaseTooltip"
:data-tooltip-content="
$t('general.TWR') + `${timetable.warningNotes ? ':\n' + timetable.warningNotes : ''}`
"
>
TWR
</span>
<span
class="train-badge skr"
v-if="timetable.skr"
data-tooltip-type="BaseTooltip"
:data-tooltip-content="$t('general.SKR')"
>
SKR
</span>
<span> <span>
<strong <strong
@@ -1,5 +1,5 @@
<template> <template>
<div class="item-status" style="margin: 0.5em 0"> <div class="entry-status" style="margin: 0.5em 0">
<ProgressBar <ProgressBar
:progressPercent="~~((timetable.currentDistance / timetable.routeDistance) * 100)" :progressPercent="~~((timetable.currentDistance / timetable.routeDistance) * 100)"
:progressType="!timetable.fulfilled && timetable.terminated ? 'abandoned' : ''" :progressType="!timetable.fulfilled && timetable.terminated ? 'abandoned' : ''"
@@ -61,7 +61,7 @@ export default defineComponent({
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../../styles/responsive.scss'; @import '../../../styles/responsive.scss';
.item-status { .entry-status {
display: flex; display: flex;
align-items: center; align-items: center;
flex-wrap: wrap; flex-wrap: wrap;
@@ -0,0 +1,296 @@
<template>
<div class="entry-stops">
<ul class="stop-list">
<li v-for="(stop, i) in timetableStops" :key="stop.stopName">
<span class="stop-label" :data-confirmed="stop.isConfirmed">
<span v-if="i > 0">&gt;</span>
<span class="stop-name">{{ stop.stopName }}</span>
<span
class="stop-date"
v-if="stop.scheduledArrivalTimestamp != 0"
:data-delayed="
stop.isConfirmed && stop.arrivalTimestamp - stop.scheduledArrivalTimestamp > 0
"
:data-preponed="
stop.isConfirmed &&
stop.arrivalTimestamp != 0 &&
stop.arrivalTimestamp - stop.scheduledArrivalTimestamp < 0
"
>
<span
v-if="stop.isConfirmed && stop.arrivalTimestamp - stop.scheduledArrivalTimestamp != 0"
>
p. <s>{{ timestampToString(stop.scheduledArrivalTimestamp) }}</s>
{{ timestampToString(stop.arrivalTimestamp) }}
</span>
<span v-else>p. {{ timestampToString(stop.scheduledArrivalTimestamp) }}</span>
</span>
<span
class="stop-time"
v-if="stop.stopTime > 0"
:data-stop-ph="stop.stopType.includes('ph')"
:data-stop-pt="stop.stopType.includes('pt')"
:data-stop-pm="stop.stopType.includes('pm')"
>
/<span>{{ stop.stopTime }} {{ stop.stopType }}</span
>/
</span>
<span
class="stop-date"
v-if="
stop.scheduledDepartureTimestamp != 0 &&
stop.scheduledArrivalTimestamp != stop.scheduledDepartureTimestamp
"
:data-delayed="
stop.isConfirmed && stop.departureTimestamp - stop.scheduledDepartureTimestamp > 0
"
:data-preponed="
stop.isConfirmed &&
stop.departureTimestamp != 0 &&
stop.departureTimestamp - stop.scheduledDepartureTimestamp < 0
"
>
<span
v-if="
stop.isConfirmed && stop.departureTimestamp - stop.scheduledDepartureTimestamp != 0
"
>
o. <s>{{ timestampToString(stop.scheduledDepartureTimestamp) }}</s>
{{ timestampToString(stop.departureTimestamp) }}
</span>
<span v-else>o. {{ timestampToString(stop.scheduledDepartureTimestamp) }}</span>
</span>
</span>
</li>
</ul>
<ul class="timetable-path-list" v-if="timetablePathDetails">
<li
v-for="(pathData, i) in timetablePathDetails"
:data-visited="pathData.isVisited"
:data-next-visited="
i < timetablePathDetails.length - 1 && timetablePathDetails[i + 1].isVisited
"
>
<span v-if="i > 0" class="path-arrow">&gt;</span>
<span class="path-arrival" v-if="pathData.arrival">{{ pathData.arrival }}</span>
<b class="path-scenery">{{ pathData.sceneryName }}</b>
<span class="path-departure" v-if="pathData.departure">{{ pathData.departure }}</span>
</li>
</ul>
</div>
</template>
<script lang="ts">
import { PropType, defineComponent } from 'vue';
import dateMixin from '../../../mixins/dateMixin';
import { API } from '../../../typings/api';
interface ITimetableStopDetails {
stopName: string;
arrivalTimestamp: number;
scheduledArrivalTimestamp: number;
departureTimestamp: number;
scheduledDepartureTimestamp: number;
stopTime: number;
stopType: string;
isConfirmed: boolean;
}
export default defineComponent({
mixins: [dateMixin],
props: {
timetable: {
type: Object as PropType<API.TimetableHistory.Data>,
required: true
}
},
computed: {
timetablePathDetails() {
if (!this.timetable.path || this.timetable.path == '') return null;
return this.timetable.path.split(';').map((pathEl, i) => {
const [arrival, name, departure] = pathEl.split(',');
const sceneryName = name.split(' ').slice(0, -1).join(' ');
const sceneryHash = name.split(' ').pop()?.replace('.sc', '') ?? '';
const isVisited = this.timetable.visitedSceneries.includes(sceneryHash);
return {
arrival,
sceneryName,
sceneryHash,
departure,
isVisited,
isVisitedOffline:
!isVisited &&
this.timetable.visitedSceneries.includes(`${sceneryName} ${sceneryHash}.sc`)
};
});
},
timetableStops(): ITimetableStopDetails[] {
const timetable = this.timetable;
const stopNames = timetable.sceneriesString.split('%');
return stopNames.reduce<ITimetableStopDetails[]>((acc, stopName, i, arr) => {
const arrivalDate =
i == arr.length - 1
? (timetable.checkpointArrivals.at(i) ?? timetable.endDate)
: timetable.checkpointArrivals.at(i);
const scheduledArrivalDate =
i == arr.length - 1
? (timetable.checkpointArrivalsScheduled.at(i) ?? timetable.scheduledEndDate)
: timetable.checkpointArrivalsScheduled.at(i);
const departureDate =
i == 0
? (timetable.checkpointDepartures.at(i) ?? timetable.beginDate)
: timetable.checkpointDepartures.at(i);
const scheduledDepartureDate =
i == 0
? (timetable.checkpointDeparturesScheduled.at(i) ?? timetable.scheduledBeginDate)
: timetable.checkpointDeparturesScheduled.at(i);
const stopTime = Number(timetable.checkpointStopTypes.at(i)?.split(',')[0]) || 0;
const stopType = timetable.checkpointStopTypes.at(i)?.split(',').slice(1).join(',') || 'pt';
acc.push({
stopName,
arrivalTimestamp: this.dateStringToTimestamp(arrivalDate),
scheduledArrivalTimestamp: this.dateStringToTimestamp(scheduledArrivalDate),
departureTimestamp: this.dateStringToTimestamp(departureDate),
scheduledDepartureTimestamp: this.dateStringToTimestamp(scheduledDepartureDate),
stopTime,
stopType,
isConfirmed: i < timetable.confirmedStopsCount
});
return acc;
}, []);
}
}
});
</script>
<style lang="scss" scoped>
@import '../../../styles/badge.scss';
.entry-stops {
word-wrap: break-word;
gap: 0.25em;
font-size: 0.95em;
}
.stop-list {
display: flex;
flex-wrap: wrap;
gap: 0.5em;
padding: 0.5em 0;
}
.stop-label {
display: flex;
flex-wrap: wrap;
gap: 0.5em;
align-items: center;
color: white;
&[data-confirmed='true'] > .stop-name {
color: lightgreen;
}
&[data-confirmed='true'] > .stop-date:not([data-preponed='true']):not([data-delayed='true']) {
color: lightgreen;
}
}
.stop-name {
font-weight: bold;
color: #ccc;
}
.stop-date {
color: #ccc;
s {
color: #aaa;
}
&[data-delayed='true'] {
color: salmon;
}
&[data-preponed='true'] {
color: mediumspringgreen;
}
}
.stop-time {
&[data-stop-pt='true'] span {
color: #999;
}
&[data-stop-ph='true'] span,
&[data-stop-pm='true'] span {
color: gold;
}
}
.timetable-path-list {
display: flex;
flex-wrap: wrap;
gap: 0.5em 0;
padding: 0.5em 0;
color: #ccc;
li > .path-scenery:first-child,
li > .path-arrival:nth-child(2) {
border-radius: 0.5em 0 0 0.5em;
}
li > :last-child {
border-radius: 0 0.5em 0.5em 0;
}
}
.path-scenery {
padding: 0.25em 0.5em;
background-color: #303030;
}
.path-arrival,
.path-departure {
padding: 0.25em;
display: inline-block;
background-color: #4e4e4e;
min-width: 25px;
text-align: center;
}
.path-arrow {
padding: 0 0.5em;
}
.timetable-path-list > li[data-visited='true'] {
.path-arrival,
.path-scenery,
.path-arrow {
color: lightgreen;
}
&[data-next-visited='true'] .path-departure {
color: lightgreen;
}
}
</style>
@@ -0,0 +1,149 @@
<template>
<li class="timetable-history-entry">
<!-- General -->
<EntryGeneral :timetable="timetableEntry" />
<div @click="toggleExtraInfo" style="cursor: pointer">
<!-- Route -->
<div class="entry-route">
<b>{{ timetableEntry.route.replace('|', ' - ') }}</b>
</div>
<hr />
<!-- Status -->
<EntryStatus :timetable="timetableEntry" />
</div>
<!-- Extra -->
<EntryDetails
:timetable="timetableEntry"
:show-extra-info="showExtraInfo"
@toggle-extra-info="toggleExtraInfo"
/>
</li>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue';
import { API } from '../../../typings/api';
import { useApiStore } from '../../../store/apiStore';
import { Journal } from '../typings';
import trainCategoryMixin from '../../../mixins/trainCategoryMixin';
import dateMixin from '../../../mixins/dateMixin';
import styleMixin from '../../../mixins/styleMixin';
import EntryGeneral from './EntryGeneral.vue';
import EntryStatus from './EntryStatus.vue';
import EntryDetails from './EntryDetails.vue';
export default defineComponent({
props: {
timetableEntry: {
type: Object as PropType<API.TimetableHistory.Data>,
required: true
},
showExtraInfo: {
type: Boolean,
required: true
}
},
components: { EntryDetails, EntryGeneral, EntryStatus },
mixins: [trainCategoryMixin, dateMixin, styleMixin],
emits: ['toggleShowExtraInfo'],
data() {
return {
apiStore: useApiStore()
};
},
computed: {
timetablePathDetails() {
if (!this.timetableEntry.path || this.timetableEntry.path == '') return null;
return this.timetableEntry.path.split(';').map((pathEl, i) => {
const [arrival, name, departure] = pathEl.split(',');
const sceneryName = name.split(' ').slice(0, -1).join(' ');
const sceneryHash = name.split(' ').pop()?.replace('.sc', '') ?? '';
return {
arrival,
sceneryName,
sceneryHash,
departure,
isVisited: this.timetableEntry.visitedSceneries?.includes(sceneryHash) ?? false
};
});
},
timetableStops(): Journal.TimetableStopDetails[] {
const timetableEntry = this.timetableEntry;
const stopNames = timetableEntry.sceneriesString.split('%');
return stopNames.reduce<Journal.TimetableStopDetails[]>((acc, stopName, i, arr) => {
const arrivalDate =
i == arr.length - 1
? (timetableEntry.checkpointArrivals.at(i) ?? timetableEntry.endDate)
: timetableEntry.checkpointArrivals.at(i);
const scheduledArrivalDate =
i == arr.length - 1
? (timetableEntry.checkpointArrivalsScheduled.at(i) ?? timetableEntry.scheduledEndDate)
: timetableEntry.checkpointArrivalsScheduled.at(i);
const departureDate =
i == 0
? (timetableEntry.checkpointDepartures.at(i) ?? timetableEntry.beginDate)
: timetableEntry.checkpointDepartures.at(i);
const scheduledDepartureDate =
i == 0
? (timetableEntry.checkpointDeparturesScheduled.at(i) ??
timetableEntry.scheduledBeginDate)
: timetableEntry.checkpointDeparturesScheduled.at(i);
const stopTime = Number(timetableEntry.checkpointStopTypes.at(i)?.split(',')[0]) || 0;
const stopType = timetableEntry.checkpointStopTypes.at(i)?.split(',')[1] || '';
acc.push({
stopName,
arrivalTimestamp: this.dateStringToTimestamp(arrivalDate),
scheduledArrivalTimestamp: this.dateStringToTimestamp(scheduledArrivalDate),
departureTimestamp: this.dateStringToTimestamp(departureDate),
scheduledDepartureTimestamp: this.dateStringToTimestamp(scheduledDepartureDate),
stopTime,
stopType,
isConfirmed: i < timetableEntry.confirmedStopsCount
});
return acc;
}, []);
}
},
methods: {
toggleExtraInfo() {
this.$emit('toggleShowExtraInfo');
}
}
});
</script>
<style lang="scss" scoped>
@import '../../../styles/responsive.scss';
.timetable-history-entry {
background-color: #1a1a1a;
padding: 1em;
}
@include smallScreen {
.entry-route {
text-align: center;
}
}
</style>
@@ -1,63 +1,37 @@
<template> <template>
<div> <div>
<transition name="status-anim" mode="out-in"> <div class="journal_warning" v-if="store.isOffline">
<div :key="dataStatus"> {{ $t('app.offline') }}
<div class="journal_warning" v-if="store.isOffline"> </div>
{{ $t('app.offline') }}
</div>
<Loading v-else-if="dataStatus == Status.Data.Loading" /> <Loading v-else-if="dataStatus == Status.Data.Loading" />
<div v-else-if="dataStatus == Status.Data.Error" class="journal_warning error"> <div v-else-if="dataStatus == Status.Data.Error" class="journal_warning error">
{{ $t('app.error') }} {{ $t('app.error') }}
</div> </div>
<div v-else-if="timetableHistory.length == 0" class="journal_warning"> <div v-else-if="timetableHistory.length == 0" class="journal_warning">
{{ $t('app.no-result') }} {{ $t('app.no-result') }}
</div> </div>
<div v-else> <div v-else>
<transition-group name="list-anim" tag="ul" class="journal-list"> <transition-group name="list-anim" class="journal-list" tag="ul">
<li v-for="timetable in timetableHistory" class="journal_item" :key="timetable.id"> <JournalTimetableEntry
<div class="journal_item-info"> v-for="(timetableEntry, i) in timetableHistory"
<!-- General --> :key="timetableEntry.id"
<TimetableGeneral :timetable="timetable" /> :timetableEntry="timetableEntry"
:onToggleShowExtraInfo="() => toggleExtraInfo(timetableEntry.id)"
:showExtraInfo="extraInfoIndexes.includes(timetableEntry.id)"
/>
</transition-group>
<div @click="toggleExtraInfo(timetable.id)" style="cursor: pointer"> <AddDataButton
<!-- Route --> :list="timetableHistory"
<span class="item-route"> :scrollDataLoaded="scrollDataLoaded"
<b>{{ timetable.route.replace('|', ' - ') }}</b> :scrollNoMoreData="scrollNoMoreData"
</span> @addHistoryData="addHistoryData"
/>
<hr /> </div>
<!-- Stops -->
<TimetableStops
:timetable="timetable"
:showExtraInfo="extraInfoIndexes.includes(timetable.id)"
/>
<!-- Status -->
<TimetableStatus :timetable="timetable" />
</div>
<!-- Extra -->
<TimetableDetails
:timetable="timetable"
:showExtraInfo="extraInfoIndexes.includes(timetable.id)"
@toggle-extra-info="toggleExtraInfo"
/>
</div>
</li>
</transition-group>
<AddDataButton
:list="timetableHistory"
:scrollDataLoaded="scrollDataLoaded"
:scrollNoMoreData="scrollNoMoreData"
@addHistoryData="addHistoryData"
/>
</div>
</div>
</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>
@@ -68,28 +42,21 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, Prop, PropType, ref } from 'vue'; import { defineComponent, PropType } 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';
import JournalTimetableEntry from './JournalTimetableEntry.vue';
import { useMainStore } from '../../../store/mainStore'; import { useMainStore } from '../../../store/mainStore';
import { Status } from '../../../typings/common'; import { Status } from '../../../typings/common';
import { API } from '../../../typings/api'; import { API } from '../../../typings/api';
import TimetableGeneral from './TimetableGeneral.vue';
import TimetableStops from './TimetableStops.vue';
import TimetableStatus from './TimetableStatus.vue';
import TimetableDetails from './TimetableDetails.vue';
export default defineComponent({ export default defineComponent({
components: { components: {
Loading, Loading,
AddDataButton, AddDataButton,
TimetableDetails, JournalTimetableEntry
TimetableGeneral,
TimetableStatus,
TimetableStops
}, },
props: { props: {
@@ -119,6 +86,14 @@ export default defineComponent({
}; };
}, },
watch: {
'$route.query': {
deep: true,
handler() {
this.extraInfoIndexes.length = 0;
}
}
},
methods: { methods: {
toggleExtraInfo(id: number) { toggleExtraInfo(id: number) {
const existingIdx = this.extraInfoIndexes.indexOf(id); const existingIdx = this.extraInfoIndexes.indexOf(id);
@@ -1,164 +0,0 @@
<template>
<div class="timetable-stops">
<div class="stop-list">
<span
v-for="(stop, i) in timetableStops.filter((_, i) =>
!showExtraInfo ? i == 0 || i == timetableStops.length - 1 : true
)"
class="stop-list-item"
:key="stop.stopName"
:data-confirmed="stop.confirmed"
>
<span v-if="i > 0">
&gt;
<span v-if="!showExtraInfo && i == 1 && timetableStops.length > 2">
... (+{{ timetableStops.length - 2 }}) &gt;
</span>
</span>
<span class="stop-name">{{ stop.stopName }}</span>
<span v-html="stop.html"></span>
</span>
</div>
<div class="path-details" v-if="showExtraInfo && timetablePathDetails">
<span
v-for="(pathData, i) in timetablePathDetails"
:data-visited="pathData.isVisited"
:data-next-visited="
i < timetablePathDetails.length - 1 && timetablePathDetails[i + 1].isVisited
"
>
<span class="path-arrival" v-if="pathData.arrival">/ {{ pathData.arrival }} &RightArrow; </span>
<b class="path-scenery">{{ pathData.sceneryName }}</b>
<span class="path-departure" v-if="pathData.departure">
&RightArrow; {{ pathData.departure }}&nbsp;
</span>
</span>
</div>
</div>
</template>
<script lang="ts">
import { PropType, defineComponent } from 'vue';
import dateMixin from '../../../mixins/dateMixin';
import { API } from '../../../typings/api';
export default defineComponent({
mixins: [dateMixin],
props: {
showExtraInfo: {
type: Boolean,
required: true
},
timetable: {
type: Object as PropType<API.TimetableHistory.Data>,
required: true
}
},
computed: {
timetablePathDetails() {
if (!this.timetable.path || this.timetable.path == '') return null;
return this.timetable.path.split(';').map((pathEl, i) => {
const [arrival, name, departure] = pathEl.split(',');
const sceneryName = name.split(' ').slice(0, -1).join(' ');
const sceneryHash = name.split(' ').pop()?.replace('.sc', '') ?? '';
return {
arrival,
sceneryName,
sceneryHash,
departure,
isVisited: this.timetable.visitedSceneries?.includes(sceneryHash) ?? false
};
});
},
timetableStops() {
const timetable = this.timetable;
const stopNames = timetable.sceneriesString.split('%');
const beginDateHTML = ` (o. ${
timetable.beginDate != timetable.scheduledBeginDate
? `<s class="text--grayed">${this.localeTime(timetable.beginDate, this.$i18n.locale)}</s>`
: ''
} <span>${this.localeTime(timetable.scheduledBeginDate, this.$i18n.locale)}</span>)`;
const endDateHTML = ` (p. ${
timetable.endDate != timetable.scheduledEndDate && timetable.fulfilled
? `<s class="text--grayed">${this.localeTime(timetable.endDate, this.$i18n.locale)}</s>`
: ''
} <span>${this.localeTime(timetable.scheduledEndDate, this.$i18n.locale)}</span>)`;
return stopNames.map((stopName, i) => {
const confirmed = i < timetable.confirmedStopsCount;
if (i == 0) return { stopName, html: beginDateHTML, confirmed };
if (i == stopNames.length - 1) return { stopName, html: endDateHTML, confirmed };
const departureDateScheduled = this.stringToDate(
timetable.checkpointDeparturesScheduled?.at(i)
);
const departureDateReal = this.stringToDate(timetable.checkpointDepartures?.at(i));
const arrivalDateScheduled = this.stringToDate(
timetable.checkpointArrivalsScheduled?.at(i)
);
const arrivalDateReal = this.stringToDate(timetable.checkpointArrivals?.at(i));
const arrivalHTML =
(arrivalDateReal &&
arrivalDateScheduled &&
arrivalDateReal?.getTime() != arrivalDateScheduled?.getTime()
? `<s class="text--grayed">${this.parseDateToTimeString(arrivalDateScheduled)}</s> `
: '') + this.parseDateToTimeString(arrivalDateReal || arrivalDateScheduled);
const departureHTML =
(departureDateReal &&
departureDateScheduled &&
departureDateReal?.getTime() != departureDateScheduled?.getTime()
? `<s class="text--grayed">${this.parseDateToTimeString(departureDateScheduled)}</s> `
: '') + this.parseDateToTimeString(departureDateReal || departureDateScheduled);
let html = `${arrivalHTML}${departureHTML ? ` / ${departureHTML}` : ''}`;
if (html) html = ` (${html})`;
return { stopName, html, confirmed };
});
}
}
});
</script>
<style lang="scss" scoped>
.timetable-stops {
word-wrap: break-word;
gap: 0.25em;
font-size: 0.95em;
color: #adadad;
}
.stop-list {
&-item[data-confirmed='true'] {
color: lightgreen;
.stop-name {
font-weight: bold;
}
}
}
.path-details {
margin-top: 0.5em;
}
.path-details > span[data-visited='true'] {
.path-arrival,
.path-scenery {
color: lightgreen;
}
&[data-next-visited='true'] .path-departure {
color: lightgreen;
}
}
</style>
+12
View File
@@ -66,4 +66,16 @@ export namespace Journal {
iconName: string; iconName: string;
disabled: boolean; disabled: boolean;
} }
export interface TimetableStopDetails {
stopName: string;
arrivalTimestamp: number;
scheduledArrivalTimestamp: number;
departureTimestamp: number;
scheduledDepartureTimestamp: number;
stopTime: number;
stopType: string;
isConfirmed: boolean;
}
} }
+2 -2
View File
@@ -407,9 +407,9 @@ export default defineComponent({
$rowCol: #424242; $rowCol: #424242;
.station_table { .station_table {
height: 90vh; height: calc(100vh - 11em);
max-height: 2000px; max-height: 2000px;
min-height: 700px; min-height: 500px;
overflow: auto; overflow: auto;
font-weight: 500; font-weight: 500;
} }
+1 -1
View File
@@ -29,7 +29,7 @@ export default defineComponent({
border-radius: 0.25em; border-radius: 0.25em;
width: 100%; width: 100%;
background-color: #333; background-color: #1f1f1f;
box-shadow: 0 0 5px 2px #aaa; box-shadow: 0 0 5px 2px #aaa;
} }
+1 -2
View File
@@ -32,12 +32,11 @@ export default defineComponent({
<style scoped> <style scoped>
.tooltip-content { .tooltip-content {
width: 300px; width: 200px;
padding: 0.25em 0.5em; padding: 0.25em 0.5em;
border-radius: 0.25em; border-radius: 0.25em;
width: 100%;
background-color: #1b1b1b; background-color: #1b1b1b;
box-shadow: 0 0 5px 2px #aaa; box-shadow: 0 0 5px 2px #aaa;
} }
+10 -8
View File
@@ -13,6 +13,8 @@ import BaseTooltip from './BaseTooltip.vue';
import SpawnsTooltip from './SpawnsTooltip.vue'; import SpawnsTooltip from './SpawnsTooltip.vue';
import UsersTooltip from './UsersTooltip.vue'; import UsersTooltip from './UsersTooltip.vue';
const BOX_PADDING_PX = 20;
export default defineComponent({ export default defineComponent({
components: { DonatorTooltip, VehiclePreviewTooltip, BaseTooltip, SpawnsTooltip, UsersTooltip }, components: { DonatorTooltip, VehiclePreviewTooltip, BaseTooltip, SpawnsTooltip, UsersTooltip },
@@ -33,14 +35,14 @@ export default defineComponent({
const boxWidth = previewEl.getBoundingClientRect().width; const boxWidth = previewEl.getBoundingClientRect().width;
let translateX = '0', let translateX = '0',
translateY = '30px'; translateY = `calc(-100% - ${BOX_PADDING_PX}px)`;
if (val[0] <= boxWidth / 2) { if (val[0] <= boxWidth / 2 + BOX_PADDING_PX) {
previewEl.style.left = '0'; previewEl.style.left = '0';
translateX = '0px'; translateX = BOX_PADDING_PX + 'px';
} else if (val[0] >= clientWidth - boxWidth / 2) { } else if (val[0] >= clientWidth - boxWidth / 2 - BOX_PADDING_PX) {
previewEl.style.left = '100%'; previewEl.style.left = '100%';
translateX = '-100%'; translateX = `calc(-100% - ${BOX_PADDING_PX}px)`;
} else { } else {
previewEl.style.left = `${val[0]}px`; previewEl.style.left = `${val[0]}px`;
translateX = '-50%'; translateX = '-50%';
@@ -49,10 +51,10 @@ export default defineComponent({
previewEl.style.top = `${val[1]}px`; previewEl.style.top = `${val[1]}px`;
const isOutside = const isOutside =
val[1] + previewEl.getBoundingClientRect().height + 30 >= val[1] - previewEl.getBoundingClientRect().height <=
window.innerHeight + window.scrollY; window.scrollY + BOX_PADDING_PX * 2;
if (isOutside) translateY = 'calc(-100% - 30px)'; if (isOutside) translateY = BOX_PADDING_PX + 'px';
previewEl.style.transform = `translate(${translateX}, ${translateY})`; previewEl.style.transform = `translate(${translateX}, ${translateY})`;
}); });
} }
+1 -2
View File
@@ -32,12 +32,11 @@ export default defineComponent({
<style scoped> <style scoped>
.tooltip-content { .tooltip-content {
width: 300px; width: 200px;
padding: 0.25em 0.5em; padding: 0.25em 0.5em;
border-radius: 0.25em; border-radius: 0.25em;
width: 100%;
background-color: #1b1b1b; background-color: #1b1b1b;
box-shadow: 0 0 5px 2px #aaa; box-shadow: 0 0 5px 2px #aaa;
} }
@@ -77,9 +77,11 @@ export default defineComponent({
}, },
vehicleCargo() { vehicleCargo() {
return this.vehicleData?.group.cargoTypes?.find( const x = this.vehicleData?.group.cargoTypes?.find(
(c) => c.id == this.tooltipStore.content.split(':')[1] (c) => c.id == this.tooltipStore.content.split(':')[1]
); );
return x;
} }
} }
}); });
@@ -89,7 +91,7 @@ export default defineComponent({
.tooltip-content { .tooltip-content {
width: 300px; width: 300px;
min-height: 200px; min-height: 200px;
background-color: #333; background-color: #1f1f1f;
box-shadow: 0 0 10px 2px #aaa; box-shadow: 0 0 10px 2px #aaa;
padding: 0.5em; padding: 0.5em;
+5 -5
View File
@@ -4,10 +4,10 @@
:data-minor="stop.isSBL || (stop.nameRaw.endsWith(', po') && !stop.duration)" :data-minor="stop.isSBL || (stop.nameRaw.endsWith(', po') && !stop.duration)"
> >
<router-link v-if="/(, podg$|<strong>)/.test(stop.nameHtml)" :to="sceneryHref"> <router-link v-if="/(, podg$|<strong>)/.test(stop.nameHtml)" :to="sceneryHref">
<span class="name" v-html="stop.nameHtml"></span> <span class="stop-name" v-html="stop.nameHtml"></span>
</router-link> </router-link>
<span v-else class="name" v-html="stop.nameHtml"></span> <span v-else class="stop-name" v-html="stop.nameHtml"></span>
<span <span
v-if="stop.position != 'begin'" v-if="stop.position != 'begin'"
@@ -70,7 +70,7 @@
<script lang="ts"> <script lang="ts">
import { PropType, defineComponent } from 'vue'; import { PropType, defineComponent } from 'vue';
import dateMixin from '../../mixins/dateMixin'; import dateMixin from '../../mixins/dateMixin';
import { TrainScheduleStop } from './TrainSchedule.vue'; import { TrainScheduleStop } from './typings';
export default defineComponent({ export default defineComponent({
mixins: [dateMixin], mixins: [dateMixin],
@@ -107,7 +107,7 @@ s {
flex-wrap: wrap; flex-wrap: wrap;
align-items: center; align-items: center;
.name { .stop-name {
background: $stopNameClr; background: $stopNameClr;
border-radius: 0.5em 0 0 0.5em; border-radius: 0.5em 0 0 0.5em;
padding: 0.3em 0.5em; padding: 0.3em 0.5em;
@@ -134,7 +134,7 @@ s {
display: none; display: none;
} }
.name { .stop-name {
background: none; background: none;
color: #aaa; color: #aaa;
padding: 0; padding: 0;
+31 -3
View File
@@ -8,11 +8,21 @@
#{{ train.timetableData.timetableId }} #{{ train.timetableData.timetableId }}
</span> </span>
<span class="train-badge twr" v-if="train.timetableData?.TWR" :title="$t('general.TWR')"> <span
class="train-badge twr"
v-if="train.timetableData?.TWR"
data-tooltip-type="BaseTooltip"
:data-tooltip-content="$t('general.TWR') + `:\n${train.timetableData.warningNotes}`"
>
TWR TWR
</span> </span>
<span class="train-badge skr" v-if="train.timetableData?.SKR" :title="$t('general.SKR')"> <span
class="train-badge skr"
v-if="train.timetableData?.SKR"
data-tooltip-type="BaseTooltip"
:data-tooltip-content="$t('general.SKR')"
>
SKR SKR
</span> </span>
@@ -129,6 +139,20 @@
<div class="text--grayed" style="margin-top: 0.25em"> <div class="text--grayed" style="margin-top: 0.25em">
{{ displayTrainPosition(train) }} {{ displayTrainPosition(train) }}
</div> </div>
<div
class="train-dangers"
v-if="extended && (train.timetableData?.TWR || train.timetableData?.SKR)"
>
<div v-if="train.timetableData.TWR">
<b style="color: var(--clr-twr)">TWR</b> - {{ $t('general.TWR') }}
<i>({{ train.timetableData?.warningNotes }})</i>
</div>
<div v-if="train.timetableData.SKR">
<b style="color: var(--clr-skr)">SKR</b> - {{ $t('general.SKR') }}
</div>
</div>
</section> </section>
<section class="train-stats" v-if="!extended"> <section class="train-stats" v-if="!extended">
@@ -199,7 +223,7 @@ export default defineComponent({
query: { query: {
'search-driver': this.train.driverName 'search-driver': this.train.driverName
} }
} };
} }
} }
}); });
@@ -226,6 +250,10 @@ export default defineComponent({
line-height: 1.5em; line-height: 1.5em;
} }
.train-dangers {
margin-top: 0.5em;
}
.train-info { .train-info {
display: grid; display: grid;
grid-template-columns: 2fr 1fr; grid-template-columns: 2fr 1fr;
+43 -46
View File
@@ -55,12 +55,28 @@
<span>{{ stop.departureLine }}</span> <span>{{ stop.departureLine }}</span>
<span v-if="stop.departureLineInfo"> <span v-if="stop.departureLineInfo">
| {{ stop.departureLineInfo.routeSpeed }} | {{ stop.departureLineInfo.routeSpeed }}
<span v-if="stop.departureLineInfo.isElectric">⚡</span>
<img <img
v-else :src="
src="/images/icon-we4a.png" stop.departureLineInfo.isElectric
:title="$t('trains.we4a-tooltip')" ? '/images/icon-catenary.svg'
: '/images/icon-we4a.png'
"
width="10" width="10"
data-tooltip-type="BaseTooltip"
:data-tooltip-content="
$t(
`trains.${!stop.departureLineInfo.isElectric ? 'no-' : ''}catenary-tooltip`
)
"
/>
<img
v-if="stop.departureLineInfo.isRouteSBL"
src="/images/icon-sbl-transparent.svg"
width="10"
data-tooltip-type="BaseTooltip"
:data-tooltip-content="$t('trains.sbl-tooltip')"
/> />
</span> </span>
</div> </div>
@@ -71,7 +87,7 @@
> >
<span>{{ scheduleStops[i + 1].sceneryName }}</span> <span>{{ scheduleStops[i + 1].sceneryName }}</span>
<span v-if="stop.departureLineInfo?.routeTracks == 1"> &UpDownArrow;</span> <span v-if="stop.departureLineInfo?.routeTracks == 1"> &UpDownArrow;</span>
<span v-else> &UpArrowDownArrow;</span> <span v-else> &DownArrowUpArrow;</span>
</div> </div>
<div class="scenery-route"> <div class="scenery-route">
@@ -79,13 +95,29 @@
<span v-if="scheduleStops[i + 1].arrivalLineInfo"> <span v-if="scheduleStops[i + 1].arrivalLineInfo">
| {{ scheduleStops[i + 1].arrivalLineInfo!.routeSpeed }} | {{ scheduleStops[i + 1].arrivalLineInfo!.routeSpeed }}
<span v-if="scheduleStops[i + 1].arrivalLineInfo!.isElectric">⚡</span>
<img <img
v-else :src="
src="/images/icon-we4a.png" scheduleStops[i + 1].arrivalLineInfo!.isElectric
:title="$t('trains.we4a-tooltip')" ? '/images/icon-catenary.svg'
: '/images/icon-we4a.png'
"
data-tooltip-type="BaseTooltip"
:data-tooltip-content="
$t(
`trains.${!scheduleStops[i + 1].arrivalLineInfo!.isElectric ? 'no-' : ''}catenary-tooltip`
)
"
width="10" width="10"
/> />
<img
v-if="scheduleStops[i + 1].arrivalLineInfo!.isRouteSBL"
src="/images/icon-sbl-transparent.svg"
width="10"
data-tooltip-type="BaseTooltip"
:data-tooltip-content="$t('trains.sbl-tooltip')"
/>
</span> </span>
</div> </div>
</span> </span>
@@ -105,43 +137,7 @@ import StockList from '../Global/StockList.vue';
import { useMainStore } from '../../store/mainStore'; import { useMainStore } from '../../store/mainStore';
import { useApiStore } from '../../store/apiStore'; import { useApiStore } from '../../store/apiStore';
import { StationRoutesInfo, Train } from '../../typings/common'; import { StationRoutesInfo, Train } from '../../typings/common';
import { TrainScheduleStop } from './typings';
export interface TrainScheduleStop {
nameHtml: string;
nameRaw: string;
status: 'confirmed' | 'unconfirmed' | 'stopped';
type: string;
position: 'begin' | 'end' | 'en-route';
arrivalScheduled: number;
arrivalReal: number;
departureScheduled: number;
departureReal: number;
departureDelay: number;
arrivalDelay: number;
duration: number | null;
isActive: boolean;
isLastConfirmed: boolean;
isSBL: boolean;
sceneryName: string | null;
distance: number;
arrivalLine: string | null;
departureLine: string | null;
arrivalLineInfo?: StationRoutesInfo;
departureLineInfo?: StationRoutesInfo;
isExternal: boolean;
comments: string | null;
}
export default defineComponent({ export default defineComponent({
components: { StopLabel, StockList }, components: { StopLabel, StockList },
@@ -534,6 +530,7 @@ $blinkAnim: 0.5s ease-in-out alternate infinite blink;
img { img {
width: 1em; width: 1em;
cursor: help;
} }
} }
+3 -3
View File
@@ -82,10 +82,10 @@ export default defineComponent({
@import '../../styles/animations.scss'; @import '../../styles/animations.scss';
.train-table { .train-table {
position: relative; height: calc(100vh - 11em);
min-height: 500px;
height: 90vh; position: relative;
min-height: 550px;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
} }
+39
View File
@@ -1,3 +1,5 @@
import { StationRoutesInfo } from "../../typings/common";
export enum TrainFilterSection { export enum TrainFilterSection {
TRAIN_TYPE = 'TRAIN_TYPE', TRAIN_TYPE = 'TRAIN_TYPE',
TIMETABLE_TYPE = 'TIMETABLE_TYPE', TIMETABLE_TYPE = 'TIMETABLE_TYPE',
@@ -117,3 +119,40 @@ export const sorterOptions: TrainSorter[] = [
value: 'długość' value: 'długość'
} }
]; ];
export interface TrainScheduleStop {
nameHtml: string;
nameRaw: string;
status: 'confirmed' | 'unconfirmed' | 'stopped';
type: string;
position: 'begin' | 'end' | 'en-route';
arrivalScheduled: number;
arrivalReal: number;
departureScheduled: number;
departureReal: number;
departureDelay: number;
arrivalDelay: number;
duration: number | null;
isActive: boolean;
isLastConfirmed: boolean;
isSBL: boolean;
sceneryName: string | null;
distance: number;
arrivalLine: string | null;
departureLine: string | null;
arrivalLineInfo?: StationRoutesInfo;
departureLineInfo?: StationRoutesInfo;
isExternal: boolean;
comments: string | null;
}
+16 -4
View File
@@ -83,7 +83,9 @@
"ZN": "inspection / diagnostic type", "ZN": "inspection / diagnostic type",
"ZU": "other maintenance type", "ZU": "other maintenance type",
"ZG": "emergency (deprecated category)", "ZG": "emergency (deprecated)",
"AP": "voivodeship regio (deprecated)",
"E": "electric loco", "E": "electric loco",
"J": "EMU", "J": "EMU",
@@ -375,7 +377,10 @@
"current-track": "on track", "current-track": "on track",
"vmax-tooltip": "Maximum train speed based on rolling stock vehicles - braked weight is not included", "vmax-tooltip": "Maximum train speed based on rolling stock vehicles - braked weight is not included",
"we4a-tooltip": "Non-electrified track",
"catenary-tooltip": "Electrified route",
"no-catenary-tooltip": "Non-electrified route",
"sbl-tooltip": "Route with SBL\n(automatic block signalling)",
"delayed": "Delayed: ", "delayed": "Delayed: ",
"preponed": "Ahead of schedule: ", "preponed": "Ahead of schedule: ",
@@ -407,9 +412,10 @@
"driver-return-link": "GO BACK", "driver-return-link": "GO BACK",
"driver-not-found-header": "Train not found! :/", "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-1": "This train has already been terminated, changed its number or is offline.",
"driver-not-found-desc-2": "You can browse timetable history in the", "driver-not-found-desc-2": "You can browse timetable history in the",
"driver-not-found-journal": "TIMETABLES JOURNAL", "driver-not-found-journal": "TIMETABLES JOURNAL",
"driver-not-found-others": "Player {driver} is online as:",
"driver-not-found-return": "GO BACK TO THE MAIN SITE" "driver-not-found-return": "GO BACK TO THE MAIN SITE"
}, },
"train-stats": { "train-stats": {
@@ -438,6 +444,7 @@
"no-further-data": "No further data for current parameters", "no-further-data": "No further data for current parameters",
"loading-further-data": "Loading...", "loading-further-data": "Loading...",
"route-length": "Route length:", "route-length": "Route length:",
"station-count": "Stations:", "station-count": "Stations:",
@@ -455,11 +462,16 @@
"minutes": "{value} min | {value} mins", "minutes": "{value} min | {value} mins",
"seconds": "{value} s", "seconds": "{value} s",
"stock-info": "DETAILS", "entry-details": "DETAILS",
"no-entry-details": "NO DETAILS AVAILABLE",
"stock-length": "Length", "stock-length": "Length",
"stock-mass": "Mass", "stock-mass": "Mass",
"stock-max-speed": "Max. speed", "stock-max-speed": "Max. speed",
"stock-dangers": "ADDITIONAL NOTES",
"stock-preview": "STOCK PREVIEW",
"load-data": "Load further data...", "load-data": "Load further data...",
"last-seen-at": "Last seen at", "last-seen-at": "Last seen at",
+14 -3
View File
@@ -82,6 +82,8 @@
"ZU": "inny utrzymaniowy", "ZU": "inny utrzymaniowy",
"ZG": "ratunkowy (kat. wycofana)", "ZG": "ratunkowy (kat. wycofana)",
"AP": "wojewódzki osobowy (kat. wycofana)",
"E": "elektrowóz", "E": "elektrowóz",
"J": "EZT", "J": "EZT",
"S": "spalinowóz", "S": "spalinowóz",
@@ -362,7 +364,10 @@
"current-track": "na szlaku", "current-track": "na szlaku",
"vmax-tooltip": "Maksymalna prędkość na podstawie pojazdów w składzie - nie bierze pod uwagę masy hamowania", "vmax-tooltip": "Maksymalna prędkość na podstawie pojazdów w składzie - nie bierze pod uwagę masy hamowania",
"we4a-tooltip": "Szlak niezelektryfikowany",
"catenary-tooltip": "Szlak zelektryfikowany",
"no-catenary-tooltip": "Szlak niezelektryfikowany",
"sbl-tooltip": "Szlak posiadający\nsamoczynną blokadę liniową",
"delayed": "Opóźniony: ", "delayed": "Opóźniony: ",
"preponed": "Przed czasem: ", "preponed": "Przed czasem: ",
@@ -393,9 +398,10 @@
"driver-return-link": "POWRÓT", "driver-return-link": "POWRÓT",
"driver-not-found-header": "Nie znaleziono pociągu! :/", "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-1": "Ten pociąg prawdopodobnie zakończył już swój bieg, zmienił numer lub jest offline.",
"driver-not-found-desc-2": "Historię rozkładów jazdy możesz przejrzeć w", "driver-not-found-desc-2": "Historię rozkładów jazdy możesz przejrzeć w",
"driver-not-found-journal": "DZIENNIKU RJ", "driver-not-found-journal": "DZIENNIKU RJ",
"driver-not-found-others": "Gracz {driver} jest online jako:",
"driver-not-found-return": "WRÓĆ NA STRONĘ GŁÓWNĄ" "driver-not-found-return": "WRÓĆ NA STRONĘ GŁÓWNĄ"
}, },
"train-stats": { "train-stats": {
@@ -440,11 +446,16 @@
"timetable-abandoned": "PORZUCONY", "timetable-abandoned": "PORZUCONY",
"timetable-online-button": "RJ ONLINE", "timetable-online-button": "RJ ONLINE",
"stock-info": "SZCZEGÓŁY", "entry-details": "SZCZEGÓŁY",
"no-entry-details": "BRAK DOSTĘPNYCH SZCZEGÓŁÓW",
"stock-length": "Długość", "stock-length": "Długość",
"stock-mass": "Masa", "stock-mass": "Masa",
"stock-max-speed": "Prędkość maks.", "stock-max-speed": "Prędkość maks.",
"stock-dangers": "DODATKOWE UWAGI",
"stock-preview": "PODGLĄD SKŁADU",
"load-data": "Pobierz dalszą historię...", "load-data": "Pobierz dalszą historię...",
"last-seen-at": "Ostatnio widziany na: ", "last-seen-at": "Ostatnio widziany na: ",
+6 -2
View File
@@ -57,6 +57,10 @@ export default defineComponent({
: ''; : '';
}, },
dateStringToTimestamp(dateString?: string) {
return dateString ? new Date(dateString).getTime() : 0;
},
calculateDuration(timestampMs: number, showSeconds = false) { calculateDuration(timestampMs: number, showSeconds = false) {
const secondsTotal = Math.floor(timestampMs / 1000); const secondsTotal = Math.floor(timestampMs / 1000);
const minsTotal = Math.round(timestampMs / 60000); const minsTotal = Math.round(timestampMs / 60000);
@@ -70,8 +74,8 @@ export default defineComponent({
minsInHour minsInHour
)}` )}`
: showSeconds && secondsTotal <= 60 : showSeconds && secondsTotal <= 60
? this.$t('journal.seconds', { value: secondsTotal }, secondsTotal) ? this.$t('journal.seconds', { value: secondsTotal }, secondsTotal)
: this.$t('journal.minutes', { value: minsTotal }, minsTotal); : this.$t('journal.minutes', { value: minsTotal }, minsTotal);
} }
} }
}); });
+2 -7
View File
@@ -69,7 +69,8 @@ const router = createRouter({
if ( if (
(to.name == 'SceneryView' || to.name == 'DriverView') && (to.name == 'SceneryView' || to.name == 'DriverView') &&
from.name !== to.name && from.name !== to.name &&
from.query['view'] === undefined from.query['view'] === undefined &&
!savedPosition
) )
return { el: `.app_main`, top: -15 }; return { el: `.app_main`, top: -15 };
@@ -79,10 +80,4 @@ const router = createRouter({
routes routes
}); });
router.beforeEach((to, from, next) => {
next((vm) => {
(vm as any)['xd'] = 'xd';
});
});
export default router; export default router;
+16 -27
View File
@@ -19,6 +19,7 @@ export const useApiStore = defineStore('apiStore', {
sceneryData: [] as StationJSONData[], sceneryData: [] as StationJSONData[],
nextUpdateTime: 0, nextUpdateTime: 0,
nextDataCheckTime: 0,
client: undefined as AxiosInstance | undefined, client: undefined as AxiosInstance | undefined,
@@ -48,17 +49,26 @@ export const useApiStore = defineStore('apiStore', {
}, },
async connectToAPI() { async connectToAPI() {
// Static data
this.fetchDonatorsData();
this.fetchStationsGeneralInfo();
this.fetchVehiclesInfo();
window.requestAnimationFrame(this.updateTick); window.requestAnimationFrame(this.updateTick);
}, },
updateTick(t: number) { updateTick(t: number) {
if (this.dataStatuses.connection == Status.Data.Offline) return; if (this.dataStatuses.connection == Status.Data.Offline) return;
// Static data refresh
if (t >= this.nextDataCheckTime) {
this.fetchDonatorsData();
this.fetchVehiclesInfo();
// Revalidation after staling
this.fetchStationsGeneralInfo().then(() => {
this.fetchStationsGeneralInfo();
});
this.nextDataCheckTime = t + 3600000;
}
// Active data fefresh
if (t >= this.nextUpdateTime) { if (t >= this.nextUpdateTime) {
this.fetchActiveData(); this.fetchActiveData();
this.nextUpdateTime = t + 20000; this.nextUpdateTime = t + 20000;
@@ -68,17 +78,6 @@ export const useApiStore = defineStore('apiStore', {
}, },
async fetchActiveData() { async fetchActiveData() {
// if (import.meta.env.VITE_API_ACTIVE_DATA_MODE == 'mocking') {
// import('../../tests/data/getActiveData.json').then((data) => {
// console.warn('activeData: mocking mode');
// this.activeData = data.default as API.ActiveData.Response;
// this.dataStatuses.connection = Status.Data.Loaded;
// });
// return;
// }
if (!this.activeData) this.dataStatuses.connection = Status.Data.Loading; if (!this.activeData) this.dataStatuses.connection = Status.Data.Loading;
try { try {
@@ -105,7 +104,7 @@ export const useApiStore = defineStore('apiStore', {
async fetchStationsGeneralInfo() { async fetchStationsGeneralInfo() {
try { try {
const sceneryData: StationJSONData[] = ( const sceneryData: StationJSONData[] = (
await this.client!.get<StationJSONData[]>('api/getSceneries') await this.client!.get<StationJSONData[]>(`api/getSceneries`)
).data; ).data;
this.dataStatuses.sceneries = Status.Data.Loaded; this.dataStatuses.sceneries = Status.Data.Loaded;
@@ -117,16 +116,6 @@ export const useApiStore = defineStore('apiStore', {
}, },
async fetchVehiclesInfo() { async fetchVehiclesInfo() {
// if (import.meta.env.VITE_API_VEHICLES_MODE == 'mocking') {
// import('../../tests/data/vehicles.json').then((data) => {
// console.warn('vehicles.json: mocking mode');
// this.vehiclesData = data.default;
// this.dataStatuses.vehicles = Status.Data.Loaded;
// });
// return;
// }
try { try {
const response = await this.client!.get<API.Vehicles.Response>('api/getVehicles'); const response = await this.client!.get<API.Vehicles.Response>('api/getVehicles');
+2 -1
View File
@@ -94,7 +94,8 @@ 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(), warningNotes: timetable.warningNotes,
timetablePath: timetable.path.split(';').map((pathElementString) => { timetablePath: timetable.path.split(';').map((pathElementString) => {
const [arrival, station, departure] = pathElementString.split(','); const [arrival, station, departure] = pathElementString.split(',');
+3 -3
View File
@@ -11,8 +11,8 @@
.list_wrapper { .list_wrapper {
overflow-y: auto; overflow-y: auto;
height: 90vh; height: calc(100vh - 12.5em);
min-height: 650px; min-height: 500px;
margin-top: 0.5em; margin-top: 0.5em;
position: relative; position: relative;
@@ -20,7 +20,7 @@
} }
.journal_wrapper { .journal_wrapper {
max-width: 1500px; max-width: var(--max-container-width);
width: 100%; width: 100%;
margin: 0 auto; margin: 0 auto;
+2 -2
View File
@@ -1,5 +1,6 @@
@import 'variables.scss'; @import 'variables.scss';
@import 'responsive.scss'; @import 'responsive.scss';
@import 'badge.scss';
.stats-tab { .stats-tab {
position: absolute; position: absolute;
@@ -7,7 +8,6 @@
z-index: 99; z-index: 99;
transform: translateY(1em); transform: translateY(1em);
width: 100%; width: 100%;
background-color: #1a1a1a; background-color: #1a1a1a;
@@ -29,7 +29,7 @@ hr.section-separator {
.info-stats { .info-stats {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: center;
gap: 0.5em; gap: 0.5em;
} }
+2 -2
View File
@@ -41,11 +41,11 @@ $animType: ease-in-out;
} }
&-enter-active { &-enter-active {
transition: all $animDuration ease-out; transition: all $animDuration ease-in-out;
} }
&-leave-active { &-leave-active {
transition: all $animDuration ease-out; transition: all $animDuration ease-in-out;
} }
} }
+1
View File
@@ -1,4 +1,5 @@
@import 'variables.scss'; @import 'variables.scss';
@import 'responsive.scss';
.badge { .badge {
font-weight: 600; font-weight: 600;
+12
View File
@@ -227,6 +227,10 @@ a.a-button {
&:hover { &:hover {
background-color: #424242; background-color: #424242;
} }
&:disabled {
opacity: 0.75;
}
} }
&.btn--option { &.btn--option {
@@ -345,3 +349,11 @@ a.a-button {
width: 100%; width: 100%;
} }
} }
.g-separator {
display: block;
width: 100%;
height: 2px;
background-color: #aaa;
margin: 0.5em 0;
}
+11 -11
View File
@@ -17,13 +17,13 @@ export namespace API {
trainsAPI: APIDataStatus; trainsAPI: APIDataStatus;
dispatchersAPI: APIDataStatus; dispatchersAPI: APIDataStatus;
sceneryRequirementsAPI: APIDataStatus; sceneryRequirementsAPI: APIDataStatus;
caches: APICache[];
} }
export interface Response { export interface Response {
activeSceneries?: API.ActiveSceneries.Response; activeSceneries?: API.ActiveSceneries.Response;
trains?: API.ActiveTrains.Response; trains?: API.ActiveTrains.Response;
apiStatuses?: APIStatuses; apiStatuses?: APIStatuses;
caches: APICache[];
} }
} }
@@ -204,6 +204,7 @@ export namespace API {
sceneries: string[]; sceneries: string[];
path: string; path: string;
warningNotes: string | null;
} }
} }
@@ -246,8 +247,6 @@ export namespace API {
authorName?: string; authorName?: string;
authorId?: number; authorId?: number;
stopsString?: string;
stockString?: string; stockString?: string;
stockHistory: string[]; stockHistory: string[];
@@ -255,17 +254,18 @@ export namespace API {
stockLength?: number; stockLength?: number;
maxSpeed?: number; maxSpeed?: number;
hashesString?: string;
currentSceneryName?: string; currentSceneryName?: string;
currentSceneryHash?: string; currentSceneryHash?: string;
routeSceneries?: string; routeSceneries: string;
checkpointArrivals?: string[]; checkpointArrivals: string[];
checkpointDepartures?: string[]; checkpointDepartures: string[];
checkpointArrivalsScheduled?: string[]; checkpointArrivalsScheduled: string[];
checkpointDeparturesScheduled?: string[]; checkpointDeparturesScheduled: string[];
checkpointStopTypes?: string[]; checkpointStopTypes: string[];
visitedSceneries?: string[]; visitedSceneries: string[];
sceneryNames: string[];
path: string; path: string;
warningNotes: string | null;
} }
export type Response = Data[]; export type Response = Data[];
+15 -12
View File
@@ -72,19 +72,22 @@ export interface Train {
isTimeout: boolean; isTimeout: boolean;
isSupporter: boolean; isSupporter: boolean;
driverRouteLocation: RouteLocationRaw, driverRouteLocation: RouteLocationRaw;
timetableData?: { timetableData?: TrainTimetableData;
timetableId: number; }
category: string;
route: string; export interface TrainTimetableData {
followingStops: TrainStop[]; timetableId: number;
TWR: boolean; category: string;
SKR: boolean; route: string;
routeDistance: number; followingStops: TrainStop[];
sceneries: string[]; TWR: boolean;
timetablePath: TimetablePathElement[]; SKR: boolean;
}; routeDistance: number;
sceneries: string[];
timetablePath: TimetablePathElement[];
warningNotes: string | null;
} }
export interface Station { export interface Station {
+50 -12
View File
@@ -3,6 +3,13 @@
<div class="view-wrapper"> <div class="view-wrapper">
<div v-if="chosenTrain"> <div v-if="chosenTrain">
<div class="actions"> <div class="actions">
<a class="a-button btn--image" @click="$router.back()">
<img src="/images/icon-back.svg" alt="train icon" />
<span>
{{ $t('trains.driver-return-link') }}
</span>
</a>
<router-link <router-link
:to="`/journal/timetables?search-driver=${chosenTrain.driverName}`" :to="`/journal/timetables?search-driver=${chosenTrain.driverName}`"
class="a-button btn--image" class="a-button btn--image"
@@ -24,7 +31,8 @@
<div v-else class="driver-not-found"> <div v-else class="driver-not-found">
<h2>&olcross; {{ $t('trains.driver-not-found-header') }}</h2> <h2>&olcross; {{ $t('trains.driver-not-found-header') }}</h2>
<p>
<p class="text--grayed">
{{ $t('trains.driver-not-found-desc-1') }} <br /> {{ $t('trains.driver-not-found-desc-1') }} <br />
{{ $t('trains.driver-not-found-desc-2') }} {{ $t('trains.driver-not-found-desc-2') }}
<router-link to="/journal/timetables" <router-link to="/journal/timetables"
@@ -32,25 +40,44 @@
>! >!
</p> </p>
<router-link to="/">&lt;&lt; {{ $t('trains.driver-not-found-return') }}</router-link> <p v-if="props.trainId && otherDriverTrains.length > 0">
<i18n-t keypath="trains.driver-not-found-others">
<template v-slot:driver>
<b>{{ otherDriverTrains[0].driverName }}</b>
</template>
</i18n-t>
</p>
<div class="other-driver-trains">
<template v-for="(train, i) in otherDriverTrains">
<router-link :to="`/driver?trainId=${train.id}`">
{{ train.trainNo }}
| {{ regionsJSON.find((r) => r.id == train.region)?.name ?? 'PL1' }}
</router-link>
</template>
</div>
<div style="margin-top: 1em">
<router-link to="/">&lt;&lt; {{ $t('trains.driver-not-found-return') }}</router-link>
</div>
</div> </div>
</div> </div>
</section> </section>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, onActivated, onMounted, useAttrs } from 'vue'; import { computed } from 'vue';
import TrainInfo from '../components/TrainsView/TrainInfo.vue'; import TrainInfo from '../components/TrainsView/TrainInfo.vue';
import TrainSchedule from '../components/TrainsView/TrainSchedule.vue'; import TrainSchedule from '../components/TrainsView/TrainSchedule.vue';
import Loading from '../components/Global/Loading.vue'; import Loading from '../components/Global/Loading.vue';
import { useMainStore } from '../store/mainStore'; import { useMainStore } from '../store/mainStore';
import { useApiStore } from '../store/apiStore'; import { useApiStore } from '../store/apiStore';
import { Status } from '../typings/common'; import { Status } from '../typings/common';
import { regions as regionsJSON } from '../data/options.json';
const props = defineProps({ const props = defineProps({
trainId: { trainId: {
type: String, type: String
required: true
}, },
modalId: { modalId: {
@@ -65,8 +92,12 @@ const chosenTrain = computed(() =>
mainStore.trainList.find((train) => train.id == props.trainId || train.modalId == props.modalId) mainStore.trainList.find((train) => train.id == props.trainId || train.modalId == props.modalId)
); );
onActivated(() => { const otherDriverTrains = computed(() => {
console.log(); return mainStore.trainList.filter(
(train) =>
train.driverId == Number(props.trainId?.split('|')[0]) &&
(train.timetableData || train.online || train.lastSeen >= Date.now() - 60000)
);
}); });
</script> </script>
@@ -78,14 +109,14 @@ $viewBgCol: #1a1a1a;
.driver-view { .driver-view {
margin: 0 auto; margin: 0 auto;
padding: 1em 0; padding: 1em 0;
max-width: 2000px; max-width: var(--max-container-width);
min-height: 95vh; min-height: calc(100vh - 7em);
} }
.actions { .actions {
display: flex; display: flex;
align-items: flex-end; align-items: flex-end;
justify-content: flex-end; justify-content: space-between;
gap: 0.5em; gap: 0.5em;
} }
@@ -108,10 +139,10 @@ $viewBgCol: #1a1a1a;
background-color: $viewBgCol; background-color: $viewBgCol;
text-align: center; text-align: center;
padding: 1em; padding: 1em;
border-radius: 0.5em 0.5em;
p { p {
padding: 1em 0; padding: 0.5em 0;
color: #aaa;
} }
a { a {
@@ -120,6 +151,13 @@ $viewBgCol: #1a1a1a;
} }
} }
.other-driver-trains {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 0.5em;
}
@include smallScreen { @include smallScreen {
.actions > a > span.hidable { .actions > a > span.hidable {
display: none; display: none;
-8
View File
@@ -306,14 +306,6 @@ export default defineComponent({
this.searchersValues[v as Journal.TimetableSearchKey] = options[v] ?? ''; this.searchersValues[v as Journal.TimetableSearchKey] = options[v] ?? '';
}); });
// this.searchersValues['search-date'] = options['search-date'] ?? '';
// this.searchersValues['search-driver'] = options['search-driver'] ?? '';
// this.searchersValues['search-train'] = options['search-train'] ?? '';
// this.searchersValues['search-dispatcher'] = options['search-dispatcher'] ?? '';
// this.searchersValues['search-issuedFrom'] = options['search-issuedFrom'] ?? '';
// this.searchersValues['search-via'] = options['search-via'] ?? '';
// this.searchersValues['search-terminatingAt'] = options['search-terminatingAt'] ?? '';
this.sorterActive.id = this.sorterActive.id =
(options['sorter-active'] as Journal.TimetableSorterKey) ?? 'timetableId'; (options['sorter-active'] as Journal.TimetableSorterKey) ?? 'timetableId';
+1 -1
View File
@@ -234,7 +234,7 @@ button.back-btn {
padding: 1em 0.5em; padding: 1em 0.5em;
height: calc(100vh - 0.5em); height: calc(100vh - 0.5em);
min-height: 800px; min-height: 500px;
max-height: 2000px; max-height: 2000px;
} }
+4 -1
View File
@@ -101,7 +101,6 @@ export default defineComponent({
gap: 0.5em; gap: 0.5em;
position: relative; position: relative;
margin-bottom: 0.5em; margin-bottom: 0.5em;
} }
@@ -122,4 +121,8 @@ button.btn-donation {
} }
} }
} }
.count {
padding: 0.5em;
}
</style> </style>
+1 -2
View File
@@ -117,13 +117,12 @@ export default defineComponent({
@import '../styles/responsive.scss'; @import '../styles/responsive.scss';
.trains-view { .trains-view {
min-height: 600px;
position: relative; position: relative;
} }
.trains_wrapper { .trains_wrapper {
margin: 1rem auto; margin: 1rem auto;
max-width: 1500px; max-width: var(--max-container-width);
} }
.trains_topbar { .trains_topbar {