Compare commits

...

11 Commits

Author SHA1 Message Date
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
28 changed files with 356 additions and 291 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "stacjownik",
"version": "1.11.1",
"version": "1.11.2",
"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.5rem + 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() {
+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

@@ -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>
@@ -44,8 +47,7 @@
</span>
</div>
</li>
<!-- </transition-group> -->
</ul>
</transition-group>
</template>
<script lang="ts">
@@ -95,6 +97,7 @@ export default defineComponent({
</script>
<style lang="scss" scoped>
@import '../../styles/animations.scss';
@import '../../styles/responsive.scss';
@import '../../styles/JournalSection.scss';
@@ -117,9 +120,12 @@ li.sticky {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
text-align: left;
gap: 0.25em;
line-height: 1.7em;
padding: 0.75em;
&.online {
@@ -155,23 +161,12 @@ li.sticky {
.dispatcher-level {
display: inline-block;
text-align: center;
line-height: 150%;
width: 25px;
height: 25px;
line-height: 1.45em;
width: 1.45em;
height: 1.45em;
margin-right: 0.5em;
margin-right: 0.45em;
border-radius: 0.25em;
}
@include smallScreen() {
.journal_item {
flex-direction: column;
span {
margin-top: 0.25em;
text-align: center;
}
}
}
</style>
+17 -6
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>
@@ -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: {
@@ -206,6 +213,10 @@ export default defineComponent({
}
},
refreshData() {
this.$emit('onRefreshData');
},
startSearchTimeout(type: 'driver' | 'dispatcher', value: string) {
if (this[`${type}Suggestions`].includes(value)) return;
@@ -1,9 +1,9 @@
<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"
>
<div class="journal_item-info">
<div class="info-top">
@@ -17,8 +17,13 @@
<b>{{ timetable.trainNo }}</b>
| <span>{{ timetable.driverName }}</span> |
<span class="text--grayed">#{{ timetable.id }}</span>
<span v-if="timetable.driverLevel !== null">
|
<b :style="calculateTextExpStyle(timetable.driverLevel, timetable.driverIsSupporter)">
{{ timetable.driverLevel < 2 ? 'L' : `${timetable.driverLevel} lvl` }}
</b>
</span>
</span>
<span>
<b class="info-date">{{ localeDay(timetable.beginDate, $i18n.locale) }}</b>
<b
@@ -39,26 +44,20 @@
</b>
</span>
</div>
<div class="info-route">
<b>{{ timetable.route.replace('|', ' - ') }}</b>
</div>
<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>
{{ 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>
</div>
<!-- Status RJ -->
<div style="margin: 0.5em 0">
<span>
@@ -80,7 +79,6 @@
</b>
</span>
</div>
<!-- Nick dyżurnego -->
<div v-if="timetable.authorName">
<b class="text--grayed">{{ $t('journal.dispatcher-name') }}&nbsp;</b>
@@ -88,7 +86,6 @@
<b>{{ timetable.authorName }}</b>
</router-link>
</div>
<button
v-if="timetable.stockString"
class="btn--option btn--show"
@@ -97,27 +94,22 @@
{{ $t('journal.stock-info') }}
<img :src="getIcon(`arrow-${item.showStock.value ? 'asc' : 'desc'}`)" alt="Arrow" />
</button>
<div class="info-extended" v-if="timetable.stockString && item.showStock.value">
<hr />
<div>
<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 +117,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,6 +131,7 @@ 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';
export default defineComponent({
@@ -150,7 +142,7 @@ export default defineComponent({
},
},
mixins: [dateMixin, imageMixin, modalTrainMixin],
mixins: [dateMixin, imageMixin, modalTrainMixin, styleMixin],
computed: {
computedTimetableHistory() {
@@ -211,11 +203,13 @@ 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';
hr {
margin: 0.25em 0;
}
@@ -41,7 +41,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"
@@ -272,22 +272,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%;
@@ -486,21 +471,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;
+29 -27
View File
@@ -2,22 +2,24 @@
<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">{{ train.timetableData.category }}&nbsp;</span>
<span class="train-number">{{ train.trainNo }}</span>
</strong>
<span>|</span>
<span>{{ train.driverName }}</span>
<span>|</span>
<b :style="calculateTextExpStyle(train.driverLevel, train.isSupporter)">
{{ train.driverLevel < 2 ? 'L' : `${train.driverLevel} lvl` }}
</b>
</div>
<div class="timetable_route" v-if="train.timetableData">
@@ -40,9 +42,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 +94,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,7 +111,7 @@ export default defineComponent({
},
},
mixins: [trainInfoMixin, imageMixin],
mixins: [trainInfoMixin, imageMixin, styleMixin],
});
</script>
@@ -149,7 +150,6 @@ export default defineComponent({
}
.timetable-id {
margin-right: 0.3em;
color: #d2d2d2;
}
@@ -159,11 +159,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 +170,19 @@ export default defineComponent({
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0.25em;
}
.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 +196,7 @@ export default defineComponent({
}
&.offline {
background-color: #b83b2d;
background-color: #9c362b;
}
}
@@ -216,6 +215,9 @@ export default defineComponent({
}
.timetable_warnings {
display: flex;
gap: 0.2em;
color: black;
}
+5 -5
View File
@@ -16,8 +16,7 @@
<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 +26,7 @@
>
<TrainInfo :train="train" />
</li>
</ul>
</transition-group>
</div>
</transition>
</div>
@@ -98,6 +97,7 @@ export default defineComponent({
<style lang="scss" scoped>
@import '../../styles/responsive.scss';
@import '../../styles/animations.scss';
.anim {
&-enter-from,
@@ -158,8 +158,8 @@ img.train-image {
.train {
&-list {
overflow: auto;
position: relative;
@include smallScreen() {
width: 100%;
}
@@ -37,6 +37,10 @@ export const sorterOptions = [
id: 'distance',
value: 'kilometraż',
},
{
id: 'id',
value: 'id rozkładu',
},
{
id: 'progress',
value: 'przebyta trasa',
+3 -1
View File
@@ -1,6 +1,7 @@
{
"general": {
"and": " and "
"and": " and ",
"refresh": "REFRESH"
},
"app": {
"sceneries": "SCENERIES",
@@ -102,6 +103,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",
+4 -2
View File
@@ -1,6 +1,7 @@
{
"general": {
"and": " oraz "
"and": " oraz ",
"refresh": "ODŚWIEŻ"
},
"app": {
"sceneries": "SCENERIE",
@@ -103,7 +104,8 @@
"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ść",
"sort-length": "długość",
+10
View File
@@ -7,6 +7,7 @@ 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',
@@ -19,6 +20,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) => {
+12 -6
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}`;
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 10px ' + textColor : ''};`;
},
statusClasses(occupiedTo: string) {
@@ -41,6 +47,6 @@ export default defineComponent({
}
return className;
}
}
})
},
},
});
+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;
@@ -4,8 +4,12 @@ export interface TimetableHistory {
timetableId: number;
trainNo: number;
trainCategoryCode: string;
driverId: number;
driverName: string;
driverLevel: number | null;
driverIsSupporter: boolean;
route: string;
twr: number;
skr: number;
@@ -13,6 +13,7 @@ export default interface TrainAPIData {
driverName: string;
driverId: number;
driverIsSupporter: boolean;
driverLevel?: number;
currentStationName: string;
currentStationHash: string;
+88 -83
View File
@@ -1,115 +1,120 @@
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;
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;
if (!train.timetableData) return filters.find((filter) => filter.id == TrainFilterType.noTimetable)!.isActive;
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.comments:
return !train.timetableData.followingStops.some((stop) => stop.comments);
return (searchedTrain.length > 0 ? train.trainNo.toString().startsWith(searchedTrain) : true) &&
(searchedDriver.length > 0 ? train.driverName.toLowerCase().startsWith(searchedDriver.toLowerCase()) : true) && isFiltered
}
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;
}
});
return (
(searchedTrain.length > 0 ? train.trainNo.toString().startsWith(searchedTrain) : true) &&
(searchedDriver.length > 0 ? train.driverName.toLowerCase().startsWith(searchedDriver.toLowerCase()) : true) &&
(!train.timetableData ? !train.online : true) &&
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)];
}
+1
View File
@@ -101,6 +101,7 @@ export const useStore = defineStore('store', {
isTimeout: train.isTimeout,
isSupporter: train.driverIsSupporter,
driverLevel: train.driverLevel,
timetableData: timetable
? {
+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;
}
}
+5
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;
+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: 'beginDate' | 'distance' | 'total-stops';
id: 'timetableId' | 'beginDate' | 'distance' | 'total-stops';
dir: -1 | 1;
}
+25 -24
View File
@@ -6,41 +6,42 @@
<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"
/>
<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.Initialized || 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') }}
+35 -32
View File
@@ -6,7 +6,8 @@
<JournalOptions
@on-search-confirm="fetchHistoryData"
@on-options-reset="resetOptions"
:sorter-option-ids="[ 'beginDate', 'distance', 'total-stops']"
@on-refresh-data="fetchHistoryData"
:sorter-option-ids="['timetableId', 'beginDate', 'distance', 'total-stops']"
:filters="journalTimetableFilters"
:currentOptionsActive="currentOptionsActive"
:data-status="dataStatus"
@@ -15,35 +16,35 @@
<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.Initialized || 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 +54,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';
@@ -111,7 +112,7 @@ export default defineComponent({
}),
setup() {
const sorterActive: JournalTimetableSorter = reactive({ id: 'beginDate', dir: 1 });
const sorterActive: JournalTimetableSorter = reactive({ id: 'timetableId', dir: 1 });
const journalFilterActive = ref(journalTimetableFilters[0]);
const searchersValues = reactive({
@@ -145,9 +146,8 @@ export default defineComponent({
},
watch: {
currentQueryArray(q: string[]) {
this.currentOptionsActive =
q.length > 2 || q.some((qv) => qv.startsWith('sortBy=') && qv.split('=')[1] != 'beginDate');
currentQueryArray(q: string[]) {
this.currentOptionsActive = q.length >= 2 || q.some((qv) => qv.startsWith('sortBy=') && qv.split('=')[1]);
},
},
@@ -162,6 +162,7 @@ export default defineComponent({
this.fetchHistoryData();
},
methods: {
handleScroll(e: Event) {
const listElement = e.target as HTMLElement;
@@ -188,7 +189,7 @@ export default defineComponent({
this.setSearchers('', '', '', '');
this.journalFilterActive = this.journalTimetableFilters[0];
this.sorterActive.id = 'beginDate';
this.sorterActive.id = 'timetableId';
this.fetchHistoryData();
},
@@ -214,6 +215,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"
/>