mirror of
https://github.com/Spythere/stacjownik.git
synced 2026-05-03 05:18:11 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3ef27e1d69 | |||
| f53993c717 | |||
| 235c16e30f | |||
| c3533f07ad | |||
| d05579c5ee | |||
| c8f53c2f06 | |||
| b44f88ebcd | |||
| 7805d1350c | |||
| b17bd19433 | |||
| c12a6cbacd | |||
| ba650238db | |||
| d5ec9919e2 |
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "stacjownik",
|
||||
"version": "1.23.0",
|
||||
"version": "1.23.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
+28
-34
@@ -4,7 +4,7 @@
|
||||
|
||||
<transition name="modal-anim">
|
||||
<keep-alive>
|
||||
<TrainModal v-if="store.chosenModalTrainId" />
|
||||
<TrainModal />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
|
||||
@@ -34,11 +34,12 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, watch } from 'vue';
|
||||
import { defineComponent } from 'vue';
|
||||
import axios from 'axios';
|
||||
import { version } from '.././package.json';
|
||||
|
||||
import { useMainStore } from './store/mainStore';
|
||||
import popupMixin from './mixins/popupMixin';
|
||||
|
||||
import Clock from './components/App/Clock.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 { useApiStore } from './store/apiStore';
|
||||
import { Status } from './typings/common';
|
||||
import { usePopupStore } from './store/popupStore';
|
||||
|
||||
const STORAGE_VERSION_KEY = 'app_version';
|
||||
|
||||
@@ -61,11 +61,12 @@ export default defineComponent({
|
||||
PopUp
|
||||
},
|
||||
|
||||
mixins: [popupMixin],
|
||||
|
||||
data: () => ({
|
||||
VERSION: version,
|
||||
store: useMainStore(),
|
||||
apiStore: useApiStore(),
|
||||
popupStore: usePopupStore(),
|
||||
|
||||
currentLang: 'pl',
|
||||
releaseURL: '',
|
||||
@@ -83,35 +84,7 @@ export default defineComponent({
|
||||
this.apiStore.fetchActiveData();
|
||||
});
|
||||
|
||||
// popup handling
|
||||
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');
|
||||
}
|
||||
);
|
||||
window.addEventListener('mousemove', (e: MouseEvent) => this.handlePopUpEvents(e));
|
||||
},
|
||||
|
||||
methods: {
|
||||
@@ -164,6 +137,27 @@ export default defineComponent({
|
||||
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) {
|
||||
this.$i18n.locale = lang;
|
||||
this.currentLang = lang;
|
||||
@@ -243,7 +237,7 @@ export default defineComponent({
|
||||
grid-template-columns: 100%;
|
||||
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.app_main {
|
||||
|
||||
@@ -1,15 +1,36 @@
|
||||
<template>
|
||||
<AnimatedModal :is-open="mainStore.isNewUpdate" @toggle-modal="toggleModal">
|
||||
<div class="modal_content">
|
||||
<h1 class="header">Aktualizacja Stacjownika</h1>
|
||||
<h2>wersja {{ version }}</h2>
|
||||
<div>
|
||||
<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>
|
||||
<p>
|
||||
<ul>
|
||||
<li>test</li>
|
||||
</ul>
|
||||
</p>
|
||||
<div class="features-list">
|
||||
<h2>Nowości i zmiany:</h2>
|
||||
<ul>
|
||||
<li v-for="content in localeChangesArray" :key="content">{{ content }}</li>
|
||||
</ul>
|
||||
</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>
|
||||
</AnimatedModal>
|
||||
</template>
|
||||
@@ -30,6 +51,12 @@ export default defineComponent({
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
localeChangesArray() {
|
||||
return this.$t('update.content').split('\n');
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggleModal(value: boolean) {
|
||||
this.$emit('toggleModal', value);
|
||||
@@ -40,9 +67,67 @@ export default defineComponent({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.modal_content {
|
||||
font-size: 1.2em;
|
||||
text-align: center;
|
||||
padding: 1em;
|
||||
height: 80vh;
|
||||
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>
|
||||
|
||||
@@ -34,15 +34,6 @@
|
||||
<strong v-else>
|
||||
{{ timetable.driverName }}
|
||||
</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 class="general-time">
|
||||
@@ -70,6 +61,15 @@
|
||||
: `${$t('journal.timetable-abandoned')} ${localeTime(timetable.endDate, $i18n.locale)}`
|
||||
}}
|
||||
</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>
|
||||
</div>
|
||||
</template>
|
||||
@@ -144,12 +144,14 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
.general-train {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.25em;
|
||||
|
||||
cursor: pointer;
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
.btn-timetable {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
v-for="{ timetable, showExtraInfo } in computedTimetableHistory"
|
||||
class="journal_item"
|
||||
:key="timetable.id"
|
||||
@click.stop.prevent="showExtraInfo.value = !showExtraInfo.value"
|
||||
@click="showExtraInfo.value = !showExtraInfo.value"
|
||||
>
|
||||
<div class="journal_item-info">
|
||||
<!-- General -->
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
<template>
|
||||
<div class="popup-content">
|
||||
<img src="/images/icon-diamond.svg" alt="" />
|
||||
<span>{{ popupStore.currentPopupContent }}</span>
|
||||
<span>{{ store.popUpData.content }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { usePopupStore } from '../../store/popupStore';
|
||||
import { useMainStore } from '../../store/mainStore';
|
||||
|
||||
export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
popupStore: usePopupStore()
|
||||
store: useMainStore()
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="popup" v-show="popupStore.currentPopupComponent" ref="preview">
|
||||
<component v-if="popupStore.currentPopupComponent" :is="popupStore.currentPopupComponent" />
|
||||
<div class="popup" v-show="store.popUpData.key" ref="preview">
|
||||
<component v-if="store.popUpData.key" :is="store.popUpData.key" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -9,35 +9,51 @@ import { defineComponent } from 'vue';
|
||||
import DonatorPopUp from './DonatorPopUp.vue';
|
||||
import TrainCommentsPopUp from './TrainCommentsPopUp.vue';
|
||||
import VehiclePreviewPopUp from './VehiclePreviewPopUp.vue';
|
||||
import { usePopupStore } from '../../store/popupStore';
|
||||
import { useMainStore } from '../../store/mainStore';
|
||||
|
||||
export default defineComponent({
|
||||
components: { DonatorPopUp, TrainCommentsPopUp, VehiclePreviewPopUp },
|
||||
|
||||
data() {
|
||||
return {
|
||||
popupStore: usePopupStore()
|
||||
store: useMainStore()
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
'popupStore.popupPosition': {
|
||||
'store.mousePos': {
|
||||
deep: true,
|
||||
handler(val: typeof this.popupStore.popupPosition) {
|
||||
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)';
|
||||
|
||||
handler(val: typeof this.store.mousePos) {
|
||||
this.$nextTick(() => {
|
||||
const isOutside =
|
||||
val.y + previewEl.getBoundingClientRect().height > window.innerHeight + window.scrollY;
|
||||
const previewEl = this.$refs['preview'] as HTMLElement;
|
||||
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'}))`;
|
||||
previewEl.style.transform = `translate(-${~~((val.x / window.innerWidth) * 100)}%, calc(${
|
||||
isOutside ? '-100% - 1.5rem' : '1.5rem'
|
||||
}))`;
|
||||
let translateX = '0px',
|
||||
translateY = '30px';
|
||||
|
||||
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})`;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<template>
|
||||
<div class="popup-content">
|
||||
<span>{{ popupStore.currentPopupContent }}</span>
|
||||
<span>{{ store.popUpData.content }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { usePopupStore } from '../../store/popupStore';
|
||||
import { useMainStore } from '../../store/mainStore';
|
||||
|
||||
export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
popupStore: usePopupStore()
|
||||
store: useMainStore()
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -7,30 +7,29 @@
|
||||
<div v-if="imageState == 'error'">{{ $t('vehicle-preview.error') }}</div>
|
||||
|
||||
<img
|
||||
v-if="popupStore.currentPopupContent"
|
||||
v-if="store.popUpData.key"
|
||||
@load="onImageLoad"
|
||||
@error="onImageError"
|
||||
@click="popupStore.onPopUpHide"
|
||||
width="300"
|
||||
height="176"
|
||||
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'">
|
||||
{{ popupStore.currentPopupContent.replace(/_/g, ' ') }}
|
||||
{{ store.popUpData.content.replace(/_/g, ' ') }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { usePopupStore } from '../../store/popupStore';
|
||||
import { useMainStore } from '../../store/mainStore';
|
||||
|
||||
export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
popupStore: usePopupStore(),
|
||||
store: useMainStore(),
|
||||
imageState: 'loading'
|
||||
};
|
||||
},
|
||||
|
||||
@@ -312,7 +312,7 @@ import { HeadIdsTypes, headIconsIds, headIds } from '../../scripts/data/stationH
|
||||
import StationStatusBadge from '../Global/StationStatusBadge.vue';
|
||||
import { Status } from '../../typings/common';
|
||||
import { useApiStore } from '../../store/apiStore';
|
||||
import { usePopupStore } from '../../store/popupStore';
|
||||
import popupMixin from '../../mixins/popupMixin';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
@@ -324,7 +324,7 @@ export default defineComponent({
|
||||
|
||||
emits: ['toggleDonationModal'],
|
||||
components: { Loading, StationStatusBadge },
|
||||
mixins: [styleMixin, dateMixin, stationInfoMixin],
|
||||
mixins: [styleMixin, dateMixin, stationInfoMixin, popupMixin],
|
||||
|
||||
data: () => ({
|
||||
headIconsIds,
|
||||
@@ -341,7 +341,6 @@ export default defineComponent({
|
||||
setup() {
|
||||
const mainStore = useMainStore();
|
||||
const apiStore = useApiStore();
|
||||
const popupStore = usePopupStore();
|
||||
|
||||
const stationFiltersStore = useStationFiltersStore();
|
||||
|
||||
@@ -349,8 +348,7 @@ export default defineComponent({
|
||||
Status: Status.Data,
|
||||
stationFiltersStore,
|
||||
mainStore,
|
||||
apiStore,
|
||||
popupStore
|
||||
apiStore
|
||||
};
|
||||
},
|
||||
|
||||
@@ -373,7 +371,7 @@ export default defineComponent({
|
||||
openDonationModal(e: Event) {
|
||||
this.$emit('toggleDonationModal', true);
|
||||
this.mainStore.modalLastClickedTarget = e.target;
|
||||
this.popupStore.currentPopupComponent = null;
|
||||
this.hidePopUp();
|
||||
},
|
||||
|
||||
openForumSite(e: Event, url: string | undefined) {
|
||||
|
||||
@@ -1,57 +1,71 @@
|
||||
<template>
|
||||
<div class="train-info">
|
||||
<div class="train-info" :data-extended="extended">
|
||||
<section class="train-general">
|
||||
<div class="general-info">
|
||||
<b class="warning-timeout" v-if="train.isTimeout" :title="$t('trains.timeout')">?</b>
|
||||
<span class="timetable-id" v-if="train.timetableData">
|
||||
#{{ train.timetableData.timetableId }}
|
||||
</span>
|
||||
|
||||
<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
|
||||
<div class="general-top-bar">
|
||||
<div>
|
||||
<b class="warning-timeout" v-if="train.isTimeout" :title="$t('trains.timeout')">?</b>
|
||||
<span class="timetable-id" v-if="train.timetableData">
|
||||
#{{ train.timetableData.timetableId }}
|
||||
</span>
|
||||
<span class="train-badge skr" v-if="train.timetableData?.SKR" :title="$t('general.SKR')">
|
||||
SKR
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<strong>
|
||||
<span v-if="train.timetableData" class="text--primary"
|
||||
>{{ train.timetableData.category }} </span
|
||||
<span
|
||||
class="timetable-warnings"
|
||||
v-if="train.timetableData?.TWR || train.timetableData?.SKR"
|
||||
>
|
||||
<span class="train-number">{{ train.trainNo }}</span>
|
||||
</strong>
|
||||
<span>•</span>
|
||||
<b
|
||||
class="level-badge driver"
|
||||
:style="calculateExpStyle(train.driverLevel, train.isSupporter)"
|
||||
>
|
||||
{{ train.driverLevel < 2 ? 'L' : `${train.driverLevel}` }}
|
||||
</b>
|
||||
<span
|
||||
class="train-badge twr"
|
||||
v-if="train.timetableData?.TWR"
|
||||
:title="$t('general.TWR')"
|
||||
>
|
||||
TWR
|
||||
</span>
|
||||
<span
|
||||
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 }} </span
|
||||
>
|
||||
<span class="train-number">{{ train.trainNo }}</span>
|
||||
</strong>
|
||||
<span>•</span>
|
||||
<b
|
||||
v-if="apiStore.donatorsData.includes(train.driverName)"
|
||||
data-popup-key="DonatorPopUp"
|
||||
:data-popup-content="$t('donations.driver-message')"
|
||||
class="level-badge driver"
|
||||
:style="calculateExpStyle(train.driverLevel, train.isSupporter)"
|
||||
>
|
||||
{{ train.driverName }}
|
||||
<img src="/images/icon-diamond.svg" alt="donator diamond icon" />
|
||||
{{ train.driverLevel < 2 ? 'L' : `${train.driverLevel}` }}
|
||||
</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
|
||||
class="btn--image btn--action btn-timetable"
|
||||
@click="navigateToJournal"
|
||||
v-if="extended"
|
||||
>
|
||||
<img src="/images/icon-train.svg" alt="" />
|
||||
{{ $t('trains.journal-button') }}
|
||||
<span v-else>{{ train.driverName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="extended">
|
||||
<button class="btn-timetable btn--image btn--action" @click="navigateToJournal">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -79,7 +93,7 @@
|
||||
</div>
|
||||
|
||||
<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)" />
|
||||
|
||||
<span class="progress-distance">
|
||||
@@ -103,12 +117,29 @@
|
||||
</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) }}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="train-stats">
|
||||
<section class="train-stats" v-if="!extended">
|
||||
<StockList :trainStockList="train.stockList" :tractionOnly="true" />
|
||||
|
||||
<div>
|
||||
@@ -137,7 +168,6 @@ import ProgressBar from '../Global/ProgressBar.vue';
|
||||
import { useMainStore } from '../../store/mainStore';
|
||||
import { useApiStore } from '../../store/apiStore';
|
||||
import StockList from '../Global/StockList.vue';
|
||||
import { usePopupStore } from '../../store/popupStore';
|
||||
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
||||
|
||||
export default defineComponent({
|
||||
@@ -157,8 +187,7 @@ export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
store: useMainStore(),
|
||||
apiStore: useApiStore(),
|
||||
popupStore: usePopupStore()
|
||||
apiStore: useApiStore()
|
||||
};
|
||||
},
|
||||
|
||||
@@ -203,6 +232,10 @@ export default defineComponent({
|
||||
grid-template-columns: 2fr 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
|
||||
&[data-extended='true'] {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
padding: 1em;
|
||||
|
||||
background-color: #1a1a1a;
|
||||
@@ -214,12 +247,6 @@ export default defineComponent({
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
|
||||
.btn-timetable {
|
||||
display: inline-block;
|
||||
padding: 0.25em;
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
.timetable-id {
|
||||
color: #d2d2d2;
|
||||
}
|
||||
@@ -243,14 +270,29 @@ export default defineComponent({
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.general-info {
|
||||
.general-top-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5em;
|
||||
|
||||
gap: 0.25em;
|
||||
margin-right: 1.5em;
|
||||
& > div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
|
||||
gap: 0.25em;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-timetable {
|
||||
padding: 0.25em;
|
||||
}
|
||||
|
||||
.btn-exit {
|
||||
padding: 0.25em;
|
||||
}
|
||||
|
||||
.general-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -259,6 +301,27 @@ export default defineComponent({
|
||||
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 {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -270,17 +333,7 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
.general-timetable {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.timetable-warnings {
|
||||
display: flex;
|
||||
gap: 0.25em;
|
||||
}
|
||||
|
||||
.timetable-progress {
|
||||
.status-timetable-progress {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
@@ -290,30 +343,19 @@ export default defineComponent({
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
|
||||
.timetable-warnings {
|
||||
display: flex;
|
||||
gap: 0.25em;
|
||||
}
|
||||
|
||||
@include smallScreen() {
|
||||
.train-info {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1em 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.general-info,
|
||||
.general-status,
|
||||
.general-timetable {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.timetable-progress {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.comments {
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
img {
|
||||
margin: 0 0 0.5em 0;
|
||||
}
|
||||
.btn-timetable > span {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
<div class="train-modal" v-if="chosenTrain" @keydown.esc="closeModal">
|
||||
<div class="modal_background" @click="closeModal"></div>
|
||||
<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" />
|
||||
<TrainSchedule :train="chosenTrain" tabindex="0" />
|
||||
</div>
|
||||
@@ -17,17 +13,27 @@ import { defineComponent } from 'vue';
|
||||
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
||||
import TrainInfo from './TrainInfo.vue';
|
||||
import TrainSchedule from './TrainSchedule.vue';
|
||||
import Train from '../../scripts/interfaces/Train';
|
||||
|
||||
export default defineComponent({
|
||||
components: { TrainInfo, TrainSchedule },
|
||||
mixins: [modalTrainMixin],
|
||||
|
||||
activated() {
|
||||
const contentEl = this.$refs['content'] as HTMLElement;
|
||||
computed: {
|
||||
chosenTrain() {
|
||||
return this.store.trainList.find((train) => train.trainId == this.store.chosenModalTrainId);
|
||||
}
|
||||
},
|
||||
|
||||
this.$nextTick(() => {
|
||||
contentEl.focus();
|
||||
});
|
||||
watch: {
|
||||
chosenTrain(train: Train | undefined) {
|
||||
this.$nextTick(() => {
|
||||
if (train) {
|
||||
const contentEl = this.$refs['content'] as HTMLElement;
|
||||
contentEl.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
</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 {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
|
||||
+8
-7
@@ -26,6 +26,13 @@
|
||||
"TWR": "High risk freight train",
|
||||
"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": {
|
||||
"sceneries": "SCENERIES",
|
||||
"trains": "TRAINS",
|
||||
@@ -41,13 +48,7 @@
|
||||
"footer": {
|
||||
"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": {
|
||||
"loading": "Loading preview...",
|
||||
"error": "Oops! The vehicle preview seems to be missing! :/"
|
||||
|
||||
+8
-1
@@ -4,7 +4,7 @@
|
||||
"header": "Grosza daj Stacjownikowi!",
|
||||
"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!",
|
||||
"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-b2": "Dziennik",
|
||||
"p2-b3": "Stacjobot",
|
||||
@@ -26,6 +26,13 @@
|
||||
"TWR": "Towar niebezpieczny wysokiego ryzyka",
|
||||
"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": {
|
||||
"sceneries": "SCENERIE",
|
||||
"trains": "POCIĄGI",
|
||||
|
||||
@@ -1,21 +1,13 @@
|
||||
import { defineComponent } from 'vue';
|
||||
import { useMainStore } from '../store/mainStore';
|
||||
import { usePopupStore } from '../store/popupStore';
|
||||
|
||||
export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
store: useMainStore(),
|
||||
popupStore: usePopupStore()
|
||||
store: useMainStore()
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
chosenTrain() {
|
||||
return this.store.trainList.find((train) => train.trainId == this.store.chosenModalTrainId);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
selectModalTrain(trainId: string, target?: EventTarget | null) {
|
||||
this.store.chosenModalTrainId = trainId;
|
||||
@@ -25,7 +17,7 @@ export default defineComponent({
|
||||
|
||||
closeModal() {
|
||||
this.store.chosenModalTrainId = undefined;
|
||||
this.popupStore.currentPopupComponent = null;
|
||||
this.store.popUpData.key = null;
|
||||
|
||||
setTimeout(() => {
|
||||
(this.store.modalLastClickedTarget as any)?.focus();
|
||||
|
||||
@@ -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'] = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -4,9 +4,6 @@ import { Status } from '../typings/common';
|
||||
import { StationJSONData } from './typings';
|
||||
import axios, { AxiosInstance } from 'axios';
|
||||
|
||||
// Update seconds cron for active data scheduler
|
||||
const UPDATE_SECONDS = [3, 23, 43];
|
||||
|
||||
export enum APIMode {
|
||||
PRODUCTION = 0,
|
||||
DEV = 1,
|
||||
|
||||
@@ -26,8 +26,10 @@ export const useMainStore = defineStore('store', {
|
||||
|
||||
chosenModalTrainId: undefined,
|
||||
|
||||
blockScroll: false,
|
||||
modalLastClickedTarget: null
|
||||
modalLastClickedTarget: null,
|
||||
|
||||
mousePos: { x: 0, y: 0 },
|
||||
popUpData: { key: null, content: '' }
|
||||
}) as StoreState,
|
||||
|
||||
getters: {
|
||||
@@ -45,7 +47,8 @@ export const useMainStore = defineStore('store', {
|
||||
const sceneryNames =
|
||||
train.timetable?.sceneries?.map(
|
||||
(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 ??
|
||||
sceneryHash
|
||||
) ?? [];
|
||||
|
||||
@@ -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 = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1,7 +1,9 @@
|
||||
import { API } from '../typings/api';
|
||||
import { Status } from '../typings/common';
|
||||
|
||||
export const popupKeys = ['DonatorPopUp', 'TrainCommentsPopUp', 'VehiclePreviewPopUp'] as const;
|
||||
export type Availability = 'default' | 'unavailable' | 'nonPublic' | 'abandoned' | 'nonDefault';
|
||||
export type PopUpType = (typeof popupKeys)[number];
|
||||
|
||||
export interface RegionCounters {
|
||||
stationCount: number;
|
||||
@@ -19,8 +21,9 @@ export interface StoreState {
|
||||
driverStatsData?: API.DriverStats.Response;
|
||||
driverStatsStatus: Status.Data;
|
||||
chosenModalTrainId?: string;
|
||||
blockScroll: boolean;
|
||||
modalLastClickedTarget: EventTarget | null;
|
||||
mousePos: { x: number; y: number };
|
||||
popUpData: { key: PopUpType | null; content: string };
|
||||
}
|
||||
|
||||
export interface StationRoutesInfo {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
height: 90vh;
|
||||
min-height: 550px;
|
||||
margin-top: 0.5em;
|
||||
position: relative;
|
||||
|
||||
padding-right: 0.2em;
|
||||
}
|
||||
|
||||
@@ -55,6 +55,8 @@ body {
|
||||
-webkit-font-smoothing: antialiased !important;
|
||||
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
position: relative;
|
||||
|
||||
&.no-scroll {
|
||||
overflow-y: hidden;
|
||||
|
||||
Reference in New Issue
Block a user