mirror of
https://github.com/Spythere/stacjownik.git
synced 2026-05-03 05:18:11 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4a4304d65f | |||
| edad5306f2 | |||
| 5b775dfec9 | |||
| a485652ca5 | |||
| ed308246d7 | |||
| 621bb1ad55 | |||
| 154ae2ddac | |||
| d9da49a867 |
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "stacjownik",
|
"name": "stacjownik",
|
||||||
"version": "1.18.0",
|
"version": "1.18.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
+7
-3
@@ -37,10 +37,10 @@ import { defineComponent, watch } from 'vue';
|
|||||||
import Clock from './components/App/Clock.vue';
|
import Clock from './components/App/Clock.vue';
|
||||||
|
|
||||||
import packageInfo from '.././package.json';
|
import packageInfo from '.././package.json';
|
||||||
|
import { regions } from './data/options.json';
|
||||||
|
|
||||||
import { useStore } from './store/store';
|
import { useStore } from './store/store';
|
||||||
import StatusIndicator from './components/App/StatusIndicator.vue';
|
import StatusIndicator from './components/App/StatusIndicator.vue';
|
||||||
import SelectBox from './components/Global/SelectBox.vue';
|
|
||||||
import TrainModal from './components/Global/TrainModal.vue';
|
import TrainModal from './components/Global/TrainModal.vue';
|
||||||
import StorageManager from './scripts/managers/storageManager';
|
import StorageManager from './scripts/managers/storageManager';
|
||||||
import AppHeader from './components/App/AppHeader.vue';
|
import AppHeader from './components/App/AppHeader.vue';
|
||||||
@@ -50,7 +50,6 @@ export default defineComponent({
|
|||||||
components: {
|
components: {
|
||||||
Clock,
|
Clock,
|
||||||
StatusIndicator,
|
StatusIndicator,
|
||||||
SelectBox,
|
|
||||||
TrainModal,
|
TrainModal,
|
||||||
AppHeader
|
AppHeader
|
||||||
},
|
},
|
||||||
@@ -105,7 +104,12 @@ export default defineComponent({
|
|||||||
immediate: true,
|
immediate: true,
|
||||||
handler(regionQuery: string) {
|
handler(regionQuery: string) {
|
||||||
if (regionQuery) {
|
if (regionQuery) {
|
||||||
this.store.region.id = regionQuery;
|
this.store.region.id =
|
||||||
|
regions.find(
|
||||||
|
(reg) =>
|
||||||
|
reg.id == regionQuery.toLocaleLowerCase() ||
|
||||||
|
reg.value.toLocaleLowerCase() == regionQuery.toLocaleLowerCase()
|
||||||
|
)?.id || 'eu';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,9 +39,9 @@
|
|||||||
<img src="/images/icon-train.svg" alt="icon train" />
|
<img src="/images/icon-train.svg" alt="icon train" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="info_region">
|
<div class="info_region">
|
||||||
<SelectBox :itemList="computedRegions" :defaultItemIndex="0" @selected="changeRegion" />
|
<RegionDropdown />
|
||||||
</span>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="header_links">
|
<span class="header_links">
|
||||||
@@ -69,10 +69,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { useStore } from '../../store/store';
|
import { useStore } from '../../store/store';
|
||||||
import options from '../../data/options.json';
|
|
||||||
import SelectBox from '../Global/SelectBox.vue';
|
|
||||||
import StatusIndicator from './StatusIndicator.vue';
|
import StatusIndicator from './StatusIndicator.vue';
|
||||||
import Clock from './Clock.vue';
|
import Clock from './Clock.vue';
|
||||||
|
import RegionDropdown from '../Global/RegionDropdown.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
emits: ['changeLang'],
|
emits: ['changeLang'],
|
||||||
@@ -90,10 +89,6 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
changeRegion(region: { id: string; value: string }) {
|
|
||||||
this.store.changeRegion(region);
|
|
||||||
},
|
|
||||||
|
|
||||||
changeLang(lang: string) {
|
changeLang(lang: string) {
|
||||||
this.$emit('changeLang', lang);
|
this.$emit('changeLang', lang);
|
||||||
}
|
}
|
||||||
@@ -112,26 +107,9 @@ export default defineComponent({
|
|||||||
return this.onlineDispatchersCount == 0
|
return this.onlineDispatchersCount == 0
|
||||||
? '-'
|
? '-'
|
||||||
: (this.onlineTrainsCount / this.onlineDispatchersCount).toFixed(2);
|
: (this.onlineTrainsCount / this.onlineDispatchersCount).toFixed(2);
|
||||||
},
|
|
||||||
|
|
||||||
computedRegions() {
|
|
||||||
return options.regions.map((region) => {
|
|
||||||
const regionStationCount =
|
|
||||||
this.store.apiData.stations?.filter(
|
|
||||||
(station) => station.region == region.id && station.isOnline
|
|
||||||
).length || 0;
|
|
||||||
const regionTrainCount =
|
|
||||||
this.store.apiData.trains?.filter((train) => train.region == region.id && train.online)
|
|
||||||
.length || 0;
|
|
||||||
return {
|
|
||||||
id: region.id,
|
|
||||||
value: `${region.value} <div class='text--grayed'>${regionStationCount} / ${regionTrainCount}</div>`,
|
|
||||||
selectedValue: region.value
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: { SelectBox, StatusIndicator, Clock }
|
components: { StatusIndicator, Clock, RegionDropdown }
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -227,23 +205,8 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// REGION SELECTION
|
|
||||||
.info_region {
|
.info_region {
|
||||||
color: white;
|
|
||||||
font-weight: bold;
|
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
|
||||||
.select-box_content button {
|
|
||||||
background-color: transparent;
|
|
||||||
font-weight: bold;
|
|
||||||
padding: 0.1em 0.5em;
|
|
||||||
color: paleturquoise;
|
|
||||||
}
|
|
||||||
|
|
||||||
.options {
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,28 +1,23 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="select-box">
|
<div class="region-dropdown" v-click-outside="clickedOutside">
|
||||||
<div class="select-box_content">
|
<div class="content">
|
||||||
<button class="selected" @click="toggleBox">
|
<button class="selected-region" @click="toggleBox">
|
||||||
<span>{{ computedSelectedItem.selectedValue || computedSelectedItem.value }}</span>
|
<span>{{ selectedItem.name }}</span>
|
||||||
|
|
||||||
<div class="arrow">
|
<img :src="`/images/icon-arrow-${listOpen ? 'asc' : 'desc'}.svg`" alt="Arrow icon" />
|
||||||
<img :src="`/images/icon-arrow-${listOpen ? 'asc' : 'desc'}.svg`" alt="Arrow icon" />
|
|
||||||
</div>
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<ul class="options" :ref="(el) => (listRef = el as Element)">
|
<ul class="options">
|
||||||
<li class="option" v-for="(item, i) in itemList" :key="item.id">
|
<li class="option" v-for="(item, i) in regionList" :key="item.id">
|
||||||
<transition
|
<transition
|
||||||
name="unfold"
|
name="unfold"
|
||||||
:style="`
|
:style="`
|
||||||
--delay-in: ${i * 55}ms;
|
--delay-in: ${i * 55}ms;
|
||||||
--delay-out: ${(itemList.length - 1 - i) * 55}ms`"
|
--delay-out: ${(regionList.length - 1 - i) * 55}ms`"
|
||||||
>
|
>
|
||||||
<label :for="item.id" v-if="listOpen">
|
<label :for="item.id" v-if="listOpen">
|
||||||
<input type="button" :id="item.id" name="select-box" @click="selectOption(item)" />
|
<input type="button" :id="item.id" name="select-box" @click="selectOption(item)" />
|
||||||
<span
|
<span :style="selectedItem.id == item.id ? 'color: gold;' : ''" v-html="item.value">
|
||||||
:style="computedSelectedItem.id == item.id ? 'color: gold;' : ''"
|
|
||||||
v-html="item.value"
|
|
||||||
>
|
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</transition>
|
</transition>
|
||||||
@@ -33,79 +28,69 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, Ref, ref, computed } from 'vue';
|
import { defineComponent, Ref, ref } from 'vue';
|
||||||
|
import { useStore } from '../../store/store';
|
||||||
|
import { regions as regionsJSON } from '../../data/options.json';
|
||||||
|
|
||||||
interface Item {
|
interface Item {
|
||||||
id: string;
|
id: string;
|
||||||
value: string;
|
value: string;
|
||||||
selectedValue?: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
emits: ['selected'],
|
data() {
|
||||||
|
return {
|
||||||
props: {
|
store: useStore(),
|
||||||
itemList: {
|
selectedItemIndex: 0,
|
||||||
type: Array as () => Item[],
|
listOpen: false
|
||||||
required: true
|
};
|
||||||
},
|
|
||||||
|
|
||||||
defaultItemIndex: {
|
|
||||||
type: Number,
|
|
||||||
default: 0
|
|
||||||
},
|
|
||||||
|
|
||||||
prefix: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setup(props) {
|
setup() {
|
||||||
let listRef: Ref<Element | null> = ref(null);
|
|
||||||
let buttonRef: Ref<HTMLButtonElement | null> = ref(null);
|
let buttonRef: Ref<HTMLButtonElement | null> = ref(null);
|
||||||
|
|
||||||
let activeEl: Ref<Element | null> = ref(document.activeElement);
|
|
||||||
|
|
||||||
let listOpen = 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 {
|
return {
|
||||||
computedSelectedItem,
|
buttonRef
|
||||||
listOpen,
|
|
||||||
selectedItem,
|
|
||||||
listRef,
|
|
||||||
buttonRef,
|
|
||||||
activeEl
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
'$route.query': {
|
'store.region.id': {
|
||||||
immediate: true,
|
handler(regionId) {
|
||||||
handler(newVal) {
|
this.selectedItemIndex = this.regionList.findIndex((reg) => reg.id == regionId);
|
||||||
if (newVal.region) {
|
|
||||||
const item = this.itemList.find((it) => it.id == newVal.region);
|
|
||||||
|
|
||||||
if (item) this.selectedItem = item;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
computed: {
|
||||||
selectOption(item: Item) {
|
selectedItem() {
|
||||||
this.selectedItem = item;
|
return this.regionList[this.selectedItemIndex] || null;
|
||||||
this.listOpen = false;
|
},
|
||||||
|
regionList() {
|
||||||
|
return regionsJSON.map((region) => {
|
||||||
|
const regionStationCount =
|
||||||
|
this.store.apiData.stations?.filter(
|
||||||
|
(station) => station.region == region.id && station.isOnline
|
||||||
|
).length || 0;
|
||||||
|
|
||||||
this.$emit('selected', item);
|
const regionTrainCount =
|
||||||
|
this.store.apiData.trains?.filter((train) => train.region == region.id && train.online)
|
||||||
|
.length || 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: region.id,
|
||||||
|
value: `${region.value} <div class='text--grayed'>${regionStationCount} / ${regionTrainCount}</div>`,
|
||||||
|
name: region.name
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
selectOption(selectedRegion: Item) {
|
||||||
|
this.store.region = selectedRegion;
|
||||||
|
this.listOpen = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleBox(e: Event) {
|
toggleBox(e: Event) {
|
||||||
@@ -125,40 +110,20 @@ export default defineComponent({
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../styles/variables.scss';
|
@import '../../styles/variables.scss';
|
||||||
|
|
||||||
.unfold {
|
.region-dropdown {
|
||||||
&-enter-from,
|
|
||||||
&-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(-10px) scale(0.85);
|
|
||||||
}
|
|
||||||
|
|
||||||
&-enter-active,
|
|
||||||
&-leave-active {
|
|
||||||
transition: all 110ms ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-enter-active {
|
|
||||||
transition-delay: var(--delay-in);
|
|
||||||
}
|
|
||||||
|
|
||||||
&-leave-active {
|
|
||||||
transition-delay: var(--delay-out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.select-box {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.arrow {
|
button img {
|
||||||
img {
|
vertical-align: middle;
|
||||||
vertical-align: middle;
|
width: 1.35em;
|
||||||
width: 1.35em;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button.selected {
|
button.selected-region {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
color: paleturquoise;
|
color: paleturquoise;
|
||||||
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -167,11 +132,16 @@ button.selected {
|
|||||||
&:focus {
|
&:focus {
|
||||||
background-color: #262626;
|
background-color: #262626;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.select-box_content {
|
.content {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
@@ -232,4 +202,25 @@ li.option {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.unfold {
|
||||||
|
&-enter-from,
|
||||||
|
&-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-10px) scale(0.85);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-enter-active,
|
||||||
|
&-leave-active {
|
||||||
|
transition: all 110ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-enter-active {
|
||||||
|
transition-delay: var(--delay-in);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-leave-active {
|
||||||
|
transition-delay: var(--delay-out);
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -299,22 +299,27 @@
|
|||||||
"regions": [
|
"regions": [
|
||||||
{
|
{
|
||||||
"id": "eu",
|
"id": "eu",
|
||||||
|
"name": "PL1",
|
||||||
"value": "PL1"
|
"value": "PL1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cae",
|
"id": "cae",
|
||||||
|
"name": "PL2",
|
||||||
"value": "PL2"
|
"value": "PL2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "usw",
|
"id": "usw",
|
||||||
|
"name": "DE",
|
||||||
"value": "DE"
|
"value": "DE"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "us",
|
"id": "us",
|
||||||
|
"name": "CZE",
|
||||||
"value": "CZE"
|
"value": "CZE"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "ru",
|
"id": "ru",
|
||||||
|
"name": "ENG",
|
||||||
"value": "ENG"
|
"value": "ENG"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -53,28 +53,20 @@ export const sortStations = (
|
|||||||
|
|
||||||
case 'timetableConfirmed':
|
case 'timetableConfirmed':
|
||||||
diff =
|
diff =
|
||||||
(a.onlineInfo?.scheduledTrains
|
(a.onlineInfo?.scheduledTrainCount.confirmed ?? -1) -
|
||||||
? a.onlineInfo.scheduledTrains.filter((train) => train.stopInfo.confirmed).length
|
(b.onlineInfo?.scheduledTrainCount.confirmed ?? -1);
|
||||||
: -1) -
|
|
||||||
(b.onlineInfo?.scheduledTrains
|
|
||||||
? b.onlineInfo.scheduledTrains.filter((train) => train.stopInfo.confirmed).length
|
|
||||||
: -1);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'timetableUnconfirmed':
|
case 'timetableUnconfirmed':
|
||||||
diff =
|
diff =
|
||||||
(a.onlineInfo?.scheduledTrains
|
(a.onlineInfo?.scheduledTrainCount.unconfirmed ?? -1) -
|
||||||
? a.onlineInfo.scheduledTrains.filter((train) => !train.stopInfo.confirmed).length
|
(b.onlineInfo?.scheduledTrainCount.unconfirmed ?? -1);
|
||||||
: -1) -
|
|
||||||
(b.onlineInfo?.scheduledTrains
|
|
||||||
? b.onlineInfo.scheduledTrains.filter((train) => !train.stopInfo.confirmed).length
|
|
||||||
: -1);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'timetableAll':
|
case 'timetableAll':
|
||||||
diff =
|
diff =
|
||||||
(a.onlineInfo?.scheduledTrains ? a.onlineInfo.scheduledTrains.length : -1) -
|
(a.onlineInfo?.scheduledTrainCount.all ?? -1) -
|
||||||
(b.onlineInfo?.scheduledTrains ? b.onlineInfo.scheduledTrains.length : -1);
|
(b.onlineInfo?.scheduledTrainCount.all ?? -1);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
+8
-5
@@ -135,6 +135,9 @@ export const useStore = defineStore('store', {
|
|||||||
.reduce((list, apiStation) => {
|
.reduce((list, apiStation) => {
|
||||||
if (apiStation.region != state.region.id) return list;
|
if (apiStation.region != state.region.id) return list;
|
||||||
|
|
||||||
|
if (apiStation.isOnline !== 1 && Date.now() - apiStation.lastSeen > 1000 * 60 * 3)
|
||||||
|
return list;
|
||||||
|
|
||||||
const dispatcherStatus = getDispatcherStatus(state as StoreState, apiStation);
|
const dispatcherStatus = getDispatcherStatus(state as StoreState, apiStation);
|
||||||
|
|
||||||
if (dispatcherStatus.statusID == DispatcherStatusID.Unknown) return list;
|
if (dispatcherStatus.statusID == DispatcherStatusID.Unknown) return list;
|
||||||
@@ -185,7 +188,8 @@ export const useStore = defineStore('store', {
|
|||||||
scheduledTrainCount: {
|
scheduledTrainCount: {
|
||||||
all: uniqueScheduledTrains.length,
|
all: uniqueScheduledTrains.length,
|
||||||
confirmed: uniqueScheduledTrains.filter((train) => train.stopInfo.confirmed).length,
|
confirmed: uniqueScheduledTrains.filter((train) => train.stopInfo.confirmed).length,
|
||||||
unconfirmed: uniqueScheduledTrains.filter((train) => !train.stopInfo.confirmed).length
|
unconfirmed: uniqueScheduledTrains.filter((train) => !train.stopInfo.confirmed)
|
||||||
|
.length
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -271,12 +275,11 @@ export const useStore = defineStore('store', {
|
|||||||
const socket = io(URLs.stacjownikAPI, {
|
const socket = io(URLs.stacjownikAPI, {
|
||||||
transports: ['websocket', 'polling'],
|
transports: ['websocket', 'polling'],
|
||||||
rememberUpgrade: true,
|
rememberUpgrade: true,
|
||||||
reconnection: true,
|
reconnection: true
|
||||||
extraHeaders: {
|
|
||||||
version: packageInfo.version
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socket.emit('CONNECTION', { version: packageInfo.version });
|
||||||
|
|
||||||
socket.on('connect_error', () => {
|
socket.on('connect_error', () => {
|
||||||
this.dataStatuses.connection = DataStatus.Error;
|
this.dataStatuses.connection = DataStatus.Error;
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user