Merge pull request #85 from Spythere/development

Wersja 1.23.1
This commit is contained in:
Spythere
2024-04-01 13:00:28 +02:00
committed by GitHub
22 changed files with 388 additions and 267 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "stacjownik", "name": "stacjownik",
"version": "1.23.0", "version": "1.23.1",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
+28 -34
View File
@@ -4,7 +4,7 @@
<transition name="modal-anim"> <transition name="modal-anim">
<keep-alive> <keep-alive>
<TrainModal v-if="store.chosenModalTrainId" /> <TrainModal />
</keep-alive> </keep-alive>
</transition> </transition>
@@ -34,11 +34,12 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, watch } from 'vue'; import { defineComponent } from 'vue';
import axios from 'axios'; import axios from 'axios';
import { version } from '.././package.json'; import { version } from '.././package.json';
import { useMainStore } from './store/mainStore'; import { useMainStore } from './store/mainStore';
import popupMixin from './mixins/popupMixin';
import Clock from './components/App/Clock.vue'; import Clock from './components/App/Clock.vue';
import StatusIndicator from './components/App/StatusIndicator.vue'; import StatusIndicator from './components/App/StatusIndicator.vue';
@@ -48,7 +49,6 @@ import StorageManager from './managers/storageManager';
import PopUp from './components/PopUp/PopUp.vue'; import PopUp from './components/PopUp/PopUp.vue';
import { useApiStore } from './store/apiStore'; import { useApiStore } from './store/apiStore';
import { Status } from './typings/common'; import { Status } from './typings/common';
import { usePopupStore } from './store/popupStore';
const STORAGE_VERSION_KEY = 'app_version'; const STORAGE_VERSION_KEY = 'app_version';
@@ -61,11 +61,12 @@ export default defineComponent({
PopUp PopUp
}, },
mixins: [popupMixin],
data: () => ({ data: () => ({
VERSION: version, VERSION: version,
store: useMainStore(), store: useMainStore(),
apiStore: useApiStore(), apiStore: useApiStore(),
popupStore: usePopupStore(),
currentLang: 'pl', currentLang: 'pl',
releaseURL: '', releaseURL: '',
@@ -83,35 +84,7 @@ export default defineComponent({
this.apiStore.fetchActiveData(); this.apiStore.fetchActiveData();
}); });
// popup handling window.addEventListener('mousemove', (e: MouseEvent) => this.handlePopUpEvents(e));
window.addEventListener('mousemove', (e: MouseEvent) => {
e.stopPropagation();
const targetEl = e
.composedPath()
.find((p) => p instanceof HTMLElement && p.getAttribute('data-popup-key'));
if (!targetEl || !(targetEl instanceof HTMLElement)) {
if (this.popupStore.currentPopupComponent != null) this.popupStore.onPopUpHide();
return;
}
const popupComponentKey = targetEl.getAttribute('data-popup-key');
const popupContent = targetEl.getAttribute('data-popup-content');
if (popupComponentKey && popupContent)
this.popupStore.onPopUpShow(e, popupComponentKey, popupContent);
else if (this.popupStore.currentPopupComponent != null) this.popupStore.onPopUpHide();
});
watch(
() => this.store.blockScroll,
(value) => {
if (value) document.body.classList.add('no-scroll');
else document.body.classList.remove('no-scroll');
}
);
}, },
methods: { methods: {
@@ -164,6 +137,27 @@ export default defineComponent({
this.apiStore.connectToAPI(); this.apiStore.connectToAPI();
}, },
handlePopUpEvents(e: MouseEvent) {
const targetEl = e
.composedPath()
.find((p) => p instanceof HTMLElement && p.getAttribute('data-popup-key'));
if (!targetEl || !(targetEl instanceof HTMLElement)) {
if (this.store.popUpData.key != null) this.hidePopUp();
return;
}
const popupComponentKey = targetEl.getAttribute('data-popup-key');
const popupContent = targetEl.getAttribute('data-popup-content');
if (popupComponentKey && popupContent) this.showPopUp(e, popupComponentKey, popupContent);
else if (this.store.popUpData.key != null) this.hidePopUp();
this.store.mousePos.x = e.pageX;
this.store.mousePos.y = e.pageY;
},
changeLang(lang: string) { changeLang(lang: string) {
this.$i18n.locale = lang; this.$i18n.locale = lang;
this.currentLang = lang; this.currentLang = lang;
@@ -243,7 +237,7 @@ export default defineComponent({
grid-template-columns: 100%; grid-template-columns: 100%;
min-height: 100vh; min-height: 100vh;
position: relative; overflow: hidden;
} }
.app_main { .app_main {
+93 -8
View File
@@ -1,15 +1,36 @@
<template> <template>
<AnimatedModal :is-open="mainStore.isNewUpdate" @toggle-modal="toggleModal"> <AnimatedModal :is-open="mainStore.isNewUpdate" @toggle-modal="toggleModal">
<div class="modal_content"> <div class="modal_content">
<h1 class="header">Aktualizacja Stacjownika</h1> <div>
<h2>wersja {{ version }}</h2> <h1 style="margin-bottom: 0.5em">{{ $t('update.title') }}</h1>
<h2 class="text--primary">{{ $t('update.version', [version]) }}</h2>
<hr class="separator" />
</div>
<b>Co nowego?</b> <div class="features-list">
<p> <h2>Nowości i zmiany:</h2>
<ul> <ul>
<li>test</li> <li v-for="content in localeChangesArray" :key="content">{{ content }}</li>
</ul> </ul>
</p> </div>
<div class="modal_actions">
<button class="btn--action">Przyjąłem!</button>
<p>Ten changelog będzie zawsze dostępny po kliknięciu numeru wersji w stopce strony!</p>
<!-- <div class="actions-checkboxes">
<label>
<input type="checkbox" />
<span>nie pokazuj dla przyszłych aktualizacji</span>
</label>
<label>
<input type="checkbox" />
<span>nie pokazuj dla przyszłych aktualizacji</span>
</label>
</div> -->
</div>
</div> </div>
</AnimatedModal> </AnimatedModal>
</template> </template>
@@ -30,6 +51,12 @@ export default defineComponent({
}; };
}, },
computed: {
localeChangesArray() {
return this.$t('update.content').split('\n');
}
},
methods: { methods: {
toggleModal(value: boolean) { toggleModal(value: boolean) {
this.$emit('toggleModal', value); this.$emit('toggleModal', value);
@@ -40,9 +67,67 @@ export default defineComponent({
<style lang="scss" scoped> <style lang="scss" scoped>
.modal_content { .modal_content {
font-size: 1.2em;
text-align: center; text-align: center;
padding: 1em; padding: 1em;
height: 80vh; height: 80vh;
min-height: 550px; min-height: 550px;
display: grid;
grid-template-rows: auto 1fr auto;
gap: 0.5em;
}
hr.separator {
margin: 0.5em 0;
padding: 0;
height: 3px;
background-color: #fff;
}
.features-list {
margin-top: 0.5em;
overflow: auto;
ul {
text-align: left;
list-style: '\21D2 ';
padding: 1em;
}
li {
margin: 0.5em 0;
}
}
.modal_actions {
display: flex;
flex-wrap: wrap;
gap: 0.5em;
button {
font-weight: bold;
padding: 0.35em;
}
p {
font-size: 0.9em;
}
}
.actions-checkboxes {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 1em;
label {
font-size: 0.9em;
}
label > input {
margin-right: 0.5em;
}
} }
</style> </style>
@@ -34,15 +34,6 @@
<strong v-else> <strong v-else>
{{ timetable.driverName }} {{ timetable.driverName }}
</strong> </strong>
<button
v-if="timetable.terminated == false"
class="btn--image btn--action btn-timetable"
@click.stop="showTimetable(timetable, $event.currentTarget)"
>
<img src="/images/icon-train.svg" alt="" />
{{ $t('journal.timetable-online-button') }}
</button>
</span> </span>
<span class="general-time"> <span class="general-time">
@@ -70,6 +61,15 @@
: `${$t('journal.timetable-abandoned')} ${localeTime(timetable.endDate, $i18n.locale)}` : `${$t('journal.timetable-abandoned')} ${localeTime(timetable.endDate, $i18n.locale)}`
}} }}
</b> </b>
<button
v-if="timetable.terminated == false"
class="btn--image btn--action btn-timetable"
@click.stop="showTimetable(timetable, $event.currentTarget)"
>
<img src="/images/icon-train.svg" alt="" />
{{ $t('journal.timetable-online-button') }}
</button>
</span> </span>
</div> </div>
</template> </template>
@@ -144,12 +144,14 @@ export default defineComponent({
} }
.general-train { .general-train {
cursor: pointer;
display: flex; display: flex;
flex-wrap: wrap;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
flex-wrap: wrap;
gap: 0.25em; gap: 0.25em;
cursor: pointer;
line-height: 2;
} }
.btn-timetable { .btn-timetable {
@@ -5,7 +5,7 @@
v-for="{ timetable, showExtraInfo } in computedTimetableHistory" v-for="{ timetable, showExtraInfo } in computedTimetableHistory"
class="journal_item" class="journal_item"
:key="timetable.id" :key="timetable.id"
@click.stop.prevent="showExtraInfo.value = !showExtraInfo.value" @click="showExtraInfo.value = !showExtraInfo.value"
> >
<div class="journal_item-info"> <div class="journal_item-info">
<!-- General --> <!-- General -->
+3 -3
View File
@@ -1,18 +1,18 @@
<template> <template>
<div class="popup-content"> <div class="popup-content">
<img src="/images/icon-diamond.svg" alt="" /> <img src="/images/icon-diamond.svg" alt="" />
<span>{{ popupStore.currentPopupContent }}</span> <span>{{ store.popUpData.content }}</span>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { usePopupStore } from '../../store/popupStore'; import { useMainStore } from '../../store/mainStore';
export default defineComponent({ export default defineComponent({
data() { data() {
return { return {
popupStore: usePopupStore() store: useMainStore()
}; };
} }
}); });
+34 -18
View File
@@ -1,6 +1,6 @@
<template> <template>
<div class="popup" v-show="popupStore.currentPopupComponent" ref="preview"> <div class="popup" v-show="store.popUpData.key" ref="preview">
<component v-if="popupStore.currentPopupComponent" :is="popupStore.currentPopupComponent" /> <component v-if="store.popUpData.key" :is="store.popUpData.key" />
</div> </div>
</template> </template>
@@ -9,35 +9,51 @@ import { defineComponent } from 'vue';
import DonatorPopUp from './DonatorPopUp.vue'; import DonatorPopUp from './DonatorPopUp.vue';
import TrainCommentsPopUp from './TrainCommentsPopUp.vue'; import TrainCommentsPopUp from './TrainCommentsPopUp.vue';
import VehiclePreviewPopUp from './VehiclePreviewPopUp.vue'; import VehiclePreviewPopUp from './VehiclePreviewPopUp.vue';
import { usePopupStore } from '../../store/popupStore'; import { useMainStore } from '../../store/mainStore';
export default defineComponent({ export default defineComponent({
components: { DonatorPopUp, TrainCommentsPopUp, VehiclePreviewPopUp }, components: { DonatorPopUp, TrainCommentsPopUp, VehiclePreviewPopUp },
data() { data() {
return { return {
popupStore: usePopupStore() store: useMainStore()
}; };
}, },
watch: { watch: {
'popupStore.popupPosition': { 'store.mousePos': {
deep: true, deep: true,
handler(val: typeof this.popupStore.popupPosition) { handler(val: typeof this.store.mousePos) {
const previewEl = this.$refs['preview'] as HTMLElement;
previewEl.style.top = `${val.y}px`;
previewEl.style.left = `${val.x}px`;
previewEl.style.transform = 'translateY(1.5rem)';
this.$nextTick(() => { this.$nextTick(() => {
const isOutside = const previewEl = this.$refs['preview'] as HTMLElement;
val.y + previewEl.getBoundingClientRect().height > window.innerHeight + window.scrollY; const clientWidth = document.body.clientWidth;
const boxWidth = previewEl.getBoundingClientRect().width;
// previewEl.style.transform = `translate(-${~~((val.x / window.innerWidth) * 100)}%, calc(${isOutside ? '-100% - 1.5rem' : '1.5rem'}))`; let translateX = '0px',
previewEl.style.transform = `translate(-${~~((val.x / window.innerWidth) * 100)}%, calc(${ translateY = '30px';
isOutside ? '-100% - 1.5rem' : '1.5rem'
}))`; if (clientWidth < 500) {
previewEl.style.left = '50%';
translateX = '-50%';
} else if (val.x <= boxWidth / 2) {
previewEl.style.left = '0';
translateX = '0px';
} else if (val.x >= clientWidth - boxWidth / 2) {
previewEl.style.left = '100%';
translateX = '-100%';
} else {
previewEl.style.left = `${val.x}px`;
translateX = '-50%';
}
previewEl.style.top = `${val.y}px`;
const isOutside =
val.y + previewEl.getBoundingClientRect().height + 30 >=
window.innerHeight + window.scrollY;
if (isOutside) translateY = 'calc(-100% - 30px)';
previewEl.style.transform = `translate(${translateX}, ${translateY})`;
}); });
} }
} }
+3 -3
View File
@@ -1,17 +1,17 @@
<template> <template>
<div class="popup-content"> <div class="popup-content">
<span>{{ popupStore.currentPopupContent }}</span> <span>{{ store.popUpData.content }}</span>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { usePopupStore } from '../../store/popupStore'; import { useMainStore } from '../../store/mainStore';
export default defineComponent({ export default defineComponent({
data() { data() {
return { return {
popupStore: usePopupStore() store: useMainStore()
}; };
} }
}); });
+5 -6
View File
@@ -7,30 +7,29 @@
<div v-if="imageState == 'error'">{{ $t('vehicle-preview.error') }}</div> <div v-if="imageState == 'error'">{{ $t('vehicle-preview.error') }}</div>
<img <img
v-if="popupStore.currentPopupContent" v-if="store.popUpData.key"
@load="onImageLoad" @load="onImageLoad"
@error="onImageError" @error="onImageError"
@click="popupStore.onPopUpHide"
width="300" width="300"
height="176" height="176"
class="rounded-md w-full h-auto" class="rounded-md w-full h-auto"
:src="`https://spythere.github.io/api/td2/images/${popupStore.currentPopupContent}--300px.jpg`" :src="`https://static.spythere.eu/images/${store.popUpData.content}--300px.jpg`"
/> />
<div class="vehicle-name" v-if="imageState != 'error'"> <div class="vehicle-name" v-if="imageState != 'error'">
{{ popupStore.currentPopupContent.replace(/_/g, ' ') }} {{ store.popUpData.content.replace(/_/g, ' ') }}
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { usePopupStore } from '../../store/popupStore'; import { useMainStore } from '../../store/mainStore';
export default defineComponent({ export default defineComponent({
data() { data() {
return { return {
popupStore: usePopupStore(), store: useMainStore(),
imageState: 'loading' imageState: 'loading'
}; };
}, },
+4 -6
View File
@@ -312,7 +312,7 @@ import { HeadIdsTypes, headIconsIds, headIds } from '../../scripts/data/stationH
import StationStatusBadge from '../Global/StationStatusBadge.vue'; import StationStatusBadge from '../Global/StationStatusBadge.vue';
import { Status } from '../../typings/common'; import { Status } from '../../typings/common';
import { useApiStore } from '../../store/apiStore'; import { useApiStore } from '../../store/apiStore';
import { usePopupStore } from '../../store/popupStore'; import popupMixin from '../../mixins/popupMixin';
export default defineComponent({ export default defineComponent({
props: { props: {
@@ -324,7 +324,7 @@ export default defineComponent({
emits: ['toggleDonationModal'], emits: ['toggleDonationModal'],
components: { Loading, StationStatusBadge }, components: { Loading, StationStatusBadge },
mixins: [styleMixin, dateMixin, stationInfoMixin], mixins: [styleMixin, dateMixin, stationInfoMixin, popupMixin],
data: () => ({ data: () => ({
headIconsIds, headIconsIds,
@@ -341,7 +341,6 @@ export default defineComponent({
setup() { setup() {
const mainStore = useMainStore(); const mainStore = useMainStore();
const apiStore = useApiStore(); const apiStore = useApiStore();
const popupStore = usePopupStore();
const stationFiltersStore = useStationFiltersStore(); const stationFiltersStore = useStationFiltersStore();
@@ -349,8 +348,7 @@ export default defineComponent({
Status: Status.Data, Status: Status.Data,
stationFiltersStore, stationFiltersStore,
mainStore, mainStore,
apiStore, apiStore
popupStore
}; };
}, },
@@ -373,7 +371,7 @@ export default defineComponent({
openDonationModal(e: Event) { openDonationModal(e: Event) {
this.$emit('toggleDonationModal', true); this.$emit('toggleDonationModal', true);
this.mainStore.modalLastClickedTarget = e.target; this.mainStore.modalLastClickedTarget = e.target;
this.popupStore.currentPopupComponent = null; this.hidePopUp();
}, },
openForumSite(e: Event, url: string | undefined) { openForumSite(e: Event, url: string | undefined) {
+130 -88
View File
@@ -1,57 +1,71 @@
<template> <template>
<div class="train-info"> <div class="train-info" :data-extended="extended">
<section class="train-general"> <section class="train-general">
<div class="general-info"> <div class="general-top-bar">
<b class="warning-timeout" v-if="train.isTimeout" :title="$t('trains.timeout')">?</b> <div>
<span class="timetable-id" v-if="train.timetableData"> <b class="warning-timeout" v-if="train.isTimeout" :title="$t('trains.timeout')">?</b>
#{{ train.timetableData.timetableId }} <span class="timetable-id" v-if="train.timetableData">
</span> #{{ train.timetableData.timetableId }}
<span
class="timetable-warnings"
v-if="train.timetableData?.TWR || train.timetableData?.SKR"
>
<span class="train-badge twr" v-if="train.timetableData?.TWR" :title="$t('general.TWR')">
TWR
</span> </span>
<span class="train-badge skr" v-if="train.timetableData?.SKR" :title="$t('general.SKR')">
SKR
</span>
</span>
<strong> <span
<span v-if="train.timetableData" class="text--primary" class="timetable-warnings"
>{{ train.timetableData.category }}&nbsp;</span v-if="train.timetableData?.TWR || train.timetableData?.SKR"
> >
<span class="train-number">{{ train.trainNo }}</span> <span
</strong> class="train-badge twr"
<span>&bull;</span> v-if="train.timetableData?.TWR"
<b :title="$t('general.TWR')"
class="level-badge driver" >
:style="calculateExpStyle(train.driverLevel, train.isSupporter)" TWR
> </span>
{{ train.driverLevel < 2 ? 'L' : `${train.driverLevel}` }} <span
</b> class="train-badge skr"
v-if="train.timetableData?.SKR"
:title="$t('general.SKR')"
>
SKR
</span>
</span>
<div class="train-driver"> <strong>
<span v-if="train.timetableData" class="text--primary"
>{{ train.timetableData.category }}&nbsp;</span
>
<span class="train-number">{{ train.trainNo }}</span>
</strong>
<span>&bull;</span>
<b <b
v-if="apiStore.donatorsData.includes(train.driverName)" class="level-badge driver"
data-popup-key="DonatorPopUp" :style="calculateExpStyle(train.driverLevel, train.isSupporter)"
:data-popup-content="$t('donations.driver-message')"
> >
{{ train.driverName }} {{ train.driverLevel < 2 ? 'L' : `${train.driverLevel}` }}
<img src="/images/icon-diamond.svg" alt="donator diamond icon" />
</b> </b>
<span v-else>{{ train.driverName }}</span> <div class="train-driver">
<b
v-if="apiStore.donatorsData.includes(train.driverName)"
data-popup-key="DonatorPopUp"
:data-popup-content="$t('donations.driver-message')"
>
{{ train.driverName }}
<img src="/images/icon-diamond.svg" alt="donator diamond icon" />
</b>
<button <span v-else>{{ train.driverName }}</span>
class="btn--image btn--action btn-timetable" </div>
@click="navigateToJournal" </div>
v-if="extended"
> <div v-if="extended">
<img src="/images/icon-train.svg" alt="" /> <button class="btn-timetable btn--image btn--action" @click="navigateToJournal">
{{ $t('trains.journal-button') }} <img src="/images/icon-train.svg" alt="train icon" />
<span>
{{ $t('trains.journal-button') }}
</span>
</button>
<button class="btn-exit btn--image btn--action" @click="closeModal">
<img src="/images/icon-exit.svg" alt="modal exit icon" />
</button> </button>
</div> </div>
</div> </div>
@@ -79,7 +93,7 @@
</div> </div>
<div class="general-status"> <div class="general-status">
<div class="timetable-progress" v-if="train.timetableData"> <div class="status-timetable-progress" v-if="train.timetableData">
<ProgressBar :progressPercent="confirmedPercentage(train.timetableData.followingStops)" /> <ProgressBar :progressPercent="confirmedPercentage(train.timetableData.followingStops)" />
<span class="progress-distance"> <span class="progress-distance">
@@ -103,12 +117,29 @@
</div> </div>
</div> </div>
<div class="driver_position text--grayed" style="margin-top: 0.25em"> <div class="general-stats" v-if="extended">
<div>
<img src="/images/icon-length.svg" alt="length icon" />
{{ train.length }}m
</div>
<div>
<img src="/images/icon-mass.svg" alt="mass icon" />
{{ (train.mass / 1000).toFixed(1) }}t
</div>
<div>
<img src="/images/icon-speed.svg" alt="speed icon" />
{{ train.speed }} km/h
</div>
</div>
<div class="text--grayed" style="margin-top: 0.25em">
{{ displayTrainPosition(train) }} {{ displayTrainPosition(train) }}
</div> </div>
</section> </section>
<section class="train-stats"> <section class="train-stats" v-if="!extended">
<StockList :trainStockList="train.stockList" :tractionOnly="true" /> <StockList :trainStockList="train.stockList" :tractionOnly="true" />
<div> <div>
@@ -137,7 +168,6 @@ import ProgressBar from '../Global/ProgressBar.vue';
import { useMainStore } from '../../store/mainStore'; import { useMainStore } from '../../store/mainStore';
import { useApiStore } from '../../store/apiStore'; import { useApiStore } from '../../store/apiStore';
import StockList from '../Global/StockList.vue'; import StockList from '../Global/StockList.vue';
import { usePopupStore } from '../../store/popupStore';
import modalTrainMixin from '../../mixins/modalTrainMixin'; import modalTrainMixin from '../../mixins/modalTrainMixin';
export default defineComponent({ export default defineComponent({
@@ -157,8 +187,7 @@ export default defineComponent({
data() { data() {
return { return {
store: useMainStore(), store: useMainStore(),
apiStore: useApiStore(), apiStore: useApiStore()
popupStore: usePopupStore()
}; };
}, },
@@ -203,6 +232,10 @@ export default defineComponent({
grid-template-columns: 2fr 1fr; grid-template-columns: 2fr 1fr;
grid-template-rows: 1fr; grid-template-rows: 1fr;
&[data-extended='true'] {
grid-template-columns: 1fr;
}
padding: 1em; padding: 1em;
background-color: #1a1a1a; background-color: #1a1a1a;
@@ -214,12 +247,6 @@ export default defineComponent({
vertical-align: text-bottom; vertical-align: text-bottom;
} }
.btn-timetable {
display: inline-block;
padding: 0.25em;
margin-left: 0.5em;
}
.timetable-id { .timetable-id {
color: #d2d2d2; color: #d2d2d2;
} }
@@ -243,14 +270,29 @@ export default defineComponent({
font-size: 0.8em; font-size: 0.8em;
} }
.general-info { .general-top-bar {
display: flex; display: flex;
align-items: center; justify-content: space-between;
flex-wrap: wrap; flex-wrap: wrap;
gap: 0.5em;
gap: 0.25em; & > div {
margin-right: 1.5em; display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0.25em;
}
} }
.btn-timetable {
padding: 0.25em;
}
.btn-exit {
padding: 0.25em;
}
.general-status { .general-status {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -259,6 +301,27 @@ export default defineComponent({
gap: 0.25em; gap: 0.25em;
} }
.general-stats {
display: flex;
gap: 0.5em;
flex-wrap: wrap;
& > div {
display: flex;
align-items: center;
gap: 0.25em;
}
img {
width: 1.5em;
}
}
.general-timetable {
display: flex;
align-items: center;
}
.status-badges { .status-badges {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
@@ -270,17 +333,7 @@ export default defineComponent({
} }
} }
.general-timetable { .status-timetable-progress {
display: flex;
align-items: center;
}
.timetable-warnings {
display: flex;
gap: 0.25em;
}
.timetable-progress {
display: flex; display: flex;
align-items: center; align-items: center;
flex-wrap: wrap; flex-wrap: wrap;
@@ -290,30 +343,19 @@ export default defineComponent({
margin-right: 0.25em; margin-right: 0.25em;
} }
.timetable-warnings {
display: flex;
gap: 0.25em;
}
@include smallScreen() { @include smallScreen() {
.train-info { .train-info {
grid-template-columns: 1fr; grid-template-columns: 1fr;
gap: 1em 0; gap: 1em 0;
text-align: center;
} }
.general-info, .btn-timetable > span {
.general-status, display: none;
.general-timetable {
justify-content: center;
}
.timetable-progress {
justify-content: center;
}
.comments {
flex-direction: column;
justify-content: center;
img {
margin: 0 0 0.5em 0;
}
} }
} }
</style> </style>
+15 -26
View File
@@ -2,10 +2,6 @@
<div class="train-modal" v-if="chosenTrain" @keydown.esc="closeModal"> <div class="train-modal" v-if="chosenTrain" @keydown.esc="closeModal">
<div class="modal_background" @click="closeModal"></div> <div class="modal_background" @click="closeModal"></div>
<div class="modal_content" ref="content" tabindex="0"> <div class="modal_content" ref="content" tabindex="0">
<button class="btn exit" @click="closeModal">
<img src="/images/icon-exit.svg" alt="close card" />
</button>
<TrainInfo :train="chosenTrain" :extended="true" ref="trainInfo" /> <TrainInfo :train="chosenTrain" :extended="true" ref="trainInfo" />
<TrainSchedule :train="chosenTrain" tabindex="0" /> <TrainSchedule :train="chosenTrain" tabindex="0" />
</div> </div>
@@ -17,17 +13,27 @@ import { defineComponent } from 'vue';
import modalTrainMixin from '../../mixins/modalTrainMixin'; import modalTrainMixin from '../../mixins/modalTrainMixin';
import TrainInfo from './TrainInfo.vue'; import TrainInfo from './TrainInfo.vue';
import TrainSchedule from './TrainSchedule.vue'; import TrainSchedule from './TrainSchedule.vue';
import Train from '../../scripts/interfaces/Train';
export default defineComponent({ export default defineComponent({
components: { TrainInfo, TrainSchedule }, components: { TrainInfo, TrainSchedule },
mixins: [modalTrainMixin], mixins: [modalTrainMixin],
activated() { computed: {
const contentEl = this.$refs['content'] as HTMLElement; chosenTrain() {
return this.store.trainList.find((train) => train.trainId == this.store.chosenModalTrainId);
}
},
this.$nextTick(() => { watch: {
contentEl.focus(); chosenTrain(train: Train | undefined) {
}); this.$nextTick(() => {
if (train) {
const contentEl = this.$refs['content'] as HTMLElement;
contentEl.focus();
}
});
}
} }
}); });
</script> </script>
@@ -49,23 +55,6 @@ export default defineComponent({
} }
} }
.exit {
position: absolute;
top: 0;
right: 0;
margin: 0.5em 1em;
padding: 0.25em;
z-index: 201;
img {
width: 1.5rem;
vertical-align: middle;
}
}
.train-modal { .train-modal {
position: fixed; position: fixed;
top: 0; top: 0;
+8 -7
View File
@@ -26,6 +26,13 @@
"TWR": "High risk freight train", "TWR": "High risk freight train",
"SKR": "Train with exceeded gauge" "SKR": "Train with exceeded gauge"
}, },
"update": {
"title": "Stacjownik app update!",
"version": "Version {0}",
"confirm-button": "UPDATE NOW",
"later-button": "LATER",
"content": "context tooltips when hovering over project sponsors and timetable comment warnings\nvehicle image preview when hovering over its thumbnail in the active timetable card view and timetable journal\nlink to the driver's timetable history in the active timetable card view\nlink to the driver's active timetable card view in the timetable journal (available for online trains only)\nnew update card with version changelog"
},
"app": { "app": {
"sceneries": "SCENERIES", "sceneries": "SCENERIES",
"trains": "TRAINS", "trains": "TRAINS",
@@ -41,13 +48,7 @@
"footer": { "footer": {
"discord": "Stacjownik Discord server" "discord": "Stacjownik Discord server"
}, },
"update": {
"title": "New version of the app is available!",
"paragraph1": "Enjoy the application and may the green signal be with you!",
"release-link": "Click here to browse version changelog (GitHub)",
"confirm-button": "UPDATE NOW",
"later-button": "LATER"
},
"vehicle-preview": { "vehicle-preview": {
"loading": "Loading preview...", "loading": "Loading preview...",
"error": "Oops! The vehicle preview seems to be missing! :/" "error": "Oops! The vehicle preview seems to be missing! :/"
+8 -1
View File
@@ -4,7 +4,7 @@
"header": "Grosza daj Stacjownikowi!", "header": "Grosza daj Stacjownikowi!",
"donator-title": "Projekt ma już ponad <b>{count}</b> wspierających, w tym:", "donator-title": "Projekt ma już ponad <b>{count}</b> wspierających, w tym:",
"p1": "<b>Hej o7!</b> Z tej strony Spythere, twórca Stacjownika, Pojazdownika oraz kilku innych aplikacji wspomagających rozgrywkę symulatora Train Driver 2!", "p1": "<b>Hej o7!</b> Z tej strony Spythere, twórca Stacjownika, Pojazdownika oraz kilku innych aplikacji wspomagających rozgrywkę symulatora Train Driver 2!",
"p2": "{b1} to narzędzie całkowicie darmowe, tworzone i rozwijane dla społeczności symulatora TD2 nieprzerwanie od 2020 roku. Jednakże, część projektu jest podtrzymywana wyłącznie dzięki mojemu prywatnemu wkładowi finansowemu. Funkcje takie jak {b2} czy też {b3} działający na moim {link} (na który serdeczne zapraszam) muszą działać na wydzielonym serwerze, gdzie będą mogły zbierać i przetwarzać dane, aby następnie pokazać je na stronie.", "p2": "{b1} to narzędzie całkowicie darmowe, tworzone i rozwijane dla społeczności symulatora TD2 nieprzerwanie od 2020 roku. Jednakże, część projektu jest podtrzymywana wyłącznie dzięki mojemu prywatnemu wkładowi finansowemu. Funkcje takie jak {b2} czy też {b3} działający na moim {link} (na który serdecznie zapraszam) muszą działać na wydzielonym serwerze, gdzie będą mogły zbierać i przetwarzać dane, aby następnie pokazać je na stronie.",
"p2-b1": "Stacjownik", "p2-b1": "Stacjownik",
"p2-b2": "Dziennik", "p2-b2": "Dziennik",
"p2-b3": "Stacjobot", "p2-b3": "Stacjobot",
@@ -26,6 +26,13 @@
"TWR": "Towar niebezpieczny wysokiego ryzyka", "TWR": "Towar niebezpieczny wysokiego ryzyka",
"SKR": "Przekroczona skrajnia" "SKR": "Przekroczona skrajnia"
}, },
"update": {
"title": "Aktualizacja Stacjownika!",
"version": "Wersja {0}",
"confirm-button": "UPDATE NOW",
"later-button": "LATER",
"content": "dymki kontekstowe po najechaniu kursorem na m.in. sponsorów projektu i uwagi eksploatacyjne\npodgląd pojazdu po najechaniu kursorem na jego miniaturkę w karcie aktywnego rozkładu jazdy oraz dzienniku RJ\nodnośnik do historii RJ maszynisty w widoku karty aktywnego rozkładu jazdy\nodnośnik do karty aktywnego rozkładu jazdy maszynisty w dzienniku (dostępny tylko dla pociągów online)\nnowa karta ze zmianami w aktualizacji"
},
"app": { "app": {
"sceneries": "SCENERIE", "sceneries": "SCENERIE",
"trains": "POCIĄGI", "trains": "POCIĄGI",
+2 -10
View File
@@ -1,21 +1,13 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { useMainStore } from '../store/mainStore'; import { useMainStore } from '../store/mainStore';
import { usePopupStore } from '../store/popupStore';
export default defineComponent({ export default defineComponent({
data() { data() {
return { return {
store: useMainStore(), store: useMainStore()
popupStore: usePopupStore()
}; };
}, },
computed: {
chosenTrain() {
return this.store.trainList.find((train) => train.trainId == this.store.chosenModalTrainId);
}
},
methods: { methods: {
selectModalTrain(trainId: string, target?: EventTarget | null) { selectModalTrain(trainId: string, target?: EventTarget | null) {
this.store.chosenModalTrainId = trainId; this.store.chosenModalTrainId = trainId;
@@ -25,7 +17,7 @@ export default defineComponent({
closeModal() { closeModal() {
this.store.chosenModalTrainId = undefined; this.store.chosenModalTrainId = undefined;
this.popupStore.currentPopupComponent = null; this.store.popUpData.key = null;
setTimeout(() => { setTimeout(() => {
(this.store.modalLastClickedTarget as any)?.focus(); (this.store.modalLastClickedTarget as any)?.focus();
+27
View File
@@ -0,0 +1,27 @@
import { defineComponent } from 'vue';
import { useMainStore } from '../store/mainStore';
import { PopUpType, popupKeys } from '../store/typings';
const isPopUp = (v: any): v is PopUpType => popupKeys.includes(v);
export default defineComponent({
data() {
return {
store: useMainStore()
};
},
methods: {
showPopUp(e: MouseEvent, componentKey: string, value?: string) {
if (!isPopUp(componentKey)) return;
this.store.popUpData['key'] = componentKey;
this.store.popUpData['content'] = value ?? '';
},
hidePopUp() {
this.store.popUpData['key'] = null;
this.store.popUpData['content'] = '';
}
}
});
-3
View File
@@ -4,9 +4,6 @@ import { Status } from '../typings/common';
import { StationJSONData } from './typings'; import { StationJSONData } from './typings';
import axios, { AxiosInstance } from 'axios'; import axios, { AxiosInstance } from 'axios';
// Update seconds cron for active data scheduler
const UPDATE_SECONDS = [3, 23, 43];
export enum APIMode { export enum APIMode {
PRODUCTION = 0, PRODUCTION = 0,
DEV = 1, DEV = 1,
+6 -3
View File
@@ -26,8 +26,10 @@ export const useMainStore = defineStore('store', {
chosenModalTrainId: undefined, chosenModalTrainId: undefined,
blockScroll: false, modalLastClickedTarget: null,
modalLastClickedTarget: null
mousePos: { x: 0, y: 0 },
popUpData: { key: null, content: '' }
}) as StoreState, }) as StoreState,
getters: { getters: {
@@ -45,7 +47,8 @@ export const useMainStore = defineStore('store', {
const sceneryNames = const sceneryNames =
train.timetable?.sceneries?.map( train.timetable?.sceneries?.map(
(sceneryHash) => (sceneryHash) =>
this.activeSceneryList.find((st) => st.hash === sceneryHash)?.name ?? apiStore.activeData?.activeSceneries?.find((st) => st.stationHash === sceneryHash)
?.stationName ??
apiStore.sceneryData.find((sd) => sd.hash === sceneryHash)?.name ?? apiStore.sceneryData.find((sd) => sd.hash === sceneryHash)?.name ??
sceneryHash sceneryHash
) ?? []; ) ?? [];
-37
View File
@@ -1,37 +0,0 @@
import { defineStore } from 'pinia';
export const popupKeys = ['DonatorPopUp', 'TrainCommentsPopUp', 'VehiclePreviewPopUp'] as const;
export type PopUp = (typeof popupKeys)[number];
const isPopUp = (v: any): v is PopUp => popupKeys.includes(v);
export const usePopupStore = defineStore('popupStore', {
state: () => ({
popupPosition: { x: 0, y: 0 },
currentPopupComponent: null as PopUp | null,
currentPopupContent: '',
donatorPopupVisible: false
}),
actions: {
onPopUpShow(e: MouseEvent, componentKey: string, value?: string) {
if (!isPopUp(componentKey)) return;
this.popupPosition.x = e.pageX;
this.popupPosition.y = e.pageY;
this.currentPopupComponent = componentKey;
this.currentPopupContent = value ?? '';
},
onPopUpMove(e: MouseEvent) {
this.popupPosition.x = e.pageX;
this.popupPosition.y = e.pageY;
},
onPopUpHide() {
this.currentPopupComponent = null;
this.currentPopupContent = '';
}
}
});
+4 -1
View File
@@ -1,7 +1,9 @@
import { API } from '../typings/api'; import { API } from '../typings/api';
import { Status } from '../typings/common'; import { Status } from '../typings/common';
export const popupKeys = ['DonatorPopUp', 'TrainCommentsPopUp', 'VehiclePreviewPopUp'] as const;
export type Availability = 'default' | 'unavailable' | 'nonPublic' | 'abandoned' | 'nonDefault'; export type Availability = 'default' | 'unavailable' | 'nonPublic' | 'abandoned' | 'nonDefault';
export type PopUpType = (typeof popupKeys)[number];
export interface RegionCounters { export interface RegionCounters {
stationCount: number; stationCount: number;
@@ -19,8 +21,9 @@ export interface StoreState {
driverStatsData?: API.DriverStats.Response; driverStatsData?: API.DriverStats.Response;
driverStatsStatus: Status.Data; driverStatsStatus: Status.Data;
chosenModalTrainId?: string; chosenModalTrainId?: string;
blockScroll: boolean;
modalLastClickedTarget: EventTarget | null; modalLastClickedTarget: EventTarget | null;
mousePos: { x: number; y: number };
popUpData: { key: PopUpType | null; content: string };
} }
export interface StationRoutesInfo { export interface StationRoutesInfo {
+1
View File
@@ -6,6 +6,7 @@
height: 90vh; height: 90vh;
min-height: 550px; min-height: 550px;
margin-top: 0.5em; margin-top: 0.5em;
position: relative;
padding-right: 0.2em; padding-right: 0.2em;
} }
+2
View File
@@ -55,6 +55,8 @@ body {
-webkit-font-smoothing: antialiased !important; -webkit-font-smoothing: antialiased !important;
overflow-y: scroll; overflow-y: scroll;
overflow-x: hidden;
position: relative;
&.no-scroll { &.no-scroll {
overflow-y: hidden; overflow-y: hidden;