Zmiana w funkcjonowaniu filtrów, zmiany estetyczne

This commit is contained in:
2020-09-01 20:49:15 +02:00
parent c7950ac757
commit 3febe562bf
10 changed files with 562 additions and 620 deletions
+57 -4
View File
@@ -38,6 +38,17 @@
<footer class="app-footer flex"> <footer class="app-footer flex">
<span>&copy; Spythere 2020</span> <span>&copy; Spythere 2020</span>
</footer> </footer>
<transition name="message-anim" mode="out-in">
<span :key="connState">
<div class="message loading" v-if="connState == 0">Pobieranie danych...</div>
<div class="message error" v-if="connState == 1">
<img :src="ErrorIcon" alt="Error" />
Brak odpowiedzi ze strony serwera!
</div>
</span>
</transition>
</div> </div>
</div> </div>
</template> </template>
@@ -48,16 +59,16 @@ import { Action, Getter } from "vuex-class";
import { mapGetters, mapActions } from "vuex"; import { mapGetters, mapActions } from "vuex";
import Error from "@/components/App/Error.vue";
import Loading from "@/components/App/Loading.vue";
import Clock from "@/components/App/Clock.vue"; import Clock from "@/components/App/Clock.vue";
@Component({ @Component({
components: { Error, Loading, Clock }, components: { Clock },
}) })
export default class App extends Vue { export default class App extends Vue {
ErrorIcon = require("@/assets/icon-error.svg");
@Getter("getOnlineInfo") onlineInfo; @Getter("getOnlineInfo") onlineInfo;
@Getter("getConnectionState") connState;
@Action("initStations") initStations; @Action("initStations") initStations;
@@ -95,6 +106,48 @@ export default class App extends Vue {
} }
} }
.message {
&-anim {
&-enter-active,
&-leave-active {
transition: all $animDuration $animType;
}
&-enter {
transform: translateY(100%);
}
&-leave-to {
transform: translateY(0);
}
}
display: flex;
justify-content: center;
align-items: center;
position: fixed;
bottom: 0;
width: 100%;
font-size: calc(0.5rem + 0.5vw);
padding: 0.5rem;
img {
width: 1.5em;
margin: 0 0.5em;
}
&.loading {
background: #cc8b21;
}
&.error {
background: firebrick;
}
}
.route { .route {
margin: 0 0.2em; margin: 0 0.2em;
-46
View File
@@ -1,46 +0,0 @@
<template>
<div class="error">
<img src="@/assets/icon-error.svg" alt="Error" />
<div>Mechaniku, brak przejazdu!</div>
<div class="error-message">{{ error }}</div>
</div>
</template>
<script>
export default {
props: ["error"],
};
</script>
<style lang="scss" scoped>
.error {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
font-size: calc(1rem + 1.5vw);
font-weight: 550;
color: #ff1919;
img {
margin-bottom: 1rem;
width: 200px;
}
&-message {
font-size: 0.6em;
}
}
</style>
@@ -1,5 +1,5 @@
<template> <template>
<section class="option-card"> <section class="filter-card">
<div class="card-exit" @click="exit"> <div class="card-exit" @click="exit">
<img :src="require('@/assets/icon-exit.svg')" alt="exit icon" /> <img :src="require('@/assets/icon-exit.svg')" alt="exit icon" />
</div> </div>
@@ -18,7 +18,10 @@
v-model="option.value" v-model="option.value"
@change="handleChange" @change="handleChange"
/> />
<span class="option-content" :class="option.section">{{option.content}}</span> <span
class="option-content"
:class="option.section + (option.value ? ' checked' : '')"
>{{option.content}}</span>
</label> </label>
</div> </div>
</div> </div>
@@ -45,13 +48,8 @@
<div class="card-save"> <div class="card-save">
<div class="option"> <div class="option">
<label class="option-label"> <label class="option-label">
<input <input class="option-input" type="checkbox" v-model="saveOptions" @change="saveFilters" />
class="option-input" <span class="option-content save" :class="{'checked': saveOptions}">ZAPISZ FILTRY</span>
type="checkbox"
v-model="saveOptions"
@change="changeSaveState"
/>
<span class="option-content save">ZAPISZ FILTRY</span>
</label> </label>
</div> </div>
</div> </div>
@@ -65,22 +63,17 @@
<script lang="ts"> <script lang="ts">
import { Vue, Component, Prop } from "vue-property-decorator"; import { Vue, Component, Prop } from "vue-property-decorator";
import { Action } from "vuex-class";
import inputData from "@/data/options.json"; import inputData from "@/data/options.json";
@Component @Component
export default class OptionCard extends Vue { export default class FilterCard extends Vue {
inputs = { ...inputData }; inputs = { ...inputData };
saveOptions: boolean = false; saveOptions: boolean = false;
STORAGE_KEY: string = "options_saved"; STORAGE_KEY: string = "options_saved";
@Prop() exit!: () => void; @Prop() exit!: () => void;
@Action("setFilter") setFilter;
@Action("resetFilters") resetFilters;
mounted() { mounted() {
const storage = window.localStorage; const storage = window.localStorage;
@@ -91,8 +84,8 @@ export default class OptionCard extends Vue {
handleChange(e: Event): void { handleChange(e: Event): void {
const target = <HTMLInputElement>e.target; const target = <HTMLInputElement>e.target;
this.setFilter({ this.$emit("changeFilterValue", {
filterName: target.name, name: target.name,
value: !target.checked, value: !target.checked,
}); });
@@ -102,17 +95,16 @@ export default class OptionCard extends Vue {
handleInput(e: Event): void { handleInput(e: Event): void {
const target = <HTMLInputElement>e.target; const target = <HTMLInputElement>e.target;
this.$emit("changeFilterValue", {
this.setFilter({ name: target.name,
filterName: target.name, value: target.value,
value: parseInt(target.value),
}); });
if (this.saveOptions) if (this.saveOptions)
window.localStorage.setItem(target.name, target.value + ""); window.localStorage.setItem(target.name, target.value + "");
} }
changeSaveState(): void { saveFilters(): void {
const storage = window.localStorage; const storage = window.localStorage;
if (this.saveOptions) { if (this.saveOptions) {
@@ -139,7 +131,7 @@ export default class OptionCard extends Vue {
window.localStorage.setItem(slider.name, slider.value + ""); window.localStorage.setItem(slider.name, slider.value + "");
}); });
this.resetFilters(); this.$emit('resetFilters');
} }
closeCard(): void { closeCard(): void {
@@ -152,7 +144,7 @@ export default class OptionCard extends Vue {
@import "../../styles/responsive"; @import "../../styles/responsive";
@import "../../styles/variables"; @import "../../styles/variables";
.option-card { .filter-card {
position: fixed; position: fixed;
top: 50%; top: 50%;
left: 50%; left: 50%;
@@ -235,14 +227,6 @@ export default class OptionCard extends Vue {
&-input { &-input {
display: none; display: none;
&:not(:checked) + span {
background-color: #585858;
&::before {
box-shadow: none;
}
}
} }
&-content { &-content {
@@ -267,57 +251,67 @@ export default class OptionCard extends Vue {
transition: all 0.2s; transition: all 0.2s;
&.access { &:not(.checked) {
background-color: #e03b07; background-color: #585858;
&::before { &::before {
box-shadow: 0 0 6px 1px #e03b07; box-shadow: none;
} }
} }
&.control { &.checked {
background-color: #0085ff; &.access {
background-color: #e03b07;
&::before {
box-shadow: 0 0 6px 1px #e03b07;
}
}
&.control {
background-color: #0085ff;
&::before {
box-shadow: 0 0 6px 1px #0085ff;
}
}
&.signals {
background-color: #b000bf;
&::before {
box-shadow: 0 0 6px 1px #b000bf;
}
}
&.status {
background-color: #05b702;
&::before {
box-shadow: 0 0 6px 1px #05b702;
}
}
&.save {
background-color: #05b702;
&::before {
box-shadow: 0 0 6px 1px #05b702;
}
}
&::before { &::before {
box-shadow: 0 0 6px 1px #0085ff; position: absolute;
content: "";
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: inherit;
} }
} }
&.signals {
background-color: #b000bf;
&::before {
box-shadow: 0 0 6px 1px #b000bf;
}
}
&.status {
background-color: #05b702;
&::before {
box-shadow: 0 0 6px 1px #05b702;
}
}
&.save {
background-color: #05b702;
&::before {
box-shadow: 0 0 6px 1px #05b702;
}
}
&::before {
position: absolute;
content: "";
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: inherit;
}
} }
} }
-115
View File
@@ -1,115 +0,0 @@
<template>
<section class="legend-card card">
<div class="card-exit" @click="exit">
<img :src="require('@/assets/icon-exit.svg')" alt="exit icon" />
</div>
<div class="card-title flex">LEGENDA</div>
<div class="card-icons">
<div class="legend-icon" v-for="(icon, i) in icons" :key="i">
<img :src="require(`@/assets/icon-${icon.name}.svg`)" :alt="icon.name" />
<span>{{icon.desc}}</span>
</div>
</div>
</section>
</template>
<script lang="ts">
import { Vue, Component, Prop } from "vue-property-decorator";
@Component
export default class LegendCard extends Vue {
@Prop() exit!: void;
icons: { name: string; desc: string }[] = [
{ name: "SPK", desc: "Sceneria sterowana za pomocą programu SPK" },
{ name: "SCS", desc: "Sceneria sterowana za pomocą programu SCS" },
{
name: "SCS-SPK",
desc: "Sceneria sterowana za pomocą programu SCS lub SPK"
},
{
name: "mechaniczne",
desc: "Sceneria posiadająca urządzenia sterowane mechanicznie"
},
{
name: "mechaniczne+SPK",
desc:
"Sceneria posiadająca urządzenia sterowane mechanicznie zintegrowane z SPK"
},
{
name: "mechaniczne+SCS",
desc:
"Sceneria posiadająca urządzenia sterowane mechanicznie zintegrowane z SCS"
},
{ name: "ręczne", desc: "Sceneria z ręcznie sterowanymi zwrotnicami" },
{
name: "ręczne+SPK",
desc: "Sceneria z ręcznie sterowanymi zwrotnicami zintegrowana z SPK"
},
{
name: "współczesna",
desc: "Sceneria ze współczesną sygnalizacją świetlną"
},
{ name: "kształtowa", desc: "Sceneria ze sygnalizacją kształtową" },
{ name: "historyczna", desc: "Sceneria ze sygnalizacją historyczną" },
{
name: "mieszana",
desc:
"Sceneria ze sygnalizacją mieszaną (kształtowe, historyczne lub/i współczesne)"
},
{
name: "SBL",
desc:
"Sceneria posiadająca samoczynną blokadę liniową na co najmniej jednym z jej szlaków"
}
];
}
</script>
<style lang="scss">
@import "../../styles/variables.scss";
@import "../../styles/responsive.scss";
.card {
&-exit {
position: absolute;
top: 0;
right: 0;
margin: 0.8em;
img {
width: 1.1em;
}
cursor: pointer;
}
&-title {
font-size: 3em;
font-weight: 700;
color: $accentCol;
margin: 0.3em 0;
}
}
.legend-icon {
display: flex;
align-items: center;
padding: 0.5em;
img {
width: 2.5em;
margin-right: 0.5em;
}
span {
font-size: 1.1em;
text-align: start;
}
}
</style>
+2 -12
View File
@@ -21,25 +21,15 @@
</div> </div>
<div class="options-content"> <div class="options-content">
<transition name="card-anim"> <transition name="card-anim"></transition>
<OptionCard v-if="filterCardOpen" :exit="() => toggleCardsState('filter')" />
</transition>
<transition name="card-anim">
<LegendCard v-if="legendCardOpen" :exit="() => toggleCardsState('legend')" />
</transition>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Vue, Component } from "vue-property-decorator"; import { Vue, Component } from "vue-property-decorator";
import OptionCard from "@/components/StationsView/OptionCard.vue";
import LegendCard from "@/components/StationsView/LegendCard.vue";
@Component({ @Component({})
components: { OptionCard, LegendCard },
})
export default class Options extends Vue { export default class Options extends Vue {
filterCardOpen: boolean = false; filterCardOpen: boolean = false;
legendCardOpen: boolean = false; legendCardOpen: boolean = false;
+137 -132
View File
@@ -1,140 +1,142 @@
<template> <template>
<section class="station-table"> <section class="station-table">
<table class="table" v-if="stations.length != 0"> <div class="table-wrapper">
<thead class="table-head"> <table class="table">
<tr> <thead class="table-head">
<th v-for="(head, i) in headTitles" :key="i" @click="() => changeSorter(i)"> <tr>
<span> <th v-for="(head, i) in headTitles" :key="i" @click="() => changeSorter(i)">
<div> <span>
<div>{{head[0]}}</div> <div>
<div v-if="head.length > 1">{{head[1]}}</div> <div>{{head[0]}}</div>
</div> <div v-if="head.length > 1">{{head[1]}}</div>
</div>
<img <img
class="sort-icon" class="sort-icon"
v-if="sorterActive.index == i" v-if="sorterActive.index == i"
:src="sorterActive.dir == 1 ? icons.ascSVG : icons.descSVG" :src="sorterActive.dir == 1 ? icons.ascSVG : icons.descSVG"
alt alt
/> />
</span>
</th>
</tr>
</thead>
<tr
class="table-item"
v-for="(station, i) in stations"
:key="i + station.stationHash"
@click="() => { setFocusedStation(station.stationName) }"
>
<td
class="item-station-name"
:class="{'default-station': station.default, 'online': station.online}"
>{{station.stationName}}</td>
<td class="item-station-level">
<span
v-if="station.reqLevel"
:style="calculateExpStyle(station.reqLevel)"
>{{ (station.reqLevel && station.reqLevel > -1) ? (parseInt(station.reqLevel) >= 2 ? station.reqLevel : "L") : "?" }}</span>
<span v-else>?</span>
</td>
<td class="item-station-status">
<span class="status" :class="statusClasses(station.occupiedTo)">{{station.occupiedTo}}</span>
</td>
<td class="item-dispatcher-name">{{station.online ? station.dispatcherName : ""}}</td>
<td class="item-dispatcher-exp">
<span
v-if="station.online"
:style="calculateExpStyle(station.dispatcherExp)"
>{{2 > station.dispatcherExp ? 'L' : station.dispatcherExp}}</span>
</td>
<td
class="item-users"
>{{station.online ? (station.currentUsers + "/" + station.maxUsers) : ""}}</td>
<td class="item-info">
<img
class="icon-info"
v-if="station.controlType"
:src="require(`@/assets/icon-${station.controlType}.svg`)"
:alt="station.controlType"
:title="'Sterowanie ' + station.controlType"
/>
<img
class="icon-info"
v-if="station.signalType"
:src="require(`@/assets/icon-${station.signalType}.svg`)"
:alt="station.signalType"
:title="'Sygnalizacja ' + station.signalType"
/>
<img
class="icon-info"
v-if="station.SBL && station.SBL !== ''"
:src="require(`@/assets/icon-SBL.svg`)"
alt="SBL"
title="Sceneria posiada SBL na przynajmniej jednym ze szlaków"
/>
<img
class="icon-info"
v-if="!station.reqLevel || station.nonPublic"
:src="require(`@/assets/icon-lock.svg`)"
alt="non-public"
title="Sceneria niepubliczna"
/>
</td>
<td class="item-tracks twoway">
<span
v-if="station.routes && station.routes.twoWay.catenary > 0"
class="track catenary"
:title="'Liczba zelektryfikowanych szlaków dwutorowych: ' + station.routes.twoWay.catenary"
>{{station.routes.twoWay.catenary}}</span>
<span
v-if="station.routes && station.routes.twoWay.noCatenary > 0"
class="track no-catenary"
:title="'Liczba niezelektryfikowanych szlaków dwutorowych: ' + station.routes.twoWay.noCatenary"
>{{station.routes.twoWay.noCatenary}}</span>
<span class="separator"></span>
<span
v-if="station.routes && station.routes.oneWay.catenary > 0"
class="track catenary"
:title="'Liczba zelektryfikowanych szlaków jednotorowych: ' + station.routes.oneWay.catenary"
>{{station.routes.oneWay.catenary}}</span>
<span
v-if="station.routes && station.routes.oneWay.noCatenary > 0"
class="track no-catenary"
:title="'Liczba niezelektryfikowanych szlaków jednotorowych: ' + station.routes.oneWay.noCatenary"
>{{station.routes.oneWay.noCatenary}}</span>
</td>
<td class="active-timetables">
<transition name="change-anim" mode="out-in">
<span :key="station.scheduledTrains.length">
<span v-if="station.scheduledTrains">
<span style="color:#eee">{{ station.scheduledTrains.length}}</span>
/
<span
style="color:#bbb"
>{{ station.scheduledTrains.filter(train => train.confirmed).length }}</span>
</span> </span>
</th>
</tr>
</thead>
<span v-else>...</span> <tr
</span> class="table-item"
</transition> v-for="(station, i) in stations"
</td> :key="i + station.stationHash"
</tr> @click="() => { setFocusedStation(station.stationName) }"
</table> >
<div class="no-stations" v-else>Ups! Brak stacji do wyświetlenia!</div> <td
class="item-station-name"
:class="{'default-station': station.default, 'online': station.online}"
>{{station.stationName}}</td>
<td class="item-station-level">
<span
v-if="station.reqLevel"
:style="calculateExpStyle(station.reqLevel)"
>{{ (station.reqLevel && station.reqLevel > -1) ? (parseInt(station.reqLevel) >= 2 ? station.reqLevel : "L") : "?" }}</span>
<span v-else>?</span>
</td>
<td class="item-station-status">
<span class="status" :class="statusClasses(station.occupiedTo)">{{station.occupiedTo}}</span>
</td>
<td class="item-dispatcher-name">{{station.online ? station.dispatcherName : ""}}</td>
<td class="item-dispatcher-exp">
<span
v-if="station.online"
:style="calculateExpStyle(station.dispatcherExp)"
>{{2 > station.dispatcherExp ? 'L' : station.dispatcherExp}}</span>
</td>
<td
class="item-users"
>{{station.online ? (station.currentUsers + "/" + station.maxUsers) : ""}}</td>
<td class="item-info">
<img
class="icon-info"
v-if="station.controlType"
:src="require(`@/assets/icon-${station.controlType}.svg`)"
:alt="station.controlType"
:title="'Sterowanie ' + station.controlType"
/>
<img
class="icon-info"
v-if="station.signalType"
:src="require(`@/assets/icon-${station.signalType}.svg`)"
:alt="station.signalType"
:title="'Sygnalizacja ' + station.signalType"
/>
<img
class="icon-info"
v-if="station.SBL && station.SBL !== ''"
:src="require(`@/assets/icon-SBL.svg`)"
alt="SBL"
title="Sceneria posiada SBL na przynajmniej jednym ze szlaków"
/>
<img
class="icon-info"
v-if="!station.reqLevel || station.nonPublic"
:src="require(`@/assets/icon-lock.svg`)"
alt="non-public"
title="Sceneria niepubliczna"
/>
</td>
<td class="item-tracks twoway">
<span
v-if="station.routes && station.routes.twoWay.catenary > 0"
class="track catenary"
:title="'Liczba zelektryfikowanych szlaków dwutorowych: ' + station.routes.twoWay.catenary"
>{{station.routes.twoWay.catenary}}</span>
<span
v-if="station.routes && station.routes.twoWay.noCatenary > 0"
class="track no-catenary"
:title="'Liczba niezelektryfikowanych szlaków dwutorowych: ' + station.routes.twoWay.noCatenary"
>{{station.routes.twoWay.noCatenary}}</span>
<span class="separator"></span>
<span
v-if="station.routes && station.routes.oneWay.catenary > 0"
class="track catenary"
:title="'Liczba zelektryfikowanych szlaków jednotorowych: ' + station.routes.oneWay.catenary"
>{{station.routes.oneWay.catenary}}</span>
<span
v-if="station.routes && station.routes.oneWay.noCatenary > 0"
class="track no-catenary"
:title="'Liczba niezelektryfikowanych szlaków jednotorowych: ' + station.routes.oneWay.noCatenary"
>{{station.routes.oneWay.noCatenary}}</span>
</td>
<td class="active-timetables">
<transition name="change-anim" mode="out-in">
<span :key="station.scheduledTrains.length">
<span v-if="station.scheduledTrains">
<span style="color:#eee">{{ station.scheduledTrains.length}}</span>
/
<span
style="color:#bbb"
>{{ station.scheduledTrains.filter(train => train.confirmed).length }}</span>
</span>
<span v-else>...</span>
</span>
</transition>
</td>
</tr>
</table>
</div>
<div class="no-stations" v-if="stations.length == 0">Ups! Brak stacji do wyświetlenia!</div>
</section> </section>
</template> </template>
@@ -218,6 +220,9 @@ export default class StationTable extends styleMixin {
} }
.table { .table {
&-wrapper {
overflow: auto;
}
white-space: nowrap; white-space: nowrap;
border-collapse: collapse; border-collapse: collapse;
+2 -4
View File
@@ -1,6 +1,5 @@
{ {
"options": [ "options": [{
{
"id": "is-default", "id": "is-default",
"name": "default", "name": "default",
"section": "access", "section": "access",
@@ -113,8 +112,7 @@
"content": "KOŃCZY" "content": "KOŃCZY"
} }
], ],
"sliders": [ "sliders": [{
{
"id": "min-level", "id": "min-level",
"name": "minLevel", "name": "minLevel",
"minRange": 0, "minRange": 0,
+79 -167
View File
@@ -1,14 +1,14 @@
import { Module, VuexModule, Mutation, Action } from "vuex-module-decorators"; import { Module, VuexModule, Mutation, Action } from 'vuex-module-decorators';
import axios from "axios"; import axios from 'axios';
import data from "@/data/stations.json"; import data from '@/data/stations.json';
import Station from "@/scripts/interfaces/Station"; import Station from '@/scripts/interfaces/Station';
const stationsOnlineURL = const stationsOnlineURL =
"https://api.td2.info.pl:9640/?method=getStationsOnline"; 'https://api.td2.info.pl:9640/?method=getStationsOnline';
const trainsOnlineURL = "https://api.td2.info.pl:9640/?method=getTrainsOnline"; const trainsOnlineURL = 'https://api.td2.info.pl:9640/?method=getTrainsOnline';
const dispatchersOnlineURL = const dispatchersOnlineURL =
"https://api.td2.info.pl:9640/?method=readFromSWDR&value=getDispatcherStatusList%3B1"; 'https://api.td2.info.pl:9640/?method=readFromSWDR&value=getDispatcherStatusList%3B1';
enum ConnState { enum ConnState {
Loading = 0, Loading = 0,
@@ -18,14 +18,13 @@ enum ConnState {
interface TimetableResponseData { interface TimetableResponseData {
stopPoints: stopPoints:
| { {
arrivalTime: string; arrivalTime: string;
arrivalDelay: number; arrivalDelay: number;
departureTime: string; departureTime: string;
departureDelay: number; departureDelay: number;
pointNameRAW: string; pointNameRAW: string;
}[] }[];
| [];
trainInfo: { trainInfo: {
timetableId: number; timetableId: number;
trainCategoryCode: string; trainCategoryCode: string;
@@ -53,127 +52,54 @@ let onlineTrainsData: {
isOnline: number; isOnline: number;
region: string; region: string;
trainNo: number; trainNo: number;
station: { stationName: string }; station: {
stationName: string;
};
}[]; }[];
const filterInitStates = {
default: false,
notDefault: false,
nonPublic: false,
SPK: false,
SCS: false,
ręczne: false,
mechaniczne: false,
współczesna: false,
kształtowa: false,
historyczna: false,
mieszana: false,
minLevel: 0,
minOneWayCatenary: 0,
minOneWay: 0,
minTwoWayCatenary: 0,
minTwoWay: 0,
"no-1track": false,
"no-2track": false,
free: true,
occupied: false,
ending: false,
};
const queryStations = axios.get(stationsOnlineURL); const queryStations = axios.get(stationsOnlineURL);
const queryTrains = axios.get(trainsOnlineURL); const queryTrains = axios.get(trainsOnlineURL);
const queryDispatchers = axios.get(dispatchersOnlineURL); const queryDispatchers = axios.get(dispatchersOnlineURL);
const getStationLabel = (stationStatus: any) => { const getStationLabel = (stationStatus: any) => {
if (!stationStatus) return "NIEZALOGOWANY"; if (!stationStatus) return 'NIEZALOGOWANY';
const statusCode = stationStatus[2]; const statusCode = stationStatus[2];
const statusTimestamp = stationStatus[3]; const statusTimestamp = stationStatus[3];
switch (statusCode) { switch (statusCode) {
case 0: case 0:
if (statusTimestamp - Date.now() > 21000000) return "BEZ LIMITU"; if (statusTimestamp - Date.now() > 21000000) return 'BEZ LIMITU';
return `DO ${new Date(statusTimestamp).toLocaleTimeString("en-US", { return `DO ${new Date(statusTimestamp).toLocaleTimeString('en-US', {
hour12: false, hour12: false,
hour: "2-digit", hour: '2-digit',
minute: "2-digit", minute: '2-digit',
})}`; })}`;
case 1: case 1:
return "Z/W"; return 'Z/W';
case 2: case 2:
if (statusTimestamp == 0) return "KOŃCZY"; if (statusTimestamp == 0) return 'KOŃCZY';
break; break;
case 3: case 3:
return "BRAK MIEJSCA"; return 'BRAK MIEJSCA';
default: default:
break; break;
} }
return "NIEDOSTĘPNY"; return 'NIEDOSTĘPNY';
}; };
const getOpenSpawns = (spawnString: string) => { const getOpenSpawns = (spawnString: string) => {
if (!spawnString) return ""; if (!spawnString) return '';
return spawnString return spawnString
.split(";") .split(';')
.map((v) => (v.split(",")[6] ? v.split(",")[6] : v.split(",")[0])); .map(v => (v.split(',')[6] ? v.split(',')[6] : v.split(',')[0]));
};
const filterStations = (stations, filters) => {
return stations.filter((station) => {
if ((station.nonPublic || !station.reqLevel) && filters["nonPublic"])
return false;
if (!station.reqLevel || station.reqLevel == "-1") return true;
if (station.online && station.occupiedTo == "KOŃCZY" && filters["ending"])
return false;
if (station.online && filters["occupied"]) return false;
if (!station.online && filters["free"]) return false;
if (station.default && filters["default"]) return false;
if (!station.default && filters["notDefault"]) return false;
if (station.reqLevel < filters["minLevel"]) return false;
if (
filters["no-1track"] &&
(station.routes.oneWay.catenary != 0 ||
station.routes.oneWay.noCatenary != 0)
)
return false;
if (
filters["no-2track"] &&
(station.routes.twoWay.catenary != 0 ||
station.routes.twoWay.noCatenary != 0)
)
return false;
if (station.routes.oneWay.catenary < filters["minOneWayCatenary"])
return false;
if (station.routes.oneWay.noCatenary < filters["minOneWay"]) return false;
if (station.routes.twoWay.catenary < filters["minTwoWayCatenary"])
return false;
if (station.routes.twoWay.noCatenary < filters["minTwoWay"]) return false;
if (filters[station.controlType]) return false;
if (filters[station.signalType]) return false;
if (filters["SPK"] && station.controlType.includes("SPK")) return false;
if (filters["SCS"] && station.controlType.includes("SCS")) return false;
if (filters["mechaniczne"] && station.controlType.includes("mechaniczne"))
return false;
if (filters["ręczne"] && station.controlType.includes("ręczne"))
return false;
return true;
});
}; };
@Module @Module
@@ -184,24 +110,20 @@ export default class StationsModule extends VuexModule {
private stationsConnectionState: ConnState = ConnState.Loading; private stationsConnectionState: ConnState = ConnState.Loading;
private stations: Station[] = []; private stations: Station[] = [];
private filteredStations: {}[] = [];
private filters: any = { ...filterInitStates };
get getConnectionState() { get getConnectionState() {
return this.stationsConnectionState; return this.stationsConnectionState;
} }
get getOnlineInfo() { get getOnlineInfo() {
return { trainCount: this.trainCount, stationCount: this.stationCount }; return {
trainCount: this.trainCount,
stationCount: this.stationCount,
};
} }
get getStationList() { get getStationList() {
return this.filteredStations; return this.stations;
}
get getFilters() {
return this.filters;
} }
@Mutation @Mutation
@@ -213,22 +135,22 @@ export default class StationsModule extends VuexModule {
private updateStations(updatedStations) { private updateStations(updatedStations) {
this.stations = this.stations.reduce((acc, station) => { this.stations = this.stations.reduce((acc, station) => {
const onlineStationData = updatedStations.find( const onlineStationData = updatedStations.find(
(uStation) => uStation.stationName === station.stationName uStation => uStation.stationName === station.stationName
); );
if (!onlineStationData) { if (!onlineStationData) {
acc.push({ acc.push({
...station, ...station,
stationProject: "", stationProject: '',
spawnString: "", spawnString: '',
stationHash: "", stationHash: '',
maxUsers: 0, maxUsers: 0,
currentUsers: 0, currentUsers: 0,
dispatcherName: "", dispatcherName: '',
dispatcherRate: 0, dispatcherRate: 0,
dispatcherExp: -1, dispatcherExp: -1,
dispatcherId: 0, dispatcherId: 0,
occupiedTo: "WOLNA", occupiedTo: 'WOLNA',
statusTimestamp: 0, statusTimestamp: 0,
scheduledTrains: [], scheduledTrains: [],
online: false, online: false,
@@ -237,7 +159,11 @@ export default class StationsModule extends VuexModule {
return acc; return acc;
} }
acc.push({ ...station, ...onlineStationData, online: true }); acc.push({
...station,
...onlineStationData,
online: true,
});
// updatedStations = updatedStations.filter( // updatedStations = updatedStations.filter(
// (updated: any) => updated.stationName !== station.stationName // (updated: any) => updated.stationName !== station.stationName
@@ -249,66 +175,48 @@ export default class StationsModule extends VuexModule {
// Dodawanie do listy online potencjalnych scenerii niewpisanych do bazy // Dodawanie do listy online potencjalnych scenerii niewpisanych do bazy
updatedStations.forEach((updated: any) => { updatedStations.forEach((updated: any) => {
const alreadyInList: any = this.stations.find( const alreadyInList: any = this.stations.find(
(station) => station.stationName === updated.stationName station => station.stationName === updated.stationName
); );
if (!alreadyInList) { if (!alreadyInList) {
this.stations.push({ ...updated, online: true, reqLevel: "-1" }); this.stations.push({
...updated,
online: true,
reqLevel: '-1',
});
} }
}); });
this.filteredStations = filterStations(this.stations, this.filters); this.stationCount = this.stations.filter(station => station.online).length;
this.stationCount = this.stations.filter(
(station) => station.online
).length;
this.trainCount = onlineTrainsData.filter( this.trainCount = onlineTrainsData.filter(
(train) => train.isOnline && train.region === "eu" train => train.isOnline && train.region === 'eu'
).length; ).length;
this.stationsConnectionState = ConnState.Connected; this.stationsConnectionState = ConnState.Connected;
} }
@Mutation
private resetFilterList() {
this.filters = { ...filterInitStates };
this.filteredStations = filterStations(this.stations, this.filters);
}
@Mutation
private changeFilter({ filterName, value }) {
this.filters[filterName] = value;
this.filteredStations = filterStations(this.stations, this.filters);
}
@Mutation @Mutation
private mutateStations(stations) { private mutateStations(stations) {
this.stations = stations; this.stations = stations;
} }
@Action({ commit: "changeFilter" }) @Action({
setFilter(payload: { filterName: string; value: number | boolean }) { commit: 'mutateStations',
return payload; })
}
@Action({ commit: "resetFilterList" })
resetFilters() {}
@Action({ commit: "mutateStations" })
async loadStations() { async loadStations() {
return await data.map((stationData) => ({ return await data.map(stationData => ({
stationProject: "", stationProject: '',
spawnString: "", spawnString: '',
stationHash: "", stationHash: '',
maxUsers: 0, maxUsers: 0,
currentUsers: 0, currentUsers: 0,
dispatcherName: "", dispatcherName: '',
dispatcherRate: 0, dispatcherRate: 0,
dispatcherExp: -1, dispatcherExp: -1,
dispatcherId: 0, dispatcherId: 0,
online: false, online: false,
occupiedTo: "WOLNA", occupiedTo: 'WOLNA',
statusTimestamp: 0, statusTimestamp: 0,
scheduledTrains: [], scheduledTrains: [],
...stationData, ...stationData,
@@ -317,43 +225,47 @@ export default class StationsModule extends VuexModule {
@Action @Action
async initStations() { async initStations() {
this.context.dispatch("loadStations"); this.context.dispatch('loadStations');
this.context.dispatch("fetchOnlineStations"); this.context.dispatch('fetchOnlineStations');
} }
@Action({ commit: "updateStations" }) @Action({
commit: 'updateStations',
})
async fetchOnlineStations() { async fetchOnlineStations() {
return await Promise.all([ return await Promise.all([
axios.get(stationsOnlineURL), axios.get(stationsOnlineURL),
axios.get(trainsOnlineURL), axios.get(trainsOnlineURL),
axios.get(dispatchersOnlineURL), axios.get(dispatchersOnlineURL),
]) ])
.then(async (response) => { .then(async response => {
onlineStationsData = response[0].data.message; onlineStationsData = response[0].data.message;
onlineTrainsData = await response[1].data.message; onlineTrainsData = await response[1].data.message;
onlineDispatchersData = await response[2].data.message; onlineDispatchersData = await response[2].data.message;
const updatedStations = await Promise.all( const updatedStations = await Promise.all(
onlineStationsData onlineStationsData
.filter((station) => station.region === "eu" && station.isOnline) .filter(station => station.region === 'eu' && station.isOnline)
.map(async (station) => { .map(async station => {
const stationStatus = onlineDispatchersData.find( const stationStatus = onlineDispatchersData.find(
(status) => status => status[0] == station.stationHash && status[1] == 'eu'
status[0] == station.stationHash && status[1] == "eu"
); );
const statusLabel = getStationLabel(stationStatus); const statusLabel = getStationLabel(stationStatus);
const statusTimestamp = stationStatus ? stationStatus[3] : -1; const statusTimestamp = stationStatus ? stationStatus[3] : -1;
const trains = onlineTrainsData.filter( const trains = onlineTrainsData.filter(
(train) => train =>
train.region === "eu" && train.region === 'eu' &&
train.isOnline && train.isOnline &&
train.station.stationName === station.stationName train.station.stationName === station.stationName
); );
const stationData = data.find( const stationData = data.find(
(s) => s.stationName === station.stationName s => s.stationName === station.stationName
) || { stationName: station.stationName, stationURL: "" }; ) || {
stationName: station.stationName,
stationURL: '',
};
return { return {
...stationData, ...stationData,
@@ -374,7 +286,7 @@ export default class StationsModule extends VuexModule {
return updatedStations; return updatedStations;
}) })
.catch(() => { .catch(() => {
this.context.commit("setConnectionState", ConnState.Error); this.context.commit('setConnectionState', ConnState.Error);
}); });
} }
} }
+214 -56
View File
@@ -1,15 +1,20 @@
<template> <template>
<div class="stations-view"> <div class="stations-view">
<Loading v-if="connectionState == 0" message="Ładowanie scenerii..." /> <div class="stations-wrapper">
<Error v-if="connectionState == 1" />
<transition name="card-anim">
<StationCard v-if="focusedStationInfo" :stationInfo="focusedStationInfo" :exit="closeCard" />
</transition>
<div class="stations-wrapper" v-if="connectionState == 2">
<div class="stations-body"> <div class="stations-body">
<Options /> <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>
</div>
</div>
<StationTable <StationTable
:stations="computedStations" :stations="computedStations"
:sorterActive="sorterActive" :sorterActive="sorterActive"
@@ -18,54 +23,84 @@
/> />
</div> </div>
</div> </div>
<transition name="card-anim">
<StationCard v-if="focusedStationInfo" :stationInfo="focusedStationInfo" :exit="closeCard" />
</transition>
<transition name="card-anim">
<FilterCard
v-if="filterCardOpen"
:exit="() => toggleCardsState('filter')"
@changeFilterValue="changeFilterValue"
@resetFilters="resetFilters"
/>
</transition>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Vue, Component } from "vue-property-decorator"; import { Vue, Component } from "vue-property-decorator";
import { Getter, Action } from "vuex-class"; import { Getter } from "vuex-class";
import Station from "@/scripts/interfaces/Station"; import Station from "@/scripts/interfaces/Station";
import Train from "@/scripts/interfaces/Train"; import Train from "@/scripts/interfaces/Train";
import inputData from "@/data/options.json"; import inputData from "@/data/options.json";
import Loading from "@/components/App/Loading.vue";
import Error from "@/components/App/Error.vue";
import StationTable from "@/components/StationsView/StationTable.vue"; import StationTable from "@/components/StationsView/StationTable.vue";
import StationCard from "@/components/StationsView/StationCard.vue"; import StationCard from "@/components/StationsView/StationCard.vue";
import Options from "@/components/StationsView/Options.vue"; import FilterCard from "@/components/StationsView/FilterCard.vue";
enum ConnState { const filterInitStates = {
Loading = 0, default: false,
Error = 1, notDefault: false,
Connected = 2, nonPublic: false,
} SPK: false,
SCS: false,
ręczne: false,
mechaniczne: false,
współczesna: false,
kształtowa: false,
historyczna: false,
mieszana: false,
minLevel: 0,
minOneWayCatenary: 0,
minOneWay: 0,
minTwoWayCatenary: 0,
minTwoWay: 0,
"no-1track": false,
"no-2track": false,
free: true,
occupied: false,
ending: false,
};
@Component({ @Component({
components: { components: {
StationCard, StationCard,
StationTable, StationTable,
Loading, FilterCard,
Error,
Options,
}, },
}) })
export default class StationsView extends Vue { export default class StationsView extends Vue {
focusedStationName: string = "";
inputs = { ...inputData };
STORAGE_KEY: string = "options_saved"; STORAGE_KEY: string = "options_saved";
@Getter("getStationList") stations!: Station[];
@Getter("getConnectionState") connectionState!: ConnState;
@Getter("trainsDataList") trains!: Train[];
@Getter("trainsDataState") trainsDataState!: number;
@Action("setFilter") setFilter;
sorterActive: { index: number; dir: number } = { index: 0, dir: 1 }; sorterActive: { index: number; dir: number } = { index: 0, dir: 1 };
focusedStationName: string = "";
filterCardOpen: boolean = false;
filters = { ...filterInitStates };
inputs = inputData;
@Getter("getStationList") stations!: Station[];
@Getter("trainsDataList") trains!: Train[];
toggleCardsState(name: string): void {
if (name == "filter") {
this.filterCardOpen = !this.filterCardOpen;
}
}
changeSorter(index: number) { changeSorter(index: number) {
if (index > 5) return; if (index > 5) return;
@@ -77,6 +112,14 @@ export default class StationsView extends Vue {
this.sorterActive.index = index; this.sorterActive.index = index;
} }
changeFilterValue(filter: { name: string; value: number }) {
this.filters[filter.name] = filter.value;
}
resetFilters() {
this.filters = { ...filterInitStates };
}
get scheduledTrains() { get scheduledTrains() {
const reducedList = this.stations.reduce((acc, station) => { const reducedList = this.stations.reduce((acc, station) => {
if (!acc[station.stationName]) acc[station.stationName] = []; if (!acc[station.stationName]) acc[station.stationName] = [];
@@ -120,6 +163,74 @@ export default class StationsView extends Vue {
const scheduledTrainList = this.scheduledTrains; const scheduledTrainList = this.scheduledTrains;
return this.stations return this.stations
.filter((station) => {
if (!station.reqLevel || station.reqLevel == "-1") return true;
if (
(station.nonPublic || !station.reqLevel) &&
this.filters["nonPublic"]
)
return false;
if (
station.online &&
station.occupiedTo == "KOŃCZY" &&
this.filters["ending"]
)
return false;
if (station.online && this.filters["occupied"]) return false;
if (!station.online && this.filters["free"]) return false;
if (station.default && this.filters["default"]) return false;
if (!station.default && this.filters["notDefault"]) return false;
if (parseInt(station.reqLevel) < this.filters["minLevel"]) return false;
if (
this.filters["no-1track"] &&
(station.routes.oneWay.catenary != 0 ||
station.routes.oneWay.noCatenary != 0)
)
return false;
if (
this.filters["no-2track"] &&
(station.routes.twoWay.catenary != 0 ||
station.routes.twoWay.noCatenary != 0)
)
return false;
if (station.routes.oneWay.catenary < this.filters["minOneWayCatenary"])
return false;
if (station.routes.oneWay.noCatenary < this.filters["minOneWay"])
return false;
if (station.routes.twoWay.catenary < this.filters["minTwoWayCatenary"])
return false;
if (station.routes.twoWay.noCatenary < this.filters["minTwoWay"])
return false;
if (this.filters[station.controlType]) return false;
if (this.filters[station.signalType]) return false;
if (this.filters["SPK"] && (station.controlType === "SPK" || station.controlType.includes("+SPK")))
return false;
if (this.filters["SCS"] && station.controlType === "SCS" || station.controlType.includes("+SCS"))
return false;
if (this.filters["SCS"] && this.filters["SPK"] && (station.controlType.includes("SPK") || station.controlType.includes("SCS")))
return false;
if (
this.filters["mechaniczne"] &&
station.controlType.includes("mechaniczne")
)
return false;
if (this.filters["ręczne"] && station.controlType.includes("ręczne"))
return false;
return true;
})
.sort((a, b) => { .sort((a, b) => {
switch (this.sorterActive.index) { switch (this.sorterActive.index) {
case 1: case 1:
@@ -164,39 +275,26 @@ export default class StationsView extends Vue {
mounted() { mounted() {
const storage = window.localStorage; const storage = window.localStorage;
console.log(storage.getItem(this.STORAGE_KEY));
if (storage.getItem(this.STORAGE_KEY) !== "true") return; if (storage.getItem(this.STORAGE_KEY) !== "true") return;
this.inputs.options.forEach((input) => { this.inputs.options.forEach(option => {
if (storage.getItem(input.name) === "true") { const value = storage.getItem(option.name) === "true" ? true : false;
this.setFilter({ console.log(option.name, value);
filterName: input.name,
value: false,
});
input.value = true;
} else if (storage.getItem(input.name) === "false") {
this.setFilter({
filterName: input.name,
value: true,
});
input.value = false; this.changeFilterValue({ name: option.name, value: value ? 0 : 1 });
} option.value = value;
}); })
this.inputs.sliders.forEach((slider) => { this.inputs.sliders.forEach(slider => {
const value = parseInt( const value = parseInt(storage.getItem(slider.name) || "0");
window.localStorage.getItem(slider.name) as string
);
this.setFilter({
filterName: slider.name,
value,
});
this.changeFilterValue({ name: slider.name, value });
slider.value = value; slider.value = value;
}); })
} }
closeCard() { closeCard() {
@@ -220,6 +318,8 @@ export default class StationsView extends Vue {
.stations-view { .stations-view {
padding: 1rem 0; padding: 1rem 0;
min-height: 100%; min-height: 100%;
position: relative;
} }
@import "../styles/variables.scss"; @import "../styles/variables.scss";
@@ -238,6 +338,64 @@ export default class StationsView extends Vue {
} }
} }
.options {
font-size: calc(0.6rem + 0.9vw);
&-actions {
display: flex;
}
}
.action-btn {
display: flex;
align-items: center;
background: #333;
border: none;
color: #e0e0e0;
font-size: 0.75em;
padding: 0.3em;
outline: none;
cursor: pointer;
transition: all 0.3s;
img {
width: 1.3em;
margin-right: 0.2em;
}
p {
font-size: 1em;
overflow: hidden;
transition: max-width 0.35s ease-in-out;
}
&:hover {
color: $accentCol;
background: rgba(#e0e0e0, 0.4);
}
&.open {
color: $accentCol;
}
}
@include smallScreen {
.options {
display: flex;
justify-content: center;
}
.action-btn {
font-size: 0.8rem;
}
}
.stations-wrapper { .stations-wrapper {
display: flex; display: flex;
justify-content: center; justify-content: center;
+2 -9
View File
@@ -1,8 +1,6 @@
<template> <template>
<section class="trains-view"> <section class="trains-view">
<Loading v-if="connectionState == 0" message="Liczenie pociągów..." /> <div class="body-wrapper">
<div class="body-wrapper" v-else>
<div class="options-wrapper"> <div class="options-wrapper">
<TrainSorter :trainList="computedTrains" @changeSorter="changeSorter" /> <TrainSorter :trainList="computedTrains" @changeSorter="changeSorter" />
<TrainSearch <TrainSearch
@@ -25,8 +23,6 @@ import { Getter, Action } from "vuex-class";
import Station from "@/scripts/interfaces/Station"; import Station from "@/scripts/interfaces/Station";
import Train from "@/scripts/interfaces/Train"; import Train from "@/scripts/interfaces/Train";
import Loading from "@/components/App/Loading.vue";
import TrainSorter from "@/components/TrainsView/TrainSorter.vue"; import TrainSorter from "@/components/TrainsView/TrainSorter.vue";
import TrainSearch from "@/components/TrainsView/TrainSearch.vue"; import TrainSearch from "@/components/TrainsView/TrainSearch.vue";
import TrainTable from "@/components/TrainsView/TrainTable.vue"; import TrainTable from "@/components/TrainsView/TrainTable.vue";
@@ -36,7 +32,6 @@ import axios from "axios";
@Component({ @Component({
components: { components: {
Loading,
TrainSorter, TrainSorter,
TrainTable, TrainTable,
TrainStats, TrainStats,
@@ -46,8 +41,6 @@ import axios from "axios";
export default class TrainsView extends Vue { export default class TrainsView extends Vue {
@Getter("trainsDataList") trains!: Train[]; @Getter("trainsDataList") trains!: Train[];
@Getter("trainsDataState") connectionState;
@Action("fetchTrainsData") fetchTrainsData; @Action("fetchTrainsData") fetchTrainsData;
sorterActive: { id: string; dir: number } = { id: "timetable", dir: 1 }; sorterActive: { id: string; dir: number } = { id: "timetable", dir: 1 };
@@ -121,8 +114,8 @@ export default class TrainsView extends Vue {
@import "../styles/responsive.scss"; @import "../styles/responsive.scss";
.trains-view { .trains-view {
position: relative;
min-height: 100%; min-height: 100%;
position: relative;
} }
.body-wrapper { .body-wrapper {