Wersja 1.12

Merge produkcyjny do wersji 1.12.0
This commit is contained in:
Spythere
2023-02-14 21:32:46 +01:00
committed by GitHub
24 changed files with 485 additions and 345 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "stacjownik", "name": "stacjownik",
"version": "1.11.2", "version": "1.12.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
+1 -1
View File
@@ -46,7 +46,7 @@
font-size: 1rem; font-size: 1rem;
@include smallScreen() { @include smallScreen() {
font-size: calc(0.5rem + 1.1vw); font-size: calc(0.55rem + 1.1vw);
} }
@include screenLandscape() { @include screenLandscape() {
@@ -17,10 +17,10 @@
@keydown.enter="navigateToScenery(item.stationName, item.isOnline)" @keydown.enter="navigateToScenery(item.stationName, item.isOnline)"
tabindex="0" tabindex="0"
> >
<span> <span class="item-general">
<b <b
v-if="item.dispatcherLevel !== null" v-if="item.dispatcherLevel !== null"
class="dispatcher-level" class="level-badge dispatcher"
:style="calculateExpStyle(item.dispatcherLevel, item.dispatcherIsSupporter)" :style="calculateExpStyle(item.dispatcherLevel, item.dispatcherIsSupporter)"
> >
{{ item.dispatcherLevel >= 2 ? item.dispatcherLevel : 'L' }} {{ item.dispatcherLevel >= 2 ? item.dispatcherLevel : 'L' }}
@@ -31,7 +31,7 @@
<span class="region-badge" :class="item.region">PL1</span> <span class="region-badge" :class="item.region">PL1</span>
</span> </span>
<span> <span class="item-time">
<span :data-status="item.isOnline"> {{ item.isOnline ? $t('journal.online-since') : 'OFFLINE' }}&nbsp; </span> <span :data-status="item.isOnline"> {{ item.isOnline ? $t('journal.online-since') : 'OFFLINE' }}&nbsp; </span>
<span> <span>
{{ new Date(item.timestampFrom).toLocaleTimeString('pl-PL', { timeStyle: 'short' }) }} {{ new Date(item.timestampFrom).toLocaleTimeString('pl-PL', { timeStyle: 'short' }) }}
@@ -99,18 +99,9 @@ export default defineComponent({
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/animations.scss'; @import '../../styles/animations.scss';
@import '../../styles/responsive.scss'; @import '../../styles/responsive.scss';
@import '../../styles/badge.scss';
@import '../../styles/JournalSection.scss'; @import '../../styles/JournalSection.scss';
.region-badge {
padding: 0.1em 0.5em;
border-radius: 0.5em;
font-weight: bold;
&.eu {
background-color: forestgreen;
}
}
li.sticky { li.sticky {
position: sticky; position: sticky;
top: 0; top: 0;
@@ -141,6 +132,18 @@ li.sticky {
} }
} }
.item-general {
display: flex;
align-items: center;
gap: 0.25em;
flex-wrap: wrap;
.level-badge {
margin-right: 0.25em;
}
}
.journal_day { .journal_day {
margin-bottom: 1em; margin-bottom: 1em;
padding: 0.5em; padding: 0.5em;
@@ -157,16 +160,4 @@ li.sticky {
font-weight: bold; font-weight: bold;
} }
} }
.dispatcher-level {
display: inline-block;
text-align: center;
line-height: 1.45em;
width: 1.45em;
height: 1.45em;
margin-right: 0.45em;
border-radius: 0.25em;
}
</style> </style>
+7 -5
View File
@@ -11,7 +11,7 @@
{{ $t(tab.titlePath) }} {{ $t(tab.titlePath) }}
</button> </button>
</div> </div>
<div class="stats-tab" v-show="areStatsOpen"> <div class="stats-tab" v-show="areStatsOpen">
<keep-alive> <keep-alive>
<JournalDailyStats v-if="store.currentStatsTab == 'daily'" ref="dailyStatsComp" /> <JournalDailyStats v-if="store.currentStatsTab == 'daily'" ref="dailyStatsComp" />
@@ -35,7 +35,8 @@ type TStatTab = 'daily' | 'driver';
const store = useStore(); const store = useStore();
const dailyStatsComp: Ref<InstanceType<typeof JournalDailyStats> | null> = ref(null); const dailyStatsComp: Ref<InstanceType<typeof JournalDailyStats> | null> = ref(null);
const areStatsOpen = ref(true); const lastDailyStatsOpen = ref(false);
const areStatsOpen = ref(false);
const lastClickedTab = ref('daily'); const lastClickedTab = ref('daily');
let data = reactive({ let data = reactive({
@@ -54,9 +55,9 @@ let data = reactive({
// Methods // Methods
function onTabButtonClick(tab: TStatTab) { function onTabButtonClick(tab: TStatTab) {
if (lastClickedTab.value == tab || !areStatsOpen.value) { if (lastClickedTab.value == tab || !areStatsOpen.value) areStatsOpen.value = !areStatsOpen.value;
areStatsOpen.value = !areStatsOpen.value;
} if (tab == 'daily') lastDailyStatsOpen.value = areStatsOpen.value;
store.currentStatsTab = tab; store.currentStatsTab = tab;
lastClickedTab.value = tab; lastClickedTab.value = tab;
@@ -77,6 +78,7 @@ watch(
lastClickedTab.value = statsData ? 'driver' : 'daily'; lastClickedTab.value = statsData ? 'driver' : 'daily';
if (statsData) areStatsOpen.value = true; if (statsData) areStatsOpen.value = true;
if (!statsData) areStatsOpen.value = lastDailyStatsOpen.value;
} }
); );
</script> </script>
@@ -6,25 +6,34 @@
:key="timetable.id" :key="timetable.id"
> >
<div class="journal_item-info"> <div class="journal_item-info">
<div class="info-top"> <div class="info-general">
<span <span
class="general-train"
tabindex="0" tabindex="0"
@click="showTimetable(timetable)" @click="showTimetable(timetable)"
@keydown.enter="showTimetable(timetable)" @keydown.enter="showTimetable(timetable)"
style="cursor: pointer" style="cursor: pointer"
> >
<b class="text--primary">{{ timetable.trainCategoryCode }}&nbsp;</b>
<b>{{ timetable.trainNo }}</b>
| <span>{{ timetable.driverName }}</span> |
<span class="text--grayed">#{{ timetable.id }}</span> <span class="text--grayed">#{{ timetable.id }}</span>
<span v-if="timetable.driverLevel !== null"> <span>
| <strong class="text--primary">
<b :style="calculateTextExpStyle(timetable.driverLevel, timetable.driverIsSupporter)"> {{ timetable.trainCategoryCode }}
{{ timetable.driverLevel < 2 ? 'L' : `${timetable.driverLevel} lvl` }} </strong>
</b> <strong>&nbsp;{{ timetable.trainNo }}</strong>
</span> </span>
&bull;
<strong
v-if="timetable.driverLevel !== null"
class="level-badge driver"
:style="calculateExpStyle(timetable.driverLevel, timetable.driverIsSupporter)"
>
{{ timetable.driverLevel < 2 ? 'L' : `${timetable.driverLevel}` }}
</strong>
<strong>{{ timetable.driverName }}</strong>
</span> </span>
<span>
<span class="general-time">
<b class="info-date">{{ localeDay(timetable.beginDate, $i18n.locale) }}</b> <b class="info-date">{{ localeDay(timetable.beginDate, $i18n.locale) }}</b>
<b <b
class="info-status" class="info-status"
@@ -86,6 +95,7 @@
<b>{{ timetable.authorName }}</b> <b>{{ timetable.authorName }}</b>
</router-link> </router-link>
</div> </div>
<button <button
v-if="timetable.stockString" v-if="timetable.stockString"
class="btn--option btn--show" class="btn--option btn--show"
@@ -94,6 +104,7 @@
{{ $t('journal.stock-info') }} {{ $t('journal.stock-info') }}
<img :src="getIcon(`arrow-${item.showStock.value ? 'asc' : 'desc'}`)" alt="Arrow" /> <img :src="getIcon(`arrow-${item.showStock.value ? 'asc' : 'desc'}`)" alt="Arrow" />
</button> </button>
<div class="info-extended" v-if="timetable.stockString && item.showStock.value"> <div class="info-extended" v-if="timetable.stockString && item.showStock.value">
<hr /> <hr />
<div> <div>
@@ -189,6 +200,7 @@ export default defineComponent({
}, },
showTimetable(timetable: TimetableHistory) { showTimetable(timetable: TimetableHistory) {
if (!timetable) return;
if (timetable.terminated) return; if (timetable.terminated) return;
this.selectModalTrain(timetable.driverName + timetable.trainNo.toString()); this.selectModalTrain(timetable.driverName + timetable.trainNo.toString());
@@ -209,7 +221,6 @@ export default defineComponent({
@import '../../styles/badge.scss'; @import '../../styles/badge.scss';
@import '../../styles/JournalSection.scss'; @import '../../styles/JournalSection.scss';
hr { hr {
margin: 0.25em 0; margin: 0.25em 0;
} }
@@ -236,10 +247,14 @@ hr {
} }
} }
&-top { &-general {
display: flex; display: flex;
flex-wrap: wrap;
justify-content: space-between; justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 0.5em;
margin-bottom: 0.5em;
} }
&-route { &-route {
@@ -251,6 +266,12 @@ hr {
} }
} }
.general-train {
display: flex;
align-items: center;
gap: 0.25em;
}
ul.stock-list { ul.stock-list {
display: flex; display: flex;
align-items: flex-end; align-items: flex-end;
@@ -291,14 +312,9 @@ ul.stock-list {
} }
@include smallScreen { @include smallScreen {
.info-top { .info-general {
flex-direction: column; flex-direction: column;
span {
margin: 0.1em auto;
}
} }
.info-extended { .info-extended {
text-align: center; text-align: center;
} }
@@ -5,25 +5,31 @@
<div class="list-warning" v-else-if="dispatcherHistoryList.length == 0">{{ $t('scenery.history-list-empty') }}</div> <div class="list-warning" v-else-if="dispatcherHistoryList.length == 0">{{ $t('scenery.history-list-empty') }}</div>
<ul class="history-list" v-else> <ul class="history-list" v-else>
<li class="list-item" v-for="historyItem in dispatcherHistoryList"> <li class="list-item" v-for="item in dispatcherHistoryList">
<div> <router-link class="item-general" :to="`/journal/dispatchers?dispatcherName=${item.dispatcherName}`">
<router-link :to="`/journal/dispatchers?dispatcherName=${historyItem.dispatcherName}`"> <span class="text--grayed">#{{ item.stationHash }}&nbsp;</span>
<span class="text--grayed">#{{ historyItem.stationHash }}&nbsp;</span> <b
<b>{{ historyItem.dispatcherName }}</b> v-if="item.dispatcherLevel !== null"
</router-link> class="level-badge dispatcher"
</div> :style="calculateExpStyle(item.dispatcherLevel, item.dispatcherIsSupporter)"
>
{{ item.dispatcherLevel >= 2 ? item.dispatcherLevel : 'L' }}
</b>
<div v-if="historyItem.timestampTo"> <b>{{ item.dispatcherName }}</b>
<b>{{ $d(historyItem.timestampFrom) }}</b> </router-link>
{{ timestampToString(historyItem.timestampFrom) }} <div v-if="item.timestampTo">
- {{ timestampToString(historyItem.timestampTo) }} ({{ calculateDuration(historyItem.currentDuration) }}) <b>{{ $d(item.timestampFrom) }}</b>
{{ timestampToString(item.timestampFrom) }}
- {{ timestampToString(item.timestampTo) }} ({{ calculateDuration(item.currentDuration) }})
</div> </div>
<div class="dispatcher-online" v-else> <div class="dispatcher-online" v-else>
{{ $t('journal.online-since') }} {{ $t('journal.online-since') }}
<b>{{ timestampToString(historyItem.timestampFrom) }}</b> <b>{{ timestampToString(item.timestampFrom) }}</b>
({{ calculateDuration(historyItem.currentDuration) }}) ({{ calculateDuration(item.currentDuration) }})
</div> </div>
</li> </li>
</ul> </ul>
@@ -39,10 +45,11 @@ import { DispatcherHistory } from '../../scripts/interfaces/api/DispatchersAPIDa
import Station from '../../scripts/interfaces/Station'; import Station from '../../scripts/interfaces/Station';
import { URLs } from '../../scripts/utils/apiURLs'; import { URLs } from '../../scripts/utils/apiURLs';
import Loading from '../Global/Loading.vue'; import Loading from '../Global/Loading.vue';
import styleMixin from '../../mixins/styleMixin';
export default defineComponent({ export default defineComponent({
name: 'SceneryDispatchersHistory', name: 'SceneryDispatchersHistory',
mixins: [dateMixin], mixins: [dateMixin, styleMixin],
props: { props: {
station: { station: {
type: Object as PropType<Station>, type: Object as PropType<Station>,
@@ -55,7 +62,7 @@ export default defineComponent({
dataStatus: DataStatus.Loading, dataStatus: DataStatus.Loading,
}; };
}, },
mounted() { activated() {
this.fetchAPIData(); this.fetchAPIData();
}, },
methods: { methods: {
@@ -96,6 +103,13 @@ export default defineComponent({
line-height: 1.5em; line-height: 1.5em;
} }
.item-general {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0.25em;
}
.dispatcher-online { .dispatcher-online {
color: springgreen; color: springgreen;
} }
+1 -1
View File
@@ -1,6 +1,6 @@
<template> <template>
<section class="info-header"> <section class="info-header">
<a class="scenery-name" :href="station.generalInfo?.url"> <a class="scenery-name" :href="station.generalInfo?.url" target="_blank">
{{ station.name }} {{ station.name }}
</a> </a>
+20 -14
View File
@@ -1,10 +1,10 @@
<template> <template>
<div class="scenery-info"> <div class="scenery-info">
<section v-if="!timetableOnly"> <section v-if="!timetableOnly">
<div class="info-general" v-if="station.generalInfo"> <div class="scenery-info-general" v-if="station.generalInfo">
<scenery-info-icons :station="station" /> <scenery-info-icons :station="station" />
<div class="general-list"> <div class="scenery-general-list">
<span> <span>
<b>{{ $t('availability.title') }}:</b> {{ $t(`availability.${station.generalInfo.availability}`) }} <b>{{ $t('availability.title') }}:</b> {{ $t(`availability.${station.generalInfo.availability}`) }}
@@ -26,26 +26,32 @@
</span> </span>
<span v-if="station.generalInfo.project"> <span v-if="station.generalInfo.project">
&bull; <b>{{ $t('scenery.project-title') }}: </b> &bull; <b>{{ $t('scenery.project-title') }}: </b>
<b style="color: salmon">{{ station.generalInfo.project }}</b> <a
style="color: salmon; text-decoration: underline; font-weight: bold"
:href="station.generalInfo.projectUrl"
target="_blank"
>{{ station.generalInfo.project }}</a
>
</span> </span>
</div> </div>
<scenery-info-routes :station="station" /> <scenery-info-routes :station="station" />
<div class="scenery-authors" v-if="station.generalInfo.authors && station.generalInfo.authors.length > 0"> <div class="scenery-authors" v-if="station.generalInfo.authors && station.generalInfo.authors.length > 0">
<b> {{ $t('scenery.authors-title', { authors: station.generalInfo.authors.length }, station.generalInfo.authors.length) }}: </b> <b>
{{
$t(
'scenery.authors-title',
{ authors: station.generalInfo.authors.length },
station.generalInfo.authors.length
)
}}:
</b>
{{ station.generalInfo.authors.join(', ') }} {{ station.generalInfo.authors.join(', ') }}
</div> </div>
<br />
<div class="scenery-topic" v-if="station.generalInfo.url">
<a :href="station.generalInfo.url" target="_blank">
&gt; {{ $t('scenery.forum-topic', { name: station.name }) }} &lt;
</a>
</div>
</div> </div>
<div style="margin: 2em 0; height: 2px; background-color: white" /> <div style="margin: 2em 0; height: 2px; background-color: white"></div>
<!-- info dispatcher --> <!-- info dispatcher -->
<scenery-info-dispatcher :station="station" :onlineFrom="onlineFrom" /> <scenery-info-dispatcher :station="station" :onlineFrom="onlineFrom" />
@@ -124,11 +130,11 @@ h3.section-header {
margin-top: 1em; margin-top: 1em;
} }
.info-general { .scenery-info-general {
margin-top: 1em; margin-top: 1em;
} }
.general-list { .scenery-general-list {
display: flex; display: flex;
justify-content: center; justify-content: center;
flex-wrap: wrap; flex-wrap: wrap;
@@ -1,114 +1,108 @@
<template> <template>
<section class="info-routes" v-if="station.generalInfo"> <section class="info-routes" v-if="station.generalInfo">
<div class="routes one-way" v-if="station.generalInfo.routes.oneWay.length > 0"> <div class="routes one-way" v-if="station.generalInfo.routes.oneWay.length > 0">
<b>{{ $t('scenery.one-way-routes') }}</b> <b>{{ $t('scenery.one-way-routes') }}</b>
<ul class="routes-list"> <ul class="routes-list">
<li <li v-for="route in station.generalInfo.routes.oneWay">
v-for="route in station.generalInfo.routes.oneWay" <span :class="{ 'no-catenary': !route.catenary, internal: route.isInternal }"> {{ route.name }}</span>
:class="{ 'no-catenary': !route.catenary, internal: route.isInternal }" <span v-if="route.speed" class="speed">{{ route.speed }}</span>
> <span v-if="route.SBL" class="sbl">SBL</span>
{{ route.name }} </li>
<b v-if="route.SBL">SBL</b> </ul>
</li> </div>
</ul>
</div> <div class="routes two-way" v-if="station.generalInfo.routes.twoWay.length > 0">
<b>{{ $t('scenery.two-way-routes') }}</b>
<div class="routes two-way" v-if="station.generalInfo.routes.twoWay.length > 0">
<b>{{ $t('scenery.two-way-routes') }}</b> <ul class="routes-list">
<li v-for="route in station.generalInfo.routes.twoWay">
<ul class="routes-list"> <span :class="{ 'no-catenary': !route.catenary, internal: route.isInternal }">{{ route.name }}</span>
<li <span v-if="route.speed" class="speed">{{ route.speed }}</span>
v-for="route in station.generalInfo.routes.twoWay" <span v-if="route.SBL" class="sbl">SBL</span>
:class="{ 'no-catenary': !route.catenary, internal: route.isInternal }" </li>
> </ul>
{{ route.name }} <b v-if="route.SBL">SBL</b> </div>
</li> </section>
</ul> </template>
</div>
<script lang="ts">
<!-- <div import { defineComponent } from 'vue';
class="route-info" import Station from '../../../scripts/interfaces/Station';
:class="{ 'no-catenary': !route.catenary, internal: route.isInternal }"
v-for="route in [...station.generalInfo.routes.oneWay, ...station.generalInfo.routes.twoWay].filter( export default defineComponent({
(route) => route.name != '-' props: {
)" station: {
:key="route.name" type: Object as () => Station,
:title="`Szlak ${route.name}: ${route.isInternal ? 'wewnętrzny' : 'zewnętrzny'}, ${ default: {},
route.tracks == 2 ? 'dwutorowy' : 'jednotorowy' },
}, ${route.catenary ? 'zelektryfikowany' : 'niezelektryfikowany'} z ${route.SBL ? 'SBL' : 'PBL'} ${ },
route.TWB ? 'i blokadą dwukierunkową' : '' });
}`" </script>
> -->
<!-- <span class="track-name"> <style lang="scss" scoped>
<b>{{ route.name }}</b> .info-routes {
</span> --> display: flex;
<!-- justify-content: center;
<span class="track-specs"> flex-wrap: wrap;
{{ route.tracks }}tor
<img v-if="route.catenary" :src="icons.trackCatenary" alt="icon track catenary" /> margin: 1em 0;
<img v-else :src="icons.trackNoCatenary" alt="icon track no catenary" /> }
<img v-if="route.TWB" :src="icons.trackTWB" alt="icon track twb" /> .routes {
<img v-if="route.SBL" :src="icons.trackSBL" alt="icon track sbl" /> display: flex;
</span> --> justify-content: center;
<!-- </div> --> align-items: center;
</section> flex-wrap: wrap;
</template>
padding: 0.25em;
<script lang="ts"> }
import { defineComponent } from 'vue';
import Station from '../../../scripts/interfaces/Station'; ul.routes-list {
margin: 0.45em 0.25em;
export default defineComponent({ display: flex;
props: { justify-content: center;
station: { flex-wrap: wrap;
type: Object as () => Station,
default: {}, li {
}, margin: 0.5em 0.25em;
},
}); span {
</script> padding: 0.2em 0.25em;
background-color: #007599;
<style lang="scss" scoped> font-weight: bold;
.info-routes {
display: flex; &.no-catenary {
justify-content: center; background-color: #686868;
flex-wrap: wrap; }
margin: 1em 0; &.internal {
} text-decoration: underline;
}
.routes {
display: flex; &.speed {
justify-content: center; background-color: #404040;
align-items: center; color: #cfcfcf;
flex-wrap: wrap; }
padding: 0.25em; &.sbl {
} color: var(--clr-primary);
background-color: #404040;
ul.routes-list { }
margin: 0.45em 0.25em;
display: flex; &:last-child {
border-radius: 0 0.5em 0.5em 0;
li { }
background-color: #007599;
&:first-child {
padding: 0.2em 0.25em; border-radius: 0.5em 0 0 0.5em;
margin-left: 0.25em; }
&.no-catenary { &:only-child {
background-color: #686868; border-radius: 0.5em;
}
}
&.internal { }
text-decoration: underline; }
} }
</style>
b {
color: var(--clr-primary);
}
}
}
</style>
@@ -2,8 +2,46 @@
<section class="scenery-timetables-history scenery-section"> <section class="scenery-timetables-history scenery-section">
<Loading v-if="dataStatus != 2" /> <Loading v-if="dataStatus != 2" />
<div class="list-warning" v-else-if="sceneryHistoryList.length == 0">{{ $t('scenery.history-list-empty') }}</div> <table v-else-if="sceneryHistoryList.length">
<ul class="history-list" v-else> <thead>
<th>{{ $t('scenery.timetables-history-id') }}</th>
<th>{{ $t('scenery.timetables-history-number')}}</th>
<th>{{ $t('scenery.timetables-history-route')}}</th>
<th>{{ $t('scenery.timetables-history-driver')}}</th>
<th>{{ $t('scenery.timetables-history-author')}}</th>
<th>{{ $t('scenery.timetables-history-date')}}</th>
</thead>
<tbody>
<tr v-for="historyItem in sceneryHistoryList" @click="test">
<td>
<router-link :to="`/journal/timetables?timetableId=${historyItem.id}`">#{{ historyItem.id }}</router-link>
</td>
<td>
<b class="text--primary">{{ historyItem.trainCategoryCode }}</b> <br />
{{ historyItem.trainNo }}
</td>
<td>{{ historyItem.route.replace('|', ' -> ') }}</td>
<td>{{ historyItem.driverName }}</td>
<td>
<router-link
v-if="historyItem.authorName"
:to="`/journal/dispatchers?dispatcherName=${historyItem.authorName}`"
>{{ historyItem.authorName }}
</router-link>
<i v-else>{{ $t('scenery.timetable-author-unknown') }}</i>
</td>
<td>
<b>{{ localeDay(historyItem.beginDate, $i18n.locale) }}</b>
{{ localeTime(historyItem.beginDate, $i18n.locale) }}
</td>
</tr>
</tbody>
</table>
<div class="list-warning" v-else>{{ $t('scenery.history-list-empty') }}</div>
<!-- <ul class="history-list" v-else>
<li class="list-item" v-for="historyItem in sceneryHistoryList"> <li class="list-item" v-for="historyItem in sceneryHistoryList">
<div> <div>
<b>{{ localeDay(historyItem.beginDate, $i18n.locale) }}</b> <b>{{ localeDay(historyItem.beginDate, $i18n.locale) }}</b>
@@ -19,16 +57,14 @@
</div> </div>
<div>{{ historyItem.route.replace('|', ' -> ') }}</div> <div>{{ historyItem.route.replace('|', ' -> ') }}</div>
<!-- <div>{{ historyItem.routeDistance }} km</div> -->
<div> <div>
{{ $t('scenery.timetable-author-title') }}: {{ $t('scenery.timetable-author-title') }}:
<b v-if="historyItem.authorName">{{ historyItem.authorName }}</b> <b v-if="historyItem.authorName">{{ historyItem.authorName }}</b>
<i v-else>{{ $t('scenery.timetable-author-unknown') }}</i> <i v-else>{{ $t('scenery.timetable-author-unknown') }}</i>
</div> </div>
<!-- <div v-if="historyItem.authorId">{{ historyItem.authorName }}</div> -->
</li> </li>
</ul> </ul> -->
</section> </section>
</template> </template>
@@ -57,7 +93,7 @@ export default defineComponent({
dataStatus: DataStatus.Loading, dataStatus: DataStatus.Loading,
}; };
}, },
mounted() { activated() {
this.fetchAPIData(); this.fetchAPIData();
}, },
methods: { methods: {
@@ -72,6 +108,10 @@ export default defineComponent({
console.error(error); console.error(error);
} }
}, },
test() {
console.log('test');
},
}, },
components: { Loading }, components: { Loading },
}); });
@@ -91,17 +131,29 @@ export default defineComponent({
padding: 0 0.5em; padding: 0 0.5em;
} }
.list-item { table {
display: grid; width: 100%;
grid-template-columns: 1fr 2fr 2fr 1fr; border-collapse: collapse;
gap: 1em;
align-items: center;
background-color: #353535; thead {
padding: 0.5em; position: sticky;
margin: 0.5em 0; top: 0;
background-color: #222222;
}
line-height: 1.5em; th {
padding: 0.5em;
}
tr {
background-color: #353535;
border: none;
}
td {
padding: 0.75em;
border-bottom: solid 5px #111;
}
} }
@include smallScreen { @include smallScreen {
+8 -6
View File
@@ -11,15 +11,14 @@
</span> </span>
<strong> <strong>
<span v-if="train.timetableData">{{ train.timetableData.category }}&nbsp;</span> <span v-if="train.timetableData" class="text--primary">{{ train.timetableData.category }}&nbsp;</span>
<span class="train-number">{{ train.trainNo }}</span> <span class="train-number">{{ train.trainNo }}</span>
</strong> </strong>
<span>|</span> <span>&bull;</span>
<span>{{ train.driverName }}</span> <b class="level-badge driver" :style="calculateExpStyle(train.driverLevel, train.isSupporter)">
<span>|</span> {{ train.driverLevel < 2 ? 'L' : `${train.driverLevel}` }}
<b :style="calculateTextExpStyle(train.driverLevel, train.isSupporter)">
{{ train.driverLevel < 2 ? 'L' : `${train.driverLevel} lvl` }}
</b> </b>
<span>{{ train.driverName }}</span>
</div> </div>
<div class="timetable_route" v-if="train.timetableData"> <div class="timetable_route" v-if="train.timetableData">
@@ -117,6 +116,8 @@ export default defineComponent({
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/responsive.scss'; @import '../../styles/responsive.scss';
@import '../../styles/badge.scss';
.image-warning { .image-warning {
height: 1em; height: 1em;
@@ -172,6 +173,7 @@ export default defineComponent({
flex-wrap: wrap; flex-wrap: wrap;
gap: 0.25em; gap: 0.25em;
margin-right: 1.5em;
} }
.train-status-badges { .train-status-badges {
display: flex; display: flex;
@@ -89,6 +89,7 @@ import dateMixin from '../../mixins/dateMixin';
import imageMixin from '../../mixins/imageMixin'; import imageMixin from '../../mixins/imageMixin';
import Train from '../../scripts/interfaces/Train'; import Train from '../../scripts/interfaces/Train';
import TrainStop from '../../scripts/interfaces/TrainStop'; import TrainStop from '../../scripts/interfaces/TrainStop';
import { useStore } from '../../store/store';
import StopDate from '../Global/StopDate.vue'; import StopDate from '../Global/StopDate.vue';
export default defineComponent({ export default defineComponent({
@@ -106,6 +107,8 @@ export default defineComponent({
setup(props) { setup(props) {
return { return {
store: useStore(),
lastConfirmed: computed(() => { lastConfirmed: computed(() => {
return props.train.timetableData!.followingStops.findIndex( return props.train.timetableData!.followingStops.findIndex(
(stop, i, stops) => stop.confirmed && !stops[i + 1]?.confirmed && !stops[i + 1]?.stopped (stop, i, stops) => stop.confirmed && !stops[i + 1]?.confirmed && !stops[i + 1]?.stopped
@@ -424,3 +427,4 @@ ul.stop_list > li.stop {
} }
} }
</style> </style>
+7
View File
@@ -313,6 +313,13 @@
"timetable-author-title": "Issued by", "timetable-author-title": "Issued by",
"timetable-author-unknown": "Author unknown", "timetable-author-unknown": "Author unknown",
"timetables-history-id": "ID",
"timetables-history-number": "Number",
"timetables-history-route": "Route",
"timetables-history-driver": "Driver",
"timetables-history-author": "TT author",
"timetables-history-date": "Date",
"req-level": "all dispatcher levels | dispatcher level {lvl} required | dispatcher level {lvl} required", "req-level": "all dispatcher levels | dispatcher level {lvl} required | dispatcher level {lvl} required",
"history-list-empty": "No recorded scenery history!", "history-list-empty": "No recorded scenery history!",
+7
View File
@@ -317,6 +317,13 @@
"timetable-author-title": "Wydany przez", "timetable-author-title": "Wydany przez",
"timetable-author-unknown": "Autor nieznany", "timetable-author-unknown": "Autor nieznany",
"timetables-history-id": "ID",
"timetables-history-number": "Numer",
"timetables-history-route": "Trasa",
"timetables-history-driver": "Maszynista",
"timetables-history-author": "Autor RJ",
"timetables-history-date": "Data",
"req-level": "ogólnodostępna | minimum {lvl} poziom dyżurnego | minimum {lvl} poziom dyżurnego", "req-level": "ogólnodostępna | minimum {lvl} poziom dyżurnego | minimum {lvl} poziom dyżurnego",
"history-list-empty": "Brak historii dla tej scenerii!", "history-list-empty": "Brak historii dla tej scenerii!",
+2 -2
View File
@@ -6,7 +6,7 @@ export default defineComponent({
const bgColor = exp > -1 ? (exp < 2 ? '#26B0D9' : `hsl(${-exp * 5 + 100}, 85%, 50%)`) : '#666'; const bgColor = exp > -1 ? (exp < 2 ? '#26B0D9' : `hsl(${-exp * 5 + 100}, 85%, 50%)`) : '#666';
const fontColor = exp > 14 || exp == -1 ? 'white' : 'black'; const fontColor = exp > 14 || exp == -1 ? 'white' : 'black';
const boxShadow = isSupporter ? `box-shadow: 0 0 10px 2px ${bgColor};` : ''; const boxShadow = isSupporter ? `box-shadow: 0 0 6px 2px ${bgColor};` : '';
return `background-color: ${bgColor}; color: ${fontColor}; ${boxShadow};`; return `background-color: ${bgColor}; color: ${fontColor}; ${boxShadow};`;
}, },
@@ -14,7 +14,7 @@ export default defineComponent({
calculateTextExpStyle(exp: number, isSupporter = false): string { calculateTextExpStyle(exp: number, isSupporter = false): string {
const textColor = exp > -1 ? (exp < 2 ? '#26B0D9' : `hsl(${-exp * 5 + 100}, 75%, 50%)`) : '#666'; const textColor = exp > -1 ? (exp < 2 ? '#26B0D9' : `hsl(${-exp * 5 + 100}, 75%, 50%)`) : '#666';
return `color: ${textColor}; ${isSupporter ? 'text-shadow: 0 0 10px ' + textColor : ''};`; return `color: ${textColor}; ${isSupporter ? 'text-shadow: 0 0 6px ' + textColor : ''};`;
}, },
statusClasses(occupiedTo: string) { statusClasses(occupiedTo: string) {
+3 -2
View File
@@ -8,12 +8,13 @@ export default interface Station {
generalInfo?: { generalInfo?: {
name: string; name: string;
url: string; url: string;
reqLevel: number; reqLevel: number;
// supportersOnly: boolean; // supportersOnly: boolean;
lines: string; lines: string;
project: string; project: string;
projectUrl?: string;
signalType: string; signalType: string;
controlType: string; controlType: string;
+30 -27
View File
@@ -1,27 +1,30 @@
export default interface StationRoutes { export default interface StationRoutes {
oneWay: oneWay: {
{ name: string;
name: string; catenary: boolean;
catenary: boolean; SBL: boolean;
SBL: boolean; TWB: boolean;
TWB: boolean; isInternal: boolean;
isInternal: boolean; tracks: number;
tracks: number; speed: number;
}[]; length: number;
}[];
twoWay: {
name: string; twoWay: {
catenary: boolean; name: string;
SBL: boolean; catenary: boolean;
TWB: boolean; SBL: boolean;
isInternal: boolean; TWB: boolean;
tracks: number; isInternal: boolean;
}[]; tracks: number;
speed: number;
/* [catenary, noCatenary] */ length: number;
oneWayCatenaryRouteNames: string[]; }[];
oneWayNoCatenaryRouteNames: string[];
twoWayCatenaryRouteNames: string[]; /* [catenary, noCatenary] */
twoWayNoCatenaryRouteNames: string[]; oneWayCatenaryRouteNames: string[];
sblRouteNames: string[]; oneWayNoCatenaryRouteNames: string[];
} twoWayCatenaryRouteNames: string[];
twoWayNoCatenaryRouteNames: string[];
sblRouteNames: string[];
}
+1 -1
View File
@@ -1,4 +1,4 @@
export default interface TrainStop { export default interface TrainStop {
stopName: string; stopName: string;
stopNameRAW: string; stopNameRAW: string;
stopType: string; stopType: string;
+65 -58
View File
@@ -24,6 +24,7 @@ export const useStore = defineStore('store', {
stationList: [], stationList: [],
trainList: [], trainList: [],
routesList: [],
sceneryData: [], sceneryData: [],
lastDispatcherStatuses: [], lastDispatcherStatuses: [],
@@ -115,8 +116,8 @@ export const useStore = defineStore('store', {
sceneries: timetable.sceneries, sceneries: timetable.sceneries,
} }
: undefined, : undefined,
}; } as Train;
}) as Train[]; });
}, },
getDispatcherStatus(onlineStationData: StationAPIData) { getDispatcherStatus(onlineStationData: StationAPIData) {
@@ -294,73 +295,78 @@ export const useStore = defineStore('store', {
return; return;
} }
this.stationList = sceneryData.map((scenery) => ({ this.stationList = sceneryData.map((scenery) => {
name: scenery.name, return {
name: scenery.name,
generalInfo: { generalInfo: {
...scenery, ...scenery,
authors: scenery.authors?.split(',').map((a) => a.trim()), authors: scenery.authors?.split(',').map((a) => a.trim()),
routes: routes:
scenery.routes scenery.routes
?.split(';') ?.split(';')
.filter((routeString) => routeString) .filter((routeString) => routeString)
.reduce( .reduce(
(acc, routeString) => { (acc, routeString) => {
const specs1 = routeString.split('_')[0]; const specs1 = routeString.split('_')[0];
const isInternal = specs1.startsWith('!'); const isInternal = specs1.startsWith('!');
const name = isInternal ? specs1.replace('!', '') : specs1; const name = isInternal ? specs1.replace('!', '') : specs1;
const specs2 = routeString.split('_')[1].split(''); const specs2 = routeString.split('_')[1].split('');
const twoWay = specs2[0] == '2'; const twoWay = specs2[0] == '2';
const catenary = specs2[1] == 'E'; const catenary = specs2[1] == 'E';
const SBL = specs2[2] == 'S'; const SBL = specs2[2] == 'S';
const TWB = specs2[3] ? true : false; const TWB = specs2[3] ? true : false;
const speed = Number(routeString.split(':')[1]) || 0;
const length = Number(routeString.split(':')[2]) || 0;
const propName = twoWay const propName = twoWay
? catenary ? catenary
? 'twoWayCatenaryRouteNames' ? 'twoWayCatenaryRouteNames'
: 'twoWayNoCatenaryRouteNames' : 'twoWayNoCatenaryRouteNames'
: catenary : catenary
? 'oneWayCatenaryRouteNames' ? 'oneWayCatenaryRouteNames'
: 'oneWayNoCatenaryRouteNames'; : 'oneWayNoCatenaryRouteNames';
acc[twoWay ? 'twoWay' : 'oneWay'].push({ acc[twoWay ? 'twoWay' : 'oneWay'].push({
name, name,
SBL, SBL,
TWB, TWB,
catenary, catenary,
isInternal, isInternal,
tracks: twoWay ? 2 : 1, tracks: twoWay ? 2 : 1,
}); length,
if (!isInternal) acc[propName].push(name); speed,
});
if (!isInternal) acc[propName].push(name);
if (SBL) acc['sblRouteNames'].push(name); if (SBL) acc['sblRouteNames'].push(name);
return acc; return acc;
}, },
{ {
oneWay: [], oneWay: [],
twoWay: [], twoWay: [],
sblRouteNames: [], sblRouteNames: [],
oneWayCatenaryRouteNames: [], oneWayCatenaryRouteNames: [],
oneWayNoCatenaryRouteNames: [], oneWayNoCatenaryRouteNames: [],
twoWayCatenaryRouteNames: [], twoWayCatenaryRouteNames: [],
twoWayNoCatenaryRouteNames: [], twoWayNoCatenaryRouteNames: [],
} as StationRoutes } as StationRoutes
) || {}, ) || {},
checkpoints: scenery.checkpoints checkpoints: scenery.checkpoints
? scenery.checkpoints.split(';').map((sub) => ({ checkpointName: sub, scheduledTrains: [] })) ? scenery.checkpoints.split(';').map((sub) => ({ checkpointName: sub, scheduledTrains: [] }))
: [], : [],
}, },
})); };
});
}, },
connectToWebsocket() { connectToWebsocket() {
const socket = io(URLs.stacjownikAPI, { const socket = io(URLs.stacjownikAPI, {
transports: ['websocket', 'polling'], // transports: ['websocket', 'polling'],
rememberUpgrade: true, rememberUpgrade: true,
reconnection: true, reconnection: true,
timeout: 2000,
}); });
socket.on('connect_error', (err) => { socket.on('connect_error', (err) => {
@@ -374,8 +380,9 @@ export const useStore = defineStore('store', {
}); });
socket.emit('FETCH_DATA', {}, (data: APIData) => { socket.emit('FETCH_DATA', {}, (data: APIData) => {
this.apiData = data;
this.dataStatuses.connection = DataStatus.Loaded; this.dataStatuses.connection = DataStatus.Loaded;
this.apiData = data;
this.setOnlineData(); this.setOnlineData();
}); });
+3 -1
View File
@@ -60,6 +60,7 @@ export interface StationJSONData {
url: string; url: string;
lines: string; lines: string;
project: string; project: string;
projectUrl: string;
reqLevel: number; reqLevel: number;
@@ -69,8 +70,9 @@ export interface StationJSONData {
SUP: boolean; SUP: boolean;
routes: string; routes: string;
checkpoints: string | null; checkpoints: string | null;
authors?: string; authors?: string;
availability: Availability; availability: Availability;
} }
+58 -28
View File
@@ -1,28 +1,58 @@
.badge { .badge {
font-weight: 600; font-weight: 600;
display: inline-block; display: inline-block;
padding: 0; padding: 0;
background: #585858; background: #585858;
margin: 0.25em; margin: 0.25em;
span { span {
display: inline-block; display: inline-block;
padding: 0.2em 0.4em; padding: 0.2em 0.4em;
} }
&-none { &-none {
font-weight: 600; font-weight: 600;
padding: 0.2em 0.4em; padding: 0.2em 0.4em;
background: firebrick; background: firebrick;
text-align: center; text-align: center;
@include smallScreen() { @include smallScreen() {
font-size: 1em; font-size: 1em;
} }
} }
} }
.level-badge {
display: flex;
justify-content: center;
align-items: center;
font-size: 0.9em;
&.driver {
border-radius: 50%;
width: 1.7em;
height: 1.7em;
}
&.dispatcher {
border-radius: 0.25em;
width: 1.6em;
height: 1.6em;
}
}
.region-badge {
padding: 0 0.5em;
border-radius: 0.5em;
font-weight: bold;
&.eu {
background-color: forestgreen;
}
}
+7 -5
View File
@@ -18,19 +18,21 @@
} }
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 1rem; width: 15px;
height: 1rem; height: 15px;
background-color: transparent; background-color: transparent;
&-track { &-track {
border-radius: 0.5em;
background-color: #333; background-color: #333;
} }
&-thumb { &-thumb {
border-radius: 0.5em;
background-color: #666; background-color: #666;
} }
&-corner {
background-color: #333;
}
} }
html { html {
@@ -47,7 +49,7 @@ body {
&.no-scroll { &.no-scroll {
overflow-y: hidden; overflow-y: hidden;
padding-right: 1rem; padding-right: 15px;
@include smallScreen() { @include smallScreen() {
padding: 0; padding: 0;
+2 -2
View File
@@ -19,7 +19,7 @@
{{ $t('app.offline') }} {{ $t('app.offline') }}
</div> </div>
<Loading v-else-if="dataStatus == DataStatus.Initialized || dataStatus == DataStatus.Loading" /> <Loading v-else-if="dataStatus == DataStatus.Loading" />
<div v-else-if="dataStatus == DataStatus.Error" class="journal_warning error"> <div v-else-if="dataStatus == DataStatus.Error" class="journal_warning error">
{{ $t('app.error') }} {{ $t('app.error') }}
@@ -111,7 +111,7 @@ export default defineComponent({
statsCardOpen: false, statsCardOpen: false,
currentOptionsActive: false, currentOptionsActive: false,
dataStatus: DataStatus.Initialized, dataStatus: DataStatus.Loading,
DataStatus, DataStatus,
historyList: [] as DispatcherHistory[], historyList: [] as DispatcherHistory[],
+3 -3
View File
@@ -22,7 +22,7 @@
{{ $t('app.offline') }} {{ $t('app.offline') }}
</div> </div>
<Loading v-else-if="dataStatus == DataStatus.Initialized || dataStatus == DataStatus.Loading" /> <Loading v-else-if="dataStatus == DataStatus.Loading" />
<div v-else-if="dataStatus == DataStatus.Error" class="journal_warning error"> <div v-else-if="dataStatus == DataStatus.Error" class="journal_warning error">
{{ $t('app.error') }} {{ $t('app.error') }}
@@ -105,7 +105,7 @@ export default defineComponent({
timetableHistory: [] as TimetableHistory[], timetableHistory: [] as TimetableHistory[],
journalTimetableFilters, journalTimetableFilters,
dataStatus: DataStatus.Initialized, dataStatus: DataStatus.Loading,
dataErrorMessage: '', dataErrorMessage: '',
DataStatus, DataStatus,
@@ -215,7 +215,7 @@ export default defineComponent({
}, },
async fetchHistoryData() { async fetchHistoryData() {
if(this.dataStatus == DataStatus.Loading) return; // if(this.dataStatus == DataStatus.Loading) return;
const queries: string[] = []; const queries: string[] = [];