mirror of
https://github.com/Spythere/stacjownik.git
synced 2026-05-03 13:28:11 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 063d5283e4 | |||
| 29de1b3c4b | |||
| f0c02bf12e | |||
| 8aa23468b3 | |||
| 4c1fcf710b | |||
| a529d6e9eb | |||
| 9fc602e08f | |||
| 56e40bd84b | |||
| a5b5df7452 | |||
| 1a8da02ced | |||
| 7e75fa2516 | |||
| 3ed2c09184 | |||
| 6901c3d2b4 | |||
| 8417754403 |
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "stacjownik",
|
"name": "stacjownik",
|
||||||
"version": "1.24.1",
|
"version": "1.24.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
+8
-8
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="app_container">
|
<div class="app_container">
|
||||||
<UpdateModal
|
<UpdateCard
|
||||||
:update-modal-open="isUpdateModalOpen"
|
:is-update-card-open="isUpdateCardOpen"
|
||||||
@toggle-modal="() => (isUpdateModalOpen = false)"
|
@toggle-card="() => (isUpdateCardOpen = false)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Tooltip />
|
<Tooltip />
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
©
|
©
|
||||||
<a href="https://td2.info.pl/profile/?u=20777" target="_blank">Spythere</a>
|
<a href="https://td2.info.pl/profile/?u=20777" target="_blank">Spythere</a>
|
||||||
{{ new Date().getUTCFullYear() }} |
|
{{ new Date().getUTCFullYear() }} |
|
||||||
<button class="btn--text" @click="() => (isUpdateModalOpen = true)">
|
<button class="btn--text" @click="() => (isUpdateCardOpen = true)">
|
||||||
v{{ VERSION }}{{ isOnProductionHost ? '' : 'dev' }}
|
v{{ VERSION }}{{ isOnProductionHost ? '' : 'dev' }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ import StatusIndicator from './components/App/StatusIndicator.vue';
|
|||||||
import AppHeader from './components/App/AppHeader.vue';
|
import AppHeader from './components/App/AppHeader.vue';
|
||||||
import TrainModal from './components/TrainsView/TrainModal.vue';
|
import TrainModal from './components/TrainsView/TrainModal.vue';
|
||||||
import Tooltip from './components/Tooltip/Tooltip.vue';
|
import Tooltip from './components/Tooltip/Tooltip.vue';
|
||||||
import UpdateModal from './components/App/UpdateModal.vue';
|
import UpdateCard from './components/App/UpdateCard.vue';
|
||||||
|
|
||||||
import StorageManager from './managers/storageManager';
|
import StorageManager from './managers/storageManager';
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ export default defineComponent({
|
|||||||
StatusIndicator,
|
StatusIndicator,
|
||||||
AppHeader,
|
AppHeader,
|
||||||
TrainModal,
|
TrainModal,
|
||||||
UpdateModal,
|
UpdateCard,
|
||||||
Tooltip
|
Tooltip
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -78,7 +78,7 @@ export default defineComponent({
|
|||||||
apiStore: useApiStore(),
|
apiStore: useApiStore(),
|
||||||
tooltipStore: useTooltipStore(),
|
tooltipStore: useTooltipStore(),
|
||||||
|
|
||||||
isUpdateModalOpen: false,
|
isUpdateCardOpen: false,
|
||||||
|
|
||||||
currentLang: 'pl',
|
currentLang: 'pl',
|
||||||
isOnProductionHost: location.hostname == 'stacjownik-td2.web.app',
|
isOnProductionHost: location.hostname == 'stacjownik-td2.web.app',
|
||||||
@@ -130,7 +130,7 @@ export default defineComponent({
|
|||||||
releaseURL: releaseData.html_url
|
releaseURL: releaseData.html_url
|
||||||
};
|
};
|
||||||
|
|
||||||
this.isUpdateModalOpen =
|
this.isUpdateCardOpen =
|
||||||
storageVersion != version || import.meta.env.VITE_UPDATE_TEST === 'test';
|
storageVersion != version || import.meta.env.VITE_UPDATE_TEST === 'test';
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Wystąpił błąd podczas pobierania danych z API GitHuba: ${error}`);
|
console.error(`Wystąpił błąd podczas pobierania danych z API GitHuba: ${error}`);
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<AnimatedModal :is-open="updateModalOpen" @toggle-modal="toggleModal(false)">
|
<Card :is-open="isUpdateCardOpen" @toggle-card="toggleCard(false)">
|
||||||
<div class="modal-content">
|
<div class="content">
|
||||||
<h1 style="margin-bottom: 0.5em">🚀 {{ $t('update.title') }}</h1>
|
<h1 style="margin-bottom: 0.5em">🚀 {{ $t('update.title') }}</h1>
|
||||||
|
|
||||||
<div class="features-body" v-if="htmlChangelog != ''" v-html="htmlChangelog"></div>
|
<div class="features-body" v-if="htmlChangelog != ''" v-html="htmlChangelog"></div>
|
||||||
<div class="no-features" v-else>{{ $t('update.no-data') }}</div>
|
<div class="no-features" v-else>{{ $t('update.no-data') }}</div>
|
||||||
|
|
||||||
<button class="btn btn--action" ref="confirm-btn" @click="toggleModal(false)">
|
<button class="btn btn--action" ref="confirm-btn" @click="toggleCard(false)">
|
||||||
{{ $t('update.confirm') }}
|
{{ $t('update.confirm') }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
<span v-html="$t('update.info-2')"></span>
|
<span v-html="$t('update.info-2')"></span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</AnimatedModal>
|
</Card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
@@ -25,21 +25,21 @@ import { useMainStore } from '../../store/mainStore';
|
|||||||
import { version } from '../../../package.json';
|
import { version } from '../../../package.json';
|
||||||
import { Converter } from 'showdown';
|
import { Converter } from 'showdown';
|
||||||
|
|
||||||
import AnimatedModal from '../Global/AnimatedModal.vue';
|
import Card from '../Global/Card.vue';
|
||||||
|
|
||||||
const converter = new Converter();
|
const converter = new Converter();
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { AnimatedModal },
|
components: { Card },
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
updateModalOpen: {
|
isUpdateCardOpen: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
emits: ['toggleModal'],
|
emits: ['toggleCard'],
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -49,7 +49,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
updateModalOpen(val: boolean) {
|
isUpdateCardOpen(val: boolean) {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (val) (this.$refs['confirm-btn'] as HTMLElement).focus();
|
if (val) (this.$refs['confirm-btn'] as HTMLElement).focus();
|
||||||
});
|
});
|
||||||
@@ -60,37 +60,38 @@ export default defineComponent({
|
|||||||
htmlChangelog() {
|
htmlChangelog() {
|
||||||
if (this.mainStore.appUpdate == null) return '';
|
if (this.mainStore.appUpdate == null) return '';
|
||||||
|
|
||||||
const x = converter.makeHtml(this.mainStore.appUpdate.changelog);
|
return converter.makeHtml(this.mainStore.appUpdate.changelog);
|
||||||
console.log(x);
|
|
||||||
|
|
||||||
return x;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
toggleModal(value: boolean) {
|
toggleCard(value: boolean) {
|
||||||
this.$emit('toggleModal', value);
|
this.$emit('toggleCard', value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@import '../../styles/variables';
|
||||||
|
|
||||||
::v-deep(h1) {
|
::v-deep(h1) {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
color: $accentCol;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(h2) {
|
::v-deep(h2) {
|
||||||
padding: 0.25em 0;
|
padding: 0.25em 0;
|
||||||
|
border-bottom: 1px solid #aaa;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(ul) {
|
::v-deep(ul) {
|
||||||
list-style: inside;
|
list-style: initial;
|
||||||
padding: 0.5em;
|
padding: 1em;
|
||||||
line-height: 1.5em;
|
line-height: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-content {
|
.content {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: auto 1fr auto;
|
grid-template-rows: auto 1fr auto;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
@@ -98,6 +99,7 @@ export default defineComponent({
|
|||||||
min-height: 700px;
|
min-height: 700px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
text-align: justify;
|
text-align: justify;
|
||||||
|
max-width: 700px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-features {
|
.no-features {
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<transition name="modal-anim" tag="div">
|
<transition name="modal-anim" tag="div">
|
||||||
<div class="modal" v-if="isOpen">
|
<div class="card" v-if="isOpen">
|
||||||
<div class="modal-background" @click="toggleModal(false)"></div>
|
<div class="card-background" @click="toggleCard(false)"></div>
|
||||||
<div class="modal-wrapper" ref="wrapper" tabindex="0">
|
<div class="card-body" ref="wrapper" tabindex="0">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-exit" ref="exit" tabindex="0" @focus="toggleModal(false)"></div>
|
<div class="tab-exit" ref="exit" tabindex="0" @focus="toggleCard(false)"></div>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</template>
|
</template>
|
||||||
@@ -15,7 +15,7 @@ import { defineComponent } from 'vue';
|
|||||||
import { useMainStore } from '../../store/mainStore';
|
import { useMainStore } from '../../store/mainStore';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
emits: ['toggleModal'],
|
emits: ['toggleCard'],
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
isOpen: Boolean
|
isOpen: Boolean
|
||||||
@@ -36,8 +36,8 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
toggleModal(value: boolean) {
|
toggleCard(value: boolean) {
|
||||||
this.$emit('toggleModal', value);
|
this.$emit('toggleCard', value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -46,7 +46,7 @@ export default defineComponent({
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../styles/responsive.scss';
|
@import '../../styles/responsive.scss';
|
||||||
|
|
||||||
.modal {
|
.card {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@@ -56,7 +56,7 @@ export default defineComponent({
|
|||||||
z-index: 200;
|
z-index: 200;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-background {
|
.card-background {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@@ -68,7 +68,7 @@ export default defineComponent({
|
|||||||
background-color: rgba(0, 0, 0, 0.55);
|
background-color: rgba(0, 0, 0, 0.55);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-wrapper {
|
.card-body {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
@@ -77,12 +77,11 @@ export default defineComponent({
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
max-height: 95vh;
|
max-height: 95vh;
|
||||||
|
|
||||||
|
box-shadow: 0 0 15px 10px #0e0e0e;
|
||||||
|
|
||||||
& > :slotted(div) {
|
& > :slotted(div) {
|
||||||
background-color: #1a1a1a;
|
background-color: #1a1a1a;
|
||||||
box-shadow: 0 0 15px 10px #0e0e0e;
|
|
||||||
|
|
||||||
width: 95vw;
|
width: 95vw;
|
||||||
max-width: 850px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,12 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<AnimatedModal
|
<Card :isOpen="isCardOpen" @toggleCard="toggleCard" @keydown.esc="toggleCard(false)">
|
||||||
class="donation-modal"
|
<div class="body">
|
||||||
:isOpen="isModalOpen"
|
<div class="content">
|
||||||
@toggleModal="toggleModal"
|
|
||||||
@keydown.esc="toggleModal(false)"
|
|
||||||
>
|
|
||||||
<div class="modal_content">
|
|
||||||
<div class="modal_main">
|
|
||||||
<h1 v-html="$t('donations.header')"></h1>
|
<h1 v-html="$t('donations.header')"></h1>
|
||||||
<div class="donators-slider" v-if="donatorList.length != 0">
|
<div class="donators-slider" v-if="donatorList.length != 0">
|
||||||
<span v-html="$t('donations.donator-title', { count: donatorList.length })"></span>
|
<span v-html="$t('donations.donator-title', { count: donatorList.length })"></span>
|
||||||
@@ -61,9 +56,9 @@
|
|||||||
</i>
|
</i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal_actions">
|
<div class="actions">
|
||||||
<a
|
<a
|
||||||
class="modal-action a-button btn--image coffee"
|
class="action a-button btn--image coffee"
|
||||||
href="https://buycoffee.to/spythere"
|
href="https://buycoffee.to/spythere"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
ref="action"
|
ref="action"
|
||||||
@@ -73,7 +68,7 @@
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a
|
<a
|
||||||
class="modal-action a-button btn--image paypal"
|
class="action a-button btn--image paypal"
|
||||||
href="https://www.paypal.com/donate/?hosted_button_id=EDB3SKFAHXFTW"
|
href="https://www.paypal.com/donate/?hosted_button_id=EDB3SKFAHXFTW"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
@@ -81,30 +76,30 @@
|
|||||||
{{ $t('donations.action-paypal') }}
|
{{ $t('donations.action-paypal') }}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<button class="modal-action btn--image exit" @click="toggleModal(false)">
|
<button class="action btn--image exit" @click="toggleCard(false)">
|
||||||
<img src="/images/icon-exit.svg" alt="dollar donation icon" />
|
<img src="/images/icon-exit.svg" alt="dollar donation icon" />
|
||||||
{{ $t('donations.action-exit') }}
|
{{ $t('donations.action-exit') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</AnimatedModal>
|
</Card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import AnimatedModal from './AnimatedModal.vue';
|
|
||||||
import { useApiStore } from '../../store/apiStore';
|
import { useApiStore } from '../../store/apiStore';
|
||||||
|
import Card from './Card.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { AnimatedModal },
|
components: { Card },
|
||||||
props: {
|
props: {
|
||||||
isModalOpen: Boolean
|
isCardOpen: Boolean
|
||||||
},
|
},
|
||||||
|
|
||||||
emits: ['toggleModal'],
|
emits: ['toggleCard'],
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
isModalOpen(val: boolean) {
|
isCardOpen(val: boolean) {
|
||||||
this.running = val;
|
this.running = val;
|
||||||
this.lastUpdate = Date.now();
|
this.lastUpdate = Date.now();
|
||||||
|
|
||||||
@@ -138,8 +133,8 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
toggleModal(value: boolean) {
|
toggleCard(value: boolean) {
|
||||||
this.$emit('toggleModal', value);
|
this.$emit('toggleCard', value);
|
||||||
},
|
},
|
||||||
|
|
||||||
runUpdate() {
|
runUpdate() {
|
||||||
@@ -157,53 +152,53 @@ export default defineComponent({
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../styles/responsive.scss';
|
@import '../../styles/responsive.scss';
|
||||||
|
|
||||||
.modal_content {
|
.body {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: 1fr auto;
|
grid-template-rows: 1fr auto;
|
||||||
gap: 1em;
|
gap: 1em;
|
||||||
|
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
|
|
||||||
& > div {
|
max-width: 820px;
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 1.95em;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
text-align: justify;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.discord {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal_main {
|
.content {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
padding: 1em;
|
||||||
img {
|
|
||||||
max-height: 20px;
|
|
||||||
margin-right: 5px;
|
|
||||||
vertical-align: text-bottom;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal_actions {
|
img {
|
||||||
|
max-height: 20px;
|
||||||
|
margin-right: 5px;
|
||||||
|
vertical-align: text-bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 1.95em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
text-align: justify;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.discord {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
|
padding: 1em;
|
||||||
|
|
||||||
form button {
|
form button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal_actions > .modal-action {
|
.actions > .action {
|
||||||
&.paypal {
|
&.paypal {
|
||||||
$btnColor: #254069;
|
$btnColor: #254069;
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
|
|
||||||
<div class="info-lists">
|
<div class="info-lists">
|
||||||
<!-- user list -->
|
<!-- user list -->
|
||||||
<SceneryInfoUserList :onlineScenery="onlineScenery" />
|
<SceneryInfoUserList :onlineScenery="onlineScenery" :station="station" />
|
||||||
|
|
||||||
<!-- spawn list -->
|
<!-- spawn list -->
|
||||||
<SceneryInfoSpawnList :onlineScenery="onlineScenery" />
|
<SceneryInfoSpawnList :onlineScenery="onlineScenery" />
|
||||||
|
|||||||
@@ -13,13 +13,13 @@
|
|||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li
|
<li
|
||||||
v-for="train in onlineScenery?.stationTrains"
|
v-for="{ train, status } in stationTrains"
|
||||||
class="badge user"
|
class="badge user"
|
||||||
:class="train.stopStatus"
|
|
||||||
:key="train.trainId"
|
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@click.prevent="selectModalTrain(train.trainId, $event.currentTarget)"
|
:key="train.id"
|
||||||
@keydown.enter="selectModalTrain(train.trainId, $event.currentTarget)"
|
:data-status="status"
|
||||||
|
@click.prevent="selectModalTrain(train.id, $event.currentTarget)"
|
||||||
|
@keydown.enter="selectModalTrain(train.id, $event.currentTarget)"
|
||||||
>
|
>
|
||||||
<span class="user_train">{{ train.trainNo }}</span>
|
<span class="user_train">{{ train.trainNo }}</span>
|
||||||
<span class="user_name">{{ train.driverName }}</span>
|
<span class="user_name">{{ train.driverName }}</span>
|
||||||
@@ -32,7 +32,9 @@
|
|||||||
import { PropType, defineComponent } from 'vue';
|
import { PropType, defineComponent } from 'vue';
|
||||||
import modalTrainMixin from '../../../mixins/modalTrainMixin';
|
import modalTrainMixin from '../../../mixins/modalTrainMixin';
|
||||||
import routerMixin from '../../../mixins/routerMixin';
|
import routerMixin from '../../../mixins/routerMixin';
|
||||||
import { ActiveScenery } from '../../../typings/common';
|
import { ActiveScenery, Station, StopStatus } from '../../../typings/common';
|
||||||
|
import { getTrainStopStatus } from '../utils';
|
||||||
|
import { useMainStore } from '../../../store/mainStore';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
mixins: [routerMixin, modalTrainMixin],
|
mixins: [routerMixin, modalTrainMixin],
|
||||||
@@ -41,6 +43,38 @@ export default defineComponent({
|
|||||||
onlineScenery: {
|
onlineScenery: {
|
||||||
type: Object as PropType<ActiveScenery>,
|
type: Object as PropType<ActiveScenery>,
|
||||||
required: false
|
required: false
|
||||||
|
},
|
||||||
|
station: {
|
||||||
|
type: Object as PropType<Station>
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
mainStore: useMainStore()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
stationTrains() {
|
||||||
|
if (!this.onlineScenery) return;
|
||||||
|
|
||||||
|
const name = this.station?.generalInfo?.checkpoints[0] ?? this.onlineScenery.name;
|
||||||
|
|
||||||
|
return this.onlineScenery.stationTrains.map((train) => {
|
||||||
|
const stop = train.timetableData?.followingStops.find(
|
||||||
|
(stop) => stop.stopNameRAW.toLowerCase() == name.toLowerCase()
|
||||||
|
);
|
||||||
|
|
||||||
|
const status = stop
|
||||||
|
? getTrainStopStatus(stop, train.currentStationName, this.onlineScenery!.name)
|
||||||
|
: 'no-timetable';
|
||||||
|
|
||||||
|
return {
|
||||||
|
train,
|
||||||
|
status
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -74,31 +108,31 @@ ul {
|
|||||||
-webkit-transition: background-color 200ms;
|
-webkit-transition: background-color 200ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.no-timetable .user_train {
|
&[data-status='no-timetable'] .user_train {
|
||||||
background-color: $no-timetable;
|
background-color: $no-timetable;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.departed > &_train {
|
&[data-status='departed'] > &_train {
|
||||||
background-color: $departed;
|
background-color: $departed;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.stopped > &_train {
|
&[data-status='stopped'] > &_train {
|
||||||
background-color: $stopped;
|
background-color: $stopped;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.online > &_train {
|
&[data-status='online'] > &_train {
|
||||||
background-color: $online;
|
background-color: $online;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.terminated > &_train {
|
&[data-status='terminated'] > &_train {
|
||||||
background-color: $terminated;
|
background-color: $terminated;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.disconnected > &_train {
|
&[data-status='disconnected'] > &_train {
|
||||||
background-color: $disconnected;
|
background-color: $disconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.offline {
|
&[data-status='offline'] {
|
||||||
background: firebrick;
|
background: firebrick;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
<transition-group name="list-anim">
|
<transition-group name="list-anim">
|
||||||
<div
|
<div
|
||||||
style="padding-bottom: 5em"
|
style="padding-bottom: 5em"
|
||||||
v-if="apiStore.dataStatuses.connection == 0 && computedScheduledTrains.length == 0"
|
v-if="apiStore.dataStatuses.connection == 0 && sceneryTimetables.length == 0"
|
||||||
key="list-loading"
|
key="list-loading"
|
||||||
>
|
>
|
||||||
<Loading />
|
<Loading />
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
|
|
||||||
<span
|
<span
|
||||||
class="timetable-item empty"
|
class="timetable-item empty"
|
||||||
v-else-if="computedScheduledTrains.length == 0 && !onlineScenery"
|
v-else-if="sceneryTimetables.length == 0 && !onlineScenery"
|
||||||
key="list-offline"
|
key="list-offline"
|
||||||
>
|
>
|
||||||
{{ $t('scenery.offline') }}
|
{{ $t('scenery.offline') }}
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
class="timetable-item empty"
|
class="timetable-item empty"
|
||||||
v-else-if="computedScheduledTrains.length == 0"
|
v-else-if="sceneryTimetables.length == 0"
|
||||||
key="list-no-timetables"
|
key="list-no-timetables"
|
||||||
>
|
>
|
||||||
{{ $t('scenery.no-timetables') }}
|
{{ $t('scenery.no-timetables') }}
|
||||||
@@ -65,59 +65,56 @@
|
|||||||
<div
|
<div
|
||||||
class="timetable-item"
|
class="timetable-item"
|
||||||
v-else
|
v-else
|
||||||
v-for="scheduledTrain in computedScheduledTrains"
|
v-for="row in sceneryTimetables"
|
||||||
:key="scheduledTrain.trainId + scheduledTrain.stopInfo.arrivalTimestamp"
|
:key="row.train.id + row.checkpointStop.arrivalTimestamp"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@click.prevent.stop="selectModalTrain(scheduledTrain.trainId, $event.currentTarget)"
|
@click.prevent.stop="selectModalTrain(row.train.id, $event.currentTarget)"
|
||||||
@keydown.enter.prevent="selectModalTrain(scheduledTrain.trainId, $event.currentTarget)"
|
@keydown.enter.prevent="selectModalTrain(row.train.id, $event.currentTarget)"
|
||||||
>
|
>
|
||||||
<span class="timetable-general">
|
<span class="timetable-general">
|
||||||
<span class="general-info">
|
<span class="general-info">
|
||||||
<span class="info-number">
|
<span class="info-number">
|
||||||
<strong>{{ scheduledTrain.category }}</strong>
|
<strong>{{ row.train.timetableData!.category }}</strong>
|
||||||
{{ scheduledTrain.trainNo }}
|
{{ row.train.trainNo }}
|
||||||
|
|
||||||
<span
|
<span v-if="row.checkpointStop.comments" :title="row.checkpointStop.comments">
|
||||||
v-if="scheduledTrain.stopInfo.comments"
|
|
||||||
:title="scheduledTrain.stopInfo.comments"
|
|
||||||
>
|
|
||||||
<img src="/images/icon-warning.svg" />
|
<img src="/images/icon-warning.svg" />
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
|
||||||
<span>
|
<span>
|
||||||
{{ scheduledTrain.driverName }}
|
{{ row.train.driverName }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div class="info-route">
|
<div class="info-route">
|
||||||
<strong>{{ scheduledTrain.beginsAt }} - {{ scheduledTrain.terminatesAt }}</strong>
|
<strong>{{ row.train.timetableData!.route.replace('|', ' - ') }}</strong>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ScheduledTrainStatus :scheduledTrain="scheduledTrain" />
|
<ScheduledTrainStatus :sceneryTimetableRow="row" />
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="timetable-schedule">
|
<span class="timetable-schedule">
|
||||||
<span class="schedule-arrival">
|
<span class="schedule-arrival">
|
||||||
<span class="arrival-time begins" v-if="scheduledTrain.stopInfo.beginsHere">
|
<span class="arrival-time begins" v-if="row.checkpointStop.beginsHere">
|
||||||
{{ $t('timetables.begins') }}
|
{{ $t('timetables.begins') }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="arrival-time" v-else>
|
<span class="arrival-time" v-else>
|
||||||
<div v-if="scheduledTrain.stopInfo.arrivalDelay == 0">
|
<div v-if="row.checkpointStop.arrivalDelay == 0">
|
||||||
<span>{{ timestampToString(scheduledTrain.stopInfo.arrivalTimestamp) }}</span>
|
<span>{{ timestampToString(row.checkpointStop.arrivalTimestamp) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<div>
|
<div>
|
||||||
<s style="margin-right: 0.2em" class="text--grayed">{{
|
<s style="margin-right: 0.2em" class="text--grayed">{{
|
||||||
timestampToString(scheduledTrain.stopInfo.arrivalTimestamp)
|
timestampToString(row.checkpointStop.arrivalTimestamp)
|
||||||
}}</s>
|
}}</s>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
{{ timestampToString(scheduledTrain.stopInfo.arrivalRealTimestamp) }}
|
{{ timestampToString(row.checkpointStop.arrivalRealTimestamp) }}
|
||||||
({{ scheduledTrain.stopInfo.arrivalDelay > 0 ? '+' : ''
|
({{ row.checkpointStop.arrivalDelay > 0 ? '+' : ''
|
||||||
}}{{ scheduledTrain.stopInfo.arrivalDelay }})
|
}}{{ row.checkpointStop.arrivalDelay }})
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
@@ -125,41 +122,39 @@
|
|||||||
|
|
||||||
<span class="schedule-stop">
|
<span class="schedule-stop">
|
||||||
<span class="stop-connection">
|
<span class="stop-connection">
|
||||||
{{ scheduledTrain.arrivingLine }}
|
{{ row.arrivingLine }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="stop-time">
|
<span class="stop-time">
|
||||||
{{ scheduledTrain.stopInfo.stopTime || '' }}
|
{{ row.checkpointStop.stopTime || '' }}
|
||||||
{{
|
{{ row.checkpointStop.stopTime ? row.checkpointStop.stopType || 'pt' : '' }}
|
||||||
scheduledTrain.stopInfo.stopTime ? scheduledTrain.stopInfo.stopType || 'pt' : ''
|
|
||||||
}}
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="stop-connection">
|
<span class="stop-connection">
|
||||||
{{ scheduledTrain.departureLine }}
|
{{ row.departureLine }}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="schedule-departure">
|
<span class="schedule-departure">
|
||||||
<span class="departure-time terminates" v-if="scheduledTrain.stopInfo.terminatesHere">
|
<span class="departure-time terminates" v-if="row.checkpointStop.terminatesHere">
|
||||||
{{ $t('timetables.terminates') }}
|
{{ $t('timetables.terminates') }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="departure-time" v-else>
|
<span class="departure-time" v-else>
|
||||||
<div v-if="scheduledTrain.stopInfo.departureDelay == 0">
|
<div v-if="row.checkpointStop.departureDelay == 0">
|
||||||
<span>{{ timestampToString(scheduledTrain.stopInfo.departureTimestamp) }}</span>
|
<span>{{ timestampToString(row.checkpointStop.departureTimestamp) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<div>
|
<div>
|
||||||
<s style="margin-right: 0.2em" class="text--grayed">{{
|
<s style="margin-right: 0.2em" class="text--grayed">{{
|
||||||
timestampToString(scheduledTrain.stopInfo.departureTimestamp)
|
timestampToString(row.checkpointStop.departureTimestamp)
|
||||||
}}</s>
|
}}</s>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
{{ timestampToString(scheduledTrain.stopInfo.departureRealTimestamp) }}
|
{{ timestampToString(row.checkpointStop.departureRealTimestamp) }}
|
||||||
({{ scheduledTrain.stopInfo.departureDelay > 0 ? '+' : ''
|
({{ row.checkpointStop.departureDelay > 0 ? '+' : ''
|
||||||
}}{{ scheduledTrain.stopInfo.departureDelay }})
|
}}{{ row.checkpointStop.departureDelay }})
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
@@ -183,6 +178,8 @@ import modalTrainMixin from '../../mixins/modalTrainMixin';
|
|||||||
import ScheduledTrainStatus from './ScheduledTrainStatus.vue';
|
import ScheduledTrainStatus from './ScheduledTrainStatus.vue';
|
||||||
import { useApiStore } from '../../store/apiStore';
|
import { useApiStore } from '../../store/apiStore';
|
||||||
import { ActiveScenery, Station } from '../../typings/common';
|
import { ActiveScenery, Station } from '../../typings/common';
|
||||||
|
import { SceneryTimetableRow } from './typings';
|
||||||
|
import { getTrainStopStatus, stopStatusPriority } from './utils';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'SceneryTimetable',
|
name: 'SceneryTimetable',
|
||||||
@@ -204,10 +201,6 @@ export default defineComponent({
|
|||||||
listOpen: false
|
listOpen: false
|
||||||
}),
|
}),
|
||||||
|
|
||||||
mounted() {
|
|
||||||
this.loadSelectedOption();
|
|
||||||
},
|
|
||||||
|
|
||||||
activated() {
|
activated() {
|
||||||
this.loadSelectedOption();
|
this.loadSelectedOption();
|
||||||
},
|
},
|
||||||
@@ -220,9 +213,7 @@ export default defineComponent({
|
|||||||
const mainStore = useMainStore();
|
const mainStore = useMainStore();
|
||||||
|
|
||||||
const chosenCheckpoint = ref(
|
const chosenCheckpoint = ref(
|
||||||
props.station?.generalInfo?.checkpoints?.length == 0
|
props.station?.generalInfo?.checkpoints[0] ?? props.station?.name ?? ''
|
||||||
? ''
|
|
||||||
: props.station?.generalInfo?.checkpoints[0] ?? null
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -241,27 +232,107 @@ export default defineComponent({
|
|||||||
return url;
|
return url;
|
||||||
},
|
},
|
||||||
|
|
||||||
computedScheduledTrains() {
|
sceneryTimetables(): SceneryTimetableRow[] {
|
||||||
if (!this.station) return [];
|
if (!this.station) return [];
|
||||||
|
if (!this.onlineScenery) return [];
|
||||||
|
|
||||||
return (
|
console.log(this.onlineScenery.scheduledTrains, this.chosenCheckpoint);
|
||||||
this.onlineScenery?.scheduledTrains
|
|
||||||
?.filter(
|
|
||||||
(train) =>
|
|
||||||
train.checkpointName.toLocaleLowerCase() ==
|
|
||||||
(this.chosenCheckpoint || this.station!.name).toLocaleLowerCase() &&
|
|
||||||
train.region == this.mainStore.region.id
|
|
||||||
)
|
|
||||||
.sort((a, b) => {
|
|
||||||
if (a.stopStatusID > b.stopStatusID) return 1;
|
|
||||||
if (a.stopStatusID < b.stopStatusID) return -1;
|
|
||||||
|
|
||||||
if (a.stopInfo.arrivalTimestamp > b.stopInfo.arrivalTimestamp) return 1;
|
return this.onlineScenery.scheduledTrains
|
||||||
if (a.stopInfo.arrivalTimestamp < b.stopInfo.arrivalTimestamp) return -1;
|
.filter(
|
||||||
|
(ct) =>
|
||||||
|
ct.train.region == this.mainStore.region.id &&
|
||||||
|
this.chosenCheckpoint &&
|
||||||
|
ct.checkpointStop.stopNameRAW.toLowerCase() == this.chosenCheckpoint.toLowerCase()
|
||||||
|
)
|
||||||
|
.map((ct) => {
|
||||||
|
const trainStopStatus = getTrainStopStatus(
|
||||||
|
ct.checkpointStop,
|
||||||
|
ct.train.currentStationName,
|
||||||
|
this.station!.name
|
||||||
|
);
|
||||||
|
|
||||||
return a.stopInfo.departureTimestamp > b.stopInfo.departureTimestamp ? 1 : -1;
|
const trainStopIndex =
|
||||||
}) || []
|
ct.train.timetableData?.followingStops.findIndex(
|
||||||
);
|
(stop) => stop.stopName == ct.checkpointStop.stopName
|
||||||
|
) ?? -1;
|
||||||
|
|
||||||
|
let prevStationName = '',
|
||||||
|
nextStationName = '';
|
||||||
|
|
||||||
|
let departureLine: string | null = null;
|
||||||
|
let arrivingLine: string | null = null;
|
||||||
|
|
||||||
|
let prevDepartureLine: string | null = null,
|
||||||
|
nextArrivalLine: string | null = null;
|
||||||
|
|
||||||
|
if (trainStopIndex > -1 && ct.train.timetableData?.followingStops !== undefined) {
|
||||||
|
for (let i = trainStopIndex; i >= 0; i--) {
|
||||||
|
const stop = ct.train.timetableData.followingStops[i];
|
||||||
|
|
||||||
|
if (
|
||||||
|
/strong|podg\.|pe\./g.test(stop.stopName) &&
|
||||||
|
!prevStationName &&
|
||||||
|
i <= trainStopIndex - 1
|
||||||
|
)
|
||||||
|
prevStationName = stop.stopNameRAW.replace(/,.*/g, '');
|
||||||
|
|
||||||
|
if (
|
||||||
|
stop.arrivalLine != null &&
|
||||||
|
!arrivingLine &&
|
||||||
|
!/-|_|it|sbl/gi.test(stop.arrivalLine)
|
||||||
|
) {
|
||||||
|
arrivingLine = stop.arrivalLine;
|
||||||
|
prevDepartureLine =
|
||||||
|
ct.train.timetableData.followingStops[i - 1]?.departureLine || null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = trainStopIndex; i < ct.train.timetableData.followingStops.length; i++) {
|
||||||
|
const stop = ct.train.timetableData.followingStops[i];
|
||||||
|
|
||||||
|
if (
|
||||||
|
/strong|podg\.|pe\./g.test(stop.stopName) &&
|
||||||
|
!nextStationName &&
|
||||||
|
i > trainStopIndex
|
||||||
|
)
|
||||||
|
nextStationName = stop.stopNameRAW.replace(/,.*/g, '');
|
||||||
|
|
||||||
|
if (
|
||||||
|
stop.departureLine &&
|
||||||
|
!departureLine &&
|
||||||
|
!/-|_|it|sbl/gi.test(stop.departureLine)
|
||||||
|
) {
|
||||||
|
departureLine = stop.departureLine;
|
||||||
|
nextArrivalLine = ct.train.timetableData.followingStops[i + 1]?.arrivalLine || null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
checkpointStop: ct.checkpointStop,
|
||||||
|
train: ct.train,
|
||||||
|
prevDepartureLine,
|
||||||
|
nextArrivalLine,
|
||||||
|
departureLine,
|
||||||
|
arrivingLine,
|
||||||
|
prevStationName,
|
||||||
|
nextStationName,
|
||||||
|
status: trainStopStatus
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.sort((a, b) => {
|
||||||
|
if (stopStatusPriority.indexOf(a.status) - stopStatusPriority.indexOf(b.status) < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (stopStatusPriority.indexOf(a.status) - stopStatusPriority.indexOf(b.status) > 0)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
if (a.checkpointStop.arrivalTimestamp > b.checkpointStop.arrivalTimestamp) return 1;
|
||||||
|
if (a.checkpointStop.arrivalTimestamp < b.checkpointStop.arrivalTimestamp) return -1;
|
||||||
|
|
||||||
|
return a.checkpointStop.departureTimestamp > b.checkpointStop.departureTimestamp ? 1 : -1;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="general-status">
|
<div class="general-status">
|
||||||
<span
|
<span
|
||||||
:class="computedScheduledTrain.stopStatus"
|
:class="computedScheduledTrain.status"
|
||||||
:title="computedScheduledTrain.stopStatusDescription"
|
:title="computedScheduledTrain.stopStatusDescription"
|
||||||
>
|
>
|
||||||
{{ computedScheduledTrain.stopStatusIndicator }}
|
{{ computedScheduledTrain.stopStatusIndicator }}
|
||||||
@@ -11,25 +11,21 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, PropType } from 'vue';
|
import { defineComponent, PropType } from 'vue';
|
||||||
import { ScheduledTrain, StopStatus } from '../../typings/common';
|
import { StopStatus } from '../../typings/common';
|
||||||
|
import { SceneryTimetableRow } from './typings';
|
||||||
interface ScheduledTrainComp extends ScheduledTrain {
|
|
||||||
stopStatusIndicator: string;
|
|
||||||
stopStatusDescription: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
scheduledTrain: {
|
sceneryTimetableRow: {
|
||||||
type: Object as PropType<ScheduledTrain>,
|
type: Object as PropType<SceneryTimetableRow>,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
computedScheduledTrain(): ScheduledTrainComp {
|
computedScheduledTrain() {
|
||||||
const { prevDepartureLine, prevStationName, stopStatus, nextArrivalLine, nextStationName } =
|
const { prevDepartureLine, prevStationName, nextArrivalLine, nextStationName, status } =
|
||||||
this.scheduledTrain;
|
this.sceneryTimetableRow;
|
||||||
|
|
||||||
const prevDepartureIndicator = prevDepartureLine
|
const prevDepartureIndicator = prevDepartureLine
|
||||||
? `(${prevDepartureLine}) ${prevStationName}`
|
? `(${prevDepartureLine}) ${prevStationName}`
|
||||||
@@ -41,7 +37,7 @@ export default defineComponent({
|
|||||||
let stopStatusDescription = '',
|
let stopStatusDescription = '',
|
||||||
stopStatusIndicator = '';
|
stopStatusIndicator = '';
|
||||||
|
|
||||||
switch (stopStatus) {
|
switch (status) {
|
||||||
case StopStatus.ARRIVING:
|
case StopStatus.ARRIVING:
|
||||||
stopStatusIndicator = `${this.$t('timetables.from')}: ${prevDepartureIndicator}`;
|
stopStatusIndicator = `${this.$t('timetables.from')}: ${prevDepartureIndicator}`;
|
||||||
stopStatusDescription = this.$t('timetables.desc-arriving', {
|
stopStatusDescription = this.$t('timetables.desc-arriving', {
|
||||||
@@ -56,7 +52,7 @@ export default defineComponent({
|
|||||||
? `${this.$t('timetables.to')}: ${nextArrivalIndicator}`
|
? `${this.$t('timetables.to')}: ${nextArrivalIndicator}`
|
||||||
: `${this.$t('timetables.desc-end')}`;
|
: `${this.$t('timetables.desc-end')}`;
|
||||||
stopStatusDescription = nextArrivalLine
|
stopStatusDescription = nextArrivalLine
|
||||||
? this.$t(`timetables.desc-${stopStatus}`, { nextStationName, nextArrivalLine })
|
? this.$t(`timetables.desc-${status}`, { nextStationName, nextArrivalLine })
|
||||||
: '';
|
: '';
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -85,7 +81,7 @@ export default defineComponent({
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...this.scheduledTrain,
|
...this.sceneryTimetableRow,
|
||||||
stopStatusDescription,
|
stopStatusDescription,
|
||||||
stopStatusIndicator
|
stopStatusIndicator
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { StopStatus, Train, TrainStop } from '../../typings/common';
|
||||||
|
|
||||||
|
export interface SceneryTimetableRow {
|
||||||
|
checkpointStop: TrainStop;
|
||||||
|
train: Train;
|
||||||
|
prevDepartureLine: string | null;
|
||||||
|
nextArrivalLine: string | null;
|
||||||
|
departureLine: string | null;
|
||||||
|
arrivingLine: string | null;
|
||||||
|
prevStationName: string | null;
|
||||||
|
nextStationName: string | null;
|
||||||
|
status: StopStatus;
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { StopStatus, TrainStop } from '../../typings/common';
|
||||||
|
|
||||||
|
export const stopStatusPriority = [
|
||||||
|
StopStatus.ONLINE,
|
||||||
|
StopStatus.STOPPED,
|
||||||
|
StopStatus.DEPARTED,
|
||||||
|
StopStatus.ARRIVING,
|
||||||
|
StopStatus.DEPARTED_AWAY,
|
||||||
|
StopStatus.TERMINATED
|
||||||
|
];
|
||||||
|
|
||||||
|
export function getTrainStopStatus(
|
||||||
|
stopInfo: TrainStop,
|
||||||
|
currentStationName: string,
|
||||||
|
sceneryName: string
|
||||||
|
) {
|
||||||
|
if (stopInfo.terminatesHere && stopInfo.confirmed) {
|
||||||
|
return StopStatus.TERMINATED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stopInfo.terminatesHere && stopInfo.confirmed && currentStationName == sceneryName) {
|
||||||
|
return StopStatus.DEPARTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stopInfo.terminatesHere && stopInfo.confirmed && currentStationName != sceneryName) {
|
||||||
|
return StopStatus.DEPARTED_AWAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentStationName == sceneryName && !stopInfo.stopped) {
|
||||||
|
return StopStatus.ONLINE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentStationName == sceneryName && stopInfo.stopped) {
|
||||||
|
return StopStatus.STOPPED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentStationName != sceneryName) {
|
||||||
|
return StopStatus.ARRIVING;
|
||||||
|
}
|
||||||
|
|
||||||
|
return StopStatus.ONLINE;
|
||||||
|
}
|
||||||
@@ -15,7 +15,6 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { useStationFiltersStore } from '../../store/stationFiltersStore';
|
|
||||||
|
|
||||||
interface FilterOption {
|
interface FilterOption {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -40,15 +39,9 @@ export default defineComponent({
|
|||||||
|
|
||||||
emits: ['update:optionValue'],
|
emits: ['update:optionValue'],
|
||||||
|
|
||||||
setup() {
|
|
||||||
return {
|
|
||||||
filterStore: useStationFiltersStore()
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
'option.value'() {
|
'option.value'() {
|
||||||
this.filterStore.changeFilterValue(this.option.name, !this.option.value);
|
// this.filterStore.changeFilterValue(this.option.name, !this.option.value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -56,17 +49,17 @@ export default defineComponent({
|
|||||||
handleDbClick(e: Event) {
|
handleDbClick(e: Event) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
this.filterStore.lastClickedFilterId = this.option.id;
|
// this.filterStore.lastClickedFilterId = this.option.id;
|
||||||
// this.option.value = true;
|
// this.option.value = true;
|
||||||
this.$emit('update:optionValue', true);
|
this.$emit('update:optionValue', true);
|
||||||
|
|
||||||
this.filterStore.inputs.options
|
// this.filterStore.inputs.options
|
||||||
.filter((option) => {
|
// .filter((option) => {
|
||||||
return option.section == this.option.section && option.id != this.option.id;
|
// return option.section == this.option.section && option.id != this.option.id;
|
||||||
})
|
// })
|
||||||
.forEach((option) => {
|
// .forEach((option) => {
|
||||||
option.value = !this.option.value;
|
// option.value = !this.option.value;
|
||||||
});
|
// });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<button class="card-button btn--filled btn--image" @click="toggleCard">
|
<button class="card-button btn--filled btn--image" @click="toggleCard">
|
||||||
<img class="button_icon" src="/images/icon-filter2.svg" alt="filter icon" />
|
<img class="button_icon" src="/images/icon-filter2.svg" alt="filter icon" />
|
||||||
<p>[F] {{ $t('options.filters') }}</p>
|
<p>[F] {{ $t('options.filters') }}</p>
|
||||||
<span class="active-indicator" v-if="!filterStore.areFiltersAtDefault"></span>
|
<span class="active-indicator" v-if="changedFilters.length != 0"></span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<label for="scenery-search">
|
<label for="scenery-search">
|
||||||
@@ -33,29 +33,44 @@
|
|||||||
<div class="card_title flex">{{ $t('filters.title') }}</div>
|
<div class="card_title flex">{{ $t('filters.title') }}</div>
|
||||||
<p class="card_info" v-html="$t('filters.desc')"></p>
|
<p class="card_info" v-html="$t('filters.desc')"></p>
|
||||||
|
|
||||||
|
<div class="changed-filters" v-if="changedFilters.length > 0">
|
||||||
|
{{ $t('filters.changed-filters-count') }} <b>{{ changedFilters.length }}</b>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="changed-filters" v-else>{{ $t('filters.no-changed-filters') }}</div>
|
||||||
|
|
||||||
<section class="card_options">
|
<section class="card_options">
|
||||||
<div
|
<div
|
||||||
class="option-section"
|
class="option-section"
|
||||||
v-for="section in filterStore.inputs.optionSections"
|
v-for="(sectionFilters, sectionKey) in filtersSections"
|
||||||
:key="section"
|
:key="sectionKey"
|
||||||
>
|
>
|
||||||
<h3 class="text--primary">
|
<h3 class="text--primary">
|
||||||
{{ $t(`filters.sections.${section}`) }}
|
<span class="active-indicator" v-if="!areSectionFiltersDefault(sectionKey)"></span>
|
||||||
|
{{ $t(`filters.sections.${sectionKey}`) }}
|
||||||
<button @click="filterStore.resetSectionOptions(section)">RESET</button>
|
<button @click="resetSectionFilters(sectionKey)">RESET</button>
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<div class="section-inputs">
|
<div class="section-filters">
|
||||||
<FilterOption
|
<label
|
||||||
v-for="(option, i) in filterStore.inputs.options.filter(
|
v-for="filterKey in sectionFilters"
|
||||||
(o) => o.section == section
|
@click="() => (filters[filterKey] = !filters[filterKey])"
|
||||||
)"
|
@dblclick="setSingleSectionFilter(sectionKey, filterKey)"
|
||||||
v-model:optionValue="option.value"
|
:for="filterKey"
|
||||||
:option="option"
|
>
|
||||||
:key="i"
|
<input
|
||||||
/>
|
:checked="filters[filterKey]"
|
||||||
|
v-model="filters[filterKey]"
|
||||||
|
type="checkbox"
|
||||||
|
:class="sectionKey"
|
||||||
|
:name="filterKey"
|
||||||
|
/>
|
||||||
|
<span>
|
||||||
|
{{ $t(`filters.${filterKey}`) }}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -68,7 +83,7 @@
|
|||||||
<span>{{
|
<span>{{
|
||||||
minimumHours == 0
|
minimumHours == 0
|
||||||
? $t('filters.now')
|
? $t('filters.now')
|
||||||
: minimumHours < 8
|
: minimumHours < 7
|
||||||
? minimumHours + $t('filters.hour')
|
? minimumHours + $t('filters.hour')
|
||||||
: $t('filters.no-limit')
|
: $t('filters.no-limit')
|
||||||
}}</span>
|
}}</span>
|
||||||
@@ -76,21 +91,21 @@
|
|||||||
</span>
|
</span>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<datalist id="authors">
|
|
||||||
<option v-for="(author, i) in authors" :key="i" :value="author"></option>
|
|
||||||
</datalist>
|
|
||||||
|
|
||||||
<section class="card_authors-search">
|
<section class="card_authors-search">
|
||||||
<h3 class="section-header">{{ $t('filters.authors-search') }}</h3>
|
<h3 class="section-header">{{ $t('filters.authors-search') }}</h3>
|
||||||
|
|
||||||
|
<datalist id="authors" name="authors">
|
||||||
|
<option v-for="(author, i) in authorsHint" :key="i" :value="author"></option>
|
||||||
|
</datalist>
|
||||||
|
|
||||||
<form action="javascript:void(0);" @submit="handleAuthorsInput">
|
<form action="javascript:void(0);" @submit="handleAuthorsInput">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="author"
|
id="author"
|
||||||
list="authors"
|
list="authors"
|
||||||
name="authors"
|
name="authors"
|
||||||
|
v-model="authors"
|
||||||
:placeholder="$t('filters.authors-placeholder')"
|
:placeholder="$t('filters.authors-placeholder')"
|
||||||
v-model="authorsInputValue"
|
|
||||||
@focus="preventKeyDown = true"
|
@focus="preventKeyDown = true"
|
||||||
@blur="preventKeyDown = false"
|
@blur="preventKeyDown = false"
|
||||||
/>
|
/>
|
||||||
@@ -100,19 +115,18 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="card_sliders">
|
<section class="card_sliders">
|
||||||
<div class="slider" v-for="(slider, i) in filterStore.inputs.sliders" :key="i">
|
<div class="slider" v-for="(slider, i) in initSliders" :key="i">
|
||||||
<input
|
<input
|
||||||
class="slider-input"
|
class="slider-input"
|
||||||
type="range"
|
type="range"
|
||||||
:name="slider.name"
|
:name="slider.id"
|
||||||
:id="slider.id"
|
:id="slider.id"
|
||||||
:min="slider.minRange"
|
:min="slider.minRange"
|
||||||
:max="slider.maxRange"
|
:max="slider.maxRange"
|
||||||
:step="slider.step"
|
:step="slider.step"
|
||||||
v-model="slider.value"
|
v-model="filters[slider.id]"
|
||||||
@change="handleInput"
|
|
||||||
/>
|
/>
|
||||||
<span class="slider-value">{{ slider.value }}</span>
|
<span class="slider-value">{{ filters[slider.id] }}</span>
|
||||||
<div class="slider-content">
|
<div class="slider-content">
|
||||||
{{ $t(`filters.sliders.${slider.id}`) }}
|
{{ $t(`filters.sliders.${slider.id}`) }}
|
||||||
</div>
|
</div>
|
||||||
@@ -133,9 +147,9 @@
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
class="btn--action"
|
class="btn--action"
|
||||||
|
:disabled="changedFilters.length == 0"
|
||||||
|
:data-disabled="changedFilters.length == 0"
|
||||||
@click="resetFilters"
|
@click="resetFilters"
|
||||||
:disabled="filterStore.areFiltersAtDefault"
|
|
||||||
:data-disabled="filterStore.areFiltersAtDefault"
|
|
||||||
>
|
>
|
||||||
[R] {{ $t('filters.reset') }}
|
[R] {{ $t('filters.reset') }}
|
||||||
</button>
|
</button>
|
||||||
@@ -151,22 +165,36 @@
|
|||||||
import { defineComponent, inject } from 'vue';
|
import { defineComponent, inject } from 'vue';
|
||||||
import keyMixin from '../../mixins/keyMixin';
|
import keyMixin from '../../mixins/keyMixin';
|
||||||
import routerMixin from '../../mixins/routerMixin';
|
import routerMixin from '../../mixins/routerMixin';
|
||||||
import { useStationFiltersStore } from '../../store/stationFiltersStore';
|
|
||||||
import { useMainStore } from '../../store/mainStore';
|
import { useMainStore } from '../../store/mainStore';
|
||||||
|
|
||||||
import FilterOption from './FilterOption.vue';
|
import FilterOption from './FilterOption.vue';
|
||||||
import StorageManager from '../../managers/storageManager';
|
import StorageManager from '../../managers/storageManager';
|
||||||
|
|
||||||
|
import {
|
||||||
|
filtersSections,
|
||||||
|
initSliders,
|
||||||
|
initFilters,
|
||||||
|
getChangedFilters
|
||||||
|
} from '../../managers/stationFilterManager';
|
||||||
|
|
||||||
|
import { StationFilterSection } from '../../managers/stationFilterManager';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { watch } from 'vue';
|
||||||
|
|
||||||
|
const STORAGE_KEY = 'options_saved';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { FilterOption },
|
components: { FilterOption },
|
||||||
mixins: [keyMixin, routerMixin],
|
mixins: [keyMixin, routerMixin],
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
saveOptions: false,
|
saveOptions: false,
|
||||||
STORAGE_KEY: 'options_saved',
|
|
||||||
|
|
||||||
authorsInputValue: '',
|
filtersSections,
|
||||||
|
initSliders,
|
||||||
|
|
||||||
minimumHours: 0,
|
minimumHours: 0,
|
||||||
|
authors: '',
|
||||||
|
|
||||||
currentRegion: { id: '', value: '' },
|
currentRegion: { id: '', value: '' },
|
||||||
|
|
||||||
@@ -180,22 +208,33 @@ export default defineComponent({
|
|||||||
setup() {
|
setup() {
|
||||||
const isVisible = inject('isFilterCardVisible');
|
const isVisible = inject('isFilterCardVisible');
|
||||||
const store = useMainStore();
|
const store = useMainStore();
|
||||||
const filterStore = useStationFiltersStore();
|
|
||||||
|
const filters = inject('StationsView_filters') as Record<string, any>;
|
||||||
|
|
||||||
|
const changedFilters = computed(() => getChangedFilters(filters));
|
||||||
|
|
||||||
|
// Save filters to persistent storage
|
||||||
|
watch(filters, (value) => {
|
||||||
|
if (!StorageManager.isRegistered(STORAGE_KEY)) return;
|
||||||
|
|
||||||
|
Object.keys(value).forEach((filterKey) => {
|
||||||
|
StorageManager.setValue(filterKey, filters[filterKey]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isVisible,
|
isVisible,
|
||||||
store,
|
store,
|
||||||
filterStore
|
filters,
|
||||||
|
changedFilters
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.saveOptions = StorageManager.isRegistered(this.STORAGE_KEY);
|
this.saveOptions = StorageManager.isRegistered(STORAGE_KEY);
|
||||||
|
|
||||||
if (StorageManager.isRegistered('onlineFromHours') && this.saveOptions) {
|
if (StorageManager.isRegistered('onlineFromHours') && this.saveOptions) {
|
||||||
this.minimumHours = StorageManager.getNumericValue('onlineFromHours');
|
this.minimumHours = StorageManager.getNumericValue('onlineFromHours');
|
||||||
|
|
||||||
this.changeNumericFilterValue('onlineFromHours', this.minimumHours);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currentRegion = this.store.region;
|
this.currentRegion = this.store.region;
|
||||||
@@ -214,7 +253,7 @@ export default defineComponent({
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
authors() {
|
authorsHint() {
|
||||||
return this.store.stationList
|
return this.store.stationList
|
||||||
.reduce((acc, station) => {
|
.reduce((acc, station) => {
|
||||||
station.generalInfo?.authors?.forEach((author) => {
|
station.generalInfo?.authors?.forEach((author) => {
|
||||||
@@ -258,61 +297,63 @@ export default defineComponent({
|
|||||||
this.scrollTop = (e.target as HTMLElement).scrollTop;
|
this.scrollTop = (e.target as HTMLElement).scrollTop;
|
||||||
},
|
},
|
||||||
|
|
||||||
handleInput(e: Event) {
|
|
||||||
const target = e.target as HTMLInputElement;
|
|
||||||
|
|
||||||
this.filterStore.changeFilterValue(target.name, target.value);
|
|
||||||
|
|
||||||
if (this.saveOptions) StorageManager.setStringValue(target.name, target.value);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleAuthorsInput() {
|
handleAuthorsInput() {
|
||||||
this.filterStore.changeFilterValue('authors', this.authorsInputValue);
|
this.filters['authors'] = this.authors;
|
||||||
|
// if (this.saveOptions) StorageManager.setStringValue('authors', target.value);
|
||||||
if (this.saveOptions) StorageManager.setStringValue('authors', this.authorsInputValue);
|
|
||||||
},
|
|
||||||
|
|
||||||
changeNumericFilterValue(name: string, value: number, saveToStorage = false) {
|
|
||||||
this.filterStore.changeFilterValue(name, value);
|
|
||||||
if (this.saveOptions && saveToStorage) StorageManager.setNumericValue(name, value);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
subHour() {
|
subHour() {
|
||||||
this.minimumHours = this.minimumHours < 1 ? 8 : this.minimumHours - 1;
|
this.minimumHours = this.minimumHours < 1 ? 7 : this.minimumHours - 1;
|
||||||
|
this.filters['onlineFromHours'] = this.minimumHours;
|
||||||
this.changeNumericFilterValue('onlineFromHours', this.minimumHours, true);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
addHour() {
|
addHour() {
|
||||||
this.minimumHours = this.minimumHours > 7 ? 0 : this.minimumHours + 1;
|
this.minimumHours = this.minimumHours > 6 ? 0 : this.minimumHours + 1;
|
||||||
|
this.filters['onlineFromHours'] = this.minimumHours;
|
||||||
this.changeNumericFilterValue('onlineFromHours', this.minimumHours, true);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
saveFilters() {
|
saveFilters() {
|
||||||
this.saveOptions = !this.saveOptions;
|
this.saveOptions = !this.saveOptions;
|
||||||
|
|
||||||
if (!this.saveOptions) {
|
if (!this.saveOptions) {
|
||||||
StorageManager.unregisterStorage(this.STORAGE_KEY);
|
StorageManager.unregisterStorage(STORAGE_KEY);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
StorageManager.registerStorage(this.STORAGE_KEY);
|
StorageManager.registerStorage(STORAGE_KEY);
|
||||||
|
|
||||||
this.filterStore.inputs.options.forEach((option) =>
|
Object.keys(this.filters).forEach((filterKey) => {
|
||||||
StorageManager.setBooleanValue(option.name, !option.value)
|
StorageManager.setValue(filterKey, this.filters[filterKey]);
|
||||||
);
|
});
|
||||||
this.filterStore.inputs.sliders.forEach((slider) =>
|
|
||||||
StorageManager.setNumericValue(slider.name, slider.value)
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
resetFilters() {
|
resetFilters() {
|
||||||
this.authorsInputValue = '';
|
// Reset local model values
|
||||||
|
|
||||||
this.minimumHours = 0;
|
this.minimumHours = 0;
|
||||||
this.changeNumericFilterValue('onlineFromHours', this.minimumHours, true);
|
this.authors = '';
|
||||||
this.filterStore.resetFilters();
|
|
||||||
|
// Reset global filters
|
||||||
|
Object.keys(this.filters).forEach((filterKey) => {
|
||||||
|
this.filters[filterKey] = (initFilters as any)[filterKey];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
areSectionFiltersDefault(sectionKey: StationFilterSection) {
|
||||||
|
return filtersSections[sectionKey].every((filterKey) => {
|
||||||
|
return this.filters[filterKey] == initFilters[filterKey];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
resetSectionFilters(sectionKey: StationFilterSection) {
|
||||||
|
filtersSections[sectionKey].forEach((filterKey) => {
|
||||||
|
this.filters[filterKey] = initFilters[filterKey];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
setSingleSectionFilter(sectionKey: StationFilterSection, chosenKey: string) {
|
||||||
|
filtersSections[sectionKey].forEach((filterKey) => {
|
||||||
|
if (filterKey != chosenKey) this.filters[filterKey] = initFilters[filterKey];
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
closeCard() {
|
closeCard() {
|
||||||
@@ -346,6 +387,11 @@ h3.section-header {
|
|||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.changed-filters {
|
||||||
|
background-color: #111;
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
.card_controls {
|
.card_controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
@@ -374,28 +420,6 @@ h3.section-header {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card_regions {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
label > input {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
label > span {
|
|
||||||
padding: 0.25em 0.5em;
|
|
||||||
margin: 0 0.25em;
|
|
||||||
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
background-color: gray;
|
|
||||||
|
|
||||||
&.checked {
|
|
||||||
background-color: seagreen;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.card_timestamp {
|
.card_timestamp {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -441,6 +465,52 @@ h3.section-header {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.section-filters {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 0.5em;
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-filters > label {
|
||||||
|
position: relative;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
|
||||||
|
span {
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
padding: 0.25em;
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: forestgreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
span:hover {
|
||||||
|
background-color: #22aa22;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='checkbox'] {
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
|
&:checked + span {
|
||||||
|
background-color: #444;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #555;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-visible + span {
|
||||||
|
outline: 1px solid $accentCol;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.card_actions {
|
.card_actions {
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
|
|
||||||
@@ -475,35 +545,18 @@ h3.section-header {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-inputs {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
gap: 0.5em;
|
|
||||||
margin: 1em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quick-actions div {
|
|
||||||
display: flex;
|
|
||||||
margin: 1em 0;
|
|
||||||
gap: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider {
|
.slider {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 0.25em;
|
||||||
|
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
|
|
||||||
&-value {
|
&-value {
|
||||||
color: $accentCol;
|
color: $accentCol;
|
||||||
margin-right: 0.5em;
|
|
||||||
padding: 0.1em 0.2em;
|
padding: 0.1em 0.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-content {
|
|
||||||
flex-grow: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-input {
|
&-input {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
appearance: none;
|
appearance: none;
|
||||||
@@ -512,7 +565,6 @@ h3.section-header {
|
|||||||
outline: none;
|
outline: none;
|
||||||
|
|
||||||
min-width: 25%;
|
min-width: 25%;
|
||||||
max-width: 120px;
|
|
||||||
|
|
||||||
&:focus-visible ~ * {
|
&:focus-visible ~ * {
|
||||||
color: gold;
|
color: gold;
|
||||||
@@ -587,5 +639,14 @@ h3.section-header {
|
|||||||
.card_controls > button.card-button > p {
|
.card_controls > button.card-button > p {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.slider {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&-input {
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -21,8 +21,8 @@
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
•
|
•
|
||||||
{{ $t('station-stats.med-timetable-count') }}
|
{{ $t('station-stats.avg-timetable-count') }}
|
||||||
<b>{{ medTimetableCount }}</b>
|
<b>{{ avgTimetableCount.toFixed(2) }}</b>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@@ -89,27 +89,19 @@ export default defineComponent({
|
|||||||
return activeDispatchers.length != 0 ? activeTrains.length / activeDispatchers.length : 0;
|
return activeDispatchers.length != 0 ? activeTrains.length / activeDispatchers.length : 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
medTimetableCount() {
|
avgTimetableCount() {
|
||||||
const scheduledTrainsArr = this.mainStore.activeSceneryList
|
const regionSceneries = this.mainStore.activeSceneryList.filter((sc) => {
|
||||||
.reduce<number[]>((acc, sc) => {
|
return sc.region == this.mainStore.region.id;
|
||||||
if (sc.region != this.mainStore.region.id) return acc;
|
});
|
||||||
|
|
||||||
acc.push(sc.scheduledTrainCount.all);
|
const timetableCountSum = regionSceneries.reduce((acc, sc) => {
|
||||||
|
acc += sc.scheduledTrainCount.all;
|
||||||
|
return acc;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
return acc;
|
if (regionSceneries.length == 0) return 0;
|
||||||
}, [])
|
|
||||||
.sort((a, b) => Math.sign(a - b));
|
|
||||||
|
|
||||||
if (scheduledTrainsArr.length == 0) return 0;
|
return timetableCountSum / regionSceneries.length;
|
||||||
|
|
||||||
if (scheduledTrainsArr.length % 2 == 0) {
|
|
||||||
let v1 = scheduledTrainsArr[scheduledTrainsArr.length / 2];
|
|
||||||
let v2 = scheduledTrainsArr[scheduledTrainsArr.length / 2 - 1];
|
|
||||||
|
|
||||||
return (v1 + v2) / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
return scheduledTrainsArr[~~(scheduledTrainsArr.length / 2)];
|
|
||||||
},
|
},
|
||||||
|
|
||||||
trackCount() {
|
trackCount() {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="station_table">
|
<section class="station_table">
|
||||||
<Loading
|
<Loading
|
||||||
v-if="apiStore.dataStatuses.connection == Status.Loading && displayedStations.length == 0"
|
v-if="apiStore.dataStatuses.connection == Status.Loading && filteredStationList.length == 0"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="table_wrapper" v-else-if="displayedStations.length > 0">
|
<div class="table_wrapper" v-else-if="filteredStationList.length > 0">
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -20,8 +20,8 @@
|
|||||||
|
|
||||||
<img
|
<img
|
||||||
class="sort-icon"
|
class="sort-icon"
|
||||||
v-if="sorterActive.headerName == headerName"
|
v-if="activeSorter.headerName == headerName"
|
||||||
:src="`/images/icon-arrow-${sorterActive.dir == 1 ? 'asc' : 'desc'}.svg`"
|
:src="`/images/icon-arrow-${activeSorter.dir == 1 ? 'asc' : 'desc'}.svg`"
|
||||||
alt="sort icon"
|
alt="sort icon"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
@@ -43,8 +43,8 @@
|
|||||||
|
|
||||||
<img
|
<img
|
||||||
class="sort-icon"
|
class="sort-icon"
|
||||||
v-if="sorterActive.headerName == headerName"
|
v-if="activeSorter.headerName == headerName"
|
||||||
:src="`/images/icon-arrow-${sorterActive.dir == 1 ? 'asc' : 'desc'}.svg`"
|
:src="`/images/icon-arrow-${activeSorter.dir == 1 ? 'asc' : 'desc'}.svg`"
|
||||||
alt="sort icon"
|
alt="sort icon"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr
|
<tr
|
||||||
v-for="station in displayedStations"
|
v-for="station in filteredStationList"
|
||||||
:class="{ 'last-selected': lastSelectedStationName == station.name }"
|
:class="{ 'last-selected': lastSelectedStationName == station.name }"
|
||||||
:key="station.name"
|
:key="station.name"
|
||||||
@click.left="setScenery(station.name)"
|
@click.left="setScenery(station.name)"
|
||||||
@@ -122,7 +122,7 @@
|
|||||||
<span v-if="station.onlineInfo?.dispatcherName">
|
<span v-if="station.onlineInfo?.dispatcherName">
|
||||||
<b
|
<b
|
||||||
v-if="apiStore.donatorsData.includes(station.onlineInfo.dispatcherName)"
|
v-if="apiStore.donatorsData.includes(station.onlineInfo.dispatcherName)"
|
||||||
@click.stop="openDonationModal"
|
@click.stop="openDonationCard"
|
||||||
data-tooltip-type="DonatorTooltip"
|
data-tooltip-type="DonatorTooltip"
|
||||||
:data-tooltip-content="$t('donations.dispatcher-message')"
|
:data-tooltip-content="$t('donations.dispatcher-message')"
|
||||||
>
|
>
|
||||||
@@ -302,65 +302,73 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="no-stations" v-else>
|
<div class="no-stations" v-else>
|
||||||
{{ $t('sceneries.no-stations') }} (region: <b>{{ mainStore.region.name }}</b
|
<div>
|
||||||
>)
|
{{ $t('sceneries.no-stations') }} (region: <b>{{ mainStore.region.name }}</b
|
||||||
|
>)
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text--primary" v-if="getChangedFilters(filters).length != 0">
|
||||||
|
⚠ {{ $t('sceneries.active-filters') }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent, inject, computed } from 'vue';
|
||||||
|
import StationStatusBadge from '../Global/StationStatusBadge.vue';
|
||||||
|
import Loading from '../Global/Loading.vue';
|
||||||
import dateMixin from '../../mixins/dateMixin';
|
import dateMixin from '../../mixins/dateMixin';
|
||||||
import styleMixin from '../../mixins/styleMixin';
|
import styleMixin from '../../mixins/styleMixin';
|
||||||
import { useStationFiltersStore } from '../../store/stationFiltersStore';
|
|
||||||
import { useMainStore } from '../../store/mainStore';
|
|
||||||
import Loading from '../Global/Loading.vue';
|
|
||||||
import { HeadIdsTypes, headIconsIds, headIds } from '../../scripts/data/stationHeaderNames';
|
|
||||||
import StationStatusBadge from '../Global/StationStatusBadge.vue';
|
|
||||||
import { Station, Status } from '../../typings/common';
|
|
||||||
import { useApiStore } from '../../store/apiStore';
|
import { useApiStore } from '../../store/apiStore';
|
||||||
|
import { useMainStore } from '../../store/mainStore';
|
||||||
|
import { Status } from '../../typings/common';
|
||||||
import { useTooltipStore } from '../../store/tooltipStore';
|
import { useTooltipStore } from '../../store/tooltipStore';
|
||||||
|
import { getChangedFilters } from '../../managers/stationFilterManager';
|
||||||
|
import { ActiveSorter, HeadIdsType, headIconsIds, headIds } from './typings';
|
||||||
|
import { filterStations, sortStations } from './utils';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
emits: ['toggleDonationModal'],
|
emits: ['toggleDonationCard'],
|
||||||
|
|
||||||
components: { Loading, StationStatusBadge },
|
components: { Loading, StationStatusBadge },
|
||||||
mixins: [styleMixin, dateMixin],
|
mixins: [styleMixin, dateMixin],
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
headIconsIds,
|
headIconsIds,
|
||||||
headIds,
|
headIds,
|
||||||
lastSelectedStationName: ''
|
lastSelectedStationName: '',
|
||||||
|
getChangedFilters
|
||||||
}),
|
}),
|
||||||
|
|
||||||
computed: {
|
|
||||||
sorterActive() {
|
|
||||||
return this.stationFiltersStore.sorterActive;
|
|
||||||
},
|
|
||||||
|
|
||||||
displayedStations() {
|
|
||||||
return this.stationFiltersStore.filteredStationList;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
const mainStore = useMainStore();
|
const mainStore = useMainStore();
|
||||||
const apiStore = useApiStore();
|
const apiStore = useApiStore();
|
||||||
const tooltipStore = useTooltipStore();
|
const tooltipStore = useTooltipStore();
|
||||||
|
|
||||||
const stationFiltersStore = useStationFiltersStore();
|
const filters = inject('StationsView_filters') as Record<string, any>;
|
||||||
|
const activeSorter = inject('StationsView_activeSorter') as ActiveSorter;
|
||||||
|
|
||||||
|
const filteredStationList = computed(() =>
|
||||||
|
mainStore.allStationInfo
|
||||||
|
.filter((station) => filterStations(station, filters))
|
||||||
|
.sort((a, b) => sortStations(a, b, activeSorter))
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
Status: Status.Data,
|
Status: Status.Data,
|
||||||
stationFiltersStore,
|
|
||||||
mainStore,
|
mainStore,
|
||||||
apiStore,
|
apiStore,
|
||||||
tooltipStore
|
tooltipStore,
|
||||||
|
filters,
|
||||||
|
filteredStationList,
|
||||||
|
activeSorter
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
setScenery(name: string) {
|
setScenery(name: string) {
|
||||||
const station = this.displayedStations.find((station) => station.name === name);
|
const station = this.filteredStationList.find((station) => station.name === name);
|
||||||
|
|
||||||
if (!station) return;
|
if (!station) return;
|
||||||
|
|
||||||
@@ -376,8 +384,8 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
openDonationModal(e: Event) {
|
openDonationCard(e: Event) {
|
||||||
this.$emit('toggleDonationModal', true);
|
this.$emit('toggleDonationCard', true);
|
||||||
this.mainStore.modalLastClickedTarget = e.target;
|
this.mainStore.modalLastClickedTarget = e.target;
|
||||||
this.tooltipStore.hide();
|
this.tooltipStore.hide();
|
||||||
},
|
},
|
||||||
@@ -388,10 +396,14 @@ export default defineComponent({
|
|||||||
window.open(url, '_blank');
|
window.open(url, '_blank');
|
||||||
},
|
},
|
||||||
|
|
||||||
changeSorter(headerName: HeadIdsTypes) {
|
changeSorter(headerName: HeadIdsType) {
|
||||||
if (headerName == 'general') return;
|
if (headerName == 'general') return;
|
||||||
|
|
||||||
this.stationFiltersStore.changeSorter(headerName);
|
if (headerName == this.activeSorter.headerName)
|
||||||
|
this.activeSorter.dir = -1 * this.activeSorter.dir;
|
||||||
|
else this.activeSorter.dir = 1;
|
||||||
|
|
||||||
|
this.activeSorter.headerName = headerName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -413,11 +425,10 @@ $rowCol: #424242;
|
|||||||
|
|
||||||
.no-stations {
|
.no-stations {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 1.5em;
|
font-size: 1.25em;
|
||||||
|
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
|
|
||||||
background: #1a1a1a;
|
background: #1a1a1a;
|
||||||
|
line-height: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
|
|||||||
@@ -5,56 +5,29 @@ export interface FilterOption {
|
|||||||
defaultValue: boolean;
|
defaultValue: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Filter {
|
export const headIds = [
|
||||||
[key: string]: boolean | number | string;
|
'station',
|
||||||
default: boolean;
|
'min-lvl',
|
||||||
notDefault: boolean;
|
'status',
|
||||||
real: boolean;
|
'dispatcher',
|
||||||
fictional: boolean;
|
'dispatcher-lvl',
|
||||||
SPK: boolean;
|
'routes-single',
|
||||||
SCS: boolean;
|
'routes-double',
|
||||||
SPE: boolean;
|
'general'
|
||||||
SUP: boolean;
|
] as const;
|
||||||
noSUP: boolean;
|
|
||||||
ASDEK: boolean;
|
export const headIconsIds = [
|
||||||
noASDEK: boolean;
|
'user',
|
||||||
ręczne: boolean;
|
'like',
|
||||||
'ręczne+SPK': boolean;
|
'spawn',
|
||||||
'ręczne+SCS': boolean;
|
'timetableAll',
|
||||||
mechaniczne: boolean;
|
'timetableUnconfirmed',
|
||||||
'mechaniczne+SPK': boolean;
|
'timetableConfirmed'
|
||||||
'mechaniczne+SCS': boolean;
|
] as const;
|
||||||
SBL: boolean;
|
|
||||||
PBL: boolean;
|
export type HeadIdsType = (typeof headIds)[number] | (typeof headIconsIds)[number];
|
||||||
współczesna: boolean;
|
|
||||||
kształtowa: boolean;
|
export interface ActiveSorter {
|
||||||
historyczna: boolean;
|
headerName: HeadIdsType;
|
||||||
mieszana: boolean;
|
dir: number;
|
||||||
minLevel: number;
|
|
||||||
maxLevel: number;
|
|
||||||
minOneWayCatenary: number;
|
|
||||||
minOneWay: number;
|
|
||||||
minTwoWayCatenary: number;
|
|
||||||
minTwoWay: number;
|
|
||||||
minVmax: number;
|
|
||||||
maxVmax: number;
|
|
||||||
'no-1track': boolean;
|
|
||||||
'no-2track': boolean;
|
|
||||||
'include-selected': boolean;
|
|
||||||
free: boolean;
|
|
||||||
occupied: boolean;
|
|
||||||
nonPublic: boolean;
|
|
||||||
unavailable: boolean;
|
|
||||||
abandoned: boolean;
|
|
||||||
endingStatus: boolean;
|
|
||||||
afkStatus: boolean;
|
|
||||||
noSpaceStatus: boolean;
|
|
||||||
unavailableStatus: boolean;
|
|
||||||
unsignedStatus: boolean;
|
|
||||||
authors: string;
|
|
||||||
onlineFromHours: number;
|
|
||||||
withActiveTimetables: boolean;
|
|
||||||
withoutActiveTimetables: boolean;
|
|
||||||
junction: boolean;
|
|
||||||
nonJunction: boolean;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,275 @@
|
|||||||
|
import { ActiveSorter } from '../../components/StationsView/typings';
|
||||||
|
import { ActiveScenery, StationGeneralInfo, Status } from '../../typings/common';
|
||||||
|
import { Station } from '../../typings/common';
|
||||||
|
|
||||||
|
const dispatcherStatusPriority = [
|
||||||
|
Status.ActiveDispatcher.UNKNOWN,
|
||||||
|
Status.ActiveDispatcher.INVALID,
|
||||||
|
Status.ActiveDispatcher.NOT_LOGGED_IN,
|
||||||
|
Status.ActiveDispatcher.UNAVAILABLE,
|
||||||
|
Status.ActiveDispatcher.AFK,
|
||||||
|
Status.ActiveDispatcher.ENDING,
|
||||||
|
Status.ActiveDispatcher.NO_SPACE,
|
||||||
|
undefined
|
||||||
|
];
|
||||||
|
|
||||||
|
const filtersAssociations: Record<string, string> = {
|
||||||
|
mechaniczne: 'mechanical',
|
||||||
|
ręczne: 'manual',
|
||||||
|
'mechaniczne+SPK': 'SPK-M',
|
||||||
|
'ręczne+SPK': 'SPK-R',
|
||||||
|
'mechaniczne+SCS': 'SCS-M',
|
||||||
|
'ręczne+SCS': 'SCS-R',
|
||||||
|
współczesna: 'modern',
|
||||||
|
historyczna: 'historical',
|
||||||
|
kształtowa: 'semaphores',
|
||||||
|
mieszana: 'mixed'
|
||||||
|
};
|
||||||
|
|
||||||
|
function filterStatusSection(
|
||||||
|
filters: Record<string, any>,
|
||||||
|
{ dispatcherStatus, dispatcherTimestamp }: ActiveScenery
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
(filters['endingStatus'] && dispatcherStatus == Status.ActiveDispatcher.ENDING) ||
|
||||||
|
(filters['unavailableStatus'] &&
|
||||||
|
(dispatcherStatus == Status.ActiveDispatcher.UNAVAILABLE ||
|
||||||
|
dispatcherStatus == Status.ActiveDispatcher.NOT_LOGGED_IN)) ||
|
||||||
|
(filters['afkStatus'] && dispatcherStatus == Status.ActiveDispatcher.AFK) ||
|
||||||
|
(filters['noSpaceStatus'] && dispatcherStatus == Status.ActiveDispatcher.NO_SPACE) ||
|
||||||
|
(filters['occupied'] && dispatcherStatus != Status.ActiveDispatcher.FREE) ||
|
||||||
|
(filters['onlineFromHours'] > 0 &&
|
||||||
|
(dispatcherTimestamp ?? 0) <= Date.now() + filters['onlineFromHours'] * 3600000)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterTimetablesSection(filters: Record<string, any>, station: Station) {
|
||||||
|
return (
|
||||||
|
(filters['withoutActiveTimetables'] &&
|
||||||
|
(!station.onlineInfo || station.onlineInfo.scheduledTrainCount.all == 0)) ||
|
||||||
|
(filters['withActiveTimetables'] &&
|
||||||
|
station.onlineInfo &&
|
||||||
|
(station.onlineInfo.scheduledTrainCount.all != 0 ||
|
||||||
|
station.onlineInfo.dispatcherStatus == Status.ActiveDispatcher.FREE))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterAccessibilitySection(filters: Record<string, any>, station: Station) {
|
||||||
|
if (
|
||||||
|
filters['nonPublic'] &&
|
||||||
|
(!station.generalInfo || station.generalInfo.availability == 'nonPublic')
|
||||||
|
)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (!station.generalInfo) return false;
|
||||||
|
|
||||||
|
const { availability } = station.generalInfo;
|
||||||
|
|
||||||
|
return (
|
||||||
|
(filters['unavailable'] && availability == 'unavailable' && !station.onlineInfo) ||
|
||||||
|
(filters['abandoned'] && availability == 'abandoned' && !station.onlineInfo) ||
|
||||||
|
(filters['default'] && availability == 'default') ||
|
||||||
|
(filters['notDefault'] &&
|
||||||
|
availability != 'default' &&
|
||||||
|
availability != 'abandoned' &&
|
||||||
|
availability != 'unavailable')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterRealitySection(filters: Record<string, any>, generalInfo: StationGeneralInfo) {
|
||||||
|
return (filters['real'] && generalInfo.lines) || (filters['fictional'] && !generalInfo.lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterProgramsSection(filters: Record<string, any>, generalInfo: StationGeneralInfo) {
|
||||||
|
return (
|
||||||
|
(filters['SUP'] && generalInfo.SUP) ||
|
||||||
|
(filters['noSUP'] && !generalInfo.SUP) ||
|
||||||
|
(filters['ASDEK'] && generalInfo.ASDEK) ||
|
||||||
|
(filters['noASDEK'] && !generalInfo.ASDEK)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterControlsSection(filters: Record<string, any>, generalInfo: StationGeneralInfo) {
|
||||||
|
return (
|
||||||
|
filters[generalInfo.controlType] == true ||
|
||||||
|
filters[filtersAssociations[generalInfo.controlType]] == true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterSignalsSection(filters: Record<string, any>, generalInfo: StationGeneralInfo) {
|
||||||
|
return (
|
||||||
|
filters[generalInfo.signalType] == true ||
|
||||||
|
filters[filtersAssociations[generalInfo.signalType]] == true ||
|
||||||
|
(filters['SBL'] && generalInfo.routes.sblNames.length > 0) ||
|
||||||
|
(filters['PBL'] && generalInfo.routes.sblNames.length == 0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterStationType(filters: Record<string, any>, generalInfo: StationGeneralInfo) {
|
||||||
|
const singleTracks = generalInfo.routes.single.filter((r) => !r.isInternal);
|
||||||
|
const doubleTracks = generalInfo.routes.double.filter((r) => !r.isInternal);
|
||||||
|
|
||||||
|
let isJunction = singleTracks.length > 0 && doubleTracks.length > 0;
|
||||||
|
|
||||||
|
return (filters['junction'] && isJunction) || (filters['nonJunction'] && !isJunction);
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterSliderValues(filters: Record<string, any>, generalInfo: StationGeneralInfo) {
|
||||||
|
const { availability, reqLevel, routes } = generalInfo;
|
||||||
|
|
||||||
|
const otherAvailability =
|
||||||
|
availability == 'nonPublic' || availability == 'unavailable' || availability == 'abandoned';
|
||||||
|
|
||||||
|
return (
|
||||||
|
filters['minLevel'] > reqLevel + (otherAvailability ? 1 : 0) ||
|
||||||
|
filters['maxLevel'] < reqLevel + (otherAvailability ? 1 : 0) ||
|
||||||
|
filters['minVmax'] > routes.maxRouteSpeed ||
|
||||||
|
filters['maxVmax'] < routes.minRouteSpeed ||
|
||||||
|
(filters['no-1track'] && routes.single.length != 0) ||
|
||||||
|
(filters['no-2track'] && routes.double.length != 0) ||
|
||||||
|
filters['minOneWayCatenary'] > routes.singleElectrifiedNames.length ||
|
||||||
|
filters['minOneWay'] > routes.singleOtherNames.length ||
|
||||||
|
filters['minTwoWayCatenary'] > routes.doubleElectrifiedNames.length ||
|
||||||
|
filters['minTwoWay'] > routes.doubleOtherNames.length
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterInputValues(filters: Record<string, any>, generalInfo: StationGeneralInfo) {
|
||||||
|
return (
|
||||||
|
filters['authors'].length > 3 &&
|
||||||
|
!generalInfo.authors
|
||||||
|
?.map((a) => a.toLocaleLowerCase())
|
||||||
|
.includes(filters['authors'].toLocaleLowerCase())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const sortStations = (a: Station, b: Station, sorter: ActiveSorter) => {
|
||||||
|
let diff = 0;
|
||||||
|
|
||||||
|
switch (sorter.headerName) {
|
||||||
|
case 'station':
|
||||||
|
return sorter.dir == 1 ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name);
|
||||||
|
|
||||||
|
case 'min-lvl':
|
||||||
|
diff = (a.generalInfo?.reqLevel || 0) - (b.generalInfo?.reqLevel || 0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'status':
|
||||||
|
diff =
|
||||||
|
(a.onlineInfo?.dispatcherTimestamp ??
|
||||||
|
dispatcherStatusPriority.indexOf(a.onlineInfo?.dispatcherStatus)) -
|
||||||
|
(b.onlineInfo?.dispatcherTimestamp ??
|
||||||
|
dispatcherStatusPriority.indexOf(b.onlineInfo?.dispatcherStatus));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'dispatcher':
|
||||||
|
if (
|
||||||
|
(a.onlineInfo?.dispatcherName.toLowerCase() || '') >
|
||||||
|
(b.onlineInfo?.dispatcherName.toLowerCase() || '')
|
||||||
|
)
|
||||||
|
return sorter.dir;
|
||||||
|
if (
|
||||||
|
(a.onlineInfo?.dispatcherName.toLowerCase() || '') <
|
||||||
|
(b.onlineInfo?.dispatcherName.toLowerCase() || '')
|
||||||
|
)
|
||||||
|
return -sorter.dir;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'dispatcher-lvl':
|
||||||
|
diff = (a.onlineInfo?.dispatcherExp || 0) - (b.onlineInfo?.dispatcherExp || 0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'routes-single':
|
||||||
|
diff =
|
||||||
|
(a.generalInfo?.routes.single.filter((r) => !r.hidden && !r.isInternal).length ?? -1) -
|
||||||
|
(b.generalInfo?.routes.single.filter((r) => !r.hidden && !r.isInternal).length ?? -1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'routes-double':
|
||||||
|
diff =
|
||||||
|
(a.generalInfo?.routes.double.filter((r) => !r.hidden && !r.isInternal).length ?? -1) -
|
||||||
|
(b.generalInfo?.routes.double.filter((r) => !r.hidden && !r.isInternal).length ?? -1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'user':
|
||||||
|
diff =
|
||||||
|
(b.onlineInfo?.stationTrains ? b.onlineInfo.stationTrains.length : -1) -
|
||||||
|
(a.onlineInfo?.stationTrains ? a.onlineInfo.stationTrains.length : -1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'like':
|
||||||
|
diff =
|
||||||
|
(a.onlineInfo ? a.onlineInfo.dispatcherRate : -Infinity) -
|
||||||
|
(b.onlineInfo ? b.onlineInfo.dispatcherRate : -Infinity);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'spawn':
|
||||||
|
diff =
|
||||||
|
(a.onlineInfo ? a.onlineInfo.spawns.length : -1) -
|
||||||
|
(b.onlineInfo ? b.onlineInfo.spawns.length : -1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'timetableConfirmed':
|
||||||
|
diff =
|
||||||
|
(a.onlineInfo?.scheduledTrainCount.confirmed ?? -1) -
|
||||||
|
(b.onlineInfo?.scheduledTrainCount.confirmed ?? -1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'timetableUnconfirmed':
|
||||||
|
diff =
|
||||||
|
(a.onlineInfo?.scheduledTrainCount.unconfirmed ?? -1) -
|
||||||
|
(b.onlineInfo?.scheduledTrainCount.unconfirmed ?? -1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'timetableAll':
|
||||||
|
diff =
|
||||||
|
(a.onlineInfo?.scheduledTrainCount.all ?? -1) -
|
||||||
|
(b.onlineInfo?.scheduledTrainCount.all ?? -1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (diff != 0) return Math.sign(diff) * sorter.dir;
|
||||||
|
return a.name.localeCompare(b.name);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const filterStations = (station: Station, filters: Record<string, any>) => {
|
||||||
|
if (filters['free'] && (!station.onlineInfo || station.onlineInfo.dispatcherId == -1))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Scenery Timetables section
|
||||||
|
if (filterTimetablesSection(filters, station)) return false;
|
||||||
|
|
||||||
|
// Scenery Accessibility section
|
||||||
|
if (filterAccessibilitySection(filters, station)) return false;
|
||||||
|
|
||||||
|
// Scenery Status section
|
||||||
|
if (station.onlineInfo && filterStatusSection(filters, station.onlineInfo)) return false;
|
||||||
|
|
||||||
|
if (station.generalInfo) {
|
||||||
|
// Scenery Reality section
|
||||||
|
if (filterRealitySection(filters, station.generalInfo)) return false;
|
||||||
|
|
||||||
|
// Scenery Additional Programs section
|
||||||
|
if (filterProgramsSection(filters, station.generalInfo)) return false;
|
||||||
|
|
||||||
|
// Scenery Controls section
|
||||||
|
if (filterControlsSection(filters, station.generalInfo)) return false;
|
||||||
|
|
||||||
|
// Scenery Signalling section(s)
|
||||||
|
if (filterSignalsSection(filters, station.generalInfo)) return false;
|
||||||
|
|
||||||
|
// Scenery Station Type section
|
||||||
|
if (filterStationType(filters, station.generalInfo)) return false;
|
||||||
|
|
||||||
|
// Scenery sliders
|
||||||
|
if (filterSliderValues(filters, station.generalInfo)) return false;
|
||||||
|
|
||||||
|
// Scenery Authors section
|
||||||
|
if (filterInputValues(filters, station.generalInfo)) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { useTooltipStore } from '../../store/tooltipStore';
|
import { useTooltipStore } from '../../store/tooltipStore';
|
||||||
import { StationTrain } from '../../typings/common';
|
import { Train } from '../../typings/common';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
data() {
|
data() {
|
||||||
@@ -23,7 +23,7 @@ export default defineComponent({
|
|||||||
trains() {
|
trains() {
|
||||||
if (this.tooltipStore.content == '') return [];
|
if (this.tooltipStore.content == '') return [];
|
||||||
|
|
||||||
const parsedTrains = JSON.parse(this.tooltipStore.content) as StationTrain[];
|
const parsedTrains = JSON.parse(this.tooltipStore.content) as Train[];
|
||||||
return (parsedTrains ?? []).sort((a, b) => a.trainNo - b.trainNo);
|
return (parsedTrains ?? []).sort((a, b) => a.trainNo - b.trainNo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
chosenTrain() {
|
chosenTrain() {
|
||||||
return this.store.trainList.find((train) => train.trainId == this.store.chosenModalTrainId);
|
return this.store.trainList.find((train) => train.id == this.store.chosenModalTrainId);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -40,20 +40,6 @@ export default defineComponent({
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../styles/responsive.scss';
|
@import '../../styles/responsive.scss';
|
||||||
@import '../../styles/card.scss';
|
|
||||||
|
|
||||||
.top-info-bar-anim {
|
|
||||||
&-enter-active,
|
|
||||||
&-leave-active {
|
|
||||||
transition: all 150ms ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-enter-from,
|
|
||||||
&-leave-to {
|
|
||||||
transform: translate(-50%, -50%) scale(0.8);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.train-modal {
|
.train-modal {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|||||||
@@ -8,17 +8,18 @@
|
|||||||
<Loading v-else-if="apiStore.dataStatuses.connection == Status.Loading" key="loading" />
|
<Loading v-else-if="apiStore.dataStatuses.connection == Status.Loading" key="loading" />
|
||||||
|
|
||||||
<div class="table-warning" key="no-trains" v-else-if="trains.length == 0">
|
<div class="table-warning" key="no-trains" v-else-if="trains.length == 0">
|
||||||
{{ $t('trains.no-trains') }}
|
{{ $t('trains.no-trains') }} (region: <b>{{ store.region.name }}</b
|
||||||
|
>)
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<transition-group name="list-anim" tag="ul">
|
<transition-group name="list-anim" tag="ul">
|
||||||
<li
|
<li
|
||||||
class="train-row"
|
class="train-row"
|
||||||
v-for="train in trains"
|
v-for="train in trains"
|
||||||
:key="train.trainId"
|
:key="train.id"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@click.stop="selectModalTrain(train.trainId, $event.currentTarget)"
|
@click.stop="selectModalTrain(train.id, $event.currentTarget)"
|
||||||
@keydown.enter="selectModalTrain(train.trainId, $event.currentTarget)"
|
@keydown.enter="selectModalTrain(train.id, $event.currentTarget)"
|
||||||
>
|
>
|
||||||
<TrainInfo :train="train" :extended="false" />
|
<TrainInfo :train="train" :extended="false" />
|
||||||
</li>
|
</li>
|
||||||
@@ -108,8 +109,7 @@ export default defineComponent({
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
padding: 1em 0;
|
padding: 1em 0;
|
||||||
|
font-size: 1.25em;
|
||||||
font-size: 1.5em;
|
|
||||||
|
|
||||||
background: #1a1a1a;
|
background: #1a1a1a;
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-15
@@ -174,9 +174,9 @@
|
|||||||
|
|
||||||
"sections": {
|
"sections": {
|
||||||
"quick": "QUICK FILTERS",
|
"quick": "QUICK FILTERS",
|
||||||
"station-type": "STATION TYPE",
|
"stationType": "STATION TYPE",
|
||||||
"reality": "SCENERY REALITY",
|
"reality": "SCENERY REALITY",
|
||||||
"package-access": "IN-GAME AVAILABILITY",
|
"packageAccess": "IN-GAME AVAILABILITY",
|
||||||
"access": "GENERAL AVAILABILITY",
|
"access": "GENERAL AVAILABILITY",
|
||||||
"control": "CONTROLS",
|
"control": "CONTROLS",
|
||||||
"signals": "SIGNALLING",
|
"signals": "SIGNALLING",
|
||||||
@@ -187,6 +187,9 @@
|
|||||||
"spawns": "OPEN SPAWNS"
|
"spawns": "OPEN SPAWNS"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"changed-filters-count": "Changed filters:",
|
||||||
|
"no-changed-filters": "No changed filters",
|
||||||
|
|
||||||
"all-available": "ALL AVAILABLE",
|
"all-available": "ALL AVAILABLE",
|
||||||
"all-free": "CURRENTLY FREE",
|
"all-free": "CURRENTLY FREE",
|
||||||
|
|
||||||
@@ -197,11 +200,11 @@
|
|||||||
|
|
||||||
"title": "STATION FILTERS",
|
"title": "STATION FILTERS",
|
||||||
"default": "IN-GAME",
|
"default": "IN-GAME",
|
||||||
"not-default": "ADDITIONAL",
|
"notDefault": "ADDITIONAL",
|
||||||
"real": "REAL",
|
"real": "REAL",
|
||||||
"fictional": "FICTIONAL",
|
"fictional": "FICTIONAL",
|
||||||
"unavailable": "UNSUPPORTED",
|
"unavailable": "UNSUPPORTED",
|
||||||
"non-public": "NON-PUBLIC",
|
"nonPublic": "NON-PUBLIC",
|
||||||
"abandoned": "ABANDONED",
|
"abandoned": "ABANDONED",
|
||||||
|
|
||||||
"SPK": "SPK",
|
"SPK": "SPK",
|
||||||
@@ -211,7 +214,6 @@
|
|||||||
"SCS-R": "SCS + MANUAL",
|
"SCS-R": "SCS + MANUAL",
|
||||||
"SCS-M": "SCS + MECH.",
|
"SCS-M": "SCS + MECH.",
|
||||||
"SPE": "SPE",
|
"SPE": "SPE",
|
||||||
|
|
||||||
"manual": "MANUAL",
|
"manual": "MANUAL",
|
||||||
"mechanical": "MECHANICAL",
|
"mechanical": "MECHANICAL",
|
||||||
|
|
||||||
@@ -238,14 +240,14 @@
|
|||||||
"nonJunction": "OTHER",
|
"nonJunction": "OTHER",
|
||||||
|
|
||||||
"sliders": {
|
"sliders": {
|
||||||
"min-lvl": "MIN. REQUIRED DISPATCHER LEVEL",
|
"minLevel": "MIN. REQUIRED DISPATCHER LEVEL",
|
||||||
"max-lvl": "MAX. REQUIRED DISPATCHER LEVEL",
|
"maxLevel": "MAX. REQUIRED DISPATCHER LEVEL",
|
||||||
"min-vmax": "MIN. SCENERY ROUTE SPEED",
|
"minVmax": "MIN. SCENERY ROUTE SPEED",
|
||||||
"max-vmax": "MAX. SCENERY ROUTE SPEED",
|
"maxVmax": "MAX. SCENERY ROUTE SPEED",
|
||||||
"routes-1t-cat": "MIN. CATENARY SINGLE TRACK ROUTES",
|
"minOneWayCatenary": "MIN. CATENARY SINGLE TRACK ROUTES",
|
||||||
"routes-1t-other": "MIN. OTHER SINGLE TRACK ROUTES",
|
"minOneWay": "MIN. OTHER SINGLE TRACK ROUTES",
|
||||||
"routes-2t-cat": "MIN. CATENARY DOUBLE TRACK ROUTES",
|
"minTwoWayCatenary": "MIN. CATENARY DOUBLE TRACK ROUTES",
|
||||||
"routes-2t-other": "MIN. OTHER DOUBLE TRACK ROUTES"
|
"minTwoWay": "MIN. OTHER DOUBLE TRACK ROUTES"
|
||||||
},
|
},
|
||||||
|
|
||||||
"authors-search": "SEARCH BY AUTHOR NAME (other filters apply):",
|
"authors-search": "SEARCH BY AUTHOR NAME (other filters apply):",
|
||||||
@@ -298,12 +300,13 @@
|
|||||||
"single-track-routes-other": "Not electrified single-track routes count: "
|
"single-track-routes-other": "Not electrified single-track routes count: "
|
||||||
},
|
},
|
||||||
"no-stations": "No stations to show here!",
|
"no-stations": "No stations to show here!",
|
||||||
"scenery-search": "Search for scenery..."
|
"scenery-search": "Search for scenery...",
|
||||||
|
"active-filters": "Attention! You got active filters!"
|
||||||
},
|
},
|
||||||
"station-stats": {
|
"station-stats": {
|
||||||
"u-factor": "U-factor",
|
"u-factor": "U-factor",
|
||||||
"u-factor-tooltip": "(?) Current server traffic factor (driver count divided by dispatcher count)",
|
"u-factor-tooltip": "(?) Current server traffic factor (driver count divided by dispatcher count)",
|
||||||
"med-timetable-count": "Median of scenery timetables:",
|
"avg-timetable-count": "Average count of scenery timetables:",
|
||||||
"single-track-count": "Single track routes:",
|
"single-track-count": "Single track routes:",
|
||||||
"double-track-count": "Double track routes:",
|
"double-track-count": "Double track routes:",
|
||||||
"cross-sceneries": "Cross-track sceneries (1-track <-> 2-track)",
|
"cross-sceneries": "Cross-track sceneries (1-track <-> 2-track)",
|
||||||
|
|||||||
+18
-14
@@ -171,9 +171,9 @@
|
|||||||
|
|
||||||
"sections": {
|
"sections": {
|
||||||
"quick": "SZYBKIE FILTRY",
|
"quick": "SZYBKIE FILTRY",
|
||||||
"station-type": "RODZAJ STACJI",
|
"stationType": "RODZAJ STACJI",
|
||||||
"reality": "FIKCYJNOŚĆ SCENERII",
|
"reality": "FIKCYJNOŚĆ SCENERII",
|
||||||
"package-access": "DOSTĘPNOŚĆ W PACZCE",
|
"packageAccess": "DOSTĘPNOŚĆ W PACZCE",
|
||||||
"access": "DOSTĘPNOŚĆ OGÓLNA",
|
"access": "DOSTĘPNOŚĆ OGÓLNA",
|
||||||
"control": "TYP STEROWANIA",
|
"control": "TYP STEROWANIA",
|
||||||
"signals": "TYP SYGNALIZACJI",
|
"signals": "TYP SYGNALIZACJI",
|
||||||
@@ -184,6 +184,9 @@
|
|||||||
"spawns": "OTWARTE SPAWNY"
|
"spawns": "OTWARTE SPAWNY"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"changed-filters-count": "Zmienione filtry:",
|
||||||
|
"no-changed-filters": "Brak zmienionych filtrów",
|
||||||
|
|
||||||
"all-available": "WSZYSTKIE DOSTĘPNE",
|
"all-available": "WSZYSTKIE DOSTĘPNE",
|
||||||
"all-free": "WSZYSTKIE WOLNE",
|
"all-free": "WSZYSTKIE WOLNE",
|
||||||
|
|
||||||
@@ -194,11 +197,11 @@
|
|||||||
|
|
||||||
"title": "FILTRUJ STACJE",
|
"title": "FILTRUJ STACJE",
|
||||||
"default": "DOMYŚLNA",
|
"default": "DOMYŚLNA",
|
||||||
"not-default": "POZA PACZKĄ",
|
"notDefault": "POZA PACZKĄ",
|
||||||
"real": "REALNA",
|
"real": "REALNA",
|
||||||
"fictional": "FIKCYJNA",
|
"fictional": "FIKCYJNA",
|
||||||
"unavailable": "NIEDOSTĘPNA",
|
"unavailable": "NIEDOSTĘPNA",
|
||||||
"non-public": "NIEPUBLICZNA",
|
"nonPublic": "NIEPUBLICZNA",
|
||||||
"abandoned": "WYCOFANA",
|
"abandoned": "WYCOFANA",
|
||||||
|
|
||||||
"SPK": "SPK",
|
"SPK": "SPK",
|
||||||
@@ -234,14 +237,14 @@
|
|||||||
"nonJunction": "INNE",
|
"nonJunction": "INNE",
|
||||||
|
|
||||||
"sliders": {
|
"sliders": {
|
||||||
"min-lvl": "MIN. WYMAGANY POZIOM DYŻURNEGO",
|
"minLevel": "MIN. WYMAGANY POZIOM DYŻURNEGO",
|
||||||
"max-lvl": "MAKS. WYMAGANY POZIOM DYŻURNEGO",
|
"maxLevel": "MAKS. WYMAGANY POZIOM DYŻURNEGO",
|
||||||
"min-vmax": "MIN. PRĘDKOŚĆ SZLAKOWA",
|
"minVmax": "MIN. PRĘDKOŚĆ SZLAKOWA",
|
||||||
"max-vmax": "MAKS. PRĘDKOŚĆ SZLAKOWA",
|
"maxVmax": "MAKS. PRĘDKOŚĆ SZLAKOWA",
|
||||||
"routes-1t-cat": "SZLAKI JEDNOTOROWE ZELEKTR. (MINIMUM)",
|
"minOneWayCatenary": "SZLAKI JEDNOTOROWE ZELEKTR. (MINIMUM)",
|
||||||
"routes-1t-other": "SZLAKI JEDNOTOROWE NIEZELEKTR. (MINIMUM)",
|
"minOneWay": "SZLAKI JEDNOTOROWE NIEZELEKTR. (MINIMUM)",
|
||||||
"routes-2t-cat": "SZLAKI DWUTOROWE ZELEKTR. (MINIMUM)",
|
"minTwoWayCatenary": "SZLAKI DWUTOROWE ZELEKTR. (MINIMUM)",
|
||||||
"routes-2t-other": "SZLAKI DWUTOROWE NIEZELEKTR. (MINIMUM)"
|
"minTwoWay": "SZLAKI DWUTOROWE NIEZELEKTR. (MINIMUM)"
|
||||||
},
|
},
|
||||||
|
|
||||||
"authors-search": "SZUKAJ AUTORA (uwzględnia inne filtry):",
|
"authors-search": "SZUKAJ AUTORA (uwzględnia inne filtry):",
|
||||||
@@ -291,12 +294,13 @@
|
|||||||
"single-track-routes-other": "Liczba niezelektryfikowanych szlaków jednotorowych: "
|
"single-track-routes-other": "Liczba niezelektryfikowanych szlaków jednotorowych: "
|
||||||
},
|
},
|
||||||
"no-stations": "Brak stacji do wyświetlenia!",
|
"no-stations": "Brak stacji do wyświetlenia!",
|
||||||
"scenery-search": "Wyszukaj scenerię..."
|
"scenery-search": "Wyszukaj scenerię...",
|
||||||
|
"active-filters": "Uwaga! Masz obecnie aktywne filtry!"
|
||||||
},
|
},
|
||||||
"station-stats": {
|
"station-stats": {
|
||||||
"u-factor": "Współczynnik Ugla",
|
"u-factor": "Współczynnik Ugla",
|
||||||
"u-factor-tooltip": "(?) Współczynnik ruchu na serwerze (liczba maszynistów online dzielona na liczbę dyżurnych ruchu)",
|
"u-factor-tooltip": "(?) Współczynnik ruchu na serwerze (liczba maszynistów online dzielona na liczbę dyżurnych ruchu)",
|
||||||
"med-timetable-count": "Mediana rozkładów jazdy na sceneriach:",
|
"avg-timetable-count": "Średnia liczba rozkładów jazdy na sceneriach:",
|
||||||
"single-track-count": "Szlaki jednotorowe:",
|
"single-track-count": "Szlaki jednotorowe:",
|
||||||
"double-track-count": "Szlaki dwutorowe:",
|
"double-track-count": "Szlaki dwutorowe:",
|
||||||
"cross-sceneries": "Scenerie przejściowe (1-tor <-> 2-tor):",
|
"cross-sceneries": "Scenerie przejściowe (1-tor <-> 2-tor):",
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
import StorageManager from './storageManager';
|
||||||
|
|
||||||
|
export const sections = [
|
||||||
|
'status',
|
||||||
|
'timetables',
|
||||||
|
'reality',
|
||||||
|
'packageAccess',
|
||||||
|
'stationType',
|
||||||
|
'access',
|
||||||
|
'control',
|
||||||
|
'blockades',
|
||||||
|
'signals',
|
||||||
|
'addons'
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export const initFilters = {
|
||||||
|
default: false,
|
||||||
|
notDefault: false,
|
||||||
|
real: false,
|
||||||
|
fictional: false,
|
||||||
|
SPK: false,
|
||||||
|
SCS: false,
|
||||||
|
SPE: false,
|
||||||
|
SUP: false,
|
||||||
|
noSUP: false,
|
||||||
|
ASDEK: false,
|
||||||
|
noASDEK: false,
|
||||||
|
manual: false,
|
||||||
|
'SPK-R': false,
|
||||||
|
'SCS-R': false,
|
||||||
|
mechanical: false,
|
||||||
|
'SPK-M': false,
|
||||||
|
'SCS-M': false,
|
||||||
|
modern: false,
|
||||||
|
semaphores: false,
|
||||||
|
historical: false,
|
||||||
|
mixed: false,
|
||||||
|
SBL: false,
|
||||||
|
PBL: false,
|
||||||
|
'include-selected': false,
|
||||||
|
'no-1track': false,
|
||||||
|
'no-2track': false,
|
||||||
|
free: true,
|
||||||
|
occupied: false,
|
||||||
|
nonPublic: false,
|
||||||
|
unavailable: true,
|
||||||
|
abandoned: true,
|
||||||
|
afkStatus: false,
|
||||||
|
endingStatus: false,
|
||||||
|
noSpaceStatus: false,
|
||||||
|
unavailableStatus: false,
|
||||||
|
unsignedStatus: false,
|
||||||
|
withActiveTimetables: false,
|
||||||
|
withoutActiveTimetables: false,
|
||||||
|
junction: false,
|
||||||
|
nonJunction: false,
|
||||||
|
maxVmax: 200,
|
||||||
|
minVmax: 0,
|
||||||
|
onlineFromHours: 0,
|
||||||
|
minLevel: 0,
|
||||||
|
maxLevel: 20,
|
||||||
|
minOneWayCatenary: 0,
|
||||||
|
minOneWay: 0,
|
||||||
|
minTwoWayCatenary: 0,
|
||||||
|
minTwoWay: 0,
|
||||||
|
authors: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
export const initSliders = [
|
||||||
|
{ id: 'maxVmax', minRange: 0, maxRange: 200, step: 10 },
|
||||||
|
{ id: 'minVmax', minRange: 0, maxRange: 200, step: 10 },
|
||||||
|
{ id: 'minLevel', minRange: 0, maxRange: 20, step: 1 },
|
||||||
|
{ id: 'maxLevel', minRange: 0, maxRange: 20, step: 1 },
|
||||||
|
{ id: 'minOneWayCatenary', minRange: 0, maxRange: 5, step: 1 },
|
||||||
|
{ id: 'minOneWay', minRange: 0, maxRange: 5, step: 1 },
|
||||||
|
{ id: 'minTwoWayCatenary', minRange: 0, maxRange: 5, step: 1 },
|
||||||
|
{ id: 'minTwoWay', minRange: 0, maxRange: 5, step: 1 }
|
||||||
|
];
|
||||||
|
|
||||||
|
export type StationFilter = keyof typeof initFilters;
|
||||||
|
export type StationFilterSection = (typeof sections)[number];
|
||||||
|
|
||||||
|
export const filtersSections: Record<StationFilterSection, StationFilter[]> = {
|
||||||
|
status: ['free', 'occupied', 'endingStatus', 'afkStatus', 'noSpaceStatus', 'unavailableStatus'],
|
||||||
|
timetables: ['withActiveTimetables', 'withoutActiveTimetables'],
|
||||||
|
reality: ['real', 'fictional'],
|
||||||
|
packageAccess: ['default', 'notDefault'],
|
||||||
|
stationType: ['junction', 'nonJunction'],
|
||||||
|
access: ['nonPublic', 'unavailable', 'abandoned'],
|
||||||
|
addons: ['SUP', 'ASDEK', 'noSUP', 'noASDEK'],
|
||||||
|
control: ['SPK', 'SCS', 'SPE', 'SPK-M', 'SCS-M', 'mechanical', 'SPK-R', 'SCS-R', 'manual'],
|
||||||
|
blockades: ['SBL', 'PBL'],
|
||||||
|
signals: ['modern', 'semaphores', 'mixed', 'historical']
|
||||||
|
};
|
||||||
|
|
||||||
|
export function setupFilters(currentFilters: Record<string, any>) {
|
||||||
|
if (!StorageManager.isRegistered('options_saved')) return;
|
||||||
|
|
||||||
|
Object.keys(currentFilters).forEach((filterKey) => {
|
||||||
|
const savedValue = StorageManager.getValue(filterKey);
|
||||||
|
|
||||||
|
if (savedValue != null) {
|
||||||
|
if (typeof currentFilters[filterKey] == 'boolean')
|
||||||
|
currentFilters[filterKey] = savedValue === 'true';
|
||||||
|
else if (typeof currentFilters[filterKey] == 'number')
|
||||||
|
currentFilters[filterKey] = Number(savedValue);
|
||||||
|
else currentFilters[filterKey] = savedValue.toString();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getChangedFilters(currentFilters: Record<string, any>): string[] {
|
||||||
|
return (
|
||||||
|
Object.keys(currentFilters).filter(
|
||||||
|
(filterKey) =>
|
||||||
|
currentFilters[filterKey] !== initFilters[filterKey as keyof typeof initFilters]
|
||||||
|
) ?? []
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -34,6 +34,10 @@ export default class StorageManager {
|
|||||||
window.localStorage.removeItem(key);
|
window.localStorage.removeItem(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getValue(key: string) {
|
||||||
|
return window.localStorage.getItem(key);
|
||||||
|
}
|
||||||
|
|
||||||
static getBooleanValue(key: string): boolean {
|
static getBooleanValue(key: string): boolean {
|
||||||
return window.localStorage.getItem(key) === 'true' ? true : false;
|
return window.localStorage.getItem(key) === 'true' ? true : false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
export const headIds = [
|
|
||||||
'station',
|
|
||||||
'min-lvl',
|
|
||||||
'status',
|
|
||||||
'dispatcher',
|
|
||||||
'dispatcher-lvl',
|
|
||||||
'routes-single',
|
|
||||||
'routes-double',
|
|
||||||
'general'
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export const headIconsIds = [
|
|
||||||
'user',
|
|
||||||
'like',
|
|
||||||
'spawn',
|
|
||||||
'timetableAll',
|
|
||||||
'timetableUnconfirmed',
|
|
||||||
'timetableConfirmed'
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export type HeadIdsTypes = (typeof headIds)[number] | (typeof headIconsIds)[number];
|
|
||||||
@@ -1,242 +0,0 @@
|
|||||||
import { Filter } from '../../components/StationsView/typings';
|
|
||||||
import { Status } from '../../typings/common';
|
|
||||||
import { HeadIdsTypes } from '../data/stationHeaderNames';
|
|
||||||
import { Station } from '../../typings/common';
|
|
||||||
|
|
||||||
const dispatcherStatusPriority = [
|
|
||||||
Status.ActiveDispatcher.UNKNOWN,
|
|
||||||
Status.ActiveDispatcher.INVALID,
|
|
||||||
Status.ActiveDispatcher.NOT_LOGGED_IN,
|
|
||||||
Status.ActiveDispatcher.UNAVAILABLE,
|
|
||||||
Status.ActiveDispatcher.AFK,
|
|
||||||
Status.ActiveDispatcher.ENDING,
|
|
||||||
Status.ActiveDispatcher.NO_SPACE,
|
|
||||||
undefined
|
|
||||||
];
|
|
||||||
|
|
||||||
export const sortStations = (
|
|
||||||
a: Station,
|
|
||||||
b: Station,
|
|
||||||
sorter: { headerName: HeadIdsTypes; dir: number }
|
|
||||||
) => {
|
|
||||||
let diff = 0;
|
|
||||||
|
|
||||||
switch (sorter.headerName) {
|
|
||||||
case 'station':
|
|
||||||
return sorter.dir == 1 ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name);
|
|
||||||
|
|
||||||
case 'min-lvl':
|
|
||||||
diff = (a.generalInfo?.reqLevel || 0) - (b.generalInfo?.reqLevel || 0);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'status':
|
|
||||||
diff =
|
|
||||||
(a.onlineInfo?.dispatcherTimestamp ??
|
|
||||||
dispatcherStatusPriority.indexOf(a.onlineInfo?.dispatcherStatus)) -
|
|
||||||
(b.onlineInfo?.dispatcherTimestamp ??
|
|
||||||
dispatcherStatusPriority.indexOf(b.onlineInfo?.dispatcherStatus));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'dispatcher':
|
|
||||||
if (
|
|
||||||
(a.onlineInfo?.dispatcherName.toLowerCase() || '') >
|
|
||||||
(b.onlineInfo?.dispatcherName.toLowerCase() || '')
|
|
||||||
)
|
|
||||||
return sorter.dir;
|
|
||||||
if (
|
|
||||||
(a.onlineInfo?.dispatcherName.toLowerCase() || '') <
|
|
||||||
(b.onlineInfo?.dispatcherName.toLowerCase() || '')
|
|
||||||
)
|
|
||||||
return -sorter.dir;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'dispatcher-lvl':
|
|
||||||
diff = (a.onlineInfo?.dispatcherExp || 0) - (b.onlineInfo?.dispatcherExp || 0);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'routes-single':
|
|
||||||
diff =
|
|
||||||
(a.generalInfo?.routes.single.filter((r) => !r.hidden && !r.isInternal).length ?? -1) -
|
|
||||||
(b.generalInfo?.routes.single.filter((r) => !r.hidden && !r.isInternal).length ?? -1);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'routes-double':
|
|
||||||
diff =
|
|
||||||
(a.generalInfo?.routes.double.filter((r) => !r.hidden && !r.isInternal).length ?? -1) -
|
|
||||||
(b.generalInfo?.routes.double.filter((r) => !r.hidden && !r.isInternal).length ?? -1);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'user':
|
|
||||||
diff =
|
|
||||||
(b.onlineInfo?.stationTrains ? b.onlineInfo.stationTrains.length : -1) -
|
|
||||||
(a.onlineInfo?.stationTrains ? a.onlineInfo.stationTrains.length : -1);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'like':
|
|
||||||
diff =
|
|
||||||
(a.onlineInfo ? a.onlineInfo.dispatcherRate : -Infinity) -
|
|
||||||
(b.onlineInfo ? b.onlineInfo.dispatcherRate : -Infinity);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'spawn':
|
|
||||||
diff =
|
|
||||||
(a.onlineInfo ? a.onlineInfo.spawns.length : -1) -
|
|
||||||
(b.onlineInfo ? b.onlineInfo.spawns.length : -1);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'timetableConfirmed':
|
|
||||||
diff =
|
|
||||||
(a.onlineInfo?.scheduledTrainCount.confirmed ?? -1) -
|
|
||||||
(b.onlineInfo?.scheduledTrainCount.confirmed ?? -1);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'timetableUnconfirmed':
|
|
||||||
diff =
|
|
||||||
(a.onlineInfo?.scheduledTrainCount.unconfirmed ?? -1) -
|
|
||||||
(b.onlineInfo?.scheduledTrainCount.unconfirmed ?? -1);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'timetableAll':
|
|
||||||
diff =
|
|
||||||
(a.onlineInfo?.scheduledTrainCount.all ?? -1) -
|
|
||||||
(b.onlineInfo?.scheduledTrainCount.all ?? -1);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (diff != 0) return Math.sign(diff) * sorter.dir;
|
|
||||||
return a.name.localeCompare(b.name);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const filterStations = (station: Station, filters: Filter) => {
|
|
||||||
if (filters['free'] && (!station.onlineInfo || station.onlineInfo.dispatcherId == -1))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (station.onlineInfo) {
|
|
||||||
const { dispatcherStatus } = station.onlineInfo;
|
|
||||||
|
|
||||||
const excludeEnding =
|
|
||||||
dispatcherStatus == Status.ActiveDispatcher.ENDING && filters['endingStatus'];
|
|
||||||
|
|
||||||
const excludeNotSigned =
|
|
||||||
(dispatcherStatus == Status.ActiveDispatcher.NOT_LOGGED_IN ||
|
|
||||||
dispatcherStatus == Status.ActiveDispatcher.UNAVAILABLE) &&
|
|
||||||
filters['unavailableStatus'];
|
|
||||||
|
|
||||||
const excludeAFK = dispatcherStatus == Status.ActiveDispatcher.AFK && filters['afkStatus'];
|
|
||||||
|
|
||||||
const excludeNoSpace =
|
|
||||||
dispatcherStatus == Status.ActiveDispatcher.NO_SPACE && filters['noSpaceStatus'];
|
|
||||||
|
|
||||||
const excludeOccupied = filters['occupied'] && dispatcherStatus != Status.ActiveDispatcher.FREE;
|
|
||||||
|
|
||||||
const excludeActiveTTs =
|
|
||||||
(dispatcherStatus == Status.ActiveDispatcher.FREE ||
|
|
||||||
station.onlineInfo.scheduledTrainCount.all != 0) &&
|
|
||||||
filters['withActiveTimetables'];
|
|
||||||
|
|
||||||
if (
|
|
||||||
excludeEnding ||
|
|
||||||
excludeAFK ||
|
|
||||||
excludeNoSpace ||
|
|
||||||
excludeNotSigned ||
|
|
||||||
excludeOccupied ||
|
|
||||||
excludeActiveTTs
|
|
||||||
)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (
|
|
||||||
filters['onlineFromHours'] > 0 &&
|
|
||||||
dispatcherStatus <= Date.now() + filters['onlineFromHours'] * 3600000
|
|
||||||
)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const excludeNoActiveTTs =
|
|
||||||
filters['withoutActiveTimetables'] &&
|
|
||||||
(!station.onlineInfo || station.onlineInfo.scheduledTrainCount.all == 0);
|
|
||||||
|
|
||||||
if (excludeNoActiveTTs) return false;
|
|
||||||
|
|
||||||
if (
|
|
||||||
(station.generalInfo?.availability == 'nonPublic' || !station.generalInfo) &&
|
|
||||||
filters['nonPublic']
|
|
||||||
)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (station.generalInfo) {
|
|
||||||
const { routes, availability, controlType, lines, reqLevel, signalType, SUP, ASDEK, authors } =
|
|
||||||
station.generalInfo;
|
|
||||||
|
|
||||||
if (availability == 'unavailable' && filters['unavailable'] && !station.onlineInfo)
|
|
||||||
return false;
|
|
||||||
if (availability == 'abandoned' && filters['abandoned'] && !station.onlineInfo) return false;
|
|
||||||
if (availability == 'default' && filters['default']) return false;
|
|
||||||
|
|
||||||
if (
|
|
||||||
availability != 'default' &&
|
|
||||||
filters['notDefault'] &&
|
|
||||||
!(availability == 'abandoned' || availability == 'unavailable')
|
|
||||||
)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (filters['real'] && lines) return false;
|
|
||||||
if (filters['fictional'] && !lines) return false;
|
|
||||||
|
|
||||||
const otherAvailability =
|
|
||||||
availability == 'nonPublic' || availability == 'unavailable' || availability == 'abandoned';
|
|
||||||
|
|
||||||
if (reqLevel + (otherAvailability ? 1 : 0) < filters['minLevel']) return false;
|
|
||||||
if (reqLevel + (otherAvailability ? 1 : 0) > filters['maxLevel']) return false;
|
|
||||||
|
|
||||||
if (filters['minVmax'] > station.generalInfo.routes.maxRouteSpeed) return false;
|
|
||||||
if (filters['maxVmax'] < station.generalInfo.routes.minRouteSpeed) return false;
|
|
||||||
|
|
||||||
if (
|
|
||||||
filters['no-1track'] &&
|
|
||||||
(routes.singleElectrifiedNames.length != 0 || routes.singleOtherNames.length != 0)
|
|
||||||
)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (
|
|
||||||
filters['no-2track'] &&
|
|
||||||
(routes.doubleElectrifiedNames.length != 0 || routes.doubleOtherNames.length != 0)
|
|
||||||
)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (routes.singleElectrifiedNames.length < filters['minOneWayCatenary']) return false;
|
|
||||||
if (routes.singleOtherNames.length < filters['minOneWay']) return false;
|
|
||||||
|
|
||||||
if (routes.doubleElectrifiedNames.length < filters['minTwoWayCatenary']) return false;
|
|
||||||
if (routes.doubleOtherNames.length < filters['minTwoWay']) return false;
|
|
||||||
|
|
||||||
if (filters[controlType]) return false;
|
|
||||||
if (filters[signalType]) return false;
|
|
||||||
|
|
||||||
if (filters['SUP'] && SUP) return false;
|
|
||||||
if (filters['noSUP'] && !SUP) return false;
|
|
||||||
|
|
||||||
if (filters['ASDEK'] && ASDEK) return false;
|
|
||||||
if (filters['noASDEK'] && !ASDEK) return false;
|
|
||||||
|
|
||||||
if (filters['SBL'] && routes.sblNames.length > 0) return false;
|
|
||||||
if (filters['PBL'] && routes.sblNames.length == 0) return false;
|
|
||||||
|
|
||||||
if (
|
|
||||||
filters['authors'].length > 3 &&
|
|
||||||
!authors?.map((a) => a.toLocaleLowerCase()).includes(filters['authors'].toLocaleLowerCase())
|
|
||||||
)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const singleTracks = routes.single.filter((r) => !r.isInternal);
|
|
||||||
const doubleTracks = routes.double.filter((r) => !r.isInternal);
|
|
||||||
|
|
||||||
let isJunction = singleTracks.length > 0 && doubleTracks.length > 0;
|
|
||||||
if (filters['junction'] && isJunction) return false;
|
|
||||||
if (filters['nonJunction'] && !isJunction) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
+78
-36
@@ -1,9 +1,9 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { parseSpawns, getScheduledTrains, getStationTrains } from './utils';
|
import { parseSpawns } from './utils';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ActiveScenery,
|
ActiveScenery,
|
||||||
ScheduledTrain,
|
CheckpointTrain,
|
||||||
Station,
|
Station,
|
||||||
StationRoutes,
|
StationRoutes,
|
||||||
Status,
|
Status,
|
||||||
@@ -12,6 +12,9 @@ import {
|
|||||||
import { useApiStore } from './apiStore';
|
import { useApiStore } from './apiStore';
|
||||||
import { MainStoreState } from './typings';
|
import { MainStoreState } from './typings';
|
||||||
|
|
||||||
|
const checkpointsTrains: Map<string, CheckpointTrain[]> = new Map();
|
||||||
|
const sceneriesTrains: Map<string, Train[]> = new Map();
|
||||||
|
|
||||||
export const useMainStore = defineStore('mainStore', {
|
export const useMainStore = defineStore('mainStore', {
|
||||||
state: () =>
|
state: () =>
|
||||||
({
|
({
|
||||||
@@ -33,10 +36,17 @@ export const useMainStore = defineStore('mainStore', {
|
|||||||
}) as MainStoreState,
|
}) as MainStoreState,
|
||||||
|
|
||||||
getters: {
|
getters: {
|
||||||
|
checkpointsTrains() {
|
||||||
|
return checkpointsTrains;
|
||||||
|
},
|
||||||
|
|
||||||
trainList(): Train[] {
|
trainList(): Train[] {
|
||||||
const apiStore = useApiStore();
|
const apiStore = useApiStore();
|
||||||
|
|
||||||
return (apiStore.activeData?.trains ?? [])
|
checkpointsTrains.clear();
|
||||||
|
sceneriesTrains.clear();
|
||||||
|
|
||||||
|
const x = (apiStore.activeData?.trains ?? [])
|
||||||
.filter((train) => train.timetable || train.online)
|
.filter((train) => train.timetable || train.online)
|
||||||
.map((train) => {
|
.map((train) => {
|
||||||
const stock = train.stockString.split(';');
|
const stock = train.stockString.split(';');
|
||||||
@@ -53,8 +63,8 @@ export const useMainStore = defineStore('mainStore', {
|
|||||||
sceneryHash
|
sceneryHash
|
||||||
) ?? [];
|
) ?? [];
|
||||||
|
|
||||||
return {
|
const trainObj = {
|
||||||
trainId: train.driverName + train.trainNo.toString(),
|
id: train.id,
|
||||||
|
|
||||||
trainNo: train.trainNo,
|
trainNo: train.trainNo,
|
||||||
mass: train.mass,
|
mass: train.mass,
|
||||||
@@ -93,7 +103,36 @@ export const useMainStore = defineStore('mainStore', {
|
|||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
} as Train;
|
} as Train;
|
||||||
|
|
||||||
|
// Sceneries trains map
|
||||||
|
if (sceneriesTrains.has(train.currentStationName)) {
|
||||||
|
sceneriesTrains.set(train.currentStationName, [
|
||||||
|
...sceneriesTrains.get(train.currentStationName)!,
|
||||||
|
trainObj
|
||||||
|
]);
|
||||||
|
} else sceneriesTrains.set(train.currentStationName, [trainObj]);
|
||||||
|
|
||||||
|
// Checkpoints trains map
|
||||||
|
timetable?.stopList.forEach((stop, i) => {
|
||||||
|
if (/strong|podg\.|pe\./.test(stop.stopName)) {
|
||||||
|
const checkpointTrain: CheckpointTrain = {
|
||||||
|
train: trainObj,
|
||||||
|
checkpointStop: stop
|
||||||
|
};
|
||||||
|
|
||||||
|
if (checkpointsTrains.has(stop.stopNameRAW.toLowerCase())) {
|
||||||
|
checkpointsTrains.set(stop.stopNameRAW.toLowerCase(), [
|
||||||
|
...checkpointsTrains.get(stop.stopNameRAW.toLowerCase())!,
|
||||||
|
checkpointTrain
|
||||||
|
]);
|
||||||
|
} else checkpointsTrains.set(stop.stopNameRAW.toLowerCase(), [checkpointTrain]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return trainObj;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return x;
|
||||||
},
|
},
|
||||||
|
|
||||||
// computed active sceneries
|
// computed active sceneries
|
||||||
@@ -104,6 +143,7 @@ export const useMainStore = defineStore('mainStore', {
|
|||||||
|
|
||||||
if (!apiStore.activeData?.activeSceneries) return [];
|
if (!apiStore.activeData?.activeSceneries) return [];
|
||||||
|
|
||||||
|
console.time('activeSceneryList');
|
||||||
const offlineActiveSceneries = this.trainList.reduce((acc, train) => {
|
const offlineActiveSceneries = this.trainList.reduce((acc, train) => {
|
||||||
if (!train.timetableData) return acc;
|
if (!train.timetableData) return acc;
|
||||||
|
|
||||||
@@ -131,13 +171,14 @@ export const useMainStore = defineStore('mainStore', {
|
|||||||
dispatcherId: -1,
|
dispatcherId: -1,
|
||||||
dispatcherExp: -1,
|
dispatcherExp: -1,
|
||||||
dispatcherIsSupporter: false,
|
dispatcherIsSupporter: false,
|
||||||
scheduledTrains: [],
|
|
||||||
stationTrains: [],
|
|
||||||
dispatcherStatus: Status.ActiveDispatcher.FREE,
|
dispatcherStatus: Status.ActiveDispatcher.FREE,
|
||||||
dispatcherTimestamp: -1,
|
dispatcherTimestamp: -1,
|
||||||
|
|
||||||
isOnline: false,
|
isOnline: false,
|
||||||
|
|
||||||
|
stationTrains: [],
|
||||||
|
scheduledTrains: [],
|
||||||
|
|
||||||
scheduledTrainCount: {
|
scheduledTrainCount: {
|
||||||
all: 0,
|
all: 0,
|
||||||
confirmed: 0,
|
confirmed: 0,
|
||||||
@@ -177,8 +218,9 @@ export const useMainStore = defineStore('mainStore', {
|
|||||||
|
|
||||||
isOnline: scenery.isOnline == 1,
|
isOnline: scenery.isOnline == 1,
|
||||||
|
|
||||||
scheduledTrains: [],
|
|
||||||
stationTrains: [],
|
stationTrains: [],
|
||||||
|
scheduledTrains: [],
|
||||||
|
|
||||||
scheduledTrainCount: {
|
scheduledTrainCount: {
|
||||||
all: 0,
|
all: 0,
|
||||||
confirmed: 0,
|
confirmed: 0,
|
||||||
@@ -196,39 +238,36 @@ export const useMainStore = defineStore('mainStore', {
|
|||||||
|
|
||||||
const station = this.stationList.find((s) => s.name === scenery.name);
|
const station = this.stationList.find((s) => s.name === scenery.name);
|
||||||
|
|
||||||
const scheduledTrains = getScheduledTrains(
|
const checkpoints = [scenery.name];
|
||||||
this.trainList,
|
if (station?.generalInfo?.checkpoints && station.generalInfo.checkpoints.length > 0)
|
||||||
station?.generalInfo,
|
checkpoints.push(...station.generalInfo.checkpoints.filter((cp) => cp !== station.name));
|
||||||
scenery.name,
|
|
||||||
scenery.region
|
|
||||||
);
|
|
||||||
|
|
||||||
const stationTrains = getStationTrains(
|
scenery.stationTrains =
|
||||||
this.trainList,
|
sceneriesTrains.get(scenery.name)?.filter((sc) => sc.region == this.region.id) ?? [];
|
||||||
scheduledTrains,
|
|
||||||
this.region.id,
|
|
||||||
scenery.name
|
|
||||||
);
|
|
||||||
|
|
||||||
// Remove checkpoint duplicates
|
const uniqueTrainIds: string[] = [];
|
||||||
const uniqueScheduledTrains = scheduledTrains.reduce(
|
checkpoints.forEach((cp) => {
|
||||||
(uniqueList, sTrain) =>
|
const scheduledTrains = checkpointsTrains.get(cp.toLowerCase());
|
||||||
uniqueList.find((v) => v.trainId === sTrain.trainId)
|
|
||||||
? uniqueList
|
|
||||||
: [...uniqueList, sTrain],
|
|
||||||
[] as ScheduledTrain[]
|
|
||||||
);
|
|
||||||
|
|
||||||
scenery.scheduledTrains = scheduledTrains;
|
if (!scheduledTrains) return;
|
||||||
scenery.stationTrains = stationTrains;
|
|
||||||
|
|
||||||
scenery.scheduledTrainCount = {
|
scheduledTrains.forEach(({ train, checkpointStop }) => {
|
||||||
all: uniqueScheduledTrains.length,
|
scenery.scheduledTrains.push({ train, checkpointStop });
|
||||||
confirmed: uniqueScheduledTrains.filter((train) => train.stopInfo.confirmed).length,
|
|
||||||
unconfirmed: uniqueScheduledTrains.filter((train) => !train.stopInfo.confirmed).length
|
if (uniqueTrainIds.includes(train.id) || train.region != this.region.id) return;
|
||||||
};
|
|
||||||
|
scenery.scheduledTrainCount.all += 1;
|
||||||
|
|
||||||
|
if (checkpointStop.confirmed) scenery.scheduledTrainCount.confirmed++;
|
||||||
|
else scenery.scheduledTrainCount.unconfirmed++;
|
||||||
|
|
||||||
|
uniqueTrainIds.push(train.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.timeEnd('activeSceneryList');
|
||||||
|
|
||||||
return allActiveSceneries;
|
return allActiveSceneries;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -281,7 +320,10 @@ export const useMainStore = defineStore('mainStore', {
|
|||||||
...scenery,
|
...scenery,
|
||||||
authors: scenery.authors?.split(',').map((a) => a.trim()),
|
authors: scenery.authors?.split(',').map((a) => a.trim()),
|
||||||
routes: routes,
|
routes: routes,
|
||||||
checkpoints: scenery.checkpoints?.split(';') ?? []
|
checkpoints:
|
||||||
|
scenery.checkpoints && scenery.checkpoints.trim().length > 0
|
||||||
|
? scenery.checkpoints.split(';')
|
||||||
|
: []
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,146 +0,0 @@
|
|||||||
import { defineStore } from 'pinia';
|
|
||||||
import inputData from '../data/options.json';
|
|
||||||
import { useMainStore } from './mainStore';
|
|
||||||
import { filterStations, sortStations } from '../scripts/utils/stationFilterUtils';
|
|
||||||
import { HeadIdsTypes } from '../scripts/data/stationHeaderNames';
|
|
||||||
import StorageManager from '../managers/storageManager';
|
|
||||||
import { Filter } from '../components/StationsView/typings';
|
|
||||||
|
|
||||||
const filterInitStates: Filter = {
|
|
||||||
default: false,
|
|
||||||
notDefault: false,
|
|
||||||
real: false,
|
|
||||||
fictional: false,
|
|
||||||
SPK: false,
|
|
||||||
SCS: false,
|
|
||||||
SPE: false,
|
|
||||||
SUP: false,
|
|
||||||
noSUP: false,
|
|
||||||
ASDEK: false,
|
|
||||||
noASDEK: false,
|
|
||||||
ręczne: false,
|
|
||||||
'ręczne+SPK': false,
|
|
||||||
'ręczne+SCS': false,
|
|
||||||
mechaniczne: false,
|
|
||||||
'mechaniczne+SPK': false,
|
|
||||||
'mechaniczne+SCS': false,
|
|
||||||
współczesna: false,
|
|
||||||
kształtowa: false,
|
|
||||||
historyczna: false,
|
|
||||||
mieszana: false,
|
|
||||||
SBL: false,
|
|
||||||
PBL: false,
|
|
||||||
'include-selected': false,
|
|
||||||
'no-1track': false,
|
|
||||||
'no-2track': false,
|
|
||||||
free: true,
|
|
||||||
occupied: false,
|
|
||||||
ending: false,
|
|
||||||
nonPublic: false,
|
|
||||||
unavailable: true,
|
|
||||||
abandoned: true,
|
|
||||||
afkStatus: false,
|
|
||||||
endingStatus: false,
|
|
||||||
noSpaceStatus: false,
|
|
||||||
unavailableStatus: false,
|
|
||||||
unsignedStatus: false,
|
|
||||||
withActiveTimetables: false,
|
|
||||||
withoutActiveTimetables: false,
|
|
||||||
|
|
||||||
junction: false,
|
|
||||||
nonJunction: false,
|
|
||||||
|
|
||||||
maxVmax: 200,
|
|
||||||
minVmax: 0,
|
|
||||||
authors: '',
|
|
||||||
onlineFromHours: 0,
|
|
||||||
minLevel: 0,
|
|
||||||
maxLevel: 20,
|
|
||||||
minOneWayCatenary: 0,
|
|
||||||
minOneWay: 0,
|
|
||||||
minTwoWayCatenary: 0,
|
|
||||||
minTwoWay: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useStationFiltersStore = defineStore('stationFiltersStore', {
|
|
||||||
state() {
|
|
||||||
return {
|
|
||||||
inputs: inputData,
|
|
||||||
filters: { ...filterInitStates },
|
|
||||||
sorterActive: { headerName: 'station' as HeadIdsTypes, dir: 1 },
|
|
||||||
lastClickedFilterId: ''
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
getters: {
|
|
||||||
areFiltersAtDefault: (state) => {
|
|
||||||
return Object.keys(state.filters).every((f) => state.filters[f] === filterInitStates[f]);
|
|
||||||
},
|
|
||||||
|
|
||||||
filteredStationList: (state) => {
|
|
||||||
const store = useMainStore();
|
|
||||||
|
|
||||||
return store.allStationInfo
|
|
||||||
.filter((station) => filterStations(station, state.filters))
|
|
||||||
.sort((a, b) => sortStations(a, b, state.sorterActive));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
setupFilters() {
|
|
||||||
if (!StorageManager.isRegistered('options_saved')) return;
|
|
||||||
|
|
||||||
this.inputs.options.forEach((option) => {
|
|
||||||
if (!StorageManager.isRegistered(option.name)) return;
|
|
||||||
const savedValue = StorageManager.getBooleanValue(option.name);
|
|
||||||
|
|
||||||
this.filters[option.name] = savedValue;
|
|
||||||
option.value = !savedValue;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.inputs.sliders.forEach((slider) => {
|
|
||||||
if (!StorageManager.isRegistered(slider.name)) return;
|
|
||||||
const savedValue = StorageManager.getNumericValue(slider.name);
|
|
||||||
|
|
||||||
this.filters[slider.name] = savedValue;
|
|
||||||
slider.value = savedValue;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
changeFilterValue(name: string, value: any) {
|
|
||||||
this.filters[name] = value;
|
|
||||||
if (StorageManager.isRegistered('options_saved')) StorageManager.setValue(name, value);
|
|
||||||
},
|
|
||||||
|
|
||||||
resetFilters() {
|
|
||||||
this.filters = { ...filterInitStates };
|
|
||||||
|
|
||||||
this.inputs.options.forEach((option) => {
|
|
||||||
option.value = option.defaultValue;
|
|
||||||
StorageManager.setBooleanValue(option.name, !option.defaultValue);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.inputs.sliders.forEach((slider) => {
|
|
||||||
slider.value = slider.defaultValue;
|
|
||||||
StorageManager.setNumericValue(slider.name, slider.defaultValue);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
resetSectionOptions(section: string) {
|
|
||||||
this.inputs.options
|
|
||||||
.filter((option) => option.section == section)
|
|
||||||
.forEach((option) => {
|
|
||||||
option.value = option.defaultValue;
|
|
||||||
StorageManager.setBooleanValue(option.name, !option.defaultValue);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
changeSorter(headerName: HeadIdsTypes) {
|
|
||||||
if (headerName == this.sorterActive.headerName)
|
|
||||||
this.sorterActive.dir = -1 * this.sorterActive.dir;
|
|
||||||
else this.sorterActive.dir = 1;
|
|
||||||
|
|
||||||
this.sorterActive.headerName = headerName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { API } from '../typings/api';
|
import { API } from '../typings/api';
|
||||||
import { Availability, StationRoutesInfo, Status } from '../typings/common';
|
import { Availability, CheckpointTrain, StationRoutesInfo, Status } from '../typings/common';
|
||||||
|
|
||||||
export interface MainStoreState {
|
export interface MainStoreState {
|
||||||
region: { id: string; value: string; name: string };
|
region: { id: string; value: string; name: string };
|
||||||
|
|||||||
+1
-183
@@ -1,13 +1,4 @@
|
|||||||
import {
|
import { ScenerySpawn, ScenerySpawnType } from '../typings/common';
|
||||||
TrainStop,
|
|
||||||
StopStatus,
|
|
||||||
Train,
|
|
||||||
ScheduledTrain,
|
|
||||||
Station,
|
|
||||||
StationTrain,
|
|
||||||
ScenerySpawn,
|
|
||||||
ScenerySpawnType
|
|
||||||
} from '../typings/common';
|
|
||||||
|
|
||||||
export function getStatusTimestamp(stationStatus: any): number {
|
export function getStatusTimestamp(stationStatus: any): number {
|
||||||
if (!stationStatus) return -2;
|
if (!stationStatus) return -2;
|
||||||
@@ -57,176 +48,3 @@ export function parseSpawns(spawnString: string | null): ScenerySpawn[] {
|
|||||||
export function getTimestamp(date: string | null): number {
|
export function getTimestamp(date: string | null): number {
|
||||||
return date ? new Date(date).getTime() : 0;
|
return date ? new Date(date).getTime() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTrainStopStatus(
|
|
||||||
stopInfo: TrainStop,
|
|
||||||
currentStationName: string,
|
|
||||||
sceneryName: string
|
|
||||||
) {
|
|
||||||
let stopStatus = StopStatus.ARRIVING,
|
|
||||||
stopLabel = '',
|
|
||||||
stopStatusID = -1;
|
|
||||||
|
|
||||||
if (stopInfo.terminatesHere && stopInfo.confirmed) {
|
|
||||||
stopStatus = StopStatus.TERMINATED;
|
|
||||||
stopLabel = 'Skończył bieg';
|
|
||||||
stopStatusID = 5;
|
|
||||||
} else if (!stopInfo.terminatesHere && stopInfo.confirmed && currentStationName == sceneryName) {
|
|
||||||
stopStatus = StopStatus.DEPARTED;
|
|
||||||
stopLabel = 'Odprawiony';
|
|
||||||
stopStatusID = 2;
|
|
||||||
} else if (!stopInfo.terminatesHere && stopInfo.confirmed && currentStationName != sceneryName) {
|
|
||||||
stopStatus = StopStatus.DEPARTED_AWAY;
|
|
||||||
stopLabel = 'Odjechał';
|
|
||||||
stopStatusID = 4;
|
|
||||||
} else if (currentStationName == sceneryName && !stopInfo.stopped) {
|
|
||||||
stopStatus = StopStatus.ONLINE;
|
|
||||||
stopLabel = 'Na stacji';
|
|
||||||
stopStatusID = 0;
|
|
||||||
} else if (currentStationName == sceneryName && stopInfo.stopped) {
|
|
||||||
stopStatus = StopStatus.STOPPED;
|
|
||||||
stopLabel = 'Postój';
|
|
||||||
stopStatusID = 1;
|
|
||||||
} else if (currentStationName != sceneryName) {
|
|
||||||
stopStatus = StopStatus.ARRIVING;
|
|
||||||
stopLabel = 'W drodze';
|
|
||||||
stopStatusID = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { stopStatus, stopLabel, stopStatusID };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCheckpointTrain(
|
|
||||||
train: Train,
|
|
||||||
trainStopIndex: number,
|
|
||||||
sceneryName: string
|
|
||||||
): ScheduledTrain {
|
|
||||||
const timetable = train.timetableData!;
|
|
||||||
const followingStops = timetable.followingStops;
|
|
||||||
const trainStop = followingStops[trainStopIndex];
|
|
||||||
|
|
||||||
const trainStopStatus = getTrainStopStatus(trainStop, train.currentStationName, sceneryName);
|
|
||||||
|
|
||||||
let prevStationName = '',
|
|
||||||
nextStationName = '';
|
|
||||||
|
|
||||||
let departureLine: string | null = null;
|
|
||||||
let arrivingLine: string | null = null;
|
|
||||||
|
|
||||||
let prevDepartureLine: string | null = null,
|
|
||||||
nextArrivalLine: string | null = null;
|
|
||||||
|
|
||||||
for (let i = trainStopIndex; i >= 0; i--) {
|
|
||||||
const stop = followingStops[i];
|
|
||||||
|
|
||||||
if (/strong|podg\.|pe\./g.test(stop.stopName) && !prevStationName && i <= trainStopIndex - 1)
|
|
||||||
prevStationName = stop.stopNameRAW.replace(/,.*/g, '');
|
|
||||||
|
|
||||||
if (stop.arrivalLine != null && !arrivingLine && !/-|_|it|sbl/gi.test(stop.arrivalLine)) {
|
|
||||||
arrivingLine = stop.arrivalLine;
|
|
||||||
prevDepartureLine = followingStops[i - 1]?.departureLine || null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = trainStopIndex; i < followingStops.length; i++) {
|
|
||||||
const stop = followingStops[i];
|
|
||||||
|
|
||||||
if (/strong|podg\.|pe\./g.test(stop.stopName) && !nextStationName && i > trainStopIndex)
|
|
||||||
nextStationName = stop.stopNameRAW.replace(/,.*/g, '');
|
|
||||||
|
|
||||||
if (stop.departureLine && !departureLine && !/-|_|it|sbl/gi.test(stop.departureLine)) {
|
|
||||||
departureLine = stop.departureLine;
|
|
||||||
nextArrivalLine = followingStops[i + 1]?.arrivalLine || null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
checkpointName: trainStop.stopNameRAW,
|
|
||||||
|
|
||||||
trainNo: train.trainNo,
|
|
||||||
trainId: train.trainId,
|
|
||||||
|
|
||||||
signal: train.signal,
|
|
||||||
connectedTrack: train.connectedTrack,
|
|
||||||
|
|
||||||
driverName: train.driverName,
|
|
||||||
driverId: train.driverId,
|
|
||||||
currentStationName: train.currentStationName,
|
|
||||||
currentStationHash: train.currentStationHash,
|
|
||||||
category: timetable.category,
|
|
||||||
beginsAt: timetable.followingStops[0].stopNameRAW,
|
|
||||||
terminatesAt: timetable.followingStops[timetable.followingStops.length - 1].stopNameRAW,
|
|
||||||
|
|
||||||
nextStationName,
|
|
||||||
prevStationName,
|
|
||||||
|
|
||||||
stopInfo: trainStop,
|
|
||||||
stopLabel: trainStopStatus.stopLabel,
|
|
||||||
stopStatus: trainStopStatus.stopStatus,
|
|
||||||
stopStatusID: trainStopStatus.stopStatusID,
|
|
||||||
|
|
||||||
region: train.region,
|
|
||||||
|
|
||||||
arrivingLine: arrivingLine,
|
|
||||||
departureLine: departureLine,
|
|
||||||
|
|
||||||
nextArrivalLine,
|
|
||||||
prevDepartureLine
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getScheduledTrains(
|
|
||||||
trainList: Train[],
|
|
||||||
stationGeneralInfo: Station['generalInfo'],
|
|
||||||
stationName: string,
|
|
||||||
region: string
|
|
||||||
// sceneryData: API.ActiveSceneries.Data,
|
|
||||||
): ScheduledTrain[] {
|
|
||||||
// stationGeneralInfo?.checkpoints.forEach((cp) => (cp.scheduledTrains.length = 0));
|
|
||||||
|
|
||||||
return trainList.reduce((acc: ScheduledTrain[], train) => {
|
|
||||||
if (!train.timetableData) return acc;
|
|
||||||
if (train.region != region) return acc;
|
|
||||||
|
|
||||||
const timetable = train.timetableData;
|
|
||||||
if (!timetable.sceneryNames.includes(stationName)) return acc;
|
|
||||||
|
|
||||||
const checkpoints = [stationName];
|
|
||||||
if (stationGeneralInfo?.checkpoints) checkpoints.push(...stationGeneralInfo.checkpoints);
|
|
||||||
|
|
||||||
const checkpointScheduledTrains: ScheduledTrain[] = [];
|
|
||||||
for (let i = 0; i < timetable.followingStops.length; i++) {
|
|
||||||
if (
|
|
||||||
new RegExp(`^(${checkpoints.join('|')})$`, 'i').test(
|
|
||||||
timetable.followingStops[i].stopNameRAW
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
checkpointScheduledTrains.push(getCheckpointTrain(train, i, stationName));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
acc.push(...checkpointScheduledTrains);
|
|
||||||
return acc;
|
|
||||||
}, []) as ScheduledTrain[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getStationTrains(
|
|
||||||
trainList: Train[],
|
|
||||||
scheduledTrainList: ScheduledTrain[],
|
|
||||||
region: string,
|
|
||||||
stationName: string
|
|
||||||
): StationTrain[] {
|
|
||||||
return trainList
|
|
||||||
.filter(
|
|
||||||
(train) =>
|
|
||||||
train?.region === region && train.online && train.currentStationName === stationName
|
|
||||||
)
|
|
||||||
.map((train) => ({
|
|
||||||
driverName: train.driverName,
|
|
||||||
driverId: train.driverId,
|
|
||||||
trainNo: train.trainNo,
|
|
||||||
trainId: train.trainId,
|
|
||||||
stopStatus:
|
|
||||||
scheduledTrainList.find((st) => st.trainNo === train.trainNo)?.stopStatus || 'no-timetable'
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -161,7 +161,6 @@ export namespace API {
|
|||||||
stopNameRAW: string;
|
stopNameRAW: string;
|
||||||
stopType: string;
|
stopType: string;
|
||||||
stopDistance: number;
|
stopDistance: number;
|
||||||
pointId: string;
|
|
||||||
|
|
||||||
mainStop: boolean;
|
mainStop: boolean;
|
||||||
|
|
||||||
|
|||||||
+29
-74
@@ -41,7 +41,6 @@ export interface RegionCounters {
|
|||||||
|
|
||||||
export interface Train {
|
export interface Train {
|
||||||
id: string;
|
id: string;
|
||||||
trainId: string;
|
|
||||||
mass: number;
|
mass: number;
|
||||||
length: number;
|
length: number;
|
||||||
speed: number;
|
speed: number;
|
||||||
@@ -79,35 +78,30 @@ export interface Train {
|
|||||||
export interface Station {
|
export interface Station {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
generalInfo?: {
|
generalInfo?: StationGeneralInfo;
|
||||||
name: string;
|
|
||||||
url: string;
|
|
||||||
abbr: string;
|
|
||||||
hash?: string;
|
|
||||||
|
|
||||||
reqLevel: number;
|
|
||||||
// supportersOnly: boolean;
|
|
||||||
|
|
||||||
lines: string;
|
|
||||||
project: string;
|
|
||||||
projectUrl?: string;
|
|
||||||
|
|
||||||
signalType: string;
|
|
||||||
controlType: string;
|
|
||||||
|
|
||||||
SUP: boolean;
|
|
||||||
ASDEK: boolean;
|
|
||||||
authors?: string[];
|
|
||||||
|
|
||||||
availability: Availability;
|
|
||||||
routes: StationRoutes;
|
|
||||||
|
|
||||||
checkpoints: string[];
|
|
||||||
};
|
|
||||||
|
|
||||||
onlineInfo?: ActiveScenery;
|
onlineInfo?: ActiveScenery;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface StationGeneralInfo {
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
abbr: string;
|
||||||
|
hash?: string;
|
||||||
|
reqLevel: number;
|
||||||
|
lines: string;
|
||||||
|
project: string;
|
||||||
|
projectUrl?: string;
|
||||||
|
signalType: string;
|
||||||
|
controlType: string;
|
||||||
|
SUP: boolean;
|
||||||
|
ASDEK: boolean;
|
||||||
|
authors?: string[];
|
||||||
|
availability: Availability;
|
||||||
|
routes: StationRoutes;
|
||||||
|
checkpoints: string[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface StationRoutes {
|
export interface StationRoutes {
|
||||||
single: StationRoutesInfo[];
|
single: StationRoutesInfo[];
|
||||||
double: StationRoutesInfo[];
|
double: StationRoutesInfo[];
|
||||||
@@ -148,8 +142,8 @@ export interface ActiveScenery {
|
|||||||
dispatcherStatus: Status.ActiveDispatcher | number;
|
dispatcherStatus: Status.ActiveDispatcher | number;
|
||||||
dispatcherTimestamp: number | null;
|
dispatcherTimestamp: number | null;
|
||||||
isOnline: boolean;
|
isOnline: boolean;
|
||||||
stationTrains?: StationTrain[];
|
stationTrains: Train[];
|
||||||
scheduledTrains?: ScheduledTrain[];
|
scheduledTrains: CheckpointTrain[];
|
||||||
scheduledTrainCount: {
|
scheduledTrainCount: {
|
||||||
all: number;
|
all: number;
|
||||||
confirmed: number;
|
confirmed: number;
|
||||||
@@ -164,49 +158,6 @@ export interface ScenerySpawn {
|
|||||||
spawnType: ScenerySpawnType;
|
spawnType: ScenerySpawnType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StationTrain {
|
|
||||||
driverName: string;
|
|
||||||
driverId: number;
|
|
||||||
trainNo: number;
|
|
||||||
trainId: string;
|
|
||||||
stopStatus: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ScheduledTrain {
|
|
||||||
checkpointName: string;
|
|
||||||
|
|
||||||
trainId: string;
|
|
||||||
trainNo: number;
|
|
||||||
|
|
||||||
driverName: string;
|
|
||||||
driverId: number;
|
|
||||||
currentStationName: string;
|
|
||||||
currentStationHash: string;
|
|
||||||
category: string;
|
|
||||||
stopInfo: TrainStop;
|
|
||||||
|
|
||||||
terminatesAt: string;
|
|
||||||
beginsAt: string;
|
|
||||||
|
|
||||||
prevStationName: string;
|
|
||||||
nextStationName: string;
|
|
||||||
|
|
||||||
arrivingLine: string | null;
|
|
||||||
departureLine: string | null;
|
|
||||||
|
|
||||||
prevDepartureLine: string | null;
|
|
||||||
nextArrivalLine: string | null;
|
|
||||||
|
|
||||||
signal: string;
|
|
||||||
connectedTrack: string;
|
|
||||||
|
|
||||||
stopLabel: string;
|
|
||||||
stopStatus: StopStatus;
|
|
||||||
stopStatusID: number;
|
|
||||||
|
|
||||||
region: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TrainStop {
|
export interface TrainStop {
|
||||||
stopName: string;
|
stopName: string;
|
||||||
stopNameRAW: string;
|
stopNameRAW: string;
|
||||||
@@ -223,13 +174,17 @@ export interface TrainStop {
|
|||||||
departureTimestamp: number;
|
departureTimestamp: number;
|
||||||
departureRealTimestamp: number;
|
departureRealTimestamp: number;
|
||||||
departureDelay: number;
|
departureDelay: number;
|
||||||
pointId: number;
|
|
||||||
|
|
||||||
comments?: string;
|
comments?: string;
|
||||||
|
|
||||||
beginsHere: boolean;
|
beginsHere: boolean;
|
||||||
terminatesHere: boolean;
|
terminatesHere: boolean;
|
||||||
confirmed: boolean;
|
confirmed: number;
|
||||||
stopped: boolean;
|
stopped: number;
|
||||||
stopTime: number | null;
|
stopTime: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CheckpointTrain {
|
||||||
|
checkpointStop: TrainStop;
|
||||||
|
train: Train;
|
||||||
|
}
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ button.back-btn {
|
|||||||
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
||||||
font-size: 1.5em;
|
font-size: 1.25em;
|
||||||
|
|
||||||
button {
|
button {
|
||||||
margin: 1em auto;
|
margin: 1em auto;
|
||||||
@@ -233,7 +233,7 @@ button.back-btn {
|
|||||||
background-color: #181818;
|
background-color: #181818;
|
||||||
padding: 1em 0.5em;
|
padding: 1em 0.5em;
|
||||||
|
|
||||||
height: 95vh;
|
height: 100vh;
|
||||||
min-height: 750px;
|
min-height: 750px;
|
||||||
max-height: 1000px;
|
max-height: 1000px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
@@ -246,7 +246,7 @@ button.back-btn {
|
|||||||
background: #181818;
|
background: #181818;
|
||||||
padding: 1em 0.5em;
|
padding: 1em 0.5em;
|
||||||
|
|
||||||
height: 95vh;
|
height: 100vh;
|
||||||
min-height: 750px;
|
min-height: 750px;
|
||||||
max-height: 1000px;
|
max-height: 1000px;
|
||||||
|
|
||||||
|
|||||||
+27
-14
@@ -11,16 +11,16 @@
|
|||||||
<button
|
<button
|
||||||
class="btn-donation btn--image"
|
class="btn-donation btn--image"
|
||||||
ref="btn"
|
ref="btn"
|
||||||
@click="isDonationModalOpen = true"
|
@click="isDonationCardOpen = true"
|
||||||
@focus="isDonationModalOpen = false"
|
@focus="isDonationCardOpen = false"
|
||||||
>
|
>
|
||||||
<img src="/images/icon-dollar.svg" alt="dollar donation icon" />
|
<img src="/images/icon-dollar.svg" alt="dollar donation icon" />
|
||||||
<span>{{ $t('donations.button-title') }}</span>
|
<span>{{ $t('donations.button-title') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DonationModal :isModalOpen="isDonationModalOpen" @toggleModal="toggleDonationModal" />
|
<DonationCard :is-card-open="isDonationCardOpen" @toggle-card="toggleDonationCard" />
|
||||||
<StationTable @toggleDonationModal="toggleDonationModal" />
|
<StationTable @toggle-donation-card="toggleDonationCard" />
|
||||||
<StationStats />
|
<StationStats />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -30,34 +30,47 @@
|
|||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import StationTable from '../components/StationsView/StationTable.vue';
|
import StationTable from '../components/StationsView/StationTable.vue';
|
||||||
import StationFilterCard from '../components/StationsView/StationFilterCard.vue';
|
import StationFilterCard from '../components/StationsView/StationFilterCard.vue';
|
||||||
import { useStationFiltersStore } from '../store/stationFiltersStore';
|
|
||||||
import { useMainStore } from '../store/mainStore';
|
import { useMainStore } from '../store/mainStore';
|
||||||
import DonationModal from '../components/Global/DonationModal.vue';
|
import DonationCard from '../components/Global/DonationCard.vue';
|
||||||
import StationStats from '../components/StationsView/StationStats.vue';
|
import StationStats from '../components/StationsView/StationStats.vue';
|
||||||
|
import { initFilters, setupFilters } from '../managers/stationFilterManager';
|
||||||
|
import { reactive } from 'vue';
|
||||||
|
import { provide } from 'vue';
|
||||||
|
import { ActiveSorter } from '../components/StationsView/typings';
|
||||||
|
import { onMounted } from 'vue';
|
||||||
|
|
||||||
|
const filterInitStates = { ...initFilters };
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
StationTable,
|
StationTable,
|
||||||
StationFilterCard,
|
StationFilterCard,
|
||||||
StationStats,
|
StationStats,
|
||||||
DonationModal
|
DonationCard
|
||||||
},
|
},
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
filterCardOpen: false,
|
filterCardOpen: false,
|
||||||
isDonationModalOpen: false,
|
isDonationCardOpen: false,
|
||||||
|
|
||||||
filterStore: useStationFiltersStore(),
|
mainStore: useMainStore()
|
||||||
store: useMainStore()
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
mounted() {
|
setup() {
|
||||||
this.filterStore.setupFilters();
|
const filters = reactive(filterInitStates);
|
||||||
|
const activeSorter = reactive({ headerName: 'station', dir: 1 }) as ActiveSorter;
|
||||||
|
|
||||||
|
provide('StationsView_filters', filters);
|
||||||
|
provide('StationsView_activeSorter', activeSorter);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
setupFilters(filters);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
toggleDonationModal(value: boolean) {
|
toggleDonationCard(value: boolean) {
|
||||||
this.isDonationModalOpen = value;
|
this.isDonationCardOpen = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ export default defineConfig({
|
|||||||
port: 5001,
|
port: 5001,
|
||||||
open: true
|
open: true
|
||||||
},
|
},
|
||||||
|
preview: {
|
||||||
|
port: 4001,
|
||||||
|
open: true
|
||||||
|
},
|
||||||
publicDir: 'public',
|
publicDir: 'public',
|
||||||
plugins: [
|
plugins: [
|
||||||
vue(),
|
vue(),
|
||||||
@@ -36,6 +40,10 @@ export default defineConfig({
|
|||||||
cacheName: 'spythere-static-cache',
|
cacheName: 'spythere-static-cache',
|
||||||
cacheableResponse: {
|
cacheableResponse: {
|
||||||
statuses: [0, 200]
|
statuses: [0, 200]
|
||||||
|
},
|
||||||
|
expiration: {
|
||||||
|
maxEntries: 100,
|
||||||
|
maxAgeSeconds: 60 * 60 * 8
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user