Compare commits

...

17 Commits

Author SHA1 Message Date
Spythere dd7d1b0bb0 Wersja 1.12.1
Wersja 1.12.1
2023-02-26 14:20:40 +01:00
Spythere 68934a89a4 bump: wersja 1.12.1 2023-02-26 14:16:13 +01:00
Spythere b88a240ec1 feature: like count historii dyżurnych 2023-02-26 14:14:32 +01:00
Spythere eaa34f3359 hotfix: dostępność grubości czcionki 2023-02-26 13:50:47 +01:00
Spythere febb22e1bc Wersja 1.12
Merge produkcyjny do wersji 1.12.0
2023-02-14 21:32:46 +01:00
Spythere 500f3c1223 dziennik RJ: wyświetlanie statów 2023-02-14 16:57:22 +01:00
Spythere 221e0c7e82 dzienniki: fix ładowania 2023-02-14 16:50:12 +01:00
Spythere ca19f7e397 hotfix: websocket 2023-02-14 16:40:15 +01:00
Spythere a71ccd3e1a bump: wersja 1.12 2023-02-14 13:52:20 +01:00
Spythere d496c70fa8 aktualizacja tłumaczenia 2023-02-14 13:51:52 +01:00
Spythere b9868ba52e dzienniki: stylistyka 2023-02-12 16:12:48 +01:00
Spythere 59bd3fa2ef design: badge poziomów 2023-02-12 12:58:23 +01:00
Spythere e14d328ed9 fix: wielkość scrollbaru 2023-02-12 00:48:18 +01:00
Spythere 36d71292bc feature: url projektów 2023-02-12 00:42:37 +01:00
Spythere 2f6e2e7402 fix: responsywność 2023-02-12 00:30:05 +01:00
Spythere e959eac6c5 hotfix 2023-02-11 03:14:43 +01:00
Spythere 8bedc4dfc6 feature: vmax szlaków 2023-02-11 03:08:24 +01:00
26 changed files with 512 additions and 363 deletions
+4 -3
View File
@@ -5,8 +5,8 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<meta name="keywords" content="Stacjownik, TD2, Train Driver 2, stacjownik-td2" />
<meta name="description" content="Automatycznie odświeżana strona wyświetlająca stacje w Train Driver 2!" />
<meta name="keywords" content="Stacjownik, TD2, Train Driver 2, stacjownik-td2, stacjownik, td2.info.pl" />
<meta name="description" content="Pomocnik maszynisty i dyżurnego symulatora Train Driver 2" />
<title>Stacjownik</title>
@@ -24,7 +24,7 @@
<link rel="icon" href="favicon-16.png" sizes="16x16" type="image/png" />
<link rel="icon" href="favicon.ico" />
<link href="https://fonts.googleapis.com/css2?family=Quicksand:wght@400;500;700&display=swap" rel="stylesheet" />
<link href="https://fonts.googleapis.com/css2?family=Quicksand:wght@500;700&display=swap" rel="stylesheet" />
</head>
<body>
@@ -32,3 +32,4 @@
<script type="module" src="/src/main.ts"></script>
</body>
</html>
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "stacjownik",
"version": "1.11.2",
"version": "1.12.1",
"private": true,
"scripts": {
"dev": "vite",
+1 -1
View File
@@ -46,7 +46,7 @@
font-size: 1rem;
@include smallScreen() {
font-size: calc(0.5rem + 1.1vw);
font-size: calc(0.55rem + 1.1vw);
}
@include screenLandscape() {
@@ -17,10 +17,10 @@
@keydown.enter="navigateToScenery(item.stationName, item.isOnline)"
tabindex="0"
>
<span>
<span class="item-general">
<b
v-if="item.dispatcherLevel !== null"
class="dispatcher-level"
class="level-badge dispatcher"
:style="calculateExpStyle(item.dispatcherLevel, item.dispatcherIsSupporter)"
>
{{ item.dispatcherLevel >= 2 ? item.dispatcherLevel : 'L' }}
@@ -29,9 +29,13 @@
<b class="text--primary">{{ item.dispatcherName }}</b> &bull; <b>{{ item.stationName }}</b>
<span class="text--grayed">&nbsp;#{{ item.stationHash }}&nbsp;</span>
<span class="region-badge" :class="item.region">PL1</span>
<span class="like-count" v-if="item.dispatcherRate">
<img :src="getIcon('like')" alt="like icon" />
{{ item.dispatcherRate }}
</span>
</span>
<span>
<span class="item-time">
<span :data-status="item.isOnline"> {{ item.isOnline ? $t('journal.online-since') : 'OFFLINE' }}&nbsp; </span>
<span>
{{ new Date(item.timestampFrom).toLocaleTimeString('pl-PL', { timeStyle: 'short' }) }}
@@ -55,6 +59,7 @@ import { defineComponent, PropType } from 'vue';
import dateMixin from '../../mixins/dateMixin';
import { DispatcherHistory } from '../../scripts/interfaces/api/DispatchersAPIData';
import styleMixin from '../../mixins/styleMixin';
import imageMixin from '../../mixins/imageMixin';
export default defineComponent({
props: {
@@ -64,7 +69,7 @@ export default defineComponent({
},
},
mixins: [dateMixin, styleMixin],
mixins: [dateMixin, styleMixin, imageMixin],
computed: {
computedDispatcherHistory() {
@@ -99,17 +104,9 @@ export default defineComponent({
<style lang="scss" scoped>
@import '../../styles/animations.scss';
@import '../../styles/responsive.scss';
@import '../../styles/badge.scss';
@import '../../styles/JournalSection.scss';
.region-badge {
padding: 0.1em 0.5em;
border-radius: 0.5em;
font-weight: bold;
&.eu {
background-color: forestgreen;
}
}
@import '../../styles/variables.scss';
li.sticky {
position: sticky;
@@ -123,7 +120,7 @@ li.sticky {
flex-wrap: wrap;
text-align: left;
gap: 0.25em;
gap: 0.5em 1em;
line-height: 1.7em;
padding: 0.75em;
@@ -141,6 +138,18 @@ li.sticky {
}
}
.item-general {
display: flex;
justify-content: center;
align-items: center;
gap: 0.25em;
flex-wrap: wrap;
.level-badge {
margin-right: 0.25em;
}
}
.journal_day {
margin-bottom: 1em;
padding: 0.5em;
@@ -158,15 +167,17 @@ li.sticky {
}
}
.dispatcher-level {
display: inline-block;
text-align: center;
.like-count {
display: flex;
align-items: center;
gap: 0.25em;
font-size: 1.2em;
color: $accentCol;
}
line-height: 1.45em;
width: 1.45em;
height: 1.45em;
margin-right: 0.45em;
border-radius: 0.25em;
@include smallScreen {
.journal_item {
flex-direction: column;
}
}
</style>
+7 -5
View File
@@ -11,7 +11,7 @@
{{ $t(tab.titlePath) }}
</button>
</div>
<div class="stats-tab" v-show="areStatsOpen">
<keep-alive>
<JournalDailyStats v-if="store.currentStatsTab == 'daily'" ref="dailyStatsComp" />
@@ -35,7 +35,8 @@ type TStatTab = 'daily' | 'driver';
const store = useStore();
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');
let data = reactive({
@@ -54,9 +55,9 @@ let data = reactive({
// Methods
function onTabButtonClick(tab: TStatTab) {
if (lastClickedTab.value == tab || !areStatsOpen.value) {
areStatsOpen.value = !areStatsOpen.value;
}
if (lastClickedTab.value == tab || !areStatsOpen.value) areStatsOpen.value = !areStatsOpen.value;
if (tab == 'daily') lastDailyStatsOpen.value = areStatsOpen.value;
store.currentStatsTab = tab;
lastClickedTab.value = tab;
@@ -77,6 +78,7 @@ watch(
lastClickedTab.value = statsData ? 'driver' : 'daily';
if (statsData) areStatsOpen.value = true;
if (!statsData) areStatsOpen.value = lastDailyStatsOpen.value;
}
);
</script>
@@ -6,25 +6,34 @@
:key="timetable.id"
>
<div class="journal_item-info">
<div class="info-top">
<div class="info-general">
<span
class="general-train"
tabindex="0"
@click="showTimetable(timetable)"
@keydown.enter="showTimetable(timetable)"
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 v-if="timetable.driverLevel !== null">
|
<b :style="calculateTextExpStyle(timetable.driverLevel, timetable.driverIsSupporter)">
{{ timetable.driverLevel < 2 ? 'L' : `${timetable.driverLevel} lvl` }}
</b>
<span>
<strong class="text--primary">
{{ timetable.trainCategoryCode }}
</strong>
<strong>&nbsp;{{ timetable.trainNo }}</strong>
</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 class="general-time">
<b class="info-date">{{ localeDay(timetable.beginDate, $i18n.locale) }}</b>
<b
class="info-status"
@@ -86,6 +95,7 @@
<b>{{ timetable.authorName }}</b>
</router-link>
</div>
<button
v-if="timetable.stockString"
class="btn--option btn--show"
@@ -94,6 +104,7 @@
{{ $t('journal.stock-info') }}
<img :src="getIcon(`arrow-${item.showStock.value ? 'asc' : 'desc'}`)" alt="Arrow" />
</button>
<div class="info-extended" v-if="timetable.stockString && item.showStock.value">
<hr />
<div>
@@ -189,6 +200,7 @@ export default defineComponent({
},
showTimetable(timetable: TimetableHistory) {
if (!timetable) return;
if (timetable.terminated) return;
this.selectModalTrain(timetable.driverName + timetable.trainNo.toString());
@@ -209,7 +221,6 @@ export default defineComponent({
@import '../../styles/badge.scss';
@import '../../styles/JournalSection.scss';
hr {
margin: 0.25em 0;
}
@@ -236,10 +247,14 @@ hr {
}
}
&-top {
&-general {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 0.5em;
margin-bottom: 0.5em;
}
&-route {
@@ -251,6 +266,12 @@ hr {
}
}
.general-train {
display: flex;
align-items: center;
gap: 0.25em;
}
ul.stock-list {
display: flex;
align-items: flex-end;
@@ -291,14 +312,9 @@ ul.stock-list {
}
@include smallScreen {
.info-top {
.info-general {
flex-direction: column;
span {
margin: 0.1em auto;
}
}
.info-extended {
text-align: center;
}
@@ -5,25 +5,31 @@
<div class="list-warning" v-else-if="dispatcherHistoryList.length == 0">{{ $t('scenery.history-list-empty') }}</div>
<ul class="history-list" v-else>
<li class="list-item" v-for="historyItem in dispatcherHistoryList">
<div>
<router-link :to="`/journal/dispatchers?dispatcherName=${historyItem.dispatcherName}`">
<span class="text--grayed">#{{ historyItem.stationHash }}&nbsp;</span>
<b>{{ historyItem.dispatcherName }}</b>
</router-link>
</div>
<li class="list-item" v-for="item in dispatcherHistoryList">
<router-link class="item-general" :to="`/journal/dispatchers?dispatcherName=${item.dispatcherName}`">
<span class="text--grayed">#{{ item.stationHash }}&nbsp;</span>
<b
v-if="item.dispatcherLevel !== null"
class="level-badge dispatcher"
:style="calculateExpStyle(item.dispatcherLevel, item.dispatcherIsSupporter)"
>
{{ item.dispatcherLevel >= 2 ? item.dispatcherLevel : 'L' }}
</b>
<div v-if="historyItem.timestampTo">
<b>{{ $d(historyItem.timestampFrom) }}</b>
<b>{{ item.dispatcherName }}</b>
</router-link>
{{ timestampToString(historyItem.timestampFrom) }}
- {{ timestampToString(historyItem.timestampTo) }} ({{ calculateDuration(historyItem.currentDuration) }})
<div v-if="item.timestampTo">
<b>{{ $d(item.timestampFrom) }}</b>
{{ timestampToString(item.timestampFrom) }}
- {{ timestampToString(item.timestampTo) }} ({{ calculateDuration(item.currentDuration) }})
</div>
<div class="dispatcher-online" v-else>
{{ $t('journal.online-since') }}
<b>{{ timestampToString(historyItem.timestampFrom) }}</b>
({{ calculateDuration(historyItem.currentDuration) }})
<b>{{ timestampToString(item.timestampFrom) }}</b>
({{ calculateDuration(item.currentDuration) }})
</div>
</li>
</ul>
@@ -39,10 +45,11 @@ import { DispatcherHistory } from '../../scripts/interfaces/api/DispatchersAPIDa
import Station from '../../scripts/interfaces/Station';
import { URLs } from '../../scripts/utils/apiURLs';
import Loading from '../Global/Loading.vue';
import styleMixin from '../../mixins/styleMixin';
export default defineComponent({
name: 'SceneryDispatchersHistory',
mixins: [dateMixin],
mixins: [dateMixin, styleMixin],
props: {
station: {
type: Object as PropType<Station>,
@@ -55,7 +62,7 @@ export default defineComponent({
dataStatus: DataStatus.Loading,
};
},
mounted() {
activated() {
this.fetchAPIData();
},
methods: {
@@ -96,6 +103,13 @@ export default defineComponent({
line-height: 1.5em;
}
.item-general {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0.25em;
}
.dispatcher-online {
color: springgreen;
}
+1 -1
View File
@@ -1,6 +1,6 @@
<template>
<section class="info-header">
<a class="scenery-name" :href="station.generalInfo?.url">
<a class="scenery-name" :href="station.generalInfo?.url" target="_blank">
{{ station.name }}
</a>
+20 -14
View File
@@ -1,10 +1,10 @@
<template>
<div class="scenery-info">
<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" />
<div class="general-list">
<div class="scenery-general-list">
<span>
<b>{{ $t('availability.title') }}:</b> {{ $t(`availability.${station.generalInfo.availability}`) }}
@@ -26,26 +26,32 @@
</span>
<span v-if="station.generalInfo.project">
&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>
</div>
<scenery-info-routes :station="station" />
<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(', ') }}
</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 style="margin: 2em 0; height: 2px; background-color: white" />
<div style="margin: 2em 0; height: 2px; background-color: white"></div>
<!-- info dispatcher -->
<scenery-info-dispatcher :station="station" :onlineFrom="onlineFrom" />
@@ -124,11 +130,11 @@ h3.section-header {
margin-top: 1em;
}
.info-general {
.scenery-info-general {
margin-top: 1em;
}
.general-list {
.scenery-general-list {
display: flex;
justify-content: center;
flex-wrap: wrap;
@@ -1,114 +1,108 @@
<template>
<section class="info-routes" v-if="station.generalInfo">
<div class="routes one-way" v-if="station.generalInfo.routes.oneWay.length > 0">
<b>{{ $t('scenery.one-way-routes') }}</b>
<ul class="routes-list">
<li
v-for="route in station.generalInfo.routes.oneWay"
:class="{ 'no-catenary': !route.catenary, internal: route.isInternal }"
>
{{ route.name }}
<b v-if="route.SBL">SBL</b>
</li>
</ul>
</div>
<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"
:class="{ 'no-catenary': !route.catenary, internal: route.isInternal }"
>
{{ route.name }} <b v-if="route.SBL">SBL</b>
</li>
</ul>
</div>
<!-- <div
class="route-info"
:class="{ 'no-catenary': !route.catenary, internal: route.isInternal }"
v-for="route in [...station.generalInfo.routes.oneWay, ...station.generalInfo.routes.twoWay].filter(
(route) => route.name != '-'
)"
:key="route.name"
:title="`Szlak ${route.name}: ${route.isInternal ? 'wewnętrzny' : 'zewnętrzny'}, ${
route.tracks == 2 ? 'dwutorowy' : 'jednotorowy'
}, ${route.catenary ? 'zelektryfikowany' : 'niezelektryfikowany'} z ${route.SBL ? 'SBL' : 'PBL'} ${
route.TWB ? 'i blokadą dwukierunkową' : ''
}`"
> -->
<!-- <span class="track-name">
<b>{{ route.name }}</b>
</span> -->
<!--
<span class="track-specs">
{{ route.tracks }}tor
<img v-if="route.catenary" :src="icons.trackCatenary" alt="icon track catenary" />
<img v-else :src="icons.trackNoCatenary" alt="icon track no catenary" />
<img v-if="route.TWB" :src="icons.trackTWB" alt="icon track twb" />
<img v-if="route.SBL" :src="icons.trackSBL" alt="icon track sbl" />
</span> -->
<!-- </div> -->
</section>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import Station from '../../../scripts/interfaces/Station';
export default defineComponent({
props: {
station: {
type: Object as () => Station,
default: {},
},
},
});
</script>
<style lang="scss" scoped>
.info-routes {
display: flex;
justify-content: center;
flex-wrap: wrap;
margin: 1em 0;
}
.routes {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
padding: 0.25em;
}
ul.routes-list {
margin: 0.45em 0.25em;
display: flex;
li {
background-color: #007599;
padding: 0.2em 0.25em;
margin-left: 0.25em;
&.no-catenary {
background-color: #686868;
}
&.internal {
text-decoration: underline;
}
b {
color: var(--clr-primary);
}
}
}
</style>
<template>
<section class="info-routes" v-if="station.generalInfo">
<div class="routes one-way" v-if="station.generalInfo.routes.oneWay.length > 0">
<b>{{ $t('scenery.one-way-routes') }}</b>
<ul class="routes-list">
<li v-for="route in station.generalInfo.routes.oneWay">
<span :class="{ 'no-catenary': !route.catenary, internal: route.isInternal }"> {{ route.name }}</span>
<span v-if="route.speed" class="speed">{{ route.speed }}</span>
<span v-if="route.SBL" class="sbl">SBL</span>
</li>
</ul>
</div>
<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">
<span :class="{ 'no-catenary': !route.catenary, internal: route.isInternal }">{{ route.name }}</span>
<span v-if="route.speed" class="speed">{{ route.speed }}</span>
<span v-if="route.SBL" class="sbl">SBL</span>
</li>
</ul>
</div>
</section>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import Station from '../../../scripts/interfaces/Station';
export default defineComponent({
props: {
station: {
type: Object as () => Station,
default: {},
},
},
});
</script>
<style lang="scss" scoped>
.info-routes {
display: flex;
justify-content: center;
flex-wrap: wrap;
margin: 1em 0;
}
.routes {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
padding: 0.25em;
}
ul.routes-list {
margin: 0.45em 0.25em;
display: flex;
justify-content: center;
flex-wrap: wrap;
li {
margin: 0.5em 0.25em;
span {
padding: 0.2em 0.25em;
background-color: #007599;
font-weight: bold;
&.no-catenary {
background-color: #686868;
}
&.internal {
text-decoration: underline;
}
&.speed {
background-color: #404040;
color: #cfcfcf;
}
&.sbl {
color: var(--clr-primary);
background-color: #404040;
}
&:last-child {
border-radius: 0 0.5em 0.5em 0;
}
&:first-child {
border-radius: 0.5em 0 0 0.5em;
}
&:only-child {
border-radius: 0.5em;
}
}
}
}
</style>
@@ -2,8 +2,46 @@
<section class="scenery-timetables-history scenery-section">
<Loading v-if="dataStatus != 2" />
<div class="list-warning" v-else-if="sceneryHistoryList.length == 0">{{ $t('scenery.history-list-empty') }}</div>
<ul class="history-list" v-else>
<table v-else-if="sceneryHistoryList.length">
<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">
<div>
<b>{{ localeDay(historyItem.beginDate, $i18n.locale) }}</b>
@@ -19,16 +57,14 @@
</div>
<div>{{ historyItem.route.replace('|', ' -> ') }}</div>
<!-- <div>{{ historyItem.routeDistance }} km</div> -->
<div>
{{ $t('scenery.timetable-author-title') }}:
<b v-if="historyItem.authorName">{{ historyItem.authorName }}</b>
<i v-else>{{ $t('scenery.timetable-author-unknown') }}</i>
</div>
<!-- <div v-if="historyItem.authorId">{{ historyItem.authorName }}</div> -->
</li>
</ul>
</ul> -->
</section>
</template>
@@ -57,7 +93,7 @@ export default defineComponent({
dataStatus: DataStatus.Loading,
};
},
mounted() {
activated() {
this.fetchAPIData();
},
methods: {
@@ -72,6 +108,10 @@ export default defineComponent({
console.error(error);
}
},
test() {
console.log('test');
},
},
components: { Loading },
});
@@ -91,17 +131,29 @@ export default defineComponent({
padding: 0 0.5em;
}
.list-item {
display: grid;
grid-template-columns: 1fr 2fr 2fr 1fr;
gap: 1em;
align-items: center;
table {
width: 100%;
border-collapse: collapse;
background-color: #353535;
padding: 0.5em;
margin: 0.5em 0;
thead {
position: sticky;
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 {
+8 -6
View File
@@ -11,15 +11,14 @@
</span>
<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>
</strong>
<span>|</span>
<span>{{ train.driverName }}</span>
<span>|</span>
<b :style="calculateTextExpStyle(train.driverLevel, train.isSupporter)">
{{ train.driverLevel < 2 ? 'L' : `${train.driverLevel} lvl` }}
<span>&bull;</span>
<b class="level-badge driver" :style="calculateExpStyle(train.driverLevel, train.isSupporter)">
{{ train.driverLevel < 2 ? 'L' : `${train.driverLevel}` }}
</b>
<span>{{ train.driverName }}</span>
</div>
<div class="timetable_route" v-if="train.timetableData">
@@ -117,6 +116,8 @@ export default defineComponent({
<style lang="scss" scoped>
@import '../../styles/responsive.scss';
@import '../../styles/badge.scss';
.image-warning {
height: 1em;
@@ -172,6 +173,7 @@ export default defineComponent({
flex-wrap: wrap;
gap: 0.25em;
margin-right: 1.5em;
}
.train-status-badges {
display: flex;
@@ -89,6 +89,7 @@ import dateMixin from '../../mixins/dateMixin';
import imageMixin from '../../mixins/imageMixin';
import Train from '../../scripts/interfaces/Train';
import TrainStop from '../../scripts/interfaces/TrainStop';
import { useStore } from '../../store/store';
import StopDate from '../Global/StopDate.vue';
export default defineComponent({
@@ -106,6 +107,8 @@ export default defineComponent({
setup(props) {
return {
store: useStore(),
lastConfirmed: computed(() => {
return props.train.timetableData!.followingStops.findIndex(
(stop, i, stops) => stop.confirmed && !stops[i + 1]?.confirmed && !stops[i + 1]?.stopped
@@ -424,3 +427,4 @@ ul.stop_list > li.stop {
}
}
</style>
+7
View File
@@ -313,6 +313,13 @@
"timetable-author-title": "Issued by",
"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",
"history-list-empty": "No recorded scenery history!",
+7
View File
@@ -317,6 +317,13 @@
"timetable-author-title": "Wydany przez",
"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",
"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 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};`;
},
@@ -14,7 +14,7 @@ export default defineComponent({
calculateTextExpStyle(exp: number, isSupporter = false): string {
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) {
+3 -2
View File
@@ -8,12 +8,13 @@ export default interface Station {
generalInfo?: {
name: string;
url: string;
reqLevel: number;
// supportersOnly: boolean;
lines: string;
project: string;
projectUrl?: string;
signalType: string;
controlType: string;
+30 -27
View File
@@ -1,27 +1,30 @@
export default interface StationRoutes {
oneWay:
{
name: string;
catenary: boolean;
SBL: boolean;
TWB: boolean;
isInternal: boolean;
tracks: number;
}[];
twoWay: {
name: string;
catenary: boolean;
SBL: boolean;
TWB: boolean;
isInternal: boolean;
tracks: number;
}[];
/* [catenary, noCatenary] */
oneWayCatenaryRouteNames: string[];
oneWayNoCatenaryRouteNames: string[];
twoWayCatenaryRouteNames: string[];
twoWayNoCatenaryRouteNames: string[];
sblRouteNames: string[];
}
export default interface StationRoutes {
oneWay: {
name: string;
catenary: boolean;
SBL: boolean;
TWB: boolean;
isInternal: boolean;
tracks: number;
speed: number;
length: number;
}[];
twoWay: {
name: string;
catenary: boolean;
SBL: boolean;
TWB: boolean;
isInternal: boolean;
tracks: number;
speed: number;
length: number;
}[];
/* [catenary, noCatenary] */
oneWayCatenaryRouteNames: 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;
stopNameRAW: string;
stopType: string;
@@ -1,10 +1,11 @@
export interface DispatcherHistory {
id: string;
currentDuration: number;
dispatcherId: number;
dispatcherName: string;
dispatcherLevel: number | null;
dispatcherRate: number;
dispatcherIsSupporter: boolean;
isOnline: boolean;
lastOnlineTimestamp: number;
@@ -13,4 +14,4 @@ export interface DispatcherHistory {
stationName: string;
timestampFrom: number;
timestampTo?: number;
}
}
+65 -58
View File
@@ -24,6 +24,7 @@ export const useStore = defineStore('store', {
stationList: [],
trainList: [],
routesList: [],
sceneryData: [],
lastDispatcherStatuses: [],
@@ -115,8 +116,8 @@ export const useStore = defineStore('store', {
sceneries: timetable.sceneries,
}
: undefined,
};
}) as Train[];
} as Train;
});
},
getDispatcherStatus(onlineStationData: StationAPIData) {
@@ -294,73 +295,78 @@ export const useStore = defineStore('store', {
return;
}
this.stationList = sceneryData.map((scenery) => ({
name: scenery.name,
this.stationList = sceneryData.map((scenery) => {
return {
name: scenery.name,
generalInfo: {
...scenery,
authors: scenery.authors?.split(',').map((a) => a.trim()),
routes:
scenery.routes
?.split(';')
.filter((routeString) => routeString)
.reduce(
(acc, routeString) => {
const specs1 = routeString.split('_')[0];
const isInternal = specs1.startsWith('!');
const name = isInternal ? specs1.replace('!', '') : specs1;
generalInfo: {
...scenery,
authors: scenery.authors?.split(',').map((a) => a.trim()),
routes:
scenery.routes
?.split(';')
.filter((routeString) => routeString)
.reduce(
(acc, routeString) => {
const specs1 = routeString.split('_')[0];
const isInternal = specs1.startsWith('!');
const name = isInternal ? specs1.replace('!', '') : specs1;
const specs2 = routeString.split('_')[1].split('');
const twoWay = specs2[0] == '2';
const catenary = specs2[1] == 'E';
const SBL = specs2[2] == 'S';
const TWB = specs2[3] ? true : false;
const specs2 = routeString.split('_')[1].split('');
const twoWay = specs2[0] == '2';
const catenary = specs2[1] == 'E';
const SBL = specs2[2] == 'S';
const TWB = specs2[3] ? true : false;
const speed = Number(routeString.split(':')[1]) || 0;
const length = Number(routeString.split(':')[2]) || 0;
const propName = twoWay
? catenary
? 'twoWayCatenaryRouteNames'
: 'twoWayNoCatenaryRouteNames'
: catenary
? 'oneWayCatenaryRouteNames'
: 'oneWayNoCatenaryRouteNames';
const propName = twoWay
? catenary
? 'twoWayCatenaryRouteNames'
: 'twoWayNoCatenaryRouteNames'
: catenary
? 'oneWayCatenaryRouteNames'
: 'oneWayNoCatenaryRouteNames';
acc[twoWay ? 'twoWay' : 'oneWay'].push({
name,
SBL,
TWB,
catenary,
isInternal,
tracks: twoWay ? 2 : 1,
});
if (!isInternal) acc[propName].push(name);
acc[twoWay ? 'twoWay' : 'oneWay'].push({
name,
SBL,
TWB,
catenary,
isInternal,
tracks: twoWay ? 2 : 1,
length,
speed,
});
if (!isInternal) acc[propName].push(name);
if (SBL) acc['sblRouteNames'].push(name);
if (SBL) acc['sblRouteNames'].push(name);
return acc;
},
{
oneWay: [],
twoWay: [],
sblRouteNames: [],
oneWayCatenaryRouteNames: [],
oneWayNoCatenaryRouteNames: [],
twoWayCatenaryRouteNames: [],
twoWayNoCatenaryRouteNames: [],
} as StationRoutes
) || {},
checkpoints: scenery.checkpoints
? scenery.checkpoints.split(';').map((sub) => ({ checkpointName: sub, scheduledTrains: [] }))
: [],
},
}));
return acc;
},
{
oneWay: [],
twoWay: [],
sblRouteNames: [],
oneWayCatenaryRouteNames: [],
oneWayNoCatenaryRouteNames: [],
twoWayCatenaryRouteNames: [],
twoWayNoCatenaryRouteNames: [],
} as StationRoutes
) || {},
checkpoints: scenery.checkpoints
? scenery.checkpoints.split(';').map((sub) => ({ checkpointName: sub, scheduledTrains: [] }))
: [],
},
};
});
},
connectToWebsocket() {
const socket = io(URLs.stacjownikAPI, {
transports: ['websocket', 'polling'],
// transports: ['websocket', 'polling'],
rememberUpgrade: true,
reconnection: true,
timeout: 2000,
});
socket.on('connect_error', (err) => {
@@ -374,8 +380,9 @@ export const useStore = defineStore('store', {
});
socket.emit('FETCH_DATA', {}, (data: APIData) => {
this.apiData = data;
this.dataStatuses.connection = DataStatus.Loaded;
this.apiData = data;
this.setOnlineData();
});
+3 -1
View File
@@ -60,6 +60,7 @@ export interface StationJSONData {
url: string;
lines: string;
project: string;
projectUrl: string;
reqLevel: number;
@@ -69,8 +70,9 @@ export interface StationJSONData {
SUP: boolean;
routes: string;
checkpoints: string | null;
authors?: string;
availability: Availability;
}
}
+58 -28
View File
@@ -1,28 +1,58 @@
.badge {
font-weight: 600;
display: inline-block;
padding: 0;
background: #585858;
margin: 0.25em;
span {
display: inline-block;
padding: 0.2em 0.4em;
}
&-none {
font-weight: 600;
padding: 0.2em 0.4em;
background: firebrick;
text-align: center;
@include smallScreen() {
font-size: 1em;
}
}
}
.badge {
font-weight: 600;
display: inline-block;
padding: 0;
background: #585858;
margin: 0.25em;
span {
display: inline-block;
padding: 0.2em 0.4em;
}
&-none {
font-weight: 600;
padding: 0.2em 0.4em;
background: firebrick;
text-align: center;
@include smallScreen() {
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;
}
}
+8 -19
View File
@@ -18,19 +18,21 @@
}
::-webkit-scrollbar {
width: 1rem;
height: 1rem;
width: 15px;
height: 15px;
background-color: transparent;
&-track {
border-radius: 0.5em;
background-color: #333;
}
&-thumb {
border-radius: 0.5em;
background-color: #666;
}
&-corner {
background-color: #333;
}
}
html {
@@ -43,11 +45,12 @@ body {
margin: 0;
padding: 0;
font-family: 'Quicksand', sans-serif;
font-weight: 500;
overflow-y: scroll;
&.no-scroll {
overflow-y: hidden;
padding-right: 1rem;
padding-right: 15px;
@include smallScreen() {
padding: 0;
@@ -79,22 +82,9 @@ body {
transition: opacity 0.3s;
padding: 0.25em;
// @include smallScreen() {
// right: 0;
// left: 0;
// &::after {
// left: 75%;
// }
// }
}
&:hover > .content {
// @include smallScreen() {
// display: none;
// }
visibility: visible;
opacity: 1;
}
@@ -103,7 +93,6 @@ body {
button,
input,
select {
// font-family: "Open Sans", sans-serif;
border: none;
font-family: 'Quicksand', sans-serif;
font-size: 1em;
+2 -2
View File
@@ -19,7 +19,7 @@
{{ $t('app.offline') }}
</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">
{{ $t('app.error') }}
@@ -111,7 +111,7 @@ export default defineComponent({
statsCardOpen: false,
currentOptionsActive: false,
dataStatus: DataStatus.Initialized,
dataStatus: DataStatus.Loading,
DataStatus,
historyList: [] as DispatcherHistory[],
+3 -3
View File
@@ -22,7 +22,7 @@
{{ $t('app.offline') }}
</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">
{{ $t('app.error') }}
@@ -105,7 +105,7 @@ export default defineComponent({
timetableHistory: [] as TimetableHistory[],
journalTimetableFilters,
dataStatus: DataStatus.Initialized,
dataStatus: DataStatus.Loading,
dataErrorMessage: '',
DataStatus,
@@ -215,7 +215,7 @@ export default defineComponent({
},
async fetchHistoryData() {
if(this.dataStatus == DataStatus.Loading) return;
// if(this.dataStatus == DataStatus.Loading) return;
const queries: string[] = [];