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">
<span>&copy; Spythere 2020</span>
</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>
</template>
@@ -48,16 +59,16 @@ import { Action, Getter } from "vuex-class";
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";
@Component({
components: { Error, Loading, Clock },
components: { Clock },
})
export default class App extends Vue {
ErrorIcon = require("@/assets/icon-error.svg");
@Getter("getOnlineInfo") onlineInfo;
@Getter("getConnectionState") connState;
@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 {
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>
<section class="option-card">
<section class="filter-card">
<div class="card-exit" @click="exit">
<img :src="require('@/assets/icon-exit.svg')" alt="exit icon" />
</div>
@@ -18,7 +18,10 @@
v-model="option.value"
@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>
</div>
</div>
@@ -45,13 +48,8 @@
<div class="card-save">
<div class="option">
<label class="option-label">
<input
class="option-input"
type="checkbox"
v-model="saveOptions"
@change="changeSaveState"
/>
<span class="option-content save">ZAPISZ FILTRY</span>
<input class="option-input" type="checkbox" v-model="saveOptions" @change="saveFilters" />
<span class="option-content save" :class="{'checked': saveOptions}">ZAPISZ FILTRY</span>
</label>
</div>
</div>
@@ -65,22 +63,17 @@
<script lang="ts">
import { Vue, Component, Prop } from "vue-property-decorator";
import { Action } from "vuex-class";
import inputData from "@/data/options.json";
@Component
export default class OptionCard extends Vue {
export default class FilterCard extends Vue {
inputs = { ...inputData };
saveOptions: boolean = false;
STORAGE_KEY: string = "options_saved";
@Prop() exit!: () => void;
@Action("setFilter") setFilter;
@Action("resetFilters") resetFilters;
mounted() {
const storage = window.localStorage;
@@ -91,8 +84,8 @@ export default class OptionCard extends Vue {
handleChange(e: Event): void {
const target = <HTMLInputElement>e.target;
this.setFilter({
filterName: target.name,
this.$emit("changeFilterValue", {
name: target.name,
value: !target.checked,
});
@@ -102,17 +95,16 @@ export default class OptionCard extends Vue {
handleInput(e: Event): void {
const target = <HTMLInputElement>e.target;
this.setFilter({
filterName: target.name,
value: parseInt(target.value),
this.$emit("changeFilterValue", {
name: target.name,
value: target.value,
});
if (this.saveOptions)
window.localStorage.setItem(target.name, target.value + "");
}
changeSaveState(): void {
saveFilters(): void {
const storage = window.localStorage;
if (this.saveOptions) {
@@ -139,7 +131,7 @@ export default class OptionCard extends Vue {
window.localStorage.setItem(slider.name, slider.value + "");
});
this.resetFilters();
this.$emit('resetFilters');
}
closeCard(): void {
@@ -152,7 +144,7 @@ export default class OptionCard extends Vue {
@import "../../styles/responsive";
@import "../../styles/variables";
.option-card {
.filter-card {
position: fixed;
top: 50%;
left: 50%;
@@ -235,14 +227,6 @@ export default class OptionCard extends Vue {
&-input {
display: none;
&:not(:checked) + span {
background-color: #585858;
&::before {
box-shadow: none;
}
}
}
&-content {
@@ -267,57 +251,67 @@ export default class OptionCard extends Vue {
transition: all 0.2s;
&.access {
background-color: #e03b07;
&:not(.checked) {
background-color: #585858;
&::before {
box-shadow: 0 0 6px 1px #e03b07;
box-shadow: none;
}
}
&.control {
background-color: #0085ff;
&.checked {
&.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 {
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 class="options-content">
<transition name="card-anim">
<OptionCard v-if="filterCardOpen" :exit="() => toggleCardsState('filter')" />
</transition>
<transition name="card-anim">
<LegendCard v-if="legendCardOpen" :exit="() => toggleCardsState('legend')" />
</transition>
<transition name="card-anim"></transition>
</div>
</div>
</template>
<script lang="ts">
import { Vue, Component } from "vue-property-decorator";
import OptionCard from "@/components/StationsView/OptionCard.vue";
import LegendCard from "@/components/StationsView/LegendCard.vue";
@Component({
components: { OptionCard, LegendCard },
})
@Component({})
export default class Options extends Vue {
filterCardOpen: boolean = false;
legendCardOpen: boolean = false;
+137 -132
View File
@@ -1,140 +1,142 @@
<template>
<section class="station-table">
<table class="table" v-if="stations.length != 0">
<thead class="table-head">
<tr>
<th v-for="(head, i) in headTitles" :key="i" @click="() => changeSorter(i)">
<span>
<div>
<div>{{head[0]}}</div>
<div v-if="head.length > 1">{{head[1]}}</div>
</div>
<div class="table-wrapper">
<table class="table">
<thead class="table-head">
<tr>
<th v-for="(head, i) in headTitles" :key="i" @click="() => changeSorter(i)">
<span>
<div>
<div>{{head[0]}}</div>
<div v-if="head.length > 1">{{head[1]}}</div>
</div>
<img
class="sort-icon"
v-if="sorterActive.index == i"
:src="sorterActive.dir == 1 ? icons.ascSVG : icons.descSVG"
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>
<img
class="sort-icon"
v-if="sorterActive.index == i"
:src="sorterActive.dir == 1 ? icons.ascSVG : icons.descSVG"
alt
/>
</span>
</th>
</tr>
</thead>
<span v-else>...</span>
</span>
</transition>
</td>
</tr>
</table>
<div class="no-stations" v-else>Ups! Brak stacji do wyświetlenia!</div>
<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 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>
</template>
@@ -218,6 +220,9 @@ export default class StationTable extends styleMixin {
}
.table {
&-wrapper {
overflow: auto;
}
white-space: nowrap;
border-collapse: collapse;
+3 -5
View File
@@ -1,6 +1,5 @@
{
"options": [
{
"options": [{
"id": "is-default",
"name": "default",
"section": "access",
@@ -113,8 +112,7 @@
"content": "KOŃCZY"
}
],
"sliders": [
{
"sliders": [{
"id": "min-level",
"name": "minLevel",
"minRange": 0,
@@ -160,4 +158,4 @@
"content": "SZLAKI DWUTOROWE NIEZELEKTR. (MINIMUM)"
}
]
}
}
+79 -167
View File
@@ -1,14 +1,14 @@
import { Module, VuexModule, Mutation, Action } from "vuex-module-decorators";
import axios from "axios";
import { Module, VuexModule, Mutation, Action } from 'vuex-module-decorators';
import axios from 'axios';
import data from "@/data/stations.json";
import Station from "@/scripts/interfaces/Station";
import data from '@/data/stations.json';
import Station from '@/scripts/interfaces/Station';
const stationsOnlineURL =
"https://api.td2.info.pl:9640/?method=getStationsOnline";
const trainsOnlineURL = "https://api.td2.info.pl:9640/?method=getTrainsOnline";
'https://api.td2.info.pl:9640/?method=getStationsOnline';
const trainsOnlineURL = 'https://api.td2.info.pl:9640/?method=getTrainsOnline';
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 {
Loading = 0,
@@ -18,14 +18,13 @@ enum ConnState {
interface TimetableResponseData {
stopPoints:
| {
arrivalTime: string;
arrivalDelay: number;
departureTime: string;
departureDelay: number;
pointNameRAW: string;
}[]
| [];
{
arrivalTime: string;
arrivalDelay: number;
departureTime: string;
departureDelay: number;
pointNameRAW: string;
}[];
trainInfo: {
timetableId: number;
trainCategoryCode: string;
@@ -53,127 +52,54 @@ let onlineTrainsData: {
isOnline: number;
region: string;
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 queryTrains = axios.get(trainsOnlineURL);
const queryDispatchers = axios.get(dispatchersOnlineURL);
const getStationLabel = (stationStatus: any) => {
if (!stationStatus) return "NIEZALOGOWANY";
if (!stationStatus) return 'NIEZALOGOWANY';
const statusCode = stationStatus[2];
const statusTimestamp = stationStatus[3];
switch (statusCode) {
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,
hour: "2-digit",
minute: "2-digit",
hour: '2-digit',
minute: '2-digit',
})}`;
case 1:
return "Z/W";
return 'Z/W';
case 2:
if (statusTimestamp == 0) return "KOŃCZY";
if (statusTimestamp == 0) return 'KOŃCZY';
break;
case 3:
return "BRAK MIEJSCA";
return 'BRAK MIEJSCA';
default:
break;
}
return "NIEDOSTĘPNY";
return 'NIEDOSTĘPNY';
};
const getOpenSpawns = (spawnString: string) => {
if (!spawnString) return "";
if (!spawnString) return '';
return spawnString
.split(";")
.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;
});
.split(';')
.map(v => (v.split(',')[6] ? v.split(',')[6] : v.split(',')[0]));
};
@Module
@@ -184,24 +110,20 @@ export default class StationsModule extends VuexModule {
private stationsConnectionState: ConnState = ConnState.Loading;
private stations: Station[] = [];
private filteredStations: {}[] = [];
private filters: any = { ...filterInitStates };
get getConnectionState() {
return this.stationsConnectionState;
}
get getOnlineInfo() {
return { trainCount: this.trainCount, stationCount: this.stationCount };
return {
trainCount: this.trainCount,
stationCount: this.stationCount,
};
}
get getStationList() {
return this.filteredStations;
}
get getFilters() {
return this.filters;
return this.stations;
}
@Mutation
@@ -213,22 +135,22 @@ export default class StationsModule extends VuexModule {
private updateStations(updatedStations) {
this.stations = this.stations.reduce((acc, station) => {
const onlineStationData = updatedStations.find(
(uStation) => uStation.stationName === station.stationName
uStation => uStation.stationName === station.stationName
);
if (!onlineStationData) {
acc.push({
...station,
stationProject: "",
spawnString: "",
stationHash: "",
stationProject: '',
spawnString: '',
stationHash: '',
maxUsers: 0,
currentUsers: 0,
dispatcherName: "",
dispatcherName: '',
dispatcherRate: 0,
dispatcherExp: -1,
dispatcherId: 0,
occupiedTo: "WOLNA",
occupiedTo: 'WOLNA',
statusTimestamp: 0,
scheduledTrains: [],
online: false,
@@ -237,7 +159,11 @@ export default class StationsModule extends VuexModule {
return acc;
}
acc.push({ ...station, ...onlineStationData, online: true });
acc.push({
...station,
...onlineStationData,
online: true,
});
// updatedStations = updatedStations.filter(
// (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
updatedStations.forEach((updated: any) => {
const alreadyInList: any = this.stations.find(
(station) => station.stationName === updated.stationName
station => station.stationName === updated.stationName
);
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(
(train) => train.isOnline && train.region === "eu"
train => train.isOnline && train.region === 'eu'
).length;
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
private mutateStations(stations) {
this.stations = stations;
}
@Action({ commit: "changeFilter" })
setFilter(payload: { filterName: string; value: number | boolean }) {
return payload;
}
@Action({ commit: "resetFilterList" })
resetFilters() {}
@Action({ commit: "mutateStations" })
@Action({
commit: 'mutateStations',
})
async loadStations() {
return await data.map((stationData) => ({
stationProject: "",
spawnString: "",
stationHash: "",
return await data.map(stationData => ({
stationProject: '',
spawnString: '',
stationHash: '',
maxUsers: 0,
currentUsers: 0,
dispatcherName: "",
dispatcherName: '',
dispatcherRate: 0,
dispatcherExp: -1,
dispatcherId: 0,
online: false,
occupiedTo: "WOLNA",
occupiedTo: 'WOLNA',
statusTimestamp: 0,
scheduledTrains: [],
...stationData,
@@ -317,43 +225,47 @@ export default class StationsModule extends VuexModule {
@Action
async initStations() {
this.context.dispatch("loadStations");
this.context.dispatch("fetchOnlineStations");
this.context.dispatch('loadStations');
this.context.dispatch('fetchOnlineStations');
}
@Action({ commit: "updateStations" })
@Action({
commit: 'updateStations',
})
async fetchOnlineStations() {
return await Promise.all([
axios.get(stationsOnlineURL),
axios.get(trainsOnlineURL),
axios.get(dispatchersOnlineURL),
])
.then(async (response) => {
.then(async response => {
onlineStationsData = response[0].data.message;
onlineTrainsData = await response[1].data.message;
onlineDispatchersData = await response[2].data.message;
const updatedStations = await Promise.all(
onlineStationsData
.filter((station) => station.region === "eu" && station.isOnline)
.map(async (station) => {
.filter(station => station.region === 'eu' && station.isOnline)
.map(async station => {
const stationStatus = onlineDispatchersData.find(
(status) =>
status[0] == station.stationHash && status[1] == "eu"
status => status[0] == station.stationHash && status[1] == 'eu'
);
const statusLabel = getStationLabel(stationStatus);
const statusTimestamp = stationStatus ? stationStatus[3] : -1;
const trains = onlineTrainsData.filter(
(train) =>
train.region === "eu" &&
train =>
train.region === 'eu' &&
train.isOnline &&
train.station.stationName === station.stationName
);
const stationData = data.find(
(s) => s.stationName === station.stationName
) || { stationName: station.stationName, stationURL: "" };
s => s.stationName === station.stationName
) || {
stationName: station.stationName,
stationURL: '',
};
return {
...stationData,
@@ -374,7 +286,7 @@ export default class StationsModule extends VuexModule {
return updatedStations;
})
.catch(() => {
this.context.commit("setConnectionState", ConnState.Error);
this.context.commit('setConnectionState', ConnState.Error);
});
}
}
+214 -56
View File
@@ -1,15 +1,20 @@
<template>
<div class="stations-view">
<Loading v-if="connectionState == 0" message="Ładowanie scenerii..." />
<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-wrapper">
<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
:stations="computedStations"
:sorterActive="sorterActive"
@@ -18,54 +23,84 @@
/>
</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>
</template>
<script lang="ts">
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 Train from "@/scripts/interfaces/Train";
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 StationCard from "@/components/StationsView/StationCard.vue";
import Options from "@/components/StationsView/Options.vue";
import FilterCard from "@/components/StationsView/FilterCard.vue";
enum ConnState {
Loading = 0,
Error = 1,
Connected = 2,
}
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,
};
@Component({
components: {
StationCard,
StationTable,
Loading,
Error,
Options,
FilterCard,
},
})
export default class StationsView extends Vue {
focusedStationName: string = "";
inputs = { ...inputData };
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 };
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) {
if (index > 5) return;
@@ -77,6 +112,14 @@ export default class StationsView extends Vue {
this.sorterActive.index = index;
}
changeFilterValue(filter: { name: string; value: number }) {
this.filters[filter.name] = filter.value;
}
resetFilters() {
this.filters = { ...filterInitStates };
}
get scheduledTrains() {
const reducedList = this.stations.reduce((acc, station) => {
if (!acc[station.stationName]) acc[station.stationName] = [];
@@ -120,6 +163,74 @@ export default class StationsView extends Vue {
const scheduledTrainList = this.scheduledTrains;
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) => {
switch (this.sorterActive.index) {
case 1:
@@ -164,39 +275,26 @@ export default class StationsView extends Vue {
mounted() {
const storage = window.localStorage;
console.log(storage.getItem(this.STORAGE_KEY));
if (storage.getItem(this.STORAGE_KEY) !== "true") return;
this.inputs.options.forEach((input) => {
if (storage.getItem(input.name) === "true") {
this.setFilter({
filterName: input.name,
value: false,
});
this.inputs.options.forEach(option => {
const value = storage.getItem(option.name) === "true" ? true : false;
console.log(option.name, value);
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) => {
const value = parseInt(
window.localStorage.getItem(slider.name) as string
);
this.inputs.sliders.forEach(slider => {
const value = parseInt(storage.getItem(slider.name) || "0");
this.setFilter({
filterName: slider.name,
value,
});
this.changeFilterValue({ name: slider.name, value });
slider.value = value;
});
})
}
closeCard() {
@@ -220,6 +318,8 @@ export default class StationsView extends Vue {
.stations-view {
padding: 1rem 0;
min-height: 100%;
position: relative;
}
@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 {
display: flex;
justify-content: center;
+2 -9
View File
@@ -1,8 +1,6 @@
<template>
<section class="trains-view">
<Loading v-if="connectionState == 0" message="Liczenie pociągów..." />
<div class="body-wrapper" v-else>
<div class="body-wrapper">
<div class="options-wrapper">
<TrainSorter :trainList="computedTrains" @changeSorter="changeSorter" />
<TrainSearch
@@ -25,8 +23,6 @@ import { Getter, Action } from "vuex-class";
import Station from "@/scripts/interfaces/Station";
import Train from "@/scripts/interfaces/Train";
import Loading from "@/components/App/Loading.vue";
import TrainSorter from "@/components/TrainsView/TrainSorter.vue";
import TrainSearch from "@/components/TrainsView/TrainSearch.vue";
import TrainTable from "@/components/TrainsView/TrainTable.vue";
@@ -36,7 +32,6 @@ import axios from "axios";
@Component({
components: {
Loading,
TrainSorter,
TrainTable,
TrainStats,
@@ -46,8 +41,6 @@ import axios from "axios";
export default class TrainsView extends Vue {
@Getter("trainsDataList") trains!: Train[];
@Getter("trainsDataState") connectionState;
@Action("fetchTrainsData") fetchTrainsData;
sorterActive: { id: string; dir: number } = { id: "timetable", dir: 1 };
@@ -121,8 +114,8 @@ export default class TrainsView extends Vue {
@import "../styles/responsive.scss";
.trains-view {
position: relative;
min-height: 100%;
position: relative;
}
.body-wrapper {