mirror of
https://github.com/Spythere/stacjownik.git
synced 2026-05-03 13:28:11 +00:00
Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| de5c57181a | |||
| d91d4cc6a8 | |||
| 9a5fd4d670 | |||
| 4202a55673 | |||
| 5181e8f4af | |||
| e117f62fcb | |||
| e0036bf969 | |||
| 1f457d6389 | |||
| eb5b94c9f6 | |||
| 328e8c0573 | |||
| 9f58ae5428 | |||
| ebd0eeb8c4 | |||
| fa656c2f26 | |||
| 0cc3a12d1d | |||
| 392a6437f8 | |||
| 122532f0ed | |||
| 366ff91f60 | |||
| a0496736dd | |||
| f974120e87 | |||
| abd8b8178b | |||
| f1fcde8459 | |||
| b3289d6aab | |||
| 6481a4a3b0 | |||
| 05dc268526 | |||
| 669acc98d2 | |||
| 3371b661c2 | |||
| 871b2c0221 | |||
| d366a877a4 | |||
| 405aab96bd | |||
| f29c160000 | |||
| a2de0e2030 | |||
| 7dd1c06f3f | |||
| ff041b9aaf | |||
| 4782dba444 | |||
| d6b8d032d6 | |||
| c16616330c | |||
| 57cec8bfe7 | |||
| 6bea340e19 | |||
| c181cf7e64 | |||
| 8e4ae64cd3 | |||
| 5750490f01 | |||
| 3ef27e1d69 | |||
| f53993c717 | |||
| 235c16e30f | |||
| c3533f07ad | |||
| d05579c5ee | |||
| c8f53c2f06 | |||
| b44f88ebcd | |||
| 7805d1350c | |||
| b17bd19433 | |||
| c12a6cbacd | |||
| ba650238db | |||
| d5ec9919e2 |
@@ -0,0 +1,17 @@
|
|||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
github-releases-to-discord:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Github Releases To Discord
|
||||||
|
uses: SethCohen/github-releases-to-discord@v1.13.1
|
||||||
|
with:
|
||||||
|
webhook_url: ${{ secrets.WEBHOOK_URL }}
|
||||||
|
color: "15844367"
|
||||||
|
footer_title: "Changelog - Stacjownik"
|
||||||
|
footer_timestamp: true
|
||||||
Generated
+2828
-9512
File diff suppressed because it is too large
Load Diff
+5
-6
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "stacjownik",
|
"name": "stacjownik",
|
||||||
"version": "1.23.0",
|
"version": "1.24.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
@@ -14,11 +14,9 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"core-js": "^3.32.2",
|
"core-js": "^3.32.2",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"firebase": "^10.4.0",
|
|
||||||
"howler": "^2.2.4",
|
|
||||||
"pinia": "^2.1.6",
|
"pinia": "^2.1.6",
|
||||||
"sass": "^1.67.0",
|
"sass": "^1.67.0",
|
||||||
"socket.io-client": "^4.7.4",
|
"showdown": "^2.1.0",
|
||||||
"vue": "^3.3.4",
|
"vue": "^3.3.4",
|
||||||
"vue-i18n": "^9.4.1",
|
"vue-i18n": "^9.4.1",
|
||||||
"vue-router": "^4.2.4"
|
"vue-router": "^4.2.4"
|
||||||
@@ -26,7 +24,8 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rushstack/eslint-patch": "^1.3.3",
|
"@rushstack/eslint-patch": "^1.3.3",
|
||||||
"@types/node": "^20.6.2",
|
"@types/node": "^20.6.2",
|
||||||
"@vite-pwa/assets-generator": "^0.0.10",
|
"@types/showdown": "^2.0.6",
|
||||||
|
"@vite-pwa/assets-generator": "^0.2.4",
|
||||||
"@vitejs/plugin-vue": "^4.3.4",
|
"@vitejs/plugin-vue": "^4.3.4",
|
||||||
"@vue/eslint-config-prettier": "^8.0.0",
|
"@vue/eslint-config-prettier": "^8.0.0",
|
||||||
"@vue/eslint-config-typescript": "^12.0.0",
|
"@vue/eslint-config-typescript": "^12.0.0",
|
||||||
@@ -37,7 +36,7 @@
|
|||||||
"prettier": "^3.0.3",
|
"prettier": "^3.0.3",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
"vite": "^4.4.9",
|
"vite": "^4.4.9",
|
||||||
"vite-plugin-pwa": "^0.16.5",
|
"vite-plugin-pwa": "^0.20.0",
|
||||||
"vue-tsc": "^1.8.11"
|
"vue-tsc": "^1.8.11"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
|
|||||||
+62
-75
@@ -1,10 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="app_container" v-cloak>
|
<div class="app_container">
|
||||||
<PopUp />
|
<UpdateModal
|
||||||
|
:update-modal-open="isUpdateModalOpen"
|
||||||
|
@toggle-modal="() => (isUpdateModalOpen = false)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Tooltip />
|
||||||
|
|
||||||
<transition name="modal-anim">
|
<transition name="modal-anim">
|
||||||
<keep-alive>
|
<keep-alive>
|
||||||
<TrainModal v-if="store.chosenModalTrainId" />
|
<TrainModal />
|
||||||
</keep-alive>
|
</keep-alive>
|
||||||
</transition>
|
</transition>
|
||||||
|
|
||||||
@@ -22,7 +27,10 @@
|
|||||||
©
|
©
|
||||||
<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() }} |
|
||||||
<a :href="releaseURL" target="_blank">v{{ VERSION }}{{ isOnProductionHost ? '' : 'dev' }}</a>
|
<button class="btn--text" @click="() => (isUpdateModalOpen = true)">
|
||||||
|
v{{ VERSION }}{{ isOnProductionHost ? '' : 'dev' }}
|
||||||
|
</button>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
<a href="https://discord.gg/x2mpNN3svk">
|
<a href="https://discord.gg/x2mpNN3svk">
|
||||||
<img src="/images/icon-discord.png" alt="" /> <b>{{ $t('footer.discord') }}</b>
|
<img src="/images/icon-discord.png" alt="" /> <b>{{ $t('footer.discord') }}</b>
|
||||||
@@ -34,21 +42,23 @@
|
|||||||
</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 { Status } from './typings/common';
|
||||||
import { useMainStore } from './store/mainStore';
|
import { useMainStore } from './store/mainStore';
|
||||||
|
import { useApiStore } from './store/apiStore';
|
||||||
|
import { useTooltipStore } from './store/tooltipStore';
|
||||||
|
|
||||||
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';
|
||||||
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 UpdateModal from './components/App/UpdateModal.vue';
|
||||||
|
|
||||||
import StorageManager from './managers/storageManager';
|
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';
|
const STORAGE_VERSION_KEY = 'app_version';
|
||||||
|
|
||||||
@@ -58,18 +68,22 @@ export default defineComponent({
|
|||||||
StatusIndicator,
|
StatusIndicator,
|
||||||
AppHeader,
|
AppHeader,
|
||||||
TrainModal,
|
TrainModal,
|
||||||
PopUp
|
UpdateModal,
|
||||||
|
Tooltip
|
||||||
},
|
},
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
VERSION: version,
|
VERSION: version,
|
||||||
store: useMainStore(),
|
store: useMainStore(),
|
||||||
apiStore: useApiStore(),
|
apiStore: useApiStore(),
|
||||||
popupStore: usePopupStore(),
|
tooltipStore: useTooltipStore(),
|
||||||
|
|
||||||
|
isUpdateModalOpen: false,
|
||||||
|
|
||||||
currentLang: 'pl',
|
currentLang: 'pl',
|
||||||
releaseURL: '',
|
isOnProductionHost: location.hostname == 'stacjownik-td2.web.app',
|
||||||
isOnProductionHost: location.hostname == 'stacjownik-td2.web.app'
|
|
||||||
|
nextUpdateTime: 0
|
||||||
}),
|
}),
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
@@ -77,69 +91,52 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
async mounted() {
|
async mounted() {
|
||||||
window.addEventListener('focus', () => {
|
window.addEventListener('mousemove', (e: MouseEvent) => this.tooltipStore.handle(e));
|
||||||
if (Date.now() - this.apiStore.lastFetchData.getTime() < 15000) return;
|
|
||||||
|
|
||||||
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');
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
init() {
|
init() {
|
||||||
this.loadLang();
|
this.loadLang();
|
||||||
this.setReleaseURL();
|
|
||||||
this.setupOfflineHandling();
|
this.setupOfflineHandling();
|
||||||
this.checkAppVersion();
|
this.checkAppVersion();
|
||||||
|
|
||||||
this.apiStore.setupAPIData();
|
this.apiStore.setupAPIData();
|
||||||
|
window.requestAnimationFrame(this.update);
|
||||||
|
|
||||||
if (!this.isOnProductionHost) document.title = 'Stacjownik Dev';
|
if (!this.isOnProductionHost) document.title = 'Stacjownik Dev';
|
||||||
},
|
},
|
||||||
|
|
||||||
checkAppVersion() {
|
update(t: number) {
|
||||||
if (import.meta.env.DEV) {
|
if (t >= this.nextUpdateTime) {
|
||||||
this.store.isNewUpdate = true;
|
this.apiStore.fetchActiveData();
|
||||||
|
this.nextUpdateTime = t + 20000;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
window.requestAnimationFrame(this.update);
|
||||||
|
},
|
||||||
|
|
||||||
|
async checkAppVersion() {
|
||||||
const storageVersion = StorageManager.getStringValue(STORAGE_VERSION_KEY);
|
const storageVersion = StorageManager.getStringValue(STORAGE_VERSION_KEY);
|
||||||
|
|
||||||
if (storageVersion === undefined || storageVersion != version) {
|
try {
|
||||||
this.store.isNewUpdate = true;
|
const releaseData = await (
|
||||||
|
await axios.get('https://api.github.com/repos/Spythere/stacjownik/releases/latest')
|
||||||
|
).data;
|
||||||
|
|
||||||
|
if (!releaseData) return;
|
||||||
|
|
||||||
|
this.store.appUpdate = {
|
||||||
|
version,
|
||||||
|
changelog: releaseData.body,
|
||||||
|
releaseURL: releaseData.html_url
|
||||||
|
};
|
||||||
|
|
||||||
|
this.isUpdateModalOpen =
|
||||||
|
storageVersion != version || import.meta.env.VITE_UPDATE_TEST === 'test';
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Wystąpił błąd podczas pobierania danych z API GitHuba: ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
StorageManager.setStringValue(STORAGE_VERSION_KEY, version);
|
StorageManager.setStringValue(STORAGE_VERSION_KEY, version);
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setupOfflineHandling() {
|
setupOfflineHandling() {
|
||||||
@@ -171,21 +168,6 @@ export default defineComponent({
|
|||||||
StorageManager.setStringValue('lang', lang);
|
StorageManager.setStringValue('lang', lang);
|
||||||
},
|
},
|
||||||
|
|
||||||
async setReleaseURL() {
|
|
||||||
try {
|
|
||||||
const releaseData = await (
|
|
||||||
await axios.get('https://api.github.com/repos/Spythere/stacjownik/releases/latest')
|
|
||||||
).data;
|
|
||||||
|
|
||||||
if (!releaseData) return;
|
|
||||||
|
|
||||||
this.releaseURL = releaseData.html_url;
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Wystąpił błąd podczas pobierania danych z API GitHuba: ${error}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
loadLang() {
|
loadLang() {
|
||||||
const storageLang = StorageManager.getStringValue('lang');
|
const storageLang = StorageManager.getStringValue('lang');
|
||||||
|
|
||||||
@@ -243,7 +225,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 {
|
||||||
@@ -261,10 +243,15 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FOOTER
|
// FOOTER
|
||||||
footer.app_footer {
|
.app_footer {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
|
|
||||||
|
button {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.1em;
|
||||||
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 1.1em;
|
width: 1.1em;
|
||||||
vertical-align: text-bottom;
|
vertical-align: text-bottom;
|
||||||
|
|||||||
@@ -29,11 +29,6 @@
|
|||||||
<img src="/images/icon-dispatcher.svg" alt="icon dispatcher" />
|
<img src="/images/icon-dispatcher.svg" alt="icon dispatcher" />
|
||||||
<span class="text--primary">{{ onlineDispatchersCount }}</span>
|
<span class="text--primary">{{ onlineDispatchersCount }}</span>
|
||||||
|
|
||||||
<!-- <span class="g-tooltip">
|
|
||||||
<b class="text--primary">{{ factorU }}U</b>
|
|
||||||
<div class="content">Test</div>
|
|
||||||
</span> -->
|
|
||||||
|
|
||||||
<span class="text--grayed"> / </span>
|
<span class="text--grayed"> / </span>
|
||||||
<span class="text--primary">{{ onlineTrainsCount }}</span>
|
<span class="text--primary">{{ onlineTrainsCount }}</span>
|
||||||
<img src="/images/icon-train.svg" alt="icon train" />
|
<img src="/images/icon-train.svg" alt="icon train" />
|
||||||
@@ -103,12 +98,6 @@ export default defineComponent({
|
|||||||
return this.store.activeSceneryList.filter(
|
return this.store.activeSceneryList.filter(
|
||||||
(scenery) => scenery.region == this.store.region.id && scenery.dispatcherId != -1
|
(scenery) => scenery.region == this.store.region.id && scenery.dispatcherId != -1
|
||||||
).length;
|
).length;
|
||||||
},
|
|
||||||
|
|
||||||
factorU() {
|
|
||||||
return this.onlineDispatchersCount == 0
|
|
||||||
? '-'
|
|
||||||
: (this.onlineTrainsCount / this.onlineDispatchersCount).toFixed(2);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: { StatusIndicator, Clock, RegionDropdown }
|
components: { StatusIndicator, Clock, RegionDropdown }
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<AnimatedModal :is-open="mainStore.isNewUpdate" @toggle-modal="toggleModal">
|
<AnimatedModal :is-open="updateModalOpen" @toggle-modal="toggleModal(false)">
|
||||||
<div class="modal_content">
|
<div class="modal-content">
|
||||||
<h1 class="header">Aktualizacja Stacjownika</h1>
|
<h1 style="margin-bottom: 0.5em">🚀 {{ $t('update.title') }}</h1>
|
||||||
<h2>wersja {{ version }}</h2>
|
|
||||||
|
|
||||||
<b>Co nowego?</b>
|
<div class="features-body" v-if="htmlChangelog != ''" v-html="htmlChangelog"></div>
|
||||||
<p>
|
<div class="no-features" v-else>{{ $t('update.no-data') }}</div>
|
||||||
<ul>
|
|
||||||
<li>test</li>
|
<button class="btn btn--action" ref="confirm-btn" @click="toggleModal(false)">
|
||||||
</ul>
|
{{ $t('update.confirm') }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<p class="bottom-info">
|
||||||
|
{{ $t('update.info-1') }}
|
||||||
|
<br />
|
||||||
|
<span v-html="$t('update.info-2')"></span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</AnimatedModal>
|
</AnimatedModal>
|
||||||
@@ -18,11 +23,24 @@
|
|||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { useMainStore } from '../../store/mainStore';
|
import { useMainStore } from '../../store/mainStore';
|
||||||
import { version } from '../../../package.json';
|
import { version } from '../../../package.json';
|
||||||
|
import { Converter } from 'showdown';
|
||||||
|
|
||||||
import AnimatedModal from '../Global/AnimatedModal.vue';
|
import AnimatedModal from '../Global/AnimatedModal.vue';
|
||||||
|
|
||||||
|
const converter = new Converter();
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { AnimatedModal },
|
components: { AnimatedModal },
|
||||||
|
|
||||||
|
props: {
|
||||||
|
updateModalOpen: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
emits: ['toggleModal'],
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
mainStore: useMainStore(),
|
mainStore: useMainStore(),
|
||||||
@@ -30,6 +48,25 @@ export default defineComponent({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
updateModalOpen(val: boolean) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (val) (this.$refs['confirm-btn'] as HTMLElement).focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
htmlChangelog() {
|
||||||
|
if (this.mainStore.appUpdate == null) return '';
|
||||||
|
|
||||||
|
const x = converter.makeHtml(this.mainStore.appUpdate.changelog);
|
||||||
|
console.log(x);
|
||||||
|
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
toggleModal(value: boolean) {
|
toggleModal(value: boolean) {
|
||||||
this.$emit('toggleModal', value);
|
this.$emit('toggleModal', value);
|
||||||
@@ -39,10 +76,46 @@ export default defineComponent({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.modal_content {
|
::v-deep(h1) {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep(h2) {
|
||||||
|
padding: 0.25em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep(ul) {
|
||||||
|
list-style: inside;
|
||||||
|
padding: 0.5em;
|
||||||
|
line-height: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto 1fr auto;
|
||||||
|
gap: 0.5em;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
height: 80vh;
|
min-height: 700px;
|
||||||
min-height: 550px;
|
overflow: auto;
|
||||||
|
text-align: justify;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-features {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0.5em 0.75em;
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.bottom-info {
|
||||||
|
text-align: center;
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<transition name="modal-anim" tag="div" class="modal">
|
<transition name="modal-anim" tag="div">
|
||||||
<div class="body" v-if="isOpen">
|
<div class="modal" v-if="isOpen">
|
||||||
<div class="background" @click="toggleModal(false)"></div>
|
<div class="modal-background" @click="toggleModal(false)"></div>
|
||||||
<div class="wrapper" ref="wrapper" tabindex="0">
|
<div class="modal-wrapper" 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="toggleModal(false)"></div>
|
||||||
@@ -30,8 +30,7 @@ export default defineComponent({
|
|||||||
watch: {
|
watch: {
|
||||||
isOpen(v) {
|
isOpen(v) {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (v) (this.$refs['wrapper'] as HTMLElement).focus();
|
if (v == false) (this.store.modalLastClickedTarget as HTMLElement)?.focus();
|
||||||
else (this.store.modalLastClickedTarget as HTMLElement)?.focus();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -47,17 +46,17 @@ export default defineComponent({
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../styles/responsive.scss';
|
@import '../../styles/responsive.scss';
|
||||||
|
|
||||||
.body {
|
.modal {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: 200;
|
|
||||||
|
|
||||||
width: 100vw;
|
width: 100%;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
z-index: 200;
|
||||||
}
|
}
|
||||||
|
|
||||||
.background {
|
.modal-background {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@@ -69,33 +68,21 @@ export default defineComponent({
|
|||||||
background-color: rgba(0, 0, 0, 0.55);
|
background-color: rgba(0, 0, 0, 0.55);
|
||||||
}
|
}
|
||||||
|
|
||||||
.wrapper {
|
.modal-wrapper {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 210;
|
||||||
|
overflow: auto;
|
||||||
|
max-height: 95vh;
|
||||||
|
|
||||||
|
& > :slotted(div) {
|
||||||
background-color: #1a1a1a;
|
background-color: #1a1a1a;
|
||||||
box-shadow: 0 0 15px 10px #333333;
|
box-shadow: 0 0 15px 10px #0e0e0e;
|
||||||
|
|
||||||
width: 95%;
|
width: 95vw;
|
||||||
max-width: 800px;
|
max-width: 850px;
|
||||||
max-height: 95vh;
|
|
||||||
|
|
||||||
& > :slotted(div) {
|
|
||||||
max-height: 95vh;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include smallScreen {
|
|
||||||
.wrapper {
|
|
||||||
top: 0;
|
|
||||||
transform: translate(-50%, 1em);
|
|
||||||
max-height: 90vh;
|
|
||||||
|
|
||||||
& > :slotted(div) {
|
|
||||||
max-height: 90vh;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -66,6 +66,7 @@
|
|||||||
class="modal-action a-button btn--image coffee"
|
class="modal-action a-button btn--image coffee"
|
||||||
href="https://buycoffee.to/spythere"
|
href="https://buycoffee.to/spythere"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
ref="action"
|
||||||
>
|
>
|
||||||
<img src="/images/icon-coffee.png" width="20" alt="buycoffee.to donation" />
|
<img src="/images/icon-coffee.png" width="20" alt="buycoffee.to donation" />
|
||||||
{{ $t('donations.action-buycoffee') }}
|
{{ $t('donations.action-buycoffee') }}
|
||||||
@@ -103,9 +104,13 @@ export default defineComponent({
|
|||||||
emits: ['toggleModal'],
|
emits: ['toggleModal'],
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
isModalOpen(b: boolean) {
|
isModalOpen(val: boolean) {
|
||||||
this.running = b;
|
this.running = val;
|
||||||
this.lastUpdate = Date.now();
|
this.lastUpdate = Date.now();
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (val) (this.$refs['action'] as HTMLElement).focus();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -65,12 +65,12 @@ export default defineComponent({
|
|||||||
immediate: true,
|
immediate: true,
|
||||||
handler(regionQuery: string) {
|
handler(regionQuery: string) {
|
||||||
if (regionQuery) {
|
if (regionQuery) {
|
||||||
this.store.region.id =
|
this.store.region =
|
||||||
regionsJSON.find(
|
regionsJSON.find(
|
||||||
(reg) =>
|
(reg) =>
|
||||||
reg.id == regionQuery.toLocaleLowerCase() ||
|
reg.id == regionQuery.toLocaleLowerCase() ||
|
||||||
reg.value.toLocaleLowerCase() == regionQuery.toLocaleLowerCase()
|
reg.value.toLocaleLowerCase() == regionQuery.toLocaleLowerCase()
|
||||||
)?.id || 'eu';
|
) ?? regionsJSON[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -139,15 +139,10 @@ button.selected-region {
|
|||||||
color: paleturquoise;
|
color: paleturquoise;
|
||||||
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
padding: 0.1em 0.5em;
|
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
background-color: #262626;
|
background-color: #262626;
|
||||||
}
|
}
|
||||||
|
|
||||||
span {
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
@@ -197,6 +192,8 @@ li.option {
|
|||||||
}
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5em 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@@ -207,10 +204,6 @@ li.option {
|
|||||||
background-color: #333333f2;
|
background-color: #333333f2;
|
||||||
}
|
}
|
||||||
|
|
||||||
padding: 0.5em 0;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,12 @@
|
|||||||
|
|
||||||
<img
|
<img
|
||||||
class="traction-only"
|
class="traction-only"
|
||||||
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${computedStockList[0].split(':')[0]}${
|
:src="
|
||||||
|
getVehicleThumbnailURL(
|
||||||
|
computedStockList[0].split(':')[0],
|
||||||
/^EN/.test(computedStockList[0]) ? 'rb' : ''
|
/^EN/.test(computedStockList[0]) ? 'rb' : ''
|
||||||
}.png`"
|
)
|
||||||
|
"
|
||||||
@error="onImageError($event, computedStockList[0])"
|
@error="onImageError($event, computedStockList[0])"
|
||||||
width="300"
|
width="300"
|
||||||
height="60"
|
height="60"
|
||||||
@@ -27,11 +30,11 @@
|
|||||||
<span>
|
<span>
|
||||||
<img
|
<img
|
||||||
:data-mouseover="stockName"
|
:data-mouseover="stockName"
|
||||||
data-popup-key="VehiclePreviewPopUp"
|
data-tooltip-type="VehiclePreviewTooltip"
|
||||||
:data-popup-content="stockName.split(':')[0]"
|
:data-tooltip-content="stockName.split(':')[0]"
|
||||||
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}${
|
:src="
|
||||||
/^EN/.test(stockName) ? 'rb' : ''
|
getVehicleThumbnailURL(stockName.split(':')[0], /^EN/.test(stockName) ? 'rb' : '')
|
||||||
}.png`"
|
"
|
||||||
@error="onImageError($event, stockName)"
|
@error="onImageError($event, stockName)"
|
||||||
@click.stop="() => {}"
|
@click.stop="() => {}"
|
||||||
width="400"
|
width="400"
|
||||||
@@ -41,10 +44,10 @@
|
|||||||
<!-- /// Manualne dodawanie miniaturek członów dla kibelków /// -->
|
<!-- /// Manualne dodawanie miniaturek członów dla kibelków /// -->
|
||||||
<img
|
<img
|
||||||
:data-mouseover="stockName"
|
:data-mouseover="stockName"
|
||||||
data-popup-key="VehiclePreviewPopUp"
|
data-tooltip-type="VehiclePreviewTooltip"
|
||||||
:data-popup-content="stockName.split(':')[0]"
|
:data-tooltip-content="stockName.split(':')[0]"
|
||||||
v-if="/^(EN|2EN)/.test(stockName)"
|
v-if="/^(EN|2EN)/.test(stockName)"
|
||||||
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}s.png`"
|
:src="getVehicleThumbnailURL(stockName, 's')"
|
||||||
@error="
|
@error="
|
||||||
(event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-s.png')
|
(event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-s.png')
|
||||||
"
|
"
|
||||||
@@ -53,10 +56,10 @@
|
|||||||
|
|
||||||
<img
|
<img
|
||||||
:data-mouseover="stockName"
|
:data-mouseover="stockName"
|
||||||
data-popup-key="VehiclePreviewPopUp"
|
data-tooltip-type="VehiclePreviewTooltip"
|
||||||
:data-popup-content="stockName.split(':')[0]"
|
:data-tooltip-content="stockName.split(':')[0]"
|
||||||
v-if="/^EN71/.test(stockName)"
|
v-if="/^EN71/.test(stockName)"
|
||||||
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}s.png`"
|
:src="getVehicleThumbnailURL(stockName, 's')"
|
||||||
@error="
|
@error="
|
||||||
(event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-s.png')
|
(event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-s.png')
|
||||||
"
|
"
|
||||||
@@ -65,10 +68,10 @@
|
|||||||
|
|
||||||
<img
|
<img
|
||||||
:data-mouseover="stockName"
|
:data-mouseover="stockName"
|
||||||
data-popup-key="VehiclePreviewPopUp"
|
data-tooltip-type="VehiclePreviewTooltip"
|
||||||
:data-popup-content="stockName.split(':')[0]"
|
:data-tooltip-content="stockName.split(':')[0]"
|
||||||
v-if="/^(EN|2EN)/.test(stockName)"
|
v-if="/^(EN|2EN)/.test(stockName)"
|
||||||
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}ra.png`"
|
:src="getVehicleThumbnailURL(stockName, 'ra')"
|
||||||
@error="
|
@error="
|
||||||
(event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-ra.png')
|
(event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-ra.png')
|
||||||
"
|
"
|
||||||
@@ -110,20 +113,20 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
getVehicleThumbnailURL(locoType: string, suffix?: string) {
|
||||||
|
return `https://static.spythere.eu/thumbnails/${locoType}${suffix}.png`;
|
||||||
|
},
|
||||||
|
|
||||||
onImageError(event: Event, stockName: string) {
|
onImageError(event: Event, stockName: string) {
|
||||||
let fallbackName = '';
|
let fallbackName = '';
|
||||||
|
|
||||||
const isLoco = /.-\d{3}/.test(stockName);
|
const isLoco = /.-\d{3}/.test(stockName);
|
||||||
|
|
||||||
if (isLoco) {
|
if (isLoco) {
|
||||||
fallbackName += 'loco-';
|
if (/^\d?EN\d{2}/.test(stockName)) fallbackName = 'loco-ezt';
|
||||||
fallbackName += /^\d?EN\d{2}/.test(stockName)
|
else if (/^SN\d{2}/.test(stockName)) fallbackName = 'loco-szt';
|
||||||
? 'ezt'
|
else if (/^\d{0,}?E/.test(stockName)) fallbackName = 'loco-e';
|
||||||
: /^SN\d{2}/.test(stockName)
|
else fallbackName = 'loco-s';
|
||||||
? 'szt'
|
|
||||||
: /^\d?E/.test(stockName)
|
|
||||||
? 'e'
|
|
||||||
: 's';
|
|
||||||
} else {
|
} else {
|
||||||
const isCarPassenger = /(\d{3}a|(Bau|Gor)\d{2}|304C)_/.test(stockName);
|
const isCarPassenger = /(\d{3}a|(Bau|Gor)\d{2}|304C)_/.test(stockName);
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
:to="`/journal/dispatchers?search-dispatcher=${historyItem.dispatcherName}`"
|
:to="`/journal/dispatchers?search-dispatcher=${historyItem.dispatcherName}`"
|
||||||
>
|
>
|
||||||
<b
|
<b
|
||||||
v-if="isDonator(historyItem.dispatcherName)"
|
v-if="apiStore.donatorsData.includes(historyItem.dispatcherName)"
|
||||||
class="text--donator"
|
class="text--donator"
|
||||||
:title="$t('donations.dispatcher-message')"
|
:title="$t('donations.dispatcher-message')"
|
||||||
>
|
>
|
||||||
@@ -128,13 +128,13 @@ import { Status } from '../../../typings/common';
|
|||||||
import Loading from '../../Global/Loading.vue';
|
import Loading from '../../Global/Loading.vue';
|
||||||
import AddDataButton from '../../Global/AddDataButton.vue';
|
import AddDataButton from '../../Global/AddDataButton.vue';
|
||||||
import dateMixin from '../../../mixins/dateMixin';
|
import dateMixin from '../../../mixins/dateMixin';
|
||||||
import donatorMixin from '../../../mixins/donatorMixin';
|
|
||||||
import styleMixin from '../../../mixins/styleMixin';
|
import styleMixin from '../../../mixins/styleMixin';
|
||||||
|
import { useApiStore } from '../../../store/apiStore';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { Loading, AddDataButton },
|
components: { Loading, AddDataButton },
|
||||||
|
|
||||||
mixins: [dateMixin, styleMixin, donatorMixin],
|
mixins: [dateMixin, styleMixin],
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
dispatcherHistory: {
|
dispatcherHistory: {
|
||||||
@@ -159,6 +159,7 @@ export default defineComponent({
|
|||||||
return {
|
return {
|
||||||
Status,
|
Status,
|
||||||
store: useMainStore(),
|
store: useMainStore(),
|
||||||
|
apiStore: useApiStore(),
|
||||||
regions
|
regions
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
ref="button"
|
ref="button"
|
||||||
>
|
>
|
||||||
<img src="/images/icon-filter2.svg" alt="Open filters" />
|
<img src="/images/icon-filter2.svg" alt="Open filters" />
|
||||||
{{ $t('options.filters') }} [F]
|
[F] {{ $t('options.filters') }}
|
||||||
<span class="active-indicator" v-if="currentOptionsActive"></span>
|
<span class="active-indicator" v-if="currentOptionsActive"></span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -301,6 +301,6 @@ export default defineComponent({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../styles/dropdown.scss';
|
@import '../../styles/dropdown';
|
||||||
@import '../../styles/dropdown_filters.scss';
|
@import '../../styles/dropdown_filters';
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -17,7 +17,34 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<TimetableHistoryList :timetableHistory="timetableHistory" />
|
<ul class="journal-list">
|
||||||
|
<transition-group name="list-anim">
|
||||||
|
<li
|
||||||
|
v-for="{ timetable, showExtraInfo } in computedTimetableHistory"
|
||||||
|
class="journal_item"
|
||||||
|
:key="timetable.id"
|
||||||
|
@click="showExtraInfo.value = !showExtraInfo.value"
|
||||||
|
>
|
||||||
|
<div class="journal_item-info">
|
||||||
|
<!-- General -->
|
||||||
|
<TimetableGeneral :timetable="timetable" />
|
||||||
|
<!-- Route -->
|
||||||
|
<span class="item-route">
|
||||||
|
<b>{{ timetable.route.replace('|', ' - ') }}</b>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
<!-- Stops -->
|
||||||
|
<TimetableStops :timetable="timetable" :showExtraInfo="showExtraInfo.value" />
|
||||||
|
<!-- Status -->
|
||||||
|
<TimetableStatus :timetable="timetable" />
|
||||||
|
|
||||||
|
<!-- Extra -->
|
||||||
|
<TimetableDetails :timetable="timetable" :showExtraInfo="showExtraInfo.value" />
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</transition-group>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<AddDataButton
|
<AddDataButton
|
||||||
:list="timetableHistory"
|
:list="timetableHistory"
|
||||||
@@ -37,17 +64,29 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, PropType } from 'vue';
|
import { defineComponent, PropType, ref } from 'vue';
|
||||||
|
|
||||||
import Loading from '../../Global/Loading.vue';
|
import Loading from '../../Global/Loading.vue';
|
||||||
import AddDataButton from '../../Global/AddDataButton.vue';
|
import AddDataButton from '../../Global/AddDataButton.vue';
|
||||||
import TimetableHistoryList from './TimetableHistoryList.vue';
|
|
||||||
import { useMainStore } from '../../../store/mainStore';
|
import { useMainStore } from '../../../store/mainStore';
|
||||||
import { Status } from '../../../typings/common';
|
import { Status } from '../../../typings/common';
|
||||||
import { API } from '../../../typings/api';
|
import { API } from '../../../typings/api';
|
||||||
|
|
||||||
|
import TimetableGeneral from './TimetableGeneral.vue';
|
||||||
|
import TimetableStops from './TimetableStops.vue';
|
||||||
|
import TimetableStatus from './TimetableStatus.vue';
|
||||||
|
import TimetableDetails from './TimetableDetails.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { Loading, AddDataButton, TimetableHistoryList },
|
components: {
|
||||||
|
Loading,
|
||||||
|
AddDataButton,
|
||||||
|
TimetableDetails,
|
||||||
|
TimetableGeneral,
|
||||||
|
TimetableStatus,
|
||||||
|
TimetableStops
|
||||||
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
timetableHistory: {
|
timetableHistory: {
|
||||||
@@ -73,6 +112,15 @@ export default defineComponent({
|
|||||||
Status,
|
Status,
|
||||||
store: useMainStore()
|
store: useMainStore()
|
||||||
};
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
computedTimetableHistory() {
|
||||||
|
return this.timetableHistory.map((timetable) => ({
|
||||||
|
timetable,
|
||||||
|
showExtraInfo: ref(false)
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -80,4 +128,15 @@ export default defineComponent({
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../../styles/JournalSection.scss';
|
@import '../../../styles/JournalSection.scss';
|
||||||
@import '../../../styles/animations.scss';
|
@import '../../../styles/animations.scss';
|
||||||
|
|
||||||
|
@include smallScreen {
|
||||||
|
.journal_item-info {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-route {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,195 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="details-actions">
|
||||||
|
<button class="btn--action">
|
||||||
|
<b>{{ $t('journal.stock-info') }}</b>
|
||||||
|
<img :src="`/images/icon-arrow-${showExtraInfo ? 'asc' : 'desc'}.svg`" alt="Arrow icon" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="details-body" v-if="timetable.stockString && timetable.stockMass && showExtraInfo">
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<div class="stock-specs">
|
||||||
|
<span class="badge">
|
||||||
|
<span>{{ $t('journal.dispatcher-name') }}</span>
|
||||||
|
<span>{{ timetable.authorName }}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stock-specs">
|
||||||
|
<span class="badge">
|
||||||
|
<span>{{ $t('journal.stock-max-speed') }}</span>
|
||||||
|
<span>{{ timetable.maxSpeed }}km/h</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="badge">
|
||||||
|
<span>{{ $t('journal.stock-length') }}</span>
|
||||||
|
<span>
|
||||||
|
{{
|
||||||
|
currentHistoryIndex == 0
|
||||||
|
? timetable.stockLength
|
||||||
|
: stockHistory[currentHistoryIndex].stockLength || timetable.stockLength
|
||||||
|
}}m
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="badge">
|
||||||
|
<span>{{ $t('journal.stock-mass') }}</span>
|
||||||
|
<span>
|
||||||
|
{{
|
||||||
|
Math.floor(
|
||||||
|
(currentHistoryIndex == 0
|
||||||
|
? timetable.stockMass!
|
||||||
|
: stockHistory[currentHistoryIndex].stockMass || timetable.stockMass) / 1000
|
||||||
|
)
|
||||||
|
}}t
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Historia zmian w składzie -->
|
||||||
|
<div class="stock-history" v-if="stockHistory.length > 1">
|
||||||
|
<button
|
||||||
|
v-for="(sh, i) in stockHistory"
|
||||||
|
:key="i"
|
||||||
|
class="btn--action"
|
||||||
|
:data-checked="i == currentHistoryIndex"
|
||||||
|
@click.stop="currentHistoryIndex = i"
|
||||||
|
>
|
||||||
|
{{ sh.updatedAt }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<StockList
|
||||||
|
:trainStockList="
|
||||||
|
(currentHistoryIndex == 0
|
||||||
|
? timetable.stockString
|
||||||
|
: stockHistory[currentHistoryIndex].stockString
|
||||||
|
).split(';')
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { PropType, defineComponent } from 'vue';
|
||||||
|
import StockList from '../../Global/StockList.vue';
|
||||||
|
import { API } from '../../../typings/api';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: { StockList },
|
||||||
|
props: {
|
||||||
|
showExtraInfo: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
timetable: {
|
||||||
|
type: Object as PropType<API.TimetableHistory.Data>,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
currentHistoryIndex: 0
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
stockHistory() {
|
||||||
|
return this.timetable.stockHistory
|
||||||
|
.slice()
|
||||||
|
.reverse()
|
||||||
|
.map((h) => {
|
||||||
|
const historyData = h.split('@');
|
||||||
|
return {
|
||||||
|
updatedAt: new Date(Number(historyData[0])).toLocaleTimeString(this.$i18n.locale, {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
}),
|
||||||
|
stockString: historyData[1],
|
||||||
|
stockMass: Number(historyData[2]) || undefined,
|
||||||
|
stockLength: Number(historyData[3]) || undefined
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onImageError(e: Event) {
|
||||||
|
const imageEl = e.target as HTMLImageElement;
|
||||||
|
imageEl.src = '/images/icon-unknown.png';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '../../../styles/variables.scss';
|
||||||
|
@import '../../../styles/responsive.scss';
|
||||||
|
@import '../../../styles/badge.scss';
|
||||||
|
|
||||||
|
.details-body {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-actions {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
button img {
|
||||||
|
height: 1.25em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-history {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5em;
|
||||||
|
margin-top: 1em;
|
||||||
|
|
||||||
|
button[data-checked='true'] {
|
||||||
|
color: $accentCol;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-specs {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5em;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
span:last-child {
|
||||||
|
color: black;
|
||||||
|
background-color: $accentCol;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.stock-list {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
padding-bottom: 0.5em;
|
||||||
|
|
||||||
|
li > div {
|
||||||
|
margin: 1em 0;
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
|
color: #aaa;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include smallScreen() {
|
||||||
|
.stock-specs {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-actions {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="item-extra" v-if="timetable.stockString && timetable.stockMass && showExtraInfo">
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<div class="stock-specs">
|
|
||||||
<span class="badge">
|
|
||||||
<span>{{ $t('journal.dispatcher-name') }}</span>
|
|
||||||
<span>{{ timetable.authorName }}</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="stock-specs">
|
|
||||||
<span class="badge">
|
|
||||||
<span>{{ $t('journal.stock-max-speed') }}</span>
|
|
||||||
<span>{{ timetable.maxSpeed }}km/h</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="badge">
|
|
||||||
<span>{{ $t('journal.stock-length') }}</span>
|
|
||||||
<span>
|
|
||||||
{{
|
|
||||||
currentHistoryIndex == 0
|
|
||||||
? timetable.stockLength
|
|
||||||
: stockHistory[currentHistoryIndex].stockLength || timetable.stockLength
|
|
||||||
}}m
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="badge">
|
|
||||||
<span>{{ $t('journal.stock-mass') }}</span>
|
|
||||||
<span>
|
|
||||||
{{
|
|
||||||
Math.floor(
|
|
||||||
(currentHistoryIndex == 0
|
|
||||||
? timetable.stockMass!
|
|
||||||
: stockHistory[currentHistoryIndex].stockMass || timetable.stockMass) / 1000
|
|
||||||
)
|
|
||||||
}}t
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Historia zmian w składzie -->
|
|
||||||
<div class="stock-history" v-if="stockHistory.length > 1">
|
|
||||||
<button
|
|
||||||
v-for="(sh, i) in stockHistory"
|
|
||||||
:key="i"
|
|
||||||
class="btn--action"
|
|
||||||
:data-checked="i == currentHistoryIndex"
|
|
||||||
@click.stop="currentHistoryIndex = i"
|
|
||||||
>
|
|
||||||
{{ sh.updatedAt }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<StockList
|
|
||||||
:trainStockList="
|
|
||||||
(currentHistoryIndex == 0
|
|
||||||
? timetable.stockString
|
|
||||||
: stockHistory[currentHistoryIndex].stockString
|
|
||||||
).split(';')
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { PropType, defineComponent } from 'vue';
|
|
||||||
import StockList from '../../Global/StockList.vue';
|
|
||||||
import { API } from '../../../typings/api';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: { StockList },
|
|
||||||
props: {
|
|
||||||
showExtraInfo: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
timetable: {
|
|
||||||
type: Object as PropType<API.TimetableHistory.Data>,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
currentHistoryIndex: 0
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
stockHistory() {
|
|
||||||
return this.timetable.stockHistory
|
|
||||||
.slice()
|
|
||||||
.reverse()
|
|
||||||
.map((h) => {
|
|
||||||
const historyData = h.split('@');
|
|
||||||
return {
|
|
||||||
updatedAt: new Date(Number(historyData[0])).toLocaleTimeString(this.$i18n.locale, {
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit'
|
|
||||||
}),
|
|
||||||
stockString: historyData[1],
|
|
||||||
stockMass: Number(historyData[2]) || undefined,
|
|
||||||
stockLength: Number(historyData[3]) || undefined
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
onImageError(e: Event) {
|
|
||||||
const imageEl = e.target as HTMLImageElement;
|
|
||||||
imageEl.src = '/images/icon-unknown.png';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@import '../../../styles/variables.scss';
|
|
||||||
@import '../../../styles/responsive.scss';
|
|
||||||
@import '../../../styles/badge.scss';
|
|
||||||
|
|
||||||
.item-extra {
|
|
||||||
margin-top: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stock-history {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.5em;
|
|
||||||
margin-top: 1em;
|
|
||||||
|
|
||||||
button[data-checked='true'] {
|
|
||||||
color: $accentCol;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.stock-specs {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.5em;
|
|
||||||
margin-top: 0.5em;
|
|
||||||
|
|
||||||
.badge {
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
span:last-child {
|
|
||||||
color: black;
|
|
||||||
background-color: $accentCol;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include smallScreen() {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.stock-list {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-end;
|
|
||||||
overflow: auto;
|
|
||||||
|
|
||||||
padding-bottom: 0.5em;
|
|
||||||
|
|
||||||
li > div {
|
|
||||||
margin: 1em 0;
|
|
||||||
|
|
||||||
text-align: center;
|
|
||||||
color: #aaa;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
</strong>
|
</strong>
|
||||||
|
|
||||||
<strong
|
<strong
|
||||||
v-if="isDonator(timetable.driverName)"
|
v-if="apiStore.donatorsData.includes(timetable.driverName)"
|
||||||
class="text--donator"
|
class="text--donator"
|
||||||
:title="$t('donations.driver-message')"
|
:title="$t('donations.driver-message')"
|
||||||
>
|
>
|
||||||
@@ -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--action btn-timetable"
|
||||||
|
@click.stop="showTimetable(timetable, $event.currentTarget)"
|
||||||
|
>
|
||||||
|
<img src="/images/icon-train.svg" alt="train icon" />
|
||||||
|
<b>{{ $t('journal.timetable-online-button') }}</b>
|
||||||
|
</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -81,10 +81,16 @@ import { API } from '../../../typings/api';
|
|||||||
import dateMixin from '../../../mixins/dateMixin';
|
import dateMixin from '../../../mixins/dateMixin';
|
||||||
import modalTrainMixin from '../../../mixins/modalTrainMixin';
|
import modalTrainMixin from '../../../mixins/modalTrainMixin';
|
||||||
import styleMixin from '../../../mixins/styleMixin';
|
import styleMixin from '../../../mixins/styleMixin';
|
||||||
import donatorMixin from '../../../mixins/donatorMixin';
|
import { useApiStore } from '../../../store/apiStore';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
mixins: [dateMixin, modalTrainMixin, styleMixin, donatorMixin],
|
mixins: [dateMixin, modalTrainMixin, styleMixin],
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
apiStore: useApiStore()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
timetable: {
|
timetable: {
|
||||||
@@ -104,8 +110,8 @@ export default defineComponent({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../../styles/responsive.scss';
|
@import '../../../styles/responsive';
|
||||||
@import '../../../styles/badge.scss';
|
@import '../../../styles/badge';
|
||||||
|
|
||||||
.item-general {
|
.item-general {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -117,8 +123,22 @@ export default defineComponent({
|
|||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-date {
|
.general-train {
|
||||||
margin-right: 0.5em;
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.25em;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
line-height: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.general-time {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
gap: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.badges {
|
.badges {
|
||||||
@@ -143,22 +163,12 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.general-train {
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-timetable {
|
.btn-timetable {
|
||||||
display: inline-block;
|
display: flex;
|
||||||
padding: 0.1em 0.4em;
|
padding: 0.2em 0.5em;
|
||||||
margin-left: 0.5em;
|
|
||||||
|
|
||||||
img {
|
img {
|
||||||
vertical-align: top;
|
height: 1.25em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 -->
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
<!-- Status -->
|
<!-- Status -->
|
||||||
<TimetableStatus :timetable="timetable" />
|
<TimetableStatus :timetable="timetable" />
|
||||||
|
|
||||||
<button class="btn--option btn--show">
|
<button class="btn--action btn--show">
|
||||||
{{ $t('journal.stock-info') }}
|
{{ $t('journal.stock-info') }}
|
||||||
<img
|
<img
|
||||||
:src="`/images/icon-arrow-${showExtraInfo.value ? 'asc' : 'desc'}.svg`"
|
:src="`/images/icon-arrow-${showExtraInfo.value ? 'asc' : 'desc'}.svg`"
|
||||||
@@ -66,9 +66,9 @@ export default defineComponent({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../../styles/variables.scss';
|
@import '../../../styles/variables';
|
||||||
@import '../../../styles/responsive.scss';
|
@import '../../../styles/responsive';
|
||||||
@import '../../../styles/JournalSection.scss';
|
@import '../../../styles/JournalSection';
|
||||||
|
|
||||||
.btn--show {
|
.btn--show {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="popup" v-show="popupStore.currentPopupComponent" ref="preview">
|
|
||||||
<component v-if="popupStore.currentPopupComponent" :is="popupStore.currentPopupComponent" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
import DonatorPopUp from './DonatorPopUp.vue';
|
|
||||||
import TrainCommentsPopUp from './TrainCommentsPopUp.vue';
|
|
||||||
import VehiclePreviewPopUp from './VehiclePreviewPopUp.vue';
|
|
||||||
import { usePopupStore } from '../../store/popupStore';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: { DonatorPopUp, TrainCommentsPopUp, VehiclePreviewPopUp },
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
popupStore: usePopupStore()
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
watch: {
|
|
||||||
'popupStore.popupPosition': {
|
|
||||||
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)';
|
|
||||||
|
|
||||||
this.$nextTick(() => {
|
|
||||||
const isOutside =
|
|
||||||
val.y + previewEl.getBoundingClientRect().height > window.innerHeight + window.scrollY;
|
|
||||||
|
|
||||||
// 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'
|
|
||||||
}))`;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.popup {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 250;
|
|
||||||
max-width: 400px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -72,18 +72,15 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, PropType } from 'vue';
|
import { defineComponent, PropType } from 'vue';
|
||||||
import dateMixin from '../../mixins/dateMixin';
|
import dateMixin from '../../mixins/dateMixin';
|
||||||
import Station from '../../scripts/interfaces/Station';
|
|
||||||
import Loading from '../Global/Loading.vue';
|
import Loading from '../Global/Loading.vue';
|
||||||
import styleMixin from '../../mixins/styleMixin';
|
import styleMixin from '../../mixins/styleMixin';
|
||||||
import listObserverMixin from '../../mixins/listObserverMixin';
|
|
||||||
import { ActiveScenery } from '../../store/typings';
|
|
||||||
import { API } from '../../typings/api';
|
import { API } from '../../typings/api';
|
||||||
import { Status } from '../../typings/common';
|
import { ActiveScenery, Station, Status } from '../../typings/common';
|
||||||
import { useApiStore } from '../../store/apiStore';
|
import { useApiStore } from '../../store/apiStore';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'SceneryDispatchersHistory',
|
name: 'SceneryDispatchersHistory',
|
||||||
mixins: [dateMixin, styleMixin, listObserverMixin],
|
mixins: [dateMixin, styleMixin],
|
||||||
components: { Loading },
|
components: { Loading },
|
||||||
props: {
|
props: {
|
||||||
station: {
|
station: {
|
||||||
|
|||||||
@@ -14,8 +14,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { PropType, defineComponent } from 'vue';
|
import { PropType, defineComponent } from 'vue';
|
||||||
import Station from '../../scripts/interfaces/Station';
|
import { ActiveScenery, Station } from '../../typings/common';
|
||||||
import { ActiveScenery } from '../../store/typings';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
|
|||||||
@@ -89,8 +89,7 @@ import SceneryInfoIcons from './SceneryInfo/SceneryInfoIcons.vue';
|
|||||||
import SceneryInfoUserList from './SceneryInfo/SceneryInfoUserList.vue';
|
import SceneryInfoUserList from './SceneryInfo/SceneryInfoUserList.vue';
|
||||||
import SceneryInfoSpawnList from './SceneryInfo/SceneryInfoSpawnList.vue';
|
import SceneryInfoSpawnList from './SceneryInfo/SceneryInfoSpawnList.vue';
|
||||||
import SceneryInfoRoutes from './SceneryInfo/SceneryInfoRoutes.vue';
|
import SceneryInfoRoutes from './SceneryInfo/SceneryInfoRoutes.vue';
|
||||||
import Station from '../../scripts/interfaces/Station';
|
import { ActiveScenery, Station } from '../../typings/common';
|
||||||
import { ActiveScenery } from '../../store/typings';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="text--donator"
|
class="text--donator"
|
||||||
v-if="isDonator(onlineScenery.dispatcherName)"
|
v-if="apiStore.donatorsData.includes(onlineScenery.dispatcherName)"
|
||||||
:title="$t('donations.dispatcher-message')"
|
:title="$t('donations.dispatcher-message')"
|
||||||
>
|
>
|
||||||
{{ onlineScenery.dispatcherName }}
|
{{ onlineScenery.dispatcherName }}
|
||||||
@@ -49,11 +49,18 @@ import dateMixin from '../../../mixins/dateMixin';
|
|||||||
import routerMixin from '../../../mixins/routerMixin';
|
import routerMixin from '../../../mixins/routerMixin';
|
||||||
import styleMixin from '../../../mixins/styleMixin';
|
import styleMixin from '../../../mixins/styleMixin';
|
||||||
import StationStatusBadge from '../../Global/StationStatusBadge.vue';
|
import StationStatusBadge from '../../Global/StationStatusBadge.vue';
|
||||||
import { ActiveScenery } from '../../../store/typings';
|
import { ActiveScenery } from '../../../typings/common';
|
||||||
import donatorMixin from '../../../mixins/donatorMixin';
|
import { useApiStore } from '../../../store/apiStore';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
mixins: [styleMixin, dateMixin, routerMixin, donatorMixin],
|
mixins: [styleMixin, dateMixin, routerMixin],
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
apiStore: useApiStore()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
onlineScenery: {
|
onlineScenery: {
|
||||||
type: Object as PropType<ActiveScenery>,
|
type: Object as PropType<ActiveScenery>,
|
||||||
|
|||||||
@@ -24,8 +24,8 @@
|
|||||||
:title="
|
:title="
|
||||||
$t('sceneries.info.control-type') + $t(`controls.${station?.generalInfo.controlType}`)
|
$t('sceneries.info.control-type') + $t(`controls.${station?.generalInfo.controlType}`)
|
||||||
"
|
"
|
||||||
v-html="getControlTypeAbbrev(station?.generalInfo.controlType)"
|
|
||||||
>
|
>
|
||||||
|
{{ $t(`controls.abbrevs.${station.generalInfo.controlType}`) }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<img
|
<img
|
||||||
@@ -88,12 +88,11 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { PropType, defineComponent } from 'vue';
|
import { PropType, defineComponent } from 'vue';
|
||||||
import stationInfoMixin from '../../../mixins/stationInfoMixin';
|
|
||||||
import styleMixin from '../../../mixins/styleMixin';
|
import styleMixin from '../../../mixins/styleMixin';
|
||||||
import Station from '../../../scripts/interfaces/Station';
|
import { Station } from '../../../typings/common';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
mixins: [stationInfoMixin, styleMixin],
|
mixins: [styleMixin],
|
||||||
props: {
|
props: {
|
||||||
station: {
|
station: {
|
||||||
type: Object as PropType<Station>
|
type: Object as PropType<Station>
|
||||||
@@ -104,6 +103,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../../styles/icons.scss';
|
@import '../../../styles/icons.scss';
|
||||||
|
|
||||||
.info-icons {
|
.info-icons {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -111,6 +111,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
margin: 1em;
|
margin: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-info {
|
.icon-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { PropType, defineComponent } from 'vue';
|
import { PropType, defineComponent } from 'vue';
|
||||||
import Station from '../../../scripts/interfaces/Station';
|
import { Station } from '../../../typings/common';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<transition-group name="spawns-anim" tag="ul">
|
<transition-group name="spawns-anim" tag="ul">
|
||||||
<li
|
<li
|
||||||
class="badge spawn badge-none"
|
class="badge badge-none"
|
||||||
v-if="!onlineScenery || onlineScenery.spawns.length == 0"
|
v-if="!onlineScenery || onlineScenery.spawns.length == 0"
|
||||||
key="no-spawns"
|
key="no-spawns"
|
||||||
>
|
>
|
||||||
@@ -16,13 +16,13 @@
|
|||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li
|
<li
|
||||||
class="badge spawn"
|
class="badge spawn-badge"
|
||||||
v-for="(spawn, i) in sortedSpawns"
|
v-for="(spawn, i) in sortedSpawns"
|
||||||
:key="spawn.spawnName + onlineScenery?.dispatcherName + i"
|
:key="spawn.spawnName + onlineScenery?.dispatcherName + i"
|
||||||
:data-electrified="spawn.isElectrified"
|
:data-electrified="spawn.isElectrified"
|
||||||
>
|
>
|
||||||
<span class="spawn_name">{{ spawn.spawnName }}</span>
|
<span class="name">{{ spawn.spawnName }}</span>
|
||||||
<span class="spawn_length">{{ spawn.spawnLength }}m</span>
|
<span class="length">{{ spawn.spawnLength }}m</span>
|
||||||
</li>
|
</li>
|
||||||
</transition-group>
|
</transition-group>
|
||||||
</section>
|
</section>
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { PropType, defineComponent } from 'vue';
|
import { PropType, defineComponent } from 'vue';
|
||||||
import { ActiveScenery } from '../../../store/typings';
|
import { ActiveScenery } from '../../../typings/common';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
@@ -59,19 +59,6 @@ ul {
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.spawn {
|
|
||||||
color: white;
|
|
||||||
|
|
||||||
&_length {
|
|
||||||
background-color: #404040;
|
|
||||||
color: #cfcfcf;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-electrified='true'] > &_name {
|
|
||||||
background-color: #007599;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.spawns-anim {
|
.spawns-anim {
|
||||||
&-move,
|
&-move,
|
||||||
&-enter-active,
|
&-enter-active,
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
<template>
|
|
||||||
<section class="info-stats" :class="!station.onlineInfo ? 'no-stats' : ''">
|
|
||||||
<span class="likes">
|
|
||||||
<img src="/images/icon-like" alt="Likes count icon" />
|
|
||||||
<span>{{ station.onlineInfo?.dispatcherRate || '0' }}</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="users">
|
|
||||||
<img src="/images/icon-user" alt="Users count icon" />
|
|
||||||
<span>{{ station.onlineInfo?.currentUsers || '0' }}</span>
|
|
||||||
/
|
|
||||||
<span>{{ station.onlineInfo?.maxUsers || '0' }}</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="spawns">
|
|
||||||
<img src="/images/icon-spawn" alt="Spawns count icon" />
|
|
||||||
<span>{{ station.onlineInfo?.spawns.length || '0' }}</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="schedules">
|
|
||||||
<img src="/images/icon-timetable" alt="Timetables count icon" />
|
|
||||||
<span>
|
|
||||||
<span style="color: #eee">{{ station.onlineInfo?.scheduledTrains?.length || '0' }}</span>
|
|
||||||
/
|
|
||||||
<span style="color: #bbb"
|
|
||||||
>{{
|
|
||||||
station.onlineInfo?.scheduledTrains?.filter((train) => train.stopInfo.confirmed)
|
|
||||||
.length || '0'
|
|
||||||
}}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</section>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { PropType, defineComponent } from 'vue';
|
|
||||||
import Station from '../../../scripts/interfaces/Station';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
station: {
|
|
||||||
type: Object as PropType<Station>,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@import '../../../styles/variables.scss';
|
|
||||||
|
|
||||||
.info-stats {
|
|
||||||
padding: 1rem 0;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
font-size: 1.65em;
|
|
||||||
|
|
||||||
&.no-stats {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > span {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
margin: 0.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.likes,
|
|
||||||
.spawns {
|
|
||||||
color: $accentCol;
|
|
||||||
}
|
|
||||||
|
|
||||||
span > img {
|
|
||||||
width: 1.2em;
|
|
||||||
margin-right: 0.5em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
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 '../../../store/typings';
|
import { ActiveScenery } from '../../../typings/common';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
mixins: [routerMixin, modalTrainMixin],
|
mixins: [routerMixin, modalTrainMixin],
|
||||||
|
|||||||
@@ -14,14 +14,6 @@
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="header_links" v-if="station">
|
<span class="header_links" v-if="station">
|
||||||
<!-- <a
|
|
||||||
:href="`https://pragotron-td2.web.app/board?name=${station.name}`"
|
|
||||||
target="_blank"
|
|
||||||
:title="$t('scenery.pragotron-link')"
|
|
||||||
>
|
|
||||||
<img src="/images/icon-pragotron.svg" alt="icon-pragotron" />
|
|
||||||
</a> -->
|
|
||||||
|
|
||||||
<a :href="tabliceZbiorczeHref" target="_blank" :title="$t('scenery.tablice-link')">
|
<a :href="tabliceZbiorczeHref" target="_blank" :title="$t('scenery.tablice-link')">
|
||||||
<img src="/images/icon-tablice.ico" alt="icon-tablice" />
|
<img src="/images/icon-tablice.ico" alt="icon-tablice" />
|
||||||
</a>
|
</a>
|
||||||
@@ -186,12 +178,11 @@ import { useRoute } from 'vue-router';
|
|||||||
import Loading from '../Global/Loading.vue';
|
import Loading from '../Global/Loading.vue';
|
||||||
import dateMixin from '../../mixins/dateMixin';
|
import dateMixin from '../../mixins/dateMixin';
|
||||||
import routerMixin from '../../mixins/routerMixin';
|
import routerMixin from '../../mixins/routerMixin';
|
||||||
import Station from '../../scripts/interfaces/Station';
|
|
||||||
import { useMainStore } from '../../store/mainStore';
|
import { useMainStore } from '../../store/mainStore';
|
||||||
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
||||||
import ScheduledTrainStatus from './ScheduledTrainStatus.vue';
|
import ScheduledTrainStatus from './ScheduledTrainStatus.vue';
|
||||||
import { ActiveScenery } from '../../store/typings';
|
|
||||||
import { useApiStore } from '../../store/apiStore';
|
import { useApiStore } from '../../store/apiStore';
|
||||||
|
import { ActiveScenery, Station } from '../../typings/common';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'SceneryTimetable',
|
name: 'SceneryTimetable',
|
||||||
@@ -414,13 +405,6 @@ export default defineComponent({
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.g-tooltip > .content {
|
|
||||||
z-index: 100;
|
|
||||||
color: white;
|
|
||||||
|
|
||||||
left: 110%;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 1.1em;
|
width: 1.1em;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,17 +71,14 @@
|
|||||||
import { defineComponent, PropType } from 'vue';
|
import { defineComponent, PropType } from 'vue';
|
||||||
import dateMixin from '../../mixins/dateMixin';
|
import dateMixin from '../../mixins/dateMixin';
|
||||||
|
|
||||||
import Station from '../../scripts/interfaces/Station';
|
|
||||||
import Loading from '../Global/Loading.vue';
|
import Loading from '../Global/Loading.vue';
|
||||||
import listObserverMixin from '../../mixins/listObserverMixin';
|
|
||||||
import { ActiveScenery } from '../../store/typings';
|
|
||||||
import { API } from '../../typings/api';
|
import { API } from '../../typings/api';
|
||||||
import { Status } from '../../typings/common';
|
import { ActiveScenery, Station, Status } from '../../typings/common';
|
||||||
import { useApiStore } from '../../store/apiStore';
|
import { useApiStore } from '../../store/apiStore';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'SceneryTimetablesHistory',
|
name: 'SceneryTimetablesHistory',
|
||||||
mixins: [dateMixin, listObserverMixin],
|
mixins: [dateMixin],
|
||||||
props: {
|
props: {
|
||||||
station: {
|
station: {
|
||||||
type: Object as PropType<Station>
|
type: Object as PropType<Station>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, PropType } from 'vue';
|
import { defineComponent, PropType } from 'vue';
|
||||||
import { ScheduledTrain, StopStatus } from '../../store/typings';
|
import { ScheduledTrain, StopStatus } from '../../typings/common';
|
||||||
|
|
||||||
interface ScheduledTrainComp extends ScheduledTrain {
|
interface ScheduledTrainComp extends ScheduledTrain {
|
||||||
stopStatusIndicator: string;
|
stopStatusIndicator: string;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="filter-card" v-click-outside="closeCard" @keydown.esc="closeCard">
|
<section class="filter-card" v-click-outside="closeCard" @keydown.esc="closeCard">
|
||||||
<div class="card_controls">
|
<div class="card_controls">
|
||||||
<button class="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" />
|
||||||
{{ $t('options.filters') }} [F]
|
<p>[F] {{ $t('options.filters') }}</p>
|
||||||
<span class="active-indicator" v-if="!filterStore.areFiltersAtDefault"></span>
|
<span class="active-indicator" v-if="!filterStore.areFiltersAtDefault"></span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -28,8 +28,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<transition name="card-anim">
|
<transition name="card-anim">
|
||||||
<div class="card" v-if="isVisible" tabindex="0" ref="cardEl">
|
<div class="card" v-if="isVisible" tabindex="0" ref="cardRef" @keydown.r="resetFilters">
|
||||||
<div class="card_content">
|
<div class="card_content" @scroll="onScroll" ref="cardContentRef">
|
||||||
<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>
|
||||||
|
|
||||||
@@ -108,6 +108,7 @@
|
|||||||
:id="slider.id"
|
:id="slider.id"
|
||||||
:min="slider.minRange"
|
:min="slider.minRange"
|
||||||
:max="slider.maxRange"
|
:max="slider.maxRange"
|
||||||
|
:step="slider.step"
|
||||||
v-model="slider.value"
|
v-model="slider.value"
|
||||||
@change="handleInput"
|
@change="handleInput"
|
||||||
/>
|
/>
|
||||||
@@ -136,7 +137,7 @@
|
|||||||
:disabled="filterStore.areFiltersAtDefault"
|
:disabled="filterStore.areFiltersAtDefault"
|
||||||
:data-disabled="filterStore.areFiltersAtDefault"
|
:data-disabled="filterStore.areFiltersAtDefault"
|
||||||
>
|
>
|
||||||
{{ $t('filters.reset') }}
|
[R] {{ $t('filters.reset') }}
|
||||||
</button>
|
</button>
|
||||||
<button class="btn--action" @click="closeCard">{{ $t('filters.close') }}</button>
|
<button class="btn--action" @click="closeCard">{{ $t('filters.close') }}</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -170,7 +171,10 @@ export default defineComponent({
|
|||||||
currentRegion: { id: '', value: '' },
|
currentRegion: { id: '', value: '' },
|
||||||
|
|
||||||
delayInputTimer: -1,
|
delayInputTimer: -1,
|
||||||
chosenSearchScenery: ''
|
chosenSearchScenery: '',
|
||||||
|
|
||||||
|
scrollTop: 0,
|
||||||
|
lastFocusedEl: null as HTMLElement | null
|
||||||
}),
|
}),
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
@@ -236,7 +240,10 @@ export default defineComponent({
|
|||||||
|
|
||||||
isVisible(value: boolean) {
|
isVisible(value: boolean) {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (value) (this.$refs['cardEl'] as HTMLDivElement).focus();
|
if (value) {
|
||||||
|
(this.$refs['cardRef'] as HTMLDivElement).focus();
|
||||||
|
(this.$refs['cardContentRef'] as HTMLDivElement).scrollTop = this.scrollTop;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -247,6 +254,10 @@ export default defineComponent({
|
|||||||
this.isVisible = !this.isVisible;
|
this.isVisible = !this.isVisible;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onScroll(e: Event) {
|
||||||
|
this.scrollTop = (e.target as HTMLElement).scrollTop;
|
||||||
|
},
|
||||||
|
|
||||||
handleInput(e: Event) {
|
handleInput(e: Event) {
|
||||||
const target = e.target as HTMLInputElement;
|
const target = e.target as HTMLInputElement;
|
||||||
|
|
||||||
@@ -316,9 +327,9 @@ export default defineComponent({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../styles/responsive.scss';
|
@import '../../styles/responsive';
|
||||||
@import '../../styles/card.scss';
|
@import '../../styles/card';
|
||||||
@import '../../styles/animations.scss';
|
@import '../../styles/animations';
|
||||||
|
|
||||||
h3.section-header {
|
h3.section-header {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -431,23 +442,16 @@ h3.section-header {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card_actions {
|
.card_actions {
|
||||||
width: 100%;
|
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
|
|
||||||
.filter-option {
|
|
||||||
max-width: 50%;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-buttons {
|
.action-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
margin-top: 0.5em;
|
margin-top: 0.5em;
|
||||||
|
|
||||||
button {
|
button {
|
||||||
width: 50%;
|
width: 100%;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
|
|
||||||
@@ -578,4 +582,10 @@ h3.section-header {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@include smallScreen {
|
||||||
|
.card_controls > button.card-button > p {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,220 @@
|
|||||||
|
<template>
|
||||||
|
<div class="station-stats">
|
||||||
|
<div class="separator" />
|
||||||
|
|
||||||
|
<div class="stats-row">
|
||||||
|
<div>
|
||||||
|
<span
|
||||||
|
>{{ $t('station-stats.u-factor') }}
|
||||||
|
<a
|
||||||
|
href="https://td2.info.pl/dyskusje/wspolczynnik-ugla-czy-to-ma-sens/msg81011/#msg81011"
|
||||||
|
target="_blank"
|
||||||
|
:data-tooltip="$t('station-stats.u-factor-tooltip')"
|
||||||
|
>(?)</a
|
||||||
|
>:
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<b class="u-factor" :style="calculateFactorStyle()">
|
||||||
|
{{ uFactor.toFixed(2) }}
|
||||||
|
</b>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
•
|
||||||
|
{{ $t('station-stats.med-timetable-count') }}
|
||||||
|
<b>{{ medTimetableCount }}</b>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
•
|
||||||
|
{{ $t('station-stats.single-track-count') }}
|
||||||
|
<b>{{ trackCount.oneWay }}</b> (<b>{{ trackCount.oneWayElectric }} ⚡</b>)
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
•
|
||||||
|
{{ $t('station-stats.double-track-count') }}
|
||||||
|
<b>{{ trackCount.twoWay }}</b>
|
||||||
|
(<b>{{ trackCount.twoWayElectric }} ⚡</b>)
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
• {{ $t('station-stats.cross-sceneries') }} <b>{{ trackCount.crossTrack }}</b> (<b
|
||||||
|
>{{ trackCount.crossTrackElectric }} ⚡</b
|
||||||
|
>)
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
•
|
||||||
|
{{ $t('station-stats.open-spawns') }} <b>{{ spawnCount.passenger }}</b> - PAS /
|
||||||
|
<b>{{ spawnCount.freight }}</b> - TOW / <b>{{ spawnCount.loco }}</b> - LUZ /
|
||||||
|
<b>{{ spawnCount.all }}</b> - ALL
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import { useMainStore } from '../../store/mainStore';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
mainStore: useMainStore()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
calculateFactorStyle() {
|
||||||
|
if (this.uFactor == 0) return '';
|
||||||
|
|
||||||
|
const norm = this.uFactor == 0 ? 1 : Math.max(Math.min(this.uFactor / 2, 1), 0);
|
||||||
|
const lerp = 120 * norm;
|
||||||
|
|
||||||
|
return `color: hsl(${lerp}, 100%, 60%)`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
uFactor() {
|
||||||
|
const activeDispatchers = this.mainStore.activeSceneryList.filter(
|
||||||
|
(scenery) => scenery.region == this.mainStore.region.id && scenery.dispatcherId != -1
|
||||||
|
);
|
||||||
|
|
||||||
|
const activeTrains = this.mainStore.trainList.filter(
|
||||||
|
(train) => train.region == this.mainStore.region.id
|
||||||
|
);
|
||||||
|
|
||||||
|
return activeDispatchers.length != 0 ? activeTrains.length / activeDispatchers.length : 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
medTimetableCount() {
|
||||||
|
const scheduledTrainsArr = this.mainStore.activeSceneryList
|
||||||
|
.reduce<number[]>((acc, sc) => {
|
||||||
|
if (sc.region != this.mainStore.region.id) return acc;
|
||||||
|
|
||||||
|
acc.push(sc.scheduledTrainCount.all);
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, [])
|
||||||
|
.sort((a, b) => Math.sign(a - b));
|
||||||
|
|
||||||
|
if (scheduledTrainsArr.length == 0) return 0;
|
||||||
|
|
||||||
|
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() {
|
||||||
|
return this.mainStore.allStationInfo
|
||||||
|
.filter(
|
||||||
|
(st) =>
|
||||||
|
st.onlineInfo?.dispatcherId != -1 &&
|
||||||
|
st.onlineInfo?.region == this.mainStore.region.id &&
|
||||||
|
st.generalInfo?.routes
|
||||||
|
)
|
||||||
|
.reduce(
|
||||||
|
(acc, st) => {
|
||||||
|
const { routes } = st.generalInfo!;
|
||||||
|
|
||||||
|
if (
|
||||||
|
routes.single.filter((r) => !r.isInternal).length > 0 &&
|
||||||
|
routes.double.filter((r) => !r.isInternal).length > 0
|
||||||
|
) {
|
||||||
|
acc.crossTrack++;
|
||||||
|
|
||||||
|
if (
|
||||||
|
routes.single.some((r) => r.isElectric) &&
|
||||||
|
routes.double.some((r) => r.isElectric)
|
||||||
|
)
|
||||||
|
acc.crossTrackElectric++;
|
||||||
|
}
|
||||||
|
|
||||||
|
[...routes.single, ...routes.double].forEach((r) => {
|
||||||
|
if (r.isInternal) return;
|
||||||
|
|
||||||
|
acc[r.routeTracks == 2 ? 'twoWay' : 'oneWay'] += 1;
|
||||||
|
if (r.isElectric) acc[r.routeTracks == 2 ? 'twoWayElectric' : 'oneWayElectric'] += 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
oneWay: 0,
|
||||||
|
oneWayElectric: 0,
|
||||||
|
twoWay: 0,
|
||||||
|
twoWayElectric: 0,
|
||||||
|
crossTrack: 0,
|
||||||
|
crossTrackElectric: 0
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
spawnCount() {
|
||||||
|
return this.mainStore.activeSceneryList.reduce(
|
||||||
|
(acc, scenery) => {
|
||||||
|
if (scenery.region != this.mainStore.region.id) return acc;
|
||||||
|
|
||||||
|
scenery.spawns.forEach((spawn) => {
|
||||||
|
if (/EZT|POS|OSOB/i.test(spawn.spawnName)) acc['passenger'] += 1;
|
||||||
|
if (/TOW/i.test(spawn.spawnName)) acc['freight'] += 1;
|
||||||
|
if (/LUZ|SM/i.test(spawn.spawnName)) acc['loco'] += 1;
|
||||||
|
if (/ALL/i.test(spawn.spawnName)) acc['all'] += 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{ passenger: 0, freight: 0, loco: 0, all: 0 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.separator {
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
margin: 0.5em 0;
|
||||||
|
background-color: #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.station-stats {
|
||||||
|
text-align: center;
|
||||||
|
color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
text-wrap: pretty;
|
||||||
|
gap: 0.25em;
|
||||||
|
margin-top: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.u-factor {
|
||||||
|
[data-factor-low='true'] {
|
||||||
|
color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-factor-mediocre='true'] {
|
||||||
|
color: lightgreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-factor-high='true'] {
|
||||||
|
color: greenyellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-factor-highest='true'] {
|
||||||
|
color: rgb(22, 245, 22);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="station_table">
|
<section class="station_table">
|
||||||
<transition name="status-anim" mode="out-in">
|
<Loading
|
||||||
<div class="table_wrapper" :key="apiStore.dataStatuses.connection">
|
v-if="apiStore.dataStatuses.connection == Status.Loading && displayedStations.length == 0"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="table_wrapper" v-else-if="displayedStations.length > 0">
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -51,7 +54,7 @@
|
|||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr
|
<tr
|
||||||
v-for="station in stations"
|
v-for="station in displayedStations"
|
||||||
: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)"
|
||||||
@@ -120,8 +123,8 @@
|
|||||||
<b
|
<b
|
||||||
v-if="apiStore.donatorsData.includes(station.onlineInfo.dispatcherName)"
|
v-if="apiStore.donatorsData.includes(station.onlineInfo.dispatcherName)"
|
||||||
@click.stop="openDonationModal"
|
@click.stop="openDonationModal"
|
||||||
data-popup-key="DonatorPopUp"
|
data-tooltip-type="DonatorTooltip"
|
||||||
:data-popup-content="$t('donations.dispatcher-message')"
|
:data-tooltip-content="$t('donations.dispatcher-message')"
|
||||||
>
|
>
|
||||||
<img src="/images/icon-diamond.svg" alt="" />
|
<img src="/images/icon-diamond.svg" alt="" />
|
||||||
{{ station.onlineInfo.dispatcherName }}
|
{{ station.onlineInfo.dispatcherName }}
|
||||||
@@ -143,9 +146,7 @@
|
|||||||
)
|
)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
{{
|
{{ station.onlineInfo.dispatcherExp < 2 ? 'L' : station.onlineInfo.dispatcherExp }}
|
||||||
station.onlineInfo.dispatcherExp < 2 ? 'L' : station.onlineInfo.dispatcherExp
|
|
||||||
}}
|
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
@@ -206,8 +207,8 @@
|
|||||||
$t('sceneries.info.control-type') +
|
$t('sceneries.info.control-type') +
|
||||||
$t(`controls.${station.generalInfo?.controlType}`)
|
$t(`controls.${station.generalInfo?.controlType}`)
|
||||||
"
|
"
|
||||||
v-html="getControlTypeAbbrev(station.generalInfo.controlType)"
|
|
||||||
>
|
>
|
||||||
|
{{ $t(`controls.abbrevs.${station.generalInfo.controlType}`) }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<img
|
<img
|
||||||
@@ -246,8 +247,15 @@
|
|||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="station-users" :class="{ inactive: !station.onlineInfo }">
|
<td
|
||||||
<span class="text--primary">{{ station.onlineInfo?.currentUsers ?? '-' }}</span>
|
class="station-users"
|
||||||
|
:class="{ inactive: !station.onlineInfo }"
|
||||||
|
data-tooltip-type="UsersTooltip"
|
||||||
|
:data-tooltip-content="JSON.stringify(station.onlineInfo?.stationTrains ?? [])"
|
||||||
|
>
|
||||||
|
<span class="text--primary">{{
|
||||||
|
station.onlineInfo?.stationTrains?.length ?? '-'
|
||||||
|
}}</span>
|
||||||
/
|
/
|
||||||
<span class="text--primary">{{ station.onlineInfo?.maxUsers ?? '-' }}</span>
|
<span class="text--primary">{{ station.onlineInfo?.maxUsers ?? '-' }}</span>
|
||||||
</td>
|
</td>
|
||||||
@@ -256,7 +264,12 @@
|
|||||||
<span>{{ station.onlineInfo?.dispatcherRate ?? '-' }}</span>
|
<span>{{ station.onlineInfo?.dispatcherRate ?? '-' }}</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="station-spawns" :class="{ inactive: !station.onlineInfo }">
|
<td
|
||||||
|
class="station-spawns"
|
||||||
|
:class="{ inactive: !station.onlineInfo }"
|
||||||
|
data-tooltip-type="SpawnsTooltip"
|
||||||
|
:data-tooltip-content="JSON.stringify(station.onlineInfo?.spawns ?? [])"
|
||||||
|
>
|
||||||
<span>{{ station.onlineInfo?.spawns.length ?? '-' }}</span>
|
<span>{{ station.onlineInfo?.spawns.length ?? '-' }}</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
@@ -286,45 +299,32 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<Loading
|
|
||||||
v-if="apiStore.dataStatuses.connection == Status.Loading && stations.length == 0"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="no-stations" v-else-if="stations.length == 0">
|
|
||||||
{{ $t('sceneries.no-stations') }}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="no-stations" v-else>
|
||||||
|
{{ $t('sceneries.no-stations') }} (region: <b>{{ mainStore.region.name }}</b
|
||||||
|
>)
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, PropType } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import dateMixin from '../../mixins/dateMixin';
|
import dateMixin from '../../mixins/dateMixin';
|
||||||
import stationInfoMixin from '../../mixins/stationInfoMixin';
|
|
||||||
import styleMixin from '../../mixins/styleMixin';
|
import styleMixin from '../../mixins/styleMixin';
|
||||||
import Station from '../../scripts/interfaces/Station';
|
|
||||||
import { useStationFiltersStore } from '../../store/stationFiltersStore';
|
import { useStationFiltersStore } from '../../store/stationFiltersStore';
|
||||||
import { useMainStore } from '../../store/mainStore';
|
import { useMainStore } from '../../store/mainStore';
|
||||||
import Loading from '../Global/Loading.vue';
|
import Loading from '../Global/Loading.vue';
|
||||||
import { HeadIdsTypes, headIconsIds, headIds } from '../../scripts/data/stationHeaderNames';
|
import { HeadIdsTypes, headIconsIds, headIds } from '../../scripts/data/stationHeaderNames';
|
||||||
import StationStatusBadge from '../Global/StationStatusBadge.vue';
|
import StationStatusBadge from '../Global/StationStatusBadge.vue';
|
||||||
import { Status } from '../../typings/common';
|
import { Station, Status } from '../../typings/common';
|
||||||
import { useApiStore } from '../../store/apiStore';
|
import { useApiStore } from '../../store/apiStore';
|
||||||
import { usePopupStore } from '../../store/popupStore';
|
import { useTooltipStore } from '../../store/tooltipStore';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
|
||||||
stations: {
|
|
||||||
type: Array as PropType<Station[]>,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
emits: ['toggleDonationModal'],
|
emits: ['toggleDonationModal'],
|
||||||
components: { Loading, StationStatusBadge },
|
components: { Loading, StationStatusBadge },
|
||||||
mixins: [styleMixin, dateMixin, stationInfoMixin],
|
mixins: [styleMixin, dateMixin],
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
headIconsIds,
|
headIconsIds,
|
||||||
@@ -335,13 +335,17 @@ export default defineComponent({
|
|||||||
computed: {
|
computed: {
|
||||||
sorterActive() {
|
sorterActive() {
|
||||||
return this.stationFiltersStore.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 popupStore = usePopupStore();
|
const tooltipStore = useTooltipStore();
|
||||||
|
|
||||||
const stationFiltersStore = useStationFiltersStore();
|
const stationFiltersStore = useStationFiltersStore();
|
||||||
|
|
||||||
@@ -350,16 +354,18 @@ export default defineComponent({
|
|||||||
stationFiltersStore,
|
stationFiltersStore,
|
||||||
mainStore,
|
mainStore,
|
||||||
apiStore,
|
apiStore,
|
||||||
popupStore
|
tooltipStore
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
setScenery(name: string) {
|
setScenery(name: string) {
|
||||||
const station = this.stations.find((station) => station.name === name);
|
const station = this.displayedStations.find((station) => station.name === name);
|
||||||
|
|
||||||
if (!station) return;
|
if (!station) return;
|
||||||
|
|
||||||
this.lastSelectedStationName = station.name;
|
this.lastSelectedStationName = station.name;
|
||||||
|
this.tooltipStore.hide();
|
||||||
|
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
name: 'SceneryView',
|
name: 'SceneryView',
|
||||||
@@ -373,7 +379,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.tooltipStore.hide();
|
||||||
},
|
},
|
||||||
|
|
||||||
openForumSite(e: Event, url: string | undefined) {
|
openForumSite(e: Event, url: string | undefined) {
|
||||||
@@ -398,23 +404,11 @@ export default defineComponent({
|
|||||||
|
|
||||||
$rowCol: #424242;
|
$rowCol: #424242;
|
||||||
|
|
||||||
.change-anim {
|
.station_table {
|
||||||
&-enter-active,
|
height: 80vh;
|
||||||
&-leave-active {
|
min-height: 700px;
|
||||||
transition: opacity 100ms ease-in;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-enter,
|
|
||||||
&-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.table_wrapper {
|
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
height: 90vh;
|
|
||||||
min-height: 550px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-stations {
|
.no-stations {
|
||||||
@@ -422,9 +416,8 @@ $rowCol: #424242;
|
|||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
|
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
margin: 1em 0;
|
|
||||||
|
|
||||||
background: #333;
|
background: #1a1a1a;
|
||||||
}
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ export interface Filter {
|
|||||||
minOneWay: number;
|
minOneWay: number;
|
||||||
minTwoWayCatenary: number;
|
minTwoWayCatenary: number;
|
||||||
minTwoWay: number;
|
minTwoWay: number;
|
||||||
|
minVmax: number;
|
||||||
|
maxVmax: number;
|
||||||
'no-1track': boolean;
|
'no-1track': boolean;
|
||||||
'no-2track': boolean;
|
'no-2track': boolean;
|
||||||
'include-selected': boolean;
|
'include-selected': boolean;
|
||||||
@@ -53,4 +55,6 @@ export interface Filter {
|
|||||||
onlineFromHours: number;
|
onlineFromHours: number;
|
||||||
withActiveTimetables: boolean;
|
withActiveTimetables: boolean;
|
||||||
withoutActiveTimetables: boolean;
|
withoutActiveTimetables: boolean;
|
||||||
|
junction: boolean;
|
||||||
|
nonJunction: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-5
@@ -1,24 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="popup-content">
|
<div class="tooltip-content">
|
||||||
<span>{{ popupStore.currentPopupContent }}</span>
|
<span>{{ tooltipStore.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 { useTooltipStore } from '../../store/tooltipStore';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
popupStore: usePopupStore()
|
tooltipStore: useTooltipStore()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.popup-content {
|
.tooltip-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -1,25 +1,25 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="popup-content">
|
<div class="tooltip-content">
|
||||||
<img src="/images/icon-diamond.svg" alt="" />
|
<img src="/images/icon-diamond.svg" alt="" />
|
||||||
<span>{{ popupStore.currentPopupContent }}</span>
|
<span>{{ tooltipStore.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 { useTooltipStore } from '../../store/tooltipStore';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
popupStore: usePopupStore()
|
tooltipStore: useTooltipStore()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.popup-content {
|
.tooltip-content {
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
|
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<template>
|
||||||
|
<div class="tooltip-content" v-if="spawns.length != 0">
|
||||||
|
<span v-for="(spawn, i) in spawns">
|
||||||
|
<template v-if="i > 0"> | </template>
|
||||||
|
<b>{{ spawn.spawnName }}</b> ({{ spawn.spawnLength }}m)
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import { useTooltipStore } from '../../store/tooltipStore';
|
||||||
|
import { ScenerySpawn } from '../../typings/common';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tooltipStore: useTooltipStore()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
spawns() {
|
||||||
|
if (this.tooltipStore.content == '') return [];
|
||||||
|
|
||||||
|
const parsedSpawns = JSON.parse(this.tooltipStore.content) as ScenerySpawn[];
|
||||||
|
return parsedSpawns ?? [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tooltip-content {
|
||||||
|
width: 300px;
|
||||||
|
|
||||||
|
padding: 0.25em 0.5em;
|
||||||
|
border-radius: 0.25em;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
background-color: #1b1b1b;
|
||||||
|
box-shadow: 0 0 5px 2px #aaa;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
<template>
|
||||||
|
<div class="tooltip" v-show="tooltipStore.type" ref="preview">
|
||||||
|
<component v-if="tooltipStore.type" :is="tooltipStore.type" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import { useTooltipStore } from '../../store/tooltipStore';
|
||||||
|
import DonatorTooltip from './DonatorTooltip.vue';
|
||||||
|
import VehiclePreviewTooltip from './VehiclePreviewTooltip.vue';
|
||||||
|
import BaseTooltip from './BaseTooltip.vue';
|
||||||
|
import SpawnsTooltip from './SpawnsTooltip.vue';
|
||||||
|
import UsersTooltip from './UsersTooltip.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: { DonatorTooltip, VehiclePreviewTooltip, BaseTooltip, SpawnsTooltip, UsersTooltip },
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tooltipStore: useTooltipStore()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
'tooltipStore.mousePos': {
|
||||||
|
deep: true,
|
||||||
|
// [x, y]
|
||||||
|
handler(val: [number, number]) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
const previewEl = this.$refs['preview'] as HTMLElement;
|
||||||
|
const clientWidth = document.body.clientWidth;
|
||||||
|
const boxWidth = previewEl.getBoundingClientRect().width;
|
||||||
|
|
||||||
|
let translateX = '0',
|
||||||
|
translateY = '30px';
|
||||||
|
|
||||||
|
if (clientWidth < 500) {
|
||||||
|
previewEl.style.left = '50%';
|
||||||
|
translateX = '-50%';
|
||||||
|
} else if (val[0] <= boxWidth / 2) {
|
||||||
|
previewEl.style.left = '0';
|
||||||
|
translateX = '0px';
|
||||||
|
} else if (val[0] >= clientWidth - boxWidth / 2) {
|
||||||
|
previewEl.style.left = '100%';
|
||||||
|
translateX = '-100%';
|
||||||
|
} else {
|
||||||
|
previewEl.style.left = `${val[0]}px`;
|
||||||
|
translateX = '-50%';
|
||||||
|
}
|
||||||
|
|
||||||
|
previewEl.style.top = `${val[1]}px`;
|
||||||
|
|
||||||
|
const isOutside =
|
||||||
|
val[1] + previewEl.getBoundingClientRect().height + 30 >=
|
||||||
|
window.innerHeight + window.scrollY;
|
||||||
|
|
||||||
|
if (isOutside) translateY = 'calc(-100% - 30px)';
|
||||||
|
previewEl.style.transform = `translate(${translateX}, ${translateY})`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.tooltip {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 250;
|
||||||
|
max-width: 400px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<template>
|
||||||
|
<div class="tooltip-content" v-if="trains.length != 0">
|
||||||
|
<span v-for="(train, i) in trains">
|
||||||
|
<template v-if="i > 0"> | </template>
|
||||||
|
<b>{{ train.trainNo }}</b> {{ train.driverName }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import { useTooltipStore } from '../../store/tooltipStore';
|
||||||
|
import { StationTrain } from '../../typings/common';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tooltipStore: useTooltipStore()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
trains() {
|
||||||
|
if (this.tooltipStore.content == '') return [];
|
||||||
|
|
||||||
|
const parsedTrains = JSON.parse(this.tooltipStore.content) as StationTrain[];
|
||||||
|
return (parsedTrains ?? []).sort((a, b) => a.trainNo - b.trainNo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tooltip-content {
|
||||||
|
width: 300px;
|
||||||
|
|
||||||
|
padding: 0.25em 0.5em;
|
||||||
|
border-radius: 0.25em;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
background-color: #1b1b1b;
|
||||||
|
box-shadow: 0 0 5px 2px #aaa;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
+20
-11
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="popup-content">
|
<div class="tooltip-content">
|
||||||
<div v-if="imageState == 'loading'" class="loading-info">
|
<div v-if="imageState == 'loading'" class="loading-info">
|
||||||
{{ $t('vehicle-preview.loading') }}
|
{{ $t('vehicle-preview.loading') }}
|
||||||
</div>
|
</div>
|
||||||
@@ -7,30 +7,31 @@
|
|||||||
<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="tooltipStore.type"
|
||||||
@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/${tooltipStore.content}--300px.jpg`"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="vehicle-name" v-if="imageState != 'error'">
|
<div v-if="imageState == 'error'" class="error-placeholder"></div>
|
||||||
{{ popupStore.currentPopupContent.replace(/_/g, ' ') }}
|
|
||||||
|
<div class="vehicle-name">
|
||||||
|
{{ tooltipStore.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 { useTooltipStore } from '../../store/tooltipStore';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
popupStore: usePopupStore(),
|
tooltipStore: useTooltipStore(),
|
||||||
imageState: 'loading'
|
imageState: 'loading'
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -39,6 +40,12 @@ export default defineComponent({
|
|||||||
this.imageState = 'loading';
|
this.imageState = 'loading';
|
||||||
},
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
'tooltipStore.type'(prev, val) {
|
||||||
|
if (prev != val) this.imageState = 'loading';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
onImageLoad() {
|
onImageLoad() {
|
||||||
this.imageState = 'loaded';
|
this.imageState = 'loaded';
|
||||||
@@ -54,9 +61,7 @@ export default defineComponent({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.popup-content {
|
.tooltip-content {
|
||||||
// min-w-[300px] min-h-[200px] p-2 bg-slate-800 rounded-md
|
|
||||||
|
|
||||||
width: 300px;
|
width: 300px;
|
||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
background-color: #333;
|
background-color: #333;
|
||||||
@@ -83,4 +88,8 @@ img {
|
|||||||
color: #ccc;
|
color: #ccc;
|
||||||
text-wrap: wrap;
|
text-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.error-placeholder {
|
||||||
|
height: 176px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
<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">
|
||||||
|
<div>
|
||||||
<b class="warning-timeout" v-if="train.isTimeout" :title="$t('trains.timeout')">?</b>
|
<b class="warning-timeout" v-if="train.isTimeout" :title="$t('trains.timeout')">?</b>
|
||||||
<span class="timetable-id" v-if="train.timetableData">
|
<span class="timetable-id" v-if="train.timetableData">
|
||||||
#{{ train.timetableData.timetableId }}
|
#{{ train.timetableData.timetableId }}
|
||||||
@@ -11,10 +12,18 @@
|
|||||||
class="timetable-warnings"
|
class="timetable-warnings"
|
||||||
v-if="train.timetableData?.TWR || train.timetableData?.SKR"
|
v-if="train.timetableData?.TWR || train.timetableData?.SKR"
|
||||||
>
|
>
|
||||||
<span class="train-badge twr" v-if="train.timetableData?.TWR" :title="$t('general.TWR')">
|
<span
|
||||||
|
class="train-badge twr"
|
||||||
|
v-if="train.timetableData?.TWR"
|
||||||
|
:title="$t('general.TWR')"
|
||||||
|
>
|
||||||
TWR
|
TWR
|
||||||
</span>
|
</span>
|
||||||
<span class="train-badge skr" v-if="train.timetableData?.SKR" :title="$t('general.SKR')">
|
<span
|
||||||
|
class="train-badge skr"
|
||||||
|
v-if="train.timetableData?.SKR"
|
||||||
|
:title="$t('general.SKR')"
|
||||||
|
>
|
||||||
SKR
|
SKR
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -36,22 +45,27 @@
|
|||||||
<div class="train-driver">
|
<div class="train-driver">
|
||||||
<b
|
<b
|
||||||
v-if="apiStore.donatorsData.includes(train.driverName)"
|
v-if="apiStore.donatorsData.includes(train.driverName)"
|
||||||
data-popup-key="DonatorPopUp"
|
data-tooltip-type="DonatorTooltip"
|
||||||
:data-popup-content="$t('donations.driver-message')"
|
:data-tooltip-content="$t('donations.driver-message')"
|
||||||
>
|
>
|
||||||
{{ train.driverName }}
|
{{ train.driverName }}
|
||||||
<img src="/images/icon-diamond.svg" alt="donator diamond icon" />
|
<img src="/images/icon-diamond.svg" alt="donator diamond icon" />
|
||||||
</b>
|
</b>
|
||||||
|
|
||||||
<span v-else>{{ train.driverName }}</span>
|
<span v-else>{{ train.driverName }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button
|
<div v-if="extended">
|
||||||
class="btn--image btn--action btn-timetable"
|
<button class="btn-timetable btn--image btn--action" @click="navigateToJournal">
|
||||||
@click="navigateToJournal"
|
<img src="/images/icon-train.svg" alt="train icon" />
|
||||||
v-if="extended"
|
<span>
|
||||||
>
|
|
||||||
<img src="/images/icon-train.svg" alt="" />
|
|
||||||
{{ $t('trains.journal-button') }}
|
{{ $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>
|
||||||
@@ -60,8 +74,8 @@
|
|||||||
<strong>{{ train.timetableData.route.replace('|', ' - ') }}</strong>
|
<strong>{{ train.timetableData.route.replace('|', ' - ') }}</strong>
|
||||||
<span
|
<span
|
||||||
v-if="getSceneriesWithComments(train.timetableData).length > 0"
|
v-if="getSceneriesWithComments(train.timetableData).length > 0"
|
||||||
data-popup-key="TrainCommentsPopUp"
|
data-tooltip-type="BaseTooltip"
|
||||||
:data-popup-content="`${$t('trains.timetable-comments')} (${getSceneriesWithComments(
|
:data-tooltip-content="`${$t('trains.timetable-comments')} (${getSceneriesWithComments(
|
||||||
train.timetableData
|
train.timetableData
|
||||||
)})`"
|
)})`"
|
||||||
>
|
>
|
||||||
@@ -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>
|
||||||
@@ -132,13 +163,12 @@
|
|||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import styleMixin from '../../mixins/styleMixin';
|
import styleMixin from '../../mixins/styleMixin';
|
||||||
import trainInfoMixin from '../../mixins/trainInfoMixin';
|
import trainInfoMixin from '../../mixins/trainInfoMixin';
|
||||||
import Train from '../../scripts/interfaces/Train';
|
|
||||||
import ProgressBar from '../Global/ProgressBar.vue';
|
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';
|
||||||
|
import { Train } from '../../typings/common';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
mixins: [trainInfoMixin, styleMixin, modalTrainMixin],
|
mixins: [trainInfoMixin, styleMixin, modalTrainMixin],
|
||||||
@@ -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;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5em;
|
||||||
|
|
||||||
|
& > div {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
||||||
gap: 0.25em;
|
gap: 0.25em;
|
||||||
margin-right: 1.5em;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.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>
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<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,18 +13,28 @@ 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 '../../typings/common';
|
||||||
|
|
||||||
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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
chosenTrain(train: Train | undefined) {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
|
if (train) {
|
||||||
|
const contentEl = this.$refs['content'] as HTMLElement;
|
||||||
contentEl.focus();
|
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;
|
||||||
@@ -82,7 +71,7 @@ export default defineComponent({
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal_background {
|
.modal-background {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@@ -94,14 +83,14 @@ export default defineComponent({
|
|||||||
background-color: rgba(0, 0, 0, 0.55);
|
background-color: rgba(0, 0, 0, 0.55);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal_content {
|
.modal-content {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
|
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
|
|
||||||
width: 95vw;
|
width: 95vw;
|
||||||
max-height: 96vh;
|
max-height: 95vh;
|
||||||
|
|
||||||
background-color: #1a1a1a;
|
background-color: #1a1a1a;
|
||||||
box-shadow: 0 0 15px 10px #0e0e0e;
|
box-shadow: 0 0 15px 10px #0e0e0e;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<button class="filter-button btn--filled btn--image" @click="toggleShowOptions" ref="button">
|
<button class="filter-button btn--filled btn--image" @click="toggleShowOptions" ref="button">
|
||||||
<img src="/images/icon-filter2.svg" alt="Open filters icon" />
|
<img src="/images/icon-filter2.svg" alt="Open filters icon" />
|
||||||
{{ $t('options.filters') }} [F]
|
[F] {{ $t('options.filters') }}
|
||||||
<span class="active-indicator" v-if="currentOptionsActive"></span>
|
<span class="active-indicator" v-if="currentOptionsActive"></span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -81,7 +81,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="filter-actions">
|
<div class="filter-actions">
|
||||||
<div></div>
|
|
||||||
<button class="btn--action" @click="resetAllFilters">
|
<button class="btn--action" @click="resetAllFilters">
|
||||||
{{ $t('options.filter-reset') }}
|
{{ $t('options.filter-reset') }}
|
||||||
</button>
|
</button>
|
||||||
@@ -223,9 +222,6 @@ export default defineComponent({
|
|||||||
|
|
||||||
.filter-actions {
|
.filter-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.5em;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
|
|
||||||
> * {
|
> * {
|
||||||
|
|||||||
@@ -81,11 +81,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, PropType } from 'vue';
|
import { defineComponent, PropType } from 'vue';
|
||||||
import dateMixin from '../../mixins/dateMixin';
|
import dateMixin from '../../mixins/dateMixin';
|
||||||
import Train from '../../scripts/interfaces/Train';
|
|
||||||
import StopLabel from './StopLabel.vue';
|
import StopLabel from './StopLabel.vue';
|
||||||
import StockList from '../Global/StockList.vue';
|
import StockList from '../Global/StockList.vue';
|
||||||
import { useMainStore } from '../../store/mainStore';
|
import { useMainStore } from '../../store/mainStore';
|
||||||
import { useApiStore } from '../../store/apiStore';
|
import { useApiStore } from '../../store/apiStore';
|
||||||
|
import { Train } from '../../typings/common';
|
||||||
|
|
||||||
export interface TrainScheduleStop {
|
export interface TrainScheduleStop {
|
||||||
nameHtml: string;
|
nameHtml: string;
|
||||||
|
|||||||
@@ -30,11 +30,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, inject, PropType, Ref } from 'vue';
|
import { defineComponent, inject, PropType, Ref } from 'vue';
|
||||||
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
||||||
import Train from '../../scripts/interfaces/Train';
|
|
||||||
import { useMainStore } from '../../store/mainStore';
|
import { useMainStore } from '../../store/mainStore';
|
||||||
import Loading from '../Global/Loading.vue';
|
import Loading from '../Global/Loading.vue';
|
||||||
import TrainInfo from './TrainInfo.vue';
|
import TrainInfo from './TrainInfo.vue';
|
||||||
import { Status } from '../../typings/common';
|
import { Status, Train } from '../../typings/common';
|
||||||
import { useApiStore } from '../../store/apiStore';
|
import { useApiStore } from '../../store/apiStore';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
"timetables",
|
"timetables",
|
||||||
"reality",
|
"reality",
|
||||||
"package-access",
|
"package-access",
|
||||||
|
"station-type",
|
||||||
"access",
|
"access",
|
||||||
"control",
|
"control",
|
||||||
"blockades",
|
"blockades",
|
||||||
@@ -61,6 +62,20 @@
|
|||||||
"value": false,
|
"value": false,
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "junction",
|
||||||
|
"name": "junction",
|
||||||
|
"section": "station-type",
|
||||||
|
"value": true,
|
||||||
|
"defaultValue": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "nonJunction",
|
||||||
|
"name": "nonJunction",
|
||||||
|
"section": "station-type",
|
||||||
|
"value": true,
|
||||||
|
"defaultValue": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "SPK",
|
"id": "SPK",
|
||||||
"name": "SPK",
|
"name": "SPK",
|
||||||
@@ -265,6 +280,7 @@
|
|||||||
"name": "minLevel",
|
"name": "minLevel",
|
||||||
"minRange": 0,
|
"minRange": 0,
|
||||||
"maxRange": 20,
|
"maxRange": 20,
|
||||||
|
"step": 1,
|
||||||
"value": 0,
|
"value": 0,
|
||||||
"defaultValue": 0
|
"defaultValue": 0
|
||||||
},
|
},
|
||||||
@@ -273,14 +289,34 @@
|
|||||||
"name": "maxLevel",
|
"name": "maxLevel",
|
||||||
"minRange": 0,
|
"minRange": 0,
|
||||||
"maxRange": 20,
|
"maxRange": 20,
|
||||||
|
"step": 1,
|
||||||
"value": 20,
|
"value": 20,
|
||||||
"defaultValue": 20
|
"defaultValue": 20
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "min-vmax",
|
||||||
|
"name": "minVmax",
|
||||||
|
"minRange": 0,
|
||||||
|
"maxRange": 200,
|
||||||
|
"step": 10,
|
||||||
|
"value": 0,
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "max-vmax",
|
||||||
|
"name": "maxVmax",
|
||||||
|
"minRange": 0,
|
||||||
|
"maxRange": 200,
|
||||||
|
"step": 10,
|
||||||
|
"value": 200,
|
||||||
|
"defaultValue": 200
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "routes-1t-cat",
|
"id": "routes-1t-cat",
|
||||||
"name": "minOneWayCatenary",
|
"name": "minOneWayCatenary",
|
||||||
"minRange": 0,
|
"minRange": 0,
|
||||||
"maxRange": 5,
|
"maxRange": 5,
|
||||||
|
"step": 1,
|
||||||
"value": 0,
|
"value": 0,
|
||||||
"defaultValue": 0
|
"defaultValue": 0
|
||||||
},
|
},
|
||||||
@@ -289,6 +325,7 @@
|
|||||||
"name": "minOneWay",
|
"name": "minOneWay",
|
||||||
"minRange": 0,
|
"minRange": 0,
|
||||||
"maxRange": 5,
|
"maxRange": 5,
|
||||||
|
"step": 1,
|
||||||
"value": 0,
|
"value": 0,
|
||||||
"defaultValue": 0
|
"defaultValue": 0
|
||||||
},
|
},
|
||||||
@@ -297,6 +334,7 @@
|
|||||||
"name": "minTwoWayCatenary",
|
"name": "minTwoWayCatenary",
|
||||||
"minRange": 0,
|
"minRange": 0,
|
||||||
"maxRange": 5,
|
"maxRange": 5,
|
||||||
|
"step": 1,
|
||||||
"value": 0,
|
"value": 0,
|
||||||
"defaultValue": 0
|
"defaultValue": 0
|
||||||
},
|
},
|
||||||
@@ -305,6 +343,7 @@
|
|||||||
"name": "minTwoWay",
|
"name": "minTwoWay",
|
||||||
"minRange": 0,
|
"minRange": 0,
|
||||||
"maxRange": 5,
|
"maxRange": 5,
|
||||||
|
"step": 1,
|
||||||
"value": 0,
|
"value": 0,
|
||||||
"defaultValue": 0
|
"defaultValue": 0
|
||||||
}
|
}
|
||||||
|
|||||||
+41
-10
@@ -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 update!",
|
||||||
|
"confirm": "ROGER THAT!",
|
||||||
|
"no-data": "No data about the latest app update has been found",
|
||||||
|
"info-1": "This changelog will be available to see once again after clicking the version number in the footer",
|
||||||
|
"info-2": "The full app changelog available on <a href='https://github.com/Spythere/stacjownik' target='_blank'>the project's GitHub</a>"
|
||||||
|
},
|
||||||
"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! :/"
|
||||||
@@ -80,7 +81,20 @@
|
|||||||
"ręczne+SCS": "manual + SCS",
|
"ręczne+SCS": "manual + SCS",
|
||||||
"mechaniczne": "levers (mechanical)",
|
"mechaniczne": "levers (mechanical)",
|
||||||
"mechaniczne+SPK": "levers + SPK",
|
"mechaniczne+SPK": "levers + SPK",
|
||||||
"mechaniczne+SCS": "levers + SCS"
|
"mechaniczne+SCS": "levers + SCS",
|
||||||
|
|
||||||
|
"abbrevs": {
|
||||||
|
"SPK": "SPK",
|
||||||
|
"SCS": "SCS",
|
||||||
|
"SCS-SPK": "S/S",
|
||||||
|
"SPE": "SPE",
|
||||||
|
"ręczne": "R",
|
||||||
|
"ręczne+SPK": "R",
|
||||||
|
"ręczne+SCS": "R",
|
||||||
|
"mechaniczne": "M",
|
||||||
|
"mechaniczne+SPK": "M",
|
||||||
|
"mechaniczne+SCS": "M"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"online": "UNTIL ",
|
"online": "UNTIL ",
|
||||||
@@ -160,6 +174,7 @@
|
|||||||
|
|
||||||
"sections": {
|
"sections": {
|
||||||
"quick": "QUICK FILTERS",
|
"quick": "QUICK FILTERS",
|
||||||
|
"station-type": "STATION TYPE",
|
||||||
"reality": "SCENERY REALITY",
|
"reality": "SCENERY REALITY",
|
||||||
"package-access": "IN-GAME AVAILABILITY",
|
"package-access": "IN-GAME AVAILABILITY",
|
||||||
"access": "GENERAL AVAILABILITY",
|
"access": "GENERAL AVAILABILITY",
|
||||||
@@ -168,7 +183,8 @@
|
|||||||
"addons": "ADDITIONAL PROGRAMS",
|
"addons": "ADDITIONAL PROGRAMS",
|
||||||
"blockades": "BLOCK SIGNALLING",
|
"blockades": "BLOCK SIGNALLING",
|
||||||
"status": "ONLINE STATUS",
|
"status": "ONLINE STATUS",
|
||||||
"timetables": "ACTIVE TIMETABLES"
|
"timetables": "ACTIVE TIMETABLES",
|
||||||
|
"spawns": "OPEN SPAWNS"
|
||||||
},
|
},
|
||||||
|
|
||||||
"all-available": "ALL AVAILABLE",
|
"all-available": "ALL AVAILABLE",
|
||||||
@@ -218,14 +234,20 @@
|
|||||||
"withActiveTimetables": "ACTIVE",
|
"withActiveTimetables": "ACTIVE",
|
||||||
"withoutActiveTimetables": "NO ACTIVE",
|
"withoutActiveTimetables": "NO ACTIVE",
|
||||||
|
|
||||||
|
"junction": "JUNCTIONS",
|
||||||
|
"nonJunction": "OTHER",
|
||||||
|
|
||||||
"sliders": {
|
"sliders": {
|
||||||
"min-lvl": "MIN. REQUIRED DISPATCHER LEVEL",
|
"min-lvl": "MIN. REQUIRED DISPATCHER LEVEL",
|
||||||
"max-lvl": "MAX. REQUIRED DISPATCHER LEVEL",
|
"max-lvl": "MAX. REQUIRED DISPATCHER LEVEL",
|
||||||
|
"min-vmax": "MIN. SCENERY ROUTE SPEED",
|
||||||
|
"max-vmax": "MAX. SCENERY ROUTE SPEED",
|
||||||
"routes-1t-cat": "MIN. CATENARY SINGLE TRACK ROUTES",
|
"routes-1t-cat": "MIN. CATENARY SINGLE TRACK ROUTES",
|
||||||
"routes-1t-other": "MIN. OTHER SINGLE TRACK ROUTES",
|
"routes-1t-other": "MIN. OTHER SINGLE TRACK ROUTES",
|
||||||
"routes-2t-cat": "MIN. CATENARY DOUBLE TRACK ROUTES",
|
"routes-2t-cat": "MIN. CATENARY DOUBLE TRACK ROUTES",
|
||||||
"routes-2t-other": "MIN. OTHER DOUBLE TRACK ROUTES"
|
"routes-2t-other": "MIN. OTHER DOUBLE TRACK ROUTES"
|
||||||
},
|
},
|
||||||
|
|
||||||
"authors-search": "SEARCH BY AUTHOR NAME (other filters apply):",
|
"authors-search": "SEARCH BY AUTHOR NAME (other filters apply):",
|
||||||
"authors-placeholder": "Enter the author nickname...",
|
"authors-placeholder": "Enter the author nickname...",
|
||||||
"authors-button-title": "Search",
|
"authors-button-title": "Search",
|
||||||
@@ -278,6 +300,15 @@
|
|||||||
"no-stations": "No stations to show here!",
|
"no-stations": "No stations to show here!",
|
||||||
"scenery-search": "Search for scenery..."
|
"scenery-search": "Search for scenery..."
|
||||||
},
|
},
|
||||||
|
"station-stats": {
|
||||||
|
"u-factor": "U-factor",
|
||||||
|
"u-factor-tooltip": "(?) Current server traffic factor (driver count divided by dispatcher count)",
|
||||||
|
"med-timetable-count": "Median of scenery timetables:",
|
||||||
|
"single-track-count": "Single track routes:",
|
||||||
|
"double-track-count": "Double track routes:",
|
||||||
|
"cross-sceneries": "Cross-track sceneries (1-track <-> 2-track)",
|
||||||
|
"open-spawns": "Open spawns:"
|
||||||
|
},
|
||||||
"trains": {
|
"trains": {
|
||||||
"no-trains": "No trains to show here!",
|
"no-trains": "No trains to show here!",
|
||||||
"loading": "Loading train data...",
|
"loading": "Loading train data...",
|
||||||
@@ -366,7 +397,7 @@
|
|||||||
"minutes": "{value} min | {value} mins",
|
"minutes": "{value} min | {value} mins",
|
||||||
"seconds": "{value} s",
|
"seconds": "{value} s",
|
||||||
|
|
||||||
"stock-info": "EXTRA INFO",
|
"stock-info": "DETAILS",
|
||||||
"stock-length": "Length",
|
"stock-length": "Length",
|
||||||
"stock-mass": "Mass",
|
"stock-mass": "Mass",
|
||||||
"stock-max-speed": "Max. speed",
|
"stock-max-speed": "Max. speed",
|
||||||
|
|||||||
+43
-8
@@ -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!",
|
||||||
|
"confirm": "PRZYJĄŁEM!",
|
||||||
|
"no-data": "Nie znaleziono informacji o ostatnich zmianach w aplikacji",
|
||||||
|
"info-1": "Ten changelog będzie zawsze dostępny po kliknięciu numeru wersji w stopce strony",
|
||||||
|
"info-2": "Pełny changelog dostępny na <a href='https://github.com/Spythere/stacjownik' target='_blank'>GitHubie projektu</a>"
|
||||||
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"sceneries": "SCENERIE",
|
"sceneries": "SCENERIE",
|
||||||
"trains": "POCIĄGI",
|
"trains": "POCIĄGI",
|
||||||
@@ -70,7 +77,20 @@
|
|||||||
"ręczne+SCS": "ręczne z SCS",
|
"ręczne+SCS": "ręczne z SCS",
|
||||||
"mechaniczne": "mechaniczne",
|
"mechaniczne": "mechaniczne",
|
||||||
"mechaniczne+SPK": "mechaniczne z SPK",
|
"mechaniczne+SPK": "mechaniczne z SPK",
|
||||||
"mechaniczne+SCS": "mechaniczne z SCS"
|
"mechaniczne+SCS": "mechaniczne z SCS",
|
||||||
|
|
||||||
|
"abbrevs": {
|
||||||
|
"SPK": "SPK",
|
||||||
|
"SCS": "SCS",
|
||||||
|
"SCS-SPK": "S/S",
|
||||||
|
"SPE": "SPE",
|
||||||
|
"ręczne": "R",
|
||||||
|
"ręczne+SPK": "R",
|
||||||
|
"ręczne+SCS": "R",
|
||||||
|
"mechaniczne": "M",
|
||||||
|
"mechaniczne+SPK": "M",
|
||||||
|
"mechaniczne+SCS": "M"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"online": "DO ",
|
"online": "DO ",
|
||||||
@@ -88,8 +108,8 @@
|
|||||||
"filters": "FILTRY",
|
"filters": "FILTRY",
|
||||||
"donate": "WESPRZYJ",
|
"donate": "WESPRZYJ",
|
||||||
|
|
||||||
"search-button": "Szukaj",
|
"search-button": "SZUKAJ",
|
||||||
"reset-button": "Zresetuj",
|
"reset-button": "ZRESETUJ",
|
||||||
|
|
||||||
"sort-title": "SORTUJ WG:",
|
"sort-title": "SORTUJ WG:",
|
||||||
"filter-title": "FILTRUJ WG:",
|
"filter-title": "FILTRUJ WG:",
|
||||||
@@ -151,15 +171,17 @@
|
|||||||
|
|
||||||
"sections": {
|
"sections": {
|
||||||
"quick": "SZYBKIE FILTRY",
|
"quick": "SZYBKIE FILTRY",
|
||||||
|
"station-type": "RODZAJ STACJI",
|
||||||
"reality": "FIKCYJNOŚĆ SCENERII",
|
"reality": "FIKCYJNOŚĆ SCENERII",
|
||||||
"package-access": "DOSTĘPNOŚĆ W PACZCE",
|
"package-access": "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",
|
||||||
"addons": "DODATKOWE PROGRAMY",
|
"addons": "SZCZEGÓŁY",
|
||||||
"blockades": "BLOKADY LINIOWE",
|
"blockades": "BLOKADY LINIOWE",
|
||||||
"status": "STATUS ONLINE",
|
"status": "STATUS ONLINE",
|
||||||
"timetables": "AKTYWNE ROZKŁADY JAZDY"
|
"timetables": "AKTYWNE ROZKŁADY JAZDY",
|
||||||
|
"spawns": "OTWARTE SPAWNY"
|
||||||
},
|
},
|
||||||
|
|
||||||
"all-available": "WSZYSTKIE DOSTĘPNE",
|
"all-available": "WSZYSTKIE DOSTĘPNE",
|
||||||
@@ -202,16 +224,20 @@
|
|||||||
"semaphores": "KSZTAŁTOWA",
|
"semaphores": "KSZTAŁTOWA",
|
||||||
"mixed": "MIESZANA",
|
"mixed": "MIESZANA",
|
||||||
"historical": "HISTORYCZNA",
|
"historical": "HISTORYCZNA",
|
||||||
|
|
||||||
"free": "WOLNA",
|
"free": "WOLNA",
|
||||||
"occupied": "ZAJĘTA",
|
"occupied": "ZAJĘTA",
|
||||||
|
|
||||||
"withActiveTimetables": "AKTYWNE",
|
"withActiveTimetables": "AKTYWNE",
|
||||||
"withoutActiveTimetables": "BEZ AKTYWNYCH",
|
"withoutActiveTimetables": "BEZ AKTYWNYCH",
|
||||||
|
|
||||||
|
"junction": "WĘZŁOWE",
|
||||||
|
"nonJunction": "INNE",
|
||||||
|
|
||||||
"sliders": {
|
"sliders": {
|
||||||
"min-lvl": "MIN. WYMAGANY POZIOM DYŻURNEGO",
|
"min-lvl": "MIN. WYMAGANY POZIOM DYŻURNEGO",
|
||||||
"max-lvl": "MAKS. WYMAGANY POZIOM DYŻURNEGO",
|
"max-lvl": "MAKS. WYMAGANY POZIOM DYŻURNEGO",
|
||||||
|
"min-vmax": "MIN. PRĘDKOŚĆ SZLAKOWA",
|
||||||
|
"max-vmax": "MAKS. PRĘDKOŚĆ SZLAKOWA",
|
||||||
"routes-1t-cat": "SZLAKI JEDNOTOROWE ZELEKTR. (MINIMUM)",
|
"routes-1t-cat": "SZLAKI JEDNOTOROWE ZELEKTR. (MINIMUM)",
|
||||||
"routes-1t-other": "SZLAKI JEDNOTOROWE NIEZELEKTR. (MINIMUM)",
|
"routes-1t-other": "SZLAKI JEDNOTOROWE NIEZELEKTR. (MINIMUM)",
|
||||||
"routes-2t-cat": "SZLAKI DWUTOROWE ZELEKTR. (MINIMUM)",
|
"routes-2t-cat": "SZLAKI DWUTOROWE ZELEKTR. (MINIMUM)",
|
||||||
@@ -267,6 +293,15 @@
|
|||||||
"no-stations": "Brak stacji do wyświetlenia!",
|
"no-stations": "Brak stacji do wyświetlenia!",
|
||||||
"scenery-search": "Wyszukaj scenerię..."
|
"scenery-search": "Wyszukaj scenerię..."
|
||||||
},
|
},
|
||||||
|
"station-stats": {
|
||||||
|
"u-factor": "Współczynnik Ugla",
|
||||||
|
"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:",
|
||||||
|
"single-track-count": "Szlaki jednotorowe:",
|
||||||
|
"double-track-count": "Szlaki dwutorowe:",
|
||||||
|
"cross-sceneries": "Scenerie przejściowe (1-tor <-> 2-tor):",
|
||||||
|
"open-spawns": "Otwarte spawny:"
|
||||||
|
},
|
||||||
"trains": {
|
"trains": {
|
||||||
"no-trains": "Brak pociągów do wyświetlenia!",
|
"no-trains": "Brak pociągów do wyświetlenia!",
|
||||||
"loading": "Pobieranie danych o pociągach...",
|
"loading": "Pobieranie danych o pociągach...",
|
||||||
@@ -345,7 +380,7 @@
|
|||||||
"timetable-abandoned": "PORZUCONY",
|
"timetable-abandoned": "PORZUCONY",
|
||||||
"timetable-online-button": "RJ ONLINE",
|
"timetable-online-button": "RJ ONLINE",
|
||||||
|
|
||||||
"stock-info": "DODATKOWE INFORMACJE",
|
"stock-info": "SZCZEGÓŁY",
|
||||||
"stock-length": "Długość",
|
"stock-length": "Długość",
|
||||||
"stock-mass": "Masa",
|
"stock-mass": "Masa",
|
||||||
"stock-max-speed": "Prędkość maks.",
|
"stock-max-speed": "Prędkość maks.",
|
||||||
|
|||||||
+7
-2
@@ -5,10 +5,15 @@ import router from './router';
|
|||||||
import i18n from './i18n';
|
import i18n from './i18n';
|
||||||
|
|
||||||
import { createPinia } from 'pinia';
|
import { createPinia } from 'pinia';
|
||||||
import useCustomSW from './mixins/useCustomSW';
|
import { registerSW } from 'virtual:pwa-register';
|
||||||
|
|
||||||
// Service worker
|
// Service worker
|
||||||
useCustomSW();
|
registerSW({
|
||||||
|
immediate: true,
|
||||||
|
onNeedRefresh() {
|
||||||
|
console.log('Needs refresh!');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const clickOutsideDirective: Directive = {
|
const clickOutsideDirective: Directive = {
|
||||||
mounted(el, binding) {
|
mounted(el, binding) {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { TrainFilter, TrainFilterId } from '../components/TrainsView/typings';
|
import { TrainFilter, TrainFilterId } from '../components/TrainsView/typings';
|
||||||
import Train from '../scripts/interfaces/Train';
|
import { Train, TrainStop } from '../typings/common';
|
||||||
import { TrainStop } from '../store/typings';
|
|
||||||
|
|
||||||
function confirmedPercentage(stops: TrainStop[] | undefined) {
|
function confirmedPercentage(stops: TrainStop[] | undefined) {
|
||||||
if (!stops) return -1;
|
if (!stops) return -1;
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
import { defineComponent } from 'vue';
|
|
||||||
import { useApiStore } from '../store/apiStore';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
apiStore: useApiStore()
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
isDonator(name: string) {
|
|
||||||
return this.apiStore.donatorsData.includes(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import { defineComponent } from 'vue';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
data: () => ({
|
|
||||||
observer: null as IntersectionObserver | null,
|
|
||||||
observerTarget: null as Element | null
|
|
||||||
}),
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
mountObserver(actionFunction: () => void, target: Element) {
|
|
||||||
this.observer = new IntersectionObserver(
|
|
||||||
(entries) => {
|
|
||||||
if (entries[0].intersectionRatio > 0.5) actionFunction();
|
|
||||||
},
|
|
||||||
{ threshold: 0.2 }
|
|
||||||
);
|
|
||||||
|
|
||||||
this.observer.observe(target);
|
|
||||||
},
|
|
||||||
|
|
||||||
unmountObserver() {
|
|
||||||
if (!this.observerTarget) return;
|
|
||||||
|
|
||||||
this.observer?.unobserve(this.observerTarget);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,21 +1,15 @@
|
|||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { useMainStore } from '../store/mainStore';
|
import { useMainStore } from '../store/mainStore';
|
||||||
import { usePopupStore } from '../store/popupStore';
|
import { useTooltipStore } from '../store/tooltipStore';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
store: useMainStore(),
|
store: useMainStore(),
|
||||||
popupStore: usePopupStore()
|
tooltipStore: useTooltipStore()
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
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 +19,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
closeModal() {
|
closeModal() {
|
||||||
this.store.chosenModalTrainId = undefined;
|
this.store.chosenModalTrainId = undefined;
|
||||||
this.popupStore.currentPopupComponent = null;
|
this.tooltipStore.hide();
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
(this.store.modalLastClickedTarget as any)?.focus();
|
(this.store.modalLastClickedTarget as any)?.focus();
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
import { defineComponent } from 'vue';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
methods: {
|
|
||||||
getControlTypeAbbrev(controlType: string) {
|
|
||||||
switch (controlType) {
|
|
||||||
case 'mechaniczne':
|
|
||||||
return 'M';
|
|
||||||
case 'SCS-SPK':
|
|
||||||
return 'S/S';
|
|
||||||
case 'ręczne':
|
|
||||||
return 'R';
|
|
||||||
case 'mechaniczne+SPK':
|
|
||||||
return 'M';
|
|
||||||
case 'ręczne+SPK':
|
|
||||||
return 'R';
|
|
||||||
case 'mechaniczne+SCS':
|
|
||||||
return 'M';
|
|
||||||
default:
|
|
||||||
return controlType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import Train from '../scripts/interfaces/Train';
|
import { Train, TrainStop } from '../typings/common';
|
||||||
import { TrainStop } from '../store/typings';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
data: () => ({
|
data: () => ({
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
import { useRegisterSW } from 'virtual:pwa-register/vue';
|
|
||||||
|
|
||||||
export default () => {
|
|
||||||
const { needRefresh, updateServiceWorker, offlineReady } = useRegisterSW({
|
|
||||||
immediate: true
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
needRefresh,
|
|
||||||
updateServiceWorker,
|
|
||||||
offlineReady
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
import { Availability, ActiveScenery } from '../../store/typings';
|
|
||||||
import { StationRoutes } from './StationRoutes';
|
|
||||||
|
|
||||||
export default interface Station {
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
generalInfo?: {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { StationRoutesInfo } from '../../store/typings';
|
|
||||||
|
|
||||||
export interface StationRoutes {
|
|
||||||
single: StationRoutesInfo[];
|
|
||||||
double: StationRoutesInfo[];
|
|
||||||
|
|
||||||
singleElectrifiedNames: string[];
|
|
||||||
singleOtherNames: string[];
|
|
||||||
doubleElectrifiedNames: string[];
|
|
||||||
doubleOtherNames: string[];
|
|
||||||
sblNames: string[];
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
import { TrainStop } from '../../store/typings';
|
|
||||||
|
|
||||||
export default interface Train {
|
|
||||||
trainId: string;
|
|
||||||
|
|
||||||
mass: number;
|
|
||||||
length: number;
|
|
||||||
speed: number;
|
|
||||||
signal: string;
|
|
||||||
distance: number;
|
|
||||||
connectedTrack: string;
|
|
||||||
driverId: number;
|
|
||||||
trainNo: number;
|
|
||||||
driverName: string;
|
|
||||||
driverLevel: number;
|
|
||||||
currentStationName: string;
|
|
||||||
currentStationHash: string;
|
|
||||||
locoType: string;
|
|
||||||
online: boolean;
|
|
||||||
lastSeen: number;
|
|
||||||
region: string;
|
|
||||||
stockList: string[];
|
|
||||||
|
|
||||||
isTimeout: boolean;
|
|
||||||
isSupporter: boolean;
|
|
||||||
|
|
||||||
timetableData?: {
|
|
||||||
timetableId: number;
|
|
||||||
category: string;
|
|
||||||
route: string;
|
|
||||||
followingStops: TrainStop[];
|
|
||||||
TWR: boolean;
|
|
||||||
SKR: boolean;
|
|
||||||
routeDistance: number;
|
|
||||||
sceneries: string[];
|
|
||||||
sceneryNames: string[];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Filter } from '../../components/StationsView/typings';
|
import { Filter } from '../../components/StationsView/typings';
|
||||||
import { Status } from '../../typings/common';
|
import { Status } from '../../typings/common';
|
||||||
import { HeadIdsTypes } from '../data/stationHeaderNames';
|
import { HeadIdsTypes } from '../data/stationHeaderNames';
|
||||||
import Station from '../interfaces/Station';
|
import { Station } from '../../typings/common';
|
||||||
|
|
||||||
const dispatcherStatusPriority = [
|
const dispatcherStatusPriority = [
|
||||||
Status.ActiveDispatcher.UNKNOWN,
|
Status.ActiveDispatcher.UNKNOWN,
|
||||||
@@ -68,8 +68,8 @@ export const sortStations = (
|
|||||||
|
|
||||||
case 'user':
|
case 'user':
|
||||||
diff =
|
diff =
|
||||||
(b.onlineInfo ? b.onlineInfo.currentUsers : -1) -
|
(b.onlineInfo?.stationTrains ? b.onlineInfo.stationTrains.length : -1) -
|
||||||
(a.onlineInfo ? a.onlineInfo.currentUsers : -1);
|
(a.onlineInfo?.stationTrains ? a.onlineInfo.stationTrains.length : -1);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'like':
|
case 'like':
|
||||||
@@ -189,9 +189,11 @@ export const filterStations = (station: Station, filters: Filter) => {
|
|||||||
availability == 'nonPublic' || availability == 'unavailable' || availability == 'abandoned';
|
availability == 'nonPublic' || availability == 'unavailable' || availability == 'abandoned';
|
||||||
|
|
||||||
if (reqLevel + (otherAvailability ? 1 : 0) < filters['minLevel']) return false;
|
if (reqLevel + (otherAvailability ? 1 : 0) < filters['minLevel']) return false;
|
||||||
|
|
||||||
if (reqLevel + (otherAvailability ? 1 : 0) > filters['maxLevel']) 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 (
|
if (
|
||||||
filters['no-1track'] &&
|
filters['no-1track'] &&
|
||||||
(routes.singleElectrifiedNames.length != 0 || routes.singleOtherNames.length != 0)
|
(routes.singleElectrifiedNames.length != 0 || routes.singleOtherNames.length != 0)
|
||||||
@@ -227,6 +229,13 @@ export const filterStations = (station: Station, filters: Filter) => {
|
|||||||
!authors?.map((a) => a.toLocaleLowerCase()).includes(filters['authors'].toLocaleLowerCase())
|
!authors?.map((a) => a.toLocaleLowerCase()).includes(filters['authors'].toLocaleLowerCase())
|
||||||
)
|
)
|
||||||
return false;
|
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;
|
return true;
|
||||||
|
|||||||
+2
-17
@@ -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,
|
||||||
@@ -57,31 +54,19 @@ export const useApiStore = defineStore('apiStore', {
|
|||||||
// Static data
|
// Static data
|
||||||
this.fetchDonatorsData();
|
this.fetchDonatorsData();
|
||||||
this.fetchStationsGeneralInfo();
|
this.fetchStationsGeneralInfo();
|
||||||
|
|
||||||
// Active data schedueler
|
|
||||||
this.fetchActiveData();
|
|
||||||
this.setupActiveDataFetcher();
|
|
||||||
},
|
|
||||||
|
|
||||||
async setupActiveDataFetcher() {
|
|
||||||
if (this.activeDataScheduler) return;
|
|
||||||
|
|
||||||
this.activeDataScheduler = window.setInterval(() => {
|
|
||||||
this.fetchActiveData();
|
|
||||||
}, 25000);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async fetchActiveData() {
|
async fetchActiveData() {
|
||||||
if (!this.activeData) this.dataStatuses.connection = Status.Data.Loading;
|
if (!this.activeData) this.dataStatuses.connection = Status.Data.Loading;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
console.log('Fetching active data at ' + new Date().toLocaleTimeString('pl-PL'));
|
||||||
|
|
||||||
const response = await this.client!.get<API.ActiveData.Response>('api/getActiveData');
|
const response = await this.client!.get<API.ActiveData.Response>('api/getActiveData');
|
||||||
|
|
||||||
this.activeData = response.data;
|
this.activeData = response.data;
|
||||||
this.lastFetchData = new Date();
|
this.lastFetchData = new Date();
|
||||||
this.dataStatuses.connection = Status.Data.Loaded;
|
this.dataStatuses.connection = Status.Data.Loaded;
|
||||||
|
|
||||||
console.log('Fetching active data at ' + new Date().toLocaleTimeString('pl-PL'));
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.dataStatuses.connection = Status.Data.Error;
|
this.dataStatuses.connection = Status.Data.Error;
|
||||||
console.error('Ups! Wystąpił błąd podczas pobierania danych online:', error);
|
console.error('Ups! Wystąpił błąd podczas pobierania danych online:', error);
|
||||||
|
|||||||
+51
-13
@@ -1,21 +1,24 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import Train from '../scripts/interfaces/Train';
|
|
||||||
import { parseSpawns, getScheduledTrains, getStationTrains } from './utils';
|
import { parseSpawns, getScheduledTrains, getStationTrains } from './utils';
|
||||||
|
|
||||||
import { ActiveScenery, ScheduledTrain, StoreState } from './typings';
|
import {
|
||||||
|
ActiveScenery,
|
||||||
import { Status } from '../typings/common';
|
ScheduledTrain,
|
||||||
import Station from '../scripts/interfaces/Station';
|
Station,
|
||||||
|
StationRoutes,
|
||||||
|
Status,
|
||||||
|
Train
|
||||||
|
} from '../typings/common';
|
||||||
import { useApiStore } from './apiStore';
|
import { useApiStore } from './apiStore';
|
||||||
import { StationRoutes } from '../scripts/interfaces/StationRoutes';
|
import { MainStoreState } from './typings';
|
||||||
|
|
||||||
export const useMainStore = defineStore('store', {
|
export const useMainStore = defineStore('mainStore', {
|
||||||
state: () =>
|
state: () =>
|
||||||
({
|
({
|
||||||
region: { id: 'eu', value: 'PL1' },
|
region: { id: 'eu', value: 'PL1', name: 'PL1' },
|
||||||
|
|
||||||
isOffline: false,
|
isOffline: false,
|
||||||
isNewUpdate: false,
|
appUpdate: null,
|
||||||
|
|
||||||
dispatcherStatsName: '',
|
dispatcherStatsName: '',
|
||||||
dispatcherStatsStatus: Status.Data.Initialized,
|
dispatcherStatsStatus: Status.Data.Initialized,
|
||||||
@@ -26,9 +29,8 @@ export const useMainStore = defineStore('store', {
|
|||||||
|
|
||||||
chosenModalTrainId: undefined,
|
chosenModalTrainId: undefined,
|
||||||
|
|
||||||
blockScroll: false,
|
|
||||||
modalLastClickedTarget: null
|
modalLastClickedTarget: null
|
||||||
}) as StoreState,
|
}) as MainStoreState,
|
||||||
|
|
||||||
getters: {
|
getters: {
|
||||||
trainList(): Train[] {
|
trainList(): Train[] {
|
||||||
@@ -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
|
||||||
) ?? [];
|
) ?? [];
|
||||||
@@ -93,6 +96,7 @@ export const useMainStore = defineStore('store', {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// computed active sceneries
|
||||||
activeSceneryList(state): ActiveScenery[] {
|
activeSceneryList(state): ActiveScenery[] {
|
||||||
const apiStore = useApiStore();
|
const apiStore = useApiStore();
|
||||||
|
|
||||||
@@ -228,6 +232,7 @@ export const useMainStore = defineStore('store', {
|
|||||||
return allActiveSceneries;
|
return allActiveSceneries;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// computed station data
|
||||||
stationList(): Station[] {
|
stationList(): Station[] {
|
||||||
const apiStore = useApiStore();
|
const apiStore = useApiStore();
|
||||||
|
|
||||||
@@ -245,6 +250,13 @@ export const useMainStore = defineStore('store', {
|
|||||||
if (!route.isInternal) acc[routesKey].push(route.routeName);
|
if (!route.isInternal) acc[routesKey].push(route.routeName);
|
||||||
if (route.isRouteSBL) acc['sblNames'].push(route.routeName);
|
if (route.isRouteSBL) acc['sblNames'].push(route.routeName);
|
||||||
|
|
||||||
|
acc.minRouteSpeed =
|
||||||
|
acc.minRouteSpeed == 0
|
||||||
|
? route.routeSpeed
|
||||||
|
: Math.min(route.routeSpeed, acc.minRouteSpeed);
|
||||||
|
|
||||||
|
acc.maxRouteSpeed = Math.max(route.routeSpeed, acc.maxRouteSpeed);
|
||||||
|
|
||||||
acc[tracksKey].push(route);
|
acc[tracksKey].push(route);
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
@@ -256,7 +268,9 @@ export const useMainStore = defineStore('store', {
|
|||||||
double: [],
|
double: [],
|
||||||
doubleElectrifiedNames: [],
|
doubleElectrifiedNames: [],
|
||||||
doubleOtherNames: [],
|
doubleOtherNames: [],
|
||||||
sblNames: []
|
sblNames: [],
|
||||||
|
minRouteSpeed: 0,
|
||||||
|
maxRouteSpeed: 0
|
||||||
} as StationRoutes
|
} as StationRoutes
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -271,6 +285,30 @@ export const useMainStore = defineStore('store', {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
allStationInfo(): Station[] {
|
||||||
|
const onlineUnsavedStations = this.activeSceneryList
|
||||||
|
.filter(
|
||||||
|
(scenery) =>
|
||||||
|
this.stationList.findIndex((st) => st.name == scenery.name) == -1 &&
|
||||||
|
scenery.region == this.region.id
|
||||||
|
)
|
||||||
|
.map((os) => ({
|
||||||
|
name: os.name,
|
||||||
|
generalInfo: undefined,
|
||||||
|
onlineInfo: os
|
||||||
|
}));
|
||||||
|
|
||||||
|
return [
|
||||||
|
...onlineUnsavedStations,
|
||||||
|
...this.stationList.map((st) => ({
|
||||||
|
...st,
|
||||||
|
onlineInfo: this.activeSceneryList.find(
|
||||||
|
(os) => os.name == st.name && os.region == this.region.id
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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 = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -30,12 +30,6 @@ const filterInitStates: Filter = {
|
|||||||
mieszana: false,
|
mieszana: false,
|
||||||
SBL: false,
|
SBL: false,
|
||||||
PBL: false,
|
PBL: false,
|
||||||
minLevel: 0,
|
|
||||||
maxLevel: 20,
|
|
||||||
minOneWayCatenary: 0,
|
|
||||||
minOneWay: 0,
|
|
||||||
minTwoWayCatenary: 0,
|
|
||||||
minTwoWay: 0,
|
|
||||||
'include-selected': false,
|
'include-selected': false,
|
||||||
'no-1track': false,
|
'no-1track': false,
|
||||||
'no-2track': false,
|
'no-2track': false,
|
||||||
@@ -53,9 +47,19 @@ const filterInitStates: Filter = {
|
|||||||
withActiveTimetables: false,
|
withActiveTimetables: false,
|
||||||
withoutActiveTimetables: false,
|
withoutActiveTimetables: false,
|
||||||
|
|
||||||
authors: '',
|
junction: false,
|
||||||
|
nonJunction: false,
|
||||||
|
|
||||||
onlineFromHours: 0
|
maxVmax: 200,
|
||||||
|
minVmax: 0,
|
||||||
|
authors: '',
|
||||||
|
onlineFromHours: 0,
|
||||||
|
minLevel: 0,
|
||||||
|
maxLevel: 20,
|
||||||
|
minOneWayCatenary: 0,
|
||||||
|
minOneWay: 0,
|
||||||
|
minTwoWayCatenary: 0,
|
||||||
|
minTwoWay: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useStationFiltersStore = defineStore('stationFiltersStore', {
|
export const useStationFiltersStore = defineStore('stationFiltersStore', {
|
||||||
@@ -75,26 +79,8 @@ export const useStationFiltersStore = defineStore('stationFiltersStore', {
|
|||||||
|
|
||||||
filteredStationList: (state) => {
|
filteredStationList: (state) => {
|
||||||
const store = useMainStore();
|
const store = useMainStore();
|
||||||
const savedStationNames = store.stationList.map((s) => s.name);
|
|
||||||
|
|
||||||
const onlineUnsavedStations = store.activeSceneryList
|
return store.allStationInfo
|
||||||
.filter((os) => !savedStationNames.includes(os.name) && os.region == store.region.id)
|
|
||||||
.map((os) => ({
|
|
||||||
name: os.name,
|
|
||||||
generalInfo: undefined,
|
|
||||||
onlineInfo: os
|
|
||||||
}));
|
|
||||||
|
|
||||||
return [
|
|
||||||
...onlineUnsavedStations,
|
|
||||||
...store.stationList.map((station) => ({
|
|
||||||
...station,
|
|
||||||
// append to 'onlineInfo' object for filtering legacy reasons - to optimize later (hopefully)
|
|
||||||
onlineInfo: store.activeSceneryList.find(
|
|
||||||
(os) => os.name == station.name && os.region == store.region.id
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
]
|
|
||||||
.filter((station) => filterStations(station, state.filters))
|
.filter((station) => filterStations(station, state.filters))
|
||||||
.sort((a, b) => sortStations(a, b, state.sorterActive));
|
.sort((a, b) => sortStations(a, b, state.sorterActive));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { defineStore } from 'pinia';
|
||||||
|
|
||||||
|
const isTooltip = (v: any): v is TooltipType => tooltipKeys.includes(v);
|
||||||
|
|
||||||
|
export const tooltipKeys = [
|
||||||
|
'DonatorTooltip',
|
||||||
|
'BaseTooltip',
|
||||||
|
'VehiclePreviewTooltip',
|
||||||
|
'SpawnsTooltip',
|
||||||
|
'UsersTooltip'
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export type TooltipType = (typeof tooltipKeys)[number];
|
||||||
|
|
||||||
|
export const useTooltipStore = defineStore('tooltipStore', {
|
||||||
|
state: () => ({
|
||||||
|
mousePos: [0, 0],
|
||||||
|
type: null as TooltipType | null,
|
||||||
|
content: ''
|
||||||
|
}),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
show(_e: MouseEvent, type: string, value?: string) {
|
||||||
|
if (!isTooltip(type)) return;
|
||||||
|
|
||||||
|
this.type = type;
|
||||||
|
this.content = value ?? '';
|
||||||
|
},
|
||||||
|
|
||||||
|
hide() {
|
||||||
|
this.type = null;
|
||||||
|
this.content = '';
|
||||||
|
},
|
||||||
|
|
||||||
|
handle(e: MouseEvent) {
|
||||||
|
const targetEl = e
|
||||||
|
.composedPath()
|
||||||
|
.find((p) => p instanceof HTMLElement && p.getAttribute('data-tooltip-type'));
|
||||||
|
|
||||||
|
if (!targetEl || !(targetEl instanceof HTMLElement)) {
|
||||||
|
if (this.type != null) this.hide();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tooltipType = targetEl.getAttribute('data-tooltip-type');
|
||||||
|
const tooltipContent = targetEl.getAttribute('data-tooltip-content');
|
||||||
|
|
||||||
|
if (tooltipType && tooltipContent) this.show(e, tooltipType, tooltipContent);
|
||||||
|
else if (this.type != null) this.hide();
|
||||||
|
|
||||||
|
this.mousePos[0] = e.pageX;
|
||||||
|
this.mousePos[1] = e.pageY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
+4
-131
@@ -1,39 +1,19 @@
|
|||||||
import { API } from '../typings/api';
|
import { API } from '../typings/api';
|
||||||
import { Status } from '../typings/common';
|
import { Availability, StationRoutesInfo, Status } from '../typings/common';
|
||||||
|
|
||||||
export type Availability = 'default' | 'unavailable' | 'nonPublic' | 'abandoned' | 'nonDefault';
|
export interface MainStoreState {
|
||||||
|
region: { id: string; value: string; name: string };
|
||||||
export interface RegionCounters {
|
|
||||||
stationCount: number;
|
|
||||||
trainsCount: number;
|
|
||||||
timetablesCount: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface StoreState {
|
|
||||||
region: { id: string; value: string };
|
|
||||||
isOffline: boolean;
|
isOffline: boolean;
|
||||||
isNewUpdate: boolean;
|
appUpdate: { version: string; changelog: string; releaseURL: string } | null;
|
||||||
dispatcherStatsName: string;
|
dispatcherStatsName: string;
|
||||||
dispatcherStatsData?: API.DispatcherStats.Response;
|
dispatcherStatsData?: API.DispatcherStats.Response;
|
||||||
driverStatsName: string;
|
driverStatsName: string;
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StationRoutesInfo {
|
|
||||||
routeName: string;
|
|
||||||
isElectric: boolean;
|
|
||||||
isInternal: boolean;
|
|
||||||
isRouteSBL: boolean;
|
|
||||||
routeLength: number;
|
|
||||||
routeSpeed: number;
|
|
||||||
routeTracks: number;
|
|
||||||
hidden?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface StationJSONData {
|
export interface StationJSONData {
|
||||||
name: string;
|
name: string;
|
||||||
abbr: string;
|
abbr: string;
|
||||||
@@ -59,110 +39,3 @@ export interface StationJSONData {
|
|||||||
|
|
||||||
availability: Availability;
|
availability: Availability;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ActiveScenery {
|
|
||||||
name: string;
|
|
||||||
hash: string;
|
|
||||||
region: string;
|
|
||||||
maxUsers: number;
|
|
||||||
currentUsers: number;
|
|
||||||
spawns: { spawnName: string; spawnLength: number; isElectrified: boolean }[];
|
|
||||||
dispatcherName: string;
|
|
||||||
dispatcherRate: number;
|
|
||||||
dispatcherId: number;
|
|
||||||
dispatcherExp: number;
|
|
||||||
dispatcherIsSupporter: boolean;
|
|
||||||
|
|
||||||
dispatcherStatus: Status.ActiveDispatcher | number;
|
|
||||||
dispatcherTimestamp: number | null;
|
|
||||||
|
|
||||||
isOnline: boolean;
|
|
||||||
|
|
||||||
stationTrains?: StationTrain[];
|
|
||||||
scheduledTrains?: ScheduledTrain[];
|
|
||||||
|
|
||||||
scheduledTrainCount: {
|
|
||||||
all: number;
|
|
||||||
confirmed: number;
|
|
||||||
unconfirmed: number;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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 enum StopStatus {
|
|
||||||
ARRIVING = 'arriving',
|
|
||||||
DEPARTED = 'departed',
|
|
||||||
DEPARTED_AWAY = 'departed-away',
|
|
||||||
ONLINE = 'online',
|
|
||||||
STOPPED = 'stopped',
|
|
||||||
TERMINATED = 'terminated'
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TrainStop {
|
|
||||||
stopName: string;
|
|
||||||
stopNameRAW: string;
|
|
||||||
stopType: string;
|
|
||||||
stopDistance: number;
|
|
||||||
mainStop: boolean;
|
|
||||||
|
|
||||||
arrivalLine: string | null;
|
|
||||||
arrivalTimestamp: number;
|
|
||||||
arrivalRealTimestamp: number;
|
|
||||||
arrivalDelay: number;
|
|
||||||
|
|
||||||
departureLine: string | null;
|
|
||||||
departureTimestamp: number;
|
|
||||||
departureRealTimestamp: number;
|
|
||||||
departureDelay: number;
|
|
||||||
pointId: number;
|
|
||||||
|
|
||||||
comments?: string;
|
|
||||||
|
|
||||||
beginsHere: boolean;
|
|
||||||
terminatesHere: boolean;
|
|
||||||
confirmed: boolean;
|
|
||||||
stopped: boolean;
|
|
||||||
stopTime: number | null;
|
|
||||||
}
|
|
||||||
|
|||||||
+20
-11
@@ -1,12 +1,13 @@
|
|||||||
import Station from '../scripts/interfaces/Station';
|
import {
|
||||||
import Train from '../scripts/interfaces/Train';
|
TrainStop,
|
||||||
import { ScheduledTrain, StationTrain, StopStatus, TrainStop } from './typings';
|
StopStatus,
|
||||||
|
Train,
|
||||||
export function getLocoURL(locoType: string): string {
|
ScheduledTrain,
|
||||||
return `https://rj.td2.info.pl/dist/img/thumbnails/${
|
Station,
|
||||||
locoType.includes('EN') ? locoType + 'rb' : locoType
|
StationTrain,
|
||||||
}.png`;
|
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;
|
||||||
@@ -31,7 +32,7 @@ export function getStatusTimestamp(stationStatus: any): number {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseSpawns(spawnString: string | null) {
|
export function parseSpawns(spawnString: string | null): ScenerySpawn[] {
|
||||||
if (!spawnString) return [];
|
if (!spawnString) return [];
|
||||||
if (spawnString === 'NO_SPAWN') return [];
|
if (spawnString === 'NO_SPAWN') return [];
|
||||||
|
|
||||||
@@ -41,7 +42,15 @@ export function parseSpawns(spawnString: string | null) {
|
|||||||
const spawnLength = parseInt(spawnArray[2]);
|
const spawnLength = parseInt(spawnArray[2]);
|
||||||
const isElectrified = spawnArray[3] == 'True';
|
const isElectrified = spawnArray[3] == 'True';
|
||||||
|
|
||||||
return { spawnName, spawnLength, isElectrified };
|
let spawnType: ScenerySpawnType = /EZT|POS|OSOB|PAS/i.test(spawnName)
|
||||||
|
? 'passenger'
|
||||||
|
: /TOW/i.test(spawnName)
|
||||||
|
? 'freight'
|
||||||
|
: /LUZ/i.test(spawnName)
|
||||||
|
? 'loco'
|
||||||
|
: 'all';
|
||||||
|
|
||||||
|
return { spawnName, spawnLength, isElectrified, spawnType };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,3 +101,16 @@
|
|||||||
background-color: #be3728;
|
background-color: #be3728;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spawn-badge {
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
.length {
|
||||||
|
background-color: #404040;
|
||||||
|
color: #cfcfcf;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-electrified='true'] > .name {
|
||||||
|
background-color: #007599;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+23
-2
@@ -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;
|
||||||
@@ -119,8 +121,6 @@ input {
|
|||||||
height: 7px;
|
height: 7px;
|
||||||
background-color: lightgreen;
|
background-color: lightgreen;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
@@ -211,6 +211,7 @@ a.a-button {
|
|||||||
&.btn--action {
|
&.btn--action {
|
||||||
background-color: #424242;
|
background-color: #424242;
|
||||||
border-radius: 0.25em;
|
border-radius: 0.25em;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #555;
|
background-color: #555;
|
||||||
@@ -296,3 +297,23 @@ a.a-button {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Basic tooltip
|
||||||
|
[data-tooltip]:hover::after,
|
||||||
|
[data-tooltip]:focus::after {
|
||||||
|
position: absolute;
|
||||||
|
transform: translate(10px, -50%);
|
||||||
|
|
||||||
|
content: attr(data-tooltip);
|
||||||
|
color: white;
|
||||||
|
background-color: #171717;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
padding: 0.5em;
|
||||||
|
margin: 0 0.25em;
|
||||||
|
max-width: 300px;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-tooltip] {
|
||||||
|
cursor: help;
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
@@ -128,8 +128,8 @@ export namespace API {
|
|||||||
export type Response = Data[];
|
export type Response = Data[];
|
||||||
|
|
||||||
export interface Data {
|
export interface Data {
|
||||||
|
id: string;
|
||||||
trainNo: number;
|
trainNo: number;
|
||||||
|
|
||||||
mass: number;
|
mass: number;
|
||||||
length: number;
|
length: number;
|
||||||
speed: number;
|
speed: number;
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
export type Availability = 'default' | 'unavailable' | 'nonPublic' | 'abandoned' | 'nonDefault';
|
||||||
|
export type ScenerySpawnType = 'passenger' | 'freight' | 'loco' | 'all';
|
||||||
|
|
||||||
|
export enum StopStatus {
|
||||||
|
ARRIVING = 'arriving',
|
||||||
|
DEPARTED = 'departed',
|
||||||
|
DEPARTED_AWAY = 'departed-away',
|
||||||
|
ONLINE = 'online',
|
||||||
|
STOPPED = 'stopped',
|
||||||
|
TERMINATED = 'terminated'
|
||||||
|
}
|
||||||
|
|
||||||
export namespace Status {
|
export namespace Status {
|
||||||
export enum ActiveDispatcher {
|
export enum ActiveDispatcher {
|
||||||
FREE = -3,
|
FREE = -3,
|
||||||
@@ -20,3 +32,204 @@ export namespace Status {
|
|||||||
Warning = 3
|
Warning = 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RegionCounters {
|
||||||
|
stationCount: number;
|
||||||
|
trainsCount: number;
|
||||||
|
timetablesCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Train {
|
||||||
|
id: string;
|
||||||
|
trainId: string;
|
||||||
|
mass: number;
|
||||||
|
length: number;
|
||||||
|
speed: number;
|
||||||
|
signal: string;
|
||||||
|
distance: number;
|
||||||
|
connectedTrack: string;
|
||||||
|
driverId: number;
|
||||||
|
trainNo: number;
|
||||||
|
driverName: string;
|
||||||
|
driverLevel: number;
|
||||||
|
currentStationName: string;
|
||||||
|
currentStationHash: string;
|
||||||
|
locoType: string;
|
||||||
|
online: boolean;
|
||||||
|
lastSeen: number;
|
||||||
|
region: string;
|
||||||
|
stockList: string[];
|
||||||
|
|
||||||
|
isTimeout: boolean;
|
||||||
|
isSupporter: boolean;
|
||||||
|
|
||||||
|
timetableData?: {
|
||||||
|
timetableId: number;
|
||||||
|
category: string;
|
||||||
|
route: string;
|
||||||
|
followingStops: TrainStop[];
|
||||||
|
TWR: boolean;
|
||||||
|
SKR: boolean;
|
||||||
|
routeDistance: number;
|
||||||
|
sceneries: string[];
|
||||||
|
sceneryNames: string[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Station {
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
generalInfo?: {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StationRoutes {
|
||||||
|
single: StationRoutesInfo[];
|
||||||
|
double: StationRoutesInfo[];
|
||||||
|
|
||||||
|
singleElectrifiedNames: string[];
|
||||||
|
singleOtherNames: string[];
|
||||||
|
doubleElectrifiedNames: string[];
|
||||||
|
doubleOtherNames: string[];
|
||||||
|
sblNames: string[];
|
||||||
|
|
||||||
|
minRouteSpeed: number;
|
||||||
|
maxRouteSpeed: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StationRoutesInfo {
|
||||||
|
routeName: string;
|
||||||
|
isElectric: boolean;
|
||||||
|
isInternal: boolean;
|
||||||
|
isRouteSBL: boolean;
|
||||||
|
routeLength: number;
|
||||||
|
routeSpeed: number;
|
||||||
|
routeTracks: number;
|
||||||
|
hidden?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ActiveScenery {
|
||||||
|
name: string;
|
||||||
|
hash: string;
|
||||||
|
region: string;
|
||||||
|
maxUsers: number;
|
||||||
|
currentUsers: number;
|
||||||
|
spawns: ScenerySpawn[];
|
||||||
|
dispatcherName: string;
|
||||||
|
dispatcherRate: number;
|
||||||
|
dispatcherId: number;
|
||||||
|
dispatcherExp: number;
|
||||||
|
dispatcherIsSupporter: boolean;
|
||||||
|
dispatcherStatus: Status.ActiveDispatcher | number;
|
||||||
|
dispatcherTimestamp: number | null;
|
||||||
|
isOnline: boolean;
|
||||||
|
stationTrains?: StationTrain[];
|
||||||
|
scheduledTrains?: ScheduledTrain[];
|
||||||
|
scheduledTrainCount: {
|
||||||
|
all: number;
|
||||||
|
confirmed: number;
|
||||||
|
unconfirmed: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScenerySpawn {
|
||||||
|
spawnName: string;
|
||||||
|
spawnLength: number;
|
||||||
|
isElectrified: boolean;
|
||||||
|
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 {
|
||||||
|
stopName: string;
|
||||||
|
stopNameRAW: string;
|
||||||
|
stopType: string;
|
||||||
|
stopDistance: number;
|
||||||
|
mainStop: boolean;
|
||||||
|
|
||||||
|
arrivalLine: string | null;
|
||||||
|
arrivalTimestamp: number;
|
||||||
|
arrivalRealTimestamp: number;
|
||||||
|
arrivalDelay: number;
|
||||||
|
|
||||||
|
departureLine: string | null;
|
||||||
|
departureTimestamp: number;
|
||||||
|
departureRealTimestamp: number;
|
||||||
|
departureDelay: number;
|
||||||
|
pointId: number;
|
||||||
|
|
||||||
|
comments?: string;
|
||||||
|
|
||||||
|
beginsHere: boolean;
|
||||||
|
terminatesHere: boolean;
|
||||||
|
confirmed: boolean;
|
||||||
|
stopped: boolean;
|
||||||
|
stopTime: number | null;
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,8 +17,9 @@
|
|||||||
<JournalStats :statsButtons="statsButtons" />
|
<JournalStats :statsButtons="statsButtons" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="journal_refreshed-date" v-if="dataRefreshedAt">
|
<div class="journal_refreshed-date">
|
||||||
{{ $t('journal.data-refreshed-at') }}: {{ dataRefreshedAt.toLocaleString($i18n.locale) }}
|
{{ $t('journal.data-refreshed-at') }}:
|
||||||
|
{{ dataRefreshedAt?.toLocaleString($i18n.locale) ?? '---' }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="list_wrapper" @scroll="handleScroll">
|
<div class="list_wrapper" @scroll="handleScroll">
|
||||||
|
|||||||
@@ -17,8 +17,9 @@
|
|||||||
<JournalStats :statsButtons="statsButtons" />
|
<JournalStats :statsButtons="statsButtons" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="journal_refreshed-date" v-if="dataRefreshedAt">
|
<div class="journal_refreshed-date">
|
||||||
{{ $t('journal.data-refreshed-at') }}: {{ dataRefreshedAt.toLocaleString($i18n.locale) }}
|
{{ $t('journal.data-refreshed-at') }}:
|
||||||
|
{{ dataRefreshedAt?.toLocaleString($i18n.locale) ?? '---' }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="list_wrapper" @scroll="handleScroll">
|
<div class="list_wrapper" @scroll="handleScroll">
|
||||||
|
|||||||
+11
-40
@@ -19,8 +19,9 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Donation :isModalOpen="isDonationModalOpen" @toggleModal="toggleDonationModal" />
|
<DonationModal :isModalOpen="isDonationModalOpen" @toggleModal="toggleDonationModal" />
|
||||||
<StationTable :stations="computedStationList" @toggleDonationModal="toggleDonationModal" />
|
<StationTable @toggleDonationModal="toggleDonationModal" />
|
||||||
|
<StationStats />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
@@ -31,36 +32,29 @@ 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 { useStationFiltersStore } from '../store/stationFiltersStore';
|
||||||
import { useMainStore } from '../store/mainStore';
|
import { useMainStore } from '../store/mainStore';
|
||||||
import Donation from '../components/Global/Donation.vue';
|
import DonationModal from '../components/Global/DonationModal.vue';
|
||||||
|
import StationStats from '../components/StationsView/StationStats.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
StationTable,
|
StationTable,
|
||||||
StationFilterCard,
|
StationFilterCard,
|
||||||
Donation
|
StationStats,
|
||||||
|
DonationModal
|
||||||
},
|
},
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
filterCardOpen: false,
|
filterCardOpen: false,
|
||||||
modalHidden: true,
|
isDonationModalOpen: false,
|
||||||
STORAGE_KEY: 'options_saved',
|
|
||||||
focusedStationName: '',
|
|
||||||
filterStore: useStationFiltersStore(),
|
|
||||||
store: useMainStore(),
|
|
||||||
|
|
||||||
isDonationModalOpen: false
|
filterStore: useStationFiltersStore(),
|
||||||
|
store: useMainStore()
|
||||||
}),
|
}),
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.filterStore.setupFilters();
|
this.filterStore.setupFilters();
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
|
||||||
computedStationList() {
|
|
||||||
return this.filterStore.filteredStationList;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
toggleDonationModal(value: boolean) {
|
toggleDonationModal(value: boolean) {
|
||||||
this.isDonationModalOpen = value;
|
this.isDonationModalOpen = value;
|
||||||
@@ -73,30 +67,6 @@ export default defineComponent({
|
|||||||
@import '../styles/variables.scss';
|
@import '../styles/variables.scss';
|
||||||
@import '../styles/responsive.scss';
|
@import '../styles/responsive.scss';
|
||||||
|
|
||||||
@keyframes blinkAnim {
|
|
||||||
0%,
|
|
||||||
100% {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
50% {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.indicator-anim {
|
|
||||||
&-enter-active,
|
|
||||||
&-leave-active {
|
|
||||||
transition: all 0.25s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-enter,
|
|
||||||
&-leave-to {
|
|
||||||
transform: translateY(100%);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.stations-view {
|
.stations-view {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -114,6 +84,7 @@ export default defineComponent({
|
|||||||
.stations-options {
|
.stations-options {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
flex-wrap: wrap;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
|
|
||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
|
|||||||
@@ -20,11 +20,11 @@ import { computed, ComputedRef, defineComponent, provide, reactive, ref, watch }
|
|||||||
import TrainOptions from '../components/TrainsView/TrainOptions.vue';
|
import TrainOptions from '../components/TrainsView/TrainOptions.vue';
|
||||||
import TrainTable from '../components/TrainsView/TrainTable.vue';
|
import TrainTable from '../components/TrainsView/TrainTable.vue';
|
||||||
import modalTrainMixin from '../mixins/modalTrainMixin';
|
import modalTrainMixin from '../mixins/modalTrainMixin';
|
||||||
import Train from '../scripts/interfaces/Train';
|
|
||||||
import { useMainStore } from '../store/mainStore';
|
import { useMainStore } from '../store/mainStore';
|
||||||
import { TrainFilter, trainFilters } from '../components/TrainsView/typings';
|
import { TrainFilter, trainFilters } from '../components/TrainsView/typings';
|
||||||
import { filteredTrainList } from '../managers/trainFilterManager';
|
import { filteredTrainList } from '../managers/trainFilterManager';
|
||||||
import TrainStats from '../components/TrainsView/TrainStats.vue';
|
import TrainStats from '../components/TrainsView/TrainStats.vue';
|
||||||
|
import { Train } from '../typings/common';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
+18
-23
@@ -12,44 +12,30 @@ export default defineConfig({
|
|||||||
vue(),
|
vue(),
|
||||||
VitePWA({
|
VitePWA({
|
||||||
registerType: 'autoUpdate',
|
registerType: 'autoUpdate',
|
||||||
includeAssets: ['/images/*.png', '/fonts/*.woff', '/fonts/*.woff2'],
|
includeAssets: ['/images/*.{png,svg,jpg}', '/fonts/*.{woff,woff2}'],
|
||||||
|
|
||||||
workbox: {
|
workbox: {
|
||||||
disableDevLogs: true,
|
disableDevLogs: true,
|
||||||
globPatterns: ['**/*.{js,css,html,png,svg,jpg}'],
|
globPatterns: ['**/*.{js,css,html,png,svg,jpg}'],
|
||||||
|
cleanupOutdatedCaches: true,
|
||||||
runtimeCaching: [
|
runtimeCaching: [
|
||||||
{
|
{
|
||||||
urlPattern: new RegExp('^https://stacjownik.spythere.eu/api/getSceneries', 'i'),
|
urlPattern: /^https:\/\/stacjownik.spythere.eu\/api\/getSceneries/i,
|
||||||
handler: 'NetworkFirst',
|
handler: 'StaleWhileRevalidate',
|
||||||
options: {
|
options: {
|
||||||
cacheName: 'sceneries-cache',
|
cacheName: 'spythere-sceneries-cache',
|
||||||
cacheableResponse: {
|
cacheableResponse: {
|
||||||
statuses: [0, 200]
|
statuses: [0, 200]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
urlPattern: new RegExp('^https://raw.githubusercontent.com/Spythere/api/*', 'i'),
|
urlPattern: /^https:\/\/static.spythere.eu\/.*/i,
|
||||||
handler: 'NetworkFirst',
|
|
||||||
options: {
|
|
||||||
cacheName: 'github-api-cache',
|
|
||||||
|
|
||||||
cacheableResponse: {
|
|
||||||
statuses: [0, 200]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
urlPattern: /^https:\/\/rj.td2.info.pl\/dist\/img\/thumbnails\/.*/i,
|
|
||||||
handler: 'CacheFirst',
|
handler: 'CacheFirst',
|
||||||
options: {
|
options: {
|
||||||
cacheName: 'images-cache',
|
cacheName: 'spythere-static-cache',
|
||||||
expiration: {
|
|
||||||
maxEntries: 100,
|
|
||||||
maxAgeSeconds: 60 * 60 * 24 * 7 // <== 7 days
|
|
||||||
},
|
|
||||||
cacheableResponse: {
|
cacheableResponse: {
|
||||||
statuses: [0, 200, 404]
|
statuses: [0, 200]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,5 +46,14 @@ export default defineConfig({
|
|||||||
suppressWarnings: true
|
suppressWarnings: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
]
|
],
|
||||||
|
build: {
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
entryFileNames: 'app-[name].js',
|
||||||
|
assetFileNames: 'app-[name].css',
|
||||||
|
chunkFileNames: 'chunk-[name].js'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user