Compare commits

...

48 Commits

Author SHA1 Message Date
Spythere de9851ebcc Wersja 1.13.0
Wersja 1.13.0
2023-03-16 01:31:18 +01:00
Spythere ff78eba927 hotfixy filtrów 2023-03-15 18:15:01 +01:00
Spythere e4c5f6a322 filtry pociągów 2023-03-15 17:56:27 +01:00
Spythere 0a78761928 hotfix tłumaczeń 2023-03-15 15:34:36 +01:00
Spythere 4843043c29 bump wersji: 1.13.0 2023-03-15 15:20:28 +01:00
Spythere 9e1df1fb61 filtry pociągów 2023-03-15 15:19:50 +01:00
Spythere 021474cfb0 fix: tłumaczenia; hotfixy 2023-03-14 23:13:37 +01:00
Spythere 7d0e68862c filtry scenerii 2023-03-14 21:42:39 +01:00
Spythere 653d45dfc6 scenerie: remake filtrów 2023-03-13 22:26:00 +01:00
Spythere 4a4e1240a4 widok scenerii: odnośniki do tablic przy aktywnych RJ 2023-03-10 16:59:16 +01:00
Spythere 14ca48a90d hotfix 2023-03-10 16:30:36 +01:00
Spythere a02f9804b1 dziennik RJ: dodatkowe info 2023-03-10 16:30:31 +01:00
Spythere c5efc6fbac bump: wersja 1.12.2 2023-03-10 02:03:37 +01:00
Spythere cacd0a1e4e fix: data stworzenia RJ 2023-03-10 01:53:19 +01:00
Spythere 50375099ab update url api 2023-03-10 01:42:10 +01:00
Spythere 6af67ec741 fix: tłumaczenia filtrów 2023-03-09 14:35:03 +01:00
Spythere c64112c86a dziennik RJ: dodatkowe informacje 2023-03-09 14:28:16 +01:00
Spythere 0434702d3b update: URL api 2023-03-09 13:41:59 +01:00
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
Spythere 73563d5db7 Wersja 1.11.2
Wersja 1.11.2
2023-01-05 22:05:34 +01:00
Spythere 3f818069cd hotfix: podświetlenie sponsorów w dzienniku RJ 2023-01-05 16:09:27 +01:00
Spythere cdf0b2a426 feature: nasłuchiwanie aktualizacji 2023-01-05 16:05:34 +01:00
Spythere c29ddeb78c fix: poziom 0 w dzienniku RJ 2023-01-05 15:59:14 +01:00
Spythere b81d98cab7 fix: filtrowanie pociągów offline 2023-01-05 15:58:17 +01:00
Spythere 0e45bca5da feature: przycisk odświeżania dzienników 2023-01-05 14:49:44 +01:00
Spythere 715e66879f feature: przejścia pomiędzy statusami ładowań 2023-01-04 14:01:25 +01:00
Spythere 1747e15dc8 bump: 1.11.2 2023-01-03 14:58:04 +01:00
Spythere 6a923a8e1d feature: sygnatura dev w stopce 2023-01-03 14:57:36 +01:00
Spythere 25a248e95e feature: animacje list 2023-01-03 14:51:19 +01:00
Spythere aa7a6b220e feature: lvl maszynisty przy dzienniku i pociągach 2023-01-02 18:30:09 +01:00
Spythere deb7b68985 Merge branch 'development' 2023-01-01 03:02:11 +01:00
Spythere 633f05f690 fix: wyświetlanie poprawnych id RJ 2023-01-01 02:57:11 +01:00
61 changed files with 4059 additions and 3463 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.1",
"version": "1.13.0",
"private": true,
"scripts": {
"dev": "vite",
+5 -1
View File
@@ -46,7 +46,11 @@
font-size: 1rem;
@include smallScreen() {
font-size: calc(0.5rem + 1.3vw);
font-size: calc(0.55rem + 1.1vw);
}
@include screenLandscape() {
font-size: calc(0.45rem + 0.8vw);
}
}
+3 -1
View File
@@ -21,7 +21,8 @@
<footer class="app_footer">
&copy;
<a href="https://td2.info.pl/profile/?u=20777" target="_blank">Spythere</a>
{{ new Date().getUTCFullYear() }} | <a :href="releaseURL" target="_blank">v{{ VERSION }}</a>
{{ new Date().getUTCFullYear() }} |
<a :href="releaseURL" target="_blank">v{{ VERSION }}{{ isOnProductionHost ? '' : 'dev' }}</a>
<div style="display: none">&int; ukryta taktyczna całka do programowania w HTMLu</div>
</footer>
@@ -86,6 +87,7 @@ export default defineComponent({
currentLang: 'pl',
releaseURL: '',
isOnProductionHost: location.hostname == 'stacjownik-td2.web.app',
}),
created() {
+20 -18
View File
@@ -1,18 +1,20 @@
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="60" height="60" fill="#898989"/>
<path d="M30.5 6.04878H35.2195" stroke="#BFBFBF"/>
<path d="M27.9024 4.00303C25.2115 4.10008 24.2403 6.24494 24 7.41767H32.0488C31.8486 6.16406 30.5934 3.90598 27.9024 4.00303Z" fill="black"/>
<path d="M33.0244 29.6688V5.47793V4.68292H34.4878V5.47793V56.5854H33.0244V32.5H27.5V28.5V28.0163L28.5 28V31.5L31.9268 31.5447H33.0244V29.6688Z" fill="#BFBFBF"/>
<path d="M28.1463 29.2683C30.8373 29.1712 31.8085 27.0264 32.0488 25.8537H24C24.2002 27.1073 25.4554 29.3654 28.1463 29.2683Z" fill="black"/>
<path d="M32.0488 25.8537V7.86993V7.41464H24V25.8537H32.0488Z" fill="black"/>
<path d="M25 26V29.5L33.8781 44.9756" stroke="black"/>
<rect x="33.0244" y="31.5447" width="1.46341" height="25.0407" fill="white"/>
<rect x="33.0244" y="31.5447" width="1.46341" height="5.69106" fill="#FF0000"/>
<rect x="33.0244" y="42.9268" width="1.46341" height="5.69106" fill="#FF0000"/>
<rect x="33.0244" y="54.3089" width="1.46341" height="5.69106" fill="#FF0000"/>
<ellipse cx="27.9024" cy="7.40022" rx="1.46341" ry="1.40022" fill="#212121"/>
<ellipse cx="27.9024" cy="11.8343" rx="1.46341" ry="1.40022" fill="#212121"/>
<ellipse cx="27.9024" cy="16.2683" rx="1.46341" ry="1.40022" fill="#FF0000"/>
<ellipse cx="27.9024" cy="20.7023" rx="1.46341" ry="1.40022" fill="#212121"/>
<ellipse cx="27.9024" cy="25.1364" rx="1.46341" ry="1.40022" fill="#212121"/>
</svg>
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect y="-0.00012207" width="60" height="60" fill="#898989"/>
<path d="M29.0126 32.4897V10.2511V9.52028H30.4337V10.2511V57.234H29.0126V32.4897Z" fill="#BFBFBF"/>
<path d="M26.955 29.3992V32.9949L29.7672 36.9105" stroke="black" stroke-width="0.61183"/>
<rect x="29.0051" y="34.0686" width="1.42857" height="22.8196" fill="white"/>
<rect x="29.0051" y="34.0686" width="1.42857" height="5.18627" fill="#FF0000"/>
<rect x="29.0051" y="54.8137" width="1.42857" height="5.18627" fill="#FF0000"/>
<rect x="29.0051" y="44.4412" width="1.42857" height="5.18627" fill="#FF0000"/>
<rect x="27.8749" y="31.8649" width="3.75" height="2.17823" fill="white"/>
<path d="M33.5 28.5111V8.61545V8.11176H26V28.5111H33.5Z" fill="black"/>
<path d="M29.6364 5.00276C27.1289 5.09112 26.2239 7.044 26 8.11176H33.5C33.3134 6.97036 32.1438 4.91439 29.6364 5.00276Z" fill="black"/>
<path d="M29.8636 31.6201C32.3711 31.5317 33.2761 29.5789 33.5 28.5111H26C26.1865 29.6525 27.3561 31.7085 29.8636 31.6201Z" fill="black"/>
<ellipse cx="29.887" cy="11.8168" rx="1.38696" ry="1.28474" fill="#212121"/>
<ellipse cx="29.887" cy="8.0135" rx="1.38696" ry="1.28474" fill="#212121"/>
<ellipse cx="29.887" cy="15.6151" rx="1.38696" ry="1.28474" fill="#212121"/>
<ellipse cx="29.887" cy="19.6834" rx="1.38696" ry="1.28474" fill="#212121"/>
<ellipse cx="29.887" cy="23.7518" rx="1.38696" ry="1.28474" fill="#212121"/>
<ellipse cx="29.887" cy="27.8201" rx="1.38696" ry="1.28474" fill="#00FF0A"/>
<ellipse cx="29.887" cy="19.769" rx="1.38696" ry="1.28474" fill="#00FF0A"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.4 KiB

+18
View File
@@ -0,0 +1,18 @@
<svg width="144" height="147" viewBox="0 0 144 147" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_1343_19)">
<path d="M115.039 101.247C116.397 98.6665 115.405 95.4739 112.824 94.1167C110.243 92.7594 107.05 93.7514 105.693 96.3323L115.039 101.247ZM89.4447 44.0402L94.1179 46.4977L99.0329 37.1513L94.3597 34.6938L89.4447 44.0402ZM105.693 96.3323C95.7398 115.259 72.3278 122.534 53.4008 112.581L48.4858 121.927C72.5746 134.595 102.372 125.336 115.039 101.247L105.693 96.3323ZM53.4008 112.581C34.4739 102.627 27.1993 79.2155 37.1525 60.2885L27.8061 55.3735C15.1383 79.4623 24.397 109.259 48.4858 121.927L53.4008 112.581ZM37.1525 60.2885C47.1057 41.3616 70.5177 34.087 89.4447 44.0402L94.3597 34.6938C70.2709 22.026 40.4738 31.2846 27.8061 55.3735L37.1525 60.2885Z" fill="white"/>
<path d="M91.2258 38.7627L101.056 20.0698L116.15 51.8695L81.3956 57.4555L91.2258 38.7627Z" fill="white"/>
</g>
<defs>
<filter id="filter0_d_1343_19" x="18.1328" y="20.0698" width="102.017" height="115.531" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1343_19"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1343_19" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

