Compare commits

...

23 Commits

Author SHA1 Message Date
Spythere 425241c8e7 Merge pull request #118 from Spythere/development
v1.28.6
2025-01-11 13:22:13 +01:00
Spythere f24f961d52 fix: warning notes display for TN & PN in journal 2025-01-11 13:09:45 +01:00
Spythere 4718eeeaaf bump: v1.28.6 2025-01-11 00:21:11 +01:00
Spythere 931fd7b21b feat: copying a railway stock of active drivers and timetable journal 2025-01-11 00:20:58 +01:00
Spythere bb79c5033a chore: added icon packs 2025-01-10 23:20:24 +01:00
Spythere ee290788dc Merge pull request #117 from Spythere/development
v1.28.5
2024-12-23 16:50:34 +01:00
Spythere a87d1060d3 chore: adjusted christmas dates 2024-12-23 16:45:46 +01:00
Spythere 1804d6d0f0 bump: v1.28.5 2024-12-23 16:44:59 +01:00
Spythere 77250e30c7 fix: preview tooltip fallback image 2024-12-23 16:44:42 +01:00
Spythere c5aefd03b8 Merge pull request #116 from Spythere/development
1.28.4 - minor fixes & updates
2024-12-20 16:27:08 +01:00
Spythere 2ec4694bd3 restruct: train info & timetable code 2024-12-20 15:51:16 +01:00
Spythere 729f66bcdb chore: added changing logo to christmas version 2024-12-20 15:46:34 +01:00
Spythere b746843086 Merge pull request #115 from Spythere/development
v1.28.4
2024-11-15 19:00:00 +01:00
Spythere cbbd06fecd bump: v1.28.4 2024-11-15 18:52:21 +01:00
Spythere 11e99b6af0 hotfix: stations sorting by 'no limit' 2024-11-15 18:51:34 +01:00
Spythere 31f4a2e5b2 Merge pull request #114 from Spythere/development
v1.28.3
2024-10-22 21:30:53 +02:00
Spythere 22514c3263 bump: v.1.28.3 2024-10-22 21:22:37 +02:00
Spythere 0df673467c fix: image loading styles 2024-10-22 21:22:22 +02:00
Spythere 6377e13809 refactor: footer component 2024-10-22 21:13:23 +02:00
Spythere 13fa633db4 chore: updated api image source 2024-10-22 20:58:20 +02:00
Spythere dd9661551c fix: typo 2024-10-22 20:58:08 +02:00
Spythere 495012a5ca Merge pull request #113 from Spythere/development
hotfix: data fetching
2024-10-02 14:59:34 +02:00
Spythere 3cfccb1bb4 hotfix: data fetching 2024-10-02 14:58:58 +02:00
35 changed files with 8397 additions and 294 deletions
+5
View File
@@ -22,6 +22,11 @@
<link rel="icon" href="favicon.ico" /> <link rel="icon" href="favicon.ico" />
<link rel="stylesheet" href="fa/css/fontawesome.css" />
<link rel="stylesheet" href="fa/css/brands.css" />
<link rel="stylesheet" href="fa/css/regular.css" />
<link rel="stylesheet" href="fa/css/solid.css" />
<!-- Static OpenGraph meta --> <!-- Static OpenGraph meta -->
<meta name="description" content="Pomocnik maszynisty i dyżurnego symulatora Train Driver 2" /> <meta name="description" content="Pomocnik maszynisty i dyżurnego symulatora Train Driver 2" />
<meta property="og:url" content="https://stacjownik-td2.web.app/" /> <meta property="og:url" content="https://stacjownik-td2.web.app/" />
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "stacjownik", "name": "stacjownik",
"version": "1.28.2", "version": "1.28.6",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
File diff suppressed because it is too large Load Diff
+6243
View File
File diff suppressed because it is too large Load Diff
+19
View File
@@ -0,0 +1,19 @@
/*!
* Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2024 Fonticons, Inc.
*/
:root, :host {
--fa-style-family-classic: 'Font Awesome 6 Free';
--fa-font-regular: normal 400 1em/1 'Font Awesome 6 Free'; }
@font-face {
font-family: 'Font Awesome 6 Free';
font-style: normal;
font-weight: 400;
font-display: block;
src: url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.ttf") format("truetype"); }
.far,
.fa-regular {
font-weight: 400; }
+19
View File
@@ -0,0 +1,19 @@
/*!
* Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2024 Fonticons, Inc.
*/
:root, :host {
--fa-style-family-classic: 'Font Awesome 6 Free';
--fa-font-solid: normal 900 1em/1 'Font Awesome 6 Free'; }
@font-face {
font-family: 'Font Awesome 6 Free';
font-style: normal;
font-weight: 900;
font-display: block;
src: url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.ttf") format("truetype"); }
.fas,
.fa-solid {
font-weight: 900; }
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

+10 -16
View File
@@ -6,6 +6,7 @@
/> />
<Tooltip /> <Tooltip />
<AppHeader :current-lang="currentLang" @change-lang="changeLang" /> <AppHeader :current-lang="currentLang" @change-lang="changeLang" />
<main class="app_main"> <main class="app_main">
@@ -16,21 +17,12 @@
</router-view> </router-view>
</main> </main>
<footer class="app_footer"> <AppFooter
&copy; :version="VERSION"
<a href="https://td2.info.pl/profile/?u=20777" target="_blank">Spythere</a> :is-on-production-host="isOnProductionHost"
{{ new Date().getUTCFullYear() }} | :is-update-card-open="isUpdateCardOpen"
<button class="btn--text" @click="() => (isUpdateCardOpen = true)"> @open-update-card="() => (isUpdateCardOpen = true)"
v{{ VERSION }}{{ isOnProductionHost ? '' : 'dev' }} />
</button>
<br />
<a href="https://discord.gg/x2mpNN3svk">
<img src="/images/icon-discord.png" alt="" />&nbsp;<b>{{ $t('footer.discord') }}</b>
</a>
<div style="display: none">&int; ukryta taktyczna całka do programowania w HTMLu</div>
</footer>
</div> </div>
</template> </template>
@@ -38,7 +30,7 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import axios from 'axios'; import axios from 'axios';
import { version } from '.././package.json'; import { version } from '../package.json';
import { Status } from './typings/common'; import { Status } from './typings/common';
import { useMainStore } from './store/mainStore'; import { useMainStore } from './store/mainStore';
import { useApiStore } from './store/apiStore'; import { useApiStore } from './store/apiStore';
@@ -51,6 +43,7 @@ import Tooltip from './components/Tooltip/Tooltip.vue';
import UpdateCard from './components/App/UpdateCard.vue'; import UpdateCard from './components/App/UpdateCard.vue';
import StorageManager from './managers/storageManager'; import StorageManager from './managers/storageManager';
import AppFooter from './components/App/AppFooter.vue';
const STORAGE_VERSION_KEY = 'app_version'; const STORAGE_VERSION_KEY = 'app_version';
@@ -59,6 +52,7 @@ export default defineComponent({
Clock, Clock,
StatusIndicator, StatusIndicator,
AppHeader, AppHeader,
AppFooter,
UpdateCard, UpdateCard,
Tooltip Tooltip
}, },
+41
View File
@@ -0,0 +1,41 @@
<template>
<footer class="app_footer">
&copy;
<a href="https://td2.info.pl/profile/?u=20777" target="_blank">Spythere</a>
{{ new Date().getUTCFullYear() }} |
<button class="btn--text" @click="openUpdateCard">
v{{ version }}{{ isOnProductionHost ? '' : 'dev' }}
</button>
<br />
<a href="https://discord.gg/x2mpNN3svk">
<img src="/images/icon-discord.png" alt="" />&nbsp;<b>{{ $t('footer.discord') }}</b>
</a>
<div style="display: none">&int; ukryta taktyczna całka do programowania w HTMLu</div>
</footer>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
emits: ['openUpdateCard'],
props: {
isUpdateCardOpen: {
type: Boolean,
required: true
},
version: String,
isOnProductionHost: Boolean
},
methods: {
openUpdateCard() {
this.$emit('openUpdateCard');
}
}
});
</script>
<style scoped></style>
+16 -3
View File
@@ -18,7 +18,12 @@
<span class="header_brand"> <span class="header_brand">
<router-link to="/"> <router-link to="/">
<img src="/images/stacjownik-header-logo.svg" alt="Stacjownik" /> <img
v-if="isChristmas"
src="/images/stacjownik-header-logo-christmas.svg"
alt="Stacjownik logo (christmas)"
/>
<img v-else src="/images/stacjownik-header-logo.svg" alt="Stacjownik logo" />
</router-link> </router-link>
</span> </span>
@@ -69,7 +74,10 @@ import Clock from './Clock.vue';
import RegionDropdown from '../Global/RegionDropdown.vue'; import RegionDropdown from '../Global/RegionDropdown.vue';
export default defineComponent({ export default defineComponent({
components: { StatusIndicator, Clock, RegionDropdown },
emits: ['changeLang'], emits: ['changeLang'],
props: { props: {
currentLang: { currentLang: {
type: String, type: String,
@@ -98,9 +106,14 @@ export default defineComponent({
return this.store.activeSceneryList.filter( return this.store.activeSceneryList.filter(
(scenery) => scenery.region == this.store.region.id && scenery.dispatcherId != -1 (scenery) => scenery.region == this.store.region.id && scenery.dispatcherId != -1
).length; ).length;
},
isChristmas() {
const date = new Date();
return date.getUTCMonth() == 11 && date.getUTCDate() >= 20 && date.getUTCDate() <= 31;
} }
}, }
components: { StatusIndicator, Clock, RegionDropdown }
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
+1 -1
View File
@@ -8,7 +8,7 @@
<div class="stock-images"> <div class="stock-images">
<img <img
v-for="(thumbnailImage, imageIndex) in images" v-for="(thumbnailImage, imageIndex) in images"
:src="`https://static.spythere.eu/thumbnails/v2/${thumbnailImage}.png`" :src="`https://stacjownik.spythere.eu/static/thumbnails/${thumbnailImage}.png`"
height="60" height="60"
loading="lazy" loading="lazy"
data-tooltip-type="VehiclePreviewTooltip" data-tooltip-type="VehiclePreviewTooltip"
@@ -59,7 +59,7 @@
</span> </span>
</div> </div>
<div class="stock-dangers" v-if="timetable.twr || timetable.skr"> <div class="stock-dangers" v-if="timetable.warningNotes">
<div class="g-separator"></div> <div class="g-separator"></div>
<b>{{ $t('journal.stock-dangers') }}:</b> <b>{{ $t('journal.stock-dangers') }}:</b>
@@ -95,7 +95,11 @@
<div class="g-separator"></div> <div class="g-separator"></div>
<b>{{ $t('journal.stock-preview') }}:</b> <b>{{ $t('journal.stock-preview') }}:</b>
<div class="stock-history" v-if="stockHistory.length > 1"> <div class="stock-history">
<button class="btn btn--action" @click="copyStockToClipboard()">
<i class="fa-regular fa-copy"></i> {{ $t('journal.stock-copy') }}
</button>
<button <button
v-for="(sh, i) in stockHistory" v-for="(sh, i) in stockHistory"
:key="i" :key="i"
@@ -128,6 +132,7 @@ 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'; import EntryStops from './EntryStops.vue';
import { useI18n } from 'vue-i18n';
export default defineComponent({ export default defineComponent({
components: { StockList, EntryStops }, components: { StockList, EntryStops },
@@ -146,7 +151,8 @@ export default defineComponent({
}, },
data() { data() {
return { return {
currentHistoryIndex: 0 currentHistoryIndex: 0,
i18n: useI18n()
}; };
}, },
computed: { computed: {
@@ -167,6 +173,7 @@ export default defineComponent({
}; };
}); });
}, },
driverRouteLocation(): RouteLocationRaw | null { driverRouteLocation(): RouteLocationRaw | null {
if (this.timetable.terminated) return null; if (this.timetable.terminated) return null;
return { return {
@@ -185,6 +192,25 @@ export default defineComponent({
toggleExtraInfo() { toggleExtraInfo() {
this.$emit('toggleExtraInfo', this.timetable.id); this.$emit('toggleExtraInfo', this.timetable.id);
},
copyStockToClipboard() {
const currentStockString =
this.stockHistory[this.currentHistoryIndex]?.stockString ?? this.timetable.stockString;
if (!currentStockString) {
alert(this.i18n.t('journal.stock-clipboard-failure'));
return;
}
navigator.clipboard
.writeText(currentStockString)
.then(() => {
prompt(this.i18n.t('journal.stock-clipboard-success'), currentStockString);
})
.catch(() => {
alert(this.i18n.t('journal.stock-clipboard-failure'));
});
} }
} }
}); });
@@ -221,6 +221,10 @@ export default defineComponent({
.stop-name { .stop-name {
font-weight: bold; font-weight: bold;
color: #ccc; color: #ccc;
i {
display: none;
}
} }
.stop-date { .stop-date {
@@ -1,23 +1,18 @@
<template> <template>
<div class="tooltip-content"> <div class="tooltip-content">
<div v-if="imageState == 'loading'" class="loading-info"> <div class="image-box">
{{ $t('vehicle-preview.loading') }} <Loading v-if="imageState == 'loading'" class="loading-info" />
<img
v-if="tooltipStore.type"
@load="onImageLoad"
@error="onImageError"
width="300"
height="176"
:src="`https://stacjownik.spythere.eu/static/images/${vehicleName}--300px.jpg`"
/>
</div> </div>
<div v-if="imageState == 'error'">{{ $t('vehicle-preview.error') }}</div>
<img
v-if="tooltipStore.type"
@load="onImageLoad"
@error="onImageError"
width="300"
height="176"
class="rounded-md w-full h-auto"
:src="`https://static.spythere.eu/images/${vehicleName}--300px.jpg`"
/>
<div v-if="imageState == 'error'" class="error-placeholder"></div>
<div class="vehicle-name"> <div class="vehicle-name">
{{ vehicleName.replace(/_/g, ' ') }} {{ vehicleName.replace(/_/g, ' ') }}
<span v-if="vehicleCargo">({{ vehicleCargo.id }})</span> <span v-if="vehicleCargo">({{ vehicleCargo.id }})</span>
@@ -35,8 +30,11 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { useTooltipStore } from '../../store/tooltipStore'; import { useTooltipStore } from '../../store/tooltipStore';
import { useApiStore } from '../../store/apiStore'; import { useApiStore } from '../../store/apiStore';
import Loading from '../Global/Loading.vue';
export default defineComponent({ export default defineComponent({
components: { Loading },
data() { data() {
return { return {
tooltipStore: useTooltipStore(), tooltipStore: useTooltipStore(),
@@ -61,9 +59,12 @@ export default defineComponent({
}, },
onImageError(e: Event) { onImageError(e: Event) {
if (!e.target || !(e.target instanceof HTMLImageElement) || this.imageState == 'error')
return;
this.imageState = 'error'; this.imageState = 'error';
(e.target as HTMLElement).style.display = 'none'; e.target.src = '/images/no-vehicle-image.png';
} }
}, },
@@ -98,10 +99,16 @@ export default defineComponent({
border-radius: 0.5em; border-radius: 0.5em;
} }
.image-box {
position: relative;
min-height: 170px;
}
.loading-info { .loading-info {
position: absolute; position: absolute;
left: 50%; left: 50%;
transform: translateX(-50%); top: 35%;
transform: translate(-50%, -50%);
} }
img { img {
+10 -2
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="stop-name" v-html="stop.nameHtml"></span> <b class="stop-name">{{ stop.nameRaw }}</b>
</router-link> </router-link>
<span v-else class="stop-name" v-html="stop.nameHtml"></span> <span v-else class="stop-name">{{ stop.nameRaw }}</span>
<span <span
v-if="stop.position != 'begin'" v-if="stop.position != 'begin'"
@@ -118,6 +118,10 @@ s {
&.misc { &.misc {
background: gray; background: gray;
} }
i {
display: none;
}
} }
.date { .date {
@@ -139,6 +143,10 @@ s {
color: #aaa; color: #aaa;
padding: 0; padding: 0;
} }
i {
display: none;
}
} }
.stop { .stop {
+155 -206
View File
@@ -1,203 +1,183 @@
<template> <template>
<div class="train-info" :data-extended="extended"> <div class="train-info" :data-extended="extended">
<section class="train-general"> <div class="general-top-bar">
<div class="general-top-bar"> <div class="top-bar-header">
<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" class="train-badge twr"
v-if="train.timetableData?.TWR" v-if="train.timetableData?.TWR"
data-tooltip-type="BaseTooltip" data-tooltip-type="BaseTooltip"
:data-tooltip-content="$t('warnings.TWR')" :data-tooltip-content="$t('warnings.TWR')"
> >
TWR TWR
</span> </span>
<span <span
class="train-badge tn" class="train-badge tn"
v-if="train.timetableData?.hasDangerousCargo" v-if="train.timetableData?.hasDangerousCargo"
data-tooltip-type="BaseTooltip" data-tooltip-type="BaseTooltip"
:data-tooltip-content="$t('warnings.TN')" :data-tooltip-content="$t('warnings.TN')"
> >
TN TN
</span> </span>
<span <span
class="train-badge pn" class="train-badge pn"
v-if="train.timetableData?.hasExtraDeliveries" v-if="train.timetableData?.hasExtraDeliveries"
data-tooltip-type="BaseTooltip" data-tooltip-type="BaseTooltip"
:data-tooltip-content="$t('warnings.PN')" :data-tooltip-content="$t('warnings.PN')"
> >
PN PN
</span> </span>
<b
v-if="train.timetableData"
data-tooltip-type="BaseTooltip"
:data-tooltip-content="getCategoryExplanation(train.timetableData.category)"
class="text--primary tooltip-help"
>
{{ train.timetableData.category }}
</b>
<b class="train-number">{{ train.trainNo }}</b>
<span>&bull;</span>
<div class="train-driver">
<b <b
v-if="train.timetableData" class="level-badge driver"
data-tooltip-type="BaseTooltip" :style="calculateExpStyle(train.driverLevel, train.isSupporter)"
:data-tooltip-content="getCategoryExplanation(train.timetableData.category)"
class="text--primary tooltip-help"
> >
{{ train.timetableData.category }} {{ train.driverLevel < 2 ? 'L' : `${train.driverLevel}` }}
</b> </b>
<b class="train-number">{{ train.trainNo }}</b> <b
v-if="apiStore.donatorsData.includes(train.driverName)"
data-tooltip-type="DonatorTooltip"
:data-tooltip-content="$t('donations.driver-message')"
>
{{ train.driverName }}
<img src="/images/icon-diamond.svg" alt="donator diamond icon" />
</b>
<span>&bull;</span> <span v-else>{{ train.driverName }}</span>
<div class="train-driver">
<b
class="level-badge driver"
:style="calculateExpStyle(train.driverLevel, train.isSupporter)"
>
{{ train.driverLevel < 2 ? 'L' : `${train.driverLevel}` }}
</b>
<b
v-if="apiStore.donatorsData.includes(train.driverName)"
data-tooltip-type="DonatorTooltip"
:data-tooltip-content="$t('donations.driver-message')"
>
{{ train.driverName }}
<img src="/images/icon-diamond.svg" alt="donator diamond icon" />
</b>
<span v-else>{{ train.driverName }}</span>
</div>
</div> </div>
</div> </div>
</div>
<div class="general-timetable" v-if="train.timetableData"> <div class="general-timetable" v-if="train.timetableData">
<strong>{{ train.timetableData.route.replace('|', ' - ') }}</strong> <strong>{{ train.timetableData.route.replace('|', ' - ') }}</strong>
<span <span
v-if="getSceneriesWithComments(train.timetableData).length > 0" v-if="getSceneriesWithComments(train.timetableData).length > 0"
data-tooltip-type="BaseTooltip" data-tooltip-type="BaseTooltip"
:data-tooltip-content="`${$t('trains.timetable-comments')} (${getSceneriesWithComments( :data-tooltip-content="`${$t('trains.timetable-comments')} (${getSceneriesWithComments(
train.timetableData train.timetableData
)})`" )})`"
>
<img class="image-warning" src="/images/icon-warning.svg" />
</span>
</div>
<hr style="margin: 0.25em 0" />
<div class="general-stops" v-if="train.timetableData">
<span v-if="train.timetableData.followingStops.length > 2">
{{ $t('trains.via-title') }}
<span v-html="getTrainStopsHtml(train.timetableData.followingStops)"></span>
</span>
</div>
<div class="general-status">
<div class="status-timetable-progress" v-if="train.timetableData">
<ProgressBar :progressPercent="confirmedPercentage(train.timetableData.followingStops)" />
<span class="progress-distance">
<span>{{ currentDistance(train.timetableData.followingStops) }} km</span>
<span>/</span>
<span class="text--primary">{{ train.timetableData.routeDistance }} km </span>
<span>|</span>
<span v-html="currentDelay(train.timetableData.followingStops)"></span>
</span>
</div>
<div class="status-badges">
<div v-if="!train.currentStationHash" class="train-badge offline">
<img src="/images/icon-offline.svg" alt="offline train icon" />
{{ $t('trains.scenery-offline') }}
</div>
<div v-if="!train.online" class="train-badge offline">
<img src="/images/icon-offline.svg" alt="offline train icon" />
Offline {{ lastSeenMessage(train.lastSeen) }}
</div>
</div>
</div>
<div class="general-stats" v-if="extended">
<div>
<img src="/images/icon-length.svg" alt="length icon" />
{{ train.length }}m
</div>
<div>
<img src="/images/icon-mass.svg" alt="mass icon" />
{{ (train.mass / 1000).toFixed(1) }}t
</div>
<div>
<img src="/images/icon-speed.svg" alt="speed icon" />
{{ train.speed }} km/h
<span v-if="stockSpeedLimit != Infinity">
&bull;
<em
class="text--grayed"
style="text-decoration: underline dotted"
tabindex="0"
:data-tooltip="$t('trains.vmax-tooltip')"
>
{{ stockSpeedLimit }} km/h
</em>
</span>
</div>
</div>
<div class="text--grayed" style="margin-top: 0.25em">
{{ displayTrainPosition(train) }}
</div>
<div
class="train-dangers"
v-if="extended && train.timetableData && train.timetableData.warningNotes"
> >
<div class="dangers-badges"> <img class="image-warning" src="/images/icon-warning.svg" />
<div v-if="train.timetableData?.TWR"> </span>
<div class="train-badge twr">TWR</div> </div>
- {{ $t('warnings.TWR') }}
</div>
<div v-if="train.timetableData?.hasDangerousCargo"> <hr style="margin: 0.25em 0" />
<div class="train-badge tn">TN</div>
- {{ $t('warnings.TN') }}
</div>
<div v-if="train.timetableData?.hasExtraDeliveries"> <div class="general-stops" v-if="train.timetableData">
<div class="train-badge pn">PN</div> <span v-if="train.timetableData.followingStops.length > 2">
- {{ $t('warnings.PN') }} {{ $t('trains.via-title') }}
</div> <span v-html="getTrainStopsHtml(train.timetableData.followingStops)"></span>
</span>
</div>
<div class="general-status">
<div class="status-timetable-progress" v-if="train.timetableData">
<ProgressBar :progressPercent="confirmedPercentage(train.timetableData.followingStops)" />
<span class="progress-distance">
<span>{{ currentDistance(train.timetableData.followingStops) }} km</span>
<span>/</span>
<span class="text--primary">{{ train.timetableData.routeDistance }} km </span>
<span>|</span>
<span v-html="currentDelay(train.timetableData.followingStops)"></span>
</span>
</div>
<div class="status-badges">
<div v-if="!train.currentStationHash" class="train-badge offline">
<i class="fa-solid fa-shop-slash"></i>
{{ $t('trains.scenery-offline') }}
</div> </div>
<div class="dangers-notes"> <div v-if="!train.online" class="train-badge offline">
<h4>{{ $t('warnings.header-title') }}</h4> <i class="fa-solid fa-user-slash"></i>
<p> Offline {{ lastSeenMessage(train.lastSeen) }}
<i>{{ train.timetableData?.warningNotes }}</i>
</p>
</div> </div>
</div> </div>
</section> </div>
<section class="train-stats" v-if="!extended"> <div class="general-stats" v-if="extended">
<StockList :trainStockList="train.stockList" :tractionOnly="true" /> <div>
<img src="/images/icon-length.svg" alt="length icon" />
{{ train.length }}m
</div>
<div> <div>
<span>{{ train.speed }}km/h</span> <img src="/images/icon-mass.svg" alt="mass icon" />
{{ (train.mass / 1000).toFixed(1) }}t
</div>
<div> <div>
<span> {{ train.length }}m</span> <img src="/images/icon-speed.svg" alt="speed icon" />
{{ train.speed }} km/h
<span v-if="stockSpeedLimit != Infinity">
&bull; &bull;
<span> {{ (train.mass / 1000).toFixed(1) }}t</span> <em
<span v-if="train.stockList.length > 1"> class="text--grayed"
&bull; style="text-decoration: underline dotted"
{{ $t('trains.cars') }}: {{ train.stockList.length - 1 }} tabindex="0"
</span> :data-tooltip="$t('trains.vmax-tooltip')"
>
{{ stockSpeedLimit }} km/h
</em>
</span>
</div>
</div>
<div class="text--grayed" style="margin-top: 0.25em">
{{ displayTrainPosition(train) }}
</div>
<div
class="train-dangers"
v-if="extended && train.timetableData && train.timetableData.warningNotes"
>
<div class="dangers-badges">
<div v-if="train.timetableData?.TWR">
<div class="train-badge twr">TWR</div>
- {{ $t('warnings.TWR') }}
</div>
<div v-if="train.timetableData?.hasDangerousCargo">
<div class="train-badge tn">TN</div>
- {{ $t('warnings.TN') }}
</div>
<div v-if="train.timetableData?.hasExtraDeliveries">
<div class="train-badge pn">PN</div>
- {{ $t('warnings.PN') }}
</div> </div>
</div> </div>
</section>
<div class="dangers-notes">
<h4>{{ $t('warnings.header-title') }}</h4>
<p>
<i>{{ train.timetableData?.warningNotes }}</i>
</p>
</div>
</div>
</div> </div>
</template> </template>
@@ -256,7 +236,6 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/responsive.scss';
@import '../../styles/badge.scss'; @import '../../styles/badge.scss';
.image-warning { .image-warning {
@@ -265,17 +244,6 @@ export default defineComponent({
vertical-align: middle; vertical-align: middle;
} }
.train-stats {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
text-align: center;
line-height: 1.5em;
}
.train-dangers { .train-dangers {
margin-top: 0.5em; margin-top: 0.5em;
} }
@@ -299,15 +267,9 @@ export default defineComponent({
} }
.train-info { .train-info {
display: grid; display: flex;
grid-template-columns: 2fr 1fr; flex-direction: column;
grid-template-rows: 1fr; gap: 0.25em;
&[data-extended='true'] {
grid-template-columns: 1fr;
}
padding: 1em;
background-color: #1a1a1a; background-color: #1a1a1a;
gap: 0.5em; gap: 0.5em;
@@ -337,12 +299,6 @@ export default defineComponent({
padding: 0 0.25em; padding: 0 0.25em;
} }
.train-general {
display: flex;
flex-direction: column;
gap: 0.25em;
}
.general-stops { .general-stops {
font-size: 0.8em; font-size: 0.8em;
} }
@@ -419,11 +375,4 @@ export default defineComponent({
display: flex; display: flex;
gap: 0.25em; gap: 0.25em;
} }
@include smallScreen() {
.train-info {
grid-template-columns: 1fr;
gap: 1em 0;
}
}
</style> </style>
+1 -13
View File
@@ -1,7 +1,5 @@
<template> <template>
<div class="train-schedule" @click="toggleShowState"> <div class="train-schedule">
<StockList :trainStockList="train.stockList" />
<div class="schedule-wrapper" v-if="train.timetableData"> <div class="schedule-wrapper" v-if="train.timetableData">
<div class="stops"> <div class="stops">
<div <div
@@ -252,12 +250,6 @@ export default defineComponent({
return activeMinorStopList; return activeMinorStopList;
} }
},
methods: {
toggleShowState() {
this.$emit('click');
}
} }
}); });
</script> </script>
@@ -281,10 +273,6 @@ $blinkAnim: 0.5s ease-in-out alternate infinite blink;
} }
} }
.train-schedule {
padding: 1em;
}
.schedule-wrapper { .schedule-wrapper {
overflow-y: auto; overflow-y: auto;
width: 100%; width: 100%;
-1
View File
@@ -249,7 +249,6 @@ h3 {
} }
@include smallScreen { @include smallScreen {
h1,
.no-data { .no-data {
text-align: center; text-align: center;
} }
+8 -18
View File
@@ -13,15 +13,7 @@
</div> </div>
<transition-group name="list-anim" tag="ul"> <transition-group name="list-anim" tag="ul">
<li <TrainTableItem v-for="train in trains" :key="train.id" :train="train" />
class="train-row"
v-for="train in trains"
:key="train.id"
>
<router-link class="a-block" :to="train.driverRouteLocation">
<TrainInfo :train="train" :extended="false" />
</router-link>
</li>
</transition-group> </transition-group>
</div> </div>
</transition> </transition>
@@ -30,13 +22,15 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, inject, PropType, Ref } from 'vue'; import { defineComponent, inject, PropType, Ref } from 'vue';
import { useMainStore } from '../../store/mainStore'; import { useMainStore } from '../../store/mainStore';
import Loading from '../Global/Loading.vue';
import TrainInfo from './TrainInfo.vue';
import { Status, Train } from '../../typings/common';
import { useApiStore } from '../../store/apiStore'; import { useApiStore } from '../../store/apiStore';
import { Status, Train } from '../../typings/common';
import Loading from '../Global/Loading.vue';
import TrainTableItem from './TrainTableItem.vue';
import TrainInfo from './TrainInfo.vue';
export default defineComponent({ export default defineComponent({
components: { Loading, TrainInfo }, components: { Loading, TrainInfo, TrainTableItem },
props: { props: {
trains: { trains: {
@@ -99,9 +93,5 @@ export default defineComponent({
background: #1a1a1a; background: #1a1a1a;
} }
li.train-row {
background-color: var(--clr-secondary);
margin-bottom: 1em;
width: 100%;
}
</style> </style>
@@ -0,0 +1,76 @@
<template>
<li class="train-item">
<router-link class="a-block" :to="train.driverRouteLocation">
<div class="item-wrapper">
<TrainInfo :train="train" />
<div class="train-stats">
<StockList :trainStockList="train.stockList" :tractionOnly="true" />
<div>
<span>{{ train.speed }}km/h</span>
<div>
<span> {{ train.length }}m</span>
&bull;
<span> {{ (train.mass / 1000).toFixed(1) }}t</span>
<span v-if="train.stockList.length > 1">
&bull;
{{ $t('trains.cars') }}: {{ train.stockList.length - 1 }}
</span>
</div>
</div>
</div>
</div>
</router-link>
</li>
</template>
<script setup lang="ts">
import { PropType } from 'vue';
import { Train } from '../../typings/common';
import TrainInfo from './TrainInfo.vue';
import StockList from '../Global/StockList.vue';
defineProps({
train: {
type: Object as PropType<Train>,
required: true
}
});
</script>
<style lang="scss" scoped>
@import '../../styles/responsive.scss';
.train-item {
background-color: #1a1a1a;
margin-bottom: 1em;
width: 100%;
padding: 1em;
}
.item-wrapper {
display: grid;
grid-template-columns: 2fr 1fr;
grid-template-rows: 1fr;
}
.train-stats {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
text-align: center;
line-height: 1.5em;
}
@include smallScreen() {
.item-wrapper {
grid-template-columns: 1fr;
gap: 1em 0;
}
}
</style>
+8 -1
View File
@@ -423,7 +423,11 @@
"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-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",
"stock-copy": "COPY THE STOCK",
"stock-clipboard-success": "Successfully copied the railway stock in a text form to your clipboard!",
"stock-clipboard-failure": "Oops! Something happened and the railway stock couldn't be copied to your clipboard! :/"
}, },
"train-stats": { "train-stats": {
"stats-button": "STATISTICS", "stats-button": "STATISTICS",
@@ -478,6 +482,9 @@
"stock-dangers": "ADDITIONAL NOTES", "stock-dangers": "ADDITIONAL NOTES",
"stock-preview": "STOCK PREVIEW", "stock-preview": "STOCK PREVIEW",
"stock-copy": "COPY THE STOCK",
"stock-clipboard-success": "Successfully copied the railway stock in a text form to your clipboard:",
"stock-clipboard-failure": "Oops! Something happened and the railway stock couldn't be copied to your clipboard! :/",
"load-data": "Load further data...", "load-data": "Load further data...",
+9 -2
View File
@@ -63,7 +63,7 @@
"RP": "wojewódzki pospieszny", "RP": "wojewódzki pospieszny",
"RO": "wojewódzki osobowy", "RO": "wojewódzki osobowy",
"RM": "wojewódzki osobowy międzynarodowy", "RM": "wojewódzki osobowy międzynarodowy",
"RA": "wojewódzki osobowy algomeracyjny", "RA": "wojewódzki osobowy aglomeracyjny",
"PW": "pasażerski próżny - służbowy", "PW": "pasażerski próżny - służbowy",
"PX": "pasażerski próżny próbny", "PX": "pasażerski próżny próbny",
@@ -409,7 +409,11 @@
"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-others": "Gracz {driver} jest online jako:",
"driver-not-found-return": "WRÓĆ NA STRONĘ GŁÓWNĄ" "driver-not-found-return": "WRÓĆ NA STRONĘ GŁÓWNĄ",
"stock-copy": "SKOPIUJ SKŁAD",
"stock-clipboard-success": "Pomyślnie skopiowano skład w postaci tekstowej do schowka!",
"stock-clipboard-failure": "Ups! Nie udało się skopiować składu do schowka! :/"
}, },
"train-stats": { "train-stats": {
"stats-button": "STATYSTYKI", "stats-button": "STATYSTYKI",
@@ -462,6 +466,9 @@
"stock-dangers": "DODATKOWE UWAGI", "stock-dangers": "DODATKOWE UWAGI",
"stock-preview": "PODGLĄD SKŁADU", "stock-preview": "PODGLĄD SKŁADU",
"stock-copy": "SKOPIUJ SKŁAD",
"stock-clipboard-success": "Pomyślnie skopiowano skład w postaci tekstowej do schowka:",
"stock-clipboard-failure": "Ups! Nie udało się skopiować składu do schowka! :/",
"load-data": "Pobierz dalszą historię...", "load-data": "Pobierz dalszą historię...",
+1 -5
View File
@@ -59,11 +59,7 @@ export const useApiStore = defineStore('apiStore', {
if (t >= this.nextDataCheckTime) { if (t >= this.nextDataCheckTime) {
this.fetchDonatorsData(); this.fetchDonatorsData();
this.fetchVehiclesInfo(); this.fetchVehiclesInfo();
this.fetchStationsGeneralInfo();
// Revalidation after staling
this.fetchStationsGeneralInfo().then(() => {
this.fetchStationsGeneralInfo();
});
this.nextDataCheckTime = t + 3600000; this.nextDataCheckTime = t + 3600000;
} }
+5 -1
View File
@@ -216,16 +216,20 @@ export const useMainStore = defineStore('mainStore', {
return acc; return acc;
}, [] as ActiveScenery[]); }, [] as ActiveScenery[]);
const referenceTimestamp = Date.now();
const onlineActiveSceneries = apiStore.activeData?.activeSceneries.reduce((list, scenery) => { const onlineActiveSceneries = apiStore.activeData?.activeSceneries.reduce((list, scenery) => {
if (scenery.isOnline !== 1 && Date.now() - scenery.lastSeen > 1000 * 60 * 2) return list; if (scenery.isOnline !== 1 && Date.now() - scenery.lastSeen > 1000 * 60 * 2) return list;
if (scenery.dispatcherStatus == Status.ActiveDispatcher.UNKNOWN) return list; if (scenery.dispatcherStatus == Status.ActiveDispatcher.UNKNOWN) return list;
const dispatcherTimestamp = const dispatcherTimestamp =
scenery.dispatcherStatus == Status.ActiveDispatcher.NO_LIMIT scenery.dispatcherStatus == Status.ActiveDispatcher.NO_LIMIT
? Date.now() + 25500000 ? referenceTimestamp + 25500000
: scenery.dispatcherStatus > 5 : scenery.dispatcherStatus > 5
? scenery.dispatcherStatus ? scenery.dispatcherStatus
: null; : null;
list.push({ list.push({
name: scenery.stationName, name: scenery.stationName,
+1 -1
View File
@@ -227,7 +227,7 @@ a.a-button {
font-weight: bold; font-weight: bold;
&:hover { &:hover {
background-color: #424242; background-color: #505050;
} }
&:disabled { &:disabled {
+31 -1
View File
@@ -17,12 +17,19 @@
<span class="hidable"> <span class="hidable">
{{ $t('trains.driver-journal-link') }} {{ $t('trains.driver-journal-link') }}
</span> </span>
<img src="/images/icon-train.svg" alt="train icon" /> <img src="/images/icon-train.svg" alt="train icon" />
</router-link> </router-link>
</div> </div>
<div class="train-card"> <div class="train-card">
<TrainInfo :train="chosenTrain" :extended="true" ref="trainInfo" /> <TrainInfo :train="chosenTrain" :extended="true" />
<button class="btn btn--action" style="margin: 1em 0" @click="copyStockToClipboard()">
<i class="fa-regular fa-copy"></i> {{ $t('trains.stock-copy') }}
</button>
<StockList :trainStockList="chosenTrain.stockList" />
<TrainSchedule :train="chosenTrain" /> <TrainSchedule :train="chosenTrain" />
</div> </div>
</div> </div>
@@ -69,11 +76,13 @@
import { computed } 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 StockList from '../components/Global/StockList.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'; import { regions as regionsJSON } from '../data/options.json';
import { useI18n } from 'vue-i18n';
const props = defineProps({ const props = defineProps({
trainId: { trainId: {
@@ -88,6 +97,8 @@ const props = defineProps({
const mainStore = useMainStore(); const mainStore = useMainStore();
const apiStore = useApiStore(); const apiStore = useApiStore();
const i18n = useI18n();
const chosenTrain = computed(() => 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)
); );
@@ -99,6 +110,24 @@ const otherDriverTrains = computed(() => {
(train.timetableData || train.online || train.lastSeen >= Date.now() - 60000) (train.timetableData || train.online || train.lastSeen >= Date.now() - 60000)
); );
}); });
function copyStockToClipboard() {
const stockString = chosenTrain.value?.stockList.join(';');
if (!stockString) {
alert(i18n.t('trains.stock-clipboard-failure'));
return;
}
navigator.clipboard
.writeText(stockString)
.then(() => {
prompt(i18n.t('trains.stock-clipboard-success'), stockString);
})
.catch(() => {
alert(i18n.t('trains.stock-clipboard-failure'));
});
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@@ -131,6 +160,7 @@ $viewBgCol: #1a1a1a;
} }
.train-card { .train-card {
padding: 1em;
background-color: $viewBgCol; background-color: $viewBgCol;
border-radius: 0 0 0.5em 0.5em; border-radius: 0 0 0.5em 0.5em;
} }
+6
View File
@@ -133,4 +133,10 @@ export default defineComponent({
position: relative; position: relative;
margin-bottom: 0.5em; margin-bottom: 0.5em;
} }
@include smallScreen {
.trains_topbar {
justify-content: space-between;
}
}
</style> </style>
File diff suppressed because one or more lines are too long