Migracja z wersji Vue 2 na Vue 3

This commit is contained in:
2021-06-29 02:26:36 +02:00
parent 6391b997b1
commit 26ae065837
49 changed files with 2906 additions and 3279 deletions
+13 -12
View File
@@ -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>
+4 -5
View File
@@ -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>
+16 -5
View File
@@ -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>
+3 -7
View File
@@ -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 {
-66
View File
@@ -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>
+81 -43
View File
@@ -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>
-97
View File
@@ -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>
+79 -41
View File
@@ -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>
+100 -72
View File
@@ -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>
+64 -59
View File
@@ -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>
-139
View File
@@ -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>
+69 -60
View File
@@ -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>
+73 -71
View File
@@ -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>
+28 -30
View File
@@ -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>
+141 -99
View File
@@ -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);
+134 -88
View File
@@ -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>