+1 -1
View File
@@ -143,7 +143,7 @@ async function fetchDailyTimetableStats() {
distanceAvg,
timetableAuthor: maxTimetable?.authorName || '',
timetableDriver: maxTimetable?.driverName || '',
timetableId: maxTimetable?.timetableId || 0,
timetableId: maxTimetable?.id || 0,
timetableRouteDistance: maxTimetable?.routeDistance || 0,
mostActiveDispatchers,
@@ -1,7 +1,10 @@
<template>
<ul class="journal-list">
<!-- <transition-group name="journal-list-anim"> -->
<li v-for="item in computedDispatcherHistory" :class="{ sticky: typeof item == 'string' }">
<transition-group class="journal-list" tag="ul" name="list-anim">
<li
v-for="item in computedDispatcherHistory"
:key="typeof item === 'string' ? item : item.timestampFrom + item.dispatcherId"
:class="{ sticky: typeof item == 'string' }"
>
<div v-if="typeof item == 'string'" class="journal_day">
{{ item }}
</div>
@@ -14,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' }}
@@ -26,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' }) }}
@@ -44,8 +51,7 @@
</span>
</div>
</li>
<!-- </transition-group> -->
</ul>
</transition-group>
</template>
<script lang="ts">
@@ -53,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: {
@@ -62,7 +69,7 @@ export default defineComponent({
},
},
mixins: [dateMixin, styleMixin],
mixins: [dateMixin, styleMixin, imageMixin],
computed: {
computedDispatcherHistory() {
@@ -95,18 +102,11 @@ export default defineComponent({
</script>
<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;
@@ -117,9 +117,12 @@ li.sticky {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
text-align: left;
gap: 0.5em 1em;
line-height: 1.7em;
padding: 0.75em;
&.online {
@@ -135,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;
@@ -152,26 +167,17 @@ li.sticky {
}
}
.dispatcher-level {
display: inline-block;
text-align: center;
line-height: 150%;
width: 25px;
height: 25px;
margin-right: 0.5em;
border-radius: 0.25em;
.like-count {
display: flex;
align-items: center;
gap: 0.25em;
font-size: 1.2em;
color: $accentCol;
}
@include smallScreen() {
@include smallScreen {
.journal_item {
flex-direction: column;
span {
margin-top: 0.25em;
text-align: center;
}
}
}
</style>
+23 -7
View File
@@ -2,11 +2,18 @@
<div class="filters-options" @keydown.esc="showOptions = false">
<div class="bg" v-if="showOptions" @click="showOptions = false"></div>
<button class="filter-button btn--filled btn--image" @click="showOptions = !showOptions" ref="button">
<img :src="getIcon('filter2')" alt="Open filters" />
{{ $t('options.filters') }} [F]
<span class="active-indicator" v-if="currentOptionsActive"></span>
</button>
<div class="actions-bar">
<button class="filter-button btn--filled btn--image" @click="showOptions = !showOptions" ref="button">
<img :src="getIcon('filter2')" alt="Open filters" />
{{ $t('options.filters') }} [F]
<span class="active-indicator" v-if="currentOptionsActive"></span>
</button>
<button class="filter-button btn--filled btn--image" @click="refreshData">
<img :src="getIcon('refresh')" alt="Refresh data" />
{{ $t('general.refresh') }}
</button>
</div>
<datalist id="search-driver">
<option v-for="sugg in driverSuggestions" :value="sugg"></option>
@@ -22,7 +29,7 @@
<h1 class="option-title">{{ $t('options.search-title') }}</h1>
<div class="search_content">
<div class="search" v-for="(_, propName) in searchersValues" :key="propName">
<label v-if="propName == 'search-date'" for="date">{{ $t('options.search-date') }}</label>
<label v-if="propName == 'search-date'" for="date">{{ $t(`options.search-${optionsType}-date`) }}</label>
<div class="search-box">
<input
@@ -99,7 +106,7 @@ import SelectBox from '../Global/SelectBox.vue';
export default defineComponent({
components: { SelectBox, ActionButton },
emits: ['onSearchConfirm', 'onOptionsReset'],
emits: ['onSearchConfirm', 'onOptionsReset', 'onRefreshData'],
mixins: [imageMixin, keyMixin],
props: {
@@ -122,6 +129,11 @@ export default defineComponent({
type: Boolean,
default: false,
},
optionsType: {
type: String,
required: true
}
},
data() {
@@ -206,6 +218,10 @@ export default defineComponent({
}
},
refreshData() {
this.$emit('onRefreshData');
},
startSearchTimeout(type: 'driver' | 'dispatcher', value: string) {
if (this[`${type}Suggestions`].includes(value)) return;
+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>
@@ -1,25 +1,40 @@
<template>
<ul class="journal-list">
<transition-group class="journal-list" tag="ul" name="list-anim">
<li
v-for="{ timetable, sceneryList, ...item } in computedTimetableHistory"
class="journal_item"
:key="timetable.timetableId"
:key="timetable.id"
@click="item.showExtra.value = !item.showExtra.value"
>
<div class="journal_item-info">
<div class="info-top">
<div class="info-general">
<span
class="general-train"
tabindex="0"
@click="showTimetable(timetable)"
@click.stop="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>
<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"
@@ -47,15 +62,27 @@
<hr />
<div class="scenery-list">
<span v-for="(scenery, i) in sceneryList" :key="scenery.name" :class="{ confirmed: scenery.confirmed }">
<span v-if="i > 0"> &gt;</span>
<span
v-for="(scenery, i) in sceneryList.filter((_, i) =>
!item.showExtra.value ? i == 0 || i == sceneryList.length - 1 : true
)"
:key="scenery.name"
:class="{ confirmed: scenery.confirmed }"
>
<span v-if="i > 0">
&gt;
<span v-if="!item.showExtra.value && i == 1 && sceneryList.length > 2"
>... (+{{ sceneryList.length - 2 }}) &gt;</span
>
</span>
{{ scenery.name }}
<!-- Data odjazdu ze stacji początkowej -->
<span v-if="i == 0" v-html="scenery.beginDateHTML"></span>
<!-- Data przyjazdu do stacji końcowej -->
<span v-if="i == sceneryList.length - 1" v-html="scenery.endDateHTML"> </span>
<span
v-if="i == sceneryList.length - 1 || (i == 1 && !item.showExtra.value)"
v-html="scenery.endDateHTML"
></span>
</span>
</div>
@@ -87,37 +114,37 @@
<router-link class="dispatcher-link" :to="`/journal/dispatchers?dispatcherName=${timetable.authorName}`">
<b>{{ timetable.authorName }}</b>
</router-link>
<span class="text--grayed">
({{
(new Date(timetable.createdAt).getTime() - new Date(timetable.beginDate).getTime() < 0
? new Date(timetable.createdAt)
: new Date(timetable.beginDate)
).toLocaleString($i18n.locale, { timeStyle: 'short', dateStyle: 'full' })
}})
</span>
</div>
<button
v-if="timetable.stockString"
class="btn--option btn--show"
@click="item.showStock.value = !item.showStock.value"
>
<button class="btn--option btn--show">
{{ $t('journal.stock-info') }}
<img :src="getIcon(`arrow-${item.showStock.value ? 'asc' : 'desc'}`)" alt="Arrow" />
<img :src="getIcon(`arrow-${item.showExtra.value ? 'asc' : 'desc'}`)" alt="Arrow" />
</button>
<div class="info-extended" v-if="timetable.stockString && item.showStock.value">
<div class="info-extended" v-if="timetable.stockString && item.showExtra.value">
<hr />
<div>
<span class="badge info-badge">
<span>{{ $t('journal.stock-max-speed') }}</span>
<span>{{ timetable.maxSpeed }}km/h</span>
</span>
<span class="badge info-badge">
<span>{{ $t('journal.stock-length') }}</span>
<span>{{ timetable.stockLength }}m</span>
</span>
<span class="badge info-badge">
<span>{{ $t('journal.stock-mass') }}</span>
<span>{{ Math.floor(timetable.stockMass! / 1000) }}t</span>
</span>
</div>
<ul class="stock-list">
<li v-for="(car, i) in timetable.stockString.split(';')" :key="i">
<img
@@ -125,14 +152,13 @@
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${car.split(':')[0]}.png`"
:alt="car"
/>
<div>{{ car.replace(/_/g, ' ').split(':')[0] }}</div>
</li>
</ul>
</div>
</div>
</li>
</ul>
</transition-group>
</template>
<script lang="ts">
@@ -140,7 +166,9 @@ import { defineComponent, PropType, ref } from 'vue';
import dateMixin from '../../mixins/dateMixin';
import imageMixin from '../../mixins/imageMixin';
import modalTrainMixin from '../../mixins/modalTrainMixin';
import styleMixin from '../../mixins/styleMixin';
import { TimetableHistory } from '../../scripts/interfaces/api/TimetablesAPIData';
import { TimetableStop } from '../../scripts/interfaces/api/TrainAPIData';
export default defineComponent({
props: {
@@ -150,14 +178,14 @@ export default defineComponent({
},
},
mixins: [dateMixin, imageMixin, modalTrainMixin],
mixins: [dateMixin, imageMixin, modalTrainMixin, styleMixin],
computed: {
computedTimetableHistory() {
return this.timetableHistory.map((timetable) => ({
timetable,
sceneryList: this.getSceneryList(timetable),
showStock: ref(false),
showExtra: ref(false),
}));
},
},
@@ -187,16 +215,12 @@ export default defineComponent({
this.$i18n.locale
)}</span>)`;
const abandonedDateHTML = ` (porz. ${this.localeTime(
timetable.fulfilled ? timetable.scheduledEndDate : timetable.endDate,
this.$i18n.locale
)})`;
return { name, confirmed: i < timetable.confirmedStopsCount, beginDateHTML, endDateHTML, abandonedDateHTML };
return { name, confirmed: i < timetable.confirmedStopsCount, beginDateHTML, endDateHTML };
});
},
showTimetable(timetable: TimetableHistory) {
if (!timetable) return;
if (timetable.terminated) return;
this.selectModalTrain(timetable.driverName + timetable.trainNo.toString());
@@ -211,11 +235,16 @@ export default defineComponent({
</script>
<style lang="scss" scoped>
@import '../../styles/animations.scss';
@import '../../styles/variables.scss';
@import '../../styles/responsive.scss';
@import '../../styles/badge.scss';
@import '../../styles/JournalSection.scss';
.journal_item {
cursor: pointer;
}
hr {
margin: 0.25em 0;
}
@@ -242,10 +271,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 {
@@ -257,6 +290,12 @@ hr {
}
}
.general-train {
display: flex;
align-items: center;
gap: 0.25em;
}
ul.stock-list {
display: flex;
align-items: flex-end;
@@ -297,14 +336,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>
+48 -46
View File
@@ -2,17 +2,37 @@
<section class="scenery-timetable">
<div class="timetable-header">
<h3>
<img :src="getIcon('timetable')" alt="icon-timetable" />&nbsp;
<img :src="getIcon('timetable')" alt="icon-timetable" />
<span>{{ $t('scenery.timetables') }}</span>
&nbsp;
<span class="text--primary">{{ station.onlineInfo?.scheduledTrains?.length || '0' }}</span>
<span>&nbsp;/&nbsp;</span>
<span class="text--grayed">
{{ station.onlineInfo?.scheduledTrains?.filter((train) => train.stopInfo.confirmed).length || '0' }}
<span>
<span class="text--primary">{{ station.onlineInfo?.scheduledTrains?.length || '0' }}</span>
<span> / </span>
<span class="text--grayed">
{{ station.onlineInfo?.scheduledTrains?.filter((train) => train.stopInfo.confirmed).length || '0' }}
</span>
</span>
<span class="header_links">
<a
:href="`https://pragotron-td2.web.app/board?name=${station.name}`"
target="_blank"
:title="$t('scenery.pragotron-link')"
>
<img :src="getIcon('pragotron')" alt="icon-pragotron" />
</a>
<a
:href="`https://tablice-td2.web.app/?station=${station.name}`"
target="_blank"
:title="$t('scenery.tablice-link')"
>
<img :src="getIcon('tablice', 'ico')" alt="icon-tablice" />
</a>
</span>
</h3>
<div class="timetable-checkpoints" v-if="station && station.generalInfo?.checkpoints">
<div class="timetable-checkpoints" v-if="station?.generalInfo?.checkpoints">
<span v-for="(cp, i) in station.generalInfo.checkpoints" :key="i">
{{ (i > 0 && '&bull;') || '' }}
@@ -41,7 +61,7 @@
{{ $t('scenery.no-timetables') }}
</span>
<transition-group name="timetables-anim">
<transition-group name="list-anim">
<div
class="timetable-item"
v-for="(scheduledTrain, i) in computedScheduledTrains"
@@ -168,7 +188,6 @@ import { useStore } from '../../store/store';
import imageMixin from '../../mixins/imageMixin';
import modalTrainMixin from '../../mixins/modalTrainMixin';
import ScheduledTrainStatus from './ScheduledTrainStatus.vue';
import ScheduledTrain from '../../scripts/interfaces/ScheduledTrain';
export default defineComponent({
name: 'SceneryTimetable',
@@ -272,22 +291,7 @@ export default defineComponent({
<style lang="scss" scoped>
@import '../../styles/responsive.scss';
@import '../../styles/variables.scss';
.timetables-anim-move,
.timetables-anim-enter-active,
.timetables-anim-leave-active {
transition: all 250ms ease;
}
.timetables-anim-enter-from,
.timetables-anim-leave-to {
opacity: 0;
transform: translateY(30px);
}
.timetables-anim-leave-active {
position: absolute;
}
@import '../../styles/animations.scss';
.scenery-timetable {
height: 100%;
@@ -296,24 +300,36 @@ export default defineComponent({
}
.timetable-header {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
position: sticky;
top: 0;
z-index: 99;
background-color: #181818;
padding: 0.5em;
img {
width: 25px;
vertical-align: middle;
}
h3 {
display: flex;
justify-content: center;
flex-wrap: wrap;
align-items: center;
gap: 0.5em;
font-size: 1.3em;
}
}
.header_links {
display: flex;
gap: 0.5em;
margin-left: 0.5em;
}
.timetable {
&-count {
margin-left: 0.5em;
@@ -370,7 +386,8 @@ export default defineComponent({
flex-wrap: wrap;
font-size: 1.1em;
padding: 0.75em 0;
margin-top: 0.5em;
button.checkpoint_item {
color: #aaa;
@@ -486,21 +503,6 @@ export default defineComponent({
font-size: 0.85em;
}
.scenery-timetable-list-anim {
&-enter-from,
&-leave-to {
opacity: 0;
}
&-enter-active {
transition: all 100ms ease-out;
}
&-leave-active {
transition: all 100ms ease-out 100ms;
}
}
@include smallScreen {
.timetable-item {
grid-template-columns: 1fr;
@@ -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>
@@ -11,24 +49,22 @@
</div>
<div>
<router-link :to="`/journal/timetables?timetableId=${historyItem.timetableId}`">
<span class="text--grayed"> #{{ historyItem.timetableId }} </span>
<router-link :to="`/journal/timetables?timetableId=${historyItem.id}`">
<span class="text--grayed"> #{{ historyItem.id }} </span>
<b class="text--primary">&nbsp;{{ historyItem.trainCategoryCode }} {{ historyItem.trainNo }}</b>
<div>{{ historyItem.driverName }}</div>
</router-link>
</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 {
+79 -38
View File
@@ -1,5 +1,11 @@
<template>
<button class="btn--action" :class="option.section" :data-selected="option.value" @click="handleChange">
<button
class="btn--action"
:class="option.section"
:data-selected="option.value"
@click="handleLeftClick"
@dblclick="handleDbClick"
>
{{ $t(`filters.${option.id}`) }}
</button>
</template>
@@ -29,20 +35,51 @@ export default defineComponent({
filterStore: useStationFiltersStore(),
};
},
methods: {
handleChange() {
handleLeftClick() {
this.option.value = !this.option.value;
this.filterStore.lastClickedFilterId = '';
this.filterStore.changeFilterValue({
name: this.option.name,
value: !this.option.value,
});
},
handleDbClick(e: Event) {
e.preventDefault();
const lastClicked = this.filterStore.lastClickedFilterId == this.option.id;
console.log(this.filterStore.lastClickedFilterId);
this.filterStore.lastClickedFilterId = this.option.id;
this.option.value = true;
this.filterStore.changeFilterValue({
name: this.option.name,
value: !this.option.value,
});
this.filterStore.inputs.options
.filter((option) => {
return option.section == this.option.section && option.id != this.option.id;
})
.forEach((option) => {
this.filterStore.changeFilterValue({
name: option.name,
value: this.option.value,
});
option.value = !this.option.value;
});
},
},
});
</script>
<style lang="scss" scoped>
$realityCol: #e03b07;
$accessCol: #e03b07;
$controlCol: #0085ff;
$signalCol: #bf7c00;
@@ -51,56 +88,60 @@ $saveCol: #28a826;
$routesCol: #9049c0;
button {
width: 100%;
padding: 0.4em;
border-radius: 0.4em;
padding: 0.25em;
border-radius: 0;
&:focus-visible {
outline: 1px solid white;
}
&[data-selected='true'] {
&.access {
background-color: $accessCol;
box-shadow: 0 0 6px 1px $accessCol;
}
// &.reality {
// background-color: $realityCol;
// box-shadow: 0 0 6px 1px $realityCol;
// }
&.control {
background-color: $controlCol;
box-shadow: 0 0 6px 1px $controlCol;
}
// &.access {
// background-color: $accessCol;
// box-shadow: 0 0 6px 1px $accessCol;
// }
&.signals {
background-color: $signalCol;
box-shadow: 0 0 6px 1px $signalCol;
}
// &.control {
// background-color: $controlCol;
// box-shadow: 0 0 6px 1px $controlCol;
// }
&.routes {
background-color: $routesCol;
box-shadow: 0 0 6px 1px $routesCol;
}
// &.signals {
// background-color: $signalCol;
// box-shadow: 0 0 6px 1px $signalCol;
// }
&.status {
background-color: $statusCol;
box-shadow: 0 0 6px 1px $statusCol;
}
// &.routes {
// background-color: $routesCol;
// box-shadow: 0 0 6px 1px $routesCol;
// }
&.save {
background-color: $saveCol;
box-shadow: 0 0 6px 1px $saveCol;
}
// &.status {
// background-color: $statusCol;
// box-shadow: 0 0 6px 1px $statusCol;
// }
&.troll {
background-color: firebrick;
box-shadow: 0 0 6px 1px firebrick;
}
// &.save {
// background-color: $saveCol;
// box-shadow: 0 0 6px 1px $saveCol;
// }
&.mode {
background-color: lightgreen;
color: black;
// &.troll {
// background-color: firebrick;
// box-shadow: 0 0 6px 1px firebrick;
// }
font-weight: 500;
}
// & {
background-color: forestgreen;
font-weight: bold;
// }
}
}
</style>
@@ -26,15 +26,28 @@
<div class="card" v-if="isVisible" tabindex="0" ref="cardEl">
<div class="card_content">
<div class="card_title flex">{{ $t('filters.title') }}</div>
<p class="card_info" v-html="$t('filters.desc')"></p>
<section class="card_options">
<filter-option
v-for="(option, i) in filterStore.inputs.options"
:option="option"
:key="i"
@optionChange="handleChange"
/>
<div class="option-section" v-for="section in filterStore.inputs.optionSections">
<h3 class="text--primary">
{{ $t(`filters.sections.${section}`) }}
<button @click="filterStore.resetSectionOptions(section)">RESET</button>
</h3>
<hr />
<div class="section-inputs">
<filter-option
v-for="(option, i) in filterStore.inputs.options.filter((o) => o.section == section)"
:option="option"
:key="i"
/>
</div>
</div>
</section>
<section class="card_timestamp" style="text-align: center">
<div>{{ $t('filters.minimum-hours-title') }}</div>
<span class="clock">
@@ -80,18 +93,25 @@
</div>
</div>
</section>
<section class="card_actions">
<div class="action-buttons">
<button class="btn--action" style="width: 100%" @click="saveFilters" :data-selected="saveOptions">
{{ $t('filters.save') }}
</button>
<button class="btn--action" @click="resetFilters">{{ $t('filters.reset') }}</button>
<button class="btn--action" @click="closeCard">{{ $t('filters.close') }}</button>
</div>
</section>
</div>
<section class="card_actions">
<div class="action-buttons">
<button class="btn--action" style="width: 100%" @click="saveFilters" :data-selected="saveOptions">
{{ $t('filters.save') }}
</button>
<button
class="btn--action"
@click="resetFilters"
:disabled="filterStore.areFiltersAtDefault"
:data-disabled="filterStore.areFiltersAtDefault"
>
{{ $t('filters.reset') }}
</button>
<button class="btn--action" @click="closeCard">{{ $t('filters.close') }}</button>
</div>
</section>
</div>
</transition>
</section>
@@ -108,6 +128,7 @@ import { useStore } from '../../store/store';
import ActionButton from '../Global/ActionButton.vue';
import FilterOption from './FilterOption.vue';
import { filterInitStates } from '../../store/constants/initFilterStates';
export default defineComponent({
components: { ActionButton, FilterOption },
@@ -181,15 +202,6 @@ export default defineComponent({
this.isVisible = !this.isVisible;
},
handleChange(change: { name: string; value: boolean }) {
this.filterStore.changeFilterValue({
name: change.name,
value: !change.value,
});
if (this.saveOptions) StorageManager.setBooleanValue(change.name, change.value);
},
handleInput(e: Event) {
const target = e.target as HTMLInputElement;
@@ -281,6 +293,14 @@ export default defineComponent({
}
.card {
display: grid;
grid-template-rows: 1fr auto;
&_info {
background-color: #111;
padding: 0.5em;
}
&_controls {
display: flex;
gap: 0.5em;
@@ -292,13 +312,13 @@ export default defineComponent({
}
&_content {
padding: 1em 0.5em;
display: flex;
flex-direction: column;
gap: 1em;
max-height: 90vh;
padding: 1em;
overflow: auto;
}
&_title {
@@ -309,18 +329,6 @@ export default defineComponent({
text-align: center;
}
&_options {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
grid-template-rows: repeat(4, 1fr);
gap: 0.5em;
@include smallScreen() {
grid-template-columns: repeat(auto-fit, minmax(8em, 1fr));
grid-template-rows: auto;
}
}
&_regions {
display: flex;
justify-content: center;
@@ -391,6 +399,9 @@ export default defineComponent({
}
&_actions {
width: 100%;
padding: 0.5em;
.filter-option {
max-width: 50%;
margin: 0 auto;
@@ -409,14 +420,42 @@ export default defineComponent({
padding: 0.5em;
&[data-selected='true'] {
background-color: lightgreen;
color: black;
background-color: forestgreen;
}
}
}
}
}
.card_options {
.option-section h3 {
display: flex;
align-items: center;
margin-bottom: 0.25em;
gap: 0.5em;
button {
padding: 0.15em;
color: coral;
}
}
.section-inputs {
display: grid;
// flex-wrap: wrap;
grid-template-columns: repeat(3, minmax(0, 1fr));
// grid-template-rows: repeat(3, 1fr);
gap: 0.5em;
margin: 1em 0;
// @include smallScreen() {
// grid-template-columns: repeat(auto-fit, minmax(8em, 1fr));
// grid-template-rows: auto;
// }
}
}
.slider {
display: flex;
align-items: center;
+31 -27
View File
@@ -2,22 +2,23 @@
<div class="train-info" tabindex="0">
<section class="train-route">
<div class="train_general">
<span>
<span class="timetable-id" v-if="train.timetableData">#{{ train.timetableData.timetableId }}</span>
<b class="warning-timeout" v-if="train.isTimeout" :title="$t('trains.timeout')">?</b>
<span class="timetable-id" v-if="train.timetableData">#{{ train.timetableData.timetableId }}</span>
<span class="timetable_warnings">
<span class="train-badge twr" v-if="train.timetableData?.TWR">TWR</span>
<span class="train-badge skr" v-if="train.timetableData?.SKR">SKR</span>
</span>
<strong class="timetable-category" v-if="train.timetableData">
{{ train.timetableData.category }}
</strong>
<strong class="train-number">&nbsp;{{ train.trainNo }}</strong>
|
<span class="train-driver" :class="{ supporter: train.isSupporter }">{{ train.driverName }}</span>
<b class="warning-timeout" v-if="train.isTimeout" :title="$t('trains.timeout')">?</b>
<span class="timetable_warnings" v-if="train.timetableData?.TWR || train.timetableData?.SKR">
<span class="train-badge twr" v-if="train.timetableData?.TWR">TWR</span>
<span class="train-badge skr" v-if="train.timetableData?.SKR">SKR</span>
</span>
<strong>
<span v-if="train.timetableData" class="text--primary">{{ train.timetableData.category }}&nbsp;</span>
<span class="train-number">{{ train.trainNo }}</span>
</strong>
<span>&bull;</span>
<b 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">
@@ -40,9 +41,7 @@
</div>
<div class="timetable_progress" style="margin-top: 0.5em" v-if="train.timetableData">
<!-- <span> </span> -->
<span class="timetable_progress-bar">
<!-- {{ confirmedPercentage(train.timetableData.followingStops) }}%&nbsp; -->
<span class="bar-bg"></span>
<span
class="bar-fg"
@@ -94,6 +93,7 @@
<script lang="ts">
import { defineComponent } from 'vue';
import imageMixin from '../../mixins/imageMixin';
import styleMixin from '../../mixins/styleMixin';
import trainInfoMixin from '../../mixins/trainInfoMixin';
import Train from '../../scripts/interfaces/Train';
@@ -110,12 +110,14 @@ export default defineComponent({
},
},
mixins: [trainInfoMixin, imageMixin],
mixins: [trainInfoMixin, imageMixin, styleMixin],
});
</script>
<style lang="scss" scoped>
@import '../../styles/responsive.scss';
@import '../../styles/badge.scss';
.image-warning {
height: 1em;
@@ -149,7 +151,6 @@ export default defineComponent({
}
.timetable-id {
margin-right: 0.3em;
color: #d2d2d2;
}
@@ -159,11 +160,7 @@ export default defineComponent({
display: inline-block;
text-align: center;
width: 1.25em;
height: 1.25em;
border-radius: 50%;
margin-left: 0.25em;
padding: 0 0.25em;
}
.timetable_stops {
@@ -174,16 +171,20 @@ export default defineComponent({
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0.25em;
margin-right: 1.5em;
}
.train-status-badges {
display: flex;
flex-wrap: wrap;
gap: 0.25em;
}
.train-badge {
padding: 0.15em 0.35em;
margin-right: 0.3em;
padding: 0.1em 0.2em;
border-radius: 0.2em;
font-weight: bold;
font-size: 0.9em;
@@ -197,7 +198,7 @@ export default defineComponent({
}
&.offline {
background-color: #b83b2d;
background-color: #9c362b;
}
}
@@ -216,6 +217,9 @@ export default defineComponent({
}
.timetable_warnings {
display: flex;
gap: 0.2em;
color: black;
}
+40 -22
View File
@@ -43,29 +43,34 @@
<h1 class="option-title">{{ $t('options.sort-title') }}</h1>
<div class="options_sorters">
<div v-for="opt in translatedSorterOptions">
<button
v-for="opt in translatedSorterOptions"
class="sort-option btn--option"
:data-selected="opt.id == sorterActive.id"
@click="onSorterChange(opt)"
>
{{ opt.value.toUpperCase() }}
</button>
</div>
<h1 class="option-title" v-if="trainFilterList.length != 0">{{ $t('options.filter-title') }}</h1>
<div class="options_filters">
<div v-for="section in Object.keys(TrainFilterSection)">
<button
class="sort-option btn--option"
:data-selected="opt.id == sorterActive.id"
@click="onSorterChange(opt)"
class="btn--option"
v-for="filter in trainFilterList.filter((f) => f.section == section)"
:data-inactive="!filter.isActive"
@click="onFilterChange(filter)"
>
{{ opt.value.toUpperCase() }}
{{ $t(`options.filter-${filter.id}`) }}
</button>
</div>
</div>
<h1 class="option-title" v-if="trainFilterList.length != 0">{{ $t('options.filter-title') }}</h1>
<div class="options_filters">
<div class="filter-option" v-for="filter in trainFilterList">
<button class="btn--option" :data-inactive="!filter.isActive" @click="onFilterChange(filter)">
{{ $t(`options.filter-${filter.id}`) }}
</button>
</div>
<div class="filter-actions">
<button class="btn--action" @click="clearAllFilters">{{ $t('options.filter-clear') }}</button>
<button class="btn--action" @click="resetAllFilters">{{ $t('options.filter-reset') }}</button>
</div>
<div class="filter-actions">
<div></div>
<button class="btn--action" @click="resetAllFilters">{{ $t('options.filter-reset') }}</button>
</div>
</div>
</div>
@@ -80,6 +85,7 @@ import keyMixin from '../../mixins/keyMixin';
import { TrainFilter } from '../../types/Trains/TrainOptionsTypes';
import ActionButton from '../Global/ActionButton.vue';
import SelectBox from '../Global/SelectBox.vue';
import { TrainFilterSection } from '../../scripts/enums/TrainFilterType';
export default defineComponent({
components: { SelectBox, ActionButton },
@@ -101,6 +107,7 @@ export default defineComponent({
return {
showOptions: false,
lastSelectedFilter: null as TrainFilter | null,
TrainFilterSection,
};
},
@@ -183,13 +190,24 @@ export default defineComponent({
margin: 0 auto;
}
.filter-option {
.options_sorters {
display: flex;
grid-template-columns: repeat(3, 1fr);
}
.options_filters > div {
display: flex;
width: 100%;
gap: 0.5em;
button {
color: white;
width: 100%;
color: springgreen;
font-weight: bold;
&[data-disabled='true'] {
color: #888;
&[data-inactive='true'] {
color: #aaa;
}
}
}
@@ -201,7 +219,7 @@ export default defineComponent({
margin-top: 1em;
button {
> * {
width: 100%;
}
}
@@ -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>
+4 -17
View File
@@ -12,12 +12,7 @@
{{ $t('trains.no-trains') }}
</div>
<!-- <div class="timeouts-warning" v-if="trainNumbersWithTimeouts.length == 0">
<b class="warning-timeout">?</b>
{{ $t('trains.timeout') }}
</div> -->
<ul class="train-list" v-else>
<transition-group name="list-anim" tag="ul" class="train-list" v-else>
<li
class="train-row"
v-for="train in currentTrains"
@@ -27,7 +22,7 @@
>
<TrainInfo :train="train" />
</li>
</ul>
</transition-group>
</div>
</transition>
</div>
@@ -71,18 +66,9 @@ export default defineComponent({
id: string | number;
dir: number;
},
distanceLimitExceeded: computed(
() => props.trains.findIndex(({ timetableData }) => timetableData && timetableData.routeDistance > 200) != -1
),
};
},
computed: {
trainNumbersWithTimeouts() {
return this.store.trainList.filter((train) => train.isTimeout).map((train) => train.trainNo);
},
},
activated() {
const query = this.$route.query;
if (query.trainNo && query.driverName) {
@@ -98,6 +84,7 @@ export default defineComponent({
<style lang="scss" scoped>
@import '../../styles/responsive.scss';
@import '../../styles/animations.scss';
.anim {
&-enter-from,
@@ -158,7 +145,7 @@ img.train-image {
.train {
&-list {
overflow: auto;
position: relative;
@include smallScreen() {
width: 100%;
+31 -2
View File
@@ -1,33 +1,58 @@
import { TrainFilterType } from '../../scripts/enums/TrainFilterType';
import { TrainFilterSection, TrainFilterType } from '../../scripts/enums/TrainFilterType';
import { TrainFilter } from '../../types/Trains/TrainOptionsTypes';
export const trainFilters: TrainFilter[] = [
{
id: TrainFilterType.twr,
section: TrainFilterSection.TRAIN_TYPE,
isActive: true,
},
{
id: TrainFilterType.skr,
section: TrainFilterSection.TRAIN_TYPE,
isActive: true,
},
{
id: TrainFilterType.common,
section: TrainFilterSection.TRAIN_TYPE,
isActive: true,
},
{
id: TrainFilterType.passenger,
section: TrainFilterSection.TIMETABLE_TYPE,
isActive: true,
},
{
id: TrainFilterType.freight,
section: TrainFilterSection.TIMETABLE_TYPE,
isActive: true,
},
{
id: TrainFilterType.other,
section: TrainFilterSection.TIMETABLE_TYPE,
isActive: true,
},
{
id: TrainFilterType.withComments,
section: TrainFilterSection.COMMENTS,
isActive: true,
},
{
id: TrainFilterType.comments,
id: TrainFilterType.noComments,
section: TrainFilterSection.COMMENTS,
isActive: true,
},
{
id: TrainFilterType.withTimetable,
section: TrainFilterSection.TIMETABLE,
isActive: true,
},
{
id: TrainFilterType.noTimetable,
section: TrainFilterSection.TIMETABLE,
isActive: true,
},
];
@@ -37,6 +62,10 @@ export const sorterOptions = [
id: 'distance',
value: 'kilometraż',
},
{
id: 'id',
value: 'id rozkładu',
},
{
id: 'progress',
value: 'przebyta trasa',
+65 -46
View File
@@ -1,41 +1,38 @@
{
"optionSections": ["reality", "package-access", "access", "control", "addons", "blockades", "signals", "status"],
"options": [
{
"id": "default",
"name": "default",
"iconName": "td2",
"section": "access",
"value": true,
"defaultValue": true
},
{
"id": "not-default",
"name": "notDefault",
"iconName": "",
"section": "access",
"value": true,
"defaultValue": true
},
{
"id": "real",
"name": "real",
"iconName": "lock",
"section": "access",
"section": "reality",
"value": true,
"defaultValue": true
},
{
"id": "fictional",
"name": "fictional",
"iconName": "user",
"section": "access",
"section": "reality",
"value": true,
"defaultValue": true
},
{
"id": "default",
"name": "default",
"section": "package-access",
"value": true,
"defaultValue": true
},
{
"id": "not-default",
"name": "notDefault",
"section": "package-access",
"value": true,
"defaultValue": true
},
{
"id": "non-public",
"name": "nonPublic",
"iconName": "user",
"section": "access",
"value": true,
"defaultValue": true
@@ -43,7 +40,6 @@
{
"id": "unavailable",
"name": "unavailable",
"iconName": "user",
"section": "access",
"value": false,
"defaultValue": false
@@ -51,7 +47,6 @@
{
"id": "abandoned",
"name": "abandoned",
"iconName": "user",
"section": "access",
"value": false,
"defaultValue": false
@@ -59,7 +54,6 @@
{
"id": "SPK",
"name": "SPK",
"iconName": "SPK",
"section": "control",
"value": true,
"defaultValue": true
@@ -67,7 +61,6 @@
{
"id": "SCS",
"name": "SCS",
"iconName": "SCS",
"section": "control",
"value": true,
"defaultValue": true
@@ -75,15 +68,21 @@
{
"id": "SPE",
"name": "SPE",
"iconName": "SPE",
"section": "control",
"value": true,
"defaultValue": true
},
{
"id": "SPK-M",
"name": "mechaniczne+SPK",
"section": "control",
"value": true,
"defaultValue": true
},
{
"id": "manual",
"name": "ręczne",
"iconName": "ręczne",
"id": "SCS-M",
"name": "mechaniczne+SCS",
"section": "control",
"value": true,
"defaultValue": true
@@ -91,7 +90,27 @@
{
"id": "mechanical",
"name": "mechaniczne",
"iconName": "mechaniczne",
"section": "control",
"value": true,
"defaultValue": true
},
{
"id": "SPK-R",
"name": "ręczne+SPK",
"section": "control",
"value": true,
"defaultValue": true
},
{
"id": "SCS-R",
"name": "ręczne+SCS",
"section": "control",
"value": true,
"defaultValue": true
},
{
"id": "manual",
"name": "ręczne",
"section": "control",
"value": true,
"defaultValue": true
@@ -99,23 +118,34 @@
{
"id": "SUP",
"name": "SUP",
"iconName": "SUP",
"section": "control",
"section": "addons",
"value": true,
"defaultValue": true
},
{
"id": "noSUP",
"name": "noSUP",
"section": "addons",
"value": true,
"defaultValue": true
},
{
"id": "SBL",
"name": "SBL",
"iconName": "SBL",
"section": "routes",
"section": "blockades",
"value": true,
"defaultValue": true
},
{
"id": "PBL",
"name": "PBL",
"section": "blockades",
"value": true,
"defaultValue": true
},
{
"id": "modern",
"name": "współczesna",
"iconName": "współczesna",
"section": "signals",
"value": true,
"defaultValue": true
@@ -123,7 +153,6 @@
{
"id": "semaphores",
"name": "kształtowa",
"iconName": "kształtowa",
"section": "signals",
"value": true,
"defaultValue": true
@@ -131,7 +160,6 @@
{
"id": "mixed",
"name": "mieszana",
"iconName": "mieszana",
"section": "signals",
"value": true,
"defaultValue": true
@@ -139,7 +167,6 @@
{
"id": "historical",
"name": "historyczna",
"iconName": "historyczna",
"section": "signals",
"value": true,
"defaultValue": true
@@ -148,7 +175,6 @@
{
"id": "free",
"name": "free",
"iconName": "",
"section": "status",
"value": false,
@@ -157,7 +183,6 @@
{
"id": "occupied",
"name": "occupied",
"iconName": "",
"section": "status",
"value": true,
@@ -166,7 +191,6 @@
{
"id": "endingStatus",
"name": "endingStatus",
"iconName": "",
"section": "status",
"value": true,
@@ -175,7 +199,6 @@
{
"id": "afkStatus",
"name": "afkStatus",
"iconName": "",
"section": "status",
"value": true,
@@ -184,7 +207,6 @@
{
"id": "noSpaceStatus",
"name": "noSpaceStatus",
"iconName": "",
"section": "status",
"value": true,
@@ -193,7 +215,6 @@
{
"id": "unavailableStatus",
"name": "unavailableStatus",
"iconName": "",
"section": "status",
"value": true,
@@ -254,7 +275,6 @@
{
"id": "include-selected",
"name": "include-selected",
"iconName": "",
"section": "mode",
"value": true,
"defaultValue": true
@@ -262,7 +282,6 @@
{
"id": "save",
"name": "save",
"iconName": "",
"section": "mode",
"value": true,
"defaultValue": true
+56 -17
View File
@@ -1,6 +1,7 @@
{
"general": {
"and": " and "
"and": " and ",
"refresh": "REFRESH"
},
"app": {
"sceneries": "SCENERIES",
@@ -34,7 +35,7 @@
"desc": {
"control-type": "Control type: ",
"signals-type": "Signals type: ",
"SBL": "This scenery has automatic line blockade system on following routes: ",
"SBL": "This scenery has automatic block signalling (ABS/SBL) system on following routes: ",
"SUP": "Requires the SUP application (level crossing remote control simulator)",
"TWB-all": "This scenery has two-way route blockade on all routes",
"TWB-routes": "This scenery has two-way route blockade on following routes: ",
@@ -93,7 +94,8 @@
"search-dispatcher": "Dispatcher name",
"search-station": "Scenery name",
"search-author": "Timetable author name",
"search-date": "Timetable date (CEST / GMT+2)",
"search-timetables-date": "Timetable date (CEST / GMT+2)",
"search-dispatchers-date": "Service date (CEST / GMT+2)",
"sort-mass": "mass",
"sort-speed": "speed",
@@ -102,6 +104,7 @@
"sort-timetable": "train no.",
"sort-progress": "route progress",
"sort-delay": "current delay",
"sort-id": "timetable id",
"sort-total-stops": "total stops",
"sort-beginDate": "date",
@@ -109,13 +112,16 @@
"sort-timestampFrom": "date",
"sort-duration": "duration",
"filter-comments": "COMMENTS",
"filter-twr": "TWR",
"filter-skr": "SKR",
"filter-noComments": "NO COMMENTS",
"filter-withComments": "COMMENTS",
"filter-twr": "HIGH RISK CARGO",
"filter-skr": "EXCEEDED GAUGE",
"filter-common": "NO WARNINGS",
"filter-passenger": "PASSENGER",
"filter-freight": "FREIGHT",
"filter-other": "OTHER",
"filter-noTimetable": "NO TIMETABLE",
"filter-withTimetable": "TIMETABLE",
"filter-reset": "RESET FILTERS",
"filter-clear": "CLEAR FILTERS",
@@ -126,14 +132,27 @@
"filter-active": "ACTIVE"
},
"filters": {
"desc": " &bull; Left mouse click: select / unselect chosen filter <br /> &bull; Double left click: unselect all filters but chosen from a <b class='text--primary'>group</b> <br /> &bull; <span style='color: coral'>RESET</span>: reset all filters from a <b class='text--primary'>group</b>",
"sections": {
"reality": "SCENERY REALITY",
"package-access": "IN-GAME AVAILABILITY",
"access": "GENERAL AVAILABILITY",
"control": "CONTROLS",
"signals": "SIGNALLING",
"addons": "ADDITIONAL PROGRAMS",
"blockades": "BLOCK SIGNALLING",
"status": "ONLINE STATUS"
},
"endingStatus": "ENDS SOON",
"afkStatus": "AFK",
"noSpaceStatus": "NO SPACE",
"unavailableStatus": "UNAVAILABLE",
"title": "STATION FILTER",
"default": "DEFAULT",
"not-default": "OTHER",
"title": "STATION FILTERS",
"default": "IN-GAME",
"not-default": "ADDITIONAL",
"real": "REAL",
"fictional": "FICTIONAL",
"unavailable": "UNSUPPORTED",
@@ -141,12 +160,22 @@
"abandoned": "ABANDONED",
"SPK": "SPK",
"SPK-R": "SPK + MANUAL",
"SPK-M": "SPK + MECH.",
"SCS": "SCS",
"SCS-R": "SCS + MANUAL",
"SCS-M": "SCS + MECH.",
"SPE": "SPE",
"manual": "MANUAL",
"mechanical": "MECHANICAL",
"SUP": "SUP",
"SBL": "SBL",
"SUP": "SUP (RASP-UZK)",
"noSUP": "WITHOUT SUP",
"SBL": "AUTOMATIC (SBL)",
"PBL": "SEMIAUTOMATIC (PBL)",
"modern": "MODERN",
"semaphores": "SEMAPHORES",
"mixed": "MIXED",
@@ -167,7 +196,7 @@
"hour": "h",
"no-limit": "NO LIMIT",
"include-selected": "INCLUDE SELECTED",
"save": "SAVE FILTERS",
"save": "REMEMBER FILTERS",
"reset": "RESET FILTERS",
"close": "CLOSE FILTERS"
},
@@ -185,7 +214,7 @@
"no-stations": "No stations to show here!",
"scenery-search": "Search for scenery..."
},
"trains": {
"trains": {
"no-trains": "No trains to show here!",
"loading": "Loading train data...",
"offline": "Offline ride",
@@ -252,7 +281,7 @@
"minutes": "{minutes} mins",
"hours": "{hours}h {minutes} mins",
"stock-info": "STOCK INFO",
"stock-info": "EXTRA INFO",
"stock-length": "Length",
"stock-mass": "Mass",
"stock-max-speed": "Maximum registered speed",
@@ -305,16 +334,26 @@
"two-way-routes": "Two way routes",
"option-active-timetables": "Active timetables",
"option-timetables-history": "Scenery timetables history",
"option-dispatchers-history": "Scenery dispatchers history",
"option-timetables-history": "Timetables history",
"option-dispatchers-history": "Dispatchers history",
"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!",
"forum-topic": "Official {name} forum topic"
"forum-topic": "Official {name} forum topic",
"pragotron-link": "Timetable pallet board (beta)",
"tablice-link": "Timetable summary board (by Thundo)"
},
"availability": {
"title": "Availability",
+54 -13
View File
@@ -1,6 +1,7 @@
{
"general": {
"and": " oraz "
"and": " oraz ",
"refresh": "ODŚWIEŻ"
},
"app": {
"sceneries": "SCENERIE",
@@ -95,7 +96,8 @@
"search-dispatcher": "Nick dyżurnego",
"search-station": "Nazwa scenerii",
"search-author": "Nick autora rozkładu jazdy",
"search-date": "Data rozkładu jazdy (czas polski)",
"search-timetables-date": "Data rozkładu jazdy (czas polski)",
"search-dispatchers-date": "Data służby (czas polski)",
"sort-distance": "kilometraż",
"sort-total-stops": "stacje",
@@ -103,6 +105,7 @@
"sort-timetableId": "ID rozkładu",
"sort-timestampFrom": "data",
"sort-duration": "czas dyżuru",
"sort-id": "id rozkładu",
"sort-mass": "masa",
"sort-speed": "prędkość",
@@ -112,13 +115,16 @@
"sort-delay": "opóźnienie",
"sort-comments": "uwagi ekspl.",
"filter-comments": "UWAGI EKSPLOATACYJNE",
"filter-twr": "TWR",
"filter-skr": "PRZEKR. SKRAJNIA",
"filter-withComments": "UWAGI EKSPLOATACYJNE",
"filter-noComments": "BEZ UWAG",
"filter-twr": "WYS. RYZYKA",
"filter-skr": "SKRAJNIA",
"filter-common": "ZWYKŁE",
"filter-passenger": "PASAŻERSKIE",
"filter-freight": "TOWAROWE",
"filter-other": "INNE",
"filter-noTimetable": "BEZ RJ",
"filter-withTimetable": "ROZKŁAD JAZDY",
"filter-reset": "ZRESETUJ FILTRY",
"filter-clear": "WYŁĄCZ FILTRY",
@@ -129,6 +135,19 @@
"filter-active": "AKTYWNE"
},
"filters": {
"desc": " &bull; Kliknięcie: zaznaczenie / odznaczenie filtru <br /> &bull; Podwójne kliknięcie: odznaczenie reszty filtrów z <b class='text--primary'>grupy</b> <br /> &bull; <span style='color: coral'>RESET</span>: zresetowanie filtrów z <b class='text--primary'>grupy</b>",
"sections": {
"reality": "FIKCYJNOŚĆ SCENERII",
"package-access": "DOSTĘPNOŚĆ W PACZCE",
"access": "DOSTĘPNOŚĆ OGÓLNA",
"control": "TYP STEROWANIA",
"signals": "TYP SYGNALIZACJI",
"addons": "DODATKOWE PROGRAMY",
"blockades": "BLOKADY LINIOWE",
"status": "STATUS ONLINE"
},
"endingStatus": "KOŃCZY",
"afkStatus": "Z/W",
"noSpaceStatus": "BRAK MIEJSCA",
@@ -144,18 +163,29 @@
"abandoned": "WYCOFANA",
"SPK": "SPK",
"SPK-R": "SPK + RĘCZNE",
"SPK-M": "SPK + MECH.",
"SCS": "SCS",
"SCS-R": "SCS + RĘCZNE",
"SCS-M": "SCS + MECH.",
"SPE": "SPE",
"manual": "RĘCZNE",
"SUP": "SUP",
"SBL": "SBL",
"SUP": "SUP (RASP-UZK)",
"noSUP": "BEZ SUP",
"SBL": "SAMOCZYNNA",
"PBL": "PÓŁSAMOCZYNNA",
"mechanical": "MECHANICZNE",
"modern": "WSPÓŁCZESNA",
"semaphores": "KSZTAŁTOWA",
"mixed": "MIESZANA",
"historical": "HISTORYCZNA",
"free": "WOLNA",
"occupied": "ZAJĘTA",
"sliders": {
"min-lvl": "MIN. WYMAGANY POZIOM DYŻURNEGO",
"max-lvl": "MAKS. WYMAGANY POZIOM DYŻURNEGO",
@@ -164,13 +194,14 @@
"routes-2t-cat": "SZLAKI DWUTOROWE ZELEKTR. (MINIMUM)",
"routes-2t-other": "SZLAKI DWUTOROWE NIEZELEKTR. (MINIMUM)"
},
"authors-search": "Szukaj autora (uwzględnia inne filtry)",
"minimum-hours-title": "POKAŻ TYLKO SCENERIE DOSTĘPNE MINIMUM DO:",
"now": "TERAZ",
"hour": " godz.",
"no-limit": "BEZ LIMITU",
"include-selected": "POKAŻ ZAZNACZONE",
"save": "ZAPISZ FILTRY",
"save": "ZAPAMIĘTAJ FILTRY",
"reset": "RESETUJ FILTRY",
"close": "ZAMKNIJ FILTRY"
},
@@ -256,7 +287,7 @@
"timetable-fulfilled": "WYPEŁNIONY",
"timetable-abandoned": "PORZUCONY",
"stock-info": "INFORMACJE O SKŁADZIE",
"stock-info": "DODATKOWE INFORMACJE",
"stock-length": "Długość",
"stock-mass": "Masa",
"stock-max-speed": "Maks. zarejestrowana prędkość",
@@ -301,7 +332,7 @@
"no-scenery": "Ups! Ta sceneria nie istnieje!",
"return-btn": "Wróć na stronę główną",
"history-btn": "Przejdź do widoku historii dyżurnych ruchu",
"info-btn": "Wróc do widoku scenerii",
"info-btn": "Wróć do widoku scenerii",
"authors-title": "Autor scenerii | Autorzy scenerii",
"lines-title": "Rzeczywiste linie",
"project-title": "Projekt",
@@ -309,16 +340,26 @@
"two-way-routes": "Szlaki dwutorowe",
"option-active-timetables": "Aktywne rozkłady jazdy",
"option-timetables-history": "Historia rozkładów scenerii",
"option-dispatchers-history": "Historia dyżurów scenerii",
"option-timetables-history": "Historia rozkładów",
"option-dispatchers-history": "Historia dyżurów",
"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!",
"forum-topic": "Oficjalny wątek scenerii {name}"
"forum-topic": "Oficjalny wątek scenerii {name}",
"pragotron-link": "Paletowa tablica informacyjna (beta)",
"tablice-link": "Tablica informacyjna zbiorcza (autorstwa Thundo)"
},
"availability": {
"title": "Dostępność",
+11
View File
@@ -7,10 +7,12 @@ import plLang from './locales/pl.json';
import { createI18n } from 'vue-i18n';
import { createPinia } from 'pinia';
import { registerSW } from 'virtual:pwa-register';
const i18n = createI18n({
locale: 'pl',
legacy: false,
warnHtmlMessage: false,
fallbackLocale: 'pl',
messages: {
en: enLang,
@@ -19,6 +21,15 @@ const i18n = createI18n({
enableLegacy: false,
});
registerSW({
onRegistered(r) {
r &&
setInterval(() => {
r.update();
}, 60 * 60 * 1000);
},
});
const clickOutsideDirective: Directive = {
mounted(el, binding) {
el.clickOutsideEvent = (event: Event) => {
+13 -7
View File
@@ -4,11 +4,17 @@ export default defineComponent({
methods: {
calculateExpStyle(exp: number, isSupporter = false): string {
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};` : '';
return `background-color: ${bgColor}; color: ${fontColor}; ${boxShadow}`;
const boxShadow = isSupporter ? `box-shadow: 0 0 6px 2px ${bgColor};` : '';
return `background-color: ${bgColor}; color: ${fontColor}; ${boxShadow};`;
},
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 6px ' + textColor : ''};`;
},
statusClasses(occupiedTo: string) {
@@ -41,6 +47,6 @@ export default defineComponent({
}
return className;
}
}
})
},
},
});
+20 -8
View File
@@ -1,9 +1,21 @@
export const enum TrainFilterType {
comments = "comments",
twr = "twr",
skr = "skr",
passenger = "passenger",
freight = "freight",
other = "other",
noTimetable = "noTimetable"
export enum TrainFilterSection {
TRAIN_TYPE = 'TRAIN_TYPE',
TIMETABLE_TYPE = 'TIMETABLE_TYPE',
COMMENTS = 'COMMENTS',
TIMETABLE = 'TIMETABLE',
}
export const enum TrainFilterType {
noComments = 'noComments',
withComments = 'withComments',
twr = 'twr',
skr = 'skr',
common = 'common',
passenger = 'passenger',
freight = 'freight',
other = 'other',
noTimetable = 'noTimetable',
withTimetable = 'withTimetable',
}
+12 -6
View File
@@ -1,16 +1,22 @@
export default interface Filter {
[key: string]: (boolean | number | string),
[key: string]: boolean | number | string;
default: boolean;
notDefault: boolean;
real: boolean;
fictional: boolean;
"SPK": boolean;
"SCS": boolean;
"SPE": boolean;
"SUP": boolean;
SPK: boolean;
SCS: boolean;
SPE: boolean;
SUP: boolean;
noSUP: boolean;
ręczne: boolean;
'ręczne+SPK': boolean;
'ręczne+SCS': boolean;
mechaniczne: boolean;
"SBL": boolean;
'mechaniczne+SPK': boolean;
'mechaniczne+SCS': boolean;
SBL: boolean;
PBL: boolean;
współczesna: boolean;
kształtowa: boolean;
historyczna: boolean;
+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
View File
@@ -12,6 +12,7 @@ export default interface Train {
driverId: number;
trainNo: number;
driverName: string;
driverLevel: number;
currentStationName: string;
currentStationHash: string;
locoURL: 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;
}
}
@@ -1,11 +1,17 @@
export interface TimetableHistory {
id: number;
createdAt: string;
updatedAt: string;
timetableId: number;
trainNo: number;
trainCategoryCode: string;
driverId: number;
driverName: string;
driverLevel: number | null;
driverIsSupporter: boolean;
route: string;
twr: number;
skr: number;
@@ -29,6 +35,8 @@ export interface TimetableHistory {
authorName?: string;
authorId?: number;
stopsString?: string;
stockString?: string;
stockMass?: number;
stockLength?: number;
+43 -38
View File
@@ -1,4 +1,44 @@
export default interface TrainAPIData {
export interface TimetableStop {
stopName: string;
stopNameRAW: string;
stopType: string;
stopDistance: number;
pointId: number;
mainStop: boolean;
arrivalLine: string;
arrivalTimestamp: number;
arrivalRealTimestamp: number;
arrivalDelay: number;
departureLine: string;
departureTimestamp: number;
departureRealTimestamp: number;
departureDelay: number;
comments?: any;
beginsHere: boolean;
terminatesHere: boolean;
confirmed: boolean;
stopped: boolean;
stopTime: number;
}
export interface TrainTimetable {
timetableId: number;
category: string;
route: string;
stopList: TimetableStop[];
TWR: boolean;
SKR: boolean;
sceneries: string[];
}
export interface TrainAPIData {
trainNo: number;
mass: number;
@@ -13,6 +53,7 @@ export default interface TrainAPIData {
driverName: string;
driverId: number;
driverIsSupporter: boolean;
driverLevel?: number;
currentStationName: string;
currentStationHash: string;
@@ -23,41 +64,5 @@ export default interface TrainAPIData {
region: string;
isTimeout: boolean;
timetable?: {
timetableId: number;
category: string;
route: string;
stopList: {
stopName: string;
stopNameRAW: string;
stopType: string;
stopDistance: number;
pointId: number;
mainStop: boolean;
arrivalLine: string;
arrivalTimestamp: number;
arrivalRealTimestamp: number;
arrivalDelay: number;
departureLine: string;
departureTimestamp: number;
departureRealTimestamp: number;
departureDelay: number;
comments?: any;
beginsHere: boolean;
terminatesHere: boolean;
confirmed: boolean;
stopped: boolean;
stopTime: number;
}[];
TWR: boolean;
SKR: boolean;
sceneries: string[];
};
timetable?: TrainTimetable;
}
+99 -84
View File
@@ -1,115 +1,130 @@
import { TrainFilter } from "../../types/Trains/TrainOptionsTypes";
import { TrainFilterType } from "../enums/TrainFilterType";
import Train from "../interfaces/Train";
import TrainStop from "../interfaces/TrainStop";
import { TrainFilter } from '../../types/Trains/TrainOptionsTypes';
import { TrainFilterType } from '../enums/TrainFilterType';
import Train from '../interfaces/Train';
import TrainStop from '../interfaces/TrainStop';
function confirmedPercentage(stops: TrainStop[] | undefined) {
if (!stops) return -1;
if (!stops) return -1;
return Number(((stops.filter((stop) => stop.confirmed).length / stops.length) * 100).toFixed(0));
};
return Number(((stops.filter((stop) => stop.confirmed).length / stops.length) * 100).toFixed(0));
}
function currentDelay(stops: TrainStop[] | undefined) {
if (!stops) return -Infinity;
if (!stops) return -Infinity;
const delay =
stops.find((stop, i) => (i == 0 && !stop.confirmed) || (i > 0 && stops[i - 1].confirmed && !stop.confirmed))
?.departureDelay || 0;
const delay =
stops.find((stop, i) => (i == 0 && !stop.confirmed) || (i > 0 && stops[i - 1].confirmed && !stop.confirmed))
?.departureDelay || 0;
return delay;
};
return delay;
}
function filterTrainList(trainList: Train[], searchedTrain: string, searchedDriver: string, filters: TrainFilter[]) {
return trainList.filter(
(train) => {
const isFiltered = filters.every(f => {
if (f.isActive) return true;
if (!train.timetableData) return filters.find(filter => filter.id == TrainFilterType.noTimetable)!.isActive;
return trainList.filter((train) => {
const isFiltered = filters.every((f) => {
if (f.isActive) return true;
switch (f.id) {
case TrainFilterType.comments:
return !train.timetableData.followingStops.some(stop => stop.comments);
case TrainFilterType.twr:
return !train.timetableData.TWR;
case TrainFilterType.skr:
return !train.timetableData.SKR;
case TrainFilterType.passenger:
return !/^[AMRE]\D{2}$/.test(train.timetableData.category);
case TrainFilterType.freight:
return !train.timetableData.category.startsWith('T');
case TrainFilterType.other:
return !/^[PXZL]\D{2}$/.test(train.timetableData.category);
default:
return true;
}
})
switch (f.id) {
case TrainFilterType.noTimetable:
return train.timetableData;
return (searchedTrain.length > 0 ? train.trainNo.toString().startsWith(searchedTrain) : true) &&
(searchedDriver.length > 0 ? train.driverName.toLowerCase().startsWith(searchedDriver.toLowerCase()) : true) && isFiltered
}
case TrainFilterType.withTimetable:
return !train.timetableData;
case TrainFilterType.withComments:
return !train.timetableData?.followingStops.some((stop) => stop.comments);
case TrainFilterType.noComments:
return train.timetableData?.followingStops.some((stop) => stop.comments);
case TrainFilterType.twr:
return !train.timetableData?.TWR;
case TrainFilterType.skr:
return !train.timetableData?.SKR;
case TrainFilterType.common:
return train.timetableData?.SKR || train.timetableData?.TWR;
case TrainFilterType.passenger:
return !/^[AMRE]\D{2}$/.test(train.timetableData?.category || '');
case TrainFilterType.freight:
return !train.timetableData?.category.startsWith('T');
case TrainFilterType.other:
return !/^[PXZL]\D{2}$/.test(train.timetableData?.category || '');
default:
return true;
}
});
return (
(searchedTrain.length > 0 ? train.trainNo.toString().startsWith(searchedTrain) : true) &&
(searchedDriver.length > 0 ? train.driverName.toLowerCase().startsWith(searchedDriver.toLowerCase()) : true) &&
(!train.timetableData ? train.online : train.timetableData) &&
isFiltered
);
});
}
function sortTrainList(trainList: Train[], sorterActive: { id: string; dir: number }) {
return trainList.sort((a: Train, b: Train) => {
switch (sorterActive.id) {
case 'mass':
if (a.mass > b.mass) return sorterActive.dir;
return -sorterActive.dir;
return trainList.sort((a: Train, b: Train) => {
switch (sorterActive.id) {
case 'id':
if ((a.timetableData?.timetableId || -1) > (b.timetableData?.timetableId || -1)) return sorterActive.dir;
case 'distance':
if ((a.timetableData?.routeDistance || -1) > (b.timetableData?.routeDistance || -1)) return sorterActive.dir;
return -sorterActive.dir;
return -sorterActive.dir;
case 'mass':
if (a.mass > b.mass) return sorterActive.dir;
return -sorterActive.dir;
case 'progress':
if (confirmedPercentage(a.timetableData?.followingStops) > confirmedPercentage(b.timetableData?.followingStops))
return sorterActive.dir;
case 'distance':
if ((a.timetableData?.routeDistance || -1) > (b.timetableData?.routeDistance || -1)) return sorterActive.dir;
return -sorterActive.dir;
return -sorterActive.dir;
case 'delay':
if (currentDelay(a.timetableData?.followingStops) > currentDelay(b.timetableData?.followingStops))
return sorterActive.dir;
case 'progress':
if (confirmedPercentage(a.timetableData?.followingStops) > confirmedPercentage(b.timetableData?.followingStops))
return sorterActive.dir;
return -sorterActive.dir;
return -sorterActive.dir;
case 'speed':
if (a.speed > b.speed) return sorterActive.dir;
return -sorterActive.dir;
case 'delay':
if (currentDelay(a.timetableData?.followingStops) > currentDelay(b.timetableData?.followingStops))
return sorterActive.dir;
case 'timetable':
if (a.trainNo > b.trainNo) return sorterActive.dir;
return -sorterActive.dir;
return -sorterActive.dir;
case 'length':
if (a.length > b.length) return sorterActive.dir;
return -sorterActive.dir;
case 'speed':
if (a.speed > b.speed) return sorterActive.dir;
return -sorterActive.dir;
default:
break;
}
case 'timetable':
if (a.trainNo > b.trainNo) return sorterActive.dir;
return -sorterActive.dir;
return 0;
});
case 'length':
if (a.length > b.length) return sorterActive.dir;
return -sorterActive.dir;
default:
break;
}
return 0;
});
}
export function filteredTrainList(
trainList: Train[],
searchedTrain: string,
searchedDriver: string,
sorterActive: { id: string; dir: number },
filters: TrainFilter[]
trainList: Train[],
searchedTrain: string,
searchedDriver: string,
sorterActive: { id: string; dir: number },
filters: TrainFilter[]
) {
const filtered = filterTrainList(trainList, searchedTrain, searchedDriver, filters);
return [...sortTrainList(filtered, sorterActive)];
};
const filtered = filterTrainList(trainList, searchedTrain, searchedDriver, filters);
return [...sortTrainList(filtered, sorterActive)];
}
+3 -1
View File
@@ -1,5 +1,7 @@
export const URLs = {
stacjownikAPI:
import.meta.env.VITE_APP_API_DEV == 1 && !import.meta.env.PROD ? 'http://localhost:3000' : 'https://spythere.pl',
import.meta.env.VITE_APP_API_DEV == 1 && !import.meta.env.PROD
? 'http://localhost:3000'
: 'https://spythere.pl',
stacjownikAPIDev: 'localhost:3000',
};
+49
View File
@@ -0,0 +1,49 @@
import Filter from "../../scripts/interfaces/Filter";
export const filterInitStates: Filter = {
default: false,
notDefault: false,
real: false,
fictional: false,
SPK: false,
SCS: false,
SPE: false,
SUP: false,
noSUP: false,
ręczne: false,
'ręczne+SPK': false,
'ręczne+SCS': false,
mechaniczne: false,
'mechaniczne+SPK': false,
'mechaniczne+SCS': false,
współczesna: false,
kształtowa: false,
historyczna: false,
mieszana: false,
SBL: false,
PBL: false,
minLevel: 0,
maxLevel: 20,
minOneWayCatenary: 0,
minOneWay: 0,
minTwoWayCatenary: 0,
minTwoWay: 0,
'include-selected': false,
'no-1track': false,
'no-2track': false,
free: true,
occupied: false,
ending: false,
nonPublic: false,
unavailable: true,
abandoned: true,
afkStatus: false,
endingStatus: false,
noSpaceStatus: false,
unavailableStatus: false,
unsignedStatus: false,
authors: '',
onlineFromHours: 0,
};
+24 -231
View File
@@ -4,232 +4,8 @@ import Filter from '../scripts/interfaces/Filter';
import Station from '../scripts/interfaces/Station';
import StorageManager from '../scripts/managers/storageManager';
import { useStore } from './store';
const sortStations = (a: Station, b: Station, sorter: { index: number; dir: number }) => {
switch (sorter.index) {
case 0:
return sorter.dir == 1 ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name);
case 1:
if ((a.generalInfo?.reqLevel || 0) > (b.generalInfo?.reqLevel || 0)) return sorter.dir;
if ((a.generalInfo?.reqLevel || 0) < (b.generalInfo?.reqLevel || 0)) return -sorter.dir;
break;
case 2:
if ((a.onlineInfo?.statusTimestamp || 0) > (b.onlineInfo?.statusTimestamp || 0)) return sorter.dir;
if ((a.onlineInfo?.statusTimestamp || 0) < (b.onlineInfo?.statusTimestamp || 0)) return -sorter.dir;
break;
case 3:
if ((a.onlineInfo?.dispatcherName.toLowerCase() || '') > (b.onlineInfo?.dispatcherName.toLowerCase() || ''))
return sorter.dir;
if ((a.onlineInfo?.dispatcherName.toLowerCase() || '') < (b.onlineInfo?.dispatcherName.toLowerCase() || ''))
return -sorter.dir;
break;
case 4:
if ((a.onlineInfo?.dispatcherExp || 0) > (b.onlineInfo?.dispatcherExp || 0)) return sorter.dir;
if ((a.onlineInfo?.dispatcherExp || 0) < (b.onlineInfo?.dispatcherExp || 0)) return -sorter.dir;
break;
case 7:
if ((a.onlineInfo?.currentUsers || 0) > (b.onlineInfo?.currentUsers || 0)) return sorter.dir;
if ((a.onlineInfo?.currentUsers || 0) < (b.onlineInfo?.currentUsers || 0)) return -sorter.dir;
if ((a.onlineInfo?.maxUsers || 0) > (b.onlineInfo?.maxUsers || 0)) return sorter.dir;
if ((a.onlineInfo?.maxUsers || 0) < (b.onlineInfo?.maxUsers || 0)) return -sorter.dir;
break;
case 8:
if ((a.onlineInfo?.spawns.length || 0) > (b.onlineInfo?.spawns.length || 0)) return sorter.dir;
if ((a.onlineInfo?.spawns.length || 0) < (b.onlineInfo?.spawns.length || 0)) return -sorter.dir;
break;
case 9:
if ((a.onlineInfo?.scheduledTrains?.length || 0) > (b.onlineInfo?.scheduledTrains?.length || 0))
return sorter.dir;
if ((a.onlineInfo?.scheduledTrains?.length || 0) < (b.onlineInfo?.scheduledTrains?.length || 0))
return -sorter.dir;
default:
break;
}
return a.name.localeCompare(b.name);
};
const filterStations = (station: Station, filters: Filter, isOffline = false) => {
const returnMode = false;
if ((station.generalInfo?.availability == 'nonPublic' || !station.generalInfo) && filters['nonPublic'])
return returnMode;
if (station.onlineInfo?.statusID == 'ending' && filters['ending']) return returnMode;
if (
station.onlineInfo &&
station.onlineInfo.statusTimestamp > 0 &&
filters['onlineFromHours'] < 8 &&
station.onlineInfo.statusTimestamp <= Date.now() + filters['onlineFromHours'] * 3600000
)
return returnMode;
if (filters['onlineFromHours'] > 0 && station.onlineInfo && station.onlineInfo.statusTimestamp <= 0)
return returnMode;
if (filters['onlineFromHours'] == 8 && station.onlineInfo?.statusID != 'no-limit') return returnMode;
if (station.onlineInfo?.statusID == 'ending' && filters['endingStatus']) return returnMode;
if (
(station.onlineInfo?.statusID == 'not-signed' || station.onlineInfo?.statusID == 'unavailable') &&
filters['unavailableStatus']
)
return returnMode;
if (station.onlineInfo?.statusID == 'brb' && filters['afkStatus']) return returnMode;
if (station.onlineInfo?.statusID == 'no-space' && filters['noSpaceStatus']) return returnMode;
if (station.onlineInfo && filters['occupied']) return returnMode;
if (!station.onlineInfo && filters['free']) return returnMode;
if (station.generalInfo?.availability == 'unavailable' && filters['unavailable'] && !station.onlineInfo)
return returnMode;
if (station.generalInfo) {
const routes = station.generalInfo.routes;
const availability = station.generalInfo.availability;
if (filters['abandoned'] && availability == 'abandoned' && !station.onlineInfo) return returnMode;
if (availability == 'default' && filters['default']) return returnMode;
if (
availability != 'default' &&
filters['notDefault'] &&
!(availability == 'abandoned' || availability == 'unavailable')
)
return returnMode;
if (filters['real'] && station.generalInfo.lines != '') return returnMode;
if (
filters['fictional'] &&
station.generalInfo.lines == '' &&
availability != 'abandoned' &&
availability != 'unavailable'
)
return returnMode;
if (
station.generalInfo.reqLevel +
(availability == 'nonPublic' || availability == 'unavailable' || availability == 'abandoned' ? 1 : 0) <
filters['minLevel']
)
return returnMode;
if (
station.generalInfo.reqLevel +
(availability == 'nonPublic' || availability == 'unavailable' || availability == 'abandoned' ? 1 : 0) >
filters['maxLevel']
)
return returnMode;
if (
filters['no-1track'] &&
(routes.oneWayCatenaryRouteNames.length != 0 || routes.oneWayNoCatenaryRouteNames.length != 0)
)
return returnMode;
if (
filters['no-2track'] &&
(routes.twoWayCatenaryRouteNames.length != 0 || routes.twoWayNoCatenaryRouteNames.length != 0)
)
return returnMode;
if (routes.oneWayCatenaryRouteNames.length < filters['minOneWayCatenary']) return returnMode;
if (routes.oneWayNoCatenaryRouteNames.length < filters['minOneWay']) return returnMode;
if (routes.twoWayCatenaryRouteNames.length < filters['minTwoWayCatenary']) return returnMode;
if (routes.twoWayNoCatenaryRouteNames.length < filters['minTwoWay']) return returnMode;
if (filters[station.generalInfo.controlType]) return returnMode;
if (filters[station.generalInfo.signalType]) return returnMode;
if (
filters['SPK'] &&
(station.generalInfo.controlType === 'SPK' || station.generalInfo.controlType.includes('+SPK'))
)
return returnMode;
if (
filters['SCS'] &&
(station.generalInfo.controlType === 'SCS' || station.generalInfo.controlType.includes('+SCS'))
)
return returnMode;
if (
filters['SPE'] &&
(station.generalInfo.controlType === 'SPE' || station.generalInfo.controlType.includes('+SPE'))
)
return returnMode;
if (filters['SUP'] && station.generalInfo.SUP) return returnMode;
if (
filters['SCS'] &&
filters['SPK'] &&
(station.generalInfo.controlType.includes('SPK') || station.generalInfo.controlType.includes('SCS'))
)
return returnMode;
if (filters['mechaniczne'] && station.generalInfo.controlType.includes('mechaniczne')) return returnMode;
if (filters['ręczne'] && station.generalInfo.controlType.includes('ręczne')) return returnMode;
if (filters['SBL'] && routes.sblRouteNames.length > 0) return returnMode;
if (
filters['authors'].length > 3 &&
!station.generalInfo.authors?.map((a) => a.toLocaleLowerCase()).includes(filters['authors'].toLocaleLowerCase())
)
return returnMode;
}
return true;
};
const filterInitStates: Filter = {
default: false,
notDefault: false,
real: false,
fictional: false,
SPK: false,
SCS: false,
SPE: false,
SUP: false,
ręczne: false,
mechaniczne: false,
współczesna: false,
kształtowa: false,
historyczna: false,
mieszana: false,
SBL: false,
minLevel: 0,
maxLevel: 20,
minOneWayCatenary: 0,
minOneWay: 0,
minTwoWayCatenary: 0,
minTwoWay: 0,
'include-selected': false,
'no-1track': false,
'no-2track': false,
free: true,
occupied: false,
ending: false,
nonPublic: false,
unavailable: true,
abandoned: true,
afkStatus: false,
endingStatus: false,
noSpaceStatus: false,
unavailableStatus: false,
unsignedStatus: false,
authors: '',
onlineFromHours: 0,
};
import { filterInitStates } from './constants/initFilterStates';
import { filterStations, sortStations } from './utils/filterUtils';
export const useStationFiltersStore = defineStore('stationFiltersStore', {
state() {
@@ -238,9 +14,16 @@ export const useStationFiltersStore = defineStore('stationFiltersStore', {
filters: { ...filterInitStates },
sorterActive: { index: 0, dir: 1 },
store: useStore(),
lastClickedFilterId: '',
};
},
getters: {
areFiltersAtDefault(state) {
return Object.keys(state.filters).every((f) => state.filters[f] === filterInitStates[f]);
},
},
actions: {
getFilteredStationList(stationList: Station[], region: string): Station[] {
return stationList
@@ -251,7 +34,7 @@ export const useStationFiltersStore = defineStore('stationFiltersStore', {
return station;
})
.filter((station) => filterStations(station, this.filters, this.store.isOffline))
.filter((station) => filterStations(station, this.filters))
.sort((a, b) => sortStations(a, b, this.sorterActive));
},
@@ -259,10 +42,10 @@ export const useStationFiltersStore = defineStore('stationFiltersStore', {
if (!StorageManager.isRegistered('options_saved')) return;
this.inputs.options.forEach((option) => {
if (!StorageManager.isRegistered(option.id)) return;
const savedValue = StorageManager.getBooleanValue(option.id);
if (!StorageManager.isRegistered(option.name)) return;
const savedValue = StorageManager.getBooleanValue(option.name);
this.filters[option.id] = savedValue;
this.filters[option.name] = savedValue;
option.value = !savedValue;
});
@@ -295,6 +78,17 @@ export const useStationFiltersStore = defineStore('stationFiltersStore', {
});
},
resetSectionOptions(section: string) {
this.inputs.options.forEach((option) => {
if (option.section != section) return;
option.value = option.defaultValue;
this.filters[option.id] = !option.defaultValue;
StorageManager.setBooleanValue(option.name, !option.defaultValue);
});
},
changeSorter(index: number) {
if (index > 4 && index < 7) return;
@@ -305,4 +99,3 @@ export const useStationFiltersStore = defineStore('stationFiltersStore', {
},
},
});
+66 -58
View File
@@ -24,6 +24,7 @@ export const useStore = defineStore('store', {
stationList: [],
trainList: [],
routesList: [],
sceneryData: [],
lastDispatcherStatuses: [],
@@ -101,6 +102,7 @@ export const useStore = defineStore('store', {
isTimeout: train.isTimeout,
isSupporter: train.driverIsSupporter,
driverLevel: train.driverLevel,
timetableData: timetable
? {
@@ -114,8 +116,8 @@ export const useStore = defineStore('store', {
sceneries: timetable.sceneries,
}
: undefined,
};
}) as Train[];
} as Train;
});
},
getDispatcherStatus(onlineStationData: StationAPIData) {
@@ -293,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) => {
@@ -373,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
@@ -1,7 +1,7 @@
import { Socket } from 'socket.io-client';
import { DataStatus } from '../scripts/enums/DataStatus';
import StationAPIData from '../scripts/interfaces/api/StationAPIData';
import TrainAPIData from '../scripts/interfaces/api/TrainAPIData';
import { TrainAPIData } from '../scripts/interfaces/api/TrainAPIData';
import Station from '../scripts/interfaces/Station';
import Train from '../scripts/interfaces/Train';
import { DispatcherStatsAPIData } from '../scripts/interfaces/api/DispatcherStatsAPIData';
@@ -60,6 +60,7 @@ export interface StationJSONData {
url: string;
lines: string;
project: string;
projectUrl: string;
reqLevel: number;
@@ -69,6 +70,7 @@ export interface StationJSONData {
SUP: boolean;
routes: string;
checkpoints: string | null;
authors?: string;
+141
View File
@@ -0,0 +1,141 @@
import Filter from '../../scripts/interfaces/Filter';
import Station from '../../scripts/interfaces/Station';
export const sortStations = (a: Station, b: Station, sorter: { index: number; dir: number }) => {
switch (sorter.index) {
case 0:
return sorter.dir == 1 ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name);
case 1:
if ((a.generalInfo?.reqLevel || 0) > (b.generalInfo?.reqLevel || 0)) return sorter.dir;
if ((a.generalInfo?.reqLevel || 0) < (b.generalInfo?.reqLevel || 0)) return -sorter.dir;
break;
case 2:
if ((a.onlineInfo?.statusTimestamp || 0) > (b.onlineInfo?.statusTimestamp || 0)) return sorter.dir;
if ((a.onlineInfo?.statusTimestamp || 0) < (b.onlineInfo?.statusTimestamp || 0)) return -sorter.dir;
break;
case 3:
if ((a.onlineInfo?.dispatcherName.toLowerCase() || '') > (b.onlineInfo?.dispatcherName.toLowerCase() || ''))
return sorter.dir;
if ((a.onlineInfo?.dispatcherName.toLowerCase() || '') < (b.onlineInfo?.dispatcherName.toLowerCase() || ''))
return -sorter.dir;
break;
case 4:
if ((a.onlineInfo?.dispatcherExp || 0) > (b.onlineInfo?.dispatcherExp || 0)) return sorter.dir;
if ((a.onlineInfo?.dispatcherExp || 0) < (b.onlineInfo?.dispatcherExp || 0)) return -sorter.dir;
break;
case 7:
if ((a.onlineInfo?.currentUsers || 0) > (b.onlineInfo?.currentUsers || 0)) return sorter.dir;
if ((a.onlineInfo?.currentUsers || 0) < (b.onlineInfo?.currentUsers || 0)) return -sorter.dir;
if ((a.onlineInfo?.maxUsers || 0) > (b.onlineInfo?.maxUsers || 0)) return sorter.dir;
if ((a.onlineInfo?.maxUsers || 0) < (b.onlineInfo?.maxUsers || 0)) return -sorter.dir;
break;
case 8:
if ((a.onlineInfo?.spawns.length || 0) > (b.onlineInfo?.spawns.length || 0)) return sorter.dir;
if ((a.onlineInfo?.spawns.length || 0) < (b.onlineInfo?.spawns.length || 0)) return -sorter.dir;
break;
case 9:
if ((a.onlineInfo?.scheduledTrains?.length || 0) > (b.onlineInfo?.scheduledTrains?.length || 0))
return sorter.dir;
if ((a.onlineInfo?.scheduledTrains?.length || 0) < (b.onlineInfo?.scheduledTrains?.length || 0))
return -sorter.dir;
default:
break;
}
return a.name.localeCompare(b.name);
};
export const filterStations = (station: Station, filters: Filter) => {
if (!station.onlineInfo && filters['free']) return false;
if (station.onlineInfo) {
const { statusID, statusTimestamp } = station.onlineInfo;
const isEnding = statusID == 'ending' && filters['endingStatus'];
const isNotSigned = (statusID == 'not-signed' || statusID == 'unavailable') && filters['unavailableStatus'];
const isAFK = statusID == 'brb' && filters['afkStatus'];
const isNoSpace = statusID == 'no-space' && filters['noSpaceStatus'];
const isOccupied = station.onlineInfo && filters['occupied'];
const isOnlineInBounds =
(filters['onlineFromHours'] < 8 &&
statusTimestamp > 0 &&
statusTimestamp <= Date.now() + filters['onlineFromHours'] * 3600000) ||
(filters['onlineFromHours'] > 0 && statusTimestamp <= 0) ||
(filters['onlineFromHours'] == 8 && statusID != 'no-limit');
if (isEnding || isOnlineInBounds || isNotSigned || isAFK || isNoSpace || isOccupied) return false;
}
if ((station.generalInfo?.availability == 'nonPublic' || !station.generalInfo) && filters['nonPublic']) return false;
if (station.generalInfo) {
const { routes, availability, controlType, lines, reqLevel, signalType, SUP, authors } = station.generalInfo;
if (availability == 'unavailable' && filters['unavailable'] && !station.onlineInfo) return false;
if (availability == 'abandoned' && filters['abandoned'] && !station.onlineInfo) return false;
if (availability == 'default' && filters['default']) return false;
if (
availability != 'default' &&
filters['notDefault'] &&
!(availability == 'abandoned' || availability == 'unavailable')
)
return false;
if (filters['real'] && lines) return false;
if (filters['fictional'] && !lines) return false;
const otherAvailability =
availability == 'nonPublic' || availability == 'unavailable' || availability == 'abandoned';
if (reqLevel + (otherAvailability ? 1 : 0) < filters['minLevel']) return false;
if (reqLevel + (otherAvailability ? 1 : 0) > filters['maxLevel']) return false;
if (
filters['no-1track'] &&
(routes.oneWayCatenaryRouteNames.length != 0 || routes.oneWayNoCatenaryRouteNames.length != 0)
)
return false;
if (
filters['no-2track'] &&
(routes.twoWayCatenaryRouteNames.length != 0 || routes.twoWayNoCatenaryRouteNames.length != 0)
)
return false;
if (routes.oneWayCatenaryRouteNames.length < filters['minOneWayCatenary']) return false;
if (routes.oneWayNoCatenaryRouteNames.length < filters['minOneWay']) return false;
if (routes.twoWayCatenaryRouteNames.length < filters['minTwoWayCatenary']) return false;
if (routes.twoWayNoCatenaryRouteNames.length < filters['minTwoWay']) return false;
if (filters[controlType]) return false;
if (filters[signalType]) return false;
if (filters['SUP'] && SUP) return false;
if (filters['noSUP'] && !SUP) return false;
if (filters['SBL'] && routes.sblRouteNames.length > 0) return false;
if (filters['PBL'] && routes.sblRouteNames.length == 0) return false;
if (
filters['authors'].length > 3 &&
!authors?.map((a) => a.toLocaleLowerCase()).includes(filters['authors'].toLocaleLowerCase())
)
return false;
}
return true;
};
+5 -17
View File
@@ -1,21 +1,5 @@
@import 'responsive.scss';
// Animations
.warning {
&-enter-from,
&-leave-to {
opacity: 0;
}
&-enter-active {
transition: all 150ms 100ms ease-out;
}
&-leave-active {
transition: all 150ms 100ms ease-out;
}
}
@import 'animations.scss';
//Styles
.list_wrapper {
@@ -26,6 +10,10 @@
padding-right: 0.2em;
}
.journal-list {
position: relative;
}
.journal_wrapper {
max-width: 1350px;
width: 100%;
+31
View File
@@ -0,0 +1,31 @@
.list-anim-move,
.list-anim-enter-active,
.list-anim-leave-active {
transition: all 250ms ease;
}
.list-anim-enter-from,
.list-anim-leave-to {
opacity: 0;
transform: translateY(30px);
}
.list-anim-leave-active {
position: absolute;
width: 100%;
}
.status-anim {
&-enter-from,
&-leave-to {
opacity: 0;
}
&-enter-active {
transition: all 100ms ease-out;
}
&-leave-active {
transition: all 100ms ease-out;
}
}
+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;
}
}
+13 -8
View File
@@ -21,17 +21,16 @@
transform: translate(-50%, -50%);
overflow-x: hidden;
background: #202020da;
overflow: hidden;
background: #202020e8;
box-shadow: 0 0 15px 5px #303030;
box-shadow: 0 0 15px 0 black;
border: 1px solid #202020e8;
width: 600px;
width: 95%;
max-width: 700px;
@include smallScreen {
width: 100%;
height: 80vh;
}
max-height: 95vh;
&-exit {
position: absolute;
@@ -46,3 +45,9 @@
cursor: pointer;
}
}
@include smallScreen {
.card {
max-height: 85vh;
}
}
+10 -3
View File
@@ -6,6 +6,11 @@
margin-bottom: 0.5em;
}
.actions-bar {
display: flex;
gap: 0.5em;
}
.filter-button .active-indicator {
width: 7px;
height: 7px;
@@ -62,7 +67,7 @@ h1.option-title {
box-shadow: 0 5px 10px 2px #0f0f0f;
width: 97%;
max-width: 500px;
max-width: 550px;
padding: 1em;
z-index: 100;
@@ -72,6 +77,7 @@ h1.option-title {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0.5em;
padding: 0.25em 0.25em 0 0;
}
@@ -79,17 +85,18 @@ h1.option-title {
.options_filters {
display: flex;
flex-wrap: wrap;
gap: 0.5em;
margin: 0.5em 0 0 0;
}
.sort-option,
.filter-option {
margin: 0.25em 0.25em 0.25em 0;
padding: 0.25em 0.5em;
}
.sort-option[data-selected='true'] {
color: $accentCol;
font-weight: bold;
}
.filter-option {
+8 -23
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;
@@ -213,10 +202,6 @@ button {
pointer-events: none;
opacity: 0.85;
}
&[data-inactive='true'] {
opacity: 0.55;
}
}
button.btn--filled {
+16 -11
View File
@@ -1,18 +1,23 @@
@mixin smallScreen() {
@media only screen and (max-width: 700px) {
@content;
}
@media only screen and (max-width: 700px) {
@content;
}
}
@mixin midScreen() {
@media only screen and (max-width: 1150px) {
@content;
}
@media only screen and (max-width: 1150px) {
@content;
}
}
@mixin screenLandscape() {
@media only screen and (orientation: landscape) and (max-device-height: 450px) {
@content;
}
}
@mixin bigScreen() {
@media only screen and (min-width: 2000px) {
@content;
}
}
@media only screen and (min-width: 2000px) {
@content;
}
}
+1 -1
View File
@@ -13,6 +13,6 @@ export interface JournalTimetableFilter {
}
export interface JournalTimetableSorter {
id: 'timetableId' | 'beginDate' | 'distance' | 'total-stops';
id: 'timetableId' | 'beginDate' | 'distance' | 'total-stops';
dir: -1 | 1;
}
+3 -2
View File
@@ -1,6 +1,7 @@
import { TrainFilterType } from "../../scripts/enums/TrainFilterType";
import { TrainFilterSection, TrainFilterType } from '../../scripts/enums/TrainFilterType';
export interface TrainFilter {
id: TrainFilterType;
section: TrainFilterSection;
isActive: boolean;
}
}
+27 -25
View File
@@ -6,41 +6,43 @@
<JournalOptions
@on-search-confirm="fetchHistoryData"
@on-options-reset="resetOptions"
@on-refresh-data="fetchHistoryData"
:sorter-option-ids="['timestampFrom', 'duration']"
:data-status="dataStatus"
:current-options-active="currentOptionsActive"
optionsType="dispatchers"
/>
<div class="list_wrapper" @scroll="handleScroll">
<!-- <transition name="warning" mode="out-in"> -->
<!-- <div :key="dataStatus"> -->
<div class="journal_warning" v-if="store.isOffline">
{{ $t('app.offline') }}
</div>
<transition name="status-anim" mode="out-in">
<div :key="dataStatus">
<div class="journal_warning" v-if="store.isOffline">
{{ $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') }}
</div>
<div v-else-if="dataStatus == DataStatus.Error" class="journal_warning error">
{{ $t('app.error') }}
</div>
<div class="journal_warning" v-else-if="historyList.length == 0">
{{ $t('app.no-result') }}
</div>
<div class="journal_warning" v-else-if="historyList.length == 0">
{{ $t('app.no-result') }}
</div>
<div v-else>
<JournalDispatchersList :dispatcherHistory="computedHistoryList" />
<div v-else>
<JournalDispatchersList :dispatcherHistory="computedHistoryList" />
<button
class="btn btn--option btn--load-data"
v-if="!scrollNoMoreData && scrollDataLoaded && computedHistoryList.length > 15"
@click="addHistoryData"
>
{{ $t('journal.load-data') }}
</button>
</div>
<!-- </div>
</transition> -->
<button
class="btn btn--option btn--load-data"
v-if="!scrollNoMoreData && scrollDataLoaded && computedHistoryList.length > 15"
@click="addHistoryData"
>
{{ $t('journal.load-data') }}
</button>
</div>
</div>
</transition>
<div class="journal_warning" v-if="scrollNoMoreData">
{{ $t('journal.no-further-data') }}
@@ -110,7 +112,7 @@ export default defineComponent({
statsCardOpen: false,
currentOptionsActive: false,
dataStatus: DataStatus.Initialized,
dataStatus: DataStatus.Loading,
DataStatus,
historyList: [] as DispatcherHistory[],
+34 -30
View File
@@ -6,44 +6,46 @@
<JournalOptions
@on-search-confirm="fetchHistoryData"
@on-options-reset="resetOptions"
@on-refresh-data="fetchHistoryData"
:sorter-option-ids="['timetableId', 'beginDate', 'distance', 'total-stops']"
:filters="journalTimetableFilters"
:currentOptionsActive="currentOptionsActive"
:data-status="dataStatus"
optionsType="timetables"
/>
<JournalStats />
<div class="list_wrapper" @scroll="handleScroll">
<!-- <transition name="warning" mode="out-in"> -->
<!-- <div :key="dataStatus"> -->
<div class="journal_warning" v-if="store.isOffline">
{{ $t('app.offline') }}
</div>
<transition name="status-anim" mode="out-in">
<div :key="dataStatus">
<div class="journal_warning" v-if="store.isOffline">
{{ $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') }}
</div>
<div v-else-if="dataStatus == DataStatus.Error" class="journal_warning error">
{{ $t('app.error') }}
</div>
<div v-else-if="timetableHistory.length == 0" class="journal_warning">
{{ $t('app.no-result') }}
</div>
<div v-else-if="timetableHistory.length == 0" class="journal_warning">
{{ $t('app.no-result') }}
</div>
<div v-else>
<JournalTimetablesList :timetableHistory="timetableHistory" />
<div v-else>
<JournalTimetablesList :timetableHistory="timetableHistory" />
<button
class="btn btn--option btn--load-data"
v-if="!scrollNoMoreData && scrollDataLoaded && timetableHistory.length >= 15"
@click="addHistoryData"
>
{{ $t('journal.load-data') }}
</button>
</div>
<!-- </div> -->
<!-- </transition> -->
<button
class="btn btn--option btn--load-data"
v-if="!scrollNoMoreData && scrollDataLoaded && timetableHistory.length >= 15"
@click="addHistoryData"
>
{{ $t('journal.load-data') }}
</button>
</div>
</div>
</transition>
<div class="journal_warning" v-if="scrollNoMoreData">{{ $t('journal.no-further-data') }}</div>
<div class="journal_warning" v-else-if="!scrollDataLoaded">{{ $t('journal.loading-further-data') }}</div>
@@ -53,12 +55,12 @@
</template>
<script lang="ts">
import { defineComponent, provide, reactive, Ref, ref, watch } from 'vue';
import { defineComponent, provide, reactive, Ref, ref } from 'vue';
import axios from 'axios';
import DriverStats from '../components/JournalView/JournalDriverStats.vue';
import Loading from '../components/Global/Loading.vue';
import { JournalTimetableFilter, JournalTimetableSorter } from '../types/Journal/JournalTimetablesTypes';
import { JournalTimetableSorter } from '../types/Journal/JournalTimetablesTypes';
import dateMixin from '../mixins/dateMixin';
import routerMixin from '../mixins/routerMixin';
import { DataStatus } from '../scripts/enums/DataStatus';
@@ -104,7 +106,7 @@ export default defineComponent({
timetableHistory: [] as TimetableHistory[],
journalTimetableFilters,
dataStatus: DataStatus.Initialized,
dataStatus: DataStatus.Loading,
dataErrorMessage: '',
DataStatus,
@@ -145,9 +147,8 @@ export default defineComponent({
},
watch: {
currentQueryArray(q: string[]) {
this.currentOptionsActive =
q.length > 2 || q.some((qv) => qv.startsWith('sortBy=') && qv.split('=')[1] != 'timetableId');
currentQueryArray(q: string[]) {
this.currentOptionsActive = q.length >= 2 || q.some((qv) => qv.startsWith('sortBy=') && qv.split('=')[1]);
},
},
@@ -162,6 +163,7 @@ export default defineComponent({
this.fetchHistoryData();
},
methods: {
handleScroll(e: Event) {
const listElement = e.target as HTMLElement;
@@ -214,6 +216,8 @@ export default defineComponent({
},
async fetchHistoryData() {
// if(this.dataStatus == DataStatus.Loading) return;
const queries: string[] = [];
const driverName = this.searchersValues['search-driver'].trim();
+1 -1
View File
@@ -2,7 +2,7 @@
<section class="trains-view">
<div class="trains_wrapper">
<TrainOptions
:sorter-option-ids="['distance', 'progress', 'delay', 'mass', 'speed', 'length']"
:sorter-option-ids="['distance', 'id', 'progress', 'delay', 'mass', 'speed', 'length']"
:current-options-active="currentOptionsActive"
/>
+1
View File
@@ -52,3 +52,4 @@ export default defineConfig({
});
+2386 -2299
View File
File diff suppressed because it is too large Load Diff