mirror of
https://github.com/Spythere/stacjownik.git
synced 2026-05-03 21:38:13 +00:00
Migracja z wersji Vue 2 na Vue 3
This commit is contained in:
@@ -1,27 +1,28 @@
|
||||
<template>
|
||||
<div class="clock">{{ formattedDate }}</div>
|
||||
<div class="clock">{{ computedDate }}</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from "vue";
|
||||
export default Vue.extend({
|
||||
import { computed, defineComponent, ref } from "vue";
|
||||
export default defineComponent({
|
||||
name: "clock",
|
||||
data: () => ({
|
||||
timestamp: Date.now(),
|
||||
}),
|
||||
computed: {
|
||||
formattedDate() {
|
||||
return new Date(this.timestamp).toLocaleString("pl-PL", {
|
||||
setup() {
|
||||
let timestamp = ref(Date.now());
|
||||
|
||||
const computedDate = computed(() => new Date(timestamp.value).toLocaleString("pl-PL", {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
setInterval(() => (this.timestamp = Date.now()), 1000);
|
||||
},
|
||||
}));
|
||||
|
||||
setInterval(() => (timestamp.value = Date.now()), 1000);
|
||||
|
||||
return { computedDate }
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -3,12 +3,11 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Vue, Component, Prop } from "vue-property-decorator";
|
||||
import { defineComponent } from "@vue/runtime-core";
|
||||
|
||||
@Component
|
||||
export default class Loading extends Vue {
|
||||
@Prop() readonly message!: string;
|
||||
}
|
||||
export default defineComponent({
|
||||
props: ["message"],
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
<template>
|
||||
<section class="updates card" v-if="cardOpen">
|
||||
<section
|
||||
class="updates card"
|
||||
v-if="cardOpen"
|
||||
>
|
||||
<h2>Ostatnie aktualizacje w Stacjowniku</h2>
|
||||
<p>Tutaj będą pojawiać się informacje o kolejnych nowościach na stronie :)</p>
|
||||
|
||||
<ul>
|
||||
<li v-for="(update, i) in updates" :key="i">
|
||||
<li
|
||||
v-for="(update, i) in updates"
|
||||
:key="i"
|
||||
>
|
||||
<div>{{update.date}}</div>
|
||||
|
||||
<div>
|
||||
<span v-for="(line, l) in content" :key="l">{{line}}</span>
|
||||
<span
|
||||
v-for="(line, l) in content"
|
||||
:key="l"
|
||||
>{{line}}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -16,7 +25,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
import { defineComponent } from "@vue/runtime-core";
|
||||
|
||||
export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
updates: {
|
||||
@@ -29,7 +40,7 @@ export default {
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -5,13 +5,12 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from "vue-property-decorator";
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
@Component
|
||||
export default class ActionButton extends Vue {}
|
||||
export default defineComponent({});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
<style lang="scss">
|
||||
@import "../../styles/variables";
|
||||
@import "../../styles/responsive";
|
||||
|
||||
@@ -40,15 +39,12 @@ export default class ActionButton extends Vue {}
|
||||
img {
|
||||
width: 1.25em;
|
||||
vertical-align: middle;
|
||||
|
||||
margin-right: 0.35em;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1em;
|
||||
overflow: hidden;
|
||||
|
||||
transition: max-width 0.35s ease-in-out;
|
||||
}
|
||||
|
||||
&.open {
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
<template>
|
||||
<div class="search-box">
|
||||
<input v-model="searchedItem" :placeholder="title" />
|
||||
|
||||
<img
|
||||
class="search-exit"
|
||||
:src="exitIcon"
|
||||
alt="exit-icon"
|
||||
@click="() => (searchedItem = '')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Prop, Model } from "vue-property-decorator";
|
||||
|
||||
@Component
|
||||
export default class SearchBox extends Vue {
|
||||
@Prop({ required: true }) title!: string;
|
||||
@Model("changed") readonly searchedItem!: string;
|
||||
|
||||
// @Emit("changed")
|
||||
// onItemChanged() {
|
||||
// return this.searchedItem;
|
||||
// }
|
||||
|
||||
exitIcon = require("@/assets/icon-exit.svg");
|
||||
|
||||
// searchedItem: string = "";
|
||||
|
||||
// @Watch("searchedItem")
|
||||
// watchSelectedItem(item) {
|
||||
// this.onItemChanged();
|
||||
// }
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.search {
|
||||
&-box {
|
||||
position: relative;
|
||||
|
||||
background: #333;
|
||||
border-radius: 0.5em;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
&-exit {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
|
||||
top: 50%;
|
||||
right: 10px;
|
||||
transform: translateY(-50%);
|
||||
|
||||
width: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
border: none;
|
||||
|
||||
min-width: 85%;
|
||||
padding: 0.35em 0.5em;
|
||||
}
|
||||
</style>
|
||||
@@ -1,12 +1,22 @@
|
||||
<template>
|
||||
<div class="select-box">
|
||||
<div class="select-box_content">
|
||||
<button class="selected" @click="toggleBox">
|
||||
{{ selectedItemComp ? selectedItemComp.value : "" }}
|
||||
<button
|
||||
class="selected"
|
||||
@click="toggleBox"
|
||||
>
|
||||
{{ computedSelectedItem.value }}
|
||||
</button>
|
||||
|
||||
<div class="options" v-if="boxVisible">
|
||||
<div class="option" v-for="item in itemList" :key="item.id">
|
||||
<div
|
||||
class="options"
|
||||
v-if="boxVisible"
|
||||
>
|
||||
<div
|
||||
class="option"
|
||||
v-for="item in itemList"
|
||||
:key="item.id"
|
||||
>
|
||||
<label :for="item.id">
|
||||
<input
|
||||
type="button"
|
||||
@@ -15,7 +25,7 @@
|
||||
@click="selectOption(item)"
|
||||
/>
|
||||
|
||||
<span :style="selectedItemComp.id == item.id ? 'color: gold;' : ''">
|
||||
<span :style="computedSelectedItem.id == item.id ? 'color: gold;' : ''">
|
||||
{{ item.value }}
|
||||
</span>
|
||||
</label>
|
||||
@@ -24,51 +34,79 @@
|
||||
</div>
|
||||
|
||||
<div class="arrow">
|
||||
<img :src="boxVisible ? ascIcon : descIcon" alt="arrow-icon" />
|
||||
<img
|
||||
:src="boxVisible ? ascIcon : descIcon"
|
||||
alt="arrow-icon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Prop, Emit } from "vue-property-decorator";
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
defineEmit,
|
||||
Ref,
|
||||
ref,
|
||||
} from "@vue/runtime-core";
|
||||
|
||||
@Component
|
||||
export default class SelectBox extends Vue {
|
||||
@Prop({ required: true }) itemList!: { id: string | number; value: string }[];
|
||||
@Prop({ default: 0 }) defaultItemIndex!: number;
|
||||
|
||||
boxVisible = false;
|
||||
|
||||
test() {
|
||||
console.log("test");
|
||||
}
|
||||
|
||||
@Emit("selected")
|
||||
onItemSelected() {
|
||||
return this.selectedItem;
|
||||
}
|
||||
|
||||
ascIcon = require("@/assets/icon-arrow-asc.svg");
|
||||
descIcon = require("@/assets/icon-arrow-desc.svg");
|
||||
|
||||
selectedItem: { id: string | number; value: string } | null = null;
|
||||
|
||||
get selectedItemComp() {
|
||||
if (!this.selectedItem) return this.itemList[this.defaultItemIndex];
|
||||
|
||||
return this.itemList.find((item) => item.id === this.selectedItem?.id);
|
||||
}
|
||||
|
||||
toggleBox() {
|
||||
this.boxVisible = !this.boxVisible;
|
||||
}
|
||||
|
||||
selectOption(item: { id: string | number; value: string }) {
|
||||
this.selectedItem = item;
|
||||
this.boxVisible = false;
|
||||
this.onItemSelected();
|
||||
}
|
||||
interface Item {
|
||||
id: string | number;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
emits: ["selected"],
|
||||
|
||||
props: {
|
||||
itemList: {
|
||||
type: Array as () => Item[],
|
||||
required: true,
|
||||
},
|
||||
|
||||
defaultItemIndex: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
ascIcon: require("@/assets/icon-arrow-asc.svg"),
|
||||
descIcon: require("@/assets/icon-arrow-desc.svg"),
|
||||
}),
|
||||
|
||||
setup(props) {
|
||||
let boxVisible = ref(false);
|
||||
let selectedItem: Ref<Item> = ref(props.itemList[props.defaultItemIndex]);
|
||||
|
||||
const computedSelectedItem = computed(() => {
|
||||
return (
|
||||
props.itemList.find((item) => item.id === selectedItem.value.id) ||
|
||||
props.itemList[props.defaultItemIndex]
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
computedSelectedItem,
|
||||
boxVisible,
|
||||
selectedItem,
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
selectOption(item: Item) {
|
||||
this.selectedItem = item;
|
||||
this.boxVisible = false;
|
||||
|
||||
this.$emit("selected", item);
|
||||
},
|
||||
|
||||
toggleBox() {
|
||||
this.boxVisible = !this.boxVisible;
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
<template>
|
||||
<div class="modal">
|
||||
<div class="header">
|
||||
<span>Stacj</span>
|
||||
<img src="@/assets/trainlogo.png" alt="trainlogo" />
|
||||
<span>wnik</span>
|
||||
<sup style="font-size: 0.5em; margin-left: 10px;" class="title">1.4</sup>
|
||||
</div>
|
||||
|
||||
<div class="title">Dziennik Aktywności Scenerii dostępny w wersji beta!</div>
|
||||
|
||||
<div class="content">
|
||||
Do użytku został oddany Dziennik Aktywności Scenerii, który pozwala na dostęp do informacji kto i kiedy dyżurował na danej stacji.
|
||||
Aby przejść do zakładki z dziennikiem wystarczy wybrać opcję "DZIENNIK" w menu na górze strony. Funkcjonalność ta jest nadal w trakcie prac,
|
||||
więc informacje, które pokazuje, mogą być niepoprawne, a dane kasowane w ramach dalszych testów.
|
||||
<div style="text-align: center; font-weight: bold; margin: 0.5em 0;">Miłego korzystania!</div>
|
||||
</div>
|
||||
|
||||
<button class="button" @click="toggleUpdateModal">PRZYJĄŁEM!</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Prop } from "vue-property-decorator";
|
||||
|
||||
@Component
|
||||
export default class UpdateModal extends Vue {
|
||||
@Prop() currentVersion!: string;
|
||||
|
||||
STORAGE_ID = "modal_update";
|
||||
|
||||
toggleUpdateModal(type: string) {
|
||||
this.$emit("toggleUpdateModal");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../styles/responsive";
|
||||
|
||||
.modal {
|
||||
z-index: 100;
|
||||
|
||||
padding: 1em;
|
||||
|
||||
border-radius: 1em;
|
||||
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
|
||||
width: 65%;
|
||||
max-width: 950px;
|
||||
|
||||
max-height: 95vh;
|
||||
overflow: auto;
|
||||
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
background: rgba(black, 0.85);
|
||||
color: white;
|
||||
|
||||
text-align: center;
|
||||
|
||||
@include smallScreen() {
|
||||
font-size: 0.8em;
|
||||
width: 95%;
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
font-size: 4.5em;
|
||||
|
||||
img {
|
||||
width: 0.8em;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.content {
|
||||
font-size: 1.4em;
|
||||
|
||||
text-align: justify;
|
||||
|
||||
ul {
|
||||
list-style: square inside;
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
font-size: 1.25em;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
@@ -7,12 +7,14 @@
|
||||
:href="stationInfo.stationURL"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>{{ stationInfo.stationName }}</a
|
||||
>
|
||||
>{{ stationInfo.stationName }}</a>
|
||||
|
||||
<span v-else>{{ stationInfo.stationName }}</span>
|
||||
</div>
|
||||
<div class="scenery-hash" v-if="stationInfo.stationHash">
|
||||
<div
|
||||
class="scenery-hash"
|
||||
v-if="stationInfo.stationHash"
|
||||
>
|
||||
#{{ stationInfo.stationHash }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -23,24 +25,36 @@
|
||||
:class="!stationInfo.stationHash ? 'no-stats' : ''"
|
||||
>
|
||||
<span class="likes">
|
||||
<img :src="likeIcon" alt="icon-like" />
|
||||
<img
|
||||
:src="likeIcon"
|
||||
alt="icon-like"
|
||||
/>
|
||||
<span>{{ stationInfo.dispatcherRate }}</span>
|
||||
</span>
|
||||
|
||||
<span class="users">
|
||||
<img :src="userIcon" alt="icon-user" />
|
||||
<img
|
||||
:src="userIcon"
|
||||
alt="icon-user"
|
||||
/>
|
||||
<span>{{ stationInfo.currentUsers }}</span>
|
||||
/
|
||||
<span>{{ stationInfo.maxUsers }}</span>
|
||||
</span>
|
||||
|
||||
<span class="spawns">
|
||||
<img :src="spawnIcon" alt="icon-spawn" />
|
||||
<img
|
||||
:src="spawnIcon"
|
||||
alt="icon-spawn"
|
||||
/>
|
||||
<span>{{ stationInfo.spawns.length }}</span>
|
||||
</span>
|
||||
|
||||
<span class="schedules">
|
||||
<img :src="timetableIcon" alt="icon-timetable" />
|
||||
<img
|
||||
:src="timetableIcon"
|
||||
alt="icon-timetable"
|
||||
/>
|
||||
<span v-if="stationInfo.scheduledTrains">
|
||||
<span style="color: #eee">{{
|
||||
stationInfo.scheduledTrains.length
|
||||
@@ -111,7 +125,10 @@
|
||||
</div>
|
||||
|
||||
<div class="info-dispatcher">
|
||||
<div class="dispatcher" v-if="stationInfo.stationHash">
|
||||
<div
|
||||
class="dispatcher"
|
||||
v-if="stationInfo.stationHash"
|
||||
>
|
||||
<span
|
||||
class="dispatcher_level"
|
||||
:style="
|
||||
@@ -129,7 +146,10 @@
|
||||
<span class="dispatcher_name">{{ stationInfo.dispatcherName }}</span>
|
||||
</div>
|
||||
|
||||
<span class="status-badge" :class="stationInfo.statusID">
|
||||
<span
|
||||
class="status-badge"
|
||||
:class="stationInfo.statusID"
|
||||
>
|
||||
{{ $t(`status.${stationInfo.statusID}`) }}
|
||||
{{
|
||||
stationInfo.statusID == "online" ? stationInfo.statusTimeString : ""
|
||||
@@ -141,7 +161,10 @@
|
||||
<div class="user-list">
|
||||
<h3 class="user-header">
|
||||
{{ $t("scenery.users") }}
|
||||
<img :src="userIcon" alt="icon-user" />
|
||||
<img
|
||||
:src="userIcon"
|
||||
alt="icon-user"
|
||||
/>
|
||||
</h3>
|
||||
|
||||
<div
|
||||
@@ -166,7 +189,10 @@
|
||||
<div class="spawn-list">
|
||||
<h3 class="spawn-header">
|
||||
{{ $t("scenery.spawns") }}
|
||||
<img :src="spawnIcon" alt="icon-spawn" />
|
||||
<img
|
||||
:src="spawnIcon"
|
||||
alt="icon-spawn"
|
||||
/>
|
||||
</h3>
|
||||
|
||||
<span
|
||||
@@ -181,7 +207,7 @@
|
||||
<span
|
||||
class="spawn none"
|
||||
v-if="!stationInfo.spawns || stationInfo.spawns.length == 0"
|
||||
>{{ $t("scenery.no-spawns") }}
|
||||
>{{ $t("scenery.no-spawns") }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -190,44 +216,56 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop } from "vue-property-decorator";
|
||||
|
||||
import Station from "@/scripts/interfaces/Station";
|
||||
|
||||
import styleMixin from "@/mixins/styleMixin";
|
||||
import { computed, defineComponent } from "@vue/runtime-core";
|
||||
|
||||
@Component
|
||||
export default class SceneryInfo extends styleMixin {
|
||||
@Prop() readonly stationInfo!: Station;
|
||||
@Prop() readonly timetableOnly!: boolean;
|
||||
export default defineComponent({
|
||||
props: {
|
||||
stationInfo: {
|
||||
type: Object as () => Station,
|
||||
},
|
||||
|
||||
likeIcon: string = require("@/assets/icon-like.svg");
|
||||
spawnIcon: string = require("@/assets/icon-spawn.svg");
|
||||
timetableIcon: string = require("@/assets/icon-timetable.svg");
|
||||
userIcon: string = require("@/assets/icon-user.svg");
|
||||
timetableOnly: Boolean,
|
||||
},
|
||||
|
||||
get computedStationTrains() {
|
||||
if (!this.stationInfo) return null;
|
||||
mixins: [styleMixin],
|
||||
|
||||
return this.stationInfo.stationTrains.map((stationTrain) => {
|
||||
const scheduledData = this.stationInfo?.scheduledTrains.find(
|
||||
(scheduledTrain) => scheduledTrain.trainNo === stationTrain.trainNo
|
||||
);
|
||||
data: () => ({
|
||||
likeIcon: require("@/assets/icon-like.svg"),
|
||||
spawnIcon: require("@/assets/icon-spawn.svg"),
|
||||
timetableIcon: require("@/assets/icon-timetable.svg"),
|
||||
userIcon: require("@/assets/icon-user.svg"),
|
||||
}),
|
||||
|
||||
return {
|
||||
...stationTrain,
|
||||
stopStatus: scheduledData?.stopStatus || "no-timetable",
|
||||
};
|
||||
setup(props) {
|
||||
const computedStationTrains = computed(() => {
|
||||
if (!props.stationInfo) return [];
|
||||
|
||||
return props.stationInfo.stationTrains.map((train) => {
|
||||
const scheduledTrainStatus = props.stationInfo?.scheduledTrains.find(
|
||||
(st) => st.trainNo === train.trainNo
|
||||
);
|
||||
|
||||
return {
|
||||
...train,
|
||||
stopStatus: scheduledTrainStatus?.stopStatus || "no-timetable",
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
navigateToTrain(trainNo: number) {
|
||||
this.$router.push({
|
||||
name: "TrainsView",
|
||||
params: { queryTrain: trainNo.toString() },
|
||||
});
|
||||
}
|
||||
}
|
||||
return { computedStationTrains };
|
||||
},
|
||||
|
||||
methods: {
|
||||
navigateToTrain(trainNo: number) {
|
||||
this.$router.push({
|
||||
name: "TrainsView",
|
||||
params: { queryTrain: trainNo.toString() },
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -25,11 +25,14 @@
|
||||
value: cp.checkpointName,
|
||||
}))
|
||||
"
|
||||
@selected="chooseOption"
|
||||
@selected="selectCheckpoint"
|
||||
></select-box>
|
||||
</div>
|
||||
|
||||
<span class="timetable-item loading" v-if="dataStatus == 0">{{
|
||||
<span
|
||||
class="timetable-item loading"
|
||||
v-if="dataStatus == 0"
|
||||
>{{
|
||||
$t("app.loading")
|
||||
}}</span>
|
||||
|
||||
@@ -48,14 +51,12 @@
|
||||
>
|
||||
<span class="timetable-general">
|
||||
<span class="general-info">
|
||||
<router-link
|
||||
:to="{
|
||||
<router-link :to="{
|
||||
name: 'TrainsView',
|
||||
params: {
|
||||
queryTrain: scheduledTrain.trainNo.toString(),
|
||||
},
|
||||
}"
|
||||
>
|
||||
}">
|
||||
<span>
|
||||
<strong>{{ scheduledTrain.category }}</strong>
|
||||
{{ scheduledTrain.trainNo }}
|
||||
@@ -68,21 +69,17 @@
|
||||
'https://td2.info.pl/profile/?u=' + scheduledTrain.driverId
|
||||
"
|
||||
target="_blank"
|
||||
>{{ scheduledTrain.driverName }}</a
|
||||
>
|
||||
>{{ scheduledTrain.driverName }}</a>
|
||||
</span>
|
||||
|
||||
<div class="info-route">
|
||||
<strong
|
||||
>{{ scheduledTrain.beginsAt }} -
|
||||
{{ scheduledTrain.terminatesAt }}</strong
|
||||
>
|
||||
<strong>{{ scheduledTrain.beginsAt }} -
|
||||
{{ scheduledTrain.terminatesAt }}</strong>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
<span class="general-status">
|
||||
<span :class="scheduledTrain.stopStatus"
|
||||
>{{ $t(`timetables.${scheduledTrain.stopStatus}`) }}
|
||||
<span :class="scheduledTrain.stopStatus">{{ $t(`timetables.${scheduledTrain.stopStatus}`) }}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
@@ -95,7 +92,10 @@
|
||||
v-html="$t('timetables.begins')"
|
||||
>
|
||||
</span>
|
||||
<span class="arrival-time" v-else>
|
||||
<span
|
||||
class="arrival-time"
|
||||
v-else
|
||||
>
|
||||
{{ scheduledTrain.stopInfo.arrivalTimeString }} ({{
|
||||
scheduledTrain.stopInfo.arrivalDelay
|
||||
}})
|
||||
@@ -103,7 +103,10 @@
|
||||
</span>
|
||||
|
||||
<span class="schedule-stop">
|
||||
<span class="stop-time" v-if="scheduledTrain.stopInfo.stopTime">
|
||||
<span
|
||||
class="stop-time"
|
||||
v-if="scheduledTrain.stopInfo.stopTime"
|
||||
>
|
||||
{{ scheduledTrain.stopInfo.stopTime }}
|
||||
{{ scheduledTrain.stopInfo.stopType }}
|
||||
</span>
|
||||
@@ -116,7 +119,10 @@
|
||||
v-html="$t('timetables.terminates')"
|
||||
>
|
||||
</span>
|
||||
<span class="departure-time" v-else>
|
||||
<span
|
||||
class="departure-time"
|
||||
v-else
|
||||
>
|
||||
{{ scheduledTrain.stopInfo.departureTimeString }} ({{
|
||||
scheduledTrain.stopInfo.departureDelay
|
||||
}})
|
||||
@@ -129,74 +135,96 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Prop } from "vue-property-decorator";
|
||||
|
||||
import Station from "@/scripts/interfaces/Station";
|
||||
import ScheduledTrain from "@/scripts/interfaces/ScheduledTrain";
|
||||
import SelectBox from "../Global/SelectBox.vue";
|
||||
import { computed, defineComponent, ref } from "@vue/runtime-core";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
@Component({ components: { SelectBox } })
|
||||
export default class SceneryTimetable extends Vue {
|
||||
@Prop() readonly stationInfo!: Station;
|
||||
@Prop() readonly timetableOnly!: boolean;
|
||||
@Prop() readonly dataStatus!: number;
|
||||
export default defineComponent({
|
||||
components: { SelectBox },
|
||||
|
||||
viewIcon: string = require("@/assets/icon-view.svg");
|
||||
props: {
|
||||
stationInfo: {
|
||||
type: Object as () => Station,
|
||||
},
|
||||
timetableOnly: {
|
||||
type: Boolean,
|
||||
},
|
||||
dataStatus: {
|
||||
type: Number,
|
||||
},
|
||||
},
|
||||
|
||||
listOpen: boolean = false;
|
||||
selectedOption: string = "";
|
||||
data: () => ({
|
||||
viewIcon: require("@/assets/icon-view.svg"),
|
||||
listOpen: false,
|
||||
}),
|
||||
|
||||
loadSelectedOption() {
|
||||
if (!this.stationInfo) return;
|
||||
if (!this.stationInfo.checkpoints) return;
|
||||
if (this.selectedOption != "") return;
|
||||
setup(props) {
|
||||
const route = useRoute();
|
||||
const currentURL = computed(() => `${location.origin}${route.fullPath}`);
|
||||
|
||||
this.selectedOption = this.stationInfo.checkpoints[0].checkpointName;
|
||||
}
|
||||
const selectedCheckpoint = ref("");
|
||||
|
||||
const computedScheduledTrains = computed(() => {
|
||||
if (!props.stationInfo) return [];
|
||||
|
||||
let scheduledTrains = props.stationInfo.checkpoints?.find(
|
||||
(cp) => cp.checkpointName === selectedCheckpoint.value
|
||||
)?.scheduledTrains;
|
||||
|
||||
// if (props.stationInfo.checkpoints)
|
||||
// scheduledTrains = props.stationInfo.checkpoints.find(
|
||||
// (cp) => cp.checkpointName === selectedCheckpoint.value
|
||||
// )?.scheduledTrains;
|
||||
// else scheduledTrains = props.stationInfo.scheduledTrains;
|
||||
|
||||
return (
|
||||
scheduledTrains?.sort((a, b) => {
|
||||
if (a.stopStatusID > b.stopStatusID) return 1;
|
||||
else if (a.stopStatusID < b.stopStatusID) return -1;
|
||||
|
||||
if (a.stopInfo.arrivalTimestamp > b.stopInfo.arrivalTimestamp)
|
||||
return 1;
|
||||
else if (a.stopInfo.arrivalTimestamp < b.stopInfo.arrivalTimestamp)
|
||||
return -1;
|
||||
|
||||
return a.stopInfo.departureTimestamp > b.stopInfo.departureTimestamp
|
||||
? 1
|
||||
: -1;
|
||||
}) || []
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
currentURL,
|
||||
selectedCheckpoint,
|
||||
computedScheduledTrains,
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
loadSelectedOption() {
|
||||
if (!this.stationInfo) return;
|
||||
if (!this.stationInfo.checkpoints) return;
|
||||
if (this.selectedCheckpoint != "") return;
|
||||
|
||||
this.selectedCheckpoint = this.stationInfo.checkpoints[0].checkpointName;
|
||||
},
|
||||
|
||||
selectCheckpoint(item: { id: number | string; value: string }) {
|
||||
this.selectedCheckpoint = item.value;
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.loadSelectedOption();
|
||||
}
|
||||
},
|
||||
|
||||
activated() {
|
||||
this.loadSelectedOption();
|
||||
}
|
||||
|
||||
chooseOption(item: { id: number | string; value: string }) {
|
||||
this.selectedOption = item.value;
|
||||
}
|
||||
|
||||
get currentURL() {
|
||||
return `${location.origin}${this.$route.fullPath}`;
|
||||
}
|
||||
|
||||
get computedScheduledTrains() {
|
||||
if (!this.stationInfo) return [];
|
||||
|
||||
let scheduledTrains: ScheduledTrain[] | undefined;
|
||||
|
||||
if (this.stationInfo.checkpoints)
|
||||
scheduledTrains = this.stationInfo.checkpoints.find(
|
||||
(cp) => cp.checkpointName === this.selectedOption
|
||||
)?.scheduledTrains;
|
||||
else scheduledTrains = this.stationInfo.scheduledTrains;
|
||||
|
||||
return (
|
||||
scheduledTrains?.sort((a, b) => {
|
||||
if (a.stopStatusID > b.stopStatusID) return 1;
|
||||
else if (a.stopStatusID < b.stopStatusID) return -1;
|
||||
|
||||
if (a.stopInfo.arrivalTimestamp > b.stopInfo.arrivalTimestamp) return 1;
|
||||
else if (a.stopInfo.arrivalTimestamp < b.stopInfo.arrivalTimestamp)
|
||||
return -1;
|
||||
|
||||
return a.stopInfo.departureTimestamp > b.stopInfo.departureTimestamp
|
||||
? 1
|
||||
: -1;
|
||||
}) || []
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<section class="card">
|
||||
<section
|
||||
class="card"
|
||||
v-if="showCard"
|
||||
>
|
||||
<div
|
||||
class="card-exit"
|
||||
@click="exit"
|
||||
@@ -79,13 +82,13 @@
|
||||
<div class="card-actions flex">
|
||||
<action-button
|
||||
class="outlined"
|
||||
@click.native="resetFilters"
|
||||
@click="resetFilters"
|
||||
>
|
||||
{{ $t("filters.reset") }}
|
||||
</action-button>
|
||||
<action-button
|
||||
class="outlined"
|
||||
@click.native="exit"
|
||||
@click="exit"
|
||||
>{{
|
||||
$t("filters.close")
|
||||
}}</action-button>
|
||||
@@ -94,83 +97,85 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Vue, Component, Prop } from "vue-property-decorator";
|
||||
|
||||
import inputData from "@/data/options.json";
|
||||
|
||||
import StorageManager from "@/scripts/managers/storageManager";
|
||||
import { defineComponent } from "@vue/runtime-core";
|
||||
import ActionButton from "../Global/ActionButton.vue";
|
||||
|
||||
@Component({ components: { ActionButton } })
|
||||
export default class FilterCard extends Vue {
|
||||
inputs = { ...inputData };
|
||||
saveOptions: boolean = false;
|
||||
STORAGE_KEY: string = "options_saved";
|
||||
export default defineComponent({
|
||||
components: { ActionButton },
|
||||
props: ["showCard", "exit"],
|
||||
|
||||
@Prop() exit!: () => void;
|
||||
data: () => ({
|
||||
inputs: { ...inputData },
|
||||
saveOptions: false,
|
||||
STORAGE_KEY: "options_saved",
|
||||
}),
|
||||
|
||||
mounted() {
|
||||
this.saveOptions = StorageManager.isRegistered(this.STORAGE_KEY);
|
||||
}
|
||||
},
|
||||
|
||||
handleChange(e: Event): void {
|
||||
const target = <HTMLInputElement>e.target;
|
||||
methods: {
|
||||
handleChange(e: Event) {
|
||||
const target = e.target as HTMLInputElement;
|
||||
|
||||
this.$emit("changeFilterValue", {
|
||||
name: target.name,
|
||||
value: !target.checked,
|
||||
});
|
||||
this.$emit("changeFilterValue", {
|
||||
name: target.name,
|
||||
value: !target.checked,
|
||||
});
|
||||
if (this.saveOptions)
|
||||
StorageManager.setBooleanValue(target.name, target.checked);
|
||||
},
|
||||
|
||||
if (this.saveOptions)
|
||||
StorageManager.setBooleanValue(target.name, target.checked);
|
||||
}
|
||||
handleInput(e: Event) {
|
||||
const target = e.target as HTMLInputElement;
|
||||
|
||||
handleInput(e: Event): void {
|
||||
const target = <HTMLInputElement>e.target;
|
||||
this.$emit("changeFilterValue", {
|
||||
name: target.name,
|
||||
value: target.value,
|
||||
});
|
||||
this.$emit("changeFilterValue", {
|
||||
name: target.name,
|
||||
value: target.value,
|
||||
});
|
||||
if (this.saveOptions)
|
||||
StorageManager.setStringValue(target.name, target.value);
|
||||
},
|
||||
|
||||
if (this.saveOptions)
|
||||
StorageManager.setStringValue(target.name, target.value);
|
||||
}
|
||||
saveFilters() {
|
||||
if (!this.saveOptions) {
|
||||
StorageManager.unregisterStorage(this.STORAGE_KEY);
|
||||
return;
|
||||
}
|
||||
|
||||
saveFilters(): void {
|
||||
if (!this.saveOptions) {
|
||||
StorageManager.unregisterStorage(this.STORAGE_KEY);
|
||||
return;
|
||||
}
|
||||
StorageManager.registerStorage(this.STORAGE_KEY);
|
||||
|
||||
StorageManager.registerStorage(this.STORAGE_KEY);
|
||||
this.inputs.options.forEach((option) =>
|
||||
StorageManager.setBooleanValue(option.name, option.value)
|
||||
);
|
||||
|
||||
this.inputs.options.forEach((option) =>
|
||||
StorageManager.setBooleanValue(option.name, option.value)
|
||||
);
|
||||
this.inputs.sliders.forEach((slider) =>
|
||||
StorageManager.setNumericValue(slider.name, slider.value)
|
||||
);
|
||||
},
|
||||
|
||||
this.inputs.sliders.forEach((slider) =>
|
||||
StorageManager.setNumericValue(slider.name, slider.value)
|
||||
);
|
||||
}
|
||||
resetFilters() {
|
||||
this.inputs.options.forEach((option) => {
|
||||
option.value = option.defaultValue;
|
||||
StorageManager.setBooleanValue(option.name, option.value);
|
||||
});
|
||||
|
||||
resetFilters(): void {
|
||||
this.inputs.options.forEach((option) => {
|
||||
option.value = option.defaultValue;
|
||||
StorageManager.setBooleanValue(option.name, option.value);
|
||||
});
|
||||
this.inputs.sliders.forEach((slider) => {
|
||||
slider.value = slider.defaultValue;
|
||||
StorageManager.setNumericValue(slider.name, slider.value);
|
||||
});
|
||||
|
||||
this.inputs.sliders.forEach((slider) => {
|
||||
slider.value = slider.defaultValue;
|
||||
StorageManager.setNumericValue(slider.name, slider.value);
|
||||
});
|
||||
this.$emit("resetFilters");
|
||||
},
|
||||
|
||||
this.$emit("resetFilters");
|
||||
}
|
||||
|
||||
closeCard(): void {
|
||||
this.exit();
|
||||
}
|
||||
}
|
||||
closeCard() {
|
||||
this.exit();
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
<template>
|
||||
<div class="options">
|
||||
<div class="options-actions">
|
||||
<button
|
||||
class="action-btn"
|
||||
:class="{'open': filterCardOpen}"
|
||||
@click="() => toggleCardsState('filter')"
|
||||
>
|
||||
<img :src="require('@/assets/icon-filter2.svg')" alt="icon-filter" />
|
||||
<p>FILTRY</p>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="action-btn"
|
||||
:class="{'open': legendCardOpen}"
|
||||
@click="() => toggleCardsState('legend')"
|
||||
>
|
||||
<img :src="require('@/assets/icon-legend.svg')" alt="icon legend" />
|
||||
<p>LEGENDA</p>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="options-content">
|
||||
<transition name="card-anim"></transition>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Vue, Component } from "vue-property-decorator";
|
||||
|
||||
@Component({})
|
||||
export default class Options extends Vue {
|
||||
filterCardOpen: boolean = false;
|
||||
legendCardOpen: boolean = false;
|
||||
|
||||
toggleCardsState(name: string): void {
|
||||
if (name == "filter") {
|
||||
this.legendCardOpen = false;
|
||||
this.filterCardOpen = !this.filterCardOpen;
|
||||
}
|
||||
|
||||
if (name == "legend") {
|
||||
this.filterCardOpen = false;
|
||||
this.legendCardOpen = !this.legendCardOpen;
|
||||
}
|
||||
}
|
||||
|
||||
toggleCardState(): void {
|
||||
this.filterCardOpen = !this.filterCardOpen;
|
||||
}
|
||||
|
||||
toggleLegendCardState(): void {
|
||||
this.legendCardOpen = !this.legendCardOpen;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../styles/variables.scss";
|
||||
@import "../../styles/responsive.scss";
|
||||
|
||||
.options {
|
||||
display: flex;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.card-anim {
|
||||
&-enter-active,
|
||||
&-leave-active {
|
||||
transition: all 0.25s ease-in-out;
|
||||
}
|
||||
|
||||
&-enter,
|
||||
&-leave-to {
|
||||
transform: translate(-45%, -50%);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.options {
|
||||
&-actions {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
background: #333;
|
||||
border: none;
|
||||
|
||||
color: #e0e0e0;
|
||||
font-size: 0.4em;
|
||||
|
||||
padding: 0.3em;
|
||||
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
|
||||
transition: all 0.3s;
|
||||
|
||||
img {
|
||||
width: 1.3em;
|
||||
margin-right: 0.2em;
|
||||
}
|
||||
|
||||
p {
|
||||
max-width: 0;
|
||||
font-size: 1em;
|
||||
overflow: hidden;
|
||||
|
||||
transition: max-width 0.35s ease-in-out;
|
||||
}
|
||||
|
||||
&:hover > p,
|
||||
&.open > p {
|
||||
max-width: 500px;
|
||||
color: $accentCol;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: rgba(#e0e0e0, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
@include smallScreen {
|
||||
.options {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -160,6 +160,7 @@
|
||||
$t('desc.signals-type') + $t(`signals.${station.signalType}`)
|
||||
"
|
||||
/>
|
||||
|
||||
<img
|
||||
v-if="station.SBL && station.SBL !== ''"
|
||||
:src="require(`@/assets/icon-SBL.svg`)"
|
||||
@@ -236,80 +237,87 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop } from "vue-property-decorator";
|
||||
|
||||
import Station from "@/scripts/interfaces/Station";
|
||||
import styleMixin from "@/mixins/styleMixin";
|
||||
|
||||
import { Getter } from "vuex-class";
|
||||
|
||||
import Options from "@/components/StationsView/Options.vue";
|
||||
import { StoreData } from "@/scripts/interfaces/StoreData";
|
||||
import { DataStatus } from "@/scripts/enums/DataStatus";
|
||||
import { computed, ComputedRef, defineComponent } from "@vue/runtime-core";
|
||||
import { useStore } from "@/store";
|
||||
import { GETTERS } from "@/constants/storeConstants";
|
||||
import Station from "@/scripts/interfaces/Station";
|
||||
|
||||
@Component({
|
||||
components: { Options },
|
||||
})
|
||||
export default class StationTable extends styleMixin {
|
||||
@Prop() readonly stations!: Station[];
|
||||
@Prop() readonly sorterActive!: number;
|
||||
export default defineComponent({
|
||||
props: {
|
||||
stations: {
|
||||
type: Array as () => Station[],
|
||||
required: true,
|
||||
},
|
||||
|
||||
@Prop() readonly setFocusedStation!: () => void;
|
||||
@Prop() readonly changeSorter!: () => void;
|
||||
sorterActive: {
|
||||
type: Object as () => { id: string; dir: number },
|
||||
required: true,
|
||||
},
|
||||
|
||||
@Getter("getAllData") storeAPIData!: StoreData;
|
||||
setFocusedStation: Function,
|
||||
changeSorter: Function,
|
||||
},
|
||||
|
||||
likeIcon: string = require("@/assets/icon-like.svg");
|
||||
spawnIcon: string = require("@/assets/icon-spawn.svg");
|
||||
timetableIcon: string = require("@/assets/icon-timetable.svg");
|
||||
userIcon: string = require("@/assets/icon-user.svg");
|
||||
trainIcon: string = require("@/assets/icon-train.svg");
|
||||
mixins: [styleMixin],
|
||||
|
||||
ascIcon: string = require("@/assets/icon-arrow-asc.svg");
|
||||
descIcon: string = require("@/assets/icon-arrow-desc.svg");
|
||||
data: () => ({
|
||||
likeIcon: require("@/assets/icon-like.svg"),
|
||||
spawnIcon: require("@/assets/icon-spawn.svg"),
|
||||
timetableIcon: require("@/assets/icon-timetable.svg"),
|
||||
userIcon: require("@/assets/icon-user.svg"),
|
||||
trainIcon: require("@/assets/icon-train.svg"),
|
||||
|
||||
headIds = [
|
||||
"station",
|
||||
"min-lvl",
|
||||
"status",
|
||||
"dispatcher",
|
||||
"dispatcher-lvl",
|
||||
"routes",
|
||||
"general",
|
||||
];
|
||||
ascIcon: require("@/assets/icon-arrow-asc.svg"),
|
||||
descIcon: require("@/assets/icon-arrow-desc.svg"),
|
||||
|
||||
headIconsIds = ["user", "spawn", "timetable"];
|
||||
headIds: [
|
||||
"station",
|
||||
"min-lvl",
|
||||
"status",
|
||||
"dispatcher",
|
||||
"dispatcher-lvl",
|
||||
"routes",
|
||||
"general",
|
||||
],
|
||||
|
||||
headTitles: string[][] = [
|
||||
["Stacja"],
|
||||
["Min. poziom", "dyżurnego"],
|
||||
["Status"],
|
||||
["Dyżurny"],
|
||||
["Poziom", "dyżurnego"],
|
||||
["Szlaki", "2tor | 1tor"],
|
||||
["Informacje", "ogólne"],
|
||||
[this.userIcon, "Mechanicy online"],
|
||||
[this.spawnIcon, "Otwarte spawny"],
|
||||
[this.timetableIcon, "Aktywne RJ"],
|
||||
];
|
||||
headIconsIds: ["user", "spawn", "timetable"],
|
||||
}),
|
||||
|
||||
setScenery(name: string) {
|
||||
const station = this.stations.find(
|
||||
(station) => station.stationName === name
|
||||
setup() {
|
||||
const store = useStore();
|
||||
|
||||
const dataConnectionStatus: ComputedRef<DataStatus> = computed(
|
||||
() => store.getters[GETTERS.dataStatus]
|
||||
);
|
||||
|
||||
if (!station) return;
|
||||
const isDataLoaded = () =>
|
||||
computed(() => {
|
||||
dataConnectionStatus.value == DataStatus.Loaded;
|
||||
});
|
||||
|
||||
this.$router.push({
|
||||
name: "SceneryView",
|
||||
query: { station: station.stationName.replaceAll(" ", "_") },
|
||||
});
|
||||
}
|
||||
return {
|
||||
isDataLoaded,
|
||||
};
|
||||
},
|
||||
|
||||
get isDataLoaded() {
|
||||
return this.storeAPIData.dataConnectionStatus == DataStatus.Loaded;
|
||||
}
|
||||
}
|
||||
methods: {
|
||||
setScenery(name: string) {
|
||||
const station = this.stations.find(
|
||||
(station) => station.stationName === name
|
||||
);
|
||||
|
||||
if (!station) return;
|
||||
|
||||
this.$router.push({
|
||||
name: "SceneryView",
|
||||
query: { station: station.stationName.replaceAll(" ", "_") },
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -358,6 +366,7 @@ table {
|
||||
|
||||
padding: 0.5em;
|
||||
background-color: $primaryCol;
|
||||
white-space: pre-wrap;
|
||||
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
@@ -442,8 +451,8 @@ td.station {
|
||||
}
|
||||
|
||||
.track {
|
||||
margin: 0 0.3em;
|
||||
padding: 0.35em 0.1em;
|
||||
margin: 0 0.35em;
|
||||
padding: 0.35em;
|
||||
font-size: 1.05em;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
@@ -1,286 +0,0 @@
|
||||
<template>
|
||||
<div class="station-timetable">
|
||||
<div class="timetable-wrapper">
|
||||
<div class="timetable-title title">
|
||||
<div style="font-size: 1.5em">{{ stationName.toUpperCase() }}</div>
|
||||
<div style="font-size: 0.7em">AKTYWNE ROZKŁADY JAZDY</div>
|
||||
</div>
|
||||
|
||||
<div class="timetable-content">
|
||||
<div
|
||||
class="timetable-item"
|
||||
v-for="(scheduledTrain, i) in computedScheduledTrains"
|
||||
:key="i"
|
||||
>
|
||||
<span class="timetable-general">
|
||||
<span class="general-info">
|
||||
<router-link
|
||||
:to="{
|
||||
name: 'TrainsView',
|
||||
params: {
|
||||
passedSearchedTrain: scheduledTrain.trainNo.toString(),
|
||||
},
|
||||
}"
|
||||
>
|
||||
<span>
|
||||
<strong>{{ scheduledTrain.category }}</strong>
|
||||
{{ scheduledTrain.trainNo }}
|
||||
</span>
|
||||
</router-link>
|
||||
|
|
||||
<span>
|
||||
<a
|
||||
:href="
|
||||
'https://td2.info.pl/profile/?u=' + scheduledTrain.driverId
|
||||
"
|
||||
target="_blank"
|
||||
>{{ scheduledTrain.driverName }}</a
|
||||
>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span class="general-status">
|
||||
<span :class="scheduledTrain.stopStatus">{{
|
||||
scheduledTrain.stopLabel
|
||||
}}</span>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span class="timetable-schedule">
|
||||
<span class="schedule-arrival">
|
||||
<span
|
||||
class="arrival-time begins"
|
||||
v-if="scheduledTrain.stopInfo.beginsHere"
|
||||
>ROZPOCZYNA BIEG</span
|
||||
>
|
||||
<span class="arrival-time" v-else
|
||||
>{{ scheduledTrain.stopInfo.arrivalTimeString }} ({{
|
||||
scheduledTrain.stopInfo.arrivalDelay
|
||||
}})</span
|
||||
>
|
||||
</span>
|
||||
|
||||
<span class="schedule-stop">
|
||||
<span class="stop-time" v-if="scheduledTrain.stopInfo.stopTime"
|
||||
>{{ scheduledTrain.stopInfo.stopTime }}
|
||||
{{ scheduledTrain.stopInfo.stopType }}</span
|
||||
>
|
||||
<span class="stop-arrow arrow"></span>
|
||||
</span>
|
||||
<span class="schedule-departure">
|
||||
<span
|
||||
class="departure-time terminates"
|
||||
v-if="scheduledTrain.stopInfo.terminatesHere"
|
||||
>KOŃCZY BIEG</span
|
||||
>
|
||||
<span class="departure-time" v-else
|
||||
>{{ scheduledTrain.stopInfo.departureTimeString }} ({{
|
||||
scheduledTrain.stopInfo.departureDelay
|
||||
}})</span
|
||||
>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Prop } from "vue-property-decorator";
|
||||
|
||||
@Component
|
||||
export default class StationTimetable extends Vue {
|
||||
@Prop() readonly scheduledTrains;
|
||||
@Prop() readonly stationName;
|
||||
|
||||
get computedScheduledTrains() {
|
||||
return this.scheduledTrains.sort((a, b) => {
|
||||
if (a.stopInfo.arrivalTimestamp > b.stopInfo.arrivalTimestamp) return 1;
|
||||
else if (a.stopInfo.arrivalTimestamp < b.stopInfo.arrivalTimestamp)
|
||||
return -1;
|
||||
|
||||
return a.stopInfo.departureTimestamp > b.stopInfo.departureTimestamp
|
||||
? 1
|
||||
: -1;
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../styles/variables.scss";
|
||||
@import "../../styles/responsive.scss";
|
||||
|
||||
.station-timetable {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
transform: translateY(-100%);
|
||||
-webikit-transform: translateY(-100%);
|
||||
|
||||
&.show {
|
||||
transform: translateY(0);
|
||||
-webkit-transform: translateY(0);
|
||||
}
|
||||
|
||||
transition: transform 150ms ease-out;
|
||||
|
||||
background: #333;
|
||||
|
||||
@include smallScreen() {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
}
|
||||
|
||||
.timetable {
|
||||
&-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
&-title {
|
||||
padding-top: 2rem;
|
||||
padding-bottom: 0.3rem;
|
||||
font-size: 1.6em;
|
||||
}
|
||||
|
||||
&-wrapper {
|
||||
height: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&-item {
|
||||
margin: 1em auto;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
|
||||
|
||||
padding: 0 1rem;
|
||||
|
||||
@include smallScreen() {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.timetable {
|
||||
&-general {
|
||||
padding: 0.3rem 0.7rem;
|
||||
border: 2px solid white;
|
||||
border-radius: 10px;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
@include smallScreen() {
|
||||
width: 95%;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
}
|
||||
|
||||
&-schedule {
|
||||
@include smallScreen() {
|
||||
width: 80%;
|
||||
margin: 0.7em 0;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(50px, 1fr));
|
||||
font-size: 1.35em;
|
||||
}
|
||||
}
|
||||
|
||||
.arrow {
|
||||
border: solid white;
|
||||
border-width: 0 2px 2px 0;
|
||||
display: inline-block;
|
||||
padding: 2px;
|
||||
margin-left: 50px;
|
||||
|
||||
position: relative;
|
||||
|
||||
transform: rotate(-45deg);
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: 55px;
|
||||
height: 3px;
|
||||
top: 4px;
|
||||
left: 4px;
|
||||
|
||||
transform: translate(-100%, -1px) rotate(45deg);
|
||||
transform-origin: right bottom;
|
||||
|
||||
background: white;
|
||||
}
|
||||
}
|
||||
|
||||
.general-info {
|
||||
span {
|
||||
color: $accentCol;
|
||||
}
|
||||
}
|
||||
|
||||
.general-status {
|
||||
span.arriving {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
span.departed {
|
||||
color: lime;
|
||||
}
|
||||
|
||||
span.stopped {
|
||||
color: #ffa600;
|
||||
}
|
||||
|
||||
span.online {
|
||||
color: gold;
|
||||
}
|
||||
|
||||
span.terminated {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
|
||||
.schedule {
|
||||
&-arrival,
|
||||
&-stop,
|
||||
&-departure {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
margin: 0 0.3rem;
|
||||
}
|
||||
|
||||
&-stop {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.stop-time {
|
||||
font-size: 0.7em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.arrival-time.begins,
|
||||
.departure-time.terminates {
|
||||
font-size: 0.75em;
|
||||
}
|
||||
</style>
|
||||
@@ -42,96 +42,98 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Watch, Prop, Emit } from "vue-property-decorator";
|
||||
import { computed, defineComponent } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import SelectBox from "../Global/SelectBox.vue";
|
||||
|
||||
@Component({ components: { SelectBox } })
|
||||
export default class TrainOptions extends Vue {
|
||||
// Passed as component parameters
|
||||
@Prop() readonly queryTrain!: string;
|
||||
export default defineComponent({
|
||||
components: { SelectBox },
|
||||
props: ["queryTrain"],
|
||||
emits: ["changeSearchedTrain", "changeSearchedDriver", "changeSorter"],
|
||||
|
||||
exitIcon = require("@/assets/icon-exit.svg");
|
||||
data: () => ({
|
||||
exitIcon: require("@/assets/icon-exit.svg"),
|
||||
searchedTrain: "",
|
||||
searchedDriver: "",
|
||||
}),
|
||||
|
||||
searchedTrain = "";
|
||||
searchedDriver = "";
|
||||
setup() {
|
||||
const { t } = useI18n();
|
||||
|
||||
sorterOptions: { id: string; value: string }[] = [
|
||||
{
|
||||
id: "mass",
|
||||
value: "masa",
|
||||
},
|
||||
{
|
||||
id: "speed",
|
||||
value: "prędkość",
|
||||
},
|
||||
{
|
||||
id: "length",
|
||||
value: "długość",
|
||||
},
|
||||
{
|
||||
id: "distance",
|
||||
value: "kilometraż",
|
||||
},
|
||||
{
|
||||
id: "timetable",
|
||||
value: "numer pociągu",
|
||||
},
|
||||
];
|
||||
const sorterOptions = [
|
||||
{
|
||||
id: "mass",
|
||||
value: "masa",
|
||||
},
|
||||
{
|
||||
id: "speed",
|
||||
value: "prędkość",
|
||||
},
|
||||
{
|
||||
id: "length",
|
||||
value: "długość",
|
||||
},
|
||||
{
|
||||
id: "distance",
|
||||
value: "kilometraż",
|
||||
},
|
||||
{
|
||||
id: "timetable",
|
||||
value: "numer pociągu",
|
||||
},
|
||||
];
|
||||
|
||||
get translatedSorterOptions() {
|
||||
return this.sorterOptions.map((option) => ({
|
||||
id: option.id,
|
||||
value: this.$t(`trains.option-${option.id}`),
|
||||
}));
|
||||
}
|
||||
const translatedSorterOptions = computed(() =>
|
||||
sorterOptions.map(({ id }) => ({
|
||||
id,
|
||||
value: t(`trains.option-${id}`),
|
||||
}))
|
||||
);
|
||||
|
||||
return {
|
||||
translatedSorterOptions,
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (this.queryTrain) {
|
||||
this.searchedTrain = this.queryTrain;
|
||||
this.searchedDriver = "";
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/* Emitters to TrainsView managing variables */
|
||||
methods: {
|
||||
chooseTrain(train: string) {
|
||||
this.$emit("changeSearchedTrain", train);
|
||||
},
|
||||
|
||||
@Emit("changeSearchedTrain")
|
||||
chooseTrain(train: string) {
|
||||
return train;
|
||||
}
|
||||
chooseDriver(driverName: string) {
|
||||
this.$emit("changeSearchedDriver", driverName);
|
||||
},
|
||||
|
||||
@Emit("changeSearchedDriver")
|
||||
chooseDriver(driverName: string) {
|
||||
return driverName;
|
||||
}
|
||||
changeSorter(item: { id: string | number; value: string }) {
|
||||
this.$emit("changeSorter", { id: item.id, dir: -1 });
|
||||
},
|
||||
},
|
||||
|
||||
@Emit()
|
||||
changeSorter(item: { id: string | number; value: string }) {
|
||||
return { id: item.id, dir: -1 };
|
||||
}
|
||||
watch: {
|
||||
searchedTrain(value: string) {
|
||||
this.chooseTrain(value);
|
||||
},
|
||||
|
||||
/* Watchers for search boxes */
|
||||
searchedDriver(value: string) {
|
||||
this.chooseDriver(value);
|
||||
},
|
||||
|
||||
@Watch("searchedTrain")
|
||||
watchSearchedTrain(train: string) {
|
||||
this.chooseTrain(train);
|
||||
}
|
||||
queryTrain(train: string) {
|
||||
if (!train) return;
|
||||
if (train == "") return;
|
||||
|
||||
@Watch("searchedDriver")
|
||||
watchSearchedDriver(driver: string) {
|
||||
this.chooseDriver(driver);
|
||||
}
|
||||
|
||||
/* Watcher for train no passed in link params */
|
||||
|
||||
@Watch("queryTrain")
|
||||
onQueryTrainChanged(train: string | undefined) {
|
||||
if (!train) return;
|
||||
if (train == "") return;
|
||||
|
||||
this.searchedTrain = train;
|
||||
this.searchedDriver = "";
|
||||
}
|
||||
}
|
||||
this.searchedTrain = train;
|
||||
this.searchedDriver = "";
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<div class="train-schedule" @click="click">
|
||||
<div
|
||||
class="train-schedule"
|
||||
@click="this.$emit('click')"
|
||||
>
|
||||
<div class="schedule-wrapper">
|
||||
<ul class="stop_list">
|
||||
<li
|
||||
@@ -19,11 +22,17 @@
|
||||
|
||||
<div class="stop-bar"></div>
|
||||
|
||||
<span class="distance" v-if="stop.stopDistance">
|
||||
<span
|
||||
class="distance"
|
||||
v-if="stop.stopDistance"
|
||||
>
|
||||
{{ Math.floor(stop.stopDistance) }}
|
||||
</span>
|
||||
|
||||
<span class="stop-name" v-html="stop.stopName"></span>
|
||||
<span
|
||||
class="stop-name"
|
||||
v-html="stop.stopName"
|
||||
></span>
|
||||
<span class="stop-date">
|
||||
<span
|
||||
class="date arrival"
|
||||
@@ -79,9 +88,7 @@
|
||||
<div class="progress-bar"></div>
|
||||
|
||||
<span v-if="i < followingStops.length - 1">
|
||||
<span
|
||||
v-if="stop.departureLine == followingStops[i + 1].arrivalLine"
|
||||
>
|
||||
<span v-if="stop.departureLine == followingStops[i + 1].arrivalLine">
|
||||
{{ stop.departureLine }}
|
||||
</span>
|
||||
|
||||
@@ -98,32 +105,23 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Prop } from "vue-property-decorator";
|
||||
import { defineComponent } from "@vue/runtime-core";
|
||||
|
||||
import TrainStop from "@/scripts/interfaces/TrainStop";
|
||||
export default defineComponent({
|
||||
props: ["followingStops", "currentStationName"],
|
||||
emits: ["click"],
|
||||
|
||||
@Component
|
||||
export default class TrainSchedule extends Vue {
|
||||
@Prop() readonly followingStops!: TrainStop[];
|
||||
@Prop() readonly currentStationName!: string;
|
||||
|
||||
stylizeTime(timeString: string, delay: number, confirmed: boolean) {
|
||||
return (
|
||||
timeString +
|
||||
(delay != 0 && confirmed
|
||||
? " (" + (delay > 0 ? "+" : "") + delay.toString() + ")"
|
||||
: "")
|
||||
);
|
||||
}
|
||||
|
||||
click() {
|
||||
this.$emit("click");
|
||||
}
|
||||
|
||||
mounted() {
|
||||
console.log("mounted");
|
||||
}
|
||||
}
|
||||
methods: {
|
||||
stylizeTime(timeString: string, delay: number, confirmed: boolean) {
|
||||
return (
|
||||
timeString +
|
||||
(delay != 0 && confirmed
|
||||
? " (" + (delay > 0 ? "+" : "") + delay.toString() + ")"
|
||||
: "")
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,16 +1,29 @@
|
||||
<template>
|
||||
<div class="train-stats">
|
||||
<div class="stats_button">
|
||||
<action-button @click.native="toggleStatsOpen">
|
||||
<img :src="statsIcon" :alt="$t('trains.stats')" />
|
||||
{{ $t("trains.stats") }}
|
||||
<action-button @click="toggleStatsOpen">
|
||||
<img
|
||||
:src="statsIcon"
|
||||
:alt="$t('trains.stats')"
|
||||
/>
|
||||
<p class="xd">{{ $t("trains.stats") }}</p>
|
||||
</action-button>
|
||||
</div>
|
||||
|
||||
<transition name="stats-anim" class="stats_wrapper" tag="div">
|
||||
<div class="stats-body" v-if="trainStatsOpen">
|
||||
<transition
|
||||
name="stats-anim"
|
||||
class="stats_wrapper"
|
||||
tag="div"
|
||||
>
|
||||
<div
|
||||
class="stats-body"
|
||||
v-if="trainStatsOpen"
|
||||
>
|
||||
<h2 class="stats-header">
|
||||
<img :src="statsIcon" :alt="$t('trains.stats')" />
|
||||
<img
|
||||
:src="statsIcon"
|
||||
:alt="$t('trains.stats')"
|
||||
/>
|
||||
{{ $t("trains.stats") }}
|
||||
</h2>
|
||||
|
||||
@@ -70,7 +83,11 @@
|
||||
<div class="title stats-title">{{ $t("trains.stats-locos") }}</div>
|
||||
|
||||
<div class="loco-list stats-content">
|
||||
<div class="loco-item" v-for="(loco, i) in locoList" :key="i">
|
||||
<div
|
||||
class="loco-item"
|
||||
v-for="(loco, i) in locoList"
|
||||
:key="i"
|
||||
>
|
||||
{{ loco[0] }} | {{ loco[1] }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -81,120 +98,145 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Prop } from "vue-property-decorator";
|
||||
import ActionButton from "@/components/Global/ActionButton.vue";
|
||||
|
||||
import Train from "@/scripts/interfaces/Train";
|
||||
import { computed, defineComponent } from "@vue/runtime-core";
|
||||
|
||||
@Component({ components: { ActionButton } })
|
||||
export default class TrainStats extends Vue {
|
||||
@Prop() readonly trains!: Train[];
|
||||
trainStatsOpen = false;
|
||||
export default defineComponent({
|
||||
components: { ActionButton },
|
||||
props: {
|
||||
trains: {
|
||||
type: Array as () => Train[],
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
toggleStatsOpen() {
|
||||
this.trainStatsOpen = !this.trainStatsOpen;
|
||||
}
|
||||
data: () => ({
|
||||
trainStatsOpen: false,
|
||||
statsIcon: require("@/assets/icon-stats.svg"),
|
||||
}),
|
||||
|
||||
statsIcon = require("@/assets/icon-stats.svg");
|
||||
methods: {
|
||||
toggleStatsOpen() {
|
||||
this.trainStatsOpen = !this.trainStatsOpen;
|
||||
},
|
||||
},
|
||||
|
||||
get speedStats(): { avg: string; min: string; max: string } {
|
||||
if (this.trains.length == 0) return { avg: "0", min: "0", max: "0" };
|
||||
setup(props) {
|
||||
const speedStats = computed(() => {
|
||||
if (props.trains.length == 0) return { avg: "0", min: "0", max: "0" };
|
||||
|
||||
const avg = (
|
||||
this.trains.reduce((acc, train) => acc + train.speed, 0) /
|
||||
this.trains.length
|
||||
).toFixed(2);
|
||||
const avg = (
|
||||
props.trains.reduce((acc, train) => acc + train.speed, 0) /
|
||||
props.trains.length
|
||||
).toFixed(2);
|
||||
|
||||
const minMax = this.trains.reduce((acc, train) => {
|
||||
if (!train.timetableData) return acc;
|
||||
const minMaxSpeed = props.trains.reduce((acc, train) => {
|
||||
if (!train.timetableData) return acc;
|
||||
|
||||
acc[0] =
|
||||
acc[0] === undefined || train.speed < acc[0] ? train.speed : acc[0];
|
||||
acc[0] = !acc[0] || train.speed < acc[0] ? train.speed : acc[0];
|
||||
|
||||
acc[1] =
|
||||
acc[1] === undefined || train.speed > acc[1] ? train.speed : acc[1];
|
||||
return acc;
|
||||
}, [] as any);
|
||||
acc[1] = !acc[1] || train.speed > acc[1] ? train.speed : acc[1];
|
||||
return acc;
|
||||
}, [] as any);
|
||||
|
||||
return { avg, min: minMax[0].toString(), max: minMax[1].toString() };
|
||||
}
|
||||
return {
|
||||
avg,
|
||||
min: minMaxSpeed[0].toString(),
|
||||
max: minMaxSpeed[1].toString(),
|
||||
};
|
||||
});
|
||||
|
||||
get timetableStats(): { avg: string; min: string; max: string } {
|
||||
if (this.trains.length == 0) return { avg: "0", min: "0", max: "0" };
|
||||
const timetableStats = computed(() => {
|
||||
if (props.trains.length == 0) return { avg: "0", min: "0", max: "0" };
|
||||
|
||||
const avg = (
|
||||
this.trains.reduce(
|
||||
(acc, train) =>
|
||||
train.timetableData ? acc + train.timetableData.routeDistance : acc,
|
||||
0
|
||||
) / this.trains.length
|
||||
).toFixed(2);
|
||||
const avg = (
|
||||
props.trains.reduce(
|
||||
(acc, train) =>
|
||||
train.timetableData ? acc + train.timetableData.routeDistance : acc,
|
||||
0
|
||||
) / props.trains.length
|
||||
).toFixed(2);
|
||||
|
||||
const minMax = this.trains.reduce((acc, train) => {
|
||||
if (!train.timetableData) return acc;
|
||||
const minMaxDistance = props.trains.reduce((acc, train) => {
|
||||
if (!train.timetableData) return acc;
|
||||
|
||||
acc[0] =
|
||||
acc[0] === undefined || train.timetableData.routeDistance < acc[0]
|
||||
? train.timetableData.routeDistance
|
||||
: acc[0];
|
||||
acc[0] =
|
||||
!acc[0] || train.timetableData.routeDistance < acc[0]
|
||||
? train.timetableData.routeDistance
|
||||
: acc[0];
|
||||
|
||||
acc[1] =
|
||||
acc[1] === undefined || train.timetableData.routeDistance > acc[1]
|
||||
? train.timetableData.routeDistance
|
||||
: acc[1];
|
||||
return acc;
|
||||
}, [] as any);
|
||||
acc[1] =
|
||||
!acc[1] || train.timetableData.routeDistance > acc[1]
|
||||
? train.timetableData.routeDistance
|
||||
: acc[1];
|
||||
return acc;
|
||||
}, [] as any);
|
||||
|
||||
return { avg, min: minMax[0].toString(), max: minMax[1].toString() };
|
||||
}
|
||||
return {
|
||||
avg,
|
||||
min: minMaxDistance[0].toString(),
|
||||
max: minMaxDistance[1].toString(),
|
||||
};
|
||||
});
|
||||
|
||||
get categoryList(): Map<string, number> {
|
||||
const map = this.trains.reduce((acc, train) => {
|
||||
if (!train.timetableData || !train.timetableData.category) return acc;
|
||||
const categoryList = computed(() => {
|
||||
const map = props.trains.reduce((acc, train) => {
|
||||
if (!train.timetableData || !train.timetableData.category) return acc;
|
||||
|
||||
acc.set(
|
||||
train.timetableData.category,
|
||||
acc.get(train.timetableData.category)
|
||||
? acc.get(train.timetableData.category) + 1
|
||||
: 1
|
||||
acc.set(
|
||||
train.timetableData.category,
|
||||
acc.get(train.timetableData.category)
|
||||
? acc.get(train.timetableData.category) + 1
|
||||
: 1
|
||||
);
|
||||
|
||||
return acc;
|
||||
}, new Map());
|
||||
|
||||
return new Map([...map.entries()].sort((a, b) => b[1] - a[1]));
|
||||
});
|
||||
|
||||
const locoList = computed(() => {
|
||||
const map: Map<string, number> = props.trains.reduce((acc, train) => {
|
||||
if (!train.timetableData || !train.locoType) return acc;
|
||||
|
||||
acc.set(
|
||||
train.locoType,
|
||||
acc.get(train.locoType) ? acc.get(train.locoType) + 1 : 1
|
||||
);
|
||||
|
||||
return acc;
|
||||
}, new Map());
|
||||
|
||||
const sorted = [...map.entries()]
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.filter((v, i) => i < 3);
|
||||
|
||||
return sorted;
|
||||
});
|
||||
|
||||
const specialTrainCount = computed(() => {
|
||||
const twrList = props.trains.filter(
|
||||
(train) => train.timetableData && train.timetableData.TWR
|
||||
);
|
||||
const skrList = props.trains.filter(
|
||||
(train) => train.timetableData && train.timetableData.SKR
|
||||
);
|
||||
|
||||
return acc;
|
||||
}, new Map());
|
||||
return [twrList.length, skrList.length];
|
||||
});
|
||||
|
||||
return new Map([...map.entries()].sort((a, b) => b[1] - a[1]));
|
||||
}
|
||||
|
||||
get locoList(): any[] {
|
||||
const map = this.trains.reduce((acc, train) => {
|
||||
if (!train.timetableData || !train.locoType) return acc;
|
||||
|
||||
acc.set(
|
||||
train.locoType,
|
||||
acc.get(train.locoType) ? acc.get(train.locoType) + 1 : 1
|
||||
);
|
||||
|
||||
return acc;
|
||||
}, new Map());
|
||||
|
||||
const sorted = [...map.entries()]
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.filter((v, i) => i < 3);
|
||||
|
||||
return sorted;
|
||||
}
|
||||
|
||||
get specialTrainCount(): [number, number] {
|
||||
const twrList = this.trains.filter(
|
||||
(train) => train.timetableData && train.timetableData.TWR
|
||||
);
|
||||
const skrList = this.trains.filter(
|
||||
(train) => train.timetableData && train.timetableData.SKR
|
||||
);
|
||||
|
||||
return [twrList.length, skrList.length];
|
||||
}
|
||||
}
|
||||
return {
|
||||
speedStats,
|
||||
timetableStats,
|
||||
categoryList,
|
||||
locoList,
|
||||
specialTrainCount,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -206,7 +248,7 @@ export default class TrainStats extends Vue {
|
||||
transition: all 150ms ease-out;
|
||||
}
|
||||
|
||||
&-enter,
|
||||
&-enter-from,
|
||||
&-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
class="train-row"
|
||||
v-for="(train, i) in computedTrains"
|
||||
:key="i"
|
||||
:ref="train.timetableData ? train.timetableData.timetableId : -1"
|
||||
:ref="train.timetableData && (el => { elList[train.timetableData.timetableId] = el })"
|
||||
>
|
||||
<div
|
||||
class="wrapper no-timetable"
|
||||
@@ -319,117 +319,163 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Vue, Component, Prop, Watch } from "vue-property-decorator";
|
||||
|
||||
import Train from "@/scripts/interfaces/Train";
|
||||
import TrainStop from "@/scripts/interfaces/TrainStop";
|
||||
|
||||
import TrainSchedule from "@/components/TrainsView/TrainSchedule.vue";
|
||||
import { DataStatus } from "@/scripts/enums/DataStatus";
|
||||
import {
|
||||
computed,
|
||||
ComputedRef,
|
||||
defineComponent,
|
||||
onBeforeUpdate,
|
||||
Ref,
|
||||
ref,
|
||||
} from "@vue/runtime-core";
|
||||
import { useStore } from "@/store";
|
||||
import { GETTERS } from "@/constants/storeConstants";
|
||||
|
||||
@Component({
|
||||
components: { TrainSchedule },
|
||||
})
|
||||
export default class TrainTable extends Vue {
|
||||
@Prop() computedTrains!: Train[];
|
||||
@Prop() timetableDataStatus!: DataStatus;
|
||||
@Prop() queryTrain!: string;
|
||||
export default defineComponent({
|
||||
components: {
|
||||
TrainSchedule,
|
||||
},
|
||||
|
||||
showedSchedule = 0;
|
||||
props: {
|
||||
computedTrains: {
|
||||
type: Array as () => Train[],
|
||||
required: true,
|
||||
},
|
||||
|
||||
defaultLocoImage = require("@/assets/unknown.png");
|
||||
queryTrain: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
|
||||
ascSVG = require("@/assets/icon-arrow-asc.svg");
|
||||
descSVG = require("@/assets/icon-arrow-desc.svg");
|
||||
data: () => ({
|
||||
showedSchedule: 0,
|
||||
defaultLocoImage: require("@/assets/unknown.png"),
|
||||
|
||||
speedIcon: string = require("@/assets/icon-speed.svg");
|
||||
massIcon: string = require("@/assets/icon-mass.svg");
|
||||
lengthIcon: string = require("@/assets/icon-length.svg");
|
||||
ascSVG: require("@/assets/icon-arrow-asc.svg"),
|
||||
descSVG: require("@/assets/icon-arrow-desc.svg"),
|
||||
|
||||
distanceIcon: string = require("@/assets/icon-distance.svg");
|
||||
sceneryIcon: string = require("@/assets/icon-scenery.svg");
|
||||
signalIcon: string = require("@/assets/icon-signal.svg");
|
||||
routeIcon: string = require("@/assets/icon-route.svg");
|
||||
speedIcon: require("@/assets/icon-speed.svg"),
|
||||
massIcon: require("@/assets/icon-mass.svg"),
|
||||
lengthIcon: require("@/assets/icon-length.svg"),
|
||||
|
||||
get timetableLoaded() {
|
||||
return this.timetableDataStatus == DataStatus.Loaded;
|
||||
}
|
||||
distanceIcon: require("@/assets/icon-distance.svg"),
|
||||
sceneryIcon: require("@/assets/icon-scenery.svg"),
|
||||
signalIcon: require("@/assets/icon-signal.svg"),
|
||||
routeIcon: require("@/assets/icon-route.svg"),
|
||||
}),
|
||||
|
||||
get timetableError() {
|
||||
return this.timetableDataStatus == DataStatus.Error;
|
||||
}
|
||||
setup(props) {
|
||||
const store = useStore();
|
||||
const elList: Ref<HTMLElement[]> = ref([]);
|
||||
|
||||
get distanceLimitExceeded() {
|
||||
return (
|
||||
this.computedTrains.findIndex(
|
||||
(train) =>
|
||||
train.timetableData && train.timetableData.routeDistance > 200
|
||||
) != -1
|
||||
);
|
||||
}
|
||||
|
||||
changeScheduleShowState(elementId: number) {
|
||||
if (elementId < 0) return;
|
||||
|
||||
this.showedSchedule = this.showedSchedule == elementId ? 0 : elementId;
|
||||
|
||||
this.$nextTick(() => {
|
||||
const currentEl: HTMLElement = this.$refs[elementId][0];
|
||||
|
||||
currentEl.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "nearest",
|
||||
});
|
||||
onBeforeUpdate(() => {
|
||||
elList.value.length = 0;
|
||||
});
|
||||
}
|
||||
|
||||
@Watch("queryTrain")
|
||||
onSearchedTrainChange(trainNo: string) {
|
||||
const timetableId = this.computedTrains.find(
|
||||
(train) => train.trainNo == parseInt(trainNo)
|
||||
)?.timetableData?.timetableId;
|
||||
const timetableDataStatus: ComputedRef<DataStatus> = computed(
|
||||
() => store.getters[GETTERS.timetableDataStatus]
|
||||
);
|
||||
|
||||
if (!timetableId) return;
|
||||
const timetableLoaded = computed(
|
||||
() => timetableDataStatus.value === DataStatus.Loaded
|
||||
);
|
||||
const timetableError = computed(
|
||||
() => timetableDataStatus.value === DataStatus.Error
|
||||
);
|
||||
|
||||
this.changeScheduleShowState(timetableId);
|
||||
}
|
||||
const distanceLimitExceeded = computed(
|
||||
() =>
|
||||
props.computedTrains.findIndex(
|
||||
({ timetableData }) =>
|
||||
timetableData && timetableData.routeDistance > 200
|
||||
) != -1
|
||||
);
|
||||
|
||||
onImageError(e: Event) {
|
||||
const imageEl = e.target as HTMLImageElement;
|
||||
return {
|
||||
timetableLoaded,
|
||||
timetableError,
|
||||
distanceLimitExceeded,
|
||||
elList,
|
||||
};
|
||||
},
|
||||
|
||||
imageEl.src = this.defaultLocoImage;
|
||||
}
|
||||
methods: {
|
||||
changeScheduleShowState(timetableId: number) {
|
||||
if (timetableId < 0) return;
|
||||
|
||||
generateStopList(stops: any): string | undefined {
|
||||
if (!stops) return "";
|
||||
this.showedSchedule =
|
||||
this.showedSchedule == timetableId ? 0 : timetableId;
|
||||
|
||||
return stops
|
||||
.reduce((acc, stop: TrainStop, i) => {
|
||||
if (stop.stopType.includes("ph"))
|
||||
acc.push(
|
||||
`<strong style='color:${
|
||||
stop.confirmed ? "springgreen" : "white"
|
||||
}'>${stop.stopName}</strong>`
|
||||
);
|
||||
else if (
|
||||
i > 0 &&
|
||||
i < stops.length - 1 &&
|
||||
!stop.stopNameRAW.includes("po.") &&
|
||||
!stop.stopNameRAW.includes("SBL")
|
||||
)
|
||||
acc.push(`<span style='color:${ stop.confirmed ? "springgreen" : "lightgray" }'>${stop.stopName}</span>`);
|
||||
return acc;
|
||||
}, [])
|
||||
.join(" > ");
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
const currentEl: HTMLElement = this.elList[timetableId];
|
||||
|
||||
calculateCars(locoType: string, cars: string[]) {
|
||||
if (cars.length == 0 && locoType.includes("EN")) return "EZT";
|
||||
else if (cars.length == 0) return "LOK";
|
||||
currentEl.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "nearest",
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
return `${this.$t("trains.cars")}: <span style='color:gold'> ${cars.length}</span>`;
|
||||
}
|
||||
}
|
||||
onImageError(e: Event) {
|
||||
const imageEl = e.target as HTMLImageElement;
|
||||
|
||||
imageEl.src = this.defaultLocoImage;
|
||||
},
|
||||
|
||||
generateStopList(stops: TrainStop[]): string | undefined {
|
||||
if (!stops) return "";
|
||||
|
||||
return stops
|
||||
.reduce((acc: string[], stop: TrainStop, i: number) => {
|
||||
if (stop.stopType.includes("ph"))
|
||||
acc.push(
|
||||
`<strong style='color:${
|
||||
stop.confirmed ? "springgreen" : "white"
|
||||
}'>${stop.stopName}</strong>`
|
||||
);
|
||||
else if (
|
||||
i > 0 &&
|
||||
i < stops.length - 1 &&
|
||||
!stop.stopNameRAW.includes("po.") &&
|
||||
!stop.stopNameRAW.includes("SBL")
|
||||
)
|
||||
acc.push(
|
||||
`<span style='color:${
|
||||
stop.confirmed ? "springgreen" : "lightgray"
|
||||
}'>${stop.stopName}</span>`
|
||||
);
|
||||
return acc;
|
||||
}, [])
|
||||
.join(" > ");
|
||||
},
|
||||
|
||||
calculateCars(locoType: string, cars: string[]) {
|
||||
if (cars.length == 0 && locoType.includes("EN")) return "EZT";
|
||||
else if (cars.length == 0) return "LOK";
|
||||
|
||||
return `${this.$t("trains.cars")}: <span style='color:gold'> ${
|
||||
cars.length
|
||||
}</span>`;
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
queryTrain(trainNo: string) {
|
||||
const timetableId = this.computedTrains.find(
|
||||
(train) => train.trainNo == parseInt(trainNo)
|
||||
)?.timetableData?.timetableId;
|
||||
|
||||
if (!timetableId) return;
|
||||
|
||||
this.changeScheduleShowState(timetableId);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
Reference in New Issue
Block a user