Compare commits

..

8 Commits

Author SHA1 Message Date
Spythere 4a4304d65f Merge pull request #61 - Wersja 1.18.2
Wersja 1.18.2
2023-11-04 17:03:51 +01:00
Spythere edad5306f2 bump: 1.18.2 2023-11-04 17:01:01 +01:00
Spythere 5b775dfec9 fix: filtry RJ 2023-11-04 17:00:50 +01:00
Spythere a485652ca5 Merge pull request #60 (hotfix)
hotfix: maksymalny timeout dyżurnych (1.18.1)
2023-11-02 22:44:48 +01:00
Spythere ed308246d7 hotfix: maksymalny timeout dyżurnych 2023-11-02 22:42:28 +01:00
Spythere 621bb1ad55 Merge pull request #59 - Wersja 1.18.1
Wersja 1.18.1
2023-11-02 17:41:59 +01:00
Spythere 154ae2ddac bump 1.18.1 2023-11-02 17:40:55 +01:00
Spythere d9da49a867 rozszerzony wybór regionów przez URL; poprawki headerów 2023-11-02 17:40:31 +01:00
7 changed files with 118 additions and 160 deletions
+1 -1
View File
@@ -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
View File
@@ -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';
} }
} }
} }
+5 -42
View File
@@ -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>
+5
View File
@@ -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"
} }
] ]
+6 -14
View File
@@ -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
View File
@@ -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;
}); });